INVITE ONLY
OBSERVATORY

Research

protocol-spec

Q-Ratchet: Four Parallel Ratchets

Version
v1.0
Category
protocol-spec
Published
Last updated
Authors
QERYX Research
Primitives X25519 ML-KEM-1024 HKDF-SHA-512 ChaCha20-Poly1305
What changed v1.0
  1. v1.0 First publication of the four-ratchet construction and its FS/PCS claims.
Contents
  1. §1 The four ratchets
  2. §2 Bootstrap from the handshake
  3. §3 Each chain, precisely
  4. §4 Header binding
  5. §5 Out-of-order delivery
  6. §6 FS and PCS claims
  7. §7 What it does not defend
  8. §8 References

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.

ChainPrimitiveCadenceWhat 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
X25519 — EVERY MESSAGE ML-KEM-1024 — EVERY EPOCH HKDF CHAIN — EVERY MESSAGE QRNG — EVERY 5 MESSAGES
FIG. 1 — the four chains at their default cadences. Dots mark ratchet steps; the PQ chain steps once per 100-message epoch.

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.

CompromiseRecovery mechanismBound
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

  1. QERYX Protocol Specification §6 — docs/SPEC/04-q-ratchet.md, source snapshot 2026-05-01; implementation crypto-core/src/quadruple_ratchet.rs.
  2. NIST FIPS 203, Module-Lattice-Based Key-Encapsulation Mechanism Standard (ML-KEM), 2024.
  3. RFC 5869 — HMAC-based Extract-and-Expand Key Derivation Function (HKDF), 2010.
  4. RFC 8439 — ChaCha20 and Poly1305 for IETF Protocols, 2018.
  5. The Hybrid Handshake — the ESv1 bootstrap this ratchet consumes.
  6. Message AEAD, Frame Header v2, and the Padding Ladder — the wire format that binds this header.