sp1_recursion_circuit_v2/machine/
core.rs

1use std::{
2    array,
3    borrow::{Borrow, BorrowMut},
4    marker::PhantomData,
5};
6
7use itertools::Itertools;
8use p3_baby_bear::BabyBear;
9use p3_commit::Mmcs;
10use p3_field::AbstractField;
11use p3_matrix::dense::RowMajorMatrix;
12use sp1_core_machine::riscv::RiscvAir;
13use sp1_primitives::consts::WORD_SIZE;
14use sp1_recursion_core_v2::air::PV_DIGEST_NUM_WORDS;
15use sp1_stark::{
16    air::{PublicValues, POSEIDON_NUM_WORDS},
17    StarkMachine, Word,
18};
19
20use crate::{
21    utils::commit_recursion_public_values, BabyBearFriConfig, BabyBearFriConfigVariable,
22    CircuitConfig,
23};
24
25use sp1_recursion_compiler::{
26    circuit::CircuitV2Builder,
27    ir::{Builder, Config, Ext, ExtConst, Felt},
28};
29
30use sp1_recursion_core_v2::{
31    air::{RecursionPublicValues, RECURSIVE_PROOF_NUM_PV_ELTS},
32    DIGEST_SIZE,
33};
34
35use crate::{
36    challenger::{CanObserveVariable, DuplexChallengerVariable},
37    stark::{ShardProofVariable, StarkVerifier},
38    VerifyingKeyVariable,
39};
40
41pub struct SP1RecursionWitnessVariable<
42    C: CircuitConfig<F = BabyBear>,
43    SC: BabyBearFriConfigVariable<C>,
44> {
45    pub vk: VerifyingKeyVariable<C, SC>,
46    pub shard_proofs: Vec<ShardProofVariable<C, SC>>,
47    pub leaf_challenger: SC::FriChallengerVariable,
48    pub initial_reconstruct_challenger: DuplexChallengerVariable<C>,
49    pub is_complete: Felt<C::F>,
50}
51
52/// A program for recursively verifying a batch of SP1 proofs.
53#[derive(Debug, Clone, Copy)]
54pub struct SP1RecursiveVerifier<C: Config, SC: BabyBearFriConfig> {
55    _phantom: PhantomData<(C, SC)>,
56}
57
58impl<C, SC> SP1RecursiveVerifier<C, SC>
59where
60    SC: BabyBearFriConfigVariable<
61        C,
62        FriChallengerVariable = DuplexChallengerVariable<C>,
63        Digest = [Felt<BabyBear>; DIGEST_SIZE],
64    >,
65    C: CircuitConfig<F = SC::Val, EF = SC::Challenge, Bit = Felt<BabyBear>>,
66    <SC::ValMmcs as Mmcs<BabyBear>>::ProverData<RowMajorMatrix<BabyBear>>: Clone,
67{
68    /// Verify a batch of SP1 shard proofs and aggregate their public values.
69    ///
70    /// This program represents a first recursive step in the verification of an SP1 proof
71    /// consisting of one or more shards. Each shard proof is verified and its public values are
72    /// aggregated into a single set representing the start and end state of the program execution
73    /// across all shards.
74    ///
75    /// # Constraints
76    ///
77    /// ## Verifying the STARK proofs.
78    /// For each shard, the verifier asserts the correctness of the STARK proof which is composed
79    /// of verifying the FRI proof for openings and verifying the constraints.
80    ///
81    /// ## Aggregating the shard public values.
82    /// See [SP1Prover::verify] for the verification algorithm of a complete SP1 proof. In this
83    /// function, we are aggregating several shard proofs and attesting to an aggregated state which
84    /// represents all the shards.
85    ///
86    /// ## The leaf challenger.
87    /// A key difference between the recursive tree verification and the complete one in
88    /// [SP1Prover::verify] is that the recursive verifier has no way of reconstructing the
89    /// chanllenger only from a part of the shard proof. Therefore, the value of the leaf challenger
90    /// is witnessed in the program and the verifier asserts correctness given this challenger.
91    /// In the course of the recursive verification, the challenger is reconstructed by observing
92    /// the commitments one by one, and in the final step, the challenger is asserted to be the same
93    /// as the one witnessed here.
94    pub fn verify(
95        builder: &mut Builder<C>,
96        machine: &StarkMachine<SC, RiscvAir<SC::Val>>,
97        input: SP1RecursionWitnessVariable<C, SC>,
98    ) {
99        // Read input.
100        let SP1RecursionWitnessVariable {
101            vk,
102            shard_proofs,
103            leaf_challenger,
104            initial_reconstruct_challenger,
105            is_complete,
106        } = input;
107
108        // Initialize shard variables.
109        let initial_shard: Felt<_> = builder.uninit();
110        let current_shard: Felt<_> = builder.uninit();
111
112        // Initialize execution shard variables.
113        let initial_execution_shard: Felt<_> = builder.uninit();
114        let current_execution_shard: Felt<_> = builder.uninit();
115
116        // Initialize program counter variables.
117        let start_pc: Felt<_> = builder.uninit();
118        let current_pc: Felt<_> = builder.uninit();
119
120        // Initialize memory initialization and finalization variables.
121        let initial_previous_init_addr_bits: [Felt<_>; 32] = array::from_fn(|_| builder.uninit());
122        let initial_previous_finalize_addr_bits: [Felt<_>; 32] =
123            array::from_fn(|_| builder.uninit());
124        let current_init_addr_bits: [Felt<_>; 32] = array::from_fn(|_| builder.uninit());
125        let current_finalize_addr_bits: [Felt<_>; 32] = array::from_fn(|_| builder.uninit());
126
127        // Initialize the exit code variable.
128        let exit_code: Felt<_> = builder.uninit();
129
130        // Initialize the public values digest.
131        let committed_value_digest: [Word<Felt<_>>; PV_DIGEST_NUM_WORDS] =
132            array::from_fn(|_| Word(array::from_fn(|_| builder.uninit())));
133
134        // Initialize the deferred proofs digest.
135        let deferred_proofs_digest: [Felt<_>; POSEIDON_NUM_WORDS] =
136            array::from_fn(|_| builder.uninit());
137
138        // Initialize the challenger variables.
139        let leaf_challenger_public_values = leaf_challenger.public_values(builder);
140        let mut reconstruct_challenger: DuplexChallengerVariable<_> =
141            initial_reconstruct_challenger.copy(builder);
142
143        // Initialize the cumulative sum.
144        let cumulative_sum: Ext<_, _> = builder.eval(C::EF::zero().cons());
145
146        // Assert that the number of proofs is not zero.
147        assert!(!shard_proofs.is_empty());
148
149        // Verify proofs.
150        for (i, shard_proof) in shard_proofs.into_iter().enumerate() {
151            let contains_cpu = shard_proof.contains_cpu();
152            let _contains_memory_init = shard_proof.contains_memory_init();
153            let _contains_memory_finalize = shard_proof.contains_memory_finalize();
154
155            // Get the public values.
156            let public_values: &PublicValues<Word<Felt<_>>, Felt<_>> =
157                shard_proof.public_values.as_slice().borrow();
158
159            let _shard = public_values.shard;
160
161            // If this is the first proof in the batch, initialize the variables.
162            if i == 0 {
163                // Shard.
164                builder.assign(initial_shard, public_values.shard);
165                builder.assign(current_shard, public_values.shard);
166
167                // Execution shard.
168                builder.assign(initial_execution_shard, public_values.execution_shard);
169                builder.assign(current_execution_shard, public_values.execution_shard);
170
171                // Program counter.
172                builder.assign(start_pc, public_values.start_pc);
173                builder.assign(current_pc, public_values.start_pc);
174
175                // Memory initialization & finalization.
176                for ((bit, pub_bit), first_bit) in current_init_addr_bits
177                    .iter()
178                    .zip(public_values.previous_init_addr_bits.iter())
179                    .zip(initial_previous_init_addr_bits.iter())
180                {
181                    builder.assign(*bit, *pub_bit);
182                    builder.assign(*first_bit, *pub_bit);
183                }
184                for ((bit, pub_bit), first_bit) in current_finalize_addr_bits
185                    .iter()
186                    .zip(public_values.previous_finalize_addr_bits.iter())
187                    .zip(initial_previous_finalize_addr_bits.iter())
188                {
189                    builder.assign(*bit, *pub_bit);
190                    builder.assign(*first_bit, *pub_bit);
191                }
192
193                // Exit code.
194                builder.assign(exit_code, public_values.exit_code);
195
196                // Commited public values digests.
197                for (word, first_word) in committed_value_digest
198                    .iter()
199                    .zip_eq(public_values.committed_value_digest.iter())
200                {
201                    for (byte, first_byte) in word.0.iter().zip_eq(first_word.0.iter()) {
202                        builder.assign(*byte, *first_byte);
203                    }
204                }
205
206                // Deferred proofs digests.
207                for (digest, first_digest) in deferred_proofs_digest
208                    .iter()
209                    .zip_eq(public_values.deferred_proofs_digest.iter())
210                {
211                    builder.assign(*digest, *first_digest);
212                }
213            }
214
215            // // If the shard is the first shard, assert that the initial challenger is equal to a
216            // // fresh challenger observing the verifier key and the initial pc.
217            // let shard = felt2var(builder, public_values.shard);
218            // builder.if_eq(shard, C::N::one()).then(|builder| {
219            //     let mut first_initial_challenger = DuplexChallengerVariable::new(builder);
220            //     first_initial_challenger.observe(builder, vk.commitment.clone());
221            //     first_initial_challenger.observe(builder, vk.pc_start);
222            //     initial_reconstruct_challenger.assert_eq(builder, &first_initial_challenger);
223            // });
224
225            // Verify the shard.
226            //
227            // Do not verify the cumulative sum here, since the permutation challenge is shared
228            // between all shards.
229            let mut challenger = leaf_challenger.copy(builder);
230            StarkVerifier::<C, SC, _>::verify_shard(
231                builder,
232                &vk,
233                machine,
234                &mut challenger,
235                &shard_proof,
236            );
237
238            // // First shard has a "CPU" constraint.
239            // {
240            //     builder.if_eq(shard, C::N::one()).then(|builder| {
241            //         builder.assert_var_eq(contains_cpu, C::N::one());
242            //     });
243            // }
244
245            // // CPU log degree bound check constraints.
246            // {
247            //     for (i, chip) in machine.chips().iter().enumerate() {
248            //         if chip.name() == "CPU" {
249            //             builder.if_eq(contains_cpu, C::N::one()).then(|builder| {
250            //                 let index = builder.get(&proof.sorted_idxs, i);
251            //                 let cpu_log_degree =
252            //                     builder.get(&proof.opened_values.chips, index).log_degree;
253            //                 let cpu_log_degree_lt_max: Var<_> = builder.eval(C::N::zero());
254            //                 builder
255            //                     .range(0, MAX_CPU_LOG_DEGREE + 1)
256            //                     .for_each(|j, builder| {
257            //                         builder.if_eq(j, cpu_log_degree).then(|builder| {
258            //                             builder.assign(cpu_log_degree_lt_max, C::N::one());
259            //                         });
260            //                     });
261            //                 builder.assert_var_eq(cpu_log_degree_lt_max, C::N::one());
262            //             });
263            //         }
264            //     }
265            // }
266
267            // Shard constraints.
268            {
269                // Assert that the shard of the proof is equal to the current shard.
270                builder.assert_felt_eq(current_shard, public_values.shard);
271
272                // Increment the current shard by one.
273                builder.assign(current_shard, current_shard + C::F::one());
274            }
275
276            // Execution shard constraints.
277            // let execution_shard = felt2var(builder, public_values.execution_shard);
278            {
279                // If the shard has a "CPU" chip, then the execution shard should be incremented by
280                // 1.
281                if contains_cpu {
282                    // Assert that the shard of the proof is equal to the current shard.
283                    // builder.assert_felt_eq(current_execution_shard,
284                    // public_values.execution_shard);
285
286                    builder.assign(current_execution_shard, current_execution_shard + C::F::one());
287                }
288            }
289
290            // Program counter constraints.
291            {
292                // // If it's the first shard (which is the first execution shard), then the
293                // start_pc // should be vk.pc_start.
294                // builder.if_eq(shard, C::N::one()).then(|builder| {
295                //     builder.assert_felt_eq(public_values.start_pc, vk.pc_start);
296                // });
297
298                // // Assert that the start_pc of the proof is equal to the current pc.
299                // builder.assert_felt_eq(current_pc, public_values.start_pc);
300
301                // // If it's not a shard with "CPU", then assert that the start_pc equals the
302                // next_pc. builder.if_ne(contains_cpu, C::N::one()).then(|builder|
303                // {     builder.assert_felt_eq(public_values.start_pc,
304                // public_values.next_pc); });
305
306                // // If it's a shard with "CPU", then assert that the start_pc is not zero.
307                // builder.if_eq(contains_cpu, C::N::one()).then(|builder| {
308                //     builder.assert_felt_ne(public_values.start_pc, C::F::zero());
309                // });
310
311                // Update current_pc to be the end_pc of the current proof.
312                builder.assign(current_pc, public_values.next_pc);
313            }
314
315            // Exit code constraints.
316            {
317                // Assert that the exit code is zero (success) for all proofs.
318                builder.assert_felt_eq(exit_code, C::F::zero());
319            }
320
321            // Memory initialization & finalization constraints.
322            {
323                // // Assert that `init_addr_bits` and `finalize_addr_bits` are zero for the first
324                // execution shard. builder.if_eq(execution_shard,
325                // C::N::one()).then(|builder| {     // Assert that the
326                // MemoryInitialize address bits are zero.     for bit in
327                // current_init_addr_bits.iter() {         builder.assert_felt_eq(*
328                // bit, C::F::zero());     }
329
330                //     // Assert that the MemoryFinalize address bits are zero.
331                //     for bit in current_finalize_addr_bits.iter() {
332                //         builder.assert_felt_eq(*bit, C::F::zero());
333                //     }
334                // });
335
336                // // Assert that the MemoryInitialize address bits match the current loop variable.
337                // for (bit, current_bit) in current_init_addr_bits
338                //     .iter()
339                //     .zip_eq(public_values.previous_init_addr_bits.iter())
340                // {
341                //     builder.assert_felt_eq(*bit, *current_bit);
342                // }
343
344                // // Assert that the MemoryFinalize address bits match the current loop variable.
345                // for (bit, current_bit) in current_finalize_addr_bits
346                //     .iter()
347                //     .zip_eq(public_values.previous_finalize_addr_bits.iter())
348                // {
349                //     builder.assert_felt_eq(*bit, *current_bit);
350                // }
351
352                // // Assert that if MemoryInit is not present, then the address bits are the same.
353                // builder
354                //     .if_ne(contains_memory_init, C::N::one())
355                //     .then(|builder| {
356                //         for (prev_bit, last_bit) in public_values
357                //             .previous_init_addr_bits
358                //             .iter()
359                //             .zip_eq(public_values.last_init_addr_bits.iter())
360                //         {
361                //             builder.assert_felt_eq(*prev_bit, *last_bit);
362                //         }
363                //     });
364
365                // // Assert that if MemoryFinalize is not present, then the address bits are the
366                // same. builder
367                //     .if_ne(contains_memory_finalize, C::N::one())
368                //     .then(|builder| {
369                //         for (prev_bit, last_bit) in public_values
370                //             .previous_finalize_addr_bits
371                //             .iter()
372                //             .zip_eq(public_values.last_finalize_addr_bits.iter())
373                //         {
374                //             builder.assert_felt_eq(*prev_bit, *last_bit);
375                //         }
376                //     });
377
378                // Update the MemoryInitialize address bits.
379                for (bit, pub_bit) in
380                    current_init_addr_bits.iter().zip(public_values.last_init_addr_bits.iter())
381                {
382                    builder.assign(*bit, *pub_bit);
383                }
384
385                // Update the MemoryFinalize address bits.
386                for (bit, pub_bit) in current_finalize_addr_bits
387                    .iter()
388                    .zip(public_values.last_finalize_addr_bits.iter())
389                {
390                    builder.assign(*bit, *pub_bit);
391                }
392            }
393
394            // Digest constraints.
395            {
396                // // If `commited_value_digest` is not zero, then
397                // `public_values.commited_value_digest // should be the current
398                // value. let is_zero: Var<_> = builder.eval(C::N::one());
399                // #[allow(clippy::needless_range_loop)]
400                // for i in 0..committed_value_digest.len() {
401                //     for j in 0..WORD_SIZE {
402                //         let d = felt2var(builder, committed_value_digest[i][j]);
403                //         builder.if_ne(d, C::N::zero()).then(|builder| {
404                //             builder.assign(is_zero, C::N::zero());
405                //         });
406                //     }
407                // }
408                // builder.if_eq(is_zero, C::N::zero()).then(|builder| {
409                //     #[allow(clippy::needless_range_loop)]
410                //     for i in 0..committed_value_digest.len() {
411                //         for j in 0..WORD_SIZE {
412                //             builder.assert_felt_eq(
413                //                 committed_value_digest[i][j],
414                //                 public_values.committed_value_digest[i][j],
415                //             );
416                //         }
417                //     }
418                // });
419
420                // // If it's not a shard with "CPU", then the committed value digest should not
421                // change. builder.if_ne(contains_cpu, C::N::one()).then(|builder| {
422                //     #[allow(clippy::needless_range_loop)]
423                //     for i in 0..committed_value_digest.len() {
424                //         for j in 0..WORD_SIZE {
425                //             builder.assert_felt_eq(
426                //                 committed_value_digest[i][j],
427                //                 public_values.committed_value_digest[i][j],
428                //             );
429                //         }
430                //     }
431                // });
432
433                // Update the committed value digest.
434                #[allow(clippy::needless_range_loop)]
435                for i in 0..committed_value_digest.len() {
436                    for j in 0..WORD_SIZE {
437                        builder.assign(
438                            committed_value_digest[i][j],
439                            public_values.committed_value_digest[i][j],
440                        );
441                    }
442                }
443
444                // // If `deferred_proofs_digest` is not zero, then
445                // `public_values.deferred_proofs_digest // should be the current
446                // value. let is_zero: Var<_> = builder.eval(C::N::one());
447                // #[allow(clippy::needless_range_loop)]
448                // for i in 0..deferred_proofs_digest.len() {
449                //     let d = felt2var(builder, deferred_proofs_digest[i]);
450                //     builder.if_ne(d, C::N::zero()).then(|builder| {
451                //         builder.assign(is_zero, C::N::zero());
452                //     });
453                // }
454                // builder.if_eq(is_zero, C::N::zero()).then(|builder| {
455                //     #[allow(clippy::needless_range_loop)]
456                //     for i in 0..deferred_proofs_digest.len() {
457                //         builder.assert_felt_eq(
458                //             deferred_proofs_digest[i],
459                //             public_values.deferred_proofs_digest[i],
460                //         );
461                //     }
462                // });
463
464                // // If it's not a shard with "CPU", then the deferred proofs digest should not
465                // change. builder.if_ne(contains_cpu, C::N::one()).then(|builder| {
466                //     #[allow(clippy::needless_range_loop)]
467                //     for i in 0..deferred_proofs_digest.len() {
468                //         builder.assert_felt_eq(
469                //             deferred_proofs_digest[i],
470                //             public_values.deferred_proofs_digest[i],
471                //         );
472                //     }
473                // });
474
475                // Update the deferred proofs digest.
476                #[allow(clippy::needless_range_loop)]
477                for i in 0..deferred_proofs_digest.len() {
478                    builder
479                        .assign(deferred_proofs_digest[i], public_values.deferred_proofs_digest[i]);
480                }
481            }
482
483            // // Verify that the number of shards is not too large.
484            // builder.range_check_f(public_values.shard, 16);
485
486            // Update the reconstruct challenger.
487            reconstruct_challenger.observe(builder, shard_proof.commitment.main_commit);
488            for element in shard_proof.public_values.iter() {
489                reconstruct_challenger.observe(builder, *element);
490            }
491
492            // Cumulative sum is updated by sums of all chips.
493            for values in shard_proof.opened_values.chips.iter() {
494                builder.assign(cumulative_sum, cumulative_sum + values.cumulative_sum);
495            }
496        }
497
498        // Write all values to the public values struct and commit to them.
499        {
500            // Compute the vk digest.
501            let vk_digest = vk.hash(builder);
502
503            // Collect the public values for challengers.
504            let initial_challenger_public_values =
505                initial_reconstruct_challenger.public_values(builder);
506            let final_challenger_public_values = reconstruct_challenger.public_values(builder);
507
508            // Collect the cumulative sum.
509            let cumulative_sum_array = builder.ext2felt_v2(cumulative_sum);
510
511            // Collect the deferred proof digests.
512            let zero: Felt<_> = builder.eval(C::F::zero());
513            let start_deferred_digest = [zero; POSEIDON_NUM_WORDS];
514            let end_deferred_digest = [zero; POSEIDON_NUM_WORDS];
515
516            // Collect the is_complete flag.
517            // let is_complete_felt = var2felt(builder, is_complete);
518
519            // Initialize the public values we will commit to.
520            let mut recursion_public_values_stream = [zero; RECURSIVE_PROOF_NUM_PV_ELTS];
521            let recursion_public_values: &mut RecursionPublicValues<_> =
522                recursion_public_values_stream.as_mut_slice().borrow_mut();
523            recursion_public_values.committed_value_digest = committed_value_digest;
524            recursion_public_values.deferred_proofs_digest = deferred_proofs_digest;
525            recursion_public_values.start_pc = start_pc;
526            recursion_public_values.next_pc = current_pc;
527            recursion_public_values.start_shard = initial_shard;
528            recursion_public_values.next_shard = current_shard;
529            recursion_public_values.start_execution_shard = initial_execution_shard;
530            recursion_public_values.next_execution_shard = current_execution_shard;
531            recursion_public_values.previous_init_addr_bits = initial_previous_init_addr_bits;
532            recursion_public_values.last_init_addr_bits = current_init_addr_bits;
533            recursion_public_values.previous_finalize_addr_bits =
534                initial_previous_finalize_addr_bits;
535            recursion_public_values.last_finalize_addr_bits = current_finalize_addr_bits;
536            recursion_public_values.sp1_vk_digest = vk_digest;
537            recursion_public_values.leaf_challenger = leaf_challenger_public_values;
538            recursion_public_values.start_reconstruct_challenger = initial_challenger_public_values;
539            recursion_public_values.end_reconstruct_challenger = final_challenger_public_values;
540            recursion_public_values.cumulative_sum = cumulative_sum_array;
541            recursion_public_values.start_reconstruct_deferred_digest = start_deferred_digest;
542            recursion_public_values.end_reconstruct_deferred_digest = end_deferred_digest;
543            recursion_public_values.exit_code = exit_code;
544            recursion_public_values.is_complete = is_complete;
545
546            // // If the proof represents a complete proof, make completeness assertions.
547            // //
548            // // *Remark*: In this program, this only happends if there is one shard and the
549            // program has // no deferred proofs to verify. However, the completeness
550            // check is independent of these // facts.
551            // builder.if_eq(is_complete, C::N::one()).then(|builder| {
552            //     assert_complete(builder, recursion_public_values, &reconstruct_challenger)
553            // });
554
555            commit_recursion_public_values(builder, recursion_public_values);
556        }
557    }
558}