diff --git a/src/circuit_builder.rs b/src/circuit_builder.rs index a83a4977..c9f35212 100644 --- a/src/circuit_builder.rs +++ b/src/circuit_builder.rs @@ -21,6 +21,7 @@ use crate::plonk_common::PlonkPolynomials; use crate::polynomial::commitment::ListPolynomialCommitment; use crate::polynomial::polynomial::PolynomialValues; use crate::target::Target; +use crate::util::partial_products::num_partial_products; use crate::util::{log2_ceil, log2_strict, transpose, transpose_poly_values}; use crate::wire::Wire; @@ -391,6 +392,7 @@ impl, const D: usize> CircuitBuilder { /// Builds a "full circuit", with both prover and verifier data. pub fn build(mut self) -> CircuitData { + let quotient_degree_factor = 7; // TODO: add this as a parameter. let start = Instant::now(); info!( "Degree before blinding & padding: {}", @@ -402,6 +404,10 @@ impl, const D: usize> CircuitBuilder { let gates = self.gates.iter().cloned().collect(); let (gate_tree, max_filtered_constraint_degree, num_constants) = Tree::from_gates(gates); + assert!( + max_filtered_constraint_degree <= quotient_degree_factor + 1, + "Constraints are too high degree." + ); let prefixed_gates = PrefixedGate::from_tree(gate_tree); let degree_bits = log2_strict(degree); @@ -444,6 +450,9 @@ impl, const D: usize> CircuitBuilder { .max() .expect("No gates?"); + let num_partial_products = + num_partial_products(self.config.num_routed_wires, quotient_degree_factor); + // TODO: This should also include an encoding of gate constraints. let circuit_digest_parts = [ constants_sigmas_root.elements.to_vec(), @@ -455,10 +464,11 @@ impl, const D: usize> CircuitBuilder { config: self.config, degree_bits, gates: prefixed_gates, - max_filtered_constraint_degree, + quotient_degree_factor, num_gate_constraints, num_constants, k_is, + num_partial_products, circuit_digest, }; diff --git a/src/circuit_data.rs b/src/circuit_data.rs index d72a0be3..10c5ffe0 100644 --- a/src/circuit_data.rs +++ b/src/circuit_data.rs @@ -1,4 +1,4 @@ -use std::ops::Range; +use std::ops::{Range, RangeFrom}; use anyhow::Result; @@ -145,8 +145,8 @@ pub struct CommonCircuitData, const D: usize> { /// The types of gates used in this circuit, along with their prefixes. pub(crate) gates: Vec>, - /// The maximum degree of a filter times a constraint by any gate. - pub(crate) max_filtered_constraint_degree: usize, + /// The degree of the PLONK quotient polynomial. + pub(crate) quotient_degree_factor: usize, /// The largest number of constraints imposed by any gate. pub(crate) num_gate_constraints: usize, @@ -157,6 +157,10 @@ pub struct CommonCircuitData, const D: usize> { /// The `{k_i}` valued used in `S_ID_i` in Plonk's permutation argument. pub(crate) k_is: Vec, + /// The number of partial products needed to compute the `Z` polynomials and the number + /// of partial products needed to compute the final product. + pub(crate) num_partial_products: (usize, usize), + /// A digest of the "circuit" (i.e. the instance, minus public inputs), which can be used to /// seed Fiat-Shamir. pub(crate) circuit_digest: Hash, @@ -184,7 +188,7 @@ impl, const D: usize> CommonCircuitData { } pub fn quotient_degree(&self) -> usize { - (self.max_filtered_constraint_degree - 1) * self.degree() + self.quotient_degree_factor * self.degree() } pub fn total_constraints(&self) -> usize { @@ -201,6 +205,16 @@ impl, const D: usize> CommonCircuitData { pub fn sigmas_range(&self) -> Range { self.num_constants..self.num_constants + self.config.num_routed_wires } + + /// Range of the `z`s polynomials in the `zs_partial_products_commitment`. + pub fn zs_range(&self) -> Range { + 0..self.config.num_challenges + } + + /// Range of the partial products polynomials in the `zs_partial_products_commitment`. + pub fn partial_products_range(&self) -> RangeFrom { + self.config.num_challenges.. + } } /// The `Target` version of `VerifierCircuitData`, for use inside recursive circuits. Note that this diff --git a/src/fri/recursive_verifier.rs b/src/fri/recursive_verifier.rs index fc320f31..31ab0a25 100644 --- a/src/fri/recursive_verifier.rs +++ b/src/fri/recursive_verifier.rs @@ -183,7 +183,7 @@ impl, const D: usize> CircuitBuilder { // Polynomials opened at `x` and `g x`, i.e., the Zs polynomials. let zs_evals = proof - .unsalted_evals(PlonkPolynomials::ZS) + .unsalted_evals(PlonkPolynomials::ZS_PARTIAL_PRODUCTS) .iter() .map(|&e| self.convert_to_ext(e)) .collect::>(); diff --git a/src/fri/verifier.rs b/src/fri/verifier.rs index 3db4f6f5..e0716cde 100644 --- a/src/fri/verifier.rs +++ b/src/fri/verifier.rs @@ -1,5 +1,6 @@ use anyhow::{ensure, Result}; +use crate::circuit_data::CommonCircuitData; use crate::field::extension_field::{flatten, Extendable, FieldExtension, Frobenius}; use crate::field::field::Field; use crate::field::interpolation::{barycentric_weights, interpolate, interpolate2}; @@ -75,8 +76,9 @@ pub fn verify_fri_proof, const D: usize>( initial_merkle_roots: &[Hash], proof: &FriProof, challenger: &mut Challenger, - config: &FriConfig, + common_data: &CommonCircuitData, ) -> Result<()> { + let config = &common_data.config.fri_config; let total_arities = config.reduction_arity_bits.iter().sum::(); ensure!( purported_degree_log @@ -122,7 +124,7 @@ pub fn verify_fri_proof, const D: usize>( n, &betas, round_proof, - config, + common_data, )?; } @@ -147,8 +149,9 @@ fn fri_combine_initial, const D: usize>( os: &OpeningSet, zeta: F::Extension, subgroup_x: F, - config: &FriConfig, + common_data: &CommonCircuitData, ) -> F::Extension { + let config = &common_data.config.fri_config; assert!(D > 1, "Not implemented for D=1."); let degree_log = proof.evals_proofs[0].1.siblings.len() - config.rate_bits; let subgroup_x = F::Extension::from_basefield(subgroup_x); @@ -167,12 +170,17 @@ fn fri_combine_initial, const D: usize>( ] .iter() .flat_map(|&p| proof.unsalted_evals(p)) + .chain( + &proof.unsalted_evals(PlonkPolynomials::ZS_PARTIAL_PRODUCTS) + [common_data.partial_products_range()], + ) .map(|&e| F::Extension::from_basefield(e)); let single_openings = os .constants .iter() .chain(&os.plonk_s_sigmas) - .chain(&os.quotient_polys); + .chain(&os.quotient_polys) + .chain(&os.partial_products); let single_diffs = single_evals .into_iter() .zip(single_openings) @@ -185,9 +193,10 @@ fn fri_combine_initial, const D: usize>( // Polynomials opened at `x` and `g x`, i.e., the Zs polynomials. let zs_evals = proof - .unsalted_evals(PlonkPolynomials::ZS) + .unsalted_evals(PlonkPolynomials::ZS_PARTIAL_PRODUCTS) .iter() - .map(|&e| F::Extension::from_basefield(e)); + .map(|&e| F::Extension::from_basefield(e)) + .take(common_data.zs_range().end); let zs_composition_eval = alpha.clone().reduce(zs_evals); let zeta_right = F::Extension::primitive_root_of_unity(degree_log) * zeta; let zs_interpol = interpolate2( @@ -236,8 +245,9 @@ fn fri_verifier_query_round, const D: usize>( n: usize, betas: &[F::Extension], round_proof: &FriQueryRound, - config: &FriConfig, + common_data: &CommonCircuitData, ) -> Result<()> { + let config = &common_data.config.fri_config; let mut evaluations: Vec> = Vec::new(); let x = challenger.get_challenge(); let mut domain_size = n; @@ -262,7 +272,7 @@ fn fri_verifier_query_round, const D: usize>( os, zeta, subgroup_x, - config, + common_data, ) } else { let last_evals = &evaluations[i - 1]; diff --git a/src/gadgets/arithmetic_extension.rs b/src/gadgets/arithmetic_extension.rs index 4f7b1e14..3bf9c8a9 100644 --- a/src/gadgets/arithmetic_extension.rs +++ b/src/gadgets/arithmetic_extension.rs @@ -438,6 +438,7 @@ mod tests { use crate::field::extension_field::quartic::QuarticCrandallField; use crate::field::field::Field; use crate::fri::FriConfig; + use crate::verifier::verify; use crate::witness::PartialWitness; #[test] @@ -461,5 +462,7 @@ mod tests { let data = builder.build(); let proof = data.prove(PartialWitness::new()); + + verify(proof, &data.verifier_only, &data.common).unwrap(); } } diff --git a/src/gadgets/insert.rs b/src/gadgets/insert.rs index 7ddda1fb..734b6c03 100644 --- a/src/gadgets/insert.rs +++ b/src/gadgets/insert.rs @@ -78,6 +78,7 @@ mod tests { use crate::field::crandall_field::CrandallField; use crate::field::extension_field::quartic::QuarticCrandallField; use crate::field::field::Field; + use crate::verifier::verify; use crate::witness::PartialWitness; fn real_insert( @@ -115,6 +116,8 @@ mod tests { let data = builder.build(); let proof = data.prove(PartialWitness::new()); + + verify(proof, &data.verifier_only, &data.common).unwrap(); } #[test] diff --git a/src/gadgets/interpolation.rs b/src/gadgets/interpolation.rs index 37746685..8ece6063 100644 --- a/src/gadgets/interpolation.rs +++ b/src/gadgets/interpolation.rs @@ -66,6 +66,7 @@ mod tests { use crate::field::extension_field::FieldExtension; use crate::field::field::Field; use crate::field::interpolation::{interpolant, interpolate}; + use crate::verifier::verify; use crate::witness::PartialWitness; #[test] @@ -103,6 +104,8 @@ mod tests { let data = builder.build(); let proof = data.prove(PartialWitness::new()); + + verify(proof, &data.verifier_only, &data.common).unwrap(); } #[test] @@ -135,5 +138,7 @@ mod tests { let data = builder.build(); let proof = data.prove(PartialWitness::new()); + + verify(proof, &data.verifier_only, &data.common).unwrap(); } } diff --git a/src/gadgets/rotate.rs b/src/gadgets/rotate.rs index bd39a36b..61c1d2cc 100644 --- a/src/gadgets/rotate.rs +++ b/src/gadgets/rotate.rs @@ -118,6 +118,7 @@ mod tests { use crate::field::crandall_field::CrandallField; use crate::field::extension_field::quartic::QuarticCrandallField; use crate::field::field::Field; + use crate::verifier::verify; use crate::witness::PartialWitness; fn real_rotate( @@ -150,6 +151,8 @@ mod tests { let data = builder.build(); let proof = data.prove(PartialWitness::new()); + + verify(proof, &data.verifier_only, &data.common).unwrap(); } #[test] diff --git a/src/gadgets/split_base.rs b/src/gadgets/split_base.rs index 37f88409..a3177a1f 100644 --- a/src/gadgets/split_base.rs +++ b/src/gadgets/split_base.rs @@ -41,6 +41,7 @@ mod tests { use crate::circuit_data::CircuitConfig; use crate::field::crandall_field::CrandallField; use crate::field::field::Field; + use crate::verifier::verify; use crate::witness::PartialWitness; #[test] @@ -67,5 +68,7 @@ mod tests { let data = builder.build(); let proof = data.prove(PartialWitness::new()); + + verify(proof, &data.verifier_only, &data.common).unwrap(); } } diff --git a/src/gates/gate_tree.rs b/src/gates/gate_tree.rs index 5c2e084e..0c10ed70 100644 --- a/src/gates/gate_tree.rs +++ b/src/gates/gate_tree.rs @@ -74,7 +74,7 @@ impl, const D: usize> Tree> { // Iterate backwards from `max_degree` to try to find a tree with a lower degree // but the same number of constants. 'optdegree: for degree in (0..max_degree).rev() { - if let Some(mut tree) = Self::find_tree(&gates, degree, max_constants) { + if let Some(tree) = Self::find_tree(&gates, degree, max_constants) { let num_constants = tree.num_constants(); if num_constants > best_num_constants { break 'optdegree; diff --git a/src/plonk_challenger.rs b/src/plonk_challenger.rs index f9025ee8..149dceb3 100644 --- a/src/plonk_challenger.rs +++ b/src/plonk_challenger.rs @@ -72,6 +72,7 @@ impl Challenger { wires, plonk_zs, plonk_zs_right, + partial_products, quotient_polys, } = os; for v in &[ @@ -80,6 +81,7 @@ impl Challenger { wires, plonk_zs, plonk_zs_right, + partial_products, quotient_polys, ] { self.observe_extension_elements(v); diff --git a/src/plonk_common.rs b/src/plonk_common.rs index db304c0a..b1f29803 100644 --- a/src/plonk_common.rs +++ b/src/plonk_common.rs @@ -9,6 +9,7 @@ use crate::gates::gate::{GateRef, PrefixedGate}; use crate::polynomial::commitment::SALT_SIZE; use crate::polynomial::polynomial::PolynomialCoeffs; use crate::target::Target; +use crate::util::partial_products::check_partial_products; use crate::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; /// Holds the Merkle tree index and blinding flag of a set of polynomials used in FRI. @@ -37,7 +38,7 @@ impl PlonkPolynomials { index: 1, blinding: true, }; - pub const ZS: PolynomialsIndexBlinding = PolynomialsIndexBlinding { + pub const ZS_PARTIAL_PRODUCTS: PolynomialsIndexBlinding = PolynomialsIndexBlinding { index: 2, blinding: true, }; @@ -50,7 +51,7 @@ impl PlonkPolynomials { match i { 0 => Self::CONSTANTS_SIGMAS, 1 => Self::WIRES, - 2 => Self::ZS, + 2 => Self::ZS_PARTIAL_PRODUCTS, 3 => Self::QUOTIENT, _ => panic!("There are only 4 sets of polynomials in Plonk."), } @@ -64,41 +65,80 @@ pub(crate) fn eval_vanishing_poly, const D: usize>( common_data: &CommonCircuitData, x: F::Extension, vars: EvaluationVars, - local_plonk_zs: &[F::Extension], - next_plonk_zs: &[F::Extension], + local_zs: &[F::Extension], + next_zs: &[F::Extension], + partial_products: &[F::Extension], s_sigmas: &[F::Extension], betas: &[F], gammas: &[F], alphas: &[F], ) -> Vec { + let partial_products_degree = common_data.quotient_degree_factor; + let (num_prods, final_num_prod) = common_data.num_partial_products; + let constraint_terms = evaluate_gate_constraints(&common_data.gates, common_data.num_gate_constraints, vars); // The L_1(x) (Z(x) - 1) vanishing terms. let mut vanishing_z_1_terms = Vec::new(); + // The terms checking the partial products. + let mut vanishing_partial_products_terms = Vec::new(); // The Z(x) f'(x) - g'(x) Z(g x) terms. let mut vanishing_v_shift_terms = Vec::new(); for i in 0..common_data.config.num_challenges { - let z_x = local_plonk_zs[i]; - let z_gz = next_plonk_zs[i]; + let z_x = local_zs[i]; + let z_gz = next_zs[i]; vanishing_z_1_terms.push(eval_l_1(common_data.degree(), x) * (z_x - F::Extension::ONE)); - let mut f_prime = F::Extension::ONE; - let mut g_prime = F::Extension::ONE; - for j in 0..common_data.config.num_routed_wires { - let wire_value = vars.local_wires[j]; - let k_i = common_data.k_is[j]; - let s_id = x * k_i.into(); - let s_sigma = s_sigmas[j]; - f_prime *= wire_value + s_id * betas[i].into() + gammas[i].into(); - g_prime *= wire_value + s_sigma * betas[i].into() + gammas[i].into(); - } - vanishing_v_shift_terms.push(f_prime * z_x - g_prime * z_gz); + let numerator_values = (0..common_data.config.num_routed_wires) + .map(|j| { + let wire_value = vars.local_wires[j]; + let k_i = common_data.k_is[j]; + let s_id = x * k_i.into(); + wire_value + s_id * betas[i].into() + gammas[i].into() + }) + .collect::>(); + let denominator_values = (0..common_data.config.num_routed_wires) + .map(|j| { + let wire_value = vars.local_wires[j]; + let s_sigma = s_sigmas[j]; + wire_value + s_sigma * betas[i].into() + gammas[i].into() + }) + .collect::>(); + let quotient_values = (0..common_data.config.num_routed_wires) + .map(|j| numerator_values[j] / denominator_values[j]) + .collect::>(); + + // The partial products considered for this iteration of `i`. + let current_partial_products = &partial_products[i * num_prods..(i + 1) * num_prods]; + // Check the quotient partial products. + let mut partial_product_check = check_partial_products( + "ient_values, + current_partial_products, + partial_products_degree, + ); + // The first checks are of the form `q - n/d` which is a rational function not a polynomial. + // We multiply them by `d` to get checks of the form `q*d - n` which low-degree polynomials. + denominator_values + .chunks(partial_products_degree) + .zip(partial_product_check.iter_mut()) + .for_each(|(d, q)| { + *q *= d.iter().copied().product(); + }); + vanishing_partial_products_terms.extend(partial_product_check); + + // The quotient final product is the product of the last `final_num_prod` elements. + let quotient: F::Extension = current_partial_products[num_prods - final_num_prod..] + .iter() + .copied() + .product(); + vanishing_v_shift_terms.push(quotient * z_x - z_gz); } let vanishing_terms = [ vanishing_z_1_terms, + vanishing_partial_products_terms, vanishing_v_shift_terms, constraint_terms, ] @@ -114,42 +154,80 @@ pub(crate) fn eval_vanishing_poly_base, const D: usize>( index: usize, x: F, vars: EvaluationVarsBase, - local_plonk_zs: &[F], - next_plonk_zs: &[F], + local_zs: &[F], + next_zs: &[F], + partial_products: &[F], s_sigmas: &[F], betas: &[F], gammas: &[F], alphas: &[F], z_h_on_coset: &ZeroPolyOnCoset, ) -> Vec { + let partial_products_degree = common_data.quotient_degree_factor; + let (num_prods, final_num_prod) = common_data.num_partial_products; + let constraint_terms = evaluate_gate_constraints_base(&common_data.gates, common_data.num_gate_constraints, vars); // The L_1(x) (Z(x) - 1) vanishing terms. let mut vanishing_z_1_terms = Vec::new(); + // The terms checking the partial products. + let mut vanishing_partial_products_terms = Vec::new(); // The Z(x) f'(x) - g'(x) Z(g x) terms. let mut vanishing_v_shift_terms = Vec::new(); for i in 0..common_data.config.num_challenges { - let z_x = local_plonk_zs[i]; - let z_gz = next_plonk_zs[i]; + let z_x = local_zs[i]; + let z_gz = next_zs[i]; vanishing_z_1_terms.push(z_h_on_coset.eval_l1(index, x) * (z_x - F::ONE)); - let mut f_prime = F::ONE; - let mut g_prime = F::ONE; - for j in 0..common_data.config.num_routed_wires { - let wire_value = vars.local_wires[j]; - let k_i = common_data.k_is[j]; - let s_id = k_i * x; - let s_sigma = s_sigmas[j]; - f_prime *= wire_value + betas[i] * s_id + gammas[i]; - g_prime *= wire_value + betas[i] * s_sigma + gammas[i]; - } - vanishing_v_shift_terms.push(f_prime * z_x - g_prime * z_gz); - } + let numerator_values = (0..common_data.config.num_routed_wires) + .map(|j| { + let wire_value = vars.local_wires[j]; + let k_i = common_data.k_is[j]; + let s_id = k_i * x; + wire_value + betas[i] * s_id + gammas[i] + }) + .collect::>(); + let denominator_values = (0..common_data.config.num_routed_wires) + .map(|j| { + let wire_value = vars.local_wires[j]; + let s_sigma = s_sigmas[j]; + wire_value + betas[i] * s_sigma + gammas[i] + }) + .collect::>(); + let quotient_values = (0..common_data.config.num_routed_wires) + .map(|j| numerator_values[j] / denominator_values[j]) + .collect::>(); + // The partial products considered for this iteration of `i`. + let current_partial_products = &partial_products[i * num_prods..(i + 1) * num_prods]; + // Check the quotient partial products. + let mut partial_product_check = check_partial_products( + "ient_values, + current_partial_products, + partial_products_degree, + ); + // The first checks are of the form `q - n/d` which is a rational function not a polynomial. + // We multiply them by `d` to get checks of the form `q*d - n` which low-degree polynomials. + denominator_values + .chunks(partial_products_degree) + .zip(partial_product_check.iter_mut()) + .for_each(|(d, q)| { + *q *= d.iter().copied().product(); + }); + vanishing_partial_products_terms.extend(partial_product_check); + + // The quotient final product is the product of the last `final_num_prod` elements. + let quotient: F = current_partial_products[num_prods - final_num_prod..] + .iter() + .copied() + .product(); + vanishing_v_shift_terms.push(quotient * z_x - z_gz); + } let vanishing_terms = [ vanishing_z_1_terms, + vanishing_partial_products_terms, vanishing_v_shift_terms, constraint_terms, ] diff --git a/src/polynomial/commitment.rs b/src/polynomial/commitment.rs index 9bd3905b..7298331c 100644 --- a/src/polynomial/commitment.rs +++ b/src/polynomial/commitment.rs @@ -147,6 +147,13 @@ impl ListPolynomialCommitment { // Final low-degree polynomial that goes into FRI. let mut final_poly = PolynomialCoeffs::empty(); + let mut zs_polys = commitments[PlonkPolynomials::ZS_PARTIAL_PRODUCTS.index] + .polynomials + .iter() + .map(|p| p.to_extension()) + .collect::>(); + let partial_products_polys = zs_polys.split_off(common_data.zs_range().end); + // Polynomials opened at a single point. let single_polys = [ PlonkPolynomials::CONSTANTS_SIGMAS, @@ -154,7 +161,8 @@ impl ListPolynomialCommitment { ] .iter() .flat_map(|&p| &commitments[p.index].polynomials) - .map(|p| p.to_extension()); + .map(|p| p.to_extension()) + .chain(partial_products_polys); let single_composition_poly = alpha.reduce_polys(single_polys); let single_quotient = Self::compute_quotient([zeta], single_composition_poly); @@ -162,11 +170,7 @@ impl ListPolynomialCommitment { alpha.reset(); // Zs polynomials are opened at `zeta` and `g*zeta`. - let zs_polys = commitments[PlonkPolynomials::ZS.index] - .polynomials - .iter() - .map(|p| p.to_extension()); - let zs_composition_poly = alpha.reduce_polys(zs_polys); + let zs_composition_poly = alpha.reduce_polys(zs_polys.into_iter()); let zs_quotient = Self::compute_quotient([zeta, g * zeta], zs_composition_poly); alpha.shift_poly(&mut final_poly); @@ -254,7 +258,7 @@ impl, const D: usize> OpeningProof { os: &OpeningSet, merkle_roots: &[Hash], challenger: &mut Challenger, - fri_config: &FriConfig, + common_data: &CommonCircuitData, ) -> Result<()> { challenger.observe_opening_set(os); @@ -268,7 +272,7 @@ impl, const D: usize> OpeningProof { merkle_roots, &self.fri_proof, challenger, - fri_config, + common_data, ) } } @@ -310,7 +314,7 @@ mod tests { } fn check_batch_polynomial_commitment, const D: usize>() -> Result<()> { - let ks = [10, 2, 3, 8]; + let ks = [10, 2, 10, 8]; let degree_log = 11; let fri_config = FriConfig { proof_of_work_bits: 2, @@ -327,10 +331,11 @@ mod tests { }, degree_bits: 0, gates: vec![], - max_filtered_constraint_degree: 0, + quotient_degree_factor: 0, num_gate_constraints: 0, num_constants: 4, k_is: vec![F::ONE; 6], + num_partial_products: (0, 0), circuit_digest: Hash::from_partial(vec![]), }; @@ -362,7 +367,7 @@ mod tests { lpcs[3].merkle_tree.root, ], &mut Challenger::new(), - &common_data.config.fri_config, + &common_data, ) } diff --git a/src/proof.rs b/src/proof.rs index 47b67c9c..f712da40 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -154,6 +154,7 @@ pub struct OpeningSet, const D: usize> { pub wires: Vec, pub plonk_zs: Vec, pub plonk_zs_right: Vec, + pub partial_products: Vec, pub quotient_polys: Vec, } @@ -163,7 +164,7 @@ impl, const D: usize> OpeningSet { g: F::Extension, constants_sigmas_commitment: &ListPolynomialCommitment, wires_commitment: &ListPolynomialCommitment, - plonk_zs_commitment: &ListPolynomialCommitment, + zs_partial_products_commitment: &ListPolynomialCommitment, quotient_polys_commitment: &ListPolynomialCommitment, common_data: &CommonCircuitData, ) -> Self { @@ -174,12 +175,17 @@ impl, const D: usize> OpeningSet { .collect::>() }; let constants_sigmas_eval = eval_commitment(z, constants_sigmas_commitment); + let zs_partial_products_eval = eval_commitment(z, zs_partial_products_commitment); Self { constants: constants_sigmas_eval[common_data.constants_range()].to_vec(), plonk_s_sigmas: constants_sigmas_eval[common_data.sigmas_range()].to_vec(), wires: eval_commitment(z, wires_commitment), - plonk_zs: eval_commitment(z, plonk_zs_commitment), - plonk_zs_right: eval_commitment(g * z, plonk_zs_commitment), + plonk_zs: zs_partial_products_eval[common_data.zs_range()].to_vec(), + plonk_zs_right: eval_commitment(g * z, zs_partial_products_commitment) + [common_data.zs_range()] + .to_vec(), + partial_products: zs_partial_products_eval[common_data.partial_products_range()] + .to_vec(), quotient_polys: eval_commitment(z, quotient_polys_commitment), } } diff --git a/src/prover.rs b/src/prover.rs index 67b5a185..63c32995 100644 --- a/src/prover.rs +++ b/src/prover.rs @@ -12,6 +12,7 @@ use crate::polynomial::commitment::ListPolynomialCommitment; use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues}; use crate::proof::Proof; use crate::timed; +use crate::util::partial_products::partial_products; use crate::util::{log2_ceil, transpose}; use crate::vars::EvaluationVarsBase; use crate::witness::{PartialWitness, Witness}; @@ -78,21 +79,34 @@ pub(crate) fn prove, const D: usize>( let betas = challenger.get_n_challenges(num_challenges); let gammas = challenger.get_n_challenges(num_challenges); - let plonk_z_vecs = timed!( - compute_zs(&witness, &betas, &gammas, prover_data, common_data), - "to compute Z's" + assert!( + common_data.quotient_degree_factor + 1 <=common_data.config.num_routed_wires, + "When the number of routed wires is smaller that the degree, we should change the logic to avoid computing partial products." + ); + let mut partial_products = timed!( + all_wires_permutation_partial_products(&witness, &betas, &gammas, prover_data, common_data), + "to compute partial products" ); - let plonk_zs_commitment = timed!( + let plonk_z_vecs = timed!(compute_zs(&partial_products, common_data), "to compute Z's"); + + // The first polynomial in `partial_products` represent the final product used in the + // computation of `Z`. It isn't needed anymore so we discard it. + partial_products.iter_mut().for_each(|part| { + part.remove(0); + }); + + let zs_partial_products = [plonk_z_vecs, partial_products.concat()].concat(); + let zs_partial_products_commitment = timed!( ListPolynomialCommitment::new( - plonk_z_vecs, + zs_partial_products, fri_config.rate_bits, - PlonkPolynomials::ZS.blinding + PlonkPolynomials::ZS_PARTIAL_PRODUCTS.blinding ), "to commit to Z's" ); - challenger.observe_hash(&plonk_zs_commitment.merkle_tree.root); + challenger.observe_hash(&zs_partial_products_commitment.merkle_tree.root); let alphas = challenger.get_n_challenges(num_challenges); @@ -101,7 +115,7 @@ pub(crate) fn prove, const D: usize>( common_data, prover_data, &wires_commitment, - &plonk_zs_commitment, + &zs_partial_products_commitment, &betas, &gammas, &alphas, @@ -116,7 +130,7 @@ pub(crate) fn prove, const D: usize>( .flat_map(|mut quotient_poly| { quotient_poly.trim(); quotient_poly.pad(quotient_degree).expect( - "The quotient polynomial doesn't have the right degree.\ + "The quotient polynomial doesn't have the right degree. \ This may be because the `Z`s polynomials are still too high degree.", ); // Split t into degree-n chunks. @@ -144,7 +158,7 @@ pub(crate) fn prove, const D: usize>( &[ &prover_data.constants_sigmas_commitment, &wires_commitment, - &plonk_zs_commitment, + &zs_partial_products_commitment, "ient_polys_commitment, ], zeta, @@ -161,50 +175,103 @@ pub(crate) fn prove, const D: usize>( Proof { wires_root: wires_commitment.merkle_tree.root, - plonk_zs_root: plonk_zs_commitment.merkle_tree.root, + plonk_zs_root: zs_partial_products_commitment.merkle_tree.root, quotient_polys_root: quotient_polys_commitment.merkle_tree.root, openings, opening_proof, } } -fn compute_zs, const D: usize>( +/// Compute the partial products used in the `Z` polynomials. +fn all_wires_permutation_partial_products, const D: usize>( witness: &Witness, betas: &[F], gammas: &[F], prover_data: &ProverOnlyCircuitData, common_data: &CommonCircuitData, -) -> Vec> { +) -> Vec>> { (0..common_data.config.num_challenges) - .map(|i| compute_z(witness, betas[i], gammas[i], prover_data, common_data)) + .map(|i| { + wires_permutation_partial_products( + witness, + betas[i], + gammas[i], + prover_data, + common_data, + ) + }) .collect() } -fn compute_z, const D: usize>( +/// Compute the partial products used in the `Z` polynomial. +/// Returns the polynomials interpolating `partial_products(f / g)` +/// where `f, g` are the products in the definition of `Z`: `Z(g^i) = f / g`. +fn wires_permutation_partial_products, const D: usize>( witness: &Witness, beta: F, gamma: F, prover_data: &ProverOnlyCircuitData, common_data: &CommonCircuitData, -) -> PolynomialValues { +) -> Vec> { + let degree = common_data.quotient_degree_factor; let subgroup = &prover_data.subgroup; - let mut plonk_z_points = vec![F::ONE]; let k_is = &common_data.k_is; + let values = subgroup + .par_iter() + .enumerate() + .map(|(i, &x)| { + let s_sigmas = &prover_data.sigmas[i]; + let quotient_values = (0..common_data.config.num_routed_wires) + .map(|j| { + let wire_value = witness.get_wire(i, j); + let k_i = k_is[j]; + let s_id = k_i * x; + let s_sigma = s_sigmas[j]; + let numerator = wire_value + beta * s_id + gamma; + let denominator = wire_value + beta * s_sigma + gamma; + numerator / denominator + }) + .collect::>(); + + let quotient_partials = partial_products("ient_values, degree); + + // This is the final product for the quotient. + let quotient = quotient_partials + [common_data.num_partial_products.0 - common_data.num_partial_products.1..] + .iter() + .copied() + .product(); + + // We add the quotient at the beginning of the vector to reuse them later in the computation of `Z`. + [vec![quotient], quotient_partials].concat() + }) + .collect::>(); + + transpose(&values) + .into_par_iter() + .map(PolynomialValues::new) + .collect() +} + +fn compute_zs, const D: usize>( + partial_products: &[Vec>], + common_data: &CommonCircuitData, +) -> Vec> { + (0..common_data.config.num_challenges) + .map(|i| compute_z(&partial_products[i], common_data)) + .collect() +} + +/// Compute the `Z` polynomial by reusing the computations done in `wires_permutation_partial_products`. +fn compute_z, const D: usize>( + partial_products: &[PolynomialValues], + common_data: &CommonCircuitData, +) -> PolynomialValues { + let mut plonk_z_points = vec![F::ONE]; for i in 1..common_data.degree() { - let x = subgroup[i - 1]; - let mut numerator = F::ONE; - let mut denominator = F::ONE; - let s_sigmas = &prover_data.sigmas[i - 1]; - for j in 0..common_data.config.num_routed_wires { - let wire_value = witness.get_wire(i - 1, j); - let k_i = k_is[j]; - let s_id = k_i * x; - let s_sigma = s_sigmas[j]; - numerator *= wire_value + beta * s_id + gamma; - denominator *= wire_value + beta * s_sigma + gamma; - } + let quotient = partial_products[0].values[i - 1]; let last = *plonk_z_points.last().unwrap(); - plonk_z_points.push(last * numerator / denominator); + plonk_z_points.push(last * quotient); } plonk_z_points.into() } @@ -213,28 +280,27 @@ fn compute_quotient_polys<'a, F: Extendable, const D: usize>( common_data: &CommonCircuitData, prover_data: &'a ProverOnlyCircuitData, wires_commitment: &'a ListPolynomialCommitment, - plonk_zs_commitment: &'a ListPolynomialCommitment, + zs_partial_products_commitment: &'a ListPolynomialCommitment, betas: &[F], gammas: &[F], alphas: &[F], ) -> Vec> { let num_challenges = common_data.config.num_challenges; - let max_filtered_constraint_degree_bits = log2_ceil(common_data.max_filtered_constraint_degree); + let max_degree_bits = log2_ceil(common_data.quotient_degree_factor + 1); assert!( - max_filtered_constraint_degree_bits <= common_data.config.rate_bits, + max_degree_bits <= common_data.config.rate_bits, "Having constraints of degree higher than the rate is not supported yet. \ If we need this in the future, we can precompute the larger LDE before computing the `ListPolynomialCommitment`s." ); // We reuse the LDE computed in `ListPolynomialCommitment` and extract every `step` points to get // an LDE matching `max_filtered_constraint_degree`. - let step = 1 << (common_data.config.rate_bits - max_filtered_constraint_degree_bits); + let step = 1 << (common_data.config.rate_bits - max_degree_bits); // When opening the `Z`s polys at the "next" point in Plonk, need to look at the point `next_step` // steps away since we work on an LDE of degree `max_filtered_constraint_degree`. - let next_step = 1 << max_filtered_constraint_degree_bits; + let next_step = 1 << max_degree_bits; - let points = - F::two_adic_subgroup(common_data.degree_bits + max_filtered_constraint_degree_bits); + let points = F::two_adic_subgroup(common_data.degree_bits + max_degree_bits); let lde_size = points.len(); // Retrieve the LDE values at index `i`. @@ -242,8 +308,7 @@ fn compute_quotient_polys<'a, F: Extendable, const D: usize>( comm.get_lde_values(i * step) }; - let z_h_on_coset = - ZeroPolyOnCoset::new(common_data.degree_bits, max_filtered_constraint_degree_bits); + let z_h_on_coset = ZeroPolyOnCoset::new(common_data.degree_bits, max_degree_bits); let quotient_values: Vec> = points .into_par_iter() @@ -255,11 +320,14 @@ fn compute_quotient_polys<'a, F: Extendable, const D: usize>( let local_constants = &local_constants_sigmas[common_data.constants_range()]; let s_sigmas = &local_constants_sigmas[common_data.sigmas_range()]; let local_wires = get_at_index(wires_commitment, i); - let local_plonk_zs = get_at_index(plonk_zs_commitment, i); - let next_plonk_zs = get_at_index(plonk_zs_commitment, i_next); + let local_zs_partial_products = get_at_index(zs_partial_products_commitment, i); + let local_zs = &local_zs_partial_products[common_data.zs_range()]; + let next_zs = + &get_at_index(zs_partial_products_commitment, i_next)[common_data.zs_range()]; + let partial_products = &local_zs_partial_products[common_data.partial_products_range()]; debug_assert_eq!(local_wires.len(), common_data.config.num_wires); - debug_assert_eq!(local_plonk_zs.len(), num_challenges); + debug_assert_eq!(local_zs.len(), num_challenges); let vars = EvaluationVarsBase { local_constants, @@ -270,8 +338,9 @@ fn compute_quotient_polys<'a, F: Extendable, const D: usize>( i, shifted_x, vars, - local_plonk_zs, - next_plonk_zs, + local_zs, + next_zs, + partial_products, s_sigmas, betas, gammas, diff --git a/src/util/mod.rs b/src/util/mod.rs index ee3b8440..85440d70 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,3 +1,4 @@ +pub mod partial_products; pub mod scaling; pub(crate) mod timing; diff --git a/src/util/partial_products.rs b/src/util/partial_products.rs new file mode 100644 index 00000000..1d169b8d --- /dev/null +++ b/src/util/partial_products.rs @@ -0,0 +1,95 @@ +use std::iter::Product; +use std::ops::Sub; + +use crate::util::ceil_div_usize; + +/// Compute partial products of the original vector `v` such that all products consist of `max_degree` +/// or less elements. This is done until we've computed the product `P` of all elements in the vector. +pub fn partial_products(v: &[T], max_degree: usize) -> Vec { + let mut res = Vec::new(); + let mut remainder = v.to_vec(); + while remainder.len() > max_degree { + let new_partials = remainder + .chunks(max_degree) + // TODO: can filter out chunks of length 1. + .map(|chunk| chunk.iter().copied().product()) + .collect::>(); + res.extend_from_slice(&new_partials); + remainder = new_partials; + } + + res +} + +/// Returns a tuple `(a,b)`, where `a` is the length of the output of `partial_products()` on a +/// vector of length `n`, and `b` is the number of elements needed to compute the final product. +pub fn num_partial_products(n: usize, max_degree: usize) -> (usize, usize) { + debug_assert!(max_degree > 1); + let mut res = 0; + let mut remainder = n; + while remainder > max_degree { + let new_partials_len = ceil_div_usize(remainder, max_degree); + res += new_partials_len; + remainder = new_partials_len; + } + + (res, remainder) +} + +/// Checks that the partial products of `v` are coherent with those in `partials` by only computing +/// products of size `max_degree` or less. +pub fn check_partial_products>( + v: &[T], + partials: &[T], + max_degree: usize, +) -> Vec { + let mut res = Vec::new(); + let mut remainder = v.to_vec(); + let mut partials = partials.to_vec(); + while remainder.len() > max_degree { + let products = remainder + .chunks(max_degree) + .map(|chunk| chunk.iter().copied().product()) + .collect::>(); + res.extend(products.iter().zip(&partials).map(|(&a, &b)| a - b)); + remainder = partials.drain(..products.len()).collect(); + } + + res +} + +#[cfg(test)] +mod tests { + use num::Zero; + + use super::*; + + #[test] + fn test_partial_products() { + let v = vec![1, 2, 3, 4, 5, 6]; + let p = partial_products(&v, 2); + assert_eq!(p, vec![2, 12, 30, 24, 30]); + let nums = num_partial_products(v.len(), 2); + assert_eq!(p.len(), nums.0); + assert!(check_partial_products(&v, &p, 2) + .iter() + .all(|x| x.is_zero())); + assert_eq!( + v.into_iter().product::(), + p[p.len() - nums.1..].iter().copied().product(), + ); + + let v = vec![1, 2, 3, 4, 5, 6]; + let p = partial_products(&v, 3); + assert_eq!(p, vec![6, 120]); + let nums = num_partial_products(v.len(), 3); + assert_eq!(p.len(), nums.0); + assert!(check_partial_products(&v, &p, 3) + .iter() + .all(|x| x.is_zero())); + assert_eq!( + v.into_iter().product::(), + p[p.len() - nums.1..].iter().copied().product(), + ); + } +} diff --git a/src/util/scaling.rs b/src/util/scaling.rs index 3449e0b9..6effc5db 100644 --- a/src/util/scaling.rs +++ b/src/util/scaling.rs @@ -174,6 +174,7 @@ mod tests { use crate::circuit_data::CircuitConfig; use crate::field::crandall_field::CrandallField; use crate::field::extension_field::quartic::QuarticCrandallField; + use crate::verifier::verify; use crate::witness::PartialWitness; fn test_reduce_gadget(n: usize) { @@ -205,6 +206,8 @@ mod tests { let data = builder.build(); let proof = data.prove(PartialWitness::new()); + + verify(proof, &data.verifier_only, &data.common).unwrap(); } #[test] diff --git a/src/verifier.rs b/src/verifier.rs index 57bad7cf..d7c96365 100644 --- a/src/verifier.rs +++ b/src/verifier.rs @@ -2,8 +2,9 @@ use anyhow::{ensure, Result}; use crate::circuit_data::{CommonCircuitData, VerifierOnlyCircuitData}; use crate::field::extension_field::Extendable; +use crate::field::field::Field; use crate::plonk_challenger::Challenger; -use crate::plonk_common::{eval_vanishing_poly, eval_zero_poly}; +use crate::plonk_common::{eval_vanishing_poly, eval_zero_poly, reduce_with_powers}; use crate::proof::Proof; use crate::vars::EvaluationVars; @@ -13,7 +14,6 @@ pub(crate) fn verify, const D: usize>( common_data: &CommonCircuitData, ) -> Result<()> { let config = &common_data.config; - let fri_config = &config.fri_config; let num_challenges = config.num_challenges; let mut challenger = Challenger::new(); @@ -37,17 +37,19 @@ pub(crate) fn verify, const D: usize>( local_constants, local_wires, }; - let local_plonk_zs = &proof.openings.plonk_zs; - let next_plonk_zs = &proof.openings.plonk_zs_right; + let local_zs = &proof.openings.plonk_zs; + let next_zs = &proof.openings.plonk_zs_right; let s_sigmas = &proof.openings.plonk_s_sigmas; + let partial_products = &proof.openings.partial_products; // Evaluate the vanishing polynomial at our challenge point, zeta. let vanishing_polys_zeta = eval_vanishing_poly( common_data, zeta, vars, - local_plonk_zs, - next_plonk_zs, + local_zs, + next_zs, + partial_products, s_sigmas, &betas, &gammas, @@ -56,9 +58,18 @@ pub(crate) fn verify, const D: usize>( // Check each polynomial identity, of the form `vanishing(x) = Z_H(x) quotient(x)`, at zeta. let quotient_polys_zeta = &proof.openings.quotient_polys; - let z_h_zeta = eval_zero_poly(common_data.degree(), zeta); - for i in 0..num_challenges { - ensure!(vanishing_polys_zeta[i] == z_h_zeta * quotient_polys_zeta[i]); + let zeta_pow_deg = zeta.exp_power_of_2(common_data.degree_bits); + let z_h_zeta = zeta_pow_deg - F::Extension::ONE; + // `quotient_polys_zeta` holds `num_challenges * quotient_degree_factor` evaluations. + // Each chunk of `quotient_degree_factor` holds the evaluations of `t_0(zeta),...,t_{quotient_degree_factor-1}(zeta)` + // where the "real" quotient polynomial is `t(X) = t_0(X) + t_1(X)*X^n + t_2(X)*X^{2n} + ...`. + // So to reconstruct `t(zeta)` we can compute `reduce_with_powers(chunk, zeta^n)` for each + // `quotient_degree_factor`-sized chunk of the original evaluations. + for (i, chunk) in quotient_polys_zeta + .chunks(common_data.quotient_degree_factor) + .enumerate() + { + ensure!(vanishing_polys_zeta[i] == z_h_zeta * reduce_with_powers(chunk, zeta_pow_deg)); } let evaluations = proof.openings.clone(); @@ -75,7 +86,7 @@ pub(crate) fn verify, const D: usize>( &evaluations, merkle_roots, &mut challenger, - fri_config, + common_data, )?; Ok(())