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.
Modern ZK systems don't prove things about text, or x86 assembly, or even Rust. They prove things about arithmetic circuits — computations expressed as additions and multiplications over a finite field. The dominant intermediate representation is R1CS (Rank-1 Constraint System): each constraint is an equation (A · z)(B · z) = (C · z) where z is a witness vector. Your Rust code gets compiled down to R1CS (by Circom, Gnark, or a newer zkDSL). Understanding R1CS is the difference between 'using Groth16' and 'knowing what my prover is actually proving.'
A minimal R1CS: prove 'I know x such that x^3 + x + 5 = 35.' Introduce witness variables sym1 = x·x, y = sym1·x, sym2 = y+x, out = sym2+5. Each is a multiplication (except sym2, which is addition → folded in). Three multiplication constraints. z = (1, x, out, sym1, y, sym2). The A, B, C matrices encode the constraints. A witness-assignment check is matrix-vector arithmetic.
// main.go — cubic equation "x^3 + x + 5 = out" as R1CS
// run: go run main.go
package main
import "fmt"
// Witness layout: z = [1, x, out, sym1, y, sym2]
// Constraints:
// (1) x * x = sym1
// (2) sym1 * x = y
// (3) (y + x) * 1 = sym2 (addition folded into a mul-by-1 constraint)
// (4) (sym2 + 5) * 1 = out
func dot(a, z []int) int {
s := 0
for i := range a {
s += a[i] * z[i]
}
return s
}
func checkR1CS(A, B, C [][]int, z []int) bool {
for i := range A {
if dot(A[i], z)*dot(B[i], z) != dot(C[i], z) {
fmt.Printf("constraint %d failed\n", i+1)
return false
}
}
return true
}
func main() {
A := [][]int{
{0, 1, 0, 0, 0, 0}, // (1)
{0, 0, 0, 1, 0, 0}, // (2)
{0, 1, 0, 0, 1, 0}, // (3)
{5, 0, 0, 0, 0, 1}, // (4)
}
B := [][]int{
{0, 1, 0, 0, 0, 0},
{0, 1, 0, 0, 0, 0},
{1, 0, 0, 0, 0, 0},
{1, 0, 0, 0, 0, 0},
}
C := [][]int{
{0, 0, 0, 1, 0, 0},
{0, 0, 0, 0, 1, 0},
{0, 0, 0, 0, 0, 1},
{0, 0, 1, 0, 0, 0},
}
// Witness: x=3 → sym1=9, y=27, sym2=30, out=35
z := []int{1, 3, 35, 9, 27, 30}
fmt.Println("R1CS satisfies:", checkR1CS(A, B, C, z)) // true
}go run main.goUse these three in order. Each builds on the one before.
In one paragraph, explain R1CS — what a Rank-1 Constraint System is, why it's the universal IR for ZK-SNARKs, and how it compiles high-level code.
Walk me through how a simple arithmetic expression (like x^3 + x + 5) is compiled into R1CS: identify the witness variables, the multiplication constraints, and how additions get folded in.
I need to ZK-prove a Keccak-256 preimage (the hash function Ethereum uses). Estimate the R1CS constraint count, the prover time under Groth16 vs PLONK, and whether I should switch to a SNARK-friendly hash (Poseidon) to reduce constraint count.