Riot Web 1.6, RiotX Android 0.19 & Riot iOS 0.11 — E2E Encryption by Default & Cross-signing is here!!

May 06, 2020

Hi folks,

We are incredibly excited to present the biggest change in Riot ever: as of the last 24 hours we are enabling end-to-end encryption by default for all new non-public conversations, together with a complete rework of Riot’s user experience around E2E encryption, powered by a whole new suite of encryption features in Matrix. We have released this simultaneously on Web, Desktop, iOS and RiotX Android! 🎉🍾🎊🔐🛡

The things we needed to fix to turn on E2EE by default were...

  • Cross-signing: verifying your own logins so others don’t have to.
  • Even better verification UX, to make cross-signing as painless as possible.
  • Replacing the old prototype UI for E2EE with all new UI/UX.
  • Ability to support non-E2EE clients.
  • Ability to search encrypted rooms.
  • Ability to view file indexes in encrypted rooms.
  • Fixing remaining “Unable to decrypt” errors.

...and we have now solved all of these enough to flip the switch!

This has been a long time coming - the first designs for cross-signing started in Nov 2018 with Nad’s User Experience Preview for End-to-end encryption, and for much of 2019 Uhoreg was plugging away by himself iterating on the specification and writing the core implementations. Then, since late Oct 2019 the rest of the Riot team has piled on to bring it to production - starting with the initial implementation in Feb 2020 for FOSDEM 2020, and now the full go-live today. So, let’s see what all the fuss is about!

Table of Contents

Introducing cross-signing

Everyone who’s used E2E encryption in Riot in the past will be familiar with the fact it warns you whenever any user logs in on a new device in any of your rooms - and that this gets very boring, very fast. The rationale was that E2EE is pretty useless if you don’t know if you’re actually talking to the person you think you are, just as SSL / TLS would be useless without certificates. Moreover, one of the most likely ways for someone to try to compromise encryption in Matrix is to gain access to the target’s account and add a ‘ghost’ device in order to spy on their conversations. Therefore you should warn users when unexpected devices suddenly appear so they can verify them. But in practice, we oversteered and this just ends up being annoying.

So to fix this, we’ve introduced the ability for users to verify their own devices at login - confirming that they are the rightful owners and so letting everyone who has verified them automagically trust the new login. This then gives us the ability to automatically warn users about unexpected logins on their account - and provides a form of two factor authentication in order to complete login (given the user needs to vouch for the new login somehow). We call this cross-signing, because the user is crossing over the trust of one device to another by signing its identity.

The first thing you’ll notice on upgrading to today’s Riot releases is that you’ll be prompted to “set up encryption” in order for cross-signing to work:

This is because we need to set up a recovery passphrase to allow you to verify your new logins when you have no other logged in devices available.  This passphrase secures your cross-signing data on the server, and replaces the old passphrase used to retrieve your history on new devices, when you backed up your encrypted message keys on the server. So you now have one single passphrase to both verify your devices when you log in and retrieve your history (if you have set up the backup with the toggle in the screenshot below).

Riot iOS does not have this setup phase yet - for now, to upgrade your account to support cross-signing you’ll need to use Riot Web, Riot Desktop or RiotX Android. (Heads up that Riot Android is shortly to be replaced by RiotX Android, and we haven’t implemented any of the new E2EE work on the old app.)

Then, having set up cross-signing on your account, you will be prompted to review your current sessions and verify them as your own - meaning that everyone who subsequently verifies you will automatically trust them.

Now is a great time to clear up your stale old sessions still present on your account - these are a security hazard (you can't prove that you own them by cross-signing them, so you will always appear untrusted) plus they slow down encryption for everyone else. You can remove them in the Settings > Security & Privacy section.

Similarly, when you log in on a new device, you’ll see this...

...meaning you can either vouch for the new login by verifying it from one of your existing sessions (which should be helpfully prompting you with an “Unverified login” popup) - or you can enter your recovery passphrase or recovery key to prove your ownership of the device. N.B. this only works if your other Riots have been upgraded to the latest version in order to support cross-signing.

Another nice new thing here is that if you are backing up your encryption keys to the server, the act of verifying yourself (either by verifying an existing device or entering the recovery passphrase/key) will magically make your encrypted history available on your new login! ✨

Once you’ve logged in, then all it takes is a single verification to trust other users and all of their current and future devices forever (unless they revoke it) - see all the green checkboxes resulting from a single successful verification.

Please note that cross-signing isn’t just for Riot: if you cross-sign any E2EE-capable Matrix client from Riot, then the verification will be recorded and others will trust it. So, if I logged into weechat on my account (speaks E2EE but doesn’t do cross-signing yet), as long as I verify it from Riot, everyone else who speaks cross-signing will automatically trust the weechat.

Cross-signing first merged to the develop branches of Riot back in February at FOSDEM 2020 (we’ve been iterating on it since to iron out some thinkos and bugs) and our talk Making and Breaking Encryption in Matrix provides a lot more technical detail for those interested.

On the spec side, the gory details live at:

One point for super-paranoid users: currently the private key used to sign your own devices and the private key used to sign other users are encrypted by your recovery passphrase/key and stored on the server to allow recovery if you lose all your devices. We also allow signing keys to be shared (gossiped) between devices, but right now the implementation also stores them encrypted on the server too.  This restriction will be fixed in future, but for now if you don’t trust your server with encrypted keys, you may want to hold off on using cross-signing.

Finally, in terms of technical details: internally, your recovery passphrase/key is referred to as your SSSS (Secure Secret Storage and Sharing, aka Quad-S) key - it encrypts arbitrary secret data (e.g. signing keys and backup keys) which are then stored in your personal account data on the server. You can see it in action in Riot Web/Desktop via the /devtools slash command if you look for the m.secret_storage.default_key in account data, and then the various secrets it encrypts are at m.cross_signing.master, m.cross_signing.self_signing, m.cross_signing.user_signing and m.megolm_backup.v1. (These respectively handle how you secure your cross-signing data overall; how you cross-sign your own devices; how you cross-sign other users; and how you encrypt your message backups if you use them). SSSS supports both storing data in account data (so you can recover it if you lose all your devices) as well as defining how to securely share/gossip it between device (if you don't want to store it on the server in account_data).

All new verification

The other side of cross-signing is that we’ve massively improved how verification works, to make it even easier to trust who you’re talking to.

The first big change is: we now support scanning QR codes for verification!  If you’re logging in on RiotX Android or Riot iOS, you can now verify by scanning a QR code on Riot Web (or any other Riot for that matter) and 💥, you’re in :D  We even do some magic so the device you just scanned will reciprocate and automatically trust you too. And don’t worry, if you don’t want to scan QR codes you can still choose to compare emoji (or even compare public fingerprints if you’re feeling nostalgic or masochistic).

The second big change is that: verifications now use DMs!  Rather than verifications being transient things which if you miss them are gone forever, we now show a popup when they appear and also track them in the Direct Message history with whoever is verifying you.

The third big change is: verifications now happen in the side panel!  Previously verifications were ugly modal popups which blocked what you were doing until you dealt with them (particularly annoying given you couldn’t resume them). Now you can hop in and out of them as needed, do multiple ones at the same time, and never be beholden to the evil modal popup again.

For a full run-through of cross-signing and verification on Riot Web, RiotX Android and Riot iOS, there’s a great video from Valère giving a full run through from last week’s Open Tech Will Save Us meetup:

For the gory details, the Matrix spec changes are:

Finally, now that verifying users is generally a once-off activity, we highly recommend having cross-signing parties!

Once upon a time this might have been a very silly fun thing to do in person (imagine a room of people yelling emoji at each other and frantically running around scanning each other's QR codes) - but in our brave new dystopian future the best we can do is to do it over Jitsi. (Obviously it's more efficient to do it via 1:1, but it's more fun to do it en masse - turns out that you can successfully do at least 3 overlapping emoji verifications via Jitsi!)  Dave (original author of Riot Web and cross-signing dev champion) recorded the edited highlights of the New Vector cross-signing party last week for posterity... :D

All new UI/UX for E2EE

Hopefully it’s clear from the previous sections that a huge amount of effort has gone into refining the UI and UX here by Nad - Riot’s lead designer. Anyone interested in the full design assets we've been working from can check them out at Figma.

One of the main improvements we should call out is that we finally have a consistent way now of representing encrypted rooms and trust level in the UI using shields:

You’ve verified this user, and they’ve verified all their sessions. A room containing all green users is shown as green.
You’ve verified this user, but they have risky unverified sessions logged in! A room containing any red users is shown as red.
You haven’t explicitly verified this user yet. A room with a mix of black or green users is shown as black.

With the new shields, we no longer nag the user whenever a new device is added in their conversations - instead we expect them to see the room's shields flash red everywhere if an untrustworthy device is added.

Padlocks are now used to show rooms which are private (similar to other messaging apps) rather than encrypted - although in practice almost all private rooms should be encrypted in our new E2EE-by-default world. We also now refer to your active app logins as ‘sessions’ as opposed to the rather cryptic ‘devices’ we used before.

Finally, we no longer nag and block messages even when an unverified session appears in a conversation - the user’s shield going red is your cue to warn you that badness may be happening.

Supporting non-E2EE Clients

One of the other major things that has been stopping us making all private conversations encrypted by default is not wanting to suddenly sabotage all the clients, bots and bridges which the Matrix community has come up with over the years. Implementing E2EE securely is Hard, and it’s crazy to think that each and every random Matrix project would have to figure it out themselves.

So, we created Pantalaimon - a little application which does all the E2EE heavy lifting for you; letting any existing Matrix client/bot/bridge participate in encrypted private rooms without having to actually speak E2EE. Pan (as he’s called to his friends) works incredibly well - we use it in production to use Mjolnir from encrypted rooms, for instance. Originally written in Python by poljar using his matrix-nio library, there’s currently a porting to Rust in progress using our new matrix-rust-sdk library - providing a truly lightweight daemon to handle the most complicated bits of Matrix. Our FOSDEM talk has a demo and a lot more information for those interested.

It's worth noting that while Pantalaimon is super useful, an increasing number of clients do now have native E2EE support - powered by an ever increasing number of independent E2EE implementations.  The current list includes:

Searching and Indexing Encrypted Rooms

Another huge task has been supporting search in E2EE rooms. As of Riot Desktop 1.6 we have full support by default for search in E2EE rooms - and you can now even list all the files in E2EE rooms in the right-hand panel.

This is thanks to Seshat - another project from poljar, which provides client-side full text search indexing of E2EE rooms, securely encrypting and storing the indexes so that Riot Desktop can implement search. Seshat is written in Rust, and in future we expect to see it integrated into Riot Mobile (and Pantalaimon) too - gossipping the encrypted search indexes between desktop and mobile, so you can quickly search your rooms no matter what platform you’re on.

It’s worth noting this is also the first time that Riot Desktop has deviated in functionality from Riot Web - and we now have the full build infrastructure in place to ship much richer native functionality (like Seshat) for Linux, macOS and Windows in Riot Desktop. Watch this space for more native functionality - we’ll be finally able to implement push to talk, for instance!

Solving “Unable to decrypt” errors

The final thing which has kept E2EE off by default is that we’ve needed to chase down the remaining scenarios where clients end up not having the keys they need to decrypt received messages. This turns out to be a real pain, because there are some scenarios where you may legitimately be unable to decrypt (e.g. the sender doesn’t know you exist, because when they sent the the message their server hadn’t yet seen you were in the room - or perhaps the sender has blacklisted you - or perhaps you weren’t in the room when the messages were sent?). Conversely, any bug when transporting keys between clients could result in the same symptoms (e.g. if a client or server has failed to correctly track which devices are in a room, or if a user clones their device from a backup and the encryption fails to handle the resulting split brain). Distinguishing bugs from legitimate behaviour when the symptoms are all the same is a bit of a nightmare.

In the end, we’ve solved this both by hunting down the remaining bugs, but also improving the user experience so that Riot tells you *why* it couldn’t decrypt a given message (e.g. “you were blacklisted” or “the sender didn’t know you were in the room” etc). It’s a huge improvement, and it’s pretty rare to see one of these errors unless something strange is going on. We’ve also taken some solace in the fact that WhatsApp has the same problem, and they’re not even decentralised!

What’s in future for E2EE?

While we’ve got to the point where we feel comfortable turning on encryption by default, there’s still an extensive roadmap of work we can do to improve encryption further. Quickly going through the main remaining items:

  • There’s inevitably going to be more UI/UX polishing needed based on user feedback. Thanks to the amazing testing from everyone who used the release candidates we shipped over the last two weeks we believe we caught the remaining showstoppers, but there’s always going to be more fine-tuning. See “Phase 4” in the dashboard at the end of this post for more details.
  • Currently cross-signing requires that you store your encrypted cross-signing keys on the server, meaning that an attacker on the server who guesses your recovery passphrase/key could cross-sign a malicious device to spy on your account. Generally this is fine, as if you lose all your devices it’s the only way to recover your account’s cross-signing state. However, if you are super paranoid, you may wish to share (gossip) the keys between devices instead rather than storing them encrypted on the server. The protocol supports that - we just haven’t hooked it up yet.
  • It’s clunky to have two passwords now - one for your account, and one for your encryption state. We could extend your encryption password to also unlock your account too - but it’d mean completely rewriting Riot’s login process and password reset logic (and migrating everyone’s passwords over). We could have done it in this release, but we’re already late and so we decided to defer it in future.
  • Currently we don’t have a way to mark a user as trusted without explicitly verifying their keys. We have plans for implicitly trusting users where we ‘trust on first use’ (TOFU), but this complicates the UI further - and while we’ve designed it, it got punted to a future release.
  • You can’t set up cross-signing on Riot iOS; you get routed to sort it out on Riot Web instead. This is a real shame if your only device is on iOS - we just need to port this over.
  • Encrypted search via Seshat is only supported on Riot Desktop today. We need to implement it in RiotX Android and Riot iOS and possibly Riot Web too, and then share the encrypted search indexes between the devices.
  • We don’t have a way to selectively export / import keys for a room in order to consciously fix the history for a user who’s missing history. Obviously this sidesteps E2EE, but sometimes it’s a really useful thing to have. There are third party tools which provide this, but it’d be great for Riot to have native support.
  • At the protocol layer, there’s nothing stopping you delegating trust by signing a trusted user. For instance, a sysadmin could sign every new user joining a team, and they would then automatically trust everyone else in the team. We haven’t hooked this up in Riot yet though.
  • Finally, last but not least: it’s been a few years since Olm and Megolm got their original public audits from NCC Group. Whilst the core cryptographic ratchets haven’t changed significantly since then, all this key management work is of course new - and we will be doing another audit covering the whole stack once we are confident we have a long-term stable release worthy of validation.

What else has been going on in Riot?

While E2EE-by-default has been our primary project for the last 6 months, there’s of course been lots of other stuff happening too - but we’ve been so engrossed in E2EE that we forgot to properly tell anyone about it. So, better late than never, here’s some of the highlights!

Support for self-hosted Jitsi

Back when we first added video conferencing into Matrix in 2015, the intention was always to let folks hook up to a local conferencing server rather than the default we ran at - but the resulting additional complexity meant that it kept getting bumped down the todo list… until now!

Riot Web now has support to directly instantiate Jitsi video/voice conferences, integrating Jitsi directly via Riot Web rather than by having to use a custom integration manager. This means that to configure your Riot deployment to point by default to a custom Jitsi is now a single configuration line: has the details. You can also see a full tutorial for setting this up over on the Matrix blog. It’s worth noting that so far only Riot Web and Desktop have the ability to use this feature, but Riot iOS and RiotX Android should follow shortly.  (In fact, today's iOS release supports joining self-hosted Jitsis, and will support creating them too shortly).

Proper keyboard shortcuts, at last!

One of the recurring bits of feedback we’ve heard from Mozilla has been the need for better keyboard shortcuts in Riot - both documenting the ones we have already, as well as introducing new ones like “jump to previous/next rooms” and “jump to previous/next unread rooms”. Well, we’re happy to say this is finally here thanks to t3chguy’s efforts!  You can see all the new shortcuts by hitting Cmd/Ctrl + / or hitting the ‘Keyboard Shortcuts’ button in the Help tab of User Settings. It looks like this!

All new Room Directory!

One of the worst bits of UI/UX in Riot Web for many years has been the Room Directory - particularly how you go about viewing the directories on other servers. Again, we’re happy to announce that this is now fixed with a massively improved UI - it’s now trivial to add new servers to your room directory, and view the room directories exposed by bridges on your server. It looks like this!

All new invite dialog!

Hopefully everyone has also spotted the new Invite dialog - hugely improving the process of inviting people to conversations by providing suggestions based on who you’ve been talking to recently, and streamlining the old design. There’s are a few cosmetic and accessibility issues we’re still working through here, but it’s definitely an improvement on the old one:

Introducing Riot Nightly

With next generation features like E2EE Search landing first in Riot Desktop, we’ve massively improved our tooling around Riot Desktop. The biggest change has been setting up full continuous integration and deployment (CI/CD) using a secure build server for Riot Desktop for Windows, MacOS and Linux - and now every day at 08:00 UTC we ship a “nightly” automatic new build of Riot Desktop built from the very latest code in the develop branch of Riot Web.

In other words, if you are a power user who wants to help test and play with the very latest functionality (knowing that it may well be unstable and buggy), you can now install “Riot Nightly” from (Windows, MacOS) or the riot-nightly Debian package at and help test the desktop app, much as you can test the very latest bleeding edge of Riot Web at

This has been transformative in terms of improving testing of the Desktop app (and also is empirically a big performance improvement over, as Electron doesn’t have the safe performance safety limits as Chrome) - we strongly recommend testers give it a go. We expect to ship more Desktop-only features in future, so the more folks we can get testing it the better!  Huge thanks to Dave for surviving the nightmare of getting all the CI/CD set up.

Custom Themes

Something that we’ve added but forgotten to mention is that you can now define custom themes for Riot Web and Desktop!  There’s not much official UI or documentation for it yet beyond the developer docs, but Aaron Raimist has done an excellent job of how it works and has started to amass a collection of themes over at - so you too can get tinting if you want :)

Proper Emoji Picker for Reactions

This one must be very familiar to everyone now, but it’s worth calling out that Tulir very kindly contributed a full emoji picker implementation for reactions - going way beyond the 8 that we shipped with originally. It’s just as well that Tulir contributed it as this is the sort of thing that could easily have got stuck behind the E2EE-by-default epic, but it makes a huge difference to the app; thank you!

Other UX and accessibility improvements

Based on feedback from Mozilla, we’ve finally added the ability to order the roomlist alphabetically rather than by most recently active. This is a temporary measure until we fully re-work the room list implementation in the near future, and as a result it has some flaws (e.g. it doesn’t tell you yet whether active rooms are off the top/bottom of an overflowed scroll list), but it seems to have made some folks happy :)

We’ve also continued to refine accessibility - continuing to add as much screen-reader support as we can. If you’re a screen-reader user, please do keep the feedback coming so we know where best to focus next!

Also, it’s not merged yet, but we have support for dynamic font sizing in Riot Web on the horizon - expect to see it in a later Riot 1.6.x release. Huge thanks to Jorik for putting it together… and preparing the timeline CSS for more interesting layouts in future!

Single Sign-On Improvements

We’ve seen a huge shift in focus towards larger Matrix deployments with exciting Single Sign On integrations over the last 6 months - most notably Mozilla and their IAM (Auth0) SAML authentication, but many others too.

Implementing full SSO support has ended up being much more effort than we anticipated, mainly because SSO isn’t just how you sign in, but also how you authenticate whenever you do certain privileged operations (e.g. deleting devices, bootstrapping your cross-signing configuration, adding email addresses etc). But as of Riot Web 1.6 we finally have this sorted out, so anywhere where historically the app prompted you for your password will also support prompting you for SSO too. This also required a large amount of reworking in Synapse, which should be available in Synapse 1.13, due in the next few days.

Performance and Security Improvements

There’s been a fairly constant stream of performance work in the background - Matthew went on a Captain Ahab mission to slay DOM leaks while chasing a Chrome performance issue (which turned out to be unrelated to memory usage), meaning that Riot should no longer slowly leak memory over time.

Meanwhile, t3chguy killed off Gemini Scrollbars - this is the library we used to use to make all our scrollbars look like MacOS ones, no matter what OS the user was on. While it worked, it was a constant cause of performance problems and weird bugs, and as of a few weeks ago we’ve now shifted to pure CSS scrollbars, which have finally come of age. The render performance improvements are spectacular - if you try dragging the corner of a Riot Web window and resizing the layout rapidly and smoothly update, whereas with Gemini it could be incredibly jerky.

Finally, t3chguy also killed off - the slightly hacky way we used to sandbox E2EE attachments to stop malicious encrypted attachments being able to attack your Riot after being decrypted. All modern browsers now support iframe sandboxing with the necessary security capabilities, so we use that instead - see and for more details (and thanks to rugk for digging into this in the first place!)

What’s next?

Our next big project for Riot is First Time User Experience (FTUE).

Specifically, we are determined to ensure that the first few minutes and hours that a typical mainstream user spends using Riot ends up delighting rather than confusing them. Everyone hopefully agrees that today Riot is a great tool… once you’re used to it. But almost everyone also agrees that today the initial experience of using the app contains a few too many “wtf” moments. For instance: Why do public chatrooms send me push notifications by default? How do notifications work anyway, and how do i set up notifications on keywords? Why is so ugly? Why is so slow? Why don’t Communities work? Why does the timeline jump around when I scroll backwards on my Mac? Why do I have to go through a full registration process in order to speak in a room - why can’t I incrementally sign up? Why are direct messages called ‘rooms’? Why does Riot let me create multiple DMs with the same user? Why is the Room List split into these weird narrow letterboxes?  Why isn’t there a proper emoji picker? Why is the password reset UX so strange? etc. etc.

As you can see from the “what else has been going on” section above, we’ve been making some pretty major improvements to usability in parallel with the E2EE-by-default work, but there is much more to be done here. We want the app to feel intuitive and familiar to refugees from both WhatsApp and Slack/Discord, and will do everything in our power to do so - putting the whole Riot team on the case (and freezing new features) until this is fixed.

In parallel with this, performance on massive servers like is obviously a concern, but the Synapse team is working hard on clustering which should solve this in the coming weeks!

What about RiotX?

We’ve concentrated mainly on Riot Web and Desktop above, but it’s worth reiterating that all of the E2EE-by-default work is also available on both RiotX Android and Riot iOS. This puts us in a slightly weird situation given RiotX is still technically in beta, and most casual Android users end up on the old legacy Riot Android app. However, we can’t replace the old app until RiotX has feature parity with it. So, our plan is:

  • To use cross-signing today, you have to install RiotX.
  • Now E2EE-by-default is done, the RiotX team is going back to working through the remaining backlog to get feature parity with the old app - the first three columns of show the work remaining to be done.
  • The good news is that all of this work is a “simple matter of programming” (with the exception of some further performance investigations required on the storage layer) - i.e. we are hoping there are no nasty surprises left.
  • Our plan is then to exit beta on RiotX by replacing the current Riot Android app on the stores with RiotX (and migrating the user’s data over). This is to avoid everyone having to log back in, or install a new app, or otherwise get confused.
  • We’ll then remove the RiotX app (or replace it with a pointer for folks to switch to Riot itself).

In terms of timings, this will happen “when it’s ready”. But based on the current maturity of the app and the enthusiasm of the team to finish it off, it’s not too far away :)

And finally... the dashboard.

When we do big projects E2EE-by-default we maintain a massive dashboard of all the bugs which need to come together in order to ship.

For the sake of completeness (and to underscore what a literal epic this has been), here’s the final dashboard as of the point of cutting release candidates - split into user stories. Phase 1 was “First cut for FOSDEM”, Phase 2 was “Make it stable enough to merge to develop branches” and Phase 3 was “Make it stable enough to ship to production” and Phase 4 is “Post-launch polish”. In other words, we’re at the end of Phase 3 now. So, for posterity, here’s what went into this...

Related Posts

By the same author

Thanks for reading our blog— if you got this far, you should head toelement.ioto learn more!