sp1_prover/
lib.rs

1//! An end-to-end-prover implementation for the SP1 RISC-V zkVM.
2//!
3//! Separates the proof generation process into multiple stages:
4//!
5//! 1. Generate shard proofs which split up and prove the valid execution of a RISC-V program.
6//! 2. Compress shard proofs into a single shard proof.
7//! 3. Wrap the shard proof into a SNARK-friendly field.
8//! 4. Wrap the last shard proof, proven over the SNARK-friendly field, into a PLONK proof.
9
10#![allow(clippy::too_many_arguments)]
11#![allow(clippy::new_without_default)]
12#![allow(clippy::collapsible_else_if)]
13
14pub mod build;
15pub mod components;
16pub mod gas;
17pub mod shapes;
18pub mod types;
19pub mod utils;
20pub mod verify;
21
22use std::{
23    borrow::Borrow,
24    collections::BTreeMap,
25    env,
26    error::Error,
27    num::NonZeroUsize,
28    path::Path,
29    sync::{
30        atomic::{AtomicUsize, Ordering},
31        mpsc::{channel, sync_channel},
32        Arc, Mutex, OnceLock,
33    },
34    thread,
35};
36
37use crate::shapes::SP1CompressProgramShape;
38use lru::LruCache;
39use p3_baby_bear::BabyBear;
40use p3_field::{AbstractField, PrimeField, PrimeField32};
41use p3_matrix::dense::RowMajorMatrix;
42use shapes::SP1ProofShape;
43use sp1_core_executor::{
44    estimator::RecordEstimator, ExecutionError, ExecutionReport, Executor, Program, RiscvAirId,
45    SP1Context,
46};
47use sp1_core_machine::{
48    io::SP1Stdin,
49    reduce::SP1ReduceProof,
50    riscv::RiscvAir,
51    shape::CoreShapeConfig,
52    utils::{concurrency::TurnBasedSync, SP1CoreProverError},
53};
54use sp1_primitives::hash_deferred_proof;
55pub use sp1_primitives::io::SP1PublicValues;
56use sp1_recursion_circuit::{
57    hash::FieldHasher,
58    machine::{
59        PublicValuesOutputDigest, SP1CompressRootVerifierWithVKey, SP1CompressShape,
60        SP1CompressWithVKeyVerifier, SP1CompressWithVKeyWitnessValues, SP1CompressWithVkeyShape,
61        SP1CompressWitnessValues, SP1DeferredVerifier, SP1DeferredWitnessValues,
62        SP1MerkleProofWitnessValues, SP1RecursionShape, SP1RecursionWitnessValues,
63        SP1RecursiveVerifier,
64    },
65    merkle_tree::MerkleTree,
66    witness::Witnessable,
67    WrapConfig,
68};
69use sp1_recursion_compiler::{
70    circuit::AsmCompiler,
71    config::InnerConfig,
72    ir::{Builder, DslIrProgram, Witness},
73};
74use sp1_recursion_core::{
75    air::RecursionPublicValues,
76    machine::RecursionAir,
77    runtime::ExecutionRecord,
78    shape::{RecursionShape, RecursionShapeConfig},
79    stark::BabyBearPoseidon2Outer,
80    RecursionProgram, Runtime as RecursionRuntime,
81};
82pub use sp1_recursion_gnark_ffi::proof::{Groth16Bn254Proof, PlonkBn254Proof};
83use sp1_recursion_gnark_ffi::{groth16_bn254::Groth16Bn254Prover, plonk_bn254::PlonkBn254Prover};
84use sp1_stark::{
85    baby_bear_poseidon2::BabyBearPoseidon2,
86    shape::{OrderedShape, Shape},
87    Challenge, MachineProver, MachineProvingKey, SP1ProverOpts, ShardProof, SplitOpts,
88    StarkGenericConfig, StarkVerifyingKey, Val, Word, DIGEST_SIZE,
89};
90use tracing::instrument;
91
92pub use types::*;
93use utils::{sp1_committed_values_digest_bn254, sp1_vkey_digest_bn254, words_to_bytes};
94
95use components::{CpuProverComponents, SP1ProverComponents};
96
97pub use sp1_stark::{CoreSC, InnerSC};
98
99/// The global version for all components of SP1.
100///
101/// This string should be updated whenever any step in verifying an SP1 proof changes, including
102/// core, recursion, and plonk-bn254. This string is used to download SP1 artifacts and the gnark
103/// docker image.
104pub const SP1_CIRCUIT_VERSION: &str = include_str!("../SP1_VERSION");
105
106/// The configuration for the outer prover.
107pub type OuterSC = BabyBearPoseidon2Outer;
108
109pub type DeviceProvingKey<C> = <<C as SP1ProverComponents>::CoreProver as MachineProver<
110    BabyBearPoseidon2,
111    RiscvAir<BabyBear>,
112>>::DeviceProvingKey;
113
114const COMPRESS_DEGREE: usize = 3;
115const SHRINK_DEGREE: usize = 3;
116const WRAP_DEGREE: usize = 9;
117
118const CORE_CACHE_SIZE: usize = 5;
119pub const REDUCE_BATCH_SIZE: usize = 2;
120
121pub type CompressAir<F> = RecursionAir<F, COMPRESS_DEGREE>;
122pub type ShrinkAir<F> = RecursionAir<F, SHRINK_DEGREE>;
123pub type WrapAir<F> = RecursionAir<F, WRAP_DEGREE>;
124
125/// A end-to-end for the SP1 RISC-V zkVM.
126///
127/// This object coordinates the proving along all the steps: core, compression, shrinkage, and
128/// wrapping.
129pub struct SP1Prover<C: SP1ProverComponents = CpuProverComponents> {
130    /// The core prover.
131    pub core_prover: C::CoreProver,
132    /// The compress prover (for both lift and join).
133    pub compress_prover: C::CompressProver,
134    /// The shrink prover.
135    pub shrink_prover: C::ShrinkProver,
136    /// The wrap prover.
137    pub wrap_prover: C::WrapProver,
138    /// The cache of compiled recursion programs.
139    pub lift_programs_lru: Mutex<LruCache<SP1RecursionShape, Arc<RecursionProgram<BabyBear>>>>,
140    /// The number of cache misses for recursion programs.
141    pub lift_cache_misses: AtomicUsize,
142    /// The cache of compiled compression programs.
143    pub join_programs_map: BTreeMap<SP1CompressWithVkeyShape, Arc<RecursionProgram<BabyBear>>>,
144    /// The number of cache misses for compression programs.
145    pub join_cache_misses: AtomicUsize,
146    /// The root of the allowed recursion verification keys.
147    pub recursion_vk_root: <InnerSC as FieldHasher<BabyBear>>::Digest,
148    /// The allowed VKs and their corresponding indices.
149    pub recursion_vk_map: BTreeMap<<InnerSC as FieldHasher<BabyBear>>::Digest, usize>,
150    /// The Merkle tree for the allowed VKs.
151    pub recursion_vk_tree: MerkleTree<BabyBear, InnerSC>,
152    /// The core shape configuration.
153    pub core_shape_config: Option<CoreShapeConfig<BabyBear>>,
154    /// The recursion shape configuration.
155    pub compress_shape_config: Option<RecursionShapeConfig<BabyBear, CompressAir<BabyBear>>>,
156    /// The program for wrapping.
157    pub wrap_program: OnceLock<Arc<RecursionProgram<BabyBear>>>,
158    /// The verifying key for wrapping.
159    pub wrap_vk: OnceLock<StarkVerifyingKey<OuterSC>>,
160    /// Whether to verify verification keys.
161    pub vk_verification: bool,
162}
163
164impl<C: SP1ProverComponents> SP1Prover<C> {
165    /// Initializes a new [SP1Prover].
166    #[instrument(name = "initialize prover", level = "debug", skip_all)]
167    pub fn new() -> Self {
168        Self::uninitialized()
169    }
170
171    /// Creates a new [SP1Prover] with lazily initialized components.
172    pub fn uninitialized() -> Self {
173        // Initialize the provers.
174        let core_machine = RiscvAir::machine(CoreSC::default());
175        let core_prover = C::CoreProver::new(core_machine);
176
177        let compress_machine = CompressAir::compress_machine(InnerSC::default());
178        let compress_prover = C::CompressProver::new(compress_machine);
179
180        let shrink_machine = ShrinkAir::shrink_machine(InnerSC::compressed());
181        let shrink_prover = C::ShrinkProver::new(shrink_machine);
182
183        let wrap_machine = WrapAir::wrap_machine(OuterSC::default());
184        let wrap_prover = C::WrapProver::new(wrap_machine);
185
186        let core_cache_size = NonZeroUsize::new(
187            env::var("PROVER_CORE_CACHE_SIZE")
188                .unwrap_or_else(|_| CORE_CACHE_SIZE.to_string())
189                .parse()
190                .unwrap_or(CORE_CACHE_SIZE),
191        )
192        .expect("PROVER_CORE_CACHE_SIZE must be a non-zero usize");
193
194        let core_shape_config = env::var("FIX_CORE_SHAPES")
195            .map(|v| v.eq_ignore_ascii_case("true"))
196            .unwrap_or(true)
197            .then_some(CoreShapeConfig::default());
198
199        let recursion_shape_config = env::var("FIX_RECURSION_SHAPES")
200            .map(|v| v.eq_ignore_ascii_case("true"))
201            .unwrap_or(true)
202            .then_some(RecursionShapeConfig::default());
203
204        let vk_verification =
205            env::var("VERIFY_VK").map(|v| v.eq_ignore_ascii_case("true")).unwrap_or(true);
206        tracing::debug!("vk verification: {}", vk_verification);
207
208        // Read the shapes from the shapes directory and deserialize them into memory.
209        let allowed_vk_map: BTreeMap<[BabyBear; DIGEST_SIZE], usize> = if vk_verification {
210            bincode::deserialize(include_bytes!(concat!(env!("OUT_DIR"), "/vk_map.bin"))).unwrap()
211        } else {
212            bincode::deserialize(include_bytes!("vk_map_dummy.bin")).unwrap()
213        };
214
215        let (root, merkle_tree) = MerkleTree::commit(allowed_vk_map.keys().copied().collect());
216
217        let mut compress_programs = BTreeMap::new();
218        let program_cache_disabled = env::var("SP1_DISABLE_PROGRAM_CACHE")
219            .map(|v| v.eq_ignore_ascii_case("true"))
220            .unwrap_or(false);
221        if !program_cache_disabled {
222            if let Some(config) = &recursion_shape_config {
223                SP1ProofShape::generate_compress_shapes(config, REDUCE_BATCH_SIZE).for_each(
224                    |shape| {
225                        let compress_shape = SP1CompressWithVkeyShape {
226                            compress_shape: shape.into(),
227                            merkle_tree_height: merkle_tree.height,
228                        };
229                        let input = SP1CompressWithVKeyWitnessValues::dummy(
230                            compress_prover.machine(),
231                            &compress_shape,
232                        );
233                        let program = compress_program_from_input::<C>(
234                            recursion_shape_config.as_ref(),
235                            &compress_prover,
236                            vk_verification,
237                            &input,
238                        );
239                        let program = Arc::new(program);
240                        compress_programs.insert(compress_shape, program);
241                    },
242                );
243            }
244        }
245
246        Self {
247            core_prover,
248            compress_prover,
249            shrink_prover,
250            wrap_prover,
251            lift_programs_lru: Mutex::new(LruCache::new(core_cache_size)),
252            lift_cache_misses: AtomicUsize::new(0),
253            join_programs_map: compress_programs,
254            join_cache_misses: AtomicUsize::new(0),
255            recursion_vk_root: root,
256            recursion_vk_tree: merkle_tree,
257            recursion_vk_map: allowed_vk_map,
258            core_shape_config,
259            compress_shape_config: recursion_shape_config,
260            vk_verification,
261            wrap_program: OnceLock::new(),
262            wrap_vk: OnceLock::new(),
263        }
264    }
265
266    /// Creates a proving key and a verifying key for a given RISC-V ELF.
267    #[instrument(name = "setup", level = "debug", skip_all)]
268    pub fn setup(
269        &self,
270        elf: &[u8],
271    ) -> (SP1ProvingKey, DeviceProvingKey<C>, Program, SP1VerifyingKey) {
272        let program = self.get_program(elf).unwrap();
273        let (pk, vk) = self.core_prover.setup(&program);
274        let vk = SP1VerifyingKey { vk };
275        let pk = SP1ProvingKey {
276            pk: self.core_prover.pk_to_host(&pk),
277            elf: elf.to_vec(),
278            vk: vk.clone(),
279        };
280        let pk_d = self.core_prover.pk_to_device(&pk.pk);
281        (pk, pk_d, program, vk)
282    }
283
284    /// Get a program with an allowed preprocessed shape.
285    pub fn get_program(&self, elf: &[u8]) -> eyre::Result<Program> {
286        let mut program = Program::from(elf)?;
287        if let Some(core_shape_config) = &self.core_shape_config {
288            core_shape_config.fix_preprocessed_shape(&mut program)?;
289        }
290        Ok(program)
291    }
292
293    fn get_gas_calculator(
294        &self,
295        preprocessed_shape: Shape<RiscvAirId>,
296        split_opts: SplitOpts,
297    ) -> impl FnMut(&RecordEstimator) -> Result<u64, Box<dyn Error>> + '_ {
298        move |estimator: &RecordEstimator| -> Result<u64, Box<dyn Error>> {
299            let est_records = gas::estimated_records(&split_opts, estimator);
300            let raw_gas =
301                gas::fit_records_to_shapes(self.core_shape_config.as_ref().unwrap(), est_records)
302                    .enumerate()
303                    .map(|(i, shape)| {
304                        let mut shape: Shape<RiscvAirId> = shape.map_err(Box::new)?;
305                        shape.extend(preprocessed_shape.iter().map(|(k, v)| (*k, *v)));
306                        tracing::debug!("shape for estimated shard {i}: {:?}", &shape.inner);
307                        Ok(gas::predict(enum_map::EnumMap::from_iter(shape).as_array()))
308                    })
309                    .sum::<Result<_, Box<dyn Error>>>()?;
310            let gas = gas::final_transform(raw_gas).map_err(Box::new)?;
311            Ok(gas)
312        }
313    }
314
315    /// Execute an SP1 program with the specified inputs.
316    #[instrument(name = "execute", level = "info", skip_all)]
317    pub fn execute<'a>(
318        &'a self,
319        elf: &[u8],
320        stdin: &SP1Stdin,
321        mut context: SP1Context<'a>,
322    ) -> Result<(SP1PublicValues, [u8; 32], ExecutionReport), ExecutionError> {
323        context.subproof_verifier = Some(self);
324
325        let calculate_gas = context.calculate_gas;
326
327        let (opts, program) = if calculate_gas {
328            (gas::GAS_OPTS, self.get_program(elf).unwrap())
329        } else {
330            (sp1_stark::SP1CoreOpts::default(), Program::from(elf).unwrap())
331        };
332        let preprocessed_shape = program.preprocessed_shape.clone();
333
334        let mut runtime = Executor::with_context(program, opts, context);
335
336        if calculate_gas {
337            // Needed to figure out where the shard boundaries are.
338            runtime.maximal_shapes = self.core_shape_config.as_ref().map(|config| {
339                config.maximal_core_shapes(opts.shard_size.ilog2() as usize).into_iter().collect()
340            });
341            runtime.record_estimator = Some(Box::default());
342        }
343
344        runtime.maybe_setup_profiler(elf);
345
346        runtime.write_vecs(&stdin.buffer);
347        for (proof, vkey) in stdin.proofs.iter() {
348            runtime.write_proof(proof.clone(), vkey.clone());
349        }
350        runtime.run_fast()?;
351
352        if calculate_gas {
353            let gas = self.get_gas_calculator(preprocessed_shape.unwrap(), opts.split_opts)(
354                runtime.record_estimator.as_ref().unwrap(),
355            );
356            runtime.report.gas = gas
357                .inspect(|g| tracing::info!("gas: {}", g))
358                .inspect_err(|e| tracing::error!("Encountered error while calculating gas: {}", e))
359                .ok();
360        }
361
362        let mut committed_value_digest = [0u8; 32];
363        runtime.record.public_values.committed_value_digest.iter().enumerate().for_each(
364            |(i, word)| {
365                let bytes = word.to_le_bytes();
366                committed_value_digest[i * 4..(i + 1) * 4].copy_from_slice(&bytes);
367            },
368        );
369
370        Ok((
371            SP1PublicValues::from(&runtime.state.public_values_stream),
372            committed_value_digest,
373            runtime.report,
374        ))
375    }
376
377    /// Generate shard proofs which split up and prove the valid execution of a RISC-V program with
378    /// the core prover. Uses the provided context.
379    #[instrument(name = "prove_core", level = "info", skip_all)]
380    pub fn prove_core<'a>(
381        &'a self,
382        pk_d: &<<C as SP1ProverComponents>::CoreProver as MachineProver<
383            BabyBearPoseidon2,
384            RiscvAir<BabyBear>,
385        >>::DeviceProvingKey,
386        program: Program,
387        stdin: &SP1Stdin,
388        opts: SP1ProverOpts,
389        mut context: SP1Context<'a>,
390    ) -> Result<SP1CoreProof, SP1CoreProverError> {
391        context.subproof_verifier = Some(self);
392
393        // Launch two threads to simultaneously prove the core and compile the first few
394        // recursion programs in parallel.
395        let span = tracing::Span::current().clone();
396        std::thread::scope(|s| {
397            let _span = span.enter();
398            let (proof_tx, proof_rx) = channel();
399            let (shape_tx, shape_rx) = channel();
400
401            let span = tracing::Span::current().clone();
402            let handle = s.spawn(move || {
403                let _span = span.enter();
404
405                // Copy the proving key to the device.
406                let pk = pk_d;
407
408                // We may calculate gas while proving if the opts match the hardcoded variant.
409                // This ensures that the gas number is consistent between `execute` and `prove_core`.
410                // This behavior is undocumented because it is confusing and not very useful.
411                //
412                // If `context.calculate_gas` is set, we use the logic from the `gas` module
413                // after checkpoint execution to print gas as part of the execution report.
414                #[allow(clippy::type_complexity)]
415                let gas_calculator = (context.calculate_gas
416                    && std::env::var("SP1_FORCE_GAS").is_ok())
417                .then(
418                    || -> Box<dyn FnOnce(&RecordEstimator) -> Result<u64, Box<dyn Error>> + '_> {
419                        tracing::info!("Forcing calculation of gas while proving.");
420                        if opts.core_opts == gas::GAS_OPTS {
421                            tracing::info!(
422                                "The SP1CoreOpts matches the gas opts, so gas will be consistent."
423                            );
424                        } else {
425                            tracing::warn!(
426                                "The SP1CoreOpts does not match the gas opts. \
427                                Gas will likely disagree with the standard gas calculated when executing."
428                            );
429                        }
430                        let preprocessed_shape = program.preprocessed_shape.clone().unwrap();
431                        Box::new(
432                            self.get_gas_calculator(preprocessed_shape, opts.core_opts.split_opts),
433                        )
434                    },
435                );
436
437                // Prove the core and stream the proofs and shapes.
438                sp1_core_machine::utils::prove_core_stream::<_, C::CoreProver>(
439                    &self.core_prover,
440                    pk,
441                    program,
442                    stdin,
443                    opts.core_opts,
444                    context,
445                    self.core_shape_config.as_ref(),
446                    proof_tx,
447                    shape_tx,
448                    None,
449                    gas_calculator,
450                )
451            });
452
453            // Receive the first few shapes and comile the recursion programs.
454            for _ in 0..3 {
455                if let Ok((shape, is_complete)) = shape_rx.recv() {
456                    let recursion_shape =
457                        SP1RecursionShape { proof_shapes: vec![shape], is_complete };
458
459                    // Only need to compile the recursion program if we're not in the one-shard
460                    // case.
461                    let compress_shape = SP1CompressProgramShape::Recursion(recursion_shape);
462
463                    // Insert the program into the cache.
464                    self.program_from_shape(compress_shape, None);
465                }
466            }
467
468            // Collect the shard proofs and the public values stream.
469            let shard_proofs: Vec<ShardProof<_>> = proof_rx.iter().collect();
470            let (public_values_stream, cycles) = handle.join().unwrap().unwrap();
471            let public_values = SP1PublicValues::from(&public_values_stream);
472            Self::check_for_high_cycles(cycles);
473            Ok(SP1CoreProof {
474                proof: SP1CoreProofData(shard_proofs),
475                stdin: stdin.clone(),
476                public_values,
477                cycles,
478            })
479        })
480    }
481
482    /// Reduce shards proofs to a single shard proof using the recursion prover.
483    #[instrument(name = "compress", level = "info", skip_all)]
484    pub fn compress(
485        &self,
486        vk: &SP1VerifyingKey,
487        proof: SP1CoreProof,
488        deferred_proofs: Vec<SP1ReduceProof<InnerSC>>,
489        opts: SP1ProverOpts,
490    ) -> Result<SP1ReduceProof<InnerSC>, SP1RecursionProverError> {
491        #[allow(clippy::type_complexity)]
492        enum TracesOrInput {
493            ProgramRecordTraces(
494                Box<(
495                    Arc<RecursionProgram<BabyBear>>,
496                    ExecutionRecord<BabyBear>,
497                    Vec<(String, RowMajorMatrix<BabyBear>)>,
498                )>,
499            ),
500            CircuitWitness(Box<SP1CircuitWitness>),
501        }
502
503        // The batch size for reducing two layers of recursion.
504        let batch_size = REDUCE_BATCH_SIZE;
505        // The batch size for reducing the first layer of recursion.
506        let first_layer_batch_size = 1;
507
508        let shard_proofs = &proof.proof.0;
509
510        // Generate the first layer inputs.
511        let first_layer_inputs =
512            self.get_first_layer_inputs(vk, shard_proofs, &deferred_proofs, first_layer_batch_size);
513
514        // Calculate the expected height of the tree.
515        let mut expected_height = if first_layer_inputs.len() == 1 { 0 } else { 1 };
516        let num_first_layer_inputs = first_layer_inputs.len();
517        let mut num_layer_inputs = num_first_layer_inputs;
518        while num_layer_inputs > batch_size {
519            num_layer_inputs = num_layer_inputs.div_ceil(2);
520            expected_height += 1;
521        }
522
523        // Generate the proofs.
524        let span = tracing::Span::current().clone();
525        let (vk, proof) = thread::scope(|s| {
526            let _span = span.enter();
527
528            // Spawn a worker that sends the first layer inputs to a bounded channel.
529            let input_sync = Arc::new(TurnBasedSync::new());
530            let (input_tx, input_rx) = sync_channel::<(usize, usize, SP1CircuitWitness, bool)>(
531                opts.recursion_opts.checkpoints_channel_capacity,
532            );
533            let input_tx = Arc::new(Mutex::new(input_tx));
534            {
535                let input_tx = Arc::clone(&input_tx);
536                let input_sync = Arc::clone(&input_sync);
537                s.spawn(move || {
538                    for (index, input) in first_layer_inputs.into_iter().enumerate() {
539                        input_sync.wait_for_turn(index);
540                        input_tx.lock().unwrap().send((index, 0, input, false)).unwrap();
541                        input_sync.advance_turn();
542                    }
543                });
544            }
545
546            // Spawn workers who generate the records and traces.
547            let record_and_trace_sync = Arc::new(TurnBasedSync::new());
548            let (record_and_trace_tx, record_and_trace_rx) =
549                sync_channel::<(usize, usize, TracesOrInput)>(
550                    opts.recursion_opts.records_and_traces_channel_capacity,
551                );
552            let record_and_trace_tx = Arc::new(Mutex::new(record_and_trace_tx));
553            let record_and_trace_rx = Arc::new(Mutex::new(record_and_trace_rx));
554            let input_rx = Arc::new(Mutex::new(input_rx));
555            for _ in 0..opts.recursion_opts.trace_gen_workers {
556                let record_and_trace_sync = Arc::clone(&record_and_trace_sync);
557                let record_and_trace_tx = Arc::clone(&record_and_trace_tx);
558                let input_rx = Arc::clone(&input_rx);
559                let span = tracing::debug_span!("generate records and traces");
560                s.spawn(move || {
561                    let _span = span.enter();
562                    loop {
563                        let received = { input_rx.lock().unwrap().recv() };
564                        if let Ok((index, height, input, false)) = received {
565                            // Get the program and witness stream.
566                            let (program, witness_stream) = tracing::debug_span!(
567                                "get program and witness stream"
568                            )
569                            .in_scope(|| match input {
570                                SP1CircuitWitness::Core(input) => {
571                                    let mut witness_stream = Vec::new();
572                                    Witnessable::<InnerConfig>::write(&input, &mut witness_stream);
573                                    (self.recursion_program(&input), witness_stream)
574                                }
575                                SP1CircuitWitness::Deferred(input) => {
576                                    let mut witness_stream = Vec::new();
577                                    Witnessable::<InnerConfig>::write(&input, &mut witness_stream);
578                                    (self.deferred_program(&input), witness_stream)
579                                }
580                                SP1CircuitWitness::Compress(input) => {
581                                    let mut witness_stream = Vec::new();
582
583                                    let input_with_merkle = self.make_merkle_proofs(input);
584
585                                    Witnessable::<InnerConfig>::write(
586                                        &input_with_merkle,
587                                        &mut witness_stream,
588                                    );
589
590                                    (self.compress_program(&input_with_merkle), witness_stream)
591                                }
592                            });
593
594                            // Execute the runtime.
595                            let record = tracing::debug_span!("execute runtime").in_scope(|| {
596                                let mut runtime =
597                                    RecursionRuntime::<Val<InnerSC>, Challenge<InnerSC>, _>::new(
598                                        program.clone(),
599                                        self.compress_prover.config().perm.clone(),
600                                    );
601                                runtime.witness_stream = witness_stream.into();
602                                runtime
603                                    .run()
604                                    .map_err(|e| {
605                                        SP1RecursionProverError::RuntimeError(e.to_string())
606                                    })
607                                    .unwrap();
608                                runtime.record
609                            });
610
611                            // Generate the dependencies.
612                            let mut records = vec![record];
613                            tracing::debug_span!("generate dependencies").in_scope(|| {
614                                self.compress_prover.machine().generate_dependencies(
615                                    &mut records,
616                                    &opts.recursion_opts,
617                                    None,
618                                )
619                            });
620
621                            // Generate the traces.
622                            let record = records.into_iter().next().unwrap();
623                            let traces = tracing::debug_span!("generate traces")
624                                .in_scope(|| self.compress_prover.generate_traces(&record));
625
626                            // Wait for our turn to update the state.
627                            record_and_trace_sync.wait_for_turn(index);
628
629                            // Send the record and traces to the worker.
630                            record_and_trace_tx
631                                .lock()
632                                .unwrap()
633                                .send((
634                                    index,
635                                    height,
636                                    TracesOrInput::ProgramRecordTraces(Box::new((
637                                        program, record, traces,
638                                    ))),
639                                ))
640                                .unwrap();
641
642                            // Advance the turn.
643                            record_and_trace_sync.advance_turn();
644                        } else if let Ok((index, height, input, true)) = received {
645                            record_and_trace_sync.wait_for_turn(index);
646
647                            // Send the record and traces to the worker.
648                            record_and_trace_tx
649                                .lock()
650                                .unwrap()
651                                .send((
652                                    index,
653                                    height,
654                                    TracesOrInput::CircuitWitness(Box::new(input)),
655                                ))
656                                .unwrap();
657
658                            // Advance the turn.
659                            record_and_trace_sync.advance_turn();
660                        } else {
661                            break;
662                        }
663                    }
664                });
665            }
666
667            // Spawn workers who generate the compress proofs.
668            let proofs_sync = Arc::new(TurnBasedSync::new());
669            let (proofs_tx, proofs_rx) =
670                sync_channel::<(usize, usize, StarkVerifyingKey<InnerSC>, ShardProof<InnerSC>)>(
671                    num_first_layer_inputs * 2,
672                );
673            let proofs_tx = Arc::new(Mutex::new(proofs_tx));
674            let proofs_rx = Arc::new(Mutex::new(proofs_rx));
675            let mut prover_handles = Vec::new();
676            for _ in 0..opts.recursion_opts.shard_batch_size {
677                let prover_sync = Arc::clone(&proofs_sync);
678                let record_and_trace_rx = Arc::clone(&record_and_trace_rx);
679                let proofs_tx = Arc::clone(&proofs_tx);
680                let span = tracing::debug_span!("prove");
681                let handle = s.spawn(move || {
682                    let _span = span.enter();
683                    loop {
684                        let received = { record_and_trace_rx.lock().unwrap().recv() };
685                        if let Ok((index, height, TracesOrInput::ProgramRecordTraces(boxed_prt))) =
686                            received
687                        {
688                            let (program, record, traces) = *boxed_prt;
689                            tracing::debug_span!("batch").in_scope(|| {
690                                // Get the keys.
691                                let (pk, vk) = tracing::debug_span!("Setup compress program")
692                                    .in_scope(|| self.compress_prover.setup(&program));
693
694                                // Observe the proving key.
695                                let mut challenger = self.compress_prover.config().challenger();
696                                tracing::debug_span!("observe proving key").in_scope(|| {
697                                    pk.observe_into(&mut challenger);
698                                });
699
700                                #[cfg(feature = "debug")]
701                                self.compress_prover.debug_constraints(
702                                    &self.compress_prover.pk_to_host(&pk),
703                                    vec![record.clone()],
704                                    &mut challenger.clone(),
705                                );
706
707                                // Commit to the record and traces.
708                                let data = tracing::debug_span!("commit")
709                                    .in_scope(|| self.compress_prover.commit(&record, traces));
710
711                                // Generate the proof.
712                                let proof = tracing::debug_span!("open").in_scope(|| {
713                                    self.compress_prover.open(&pk, data, &mut challenger).unwrap()
714                                });
715
716                                // Verify the proof.
717                                #[cfg(feature = "debug")]
718                                self.compress_prover
719                                    .machine()
720                                    .verify(
721                                        &vk,
722                                        &sp1_stark::MachineProof {
723                                            shard_proofs: vec![proof.clone()],
724                                        },
725                                        &mut self.compress_prover.config().challenger(),
726                                    )
727                                    .unwrap();
728
729                                // Wait for our turn to update the state.
730                                prover_sync.wait_for_turn(index);
731
732                                // Send the proof.
733                                proofs_tx.lock().unwrap().send((index, height, vk, proof)).unwrap();
734
735                                // Advance the turn.
736                                prover_sync.advance_turn();
737                            });
738                        } else if let Ok((
739                            index,
740                            height,
741                            TracesOrInput::CircuitWitness(witness_box),
742                        )) = received
743                        {
744                            let witness = *witness_box;
745                            if let SP1CircuitWitness::Compress(inner_witness) = witness {
746                                let SP1CompressWitnessValues { vks_and_proofs, is_complete: _ } =
747                                    inner_witness;
748                                assert!(vks_and_proofs.len() == 1);
749                                let (vk, proof) = vks_and_proofs.last().unwrap();
750                                // Wait for our turn to update the state.
751                                prover_sync.wait_for_turn(index);
752
753                                // Send the proof.
754                                proofs_tx
755                                    .lock()
756                                    .unwrap()
757                                    .send((index, height, vk.clone(), proof.clone()))
758                                    .unwrap();
759
760                                // Advance the turn.
761                                prover_sync.advance_turn();
762                            }
763                        } else {
764                            break;
765                        }
766                    }
767                });
768                prover_handles.push(handle);
769            }
770
771            // Spawn a worker that generates inputs for the next layer.
772            let handle = {
773                let input_tx = Arc::clone(&input_tx);
774                let proofs_rx = Arc::clone(&proofs_rx);
775                let span = tracing::debug_span!("generate next layer inputs");
776                s.spawn(move || {
777                    let _span = span.enter();
778                    let mut count = num_first_layer_inputs;
779                    let mut batch: Vec<(
780                        usize,
781                        usize,
782                        StarkVerifyingKey<InnerSC>,
783                        ShardProof<InnerSC>,
784                    )> = Vec::new();
785                    loop {
786                        if expected_height == 0 {
787                            break;
788                        }
789                        let received = { proofs_rx.lock().unwrap().recv() };
790                        if let Ok((index, height, vk, proof)) = received {
791                            batch.push((index, height, vk, proof));
792
793                            // If we haven't reached the batch size, continue.
794                            if batch.len() < batch_size {
795                                continue;
796                            }
797
798                            // Compute whether we're at the last input of a layer.
799                            let mut is_last = false;
800                            if let Some(first) = batch.first() {
801                                is_last = first.1 != height;
802                            }
803
804                            // If we're at the last input of a layer, we need to only include the
805                            // first input, otherwise we include all inputs.
806                            let inputs =
807                                if is_last { vec![batch[0].clone()] } else { batch.clone() };
808
809                            let next_input_height = inputs[0].1 + 1;
810
811                            let is_complete = next_input_height == expected_height;
812
813                            let vks_and_proofs = inputs
814                                .into_iter()
815                                .map(|(_, _, vk, proof)| (vk, proof))
816                                .collect::<Vec<_>>();
817                            let input = SP1CircuitWitness::Compress(SP1CompressWitnessValues {
818                                vks_and_proofs,
819                                is_complete,
820                            });
821
822                            input_sync.wait_for_turn(count);
823                            input_tx
824                                .lock()
825                                .unwrap()
826                                .send((count, next_input_height, input, is_last))
827                                .unwrap();
828                            input_sync.advance_turn();
829                            count += 1;
830
831                            // If we're at the root of the tree, stop generating inputs.
832                            if is_complete {
833                                break;
834                            }
835
836                            // If we were at the last input of a layer, we keep everything but the
837                            // first input. Otherwise, we empty the batch.
838                            if is_last {
839                                batch = vec![batch[1].clone()];
840                            } else {
841                                batch = Vec::new();
842                            }
843                        } else {
844                            break;
845                        }
846                    }
847                })
848            };
849
850            // Wait for all the provers to finish.
851            drop(input_tx);
852            drop(record_and_trace_tx);
853            drop(proofs_tx);
854
855            for handle in prover_handles {
856                handle.join().unwrap();
857            }
858            handle.join().unwrap();
859            tracing::debug!("joined handles");
860
861            let (_, _, vk, proof) = proofs_rx.lock().unwrap().recv().unwrap();
862            (vk, proof)
863        });
864
865        Ok(SP1ReduceProof { vk, proof })
866    }
867
868    /// Wrap a reduce proof into a STARK proven over a SNARK-friendly field.
869    #[instrument(name = "shrink", level = "info", skip_all)]
870    pub fn shrink(
871        &self,
872        reduced_proof: SP1ReduceProof<InnerSC>,
873        opts: SP1ProverOpts,
874    ) -> Result<SP1ReduceProof<InnerSC>, SP1RecursionProverError> {
875        // Make the compress proof.
876        let SP1ReduceProof { vk: compressed_vk, proof: compressed_proof } = reduced_proof;
877        let input = SP1CompressWitnessValues {
878            vks_and_proofs: vec![(compressed_vk.clone(), compressed_proof)],
879            is_complete: true,
880        };
881
882        let input_with_merkle = self.make_merkle_proofs(input);
883
884        let program =
885            self.shrink_program(ShrinkAir::<BabyBear>::shrink_shape(), &input_with_merkle);
886
887        // Run the compress program.
888        let mut runtime = RecursionRuntime::<Val<InnerSC>, Challenge<InnerSC>, _>::new(
889            program.clone(),
890            self.shrink_prover.config().perm.clone(),
891        );
892
893        let mut witness_stream = Vec::new();
894        Witnessable::<InnerConfig>::write(&input_with_merkle, &mut witness_stream);
895
896        runtime.witness_stream = witness_stream.into();
897
898        runtime.run().map_err(|e| SP1RecursionProverError::RuntimeError(e.to_string()))?;
899
900        runtime.print_stats();
901        tracing::debug!("Shrink program executed successfully");
902
903        let (shrink_pk, shrink_vk) =
904            tracing::debug_span!("setup shrink").in_scope(|| self.shrink_prover.setup(&program));
905
906        // Prove the compress program.
907        let mut compress_challenger = self.shrink_prover.config().challenger();
908        let mut compress_proof = self
909            .shrink_prover
910            .prove(&shrink_pk, vec![runtime.record], &mut compress_challenger, opts.recursion_opts)
911            .unwrap();
912
913        Ok(SP1ReduceProof { vk: shrink_vk, proof: compress_proof.shard_proofs.pop().unwrap() })
914    }
915
916    /// Wrap a reduce proof into a STARK proven over a SNARK-friendly field.
917    #[instrument(name = "wrap_bn254", level = "info", skip_all)]
918    pub fn wrap_bn254(
919        &self,
920        compressed_proof: SP1ReduceProof<InnerSC>,
921        opts: SP1ProverOpts,
922    ) -> Result<SP1ReduceProof<OuterSC>, SP1RecursionProverError> {
923        let SP1ReduceProof { vk: compressed_vk, proof: compressed_proof } = compressed_proof;
924        let input = SP1CompressWitnessValues {
925            vks_and_proofs: vec![(compressed_vk, compressed_proof)],
926            is_complete: true,
927        };
928        let input_with_vk = self.make_merkle_proofs(input);
929
930        let program = self.wrap_program();
931
932        // Run the compress program.
933        let mut runtime = RecursionRuntime::<Val<InnerSC>, Challenge<InnerSC>, _>::new(
934            program.clone(),
935            self.shrink_prover.config().perm.clone(),
936        );
937
938        let mut witness_stream = Vec::new();
939        Witnessable::<InnerConfig>::write(&input_with_vk, &mut witness_stream);
940
941        runtime.witness_stream = witness_stream.into();
942
943        runtime.run().map_err(|e| SP1RecursionProverError::RuntimeError(e.to_string()))?;
944
945        runtime.print_stats();
946        tracing::debug!("wrap program executed successfully");
947
948        // Setup the wrap program.
949        let (wrap_pk, wrap_vk) =
950            tracing::debug_span!("setup wrap").in_scope(|| self.wrap_prover.setup(&program));
951
952        if self.wrap_vk.set(wrap_vk.clone()).is_ok() {
953            tracing::debug!("wrap verifier key set");
954        }
955
956        // Prove the wrap program.
957        let mut wrap_challenger = self.wrap_prover.config().challenger();
958        let time = std::time::Instant::now();
959        let mut wrap_proof = self
960            .wrap_prover
961            .prove(&wrap_pk, vec![runtime.record], &mut wrap_challenger, opts.recursion_opts)
962            .unwrap();
963        let elapsed = time.elapsed();
964        tracing::debug!("wrap proving time: {:?}", elapsed);
965        let mut wrap_challenger = self.wrap_prover.config().challenger();
966        self.wrap_prover.machine().verify(&wrap_vk, &wrap_proof, &mut wrap_challenger).unwrap();
967        tracing::debug!("wrapping successful");
968
969        Ok(SP1ReduceProof { vk: wrap_vk, proof: wrap_proof.shard_proofs.pop().unwrap() })
970    }
971
972    /// Wrap the STARK proven over a SNARK-friendly field into a PLONK proof.
973    #[instrument(name = "wrap_plonk_bn254", level = "info", skip_all)]
974    pub fn wrap_plonk_bn254(
975        &self,
976        proof: SP1ReduceProof<OuterSC>,
977        build_dir: &Path,
978    ) -> PlonkBn254Proof {
979        let input = SP1CompressWitnessValues {
980            vks_and_proofs: vec![(proof.vk.clone(), proof.proof.clone())],
981            is_complete: true,
982        };
983        let vkey_hash = sp1_vkey_digest_bn254(&proof);
984        let committed_values_digest = sp1_committed_values_digest_bn254(&proof);
985
986        let mut witness = Witness::default();
987        input.write(&mut witness);
988        witness.write_committed_values_digest(committed_values_digest);
989        witness.write_vkey_hash(vkey_hash);
990
991        let prover = PlonkBn254Prover::new();
992        let proof = prover.prove(witness, build_dir.to_path_buf());
993
994        // Verify the proof.
995        prover
996            .verify(
997                &proof,
998                &vkey_hash.as_canonical_biguint(),
999                &committed_values_digest.as_canonical_biguint(),
1000                build_dir,
1001            )
1002            .unwrap();
1003
1004        proof
1005    }
1006
1007    /// Wrap the STARK proven over a SNARK-friendly field into a Groth16 proof.
1008    #[instrument(name = "wrap_groth16_bn254", level = "info", skip_all)]
1009    pub fn wrap_groth16_bn254(
1010        &self,
1011        proof: SP1ReduceProof<OuterSC>,
1012        build_dir: &Path,
1013    ) -> Groth16Bn254Proof {
1014        let input = SP1CompressWitnessValues {
1015            vks_and_proofs: vec![(proof.vk.clone(), proof.proof.clone())],
1016            is_complete: true,
1017        };
1018        let vkey_hash = sp1_vkey_digest_bn254(&proof);
1019        let committed_values_digest = sp1_committed_values_digest_bn254(&proof);
1020
1021        let mut witness = Witness::default();
1022        input.write(&mut witness);
1023        witness.write_committed_values_digest(committed_values_digest);
1024        witness.write_vkey_hash(vkey_hash);
1025
1026        let prover = Groth16Bn254Prover::new();
1027        let proof = prover.prove(witness, build_dir.to_path_buf());
1028
1029        // Verify the proof.
1030        prover
1031            .verify(
1032                &proof,
1033                &vkey_hash.as_canonical_biguint(),
1034                &committed_values_digest.as_canonical_biguint(),
1035                build_dir,
1036            )
1037            .unwrap();
1038
1039        proof
1040    }
1041
1042    pub fn recursion_program(
1043        &self,
1044        input: &SP1RecursionWitnessValues<CoreSC>,
1045    ) -> Arc<RecursionProgram<BabyBear>> {
1046        // Check if the program is in the cache.
1047        let mut cache = self.lift_programs_lru.lock().unwrap_or_else(|e| e.into_inner());
1048        let shape = input.shape();
1049        let program = cache.get(&shape).cloned();
1050        drop(cache);
1051        match program {
1052            Some(program) => program,
1053            None => {
1054                let misses = self.lift_cache_misses.fetch_add(1, Ordering::Relaxed);
1055                tracing::debug!("core cache miss, misses: {}", misses);
1056                // Get the operations.
1057                let builder_span = tracing::debug_span!("build recursion program").entered();
1058                let mut builder = Builder::<InnerConfig>::default();
1059
1060                let input =
1061                    tracing::debug_span!("read input").in_scope(|| input.read(&mut builder));
1062                tracing::debug_span!("verify").in_scope(|| {
1063                    SP1RecursiveVerifier::verify(&mut builder, self.core_prover.machine(), input)
1064                });
1065                let block =
1066                    tracing::debug_span!("build block").in_scope(|| builder.into_root_block());
1067                builder_span.exit();
1068                // SAFETY: The circuit is well-formed. It does not use synchronization primitives
1069                // (or possibly other means) to violate the invariants.
1070                let dsl_program = unsafe { DslIrProgram::new_unchecked(block) };
1071
1072                // Compile the program.
1073                let compiler_span = tracing::debug_span!("compile recursion program").entered();
1074                let mut compiler = AsmCompiler::<InnerConfig>::default();
1075                let mut program = compiler.compile(dsl_program);
1076                if let Some(inn_recursion_shape_config) = &self.compress_shape_config {
1077                    inn_recursion_shape_config.fix_shape(&mut program);
1078                }
1079                let program = Arc::new(program);
1080                compiler_span.exit();
1081
1082                // Insert the program into the cache.
1083                let mut cache = self.lift_programs_lru.lock().unwrap_or_else(|e| e.into_inner());
1084                cache.put(shape, program.clone());
1085                drop(cache);
1086                program
1087            }
1088        }
1089    }
1090
1091    pub fn compress_program(
1092        &self,
1093        input: &SP1CompressWithVKeyWitnessValues<InnerSC>,
1094    ) -> Arc<RecursionProgram<BabyBear>> {
1095        self.join_programs_map.get(&input.shape()).cloned().unwrap_or_else(|| {
1096            tracing::warn!("join program not found in map, recomputing join program.");
1097            // Get the operations.
1098            Arc::new(compress_program_from_input::<C>(
1099                self.compress_shape_config.as_ref(),
1100                &self.compress_prover,
1101                self.vk_verification,
1102                input,
1103            ))
1104        })
1105    }
1106
1107    pub fn shrink_program(
1108        &self,
1109        shrink_shape: RecursionShape,
1110        input: &SP1CompressWithVKeyWitnessValues<InnerSC>,
1111    ) -> Arc<RecursionProgram<BabyBear>> {
1112        // Get the operations.
1113        let builder_span = tracing::debug_span!("build shrink program").entered();
1114        let mut builder = Builder::<InnerConfig>::default();
1115        let input = input.read(&mut builder);
1116        // Verify the proof.
1117        SP1CompressRootVerifierWithVKey::verify(
1118            &mut builder,
1119            self.compress_prover.machine(),
1120            input,
1121            self.vk_verification,
1122            PublicValuesOutputDigest::Reduce,
1123        );
1124        let block = builder.into_root_block();
1125        builder_span.exit();
1126        // SAFETY: The circuit is well-formed. It does not use synchronization primitives
1127        // (or possibly other means) to violate the invariants.
1128        let dsl_program = unsafe { DslIrProgram::new_unchecked(block) };
1129
1130        // Compile the program.
1131        let compiler_span = tracing::debug_span!("compile shrink program").entered();
1132        let mut compiler = AsmCompiler::<InnerConfig>::default();
1133        let mut program = compiler.compile(dsl_program);
1134
1135        *program.shape_mut() = Some(shrink_shape);
1136        let program = Arc::new(program);
1137        compiler_span.exit();
1138        program
1139    }
1140
1141    pub fn wrap_program(&self) -> Arc<RecursionProgram<BabyBear>> {
1142        self.wrap_program
1143            .get_or_init(|| {
1144                // Get the operations.
1145                let builder_span = tracing::debug_span!("build compress program").entered();
1146                let mut builder = Builder::<WrapConfig>::default();
1147
1148                let shrink_shape: OrderedShape = ShrinkAir::<BabyBear>::shrink_shape().into();
1149                let input_shape = SP1CompressShape::from(vec![shrink_shape]);
1150                let shape = SP1CompressWithVkeyShape {
1151                    compress_shape: input_shape,
1152                    merkle_tree_height: self.recursion_vk_tree.height,
1153                };
1154                let dummy_input =
1155                    SP1CompressWithVKeyWitnessValues::dummy(self.shrink_prover.machine(), &shape);
1156
1157                let input = dummy_input.read(&mut builder);
1158
1159                // Attest that the merkle tree root is correct.
1160                let root = input.merkle_var.root;
1161                for (val, expected) in root.iter().zip(self.recursion_vk_root.iter()) {
1162                    builder.assert_felt_eq(*val, *expected);
1163                }
1164                // Verify the proof.
1165                SP1CompressRootVerifierWithVKey::verify(
1166                    &mut builder,
1167                    self.shrink_prover.machine(),
1168                    input,
1169                    self.vk_verification,
1170                    PublicValuesOutputDigest::Root,
1171                );
1172
1173                let block = builder.into_root_block();
1174                builder_span.exit();
1175                // SAFETY: The circuit is well-formed. It does not use synchronization primitives
1176                // (or possibly other means) to violate the invariants.
1177                let dsl_program = unsafe { DslIrProgram::new_unchecked(block) };
1178
1179                // Compile the program.
1180                let compiler_span = tracing::debug_span!("compile compress program").entered();
1181                let mut compiler = AsmCompiler::<WrapConfig>::default();
1182                let program = Arc::new(compiler.compile(dsl_program));
1183                compiler_span.exit();
1184                program
1185            })
1186            .clone()
1187    }
1188
1189    pub fn deferred_program(
1190        &self,
1191        input: &SP1DeferredWitnessValues<InnerSC>,
1192    ) -> Arc<RecursionProgram<BabyBear>> {
1193        // Compile the program.
1194
1195        // Get the operations.
1196        let operations_span =
1197            tracing::debug_span!("get operations for the deferred program").entered();
1198        let mut builder = Builder::<InnerConfig>::default();
1199        let input_read_span = tracing::debug_span!("Read input values").entered();
1200        let input = input.read(&mut builder);
1201        input_read_span.exit();
1202        let verify_span = tracing::debug_span!("Verify deferred program").entered();
1203
1204        // Verify the proof.
1205        SP1DeferredVerifier::verify(
1206            &mut builder,
1207            self.compress_prover.machine(),
1208            input,
1209            self.vk_verification,
1210        );
1211        verify_span.exit();
1212        let block = builder.into_root_block();
1213        operations_span.exit();
1214        // SAFETY: The circuit is well-formed. It does not use synchronization primitives
1215        // (or possibly other means) to violate the invariants.
1216        let dsl_program = unsafe { DslIrProgram::new_unchecked(block) };
1217
1218        let compiler_span = tracing::debug_span!("compile deferred program").entered();
1219        let mut compiler = AsmCompiler::<InnerConfig>::default();
1220        let mut program = compiler.compile(dsl_program);
1221        if let Some(recursion_shape_config) = &self.compress_shape_config {
1222            recursion_shape_config.fix_shape(&mut program);
1223        }
1224        let program = Arc::new(program);
1225        compiler_span.exit();
1226        program
1227    }
1228
1229    pub fn get_recursion_core_inputs(
1230        &self,
1231        vk: &StarkVerifyingKey<CoreSC>,
1232        shard_proofs: &[ShardProof<CoreSC>],
1233        batch_size: usize,
1234        is_complete: bool,
1235        deferred_digest: [Val<CoreSC>; 8],
1236    ) -> Vec<SP1RecursionWitnessValues<CoreSC>> {
1237        let mut core_inputs = Vec::new();
1238
1239        // Prepare the inputs for the recursion programs.
1240        for (batch_idx, batch) in shard_proofs.chunks(batch_size).enumerate() {
1241            let proofs = batch.to_vec();
1242
1243            core_inputs.push(SP1RecursionWitnessValues {
1244                vk: vk.clone(),
1245                shard_proofs: proofs.clone(),
1246                is_complete,
1247                is_first_shard: batch_idx == 0,
1248                vk_root: self.recursion_vk_root,
1249                reconstruct_deferred_digest: deferred_digest,
1250            });
1251        }
1252        core_inputs
1253    }
1254
1255    pub fn get_recursion_deferred_inputs_with_initial_digest<'a>(
1256        &'a self,
1257        vk: &'a StarkVerifyingKey<CoreSC>,
1258        deferred_proofs: &[SP1ReduceProof<InnerSC>],
1259        mut deferred_digest: [Val<CoreSC>; 8],
1260        batch_size: usize,
1261    ) -> (Vec<SP1DeferredWitnessValues<InnerSC>>, [BabyBear; 8]) {
1262        // Prepare the inputs for the deferred proofs recursive verification.
1263        let mut deferred_inputs = Vec::new();
1264
1265        for batch in deferred_proofs.chunks(batch_size) {
1266            let vks_and_proofs =
1267                batch.iter().cloned().map(|proof| (proof.vk, proof.proof)).collect::<Vec<_>>();
1268
1269            let input = SP1CompressWitnessValues { vks_and_proofs, is_complete: true };
1270            let input = self.make_merkle_proofs(input);
1271            let SP1CompressWithVKeyWitnessValues { compress_val, merkle_val } = input;
1272
1273            deferred_inputs.push(SP1DeferredWitnessValues {
1274                vks_and_proofs: compress_val.vks_and_proofs,
1275                vk_merkle_data: merkle_val,
1276                start_reconstruct_deferred_digest: deferred_digest,
1277                is_complete: false,
1278                sp1_vk_digest: vk.hash_babybear(),
1279                end_pc: vk.pc_start,
1280                end_shard: BabyBear::one(),
1281                end_execution_shard: BabyBear::one(),
1282                init_addr_bits: [BabyBear::zero(); 32],
1283                finalize_addr_bits: [BabyBear::zero(); 32],
1284                committed_value_digest: [Word::<BabyBear>([BabyBear::zero(); 4]); 8],
1285                deferred_proofs_digest: [BabyBear::zero(); 8],
1286            });
1287
1288            deferred_digest = Self::hash_deferred_proofs(deferred_digest, batch);
1289        }
1290        (deferred_inputs, deferred_digest)
1291    }
1292
1293    pub fn get_recursion_deferred_inputs<'a>(
1294        &'a self,
1295        vk: &'a StarkVerifyingKey<CoreSC>,
1296        deferred_proofs: &[SP1ReduceProof<InnerSC>],
1297        batch_size: usize,
1298    ) -> (Vec<SP1DeferredWitnessValues<InnerSC>>, [BabyBear; 8]) {
1299        self.get_recursion_deferred_inputs_with_initial_digest(
1300            vk,
1301            deferred_proofs,
1302            [Val::<CoreSC>::zero(); DIGEST_SIZE],
1303            batch_size,
1304        )
1305    }
1306
1307    /// Generate the inputs for the first layer of recursive proofs.
1308    #[allow(clippy::type_complexity)]
1309    pub fn get_first_layer_inputs<'a>(
1310        &'a self,
1311        vk: &'a SP1VerifyingKey,
1312        shard_proofs: &[ShardProof<InnerSC>],
1313        deferred_proofs: &[SP1ReduceProof<InnerSC>],
1314        batch_size: usize,
1315    ) -> Vec<SP1CircuitWitness> {
1316        let (deferred_inputs, deferred_digest) =
1317            self.get_recursion_deferred_inputs(&vk.vk, deferred_proofs, batch_size);
1318
1319        let is_complete = shard_proofs.len() == 1 && deferred_proofs.is_empty();
1320        let core_inputs = self.get_recursion_core_inputs(
1321            &vk.vk,
1322            shard_proofs,
1323            batch_size,
1324            is_complete,
1325            deferred_digest,
1326        );
1327
1328        let mut inputs = Vec::new();
1329        inputs.extend(deferred_inputs.into_iter().map(SP1CircuitWitness::Deferred));
1330        inputs.extend(core_inputs.into_iter().map(SP1CircuitWitness::Core));
1331        inputs
1332    }
1333
1334    /// Accumulate deferred proofs into a single digest.
1335    pub fn hash_deferred_proofs(
1336        prev_digest: [Val<CoreSC>; DIGEST_SIZE],
1337        deferred_proofs: &[SP1ReduceProof<InnerSC>],
1338    ) -> [Val<CoreSC>; 8] {
1339        let mut digest = prev_digest;
1340        for proof in deferred_proofs.iter() {
1341            let pv: &RecursionPublicValues<Val<CoreSC>> =
1342                proof.proof.public_values.as_slice().borrow();
1343            let committed_values_digest = words_to_bytes(&pv.committed_value_digest);
1344            digest = hash_deferred_proof(
1345                &digest,
1346                &pv.sp1_vk_digest,
1347                &committed_values_digest.try_into().unwrap(),
1348            );
1349        }
1350        digest
1351    }
1352
1353    pub fn make_merkle_proofs(
1354        &self,
1355        input: SP1CompressWitnessValues<CoreSC>,
1356    ) -> SP1CompressWithVKeyWitnessValues<CoreSC> {
1357        let num_vks = self.recursion_vk_map.len();
1358        let (vk_indices, vk_digest_values): (Vec<_>, Vec<_>) = if self.vk_verification {
1359            input
1360                .vks_and_proofs
1361                .iter()
1362                .map(|(vk, _)| {
1363                    let vk_digest = vk.hash_babybear();
1364                    let index = self.recursion_vk_map.get(&vk_digest).expect("vk not allowed");
1365                    (index, vk_digest)
1366                })
1367                .unzip()
1368        } else {
1369            input
1370                .vks_and_proofs
1371                .iter()
1372                .map(|(vk, _)| {
1373                    let vk_digest = vk.hash_babybear();
1374                    let index = (vk_digest[0].as_canonical_u32() as usize) % num_vks;
1375                    (index, [BabyBear::from_canonical_usize(index); 8])
1376                })
1377                .unzip()
1378        };
1379
1380        let proofs = vk_indices
1381            .iter()
1382            .map(|index| {
1383                let (_, proof) = MerkleTree::open(&self.recursion_vk_tree, *index);
1384                proof
1385            })
1386            .collect();
1387
1388        let merkle_val = SP1MerkleProofWitnessValues {
1389            root: self.recursion_vk_root,
1390            values: vk_digest_values,
1391            vk_merkle_proofs: proofs,
1392        };
1393
1394        SP1CompressWithVKeyWitnessValues { compress_val: input, merkle_val }
1395    }
1396
1397    fn check_for_high_cycles(cycles: u64) {
1398        if cycles > 100_000_000 {
1399            tracing::warn!(
1400                "High cycle count detected ({}M cycles). For better performance, consider using the Succinct Prover Network: https://docs.succinct.xyz/docs/sp1/generating-proofs/prover-network",
1401                cycles / 1_000_000
1402            );
1403        }
1404    }
1405}
1406
1407pub fn compress_program_from_input<C: SP1ProverComponents>(
1408    config: Option<&RecursionShapeConfig<BabyBear, CompressAir<BabyBear>>>,
1409    compress_prover: &C::CompressProver,
1410    vk_verification: bool,
1411    input: &SP1CompressWithVKeyWitnessValues<BabyBearPoseidon2>,
1412) -> RecursionProgram<BabyBear> {
1413    let builder_span = tracing::debug_span!("build compress program").entered();
1414    let mut builder = Builder::<InnerConfig>::default();
1415    // read the input.
1416    let input = input.read(&mut builder);
1417    // Verify the proof.
1418    SP1CompressWithVKeyVerifier::verify(
1419        &mut builder,
1420        compress_prover.machine(),
1421        input,
1422        vk_verification,
1423        PublicValuesOutputDigest::Reduce,
1424    );
1425    let block = builder.into_root_block();
1426    builder_span.exit();
1427    // SAFETY: The circuit is well-formed. It does not use synchronization primitives
1428    // (or possibly other means) to violate the invariants.
1429    let dsl_program = unsafe { DslIrProgram::new_unchecked(block) };
1430
1431    // Compile the program.
1432    let compiler_span = tracing::debug_span!("compile compress program").entered();
1433    let mut compiler = AsmCompiler::<InnerConfig>::default();
1434    let mut program = compiler.compile(dsl_program);
1435    if let Some(config) = config {
1436        config.fix_shape(&mut program);
1437    }
1438    compiler_span.exit();
1439
1440    program
1441}
1442
1443#[cfg(test)]
1444pub mod tests {
1445    #![allow(clippy::print_stdout)]
1446
1447    use std::{
1448        collections::BTreeSet,
1449        fs::File,
1450        io::{Read, Write},
1451    };
1452
1453    use super::*;
1454
1455    use crate::build::try_build_plonk_bn254_artifacts_dev;
1456    use anyhow::Result;
1457    use build::{build_constraints_and_witness, try_build_groth16_bn254_artifacts_dev};
1458    use itertools::Itertools;
1459    use p3_field::PrimeField32;
1460
1461    use shapes::SP1ProofShape;
1462    use sp1_recursion_core::air::RecursionPublicValues;
1463
1464    #[cfg(test)]
1465    use serial_test::serial;
1466    #[cfg(test)]
1467    use sp1_core_machine::utils::setup_logger;
1468    use utils::sp1_vkey_digest_babybear;
1469
1470    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1471    pub enum Test {
1472        Core,
1473        Compress,
1474        Shrink,
1475        Wrap,
1476        CircuitTest,
1477        All,
1478    }
1479
1480    pub fn test_e2e_prover<C: SP1ProverComponents>(
1481        prover: &SP1Prover<C>,
1482        elf: &[u8],
1483        stdin: SP1Stdin,
1484        opts: SP1ProverOpts,
1485        test_kind: Test,
1486    ) -> Result<()> {
1487        run_e2e_prover_with_options(prover, elf, stdin, opts, test_kind, true)
1488    }
1489
1490    pub fn bench_e2e_prover<C: SP1ProverComponents>(
1491        prover: &SP1Prover<C>,
1492        elf: &[u8],
1493        stdin: SP1Stdin,
1494        opts: SP1ProverOpts,
1495        test_kind: Test,
1496    ) -> Result<()> {
1497        run_e2e_prover_with_options(prover, elf, stdin, opts, test_kind, false)
1498    }
1499
1500    pub fn run_e2e_prover_with_options<C: SP1ProverComponents>(
1501        prover: &SP1Prover<C>,
1502        elf: &[u8],
1503        stdin: SP1Stdin,
1504        opts: SP1ProverOpts,
1505        test_kind: Test,
1506        verify: bool,
1507    ) -> Result<()> {
1508        tracing::info!("initializing prover");
1509        let context = SP1Context::default();
1510
1511        tracing::info!("setup elf");
1512        let (_, pk_d, program, vk) = prover.setup(elf);
1513
1514        tracing::info!("prove core");
1515        let core_proof = prover.prove_core(&pk_d, program, &stdin, opts, context)?;
1516        let public_values = core_proof.public_values.clone();
1517
1518        if env::var("COLLECT_SHAPES").is_ok() {
1519            let mut shapes = BTreeSet::new();
1520            for proof in core_proof.proof.0.iter() {
1521                let shape = SP1ProofShape::Recursion(proof.shape());
1522                shapes.insert(shape);
1523            }
1524
1525            let mut file = File::create("../shapes.bin").unwrap();
1526            bincode::serialize_into(&mut file, &shapes).unwrap();
1527        }
1528
1529        if verify {
1530            tracing::info!("verify core");
1531            prover.verify(&core_proof.proof, &vk)?;
1532        }
1533
1534        if test_kind == Test::Core {
1535            return Ok(());
1536        }
1537
1538        tracing::info!("compress");
1539        let compress_span = tracing::debug_span!("compress").entered();
1540        let compressed_proof = prover.compress(&vk, core_proof, vec![], opts)?;
1541        compress_span.exit();
1542
1543        if verify {
1544            tracing::info!("verify compressed");
1545            prover.verify_compressed(&compressed_proof, &vk)?;
1546        }
1547
1548        if test_kind == Test::Compress {
1549            return Ok(());
1550        }
1551
1552        tracing::info!("shrink");
1553        let shrink_proof = prover.shrink(compressed_proof, opts)?;
1554
1555        if verify {
1556            tracing::info!("verify shrink");
1557            prover.verify_shrink(&shrink_proof, &vk)?;
1558        }
1559
1560        if test_kind == Test::Shrink {
1561            return Ok(());
1562        }
1563
1564        tracing::info!("wrap bn254");
1565        let wrapped_bn254_proof = prover.wrap_bn254(shrink_proof, opts)?;
1566        let bytes = bincode::serialize(&wrapped_bn254_proof).unwrap();
1567
1568        // Save the proof.
1569        let mut file = File::create("proof-with-pis.bin").unwrap();
1570        file.write_all(bytes.as_slice()).unwrap();
1571
1572        // Load the proof.
1573        let mut file = File::open("proof-with-pis.bin").unwrap();
1574        let mut bytes = Vec::new();
1575        file.read_to_end(&mut bytes).unwrap();
1576
1577        let wrapped_bn254_proof = bincode::deserialize(&bytes).unwrap();
1578
1579        if verify {
1580            tracing::info!("verify wrap bn254");
1581            prover.verify_wrap_bn254(&wrapped_bn254_proof, &vk).unwrap();
1582        }
1583
1584        if test_kind == Test::Wrap {
1585            return Ok(());
1586        }
1587
1588        tracing::info!("checking vkey hash babybear");
1589        let vk_digest_babybear = sp1_vkey_digest_babybear(&wrapped_bn254_proof);
1590        assert_eq!(vk_digest_babybear, vk.hash_babybear());
1591
1592        tracing::info!("checking vkey hash bn254");
1593        let vk_digest_bn254 = sp1_vkey_digest_bn254(&wrapped_bn254_proof);
1594        assert_eq!(vk_digest_bn254, vk.hash_bn254());
1595
1596        tracing::info!("Test the outer Plonk circuit");
1597        let (constraints, witness) =
1598            build_constraints_and_witness(&wrapped_bn254_proof.vk, &wrapped_bn254_proof.proof);
1599        PlonkBn254Prover::test(constraints, witness);
1600        tracing::info!("Circuit test succeeded");
1601
1602        if test_kind == Test::CircuitTest {
1603            return Ok(());
1604        }
1605
1606        tracing::info!("generate plonk bn254 proof");
1607        let artifacts_dir = try_build_plonk_bn254_artifacts_dev(
1608            &wrapped_bn254_proof.vk,
1609            &wrapped_bn254_proof.proof,
1610        );
1611        let plonk_bn254_proof =
1612            prover.wrap_plonk_bn254(wrapped_bn254_proof.clone(), &artifacts_dir);
1613        println!("{plonk_bn254_proof:?}");
1614
1615        prover.verify_plonk_bn254(&plonk_bn254_proof, &vk, &public_values, &artifacts_dir)?;
1616
1617        tracing::info!("generate groth16 bn254 proof");
1618        let artifacts_dir = try_build_groth16_bn254_artifacts_dev(
1619            &wrapped_bn254_proof.vk,
1620            &wrapped_bn254_proof.proof,
1621        );
1622        let groth16_bn254_proof = prover.wrap_groth16_bn254(wrapped_bn254_proof, &artifacts_dir);
1623        println!("{groth16_bn254_proof:?}");
1624
1625        if verify {
1626            prover.verify_groth16_bn254(
1627                &groth16_bn254_proof,
1628                &vk,
1629                &public_values,
1630                &artifacts_dir,
1631            )?;
1632        }
1633
1634        Ok(())
1635    }
1636
1637    pub fn test_e2e_with_deferred_proofs_prover<C: SP1ProverComponents>(
1638        opts: SP1ProverOpts,
1639    ) -> Result<()> {
1640        // Test program which proves the Keccak-256 hash of various inputs.
1641        let keccak_elf = test_artifacts::KECCAK256_ELF;
1642
1643        // Test program which verifies proofs of a vkey and a list of committed inputs.
1644        let verify_elf = test_artifacts::VERIFY_PROOF_ELF;
1645
1646        tracing::info!("initializing prover");
1647        let prover = SP1Prover::<C>::new();
1648
1649        tracing::info!("setup keccak elf");
1650        let (_, keccak_pk_d, keccak_program, keccak_vk) = prover.setup(keccak_elf);
1651
1652        tracing::info!("setup verify elf");
1653        let (_, verify_pk_d, verify_program, verify_vk) = prover.setup(verify_elf);
1654
1655        tracing::info!("prove subproof 1");
1656        let mut stdin = SP1Stdin::new();
1657        stdin.write(&1usize);
1658        stdin.write(&vec![0u8, 0, 0]);
1659        let deferred_proof_1 = prover.prove_core(
1660            &keccak_pk_d,
1661            keccak_program.clone(),
1662            &stdin,
1663            opts,
1664            Default::default(),
1665        )?;
1666        let pv_1 = deferred_proof_1.public_values.as_slice().to_vec().clone();
1667
1668        // Generate a second proof of keccak of various inputs.
1669        tracing::info!("prove subproof 2");
1670        let mut stdin = SP1Stdin::new();
1671        stdin.write(&3usize);
1672        stdin.write(&vec![0u8, 1, 2]);
1673        stdin.write(&vec![2, 3, 4]);
1674        stdin.write(&vec![5, 6, 7]);
1675        let deferred_proof_2 =
1676            prover.prove_core(&keccak_pk_d, keccak_program, &stdin, opts, Default::default())?;
1677        let pv_2 = deferred_proof_2.public_values.as_slice().to_vec().clone();
1678
1679        // Generate recursive proof of first subproof.
1680        tracing::info!("compress subproof 1");
1681        let deferred_reduce_1 = prover.compress(&keccak_vk, deferred_proof_1, vec![], opts)?;
1682        prover.verify_compressed(&deferred_reduce_1, &keccak_vk)?;
1683
1684        // Generate recursive proof of second subproof.
1685        tracing::info!("compress subproof 2");
1686        let deferred_reduce_2 = prover.compress(&keccak_vk, deferred_proof_2, vec![], opts)?;
1687        prover.verify_compressed(&deferred_reduce_2, &keccak_vk)?;
1688
1689        // Run verify program with keccak vkey, subproofs, and their committed values.
1690        let mut stdin = SP1Stdin::new();
1691        let vkey_digest = keccak_vk.hash_babybear();
1692        let vkey_digest: [u32; 8] = vkey_digest
1693            .iter()
1694            .map(|n| n.as_canonical_u32())
1695            .collect::<Vec<_>>()
1696            .try_into()
1697            .unwrap();
1698        stdin.write(&vkey_digest);
1699        stdin.write(&vec![pv_1.clone(), pv_2.clone(), pv_2.clone()]);
1700        stdin.write_proof(deferred_reduce_1.clone(), keccak_vk.vk.clone());
1701        stdin.write_proof(deferred_reduce_2.clone(), keccak_vk.vk.clone());
1702        stdin.write_proof(deferred_reduce_2.clone(), keccak_vk.vk.clone());
1703
1704        tracing::info!("proving verify program (core)");
1705        let verify_proof =
1706            prover.prove_core(&verify_pk_d, verify_program, &stdin, opts, Default::default())?;
1707        // let public_values = verify_proof.public_values.clone();
1708
1709        // Generate recursive proof of verify program
1710        tracing::info!("compress verify program");
1711        let verify_reduce = prover.compress(
1712            &verify_vk,
1713            verify_proof,
1714            vec![deferred_reduce_1, deferred_reduce_2.clone(), deferred_reduce_2],
1715            opts,
1716        )?;
1717        let reduce_pv: &RecursionPublicValues<_> =
1718            verify_reduce.proof.public_values.as_slice().borrow();
1719        println!("deferred_hash: {:?}", reduce_pv.deferred_proofs_digest);
1720        println!("complete: {:?}", reduce_pv.is_complete);
1721
1722        tracing::info!("verify verify program");
1723        prover.verify_compressed(&verify_reduce, &verify_vk)?;
1724
1725        let shrink_proof = prover.shrink(verify_reduce, opts)?;
1726
1727        tracing::info!("verify shrink");
1728        prover.verify_shrink(&shrink_proof, &verify_vk)?;
1729
1730        tracing::info!("wrap bn254");
1731        let wrapped_bn254_proof = prover.wrap_bn254(shrink_proof, opts)?;
1732
1733        tracing::info!("verify wrap bn254");
1734        println!("verify wrap bn254 {:#?}", wrapped_bn254_proof.vk.commit);
1735        prover.verify_wrap_bn254(&wrapped_bn254_proof, &verify_vk).unwrap();
1736
1737        Ok(())
1738    }
1739
1740    /// Tests an end-to-end workflow of proving a program across the entire proof generation
1741    /// pipeline.
1742    ///
1743    /// Add `FRI_QUERIES`=1 to your environment for faster execution. Should only take a few minutes
1744    /// on a Mac M2. Note: This test always re-builds the plonk bn254 artifacts, so setting SP1_DEV
1745    /// is not needed.
1746    #[test]
1747    #[serial]
1748    fn test_e2e() -> Result<()> {
1749        let elf = test_artifacts::FIBONACCI_ELF;
1750        setup_logger();
1751        let opts = SP1ProverOpts::auto();
1752        // TODO(mattstam): We should Test::Plonk here, but this uses the existing
1753        // docker image which has a different API than the current. So we need to wait until the
1754        // next release (v1.2.0+), and then switch it back.
1755        let prover = SP1Prover::<CpuProverComponents>::new();
1756        test_e2e_prover::<CpuProverComponents>(&prover, elf, SP1Stdin::default(), opts, Test::All)
1757    }
1758
1759    /// Tests an end-to-end workflow of proving a program across the entire proof generation
1760    /// pipeline in addition to verifying deferred proofs.
1761    #[test]
1762    #[serial]
1763    fn test_e2e_with_deferred_proofs() -> Result<()> {
1764        setup_logger();
1765        test_e2e_with_deferred_proofs_prover::<CpuProverComponents>(SP1ProverOpts::auto())
1766    }
1767
1768    /// Checks that the constants, types, etc. in sp1-verifier are valid.
1769    ///
1770    /// # How to obtain constants
1771    ///
1772    /// To obtain `RECURSION_VK_ROOT`, just print the value of `prover.recursion_vk_root`.
1773    /// To obtain `RECURSION_VK_SET`:
1774    /// - Prepare to use `cargo run --release -p sp1-prover --bin build_recursion_vks [...]` to run
1775    ///   the shape-generation code. Be aware that it writes to the specified directory, so either
1776    ///   prevent writing to the filesystem or avoid committing the generated artifacts.
1777    /// - Locate `sp1_prover::shapes::build_vk_map`.
1778    ///   - In the `false` branch of `if dummy [...]`, locate the `let height = [...];` statement.
1779    ///   - Hardcode the value of `height`. (For example, obtain it with `panic!("{height}")`.) The
1780    ///     value is 19 as of the time of writing this.
1781    /// - Locate `sp1_prover::shapes::SP1ProofShape::generate`.
1782    ///   - Find the iterator consisting of data piped through `Self::Compress`. Comment out
1783    ///   - the other iterators so the function just returns the `Self::Compress` iterator.
1784    ///   - Print out the returned `vk_set: BTreeSet<[_; 8]>`. Q.E.D.
1785    #[test]
1786    fn sp1_verifier_valid() {
1787        use sp1_verifier::compressed::internal::{
1788            self, COMPRESS_DEGREE, RECURSION_VK_ROOT, RECURSION_VK_SET,
1789        };
1790
1791        // The field and stark config types are the same.
1792        type F = BabyBear;
1793        type SC = BabyBearPoseidon2;
1794        let _: Option<internal::F> = Option::<F>::None;
1795        let _: Option<internal::SC> = Option::<SC>::None;
1796
1797        // The compress degree is correct.
1798        assert_eq!(COMPRESS_DEGREE, super::COMPRESS_DEGREE);
1799
1800        let prover = SP1Prover::<CpuProverComponents>::new();
1801
1802        // The vk root matches.
1803        assert_eq!(RECURSION_VK_ROOT.map(F::from_canonical_u32), prover.recursion_vk_root);
1804        // The verifier's set of vkeys is a subset of the (true) set of vkeys.
1805        assert_eq!(
1806            RECURSION_VK_SET.iter().find(|digest| !prover
1807                .recursion_vk_map
1808                .contains_key(&digest.map(F::from_canonical_u32))),
1809            None
1810        );
1811        // The list canonically represents a set, i.e. it is sorted and consists of unique elements.
1812        assert!(RECURSION_VK_SET.is_sorted());
1813        assert!(RECURSION_VK_SET.iter().all_unique());
1814    }
1815}