1use itertools::Itertools;
2use serde::{Deserialize, Serialize};
3use slop_algebra::{AbstractExtensionField, AbstractField, TwoAdicField};
4use slop_challenger::{
5 CanObserve, CanSampleBits, FieldChallenger, GrindingChallenger, IopCtx,
6 VariableLengthChallenger,
7};
8use slop_merkle_tree::{MerkleTreeOpeningAndProof, MerkleTreeTcs, MerkleTreeTcsError};
9use slop_multilinear::{partial_lagrange_blocking, MleEval, MultilinearPcsChallenger, Point};
10use slop_utils::reverse_bits_len;
11use thiserror::Error;
12
13pub use slop_primitives::FriConfig;
14
15pub const BATCH_GRINDING_BITS: usize = 5;
17
18#[derive(Clone)]
19pub struct BasefoldVerifier<GC: IopCtx> {
20 pub fri_config: crate::FriConfig<GC::F>,
21 pub tcs: MerkleTreeTcs<GC>,
22 pub num_expected_commitments: usize,
23}
24
25impl<GC: IopCtx> std::fmt::Debug for BasefoldVerifier<GC> {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 f.debug_struct("BasefoldVerifier")
28 .field("fri_config", &self.fri_config)
29 .field("num_expected_commitments", &self.num_expected_commitments)
30 .finish()
31 }
32}
33
34impl<GC: IopCtx> BasefoldVerifier<GC> {
35 pub fn new(fri_config: crate::FriConfig<GC::F>, num_expected_commitments: usize) -> Self {
36 assert_ne!(num_expected_commitments, 0, "commitment must exist");
37 Self { fri_config, tcs: MerkleTreeTcs::default(), num_expected_commitments }
38 }
39}
40
41#[derive(Error)]
42pub enum BaseFoldVerifierError<TcsError> {
43 #[error("Sumcheck and FRI commitments length mismatch")]
44 SumcheckFriLengthMismatch,
45 #[error("Query failed to verify: {0}")]
46 TcsError(#[from] TcsError),
47 #[error("Sumcheck error")]
48 Sumcheck,
49 #[error("Invalid proof of work witness")]
50 Pow,
51 #[error("Invalid batch grinding witness")]
52 BatchPow,
53 #[error("Query value mismatch")]
54 QueryValueMismatch,
55 #[error("query final polynomial mismatch")]
56 QueryFinalPolyMismatch,
57 #[error("sumcheck final polynomial mismatch")]
58 SumcheckFinalPolyMismatch,
59 #[error("incorrect shape of proof")]
60 IncorrectShape,
61 #[error("instance overflows the field two_adicity")]
62 TwoAdicityOverflow,
63}
64
65impl<TcsError: std::fmt::Display> std::fmt::Debug for BaseFoldVerifierError<TcsError> {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 match self {
68 BaseFoldVerifierError::SumcheckFriLengthMismatch => {
69 write!(f, "sumcheck and FRI commitments length mismatch")
70 }
71 BaseFoldVerifierError::TcsError(e) => write!(f, "tensor opening error: {e}"),
72 BaseFoldVerifierError::Sumcheck => write!(f, "sumcheck error"),
73 BaseFoldVerifierError::Pow => write!(f, "invalid proof of work witness"),
74 BaseFoldVerifierError::BatchPow => {
75 write!(f, "invalid batch grinding witness")
76 }
77 BaseFoldVerifierError::QueryValueMismatch => write!(f, "query value mismatch"),
78 BaseFoldVerifierError::QueryFinalPolyMismatch => {
79 write!(f, "query final polynomial mismatch")
80 }
81 BaseFoldVerifierError::SumcheckFinalPolyMismatch => {
82 write!(f, "sumcheck final polynomial mismatch")
83 }
84 BaseFoldVerifierError::IncorrectShape => {
85 write!(f, "incorrect shape of proof")
86 }
87 BaseFoldVerifierError::TwoAdicityOverflow => {
88 write!(f, "instance overflows the field two_adicity")
89 }
90 }
91 }
92}
93
94#[derive(Clone, Serialize, Deserialize)]
96#[serde(bound(serialize = "", deserialize = ""))]
97pub struct BasefoldProof<GC: IopCtx> {
98 pub univariate_messages: Vec<[GC::EF; 2]>,
100 pub fri_commitments: Vec<GC::Digest>,
103 pub component_polynomials_query_openings_and_proofs: Vec<MerkleTreeOpeningAndProof<GC>>,
107 pub query_phase_openings_and_proofs: Vec<MerkleTreeOpeningAndProof<GC>>,
109 pub final_poly: GC::EF,
112 pub pow_witness: <GC::Challenger as GrindingChallenger>::Witness,
114 pub batch_grinding_witness: <GC::Challenger as GrindingChallenger>::Witness,
116}
117
118impl<GC: IopCtx> BasefoldVerifier<GC>
119where
120 GC::F: TwoAdicField,
121{
122 pub fn verify_mle_evaluations(
123 &self,
124 commitments: &[GC::Digest],
125 mut point: Point<GC::EF>,
126 evaluation_claims: &[MleEval<GC::EF>],
127 proof: &BasefoldProof<GC>,
128 challenger: &mut GC::Challenger,
129 ) -> Result<(), BaseFoldVerifierError<MerkleTreeTcsError>> {
130 if !challenger.check_witness(BATCH_GRINDING_BITS, proof.batch_grinding_witness) {
132 return Err(BaseFoldVerifierError::BatchPow);
133 }
134
135 let total_len = evaluation_claims
137 .iter()
138 .map(|batch_claims| batch_claims.num_polynomials())
139 .sum::<usize>();
140
141 let num_batching_variables = total_len.next_power_of_two().ilog2();
142 let batching_point = challenger.sample_point::<GC::EF>(num_batching_variables);
143 let batching_coefficients = partial_lagrange_blocking(&batching_point);
144
145 let eval_claim = evaluation_claims
147 .iter()
148 .flat_map(|batch_claims| batch_claims.iter())
149 .zip(batching_coefficients.as_slice())
150 .map(|(eval, batch_power)| *eval * *batch_power)
151 .sum::<GC::EF>();
152
153 if evaluation_claims.len() != commitments.len()
154 || commitments.len() != proof.component_polynomials_query_openings_and_proofs.len()
155 || commitments.len() != self.num_expected_commitments
156 {
157 return Err(BaseFoldVerifierError::IncorrectShape);
158 }
159
160 if proof.fri_commitments.len() != proof.univariate_messages.len()
162 || proof.fri_commitments.len() != point.dimension()
163 || proof.univariate_messages.is_empty()
164 {
165 return Err(BaseFoldVerifierError::SumcheckFriLengthMismatch);
166 }
167
168 point.reverse();
171
172 let len = proof.fri_commitments.len();
178 challenger.observe(GC::F::from_canonical_usize(len));
179 let betas = proof
180 .fri_commitments
181 .iter()
182 .zip_eq(proof.univariate_messages.iter())
183 .map(|(commitment, poly)| {
184 challenger.observe_constant_length_extension_slice(poly);
185 challenger.observe(*commitment);
186 challenger.sample_ext_element::<GC::EF>()
187 })
188 .collect::<Vec<_>>();
189
190 let first_poly = proof.univariate_messages[0];
195 if eval_claim != (GC::EF::one() - *point[0]) * first_poly[0] + *point[0] * first_poly[1] {
196 println!("failed in first_poly");
197 return Err(BaseFoldVerifierError::Sumcheck);
198 };
199
200 let mut expected_eval = first_poly[0] + betas[0] * first_poly[1];
203
204 for (i, (poly, beta)) in
206 proof.univariate_messages[1..].iter().zip_eq(betas[1..].iter()).enumerate()
207 {
208 let i = i + 1;
210 if expected_eval != (GC::EF::one() - *point[i]) * poly[0] + *point[i] * poly[1] {
211 println!("failed in round {i}");
212 return Err(BaseFoldVerifierError::Sumcheck);
213 }
214
215 expected_eval = poly[0] + *beta * poly[1];
217 }
218
219 challenger.observe_ext_element(proof.final_poly);
220
221 if !challenger.check_witness(self.fri_config.proof_of_work_bits, proof.pow_witness) {
224 return Err(BaseFoldVerifierError::Pow);
225 }
226
227 let log_len = proof.fri_commitments.len();
228
229 if log_len + self.fri_config.log_blowup() > GC::F::TWO_ADICITY {
230 return Err(BaseFoldVerifierError::TwoAdicityOverflow);
231 }
232
233 let query_indices = (0..self.fri_config.num_queries)
236 .map(|_| challenger.sample_bits(log_len + self.fri_config.log_blowup()))
237 .collect::<Vec<_>>();
238
239 let mut batch_evals = vec![GC::EF::zero(); query_indices.len()];
241 let mut batch_idx = 0;
242 for (round_idx, opening_and_proof) in
243 proof.component_polynomials_query_openings_and_proofs.iter().enumerate()
244 {
245 let values = &opening_and_proof.values;
246 let total_columns = evaluation_claims[round_idx].num_polynomials();
247 if values.dimensions.sizes().len() != 2 {
248 return Err(BaseFoldVerifierError::IncorrectShape);
249 }
250 if values.dimensions.sizes()[0] != query_indices.len() {
251 return Err(BaseFoldVerifierError::IncorrectShape);
252 }
253 if values.dimensions.sizes()[1] != total_columns {
254 return Err(BaseFoldVerifierError::IncorrectShape);
255 }
256 let round_coefficients =
257 &batching_coefficients.as_slice()[batch_idx..batch_idx + total_columns];
258 for (batch_eval, values) in batch_evals.iter_mut().zip_eq(values.split()) {
259 for (value, batching_coefficient) in
260 values.as_slice().iter().zip(round_coefficients)
261 {
262 *batch_eval += *batching_coefficient * *value;
263 }
264 }
265 batch_idx += total_columns;
266 }
267
268 for (commit, opening_and_proof) in
270 commitments.iter().zip_eq(proof.component_polynomials_query_openings_and_proofs.iter())
271 {
272 let width = opening_and_proof.values.sizes()[1];
274 self.tcs
275 .verify_tensor_openings(
276 commit,
277 &query_indices,
278 &opening_and_proof.values,
279 width,
280 log_len + self.fri_config.log_blowup(),
281 &opening_and_proof.proof,
282 )
283 .map_err(BaseFoldVerifierError::TcsError)?;
284 }
285
286 self.verify_queries(
288 &proof.fri_commitments,
289 &query_indices,
290 proof.final_poly,
291 batch_evals,
292 &proof.query_phase_openings_and_proofs,
293 &betas,
294 )?;
295
296 if proof.final_poly
298 != proof.univariate_messages.last().unwrap()[0]
299 + *betas.last().unwrap() * proof.univariate_messages.last().unwrap()[1]
300 {
301 return Err(BaseFoldVerifierError::SumcheckFinalPolyMismatch);
302 }
303
304 Ok(())
305 }
306
307 fn verify_queries(
310 &self,
311 commitments: &[GC::Digest],
312 indices: &[usize],
313 final_poly: GC::EF,
314 reduced_openings: Vec<GC::EF>,
315 query_openings: &[MerkleTreeOpeningAndProof<GC>],
316 betas: &[GC::EF],
317 ) -> Result<(), BaseFoldVerifierError<MerkleTreeTcsError>> {
318 let log_max_height = commitments.len() + self.fri_config.log_blowup();
319
320 let mut folded_evals = reduced_openings;
321 let mut indices = indices.to_vec();
322
323 let mut xis = indices
324 .iter()
325 .map(|index| {
326 GC::F::two_adic_generator(log_max_height)
327 .exp_u64(reverse_bits_len(*index, log_max_height) as u64)
328 })
329 .collect::<Vec<_>>();
330
331 if commitments.len() != query_openings.len() || commitments.len() != betas.len() {
332 return Err(BaseFoldVerifierError::IncorrectShape);
333 }
334
335 for (round_idx, ((commitment, query_opening), beta)) in (self.fri_config.log_blowup()
337 ..log_max_height)
338 .rev()
339 .zip_eq(commitments.iter().zip_eq(query_openings.iter()).zip_eq(betas))
340 {
341 let openings = &query_opening.values;
342 if openings.dimensions.sizes().len() != 2 {
343 return Err(BaseFoldVerifierError::IncorrectShape);
344 }
345
346 if indices.len() != folded_evals.len()
347 || indices.len() != openings.dimensions.sizes()[0]
348 || indices.len() != xis.len()
349 {
350 return Err(BaseFoldVerifierError::IncorrectShape);
351 }
352
353 for (((index, folded_eval), opening), x) in indices
354 .iter_mut()
355 .zip_eq(folded_evals.iter_mut())
356 .zip_eq(openings.split())
357 .zip_eq(xis.iter_mut())
358 {
359 let index_sibling = *index ^ 1;
360 let index_pair = *index >> 1;
361
362 if opening.total_len() != 2 * <GC::EF as AbstractExtensionField<GC::F>>::D {
363 return Err(BaseFoldVerifierError::IncorrectShape);
364 }
365
366 let evals: [GC::EF; 2] = opening
367 .as_slice()
368 .chunks_exact(GC::EF::D)
369 .map(GC::EF::from_base_slice)
370 .collect::<Vec<_>>()
371 .try_into()
372 .unwrap();
373
374 if evals[*index % 2] != *folded_eval {
376 return Err(BaseFoldVerifierError::QueryValueMismatch);
377 }
378
379 let mut xs = [*x; 2];
380 xs[index_sibling % 2] *= GC::F::two_adic_generator(1);
381
382 *folded_eval = evals[0]
384 + (*beta - xs[0]) * (evals[1] - evals[0]) / GC::EF::from(xs[1] - xs[0]);
385
386 *index = index_pair;
387 *x = x.square();
388 }
389
390 self.tcs
393 .verify_tensor_openings(
394 commitment,
395 &indices,
396 &query_opening.values,
397 GC::EF::D * 2,
398 round_idx,
399 &query_opening.proof,
400 )
401 .map_err(BaseFoldVerifierError::TcsError)?;
402 }
403
404 for folded_eval in folded_evals {
405 if folded_eval != final_poly {
406 return Err(BaseFoldVerifierError::QueryFinalPolyMismatch);
407 }
408 }
409
410 Ok(())
411 }
412
413 pub fn verify_untrusted_evaluations(
414 &self,
415 commitments: &[GC::Digest],
416 eval_point: Point<GC::EF>,
417 evaluation_claims: &[MleEval<GC::EF>],
418 proof: &BasefoldProof<GC>,
419 challenger: &mut GC::Challenger,
420 ) -> Result<(), BaseFoldVerifierError<MerkleTreeTcsError>> {
421 for round in evaluation_claims.iter() {
423 challenger.observe_constant_length_extension_slice(round);
427 }
428
429 self.verify_mle_evaluations(commitments, eval_point, evaluation_claims, proof, challenger)
430 }
431}