The Double Ratchet Algorithm
Overview
The Double Ratchet (also called the "X3DH + Double Ratchet" protocol) is used by Signal, WhatsApp, and Google Messages to provide:
- Forward Secrecy: Old messages stay secure even if keys leak
- Break-in Recovery: New messages become secure after key changes
Notation Cheat-Sheet
The specs (and this demo's labels) use a compact shorthand. Keep this handy:
| Symbol | Meaning | Stage |
|---|---|---|
IK | Identity Key — long-term key identifying a party | X3DH |
EK | Ephemeral Key — Alice's one-off key for this handshake | X3DH |
SPK | Signed Pre-Key — Bob's medium-term key, signed by his IK | X3DH |
OPK | One-Time Pre-Key — Bob's single-use key (the 4th DH) | X3DH |
SK | Shared secret from X3DH; becomes the first root key | X3DH |
RK | Root Key — advanced by the DH ratchet | Double Ratchet |
CK | Chain Key — advanced by the symmetric ratchet | Double Ratchet |
MK | Message Key — used once to encrypt/decrypt one message | Double Ratchet |
DHs / DHr | Our / their current ratchet key pair (sending / receiving) | Double Ratchet |
Ns / Nr | Message number in the sending / receiving chain | Double Ratchet |
PN | Previous-chain length — # of messages in the prior sending chain | Double Ratchet |
Two Ratchets
| Ratchet | Purpose | Trigger | Output |
|---|---|---|---|
| Symmetric Ratchet | Forward Secrecy | Every message sent | Unique message key |
| DH Ratchet | Break-in Recovery | Direction change | New root + chain keys |
Symmetric Ratchet:
Uses HKDF to derive a unique message key from a chain key:
MK[n] = HKDF(CK[n], salt=0x01, "ratchet-wire-message")
CK[n+1] = HKDF(CK[n], salt=0x02, "ratchet-wire-chain")
DH Ratchet:
Uses fresh X25519 key pairs to derive a new root key and a new chain key (the root key is the HKDF salt, the DH output is the input key material):
root_new ‖ chain_key = HKDF(
ikm = DH(my_private, their_public),
salt = root_old,
info = "ratchet-wire-root"
)
Key Properties
- One-way: Chain keys advance forward; you can't derive the previous chain key
- Deletable: Message keys are ephemeral; no stored history
- Testable: Both parties use the same HKDF labels, ensuring consistency
Authenticated X3DH
Session setup runs the full X3DH ("Extended Triple Diffie-Hellman") key agreement and authenticates it:
- Signed pre-key: Bob signs his pre-key with his long-term Ed25519 identity key; Alice verifies that signature before deriving anything. A man-in-the-middle who swaps the pre-key is rejected — try the "Simulate a tampered pre-key" button on the Conversation tab.
- One-time pre-key: a single-use key contributes a fourth DH, so the session stays secret even if Bob's long-term keys later leak.
verify Sig(IK_B, SPK_B) ← abort on MITM
SK = HKDF( DH1 ‖ DH2 ‖ DH3 ‖ DH4 ) ← DH4 uses the one-time pre-key
This Demo vs. Production
Faithful to the spec here:
- Authenticated X3DH (signed pre-key signature + one-time pre-key)
- Double Ratchet with out-of-order handling and skipped-key bounds
- Low-order public keys rejected; message headers bound as AEAD associated data
Still simplified (for the demo):
- Identity uses a separate Ed25519 signing key + X25519 DH key, where production Signal uses a single key via XEdDSA (not exposed by Web Crypto)
- No pre-key server, prekey rotation, or OPK-exhaustion handling
- In-memory state only (no persistence)
References
Credits
The Double Ratchet Algorithm was developed by Trevor Perrin and Moxie Marlinspike (2016), building on prior work in the Off-the-Record Messaging (opens in new tab) protocol.