binius_core/protocols/evalcheck/
verify.rs

1// Copyright 2024-2025 Irreducible Inc.
2
3use std::mem;
4
5use binius_field::{util::inner_product_unchecked, TowerField};
6use binius_math::extrapolate_line_scalar;
7use getset::{Getters, MutGetters};
8use tracing::instrument;
9
10use super::{
11	error::{Error, VerificationError},
12	evalcheck::{EvalcheckMultilinearClaim, EvalcheckProof},
13	subclaims::{
14		add_bivariate_sumcheck_to_constraints, add_composite_sumcheck_to_constraints,
15		composite_sumcheck_meta, packed_sumcheck_meta, shifted_sumcheck_meta,
16	},
17};
18use crate::oracle::{
19	ConstraintSet, ConstraintSetBuilder, Error as OracleError, MultilinearOracleSet,
20	MultilinearPolyVariant, OracleId, ProjectionVariant,
21};
22
23/// A mutable verifier state.
24///
25/// Can be persisted across [`EvalcheckVerifier::verify`] invocations. Accumulates
26/// `new_sumchecks` bivariate sumcheck constraints, as well as holds mutable references to
27/// the trace (to which new oracles & multilinears may be added during verification)
28#[derive(Getters, MutGetters)]
29pub struct EvalcheckVerifier<'a, F>
30where
31	F: TowerField,
32{
33	pub(crate) oracles: &'a mut MultilinearOracleSet<F>,
34
35	#[getset(get = "pub", get_mut = "pub")]
36	committed_eval_claims: Vec<EvalcheckMultilinearClaim<F>>,
37
38	new_sumcheck_constraints: Vec<ConstraintSetBuilder<F>>,
39}
40
41impl<'a, F: TowerField> EvalcheckVerifier<'a, F> {
42	/// Create a new verifier state from a mutable reference to the oracle set
43	/// (it needs to be mutable because `new_sumcheck` reduction may add new
44	/// oracles & multilinears)
45	pub const fn new(oracles: &'a mut MultilinearOracleSet<F>) -> Self {
46		Self {
47			oracles,
48			committed_eval_claims: Vec::new(),
49			new_sumcheck_constraints: Vec::new(),
50		}
51	}
52
53	/// A helper method to move out sumcheck constraints
54	pub fn take_new_sumcheck_constraints(&mut self) -> Result<Vec<ConstraintSet<F>>, OracleError> {
55		self.new_sumcheck_constraints
56			.iter_mut()
57			.map(|builder| mem::take(builder).build_one(self.oracles))
58			.filter(|constraint| !matches!(constraint, Err(OracleError::EmptyConstraintSet)))
59			.rev()
60			.collect()
61	}
62
63	/// Verify an evalcheck claim.
64	///
65	/// See [`EvalcheckProver::prove`](`super::prove::EvalcheckProver::prove`) docs for comments.
66	#[instrument(skip_all, name = "EvalcheckVerifierState::verify", level = "debug")]
67	pub fn verify(
68		&mut self,
69		evalcheck_claims: Vec<EvalcheckMultilinearClaim<F>>,
70		evalcheck_proofs: Vec<EvalcheckProof<F>>,
71	) -> Result<(), Error> {
72		for (claim, proof) in evalcheck_claims
73			.into_iter()
74			.zip(evalcheck_proofs.into_iter())
75		{
76			self.verify_multilinear(claim, proof)?;
77		}
78
79		Ok(())
80	}
81
82	fn verify_multilinear(
83		&mut self,
84		evalcheck_claim: EvalcheckMultilinearClaim<F>,
85		evalcheck_proof: EvalcheckProof<F>,
86	) -> Result<(), Error> {
87		let EvalcheckMultilinearClaim {
88			id,
89			eval_point,
90			eval,
91		} = evalcheck_claim;
92
93		let multilinear = self.oracles.oracle(id);
94		match multilinear.variant.clone() {
95			MultilinearPolyVariant::Transparent(inner) => {
96				if evalcheck_proof != EvalcheckProof::Transparent {
97					return Err(VerificationError::SubproofMismatch.into());
98				}
99
100				let actual_eval = inner.poly().evaluate(&eval_point)?;
101				if actual_eval != eval {
102					return Err(VerificationError::IncorrectEvaluation(
103						self.oracles.oracle(id).label(),
104					)
105					.into());
106				}
107			}
108
109			MultilinearPolyVariant::Committed => {
110				if evalcheck_proof != EvalcheckProof::Committed {
111					return Err(VerificationError::SubproofMismatch.into());
112				}
113
114				let claim = EvalcheckMultilinearClaim {
115					id: multilinear.id(),
116					eval_point,
117					eval,
118				};
119
120				self.committed_eval_claims.push(claim);
121			}
122
123			MultilinearPolyVariant::Repeating { id, .. } => {
124				let subproof = match evalcheck_proof {
125					EvalcheckProof::Repeating(subproof) => subproof,
126					_ => return Err(VerificationError::SubproofMismatch.into()),
127				};
128				let n_vars = self.oracles.n_vars(id);
129				let subclaim = EvalcheckMultilinearClaim {
130					id,
131					eval_point: eval_point[..n_vars].into(),
132					eval,
133				};
134
135				self.verify_multilinear(subclaim, *subproof)?;
136			}
137
138			MultilinearPolyVariant::Projected(projected) => {
139				let (id, values) = (projected.id(), projected.values());
140				let eval_point = match projected.projection_variant() {
141					ProjectionVariant::LastVars => {
142						let mut eval_point = eval_point.to_vec();
143						eval_point.extend(values);
144						eval_point
145					}
146					ProjectionVariant::FirstVars => {
147						values.iter().copied().chain(eval_point.to_vec()).collect()
148					}
149				};
150
151				let new_claim = EvalcheckMultilinearClaim {
152					id,
153					eval_point: eval_point.into(),
154					eval,
155				};
156
157				self.verify_multilinear(new_claim, evalcheck_proof)?;
158			}
159
160			MultilinearPolyVariant::Shifted(shifted) => {
161				if evalcheck_proof != EvalcheckProof::Shifted {
162					return Err(VerificationError::SubproofMismatch.into());
163				}
164
165				let meta = shifted_sumcheck_meta(self.oracles, &shifted, &eval_point)?;
166				add_bivariate_sumcheck_to_constraints(
167					meta,
168					&mut self.new_sumcheck_constraints,
169					shifted.block_size(),
170					eval,
171				)
172			}
173
174			MultilinearPolyVariant::Packed(packed) => {
175				if evalcheck_proof != EvalcheckProof::Packed {
176					return Err(VerificationError::SubproofMismatch.into());
177				}
178
179				let meta = packed_sumcheck_meta(self.oracles, &packed, &eval_point)?;
180				add_bivariate_sumcheck_to_constraints(
181					meta,
182					&mut self.new_sumcheck_constraints,
183					packed.log_degree(),
184					eval,
185				)
186			}
187
188			MultilinearPolyVariant::LinearCombination(linear_combination) => {
189				let subproofs = match evalcheck_proof {
190					EvalcheckProof::LinearCombination { subproofs } => subproofs,
191					_ => return Err(VerificationError::SubproofMismatch.into()),
192				};
193
194				if subproofs.len() != linear_combination.n_polys() {
195					return Err(VerificationError::SubproofMismatch.into());
196				}
197
198				// Verify the evaluation of the linear combination over the claimed evaluations
199				let actual_eval = linear_combination.offset()
200					+ inner_product_unchecked::<F, F>(
201						subproofs.iter().map(|(eval, _)| *eval),
202						linear_combination.coefficients(),
203					);
204
205				if actual_eval != eval {
206					return Err(VerificationError::IncorrectEvaluation(multilinear.label()).into());
207				}
208
209				subproofs
210					.into_iter()
211					.zip(linear_combination.polys())
212					.try_for_each(|((eval, subproof), suboracle_id)| {
213						self.verify_multilinear_subclaim(eval, subproof, suboracle_id, &eval_point)
214					})?;
215			}
216			MultilinearPolyVariant::ZeroPadded(inner) => {
217				let (inner_eval, subproof) = match evalcheck_proof {
218					EvalcheckProof::ZeroPadded(eval, subproof) => (eval, subproof),
219					_ => return Err(VerificationError::SubproofMismatch.into()),
220				};
221
222				let inner_n_vars = self.oracles.n_vars(inner);
223
224				let (subclaim_eval_point, zs) = eval_point.split_at(inner_n_vars);
225
226				let mut extrapolate_eval = inner_eval;
227
228				for z in zs {
229					extrapolate_eval =
230						extrapolate_line_scalar::<F, F>(F::ZERO, extrapolate_eval, *z);
231				}
232
233				if extrapolate_eval != eval {
234					return Err(VerificationError::IncorrectEvaluation(multilinear.label()).into());
235				}
236
237				self.verify_multilinear_subclaim(
238					inner_eval,
239					*subproof,
240					inner,
241					subclaim_eval_point,
242				)?;
243			}
244			MultilinearPolyVariant::Composite(composition) => {
245				if evalcheck_proof != EvalcheckProof::CompositeMLE {
246					return Err(VerificationError::SubproofMismatch.into());
247				}
248
249				let meta = composite_sumcheck_meta(self.oracles, &eval_point)?;
250				add_composite_sumcheck_to_constraints(
251					meta,
252					&mut self.new_sumcheck_constraints,
253					&composition,
254					eval,
255				)
256			}
257		}
258
259		Ok(())
260	}
261
262	fn verify_multilinear_subclaim(
263		&mut self,
264		eval: F,
265		subproof: EvalcheckProof<F>,
266		oracle_id: OracleId,
267		eval_point: &[F],
268	) -> Result<(), Error> {
269		let subclaim = EvalcheckMultilinearClaim {
270			id: oracle_id,
271			eval_point: eval_point.into(),
272			eval,
273		};
274		self.verify_multilinear(subclaim, subproof)
275	}
276}