summary refs log tree commit diff
path: root/vendor/maunium.net/go/mautrix/crypto
diff options
context:
space:
mode:
authorEmile <git@emile.space>2024-10-25 15:55:50 +0200
committerEmile <git@emile.space>2024-10-25 15:55:50 +0200
commitc90f36e3dd179d2de96f4f5fe38d8dc9a9de6dfe (patch)
tree89e9afb41c5bf76f48cfb09305a2d3db8d302b06 /vendor/maunium.net/go/mautrix/crypto
parent98bbb0f559a8883bc47bae80607dbe326a448e61 (diff)
vendor HEAD main
Diffstat (limited to 'vendor/maunium.net/go/mautrix/crypto')
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/aescbc/aes_cbc.go60
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/aescbc/errors.go15
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/attachment/attachments.go300
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/backup/encryptedsessiondata.go131
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/backup/ephemeralkey.go41
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/backup/megolmbackup.go39
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/backup/megolmbackupkey.go34
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/canonicaljson/README.md6
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/canonicaljson/json.go257
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/ed25519/ed25519.go302
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/goolm/crypto/curve25519.go186
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/goolm/crypto/doc.go2
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/goolm/crypto/ed25519.go184
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/goolm/crypto/hmac.go29
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/goolm/crypto/one_time_key.go95
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/goolm/libolmpickle/pickle.go41
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/goolm/libolmpickle/unpickle.go53
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/olm/README.md4
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/olm/account.go113
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/olm/errors.go60
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/olm/inboundgroupsession.go80
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/olm/olm.go20
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/olm/outboundgroupsession.go57
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/olm/pk.go57
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/olm/session.go83
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/pkcs7/pkcs7.go30
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/signatures/signatures.go94
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/utils/utils.go132
28 files changed, 2505 insertions, 0 deletions
diff --git a/vendor/maunium.net/go/mautrix/crypto/aescbc/aes_cbc.go b/vendor/maunium.net/go/mautrix/crypto/aescbc/aes_cbc.go
new file mode 100644
index 0000000..d69a5f4
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/aescbc/aes_cbc.go
@@ -0,0 +1,60 @@
+// Copyright (c) 2024 Sumner Evans
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package aescbc
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+
+	"maunium.net/go/mautrix/crypto/pkcs7"
+)
+
+// Encrypt encrypts the plaintext with the key and IV. The IV length must be
+// equal to the AES block size.
+//
+// This function might mutate the plaintext.
+func Encrypt(key, iv, plaintext []byte) ([]byte, error) {
+	if len(key) == 0 {
+		return nil, ErrNoKeyProvided
+	}
+	if len(iv) != aes.BlockSize {
+		return nil, ErrIVNotBlockSize
+	}
+	plaintext = pkcs7.Pad(plaintext, aes.BlockSize)
+
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, err
+	}
+
+	cipher.NewCBCEncrypter(block, iv).CryptBlocks(plaintext, plaintext)
+	return plaintext, nil
+}
+
+// Decrypt decrypts the ciphertext with the key and IV. The IV length must be
+// equal to the block size.
+//
+// This function mutates the ciphertext.
+func Decrypt(key, iv, ciphertext []byte) ([]byte, error) {
+	if len(key) == 0 {
+		return nil, ErrNoKeyProvided
+	}
+	if len(iv) != aes.BlockSize {
+		return nil, ErrIVNotBlockSize
+	}
+
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, err
+	}
+	if len(ciphertext) < aes.BlockSize {
+		return nil, ErrNotMultipleBlockSize
+	}
+
+	cipher.NewCBCDecrypter(block, iv).CryptBlocks(ciphertext, ciphertext)
+	return pkcs7.Unpad(ciphertext), nil
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/aescbc/errors.go b/vendor/maunium.net/go/mautrix/crypto/aescbc/errors.go
new file mode 100644
index 0000000..f3d2d7c
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/aescbc/errors.go
@@ -0,0 +1,15 @@
+// Copyright (c) 2024 Sumner Evans
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package aescbc
+
+import "errors"
+
+var (
+	ErrNoKeyProvided        = errors.New("no key")
+	ErrIVNotBlockSize       = errors.New("IV length does not match AES block size")
+	ErrNotMultipleBlockSize = errors.New("ciphertext length is not a multiple of the AES block size")
+)
diff --git a/vendor/maunium.net/go/mautrix/crypto/attachment/attachments.go b/vendor/maunium.net/go/mautrix/crypto/attachment/attachments.go
new file mode 100644
index 0000000..cfa1c3e
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/attachment/attachments.go
@@ -0,0 +1,300 @@
+// Copyright (c) 2022 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package attachment
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/sha256"
+	"encoding/base64"
+	"errors"
+	"fmt"
+	"hash"
+	"io"
+
+	"maunium.net/go/mautrix/crypto/utils"
+)
+
+var (
+	HashMismatch         = errors.New("mismatching SHA-256 digest")
+	UnsupportedVersion   = errors.New("unsupported Matrix file encryption version")
+	UnsupportedAlgorithm = errors.New("unsupported JWK encryption algorithm")
+	InvalidKey           = errors.New("failed to decode key")
+	InvalidInitVector    = errors.New("failed to decode initialization vector")
+	InvalidHash          = errors.New("failed to decode SHA-256 hash")
+	ReaderClosed         = errors.New("encrypting reader was already closed")
+)
+
+var (
+	keyBase64Length  = base64.RawURLEncoding.EncodedLen(utils.AESCTRKeyLength)
+	ivBase64Length   = base64.RawStdEncoding.EncodedLen(utils.AESCTRIVLength)
+	hashBase64Length = base64.RawStdEncoding.EncodedLen(utils.SHAHashLength)
+)
+
+type JSONWebKey struct {
+	Key         string   `json:"k"`
+	Algorithm   string   `json:"alg"`
+	Extractable bool     `json:"ext"`
+	KeyType     string   `json:"kty"`
+	KeyOps      []string `json:"key_ops"`
+}
+
+type EncryptedFileHashes struct {
+	SHA256 string `json:"sha256"`
+}
+
+type decodedKeys struct {
+	key [utils.AESCTRKeyLength]byte
+	iv  [utils.AESCTRIVLength]byte
+
+	sha256 [utils.SHAHashLength]byte
+}
+
+type EncryptedFile struct {
+	Key        JSONWebKey          `json:"key"`
+	InitVector string              `json:"iv"`
+	Hashes     EncryptedFileHashes `json:"hashes"`
+	Version    string              `json:"v"`
+
+	decoded *decodedKeys
+}
+
+func NewEncryptedFile() *EncryptedFile {
+	key, iv := utils.GenAttachmentA256CTR()
+	return &EncryptedFile{
+		Key: JSONWebKey{
+			Key:         base64.RawURLEncoding.EncodeToString(key[:]),
+			Algorithm:   "A256CTR",
+			Extractable: true,
+			KeyType:     "oct",
+			KeyOps:      []string{"encrypt", "decrypt"},
+		},
+		InitVector: base64.RawStdEncoding.EncodeToString(iv[:]),
+		Version:    "v2",
+
+		decoded: &decodedKeys{key: key, iv: iv},
+	}
+}
+
+func (ef *EncryptedFile) decodeKeys(includeHash bool) error {
+	if ef.decoded != nil {
+		return nil
+	} else if len(ef.Key.Key) != keyBase64Length {
+		return InvalidKey
+	} else if len(ef.InitVector) != ivBase64Length {
+		return InvalidInitVector
+	} else if includeHash && len(ef.Hashes.SHA256) != hashBase64Length {
+		return InvalidHash
+	}
+	ef.decoded = &decodedKeys{}
+	_, err := base64.RawURLEncoding.Decode(ef.decoded.key[:], []byte(ef.Key.Key))
+	if err != nil {
+		return InvalidKey
+	}
+	_, err = base64.RawStdEncoding.Decode(ef.decoded.iv[:], []byte(ef.InitVector))
+	if err != nil {
+		return InvalidInitVector
+	}
+	if includeHash {
+		_, err = base64.RawStdEncoding.Decode(ef.decoded.sha256[:], []byte(ef.Hashes.SHA256))
+		if err != nil {
+			return InvalidHash
+		}
+	}
+	return nil
+}
+
+// Encrypt encrypts the given data, updates the SHA256 hash in the EncryptedFile struct and returns the ciphertext.
+//
+// Deprecated: this makes a copy for the ciphertext, which means 2x memory usage. EncryptInPlace is recommended.
+func (ef *EncryptedFile) Encrypt(plaintext []byte) []byte {
+	ciphertext := make([]byte, len(plaintext))
+	copy(ciphertext, plaintext)
+	ef.EncryptInPlace(ciphertext)
+	return ciphertext
+}
+
+// EncryptInPlace encrypts the given data in-place (i.e. the provided data is overridden with the ciphertext)
+// and updates the SHA256 hash in the EncryptedFile struct.
+func (ef *EncryptedFile) EncryptInPlace(data []byte) {
+	ef.decodeKeys(false)
+	utils.XorA256CTR(data, ef.decoded.key, ef.decoded.iv)
+	checksum := sha256.Sum256(data)
+	ef.Hashes.SHA256 = base64.RawStdEncoding.EncodeToString(checksum[:])
+}
+
+type ReadWriterAt interface {
+	io.WriterAt
+	io.Reader
+}
+
+// EncryptFile encrypts the given file in-place and updates the SHA256 hash in the EncryptedFile struct.
+func (ef *EncryptedFile) EncryptFile(file ReadWriterAt) error {
+	err := ef.decodeKeys(false)
+	if err != nil {
+		return err
+	}
+	block, _ := aes.NewCipher(ef.decoded.key[:])
+	stream := cipher.NewCTR(block, ef.decoded.iv[:])
+	hasher := sha256.New()
+	buf := make([]byte, 32*1024)
+	var writePtr int64
+	var n int
+	for {
+		n, err = file.Read(buf)
+		if err != nil && !errors.Is(err, io.EOF) {
+			return err
+		}
+		if n == 0 {
+			break
+		}
+		stream.XORKeyStream(buf[:n], buf[:n])
+		_, err = file.WriteAt(buf[:n], writePtr)
+		if err != nil {
+			return err
+		}
+		writePtr += int64(n)
+		hasher.Write(buf[:n])
+	}
+	ef.Hashes.SHA256 = base64.RawStdEncoding.EncodeToString(hasher.Sum(nil))
+	return nil
+}
+
+type encryptingReader struct {
+	stream cipher.Stream
+	hash   hash.Hash
+	source io.Reader
+	file   *EncryptedFile
+	closed bool
+
+	isDecrypting bool
+}
+
+var _ io.ReadSeekCloser = (*encryptingReader)(nil)
+
+func (r *encryptingReader) Seek(offset int64, whence int) (int64, error) {
+	if r.closed {
+		return 0, ReaderClosed
+	}
+	if offset != 0 || whence != io.SeekStart {
+		return 0, fmt.Errorf("attachments.EncryptStream: only seeking to the beginning is supported")
+	}
+	seeker, ok := r.source.(io.ReadSeeker)
+	if !ok {
+		return 0, fmt.Errorf("attachments.EncryptStream: source reader (%T) is not an io.ReadSeeker", r.source)
+	}
+	n, err := seeker.Seek(offset, whence)
+	if err != nil {
+		return 0, err
+	}
+	block, _ := aes.NewCipher(r.file.decoded.key[:])
+	r.stream = cipher.NewCTR(block, r.file.decoded.iv[:])
+	r.hash.Reset()
+	return n, nil
+}
+
+func (r *encryptingReader) Read(dst []byte) (n int, err error) {
+	if r.closed {
+		return 0, ReaderClosed
+	} else if r.isDecrypting && r.file.decoded == nil {
+		if err = r.file.PrepareForDecryption(); err != nil {
+			return
+		}
+	}
+	n, err = r.source.Read(dst)
+	r.stream.XORKeyStream(dst[:n], dst[:n])
+	r.hash.Write(dst[:n])
+	return
+}
+
+func (r *encryptingReader) Close() (err error) {
+	closer, ok := r.source.(io.ReadCloser)
+	if ok {
+		err = closer.Close()
+	}
+	if r.isDecrypting {
+		var downloadedChecksum [utils.SHAHashLength]byte
+		r.hash.Sum(downloadedChecksum[:])
+		if downloadedChecksum != r.file.decoded.sha256 {
+			return HashMismatch
+		}
+	} else {
+		r.file.Hashes.SHA256 = base64.RawStdEncoding.EncodeToString(r.hash.Sum(nil))
+	}
+	r.closed = true
+	return
+}
+
+// EncryptStream wraps the given io.Reader in order to encrypt the data.
+//
+// The Close() method of the returned io.ReadCloser must be called for the SHA256 hash
+// in the EncryptedFile struct to be updated. The metadata is not valid before the hash
+// is filled.
+func (ef *EncryptedFile) EncryptStream(reader io.Reader) io.ReadSeekCloser {
+	ef.decodeKeys(false)
+	block, _ := aes.NewCipher(ef.decoded.key[:])
+	return &encryptingReader{
+		stream: cipher.NewCTR(block, ef.decoded.iv[:]),
+		hash:   sha256.New(),
+		source: reader,
+		file:   ef,
+	}
+}
+
+// Decrypt decrypts the given data and returns the plaintext.
+//
+// Deprecated: this makes a copy for the plaintext data, which means 2x memory usage. DecryptInPlace is recommended.
+func (ef *EncryptedFile) Decrypt(ciphertext []byte) ([]byte, error) {
+	plaintext := make([]byte, len(ciphertext))
+	copy(plaintext, ciphertext)
+	return plaintext, ef.DecryptInPlace(plaintext)
+}
+
+// PrepareForDecryption checks that the version and algorithm are supported and decodes the base64 keys
+//
+// DecryptStream will call this with the first Read() call if this hasn't been called manually.
+//
+// DecryptInPlace will always call this automatically, so calling this manually is not necessary when using that function.
+func (ef *EncryptedFile) PrepareForDecryption() error {
+	if ef.Version != "v2" {
+		return UnsupportedVersion
+	} else if ef.Key.Algorithm != "A256CTR" {
+		return UnsupportedAlgorithm
+	} else if err := ef.decodeKeys(true); err != nil {
+		return err
+	}
+	return nil
+}
+
+// DecryptInPlace decrypts the given data in-place (i.e. the provided data is overridden with the plaintext).
+func (ef *EncryptedFile) DecryptInPlace(data []byte) error {
+	if err := ef.PrepareForDecryption(); err != nil {
+		return err
+	} else if ef.decoded.sha256 != sha256.Sum256(data) {
+		return HashMismatch
+	} else {
+		utils.XorA256CTR(data, ef.decoded.key, ef.decoded.iv)
+		return nil
+	}
+}
+
+// DecryptStream wraps the given io.Reader in order to decrypt the data.
+//
+// The first Read call will check the algorithm and decode keys, so it might return an error before actually reading anything.
+// If you want to validate the file before opening the stream, call PrepareForDecryption manually and check for errors.
+//
+// The Close call will validate the hash and return an error if it doesn't match.
+// In this case, the written data should be considered compromised and should not be used further.
+func (ef *EncryptedFile) DecryptStream(reader io.Reader) io.ReadSeekCloser {
+	block, _ := aes.NewCipher(ef.decoded.key[:])
+	return &encryptingReader{
+		stream: cipher.NewCTR(block, ef.decoded.iv[:]),
+		hash:   sha256.New(),
+		source: reader,
+		file:   ef,
+	}
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/backup/encryptedsessiondata.go b/vendor/maunium.net/go/mautrix/crypto/backup/encryptedsessiondata.go
new file mode 100644
index 0000000..ec551db
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/backup/encryptedsessiondata.go
@@ -0,0 +1,131 @@
+// Copyright (c) 2024 Sumner Evans
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package backup
+
+import (
+	"bytes"
+	"crypto/ecdh"
+	"crypto/hmac"
+	"crypto/rand"
+	"crypto/sha256"
+	"encoding/json"
+	"errors"
+
+	"go.mau.fi/util/jsonbytes"
+	"golang.org/x/crypto/hkdf"
+
+	"maunium.net/go/mautrix/crypto/aescbc"
+)
+
+var ErrInvalidMAC = errors.New("invalid MAC")
+
+// EncryptedSessionData is the encrypted session_data field of a key backup as
+// defined in [Section 11.12.3.2.2 of the Spec].
+//
+// The type parameter T represents the format of the session data contained in
+// the encrypted payload.
+//
+// [Section 11.12.3.2.2 of the Spec]: https://spec.matrix.org/v1.9/client-server-api/#backup-algorithm-mmegolm_backupv1curve25519-aes-sha2
+type EncryptedSessionData[T any] struct {
+	Ciphertext jsonbytes.UnpaddedBytes `json:"ciphertext"`
+	Ephemeral  EphemeralKey            `json:"ephemeral"`
+	MAC        jsonbytes.UnpaddedBytes `json:"mac"`
+}
+
+func calculateEncryptionParameters(sharedSecret []byte) (key, macKey, iv []byte, err error) {
+	hkdfReader := hkdf.New(sha256.New, sharedSecret, nil, nil)
+	encryptionParams := make([]byte, 80)
+	_, err = hkdfReader.Read(encryptionParams)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	return encryptionParams[:32], encryptionParams[32:64], encryptionParams[64:], nil
+}
+
+// calculateCompatMAC calculates the MAC as described in step 5 of according to
+// [Section 11.12.3.2.2] of the Spec which was updated in spec version 1.10 to
+// reflect what is actually implemented in libolm and Vodozemac.
+//
+// Libolm implemented the MAC functionality incorrectly. The MAC is computed
+// over an empty string rather than the ciphertext. Vodozemac implemented this
+// functionality the same way as libolm for compatibility. In version 1.10 of
+// the spec, the description of step 5 was updated to reflect the de-facto
+// standard of libolm and Vodozemac.
+//
+// [Section 11.12.3.2.2]: https://spec.matrix.org/v1.11/client-server-api/#backup-algorithm-mmegolm_backupv1curve25519-aes-sha2
+func calculateCompatMAC(macKey []byte) []byte {
+	hash := hmac.New(sha256.New, macKey)
+	return hash.Sum(nil)[:8]
+}
+
+// EncryptSessionData encrypts the given session data with the given recovery
+// key as defined in [Section 11.12.3.2.2 of the Spec].
+//
+// [Section 11.12.3.2.2 of the Spec]: https://spec.matrix.org/v1.9/client-server-api/#backup-algorithm-mmegolm_backupv1curve25519-aes-sha2
+func EncryptSessionData[T any](backupKey *MegolmBackupKey, sessionData T) (*EncryptedSessionData[T], error) {
+	sessionJSON, err := json.Marshal(sessionData)
+	if err != nil {
+		return nil, err
+	}
+
+	ephemeralKey, err := ecdh.X25519().GenerateKey(rand.Reader)
+	if err != nil {
+		return nil, err
+	}
+
+	sharedSecret, err := ephemeralKey.ECDH(backupKey.PublicKey())
+	if err != nil {
+		return nil, err
+	}
+
+	key, macKey, iv, err := calculateEncryptionParameters(sharedSecret)
+	if err != nil {
+		return nil, err
+	}
+
+	ciphertext, err := aescbc.Encrypt(key, iv, sessionJSON)
+	if err != nil {
+		return nil, err
+	}
+
+	return &EncryptedSessionData[T]{
+		Ciphertext: ciphertext,
+		Ephemeral:  EphemeralKey{ephemeralKey.PublicKey()},
+		MAC:        calculateCompatMAC(macKey),
+	}, nil
+}
+
+// Decrypt decrypts the [EncryptedSessionData] into a *T using the recovery key
+// by reversing the process described in [Section 11.12.3.2.2 of the Spec].
+//
+// [Section 11.12.3.2.2 of the Spec]: https://spec.matrix.org/v1.9/client-server-api/#backup-algorithm-mmegolm_backupv1curve25519-aes-sha2
+func (esd *EncryptedSessionData[T]) Decrypt(backupKey *MegolmBackupKey) (*T, error) {
+	sharedSecret, err := backupKey.ECDH(esd.Ephemeral.PublicKey)
+	if err != nil {
+		return nil, err
+	}
+
+	key, macKey, iv, err := calculateEncryptionParameters(sharedSecret)
+	if err != nil {
+		return nil, err
+	}
+
+	// Verify the MAC before decrypting.
+	if !bytes.Equal(calculateCompatMAC(macKey), esd.MAC) {
+		return nil, ErrInvalidMAC
+	}
+
+	plaintext, err := aescbc.Decrypt(key, iv, esd.Ciphertext)
+	if err != nil {
+		return nil, err
+	}
+
+	var sessionData T
+	err = json.Unmarshal(plaintext, &sessionData)
+	return &sessionData, err
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/backup/ephemeralkey.go b/vendor/maunium.net/go/mautrix/crypto/backup/ephemeralkey.go
new file mode 100644
index 0000000..e481e7a
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/backup/ephemeralkey.go
@@ -0,0 +1,41 @@
+// Copyright (c) 2024 Sumner Evans
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package backup
+
+import (
+	"crypto/ecdh"
+	"encoding/base64"
+	"encoding/json"
+)
+
+// EphemeralKey is a wrapper around an ECDH X25519 public key that implements
+// JSON marshalling and unmarshalling.
+type EphemeralKey struct {
+	*ecdh.PublicKey
+}
+
+func (k *EphemeralKey) MarshalJSON() ([]byte, error) {
+	if k == nil || k.PublicKey == nil {
+		return json.Marshal(nil)
+	}
+	return json.Marshal(base64.RawStdEncoding.EncodeToString(k.Bytes()))
+}
+
+func (k *EphemeralKey) UnmarshalJSON(data []byte) error {
+	var keyStr string
+	err := json.Unmarshal(data, &keyStr)
+	if err != nil {
+		return err
+	}
+
+	keyBytes, err := base64.RawStdEncoding.DecodeString(keyStr)
+	if err != nil {
+		return err
+	}
+	k.PublicKey, err = ecdh.X25519().NewPublicKey(keyBytes)
+	return err
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/backup/megolmbackup.go b/vendor/maunium.net/go/mautrix/crypto/backup/megolmbackup.go
new file mode 100644
index 0000000..71b8e88
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/backup/megolmbackup.go
@@ -0,0 +1,39 @@
+// Copyright (c) 2024 Sumner Evans
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package backup
+
+import (
+	"maunium.net/go/mautrix/crypto/signatures"
+	"maunium.net/go/mautrix/id"
+)
+
+// MegolmAuthData is the auth_data when the key backup is created with
+// the [id.KeyBackupAlgorithmMegolmBackupV1] algorithm as defined in
+// [Section 11.12.3.2.2 of the Spec].
+//
+// [Section 11.12.3.2.2 of the Spec]: https://spec.matrix.org/v1.9/client-server-api/#backup-algorithm-mmegolm_backupv1curve25519-aes-sha2
+type MegolmAuthData struct {
+	PublicKey  id.Ed25519            `json:"public_key"`
+	Signatures signatures.Signatures `json:"signatures"`
+}
+
+type SenderClaimedKeys struct {
+	Ed25519 id.Ed25519 `json:"ed25519"`
+}
+
+// MegolmSessionData is the decrypted session_data when the key backup is created
+// with the [id.KeyBackupAlgorithmMegolmBackupV1] algorithm as defined in
+// [Section 11.12.3.2.2 of the Spec].
+//
+// [Section 11.12.3.2.2 of the Spec]: https://spec.matrix.org/v1.9/client-server-api/#backup-algorithm-mmegolm_backupv1curve25519-aes-sha2
+type MegolmSessionData struct {
+	Algorithm          id.Algorithm      `json:"algorithm"`
+	ForwardingKeyChain []string          `json:"forwarding_curve25519_key_chain"`
+	SenderClaimedKeys  SenderClaimedKeys `json:"sender_claimed_keys"`
+	SenderKey          id.SenderKey      `json:"sender_key"`
+	SessionKey         string            `json:"session_key"`
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/backup/megolmbackupkey.go b/vendor/maunium.net/go/mautrix/crypto/backup/megolmbackupkey.go
new file mode 100644
index 0000000..8f23d10
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/backup/megolmbackupkey.go
@@ -0,0 +1,34 @@
+// Copyright (c) 2024 Sumner Evans
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package backup
+
+import (
+	"crypto/ecdh"
+	"crypto/rand"
+)
+
+// MegolmBackupKey is a wrapper around an ECDH X25519 private key that is used
+// to decrypt a megolm key backup.
+type MegolmBackupKey struct {
+	*ecdh.PrivateKey
+}
+
+func NewMegolmBackupKey() (*MegolmBackupKey, error) {
+	key, err := ecdh.X25519().GenerateKey(rand.Reader)
+	if err != nil {
+		return nil, err
+	}
+	return &MegolmBackupKey{key}, nil
+}
+
+func MegolmBackupKeyFromBytes(bytes []byte) (*MegolmBackupKey, error) {
+	key, err := ecdh.X25519().NewPrivateKey(bytes)
+	if err != nil {
+		return nil, err
+	}
+	return &MegolmBackupKey{key}, nil
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/canonicaljson/README.md b/vendor/maunium.net/go/mautrix/crypto/canonicaljson/README.md
new file mode 100644
index 0000000..da9d71f
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/canonicaljson/README.md
@@ -0,0 +1,6 @@
+# canonicaljson
+This is a Go package to produce Matrix [Canonical JSON](https://matrix.org/docs/spec/appendices#canonical-json).
+It is essentially just [json.go](https://github.com/matrix-org/gomatrixserverlib/blob/master/json.go)
+from gomatrixserverlib without all the other files that are completely useless for non-server use cases.
+
+The original project is licensed under the Apache 2.0 license.
diff --git a/vendor/maunium.net/go/mautrix/crypto/canonicaljson/json.go b/vendor/maunium.net/go/mautrix/crypto/canonicaljson/json.go
new file mode 100644
index 0000000..fd296e6
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/canonicaljson/json.go
@@ -0,0 +1,257 @@
+/* Copyright 2016-2017 Vector Creations Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package canonicaljson
+
+import (
+	"encoding/binary"
+	"fmt"
+	"sort"
+	"unicode/utf8"
+
+	"github.com/tidwall/gjson"
+)
+
+// CanonicalJSON re-encodes the JSON in a canonical encoding. The encoding is
+// the shortest possible encoding using integer values with sorted object keys.
+// https://matrix.org/docs/spec/appendices#canonical-json
+func CanonicalJSON(input []byte) ([]byte, error) {
+	if !gjson.Valid(string(input)) {
+		return nil, fmt.Errorf("invalid json")
+	}
+
+	return CanonicalJSONAssumeValid(input), nil
+}
+
+// CanonicalJSONAssumeValid is the same as CanonicalJSON, but assumes the
+// input is valid JSON
+func CanonicalJSONAssumeValid(input []byte) []byte {
+	input = CompactJSON(input, make([]byte, 0, len(input)))
+	return SortJSON(input, make([]byte, 0, len(input)))
+}
+
+// SortJSON reencodes the JSON with the object keys sorted by lexicographically
+// by codepoint. The input must be valid JSON.
+func SortJSON(input, output []byte) []byte {
+	result := gjson.ParseBytes(input)
+
+	return sortJSONValue(result, input, output)
+}
+
+// sortJSONValue takes a gjson.Result and sorts it. inputJSON must be the
+// raw JSON bytes that gjson.Result points to.
+func sortJSONValue(input gjson.Result, inputJSON, output []byte) []byte {
+	if input.IsArray() {
+		return sortJSONArray(input, inputJSON, output)
+	}
+
+	if input.IsObject() {
+		return sortJSONObject(input, inputJSON, output)
+	}
+
+	// If its neither an object nor an array then there is no sub structure
+	// to sort, so just append the raw bytes.
+	return append(output, input.Raw...)
+}
+
+// sortJSONArray takes a gjson.Result and sorts it, assuming its an array.
+// inputJSON must be the raw JSON bytes that gjson.Result points to.
+func sortJSONArray(input gjson.Result, inputJSON, output []byte) []byte {
+	sep := byte('[')
+
+	// Iterate over each value in the array and sort it.
+	input.ForEach(func(_, value gjson.Result) bool {
+		output = append(output, sep)
+		sep = ','
+		output = sortJSONValue(value, inputJSON, output)
+		return true // keep iterating
+	})
+
+	if sep == '[' {
+		// If sep is still '[' then the array was empty and we never wrote the
+		// initial '[', so we write it now along with the closing ']'.
+		output = append(output, '[', ']')
+	} else {
+		// Otherwise we end the array by writing a single ']'
+		output = append(output, ']')
+	}
+	return output
+}
+
+// sortJSONObject takes a gjson.Result and sorts it, assuming its an object.
+// inputJSON must be the raw JSON bytes that gjson.Result points to.
+func sortJSONObject(input gjson.Result, inputJSON, output []byte) []byte {
+	type entry struct {
+		key    string // The parsed key string
+		rawKey string // The raw, unparsed key JSON string
+		value  gjson.Result
+	}
+
+	var entries []entry
+
+	// Iterate over each key/value pair and add it to a slice
+	// that we can sort
+	input.ForEach(func(key, value gjson.Result) bool {
+		entries = append(entries, entry{
+			key:    key.String(),
+			rawKey: key.Raw,
+			value:  value,
+		})
+		return true // keep iterating
+	})
+
+	// Sort the slice based on the *parsed* key
+	sort.Slice(entries, func(a, b int) bool {
+		return entries[a].key < entries[b].key
+	})
+
+	sep := byte('{')
+
+	for _, entry := range entries {
+		output = append(output, sep)
+		sep = ','
+
+		// Append the raw unparsed JSON key, *not* the parsed key
+		output = append(output, entry.rawKey...)
+		output = append(output, ':')
+		output = sortJSONValue(entry.value, inputJSON, output)
+	}
+	if sep == '{' {
+		// If sep is still '{' then the object was empty and we never wrote the
+		// initial '{', so we write it now along with the closing '}'.
+		output = append(output, '{', '}')
+	} else {
+		// Otherwise we end the object by writing a single '}'
+		output = append(output, '}')
+	}
+	return output
+}
+
+// CompactJSON makes the encoded JSON as small as possible by removing
+// whitespace and unneeded unicode escapes
+func CompactJSON(input, output []byte) []byte {
+	var i int
+	for i < len(input) {
+		c := input[i]
+		i++
+		// The valid whitespace characters are all less than or equal to SPACE 0x20.
+		// The valid non-white characters are all greater than SPACE 0x20.
+		// So we can check for whitespace by comparing against SPACE 0x20.
+		if c <= ' ' {
+			// Skip over whitespace.
+			continue
+		}
+		// Add the non-whitespace character to the output.
+		output = append(output, c)
+		if c == '"' {
+			// We are inside a string.
+			for i < len(input) {
+				c = input[i]
+				i++
+				// Check if this is an escape sequence.
+				if c == '\\' {
+					escape := input[i]
+					i++
+					if escape == 'u' {
+						// If this is a unicode escape then we need to handle it specially
+						output, i = compactUnicodeEscape(input, output, i)
+					} else if escape == '/' {
+						// JSON does not require escaping '/', but allows encoders to escape it as a special case.
+						// Since the escape isn't required we remove it.
+						output = append(output, escape)
+					} else {
+						// All other permitted escapes are single charater escapes that are already in their shortest form.
+						output = append(output, '\\', escape)
+					}
+				} else {
+					output = append(output, c)
+				}
+				if c == '"' {
+					break
+				}
+			}
+		}
+	}
+	return output
+}
+
+// compactUnicodeEscape unpacks a 4 byte unicode escape starting at index.
+// If the escape is a surrogate pair then decode the 6 byte \uXXXX escape
+// that follows. Returns the output slice and a new input index.
+func compactUnicodeEscape(input, output []byte, index int) ([]byte, int) {
+	const (
+		ESCAPES = "uuuuuuuubtnufruuuuuuuuuuuuuuuuuu"
+		HEX     = "0123456789ABCDEF"
+	)
+	// If there aren't enough bytes to decode the hex escape then return.
+	if len(input)-index < 4 {
+		return output, len(input)
+	}
+	// Decode the 4 hex digits.
+	c := readHexDigits(input[index:])
+	index += 4
+	if c < ' ' {
+		// If the character is less than SPACE 0x20 then it will need escaping.
+		escape := ESCAPES[c]
+		output = append(output, '\\', escape)
+		if escape == 'u' {
+			output = append(output, '0', '0', byte('0'+(c>>4)), HEX[c&0xF])
+		}
+	} else if c == '\\' || c == '"' {
+		// Otherwise the character only needs escaping if it is a QUOTE '"' or BACKSLASH '\\'.
+		output = append(output, '\\', byte(c))
+	} else if c < 0xD800 || c >= 0xE000 {
+		// If the character isn't a surrogate pair then encoded it directly as UTF-8.
+		var buffer [4]byte
+		n := utf8.EncodeRune(buffer[:], rune(c))
+		output = append(output, buffer[:n]...)
+	} else {
+		// Otherwise the escaped character was the first part of a UTF-16 style surrogate pair.
+		// The next 6 bytes MUST be a '\uXXXX'.
+		// If there aren't enough bytes to decode the hex escape then return.
+		if len(input)-index < 6 {
+			return output, len(input)
+		}
+		// Decode the 4 hex digits from the '\uXXXX'.
+		surrogate := readHexDigits(input[index+2:])
+		index += 6
+		// Reconstruct the UCS4 codepoint from the surrogates.
+		codepoint := 0x10000 + (((c & 0x3FF) << 10) | (surrogate & 0x3FF))
+		// Encode the charater as UTF-8.
+		var buffer [4]byte
+		n := utf8.EncodeRune(buffer[:], rune(codepoint))
+		output = append(output, buffer[:n]...)
+	}
+	return output, index
+}
+
+// Read 4 hex digits from the input slice.
+// Taken from https://github.com/NegativeMjark/indolentjson-rust/blob/8b959791fe2656a88f189c5d60d153be05fe3deb/src/readhex.rs#L21
+func readHexDigits(input []byte) uint32 {
+	hex := binary.BigEndian.Uint32(input)
+	// subtract '0'
+	hex -= 0x30303030
+	// strip the higher bits, maps 'a' => 'A'
+	hex &= 0x1F1F1F1F
+	mask := hex & 0x10101010
+	// subtract 'A' - 10 - '9' - 9 = 7 from the letters.
+	hex -= mask >> 1
+	hex += mask >> 4
+	// collect the nibbles
+	hex |= hex >> 4
+	hex &= 0xFF00FF
+	hex |= hex >> 8
+	return hex & 0xFFFF
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/ed25519/ed25519.go b/vendor/maunium.net/go/mautrix/crypto/ed25519/ed25519.go
new file mode 100644
index 0000000..327cbb3
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/ed25519/ed25519.go
@@ -0,0 +1,302 @@
+// Copyright 2024 Sumner Evans.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package ed25519 implements the Ed25519 signature algorithm. See
+// https://ed25519.cr.yp.to/.
+//
+// This package stores the private key in the NaCl format, which is a different
+// format than that used by the [crypto/ed25519] package in the standard
+// library.
+//
+// This picture will help with the rest of the explanation:
+// https://blog.mozilla.org/warner/files/2011/11/key-formats.png
+//
+// The private key in the [crypto/ed25519] package is a 64-byte value where the
+// first 32-bytes are the seed and the last 32-bytes are the public key.
+//
+// The private key in this package is stored in the NaCl format. That is, the
+// left 32-bytes are the private scalar A and the right 32-bytes are the right
+// half of the SHA512 result.
+//
+// The contents of this package are mostly copied from the standard library,
+// and as such the source code is licensed under the BSD license of the
+// standard library implementation.
+//
+// Other notable changes from the standard library include:
+//
+//   - The Seed function of the standard library is not implemented in this
+//     package because there is no way to recover the seed after hashing it.
+package ed25519
+
+import (
+	"crypto"
+	"crypto/ed25519"
+	cryptorand "crypto/rand"
+	"crypto/sha512"
+	"crypto/subtle"
+	"errors"
+	"io"
+	"strconv"
+
+	"filippo.io/edwards25519"
+)
+
+const (
+	// PublicKeySize is the size, in bytes, of public keys as used in this package.
+	PublicKeySize = 32
+	// PrivateKeySize is the size, in bytes, of private keys as used in this package.
+	PrivateKeySize = 64
+	// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
+	SignatureSize = 64
+	// SeedSize is the size, in bytes, of private key seeds. These are the private key representations used by RFC 8032.
+	SeedSize = 32
+)
+
+// PublicKey is the type of Ed25519 public keys.
+type PublicKey []byte
+
+// Any methods implemented on PublicKey might need to also be implemented on
+// PrivateKey, as the latter embeds the former and will expose its methods.
+
+// Equal reports whether pub and x have the same value.
+func (pub PublicKey) Equal(x crypto.PublicKey) bool {
+	switch x := x.(type) {
+	case PublicKey:
+		return subtle.ConstantTimeCompare(pub, x) == 1
+	case ed25519.PublicKey:
+		return subtle.ConstantTimeCompare(pub, x) == 1
+	default:
+		return false
+	}
+}
+
+// PrivateKey is the type of Ed25519 private keys. It implements [crypto.Signer].
+type PrivateKey []byte
+
+// Public returns the [PublicKey] corresponding to priv.
+//
+// This method differs from the standard library because it calculates the
+// public key instead of returning the right half of the private key (which
+// contains the public key in the standard library).
+func (priv PrivateKey) Public() crypto.PublicKey {
+	s, err := edwards25519.NewScalar().SetBytesWithClamping(priv[:32])
+	if err != nil {
+		panic("ed25519: internal error: setting scalar failed")
+	}
+	return (&edwards25519.Point{}).ScalarBaseMult(s).Bytes()
+}
+
+// Equal reports whether priv and x have the same value.
+func (priv PrivateKey) Equal(x crypto.PrivateKey) bool {
+	// TODO do we have any need to check equality with standard library ed25519
+	// private keys?
+	xx, ok := x.(PrivateKey)
+	if !ok {
+		return false
+	}
+	return subtle.ConstantTimeCompare(priv, xx) == 1
+}
+
+// Sign signs the given message with priv. rand is ignored and can be nil.
+//
+// If opts.HashFunc() is [crypto.SHA512], the pre-hashed variant Ed25519ph is used
+// and message is expected to be a SHA-512 hash, otherwise opts.HashFunc() must
+// be [crypto.Hash](0) and the message must not be hashed, as Ed25519 performs two
+// passes over messages to be signed.
+//
+// A value of type [Options] can be used as opts, or crypto.Hash(0) or
+// crypto.SHA512 directly to select plain Ed25519 or Ed25519ph, respectively.
+func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
+	hash := opts.HashFunc()
+	context := ""
+	if opts, ok := opts.(*Options); ok {
+		context = opts.Context
+	}
+	switch {
+	case hash == crypto.SHA512: // Ed25519ph
+		if l := len(message); l != sha512.Size {
+			return nil, errors.New("ed25519: bad Ed25519ph message hash length: " + strconv.Itoa(l))
+		}
+		if l := len(context); l > 255 {
+			return nil, errors.New("ed25519: bad Ed25519ph context length: " + strconv.Itoa(l))
+		}
+		signature := make([]byte, SignatureSize)
+		sign(signature, priv, message, domPrefixPh, context)
+		return signature, nil
+	case hash == crypto.Hash(0) && context != "": // Ed25519ctx
+		if l := len(context); l > 255 {
+			return nil, errors.New("ed25519: bad Ed25519ctx context length: " + strconv.Itoa(l))
+		}
+		signature := make([]byte, SignatureSize)
+		sign(signature, priv, message, domPrefixCtx, context)
+		return signature, nil
+	case hash == crypto.Hash(0): // Ed25519
+		return Sign(priv, message), nil
+	default:
+		return nil, errors.New("ed25519: expected opts.HashFunc() zero (unhashed message, for standard Ed25519) or SHA-512 (for Ed25519ph)")
+	}
+}
+
+// Options can be used with [PrivateKey.Sign] or [VerifyWithOptions]
+// to select Ed25519 variants.
+type Options struct {
+	// Hash can be zero for regular Ed25519, or crypto.SHA512 for Ed25519ph.
+	Hash crypto.Hash
+
+	// Context, if not empty, selects Ed25519ctx or provides the context string
+	// for Ed25519ph. It can be at most 255 bytes in length.
+	Context string
+}
+
+// HashFunc returns o.Hash.
+func (o *Options) HashFunc() crypto.Hash { return o.Hash }
+
+// GenerateKey generates a public/private key pair using entropy from rand.
+// If rand is nil, [crypto/rand.Reader] will be used.
+//
+// The output of this function is deterministic, and equivalent to reading
+// [SeedSize] bytes from rand, and passing them to [NewKeyFromSeed].
+func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) {
+	if rand == nil {
+		rand = cryptorand.Reader
+	}
+
+	seed := make([]byte, SeedSize)
+	if _, err := io.ReadFull(rand, seed); err != nil {
+		return nil, nil, err
+	}
+
+	privateKey := NewKeyFromSeed(seed)
+	return PublicKey(privateKey.Public().([]byte)), privateKey, nil
+}
+
+// NewKeyFromSeed calculates a private key from a seed. It will panic if
+// len(seed) is not [SeedSize]. This function is provided for interoperability
+// with RFC 8032. RFC 8032's private keys correspond to seeds in this
+// package.
+func NewKeyFromSeed(seed []byte) PrivateKey {
+	// Outline the function body so that the returned key can be stack-allocated.
+	privateKey := make([]byte, PrivateKeySize)
+	newKeyFromSeed(privateKey, seed)
+	return privateKey
+}
+
+func newKeyFromSeed(privateKey, seed []byte) {
+	if l := len(seed); l != SeedSize {
+		panic("ed25519: bad seed length: " + strconv.Itoa(l))
+	}
+
+	h := sha512.Sum512(seed)
+
+	// Apply clamping to get A in the left half, and leave the right half
+	// as-is. This gets the private key into the NaCl format.
+	h[0] &= 248
+	h[31] &= 63
+	h[31] |= 64
+	copy(privateKey, h[:])
+}
+
+// Sign signs the message with privateKey and returns a signature. It will
+// panic if len(privateKey) is not [PrivateKeySize].
+func Sign(privateKey PrivateKey, message []byte) []byte {
+	// Outline the function body so that the returned signature can be
+	// stack-allocated.
+	signature := make([]byte, SignatureSize)
+	sign(signature, privateKey, message, domPrefixPure, "")
+	return signature
+}
+
+// Domain separation prefixes used to disambiguate Ed25519/Ed25519ph/Ed25519ctx.
+// See RFC 8032, Section 2 and Section 5.1.
+const (
+	// domPrefixPure is empty for pure Ed25519.
+	domPrefixPure = ""
+	// domPrefixPh is dom2(phflag=1) for Ed25519ph. It must be followed by the
+	// uint8-length prefixed context.
+	domPrefixPh = "SigEd25519 no Ed25519 collisions\x01"
+	// domPrefixCtx is dom2(phflag=0) for Ed25519ctx. It must be followed by the
+	// uint8-length prefixed context.
+	domPrefixCtx = "SigEd25519 no Ed25519 collisions\x00"
+)
+
+func sign(signature []byte, privateKey PrivateKey, message []byte, domPrefix, context string) {
+	if l := len(privateKey); l != PrivateKeySize {
+		panic("ed25519: bad private key length: " + strconv.Itoa(l))
+	}
+	// We have to extract the public key from the private key.
+	publicKey := privateKey.Public().([]byte)
+	// The private key is already the hashed value of the seed.
+	h := privateKey
+
+	s, err := edwards25519.NewScalar().SetBytesWithClamping(h[:32])
+	if err != nil {
+		panic("ed25519: internal error: setting scalar failed")
+	}
+	prefix := h[32:]
+
+	mh := sha512.New()
+	if domPrefix != domPrefixPure {
+		mh.Write([]byte(domPrefix))
+		mh.Write([]byte{byte(len(context))})
+		mh.Write([]byte(context))
+	}
+	mh.Write(prefix)
+	mh.Write(message)
+	messageDigest := make([]byte, 0, sha512.Size)
+	messageDigest = mh.Sum(messageDigest)
+	r, err := edwards25519.NewScalar().SetUniformBytes(messageDigest)
+	if err != nil {
+		panic("ed25519: internal error: setting scalar failed")
+	}
+
+	R := (&edwards25519.Point{}).ScalarBaseMult(r)
+
+	kh := sha512.New()
+	if domPrefix != domPrefixPure {
+		kh.Write([]byte(domPrefix))
+		kh.Write([]byte{byte(len(context))})
+		kh.Write([]byte(context))
+	}
+	kh.Write(R.Bytes())
+	kh.Write(publicKey)
+	kh.Write(message)
+	hramDigest := make([]byte, 0, sha512.Size)
+	hramDigest = kh.Sum(hramDigest)
+	k, err := edwards25519.NewScalar().SetUniformBytes(hramDigest)
+	if err != nil {
+		panic("ed25519: internal error: setting scalar failed")
+	}
+
+	S := edwards25519.NewScalar().MultiplyAdd(k, s, r)
+
+	copy(signature[:32], R.Bytes())
+	copy(signature[32:], S.Bytes())
+}
+
+// Verify reports whether sig is a valid signature of message by publicKey. It
+// will panic if len(publicKey) is not [PublicKeySize].
+//
+// This is just a wrapper around [ed25519.Verify] from the standard library.
+func Verify(publicKey PublicKey, message, sig []byte) bool {
+	return ed25519.Verify(ed25519.PublicKey(publicKey), message, sig)
+}
+
+// VerifyWithOptions reports whether sig is a valid signature of message by
+// publicKey. A valid signature is indicated by returning a nil error. It will
+// panic if len(publicKey) is not [PublicKeySize].
+//
+// If opts.Hash is [crypto.SHA512], the pre-hashed variant Ed25519ph is used and
+// message is expected to be a SHA-512 hash, otherwise opts.Hash must be
+// [crypto.Hash](0) and the message must not be hashed, as Ed25519 performs two
+// passes over messages to be signed.
+//
+// This is just a wrapper around [ed25519.VerifyWithOptions] from the standard
+// library.
+func VerifyWithOptions(publicKey PublicKey, message, sig []byte, opts *Options) error {
+	return ed25519.VerifyWithOptions(ed25519.PublicKey(publicKey), message, sig, &ed25519.Options{
+		Hash:    opts.Hash,
+		Context: opts.Context,
+	})
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/goolm/crypto/curve25519.go b/vendor/maunium.net/go/mautrix/crypto/goolm/crypto/curve25519.go
new file mode 100644
index 0000000..1c182ca
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/goolm/crypto/curve25519.go
@@ -0,0 +1,186 @@
+package crypto
+
+import (
+	"bytes"
+	"crypto/rand"
+	"encoding/base64"
+	"fmt"
+	"io"
+
+	"golang.org/x/crypto/curve25519"
+
+	"maunium.net/go/mautrix/crypto/goolm/libolmpickle"
+	"maunium.net/go/mautrix/crypto/olm"
+	"maunium.net/go/mautrix/id"
+)
+
+const (
+	Curve25519KeyLength    = curve25519.ScalarSize //The length of the private key.
+	curve25519PubKeyLength = 32
+)
+
+// Curve25519GenerateKey creates a new curve25519 key pair. If reader is nil, the random data is taken from crypto/rand.
+func Curve25519GenerateKey(reader io.Reader) (Curve25519KeyPair, error) {
+	privateKeyByte := make([]byte, Curve25519KeyLength)
+	if reader == nil {
+		_, err := rand.Read(privateKeyByte)
+		if err != nil {
+			return Curve25519KeyPair{}, err
+		}
+	} else {
+		_, err := reader.Read(privateKeyByte)
+		if err != nil {
+			return Curve25519KeyPair{}, err
+		}
+	}
+
+	privateKey := Curve25519PrivateKey(privateKeyByte)
+
+	publicKey, err := privateKey.PubKey()
+	if err != nil {
+		return Curve25519KeyPair{}, err
+	}
+	return Curve25519KeyPair{
+		PrivateKey: Curve25519PrivateKey(privateKey),
+		PublicKey:  Curve25519PublicKey(publicKey),
+	}, nil
+}
+
+// Curve25519GenerateFromPrivate creates a new curve25519 key pair with the private key given.
+func Curve25519GenerateFromPrivate(private Curve25519PrivateKey) (Curve25519KeyPair, error) {
+	publicKey, err := private.PubKey()
+	if err != nil {
+		return Curve25519KeyPair{}, err
+	}
+	return Curve25519KeyPair{
+		PrivateKey: private,
+		PublicKey:  Curve25519PublicKey(publicKey),
+	}, nil
+}
+
+// Curve25519KeyPair stores both parts of a curve25519 key.
+type Curve25519KeyPair struct {
+	PrivateKey Curve25519PrivateKey `json:"private,omitempty"`
+	PublicKey  Curve25519PublicKey  `json:"public,omitempty"`
+}
+
+// B64Encoded returns a base64 encoded string of the public key.
+func (c Curve25519KeyPair) B64Encoded() id.Curve25519 {
+	return c.PublicKey.B64Encoded()
+}
+
+// SharedSecret returns the shared secret between the key pair and the given public key.
+func (c Curve25519KeyPair) SharedSecret(pubKey Curve25519PublicKey) ([]byte, error) {
+	return c.PrivateKey.SharedSecret(pubKey)
+}
+
+// PickleLibOlm encodes the key pair into target. target has to have a size of at least PickleLen() and is written to from index 0.
+// It returns the number of bytes written.
+func (c Curve25519KeyPair) PickleLibOlm(target []byte) (int, error) {
+	if len(target) < c.PickleLen() {
+		return 0, fmt.Errorf("pickle curve25519 key pair: %w", olm.ErrValueTooShort)
+	}
+	written, err := c.PublicKey.PickleLibOlm(target)
+	if err != nil {
+		return 0, fmt.Errorf("pickle curve25519 key pair: %w", err)
+	}
+	if len(c.PrivateKey) != Curve25519KeyLength {
+		written += libolmpickle.PickleBytes(make([]byte, Curve25519KeyLength), target[written:])
+	} else {
+		written += libolmpickle.PickleBytes(c.PrivateKey, target[written:])
+	}
+	return written, nil
+}
+
+// UnpickleLibOlm decodes the unencryted value and populates the key pair accordingly. It returns the number of bytes read.
+func (c *Curve25519KeyPair) UnpickleLibOlm(value []byte) (int, error) {
+	//unpickle PubKey
+	read, err := c.PublicKey.UnpickleLibOlm(value)
+	if err != nil {
+		return 0, err
+	}
+	//unpickle PrivateKey
+	privKey, readPriv, err := libolmpickle.UnpickleBytes(value[read:], Curve25519KeyLength)
+	if err != nil {
+		return read, err
+	}
+	c.PrivateKey = privKey
+	return read + readPriv, nil
+}
+
+// PickleLen returns the number of bytes the pickled key pair will have.
+func (c Curve25519KeyPair) PickleLen() int {
+	lenPublic := c.PublicKey.PickleLen()
+	var lenPrivate int
+	if len(c.PrivateKey) != Curve25519KeyLength {
+		lenPrivate = libolmpickle.PickleBytesLen(make([]byte, Curve25519KeyLength))
+	} else {
+		lenPrivate = libolmpickle.PickleBytesLen(c.PrivateKey)
+	}
+	return lenPublic + lenPrivate
+}
+
+// Curve25519PrivateKey represents the private key for curve25519 usage
+type Curve25519PrivateKey []byte
+
+// Equal compares the private key to the given private key.
+func (c Curve25519PrivateKey) Equal(x Curve25519PrivateKey) bool {
+	return bytes.Equal(c, x)
+}
+
+// PubKey returns the public key derived from the private key.
+func (c Curve25519PrivateKey) PubKey() (Curve25519PublicKey, error) {
+	publicKey, err := curve25519.X25519(c, curve25519.Basepoint)
+	if err != nil {
+		return nil, err
+	}
+	return publicKey, nil
+}
+
+// SharedSecret returns the shared secret between the private key and the given public key.
+func (c Curve25519PrivateKey) SharedSecret(pubKey Curve25519PublicKey) ([]byte, error) {
+	return curve25519.X25519(c, pubKey)
+}
+
+// Curve25519PublicKey represents the public key for curve25519 usage
+type Curve25519PublicKey []byte
+
+// Equal compares the public key to the given public key.
+func (c Curve25519PublicKey) Equal(x Curve25519PublicKey) bool {
+	return bytes.Equal(c, x)
+}
+
+// B64Encoded returns a base64 encoded string of the public key.
+func (c Curve25519PublicKey) B64Encoded() id.Curve25519 {
+	return id.Curve25519(base64.RawStdEncoding.EncodeToString(c))
+}
+
+// PickleLibOlm encodes the public key into target. target has to have a size of at least PickleLen() and is written to from index 0.
+// It returns the number of bytes written.
+func (c Curve25519PublicKey) PickleLibOlm(target []byte) (int, error) {
+	if len(target) < c.PickleLen() {
+		return 0, fmt.Errorf("pickle curve25519 public key: %w", olm.ErrValueTooShort)
+	}
+	if len(c) != curve25519PubKeyLength {
+		return libolmpickle.PickleBytes(make([]byte, curve25519PubKeyLength), target), nil
+	}
+	return libolmpickle.PickleBytes(c, target), nil
+}
+
+// UnpickleLibOlm decodes the unencryted value and populates the public key accordingly. It returns the number of bytes read.
+func (c *Curve25519PublicKey) UnpickleLibOlm(value []byte) (int, error) {
+	unpickled, readBytes, err := libolmpickle.UnpickleBytes(value, curve25519PubKeyLength)
+	if err != nil {
+		return 0, err
+	}
+	*c = unpickled
+	return readBytes, nil
+}
+
+// PickleLen returns the number of bytes the pickled public key will have.
+func (c Curve25519PublicKey) PickleLen() int {
+	if len(c) != curve25519PubKeyLength {
+		return libolmpickle.PickleBytesLen(make([]byte, curve25519PubKeyLength))
+	}
+	return libolmpickle.PickleBytesLen(c)
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/goolm/crypto/doc.go b/vendor/maunium.net/go/mautrix/crypto/goolm/crypto/doc.go
new file mode 100644
index 0000000..5bdb01d
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/goolm/crypto/doc.go
@@ -0,0 +1,2 @@
+// Package crpyto provides the nessesary encryption methods for olm/megolm
+package crypto
diff --git a/vendor/maunium.net/go/mautrix/crypto/goolm/crypto/ed25519.go b/vendor/maunium.net/go/mautrix/crypto/goolm/crypto/ed25519.go
new file mode 100644
index 0000000..57fc25f
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/goolm/crypto/ed25519.go
@@ -0,0 +1,184 @@
+package crypto
+
+import (
+	"encoding/base64"
+	"fmt"
+	"io"
+
+	"maunium.net/go/mautrix/crypto/ed25519"
+	"maunium.net/go/mautrix/crypto/goolm/libolmpickle"
+	"maunium.net/go/mautrix/crypto/olm"
+	"maunium.net/go/mautrix/id"
+)
+
+const (
+	ED25519SignatureSize = ed25519.SignatureSize //The length of a signature
+)
+
+// Ed25519GenerateKey creates a new ed25519 key pair. If reader is nil, the random data is taken from crypto/rand.
+func Ed25519GenerateKey(reader io.Reader) (Ed25519KeyPair, error) {
+	publicKey, privateKey, err := ed25519.GenerateKey(reader)
+	if err != nil {
+		return Ed25519KeyPair{}, err
+	}
+	return Ed25519KeyPair{
+		PrivateKey: Ed25519PrivateKey(privateKey),
+		PublicKey:  Ed25519PublicKey(publicKey),
+	}, nil
+}
+
+// Ed25519GenerateFromPrivate creates a new ed25519 key pair with the private key given.
+func Ed25519GenerateFromPrivate(privKey Ed25519PrivateKey) Ed25519KeyPair {
+	return Ed25519KeyPair{
+		PrivateKey: privKey,
+		PublicKey:  privKey.PubKey(),
+	}
+}
+
+// Ed25519GenerateFromSeed creates a new ed25519 key pair with a given seed.
+func Ed25519GenerateFromSeed(seed []byte) Ed25519KeyPair {
+	privKey := Ed25519PrivateKey(ed25519.NewKeyFromSeed(seed))
+	return Ed25519KeyPair{
+		PrivateKey: privKey,
+		PublicKey:  privKey.PubKey(),
+	}
+}
+
+// Ed25519KeyPair stores both parts of a ed25519 key.
+type Ed25519KeyPair struct {
+	PrivateKey Ed25519PrivateKey `json:"private,omitempty"`
+	PublicKey  Ed25519PublicKey  `json:"public,omitempty"`
+}
+
+// B64Encoded returns a base64 encoded string of the public key.
+func (c Ed25519KeyPair) B64Encoded() id.Ed25519 {
+	return id.Ed25519(base64.RawStdEncoding.EncodeToString(c.PublicKey))
+}
+
+// Sign returns the signature for the message.
+func (c Ed25519KeyPair) Sign(message []byte) []byte {
+	return c.PrivateKey.Sign(message)
+}
+
+// Verify checks the signature of the message against the givenSignature
+func (c Ed25519KeyPair) Verify(message, givenSignature []byte) bool {
+	return c.PublicKey.Verify(message, givenSignature)
+}
+
+// PickleLibOlm encodes the key pair into target. target has to have a size of at least PickleLen() and is written to from index 0.
+// It returns the number of bytes written.
+func (c Ed25519KeyPair) PickleLibOlm(target []byte) (int, error) {
+	if len(target) < c.PickleLen() {
+		return 0, fmt.Errorf("pickle ed25519 key pair: %w", olm.ErrValueTooShort)
+	}
+	written, err := c.PublicKey.PickleLibOlm(target)
+	if err != nil {
+		return 0, fmt.Errorf("pickle ed25519 key pair: %w", err)
+	}
+
+	if len(c.PrivateKey) != ed25519.PrivateKeySize {
+		written += libolmpickle.PickleBytes(make([]byte, ed25519.PrivateKeySize), target[written:])
+	} else {
+		written += libolmpickle.PickleBytes(c.PrivateKey, target[written:])
+	}
+	return written, nil
+}
+
+// UnpickleLibOlm decodes the unencryted value and populates the key pair accordingly. It returns the number of bytes read.
+func (c *Ed25519KeyPair) UnpickleLibOlm(value []byte) (int, error) {
+	//unpickle PubKey
+	read, err := c.PublicKey.UnpickleLibOlm(value)
+	if err != nil {
+		return 0, err
+	}
+	//unpickle PrivateKey
+	privKey, readPriv, err := libolmpickle.UnpickleBytes(value[read:], ed25519.PrivateKeySize)
+	if err != nil {
+		return read, err
+	}
+	c.PrivateKey = privKey
+	return read + readPriv, nil
+}
+
+// PickleLen returns the number of bytes the pickled key pair will have.
+func (c Ed25519KeyPair) PickleLen() int {
+	lenPublic := c.PublicKey.PickleLen()
+	var lenPrivate int
+	if len(c.PrivateKey) != ed25519.PrivateKeySize {
+		lenPrivate = libolmpickle.PickleBytesLen(make([]byte, ed25519.PrivateKeySize))
+	} else {
+		lenPrivate = libolmpickle.PickleBytesLen(c.PrivateKey)
+	}
+	return lenPublic + lenPrivate
+}
+
+// Curve25519PrivateKey represents the private key for ed25519 usage. This is just a wrapper.
+type Ed25519PrivateKey ed25519.PrivateKey
+
+// Equal compares the private key to the given private key.
+func (c Ed25519PrivateKey) Equal(x Ed25519PrivateKey) bool {
+	return ed25519.PrivateKey(c).Equal(ed25519.PrivateKey(x))
+}
+
+// PubKey returns the public key derived from the private key.
+func (c Ed25519PrivateKey) PubKey() Ed25519PublicKey {
+	publicKey := ed25519.PrivateKey(c).Public()
+	return Ed25519PublicKey(publicKey.([]byte))
+}
+
+// Sign returns the signature for the message.
+func (c Ed25519PrivateKey) Sign(message []byte) []byte {
+	signature, err := ed25519.PrivateKey(c).Sign(nil, message, &ed25519.Options{})
+	if err != nil {
+		panic(err)
+	}
+	return signature
+}
+
+// Ed25519PublicKey represents the public key for ed25519 usage. This is just a wrapper.
+type Ed25519PublicKey ed25519.PublicKey
+
+// Equal compares the public key to the given public key.
+func (c Ed25519PublicKey) Equal(x Ed25519PublicKey) bool {
+	return ed25519.PublicKey(c).Equal(ed25519.PublicKey(x))
+}
+
+// B64Encoded returns a base64 encoded string of the public key.
+func (c Ed25519PublicKey) B64Encoded() id.Curve25519 {
+	return id.Curve25519(base64.RawStdEncoding.EncodeToString(c))
+}
+
+// Verify checks the signature of the message against the givenSignature
+func (c Ed25519PublicKey) Verify(message, givenSignature []byte) bool {
+	return ed25519.Verify(ed25519.PublicKey(c), message, givenSignature)
+}
+
+// PickleLibOlm encodes the public key into target. target has to have a size of at least PickleLen() and is written to from index 0.
+// It returns the number of bytes written.
+func (c Ed25519PublicKey) PickleLibOlm(target []byte) (int, error) {
+	if len(target) < c.PickleLen() {
+		return 0, fmt.Errorf("pickle ed25519 public key: %w", olm.ErrValueTooShort)
+	}
+	if len(c) != ed25519.PublicKeySize {
+		return libolmpickle.PickleBytes(make([]byte, ed25519.PublicKeySize), target), nil
+	}
+	return libolmpickle.PickleBytes(c, target), nil
+}
+
+// UnpickleLibOlm decodes the unencryted value and populates the public key accordingly. It returns the number of bytes read.
+func (c *Ed25519PublicKey) UnpickleLibOlm(value []byte) (int, error) {
+	unpickled, readBytes, err := libolmpickle.UnpickleBytes(value, ed25519.PublicKeySize)
+	if err != nil {
+		return 0, err
+	}
+	*c = unpickled
+	return readBytes, nil
+}
+
+// PickleLen returns the number of bytes the pickled public key will have.
+func (c Ed25519PublicKey) PickleLen() int {
+	if len(c) != ed25519.PublicKeySize {
+		return libolmpickle.PickleBytesLen(make([]byte, ed25519.PublicKeySize))
+	}
+	return libolmpickle.PickleBytesLen(c)
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/goolm/crypto/hmac.go b/vendor/maunium.net/go/mautrix/crypto/goolm/crypto/hmac.go
new file mode 100644
index 0000000..8542f7c
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/goolm/crypto/hmac.go
@@ -0,0 +1,29 @@
+package crypto
+
+import (
+	"crypto/hmac"
+	"crypto/sha256"
+	"io"
+
+	"golang.org/x/crypto/hkdf"
+)
+
+// HMACSHA256 returns the hash message authentication code with SHA-256 of the input with the key.
+func HMACSHA256(key, input []byte) []byte {
+	hash := hmac.New(sha256.New, key)
+	hash.Write(input)
+	return hash.Sum(nil)
+}
+
+// SHA256 return the SHA-256 of the value.
+func SHA256(value []byte) []byte {
+	hash := sha256.New()
+	hash.Write(value)
+	return hash.Sum(nil)
+}
+
+// HKDFSHA256 is the key deivation function based on HMAC and returns a reader based on input. salt and info can both be nil.
+// The reader can be used to read an arbitary length of bytes which are based on all parameters.
+func HKDFSHA256(input, salt, info []byte) io.Reader {
+	return hkdf.New(sha256.New, input, salt, info)
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/goolm/crypto/one_time_key.go b/vendor/maunium.net/go/mautrix/crypto/goolm/crypto/one_time_key.go
new file mode 100644
index 0000000..aaa253d
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/goolm/crypto/one_time_key.go
@@ -0,0 +1,95 @@
+package crypto
+
+import (
+	"encoding/base64"
+	"encoding/binary"
+	"fmt"
+
+	"maunium.net/go/mautrix/crypto/goolm/libolmpickle"
+	"maunium.net/go/mautrix/crypto/olm"
+	"maunium.net/go/mautrix/id"
+)
+
+// OneTimeKey stores the information about a one time key.
+type OneTimeKey struct {
+	ID        uint32            `json:"id"`
+	Published bool              `json:"published"`
+	Key       Curve25519KeyPair `json:"key,omitempty"`
+}
+
+// Equal compares the one time key to the given one.
+func (otk OneTimeKey) Equal(s OneTimeKey) bool {
+	if otk.ID != s.ID {
+		return false
+	}
+	if otk.Published != s.Published {
+		return false
+	}
+	if !otk.Key.PrivateKey.Equal(s.Key.PrivateKey) {
+		return false
+	}
+	if !otk.Key.PublicKey.Equal(s.Key.PublicKey) {
+		return false
+	}
+	return true
+}
+
+// PickleLibOlm encodes the key pair into target. target has to have a size of at least PickleLen() and is written to from index 0.
+// It returns the number of bytes written.
+func (c OneTimeKey) PickleLibOlm(target []byte) (int, error) {
+	if len(target) < c.PickleLen() {
+		return 0, fmt.Errorf("pickle one time key: %w", olm.ErrValueTooShort)
+	}
+	written := libolmpickle.PickleUInt32(uint32(c.ID), target)
+	written += libolmpickle.PickleBool(c.Published, target[written:])
+	writtenKey, err := c.Key.PickleLibOlm(target[written:])
+	if err != nil {
+		return 0, fmt.Errorf("pickle one time key: %w", err)
+	}
+	written += writtenKey
+	return written, nil
+}
+
+// UnpickleLibOlm decodes the unencryted value and populates the OneTimeKey accordingly. It returns the number of bytes read.
+func (c *OneTimeKey) UnpickleLibOlm(value []byte) (int, error) {
+	totalReadBytes := 0
+	id, readBytes, err := libolmpickle.UnpickleUInt32(value)
+	if err != nil {
+		return 0, err
+	}
+	totalReadBytes += readBytes
+	c.ID = id
+	published, readBytes, err := libolmpickle.UnpickleBool(value[totalReadBytes:])
+	if err != nil {
+		return 0, err
+	}
+	totalReadBytes += readBytes
+	c.Published = published
+	readBytes, err = c.Key.UnpickleLibOlm(value[totalReadBytes:])
+	if err != nil {
+		return 0, err
+	}
+	totalReadBytes += readBytes
+	return totalReadBytes, nil
+}
+
+// PickleLen returns the number of bytes the pickled OneTimeKey will have.
+func (c OneTimeKey) PickleLen() int {
+	length := 0
+	length += libolmpickle.PickleUInt32Len(c.ID)
+	length += libolmpickle.PickleBoolLen(c.Published)
+	length += c.Key.PickleLen()
+	return length
+}
+
+// KeyIDEncoded returns the base64 encoded id.
+func (c OneTimeKey) KeyIDEncoded() string {
+	resSlice := make([]byte, 4)
+	binary.BigEndian.PutUint32(resSlice, c.ID)
+	return base64.RawStdEncoding.EncodeToString(resSlice)
+}
+
+// PublicKeyEncoded returns the base64 encoded public key
+func (c OneTimeKey) PublicKeyEncoded() id.Curve25519 {
+	return c.Key.PublicKey.B64Encoded()
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/goolm/libolmpickle/pickle.go b/vendor/maunium.net/go/mautrix/crypto/goolm/libolmpickle/pickle.go
new file mode 100644
index 0000000..ec125a3
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/goolm/libolmpickle/pickle.go
@@ -0,0 +1,41 @@
+package libolmpickle
+
+import (
+	"encoding/binary"
+)
+
+func PickleUInt8(value uint8, target []byte) int {
+	target[0] = value
+	return 1
+}
+func PickleUInt8Len(value uint8) int {
+	return 1
+}
+
+func PickleBool(value bool, target []byte) int {
+	if value {
+		target[0] = 0x01
+	} else {
+		target[0] = 0x00
+	}
+	return 1
+}
+func PickleBoolLen(value bool) int {
+	return 1
+}
+
+func PickleBytes(value, target []byte) int {
+	return copy(target, value)
+}
+func PickleBytesLen(value []byte) int {
+	return len(value)
+}
+
+func PickleUInt32(value uint32, target []byte) int {
+	res := make([]byte, 4) //4 bytes for int32
+	binary.BigEndian.PutUint32(res, value)
+	return copy(target, res)
+}
+func PickleUInt32Len(value uint32) int {
+	return 4
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/goolm/libolmpickle/unpickle.go b/vendor/maunium.net/go/mautrix/crypto/goolm/libolmpickle/unpickle.go
new file mode 100644
index 0000000..dbd275a
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/goolm/libolmpickle/unpickle.go
@@ -0,0 +1,53 @@
+package libolmpickle
+
+import (
+	"fmt"
+
+	"maunium.net/go/mautrix/crypto/olm"
+)
+
+func isZeroByteSlice(bytes []byte) bool {
+	b := byte(0)
+	for _, s := range bytes {
+		b |= s
+	}
+	return b == 0
+}
+
+func UnpickleUInt8(value []byte) (uint8, int, error) {
+	if len(value) < 1 {
+		return 0, 0, fmt.Errorf("unpickle uint8: %w", olm.ErrValueTooShort)
+	}
+	return value[0], 1, nil
+}
+
+func UnpickleBool(value []byte) (bool, int, error) {
+	if len(value) < 1 {
+		return false, 0, fmt.Errorf("unpickle bool: %w", olm.ErrValueTooShort)
+	}
+	return value[0] != uint8(0x00), 1, nil
+}
+
+func UnpickleBytes(value []byte, length int) ([]byte, int, error) {
+	if len(value) < length {
+		return nil, 0, fmt.Errorf("unpickle bytes: %w", olm.ErrValueTooShort)
+	}
+	resp := value[:length]
+	if isZeroByteSlice(resp) {
+		return nil, length, nil
+	}
+	return resp, length, nil
+}
+
+func UnpickleUInt32(value []byte) (uint32, int, error) {
+	if len(value) < 4 {
+		return 0, 0, fmt.Errorf("unpickle uint32: %w", olm.ErrValueTooShort)
+	}
+	var res uint32
+	count := 0
+	for i := 3; i >= 0; i-- {
+		res |= uint32(value[count]) << (8 * i)
+		count++
+	}
+	return res, 4, nil
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/olm/README.md b/vendor/maunium.net/go/mautrix/crypto/olm/README.md
new file mode 100644
index 0000000..7d8086c
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/olm/README.md
@@ -0,0 +1,4 @@
+# Go olm bindings
+Based on [Dhole/go-olm](https://github.com/Dhole/go-olm)
+
+The original project is licensed under the Apache 2.0 license.
diff --git a/vendor/maunium.net/go/mautrix/crypto/olm/account.go b/vendor/maunium.net/go/mautrix/crypto/olm/account.go
new file mode 100644
index 0000000..3271b1c
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/olm/account.go
@@ -0,0 +1,113 @@
+// Copyright (c) 2024 Sumner Evans
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package olm
+
+import (
+	"io"
+
+	"maunium.net/go/mautrix/id"
+)
+
+type Account interface {
+	// Pickle returns an Account as a base64 string. Encrypts the Account using the
+	// supplied key.
+	Pickle(key []byte) ([]byte, error)
+
+	// Unpickle loads an Account from a pickled base64 string. Decrypts the
+	// Account using the supplied key. Returns error on failure.
+	Unpickle(pickled, key []byte) error
+
+	// IdentityKeysJSON returns the public parts of the identity keys for the Account.
+	IdentityKeysJSON() ([]byte, error)
+
+	// IdentityKeys returns the public parts of the Ed25519 and Curve25519 identity
+	// keys for the Account.
+	IdentityKeys() (id.Ed25519, id.Curve25519, error)
+
+	// Sign returns the signature of a message using the ed25519 key for this
+	// Account.
+	Sign(message []byte) ([]byte, error)
+
+	// OneTimeKeys returns the public parts of the unpublished one time keys for
+	// the Account.
+	//
+	// The returned data is a struct with the single value "Curve25519", which is
+	// itself an object mapping key id to base64-encoded Curve25519 key.  For
+	// example:
+	//
+	//	{
+	//	    Curve25519: {
+	//	        "AAAAAA": "wo76WcYtb0Vk/pBOdmduiGJ0wIEjW4IBMbbQn7aSnTo",
+	//	        "AAAAAB": "LRvjo46L1X2vx69sS9QNFD29HWulxrmW11Up5AfAjgU"
+	//	    }
+	//	}
+	OneTimeKeys() (map[string]id.Curve25519, error)
+
+	// MarkKeysAsPublished marks the current set of one time keys as being
+	// published.
+	MarkKeysAsPublished()
+
+	// MaxNumberOfOneTimeKeys returns the largest number of one time keys this
+	// Account can store.
+	MaxNumberOfOneTimeKeys() uint
+
+	// GenOneTimeKeys generates a number of new one time keys.  If the total
+	// number of keys stored by this Account exceeds MaxNumberOfOneTimeKeys
+	// then the old keys are discarded. Reads random data from the given
+	// reader, or if nil is passed, defaults to crypto/rand.
+	GenOneTimeKeys(reader io.Reader, num uint) error
+
+	// NewOutboundSession creates a new out-bound session for sending messages to a
+	// given curve25519 identityKey and oneTimeKey.  Returns error on failure.  If the
+	// keys couldn't be decoded as base64 then the error will be "INVALID_BASE64"
+	NewOutboundSession(theirIdentityKey, theirOneTimeKey id.Curve25519) (Session, error)
+
+	// NewInboundSession creates a new in-bound session for sending/receiving
+	// messages from an incoming PRE_KEY message.  Returns error on failure.  If
+	// the base64 couldn't be decoded then the error will be "INVALID_BASE64".  If
+	// the message was for an unsupported protocol version then the error will be
+	// "BAD_MESSAGE_VERSION".  If the message couldn't be decoded then then the
+	// error will be "BAD_MESSAGE_FORMAT".  If the message refers to an unknown one
+	// time key then the error will be "BAD_MESSAGE_KEY_ID".
+	NewInboundSession(oneTimeKeyMsg string) (Session, error)
+
+	// NewInboundSessionFrom creates a new in-bound session for sending/receiving
+	// messages from an incoming PRE_KEY message.  Returns error on failure.  If
+	// the base64 couldn't be decoded then the error will be "INVALID_BASE64".  If
+	// the message was for an unsupported protocol version then the error will be
+	// "BAD_MESSAGE_VERSION".  If the message couldn't be decoded then then the
+	// error will be "BAD_MESSAGE_FORMAT".  If the message refers to an unknown one
+	// time key then the error will be "BAD_MESSAGE_KEY_ID".
+	NewInboundSessionFrom(theirIdentityKey *id.Curve25519, oneTimeKeyMsg string) (Session, error)
+
+	// RemoveOneTimeKeys removes the one time keys that the session used from the
+	// Account.  Returns error on failure.  If the Account doesn't have any
+	// matching one time keys then the error will be "BAD_MESSAGE_KEY_ID".
+	RemoveOneTimeKeys(s Session) error
+}
+
+var InitBlankAccount func() Account
+var InitNewAccount func(io.Reader) (Account, error)
+var InitNewAccountFromPickled func(pickled, key []byte) (Account, error)
+
+// NewAccount creates a new Account.
+func NewAccount(r io.Reader) (Account, error) {
+	return InitNewAccount(r)
+}
+
+func NewBlankAccount() Account {
+	return InitBlankAccount()
+}
+
+// AccountFromPickled loads an Account from a pickled base64 string.  Decrypts
+// the Account using the supplied key.  Returns error on failure.  If the key
+// doesn't match the one used to encrypt the Account then the error will be
+// "BAD_ACCOUNT_KEY".  If the base64 couldn't be decoded then the error will be
+// "INVALID_BASE64".
+func AccountFromPickled(pickled, key []byte) (Account, error) {
+	return InitNewAccountFromPickled(pickled, key)
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/olm/errors.go b/vendor/maunium.net/go/mautrix/crypto/olm/errors.go
new file mode 100644
index 0000000..c80b82e
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/olm/errors.go
@@ -0,0 +1,60 @@
+// Copyright (c) 2024 Sumner Evans
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package olm
+
+import "errors"
+
+// Those are the most common used errors
+var (
+	ErrBadSignature         = errors.New("bad signature")
+	ErrBadMAC               = errors.New("bad mac")
+	ErrBadMessageFormat     = errors.New("bad message format")
+	ErrBadVerification      = errors.New("bad verification")
+	ErrWrongProtocolVersion = errors.New("wrong protocol version")
+	ErrEmptyInput           = errors.New("empty input")
+	ErrNoKeyProvided        = errors.New("no key")
+	ErrBadMessageKeyID      = errors.New("bad message key id")
+	ErrRatchetNotAvailable  = errors.New("ratchet not available: attempt to decode a message whose index is earlier than our earliest known session key")
+	ErrMsgIndexTooHigh      = errors.New("message index too high")
+	ErrProtocolViolation    = errors.New("not protocol message order")
+	ErrMessageKeyNotFound   = errors.New("message key not found")
+	ErrChainTooHigh         = errors.New("chain index too high")
+	ErrBadInput             = errors.New("bad input")
+	ErrBadVersion           = errors.New("wrong version")
+	ErrWrongPickleVersion   = errors.New("wrong pickle version")
+	ErrValueTooShort        = errors.New("value too short")
+	ErrInputToSmall         = errors.New("input too small (truncated?)")
+	ErrOverflow             = errors.New("overflow")
+)
+
+// Error codes from go-olm
+var (
+	EmptyInput         = errors.New("empty input")
+	NoKeyProvided      = errors.New("no pickle key provided")
+	NotEnoughGoRandom  = errors.New("couldn't get enough randomness from crypto/rand")
+	SignatureNotFound  = errors.New("input JSON doesn't contain signature from specified device")
+	InputNotJSONString = errors.New("input doesn't look like a JSON string")
+)
+
+// Error codes from olm code
+var (
+	NotEnoughRandom        = errors.New("not enough entropy was supplied")
+	OutputBufferTooSmall   = errors.New("supplied output buffer is too small")
+	BadMessageVersion      = errors.New("the message version is unsupported")
+	BadMessageFormat       = errors.New("the message couldn't be decoded")
+	BadMessageMAC          = errors.New("the message couldn't be decrypted")
+	BadMessageKeyID        = errors.New("the message references an unknown key ID")
+	InvalidBase64          = errors.New("the input base64 was invalid")
+	BadAccountKey          = errors.New("the supplied account key is invalid")
+	UnknownPickleVersion   = errors.New("the pickled object is too new")
+	CorruptedPickle        = errors.New("the pickled object couldn't be decoded")
+	BadSessionKey          = errors.New("attempt to initialise an inbound group session from an invalid session key")
+	UnknownMessageIndex    = errors.New("attempt to decode a message whose index is earlier than our earliest known session key")
+	BadLegacyAccountPickle = errors.New("attempt to unpickle an account which uses pickle version 1")
+	BadSignature           = errors.New("received message had a bad signature")
+	InputBufferTooSmall    = errors.New("the input data was too small to be valid")
+)
diff --git a/vendor/maunium.net/go/mautrix/crypto/olm/inboundgroupsession.go b/vendor/maunium.net/go/mautrix/crypto/olm/inboundgroupsession.go
new file mode 100644
index 0000000..8839b48
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/olm/inboundgroupsession.go
@@ -0,0 +1,80 @@
+// Copyright (c) 2024 Sumner Evans
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package olm
+
+import "maunium.net/go/mautrix/id"
+
+type InboundGroupSession interface {
+	// Pickle returns an InboundGroupSession as a base64 string.  Encrypts the
+	// InboundGroupSession using the supplied key.
+	Pickle(key []byte) ([]byte, error)
+
+	// Unpickle loads an [InboundGroupSession] from a pickled base64 string.
+	// Decrypts the [InboundGroupSession] using the supplied key.
+	Unpickle(pickled, key []byte) error
+
+	// Decrypt decrypts a message using the [InboundGroupSession]. Returns the
+	// plain-text and message index on success.  Returns error on failure.  If
+	// the base64 couldn't be decoded then the error will be "INVALID_BASE64".
+	// If the message is for an unsupported version of the protocol then the
+	// error will be "BAD_MESSAGE_VERSION".  If the message couldn't be decoded
+	// then the error will be BAD_MESSAGE_FORMAT".  If the MAC on the message
+	// was invalid then the error will be "BAD_MESSAGE_MAC".  If we do not have
+	// a session key corresponding to the message's index (ie, it was sent
+	// before the session key was shared with us) the error will be
+	// "OLM_UNKNOWN_MESSAGE_INDEX".
+	Decrypt(message []byte) ([]byte, uint, error)
+
+	// ID returns a base64-encoded identifier for this session.
+	ID() id.SessionID
+
+	// FirstKnownIndex returns the first message index we know how to decrypt.
+	FirstKnownIndex() uint32
+
+	// IsVerified check if the session has been verified as a valid session.
+	// (A session is verified either because the original session share was
+	// signed, or because we have subsequently successfully decrypted a
+	// message.)
+	IsVerified() bool
+
+	// Export returns the base64-encoded ratchet key for this session, at the
+	// given index, in a format which can be used by
+	// InboundGroupSession.InboundGroupSessionImport().  Encrypts the
+	// InboundGroupSession using the supplied key.  Returns error on failure.
+	// if we do not have a session key corresponding to the given index (ie, it
+	// was sent before the session key was shared with us) the error will be
+	// "OLM_UNKNOWN_MESSAGE_INDEX".
+	Export(messageIndex uint32) ([]byte, error)
+}
+
+var InitInboundGroupSessionFromPickled func(pickled, key []byte) (InboundGroupSession, error)
+var InitNewInboundGroupSession func(sessionKey []byte) (InboundGroupSession, error)
+var InitInboundGroupSessionImport func(sessionKey []byte) (InboundGroupSession, error)
+var InitBlankInboundGroupSession func() InboundGroupSession
+
+// InboundGroupSessionFromPickled loads an InboundGroupSession from a pickled
+// base64 string. Decrypts the InboundGroupSession using the supplied key.
+// Returns error on failure.
+func InboundGroupSessionFromPickled(pickled, key []byte) (InboundGroupSession, error) {
+	return InitInboundGroupSessionFromPickled(pickled, key)
+}
+
+// NewInboundGroupSession creates a new inbound group session from a key
+// exported from OutboundGroupSession.Key(). Returns error on failure.
+func NewInboundGroupSession(sessionKey []byte) (InboundGroupSession, error) {
+	return InitNewInboundGroupSession(sessionKey)
+}
+
+// InboundGroupSessionImport imports an inbound group session from a previous
+// export. Returns error on failure.
+func InboundGroupSessionImport(sessionKey []byte) (InboundGroupSession, error) {
+	return InitInboundGroupSessionImport(sessionKey)
+}
+
+func NewBlankInboundGroupSession() InboundGroupSession {
+	return InitBlankInboundGroupSession()
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/olm/olm.go b/vendor/maunium.net/go/mautrix/crypto/olm/olm.go
new file mode 100644
index 0000000..fa2345e
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/olm/olm.go
@@ -0,0 +1,20 @@
+// Copyright (c) 2024 Sumner Evans
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package olm
+
+var GetVersion func() (major, minor, patch uint8)
+var SetPickleKeyImpl func(key []byte)
+
+// Version returns the version number of the olm library.
+func Version() (major, minor, patch uint8) {
+	return GetVersion()
+}
+
+// SetPickleKey sets the global pickle key used when encoding structs with Gob or JSON.
+func SetPickleKey(key []byte) {
+	SetPickleKeyImpl(key)
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/olm/outboundgroupsession.go b/vendor/maunium.net/go/mautrix/crypto/olm/outboundgroupsession.go
new file mode 100644
index 0000000..c5b7bcb
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/olm/outboundgroupsession.go
@@ -0,0 +1,57 @@
+// Copyright (c) 2024 Sumner Evans
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package olm
+
+import "maunium.net/go/mautrix/id"
+
+type OutboundGroupSession interface {
+	// Pickle returns a Session as a base64 string. Encrypts the Session using
+	// the supplied key.
+	Pickle(key []byte) ([]byte, error)
+
+	// Unpickle loads an [OutboundGroupSession] from a pickled base64 string.
+	// Decrypts the [OutboundGroupSession] using the supplied key.
+	Unpickle(pickled, key []byte) error
+
+	// Encrypt encrypts a message using the [OutboundGroupSession]. Returns the
+	// encrypted message as base64.
+	Encrypt(plaintext []byte) ([]byte, error)
+
+	// ID returns a base64-encoded identifier for this session.
+	ID() id.SessionID
+
+	// MessageIndex returns the message index for this session.  Each message
+	// is sent with an increasing index; this returns the index for the next
+	// message.
+	MessageIndex() uint
+
+	// Key returns the base64-encoded current ratchet key for this session.
+	Key() string
+}
+
+var InitNewOutboundGroupSessionFromPickled func(pickled, key []byte) (OutboundGroupSession, error)
+var InitNewOutboundGroupSession func() OutboundGroupSession
+var InitNewBlankOutboundGroupSession func() OutboundGroupSession
+
+// OutboundGroupSessionFromPickled loads an OutboundGroupSession from a pickled
+// base64 string.  Decrypts the OutboundGroupSession using the supplied key.
+// Returns error on failure.  If the key doesn't match the one used to encrypt
+// the OutboundGroupSession then the error will be "BAD_SESSION_KEY".  If the
+// base64 couldn't be decoded then the error will be "INVALID_BASE64".
+func OutboundGroupSessionFromPickled(pickled, key []byte) (OutboundGroupSession, error) {
+	return InitNewOutboundGroupSessionFromPickled(pickled, key)
+}
+
+// NewOutboundGroupSession creates a new outbound group session.
+func NewOutboundGroupSession() OutboundGroupSession {
+	return InitNewOutboundGroupSession()
+}
+
+// NewBlankOutboundGroupSession initialises an empty [OutboundGroupSession].
+func NewBlankOutboundGroupSession() OutboundGroupSession {
+	return InitNewBlankOutboundGroupSession()
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/olm/pk.go b/vendor/maunium.net/go/mautrix/crypto/olm/pk.go
new file mode 100644
index 0000000..70ee452
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/olm/pk.go
@@ -0,0 +1,57 @@
+// Copyright (c) 2024 Sumner Evans
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package olm
+
+import (
+	"maunium.net/go/mautrix/id"
+)
+
+// PKSigning is an interface for signing messages.
+type PKSigning interface {
+	// Seed returns the seed of the key.
+	Seed() []byte
+
+	// PublicKey returns the public key.
+	PublicKey() id.Ed25519
+
+	// Sign creates a signature for the given message using this key.
+	Sign(message []byte) ([]byte, error)
+
+	// SignJSON creates a signature for the given object after encoding it to
+	// canonical JSON.
+	SignJSON(obj any) (string, error)
+}
+
+// PKDecryption is an interface for decrypting messages.
+type PKDecryption interface {
+	// PublicKey returns the public key.
+	PublicKey() id.Curve25519
+
+	// Decrypt verifies and decrypts the given message.
+	Decrypt(ephemeralKey, mac, ciphertext []byte) ([]byte, error)
+}
+
+var InitNewPKSigning func() (PKSigning, error)
+var InitNewPKSigningFromSeed func(seed []byte) (PKSigning, error)
+var InitNewPKDecryptionFromPrivateKey func(privateKey []byte) (PKDecryption, error)
+
+// NewPKSigning creates a new [PKSigning] object, containing a key pair for
+// signing messages.
+func NewPKSigning() (PKSigning, error) {
+	return InitNewPKSigning()
+}
+
+// NewPKSigningFromSeed creates a new PKSigning object using the given seed.
+func NewPKSigningFromSeed(seed []byte) (PKSigning, error) {
+	return InitNewPKSigningFromSeed(seed)
+}
+
+// NewPKDecryptionFromPrivateKey creates a new [PKDecryption] from a
+// base64-encoded private key.
+func NewPKDecryptionFromPrivateKey(privateKey []byte) (PKDecryption, error) {
+	return InitNewPKDecryptionFromPrivateKey(privateKey)
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/olm/session.go b/vendor/maunium.net/go/mautrix/crypto/olm/session.go
new file mode 100644
index 0000000..c4b91ff
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/olm/session.go
@@ -0,0 +1,83 @@
+// Copyright (c) 2024 Sumner Evans
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package olm
+
+import "maunium.net/go/mautrix/id"
+
+type Session interface {
+	// Pickle returns a Session as a base64 string. Encrypts the Session using
+	// the supplied key.
+	Pickle(key []byte) ([]byte, error)
+
+	// Unpickle loads a Session from a pickled base64 string. Decrypts the
+	// Session using the supplied key.
+	Unpickle(pickled, key []byte) error
+
+	// ID returns an identifier for this Session. Will be the same for both
+	// ends of the conversation.
+	ID() id.SessionID
+
+	// HasReceivedMessage returns true if this session has received any
+	// message.
+	HasReceivedMessage() bool
+
+	// MatchesInboundSession checks if the PRE_KEY message is for this in-bound
+	// Session. This can happen if multiple messages are sent to this Account
+	// before this Account sends a message in reply. Returns true if the
+	// session matches. Returns false if the session does not match. Returns
+	// error on failure. If the base64 couldn't be decoded then the error will
+	// be "INVALID_BASE64". If the message was for an unsupported protocol
+	// version then the error will be "BAD_MESSAGE_VERSION". If the message
+	// couldn't be decoded then then the error will be "BAD_MESSAGE_FORMAT".
+	MatchesInboundSession(oneTimeKeyMsg string) (bool, error)
+
+	// MatchesInboundSessionFrom checks if the PRE_KEY message is for this
+	// in-bound Session. This can happen if multiple messages are sent to this
+	// Account before this Account sends a message in reply. Returns true if
+	// the session matches. Returns false if the session does not match.
+	// Returns error on failure. If the base64 couldn't be decoded then the
+	// error will be "INVALID_BASE64". If the message was for an unsupported
+	// protocol version then the error will be "BAD_MESSAGE_VERSION". If the
+	// message couldn't be decoded then then the error will be
+	// "BAD_MESSAGE_FORMAT".
+	MatchesInboundSessionFrom(theirIdentityKey, oneTimeKeyMsg string) (bool, error)
+
+	// EncryptMsgType returns the type of the next message that Encrypt will
+	// return. Returns MsgTypePreKey if the message will be a PRE_KEY message.
+	// Returns MsgTypeMsg if the message will be a normal message.
+	EncryptMsgType() id.OlmMsgType
+
+	// Encrypt encrypts a message using the Session. Returns the encrypted
+	// message as base64.
+	Encrypt(plaintext []byte) (id.OlmMsgType, []byte, error)
+
+	// Decrypt decrypts a message using the Session. Returns the plain-text on
+	// success. Returns error on failure. If the base64 couldn't be decoded
+	// then the error will be "INVALID_BASE64". If the message is for an
+	// unsupported version of the protocol then the error will be
+	// "BAD_MESSAGE_VERSION". If the message couldn't be decoded then the error
+	// will be BAD_MESSAGE_FORMAT". If the MAC on the message was invalid then
+	// the error will be "BAD_MESSAGE_MAC".
+	Decrypt(message string, msgType id.OlmMsgType) ([]byte, error)
+
+	// Describe generates a string describing the internal state of an olm
+	// session for debugging and logging purposes.
+	Describe() string
+}
+
+var InitSessionFromPickled func(pickled, key []byte) (Session, error)
+var InitNewBlankSession func() Session
+
+// SessionFromPickled loads a Session from a pickled base64 string.  Decrypts
+// the Session using the supplied key.  Returns error on failure.
+func SessionFromPickled(pickled, key []byte) (Session, error) {
+	return InitSessionFromPickled(pickled, key)
+}
+
+func NewBlankSession() Session {
+	return InitNewBlankSession()
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/pkcs7/pkcs7.go b/vendor/maunium.net/go/mautrix/crypto/pkcs7/pkcs7.go
new file mode 100644
index 0000000..dc28ed6
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/pkcs7/pkcs7.go
@@ -0,0 +1,30 @@
+// Copyright (c) 2024 Sumner Evans
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package pkcs7
+
+import "bytes"
+
+// Pad implements PKCS#7 padding as defined in [RFC2315]. It pads the data to
+// the given blockSize in the range [1, 255]. This is normally used in AES-CBC
+// encryption.
+//
+// [RFC2315]: https://www.ietf.org/rfc/rfc2315.txt
+func Pad(data []byte, blockSize int) []byte {
+	padding := blockSize - len(data)%blockSize
+	return append(data, bytes.Repeat([]byte{byte(padding)}, padding)...)
+}
+
+// Unpad implements PKCS#7 unpadding as defined in [RFC2315]. It unpads the
+// data by reading the padding amount from the last byte of the data. This is
+// normally used in AES-CBC decryption.
+//
+// [RFC2315]: https://www.ietf.org/rfc/rfc2315.txt
+func Unpad(data []byte) []byte {
+	length := len(data)
+	unpadding := int(data[length-1])
+	return data[:length-unpadding]
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/signatures/signatures.go b/vendor/maunium.net/go/mautrix/crypto/signatures/signatures.go
new file mode 100644
index 0000000..0c4422f
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/signatures/signatures.go
@@ -0,0 +1,94 @@
+// Copyright (c) 2024 Sumner Evans
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package signatures
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"errors"
+	"fmt"
+
+	"github.com/tidwall/gjson"
+	"github.com/tidwall/sjson"
+	"go.mau.fi/util/exgjson"
+
+	"maunium.net/go/mautrix/crypto/canonicaljson"
+	"maunium.net/go/mautrix/crypto/goolm/crypto"
+	"maunium.net/go/mautrix/id"
+)
+
+var (
+	ErrEmptyInput        = errors.New("empty input")
+	ErrSignatureNotFound = errors.New("input JSON doesn't contain signature from specified device")
+)
+
+// Signatures represents a set of signatures for some data from multiple users
+// and keys.
+type Signatures map[id.UserID]map[id.KeyID]string
+
+// NewSingleSignature creates a new [Signatures] object with a single
+// signature.
+func NewSingleSignature(userID id.UserID, algorithm id.KeyAlgorithm, keyID string, signature string) Signatures {
+	return Signatures{
+		userID: {
+			id.NewKeyID(algorithm, keyID): signature,
+		},
+	}
+}
+
+// VerifySignature verifies an Ed25519 signature.
+func VerifySignature(message []byte, key id.Ed25519, signature []byte) (ok bool, err error) {
+	if len(message) == 0 || len(key) == 0 || len(signature) == 0 {
+		return false, ErrEmptyInput
+	}
+	keyDecoded, err := base64.RawStdEncoding.DecodeString(key.String())
+	if err != nil {
+		return false, err
+	}
+	publicKey := crypto.Ed25519PublicKey(keyDecoded)
+	return publicKey.Verify(message, signature), nil
+}
+
+// VerifySignatureJSON verifies the signature in the given JSON object "obj"
+// as described in [Appendix 3] of the Matrix Spec.
+//
+// This function is a wrapper over [Utility.VerifySignatureJSON] that creates
+// and destroys the [Utility] object transparently.
+//
+// If the "obj" is not already a [json.RawMessage], it will re-encoded as JSON
+// for the verification, so "json" tags will be honored.
+//
+// [Appendix 3]: https://spec.matrix.org/v1.9/appendices/#signing-json
+func VerifySignatureJSON(obj any, userID id.UserID, keyName string, key id.Ed25519) (bool, error) {
+	var err error
+	objJSON, ok := obj.(json.RawMessage)
+	if !ok {
+		objJSON, err = json.Marshal(obj)
+		if err != nil {
+			return false, err
+		}
+	}
+
+	sig := gjson.GetBytes(objJSON, exgjson.Path("signatures", string(userID), fmt.Sprintf("ed25519:%s", keyName)))
+	if !sig.Exists() || sig.Type != gjson.String {
+		return false, ErrSignatureNotFound
+	}
+	objJSON, err = sjson.DeleteBytes(objJSON, "unsigned")
+	if err != nil {
+		return false, err
+	}
+	objJSON, err = sjson.DeleteBytes(objJSON, "signatures")
+	if err != nil {
+		return false, err
+	}
+	objJSONString := canonicaljson.CanonicalJSONAssumeValid(objJSON)
+	sigBytes, err := base64.RawStdEncoding.DecodeString(sig.Str)
+	if err != nil {
+		return false, err
+	}
+	return VerifySignature(objJSONString, key, sigBytes)
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/utils/utils.go b/vendor/maunium.net/go/mautrix/crypto/utils/utils.go
new file mode 100644
index 0000000..e2f8a19
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/utils/utils.go
@@ -0,0 +1,132 @@
+// Copyright (c) 2020 Nikos Filippakis
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package utils
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/hmac"
+	"crypto/rand"
+	"crypto/sha256"
+	"crypto/sha512"
+	"encoding/base64"
+	"strings"
+
+	"go.mau.fi/util/base58"
+	"golang.org/x/crypto/hkdf"
+	"golang.org/x/crypto/pbkdf2"
+)
+
+const (
+	// AESCTRKeyLength is the length of the AES256-CTR key used.
+	AESCTRKeyLength = 32
+	// AESCTRIVLength is the length of the AES256-CTR IV used.
+	AESCTRIVLength = 16
+	// HMACKeyLength is the length of the HMAC key used.
+	HMACKeyLength = 32
+	// SHAHashLength is the length of the SHA hash used.
+	SHAHashLength = 32
+)
+
+// XorA256CTR encrypts the input with the keystream generated by the AES256-CTR algorithm with the given arguments.
+func XorA256CTR(source []byte, key [AESCTRKeyLength]byte, iv [AESCTRIVLength]byte) []byte {
+	block, _ := aes.NewCipher(key[:])
+	cipher.NewCTR(block, iv[:]).XORKeyStream(source, source)
+	return source
+}
+
+// GenAttachmentA256CTR generates a new random AES256-CTR key and IV suitable for encrypting attachments.
+func GenAttachmentA256CTR() (key [AESCTRKeyLength]byte, iv [AESCTRIVLength]byte) {
+	_, err := rand.Read(key[:])
+	if err != nil {
+		panic(err)
+	}
+
+	// The last 8 bytes of the IV act as the counter in AES-CTR, which means they're left empty here
+	_, err = rand.Read(iv[:8])
+	if err != nil {
+		panic(err)
+	}
+	return
+}
+
+// GenA256CTRIV generates a random IV for AES256-CTR with the last bit set to zero.
+func GenA256CTRIV() (iv [AESCTRIVLength]byte) {
+	_, err := rand.Read(iv[:])
+	if err != nil {
+		panic(err)
+	}
+	iv[8] &= 0x7F
+	return
+}
+
+// DeriveKeysSHA256 derives an AES and a HMAC key from the given recovery key.
+func DeriveKeysSHA256(key []byte, name string) ([AESCTRKeyLength]byte, [HMACKeyLength]byte) {
+	var zeroBytes [32]byte
+
+	derivedHkdf := hkdf.New(sha256.New, key[:], zeroBytes[:], []byte(name))
+
+	var aesKey [AESCTRKeyLength]byte
+	var hmacKey [HMACKeyLength]byte
+	derivedHkdf.Read(aesKey[:])
+	derivedHkdf.Read(hmacKey[:])
+
+	return aesKey, hmacKey
+}
+
+// PBKDF2SHA512 generates a key of the given bit-length using the given passphrase, salt and iteration count.
+func PBKDF2SHA512(password []byte, salt []byte, iters int, keyLenBits int) []byte {
+	return pbkdf2.Key(password, salt, iters, keyLenBits/8, sha512.New)
+}
+
+// DecodeBase58RecoveryKey recovers the secret storage from a recovery key.
+func DecodeBase58RecoveryKey(recoveryKey string) []byte {
+	noSpaces := strings.ReplaceAll(recoveryKey, " ", "")
+	decoded := base58.Decode(noSpaces)
+	if len(decoded) != AESCTRKeyLength+3 { // AESCTRKeyLength bytes key and 3 bytes prefix / parity
+		return nil
+	}
+	var parity byte
+	for _, b := range decoded[:34] {
+		parity ^= b
+	}
+	if parity != decoded[34] || decoded[0] != 0x8B || decoded[1] != 1 {
+		return nil
+	}
+	return decoded[2:34]
+}
+
+// EncodeBase58RecoveryKey recovers the secret storage from a recovery key.
+func EncodeBase58RecoveryKey(key []byte) string {
+	var inputBytes [35]byte
+	copy(inputBytes[2:34], key[:])
+	inputBytes[0] = 0x8B
+	inputBytes[1] = 1
+
+	var parity byte
+	for _, b := range inputBytes[:34] {
+		parity ^= b
+	}
+	inputBytes[34] = parity
+	recoveryKey := base58.Encode(inputBytes[:])
+
+	var spacedKey string
+	for i, c := range recoveryKey {
+		if i > 0 && i%4 == 0 {
+			spacedKey += " "
+		}
+		spacedKey += string(c)
+	}
+	return spacedKey
+}
+
+// HMACSHA256B64 calculates the unpadded base64 of the SHA256 hmac of the input with the given key.
+func HMACSHA256B64(input []byte, hmacKey [HMACKeyLength]byte) string {
+	h := hmac.New(sha256.New, hmacKey[:])
+	h.Write(input)
+	return base64.RawStdEncoding.EncodeToString(h.Sum(nil))
+}