E2EE v2 · Security whitepaper

Encryption built to outlast the quantum computer

DChat's second-generation end-to-end encryption, from the account up: who you are, how your devices prove it, how chats, groups and calls are sealed, what the network can still see, and exactly what is post-quantum today. For the plain-language version, read what changed for you.

DChat SecurityProtocol version 2.0Reading time 14 minPlain-language version

The E2EE v2 stack

Post-quantum key exchange or signatures
1IdentityA DID hashed from an ML-DSA-87 + Ed25519 account key
2DevicesCertificates signed with ML-DSA-87, list anchored on chain
3ChatsPQXDH, the Double Ratchet and SPQR
4GroupsMLS on suite 0xF005, ML-KEM-1024 + P-384
5CallsFrame keys from the group's MLS, up to 200 people
6Sealed senderMulti-recipient sealed sender, the sender hidden from relays

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.
It turns on by itself. A chat moves to E2EE v2 as soon as both people are on the new app, and a group moves to MLS once every member is. Once a chat is on E2EE v2, it stays there.

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.

v1E2EE v2
Message keysOne key per window of 10,000 messages or 30 daysA new key for every message, deleted after use
After a key is stolenExposed until the next rotationHeals by itself on the next exchange
Post-quantum key exchangeNoneML-KEM-1024 in chats and groups
One-to-one chatsStatic secp256k1 ECDH between accountsPQXDH and the Double Ratchet, per device
GroupsSender keys sealed to identity keysMLS (RFC 9420), suite 0xF005
Group callsThe call server decrypted the mediaEnd-to-end encrypted, frame by frame
People in one group callUp to 25Up to 200
Message cipherChaCha20-Poly1305AES-256-GCM
Linked devicesGot a copy of the account's private keysEach makes its own; no private message key leaves a device
Account IDdid:dchat:<username>Hashed from the account's public keys, never the username
Who vouches for a deviceThe device registryAn 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.

The account identifier did = "did:dchat:" || base32-lower( SHA-384( "dchat:did:v2" || ed25519_pub || mldsa87_pub )[0..32] )

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.

PQXDH shared secret SK = HKDF-SHA-384( salt = 0x00 x 48,
     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.

JobAlgorithm
Key agreementX25519 + ML-KEM-1024 (FIPS 203)
Message encryptionAES-256-GCM
Key derivationHKDF-SHA-384, HMAC-SHA-384
Session signaturesEd25519, strict verification
Device vouchingCertificate 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
NameMLS_192_MLKEM1024P384_AES256GCM_SHA384_P384
Key exchangeML-KEM-1024 + P-384 hybrid HPKE
EncryptionAES-256-GCM
HashSHA-384
Leaf signaturesECDSA P-384
Leaf credentialThe 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.

PartProtection
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 encryptionAES-256-GCM, 256-bit keys
Device certificatesPost-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.

Sealed frame u8 version = 0x22
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

DataCan a relay read it?
Messages, photos, files, voice messagesNo, end-to-end encrypted
Call audio and video, one-to-one and groupNo, end-to-end encrypted
Group messages and group keysNo, MLS
Which account sent a messageNo, sealed sender
Message type and expiry settingsNo, inside the seal
Exact sizeNo, padded to fixed steps
Who to deliver toYes, so the message can arrive
DChat SecurityWritten against the shipped protocol and its test vectors.

Update, and your chats move up

E2EE v2 turns on by itself once both sides are on the new app. Free on Android, web and desktop.