Expand description
§pq-ratchet
Post-quantum hybrid double ratchet. ML-KEM-768 meets X25519.
Signal added post-quantum resistance to their protocol in 2023 with the SPQR deployment. This crate implements the same SCKA epoch model: tie an ML-KEM-768 round-trip to each DH ratchet step so the symmetric chain handles reordering like it always did. An attacker has to break X25519 AND ML-KEM-768 to touch any session key.
§What you get
A key derivation state machine. Not encryption, not transport, not initial key agreement. You bring ChaCha20-Poly1305 or AES-256-GCM for the AEAD layer and whatever wire protocol you use. This crate derives the keys.
§Usage
use pq_ratchet::HybridRatchet;
// Shared secret from PQXDH or X3DH
let shared_secret = [0u8; 32];
let mut rng = rand::thread_rng();
let bob_dh_sk = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
let bob_dh_pk = x25519_dalek::PublicKey::from(&bob_dh_sk);
let mut alice = HybridRatchet::init_sender(&shared_secret, bob_dh_pk.as_bytes(), &mut rng);
let mut bob = HybridRatchet::init_receiver(&shared_secret, bob_dh_sk, &mut rng);
let (header, mk) = alice.ratchet_encrypt(&mut rng).unwrap();
let mk2 = bob.ratchet_decrypt(&header, &mut rng).unwrap();
assert_eq!(mk.as_bytes(), mk2.as_bytes());§Key confirmation
There’s no explicit handshake to verify both sides derived the same keys. This matches Signal’s design. You confirm implicitly: if AEAD decryption succeeds, the keys matched. If it fails, the session is out of sync or someone’s tampering with your traffic. Surface those failures. Don’t silently retry.
§Performance
ML-KEM-768 key generation is the expensive part (triggered at each DH ratchet
step). Run cargo bench to measure on your hardware. Within-epoch encrypt
is just HKDF-SHA256. Cheap.
§Security
Not independently audited. The underlying ml-kem crate isn’t either.
Don’t deploy without a review.
Structs§
- Header
- Per-message header transmitted alongside each ciphertext.
- Hybrid
Ratchet - Hybrid Double Ratchet session state.
- Message
Key - Single-use message key derived from the symmetric ratchet chain.
- PqCt
- Serialised ML-KEM-768 ciphertext (1088 bytes, carried in ratchet headers).
- PqEk
- Serialised ML-KEM-768 encapsulation key (1184 bytes, carried in ratchet headers).
Enums§
- Ratchet
Error - Errors returned by the hybrid Double Ratchet state machine.
Constants§
- MAX_
SKIP - Maximum skipped messages in a single chain step. Signal’s recommended value. Raise it if your transport layer reorders deeper than 1,000 messages.
- MAX_
SKIP_ TOTAL - Hard cap on the total number of entries in the skipped-key cache across all DH epochs. Without this, a malicious peer can force unbounded memory growth by ratcheting through many epochs each with skipped messages.
- PQ_
CT_ LEN - Byte length of an ML-KEM-768 ciphertext.
- PQ_
DK_ LEN - Byte length of an ML-KEM-768 decapsulation key (private key, per FIPS 203 §7.2).
- PQ_
EK_ LEN - Byte length of an ML-KEM-768 encapsulation key (public key).
- PQ_
SS_ LEN - Byte length of the ML-KEM-768 shared secret output.