diff --git a/plonky2/src/batch_fri/mod.rs b/plonky2/src/batch_fri/mod.rs new file mode 100644 index 0000000000..8d7cc5e1f5 --- /dev/null +++ b/plonky2/src/batch_fri/mod.rs @@ -0,0 +1,4 @@ +pub mod oracle; +pub mod prover; +pub mod recursive_verifier; +pub mod verifier; diff --git a/plonky2/src/batch_fri/oracle.rs b/plonky2/src/batch_fri/oracle.rs new file mode 100644 index 0000000000..71d808ed9b --- /dev/null +++ b/plonky2/src/batch_fri/oracle.rs @@ -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, C: GenericConfig, const D: usize> +{ + pub polynomials: Vec>, + pub batch_merkle_tree: BatchMerkleTree, + // The degree bits of each polynomial group. + pub degree_bits: Vec, + pub rate_bits: usize, + pub blinding: bool, +} + +impl, C: GenericConfig, const D: usize> + BatchFriOracle +{ + /// Creates a list polynomial commitment for the polynomials interpolating the values in `values`. + pub fn from_values( + values: Vec>, + rate_bits: usize, + blinding: bool, + cap_height: usize, + timing: &mut TimingTree, + fft_root_table: &[Option<&FftRootTable>], + ) -> Self { + let coeffs = timed!( + timing, + "IFFT", + values.into_par_iter().map(|v| v.ifft()).collect::>() + ); + + 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>, + rate_bits: usize, + blinding: bool, + cap_height: usize, + timing: &mut TimingTree, + fft_root_table: &[Option<&FftRootTable>], + ) -> 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::::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], + oracles: &[&Self], + challenger: &mut Challenger, + fri_params: &FriParams, + timing: &mut TimingTree, + ) -> FriProof { + assert_eq!(degree_bits.len(), instances.len()); + assert!(D > 1, "Not implemented for D=1."); + let alpha = challenger.get_extension_challenge::(); + 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::( + &oracles + .iter() + .map(|o| &o.batch_merkle_tree) + .collect::>(), + 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

( + &self, + degree_bits_index: usize, + index_start: usize, + step: usize, + slice_start: usize, + slice_len: usize, + ) -> Vec

+ where + P: PackedField, + { + 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 = >::F; + type H = >::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 = 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::::new(); + challenger.observe_cap(&trace_oracle.batch_merkle_tree.cap); + let zeta = challenger.get_extension_challenge::(); + let eta = challenger.get_extension_challenge::(); + 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::::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::().eval(zeta); + let poly0_eta = poly0.to_extension::().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::().eval(zeta); + let poly11_zeta = poly1_1.to_extension::().eval(zeta); + let poly11_eta = poly1_1.to_extension::().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::().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::( + &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::( + °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::::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::::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::( + °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::(); + let proof = prove::(&data.prover_only, &data.common, pw, &mut timing)?; + data.verify(proof.clone())?; + + Ok(()) + } +} diff --git a/plonky2/src/batch_fri/prover.rs b/plonky2/src/batch_fri/prover.rs new file mode 100644 index 0000000000..770c2c2285 --- /dev/null +++ b/plonky2/src/batch_fri/prover.rs @@ -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, C: GenericConfig, const D: usize>( + initial_merkle_trees: &[&BatchMerkleTree], + lde_polynomial_coeffs: PolynomialCoeffs, + lde_polynomial_values: &[PolynomialValues], + challenger: &mut Challenger, + fri_params: &FriParams, + timing: &mut TimingTree, +) -> FriProof { + 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::( + 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::(challenger, &fri_params.config) + ); + + // Query phase + let query_round_proofs = batch_fri_prover_query_rounds::( + 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, + C: GenericConfig, + const D: usize, +>( + mut final_coeffs: PolynomialCoeffs, + values: &[PolynomialValues], + challenger: &mut Challenger, + fri_params: &FriParams, +) -> FriCommitedTrees { + 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::::new(chunked_values, fri_params.config.cap_height); + + challenger.observe_cap(&tree.cap); + trees.push(tree); + + let beta = challenger.get_extension_challenge::(); + // P(x) = sum_{i>(), + ); + 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::>(), + ); + 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, + C: GenericConfig, + const D: usize, +>( + initial_merkle_trees: &[&BatchMerkleTree], + trees: &[MerkleTree], + challenger: &mut Challenger, + n: usize, + fri_params: &FriParams, +) -> Vec> { + 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::( + initial_merkle_trees, + trees, + x_index, + fri_params, + ) + }) + .collect() +} + +fn batch_fri_prover_query_round< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + initial_merkle_trees: &[&BatchMerkleTree], + trees: &[MerkleTree], + mut x_index: usize, + fri_params: &FriParams, +) -> FriQueryRound { + 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::>(), + t.open_batch(x_index), + ) + }) + .collect::>(); + 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 = >::F; + type H = >::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 = 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::::new(); + challenger.observe_cap(&polynomial_batch.batch_merkle_tree.cap); + let _alphas = challenger.get_n_challenges(2); + let zeta = challenger.get_extension_challenge::(); + challenger.observe_extension_element::(&poly.to_extension::().eval(zeta)); + let mut verifier_challenger = challenger.clone(); + + let fri_instance: FriInstanceInfo = 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::(); + + let composition_poly = poly.mul_extension::(>::Extension::ONE); + let mut quotient = composition_poly.divide_by_linear(zeta); + quotient.coeffs.push(>::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::( + &[&polynomial_batch.batch_merkle_tree], + lde_final_poly, + &[lde_final_values], + &mut challenger, + &fri_params, + &mut timing, + ); + + let fri_challenges = verifier_challenger.fri_challenges::( + &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::().eval(zeta)], + }; + verify_batch_fri_proof::( + &[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 = 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::::new(); + challenger.observe_cap(&trace_oracle.batch_merkle_tree.cap); + let _alphas = challenger.get_n_challenges(2); + let zeta = challenger.get_extension_challenge::(); + let poly0 = &trace_oracle.polynomials[0]; + let poly1 = &trace_oracle.polynomials[1]; + let poly2 = &trace_oracle.polynomials[2]; + challenger.observe_extension_element::(&poly0.to_extension::().eval(zeta)); + challenger.observe_extension_element::(&poly1.to_extension::().eval(zeta)); + challenger.observe_extension_element::(&poly2.to_extension::().eval(zeta)); + let mut verifier_challenger = challenger.clone(); + + let _alpha = challenger.get_extension_challenge::(); + + let composition_poly = poly0.mul_extension::(>::Extension::ONE); + let mut quotient = composition_poly.divide_by_linear(zeta); + quotient.coeffs.push(>::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::(>::Extension::ONE); + let mut quotient = composition_poly.divide_by_linear(zeta); + quotient.coeffs.push(>::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::(>::Extension::ONE); + let mut quotient = composition_poly.divide_by_linear(zeta); + quotient.coeffs.push(>::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::( + &[&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 { + 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::( + &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::().eval(zeta)], + }], + }; + let fri_opening_batch_1 = FriOpenings { + batches: vec![FriOpeningBatch { + values: vec![poly1.to_extension::().eval(zeta)], + }], + }; + let fri_opening_batch_2 = FriOpenings { + batches: vec![FriOpeningBatch { + values: vec![poly2.to_extension::().eval(zeta)], + }], + }; + let fri_openings = vec![ + fri_opening_batch_0, + fri_opening_batch_1, + fri_opening_batch_2, + ]; + + verify_batch_fri_proof::( + &[k0, k1, k2], + &fri_instances, + &fri_openings, + &fri_challenges, + &[trace_oracle.batch_merkle_tree.cap], + &proof, + &fri_params, + ) + } +} diff --git a/plonky2/src/batch_fri/recursive_verifier.rs b/plonky2/src/batch_fri/recursive_verifier.rs new file mode 100644 index 0000000000..95731ba403 --- /dev/null +++ b/plonky2/src/batch_fri/recursive_verifier.rs @@ -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, const D: usize> CircuitBuilder { + pub fn verify_batch_fri_proof>( + &mut self, + degree_bits: &[usize], + instance: &[FriInstanceInfoTarget], + openings: &[FriOpeningsTarget], + challenges: &FriChallengesTarget, + initial_merkle_caps: &[MerkleCapTarget], + proof: &FriProofTarget, + params: &FriParams, + ) where + C::Hasher: AlgebraicHasher, + { + 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::( + °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>( + &mut self, + degree_bits: &[usize], + instances: &[FriInstanceInfoTarget], + 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::>(); + *leaf_index += num_polys; + Some(leaves) + }) + .collect::>(); + + with_context!( + self, + &format!("verify {i}'th initial Merkle proof"), + self.verify_batch_merkle_proof_to_cap_with_cap_index::( + &leaves, + degree_bits, + x_index_bits, + cap_index, + cap, + merkle_proof + ) + ); + } + } + + fn batch_fri_combine_initial( + &mut self, + instance: &[FriInstanceInfoTarget], + index: usize, + proof: &FriInitialTreeProofTarget, + alpha: ExtensionTarget, + subgroup_x: Target, + precomputed_reduced_evals: &PrecomputedReducedOpeningsTarget, + params: &FriParams, + ) -> ExtensionTarget { + 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>( + &mut self, + degree_bits: &[usize], + instance: &[FriInstanceInfoTarget], + challenges: &FriChallengesTarget, + precomputed_reduced_evals: &[PrecomputedReducedOpeningsTarget], + initial_merkle_caps: &[MerkleCapTarget], + proof: &FriProofTarget, + x_index: Target, + round_proof: &FriQueryRoundTarget, + params: &FriParams, + ) where + C::Hasher: AlgebraicHasher, + { + 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::( + 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::( + 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); + } +} diff --git a/plonky2/src/batch_fri/verifier.rs b/plonky2/src/batch_fri/verifier.rs new file mode 100644 index 0000000000..b7dd552b70 --- /dev/null +++ b/plonky2/src/batch_fri/verifier.rs @@ -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, + C: GenericConfig, + const D: usize, +>( + degree_bits: &[usize], + instances: &[FriInstanceInfo], + openings: &[FriOpenings], + challenges: &FriChallenges, + initial_merkle_cap: &[MerkleCap], + proof: &FriProof, + params: &FriParams, +) -> anyhow::Result<()> { + validate_batch_fri_proof_shape::(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::( + °ree_bits, + instances, + challenges, + &precomputed_reduced_evals, + initial_merkle_cap, + proof, + x_index, + round_proof, + params, + )?; + } + + Ok(()) +} + +fn batch_fri_verify_initial_proof, H: Hasher, const D: usize>( + degree_bits: &[usize], + instances: &[FriInstanceInfo], + x_index: usize, + proof: &FriInitialTreeProof, + initial_merkle_caps: &[MerkleCap], +) -> 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::>(); + *leaf_index += num_polys; + Some(leaves) + }) + .collect::>(); + + verify_batch_merkle_proof_to_cap::(&leaves, degree_bits, x_index, cap, merkle_proof)?; + } + + Ok(()) +} + +fn batch_fri_combine_initial< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + instances: &[FriInstanceInfo], + index: usize, + proof: &FriInitialTreeProof, + alpha: F::Extension, + subgroup_x: F, + precomputed_reduced_evals: &PrecomputedReducedOpenings, + 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, + C: GenericConfig, + const D: usize, +>( + degree_bits: &[usize], + instances: &[FriInstanceInfo], + challenges: &FriChallenges, + precomputed_reduced_evals: &[PrecomputedReducedOpenings], + initial_merkle_caps: &[MerkleCap], + proof: &FriProof, + mut x_index: usize, + round_proof: &FriQueryRound, + params: &FriParams, +) -> anyhow::Result<()> { + batch_fri_verify_initial_proof::( + 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::( + 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::( + 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::( + 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(()) +} diff --git a/plonky2/src/fri/mod.rs b/plonky2/src/fri/mod.rs index 3445ada8f4..5f18600c3c 100644 --- a/plonky2/src/fri/mod.rs +++ b/plonky2/src/fri/mod.rs @@ -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; diff --git a/plonky2/src/fri/oracle.rs b/plonky2/src/fri/oracle.rs index 64dcbc6095..3e1ac781b1 100644 --- a/plonky2/src/fri/oracle.rs +++ b/plonky2/src/fri/oracle.rs @@ -111,7 +111,7 @@ impl, C: GenericConfig, const D: usize> } } - fn lde_values( + pub(crate) fn lde_values( polynomials: &[PolynomialCoeffs], rate_bits: usize, blinding: bool, diff --git a/plonky2/src/fri/prover.rs b/plonky2/src/fri/prover.rs index 4fb15614eb..b385fb5369 100644 --- a/plonky2/src/fri/prover.rs +++ b/plonky2/src/fri/prover.rs @@ -62,7 +62,7 @@ pub fn fri_proof, C: GenericConfig, const } } -type FriCommitedTrees = ( +pub(crate) type FriCommitedTrees = ( Vec>::Hasher>>, PolynomialCoeffs<>::Extension>, ); @@ -113,7 +113,11 @@ fn fri_committed_trees, C: GenericConfig, } /// Performs the proof-of-work (a.k.a. grinding) step of the FRI protocol. Returns the PoW witness. -fn fri_proof_of_work, C: GenericConfig, const D: usize>( +pub(crate) fn fri_proof_of_work< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( challenger: &mut Challenger, config: &FriConfig, ) -> F { diff --git a/plonky2/src/fri/recursive_verifier.rs b/plonky2/src/fri/recursive_verifier.rs index 47ae08f2c9..16e02f6a81 100644 --- a/plonky2/src/fri/recursive_verifier.rs +++ b/plonky2/src/fri/recursive_verifier.rs @@ -25,7 +25,7 @@ use crate::with_context; impl, const D: usize> CircuitBuilder { /// 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, const D: usize> CircuitBuilder { /// 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::::new_from_config( &self.config, max_fri_arity_bits.max(self.config.fri_config.cap_height), @@ -91,7 +91,11 @@ impl, const D: usize> CircuitBuilder { ); } - 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, const D: usize> CircuitBuilder { /// 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, const D: usize> CircuitBuilder { /// For each opening point, holds the reduced (by `alpha`) evaluations of each polynomial that's /// opened at that point. #[derive(Clone)] -struct PrecomputedReducedOpeningsTarget { - reduced_openings_at_point: Vec>, +pub(crate) struct PrecomputedReducedOpeningsTarget { + pub(crate) reduced_openings_at_point: Vec>, } impl PrecomputedReducedOpeningsTarget { - fn from_os_and_alpha>( + pub(crate) fn from_os_and_alpha>( openings: &FriOpeningsTarget, alpha: ExtensionTarget, builder: &mut CircuitBuilder, diff --git a/plonky2/src/fri/structure.rs b/plonky2/src/fri/structure.rs index 7a580e50c6..249d4f3e9f 100644 --- a/plonky2/src/fri/structure.rs +++ b/plonky2/src/fri/structure.rs @@ -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, const D: usize> { /// The oracles involved, not counting oracles created during the commit phase. pub oracles: Vec, @@ -34,7 +34,7 @@ pub struct FriOracleInfo { } /// A batch of openings at a particular point. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct FriBatchInfo, const D: usize> { pub point: F::Extension, pub polynomials: Vec, diff --git a/plonky2/src/fri/validate_shape.rs b/plonky2/src/fri/validate_shape.rs index 526da8f776..be675ed61b 100644 --- a/plonky2/src/fri/validate_shape.rs +++ b/plonky2/src/fri/validate_shape.rs @@ -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( instance: &FriInstanceInfo, params: &FriParams, ) -> anyhow::Result<()> +where + F: RichField + Extendable, + C: GenericConfig, +{ + validate_batch_fri_proof_shape::(proof, &[instance.clone()], params) +} + +pub(crate) fn validate_batch_fri_proof_shape( + proof: &FriProof, + instances: &[FriInstanceInfo], + params: &FriParams, +) -> anyhow::Result<()> where F: RichField + Extendable, C: GenericConfig, @@ -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()); } diff --git a/plonky2/src/hash/batch_merkle_tree.rs b/plonky2/src/hash/batch_merkle_tree.rs new file mode 100644 index 0000000000..eaa49977d5 --- /dev/null +++ b/plonky2/src/hash/batch_merkle_tree.rs @@ -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> { + /// The data stored in the Merkle tree leaves. + pub leaves: Vec>>, + + /// Merkle tree node hashes, analogous to `digests` in `MerkleTree`. + pub digests: Vec, + + /// Represents the roots of the Merkle tree. This allows for using any layer as the root of the tree. + pub cap: MerkleCap, + + /// Represents the heights at which leaves reside within the tree. + pub leaf_heights: Vec, +} + +impl> BatchMerkleTree { + /// 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>>, 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::( + &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> = 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::( + &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 { + 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::>(merkle_tree_prove::( + 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> { + 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 = >::F; + type H = >::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 = 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 = + 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::>>(); + 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::(1024, 7); + let leaves_2 = crate::hash::merkle_tree::tests::random_data::(64, 3); + let leaves_3 = crate::hash::merkle_tree::tests::random_data::(32, 100); + + let fmt: BatchMerkleTree = + 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::(16, 7); + + let fmt: BatchMerkleTree = 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(()) + } +} diff --git a/plonky2/src/hash/merkle_proofs.rs b/plonky2/src/hash/merkle_proofs.rs index d773b11aaa..95a347ae55 100644 --- a/plonky2/src/hash/merkle_proofs.rs +++ b/plonky2/src/hash/merkle_proofs.rs @@ -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>( merkle_cap: &MerkleCap, proof: &MerkleProof, ) -> 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>( + leaf_data: &[Vec], + leaf_heights: &[usize], + mut leaf_index: usize, + merkle_cap: &MerkleCap, + proof: &MerkleProof, +) -> 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, const D: usize> CircuitBuilder { } } + /// 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>( + &mut self, + leaf_data: &[Vec], + 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::(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::(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::(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]); diff --git a/plonky2/src/hash/merkle_tree.rs b/plonky2/src/hash/merkle_tree.rs index 10962727c6..31bcf5e37c 100644 --- a/plonky2/src/hash/merkle_tree.rs +++ b/plonky2/src/hash/merkle_tree.rs @@ -71,7 +71,7 @@ impl> Default for MerkleTree { } } -fn capacity_up_to_mut(v: &mut Vec, len: usize) -> &mut [MaybeUninit] { +pub(crate) fn capacity_up_to_mut(v: &mut Vec, len: usize) -> &mut [MaybeUninit] { assert!(v.capacity() >= len); let v_ptr = v.as_mut_ptr().cast::>(); unsafe { @@ -83,7 +83,7 @@ fn capacity_up_to_mut(v: &mut Vec, len: usize) -> &mut [MaybeUninit] { } } -fn fill_subtree>( +pub(crate) fn fill_subtree>( digests_buf: &mut [MaybeUninit], leaves: &[Vec], ) -> H::Hash { @@ -112,7 +112,7 @@ fn fill_subtree>( } } -fn fill_digests_buf>( +pub(crate) fn fill_digests_buf>( digests_buf: &mut [MaybeUninit], cap_buf: &mut [MaybeUninit], leaves: &[Vec], @@ -148,6 +148,47 @@ fn fill_digests_buf>( ); } +pub(crate) fn merkle_tree_prove>( + leaf_index: usize, + leaves_len: usize, + cap_height: usize, + digests: &[H::Hash], +) -> Vec { + 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> MerkleTree { pub fn new(leaves: Vec>, cap_height: usize) -> Self { let log2_leaves_len = log2_strict(leaves.len()); @@ -189,43 +230,15 @@ impl> MerkleTree { /// Create a Merkle proof from a leaf index. pub fn prove(&self, leaf_index: usize) -> MerkleProof { 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::(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(n: usize, k: usize) -> Vec> { + pub(crate) fn random_data(n: usize, k: usize) -> Vec> { (0..n).map(|_| F::rand_vec(k)).collect() } diff --git a/plonky2/src/hash/mod.rs b/plonky2/src/hash/mod.rs index c98c57069c..0e4bb8a59c 100644 --- a/plonky2/src/hash/mod.rs +++ b/plonky2/src/hash/mod.rs @@ -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; diff --git a/plonky2/src/lib.rs b/plonky2/src/lib.rs index 8955194fc5..8772ecfc0e 100644 --- a/plonky2/src/lib.rs +++ b/plonky2/src/lib.rs @@ -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;