# Post-Quantum Readiness

quicproquo includes a fully implemented and tested hybrid key encapsulation
mechanism (KEM) combining X25519 (classical) with ML-KEM-768 (post-quantum).
This page describes the current implementation, the integration plan, the
security rationale, and the known gaps.

**Source:** `crates/quicproquo-core/src/hybrid_kem.rs`

## Current State

The hybrid KEM is **fully implemented and tested** in `quicproquo-core`. The
implementation provides:

- `HybridKeypair::generate()` -- generate a combined X25519 + ML-KEM-768 keypair
- `hybrid_encrypt(recipient_pk, plaintext)` -- encrypt to a hybrid public key
- `hybrid_decrypt(keypair, envelope)` -- decrypt with the hybrid private key
- Serialization/deserialization for both keypairs and public keys
- Comprehensive test coverage: round-trip, wrong-key rejection, tampering
  detection, version validation, large payload handling

The test suite in `hybrid_kem.rs` includes 10 tests covering:

- Basic encrypt/decrypt round-trip
- Wrong key decryption failure
- Tampered AEAD ciphertext detection
- Tampered ML-KEM ciphertext detection
- Tampered X25519 ephemeral public key detection
- Unsupported version rejection
- Envelope-too-short rejection
- Keypair serialization round-trip
- Public key serialization round-trip
- Large payload (50 KB) round-trip

## ML-KEM-768 (FIPS 203)

ML-KEM (Module-Lattice-Based Key Encapsulation Mechanism) is the NIST-standardized
post-quantum KEM, published as FIPS 203. quicproquo uses ML-KEM-768, the
middle parameter set:

| Parameter Set | NIST Level | Security (PQ) | EK Size | CT Size | SS Size |
|---------------|-----------|---------------|---------|---------|---------|
| ML-KEM-512 | 1 | ~128-bit | 800 B | 768 B | 32 B |
| **ML-KEM-768** | **3** | **~192-bit** | **1184 B** | **1088 B** | **32 B** |
| ML-KEM-1024 | 5 | ~256-bit | 1568 B | 1568 B | 32 B |

### Why ML-KEM-768 (not 512 or 1024)?

- **Level 3 provides a strong security margin.** The ~192-bit post-quantum
  security level exceeds the 128-bit classical security of the rest of the
  system, providing headroom against future cryptanalytic advances.
- **Moderate key and ciphertext sizes.** The encapsulation key (1184 bytes) and
  ciphertext (1088 bytes) are large compared to X25519 (32 bytes each) but
  manageable for a messaging protocol. ML-KEM-1024 would add ~400 bytes to each
  with diminishing returns.
- **Consistent with industry practice.** Signal, Google Chrome, and Cloudflare
  have all deployed ML-KEM-768 (or its predecessor Kyber-768) in production.

The `ml-kem 0.2` crate provides a pure-Rust implementation of FIPS 203 with all
three parameter sets compiled in by default.

## Hybrid Construction

The hybrid KEM follows the combiner approach described in
`draft-ietf-tls-hybrid-design`. Both a classical and a post-quantum KEM are
executed, and their shared secrets are combined through a KDF:

### Key Derivation

```text
ikm  = X25519_shared_secret(32 bytes) || ML-KEM_shared_secret(32 bytes)
key  = HKDF-SHA256(salt=[], ikm, info="quicproquo-hybrid-v1", L=32)
nonce = HKDF-SHA256(salt=[], ikm, info="quicproquo-hybrid-nonce-v1", L=12)
```

The combined IKM (input key material) is wrapped in `Zeroizing<Vec<u8>>` and
cleared after HKDF expansion.

### Why a Hybrid?

A pure ML-KEM deployment would be vulnerable if lattice-based cryptography is
broken (which, while considered unlikely, cannot be ruled out for a newly
standardized algorithm). A pure X25519 deployment provides no post-quantum
protection. The hybrid approach provides a "belt and suspenders" guarantee:

- If ML-KEM-768 is broken, X25519 still provides 128-bit classical security.
- If X25519 is broken (by a quantum computer), ML-KEM-768 still provides
  ~192-bit post-quantum security.
- Both must be broken simultaneously to compromise the shared secret.

## Wire Format

The hybrid envelope uses a versioned binary format:

```text
Offset  Length  Field
------  ------  -----
0       1       Version byte (0x01)
1       32      X25519 ephemeral public key
33      1088    ML-KEM-768 ciphertext
1121    12      ChaCha20-Poly1305 nonce
1133    var     ChaCha20-Poly1305 AEAD ciphertext (plaintext + 16-byte tag)
```

Total overhead: **1133 bytes** of header + 16 bytes of AEAD tag = **1149 bytes**
per message (in addition to the plaintext length).

The version byte enables future algorithm agility. Version `0x01` denotes
X25519 + ML-KEM-768 + HKDF-SHA256 + ChaCha20-Poly1305.

## Encryption Flow

```text
Sender                                              Recipient
------                                              ---------
                                                    HybridKeypair::generate()
                                                    ├── x25519_sk, x25519_pk
                                                    └── mlkem_dk, mlkem_ek
                                                         │
                                              public_key()│
                                                         │
    ┌─── HybridPublicKey ◄──────────────────────────────┘
    │    (x25519_pk: 32B, mlkem_ek: 1184B)
    │
    │  hybrid_encrypt(pk, plaintext):
    │  1. eph_sk = EphemeralSecret::random()
    │  2. eph_pk = PublicKey::from(&eph_sk)
    │  3. x25519_ss = eph_sk.diffie_hellman(pk.x25519_pk)
    │  4. (mlkem_ct, mlkem_ss) = pk.mlkem_ek.encapsulate()
    │  5. ikm = x25519_ss || mlkem_ss
    │  6. (key, nonce) = HKDF(ikm, info)
    │  7. ct = ChaCha20Poly1305::encrypt(key, nonce, plaintext)
    │  8. envelope = ver || eph_pk || mlkem_ct || nonce || ct
    │
    └──────────── envelope ──────────────────────────────►
                                                         │
                                      hybrid_decrypt(kp, envelope):
                                      1. Parse ver, eph_pk, mlkem_ct, nonce, ct
                                      2. x25519_ss = kp.x25519_sk.dh(eph_pk)
                                      3. mlkem_ss = kp.mlkem_dk.decapsulate(mlkem_ct)
                                      4. ikm = x25519_ss || mlkem_ss
                                      5. (key, _) = HKDF(ikm, info)
                                      6. plaintext = ChaCha20Poly1305::decrypt(key, nonce, ct)
```

## Integration Plan (M5)

The hybrid KEM is currently a standalone module. Milestone M5 will integrate it
into the MLS pipeline by creating a custom `OpenMlsCryptoProvider` that uses the
hybrid KEM for HPKE init key exchange:

1. **Custom crypto provider:** Wrap the existing `StoreCrypto` with a hybrid KEM
   layer that intercepts HPKE operations and replaces the classical X25519 DHKEM
   with the hybrid X25519 + ML-KEM-768 KEM.

2. **KeyPackage extension:** Store the hybrid public key (1216 bytes:
   32B X25519 + 1184B ML-KEM) in a custom MLS extension within the KeyPackage.

3. **Welcome encryption:** When creating a Welcome message, the hybrid KEM
   encrypts the group secrets instead of (or in addition to) the standard HPKE.

4. **Backward compatibility:** Groups can negotiate whether to use hybrid KEM
   via the MLS group context extensions. Classical-only clients can still
   participate in groups that do not require PQ protection.

## The PQ Gap

There is an important asymmetry in quicproquo's post-quantum protection:

```text
Layer                  Classical Protection    Post-Quantum Protection
---------------------------------------------------------------------
QUIC/TLS 1.3           Yes (ECDHE)             No
MLS content (M5+)      Yes (X25519 DHKEM)      Yes (hybrid KEM)
```

**What this means:**

- **Message content** (the MLS application data) is protected against quantum
  adversaries from M5 onward. An attacker with a quantum computer cannot decrypt
  the message payload.

- **Transport metadata** (who connects to the server, when, message sizes) is
  protected only by classical ECDHE. A quantum attacker who recorded the
  TLS handshake transcripts could, in theory, recover the transport session
  keys and observe the metadata.

This is the **PQ gap**: content is safe, but metadata is not.

### Why not PQ transport?

Post-quantum TLS (via ML-KEM in the TLS 1.3 handshake) is being standardized by
the IETF and is supported by some TLS libraries, but `rustls` does not yet
support it in a stable release. When `rustls` adds ML-KEM support, quicproquo
will adopt it to close the PQ gap at the transport layer.

## Harvest-Now, Decrypt-Later Risk

The "harvest-now, decrypt-later" (HNDL) threat model assumes an adversary who:

1. Records all encrypted traffic today (inexpensive storage).
2. Waits for a sufficiently powerful quantum computer (years or decades).
3. Decrypts the recorded traffic retroactively.

In quicproquo's case:

- **Content is safe from M5 onward.** The hybrid KEM wrapping MLS content uses
  ML-KEM-768, which resists quantum attacks. Even if the recorded traffic is
  decrypted at the transport layer, the MLS ciphertext inside is still protected.

- **Transport metadata is at risk.** An HNDL attacker who records TLS
  handshakes today could, with a future quantum computer, recover the transport
  session keys and observe:
  - Which clients connected to the server and when.
  - Message sizes and timing patterns.
  - The encrypted MLS blobs (which they still cannot decrypt if hybrid KEM is
    active).

- **Content before M5 is at risk.** Messages sent before the hybrid KEM
  integration (M5) use classical-only MLS encryption. If the HPKE init key
  exchange used X25519-only DHKEM, a quantum attacker could recover the HPKE
  shared secret and decrypt the Welcome message, gaining access to the group
  state.

This risk is the primary motivation for deploying the hybrid KEM as early as
possible.

## Key Sizes and Performance

| Component | Key/Ciphertext | Size |
|-----------|---------------|------|
| X25519 public key | `x25519_pk` | 32 bytes |
| X25519 shared secret | DH result | 32 bytes |
| ML-KEM-768 encapsulation key | `mlkem_ek` | 1,184 bytes |
| ML-KEM-768 decapsulation key | `mlkem_dk` | 2,400 bytes |
| ML-KEM-768 ciphertext | `mlkem_ct` | 1,088 bytes |
| ML-KEM-768 shared secret | KEM result | 32 bytes |
| Hybrid public key | `x25519_pk + mlkem_ek` | 1,216 bytes |
| Hybrid envelope overhead | Header + AEAD tag | 1,149 bytes |

The ML-KEM-768 operations (keygen, encapsulate, decapsulate) are fast in
software -- typically sub-millisecond on modern hardware. The primary cost is
bandwidth, not computation.

## Related Pages

- [Cryptography Overview](overview.md) -- algorithm inventory including ML-KEM-768
- [Key Lifecycle and Zeroization](key-lifecycle.md) -- hybrid KEM key lifecycle
- [Forward Secrecy](forward-secrecy.md) -- how FS interacts with PQ protection
- [Threat Model](threat-model.md) -- harvest-now-decrypt-later in context
- [Hybrid KEM: X25519 + ML-KEM-768](../protocol-layers/hybrid-kem.md) -- protocol layer details
