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