Unified Recursion Circuit for Multi-Degree Starky Proof Verification (#1635)

* add test

* wip

* update witness util

* degree_bits: usize->target

* wip

* fix

* opt

* passed 3 tests

* fix

* convert g to g_ext

* hack observe final poly coeffs

* wip

* poc works

* wip

* pass tests

* more in test

* better test

* fix ci

* clippy

* fix

* fix

* start on multi steps

* wip

* set all zeros

* wip

* challenge passes

* work

* poc done

* fix non std build

* add comments

* update stark verifier

* fix clippy

* fix test build

* fix tests

* add comments

* add checks

* polish the checks

* more checks

* comments
This commit is contained in:
Sai 2024-11-25 16:58:08 +00:00 committed by GitHub
parent 2488cdacd4
commit 7203b7ad0b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 804 additions and 88 deletions

View File

@ -450,6 +450,8 @@ mod test {
proof.pow_witness, proof.pow_witness,
k0, k0,
&fri_params.config, &fri_params.config,
None,
None,
); );
let degree_bits = [k0, k1, k2]; let degree_bits = [k0, k1, k2];
let merkle_cap = trace_oracle.batch_merkle_tree.cap; let merkle_cap = trace_oracle.batch_merkle_tree.cap;

View File

@ -318,6 +318,8 @@ mod tests {
proof.pow_witness, proof.pow_witness,
k, k,
&fri_params.config, &fri_params.config,
None,
None,
); );
let fri_opening_batch = FriOpeningBatch { let fri_opening_batch = FriOpeningBatch {
@ -440,6 +442,8 @@ mod tests {
proof.pow_witness, proof.pow_witness,
k0, k0,
&fri_params.config, &fri_params.config,
None,
None,
); );
let fri_opening_batch_0 = FriOpenings { let fri_opening_batch_0 = FriOpenings {
batches: vec![FriOpeningBatch { batches: vec![FriOpeningBatch {

View File

@ -1,10 +1,14 @@
#[cfg(not(feature = "std"))]
use alloc::vec;
use crate::field::extension::Extendable; use crate::field::extension::Extendable;
use crate::field::polynomial::PolynomialCoeffs; use crate::field::polynomial::PolynomialCoeffs;
use crate::field::types::Field;
use crate::fri::proof::{FriChallenges, FriChallengesTarget}; use crate::fri::proof::{FriChallenges, FriChallengesTarget};
use crate::fri::structure::{FriOpenings, FriOpeningsTarget}; use crate::fri::structure::{FriOpenings, FriOpeningsTarget};
use crate::fri::FriConfig; use crate::fri::FriConfig;
use crate::gadgets::polynomial::PolynomialCoeffsExtTarget; use crate::gadgets::polynomial::PolynomialCoeffsExtTarget;
use crate::hash::hash_types::{MerkleCapTarget, RichField}; use crate::hash::hash_types::{MerkleCapTarget, RichField, NUM_HASH_OUT_ELTS};
use crate::hash::merkle_tree::MerkleCap; use crate::hash::merkle_tree::MerkleCap;
use crate::iop::challenger::{Challenger, RecursiveChallenger}; use crate::iop::challenger::{Challenger, RecursiveChallenger};
use crate::iop::target::Target; use crate::iop::target::Target;
@ -28,6 +32,8 @@ impl<F: RichField, H: Hasher<F>> Challenger<F, H> {
pow_witness: F, pow_witness: F,
degree_bits: usize, degree_bits: usize,
config: &FriConfig, config: &FriConfig,
final_poly_coeff_len: Option<usize>,
max_num_query_steps: Option<usize>,
) -> FriChallenges<F, D> ) -> FriChallenges<F, D>
where where
F: RichField + Extendable<D>, F: RichField + Extendable<D>,
@ -46,7 +52,26 @@ impl<F: RichField, H: Hasher<F>> Challenger<F, H> {
}) })
.collect(); .collect();
// When this proof was generated in a circuit with a different number of query steps,
// the challenger needs to observe the additional hash caps.
if let Some(step_count) = max_num_query_steps {
let cap_len = (1 << config.cap_height) * NUM_HASH_OUT_ELTS;
let zero_cap = vec![F::ZERO; cap_len];
for _ in commit_phase_merkle_caps.len()..step_count {
self.observe_elements(&zero_cap);
self.get_extension_challenge::<D>();
}
}
self.observe_extension_elements(&final_poly.coeffs); self.observe_extension_elements(&final_poly.coeffs);
// When this proof was generated in a circuit with a different final polynomial length,
// the challenger needs to observe the full length of the final polynomial.
if let Some(len) = final_poly_coeff_len {
let current_len = final_poly.coeffs.len();
for _ in current_len..len {
self.observe_extension_element(&F::Extension::ZERO);
}
}
self.observe_element(pow_witness); self.observe_element(pow_witness);
let fri_pow_response = self.get_challenge(); let fri_pow_response = self.get_challenge();

View File

@ -178,6 +178,8 @@ impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
oracles: &[&Self], oracles: &[&Self],
challenger: &mut Challenger<F, C::Hasher>, challenger: &mut Challenger<F, C::Hasher>,
fri_params: &FriParams, fri_params: &FriParams,
final_poly_coeff_len: Option<usize>,
max_num_query_steps: Option<usize>,
timing: &mut TimingTree, timing: &mut TimingTree,
) -> FriProof<F, C::Hasher, D> { ) -> FriProof<F, C::Hasher, D> {
assert!(D > 1, "Not implemented for D=1."); assert!(D > 1, "Not implemented for D=1.");
@ -226,6 +228,8 @@ impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
lde_final_values, lde_final_values,
challenger, challenger,
fri_params, fri_params,
final_poly_coeff_len,
max_num_query_steps,
timing, timing,
); );

View File

@ -1,13 +1,16 @@
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::vec;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec; use alloc::vec::Vec;
use plonky2_field::types::Field;
use plonky2_maybe_rayon::*; use plonky2_maybe_rayon::*;
use crate::field::extension::{flatten, unflatten, Extendable}; use crate::field::extension::{flatten, unflatten, Extendable};
use crate::field::polynomial::{PolynomialCoeffs, PolynomialValues}; use crate::field::polynomial::{PolynomialCoeffs, PolynomialValues};
use crate::fri::proof::{FriInitialTreeProof, FriProof, FriQueryRound, FriQueryStep}; use crate::fri::proof::{FriInitialTreeProof, FriProof, FriQueryRound, FriQueryStep};
use crate::fri::{FriConfig, FriParams}; use crate::fri::{FriConfig, FriParams};
use crate::hash::hash_types::RichField; use crate::hash::hash_types::{RichField, NUM_HASH_OUT_ELTS};
use crate::hash::hashing::PlonkyPermutation; use crate::hash::hashing::PlonkyPermutation;
use crate::hash::merkle_tree::MerkleTree; use crate::hash::merkle_tree::MerkleTree;
use crate::iop::challenger::Challenger; use crate::iop::challenger::Challenger;
@ -26,6 +29,8 @@ pub fn fri_proof<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const
lde_polynomial_values: PolynomialValues<F::Extension>, lde_polynomial_values: PolynomialValues<F::Extension>,
challenger: &mut Challenger<F, C::Hasher>, challenger: &mut Challenger<F, C::Hasher>,
fri_params: &FriParams, fri_params: &FriParams,
final_poly_coeff_len: Option<usize>,
max_num_query_steps: Option<usize>,
timing: &mut TimingTree, timing: &mut TimingTree,
) -> FriProof<F, C::Hasher, D> { ) -> FriProof<F, C::Hasher, D> {
let n = lde_polynomial_values.len(); let n = lde_polynomial_values.len();
@ -40,6 +45,8 @@ pub fn fri_proof<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const
lde_polynomial_values, lde_polynomial_values,
challenger, challenger,
fri_params, fri_params,
final_poly_coeff_len,
max_num_query_steps,
) )
); );
@ -67,11 +74,20 @@ pub(crate) type FriCommitedTrees<F, C, const D: usize> = (
PolynomialCoeffs<<F as Extendable<D>>::Extension>, PolynomialCoeffs<<F as Extendable<D>>::Extension>,
); );
pub fn final_poly_coeff_len(mut degree_bits: usize, reduction_arity_bits: &Vec<usize>) -> usize {
for arity_bits in reduction_arity_bits {
degree_bits -= *arity_bits;
}
1 << degree_bits
}
fn fri_committed_trees<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>( fn fri_committed_trees<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>(
mut coeffs: PolynomialCoeffs<F::Extension>, mut coeffs: PolynomialCoeffs<F::Extension>,
mut values: PolynomialValues<F::Extension>, mut values: PolynomialValues<F::Extension>,
challenger: &mut Challenger<F, C::Hasher>, challenger: &mut Challenger<F, C::Hasher>,
fri_params: &FriParams, fri_params: &FriParams,
final_poly_coeff_len: Option<usize>,
max_num_query_steps: Option<usize>,
) -> FriCommitedTrees<F, C, D> { ) -> FriCommitedTrees<F, C, D> {
let mut trees = Vec::with_capacity(fri_params.reduction_arity_bits.len()); let mut trees = Vec::with_capacity(fri_params.reduction_arity_bits.len());
@ -103,12 +119,33 @@ fn fri_committed_trees<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>,
values = coeffs.coset_fft(shift.into()) values = coeffs.coset_fft(shift.into())
} }
// When verifying this proof in a circuit with a different number of query steps,
// we need the challenger to stay in sync with the verifier. Therefore, the challenger
// must observe the additional hash caps and generate dummy challenges.
if let Some(step_count) = max_num_query_steps {
let cap_len = (1 << fri_params.config.cap_height) * NUM_HASH_OUT_ELTS;
let zero_cap = vec![F::ZERO; cap_len];
for _ in fri_params.reduction_arity_bits.len()..step_count {
challenger.observe_elements(&zero_cap);
challenger.get_extension_challenge::<D>();
}
}
// The coefficients being removed here should always be zero. // The coefficients being removed here should always be zero.
coeffs coeffs
.coeffs .coeffs
.truncate(coeffs.len() >> fri_params.config.rate_bits); .truncate(coeffs.len() >> fri_params.config.rate_bits);
challenger.observe_extension_elements(&coeffs.coeffs); challenger.observe_extension_elements(&coeffs.coeffs);
// When verifying this proof in a circuit with a different final polynomial length,
// the challenger needs to observe the full length of the final polynomial.
if let Some(len) = final_poly_coeff_len {
let current_len = coeffs.coeffs.len();
for _ in current_len..len {
challenger.observe_extension_element(&F::Extension::ZERO);
}
}
(trees, coeffs) (trees, coeffs)
} }

View File

@ -1,5 +1,6 @@
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::{format, vec::Vec}; use alloc::{format, vec::Vec};
use core::ops::RangeInclusive;
use itertools::Itertools; use itertools::Itertools;
@ -179,6 +180,97 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
} }
} }
/// Verifies the current FRI proof with `current_degree_bits`, which may differ from the
/// circuit's `degree_bits` in `params`.
/// The circuit uses random access gates to select and connect the current hash/evaluation
/// values with those in the proof. It is designed with the maximum number of query/folding
/// steps and final polynomial length at `degree_bits`, "skipping" steps when the actual proof
/// has fewer.
pub fn verify_fri_proof_with_multiple_degree_bits<C: GenericConfig<D, F = F>>(
&mut self,
instance: &FriInstanceInfoTarget<D>,
openings: &FriOpeningsTarget<D>,
challenges: &FriChallengesTarget<D>,
initial_merkle_caps: &[MerkleCapTarget],
proof: &FriProofTarget<D>,
params: &FriParams,
current_degree_bits: Target,
degree_sub_one_bits_vec: &[BoolTarget],
min_degree_bits_to_support: usize,
) where
C::Hasher: AlgebraicHasher<F>,
{
if let Some(max_arity_bits) = params.max_arity_bits() {
self.check_recursion_config(max_arity_bits);
}
debug_assert_eq!(
params.final_poly_len(),
proof.final_poly.len(),
"Final polynomial has wrong degree."
);
// Size of the LDE domain.
let log_n = params.config.rate_bits + params.degree_bits;
let mut current_log_n = self.constant(F::from_canonical_usize(params.config.rate_bits));
current_log_n = self.add(current_log_n, current_degree_bits);
let min_log_n_to_support = params.config.rate_bits + min_degree_bits_to_support;
with_context!(
self,
"check PoW",
self.fri_verify_proof_of_work(challenges.fri_pow_response, &params.config)
);
// Check that parameters are coherent.
debug_assert_eq!(
params.config.num_query_rounds,
proof.query_round_proofs.len(),
"Number of query rounds does not match config."
);
let precomputed_reduced_evals = with_context!(
self,
"precompute reduced evaluations",
PrecomputedReducedOpeningsTarget::from_os_and_alpha(
openings,
challenges.fri_alpha,
self
)
);
for (i, round_proof) in proof.query_round_proofs.iter().enumerate() {
// To minimize noise in our logs, we will only record a context for a single FRI query.
// The very first query will have some extra gates due to constants being registered, so
// the second query is a better representative.
let level = if i == 1 {
log::Level::Debug
} else {
log::Level::Trace
};
let num_queries = proof.query_round_proofs.len();
with_context!(
self,
level,
&format!("verify one (of {num_queries}) query rounds"),
self.fri_verifier_query_round_with_multiple_degree_bits::<C>(
instance,
challenges,
&precomputed_reduced_evals,
initial_merkle_caps,
proof,
challenges.fri_query_indices[i],
min_log_n_to_support..=log_n,
current_log_n,
degree_sub_one_bits_vec,
round_proof,
params,
)
);
}
}
fn fri_verify_initial_proof<H: AlgebraicHasher<F>>( fn fri_verify_initial_proof<H: AlgebraicHasher<F>>(
&mut self, &mut self,
x_index_bits: &[BoolTarget], x_index_bits: &[BoolTarget],
@ -206,6 +298,39 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
} }
} }
fn fri_verify_initial_proof_with_multiple_degree_bits<H: AlgebraicHasher<F>>(
&mut self,
x_index_bits: &[BoolTarget],
log_n_range: RangeInclusive<usize>,
n_index: Target,
proof: &FriInitialTreeProofTarget,
initial_merkle_caps: &[MerkleCapTarget],
cap_index: Target,
) {
let one = self.one();
for (i, ((evals, merkle_proof), cap)) in proof
.evals_proofs
.iter()
.zip(initial_merkle_caps)
.enumerate()
{
with_context!(
self,
&format!("verify {i}'th initial Merkle proof"),
self.verify_merkle_proof_to_cap_with_cap_indices::<H>(
one,
evals.clone(),
x_index_bits,
log_n_range.clone(),
n_index,
cap_index,
cap,
merkle_proof
)
);
}
}
fn fri_combine_initial( fn fri_combine_initial(
&mut self, &mut self,
instance: &FriInstanceInfoTarget<D>, instance: &FriInstanceInfoTarget<D>,
@ -364,6 +489,155 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
self.connect_extension(eval, old_eval); self.connect_extension(eval, old_eval);
} }
fn fri_verifier_query_round_with_multiple_degree_bits<C: GenericConfig<D, F = F>>(
&mut self,
instance: &FriInstanceInfoTarget<D>,
challenges: &FriChallengesTarget<D>,
precomputed_reduced_evals: &PrecomputedReducedOpeningsTarget<D>,
initial_merkle_caps: &[MerkleCapTarget],
proof: &FriProofTarget<D>,
x_index: Target,
log_n_range: RangeInclusive<usize>,
log_n: Target,
degree_sub_one_bits_vec: &[BoolTarget],
round_proof: &FriQueryRoundTarget<D>,
params: &FriParams,
) where
C::Hasher: AlgebraicHasher<F>,
{
assert!(*log_n_range.start() > params.config.cap_height);
let n_index = {
let min_log_n = self.constant(F::from_canonical_usize(*log_n_range.start()));
self.sub(log_n, min_log_n)
};
// Note that this `low_bits` decomposition permits non-canonical binary encodings. Here we
// verify that this has a negligible impact on soundness error.
Self::assert_noncanonical_indices_ok(&params.config);
let mut x_index_bits = self.low_bits(x_index, *log_n_range.end(), F::BITS);
let cap_indices: Vec<_> = log_n_range
.clone()
.map(|n| {
let slice_start = n - params.config.cap_height;
self.le_sum(x_index_bits[slice_start..n].iter())
})
.collect();
let cap_index = self.random_access(n_index, cap_indices);
with_context!(
self,
"check FRI initial proof",
self.fri_verify_initial_proof_with_multiple_degree_bits::<C::Hasher>(
&x_index_bits,
log_n_range.clone(),
n_index,
&round_proof.initial_trees_proof,
initial_merkle_caps,
cap_index,
)
);
let g = self.constant(F::coset_shift());
// `subgroup_x` is `subgroup[x_index]`, i.e., the actual field element in the domain.
let subgroup_x_vec: Vec<_> = log_n_range
.clone()
.map(|n| {
with_context!(self, "compute x from its index", {
let phi = F::primitive_root_of_unity(n);
let phi = self.exp_from_bits_const_base(phi, x_index_bits[..n].iter().rev());
// subgroup_x = g * phi
self.mul(g, phi)
})
})
.collect();
let mut subgroup_x = self.random_access(n_index, subgroup_x_vec);
// old_eval is the last derived evaluation; it will be checked for consistency with its
// committed "parent" value in the next iteration.
let mut old_eval = with_context!(
self,
"combine initial oracles",
self.fri_combine_initial(
instance,
&round_proof.initial_trees_proof,
challenges.fri_alpha,
subgroup_x,
precomputed_reduced_evals,
params,
)
);
let mut index_in_degree_sub_one_bits_vec = {
let mut degree_bits_len = degree_sub_one_bits_vec.len();
for arity_bits in &params.reduction_arity_bits {
degree_bits_len -= arity_bits;
}
degree_bits_len
};
for (i, &arity_bits) in params.reduction_arity_bits.iter().enumerate() {
let evals = &round_proof.steps[i].evals;
// Split x_index into the index of the coset x is in, and the index of x within that coset.
let coset_index_bits = x_index_bits[arity_bits..].to_vec();
let x_index_within_coset_bits = &x_index_bits[..arity_bits];
let x_index_within_coset = self.le_sum(x_index_within_coset_bits.iter());
// Check consistency with our old evaluation from the previous round.
let new_eval = self.random_access_extension(x_index_within_coset, evals.clone());
let step_active = degree_sub_one_bits_vec[index_in_degree_sub_one_bits_vec];
self.conditional_assert_eq_ext(step_active.target, new_eval, old_eval);
// Infer P(y) from {P(x)}_{x^arity=y}.
let eval = with_context!(
self,
"infer evaluation using interpolation",
self.compute_evaluation(
subgroup_x,
x_index_within_coset_bits,
arity_bits,
evals,
challenges.fri_betas[i],
)
);
old_eval = self.select_ext(step_active, eval, old_eval);
with_context!(
self,
"verify FRI round Merkle proof.",
self.verify_merkle_proof_to_cap_with_cap_indices::<C::Hasher>(
step_active.target,
flatten_target(evals),
&coset_index_bits,
log_n_range.clone(),
n_index,
cap_index,
&proof.commit_phase_merkle_caps[i],
&round_proof.steps[i].merkle_proof,
)
);
// Update the point x to x^arity.
let subgroup_x_cur = self.exp_power_of_2(subgroup_x, arity_bits);
subgroup_x = self.select(step_active, subgroup_x_cur, subgroup_x);
x_index_bits = coset_index_bits;
index_in_degree_sub_one_bits_vec += arity_bits;
}
// Final check of FRI. After all the reductions, we check that the final polynomial is equal
// to the one sent by the prover.
let eval = with_context!(
self,
&format!(
"evaluate final polynomial of length {}",
proof.final_poly.len()
),
proof.final_poly.eval_scalar(self, subgroup_x)
);
self.connect_extension(eval, old_eval);
}
/// We decompose FRI query indices into bits without verifying that the decomposition given by /// We decompose FRI query indices into bits without verifying that the decomposition given by
/// the prover is the canonical one. In particular, if `x_index < 2^field_bits - p`, then the /// the prover is the canonical one. In particular, if `x_index < 2^field_bits - p`, then the
/// prover could supply the binary encoding of either `x_index` or `x_index + p`, since the are /// prover could supply the binary encoding of either `x_index` or `x_index + p`, since the are

View File

@ -1,9 +1,10 @@
use anyhow::Result; use anyhow::{anyhow, Result};
use itertools::Itertools; use itertools::Itertools;
use plonky2_field::types::Field;
use crate::field::extension::Extendable; use crate::field::extension::Extendable;
use crate::fri::proof::{FriProof, FriProofTarget}; use crate::fri::proof::{FriProof, FriProofTarget};
use crate::hash::hash_types::RichField; use crate::hash::hash_types::{HashOut, RichField};
use crate::iop::witness::WitnessWrite; use crate::iop::witness::WitnessWrite;
use crate::plonk::config::AlgebraicHasher; use crate::plonk::config::AlgebraicHasher;
@ -20,21 +21,47 @@ where
{ {
witness.set_target(fri_proof_target.pow_witness, fri_proof.pow_witness)?; witness.set_target(fri_proof_target.pow_witness, fri_proof.pow_witness)?;
for (&t, &x) in fri_proof_target let target_len = fri_proof_target.final_poly.0.len();
.final_poly let coeffs_len = fri_proof.final_poly.coeffs.len();
.0
.iter() if target_len < coeffs_len {
.zip_eq(&fri_proof.final_poly.coeffs) return Err(anyhow!(
{ "fri_proof->final_poly's target length is less than the proof length"
witness.set_extension_target(t, x)?; ));
} }
for (t, x) in fri_proof_target // Set overlapping elements
.commit_phase_merkle_caps for i in 0..coeffs_len {
.iter() witness.set_extension_target(
.zip_eq(&fri_proof.commit_phase_merkle_caps) fri_proof_target.final_poly.0[i],
{ fri_proof.final_poly.coeffs[i],
witness.set_cap_target(t, x)?; )?;
}
// Set remaining elements in target to ZERO if target is longer
for i in coeffs_len..target_len {
witness.set_extension_target(fri_proof_target.final_poly.0[i], F::Extension::ZERO)?;
}
let target_caps = &fri_proof_target.commit_phase_merkle_caps;
let proof_caps = &fri_proof.commit_phase_merkle_caps;
if target_caps.len() < proof_caps.len() {
return Err(anyhow!(
"fri_proof->commit_phase_merkle_caps's target length is less than the proof length"
));
}
// Set matching elements in both proof and target caps
for (target_cap, proof_cap) in target_caps.iter().zip(proof_caps) {
witness.set_cap_target(target_cap, proof_cap)?;
}
// Set remaining elements in target caps to ZERO if target is longer
for target_cap in target_caps.iter().skip(proof_caps.len()) {
for hash in target_cap.0.iter() {
witness.set_hash_target(*hash, HashOut::ZERO)?;
}
} }
for (qt, q) in fri_proof_target for (qt, q) in fri_proof_target
@ -51,22 +78,55 @@ where
for (&t, &x) in at.0.iter().zip_eq(&a.0) { for (&t, &x) in at.0.iter().zip_eq(&a.0) {
witness.set_target(t, x)?; witness.set_target(t, x)?;
} }
for (&t, &x) in at.1.siblings.iter().zip_eq(&a.1.siblings) { let target_len = at.1.siblings.len();
witness.set_hash_target(t, x)?; let siblings_len = a.1.siblings.len();
if target_len < siblings_len {
return Err(anyhow!("fri_proof->query_round_proofs->initial_trees_proof->evals_proofs->siblings' target length is less than the proof length"));
}
// Set overlapping elements
for i in 0..siblings_len {
witness.set_hash_target(at.1.siblings[i], a.1.siblings[i])?;
}
// Set remaining elements in target to ZERO if target is longer
for i in siblings_len..target_len {
witness.set_hash_target(at.1.siblings[i], HashOut::ZERO)?;
} }
} }
for (st, s) in qt.steps.iter().zip_eq(&q.steps) { for (st, s) in qt.steps.iter().zip(&q.steps) {
for (&t, &x) in st.evals.iter().zip_eq(&s.evals) { for (&t, &x) in st.evals.iter().zip_eq(&s.evals) {
witness.set_extension_target(t, x)?; witness.set_extension_target(t, x)?;
} }
for (&t, &x) in st
.merkle_proof let target_len = st.merkle_proof.siblings.len();
.siblings let siblings_len = s.merkle_proof.siblings.len();
.iter()
.zip_eq(&s.merkle_proof.siblings) if target_len < siblings_len {
{ return Err(anyhow!("fri_proof->query_round_proofs->steps->merkle_proof->siblings' target length is less than the proof length"));
witness.set_hash_target(t, x)?; }
// Set overlapping elements
for i in 0..siblings_len {
witness.set_hash_target(st.merkle_proof.siblings[i], s.merkle_proof.siblings[i])?;
}
// Set remaining elements in target to ZERO if target is longer
for i in siblings_len..target_len {
witness.set_hash_target(st.merkle_proof.siblings[i], HashOut::ZERO)?;
}
}
// Set remaining steps in qt to ZERO if qt.steps is longer
for st in qt.steps.iter().skip(q.steps.len()) {
for &eval in &st.evals {
witness.set_extension_target(eval, F::Extension::ZERO)?;
}
for &sibling in &st.merkle_proof.siblings {
witness.set_hash_target(sibling, HashOut::ZERO)?;
} }
} }
} }

View File

@ -15,7 +15,7 @@ use crate::gates::multiplication_extension::MulExtensionGate;
use crate::hash::hash_types::RichField; use crate::hash::hash_types::RichField;
use crate::iop::ext_target::{ExtensionAlgebraTarget, ExtensionTarget}; use crate::iop::ext_target::{ExtensionAlgebraTarget, ExtensionTarget};
use crate::iop::generator::{GeneratedValues, SimpleGenerator}; use crate::iop::generator::{GeneratedValues, SimpleGenerator};
use crate::iop::target::Target; use crate::iop::target::{BoolTarget, Target};
use crate::iop::witness::{PartitionWitness, Witness, WitnessWrite}; use crate::iop::witness::{PartitionWitness, Witness, WitnessWrite};
use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::circuit_builder::CircuitBuilder;
use crate::plonk::circuit_data::CommonCircuitData; use crate::plonk::circuit_data::CommonCircuitData;
@ -421,6 +421,21 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
self.scalar_mul_add_ext_algebra(a, b, zero) self.scalar_mul_add_ext_algebra(a, b, zero)
} }
/// Exponentiates `base` to the power of exponent expressed as `exponent_bits`.
pub fn exp_extension_from_bits(
&mut self,
mut base: ExtensionTarget<D>,
exponent_bits: &[BoolTarget],
) -> ExtensionTarget<D> {
let mut res = self.one_extension();
for i in 0..exponent_bits.len() {
let new_res = self.mul_extension(res, base);
res = self.select_ext(exponent_bits[i], new_res, res);
base = self.mul_extension(base, base);
}
res
}
/// Exponentiate `base` to the power of `2^power_log`. /// Exponentiate `base` to the power of `2^power_log`.
// TODO: Test // TODO: Test
pub fn exp_power_of_2_extension( pub fn exp_power_of_2_extension(

View File

@ -1,6 +1,8 @@
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::vec::Vec; use alloc::vec::Vec;
use itertools::repeat_n;
use crate::field::extension::Extendable; use crate::field::extension::Extendable;
use crate::gates::random_access::RandomAccessGate; use crate::gates::random_access::RandomAccessGate;
use crate::hash::hash_types::{HashOutTarget, MerkleCapTarget, RichField}; use crate::hash::hash_types::{HashOutTarget, MerkleCapTarget, RichField};
@ -13,6 +15,15 @@ use crate::util::log2_strict;
impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> { impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
/// Checks that a `Target` matches a vector at a particular index. /// Checks that a `Target` matches a vector at a particular index.
pub fn random_access(&mut self, access_index: Target, v: Vec<Target>) -> Target { pub fn random_access(&mut self, access_index: Target, v: Vec<Target>) -> Target {
let mut v = v;
let current_len = v.len();
let next_power_of_two = current_len.next_power_of_two();
if current_len < next_power_of_two {
// Get the last element (if there is one) and extend with it
if let Some(&last) = v.last() {
v.extend(repeat_n(last, next_power_of_two - current_len));
}
}
let vec_size = v.len(); let vec_size = v.len();
let bits = log2_strict(vec_size); let bits = log2_strict(vec_size);
debug_assert!(vec_size > 0); debug_assert!(vec_size > 0);
@ -45,6 +56,15 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
access_index: Target, access_index: Target,
v: Vec<ExtensionTarget<D>>, v: Vec<ExtensionTarget<D>>,
) -> ExtensionTarget<D> { ) -> ExtensionTarget<D> {
let mut v = v;
let current_len = v.len();
let next_power_of_two = current_len.next_power_of_two();
if current_len < next_power_of_two {
// Get the last element (if there is one) and extend with it
if let Some(&last) = v.last() {
v.extend(repeat_n(last, next_power_of_two - current_len));
}
}
let selected: Vec<_> = (0..D) let selected: Vec<_> = (0..D)
.map(|i| self.random_access(access_index, v.iter().map(|et| et.0[i]).collect())) .map(|i| self.random_access(access_index, v.iter().map(|et| et.0[i]).collect()))
.collect(); .collect();

View File

@ -1,5 +1,6 @@
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec}; use alloc::{vec, vec::Vec};
use core::ops::RangeInclusive;
use anyhow::{ensure, Result}; use anyhow::{ensure, Result};
use itertools::Itertools; use itertools::Itertools;
@ -180,6 +181,63 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
} }
} }
/// Same as `verify_merkle_proof_to_cap`, except with the final "cap index" as separate parameter,
/// rather than being contained in `leaf_index_bits`.
pub(crate) fn verify_merkle_proof_to_cap_with_cap_indices<H: AlgebraicHasher<F>>(
&mut self,
condition: Target,
leaf_data: Vec<Target>,
leaf_index_bits: &[BoolTarget],
log_n_range: RangeInclusive<usize>,
n_index: Target,
cap_index: Target,
merkle_cap: &MerkleCapTarget,
proof: &MerkleProofTarget,
) {
debug_assert!(H::AlgebraicPermutation::RATE >= NUM_HASH_OUT_ELTS);
let zero = self.zero();
let mut state: HashOutTarget = self.hash_or_noop::<H>(leaf_data);
debug_assert_eq!(state.elements.len(), NUM_HASH_OUT_ELTS);
let num_log_n = log_n_range.clone().count();
let mut final_states = vec![state; num_log_n];
for (&bit, &sibling) in leaf_index_bits.iter().zip(&proof.siblings) {
debug_assert_eq!(sibling.elements.len(), NUM_HASH_OUT_ELTS);
let mut perm_inputs = H::AlgebraicPermutation::default();
perm_inputs.set_from_slice(&state.elements, 0);
perm_inputs.set_from_slice(&sibling.elements, NUM_HASH_OUT_ELTS);
// Ensure the rest of the state, if any, is zero:
perm_inputs.set_from_iter(core::iter::repeat(zero), 2 * NUM_HASH_OUT_ELTS);
let perm_outs = self.permute_swapped::<H>(perm_inputs, bit);
let hash_outs = perm_outs.squeeze()[0..NUM_HASH_OUT_ELTS]
.try_into()
.unwrap();
state = HashOutTarget {
elements: hash_outs,
};
// Store state at specific indices
for n in 0..num_log_n - 1 {
final_states[n] = final_states[n + 1];
}
final_states[num_log_n - 1] = state;
}
for i in 0..NUM_HASH_OUT_ELTS {
let result = self.random_access(
cap_index,
merkle_cap.0.iter().map(|h| h.elements[i]).collect(),
);
let state = self.random_access(
n_index,
final_states.iter().map(|s| s.elements[i]).collect(),
);
self.conditional_assert_eq(condition, result, state);
}
}
/// Same as `verify_batch_merkle_proof_to_cap`, except with the final "cap index" as separate parameter, /// Same as `verify_batch_merkle_proof_to_cap`, except with the final "cap index" as separate parameter,
/// rather than being contained in `leaf_index_bits`. /// rather than being contained in `leaf_index_bits`.
pub(crate) fn verify_batch_merkle_proof_to_cap_with_cap_index<H: AlgebraicHasher<F>>( pub(crate) fn verify_batch_merkle_proof_to_cap_with_cap_index<H: AlgebraicHasher<F>>(

View File

@ -548,6 +548,18 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
self.connect(constr, zero); self.connect(constr, zero);
} }
/// If `condition`, enforces that two `ExtensionTarget<D>` values are equal.
pub fn conditional_assert_eq_ext(
&mut self,
condition: Target,
x: ExtensionTarget<D>,
y: ExtensionTarget<D>,
) {
for i in 0..D {
self.conditional_assert_eq(condition, x.0[i], y.0[i]);
}
}
/// Enforces that a routable `Target` value is 0, using Plonk's permutation argument. /// Enforces that a routable `Target` value is 0, using Plonk's permutation argument.
pub fn assert_zero(&mut self, x: Target) { pub fn assert_zero(&mut self, x: Target) {
let zero = self.zero(); let zero = self.zero();

View File

@ -85,6 +85,8 @@ fn get_challenges<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, cons
pow_witness, pow_witness,
common_data.degree_bits(), common_data.degree_bits(),
&config.fri_config, &config.fri_config,
None,
None,
), ),
}) })
} }

View File

@ -340,6 +340,8 @@ where
], ],
&mut challenger, &mut challenger,
&common_data.fri_params, &common_data.fri_params,
None,
None,
timing, timing,
) )
); );

View File

@ -134,7 +134,11 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for FibonacciStar
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use anyhow::Result; use anyhow::Result;
use itertools::Itertools;
use plonky2::field::extension::Extendable; use plonky2::field::extension::Extendable;
use plonky2::field::types::Field; use plonky2::field::types::Field;
use plonky2::hash::hash_types::RichField; use plonky2::hash::hash_types::RichField;
@ -156,17 +160,17 @@ mod tests {
use crate::stark_testing::{test_stark_circuit_constraints, test_stark_low_degree}; use crate::stark_testing::{test_stark_circuit_constraints, test_stark_low_degree};
use crate::verifier::verify_stark_proof; use crate::verifier::verify_stark_proof;
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
type S = FibonacciStark<F, D>;
fn fibonacci<F: Field>(n: usize, x0: F, x1: F) -> F { fn fibonacci<F: Field>(n: usize, x0: F, x1: F) -> F {
(0..n).fold((x0, x1), |x, _| (x.1, x.0 + x.1)).1 (0..n).fold((x0, x1), |x, _| (x.1, x.0 + x.1)).1
} }
#[test] #[test]
fn test_fibonacci_stark() -> Result<()> { fn test_fibonacci_stark() -> Result<()> {
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
type S = FibonacciStark<F, D>;
let config = StarkConfig::standard_fast_config(); let config = StarkConfig::standard_fast_config();
let num_rows = 1 << 5; let num_rows = 1 << 5;
let public_inputs = [F::ZERO, F::ONE, fibonacci(num_rows - 1, F::ZERO, F::ONE)]; let public_inputs = [F::ZERO, F::ONE, fibonacci(num_rows - 1, F::ZERO, F::ONE)];
@ -178,19 +182,15 @@ mod tests {
&config, &config,
trace, trace,
&public_inputs, &public_inputs,
None,
&mut TimingTree::default(), &mut TimingTree::default(),
)?; )?;
verify_stark_proof(stark, proof, &config) verify_stark_proof(stark, proof, &config, None)
} }
#[test] #[test]
fn test_fibonacci_stark_degree() -> Result<()> { fn test_fibonacci_stark_degree() -> Result<()> {
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
type S = FibonacciStark<F, D>;
let num_rows = 1 << 5; let num_rows = 1 << 5;
let stark = S::new(num_rows); let stark = S::new(num_rows);
test_stark_low_degree(stark) test_stark_low_degree(stark)
@ -198,11 +198,6 @@ mod tests {
#[test] #[test]
fn test_fibonacci_stark_circuit() -> Result<()> { fn test_fibonacci_stark_circuit() -> Result<()> {
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
type S = FibonacciStark<F, D>;
let num_rows = 1 << 5; let num_rows = 1 << 5;
let stark = S::new(num_rows); let stark = S::new(num_rows);
test_stark_circuit_constraints::<F, C, S, D>(stark) test_stark_circuit_constraints::<F, C, S, D>(stark)
@ -211,13 +206,10 @@ mod tests {
#[test] #[test]
fn test_recursive_stark_verifier() -> Result<()> { fn test_recursive_stark_verifier() -> Result<()> {
init_logger(); init_logger();
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
type S = FibonacciStark<F, D>;
let config = StarkConfig::standard_fast_config(); let config = StarkConfig::standard_fast_config();
let num_rows = 1 << 5; let degree_bits = 5;
let num_rows = 1 << degree_bits;
let public_inputs = [F::ZERO, F::ONE, fibonacci(num_rows - 1, F::ZERO, F::ONE)]; let public_inputs = [F::ZERO, F::ONE, fibonacci(num_rows - 1, F::ZERO, F::ONE)];
// Test first STARK // Test first STARK
@ -228,9 +220,11 @@ mod tests {
&config, &config,
trace, trace,
&public_inputs, &public_inputs,
None,
&mut TimingTree::default(), &mut TimingTree::default(),
)?; )?;
verify_stark_proof(stark, proof.clone(), &config)?; verify_stark_proof(stark, proof.clone(), &config, None)?;
assert_eq!(degree_bits, proof.proof.recover_degree_bits(&config));
recursive_proof::<F, C, S, C, D>(stark, proof, &config, true) recursive_proof::<F, C, S, C, D>(stark, proof, &config, true)
} }
@ -256,9 +250,9 @@ mod tests {
let degree_bits = inner_proof.proof.recover_degree_bits(inner_config); let degree_bits = inner_proof.proof.recover_degree_bits(inner_config);
let pt = let pt =
add_virtual_stark_proof_with_pis(&mut builder, &stark, inner_config, degree_bits, 0, 0); add_virtual_stark_proof_with_pis(&mut builder, &stark, inner_config, degree_bits, 0, 0);
set_stark_proof_with_pis_target(&mut pw, &pt, &inner_proof, builder.zero())?; set_stark_proof_with_pis_target(&mut pw, &pt, &inner_proof, degree_bits, builder.zero())?;
verify_stark_proof_circuit::<F, InnerC, S, D>(&mut builder, stark, pt, inner_config); verify_stark_proof_circuit::<F, InnerC, S, D>(&mut builder, stark, pt, inner_config, None);
if print_gate_counts { if print_gate_counts {
builder.print_gate_counts(0); builder.print_gate_counts(0);
@ -272,4 +266,87 @@ mod tests {
fn init_logger() { fn init_logger() {
let _ = env_logger::builder().format_timestamp(None).try_init(); let _ = env_logger::builder().format_timestamp(None).try_init();
} }
#[test]
fn test_recursive_verifier_with_multiple_degree_bits() -> Result<()> {
init_logger();
let mut stark_config = StarkConfig::standard_fast_config();
stark_config.fri_config.num_query_rounds = 1;
let min_degree_bits_to_support = 4;
// Currently, we only support verifier_degree_bits to be {30, 26, 22, 18, …}, as they
// generate the max final polynomial length when using the default configuration
// ConstantArityBits(4, 5). This ensures that for other degrees, the final proof polynomial
// will not be longer than the circuits final polynomial length.
let verifier_degree_bits = 30;
let degree_bits = 4..=15;
let verifier_fri_params = stark_config.fri_params(verifier_degree_bits);
// Generate STARK proofs for each degree in `degree_bits`
let proofs: Vec<_> = degree_bits
.clone()
.map(|degree_bits| {
let num_rows = 1 << degree_bits;
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]);
// Generate proof with the specified verifier degree
prove::<F, C, S, D>(
stark,
&stark_config,
trace,
&public_inputs,
Some(verifier_fri_params.clone()),
&mut TimingTree::default(),
)
.unwrap()
})
.collect();
// Configure the circuit for recursive verification
let num_rows = 1 << verifier_degree_bits;
let stark = S::new(num_rows);
for p in proofs.clone() {
verify_stark_proof(stark, p, &stark_config, Some(verifier_fri_params.clone()))?;
}
let recursive_verification_circuit_config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(recursive_verification_circuit_config);
let zero = builder.zero();
// Set up proof verification within the circuit
let pt = add_virtual_stark_proof_with_pis(
&mut builder,
&stark,
&stark_config,
verifier_degree_bits,
0,
0,
);
verify_stark_proof_circuit::<F, C, S, D>(
&mut builder,
stark,
pt.clone(),
&stark_config,
Some(min_degree_bits_to_support),
);
builder.print_gate_counts(0);
// Build the recursive circuit
let data = builder.build::<C>();
// Verify each proof using partial witnesses
degree_bits
.zip_eq(proofs)
.try_for_each(|(degree_bits, proof)| {
let mut pw = PartialWitness::new();
set_stark_proof_with_pis_target(&mut pw, &pt, &proof, degree_bits, zero)?;
let proof = data.prove(pw)?;
data.verify(proof)
})?;
Ok(())
}
} }

View File

@ -1,6 +1,8 @@
use plonky2::field::extension::Extendable; use plonky2::field::extension::Extendable;
use plonky2::field::polynomial::PolynomialCoeffs; use plonky2::field::polynomial::PolynomialCoeffs;
use plonky2::fri::proof::{FriProof, FriProofTarget}; use plonky2::fri::proof::{FriProof, FriProofTarget};
use plonky2::fri::prover::final_poly_coeff_len;
use plonky2::fri::FriParams;
use plonky2::gadgets::polynomial::PolynomialCoeffsExtTarget; use plonky2::gadgets::polynomial::PolynomialCoeffsExtTarget;
use plonky2::hash::hash_types::{MerkleCapTarget, RichField}; use plonky2::hash::hash_types::{MerkleCapTarget, RichField};
use plonky2::hash::merkle_tree::MerkleCap; use plonky2::hash::merkle_tree::MerkleCap;
@ -35,6 +37,7 @@ fn get_challenges<F, C, const D: usize>(
pow_witness: F, pow_witness: F,
config: &StarkConfig, config: &StarkConfig,
degree_bits: usize, degree_bits: usize,
verifier_circuit_fri_params: Option<FriParams>,
) -> StarkProofChallenges<F, D> ) -> StarkProofChallenges<F, D>
where where
F: RichField + Extendable<D>, F: RichField + Extendable<D>,
@ -67,6 +70,19 @@ where
challenger.observe_openings(&openings.to_fri_openings()); challenger.observe_openings(&openings.to_fri_openings());
let (final_poly_coeff_len, max_num_query_steps) =
if let Some(verifier_circuit_fri_params) = verifier_circuit_fri_params {
(
Some(final_poly_coeff_len(
verifier_circuit_fri_params.degree_bits,
&verifier_circuit_fri_params.reduction_arity_bits,
)),
Some(verifier_circuit_fri_params.reduction_arity_bits.len()),
)
} else {
(None, None)
};
StarkProofChallenges { StarkProofChallenges {
lookup_challenge_set, lookup_challenge_set,
stark_alphas, stark_alphas,
@ -77,6 +93,8 @@ where
pow_witness, pow_witness,
degree_bits, degree_bits,
&config.fri_config, &config.fri_config,
final_poly_coeff_len,
max_num_query_steps,
), ),
} }
} }
@ -99,6 +117,7 @@ where
challenges: Option<&GrandProductChallengeSet<F>>, challenges: Option<&GrandProductChallengeSet<F>>,
ignore_trace_cap: bool, ignore_trace_cap: bool,
config: &StarkConfig, config: &StarkConfig,
verifier_circuit_fri_params: Option<FriParams>,
) -> StarkProofChallenges<F, D> { ) -> StarkProofChallenges<F, D> {
let degree_bits = self.recover_degree_bits(config); let degree_bits = self.recover_degree_bits(config);
@ -134,6 +153,7 @@ where
*pow_witness, *pow_witness,
config, config,
degree_bits, degree_bits,
verifier_circuit_fri_params,
) )
} }
} }
@ -156,10 +176,16 @@ where
challenges: Option<&GrandProductChallengeSet<F>>, challenges: Option<&GrandProductChallengeSet<F>>,
ignore_trace_cap: bool, ignore_trace_cap: bool,
config: &StarkConfig, config: &StarkConfig,
verifier_circuit_fri_params: Option<FriParams>,
) -> StarkProofChallenges<F, D> { ) -> StarkProofChallenges<F, D> {
challenger.observe_elements(&self.public_inputs); challenger.observe_elements(&self.public_inputs);
self.proof self.proof.get_challenges(
.get_challenges(challenger, challenges, ignore_trace_cap, config) challenger,
challenges,
ignore_trace_cap,
config,
verifier_circuit_fri_params,
)
} }
} }
@ -258,6 +284,7 @@ impl<const D: usize> StarkProofTarget<D> {
pow_witness, pow_witness,
.. ..
}, },
..
} = self; } = self;
let trace_cap = if ignore_trace_cap { let trace_cap = if ignore_trace_cap {

View File

@ -192,10 +192,11 @@
//! &CONFIG, //! &CONFIG,
//! trace, //! trace,
//! &public_inputs, //! &public_inputs,
//! None,
//! &mut TimingTree::default(), //! &mut TimingTree::default(),
//! ).expect("We should have a valid proof!"); //! ).expect("We should have a valid proof!");
//! //!
//! verify_stark_proof(stark, proof, &CONFIG) //! verify_stark_proof(stark, proof, &CONFIG, None)
//! .expect("We should be able to verify this proof!") //! .expect("We should be able to verify this proof!")
//! } //! }
//! ``` //! ```

View File

@ -141,10 +141,11 @@ mod tests {
&config, &config,
trace, trace,
&[public_input], &[public_input],
None,
&mut TimingTree::default(), &mut TimingTree::default(),
)?; )?;
verify_stark_proof(stark, proof, &config) verify_stark_proof(stark, proof, &config, None)
} }
#[test] #[test]
@ -190,9 +191,10 @@ mod tests {
&config, &config,
trace, trace,
&[public_input], &[public_input],
None,
&mut TimingTree::default(), &mut TimingTree::default(),
)?; )?;
verify_stark_proof(stark, proof.clone(), &config)?; verify_stark_proof(stark, proof.clone(), &config, None)?;
recursive_proof::<F, C, S, C, D>(stark, proof, &config, true) recursive_proof::<F, C, S, C, D>(stark, proof, &config, true)
} }
@ -218,9 +220,9 @@ mod tests {
let degree_bits = inner_proof.proof.recover_degree_bits(inner_config); let degree_bits = inner_proof.proof.recover_degree_bits(inner_config);
let pt = let pt =
add_virtual_stark_proof_with_pis(&mut builder, &stark, inner_config, degree_bits, 0, 0); add_virtual_stark_proof_with_pis(&mut builder, &stark, inner_config, degree_bits, 0, 0);
set_stark_proof_with_pis_target(&mut pw, &pt, &inner_proof, builder.zero())?; set_stark_proof_with_pis_target(&mut pw, &pt, &inner_proof, degree_bits, builder.zero())?;
verify_stark_proof_circuit::<F, InnerC, S, D>(&mut builder, stark, pt, inner_config); verify_stark_proof_circuit::<F, InnerC, S, D>(&mut builder, stark, pt, inner_config, None);
if print_gate_counts { if print_gate_counts {
builder.print_gate_counts(0); builder.print_gate_counts(0);

View File

@ -66,11 +66,14 @@ pub struct StarkProofTarget<const D: usize> {
pub openings: StarkOpeningSetTarget<D>, pub openings: StarkOpeningSetTarget<D>,
/// `Target`s for the batch FRI argument for all openings. /// `Target`s for the batch FRI argument for all openings.
pub opening_proof: FriProofTarget<D>, pub opening_proof: FriProofTarget<D>,
/// `Target`s for the proof's degree bits.
pub degree_bits: Target,
} }
impl<const D: usize> StarkProofTarget<D> { impl<const D: usize> StarkProofTarget<D> {
/// Serializes a STARK proof. /// Serializes a STARK proof.
pub fn to_buffer(&self, buffer: &mut Vec<u8>) -> IoResult<()> { pub fn to_buffer(&self, buffer: &mut Vec<u8>) -> IoResult<()> {
buffer.write_target(self.degree_bits)?;
buffer.write_target_merkle_cap(&self.trace_cap)?; buffer.write_target_merkle_cap(&self.trace_cap)?;
buffer.write_bool(self.auxiliary_polys_cap.is_some())?; buffer.write_bool(self.auxiliary_polys_cap.is_some())?;
if let Some(poly) = &self.auxiliary_polys_cap { if let Some(poly) = &self.auxiliary_polys_cap {
@ -87,6 +90,7 @@ impl<const D: usize> StarkProofTarget<D> {
/// Deserializes a STARK proof. /// Deserializes a STARK proof.
pub fn from_buffer(buffer: &mut Buffer) -> IoResult<Self> { pub fn from_buffer(buffer: &mut Buffer) -> IoResult<Self> {
let degree_bits = buffer.read_target()?;
let trace_cap = buffer.read_target_merkle_cap()?; let trace_cap = buffer.read_target_merkle_cap()?;
let auxiliary_polys_cap = if buffer.read_bool()? { let auxiliary_polys_cap = if buffer.read_bool()? {
Some(buffer.read_target_merkle_cap()?) Some(buffer.read_target_merkle_cap()?)
@ -107,6 +111,7 @@ impl<const D: usize> StarkProofTarget<D> {
quotient_polys_cap, quotient_polys_cap,
openings, openings,
opening_proof, opening_proof,
degree_bits,
}) })
} }

View File

@ -13,6 +13,9 @@ use plonky2::field::polynomial::{PolynomialCoeffs, PolynomialValues};
use plonky2::field::types::Field; use plonky2::field::types::Field;
use plonky2::field::zero_poly_coset::ZeroPolyOnCoset; use plonky2::field::zero_poly_coset::ZeroPolyOnCoset;
use plonky2::fri::oracle::PolynomialBatch; use plonky2::fri::oracle::PolynomialBatch;
use plonky2::fri::prover::final_poly_coeff_len;
use plonky2::fri::reduction_strategies::FriReductionStrategy;
use plonky2::fri::FriParams;
use plonky2::hash::hash_types::RichField; use plonky2::hash::hash_types::RichField;
use plonky2::iop::challenger::Challenger; use plonky2::iop::challenger::Challenger;
use plonky2::plonk::config::GenericConfig; use plonky2::plonk::config::GenericConfig;
@ -39,6 +42,7 @@ pub fn prove<F, C, S, const D: usize>(
config: &StarkConfig, config: &StarkConfig,
trace_poly_values: Vec<PolynomialValues<F>>, trace_poly_values: Vec<PolynomialValues<F>>,
public_inputs: &[F], public_inputs: &[F],
verifier_circuit_fri_params: Option<FriParams>,
timing: &mut TimingTree, timing: &mut TimingTree,
) -> Result<StarkProofWithPublicInputs<F, C, D>> ) -> Result<StarkProofWithPublicInputs<F, C, D>>
where where
@ -55,6 +59,26 @@ where
fri_params.total_arities() <= degree_bits + rate_bits - cap_height, fri_params.total_arities() <= degree_bits + rate_bits - cap_height,
"FRI total reduction arity is too large.", "FRI total reduction arity is too large.",
); );
let (final_poly_coeff_len, max_num_query_steps) =
if let Some(verifier_circuit_fri_params) = verifier_circuit_fri_params {
assert_eq!(verifier_circuit_fri_params.config, fri_params.config);
match &config.fri_config.reduction_strategy {
FriReductionStrategy::ConstantArityBits(_, final_poly_bits) => {
let len = final_poly_coeff_len(
verifier_circuit_fri_params.degree_bits,
&verifier_circuit_fri_params.reduction_arity_bits,
);
assert_eq!(len, 1 << (1 + *final_poly_bits));
(
Some(len),
Some(verifier_circuit_fri_params.reduction_arity_bits.len()),
)
}
_ => panic!("Fri Reduction Strategy is not ConstantArityBits"),
}
} else {
(None, None)
};
let trace_commitment = timed!( let trace_commitment = timed!(
timing, timing,
@ -73,7 +97,6 @@ where
let mut challenger = Challenger::new(); let mut challenger = Challenger::new();
challenger.observe_elements(public_inputs); challenger.observe_elements(public_inputs);
challenger.observe_cap(&trace_cap); challenger.observe_cap(&trace_cap);
prove_with_commitment( prove_with_commitment(
&stark, &stark,
config, config,
@ -83,6 +106,8 @@ where
None, None,
&mut challenger, &mut challenger,
public_inputs, public_inputs,
final_poly_coeff_len,
max_num_query_steps,
timing, timing,
) )
} }
@ -103,6 +128,8 @@ pub fn prove_with_commitment<F, C, S, const D: usize>(
ctl_challenges: Option<&GrandProductChallengeSet<F>>, ctl_challenges: Option<&GrandProductChallengeSet<F>>,
challenger: &mut Challenger<F, C::Hasher>, challenger: &mut Challenger<F, C::Hasher>,
public_inputs: &[F], public_inputs: &[F],
final_poly_coeff_len: Option<usize>,
max_num_query_steps: Option<usize>,
timing: &mut TimingTree, timing: &mut TimingTree,
) -> Result<StarkProofWithPublicInputs<F, C, D>> ) -> Result<StarkProofWithPublicInputs<F, C, D>>
where where
@ -319,6 +346,8 @@ where
&initial_merkle_trees, &initial_merkle_trees,
challenger, challenger,
&fri_params, &fri_params,
final_poly_coeff_len,
max_num_query_steps,
timing, timing,
) )
); );

View File

@ -8,7 +8,6 @@ use core::iter::once;
use anyhow::{ensure, Result}; use anyhow::{ensure, Result};
use itertools::Itertools; use itertools::Itertools;
use plonky2::field::extension::Extendable; use plonky2::field::extension::Extendable;
use plonky2::field::types::Field;
use plonky2::fri::witness_util::set_fri_proof_target; use plonky2::fri::witness_util::set_fri_proof_target;
use plonky2::hash::hash_types::RichField; use plonky2::hash::hash_types::RichField;
use plonky2::iop::challenger::RecursiveChallenger; use plonky2::iop::challenger::RecursiveChallenger;
@ -44,10 +43,12 @@ pub fn verify_stark_proof_circuit<
stark: S, stark: S,
proof_with_pis: StarkProofWithPublicInputsTarget<D>, proof_with_pis: StarkProofWithPublicInputsTarget<D>,
inner_config: &StarkConfig, inner_config: &StarkConfig,
min_degree_bits_to_support: Option<usize>,
) where ) where
C::Hasher: AlgebraicHasher<F>, C::Hasher: AlgebraicHasher<F>,
{ {
assert_eq!(proof_with_pis.public_inputs.len(), S::PUBLIC_INPUTS); assert_eq!(proof_with_pis.public_inputs.len(), S::PUBLIC_INPUTS);
let max_degree_bits_to_support = proof_with_pis.proof.recover_degree_bits(inner_config);
let mut challenger = RecursiveChallenger::<F, C::Hasher, D>::new(builder); let mut challenger = RecursiveChallenger::<F, C::Hasher, D>::new(builder);
let challenges = with_context!( let challenges = with_context!(
@ -64,6 +65,8 @@ pub fn verify_stark_proof_circuit<
challenges, challenges,
None, None,
inner_config, inner_config,
max_degree_bits_to_support,
min_degree_bits_to_support,
); );
} }
@ -81,6 +84,8 @@ pub fn verify_stark_proof_with_challenges_circuit<
challenges: StarkProofChallengesTarget<D>, challenges: StarkProofChallengesTarget<D>,
ctl_vars: Option<&[CtlCheckVarsTarget<F, D>]>, ctl_vars: Option<&[CtlCheckVarsTarget<F, D>]>,
inner_config: &StarkConfig, inner_config: &StarkConfig,
degree_bits: usize,
min_degree_bits_to_support: Option<usize>,
) where ) where
C::Hasher: AlgebraicHasher<F>, C::Hasher: AlgebraicHasher<F>,
{ {
@ -88,6 +93,7 @@ pub fn verify_stark_proof_with_challenges_circuit<
let zero = builder.zero(); let zero = builder.zero();
let one = builder.one_extension(); let one = builder.one_extension();
let two = builder.two();
let num_ctl_polys = ctl_vars let num_ctl_polys = ctl_vars
.map(|v| v.iter().map(|ctl| ctl.helper_columns.len()).sum::<usize>()) .map(|v| v.iter().map(|ctl| ctl.helper_columns.len()).sum::<usize>())
@ -111,13 +117,29 @@ pub fn verify_stark_proof_with_challenges_circuit<
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
); );
let degree_bits = proof.recover_degree_bits(inner_config); // degree_bits should be nonzero.
let zeta_pow_deg = builder.exp_power_of_2_extension(challenges.stark_zeta, degree_bits); let _ = builder.inverse(proof.degree_bits);
let max_num_of_bits_in_degree = degree_bits + 1;
let degree = builder.exp(two, proof.degree_bits, max_num_of_bits_in_degree);
let degree_bits_vec = builder.split_le(degree, max_num_of_bits_in_degree);
let zeta_pow_deg = builder.exp_extension_from_bits(challenges.stark_zeta, &degree_bits_vec);
let z_h_zeta = builder.sub_extension(zeta_pow_deg, one); let z_h_zeta = builder.sub_extension(zeta_pow_deg, one);
let degree_ext = builder.convert_to_ext(degree);
// Calculate primitive_root_of_unity(degree_bits)
let two_adicity = builder.constant(F::from_canonical_usize(F::TWO_ADICITY));
let two_adicity_sub_degree_bits = builder.sub(two_adicity, proof.degree_bits);
let two_exp_two_adicity_sub_degree_bits =
builder.exp(two, two_adicity_sub_degree_bits, F::TWO_ADICITY);
let base = builder.constant(F::POWER_OF_TWO_GENERATOR);
let g = builder.exp(base, two_exp_two_adicity_sub_degree_bits, F::TWO_ADICITY);
let g_ext = builder.convert_to_ext(g);
let (l_0, l_last) = let (l_0, l_last) =
eval_l_0_and_l_last_circuit(builder, degree_bits, challenges.stark_zeta, z_h_zeta); eval_l_0_and_l_last_circuit(builder, degree_ext, g_ext, challenges.stark_zeta, z_h_zeta);
let last = let last = builder.inverse_extension(g_ext);
builder.constant_extension(F::Extension::primitive_root_of_unity(degree_bits).inverse());
let z_last = builder.sub_extension(challenges.stark_zeta, last); let z_last = builder.sub_extension(challenges.stark_zeta, last);
let mut consumer = RecursiveConstraintConsumer::<F, D>::new( let mut consumer = RecursiveConstraintConsumer::<F, D>::new(
@ -181,29 +203,48 @@ pub fn verify_stark_proof_with_challenges_circuit<
let fri_instance = stark.fri_instance_target( let fri_instance = stark.fri_instance_target(
builder, builder,
challenges.stark_zeta, challenges.stark_zeta,
F::primitive_root_of_unity(degree_bits), g,
num_ctl_polys, num_ctl_polys,
ctl_zs_first.as_ref().map_or(0, |c| c.len()), ctl_zs_first.as_ref().map_or(0, |c| c.len()),
inner_config, inner_config,
); );
builder.verify_fri_proof::<C>(
&fri_instance, let one = builder.one();
&proof.openings.to_fri_openings(zero), let degree_sub_one = builder.sub(degree, one);
&challenges.fri_challenges, // Used to check if we want to skip a Fri query step.
&merkle_caps, let degree_sub_one_bits_vec = builder.split_le(degree_sub_one, degree_bits);
&proof.opening_proof,
&inner_config.fri_params(degree_bits), if let Some(min_degree_bits_to_support) = min_degree_bits_to_support {
); builder.verify_fri_proof_with_multiple_degree_bits::<C>(
&fri_instance,
&proof.openings.to_fri_openings(zero),
&challenges.fri_challenges,
&merkle_caps,
&proof.opening_proof,
&inner_config.fri_params(degree_bits),
proof.degree_bits,
&degree_sub_one_bits_vec,
min_degree_bits_to_support,
);
} else {
builder.verify_fri_proof::<C>(
&fri_instance,
&proof.openings.to_fri_openings(zero),
&challenges.fri_challenges,
&merkle_caps,
&proof.opening_proof,
&inner_config.fri_params(degree_bits),
);
}
} }
fn eval_l_0_and_l_last_circuit<F: RichField + Extendable<D>, const D: usize>( fn eval_l_0_and_l_last_circuit<F: RichField + Extendable<D>, const D: usize>(
builder: &mut CircuitBuilder<F, D>, builder: &mut CircuitBuilder<F, D>,
log_n: usize, n: ExtensionTarget<D>,
g: ExtensionTarget<D>,
x: ExtensionTarget<D>, x: ExtensionTarget<D>,
z_x: ExtensionTarget<D>, z_x: ExtensionTarget<D>,
) -> (ExtensionTarget<D>, ExtensionTarget<D>) { ) -> (ExtensionTarget<D>, ExtensionTarget<D>) {
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 one = builder.one_extension();
let l_0_deno = builder.mul_sub_extension(n, x, n); let l_0_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_sub_extension(g, x, one);
@ -284,6 +325,7 @@ pub fn add_virtual_stark_proof<F: RichField + Extendable<D>, S: Stark<F, D>, con
config, config,
), ),
opening_proof: builder.add_virtual_fri_proof(&num_leaves_per_oracle, &fri_params), opening_proof: builder.add_virtual_fri_proof(&num_leaves_per_oracle, &fri_params),
degree_bits: builder.add_virtual_target(),
} }
} }
@ -324,6 +366,7 @@ pub fn set_stark_proof_with_pis_target<F, C: GenericConfig<D, F = F>, W, const D
witness: &mut W, witness: &mut W,
stark_proof_with_pis_target: &StarkProofWithPublicInputsTarget<D>, stark_proof_with_pis_target: &StarkProofWithPublicInputsTarget<D>,
stark_proof_with_pis: &StarkProofWithPublicInputs<F, C, D>, stark_proof_with_pis: &StarkProofWithPublicInputs<F, C, D>,
pis_degree_bits: usize,
zero: Target, zero: Target,
) -> Result<()> ) -> Result<()>
where where
@ -345,7 +388,7 @@ where
witness.set_target(pi_t, pi)?; witness.set_target(pi_t, pi)?;
} }
set_stark_proof_target(witness, pt, proof, zero) set_stark_proof_target(witness, pt, proof, pis_degree_bits, zero)
} }
/// Set the targets in a [`StarkProofTarget`] to their corresponding values in a /// Set the targets in a [`StarkProofTarget`] to their corresponding values in a
@ -354,6 +397,7 @@ pub fn set_stark_proof_target<F, C: GenericConfig<D, F = F>, W, const D: usize>(
witness: &mut W, witness: &mut W,
proof_target: &StarkProofTarget<D>, proof_target: &StarkProofTarget<D>,
proof: &StarkProof<F, C, D>, proof: &StarkProof<F, C, D>,
pis_degree_bits: usize,
zero: Target, zero: Target,
) -> Result<()> ) -> Result<()>
where where
@ -361,6 +405,10 @@ where
C::Hasher: AlgebraicHasher<F>, C::Hasher: AlgebraicHasher<F>,
W: WitnessWrite<F>, W: WitnessWrite<F>,
{ {
witness.set_target(
proof_target.degree_bits,
F::from_canonical_usize(pis_degree_bits),
)?;
witness.set_cap_target(&proof_target.trace_cap, &proof.trace_cap)?; witness.set_cap_target(&proof_target.trace_cap, &proof.trace_cap)?;
if let (Some(quotient_polys_cap_target), Some(quotient_polys_cap)) = if let (Some(quotient_polys_cap_target), Some(quotient_polys_cap)) =
(&proof_target.quotient_polys_cap, &proof.quotient_polys_cap) (&proof_target.quotient_polys_cap, &proof.quotient_polys_cap)

View File

@ -13,6 +13,7 @@ use plonky2::fri::structure::{
}; };
use plonky2::hash::hash_types::RichField; use plonky2::hash::hash_types::RichField;
use plonky2::iop::ext_target::ExtensionTarget; use plonky2::iop::ext_target::ExtensionTarget;
use plonky2::iop::target::Target;
use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_builder::CircuitBuilder;
use crate::config::StarkConfig; use crate::config::StarkConfig;
@ -175,7 +176,7 @@ pub trait Stark<F: RichField + Extendable<D>, const D: usize>: Sync {
&self, &self,
builder: &mut CircuitBuilder<F, D>, builder: &mut CircuitBuilder<F, D>,
zeta: ExtensionTarget<D>, zeta: ExtensionTarget<D>,
g: F, g: Target,
num_ctl_helper_polys: usize, num_ctl_helper_polys: usize,
num_ctl_zs: usize, num_ctl_zs: usize,
config: &StarkConfig, config: &StarkConfig,
@ -222,7 +223,8 @@ pub trait Stark<F: RichField + Extendable<D>, const D: usize>: Sync {
] ]
.concat(), .concat(),
}; };
let zeta_next = builder.mul_const_extension(g, zeta); let g_ext = builder.convert_to_ext(g);
let zeta_next = builder.mul_extension(g_ext, zeta);
let zeta_next_batch = FriBatchInfoTarget { let zeta_next_batch = FriBatchInfoTarget {
point: zeta_next, point: zeta_next,
polynomials: [trace_info, auxiliary_polys_info].concat(), polynomials: [trace_info, auxiliary_polys_info].concat(),

View File

@ -114,9 +114,10 @@ mod tests {
let stark = S::new(num_rows); let stark = S::new(num_rows);
let trace = stark.generate_trace(); let trace = stark.generate_trace();
let proof = prove::<F, C, S, D>(stark, &config, trace, &[], &mut TimingTree::default())?; let proof =
prove::<F, C, S, D>(stark, &config, trace, &[], None, &mut TimingTree::default())?;
verify_stark_proof(stark, proof, &config) verify_stark_proof(stark, proof, &config, None)
} }
#[test] #[test]
@ -156,8 +157,9 @@ mod tests {
let stark = S::new(num_rows); let stark = S::new(num_rows);
let trace = stark.generate_trace(); let trace = stark.generate_trace();
let proof = prove::<F, C, S, D>(stark, &config, trace, &[], &mut TimingTree::default())?; let proof =
verify_stark_proof(stark, proof.clone(), &config)?; prove::<F, C, S, D>(stark, &config, trace, &[], None, &mut TimingTree::default())?;
verify_stark_proof(stark, proof.clone(), &config, None)?;
recursive_proof::<F, C, S, C, D>(stark, proof, &config, true) recursive_proof::<F, C, S, C, D>(stark, proof, &config, true)
} }
@ -183,9 +185,9 @@ mod tests {
let degree_bits = inner_proof.proof.recover_degree_bits(inner_config); let degree_bits = inner_proof.proof.recover_degree_bits(inner_config);
let pt = let pt =
add_virtual_stark_proof_with_pis(&mut builder, &stark, inner_config, degree_bits, 0, 0); add_virtual_stark_proof_with_pis(&mut builder, &stark, inner_config, degree_bits, 0, 0);
set_stark_proof_with_pis_target(&mut pw, &pt, &inner_proof, builder.zero())?; set_stark_proof_with_pis_target(&mut pw, &pt, &inner_proof, degree_bits, builder.zero())?;
verify_stark_proof_circuit::<F, InnerC, S, D>(&mut builder, stark, pt, inner_config); verify_stark_proof_circuit::<F, InnerC, S, D>(&mut builder, stark, pt, inner_config, None);
if print_gate_counts { if print_gate_counts {
builder.print_gate_counts(0); builder.print_gate_counts(0);

View File

@ -10,6 +10,7 @@ use itertools::Itertools;
use plonky2::field::extension::{Extendable, FieldExtension}; use plonky2::field::extension::{Extendable, FieldExtension};
use plonky2::field::types::Field; use plonky2::field::types::Field;
use plonky2::fri::verifier::verify_fri_proof; use plonky2::fri::verifier::verify_fri_proof;
use plonky2::fri::FriParams;
use plonky2::hash::hash_types::RichField; use plonky2::hash::hash_types::RichField;
use plonky2::hash::merkle_tree::MerkleCap; use plonky2::hash::merkle_tree::MerkleCap;
use plonky2::iop::challenger::Challenger; use plonky2::iop::challenger::Challenger;
@ -35,11 +36,18 @@ pub fn verify_stark_proof<
stark: S, stark: S,
proof_with_pis: StarkProofWithPublicInputs<F, C, D>, proof_with_pis: StarkProofWithPublicInputs<F, C, D>,
config: &StarkConfig, config: &StarkConfig,
verifier_circuit_fri_params: Option<FriParams>,
) -> Result<()> { ) -> Result<()> {
ensure!(proof_with_pis.public_inputs.len() == S::PUBLIC_INPUTS); ensure!(proof_with_pis.public_inputs.len() == S::PUBLIC_INPUTS);
let mut challenger = Challenger::<F, C::Hasher>::new(); let mut challenger = Challenger::<F, C::Hasher>::new();
let challenges = proof_with_pis.get_challenges(&mut challenger, None, false, config); let challenges = proof_with_pis.get_challenges(
&mut challenger,
None,
false,
config,
verifier_circuit_fri_params,
);
verify_stark_proof_with_challenges( verify_stark_proof_with_challenges(
&stark, &stark,