Skip to main content

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}