Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
duyuefeng0708
GitHub Repository: duyuefeng0708/Cryptography-From-First-Principle
Path: blob/main/scripts/gen_notebook_stubs.py
483 views
unlisted
1
#!/usr/bin/env python3
2
"""Generate SageMath Jupyter notebook stubs for every module in the crypto teaching repo.
3
4
Usage:
5
python3 scripts/gen_notebook_stubs.py
6
7
Generates .ipynb files under each module's sage/ directory. Safe to re-run:
8
existing files are overwritten with identical content (idempotent).
9
10
No external dependencies beyond the Python 3 standard library.
11
"""
12
13
import json
14
import os
15
from pathlib import Path
16
17
# ---------------------------------------------------------------------------
18
# Repository root (one level up from scripts/)
19
# ---------------------------------------------------------------------------
20
REPO_ROOT = Path(__file__).resolve().parent.parent
21
22
# ---------------------------------------------------------------------------
23
# Complete module / notebook specification (69 notebooks, 12 modules)
24
#
25
# Structure:
26
# key = relative path from repo root to the module directory
27
# value = list of (filename_stem, title, description, sections)
28
# sections = list of (heading, sagemath_code_comment)
29
# ---------------------------------------------------------------------------
30
MODULES: dict[str, list[tuple[str, str, str, list[tuple[str, str]]]]] = {
31
# ------------------------------------------------------------------
32
# foundations/01 (6 notebooks)
33
# ------------------------------------------------------------------
34
"foundations/01-modular-arithmetic-groups": [
35
(
36
"01a-integers-and-division",
37
"Integers and Division",
38
"Review division algorithm, divmod, remainders",
39
[
40
("The Division Algorithm",
41
"# Explore the division algorithm: a = q*b + r\n"
42
"a, b = 37, 7\n"
43
"q, r = divmod(a, b)\n"
44
"print(f'{a} = {q}*{b} + {r}')"),
45
("Remainders and Congruence",
46
"# Compute remainders for various values and observe patterns\n"
47
"for k in range(20):\n"
48
" print(f'{k} mod 7 = {k % 7}')"),
49
("Computing with SageMath",
50
"# Use SageMath's Mod and divmod for exact arithmetic\n"
51
"print(divmod(100, 13))\n"
52
"print(Mod(100, 13))"),
53
],
54
),
55
(
56
"01b-modular-arithmetic",
57
"Modular Arithmetic",
58
"Congruences, Zmod(n), addition/multiplication tables",
59
[
60
("Congruence Classes",
61
"# Create the ring Z/12Z and inspect its elements\n"
62
"R = Zmod(12)\n"
63
"print(list(R))"),
64
("Addition and Multiplication Tables",
65
"# Build and display the addition table for Z/7Z\n"
66
"R = Zmod(7)\n"
67
"R.addition_table('elements')"),
68
("Patterns in Multiplication",
69
"# Display the multiplication table and look for zero divisors\n"
70
"R = Zmod(12)\n"
71
"R.multiplication_table('elements')"),
72
],
73
),
74
(
75
"01c-groups-first-look",
76
"Groups: A First Look",
77
"Group axioms from Z_n examples, closure/identity/inverse",
78
[
79
("Closure and the Binary Operation",
80
"# Verify closure: adding any two elements of Z_7 stays in Z_7\n"
81
"R = Zmod(7)\n"
82
"a, b = R(3), R(5)\n"
83
"print(a + b, a + b in R)"),
84
("Identity and Inverses",
85
"# Find the additive identity and inverses in Z_7\n"
86
"R = Zmod(7)\n"
87
"for x in R:\n"
88
" print(f'inverse of {x} is {-x}')"),
89
("Checking the Group Axioms",
90
"# Verify associativity for a sample triple\n"
91
"R = Zmod(7)\n"
92
"a, b, c = R(2), R(4), R(6)\n"
93
"assert (a + b) + c == a + (b + c)"),
94
],
95
),
96
(
97
"01d-cyclic-groups-generators",
98
"Cyclic Groups and Generators",
99
"Generators, multiplicative_order(), powers",
100
[
101
("Powers of an Element",
102
"# Compute successive powers of g in (Z/7Z)*\n"
103
"R = Zmod(7)\n"
104
"g = R(3)\n"
105
"print([g^k for k in range(7)])"),
106
("Generators and Orders",
107
"# Find the multiplicative order of each unit in Z/7Z\n"
108
"R = Zmod(7)\n"
109
"for x in R.list_of_elements_of_multiplicative_group():\n"
110
" print(f'ord({x}) = {Mod(x,7).multiplicative_order()}')"),
111
("Identifying Generators",
112
"# A generator has order equal to the group size\n"
113
"R = Zmod(7)\n"
114
"phi = euler_phi(7)\n"
115
"gens = [x for x in range(1,7) if Mod(x,7).multiplicative_order() == phi]\n"
116
"print('Generators:', gens)"),
117
],
118
),
119
(
120
"01e-subgroups-lagrange",
121
"Subgroups and Lagrange's Theorem",
122
"Subgroup detection, Lagrange verification",
123
[
124
("Finding Subgroups",
125
"# List all subgroups of Z/12Z (additive)\n"
126
"G = Zmod(12)\n"
127
"# Generate subgroup from element 4\n"
128
"elem = G(4)\n"
129
"subgroup = set()\n"
130
"x = elem\n"
131
"while x not in subgroup:\n"
132
" subgroup.add(x)\n"
133
" x = x + elem\n"
134
"print(sorted(subgroup))"),
135
("Lagrange's Theorem",
136
"# Verify that subgroup order divides group order\n"
137
"group_order = 12\n"
138
"for d in divisors(group_order):\n"
139
" print(f'{d} divides {group_order}: {group_order % d == 0}')"),
140
("Cosets and Counting",
141
"# Compute cosets of a subgroup in Z/12Z\n"
142
"# TODO: build cosets and verify equal partition"),
143
],
144
),
145
(
146
"01f-group-visualization",
147
"Visualizing Group Structure",
148
"Cayley graphs, subgroup lattices",
149
[
150
("Cayley Graphs",
151
"# Visualize Z/6Z with generator 1\n"
152
"G = Zmod(6)\n"
153
"# TODO: build and plot Cayley graph"),
154
("Subgroup Lattice Diagrams",
155
"# Draw the subgroup lattice of Z/12Z\n"
156
"# TODO: use SageMath poset for subgroup lattice"),
157
("Color-Coded Multiplication Tables",
158
"# Create a color-coded multiplication table\n"
159
"# TODO: matplotlib heatmap of Z/7Z multiplication"),
160
],
161
),
162
],
163
164
# ------------------------------------------------------------------
165
# foundations/02 (6 notebooks)
166
# ------------------------------------------------------------------
167
"foundations/02-rings-fields-polynomials": [
168
(
169
"02a-what-is-a-ring",
170
"What Is a Ring?",
171
"Ring axioms, Z as ring, two operations",
172
[
173
("Ring Axioms",
174
"# Verify ring axioms for Z: additive group + multiplicative monoid\n"
175
"# Distributive: a*(b+c) == a*b + a*c\n"
176
"a, b, c = 3, 5, 7\n"
177
"assert a*(b+c) == a*b + a*c"),
178
("Z as a Ring",
179
"# SageMath's ZZ is the ring of integers\n"
180
"R = ZZ\n"
181
"print(R)\n"
182
"print(R.is_commutative())"),
183
("Rings vs Groups",
184
"# A ring has TWO operations; a group has one\n"
185
"R = Zmod(12)\n"
186
"print('Zero:', R.zero())\n"
187
"print('One:', R.one())"),
188
],
189
),
190
(
191
"02b-integers-mod-n-as-ring",
192
"Integers Mod n as a Ring",
193
"Zmod(12), zero divisors, units",
194
[
195
("Building Z/nZ",
196
"# Create Z/12Z and inspect basic properties\n"
197
"R = Zmod(12)\n"
198
"print('Order:', R.order())\n"
199
"print('Is field?', R.is_field())"),
200
("Zero Divisors",
201
"# Find zero divisors in Z/12Z: a*b == 0 with a,b != 0\n"
202
"R = Zmod(12)\n"
203
"for a in R:\n"
204
" for b in R:\n"
205
" if a != 0 and b != 0 and a*b == 0:\n"
206
" print(f'{a} * {b} = 0')"),
207
("Units and the Unit Group",
208
"# The units of Z/12Z form a group under multiplication\n"
209
"R = Zmod(12)\n"
210
"units = [x for x in R if gcd(ZZ(x), 12) == 1]\n"
211
"print('Units:', units)"),
212
],
213
),
214
(
215
"02c-polynomial-rings",
216
"Polynomial Rings",
217
"PolynomialRing(), degree, evaluation",
218
[
219
("Creating Polynomial Rings",
220
"# Build a polynomial ring over the rationals\n"
221
"R.<x> = PolynomialRing(QQ)\n"
222
"f = x^3 - 2*x + 1\n"
223
"print(f, 'degree:', f.degree())"),
224
("Polynomial Arithmetic",
225
"# Add, multiply, and divide polynomials\n"
226
"R.<x> = PolynomialRing(QQ)\n"
227
"f = x^2 + 1\n"
228
"g = x - 1\n"
229
"print('f*g =', f*g)\n"
230
"print('divmod:', f.quo_rem(g))"),
231
("Evaluation and Roots",
232
"# Evaluate a polynomial and find its roots\n"
233
"R.<x> = PolynomialRing(QQ)\n"
234
"f = x^2 - 5*x + 6\n"
235
"print('f(2) =', f(2))\n"
236
"print('roots:', f.roots())"),
237
],
238
),
239
(
240
"02d-what-is-a-field",
241
"What Is a Field?",
242
"Every element invertible, Z_p is field iff p prime",
243
[
244
("Field Definition",
245
"# A field is a commutative ring where every nonzero element is a unit\n"
246
"F = GF(7)\n"
247
"print('Is field?', F.is_field())"),
248
("Z_p Is a Field When p Is Prime",
249
"# Check: Zmod(p) is a field iff p is prime\n"
250
"for n in range(2, 16):\n"
251
" R = Zmod(n)\n"
252
" print(f'Z/{n}Z is field: {R.is_field()}, {n} is prime: {is_prime(n)}')"),
253
("Inverses in a Prime Field",
254
"# Every nonzero element of GF(p) has a multiplicative inverse\n"
255
"F = GF(7)\n"
256
"for a in F:\n"
257
" if a != 0:\n"
258
" print(f'{a}^(-1) = {a^(-1)}')"),
259
],
260
),
261
(
262
"02e-polynomial-division-irreducibility",
263
"Polynomial Division and Irreducibility",
264
"divmod for polys, is_irreducible()",
265
[
266
("Polynomial Long Division",
267
"# Divide f by g and get quotient + remainder\n"
268
"R.<x> = PolynomialRing(QQ)\n"
269
"f = x^4 + x^2 + 1\n"
270
"g = x^2 + x + 1\n"
271
"q, r = f.quo_rem(g)\n"
272
"print(f'q={q}, r={r}')"),
273
("Irreducibility Testing",
274
"# Check which polynomials over GF(2) are irreducible\n"
275
"R.<x> = PolynomialRing(GF(2))\n"
276
"for f in [x^2+x+1, x^2+1, x^3+x+1, x^3+x^2+1]:\n"
277
" print(f'{f}: irreducible = {f.is_irreducible()}')"),
278
("Factoring Polynomials",
279
"# Factor polynomials over different base rings\n"
280
"R.<x> = PolynomialRing(GF(2))\n"
281
"f = x^4 + 1\n"
282
"print(f.factor())"),
283
],
284
),
285
(
286
"02f-quotient-rings",
287
"Quotient Rings and Field Extensions",
288
"quotient(), building GF(4) from F_2[x]",
289
[
290
("Quotient Ring Construction",
291
"# Build GF(4) as F_2[x] / (x^2 + x + 1)\n"
292
"R.<x> = PolynomialRing(GF(2))\n"
293
"S.<a> = R.quotient(x^2 + x + 1)\n"
294
"print(list(S))"),
295
("Arithmetic in the Quotient",
296
"# Multiply elements in GF(4)\n"
297
"R.<x> = PolynomialRing(GF(2))\n"
298
"S.<a> = R.quotient(x^2 + x + 1)\n"
299
"print(f'a * (a+1) = {a * (a+1)}')"),
300
("Verifying Field Properties",
301
"# Check that our quotient ring is actually a field\n"
302
"R.<x> = PolynomialRing(GF(2))\n"
303
"S.<a> = R.quotient(x^2 + x + 1)\n"
304
"print('Is field?', S.is_field())"),
305
],
306
),
307
],
308
309
# ------------------------------------------------------------------
310
# foundations/03 (6 notebooks)
311
# ------------------------------------------------------------------
312
"foundations/03-galois-fields-aes": [
313
(
314
"03a-binary-fields-gf2",
315
"The Binary Field GF(2)",
316
"XOR as addition, AND as multiplication",
317
[
318
("GF(2): The Smallest Field",
319
"# GF(2) has only 0 and 1\n"
320
"F = GF(2)\n"
321
"print('Elements:', list(F))"),
322
("XOR Is Addition",
323
"# Addition in GF(2) is XOR\n"
324
"F = GF(2)\n"
325
"for a in F:\n"
326
" for b in F:\n"
327
" print(f'{a} + {b} = {a+b}')"),
328
("AND Is Multiplication",
329
"# Multiplication in GF(2) is AND\n"
330
"F = GF(2)\n"
331
"for a in F:\n"
332
" for b in F:\n"
333
" print(f'{a} * {b} = {a*b}')"),
334
],
335
),
336
(
337
"03b-extension-fields-gf2n",
338
"Extension Fields GF(2^n)",
339
"Building GF(2^n) from polynomial quotient rings",
340
[
341
("From GF(2) to GF(2^n)",
342
"# Build GF(2^4) using an irreducible polynomial\n"
343
"F.<a> = GF(2^4)\n"
344
"print(F)\n"
345
"print('Modulus:', F.modulus())"),
346
("Elements as Polynomials",
347
"# Each element of GF(2^n) is a polynomial of degree < n\n"
348
"F.<a> = GF(2^4)\n"
349
"for x in F:\n"
350
" print(x, '->', x.polynomial())"),
351
("Arithmetic in GF(2^n)",
352
"# Add and multiply elements\n"
353
"F.<a> = GF(2^4)\n"
354
"x = a^3 + a + 1\n"
355
"y = a^2 + a\n"
356
"print(f'{x} + {y} = {x+y}')\n"
357
"print(f'{x} * {y} = {x*y}')"),
358
],
359
),
360
(
361
"03c-gf256-arithmetic",
362
"Arithmetic in GF(256)",
363
"AES irreducible polynomial, field operations",
364
[
365
("The AES Field: GF(2^8)",
366
"# AES uses GF(2^8) with irreducible polynomial x^8 + x^4 + x^3 + x + 1\n"
367
"R.<x> = PolynomialRing(GF(2))\n"
368
"p = x^8 + x^4 + x^3 + x + 1\n"
369
"print('Irreducible?', p.is_irreducible())"),
370
("Field Operations as Byte Manipulation",
371
"# Build GF(256) with the AES polynomial\n"
372
"F.<a> = GF(2^8, modulus=x^8 + x^4 + x^3 + x + 1)\n"
373
"print('Order:', F.order())"),
374
("Inverses and the Logarithm Table",
375
"# Compute multiplicative inverses in GF(256)\n"
376
"F.<a> = GF(2^8)\n"
377
"elem = a^6 + a^4 + a + 1 # some element\n"
378
"print(f'Inverse: {elem^(-1)}')\n"
379
"print(f'Check: {elem * elem^(-1)}')"),
380
],
381
),
382
(
383
"03d-aes-sbox-construction",
384
"The AES S-Box",
385
"Multiplicative inverse + affine transform",
386
[
387
("Multiplicative Inverse in GF(256)",
388
"# The first step of the S-Box: take the inverse in GF(2^8)\n"
389
"F.<a> = GF(2^8)\n"
390
"# Map byte to field element, invert, map back\n"
391
"# TODO: implement byte-to-field and field-to-byte conversion"),
392
("The Affine Transformation",
393
"# After inversion, apply a fixed affine map over GF(2)\n"
394
"# S(x) = A * x^(-1) + c (in GF(2)^8)\n"
395
"# TODO: implement the affine matrix and constant"),
396
("Building the Full S-Box Table",
397
"# Construct the 256-entry S-Box lookup table\n"
398
"# TODO: combine inverse + affine for all 256 inputs\n"
399
"# Expected S-Box[0x00] = 0x63, S-Box[0x01] = 0x7C"),
400
],
401
),
402
(
403
"03e-aes-mixcolumns-as-field-ops",
404
"AES MixColumns as Field Operations",
405
"Column polynomial multiplication",
406
[
407
("The MixColumns Matrix",
408
"# MixColumns multiplies each column by a fixed matrix in GF(2^8)\n"
409
"# Matrix entries: 02, 03, 01, 01 (circulant)\n"
410
"# TODO: define the MixColumns matrix over GF(2^8)"),
411
("Polynomial Representation",
412
"# Each column is a polynomial in GF(2^8)[x] / (x^4 + 1)\n"
413
"# MixColumns = multiply by c(x) = 03*x^3 + 01*x^2 + 01*x + 02\n"
414
"# TODO: implement polynomial multiplication mod x^4 + 1"),
415
("Step-by-Step Example",
416
"# Apply MixColumns to a sample column\n"
417
"# TODO: walk through one column transformation"),
418
],
419
),
420
(
421
"03f-full-aes-round",
422
"A Full AES Round",
423
"SubBytes + ShiftRows + MixColumns + AddRoundKey",
424
[
425
("SubBytes: Byte Substitution",
426
"# Apply the S-Box to every byte of the state\n"
427
"# TODO: apply S-Box lookup to 4x4 state matrix"),
428
("ShiftRows and MixColumns",
429
"# ShiftRows: cyclic left shift of each row\n"
430
"# MixColumns: matrix multiply each column\n"
431
"# TODO: implement ShiftRows and MixColumns"),
432
("AddRoundKey and Full Round",
433
"# XOR the state with the round key\n"
434
"# TODO: combine all four operations into one AES round"),
435
],
436
),
437
],
438
439
# ------------------------------------------------------------------
440
# foundations/04 (6 notebooks)
441
# ------------------------------------------------------------------
442
"foundations/04-number-theory-rsa": [
443
(
444
"04a-divisibility-gcd-euclid",
445
"Divisibility and the Euclidean Algorithm",
446
"gcd(), step-by-step Euclid",
447
[
448
("Divisibility",
449
"# Test divisibility and list divisors\n"
450
"n = 60\n"
451
"print('Divisors of', n, ':', divisors(n))"),
452
("The Euclidean Algorithm",
453
"# Step-by-step Euclidean algorithm\n"
454
"a, b = 252, 105\n"
455
"while b != 0:\n"
456
" print(f'{a} = {a//b}*{b} + {a%b}')\n"
457
" a, b = b, a % b\n"
458
"print('GCD:', a)"),
459
("Using SageMath's gcd()",
460
"# Verify with SageMath's built-in\n"
461
"print(gcd(252, 105))\n"
462
"print(gcd(48, 18))"),
463
],
464
),
465
(
466
"04b-extended-euclidean-algorithm",
467
"The Extended Euclidean Algorithm",
468
"xgcd(), Bezout, mod inverse",
469
[
470
("Bezout's Identity",
471
"# Find s, t such that gcd(a,b) = s*a + t*b\n"
472
"g, s, t = xgcd(252, 105)\n"
473
"print(f'gcd = {g}, s = {s}, t = {t}')\n"
474
"print(f'Verify: {s}*252 + {t}*105 = {s*252 + t*105}')"),
475
("Computing Modular Inverses",
476
"# The extended GCD gives us modular inverses\n"
477
"# Find 17^(-1) mod 43\n"
478
"g, s, _ = xgcd(17, 43)\n"
479
"print(f'17^(-1) mod 43 = {s % 43}')"),
480
("When Does an Inverse Exist?",
481
"# a has an inverse mod n iff gcd(a, n) = 1\n"
482
"n = 15\n"
483
"for a in range(1, n):\n"
484
" g = gcd(a, n)\n"
485
" inv = f'{inverse_mod(a, n)}' if g == 1 else 'none'\n"
486
" print(f'{a}: gcd={g}, inverse={inv}')"),
487
],
488
),
489
(
490
"04c-euler-totient-fermats-theorem",
491
"Euler's Totient and Fermat's Theorem",
492
"euler_phi(), power_mod()",
493
[
494
("Euler's Totient Function",
495
"# phi(n) counts integers 1..n coprime to n\n"
496
"for n in range(2, 21):\n"
497
" print(f'phi({n}) = {euler_phi(n)}')"),
498
("Fermat's Little Theorem",
499
"# a^(p-1) = 1 (mod p) for prime p, gcd(a,p)=1\n"
500
"p = 17\n"
501
"for a in range(1, p):\n"
502
" print(f'{a}^{p-1} mod {p} = {power_mod(a, p-1, p)}')"),
503
("Euler's Theorem",
504
"# a^phi(n) = 1 (mod n) when gcd(a,n)=1\n"
505
"n = 15\n"
506
"phi_n = euler_phi(n)\n"
507
"for a in range(1, n):\n"
508
" if gcd(a, n) == 1:\n"
509
" print(f'{a}^{phi_n} mod {n} = {power_mod(a, phi_n, n)}')"),
510
],
511
),
512
(
513
"04d-chinese-remainder-theorem",
514
"The Chinese Remainder Theorem",
515
"CRT_list(), solving congruences",
516
[
517
("Simultaneous Congruences",
518
"# Solve: x = 2 mod 3, x = 3 mod 5, x = 2 mod 7\n"
519
"x = CRT_list([2, 3, 2], [3, 5, 7])\n"
520
"print(f'x = {x}')\n"
521
"print(f'Check: {x%3}, {x%5}, {x%7}')"),
522
("CRT and Isomorphism",
523
"# Z/15Z is isomorphic to Z/3Z x Z/5Z when gcd(3,5)=1\n"
524
"for x in range(15):\n"
525
" print(f'{x} -> ({x%3}, {x%5})')"),
526
("CRT in RSA",
527
"# CRT speeds up RSA decryption by splitting mod n = p*q\n"
528
"# TODO: demonstrate CRT-based RSA decryption"),
529
],
530
),
531
(
532
"04e-rsa-key-generation",
533
"RSA Key Generation",
534
"random_prime(), step-by-step keygen",
535
[
536
("Generating Large Primes",
537
"# Use random_prime() to pick primes of a given size\n"
538
"p = random_prime(2^512)\n"
539
"q = random_prime(2^512)\n"
540
"print(f'p = {p}')\n"
541
"print(f'q = {q}')"),
542
("Computing the RSA Modulus",
543
"# n = p*q, phi(n) = (p-1)*(q-1)\n"
544
"p = random_prime(2^64)\n"
545
"q = random_prime(2^64)\n"
546
"n = p * q\n"
547
"phi_n = (p-1) * (q-1)\n"
548
"print(f'n = {n}')\n"
549
"print(f'phi(n) = {phi_n}')"),
550
("Choosing e and Computing d",
551
"# e = 65537 (standard), d = e^(-1) mod phi(n)\n"
552
"e = 65537\n"
553
"# Ensure gcd(e, phi_n) == 1\n"
554
"# d = inverse_mod(e, phi_n)\n"
555
"# TODO: complete keygen and output (n, e) and (n, d)"),
556
],
557
),
558
(
559
"04f-rsa-encryption-decryption",
560
"RSA Encryption and Decryption",
561
"Encrypt/decrypt, textbook RSA limits",
562
[
563
("Textbook RSA Encryption",
564
"# Encrypt: c = m^e mod n\n"
565
"# TODO: set up RSA parameters and encrypt a message"),
566
("Textbook RSA Decryption",
567
"# Decrypt: m = c^d mod n\n"
568
"# TODO: decrypt and verify we recover the original message"),
569
("Why Textbook RSA Is Insecure",
570
"# Textbook RSA is deterministic -> same plaintext = same ciphertext\n"
571
"# TODO: demonstrate the lack of semantic security"),
572
],
573
),
574
],
575
576
# ------------------------------------------------------------------
577
# foundations/05 (6 notebooks)
578
# ------------------------------------------------------------------
579
"foundations/05-discrete-log-diffie-hellman": [
580
(
581
"05a-the-discrete-log-problem",
582
"The Discrete Logarithm Problem",
583
"Definition, brute force, discrete_log()",
584
[
585
("The DLP Definition",
586
"# Given g, h in a group, find x such that g^x = h\n"
587
"p = 23\n"
588
"g = Mod(5, p)\n"
589
"x_secret = 13\n"
590
"h = g^x_secret\n"
591
"print(f'g={g}, h={h}, find x such that g^x = h')"),
592
("Brute Force Search",
593
"# Try all possible exponents (only feasible for small groups)\n"
594
"p = 23\n"
595
"g = Mod(5, p)\n"
596
"h = Mod(g^13, p)\n"
597
"for x in range(p):\n"
598
" if g^x == h:\n"
599
" print(f'Found: x = {x}')\n"
600
" break"),
601
("Using discrete_log()",
602
"# SageMath's built-in DLP solver\n"
603
"p = next_prime(10^6)\n"
604
"g = Mod(primitive_root(p), p)\n"
605
"x_secret = randint(2, p-2)\n"
606
"h = g^x_secret\n"
607
"x_found = discrete_log(h, g)\n"
608
"print(f'Secret: {x_secret}, Found: {x_found}')"),
609
],
610
),
611
(
612
"05b-primitive-roots-generators",
613
"Primitive Roots and Generators",
614
"primitive_root(), generator choice",
615
[
616
("Primitive Roots",
617
"# A primitive root of p generates all of (Z/pZ)*\n"
618
"p = 23\n"
619
"g = primitive_root(p)\n"
620
"print(f'Primitive root of {p}: {g}')"),
621
("Verifying a Generator",
622
"# g is a generator iff its order equals p-1\n"
623
"p = 23\n"
624
"g = Mod(5, p)\n"
625
"print(f'Order of {g}: {g.multiplicative_order()}')\n"
626
"print(f'Is generator: {g.multiplicative_order() == p-1}')"),
627
("Finding All Generators",
628
"# List all primitive roots of a prime\n"
629
"p = 23\n"
630
"gens = [g for g in range(1,p) if Mod(g,p).multiplicative_order() == p-1]\n"
631
"print(f'Generators of (Z/{p}Z)*: {gens}')"),
632
],
633
),
634
(
635
"05c-diffie-hellman-key-exchange",
636
"Diffie-Hellman Key Exchange",
637
"Full DH exchange simulation",
638
[
639
("Public Parameters",
640
"# Alice and Bob agree on a prime p and generator g\n"
641
"p = next_prime(10^20)\n"
642
"g = Mod(primitive_root(p), p)\n"
643
"print(f'p = {p}')\n"
644
"print(f'g = {g}')"),
645
("Key Exchange Protocol",
646
"# Alice picks a, sends g^a; Bob picks b, sends g^b\n"
647
"a = randint(2, p-2) # Alice's secret\n"
648
"b = randint(2, p-2) # Bob's secret\n"
649
"A = g^a # Alice sends this\n"
650
"B = g^b # Bob sends this\n"
651
"print(f'Alice sends: {A}')\n"
652
"print(f'Bob sends: {B}')"),
653
("Shared Secret",
654
"# Both compute the same shared secret\n"
655
"# Alice: B^a = g^(ba), Bob: A^b = g^(ab)\n"
656
"shared_alice = B^a\n"
657
"shared_bob = A^b\n"
658
"print(f'Alice computes: {shared_alice}')\n"
659
"print(f'Bob computes: {shared_bob}')\n"
660
"assert shared_alice == shared_bob"),
661
],
662
),
663
(
664
"05d-computational-hardness-cdh-ddh",
665
"CDH and DDH Assumptions",
666
"Computational vs decisional hardness",
667
[
668
("The CDH Assumption",
669
"# Given g, g^a, g^b, it is hard to compute g^(ab)\n"
670
"# This is what makes Diffie-Hellman secure\n"
671
"p = next_prime(10^20)\n"
672
"g = Mod(primitive_root(p), p)\n"
673
"a, b = randint(2, p-2), randint(2, p-2)\n"
674
"print(f'g^a = {g^a}')\n"
675
"print(f'g^b = {g^b}')\n"
676
"print(f'g^(ab) = {g^(a*b)} <- hard to compute without a or b')"),
677
("The DDH Assumption",
678
"# Given g, g^a, g^b, g^c: is c = ab mod (p-1)?\n"
679
"# DDH says this is indistinguishable from random\n"
680
"# TODO: create DDH challenge and random tuples for comparison"),
681
("Relationship Between Assumptions",
682
"# DDH => CDH => DLP (each implies the next)\n"
683
"# If you can solve DLP, you can solve CDH; if CDH, then DDH\n"
684
"# TODO: illustrate the hierarchy with examples"),
685
],
686
),
687
(
688
"05e-baby-step-giant-step",
689
"Baby-Step Giant-Step Algorithm",
690
"O(sqrt(n)), table visualization",
691
[
692
("The Idea: Time-Space Tradeoff",
693
"# Split x = i*m + j where m = ceil(sqrt(n))\n"
694
"# Baby steps: compute g^j for j=0..m-1\n"
695
"# Giant steps: compute h*g^(-im) for i=0..m-1\n"
696
"# Match gives x\n"
697
"p = 101\n"
698
"n = p - 1 # group order\n"
699
"m = isqrt(n) + 1\n"
700
"print(f'Group order: {n}, step size m: {m}')"),
701
("Building the Baby-Step Table",
702
"# Compute and store g^j for j = 0, 1, ..., m-1\n"
703
"g = Mod(primitive_root(p), p)\n"
704
"baby = {}\n"
705
"gj = Mod(1, p)\n"
706
"for j in range(m):\n"
707
" baby[gj] = j\n"
708
" gj *= g\n"
709
"print(f'Baby-step table has {len(baby)} entries')"),
710
("Giant Steps and Matching",
711
"# Compute h * g^(-im) and look for a match\n"
712
"h = g^42 # target\n"
713
"g_inv_m = g^(-m)\n"
714
"gamma = h\n"
715
"for i in range(m):\n"
716
" if gamma in baby:\n"
717
" x = i*m + baby[gamma]\n"
718
" print(f'Found x = {x}')\n"
719
" break\n"
720
" gamma *= g_inv_m"),
721
],
722
),
723
(
724
"05f-pohlig-hellman",
725
"The Pohlig-Hellman Algorithm",
726
"Factor order, subgroup DLPs, CRT combination",
727
[
728
("Why Group Order Matters",
729
"# If |G| has small prime factors, DLP is easier\n"
730
"p = 433 # p-1 = 432 = 2^4 * 3^3\n"
731
"print(f'p-1 = {p-1}')\n"
732
"print(f'Factorization: {factor(p-1)}')"),
733
("Solving DLP in Subgroups",
734
"# Reduce to DLP in each prime-order subgroup\n"
735
"# For each prime power q^e dividing |G|:\n"
736
"# compute x mod q^e by projecting into subgroup of order q^e\n"
737
"# TODO: implement subgroup projection"),
738
("CRT Combination",
739
"# Combine sub-results using CRT to recover full x\n"
740
"# TODO: combine the subgroup DLP solutions with CRT_list()"),
741
],
742
),
743
],
744
745
# ------------------------------------------------------------------
746
# foundations/06 (6 notebooks)
747
# ------------------------------------------------------------------
748
"foundations/06-elliptic-curves": [
749
(
750
"06a-curves-over-reals",
751
"Elliptic Curves over the Reals",
752
"Plotting y^2 = x^3 + ax + b",
753
[
754
("The Weierstrass Equation",
755
"# An elliptic curve: y^2 = x^3 + ax + b\n"
756
"# The discriminant must be nonzero: 4a^3 + 27b^2 != 0\n"
757
"a, b = -1, 1\n"
758
"print(f'Discriminant: {4*a^3 + 27*b^2}')"),
759
("Plotting Curves",
760
"# Plot an elliptic curve over the reals\n"
761
"E = EllipticCurve(RR, [-1, 1])\n"
762
"E.plot()"),
763
("Varying Parameters",
764
"# Explore how a and b affect the curve shape\n"
765
"# TODO: plot several curves with different a, b values"),
766
],
767
),
768
(
769
"06b-point-addition-geometry",
770
"Point Addition: The Geometry",
771
"Chord-and-tangent, point at infinity",
772
[
773
("The Chord-and-Tangent Rule",
774
"# To add P + Q: draw line through P and Q, find third intersection, reflect\n"
775
"E = EllipticCurve(QQ, [-1, 1])\n"
776
"P = E(0, 1)\n"
777
"Q = E(1, 1)\n"
778
"print(f'P + Q = {P + Q}')"),
779
("The Point at Infinity",
780
"# The identity element: O (point at infinity)\n"
781
"E = EllipticCurve(QQ, [-1, 1])\n"
782
"P = E(0, 1)\n"
783
"O = E(0) # point at infinity\n"
784
"print(f'P + O = {P + O}')"),
785
("Point Doubling",
786
"# When P = Q, use the tangent line\n"
787
"E = EllipticCurve(QQ, [-1, 1])\n"
788
"P = E(0, 1)\n"
789
"print(f'2*P = {2*P}')"),
790
],
791
),
792
(
793
"06c-curves-over-finite-fields",
794
"Curves over Finite Fields",
795
"EllipticCurve(GF(p),[a,b]), points()",
796
[
797
("Defining a Curve over GF(p)",
798
"# Create an elliptic curve over a prime field\n"
799
"p = 23\n"
800
"E = EllipticCurve(GF(p), [1, 1])\n"
801
"print(E)"),
802
("Listing All Points",
803
"# Enumerate every point on the curve\n"
804
"p = 23\n"
805
"E = EllipticCurve(GF(p), [1, 1])\n"
806
"pts = E.points()\n"
807
"print(f'{len(pts)} points (including O)')\n"
808
"for P in pts:\n"
809
" print(P)"),
810
("Point Arithmetic over GF(p)",
811
"# Addition and scalar multiplication work the same way\n"
812
"p = 23\n"
813
"E = EllipticCurve(GF(p), [1, 1])\n"
814
"P = E.random_point()\n"
815
"Q = E.random_point()\n"
816
"print(f'P = {P}')\n"
817
"print(f'Q = {Q}')\n"
818
"print(f'P + Q = {P + Q}')"),
819
],
820
),
821
(
822
"06d-group-structure-and-order",
823
"Group Structure and Order",
824
"E.order(), Hasse bound, abelian_group()",
825
[
826
("Counting Points: Hasse's Theorem",
827
"# |E(GF(p))| is close to p+1: |#E - (p+1)| <= 2*sqrt(p)\n"
828
"p = 101\n"
829
"E = EllipticCurve(GF(p), [1, 1])\n"
830
"n = E.order()\n"
831
"print(f'#E = {n}, p+1 = {p+1}, bound = {2*isqrt(p)}')"),
832
("Group Structure",
833
"# The group E(GF(p)) is isomorphic to Z/n1 x Z/n2\n"
834
"p = 101\n"
835
"E = EllipticCurve(GF(p), [1, 1])\n"
836
"print(E.abelian_group())"),
837
("Point Orders",
838
"# Find the order of individual points\n"
839
"p = 101\n"
840
"E = EllipticCurve(GF(p), [1, 1])\n"
841
"P = E.random_point()\n"
842
"print(f'Order of P: {P.order()}')"),
843
],
844
),
845
(
846
"06e-scalar-multiplication",
847
"Scalar Multiplication",
848
"Double-and-add, n*P",
849
[
850
("Repeated Addition",
851
"# n*P means P + P + ... + P (n times)\n"
852
"E = EllipticCurve(GF(101), [1, 1])\n"
853
"P = E.random_point()\n"
854
"print(f'P = {P}')\n"
855
"print(f'5*P = {5*P}')"),
856
("Double-and-Add Algorithm",
857
"# Efficient scalar multiplication using binary expansion of n\n"
858
"def double_and_add(P, n):\n"
859
" R = P.curve()(0) # point at infinity\n"
860
" Q = P\n"
861
" while n > 0:\n"
862
" if n % 2 == 1:\n"
863
" R = R + Q\n"
864
" Q = Q + Q\n"
865
" n = n // 2\n"
866
" return R\n"
867
"# TODO: test against SageMath's built-in n*P"),
868
("The ECDLP",
869
"# Given P and Q = n*P, finding n is the ECDLP\n"
870
"E = EllipticCurve(GF(101), [1, 1])\n"
871
"P = E.random_point()\n"
872
"n_secret = randint(1, P.order()-1)\n"
873
"Q = n_secret * P\n"
874
"print(f'P = {P}, Q = {Q}')\n"
875
"print(f'Can you find n such that Q = n*P?')"),
876
],
877
),
878
(
879
"06f-ecdh-and-ecdsa",
880
"ECDH and ECDSA",
881
"Key exchange and signatures on curves",
882
[
883
("ECDH Key Exchange",
884
"# Same Diffie-Hellman idea, but on an elliptic curve\n"
885
"E = EllipticCurve(GF(next_prime(10^6)), [1, 1])\n"
886
"P = E.random_point()\n"
887
"n = P.order()\n"
888
"a = randint(1, n-1) # Alice's secret\n"
889
"b = randint(1, n-1) # Bob's secret\n"
890
"A = a * P # Alice's public\n"
891
"B = b * P # Bob's public\n"
892
"print(f'Shared secret match: {a*B == b*A}')"),
893
("ECDSA Signing",
894
"# Sign a message hash with ECDSA\n"
895
"# TODO: implement ECDSA signing step by step"),
896
("ECDSA Verification",
897
"# Verify an ECDSA signature\n"
898
"# TODO: implement ECDSA verification step by step"),
899
],
900
),
901
],
902
903
# ------------------------------------------------------------------
904
# frontier/07 (5 notebooks)
905
# ------------------------------------------------------------------
906
"frontier/07-pairings": [
907
(
908
"07a-bilinear-maps-definition",
909
"Bilinear Maps",
910
"e: G1 x G2 -> GT, bilinearity property",
911
[
912
("What Is a Bilinear Map?",
913
"# A pairing e: G1 x G2 -> GT satisfying\n"
914
"# e(aP, bQ) = e(P, Q)^(ab)\n"
915
"# TODO: set up pairing-friendly curve in SageMath"),
916
("The Bilinearity Property",
917
"# Verify: e(P+P', Q) = e(P,Q) * e(P',Q)\n"
918
"# TODO: demonstrate bilinearity with concrete examples"),
919
("Non-Degeneracy",
920
"# If P != O and Q != O, then e(P, Q) != 1\n"
921
"# TODO: check non-degeneracy condition"),
922
],
923
),
924
(
925
"07b-weil-pairing-intuition",
926
"The Weil Pairing",
927
"Divisors intro, weil_pairing()",
928
[
929
("Divisors on Curves",
930
"# A divisor is a formal sum of points on the curve\n"
931
"# TODO: introduce divisors with simple examples"),
932
("Computing the Weil Pairing",
933
"# SageMath's weil_pairing() for torsion points\n"
934
"# TODO: compute Weil pairing on a small curve"),
935
("Properties of the Weil Pairing",
936
"# Alternating: e(P, P) = 1\n"
937
"# TODO: verify key properties of the Weil pairing"),
938
],
939
),
940
(
941
"07c-pairing-friendly-curves",
942
"Pairing-Friendly Curves",
943
"BN curves, embedding degree",
944
[
945
("Embedding Degree",
946
"# The embedding degree k is the smallest k with r | p^k - 1\n"
947
"# TODO: compute embedding degree for sample curves"),
948
("BN Curves",
949
"# Barreto-Naehrig curves: pairing-friendly with embedding degree 12\n"
950
"# TODO: parameterize a BN curve"),
951
("Security Considerations",
952
"# Embedding degree affects both efficiency and security\n"
953
"# TODO: discuss the relationship between k and security level"),
954
],
955
),
956
(
957
"07d-bls-signatures",
958
"BLS Signatures",
959
"Sign, verify, aggregate",
960
[
961
("BLS Signature Scheme",
962
"# Sign: sigma = sk * H(m) (hash to curve point)\n"
963
"# Verify: e(sigma, g2) == e(H(m), pk)\n"
964
"# TODO: implement BLS sign and verify"),
965
("Signature Aggregation",
966
"# Multiple signatures can be aggregated into one\n"
967
"# sigma_agg = sigma_1 + sigma_2 + ... + sigma_n\n"
968
"# TODO: demonstrate signature aggregation"),
969
],
970
),
971
(
972
"07e-identity-based-encryption",
973
"Identity-Based Encryption",
974
"Boneh-Franklin IBE overview",
975
[
976
("IBE Concept",
977
"# Public key = identity string (e.g., email address)\n"
978
"# A trusted authority derives private keys from a master secret\n"
979
"# TODO: outline the Boneh-Franklin IBE scheme"),
980
("Setup and Key Extraction",
981
"# Setup: master secret s, public params (P, sP)\n"
982
"# Extract: private key = s * H(ID)\n"
983
"# TODO: implement setup and key extraction"),
984
("Encrypt and Decrypt",
985
"# Encrypt to an identity; only the extracted key can decrypt\n"
986
"# TODO: demonstrate encryption and decryption"),
987
],
988
),
989
],
990
991
# ------------------------------------------------------------------
992
# frontier/08 (6 notebooks)
993
# ------------------------------------------------------------------
994
"frontier/08-lattices-post-quantum": [
995
(
996
"08a-lattices-and-bases",
997
"Lattices and Bases",
998
"2D visualization, basis vectors, LLL()",
999
[
1000
("What Is a Lattice?",
1001
"# A lattice is the set of all integer linear combinations of basis vectors\n"
1002
"B = matrix(ZZ, [[1, 0], [0, 1]])\n"
1003
"print('Basis:\\n', B)"),
1004
("Visualizing a 2D Lattice",
1005
"# Plot lattice points generated by a basis\n"
1006
"B = matrix(ZZ, [[3, 1], [1, 2]])\n"
1007
"# TODO: plot lattice points as dots in the plane"),
1008
("Different Bases, Same Lattice",
1009
"# Two bases generate the same lattice iff they differ by a unimodular matrix\n"
1010
"B1 = matrix(ZZ, [[3, 1], [1, 2]])\n"
1011
"U = matrix(ZZ, [[1, 1], [0, 1]]) # unimodular: det = +/- 1\n"
1012
"B2 = U * B1\n"
1013
"print('B2:\\n', B2)\n"
1014
"print('det(U):', det(U))"),
1015
],
1016
),
1017
(
1018
"08b-shortest-vector-problem",
1019
"The Shortest Vector Problem",
1020
"SVP, good vs bad bases",
1021
[
1022
("SVP Definition",
1023
"# Find the shortest nonzero vector in a lattice\n"
1024
"# This is NP-hard in general\n"
1025
"B = matrix(ZZ, [[1, 0], [0, 1]])\n"
1026
"L = B.LLL()\n"
1027
"print('Reduced basis:\\n', L)"),
1028
("Good Bases vs Bad Bases",
1029
"# A 'good' basis has short, nearly orthogonal vectors\n"
1030
"# A 'bad' basis has long, nearly parallel vectors\n"
1031
"# TODO: compare a good and bad basis for the same lattice"),
1032
("Approximating SVP",
1033
"# LLL gives a vector within 2^(n/2) of the shortest\n"
1034
"B = matrix(ZZ, [[1, 0, 3], [0, 1, 5], [0, 0, 7]])\n"
1035
"L = B.LLL()\n"
1036
"print('LLL-reduced basis:\\n', L)\n"
1037
"print('Short vector:', L[0])"),
1038
],
1039
),
1040
(
1041
"08c-lll-algorithm",
1042
"The LLL Algorithm",
1043
"Step-by-step on 2D, reduction quality",
1044
[
1045
("Gram-Schmidt Orthogonalization",
1046
"# LLL starts with Gram-Schmidt (but keeps integer coefficients)\n"
1047
"B = matrix(QQ, [[3, 1], [2, 3]])\n"
1048
"G, mu = B.gram_schmidt()\n"
1049
"print('Gram-Schmidt basis:\\n', G)"),
1050
("The LLL Conditions",
1051
"# 1) Size-reduced: |mu_{i,j}| <= 1/2\n"
1052
"# 2) Lovasz condition: delta * ||b*_i||^2 <= ||b*_{i+1} + mu * b*_i||^2\n"
1053
"# TODO: check LLL conditions step by step"),
1054
("Running LLL",
1055
"# Apply LLL to a badly-conditioned basis\n"
1056
"B = matrix(ZZ, [[201, 37], [1648, 297]])\n"
1057
"print('Before LLL:\\n', B)\n"
1058
"L = B.LLL()\n"
1059
"print('After LLL:\\n', L)"),
1060
],
1061
),
1062
(
1063
"08d-learning-with-errors",
1064
"Learning With Errors",
1065
"LWE definition, noise, search vs decision",
1066
[
1067
("The LWE Problem",
1068
"# Given (A, b = A*s + e mod q), find s\n"
1069
"# A is random, s is secret, e is small noise\n"
1070
"n, q = 5, 101\n"
1071
"A = random_matrix(Zmod(q), 10, n)\n"
1072
"s = random_vector(Zmod(q), n)\n"
1073
"print('A:\\n', A)\n"
1074
"print('s:', s)"),
1075
("The Role of Noise",
1076
"# Without noise: b = A*s is easy to solve (linear algebra)\n"
1077
"# With noise: b = A*s + e becomes hard\n"
1078
"# TODO: compare solving with and without noise"),
1079
("Search-LWE vs Decision-LWE",
1080
"# Search: find s from (A, b)\n"
1081
"# Decision: distinguish (A, A*s+e) from (A, random)\n"
1082
"# These are polynomially equivalent\n"
1083
"# TODO: illustrate the search vs decision variants"),
1084
],
1085
),
1086
(
1087
"08e-ring-lwe",
1088
"Ring-LWE",
1089
"Polynomial ring setting, efficiency, NTRU",
1090
[
1091
("From LWE to Ring-LWE",
1092
"# Replace random matrix A with structured (polynomial) matrix\n"
1093
"# Work in R_q = Z_q[x] / (x^n + 1)\n"
1094
"# TODO: set up the polynomial ring R_q"),
1095
("Efficiency Gains",
1096
"# Ring structure allows O(n log n) operations via NTT\n"
1097
"# Key size: O(n) instead of O(n^2)\n"
1098
"# TODO: compare key sizes for LWE vs Ring-LWE"),
1099
("Connection to NTRU",
1100
"# NTRU: one of the earliest lattice-based schemes\n"
1101
"# Uses similar polynomial ring structure\n"
1102
"# TODO: sketch the NTRU encryption scheme"),
1103
],
1104
),
1105
(
1106
"08f-kyber-overview",
1107
"Kyber / ML-KEM Overview",
1108
"Module-LWE, parameters, KEM flow",
1109
[
1110
("Module-LWE",
1111
"# Kyber uses Module-LWE: matrices of ring elements\n"
1112
"# Compromise between LWE generality and Ring-LWE efficiency\n"
1113
"# TODO: illustrate the module structure"),
1114
("Kyber Parameters",
1115
"# Kyber-512, Kyber-768, Kyber-1024\n"
1116
"# n=256, q=3329, various k values\n"
1117
"# TODO: display parameter sets and security levels"),
1118
("KEM: Key Encapsulation Mechanism",
1119
"# KeyGen -> Encaps -> Decaps\n"
1120
"# Produces a shared symmetric key\n"
1121
"# TODO: outline the KEM flow"),
1122
],
1123
),
1124
],
1125
1126
# ------------------------------------------------------------------
1127
# frontier/09 (5 notebooks)
1128
# ------------------------------------------------------------------
1129
"frontier/09-commitments-sigma-protocols": [
1130
(
1131
"09a-commitment-schemes",
1132
"Commitment Schemes",
1133
"Hiding, binding, hash commitment",
1134
[
1135
("What Is a Commitment?",
1136
"# Commit to a value without revealing it; open later\n"
1137
"# Two properties: hiding (can't see value) and binding (can't change it)\n"
1138
"# TODO: demonstrate with a hash-based commitment"),
1139
("Hash-Based Commitment",
1140
"# commit(m, r) = H(m || r)\n"
1141
"# Open: reveal m and r; verifier checks hash\n"
1142
"import hashlib\n"
1143
"m = b'secret message'\n"
1144
"r = b'random_nonce_12345'\n"
1145
"commitment = hashlib.sha256(m + r).hexdigest()\n"
1146
"print(f'Commitment: {commitment}')"),
1147
("Hiding vs Binding",
1148
"# Perfectly hiding: commitment reveals zero info about m\n"
1149
"# Perfectly binding: cannot open to a different m'\n"
1150
"# Hash commitments: computationally hiding AND binding\n"
1151
"# TODO: discuss the tradeoff"),
1152
],
1153
),
1154
(
1155
"09b-pedersen-commitments",
1156
"Pedersen Commitments",
1157
"C = g^m * h^r, homomorphic, perfect hiding",
1158
[
1159
("Pedersen Commitment Scheme",
1160
"# Setup: group G of prime order q, generators g, h\n"
1161
"# Commit: C = g^m * h^r\n"
1162
"# Open: reveal m, r\n"
1163
"p = next_prime(10^20)\n"
1164
"g = Mod(primitive_root(p), p)\n"
1165
"# h should be chosen so that log_g(h) is unknown\n"
1166
"# TODO: set up Pedersen parameters"),
1167
("Homomorphic Property",
1168
"# C1 * C2 = g^(m1+m2) * h^(r1+r2)\n"
1169
"# Commitments can be added without opening!\n"
1170
"# TODO: demonstrate homomorphic addition"),
1171
("Perfect Hiding",
1172
"# For any m, the commitment C is uniformly random\n"
1173
"# (because r is random and h^r is uniform)\n"
1174
"# TODO: illustrate that different r values produce uniform C"),
1175
],
1176
),
1177
(
1178
"09c-sigma-protocols-intuition",
1179
"Sigma Protocols",
1180
"3-move structure, soundness, ZK",
1181
[
1182
("The 3-Move Structure",
1183
"# Prover -> Verifier: commitment (a)\n"
1184
"# Verifier -> Prover: challenge (e)\n"
1185
"# Prover -> Verifier: response (z)\n"
1186
"# TODO: diagram the 3-move protocol"),
1187
("Soundness",
1188
"# A cheating prover cannot answer two different challenges\n"
1189
"# Special soundness: from two accepting transcripts, extract witness\n"
1190
"# TODO: illustrate soundness extraction"),
1191
("Zero-Knowledge Property",
1192
"# The verifier learns nothing beyond the statement's truth\n"
1193
"# Simulator: can produce valid-looking transcripts without the witness\n"
1194
"# TODO: demonstrate the simulation argument"),
1195
],
1196
),
1197
(
1198
"09d-schnorr-protocol",
1199
"The Schnorr Protocol",
1200
"Interactive Schnorr, completeness/soundness/ZK",
1201
[
1202
("Protocol Description",
1203
"# Prover knows x such that h = g^x\n"
1204
"# 1. Prover picks random r, sends a = g^r\n"
1205
"# 2. Verifier sends random challenge e\n"
1206
"# 3. Prover sends z = r + e*x\n"
1207
"# Verify: g^z == a * h^e\n"
1208
"# TODO: implement the interactive protocol"),
1209
("Completeness",
1210
"# An honest prover always convinces the verifier\n"
1211
"# g^z = g^(r + ex) = g^r * g^(ex) = a * h^e\n"
1212
"# TODO: verify completeness with a concrete example"),
1213
("Soundness and Zero-Knowledge",
1214
"# Special soundness: two transcripts -> extract x\n"
1215
"# ZK: simulator picks z, e first, computes a = g^z / h^e\n"
1216
"# TODO: implement the simulator"),
1217
],
1218
),
1219
(
1220
"09e-fiat-shamir-transform",
1221
"The Fiat-Shamir Transform",
1222
"Hash-based challenge, non-interactive",
1223
[
1224
("From Interactive to Non-Interactive",
1225
"# Replace verifier's random challenge with a hash\n"
1226
"# e = H(g, h, a)\n"
1227
"# Now the prover can compute everything alone\n"
1228
"# TODO: implement the Fiat-Shamir transform for Schnorr"),
1229
("Non-Interactive Proof",
1230
"# Proof = (a, z) where e = H(g, h, a) and z = r + e*x\n"
1231
"# Verifier recomputes e from a and checks g^z == a * h^e\n"
1232
"# TODO: implement non-interactive prove and verify"),
1233
("Security in the Random Oracle Model",
1234
"# Fiat-Shamir is secure when H behaves as a random oracle\n"
1235
"# In practice, use a cryptographic hash (SHA-256, etc.)\n"
1236
"# TODO: discuss random oracle model assumptions"),
1237
],
1238
),
1239
],
1240
1241
# ------------------------------------------------------------------
1242
# frontier/10 (6 notebooks)
1243
# ------------------------------------------------------------------
1244
"frontier/10-snarks-starks": [
1245
(
1246
"10a-arithmetic-circuits",
1247
"Arithmetic Circuits",
1248
"Addition/multiplication gates, wire values",
1249
[
1250
("What Is an Arithmetic Circuit?",
1251
"# A DAG of addition and multiplication gates over a field\n"
1252
"# Inputs -> gates -> output\n"
1253
"# TODO: define a simple circuit: f(x) = x^3 + x + 5"),
1254
("Wire Values and Evaluation",
1255
"# Assign values to input wires, propagate through gates\n"
1256
"# TODO: trace wire values for f(3) = 27 + 3 + 5 = 35"),
1257
("Flattening to Gates",
1258
"# Any computation can be flattened to a sequence of gates\n"
1259
"# x^3 + x + 5:\n"
1260
"# w1 = x * x\n"
1261
"# w2 = w1 * x\n"
1262
"# w3 = w2 + x\n"
1263
"# w4 = w3 + 5\n"
1264
"# TODO: implement flattening"),
1265
],
1266
),
1267
(
1268
"10b-r1cs-constraints",
1269
"R1CS Constraints",
1270
"A*s . B*s = C*s, flattening, witness",
1271
[
1272
("Rank-1 Constraint System",
1273
"# Each gate becomes a constraint: (A_i . s) * (B_i . s) = (C_i . s)\n"
1274
"# s = (1, x, w1, w2, ..., output) is the witness vector\n"
1275
"# TODO: build R1CS matrices for x^3 + x + 5"),
1276
("The Witness",
1277
"# The witness includes all intermediate wire values\n"
1278
"# s = [1, x, x*x, x*x*x, x*x*x+x, x*x*x+x+5]\n"
1279
"# TODO: construct witness for a given input"),
1280
("Checking Satisfiability",
1281
"# Verify that A*s . B*s == C*s for all constraints\n"
1282
"# TODO: programmatically verify the R1CS"),
1283
],
1284
),
1285
(
1286
"10c-qap-construction",
1287
"QAP Construction",
1288
"Lagrange interpolation, vanishing polynomial",
1289
[
1290
("From R1CS to QAP",
1291
"# Interpolate each column of A, B, C into polynomials\n"
1292
"# Using Lagrange interpolation at points 1, 2, ..., m\n"
1293
"# TODO: convert R1CS matrices to QAP polynomials"),
1294
("The Vanishing Polynomial",
1295
"# Z(x) = (x-1)(x-2)...(x-m) vanishes at all constraint points\n"
1296
"# Valid witness => A(x)*B(x) - C(x) = H(x)*Z(x)\n"
1297
"# TODO: compute Z(x) and verify the QAP equation"),
1298
("Why QAP?",
1299
"# QAP allows checking all constraints with a single polynomial identity\n"
1300
"# Schwartz-Zippel: check at a random point\n"
1301
"# TODO: demonstrate the probabilistic check"),
1302
],
1303
),
1304
(
1305
"10d-groth16-overview",
1306
"Groth16 Overview",
1307
"Trusted setup, CRS, proof/verify",
1308
[
1309
("Trusted Setup",
1310
"# Generate a CRS (Common Reference String) from toxic waste tau\n"
1311
"# CRS = (g^1, g^tau, g^(tau^2), ..., ...)\n"
1312
"# Toxic waste must be destroyed!\n"
1313
"# TODO: illustrate CRS generation"),
1314
("Proof Generation",
1315
"# Prover uses CRS and witness to compute proof (A, B, C)\n"
1316
"# Three group elements = constant-size proof!\n"
1317
"# TODO: outline the Groth16 prover"),
1318
("Verification",
1319
"# Verify with a single pairing equation\n"
1320
"# e(A, B) = e(alpha, beta) * e(C, delta) * e(public_input_term, gamma)\n"
1321
"# TODO: outline the Groth16 verifier"),
1322
],
1323
),
1324
(
1325
"10e-fri-protocol",
1326
"The FRI Protocol",
1327
"Polynomial commitment, folding, Reed-Solomon",
1328
[
1329
("Reed-Solomon Codes",
1330
"# A low-degree polynomial evaluated on a domain forms a codeword\n"
1331
"# FRI proves that a function is close to a low-degree polynomial\n"
1332
"# TODO: demonstrate Reed-Solomon encoding"),
1333
("The Folding Technique",
1334
"# Split f(x) into even and odd parts, combine with random challenge\n"
1335
"# f(x) = f_even(x^2) + x * f_odd(x^2)\n"
1336
"# New polynomial has half the degree\n"
1337
"# TODO: implement one round of FRI folding"),
1338
("FRI as Polynomial Commitment",
1339
"# Repeated folding until constant polynomial\n"
1340
"# Verifier checks consistency with Merkle proofs\n"
1341
"# TODO: outline the full FRI protocol"),
1342
],
1343
),
1344
(
1345
"10f-starks-vs-snarks",
1346
"STARKs vs SNARKs",
1347
"Trust, proof size, verification comparison",
1348
[
1349
("Trusted Setup vs Transparency",
1350
"# SNARKs (Groth16): require trusted setup\n"
1351
"# STARKs: transparent, no trusted setup needed\n"
1352
"# TODO: compare trust assumptions"),
1353
("Proof Size and Verification Time",
1354
"# Groth16: ~200 bytes, O(1) verification\n"
1355
"# STARKs: ~100 KB, O(log^2 n) verification\n"
1356
"# TODO: tabulate the comparison"),
1357
("When to Use Which",
1358
"# SNARKs: on-chain verification (small proofs matter)\n"
1359
"# STARKs: post-quantum security, no trust needed\n"
1360
"# TODO: discuss practical tradeoffs"),
1361
],
1362
),
1363
],
1364
1365
# ------------------------------------------------------------------
1366
# frontier/11 (5 notebooks)
1367
# ------------------------------------------------------------------
1368
"frontier/11-homomorphic-encryption": [
1369
(
1370
"11a-what-is-fhe",
1371
"What Is Fully Homomorphic Encryption?",
1372
"Enc(a) op Enc(b) = Enc(a op b)",
1373
[
1374
("The FHE Dream",
1375
"# Compute on encrypted data without decrypting\n"
1376
"# Enc(a) + Enc(b) = Enc(a + b)\n"
1377
"# Enc(a) * Enc(b) = Enc(a * b)\n"
1378
"# TODO: illustrate with a toy example"),
1379
("Generations of FHE",
1380
"# Gen 1: Gentry (2009) - bootstrapping\n"
1381
"# Gen 2: BGV, BFV - batching, modulus switching\n"
1382
"# Gen 3: GSW - approximate eigenvector\n"
1383
"# Gen 4: CKKS - approximate arithmetic\n"
1384
"# TODO: timeline visualization"),
1385
("The Noise Problem",
1386
"# Each operation adds noise to the ciphertext\n"
1387
"# Too much noise -> decryption fails\n"
1388
"# Bootstrapping: homomorphically evaluate decryption to reduce noise\n"
1389
"# TODO: demonstrate noise growth"),
1390
],
1391
),
1392
(
1393
"11b-partially-homomorphic-schemes",
1394
"Partially Homomorphic Schemes",
1395
"RSA mul, Paillier add, ElGamal",
1396
[
1397
("RSA: Multiplicatively Homomorphic",
1398
"# Enc(m1) * Enc(m2) = (m1^e * m2^e) mod n = (m1*m2)^e mod n = Enc(m1*m2)\n"
1399
"# TODO: demonstrate with textbook RSA"),
1400
("Paillier: Additively Homomorphic",
1401
"# Enc(m1) * Enc(m2) = Enc(m1 + m2) in Paillier\n"
1402
"# TODO: implement Paillier encryption and demonstrate addition"),
1403
("ElGamal: Multiplicatively Homomorphic",
1404
"# (g^r1, m1*h^r1) * (g^r2, m2*h^r2) = (g^(r1+r2), m1*m2*h^(r1+r2))\n"
1405
"# TODO: demonstrate with ElGamal"),
1406
],
1407
),
1408
(
1409
"11c-bgv-scheme",
1410
"The BGV Scheme",
1411
"RLWE-based, modulus switching, noise",
1412
[
1413
("BGV Encryption",
1414
"# Plaintext in R_t = Z_t[x]/(x^n+1)\n"
1415
"# Ciphertext in R_q = Z_q[x]/(x^n+1)\n"
1416
"# ct = (b, a) where b = a*s + t*e + m\n"
1417
"# TODO: implement basic BGV encryption"),
1418
("Modulus Switching",
1419
"# Scale ciphertext from modulus q to smaller q'\n"
1420
"# This reduces noise at the cost of modulus size\n"
1421
"# TODO: demonstrate modulus switching"),
1422
("Homomorphic Operations",
1423
"# Addition: add ciphertexts component-wise\n"
1424
"# Multiplication: tensor product + relinearization\n"
1425
"# TODO: implement add and multiply"),
1426
],
1427
),
1428
(
1429
"11d-bfv-scheme",
1430
"The BFV Scheme",
1431
"Scale-and-round, plaintext/ciphertext modulus",
1432
[
1433
("BFV vs BGV",
1434
"# BFV: noise is in the LSBs (scale-and-round)\n"
1435
"# BGV: noise is in the MSBs (modulus switching)\n"
1436
"# BFV is simpler for integer arithmetic\n"
1437
"# TODO: compare the two approaches"),
1438
("BFV Encryption",
1439
"# Encode plaintext m as floor(q/t) * m + noise\n"
1440
"# Decrypt: round(t * ct / q) mod t\n"
1441
"# TODO: implement BFV encode/encrypt/decrypt"),
1442
("Noise Budget",
1443
"# Each operation consumes noise budget\n"
1444
"# When budget reaches zero, decryption fails\n"
1445
"# TODO: track noise budget through operations"),
1446
],
1447
),
1448
(
1449
"11e-ckks-approximate-arithmetic",
1450
"CKKS: Approximate Arithmetic",
1451
"Encoding reals, rescaling",
1452
[
1453
("Encoding Real Numbers",
1454
"# CKKS encodes real/complex numbers into polynomials\n"
1455
"# Encode: scale by Delta, round to integer polynomial\n"
1456
"# Decode: divide by Delta\n"
1457
"# TODO: implement CKKS encoding"),
1458
("Rescaling",
1459
"# After multiplication, scale grows: Delta^2\n"
1460
"# Rescale: divide ciphertext by Delta to get back to Delta\n"
1461
"# This is the key innovation of CKKS\n"
1462
"# TODO: demonstrate rescaling"),
1463
("Approximate Computations",
1464
"# CKKS allows small errors in the result\n"
1465
"# Perfect for ML inference, statistics, signal processing\n"
1466
"# TODO: compute a simple function on encrypted reals"),
1467
],
1468
),
1469
],
1470
1471
# ------------------------------------------------------------------
1472
# frontier/12 (5 notebooks)
1473
# ------------------------------------------------------------------
1474
"frontier/12-mpc": [
1475
(
1476
"12a-secret-sharing-shamir",
1477
"Shamir Secret Sharing",
1478
"Polynomial interpolation, t-of-n",
1479
[
1480
("The Idea: Hide a Secret in a Polynomial",
1481
"# Secret s is the constant term of a random degree-(t-1) polynomial\n"
1482
"# Share i = f(i) for i = 1, 2, ..., n\n"
1483
"R.<x> = PolynomialRing(GF(next_prime(1000)))\n"
1484
"# TODO: construct a random polynomial with secret as f(0)"),
1485
("Sharing",
1486
"# Evaluate the polynomial at n distinct points\n"
1487
"# Any t shares can reconstruct; fewer than t reveal nothing\n"
1488
"# TODO: generate n shares from the polynomial"),
1489
("Reconstruction via Lagrange Interpolation",
1490
"# Given t shares (x_i, y_i), interpolate to recover f(0)\n"
1491
"# TODO: use Lagrange interpolation to reconstruct the secret"),
1492
],
1493
),
1494
(
1495
"12b-secret-sharing-additive",
1496
"Additive Secret Sharing",
1497
"Random splits, XOR, reconstruction",
1498
[
1499
("Additive Sharing over a Field",
1500
"# Split secret s into n shares: s = s_1 + s_2 + ... + s_n\n"
1501
"# Pick s_1, ..., s_{n-1} randomly; s_n = s - sum(others)\n"
1502
"# TODO: implement additive sharing"),
1503
("XOR-Based Sharing",
1504
"# For binary data: s = s_1 XOR s_2 XOR ... XOR s_n\n"
1505
"# TODO: implement XOR sharing and reconstruction"),
1506
("Addition on Shared Values",
1507
"# To add shared values: each party adds their shares locally\n"
1508
"# [a] + [b] = [a+b] without any communication!\n"
1509
"# TODO: demonstrate local addition on shares"),
1510
],
1511
),
1512
(
1513
"12c-yaos-garbled-circuits",
1514
"Yao's Garbled Circuits",
1515
"Gate garbling, wire labels",
1516
[
1517
("Wire Labels",
1518
"# Each wire gets two random labels: one for 0, one for 1\n"
1519
"# The evaluator sees one label per wire but doesn't know which bit it represents\n"
1520
"# TODO: generate random wire labels"),
1521
("Garbling a Gate",
1522
"# For each gate, encrypt the output label under the input labels\n"
1523
"# Four entries (for AND gate): only one decrypts correctly\n"
1524
"# TODO: garble an AND gate"),
1525
("Evaluating the Garbled Circuit",
1526
"# The evaluator decrypts one entry per gate using their wire labels\n"
1527
"# Oblivious transfer provides the input labels\n"
1528
"# TODO: evaluate a simple garbled circuit"),
1529
],
1530
),
1531
(
1532
"12d-oblivious-transfer",
1533
"Oblivious Transfer",
1534
"1-of-2 OT, sender/receiver privacy",
1535
[
1536
("1-of-2 Oblivious Transfer",
1537
"# Sender has (m0, m1); receiver has choice bit b\n"
1538
"# Receiver gets m_b; sender doesn't learn b\n"
1539
"# TODO: implement 1-of-2 OT using DH"),
1540
("RSA-Based OT",
1541
"# Classic construction using RSA blinding\n"
1542
"# TODO: implement RSA-based OT"),
1543
("OT Extension",
1544
"# A few base OTs -> many OTs using symmetric crypto\n"
1545
"# Key optimization for practical MPC\n"
1546
"# TODO: outline OT extension"),
1547
],
1548
),
1549
(
1550
"12e-spdz-protocol",
1551
"The SPDZ Protocol",
1552
"Beaver triples, online/offline, malicious security",
1553
[
1554
("Beaver Triples",
1555
"# Pre-shared random triples (a, b, c) with c = a*b\n"
1556
"# Allow multiplication on shared values\n"
1557
"# TODO: demonstrate Beaver triple generation"),
1558
("Online Phase: Multiplication",
1559
"# To multiply [x]*[y]:\n"
1560
"# Open d = x-a and e = y-b\n"
1561
"# [xy] = [c] + d*[y] + e*[x] + d*e\n"
1562
"# TODO: implement online multiplication"),
1563
("Malicious Security",
1564
"# SPDZ uses MACs to detect cheating parties\n"
1565
"# Each share has an authentication tag\n"
1566
"# TODO: demonstrate MAC-based verification"),
1567
],
1568
),
1569
],
1570
}
1571
1572
1573
# ---------------------------------------------------------------------------
1574
# SageMath kernel metadata (Jupyter v4 format)
1575
# ---------------------------------------------------------------------------
1576
SAGEMATH_KERNEL = {
1577
"kernelspec": {
1578
"display_name": "SageMath",
1579
"language": "sage",
1580
"name": "sagemath",
1581
},
1582
"language_info": {
1583
"codemirror_mode": {"name": "ipython", "version": 3},
1584
"file_extension": ".py",
1585
"mimetype": "text/x-python",
1586
"name": "sage",
1587
"nbconvert_exporter": "python",
1588
"pygments_lexer": "ipython3",
1589
},
1590
}
1591
1592
1593
# ---------------------------------------------------------------------------
1594
# Helpers
1595
# ---------------------------------------------------------------------------
1596
1597
def _md_cell(source: str) -> dict:
1598
"""Create a Jupyter markdown cell."""
1599
return {
1600
"cell_type": "markdown",
1601
"metadata": {},
1602
"source": source.splitlines(keepends=True),
1603
}
1604
1605
1606
def _code_cell(source: str) -> dict:
1607
"""Create a Jupyter code cell."""
1608
return {
1609
"cell_type": "code",
1610
"execution_count": None,
1611
"metadata": {},
1612
"outputs": [],
1613
"source": source.splitlines(keepends=True),
1614
}
1615
1616
1617
def _next_notebook_link(module_path: str, notebooks: list, idx: int) -> str:
1618
"""Return a markdown 'Next' link to the following notebook, or empty str."""
1619
if idx + 1 < len(notebooks):
1620
next_stem = notebooks[idx + 1][0]
1621
next_title = notebooks[idx + 1][1]
1622
return f"\n\n**Next:** [{next_title}]({next_stem}.ipynb)"
1623
return ""
1624
1625
1626
def _build_notebook(
1627
module_path: str,
1628
notebooks: list,
1629
idx: int,
1630
stem: str,
1631
title: str,
1632
description: str,
1633
sections: list[tuple[str, str]],
1634
) -> dict:
1635
"""Build a complete Jupyter notebook dict."""
1636
# Derive module number and name for display
1637
module_dir = Path(module_path).name # e.g. "01-modular-arithmetic-groups"
1638
module_num = module_dir.split("-")[0] # e.g. "01"
1639
1640
cells: list[dict] = []
1641
1642
# --- Title cell ---
1643
cells.append(_md_cell(
1644
f"# {title}\n"
1645
f"\n"
1646
f"**Module {module_num}** | {module_dir}\n"
1647
f"\n"
1648
f"*{description}*"
1649
))
1650
1651
# --- Objectives cell ---
1652
cells.append(_md_cell(
1653
f"## Objectives\n"
1654
f"\n"
1655
f"By the end of this notebook you will be able to:\n"
1656
f"\n"
1657
f"1. Understand the core ideas behind **{title.lower()}**.\n"
1658
f"2. Explore these concepts interactively using SageMath.\n"
1659
f"3. Build intuition through hands-on computation and visualization."
1660
))
1661
1662
# --- Prerequisites cell ---
1663
if idx == 0:
1664
prereq_text = (
1665
"## Prerequisites\n"
1666
"\n"
1667
"- Basic familiarity with Python syntax.\n"
1668
"- A working SageMath installation (or access to CoCalc/SageMathCell)."
1669
)
1670
else:
1671
prev_stem = notebooks[idx - 1][0]
1672
prev_title = notebooks[idx - 1][1]
1673
prereq_text = (
1674
"## Prerequisites\n"
1675
"\n"
1676
f"- Completion of [{prev_title}]({prev_stem}.ipynb).\n"
1677
"- Concepts and notation introduced in the previous notebook."
1678
)
1679
cells.append(_md_cell(prereq_text))
1680
1681
# --- Section + code cell pairs ---
1682
for heading, code in sections:
1683
cells.append(_md_cell(f"## {heading}"))
1684
cells.append(_code_cell(code))
1685
1686
# --- Exercises section ---
1687
cells.append(_md_cell(
1688
"## Exercises\n"
1689
"\n"
1690
"Try these on your own before moving on:\n"
1691
"\n"
1692
"1. **Exercise 1:** *(TODO: add exercise)*\n"
1693
"2. **Exercise 2:** *(TODO: add exercise)*\n"
1694
"3. **Exercise 3:** *(TODO: add exercise)*"
1695
))
1696
1697
# --- Summary cell with Next link ---
1698
next_link = _next_notebook_link(module_path, notebooks, idx)
1699
cells.append(_md_cell(
1700
f"## Summary\n"
1701
f"\n"
1702
f"In this notebook we explored **{title.lower()}**. "
1703
f"Key takeaways:\n"
1704
f"\n"
1705
f"- *(TODO: summarize key point 1)*\n"
1706
f"- *(TODO: summarize key point 2)*\n"
1707
f"- *(TODO: summarize key point 3)*"
1708
f"{next_link}"
1709
))
1710
1711
return {
1712
"cells": cells,
1713
"metadata": SAGEMATH_KERNEL,
1714
"nbformat": 4,
1715
"nbformat_minor": 5,
1716
}
1717
1718
1719
# ---------------------------------------------------------------------------
1720
# Main
1721
# ---------------------------------------------------------------------------
1722
1723
def main() -> None:
1724
total = 0
1725
1726
for module_path, notebooks in MODULES.items():
1727
sage_dir = REPO_ROOT / module_path / "sage"
1728
sage_dir.mkdir(parents=True, exist_ok=True)
1729
1730
for idx, (stem, title, description, sections) in enumerate(notebooks):
1731
nb = _build_notebook(
1732
module_path, notebooks, idx,
1733
stem, title, description, sections,
1734
)
1735
1736
out_path = sage_dir / f"{stem}.ipynb"
1737
with open(out_path, "w", encoding="utf-8") as f:
1738
json.dump(nb, f, indent=1, ensure_ascii=False)
1739
f.write("\n")
1740
1741
rel = out_path.relative_to(REPO_ROOT)
1742
print(f" created {rel}")
1743
total += 1
1744
1745
print(f"\nDone: {total} notebooks generated.")
1746
1747
1748
if __name__ == "__main__":
1749
main()
1750
1751