E2EE v2 in one page
E2EE v2 replaces DChat's first encryption design from end to end. One-to-one chats now run on PQXDH and the Double Ratchet, with a post-quantum ratchet on top. Groups run on MLS, the open standard for group encryption. Both use post-quantum key exchange. And every device has its own keys, vouched for by your account and checked against a list anchored on chain.
- One-to-one chats: PQXDH, the Double Ratchet and SPQR, matched byte for byte against the reference implementation over a whole 367-message session.
- Groups: MLS (RFC 9420) on suite 0xF005, ML-KEM-1024 with P-384.
- Key exchange: ML-KEM-1024, NIST's highest post-quantum category, in chats and groups.
- Devices: each one certified by your account with ML-DSA-87 and Ed25519.
- Group calls: end-to-end encrypted frame by frame, up to 200 people.
What changed from v1
The first design used one chat key per account, copied to every device and rotated on a schedule. A stolen key opened a window of messages, and nothing in it resisted a quantum computer. E2EE v2 removes the shared key entirely.
| v1 | E2EE v2 | |
|---|---|---|
| Message keys | One key per window of 10,000 messages or 30 days | A new key for every message, deleted after use |
| After a key is stolen | Exposed until the next rotation | Heals by itself on the next exchange |
| Post-quantum key exchange | None | ML-KEM-1024 in chats and groups |
| One-to-one chats | Static secp256k1 ECDH between accounts | PQXDH and the Double Ratchet, per device |
| Groups | Sender keys sealed to identity keys | MLS (RFC 9420), suite 0xF005 |
| Group calls | The call server decrypted the media | End-to-end encrypted, frame by frame |
| People in one group call | Up to 25 | Up to 200 |
| Message cipher | ChaCha20-Poly1305 | AES-256-GCM |
| Linked devices | Got a copy of the account's private keys | Each makes its own; no private message key leaves a device |
| Account ID | did:dchat:<username> | Hashed from the account's public keys, never the username |
| Who vouches for a device | The device registry | An ML-DSA-87 + Ed25519 certificate on a list anchored on chain |
Identity: an ID made from keys
Your recovery phrase now gives you two separate keys. The wallet key, on secp256k1 as before, signs chain transactions and nothing else. The new account identity key is a hybrid of ML-DSA-87 (FIPS 204) and Ed25519. It has three jobs: define your DID, sign your device list, and sign your device certificates. It never encrypts anything.
That is 52 lowercase characters after the prefix. Anyone holding the public keys can check a DID without asking a server, and it never contains your username. Usernames become names that point at a DID: people and chats are matched by DID, and a mention or link carries the DID underneath. An identity signature counts only when both halves of the hybrid check, with no classical-only fallback.
Devices and the device list
Every phone, tablet and computer is now its own endpoint. Each one makes its own keys on the device, and they are never exported and never backed up. Linking a new device gives it a certificate, not a copy of anyone's keys.
The device certificate
Your account identity key signs a certificate naming the device and its keys, with ML-DSA-87 and Ed25519 together. Every device checks the certificate of every device it talks to. A quantum attacker cannot forge one, so cannot slip a fake device into your account.
The device list, anchored on chain
Your certificates form a device list signed by your account key, and its hash and version are recorded on the DChat chain. Clients compare what a relay serves with that anchor, so a relay cannot add a device to your account or hide one from your contacts.
Removing a device
A removed device is left out of every new message and dropped from your groups. It never held your other devices' keys, so there is nothing shared to rotate.
One-to-one chats: a new key for every message
One-to-one chats use our own implementation of PQXDH, the Double Ratchet and SPQR, written from the published specifications and checked against the reference implementation. A message to a person is encrypted separately for each of their devices, and for each of your other devices, every one on its own session.
Starting a session: PQXDH
Each device publishes signed prekeys to the relays: X25519 keys and ML-KEM-1024 keys. The first message combines three or four X25519 exchanges with an ML-KEM-1024 encapsulation in one key derivation, so an attacker has to break both X25519 and ML-KEM to read it.
IKM = 0xFF x 32 || DH1 || DH2 || DH3 [|| DH4] || SS_mlkem1024,
info = "DChat_PQXDH_CURVE25519_SHA-384_ML-KEM-1024" )
Before using a bundle, the sender checks that its certificate is signed by the account and current, that it names the device and the bundle's key, that every signature holds, that the ML-KEM key passes the FIPS 203 check, and that the signed prekey is no more than 30 days old.
Every message after: the Double Ratchet
Each message is sealed with its own key, deleted once used, and every reply turns the ratchet with a fresh X25519 exchange. That gives forward secrecy: a key stolen today does not open last month's messages. It also gives healing: after a device's state is stolen once, the next exchanges lock the thief out again, with nothing for you to do.
The post-quantum ratchet: SPQR
A ratchet built only on X25519 heals against a classical attacker, not a quantum one. SPQR, the sparse post-quantum ratchet, keeps mixing fresh ML-KEM-1024 secrets into the session, so healing holds against a quantum attacker too.
When a message will not open
If a device loses its session, for example after restoring an old copy of its data, it asks the sender to resend. The sender starts a fresh session and sends the message again. We tested exactly this on real devices.
| Job | Algorithm |
|---|---|
| Key agreement | X25519 + ML-KEM-1024 (FIPS 203) |
| Message encryption | AES-256-GCM |
| Key derivation | HKDF-SHA-384, HMAC-SHA-384 |
| Session signatures | Ed25519, strict verification |
| Device vouching | Certificate signed with ML-DSA-87 + Ed25519 |
Groups: MLS on 0xF005
Groups use MLS, Messaging Layer Security (RFC 9420), the IETF standard for group end-to-end encryption. Every device is a leaf in the group's key tree, and every change of members moves the group to a new epoch with new keys.
- New members cannot read the past. They receive the current epoch's keys only.
- Departed members cannot read the future. A removal moves the group to keys the removed device never gets.
- The group's rules gate every change. Each commit is checked against the group's admin rules before any device applies it.
- Photos and files too. Every file gets its own key, carried inside the encrypted MLS message.
| Suite 0xF005 | |
|---|---|
| Name | MLS_192_MLKEM1024P384_AES256GCM_SHA384_P384 |
| Key exchange | ML-KEM-1024 + P-384 hybrid HPKE |
| Encryption | AES-256-GCM |
| Hash | SHA-384 |
| Leaf signatures | ECDSA P-384 |
| Leaf credential | The device certificate (ML-DSA-87 + Ed25519) |
0xF005 is a private-use code point for the hybrid ML-KEM-1024 + P-384 suite, and our implementation is checked byte for byte against an independent implementation of it.
Calls
Group calls: now end-to-end encrypted, and up to 200 people
In the first design, the call server decrypted group call media. Now every frame is encrypted on your device before it leaves. Each participant's frames get their own key, derived from the group's MLS keys: a fresh key for every call, and new keys whenever someone joins or leaves. The call server forwards frames it has no key for, and a device drops frames rather than send them in the clear. One group call now holds up to 200 people, up from 25.
One-to-one calls
One-to-one calls are set up inside your E2EE v2 session: the call signals travel through the same encrypted channel as your messages. The media is end-to-end encrypted with DTLS-SRTP, the WebRTC standard, with an emoji check you can compare.
How we checked it
Both protocols were checked against their reference code, byte for byte, and then on real phones.
- Against the reference implementation, a whole session. We ran the reference implementation of PQXDH, the Double Ratchet and SPQR outside our code base and recorded one complete session: keys, signatures and 367 messages. Our implementation reproduces every byte. The recording ships with our tests, and the check runs with them.
- Against an independent MLS implementation, the suite. We ran a production MLS implementation of suite 0xF005 at a pinned version and matched it byte for byte: suite identifiers, the 1,665-byte hybrid public key, an HPKE seal and open, the exporter, the P-384 key and signature, and a key package it built. It in turn validated a key package built by DChat.
- On real devices. Android and iPhone on a full test network: signup, chats both ways, voice messages, edits, reactions, pins, unsend, a broken session healing itself, voice and video calls, groups with a group video call, linked devices with history, and contact requests.
- More than 1,400 automated tests across the encryption core, the client, groups, devices and contacts, all passing on the code that ships.
Where post-quantum cryptography protects you
A quantum computer strong enough to break today's public-key cryptography would put every recorded conversation at risk. E2EE v2 puts post-quantum algorithms where that risk lives: in the key exchange of every chat and every group, and in the certificates that decide which devices belong to an account.
| Part | Protection |
|---|---|
| Key exchange in one-to-one chats (PQXDH, SPQR) | Post-quantum: ML-KEM-1024 |
| Key exchange in groups (suite 0xF005) | Post-quantum: ML-KEM-1024 |
| Healing after a key theft (SPQR) | Post-quantum: ML-KEM-1024 |
| Message encryption | AES-256-GCM, 256-bit keys |
| Device certificates | Post-quantum: ML-DSA-87, with Ed25519 |
Messages recorded today stay unreadable to a future quantum computer: "harvest now, decrypt later" does not work on E2EE v2 content. Every key exchange is a hybrid of a classic curve and ML-KEM-1024, so an attacker has to break both, and the device certificates mean nobody can add a fake device to your account.
Sealed sender: relays do not see who wrote
Encrypting what you say is half of privacy. The other half is who you say it to. E2EE v2 ships with sealed sender, a multi-recipient seal checked byte for byte against the reference implementation, so a relay delivers your message without learning which account sent it.
The seal
Every frame to another account is sealed to the X25519 identity keys of the recipient's devices, with one slot per device and a fresh secret for every frame. Your account, your device and your identity key travel inside the seal, where only the recipient can read them. The recipient checks that the seal really comes from the identity it names, then opens the message with the session as usual.
u8 count (1..32)
count x { c[32], at[16] } // one slot per recipient device, random order
e_pub[32]
AES-256-GCM-SIV(K, padded content) || tag[16]
Delivery without a name
Sealed frames go in on a separate channel that carries no login at all. To stop spam without knowing the sender, each frame carries a short proof made with the recipient's delivery key, a key only the recipient's contacts and group co-members hold. The relay can check that the frame comes from one of the recipient's people, never which one.
Less to read from the outside
The content inside the seal is padded to fixed steps (512 bytes, 1, 2 and 4 KiB), the message type and expiry settings are hidden inside it, and the timestamp a relay sees is rounded to the minute.
What a relay handles
| Data | Can a relay read it? |
|---|---|
| Messages, photos, files, voice messages | No, end-to-end encrypted |
| Call audio and video, one-to-one and group | No, end-to-end encrypted |
| Group messages and group keys | No, MLS |
| Which account sent a message | No, sealed sender |
| Message type and expiry settings | No, inside the seal |
| Exact size | No, padded to fixed steps |
| Who to deliver to | Yes, so the message can arrive |