use plonky2::field::extension::Extendable; use plonky2::field::polynomial::PolynomialCoeffs; 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, RecursiveChallenger}; use plonky2::iop::target::Target; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::config::{AlgebraicHasher, GenericConfig}; use crate::config::StarkConfig; use crate::lookup::{ get_grand_product_challenge_set, get_grand_product_challenge_set_target, GrandProductChallengeSet, }; use crate::proof::*; /// Generates challenges for a STARK proof from a challenger and given /// all the arguments needed to update the challenger state. /// /// Note: `trace_cap` is passed as `Option` to signify whether to observe it /// or not by the challenger. Observing it here could be redundant in a /// multi-STARK system where trace caps would have already been observed /// before proving individually each STARK. fn get_challenges( challenger: &mut Challenger, challenges: Option<&GrandProductChallengeSet>, trace_cap: Option<&MerkleCap>, auxiliary_polys_cap: Option<&MerkleCap>, quotient_polys_cap: Option<&MerkleCap>, openings: &StarkOpeningSet, commit_phase_merkle_caps: &[MerkleCap], final_poly: &PolynomialCoeffs, pow_witness: F, config: &StarkConfig, degree_bits: usize, ) -> StarkProofChallenges where F: RichField + Extendable, C: GenericConfig, { let num_challenges = config.num_challenges; if let Some(cap) = &trace_cap { challenger.observe_cap(cap); } let lookup_challenge_set = if let Some(&challenges) = challenges.as_ref() { Some(challenges.clone()) } else { auxiliary_polys_cap .is_some() .then(|| get_grand_product_challenge_set(challenger, num_challenges)) }; if let Some(cap) = &auxiliary_polys_cap { challenger.observe_cap(cap); } let stark_alphas = challenger.get_n_challenges(num_challenges); if let Some(quotient_polys_cap) = quotient_polys_cap { challenger.observe_cap(quotient_polys_cap); } let stark_zeta = challenger.get_extension_challenge::(); challenger.observe_openings(&openings.to_fri_openings()); StarkProofChallenges { lookup_challenge_set, stark_alphas, stark_zeta, fri_challenges: challenger.fri_challenges::( commit_phase_merkle_caps, final_poly, pow_witness, degree_bits, &config.fri_config, ), } } impl StarkProof where F: RichField + Extendable, C: GenericConfig, { /// Computes all Fiat-Shamir challenges used in the STARK proof. /// For a single STARK system, the `ignore_trace_cap` boolean should /// always be set to `false`. /// /// Multi-STARK systems may already observe individual trace caps /// ahead of proving each table, and hence may ignore observing /// again the cap when generating individual challenges. 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 StarkProof { trace_cap, auxiliary_polys_cap, quotient_polys_cap, openings, opening_proof: FriProof { commit_phase_merkle_caps, final_poly, pow_witness, .. }, } = &self; let trace_cap = if ignore_trace_cap { None } else { Some(trace_cap) }; get_challenges::( challenger, challenges, trace_cap, auxiliary_polys_cap.as_ref(), quotient_polys_cap.as_ref(), openings, commit_phase_merkle_caps, final_poly, *pow_witness, config, degree_bits, ) } } impl StarkProofWithPublicInputs where F: RichField + Extendable, C: GenericConfig, { /// Computes all Fiat-Shamir challenges used in the STARK proof. /// For a single STARK system, the `ignore_trace_cap` boolean should /// always be set to `false`. /// /// Multi-STARK systems may already observe individual trace caps /// ahead of proving each table, and hence may ignore observing /// again the cap when generating individual challenges. pub fn get_challenges( &self, challenger: &mut Challenger, challenges: Option<&GrandProductChallengeSet>, ignore_trace_cap: bool, config: &StarkConfig, ) -> StarkProofChallenges { challenger.observe_elements(&self.public_inputs); self.proof .get_challenges(challenger, challenges, ignore_trace_cap, config) } } /// Circuit version of `get_challenges`, with the same flexibility around /// `trace_cap` being passed as an `Option`. fn get_challenges_target( builder: &mut CircuitBuilder, challenger: &mut RecursiveChallenger, challenges: Option<&GrandProductChallengeSet>, trace_cap: Option<&MerkleCapTarget>, auxiliary_polys_cap: Option<&MerkleCapTarget>, quotient_polys_cap: Option<&MerkleCapTarget>, openings: &StarkOpeningSetTarget, commit_phase_merkle_caps: &[MerkleCapTarget], final_poly: &PolynomialCoeffsExtTarget, pow_witness: Target, config: &StarkConfig, ) -> StarkProofChallengesTarget where F: RichField + Extendable, C: GenericConfig, C::Hasher: AlgebraicHasher, { let num_challenges = config.num_challenges; if let Some(trace_cap) = trace_cap { challenger.observe_cap(trace_cap); } let lookup_challenge_set = if let Some(&challenges) = challenges.as_ref() { Some(challenges.clone()) } else { auxiliary_polys_cap .is_some() .then(|| get_grand_product_challenge_set_target(builder, challenger, num_challenges)) }; if let Some(cap) = auxiliary_polys_cap { challenger.observe_cap(cap); } let stark_alphas = challenger.get_n_challenges(builder, num_challenges); if let Some(cap) = quotient_polys_cap { challenger.observe_cap(cap); } let stark_zeta = challenger.get_extension_challenge(builder); challenger.observe_openings(&openings.to_fri_openings(builder.zero())); StarkProofChallengesTarget { lookup_challenge_set, stark_alphas, stark_zeta, fri_challenges: challenger.fri_challenges( builder, commit_phase_merkle_caps, final_poly, pow_witness, &config.fri_config, ), } } impl StarkProofTarget { /// Creates all Fiat-Shamir `Target` challenges used in the STARK proof. /// For a single STARK system, the `ignore_trace_cap` boolean should /// always be set to `false`. /// /// Multi-STARK systems may already observe individual trace caps /// ahead of proving each table, and hence may ignore observing /// again the cap when generating individual challenges. pub fn get_challenges( &self, builder: &mut CircuitBuilder, challenger: &mut RecursiveChallenger, challenges: Option<&GrandProductChallengeSet>, ignore_trace_cap: bool, config: &StarkConfig, ) -> StarkProofChallengesTarget where F: RichField + Extendable, C: GenericConfig, C::Hasher: AlgebraicHasher, { let StarkProofTarget { trace_cap, auxiliary_polys_cap, quotient_polys_cap, openings, opening_proof: FriProofTarget { commit_phase_merkle_caps, final_poly, pow_witness, .. }, } = self; let trace_cap = if ignore_trace_cap { None } else { Some(trace_cap) }; get_challenges_target::( builder, challenger, challenges, trace_cap, auxiliary_polys_cap.as_ref(), quotient_polys_cap.as_ref(), openings, commit_phase_merkle_caps, final_poly, *pow_witness, config, ) } } impl StarkProofWithPublicInputsTarget { /// Creates all Fiat-Shamir `Target` challenges used in the STARK proof. /// For a single STARK system, the `ignore_trace_cap` boolean should /// always be set to `false`. /// /// Multi-STARK systems may already observe individual trace caps /// ahead of proving each table, and hence may ignore observing /// again the cap when generating individual challenges. pub fn get_challenges( &self, builder: &mut CircuitBuilder, challenger: &mut RecursiveChallenger, challenges: Option<&GrandProductChallengeSet>, ignore_trace_cap: bool, config: &StarkConfig, ) -> StarkProofChallengesTarget where F: RichField + Extendable, C: GenericConfig, C::Hasher: AlgebraicHasher, { challenger.observe_elements(&self.public_inputs); self.proof .get_challenges::(builder, challenger, challenges, ignore_trace_cap, config) } } // TODO: Deal with the compressed stuff. // impl, C: GenericConfig, const D: usize> // CompressedProofWithPublicInputs // { // /// Computes all Fiat-Shamir challenges used in the Plonk proof. // pub(crate) fn get_challenges( // &self, // common_data: &CommonCircuitData, // ) -> anyhow::Result> { // let CompressedProof { // wires_cap, // plonk_zs_partial_products_cap, // quotient_polys_cap, // openings, // opening_proof: // CompressedFriProof { // commit_phase_merkle_caps, // final_poly, // pow_witness, // .. // }, // } = &self.proof; // // get_challenges( // self.get_public_inputs_hash(), // wires_cap, // plonk_zs_partial_products_cap, // quotient_polys_cap, // openings, // commit_phase_merkle_caps, // final_poly, // *pow_witness, // common_data, // ) // } // // /// Computes all coset elements that can be inferred in the FRI reduction steps. // pub(crate) fn get_inferred_elements( // &self, // challenges: &ProofChallenges, // common_data: &CommonCircuitData, // ) -> FriInferredElements { // let ProofChallenges { // plonk_zeta, // fri_alpha, // fri_betas, // fri_query_indices, // .. // } = challenges; // let mut fri_inferred_elements = Vec::new(); // // Holds the indices that have already been seen at each reduction depth. // let mut seen_indices_by_depth = // vec![HashSet::new(); common_data.fri_params.reduction_arity_bits.len()]; // let precomputed_reduced_evals = PrecomputedReducedOpenings::from_os_and_alpha( // &self.proof.openings.to_fri_openings(), // *fri_alpha, // ); // let log_n = common_data.degree_bits + common_data.config.fri_config.rate_bits; // // Simulate the proof verification and collect the inferred elements. // // The content of the loop is basically the same as the `fri_verifier_query_round` function. // for &(mut x_index) in fri_query_indices { // let mut subgroup_x = F::MULTIPLICATIVE_GROUP_GENERATOR // * F::primitive_root_of_unity(log_n).exp_u64(reverse_bits(x_index, log_n) as u64); // let mut old_eval = fri_combine_initial::( // &common_data.get_fri_instance(*plonk_zeta), // &self // .proof // .opening_proof // .query_round_proofs // .initial_trees_proofs[&x_index], // *fri_alpha, // subgroup_x, // &precomputed_reduced_evals, // &common_data.fri_params, // ); // for (i, &arity_bits) in common_data // .fri_params // .reduction_arity_bits // .iter() // .enumerate() // { // let coset_index = x_index >> arity_bits; // if !seen_indices_by_depth[i].insert(coset_index) { // // If this index has already been seen, we can skip the rest of the reductions. // break; // } // fri_inferred_elements.push(old_eval); // let arity = 1 << arity_bits; // let mut evals = self.proof.opening_proof.query_round_proofs.steps[i][&coset_index] // .evals // .clone(); // let x_index_within_coset = x_index & (arity - 1); // evals.insert(x_index_within_coset, old_eval); // old_eval = compute_evaluation( // subgroup_x, // x_index_within_coset, // arity_bits, // &evals, // fri_betas[i], // ); // subgroup_x = subgroup_x.exp_power_of_2(arity_bits); // x_index = coset_index; // } // } // FriInferredElements(fri_inferred_elements) // } // }