From dc77c77f2b06500e16ad4d7f1c2b057903602eed Mon Sep 17 00:00:00 2001 From: Linda Guiga <101227802+LindaGuiga@users.noreply.github.com> Date: Fri, 3 May 2024 14:10:38 +0100 Subject: [PATCH 01/21] Changes to prepare for dummy segment removal in zk_evm's continuations (#1587) * Make select_proof_with_pis public * Add API methods for dummy segment removal * Change visibility of dummy_circuit * Return actual verifier data * Fix Clippy * Apply comments --------- Co-authored-by: Hamy Ratoanina --- plonky2/src/plonk/circuit_builder.rs | 8 +++++ .../conditional_recursive_verifier.rs | 33 +++++++++++-------- plonky2/src/recursion/dummy_circuit.rs | 26 +++++++++------ 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/plonky2/src/plonk/circuit_builder.rs b/plonky2/src/plonk/circuit_builder.rs index 5f153c637e..eaa11f2b35 100644 --- a/plonky2/src/plonk/circuit_builder.rs +++ b/plonky2/src/plonk/circuit_builder.rs @@ -531,6 +531,14 @@ impl, const D: usize> CircuitBuilder { } } + /// If `condition`, enforces that two routable `Target` values are equal, using Plonk's permutation argument. + pub fn conditional_assert_eq(&mut self, condition: Target, x: Target, y: Target) { + let zero = self.zero(); + let diff = self.sub(x, y); + let constr = self.mul(condition, diff); + self.connect(constr, zero); + } + /// Enforces that a routable `Target` value is 0, using Plonk's permutation argument. pub fn assert_zero(&mut self, x: Target) { let zero = self.zero(); diff --git a/plonky2/src/recursion/conditional_recursive_verifier.rs b/plonky2/src/recursion/conditional_recursive_verifier.rs index 2dc6966a20..c41a9b583a 100644 --- a/plonky2/src/recursion/conditional_recursive_verifier.rs +++ b/plonky2/src/recursion/conditional_recursive_verifier.rs @@ -34,18 +34,8 @@ impl, const D: usize> CircuitBuilder { { let selected_proof = self.select_proof_with_pis(condition, proof_with_pis0, proof_with_pis1); - let selected_verifier_data = VerifierCircuitTarget { - constants_sigmas_cap: self.select_cap( - condition, - &inner_verifier_data0.constants_sigmas_cap, - &inner_verifier_data1.constants_sigmas_cap, - ), - circuit_digest: self.select_hash( - condition, - inner_verifier_data0.circuit_digest, - inner_verifier_data1.circuit_digest, - ), - }; + let selected_verifier_data = + self.select_verifier_data(condition, inner_verifier_data0, inner_verifier_data1); self.verify_proof::(&selected_proof, &selected_verifier_data, inner_common_data); } @@ -75,7 +65,7 @@ impl, const D: usize> CircuitBuilder { } /// Computes `if b { proof_with_pis0 } else { proof_with_pis1 }`. - fn select_proof_with_pis( + pub fn select_proof_with_pis( &mut self, b: BoolTarget, proof_with_pis0: &ProofWithPublicInputsTarget, @@ -179,6 +169,23 @@ impl, const D: usize> CircuitBuilder { .collect() } + /// Computes `if b { vk0 } else { vk1 }`. + pub fn select_verifier_data( + &mut self, + b: BoolTarget, + vk0: &VerifierCircuitTarget, + vk1: &VerifierCircuitTarget, + ) -> VerifierCircuitTarget { + VerifierCircuitTarget { + constants_sigmas_cap: self.select_cap( + b, + &vk0.constants_sigmas_cap, + &vk1.constants_sigmas_cap, + ), + circuit_digest: self.select_hash(b, vk0.circuit_digest, vk1.circuit_digest), + } + } + /// Computes `if b { os0 } else { os1 }`. fn select_opening_set( &mut self, diff --git a/plonky2/src/recursion/dummy_circuit.rs b/plonky2/src/recursion/dummy_circuit.rs index 75b3929e82..1dc872db5a 100644 --- a/plonky2/src/recursion/dummy_circuit.rs +++ b/plonky2/src/recursion/dummy_circuit.rs @@ -67,11 +67,7 @@ where /// Generate a proof for a dummy circuit. The `public_inputs` parameter let the caller specify /// certain public inputs (identified by their indices) which should be given specific values. /// The rest will default to zero. -pub(crate) fn dummy_proof< - F: RichField + Extendable, - C: GenericConfig, - const D: usize, ->( +pub fn dummy_proof, C: GenericConfig, const D: usize>( circuit: &CircuitData, nonzero_public_inputs: HashMap, ) -> anyhow::Result> @@ -86,11 +82,7 @@ where } /// Generate a circuit matching a given `CommonCircuitData`. -pub(crate) fn dummy_circuit< - F: RichField + Extendable, - C: GenericConfig, - const D: usize, ->( +pub fn dummy_circuit, C: GenericConfig, const D: usize>( common_data: &CommonCircuitData, ) -> CircuitData { let config = common_data.config.clone(); @@ -143,6 +135,20 @@ impl, const D: usize> CircuitBuilder { Ok((dummy_proof_with_pis_target, dummy_verifier_data_target)) } + + pub fn dummy_proof_and_constant_vk_no_generator + 'static>( + &mut self, + common_data: &CommonCircuitData, + ) -> anyhow::Result<(ProofWithPublicInputsTarget, VerifierCircuitTarget)> + where + C::Hasher: AlgebraicHasher, + { + let dummy_circuit = dummy_circuit::(common_data); + let dummy_proof_with_pis_target = self.add_virtual_proof_with_pis(common_data); + let dummy_verifier_data_target = self.constant_verifier_data(&dummy_circuit.verifier_only); + + Ok((dummy_proof_with_pis_target, dummy_verifier_data_target)) + } } #[derive(Debug)] From 430290fb76a8d27182751fba2071edff353fdd46 Mon Sep 17 00:00:00 2001 From: Daniel-Aaron-Bloom <76709210+Daniel-Aaron-Bloom@users.noreply.github.com> Date: Tue, 21 May 2024 13:14:19 -0700 Subject: [PATCH 02/21] fix: remove clippy unexpected_cfgs warning (#1588) Since `std` doesn't exist as a cfg, `not(std)` was always true, so this does not change any behavior. --- maybe_rayon/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maybe_rayon/src/lib.rs b/maybe_rayon/src/lib.rs index 942898492f..718b046f97 100644 --- a/maybe_rayon/src/lib.rs +++ b/maybe_rayon/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(std), no_std)] +#![no_std] #[cfg(not(feature = "parallel"))] extern crate alloc; From 15836d9d8c76c5e5efeafcdd10355a7db3109f22 Mon Sep 17 00:00:00 2001 From: Daniel-Aaron-Bloom <76709210+Daniel-Aaron-Bloom@users.noreply.github.com> Date: Tue, 28 May 2024 15:20:44 -0700 Subject: [PATCH 03/21] doc+fix: `clippy::doc-lazy-continuation` (#1594) --- plonky2/src/gates/selectors.rs | 2 ++ plonky2/src/plonk/vanishing_poly.rs | 1 + starky/src/cross_table_lookup.rs | 6 +++++- starky/src/prover.rs | 2 +- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/plonky2/src/gates/selectors.rs b/plonky2/src/gates/selectors.rs index c75f2e226e..9bd1217b50 100644 --- a/plonky2/src/gates/selectors.rs +++ b/plonky2/src/gates/selectors.rs @@ -42,9 +42,11 @@ pub enum LookupSelectors { /// Returns selector polynomials for each LUT. We have two constraint domains (remember that gates are stored upside down): /// - [last_lut_row, first_lut_row] (Sum and RE transition constraints), /// - [last_lu_row, last_lut_row - 1] (LDC column transition constraints). +/// /// We also add two more: /// - {first_lut_row + 1} where we check the initial values of sum and RE (which are 0), /// - {last_lu_row} where we check that the last value of LDC is 0. +/// /// Conceptually they're part of the selector ends lookups, but since we can have one polynomial for *all* LUTs it's here. pub(crate) fn selectors_lookup, const D: usize>( _gates: &[GateRef], diff --git a/plonky2/src/plonk/vanishing_poly.rs b/plonky2/src/plonk/vanishing_poly.rs index ef10108106..a5fac4ab05 100644 --- a/plonky2/src/plonk/vanishing_poly.rs +++ b/plonky2/src/plonk/vanishing_poly.rs @@ -329,6 +329,7 @@ pub(crate) fn eval_vanishing_poly_base_batch, const /// - RE ensures the well formation of lookup tables; /// - Sum is a running sum of m_i/(X - (input_i + a * output_i)) where (input_i, output_i) are input pairs in the lookup table (LUT); /// - LDC is a running sum of 1/(X - (input_i + a * output_i)) where (input_i, output_i) are input pairs that look in the LUT. +/// /// Sum and LDC are broken down in partial polynomials to lower the constraint degree, similarly to the permutation argument. /// They also share the same partial SLDC polynomials, so that the last SLDC value is Sum(end) - LDC(end). The final constraint /// Sum(end) = LDC(end) becomes simply SLDC(end) = 0, and we can remove the LDC initial constraint. diff --git a/starky/src/cross_table_lookup.rs b/starky/src/cross_table_lookup.rs index 29ae6cbe04..0fca49c994 100644 --- a/starky/src/cross_table_lookup.rs +++ b/starky/src/cross_table_lookup.rs @@ -19,7 +19,8 @@ //! - Z(gw) = Z(w) * combine(w) where combine(w) is the column combination at point w. //! - Z(g^(n-1)) = combine(1). //! - The verifier also checks that the product of looking table Z polynomials is equal -//! to the associated looked table Z polynomial. +//! to the associated looked table Z polynomial. +//! //! Note that the first two checks are written that way because Z polynomials are computed //! upside down for convenience. //! @@ -316,6 +317,7 @@ pub(crate) fn get_ctl_auxiliary_polys( /// - `cross_table_lookups` corresponds to all the cross-table lookups, i.e. the looked and looking tables, as described in `CrossTableLookup`. /// - `ctl_challenges` corresponds to the challenges used for CTLs. /// - `constraint_degree` is the maximal constraint degree for the table. +/// /// For each `CrossTableLookup`, and each looking/looked table, the partial products for the CTL are computed, and added to the said table's `CtlZData`. pub(crate) fn cross_table_lookup_data<'a, F: RichField, const D: usize, const N: usize>( trace_poly_values: &[Vec>; N], @@ -621,6 +623,7 @@ impl<'a, F: RichField + Extendable, const D: usize> /// Checks the cross-table lookup Z polynomials for each table: /// - Checks that the CTL `Z` partial sums are correctly updated. /// - Checks that the final value of the CTL sum is the combination of all STARKs' CTL polynomials. +/// /// CTL `Z` partial sums are upside down: the complete sum is on the first row, and /// the first term is on the last row. This allows the transition constraint to be: /// `combine(w) * (Z(w) - Z(gw)) = filter` where combine is called on the local row @@ -825,6 +828,7 @@ impl<'a, F: Field, const D: usize> CtlCheckVarsTarget { /// Circuit version of `eval_cross_table_lookup_checks`. Checks the cross-table lookup Z polynomials for each table: /// - Checks that the CTL `Z` partial sums are correctly updated. /// - Checks that the final value of the CTL sum is the combination of all STARKs' CTL polynomials. +/// /// CTL `Z` partial sums are upside down: the complete sum is on the first row, and /// the first term is on the last row. This allows the transition constraint to be: /// `combine(w) * (Z(w) - Z(gw)) = filter` where combine is called on the local row diff --git a/starky/src/prover.rs b/starky/src/prover.rs index 29b72521b0..18369594f6 100644 --- a/starky/src/prover.rs +++ b/starky/src/prover.rs @@ -92,7 +92,7 @@ where /// - all the required Merkle caps, /// - all the required polynomial and FRI argument openings. /// - individual `ctl_data` and common `ctl_challenges` if the STARK is part -/// of a multi-STARK system. +/// of a multi-STARK system. pub fn prove_with_commitment( stark: &S, config: &StarkConfig, From cfccc1b8b68d3c1a3e7f03599f83bd92af6502a2 Mon Sep 17 00:00:00 2001 From: nuno <41233686+qope@users.noreply.github.com> Date: Sat, 1 Jun 2024 15:15:26 +0200 Subject: [PATCH 04/21] change `set_stark_proof_target`'s witness to `WitnessWrite` (#1592) --- starky/src/recursive_verifier.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/starky/src/recursive_verifier.rs b/starky/src/recursive_verifier.rs index 83e39398b3..d6c2f9fec8 100644 --- a/starky/src/recursive_verifier.rs +++ b/starky/src/recursive_verifier.rs @@ -14,7 +14,7 @@ use plonky2::hash::hash_types::RichField; use plonky2::iop::challenger::RecursiveChallenger; use plonky2::iop::ext_target::ExtensionTarget; use plonky2::iop::target::Target; -use plonky2::iop::witness::Witness; +use plonky2::iop::witness::WitnessWrite; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::config::{AlgebraicHasher, GenericConfig}; use plonky2::util::reducing::ReducingFactorTarget; @@ -328,7 +328,7 @@ pub fn set_stark_proof_with_pis_target, W, const D ) where F: RichField + Extendable, C::Hasher: AlgebraicHasher, - W: Witness, + W: WitnessWrite, { let StarkProofWithPublicInputs { proof, @@ -357,7 +357,7 @@ pub fn set_stark_proof_target, W, const D: usize>( ) where F: RichField + Extendable, C::Hasher: AlgebraicHasher, - W: Witness, + W: WitnessWrite, { witness.set_cap_target(&proof_target.trace_cap, &proof.trace_cap); if let (Some(quotient_polys_cap_target), Some(quotient_polys_cap)) = From 8030ea43ff5aa57742221d24827c947f6d902d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20G=C3=B6rgens?= Date: Sat, 1 Jun 2024 21:15:53 +0800 Subject: [PATCH 05/21] Fix CTL generation of last row (#1585) Not all `correct` cross table lookups use padding for the last row. --- starky/src/lookup.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/starky/src/lookup.rs b/starky/src/lookup.rs index f260400bf9..bc079fb1a5 100644 --- a/starky/src/lookup.rs +++ b/starky/src/lookup.rs @@ -321,24 +321,16 @@ impl Column { /// Evaluate on a row of a table given in column-major form. pub(crate) fn eval_table(&self, table: &[PolynomialValues], row: usize) -> F { - let mut res = self - .linear_combination + self.linear_combination .iter() .map(|&(c, f)| table[c].values[row] * f) .sum::() - + self.constant; - - // If we access the next row at the last row, for sanity, we consider the next row's values to be 0. - // If the lookups are correctly written, the filter should be 0 in that case anyway. - if !self.next_row_linear_combination.is_empty() && row < table[0].values.len() - 1 { - res += self + + self .next_row_linear_combination .iter() - .map(|&(c, f)| table[c].values[row + 1] * f) - .sum::(); - } - - res + .map(|&(c, f)| table[c].values[(row + 1) % table[c].values.len()] * f) + .sum::() + + self.constant } /// Evaluates the column on all rows. From 42e048f45d74385127098aab6221f465d3f1ab47 Mon Sep 17 00:00:00 2001 From: nuno <41233686+qope@users.noreply.github.com> Date: Tue, 4 Jun 2024 20:10:46 +0200 Subject: [PATCH 06/21] Allow multiple extra_looking_sums for the same looked table (#1591) --- starky/src/cross_table_lookup.rs | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/starky/src/cross_table_lookup.rs b/starky/src/cross_table_lookup.rs index 0fca49c994..dd81443117 100644 --- a/starky/src/cross_table_lookup.rs +++ b/starky/src/cross_table_lookup.rs @@ -35,6 +35,7 @@ use core::fmt::Debug; use core::iter::once; use anyhow::{ensure, Result}; +use hashbrown::HashMap; use itertools::Itertools; use plonky2::field::extension::{Extendable, FieldExtension}; use plonky2::field::packed::PackedField; @@ -920,10 +921,11 @@ pub(crate) fn eval_cross_table_lookup_checks_circuit< } /// Verifies all cross-table lookups. +/// The key of `ctl_extra_looking_sums` is the corresponding CTL's position within `cross_table_lookups`. pub fn verify_cross_table_lookups, const D: usize, const N: usize>( cross_table_lookups: &[CrossTableLookup], ctl_zs_first: [Vec; N], - ctl_extra_looking_sums: Option<&[Vec]>, + ctl_extra_looking_sums: &HashMap>, config: &StarkConfig, ) -> Result<()> { let mut ctl_zs_openings = ctl_zs_first.iter().map(|v| v.iter()).collect::>(); @@ -935,6 +937,7 @@ pub fn verify_cross_table_lookups, const D: usize, }, ) in cross_table_lookups.iter().enumerate() { + let ctl_extra_looking_sum = ctl_extra_looking_sums.get(&index); // We want to iterate on each looking table only once. let mut filtered_looking_tables = vec![]; for table in looking_tables { @@ -950,8 +953,7 @@ pub fn verify_cross_table_lookups, const D: usize, .map(|&table| *ctl_zs_openings[table].next().unwrap()) .sum::() // Get elements looking into `looked_table` that are not associated to any STARK. - + ctl_extra_looking_sums - .map(|v| v[looked_table.table][c]).unwrap_or_default(); + + ctl_extra_looking_sum.map(|v| v[c]).unwrap_or_default(); // Get the looked table CTL polynomial opening. let looked_z = *ctl_zs_openings[looked_table.table].next().unwrap(); @@ -969,6 +971,7 @@ pub fn verify_cross_table_lookups, const D: usize, } /// Circuit version of `verify_cross_table_lookups`. Verifies all cross-table lookups. +/// The key of `ctl_extra_looking_sums` is the corresponding CTL's position within `cross_table_lookups`. pub fn verify_cross_table_lookups_circuit< F: RichField + Extendable, const D: usize, @@ -977,15 +980,19 @@ pub fn verify_cross_table_lookups_circuit< builder: &mut CircuitBuilder, cross_table_lookups: Vec>, ctl_zs_first: [Vec; N], - ctl_extra_looking_sums: Option<&[Vec]>, + ctl_extra_looking_sums: &HashMap>, inner_config: &StarkConfig, ) { let mut ctl_zs_openings = ctl_zs_first.iter().map(|v| v.iter()).collect::>(); - for CrossTableLookup { - looking_tables, - looked_table, - } in cross_table_lookups.into_iter() + for ( + index, + CrossTableLookup { + looking_tables, + looked_table, + }, + ) in cross_table_lookups.into_iter().enumerate() { + let ctl_extra_looking_sum = ctl_extra_looking_sums.get(&index); // We want to iterate on each looking table only once. let mut filtered_looking_tables = vec![]; for table in looking_tables { @@ -1002,9 +1009,7 @@ pub fn verify_cross_table_lookups_circuit< ); // Get elements looking into `looked_table` that are not associated to any STARK. - let extra_sum = ctl_extra_looking_sums - .map(|v| v[looked_table.table][c]) - .unwrap_or_default(); + let extra_sum = ctl_extra_looking_sum.map(|v| v[c]).unwrap_or_default(); looking_zs_sum = builder.add(looking_zs_sum, extra_sum); // Get the looked table CTL polynomial opening. @@ -1033,10 +1038,11 @@ pub mod debug_utils { type MultiSet = HashMap, Vec<(TableIdx, usize)>>; /// Check that the provided traces and cross-table lookups are consistent. + /// The key of `extra_looking_values` is the corresponding CTL's position within `cross_table_lookups`. pub fn check_ctls( trace_poly_values: &[Vec>], cross_table_lookups: &[CrossTableLookup], - extra_looking_values: &HashMap>>, + extra_looking_values: &HashMap>>, ) { for (i, ctl) in cross_table_lookups.iter().enumerate() { check_ctl(trace_poly_values, ctl, i, extra_looking_values.get(&i)); From 217b4a272ca0ab3b8ca1b3f79743c2e03a292b05 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Sat, 8 Jun 2024 12:01:43 -0400 Subject: [PATCH 07/21] Clarify zk usage with starky (#1596) --- starky/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/starky/README.md b/starky/README.md index bb4e2d8a92..3519b68696 100644 --- a/starky/README.md +++ b/starky/README.md @@ -1,3 +1,17 @@ +# Starky + +Starky is a FRI-based STARK implementation. + +It is built for speed, features highly efficient recursive verification through `plonky2` circuits and gadgets, and is +being used as backend proving system for the Polygon Zero Type-1 zkEVM. + +## Note on Zero-Knowledgeness + +While STARKs can be made Zero-Knowledge, the primary purpose of `starky` is to provide fast STARK proof generation. As such, +ZK is disabled by default on `starky`. Applications requiring their proof to be `zero-knowledge` would need to apply a +recursive wrapper on top of their STARK proof with the `zero_knowledge` parameter activated in their `CircuitConfig`. +See `plonky2` documentation for more info. + ## License Licensed under either of From 42a821dfde351e8579da6e8addbb440a30203f3a Mon Sep 17 00:00:00 2001 From: Hamy Ratoanina Date: Tue, 11 Jun 2024 17:58:41 -0400 Subject: [PATCH 08/21] Add row index to constraint failure message (#1598) --- starky/src/prover.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/starky/src/prover.rs b/starky/src/prover.rs index 18369594f6..53ec767e4d 100644 --- a/starky/src/prover.rs +++ b/starky/src/prover.rs @@ -549,6 +549,8 @@ fn check_constraints<'a, F, C, S, const D: usize>( C: GenericConfig, S: Stark, { + use core::any::type_name; + let degree = 1 << degree_bits; let rate_bits = 0; // Set this to higher value to check constraint degree. let total_num_helper_cols: usize = num_ctl_helper_cols.iter().sum(); @@ -656,11 +658,14 @@ fn check_constraints<'a, F, C, S, const D: usize>( .collect::>(); // Assert that all constraints evaluate to 0 over our subgroup. - for v in constraint_values { - assert!( - v.iter().all(|x| x.is_zero()), - "Constraint failed in {}", - core::any::type_name::() - ); + for (row, v) in constraint_values.iter().enumerate() { + for x in v.iter() { + assert!( + x.is_zero(), + "Constraint failed in {} at row {}", + type_name::(), + row + ) + } } } From ed6f452294fc296eee1f0e1896eaa7904ebfab94 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Thu, 13 Jun 2024 06:28:59 -0400 Subject: [PATCH 09/21] fix(field): reenable alloc for tests (#1601) --- field/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/field/src/lib.rs b/field/src/lib.rs index e21f042428..c713db8851 100644 --- a/field/src/lib.rs +++ b/field/src/lib.rs @@ -6,7 +6,7 @@ #![feature(specialization)] #![cfg_attr(target_arch = "x86_64", feature(stdarch_x86_avx512))] #![cfg_attr(not(test), no_std)] -#![cfg(not(test))] + extern crate alloc; pub(crate) mod arch; From 7ba3c1f298faddb99543b59fe24d898f092f78e8 Mon Sep 17 00:00:00 2001 From: Gio <102917377+gio256@users.noreply.github.com> Date: Thu, 13 Jun 2024 09:53:00 -0600 Subject: [PATCH 10/21] Add `Field::shifted_powers` and some iterator niceties (#1599) * Add Field::shifted_powers and iterator niceties * Remove comment --- field/src/types.rs | 48 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/field/src/types.rs b/field/src/types.rs index b2e43667c7..18c4b82d53 100644 --- a/field/src/types.rs +++ b/field/src/types.rs @@ -427,9 +427,13 @@ pub trait Field: } fn powers(&self) -> Powers { + self.shifted_powers(Self::ONE) + } + + fn shifted_powers(&self, start: Self) -> Powers { Powers { base: *self, - current: Self::ONE, + current: start, } } @@ -563,6 +567,7 @@ pub trait PrimeField64: PrimeField + Field64 { } /// An iterator over the powers of a certain base element `b`: `b^0, b^1, b^2, ...`. +#[must_use = "iterators are lazy and do nothing unless consumed"] #[derive(Clone, Debug)] pub struct Powers { base: F, @@ -577,6 +582,24 @@ impl Iterator for Powers { self.current *= self.base; Some(result) } + + fn size_hint(&self) -> (usize, Option) { + (usize::MAX, None) + } + + fn nth(&mut self, n: usize) -> Option { + let result = self.current * self.base.exp_u64(n.try_into().unwrap()); + self.current = result * self.base; + Some(result) + } + + fn last(self) -> Option { + panic!("called `Iterator::last()` on an infinite sequence") + } + + fn count(self) -> usize { + panic!("called `Iterator::count()` on an infinite sequence") + } } impl Powers { @@ -592,3 +615,26 @@ impl Powers { } } } + +#[cfg(test)] +mod tests { + use super::Field; + use crate::goldilocks_field::GoldilocksField; + + #[test] + fn test_powers_nth() { + type F = GoldilocksField; + + const N: usize = 10; + let powers_of_two: Vec = F::TWO.powers().take(N).collect(); + + for (n, &expect) in powers_of_two.iter().enumerate() { + let mut iter = F::TWO.powers(); + assert_eq!(iter.nth(n), Some(expect)); + + for &expect_next in &powers_of_two[n + 1..] { + assert_eq!(iter.next(), Some(expect_next)); + } + } + } +} From 4813d563d00731b6d4da137a2ab2e44da51becf7 Mon Sep 17 00:00:00 2001 From: Gio <102917377+gio256@users.noreply.github.com> Date: Sun, 16 Jun 2024 08:58:13 -0600 Subject: [PATCH 11/21] chore: replace stale link (#1602) --- plonky2/src/hash/arch/x86_64/poseidon_goldilocks_avx2_bmi2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plonky2/src/hash/arch/x86_64/poseidon_goldilocks_avx2_bmi2.rs b/plonky2/src/hash/arch/x86_64/poseidon_goldilocks_avx2_bmi2.rs index e56fea5ea7..7046a7fdd2 100644 --- a/plonky2/src/hash/arch/x86_64/poseidon_goldilocks_avx2_bmi2.rs +++ b/plonky2/src/hash/arch/x86_64/poseidon_goldilocks_avx2_bmi2.rs @@ -494,7 +494,7 @@ unsafe fn mds_multiply_and_add_round_const_s( // Fall through for MDS matrix multiplication on low 32 bits // This is a GCC _local label_. For details, see - // https://doc.rust-lang.org/beta/unstable-book/library-features/asm.html#labels + // https://doc.rust-lang.org/rust-by-example/unsafe/asm.html#labels // In short, the assembler makes sure to assign a unique name to replace `2:` with a unique // name, so the label does not clash with any compiler-generated label. `2:` can appear // multiple times; to disambiguate, we must refer to it as `2b` or `2f`, specifying the From 0c21c327dfb16f0a4fb5952bf1dfc6e401a0de3a Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:36:22 -0400 Subject: [PATCH 12/21] ci: add PR check job (#1604) --- .github/pr_checking.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/pr_checking.yml diff --git a/.github/pr_checking.yml b/.github/pr_checking.yml new file mode 100644 index 0000000000..5bed881a3a --- /dev/null +++ b/.github/pr_checking.yml @@ -0,0 +1,22 @@ +name: PR check + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +jobs: + title: + name: Validate PR + runs-on: ubuntu-latest + if: ${{ + github.event.pull_request.author_association != 'CONTRIBUTOR' && + github.event.pull_request.author_association != 'MEMBER' && + ( + contains(fromJSON(secrets.RESTRICTED_KEYWORDS), github.event.pull_request.title) || + contains(fromJSON(secrets.RESTRICTED_KEYWORDS), github.event.pull_request.description + ) }} + steps: + - run: gh pr close From 7ee5c5196c141cc85e8d4ec4aca44e950cbba674 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Wed, 26 Jun 2024 15:04:58 -0400 Subject: [PATCH 13/21] fix: Move PR check file into proper folder --- .github/{ => workflows}/pr_checking.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{ => workflows}/pr_checking.yml (100%) diff --git a/.github/pr_checking.yml b/.github/workflows/pr_checking.yml similarity index 100% rename from .github/pr_checking.yml rename to .github/workflows/pr_checking.yml From 25d062afebfe70a328c6c5bf2483fabf6a6d3918 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Wed, 26 Jun 2024 15:21:47 -0400 Subject: [PATCH 14/21] fix: fix pr_checking workflow --- .github/workflows/pr_checking.yml | 62 ++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/.github/workflows/pr_checking.yml b/.github/workflows/pr_checking.yml index 5bed881a3a..7799b94cc3 100644 --- a/.github/workflows/pr_checking.yml +++ b/.github/workflows/pr_checking.yml @@ -1,22 +1,56 @@ name: PR check on: - pull_request_target: - types: - - opened - - edited - - synchronize + pull_request: + types: [opened, reopened, synchronize] + +permissions: + pull-requests: write jobs: - title: + pr_check: name: Validate PR runs-on: ubuntu-latest - if: ${{ - github.event.pull_request.author_association != 'CONTRIBUTOR' && - github.event.pull_request.author_association != 'MEMBER' && - ( - contains(fromJSON(secrets.RESTRICTED_KEYWORDS), github.event.pull_request.title) || - contains(fromJSON(secrets.RESTRICTED_KEYWORDS), github.event.pull_request.description - ) }} steps: - - run: gh pr close + - name: Set up keywords + id: setup_keywords + run: echo "RESTRICTED_KEYWORDS=$(echo '${{ secrets.RESTRICTED_KEYWORDS }}' | jq -r '.[]' | tr '\n' ' ')" >> $GITHUB_ENV + + - name: Check for spam PR + id: check + run: | + # Initialize variables to track spam presence + title_is_spam=false + description_is_spam=false + + # Check title for spam + for keyword in $RESTRICTED_KEYWORDS; do + if echo "${{ github.event.pull_request.title }}" | grep -i -q "$keyword"; then + title_is_spam=true + break + fi + done + + # Check description for spam + for keyword in $RESTRICTED_KEYWORDS; do + if echo "${{ github.event.pull_request.body }}" | grep -i -q "$keyword"; then + description_is_spam=true + break + fi + done + + # Set the output based on the presence of spam + if [ "$title_is_spam" = true ] || [ "$description_is_spam" = true ]; then + echo "is_spam=true" >> $GITHUB_ENV + else + echo "is_spam=false" >> $GITHUB_ENV + fi + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Close PR if spam are found and author is not a contributor or member + if: ${{ env.is_spam == 'true' && github.event.pull_request.author_association != 'CONTRIBUTOR' && github.event.pull_request.author_association != 'MEMBER' && github.event.pull_request.author_association != 'OWNER' }} + run: gh pr close ${{ github.event.pull_request.number }} --comment "Spam detected" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From cedffae4f6071f6d3bb0b6a7828330f3d04405ae Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Fri, 28 Jun 2024 15:55:32 -0400 Subject: [PATCH 15/21] fix: get PR check workflow to work properly --- .github/workflows/pr_checking.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr_checking.yml b/.github/workflows/pr_checking.yml index 7799b94cc3..d155461067 100644 --- a/.github/workflows/pr_checking.yml +++ b/.github/workflows/pr_checking.yml @@ -50,7 +50,7 @@ jobs: uses: actions/checkout@v4 - name: Close PR if spam are found and author is not a contributor or member - if: ${{ env.is_spam == 'true' && github.event.pull_request.author_association != 'CONTRIBUTOR' && github.event.pull_request.author_association != 'MEMBER' && github.event.pull_request.author_association != 'OWNER' }} + if: ${{ env.is_spam == 'true' && github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' }} run: gh pr close ${{ github.event.pull_request.number }} --comment "Spam detected" env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From f5a5845bc8572a73dfcf7ee7fb2d76a16d426baa Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Thu, 4 Jul 2024 06:35:06 +0900 Subject: [PATCH 16/21] Observe public inputs (#1607) --- starky/src/get_challenges.rs | 2 ++ starky/src/prover.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/starky/src/get_challenges.rs b/starky/src/get_challenges.rs index 8000a9ef90..e8904ffcaf 100644 --- a/starky/src/get_challenges.rs +++ b/starky/src/get_challenges.rs @@ -157,6 +157,7 @@ where ignore_trace_cap: bool, config: &StarkConfig, ) -> StarkProofChallenges { + challenger.observe_elements(&self.public_inputs); self.proof .get_challenges(challenger, challenges, ignore_trace_cap, config) } @@ -302,6 +303,7 @@ impl StarkProofWithPublicInputsTarget { C: GenericConfig, C::Hasher: AlgebraicHasher, { + challenger.observe_elements(&self.public_inputs); self.proof .get_challenges::(builder, challenger, challenges, ignore_trace_cap, config) } diff --git a/starky/src/prover.rs b/starky/src/prover.rs index 53ec767e4d..36d544b624 100644 --- a/starky/src/prover.rs +++ b/starky/src/prover.rs @@ -71,6 +71,7 @@ where let trace_cap = trace_commitment.merkle_tree.cap.clone(); let mut challenger = Challenger::new(); + challenger.observe_elements(public_inputs); challenger.observe_cap(&trace_cap); prove_with_commitment( From 4090881d5c16e76050843354398bcd968b57d211 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Tue, 16 Jul 2024 07:28:16 +0900 Subject: [PATCH 17/21] chore: fix clippy (#1609) --- plonky2/benches/ffts.rs | 4 ++-- plonky2/benches/merkle.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plonky2/benches/ffts.rs b/plonky2/benches/ffts.rs index 4149c02d46..54e0a7396a 100644 --- a/plonky2/benches/ffts.rs +++ b/plonky2/benches/ffts.rs @@ -7,7 +7,7 @@ use plonky2::field::types::Field; use tynm::type_name; pub(crate) fn bench_ffts(c: &mut Criterion) { - let mut group = c.benchmark_group(&format!("fft<{}>", type_name::())); + let mut group = c.benchmark_group(format!("fft<{}>", type_name::())); for size_log in [13, 14, 15, 16] { let size = 1 << size_log; @@ -21,7 +21,7 @@ pub(crate) fn bench_ffts(c: &mut Criterion) { pub(crate) fn bench_ldes(c: &mut Criterion) { const RATE_BITS: usize = 3; - let mut group = c.benchmark_group(&format!("lde<{}>", type_name::())); + let mut group = c.benchmark_group(format!("lde<{}>", type_name::())); for size_log in [13, 14, 15, 16] { let orig_size = 1 << (size_log - RATE_BITS); diff --git a/plonky2/benches/merkle.rs b/plonky2/benches/merkle.rs index f9bae127fa..6230c13436 100644 --- a/plonky2/benches/merkle.rs +++ b/plonky2/benches/merkle.rs @@ -12,7 +12,7 @@ use tynm::type_name; const ELEMS_PER_LEAF: usize = 135; pub(crate) fn bench_merkle_tree>(c: &mut Criterion) { - let mut group = c.benchmark_group(&format!( + let mut group = c.benchmark_group(format!( "merkle-tree<{}, {}>", type_name::(), type_name::() From 0e363e16a37a2eacd3349946bd071a460485ad26 Mon Sep 17 00:00:00 2001 From: Sai <135601871+sai-deng@users.noreply.github.com> Date: Tue, 16 Jul 2024 06:43:07 +0800 Subject: [PATCH 18/21] 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> --- plonky2/src/batch_fri/mod.rs | 4 + plonky2/src/batch_fri/oracle.rs | 610 ++++++++++++++++++++ plonky2/src/batch_fri/prover.rs | 475 +++++++++++++++ plonky2/src/batch_fri/recursive_verifier.rs | 332 +++++++++++ plonky2/src/batch_fri/verifier.rs | 251 ++++++++ plonky2/src/fri/mod.rs | 2 +- plonky2/src/fri/oracle.rs | 2 +- plonky2/src/fri/prover.rs | 8 +- plonky2/src/fri/recursive_verifier.rs | 18 +- plonky2/src/fri/structure.rs | 4 +- plonky2/src/fri/validate_shape.rs | 32 +- plonky2/src/hash/batch_merkle_tree.rs | 338 +++++++++++ plonky2/src/hash/merkle_proofs.rs | 99 +++- plonky2/src/hash/merkle_tree.rs | 83 +-- plonky2/src/hash/mod.rs | 1 + plonky2/src/lib.rs | 1 + 16 files changed, 2198 insertions(+), 62 deletions(-) create mode 100644 plonky2/src/batch_fri/mod.rs create mode 100644 plonky2/src/batch_fri/oracle.rs create mode 100644 plonky2/src/batch_fri/prover.rs create mode 100644 plonky2/src/batch_fri/recursive_verifier.rs create mode 100644 plonky2/src/batch_fri/verifier.rs create mode 100644 plonky2/src/hash/batch_merkle_tree.rs 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; From 6d501cd116247a88dbd54939863b0722790828e9 Mon Sep 17 00:00:00 2001 From: Sai Deng Date: Tue, 16 Jul 2024 14:24:49 +0800 Subject: [PATCH 19/21] fixes --- plonky2/src/fri/batch_oracle.rs | 610 -------------------- plonky2/src/fri/batch_prover.rs | 463 --------------- plonky2/src/fri/batch_recursive_verifier.rs | 332 ----------- plonky2/src/fri/batch_verifier.rs | 251 -------- plonky2/src/fri/mod.rs | 4 - plonky2/src/fri/validate_shape.rs | 61 -- plonky2/src/hash/field_merkle_tree.rs | 337 ----------- 7 files changed, 2058 deletions(-) delete mode 100644 plonky2/src/fri/batch_oracle.rs delete mode 100644 plonky2/src/fri/batch_prover.rs delete mode 100644 plonky2/src/fri/batch_recursive_verifier.rs delete mode 100644 plonky2/src/fri/batch_verifier.rs delete mode 100644 plonky2/src/hash/field_merkle_tree.rs diff --git a/plonky2/src/fri/batch_oracle.rs b/plonky2/src/fri/batch_oracle.rs deleted file mode 100644 index 1be347a653..0000000000 --- a/plonky2/src/fri/batch_oracle.rs +++ /dev/null @@ -1,610 +0,0 @@ -#[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::fri::batch_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::field_merkle_tree::FieldMerkleTree; -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 Field Merkle Tree. -#[derive(Eq, PartialEq, Debug)] -pub struct BatchFriOracle, C: GenericConfig, const D: usize> -{ - pub polynomials: Vec>, - pub field_merkle_tree: FieldMerkleTree, - // 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 field_merkle_tree = timed!( - timing, - "build Field Merkle tree", - FieldMerkleTree::new(leaves, cap_height) - ); - - degree_bits.sort_unstable(); - degree_bits.dedup(); - degree_bits.reverse(); - assert_eq!(field_merkle_tree.leaves.len(), degree_bits.len()); - Self { - polynomials, - field_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.field_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.field_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::fri::batch_oracle::BatchFriOracle; - use crate::fri::batch_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.field_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.field_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/fri/batch_prover.rs b/plonky2/src/fri/batch_prover.rs deleted file mode 100644 index bf41dff03b..0000000000 --- a/plonky2/src/fri/batch_prover.rs +++ /dev/null @@ -1,463 +0,0 @@ -#[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::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::field_merkle_tree::FieldMerkleTree; -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: &[&FieldMerkleTree], - 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() })); - - // 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: &[&FieldMerkleTree], - 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: &[&FieldMerkleTree], - 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::fri::batch_oracle::BatchFriOracle; - use crate::fri::batch_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.field_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.field_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.field_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.field_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.field_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.field_merkle_tree.cap], - &proof, - &fri_params, - ) - } -} diff --git a/plonky2/src/fri/batch_recursive_verifier.rs b/plonky2/src/fri/batch_recursive_verifier.rs deleted file mode 100644 index 90e7259fc9..0000000000 --- a/plonky2/src/fri/batch_recursive_verifier.rs +++ /dev/null @@ -1,332 +0,0 @@ -#[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.iter() { - 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_field_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/fri/batch_verifier.rs b/plonky2/src/fri/batch_verifier.rs deleted file mode 100644 index e978572d35..0000000000 --- a/plonky2/src/fri/batch_verifier.rs +++ /dev/null @@ -1,251 +0,0 @@ -#[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_field_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.iter() { - 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_field_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 c99cbf21b6..5f18600c3c 100644 --- a/plonky2/src/fri/mod.rs +++ b/plonky2/src/fri/mod.rs @@ -10,10 +10,6 @@ use serde::Serialize; use crate::fri::reduction_strategies::FriReductionStrategy; -pub mod batch_oracle; -pub mod batch_prover; -pub mod batch_recursive_verifier; -pub mod batch_verifier; mod challenges; pub mod oracle; pub mod proof; diff --git a/plonky2/src/fri/validate_shape.rs b/plonky2/src/fri/validate_shape.rs index 6fdfef1e29..be675ed61b 100644 --- a/plonky2/src/fri/validate_shape.rs +++ b/plonky2/src/fri/validate_shape.rs @@ -83,64 +83,3 @@ where Ok(()) } - -pub(crate) fn validate_batch_fri_proof_shape( - proof: &FriProof, - instances: &[FriInstanceInfo], - params: &FriParams, -) -> anyhow::Result<()> -where - F: RichField + Extendable, - C: GenericConfig, -{ - let FriProof { - commit_phase_merkle_caps, - query_round_proofs, - final_poly, - pow_witness: _pow_witness, - } = proof; - - let cap_height = params.config.cap_height; - for cap in commit_phase_merkle_caps { - ensure!(cap.height() == cap_height); - } - - for query_round in query_round_proofs { - let FriQueryRound { - initial_trees_proof, - steps, - } = query_round; - - 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()); - } - - ensure!(steps.len() == params.reduction_arity_bits.len()); - let mut codeword_len_bits = params.lde_bits(); - for (step, arity_bits) in steps.iter().zip(¶ms.reduction_arity_bits) { - let FriQueryStep { - evals, - merkle_proof, - } = step; - - let arity = 1 << arity_bits; - codeword_len_bits -= arity_bits; - - ensure!(evals.len() == arity); - ensure!(merkle_proof.len() + cap_height == codeword_len_bits); - } - } - - ensure!(final_poly.len() == params.final_poly_len()); - - Ok(()) -} diff --git a/plonky2/src/hash/field_merkle_tree.rs b/plonky2/src/hash/field_merkle_tree.rs deleted file mode 100644 index 6e1828c2da..0000000000 --- a/plonky2/src/hash/field_merkle_tree.rs +++ /dev/null @@ -1,337 +0,0 @@ -#[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 FieldMerkleTree> { - /// 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> FieldMerkleTree { - /// 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. - // TODO: FieldMerkleTree does not handle duplicates; this is deferred to the caller. Revisit when implementing batch FRI to potentially optimize. - 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 leaves_len = leaves[0].len(); - let log2_leaves_len = log2_strict(leaves_len); - assert!( - cap_height <= log2_leaves_len, - "cap_height={} should be at most log2(leaves.len())={}", - cap_height, - log2_leaves_len - ); - - let mut leaf_heights = vec![]; - - 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 { - 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 leaves_cap_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 >> (leaves_cap_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_field_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: FieldMerkleTree = FieldMerkleTree::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_field_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: FieldMerkleTree = - FieldMerkleTree::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_field_merkle_proof_to_cap(&opened_values, &fmt.leaf_heights, 1, &fmt.cap, &proof)?; - Ok(()) - } - - #[test] - fn test_field_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: FieldMerkleTree = - FieldMerkleTree::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_field_merkle_proof_to_cap( - &opened_values, - &fmt.leaf_heights, - index, - &fmt.cap, - &proof, - )?; - } - - Ok(()) - } - - #[test] - fn test_field_merkle_trees_cap_at_leaves_height() -> Result<()> { - let leaves_1 = crate::hash::merkle_tree::tests::random_data::(16, 7); - - let fmt: FieldMerkleTree = FieldMerkleTree::new(vec![leaves_1], 4); - for index in 0..16 { - let proof = fmt.open_batch(index); - let opened_values = fmt.values(index); - verify_field_merkle_proof_to_cap( - &opened_values, - &fmt.leaf_heights, - index, - &fmt.cap, - &proof, - )?; - } - - Ok(()) - } -} From 384236aee7de6c051841b3a448038af2e4b15d10 Mon Sep 17 00:00:00 2001 From: Sai Deng Date: Tue, 16 Jul 2024 14:28:21 +0800 Subject: [PATCH 20/21] fix benches --- plonky2/Cargo.toml | 2 +- .../{field_merkle_tree.rs => batch_merkle_tree.rs} | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) rename plonky2/benches/{field_merkle_tree.rs => batch_merkle_tree.rs} (79%) diff --git a/plonky2/Cargo.toml b/plonky2/Cargo.toml index eba775e27f..8b45db282b 100644 --- a/plonky2/Cargo.toml +++ b/plonky2/Cargo.toml @@ -65,7 +65,7 @@ name = "field_arithmetic" harness = false [[bench]] -name = "field_merkle_tree" +name = "batch_merkle_tree" harness = false [[bench]] diff --git a/plonky2/benches/field_merkle_tree.rs b/plonky2/benches/batch_merkle_tree.rs similarity index 79% rename from plonky2/benches/field_merkle_tree.rs rename to plonky2/benches/batch_merkle_tree.rs index dfaa301321..babe8e7247 100644 --- a/plonky2/benches/field_merkle_tree.rs +++ b/plonky2/benches/batch_merkle_tree.rs @@ -2,7 +2,7 @@ mod allocator; use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; use plonky2::field::goldilocks_field::GoldilocksField; -use plonky2::hash::field_merkle_tree::FieldMerkleTree; +use plonky2::hash::batch_merkle_tree::BatchMerkleTree; use plonky2::hash::hash_types::RichField; use plonky2::hash::keccak::KeccakHash; use plonky2::hash::poseidon::PoseidonHash; @@ -13,7 +13,7 @@ const ELEMS_PER_LEAF_1: usize = 70; const ELEMS_PER_LEAF_2: usize = 5; const ELEMS_PER_LEAF_3: usize = 100; -pub(crate) fn bench_field_merkle_tree>(c: &mut Criterion) { +pub(crate) fn bench_batch_merkle_tree>(c: &mut Criterion) { let mut group = c.benchmark_group(&format!( "field-merkle-tree<{}, {}>", type_name::(), @@ -29,14 +29,14 @@ pub(crate) fn bench_field_merkle_tree>(c: &mut Criter vec![F::rand_vec(ELEMS_PER_LEAF_2); size >> 1], vec![F::rand_vec(ELEMS_PER_LEAF_3); size >> 2], ]; - b.iter(|| FieldMerkleTree::::new(black_box(leaves.clone()), black_box(5))); + b.iter(|| BatchMerkleTree::::new(black_box(leaves.clone()), black_box(5))); }); } } fn criterion_benchmark(c: &mut Criterion) { - bench_field_merkle_tree::(c); - bench_field_merkle_tree::>(c); + bench_batch_merkle_tree::(c); + bench_batch_merkle_tree::>(c); } criterion_group!(benches, criterion_benchmark); From efcb07f6e8d2245494d74227a45c16400a6dd157 Mon Sep 17 00:00:00 2001 From: Sai Deng Date: Tue, 16 Jul 2024 14:32:40 +0800 Subject: [PATCH 21/21] clippy --- plonky2/benches/batch_merkle_tree.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plonky2/benches/batch_merkle_tree.rs b/plonky2/benches/batch_merkle_tree.rs index babe8e7247..87c0ac940e 100644 --- a/plonky2/benches/batch_merkle_tree.rs +++ b/plonky2/benches/batch_merkle_tree.rs @@ -14,8 +14,8 @@ const ELEMS_PER_LEAF_2: usize = 5; const ELEMS_PER_LEAF_3: usize = 100; pub(crate) fn bench_batch_merkle_tree>(c: &mut Criterion) { - let mut group = c.benchmark_group(&format!( - "field-merkle-tree<{}, {}>", + let mut group = c.benchmark_group(format!( + "batch-merkle-tree<{}, {}>", type_name::(), type_name::() ));