binius_core/protocols/evalcheck/
verify.rs1use std::{iter, mem};
4
5use binius_field::{Field, TowerField, util::inner_product_unchecked};
6use getset::{Getters, MutGetters};
7use itertools::chain;
8use tracing::instrument;
9
10use super::{
11 EvalPoint, deserialize_evalcheck_proof,
12 error::{Error, VerificationError},
13 evalcheck::{EvalcheckHint, EvalcheckMultilinearClaim},
14 subclaims::{
15 add_bivariate_sumcheck_to_constraints, add_composite_sumcheck_to_constraints,
16 packed_sumcheck_meta, shifted_sumcheck_meta,
17 },
18};
19use crate::{
20 fiat_shamir::Challenger,
21 oracle::{
22 ConstraintSetBuilder, Error as OracleError, MultilinearOracleSet, MultilinearPolyVariant,
23 OracleId, SizedConstraintSet,
24 },
25 polynomial::MultivariatePoly,
26 transcript::VerifierTranscript,
27 transparent::select_row::SelectRow,
28};
29
30#[derive(Getters, MutGetters)]
36pub struct EvalcheckVerifier<'a, F>
37where
38 F: TowerField,
39{
40 pub(crate) oracles: &'a mut MultilinearOracleSet<F>,
43
44 #[getset(get = "pub", get_mut = "pub")]
46 committed_eval_claims: Vec<EvalcheckMultilinearClaim<F>>,
47
48 new_sumcheck_constraints: Vec<ConstraintSetBuilder<F>>,
50
51 new_mlechecks_constraints: Vec<(EvalPoint<F>, ConstraintSetBuilder<F>)>,
53
54 round_claims: Vec<EvalcheckMultilinearClaim<F>>,
56}
57
58impl<'a, F: TowerField> EvalcheckVerifier<'a, F> {
59 pub const fn new(oracles: &'a mut MultilinearOracleSet<F>) -> Self {
63 Self {
64 oracles,
65 committed_eval_claims: Vec::new(),
66 new_sumcheck_constraints: Vec::new(),
67 new_mlechecks_constraints: Vec::new(),
68 round_claims: Vec::new(),
69 }
70 }
71
72 pub fn take_new_sumcheck_constraints(
74 &mut self,
75 ) -> Result<Vec<SizedConstraintSet<F>>, OracleError> {
76 self.new_sumcheck_constraints
77 .iter_mut()
78 .map(|builder| mem::take(builder).build_one(self.oracles))
79 .filter(|constraint| !matches!(constraint, Err(OracleError::EmptyConstraintSet)))
80 .collect()
81 }
82
83 pub fn take_new_mlechecks_constraints(
85 &mut self,
86 ) -> Result<ConstraintSetsEqIndPoints<F>, OracleError> {
87 let new_mlechecks_constraints = std::mem::take(&mut self.new_mlechecks_constraints);
88
89 let mut eq_ind_challenges = Vec::with_capacity(new_mlechecks_constraints.len());
90 let mut constraint_sets = Vec::with_capacity(new_mlechecks_constraints.len());
91
92 for (ep, builder) in new_mlechecks_constraints {
93 eq_ind_challenges.push(ep.to_vec());
94 constraint_sets.push(builder.build_one(self.oracles)?)
95 }
96 Ok(ConstraintSetsEqIndPoints {
97 eq_ind_challenges,
98 constraint_sets,
99 })
100 }
101
102 #[instrument(skip_all, name = "EvalcheckVerifierState::verify", level = "debug")]
109 pub fn verify<Challenger_: Challenger>(
110 &mut self,
111 evalcheck_claims: impl IntoIterator<Item = EvalcheckMultilinearClaim<F>>,
112 transcript: &mut VerifierTranscript<Challenger_>,
113 ) -> Result<(), Error> {
114 self.round_claims.clear();
115 for claim in evalcheck_claims {
116 self.verify_multilinear(claim, transcript)?;
117 }
118 Ok(())
119 }
120
121 fn verify_multilinear<Challenger_: Challenger>(
122 &mut self,
123 evalcheck_claim: EvalcheckMultilinearClaim<F>,
124 transcript: &mut VerifierTranscript<Challenger_>,
125 ) -> Result<(), Error> {
126 let evalcheck_proof = deserialize_evalcheck_proof(&mut transcript.message())?;
127
128 if let EvalcheckHint::DuplicateClaim(index) = evalcheck_proof {
131 if let Some(expected_claim) = self.round_claims.get(index as usize) {
132 if *expected_claim == evalcheck_claim {
133 return Ok(());
134 }
135 }
136 return Err(VerificationError::DuplicateClaimMismatch.into());
137 }
138
139 self.verify_multilinear_skip_duplicate_check(evalcheck_claim, transcript)
140 }
141
142 fn verify_multilinear_skip_duplicate_check<Challenger_: Challenger>(
143 &mut self,
144 evalcheck_claim: EvalcheckMultilinearClaim<F>,
145 transcript: &mut VerifierTranscript<Challenger_>,
146 ) -> Result<(), Error> {
147 self.round_claims.push(evalcheck_claim.clone());
148
149 let EvalcheckMultilinearClaim {
150 id,
151 eval_point,
152 eval,
153 } = evalcheck_claim;
154
155 let multilinear = &self.oracles[id];
156 let multilinear_label = multilinear.label();
157 match multilinear.variant {
158 MultilinearPolyVariant::Transparent(ref inner) => {
159 let actual_eval = inner.poly().evaluate(&eval_point)?;
160 if actual_eval != eval {
161 return Err(VerificationError::IncorrectEvaluation(multilinear_label).into());
162 }
163 }
164 MultilinearPolyVariant::Structured(ref inner) => {
165 let eval_point: &[F] = &eval_point;
168 let n_pad_zeros = inner.n_vars() - eval_point.len();
169 let query = eval_point
170 .iter()
171 .copied()
172 .chain(iter::repeat_n(F::ZERO, n_pad_zeros))
173 .collect::<Vec<_>>();
174 let actual_eval = inner.evaluate(&query)?;
175 if actual_eval != eval {
176 return Err(VerificationError::IncorrectEvaluation(multilinear_label).into());
177 }
178 }
179
180 MultilinearPolyVariant::Committed => {
181 self.committed_eval_claims.push(EvalcheckMultilinearClaim {
182 id,
183 eval_point,
184 eval,
185 });
186 }
187
188 MultilinearPolyVariant::Repeating { id, log_count } => {
189 let n_vars = eval_point.len() - log_count;
190 self.verify_multilinear(
191 EvalcheckMultilinearClaim {
192 id,
193 eval_point: eval_point.slice(0..n_vars),
194 eval,
195 },
196 transcript,
197 )?;
198 }
199
200 MultilinearPolyVariant::Projected(ref projected) => {
201 let new_eval_point = {
202 let (lo, hi) = eval_point.split_at(projected.start_index());
203 chain!(lo, projected.values(), hi)
204 .copied()
205 .collect::<Vec<_>>()
206 };
207
208 self.verify_multilinear(
209 EvalcheckMultilinearClaim {
210 id: projected.id(),
211 eval_point: new_eval_point.into(),
212 eval,
213 },
214 transcript,
215 )?;
216 }
217
218 MultilinearPolyVariant::Shifted(ref shifted) => {
219 let shifted = shifted.clone();
220 let meta = shifted_sumcheck_meta(self.oracles, &shifted, &eval_point)?;
221 add_bivariate_sumcheck_to_constraints(
222 &meta,
223 &mut self.new_sumcheck_constraints,
224 shifted.block_size(),
225 eval,
226 )
227 }
228
229 MultilinearPolyVariant::Packed(ref packed) => {
230 let packed = packed.clone();
231 let meta = packed_sumcheck_meta(self.oracles, &packed, &eval_point)?;
232 add_bivariate_sumcheck_to_constraints(
233 &meta,
234 &mut self.new_sumcheck_constraints,
235 packed.log_degree(),
236 eval,
237 )
238 }
239
240 MultilinearPolyVariant::LinearCombination(ref linear_combination) => {
241 let linear_combination = linear_combination.clone();
242 let evals = linear_combination
243 .polys()
244 .map(|sub_oracle_id| {
245 self.verify_multilinear_subclaim(
246 sub_oracle_id,
247 eval_point.clone(),
248 transcript,
249 )
250 })
251 .collect::<Result<Vec<_>, _>>()?;
252
253 let actual_eval = linear_combination.offset()
255 + inner_product_unchecked::<F, F>(evals, linear_combination.coefficients());
256
257 if actual_eval != eval {
258 return Err(VerificationError::IncorrectEvaluation(multilinear_label).into());
259 }
260 }
261 MultilinearPolyVariant::ZeroPadded(ref padded) => {
262 let subclaim_eval_point = chain!(
263 &eval_point[..padded.start_index()],
264 &eval_point[padded.start_index() + padded.n_pad_vars()..],
265 )
266 .copied()
267 .collect::<Vec<_>>();
268
269 let zs =
270 &eval_point[padded.start_index()..padded.start_index() + padded.n_pad_vars()];
271 let select_row = SelectRow::new(zs.len(), padded.nonzero_index())?;
272 let select_row_term = select_row
273 .evaluate(zs)
274 .expect("select_row is constructor with zs.len() variables");
275
276 let inner_eval = match select_row_term.invert() {
277 Some(invert) => eval * invert,
278 None if eval.is_zero() => return Ok(()),
279 None => {
280 return Err(
281 VerificationError::IncorrectEvaluation(multilinear_label).into()
282 );
283 }
284 };
285
286 self.verify_multilinear(
287 EvalcheckMultilinearClaim {
288 id: padded.id(),
289 eval_point: subclaim_eval_point.into(),
290 eval: inner_eval,
291 },
292 transcript,
293 )?;
294 }
295 MultilinearPolyVariant::Composite(ref composite) => {
296 let position = transcript.message().read::<u32>()? as usize;
297
298 if let Some((constraints_eval_point, _)) =
299 self.new_mlechecks_constraints.get(position)
300 {
301 if *constraints_eval_point != eval_point {
302 return Err(VerificationError::MLECheckConstraintSetPositionMismatch.into());
303 }
304 }
305
306 add_composite_sumcheck_to_constraints(
307 position,
308 &eval_point,
309 &mut self.new_mlechecks_constraints,
310 composite,
311 eval,
312 );
313 }
314 }
315
316 Ok(())
317 }
318
319 fn verify_multilinear_subclaim<Challenger_: Challenger>(
320 &mut self,
321 oracle_id: OracleId,
322 eval_point: EvalPoint<F>,
323 transcript: &mut VerifierTranscript<Challenger_>,
324 ) -> Result<F, Error> {
325 let subproof = deserialize_evalcheck_proof(&mut transcript.message())?;
328 match subproof {
329 EvalcheckHint::DuplicateClaim(index) => {
330 let index = index as usize;
331 if self.round_claims[index].id != oracle_id
332 || self.round_claims[index].eval_point != eval_point
333 {
334 return Err(VerificationError::DuplicateClaimMismatch.into());
335 }
336
337 Ok(self.round_claims[index].eval)
338 }
339 EvalcheckHint::NewClaim => {
340 let eval = transcript.message().read_scalar()?;
341 let subclaim = EvalcheckMultilinearClaim {
342 id: oracle_id,
343 eval_point,
344 eval,
345 };
346 self.verify_multilinear_skip_duplicate_check(subclaim, transcript)?;
347 Ok(eval)
348 }
349 }
350 }
351}
352
353pub struct ConstraintSetsEqIndPoints<F: Field> {
354 pub eq_ind_challenges: Vec<Vec<F>>,
355 pub constraint_sets: Vec<SizedConstraintSet<F>>,
356}