If p is prime and a is not divisible by p, then: ap-1 ≡ 1 (mod p)
Direct consequence: a-1 ≡ ap-2 (mod p)
This gives us a formula for computing inverses!
# Verify Fermat's Little Theorem
p = 7
for a in range(1, p):
result = pow(a, p - 1, p)
inv_fermat = pow(a, p - 2, p)
inv_builtin = pow(a, -1, p)
print(f"a={a}: a^(p-1)={result}, a^(p-2)={inv_fermat}, inv={inv_builtin}")
assert result == 1
assert inv_fermat == inv_builtin
This theorem is the theoretical foundation for efficient modular inverse computation.
Any rational number a/b can be mapped into π½β:
a/b ↦ a × b-1 (mod p)
p = 17
# Map 1/3 into the field
one_third = (1 * pow(3, -1, p)) % p
print(f"1/3 β‘ {one_third} (mod {p})") # 6
# Map 3/5 into the field
three_fifths = (3 * pow(5, -1, p)) % p
print(f"3/5 β‘ {three_fifths} (mod {p})") # 4
# Verify: 1/3 + 3/5 = 14/15 in regular math
regular_sum = (1*5 + 3*3) / (3*5) # 14/15
field_sum = (one_third + three_fifths) % p
fourteen_fifteenths = (14 * pow(15, -1, p)) % p
print(f"1/3 + 3/5 = {field_sum} (mod {p})")
print(f"14/15 = {fourteen_fifteenths} (mod {p})")
assert field_sum == fourteen_fifteenths # β Homomorphism!
Python galois Library
from galois import GF
# Create a finite field of order 7
GF7 = GF(7)
# Elements behave like regular numbers but are always mod 7
a = GF7(5)
b = GF7(3)
print(f"a + b = {a + b}") # 1 (8 mod 7)
print(f"a * b = {a * b}") # 1 (15 mod 7)
print(f"a / b = {a / b}") # 4 (5 Γ 3β»ΒΉ mod 7)
print(f"a ** 2 = {a ** 2}") # 4 (25 mod 7)
print(f"-a = {-a}") # 2 (7 - 5)
# Works with larger primes too
GF101 = GF(101)
x = GF101(50)
print(f"50β»ΒΉ mod 101 = {GF101(1) / x}") # multiplicative inverse
pip install galois — Useful for experimentation and verification
Modular Square Roots
In regular math: √9 = ±3
In finite fields: √a mod p might have 0 or 2 solutions
p = 11
# Which elements have square roots mod 11?
for a in range(0, p):
roots = [x for x in range(p) if (x * x) % p == a]
if roots:
print(f"β{a} mod {p} = {roots}")
# Output:
# β0 mod 11 = [0]
# β1 mod 11 = [1, 10] β 1Β² = 10Β² β‘ 1
# β3 mod 11 = [5, 6] β 5Β² = 6Β² β‘ 3
# β4 mod 11 = [2, 9] β 2Β² = 9Β² β‘ 4
# β5 mod 11 = [4, 7] β 4Β² = 7Β² β‘ 5
# β9 mod 11 = [3, 8] β 3Β² = 8Β² β‘ 9
Notice: the two roots always sum to p! (e.g., 5 + 6 = 11)
They are additive inverses of each other β just like ±3 in regular math.
Testing for Square Roots: Euler's Criterion
a(p-1)/2 ≡ 1 (mod p) ⇒ a has a square root a(p-1)/2 ≡ p-1 (mod p) ⇒ a does NOT have a square root
p = 11
# Test which elements are quadratic residues
for a in range(1, p):
euler = pow(a, (p - 1) // 2, p)
has_sqrt = euler == 1
print(f"a={a:2d}: a^((p-1)/2) = {euler:2d} β {'has' if has_sqrt else 'NO'} sqrt")
# Elements with square roots: 1, 3, 4, 5, 9 (5 out of 10)
# Elements without: 2, 6, 7, 8, 10 (5 out of 10)
# Exactly half!
Polynomials in Finite Fields
Polynomials work the same way — just reduce coefficients mod p
from galois import GF, Poly
GF7 = GF(7)
# f(x) = 3xΒ² + 2x + 5 over GF(7)
f = Poly([3, 2, 5], field=GF7)
print(f"f(x) = {f}")
# Evaluate at x = 4
print(f"f(4) = {f(GF7(4))}") # 3(16) + 2(4) + 5 = 61 β‘ 5 mod 7
# g(x) = x + 3
g = Poly([1, 3], field=GF7)
# Polynomial addition
print(f"f + g = {f + g}")
# Polynomial multiplication
print(f"f * g = {f * g}")
Polynomials over finite fields are central to ZK proofs!
QAP, PLONK, and other ZK schemes encode computations as polynomial equations.
Why Polynomials Matter for ZK
Schwartz-Zippel Lemma (Intuition)
If two polynomials of degree d agree at more than d points, they must be the same polynomial.
A degree-d polynomial has at most d roots
If p(x) - q(x) = 0 at d+1 points → p(x) = q(x)
Over a large field, random evaluation is almost certainly unique
ZK Insight: The prover encodes their computation as a polynomial.
The verifier checks it at a random point.
If it passes, the polynomial (and computation) is almost certainly correct.
p = 23 # A prime
print("=== Finite Field GF(23) ===")
print(f"\nAdditive inverses:")
for a in [1, 5, 11, 22]:
print(f" -{a} β‘ {(-a) % p} (mod {p})")
print(f"\nMultiplicative inverses:")
for a in [2, 3, 7, 11]:
inv = pow(a, -1, p)
print(f" {a}β»ΒΉ β‘ {inv} (mod {p}) [verify: {a}Γ{inv}={a*inv}β‘{(a*inv)%p}]")
print(f"\nDivision:")
for (a, b) in [(10, 3), (1, 7), (15, 4)]:
result = (a * pow(b, -1, p)) % p
print(f" {a} Γ· {b} β‘ {result} (mod {p}) [verify: {result}Γ{b}={(result*b)%p}β‘{a%p}]")
print(f"\nFermat's Little Theorem:")
for a in [2, 5, 17]:
print(f" {a}^{p-1} β‘ {pow(a, p-1, p)} (mod {p})") # Always 1
Real-World Field Sizes
In practice, ZK proofs use enormous primes:
# The BN128 curve (used by Ethereum) has field modulus:
p = 21888242871839275222246405745257275088696311157297823662689037894645226208583
# That's a 254-bit number!
print(f"Bits: {p.bit_length()}") # 254
# But arithmetic still works identically:
a = 12345678901234567890
b = 98765432109876543210
print(f"a + b mod p = {(a + b) % p}")
print(f"a Γ b mod p = {(a * b) % p}")
print(f"a / b mod p = {(a * pow(b, -1, p)) % p}")
print(f"a^(p-1) mod p = {pow(a, p - 1, p)}") # Still 1!
Same rules, same operations — just much bigger numbers.
Key Takeaways
Finite field π½β = {0, 1, ..., p-1} with mod-p arithmetic (p prime)
Every non-zero element has a multiplicative inverse
Division is exact — no fractions, no rounding
Fermat's Little Theorem: ap-1 ≡ 1, so a-1 = ap-2
Polynomials work over finite fields and are the basis of ZK
Modular square roots exist for exactly half of non-zero elements
Everything in ZK proofs happens inside finite fields. Master this, and the rest follows.
Practice Problems
Compute all multiplicative inverses in π½ββ. Verify each one.
Solve: 4x ≡ 3 (mod 11). Hint: multiply both sides by 4-1
Map the fraction 2/5 into π½ββ. Verify: result × 5 ≡ 2 (mod 13).
Find all quadratic residues mod 13 (elements with square roots).
Verify Fermat's Little Theorem for all non-zero elements of π½ββ.
Using the galois library, define f(x) = x² + 3x + 2 over GF(11) and find all roots (values where f(x) = 0).
Next Workshop
Set Theory & Abstract Algebra
Sets, subsets, and Cartesian products
Binary operators and relations
Magma → Semigroup → Monoid → Group
Why this vocabulary matters for ZK
The language of abstract math gives us the power to reason about operations without understanding their implementation.