diff --git a/.ci/macos/Brewfile b/.ci/macos/Brewfile
index 4ef9967c5de15dafa4090401b6b5d81235ca6247..7e9687c7711296d3c33a940b2252da0e35757823 100644
--- a/.ci/macos/Brewfile
+++ b/.ci/macos/Brewfile
@@ -11,4 +11,5 @@ brew "nlohmann_json"
 brew "gstreamer"
 brew "gst-plugins-base"
 brew "gst-plugins-good"
-brew "gst-plugins-bad"
\ No newline at end of file
+brew "gst-plugins-bad"
+brew "qtkeychain"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d2689a97fdd856d2736b71b09d7304a47a64a6ce..326e5794b92c10cc20b02293af9a575669a7f8be 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -41,6 +41,8 @@ option(USE_BUNDLED_LMDBXX "Use the bundled version of lmdb++."
 	${HUNTER_ENABLED})
 option(USE_BUNDLED_TWEENY "Use the bundled version of tweeny."
 	${HUNTER_ENABLED})
+option(USE_BUNDLED_QTKEYCHAIN "Use the bundled version of Qt5Keychain."
+	${HUNTER_ENABLED})
 
 list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
 
@@ -137,6 +139,24 @@ find_package(Qt5 COMPONENTS Core Widgets LinguistTools Concurrent Svg Multimedia
 find_package(Qt5QuickCompiler)
 find_package(Qt5DBus)
 
+if (USE_BUNDLED_QTKEYCHAIN)
+	include(FetchContent)
+	FetchContent_Declare(
+		qt5keychain
+		GIT_REPOSITORY https://github.com/frankosterfeld/qtkeychain.git
+		GIT_TAG        v0.12.0
+		)
+	if (BUILD_SHARED_LIBS)
+		set(QTKEYCHAIN_STATIC OFF CACHE INTERNAL "")
+	else()
+		set(QTKEYCHAIN_STATIC ON CACHE INTERNAL "")
+	endif()
+	set(BUILD_TEST_APPLICATION OFF CACHE INTERNAL "")
+	FetchContent_MakeAvailable(qt5keychain)
+else()
+find_package(Qt5Keychain REQUIRED)
+endif()
+
 if (APPLE)
 	find_package(Qt5MacExtras REQUIRED)
 endif(APPLE)
@@ -333,25 +353,25 @@ endif()
 find_package(OpenSSL 1.1.0 REQUIRED)
 if(USE_BUNDLED_MTXCLIENT)
 	include(FetchContent)
-	set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
-	set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
 	FetchContent_Declare(
 		MatrixClient
 		GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
-		GIT_TAG        ed6315563409ce9d47978ff2a2d771b863e375c5
+		GIT_TAG        ce8bc9c3dd6bba432e716f55136133111b0186e7
 		)
+	set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
+	set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
 	FetchContent_MakeAvailable(MatrixClient)
 else()
 	find_package(MatrixClient 0.3.1 REQUIRED)
 endif()
 if(USE_BUNDLED_OLM)
 	include(FetchContent)
-	set(OLM_TESTS OFF CACHE INTERNAL "")
 	FetchContent_Declare(
 		Olm
 		GIT_REPOSITORY https://gitlab.matrix.org/matrix-org/olm.git
 		GIT_TAG        3.1.4
 		)
+	set(OLM_TESTS OFF CACHE INTERNAL "")
 	FetchContent_MakeAvailable(Olm)
 else()
 	find_package(Olm 3)
@@ -573,6 +593,11 @@ else()
 endif()
 target_include_directories(nheko PRIVATE src includes third_party/blurhash third_party/cpp-httplib-0.5.12)
 
+# Fixup bundled keychain include dirs
+if (USE_BUNDLED_QTKEYCHAIN)
+target_include_directories(nheko PRIVATE ${qt5keychain_SOURCE_DIR} ${qt5keychain_BINARY_DIR})
+endif()
+
 target_link_libraries(nheko PRIVATE
 	MatrixClient::MatrixClient
 	Boost::iostreams
@@ -587,6 +612,7 @@ target_link_libraries(nheko PRIVATE
 	Qt5::Qml
 	Qt5::QuickControls2
 	Qt5::QuickWidgets
+	qt5keychain
 	nlohmann_json::nlohmann_json
 	lmdbxx::lmdbxx
 	liblmdb::lmdb
diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json
index 913e239aba1cbd19f2d283a3cf68cd54b179a784..34b0d7e78c99fa028d6f274b7170f60c7a021229 100644
--- a/io.github.NhekoReborn.Nheko.json
+++ b/io.github.NhekoReborn.Nheko.json
@@ -94,6 +94,22 @@
         }
       ]
     },
+    {
+      "config-opts": [
+        "-DCMAKE_BUILD_TYPE=Release",
+        "-DBUILD_TEST_APPLICATION=OFF"
+      ],
+      "buildsystem": "cmake-ninja",
+      "name": "QtKeychain",
+      "sources": [
+        {
+          "commit": "815fe610353ff8ad7e2f1121c368a74df8db5eb7",
+          "tag": "v0.12.0",
+          "type": "git",
+          "url": "https://github.com/frankosterfeld/qtkeychain.git"
+        }
+      ]
+    },
     {
       "config-opts":[
         "-DJSON_BuildTests=OFF"
@@ -145,7 +161,7 @@
       "name": "mtxclient",
       "sources": [
         {
-          "commit": "ed6315563409ce9d47978ff2a2d771b863e375c5",
+          "commit": "ce8bc9c3dd6bba432e716f55136133111b0186e7",
           "type": "git",
           "url": "https://github.com/Nheko-Reborn/mtxclient.git"
         }
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 05c2e4860bfd05f0a650a0a72b0d2c10589facbf..674b5793050f63a11fe656088c8c5e90892053aa 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -24,9 +24,14 @@
 #include <QFile>
 #include <QHash>
 #include <QMap>
-#include <QSettings>
 #include <QStandardPaths>
 
+#if __has_include(<keychain.h>)
+#include <keychain.h>
+#else
+#include <qt5keychain/keychain.h>
+#endif
+
 #include <mtx/responses/common.hpp>
 
 #include "Cache.h"
@@ -569,6 +574,64 @@ Cache::restoreOlmAccount()
         return std::string(pickled.data(), pickled.size());
 }
 
+void
+Cache::storeSecret(const std::string &name, const std::string &secret)
+{
+        QKeychain::WritePasswordJob job(QCoreApplication::applicationName());
+        job.setAutoDelete(false);
+        job.setInsecureFallback(true);
+        job.setKey(QString::fromStdString(name));
+        job.setTextData(QString::fromStdString(secret));
+        QEventLoop loop;
+        job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
+        job.start();
+        loop.exec();
+
+        if (job.error()) {
+                nhlog::db()->warn(
+                  "Storing secret '{}' failed: {}", name, job.errorString().toStdString());
+        } else {
+                emit secretChanged(name);
+        }
+}
+
+void
+Cache::deleteSecret(const std::string &name)
+{
+        QKeychain::DeletePasswordJob job(QCoreApplication::applicationName());
+        job.setAutoDelete(false);
+        job.setInsecureFallback(true);
+        job.setKey(QString::fromStdString(name));
+        QEventLoop loop;
+        job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
+        job.start();
+        loop.exec();
+
+        emit secretChanged(name);
+}
+
+std::optional<std::string>
+Cache::secret(const std::string &name)
+{
+        QKeychain::ReadPasswordJob job(QCoreApplication::applicationName());
+        job.setAutoDelete(false);
+        job.setInsecureFallback(true);
+        job.setKey(QString::fromStdString(name));
+        QEventLoop loop;
+        job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
+        job.start();
+        loop.exec();
+
+        const QString secret = job.textData();
+        if (job.error()) {
+                nhlog::db()->debug(
+                  "Restoring secret '{}' failed: {}", name, job.errorString().toStdString());
+                return std::nullopt;
+        }
+
+        return secret.toStdString();
+}
+
 //
 // Media Management
 //
@@ -726,10 +789,32 @@ void
 Cache::deleteData()
 {
         // TODO: We need to remove the env_ while not accepting new requests.
+        lmdb::dbi_close(env_, syncStateDb_);
+        lmdb::dbi_close(env_, roomsDb_);
+        lmdb::dbi_close(env_, invitesDb_);
+        lmdb::dbi_close(env_, mediaDb_);
+        lmdb::dbi_close(env_, readReceiptsDb_);
+        lmdb::dbi_close(env_, notificationsDb_);
+
+        lmdb::dbi_close(env_, devicesDb_);
+        lmdb::dbi_close(env_, deviceKeysDb_);
+
+        lmdb::dbi_close(env_, inboundMegolmSessionDb_);
+        lmdb::dbi_close(env_, outboundMegolmSessionDb_);
+
+        env_.close();
+
+        verification_storage.status.clear();
+
         if (!cacheDirectory_.isEmpty()) {
                 QDir(cacheDirectory_).removeRecursively();
                 nhlog::db()->info("deleted cache files from disk");
         }
+
+        deleteSecret(mtx::secret_storage::secrets::megolm_backup_v1);
+        deleteSecret(mtx::secret_storage::secrets::cross_signing_master);
+        deleteSecret(mtx::secret_storage::secrets::cross_signing_user_signing);
+        deleteSecret(mtx::secret_storage::secrets::cross_signing_self_signing);
 }
 
 //! migrates db to the current format
@@ -4262,4 +4347,15 @@ restoreOlmAccount()
 {
         return instance_->restoreOlmAccount();
 }
+
+void
+storeSecret(const std::string &name, const std::string &secret)
+{
+        instance_->storeSecret(name, secret);
+}
+std::optional<std::string>
+secret(const std::string &name)
+{
+        return instance_->secret(name);
+}
 } // namespace cache
diff --git a/src/Cache.h b/src/Cache.h
index f38f1960b5f258aaeb08016e906050060ac4aa3d..919567257387fd20f74e53eda8134bb945cce0fd 100644
--- a/src/Cache.h
+++ b/src/Cache.h
@@ -282,4 +282,9 @@ saveOlmAccount(const std::string &pickled);
 
 std::string
 restoreOlmAccount();
+
+void
+storeSecret(const std::string &name, const std::string &secret);
+std::optional<std::string>
+secret(const std::string &name);
 }
diff --git a/src/Cache_p.h b/src/Cache_p.h
index fab2d9643c8d9a82b4cbbd2a813907ce51350174..059c1461a5fcec104c32c2b030a199add7d5958e 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -269,6 +269,10 @@ public:
         void saveOlmAccount(const std::string &pickled);
         std::string restoreOlmAccount();
 
+        void storeSecret(const std::string &name, const std::string &secret);
+        void deleteSecret(const std::string &name);
+        std::optional<std::string> secret(const std::string &name);
+
 signals:
         void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
         void roomReadStatus(const std::map<QString, bool> &status);
@@ -276,6 +280,7 @@ signals:
         void userKeysUpdate(const std::string &sync_token,
                             const mtx::responses::QueryKeys &keyQuery);
         void verificationStatusChanged(const std::string &userid);
+        void secretChanged(const std::string name);
 
 private:
         //! Save an invited room.
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index dab414a9a57d782f10783c52fc5c79492a8bfcd3..e3325c05c2eb406b4f1de53b61d94d1afaa78cec 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -17,6 +17,7 @@
 
 #include <QApplication>
 #include <QImageReader>
+#include <QInputDialog>
 #include <QMessageBox>
 #include <QSettings>
 #include <QShortcut>
@@ -64,6 +65,8 @@ constexpr size_t MAX_ONETIME_KEYS         = 50;
 Q_DECLARE_METATYPE(std::optional<mtx::crypto::EncryptedFile>)
 Q_DECLARE_METATYPE(std::optional<RelatedInfo>)
 Q_DECLARE_METATYPE(mtx::presence::PresenceState)
+Q_DECLARE_METATYPE(mtx::secret_storage::AesHmacSha2KeyDescription)
+Q_DECLARE_METATYPE(SecretsToDecrypt)
 
 ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
   : QWidget(parent)
@@ -79,6 +82,8 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
         qRegisterMetaType<std::optional<mtx::crypto::EncryptedFile>>();
         qRegisterMetaType<std::optional<RelatedInfo>>();
         qRegisterMetaType<mtx::presence::PresenceState>();
+        qRegisterMetaType<mtx::secret_storage::AesHmacSha2KeyDescription>();
+        qRegisterMetaType<SecretsToDecrypt>();
 
         topLayout_ = new QHBoxLayout(this);
         topLayout_->setSpacing(0);
@@ -136,6 +141,12 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
         splitter->addWidget(content_);
         splitter->restoreSizes(parent->width());
 
+        connect(this,
+                &ChatPage::downloadedSecrets,
+                this,
+                &ChatPage::decryptDownloadedSecrets,
+                Qt::QueuedConnection);
+
         connect(this, &ChatPage::connectionLost, this, [this]() {
                 nhlog::net()->info("connectivity lost");
                 isConnected_ = false;
@@ -372,9 +383,8 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
 void
 ChatPage::logout()
 {
-        deleteConfigs();
-
         resetUI();
+        deleteConfigs();
 
         emit closing();
         connectivityTimer_.stop();
@@ -385,12 +395,12 @@ ChatPage::dropToLoginPage(const QString &msg)
 {
         nhlog::ui()->info("dropping to the login page: {}", msg.toStdString());
 
-        deleteConfigs();
-        resetUI();
-
         http::client()->shutdown();
         connectivityTimer_.stop();
 
+        resetUI();
+        deleteConfigs();
+
         emit showLoginPage(msg);
 }
 
@@ -418,8 +428,8 @@ ChatPage::deleteConfigs()
         settings.remove("");
         settings.endGroup();
 
+        http::client()->shutdown();
         cache::deleteData();
-        http::client()->clear();
 }
 
 void
@@ -1209,3 +1219,45 @@ ChatPage::connectCallMessage()
                 view_manager_,
                 qOverload<const QString &, const T &>(&TimelineViewManager::queueCallMessage));
 }
+
+void
+ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
+                                   const SecretsToDecrypt &secrets)
+{
+        QString text = QInputDialog::getText(
+          ChatPage::instance(),
+          QCoreApplication::translate("CrossSigningSecrets", "Decrypt secrets"),
+          keyDesc.name.empty()
+            ? QCoreApplication::translate(
+                "CrossSigningSecrets",
+                "Enter your recovery key or passphrase to decrypt your secrets:")
+            : QCoreApplication::translate(
+                "CrossSigningSecrets",
+                "Enter your recovery key or passphrase called %1 to decrypt your secrets:")
+                .arg(QString::fromStdString(keyDesc.name)),
+          QLineEdit::Password);
+
+        if (text.isEmpty())
+                return;
+
+        auto decryptionKey = mtx::crypto::key_from_recoverykey(text.toStdString(), keyDesc);
+
+        if (!decryptionKey)
+                decryptionKey = mtx::crypto::key_from_passphrase(text.toStdString(), keyDesc);
+
+        if (!decryptionKey) {
+                QMessageBox::information(
+                  ChatPage::instance(),
+                  QCoreApplication::translate("CrossSigningSecrets", "Decrytion failed"),
+                  QCoreApplication::translate("CrossSigningSecrets",
+                                              "Failed to decrypt secrets with the "
+                                              "provided recovery key or passphrase"));
+                return;
+        }
+
+        for (const auto &[secretName, encryptedSecret] : secrets) {
+                auto decrypted = mtx::crypto::decrypt(encryptedSecret, *decryptionKey, secretName);
+                if (!decrypted.empty())
+                        cache::storeSecret(secretName, decrypted);
+        }
+}
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 5b336cbb28fdfd9e9a08be9b11fc6c14ce31f5f5..45a4ff63105287133e76c2386c45fe445d1a8a64 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -27,6 +27,7 @@
 #include <mtx/events/encrypted.hpp>
 #include <mtx/events/member.hpp>
 #include <mtx/events/presence.hpp>
+#include <mtx/secret_storage.hpp>
 
 #include <QFrame>
 #include <QHBoxLayout>
@@ -72,6 +73,8 @@ namespace popups {
 class UserMentions;
 }
 
+using SecretsToDecrypt = std::map<std::string, mtx::secret_storage::AesHmacSha2EncryptedData>;
+
 class ChatPage : public QWidget
 {
         Q_OBJECT
@@ -117,6 +120,8 @@ public slots:
         void unbanUser(QString userid, QString reason);
 
         void receivedSessionKey(const std::string &room_id, const std::string &session_id);
+        void decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
+                                      const SecretsToDecrypt &secrets);
 
 signals:
         void connectionLost();
@@ -185,6 +190,9 @@ signals:
         void receivedDeviceVerificationReady(const mtx::events::msg::KeyVerificationReady &message);
         void receivedDeviceVerificationDone(const mtx::events::msg::KeyVerificationDone &message);
 
+        void downloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
+                               const SecretsToDecrypt &secrets);
+
 private slots:
         void logout();
         void removeRoom(const QString &room_id);
diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp
index 509fce8cbf29484829d4d8d3ac10703be64fdcdf..f692629ee32ed0eacabacc6e376948810ee82533 100644
--- a/src/DeviceVerificationFlow.cpp
+++ b/src/DeviceVerificationFlow.cpp
@@ -275,11 +275,66 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *,
                                                   req.signatures[utils::localUser().toStdString()]
                                                                 [master_key.keys.at(mac.first)] =
                                                     master_key;
+                                          } else if (mac.first ==
+                                                     "ed25519:" + this->deviceId.toStdString()) {
+                                                  // Sign their device key with self signing key
+
+                                                  auto device_id = this->deviceId.toStdString();
+
+                                                  if (their_keys.device_keys.count(device_id)) {
+                                                          json j =
+                                                            their_keys.device_keys.at(device_id);
+                                                          j.erase("signatures");
+                                                          j.erase("unsigned");
+
+                                                          auto secret = cache::secret(
+                                                            mtx::secret_storage::secrets::
+                                                              cross_signing_self_signing);
+                                                          if (!secret)
+                                                                  continue;
+                                                          auto ssk =
+                                                            mtx::crypto::PkSigning::from_seed(
+                                                              *secret);
+
+                                                          mtx::crypto::DeviceKeys dev = j;
+                                                          dev.signatures
+                                                            [utils::localUser().toStdString()]
+                                                            ["ed25519:" + ssk.public_key()] =
+                                                            ssk.sign(j.dump());
+
+                                                          req.signatures[utils::localUser()
+                                                                           .toStdString()]
+                                                                        [device_id] = dev;
+                                                  }
                                           }
                                   }
-                                  // TODO(Nico): Sign their device key with self signing key
                           } else {
-                                  // TODO(Nico): Sign their master key with user signing key
+                                  // Sign their master key with user signing key
+                                  for (const auto &mac : msg.mac) {
+                                          if (their_keys.master_keys.keys.count(mac.first)) {
+                                                  json j = their_keys.master_keys;
+                                                  j.erase("signatures");
+                                                  j.erase("unsigned");
+
+                                                  auto secret =
+                                                    cache::secret(mtx::secret_storage::secrets::
+                                                                    cross_signing_user_signing);
+                                                  if (!secret)
+                                                          continue;
+                                                  auto usk =
+                                                    mtx::crypto::PkSigning::from_seed(*secret);
+
+                                                  mtx::crypto::CrossSigningKeys master_key = j;
+                                                  master_key
+                                                    .signatures[utils::localUser().toStdString()]
+                                                               ["ed25519:" + usk.public_key()] =
+                                                    usk.sign(j.dump());
+
+                                                  req.signatures[toClient.to_string()]
+                                                                [master_key.keys.at(mac.first)] =
+                                                    master_key;
+                                          }
+                                  }
                           }
 
                           if (!req.signatures.empty()) {
@@ -706,6 +761,14 @@ DeviceVerificationFlow::acceptDevice()
                 cache::markDeviceVerified(this->toClient.to_string(), this->deviceId.toStdString());
                 this->sendVerificationDone();
                 setState(Success);
+
+                // Request secrets. We should probably check somehow, if a device knowns about the
+                // secrets.
+                if (utils::localUser().toStdString() == this->toClient.to_string() &&
+                    (!cache::secret(mtx::secret_storage::secrets::cross_signing_self_signing) ||
+                     !cache::secret(mtx::secret_storage::secrets::cross_signing_user_signing))) {
+                        olm::request_cross_signing_keys();
+                }
         }
 }
 
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 60b5168befb3eaddcb1077dcd44c014ce641da36..d056aca601b0c55b1064d16a63f57cbc6bde7071 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -26,6 +26,7 @@
 #include <mtx/responses/login.hpp>
 
 #include "Cache.h"
+#include "Cache_p.h"
 #include "ChatPage.h"
 #include "Config.h"
 #include "Logging.h"
@@ -294,6 +295,10 @@ MainWindow::showChatPage()
 
         login_page_->reset();
         chat_page_->bootstrap(userid, homeserver, token);
+        connect(cache::client(),
+                &Cache::secretChanged,
+                userSettingsPage_,
+                &UserSettingsPage::updateSecretStatus);
 
         instance_ = this;
 }
diff --git a/src/Olm.cpp b/src/Olm.cpp
index 1f58758c1672693700890d3469d453206fe352ea..07fc49f6c6b7fe5f7fac43ff9e7d76c630ca7655 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -1,9 +1,14 @@
 #include "Olm.h"
 
 #include <QObject>
+#include <QTimer>
+
 #include <nlohmann/json.hpp>
 #include <variant>
 
+#include <mtx/responses/common.hpp>
+#include <mtx/secret_storage.hpp>
+
 #include "Cache.h"
 #include "Cache_p.h"
 #include "ChatPage.h"
@@ -13,11 +18,13 @@
 #include "UserSettingsPage.h"
 #include "Utils.h"
 
-static const std::string STORAGE_SECRET_KEY("secret");
-constexpr auto MEGOLM_ALGO = "m.megolm.v1.aes-sha2";
-
 namespace {
 auto client_ = std::make_unique<mtx::crypto::OlmClient>();
+
+std::map<std::string, std::string> request_id_to_secret_name;
+
+const std::string STORAGE_SECRET_KEY("secret");
+constexpr auto MEGOLM_ALGO = "m.megolm.v1.aes-sha2";
 }
 
 namespace olm {
@@ -43,6 +50,54 @@ client()
         return client_.get();
 }
 
+static void
+handle_secret_request(const mtx::events::DeviceEvent<mtx::events::msg::SecretRequest> *e,
+                      const std::string &sender)
+{
+        using namespace mtx::events;
+
+        if (e->content.action != mtx::events::msg::RequestAction::Request)
+                return;
+
+        auto local_user = http::client()->user_id();
+
+        if (sender != local_user.to_string())
+                return;
+
+        auto verificationStatus = cache::verificationStatus(local_user.to_string());
+
+        if (!verificationStatus)
+                return;
+
+        auto deviceKeys = cache::userKeys(local_user.to_string());
+        if (!deviceKeys)
+                return;
+
+        if (std::find(verificationStatus->verified_devices.begin(),
+                      verificationStatus->verified_devices.end(),
+                      e->content.requesting_device_id) ==
+            verificationStatus->verified_devices.end())
+                return;
+
+        // this is a verified device
+        mtx::events::DeviceEvent<mtx::events::msg::SecretSend> secretSend;
+        secretSend.type               = EventType::SecretSend;
+        secretSend.content.request_id = e->content.request_id;
+
+        auto secret = cache::client()->secret(e->content.name);
+        if (!secret)
+                return;
+        secretSend.content.secret = secret.value();
+
+        send_encrypted_to_device_messages(
+          {{local_user.to_string(), {{e->content.requesting_device_id}}}}, secretSend);
+
+        nhlog::net()->info("Sent secret '{}' to ({},{})",
+                           e->content.name,
+                           local_user.to_string(),
+                           e->content.requesting_device_id);
+}
+
 void
 handle_to_device_messages(const std::vector<mtx::events::collections::DeviceEvents> &msgs)
 {
@@ -127,6 +182,10 @@ handle_to_device_messages(const std::vector<mtx::events::collections::DeviceEven
                           std::get<mtx::events::DeviceEvent<mtx::events::msg::KeyVerificationDone>>(
                             msg);
                         ChatPage::instance()->receivedDeviceVerificationDone(message.content);
+                } else if (auto e =
+                             std::get_if<mtx::events::DeviceEvent<mtx::events::msg::SecretRequest>>(
+                               &msg)) {
+                        handle_secret_request(e, e->sender);
                 } else {
                         nhlog::crypto()->warn("unhandled event: {}", j_msg.dump(2));
                 }
@@ -163,59 +222,137 @@ handle_olm_message(const OlmMessage &msg)
                 }
 
                 if (!payload.is_null()) {
-                        std::string msg_type = payload["type"];
-
-                        if (msg_type == to_string(mtx::events::EventType::KeyVerificationAccept)) {
-                                ChatPage::instance()->receivedDeviceVerificationAccept(
-                                  payload["content"]);
-                                return;
-                        } else if (msg_type ==
-                                   to_string(mtx::events::EventType::KeyVerificationRequest)) {
-                                ChatPage::instance()->receivedDeviceVerificationRequest(
-                                  payload["content"], payload["sender"]);
-                                return;
-                        } else if (msg_type ==
-                                   to_string(mtx::events::EventType::KeyVerificationCancel)) {
-                                ChatPage::instance()->receivedDeviceVerificationCancel(
-                                  payload["content"]);
-                                return;
-                        } else if (msg_type ==
-                                   to_string(mtx::events::EventType::KeyVerificationKey)) {
-                                ChatPage::instance()->receivedDeviceVerificationKey(
-                                  payload["content"]);
-                                return;
-                        } else if (msg_type ==
-                                   to_string(mtx::events::EventType::KeyVerificationMac)) {
-                                ChatPage::instance()->receivedDeviceVerificationMac(
-                                  payload["content"]);
-                                return;
-                        } else if (msg_type ==
-                                   to_string(mtx::events::EventType::KeyVerificationStart)) {
-                                ChatPage::instance()->receivedDeviceVerificationStart(
-                                  payload["content"], payload["sender"]);
-                                return;
-                        } else if (msg_type ==
-                                   to_string(mtx::events::EventType::KeyVerificationReady)) {
-                                ChatPage::instance()->receivedDeviceVerificationReady(
-                                  payload["content"]);
-                                return;
-                        } else if (msg_type ==
-                                   to_string(mtx::events::EventType::KeyVerificationDone)) {
-                                ChatPage::instance()->receivedDeviceVerificationDone(
-                                  payload["content"]);
-                                return;
-                        } else if (msg_type == to_string(mtx::events::EventType::RoomKey)) {
-                                mtx::events::DeviceEvent<mtx::events::msg::RoomKey> roomKey =
-                                  payload;
-                                create_inbound_megolm_session(roomKey, msg.sender_key);
-                                return;
-                        } else if (msg_type ==
-                                   to_string(mtx::events::EventType::ForwardedRoomKey)) {
-                                mtx::events::DeviceEvent<mtx::events::msg::ForwardedRoomKey>
-                                  roomKey = payload;
-                                import_inbound_megolm_session(roomKey);
-                                return;
+                        mtx::events::collections::DeviceEvents device_event;
+
+                        {
+                                std::string msg_type = payload["type"];
+                                json event_array     = json::array();
+                                event_array.push_back(payload);
+
+                                std::vector<mtx::events::collections::DeviceEvents> temp_events;
+                                mtx::responses::utils::parse_device_events(event_array,
+                                                                           temp_events);
+                                if (temp_events.empty()) {
+                                        nhlog::crypto()->warn("Decrypted unknown event: {}",
+                                                              payload.dump());
+                                        continue;
+                                }
+                                device_event = temp_events.at(0);
+                        }
+
+                        using namespace mtx::events;
+                        if (auto e1 =
+                              std::get_if<DeviceEvent<msg::KeyVerificationAccept>>(&device_event)) {
+                                ChatPage::instance()->receivedDeviceVerificationAccept(e1->content);
+                        } else if (auto e2 = std::get_if<DeviceEvent<msg::KeyVerificationRequest>>(
+                                     &device_event)) {
+                                ChatPage::instance()->receivedDeviceVerificationRequest(e2->content,
+                                                                                        e2->sender);
+                        } else if (auto e3 = std::get_if<DeviceEvent<msg::KeyVerificationCancel>>(
+                                     &device_event)) {
+                                ChatPage::instance()->receivedDeviceVerificationCancel(e3->content);
+                        } else if (auto e4 = std::get_if<DeviceEvent<msg::KeyVerificationKey>>(
+                                     &device_event)) {
+                                ChatPage::instance()->receivedDeviceVerificationKey(e4->content);
+                        } else if (auto e5 = std::get_if<DeviceEvent<msg::KeyVerificationMac>>(
+                                     &device_event)) {
+                                ChatPage::instance()->receivedDeviceVerificationMac(e5->content);
+                        } else if (auto e6 = std::get_if<DeviceEvent<msg::KeyVerificationStart>>(
+                                     &device_event)) {
+                                ChatPage::instance()->receivedDeviceVerificationStart(e6->content,
+                                                                                      e6->sender);
+                        } else if (auto e7 = std::get_if<DeviceEvent<msg::KeyVerificationReady>>(
+                                     &device_event)) {
+                                ChatPage::instance()->receivedDeviceVerificationReady(e7->content);
+                        } else if (auto e8 = std::get_if<DeviceEvent<msg::KeyVerificationDone>>(
+                                     &device_event)) {
+                                ChatPage::instance()->receivedDeviceVerificationDone(e8->content);
+                        } else if (auto roomKey =
+                                     std::get_if<DeviceEvent<msg::RoomKey>>(&device_event)) {
+                                create_inbound_megolm_session(*roomKey, msg.sender_key);
+                        } else if (auto forwardedRoomKey =
+                                     std::get_if<DeviceEvent<msg::ForwardedRoomKey>>(
+                                       &device_event)) {
+                                import_inbound_megolm_session(*forwardedRoomKey);
+                        } else if (auto e =
+                                     std::get_if<DeviceEvent<msg::SecretSend>>(&device_event)) {
+                                auto local_user = http::client()->user_id();
+
+                                if (msg.sender != local_user.to_string())
+                                        continue;
+
+                                auto secret_name =
+                                  request_id_to_secret_name.find(e->content.request_id);
+
+                                if (secret_name != request_id_to_secret_name.end()) {
+                                        nhlog::crypto()->info("Received secret: {}",
+                                                              secret_name->second);
+
+                                        mtx::events::msg::SecretRequest secretRequest{};
+                                        secretRequest.action =
+                                          mtx::events::msg::RequestAction::Cancellation;
+                                        secretRequest.requesting_device_id =
+                                          http::client()->device_id();
+                                        secretRequest.request_id = e->content.request_id;
+
+                                        auto verificationStatus =
+                                          cache::verificationStatus(local_user.to_string());
+
+                                        if (!verificationStatus)
+                                                continue;
+
+                                        auto deviceKeys = cache::userKeys(local_user.to_string());
+                                        std::string sender_device_id;
+                                        if (deviceKeys) {
+                                                for (auto &[dev, key] : deviceKeys->device_keys) {
+                                                        if (key.keys["curve25519:" + dev] ==
+                                                            msg.sender_key) {
+                                                                sender_device_id = dev;
+                                                                break;
+                                                        }
+                                                }
+                                        }
+
+                                        std::map<
+                                          mtx::identifiers::User,
+                                          std::map<std::string, mtx::events::msg::SecretRequest>>
+                                          body;
+
+                                        for (const auto &dev :
+                                             verificationStatus->verified_devices) {
+                                                if (dev != secretRequest.requesting_device_id &&
+                                                    dev != sender_device_id)
+                                                        body[local_user][dev] = secretRequest;
+                                        }
+
+                                        http::client()
+                                          ->send_to_device<mtx::events::msg::SecretRequest>(
+                                            http::client()->generate_txn_id(),
+                                            body,
+                                            [name =
+                                               secret_name->second](mtx::http::RequestErr err) {
+                                                    if (err) {
+                                                            nhlog::net()->error(
+                                                              "Failed to send request cancellation "
+                                                              "for secrect "
+                                                              "'{}'",
+                                                              name);
+                                                            return;
+                                                    }
+                                            });
+
+                                        cache::client()->storeSecret(secret_name->second,
+                                                                     e->content.secret);
+
+                                        request_id_to_secret_name.erase(secret_name);
+                                }
+
+                        } else if (auto sec_req =
+                                     std::get_if<DeviceEvent<msg::SecretRequest>>(&device_event)) {
+                                handle_secret_request(sec_req, msg.sender);
                         }
+
+                        return;
                 }
         }
 }
@@ -332,11 +469,13 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
                                 // new member, send them the session at this index
                                 sendSessionTo[member_it->first] = {};
 
-                                for (const auto &dev : member_it->second->device_keys)
-                                        if (member_it->first != own_user_id ||
-                                            dev.first != device_id)
-                                                sendSessionTo[member_it->first].push_back(
-                                                  dev.first);
+                                if (member_it->second) {
+                                        for (const auto &dev : member_it->second->device_keys)
+                                                if (member_it->first != own_user_id ||
+                                                    dev.first != device_id)
+                                                        sendSessionTo[member_it->first].push_back(
+                                                          dev.first);
+                                }
 
                                 ++member_it;
                         } else {
@@ -1035,4 +1174,143 @@ send_encrypted_to_device_messages(const std::map<std::string, std::vector<std::s
         }
 }
 
+void
+request_cross_signing_keys()
+{
+        mtx::events::msg::SecretRequest secretRequest{};
+        secretRequest.action               = mtx::events::msg::RequestAction::Request;
+        secretRequest.requesting_device_id = http::client()->device_id();
+
+        auto local_user = http::client()->user_id();
+
+        auto verificationStatus = cache::verificationStatus(local_user.to_string());
+
+        if (!verificationStatus)
+                return;
+
+        auto request = [&](std::string secretName) {
+                secretRequest.name       = secretName;
+                secretRequest.request_id = "ss." + http::client()->generate_txn_id();
+
+                request_id_to_secret_name[secretRequest.request_id] = secretRequest.name;
+
+                std::map<mtx::identifiers::User,
+                         std::map<std::string, mtx::events::msg::SecretRequest>>
+                  body;
+
+                for (const auto &dev : verificationStatus->verified_devices) {
+                        if (dev != secretRequest.requesting_device_id)
+                                body[local_user][dev] = secretRequest;
+                }
+
+                http::client()->send_to_device<mtx::events::msg::SecretRequest>(
+                  http::client()->generate_txn_id(),
+                  body,
+                  [request_id = secretRequest.request_id, secretName](mtx::http::RequestErr err) {
+                          if (err) {
+                                  request_id_to_secret_name.erase(request_id);
+                                  nhlog::net()->error("Failed to send request for secrect '{}'",
+                                                      secretName);
+                                  return;
+                          }
+                  });
+
+                for (const auto &dev : verificationStatus->verified_devices) {
+                        if (dev != secretRequest.requesting_device_id)
+                                body[local_user][dev].action =
+                                  mtx::events::msg::RequestAction::Cancellation;
+                }
+
+                // timeout after 15 min
+                QTimer::singleShot(15 * 60 * 1000, [secretRequest, body]() {
+                        if (request_id_to_secret_name.count(secretRequest.request_id)) {
+                                request_id_to_secret_name.erase(secretRequest.request_id);
+                                http::client()->send_to_device<mtx::events::msg::SecretRequest>(
+                                  http::client()->generate_txn_id(),
+                                  body,
+                                  [secretRequest](mtx::http::RequestErr err) {
+                                          if (err) {
+                                                  nhlog::net()->error(
+                                                    "Failed to cancel request for secrect '{}'",
+                                                    secretRequest.name);
+                                                  return;
+                                          }
+                                  });
+                        }
+                });
+        };
+
+        request(mtx::secret_storage::secrets::cross_signing_self_signing);
+        request(mtx::secret_storage::secrets::cross_signing_user_signing);
+        request(mtx::secret_storage::secrets::megolm_backup_v1);
+}
+
+namespace {
+void
+unlock_secrets(const std::string &key,
+               const std::map<std::string, mtx::secret_storage::AesHmacSha2EncryptedData> &secrets)
+{
+        http::client()->secret_storage_key(
+          key,
+          [secrets](mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
+                    mtx::http::RequestErr err) {
+                  if (err) {
+                          nhlog::net()->error("Failed to download secret storage key");
+                          return;
+                  }
+
+                  emit ChatPage::instance()->downloadedSecrets(keyDesc, secrets);
+          });
+}
+}
+
+void
+download_cross_signing_keys()
+{
+        using namespace mtx::secret_storage;
+        http::client()->secret_storage_secret(
+          secrets::megolm_backup_v1, [](Secret secret, mtx::http::RequestErr err) {
+                  std::optional<Secret> backup_key;
+                  if (!err)
+                          backup_key = secret;
+
+                  http::client()->secret_storage_secret(
+                    secrets::cross_signing_self_signing,
+                    [backup_key](Secret secret, mtx::http::RequestErr err) {
+                            std::optional<Secret> self_signing_key;
+                            if (!err)
+                                    self_signing_key = secret;
+
+                            http::client()->secret_storage_secret(
+                              secrets::cross_signing_user_signing,
+                              [backup_key, self_signing_key](Secret secret,
+                                                             mtx::http::RequestErr err) {
+                                      std::optional<Secret> user_signing_key;
+                                      if (!err)
+                                              user_signing_key = secret;
+
+                                      std::map<std::string,
+                                               std::map<std::string, AesHmacSha2EncryptedData>>
+                                        secrets;
+
+                                      if (backup_key && !backup_key->encrypted.empty())
+                                              secrets[backup_key->encrypted.begin()->first]
+                                                     [secrets::megolm_backup_v1] =
+                                                       backup_key->encrypted.begin()->second;
+                                      if (self_signing_key && !self_signing_key->encrypted.empty())
+                                              secrets[self_signing_key->encrypted.begin()->first]
+                                                     [secrets::cross_signing_self_signing] =
+                                                       self_signing_key->encrypted.begin()->second;
+                                      if (user_signing_key && !user_signing_key->encrypted.empty())
+                                              secrets[user_signing_key->encrypted.begin()->first]
+                                                     [secrets::cross_signing_user_signing] =
+                                                       user_signing_key->encrypted.begin()->second;
+
+                                      for (const auto &[key, secrets] : secrets)
+                                              unlock_secrets(key, secrets);
+                              });
+                    });
+          });
+}
+
 } // namespace olm
diff --git a/src/Olm.h b/src/Olm.h
index 3400f993032c9628f7a565cdbba87eda4d5b85a1..78c1e64146f37c445853a7edcf85c454ebc258af 100644
--- a/src/Olm.h
+++ b/src/Olm.h
@@ -102,4 +102,11 @@ send_encrypted_to_device_messages(const std::map<std::string, std::vector<std::s
                                   const mtx::events::collections::DeviceEvents &event,
                                   bool force_new_session = false);
 
+//! Request backup and signing keys and cache them locally
+void
+request_cross_signing_keys();
+//! Download backup and signing keys and cache them locally
+void
+download_cross_signing_keys();
+
 } // namespace olm
diff --git a/src/RoomList.h b/src/RoomList.h
index d50c7de1516c3fcb9aa954c87760c29b83dc47ad..02aac869ffcb4abe49eaaa98ab03d8c5e36d96f5 100644
--- a/src/RoomList.h
+++ b/src/RoomList.h
@@ -43,7 +43,11 @@ public:
         void initialize(const QMap<QString, RoomInfo> &info);
         void sync(const std::map<QString, RoomInfo> &info);
 
-        void clear() { rooms_.clear(); };
+        void clear()
+        {
+                rooms_.clear();
+                rooms_sort_cache_.clear();
+        };
         void updateAvatar(const QString &room_id, const QString &url);
 
         void addRoom(const QString &room_id, const RoomInfo &info);
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 17d1adb8dfd243ae562af2071a46d31f60809ae4..708fb7fd9997c00e63f52ccf8c33018224a97136 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -637,6 +637,15 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
 
         deviceFingerprintValue_->setText(utils::humanReadableFingerprint(QString(44, 'X')));
 
+        backupSecretCached      = new QLabel{this};
+        masterSecretCached      = new QLabel{this};
+        selfSigningSecretCached = new QLabel{this};
+        userSigningSecretCached = new QLabel{this};
+        backupSecretCached->setFont(monospaceFont);
+        masterSecretCached->setFont(monospaceFont);
+        selfSigningSecretCached->setFont(monospaceFont);
+        userSigningSecretCached->setFont(monospaceFont);
+
         auto sessionKeysLabel = new QLabel{tr("Session Keys"), this};
         sessionKeysLabel->setFont(font);
         sessionKeysLabel->setMargin(OptionMargin);
@@ -649,6 +658,18 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
         sessionKeysLayout->addWidget(sessionKeysExportBtn, 0, Qt::AlignRight);
         sessionKeysLayout->addWidget(sessionKeysImportBtn, 0, Qt::AlignRight);
 
+        auto crossSigningKeysLabel = new QLabel{tr("Cross Signing Keys"), this};
+        crossSigningKeysLabel->setFont(font);
+        crossSigningKeysLabel->setMargin(OptionMargin);
+
+        auto crossSigningRequestBtn  = new QPushButton{tr("REQUEST"), this};
+        auto crossSigningDownloadBtn = new QPushButton{tr("DOWNLOAD"), this};
+
+        auto crossSigningKeysLayout = new QHBoxLayout;
+        crossSigningKeysLayout->addWidget(new QLabel{"", this}, 1, Qt::AlignRight);
+        crossSigningKeysLayout->addWidget(crossSigningRequestBtn, 0, Qt::AlignRight);
+        crossSigningKeysLayout->addWidget(crossSigningDownloadBtn, 0, Qt::AlignRight);
+
         auto boxWrap = [this, &font](QString labelText, QWidget *field, QString tooltipText = "") {
                 auto label = new QLabel{labelText, this};
                 label->setFont(font);
@@ -787,6 +808,28 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
           tr("Automatically replies to key requests from other users, if they are verified."));
         formLayout_->addRow(new HorizontalLine{this});
         formLayout_->addRow(sessionKeysLabel, sessionKeysLayout);
+        formLayout_->addRow(crossSigningKeysLabel, crossSigningKeysLayout);
+
+        boxWrap(tr("Master signing key"),
+                masterSecretCached,
+                tr("Your most important key. You don't need to have it cached, since not caching "
+                   "it makes it less likely it can be stolen and it is only needed to rotate your "
+                   "other signing keys."));
+        boxWrap(tr("User signing key"),
+                userSigningSecretCached,
+                tr("The key to verify other users. If it is cached, verifying a user will verify "
+                   "all their devices."));
+        boxWrap(
+          tr("Self signing key"),
+          selfSigningSecretCached,
+          tr("The key to verify your own devices. If it is cached, verifying one of your devices "
+             "will mark it verified for all your other devices and for users, that have verified "
+             "you."));
+        boxWrap(tr("Backup key"),
+                backupSecretCached,
+                tr("The key to decrypt online key backups. If it is cached, you can enable online "
+                   "key backup to store encryption keys securely encrypted on the server."));
+        updateSecretStatus();
 
         auto scrollArea_ = new QScrollArea{this};
         scrollArea_->setFrameShape(QFrame::NoFrame);
@@ -982,6 +1025,14 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
         connect(
           sessionKeysExportBtn, &QPushButton::clicked, this, &UserSettingsPage::exportSessionKeys);
 
+        connect(crossSigningRequestBtn, &QPushButton::clicked, this, []() {
+                olm::request_cross_signing_keys();
+        });
+
+        connect(crossSigningDownloadBtn, &QPushButton::clicked, this, []() {
+                olm::download_cross_signing_keys();
+        });
+
         connect(backBtn_, &QPushButton::clicked, this, [this]() {
                 settings_->save();
                 emit moveBack();
@@ -1137,3 +1188,30 @@ UserSettingsPage::exportSessionKeys()
                 QMessageBox::warning(this, tr("Error"), e.what());
         }
 }
+
+void
+UserSettingsPage::updateSecretStatus()
+{
+        QString ok      = "QLabel { color : #00cc66; }";
+        QString notSoOk = "QLabel { color : #ff9933; }";
+
+        auto updateLabel = [&ok, &notSoOk](QLabel *label, const std::string &secretName) {
+                if (cache::secret(secretName)) {
+                        label->setStyleSheet(ok);
+                        label->setText(tr("CACHED"));
+                } else {
+                        if (secretName == mtx::secret_storage::secrets::cross_signing_master)
+                                label->setStyleSheet(ok);
+                        else
+                                label->setStyleSheet(notSoOk);
+                        label->setText(tr("NOT CACHED"));
+                }
+        };
+
+        updateLabel(masterSecretCached, mtx::secret_storage::secrets::cross_signing_master);
+        updateLabel(userSigningSecretCached,
+                    mtx::secret_storage::secrets::cross_signing_user_signing);
+        updateLabel(selfSigningSecretCached,
+                    mtx::secret_storage::secrets::cross_signing_self_signing);
+        updateLabel(backupSecretCached, mtx::secret_storage::secrets::megolm_backup_v1);
+}
diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index d1ae93f0a252f705298064752c501cd46a592e12..c699fd594593600eda4a6e0a89de6ee1d3db0c20 100644
--- a/src/UserSettingsPage.h
+++ b/src/UserSettingsPage.h
@@ -253,6 +253,9 @@ signals:
         void themeChanged();
         void decryptSidebarChanged();
 
+public slots:
+        void updateSecretStatus();
+
 private slots:
         void importSessionKeys();
         void exportSessionKeys();
@@ -285,6 +288,10 @@ private:
         Toggle *mobileMode_;
         QLabel *deviceFingerprintValue_;
         QLabel *deviceIdValue_;
+        QLabel *backupSecretCached;
+        QLabel *masterSecretCached;
+        QLabel *selfSigningSecretCached;
+        QLabel *userSigningSecretCached;
 
         QComboBox *themeCombo_;
         QComboBox *scaleFactorCombo_;
diff --git a/src/Utils.cpp b/src/Utils.cpp
index 7fcaf5e2768ebd97dce69aa46640a65685e93802..1d8fcd9cb83270aa25b125f2a214133df74871ee 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -54,8 +54,9 @@ bool
 utils::codepointIsEmoji(uint code)
 {
         // TODO: Be more precise here.
-        return (code >= 0x2600 && code <= 0x27bf) || (code >= 0x1f300 && code <= 0x1f3ff) ||
-               (code >= 0x1f000 && code <= 0x1faff);
+        return (code >= 0x2600 && code <= 0x27bf) || (code >= 0x2b00 && code <= 0x2bff) ||
+               (code >= 0x1f300 && code <= 0x1f3ff) || (code >= 0x1f000 && code <= 0x1faff) ||
+               code == 0x200d;
 }
 
 QString
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index b9febf752a0883df3b3604faa180d0b6d806ea59..f346acf83bde8b597cc60c8d3feecee73cd85899 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -51,7 +51,12 @@ public:
         void sync(const mtx::responses::Rooms &rooms);
         void addRoom(const QString &room_id);
 
-        void clearAll() { models.clear(); }
+        void clearAll()
+        {
+                timeline_ = nullptr;
+                emit activeTimelineChanged(nullptr);
+                models.clear();
+        }
 
         Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; }
         Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }