Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
A field is a group's richer cousin: you get two compatible operations (addition and multiplication), each behaving well, plus a distributive law gluing them together. ZK proofs live almost entirely inside finite fields (and their extensions ) because every arithmetic statement we want to prove — 'I know such that ' — gets encoded as a system of polynomial equations over a field. You need addition to combine constraints, multiplication to compose them, and inverses so that division is unambiguous. The field axioms aren't ceremony; they're the contract that makes 'arithmetisation' work. Pick the wrong structure and SNARK soundness collapses.
A field is a set with two operations such that is an abelian group with identity 0, is an abelian group with identity 1, and multiplication distributes over addition. For prime , is a finite field of size .
Use these three in order. Each builds on the one before.
In one paragraph, explain the difference between a *ring*, a *field*, and a *group* using $\mathbb{Z}, \mathbb{Q}, \mathbb{Z}/6\mathbb{Z}$ as running examples. Why is 'every nonzero element has a multiplicative inverse' the crucial extra property a field has?
Walk me through why $\mathbb{Z}/p\mathbb{Z}$ is a field exactly when $p$ is prime. Where in the proof does primality enter, and what concretely goes wrong in $\mathbb{Z}/6\mathbb{Z}$?
ZK protocols often pick a specific 'SNARK-friendly' prime like the BN254 scalar field or BLS12-381's $r$. What properties of $p$ — beyond being prime — make a field SNARK-friendly? (Think about FFTs, two-adicity, low-degree extensions.)
p = 13 to p = 12 and re-run. Find the element where inv returns a value that isn't a true inverse — this is direct evidence that is a ring but not a field (4 has no inverse mod 12).// main.go
package main
import (
"fmt"
)
// Working in F_p with p = 13
const p = 13
func add(a, b int) int { return (a + b) % p }
func mul(a, b int) int { return (a * b) % p }
func inv(a int) int {
// Fermat: a^(p-2) is the inverse of a mod p when p is prime
result := 1
base := a % p
exp := p - 2
for exp > 0 {
if exp%2 == 1 {
result = (result * base) % p
}
base = (base * base) % p
exp /= 2
}
return result
}
func main() {
// Verify field axioms by sampling
for a := 1; a < p; a++ {
if mul(a, inv(a)) != 1 {
panic(fmt.Sprintf("inverse of %d broken", a))
}
}
// Distributivity spot-check
a, b, c := 5, 7, 11
if mul(a, add(b, c)) != add(mul(a, b), mul(a, c)) {
panic("distributivity broken")
}
inverseTable := make(map[int]int)
for a := 1; a < p; a++ {
inverseTable[a] = inv(a)
}
fmt.Printf("Inverse table: %v\n", inverseTable)
fmt.Println("Note: 12 mod 13 has inverse 12, since 12 == -1.")
}
go run main.go