diff --git a/starky/src/batch_proof.rs b/starky/src/batch_proof.rs new file mode 100644 index 0000000000..3479d03d95 --- /dev/null +++ b/starky/src/batch_proof.rs @@ -0,0 +1,290 @@ +//! All the different proof types and their associated `circuit` versions +//! to be used when proving (recursive) [`Stark`][crate::stark::Stark] +//! statements + +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; +use core::ops::Range; + +use plonky2::batch_fri::oracle::BatchFriOracle; +use plonky2::field::extension::{Extendable, FieldExtension}; +use plonky2::fri::proof::{CompressedFriProof, FriProof, FriProofTarget}; +use plonky2::hash::hash_types::{MerkleCapTarget, RichField}; +use plonky2::hash::merkle_tree::MerkleCap; +use plonky2::iop::target::Target; +use plonky2::plonk::config::{GenericConfig, Hasher}; +use plonky2::util::serialization::{Buffer, IoResult, Read, Write}; +use plonky2_maybe_rayon::*; + +use crate::config::StarkConfig; +use crate::lookup::GrandProductChallengeSet; +use crate::proof::{StarkOpeningSet, StarkOpeningSetTarget}; +use crate::stark::Stark; + +/// Merkle caps and openings that form the proof of multiple STARKs. +#[derive(Debug, Clone)] +pub struct BatchStarkProof< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + const N: usize, +> { + /// Merkle cap of LDEs of trace values. + pub trace_cap: MerkleCap, + /// Optional merkle cap of LDEs of permutation Z values, if any. + pub auxiliary_polys_cap: Option>, + /// Merkle cap of LDEs of trace values. + pub quotient_polys_cap: Option>, + /// Purported values of each polynomial at the challenge point. + pub openings: [StarkOpeningSet; N], + /// A batch FRI argument for all openings. + pub opening_proof: FriProof, +} + +impl, C: GenericConfig, const D: usize, const N: usize> + BatchStarkProof +{ + /// Recover the length of the trace from a batched STARK proof and a STARK config. + pub fn recover_degree_bits(&self, config: &StarkConfig) -> usize { + let initial_merkle_proof = &self.opening_proof.query_round_proofs[0] + .initial_trees_proof + .evals_proofs[0] + .1; + let lde_bits = config.fri_config.cap_height + initial_merkle_proof.siblings.len(); + lde_bits - config.fri_config.rate_bits + } +} + +/// Circuit version of [`BatchStarkProof`]. +/// Merkle caps and openings that form the proof of multiple STARKs. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BatchStarkProofTarget { + /// `Target` for the Merkle cap trace values LDEs. + pub trace_cap: MerkleCapTarget, + /// Optional `Target` for the Merkle cap of lookup helper and CTL columns LDEs, if any. + pub auxiliary_polys_cap: Option, + /// `Target` for the Merkle cap of quotient polynomial evaluations LDEs. + pub quotient_polys_cap: Option, + /// `Target`s for the purported values of each polynomial at the challenge point. One opening set per STARK. + pub openings: [StarkOpeningSetTarget; N], + /// `Target`s for the batch FRI argument for all openings. + pub opening_proof: FriProofTarget, +} + +impl BatchStarkProofTarget { + /// Serializes a batched STARK proof. + pub fn to_buffer(&self, buffer: &mut Vec) -> IoResult<()> { + buffer.write_target_merkle_cap(&self.trace_cap)?; + buffer.write_bool(self.auxiliary_polys_cap.is_some())?; + if let Some(poly) = &self.auxiliary_polys_cap { + buffer.write_target_merkle_cap(poly)?; + } + buffer.write_bool(self.quotient_polys_cap.is_some())?; + if let Some(poly) = &self.quotient_polys_cap { + buffer.write_target_merkle_cap(poly)?; + } + buffer.write_target_fri_proof(&self.opening_proof)?; + for opening_set in self.openings.iter() { + opening_set.to_buffer(buffer)?; + } + Ok(()) + } + + /// Deserializes a batched STARK proof. + pub fn from_buffer(buffer: &mut Buffer) -> IoResult { + let trace_cap = buffer.read_target_merkle_cap()?; + let auxiliary_polys_cap = if buffer.read_bool()? { + Some(buffer.read_target_merkle_cap()?) + } else { + None + }; + let quotient_polys_cap = if buffer.read_bool()? { + Some(buffer.read_target_merkle_cap()?) + } else { + None + }; + let opening_proof = buffer.read_target_fri_proof()?; + let mut openings = Vec::new(); + for _ in 0..N { + openings.push(StarkOpeningSetTarget::from_buffer(buffer)?); + } + + Ok(Self { + trace_cap, + auxiliary_polys_cap, + quotient_polys_cap, + openings: openings.try_into().unwrap(), + opening_proof, + }) + } + + /// Recover the length of the trace from a STARK proof and a STARK config. + pub fn recover_degree_bits(&self, config: &StarkConfig) -> usize { + let initial_merkle_proof = &self.opening_proof.query_round_proofs[0] + .initial_trees_proof + .evals_proofs[0] + .1; + let lde_bits = config.fri_config.cap_height + initial_merkle_proof.siblings.len(); + lde_bits - config.fri_config.rate_bits + } +} + +/// Merkle caps and openings that form the proof of multiple STARKs, along with its public inputs. +#[derive(Debug, Clone)] +pub struct BatchStarkProofWithPublicInputs< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + const N: usize, +> { + /// A batched STARK proof. + pub proof: BatchStarkProof, + /// Public inputs associated to this STARK proof. + // TODO: Maybe make it generic over a `S: Stark` and replace with `[F; S::PUBLIC_INPUTS]`. + pub public_inputs: Vec, +} + +/// Circuit version of [BatchStarkProofWithPublicInputs`]. +#[derive(Debug, Clone)] +pub struct BatchStarkProofWithPublicInputsTarget { + /// `Target` STARK proof. + pub proof: BatchStarkProofTarget, + /// `Target` public inputs for this STARK proof. + pub public_inputs: Vec, +} + +/// A compressed proof format of multiple STARKs. +#[derive(Debug, Clone)] +pub struct CompressedBatchStarkProof< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + const N: usize, +> { + /// Merkle cap of LDEs of trace values. + pub trace_cap: MerkleCap, + /// Purported values of each polynomial at the challenge point. + pub openings: [StarkOpeningSet; N], + /// A batch FRI argument for all openings. + pub opening_proof: CompressedFriProof, +} + +/// A compressed [`BatchStarkProof`] format of multiple STARKs with its public inputs. +#[derive(Debug, Clone)] +pub struct CompressedBatchStarkProofWithPublicInputs< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + const N: usize, +> { + /// A compressed btached STARK proof. + pub proof: CompressedBatchStarkProof, + /// Public inputs for this compressed STARK proof. + pub public_inputs: Vec, +} + +/// A [`BatchStarkProof`] along with metadata about the initial Fiat-Shamir state, which is used when +/// creating a recursive wrapper proof around a STARK proof. +#[derive(Debug, Clone)] +pub struct BatchStarkProofWithMetadata +where + F: RichField + Extendable, + C: GenericConfig, +{ + /// Initial Fiat-Shamir state. + pub init_challenger_state: >::Permutation, + /// Proof for multiple STARKs. + pub proof: BatchStarkProof, +} + +/// A combination of a batched STARK proof for independent statements operating on possibly shared variables, +/// along with Cross-Table Lookup (CTL) challenges to assert consistency of common variables across tables. +#[derive(Debug, Clone)] +pub struct BatchMultiProof< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + const N: usize, +> { + /// Proof for all the different STARK modules. + pub stark_proofs: BatchStarkProofWithMetadata, + /// Cross-table lookup challenges. + pub ctl_challenges: GrandProductChallengeSet, +} + +// impl, C: GenericConfig, const D: usize, const N: usize> +// MultiProof +// { +// /// Returns the degree (i.e. the trace length) of each STARK proof, +// /// from their common [`StarkConfig`]. +// pub fn recover_degree_bits(&self, config: &StarkConfig) -> [usize; N] { +// core::array::from_fn(|i| self.stark_proofs[i].proof.recover_degree_bits(config)) +// } +// } + +impl, const D: usize> StarkOpeningSet { + /// Returns a `StarkOpeningSet` given a STARK, the batched polynomial commitments, the evaluation point and a generator `g`. + /// + /// Polynomials are evaluated at point `zeta` and, if necessary, at `g * zeta`. + pub fn new_from_batch, S: Stark>( + stark: S, + zeta: F::Extension, + g: F, + trace_commitment: &BatchFriOracle, + trace_polys_range: Range, + auxiliary_polys_commitment: &BatchFriOracle, + auxiliary_polys_range: Range, + quotient_commitment: &BatchFriOracle, + quotient_polys_range: Range, + num_lookup_columns: usize, + num_ctl_polys: &[usize], + ) -> Self { + // Batch evaluates polynomials on the LDE, at a point `z`. + let eval_commitment = + |z: F::Extension, c: &BatchFriOracle, range: Range| { + c.polynomials[range] + .par_iter() + .map(|p| p.to_extension().eval(z)) + .collect::>() + }; + // Batch evaluates polynomials at a base field point `z`. + let eval_commitment_base = |z: F, c: &BatchFriOracle, range: Range| { + c.polynomials[range] + .par_iter() + .map(|p| p.eval(z)) + .collect::>() + }; + + let auxiliary_first = eval_commitment_base( + F::ONE, + auxiliary_polys_commitment, + auxiliary_polys_range.clone(), + ); + // `g * zeta`. + let zeta_next = zeta.scalar_mul(g); + + Self { + local_values: eval_commitment(zeta, trace_commitment, trace_polys_range.clone()), + next_values: eval_commitment(zeta_next, trace_commitment, trace_polys_range), + auxiliary_polys: Some(eval_commitment( + zeta, + auxiliary_polys_commitment, + auxiliary_polys_range.clone(), + )), + auxiliary_polys_next: Some(eval_commitment( + zeta_next, + auxiliary_polys_commitment, + auxiliary_polys_range, + )), + ctl_zs_first: stark.requires_ctls().then(|| { + let total_num_helper_cols: usize = num_ctl_polys.iter().sum(); + auxiliary_first[num_lookup_columns + total_num_helper_cols..].to_vec() + }), + quotient_polys: Some(eval_commitment( + zeta, + quotient_commitment, + quotient_polys_range, + )), + } + } +} diff --git a/starky/src/cross_table_lookup.rs b/starky/src/cross_table_lookup.rs index dd81443117..2eb34c4e13 100644 --- a/starky/src/cross_table_lookup.rs +++ b/starky/src/cross_table_lookup.rs @@ -212,7 +212,7 @@ impl<'a, F: Field> CtlData<'a, F> { } /// Returns the number of helper columns for each STARK in each /// `CtlZData`. - pub(crate) fn num_ctl_helper_polys(&self) -> Vec { + pub fn num_ctl_helper_polys(&self) -> Vec { let mut res = Vec::with_capacity(self.zs_columns.len()); for z in &self.zs_columns { res.push(z.helper_columns.len()); @@ -303,7 +303,7 @@ pub(crate) fn num_ctl_helper_columns_by_table( } /// Gets the auxiliary polynomials associated to these CTL data. -pub(crate) fn get_ctl_auxiliary_polys( +pub fn get_ctl_auxiliary_polys( ctl_data: Option<&CtlData>, ) -> Option>> { ctl_data.map(|data| { diff --git a/starky/src/get_challenges.rs b/starky/src/get_challenges.rs index e8904ffcaf..550298cba9 100644 --- a/starky/src/get_challenges.rs +++ b/starky/src/get_challenges.rs @@ -9,6 +9,7 @@ use plonky2::iop::target::Target; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::config::{AlgebraicHasher, GenericConfig}; +use crate::batch_proof::{BatchStarkProof, BatchStarkProofWithPublicInputs}; use crate::config::StarkConfig; use crate::lookup::{ get_grand_product_challenge_set, get_grand_product_challenge_set_target, @@ -29,7 +30,7 @@ fn get_challenges( trace_cap: Option<&MerkleCap>, auxiliary_polys_cap: Option<&MerkleCap>, quotient_polys_cap: Option<&MerkleCap>, - openings: &StarkOpeningSet, + opening_batch: &[&StarkOpeningSet], commit_phase_merkle_caps: &[MerkleCap], final_poly: &PolynomialCoeffs, pow_witness: F, @@ -65,7 +66,9 @@ where } let stark_zeta = challenger.get_extension_challenge::(); - challenger.observe_openings(&openings.to_fri_openings()); + for opening in opening_batch { + challenger.observe_openings(&opening.to_fri_openings()); + } StarkProofChallenges { lookup_challenge_set, @@ -128,7 +131,7 @@ where trace_cap, auxiliary_polys_cap.as_ref(), quotient_polys_cap.as_ref(), - openings, + &[openings], commit_phase_merkle_caps, final_poly, *pow_witness, @@ -416,3 +419,65 @@ impl StarkProofWithPublicInputsTarget { // FriInferredElements(fri_inferred_elements) // } // } + +impl BatchStarkProof +where + F: RichField + Extendable, + C: GenericConfig, +{ + /// Computes all Fiat-Shamir challenges used in the batched STARK proof. + pub fn get_challenges( + &self, + challenger: &mut Challenger, + challenges: Option<&GrandProductChallengeSet>, + // ignore_trace_cap: bool, + config: &StarkConfig, + ) -> StarkProofChallenges { + let degree_bits = self.recover_degree_bits(config); + + let BatchStarkProof { + trace_cap, + auxiliary_polys_cap, + quotient_polys_cap, + openings, + opening_proof: + FriProof { + commit_phase_merkle_caps, + final_poly, + pow_witness, + .. + }, + } = &self; + + get_challenges::( + challenger, + challenges, + Some(trace_cap), + auxiliary_polys_cap.as_ref(), + quotient_polys_cap.as_ref(), + &openings.iter().collect::>(), + commit_phase_merkle_caps, + final_poly, + *pow_witness, + config, + degree_bits, + ) + } +} + +impl BatchStarkProofWithPublicInputs +where + F: RichField + Extendable, + C: GenericConfig, +{ + /// Computes all Fiat-Shamir challenges used in the batched STARK proof. + pub fn get_challenges( + &self, + challenger: &mut Challenger, + challenges: Option<&GrandProductChallengeSet>, + // ignore_trace_cap: bool, + config: &StarkConfig, + ) -> StarkProofChallenges { + self.proof.get_challenges(challenger, challenges, config) + } +} diff --git a/starky/src/lib.rs b/starky/src/lib.rs index 24bea760f1..3a861b56e7 100644 --- a/starky/src/lib.rs +++ b/starky/src/lib.rs @@ -324,6 +324,7 @@ extern crate alloc; mod get_challenges; +pub mod batch_proof; pub mod config; pub mod constraint_consumer; pub mod cross_table_lookup; diff --git a/starky/src/lookup.rs b/starky/src/lookup.rs index bc079fb1a5..2461e1fa02 100644 --- a/starky/src/lookup.rs +++ b/starky/src/lookup.rs @@ -575,7 +575,7 @@ pub fn get_grand_product_challenge_set_target< /// Given columns `f0,...,fk` and a column `t`, such that `∪fi ⊆ t`, and challenges `x`, /// this computes the helper columns `h_i = 1/(x+f_2i) + 1/(x+f_2i+1)`, `g = 1/(x+t)`, /// and `Z(gx) = Z(x) + sum h_i(x) - m(x)g(x)` where `m` is the frequencies column. -pub(crate) fn lookup_helper_columns( +pub fn lookup_helper_columns( lookup: &Lookup, trace_poly_values: &[PolynomialValues], challenge: F, diff --git a/starky/src/proof.rs b/starky/src/proof.rs index d521be027a..aeb43dbf35 100644 --- a/starky/src/proof.rs +++ b/starky/src/proof.rs @@ -311,7 +311,7 @@ impl, const D: usize> StarkOpeningSet { /// Constructs the openings required by FRI. /// All openings but `ctl_zs_first` are grouped together. - pub(crate) fn to_fri_openings(&self) -> FriOpenings { + pub fn to_fri_openings(&self) -> FriOpenings { let zeta_batch = FriOpeningBatch { values: self .local_values diff --git a/starky/src/prover.rs b/starky/src/prover.rs index 36d544b624..2913e5c12f 100644 --- a/starky/src/prover.rs +++ b/starky/src/prover.rs @@ -225,21 +225,29 @@ where ); } + let get_trace_values_packed = + |index_start, step| trace_commitment.get_lde_values_packed(index_start, step); + + let get_aux_values_packed = |index_start, step| { + auxiliary_polys_commitment + .as_ref() + .unwrap() + .get_lde_values_packed(index_start, step) + }; + let quotient_polys = timed!( timing, "compute quotient polys", compute_quotient_polys::::Packing, C, S, D>( stark, - trace_commitment, - &auxiliary_polys_commitment, + &get_trace_values_packed, + &get_aux_values_packed, lookup_challenges.as_ref(), - &lookups, ctl_data, public_inputs, alphas.clone(), degree_bits, num_lookup_columns, - &num_ctl_polys, config, ) ); @@ -339,18 +347,16 @@ where /// Computes the quotient polynomials `(sum alpha^i C_i(x)) / Z_H(x)` for `alpha` in `alphas`, /// where the `C_i`s are the STARK constraints. -fn compute_quotient_polys<'a, F, P, C, S, const D: usize>( +pub fn compute_quotient_polys( stark: &S, - trace_commitment: &'a PolynomialBatch, - auxiliary_polys_commitment: &'a Option>, - lookup_challenges: Option<&'a Vec>, - lookups: &[Lookup], + get_trace_values_packed: &(dyn Fn(usize, usize) -> Vec

+ Sync + Send), + get_aux_values_packed: &(dyn Fn(usize, usize) -> Vec

+ Sync + Send), + lookup_challenges: Option<&Vec>, ctl_data: Option<&CtlData>, public_inputs: &[F], alphas: Vec, degree_bits: usize, num_lookup_columns: usize, - num_ctl_columns: &[usize], config: &StarkConfig, ) -> Option>> where @@ -365,6 +371,10 @@ where let degree = 1 << degree_bits; let rate_bits = config.fri_config.rate_bits; + let lookups = stark.lookups(); + let num_ctl_columns = ctl_data + .map(|data| data.num_ctl_helper_polys()) + .unwrap_or_default(); let total_num_helper_cols: usize = num_ctl_columns.iter().sum(); let quotient_degree_bits = log2_ceil(stark.quotient_degree_factor()); @@ -384,10 +394,6 @@ where let z_h_on_coset = ZeroPolyOnCoset::::new(degree_bits, quotient_degree_bits); - // Retrieve the LDE values at index `i`. - let get_trace_values_packed = - |i_start| -> Vec

{ trace_commitment.get_lde_values_packed(i_start, step) }; - // Last element of the subgroup. let last = F::primitive_root_of_unity(degree_bits).inverse(); let size = degree << quotient_degree_bits; @@ -420,23 +426,15 @@ where // Get the local and next row evaluations for the current STARK, // as well as the public inputs. let vars = S::EvaluationFrame::from_values( - &get_trace_values_packed(i_start), - &get_trace_values_packed(i_next_start), + &get_trace_values_packed(i_start, step), + &get_trace_values_packed(i_next_start, step), public_inputs, ); // Get the local and next row evaluations for the permutation argument, // as well as the associated challenges. let lookup_vars = lookup_challenges.map(|challenges| LookupCheckVars { - local_values: auxiliary_polys_commitment - .as_ref() - .unwrap() - .get_lde_values_packed(i_start, step)[..num_lookup_columns] - .to_vec(), - next_values: auxiliary_polys_commitment - .as_ref() - .unwrap() - .get_lde_values_packed(i_next_start, step)[..num_lookup_columns] - .to_vec(), + local_values: get_aux_values_packed(i_start, step)[..num_lookup_columns].to_vec(), + next_values: get_aux_values_packed(i_next_start, step).to_vec(), challenges: challenges.to_vec(), }); @@ -454,25 +452,16 @@ where .enumerate() .map(|(i, zs_columns)| { let num_ctl_helper_cols = num_ctl_columns[i]; - let helper_columns = auxiliary_polys_commitment - .as_ref() - .unwrap() - .get_lde_values_packed(i_start, step) - [num_lookup_columns + start_index + let helper_columns = + get_aux_values_packed(i_start, step)[num_lookup_columns + start_index ..num_lookup_columns + start_index + num_ctl_helper_cols] - .to_vec(); + .to_vec(); let ctl_vars = CtlCheckVars:: { helper_columns, - local_z: auxiliary_polys_commitment - .as_ref() - .unwrap() - .get_lde_values_packed(i_start, step) + local_z: get_aux_values_packed(i_start, step) [num_lookup_columns + total_num_helper_cols + i], - next_z: auxiliary_polys_commitment - .as_ref() - .unwrap() - .get_lde_values_packed(i_next_start, step) + next_z: get_aux_values_packed(i_next_start, step) [num_lookup_columns + total_num_helper_cols + i], challenges: zs_columns.challenge, columns: zs_columns.columns.clone(), @@ -491,7 +480,7 @@ where eval_vanishing_poly::( stark, &vars, - lookups, + &lookups, lookup_vars, ctl_vars.as_deref(), &mut consumer, diff --git a/starky/src/verifier.rs b/starky/src/verifier.rs index d56072ad3a..b9aaa82d05 100644 --- a/starky/src/verifier.rs +++ b/starky/src/verifier.rs @@ -16,6 +16,7 @@ use plonky2::iop::challenger::Challenger; use plonky2::plonk::config::GenericConfig; use plonky2::plonk::plonk_common::reduce_with_powers; +use crate::batch_proof::{BatchStarkProof, BatchStarkProofWithPublicInputs}; use crate::config::StarkConfig; use crate::constraint_consumer::ConstraintConsumer; use crate::cross_table_lookup::CtlCheckVars; @@ -51,6 +52,33 @@ pub fn verify_stark_proof< ) } +/// Verifies a [`BatchStarkProofWithPublicInputs`] against a batched STARK statement. +pub fn verify_stark_proof_batch< + F: RichField + Extendable, + C: GenericConfig, + S: Stark, + const D: usize, + const N: usize, +>( + stark: S, + proof_with_pis: BatchStarkProofWithPublicInputs, + config: &StarkConfig, +) -> Result<()> { + ensure!(proof_with_pis.public_inputs.len() == S::PUBLIC_INPUTS); + let mut challenger = Challenger::::new(); + + let challenges = proof_with_pis.get_challenges(&mut challenger, None, config); + + verify_stark_proof_with_challenges_batch( + &stark, + &proof_with_pis.proof, + &challenges, + None, + &proof_with_pis.public_inputs, + config, + ) +} + /// Verifies a [`StarkProofWithPublicInputs`] against a STARK statement, /// with the provided [`StarkProofChallenges`]. /// It also supports optional cross-table lookups data and challenges, @@ -207,6 +235,27 @@ where Ok(()) } +/// Verifies a [`BatchStarkProofWithPublicInputs`] against a batched STARK statement, +/// with the provided [`StarkProofChallenges`]. +/// It also supports optional cross-table lookups data and challenges. +pub fn verify_stark_proof_with_challenges_batch( + stark: &S, + proof: &BatchStarkProof, + challenges: &StarkProofChallenges, + ctl_vars: Option<&[CtlCheckVars]>, + public_inputs: &[F], + config: &StarkConfig, +) -> Result<()> +where + F: RichField + Extendable, + C: GenericConfig, + S: Stark, +{ + log::debug!("Checking proof: {}", type_name::()); + + Ok(()) +} + fn validate_proof_shape( stark: &S, proof: &StarkProof,