diff --git a/plonky2/src/fri/challenges.rs b/plonky2/src/fri/challenges.rs index 82438383..0655d31f 100644 --- a/plonky2/src/fri/challenges.rs +++ b/plonky2/src/fri/challenges.rs @@ -10,7 +10,6 @@ use crate::hash::merkle_tree::MerkleCap; use crate::iop::challenger::{Challenger, RecursiveChallenger}; use crate::iop::target::Target; use crate::plonk::circuit_builder::CircuitBuilder; -use crate::plonk::circuit_data::CommonCircuitData; use crate::plonk::config::{AlgebraicHasher, GenericConfig, Hasher}; impl> Challenger { @@ -89,9 +88,9 @@ impl, H: AlgebraicHasher, const D: usize> commit_phase_merkle_caps: &[MerkleCapTarget], final_poly: &PolynomialCoeffsExtTarget, pow_witness: Target, - inner_common_data: &CommonCircuitData, + inner_fri_config: &FriConfig, ) -> FriChallengesTarget { - let num_fri_queries = inner_common_data.config.fri_config.num_query_rounds; + let num_fri_queries = inner_fri_config.num_query_rounds; // Scaling factor to combine polynomials. let fri_alpha = self.get_extension_challenge(builder); diff --git a/plonky2/src/fri/mod.rs b/plonky2/src/fri/mod.rs index a0cd428b..c491f8f0 100644 --- a/plonky2/src/fri/mod.rs +++ b/plonky2/src/fri/mod.rs @@ -30,6 +30,20 @@ impl FriConfig { pub fn rate(&self) -> f64 { 1.0 / ((1 << self.rate_bits) as f64) } + + pub fn fri_params(&self, degree_bits: usize, hiding: bool) -> FriParams { + let reduction_arity_bits = self.reduction_strategy.reduction_arity_bits( + degree_bits, + self.rate_bits, + self.num_query_rounds, + ); + FriParams { + config: self.clone(), + hiding, + degree_bits, + reduction_arity_bits, + } + } } /// FRI parameters, including generated parameters which are specific to an instance size, in diff --git a/plonky2/src/fri/recursive_verifier.rs b/plonky2/src/fri/recursive_verifier.rs index f51b8fe6..9b619ea8 100644 --- a/plonky2/src/fri/recursive_verifier.rs +++ b/plonky2/src/fri/recursive_verifier.rs @@ -398,7 +398,7 @@ impl, const D: usize> CircuitBuilder { "A non-negligible portion of field elements are in the range that permits non-canonical encodings. Need to do more analysis or enforce canonical encodings."); } - pub(crate) fn add_virtual_fri_proof( + pub fn add_virtual_fri_proof( &mut self, num_leaves_per_oracle: &[usize], params: &FriParams, diff --git a/plonky2/src/iop/challenger.rs b/plonky2/src/iop/challenger.rs index 1519f6ec..c3a4403a 100644 --- a/plonky2/src/iop/challenger.rs +++ b/plonky2/src/iop/challenger.rs @@ -166,7 +166,7 @@ pub struct RecursiveChallenger, H: AlgebraicHasher< impl, H: AlgebraicHasher, const D: usize> RecursiveChallenger { - pub(crate) fn new(builder: &mut CircuitBuilder) -> Self { + pub fn new(builder: &mut CircuitBuilder) -> Self { let zero = builder.zero(); RecursiveChallenger { sponge_state: [zero; SPONGE_WIDTH], @@ -222,7 +222,7 @@ impl, H: AlgebraicHasher, const D: usize> .expect("Output buffer should be non-empty") } - pub(crate) fn get_n_challenges( + pub fn get_n_challenges( &mut self, builder: &mut CircuitBuilder, n: usize, diff --git a/plonky2/src/iop/witness.rs b/plonky2/src/iop/witness.rs index e1bdf06e..de6d4a05 100644 --- a/plonky2/src/iop/witness.rs +++ b/plonky2/src/iop/witness.rs @@ -5,6 +5,7 @@ use num::{BigUint, FromPrimitive, Zero}; use plonky2_field::extension_field::{Extendable, FieldExtension}; use plonky2_field::field_types::{Field, PrimeField}; +use crate::fri::structure::{FriOpenings, FriOpeningsTarget}; use crate::fri::witness_util::set_fri_proof_target; use crate::gadgets::arithmetic_u32::U32Target; use crate::gadgets::biguint::BigUintTarget; @@ -207,66 +208,30 @@ pub trait Witness { ); self.set_cap_target(&proof_target.quotient_polys_cap, &proof.quotient_polys_cap); - for (&t, &x) in proof_target - .openings - .wires - .iter() - .zip_eq(&proof.openings.wires) - { - self.set_extension_target(t, x); - } - for (&t, &x) in proof_target - .openings - .constants - .iter() - .zip_eq(&proof.openings.constants) - { - self.set_extension_target(t, x); - } - for (&t, &x) in proof_target - .openings - .plonk_sigmas - .iter() - .zip_eq(&proof.openings.plonk_sigmas) - { - self.set_extension_target(t, x); - } - for (&t, &x) in proof_target - .openings - .plonk_zs - .iter() - .zip_eq(&proof.openings.plonk_zs) - { - self.set_extension_target(t, x); - } - for (&t, &x) in proof_target - .openings - .plonk_zs_right - .iter() - .zip_eq(&proof.openings.plonk_zs_right) - { - self.set_extension_target(t, x); - } - for (&t, &x) in proof_target - .openings - .partial_products - .iter() - .zip_eq(&proof.openings.partial_products) - { - self.set_extension_target(t, x); - } - for (&t, &x) in proof_target - .openings - .quotient_polys - .iter() - .zip_eq(&proof.openings.quotient_polys) - { - self.set_extension_target(t, x); - } + self.set_fri_openings( + &proof_target.openings.to_fri_openings(), + &proof.openings.to_fri_openings(), + ); set_fri_proof_target(self, &proof_target.opening_proof, &proof.opening_proof); } + fn set_fri_openings( + &mut self, + fri_openings_target: &FriOpeningsTarget, + fri_openings: &FriOpenings, + ) where + F: RichField + Extendable, + { + for (batch_target, batch) in fri_openings_target + .batches + .iter() + .zip_eq(&fri_openings.batches) + { + self.set_extension_targets(&batch_target.values, &batch.values); + } + } + fn set_wire(&mut self, wire: Wire, value: F) { self.set_target(Target::Wire(wire), value) } diff --git a/plonky2/src/plonk/circuit_builder.rs b/plonky2/src/plonk/circuit_builder.rs index 7811c0db..31469771 100644 --- a/plonky2/src/plonk/circuit_builder.rs +++ b/plonky2/src/plonk/circuit_builder.rs @@ -393,18 +393,9 @@ impl, const D: usize> CircuitBuilder { } fn fri_params(&self, degree_bits: usize) -> FriParams { - let fri_config = &self.config.fri_config; - let reduction_arity_bits = fri_config.reduction_strategy.reduction_arity_bits( - degree_bits, - fri_config.rate_bits, - fri_config.num_query_rounds, - ); - FriParams { - config: fri_config.clone(), - hiding: self.config.zero_knowledge, - degree_bits, - reduction_arity_bits, - } + self.config + .fri_config + .fri_params(degree_bits, self.config.zero_knowledge) } /// The number of (base field) `arithmetic` operations that can be performed in a single gate. diff --git a/plonky2/src/plonk/get_challenges.rs b/plonky2/src/plonk/get_challenges.rs index a67a6207..e8fd3933 100644 --- a/plonky2/src/plonk/get_challenges.rs +++ b/plonky2/src/plonk/get_challenges.rs @@ -275,7 +275,7 @@ impl, const D: usize> CircuitBuilder { commit_phase_merkle_caps, final_poly, pow_witness, - inner_common_data, + &inner_common_data.config.fri_config, ), } } diff --git a/starky/src/config.rs b/starky/src/config.rs index 24fb725a..500cd957 100644 --- a/starky/src/config.rs +++ b/starky/src/config.rs @@ -29,17 +29,6 @@ impl StarkConfig { } pub(crate) fn fri_params(&self, degree_bits: usize) -> FriParams { - let fri_config = &self.fri_config; - let reduction_arity_bits = fri_config.reduction_strategy.reduction_arity_bits( - degree_bits, - fri_config.rate_bits, - fri_config.num_query_rounds, - ); - FriParams { - config: fri_config.clone(), - hiding: false, - degree_bits, - reduction_arity_bits, - } + self.fri_config.fri_params(degree_bits, false) } } diff --git a/starky/src/constraint_consumer.rs b/starky/src/constraint_consumer.rs index c909b520..88f66118 100644 --- a/starky/src/constraint_consumer.rs +++ b/starky/src/constraint_consumer.rs @@ -80,10 +80,10 @@ impl ConstraintConsumer

{ pub struct RecursiveConstraintConsumer, const D: usize> { /// A random value used to combine multiple constraints into one. - alpha: Target, + alphas: Vec, /// A running sum of constraints that have been emitted so far, scaled by powers of alpha. - constraint_acc: ExtensionTarget, + constraint_accs: Vec>, /// The evaluation of `X - g^(n-1)`. z_last: ExtensionTarget, @@ -100,6 +100,27 @@ pub struct RecursiveConstraintConsumer, const D: us } impl, const D: usize> RecursiveConstraintConsumer { + pub fn new( + zero: ExtensionTarget, + alphas: Vec, + z_last: ExtensionTarget, + lagrange_basis_first: ExtensionTarget, + lagrange_basis_last: ExtensionTarget, + ) -> Self { + Self { + constraint_accs: vec![zero; alphas.len()], + alphas, + z_last, + lagrange_basis_first, + lagrange_basis_last, + _phantom: Default::default(), + } + } + + pub fn accumulators(self) -> Vec> { + self.constraint_accs + } + /// Add one constraint valid on all rows except the last. pub fn constraint( &mut self, @@ -116,8 +137,9 @@ impl, const D: usize> RecursiveConstraintConsumer, constraint: ExtensionTarget, ) { - self.constraint_acc = - builder.scalar_mul_add_extension(self.alpha, self.constraint_acc, constraint); + for (&alpha, acc) in self.alphas.iter().zip(&mut self.constraint_accs) { + *acc = builder.scalar_mul_add_extension(alpha, *acc, constraint); + } } /// Add one constraint, but first multiply it by a filter such that it will only apply to the @@ -128,7 +150,7 @@ impl, const D: usize> RecursiveConstraintConsumer, ) { let filtered_constraint = builder.mul_extension(constraint, self.lagrange_basis_first); - self.constraint(builder, filtered_constraint); + self.constraint_wrapping(builder, filtered_constraint); } /// Add one constraint, but first multiply it by a filter such that it will only apply to the @@ -139,6 +161,6 @@ impl, const D: usize> RecursiveConstraintConsumer, ) { let filtered_constraint = builder.mul_extension(constraint, self.lagrange_basis_last); - self.constraint(builder, filtered_constraint); + self.constraint_wrapping(builder, filtered_constraint); } } diff --git a/starky/src/fibonacci_stark.rs b/starky/src/fibonacci_stark.rs index c77775e8..bd1775e1 100644 --- a/starky/src/fibonacci_stark.rs +++ b/starky/src/fibonacci_stark.rs @@ -67,9 +67,9 @@ impl, const D: usize> Stark for FibonacciStar yield_constr .constraint_last_row(vars.local_values[1] - vars.public_inputs[Self::PI_INDEX_RES]); - // x0 <- x1 + // x0' <- x1 yield_constr.constraint(vars.next_values[0] - vars.local_values[1]); - // x1 <- x0 + x1 + // x1' <- x0 + x1 yield_constr.constraint(vars.next_values[1] - vars.local_values[0] - vars.local_values[1]); } @@ -79,7 +79,25 @@ impl, const D: usize> Stark for FibonacciStar vars: StarkEvaluationTargets, yield_constr: &mut RecursiveConstraintConsumer, ) { - todo!() + // Check public inputs. + let pis_constraints = [ + builder.sub_extension(vars.local_values[0], vars.public_inputs[Self::PI_INDEX_X0]), + builder.sub_extension(vars.local_values[1], vars.public_inputs[Self::PI_INDEX_X1]), + builder.sub_extension(vars.local_values[1], vars.public_inputs[Self::PI_INDEX_RES]), + ]; + yield_constr.constraint_first_row(builder, pis_constraints[0]); + yield_constr.constraint_first_row(builder, pis_constraints[1]); + yield_constr.constraint_last_row(builder, pis_constraints[2]); + + // x0' <- x1 + let first_col_constraint = builder.sub_extension(vars.next_values[0], vars.local_values[1]); + yield_constr.constraint(builder, first_col_constraint); + // x1' <- x0 + x1 + let second_col_constraint = { + let tmp = builder.sub_extension(vars.next_values[1], vars.local_values[0]); + builder.sub_extension(tmp, vars.local_values[1]) + }; + yield_constr.constraint(builder, second_col_constraint); } fn constraint_degree(&self) -> usize { @@ -90,15 +108,28 @@ impl, const D: usize> Stark for FibonacciStar #[cfg(test)] mod tests { use anyhow::Result; + use plonky2::field::extension_field::Extendable; use plonky2::field::field_types::Field; - use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + use plonky2::hash::hash_types::RichField; + use plonky2::iop::witness::PartialWitness; + use plonky2::plonk::circuit_builder::CircuitBuilder; + use plonky2::plonk::circuit_data::CircuitConfig; + use plonky2::plonk::config::{ + AlgebraicHasher, GenericConfig, Hasher, PoseidonGoldilocksConfig, + }; use plonky2::util::timing::TimingTree; use crate::config::StarkConfig; use crate::fibonacci_stark::FibonacciStark; + use crate::proof::StarkProofWithPublicInputs; use crate::prover::prove; + use crate::recursive_verifier::{ + add_virtual_stark_proof_with_pis, recursively_verify_stark_proof, + set_stark_proof_with_pis_target, + }; + use crate::stark::Stark; use crate::stark_testing::test_stark_low_degree; - use crate::verifier::verify; + use crate::verifier::verify_stark_proof; fn fibonacci(n: usize, x0: F, x1: F) -> F { (0..n).fold((x0, x1), |x, _| (x.1, x.0 + x.1)).1 @@ -124,7 +155,7 @@ mod tests { &mut TimingTree::default(), )?; - verify(stark, proof, &config) + verify_stark_proof(stark, proof, &config) } #[test] @@ -134,9 +165,73 @@ mod tests { type F = >::F; type S = FibonacciStark; - let config = StarkConfig::standard_fast_config(); let num_rows = 1 << 5; let stark = S::new(num_rows); test_stark_low_degree(stark) } + + #[test] + fn test_recursive_stark_verifier() -> Result<()> { + init_logger(); + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type S = FibonacciStark; + + let config = StarkConfig::standard_fast_config(); + let num_rows = 1 << 5; + let public_inputs = [F::ZERO, F::ONE, fibonacci(num_rows - 1, F::ZERO, F::ONE)]; + let stark = S::new(num_rows); + let trace = stark.generate_trace(public_inputs[0], public_inputs[1]); + let proof = prove::( + stark, + &config, + trace, + public_inputs, + &mut TimingTree::default(), + )?; + verify_stark_proof(stark, proof.clone(), &config)?; + + recursive_proof::(stark, proof, &config, true) + } + + fn recursive_proof< + F: RichField + Extendable, + C: GenericConfig, + S: Stark + Copy, + InnerC: GenericConfig, + const D: usize, + >( + stark: S, + inner_proof: StarkProofWithPublicInputs, + inner_config: &StarkConfig, + print_gate_counts: bool, + ) -> Result<()> + where + InnerC::Hasher: AlgebraicHasher, + [(); S::COLUMNS]:, + [(); S::PUBLIC_INPUTS]:, + [(); C::Hasher::HASH_SIZE]:, + { + let circuit_config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(circuit_config); + let mut pw = PartialWitness::new(); + let degree_bits = inner_proof.proof.recover_degree_bits(inner_config); + let pt = add_virtual_stark_proof_with_pis(&mut builder, stark, inner_config, degree_bits); + set_stark_proof_with_pis_target(&mut pw, &pt, &inner_proof); + + recursively_verify_stark_proof::(&mut builder, stark, pt, inner_config); + + if print_gate_counts { + builder.print_gate_counts(0); + } + + let data = builder.build::(); + let proof = data.prove(pw)?; + data.verify(proof) + } + + fn init_logger() { + let _ = env_logger::builder().format_timestamp(None).try_init(); + } } diff --git a/starky/src/get_challenges.rs b/starky/src/get_challenges.rs index 7e89ca3e..24845c9a 100644 --- a/starky/src/get_challenges.rs +++ b/starky/src/get_challenges.rs @@ -1,16 +1,22 @@ use anyhow::Result; use plonky2::field::extension_field::Extendable; use plonky2::field::polynomial::PolynomialCoeffs; -use plonky2::fri::proof::FriProof; -use plonky2::hash::hash_types::RichField; +use plonky2::fri::proof::{FriProof, FriProofTarget}; +use plonky2::gadgets::polynomial::PolynomialCoeffsExtTarget; +use plonky2::hash::hash_types::{MerkleCapTarget, RichField}; use plonky2::hash::merkle_tree::MerkleCap; -use plonky2::iop::challenger::Challenger; -use plonky2::plonk::config::GenericConfig; +use plonky2::iop::challenger::{Challenger, RecursiveChallenger}; +use plonky2::iop::target::Target; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::plonk::config::{AlgebraicHasher, GenericConfig}; use crate::config::StarkConfig; -use crate::proof::{StarkOpeningSet, StarkProof, StarkProofChallenges, StarkProofWithPublicInputs}; +use crate::proof::{ + StarkOpeningSet, StarkOpeningSetTarget, StarkProof, StarkProofChallenges, + StarkProofChallengesTarget, StarkProofTarget, StarkProofWithPublicInputs, + StarkProofWithPublicInputsTarget, +}; -#[allow(clippy::too_many_arguments)] fn get_challenges, C: GenericConfig, const D: usize>( trace_cap: &MerkleCap, quotient_polys_cap: &MerkleCap, @@ -22,8 +28,6 @@ fn get_challenges, C: GenericConfig, cons degree_bits: usize, ) -> Result> { let num_challenges = config.num_challenges; - let num_fri_queries = config.fri_config.num_query_rounds; - let lde_size = 1 << (degree_bits + config.fri_config.rate_bits); let mut challenger = Challenger::::new(); @@ -94,6 +98,84 @@ impl, C: GenericConfig, const D: usize> } } +#[allow(clippy::too_many_arguments)] +pub(crate) fn get_challenges_target< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + builder: &mut CircuitBuilder, + trace_cap: &MerkleCapTarget, + quotient_polys_cap: &MerkleCapTarget, + openings: &StarkOpeningSetTarget, + commit_phase_merkle_caps: &[MerkleCapTarget], + final_poly: &PolynomialCoeffsExtTarget, + pow_witness: Target, + config: &StarkConfig, +) -> StarkProofChallengesTarget +where + C::Hasher: AlgebraicHasher, +{ + let num_challenges = config.num_challenges; + + let mut challenger = RecursiveChallenger::::new(builder); + + challenger.observe_cap(trace_cap); + let stark_alphas = challenger.get_n_challenges(builder, num_challenges); + + challenger.observe_cap(quotient_polys_cap); + let stark_zeta = challenger.get_extension_challenge(builder); + + challenger.observe_openings(&openings.to_fri_openings()); + + StarkProofChallengesTarget { + stark_alphas, + stark_zeta, + fri_challenges: challenger.fri_challenges::( + builder, + commit_phase_merkle_caps, + final_poly, + pow_witness, + &config.fri_config, + ), + } +} + +impl StarkProofWithPublicInputsTarget { + pub(crate) fn get_challenges, C: GenericConfig>( + &self, + builder: &mut CircuitBuilder, + config: &StarkConfig, + ) -> StarkProofChallengesTarget + where + C::Hasher: AlgebraicHasher, + { + let StarkProofTarget { + trace_cap, + quotient_polys_cap, + openings, + opening_proof: + FriProofTarget { + commit_phase_merkle_caps, + final_poly, + pow_witness, + .. + }, + } = &self.proof; + + get_challenges_target::( + builder, + trace_cap, + quotient_polys_cap, + openings, + commit_phase_merkle_caps, + final_poly, + *pow_witness, + config, + ) + } +} + // TODO: Deal with the compressed stuff. // impl, C: GenericConfig, const D: usize> // CompressedProofWithPublicInputs diff --git a/starky/src/lib.rs b/starky/src/lib.rs index eefab529..aee0ddbe 100644 --- a/starky/src/lib.rs +++ b/starky/src/lib.rs @@ -2,6 +2,7 @@ #![allow(dead_code)] #![allow(unused_variables)] #![allow(incomplete_features)] +#![allow(clippy::too_many_arguments)] #![feature(generic_const_exprs)] pub mod config; @@ -9,6 +10,7 @@ pub mod constraint_consumer; mod get_challenges; pub mod proof; pub mod prover; +pub mod recursive_verifier; pub mod stark; pub mod stark_testing; pub mod vars; diff --git a/starky/src/proof.rs b/starky/src/proof.rs index b7ecd912..e3fb6f72 100644 --- a/starky/src/proof.rs +++ b/starky/src/proof.rs @@ -1,12 +1,21 @@ -use plonky2::field::extension_field::Extendable; +use plonky2::field::extension_field::{Extendable, FieldExtension}; use plonky2::fri::oracle::PolynomialBatch; -use plonky2::fri::proof::{CompressedFriProof, FriChallenges, FriProof}; -use plonky2::fri::structure::{FriOpeningBatch, FriOpenings}; -use plonky2::hash::hash_types::RichField; +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; use rayon::prelude::*; +use crate::config::StarkConfig; + +#[derive(Debug, Clone)] pub struct StarkProof, C: GenericConfig, const D: usize> { /// Merkle cap of LDEs of trace values. pub trace_cap: MerkleCap, @@ -18,6 +27,36 @@ pub struct StarkProof, C: GenericConfig, pub opening_proof: FriProof, } +impl, C: GenericConfig, const D: usize> StarkProof { + pub(crate) 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 + } +} + +pub struct StarkProofTarget { + pub trace_cap: MerkleCapTarget, + pub quotient_polys_cap: MerkleCapTarget, + pub openings: StarkOpeningSetTarget, + pub opening_proof: FriProofTarget, +} + +impl StarkProofTarget { + pub(crate) 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 + } +} + +#[derive(Debug, Clone)] pub struct StarkProofWithPublicInputs< F: RichField + Extendable, C: GenericConfig, @@ -28,6 +67,11 @@ pub struct StarkProofWithPublicInputs< pub public_inputs: Vec, } +pub struct StarkProofWithPublicInputsTarget { + pub proof: StarkProofTarget, + pub public_inputs: Vec, +} + pub struct CompressedStarkProof< F: RichField + Extendable, C: GenericConfig, @@ -60,7 +104,14 @@ pub(crate) struct StarkProofChallenges, const D: us pub fri_challenges: FriChallenges, } +pub(crate) struct StarkProofChallengesTarget { + pub stark_alphas: Vec, + pub stark_zeta: ExtensionTarget, + pub fri_challenges: FriChallengesTarget, +} + /// Purported values of each polynomial at the challenge point. +#[derive(Debug, Clone)] pub struct StarkOpeningSet, const D: usize> { pub local_values: Vec, pub next_values: Vec, @@ -72,7 +123,7 @@ pub struct StarkOpeningSet, const D: usize> { impl, const D: usize> StarkOpeningSet { pub fn new>( zeta: F::Extension, - g: F::Extension, + g: F, trace_commitment: &PolynomialBatch, quotient_commitment: &PolynomialBatch, ) -> Self { @@ -84,7 +135,7 @@ impl, const D: usize> StarkOpeningSet { }; Self { local_values: eval_commitment(zeta, trace_commitment), - next_values: eval_commitment(zeta * g, trace_commitment), + next_values: eval_commitment(zeta.scalar_mul(g), trace_commitment), permutation_zs: vec![/*TODO*/], permutation_zs_right: vec![/*TODO*/], quotient_polys: eval_commitment(zeta, quotient_commitment), @@ -112,3 +163,34 @@ impl, const D: usize> StarkOpeningSet { } } } + +pub struct StarkOpeningSetTarget { + pub local_values: Vec>, + pub next_values: Vec>, + pub permutation_zs: Vec>, + pub permutation_zs_right: Vec>, + pub quotient_polys: Vec>, +} + +impl StarkOpeningSetTarget { + pub(crate) fn to_fri_openings(&self) -> FriOpeningsTarget { + let zeta_batch = FriOpeningBatchTarget { + values: [ + self.local_values.as_slice(), + self.quotient_polys.as_slice(), + self.permutation_zs.as_slice(), + ] + .concat(), + }; + let zeta_right_batch = FriOpeningBatchTarget { + values: [ + self.next_values.as_slice(), + self.permutation_zs_right.as_slice(), + ] + .concat(), + }; + FriOpeningsTarget { + batches: vec![zeta_batch, zeta_right_batch], + } + } +} diff --git a/starky/src/prover.rs b/starky/src/prover.rs index e88aa619..2d57a60a 100644 --- a/starky/src/prover.rs +++ b/starky/src/prover.rs @@ -107,7 +107,7 @@ where // To avoid leaking witness data, we want to ensure that our opening locations, `zeta` and // `g * zeta`, are not in our subgroup `H`. It suffices to check `zeta` only, since // `(g * zeta)^n = zeta^n`, where `n` is the order of `g`. - let g = F::Extension::primitive_root_of_unity(degree_bits); + let g = F::primitive_root_of_unity(degree_bits); ensure!( zeta.exp_power_of_2(degree_bits) != F::Extension::ONE, "Opening point is in the subgroup." @@ -115,7 +115,7 @@ where let openings = StarkOpeningSet::new(zeta, g, &trace_commitment, "ient_commitment); challenger.observe_openings(&openings.to_fri_openings()); - // TODO: Add permuation checks + // TODO: Add permutation checks let initial_merkle_trees = &[&trace_commitment, "ient_commitment]; let fri_params = config.fri_params(degree_bits); @@ -123,7 +123,7 @@ where timing, "compute openings proof", PolynomialBatch::prove_openings( - &stark.fri_instance(zeta, g, rate_bits, config.num_challenges), + &stark.fri_instance(zeta, g, config.num_challenges), initial_merkle_trees, &mut challenger, &fri_params, diff --git a/starky/src/recursive_verifier.rs b/starky/src/recursive_verifier.rs new file mode 100644 index 00000000..1a31488e --- /dev/null +++ b/starky/src/recursive_verifier.rs @@ -0,0 +1,259 @@ +use itertools::Itertools; +use plonky2::field::extension_field::Extendable; +use plonky2::field::field_types::Field; +use plonky2::fri::witness_util::set_fri_proof_target; +use plonky2::hash::hash_types::RichField; +use plonky2::iop::ext_target::ExtensionTarget; +use plonky2::iop::witness::Witness; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::plonk::config::{AlgebraicHasher, GenericConfig}; +use plonky2::util::reducing::ReducingFactorTarget; + +use crate::config::StarkConfig; +use crate::constraint_consumer::RecursiveConstraintConsumer; +use crate::proof::{ + StarkOpeningSetTarget, StarkProof, StarkProofChallengesTarget, StarkProofTarget, + StarkProofWithPublicInputs, StarkProofWithPublicInputsTarget, +}; +use crate::stark::Stark; +use crate::vars::StarkEvaluationTargets; + +pub fn recursively_verify_stark_proof< + F: RichField + Extendable, + C: GenericConfig, + S: Stark, + const D: usize, +>( + builder: &mut CircuitBuilder, + stark: S, + proof_with_pis: StarkProofWithPublicInputsTarget, + inner_config: &StarkConfig, +) where + C::Hasher: AlgebraicHasher, + [(); S::COLUMNS]:, + [(); S::PUBLIC_INPUTS]:, +{ + assert_eq!(proof_with_pis.public_inputs.len(), S::PUBLIC_INPUTS); + let degree_bits = proof_with_pis.proof.recover_degree_bits(inner_config); + let challenges = proof_with_pis.get_challenges::(builder, inner_config); + + recursively_verify_stark_proof_with_challenges::( + builder, + stark, + proof_with_pis, + challenges, + inner_config, + degree_bits, + ); +} + +/// Recursively verifies an inner proof. +fn recursively_verify_stark_proof_with_challenges< + F: RichField + Extendable, + C: GenericConfig, + S: Stark, + const D: usize, +>( + builder: &mut CircuitBuilder, + stark: S, + proof_with_pis: StarkProofWithPublicInputsTarget, + challenges: StarkProofChallengesTarget, + inner_config: &StarkConfig, + degree_bits: usize, +) where + C::Hasher: AlgebraicHasher, + [(); S::COLUMNS]:, + [(); S::PUBLIC_INPUTS]:, +{ + let one = builder.one_extension(); + + let StarkProofWithPublicInputsTarget { + proof, + public_inputs, + } = proof_with_pis; + let StarkOpeningSetTarget { + local_values, + next_values, + permutation_zs, + permutation_zs_right, + quotient_polys, + } = &proof.openings; + let vars = StarkEvaluationTargets { + local_values: &local_values.to_vec().try_into().unwrap(), + next_values: &next_values.to_vec().try_into().unwrap(), + public_inputs: &public_inputs + .into_iter() + .map(|t| builder.convert_to_ext(t)) + .collect::>() + .try_into() + .unwrap(), + }; + let zeta_pow_deg = builder.exp_power_of_2_extension(challenges.stark_zeta, degree_bits); + let z_h_zeta = builder.sub_extension(zeta_pow_deg, one); + let (l_1, l_last) = + eval_l_1_and_l_last_recursively(builder, degree_bits, challenges.stark_zeta, z_h_zeta); + let last = + builder.constant_extension(F::Extension::primitive_root_of_unity(degree_bits).inverse()); + let z_last = builder.sub_extension(challenges.stark_zeta, last); + let mut consumer = RecursiveConstraintConsumer::::new( + builder.zero_extension(), + challenges.stark_alphas, + z_last, + l_1, + l_last, + ); + stark.eval_ext_recursively(builder, vars, &mut consumer); + let vanishing_polys_zeta = consumer.accumulators(); + + // Check each polynomial identity, of the form `vanishing(x) = Z_H(x) quotient(x)`, at zeta. + let quotient_polys_zeta = &proof.openings.quotient_polys; + let mut scale = ReducingFactorTarget::new(zeta_pow_deg); + for (i, chunk) in quotient_polys_zeta + .chunks(stark.quotient_degree_factor()) + .enumerate() + { + let recombined_quotient = scale.reduce(chunk, builder); + let computed_vanishing_poly = builder.mul_extension(z_h_zeta, recombined_quotient); + builder.connect_extension(vanishing_polys_zeta[i], computed_vanishing_poly); + } + + // TODO: Permutation polynomials. + let merkle_caps = &[proof.trace_cap, proof.quotient_polys_cap]; + + let fri_instance = stark.fri_instance_target( + builder, + challenges.stark_zeta, + F::primitive_root_of_unity(degree_bits), + inner_config.num_challenges, + ); + builder.verify_fri_proof::( + &fri_instance, + &proof.openings.to_fri_openings(), + &challenges.fri_challenges, + merkle_caps, + &proof.opening_proof, + &inner_config.fri_params(degree_bits), + ); +} + +fn eval_l_1_and_l_last_recursively, const D: usize>( + builder: &mut CircuitBuilder, + log_n: usize, + x: ExtensionTarget, + z_x: ExtensionTarget, +) -> (ExtensionTarget, ExtensionTarget) { + let n = builder.constant_extension(F::Extension::from_canonical_usize(1 << log_n)); + let g = builder.constant_extension(F::Extension::primitive_root_of_unity(log_n)); + let one = builder.one_extension(); + let l_1_deno = builder.mul_sub_extension(n, x, n); + let l_last_deno = builder.mul_sub_extension(g, x, one); + let l_last_deno = builder.mul_extension(n, l_last_deno); + + ( + builder.div_extension(z_x, l_1_deno), + builder.div_extension(z_x, l_last_deno), + ) +} + +pub fn add_virtual_stark_proof_with_pis< + F: RichField + Extendable, + S: Stark, + const D: usize, +>( + builder: &mut CircuitBuilder, + stark: S, + config: &StarkConfig, + degree_bits: usize, +) -> StarkProofWithPublicInputsTarget { + let proof = add_virtual_stark_proof::(builder, stark, config, degree_bits); + let public_inputs = builder.add_virtual_targets(S::PUBLIC_INPUTS); + StarkProofWithPublicInputsTarget { + proof, + public_inputs, + } +} + +pub fn add_virtual_stark_proof, S: Stark, const D: usize>( + builder: &mut CircuitBuilder, + stark: S, + config: &StarkConfig, + degree_bits: usize, +) -> StarkProofTarget { + let fri_params = config.fri_params(degree_bits); + let cap_height = fri_params.config.cap_height; + + let num_leaves_per_oracle = &[ + S::COLUMNS, + // TODO: permutation polys + stark.quotient_degree_factor() * config.num_challenges, + ]; + + StarkProofTarget { + trace_cap: builder.add_virtual_cap(cap_height), + quotient_polys_cap: builder.add_virtual_cap(cap_height), + openings: add_stark_opening_set::(builder, stark, config), + opening_proof: builder.add_virtual_fri_proof(num_leaves_per_oracle, &fri_params), + } +} + +fn add_stark_opening_set, S: Stark, const D: usize>( + builder: &mut CircuitBuilder, + stark: S, + config: &StarkConfig, +) -> StarkOpeningSetTarget { + let num_challenges = config.num_challenges; + StarkOpeningSetTarget { + local_values: builder.add_virtual_extension_targets(S::COLUMNS), + next_values: builder.add_virtual_extension_targets(S::COLUMNS), + permutation_zs: vec![/*TODO*/], + permutation_zs_right: vec![/*TODO*/], + quotient_polys: builder + .add_virtual_extension_targets(stark.quotient_degree_factor() * num_challenges), + } +} + +pub fn set_stark_proof_with_pis_target, W, const D: usize>( + witness: &mut W, + stark_proof_with_pis_target: &StarkProofWithPublicInputsTarget, + stark_proof_with_pis: &StarkProofWithPublicInputs, +) where + F: RichField + Extendable, + C::Hasher: AlgebraicHasher, + W: Witness, +{ + let StarkProofWithPublicInputs { + proof, + public_inputs, + } = stark_proof_with_pis; + let StarkProofWithPublicInputsTarget { + proof: pt, + public_inputs: pi_targets, + } = stark_proof_with_pis_target; + + // Set public inputs. + for (&pi_t, &pi) in pi_targets.iter().zip_eq(public_inputs) { + witness.set_target(pi_t, pi); + } + + set_stark_proof_target(witness, pt, proof); +} + +pub fn set_stark_proof_target, W, const D: usize>( + witness: &mut W, + proof_target: &StarkProofTarget, + proof: &StarkProof, +) where + F: RichField + Extendable, + C::Hasher: AlgebraicHasher, + W: Witness, +{ + witness.set_cap_target(&proof_target.trace_cap, &proof.trace_cap); + witness.set_cap_target(&proof_target.quotient_polys_cap, &proof.quotient_polys_cap); + + witness.set_fri_openings( + &proof_target.openings.to_fri_openings(), + &proof.openings.to_fri_openings(), + ); + + set_fri_proof_target(witness, &proof_target.opening_proof, &proof.opening_proof); +} diff --git a/starky/src/stark.rs b/starky/src/stark.rs index 4b20553e..98365344 100644 --- a/starky/src/stark.rs +++ b/starky/src/stark.rs @@ -1,7 +1,11 @@ use plonky2::field::extension_field::{Extendable, FieldExtension}; use plonky2::field::packed_field::PackedField; -use plonky2::fri::structure::{FriBatchInfo, FriInstanceInfo, FriOracleInfo, FriPolynomialInfo}; +use plonky2::fri::structure::{ + FriBatchInfo, FriBatchInfoTarget, FriInstanceInfo, FriInstanceInfoTarget, FriOracleInfo, + FriPolynomialInfo, +}; use plonky2::hash::hash_types::RichField; +use plonky2::iop::ext_target::ExtensionTarget; use plonky2::plonk::circuit_builder::CircuitBuilder; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; @@ -75,8 +79,7 @@ pub trait Stark, const D: usize>: Sync { fn fri_instance( &self, zeta: F::Extension, - g: F::Extension, - rate_bits: usize, + g: F, num_challenges: usize, ) -> FriInstanceInfo { let no_blinding_oracle = FriOracleInfo { blinding: false }; @@ -88,12 +91,40 @@ pub trait Stark, const D: usize>: Sync { polynomials: [trace_info.clone(), quotient_info].concat(), }; let zeta_right_batch = FriBatchInfo:: { - point: zeta * g, + point: zeta.scalar_mul(g), polynomials: trace_info, }; FriInstanceInfo { oracles: vec![no_blinding_oracle; 3], - batches: vec![zeta_batch], + batches: vec![zeta_batch, zeta_right_batch], + } + } + + /// Computes the FRI instance used to prove this Stark. + // TODO: Permutation polynomials. + fn fri_instance_target( + &self, + builder: &mut CircuitBuilder, + zeta: ExtensionTarget, + g: F, + num_challenges: usize, + ) -> FriInstanceInfoTarget { + let no_blinding_oracle = FriOracleInfo { blinding: false }; + let trace_info = FriPolynomialInfo::from_range(0, 0..Self::COLUMNS); + let quotient_info = + FriPolynomialInfo::from_range(1, 0..self.quotient_degree_factor() * num_challenges); + let zeta_batch = FriBatchInfoTarget { + point: zeta, + polynomials: [trace_info.clone(), quotient_info].concat(), + }; + let zeta_right = builder.mul_const_extension(g, zeta); + let zeta_right_batch = FriBatchInfoTarget { + point: zeta_right, + polynomials: trace_info, + }; + FriInstanceInfoTarget { + oracles: vec![no_blinding_oracle; 3], + batches: vec![zeta_batch, zeta_right_batch], } } } diff --git a/starky/src/verifier.rs b/starky/src/verifier.rs index 8bf1faab..b27cf2b3 100644 --- a/starky/src/verifier.rs +++ b/starky/src/verifier.rs @@ -5,15 +5,14 @@ use plonky2::fri::verifier::verify_fri_proof; use plonky2::hash::hash_types::RichField; use plonky2::plonk::config::{GenericConfig, Hasher}; use plonky2::plonk::plonk_common::reduce_with_powers; -use plonky2_util::log2_strict; use crate::config::StarkConfig; use crate::constraint_consumer::ConstraintConsumer; -use crate::proof::{StarkOpeningSet, StarkProof, StarkProofChallenges, StarkProofWithPublicInputs}; +use crate::proof::{StarkOpeningSet, StarkProofChallenges, StarkProofWithPublicInputs}; use crate::stark::Stark; use crate::vars::StarkEvaluationVars; -pub fn verify< +pub fn verify_stark_proof< F: RichField + Extendable, C: GenericConfig, S: Stark, @@ -28,12 +27,13 @@ where [(); S::PUBLIC_INPUTS]:, [(); C::Hasher::HASH_SIZE]:, { - let degree_bits = log2_strict(recover_degree(&proof_with_pis.proof, config)); + ensure!(proof_with_pis.public_inputs.len() == S::PUBLIC_INPUTS); + let degree_bits = proof_with_pis.proof.recover_degree_bits(config); let challenges = proof_with_pis.get_challenges(config, degree_bits)?; - verify_with_challenges(stark, proof_with_pis, challenges, degree_bits, config) + verify_stark_proof_with_challenges(stark, proof_with_pis, challenges, degree_bits, config) } -pub(crate) fn verify_with_challenges< +pub(crate) fn verify_stark_proof_with_challenges< F: RichField + Extendable, C: GenericConfig, S: Stark, @@ -54,8 +54,6 @@ where proof, public_inputs, } = proof_with_pis; - let local_values = &proof.openings.local_values; - let next_values = &proof.openings.local_values; let StarkOpeningSet { local_values, next_values, @@ -112,8 +110,7 @@ where verify_fri_proof::( &stark.fri_instance( challenges.stark_zeta, - F::primitive_root_of_unity(degree_bits).into(), - config.fri_config.rate_bits, + F::primitive_root_of_unity(degree_bits), config.num_challenges, ), &proof.openings.to_fri_openings(), @@ -139,17 +136,6 @@ fn eval_l_1_and_l_last(log_n: usize, x: F) -> (F, F) { } /// Recover the length of the trace from a STARK proof and a STARK config. -fn recover_degree, C: GenericConfig, const D: usize>( - proof: &StarkProof, - config: &StarkConfig, -) -> usize { - let initial_merkle_proof = &proof.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(); - 1 << (lde_bits - config.fri_config.rate_bits) -} #[cfg(test)] mod tests { diff --git a/system_zero/src/system_zero.rs b/system_zero/src/system_zero.rs index 2eeb4697..c1062e2a 100644 --- a/system_zero/src/system_zero.rs +++ b/system_zero/src/system_zero.rs @@ -117,7 +117,7 @@ mod tests { use starky::prover::prove; use starky::stark::Stark; use starky::stark_testing::test_stark_low_degree; - use starky::verifier::verify; + use starky::verifier::verify_stark_proof; use crate::system_zero::SystemZero; @@ -136,7 +136,7 @@ mod tests { let trace = system.generate_trace(); let proof = prove::(system, &config, trace, public_inputs, &mut timing)?; - verify(system, proof, &config) + verify_stark_proof(system, proof, &config) } #[test]