binius_core/protocols/evalcheck/
verify.rs1use std::mem;
4
5use binius_field::{util::inner_product_unchecked, TowerField};
6use getset::{Getters, MutGetters};
7use itertools::chain;
8use tracing::instrument;
9
10use super::{
11 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 composite_mlecheck_meta, packed_sumcheck_meta, shifted_sumcheck_meta,
17 },
18 EvalPoint,
19};
20use crate::{
21 fiat_shamir::Challenger,
22 oracle::{
23 ConstraintSet, ConstraintSetBuilder, Error as OracleError, MultilinearOracleSet,
24 MultilinearPolyVariant, OracleId,
25 },
26 polynomial::MultivariatePoly,
27 transcript::VerifierTranscript,
28 transparent::select_row::SelectRow,
29};
30
31#[derive(Getters, MutGetters)]
37pub struct EvalcheckVerifier<'a, F>
38where
39 F: TowerField,
40{
41 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 round_claims: Vec<EvalcheckMultilinearClaim<F>>,
53}
54
55impl<'a, F: TowerField> EvalcheckVerifier<'a, F> {
56 pub const fn new(oracles: &'a mut MultilinearOracleSet<F>) -> Self {
60 Self {
61 oracles,
62 committed_eval_claims: Vec::new(),
63 new_sumcheck_constraints: Vec::new(),
64 round_claims: Vec::new(),
65 }
66 }
67
68 pub fn take_new_sumcheck_constraints(&mut self) -> Result<Vec<ConstraintSet<F>>, OracleError> {
70 self.new_sumcheck_constraints
71 .iter_mut()
72 .map(|builder| mem::take(builder).build_one(self.oracles))
73 .filter(|constraint| !matches!(constraint, Err(OracleError::EmptyConstraintSet)))
74 .collect()
75 }
76
77 #[instrument(skip_all, name = "EvalcheckVerifierState::verify", level = "debug")]
82 pub fn verify<Challenger_: Challenger>(
83 &mut self,
84 evalcheck_claims: impl IntoIterator<Item = EvalcheckMultilinearClaim<F>>,
85 transcript: &mut VerifierTranscript<Challenger_>,
86 ) -> Result<(), Error> {
87 self.round_claims.clear();
88 for claim in evalcheck_claims {
89 self.verify_multilinear(claim, transcript)?;
90 }
91 Ok(())
92 }
93
94 fn verify_multilinear<Challenger_: Challenger>(
95 &mut self,
96 evalcheck_claim: EvalcheckMultilinearClaim<F>,
97 transcript: &mut VerifierTranscript<Challenger_>,
98 ) -> Result<(), Error> {
99 let evalcheck_proof = deserialize_evalcheck_proof(&mut transcript.message())?;
100
101 if let EvalcheckHint::DuplicateClaim(index) = evalcheck_proof {
104 if let Some(expected_claim) = self.round_claims.get(index as usize) {
105 if *expected_claim == evalcheck_claim {
106 return Ok(());
107 }
108 }
109 return Err(VerificationError::DuplicateClaimMismatch.into());
110 }
111
112 self.verify_multilinear_skip_duplicate_check(evalcheck_claim, transcript)
113 }
114
115 fn verify_multilinear_skip_duplicate_check<Challenger_: Challenger>(
116 &mut self,
117 evalcheck_claim: EvalcheckMultilinearClaim<F>,
118 transcript: &mut VerifierTranscript<Challenger_>,
119 ) -> Result<(), Error> {
120 self.round_claims.push(evalcheck_claim.clone());
121
122 let EvalcheckMultilinearClaim {
123 id,
124 eval_point,
125 eval,
126 } = evalcheck_claim;
127
128 let multilinear = self.oracles.oracle(id);
129 let multilinear_label = multilinear.label();
130 match multilinear.variant {
131 MultilinearPolyVariant::Transparent(inner) => {
132 let actual_eval = inner.poly().evaluate(&eval_point)?;
133 if actual_eval != eval {
134 return Err(VerificationError::IncorrectEvaluation(multilinear_label).into());
135 }
136 }
137
138 MultilinearPolyVariant::Committed => {
139 self.committed_eval_claims.push(EvalcheckMultilinearClaim {
140 id,
141 eval_point,
142 eval,
143 });
144 }
145
146 MultilinearPolyVariant::Repeating { id, log_count } => {
147 let n_vars = eval_point.len() - log_count;
148 self.verify_multilinear(
149 EvalcheckMultilinearClaim {
150 id,
151 eval_point: eval_point.slice(0..n_vars),
152 eval,
153 },
154 transcript,
155 )?;
156 }
157
158 MultilinearPolyVariant::Projected(projected) => {
159 let new_eval_point = {
160 let (lo, hi) = eval_point.split_at(projected.start_index());
161 chain!(lo, projected.values(), hi)
162 .copied()
163 .collect::<Vec<_>>()
164 };
165
166 self.verify_multilinear(
167 EvalcheckMultilinearClaim {
168 id: projected.id(),
169 eval_point: new_eval_point.into(),
170 eval,
171 },
172 transcript,
173 )?;
174 }
175
176 MultilinearPolyVariant::Shifted(shifted) => {
177 let meta = shifted_sumcheck_meta(self.oracles, &shifted, &eval_point)?;
178 add_bivariate_sumcheck_to_constraints(
179 &meta,
180 &mut self.new_sumcheck_constraints,
181 shifted.block_size(),
182 eval,
183 )
184 }
185
186 MultilinearPolyVariant::Packed(packed) => {
187 let meta = packed_sumcheck_meta(self.oracles, &packed, &eval_point)?;
188 add_bivariate_sumcheck_to_constraints(
189 &meta,
190 &mut self.new_sumcheck_constraints,
191 packed.log_degree(),
192 eval,
193 )
194 }
195
196 MultilinearPolyVariant::LinearCombination(linear_combination) => {
197 let evals = linear_combination
198 .polys()
199 .map(|sub_oracle_id| {
200 self.verify_multilinear_subclaim(
201 sub_oracle_id,
202 eval_point.clone(),
203 transcript,
204 )
205 })
206 .collect::<Result<Vec<_>, _>>()?;
207
208 let actual_eval = linear_combination.offset()
210 + inner_product_unchecked::<F, F>(evals, linear_combination.coefficients());
211
212 if actual_eval != eval {
213 return Err(VerificationError::IncorrectEvaluation(multilinear_label).into());
214 }
215 }
216 MultilinearPolyVariant::ZeroPadded(padded) => {
217 let subclaim_eval_point = chain!(
218 &eval_point[..padded.start_index()],
219 &eval_point[padded.start_index() + padded.n_pad_vars()..],
220 )
221 .copied()
222 .collect::<Vec<_>>();
223
224 let zs =
225 &eval_point[padded.start_index()..padded.start_index() + padded.n_pad_vars()];
226 let select_row = SelectRow::new(zs.len(), padded.nonzero_index())?;
227 let select_row_term = select_row
228 .evaluate(zs)
229 .expect("select_row is constructor with zs.len() variables");
230
231 let inner_eval = eval * select_row_term.invert_or_zero();
232
233 self.verify_multilinear(
234 EvalcheckMultilinearClaim {
235 id: padded.id(),
236 eval_point: subclaim_eval_point.into(),
237 eval: inner_eval,
238 },
239 transcript,
240 )?;
241 }
242 MultilinearPolyVariant::Composite(composition) => {
243 let meta = composite_mlecheck_meta(self.oracles, &eval_point)?;
244 add_composite_sumcheck_to_constraints(
245 meta,
246 &mut self.new_sumcheck_constraints,
247 &composition,
248 eval,
249 )
250 }
251 }
252
253 Ok(())
254 }
255
256 fn verify_multilinear_subclaim<Challenger_: Challenger>(
257 &mut self,
258 oracle_id: OracleId,
259 eval_point: EvalPoint<F>,
260 transcript: &mut VerifierTranscript<Challenger_>,
261 ) -> Result<F, Error> {
262 let subproof = deserialize_evalcheck_proof(&mut transcript.message())?;
265 match subproof {
266 EvalcheckHint::DuplicateClaim(index) => {
267 let index = index as usize;
268 if self.round_claims[index].id != oracle_id
269 || self.round_claims[index].eval_point != eval_point
270 {
271 return Err(VerificationError::DuplicateClaimMismatch.into());
272 }
273
274 Ok(self.round_claims[index].eval)
275 }
276 EvalcheckHint::NewClaim => {
277 let eval = transcript.message().read_scalar()?;
278 let subclaim = EvalcheckMultilinearClaim {
279 id: oracle_id,
280 eval_point,
281 eval,
282 };
283 self.verify_multilinear_skip_duplicate_check(subclaim, transcript)?;
284 Ok(eval)
285 }
286 }
287 }
288}