binius_ip_prover/sumcheck/common.rs
1// Copyright 2023-2025 Irreducible Inc.
2
3use binius_field::Field;
4use binius_ip::sumcheck::RoundCoeffs;
5use either::Either;
6
7/// A sumcheck prover with a round-by-round execution interface.
8///
9/// Sumcheck prover logic is accessed via a trait because important optimizations are available
10/// depending on the structure of the multivariate polynomial that the protocol targets. For
11/// example, [Gruen24] observes a significant optimization available to the sumcheck prover when
12/// the multivariate is the product of a multilinear composite and an equality indicator
13/// polynomial, which arises in the zerocheck protocol.
14///
15/// The trait exposes a round-by-round interface so that protocol execution logic that drives the
16/// prover can interleave the executions of the interactive protocol, for example in the case of
17/// batching several sumcheck protocols.
18///
19/// The caller must make a specific sequence of calls to the provers. For a prover where
20/// [`Self::n_vars`] is $n$, the caller must call [`Self::execute`] and then [`Self::fold`] $n$
21/// times, and finally call [`Self::finish`]. If the calls aren't made in that order, the prover
22/// will panic.
23///
24/// This trait is _not_ object-safe.
25///
26/// [Gruen24]: <https://eprint.iacr.org/2024/108>
27pub trait SumcheckProver<F: Field> {
28 /// The number of variables in the remaining multivariate polynomial.
29 ///
30 /// The number of variables decrements after each [`Self::fold`] call, as that binds one free
31 /// variable with a concrete challenge.
32 fn n_vars(&self) -> usize;
33
34 /// Computes the prover messages for this round as a univariate polynomial.
35 ///
36 /// If [`Self::fold`] has already been called on the prover with the values $r_0$, ...,
37 /// $r_{k-1}$ and the sumcheck prover is proving the sums of the composite polynomials $C_0,
38 /// ..., C_{m-1}$, then the output of this method for low-to-high evaluation order would be:
39 ///
40 /// $$
41 /// R_i = \sum_{v \in B_{n-k-1}} C_i(r_0, ..., r_{k-1}, X, \{v\}), i \in \[0, ..., m-1\]
42 /// $$
43 ///
44 /// For high-to-low evaluation order the variables are specified in reverse order (starting with
45 /// the highest indexed one) and hypercube sums are performed over the lower indexed variables.
46 ///
47 /// One entry per claim the prover carries.
48 fn execute(&mut self) -> Vec<RoundCoeffs<F>>;
49
50 /// Folds the sumcheck multilinears with a new verifier challenge.
51 fn fold(&mut self, challenge: F);
52
53 /// Finishes the sumcheck proving protocol and returns the evaluations of all multilinears at
54 /// the challenge point.
55 fn finish(self) -> Vec<F>;
56}
57
58impl<F, L, R> SumcheckProver<F> for Either<L, R>
59where
60 F: Field,
61 L: SumcheckProver<F>,
62 R: SumcheckProver<F>,
63{
64 fn n_vars(&self) -> usize {
65 either::for_both!(self, inner => inner.n_vars())
66 }
67
68 fn execute(&mut self) -> Vec<RoundCoeffs<F>> {
69 either::for_both!(self, inner => inner.execute())
70 }
71
72 fn fold(&mut self, challenge: F) {
73 either::for_both!(self, inner => inner.fold(challenge));
74 }
75
76 fn finish(self) -> Vec<F> {
77 either::for_both!(self, inner => inner.finish())
78 }
79}
80
81/// A prover for the MLE-check variant of the sumcheck protocol.
82///
83/// The prover argues that a claimed value $s$ is the equality-weighted sum
84///
85/// $$
86/// s = \sum_{v \in B_n} F(v) \cdot eq(v, z)
87/// $$
88///
89/// over the hypercube $B_n$, for a point $z$ agreed in advance.
90/// The weight $eq(v, z)$ is $1$ where $v = z$ and $0$ at every other vertex.
91/// For multilinear $F$ the sum is then just $F(z)$.
92///
93/// The protocol runs one round per variable:
94///
95/// - The prover sends a univariate polynomial for the round.
96/// - The verifier answers with a random challenge, binding that variable.
97/// - After the last round the prover reports the evaluations it reached.
98///
99/// Driving it out of that order panics.
100///
101/// This trait is _not_ object-safe.
102///
103/// See [`binius_ip::mlecheck::verify`] for the verifier side, and [Gruen24] for the optimization.
104///
105/// A plain sumcheck proves the same claim by folding the weight into the summand.
106/// This protocol leaves the weight out of the polynomial it sends, which is where it saves work.
107/// The two send different polynomials:
108///
109/// ```text
110/// plain sumcheck R (X) claim = R(0) + R(1)
111/// MLE-check R'(X) claim = (1 - alpha) * R'(0) + alpha * R'(1)
112/// ```
113///
114/// with `alpha` this round's coordinate of the point, and `R = R' * eq(X, alpha)`.
115///
116/// Each verifier rebuilds a different missing coefficient.
117/// One protocol's polynomial fails the other's verifier, so neither trait inherits from the other.
118/// Multiplying the weight back in converts a prover here into a plain sumcheck one.
119/// The adaptor in this module that does so is the only sound bridge.
120///
121/// [Gruen24]: <https://eprint.iacr.org/2024/108>
122pub trait MleCheckProver<F: Field> {
123 /// The number of variables still free.
124 ///
125 /// Each round binds one variable to a challenge, so this drops by one per round.
126 fn n_vars(&self) -> usize;
127
128 /// Computes this round's message, one univariate polynomial per claim.
129 ///
130 /// With $r_0$, ..., $r_{k-1}$ already bound, for claims over $C_0, ..., C_{m-1}$, the output in
131 /// low-to-high evaluation order is
132 ///
133 /// $$
134 /// R'_i = \sum_{v \in B_{n-k-1}} C_i(r_0, ..., r_{k-1}, X, \{v\}) \cdot eq(\{v\}, z')
135 /// $$
136 ///
137 /// where $z'$ is the part of the point summed over.
138 ///
139 /// The coordinate bound this round is left out of the weight.
140 /// Multiplying it back gives the round polynomial $R_i(X) = R'_i(X) \cdot eq(X, z_k)$.
141 ///
142 /// For high-to-low order the variables are taken in reverse and summed over the lower indices.
143 fn execute(&mut self) -> Vec<RoundCoeffs<F>>;
144
145 /// Binds this round's variable to the verifier challenge.
146 fn fold(&mut self, challenge: F);
147
148 /// Consumes the prover and returns every multilinear's evaluation at the challenge point.
149 fn finish(self) -> Vec<F>;
150
151 /// The still-unbound part of the evaluation point, one coordinate per free variable.
152 fn eval_point(&self) -> &[F];
153}
154
155impl<F, L, R> MleCheckProver<F> for Either<L, R>
156where
157 F: Field,
158 L: MleCheckProver<F>,
159 R: MleCheckProver<F>,
160{
161 fn n_vars(&self) -> usize {
162 either::for_both!(self, inner => inner.n_vars())
163 }
164
165 fn execute(&mut self) -> Vec<RoundCoeffs<F>> {
166 either::for_both!(self, inner => inner.execute())
167 }
168
169 fn fold(&mut self, challenge: F) {
170 either::for_both!(self, inner => inner.fold(challenge));
171 }
172
173 fn finish(self) -> Vec<F> {
174 either::for_both!(self, inner => inner.finish())
175 }
176
177 fn eval_point(&self) -> &[F] {
178 either::for_both!(self, inner => inner.eval_point())
179 }
180}