sp1_recursion_circuit_v2/
build_wrap_v2.rs

1use std::borrow::Borrow;
2
3use itertools::Itertools;
4use p3_baby_bear::BabyBear;
5use p3_bn254_fr::Bn254Fr;
6use p3_field::AbstractField;
7use p3_fri::TwoAdicFriPcsProof;
8use sp1_recursion_compiler::{
9    config::OuterConfig,
10    constraints::{Constraint, ConstraintCompiler},
11    ir::{Builder, Config, Ext, Felt, SymbolicExt, Var},
12};
13use sp1_recursion_core_v2::{
14    air::RecursionPublicValues,
15    machine::RecursionAir,
16    stark::config::{
17        BabyBearPoseidon2Outer, OuterChallenge, OuterChallengeMmcs, OuterFriProof, OuterVal,
18        OuterValMmcs,
19    },
20};
21use sp1_stark::{
22    AirOpenedValues, ChipOpenedValues, ShardCommitment, ShardOpenedValues, ShardProof,
23    StarkMachine, StarkVerifyingKey,
24};
25
26use crate::{
27    challenger::{CanObserveVariable, MultiField32ChallengerVariable},
28    stark::{ShardProofVariable, StarkVerifier},
29    utils::{felt_bytes_to_bn254_var, felts_to_bn254_var, words_to_bytes},
30    witness::Witnessable,
31    BatchOpeningVariable, FriCommitPhaseProofStepVariable, FriProofVariable, FriQueryProofVariable,
32    TwoAdicPcsProofVariable, VerifyingKeyVariable,
33};
34
35pub const DIGEST_SIZE: usize = 1;
36
37type OuterSC = BabyBearPoseidon2Outer;
38type OuterC = OuterConfig;
39type OuterDigestVariable = [Var<<OuterC as Config>::N>; DIGEST_SIZE];
40
41/// A function to build the circuit for the final wrap layer using the architecture of core-v2.
42///
43/// For now, the witnessing logic is not implemented and we just witness via constant proof variables.
44pub fn build_wrap_circuit_v2<F, const DEGREE: usize>(
45    wrap_vk: &StarkVerifyingKey<OuterSC>,
46    template_proof: ShardProof<OuterSC>,
47    outer_machine: StarkMachine<BabyBearPoseidon2Outer, RecursionAir<BabyBear, DEGREE, 0>>,
48) -> Vec<Constraint>
49where
50{
51    let mut builder = Builder::<OuterConfig>::default();
52    let mut challenger = MultiField32ChallengerVariable::new(&mut builder);
53
54    let preprocessed_commit_val: [Bn254Fr; 1] = wrap_vk.commit.into();
55    let preprocessed_commit: OuterDigestVariable = [builder.eval(preprocessed_commit_val[0])];
56    challenger.observe_commitment(&mut builder, preprocessed_commit);
57    let pc_start = builder.eval(wrap_vk.pc_start);
58    challenger.observe(&mut builder, pc_start);
59
60    // let mut witness = OuterWitness::default();
61    // template_proof.write(&mut witness);
62
63    let proof = template_proof.read(&mut builder);
64    // let proof = const_shard_proof(&mut builder, &template_proof);
65
66    let commited_values_digest = builder.constant(<C as Config>::N::zero());
67    builder.commit_commited_values_digest_circuit(commited_values_digest);
68    let vkey_hash = builder.constant(<C as Config>::N::zero());
69    builder.commit_vkey_hash_circuit(vkey_hash);
70
71    // Validate public values
72    // let mut pv_elements = Vec::new();
73    // for i in 0..PROOF_MAX_NUM_PVS {
74    //     let element = builder.get(&proof.public_values, i);
75    //     pv_elements.push(element);
76    // }
77
78    let pv: &RecursionPublicValues<_> = proof.public_values.as_slice().borrow();
79
80    // TODO: Add back.
81    // let one_felt: Felt<_> = builder.constant(BabyBear::one());
82    // // Proof must be complete. In the reduce program, this will ensure that the SP1 proof has
83    // // been fully accumulated.
84    // builder.assert_felt_eq(pv.is_complete, one_felt);
85
86    // Convert pv.sp1_vk_digest into Bn254
87    let pv_vkey_hash = felts_to_bn254_var(&mut builder, &pv.sp1_vk_digest);
88    // Vkey hash must match the witnessed commited_values_digest that we are committing to.
89    builder.assert_var_eq(pv_vkey_hash, vkey_hash);
90
91    // Convert pv.committed_value_digest into Bn254
92    let pv_committed_values_digest_bytes: [Felt<_>; 32] =
93        words_to_bytes(&pv.committed_value_digest).try_into().unwrap();
94    let pv_committed_values_digest: Var<_> =
95        felt_bytes_to_bn254_var(&mut builder, &pv_committed_values_digest_bytes);
96
97    // // Committed values digest must match the witnessed one that we are committing to.
98    builder.assert_var_eq(pv_committed_values_digest, commited_values_digest);
99
100    let ShardCommitment { main_commit, .. } = &proof.commitment;
101    challenger.observe_commitment(&mut builder, *main_commit);
102
103    challenger.observe_slice(&mut builder, proof.clone().public_values);
104
105    let StarkVerifyingKey { commit, pc_start, chip_information, chip_ordering } = wrap_vk;
106
107    let wrap_vk = VerifyingKeyVariable {
108        commitment: commit
109            .into_iter()
110            .map(|elem| builder.eval(elem))
111            .collect_vec()
112            .try_into()
113            .unwrap(),
114        pc_start: builder.eval(*pc_start),
115        chip_information: chip_information.clone(),
116        chip_ordering: chip_ordering.clone(),
117    };
118
119    StarkVerifier::<OuterC, OuterSC, _>::verify_shard(
120        &mut builder,
121        &wrap_vk,
122        &outer_machine,
123        &mut challenger.clone(),
124        &proof,
125    );
126
127    let zero_ext: Ext<_, _> = builder.constant(<OuterConfig as Config>::EF::zero());
128    let cumulative_sum: Ext<_, _> = builder.eval(zero_ext);
129    for chip in proof.opened_values.chips {
130        builder.assign(cumulative_sum, cumulative_sum + chip.cumulative_sum);
131    }
132    builder.assert_ext_eq(cumulative_sum, zero_ext);
133
134    // TODO: Add back.
135    // Verify the public values digest.
136    // let calculated_digest =
137    //     builder.p2_circuit_babybear_hash(&proof.public_values[0..NUM_PV_ELMS_TO_HASH]);
138    // let expected_digest = pv.digest;
139    // for (calculated_elm, expected_elm) in calculated_digest.iter().zip(expected_digest.iter()) {
140    //     builder.assert_felt_eq(*expected_elm, *calculated_elm);
141    // }
142
143    let mut backend = ConstraintCompiler::<OuterConfig>::default();
144    backend.emit(builder.operations)
145}
146
147/// A utility function to convert a `ShardProof` into a `ShardProofVariable`. Should be replaced by
148/// more refined witness generation.
149pub fn const_shard_proof(
150    builder: &mut Builder<OuterConfig>,
151    proof: &ShardProof<OuterSC>,
152) -> ShardProofVariable<OuterConfig, BabyBearPoseidon2Outer> {
153    let opening_proof = const_two_adic_pcs_proof(builder, proof.opening_proof.clone());
154    let opened_values = proof
155        .opened_values
156        .chips
157        .iter()
158        .map(|chip| {
159            let ChipOpenedValues {
160                preprocessed,
161                main,
162                permutation,
163                quotient,
164                cumulative_sum,
165                log_degree,
166            } = chip;
167            let AirOpenedValues { local: prepr_local, next: prepr_next } = preprocessed;
168            let AirOpenedValues { local: main_local, next: main_next } = main;
169            let AirOpenedValues { local: perm_local, next: perm_next } = permutation;
170
171            let quotient =
172                quotient.iter().map(|q| q.iter().map(|x| builder.constant(*x)).collect()).collect();
173            let cumulative_sum = builder.constant(*cumulative_sum);
174
175            let preprocessed = AirOpenedValues {
176                local: prepr_local.iter().map(|x| builder.constant(*x)).collect(),
177                next: prepr_next.iter().map(|x| builder.constant(*x)).collect(),
178            };
179
180            let main = AirOpenedValues {
181                local: main_local.iter().map(|x| builder.constant(*x)).collect(),
182                next: main_next.iter().map(|x| builder.constant(*x)).collect(),
183            };
184
185            let permutation = AirOpenedValues {
186                local: perm_local.iter().map(|x| builder.constant(*x)).collect(),
187                next: perm_next.iter().map(|x| builder.constant(*x)).collect(),
188            };
189
190            ChipOpenedValues {
191                preprocessed,
192                main,
193                permutation,
194                quotient,
195                cumulative_sum,
196                log_degree: *log_degree,
197            }
198        })
199        .collect();
200    let opened_values = ShardOpenedValues { chips: opened_values };
201    let ShardCommitment { main_commit, permutation_commit, quotient_commit } = proof.commitment;
202    let main_commit: [Bn254Fr; 1] = main_commit.into();
203    let permutation_commit: [Bn254Fr; 1] = permutation_commit.into();
204    let quotient_commit: [Bn254Fr; 1] = quotient_commit.into();
205
206    let main_commit = core::array::from_fn(|i| builder.eval(main_commit[i]));
207    let permutation_commit = core::array::from_fn(|i| builder.eval(permutation_commit[i]));
208    let quotient_commit = core::array::from_fn(|i| builder.eval(quotient_commit[i]));
209
210    let commitment = ShardCommitment { main_commit, permutation_commit, quotient_commit };
211    ShardProofVariable {
212        commitment,
213        public_values: proof.public_values.iter().map(|x| builder.constant(*x)).collect(),
214        opened_values,
215        opening_proof,
216        chip_ordering: proof.chip_ordering.clone(),
217    }
218}
219
220type C = OuterConfig;
221type SC = BabyBearPoseidon2Outer;
222type N = <C as Config>::N;
223
224// Copy-paste from InnerCircuit implementation, changing generic parameters.
225fn const_fri_proof(
226    builder: &mut Builder<C>,
227    fri_proof: OuterFriProof,
228) -> FriProofVariable<OuterConfig, SC> {
229    // Set the commit phase commits.
230    let commit_phase_commits = fri_proof
231        .commit_phase_commits
232        .iter()
233        .map(|commit| {
234            let commit: [N; DIGEST_SIZE] = (*commit).into();
235            commit.map(|x| builder.eval(x))
236        })
237        .collect::<Vec<_>>();
238
239    // Set the query proofs.
240    let query_proofs = fri_proof
241        .query_proofs
242        .iter()
243        .map(|query_proof| {
244            let commit_phase_openings = query_proof
245                .commit_phase_openings
246                .iter()
247                .map(|commit_phase_opening| {
248                    let sibling_value =
249                        builder.eval(SymbolicExt::from_f(commit_phase_opening.sibling_value));
250                    let opening_proof = commit_phase_opening
251                        .opening_proof
252                        .iter()
253                        .map(|sibling| sibling.map(|x| builder.eval(x)))
254                        .collect::<Vec<_>>();
255                    FriCommitPhaseProofStepVariable { sibling_value, opening_proof }
256                })
257                .collect::<Vec<_>>();
258            FriQueryProofVariable { commit_phase_openings }
259        })
260        .collect::<Vec<_>>();
261
262    // Initialize the FRI proof variable.
263    FriProofVariable {
264        commit_phase_commits,
265        query_proofs,
266        final_poly: builder.eval(SymbolicExt::from_f(fri_proof.final_poly)),
267        pow_witness: builder.eval(fri_proof.pow_witness),
268    }
269}
270
271pub fn const_two_adic_pcs_proof(
272    builder: &mut Builder<OuterConfig>,
273    proof: TwoAdicFriPcsProof<OuterVal, OuterChallenge, OuterValMmcs, OuterChallengeMmcs>,
274) -> TwoAdicPcsProofVariable<OuterConfig, SC> {
275    let fri_proof = const_fri_proof(builder, proof.fri_proof);
276    let query_openings = proof
277        .query_openings
278        .iter()
279        .map(|query_opening| {
280            query_opening
281                .iter()
282                .map(|opening| BatchOpeningVariable {
283                    opened_values: opening
284                        .opened_values
285                        .iter()
286                        .map(|opened_value| {
287                            opened_value
288                                .iter()
289                                .map(|value| vec![builder.eval::<Felt<_>, _>(*value)])
290                                .collect::<Vec<_>>()
291                        })
292                        .collect::<Vec<_>>(),
293                    opening_proof: opening
294                        .opening_proof
295                        .iter()
296                        .map(|opening_proof| opening_proof.map(|x| builder.eval(x)))
297                        .collect::<Vec<_>>(),
298                })
299                .collect::<Vec<_>>()
300        })
301        .collect::<Vec<_>>();
302    TwoAdicPcsProofVariable { fri_proof, query_openings }
303}
304#[cfg(test)]
305pub mod tests {
306
307    use std::{borrow::Borrow, iter::once, sync::Arc};
308
309    use p3_baby_bear::{BabyBear, DiffusionMatrixBabyBear};
310    use p3_challenger::{CanObserve, FieldChallenger};
311    use p3_commit::{Pcs, TwoAdicMultiplicativeCoset};
312    use p3_field::{extension::BinomialExtensionField, AbstractField};
313    use p3_matrix::dense::RowMajorMatrix;
314    use rand::{rngs::StdRng, SeedableRng};
315    use sp1_core_machine::utils::{log2_strict_usize, run_test_machine, setup_logger};
316    use sp1_recursion_compiler::{
317        config::OuterConfig,
318        constraints::ConstraintCompiler,
319        ir::{Builder, Config, Ext, SymbolicExt},
320    };
321    use sp1_recursion_core_v2::{
322        air::RecursionPublicValues,
323        instruction as instr,
324        machine::RecursionAir,
325        stark::config::{
326            outer_fri_config, outer_perm, BabyBearPoseidon2Outer, OuterChallenge, OuterChallenger,
327            OuterCompress, OuterDft, OuterHash, OuterPcs, OuterVal, OuterValMmcs,
328        },
329        BaseAluOpcode, MemAccessKind, RecursionProgram, Runtime,
330    };
331    use sp1_recursion_gnark_ffi::{Groth16Bn254Prover, PlonkBn254Prover};
332    use sp1_stark::{BabyBearPoseidon2Inner, StarkMachine};
333
334    use crate::{
335        challenger::CanObserveVariable,
336        fri::verify_two_adic_pcs,
337        hash::BN254_DIGEST_SIZE,
338        utils::{babybear_bytes_to_bn254, babybears_to_bn254, words_to_bytes},
339        witness::{OuterWitness, Witnessable},
340        Digest, TwoAdicPcsMatsVariable, TwoAdicPcsRoundVariable,
341    };
342
343    use super::{build_wrap_circuit_v2, const_two_adic_pcs_proof};
344
345    fn test_machine<F, const DEGREE: usize>(machine_maker: F)
346    where
347        F: Fn() -> StarkMachine<BabyBearPoseidon2Outer, RecursionAir<BabyBear, DEGREE, 0>>,
348    {
349        setup_logger();
350        let n = 10;
351        // Fibonacci(n)
352        let instructions = once(instr::mem(MemAccessKind::Write, 1, 0, 0))
353            .chain(once(instr::mem(MemAccessKind::Write, 2, 1, 1)))
354            .chain((2..=n).map(|i| instr::base_alu(BaseAluOpcode::AddF, 2, i, i - 2, i - 1)))
355            .chain(once(instr::mem(MemAccessKind::Read, 1, n - 1, 34)))
356            .chain(once(instr::mem(MemAccessKind::Read, 2, n, 55)))
357            .collect::<Vec<_>>();
358
359        let machine = machine_maker();
360        let program = RecursionProgram { instructions, ..Default::default() };
361        let mut runtime =
362            Runtime::<BabyBear, BinomialExtensionField<BabyBear, 4>, DiffusionMatrixBabyBear>::new(
363                Arc::new(program.clone()),
364                BabyBearPoseidon2Inner::new().perm,
365            );
366        runtime.run().unwrap();
367
368        let (pk, vk) = machine.setup(&program);
369        let result = run_test_machine(vec![runtime.record], machine, pk, vk.clone()).unwrap();
370
371        let machine = machine_maker();
372        let constraints =
373            build_wrap_circuit_v2::<BabyBear, DEGREE>(&vk, result.shard_proofs[0].clone(), machine);
374
375        let pv: &RecursionPublicValues<_> =
376            result.shard_proofs[0].public_values.as_slice().borrow();
377        let vkey_hash = babybears_to_bn254(&pv.sp1_vk_digest);
378        let committed_values_digest_bytes: [BabyBear; 32] =
379            words_to_bytes(&pv.committed_value_digest).try_into().unwrap();
380        let committed_values_digest = babybear_bytes_to_bn254(&committed_values_digest_bytes);
381
382        // Build the witness.
383        let mut witness = OuterWitness::default();
384        result.shard_proofs[0].write(&mut witness);
385        witness.write_commited_values_digest(committed_values_digest);
386        witness.write_vkey_hash(vkey_hash);
387
388        PlonkBn254Prover::test::<OuterConfig>(constraints.clone(), witness.clone());
389        Groth16Bn254Prover::test::<OuterConfig>(constraints, witness);
390    }
391
392    pub fn machine_with_all_chips<const DEGREE: usize>(
393        log_erbl_rows: usize,
394        log_p2_rows: usize,
395        log_frifold_rows: usize,
396    ) -> StarkMachine<BabyBearPoseidon2Outer, RecursionAir<BabyBear, DEGREE, 0>> {
397        let config = SC::new_with_log_blowup(log2_strict_usize(DEGREE - 1));
398        RecursionAir::<BabyBear, DEGREE, 0>::machine_with_padding(
399            config,
400            log_frifold_rows,
401            log_p2_rows,
402            log_erbl_rows,
403        )
404    }
405
406    #[test]
407    fn test_build_wrap() {
408        let machine_maker = || machine_with_all_chips::<17>(3, 3, 3);
409        test_machine(machine_maker);
410    }
411    type C = OuterConfig;
412    type SC = BabyBearPoseidon2Outer;
413
414    #[allow(clippy::type_complexity)]
415    pub fn const_two_adic_pcs_rounds(
416        builder: &mut Builder<OuterConfig>,
417        commit: [<C as Config>::N; BN254_DIGEST_SIZE],
418        os: Vec<(TwoAdicMultiplicativeCoset<OuterVal>, Vec<(OuterChallenge, Vec<OuterChallenge>)>)>,
419    ) -> (Digest<OuterConfig, SC>, Vec<TwoAdicPcsRoundVariable<OuterConfig, SC>>) {
420        let commit: Digest<OuterConfig, SC> = commit.map(|x| builder.eval(x));
421
422        let mut domains_points_and_opens = Vec::new();
423        for (domain, poly) in os.into_iter() {
424            let points: Vec<Ext<OuterVal, OuterChallenge>> =
425                poly.iter().map(|(p, _)| builder.eval(SymbolicExt::from_f(*p))).collect::<Vec<_>>();
426            let values: Vec<Vec<Ext<OuterVal, OuterChallenge>>> = poly
427                .iter()
428                .map(|(_, v)| {
429                    v.clone()
430                        .iter()
431                        .map(|t| builder.eval(SymbolicExt::from_f(*t)))
432                        .collect::<Vec<_>>()
433                })
434                .collect::<Vec<_>>();
435            let domain_points_and_values = TwoAdicPcsMatsVariable { domain, points, values };
436            domains_points_and_opens.push(domain_points_and_values);
437        }
438
439        (commit, vec![TwoAdicPcsRoundVariable { batch_commit: commit, domains_points_and_opens }])
440    }
441
442    #[test]
443    fn test_verify_two_adic_pcs_outer() {
444        let mut rng = StdRng::seed_from_u64(0xDEADBEEF);
445        let log_degrees = &[19, 19];
446        let perm = outer_perm();
447        let mut fri_config = outer_fri_config();
448
449        // Lower blowup factor for testing.
450        fri_config.log_blowup = 2;
451        let hash = OuterHash::new(perm.clone()).unwrap();
452        let compress = OuterCompress::new(perm.clone());
453        let val_mmcs = OuterValMmcs::new(hash, compress);
454        let dft = OuterDft {};
455        let pcs: OuterPcs =
456            OuterPcs::new(log_degrees.iter().copied().max().unwrap(), dft, val_mmcs, fri_config);
457
458        // Generate proof.
459        let domains_and_polys = log_degrees
460            .iter()
461            .map(|&d| {
462                (
463                    <OuterPcs as Pcs<OuterChallenge, OuterChallenger>>::natural_domain_for_degree(
464                        &pcs,
465                        1 << d,
466                    ),
467                    RowMajorMatrix::<OuterVal>::rand(&mut rng, 1 << d, 100),
468                )
469            })
470            .collect::<Vec<_>>();
471        let (commit, data) = <OuterPcs as Pcs<OuterChallenge, OuterChallenger>>::commit(
472            &pcs,
473            domains_and_polys.clone(),
474        );
475        let mut challenger = OuterChallenger::new(perm.clone()).unwrap();
476        challenger.observe(commit);
477        let zeta = challenger.sample_ext_element::<OuterChallenge>();
478        let points = domains_and_polys.iter().map(|_| vec![zeta]).collect::<Vec<_>>();
479        let (opening, proof) = pcs.open(vec![(&data, points)], &mut challenger);
480
481        // Verify proof.
482        let mut challenger = OuterChallenger::new(perm.clone()).unwrap();
483        challenger.observe(commit);
484        let x1 = challenger.sample_ext_element::<OuterChallenge>();
485        let os = domains_and_polys
486            .iter()
487            .zip(&opening[0])
488            .map(|((domain, _), mat_openings)| (*domain, vec![(zeta, mat_openings[0].clone())]))
489            .collect::<Vec<_>>();
490        pcs.verify(vec![(commit, os.clone())], &proof, &mut challenger).unwrap();
491
492        // Define circuit.
493        let mut builder = Builder::<OuterConfig>::default();
494        let mut config = outer_fri_config();
495
496        // Lower blowup factor for testing.
497        config.log_blowup = 2;
498        let proof = const_two_adic_pcs_proof(&mut builder, proof);
499        let (commit, rounds) = const_two_adic_pcs_rounds(&mut builder, commit.into(), os);
500        let mut challenger = crate::challenger::MultiField32ChallengerVariable::new(&mut builder);
501        challenger.observe_slice(&mut builder, commit);
502        let x2 = challenger.sample_ext(&mut builder);
503        let x1: Ext<_, _> = builder.constant(x1);
504        builder.assert_ext_eq(x1, x2);
505        verify_two_adic_pcs::<_, BabyBearPoseidon2Outer>(
506            &mut builder,
507            &config,
508            &proof,
509            &mut challenger,
510            rounds,
511        );
512        let mut backend = ConstraintCompiler::<OuterConfig>::default();
513        let constraints = backend.emit(builder.operations);
514        let witness = OuterWitness::default();
515        PlonkBn254Prover::test::<OuterConfig>(constraints, witness);
516    }
517}