The Q-Ratchet is the per-message key ratchet that runs after the
handshake bootstraps a session. It advances four parallel chains — a
classical X25519 ratchet every message, an
ML-KEM-1024 ratchet every epoch, a symmetric
HKDF-SHA-512 chain every message, and a QRNG entropy
injection every few messages — so that compromise of any one chain
leaves the others standing. QERYX believes it is the first messenger to
ship a four-parallel-ratchet construction in production. Source:
crypto-core/src/quadruple_ratchet.rs (4,276 lines).
The four ratchets
Classical double-ratchet designs advance two chains: a Diffie–Hellman ratchet and a symmetric chain. Both are classical — a cryptographically-relevant quantum computer that breaks the discrete-log assumption reads all past and future output. The Q-Ratchet keeps both, then adds two more.
| Chain | Primitive | Cadence | What it buys |
|---|---|---|---|
| 1 — Classical DH | X25519 |
every message | per-message forward secrecy and classical PCS |
| 2 — Post-quantum KEM | ML-KEM-1024 |
every 100 messages (default) | post-quantum FS + PCS at epoch granularity |
| 3 — Symmetric chain | HKDF-SHA-512 |
every message | one-way key evolution between DH steps |
| 4 — QRNG injection | certified-QRNG entropy | every 5 messages (text), 250 packets (voice) | disconnects post-compromise chains from pre-compromise state |
Bootstrap from the handshake
The ratchet roots are bound to the exact handshake transcript that produced them. Any tweak to either side's key bundle or handshake nonce yields different roots.
classical_root = HKDF(ss, "CLASSICAL-RATCHET", info = "QERYX-v1-ROOT-KEY|" || transcript_hash)
pq_root = HKDF(ss, "PQ-RATCHET", info = "QERYX-v1-ROOT-KEY|" || transcript_hash)
combined_chain = HKDF(classical_root || pq_root, "COMBINED-CHAIN",
info = "QERYX-v1-CHAIN-KEY|" || transcript_hash)
Source: crypto-core/src/quadruple_ratchet.rs:478-568. A
transcript-free bootstrap exists for backward compatibility; new callers
use the transcript-bound variant.
Each chain, precisely
Classical X25519
Every message header carries the sender's current X25519 public key. When
a receiver sees a new one — compared in constant time — a DH ratchet step
fires and re-derives the classical root and chain
(quadruple_ratchet.rs:2173-2191).
Post-quantum ML-KEM-1024
Every 100 messages the sender generates a fresh ML-KEM-1024 keypair, encapsulates to the peer's current PQ key, and folds the shared secret into a new PQ root. An 8-byte sync tag derived from the PQ root rides the header, so a diverged PQ chain is detected at header-parse time — not N messages later as an opaque authentication failure. Decapsulation always runs the full implicit-rejection path, in constant time.
Symmetric chain
ChainKey.advance():
new_ck = HKDF(ck, "CHAIN-ADVANCE", info = "CHAIN-KEY", 32)
msg_key = HKDF(ck, "MESSAGE-DERIVE", info = "MESSAGE-KEY", 32)
Chain advance and message-key derivation use distinct labels: a leaked
message key does not reveal the next chain key, and a leaked chain key
does not reveal prior message keys. Intermediate heap copies are
explicitly zeroized before drop — Rust's Drop alone does not
zero allocator memory.
QRNG entropy injection
The fourth chain absorbs fresh certified-QRNG entropy into the symmetric chain every 5 messages (text) or 250 packets (voice). An 8-byte commitment on the header lets the receiver detect divergence without shipping the entropy itself. Injection disconnects post-compromise chains from pre-compromise state: an attacker who later steals the post-injection state cannot re-derive what came before it.
This is not QKD. Not quantum networking. Not quantum teleportation. We do not violate the no-communication theorem. We bind a key-derivation function to a verifiable physical measurement no classical adversary can fabricate in advance.
Header binding
The entire serialized ratchet header — version, flags, message number,
previous chain length, epoch, X25519 public key, ML-KEM ciphertext and
new encapsulation key, sync tag, QRNG commitment — is bound as
ChaCha20-Poly1305 AAD
(quadruple_ratchet.rs:2302-2342). A single-bit flip anywhere
in that set fails authentication at the receiver, even when the ratchet
would still derive a syntactically valid key for the claimed position.
Out-of-order delivery
- Per-session skip cap of 2,000 keys (raised to 5,000 for the production messenger) — sized for mesh and jammed transports that drop 30%+ of packets in bursts.
- A process-wide cap of ~52,428 cached entries (2 MiB) bounds memory against an adversary spinning up thousands of sessions with forged high counters.
- Cached keys older than two PQ epochs are evicted; eviction zeroizes the key bytes and decrements the global counter atomically.
Forward secrecy and post-compromise security
Forward secrecy. Compromise of any single chain at time t does not reveal messages before t: every derivation flows forward only, under labels that separate chain keys from message keys, and per-message keys are zeroized immediately after use.
Post-compromise security. After a complete state compromise, the next PQ epoch generates a fresh ML-KEM-1024 keypair that is independent of everything the attacker holds. Recovering confidentiality then requires breaking ML-KEM-1024 itself — a property that holds against a quantum adversary, where a classical-only DH ratchet recovers nothing.
| Compromise | Recovery mechanism | Bound |
|---|---|---|
| Classical secret only | PQ ratchet re-keys independently | ≤ 100 messages |
| PQ secret only | classical DH keeps advancing per message | next message |
| Full ratchet state | fresh ML-KEM epoch + QRNG injection | ≤ 100 messages; entropy re-anchor ≤ 5 |
What the Q-Ratchet does not defend against
Four ratchets bound the window a compromise stays open. They do not close a compromise that never ends, and these limits say exactly where that boundary falls.
- Continuous endpoint compromise. An attacker with persistent read/write on an unlocked device sees plaintext as it is typed. No ratchet defends against this; the threat model names it out of scope.
- A pre-handshake adversary. An attacker holding the full session root before the first message reads message 1; PCS begins at the first PQ epoch.
- Double-initiator desync. Two peers initiating simultaneously can desync chains; the mitigation is the generation-monotonic Frame Header v2 counter plus eager re-handshake, documented in the AEAD paper.
References
-
QERYX Protocol Specification §6 —
docs/SPEC/04-q-ratchet.md, source snapshot 2026-05-01; implementationcrypto-core/src/quadruple_ratchet.rs. - NIST FIPS 203, Module-Lattice-Based Key-Encapsulation Mechanism Standard (ML-KEM), 2024.
- RFC 5869 — HMAC-based Extract-and-Expand Key Derivation Function (HKDF), 2010.
- RFC 8439 — ChaCha20 and Poly1305 for IETF Protocols, 2018.
- The Hybrid Handshake — the ESv1 bootstrap this ratchet consumes.
- Message AEAD, Frame Header v2, and the Padding Ladder — the wire format that binds this header.