04 Science — the ratchet
The key that only turns forward.
A real derivation chain runs in this page. Step it with your thumb, watch a fresh key seal fresh bytes — then try to turn it backward.
01Feel — one-way time, as a shape
A chain where every link devours the last.
Each chain key births exactly two things: one message key, and the next chain key. Then it is destroyed. Hold the newest link and every older one is already gone — the chain gives you the future and burns the past.
The arrow is a hash function. Following it costs one call; reversing it costs a preimage search. That asymmetry is the whole machine.
02Touch — real SHA3-256 steps, real seals
Step it. Then try to go back.
Every press runs a real SHA3-256 derivation and seals a fresh message under the new key — while the old keys are zeroized in memory. Then tap an older link: the live key runs a real open() on it, and the seal refuses.
Real ChaCha20-Poly1305 (RFC 8439), compiled from Rust to WebAssembly, running in your browser. Same primitive family as the QERYX protocol; demo build, not the crypto-core module itself. The chain steps are real SHA3-256 derivations under distinct labels — demo build, real derivation; the messenger’s Q-Ratchet advances its symmetric chain with HKDF-SHA-512.
03Understand — two labels, one chain key
Forward secrecy is a hash you cannot invert.
Compromise of the 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.
mk_n = SHA3-256( "QX-DEMO-RATCHET/MESSAGE-DERIVE" ‖ ck_n ) ck_n+1 = SHA3-256( "QX-DEMO-RATCHET/CHAIN-ADVANCE" ‖ ck_n ) forward: one hash call backward: a preimage search — ≈2²⁵⁶ work
Two distinct labels split one chain key into a message key and the next chain key — a leaked message key never reveals the chain, and the chain never reveals past message keys. The bench prefixes its labels with QX-DEMO-RATCHET/ so demo output can never collide with a protocol derivation.
ChainKey.advance():
new_ck = HKDF(ck, "CHAIN-ADVANCE", info = "CHAIN-KEY", 32)
msg_key = HKDF(ck, "MESSAGE-DERIVE", info = "MESSAGE-KEY", 32)
Same shape, production primitive: the symmetric chain in crypto-core advances with HKDF-SHA-512 (RFC 5869) under the same two-label split, and intermediate key copies are explicitly zeroized before drop.
| Chain | Primitive | Cadence |
|---|---|---|
| 1 — Classical DH | X25519 | every message (revision 2) |
| 2 — Post-quantum KEM | ML-KEM-1024 | every 100 messages (revision 2) |
| 3 — Symmetric chain | HKDF-SHA-512 | every message |
| 4 — Entropy re-injection | OS CSPRNG + conditioned pool | every 5 messages (text) — revision 2 |
What you stepped above is chain 3’s principle in isolation. The other three exist so that compromise of any one chain leaves the others standing — the full construction is in the Q-Ratchet paper.
Why can the chain never rewind?
Each step is a full hash of the previous key. Running SHA3-256 backward is a preimage search — FIPS 202 puts it at 2²⁵⁶ work for a 256-bit digest, beyond any published attack, classical or post-quantum. The bench’s refusal is that mathematics wearing a button.
What does a stolen live key actually read?
Everything after the theft, nothing before it — until the chain heals. The symmetric chain alone reads forward from the stolen state; in the full Q-Ratchet the next ML-KEM-1024 epoch re-keys from material the thief never had, and entropy re-injection adds a second such cut in revision 2. That healing property is post-compromise security, specified in the paper.
Is this bench the messenger's ratchet?
It is one of its four chains, on the demo primitive: real SHA3-256 derivations and real ChaCha20-Poly1305 seals in the browser WASM build, where the messenger’s chain runs HKDF-SHA-512 inside crypto-core. The one-way property you just pressed is the same. The label under the bench states exactly this.
- NIST FIPS 202, "SHA-3 Standard: Permutation-Based Hash and Extendable-Output Functions" (2015)
- RFC 5869 — HMAC-based Extract-and-Expand Key Derivation Function (HKDF) (2010)
- RFC 8439 — ChaCha20 and Poly1305 for IETF Protocols (2018)
- QERYX Protocol Specification §6 — docs/SPEC/04-q-ratchet.md; implementation crypto-core/src/quadruple_ratchet.rs
04In QERYX — four parallel chains
Follow the chain into the protocol.
Every message you send rides a key that existed for that message alone. The Q-Ratchet derives it the way the bench does — forward only, distinct labels, immediate zeroization — from the X25519 + ML-KEM-1024 hybrid agreement, and in revision 2 advances four parallel chains so no single compromise opens the conversation.
A device seized today holds today’s chain head. The messages behind it were sealed under keys that no longer exist anywhere — and that the seized state cannot re-derive, for the reason your thumb just verified.
05The record — the questions we get asked hardest
The honest questions.
Is the derivation real?
Yes. The chain steps are SHA3-256 and the seals are ChaCha20-Poly1305 (RFC 8439), compiled from Rust to WebAssembly and self-tested against the RFC’s vector before first use — on any mismatch the bench refuses to run. The keys are demo keys, born in your browser from crypto.getRandomValues; the derivations on them are real.
Does anything leave this page?
No. Chain keys, message keys, nonces and ciphertext live and die in this tab. The page fetches one same-origin file — the WebAssembly module — and nothing else; no key material is ever sent anywhere.
If the math is one-way, why does the app need more than this chain?
Because a one-way chain protects the past, and only the past. An attacker holding the live state reads forward until the chain re-keys from fresh material — so in revision 2 the Q-Ratchet folds in a new ML-KEM-1024 encapsulation every epoch and fresh entropy every few messages. And no ratchet defends an unlocked device in an attacker’s hands; the limitations page names that plainly.