Skip to content

Chapter 10: Regev encryption from scratch

Regev encryption hides a message bit inside LWE noise. The public key is an LWE sample (A,b=As+e)(A, b = A s + e). A ciphertext is a random {0,1}\{0, 1\}-linear combination of the rows of this sample. The sender adds q/2μ\lfloor q/2 \rfloor \mu to the second coordinate to carry the bit μ\mu. Decryption computes c2c1sc_2 - c_1^\top s modulo qq, which cancels the secret and leaves q/2μ+er\lfloor q/2 \rfloor \mu + e^\top r. Rounding the result to the nearer of 00 or q/2\lfloor q/2 \rfloor recovers μ\mu whenever the noise stays inside the window the decoder tolerates.

For correctness, the noise budget is the entire story. Security adds two more pieces: the public-key indistinguishability from uniform under decisional LWE, and the leftover hash lemma applied to the joint hash r[Au]rr \mapsto [A \,\|\, u]^\top r.

The toy parameters are (n,q,m,B)=(4,97,8,1)(n, q, m, B) = (4, 97, 8, 1), matching Chapter 8. Key generation draws a uniform secret sZ974s \in \mathbb{Z}_{97}^4 and a uniform matrix AZ978×4A \in \mathbb{Z}_{97}^{8 \times 4}. It also draws an error vector e{1,0,1}8e \in \{-1, 0, 1\}^8 and sets b=As+eb = A s + e in Z978\mathbb{Z}_{97}^8. The public key is the LWE sample (A,b)(A, b) and the secret key is ss (Regev, 2009).

Seeding numpy’s default generator at 00 fixes a specific instance. The secret is s=(82,61,49,26)s = (82, 61, 49, 26). The error in symmetric representatives is e=(1,1,0,0,0,1,1,1)e = (-1, -1, 0, 0, 0, -1, -1, -1). Every coordinate of ee is at most 11 in absolute value.

To encrypt the bit μ=1\mu = 1, the sender draws a random r{0,1}8r \in \{0, 1\}^8. The ciphertext is (c1,c2)=(Ar,br+48)mod97(c_1, c_2) = (A^\top r, b^\top r + 48) \bmod 97. The shift 48=97/248 = \lfloor 97 / 2 \rfloor is the encoding of the bit 11. At the same seed the draw is r=(0,1,1,1,0,1,1,0)r = (0, 1, 1, 1, 0, 1, 1, 0), and the ciphertext is c1=(44,50,54,74)c_1 = (44, 50, 54, 74) with c2=21c_2 = 21.

Decryption uses the secret ss to compute c2c1smod97c_2 - c_1^\top s \bmod 97. The result is 4545. In symmetric representatives this is distance 33 from 4848, so it rounds to 4848 and decodes to the bit 11. The noise did not flip the decoded bit because er=3|e^\top r| = 3, and the decoder tolerates any er|e^\top r| up to 2323 at these parameters.

The same rr with the bit μ=0\mu = 0 gives c2=70c_2 = 70 instead of 2121, because the encoding shift drops from 4848 to 00. The decrypted value is c2c1s=94mod97c_2 - c_1^\top s = 94 \bmod 97, which in symmetric representatives is 3-3. That rounds to 00 and decodes to 00. The distance from 3-3 to 00 and the distance from 4545 to 4848 are both the same quantity er=3|e^\top r| = 3.

A numpy block reproduces the walkthrough end to end.

import numpy as np
n, q, m, B = 4, 97, 8, 1
rng = np.random.default_rng(seed=0)
s = rng.integers(0, q, size=n, dtype=np.int64)
A = rng.integers(0, q, size=(m, n), dtype=np.int64)
e = rng.integers(-B, B + 1, size=m, dtype=np.int64)
b = (A @ s + e) % q
r = rng.integers(0, 2, size=m, dtype=np.int64)
half_q = q // 2
c1 = (A.T @ r) % q
c2_one = (int(b @ r) + half_q * 1) % q
c2_zero = (int(b @ r) + half_q * 0) % q
v_one = (c2_one - int(c1 @ s)) % q
v_zero = (c2_zero - int(c1 @ s)) % q
print("s =", s.tolist())
print("r =", r.tolist())
print("c1 =", c1.tolist())
print("c2 for mu=1:", c2_one, "c2 for mu=0:", c2_zero)
print("v for mu=1:", v_one, "v for mu=0:", v_zero)
# ==> s = [82, 61, 49, 26]
# ==> r = [0, 1, 1, 1, 0, 1, 1, 0]
# ==> c1 = [44, 50, 54, 74]
# ==> c2 for mu=1: 21 c2 for mu=0: 70
# ==> v for mu=1: 45 v for mu=0: 94

Every Python block this chapter prints is also a standalone file in the companion repository, under chapter-code/ch10/, one file per block. Appendix C covers the clone and the environment they run on.

Noise budget and symmetric representatives

Section titled “Noise budget and symmetric representatives”

Two notational conventions carry through the rest of the chapter. The first is symmetric representatives on Zq\mathbb{Z}_q: every residue v{0,1,,q1}v \in \{0, 1, \ldots, q - 1\} is mapped to the signed integer in the half-open interval (q/2,q/2](-q/2, q/2] that shares its residue class. For odd qq, this is the integer closest to 00. For q=97q = 97, the values 0,1,,480, 1, \ldots, 48 map to themselves, and the values 49,50,,9649, 50, \ldots, 96 map to 48,47,,1-48, -47, \ldots, -1. The symmetric representative of 9494 is 3-3; the symmetric representative of 4545 is 4545.

The second convention is the nearest-codeword decoder for the two encodings 00 and q/2\lfloor q/2 \rfloor. Given vZqv \in \mathbb{Z}_q, the decoder asks which of 00 and q/2\lfloor q/2 \rfloor is closer to vv on the cycle Zq\mathbb{Z}_q. The integer expression (2v+q/2)/qmod2\lfloor (2 v + \lfloor q/2 \rfloor) / q \rfloor \bmod 2 gives the same answer without any floating-point arithmetic. For q=97q = 97 the decoder returns 00 on the 4949 residues {0,,24}{73,,96}\{0, \ldots, 24\} \cup \{73, \ldots, 96\} and 11 on the 4848 residues {25,,72}\{25, \ldots, 72\}. The two regions have different widths because qq is odd, and the decoder breaks the midpoint tie toward the bit 00. A ciphertext decodes correctly if and only if the decrypted value v=c2c1smodqv = c_2 - c_1^\top s \bmod q lands inside the region that contains its encoded value.

Regev noise budget on Z_97. A number line on Z_97 in symmetric representatives runs from negative 48 on the left through 0 in the middle to positive 48 on the right. Two decoding regions of roughly equal width are drawn as colored bands. The first is a blue band around 0 for the bit 0. The second is an amber band around positive 48 that wraps to the left edge at negative 48, for the bit 1. Two ciphertext points sit inside the regions: one at v equals negative 3 decoding to 0 and one at v equals 45 decoding to 1. -48 -24 0 24 48 v = -3 (mu = 0) v = 45 (mu = 1) decoding region for 0 (blue, 49 residues) decoding region for 1 (amber, 48 residues, wraps around 48)
Figure 10.1. The Regev noise budget on Z97\mathbb{Z}_{97} in symmetric representatives. The bit-0 decoding region (blue, 49 residues) surrounds 0; the bit-1 region (amber, 48 residues) surrounds 48 and wraps around the cycle. Two honest ciphertexts land inside their respective regions: v=3v = -3 decodes to 0 and v=45v = 45 decodes to 1.

The elementary noise bound is

eri:ri=1eimB,|e^\top r| \leq \sum_{i \,:\, r_i = 1} |e_i| \leq m \cdot B,

for r{0,1}mr \in \{0, 1\}^m and eB\|e\|_\infty \leq B. The worst case is achieved when every coordinate of rr is 11 and every coordinate of ee is ±B\pm B.

Regev’s 2009 theorem uses the rounded error distribution Ψˉα\bar\Psi_\alpha over Zq\mathbb{Z}_q, derived from a Gaussian on the torus, rather than the bounded uniform error used in the toy code. For bounded uniform errors with eiB|e_i| \leq B, the typical er|e^\top r| scales as O(Bm)O(B \sqrt{m}) by sub-Gaussian concentration; for Regev’s Gaussian-shaped error distribution, the analogous scale is O(αqm)O(\alpha q \sqrt{m}).

The bounded-uniform typical size sits a factor of m\sqrt{m} below the deterministic worst case mBm B. The Gaussian error has no deterministic bound at all, which is why Regev’s correctness statement is a probability rather than an inequality. The Gaussian form is what the worst-case-to-average-case reduction from Chapter 8 requires. The bounded uniform form is useful for toy correctness experiments but is not the distribution covered directly by Regev’s original theorem (Regev, 2009). The uniform worst-case bound mBm B is what the walkthrough code uses, and it suffices for decryption correctness at the toy parameters.

Key generation, encryption, and decryption all live inside Zq\mathbb{Z}_q. They use only matrix-vector multiplication, modular reduction, and the nearest-codeword decoder for the two encodings 00 and q/2\lfloor q/2 \rfloor. The blocks below pass the parameters (n,q,m,B)(n, q, m, B) around as loose integers so that each one runs on its own. The regev_pke package under solutions/ch10-regev-pke carries them in a RegevParams dataclass instead, with the noise budget exposed as a method on it.

Key generation. KeyGen takes the parameters (n,q,m,B)(n, q, m, B) and returns a public key pk=(A,b)\text{pk} = (A, b) together with a secret key sk=s\text{sk} = s. The secret ss is drawn uniformly from Zqn\mathbb{Z}_q^n. The matrix AA is drawn uniformly from Zqm×n\mathbb{Z}_q^{m \times n}. The error ee is drawn uniformly from {B,,B}m\{-B, \ldots, B\}^m. The public vector is b=As+eb = A s + e in Zqm\mathbb{Z}_q^m. The public key is the LWE sample (A,b)(A, b), and for the same error distribution χ\chi it is computationally indistinguishable from uniform under decisional LWE from Chapter 8 (Regev, 2009).

import numpy as np
def keygen(n, q, m, B, rng):
s = rng.integers(0, q, size=n, dtype=np.int64)
A = rng.integers(0, q, size=(m, n), dtype=np.int64)
e = rng.integers(-B, B + 1, size=m, dtype=np.int64)
b = (A @ s + e) % q
return (A, b), s
rng = np.random.default_rng(seed=0)
(A, b), s = keygen(4, 97, 8, 1, rng)
# Recompute A @ s with an explicit Python loop and verify that
# b - A s lives in the symmetric interval [-1, 1], which is the
# noise bound B = 1 set by the parameters.
As_loop = [sum(int(A[i, j]) * int(s[j]) for j in range(4)) % 97
for i in range(8)]
residual = [(int(b[i]) - As_loop[i]) % 97 for i in range(8)]
e_sym = [v - 97 if v > 48 else v for v in residual]
print("s =", s.tolist())
print("b - A s (symmetric) =", e_sym)
# ==> s = [82, 61, 49, 26]
# ==> b - A s (symmetric) = [-1, -1, 0, 0, 0, -1, -1, -1]

Encryption. Encrypt takes the public key (A,b)(A, b) and a message bit μ{0,1}\mu \in \{0, 1\} and returns a ciphertext (c1,c2)(c_1, c_2). The sender draws a random r{0,1}mr \in \{0, 1\}^m. The first coordinate is c1=Armodqc_1 = A^\top r \bmod q. The second coordinate is c2=br+q/2μmodqc_2 = b^\top r + \lfloor q/2 \rfloor \mu \bmod q. The ciphertext lives in Zqn×Zq\mathbb{Z}_q^n \times \mathbb{Z}_q.

import numpy as np
def encrypt(A, b, bit, q, rng):
m = b.shape[0]
r = rng.integers(0, 2, size=m, dtype=np.int64)
half_q = q // 2
c1 = (A.T @ r) % q
c2 = (int(b @ r) + half_q * bit) % q
return c1, int(c2)
# Replay the seeded keygen so this block is self-contained.
rng = np.random.default_rng(seed=0)
n, q, m, B = 4, 97, 8, 1
s = rng.integers(0, q, size=n, dtype=np.int64)
A = rng.integers(0, q, size=(m, n), dtype=np.int64)
e = rng.integers(-B, B + 1, size=m, dtype=np.int64)
b = (A @ s + e) % q
c1_one, c2_one = encrypt(A, b, 1, q, rng)
print("mu = 1: c1 =", c1_one.tolist(), "c2 =", c2_one)
# ==> mu = 1: c1 = [44, 50, 54, 74] c2 = 21

Decryption. Decrypt takes the secret key ss and a ciphertext (c1,c2)(c_1, c_2) and returns the decoded bit. It computes v=(c2c1s)modqv = (c_2 - c_1^\top s) \bmod q and returns (2v+q/2)/qmod2\lfloor (2 v + \lfloor q/2 \rfloor) / q \rfloor \bmod 2. The expression is the nearest-codeword decoder for {0,q/2}\{0, \lfloor q/2 \rfloor\} applied to vv.

import numpy as np
def decrypt(s, c1, c2, q):
v = (int(c2) - int(c1 @ s)) % q
half_q = q // 2
return ((2 * v + half_q) // q) % 2
# Replay keygen and produce ciphertexts for both message bits with
# the same random r, so the only difference is the encoding shift.
rng = np.random.default_rng(seed=0)
n, q, m, B = 4, 97, 8, 1
s = rng.integers(0, q, size=n, dtype=np.int64)
A = rng.integers(0, q, size=(m, n), dtype=np.int64)
e = rng.integers(-B, B + 1, size=m, dtype=np.int64)
b = (A @ s + e) % q
r = rng.integers(0, 2, size=m, dtype=np.int64)
c1 = (A.T @ r) % q
c2_one = (int(b @ r) + (q // 2) * 1) % q
c2_zero = (int(b @ r) + (q // 2) * 0) % q
print("decrypt mu = 1 ->", decrypt(s, c1, c2_one, q))
print("decrypt mu = 0 ->", decrypt(s, c1, c2_zero, q))
# ==> decrypt mu = 1 -> 1
# ==> decrypt mu = 0 -> 0

The secret-cancellation identity. Start from the definitions c1=Armodqc_1 = A^\top r \bmod q and c2=br+q/2μmodqc_2 = b^\top r + \lfloor q/2 \rfloor \mu \bmod q. Substituting b=As+eb = A s + e and expanding gives

c2c1s=(As+e)r+q/2μ(Ar)s.c_2 - c_1^\top s = (A s + e)^\top r + \lfloor q/2 \rfloor \mu - (A^\top r)^\top s.

The expansion (As+e)r=(As)r+er=rAs+er(A s + e)^\top r = (A s)^\top r + e^\top r = r^\top A s + e^\top r uses distributivity and the matrix transpose identity. The subtracted term (Ar)s(A^\top r)^\top s equals rAsr^\top A s by the same identity. Cancelling the two rAsr^\top A s summands leaves

c2c1sq/2μ+er(modq).c_2 - c_1^\top s \equiv \lfloor q/2 \rfloor \mu + e^\top r \pmod q.

The decryption input is therefore the encoded bit plus a small noise term, viewed modulo qq. The multiple of qq vanishes in Zq\mathbb{Z}_q before the decoder runs.

The noise budget. The decoder returns μ\mu correctly whenever q/2μ+er\lfloor q/2 \rfloor \mu + e^\top r lies inside the decoding region of the intended codeword. The two regions are separated by q/2\lfloor q/2 \rfloor on the cycle, and because qq is odd they are not perfectly symmetric: the bit-00 region contains q/2\lceil q/2 \rceil residues and the bit-11 region contains q/2\lfloor q/2 \rfloor residues. A clean symmetric sufficient condition that works for both message bits and both signs of the noise is

2er<q/2.2 \, |e^\top r| < \lfloor q/2 \rfloor.

For large qq the right side is essentially q/2q/2, and the condition is often written in the asymptotic form er<q/4|e^\top r| < q/4. For q=97q = 97 the symmetric form gives er23|e^\top r| \leq 23. The actual decoder boundary differs by at most one residue depending on the sign of the noise and the message bit. The symmetric form is the worst-case correctness condition used in the rest of this chapter.

The elementary bound gives ermB|e^\top r| \leq m \cdot B for r{0,1}mr \in \{0, 1\}^m and eB\|e\|_\infty \leq B. Combining the two inequalities produces the bounded-uniform correctness budget

2mB<q/2,2 \, m B < \lfloor q/2 \rfloor,

which is sufficient for every honest encryption to decrypt correctly. Regev’s original paper states correctness probabilistically through the distribution of sums of errors χk\chi^{*k} rather than through a deterministic worst-case bound (Regev, 2009). The deterministic form above is the bounded-uniform analogue used by the toy code in this chapter.

At the toy parameters (n,q,m,B)=(4,97,8,1)(n, q, m, B) = (4, 97, 8, 1) the left side is 1616 and the right side is 4848, so the budget has a factor-of-three margin. At the parameters (n,q,m,B)=(4,13,8,1)(n, q, m, B) = (4, 13, 8, 1) the left side is still 1616 but the right side is 66, so the budget is violated. The exact distribution of ere^\top r at those parameters predicts a failure rate of about 5%5\%. The code block below shows an empirical rate of 6.75%6.75\% across 200200 seeds with both message bits encrypted. A sample of 200200 seeds is small for a 5%5\%-rate event, so the empirical and analytical rates differ within typical sample-size variance.

import numpy as np
def keygen(n, q, m, B, rng):
s = rng.integers(0, q, size=n, dtype=np.int64)
A = rng.integers(0, q, size=(m, n), dtype=np.int64)
e = rng.integers(-B, B + 1, size=m, dtype=np.int64)
return (A, (A @ s + e) % q), s
def encrypt(A, b, bit, q, rng):
m = b.shape[0]
r = rng.integers(0, 2, size=m, dtype=np.int64)
half_q = q // 2
return (A.T @ r) % q, (int(b @ r) + half_q * bit) % q
def decrypt(s, c1, c2, q):
v = (int(c2) - int(c1 @ s)) % q
half_q = q // 2
return ((2 * v + half_q) // q) % 2
def failure_rate(n, q, m, B, num_seeds):
failures = 0
for seed in range(num_seeds):
rng = np.random.default_rng(seed=seed)
(A, b), s = keygen(n, q, m, B, rng)
for bit in (0, 1):
c1, c2 = encrypt(A, b, bit, q, rng)
if decrypt(s, c1, c2, q) != bit:
failures += 1
return failures / (2 * num_seeds)
print("feasible (n=4, q=97, m=8, B=1):", failure_rate(4, 97, 8, 1, 200))
print("infeasible (n=4, q=13, m=8, B=1):", failure_rate(4, 13, 8, 1, 200))
# ==> feasible (n=4, q=97, m=8, B=1): 0.0
# ==> infeasible (n=4, q=13, m=8, B=1): 0.0675

The Ring-LWE / LPR-style descendant. Replacing the flat LWE sample (A,b)(A, b) with a Ring-LWE sample (a,b)(a, b) in Rq×RqR_q \times R_q keeps the same cancellation idea but changes the shape of the ciphertext. The construction is no longer literally Regev. Instead of a random {0,1}\{0, 1\}-subset sum of many flat LWE samples, it works over RqR_q with short polynomial secrets and adds fresh encryption errors e1,e2Rqe_1, e_2 \in R_q.

The public key is a single polynomial aRqa \in R_q together with b=as+eb = a \cdot s + e in RqR_q. Encryption draws a random short polynomial rr and the two fresh short errors e1,e2e_1, e_2. The ciphertext is (c1,c2)=(ar+e1,br+e2+q/2m)(c_1, c_2) = (a \cdot r + e_1, b \cdot r + e_2 + \lfloor q/2 \rfloor \mathbf{m}). The message polynomial mRq\mathbf{m} \in R_q encodes up to nn bits as coefficients in {0,1}\{0, 1\}. Decryption computes c2c1s=q/2m+er+e2e1sc_2 - c_1 \cdot s = \lfloor q/2 \rfloor \mathbf{m} + e \cdot r + e_2 - e_1 \cdot s, and the noise term is now two small polynomial products plus the fresh error e2e_2. This two-element scheme is the one the journal version of the Ring-LWE paper presents. The conference version’s example cryptosystem is the dual-style scheme with about logq+1\log q + 1 ring elements per ciphertext (Lyubashevsky et al., 2013, sec. 1). Chapter 11 walks the Module-LWE refinement that ML-KEM uses and the Fujisaki-Okamoto transform that FIPS 203 applies to its component PKE, yielding a KEM that §3.2 says is believed to satisfy IND-CCA2 security (National Institute of Standards and Technology, 2024).

In the IND-CPA game, the adversary receives the public key, submits two equal-length messages m0m_0 and m1m_1 of its choice, receives an encryption of mbm_b for a uniformly random bit bb, and must guess bb. The scheme is IND-CPA secure if every polynomial-time adversary guesses correctly with probability at most 1/2+negl(λ)1/2 + \text{negl}(\lambda), where λ\lambda is the security parameter (Boneh & Shoup, 2023). For Regev PKE the two messages are the bit 00 and the bit 11, so the game reduces to: given (A,b)(A, b) and an encryption of a random bit μ\mu, guess μ\mu.

Security reduces to decisional LWE (DLWE) through a hybrid argument over three games, each changing one thing in the adversary’s view. Write Advi\text{Adv}_i for the adversary’s advantage in Hybrid ii.

HybridPublic keyChallenge ciphertextReached by
0b=As+eb = A s + e(Ar, br+q/2μ)(A^\top r,\ b^\top r + \lfloor q/2 \rfloor \mu)the real scheme
1uu uniform in Zqm\mathbb{Z}_q^m(Ar, ur+q/2μ)(A^\top r,\ u^\top r + \lfloor q/2 \rfloor \mu)decisional LWE
2uu uniform in Zqm\mathbb{Z}_q^muniform in Zqn×Zq\mathbb{Z}_q^n \times \mathbb{Z}_qthe leftover hash lemma

Hybrid 0 to Hybrid 1. A distinguisher DD for the two hybrids gives a DLWE distinguisher. On input (A,y)(A, y) with yy either As+eA s + e or uniform in Zqm\mathbb{Z}_q^m, DD installs (A,y)(A, y) as the public key. It then samples the IND-CPA challenge bit β\beta itself, forms the challenge ciphertext using yy (encryption does not need the secret), gives the ciphertext to the adversary, and outputs 11 iff the adversary’s final guess equals β\beta. If y=As+ey = A s + e the simulation is exactly Hybrid 0; if yy is uniform it is Hybrid 1. Any non-negligible gap in the adversary’s success probability between the two hybrids therefore gives a DLWE distinguisher with the same advantage, which Chapter 8 assumes is negligible (Regev, 2009).

Hybrid 1 to Hybrid 2. The adversary’s advantage in Hybrid 2 is exactly zero, because a challenge ciphertext drawn uniformly and independently of μ\mu carries no information about μ\mu. The adversary’s view in Hybrid 1 is (A,u,c1,c2)=(A,u,Ar,ur+q/2μ)(A, u, c_1, c_2) = (A, u, A^\top r, u^\top r + \lfloor q/2 \rfloor \mu), with rr uniform in {0,1}m\{0, 1\}^m. Apply the leftover hash lemma to the joint hash r[Au]rZqn+1r \mapsto [A \,\|\, u]^\top r \in \mathbb{Z}_q^{n+1}. For uniform AZqm×nA \in \mathbb{Z}_q^{m \times n} and uniform uZqmu \in \mathbb{Z}_q^m, the joint output is ϵ\epsilon-close to uniform on Zqn+1\mathbb{Z}_q^{n+1} when m(n+1)log2q+2log2(1/ϵ)m \geq (n + 1) \log_2 q + 2 \log_2 (1/\epsilon) (Boneh & Shoup, 2023; Regev, 2009). The shift by q/2μ\lfloor q/2 \rfloor \mu preserves uniformity, so the Hybrid 1 view is ϵ\epsilon-close to the Hybrid 2 view and the advantage gap Adv1Adv2|\text{Adv}_1 - \text{Adv}_2| is at most ϵ\epsilon.

Chaining the Hybrid 0 to Hybrid 1 to Hybrid 2 transitions bounds the real-world advantage: Adv0AdvDLWE+ϵLHL\text{Adv}_0 \leq \text{Adv}_\text{DLWE} + \epsilon_\text{LHL}, where AdvDLWE\text{Adv}_\text{DLWE} is the distinguishing advantage against decisional LWE and ϵLHL\epsilon_\text{LHL} is the leftover hash lemma statistical distance. Both are negligible in λ\lambda under the standard Regev parameterization, so the scheme is IND-CPA secure (Boneh & Shoup, 2023; Regev, 2009).

The proof establishes IND-CPA security only. An IND-CCA2 adversary gets an additional decryption oracle that it can query on any ciphertext except the challenge. Such an adversary can learn partial information about the secret through malformed ciphertexts, and the raw Regev scheme is not IND-CCA2 secure. Chapter 11 moves from PKE to KEMs and adds the Fujisaki-Okamoto-style transform used by ML-KEM: decapsulation decrypts the ciphertext, derives the encryption randomness again from the decrypted value, re-encrypts, and compares the recomputed ciphertext to the received one. On mismatch, ML-KEM performs implicit rejection and returns a pseudorandom fallback key derived from a stored secret rather than an explicit failure symbol. The resulting KEM is believed to satisfy IND-CCA2 under the stated module-lattice assumptions and the FO-style random-oracle / quantum-random-oracle modeling assumptions. Chapter 11 walks that construction and states the Hofheinz-Hovelmanns-Kiltz theorem’s assumptions. Their random-oracle reduction for the implicit-rejection transform is the one FIPS 203 cites, and it builds on the original Fujisaki-Okamoto transform (Fujisaki & Okamoto, 1999; Hofheinz et al., 2017, sec. 3; National Institute of Standards and Technology, 2024, sec. 3.2). Their quantum-random-oracle theorems are proved for a variant that appends a confirmation hash to the ciphertext, which ML-KEM does not carry, so the QROM analysis of ML-KEM’s own transform is not in their paper (Hofheinz et al., 2017, sec. 4).

Regev’s original flat construction uses only matrix-vector arithmetic over Zq\mathbb{Z}_q, which is why the noise budget derivation and the IND-CPA proof in this chapter both fit in a page (Regev, 2009). The Ring-LWE framing from Chapter 9 improves public-key size and amortized bandwidth by replacing unstructured flat-LWE linear algebra with structured multiplication by ring elements in RqR_q. A single ring element aRqa \in R_q acts as a negacyclic convolution operator, so an entire n×nn \times n structured linear map is carried by one polynomial.

A strict single-bit comparison is closer to n+1n + 1 field elements (flat Regev for one bit) versus 2n2 n field elements (the two-element Ring-LWE ciphertext) (Lyubashevsky et al., 2013, sec. 1). The structural gain comes from public-key compression, faster polynomial arithmetic, and message packing of up to nn bits per ciphertext. The cost is a security assumption that restricts to ideal lattices (Lyubashevsky et al., 2010).

Chapter 11 uses Module-LWE, where the secret is a vector of kk ring elements in RqR_q. At k=1k = 1 this is Ring-LWE; at ring dimension n=1n = 1 this is flat LWE. ML-KEM chooses kk and nn to match a target bandwidth and security level (National Institute of Standards and Technology, 2024). Chapter 13 walks primal and dual lattice attacks against the public LWE samples such a scheme’s key material exposes, and estimates their cost under a stated attack and cost model. It reads those estimates against the published category assessments rather than deriving a security level for arbitrary decisional-LWE parameters.

Chapter 11 is the immediate next step: it rebuilds key generation, encryption, and decryption over Module-LWE, then adds the Fujisaki-Okamoto transform that turns an IND-CPA scheme like this one into a KEM believed to satisfy IND-CCA2 (Hofheinz et al., 2017), standardized as ML-KEM (National Institute of Standards and Technology, 2024).

  1. Modify RegevParams to accept (n=4,q=13,m=8,B=1)(n = 4, q = 13, m = 8, B = 1) and measure the decryption failure rate across 1000 seeds with both message bits. Compare the observed rate against the analytical failure rate computed from the exact distribution of ere^\top r at those parameters.

  2. Extend the single-bit scheme to encrypt a kk-bit plaintext by running kk independent ciphertexts with the same public key. Report the ciphertext expansion factor in Zq\mathbb{Z}_q entries per plaintext bit.

  3. Re-derive the noise budget for rr drawn uniformly from {1,0,1}m\{-1, 0, 1\}^m instead of {0,1}m\{0, 1\}^m. Show that the worst-case bound remains ermB|e^\top r| \leq m B, while the typical size increases by a constant factor because ternary rr is nonzero with probability 2/32/3 rather than 1/21/2. The signs make the sum symmetric but do not by themselves increase the variance when the error distribution is already symmetric.

  4. Explain in two sentences why replacing the encoding shift q/2\lfloor q/2 \rfloor with q/3\lfloor q/3 \rfloor breaks the existing nearest-codeword decoder for {0,q/2}\{0, \lfloor q/2 \rfloor\}. State the new noise bound that would make a redesigned nearest-codeword decoder for {0,q/3}\{0, \lfloor q/3 \rfloor\} work. Show that it tightens to roughly er<q/6|e^\top r| < q/6.

Worked solutions and editorial notes for these exercises are in Appendix D, Chapter 10. A separate track, for rebuilding rather than reading: the package exercises/ch10-regev-pke has every function the chapter teaches replaced by a stub. Run PQC_IMPL=exercises pytest tests/ch10 to grade your version against the suite that proves the reference one.

Boneh, D., & Shoup, V. (2023). A Graduate Course in Applied Cryptography (v0.6). Free online textbook. https://toc.cryptobook.us/
Fujisaki, E., & Okamoto, T. (1999). Secure integration of asymmetric and symmetric encryption schemes. Advances in Cryptology – CRYPTO 1999, 1666, 537–554. https://doi.org/10.1007/3-540-48405-1_34
Hofheinz, D., Hövelmanns, K., & Kiltz, E. (2017). A modular analysis of the Fujisaki-Okamoto transformation. Theory of Cryptography – TCC 2017, Part I, 10677, 341–371. https://doi.org/10.1007/978-3-319-70500-2_12
Lyubashevsky, V., Peikert, C., & Regev, O. (2010). On ideal lattices and learning with errors over rings. In H. Gilbert (Ed.), Advances in Cryptology – EUROCRYPT 2010 (Vol. 6110, pp. 1–23). Springer. https://doi.org/10.1007/978-3-642-13190-5_1
Lyubashevsky, V., Peikert, C., & Regev, O. (2013). On ideal lattices and learning with errors over rings. Journal of the ACM, 60(6), 43:1-43:35. https://doi.org/10.1145/2535925
National Institute of Standards and Technology. (2024). FIPS 203: Module-Lattice-Based Key-Encapsulation Mechanism Standard. Federal Information Processing Standards Publication. https://doi.org/10.6028/NIST.FIPS.203
Regev, O. (2009). On lattices, learning with errors, random linear codes, and cryptography. Journal of the ACM, 56(6), 34:1-34:40. https://doi.org/10.1145/1568318.1568324

Last updated: