mirror of
https://github.com/logos-storage/plonky2.git
synced 2026-01-02 13:53:07 +00:00
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:
parent
4090881d5c
commit
0e363e16a3
4
plonky2/src/batch_fri/mod.rs
Normal file
4
plonky2/src/batch_fri/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod oracle;
|
||||
pub mod prover;
|
||||
pub mod recursive_verifier;
|
||||
pub mod verifier;
|
||||
610
plonky2/src/batch_fri/oracle.rs
Normal file
610
plonky2/src/batch_fri/oracle.rs
Normal 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>(
|
||||
°ree_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>(
|
||||
°ree_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(())
|
||||
}
|
||||
}
|
||||
475
plonky2/src/batch_fri/prover.rs
Normal file
475
plonky2/src/batch_fri/prover.rs
Normal 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
332
plonky2/src/batch_fri/recursive_verifier.rs
Normal file
332
plonky2/src/batch_fri/recursive_verifier.rs
Normal 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, ¶ms.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>(
|
||||
°ree_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(¶ms.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);
|
||||
}
|
||||
}
|
||||
251
plonky2/src/batch_fri/verifier.rs
Normal file
251
plonky2/src/batch_fri/verifier.rs
Normal 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, ¶ms.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>(
|
||||
°ree_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(())
|
||||
}
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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>,
|
||||
|
||||
@ -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>,
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
|
||||
338
plonky2/src/hash/batch_merkle_tree.rs
Normal file
338
plonky2/src/hash/batch_merkle_tree.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
@ -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]);
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user