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,
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
141				let new_eval_point = {
142					let idx = projected.start_index();
143					let mut new_eval_point = eval_point[0..idx].to_vec();
144					new_eval_point.extend(values.clone());
145					new_eval_point.extend(eval_point[idx..].to_vec());
146					new_eval_point
147				};
148
149				let new_claim = EvalcheckMultilinearClaim {
150					id,
151					eval_point: new_eval_point.into(),
152					eval,
153				};
154
155				self.verify_multilinear(new_claim, evalcheck_proof)?;
156			}
157
158			MultilinearPolyVariant::Shifted(shifted) => {
159				if evalcheck_proof != EvalcheckProof::Shifted {
160					return Err(VerificationError::SubproofMismatch.into());
161				}
162
163				let meta = shifted_sumcheck_meta(self.oracles, &shifted, &eval_point)?;
164				add_bivariate_sumcheck_to_constraints(
165					&meta,
166					&mut self.new_sumcheck_constraints,
167					shifted.block_size(),
168					eval,
169				)
170			}
171
172			MultilinearPolyVariant::Packed(packed) => {
173				if evalcheck_proof != EvalcheckProof::Packed {
174					return Err(VerificationError::SubproofMismatch.into());
175				}
176
177				let meta = packed_sumcheck_meta(self.oracles, &packed, &eval_point)?;
178				add_bivariate_sumcheck_to_constraints(
179					&meta,
180					&mut self.new_sumcheck_constraints,
181					packed.log_degree(),
182					eval,
183				)
184			}
185
186			MultilinearPolyVariant::LinearCombination(linear_combination) => {
187				let subproofs = match evalcheck_proof {
188					EvalcheckProof::LinearCombination { subproofs } => subproofs,
189					_ => return Err(VerificationError::SubproofMismatch.into()),
190				};
191
192				if subproofs.len() != linear_combination.n_polys() {
193					return Err(VerificationError::SubproofMismatch.into());
194				}
195
196				// Verify the evaluation of the linear combination over the claimed evaluations
197				let actual_eval = linear_combination.offset()
198					+ inner_product_unchecked::<F, F>(
199						subproofs.iter().map(|(eval, _)| *eval),
200						linear_combination.coefficients(),
201					);
202
203				if actual_eval != eval {
204					return Err(VerificationError::IncorrectEvaluation(multilinear.label()).into());
205				}
206
207				subproofs
208					.into_iter()
209					.zip(linear_combination.polys())
210					.try_for_each(|((eval, subproof), suboracle_id)| {
211						self.verify_multilinear_subclaim(eval, subproof, suboracle_id, &eval_point)
212					})?;
213			}
214			MultilinearPolyVariant::ZeroPadded(inner) => {
215				let (inner_eval, subproof) = match evalcheck_proof {
216					EvalcheckProof::ZeroPadded(eval, subproof) => (eval, subproof),
217					_ => return Err(VerificationError::SubproofMismatch.into()),
218				};
219
220				let inner_n_vars = self.oracles.n_vars(inner);
221
222				let (subclaim_eval_point, zs) = eval_point.split_at(inner_n_vars);
223
224				let mut extrapolate_eval = inner_eval;
225
226				for z in zs {
227					extrapolate_eval =
228						extrapolate_line_scalar::<F, F>(F::ZERO, extrapolate_eval, *z);
229				}
230
231				if extrapolate_eval != eval {
232					return Err(VerificationError::IncorrectEvaluation(multilinear.label()).into());
233				}
234
235				self.verify_multilinear_subclaim(
236					inner_eval,
237					*subproof,
238					inner,
239					subclaim_eval_point,
240				)?;
241			}
242			MultilinearPolyVariant::Composite(composition) => {
243				if evalcheck_proof != EvalcheckProof::CompositeMLE {
244					return Err(VerificationError::SubproofMismatch.into());
245				}
246
247				let meta = composite_sumcheck_meta(self.oracles, &eval_point)?;
248				add_composite_sumcheck_to_constraints(
249					&meta,
250					&mut self.new_sumcheck_constraints,
251					&composition,
252					eval,
253				)
254			}
255		}
256
257		Ok(())
258	}
259
260	fn verify_multilinear_subclaim(
261		&mut self,
262		eval: F,
263		subproof: EvalcheckProof<F>,
264		oracle_id: OracleId,
265		eval_point: &[F],
266	) -> Result<(), Error> {
267		let subclaim = EvalcheckMultilinearClaim {
268			id: oracle_id,
269			eval_point: eval_point.into(),
270			eval,
271		};
272		self.verify_multilinear(subclaim, subproof)
273	}
274}