Understanding Post-Quantum Lattice-Based Cryptography
This comprehensive guide explores the mathematical foundations and implementation details of post-quantum cryptography, specifically ML-KEM (Module-Lattice Key Encapsulation Mechanism) and lattice-based cryptography. Whether you're a student, developer, or cryptography enthusiast, you'll find detailed explanations of how these quantum-resistant algorithms work.
Topics covered: Learning With Errors (LWE) • Lattice cryptography • ML-KEM internals • Quantum resistance • Implementation comparisons
ML-KEM (Module-Lattice Key Encapsulation Mechanism) is based on the Learning With Errors (LWE) problem, a mathematical problem that is believed to be hard for both classical and quantum computers to solve.
The security of ML-KEM is based on this mathematical challenge:
The small error e "hides" the secret key s. Without e, this would be easy to solve (simple linear algebra). With e, it becomes extremely difficult—even for quantum computers!
To share a secret with someone, we use their public key (a, b):
The recipient uses their secret key s to recover the shared secret:
| Lattice Dimension (k × n) | 4 × 256 = 1024 coefficients |
| Modulus (q) | 3329 (prime) |
| Error Distribution (σ) | Centered binomial distribution, η = 2 |
| Public Key Size | 1568 bytes |
| Ciphertext Size | 1568 bytes |
| Security Level | 256-bit quantum security (NIST Level 5) |
📚 Further Reading: The actual ML-KEM uses polynomial rings and module lattices for efficiency, but the core security principle remains the same: adding small errors to linear equations makes them exponentially hard to solve!
The PQXDH simulation uses JavaScript to demonstrate ML-KEM (Kyber), while the Slow Lynx Cryptography Discovery project includes a Rust implementation of educational LWE encryption. Both are based on the same lattice cryptography principles!
| Aspect | JavaScript (PQXDH Simulator) | Rust (Slow Lynx) |
|---|---|---|
| Implementation | EducationalMLKEM class | LatticeKey struct |
| Purpose | ML-KEM-1024 demonstration in PQXDH protocol | Educational LWE encryption with multiple security levels |
| Security Levels | NIST Level 5 (256-bit quantum) | Toy (80-bit), Standard (128-bit), High (192-bit), Military (256-bit) |
| Dimension | 4 × 256 = 1024 | 128, 256, 512, or 1024 (configurable) |
| Modulus | q = 3329 (ML-KEM standard) | q = 7681, 12289, 16381, or 32749 |
| Error Sampling | Centered binomial (η=2) | Discrete Gaussian approximation (σ=3.2) |
| Environment | Browser (HTML/JavaScript) | Command-line (Rust CLI) |
| Encryption Capability | KEM (Key Encapsulation Mechanism) | Bit-by-bit, 4-bit (nibble), and 8-bit (byte) encryption |
Both implementations follow the same core algorithm: b = a·s + e (mod q)
// Key Generation: b = a·s + e (mod q)
generateKeyPair() {
// Generate public lattice vector 'a' (uniformly random)
const a = new Uint8Array(this.publicKeySize);
crypto.getRandomValues(a);
// Generate secret key 's' (small binary values)
const s = new Uint8Array(this.publicKeySize);
crypto.getRandomValues(s);
for (let i = 0; i < s.length; i++) {
s[i] = s[i] & 1; // Extract lowest bit for binary secret
}
// Sample small error 'e' (centered binomial)
const e = this.sampleSmallError(this.publicKeySize);
// Compute b = a·s + e (mod q)
const b = new Uint8Array(this.publicKeySize);
for (let i = 0; i < this.publicKeySize; i++) {
let sum = 0;
for (let j = 0; j < Math.min(8, this.publicKeySize - i); j++) {
sum += a[i + j] * s[j];
}
b[i] = (sum + e[i]) % 256;
}
return { publicKey: a, secretKey: s };
}
// Key Generation: b = a·s + e (mod q)
pub fn generate(rng: &CspRng, params: &LatticeParams)
-> Result<LatticeKeyPair, &'static str> {
let n = params.dimension;
let q = params.modulus;
// Generate secret key s (binary values {0, 1})
let mut s = vec![0u32; n];
for i in 0..n {
s[i] = rng.next_u32()? % 2;
}
// Generate random lattice vector a
let mut a = vec![0u32; n];
for i in 0..n {
a[i] = rng.next_u32()? % q;
}
// Sample small error e (Gaussian approximation)
let mut e = vec![0u32; n];
for i in 0..n {
e[i] = sample_discrete_gaussian(rng, params.error_stddev, q)?;
}
// Compute b = a*s + e (mod q) - element-wise
let mut b = vec![0u32; n];
for i in 0..n {
let mut sum = 0u64;
sum = (a[i] as u64 * s[i] as u64) % q as u64;
b[i] = ((sum + e[i] as u64) % q as u64) as u32;
}
Ok(LatticeKeyPair { public_key: (a, b), secret_key: s })
}
The Rust implementation offers configurable security levels through parameter presets:
| Security Level | Dimension (n) | Modulus (q) | Error (σ) | Security |
|---|---|---|---|---|
| Toy | 128 | 7681 | 3.2 | ~80-bit |
| Standard | 256 | 12289 | 3.2 | ~128-bit |
| High | 512 | 16381 | 3.2 | ~192-bit |
| Military | 1024 | 32749 | 3.2 | ~256-bit |
| Slow Butterfly Simulation | Browser-based cryptography simulations - DH, ECDH, PQXDH, ML-KEM |
| Slow Lynx Cryptography Discovery | Rust CLI: CSPRNG analysis, LWE implementation, crypto library scanner |
| Slow Owl Packet Observer | Real-time network analyzer: observe hybrid PQ (X25519Kyber768) in live traffic |
💡 Design Philosophy: The JavaScript version prioritizes browser accessibility and protocol demonstration (ML-KEM in PQXDH). The Rust version emphasizes configurable parameters, comprehensive testing, and integration with a CSPRNG for cryptographic research. Both teach the same core LWE concepts from different angles!