Skip to content

Commit

Permalink
fix: deferred proofs + cleanup hash_vkey (#615)
Browse files Browse the repository at this point in the history
  • Loading branch information
ctian1 authored Apr 26, 2024
1 parent f070638 commit 0add69b
Show file tree
Hide file tree
Showing 12 changed files with 152 additions and 178 deletions.
40 changes: 0 additions & 40 deletions core/src/stark/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,18 @@ use std::fmt::Debug;

use itertools::Itertools;
use p3_air::Air;
use p3_baby_bear::BabyBear;
use p3_challenger::CanObserve;
use p3_challenger::FieldChallenger;
use p3_commit::Pcs;
use p3_commit::TwoAdicMultiplicativeCoset;
use p3_field::AbstractField;
use p3_field::Field;
use p3_field::PrimeField32;
use p3_field::TwoAdicField;
use p3_matrix::dense::RowMajorMatrix;
use p3_matrix::Dimensions;
use p3_matrix::Matrix;
use p3_maybe_rayon::prelude::*;
use serde::Deserialize;
use serde::Serialize;
use sp1_primitives::poseidon2_hash;

use super::debug_constraints;
use super::Dom;
Expand All @@ -34,7 +30,6 @@ use crate::stark::DebugConstraintBuilder;
use crate::stark::ProverConstraintFolder;
use crate::stark::ShardProof;
use crate::stark::VerifierConstraintFolder;
use crate::utils::DIGEST_SIZE;

use super::Chip;
use super::Com;
Expand Down Expand Up @@ -107,41 +102,6 @@ impl<SC: StarkGenericConfig> Debug for StarkVerifyingKey<SC> {
}
}

impl<
SC: StarkGenericConfig<Val = BabyBear, Domain = TwoAdicMultiplicativeCoset<BabyBear>>,
A: MachineAir<BabyBear>,
> StarkMachine<SC, A>
where
<SC::Pcs as Pcs<SC::Challenge, SC::Challenger>>::Commitment: AsRef<[BabyBear; DIGEST_SIZE]>,
{
/// Hash the verifying key, producing a single commitment that uniquely identifies the program
/// being proven.
///
/// poseidon2( commit[0..8] || pc_start || prep_domains[N].{log_n, .size, .shift, .g} )
pub fn hash_vkey(&self, vkey: &StarkVerifyingKey<SC>) -> [BabyBear; DIGEST_SIZE] {
// TODO: this should live in SP1VerifyingKey
let prep_domains = self.preprocessed_chip_ids().into_iter().map(|chip_idx| {
let name = self.chips[chip_idx].name().clone();
let prep_sorted_idx = vkey.chip_ordering[&name];
vkey.chip_information[prep_sorted_idx].1
});
let num_inputs = DIGEST_SIZE + 1 + (4 * prep_domains.len());
let mut inputs = Vec::with_capacity(num_inputs);
inputs.extend(vkey.commit.as_ref());
inputs.push(vkey.pc_start);
for domain in prep_domains {
inputs.push(BabyBear::from_canonical_usize(domain.log_n));
let size = 1 << domain.log_n;
inputs.push(BabyBear::from_canonical_usize(size));
let g = BabyBear::two_adic_generator(domain.log_n);
inputs.push(domain.shift);
inputs.push(g);
}

poseidon2_hash(inputs)
}
}

impl<SC: StarkGenericConfig, A: MachineAir<Val<SC>>> StarkMachine<SC, A> {
/// Get an array containing a `ChipRef` for all the chips of this RISC-V STARK machine.
pub fn chips(&self) -> &[MachineChip<SC, A>] {
Expand Down
21 changes: 2 additions & 19 deletions core/src/utils/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use p3_symmetric::Hash;
use p3_symmetric::{PaddingFreeSponge, TruncatedPermutation};
use serde::Deserialize;
use serde::Serialize;
use sp1_primitives::RC_16_30;
use sp1_primitives::poseidon2_init;

pub const DIGEST_SIZE: usize = 8;

Expand Down Expand Up @@ -48,24 +48,7 @@ pub type InnerPcsProof =

/// The permutation for inner recursion.
pub fn inner_perm() -> InnerPerm {
const ROUNDS_F: usize = 8;
const ROUNDS_P: usize = 13;
let mut round_constants = RC_16_30.to_vec();
let internal_start = ROUNDS_F / 2;
let internal_end = (ROUNDS_F / 2) + ROUNDS_P;
let internal_round_constants = round_constants
.drain(internal_start..internal_end)
.map(|vec| vec[0])
.collect::<Vec<_>>();
let external_round_constants = round_constants;
InnerPerm::new(
ROUNDS_F,
external_round_constants,
Poseidon2ExternalMatrixGeneral,
ROUNDS_P,
internal_round_constants,
DiffusionMatrixBabyBear,
)
poseidon2_init()
}

/// The FRI config for sp1 proofs.
Expand Down
2 changes: 1 addition & 1 deletion primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1102,7 +1102,7 @@ lazy_static! {
pub fn poseidon2_init(
) -> Poseidon2<BabyBear, Poseidon2ExternalMatrixGeneral, DiffusionMatrixBabyBear, 16, 7> {
const ROUNDS_F: usize = 8;
const ROUNDS_P: usize = 22;
const ROUNDS_P: usize = 13;
let mut round_constants = RC_16_30.to_vec();
let internal_start = ROUNDS_F / 2;
let internal_end = (ROUNDS_F / 2) + ROUNDS_P;
Expand Down
66 changes: 28 additions & 38 deletions prover/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,17 +154,15 @@ impl SP1Prover {
pub fn setup(&self, elf: &[u8]) -> (SP1ProvingKey, SP1VerifyingKey) {
let program = Program::from(elf);
let (pk, vk) = self.core_machine.setup(&program);
let pk = SP1ProvingKey { pk, program };
let vk = SP1VerifyingKey { vk };
let pk = SP1ProvingKey {
pk,
program,
vk: vk.clone(),
};
(pk, vk)
}

/// Hash a verifying key, producing a single commitment that uniquely identifies the program
/// being proven.
pub fn hash_vkey(&self, vk: &StarkVerifyingKey<CoreSC>) -> [Val<CoreSC>; 8] {
self.core_machine.hash_vkey(vk)
}

/// Accumulate deferred proofs into a single digest.
pub fn hash_deferred_proofs(
prev_digest: [Val<CoreSC>; 8],
Expand Down Expand Up @@ -485,6 +483,10 @@ impl SP1Prover {
indices
})
.collect();
let deferred_chip_quotient_data: Vec<Vec<QuotientDataValues>> = deferred_proofs
.iter()
.map(|p| get_chip_quotient_data(&self.reduce_machine, p))
.collect();

// Convert the inputs into a witness stream.
let mut witness_stream = Vec::new();
Expand Down Expand Up @@ -518,6 +520,7 @@ impl SP1Prover {
}
}
}
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 };
Expand Down Expand Up @@ -755,15 +758,18 @@ mod tests {
prover.wrap_groth16(wrapped_bn254_proof, PathBuf::from("build"));
}

/// This test ensures that a proof can be deferred in the core vm and verified in recursion.
#[test]
#[ignore]
fn test_deferred_verify() {
setup_logger();
std::env::set_var("RECONSTRUCT_COMMITMENTS", "false");
std::env::set_var("FRI_QUERIES", "1");
std::env::set_var("SHARD_SIZE", "262144");
std::env::set_var("MAX_RECURSION_PROGRAM_SIZE", "1");

// Generate SP1 proof
// keccak program which proves keccak of various inputs
let keccak_elf = include_bytes!("../../tests/keccak256/elf/riscv32im-succinct-zkvm-elf");

// verify program which verifies proofs of a vkey and a list of committed inputs
let verify_elf = include_bytes!("../../tests/verify-proof/elf/riscv32im-succinct-zkvm-elf");

tracing::info!("initializing prover");
Expand All @@ -773,21 +779,12 @@ mod tests {
let (keccak_pk, keccak_vk) = prover.setup(keccak_elf);
let (verify_pk, verify_vk) = prover.setup(verify_elf);

// Prove keccak of various inputs
tracing::info!("prove subproof 1");
let mut stdin = SP1Stdin::new();
stdin.write(&1usize);
stdin.write(&vec![0u8, 0, 0]);
// Read proof from p1.bin if exists
let p1_file = std::fs::File::open("p1.bin");
let deferred_proof_1 = match p1_file {
Ok(file) => bincode::deserialize_from(file).unwrap(),
Err(_) => {
let deferred_proof_1 = prover.prove_core(&keccak_pk, &stdin);
let file = std::fs::File::create("p1.bin").unwrap();
bincode::serialize_into(file, &deferred_proof_1).unwrap();
deferred_proof_1
}
};
let deferred_proof_1 = prover.prove_core(&keccak_pk, &stdin);
let pv_1 = deferred_proof_1.public_values.buffer.data.clone();
println!("proof 1 pv: {:?}", hex::encode(pv_1.clone()));
let pv_digest_1 = deferred_proof_1.shard_proofs[0].public_values[..32]
Expand All @@ -796,23 +793,14 @@ mod tests {
.collect::<Vec<_>>();
println!("proof 1 pv_digest: {:?}", hex::encode(pv_digest_1.clone()));

// Generate a second proof of keccak of various inputs
tracing::info!("prove subproof 2");
let mut stdin = SP1Stdin::new();
stdin.write(&3usize);
stdin.write(&vec![0u8, 1, 2]);
stdin.write(&vec![2, 3, 4]);
stdin.write(&vec![5, 6, 7]);
// Read proof from p2.bin if exists
let p2_file = std::fs::File::open("p2.bin");
let deferred_proof_2 = match p2_file {
Ok(file) => bincode::deserialize_from(file).unwrap(),
Err(_) => {
let deferred_proof_2 = prover.prove_core(&keccak_pk, &stdin);
let file = std::fs::File::create("p2.bin").unwrap();
bincode::serialize_into(file, &deferred_proof_2).unwrap();
deferred_proof_2
}
};
let deferred_proof_2 = prover.prove_core(&keccak_pk, &stdin);
let pv_2 = deferred_proof_2.public_values.buffer.data.clone();
println!("proof 2 pv: {:?}", hex::encode(pv_2.clone()));
let pv_digest_2 = deferred_proof_2.shard_proofs[0].public_values[..32]
Expand All @@ -821,14 +809,17 @@ mod tests {
.collect::<Vec<_>>();
println!("proof 2 pv_digest: {:?}", hex::encode(pv_digest_2.clone()));

// Generate recursive proof of first subproof
println!("reduce subproof 1");
let deferred_reduce_1 = prover.reduce(&keccak_vk, deferred_proof_1, vec![]);

// Generate recursive proof of second subproof
println!("reduce subproof 2");
let deferred_reduce_2 = prover.reduce(&keccak_vk, deferred_proof_2, vec![]);

// Run verify program with keccak vkey, subproofs, and their committed values
let mut stdin = SP1Stdin::new();
let vkey_digest = &prover.core_machine.hash_vkey(&keccak_vk.vk);
let vkey_digest = keccak_vk.hash();
let vkey_digest: [u32; 8] = vkey_digest
.iter()
.map(|n| n.as_canonical_u32())
Expand All @@ -841,6 +832,7 @@ mod tests {
stdin.write_proof(deferred_reduce_2.proof.clone(), keccak_vk.vk.clone());
stdin.write_proof(deferred_reduce_2.proof.clone(), keccak_vk.vk.clone());

// Prove verify program
println!("proving verify program (core)");
let verify_proof = prover.prove_core(&verify_pk, &stdin);
let pv = PublicValues::<Word<BabyBear>, BabyBear>::from_vec(
Expand All @@ -849,6 +841,7 @@ mod tests {

println!("deferred_hash: {:?}", pv.deferred_proofs_digest);

// Generate recursive proof of verify program
println!("proving verify program (recursion)");
let verify_reduce = prover.reduce(
&verify_vk,
Expand All @@ -863,10 +856,7 @@ mod tests {
println!("deferred_hash: {:?}", reduce_pv.deferred_proofs_digest);
println!("complete: {:?}", reduce_pv.is_complete);

println!("wrap");
let wrapped = prover.wrap_bn254(&verify_vk, verify_reduce);

tracing::info!("groth16");
prover.wrap_groth16(wrapped, PathBuf::from("build"));
// TODO: verify verify_reduce proof once shard transition logic is moved out of machine.verify
// prover.reduce_machine.verify(vk, proof, challenger)
}
}
27 changes: 26 additions & 1 deletion prover/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use p3_baby_bear::BabyBear;
use p3_field::AbstractField;
use p3_field::{AbstractField, TwoAdicField};
use serde::{Deserialize, Serialize};
use sp1_core::{
air::{PublicValues, Word, POSEIDON_NUM_WORDS, PV_DIGEST_NUM_WORDS},
io::{SP1PublicValues, SP1Stdin},
runtime::Program,
stark::{ShardProof, StarkGenericConfig, StarkProvingKey, StarkVerifyingKey, Val},
utils::DIGEST_SIZE,
};
use sp1_primitives::poseidon2_hash;
use sp1_recursion_core::air::RecursionPublicValues;

use crate::{CoreSC, InnerSC};
Expand All @@ -15,13 +17,36 @@ use crate::{CoreSC, InnerSC};
pub struct SP1ProvingKey {
pub pk: StarkProvingKey<CoreSC>,
pub program: Program,
/// Verifying key is also included as we need it for recursion
pub vk: SP1VerifyingKey,
}

/// The information necessary to verify a proof for a given RISC-V program.
#[derive(Clone)]
pub struct SP1VerifyingKey {
pub vk: StarkVerifyingKey<CoreSC>,
}

impl SP1VerifyingKey {
pub fn hash(&self) -> [BabyBear; 8] {
let prep_domains = self.vk.chip_information.iter().map(|(_, domain, _)| domain);
let num_inputs = DIGEST_SIZE + 1 + (4 * prep_domains.len());
let mut inputs = Vec::with_capacity(num_inputs);
inputs.extend(self.vk.commit.as_ref());
inputs.push(self.vk.pc_start);
for domain in prep_domains {
inputs.push(BabyBear::from_canonical_usize(domain.log_n));
let size = 1 << domain.log_n;
inputs.push(BabyBear::from_canonical_usize(size));
let g = BabyBear::two_adic_generator(domain.log_n);
inputs.push(domain.shift);
inputs.push(g);
}

poseidon2_hash(inputs)
}
}

/// A proof of a RISC-V execution with given inputs and outputs composed of multiple shard proofs.
#[derive(Serialize, Deserialize, Clone)]
pub struct SP1CoreProof {
Expand Down
23 changes: 19 additions & 4 deletions recursion/compiler/src/ir/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,21 @@ impl<T> TracedVec<T> {

pub fn push(&mut self, value: T) {
self.vec.push(value);
match std::env::var("SP1_DEBUG") {
Ok(_) => {
self.traces.push(None);
}

/// Pushes a value to the vector and records a backtrace if SP1_DEBUG is enabled
pub fn trace_push(&mut self, value: T) {
self.vec.push(value);
match std::env::var("SP1_DEBUG")
.unwrap_or("false".to_string())
.to_lowercase()
.as_str()
{
"true" => {
self.traces.push(Some(Backtrace::new_unresolved()));
}
Err(_) => {
_ => {
self.traces.push(None);
}
};
Expand Down Expand Up @@ -123,6 +133,11 @@ impl<C: Config> Builder<C> {
self.operations.push(op);
}

/// Pushes an operation to the builder and records a trace if SP1_DEBUG.
pub fn trace_push(&mut self, op: DslIr<C>) {
self.operations.trace_push(op);
}

/// Creates an uninitialized variable.
pub fn uninit<V: Variable<C>>(&mut self) -> V {
V::uninit(self)
Expand Down Expand Up @@ -394,7 +409,7 @@ impl<C: Config> Builder<C> {

/// Throws an error.
pub fn error(&mut self) {
self.operations.push(DslIr::Error());
self.operations.trace_push(DslIr::Error());
}

/// Materializes a usize into a variable.
Expand Down
Loading

0 comments on commit 0add69b

Please sign in to comment.