Lattices · ML-KEM · ML-DSA · SLH-DSA · NIST PQC Standards · Hardware
Navigate with arrow keys · Press Esc for overview · Press F for fullscreen
Every public-key cryptosystem in wide deployment today — RSA, Diffie-Hellman, ECDSA, ECDH, EdDSA — derives security from one of two problems: integer factorisation or the discrete logarithm problem.
Shor's algorithm (1994) solves both in polynomial time on a quantum computer, turning attacks that are sub-exponential classically into polynomial ones (roughly O(n³) gates for an n-bit modulus).
Grover's algorithm gives only a quadratic speedup against symmetric ciphers — doubling key length neutralises the threat.
Adversaries record encrypted traffic today, planning to decrypt it once a Cryptographically Relevant Quantum Computer (CRQC) is available. Long-lived secrets are already at risk.
Gidney–Ekerå (2019): ~20 million noisy physical qubits for 8 hours. Gidney (2025): under 1 million noisy qubits, under a week (~1,400 logical qubits). Estimates for CRQC arrival: 2030s–2040s.
Grover reduces AES-128 → ~64-bit security. Mitigation: use AES-256 (→128-bit post-quantum).
Post-quantum schemes derive hardness from problems believed resistant to both classical and quantum algorithms.
Hard problem: Learning With Errors (LWE), Module-LWE, Ring-LWE
Standards: ML-KEM (FIPS 203), ML-DSA (FIPS 204), FN-DSA (FIPS 206, pending)
Pros: Small keys, fast operations, versatile
Cons: Relatively newer hardness assumptions
Hard problem: Preimage/collision resistance of hash functions
Standards: SLH-DSA (FIPS 205), LMS/XMSS (SP 800-208)
Pros: Very conservative, minimal assumptions, tiny keys
Cons: Large signatures, slower signing
Hard problem: Decoding random linear codes
Standards: HQC (selected March 2025; draft standard expected ~2026)
Pros: Decades of study (McEliece, 1978), diverse math basis
Cons: Very large public keys (Classic McEliece)
Hard problems: MQ problem, supersingular isogeny
Status: SIKE broken (2022); Rainbow broken (2022); research continues
Notes: Multivariate (MAYO, UOV, SNOVA, QR-UOV) and isogeny (SQIsign) signatures are in NIST's additional signature round
A lattice ℒ(B) is the set of all integer linear combinations of basis vectors B = {b₁, b₂, …, bₙ} ⊂ ℝⁿ:
Interactive 2D lattice — click to see closest vector problem (CVP)
Exact or small-factor SVP: best known algorithms (sieving, enumeration, BKZ) take 2Θ(n) time; LLL is polynomial-time but only finds vectors within an exponential factor. Quantum algorithms only shave the exponent (e.g. sieving 20.292n → ~20.257n).
Introduced by Oded Regev (2005, Gödel Prize 2018). LWE is the computational foundation for ML-KEM and ML-DSA.
Given a random matrix A ∈ ℤqm×n and a secret vector s ∈ ℤqn, compute:
where e is a "small" error vector sampled from a discrete Gaussian χ.
The search problem: given (A, b), recover s.
The decision problem: distinguish (A, b) from uniform random.
Without noise: trivial Gaussian elimination — O(n³). With noise: as hard as worst-case lattice problems (GapSVP, SIVP) — via a quantum reduction.
Work in Rq = ℤq[x]/(xn+1). Structured — O(n log n) operations via NTT. Basis of the FHE schemes BGV, BFV and CKKS (deck 08).
Matrices of ring elements: A ∈ Rqk×k. Bridges Ring-LWE efficiency with LWE flexibility. Parameter k controls security/performance trade-off.
Regev's (quantum) reduction: solving random LWE instances is at least as hard as solving worst-case GapSVP/SIVP; Peikert (2009) gave a classical reduction for GapSVP. Few assumptions have a worst-case guarantee like this.
This demonstrates why noise makes LWE hard. Without error, the system is trivially solvable.
Parameters: n=4, q=97
Error distribution: discrete samples from e mod q
| FIPS | Algorithm | Original Name | Type | Hard Problem | Purpose |
|---|---|---|---|---|---|
| 203 | ML-KEM | CRYSTALS-Kyber | Lattice | Module-LWE | Key Encapsulation |
| 204 | ML-DSA | CRYSTALS-Dilithium | Lattice | Module-LWE + SIS | Digital Signatures |
| 205 | SLH-DSA | SPHINCS+ | Hash | Hash preimage | Digital Signatures |
Based on FALCON. FFT over NTRU lattices. Compact signatures (666 B for FN-DSA-512, NIST Category 1). Draft FIPS 206 pending.
Code-based KEM selected March 2025 as backup to ML-KEM. Different mathematical basis (Hamming Quasi-Cyclic codes). Draft standard ~2026, final ~2027.
Stateful hash-based signature schemes. Already approved. Require strict state management — suitable for firmware signing, not TLS.
NIST IR 8547 (initial public draft, Nov 2024): quantum-vulnerable algorithms deprecated after 2030 and disallowed after 2035, with high-risk systems transitioning much earlier.
Module-Lattice-Based Key-Encapsulation Mechanism, derived from CRYSTALS-Kyber.
ML-KEM is built in two layers:
1. K-PKE — an IND-CPA public-key encryption scheme based on Module-LWE. Encrypts a fixed 32-byte message.
2. KEM transform — a modified Fujisaki-Okamoto (FO) transform applied to K-PKE, yielding IND-CCA2 security.
The ring Rq = ℤq[x]/(x256+1) with q = 3329. Polynomial multiplication via Number Theoretic Transform (NTT).
| Set | k | η₁,η₂ | PK (B) | SK (B) | CT (B) | NIST Cat |
|---|---|---|---|---|---|---|
| ML-KEM-512 | 2 | 3, 2 | 800 | 1632 | 768 | 1 |
| ML-KEM-768 | 3 | 2, 2 | 1184 | 2400 | 1088 | 3 |
| ML-KEM-1024 | 4 | 2, 2 | 1568 | 3168 | 1568 | 5 |
Shared secret: always 32 bytes. NIST Category 1 ≈ AES-128 security; Cat 3 ≈ AES-192; Cat 5 ≈ AES-256.
Module-LWE generalises Ring-LWE (one polynomial → k×k matrix of polynomials). Tuning k adjusts security without changing ring dimension. ML-KEM-768 (k=3) is the recommended general-purpose parameter set.
Both parties derive the same 32-byte shared secret K. The FO transform ensures IND-CCA2 security via implicit rejection.
The NTT is the workhorse of lattice cryptography — it converts polynomial multiplication from O(n²) to O(n log n).
The NTT is the finite-field analogue of the FFT, operating over ℤq instead of ℂ.
ML-KEM uses 7 layers of 128 butterflies. Each butterfly: 1 multiply + 1 add + 1 subtract in ℤ3329. Total: 128 × 7 = 896 butterflies per NTT.
Hardware: one butterfly unit at 200 MHz does an NTT in ~4.5 μs (896 cycles); 4–8 parallel butterflies bring it to ~1 μs or below.
Module-Lattice-Based Digital Signature Algorithm, derived from CRYSTALS-Dilithium.
ML-DSA uses the "Fiat-Shamir with Aborts" paradigm (Lyubashevsky, 2009):
| Set | (k,ℓ) | PK (B) | SK (B) | Sig (B) | Cat |
|---|---|---|---|---|---|
| ML-DSA-44 | (4,4) | 1,312 | 2,560 | 2,420 | 2 |
| ML-DSA-65 | (6,5) | 1,952 | 4,032 | 3,309 | 3 |
| ML-DSA-87 | (8,7) | 2,592 | 4,896 | 4,627 | 5 |
The masking vector y hides s₁ in z = y + c·s₁. If z leaks information about s₁ (by being too large or having suspicious low bits), the signature is rejected and the signer tries again. Expected iterations: 4.25 / 5.1 / 3.85 for ML-DSA-44/65/87 (FIPS 204 Table 1).
Relies on both Module-LWE (key generation) and Module-SIS (Short Integer Solution — signature forgery). Both reduce to worst-case lattice hardness.
Stateless Hash-Based Digital Signature Algorithm, derived from SPHINCS+. The conservative choice — security rests solely on hash function properties.
↑ XMSS tree layer d−1
↑ ... (d layers of XMSS Merkle trees) ...
↑ XMSS tree layer 0
↑
Each layer: WOTS+ one-time signatures authenticate tree roots. FORS (Forest of Random Subsets) signs the message. The hypertree root is the public key.
| Parameter Set | PK (B) | SK (B) | Sig (B) | Cat |
|---|---|---|---|---|
| SLH-DSA-*-128s | 32 | 64 | 7,856 | 1 |
| SLH-DSA-*-128f | 32 | 64 | 17,088 | 1 |
| SLH-DSA-*-192s | 48 | 96 | 16,224 | 3 |
| SLH-DSA-*-192f | 48 | 96 | 35,664 | 3 |
| SLH-DSA-*-256s | 64 | 128 | 29,792 | 5 |
| SLH-DSA-*-256f | 64 | 128 | 49,856 | 5 |
Hash choice: SHA-2 or SHAKE256 family. Suffix s = smaller sig / slower; f = fast sign / larger sig.
SLH-DSA has the smallest keys of any PQC signature (32–64 B public key!) but the largest signatures (7–50 KB). Ideal for code signing, firmware verification — not TLS handshakes.
Compare PQC algorithms against classical schemes. Select a metric to visualise:
All sizes in bytes. Classical schemes shown for reference. ML-KEM-768 is the recommended general-purpose KEM.
PQC algorithms are competitive with — and often faster than — classical schemes in software.
| Operation | ML-KEM-768 | ECDH (X25519) | RSA-2048 |
|---|---|---|---|
| KeyGen | ~30 μs | ~50 μs | ~200 ms |
| Encaps / Encrypt | ~40 μs | ~50 μs | ~30 μs |
| Decaps / Decrypt | ~35 μs | ~50 μs | ~1 ms |
| Operation | ML-DSA-65 | Ed25519 | RSA-2048 |
|---|---|---|---|
| KeyGen | ~80 μs | ~30 μs | ~200 ms |
| Sign | ~250 μs (avg) | ~50 μs | ~1 ms |
| Verify | ~90 μs | ~130 μs | ~30 μs |
One x86-64 core. Classical columns: openssl speed (OpenSSL 3.0); PQC columns: indicative portable-C figures (AVX2 code is 2–4× faster). Treat all as orders of magnitude.
ML-KEM key generation is thousands of times faster than RSA-2048. Total KEM round-trip is comparable to ECDH. NTT-based arithmetic is highly parallelisable.
Larger key/signature sizes increase bandwidth. ML-DSA signing requires retry loops (~4–5 iterations on average). SLH-DSA signing is 100–1000× slower than ML-DSA.
PQC accelerators for SoCs, FPGAs, and ASICs.
| Platform | Algorithm | KeyGen | Encaps / Sign | Area |
|---|---|---|---|---|
| Cortex-M4 | ML-KEM-768 | ~500K cyc | ~600K cyc | — |
| Artix-7 FPGA | ML-KEM-768 | ~3 μs | ~4 μs | ~5K LUTs |
| 28nm ASIC | ML-KEM-768 | <1 μs | <1 μs | ~25K GE |
| Cortex-M4 | ML-DSA-65 | ~1.5M cyc | ~5M cyc | — |
Indicative figures from published designs; they vary several-fold with architecture and clock.
A pedagogical implementation of the core NTT and polynomial arithmetic in ℤ3329.
Q = 3329 # ML-KEM modulus
N = 256 # polynomial degree
ZETA = 17 # primitive 256th root of unity mod Q (17^128 = -1)
def bitrev7(i):
return int(f'{i:07b}'[::-1], 2)
# Twiddle factors ζ^BitRev7(i), as in FIPS 203
ZETAS = [pow(ZETA, bitrev7(i), Q) for i in range(128)]
def ntt(f):
"""FIPS 203 Alg. 9: 7 layers, 128 -> 2; output = 128 degree-1 residues."""
a = list(f)
k, length = 1, 128
while length >= 2:
for start in range(0, N, 2 * length):
zeta = ZETAS[k]; k += 1
for j in range(start, start + length):
t = (zeta * a[j + length]) % Q
a[j + length] = (a[j] - t) % Q
a[j] = (a[j] + t) % Q
length //= 2
return a
def intt(a):
"""FIPS 203 Alg. 10: Gentleman-Sande butterflies, then multiply by 128^-1."""
a = list(a)
k, length = 127, 2
while length <= 128:
for start in range(0, N, 2 * length):
zeta = ZETAS[k]; k -= 1
for j in range(start, start + length):
t = a[j]
a[j] = (t + a[j + length]) % Q
a[j + length] = (zeta * (a[j + length] - t)) % Q
length *= 2
return [(x * 3303) % Q for x in a] # 3303 = 128^-1 mod 3329
def multiply_ntts(f_hat, g_hat):
"""FIPS 203 Alg. 11: 128 products of degree-1 polys mod (X^2 - γ_i)."""
h = [0] * N
for i in range(128):
g = pow(ZETA, 2 * bitrev7(i) + 1, Q) # γ_i = ζ^(2·BitRev7(i)+1)
a0, a1, b0, b1 = f_hat[2*i], f_hat[2*i+1], g_hat[2*i], g_hat[2*i+1]
h[2*i] = (a0 * b0 + a1 * b1 * g) % Q
h[2*i+1] = (a0 * b1 + a1 * b0) % Q
return h
import os, hashlib
def cbd(eta, seed, nonce):
"""Centred binomial sample (FIPS 203 Alg. 8): coefficient = x - y."""
data = hashlib.shake_256(seed + bytes([nonce])).digest(64 * eta) # PRF
bits = [(byte >> b) & 1 for byte in data for b in range(8)]
return [(sum(bits[2*eta*i : 2*eta*i + eta])
- sum(bits[2*eta*i + eta : 2*eta*(i + 1)])) % Q for i in range(N)]
def sample_ntt(rho, j, i):
"""FIPS 203 Alg. 7: uniform NTT-domain poly by rejection from SHAKE-128."""
xof = hashlib.shake_128(rho + bytes([j, i])).digest(840) # ample bytes
a, p = [], 0
while len(a) < N:
d1 = xof[p] | (xof[p+1] & 15) << 8
d2 = xof[p+1] >> 4 | xof[p+2] << 4
a += [d for d in (d1, d2) if d < Q]; p += 3
return a[:N]
def encode12(f): # ByteEncode_12: 256 x 12 bits = 384 B
return b''.join((f[i] | f[i+1] << 12).to_bytes(3, 'little')
for i in range(0, N, 2))
def k_pke_keygen(d, k=3, eta1=2):
"""FIPS 203 Alg. 13 (ML-KEM-768 when k=3)."""
g = hashlib.sha3_512(d + bytes([k])).digest()
rho, sigma = g[:32], g[32:] # public seed for A, private seed for s, e
A_hat = [[sample_ntt(rho, j, i) for j in range(k)] for i in range(k)]
s_hat = [ntt(cbd(eta1, sigma, n)) for n in range(k)]
e_hat = [ntt(cbd(eta1, sigma, n + k)) for n in range(k)]
t_hat = [] # t = A·s + e, all in the NTT domain
for i in range(k):
acc = e_hat[i]
for j in range(k):
prod = multiply_ntts(A_hat[i][j], s_hat[j])
acc = [(x + y) % Q for x, y in zip(acc, prod)]
t_hat.append(acc)
ek = b''.join(map(encode12, t_hat)) + rho # 1152 + 32 = 1184 B
dk_pke = b''.join(map(encode12, s_hat)) # 1152 B
return ek, dk_pke # ML-KEM dk = dk_pke ‖ ek ‖ H(ek) ‖ z = 2400 B
Uses ntt and multiply_ntts from the "Simplified ML-KEM Core" slides; for a fixed seed d the output matches the ML-KEM-768 keys of the kyber-py reference byte for byte. Production implementations: use liboqs, pqcrypto (Rust), or OpenSSL 3.5+ with ML-KEM support.
The transition strategy: combine classical + PQC for defence-in-depth.
TLS 1.3 Hybrid Handshake
↓ Combined Key Share in ClientHello
↓ SS₂ ‖ SS₁ → TLS 1.3 key schedule (X25519MLKEM768 puts the ML-KEM secret first)
If either algorithm is broken, the other still protects the session. Already deployed by Cloudflare, Google Chrome, AWS.
Identify all uses of RSA, ECDH, ECDSA in protocols, certificates, and stored data. Prioritise long-lived secrets.
Deploy hybrid TLS with ML-KEM + X25519. Update certificate chains. Test interoperability.
Transition to PQC-only where hybrid overhead is unacceptable. Deprecate classical public-key algorithms per NIST IR 8547.
Design systems to swap algorithms without rewriting protocols. Use negotiation (like TLS cipher suites) and abstraction layers.
| Algorithm | Type | PK | SK | CT/Sig | Speed | Maturity | Best For |
|---|---|---|---|---|---|---|---|
| ML-KEM-768 | KEM | 1,184 B | 2,400 B | 1,088 B | Very fast | FIPS 203 | TLS, VPN, general KE |
| ML-DSA-65 | Sig | 1,952 B | 4,032 B | 3,309 B | Fast | FIPS 204 | Code signing, auth, TLS |
| SLH-DSA-128f | Sig | 32 B | 64 B | 17,088 B | Slow sign | FIPS 205 | Firmware, long-term archive |
| FN-DSA-512 | Sig | 897 B | 1,281 B | 666 B | Fast | FIPS 206 pending | Bandwidth-constrained |
| HQC-128 | KEM | ~2.2 KB | ~2.3 KB | ~4.4 KB | Moderate | Selected 2025 | ML-KEM backup (diversity) |
| RSA-2048 | KE+Sig | 256 B | ~1.2 KB | 256 B | Slow KG | Broken by QC | Legacy (deprecate) |
| ECDSA P-256 | Sig | 64 B | 32 B | 64 B | Fast | Broken by QC | Legacy (deprecate) |
Recommendation: Use ML-KEM-768 for key exchange and ML-DSA-65 for general signing. Add SLH-DSA where conservative hash-only assumptions are required. Deploy hybrid initially.
NIST Standards
FIPS 203 — ML-KEM (Module-Lattice-Based Key-Encapsulation Mechanism)
FIPS 204 — ML-DSA (Module-Lattice-Based Digital Signature Algorithm)
FIPS 205 — SLH-DSA (Stateless Hash-Based Digital Signature Algorithm)
NIST IR 8547 — Transition to Post-Quantum Cryptography Standards
SP 800-208 — Stateful Hash-Based Signature Schemes (LMS, XMSS)
Foundational Papers
Shor, "Polynomial-Time Algorithms for Prime Factorization and Discrete Logarithms on a Quantum Computer" (1994)
Regev, "On Lattices, Learning with Errors, Random Linear Codes, and Cryptography" (2005, Gödel Prize 2018)
Lyubashevsky, Peikert, Regev, "On Ideal Lattices and Learning with Errors Over Rings" (2010)
Bos et al., "CRYSTALS-Kyber: A CCA-Secure Module-Lattice-Based KEM" (2018)
Ducas et al., "CRYSTALS-Dilithium: A Lattice-Based Digital Signature Scheme" (2018)
Bernstein et al., "The SPHINCS+ Signature Framework" (2019)
Implementation Resources
Open Quantum Safe (liboqs) · PQ-Crystals reference code · OpenSSL 3.5+ PQC support · NIST ACVP test vectors