Chapter 13: Lattice cryptanalysis
Lattice reduction finds short vectors. Concrete LWE security is a statement about that search: the primal attack builds a lattice from the LWE sample in which the secret and the error form one short vector, and the parameters are set so that lattice reduction does not reach it inside the attacker’s time budget. Regev’s reduction is the worst-case foundation the problem family rests on, and it gives no concrete cost (Regev, 2009). The Kyber Round 3 submission’s security table (Table 4) makes that concrete claim precise for Module-LWE at the ML-KEM parameter sets. FIPS 203 standardizes the ML-KEM parameter sets and states the category claims for ML-KEM-512, ML-KEM-768, and ML-KEM-1024 (Avanzi et al., 2021; National Institute of Standards and Technology, 2024). The reimplementation at solutions/ch13-lattice-cryptanalysis/ reproduces the Kyber Round 3 submission’s Table 4 block sizes for all three parameter sets within five block sizes. It uses only equation 9 of the submission, the Chen 2013 root-Hermite factor, and the Becker-Ducas-Gama-Laarhoven 2016 sieving exponents.
A toy primal attack that actually runs
Section titled “A toy primal attack that actually runs”The parameter regime where lattice reduction wins is tiny. A flat-LWE instance with unknowns, , and samples fits in a thirteen-dimensional lattice and LLL recovers the secret in milliseconds. The same construction scaled up to the ML-KEM-768 parameter set gives a lattice of dimension . On that lattice the core-SVP model gives a conservative estimate of roughly classical operations; the Kyber Round 3 Table 4 core-SVP figure is (Avanzi et al., 2021).
Fix a specific instance. Sample the matrix and the secret and the error from a seeded numpy generator, and set . The secret distribution is the simplest possible small-secret analogue of the centered binomial used in ML-KEM (National Institute of Standards and Technology, 2024). The error distribution matches the Chapter 8 toy instance. The concrete secret and error drop out of the numpy seed: the secret is and the error is .
The primal embedding lifts the LWE sample into a lattice that contains as one of its vectors, the unique-SVP instance the Kyber Round 3 submission’s primal attack builds (Avanzi et al., 2021, sec. 5.1.2). The construction: stack three block rows inside a integer matrix. The top rows are times the identity, which forces the first coordinates to live in . The middle rows pair (in the first columns) with (in the next columns), which tells the lattice that any integer combination of secret coordinates contributes the corresponding -column combination to the first block. The bottom row places in the first columns and a single in the last column, which plants the target.
A vector of the form is always in the lattice by construction. Take one copy of the bottom row. Subtract copies of the -th middle row for each . Add the right multiple of each top row. The first block becomes . The middle block becomes . The last coordinate is . The norm is on the seeded instance. A random lattice of the same dimension and determinant has a shortest vector of norm approximately under the Gaussian heuristic (Gama & Nguyen, 2008). So is far shorter than the generic expectation, and LLL finds it directly (Lenstra et al., 1982).
The block below defines a toy LLL (the swap-and-size-reduce loop from the 1982 Lenstra-Lenstra-Lovász paper at the standard parameter ) and runs it on the Kannan embedding.
import numpy as np
def lll(basis, delta=0.75): """Toy educational LLL: the standard size-reduce / swap loop at parameter delta.""" B = basis.astype(float).copy() dim = B.shape[0]
def gso(): Q = np.zeros_like(B) mu = np.zeros((dim, dim)) for i in range(dim): Q[i] = B[i].copy() for j in range(i): mu[i, j] = B[i] @ Q[j] / (Q[j] @ Q[j]) Q[i] = Q[i] - mu[i, j] * Q[j] return Q, mu
k = 1 while k < dim: Q, mu = gso() for j in range(k - 1, -1, -1): if abs(mu[k, j]) > 0.5: B[k] = B[k] - round(mu[k, j]) * B[j] Q, mu = gso() if Q[k] @ Q[k] >= (delta - mu[k, k - 1] ** 2) * (Q[k - 1] @ Q[k - 1]): k += 1 else: B[[k, k - 1]] = B[[k - 1, k]] k = max(k - 1, 1) return B.astype(int)
rng = np.random.default_rng(0)n, m, q = 4, 8, 97A = rng.integers(0, q, size=(m, n))s = rng.integers(-1, 2, size=n)e = rng.integers(-1, 2, size=m)b = (A @ s + e) % q
# Kannan primal embedding of dimension m + n + 1 = 13.d = m + n + 1basis = np.zeros((d, d), dtype=int)basis[:m, :m] = q * np.eye(m, dtype=int)basis[m:m + n, :m] = A.Tbasis[m:m + n, m:m + n] = np.eye(n, dtype=int)basis[m + n, :m] = bbasis[m + n, m + n] = 1
planted = np.concatenate([e, -s, [1]])print(f"planted secret s = {s.tolist()}")# ==> planted secret s = [-1, 1, -1, 0]print(f"planted error e = {e.tolist()}")# ==> planted error e = [-1, -1, 0, 0, 0, -1, -1, -1]print(f"planted (e || -s || 1) norm = {float(np.linalg.norm(planted)):.4f}")# ==> planted (e || -s || 1) norm = 3.0000
reduced = lll(basis)shortest = reduced[0]print(f"LLL shortest row norm = {float(np.linalg.norm(shortest)):.4f}")# ==> LLL shortest row norm = 3.0000print(f"equals planted vector ? {np.array_equal(shortest, planted) or np.array_equal(shortest, -planted)}")# ==> equals planted vector ? TrueEvery Python block this chapter prints is also a standalone file in the companion repository, under chapter-code/ch13/, one file per block. Appendix C covers the clone and the environment they run on.
LLL recovers the planted vector on the first reduced row. The recovered middle block is and the recovered first block is . Normalizing by the last coordinate recovers the sign, since LLL may return the negated planted vector. The attack terminates at dimension thirteen.
The instance scales unpleasantly. ML-KEM-768 has module rows over a polynomial ring of degree , which gives an effective LWE dimension of (National Institute of Standards and Technology, 2024). The Kannan embedding at the published optimal number of samples has dimension (Avanzi et al., 2021). LLL alone cannot handle dimension 1419. The rest of the chapter quantifies the gap between “LLL works at ” and “the core-SVP model gives a conservative estimate of roughly classical operations at ”.
The four problems and the two tools
Section titled “The four problems and the two tools”Chapter 7 stated SVP, CVP, and precisely (Micciancio & Goldwasser, 2002). Module-LWE’s attack analysis also depends on two relaxations of those problems. Bounded Distance Decoding (BDD) is the promise-version of CVP where the target is guaranteed close to the lattice. Unique-SVP is the promise-version of SVP where the shortest vector is much shorter than every lattice vector linearly independent of it. Both relaxations are easier than the full problem in principle. Both are what LWE reduces to in practice.
Bounded distance decoding (BDD). The BDD input is a basis of a lattice , a target , and a promise that for some slack factor . The output is the lattice point closest to , which is in the Lyubashevsky-Micciancio notation (Definition 2 in Lyubashevsky & Micciancio, 2009). The promise that the target is strictly within half the minimum distance guarantees uniqueness. Babai’s nearest-plane algorithm solves BDD in polynomial time whenever the reduced basis is good enough (Babai, 1986), and the Lindner-Peikert decoding attack on LWE is a widened nearest-planes search on a BKZ-reduced basis of the LWE lattice (Lindner & Peikert, 2011, sec. 4). LWE is a BDD instance. The public LWE sample corresponds to a target near the lattice spanned by the columns of modulo . The error is the promise that the target is close.
Unique-SVP. The uSVP input is a basis of a lattice and a promise that . The output is a shortest nonzero vector (Definition 1 in Lyubashevsky & Micciancio, 2009). The promise is a gap. Here is the second successive minimum from Chapter 7, so the promise says that every lattice vector not a multiple of the shortest one is more than times longer than it. The shortest vector’s own multiples do not count. The larger the gap, the easier the instance. The Kannan embedding from the previous section turns an LWE sample into a unique-SVP instance. The primal attack solves that instance by running BKZ at a block size large enough to bring the GSO of the shortest vector below the gap.
The primal attack’s success condition rests on two pieces: a function that says how short BKZ output vectors are at a given block size, and a cost model that prices one SVP oracle call at block size .
LLL. The Lenstra-Lenstra-Lovász algorithm from 1982 is the polynomial-time baseline (Lenstra et al., 1982). Given a basis of a lattice of rank , LLL at the standard parameter outputs a reduced basis whose first vector satisfies (Lenstra et al., 1982). More generally the approximation factor is with , exponential in , which is the only point needed here. The running time is polynomial in and in the bit length of the input. LLL is strong enough to solve the toy problem from the previous section. It is weak enough to be useless against ML-KEM directly. The approximation factor grows exponentially in . At the factor is roughly . That is many orders of magnitude larger than the ML-KEM-768 unique-SVP gap that the primal attack has to see, so LLL has no chance.
BKZ. The Block Korkine-Zolotarev algorithm parameterizes lattice reduction by a block size (Schnorr & Euchner, 1994). For , BKZ collapses to LLL. For , BKZ collapses to a single call of an exact SVP oracle on the whole lattice. For intermediate , BKZ iterates an SVP oracle over rank- sub-bases until the full basis is reduced. The output quality is quantified by the root-Hermite factor , and the closed-form approximation
is valid for and is the one the Kyber Round 3 submission uses, citing Chen’s 2013 thesis and the Albrecht-Player-Scott estimator for it (Albrecht et al., 2015; Avanzi et al., 2021; Chen, 2013). The practical refinements that make BKZ run at these block sizes, early abort and pruned enumeration among them, come from BKZ 2.0 (Chen & Nguyen, 2011). At , . At , . At small the asymptotic formula is not a useful root-Hermite factor, and the empirical value observed on LLL-reduced bases in dimensions of cryptographic interest is (Gama & Nguyen, 2008). BKZ’s first Gram-Schmidt (GSO) vector on a lattice of dimension and determinant has norm approximately , which is how the primal attack’s success condition will drop out.
Core-SVP cost. Inside the rank- SVP oracle that BKZ calls, the best known algorithm is a sieve (Becker et al., 2016). The classical sieving complexity is operations. Applying the Laarhoven quantum speedup to the nearest-neighbor search at the heart of the sieve brings the classical exponent down to for a quantum attacker (Laarhoven, 2015; Laarhoven et al., 2015). The core-SVP methodology was introduced by the NewHope team for their Round 1 NIST PQC submission (Alkim et al., 2016). It takes the cost of one SVP oracle call at the block size where the primal attack succeeds, and reports that cost as the attack’s bit-security. The methodology deliberately ignores the sub-exponential factor and the number of SVP oracle calls that BKZ makes (a polynomial factor). The Kyber team’s own account of the omitted term is that the costs hidden in it were positive in the experiments that preceded the dimensions-for-free technique, and that this sub-exponential speedup makes it unclear whether the total term is positive or negative, asymptotically and concretely (Avanzi et al., 2021, sec. 5.2). The omitted factors can move the estimate in either direction. The result is a conservative baseline for the attacker’s work, not a formal lower bound. Its unit is not a gate count either. The Kyber specification states the core-SVP figure as CPU cycles and keeps it in its own rows of Table 4 (, , and classical bits), separate from the refined estimate it reports as gates (, , and ), which prices the sieve as an explicit circuit, adds the progressive-sieving overhead and the dimensions-for-free saving, and is the number the specification holds against AES (Table 4 in Avanzi et al., 2021, sec. 4.4, with the costing in §5.2 and §5.2.1). The NIST category floors in the next subsection are AES-derived gate-count reference costs (National Institute of Standards and Technology, 2016). Holding a core-SVP number against them is a comparison of proxy bits with gates, and the specification says the core-SVP estimates come out smaller than the AES gate counts for classical attacks (Avanzi et al., 2021, sec. 4.4). A quantitative comparison needs a circuit-cost model, and the refined row is what supplies one.
Every block size the rest of the chapter computes translates directly into bits of classical security and bits of quantum security via these two exponents.
Step-by-step cryptanalysis of ML-KEM
Section titled “Step-by-step cryptanalysis of ML-KEM”The primal embedding
Section titled “The primal embedding”The Kyber Round 3 submission states the primal attack’s success condition as equation 9 of Section 5.1.2 (Avanzi et al., 2021). Start with a Module-LWE instance with module rank , polynomial degree , modulus , and secret-error coefficient standard deviation . The attacker chooses a number of samples , builds a lattice of dimension and volume , and runs BKZ-. The attack succeeds iff the projection of the planted short vector onto the last Gram-Schmidt vectors is shorter than the last Gram-Schmidt vector BKZ- produces. The planted vector’s projection onto a -dimensional subspace has expected norm . The last Gram-Schmidt vector has length under the geometric series assumption.
Combining the two gives equation 9:
The attacker picks the smallest for which some satisfies the inequality. A larger gives more samples and a larger lattice dimension. A larger shifts the exponent down, which is bad for the attacker. A larger also lets absorb more of the modulus, which is good for the attacker. The two effects trade, and there is a unique optimum the estimator searches for. The Kyber Round 3 submission reports its minimum at specific values ( for the three parameter sets). The estimator below picks slightly different for ML-KEM-768 and ML-KEM-1024, which is part of the reproduction-gap story in the estimator subsection below (Avanzi et al., 2021).
For ML-KEM the secret-error coefficient standard deviation is , where is the centered binomial parameter of the key-generation noise (National Institute of Standards and Technology, 2024). At ML-KEM-768 we have , so .
import math
def delta_beta(beta): """Chen 2013 root-Hermite factor approximation.""" numerator = ((math.pi * beta) ** (1.0 / beta)) * beta return (numerator / (2.0 * math.pi * math.e)) ** (1.0 / (2.0 * (beta - 1)))
def primal_succeeds(beta, d, q, m, sigma): """Kyber Round 3 submission equation 9.""" log_lhs = math.log(sigma * math.sqrt(beta)) log_rhs = (2 * beta - d - 1) * math.log(delta_beta(beta)) + (m / d) * math.log(q) return log_lhs <= log_rhs
# ML-KEM-768: k = 3, n = 256, q = 3329, eta_1 = 2, sigma = 1.k, n, q, sigma = 3, 256, 3329, 1.0# Try the attack at the published optimal number of samples m = 650 (d = 1419).m = 650d = m + k * n + 1print(f"ML-KEM-768 lattice dimension d = {d}")# ==> ML-KEM-768 lattice dimension d = 1419print(f"primal succeeds at beta = 500 ? {primal_succeeds(500, d, q, m, sigma)}")# ==> primal succeeds at beta = 500 ? Falseprint(f"primal succeeds at beta = 700 ? {primal_succeeds(700, d, q, m, sigma)}")# ==> primal succeeds at beta = 700 ? TrueThe inequality fails at and holds at . The actual threshold lies somewhere between those two block sizes. The next subsection finds it by linear search.
The dual distinguisher
Section titled “The dual distinguisher”The dual attack works in an extended dual lattice. Its plain-LWE form finds short vectors in the scaled dual of the LWE lattice and uses them to distinguish (Albrecht et al., 2015, sec. 5.3). Start with a Module-LWE sample with and . The Kyber Round 3 submission uses the lattice of integer pairs , which has dimension and volume (Avanzi et al., 2021, sec. 5.1.3). The attacker runs BKZ on to find a short vector of length , then computes on each fresh sample.
If is uniformly random, is uniform on . If is an LWE sample, because . Both and are small because is a short lattice vector and have small coefficients. So is distributed approximately as a centered Gaussian of standard deviation , where is the secret-error coefficient standard deviation. The difference between that Gaussian and the uniform distribution on is what the distinguisher sees.
The Kyber Round 3 submission bounds the distinguishing advantage from above (Section 5.1.3): the LWE and uniform distributions of the inner product have maximal variation distance at most (Albrecht et al., 2015; Avanzi et al., 2021)
Here and are integer vectors. The inner product is taken over and then reduced modulo . Shorter means a larger bound, and the bound collapses superexponentially in . BKZ at block size produces a vector of length on () under the GSA. This is the dual-lattice first-vector length estimate, a different success condition from the primal embedding, which instead compares the planted vector against the last Gram-Schmidt vectors. The dual attack’s cost is then the cost of running BKZ at the block size whose dual vector drives small enough to make the advantage usable. For core-SVP purposes, that comes out to roughly the same value as the primal attack for Kyber-scale parameters (Avanzi et al., 2021).
import math
def dual_advantage(w_norm, sigma, q): """Kyber Round 3 submission, Section 5.1.3 distinguishing bound.""" tau = (w_norm * sigma) / q return 4.0 * math.exp(-2.0 * math.pi * math.pi * tau * tau)
# At ML-KEM-768 parameters (sigma = 1) the bound depends only on the# ratio of the dual vector norm to the modulus q = 3329. The bound is# meaningful only when below 1; a much shorter vector saturates it.print(f"w norm 1000: advantage <= {dual_advantage(1000, 1.0, 3329):.4f}")# ==> w norm 1000: advantage <= 0.6738print(f"w norm 1500: advantage <= {dual_advantage(1500, 1.0, 3329):.6f}")# ==> w norm 1500: advantage <= 0.072708print(f"w norm q = 3329: advantage <= {dual_advantage(3329, 1.0, 3329):.3e}")# ==> w norm q = 3329: advantage <= 1.070e-08A dual vector at the modulus length drives the bound to roughly , which is negligible. When is a small fraction of the raw expression exceeds . The advantage is then capped at and the bound stops being a meaningful quantitative estimate. The useful regime for estimates is where the expression is below but not vanishingly small. Because the KEM hashes the agreed key, a small per-sample is not directly exploitable. The attacker amplifies it by collecting many short dual vectors and combining their distinguishing signals (Albrecht et al., 2015; Avanzi et al., 2021). The Round 3 submission’s analysis places the dual within a few block sizes of the primal for every Kyber parameter set (Avanzi et al., 2021).
In the Kyber Round 3 refined gate-count analysis the dual attack is significantly more expensive than the primal attack against Kyber. The dual analysis assumes exponentially many equally short sieve vectors, which is incompatible with the dimensions-for-free technique the primal attack exploits (Avanzi et al., 2021). Later refined dual attacks changed the RAM-model picture, as the refinement pass below describes. The chapter uses the primal attack as the canonical one for the rest of the step-by-step.
A core-SVP estimator in Python
Section titled “A core-SVP estimator in Python”The success condition plus the core-SVP cost model plus a linear search over is the whole estimator. The implementation is a handful of lines on top of delta_beta and primal_succeeds from before. The core-SVP side needs no numpy at all. The following block searches for the smallest that works at some valid , and prints the three-row ML-KEM table.
import math
def delta_beta(beta): numerator = ((math.pi * beta) ** (1.0 / beta)) * beta return (numerator / (2.0 * math.pi * math.e)) ** (1.0 / (2.0 * (beta - 1)))
def primal_succeeds(beta, d, q, m, sigma): log_lhs = math.log(sigma * math.sqrt(beta)) log_rhs = (2 * beta - d - 1) * math.log(delta_beta(beta)) + (m / d) * math.log(q) return log_lhs <= log_rhs
def core_svp_beta(k, n, q, sigma): for beta in range(50, 1200): for m in range(1, (k + 1) * n + 1): d = m + k * n + 1 if primal_succeeds(beta, d, q, m, sigma): return beta raise AssertionError("no beta in range")
parameter_sets = [ ("ML-KEM-512", 2, 256, 3329, 3), ("ML-KEM-768", 3, 256, 3329, 2), ("ML-KEM-1024", 4, 256, 3329, 2),]
print(f"{'name':<12} {'beta':>5} {'classical':>10} {'quantum':>8}")# ==> name beta classical quantumfor name, k, n, q, eta_1 in parameter_sets: sigma = math.sqrt(eta_1 / 2.0) beta = core_svp_beta(k, n, q, sigma) print(f"{name:<12} {beta:>5} {int(0.292 * beta):>10} {int(0.265 * beta):>8}")# ==> ML-KEM-512 406 118 107# ==> ML-KEM-768 624 182 165# ==> ML-KEM-1024 874 255 231Compare against Table 4, page 21 of the CRYSTALS-Kyber Round 3 submission (Avanzi et al., 2021). For the primal attack under the core-SVP methodology, Table 4 reports block sizes , , and at lattice attack dimensions , , and . The estimator’s free search returns , , and , and the difference splits into exactly two causes.
| Set | Published | Published | Estimator | Estimator | From | From shape |
|---|---|---|---|---|---|---|
| ML-KEM-512 | 406 | (486, 999) | 406 | (486, 999) | 0 | 0 |
| ML-KEM-768 | 626 | (650, 1419) | 624 | (658, 1427) | 1 | 1 |
| ML-KEM-1024 | 878 | (860, 1885) | 874 | (842, 1867) | 0 | 4 |
The first cause is the estimator’s free search over the number of samples . The Kyber team fixes per parameter set; the estimator does not. At ML-KEM-768 it finds , which buys one block size over the published under the same success condition. At ML-KEM-1024 it picks a smaller than the published one and lands on the same , so the free search buys nothing there.
The second cause is the basis shape, and it is not the approximation it looks like. Running the chapter’s own success condition at the published pairs gives for ML-KEM-768 and for ML-KEM-1024, leaving and block sizes unaccounted for. The Kyber team’s security script computes from the same closed form this chapter does, so the residual is not an error in that formula (Avanzi et al., 2021). What differs is the basis the two models reduce. Equation 9 lays a single geometric-series line across the whole basis. The script instead builds the -ary shape the embedding actually has, a flat block at , the geometric-series slope only in the middle, and a flat block at , then slides that shape until its volume matches. Run at the published pairs, the script returns , , and , which is the entire residual.
The four-block gap at ML-KEM-1024 translates to classical bits before flooring, which is inside the gap between the coarse core-SVP estimate and the refined Kyber estimates, walked in the “How the estimator oversimplifies” section below. A separate one-bit effect is the rounding of the sieve exponents. The chapter uses the headline and that the submission states for its first-pass core-SVP analysis. Flooring the unrounded and reproduces Table 4’s classical and quantum at ML-KEM-768, one bit above the chapter’s / (Avanzi et al., 2021; Becker et al., 2016; Laarhoven et al., 2015). That reproduction does not identify the submission’s own arithmetic. Rounding the headline products to nearest reaches the same pair, since and , and the submission states the exponents without saying how the table’s integers were taken from them.
The ch13-lattice-cryptanalysis package under solutions/ factors the same logic into core_svp, primal, and dual modules, plus an estimator entry point that returns a frozen dataclass record per parameter set. The package’s primal_success swaps the argument order of the inline primal_succeeds and computes internally from and . The two are numerically equivalent. The pytest suite at tests/ch13/ asserts the reproduction gap is at most five block sizes for every ML-KEM parameter set, which is the contract the package commits to.
From block size to NIST security category
Section titled “From block size to NIST security category”FIPS 203 states that ML-KEM-512, ML-KEM-768, and ML-KEM-1024 are claimed to be in NIST security categories 1, 3, and 5 respectively (National Institute of Standards and Technology, 2024). The NIST Call for Proposals defines category 1 as the classical-and-quantum cost of key recovery against AES-128, category 3 as the AES-192 version, and category 5 as the AES-256 version (National Institute of Standards and Technology, 2016). Section 4.A.5 then prices those reference attacks directly, and the classical and quantum columns behave differently. The classical costs are fixed numbers: gates for AES-128, for AES-192, and for AES-256. The quantum costs are not, because the call restricts a quantum attack to a fixed circuit depth MAXDEPTH and quotes them as , , and gates. So it is the classical column that gives the floor: , , and bits (National Institute of Standards and Technology, 2016). A core-SVP bit count held against it is a proxy held against a gate count, as the core-SVP cost definition in “The four problems and the two tools” said. The comparison below is the Kyber team’s own: the coarse baseline is shown against the floor, and the refined gate count is the number that clears it.
The ML-KEM estimator numbers above sit below those floors on the core-SVP model: classical bits for ML-KEM-512, for ML-KEM-768, for ML-KEM-1024. The gap is intentional. The core-SVP model is a deliberately coarse baseline. The refined gate-count model the Kyber team used for the final parameter choice gives larger counts: , , and classical bits for the three sets (Avanzi et al., 2021). Those clear the NIST floors of , , and by , , and bits. That margin is not unconditional. The refined dual attacks of Guo-Johansson 2021 and the MATZOV group 2022 lowered the RAM-model estimates (Guo & Johansson, 2021; MATZOV, 2022). The NIST third-round status report concludes that all three Kyber parameter sets fall slightly below their category targets when memory-access cost is not charged. They are expected to meet the targets in realistic cost models that charge for large-memory access (Alagic et al., 2022).
import math
def core_svp_classical(beta): return int(0.292 * beta)
def core_svp_quantum(beta): return int(0.265 * beta)
rows = [ ("ML-KEM-512", 406, 1), ("ML-KEM-768", 624, 3), ("ML-KEM-1024", 874, 5),]nist_floor_classical = {1: 143, 3: 207, 5: 272}
print(f"{'name':<12} {'beta':>5} {'classical':>10} {'NIST cat':>9} {'floor':>6}")# ==> name beta classical NIST cat floorfor name, beta, cat in rows: classical = core_svp_classical(beta) floor = nist_floor_classical[cat] print(f"{name:<12} {beta:>5} {classical:>10} {cat:>9} {floor:>6}")# ==> ML-KEM-512 406 118 1 143# ==> ML-KEM-768 624 182 3 207# ==> ML-KEM-1024 874 255 5 272The classical core-SVP cost is below the NIST floor for every category. The gap is the distance between a deliberately coarse core-SVP estimate and the refined Kyber estimates, not a single calibrated margin. The “How the estimator oversimplifies” section below describes those refinements and the conditions under which the FIPS 203 parameters clear the NIST category floors.
The same estimator against ML-DSA
Section titled “The same estimator against ML-DSA”Part II builds two flagship schemes, and everything above prices one of them. ML-DSA (Chapter 12) rests on Module-LWE too, so the estimator already written applies to it without a new success condition (National Institute of Standards and Technology, 2024b). Two things about the instance differ, and both change the arithmetic rather than the model.
The module is not square. ML-KEM’s public key is , so unknowns and samples both scale with . ML-DSA publishes with and at two of the three parameter sets. The unknowns are , which is ring elements, and the samples are the rows of , so the embedding dimension is with capped at rather than .
The secret is uniform rather than centered binomial. FIPS 204 draws every coefficient of and uniformly from (National Institute of Standards and Technology, 2024b). A uniform coefficient on that range has variance , so replaces ML-KEM’s . At ML-DSA-65, gives , well above any ML-KEM value. Neither that nor the much larger modulus is a hardness comparison on its own. In the success condition above, enters the right-hand side as , so raising it at fixed dimensions and noise makes the condition easier to satisfy rather than harder. Modulus, module dimensions and noise distribution decide the estimated cost together, and the block sizes below are what the estimator returns for the standardized parameter set.
import math
def delta_beta(beta): numerator = ((math.pi * beta) ** (1.0 / beta)) * beta return (numerator / (2.0 * math.pi * math.e)) ** (1.0 / (2.0 * (beta - 1)))
def primal_succeeds(beta, d, q, m, sigma): log_lhs = math.log(sigma * math.sqrt(beta)) log_rhs = (2 * beta - d - 1) * math.log(delta_beta(beta)) + (m / d) * math.log(q) return log_lhs <= log_rhs
def mldsa_beta(k, ell, n, q, eta): # The unknowns are s_1, which is ell ring elements. The samples are # the k rows of t = A s_1 + s_2. A coefficient uniform on # [-eta, eta] has variance eta (eta + 1) / 3. sigma = math.sqrt(eta * (eta + 1) / 3.0) for beta in range(50, 1200): for m in range(1, k * n + 1): if primal_succeeds(beta, m + ell * n + 1, q, m, sigma): return beta raise AssertionError("no beta in range")
# ML-DSA parameter sets from FIPS 204 Table 1; q = 2**23 - 2**13 + 1.q_dsa = 8380417parameter_sets = [ ("ML-DSA-44", 4, 4, 2, 2), ("ML-DSA-65", 6, 5, 4, 3), ("ML-DSA-87", 8, 7, 2, 5),]
print(f"{'name':<11} {'beta':>5} {'classical':>10} {'quantum':>8} {'cat':>4}")# ==> name beta classical quantum catfor name, k, ell, eta, cat in parameter_sets: beta = mldsa_beta(k, ell, 256, q_dsa, eta) print(f"{name:<11} {beta:>5} {int(0.292 * beta):>10} {int(0.265 * beta):>8} {cat:>4}")# ==> ML-DSA-44 424 123 112 2# ==> ML-DSA-65 624 182 165 3# ==> ML-DSA-87 863 251 228 5The Dilithium Round 3 submission’s security table reports its Module-LWE block sizes on a row labelled “BKZ block-size (GSA)”: , , and , with classical core-SVP costs , , and (Bai et al., 2021). The estimator lands within one block size on all three, and exactly on two of them. That is a tighter reproduction than the ML-KEM side managed, and the label is the reason. The Dilithium team publishes the pure geometric-series row, which is the model equation 9 states; the Kyber team publishes the row its security script produces, which is the basis-shape model of the “A core-SVP estimator in Python” section above. Comparing like with like removes the residual.
ML-DSA-65 lands at , the same block size as ML-KEM-768. Two schemes with different rings, different moduli, different secret distributions and different jobs are calibrated to the same lattice-reduction effort, because both are aiming at NIST category 3. The estimator is the thing that makes that visible.
The category floors do not carry across unchanged, though. ML-DSA-44 claims category 2, and categories 2 and 4 are hash-collision reference problems rather than AES key search: the Call for Proposals prices SHA3-256 collisions at classical gates, not (National Institute of Standards and Technology, 2016). The pattern from the ML-KEM table repeats regardless. Core-SVP puts all three sets below their floors, at , , and against , , and , and the Dilithium team’s refined gate counts of , , and are what clear them (Bai et al., 2021).
How the estimator oversimplifies
Section titled “How the estimator oversimplifies”Core-SVP is a deliberately coarse baseline. Three refinements push the estimate upward, and two push it downward. The balance matters for the FIPS 203 parameter choice (National Institute of Standards and Technology, 2024a).
Dimensions for free. Ducas 2018 observed that the last few Gram-Schmidt vectors of a BKZ-reduced basis are good enough for a shortcut (Ducas, 2018). The SVP oracle at the head of the block can be replaced with a smaller-dimensional SVP oracle, with a Babai nearest-plane lift completing the rest of the shortest-vector recovery (Babai, 1986). The saving is roughly dimensions at block size (Ducas, 2018). At ML-KEM-768 () this saves about dimensions, which drops the classical sieving exponent from to roughly bits (Avanzi et al., 2021). The refinement moves in the attacker’s favor.
Progressive BKZ. Actual BKZ implementations run over increasing block sizes rather than one fixed . The Kyber Round 3 refined estimate models the cumulative cost as times the cost of the final sieve in dimension (Avanzi et al., 2021). Here and is the full lattice dimension. For Kyber512 (, ) that overhead is roughly times the final SVP cost. The refinement moves in the defender’s favor and is of the same order as the dimensions-for-free saving in that worked estimate, not negligible against it.
Refined dual attacks. Guo and Johansson 2021 (Guo & Johansson, 2021) and the MATZOV group 2022 (MATZOV, 2022) published refined dual attacks that cut the cost of the dual distinguisher against Kyber by exploiting modulus-switching and the specific structure of centered binomial noise. The refinements prompted a NIST re-review in 2022. The NIST third-round status report concluded that the improvements lower the RAM-model estimates enough that all three Kyber sets fall slightly below their targeted categories when memory-access cost is ignored. They are still expected to meet those categories in realistic cost models that charge for large-memory access (Alagic et al., 2022). The refinement moves in the attacker’s favor.
Rounding noise (Module-LWER). The actual noise distribution inside ML-KEM is not pure Module-LWE. Ciphertext compression introduces an additional rounding error that Chapter 11 walked into at the noise-budget level (National Institute of Standards and Technology, 2024a). For analysis purposes the Kyber team defines Module-LWER, the rounding-error variant, and the six core-SVP bits at stake run in the direction a reader would not guess. Bounding Module-LWER by Module-LWE at the smaller ciphertext noise parameter would put ML-KEM-512 at classical core-SVP bits rather than , dropping the refined gate count from to roughly . The Kyber team rejects that bound because it discards the compression error, whose per-coefficient variance carries the total above the key-generation noise’s . They therefore treat ML-KEM-512 as at least as hard as Module-LWE at , which is what the published numbers assume (Avanzi et al., 2021). The refinement moves in the defender’s favor, and the six bits are what ignoring it costs rather than what counting it adds.
Memory costs. Core-SVP assumes the attacker has enough RAM to hold lattice vectors in memory. At ML-KEM-768 that is roughly vectors, which is beyond any physically realizable storage. The RAM model is a generous assumption. What a real sieve pays instead depends on the physical model and on the algorithm. Jaques puts processors, memory, and wire in constant proportion, charges the routing between them, and modifies the sieve to amortize that routing. Asymptotically the area-time cost is for a computer laid out in two spatial dimensions and returns to the RAM-model in three or more, which is the paper’s title (Jaques, 2024). The concrete pricing takes the two-dimensional case with a fixed memory-access constant, changes only the sieving step inside the primal attack’s core-SVP subroutine, and leaves every other aspect of the attack as the Kyber submission had it. Under those assumptions the published Table 1 puts the primal attack against the three ML-KEM parameter sets at , , and bits, which is , , and above the RAM-model estimator that already carries the MATZOV refinement, and , , and above the submission’s own gate counts, because MATZOV’s saving and the memory charge nearly cancel at ML-KEM-512 (Jaques, 2024). The ePrint revision of April 2024 carried and for the first two sets, and the journal version revised them. The author reads the result as an upper bound on the overhead of memory in sieving rather than a settled cost, and it is a costing of one primal attack under one memory model, not of the coded dual attack or of any other. The ML-KEM-512 figure sits inside the to range NIST gives around its own estimate of about (National Institute of Standards and Technology, 2023). Zhao, Ding, and Yang make the algorithmic half of the point: a BGJ sieve organized around streaming memory access, with buckets that shrink by orders of magnitude at each filtering layer, needs only non-random main-memory accesses, so which sieve is run, and how it touches memory, is part of any memory-cost claim (Zhao et al., 2025). The refinement moves in the defender’s favor.
The net balance. The FIPS 203 final parameters land the three ML-KEM variants at refined gate-count estimates of , , and classical bits, above the NIST category floors of , , and (Avanzi et al., 2021). The post-2021 refined dual attacks pulled the RAM-model estimates down far enough that the NIST third-round report places all three sets slightly below their targets when memory access is treated as free (Alagic et al., 2022; Guo & Johansson, 2021; MATZOV, 2022). The peer-reviewed 2025 coded dual attack reaches the same place without the disputed independence assumptions, at , , and bits (Carrier et al., 2025). They are still expected to meet the targets in realistic cost models that charge for large-memory access (Alagic et al., 2022). The one concrete pricing of that kind is Jaques’s, and it prices the primal attack under its own memory model at , , and bits (Jaques, 2024). It does not reprice the coded dual attack or the ring-aware dual attack, so the expectation that memory costs restore the margin is an inference from one attack’s repricing, and raising the cost of one attack bounds nothing about the cheapest. Core-SVP’s bits for ML-KEM-768 is the coarse-baseline figure; is the gate-count estimate; the realistic-memory caveat is why the margin is expected to hold rather than proven.
A concrete chosen-ciphertext attack on K-PKE. Chapter 11 promised one concrete decryption-oracle attack that the Fujisaki-Okamoto wrapper closes. Consider a small-secret variant of the Chapter 10 flat Regev PKE at Kyber-like toy parameters. Chapter 10 draws uniformly from . Here it is drawn from instead, the toy analogue of ML-KEM’s centered-binomial secret and the same change the primal-attack instance at the top of this chapter made. The error is and the noise budget inequality is at . The encryption of a message bit produces a ciphertext under fresh randomness . Decryption computes and rounds to the nearer of or .
Fluhrer 2016 observed that a decryption oracle lets an attacker read secret coordinates one at a time via carefully chosen boundary queries (Fluhrer, 2016). Pick an index and a small positive scalar . Set where is the -th standard basis vector, and set . The decryption value is
The decoder rounds to bit when and bit otherwise. At the FIPS 203 Compress_1 rule gives dec(832) = 0 and dec(833) = 1, so is the last bit- value and the first bit- value (National Institute of Standards and Technology, 2024a). Chapter 10’s decrypt formula ((2 * v + half_q) // q) % 2 produces the same midpoint-to-bit-0 tie-break used here, matching Ch 11’s K-PKE.Decrypt toy block. At , , which crosses into the bit- branch. At , , which lies on the inclusive boundary and decodes to bit-. At , , which stays inside the bit- region. A single query with therefore distinguishes (bit-) from (bit-), but it cannot tell and apart (Fluhrer, 2016). A second query with (same ) flips the sign in the term and now distinguishes from . The pair of responses uniquely determines in two queries per coordinate.
The attack recovers one ternary coordinate per pair of oracle calls, so the full secret of this small-secret toy (entries in ) comes out in calls. ML-KEM’s K-PKE does not have a ternary secret: its key-generation secret is sampled from a centered binomial with support ( or ), so each coordinate needs more than two threshold queries to pin down. FIPS 203 specifies K-PKE only as an internal component that must not be exposed as a standalone decryption oracle (National Institute of Standards and Technology, 2024a). The attacker never runs BKZ. The decryption-oracle game is easier than the primal attack by orders of magnitude whenever raw K-PKE decryption is reachable, which is exactly what the wrapper below prevents.
The Fujisaki-Okamoto wrapper from Chapter 11 closes the oracle. Every tampered ciphertext returns the pseudorandom rejection value (Hofheinz et al., 2017; National Institute of Standards and Technology, 2024a). That value depends on the per-key rejection seed and the ciphertext bytes but has no algebraic dependence on . An attacker who submits the Fluhrer-style chosen-ciphertext query gets back a random function of and learns nothing about any coordinate of . The Hofheinz-Hovelmanns-Kiltz theorem formalizes the argument. Starting from an OW-CPA, -correct K-PKE, derandomizing it with so that the result is rigid (a decrypted ciphertext re-encrypts to itself or is rejected), the FO transform produces an IND-CCA2 KEM with a concrete security bound in the random oracle model (Hofheinz et al., 2017). The QROM bound is looser and is established by follow-up work outside the scope of this chapter. The wrapper is the reason the decoder-boundary attacks above do not touch ML-KEM.
Tradeoffs inside Part II
Section titled “Tradeoffs inside Part II”Where does Module-LWE at rank over sit in the lattice family?
-
Module-LWE and the subfield attack family (Albrecht, Bai, and Ducas 2016). The subfield attack reduces an overstretched NTRU instance to a lower-dimensional problem in a subfield of a power-of-two cyclotomic (Albrecht et al., 2016). It is an attack on overstretched NTRU, not on ML-KEM’s Module-LWE instance. The Ducas-van Woerden 2021 ternary-NTRU fatigue point is a statement about the NTRU key distribution, not ML-KEM’s Module-LWE public key, so its numeric threshold should not be carried across (Ducas & van Woerden, 2021). At that point is near , close to ML-KEM’s , which is exactly why the cross-comparison is brittle. The narrower safe takeaway: the known overstretched-NTRU subfield and fatigue attacks are not direct attacks on ML-KEM’s Module-LWE instance over . The Langlois-Stehlé 2015 worst-case to average-case reduction is the foundation of the Module-LWE family the chapter inherits from Chapter 9, under its own hypotheses: an elliptical Gaussian error family in the canonical embedding and, for the direct search-to-decision route, a prime modulus that splits completely in the ring. Its Theorem 4.8 reaches other moduli by modulus switching at enlarged Gaussian noise. ML-KEM’s centered-binomial noise is not that family on either route, so the reduction certifies the problem family and not this instance (Langlois & Stehlé, 2015, sec. 4.1). The power-of-two cyclotomic and the Module-LWE public-key distribution are what keep the specific subfield attack family out.
-
Module-LWE and flat-LWE Regev from Chapter 10. A direct Regev-style unstructured-LWE construction at a security level matching Kyber pushes the public key into the megabyte range, because the flat public key carries an matrix over . The secret key is the -entry vector and is a kilobyte or two. A more key-exchange-amenable unstructured-LWE design brings the public key and ciphertext down to roughly KB each, still about ten times larger and slower than Kyber-scale Module-LWE (Avanzi et al., 2021). ML-KEM’s ring structure cuts the public key to bytes by amortizing coefficients into each matrix entry. The primal attack is analyzed the same way in every case: flatten the instance and run the primal embedding against the resulting lattice. What changes is the number of bytes the defender ships over the wire, plus a small correction the flattening misses. Hou, Jiang, and Ogilvie show that negacyclic rotations in let a hybrid attacker reuse one expensive preprocessing across many rotated secrets, which the published paper’s Table 9 prices in its “MLWE vs. LWE contribution” column at to bits for ML-KEM-512, to for ML-KEM-768, and to for ML-KEM-1024, the three values per set being its core-SVP, classical-circuit, and classical-query cost models, around one bit in the paper’s own summary (Hou et al., 2026). The table’s total gap over the prior estimator is larger, to bits, because it also includes a corrected search over the number of guesses, and that part is not a ring effect. Hou and Jiang’s half of the merged paper, the enhanced hybrid decoding attack, gains nothing over the primal attack at ML-KEM’s non-sparse secrets, where the plain uSVP estimate still dominates. The large effect is on the sparse secrets used in homomorphic encryption, up to 14.2 bits below the corresponding flat-LWE estimate.
-
Module-LWE and Ring-LWE at from Chapter 9. Ring-LWE at rank one gives a smaller public key at the same security level than Module-LWE at rank , but pays for it with a harder parameter-scaling story. Scaling security in Ring-LWE means changing , which changes the ring, the NTT, and the structural attack surface. Scaling security in Module-LWE means changing , which keeps the ring fixed and just adds matrix rows. The three ML-KEM parameter sets use the same ring and the same NTT. They differ in module rank , the key-generation noise parameter ( at ML-KEM-512, at ML-KEM-768 and ML-KEM-1024), and, for ML-KEM-1024, the ciphertext compression parameters versus (National Institute of Standards and Technology, 2024a).
Where Part II ends and Part III picks up
Section titled “Where Part II ends and Part III picks up”Part II has now built two schemes from scratch and priced the assumption underneath both. ML-KEM (Chapter 11) and ML-DSA (Chapter 12) share a ring family and a single hardness assumption for key recovery, and this chapter has shown what that assumption costs an attacker: a BKZ run at for either of the category-3 sets, which the core-SVP model prices at classical operations. ML-DSA’s forgery side rests on Module-SIS, and the note above puts its published core-SVP row at or above the Module-LWE one at every parameter set. Everything in the Part rests on that lattice reduction improving slowly.
That is also the Part’s weakness, and it is why the book does not stop here. A single family means a single failure. Parts III and IV build the alternatives from different mathematics. Part III uses hash-based signatures, where security rests on the preimage resistance of a hash function and a few related properties rather than on any algebraic structure. Part IV uses code-based encryption and isogenies, where the hard problems are syndrome decoding and finding paths in isogeny graphs. Neither family is touched by the lattice reduction this chapter has spent its length on.
The three Parts after those change the question. Part V is migration and deployment, where the constraint stops being hardness and becomes inventory, protocol negotiation, and the size of the certificates a handshake has to carry. Part VI is post-quantum zero-knowledge, which is where the lattice assumptions of this Part reappear in a different role. Part VII applies all of it to blockchains, where key exposure is public by construction and no single authority can compel every participant to migrate, which is why Chapter 41 makes distributed coordination its own subject.
Chapter 14 starts the hash-based track from one-time signatures, which need no algebraic structure at all.
Exercises
Section titled “Exercises”-
Core-SVP bit costs across a block-size sweep. Compute and for and tabulate the resulting classical and quantum bit counts. Verify the monotonicity the Kyber Round 3 submission relies on, and locate the ML-KEM-768 Table 4 block size between two rows of your table (Avanzi et al., 2021).
-
Where the estimator starts saying something. Hold and fixed and sweep the number of unknowns over , allowing up to samples. For each , find the smallest satisfying
primal_succeedsabove, searching from upward ascore_svp_betadoes. Tabulate against and confirm the last row reproduces ML-KEM-768’s . Two rows come out equal. Explain what that tells you about the estimator rather than about those two instances, and say why searching below would not fix it. -
Subfield-attack sketch at an overstretched cyclotomic. The Albrecht-Bai-Ducas attack on overstretched NTRU works at a power-of-two cyclotomic of degree with (Albrecht et al., 2016). Explain in one paragraph why this attack should not be read as a direct attack on ML-KEM’s Module-LWE instance, discussing both the much smaller ML-KEM modulus and, more importantly, the different public-key distribution. Then say what the Langlois-Stehlé 2015 reduction does and does not certify about that instance (Langlois & Stehlé, 2015).
-
Decoder-boundary oracle on flat Regev. Implement the Fluhrer-2016 chosen-ciphertext attack walked above (Fluhrer, 2016). Target the Chapter 10 flat Regev PKE at with its key generation changed to draw the secret from , as in the walkthrough. Construct two queries per secret coordinate: one with and one with , both at and small positive . Verify that the pair of decoder responses uniquely identifies , and count total oracle calls to recover the full secret.
Worked solutions and editorial notes for these exercises are in Appendix D, Chapter 13. A separate track, for rebuilding rather than reading: the package exercises/ch13-lattice-cryptanalysis has every function the chapter teaches replaced by a stub. Run PQC_IMPL=exercises pytest tests/ch13 to grade your version against the suite that proves the reference one.
References
Section titled “References”Last updated: