Skip to main content

sp1_recursion_circuit/basefold/
mod.rs

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    /// The univariate polynomials that are used in the sumcheck part of the BaseFold protocol.
30    pub univariate_messages: Vec<[Ext<SP1Field, SP1ExtensionField>; 2]>,
31    /// The FRI parts of the proof.
32    /// The commitments to the folded polynomials produced in the commit phase.
33    pub fri_commitments: Vec<<SC as FieldHasherVariable<C>>::DigestVariable>,
34    /// The query openings for the individual multilinear polynomials.
35    /// The vector is indexed by the batch number.
36    pub component_polynomials_query_openings_and_proofs:
37        Vec<RecursiveTensorCsOpening<<SC as FieldHasherVariable<C>>::DigestVariable>>,
38    /// The query openings and the FRI query proofs for the FRI query phase.
39    pub query_phase_openings_and_proofs:
40        Vec<RecursiveTensorCsOpening<<SC as FieldHasherVariable<C>>::DigestVariable>>,
41    /// The prover performs FRI until we reach a polynomial of degree 0, and return the constant
42    /// value of this polynomial.
43    pub final_poly: Ext<SP1Field, SP1ExtensionField>,
44    /// Proof-of-work witness.
45    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        // Sample batching coefficients via partial Lagrange basis.
137        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        // Force modular reduction the batching coefficients since they are used repeatedly later on
149        // which would save redundant reductions in the later computations.
150        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        // Compute the batched evaluation claim.
162        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 correctness of shape.
171        assert_eq!(
172            proof.fri_commitments.len(),
173            proof.univariate_messages.len(),
174            "Sumcheck FRI Length Mismatch"
175        );
176
177        // The prover messages correspond to fixing the last coordinate first, so we reverse the
178        // underlying point for the verification.
179        point.reverse();
180
181        // Sample the challenges used for FRI folding and BaseFold random linear combinations.
182        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        // Check the consistency of the first univariate message with the claimed evaluation. The
200        // first_poly is supposed to be `vals(X_0, X_1, ..., X_{d-1}, 0), vals(X_0, X_1, ...,
201        // X_{d-1}, 1)`. Given this, the claimed evaluation should be `(1 - X_d) *
202        // first_poly[0] + X_d * first_poly[1]`.
203        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        // Fold the two messages into a single evaluation claim for the next round, using the
212        // sampled randomness.
213        let mut expected_eval = first_poly[0] + betas[0] * first_poly[1];
214
215        // Check round-by-round consistency between the successive sumcheck univariate messages.
216        for (i, (poly, beta)) in
217            proof.univariate_messages[1..].iter().zip(betas[1..].iter()).enumerate()
218        {
219            // The check is similar to the one for `first_poly`.
220            let i = i + 1;
221            builder.assert_ext_eq(expected_eval, (one - *point[i]) * poly[0] + *point[i] * poly[1]);
222
223            // Fold the two pieces of the message.
224            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        // Check proof of work (grinding to find a number that hashes to have
231        // `self.config.proof_of_work_bits` zeroes at the beginning).
232        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        // Sample query indices for the FRI query IOPP part of BaseFold. This part is very similar
238        // to the corresponding part in the univariate FRI verifier.
239        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        // Compute the batch evaluations from the openings of the component polynomials.
247        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        // Verify the proof of the claimed values.
268        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        // Check that the query openings are consistent as FRI messages.
282        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        // The final consistency check between the FRI messages and the partial evaluation messages.
294        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    /// The FRI verifier for a single query. We modify this from Plonky3 to be compatible with
302    /// opening only a single vector.
303    #[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        // Loop over the FRI queries.
331        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                // Check that the folded evaluation is consistent with the FRI query proof opening.
367                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                // interpolate and evaluate at beta
374                let temp_1: Felt<_> = builder.uninit();
375                builder.push_op(DslIr::SubF(temp_1, xs[1], xs[0]));
376
377                // let temp_2 = evals_ext[1] - evals_ext[0];
378                let temp_2: Ext<_, _> = builder.uninit();
379                builder.push_op(DslIr::SubE(temp_2, evals[1], evals[0]));
380
381                // let temp_3 = temp_2 / temp_1;
382                let temp_3: Ext<_, _> = builder.uninit();
383                builder.push_op(DslIr::DivEF(temp_3, temp_2, temp_1));
384
385                // let temp_4 = beta - xs[0];
386                let temp_4: Ext<_, _> = builder.uninit();
387                builder.push_op(DslIr::SubEF(temp_4, *beta, xs[0]));
388
389                // let temp_5 = temp_4 * temp_3;
390                let temp_5: Ext<_, _> = builder.uninit();
391                builder.push_op(DslIr::MulE(temp_5, temp_4, temp_3));
392
393                // let temp6 = evals_ext[0] + temp_5;
394                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 = x * x;
399                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            // Check that the opening is consistent with the commitment.
406            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        // Make a new point that is different from the original point.
605        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}