import Mathlib.Data.Fin.VecNotation import Mathlib.Data.ZMod.Basic import Mathlib.Algebra.MvPolynomial.Basic import Mathlib.Algebra.MvPolynomial.Eval import Mathlib.Tactic -- TODO: Reference Crypto/ZK/Sumcheck.lean (or move this there?) -- ============================================================================ -- Sumcheck with a nonlinear round polynomial -- ============================================================================ -- -- TODO: Consider removing/stripping BiniusToy.lean; hence update this comment. -- -- A companion to the sumcheck step of Examples/BiniusToy.lean. There both -- round polynomials came out as g(t) = t: two values determine them, and -- the challenge r โˆˆ ๐”ฝโ‚‚ always lands on an interpolation node, so the -- "verifier evaluates the received polynomial at r" step never becomes -- visible. This file runs the protocol where it does become visible: -- -- p(xโ‚€, xโ‚) = xโ‚€ยฒยทxโ‚ + xโ‚€ + xโ‚ over โ„ค/7โ„ค -- -- The variable xโ‚€ occurs with degree 2, so the round-1 message gโ‚€ is -- quadratic: two values do NOT determine it, the prover must send three -- (equivalently, the polynomial's coefficients), and the challenge rโ‚€ = 2 -- lies outside the interpolation nodes {0, 1}, so the target value -- vโ‚ = gโ‚€(2) is genuinely new information. -- -- In the real protocol the challenges are uniform over the field, so a -- sample lands outside {0, 1} with probability 5/7. The values 2 and 3 -- below are not drawn at random: they are chosen deliberately to exhibit -- the off-node case. Any of {2, โ€ฆ, 6} would demonstrate the same thing. -- -- TODO: Compact the following comment block: -- -- Why three: RootsInterpolation.lean ยง1, the first consequence of the -- roots bound: polynomials of degree < n agreeing at n points are equal, -- so n = 3 points pin down gโ‚€, and ยง2 gives the interpolant's existence. -- Two points leave a one-parameter family: ฤโ‚€ = gโ‚€ + cยทXยท(Xโˆ’1) for any -- c โˆˆ โ„ค/7โ„ค is a different quadratic agreeing with gโ‚€ at both nodes, and -- it still passes the verifier's check ฤโ‚€(0) + ฤโ‚€(1) = 5, because X(Xโˆ’1) -- vanishes exactly there. -- -- The run: -- -- claim: ฮฃ p over the hypercube {0,1}ยฒ = 5 -- round 1: gโ‚€(t) = tยฒ + 2t + 1, check gโ‚€(0) + gโ‚€(1) = 5 -- rโ‚€ = 2: target vโ‚ = gโ‚€(2) = 2, pin xโ‚€ := 2 -- round 2: gโ‚(t) = 5t + 2, check gโ‚(0) + gโ‚(1) = 2 = vโ‚ -- rโ‚ = 3: target v = gโ‚(3) = 3, pin xโ‚ := 3 -- final: p(2, 3) = 3, checked directly -- -- Soundness note: a cheating prover must send ฤโ‚€ โ‰  gโ‚€ of degree โ‰ค 2 with -- ฤโ‚€(0) + ฤโ‚€(1) = 5. Two distinct degree-2 polynomials agree on at most 2 -- points (RootsInterpolation.lean), so of the 7 possible challenges at -- most 2 let ฤโ‚€ survive: the verifier catches the lie with probability -- โ‰ฅ 5/7 in round 1 alone. namespace Examples.Sumcheck open MvPolynomial /-- The statement polynomial: degree 2 in xโ‚€, degree 1 in xโ‚. -/ noncomputable def p : MvPolynomial (Fin 2) (ZMod 7) := X 0 ^ 2 * X 1 + X 0 + X 1 -- ============================================================================ -- Step 0: The claim -- ============================================================================ -- The four corners, concretely. example : eval ![0, 0] p = 0 := by simp only [p, eval_add, eval_mul, eval_pow, eval_X]; decide example : eval ![0, 1] p = 1 := by simp only [p, eval_add, eval_mul, eval_pow, eval_X]; decide example : eval ![1, 0] p = 1 := by simp only [p, eval_add, eval_mul, eval_pow, eval_X]; decide example : eval ![1, 1] p = 3 := by simp only [p, eval_add, eval_mul, eval_pow, eval_X]; decide /-- The hypercube {0,1}ยฒ embedded in (โ„ค/7โ„ค)ยฒ. -/ def cube : Finset (Fin 2 โ†’ ZMod 7) := {![0, 0], ![0, 1], ![1, 0], ![1, 1]} /-- The claim: the hypercube sum is C = 5. -/ example : โˆ‘ v โˆˆ cube, eval v p = 5 := by simp [cube, p, eval_add, eval_mul, eval_pow, eval_X] decide -- ============================================================================ -- Step 1: Round 1, the quadratic message -- ============================================================================ /-- The honest round-1 polynomial: gโ‚€(t) = p(t,0) + p(t,1) = tยฒ + 2t + 1. Term by term, with xโ‚€ set to t and xโ‚ summed over {0, 1}: term of p: xโ‚€ยฒยทxโ‚ xโ‚€ xโ‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ at xโ‚ = 0: 0 t 0 at xโ‚ = 1: tยฒ t 1 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ column sum: tยฒ + 2t + 1 = gโ‚€(t) The cross term xโ‚€ยฒยทxโ‚ survives only at xโ‚ = 1 (it vanishes at xโ‚ = 0), while xโ‚€ contributes twice, once per slice. -/ noncomputable def gโ‚€ : Polynomial (ZMod 7) := Polynomial.X ^ 2 + Polynomial.C 2 * Polynomial.X + Polynomial.C 1 example : gโ‚€.natDegree = 2 := by unfold gโ‚€; compute_degree! -- Its values at the interpolation nodes are the honest slice sums: -- gโ‚€(0) = p(0,0) + p(0,1) and gโ‚€(1) = p(1,0) + p(1,1). example : gโ‚€.eval 0 = eval ![0, 0] p + eval ![0, 1] p := by simp only [gโ‚€, p, Polynomial.eval_add, Polynomial.eval_mul, Polynomial.eval_pow, Polynomial.eval_C, Polynomial.eval_X, eval_add, eval_mul, eval_pow, eval_X] decide example : gโ‚€.eval 1 = eval ![1, 0] p + eval ![1, 1] p := by simp only [gโ‚€, p, Polynomial.eval_add, Polynomial.eval_mul, Polynomial.eval_pow, Polynomial.eval_C, Polynomial.eval_X, eval_add, eval_mul, eval_pow, eval_X] decide -- Round 1 consistency: gโ‚€(0) + gโ‚€(1) = 5 = C. example : gโ‚€.eval 0 + gโ‚€.eval 1 = 5 := by simp only [gโ‚€, Polynomial.eval_add, Polynomial.eval_mul, Polynomial.eval_pow, Polynomial.eval_C, Polynomial.eval_X] decide /-- The verifier samples rโ‚€ = 2, outside {0, 1}. Uniform over โ„ค/7โ„ค in the real protocol; 2 is a deliberate choice here, any of 2, โ€ฆ, 6 works. The new target vโ‚ = gโ‚€(2) is not among the values the slices provided: it is computed from the polynomial itself. -/ example : gโ‚€.eval 2 = 2 := by simp only [gโ‚€, Polynomial.eval_add, Polynomial.eval_mul, Polynomial.eval_pow, Polynomial.eval_C, Polynomial.eval_X] decide -- Off the nodes, concretely: gโ‚€(2) is neither gโ‚€(0) nor gโ‚€(1). example : gโ‚€.eval 2 โ‰  gโ‚€.eval 0 โˆง gโ‚€.eval 2 โ‰  gโ‚€.eval 1 := by simp only [gโ‚€, Polynomial.eval_add, Polynomial.eval_mul, Polynomial.eval_pow, Polynomial.eval_C, Polynomial.eval_X] decide -- ============================================================================ -- Step 2: Round 2, the pinned slice -- ============================================================================ /-- The honest round-2 polynomial: gโ‚(t) = p(2, t) = 5t + 2. Term by term, with xโ‚€ pinned to the challenge rโ‚€ = 2 (no summation left; only xโ‚ remains, renamed t): term of p: xโ‚€ยฒยทxโ‚ xโ‚€ xโ‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ at xโ‚€ = 2: 4ยทt + 2 + t = 5t + 2 = gโ‚(t) The cross term xโ‚€ยฒยทxโ‚ becomes 4ยทt because xโ‚€ยฒ = 2ยฒ = 4 in โ„ค/7โ„ค. -/ noncomputable def gโ‚ : Polynomial (ZMod 7) := Polynomial.C 5 * Polynomial.X + Polynomial.C 2 -- gโ‚ is the pinned slice p(2, ยท) at both interpolation nodes. example : gโ‚.eval 0 = eval ![2, 0] p := by simp only [gโ‚, p, Polynomial.eval_add, Polynomial.eval_mul, Polynomial.eval_C, Polynomial.eval_X, eval_add, eval_mul, eval_pow, eval_X] decide example : gโ‚.eval 1 = eval ![2, 1] p := by simp only [gโ‚, p, Polynomial.eval_add, Polynomial.eval_mul, Polynomial.eval_C, Polynomial.eval_X, eval_add, eval_mul, eval_pow, eval_X] decide -- Round 2 consistency: gโ‚(0) + gโ‚(1) = 2 = vโ‚ = gโ‚€(rโ‚€). example : gโ‚.eval 0 + gโ‚.eval 1 = gโ‚€.eval 2 := by simp only [gโ‚, gโ‚€, Polynomial.eval_add, Polynomial.eval_mul, Polynomial.eval_pow, Polynomial.eval_C, Polynomial.eval_X] decide /-- The verifier samples rโ‚ = 3, again outside {0, 1} and again a deliberate choice rather than a draw. The reduction is complete: the sum claim has become the single evaluation claim p(2, 3) = 3, with v = gโ‚(3). -/ example : gโ‚.eval 3 = 3 := by simp only [gโ‚, Polynomial.eval_add, Polynomial.eval_mul, Polynomial.eval_C, Polynomial.eval_X] decide -- ============================================================================ -- Step 3: The final check -- ============================================================================ /-- The end of the chain: p(2, 3) = 3 = v, checked directly against the polynomial. In the composed protocol this value comes from opening the polynomial commitment at r = (2, 3) instead (BiniusToy.lean Step 3). -/ example : eval ![2, 3] p = 3 := by simp only [p, eval_add, eval_mul, eval_pow, eval_X]; decide end Examples.Sumcheck