Appendix D: Solutions for Chapter 9
This page collects solutions and editorial notes for the exercises in Chapter 9: Ring-LWE and Module-LWE. Compute and derivation exercises have worked solutions; open-ended exercises have an editorial note describing what a strong answer addresses.
The fuller versions of these routines are in the ring_lwe package under solutions/ch09-ring-lwe. From a clone of the companion repository, pytest tests/ch09 runs its suite. Appendix C has the setup.
Exercise 1
Section titled “Exercise 1”The exercise walks the calculation. Key checks: , and . The negacyclic minus sign is the difference between this ring and the cyclic ring . In the cyclic ring rather than .
def ring_mul(f, g, n=8, q=17): out = [0] * n for i in range(n): for j in range(n): k = i + j sign = 1 if k < n else -1 out[k % n] = (out[k % n] + sign * f[i] * g[j]) % q return out
f = [0] * 8; f[5] = 1 # x^5g4 = [0] * 8; g4[4] = 1 # x^4g3 = [0] * 8; g3[3] = 1 # x^3print(ring_mul(f, g4))print(ring_mul(f, g3))# ==> [0, 16, 0, 0, 0, 0, 0, 0]# ==> [16, 0, 0, 0, 0, 0, 0, 0]Exercise 2
Section titled “Exercise 2”The negacyclic NTT evaluates at the odd powers of , where is a primitive -th root of unity in . For , the evaluation is , which gives the sequence modulo . With : , , , .
print([pow(2, 2 * k + 1, 17) for k in range(4)])# ==> [2, 8, 15, 9]Exercise 3
Section titled “Exercise 3”The verification is a transcription of the Module-LWE definition. For each output row , recompute in the ring , then assert termwise equality with the package-returned . The test in tests/ch09/test_module_collapses_to_ring.py does this for and is the reference. Writing it yourself reinforces where the two indices go. The rows are the samples; the columns are the components of the secret. At each row is a single ring product, so the rows are Ring-LWE samples sharing one secret. At each row sums ring products against a rank- secret, and those terms are never observed separately. At this exercise’s that is four samples, not three.
Exercise 4
Section titled “Exercise 4”The condition is the existence requirement for a primitive -th root of unity in , which has order . The primes below 200 satisfying this are . Once is found, the order check rules out roots of order 1, 2, 4, or 8. The order must divide 16 and be larger than 8, so the order is 16.
def is_prime(n): return n > 1 and all(n % i for i in range(2, int(n ** 0.5) + 1))
primes = [q for q in range(3, 200) if is_prime(q) and (q - 1) % 16 == 0]print(primes)# ==> [17, 97, 113, 193]Exercise 5
Section titled “Exercise 5”The identity holds at because , so reducing modulo does not change a polynomial’s value at . What changes is the error’s image. With each , takes every integer value from to , so the widened window holds values, more than the elements of . Every residual passes, and the test keeps all guesses on Ring-LWE samples and on uniform ones alike. It distinguishes nothing. The other roots, , and , behave the same way, because their powers are , , and up to sign and order.
What made useful is that every power of is , so is a plain sum of short coefficients and stays within of , which is residues out of . No root of is , since , and when the roots it does have are the odd powers of at which the NTT evaluates. The window test needs a root at which a short error keeps a short image. That claim is about this test only. At the toy parameters still lands on some residues more often than others, so a test that counts how often each residual appears could separate the two kinds of sample, given enough of them.