1use crate::{
2 challenger::{CanObserveVariable, CanSampleBitsVariable, FieldChallengerVariable},
3 hash::FieldHasherVariable,
4 symbolic::IntoSymbolic,
5 CircuitConfig, SP1FieldConfigVariable,
6};
7use itertools::Itertools;
8use slop_algebra::{AbstractField, TwoAdicField};
9use slop_basefold::FriConfig;
10use slop_multilinear::{partial_lagrange_blocking, MleEval, Point};
11use sp1_recursion_compiler::{
12 circuit::CircuitV2Builder,
13 ir::{Builder, DslIr, Ext, Felt, SymbolicExt},
14};
15
16use sp1_primitives::{SP1ExtensionField, SP1Field};
17use sp1_recursion_executor::D;
18use std::{iter::once, marker::PhantomData};
19use tcs::{RecursiveMerkleTreeTcs, RecursiveTensorCsOpening};
20pub mod merkle_tree;
21pub mod stacked;
22pub mod tcs;
23mod whir;
24pub mod witness;
25
26pub struct RecursiveBasefoldConfigImpl<C, SC>(PhantomData<(C, SC)>);
27
28pub struct RecursiveBasefoldProof<C: CircuitConfig, SC: SP1FieldConfigVariable<C>> {
29 pub univariate_messages: Vec<[Ext<SP1Field, SP1ExtensionField>; 2]>,
31 pub fri_commitments: Vec<<SC as FieldHasherVariable<C>>::DigestVariable>,
34 pub component_polynomials_query_openings_and_proofs:
37 Vec<RecursiveTensorCsOpening<<SC as FieldHasherVariable<C>>::DigestVariable>>,
38 pub query_phase_openings_and_proofs:
40 Vec<RecursiveTensorCsOpening<<SC as FieldHasherVariable<C>>::DigestVariable>>,
41 pub final_poly: Ext<SP1Field, SP1ExtensionField>,
44 pub pow_witness: Felt<SP1Field>,
46}
47
48#[derive(Clone)]
49pub struct RecursiveBasefoldVerifier<C: CircuitConfig, SC: SP1FieldConfigVariable<C>> {
50 pub fri_config: FriConfig<SP1Field>,
51 pub tcs: RecursiveMerkleTreeTcs<C, SC>,
52}
53
54pub trait RecursiveMultilinearPcsVerifier: Sized {
55 type Commitment;
56 type Proof;
57 type Circuit: CircuitConfig<Bit = Self::Bit>;
58 type Bit;
59 type Challenger: FieldChallengerVariable<Self::Circuit, Self::Bit>;
60
61 fn verify_trusted_evaluations(
62 &self,
63 builder: &mut Builder<Self::Circuit>,
64 commitments: &[Self::Commitment],
65 point: Point<Ext<SP1Field, SP1ExtensionField>>,
66 evaluation_claims: &[MleEval<Ext<SP1Field, SP1ExtensionField>>],
67 proof: &Self::Proof,
68 challenger: &mut Self::Challenger,
69 );
70
71 fn verify_untrusted_evaluations(
72 &self,
73 builder: &mut Builder<Self::Circuit>,
74 commitments: &[Self::Commitment],
75 point: Point<Ext<SP1Field, SP1ExtensionField>>,
76 evaluation_claims: &[MleEval<Ext<SP1Field, SP1ExtensionField>>],
77 proof: &Self::Proof,
78 challenger: &mut Self::Challenger,
79 ) {
80 for round in evaluation_claims.iter() {
81 for evaluation in round.iter() {
82 let evaluation_felts = Self::Circuit::ext2felt(builder, *evaluation);
83 evaluation_felts.iter().for_each(|felt| challenger.observe(builder, *felt));
84 }
85 }
86 self.verify_trusted_evaluations(
87 builder,
88 commitments,
89 point,
90 evaluation_claims,
91 proof,
92 challenger,
93 )
94 }
95}
96
97impl<C: CircuitConfig, SC: SP1FieldConfigVariable<C>> RecursiveMultilinearPcsVerifier
98 for RecursiveBasefoldVerifier<C, SC>
99{
100 type Commitment = SC::DigestVariable;
101 type Proof = RecursiveBasefoldProof<C, SC>;
102 type Circuit = C;
103 type Bit = C::Bit;
104 type Challenger = SC::FriChallengerVariable;
105
106 fn verify_trusted_evaluations(
107 &self,
108 builder: &mut Builder<Self::Circuit>,
109 commitments: &[Self::Commitment],
110 point: Point<Ext<SP1Field, SP1ExtensionField>>,
111 evaluation_claims: &[MleEval<Ext<SP1Field, SP1ExtensionField>>],
112 proof: &Self::Proof,
113 challenger: &mut Self::Challenger,
114 ) {
115 self.verify_mle_evaluations(
116 builder,
117 commitments,
118 point,
119 evaluation_claims,
120 proof,
121 challenger,
122 )
123 }
124}
125
126impl<C: CircuitConfig, SC: SP1FieldConfigVariable<C>> RecursiveBasefoldVerifier<C, SC> {
127 fn verify_mle_evaluations(
128 &self,
129 builder: &mut Builder<C>,
130 commitments: &[SC::DigestVariable],
131 mut point: Point<Ext<SP1Field, SP1ExtensionField>>,
132 evaluation_claims: &[MleEval<Ext<SP1Field, SP1ExtensionField>>],
133 proof: &RecursiveBasefoldProof<C, SC>,
134 challenger: &mut SC::FriChallengerVariable,
135 ) {
136 let total_len = evaluation_claims
138 .iter()
139 .map(|batch_claims| batch_claims.num_polynomials())
140 .sum::<usize>();
141 let num_batching_variables = total_len.next_power_of_two().ilog2();
142 let batching_point: Point<Ext<SP1Field, SP1ExtensionField>> =
143 Point::from_iter((0..num_batching_variables).map(|_| challenger.sample_ext(builder)));
144 let batching_point_symbolic = IntoSymbolic::<C>::as_symbolic(&batching_point);
145 let batching_coefficients_symbolic =
146 partial_lagrange_blocking(&batching_point_symbolic).into_buffer().into_vec();
147
148 let batching_coefficients: Vec<Ext<SP1Field, SP1ExtensionField>> =
151 batching_coefficients_symbolic
152 .into_iter()
153 .map(|x| {
154 let element = x.as_ext().expect("lagrange coefficient should be a variable");
155 builder.reduce_e(element);
156 element
157 })
158 .collect();
159
160 builder.cycle_tracker_v2_enter("compute eval_claim");
161 let eval_claim = evaluation_claims
163 .iter()
164 .flat_map(|batch_claims| batch_claims.iter())
165 .zip(batching_coefficients.iter())
166 .map(|(eval, batch_power)| *eval * *batch_power)
167 .sum::<SymbolicExt<SP1Field, SP1ExtensionField>>();
168 builder.cycle_tracker_v2_exit();
169
170 assert_eq!(
172 proof.fri_commitments.len(),
173 proof.univariate_messages.len(),
174 "Sumcheck FRI Length Mismatch"
175 );
176
177 point.reverse();
180
181 let len_felt: Felt<_> =
183 builder.constant(SP1Field::from_canonical_usize(proof.fri_commitments.len()));
184 challenger.observe(builder, len_felt);
185 let betas = proof
186 .fri_commitments
187 .iter()
188 .zip(proof.univariate_messages.iter())
189 .map(|(commitment, poly)| {
190 poly.iter().copied().for_each(|x| {
191 let x_felts = C::ext2felt(builder, x);
192 x_felts.iter().for_each(|felt| challenger.observe(builder, *felt));
193 });
194 challenger.observe(builder, *commitment);
195 challenger.sample_ext(builder)
196 })
197 .collect::<Vec<_>>();
198
199 let first_poly = proof.univariate_messages[0];
204 let one: Ext<SP1Field, SP1ExtensionField> = builder.constant(SP1ExtensionField::one());
205
206 builder.assert_ext_eq(
207 eval_claim,
208 (one - *point[0]) * first_poly[0] + *point[0] * first_poly[1],
209 );
210
211 let mut expected_eval = first_poly[0] + betas[0] * first_poly[1];
214
215 for (i, (poly, beta)) in
217 proof.univariate_messages[1..].iter().zip(betas[1..].iter()).enumerate()
218 {
219 let i = i + 1;
221 builder.assert_ext_eq(expected_eval, (one - *point[i]) * poly[0] + *point[i] * poly[1]);
222
223 expected_eval = poly[0] + *beta * poly[1];
225 }
226
227 let final_poly_felts = C::ext2felt(builder, proof.final_poly);
228 challenger.observe_slice(builder, final_poly_felts);
229
230 challenger.check_witness(builder, self.fri_config.proof_of_work_bits, proof.pow_witness);
233
234 let log_len = proof.fri_commitments.len();
235
236 builder.cycle_tracker_v2_enter("sample query_indices");
237 let query_indices = (0..self.fri_config.num_queries)
240 .map(|_| challenger.sample_bits(builder, log_len + self.fri_config.log_blowup()))
241 .collect::<Vec<_>>();
242 builder.cycle_tracker_v2_exit();
243
244 builder.cycle_tracker_v2_enter("compute batch_evals");
245
246 let zero = SymbolicExt::<SP1Field, SP1ExtensionField>::zero();
248 let mut batch_evals = vec![zero; query_indices.len()];
249 let mut batch_idx = 0;
250 for opening in proof.component_polynomials_query_openings_and_proofs.iter() {
251 let values = &opening.values;
252 let total_columns = values.get(0).unwrap().as_slice().len();
253 let round_coefficients = &batching_coefficients[batch_idx..batch_idx + total_columns];
254 for (batch_eval, values) in batch_evals.iter_mut().zip_eq(values.split()) {
255 for (value, coeff) in values.as_slice().iter().zip(round_coefficients.iter()) {
256 *batch_eval += *coeff * *value;
257 }
258 }
259 batch_idx += total_columns;
260 }
261 let batch_evals: Vec<Ext<SP1Field, SP1ExtensionField>> =
262 batch_evals.into_iter().map(|x| builder.eval(x)).collect_vec();
263 builder.cycle_tracker_v2_exit();
264
265 builder.cycle_tracker_v2_enter("verify_tensor_openings");
266
267 for (commit, opening) in
269 commitments.iter().zip_eq(proof.component_polynomials_query_openings_and_proofs.iter())
270 {
271 RecursiveMerkleTreeTcs::<C, SC>::verify_tensor_openings(
272 builder,
273 commit,
274 &query_indices,
275 opening,
276 );
277 }
278 builder.cycle_tracker_v2_exit();
279
280 builder.cycle_tracker_v2_enter("verify_queries");
281 self.verify_queries(
283 builder,
284 &proof.fri_commitments,
285 &query_indices,
286 proof.final_poly,
287 batch_evals,
288 &proof.query_phase_openings_and_proofs,
289 &betas,
290 );
291 builder.cycle_tracker_v2_exit();
292
293 builder.assert_ext_eq(
295 proof.final_poly,
296 proof.univariate_messages.last().unwrap()[0]
297 + *betas.last().unwrap() * proof.univariate_messages.last().unwrap()[1],
298 );
299 }
300
301 #[allow(clippy::too_many_arguments)]
304 fn verify_queries(
305 &self,
306 builder: &mut Builder<C>,
307 commitments: &[SC::DigestVariable],
308 indices: &[Vec<C::Bit>],
309 final_poly: Ext<SP1Field, SP1ExtensionField>,
310 reduced_openings: Vec<Ext<SP1Field, SP1ExtensionField>>,
311 query_openings: &[RecursiveTensorCsOpening<SC::DigestVariable>],
312 betas: &[Ext<SP1Field, SP1ExtensionField>],
313 ) {
314 let log_max_height = commitments.len() + self.fri_config.log_blowup();
315
316 let mut folded_evals = reduced_openings;
317 builder.cycle_tracker_v2_enter("compute exp reverse bits");
318 let mut xis: Vec<Felt<SP1Field>> = indices
319 .iter()
320 .map(|index| {
321 let two_adic_generator: Felt<SP1Field> =
322 builder.constant(SP1Field::two_adic_generator(log_max_height));
323 C::exp_reverse_bits(builder, two_adic_generator, index.to_vec())
324 })
325 .collect::<Vec<_>>();
326 builder.cycle_tracker_v2_exit();
327
328 let mut indices = indices.to_vec();
329
330 for ((commitment, query_opening), beta) in
332 commitments.iter().zip_eq(query_openings.iter()).zip_eq(betas)
333 {
334 let openings = &query_opening.values;
335
336 for (((index, folded_eval), opening), x) in indices
337 .iter_mut()
338 .zip_eq(folded_evals.iter_mut())
339 .zip_eq(openings.split())
340 .zip_eq(xis.iter_mut())
341 {
342 let index_sibling_complement = index[0];
343 let index_pair = &index[1..];
344
345 builder.reduce_e(*folded_eval);
346
347 let evals: [Ext<SP1Field, SP1ExtensionField>; 2] = opening
348 .as_slice()
349 .chunks_exact(D)
350 .map(|slice| {
351 let reconstructed_ext: Ext<SP1Field, SP1ExtensionField> =
352 C::felt2ext(builder, slice.try_into().unwrap());
353 reconstructed_ext
354 })
355 .collect::<Vec<_>>()
356 .try_into()
357 .unwrap();
358
359 let eval_ordered = C::select_chain_ef(
360 builder,
361 index_sibling_complement,
362 once(evals[0]),
363 once(evals[1]),
364 );
365
366 builder.assert_ext_eq(eval_ordered[0], *folded_eval);
368
369 let xs_new = builder.eval((*x) * SP1Field::two_adic_generator(1));
370 let xs =
371 C::select_chain_f(builder, index_sibling_complement, once(*x), once(xs_new));
372
373 let temp_1: Felt<_> = builder.uninit();
375 builder.push_op(DslIr::SubF(temp_1, xs[1], xs[0]));
376
377 let temp_2: Ext<_, _> = builder.uninit();
379 builder.push_op(DslIr::SubE(temp_2, evals[1], evals[0]));
380
381 let temp_3: Ext<_, _> = builder.uninit();
383 builder.push_op(DslIr::DivEF(temp_3, temp_2, temp_1));
384
385 let temp_4: Ext<_, _> = builder.uninit();
387 builder.push_op(DslIr::SubEF(temp_4, *beta, xs[0]));
388
389 let temp_5: Ext<_, _> = builder.uninit();
391 builder.push_op(DslIr::MulE(temp_5, temp_4, temp_3));
392
393 let temp_6: Ext<_, _> = builder.uninit();
395 builder.push_op(DslIr::AddE(temp_6, evals[0], temp_5));
396 *folded_eval = temp_6;
397
398 let temp_7: Felt<_> = builder.uninit();
400 builder.push_op(DslIr::MulF(temp_7, *x, *x));
401 *x = temp_7;
402
403 *index = index_pair.to_vec();
404 }
405 RecursiveMerkleTreeTcs::<C, SC>::verify_tensor_openings(
407 builder,
408 commitment,
409 &indices,
410 query_opening,
411 );
412 }
413
414 for folded_eval in folded_evals {
415 builder.assert_ext_eq(folded_eval, final_poly);
416 }
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use rand::thread_rng;
423 use slop_commit::Message;
424 use sp1_recursion_compiler::{circuit::AsmConfig, config::InnerConfig};
425 use std::sync::Arc;
426
427 use slop_algebra::extension::BinomialExtensionField;
428 use slop_challenger::IopCtx;
429 use sp1_primitives::SP1DiffusionMatrix;
430
431 use crate::{challenger::DuplexChallengerVariable, witness::Witnessable};
432
433 use super::*;
434 use slop_basefold::BasefoldVerifier;
435 use slop_basefold_prover::BasefoldProver;
436 use slop_challenger::CanObserve;
437
438 use slop_commit::Rounds;
439
440 use slop_multilinear::{Evaluations, Mle};
441 use sp1_hypercube::inner_perm;
442 use sp1_primitives::{SP1Field, SP1GlobalContext};
443 use sp1_recursion_compiler::circuit::{AsmBuilder, AsmCompiler};
444 use sp1_recursion_executor::Executor;
445
446 type F = SP1Field;
447 type EF = BinomialExtensionField<SP1Field, 4>;
448
449 #[test]
450 fn test_basefold_proof() {
451 type C = InnerConfig;
452 type SC = SP1GlobalContext;
453
454 type Prover = BasefoldProver<SP1GlobalContext, sp1_hypercube::prover::SP1MerkleTreeProver>;
455
456 let num_variables = 16;
457 let round_widths = [vec![16, 10, 14], vec![20, 78, 34], vec![10, 10]];
458
459 let mut rng = thread_rng();
460 let round_mles = round_widths
461 .iter()
462 .map(|widths| {
463 widths
464 .iter()
465 .map(|&w| Mle::<SP1Field>::rand(&mut rng, w, num_variables))
466 .collect::<Message<_>>()
467 })
468 .collect::<Rounds<_>>();
469
470 let verifier =
471 BasefoldVerifier::<_>::new(FriConfig::default_fri_config(), round_widths.len());
472 let recursive_verifier = RecursiveBasefoldVerifier::<C, SC> {
473 fri_config: verifier.fri_config,
474 tcs: RecursiveMerkleTreeTcs::<C, SC>(PhantomData),
475 };
476
477 let prover = Prover::new(&verifier);
478
479 let mut challenger = SC::default_challenger();
480 let mut commitments = vec![];
481 let mut prover_data = Rounds::new();
482 let mut eval_claims = Rounds::new();
483 let point = Point::<EF>::rand(&mut rng, num_variables);
484 for mles in round_mles.iter() {
485 let (commitment, data) = prover.commit_mles(mles.clone()).unwrap();
486 challenger.observe(commitment);
487 commitments.push(commitment);
488 prover_data.push(data);
489 let evaluations =
490 mles.iter().map(|mle| mle.eval_at(&point)).collect::<Evaluations<_>>();
491 eval_claims.push(evaluations);
492 }
493
494 let proof = prover
495 .prove_trusted_mle_evaluations(
496 point.clone(),
497 round_mles,
498 eval_claims.clone(),
499 prover_data,
500 &mut challenger,
501 )
502 .unwrap();
503
504 let mut builder = AsmBuilder::default();
505 let mut witness_stream = Vec::new();
506 let mut challenger_variable = DuplexChallengerVariable::new(&mut builder);
507 let eval_claims = eval_claims
508 .iter()
509 .map(|round| round.into_iter().flat_map(|x| x.into_iter()).collect::<MleEval<_>>())
510 .collect::<Rounds<_>>();
511
512 for commitment in commitments.iter() {
513 challenger.observe(*commitment);
514 }
515
516 Witnessable::<AsmConfig>::write(&commitments, &mut witness_stream);
517 let commitments = commitments.read(&mut builder);
518
519 for commitment in commitments.iter() {
520 challenger_variable.observe(&mut builder, *commitment);
521 }
522
523 Witnessable::<AsmConfig>::write(&point, &mut witness_stream);
524 let point = point.read(&mut builder);
525
526 Witnessable::<AsmConfig>::write(&eval_claims, &mut witness_stream);
527 let eval_claims = eval_claims.read(&mut builder);
528
529 Witnessable::<AsmConfig>::write(&proof, &mut witness_stream);
530 let proof = proof.read(&mut builder);
531
532 RecursiveBasefoldVerifier::<C, SC>::verify_mle_evaluations(
533 &recursive_verifier,
534 &mut builder,
535 &commitments,
536 point,
537 &eval_claims,
538 &proof,
539 &mut challenger_variable,
540 );
541 let block = builder.into_root_block();
542 let mut compiler = AsmCompiler::default();
543 let program = Arc::new(compiler.compile_inner(block).validate().unwrap());
544 let mut executor =
545 Executor::<F, EF, SP1DiffusionMatrix>::new(program.clone(), inner_perm());
546 executor.witness_stream = witness_stream.into();
547 executor.run().unwrap();
548 }
549
550 #[test]
551 fn test_invalid_basefold_proof() {
552 type C = InnerConfig;
553 type SC = SP1GlobalContext;
554 type Prover = BasefoldProver<SP1GlobalContext, sp1_hypercube::prover::SP1MerkleTreeProver>;
555
556 let num_variables = 16;
557 let round_widths = [vec![16, 10, 14], vec![20, 78, 34], vec![10, 10]];
558 let fri_config = FriConfig::default_fri_config();
559
560 let mut rng = thread_rng();
561 let round_mles = round_widths
562 .iter()
563 .map(|widths| {
564 widths
565 .iter()
566 .map(|&w| Mle::<SP1Field>::rand(&mut rng, w, num_variables))
567 .collect::<Message<_>>()
568 })
569 .collect::<Rounds<_>>();
570
571 let verifier = BasefoldVerifier::<SC>::new(fri_config, round_widths.len());
572 let recursive_verifier = RecursiveBasefoldVerifier::<C, SC> {
573 fri_config: verifier.fri_config,
574 tcs: RecursiveMerkleTreeTcs::<C, SC>(PhantomData),
575 };
576
577 let prover = Prover::new(&verifier);
578
579 let mut challenger = SC::default_challenger();
580 let mut commitments = vec![];
581 let mut prover_data = Rounds::new();
582 let mut eval_claims = Rounds::new();
583 let point = Point::<EF>::rand(&mut rng, num_variables);
584 for mles in round_mles.iter() {
585 let (commitment, data) = prover.commit_mles(mles.clone()).unwrap();
586 challenger.observe(commitment);
587 commitments.push(commitment);
588 prover_data.push(data);
589 let evaluations =
590 mles.iter().map(|mle| mle.eval_at(&point)).collect::<Evaluations<_>>();
591 eval_claims.push(evaluations);
592 }
593
594 let proof = prover
595 .prove_trusted_mle_evaluations(
596 point.clone(),
597 round_mles,
598 eval_claims.clone(),
599 prover_data,
600 &mut challenger,
601 )
602 .unwrap();
603
604 let point = Point::<EF>::rand(&mut rng, num_variables);
606
607 let mut builder = AsmBuilder::default();
608 let mut witness_stream = Vec::new();
609 let mut challenger_variable = DuplexChallengerVariable::new(&mut builder);
610
611 for commitment in commitments.iter() {
612 challenger.observe(*commitment);
613 }
614
615 Witnessable::<AsmConfig>::write(&commitments, &mut witness_stream);
616 let commitments = commitments.read(&mut builder);
617
618 for commitment in commitments.iter() {
619 challenger_variable.observe(&mut builder, *commitment);
620 }
621
622 Witnessable::<AsmConfig>::write(&point, &mut witness_stream);
623 let point = point.read(&mut builder);
624
625 Witnessable::<AsmConfig>::write(&eval_claims, &mut witness_stream);
626 let eval_claims = eval_claims.read(&mut builder);
627
628 Witnessable::<AsmConfig>::write(&proof, &mut witness_stream);
629 let proof = proof.read(&mut builder);
630
631 let eval_claims = eval_claims
632 .iter()
633 .map(|round| {
634 round.into_iter().flat_map(|x| x.into_iter()).copied().collect::<MleEval<_>>()
635 })
636 .collect::<Rounds<_>>();
637
638 RecursiveBasefoldVerifier::<C, SC>::verify_mle_evaluations(
639 &recursive_verifier,
640 &mut builder,
641 &commitments,
642 point,
643 &eval_claims,
644 &proof,
645 &mut challenger_variable,
646 );
647 let block = builder.into_root_block();
648 let mut compiler = AsmCompiler::default();
649 let program = Arc::new(compiler.compile_inner(block).validate().unwrap());
650 let mut executor =
651 Executor::<F, EF, SP1DiffusionMatrix>::new(program.clone(), inner_perm());
652 executor.witness_stream = witness_stream.into();
653 executor.run().expect_err("invalid proof should not be verified");
654 }
655}