diff --git a/Cargo.lock b/Cargo.lock index fdbfea5a2f..840fdfa832 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4711,6 +4711,7 @@ dependencies = [ "p3-field", "p3-fri", "p3-matrix", + "p3-maybe-rayon", "p3-merkle-tree", "p3-poseidon2", "p3-symmetric", diff --git a/core/src/stark/verifier.rs b/core/src/stark/verifier.rs index fbde0f4267..6f5bad846d 100644 --- a/core/src/stark/verifier.rs +++ b/core/src/stark/verifier.rs @@ -51,6 +51,8 @@ impl>> Verifier { let pcs = config.pcs(); + assert_eq!(chips.len(), opened_values.chips.len()); + let log_degrees = opened_values .chips .iter() diff --git a/prover/scripts/e2e.rs b/prover/scripts/e2e.rs new file mode 100644 index 0000000000..911bf4f113 --- /dev/null +++ b/prover/scripts/e2e.rs @@ -0,0 +1,107 @@ +#![feature(generic_const_exprs)] +#![allow(incomplete_features)] + +use std::borrow::Borrow; + +use clap::Parser; +use p3_baby_bear::BabyBear; +use sp1_core::io::SP1Stdin; +use sp1_prover::utils::{babybear_bytes_to_bn254, babybears_to_bn254, words_to_bytes}; +use sp1_prover::SP1Prover; +use sp1_recursion_circuit::stark::build_wrap_circuit; +use sp1_recursion_circuit::witness::Witnessable; +use sp1_recursion_compiler::ir::Witness; +use sp1_recursion_core::air::RecursionPublicValues; +use sp1_recursion_gnark_ffi::{convert, verify, Groth16Prover}; +use subtle_encoding::hex; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + #[clap(short, long)] + build_dir: String, +} + +pub fn main() { + sp1_core::utils::setup_logger(); + std::env::set_var("RECONSTRUCT_COMMITMENTS", "false"); + + let args = Args::parse(); + + let elf = include_bytes!("../../tests/fibonacci/elf/riscv32im-succinct-zkvm-elf"); + + tracing::info!("initializing prover"); + let prover = SP1Prover::new(); + + tracing::info!("setup elf"); + let (pk, vk) = prover.setup(elf); + + tracing::info!("prove core"); + let stdin = SP1Stdin::new(); + let core_proof = prover.prove_core(&pk, &stdin); + + tracing::info!("Compress"); + let reduced_proof = prover.compress(&vk, core_proof, vec![]); + + tracing::info!("Shrink"); + let compressed_proof = prover.shrink(reduced_proof); + + tracing::info!("wrap"); + let wrapped_proof = prover.wrap_bn254(compressed_proof); + + tracing::info!("building verifier constraints"); + let constraints = tracing::info_span!("wrap circuit") + .in_scope(|| build_wrap_circuit(&prover.wrap_vk, wrapped_proof.clone())); + + tracing::info!("building template witness"); + let pv: &RecursionPublicValues<_> = wrapped_proof.public_values.as_slice().borrow(); + let vkey_hash = babybears_to_bn254(&pv.sp1_vk_digest); + let committed_values_digest_bytes: [BabyBear; 32] = words_to_bytes(&pv.committed_value_digest) + .try_into() + .unwrap(); + let committed_values_digest = babybear_bytes_to_bn254(&committed_values_digest_bytes); + + let mut witness = Witness::default(); + wrapped_proof.write(&mut witness); + witness.write_commited_values_digest(committed_values_digest); + witness.write_vkey_hash(vkey_hash); + + tracing::info!("sanity check gnark test"); + Groth16Prover::test(constraints.clone(), witness.clone()); + + tracing::info!("sanity check gnark build"); + Groth16Prover::build( + constraints.clone(), + witness.clone(), + args.build_dir.clone().into(), + ); + + tracing::info!("sanity check gnark prove"); + let groth16_prover = Groth16Prover::new(args.build_dir.clone().into()); + + tracing::info!("gnark prove"); + let proof = groth16_prover.prove(witness.clone()); + + tracing::info!("verify gnark proof"); + let verified = verify(proof.clone(), &args.build_dir.clone().into()); + assert!(verified); + + tracing::info!("convert gnark proof"); + let solidity_proof = convert(proof.clone(), &args.build_dir.clone().into()); + + // tracing::info!("sanity check plonk bn254 build"); + // PlonkBn254Prover::build( + // constraints.clone(), + // witness.clone(), + // args.build_dir.clone().into(), + // ); + + // tracing::info!("sanity check plonk bn254 prove"); + // let proof = PlonkBn254Prover::prove(witness.clone(), args.build_dir.clone().into()); + + println!( + "{:?}", + String::from_utf8(hex::encode(proof.encoded_proof)).unwrap() + ); + println!("solidity proof: {:?}", solidity_proof); +} diff --git a/prover/src/build.rs b/prover/src/build.rs index df6c842506..0c3325e43c 100644 --- a/prover/src/build.rs +++ b/prover/src/build.rs @@ -1,3 +1,4 @@ +use std::borrow::Borrow; use std::path::PathBuf; use p3_baby_bear::BabyBear; @@ -44,7 +45,7 @@ pub fn build_constraints_and_witness( let constraints = tracing::info_span!("wrap circuit") .in_scope(|| build_wrap_circuit(template_vk, template_proof.clone())); - let pv = RecursionPublicValues::from_vec(template_proof.public_values.clone()); + let pv: &RecursionPublicValues = template_proof.public_values.as_slice().borrow(); let vkey_hash = babybears_to_bn254(&pv.sp1_vk_digest); let committed_values_digest_bytes: [BabyBear; 32] = words_to_bytes(&pv.committed_value_digest) .try_into() @@ -80,10 +81,10 @@ pub fn dummy_proof() -> (StarkVerifyingKey, ShardProof) { let compressed_proof = prover.compress(&vk, core_proof, vec![]); tracing::info!("shrink"); - let shrink_proof = prover.shrink(&vk, compressed_proof); + let shrink_proof = prover.shrink(compressed_proof); tracing::info!("wrap"); - let wrapped_proof = prover.wrap_bn254(&vk, shrink_proof); + let wrapped_proof = prover.wrap_bn254(shrink_proof); (prover.wrap_vk, wrapped_proof.proof) } diff --git a/prover/src/lib.rs b/prover/src/lib.rs index 44ad4b7602..5281728cb9 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -3,7 +3,7 @@ //! Seperates the proof generation process into multiple stages: //! //! 1. Generate shard proofs which split up and prove the valid execution of a RISC-V program. -//! 2. Reduce shard proofs into a single shard proof. +//! 2. Compress shard proofs into a single shard proof. //! 3. Wrap the shard proof into a SNARK-friendly field. //! 4. Wrap the last shard proof, proven over the SNARK-friendly field, into a Groth16/PLONK proof. @@ -18,57 +18,54 @@ mod types; pub mod utils; mod verify; +use std::borrow::Borrow; + +use crate::utils::RECONSTRUCT_COMMITMENTS_ENV_VAR; use p3_baby_bear::BabyBear; use p3_challenger::CanObserve; use p3_field::AbstractField; -use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; -use serde::de::DeserializeOwned; -use serde::Serialize; -use size::Size; +use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use rayon::prelude::*; +use sp1_core::air::PublicValues; pub use sp1_core::io::{SP1PublicValues, SP1Stdin}; use sp1_core::runtime::Runtime; -use sp1_core::stark::{ - Challenge, Com, Domain, PcsProverData, Prover, ShardMainData, StarkProvingKey, -}; +use sp1_core::stark::MachineVerificationError; +use sp1_core::stark::{Challenge, StarkProvingKey}; +use sp1_core::utils::DIGEST_SIZE; use sp1_core::{ runtime::Program, stark::{ - Challenger, LocalProver, RiscvAir, ShardProof, StarkGenericConfig, StarkMachine, - StarkVerifyingKey, Val, + LocalProver, RiscvAir, ShardProof, StarkGenericConfig, StarkMachine, StarkVerifyingKey, Val, }, utils::{run_and_prove, BabyBearPoseidon2}, }; use sp1_primitives::hash_deferred_proof; use sp1_recursion_circuit::witness::Witnessable; +use sp1_recursion_compiler::config::InnerConfig; use sp1_recursion_compiler::config::OuterConfig; use sp1_recursion_compiler::ir::Witness; use sp1_recursion_core::runtime::RecursionProgram; -use sp1_recursion_core::stark::RecursionAirSkinnyDeg7; +use sp1_recursion_core::stark::RecursionAir; use sp1_recursion_core::{ - air::RecursionPublicValues, - runtime::Runtime as RecursionRuntime, - stark::{config::BabyBearPoseidon2Outer, RecursionAirWideDeg3}, + air::RecursionPublicValues, runtime::Runtime as RecursionRuntime, + stark::config::BabyBearPoseidon2Outer, }; pub use sp1_recursion_gnark_ffi::plonk_bn254::PlonkBn254Proof; use sp1_recursion_gnark_ffi::plonk_bn254::PlonkBn254Prover; pub use sp1_recursion_gnark_ffi::Groth16Proof; use sp1_recursion_gnark_ffi::Groth16Prover; use sp1_recursion_program::hints::Hintable; -use sp1_recursion_program::reduce::ReduceProgram; -use sp1_recursion_program::types::QuotientDataValues; +use sp1_recursion_program::machine::{ + ReduceProgramType, SP1CompressVerifier, SP1DeferredMemoryLayout, SP1DeferredVerifier, + SP1RecursionMemoryLayout, SP1RecursiveVerifier, SP1ReduceMemoryLayout, SP1RootMemoryLayout, + SP1RootVerifier, +}; use std::env; use std::path::PathBuf; -use std::time::Instant; use tracing::instrument; pub use types::*; use utils::words_to_bytes; -use crate::types::ReduceState; -use crate::utils::get_chip_quotient_data; -use crate::utils::get_preprocessed_data; -use crate::utils::get_sorted_indices; -use crate::utils::RECONSTRUCT_COMMITMENTS_ENV_VAR; - /// The configuration for the core prover. pub type CoreSC = BabyBearPoseidon2; @@ -78,13 +75,36 @@ pub type InnerSC = BabyBearPoseidon2; /// The configuration for the outer prover. pub type OuterSC = BabyBearPoseidon2Outer; +const REDUCE_DEGREE: usize = 3; +const COMPRESS_DEGREE: usize = 9; +const WRAP_DEGREE: usize = 5; + +pub type ReduceAir = RecursionAir; +pub type CompressAir = RecursionAir; +pub type WrapAir = RecursionAir; + /// A end-to-end prover implementation for SP1. pub struct SP1Prover { /// The program that can recursively verify a set of proofs into a single proof. pub recursion_program: RecursionProgram, - /// The program that sets up memory for the recursion program. - pub recursion_setup_program: RecursionProgram, + /// The proving key for the recursion step. + pub rec_pk: StarkProvingKey, + + /// The verification key for the recursion step. + pub rec_vk: StarkVerifyingKey, + + /// The program that recursively verifies deferred proofs and accumulates the digests. + pub deferred_program: RecursionProgram, + + /// The proving key for the reduce step. + pub deferred_pk: StarkProvingKey, + + /// The verification key for the reduce step. + pub deferred_vk: StarkVerifyingKey, + + /// The program that reduces a set of recursive proofs into a single proof. + pub compress_program: RecursionProgram, /// The proving key for the reduce step. pub compress_pk: StarkProvingKey, @@ -92,12 +112,18 @@ pub struct SP1Prover { /// The verification key for the reduce step. pub compress_vk: StarkVerifyingKey, - /// The proving key for the shrink step. + /// The shrink program that compresses a proof into a succinct proof. + pub shrink_program: RecursionProgram, + + /// The proving key for the compress step. pub shrink_pk: StarkProvingKey, - /// The verification key for the shrink step. + /// The verification key for the compress step. pub shrink_vk: StarkVerifyingKey, + /// The wrap program that wraps a proof into a SNARK-friendly field. + pub wrap_program: RecursionProgram, + /// The proving key for the wrap step. pub wrap_pk: StarkProvingKey, @@ -107,42 +133,65 @@ pub struct SP1Prover { /// The machine used for proving the core step. pub core_machine: StarkMachine::Val>>, - /// The machine used for proving the reduce step. - pub compress_machine: - StarkMachine::Val>>, + /// The machine used for proving the recursive and reduction steps. + pub compress_machine: StarkMachine::Val>>, - /// The machine used for proving the compress step. - pub shrink_machine: - StarkMachine::Val>>, + /// The machine used for proving the shrink step. + pub shrink_machine: StarkMachine::Val>>, /// The machine used for proving the wrapping step. - pub wrap_machine: - StarkMachine::Val>>, + pub wrap_machine: StarkMachine::Val>>, } impl SP1Prover { /// Initializes a new [SP1Prover]. #[instrument(name = "initialize prover", level = "info", skip_all)] pub fn new() -> Self { - let recursion_setup_program = ReduceProgram::setup(); - let recursion_program = ReduceProgram::build(); - let (compress_pk, compress_vk) = - RecursionAirWideDeg3::machine(InnerSC::default()).setup(&recursion_program); - let (shrink_pk, shrink_vk) = - RecursionAirSkinnyDeg7::machine(InnerSC::compressed()).setup(&recursion_program); - let (wrap_pk, wrap_vk) = - RecursionAirSkinnyDeg7::machine(OuterSC::default()).setup(&recursion_program); let core_machine = RiscvAir::machine(CoreSC::default()); - let compress_machine = RecursionAirWideDeg3::machine(InnerSC::default()); - let shrink_machine = RecursionAirSkinnyDeg7::machine(InnerSC::compressed()); - let wrap_machine = RecursionAirSkinnyDeg7::wrap_machine(OuterSC::default()); + + // Get the recursive verifier and setup the proving and verifying keys. + let recursion_program = SP1RecursiveVerifier::::build(&core_machine); + let compress_machine = ReduceAir::machine(InnerSC::default()); + let (rec_pk, rec_vk) = compress_machine.setup(&recursion_program); + + // Get the deferred program and keys. + let deferred_program = SP1DeferredVerifier::::build(&compress_machine); + let (deferred_pk, deferred_vk) = compress_machine.setup(&deferred_program); + + // Make the reduce program and keys. + let compress_program = SP1CompressVerifier::::build( + &compress_machine, + &rec_vk, + &deferred_vk, + ); + let (compress_pk, compress_vk) = compress_machine.setup(&compress_program); + + // Get the compress program, machine, and keys. + let shrink_program = + SP1RootVerifier::::build(&compress_machine, &compress_vk, true); + let shrink_machine = CompressAir::machine(InnerSC::compressed()); + let (shrink_pk, shrink_vk) = shrink_machine.setup(&shrink_program); + + // Get the wrap program, machine, and keys. + let wrap_program = + SP1RootVerifier::::build(&shrink_machine, &shrink_vk, false); + let wrap_machine = WrapAir::wrap_machine(OuterSC::default()); + let (wrap_pk, wrap_vk) = wrap_machine.setup(&wrap_program); + Self { - recursion_setup_program, recursion_program, + rec_pk, + rec_vk, + deferred_program, + deferred_pk, + deferred_vk, + compress_program, compress_pk, compress_vk, + shrink_program, shrink_pk, shrink_vk, + wrap_program, wrap_pk, wrap_vk, core_machine, @@ -166,24 +215,6 @@ impl SP1Prover { (pk, vk) } - /// Accumulate deferred proofs into a single digest. - pub fn hash_deferred_proofs( - prev_digest: [Val; 8], - deferred_proofs: &[ShardProof], - ) -> [Val; 8] { - let mut digest = prev_digest; - for proof in deferred_proofs.iter() { - let pv = RecursionPublicValues::from_vec(proof.public_values.clone()); - let committed_values_digest = words_to_bytes(&pv.committed_value_digest); - digest = hash_deferred_proof( - &digest, - &pv.sp1_vk_digest, - &committed_values_digest.try_into().unwrap(), - ); - } - digest - } - /// Generate a proof of an SP1 program with the specified inputs. #[instrument(name = "execute", level = "info", skip_all)] pub fn execute(elf: &[u8], stdin: &SP1Stdin) -> SP1PublicValues { @@ -200,481 +231,340 @@ impl SP1Prover { /// Generate shard proofs which split up and prove the valid execution of a RISC-V program with /// the core prover. #[instrument(name = "prove_core", level = "info", skip_all)] - pub fn prove_core( - &self, - pk: &SP1ProvingKey, - stdin: &SP1Stdin, - ) -> SP1ProofWithMetadata { + pub fn prove_core(&self, pk: &SP1ProvingKey, stdin: &SP1Stdin) -> SP1CoreProof { let config = CoreSC::default(); let program = Program::from(&pk.elf); let (proof, public_values_stream) = run_and_prove(program, stdin, config); let public_values = SP1PublicValues::from(&public_values_stream); - SP1ProofWithMetadata { + SP1CoreProof { proof: SP1CoreProofData(proof.shard_proofs), stdin: stdin.clone(), public_values, } } - /// Compress shards proofs to a single shard proof using the recursion prover. + /// Reduce shards proofs to a single shard proof using the recursion prover. #[instrument(name = "compress", level = "info", skip_all)] pub fn compress( &self, vk: &SP1VerifyingKey, - proof: SP1ProofWithMetadata, - mut deferred_proofs: Vec>, + proof: SP1CoreProof, + deferred_proofs: Vec>, ) -> SP1ReduceProof { - // Observe all commitments and public values. - // - // This challenger will be witnessed into reduce program and used to verify sp1 proofs. It - // will also be reconstructed over all the reduce steps to prove that the witnessed - // challenger was correct. - let mut core_challenger = self.core_machine.config().challenger(); - vk.vk.observe_into(&mut core_challenger); - for shard_proof in proof.proof.0.iter() { - core_challenger.observe(shard_proof.commitment.main_commit); - core_challenger.observe_slice( - &shard_proof.public_values.to_vec()[0..self.core_machine.num_pv_elts()], - ); + // Set the batch size for the reduction tree. + let batch_size = 2; + + let shard_proofs = &proof.proof.0; + + // Setup the reconstruct commitments flags to false and save its state. + let rc = env::var(RECONSTRUCT_COMMITMENTS_ENV_VAR).unwrap_or_default(); + env::set_var(RECONSTRUCT_COMMITMENTS_ENV_VAR, "false"); + + // Get the leaf challenger. + let mut leaf_challenger = self.core_machine.config().challenger(); + vk.vk.observe_into(&mut leaf_challenger); + shard_proofs.iter().for_each(|proof| { + leaf_challenger.observe(proof.commitment.main_commit); + leaf_challenger.observe_slice(&proof.public_values[0..self.core_machine.num_pv_elts()]); + }); + // Make sure leaf challenger is not mutable anymore. + let leaf_challenger = leaf_challenger; + + let mut core_inputs = Vec::new(); + + let mut reconstruct_challenger = self.core_machine.config().challenger(); + vk.vk.observe_into(&mut reconstruct_challenger); + + // Prepare the inputs for the recursion programs. + let is_complete = shard_proofs.len() == 1 && deferred_proofs.is_empty(); + for batch in shard_proofs.chunks(batch_size) { + let proofs = batch.to_vec(); + + core_inputs.push(SP1RecursionMemoryLayout { + vk: &vk.vk, + machine: &self.core_machine, + shard_proofs: proofs, + leaf_challenger: &leaf_challenger, + initial_reconstruct_challenger: reconstruct_challenger.clone(), + is_complete, + }); + + for proof in batch.iter() { + reconstruct_challenger.observe(proof.commitment.main_commit); + reconstruct_challenger + .observe_slice(&proof.public_values[0..self.core_machine.num_pv_elts()]); + } } - // Map the existing shards to a self-reducing type of proof (i.e. Reduce: T[] -> T). - let mut reduce_proofs = proof - .proof - .0 - .into_iter() - .map(|proof| SP1ReduceProofWrapper::Core(SP1ReduceProof { proof })) - .collect::>(); + let last_proof_input = + PublicValues::from_vec(shard_proofs.last().unwrap().public_values.clone()); - // Keep reducing until we have only one shard. - while reduce_proofs.len() > 1 { - let layer_deferred_proofs = std::mem::take(&mut deferred_proofs); - reduce_proofs = self.reduce_layer( - vk, - core_challenger.clone(), - reduce_proofs, - layer_deferred_proofs, - 2, - ); + // Check that the leaf challenger is the same as the reconstruct challenger. + assert_eq!( + reconstruct_challenger.sponge_state, + leaf_challenger.sponge_state + ); + assert_eq!( + reconstruct_challenger.input_buffer, + leaf_challenger.input_buffer + ); + assert_eq!( + reconstruct_challenger.output_buffer, + leaf_challenger.output_buffer + ); + + // Prepare the inputs for the deferred proofs recursive verification. + let mut deferred_digest = [Val::::zero(); DIGEST_SIZE]; + let mut deferred_inputs = Vec::new(); + + let is_deferred_complete = shard_proofs.is_empty() && deferred_proofs.len() == 1; + + for batch in deferred_proofs.chunks(batch_size) { + let proofs = batch.to_vec(); + + deferred_inputs.push(SP1DeferredMemoryLayout { + compress_vk: &self.compress_vk, + machine: &self.compress_machine, + proofs, + start_reconstruct_deferred_digest: deferred_digest.to_vec(), + is_complete: is_deferred_complete, + sp1_vk: &vk.vk, + sp1_machine: &self.core_machine, + end_pc: Val::::zero(), + end_shard: Val::::from_canonical_usize(shard_proofs.len()), + leaf_challenger: leaf_challenger.clone(), + committed_value_digest: last_proof_input.committed_value_digest.to_vec(), + deferred_proofs_digest: last_proof_input.deferred_proofs_digest.to_vec(), + }); + + deferred_digest = Self::hash_deferred_proofs(deferred_digest, batch); } - // Return the remaining single reduce proof. If we have only one shard, we still want to - // wrap it into a reduce shard. - assert_eq!(reduce_proofs.len(), 1); - let last_proof = reduce_proofs.into_iter().next().unwrap(); - match last_proof { - SP1ReduceProofWrapper::Recursive(proof) => proof, - SP1ReduceProofWrapper::Core(ref proof) => { - let state = ReduceState::from_core_start_state(&proof.proof); - let reconstruct_challenger = self.setup_initial_core_challenger(vk); - let config = InnerSC::default(); - self.verify_batch( - config, - &self.compress_pk, - vk, - core_challenger, - reconstruct_challenger, - state, - &[last_proof], - &deferred_proofs, - true, - false, - false, - ) - } + // Run the recursion and reduce programs. + + // Run the recursion programs. + let mut records = Vec::new(); + + for input in core_inputs { + let mut runtime = RecursionRuntime::, Challenge, _>::new( + &self.recursion_program, + self.compress_machine.config().perm.clone(), + ); + + let mut witness_stream = Vec::new(); + witness_stream.extend(input.write()); + + runtime.witness_stream = witness_stream.into(); + runtime.run(); + runtime.print_stats(); + + records.push((runtime.record, ReduceProgramType::Core)); } - } - /// Reduce a set of shard proofs in groups of `batch_size` into a smaller set of shard proofs - /// using the recursion prover. - #[instrument(name = "reduce_layer", level = "info", skip_all)] - fn reduce_layer( - &self, - vk: &SP1VerifyingKey, - sp1_challenger: Challenger, - proofs: Vec, - deferred_proofs: Vec>, - batch_size: usize, - ) -> Vec { - // OPT: If there's only one proof in the last batch, we could push it to the next layer. - // OPT: We could pack deferred proofs into the last chunk if it has less than batch_size proofs. - let chunks: Vec<_> = proofs.chunks(batch_size).collect(); - - let mut reconstruct_challenger = self.setup_initial_core_challenger(vk); - let reconstruct_challengers = chunks - .iter() - .map(|proofs| { - let start_challenger = reconstruct_challenger.clone(); - for proof in proofs.iter() { - match proof { - SP1ReduceProofWrapper::Core(reduce_proof) => { - reconstruct_challenger - .observe(reduce_proof.proof.commitment.main_commit); - reconstruct_challenger.observe_slice( - &reduce_proof.proof.public_values.to_vec() - [0..self.core_machine.num_pv_elts()], - ); - } - SP1ReduceProofWrapper::Recursive(reduce_proof) => { - let pv = RecursionPublicValues::from_vec( - reduce_proof.proof.public_values.clone(), - ); - pv.end_reconstruct_challenger - .set_challenger(&mut reconstruct_challenger); - } - } - } - start_challenger - }) - .collect::>(); - let start_states = chunks - .iter() - .map(|chunk| match chunk[0] { - SP1ReduceProofWrapper::Core(ref proof) => { - ReduceState::from_core_start_state(&proof.proof) - } - SP1ReduceProofWrapper::Recursive(ref proof) => { - ReduceState::from_reduce_start_state(proof) - } - }) - .collect::>(); + // Run the deferred proofs programs. + for input in deferred_inputs { + let mut runtime = RecursionRuntime::, Challenge, _>::new( + &self.deferred_program, + self.compress_machine.config().perm.clone(), + ); - // This is the last layer only if the outcome is a single proof. If there are deferred - // proofs, it's not the last layer. - let is_complete = chunks.len() == 1 && deferred_proofs.is_empty(); - let mut new_proofs: Vec = chunks - .into_par_iter() - .zip(reconstruct_challengers.into_par_iter()) - .zip(start_states.into_par_iter()) - .map(|((chunk, reconstruct_challenger), start_state)| { - let config = InnerSC::default(); - let proof = self.verify_batch( - config, - &self.compress_pk, - vk, - sp1_challenger.clone(), - reconstruct_challenger, - start_state, - chunk, - &[], - is_complete, - false, - false, - ); - SP1ReduceProofWrapper::Recursive(proof) - }) - .collect(); - - // If there are deferred proofs, we want to add them to the end. - // Here we get the end state of the last proof from above which will be the start state for - // the deferred proofs. When verifying only deferred proofs, only reconstruct_deferred_digests - // should change. - let last_new_proof = &new_proofs[new_proofs.len() - 1]; - let mut reduce_state: ReduceState = match last_new_proof { - SP1ReduceProofWrapper::Recursive(ref proof) => { - ReduceState::from_reduce_end_state(proof) - } - _ => unreachable!(), - }; - let deferred_chunks: Vec<_> = deferred_proofs.chunks(batch_size).collect(); + let mut witness_stream = Vec::new(); + witness_stream.extend(input.write()); - // For each reduce, we need to pass in the start state from the previous proof. Here we - // need to compute updated reconstruct_deferred_digests since each proof is modifying it. - let start_states = deferred_chunks - .iter() - .map(|chunk| { - let start_state = reduce_state.clone(); - // Accumulate each deferred proof into the digest - reduce_state.reconstruct_deferred_digest = - Self::hash_deferred_proofs(reduce_state.reconstruct_deferred_digest, chunk); - start_state - }) - .collect::>(); + runtime.witness_stream = witness_stream.into(); + runtime.run(); + runtime.print_stats(); - let new_deferred_proofs = deferred_chunks + records.push((runtime.record, ReduceProgramType::Deferred)); + } + + // Prove all recursion programs and recursion deferred programs and verify the proofs. + + // Make the recursive proofs for core and deferred proofs. + let first_layer_proofs = records .into_par_iter() - .zip(start_states.into_par_iter()) - .map(|(proofs, state)| { - let config = InnerSC::default(); - self.verify_batch::( - config, - &self.compress_pk, - vk, - sp1_challenger.clone(), - reconstruct_challenger.clone(), - state, - &[], - proofs, - false, - false, - false, + .map(|(record, kind)| { + let pk = match kind { + ReduceProgramType::Core => &self.rec_pk, + ReduceProgramType::Deferred => &self.deferred_pk, + ReduceProgramType::Reduce => unreachable!(), + }; + let mut recursive_challenger = self.compress_machine.config().challenger(); + ( + self.compress_machine.prove::>( + pk, + record, + &mut recursive_challenger, + ), + kind, ) }) .collect::>(); - new_proofs.extend( - new_deferred_proofs - .into_iter() - .map(SP1ReduceProofWrapper::Recursive), - ); - new_proofs + // Chain all the individual shard proofs. + let mut reduce_proofs = first_layer_proofs + .into_iter() + .flat_map(|(proof, kind)| proof.shard_proofs.into_iter().map(move |p| (p, kind))) + .collect::>(); + + // Iterate over the recursive proof batches until there is one proof remaining. + let mut is_complete; + loop { + tracing::debug!("Recursive proof layer size: {}", reduce_proofs.len()); + is_complete = reduce_proofs.len() <= batch_size; + reduce_proofs = reduce_proofs + .par_chunks(batch_size) + .map(|batch| { + let (shard_proofs, kinds) = + batch.iter().cloned().unzip::<_, _, Vec<_>, Vec<_>>(); + + let input = SP1ReduceMemoryLayout { + compress_vk: &self.compress_vk, + recursive_machine: &self.compress_machine, + shard_proofs, + kinds, + is_complete, + }; + + let mut runtime = RecursionRuntime::, Challenge, _>::new( + &self.compress_program, + self.compress_machine.config().perm.clone(), + ); + + let mut witness_stream = Vec::new(); + witness_stream.extend(input.write()); + + runtime.witness_stream = witness_stream.into(); + runtime.run(); + runtime.print_stats(); + + let mut recursive_challenger = self.compress_machine.config().challenger(); + let mut proof = self.compress_machine.prove::>( + &self.compress_pk, + runtime.record, + &mut recursive_challenger, + ); + + debug_assert_eq!(proof.shard_proofs.len(), 1); + (proof.shard_proofs.pop().unwrap(), ReduceProgramType::Reduce) + }) + .collect(); + + if reduce_proofs.len() == 1 { + break; + } + } + debug_assert_eq!(reduce_proofs.len(), 1); + let reduce_proof = reduce_proofs.pop().unwrap(); + + // Restore the prover parameters. + env::set_var(RECONSTRUCT_COMMITMENTS_ENV_VAR, rc); + + SP1ReduceProof { + proof: reduce_proof.0, + } } - /// Verifies a batch of proofs using the recursion prover. - #[instrument(name = "verify_batch", level = "info", skip_all)] - fn verify_batch( - &self, - config: SC, - pk: &StarkProvingKey, - core_vk: &SP1VerifyingKey, - core_challenger: Challenger, - reconstruct_challenger: Challenger, - state: ReduceState, - reduce_proofs: &[SP1ReduceProofWrapper], - deferred_proofs: &[ShardProof], - is_complete: bool, - verifying_compressed_proof: bool, - proving_with_skinny: bool, - ) -> SP1ReduceProof - where - SC: StarkGenericConfig, - SC::Challenger: Clone, - Com: Send + Sync, - PcsProverData: Send + Sync, - ShardMainData: Serialize + DeserializeOwned, - LocalProver>: - Prover>, - LocalProver>: Prover>, - { + /// Wrap a reduce proof into a STARK proven over a SNARK-friendly field. + #[instrument(name = "shrink", level = "info", skip_all)] + pub fn shrink(&self, reduced_proof: SP1ReduceProof) -> SP1ReduceProof { // Setup the prover parameters. let rc = env::var(RECONSTRUCT_COMMITMENTS_ENV_VAR).unwrap_or_default(); env::set_var(RECONSTRUCT_COMMITMENTS_ENV_VAR, "false"); - // Compute inputs. - let is_recursive_flags: Vec = reduce_proofs - .iter() - .map(|p| match p { - SP1ReduceProofWrapper::Core(_) => 0, - SP1ReduceProofWrapper::Recursive(_) => 1, - }) - .collect(); - let chip_quotient_data: Vec> = reduce_proofs - .iter() - .map(|p| match p { - SP1ReduceProofWrapper::Core(reduce_proof) => { - get_chip_quotient_data(&self.core_machine, &reduce_proof.proof) - } - SP1ReduceProofWrapper::Recursive(reduce_proof) => { - if verifying_compressed_proof { - get_chip_quotient_data(&self.shrink_machine, &reduce_proof.proof) - } else { - get_chip_quotient_data(&self.compress_machine, &reduce_proof.proof) - } - } - }) - .collect(); - let sorted_indices: Vec> = reduce_proofs - .iter() - .map(|p| match p { - SP1ReduceProofWrapper::Core(reduce_proof) => { - get_sorted_indices(&self.core_machine, &reduce_proof.proof) - } - SP1ReduceProofWrapper::Recursive(reduce_proof) => { - if verifying_compressed_proof { - get_sorted_indices(&self.shrink_machine, &reduce_proof.proof) - } else { - get_sorted_indices(&self.compress_machine, &reduce_proof.proof) - } - } - }) - .collect(); - let (prep_sorted_indices, prep_domains): (Vec, Vec>) = - get_preprocessed_data(&self.core_machine, &core_vk.vk); - let (reduce_prep_sorted_indices, reduce_prep_domains): (Vec, Vec>) = - get_preprocessed_data(&self.compress_machine, &self.compress_vk); - let (compress_prep_sorted_indices, compress_prep_domains): ( - Vec, - Vec>, - ) = get_preprocessed_data(&self.shrink_machine, &self.shrink_vk); - let deferred_sorted_indices: Vec> = deferred_proofs - .iter() - .map(|proof| get_sorted_indices(&self.compress_machine, proof)) - .collect(); - let deferred_chip_quotient_data: Vec> = deferred_proofs - .iter() - .map(|p| get_chip_quotient_data(&self.compress_machine, p)) - .collect(); - - // Convert the inputs into a witness stream. - let mut witness_stream = Vec::new(); - witness_stream.extend(is_recursive_flags.write()); - witness_stream.extend(chip_quotient_data.write()); - witness_stream.extend(sorted_indices.write()); - witness_stream.extend(core_challenger.write()); - witness_stream.extend(reconstruct_challenger.write()); - witness_stream.extend(prep_sorted_indices.write()); - witness_stream.extend(Hintable::write(&prep_domains)); - witness_stream.extend(reduce_prep_sorted_indices.write()); - witness_stream.extend(Hintable::write(&reduce_prep_domains)); - witness_stream.extend(compress_prep_sorted_indices.write()); - witness_stream.extend(Hintable::write(&compress_prep_domains)); - witness_stream.extend(core_vk.vk.write()); - witness_stream.extend(self.compress_vk.write()); - witness_stream.extend(self.shrink_vk.write()); - witness_stream.extend(state.committed_values_digest.write()); - witness_stream.extend(state.deferred_proofs_digest.write()); - witness_stream.extend(Hintable::write(&state.start_pc)); - witness_stream.extend(Hintable::write(&state.exit_code)); - witness_stream.extend(Hintable::write(&state.start_shard)); - witness_stream.extend(Hintable::write(&state.reconstruct_deferred_digest)); - for proof in reduce_proofs.iter() { - match proof { - SP1ReduceProofWrapper::Core(reduce_proof) => { - witness_stream.extend(reduce_proof.proof.write()); - } - SP1ReduceProofWrapper::Recursive(reduce_proof) => { - witness_stream.extend(reduce_proof.proof.write()); - } - } - } - witness_stream.extend(deferred_chip_quotient_data.write()); - witness_stream.extend(deferred_sorted_indices.write()); - witness_stream.extend(deferred_proofs.to_vec().write()); - let is_complete = if is_complete { 1usize } else { 0 }; - witness_stream.extend(is_complete.write()); - let is_compressed = if verifying_compressed_proof { - 1usize - } else { - 0 + // Make the compress proof. + let input = SP1RootMemoryLayout { + machine: &self.compress_machine, + proof: reduced_proof.proof, + is_reduce: true, }; - witness_stream.extend(is_compressed.write()); - let machine = RecursionAirWideDeg3::machine(InnerSC::default()); + // Run the compress program. let mut runtime = RecursionRuntime::, Challenge, _>::new( - &self.recursion_setup_program, - machine.config().perm.clone(), + &self.shrink_program, + self.shrink_machine.config().perm.clone(), ); - runtime.witness_stream = witness_stream.into(); - runtime.run(); - let mut checkpoint = runtime.memory.clone(); - let checkpoint_uninit = runtime.uninitialized_memory.clone(); - // Execute runtime. - let machine = RecursionAirWideDeg3::machine(InnerSC::default()); - let mut runtime = RecursionRuntime::, Challenge, _>::new( - &self.recursion_program, - machine.config().perm.clone(), - ); - checkpoint.iter_mut().for_each(|e| { - e.1.timestamp = BabyBear::zero(); - }); - runtime.memory = checkpoint; - runtime.uninitialized_memory = checkpoint_uninit; + let mut witness_stream = Vec::new(); + witness_stream.extend(input.write()); + + runtime.witness_stream = witness_stream.into(); runtime.run(); runtime.print_stats(); - tracing::info!( - "runtime summary: cycles={}, nb_poseidons={}", - runtime.timestamp, - runtime.nb_poseidons - ); + tracing::debug!("Compress program executed successfully"); - // Generate proof. - let start = Instant::now(); - let proof = if proving_with_skinny && verifying_compressed_proof { - let machine = RecursionAirSkinnyDeg7::wrap_machine(config); - let mut challenger = machine.config().challenger(); - machine.prove::>(pk, runtime.record.clone(), &mut challenger) - } else if proving_with_skinny { - let machine = RecursionAirSkinnyDeg7::machine(config); - let mut challenger = machine.config().challenger(); - machine.prove::>(pk, runtime.record.clone(), &mut challenger) - } else { - let machine = RecursionAirWideDeg3::machine(config); - let mut challenger = machine.config().challenger(); - machine.prove::>(pk, runtime.record.clone(), &mut challenger) - }; - let elapsed = start.elapsed().as_secs_f64(); - - let proof_size = bincode::serialize(&proof).unwrap().len(); - tracing::info!( - "proving summary: cycles={}, e2e={}, khz={:.2}, proofSize={}", - runtime.timestamp, - elapsed, - (runtime.timestamp as f64 / elapsed) / 1000f64, - Size::from_bytes(proof_size), + // Prove the compress program. + let mut compress_challenger = self.shrink_machine.config().challenger(); + let mut compress_proof = self.shrink_machine.prove::>( + &self.shrink_pk, + runtime.record, + &mut compress_challenger, ); // Restore the prover parameters. env::set_var(RECONSTRUCT_COMMITMENTS_ENV_VAR, rc); - // Return the reduced proof. - assert!(proof.shard_proofs.len() == 1); - let proof = proof.shard_proofs.into_iter().next().unwrap(); - SP1ReduceProof { proof } - } - - /// Shrink a compressed proof into a STARK proven over a SNARK-friendly field. - #[instrument(name = "shrink", level = "info", skip_all)] - pub fn shrink( - &self, - vk: &SP1VerifyingKey, - reduced_proof: SP1ReduceProof, - ) -> SP1ReduceProof { - // Get verify_start_challenger from the reduce proof's public values. - let pv = RecursionPublicValues::from_vec(reduced_proof.proof.public_values.clone()); - let mut core_challenger = self.core_machine.config().challenger(); - pv.verify_start_challenger - .set_challenger(&mut core_challenger); - // Since the proof passed in should be complete already, the start reconstruct_challenger - // should be in initial state with only vk observed. - let reconstruct_challenger = self.setup_initial_core_challenger(vk); - let state = ReduceState::from_reduce_start_state(&reduced_proof); - let config = InnerSC::compressed(); - self.verify_batch::( - config, - &self.shrink_pk, - vk, - core_challenger, - reconstruct_challenger, - state, - &[SP1ReduceProofWrapper::Recursive(reduced_proof)], - &[], - true, - false, - true, - ) + SP1ReduceProof { + proof: compress_proof.shard_proofs.pop().unwrap(), + } } /// Wrap a reduce proof into a STARK proven over a SNARK-friendly field. #[instrument(name = "wrap_bn254", level = "info", skip_all)] - pub fn wrap_bn254( - &self, - vk: &SP1VerifyingKey, - reduced_proof: SP1ReduceProof, - ) -> SP1ReduceProof { - // Get verify_start_challenger from the reduce proof's public values. - let pv = RecursionPublicValues::from_vec(reduced_proof.proof.public_values.clone()); - let mut core_challenger = self.core_machine.config().challenger(); - pv.verify_start_challenger - .set_challenger(&mut core_challenger); - // Since the proof passed in should be complete already, the start reconstruct_challenger - // should be in initial state with only vk observed. - let reconstruct_challenger = self.setup_initial_core_challenger(vk); - let state = ReduceState::from_reduce_start_state(&reduced_proof); - let config = OuterSC::default(); - self.verify_batch::( - config, + pub fn wrap_bn254(&self, compressed_proof: SP1ReduceProof) -> SP1ReduceProof { + // Setup the prover parameters. + let rc = env::var(RECONSTRUCT_COMMITMENTS_ENV_VAR).unwrap_or_default(); + env::set_var(RECONSTRUCT_COMMITMENTS_ENV_VAR, "false"); + + let input = SP1RootMemoryLayout { + machine: &self.shrink_machine, + proof: compressed_proof.proof, + is_reduce: false, + }; + + // Run the compress program. + let mut runtime = RecursionRuntime::, Challenge, _>::new( + &self.wrap_program, + self.shrink_machine.config().perm.clone(), + ); + + let mut witness_stream = Vec::new(); + witness_stream.extend(input.write()); + + runtime.witness_stream = witness_stream.into(); + runtime.run(); + runtime.print_stats(); + tracing::debug!("Wrap program executed successfully"); + + // Prove the wrap program. + let mut wrap_challenger = self.wrap_machine.config().challenger(); + let time = std::time::Instant::now(); + let mut wrap_proof = self.wrap_machine.prove::>( &self.wrap_pk, - vk, - core_challenger, - reconstruct_challenger, - state, - &[SP1ReduceProofWrapper::Recursive(reduced_proof)], - &[], - true, - true, - true, - ) + runtime.record, + &mut wrap_challenger, + ); + let elapsed = time.elapsed(); + tracing::debug!("Wrap proving time: {:?}", elapsed); + let mut wrap_challenger = self.wrap_machine.config().challenger(); + let result = self + .wrap_machine + .verify(&self.wrap_vk, &wrap_proof, &mut wrap_challenger); + match result { + Ok(_) => tracing::info!("Proof verified successfully"), + Err(MachineVerificationError::NonZeroCumulativeSum) => { + tracing::info!("Proof verification failed: NonZeroCumulativeSum") + } + e => panic!("Proof verification failed: {:?}", e), + } + tracing::info!("Wrapping successful"); + + // Restore the prover parameters. + env::set_var(RECONSTRUCT_COMMITMENTS_ENV_VAR, rc); + + SP1ReduceProof { + proof: wrap_proof.shard_proofs.pop().unwrap(), + } } /// Wrap the STARK proven over a SNARK-friendly field into a Groth16 proof. @@ -709,25 +599,22 @@ impl SP1Prover { PlonkBn254Prover::prove(witness, build_dir) } - pub fn setup_initial_core_challenger(&self, vk: &SP1VerifyingKey) -> Challenger { - let mut core_challenger = self.core_machine.config().challenger(); - vk.vk.observe_into(&mut core_challenger); - core_challenger - } - - pub fn setup_core_challenger( - &self, - vk: &SP1VerifyingKey, - proof: &SP1CoreProofData, - ) -> Challenger { - let mut core_challenger = self.setup_initial_core_challenger(vk); - for shard_proof in proof.0.iter() { - core_challenger.observe(shard_proof.commitment.main_commit); - core_challenger.observe_slice( - &shard_proof.public_values.to_vec()[0..self.core_machine.num_pv_elts()], + /// Accumulate deferred proofs into a single digest. + fn hash_deferred_proofs( + prev_digest: [Val; DIGEST_SIZE], + deferred_proofs: &[ShardProof], + ) -> [Val; 8] { + let mut digest = prev_digest; + for proof in deferred_proofs.iter() { + let pv: &RecursionPublicValues> = proof.public_values.as_slice().borrow(); + let committed_values_digest = words_to_bytes(&pv.committed_value_digest); + digest = hash_deferred_proof( + &digest, + &pv.sp1_vk_digest, + &committed_values_digest.try_into().unwrap(), ); } - core_challenger + digest } } @@ -743,13 +630,10 @@ mod tests { use p3_field::PrimeField32; use serial_test::serial; use sp1_core::io::SP1Stdin; - use sp1_core::stark::MachineVerificationError; use sp1_core::utils::setup_logger; /// Tests an end-to-end workflow of proving a program across the entire proof generation /// pipeline. - /// - /// TODO: Remove the fact that we ignore [MachineVerificationError::NonZeroCumulativeSum]. #[test] #[serial] fn test_e2e() { @@ -773,26 +657,16 @@ mod tests { let compressed_proof = prover.compress(&vk, core_proof, vec![]); tracing::info!("verify compressed"); - let result = prover.verify_compressed(&compressed_proof, &vk); - if let Err(MachineVerificationError::NonZeroCumulativeSum) = result { - tracing::warn!("non-zero cumulative sum for compress"); - } else { - result.unwrap(); - } + prover.verify_compressed(&compressed_proof, &vk).unwrap(); tracing::info!("shrink"); - let shrink_proof = prover.shrink(&vk, compressed_proof); + let shrink_proof = prover.shrink(compressed_proof); tracing::info!("verify shrink"); - let result = prover.verify_shrink(&shrink_proof, &vk); - if let Err(MachineVerificationError::NonZeroCumulativeSum) = result { - tracing::warn!("non-zero cumulative sum for shrink"); - } else { - result.unwrap(); - } + prover.verify_shrink(&shrink_proof, &vk).unwrap(); tracing::info!("wrap bn254"); - let wrapped_bn254_proof = prover.wrap_bn254(&vk, shrink_proof); + let wrapped_bn254_proof = prover.wrap_bn254(shrink_proof); let bytes = bincode::serialize(&wrapped_bn254_proof).unwrap(); // Save the proof. @@ -807,12 +681,7 @@ mod tests { let wrapped_bn254_proof = bincode::deserialize(&bytes).unwrap(); tracing::info!("verify wrap bn254"); - let result = prover.verify_wrap_bn254(&wrapped_bn254_proof, &vk); - if let Err(MachineVerificationError::NonZeroCumulativeSum) = result { - tracing::warn!("non-zero cumulative sum for wrap bn254"); - } else { - result.unwrap(); - } + prover.verify_wrap_bn254(&wrapped_bn254_proof, &vk).unwrap(); tracing::info!("checking vkey hash babybear"); let vk_digest_babybear = wrapped_bn254_proof.sp1_vkey_digest_babybear(); @@ -831,8 +700,6 @@ mod tests { /// Tests an end-to-end workflow of proving a program across the entire proof generation /// pipeline in addition to verifying deferred proofs. - /// - /// TODO: Remove the fact that we ignore [MachineVerificationError::NonZeroCumulativeSum]. #[test] #[serial] fn test_e2e_with_deferred_proofs() { @@ -907,13 +774,14 @@ mod tests { deferred_reduce_2.proof, ], ); + let reduce_pv: &RecursionPublicValues<_> = + verify_reduce.proof.public_values.as_slice().borrow(); + println!("deferred_hash: {:?}", reduce_pv.deferred_proofs_digest); + println!("complete: {:?}", reduce_pv.is_complete); tracing::info!("verify verify program"); - let result = prover.verify_compressed(&verify_reduce, &verify_vk); - if let Err(MachineVerificationError::NonZeroCumulativeSum) = result { - tracing::warn!("non-zero cumulative sum for verify"); - } else { - result.unwrap(); - } + prover + .verify_compressed(&verify_reduce, &verify_vk) + .unwrap(); } } diff --git a/prover/src/types.rs b/prover/src/types.rs index 9f9dbc86d2..547eac7707 100644 --- a/prover/src/types.rs +++ b/prover/src/types.rs @@ -1,3 +1,4 @@ +use std::borrow::Borrow; use std::{fs::File, path::Path}; use anyhow::Result; @@ -8,9 +9,8 @@ use p3_field::PrimeField; use p3_field::{AbstractField, PrimeField32, TwoAdicField}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sp1_core::{ - air::{PublicValues, Word, POSEIDON_NUM_WORDS, PV_DIGEST_NUM_WORDS}, io::{SP1PublicValues, SP1Stdin}, - stark::{ShardProof, StarkGenericConfig, StarkProvingKey, StarkVerifyingKey, Val}, + stark::{ShardProof, StarkGenericConfig, StarkProvingKey, StarkVerifyingKey}, utils::DIGEST_SIZE, }; use sp1_primitives::poseidon2_hash; @@ -37,7 +37,11 @@ pub struct SP1VerifyingKey { /// A trait for keys that can be hashed into a digest. pub trait HashableKey { - fn hash_babybear(&self) -> [BabyBear; 8]; + /// Hash the key into a digest of BabyBear elements. + fn hash_babybear(&self) -> [BabyBear; DIGEST_SIZE]; + + /// Hash the key into a digest of u32 elements. + fn hash_u32(&self) -> [u32; DIGEST_SIZE]; fn hash_bn254(&self) -> Bn254Fr { babybears_to_bn254(&self.hash_babybear()) @@ -51,20 +55,18 @@ pub trait HashableKey { ) } - fn hash_u32(&self) -> [u32; 8]; - - /// Hash the key into a digest of 8 u32 elements. - fn hash_bytes(&self) -> [u8; 32] { + /// Hash the key into a digest of bytes elements. + fn hash_bytes(&self) -> [u8; DIGEST_SIZE * 4] { words_to_bytes_be(&self.hash_u32()) } } impl HashableKey for SP1VerifyingKey { - fn hash_babybear(&self) -> [BabyBear; 8] { + fn hash_babybear(&self) -> [BabyBear; DIGEST_SIZE] { self.vk.hash_babybear() } - fn hash_u32(&self) -> [u32; 8] { + fn hash_u32(&self) -> [u32; DIGEST_SIZE] { self.vk.hash_u32() } } @@ -74,7 +76,7 @@ impl>::Commitment: AsRef<[BabyBear; DIGEST_SIZE]>, { - fn hash_babybear(&self) -> [BabyBear; 8] { + fn hash_babybear(&self) -> [BabyBear; DIGEST_SIZE] { let prep_domains = self.chip_information.iter().map(|(_, domain, _)| domain); let num_inputs = DIGEST_SIZE + 1 + (4 * prep_domains.len()); let mut inputs = Vec::with_capacity(num_inputs); @@ -164,29 +166,10 @@ pub struct SP1ReduceProof { pub proof: ShardProof, } -/// A proof that can be reduced along with other proofs into one proof. -#[derive(Serialize, Deserialize)] -pub enum SP1ReduceProofWrapper { - Core(SP1ReduceProof), - Recursive(SP1ReduceProof), -} - -/// Represents the state of reducing proofs together. This is used to track the current values since -/// some reduce batches may have only deferred proofs. -#[derive(Clone)] -pub(crate) struct ReduceState { - pub committed_values_digest: [Word>; PV_DIGEST_NUM_WORDS], - pub deferred_proofs_digest: [Val; POSEIDON_NUM_WORDS], - pub start_pc: Val, - pub exit_code: Val, - pub start_shard: Val, - pub reconstruct_deferred_digest: [Val; POSEIDON_NUM_WORDS], -} - impl SP1ReduceProof { pub fn sp1_vkey_digest_babybear(&self) -> [BabyBear; 8] { let proof = &self.proof; - let pv = RecursionPublicValues::from_vec(proof.public_values.clone()); + let pv: &RecursionPublicValues = proof.public_values.as_slice().borrow(); pv.sp1_vk_digest } @@ -196,7 +179,7 @@ impl SP1ReduceProof { pub fn sp1_commited_values_digest_bn254(&self) -> Bn254Fr { let proof = &self.proof; - let pv = RecursionPublicValues::from_vec(proof.public_values.clone()); + let pv: &RecursionPublicValues = proof.public_values.as_slice().borrow(); let committed_values_digest_bytes: [BabyBear; 32] = words_to_bytes(&pv.committed_value_digest) .try_into() @@ -205,46 +188,9 @@ impl SP1ReduceProof { } } -impl ReduceState { - pub fn from_reduce_end_state>( - state: &SP1ReduceProof, - ) -> Self { - let pv = RecursionPublicValues::from_vec(state.proof.public_values.clone()); - Self { - committed_values_digest: pv.committed_value_digest, - deferred_proofs_digest: pv.deferred_proofs_digest, - start_pc: pv.next_pc, - exit_code: pv.exit_code, - start_shard: pv.next_shard, - reconstruct_deferred_digest: pv.end_reconstruct_deferred_digest, - } - } - - pub fn from_reduce_start_state>( - state: &SP1ReduceProof, - ) -> Self { - let pv = RecursionPublicValues::from_vec(state.proof.public_values.clone()); - Self { - committed_values_digest: pv.committed_value_digest, - deferred_proofs_digest: pv.deferred_proofs_digest, - start_pc: pv.start_pc, - exit_code: pv.exit_code, - start_shard: pv.start_shard, - reconstruct_deferred_digest: pv.start_reconstruct_deferred_digest, - } - } - - pub fn from_core_start_state(state: &ShardProof) -> Self { - let pv = - PublicValues::>, Val>::from_vec(state.public_values.clone()); - Self { - committed_values_digest: pv.committed_value_digest, - deferred_proofs_digest: pv.deferred_proofs_digest, - start_pc: pv.start_pc, - exit_code: pv.exit_code, - start_shard: pv.shard, - // TODO: we assume that core proofs aren't in a later batch than one with a deferred proof - reconstruct_deferred_digest: [BabyBear::zero(); 8], - } - } +/// A proof that can be reduced along with other proofs into one proof. +#[derive(Serialize, Deserialize)] +pub enum SP1ReduceProofWrapper { + Core(SP1ReduceProof), + Recursive(SP1ReduceProof), } diff --git a/prover/src/utils.rs b/prover/src/utils.rs index 8daa6cb6c0..8983efdefe 100644 --- a/prover/src/utils.rs +++ b/prover/src/utils.rs @@ -8,12 +8,10 @@ use p3_bn254_fr::Bn254Fr; use p3_field::AbstractField; use p3_field::PrimeField32; use sp1_core::{ - air::{MachineAir, Word}, + air::Word, io::SP1Stdin, runtime::{Program, Runtime}, - stark::{Dom, ShardProof, StarkGenericConfig, StarkMachine, StarkVerifyingKey, Val}, }; -use sp1_recursion_program::{stark::EMPTY, types::QuotientDataValues}; use crate::SP1CoreProofData; @@ -27,22 +25,6 @@ impl SP1CoreProofData { } } -pub fn get_chip_quotient_data>>( - machine: &StarkMachine, - proof: &ShardProof, -) -> Vec { - machine - .shard_chips_ordered(&proof.chip_ordering) - .map(|chip| { - let log_quotient_degree = chip.log_quotient_degree(); - QuotientDataValues { - log_quotient_degree, - quotient_size: 1 << log_quotient_degree, - } - }) - .collect() -} - /// Get the number of cycles for a given program. pub fn get_cycles(elf: &[u8], stdin: &SP1Stdin) -> u64 { let program = Program::from(elf); @@ -59,37 +41,6 @@ pub fn load_elf(path: &str) -> Result, std::io::Error> { Ok(elf_code) } -pub fn get_sorted_indices>>( - machine: &StarkMachine, - proof: &ShardProof, -) -> Vec { - machine - .chips_sorted_indices(proof) - .into_iter() - .map(|x| match x { - Some(x) => x, - None => EMPTY, - }) - .collect() -} - -pub fn get_preprocessed_data>>( - machine: &StarkMachine, - vk: &StarkVerifyingKey, -) -> (Vec, Vec>) { - let chips = machine.chips(); - let (prep_sorted_indices, prep_domains) = machine - .preprocessed_chip_ids() - .into_iter() - .map(|chip_idx| { - let name = chips[chip_idx].name().clone(); - let prep_sorted_idx = vk.chip_ordering[&name]; - (prep_sorted_idx, vk.chip_information[prep_sorted_idx].1) - }) - .unzip(); - (prep_sorted_indices, prep_domains) -} - pub fn words_to_bytes(words: &[Word]) -> Vec { return words.iter().flat_map(|word| word.0).collect(); } diff --git a/prover/src/verify.rs b/prover/src/verify.rs index a48586651d..9d66971e2a 100644 --- a/prover/src/verify.rs +++ b/prover/src/verify.rs @@ -1,3 +1,5 @@ +use std::borrow::Borrow; + use anyhow::Result; use p3_baby_bear::BabyBear; use p3_field::AbstractField; @@ -99,7 +101,8 @@ impl SP1Prover { .verify(&self.compress_vk, &machine_proof, &mut challenger)?; // Validate public values - let public_values = RecursionPublicValues::from_vec(proof.proof.public_values.clone()); + let public_values: &RecursionPublicValues<_> = + proof.proof.public_values.as_slice().borrow(); // `is_complete` should be 1. In the reduce program, this ensures that the proof is fully reduced. if public_values.is_complete != BabyBear::one() { @@ -118,7 +121,7 @@ impl SP1Prover { // Verify that the reduce program is the one we are expecting. let recursion_vkey_hash = self.compress_vk.hash_babybear(); - if public_values.recursion_vk_digest != recursion_vkey_hash { + if public_values.compress_vk_digest != recursion_vkey_hash { return Err(MachineVerificationError::InvalidPublicValues( "recursion vk hash mismatch", )); @@ -141,7 +144,8 @@ impl SP1Prover { .verify(&self.shrink_vk, &machine_proof, &mut challenger)?; // Validate public values - let public_values = RecursionPublicValues::from_vec(proof.proof.public_values.clone()); + let public_values: &RecursionPublicValues<_> = + proof.proof.public_values.as_slice().borrow(); // `is_complete` should be 1. In the reduce program, this ensures that the proof is fully reduced. if public_values.is_complete != BabyBear::one() { @@ -158,14 +162,6 @@ impl SP1Prover { )); } - // Verify that the reduce program is the one we are expecting. - let recursion_vkey_hash = self.shrink_vk.hash_babybear(); - if public_values.recursion_vk_digest != recursion_vkey_hash { - return Err(MachineVerificationError::InvalidPublicValues( - "recursion vk hash mismatch", - )); - } - Ok(()) } @@ -183,7 +179,8 @@ impl SP1Prover { .verify(&self.wrap_vk, &machine_proof, &mut challenger)?; // Validate public values - let public_values = RecursionPublicValues::from_vec(proof.proof.public_values.clone()); + let public_values: &RecursionPublicValues<_> = + proof.proof.public_values.as_slice().borrow(); // `is_complete` should be 1. In the reduce program, this ensures that the proof is fully reduced. if public_values.is_complete != BabyBear::one() { @@ -200,14 +197,6 @@ impl SP1Prover { )); } - // Verify that the reduce program is the one we are expecting. - let recursion_vkey_hash = self.shrink_vk.hash_babybear(); - if public_values.recursion_vk_digest != recursion_vkey_hash { - return Err(MachineVerificationError::InvalidPublicValues( - "recursion vk hash mismatch", - )); - } - Ok(()) } } diff --git a/recursion/circuit/src/stark.rs b/recursion/circuit/src/stark.rs index 194b262f28..e7c5b530ba 100644 --- a/recursion/circuit/src/stark.rs +++ b/recursion/circuit/src/stark.rs @@ -1,3 +1,4 @@ +use std::borrow::Borrow; use std::marker::PhantomData; use crate::fri::verify_two_adic_pcs; @@ -269,7 +270,7 @@ pub fn build_wrap_circuit( let element = builder.get(&proof.public_values, i); pv_elements.push(element); } - let pv = RecursionPublicValues::from_vec(pv_elements); + let pv: &RecursionPublicValues<_> = pv_elements.as_slice().borrow(); let one_felt: Felt<_> = builder.constant(BabyBear::one()); // Proof must be complete. In the reduce program, this will ensure that the SP1 proof has been // fully accumulated. diff --git a/recursion/compiler/src/ir/builder.rs b/recursion/compiler/src/ir/builder.rs index e8d1fd45a7..e4b562cda6 100644 --- a/recursion/compiler/src/ir/builder.rs +++ b/recursion/compiler/src/ir/builder.rs @@ -227,7 +227,11 @@ impl Builder { } /// Assert that two usizes are not equal. - pub fn assert_usize_ne(&mut self, lhs: SymbolicUsize, rhs: SymbolicUsize) { + pub fn assert_usize_ne( + &mut self, + lhs: impl Into>, + rhs: impl Into>, + ) { self.assert_ne::>(lhs, rhs); } diff --git a/recursion/core/src/air/public_values.rs b/recursion/core/src/air/public_values.rs index 07f2295b32..d84af5e905 100644 --- a/recursion/core/src/air/public_values.rs +++ b/recursion/core/src/air/public_values.rs @@ -9,6 +9,7 @@ use sp1_core::{ air::{Word, POSEIDON_NUM_WORDS}, stark::PROOF_MAX_NUM_PVS, }; +use sp1_derive::AlignedBorrow; use static_assertions::const_assert_eq; use std::mem::size_of; @@ -22,7 +23,8 @@ pub const RECURSIVE_PROOF_NUM_PV_ELTS: usize = size_of:: { pub sponge_state: [T; PERMUTATION_WIDTH], pub num_inputs: T, @@ -32,27 +34,6 @@ pub struct ChallengerPublicValues { } impl ChallengerPublicValues { - pub fn from_vec(data: Vec) -> Self { - if data.len() < CHALLENGER_STATE_NUM_ELTS { - panic!("Invalid number of items in the serialized vector."); - } - - let mut iter = data.iter().cloned(); - let sponge_state = iter.by_ref().take(PERMUTATION_WIDTH).collect::>(); - let num_inputs = iter.next().unwrap(); - let input_buffer = iter.by_ref().take(PERMUTATION_WIDTH).collect::>(); - let num_outputs = iter.next().unwrap(); - let output_buffer = iter.by_ref().take(PERMUTATION_WIDTH).collect::>(); - - Self { - sponge_state: unwrap_into_array(sponge_state), - num_inputs, - input_buffer: unwrap_into_array(input_buffer), - num_outputs, - output_buffer: unwrap_into_array(output_buffer), - } - } - pub fn set_challenger>( &self, challenger: &mut DuplexChallenger, @@ -68,7 +49,8 @@ impl ChallengerPublicValues { } /// The PublicValues struct is used to store all of a reduce proof's public values. -#[derive(Serialize, Deserialize, Clone, Copy, Default, Debug)] +#[derive(AlignedBorrow, Serialize, Deserialize, Clone, Copy, Default, Debug)] +#[repr(C)] pub struct RecursionPublicValues { /// The hash of all the bytes that the program has written to public values. pub committed_value_digest: [Word; PV_DIGEST_NUM_WORDS], @@ -106,11 +88,11 @@ pub struct RecursionPublicValues { /// The commitment to the sp1 program being proven. pub sp1_vk_digest: [T; DIGEST_SIZE], - /// The commitment to the recursion program being proven. - pub recursion_vk_digest: [T; DIGEST_SIZE], + /// The commitment to the compress key being used in recursive verification. + pub compress_vk_digest: [T; DIGEST_SIZE], - /// The commitment to the start program being proven. - pub verify_start_challenger: ChallengerPublicValues, + /// The leaf challenger containing the entropy from the main trace commitment. + pub leaf_challenger: ChallengerPublicValues, /// Current cumulative sum of lookup bus. pub cumulative_sum: [T; 4], @@ -118,68 +100,3 @@ pub struct RecursionPublicValues { /// Whether the proof completely proves the program execution. pub is_complete: T, } - -impl RecursionPublicValues { - /// Convert a vector of field elements into a PublicValues struct. - pub fn from_vec(data: Vec) -> Self { - if data.len() != RECURSIVE_PROOF_NUM_PV_ELTS { - panic!("Invalid number of items in the serialized vector."); - } - - let mut iter = data.iter().cloned(); - let committed_value_digest = (0..PV_DIGEST_NUM_WORDS) - .map(|_| Word::from_iter(iter.by_ref())) - .collect(); - let deferred_proofs_digest = iter.by_ref().take(POSEIDON_NUM_WORDS).collect::>(); - let start_pc = iter.next().unwrap(); - let next_pc = iter.next().unwrap(); - let exit_code = iter.next().unwrap(); - let start_shard = iter.next().unwrap(); - let next_shard = iter.next().unwrap(); - let start_reconstruct_challenger = ChallengerPublicValues::from_vec( - iter.by_ref() - .take(CHALLENGER_STATE_NUM_ELTS) - .collect::>(), - ); - let end_reconstruct_challenger = ChallengerPublicValues::from_vec( - iter.by_ref() - .take(CHALLENGER_STATE_NUM_ELTS) - .collect::>(), - ); - let start_reconstruct_deferred_digest = iter.by_ref().take(DIGEST_SIZE).collect::>(); - let end_reconstruct_deferred_digest = iter.by_ref().take(DIGEST_SIZE).collect::>(); - let sp1_vk_commit = iter.by_ref().take(DIGEST_SIZE).collect::>(); - let recursion_vk_commit = iter.by_ref().take(DIGEST_SIZE).collect::>(); - let verify_start_challenger = ChallengerPublicValues::from_vec( - iter.by_ref() - .take(CHALLENGER_STATE_NUM_ELTS) - .collect::>(), - ); - let cumulative_sum = iter.by_ref().take(4).collect::>(); - let is_complete = iter.next().unwrap(); - - Self { - committed_value_digest: unwrap_into_array(committed_value_digest), - deferred_proofs_digest: unwrap_into_array(deferred_proofs_digest), - start_pc, - next_pc, - exit_code, - start_shard, - next_shard, - start_reconstruct_challenger, - end_reconstruct_challenger, - start_reconstruct_deferred_digest: unwrap_into_array(start_reconstruct_deferred_digest), - end_reconstruct_deferred_digest: unwrap_into_array(end_reconstruct_deferred_digest), - sp1_vk_digest: unwrap_into_array(sp1_vk_commit), - recursion_vk_digest: unwrap_into_array(recursion_vk_commit), - verify_start_challenger, - cumulative_sum: unwrap_into_array(cumulative_sum), - is_complete, - } - } -} - -/// Convert a vector into an array, panicking if the length is incorrect. -fn unwrap_into_array(input: Vec) -> [T; N] { - input.try_into().unwrap() -} diff --git a/recursion/core/src/fri_fold/mod.rs b/recursion/core/src/fri_fold/mod.rs index f9bcd82508..02ef07db1d 100644 --- a/recursion/core/src/fri_fold/mod.rs +++ b/recursion/core/src/fri_fold/mod.rs @@ -23,7 +23,7 @@ use crate::runtime::{ExecutionRecord, RecursionProgram}; pub const NUM_FRI_FOLD_COLS: usize = core::mem::size_of::>(); #[derive(Default)] -pub struct FriFoldChip { +pub struct FriFoldChip { pub fixed_log2_rows: Option, } @@ -83,13 +83,13 @@ pub struct FriFoldCols { pub is_real: T, } -impl BaseAir for FriFoldChip { +impl BaseAir for FriFoldChip { fn width(&self) -> usize { NUM_FRI_FOLD_COLS } } -impl MachineAir for FriFoldChip { +impl MachineAir for FriFoldChip { type Record = ExecutionRecord; type Program = RecursionProgram; @@ -167,7 +167,7 @@ impl MachineAir for FriFoldChip { } } -impl FriFoldChip { +impl FriFoldChip { pub fn eval_fri_fold( &self, builder: &mut AB, @@ -176,6 +176,16 @@ impl FriFoldChip { receive_table: AB::Var, memory_access: AB::Var, ) { + // Dummy constraints to normalize to DEGREE when DEGREE > 3. + if DEGREE > 3 { + let lhs = (0..DEGREE) + .map(|_| local.is_real.into()) + .product::(); + let rhs = (0..DEGREE) + .map(|_| local.is_real.into()) + .product::(); + builder.assert_eq(lhs, rhs); + } // Constraint that the operands are sent from the CPU table. let first_iteration_clk = local.clk.into() - local.m.into(); let total_num_iterations = local.m.into() + AB::Expr::one(); @@ -358,7 +368,7 @@ impl FriFoldChip { } } -impl Air for FriFoldChip +impl Air for FriFoldChip where AB: SP1RecursionAirBuilder, { diff --git a/recursion/core/src/multi/mod.rs b/recursion/core/src/multi/mod.rs index 5904de276c..f0acb982ce 100644 --- a/recursion/core/src/multi/mod.rs +++ b/recursion/core/src/multi/mod.rs @@ -17,7 +17,7 @@ use crate::runtime::{ExecutionRecord, RecursionProgram}; pub const NUM_MULTI_COLS: usize = core::mem::size_of::>(); #[derive(Default)] -pub struct MultiChip { +pub struct MultiChip { pub fixed_log2_rows: Option, } @@ -42,13 +42,13 @@ pub union InstructionSpecificCols { poseidon2: Poseidon2Cols, } -impl BaseAir for MultiChip { +impl BaseAir for MultiChip { fn width(&self) -> usize { NUM_MULTI_COLS } } -impl MachineAir for MultiChip { +impl MachineAir for MultiChip { type Record = ExecutionRecord; type Program = RecursionProgram; @@ -66,7 +66,7 @@ impl MachineAir for MultiChip { input: &ExecutionRecord, output: &mut ExecutionRecord, ) -> RowMajorMatrix { - let fri_fold_chip = FriFoldChip::default(); + let fri_fold_chip = FriFoldChip::<3>::default(); let poseidon2 = Poseidon2Chip::default(); let fri_fold_trace = fri_fold_chip.generate_trace(input, output); let mut poseidon2_trace = poseidon2.generate_trace(input, output); @@ -84,8 +84,10 @@ impl MachineAir for MultiChip { cols.is_fri_fold = F::one(); let fri_fold_cols = *cols.fri_fold(); - cols.fri_fold_receive_table = FriFoldChip::do_receive_table(&fri_fold_cols); - cols.fri_fold_memory_access = FriFoldChip::do_memory_access(&fri_fold_cols); + cols.fri_fold_receive_table = + FriFoldChip::<3>::do_receive_table(&fri_fold_cols); + cols.fri_fold_memory_access = + FriFoldChip::<3>::do_memory_access(&fri_fold_cols); } else { cols.is_poseidon2 = F::one(); @@ -113,7 +115,7 @@ impl MachineAir for MultiChip { } } -impl Air for MultiChip +impl Air for MultiChip where AB: SP1RecursionAirBuilder, { @@ -155,15 +157,15 @@ where let fri_columns_local = local.fri_fold(); sub_builder.assert_eq( - local.is_fri_fold * FriFoldChip::do_memory_access::(fri_columns_local), + local.is_fri_fold * FriFoldChip::<3>::do_memory_access::(fri_columns_local), local.fri_fold_memory_access, ); sub_builder.assert_eq( - local.is_fri_fold * FriFoldChip::do_receive_table::(fri_columns_local), + local.is_fri_fold * FriFoldChip::<3>::do_receive_table::(fri_columns_local), local.fri_fold_receive_table, ); - let fri_fold_chip = FriFoldChip::default(); + let fri_fold_chip = FriFoldChip::<3>::default(); fri_fold_chip.eval_fri_fold( &mut sub_builder, local.fri_fold(), @@ -234,7 +236,7 @@ mod tests { let config = BabyBearPoseidon2::compressed(); let mut challenger = config.challenger(); - let chip = MultiChip { + let chip = MultiChip::<5> { fixed_log2_rows: None, }; diff --git a/recursion/core/src/program/mod.rs b/recursion/core/src/program/mod.rs index bc285ac7c8..e151844ee0 100644 --- a/recursion/core/src/program/mod.rs +++ b/recursion/core/src/program/mod.rs @@ -81,7 +81,7 @@ impl MachineAir for ProgramChip { pad_rows_fixed( &mut rows, || [F::zero(); NUM_PROGRAM_PREPROCESSED_COLS], - Some(20), + None, ); // Convert the trace to a row major matrix. @@ -130,7 +130,7 @@ impl MachineAir for ProgramChip { .collect::>(); // Pad the trace to a power of two. - pad_rows_fixed(&mut rows, || [F::zero(); NUM_PROGRAM_MULT_COLS], Some(20)); + pad_rows_fixed(&mut rows, || [F::zero(); NUM_PROGRAM_MULT_COLS], None); // Convert the trace to a row major matrix. RowMajorMatrix::new( diff --git a/recursion/core/src/stark/mod.rs b/recursion/core/src/stark/mod.rs index f02303adcc..2e97eec030 100644 --- a/recursion/core/src/stark/mod.rs +++ b/recursion/core/src/stark/mod.rs @@ -29,9 +29,9 @@ pub enum RecursionAir, const DEGREE: u MemoryGlobal(MemoryGlobalChip), Poseidon2Wide(Poseidon2WideChip), Poseidon2Skinny(Poseidon2Chip), - FriFold(FriFoldChip), + FriFold(FriFoldChip), RangeCheck(RangeCheckChip), - Multi(MultiChip), + Multi(MultiChip), } impl, const DEGREE: usize> RecursionAir { @@ -67,7 +67,7 @@ impl, const DEGREE: usize> RecursionAi > { fixed_log2_rows: None, }))) - .chain(once(RecursionAir::FriFold(FriFoldChip { + .chain(once(RecursionAir::FriFold(FriFoldChip:: { fixed_log2_rows: None, }))) .chain(once(RecursionAir::RangeCheck(RangeCheckChip::default()))) diff --git a/recursion/core/src/stark/utils.rs b/recursion/core/src/stark/utils.rs index 92c59508ca..9321d82288 100644 --- a/recursion/core/src/stark/utils.rs +++ b/recursion/core/src/stark/utils.rs @@ -1,4 +1,3 @@ -use crate::stark::RecursionAirWideDeg3; use p3_baby_bear::BabyBear; use sp1_core::stark::StarkGenericConfig; use sp1_core::utils; @@ -7,6 +6,7 @@ use sp1_core::utils::BabyBearPoseidon2; use crate::air::Block; use crate::runtime::RecursionProgram; use crate::runtime::Runtime; +use crate::stark::RecursionAir; use crate::stark::RecursionAirSkinnyDeg7; use p3_field::PrimeField32; use sp1_core::utils::run_test_machine; @@ -44,7 +44,7 @@ pub fn run_test_recursion( ); if test_config == TestConfig::All || test_config == TestConfig::WideDeg3 { - let machine = RecursionAirWideDeg3::machine(BabyBearPoseidon2::default()); + let machine = RecursionAir::<_, 3>::machine(BabyBearPoseidon2::default()); let (pk, vk) = machine.setup(&program); let record = runtime.record.clone(); let result = run_test_machine(record, machine, pk, vk); diff --git a/recursion/program/Cargo.toml b/recursion/program/Cargo.toml index 002b9cb815..a3069a92ad 100644 --- a/recursion/program/Cargo.toml +++ b/recursion/program/Cargo.toml @@ -11,6 +11,7 @@ p3-field = { workspace = true } p3-commit = { workspace = true } p3-fri = { workspace = true } p3-matrix = { workspace = true } +p3-maybe-rayon = { workspace = true } p3-util = { workspace = true } p3-symmetric = { workspace = true } p3-challenger = { workspace = true } @@ -26,7 +27,3 @@ serde = { version = "1.0.197", features = ["derive"] } rand = "0.8.4" array-macro = "2.1.8" tracing = "0.1.40" - -[[bin]] -name = "fri_sweep" -path = "experiments/fri_sweep.rs" diff --git a/recursion/program/experiments/.gitignore b/recursion/program/experiments/.gitignore deleted file mode 100644 index 872aa273a4..0000000000 --- a/recursion/program/experiments/.gitignore +++ /dev/null @@ -1 +0,0 @@ -results \ No newline at end of file diff --git a/recursion/program/experiments/fri_sweep.rs b/recursion/program/experiments/fri_sweep.rs deleted file mode 100644 index 6fe66748f3..0000000000 --- a/recursion/program/experiments/fri_sweep.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Sweeps end-to-end prover performance across a wide range of parameters for Fibonacci. - -#![feature(generic_const_exprs)] -#![allow(incomplete_features)] - -use std::{fs::File, io::BufWriter, io::Write}; - -use itertools::iproduct; -use sp1_core::utils::{inner_perm, InnerChallenge, InnerVal}; -use sp1_recursion_core::runtime::Runtime; -use sp1_recursion_program::fri::two_adic_pcs::tests::build_test_fri_with_cols_and_log2_rows; - -fn main() { - // Setup sweep. - let columns = [10, 50, 100, 200, 400]; - let log2_rows = [18, 19, 20, 21, 22, 23]; - - let mut lines = vec!["columns,log2_rows,cycles".to_string()]; - for (columns, log2_rows) in iproduct!(columns, log2_rows) { - println!("running: columns={}, log2_rows={}", columns, log2_rows); - let (program, witness) = build_test_fri_with_cols_and_log2_rows(columns, log2_rows); - let mut runtime = Runtime::::new(&program, inner_perm()); - runtime.witness_stream = witness; - runtime.run(); - lines.push(format!("{},{},{}", columns, log2_rows, runtime.timestamp)); - } - - let file = File::create("results/fri_sweep.csv").unwrap(); - let mut writer = BufWriter::new(file); - for line in lines.clone() { - writeln!(writer, "{}", line).unwrap(); - } - - println!("{:#?}", lines); -} diff --git a/recursion/program/src/challenger.rs b/recursion/program/src/challenger.rs index 86942bfe5a..6a05cca8e3 100644 --- a/recursion/program/src/challenger.rs +++ b/recursion/program/src/challenger.rs @@ -7,8 +7,9 @@ use sp1_recursion_compiler::prelude::{Array, Builder, Config, DslVariable, Ext, use sp1_recursion_core::runtime::{DIGEST_SIZE, PERMUTATION_WIDTH}; use crate::fri::types::DigestVariable; -use crate::utils::felt2var; +use crate::types::VerifyingKeyVariable; +/// Reference: [p3_challenger::CanObserve]. pub trait CanObserveVariable { fn observe(&mut self, builder: &mut Builder, value: V); @@ -19,6 +20,7 @@ pub trait CanSampleVariable { fn sample(&mut self, builder: &mut Builder) -> V; } +/// Reference: [p3_challenger::FieldChallenger]. pub trait FeltChallenger: CanObserveVariable> + CanSampleVariable> + CanSampleBitsVariable { @@ -90,23 +92,17 @@ impl DuplexChallengerVariable { builder.range(0, PERMUTATION_WIDTH).for_each(|i, builder| { let element = builder.get(&self.sponge_state, i); let other_element = builder.get(&other.sponge_state, i); - let element = felt2var(builder, element); - let other_element = felt2var(builder, other_element); - builder.assert_var_eq(element, other_element); + builder.assert_felt_eq(element, other_element); }); builder.range(0, self.nb_inputs).for_each(|i, builder| { let element = builder.get(&self.input_buffer, i); let other_element = builder.get(&other.input_buffer, i); - let element = felt2var(builder, element); - let other_element = felt2var(builder, other_element); - builder.assert_var_eq(element, other_element); + builder.assert_felt_eq(element, other_element); }); builder.range(0, self.nb_outputs).for_each(|i, builder| { let element = builder.get(&self.output_buffer, i); let other_element = builder.get(&other.output_buffer, i); - let element = felt2var(builder, element); - let other_element = felt2var(builder, other_element); - builder.assert_var_eq(element, other_element); + builder.assert_felt_eq(element, other_element); }); } @@ -271,6 +267,21 @@ impl CanObserveVariable> for DuplexChallengerVar } } +impl CanObserveVariable> for DuplexChallengerVariable { + fn observe(&mut self, builder: &mut Builder, value: VerifyingKeyVariable) { + self.observe_commitment(builder, value.commitment); + self.observe(builder, value.pc_start) + } + + fn observe_slice( + &mut self, + _builder: &mut Builder, + _values: Array>, + ) { + todo!() + } +} + impl FeltChallenger for DuplexChallengerVariable { fn sample_ext(&mut self, builder: &mut Builder) -> Ext { DuplexChallengerVariable::sample_ext(self, builder) diff --git a/recursion/program/src/fri/domain.rs b/recursion/program/src/fri/domain.rs index 01a821eae7..f3f20c7a76 100644 --- a/recursion/program/src/fri/domain.rs +++ b/recursion/program/src/fri/domain.rs @@ -215,7 +215,7 @@ pub(crate) mod tests { // Initialize a builder. let mut builder = AsmBuilder::::default(); - let config_var = const_fri_config(&mut builder, inner_fri_config()); + let config_var = const_fri_config(&mut builder, &inner_fri_config()); for i in 0..5 { let log_d_val = 10 + i; diff --git a/recursion/program/src/fri/hints.rs b/recursion/program/src/fri/hints.rs index 367df671aa..9b41276eee 100644 --- a/recursion/program/src/fri/hints.rs +++ b/recursion/program/src/fri/hints.rs @@ -27,7 +27,7 @@ impl Hintable for InnerDigest { } fn write(&self) -> Vec>> { - let h: [InnerVal; DIGEST_SIZE] = (*self).into(); + let h: [InnerVal; DIGEST_SIZE] = *self; vec![h.iter().map(|x| Block::from(*x)).collect()] } } diff --git a/recursion/program/src/fri/two_adic_pcs.rs b/recursion/program/src/fri/two_adic_pcs.rs index 9179ec8000..0e9c316a8b 100644 --- a/recursion/program/src/fri/two_adic_pcs.rs +++ b/recursion/program/src/fri/two_adic_pcs.rs @@ -364,7 +364,7 @@ pub mod tests { // Test the recursive Pcs. let mut builder = Builder::::default(); - let config = const_fri_config(&mut builder, compressed_fri_config()); + let config = const_fri_config(&mut builder, &compressed_fri_config()); let pcs = TwoAdicFriPcsVariable { config }; let rounds = builder.constant::>>(vec![(commit, os.clone())]); diff --git a/recursion/program/src/fri/types.rs b/recursion/program/src/fri/types.rs index 82ce3fb1cc..ca2541a3be 100644 --- a/recursion/program/src/fri/types.rs +++ b/recursion/program/src/fri/types.rs @@ -2,7 +2,7 @@ use sp1_recursion_compiler::prelude::*; use crate::fri::TwoAdicMultiplicativeCosetVariable; -pub type DigestVariable = Array>; +pub type DigestVariable = Array::F>>; #[derive(DslVariable, Clone)] pub struct FriConfigVariable { diff --git a/recursion/program/src/hints.rs b/recursion/program/src/hints.rs index daa6bbef1c..6ebfa8a8c1 100644 --- a/recursion/program/src/hints.rs +++ b/recursion/program/src/hints.rs @@ -3,14 +3,14 @@ use p3_challenger::DuplexChallenger; use p3_commit::TwoAdicMultiplicativeCoset; use p3_field::TwoAdicField; use p3_field::{AbstractExtensionField, AbstractField}; -use sp1_core::air::{Word, PV_DIGEST_NUM_WORDS}; +use sp1_core::air::{MachineAir, Word, PV_DIGEST_NUM_WORDS}; +use sp1_core::stark::StarkGenericConfig; use sp1_core::stark::{ - AirOpenedValues, ChipOpenedValues, Com, ShardCommitment, ShardOpenedValues, ShardProof, + AirOpenedValues, ChipOpenedValues, Com, RiscvAir, ShardCommitment, ShardOpenedValues, }; -use sp1_core::stark::{StarkGenericConfig, StarkVerifyingKey}; use sp1_core::utils::{ - BabyBearPoseidon2, BabyBearPoseidon2Inner, InnerChallenge, InnerDigest, InnerDigestHash, - InnerPcsProof, InnerPerm, InnerVal, + BabyBearPoseidon2, InnerChallenge, InnerDigest, InnerDigestHash, InnerPcsProof, InnerPerm, + InnerVal, }; use sp1_recursion_compiler::{ config::InnerConfig, @@ -21,11 +21,14 @@ use sp1_recursion_core::runtime::PERMUTATION_WIDTH; use crate::challenger::DuplexChallengerVariable; use crate::fri::TwoAdicMultiplicativeCosetVariable; +use crate::machine::*; +use crate::stark::{ShardProofHint, VerifyingKeyHint}; use crate::types::{ AirOpenedValuesVariable, ChipOpenedValuesVariable, Sha256DigestVariable, ShardCommitmentVariable, ShardOpenedValuesVariable, ShardProofVariable, VerifyingKeyVariable, }; use crate::types::{QuotientData, QuotientDataValues}; +use crate::utils::{get_chip_quotient_data, get_preprocessed_data, get_sorted_indices}; pub trait Hintable { type HintVariable: MemVariable; @@ -146,12 +149,12 @@ impl Hintable for TwoAdicMultiplicativeCoset { trait VecAutoHintable: Hintable {} -impl VecAutoHintable for ShardProof {} -impl VecAutoHintable for ShardProof {} +impl<'a, A: MachineAir> VecAutoHintable for ShardProofHint<'a, BabyBearPoseidon2, A> {} impl VecAutoHintable for TwoAdicMultiplicativeCoset {} impl VecAutoHintable for Vec {} impl VecAutoHintable for QuotientDataValues {} impl VecAutoHintable for Vec {} +impl VecAutoHintable for Vec {} impl> VecAutoHintable for &I {} @@ -417,41 +420,53 @@ impl Hintable for DuplexChallenger { } impl< + 'a, SC: StarkGenericConfig< Pcs = ::Pcs, Challenge = ::Challenge, Challenger = ::Challenger, >, - > Hintable for StarkVerifyingKey + A: MachineAir, + > Hintable for VerifyingKeyHint<'a, SC, A> { type HintVariable = VerifyingKeyVariable; fn read(builder: &mut Builder) -> Self::HintVariable { let commitment = InnerDigest::read(builder); let pc_start = InnerVal::read(builder); + let preprocessed_sorted_idxs = Vec::::read(builder); + let prep_domains = Vec::>::read(builder); VerifyingKeyVariable { commitment, pc_start, + preprocessed_sorted_idxs, + prep_domains, } } fn write(&self) -> Vec::F>>> { + let (preprocessed_sorted_idxs, prep_domains) = get_preprocessed_data(self.machine, self.vk); + let mut stream = Vec::new(); - let h: InnerDigest = self.commit.into(); + let h: InnerDigest = self.vk.commit.into(); stream.extend(h.write()); - stream.extend(self.pc_start.write()); + stream.extend(self.vk.pc_start.write()); + stream.extend(preprocessed_sorted_idxs.write()); + stream.extend(prep_domains.write()); stream } } // Implement Hintable for ShardProof where SC is equivalent to BabyBearPoseidon2 impl< + 'a, SC: StarkGenericConfig< Pcs = ::Pcs, Challenge = ::Challenge, Challenger = ::Challenger, >, - > Hintable for ShardProof + A: MachineAir, + > Hintable for ShardProofHint<'a, SC, A> where ShardCommitment>: Hintable, { @@ -462,20 +477,205 @@ where let opened_values = ShardOpenedValues::read(builder); let opening_proof = InnerPcsProof::read(builder); let public_values = Vec::::read(builder); + let quotient_data = Vec::::read(builder); + let sorted_idxs = Vec::::read(builder); ShardProofVariable { commitment, opened_values, opening_proof, public_values, + quotient_data, + sorted_idxs, + } + } + + fn write(&self) -> Vec::F>>> { + let quotient_data = get_chip_quotient_data(self.machine, self.proof); + let sorted_indices = get_sorted_indices(self.machine, self.proof); + + let mut stream = Vec::new(); + stream.extend(self.proof.commitment.write()); + stream.extend(self.proof.opened_values.write()); + stream.extend(self.proof.opening_proof.write()); + stream.extend(self.proof.public_values.write()); + stream.extend(quotient_data.write()); + stream.extend(sorted_indices.write()); + + stream + } +} + +impl<'a, A: MachineAir> Hintable + for SP1RecursionMemoryLayout<'a, BabyBearPoseidon2, A> +{ + type HintVariable = SP1RecursionMemoryLayoutVariable; + + fn read(builder: &mut Builder) -> Self::HintVariable { + let vk = VerifyingKeyHint::<'a, BabyBearPoseidon2, A>::read(builder); + let shard_proofs = Vec::>::read(builder); + let leaf_challenger = DuplexChallenger::::read(builder); + let initial_reconstruct_challenger = + DuplexChallenger::::read(builder); + let is_complete = builder.hint_var(); + + SP1RecursionMemoryLayoutVariable { + vk, + shard_proofs, + leaf_challenger, + initial_reconstruct_challenger, + is_complete, } } fn write(&self) -> Vec::F>>> { let mut stream = Vec::new(); - stream.extend(self.commitment.write()); - stream.extend(self.opened_values.write()); - stream.extend(self.opening_proof.write()); - stream.extend(self.public_values.write()); + + let vk_hint = VerifyingKeyHint::<'a, BabyBearPoseidon2, _>::new(self.machine, self.vk); + + let proof_hints = self + .shard_proofs + .iter() + .map(|proof| ShardProofHint::::new(self.machine, proof)) + .collect::>(); + + stream.extend(vk_hint.write()); + stream.extend(proof_hints.write()); + stream.extend(self.leaf_challenger.write()); + stream.extend(self.initial_reconstruct_challenger.write()); + stream.extend((self.is_complete as usize).write()); + + stream + } +} + +impl<'a, A: MachineAir> Hintable for SP1ReduceMemoryLayout<'a, BabyBearPoseidon2, A> { + type HintVariable = SP1ReduceMemoryLayoutVariable; + + fn read(builder: &mut Builder) -> Self::HintVariable { + let compress_vk = VerifyingKeyHint::<'a, BabyBearPoseidon2, A>::read(builder); + let shard_proofs = Vec::>::read(builder); + let kinds = Vec::::read(builder); + let is_complete = builder.hint_var(); + + SP1ReduceMemoryLayoutVariable { + compress_vk, + shard_proofs, + kinds, + is_complete, + } + } + + fn write(&self) -> Vec::F>>> { + let mut stream = Vec::new(); + + let compress_vk_hint = VerifyingKeyHint::<'a, BabyBearPoseidon2, _>::new( + self.recursive_machine, + self.compress_vk, + ); + + let proof_hints = self + .shard_proofs + .iter() + .map(|proof| ShardProofHint::::new(self.recursive_machine, proof)) + .collect::>(); + + let kinds = self.kinds.iter().map(|k| *k as usize).collect::>(); + + stream.extend(compress_vk_hint.write()); + stream.extend(proof_hints.write()); + stream.extend(kinds.write()); + stream.extend((self.is_complete as usize).write()); + + stream + } +} + +impl<'a, A: MachineAir> Hintable for SP1RootMemoryLayout<'a, BabyBearPoseidon2, A> { + type HintVariable = SP1RootMemoryLayoutVariable; + + fn read(builder: &mut Builder) -> Self::HintVariable { + let proof = ShardProofHint::<'a, BabyBearPoseidon2, A>::read(builder); + let is_reduce = builder.hint_var(); + + SP1RootMemoryLayoutVariable { proof, is_reduce } + } + + fn write(&self) -> Vec::F>>> { + let mut stream = Vec::new(); + + let proof_hint = ShardProofHint::::new(self.machine, &self.proof); + + stream.extend(proof_hint.write()); + stream.extend((self.is_reduce as usize).write()); + + stream + } +} + +impl<'a, A: MachineAir> Hintable + for SP1DeferredMemoryLayout<'a, BabyBearPoseidon2, A> +{ + type HintVariable = SP1DeferredMemoryLayoutVariable; + + fn read(builder: &mut Builder) -> Self::HintVariable { + let compress_vk = VerifyingKeyHint::<'a, BabyBearPoseidon2, A>::read(builder); + let proofs = Vec::>::read(builder); + let start_reconstruct_deferred_digest = Vec::::read(builder); + let is_complete = builder.hint_var(); + + let sp1_vk = VerifyingKeyHint::<'a, BabyBearPoseidon2, RiscvAir<_>>::read(builder); + let committed_value_digest = Vec::>::read(builder); + let deferred_proofs_digest = Vec::::read(builder); + let leaf_challenger = DuplexChallenger::::read(builder); + let end_pc = InnerVal::read(builder); + let end_shard = InnerVal::read(builder); + + SP1DeferredMemoryLayoutVariable { + compress_vk, + proofs, + start_reconstruct_deferred_digest, + is_complete, + sp1_vk, + committed_value_digest, + deferred_proofs_digest, + leaf_challenger, + end_pc, + end_shard, + } + } + + fn write(&self) -> Vec::F>>> { + let mut stream = Vec::new(); + + let sp1_vk_hint = + VerifyingKeyHint::<'a, BabyBearPoseidon2, _>::new(self.sp1_machine, self.sp1_vk); + + let compress_vk_hint = + VerifyingKeyHint::<'a, BabyBearPoseidon2, _>::new(self.machine, self.compress_vk); + + let proof_hints = self + .proofs + .iter() + .map(|proof| ShardProofHint::::new(self.machine, proof)) + .collect::>(); + + let committed_value_digest = self + .committed_value_digest + .iter() + .map(|w| w.0.to_vec()) + .collect::>(); + + stream.extend(compress_vk_hint.write()); + stream.extend(proof_hints.write()); + stream.extend(self.start_reconstruct_deferred_digest.write()); + stream.extend((self.is_complete as usize).write()); + + stream.extend(sp1_vk_hint.write()); + stream.extend(committed_value_digest.write()); + stream.extend(self.deferred_proofs_digest.write()); + stream.extend(self.leaf_challenger.write()); + stream.extend(self.end_pc.write()); + stream.extend(self.end_shard.write()); stream } diff --git a/recursion/program/src/lib.rs b/recursion/program/src/lib.rs index 8ff9fd2a43..caf5ca0b92 100644 --- a/recursion/program/src/lib.rs +++ b/recursion/program/src/lib.rs @@ -1,14 +1,12 @@ -#![allow(incomplete_features)] -#![feature(generic_const_exprs)] -#![allow(type_alias_bounds)] #![allow(clippy::type_complexity)] #![allow(clippy::too_many_arguments)] + pub mod challenger; pub mod commit; pub mod constraints; pub mod fri; pub mod hints; -pub mod reduce; +pub mod machine; pub mod stark; pub mod types; pub mod utils; diff --git a/recursion/program/src/machine/compress.rs b/recursion/program/src/machine/compress.rs new file mode 100644 index 0000000000..b3c98ad155 --- /dev/null +++ b/recursion/program/src/machine/compress.rs @@ -0,0 +1,475 @@ +use std::array; +use std::borrow::{Borrow, BorrowMut}; +use std::marker::PhantomData; + +use crate::machine::utils::assert_complete; +use itertools::{izip, Itertools}; +use p3_air::Air; +use p3_baby_bear::BabyBear; +use p3_commit::TwoAdicMultiplicativeCoset; +use p3_field::{AbstractField, PrimeField32, TwoAdicField}; +use sp1_core::air::MachineAir; +use sp1_core::air::{Word, POSEIDON_NUM_WORDS, PV_DIGEST_NUM_WORDS}; +use sp1_core::stark::StarkMachine; +use sp1_core::stark::{Com, ShardProof, StarkGenericConfig, StarkVerifyingKey}; +use sp1_core::utils::BabyBearPoseidon2; +use sp1_recursion_compiler::config::InnerConfig; +use sp1_recursion_compiler::ir::{Array, Builder, Config, Felt, Var}; +use sp1_recursion_compiler::prelude::DslVariable; +use sp1_recursion_core::air::{RecursionPublicValues, RECURSIVE_PROOF_NUM_PV_ELTS}; +use sp1_recursion_core::runtime::{RecursionProgram, D, DIGEST_SIZE}; + +use sp1_recursion_compiler::prelude::*; + +use crate::challenger::{CanObserveVariable, DuplexChallengerVariable}; +use crate::fri::TwoAdicFriPcsVariable; +use crate::hints::Hintable; +use crate::stark::{RecursiveVerifierConstraintFolder, StarkVerifier}; +use crate::types::ShardProofVariable; +use crate::types::VerifyingKeyVariable; +use crate::utils::{ + assert_challenger_eq_pv, assign_challenger_from_pv, const_fri_config, + get_challenger_public_values, hash_vkey, +}; + +use super::utils::proof_data_from_vk; + +/// A program to verify a batch of recursive proofs and aggregate their public values. +#[derive(Debug, Clone, Copy)] +pub struct SP1CompressVerifier { + _phantom: PhantomData<(C, SC, A)>, +} + +/// The different types of programs that can be verified by the `SP1ReduceVerifier`. +#[derive(Debug, Clone, Copy)] +pub enum ReduceProgramType { + /// A batch of proofs that are all SP1 Core proofs. + Core = 0, + /// A batch of proofs that are all deferred proofs. + Deferred = 1, + /// A batch of proofs that are reduce proofs of a higher level in the recursion tree. + Reduce = 2, +} + +/// An input layout for the reduce verifier. +pub struct SP1ReduceMemoryLayout<'a, SC: StarkGenericConfig, A: MachineAir> { + pub compress_vk: &'a StarkVerifyingKey, + pub recursive_machine: &'a StarkMachine, + pub shard_proofs: Vec>, + pub is_complete: bool, + pub kinds: Vec, +} + +#[derive(DslVariable, Clone)] +pub struct SP1ReduceMemoryLayoutVariable { + pub compress_vk: VerifyingKeyVariable, + pub shard_proofs: Array>, + pub kinds: Array>, + pub is_complete: Var, +} + +impl SP1CompressVerifier +where + A: MachineAir + for<'a> Air>, +{ + /// Create a new instance of the program for the [BabyBearPoseidon2] config. + pub fn build( + machine: &StarkMachine, + recursive_vk: &StarkVerifyingKey, + deferred_vk: &StarkVerifyingKey, + ) -> RecursionProgram { + let mut builder = Builder::::default(); + + let input: SP1ReduceMemoryLayoutVariable<_> = builder.uninit(); + SP1ReduceMemoryLayout::::witness(&input, &mut builder); + + let pcs = TwoAdicFriPcsVariable { + config: const_fri_config(&mut builder, machine.config().pcs().fri_config()), + }; + SP1CompressVerifier::verify( + &mut builder, + &pcs, + machine, + input, + recursive_vk, + deferred_vk, + ); + + builder.compile_program() + } +} + +impl SP1CompressVerifier +where + C::F: PrimeField32 + TwoAdicField, + SC: StarkGenericConfig< + Val = C::F, + Challenge = C::EF, + Domain = TwoAdicMultiplicativeCoset, + >, + A: MachineAir + for<'a> Air>, + Com: Into<[SC::Val; DIGEST_SIZE]>, +{ + /// Verify a batch of recursive proofs and aggregate their public values. + /// + /// The compression verifier can aggregate proofs of different kinds: + /// - Core proofs: proofs which are recursive proof of a batch of SP1 shard proofs. The + /// implementation in this function assumes a fixed recursive verifier speicified by + /// `recursive_vk`. + /// - Deferred proofs: proofs which are recursive proof of a batch of deferred proofs. The + /// implementation in this function assumes a fixed deferred verification program specified + /// by `deferred_vk`. + /// - Compress proofs: these are proofs which refer to a prove of this program. The key for + /// it is part of public values will be propagated accross all levels of recursion and will + /// be checked against itself as in [sp1_prover::Prover] or as in [super::SP1RootVerifier]. + pub fn verify( + builder: &mut Builder, + pcs: &TwoAdicFriPcsVariable, + machine: &StarkMachine, + input: SP1ReduceMemoryLayoutVariable, + recursive_vk: &StarkVerifyingKey, + deferred_vk: &StarkVerifyingKey, + ) { + let SP1ReduceMemoryLayoutVariable { + compress_vk, + shard_proofs, + kinds, + is_complete, + } = input; + + // Initialize the values for the aggregated public output. + + let mut reduce_public_values_stream: Vec> = (0..RECURSIVE_PROOF_NUM_PV_ELTS) + .map(|_| builder.uninit()) + .collect(); + + let reduce_public_values: &mut RecursionPublicValues<_> = + reduce_public_values_stream.as_mut_slice().borrow_mut(); + + // Compute the digest of compress_vk and input the value to the public values. + let compress_vk_digest = hash_vkey(builder, &compress_vk); + + reduce_public_values.compress_vk_digest = + array::from_fn(|i| builder.get(&compress_vk_digest, i)); + + // Assert that there is at least one proof. + builder.assert_usize_ne(shard_proofs.len(), 0); + // Assert that the number of proofs is equal to the number of kinds. + builder.assert_usize_eq(shard_proofs.len(), kinds.len()); + + // Initialize the consistency check variables. + let sp1_vk_digest: [Felt<_>; DIGEST_SIZE] = array::from_fn(|_| builder.uninit()); + let pc: Felt<_> = builder.uninit(); + let shard: Felt<_> = builder.uninit(); + let mut initial_reconstruct_challenger = DuplexChallengerVariable::new(builder); + let mut reconstruct_challenger = DuplexChallengerVariable::new(builder); + let mut leaf_challenger = DuplexChallengerVariable::new(builder); + let committed_value_digest: [Word>; PV_DIGEST_NUM_WORDS] = + array::from_fn(|_| Word(array::from_fn(|_| builder.uninit()))); + let deferred_proofs_digest: [Felt<_>; POSEIDON_NUM_WORDS] = + array::from_fn(|_| builder.uninit()); + let reconstruct_deferred_digest: [Felt<_>; POSEIDON_NUM_WORDS] = + core::array::from_fn(|_| builder.uninit()); + let cumulative_sum: [Felt<_>; D] = core::array::from_fn(|_| builder.eval(C::F::zero())); + + // Collect verifying keys for each kind of program. + let recursive_vk_variable = proof_data_from_vk(builder, recursive_vk, machine); + let deferred_vk_variable = proof_data_from_vk(builder, deferred_vk, machine); + + // Get field values for the proof kind. + let core_kind = C::N::from_canonical_u32(ReduceProgramType::Core as u32); + let deferred_kind = C::N::from_canonical_u32(ReduceProgramType::Deferred as u32); + let reduce_kind = C::N::from_canonical_u32(ReduceProgramType::Reduce as u32); + + // Verify the shard proofs and connect the values. + builder.range(0, shard_proofs.len()).for_each(|i, builder| { + // Load the proof. + let proof = builder.get(&shard_proofs, i); + // Get the kind of proof we are verifying. + let kind = builder.get(&kinds, i); + + // Verify the shard proof. + + // Initialize values for verifying key and proof data. + let vk: VerifyingKeyVariable<_> = builder.uninit(); + // Set the correct value given the value of kind, and assert it must be one of the + // valid values. We can do that by nested `if-else` statements. + builder.if_eq(kind, core_kind).then_or_else( + |builder| { + builder.assign(vk.clone(), recursive_vk_variable.clone()); + }, + |builder| { + builder.if_eq(kind, deferred_kind).then_or_else( + |builder| { + builder.assign(vk.clone(), deferred_vk_variable.clone()); + }, + |builder| { + builder.if_eq(kind, reduce_kind).then_or_else( + |builder| { + builder.assign(vk.clone(), compress_vk.clone()); + }, + |builder| { + // If the kind is not one of the valid values, raise + // an error. + builder.error(); + }, + ); + }, + ); + }, + ); + + // Verify the shard proof given the correct data. + + // Prepare a challenger. + let mut challenger = DuplexChallengerVariable::new(builder); + // Observe the vk and start pc. + challenger.observe(builder, vk.commitment.clone()); + challenger.observe(builder, vk.pc_start); + // Observe the main commitment and public values. + challenger.observe(builder, proof.commitment.main_commit.clone()); + for j in 0..machine.num_pv_elts() { + let element = builder.get(&proof.public_values, j); + challenger.observe(builder, element); + } + // verify proof. + StarkVerifier::::verify_shard( + builder, + &vk, + pcs, + machine, + &mut challenger, + &proof, + ); + + // Load the public values from the proof. + let current_public_values_elements = (0..RECURSIVE_PROOF_NUM_PV_ELTS) + .map(|i| builder.get(&proof.public_values, i)) + .collect::>>(); + + let current_public_values: &RecursionPublicValues> = + current_public_values_elements.as_slice().borrow(); + + // If the proof is the first proof, initialize the values. + builder.if_eq(i, C::N::zero()).then(|builder| { + // Initialize global and accumulated values. + + // Initialize the start of deferred digests. + for (digest, current_digest, global_digest) in izip!( + reconstruct_deferred_digest.iter(), + current_public_values + .start_reconstruct_deferred_digest + .iter(), + reduce_public_values + .start_reconstruct_deferred_digest + .iter() + ) { + builder.assign(*digest, *current_digest); + builder.assign(*global_digest, *current_digest); + } + + // Initialize the sp1_vk digest + for (digest, first_digest) in sp1_vk_digest + .iter() + .zip(current_public_values.sp1_vk_digest) + { + builder.assign(*digest, first_digest); + } + + // Initiallize start pc. + builder.assign( + reduce_public_values.start_pc, + current_public_values.start_pc, + ); + builder.assign(pc, current_public_values.start_pc); + + // Initialize start shard. + builder.assign(shard, current_public_values.start_shard); + builder.assign( + reduce_public_values.start_shard, + current_public_values.start_shard, + ); + + // Initialize the leaf challenger. + assign_challenger_from_pv( + builder, + &mut leaf_challenger, + current_public_values.leaf_challenger, + ); + // Initialize the reconstruct challenger. + assign_challenger_from_pv( + builder, + &mut initial_reconstruct_challenger, + current_public_values.start_reconstruct_challenger, + ); + assign_challenger_from_pv( + builder, + &mut reconstruct_challenger, + current_public_values.start_reconstruct_challenger, + ); + + // Assign the commited values and deferred proof digests. + for (word, current_word) in committed_value_digest + .iter() + .zip_eq(current_public_values.committed_value_digest.iter()) + { + for (byte, current_byte) in word.0.iter().zip_eq(current_word.0.iter()) { + builder.assign(*byte, *current_byte); + } + } + + for (digest, current_digest) in deferred_proofs_digest + .iter() + .zip_eq(current_public_values.deferred_proofs_digest.iter()) + { + builder.assign(*digest, *current_digest); + } + + // Initialize the start reconstruct deferred digest. + for (digest, first_digest, global_digest) in izip!( + reconstruct_deferred_digest.iter(), + current_public_values + .start_reconstruct_deferred_digest + .iter(), + reduce_public_values + .start_reconstruct_deferred_digest + .iter() + ) { + builder.assign(*digest, *first_digest); + builder.assign(*global_digest, *first_digest); + } + }); + + // Assert that the current values match the accumulated values. + + // Assert that the start deferred digest is equal to the current deferred digest. + for (digest, current_digest) in reconstruct_deferred_digest.iter().zip_eq( + current_public_values + .start_reconstruct_deferred_digest + .iter(), + ) { + builder.assert_felt_eq(*digest, *current_digest); + } + + // consistency checks for all accumulated values. + + // Assert that the sp1_vk digest is always the same. + for (digest, current) in sp1_vk_digest + .iter() + .zip(current_public_values.sp1_vk_digest) + { + builder.assert_felt_eq(*digest, current); + } + + // Assert that the start pc is equal to the current pc. + builder.assert_felt_eq(pc, current_public_values.start_pc); + // Verfiy that the shard is equal to the current shard. + // builder.assert_felt_eq(shard, current_public_values.start_shard); + // Assert that the leaf challenger is always the same. + + assert_challenger_eq_pv( + builder, + &leaf_challenger, + current_public_values.leaf_challenger, + ); + // Assert that the current challenger matches the start reconstruct challenger. + assert_challenger_eq_pv( + builder, + &reconstruct_challenger, + current_public_values.start_reconstruct_challenger, + ); + + // Assert that the commited digests are the same. + for (word, current_word) in committed_value_digest + .iter() + .zip_eq(current_public_values.committed_value_digest.iter()) + { + for (byte, current_byte) in word.0.iter().zip_eq(current_word.0.iter()) { + builder.assert_felt_eq(*byte, *current_byte); + } + } + + // Assert that the deferred proof digests are the same. + for (digest, current_digest) in deferred_proofs_digest + .iter() + .zip_eq(current_public_values.deferred_proofs_digest.iter()) + { + builder.assert_felt_eq(*digest, *current_digest); + } + + // Update the accumulated values. + + // Update the deffered proof digest. + for (digest, current_digest) in reconstruct_deferred_digest + .iter() + .zip_eq(current_public_values.end_reconstruct_deferred_digest.iter()) + { + builder.assign(*digest, *current_digest); + } + + // Update the accumulated values. + // Update pc to be the next pc. + builder.assign(pc, current_public_values.next_pc); + // Update the shard to be the next shard. + builder.assign(shard, current_public_values.next_shard); + // Update the reconstruct challenger. + assign_challenger_from_pv( + builder, + &mut reconstruct_challenger, + current_public_values.end_reconstruct_challenger, + ); + + // Update the cumulative sum. + for (sum_element, current_sum_element) in cumulative_sum + .iter() + .zip_eq(current_public_values.cumulative_sum.iter()) + { + builder.assign(*sum_element, *sum_element + *current_sum_element); + } + }); + + // Update the global values from the last accumulated values. + // Set sp1_vk digest to the one from the proof values. + reduce_public_values.sp1_vk_digest = sp1_vk_digest; + // Set next_pc to be the last pc (which is the same as accumulated pc) + reduce_public_values.next_pc = pc; + // Set next shard to be the last shard (which is the same as accumulated shard) + reduce_public_values.next_shard = shard; + // Set the leaf challenger to it's value. + let values = get_challenger_public_values(builder, &leaf_challenger); + reduce_public_values.leaf_challenger = values; + // Set the start reconstruct challenger to be the initial reconstruct challenger. + let values = get_challenger_public_values(builder, &initial_reconstruct_challenger); + reduce_public_values.start_reconstruct_challenger = values; + // Set the end reconstruct challenger to be the last reconstruct challenger. + let values = get_challenger_public_values(builder, &reconstruct_challenger); + reduce_public_values.end_reconstruct_challenger = values; + // Set the start reconstruct deferred digest to be the last reconstruct deferred digest. + reduce_public_values.end_reconstruct_deferred_digest = reconstruct_deferred_digest; + + // Assign the deffered proof digests. + reduce_public_values.deferred_proofs_digest = deferred_proofs_digest; + // Assign the committed value digests. + reduce_public_values.committed_value_digest = committed_value_digest; + // Assign the cumulative sum. + reduce_public_values.cumulative_sum = cumulative_sum; + + // If the proof is complete, make completeness assertions and set the flag. Otherwise, check + // the flag is zero and set the public value to zero. + builder.if_eq(is_complete, C::N::one()).then_or_else( + |builder| { + builder.assign(reduce_public_values.is_complete, C::F::one()); + assert_complete(builder, reduce_public_values, &reconstruct_challenger) + }, + |builder| { + builder.assert_var_eq(is_complete, C::N::zero()); + builder.assign(reduce_public_values.is_complete, C::F::zero()); + }, + ); + + // Commit the public values. + for value in reduce_public_values_stream { + builder.commit_public_value(value); + } + } +} diff --git a/recursion/program/src/machine/core.rs b/recursion/program/src/machine/core.rs new file mode 100644 index 0000000000..527533835a --- /dev/null +++ b/recursion/program/src/machine/core.rs @@ -0,0 +1,327 @@ +use std::array; +use std::borrow::BorrowMut; +use std::marker::PhantomData; + +use itertools::Itertools; +use p3_baby_bear::BabyBear; +use p3_commit::TwoAdicMultiplicativeCoset; +use p3_field::{AbstractField, PrimeField32, TwoAdicField}; +use sp1_core::air::{MachineAir, PublicValues}; +use sp1_core::air::{Word, POSEIDON_NUM_WORDS, PV_DIGEST_NUM_WORDS}; +use sp1_core::stark::StarkMachine; +use sp1_core::stark::{Com, RiscvAir, ShardProof, StarkGenericConfig, StarkVerifyingKey}; +use sp1_core::utils::BabyBearPoseidon2; +use sp1_recursion_compiler::config::InnerConfig; +use sp1_recursion_compiler::ir::{Array, Builder, Config, Ext, ExtConst, Felt, Var}; +use sp1_recursion_compiler::prelude::DslVariable; +use sp1_recursion_core::air::{RecursionPublicValues, RECURSIVE_PROOF_NUM_PV_ELTS}; +use sp1_recursion_core::runtime::{RecursionProgram, DIGEST_SIZE}; + +use sp1_recursion_compiler::prelude::*; + +use crate::challenger::{CanObserveVariable, DuplexChallengerVariable}; +use crate::fri::TwoAdicFriPcsVariable; +use crate::hints::Hintable; +use crate::stark::StarkVerifier; +use crate::types::ShardProofVariable; +use crate::types::VerifyingKeyVariable; +use crate::utils::{const_fri_config, felt2var, get_challenger_public_values, hash_vkey, var2felt}; + +use super::utils::assert_complete; + +/// A program for recursively verifying a batch of SP1 proofs. +#[derive(Debug, Clone, Copy)] +pub struct SP1RecursiveVerifier { + _phantom: PhantomData<(C, SC)>, +} + +pub struct SP1RecursionMemoryLayout<'a, SC: StarkGenericConfig, A: MachineAir> { + pub vk: &'a StarkVerifyingKey, + pub machine: &'a StarkMachine, + pub shard_proofs: Vec>, + pub leaf_challenger: &'a SC::Challenger, + pub initial_reconstruct_challenger: SC::Challenger, + pub is_complete: bool, +} + +#[derive(DslVariable, Clone)] +pub struct SP1RecursionMemoryLayoutVariable { + pub vk: VerifyingKeyVariable, + + pub shard_proofs: Array>, + + pub leaf_challenger: DuplexChallengerVariable, + pub initial_reconstruct_challenger: DuplexChallengerVariable, + + pub is_complete: Var, +} + +impl SP1RecursiveVerifier { + /// Create a new instance of the program for the [BabyBearPoseidon2] config. + pub fn build( + machine: &StarkMachine>, + ) -> RecursionProgram { + let mut builder = Builder::::default(); + + let input: SP1RecursionMemoryLayoutVariable<_> = builder.uninit(); + SP1RecursionMemoryLayout::>::witness(&input, &mut builder); + + let pcs = TwoAdicFriPcsVariable { + config: const_fri_config(&mut builder, machine.config().pcs().fri_config()), + }; + SP1RecursiveVerifier::verify(&mut builder, &pcs, machine, input); + + builder.compile_program() + } +} + +impl SP1RecursiveVerifier +where + C::F: PrimeField32 + TwoAdicField, + SC: StarkGenericConfig< + Val = C::F, + Challenge = C::EF, + Domain = TwoAdicMultiplicativeCoset, + >, + Com: Into<[SC::Val; DIGEST_SIZE]>, +{ + /// Verify a batch of SP1 shard proofs and aggregate their public values. + /// + /// This program represents a first recursive step in the verification of an SP1 proof + /// consisting of one or more shards. Each shard proof is verified and its public values are + /// aggregated into a single set representing the start and end state of the program execution + /// across all shards. + pub fn verify( + builder: &mut Builder, + pcs: &TwoAdicFriPcsVariable, + machine: &StarkMachine>, + input: SP1RecursionMemoryLayoutVariable, + ) { + let SP1RecursionMemoryLayoutVariable { + vk, + shard_proofs, + leaf_challenger, + initial_reconstruct_challenger, + is_complete, + } = input; + + // Initialize values we will commit to public outputs. + + // Start and end of program counters. + let start_pc: Felt<_> = builder.uninit(); + + // Start and end shard indices. + let initial_shard: Felt<_> = builder.uninit(); + + // The commited values digest and deferred proof digest. These will be checked to be the + // same for all proofs. + let committed_value_digest: [Word>; PV_DIGEST_NUM_WORDS] = + array::from_fn(|_| Word(array::from_fn(|_| builder.uninit()))); + let deferred_proofs_digest: [Felt<_>; POSEIDON_NUM_WORDS] = + array::from_fn(|_| builder.uninit()); + + // Assert that the number of proofs is not zero. + builder.assert_usize_ne(shard_proofs.len(), 0); + + let leaf_challenger_public_values = get_challenger_public_values(builder, &leaf_challenger); + + // Initialize loop variables. + let current_shard: Felt<_> = builder.uninit(); + let mut reconstruct_challenger: DuplexChallengerVariable<_> = + initial_reconstruct_challenger.copy(builder); + let cumulative_sum: Ext<_, _> = builder.eval(C::EF::zero().cons()); + let current_pc: Felt<_> = builder.uninit(); + let exit_code: Felt<_> = builder.uninit(); + // Verify proofs, validate transitions, and update accumulation variables. + builder.range(0, shard_proofs.len()).for_each(|i, builder| { + // Load the proof. + let proof = builder.get(&shard_proofs, i); + + // Verify the shard proof. + let mut challenger = leaf_challenger.copy(builder); + StarkVerifier::::verify_shard( + builder, + &vk, + pcs, + machine, + &mut challenger, + &proof, + ); + + // Extract public values. + let mut pv_elements = Vec::new(); + for i in 0..machine.num_pv_elts() { + let element = builder.get(&proof.public_values, i); + pv_elements.push(element); + } + let public_values = PublicValues::>, Felt<_>>::from_vec(pv_elements); + + // If this is the first proof in the batch, verify the initial conditions. + builder.if_eq(i, C::N::zero()).then(|builder| { + // Initialize the values of accumulated variables. + + // Shard. + builder.assign(initial_shard, public_values.shard); + builder.assign(current_shard, public_values.shard); + + // Program counter. + builder.assign(start_pc, public_values.start_pc); + builder.assign(current_pc, public_values.start_pc); + + // Commited public values digests. + for (word, first_word) in committed_value_digest + .iter() + .zip_eq(public_values.committed_value_digest.iter()) + { + for (byte, first_byte) in word.0.iter().zip_eq(first_word.0.iter()) { + builder.assign(*byte, *first_byte); + } + } + + // Deferred proofs digests. + for (digest, first_digest) in deferred_proofs_digest + .iter() + .zip_eq(public_values.deferred_proofs_digest.iter()) + { + builder.assign(*digest, *first_digest); + } + + // Exit code. + builder.assign(exit_code, public_values.exit_code); + }); + + // If the shard is zero, verify the global initial conditions hold on challenger and pc. + let shard = felt2var(builder, public_values.shard); + builder.if_eq(shard, C::N::one()).then(|builder| { + // This should be the first proof as well + builder.assert_var_eq(i, C::N::zero()); + + // Start pc should be vk.pc_start + builder.assert_felt_eq(public_values.start_pc, vk.pc_start); + + // Assert that the initial challenger is equal to a fresh challenger observing the + // verifier key and the initial pc. + let mut first_initial_challenger = DuplexChallengerVariable::new(builder); + + first_initial_challenger.observe(builder, vk.commitment.clone()); + first_initial_challenger.observe(builder, vk.pc_start); + + // Make sure the start reconstruct challenger is correct, since we will + // commit to it in public values. + initial_reconstruct_challenger.assert_eq(builder, &first_initial_challenger); + }); + + // Assert compatibility of the shard values. + for (word, current_word) in committed_value_digest + .iter() + .zip_eq(public_values.committed_value_digest.iter()) + { + for (byte, current_byte) in word.0.iter().zip_eq(current_word.0.iter()) { + builder.assert_felt_eq(*byte, *current_byte); + } + } + + // Assert that the start_pc of the proof is equal to the current pc. + builder.assert_felt_eq(current_pc, public_values.start_pc); + // Assert that the next_pc is different from the start_pc. + builder.assert_felt_ne(public_values.start_pc, public_values.next_pc); + // Assert that the start_pc is not zero (this means program has halted in a non-last + // shard). + builder.assert_felt_ne(public_values.start_pc, C::F::zero()); + + // Assert that the shard of the proof is equal to the current shard. + builder.assert_felt_eq(current_shard, public_values.shard); + + // Assert that exit code is the same for all proofs. + builder.assert_felt_eq(exit_code, public_values.exit_code); + + // Assert that the committed value digests are all the same. + + // Assert that the deferred proof digest is the same for all proofs. + for (digest, current_digest) in deferred_proofs_digest + .iter() + .zip_eq(public_values.deferred_proofs_digest.iter()) + { + builder.assert_felt_eq(*digest, *current_digest); + } + + // Update the reconstruct challenger, cumulative sum, shard number, and program counter. + reconstruct_challenger.observe(builder, proof.commitment.main_commit); + for j in 0..machine.num_pv_elts() { + let element = builder.get(&proof.public_values, j); + reconstruct_challenger.observe(builder, element); + } + + // Increment the shard count by one. + builder.assign(current_shard, current_shard + C::F::one()); + + // Update current_pc to be the end_pc of the current proof. + builder.assign(current_pc, public_values.next_pc); + + // Cumulative sum is updated by sums of all chips. + let opened_values = proof.opened_values.chips; + builder + .range(0, opened_values.len()) + .for_each(|k, builder| { + let values = builder.get(&opened_values, k); + let sum = values.cumulative_sum; + builder.assign(cumulative_sum, cumulative_sum + sum); + }); + }); + + // Compute vk digest. + let vk_digest = hash_vkey(builder, &vk); + let vk_digest: [Felt<_>; DIGEST_SIZE] = array::from_fn(|i| builder.get(&vk_digest, i)); + + // Collect values for challenges. + let initial_challenger_public_values = + get_challenger_public_values(builder, &initial_reconstruct_challenger); + let final_challenger_public_values = + get_challenger_public_values(builder, &reconstruct_challenger); + + let cumulative_sum_arrray = builder.ext2felt(cumulative_sum); + let cumulative_sum_arrray = array::from_fn(|i| builder.get(&cumulative_sum_arrray, i)); + + let zero: Felt<_> = builder.eval(C::F::zero()); + // Initialize the public values we will commit to. + let mut recursion_public_values_stream = [zero; RECURSIVE_PROOF_NUM_PV_ELTS]; + + let recursion_public_values: &mut RecursionPublicValues<_> = + recursion_public_values_stream.as_mut_slice().borrow_mut(); + + let start_deferred_digest = [zero; POSEIDON_NUM_WORDS]; + let end_deferred_digest = [zero; POSEIDON_NUM_WORDS]; + + let is_complete_felt = var2felt(builder, is_complete); + + recursion_public_values.committed_value_digest = committed_value_digest; + recursion_public_values.deferred_proofs_digest = deferred_proofs_digest; + recursion_public_values.start_pc = start_pc; + recursion_public_values.next_pc = current_pc; + recursion_public_values.start_shard = initial_shard; + recursion_public_values.next_shard = current_shard; + recursion_public_values.sp1_vk_digest = vk_digest; + recursion_public_values.leaf_challenger = leaf_challenger_public_values; + recursion_public_values.start_reconstruct_challenger = initial_challenger_public_values; + recursion_public_values.end_reconstruct_challenger = final_challenger_public_values; + recursion_public_values.cumulative_sum = cumulative_sum_arrray; + recursion_public_values.start_reconstruct_deferred_digest = start_deferred_digest; + recursion_public_values.end_reconstruct_deferred_digest = end_deferred_digest; + recursion_public_values.exit_code = zero; + recursion_public_values.is_complete = is_complete_felt; + + // If the proof represents a complete proof, make completeness assertions. + // + // *Remark*: In this program, this only happends if there is one shard and the program has + // no deferred proofs to verify. However, the completeness check is independent of these + // facts. + builder.if_eq(is_complete, C::N::one()).then(|builder| { + assert_complete(builder, recursion_public_values, &reconstruct_challenger) + }); + + // Commit to the public values. + for value in recursion_public_values_stream { + builder.commit_public_value(value); + } + } +} diff --git a/recursion/program/src/machine/deferred.rs b/recursion/program/src/machine/deferred.rs new file mode 100644 index 0000000000..206cb6fae4 --- /dev/null +++ b/recursion/program/src/machine/deferred.rs @@ -0,0 +1,291 @@ +use std::array; +use std::borrow::{Borrow, BorrowMut}; +use std::marker::PhantomData; + +use p3_air::Air; +use p3_baby_bear::BabyBear; +use p3_commit::TwoAdicMultiplicativeCoset; +use p3_field::{AbstractField, PrimeField32, TwoAdicField}; +use sp1_core::air::{MachineAir, WORD_SIZE}; +use sp1_core::air::{Word, POSEIDON_NUM_WORDS, PV_DIGEST_NUM_WORDS}; +use sp1_core::stark::StarkMachine; +use sp1_core::stark::{Com, RiscvAir, ShardProof, StarkGenericConfig, StarkVerifyingKey}; +use sp1_core::utils::BabyBearPoseidon2; +use sp1_recursion_compiler::config::InnerConfig; +use sp1_recursion_compiler::ir::{Array, Builder, Config, Felt, Var}; +use sp1_recursion_compiler::prelude::DslVariable; +use sp1_recursion_core::air::{RecursionPublicValues, RECURSIVE_PROOF_NUM_PV_ELTS}; +use sp1_recursion_core::runtime::{RecursionProgram, DIGEST_SIZE}; + +use sp1_recursion_compiler::prelude::*; + +use crate::challenger::{CanObserveVariable, DuplexChallengerVariable}; +use crate::fri::TwoAdicFriPcsVariable; +use crate::hints::Hintable; +use crate::stark::{RecursiveVerifierConstraintFolder, StarkVerifier}; +use crate::types::ShardProofVariable; +use crate::types::VerifyingKeyVariable; +use crate::utils::{const_fri_config, get_challenger_public_values, hash_vkey, var2felt}; + +#[derive(Debug, Clone, Copy)] +pub struct SP1DeferredVerifier { + _phantom: PhantomData<(C, SC, A)>, +} + +/// Inputs that are hinted to the [SP1DeferredVerifier] program. +pub struct SP1DeferredMemoryLayout<'a, SC: StarkGenericConfig, A: MachineAir> +where + SC::Val: PrimeField32, +{ + pub compress_vk: &'a StarkVerifyingKey, + pub machine: &'a StarkMachine, + pub proofs: Vec>, + + pub start_reconstruct_deferred_digest: Vec, + + pub is_complete: bool, + + pub sp1_vk: &'a StarkVerifyingKey, + pub sp1_machine: &'a StarkMachine>, + pub committed_value_digest: Vec>, + pub deferred_proofs_digest: Vec, + pub leaf_challenger: SC::Challenger, + pub end_pc: SC::Val, + pub end_shard: SC::Val, +} + +/// A variable version of the [SP1DeferredMemoryLayout] struct. +#[derive(DslVariable, Clone)] +pub struct SP1DeferredMemoryLayoutVariable { + pub compress_vk: VerifyingKeyVariable, + + pub proofs: Array>, + + pub start_reconstruct_deferred_digest: Array>, + + pub is_complete: Var, + + pub sp1_vk: VerifyingKeyVariable, + pub committed_value_digest: Array>>, + pub deferred_proofs_digest: Array>, + pub leaf_challenger: DuplexChallengerVariable, + pub end_pc: Felt, + pub end_shard: Felt, +} + +impl SP1DeferredVerifier +where + A: MachineAir + for<'a> Air>, +{ + /// Create a new instance of the program for the [BabyBearPoseidon2] config. + pub fn build(machine: &StarkMachine) -> RecursionProgram { + let mut builder = Builder::::default(); + let input: SP1DeferredMemoryLayoutVariable<_> = builder.uninit(); + SP1DeferredMemoryLayout::::witness(&input, &mut builder); + + let pcs = TwoAdicFriPcsVariable { + config: const_fri_config(&mut builder, machine.config().pcs().fri_config()), + }; + + SP1DeferredVerifier::verify(&mut builder, &pcs, machine, input); + + builder.compile_program() + } +} + +impl SP1DeferredVerifier +where + C::F: PrimeField32 + TwoAdicField, + SC: StarkGenericConfig< + Val = C::F, + Challenge = C::EF, + Domain = TwoAdicMultiplicativeCoset, + >, + A: MachineAir + for<'a> Air>, + Com: Into<[SC::Val; DIGEST_SIZE]>, +{ + /// Verify a batch of deferred proofs. + /// + /// Each deferred proof is a recursive proof representing some computation. Namely, every such + /// proof represents a recursively verified program. + /// verifier: + /// - Asserts that each of these proofs is valid as a `compress` proof. + /// - Asserts that each of these proofs is complete by checking the `is_complete` flag in the + /// proof's public values. + /// - Aggregates the proof information into an accumulated deferred digest. + pub fn verify( + builder: &mut Builder, + pcs: &TwoAdicFriPcsVariable, + machine: &StarkMachine, + input: SP1DeferredMemoryLayoutVariable, + ) { + // Read the inputs. + let SP1DeferredMemoryLayoutVariable { + compress_vk, + proofs, + start_reconstruct_deferred_digest, + is_complete, + + sp1_vk, + committed_value_digest, + deferred_proofs_digest, + leaf_challenger, + end_pc, + end_shard, + } = input; + + // Initialize the values for the aggregated public output as all zeros. + let mut deferred_public_values_stream: Vec> = (0..RECURSIVE_PROOF_NUM_PV_ELTS) + .map(|_| builder.eval(C::F::zero())) + .collect(); + + let deferred_public_values: &mut RecursionPublicValues<_> = + deferred_public_values_stream.as_mut_slice().borrow_mut(); + + // Compute the digest of compress_vk and input the value to the public values. + let compress_vk_digest = hash_vkey(builder, &compress_vk); + + deferred_public_values.compress_vk_digest = + array::from_fn(|i| builder.get(&compress_vk_digest, i)); + + // Initialize the start of deferred digests. + deferred_public_values.start_reconstruct_deferred_digest = + array::from_fn(|i| builder.get(&start_reconstruct_deferred_digest, i)); + + // Assert that there is at least one proof. + builder.assert_usize_ne(proofs.len(), 0); + + // Initialize the consistency check variable. + let mut reconstruct_deferred_digest = builder.array(POSEIDON_NUM_WORDS); + for (i, first_digest) in deferred_public_values + .start_reconstruct_deferred_digest + .iter() + .enumerate() + { + builder.set(&mut reconstruct_deferred_digest, i, *first_digest); + } + + // Verify the proofs and connect the values. + builder.range(0, proofs.len()).for_each(|i, builder| { + // Load the proof. + let proof = builder.get(&proofs, i); + + // Verify the shard proof. + + // Prepare a challenger. + let mut challenger = DuplexChallengerVariable::new(builder); + // Observe the vk and start pc. + challenger.observe(builder, compress_vk.commitment.clone()); + challenger.observe(builder, compress_vk.pc_start); + // Observe the main commitment and public values. + challenger.observe(builder, proof.commitment.main_commit.clone()); + for j in 0..machine.num_pv_elts() { + let element = builder.get(&proof.public_values, j); + challenger.observe(builder, element); + } + // verify the proof. + StarkVerifier::::verify_shard( + builder, + &compress_vk, + pcs, + machine, + &mut challenger, + &proof, + ); + + // Load the public values from the proof. + let current_public_values_elements = (0..RECURSIVE_PROOF_NUM_PV_ELTS) + .map(|i| builder.get(&proof.public_values, i)) + .collect::>>(); + + let current_public_values: &RecursionPublicValues> = + current_public_values_elements.as_slice().borrow(); + + // Assert that the proof is complete. + builder.assert_felt_eq(current_public_values.is_complete, C::F::one()); + + // Assert that the compress_vk digest is the same. + for (digest, current) in deferred_public_values + .compress_vk_digest + .iter() + .zip(current_public_values.compress_vk_digest.iter()) + { + builder.assert_felt_eq(*digest, *current); + } + + // Update deferred proof digest + // poseidon2( current_digest[..8] || pv.sp1_vk_digest[..8] || pv.committed_value_digest[..32] ) + let mut poseidon_inputs = builder.array(48); + for j in 0..DIGEST_SIZE { + let current_digest_element = builder.get(&reconstruct_deferred_digest, j); + builder.set(&mut poseidon_inputs, j, current_digest_element); + } + + for j in 0..DIGEST_SIZE { + // let input_index: Var<_> = builder.constant(F::from_canonical_usize(j + 8)); + builder.set( + &mut poseidon_inputs, + j + DIGEST_SIZE, + current_public_values.sp1_vk_digest[j], + ); + } + for j in 0..PV_DIGEST_NUM_WORDS { + for k in 0..WORD_SIZE { + // let input_index: Var<_> = + // builder.eval(F::from_canonical_usize(j * WORD_SIZE + k + 16)); + let element = current_public_values.committed_value_digest[j][k]; + builder.set(&mut poseidon_inputs, j * WORD_SIZE + k + 16, element); + } + } + let new_digest = builder.poseidon2_hash(&poseidon_inputs); + for j in 0..DIGEST_SIZE { + let new_value = builder.get(&new_digest, j); + builder.set(&mut reconstruct_deferred_digest, j, new_value); + } + }); + + // Set the public values. + + // Set initial_pc, end_pc, initial_shard, and end_shard to be the hitned values. + deferred_public_values.start_pc = end_pc; + deferred_public_values.next_pc = end_pc; + deferred_public_values.start_shard = end_shard; + deferred_public_values.next_shard = end_shard; + + // Set the sp1_vk_digest to be the hitned value. + let sp1_vk_digest = hash_vkey(builder, &sp1_vk); + deferred_public_values.sp1_vk_digest = array::from_fn(|i| builder.get(&sp1_vk_digest, i)); + + // Set the committed value digest to be the hitned value. + for (i, public_word) in deferred_public_values + .committed_value_digest + .iter_mut() + .enumerate() + { + let hinted_word = builder.get(&committed_value_digest, i); + public_word.0 = array::from_fn(|j| builder.get(&hinted_word, j)); + } + + // Set the deferred proof digest to be the hitned value. + deferred_public_values.deferred_proofs_digest = + core::array::from_fn(|i| builder.get(&deferred_proofs_digest, i)); + + // Set the initial, end, and leaf challenger to be the hitned values. + let values = get_challenger_public_values(builder, &leaf_challenger); + deferred_public_values.leaf_challenger = values; + deferred_public_values.start_reconstruct_challenger = values; + deferred_public_values.end_reconstruct_challenger = values; + + // Assign the deffered proof digests. + deferred_public_values.end_reconstruct_deferred_digest = + array::from_fn(|i| builder.get(&reconstruct_deferred_digest, i)); + + // Set the is_complete flag. + deferred_public_values.is_complete = var2felt(builder, is_complete); + + // Commit the public values. + for value in deferred_public_values_stream { + builder.commit_public_value(value); + } + } +} diff --git a/recursion/program/src/machine/mod.rs b/recursion/program/src/machine/mod.rs new file mode 100644 index 0000000000..2d262332a0 --- /dev/null +++ b/recursion/program/src/machine/mod.rs @@ -0,0 +1,421 @@ +mod compress; +mod core; +mod deferred; +mod root; +mod utils; + +pub use compress::*; +pub use core::*; +pub use deferred::*; +pub use root::*; + +#[cfg(test)] +mod tests { + + use p3_baby_bear::BabyBear; + use p3_challenger::CanObserve; + use p3_maybe_rayon::prelude::*; + use sp1_core::stark::{MachineVerificationError, RiscvAir, StarkGenericConfig}; + use sp1_core::utils::BabyBearPoseidon2; + use sp1_core::{ + io::SP1Stdin, + runtime::Program, + stark::{Challenge, LocalProver}, + }; + use sp1_recursion_compiler::config::InnerConfig; + use sp1_recursion_core::{ + runtime::Runtime, + stark::{config::BabyBearPoseidon2Outer, RecursionAir}, + }; + + use crate::hints::Hintable; + + use super::*; + + enum Test { + Recursion, + Reduce, + Compress, + Wrap, + } + + fn test_sp1_recursive_machine_verify(program: Program, batch_size: usize, test: Test) { + type SC = BabyBearPoseidon2; + type F = BabyBear; + type EF = Challenge; + + sp1_core::utils::setup_logger(); + + let machine = RiscvAir::machine(SC::default()); + let (_, vk) = machine.setup(&program); + + // Make the recursion program. + let recursive_program = SP1RecursiveVerifier::::build(&machine); + let recursive_config = SC::default(); + type A = RecursionAir; + let recursive_machine = A::machine(recursive_config.clone()); + let (rec_pk, rec_vk) = recursive_machine.setup(&recursive_program); + + // Make the deferred program. + let deferred_program = SP1DeferredVerifier::::build(&recursive_machine); + let (_, deferred_vk) = recursive_machine.setup(&deferred_program); + + // Make the compress program. + let reduce_program = SP1CompressVerifier::::build( + &recursive_machine, + &rec_vk, + &deferred_vk, + ); + + let (reduce_pk, compress_vk) = recursive_machine.setup(&reduce_program); + + // Make the compress program. + let compress_machine = RecursionAir::<_, 9>::machine(SC::compressed()); + let compress_program = + SP1RootVerifier::::build(&recursive_machine, &compress_vk, true); + let (compress_pk, compress_vk) = compress_machine.setup(&compress_program); + + // Make the wrap program. + let wrap_machine = RecursionAir::<_, 5>::machine(BabyBearPoseidon2Outer::default()); + let wrap_program = + SP1RootVerifier::::build(&compress_machine, &compress_vk, false); + + let mut challenger = machine.config().challenger(); + let time = std::time::Instant::now(); + let (proof, _) = sp1_core::utils::run_and_prove(program, &SP1Stdin::new(), SC::default()); + machine.verify(&vk, &proof, &mut challenger).unwrap(); + tracing::info!("Proof generated successfully"); + let elapsed = time.elapsed(); + tracing::info!("Execution proof time: {:?}", elapsed); + + // Get the and leaf challenger. + let mut leaf_challenger = machine.config().challenger(); + vk.observe_into(&mut leaf_challenger); + proof.shard_proofs.iter().for_each(|proof| { + leaf_challenger.observe(proof.commitment.main_commit); + leaf_challenger.observe_slice(&proof.public_values[0..machine.num_pv_elts()]); + }); + // Make sure leaf challenger is not mutable anymore. + let leaf_challenger = leaf_challenger; + + let mut layouts = Vec::new(); + + let mut reconstruct_challenger = machine.config().challenger(); + vk.observe_into(&mut reconstruct_challenger); + + let is_complete = proof.shard_proofs.len() == 1; + for batch in proof.shard_proofs.chunks(batch_size) { + let proofs = batch.to_vec(); + + layouts.push(SP1RecursionMemoryLayout { + vk: &vk, + machine: &machine, + shard_proofs: proofs, + leaf_challenger: &leaf_challenger, + initial_reconstruct_challenger: reconstruct_challenger.clone(), + is_complete, + }); + + for proof in batch.iter() { + reconstruct_challenger.observe(proof.commitment.main_commit); + reconstruct_challenger + .observe_slice(&proof.public_values[0..machine.num_pv_elts()]); + } + } + + assert_eq!( + reconstruct_challenger.sponge_state, + leaf_challenger.sponge_state + ); + assert_eq!( + reconstruct_challenger.input_buffer, + leaf_challenger.input_buffer + ); + assert_eq!( + reconstruct_challenger.output_buffer, + leaf_challenger.output_buffer + ); + + // Run the recursion programs. + let mut records = Vec::new(); + + for layout in layouts { + let mut runtime = + Runtime::::new(&recursive_program, machine.config().perm.clone()); + + let mut witness_stream = Vec::new(); + witness_stream.extend(layout.write()); + + runtime.witness_stream = witness_stream.into(); + runtime.run(); + runtime.print_stats(); + + records.push(runtime.record); + } + + // Prove all recursion programs and verify the recursive proofs. + + // Make the recursive proofs. + let time = std::time::Instant::now(); + let recursive_proofs = records + .into_par_iter() + .map(|record| { + let mut recursive_challenger = recursive_machine.config().challenger(); + recursive_machine.prove::>( + &rec_pk, + record, + &mut recursive_challenger, + ) + }) + .collect::>(); + let elapsed = time.elapsed(); + tracing::info!("Recursive first layer proving time: {:?}", elapsed); + + // Verify the recursive proofs. + for rec_proof in recursive_proofs.iter() { + let mut recursive_challenger = recursive_machine.config().challenger(); + let result = recursive_machine.verify(&rec_vk, rec_proof, &mut recursive_challenger); + + match result { + Ok(_) => tracing::info!("Proof verified successfully"), + Err(MachineVerificationError::NonZeroCumulativeSum) => { + tracing::info!("Proof verification failed: NonZeroCumulativeSum") + } + e => panic!("Proof verification failed: {:?}", e), + } + } + if let Test::Recursion = test { + return; + } + + tracing::info!("Recursive proofs verified successfully"); + + // Chain all the individual shard proofs. + let mut recursive_proofs = recursive_proofs + .into_iter() + .flat_map(|proof| proof.shard_proofs) + .collect::>(); + + // Iterate over the recursive proof batches until there is one proof remaining. + let mut is_first_layer = true; + let mut is_complete; + let time = std::time::Instant::now(); + loop { + tracing::info!("Recursive proofs: {}", recursive_proofs.len()); + is_complete = recursive_proofs.len() <= batch_size; + recursive_proofs = recursive_proofs + .par_chunks(batch_size) + .map(|batch| { + let kind = if is_first_layer { + ReduceProgramType::Core + } else { + ReduceProgramType::Reduce + }; + let kinds = batch.iter().map(|_| kind).collect::>(); + let input = SP1ReduceMemoryLayout { + compress_vk: &compress_vk, + recursive_machine: &recursive_machine, + shard_proofs: batch.to_vec(), + kinds, + is_complete, + }; + + let mut runtime = Runtime::::new( + &reduce_program, + recursive_machine.config().perm.clone(), + ); + + let mut witness_stream = Vec::new(); + witness_stream.extend(input.write()); + + runtime.witness_stream = witness_stream.into(); + runtime.run(); + runtime.print_stats(); + + let mut recursive_challenger = recursive_machine.config().challenger(); + let mut proof = recursive_machine.prove::>( + &reduce_pk, + runtime.record, + &mut recursive_challenger, + ); + let mut recursive_challenger = recursive_machine.config().challenger(); + let result = + recursive_machine.verify(&compress_vk, &proof, &mut recursive_challenger); + + match result { + Ok(_) => tracing::info!("Proof verified successfully"), + Err(MachineVerificationError::NonZeroCumulativeSum) => { + tracing::info!("Proof verification failed: NonZeroCumulativeSum") + } + e => panic!("Proof verification failed: {:?}", e), + } + + assert_eq!(proof.shard_proofs.len(), 1); + proof.shard_proofs.pop().unwrap() + }) + .collect(); + is_first_layer = false; + + if recursive_proofs.len() == 1 { + break; + } + } + let elapsed = time.elapsed(); + tracing::info!("Reduction successful, time: {:?}", elapsed); + if let Test::Reduce = test { + return; + } + + assert_eq!(recursive_proofs.len(), 1); + let reduce_proof = recursive_proofs.pop().unwrap(); + + // Make the compress proof. + let input = SP1RootMemoryLayout { + machine: &recursive_machine, + proof: reduce_proof, + is_reduce: true, + }; + + // Run the compress program. + let mut runtime = + Runtime::::new(&compress_program, compress_machine.config().perm.clone()); + + let mut witness_stream = Vec::new(); + witness_stream.extend(input.write()); + + runtime.witness_stream = witness_stream.into(); + runtime.run(); + runtime.print_stats(); + tracing::info!("Compress program executed successfully"); + + // Prove the compress program. + let mut compress_challenger = compress_machine.config().challenger(); + + let time = std::time::Instant::now(); + let mut compress_proof = compress_machine.prove::>( + &compress_pk, + runtime.record, + &mut compress_challenger, + ); + let elapsed = time.elapsed(); + tracing::info!("Compress proving time: {:?}", elapsed); + let mut compress_challenger = compress_machine.config().challenger(); + let result = + compress_machine.verify(&compress_vk, &compress_proof, &mut compress_challenger); + match result { + Ok(_) => tracing::info!("Proof verified successfully"), + Err(MachineVerificationError::NonZeroCumulativeSum) => { + tracing::info!("Proof verification failed: NonZeroCumulativeSum") + } + e => panic!("Proof verification failed: {:?}", e), + } + + if let Test::Compress = test { + return; + } + + // Run and prove the wrap program. + + let (wrap_pk, wrap_vk) = wrap_machine.setup(&wrap_program); + + let compress_proof = compress_proof.shard_proofs.pop().unwrap(); + let input = SP1RootMemoryLayout { + machine: &compress_machine, + proof: compress_proof, + is_reduce: false, + }; + + // Run the compress program. + let mut runtime = + Runtime::::new(&wrap_program, compress_machine.config().perm.clone()); + + let mut witness_stream = Vec::new(); + witness_stream.extend(input.write()); + + runtime.witness_stream = witness_stream.into(); + runtime.run(); + runtime.print_stats(); + tracing::info!("Wrap program executed successfully"); + + // Prove the wrap program. + let mut wrap_challenger = wrap_machine.config().challenger(); + let time = std::time::Instant::now(); + let wrap_proof = + wrap_machine.prove::>(&wrap_pk, runtime.record, &mut wrap_challenger); + let elapsed = time.elapsed(); + tracing::info!("Wrap proving time: {:?}", elapsed); + let mut wrap_challenger = wrap_machine.config().challenger(); + let result = wrap_machine.verify(&wrap_vk, &wrap_proof, &mut wrap_challenger); + match result { + Ok(_) => tracing::info!("Proof verified successfully"), + Err(MachineVerificationError::NonZeroCumulativeSum) => { + tracing::info!("Proof verification failed: NonZeroCumulativeSum") + } + e => panic!("Proof verification failed: {:?}", e), + } + tracing::info!("Wrapping successful"); + } + + #[test] + fn test_sp1_recursive_machine_verify_fibonacci() { + let elf = include_bytes!("../../../../tests/fibonacci/elf/riscv32im-succinct-zkvm-elf"); + test_sp1_recursive_machine_verify(Program::from(elf), 1, Test::Recursion) + } + + #[test] + #[ignore] + fn test_sp1_reduce_machine_verify_fibonacci() { + let elf = include_bytes!("../../../../tests/fibonacci/elf/riscv32im-succinct-zkvm-elf"); + test_sp1_recursive_machine_verify(Program::from(elf), 1, Test::Reduce) + } + + #[test] + #[ignore] + fn test_sp1_compress_machine_verify_fibonacci() { + let elf = include_bytes!("../../../../tests/fibonacci/elf/riscv32im-succinct-zkvm-elf"); + test_sp1_recursive_machine_verify(Program::from(elf), 1, Test::Compress) + } + + #[test] + #[ignore] + fn test_sp1_wrap_machine_verify_fibonacci() { + let elf = include_bytes!("../../../../tests/fibonacci/elf/riscv32im-succinct-zkvm-elf"); + test_sp1_recursive_machine_verify(Program::from(elf), 1, Test::Wrap) + } + + #[test] + #[ignore] + fn test_sp1_reduce_machine_verify_tendermint() { + let elf = include_bytes!( + "../../../../tests/tendermint-benchmark/elf/riscv32im-succinct-zkvm-elf" + ); + test_sp1_recursive_machine_verify(Program::from(elf), 2, Test::Reduce) + } + + #[test] + #[ignore] + fn test_sp1_recursive_machine_verify_tendermint() { + let elf = include_bytes!( + "../../../../tests/tendermint-benchmark/elf/riscv32im-succinct-zkvm-elf" + ); + test_sp1_recursive_machine_verify(Program::from(elf), 2, Test::Recursion) + } + + #[test] + #[ignore] + fn test_sp1_compress_machine_verify_tendermint() { + let elf = include_bytes!( + "../../../../tests/tendermint-benchmark/elf/riscv32im-succinct-zkvm-elf" + ); + test_sp1_recursive_machine_verify(Program::from(elf), 2, Test::Compress) + } + + #[test] + #[ignore] + fn test_sp1_wrap_machine_verify_tendermint() { + let elf = include_bytes!( + "../../../../tests/tendermint-benchmark/elf/riscv32im-succinct-zkvm-elf" + ); + test_sp1_recursive_machine_verify(Program::from(elf), 2, Test::Wrap) + } +} diff --git a/recursion/program/src/machine/root.rs b/recursion/program/src/machine/root.rs new file mode 100644 index 0000000000..16dfc3993f --- /dev/null +++ b/recursion/program/src/machine/root.rs @@ -0,0 +1,139 @@ +use std::borrow::Borrow; + +use p3_air::Air; +use p3_baby_bear::BabyBear; +use p3_commit::TwoAdicMultiplicativeCoset; +use p3_field::{AbstractField, PrimeField32, TwoAdicField}; +use sp1_core::air::MachineAir; +use sp1_core::stark::StarkMachine; +use sp1_core::stark::{Com, ShardProof, StarkGenericConfig, StarkVerifyingKey}; +use sp1_core::utils::BabyBearPoseidon2; +use sp1_recursion_compiler::config::InnerConfig; +use sp1_recursion_compiler::ir::{Builder, Config, Felt, Var}; +use sp1_recursion_compiler::prelude::DslVariable; +use sp1_recursion_core::air::{RecursionPublicValues, RECURSIVE_PROOF_NUM_PV_ELTS}; +use sp1_recursion_core::runtime::{RecursionProgram, DIGEST_SIZE}; + +use sp1_recursion_compiler::prelude::*; + +use crate::challenger::{CanObserveVariable, DuplexChallengerVariable}; +use crate::fri::TwoAdicFriPcsVariable; +use crate::hints::Hintable; +use crate::machine::utils::proof_data_from_vk; +use crate::stark::{RecursiveVerifierConstraintFolder, ShardProofHint, StarkVerifier}; +use crate::types::ShardProofVariable; +use crate::utils::{const_fri_config, hash_vkey}; + +/// The program that gets a final verifier at the root of the tree. +#[derive(Debug, Clone, Copy)] +pub struct SP1RootVerifier { + _phantom: std::marker::PhantomData<(C, SC, A)>, +} + +pub struct SP1RootMemoryLayout<'a, SC: StarkGenericConfig, A: MachineAir> { + pub machine: &'a StarkMachine, + pub proof: ShardProof, + pub is_reduce: bool, +} + +#[derive(DslVariable, Clone)] +pub struct SP1RootMemoryLayoutVariable { + pub proof: ShardProofVariable, + pub is_reduce: Var, +} + +impl SP1RootVerifier +where + A: MachineAir + for<'a> Air>, +{ + /// Create a new instance of the program for the [BabyBearPoseidon2] config. + pub fn build( + machine: &StarkMachine, + vk: &StarkVerifyingKey, + is_compress: bool, + ) -> RecursionProgram { + let mut builder = Builder::::default(); + let proof: ShardProofVariable<_> = builder.uninit(); + ShardProofHint::::witness(&proof, &mut builder); + + let pcs = TwoAdicFriPcsVariable { + config: const_fri_config(&mut builder, machine.config().pcs().fri_config()), + }; + + SP1RootVerifier::verify(&mut builder, &pcs, machine, vk, &proof, is_compress); + + builder.compile_program() + } +} + +impl SP1RootVerifier +where + C::F: PrimeField32 + TwoAdicField, + SC: StarkGenericConfig< + Val = C::F, + Challenge = C::EF, + Domain = TwoAdicMultiplicativeCoset, + >, + A: MachineAir + for<'a> Air>, + Com: Into<[SC::Val; DIGEST_SIZE]>, +{ + /// Verify a proof with given vk and aggregate their public values. + /// + /// is_reduce : if the proof is a reduce proof, we will assert that the given vk indentifies + /// with the reduce vk digest of public inputs. + pub fn verify( + builder: &mut Builder, + pcs: &TwoAdicFriPcsVariable, + machine: &StarkMachine, + vk: &StarkVerifyingKey, + proof: &ShardProofVariable, + is_compress: bool, + ) { + // Get the verifying key info from the vk. + let vk = proof_data_from_vk(builder, vk, machine); + + // Verify the proof. + + let mut challenger = DuplexChallengerVariable::new(builder); + // Observe the vk and start pc. + challenger.observe(builder, vk.commitment.clone()); + challenger.observe(builder, vk.pc_start); + // Observe the main commitment and public values. + challenger.observe(builder, proof.commitment.main_commit.clone()); + for j in 0..machine.num_pv_elts() { + let element = builder.get(&proof.public_values, j); + challenger.observe(builder, element); + } + // verify proof. + StarkVerifier::::verify_shard(builder, &vk, pcs, machine, &mut challenger, proof); + + // Get the public inputs from the proof. + let public_values_elements = (0..RECURSIVE_PROOF_NUM_PV_ELTS) + .map(|i| builder.get(&proof.public_values, i)) + .collect::>>(); + let public_values: &RecursionPublicValues> = + public_values_elements.as_slice().borrow(); + + // Assert that the proof is complete. + // + // *Remark*: here we are assuming on that the program we are verifying indludes the check + // of completeness conditions are satisfied if the flag is set to one, so we are only + // checking the `is_complete` flag in this program. + builder.assert_felt_eq(public_values.is_complete, C::F::one()); + + // If the proof is a compress proof, assert that the vk is the same as the compress vk from + // the public values. + if is_compress { + let vk_digest = hash_vkey(builder, &vk); + for (i, reduce_digest_elem) in public_values.compress_vk_digest.iter().enumerate() { + let vk_digest_elem = builder.get(&vk_digest, i); + builder.assert_felt_eq(vk_digest_elem, *reduce_digest_elem); + } + } + + // Commit to the public values, broadcasting the same ones. + for value in public_values_elements { + builder.commit_public_value(value); + } + } +} diff --git a/recursion/program/src/machine/utils.rs b/recursion/program/src/machine/utils.rs new file mode 100644 index 0000000000..61500a8d3a --- /dev/null +++ b/recursion/program/src/machine/utils.rs @@ -0,0 +1,117 @@ +use itertools::Itertools; +use p3_commit::TwoAdicMultiplicativeCoset; +use p3_field::AbstractField; + +use sp1_core::{ + air::MachineAir, + stark::{Com, StarkGenericConfig, StarkMachine, StarkVerifyingKey}, +}; +use sp1_recursion_compiler::ir::{Builder, Config, Felt, Var}; +use sp1_recursion_core::{air::RecursionPublicValues, runtime::DIGEST_SIZE}; + +use crate::{ + challenger::DuplexChallengerVariable, + fri::TwoAdicMultiplicativeCosetVariable, + types::VerifyingKeyVariable, + utils::{assert_challenger_eq_pv, get_preprocessed_data}, +}; + +/// Assertions on the public values describing a complete recursive proof state. +pub(crate) fn assert_complete( + builder: &mut Builder, + public_values: &RecursionPublicValues>, + end_reconstruct_challenger: &DuplexChallengerVariable, +) { + let RecursionPublicValues { + deferred_proofs_digest, + next_pc, + start_shard, + cumulative_sum, + start_reconstruct_deferred_digest, + end_reconstruct_deferred_digest, + leaf_challenger, + .. + } = public_values; + + // Assert that `end_pc` is equal to zero (so program execution has completed) + builder.assert_felt_eq(*next_pc, C::F::zero()); + + // Assert that the start shard is equal to 1. + builder.assert_felt_eq(*start_shard, C::F::one()); + + // The challenger has been fully verified. + + // The start_reconstruct_challenger should be the same as an empty challenger observing the + // verifier key and the start pc. This was already verified when verifying the leaf proofs so + // there is no need to assert it here. + + // Assert that the end reconstruct challenger is equal to the leaf challenger. + assert_challenger_eq_pv(builder, end_reconstruct_challenger, *leaf_challenger); + + // The deferred digest has been fully reconstructed. + + // The start reconstruct digest should be zero. + for start_digest_word in start_reconstruct_deferred_digest { + builder.assert_felt_eq(*start_digest_word, C::F::zero()); + } + + // The end reconstruct digest should be equal to the deferred proofs digest. + for (end_digest_word, deferred_digest_word) in end_reconstruct_deferred_digest + .iter() + .zip_eq(deferred_proofs_digest.iter()) + { + builder.assert_felt_eq(*end_digest_word, *deferred_digest_word); + } + + // Assert that the cumulative sum is zero. + for b in cumulative_sum.iter() { + builder.assert_felt_eq(*b, C::F::zero()); + } +} + +pub(crate) fn proof_data_from_vk( + builder: &mut Builder, + vk: &StarkVerifyingKey, + machine: &StarkMachine, +) -> VerifyingKeyVariable +where + SC: StarkGenericConfig< + Val = C::F, + Challenge = C::EF, + Domain = TwoAdicMultiplicativeCoset, + >, + A: MachineAir, + Com: Into<[SC::Val; DIGEST_SIZE]>, +{ + let mut commitment = builder.dyn_array(DIGEST_SIZE); + for (i, value) in vk.commit.clone().into().iter().enumerate() { + builder.set(&mut commitment, i, *value); + } + let pc_start: Felt<_> = builder.eval(vk.pc_start); + + let (prep_sorted_indices_val, prep_domains_val) = get_preprocessed_data(machine, vk); + + let mut prep_sorted_indices = builder.dyn_array::>(prep_sorted_indices_val.len()); + let mut prep_domains = + builder.dyn_array::>(prep_domains_val.len()); + + for (i, value) in prep_sorted_indices_val.iter().enumerate() { + builder.set( + &mut prep_sorted_indices, + i, + C::N::from_canonical_usize(*value), + ); + } + + for (i, value) in prep_domains_val.iter().enumerate() { + let domain: TwoAdicMultiplicativeCosetVariable<_> = builder.constant(*value); + builder.set(&mut prep_domains, i, domain); + } + + VerifyingKeyVariable { + commitment, + pc_start, + preprocessed_sorted_idxs: prep_sorted_indices, + prep_domains, + } +} diff --git a/recursion/program/src/reduce.rs b/recursion/program/src/reduce.rs deleted file mode 100644 index 474006197c..0000000000 --- a/recursion/program/src/reduce.rs +++ /dev/null @@ -1,700 +0,0 @@ -//! ReduceProgram defines a recursive program that can reduce a set of proofs into a single proof. -//! -//! Specifically, this program takes in an ordered list of proofs where each proof can be either an -//! SP1 Core proof or a recursive VM proof of itself. Each proof is verified and then checked to -//! ensure that each transition is valid. Finally, the overall start and end values are committed to. -//! -//! Because SP1 uses a global challenger system, `verify_start_challenger` is witnessed and used to -//! verify each core proof. As each core proof is verified, its commitment and public values are -//! observed into `reconstruct_challenger`. After recursively reducing down to one proof, -//! `reconstruct_challenger` must equal `verify_start_challenger`. -//! -//! "Deferred proofs" can also be passed in and verified. These are fully reduced proofs that were -//! committed to within the core VM. These proofs can then be verified here and then reconstructed -//! into a single digest which is checked against what was committed. Note that it is possible for -//! reduce to be called with only deferred proofs, and not any core/recursive proofs. In this case, -//! the start and end pc/shard values should be equal to each other. -//! -//! Because the program can verify ranges of a full SP1 proof, the program exposes `is_complete` -//! which is only 1 if the program has fully verified the execution of the program, including all -//! deferred proofs. - -#![allow(clippy::needless_range_loop)] - -use p3_baby_bear::BabyBear; -use p3_challenger::DuplexChallenger; -use p3_commit::TwoAdicMultiplicativeCoset; -use p3_field::AbstractField; -use sp1_core::air::{PublicValues, SP1_PROOF_NUM_PV_ELTS, WORD_SIZE}; -use sp1_core::air::{Word, POSEIDON_NUM_WORDS, PV_DIGEST_NUM_WORDS}; -use sp1_core::stark::PROOF_MAX_NUM_PVS; -use sp1_core::stark::{RiscvAir, ShardProof, StarkGenericConfig, StarkVerifyingKey}; -use sp1_core::utils::baby_bear_poseidon2::{compressed_fri_config, default_fri_config}; -use sp1_core::utils::sp1_fri_config; -use sp1_core::utils::{BabyBearPoseidon2, InnerDigest}; -use sp1_recursion_compiler::asm::{AsmBuilder, AsmConfig}; -use sp1_recursion_compiler::ir::{Array, Builder, Ext, ExtConst, Felt, Var}; -use sp1_recursion_core::air::RecursionPublicValues; -use sp1_recursion_core::cpu::Instruction; -use sp1_recursion_core::runtime::{RecursionProgram, DIGEST_SIZE}; -use sp1_recursion_core::stark::{RecursionAirSkinnyDeg7, RecursionAirWideDeg3}; - -use crate::challenger::{CanObserveVariable, DuplexChallengerVariable}; -use crate::fri::types::DigestVariable; -use crate::fri::TwoAdicFriPcsVariable; -use crate::fri::TwoAdicMultiplicativeCosetVariable; -use crate::hints::Hintable; -use crate::stark::StarkVerifier; -use crate::types::{QuotientData, QuotientDataValues, VerifyingKeyVariable}; -use crate::types::{Sha256DigestVariable, ShardProofVariable}; -use crate::utils::{ - assert_challenger_eq_pv, assign_challenger_from_pv, clone_array, commit_challenger, - const_fri_config, felt2var, hash_vkey, var2felt, -}; - -type SC = BabyBearPoseidon2; -type F = ::Val; -type EF = ::Challenge; -type C = AsmConfig; -type Val = BabyBear; - -#[derive(Debug, Clone, Copy)] -pub struct ReduceProgram; - -impl ReduceProgram { - /// The program that can reduce a set of proofs into a single proof. - pub fn build() -> RecursionProgram { - let mut reduce_program = Self::define(false); - reduce_program.instructions[0] = Instruction::dummy(); - reduce_program - } - - /// The program used for setting up the state of memory for the prover. - pub fn setup() -> RecursionProgram { - Self::define(true) - } - - /// A definition for the program. - pub fn define(setup: bool) -> RecursionProgram { - // Initialize the sp1 and recursion maachines. - let core_machine = RiscvAir::machine(BabyBearPoseidon2::default()); - let reduce_machine = RecursionAirWideDeg3::machine(BabyBearPoseidon2::default()); - let compress_machine = RecursionAirSkinnyDeg7::machine(BabyBearPoseidon2::compressed()); - - // Initialize the builder. - let mut builder = AsmBuilder::::default(); - - // Initialize the sp1 and recursion configs as constants.. - let sp1_config = const_fri_config(&mut builder, sp1_fri_config()); - let reduce_config = const_fri_config(&mut builder, default_fri_config()); - let compress_config = const_fri_config(&mut builder, compressed_fri_config()); - let sp1_pcs = TwoAdicFriPcsVariable { config: sp1_config }; - let reduce_pcs = TwoAdicFriPcsVariable { - config: reduce_config, - }; - let compress_pcs = TwoAdicFriPcsVariable { - config: compress_config, - }; - - // Allocate empty space on the stack for the inputs. - // - // In the case where setup is not true, the values on the stack will all be witnessed - // with the appropriate values using the hinting API. - let is_recursive_flags: Array<_, Var<_>> = builder.uninit(); - let chip_quotient_data: Array<_, Array<_, QuotientData<_>>> = builder.uninit(); - let sorted_indices: Array<_, Array<_, Var<_>>> = builder.uninit(); - let verify_start_challenger: DuplexChallengerVariable<_> = builder.uninit(); - let reconstruct_challenger: DuplexChallengerVariable<_> = builder.uninit(); - let prep_sorted_indices: Array<_, Var<_>> = builder.uninit(); - let prep_domains: Array<_, TwoAdicMultiplicativeCosetVariable<_>> = builder.uninit(); - let reduce_prep_sorted_indices: Array<_, Var<_>> = builder.uninit(); - let reduce_prep_domains: Array<_, TwoAdicMultiplicativeCosetVariable<_>> = builder.uninit(); - let compress_prep_sorted_indices: Array<_, Var<_>> = builder.uninit(); - let compress_prep_domains: Array<_, TwoAdicMultiplicativeCosetVariable<_>> = - builder.uninit(); - let sp1_vk: VerifyingKeyVariable<_> = builder.uninit(); - let reduce_vk: VerifyingKeyVariable<_> = builder.uninit(); - let compress_vk: VerifyingKeyVariable<_> = builder.uninit(); - let initial_committed_values_digest: Sha256DigestVariable<_> = builder.uninit(); - let initial_deferred_proofs_digest: DigestVariable<_> = builder.uninit(); - let initial_start_pc: Felt<_> = builder.uninit(); - let initial_exit_code: Felt<_> = builder.uninit(); - let initial_start_shard: Felt<_> = builder.uninit(); - let mut reconstruct_deferred_digest: DigestVariable<_> = builder.uninit(); - let proofs: Array<_, ShardProofVariable<_>> = builder.uninit(); - let deferred_chip_quotient_data: Array<_, Array<_, QuotientData<_>>> = builder.uninit(); - let deferred_sorted_indices: Array<_, Array<_, Var<_>>> = builder.uninit(); - let num_deferred_proofs: Var<_> = builder.uninit(); - let deferred_proofs: Array<_, ShardProofVariable<_>> = builder.uninit(); - let is_complete: Var<_> = builder.uninit(); - let is_compressed: Var<_> = builder.uninit(); - - // Setup the memory for the prover. - // - // If the program is being setup, we need to witness the inputs using the hinting API - // and setup the correct state of memory. - if setup { - Vec::::witness(&is_recursive_flags, &mut builder); - Vec::>::witness(&chip_quotient_data, &mut builder); - Vec::>::witness(&sorted_indices, &mut builder); - DuplexChallenger::witness(&verify_start_challenger, &mut builder); - DuplexChallenger::witness(&reconstruct_challenger, &mut builder); - Vec::::witness(&prep_sorted_indices, &mut builder); - Vec::>::witness(&prep_domains, &mut builder); - Vec::::witness(&reduce_prep_sorted_indices, &mut builder); - Vec::>::witness( - &reduce_prep_domains, - &mut builder, - ); - Vec::::witness(&compress_prep_sorted_indices, &mut builder); - Vec::>::witness( - &compress_prep_domains, - &mut builder, - ); - StarkVerifyingKey::::witness(&sp1_vk, &mut builder); - StarkVerifyingKey::::witness(&reduce_vk, &mut builder); - StarkVerifyingKey::::witness(&compress_vk, &mut builder); - <[Word; PV_DIGEST_NUM_WORDS] as Hintable>::witness( - &initial_committed_values_digest, - &mut builder, - ); - InnerDigest::witness(&initial_deferred_proofs_digest, &mut builder); - BabyBear::witness(&initial_start_pc, &mut builder); - BabyBear::witness(&initial_exit_code, &mut builder); - BabyBear::witness(&initial_start_shard, &mut builder); - InnerDigest::witness(&reconstruct_deferred_digest, &mut builder); - - let num_proofs = is_recursive_flags.len(); - let mut proofs_target = builder.dyn_array(num_proofs); - builder.range(0, num_proofs).for_each(|i, builder| { - let proof = ShardProof::::read(builder); - builder.set(&mut proofs_target, i, proof); - }); - builder.assign(proofs.clone(), proofs_target); - - Vec::>::witness(&deferred_chip_quotient_data, &mut builder); - Vec::>::witness(&deferred_sorted_indices, &mut builder); - Vec::>::witness(&deferred_proofs, &mut builder); - let num_deferred_proofs_var = deferred_proofs.len(); - builder.assign(num_deferred_proofs, num_deferred_proofs_var); - usize::witness(&is_complete, &mut builder); - usize::witness(&is_compressed, &mut builder); - - return builder.compile_program(); - } - - let num_proofs = is_recursive_flags.len(); - let zero: Var<_> = builder.constant(F::zero()); - let zero_felt: Felt<_> = builder.constant(F::zero()); - let one: Var<_> = builder.constant(F::one()); - let one_felt: Felt<_> = builder.constant(F::one()); - - // Setup the recursive challenger. - builder.cycle_tracker("stage-b-setup-recursion-challenger"); - let mut recursion_challenger = DuplexChallengerVariable::new(&mut builder); - for j in 0..DIGEST_SIZE { - let element = builder.get(&reduce_vk.commitment, j); - recursion_challenger.observe(&mut builder, element); - } - recursion_challenger.observe(&mut builder, reduce_vk.pc_start); - builder.cycle_tracker("stage-b-setup-recursion-challenger"); - - // Hash vkey + pc_start + prep_domains into a single digest. - let sp1_vk_digest = hash_vkey(&mut builder, &sp1_vk, &prep_domains, &prep_sorted_indices); - let recursion_vk_digest = hash_vkey( - &mut builder, - &reduce_vk, - &reduce_prep_domains, - &reduce_prep_sorted_indices, - ); - - // Global variables that will be commmitted to at the end. - let global_committed_values_digest: Sha256DigestVariable<_> = - initial_committed_values_digest; - let global_deferred_proofs_digest: DigestVariable<_> = initial_deferred_proofs_digest; - let global_start_pc: Felt<_> = initial_start_pc; - let global_next_pc: Felt<_> = builder.uninit(); - let global_exit_code: Felt<_> = initial_exit_code; - let global_start_shard: Felt<_> = initial_start_shard; - let global_next_shard: Felt<_> = builder.uninit(); - let global_cumulative_sum: Ext<_, _> = builder.eval(EF::zero().cons()); - let start_reconstruct_challenger = reconstruct_challenger.copy(&mut builder); - let start_reconstruct_deferred_digest = - clone_array(&mut builder, &reconstruct_deferred_digest); - - // Previous proof's values. - let prev_next_pc: Felt<_> = builder.uninit(); - let prev_next_shard: Felt<_> = builder.uninit(); - - // For each proof: - // 1) If it's the first proof of this batch, ensure that the start values are correct. - // 2) If it's not the first proof, ensure that the global values are the same and the - // transitions are valid. - // 3) If it's the last proof of this batch, set the global end variables. - // 4) If it's not the last proof, update the previous values. - let constrain_shard_transitions = - |proof_index: Var<_>, - builder: &mut Builder, - committed_value_digest_words: &[Word>; PV_DIGEST_NUM_WORDS], - start_pc: Felt<_>, - next_pc: Felt<_>, - start_shard: Felt<_>, - next_shard: Felt<_>, - exit_code: Felt<_>| { - let committed_value_digest = - Sha256DigestVariable::from_words(builder, committed_value_digest_words); - builder.if_eq(proof_index, zero).then_or_else( - // First proof: ensure that witnessed start values are correct. - |builder| { - for i in 0..(PV_DIGEST_NUM_WORDS * WORD_SIZE) { - let element = builder.get(&global_committed_values_digest.bytes, i); - let proof_element = builder.get(&committed_value_digest.bytes, i); - builder.assert_felt_eq(element, proof_element); - } - builder.assert_felt_eq(global_start_pc, start_pc); - builder.assert_felt_eq(global_start_shard, start_shard); - builder.assert_felt_eq(global_exit_code, exit_code); - }, - // Non-first proofs: verify global values are same and transitions are valid. - |builder| { - // Assert that committed_values_digest and exit_code are the same - for j in 0..(PV_DIGEST_NUM_WORDS * WORD_SIZE) { - let global_element = - builder.get(&global_committed_values_digest.bytes, j); - let element = builder.get(&committed_value_digest.bytes, j); - builder.assert_felt_eq(global_element, element); - } - builder.assert_felt_eq(global_exit_code, exit_code); - - // Shard should be previous next_shard. - builder.assert_felt_eq(start_shard, prev_next_shard); - // Start pc should be equal to next_pc declared in previous proof. - builder.assert_felt_eq(start_pc, prev_next_pc); - }, - ); - builder.if_eq(proof_index, num_proofs - one).then_or_else( - // If it's the last proof, set global end variables. - |builder| { - builder.assign(global_next_shard, next_shard); - builder.assign(global_next_pc, next_pc); - }, - // If it's not the last proof, update previous values. - |builder| { - builder.assign(prev_next_pc, next_pc); - builder.assign(prev_next_shard, next_shard); - }, - ); - }; - - // Verify sp1 and recursive proofs. - builder.range(0, num_proofs).for_each(|i, builder| { - let proof = builder.get(&proofs, i); - let sorted_indices = builder.get(&sorted_indices, i); - let chip_quotient_data = builder.get(&chip_quotient_data, i); - let is_recursive = builder.get(&is_recursive_flags, i); - - builder.if_eq(is_recursive, zero).then_or_else( - // Handle the case where the proof is a sp1 proof. - |builder| { - // Clone the variable pointer to reconstruct_challenger. - let reconstruct_challenger = reconstruct_challenger.clone(); - // Extract public values. - let mut pv_elements = Vec::new(); - for i in 0..PROOF_MAX_NUM_PVS { - let element = builder.get(&proof.public_values, i); - pv_elements.push(element); - } - let pv = PublicValues::>, Felt<_>>::from_vec(pv_elements); - - // Verify shard transitions. - let next_shard: Felt<_> = builder.uninit(); - let next_pc_var = felt2var(builder, pv.next_pc); - builder.if_eq(next_pc_var, zero).then_or_else( - // If next_pc is 0, then next_shard should be 0. - |builder| { - builder.assign(next_shard, zero_felt); - }, - // Otherwise, next_shard should be shard + 1. - |builder| { - let shard_plus_one: Felt<_> = builder.eval(pv.shard + one_felt); - builder.assign(next_shard, shard_plus_one); - }, - ); - constrain_shard_transitions( - i, - builder, - &pv.committed_value_digest, - pv.start_pc, - pv.next_pc, - pv.shard, - next_shard, - pv.exit_code, - ); - - // Need to convert the shard as a felt to a variable, since `if_eq` only handles - // variables. - let shard_f = pv.shard; - let shard = felt2var(builder, shard_f); - - // Handle the case where the shard is the first shard. - builder.if_eq(shard, one).then(|builder| { - // This should be the first proof as well - builder.assert_var_eq(i, zero); - - // Start pc should be sp1_vk.pc_start - builder.assert_felt_eq(pv.start_pc, sp1_vk.pc_start); - - // Clone the variable pointer to verify_start_challenger. - let mut reconstruct_challenger = reconstruct_challenger.clone(); - // Initialize the reconstruct challenger from empty challenger. - reconstruct_challenger.reset(builder); - reconstruct_challenger.observe(builder, sp1_vk.commitment.clone()); - reconstruct_challenger.observe(builder, sp1_vk.pc_start); - - // Make sure the start reconstruct challenger is correct, since we will - // commit to it in public values. - start_reconstruct_challenger.assert_eq(builder, &reconstruct_challenger); - - // Make sure start reconstruct deferred digest is fully zero. - for j in 0..POSEIDON_NUM_WORDS { - let element = builder.get(&start_reconstruct_deferred_digest, j); - builder.assert_felt_eq(element, zero_felt); - } - }); - - // Observe current proof commit and public values into reconstruct challenger. - for j in 0..DIGEST_SIZE { - let element = builder.get(&proof.commitment.main_commit, j); - reconstruct_challenger.clone().observe(builder, element); - } - for j in 0..SP1_PROOF_NUM_PV_ELTS { - let element = builder.get(&proof.public_values, j); - reconstruct_challenger.clone().observe(builder, element); - } - - // Accumulate lookup bus. - let num_chips = proof.opened_values.chips.len(); - builder.range(0, num_chips).for_each(|j, builder| { - let chip = builder.get(&proof.opened_values.chips, j); - let new_sum: Ext<_, _> = - builder.eval(global_cumulative_sum + chip.cumulative_sum); - builder.assign(global_cumulative_sum, new_sum); - }); - - // Verify proof with copy of witnessed challenger. - let mut current_challenger = verify_start_challenger.copy(builder); - - // Verify the shard. - StarkVerifier::::verify_shard( - builder, - &sp1_vk.clone(), - &sp1_pcs, - &core_machine, - &mut current_challenger, - &proof, - chip_quotient_data.clone(), - sorted_indices.clone(), - prep_sorted_indices.clone(), - prep_domains.clone(), - ); - }, - // Handle the case where the proof is a recursive proof. - |builder| { - let mut reconstruct_challenger = reconstruct_challenger.clone(); - let mut pv_elements = Vec::new(); - for i in 0..PROOF_MAX_NUM_PVS { - let element = builder.get(&proof.public_values, i); - pv_elements.push(element); - } - let pv = RecursionPublicValues::>::from_vec(pv_elements); - - // Verify shard transitions. - constrain_shard_transitions( - i, - builder, - &pv.committed_value_digest, - pv.start_pc, - pv.next_pc, - pv.start_shard, - pv.next_shard, - pv.exit_code, - ); - - // Assert that the current reconstruct_challenger is the same as the proof's - // start_reconstruct_challenger, then fast-forward to end_reconstruct_challenger. - assert_challenger_eq_pv( - builder, - &reconstruct_challenger, - pv.start_reconstruct_challenger, - ); - assign_challenger_from_pv( - builder, - &mut reconstruct_challenger, - pv.end_reconstruct_challenger, - ); - - // Assert that the current deferred_proof_digest is the same as the proof's - // start_reconstruct_deferred_digest, then fast-forward to end digest. - for j in 0..DIGEST_SIZE { - let element = builder.get(&reconstruct_deferred_digest, j); - builder.assert_felt_eq(element, pv.start_reconstruct_deferred_digest[j]); - } - for j in 0..DIGEST_SIZE { - builder.set( - &mut reconstruct_deferred_digest, - j, - pv.end_reconstruct_deferred_digest[j], - ); - } - - // Assert that sp1_vk, recursion_vk, and verify_start_challenger are the same. - for j in 0..DIGEST_SIZE { - let element = builder.get(&sp1_vk_digest, j); - builder.assert_felt_eq(element, pv.sp1_vk_digest[j]); - } - for j in 0..DIGEST_SIZE { - let element = builder.get(&recursion_vk_digest, j); - builder.assert_felt_eq(element, pv.recursion_vk_digest[j]); - } - assert_challenger_eq_pv( - builder, - &verify_start_challenger, - pv.verify_start_challenger, - ); - - // Accumulate lookup bus. - let pv_cumulative_sum = builder.ext_from_base_slice(&pv.cumulative_sum); - let new_sum: Ext<_, _> = - builder.eval(global_cumulative_sum + pv_cumulative_sum); - builder.assign(global_cumulative_sum, new_sum); - - // Setup the recursive challenger to use for verifying. - let mut current_challenger = recursion_challenger.copy(builder); - for j in 0..DIGEST_SIZE { - let element = builder.get(&proof.commitment.main_commit, j); - current_challenger.observe(builder, element); - } - builder.range(0, PROOF_MAX_NUM_PVS).for_each(|j, builder| { - let element = builder.get(&proof.public_values, j); - current_challenger.observe(builder, element); - }); - - builder.if_eq(is_compressed, BabyBear::one()).then_or_else( - |builder| { - StarkVerifier::::verify_shard( - builder, - &compress_vk, - &compress_pcs, - &compress_machine, - &mut current_challenger.clone(), - &proof, - chip_quotient_data.clone(), - sorted_indices.clone(), - reduce_prep_sorted_indices.clone(), - reduce_prep_domains.clone(), - ); - }, - |builder| { - StarkVerifier::::verify_shard( - builder, - &reduce_vk, - &reduce_pcs, - &reduce_machine, - &mut current_challenger.clone(), - &proof, - chip_quotient_data.clone(), - sorted_indices.clone(), - reduce_prep_sorted_indices.clone(), - reduce_prep_domains.clone(), - ); - }, - ) - }, - ); - }); - - // If num_proofs is 0, set end values to same as start values. - builder.if_eq(num_proofs, zero).then(|builder| { - builder.assign(global_next_shard, global_start_shard); - builder.assign(global_next_pc, global_start_pc); - }); - - // Verify deferred proofs and acculumate to deferred proofs digest. - builder - .range(0, num_deferred_proofs) - .for_each(|i, builder| { - let proof = builder.get(&deferred_proofs, i); - let sorted_indices = builder.get(&deferred_sorted_indices, i); - let chip_quotient_data = builder.get(&deferred_chip_quotient_data, i); - let mut challenger = recursion_challenger.copy(builder); - for j in 0..DIGEST_SIZE { - let element = builder.get(&proof.commitment.main_commit, j); - challenger.observe(builder, element); - } - builder.range(0, PROOF_MAX_NUM_PVS).for_each(|j, builder| { - let element = builder.get(&proof.public_values, j); - challenger.observe(builder, element); - }); - - // Validate proof public values. - // 1) Ensure that the proof is complete. - let mut pv_elements = Vec::new(); - for i in 0..PROOF_MAX_NUM_PVS { - let element = builder.get(&proof.public_values, i); - pv_elements.push(element); - } - let pv = RecursionPublicValues::>::from_vec(pv_elements); - builder.assert_felt_eq(pv.is_complete, one_felt); - // 2) Ensure recursion vkey is correct - for j in 0..DIGEST_SIZE { - let element = builder.get(&recursion_vk_digest, j); - builder.assert_felt_eq(element, pv.recursion_vk_digest[j]); - } - - // Verify the shard. - StarkVerifier::::verify_shard( - builder, - &reduce_vk.clone(), - &reduce_pcs, - &reduce_machine, - &mut challenger, - &proof, - chip_quotient_data.clone(), - sorted_indices.clone(), - reduce_prep_sorted_indices.clone(), - reduce_prep_domains.clone(), - ); - - // Update deferred proof digest - // poseidon2( current_digest[..8] || pv.sp1_vk_digest[..8] || pv.committed_value_digest[..32] ) - let mut poseidon_inputs = builder.array(48); - builder.range(0, 8).for_each(|j, builder| { - let element = builder.get(&reconstruct_deferred_digest, j); - builder.set(&mut poseidon_inputs, j, element); - }); - for j in 0..DIGEST_SIZE { - let input_index: Var<_> = builder.constant(F::from_canonical_usize(j + 8)); - builder.set(&mut poseidon_inputs, input_index, pv.sp1_vk_digest[j]); - } - for j in 0..PV_DIGEST_NUM_WORDS { - for k in 0..WORD_SIZE { - let input_index: Var<_> = - builder.eval(F::from_canonical_usize(j * WORD_SIZE + k + 16)); - let element = pv.committed_value_digest[j][k]; - builder.set(&mut poseidon_inputs, input_index, element); - } - } - let new_digest = builder.poseidon2_hash(&poseidon_inputs); - for j in 0..DIGEST_SIZE { - let element = builder.get(&new_digest, j); - builder.set(&mut reconstruct_deferred_digest, j, element); - } - }); - - // If witnessed as complete, then verify all of the final state is correct. - builder.if_eq(is_complete, one).then_or_else( - |builder| { - // 1) Proof begins at shard == 1. - let global_start_shard_var = felt2var(builder, global_start_shard); - builder.assert_var_eq(global_start_shard_var, one); - - // 2) Proof begins at pc == sp1_vk.pc_start. - builder.assert_felt_eq(global_start_pc, sp1_vk.pc_start); - - // 3) Execution has halted (next_pc == 0 && next_shard == 0). - let global_next_pc_var = felt2var(builder, global_next_pc); - builder.assert_var_eq(global_next_pc_var, zero); - let global_next_shard_var = felt2var(builder, global_next_shard); - builder.assert_var_eq(global_next_shard_var, zero); - - // 4) reconstruct_challenger has been fully reconstructed. - // a) start_reconstruct_challenger == challenger after observing vk and pc_start. - let mut expected_challenger = DuplexChallengerVariable::new(builder); - expected_challenger.observe(builder, sp1_vk.commitment.clone()); - expected_challenger.observe(builder, sp1_vk.pc_start); - start_reconstruct_challenger.assert_eq(builder, &expected_challenger); - // b) end_reconstruct_challenger == verify_start_challenger. - reconstruct_challenger.assert_eq(builder, &verify_start_challenger); - - // 5) reconstruct_deferred_digest has been fully reconstructed. - // a) start_reconstruct_deferred_digest == 0. - for j in 0..DIGEST_SIZE { - let element = builder.get(&start_reconstruct_deferred_digest, j); - builder.assert_felt_eq(element, zero_felt); - } - // b) end_reconstruct_deferred_digest == deferred_proofs_digest. - for j in 0..DIGEST_SIZE { - let element = builder.get(&reconstruct_deferred_digest, j); - let global_element = builder.get(&global_deferred_proofs_digest, j); - builder.assert_felt_eq(element, global_element); - } - - // 6) Verify that the cumulative sum is zero. - let zero_ext: Ext<_, _> = builder.eval(EF::zero().cons()); - builder.assert_ext_eq(global_cumulative_sum, zero_ext); - }, - // Ensure is_complete is boolean. - |builder| { - builder.assert_var_eq(is_complete, zero); - }, - ); - - // Public values: - // ( - // committed_values_digest, - // deferred_proofs_digest, - // start_pc, - // next_pc, - // exit_code, - // start_shard, - // end_shard, - // start_reconstruct_challenger, - // end_reconstruct_challenger, - // start_reconstruct_deferred_digest, - // end_reconstruct_deferred_digest, - // sp1_vk_digest, - // recursion_vk_digest, - // verify_start_challenger, - // cumulative_sum, - // is_complete, - // ) - for j in 0..(PV_DIGEST_NUM_WORDS * WORD_SIZE) { - let element = builder.get(&global_committed_values_digest.bytes, j); - builder.commit_public_value(element); - } - for j in 0..POSEIDON_NUM_WORDS { - let element = builder.get(&global_deferred_proofs_digest, j); - builder.commit_public_value(element); - } - builder.commit_public_value(global_start_pc); - builder.commit_public_value(global_next_pc); - builder.commit_public_value(global_exit_code); - builder.commit_public_value(global_start_shard); - builder.commit_public_value(global_next_shard); - commit_challenger(&mut builder, &start_reconstruct_challenger); - commit_challenger(&mut builder, &reconstruct_challenger); - builder.range(0, POSEIDON_NUM_WORDS).for_each(|j, builder| { - let element = builder.get(&start_reconstruct_deferred_digest, j); - builder.commit_public_value(element); - }); - builder.range(0, POSEIDON_NUM_WORDS).for_each(|j, builder| { - let element = builder.get(&reconstruct_deferred_digest, j); - builder.commit_public_value(element); - }); - builder.range(0, DIGEST_SIZE).for_each(|j, builder| { - let element = builder.get(&sp1_vk_digest, j); - builder.commit_public_value(element); - }); - builder.range(0, DIGEST_SIZE).for_each(|j, builder| { - let element = builder.get(&recursion_vk_digest, j); - builder.commit_public_value(element); - }); - commit_challenger(&mut builder, &verify_start_challenger); - let cumulative_sum_felts = builder.ext2felt(global_cumulative_sum); - builder.commit_public_values(&cumulative_sum_felts); - let is_complete_felt = var2felt(&mut builder, is_complete); - builder.commit_public_value(is_complete_felt); - - builder.compile_program() - } -} diff --git a/recursion/program/src/stark.rs b/recursion/program/src/stark.rs index f92ac770b2..86fcd11762 100644 --- a/recursion/program/src/stark.rs +++ b/recursion/program/src/stark.rs @@ -5,12 +5,16 @@ use p3_field::TwoAdicField; use sp1_core::air::MachineAir; use sp1_core::stark::Com; use sp1_core::stark::GenericVerifierConstraintFolder; +use sp1_core::stark::ShardProof; use sp1_core::stark::StarkGenericConfig; use sp1_core::stark::StarkMachine; +use sp1_core::stark::StarkVerifyingKey; use sp1_recursion_compiler::ir::Array; use sp1_recursion_compiler::ir::Ext; +use sp1_recursion_compiler::ir::ExtConst; use sp1_recursion_compiler::ir::SymbolicExt; +use sp1_recursion_compiler::ir::SymbolicVar; use sp1_recursion_compiler::ir::Var; use sp1_recursion_compiler::ir::{Builder, Config, Usize}; use sp1_recursion_compiler::prelude::Felt; @@ -32,11 +36,106 @@ use crate::types::QuotientData; pub const EMPTY: usize = 0x_1111_1111; +pub trait StarkRecursiveVerifier { + fn verify_shard( + &self, + builder: &mut Builder, + vk: &VerifyingKeyVariable, + pcs: &TwoAdicFriPcsVariable, + challenger: &mut DuplexChallengerVariable, + proof: &ShardProofVariable, + is_complete: impl Into>, + ); + + fn verify_shards( + &self, + builder: &mut Builder, + vk: &VerifyingKeyVariable, + pcs: &TwoAdicFriPcsVariable, + challenger: &mut DuplexChallengerVariable, + proofs: &Array>, + is_complete: impl Into> + Clone, + ) { + // Assert that the number of shards is not zero. + builder.assert_usize_ne(proofs.len(), 0); + + // Verify each shard. + builder.range(0, proofs.len()).for_each(|i, builder| { + let proof = builder.get(proofs, i); + self.verify_shard(builder, vk, pcs, challenger, &proof, is_complete.clone()); + }); + } +} + #[derive(Debug, Clone, Copy)] pub struct StarkVerifier { _phantom: std::marker::PhantomData<(C, SC)>, } +pub struct ShardProofHint<'a, SC: StarkGenericConfig, A> { + pub machine: &'a StarkMachine, + pub proof: &'a ShardProof, +} + +impl<'a, SC: StarkGenericConfig, A: MachineAir> ShardProofHint<'a, SC, A> { + pub fn new(machine: &'a StarkMachine, proof: &'a ShardProof) -> Self { + Self { machine, proof } + } +} + +pub struct VerifyingKeyHint<'a, SC: StarkGenericConfig, A> { + pub machine: &'a StarkMachine, + pub vk: &'a StarkVerifyingKey, +} + +impl<'a, SC: StarkGenericConfig, A: MachineAir> VerifyingKeyHint<'a, SC, A> { + pub fn new(machine: &'a StarkMachine, vk: &'a StarkVerifyingKey) -> Self { + Self { machine, vk } + } +} + +impl StarkRecursiveVerifier for StarkMachine +where + C::F: TwoAdicField, + SC: StarkGenericConfig< + Val = C::F, + Challenge = C::EF, + Domain = TwoAdicMultiplicativeCoset, + >, + A: MachineAir + for<'a> Air>, + C::F: TwoAdicField, + C::EF: TwoAdicField, + Com: Into<[SC::Val; DIGEST_SIZE]>, +{ + fn verify_shard( + &self, + builder: &mut Builder, + vk: &VerifyingKeyVariable, + pcs: &TwoAdicFriPcsVariable, + challenger: &mut DuplexChallengerVariable, + proof: &ShardProofVariable, + is_complete: impl Into::N>>, + ) { + // Verify the shard proof. + StarkVerifier::::verify_shard(builder, vk, pcs, self, challenger, proof); + + // Verify that the cumulative sum of the chip is zero if the shard is complete. + let cumulative_sum: Ext<_, _> = builder.uninit(); + builder + .range(0, proof.opened_values.chips.len()) + .for_each(|i, builder| { + let values = builder.get(&proof.opened_values.chips, i); + builder.assign(cumulative_sum, cumulative_sum + values.cumulative_sum); + }); + + builder + .if_eq(is_complete.into(), C::N::one()) + .then(|builder| { + builder.assert_ext_eq(cumulative_sum, C::EF::zero().cons()); + }); + } +} + pub type RecursiveVerifierConstraintFolder<'a, C> = GenericVerifierConstraintFolder< 'a, ::F, @@ -62,10 +161,6 @@ where machine: &StarkMachine, challenger: &mut DuplexChallengerVariable, proof: &ShardProofVariable, - chip_quotient_data: Array>, - chip_sorted_idxs: Array>, - preprocessed_sorted_idxs: Array>, - prep_domains: Array>, ) where A: MachineAir + for<'a> Air>, C::F: TwoAdicField, @@ -114,7 +209,7 @@ where let num_quotient_mats: Var<_> = builder.eval(C::N::zero()); builder.range(0, num_shard_chips).for_each(|i, builder| { - let num_quotient_chunks = builder.get(&chip_quotient_data, i).quotient_size; + let num_quotient_chunks = builder.get(&proof.quotient_data, i).quotient_size; builder.assign(num_quotient_mats, num_quotient_mats + num_quotient_chunks); }); @@ -127,12 +222,12 @@ where // Iterate through machine.chips filtered for preprocessed chips. for (preprocessed_id, chip_id) in machine.preprocessed_chip_ids().into_iter().enumerate() { // Get index within sorted preprocessed chips. - let preprocessed_sorted_id = builder.get(&preprocessed_sorted_idxs, preprocessed_id); + let preprocessed_sorted_id = builder.get(&vk.preprocessed_sorted_idxs, preprocessed_id); // Get domain from witnessed domains. Array is ordered by machine.chips ordering. - let domain = builder.get(&prep_domains, preprocessed_id); + let domain = builder.get(&vk.prep_domains, preprocessed_id); // Get index within all sorted chips. - let chip_sorted_id = builder.get(&chip_sorted_idxs, chip_id); + let chip_sorted_id = builder.get(&proof.sorted_idxs, chip_id); // Get opening from proof. let opening = builder.get(&opened_values.chips, chip_sorted_id); @@ -159,7 +254,7 @@ where let QuotientData { log_quotient_degree, quotient_size, - } = builder.get(&chip_quotient_data, i); + } = builder.get(&proof.quotient_data, i); let domain = pcs.natural_domain_for_log_degree(builder, Usize::Var(opening.log_degree)); builder.set_value(&mut trace_domains, i, domain.clone()); @@ -253,7 +348,7 @@ where for (i, chip) in machine.chips().iter().enumerate() { let chip_name = chip.name(); tracing::debug!("verifying constraints for chip: {}", chip_name); - let index = builder.get(&chip_sorted_idxs, i); + let index = builder.get(&proof.sorted_idxs, i); if chip.preprocessed_width() > 0 { builder.assert_var_ne(index, C::N::from_canonical_usize(EMPTY)); @@ -271,7 +366,7 @@ where let log_quotient_degree = chip.log_quotient_degree(); let quotient_size = 1 << log_quotient_degree; - let chip_quotient_data = builder.get(&chip_quotient_data, index); + let chip_quotient_data = builder.get(&proof.quotient_data, index); builder.assert_usize_eq( chip_quotient_data.log_quotient_degree, log_quotient_degree, @@ -308,6 +403,7 @@ pub(crate) mod tests { use crate::hints::Hintable; use crate::stark::DuplexChallengerVariable; use crate::stark::Ext; + use crate::stark::ShardProofHint; use crate::types::ShardCommitmentVariable; use p3_challenger::{CanObserve, FieldChallenger}; use p3_field::AbstractField; @@ -318,7 +414,7 @@ pub(crate) mod tests { use sp1_core::utils::InnerChallenge; use sp1_core::utils::InnerVal; use sp1_core::{ - stark::{RiscvAir, ShardProof, StarkGenericConfig}, + stark::{RiscvAir, StarkGenericConfig}, utils::BabyBearPoseidon2, }; use sp1_recursion_compiler::config::InnerConfig; @@ -377,8 +473,9 @@ pub(crate) mod tests { let mut witness_stream = Vec::new(); for proof in proofs { - witness_stream.extend(proof.write()); - let proof = ShardProof::::read(&mut builder); + let proof_hint = ShardProofHint::new(&machine, &proof); + witness_stream.extend(proof_hint.write()); + let proof = ShardProofHint::::read(&mut builder); let ShardCommitmentVariable { main_commit, .. } = proof.commitment; challenger.observe(&mut builder, main_commit); let pv_slice = proof.public_values.slice( diff --git a/recursion/program/src/types.rs b/recursion/program/src/types.rs index c53811a144..db49b10eb5 100644 --- a/recursion/program/src/types.rs +++ b/recursion/program/src/types.rs @@ -17,6 +17,8 @@ pub struct ShardProofVariable { pub opened_values: ShardOpenedValuesVariable, pub opening_proof: TwoAdicPcsProofVariable, pub public_values: Array>, + pub quotient_data: Array>, + pub sorted_idxs: Array>, } #[derive(DslVariable, Clone, Copy)] @@ -36,6 +38,8 @@ pub struct QuotientDataValues { pub struct VerifyingKeyVariable { pub commitment: DigestVariable, pub pc_start: Felt, + pub preprocessed_sorted_idxs: Array>, + pub prep_domains: Array>, } /// Reference: [sp1_core::stark::ShardCommitment] diff --git a/recursion/program/src/utils.rs b/recursion/program/src/utils.rs index 550b9e0e43..92b46c546e 100644 --- a/recursion/program/src/utils.rs +++ b/recursion/program/src/utils.rs @@ -6,7 +6,8 @@ use p3_fri::FriConfig; use p3_merkle_tree::FieldMerkleTreeMmcs; use p3_poseidon2::{Poseidon2, Poseidon2ExternalMatrixGeneral}; use p3_symmetric::{PaddingFreeSponge, TruncatedPermutation}; -use sp1_core::stark::StarkGenericConfig; +use sp1_core::air::MachineAir; +use sp1_core::stark::{Dom, ShardProof, StarkGenericConfig, StarkMachine, StarkVerifyingKey}; use sp1_core::utils::BabyBearPoseidon2; use sp1_recursion_compiler::asm::AsmConfig; use sp1_recursion_compiler::ir::{Array, Builder, Config, Felt, MemVariable, Var}; @@ -16,7 +17,8 @@ use sp1_recursion_core::runtime::{DIGEST_SIZE, PERMUTATION_WIDTH}; use crate::challenger::DuplexChallengerVariable; use crate::fri::types::FriConfigVariable; use crate::fri::TwoAdicMultiplicativeCosetVariable; -use crate::types::VerifyingKeyVariable; +use crate::stark::EMPTY; +use crate::types::{QuotientDataValues, VerifyingKeyVariable}; type SC = BabyBearPoseidon2; type F = ::Val; @@ -35,7 +37,7 @@ type RecursionBuilder = Builder; pub fn const_fri_config( builder: &mut RecursionBuilder, - config: FriConfig, + config: &FriConfig, ) -> FriConfigVariable { let two_addicity = Val::TWO_ADICITY; let mut generators = builder.dyn_array(two_addicity); @@ -165,15 +167,32 @@ pub fn commit_challenger(builder: &mut Builder, var: &DuplexChalle } } +pub fn get_challenger_public_values( + builder: &mut Builder, + var: &DuplexChallengerVariable, +) -> ChallengerPublicValues> { + let sponge_state = core::array::from_fn(|i| builder.get(&var.sponge_state, i)); + let num_inputs = var2felt(builder, var.nb_inputs); + let input_buffer = core::array::from_fn(|i| builder.get(&var.input_buffer, i)); + let num_outputs = var2felt(builder, var.nb_outputs); + let output_buffer = core::array::from_fn(|i| builder.get(&var.output_buffer, i)); + + ChallengerPublicValues { + sponge_state, + num_inputs, + input_buffer, + num_outputs, + output_buffer, + } +} + /// Hash the verifying key + prep domains into a single digest. /// poseidon2( commit[0..8] || pc_start || prep_domains[N].{log_n, .size, .shift, .g}) pub fn hash_vkey( builder: &mut Builder, vk: &VerifyingKeyVariable, - prep_domains: &Array>, - prep_sorted_indices: &Array>, ) -> Array> { - let domain_slots: Var<_> = builder.eval(prep_domains.len() * 4); + let domain_slots: Var<_> = builder.eval(vk.prep_domains.len() * 4); let vkey_slots: Var<_> = builder.constant(C::N::from_canonical_usize(DIGEST_SIZE + 1)); let total_slots: Var<_> = builder.eval(vkey_slots + domain_slots); let mut inputs = builder.dyn_array(total_slots); @@ -184,19 +203,68 @@ pub fn hash_vkey( builder.set(&mut inputs, DIGEST_SIZE, vk.pc_start); let four: Var<_> = builder.constant(C::N::from_canonical_usize(4)); let one: Var<_> = builder.constant(C::N::one()); - builder.range(0, prep_domains.len()).for_each(|i, builder| { - let sorted_index = builder.get(prep_sorted_indices, i); - let domain = builder.get(prep_domains, i); - let log_n_index: Var<_> = builder.eval(vkey_slots + sorted_index * four); - let size_index: Var<_> = builder.eval(log_n_index + one); - let shift_index: Var<_> = builder.eval(size_index + one); - let g_index: Var<_> = builder.eval(shift_index + one); - let log_n_felt = var2felt(builder, domain.log_n); - let size_felt = var2felt(builder, domain.size); - builder.set(&mut inputs, log_n_index, log_n_felt); - builder.set(&mut inputs, size_index, size_felt); - builder.set(&mut inputs, shift_index, domain.shift); - builder.set(&mut inputs, g_index, domain.g); - }); + builder + .range(0, vk.prep_domains.len()) + .for_each(|i, builder| { + let sorted_index = builder.get(&vk.preprocessed_sorted_idxs, i); + let domain = builder.get(&vk.prep_domains, i); + let log_n_index: Var<_> = builder.eval(vkey_slots + sorted_index * four); + let size_index: Var<_> = builder.eval(log_n_index + one); + let shift_index: Var<_> = builder.eval(size_index + one); + let g_index: Var<_> = builder.eval(shift_index + one); + let log_n_felt = var2felt(builder, domain.log_n); + let size_felt = var2felt(builder, domain.size); + builder.set(&mut inputs, log_n_index, log_n_felt); + builder.set(&mut inputs, size_index, size_felt); + builder.set(&mut inputs, shift_index, domain.shift); + builder.set(&mut inputs, g_index, domain.g); + }); builder.poseidon2_hash(&inputs) } + +pub(crate) fn get_sorted_indices>( + machine: &StarkMachine, + proof: &ShardProof, +) -> Vec { + machine + .chips_sorted_indices(proof) + .into_iter() + .map(|x| match x { + Some(x) => x, + None => EMPTY, + }) + .collect() +} + +pub(crate) fn get_preprocessed_data>( + machine: &StarkMachine, + vk: &StarkVerifyingKey, +) -> (Vec, Vec>) { + let chips = machine.chips(); + let (prep_sorted_indices, prep_domains) = machine + .preprocessed_chip_ids() + .into_iter() + .map(|chip_idx| { + let name = chips[chip_idx].name().clone(); + let prep_sorted_idx = vk.chip_ordering[&name]; + (prep_sorted_idx, vk.chip_information[prep_sorted_idx].1) + }) + .unzip(); + (prep_sorted_indices, prep_domains) +} + +pub(crate) fn get_chip_quotient_data>( + machine: &StarkMachine, + proof: &ShardProof, +) -> Vec { + machine + .shard_chips_ordered(&proof.chip_ordering) + .map(|chip| { + let log_quotient_degree = chip.log_quotient_degree(); + QuotientDataValues { + log_quotient_degree, + quotient_size: 1 << log_quotient_degree, + } + }) + .collect() +} diff --git a/sdk/src/provers/local.rs b/sdk/src/provers/local.rs index cd4f72dc53..3c062bdeea 100644 --- a/sdk/src/provers/local.rs +++ b/sdk/src/provers/local.rs @@ -60,8 +60,8 @@ impl Prover for LocalProver { let deferred_proofs = stdin.proofs.iter().map(|p| p.0.clone()).collect(); let public_values = proof.public_values.clone(); let reduce_proof = self.prover.compress(&pk.vk, proof, deferred_proofs); - let compress_proof = self.prover.shrink(&pk.vk, reduce_proof); - let outer_proof = self.prover.wrap_bn254(&pk.vk, compress_proof); + let compress_proof = self.prover.shrink(reduce_proof); + let outer_proof = self.prover.wrap_bn254(compress_proof); let artifacts_dir = sp1_prover::build::get_groth16_artifacts_dir(); let proof = self.prover.wrap_groth16(outer_proof, artifacts_dir); Ok(SP1ProofWithPublicValues {