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