diff --git a/include/mtx/requests.hpp b/include/mtx/requests.hpp
index 0177e43d901ec865085ccca46a2592d823f75aef..9c392958faea1047f9a6af1db7a628f0b9889f67 100644
--- a/include/mtx/requests.hpp
+++ b/include/mtx/requests.hpp
@@ -281,6 +281,22 @@ struct KeySignaturesUpload
 void
 to_json(json &obj, const KeySignaturesUpload &req);
 
+//! Upload cross signing keys
+struct DeviceSigningUpload
+{
+    //! Optional. The user's master key.
+    std::optional<mtx::crypto::CrossSigningKeys> master_key;
+    //! Optional. The user's self-signing key. Must be signed by the accompanying master key, or by
+    //! the user's most recently uploaded master key if no master key is included in the request.
+    std::optional<mtx::crypto::CrossSigningKeys> self_signing_key;
+    //! Optional. The user's user-signing key. Must be signed by the accompanying master key, or by
+    //! the user's most recently uploaded master key if no master key is included in the request.
+    std::optional<mtx::crypto::CrossSigningKeys> user_signing_key;
+};
+
+void
+to_json(json &obj, const DeviceSigningUpload &req);
+
 struct PusherData
 {
     //! Required if `kind` is http. The URL to use to send notifications to.
diff --git a/include/mtx/responses/common.hpp b/include/mtx/responses/common.hpp
index 753991bc32187c0da9b4bac277a971b304670b1c..fdf7b395122d674ef5364c606714a6deb71bd2d0 100644
--- a/include/mtx/responses/common.hpp
+++ b/include/mtx/responses/common.hpp
@@ -57,6 +57,15 @@ struct FilterId
 void
 from_json(const nlohmann::json &obj, FilterId &response);
 
+//! A new room version as returned by the room_keys/version API
+struct Version
+{
+    //! Required: The backup version. This is an opaque string.
+    std::string version;
+};
+
+void
+from_json(const nlohmann::json &obj, Version &response);
 //! Different helper for parsing responses.
 namespace utils {
 //! Multiple account_data events.
diff --git a/include/mtxclient/crypto/client.hpp b/include/mtxclient/crypto/client.hpp
index 2542cae12bfa2774437993ef3e569c1ff652bed8..eb4ff14e49e7364e286d2f2b044492095bbb6fb0 100644
--- a/include/mtxclient/crypto/client.hpp
+++ b/include/mtxclient/crypto/client.hpp
@@ -188,15 +188,22 @@ struct PkSigning
 {
     //! Construct from base64 key
     static PkSigning from_seed(std::string seed);
+    //! construct a new random key
+    static PkSigning new_key();
+
+    //! sign an arbitrary message
     std::string sign(const std::string &message);
 
     //! base64 public key
     std::string public_key() const { return public_key_; }
+    //! base64 private key (seed)
+    std::string seed() const { return seed_; }
 
 private:
     PkSigning() {}
     std::unique_ptr<OlmPkSigning, OlmDeleter> signing;
-    std::string public_key_;
+    std::string public_key_; // base64
+    std::string seed_;       // base64
 };
 
 //! Client for all the cryptography related functionality like olm accounts, session keys
@@ -216,6 +223,35 @@ public:
     //! A signed set of one time keys indexed by `<algorithm>:<key_id>`.
     using SignedOneTimeKeys = std::map<std::string, requests::SignedOneTimeKey>;
 
+    //! Data needed for bootstrapping crosssigning
+    struct CrossSigningSetup
+    {
+        //! The public key objects, signed and ready for upload.
+        CrossSigningKeys master_key, user_signing_key, self_signing_key;
+        //! The private keys to store in SSSS
+        std::string private_master_key, private_user_signing_key,
+          private_self_signing_key; // base64
+    };
+
+    //! Data needed to setup the online key backup
+    struct OnlineKeyBackupSetup
+    {
+        //! private key to decrypt sessions with.
+        mtx::crypto::BinaryBuf privateKey;
+        //! The backup version data including auth data to be sent to the server.
+        mtx::responses::backup::BackupVersion backupVersion;
+    };
+    //! Data needed to setup SSSS
+    struct SSSSSetup
+    {
+        //! Key to encrypt/decrypt secrets with.
+        mtx::crypto::BinaryBuf privateKey;
+        //! The key description to be stored in account data.
+        mtx::secret_storage::AesHmacSha2KeyDescription keyDescription;
+        //! The name of this key.
+        std::string key_name;
+    };
+
     //! Set the id of this device.
     void set_device_id(std::string device_id) { device_id_ = std::move(device_id); }
     //! Set the id of this user.
@@ -259,6 +295,15 @@ public:
     //! Prepare an empty /keys/upload request.
     mtx::requests::UploadKeys create_upload_keys_request();
 
+    //! Create the cross-signing keys (including signatures). Needs to be uploaded to the server
+    //! after this.
+    std::optional<CrossSigningSetup> create_crosssigning_keys();
+
+    //! Create a new online key backup. Needs to be uploaded to the server after this.
+    std::optional<OnlineKeyBackupSetup> create_online_key_backup(const std::string &masterKey);
+    //! Create a new SSSS storage key. Should be uploaded to account_data. The password is optional.
+    static std::optional<SSSSSetup> create_ssss_key(const std::string &password = "");
+
     //! Decrypt a message using megolm.
     GroupPlaintext decrypt_group_message(OlmInboundGroupSession *session,
                                          const std::string &message,
diff --git a/include/mtxclient/crypto/utils.hpp b/include/mtxclient/crypto/utils.hpp
index 7f0bca7d73722eece1df9df7e4ef2c9c105874a6..868c2581074a3a23f4cf258ee1e2811faee7a015 100644
--- a/include/mtxclient/crypto/utils.hpp
+++ b/include/mtxclient/crypto/utils.hpp
@@ -53,6 +53,14 @@ to_string(const BinaryBuf &buf)
     return std::string(reinterpret_cast<const char *>(buf.data()), buf.size());
 }
 
+//! Sets bit 63 to 0 to be compatible with other AES implementations.
+BinaryBuf
+compatible_iv(BinaryBuf incompatible_iv);
+
+//! encodes a recovery key in base58 with parity and version tag,
+std::string
+key_to_recoverykey(const BinaryBuf &key);
+
 //! Simple wrapper around the OpenSSL PKCS5_PBKDF2_HMAC function
 BinaryBuf
 PBKDF2_HMAC_SHA_512(const std::string pass,
@@ -77,6 +85,10 @@ decrypt(const mtx::secret_storage::AesHmacSha2EncryptedData &data,
         BinaryBuf decryptionKey,
         const std::string key_name);
 
+//! Encrypt a secret for SSSS
+mtx::secret_storage::AesHmacSha2EncryptedData
+encrypt(const std::string &data, BinaryBuf decryptionKey, const std::string key_name);
+
 //! HKDF key derivation with SHA256 digest
 struct HkdfKeys
 {
diff --git a/include/mtxclient/http/client.hpp b/include/mtxclient/http/client.hpp
index 94fae087d57268fd3b0c8a1448517b9ebb89d0a9..042e0a97c9d3bdddfbb1a5d77cb1765aa4b8f2ef 100644
--- a/include/mtxclient/http/client.hpp
+++ b/include/mtxclient/http/client.hpp
@@ -58,6 +58,7 @@ struct CreateRoom;
 struct EventId;
 struct RoomId;
 struct FilterId;
+struct Version;
 struct GroupId;
 struct GroupProfile;
 struct JoinedGroups;
@@ -117,6 +118,30 @@ template<class Response>
 using HeadersCallback    = std::function<void(const Response &, HeaderFields, RequestErr)>;
 using TypeErasedCallback = std::function<void(HeaderFields, const std::string_view &, int, int)>;
 
+//! A helper to handle user interactive authentication. This will cache the request and call the
+//! prompt every time there is a new stage. Advance the flow by calling next().
+class UIAHandler
+{
+public:
+    //! The callback for when a new UIA stage needs to be completed
+    using UIAPrompt =
+      std::function<void(const UIAHandler &, const user_interactive::Unauthorized &)>;
+
+    //! Create a new UIA handler. Pass a callback for when a new stage needs to be completed.
+    UIAHandler(UIAPrompt prompt_)
+      : prompt(std::move(prompt_))
+    {}
+
+    void next(const user_interactive::Auth &auth) const;
+
+private:
+    UIAPrompt prompt;
+
+    std::function<void(const UIAHandler &, const nlohmann::json &)> next_;
+
+    friend class Client;
+};
+
 //! Sync configuration options.
 struct SyncOpts
 {
@@ -242,6 +267,13 @@ public:
                       const user_interactive::Auth &auth,
                       Callback<mtx::responses::Register> cb);
 
+    //! Register with an UIA handler so you don't need to repeat the request manually.
+    //! register failed with 401
+    void registration(const std::string &user,
+                      const std::string &pass,
+                      UIAHandler uia_handler,
+                      Callback<mtx::responses::Register> cb);
+
     //! Check the validity of a registration token
     void registration_token_validity(const std::string token,
                                      Callback<mtx::responses::RegistrationTokenValidity> cb);
@@ -564,6 +596,11 @@ public:
     void keys_signatures_upload(const mtx::requests::KeySignaturesUpload &req,
                                 Callback<mtx::responses::KeySignaturesUpload> cb);
 
+    //! Upload cross signing keys
+    void device_signing_upload(const mtx::requests::DeviceSigningUpload,
+                               UIAHandler uia_handler,
+                               ErrCallback cb);
+
     //! Returns the current devices and identity keys for the given users.
     void query_keys(const mtx::requests::QueryKeys &req, Callback<mtx::responses::QueryKeys> cb);
 
@@ -587,6 +624,9 @@ public:
     void update_backup_version(const std::string &version,
                                const mtx::responses::backup::BackupVersion &data,
                                ErrCallback cb);
+    void post_backup_version(const std::string &algorithm,
+                             const std::string &auth_data,
+                             Callback<mtx::responses::Version> cb);
 
     void room_keys(const std::string &version, Callback<mtx::responses::backup::KeysBackup> cb);
     void room_keys(const std::string &version,
@@ -620,6 +660,18 @@ public:
     void secret_storage_key(const std::string &key_id,
                             Callback<mtx::secret_storage::AesHmacSha2KeyDescription> cb);
 
+    //! Upload a specific secret
+    void upload_secret_storage_secret(const std::string &secret_id,
+                                      const mtx::secret_storage::Secret &secret,
+                                      ErrCallback cb);
+    //! Upload information about a key
+    void upload_secret_storage_key(const std::string &key_id,
+                                   const mtx::secret_storage::AesHmacSha2KeyDescription &desc,
+                                   ErrCallback cb);
+
+    //! Set the default key for the secret storage
+    void set_secret_storage_default_key(const std::string &key_id, ErrCallback cb);
+
     //! Gets any TURN server URIs and authentication credentials
     void get_turn_server(Callback<mtx::responses::TurnServer> cb);
 
diff --git a/lib/crypto/client.cpp b/lib/crypto/client.cpp
index 10db860ddba1e91f59844ff179e478cf20cdea54..668ae0f4766b93efb9ae76a07c2e6e3ecacd09fd 100644
--- a/lib/crypto/client.cpp
+++ b/lib/crypto/client.cpp
@@ -206,6 +206,113 @@ OlmClient::create_upload_keys_request(const mtx::crypto::OneTimeKeys &one_time_k
     return req;
 }
 
+std::optional<OlmClient::CrossSigningSetup>
+OlmClient::create_crosssigning_keys()
+{
+    auto master       = PkSigning::new_key();
+    auto user_signing = PkSigning::new_key();
+    auto self_signing = PkSigning::new_key();
+
+    CrossSigningSetup setup{};
+    setup.private_master_key       = master.seed();
+    setup.private_user_signing_key = user_signing.seed();
+    setup.private_self_signing_key = self_signing.seed();
+
+    // master key
+    setup.master_key.usage                                  = {"master"};
+    setup.master_key.user_id                                = user_id_;
+    setup.master_key.keys["ed25519:" + master.public_key()] = master.public_key();
+
+    nlohmann::json master_j = setup.master_key;
+    master_j.erase("unsigned");
+    master_j.erase("signatures");
+    setup.master_key.signatures[user_id_]["ed25519:" + master.public_key()] =
+      master.sign(master_j.dump());
+    setup.master_key.signatures[user_id_]["ed25519:" + device_id_] = sign_message(master_j.dump());
+
+    // user_signing_key
+    setup.user_signing_key.usage                                        = {"user_signing"};
+    setup.user_signing_key.user_id                                      = user_id_;
+    setup.user_signing_key.keys["ed25519:" + user_signing.public_key()] = user_signing.public_key();
+
+    nlohmann::json user_signing_j = setup.user_signing_key;
+    user_signing_j.erase("unsigned");
+    user_signing_j.erase("signatures");
+    setup.user_signing_key.signatures[user_id_]["ed25519:" + user_signing.public_key()] =
+      user_signing.sign(user_signing_j.dump());
+    setup.user_signing_key.signatures[user_id_]["ed25519:" + master.public_key()] =
+      master.sign(user_signing_j.dump());
+
+    // self_signing_key
+    setup.self_signing_key.usage                                        = {"self_signing"};
+    setup.self_signing_key.user_id                                      = user_id_;
+    setup.self_signing_key.keys["ed25519:" + self_signing.public_key()] = self_signing.public_key();
+
+    nlohmann::json self_signing_j = setup.self_signing_key;
+    self_signing_j.erase("unsigned");
+    self_signing_j.erase("signatures");
+    setup.self_signing_key.signatures[user_id_]["ed25519:" + self_signing.public_key()] =
+      self_signing.sign(self_signing_j.dump());
+    setup.self_signing_key.signatures[user_id_]["ed25519:" + master.public_key()] =
+      master.sign(self_signing_j.dump());
+
+    return setup;
+}
+
+std::optional<OlmClient::OnlineKeyBackupSetup>
+OlmClient::create_online_key_backup(const std::string &masterKey)
+{
+    OnlineKeyBackupSetup setup{};
+
+    auto key         = create_buffer(olm_pk_private_key_length());
+    setup.privateKey = key;
+
+    json auth_data;
+    auth_data["public_key"] = bin2base64_unpadded(CURVE25519_public_key_from_private(key));
+    auto master             = PkSigning::from_seed(masterKey);
+
+    auto sig = master.sign(auth_data.dump());
+    auth_data["signatures"][user_id_]["ed25519:" + master.public_key()] = sig;
+
+    setup.backupVersion.auth_data = auth_data.dump();
+    setup.backupVersion.algorithm = "m.megolm_backup.v1.curve25519-aes-sha2";
+
+    return setup;
+}
+
+std::optional<OlmClient::SSSSSetup>
+OlmClient::create_ssss_key(const std::string &password)
+{
+    OlmClient::SSSSSetup setup{};
+
+    if (password.empty()) {
+        setup.privateKey = create_buffer(32);
+    } else {
+        mtx::secret_storage::PBKDF2 pbkdf2{};
+        pbkdf2.algorithm  = "m.pbkdf2";
+        pbkdf2.iterations = 500'000;
+        pbkdf2.bits       = 256; // 32 * 8
+        pbkdf2.salt       = bin2base64(to_string(create_buffer(32)));
+
+        setup.privateKey = mtx::crypto::PBKDF2_HMAC_SHA_512(
+          password, to_binary_buf(pbkdf2.salt), pbkdf2.iterations, pbkdf2.bits / 8);
+        setup.keyDescription.passphrase = pbkdf2;
+    }
+
+    setup.keyDescription.algorithm = "m.secret_storage.v1.aes-hmac-sha2";
+    setup.keyDescription.name = bin2base58(to_string(create_buffer(16))); // create a random name
+    setup.keyDescription.iv   = bin2base64(to_string(compatible_iv(create_buffer(32))));
+
+    auto testKeys = HKDF_SHA256(setup.privateKey, BinaryBuf(32, 0), BinaryBuf{});
+
+    auto encrypted = AES_CTR_256_Encrypt(
+      std::string(32, '\0'), testKeys.aes, to_binary_buf(base642bin(setup.keyDescription.iv)));
+
+    setup.keyDescription.mac = bin2base64(to_string(HMAC_SHA256(testKeys.mac, encrypted)));
+
+    return setup;
+}
+
 OutboundGroupSessionPtr
 OlmClient::init_outbound_group_session()
 {
@@ -561,10 +668,18 @@ SAS::calculate_mac(std::string input_data, std::string info)
     return to_string(output_buffer);
 }
 
+PkSigning
+PkSigning::new_key()
+{
+    auto priv_seed = bin2base64(to_string(create_buffer(olm_pk_signing_seed_length())));
+    return from_seed(priv_seed);
+}
+
 PkSigning
 PkSigning::from_seed(std::string seed)
 {
     PkSigning s{};
+    s.seed_   = seed;
     s.signing = create_olm_object<PkSigningObject>();
 
     auto seed_ = base642bin(seed);
diff --git a/lib/crypto/encoding.cpp b/lib/crypto/encoding.cpp
index 5589a8343914a8c7aa2b0f79e8a488042c378205..727bc669e84cc9f78a9b284a25a3dd897b278e63 100644
--- a/lib/crypto/encoding.cpp
+++ b/lib/crypto/encoding.cpp
@@ -1,5 +1,6 @@
 #include <algorithm>
 #include <array>
+#include <cassert>
 #include <string>
 #include <vector>
 
@@ -60,23 +61,25 @@ encode_base58(const std::array<char, 58> &alphabet, const std::string &input)
     if (input.empty())
         return "";
 
-    std::vector<uint8_t> digits(input.size() * 137 / 100 + 1);
+    std::vector<uint8_t> digits(input.size() * 138 / 100 + 1);
     std::size_t digitslen = 1;
-    for (uint32_t carry : input) {
+    for (uint8_t carry_ : input) {
+        uint32_t carry = static_cast<uint32_t>(carry_);
         for (size_t j = 0; j < digitslen; j++) {
-            carry += (uint32_t)(digits[j]) << 8;
+            carry += (uint32_t)(digits[j]) * 256;
             digits[j] = static_cast<uint8_t>(carry % 58);
             carry /= 58;
         }
         while (carry > 0) {
+            assert(digitslen < digits.size());
             digits[digitslen++] = static_cast<uint8_t>(carry % 58);
             carry /= 58;
         }
     }
-    std::size_t resultlen = 0;
     std::string result(digits.size(), ' ');
 
     // leading zero bytes
+    std::size_t resultlen = 0;
     for (; resultlen < input.length() && input[resultlen] == 0;)
         result[resultlen++] = '1';
 
diff --git a/lib/crypto/utils.cpp b/lib/crypto/utils.cpp
index 96ca7a567a39e0cc8b547e0b83f61f7117388694..7457779147968bdffeb89f78afdbf17c1d80f59a 100644
--- a/lib/crypto/utils.cpp
+++ b/lib/crypto/utils.cpp
@@ -108,6 +108,22 @@ key_from_recoverykey(const std::string &recoverykey,
     return decryptionKey;
 }
 
+std::string
+key_to_recoverykey(const BinaryBuf &key)
+{
+    auto buf = BinaryBuf(key.size() + 3);
+    buf[0]   = 0x8b;
+    buf[1]   = 0x01;
+    std::copy(begin(key), end(key), begin(buf) + 2);
+
+    uint8_t parity = buf[0] ^ buf[1];
+    for (uint8_t b : key)
+        parity ^= b;
+    buf.back() = parity;
+
+    return bin2base58(to_string(buf));
+};
+
 std::string
 decrypt(const mtx::secret_storage::AesHmacSha2EncryptedData &data,
         BinaryBuf decryptionKey,
@@ -126,6 +142,22 @@ decrypt(const mtx::secret_storage::AesHmacSha2EncryptedData &data,
     return to_string(decryptedSecret);
 }
 
+mtx::secret_storage::AesHmacSha2EncryptedData
+encrypt(const std::string &data, BinaryBuf decryptionKey, const std::string key_name)
+{
+    mtx::secret_storage::AesHmacSha2EncryptedData encrypted{};
+    auto iv      = compatible_iv(create_buffer(16));
+    encrypted.iv = bin2base64(to_string(iv));
+
+    auto keys = HKDF_SHA256(decryptionKey, BinaryBuf(32, 0), to_binary_buf(key_name));
+
+    auto ciphertext      = AES_CTR_256_Encrypt(data, keys.aes, iv);
+    encrypted.ciphertext = bin2base64(to_string(ciphertext));
+    encrypted.mac        = bin2base64(to_string(HMAC_SHA256(keys.mac, ciphertext)));
+
+    return encrypted;
+}
+
 HkdfKeys
 HKDF_SHA256(const BinaryBuf &key, const BinaryBuf &salt, const BinaryBuf &info)
 {
@@ -170,6 +202,19 @@ HKDF_SHA256(const BinaryBuf &key, const BinaryBuf &salt, const BinaryBuf &info)
     return {std::move(buf), std::move(macKey)};
 }
 
+BinaryBuf
+compatible_iv(BinaryBuf incompatible_iv)
+{
+    // need to set bit 63 to 0
+    // Element and everyone else seems to be counting bytes from the back, i.e. iv_data[15] is
+    // the last byte. So we need to clear byte 15 - 63%8 = 15 - 7 = 8, the highest bit, 1 << 7
+    // see:
+    // https://github.com/matrix-org/matrix-js-sdk/blob/529fe93ab14b93c515e9ab0d0277c1942a5d73c5/src/crypto/aes.ts#L144
+    uint8_t *data = incompatible_iv.data();
+    data[15 - 63 % 8] &= ~(1UL << (63 / 8));
+    return incompatible_iv;
+}
+
 BinaryBuf
 AES_CTR_256_Encrypt(const std::string plaintext, const BinaryBuf aes256Key, BinaryBuf iv)
 {
@@ -180,23 +225,14 @@ AES_CTR_256_Encrypt(const std::string plaintext, const BinaryBuf aes256Key, Bina
     int ciphertext_len;
 
     // The ciphertext expand up to block size, which is 128 for AES256
-    BinaryBuf encrypted = create_buffer(plaintext.size() + AES_BLOCK_SIZE);
-
-    uint8_t *iv_data = iv.data();
-    // need to set bit 63 to 0
-    // Element and everyone else seems to be counting bytes from the back, i.e. iv_data[15] is
-    // the last byte. So we need to clear byte 15 - 63%8 = 15 - 7 = 8, the highest bit, 1 << 7
-    // see:
-    // https://github.com/matrix-org/matrix-js-sdk/blob/529fe93ab14b93c515e9ab0d0277c1942a5d73c5/src/crypto/aes.ts#L144
-    iv_data[15 - 63 % 8] &= ~(1UL << (63 / 8));
-    //*iv_data &= ~(1UL << (63));
+    BinaryBuf encrypted = compatible_iv(create_buffer(plaintext.size() + AES_BLOCK_SIZE));
 
     /* Create and initialise the context */
     if (!(ctx = EVP_CIPHER_CTX_new())) {
         // handleErrors();
     }
 
-    if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, aes256Key.data(), iv_data)) {
+    if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, aes256Key.data(), iv.data())) {
         // handleErrors();
     }
 
diff --git a/lib/http/client.cpp b/lib/http/client.cpp
index beb9a58680a9442b13d990fe36b6f57160fbaf39..0d55eb2539a2d784be7d78fbe13c5fe5611ba522 100644
--- a/lib/http/client.cpp
+++ b/lib/http/client.cpp
@@ -23,6 +23,11 @@ struct ClientPrivate
     coeurl::Client client;
 };
 
+void
+UIAHandler::next(const user_interactive::Auth &auth) const
+{
+    next_(*this, auth);
+}
 }
 
 Client::Client(const std::string &server, uint16_t port)
@@ -895,6 +900,34 @@ Client::registration(const std::string &user,
     post<nlohmann::json, mtx::responses::Register>("/client/r0/register", req, callback, false);
 }
 
+void
+Client::registration(const std::string &user,
+                     const std::string &pass,
+                     UIAHandler uia_handler,
+                     Callback<mtx::responses::Register> cb)
+{
+    nlohmann::json req = {{"username", user}, {"password", pass}};
+
+    uia_handler.next_ = [this, req, cb](const UIAHandler &h, const nlohmann::json &auth) {
+        auto request = req;
+        if (!auth.empty())
+            request["auth"] = auth;
+
+        post<nlohmann::json, mtx::responses::Register>(
+          "/client/r0/register",
+          request,
+          [cb, h](auto &r, RequestErr e) {
+              if (e && e->status_code == 401)
+                  h.prompt(h, e->matrix_error.unauthorized);
+              else
+                  cb(r, e);
+          },
+          false);
+    };
+
+    uia_handler.next_(uia_handler, {});
+}
+
 void
 Client::registration_token_validity(const std::string token,
                                     Callback<mtx::responses::RegistrationTokenValidity> cb)
@@ -1069,6 +1102,30 @@ Client::keys_signatures_upload(const mtx::requests::KeySignaturesUpload &req,
       "/client/unstable/keys/signatures/upload", req, cb);
 }
 
+void
+Client::device_signing_upload(const mtx::requests::DeviceSigningUpload deviceKeys,
+                              UIAHandler uia_handler,
+                              ErrCallback cb)
+{
+    nlohmann::json req = deviceKeys;
+
+    uia_handler.next_ = [this, req, cb](const UIAHandler &h, const nlohmann::json &auth) {
+        auto request = req;
+        if (!auth.empty())
+            request["auth"] = auth;
+
+        post<nlohmann::json, mtx::responses::Empty>(
+          "/client/unstable/keys/device_signing/upload", request, [cb, h](auto &, RequestErr e) {
+              if (e && e->status_code == 401 && !e->matrix_error.unauthorized.flows.empty())
+                  h.prompt(h, e->matrix_error.unauthorized);
+              else
+                  cb(e);
+          });
+    };
+
+    uia_handler.next_(uia_handler, {});
+}
+
 void
 Client::query_keys(const mtx::requests::QueryKeys &req,
                    Callback<mtx::responses::QueryKeys> callback)
@@ -1136,6 +1193,16 @@ Client::update_backup_version(const std::string &version,
     put<mtx::responses::backup::BackupVersion>(
       "/client/r0/room_keys/version/" + mtx::client::utils::url_encode(version), data, cb);
 }
+
+void
+Client::post_backup_version(const std::string &algorithm,
+                            const std::string &auth_data,
+                            Callback<mtx::responses::Version> cb)
+{
+    nlohmann::json req = {{"algorithm", algorithm},
+                          {"auth_data", nlohmann::json::parse(auth_data)}};
+    post<nlohmann::json, mtx::responses::Version>("/client/r0/room_keys/version", req, cb);
+}
 void
 Client::room_keys(const std::string &version, Callback<mtx::responses::backup::KeysBackup> cb)
 {
@@ -1229,6 +1296,40 @@ Client::secret_storage_key(const std::string &key_id,
            RequestErr err) { cb(res, err); });
 }
 
+//! Upload a specific secret
+void
+Client::upload_secret_storage_secret(const std::string &secret_id,
+                                     const mtx::secret_storage::Secret &secret,
+                                     ErrCallback cb)
+{
+    put("/client/r0/user/" + mtx::client::utils::url_encode(user_id_.to_string()) +
+          "/account_data/" + mtx::client::utils::url_encode(secret_id),
+        secret,
+        cb);
+}
+
+//! Upload information about a key
+void
+Client::upload_secret_storage_key(const std::string &key_id,
+                                  const mtx::secret_storage::AesHmacSha2KeyDescription &desc,
+                                  ErrCallback cb)
+{
+    put("/client/r0/user/" + mtx::client::utils::url_encode(user_id_.to_string()) +
+          "/account_data/m.secret_storage.key." + mtx::client::utils::url_encode(key_id),
+        desc,
+        cb);
+}
+
+void
+Client::set_secret_storage_default_key(const std::string &key_id, ErrCallback cb)
+{
+    nlohmann::json key = {{"key", key_id}};
+    put("/client/r0/user/" + mtx::client::utils::url_encode(user_id_.to_string()) +
+          "/account_data/m.secret_storage.default_key",
+        key,
+        cb);
+}
+
 void
 Client::enable_encryption(const std::string &room, Callback<mtx::responses::EventId> callback)
 {
diff --git a/lib/structs/requests.cpp b/lib/structs/requests.cpp
index 6217dfdb8d09923a9bb344d11c8b7cff0ebb40aa..0c1e10935388eb61acf1714cb492b9a9d2b18070 100644
--- a/lib/structs/requests.cpp
+++ b/lib/structs/requests.cpp
@@ -196,6 +196,17 @@ to_json(json &obj, const KeySignaturesUpload &req)
             obj[user_id][key_id] = std::visit([](const auto &e) { return json(e); }, keyVar);
 }
 
+void
+to_json(json &obj, const DeviceSigningUpload &req)
+{
+    if (req.master_key)
+        obj["master_key"] = req.master_key.value();
+    if (req.self_signing_key)
+        obj["self_signing_key"] = req.self_signing_key.value();
+    if (req.user_signing_key)
+        obj["user_signing_key"] = req.user_signing_key.value();
+}
+
 void
 to_json(json &obj, const PusherData &data)
 {
diff --git a/lib/structs/responses/common.cpp b/lib/structs/responses/common.cpp
index 49a4e107fc56559bc310f3c3d3077b2cab86e959..366815f7d49a815c5d557fc63d79872dbf773783 100644
--- a/lib/structs/responses/common.cpp
+++ b/lib/structs/responses/common.cpp
@@ -53,6 +53,12 @@ from_json(const nlohmann::json &obj, FilterId &response)
     response.filter_id = obj.at("filter_id");
 }
 
+void
+from_json(const nlohmann::json &obj, Version &response)
+{
+    response.version = obj.at("version");
+}
+
 namespace utils {
 
 void
diff --git a/tests/crypto.cpp b/tests/crypto.cpp
index 909c9a8ff72253779e407b4dff0160fc1d02ea17..b47923b3c91d13b0929e1a69bc1600ddffe611df 100644
--- a/tests/crypto.cpp
+++ b/tests/crypto.cpp
@@ -184,6 +184,7 @@ TEST(Base58, EncodingDecoding)
     EXPECT_EQ(bin2base58("foob"), "3csAg9");
     EXPECT_EQ(bin2base58("fooba"), "CZJRhmz");
     EXPECT_EQ(bin2base58("foobar"), "t1Zv2yaZ");
+    EXPECT_FALSE(bin2base58(to_string(create_buffer(32))).empty());
 
     EXPECT_EQ("", base582bin(""));
     EXPECT_EQ("f", base582bin("2m"));
@@ -371,3 +372,51 @@ TEST(SecretStorage, SecretKey)
     ASSERT_EQ(desc.signatures["@alice:localhost"]["ed25519:adkfajfgaefkdahfzguerhtgduifghes"],
               "ksfjvkrfbnrtnwublrjkgnorthgnrdtjbiortbjdlbiutr");
 }
+
+TEST(SecretStorage, CreateSecretKey)
+{
+    auto ssss1 = mtx::crypto::OlmClient::create_ssss_key();
+    ASSERT_TRUE(ssss1.has_value());
+    EXPECT_FALSE(ssss1->keyDescription.passphrase.has_value());
+    EXPECT_EQ(ssss1->keyDescription.algorithm, "m.secret_storage.v1.aes-hmac-sha2");
+    EXPECT_GE(ssss1->keyDescription.iv.length(), 32);
+    EXPECT_EQ((ssss1->keyDescription.mac.length() - 1) * 3 / 4, 32);
+
+    EXPECT_EQ(key_from_recoverykey(key_to_recoverykey(ssss1->privateKey), ssss1->keyDescription),
+              ssss1->privateKey);
+
+    auto ssss2 = mtx::crypto::OlmClient::create_ssss_key("some passphrase");
+    ASSERT_TRUE(ssss2.has_value());
+    ASSERT_TRUE(ssss2->keyDescription.passphrase.has_value());
+    EXPECT_EQ(ssss2->keyDescription.algorithm, "m.secret_storage.v1.aes-hmac-sha2");
+    EXPECT_GE(ssss2->keyDescription.iv.length(), 32);
+    EXPECT_EQ((ssss2->keyDescription.mac.length() - 1) * 3 / 4, 32);
+
+    EXPECT_EQ(mtx::crypto::key_from_passphrase("some passphrase", ssss2->keyDescription),
+              ssss2->privateKey);
+}
+
+TEST(SecretStorage, CreateOnlineKeyBackup)
+{
+    mtx::crypto::OlmClient account;
+    account.create_new_account();
+
+    auto cross = account.create_crosssigning_keys();
+    ASSERT_TRUE(cross.has_value());
+
+    auto okb = account.create_online_key_backup(cross->private_master_key);
+    ASSERT_TRUE(okb.has_value());
+
+    mtx::responses::backup::SessionData s;
+    s.algorithm   = mtx::crypto::MEGOLM_ALGO;
+    s.sender_key  = "abc";
+    s.session_key = "cde";
+
+    auto enc1 =
+      mtx::crypto::encrypt_session(s, json::parse(okb->backupVersion.auth_data)["public_key"]);
+    EXPECT_FALSE(enc1.ciphertext.empty());
+
+    auto enc2 = mtx::crypto::encrypt_session(
+      s, mtx::crypto::CURVE25519_public_key_from_private(okb->privateKey));
+    EXPECT_FALSE(enc2.ciphertext.empty());
+}
diff --git a/tests/e2ee.cpp b/tests/e2ee.cpp
index 8aecd46c51a8ccc2650dc090efd6c17b4bf401dd..da89038908e7b8873c1a05cb688e170056801e17 100644
--- a/tests/e2ee.cpp
+++ b/tests/e2ee.cpp
@@ -463,6 +463,137 @@ TEST(Encryption, ClaimMultipleDeviceKeys)
     alice3->close();
 }
 
+TEST(Encryption, UploadCrossSigningKeys)
+{
+    auto alice       = make_test_client();
+    auto olm_account = std::make_shared<mtx::crypto::OlmClient>();
+
+    EXPECT_THROW(olm_account->identity_keys(), olm_exception);
+
+    olm_account->create_new_account();
+
+    alice->login(
+      "alice", "secret", [](const mtx::responses::Login &, RequestErr err) { check_error(err); });
+
+    while (alice->access_token().empty())
+        sleep();
+
+    olm_account->set_user_id(alice->user_id().to_string());
+    olm_account->set_device_id(alice->device_id());
+
+    auto id_keys = olm_account->identity_keys();
+
+    ASSERT_TRUE(id_keys.curve25519.size() > 10);
+    ASSERT_TRUE(id_keys.curve25519.size() > 10);
+
+    mtx::crypto::OneTimeKeys unused;
+    auto request = olm_account->create_upload_keys_request(unused);
+
+    // Make the request with the signed identity keys.
+    alice->upload_keys(request, [](const mtx::responses::UploadKeys &res, RequestErr err) {
+        check_error(err);
+        EXPECT_EQ(res.one_time_key_counts.size(), 0);
+    });
+
+    auto xsign_keys = olm_account->create_crosssigning_keys();
+    ASSERT_TRUE(xsign_keys.has_value());
+    mtx::requests::DeviceSigningUpload u;
+    u.master_key       = xsign_keys->master_key;
+    u.user_signing_key = xsign_keys->user_signing_key;
+    u.self_signing_key = xsign_keys->self_signing_key;
+    alice->device_signing_upload(
+      u,
+      mtx::http::UIAHandler([](const mtx::http::UIAHandler &h,
+                               const mtx::user_interactive::Unauthorized &unauthorized) {
+          ASSERT_EQ(unauthorized.flows.size(), 1);
+          ASSERT_EQ(unauthorized.flows[0].stages.size(), 1);
+          ASSERT_EQ(unauthorized.flows[0].stages[0], mtx::user_interactive::auth_types::password);
+
+          mtx::user_interactive::Auth auth;
+          auth.session = unauthorized.session;
+          mtx::user_interactive::auth::Password pass{};
+          pass.password        = "secret";
+          pass.identifier_user = "alice";
+          pass.identifier_type = mtx::user_interactive::auth::Password::IdType::UserId;
+          auth.content         = pass;
+          h.next(auth);
+      }),
+      [](RequestErr e) { check_error(e); });
+
+    alice->close();
+}
+
+TEST(Encryption, UploadOnlineBackup)
+{
+    auto alice       = make_test_client();
+    auto olm_account = std::make_shared<mtx::crypto::OlmClient>();
+
+    EXPECT_THROW(olm_account->identity_keys(), olm_exception);
+
+    olm_account->create_new_account();
+
+    alice->login(
+      "alice", "secret", [](const mtx::responses::Login &, RequestErr err) { check_error(err); });
+
+    while (alice->access_token().empty())
+        sleep();
+
+    olm_account->set_user_id(alice->user_id().to_string());
+    olm_account->set_device_id(alice->device_id());
+
+    auto id_keys = olm_account->identity_keys();
+
+    ASSERT_TRUE(id_keys.curve25519.size() > 10);
+    ASSERT_TRUE(id_keys.curve25519.size() > 10);
+
+    mtx::crypto::OneTimeKeys unused;
+    auto request = olm_account->create_upload_keys_request(unused);
+
+    // Make the request with the signed identity keys.
+    alice->upload_keys(request, [](const mtx::responses::UploadKeys &res, RequestErr err) {
+        check_error(err);
+        EXPECT_EQ(res.one_time_key_counts.size(), 0);
+    });
+
+    auto xsign_keys = olm_account->create_crosssigning_keys();
+    ASSERT_TRUE(xsign_keys.has_value());
+    mtx::requests::DeviceSigningUpload u;
+    u.master_key       = xsign_keys->master_key;
+    u.user_signing_key = xsign_keys->user_signing_key;
+    u.self_signing_key = xsign_keys->self_signing_key;
+    alice->device_signing_upload(
+      u,
+      mtx::http::UIAHandler([](const mtx::http::UIAHandler &h,
+                               const mtx::user_interactive::Unauthorized &unauthorized) {
+          ASSERT_EQ(unauthorized.flows.size(), 1);
+          ASSERT_EQ(unauthorized.flows[0].stages.size(), 1);
+          ASSERT_EQ(unauthorized.flows[0].stages[0], mtx::user_interactive::auth_types::password);
+
+          mtx::user_interactive::Auth auth;
+          auth.session = unauthorized.session;
+          mtx::user_interactive::auth::Password pass{};
+          pass.password        = "secret";
+          pass.identifier_user = "alice";
+          pass.identifier_type = mtx::user_interactive::auth::Password::IdType::UserId;
+          auth.content         = pass;
+          h.next(auth);
+      }),
+      [xsign_keys, olm_account, alice](RequestErr e) {
+          check_error(e);
+
+          auto bk = olm_account->create_online_key_backup(xsign_keys->private_master_key);
+          alice->post_backup_version(bk->backupVersion.algorithm,
+                                     bk->backupVersion.auth_data,
+                                     [](const mtx::responses::Version &v, RequestErr e) {
+                                         check_error(e);
+
+                                         EXPECT_FALSE(v.version.empty());
+                                     });
+      });
+
+    alice->close();
+}
+
 TEST(Encryption, KeyChanges)
 {
     auto carl     = make_test_client();