sp1_recursion_circuit_v2/
fri.rs

1use itertools::{izip, Itertools};
2use p3_baby_bear::BabyBear;
3use p3_commit::PolynomialSpace;
4use p3_field::{AbstractField, TwoAdicField};
5use p3_fri::FriConfig;
6use p3_matrix::Dimensions;
7use p3_util::log2_strict_usize;
8use sp1_recursion_compiler::ir::{Builder, Felt, SymbolicExt, SymbolicFelt};
9use std::{
10    cmp::Reverse,
11    iter::{once, repeat_with, zip},
12};
13
14use crate::{
15    challenger::{CanSampleBitsVariable, FieldChallengerVariable},
16    BabyBearFriConfigVariable, CanObserveVariable, CircuitConfig, Ext, FriChallenges, FriMmcs,
17    FriProofVariable, FriQueryProofVariable, TwoAdicPcsProofVariable, TwoAdicPcsRoundVariable,
18};
19
20pub fn verify_shape_and_sample_challenges<
21    C: CircuitConfig<F = BabyBear>,
22    SC: BabyBearFriConfigVariable<C>,
23>(
24    builder: &mut Builder<C>,
25    config: &FriConfig<FriMmcs<SC>>,
26    proof: &FriProofVariable<C, SC>,
27    challenger: &mut SC::FriChallengerVariable,
28) -> FriChallenges<C> {
29    let betas = proof
30        .commit_phase_commits
31        .iter()
32        .map(|commitment| {
33            challenger.observe(builder, *commitment);
34            challenger.sample_ext(builder)
35        })
36        .collect();
37
38    // Observe the final polynomial.
39    let final_poly_felts = C::ext2felt(builder, proof.final_poly);
40    final_poly_felts.iter().for_each(|felt| {
41        challenger.observe(builder, *felt);
42    });
43
44    assert_eq!(proof.query_proofs.len(), config.num_queries);
45    challenger.check_witness(builder, config.proof_of_work_bits, proof.pow_witness);
46
47    let log_max_height = proof.commit_phase_commits.len() + config.log_blowup;
48    let query_indices: Vec<Vec<C::Bit>> =
49        repeat_with(|| challenger.sample_bits(builder, log_max_height))
50            .take(config.num_queries)
51            .collect();
52
53    FriChallenges { query_indices, betas }
54}
55
56pub fn verify_two_adic_pcs<C: CircuitConfig<F = SC::Val>, SC: BabyBearFriConfigVariable<C>>(
57    builder: &mut Builder<C>,
58    config: &FriConfig<FriMmcs<SC>>,
59    proof: &TwoAdicPcsProofVariable<C, SC>,
60    challenger: &mut SC::FriChallengerVariable,
61    rounds: Vec<TwoAdicPcsRoundVariable<C, SC>>,
62) {
63    let alpha = challenger.sample_ext(builder);
64
65    let fri_challenges =
66        verify_shape_and_sample_challenges::<C, SC>(builder, config, &proof.fri_proof, challenger);
67
68    let log_global_max_height = proof.fri_proof.commit_phase_commits.len() + config.log_blowup;
69
70    // The powers of alpha, where the ith element is alpha^i.
71    let mut alpha_pows: Vec<Ext<C::F, C::EF>> =
72        vec![builder.eval(SymbolicExt::from_f(C::EF::one()))];
73
74    let reduced_openings = proof
75        .query_openings
76        .iter()
77        .zip(&fri_challenges.query_indices)
78        .map(|(query_opening, index_bits)| {
79            // The powers of alpha, where the ith element is alpha^i.
80            let mut log_height_pow = [0usize; 32];
81            let mut ro: [Ext<C::F, C::EF>; 32] =
82                [builder.eval(SymbolicExt::from_f(C::EF::zero())); 32];
83
84            for (batch_opening, round) in zip(query_opening, rounds.iter().cloned()) {
85                let batch_commit = round.batch_commit;
86                let mats = round.domains_points_and_opens;
87                let batch_heights =
88                    mats.iter().map(|mat| mat.domain.size() << config.log_blowup).collect_vec();
89                let batch_dims = batch_heights
90                    .iter()
91                    .map(|&height| Dimensions { width: 0, height })
92                    .collect_vec();
93
94                let batch_max_height = batch_heights.iter().max().expect("Empty batch?");
95                let log_batch_max_height = log2_strict_usize(*batch_max_height);
96                let bits_reduced = log_global_max_height - log_batch_max_height;
97
98                let reduced_index_bits = index_bits[bits_reduced..].to_vec();
99
100                verify_batch::<C, SC>(
101                    builder,
102                    batch_commit,
103                    batch_dims,
104                    reduced_index_bits,
105                    batch_opening.opened_values.clone(),
106                    batch_opening.opening_proof.clone(),
107                );
108
109                for (mat_opening, mat) in izip!(&batch_opening.opened_values, mats) {
110                    let mat_domain = mat.domain;
111                    let mat_points = mat.points;
112                    let mat_values = mat.values;
113                    let log_height = log2_strict_usize(mat_domain.size()) + config.log_blowup;
114
115                    let bits_reduced = log_global_max_height - log_height;
116                    let reduced_index_bits_trunc =
117                        index_bits[bits_reduced..(bits_reduced + log_height)].to_vec();
118
119                    let g = builder.generator();
120                    let two_adic_generator: Felt<_> =
121                        builder.eval(C::F::two_adic_generator(log_height));
122                    let two_adic_generator_exp =
123                        C::exp_reverse_bits(builder, two_adic_generator, reduced_index_bits_trunc);
124                    let x: Felt<_> = builder.eval(g * two_adic_generator_exp);
125
126                    for (z, ps_at_z) in izip!(mat_points, mat_values) {
127                        // builder.cycle_tracker("2adic-hotloop");
128                        let mut acc: Ext<C::F, C::EF> =
129                            builder.eval(SymbolicExt::from_f(C::EF::zero()));
130                        for (p_at_x, p_at_z) in izip!(mat_opening.clone(), ps_at_z) {
131                            let pow = log_height_pow[log_height];
132                            // Fill in any missing powers of alpha.
133                            (alpha_pows.len()..pow + 1).for_each(|_| {
134                                let new_alpha = builder.eval(*alpha_pows.last().unwrap() * alpha);
135                                builder.reduce_e(new_alpha);
136                                alpha_pows.push(new_alpha);
137                            });
138                            acc = builder.eval(acc + (alpha_pows[pow] * (p_at_z - p_at_x[0])));
139                            log_height_pow[log_height] += 1;
140                        }
141                        ro[log_height] = builder.eval(ro[log_height] + acc / (z - x));
142                        // builder.cycle_tracker("2adic-hotloop");
143                    }
144                }
145            }
146            ro
147        })
148        .collect::<Vec<_>>();
149
150    verify_challenges::<C, SC>(
151        builder,
152        config,
153        &proof.fri_proof,
154        &fri_challenges,
155        reduced_openings,
156    );
157}
158
159pub fn verify_challenges<C: CircuitConfig<F = SC::Val>, SC: BabyBearFriConfigVariable<C>>(
160    builder: &mut Builder<C>,
161    config: &FriConfig<FriMmcs<SC>>,
162    proof: &FriProofVariable<C, SC>,
163    challenges: &FriChallenges<C>,
164    reduced_openings: Vec<[Ext<C::F, C::EF>; 32]>,
165) {
166    let log_max_height = proof.commit_phase_commits.len() + config.log_blowup;
167    for ((index_bits, query_proof), ro) in
168        challenges.query_indices.iter().zip(&proof.query_proofs).zip(reduced_openings)
169    {
170        let folded_eval = verify_query::<C, SC>(
171            builder,
172            proof.commit_phase_commits.clone(),
173            index_bits,
174            query_proof.clone(),
175            challenges.betas.clone(),
176            ro,
177            log_max_height,
178        );
179
180        builder.assert_ext_eq(folded_eval, proof.final_poly);
181    }
182}
183
184pub fn verify_query<C: CircuitConfig<F = SC::Val>, SC: BabyBearFriConfigVariable<C>>(
185    builder: &mut Builder<C>,
186    commit_phase_commits: Vec<SC::Digest>,
187    index_bits: &[C::Bit],
188    proof: FriQueryProofVariable<C, SC>,
189    betas: Vec<Ext<C::F, C::EF>>,
190    reduced_openings: [Ext<C::F, C::EF>; 32],
191    log_max_height: usize,
192) -> Ext<C::F, C::EF> {
193    let mut folded_eval: Ext<_, _> = builder.constant(C::EF::zero());
194    let two_adic_generator: Felt<_> = builder.constant(C::F::two_adic_generator(log_max_height));
195
196    let x_felt =
197        C::exp_reverse_bits(builder, two_adic_generator, index_bits[..log_max_height].to_vec());
198    let mut x: Ext<_, _> = builder.eval(SymbolicExt::one() * SymbolicFelt::from(x_felt));
199
200    for (offset, log_folded_height, commit, step, beta) in izip!(
201        0..,
202        (0..log_max_height).rev(),
203        commit_phase_commits,
204        &proof.commit_phase_openings,
205        betas,
206    ) {
207        folded_eval = builder.eval(folded_eval + reduced_openings[log_folded_height + 1]);
208
209        let index_sibling_complement: C::Bit = index_bits[offset].clone();
210        // let index_sibling_complement: Felt<_> = builder.constant(C::F::one());
211        let index_pair = &index_bits[(offset + 1)..];
212
213        builder.reduce_e(folded_eval);
214
215        let evals_ext = C::select_chain_ef(
216            builder,
217            index_sibling_complement.clone(),
218            once(folded_eval),
219            once(step.sibling_value),
220        );
221        let evals_felt = vec![
222            C::ext2felt(builder, evals_ext[0]).to_vec(),
223            C::ext2felt(builder, evals_ext[1]).to_vec(),
224        ];
225
226        let dims = &[Dimensions { width: 2, height: (1 << log_folded_height) }];
227        verify_batch::<C, SC>(
228            builder,
229            commit,
230            dims.to_vec(),
231            index_pair.to_vec(),
232            [evals_felt].to_vec(),
233            step.opening_proof.clone(),
234        );
235
236        let xs_new: Ext<_, _> = builder.eval(x * C::EF::two_adic_generator(1));
237        let xs = C::select_chain_ef(builder, index_sibling_complement, once(x), once(xs_new));
238        folded_eval = builder
239            .eval(evals_ext[0] + (beta - xs[0]) * (evals_ext[1] - evals_ext[0]) / (xs[1] - xs[0]));
240        x = builder.eval(x * x);
241    }
242
243    folded_eval
244}
245
246pub fn verify_batch<C: CircuitConfig<F = SC::Val>, SC: BabyBearFriConfigVariable<C>>(
247    builder: &mut Builder<C>,
248    commit: SC::Digest,
249    dimensions: Vec<Dimensions>,
250    index_bits: Vec<C::Bit>,
251    opened_values: Vec<Vec<Vec<Felt<C::F>>>>,
252    proof: Vec<SC::Digest>,
253) {
254    let mut heights_tallest_first =
255        dimensions.iter().enumerate().sorted_by_key(|(_, dims)| Reverse(dims.height)).peekable();
256
257    let mut curr_height_padded = heights_tallest_first.peek().unwrap().1.height.next_power_of_two();
258
259    let ext_slice: Vec<Vec<Felt<C::F>>> = heights_tallest_first
260        .peeking_take_while(|(_, dims)| dims.height.next_power_of_two() == curr_height_padded)
261        .flat_map(|(i, _)| opened_values[i].as_slice())
262        .cloned()
263        .collect::<Vec<_>>();
264    let felt_slice: Vec<Felt<C::F>> =
265        ext_slice.iter().flat_map(|ext| ext.as_slice()).cloned().collect::<Vec<_>>();
266    let mut root: SC::Digest = SC::hash(builder, &felt_slice[..]);
267
268    zip(index_bits, proof).for_each(|(bit, sibling): (C::Bit, SC::Digest)| {
269        let compress_args = SC::select_chain_digest(builder, bit, [root, sibling]);
270
271        root = SC::compress(builder, compress_args);
272        curr_height_padded >>= 1;
273
274        let next_height = heights_tallest_first
275            .peek()
276            .map(|(_, dims)| dims.height)
277            .filter(|h| h.next_power_of_two() == curr_height_padded);
278
279        if let Some(next_height) = next_height {
280            let ext_slice: Vec<Vec<Felt<C::F>>> = heights_tallest_first
281                .peeking_take_while(|(_, dims)| dims.height == next_height)
282                .flat_map(|(i, _)| opened_values[i].as_slice())
283                .cloned()
284                .collect::<Vec<_>>();
285            let felt_slice: Vec<Felt<C::F>> =
286                ext_slice.iter().flat_map(|ext| ext.as_slice()).cloned().collect::<Vec<_>>();
287            let next_height_openings_digest = SC::hash(builder, &felt_slice);
288            root = SC::compress(builder, [root, next_height_openings_digest]);
289        }
290    });
291
292    SC::assert_digest_eq(builder, root, commit);
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298    use crate::{
299        challenger::DuplexChallengerVariable, utils::tests::run_test_recursion,
300        BatchOpeningVariable, FriCommitPhaseProofStepVariable, FriProofVariable,
301        FriQueryProofVariable, TwoAdicPcsMatsVariable, TwoAdicPcsProofVariable,
302    };
303    use p3_challenger::{CanObserve, CanSample, FieldChallenger};
304    use p3_commit::{Pcs, TwoAdicMultiplicativeCoset};
305    use p3_field::AbstractField;
306    use p3_fri::{verifier, TwoAdicFriPcsProof};
307    use p3_matrix::dense::RowMajorMatrix;
308    use rand::{
309        rngs::{OsRng, StdRng},
310        SeedableRng,
311    };
312    use sp1_recursion_compiler::{
313        asm::AsmBuilder,
314        config::InnerConfig,
315        ir::{Builder, Ext, SymbolicExt},
316    };
317    use sp1_stark::{
318        baby_bear_poseidon2::BabyBearPoseidon2, inner_fri_config, inner_perm, InnerChallenge,
319        InnerChallengeMmcs, InnerChallenger, InnerCompress, InnerDft, InnerFriProof, InnerHash,
320        InnerPcs, InnerVal, InnerValMmcs, StarkGenericConfig,
321    };
322
323    use sp1_recursion_core_v2::DIGEST_SIZE;
324
325    use crate::Digest;
326
327    type C = InnerConfig;
328    type SC = BabyBearPoseidon2;
329    type F = <SC as StarkGenericConfig>::Val;
330    type EF = <SC as StarkGenericConfig>::Challenge;
331
332    pub fn const_fri_proof(
333        builder: &mut AsmBuilder<F, EF>,
334        fri_proof: InnerFriProof,
335    ) -> FriProofVariable<InnerConfig, SC> {
336        // Set the commit phase commits.
337        let commit_phase_commits = fri_proof
338            .commit_phase_commits
339            .iter()
340            .map(|commit| {
341                let commit: [F; DIGEST_SIZE] = (*commit).into();
342                commit.map(|x| builder.eval(x))
343            })
344            .collect::<Vec<_>>();
345
346        // Set the query proofs.
347        let query_proofs = fri_proof
348            .query_proofs
349            .iter()
350            .map(|query_proof| {
351                let commit_phase_openings = query_proof
352                    .commit_phase_openings
353                    .iter()
354                    .map(|commit_phase_opening| {
355                        let sibling_value =
356                            builder.eval(SymbolicExt::from_f(commit_phase_opening.sibling_value));
357                        let opening_proof = commit_phase_opening
358                            .opening_proof
359                            .iter()
360                            .map(|sibling| sibling.map(|x| builder.eval(x)))
361                            .collect::<Vec<_>>();
362                        FriCommitPhaseProofStepVariable { sibling_value, opening_proof }
363                    })
364                    .collect::<Vec<_>>();
365                FriQueryProofVariable { commit_phase_openings }
366            })
367            .collect::<Vec<_>>();
368
369        // Initialize the FRI proof variable.
370        FriProofVariable {
371            commit_phase_commits,
372            query_proofs,
373            final_poly: builder.eval(SymbolicExt::from_f(fri_proof.final_poly)),
374            pow_witness: builder.eval(fri_proof.pow_witness),
375        }
376    }
377
378    pub fn const_two_adic_pcs_proof(
379        builder: &mut Builder<InnerConfig>,
380        proof: TwoAdicFriPcsProof<InnerVal, InnerChallenge, InnerValMmcs, InnerChallengeMmcs>,
381    ) -> TwoAdicPcsProofVariable<InnerConfig, SC> {
382        let fri_proof = const_fri_proof(builder, proof.fri_proof);
383        let query_openings = proof
384            .query_openings
385            .iter()
386            .map(|query_opening| {
387                query_opening
388                    .iter()
389                    .map(|opening| BatchOpeningVariable {
390                        opened_values: opening
391                            .opened_values
392                            .iter()
393                            .map(|opened_value| {
394                                opened_value
395                                    .iter()
396                                    .map(|value| vec![builder.eval::<Felt<_>, _>(*value)])
397                                    .collect::<Vec<_>>()
398                            })
399                            .collect::<Vec<_>>(),
400                        opening_proof: opening
401                            .opening_proof
402                            .iter()
403                            .map(|opening_proof| opening_proof.map(|x| builder.eval(x)))
404                            .collect::<Vec<_>>(),
405                    })
406                    .collect::<Vec<_>>()
407            })
408            .collect::<Vec<_>>();
409        TwoAdicPcsProofVariable { fri_proof, query_openings }
410    }
411
412    #[allow(clippy::type_complexity)]
413    pub fn const_two_adic_pcs_rounds(
414        builder: &mut Builder<InnerConfig>,
415        commit: [F; DIGEST_SIZE],
416        os: Vec<(TwoAdicMultiplicativeCoset<InnerVal>, Vec<(InnerChallenge, Vec<InnerChallenge>)>)>,
417    ) -> (Digest<InnerConfig, SC>, Vec<TwoAdicPcsRoundVariable<InnerConfig, SC>>) {
418        let commit: Digest<InnerConfig, SC> = commit.map(|x| builder.eval(x));
419
420        let mut domains_points_and_opens = Vec::new();
421        for (domain, poly) in os.into_iter() {
422            let points: Vec<Ext<InnerVal, InnerChallenge>> =
423                poly.iter().map(|(p, _)| builder.eval(SymbolicExt::from_f(*p))).collect::<Vec<_>>();
424            let values: Vec<Vec<Ext<InnerVal, InnerChallenge>>> = poly
425                .iter()
426                .map(|(_, v)| {
427                    v.clone()
428                        .iter()
429                        .map(|t| builder.eval(SymbolicExt::from_f(*t)))
430                        .collect::<Vec<_>>()
431                })
432                .collect::<Vec<_>>();
433            let domain_points_and_values = TwoAdicPcsMatsVariable { domain, points, values };
434            domains_points_and_opens.push(domain_points_and_values);
435        }
436
437        (commit, vec![TwoAdicPcsRoundVariable { batch_commit: commit, domains_points_and_opens }])
438    }
439
440    /// Reference: https://github.com/Plonky3/Plonky3/blob/4809fa7bedd9ba8f6f5d3267b1592618e3776c57/merkle-tree/src/mmcs.rs#L421
441    #[test]
442    fn size_gaps() {
443        use p3_commit::Mmcs;
444        let perm = inner_perm();
445        let hash = InnerHash::new(perm.clone());
446        let compress = InnerCompress::new(perm);
447        let mmcs = InnerValMmcs::new(hash, compress);
448
449        let mut builder = Builder::<InnerConfig>::default();
450
451        // 4 mats with 1000 rows, 8 columns
452        let large_mats = (0..4).map(|_| RowMajorMatrix::<F>::rand(&mut OsRng, 1000, 8));
453        let large_mat_dims = (0..4).map(|_| Dimensions { height: 1000, width: 8 });
454
455        // 5 mats with 70 rows, 8 columns
456        let medium_mats = (0..5).map(|_| RowMajorMatrix::<F>::rand(&mut OsRng, 70, 8));
457        let medium_mat_dims = (0..5).map(|_| Dimensions { height: 70, width: 8 });
458
459        // 6 mats with 8 rows, 8 columns
460        let small_mats = (0..6).map(|_| RowMajorMatrix::<F>::rand(&mut OsRng, 8, 8));
461        let small_mat_dims = (0..6).map(|_| Dimensions { height: 8, width: 8 });
462
463        let (commit, prover_data) =
464            mmcs.commit(large_mats.chain(medium_mats).chain(small_mats).collect_vec());
465
466        let commit: [_; DIGEST_SIZE] = commit.into();
467        let commit = commit.map(|x| builder.eval(x));
468        // open the 6th row of each matrix and verify
469        let (opened_values, proof) = mmcs.open_batch(6, &prover_data);
470        let opened_values = opened_values
471            .into_iter()
472            .map(|x| x.into_iter().map(|y| vec![builder.eval::<Felt<_>, _>(y)]).collect())
473            .collect();
474        let index = builder.eval(F::from_canonical_u32(6));
475        let index_bits = C::num2bits(&mut builder, index, 32);
476        let proof = proof.into_iter().map(|p| p.map(|x| builder.eval(x))).collect();
477        verify_batch::<_, SC>(
478            &mut builder,
479            commit,
480            large_mat_dims.chain(medium_mat_dims).chain(small_mat_dims).collect_vec(),
481            index_bits,
482            opened_values,
483            proof,
484        );
485    }
486
487    #[test]
488    fn test_fri_verify_shape_and_sample_challenges() {
489        let mut rng = &mut OsRng;
490        let log_degrees = &[16, 9, 7, 4, 2];
491        let perm = inner_perm();
492        let fri_config = inner_fri_config();
493        let hash = InnerHash::new(perm.clone());
494        let compress = InnerCompress::new(perm.clone());
495        let val_mmcs = InnerValMmcs::new(hash, compress);
496        let dft = InnerDft {};
497        let pcs: InnerPcs =
498            InnerPcs::new(log_degrees.iter().copied().max().unwrap(), dft, val_mmcs, fri_config);
499
500        // Generate proof.
501        let domains_and_polys = log_degrees
502            .iter()
503            .map(|&d| {
504                (
505                    <InnerPcs as Pcs<InnerChallenge, InnerChallenger>>::natural_domain_for_degree(
506                        &pcs,
507                        1 << d,
508                    ),
509                    RowMajorMatrix::<InnerVal>::rand(&mut rng, 1 << d, 10),
510                )
511            })
512            .collect::<Vec<_>>();
513        let (commit, data) = <InnerPcs as Pcs<InnerChallenge, InnerChallenger>>::commit(
514            &pcs,
515            domains_and_polys.clone(),
516        );
517        let mut challenger = InnerChallenger::new(perm.clone());
518        challenger.observe(commit);
519        let zeta = challenger.sample_ext_element::<InnerChallenge>();
520        let points = repeat_with(|| vec![zeta]).take(domains_and_polys.len()).collect::<Vec<_>>();
521        let (_, proof) = pcs.open(vec![(&data, points)], &mut challenger);
522
523        // Verify proof.
524        let mut challenger = InnerChallenger::new(perm.clone());
525        challenger.observe(commit);
526        let _: InnerChallenge = challenger.sample();
527        let fri_challenges_gt = verifier::verify_shape_and_sample_challenges(
528            &inner_fri_config(),
529            &proof.fri_proof,
530            &mut challenger,
531        )
532        .unwrap();
533
534        // Define circuit.
535        let mut builder = Builder::<InnerConfig>::default();
536        let config = inner_fri_config();
537        let fri_proof = const_fri_proof(&mut builder, proof.fri_proof);
538
539        let mut challenger = DuplexChallengerVariable::new(&mut builder);
540        let commit: [_; DIGEST_SIZE] = commit.into();
541        let commit: [Felt<InnerVal>; DIGEST_SIZE] = commit.map(|x| builder.eval(x));
542        challenger.observe_slice(&mut builder, commit);
543        let _ = challenger.sample_ext(&mut builder);
544        let fri_challenges = verify_shape_and_sample_challenges::<InnerConfig, BabyBearPoseidon2>(
545            &mut builder,
546            &config,
547            &fri_proof,
548            &mut challenger,
549        );
550
551        for i in 0..fri_challenges_gt.betas.len() {
552            builder.assert_ext_eq(
553                SymbolicExt::from_f(fri_challenges_gt.betas[i]),
554                fri_challenges.betas[i],
555            );
556        }
557
558        for i in 0..fri_challenges_gt.query_indices.len() {
559            let query_indices =
560                C::bits2num(&mut builder, fri_challenges.query_indices[i].iter().cloned());
561            builder.assert_felt_eq(
562                F::from_canonical_usize(fri_challenges_gt.query_indices[i]),
563                query_indices,
564            );
565        }
566
567        run_test_recursion(builder.operations, None);
568    }
569
570    #[test]
571    fn test_verify_two_adic_pcs_inner() {
572        let mut rng = StdRng::seed_from_u64(0xDEADBEEF);
573        let log_degrees = &[19, 19];
574        let perm = inner_perm();
575        let fri_config = inner_fri_config();
576        let hash = InnerHash::new(perm.clone());
577        let compress = InnerCompress::new(perm.clone());
578        let val_mmcs = InnerValMmcs::new(hash, compress);
579        let dft = InnerDft {};
580        let pcs: InnerPcs =
581            InnerPcs::new(log_degrees.iter().copied().max().unwrap(), dft, val_mmcs, fri_config);
582
583        // Generate proof.
584        let domains_and_polys = log_degrees
585            .iter()
586            .map(|&d| {
587                (
588                    <InnerPcs as Pcs<InnerChallenge, InnerChallenger>>::natural_domain_for_degree(
589                        &pcs,
590                        1 << d,
591                    ),
592                    RowMajorMatrix::<InnerVal>::rand(&mut rng, 1 << d, 100),
593                )
594            })
595            .collect::<Vec<_>>();
596        let (commit, data) = <InnerPcs as Pcs<InnerChallenge, InnerChallenger>>::commit(
597            &pcs,
598            domains_and_polys.clone(),
599        );
600        let mut challenger = InnerChallenger::new(perm.clone());
601        challenger.observe(commit);
602        let zeta = challenger.sample_ext_element::<InnerChallenge>();
603        let points = domains_and_polys.iter().map(|_| vec![zeta]).collect::<Vec<_>>();
604        let (opening, proof) = pcs.open(vec![(&data, points)], &mut challenger);
605
606        // Verify proof.
607        let mut challenger = InnerChallenger::new(perm.clone());
608        challenger.observe(commit);
609        let x1 = challenger.sample_ext_element::<InnerChallenge>();
610        let os = domains_and_polys
611            .iter()
612            .zip(&opening[0])
613            .map(|((domain, _), mat_openings)| (*domain, vec![(zeta, mat_openings[0].clone())]))
614            .collect::<Vec<_>>();
615        pcs.verify(vec![(commit, os.clone())], &proof, &mut challenger).unwrap();
616
617        // Define circuit.
618        let mut builder = Builder::<InnerConfig>::default();
619        let config = inner_fri_config();
620        let proof = const_two_adic_pcs_proof(&mut builder, proof);
621        let (commit, rounds) = const_two_adic_pcs_rounds(&mut builder, commit.into(), os);
622        let mut challenger = DuplexChallengerVariable::new(&mut builder);
623        challenger.observe_slice(&mut builder, commit);
624        let x2 = challenger.sample_ext(&mut builder);
625        let x1: Ext<_, _> = builder.constant(x1);
626        builder.assert_ext_eq(x1, x2);
627        verify_two_adic_pcs::<_, BabyBearPoseidon2>(
628            &mut builder,
629            &config,
630            &proof,
631            &mut challenger,
632            rounds,
633        );
634
635        run_test_recursion(builder.operations, std::iter::empty());
636    }
637}