Add Support for Batch STARKs with Proving, Verification, and Recursion (#1600)

* add batch starks

* fix build

* fix ci

* address comments from hratoanina

* address comments from Nashtare

* fix tests

* address comments from hratoanina

* Update plonky2/src/batch_fri/recursive_verifier.rs

Co-authored-by: Robin Salen <30937548+Nashtare@users.noreply.github.com>

* Update plonky2/src/hash/merkle_proofs.rs

Co-authored-by: Robin Salen <30937548+Nashtare@users.noreply.github.com>

* Update plonky2/src/batch_fri/verifier.rs

Co-authored-by: Robin Salen <30937548+Nashtare@users.noreply.github.com>

---------

Co-authored-by: Robin Salen <30937548+Nashtare@users.noreply.github.com>
This commit is contained in:
Sai 2024-07-16 06:43:07 +08:00 committed by GitHub
parent 4090881d5c
commit 0e363e16a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 2198 additions and 62 deletions

View File

@ -0,0 +1,4 @@
pub mod oracle;
pub mod prover;
pub mod recursive_verifier;
pub mod verifier;

View File

@ -0,0 +1,610 @@
#[cfg(not(feature = "std"))]
use alloc::{format, vec::Vec};
use itertools::Itertools;
use plonky2_field::extension::Extendable;
use plonky2_field::fft::FftRootTable;
use plonky2_field::packed::PackedField;
use plonky2_field::polynomial::{PolynomialCoeffs, PolynomialValues};
use plonky2_field::types::Field;
use plonky2_maybe_rayon::*;
use plonky2_util::{log2_strict, reverse_index_bits_in_place};
use crate::batch_fri::prover::batch_fri_proof;
use crate::fri::oracle::PolynomialBatch;
use crate::fri::proof::FriProof;
use crate::fri::structure::{FriBatchInfo, FriInstanceInfo};
use crate::fri::FriParams;
use crate::hash::batch_merkle_tree::BatchMerkleTree;
use crate::hash::hash_types::RichField;
use crate::iop::challenger::Challenger;
use crate::plonk::config::GenericConfig;
use crate::timed;
use crate::util::reducing::ReducingFactor;
use crate::util::timing::TimingTree;
use crate::util::{reverse_bits, transpose};
/// Represents a batch FRI oracle, i.e. a batch of polynomials with different degrees which have
/// been Merkle-ized in a [`BatchMerkleTree`].
#[derive(Eq, PartialEq, Debug)]
pub struct BatchFriOracle<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
{
pub polynomials: Vec<PolynomialCoeffs<F>>,
pub batch_merkle_tree: BatchMerkleTree<F, C::Hasher>,
// The degree bits of each polynomial group.
pub degree_bits: Vec<usize>,
pub rate_bits: usize,
pub blinding: bool,
}
impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
BatchFriOracle<F, C, D>
{
/// Creates a list polynomial commitment for the polynomials interpolating the values in `values`.
pub fn from_values(
values: Vec<PolynomialValues<F>>,
rate_bits: usize,
blinding: bool,
cap_height: usize,
timing: &mut TimingTree,
fft_root_table: &[Option<&FftRootTable<F>>],
) -> Self {
let coeffs = timed!(
timing,
"IFFT",
values.into_par_iter().map(|v| v.ifft()).collect::<Vec<_>>()
);
Self::from_coeffs(
coeffs,
rate_bits,
blinding,
cap_height,
timing,
fft_root_table,
)
}
/// Creates a list polynomial commitment for the polynomials `polynomials`.
pub fn from_coeffs(
polynomials: Vec<PolynomialCoeffs<F>>,
rate_bits: usize,
blinding: bool,
cap_height: usize,
timing: &mut TimingTree,
fft_root_table: &[Option<&FftRootTable<F>>],
) -> Self {
let mut degree_bits = polynomials
.iter()
.map(|p| log2_strict(p.len()))
.collect_vec();
assert!(degree_bits.windows(2).all(|pair| { pair[0] >= pair[1] }));
let num_polynomials = polynomials.len();
let mut group_start = 0;
let mut leaves = Vec::new();
for (i, d) in degree_bits.iter().enumerate() {
if i == num_polynomials - 1 || *d > degree_bits[i + 1] {
let lde_values = timed!(
timing,
"FFT + blinding",
PolynomialBatch::<F, C, D>::lde_values(
&polynomials[group_start..i + 1],
rate_bits,
blinding,
fft_root_table[i]
)
);
let mut leaf_group = timed!(timing, "transpose LDEs", transpose(&lde_values));
reverse_index_bits_in_place(&mut leaf_group);
leaves.push(leaf_group);
group_start = i + 1;
}
}
let batch_merkle_tree = timed!(
timing,
"build Field Merkle tree",
BatchMerkleTree::new(leaves, cap_height)
);
degree_bits.sort_unstable();
degree_bits.dedup();
degree_bits.reverse();
assert_eq!(batch_merkle_tree.leaves.len(), degree_bits.len());
Self {
polynomials,
batch_merkle_tree,
degree_bits,
rate_bits,
blinding,
}
}
/// Produces a batch opening proof.
pub fn prove_openings(
degree_bits: &[usize],
instances: &[FriInstanceInfo<F, D>],
oracles: &[&Self],
challenger: &mut Challenger<F, C::Hasher>,
fri_params: &FriParams,
timing: &mut TimingTree,
) -> FriProof<F, C::Hasher, D> {
assert_eq!(degree_bits.len(), instances.len());
assert!(D > 1, "Not implemented for D=1.");
let alpha = challenger.get_extension_challenge::<D>();
let mut alpha = ReducingFactor::new(alpha);
let mut final_lde_polynomial_coeff = Vec::with_capacity(instances.len());
let mut final_lde_polynomial_values = Vec::with_capacity(instances.len());
for (i, instance) in instances.iter().enumerate() {
// Final low-degree polynomial that goes into FRI.
let mut final_poly = PolynomialCoeffs::empty();
// Each batch `i` consists of an opening point `z_i` and polynomials `{f_ij}_j` to be opened at that point.
// For each batch, we compute the composition polynomial `F_i = sum alpha^j f_ij`,
// where `alpha` is a random challenge in the extension field.
// The final polynomial is then computed as `final_poly = sum_i alpha^(k_i) (F_i(X) - F_i(z_i))/(X-z_i)`
// where the `k_i`s are chosen such that each power of `alpha` appears only once in the final sum.
// There are usually two batches for the openings at `zeta` and `g * zeta`.
// The oracles used in Plonky2 are given in `FRI_ORACLES` in `plonky2/src/plonk/plonk_common.rs`.
for FriBatchInfo { point, polynomials } in &instance.batches {
// Collect the coefficients of all the polynomials in `polynomials`.
let polys_coeff = polynomials.iter().map(|fri_poly| {
&oracles[fri_poly.oracle_index].polynomials[fri_poly.polynomial_index]
});
let composition_poly = timed!(
timing,
&format!("reduce batch of {} polynomials", polynomials.len()),
alpha.reduce_polys_base(polys_coeff)
);
let mut quotient = composition_poly.divide_by_linear(*point);
quotient.coeffs.push(F::Extension::ZERO); // pad back to power of two
alpha.shift_poly(&mut final_poly);
final_poly += quotient;
}
assert_eq!(final_poly.len(), 1 << degree_bits[i]);
let lde_final_poly = final_poly.lde(fri_params.config.rate_bits);
let lde_final_values = timed!(
timing,
&format!("perform final FFT {}", lde_final_poly.len()),
lde_final_poly.coset_fft(F::coset_shift().into())
);
final_lde_polynomial_coeff.push(lde_final_poly);
final_lde_polynomial_values.push(lde_final_values);
}
batch_fri_proof::<F, C, D>(
&oracles
.iter()
.map(|o| &o.batch_merkle_tree)
.collect::<Vec<_>>(),
final_lde_polynomial_coeff[0].clone(),
&final_lde_polynomial_values,
challenger,
fri_params,
timing,
)
}
/// Fetches LDE values at the `index * step`th point.
pub fn get_lde_values(
&self,
degree_bits_index: usize,
index: usize,
step: usize,
slice_start: usize,
slice_len: usize,
) -> &[F] {
let index = index * step;
let index = reverse_bits(index, self.degree_bits[degree_bits_index] + self.rate_bits);
let slice = &self.batch_merkle_tree.leaves[degree_bits_index][index];
&slice[slice_start..slice_start + slice_len]
}
/// Like `get_lde_values`, but fetches LDE values from a batch of `P::WIDTH` points, and returns
/// packed values.
pub fn get_lde_values_packed<P>(
&self,
degree_bits_index: usize,
index_start: usize,
step: usize,
slice_start: usize,
slice_len: usize,
) -> Vec<P>
where
P: PackedField<Scalar = F>,
{
let row_wise = (0..P::WIDTH)
.map(|i| {
self.get_lde_values(
degree_bits_index,
index_start + i,
step,
slice_start,
slice_len,
)
})
.collect_vec();
// This is essentially a transpose, but we will not use the generic transpose method as we
// want inner lists to be of type P, not Vecs which would involve allocation.
let leaf_size = row_wise[0].len();
(0..leaf_size)
.map(|j| {
let mut packed = P::ZEROS;
packed
.as_slice_mut()
.iter_mut()
.zip(&row_wise)
.for_each(|(packed_i, row_i)| *packed_i = row_i[j]);
packed
})
.collect_vec()
}
}
#[cfg(test)]
mod test {
#[cfg(not(feature = "std"))]
use alloc::vec;
use plonky2_field::goldilocks_field::GoldilocksField;
use plonky2_field::types::Sample;
use super::*;
use crate::batch_fri::oracle::BatchFriOracle;
use crate::batch_fri::verifier::verify_batch_fri_proof;
use crate::fri::reduction_strategies::FriReductionStrategy;
use crate::fri::structure::{
FriBatchInfo, FriBatchInfoTarget, FriInstanceInfo, FriInstanceInfoTarget, FriOpeningBatch,
FriOpeningBatchTarget, FriOpenings, FriOpeningsTarget, FriOracleInfo, FriPolynomialInfo,
};
use crate::fri::witness_util::set_fri_proof_target;
use crate::fri::FriConfig;
use crate::iop::challenger::RecursiveChallenger;
use crate::iop::witness::PartialWitness;
use crate::plonk::circuit_builder::CircuitBuilder;
use crate::plonk::circuit_data::CircuitConfig;
use crate::plonk::config::PoseidonGoldilocksConfig;
use crate::plonk::prover::prove;
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
type H = <C as GenericConfig<D>>::Hasher;
#[test]
fn batch_prove_openings() -> anyhow::Result<()> {
let mut timing = TimingTree::default();
let k0 = 9;
let k1 = 8;
let k2 = 6;
let reduction_arity_bits = vec![1, 2, 1];
let fri_params = FriParams {
config: FriConfig {
rate_bits: 1,
cap_height: 0,
proof_of_work_bits: 0,
reduction_strategy: FriReductionStrategy::Fixed(reduction_arity_bits.clone()),
num_query_rounds: 10,
},
hiding: false,
degree_bits: k0,
reduction_arity_bits,
};
let n0 = 1 << k0;
let n1 = 1 << k1;
let n2 = 1 << k2;
let trace0 = PolynomialValues::new(F::rand_vec(n0));
let trace1_0 = PolynomialValues::new(F::rand_vec(n1));
let trace1_1 = PolynomialValues::new(F::rand_vec(n1));
let trace2 = PolynomialValues::new(F::rand_vec(n2));
let trace_oracle: BatchFriOracle<GoldilocksField, C, D> = BatchFriOracle::from_values(
vec![
trace0.clone(),
trace1_0.clone(),
trace1_1.clone(),
trace2.clone(),
],
fri_params.config.rate_bits,
fri_params.hiding,
fri_params.config.cap_height,
&mut timing,
&[None; 4],
);
let mut challenger = Challenger::<F, H>::new();
challenger.observe_cap(&trace_oracle.batch_merkle_tree.cap);
let zeta = challenger.get_extension_challenge::<D>();
let eta = challenger.get_extension_challenge::<D>();
let poly0 = &trace_oracle.polynomials[0];
let poly1_0 = &trace_oracle.polynomials[1];
let poly1_1 = &trace_oracle.polynomials[2];
let poly2 = &trace_oracle.polynomials[3];
let mut challenger = Challenger::<F, H>::new();
let mut verifier_challenger = challenger.clone();
let fri_instance_0 = FriInstanceInfo {
oracles: vec![FriOracleInfo {
num_polys: 1,
blinding: false,
}],
batches: vec![
FriBatchInfo {
point: zeta,
polynomials: vec![FriPolynomialInfo {
oracle_index: 0,
polynomial_index: 0,
}],
},
FriBatchInfo {
point: eta,
polynomials: vec![FriPolynomialInfo {
oracle_index: 0,
polynomial_index: 0,
}],
},
],
};
let fri_instance_1 = FriInstanceInfo {
oracles: vec![FriOracleInfo {
num_polys: 2,
blinding: false,
}],
batches: vec![
FriBatchInfo {
point: zeta,
polynomials: vec![
FriPolynomialInfo {
oracle_index: 0,
polynomial_index: 1,
},
FriPolynomialInfo {
oracle_index: 0,
polynomial_index: 2,
},
],
},
FriBatchInfo {
point: eta,
polynomials: vec![FriPolynomialInfo {
oracle_index: 0,
polynomial_index: 2,
}],
},
],
};
let fri_instance_2 = FriInstanceInfo {
oracles: vec![FriOracleInfo {
num_polys: 1,
blinding: false,
}],
batches: vec![FriBatchInfo {
point: zeta,
polynomials: vec![FriPolynomialInfo {
oracle_index: 0,
polynomial_index: 3,
}],
}],
};
let fri_instances = vec![fri_instance_0, fri_instance_1, fri_instance_2];
let poly0_zeta = poly0.to_extension::<D>().eval(zeta);
let poly0_eta = poly0.to_extension::<D>().eval(eta);
let fri_opening_batch_0 = FriOpenings {
batches: vec![
FriOpeningBatch {
values: vec![poly0_zeta],
},
FriOpeningBatch {
values: vec![poly0_eta],
},
],
};
let poly10_zeta = poly1_0.to_extension::<D>().eval(zeta);
let poly11_zeta = poly1_1.to_extension::<D>().eval(zeta);
let poly11_eta = poly1_1.to_extension::<D>().eval(eta);
let fri_opening_batch_1 = FriOpenings {
batches: vec![
FriOpeningBatch {
values: vec![poly10_zeta, poly11_zeta],
},
FriOpeningBatch {
values: vec![poly11_eta],
},
],
};
let poly2_zeta = poly2.to_extension::<D>().eval(zeta);
let fri_opening_batch_2 = FriOpenings {
batches: vec![FriOpeningBatch {
values: vec![poly2_zeta],
}],
};
let fri_openings = vec![
fri_opening_batch_0,
fri_opening_batch_1,
fri_opening_batch_2,
];
let proof = BatchFriOracle::prove_openings(
&[k0, k1, k2],
&fri_instances,
&[&trace_oracle],
&mut challenger,
&fri_params,
&mut timing,
);
let fri_challenges = verifier_challenger.fri_challenges::<C, D>(
&proof.commit_phase_merkle_caps,
&proof.final_poly,
proof.pow_witness,
k0,
&fri_params.config,
);
let degree_bits = [k0, k1, k2];
let merkle_cap = trace_oracle.batch_merkle_tree.cap;
verify_batch_fri_proof::<GoldilocksField, C, D>(
&degree_bits,
&fri_instances,
&fri_openings,
&fri_challenges,
&[merkle_cap.clone()],
&proof,
&fri_params,
)?;
// Test recursive verifier
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
let num_leaves_per_oracle = vec![4];
let fri_proof_target = builder.add_virtual_fri_proof(&num_leaves_per_oracle, &fri_params);
let zeta_target = builder.constant_extension(zeta);
let eta_target = builder.constant_extension(eta);
let fri_instance_info_target_0 = FriInstanceInfoTarget {
oracles: vec![FriOracleInfo {
num_polys: 1,
blinding: false,
}],
batches: vec![
FriBatchInfoTarget {
point: zeta_target,
polynomials: vec![FriPolynomialInfo {
oracle_index: 0,
polynomial_index: 0,
}],
},
FriBatchInfoTarget {
point: eta_target,
polynomials: vec![FriPolynomialInfo {
oracle_index: 0,
polynomial_index: 0,
}],
},
],
};
let fri_instance_info_target_1 = FriInstanceInfoTarget {
oracles: vec![FriOracleInfo {
num_polys: 2,
blinding: false,
}],
batches: vec![
FriBatchInfoTarget {
point: zeta_target,
polynomials: vec![
FriPolynomialInfo {
oracle_index: 0,
polynomial_index: 1,
},
FriPolynomialInfo {
oracle_index: 0,
polynomial_index: 2,
},
],
},
FriBatchInfoTarget {
point: eta_target,
polynomials: vec![FriPolynomialInfo {
oracle_index: 0,
polynomial_index: 2,
}],
},
],
};
let fri_instance_info_target_2 = FriInstanceInfoTarget {
oracles: vec![FriOracleInfo {
num_polys: 1,
blinding: false,
}],
batches: vec![FriBatchInfoTarget {
point: zeta_target,
polynomials: vec![FriPolynomialInfo {
oracle_index: 0,
polynomial_index: 3,
}],
}],
};
let poly0_zeta_target = builder.constant_extension(poly0_zeta);
let poly0_eta_target = builder.constant_extension(poly0_eta);
let fri_opening_batch_0 = FriOpeningsTarget {
batches: vec![
FriOpeningBatchTarget {
values: vec![poly0_zeta_target],
},
FriOpeningBatchTarget {
values: vec![poly0_eta_target],
},
],
};
let poly10_zeta_target = builder.constant_extension(poly10_zeta);
let poly11_zeta_target = builder.constant_extension(poly11_zeta);
let poly11_eta_target = builder.constant_extension(poly11_eta);
let fri_opening_batch_1 = FriOpeningsTarget {
batches: vec![
FriOpeningBatchTarget {
values: vec![poly10_zeta_target, poly11_zeta_target],
},
FriOpeningBatchTarget {
values: vec![poly11_eta_target],
},
],
};
let poly2_zeta_target = builder.constant_extension(poly2_zeta);
let fri_opening_batch_2 = FriOpeningsTarget {
batches: vec![FriOpeningBatchTarget {
values: vec![poly2_zeta_target],
}],
};
let fri_openings_target = [
fri_opening_batch_0,
fri_opening_batch_1,
fri_opening_batch_2,
];
let mut challenger = RecursiveChallenger::<F, H, D>::new(&mut builder);
let fri_challenges_target = challenger.fri_challenges(
&mut builder,
&fri_proof_target.commit_phase_merkle_caps,
&fri_proof_target.final_poly,
fri_proof_target.pow_witness,
&fri_params.config,
);
let merkle_cap_target = builder.constant_merkle_cap(&merkle_cap);
let fri_instance_info_target = vec![
fri_instance_info_target_0,
fri_instance_info_target_1,
fri_instance_info_target_2,
];
builder.verify_batch_fri_proof::<C>(
&degree_bits,
&fri_instance_info_target,
&fri_openings_target,
&fri_challenges_target,
&[merkle_cap_target],
&fri_proof_target,
&fri_params,
);
let mut pw = PartialWitness::new();
set_fri_proof_target(&mut pw, &fri_proof_target, &proof);
let data = builder.build::<C>();
let proof = prove::<F, C, D>(&data.prover_only, &data.common, pw, &mut timing)?;
data.verify(proof.clone())?;
Ok(())
}
}

View File

@ -0,0 +1,475 @@
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use plonky2_field::extension::flatten;
#[allow(unused_imports)]
use plonky2_field::types::Field;
use plonky2_maybe_rayon::*;
use plonky2_util::{log2_strict, reverse_index_bits_in_place};
use crate::field::extension::{unflatten, Extendable};
use crate::field::polynomial::{PolynomialCoeffs, PolynomialValues};
use crate::fri::proof::{FriInitialTreeProof, FriProof, FriQueryRound, FriQueryStep};
use crate::fri::prover::{fri_proof_of_work, FriCommitedTrees};
use crate::fri::FriParams;
use crate::hash::batch_merkle_tree::BatchMerkleTree;
use crate::hash::hash_types::RichField;
use crate::hash::merkle_tree::MerkleTree;
use crate::iop::challenger::Challenger;
use crate::plonk::config::GenericConfig;
use crate::plonk::plonk_common::reduce_with_powers;
use crate::timed;
use crate::util::timing::TimingTree;
/// Builds a batch FRI proof.
pub fn batch_fri_proof<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>(
initial_merkle_trees: &[&BatchMerkleTree<F, C::Hasher>],
lde_polynomial_coeffs: PolynomialCoeffs<F::Extension>,
lde_polynomial_values: &[PolynomialValues<F::Extension>],
challenger: &mut Challenger<F, C::Hasher>,
fri_params: &FriParams,
timing: &mut TimingTree,
) -> FriProof<F, C::Hasher, D> {
let n = lde_polynomial_coeffs.len();
assert_eq!(lde_polynomial_values[0].len(), n);
// The polynomial vectors should be sorted by degree, from largest to smallest, with no duplicate degrees.
assert!(lde_polynomial_values
.windows(2)
.all(|pair| { pair[0].len() > pair[1].len() }));
// Check that reduction_arity_bits covers all polynomials
let mut cur_n = log2_strict(n);
let mut cur_poly_index = 1;
for arity_bits in &fri_params.reduction_arity_bits {
cur_n -= arity_bits;
if cur_poly_index < lde_polynomial_values.len()
&& cur_n == log2_strict(lde_polynomial_values[cur_poly_index].len())
{
cur_poly_index += 1;
}
}
assert_eq!(cur_poly_index, lde_polynomial_values.len());
// Commit phase
let (trees, final_coeffs) = timed!(
timing,
"fold codewords in the commitment phase",
batch_fri_committed_trees::<F, C, D>(
lde_polynomial_coeffs,
lde_polynomial_values,
challenger,
fri_params,
)
);
// PoW phase
let pow_witness = timed!(
timing,
"find proof-of-work witness",
fri_proof_of_work::<F, C, D>(challenger, &fri_params.config)
);
// Query phase
let query_round_proofs = batch_fri_prover_query_rounds::<F, C, D>(
initial_merkle_trees,
&trees,
challenger,
n,
fri_params,
);
FriProof {
commit_phase_merkle_caps: trees.iter().map(|t| t.cap.clone()).collect(),
query_round_proofs,
final_poly: final_coeffs,
pow_witness,
}
}
pub(crate) fn batch_fri_committed_trees<
F: RichField + Extendable<D>,
C: GenericConfig<D, F = F>,
const D: usize,
>(
mut final_coeffs: PolynomialCoeffs<F::Extension>,
values: &[PolynomialValues<F::Extension>],
challenger: &mut Challenger<F, C::Hasher>,
fri_params: &FriParams,
) -> FriCommitedTrees<F, C, D> {
let mut trees = Vec::with_capacity(fri_params.reduction_arity_bits.len());
let mut shift = F::MULTIPLICATIVE_GROUP_GENERATOR;
let mut polynomial_index = 1;
let mut final_values = values[0].clone();
for arity_bits in &fri_params.reduction_arity_bits {
let arity = 1 << arity_bits;
reverse_index_bits_in_place(&mut final_values.values);
let chunked_values = final_values.values.par_chunks(arity).map(flatten).collect();
let tree = MerkleTree::<F, C::Hasher>::new(chunked_values, fri_params.config.cap_height);
challenger.observe_cap(&tree.cap);
trees.push(tree);
let beta = challenger.get_extension_challenge::<D>();
// P(x) = sum_{i<r} x^i * P_i(x^r) becomes sum_{i<r} beta^i * P_i(x).
// TODO: Optimize the folding process. Consider folding the functions directly in the value domain.
final_coeffs = PolynomialCoeffs::new(
final_coeffs
.coeffs
.par_chunks_exact(arity)
.map(|chunk| reduce_with_powers(chunk, beta))
.collect::<Vec<_>>(),
);
shift = shift.exp_u64(arity as u64);
final_values = final_coeffs.coset_fft(shift.into());
if polynomial_index != values.len() && final_values.len() == values[polynomial_index].len()
{
final_values = PolynomialValues::new(
final_values
.values
.iter()
.zip(&values[polynomial_index].values)
.map(|(&f, &v)| f * beta + v)
.collect::<Vec<_>>(),
);
polynomial_index += 1;
}
final_coeffs = final_values.clone().coset_ifft(shift.into());
}
assert_eq!(polynomial_index, values.len());
// The coefficients being removed here should always be zero.
final_coeffs
.coeffs
.truncate(final_coeffs.len() >> fri_params.config.rate_bits);
challenger.observe_extension_elements(&final_coeffs.coeffs);
(trees, final_coeffs)
}
fn batch_fri_prover_query_rounds<
F: RichField + Extendable<D>,
C: GenericConfig<D, F = F>,
const D: usize,
>(
initial_merkle_trees: &[&BatchMerkleTree<F, C::Hasher>],
trees: &[MerkleTree<F, C::Hasher>],
challenger: &mut Challenger<F, C::Hasher>,
n: usize,
fri_params: &FriParams,
) -> Vec<FriQueryRound<F, C::Hasher, D>> {
challenger
.get_n_challenges(fri_params.config.num_query_rounds)
.into_par_iter()
.map(|rand| {
let x_index = rand.to_canonical_u64() as usize % n;
batch_fri_prover_query_round::<F, C, D>(
initial_merkle_trees,
trees,
x_index,
fri_params,
)
})
.collect()
}
fn batch_fri_prover_query_round<
F: RichField + Extendable<D>,
C: GenericConfig<D, F = F>,
const D: usize,
>(
initial_merkle_trees: &[&BatchMerkleTree<F, C::Hasher>],
trees: &[MerkleTree<F, C::Hasher>],
mut x_index: usize,
fri_params: &FriParams,
) -> FriQueryRound<F, C::Hasher, D> {
let mut query_steps = Vec::with_capacity(trees.len());
let initial_proof = initial_merkle_trees
.iter()
.map(|t| {
(
t.values(x_index)
.iter()
.flatten()
.cloned()
.collect::<Vec<_>>(),
t.open_batch(x_index),
)
})
.collect::<Vec<_>>();
for (i, tree) in trees.iter().enumerate() {
let arity_bits = fri_params.reduction_arity_bits[i];
let evals = unflatten(tree.get(x_index >> arity_bits));
let merkle_proof = tree.prove(x_index >> arity_bits);
query_steps.push(FriQueryStep {
evals,
merkle_proof,
});
x_index >>= arity_bits;
}
FriQueryRound {
initial_trees_proof: FriInitialTreeProof {
evals_proofs: initial_proof,
},
steps: query_steps,
}
}
#[cfg(test)]
mod tests {
#[cfg(not(feature = "std"))]
use alloc::vec;
use anyhow::Result;
use itertools::Itertools;
use plonky2_field::goldilocks_field::GoldilocksField;
use plonky2_field::types::{Field64, Sample};
use super::*;
use crate::batch_fri::oracle::BatchFriOracle;
use crate::batch_fri::verifier::verify_batch_fri_proof;
use crate::fri::reduction_strategies::FriReductionStrategy;
use crate::fri::structure::{
FriBatchInfo, FriInstanceInfo, FriOpeningBatch, FriOpenings, FriOracleInfo,
FriPolynomialInfo,
};
use crate::fri::FriConfig;
use crate::plonk::config::PoseidonGoldilocksConfig;
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
type H = <C as GenericConfig<D>>::Hasher;
#[test]
fn single_polynomial() -> Result<()> {
let mut timing = TimingTree::default();
let k = 9;
let reduction_arity_bits = vec![1, 2, 1];
let fri_params = FriParams {
config: FriConfig {
rate_bits: 1,
cap_height: 5,
proof_of_work_bits: 0,
reduction_strategy: FriReductionStrategy::Fixed(reduction_arity_bits.clone()),
num_query_rounds: 10,
},
hiding: false,
degree_bits: k,
reduction_arity_bits,
};
let n = 1 << k;
let trace = PolynomialValues::new((1..n + 1).map(F::from_canonical_i64).collect_vec());
let polynomial_batch: BatchFriOracle<GoldilocksField, C, D> = BatchFriOracle::from_values(
vec![trace.clone()],
fri_params.config.rate_bits,
fri_params.hiding,
fri_params.config.cap_height,
&mut timing,
&[None],
);
let poly = &polynomial_batch.polynomials[0];
let mut challenger = Challenger::<F, H>::new();
challenger.observe_cap(&polynomial_batch.batch_merkle_tree.cap);
let _alphas = challenger.get_n_challenges(2);
let zeta = challenger.get_extension_challenge::<D>();
challenger.observe_extension_element::<D>(&poly.to_extension::<D>().eval(zeta));
let mut verifier_challenger = challenger.clone();
let fri_instance: FriInstanceInfo<F, D> = FriInstanceInfo {
oracles: vec![FriOracleInfo {
num_polys: 1,
blinding: false,
}],
batches: vec![FriBatchInfo {
point: zeta,
polynomials: vec![FriPolynomialInfo {
oracle_index: 0,
polynomial_index: 0,
}],
}],
};
let _alpha = challenger.get_extension_challenge::<D>();
let composition_poly = poly.mul_extension::<D>(<F as Extendable<D>>::Extension::ONE);
let mut quotient = composition_poly.divide_by_linear(zeta);
quotient.coeffs.push(<F as Extendable<D>>::Extension::ZERO);
let lde_final_poly = quotient.lde(fri_params.config.rate_bits);
let lde_final_values = lde_final_poly.coset_fft(F::coset_shift().into());
let proof = batch_fri_proof::<F, C, D>(
&[&polynomial_batch.batch_merkle_tree],
lde_final_poly,
&[lde_final_values],
&mut challenger,
&fri_params,
&mut timing,
);
let fri_challenges = verifier_challenger.fri_challenges::<C, D>(
&proof.commit_phase_merkle_caps,
&proof.final_poly,
proof.pow_witness,
k,
&fri_params.config,
);
let fri_opening_batch = FriOpeningBatch {
values: vec![poly.to_extension::<D>().eval(zeta)],
};
verify_batch_fri_proof::<GoldilocksField, C, D>(
&[k],
&[fri_instance],
&[FriOpenings {
batches: vec![fri_opening_batch],
}],
&fri_challenges,
&[polynomial_batch.batch_merkle_tree.cap],
&proof,
&fri_params,
)
}
#[test]
fn multiple_polynomials() -> Result<()> {
let mut timing = TimingTree::default();
let k0 = 9;
let k1 = 8;
let k2 = 6;
let reduction_arity_bits = vec![1, 2, 1];
let fri_params = FriParams {
config: FriConfig {
rate_bits: 1,
cap_height: 5,
proof_of_work_bits: 0,
reduction_strategy: FriReductionStrategy::Fixed(reduction_arity_bits.clone()),
num_query_rounds: 10,
},
hiding: false,
degree_bits: k0,
reduction_arity_bits,
};
let n0 = 1 << k0;
let n1 = 1 << k1;
let n2 = 1 << k2;
let trace0 = PolynomialValues::new(F::rand_vec(n0));
let trace1 = PolynomialValues::new(F::rand_vec(n1));
let trace2 = PolynomialValues::new(F::rand_vec(n2));
let trace_oracle: BatchFriOracle<GoldilocksField, C, D> = BatchFriOracle::from_values(
vec![trace0.clone(), trace1.clone(), trace2.clone()],
fri_params.config.rate_bits,
fri_params.hiding,
fri_params.config.cap_height,
&mut timing,
&[None; 3],
);
let mut challenger = Challenger::<F, H>::new();
challenger.observe_cap(&trace_oracle.batch_merkle_tree.cap);
let _alphas = challenger.get_n_challenges(2);
let zeta = challenger.get_extension_challenge::<D>();
let poly0 = &trace_oracle.polynomials[0];
let poly1 = &trace_oracle.polynomials[1];
let poly2 = &trace_oracle.polynomials[2];
challenger.observe_extension_element::<D>(&poly0.to_extension::<D>().eval(zeta));
challenger.observe_extension_element::<D>(&poly1.to_extension::<D>().eval(zeta));
challenger.observe_extension_element::<D>(&poly2.to_extension::<D>().eval(zeta));
let mut verifier_challenger = challenger.clone();
let _alpha = challenger.get_extension_challenge::<D>();
let composition_poly = poly0.mul_extension::<D>(<F as Extendable<D>>::Extension::ONE);
let mut quotient = composition_poly.divide_by_linear(zeta);
quotient.coeffs.push(<F as Extendable<D>>::Extension::ZERO);
let lde_final_poly_0 = quotient.lde(fri_params.config.rate_bits);
let lde_final_values_0 = lde_final_poly_0.coset_fft(F::coset_shift().into());
let composition_poly = poly1.mul_extension::<D>(<F as Extendable<D>>::Extension::ONE);
let mut quotient = composition_poly.divide_by_linear(zeta);
quotient.coeffs.push(<F as Extendable<D>>::Extension::ZERO);
let lde_final_poly_1 = quotient.lde(fri_params.config.rate_bits);
let lde_final_values_1 = lde_final_poly_1.coset_fft(F::coset_shift().into());
let composition_poly = poly2.mul_extension::<D>(<F as Extendable<D>>::Extension::ONE);
let mut quotient = composition_poly.divide_by_linear(zeta);
quotient.coeffs.push(<F as Extendable<D>>::Extension::ZERO);
let lde_final_poly_2 = quotient.lde(fri_params.config.rate_bits);
let lde_final_values_2 = lde_final_poly_2.coset_fft(F::coset_shift().into());
let proof = batch_fri_proof::<F, C, D>(
&[&trace_oracle.batch_merkle_tree],
lde_final_poly_0,
&[lde_final_values_0, lde_final_values_1, lde_final_values_2],
&mut challenger,
&fri_params,
&mut timing,
);
let get_test_fri_instance = |polynomial_index: usize| -> FriInstanceInfo<F, D> {
FriInstanceInfo {
oracles: vec![FriOracleInfo {
num_polys: 1,
blinding: false,
}],
batches: vec![FriBatchInfo {
point: zeta,
polynomials: vec![FriPolynomialInfo {
oracle_index: 0,
polynomial_index,
}],
}],
}
};
let fri_instances = vec![
get_test_fri_instance(0),
get_test_fri_instance(1),
get_test_fri_instance(2),
];
let fri_challenges = verifier_challenger.fri_challenges::<C, D>(
&proof.commit_phase_merkle_caps,
&proof.final_poly,
proof.pow_witness,
k0,
&fri_params.config,
);
let fri_opening_batch_0 = FriOpenings {
batches: vec![FriOpeningBatch {
values: vec![poly0.to_extension::<D>().eval(zeta)],
}],
};
let fri_opening_batch_1 = FriOpenings {
batches: vec![FriOpeningBatch {
values: vec![poly1.to_extension::<D>().eval(zeta)],
}],
};
let fri_opening_batch_2 = FriOpenings {
batches: vec![FriOpeningBatch {
values: vec![poly2.to_extension::<D>().eval(zeta)],
}],
};
let fri_openings = vec![
fri_opening_batch_0,
fri_opening_batch_1,
fri_opening_batch_2,
];
verify_batch_fri_proof::<GoldilocksField, C, D>(
&[k0, k1, k2],
&fri_instances,
&fri_openings,
&fri_challenges,
&[trace_oracle.batch_merkle_tree.cap],
&proof,
&fri_params,
)
}
}

View File

@ -0,0 +1,332 @@
#[cfg(not(feature = "std"))]
use alloc::{format, vec::Vec};
use itertools::Itertools;
use crate::field::extension::Extendable;
use crate::fri::proof::{
FriChallengesTarget, FriInitialTreeProofTarget, FriProofTarget, FriQueryRoundTarget,
};
use crate::fri::recursive_verifier::PrecomputedReducedOpeningsTarget;
use crate::fri::structure::{FriBatchInfoTarget, FriInstanceInfoTarget, FriOpeningsTarget};
use crate::fri::FriParams;
use crate::hash::hash_types::{MerkleCapTarget, RichField};
use crate::iop::ext_target::{flatten_target, ExtensionTarget};
use crate::iop::target::{BoolTarget, Target};
use crate::plonk::circuit_builder::CircuitBuilder;
use crate::plonk::config::{AlgebraicHasher, GenericConfig};
use crate::util::reducing::ReducingFactorTarget;
use crate::with_context;
impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
pub fn verify_batch_fri_proof<C: GenericConfig<D, F = F>>(
&mut self,
degree_bits: &[usize],
instance: &[FriInstanceInfoTarget<D>],
openings: &[FriOpeningsTarget<D>],
challenges: &FriChallengesTarget<D>,
initial_merkle_caps: &[MerkleCapTarget],
proof: &FriProofTarget<D>,
params: &FriParams,
) 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."
);
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 mut precomputed_reduced_evals = Vec::with_capacity(openings.len());
for opn in openings {
let pre = with_context!(
self,
"precompute reduced evaluations",
PrecomputedReducedOpeningsTarget::from_os_and_alpha(
opn,
challenges.fri_alpha,
self
)
);
precomputed_reduced_evals.push(pre);
}
let degree_bits = degree_bits
.iter()
.map(|d| d + params.config.rate_bits)
.collect_vec();
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.batch_fri_verifier_query_round::<C>(
&degree_bits,
instance,
challenges,
&precomputed_reduced_evals,
initial_merkle_caps,
proof,
challenges.fri_query_indices[i],
round_proof,
params,
)
);
}
}
fn batch_fri_verify_initial_proof<H: AlgebraicHasher<F>>(
&mut self,
degree_bits: &[usize],
instances: &[FriInstanceInfoTarget<D>],
x_index_bits: &[BoolTarget],
proof: &FriInitialTreeProofTarget,
initial_merkle_caps: &[MerkleCapTarget],
cap_index: Target,
) {
for (i, ((evals, merkle_proof), cap)) in proof
.evals_proofs
.iter()
.zip(initial_merkle_caps)
.enumerate()
{
let leaves = instances
.iter()
.scan(0, |leaf_index, inst| {
let num_polys = inst.oracles[i].num_polys;
let leaves = (*leaf_index..*leaf_index + num_polys)
.map(|idx| evals[idx])
.collect::<Vec<_>>();
*leaf_index += num_polys;
Some(leaves)
})
.collect::<Vec<_>>();
with_context!(
self,
&format!("verify {i}'th initial Merkle proof"),
self.verify_batch_merkle_proof_to_cap_with_cap_index::<H>(
&leaves,
degree_bits,
x_index_bits,
cap_index,
cap,
merkle_proof
)
);
}
}
fn batch_fri_combine_initial(
&mut self,
instance: &[FriInstanceInfoTarget<D>],
index: usize,
proof: &FriInitialTreeProofTarget,
alpha: ExtensionTarget<D>,
subgroup_x: Target,
precomputed_reduced_evals: &PrecomputedReducedOpeningsTarget<D>,
params: &FriParams,
) -> ExtensionTarget<D> {
assert!(D > 1, "Not implemented for D=1.");
let degree_log = params.degree_bits;
debug_assert_eq!(
degree_log,
params.config.cap_height + proof.evals_proofs[0].1.siblings.len()
- params.config.rate_bits
);
let subgroup_x = self.convert_to_ext(subgroup_x);
let mut alpha = ReducingFactorTarget::new(alpha);
let mut sum = self.zero_extension();
for (batch, reduced_openings) in instance[index]
.batches
.iter()
.zip(&precomputed_reduced_evals.reduced_openings_at_point)
{
let FriBatchInfoTarget { point, polynomials } = batch;
let evals = polynomials
.iter()
.map(|p| {
let poly_blinding = instance[index].oracles[p.oracle_index].blinding;
let salted = params.hiding && poly_blinding;
proof.unsalted_eval(p.oracle_index, p.polynomial_index, salted)
})
.collect_vec();
let reduced_evals = alpha.reduce_base(&evals, self);
let numerator = self.sub_extension(reduced_evals, *reduced_openings);
let denominator = self.sub_extension(subgroup_x, *point);
sum = alpha.shift(sum, self);
sum = self.div_add_extension(numerator, denominator, sum);
}
sum
}
fn batch_fri_verifier_query_round<C: GenericConfig<D, F = F>>(
&mut self,
degree_bits: &[usize],
instance: &[FriInstanceInfoTarget<D>],
challenges: &FriChallengesTarget<D>,
precomputed_reduced_evals: &[PrecomputedReducedOpeningsTarget<D>],
initial_merkle_caps: &[MerkleCapTarget],
proof: &FriProofTarget<D>,
x_index: Target,
round_proof: &FriQueryRoundTarget<D>,
params: &FriParams,
) where
C::Hasher: AlgebraicHasher<F>,
{
let mut n = degree_bits[0];
// 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, n, F::BITS);
let cap_index =
self.le_sum(x_index_bits[x_index_bits.len() - params.config.cap_height..].iter());
with_context!(
self,
"check FRI initial proof",
self.batch_fri_verify_initial_proof::<C::Hasher>(
degree_bits,
instance,
&x_index_bits,
&round_proof.initial_trees_proof,
initial_merkle_caps,
cap_index
)
);
// `subgroup_x` is `subgroup[x_index]`, i.e., the actual field element in the domain.
let mut subgroup_x = with_context!(self, "compute x from its index", {
let g = self.constant(F::coset_shift());
let phi = F::primitive_root_of_unity(n);
let phi = self.exp_from_bits_const_base(phi, x_index_bits.iter().rev());
self.mul(g, phi)
});
let mut batch_index = 0;
// 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.batch_fri_combine_initial(
instance,
batch_index,
&round_proof.initial_trees_proof,
challenges.fri_alpha,
subgroup_x,
&precomputed_reduced_evals[batch_index],
params,
)
);
batch_index += 1;
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());
self.connect_extension(new_eval, old_eval);
// Infer P(y) from {P(x)}_{x^arity=y}.
old_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],
)
);
with_context!(
self,
"verify FRI round Merkle proof.",
self.verify_merkle_proof_to_cap_with_cap_index::<C::Hasher>(
flatten_target(evals),
&coset_index_bits,
cap_index,
&proof.commit_phase_merkle_caps[i],
&round_proof.steps[i].merkle_proof,
)
);
// Update the point x to x^arity.
subgroup_x = self.exp_power_of_2(subgroup_x, arity_bits);
x_index_bits = coset_index_bits;
n -= arity_bits;
if batch_index < degree_bits.len() && n == degree_bits[batch_index] {
let subgroup_x_init = with_context!(self, "compute init x from its index", {
let g = self.constant(F::coset_shift());
let phi = F::primitive_root_of_unity(n);
let phi = self.exp_from_bits_const_base(phi, x_index_bits.iter().rev());
self.mul(g, phi)
});
let eval = self.batch_fri_combine_initial(
instance,
batch_index,
&round_proof.initial_trees_proof,
challenges.fri_alpha,
subgroup_x_init,
&precomputed_reduced_evals[batch_index],
params,
);
old_eval = self.mul_extension(old_eval, challenges.fri_betas[i]);
old_eval = self.add_extension(old_eval, eval);
batch_index += 1;
}
}
// 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);
}
}

View File

@ -0,0 +1,251 @@
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use anyhow::ensure;
use itertools::Itertools;
use plonky2_field::extension::{flatten, Extendable, FieldExtension};
use plonky2_field::types::Field;
use crate::fri::proof::{FriChallenges, FriInitialTreeProof, FriProof, FriQueryRound};
use crate::fri::structure::{FriBatchInfo, FriInstanceInfo, FriOpenings};
use crate::fri::validate_shape::validate_batch_fri_proof_shape;
use crate::fri::verifier::{
compute_evaluation, fri_verify_proof_of_work, PrecomputedReducedOpenings,
};
use crate::fri::FriParams;
use crate::hash::hash_types::RichField;
use crate::hash::merkle_proofs::{verify_batch_merkle_proof_to_cap, verify_merkle_proof_to_cap};
use crate::hash::merkle_tree::MerkleCap;
use crate::plonk::config::{GenericConfig, Hasher};
use crate::util::reducing::ReducingFactor;
use crate::util::reverse_bits;
pub fn verify_batch_fri_proof<
F: RichField + Extendable<D>,
C: GenericConfig<D, F = F>,
const D: usize,
>(
degree_bits: &[usize],
instances: &[FriInstanceInfo<F, D>],
openings: &[FriOpenings<F, D>],
challenges: &FriChallenges<F, D>,
initial_merkle_cap: &[MerkleCap<F, C::Hasher>],
proof: &FriProof<F, C::Hasher, D>,
params: &FriParams,
) -> anyhow::Result<()> {
validate_batch_fri_proof_shape::<F, C, D>(proof, instances, params)?;
// Check PoW.
fri_verify_proof_of_work(challenges.fri_pow_response, &params.config)?;
// Check that parameters are coherent.
ensure!(
params.config.num_query_rounds == proof.query_round_proofs.len(),
"Number of query rounds does not match config."
);
let mut precomputed_reduced_evals = Vec::with_capacity(openings.len());
for opn in openings {
let pre = PrecomputedReducedOpenings::from_os_and_alpha(opn, challenges.fri_alpha);
precomputed_reduced_evals.push(pre);
}
let degree_bits = degree_bits
.iter()
.map(|d| d + params.config.rate_bits)
.collect_vec();
for (&x_index, round_proof) in challenges
.fri_query_indices
.iter()
.zip(&proof.query_round_proofs)
{
batch_fri_verifier_query_round::<F, C, D>(
&degree_bits,
instances,
challenges,
&precomputed_reduced_evals,
initial_merkle_cap,
proof,
x_index,
round_proof,
params,
)?;
}
Ok(())
}
fn batch_fri_verify_initial_proof<F: RichField + Extendable<D>, H: Hasher<F>, const D: usize>(
degree_bits: &[usize],
instances: &[FriInstanceInfo<F, D>],
x_index: usize,
proof: &FriInitialTreeProof<F, H>,
initial_merkle_caps: &[MerkleCap<F, H>],
) -> anyhow::Result<()> {
for (oracle_index, ((evals, merkle_proof), cap)) in proof
.evals_proofs
.iter()
.zip(initial_merkle_caps)
.enumerate()
{
let leaves = instances
.iter()
.scan(0, |leaf_index, inst| {
let num_polys = inst.oracles[oracle_index].num_polys;
let leaves = (*leaf_index..*leaf_index + num_polys)
.map(|idx| evals[idx])
.collect::<Vec<_>>();
*leaf_index += num_polys;
Some(leaves)
})
.collect::<Vec<_>>();
verify_batch_merkle_proof_to_cap::<F, H>(&leaves, degree_bits, x_index, cap, merkle_proof)?;
}
Ok(())
}
fn batch_fri_combine_initial<
F: RichField + Extendable<D>,
C: GenericConfig<D, F = F>,
const D: usize,
>(
instances: &[FriInstanceInfo<F, D>],
index: usize,
proof: &FriInitialTreeProof<F, C::Hasher>,
alpha: F::Extension,
subgroup_x: F,
precomputed_reduced_evals: &PrecomputedReducedOpenings<F, D>,
params: &FriParams,
) -> F::Extension {
assert!(D > 1, "Not implemented for D=1.");
let subgroup_x = F::Extension::from_basefield(subgroup_x);
let mut alpha = ReducingFactor::new(alpha);
let mut sum = F::Extension::ZERO;
for (batch, reduced_openings) in instances[index]
.batches
.iter()
.zip(&precomputed_reduced_evals.reduced_openings_at_point)
{
let FriBatchInfo { point, polynomials } = batch;
let evals = polynomials
.iter()
.map(|p| {
let poly_blinding = instances[index].oracles[p.oracle_index].blinding;
let salted = params.hiding && poly_blinding;
proof.unsalted_eval(p.oracle_index, p.polynomial_index, salted)
})
.map(F::Extension::from_basefield);
let reduced_evals = alpha.reduce(evals);
let numerator = reduced_evals - *reduced_openings;
let denominator = subgroup_x - *point;
sum = alpha.shift(sum);
sum += numerator / denominator;
}
sum
}
fn batch_fri_verifier_query_round<
F: RichField + Extendable<D>,
C: GenericConfig<D, F = F>,
const D: usize,
>(
degree_bits: &[usize],
instances: &[FriInstanceInfo<F, D>],
challenges: &FriChallenges<F, D>,
precomputed_reduced_evals: &[PrecomputedReducedOpenings<F, D>],
initial_merkle_caps: &[MerkleCap<F, C::Hasher>],
proof: &FriProof<F, C::Hasher, D>,
mut x_index: usize,
round_proof: &FriQueryRound<F, C::Hasher, D>,
params: &FriParams,
) -> anyhow::Result<()> {
batch_fri_verify_initial_proof::<F, C::Hasher, D>(
degree_bits,
instances,
x_index,
&round_proof.initial_trees_proof,
initial_merkle_caps,
)?;
let mut n = degree_bits[0];
// `subgroup_x` is `subgroup[x_index]`, i.e., the actual field element in the domain.
let mut subgroup_x = F::MULTIPLICATIVE_GROUP_GENERATOR
* F::primitive_root_of_unity(n).exp_u64(reverse_bits(x_index, n) as u64);
let mut batch_index = 0;
// 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 = batch_fri_combine_initial::<F, C, D>(
instances,
batch_index,
&round_proof.initial_trees_proof,
challenges.fri_alpha,
subgroup_x,
&precomputed_reduced_evals[batch_index],
params,
);
batch_index += 1;
for (i, &arity_bits) in params.reduction_arity_bits.iter().enumerate() {
let arity = 1 << arity_bits;
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 = x_index >> arity_bits;
let x_index_within_coset = x_index & (arity - 1);
// Check consistency with our old evaluation from the previous round.
ensure!(evals[x_index_within_coset] == old_eval);
old_eval = compute_evaluation(
subgroup_x,
x_index_within_coset,
arity_bits,
evals,
challenges.fri_betas[i],
);
verify_merkle_proof_to_cap::<F, C::Hasher>(
flatten(evals),
coset_index,
&proof.commit_phase_merkle_caps[i],
&round_proof.steps[i].merkle_proof,
)?;
// Update the point x to x^arity.
subgroup_x = subgroup_x.exp_power_of_2(arity_bits);
x_index = coset_index;
n -= arity_bits;
if batch_index < degree_bits.len() && n == degree_bits[batch_index] {
let subgroup_x_init = F::MULTIPLICATIVE_GROUP_GENERATOR
* F::primitive_root_of_unity(n).exp_u64(reverse_bits(x_index, n) as u64);
let eval = batch_fri_combine_initial::<F, C, D>(
instances,
batch_index,
&round_proof.initial_trees_proof,
challenges.fri_alpha,
subgroup_x_init,
&precomputed_reduced_evals[batch_index],
params,
);
old_eval = old_eval * challenges.fri_betas[i] + eval;
batch_index += 1;
}
}
assert_eq!(
batch_index,
instances.len(),
"Wrong number of folded instances."
);
// Final check of FRI. After all the reductions, we check that the final polynomial is equal
// to the one sent by the prover.
ensure!(
proof.final_poly.eval(subgroup_x.into()) == old_eval,
"Final polynomial evaluation is invalid."
);
Ok(())
}

View File

@ -17,7 +17,7 @@ pub mod prover;
pub mod recursive_verifier;
pub mod reduction_strategies;
pub mod structure;
mod validate_shape;
pub(crate) mod validate_shape;
pub mod verifier;
pub mod witness_util;

View File

@ -111,7 +111,7 @@ impl<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>
}
}
fn lde_values(
pub(crate) fn lde_values(
polynomials: &[PolynomialCoeffs<F>],
rate_bits: usize,
blinding: bool,

View File

@ -62,7 +62,7 @@ pub fn fri_proof<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const
}
}
type FriCommitedTrees<F, C, const D: usize> = (
pub(crate) type FriCommitedTrees<F, C, const D: usize> = (
Vec<MerkleTree<F, <C as GenericConfig<D>>::Hasher>>,
PolynomialCoeffs<<F as Extendable<D>>::Extension>,
);
@ -113,7 +113,11 @@ fn fri_committed_trees<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>,
}
/// Performs the proof-of-work (a.k.a. grinding) step of the FRI protocol. Returns the PoW witness.
fn fri_proof_of_work<F: RichField + Extendable<D>, C: GenericConfig<D, F = F>, const D: usize>(
pub(crate) fn fri_proof_of_work<
F: RichField + Extendable<D>,
C: GenericConfig<D, F = F>,
const D: usize,
>(
challenger: &mut Challenger<F, C::Hasher>,
config: &FriConfig,
) -> F {

View File

@ -25,7 +25,7 @@ use crate::with_context;
impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
/// Computes P'(x^arity) from {P(x*g^i)}_(i=0..arity), where g is a `arity`-th root of unity
/// and P' is the FRI reduced polynomial.
fn compute_evaluation(
pub(crate) fn compute_evaluation(
&mut self,
x: Target,
x_index_within_coset_bits: &[BoolTarget],
@ -58,7 +58,7 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
/// Make sure we have enough wires and routed wires to do the FRI checks efficiently. This check
/// isn't required -- without it we'd get errors elsewhere in the stack -- but just gives more
/// helpful errors.
fn check_recursion_config(&self, max_fri_arity_bits: usize) {
pub(crate) fn check_recursion_config(&self, max_fri_arity_bits: usize) {
let random_access = RandomAccessGate::<F, D>::new_from_config(
&self.config,
max_fri_arity_bits.max(self.config.fri_config.cap_height),
@ -91,7 +91,11 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
);
}
fn fri_verify_proof_of_work(&mut self, fri_pow_response: Target, config: &FriConfig) {
pub(crate) fn fri_verify_proof_of_work(
&mut self,
fri_pow_response: Target,
config: &FriConfig,
) {
self.assert_leading_zeros(
fri_pow_response,
config.proof_of_work_bits + (64 - F::order().bits()) as u32,
@ -372,7 +376,7 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
/// Thus ambiguous elements contribute a negligible amount to soundness error.
///
/// Here we compare the probabilities as a sanity check, to verify the claim above.
fn assert_noncanonical_indices_ok(config: &FriConfig) {
pub(crate) fn assert_noncanonical_indices_ok(config: &FriConfig) {
let num_ambiguous_elems = u64::MAX - F::ORDER + 1;
let query_error = config.rate();
let p_ambiguous = (num_ambiguous_elems as f64) / (F::ORDER as f64);
@ -459,12 +463,12 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
/// For each opening point, holds the reduced (by `alpha`) evaluations of each polynomial that's
/// opened at that point.
#[derive(Clone)]
struct PrecomputedReducedOpeningsTarget<const D: usize> {
reduced_openings_at_point: Vec<ExtensionTarget<D>>,
pub(crate) struct PrecomputedReducedOpeningsTarget<const D: usize> {
pub(crate) reduced_openings_at_point: Vec<ExtensionTarget<D>>,
}
impl<const D: usize> PrecomputedReducedOpeningsTarget<D> {
fn from_os_and_alpha<F: RichField + Extendable<D>>(
pub(crate) fn from_os_and_alpha<F: RichField + Extendable<D>>(
openings: &FriOpeningsTarget<D>,
alpha: ExtensionTarget<D>,
builder: &mut CircuitBuilder<F, D>,

View File

@ -10,7 +10,7 @@ use crate::hash::hash_types::RichField;
use crate::iop::ext_target::ExtensionTarget;
/// Describes an instance of a FRI-based batch opening.
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct FriInstanceInfo<F: RichField + Extendable<D>, const D: usize> {
/// The oracles involved, not counting oracles created during the commit phase.
pub oracles: Vec<FriOracleInfo>,
@ -34,7 +34,7 @@ pub struct FriOracleInfo {
}
/// A batch of openings at a particular point.
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct FriBatchInfo<F: RichField + Extendable<D>, const D: usize> {
pub point: F::Extension,
pub polynomials: Vec<FriPolynomialInfo>,

View File

@ -1,3 +1,6 @@
#[cfg(not(feature = "std"))]
use alloc::vec;
use anyhow::ensure;
use crate::field::extension::Extendable;
@ -13,6 +16,18 @@ pub(crate) fn validate_fri_proof_shape<F, C, const D: usize>(
instance: &FriInstanceInfo<F, D>,
params: &FriParams,
) -> anyhow::Result<()>
where
F: RichField + Extendable<D>,
C: GenericConfig<D, F = F>,
{
validate_batch_fri_proof_shape::<F, C, D>(proof, &[instance.clone()], params)
}
pub(crate) fn validate_batch_fri_proof_shape<F, C, const D: usize>(
proof: &FriProof<F, C::Hasher, D>,
instances: &[FriInstanceInfo<F, D>],
params: &FriParams,
) -> anyhow::Result<()>
where
F: RichField + Extendable<D>,
C: GenericConfig<D, F = F>,
@ -35,13 +50,16 @@ where
steps,
} = query_round;
ensure!(initial_trees_proof.evals_proofs.len() == instance.oracles.len());
for ((leaf, merkle_proof), oracle) in initial_trees_proof
.evals_proofs
.iter()
.zip(&instance.oracles)
{
ensure!(leaf.len() == oracle.num_polys + salt_size(oracle.blinding && params.hiding));
let oracle_count = initial_trees_proof.evals_proofs.len();
let mut leaf_len = vec![0; oracle_count];
for inst in instances {
ensure!(oracle_count == inst.oracles.len());
for (i, oracle) in inst.oracles.iter().enumerate() {
leaf_len[i] += oracle.num_polys + salt_size(oracle.blinding && params.hiding);
}
}
for (i, (leaf, merkle_proof)) in initial_trees_proof.evals_proofs.iter().enumerate() {
ensure!(leaf.len() == leaf_len[i]);
ensure!(merkle_proof.len() + cap_height == params.lde_bits());
}

View File

@ -0,0 +1,338 @@
#[cfg(not(feature = "std"))]
use alloc::vec;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use itertools::Itertools;
use crate::hash::hash_types::{RichField, NUM_HASH_OUT_ELTS};
use crate::hash::merkle_proofs::MerkleProof;
use crate::hash::merkle_tree::{
capacity_up_to_mut, fill_digests_buf, merkle_tree_prove, MerkleCap,
};
use crate::plonk::config::{GenericHashOut, Hasher};
use crate::util::log2_strict;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct BatchMerkleTree<F: RichField, H: Hasher<F>> {
/// The data stored in the Merkle tree leaves.
pub leaves: Vec<Vec<Vec<F>>>,
/// Merkle tree node hashes, analogous to `digests` in `MerkleTree`.
pub digests: Vec<H::Hash>,
/// Represents the roots of the Merkle tree. This allows for using any layer as the root of the tree.
pub cap: MerkleCap<F, H>,
/// Represents the heights at which leaves reside within the tree.
pub leaf_heights: Vec<usize>,
}
impl<F: RichField, H: Hasher<F>> BatchMerkleTree<F, H> {
/// Each element in the `leaves` vector represents a matrix (a vector of vectors).
/// The height of each matrix should be a power of two.
/// The `leaves` vector should be sorted by matrix height, from tallest to shortest, with no duplicate heights.
pub fn new(mut leaves: Vec<Vec<Vec<F>>>, cap_height: usize) -> Self {
assert!(!leaves.is_empty());
assert!(leaves.iter().all(|leaf| leaf.len().is_power_of_two()));
assert!(leaves
.windows(2)
.all(|pair| { pair[0].len() > pair[1].len() }));
let last_leaves_cap_height = log2_strict(leaves.last().unwrap().len());
assert!(
cap_height <= last_leaves_cap_height,
"cap_height={} should be at most last_leaves_cap_height={}",
cap_height,
last_leaves_cap_height
);
let mut leaf_heights = Vec::with_capacity(leaves.len());
let leaves_len = leaves[0].len();
let num_digests = 2 * (leaves_len - (1 << cap_height));
let mut digests = Vec::with_capacity(num_digests);
let digests_buf = capacity_up_to_mut(&mut digests, num_digests);
let mut digests_buf_pos = 0;
let mut cap = vec![];
let dummy_leaves = vec![vec![F::ZERO]; 1 << cap_height];
leaves.push(dummy_leaves);
for window in leaves.windows(2) {
let cur = &window[0];
let next = &window[1];
let cur_leaf_len = cur.len();
let next_cap_len = next.len();
let next_cap_height = log2_strict(next_cap_len);
leaf_heights.push(log2_strict(cur_leaf_len));
let num_tmp_digests = 2 * (cur_leaf_len - next_cap_len);
if cur_leaf_len == leaves_len {
// The bottom leaf layer
cap = Vec::with_capacity(next_cap_len);
let tmp_cap_buf = capacity_up_to_mut(&mut cap, next_cap_len);
fill_digests_buf::<F, H>(
&mut digests_buf[digests_buf_pos..(digests_buf_pos + num_tmp_digests)],
tmp_cap_buf,
&cur[..],
next_cap_height,
);
} else {
// The rest leaf layers
let new_leaves: Vec<Vec<F>> = cap
.iter()
.enumerate()
.map(|(i, cap_hash)| {
let mut new_hash = Vec::with_capacity(NUM_HASH_OUT_ELTS + cur[i].len());
new_hash.extend(&cap_hash.to_vec());
new_hash.extend(&cur[i]);
new_hash
})
.collect();
cap.clear();
cap.reserve_exact(next_cap_len);
let tmp_cap_buf = capacity_up_to_mut(&mut cap, next_cap_len);
fill_digests_buf::<F, H>(
&mut digests_buf[digests_buf_pos..(digests_buf_pos + num_tmp_digests)],
tmp_cap_buf,
&new_leaves[..],
next_cap_height,
);
}
unsafe {
// SAFETY: `fill_digests_buf` and `cap` initialized the spare capacity up to
// `num_digests` and `len_cap`, resp.
cap.set_len(next_cap_len);
}
digests_buf_pos += num_tmp_digests;
}
unsafe {
// SAFETY: `fill_digests_buf` and `cap` initialized the spare capacity up to
// `num_digests` and `len_cap`, resp.
digests.set_len(num_digests);
}
// remove dummy leaves
leaves.pop();
Self {
leaves,
digests,
cap: MerkleCap(cap),
leaf_heights,
}
}
/// Create a Merkle proof from a leaf index.
pub fn open_batch(&self, leaf_index: usize) -> MerkleProof<F, H> {
let mut digests_buf_pos = 0;
let initial_leaf_height = log2_strict(self.leaves[0].len());
let mut siblings = vec![];
let mut cap_heights = self.leaf_heights.clone();
cap_heights.push(log2_strict(self.cap.len()));
for window in cap_heights.windows(2) {
let cur_cap_height = window[0];
let next_cap_height = window[1];
let num_digests: usize = 2 * ((1 << cur_cap_height) - (1 << next_cap_height));
siblings.extend::<Vec<_>>(merkle_tree_prove::<F, H>(
leaf_index >> (initial_leaf_height - cur_cap_height),
1 << cur_cap_height,
next_cap_height,
&self.digests[digests_buf_pos..digests_buf_pos + num_digests],
));
digests_buf_pos += num_digests;
}
MerkleProof { siblings }
}
pub fn values(&self, leaf_index: usize) -> Vec<Vec<F>> {
let leaves_cap_height = log2_strict(self.leaves[0].len());
self.leaves
.iter()
.zip(&self.leaf_heights)
.map(|(leaves, cap_height)| {
leaves[leaf_index >> (leaves_cap_height - cap_height)].clone()
})
.collect_vec()
}
}
#[cfg(test)]
mod tests {
#[cfg(not(feature = "std"))]
use alloc::vec;
use anyhow::Result;
use plonky2_field::goldilocks_field::GoldilocksField;
use plonky2_field::types::Field;
use super::*;
use crate::hash::merkle_proofs::verify_batch_merkle_proof_to_cap;
use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig};
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
type H = <C as GenericConfig<D>>::Hasher;
#[test]
fn commit_single() -> Result<()> {
// mat_1 = [
// 0 1
// 2 1
// 2 2
// 0 0
// ]
let mat_1 = vec![
vec![F::ZERO, F::ONE],
vec![F::TWO, F::ONE],
vec![F::TWO, F::TWO],
vec![F::ZERO, F::ZERO],
];
let fmt: BatchMerkleTree<GoldilocksField, H> = BatchMerkleTree::new(vec![mat_1], 0);
let mat_1_leaf_hashes = [
H::hash_or_noop(&[F::ZERO, F::ONE]),
H::hash_or_noop(&[F::TWO, F::ONE]),
H::hash_or_noop(&[F::TWO, F::TWO]),
H::hash_or_noop(&[F::ZERO, F::ZERO]),
];
assert_eq!(mat_1_leaf_hashes[0..2], fmt.digests[0..2]);
assert_eq!(mat_1_leaf_hashes[2..4], fmt.digests[4..6]);
let layer_1 = [
H::two_to_one(mat_1_leaf_hashes[0], mat_1_leaf_hashes[1]),
H::two_to_one(mat_1_leaf_hashes[2], mat_1_leaf_hashes[3]),
];
assert_eq!(layer_1, fmt.digests[2..4]);
let root = H::two_to_one(layer_1[0], layer_1[1]);
assert_eq!(fmt.cap.flatten(), root.to_vec());
let proof = fmt.open_batch(2);
assert_eq!(proof.siblings, [mat_1_leaf_hashes[3], layer_1[0]]);
let opened_values = fmt.values(2);
assert_eq!(opened_values, [vec![F::TWO, F::TWO]]);
verify_batch_merkle_proof_to_cap(&opened_values, &fmt.leaf_heights, 2, &fmt.cap, &proof)?;
Ok(())
}
#[test]
fn commit_mixed() -> Result<()> {
// mat_1 = [
// 0 1
// 2 1
// 2 2
// 0 0
// ]
let mat_1 = vec![
vec![F::ZERO, F::ONE],
vec![F::TWO, F::ONE],
vec![F::TWO, F::TWO],
vec![F::ZERO, F::ZERO],
];
// mat_2 = [
// 1 2 1
// 0 2 2
// ]
let mat_2 = vec![vec![F::ONE, F::TWO, F::ONE], vec![F::ZERO, F::TWO, F::TWO]];
let fmt: BatchMerkleTree<GoldilocksField, H> =
BatchMerkleTree::new(vec![mat_1, mat_2.clone()], 0);
let mat_1_leaf_hashes = [
H::hash_or_noop(&[F::ZERO, F::ONE]),
H::hash_or_noop(&[F::TWO, F::ONE]),
H::hash_or_noop(&[F::TWO, F::TWO]),
H::hash_or_noop(&[F::ZERO, F::ZERO]),
];
assert_eq!(mat_1_leaf_hashes, fmt.digests[0..4]);
let hidden_layer = [
H::two_to_one(mat_1_leaf_hashes[0], mat_1_leaf_hashes[1]).to_vec(),
H::two_to_one(mat_1_leaf_hashes[2], mat_1_leaf_hashes[3]).to_vec(),
];
let new_leaves = hidden_layer
.iter()
.zip(mat_2.iter())
.map(|(row1, row2)| {
let mut new_row = row1.clone();
new_row.extend_from_slice(row2);
new_row
})
.collect::<Vec<Vec<F>>>();
let layer_1 = [
H::hash_or_noop(&new_leaves[0]),
H::hash_or_noop(&new_leaves[1]),
];
assert_eq!(layer_1, fmt.digests[4..]);
let root = H::two_to_one(layer_1[0], layer_1[1]);
assert_eq!(fmt.cap.flatten(), root.to_vec());
let proof = fmt.open_batch(1);
assert_eq!(proof.siblings, [mat_1_leaf_hashes[0], layer_1[1]]);
let opened_values = fmt.values(1);
assert_eq!(
opened_values,
[vec![F::TWO, F::ONE], vec![F::ONE, F::TWO, F::ONE]]
);
verify_batch_merkle_proof_to_cap(&opened_values, &fmt.leaf_heights, 1, &fmt.cap, &proof)?;
Ok(())
}
#[test]
fn test_batch_merkle_trees() -> Result<()> {
let leaves_1 = crate::hash::merkle_tree::tests::random_data::<F>(1024, 7);
let leaves_2 = crate::hash::merkle_tree::tests::random_data::<F>(64, 3);
let leaves_3 = crate::hash::merkle_tree::tests::random_data::<F>(32, 100);
let fmt: BatchMerkleTree<GoldilocksField, H> =
BatchMerkleTree::new(vec![leaves_1, leaves_2, leaves_3], 3);
for index in [0, 1023, 512, 255] {
let proof = fmt.open_batch(index);
let opened_values = fmt.values(index);
verify_batch_merkle_proof_to_cap(
&opened_values,
&fmt.leaf_heights,
index,
&fmt.cap,
&proof,
)?;
}
Ok(())
}
#[test]
fn test_batch_merkle_trees_cap_at_leaves_height() -> Result<()> {
let leaves_1 = crate::hash::merkle_tree::tests::random_data::<F>(16, 7);
let fmt: BatchMerkleTree<GoldilocksField, H> = BatchMerkleTree::new(vec![leaves_1], 4);
for index in 0..16 {
let proof = fmt.open_batch(index);
let opened_values = fmt.values(index);
verify_batch_merkle_proof_to_cap(
&opened_values,
&fmt.leaf_heights,
index,
&fmt.cap,
&proof,
)?;
}
Ok(())
}
}

View File

@ -12,7 +12,7 @@ use crate::hash::merkle_tree::MerkleCap;
use crate::iop::target::{BoolTarget, Target};
use crate::plonk::circuit_builder::CircuitBuilder;
use crate::plonk::circuit_data::VerifierCircuitTarget;
use crate::plonk::config::{AlgebraicHasher, Hasher};
use crate::plonk::config::{AlgebraicHasher, GenericHashOut, Hasher};
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(bound = "")]
@ -57,19 +57,48 @@ pub fn verify_merkle_proof_to_cap<F: RichField, H: Hasher<F>>(
merkle_cap: &MerkleCap<F, H>,
proof: &MerkleProof<F, H>,
) -> Result<()> {
let mut index = leaf_index;
let mut current_digest = H::hash_or_noop(&leaf_data);
for &sibling_digest in proof.siblings.iter() {
let bit = index & 1;
index >>= 1;
verify_batch_merkle_proof_to_cap(
&[leaf_data.clone()],
&[proof.siblings.len()],
leaf_index,
merkle_cap,
proof,
)
}
/// Verifies that the given leaf data is present at the given index in the Field Merkle tree with the
/// given cap.
pub fn verify_batch_merkle_proof_to_cap<F: RichField, H: Hasher<F>>(
leaf_data: &[Vec<F>],
leaf_heights: &[usize],
mut leaf_index: usize,
merkle_cap: &MerkleCap<F, H>,
proof: &MerkleProof<F, H>,
) -> Result<()> {
assert_eq!(leaf_data.len(), leaf_heights.len());
let mut current_digest = H::hash_or_noop(&leaf_data[0]);
let mut current_height = leaf_heights[0];
let mut leaf_data_index = 1;
for &sibling_digest in &proof.siblings {
let bit = leaf_index & 1;
leaf_index >>= 1;
current_digest = if bit == 1 {
H::two_to_one(sibling_digest, current_digest)
} else {
H::two_to_one(current_digest, sibling_digest)
};
current_height -= 1;
if leaf_data_index < leaf_heights.len() && current_height == leaf_heights[leaf_data_index] {
let mut new_leaves = current_digest.to_vec();
new_leaves.extend_from_slice(&leaf_data[leaf_data_index]);
current_digest = H::hash_or_noop(&new_leaves);
leaf_data_index += 1;
}
}
assert_eq!(leaf_data_index, leaf_data.len());
ensure!(
current_digest == merkle_cap.0[index],
current_digest == merkle_cap.0[leaf_index],
"Invalid Merkle proof."
);
@ -151,6 +180,62 @@ impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
}
}
/// 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`.
pub(crate) fn verify_batch_merkle_proof_to_cap_with_cap_index<H: AlgebraicHasher<F>>(
&mut self,
leaf_data: &[Vec<Target>],
leaf_heights: &[usize],
leaf_index_bits: &[BoolTarget],
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[0].clone());
debug_assert_eq!(state.elements.len(), NUM_HASH_OUT_ELTS);
let mut current_height = leaf_heights[0];
let mut leaf_data_index = 1;
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,
};
current_height -= 1;
if leaf_data_index < leaf_heights.len()
&& current_height == leaf_heights[leaf_data_index]
{
let mut new_leaves = state.elements.to_vec();
new_leaves.extend_from_slice(&leaf_data[leaf_data_index]);
state = self.hash_or_noop::<H>(new_leaves);
leaf_data_index += 1;
}
}
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(),
);
self.connect(result, state.elements[i]);
}
}
pub fn connect_hashes(&mut self, x: HashOutTarget, y: HashOutTarget) {
for i in 0..NUM_HASH_OUT_ELTS {
self.connect(x.elements[i], y.elements[i]);

View File

@ -71,7 +71,7 @@ impl<F: RichField, H: Hasher<F>> Default for MerkleTree<F, H> {
}
}
fn capacity_up_to_mut<T>(v: &mut Vec<T>, len: usize) -> &mut [MaybeUninit<T>] {
pub(crate) fn capacity_up_to_mut<T>(v: &mut Vec<T>, len: usize) -> &mut [MaybeUninit<T>] {
assert!(v.capacity() >= len);
let v_ptr = v.as_mut_ptr().cast::<MaybeUninit<T>>();
unsafe {
@ -83,7 +83,7 @@ fn capacity_up_to_mut<T>(v: &mut Vec<T>, len: usize) -> &mut [MaybeUninit<T>] {
}
}
fn fill_subtree<F: RichField, H: Hasher<F>>(
pub(crate) fn fill_subtree<F: RichField, H: Hasher<F>>(
digests_buf: &mut [MaybeUninit<H::Hash>],
leaves: &[Vec<F>],
) -> H::Hash {
@ -112,7 +112,7 @@ fn fill_subtree<F: RichField, H: Hasher<F>>(
}
}
fn fill_digests_buf<F: RichField, H: Hasher<F>>(
pub(crate) fn fill_digests_buf<F: RichField, H: Hasher<F>>(
digests_buf: &mut [MaybeUninit<H::Hash>],
cap_buf: &mut [MaybeUninit<H::Hash>],
leaves: &[Vec<F>],
@ -148,6 +148,47 @@ fn fill_digests_buf<F: RichField, H: Hasher<F>>(
);
}
pub(crate) fn merkle_tree_prove<F: RichField, H: Hasher<F>>(
leaf_index: usize,
leaves_len: usize,
cap_height: usize,
digests: &[H::Hash],
) -> Vec<H::Hash> {
let num_layers = log2_strict(leaves_len) - cap_height;
debug_assert_eq!(leaf_index >> (cap_height + num_layers), 0);
let digest_len = 2 * (leaves_len - (1 << cap_height));
assert_eq!(digest_len, digests.len());
let digest_tree: &[H::Hash] = {
let tree_index = leaf_index >> num_layers;
let tree_len = digest_len >> cap_height;
&digests[tree_len * tree_index..tree_len * (tree_index + 1)]
};
// Mask out high bits to get the index within the sub-tree.
let mut pair_index = leaf_index & ((1 << num_layers) - 1);
(0..num_layers)
.map(|i| {
let parity = pair_index & 1;
pair_index >>= 1;
// The layers' data is interleaved as follows:
// [layer 0, layer 1, layer 0, layer 2, layer 0, layer 1, layer 0, layer 3, ...].
// Each of the above is a pair of siblings.
// `pair_index` is the index of the pair within layer `i`.
// The index of that the pair within `digests` is
// `pair_index * 2 ** (i + 1) + (2 ** i - 1)`.
let siblings_index = (pair_index << (i + 1)) + (1 << i) - 1;
// We have an index for the _pair_, but we want the index of the _sibling_.
// Double the pair index to get the index of the left sibling. Conditionally add `1`
// if we are to retrieve the right sibling.
let sibling_index = 2 * siblings_index + (1 - parity);
digest_tree[sibling_index]
})
.collect()
}
impl<F: RichField, H: Hasher<F>> MerkleTree<F, H> {
pub fn new(leaves: Vec<Vec<F>>, cap_height: usize) -> Self {
let log2_leaves_len = log2_strict(leaves.len());
@ -189,43 +230,15 @@ impl<F: RichField, H: Hasher<F>> MerkleTree<F, H> {
/// Create a Merkle proof from a leaf index.
pub fn prove(&self, leaf_index: usize) -> MerkleProof<F, H> {
let cap_height = log2_strict(self.cap.len());
let num_layers = log2_strict(self.leaves.len()) - cap_height;
debug_assert_eq!(leaf_index >> (cap_height + num_layers), 0);
let digest_tree = {
let tree_index = leaf_index >> num_layers;
let tree_len = self.digests.len() >> cap_height;
&self.digests[tree_len * tree_index..tree_len * (tree_index + 1)]
};
// Mask out high bits to get the index within the sub-tree.
let mut pair_index = leaf_index & ((1 << num_layers) - 1);
let siblings = (0..num_layers)
.map(|i| {
let parity = pair_index & 1;
pair_index >>= 1;
// The layers' data is interleaved as follows:
// [layer 0, layer 1, layer 0, layer 2, layer 0, layer 1, layer 0, layer 3, ...].
// Each of the above is a pair of siblings.
// `pair_index` is the index of the pair within layer `i`.
// The index of that the pair within `digests` is
// `pair_index * 2 ** (i + 1) + (2 ** i - 1)`.
let siblings_index = (pair_index << (i + 1)) + (1 << i) - 1;
// We have an index for the _pair_, but we want the index of the _sibling_.
// Double the pair index to get the index of the left sibling. Conditionally add `1`
// if we are to retrieve the right sibling.
let sibling_index = 2 * siblings_index + (1 - parity);
digest_tree[sibling_index]
})
.collect();
let siblings =
merkle_tree_prove::<F, H>(leaf_index, self.leaves.len(), cap_height, &self.digests);
MerkleProof { siblings }
}
}
#[cfg(test)]
mod tests {
pub(crate) mod tests {
use anyhow::Result;
use super::*;
@ -233,7 +246,7 @@ mod tests {
use crate::hash::merkle_proofs::verify_merkle_proof_to_cap;
use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig};
fn random_data<F: RichField>(n: usize, k: usize) -> Vec<Vec<F>> {
pub(crate) fn random_data<F: RichField>(n: usize, k: usize) -> Vec<Vec<F>> {
(0..n).map(|_| F::rand_vec(k)).collect()
}

View File

@ -2,6 +2,7 @@
//! as well as specific hash functions implementation.
mod arch;
pub mod batch_merkle_tree;
pub mod hash_types;
pub mod hashing;
pub mod keccak;

View File

@ -11,6 +11,7 @@ pub extern crate alloc;
#[doc(inline)]
pub use plonky2_field as field;
pub mod batch_fri;
pub mod fri;
pub mod gadgets;
pub mod gates;