//! 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 itertools::Itertools; use plonky2::field::extension::{Extendable, FieldExtension}; use plonky2::fri::oracle::PolynomialBatch; use plonky2::fri::proof::{ CompressedFriProof, FriChallenges, FriChallengesTarget, FriProof, FriProofTarget, }; use plonky2::fri::structure::{ FriOpeningBatch, FriOpeningBatchTarget, FriOpenings, FriOpeningsTarget, }; use plonky2::hash::hash_types::{MerkleCapTarget, RichField}; use plonky2::hash::merkle_tree::MerkleCap; use plonky2::iop::ext_target::ExtensionTarget; 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; /// Merkle caps and openings that form the proof of a single STARK. #[derive(Debug, Clone)] pub struct StarkProof, C: GenericConfig, const D: 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, /// A batch FRI argument for all openings. pub opening_proof: FriProof, } impl, C: GenericConfig, const D: usize> StarkProof { /// 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 } } /// Circuit version of [`StarkProof`]. /// Merkle caps and openings that form the proof of a single STARK. #[derive(Clone, Debug, PartialEq, Eq)] pub struct StarkProofTarget { /// `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. pub openings: StarkOpeningSetTarget, /// `Target`s for the batch FRI argument for all openings. pub opening_proof: FriProofTarget, } impl StarkProofTarget { /// Serializes a 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)?; self.openings.to_buffer(buffer)?; Ok(()) } /// Deserializes a 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 openings = StarkOpeningSetTarget::from_buffer(buffer)?; Ok(Self { trace_cap, auxiliary_polys_cap, quotient_polys_cap, openings, 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 a single STARK, along with its public inputs. #[derive(Debug, Clone)] pub struct StarkProofWithPublicInputs< F: RichField + Extendable, C: GenericConfig, const D: usize, > { /// A STARK proof. pub proof: StarkProof, /// 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 [`StarkProofWithPublicInputs`]. #[derive(Debug, Clone)] pub struct StarkProofWithPublicInputsTarget { /// `Target` STARK proof. pub proof: StarkProofTarget, /// `Target` public inputs for this STARK proof. pub public_inputs: Vec, } /// A compressed proof format of a single STARK. #[derive(Debug, Clone)] pub struct CompressedStarkProof< F: RichField + Extendable, C: GenericConfig, const D: usize, > { /// Merkle cap of LDEs of trace values. pub trace_cap: MerkleCap, /// Purported values of each polynomial at the challenge point. pub openings: StarkOpeningSet, /// A batch FRI argument for all openings. pub opening_proof: CompressedFriProof, } /// A compressed [`StarkProof`] format of a single STARK with its public inputs. #[derive(Debug, Clone)] pub struct CompressedStarkProofWithPublicInputs< F: RichField + Extendable, C: GenericConfig, const D: usize, > { /// A compressed STARK proof. pub proof: CompressedStarkProof, /// Public inputs for this compressed STARK proof. pub public_inputs: Vec, } /// A [`StarkProof`] 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 StarkProofWithMetadata where F: RichField + Extendable, C: GenericConfig, { /// Initial Fiat-Shamir state. pub init_challenger_state: >::Permutation, /// Proof for a single STARK. pub proof: StarkProof, } /// A combination of STARK proofs 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 MultiProof< F: RichField + Extendable, C: GenericConfig, const D: usize, const N: usize, > { /// Proofs for all the different STARK modules. pub stark_proofs: [StarkProofWithMetadata; N], /// 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)) } } /// Randomness used for a STARK proof. #[derive(Debug)] pub struct StarkProofChallenges, const D: usize> { /// Optional randomness used in any permutation argument. pub lookup_challenge_set: Option>, /// Random values used to combine STARK constraints. pub stark_alphas: Vec, /// Point at which the STARK polynomials are opened. pub stark_zeta: F::Extension, /// Randomness used in FRI. pub fri_challenges: FriChallenges, } /// Circuit version of [`StarkProofChallenges`]. #[derive(Debug)] pub struct StarkProofChallengesTarget { /// Optional `Target`'s randomness used in any permutation argument. pub lookup_challenge_set: Option>, /// `Target`s for the random values used to combine STARK constraints. pub stark_alphas: Vec, /// `ExtensionTarget` for the point at which the STARK polynomials are opened. pub stark_zeta: ExtensionTarget, /// `Target`s for the randomness used in FRI. pub fri_challenges: FriChallengesTarget, } /// Randomness for all STARK proofs contained in a [`MultiProof`]`. #[derive(Debug)] pub struct MultiProofChallenges, const D: usize, const N: usize> { /// Randomness used in each STARK proof. pub stark_challenges: [StarkProofChallenges; N], /// Randomness used for cross-table lookups. It is shared by all STARKs. pub ctl_challenges: GrandProductChallengeSet, } /// Purported values of each polynomial at the challenge point. #[derive(Debug, Clone)] pub struct StarkOpeningSet, const D: usize> { /// Openings of trace polynomials at `zeta`. pub local_values: Vec, /// Openings of trace polynomials at `g * zeta`. pub next_values: Vec, /// Openings of lookups and cross-table lookups `Z` polynomials at `zeta`. pub auxiliary_polys: Option>, /// Openings of lookups and cross-table lookups `Z` polynomials at `g * zeta`. pub auxiliary_polys_next: Option>, /// Openings of cross-table lookups `Z` polynomials at `1`. pub ctl_zs_first: Option>, /// Openings of quotient polynomials at `zeta`. pub quotient_polys: Option>, } impl, const D: usize> StarkOpeningSet { /// Returns a `StarkOpeningSet` given all the polynomial commitments, the number /// of permutation `Z`polynomials, the evaluation point and a generator `g`. /// /// Polynomials are evaluated at point `zeta` and, if necessary, at `g * zeta`. pub fn new>( zeta: F::Extension, g: F, trace_commitment: &PolynomialBatch, auxiliary_polys_commitment: Option<&PolynomialBatch>, quotient_commitment: Option<&PolynomialBatch>, num_lookup_columns: usize, requires_ctl: bool, num_ctl_polys: &[usize], ) -> Self { // Batch evaluates polynomials on the LDE, at a point `z`. let eval_commitment = |z: F::Extension, c: &PolynomialBatch| { c.polynomials .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: &PolynomialBatch| { c.polynomials .par_iter() .map(|p| p.eval(z)) .collect::>() }; let auxiliary_first = auxiliary_polys_commitment.map(|c| eval_commitment_base(F::ONE, c)); // `g * zeta`. let zeta_next = zeta.scalar_mul(g); Self { local_values: eval_commitment(zeta, trace_commitment), next_values: eval_commitment(zeta_next, trace_commitment), auxiliary_polys: auxiliary_polys_commitment.map(|c| eval_commitment(zeta, c)), auxiliary_polys_next: auxiliary_polys_commitment.map(|c| eval_commitment(zeta_next, c)), ctl_zs_first: requires_ctl.then(|| { let total_num_helper_cols: usize = num_ctl_polys.iter().sum(); auxiliary_first.unwrap()[num_lookup_columns + total_num_helper_cols..].to_vec() }), quotient_polys: quotient_commitment.map(|c| eval_commitment(zeta, c)), } } /// Constructs the openings required by FRI. /// All openings but `ctl_zs_first` are grouped together. pub(crate) fn to_fri_openings(&self) -> FriOpenings { let zeta_batch = FriOpeningBatch { values: self .local_values .iter() .chain(self.auxiliary_polys.iter().flatten()) .chain(self.quotient_polys.iter().flatten()) .copied() .collect_vec(), }; let zeta_next_batch = FriOpeningBatch { values: self .next_values .iter() .chain(self.auxiliary_polys_next.iter().flatten()) .copied() .collect_vec(), }; let mut batches = vec![zeta_batch, zeta_next_batch]; if let Some(ctl_zs_first) = self.ctl_zs_first.as_ref() { debug_assert!(!ctl_zs_first.is_empty()); debug_assert!(self.auxiliary_polys.is_some()); debug_assert!(self.auxiliary_polys_next.is_some()); let ctl_first_batch = FriOpeningBatch { values: ctl_zs_first .iter() .copied() .map(F::Extension::from_basefield) .collect(), }; batches.push(ctl_first_batch); } FriOpenings { batches } } } /// Circuit version of [`StarkOpeningSet`]. /// `Target`s for the purported values of each polynomial at the challenge point. #[derive(Clone, Debug, PartialEq, Eq)] pub struct StarkOpeningSetTarget { /// `ExtensionTarget`s for the openings of trace polynomials at `zeta`. pub local_values: Vec>, /// `ExtensionTarget`s for the opening of trace polynomials at `g * zeta`. pub next_values: Vec>, /// `ExtensionTarget`s for the opening of lookups and cross-table lookups `Z` polynomials at `zeta`. pub auxiliary_polys: Option>>, /// `ExtensionTarget`s for the opening of lookups and cross-table lookups `Z` polynomials at `g * zeta`. pub auxiliary_polys_next: Option>>, /// `ExtensionTarget`s for the opening of lookups and cross-table lookups `Z` polynomials at 1. pub ctl_zs_first: Option>, /// `ExtensionTarget`s for the opening of quotient polynomials at `zeta`. pub quotient_polys: Option>>, } impl StarkOpeningSetTarget { /// Serializes a STARK's opening set. pub(crate) fn to_buffer(&self, buffer: &mut Vec) -> IoResult<()> { buffer.write_target_ext_vec(&self.local_values)?; buffer.write_target_ext_vec(&self.next_values)?; if let Some(poly) = &self.auxiliary_polys { buffer.write_bool(true)?; buffer.write_target_ext_vec(poly)?; } else { buffer.write_bool(false)?; } if let Some(poly_next) = &self.auxiliary_polys_next { buffer.write_bool(true)?; buffer.write_target_ext_vec(poly_next)?; } else { buffer.write_bool(false)?; } if let Some(ctl_zs_first) = &self.ctl_zs_first { buffer.write_bool(true)?; buffer.write_target_vec(ctl_zs_first)?; } else { buffer.write_bool(false)?; } buffer.write_bool(self.quotient_polys.is_some())?; if let Some(quotient_polys) = &self.quotient_polys { buffer.write_target_ext_vec(quotient_polys)?; } Ok(()) } /// Deserializes a STARK's opening set. pub(crate) fn from_buffer(buffer: &mut Buffer) -> IoResult { let local_values = buffer.read_target_ext_vec::()?; let next_values = buffer.read_target_ext_vec::()?; let auxiliary_polys = if buffer.read_bool()? { Some(buffer.read_target_ext_vec::()?) } else { None }; let auxiliary_polys_next = if buffer.read_bool()? { Some(buffer.read_target_ext_vec::()?) } else { None }; let ctl_zs_first = if buffer.read_bool()? { Some(buffer.read_target_vec()?) } else { None }; let quotient_polys = if buffer.read_bool()? { Some(buffer.read_target_ext_vec::()?) } else { None }; Ok(Self { local_values, next_values, auxiliary_polys, auxiliary_polys_next, ctl_zs_first, quotient_polys, }) } /// Circuit version of `to_fri_openings`for [`FriOpeningsTarget`]. pub(crate) fn to_fri_openings(&self, zero: Target) -> FriOpeningsTarget { let zeta_batch = FriOpeningBatchTarget { values: self .local_values .iter() .chain(self.auxiliary_polys.iter().flatten()) .chain(self.quotient_polys.iter().flatten()) .copied() .collect_vec(), }; let zeta_next_batch = FriOpeningBatchTarget { values: self .next_values .iter() .chain(self.auxiliary_polys_next.iter().flatten()) .copied() .collect_vec(), }; let mut batches = vec![zeta_batch, zeta_next_batch]; if let Some(ctl_zs_first) = self.ctl_zs_first.as_ref() { debug_assert!(!ctl_zs_first.is_empty()); debug_assert!(self.auxiliary_polys.is_some()); debug_assert!(self.auxiliary_polys_next.is_some()); let ctl_first_batch = FriOpeningBatchTarget { values: ctl_zs_first .iter() .copied() .map(|t| t.to_ext_target(zero)) .collect(), }; batches.push(ctl_first_batch); } FriOpeningsTarget { batches } } }