From b7bc1bf313c5805b265cdba43cb0a4b6ec3f7762 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Thu, 22 Apr 2021 16:32:57 -0700 Subject: [PATCH 01/14] Seed Challenger with a hash of the instance I think this is the recommended way to apply Fiat-Shamir, to avoid any possible attacks like taking someone else's proof and using it to prove a slightly different statement. --- src/circuit_builder.rs | 96 ++++++++++++++++++++++++++++++--------- src/circuit_data.rs | 4 ++ src/gadgets/arithmetic.rs | 34 ++++++++++---- src/prover.rs | 4 ++ src/recursive_verifier.rs | 2 + 5 files changed, 110 insertions(+), 30 deletions(-) diff --git a/src/circuit_builder.rs b/src/circuit_builder.rs index a7e87339..06d4d8bf 100644 --- a/src/circuit_builder.rs +++ b/src/circuit_builder.rs @@ -1,16 +1,19 @@ -use std::collections::{HashSet, HashMap}; +use std::collections::{HashMap, HashSet}; use std::time::Instant; use log::info; -use crate::circuit_data::{CircuitConfig, CircuitData, CommonCircuitData, ProverCircuitData, ProverOnlyCircuitData, VerifierCircuitData, VerifierOnlyCircuitData}; +use crate::circuit_data::{ + CircuitConfig, CircuitData, CommonCircuitData, ProverCircuitData, ProverOnlyCircuitData, + VerifierCircuitData, VerifierOnlyCircuitData, +}; +use crate::field::cosets::get_unique_coset_shifts; use crate::field::field::Field; use crate::gates::constant::ConstantGate; use crate::gates::gate::{GateInstance, GateRef}; use crate::gates::noop::NoopGate; use crate::generator::{CopyGenerator, WitnessGenerator}; -use crate::hash::merkle_root_bit_rev_order; -use crate::field::cosets::get_unique_coset_shifts; +use crate::hash::{hash_n_to_hash, merkle_root_bit_rev_order}; use crate::polynomial::polynomial::PolynomialValues; use crate::target::Target; use crate::util::{log2_strict, transpose, transpose_poly_values}; @@ -84,14 +87,21 @@ impl CircuitBuilder { // TODO: Not passing next constants for now. Not sure if it's really useful... self.add_generators(gate_type.0.generators(index, &constants, &[])); - self.gate_instances.push(GateInstance { gate_type, constants }); + self.gate_instances.push(GateInstance { + gate_type, + constants, + }); index } fn check_gate_compatibility(&self, gate: &GateRef) { - assert!(gate.0.num_wires() <= self.config.num_wires, - "{:?} requires {} wires, but our GateConfig has only {}", - gate.0.id(), gate.0.num_wires(), self.config.num_wires); + assert!( + gate.0.num_wires() <= self.config.num_wires, + "{:?} requires {} wires, but our GateConfig has only {}", + gate.0.id(), + gate.0.num_wires(), + self.config.num_wires + ); } /// Shorthand for `generate_copy` and `assert_equal`. @@ -109,8 +119,14 @@ impl CircuitBuilder { /// Uses Plonk's permutation argument to require that two elements be equal. /// Both elements must be routable, otherwise this method will panic. pub fn assert_equal(&mut self, x: Target, y: Target) { - assert!(x.is_routable(self.config), "Tried to route a wire that isn't routable"); - assert!(y.is_routable(self.config), "Tried to route a wire that isn't routable"); + assert!( + x.is_routable(self.config), + "Tried to route a wire that isn't routable" + ); + assert!( + y.is_routable(self.config), + "Tried to route a wire that isn't routable" + ); // TODO: Add to copy_constraints. } @@ -150,7 +166,10 @@ impl CircuitBuilder { } let gate = self.add_gate(ConstantGate::get(), vec![c]); - let target = Target::Wire(Wire { gate, input: ConstantGate::WIRE_OUTPUT }); + let target = Target::Wire(Wire { + gate, + input: ConstantGate::WIRE_OUTPUT, + }); self.constants_to_targets.insert(c, target); self.targets_to_constants.insert(target, c); target @@ -175,11 +194,15 @@ impl CircuitBuilder { } fn constant_polys(&self) -> Vec> { - let num_constants = self.gate_instances.iter() + let num_constants = self + .gate_instances + .iter() .map(|gate_inst| gate_inst.constants.len()) .max() .unwrap(); - let constants_per_gate = self.gate_instances.iter() + let constants_per_gate = self + .gate_instances + .iter() .map(|gate_inst| { let mut padded_constants = gate_inst.constants.clone(); for _ in padded_constants.len()..num_constants { @@ -196,13 +219,17 @@ impl CircuitBuilder { } fn sigma_vecs(&self) -> Vec> { - vec![PolynomialValues::zero(self.gate_instances.len()); self.config.num_routed_wires] // TODO + vec![PolynomialValues::zero(self.gate_instances.len()); self.config.num_routed_wires] + // TODO } /// Builds a "full circuit", with both prover and verifier data. pub fn build(mut self) -> CircuitData { let start = Instant::now(); - info!("degree before blinding & padding: {}", self.gate_instances.len()); + info!( + "degree before blinding & padding: {}", + self.gate_instances.len() + ); self.blind_and_pad(); let degree = self.gate_instances.len(); info!("degree after blinding & padding: {}", degree); @@ -218,7 +245,11 @@ impl CircuitBuilder { let sigmas_root = merkle_root_bit_rev_order(sigma_ldes_t.clone()); let generators = self.generators; - let prover_only = ProverOnlyCircuitData { generators, constant_ldes_t, sigma_ldes_t }; + let prover_only = ProverOnlyCircuitData { + generators, + constant_ldes_t, + sigma_ldes_t, + }; let verifier_only = VerifierOnlyCircuitData {}; // The HashSet of gates will have a non-deterministic order. When converting to a Vec, we @@ -226,7 +257,8 @@ impl CircuitBuilder { let mut gates = self.gates.iter().cloned().collect::>(); gates.sort_unstable_by_key(|gate| gate.0.id()); - let num_gate_constraints = gates.iter() + let num_gate_constraints = gates + .iter() .map(|gate| gate.0.num_constraints()) .max() .expect("No gates?"); @@ -234,6 +266,13 @@ impl CircuitBuilder { let degree_bits = log2_strict(degree); let k_is = get_unique_coset_shifts(degree, self.config.num_routed_wires); + // TODO: This should also include an encoding of gate constraints. + let circuit_digest_parts = [ + constants_root.elements, + sigmas_root.elements, + ]; + let circuit_digest = hash_n_to_hash(circuit_digest_parts.concat(), false); + let common = CommonCircuitData { config: self.config, degree_bits, @@ -242,6 +281,7 @@ impl CircuitBuilder { constants_root, sigmas_root, k_is, + circuit_digest, }; info!("Building circuit took {}s", start.elapsed().as_secs_f32()); @@ -255,14 +295,28 @@ impl CircuitBuilder { /// Builds a "prover circuit", with data needed to generate proofs but not verify them. pub fn build_prover(self) -> ProverCircuitData { // TODO: Can skip parts of this. - let CircuitData { prover_only, common, .. } = self.build(); - ProverCircuitData { prover_only, common } + let CircuitData { + prover_only, + common, + .. + } = self.build(); + ProverCircuitData { + prover_only, + common, + } } /// Builds a "verifier circuit", with data needed to verify proofs but not generate them. pub fn build_verifier(self) -> VerifierCircuitData { // TODO: Can skip parts of this. - let CircuitData { verifier_only, common, .. } = self.build(); - VerifierCircuitData { verifier_only, common } + let CircuitData { + verifier_only, + common, + .. + } = self.build(); + VerifierCircuitData { + verifier_only, + common, + } } } diff --git a/src/circuit_data.rs b/src/circuit_data.rs index ef0a74c6..bb21e0c6 100644 --- a/src/circuit_data.rs +++ b/src/circuit_data.rs @@ -112,6 +112,10 @@ pub(crate) struct CommonCircuitData { /// The `{k_i}` valued used in `S_ID_i` in Plonk's permutation argument. pub(crate) k_is: Vec, + + /// 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, } impl CommonCircuitData { diff --git a/src/gadgets/arithmetic.rs b/src/gadgets/arithmetic.rs index 8fa727bb..00885e7e 100644 --- a/src/gadgets/arithmetic.rs +++ b/src/gadgets/arithmetic.rs @@ -1,9 +1,9 @@ use crate::circuit_builder::CircuitBuilder; use crate::field::field::Field; use crate::gates::arithmetic::ArithmeticGate; +use crate::generator::SimpleGenerator; use crate::target::Target; use crate::wire::Wire; -use crate::generator::SimpleGenerator; use crate::witness::PartialWitness; impl CircuitBuilder { @@ -22,8 +22,9 @@ impl CircuitBuilder { addend: Target, ) -> Target { // See if we can determine the result without adding an `ArithmeticGate`. - if let Some(result) = self.arithmetic_special_cases( - const_0, multiplicand_0, multiplicand_1, const_1, addend) { + if let Some(result) = + self.arithmetic_special_cases(const_0, multiplicand_0, multiplicand_1, const_1, addend) + { return result; } @@ -69,7 +70,8 @@ impl CircuitBuilder { let mul_1_const = self.target_as_constant(multiplicand_1); let addend_const = self.target_as_constant(addend); - let first_term_zero = const_0 == F::ZERO || multiplicand_0 == zero || multiplicand_1 == zero; + let first_term_zero = + const_0 == F::ZERO || multiplicand_0 == zero || multiplicand_1 == zero; let second_term_zero = const_1 == F::ZERO || addend == zero; // If both terms are constant, return their (constant) sum. @@ -156,17 +158,31 @@ impl CircuitBuilder { if y == one { return x; } - if let (Some(x_const), Some(y_const)) = (self.target_as_constant(x), self.target_as_constant(y)) { + if let (Some(x_const), Some(y_const)) = + (self.target_as_constant(x), self.target_as_constant(y)) + { return self.constant(x_const / y_const); } // Add an `ArithmeticGate` to compute `q * y`. let gate = self.add_gate(ArithmeticGate::new(), vec![F::ONE, F::ZERO]); - let wire_multiplicand_0 = Wire { gate, input: ArithmeticGate::WIRE_MULTIPLICAND_0 }; - let wire_multiplicand_1 = Wire { gate, input: ArithmeticGate::WIRE_MULTIPLICAND_1 }; - let wire_addend = Wire { gate, input: ArithmeticGate::WIRE_ADDEND }; - let wire_output = Wire { gate, input: ArithmeticGate::WIRE_OUTPUT }; + let wire_multiplicand_0 = Wire { + gate, + input: ArithmeticGate::WIRE_MULTIPLICAND_0, + }; + let wire_multiplicand_1 = Wire { + gate, + input: ArithmeticGate::WIRE_MULTIPLICAND_1, + }; + let wire_addend = Wire { + gate, + input: ArithmeticGate::WIRE_ADDEND, + }; + let wire_output = Wire { + gate, + input: ArithmeticGate::WIRE_OUTPUT, + }; let q = Target::Wire(wire_multiplicand_0); self.add_generator(QuotientGenerator { diff --git a/src/prover.rs b/src/prover.rs index 32735e17..17b52ded 100644 --- a/src/prover.rs +++ b/src/prover.rs @@ -68,6 +68,10 @@ pub(crate) fn prove( ); let mut challenger = Challenger::new(); + // Observe the instance. + // TODO: Need to include public inputs as well. + challenger.observe_hash(&common_data.circuit_digest); + challenger.observe_hash(&wires_root); let betas = challenger.get_n_challenges(num_checks); let gammas = challenger.get_n_challenges(num_checks); diff --git a/src/recursive_verifier.rs b/src/recursive_verifier.rs index 54d35bf0..b850e587 100644 --- a/src/recursive_verifier.rs +++ b/src/recursive_verifier.rs @@ -17,4 +17,6 @@ pub fn add_recursive_verifier( ) { assert!(builder.config.num_wires >= MIN_WIRES); assert!(builder.config.num_wires >= MIN_ROUTED_WIRES); + + todo!() } From 84a71c9ca5a938715dfd7da426acfe150116e5fe Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Thu, 22 Apr 2021 21:12:34 -0700 Subject: [PATCH 02/14] A few more tests, ported (with some adaptations) from plonky1 --- src/field/field.rs | 15 +++++++ src/field/field_testing.rs | 84 +++++++++++++++++++++++++------------- src/polynomial/division.rs | 32 ++++++++++++++- src/rescue.rs | 1 + 4 files changed, 102 insertions(+), 30 deletions(-) diff --git a/src/field/field.rs b/src/field/field.rs index cfadfbe6..32ee89e7 100644 --- a/src/field/field.rs +++ b/src/field/field.rs @@ -110,6 +110,21 @@ pub trait Field: subgroup } + fn cyclic_subgroup_unknown_order(generator: Self) -> Vec { + let mut subgroup = Vec::new(); + for power in generator.powers() { + if power.is_one() && !subgroup.is_empty() { + break; + } + subgroup.push(power); + } + subgroup + } + + fn generator_order(generator: Self) -> usize { + Self::cyclic_subgroup_unknown_order(generator).len() + } + /// Computes a coset of a multiplicative subgroup whose order is known in advance. fn cyclic_subgroup_coset_known_order(generator: Self, shift: Self, order: usize) -> Vec { let subgroup = Self::cyclic_subgroup_known_order(generator, order); diff --git a/src/field/field_testing.rs b/src/field/field_testing.rs index d070cb8d..59eee681 100644 --- a/src/field/field_testing.rs +++ b/src/field/field_testing.rs @@ -225,35 +225,61 @@ macro_rules! test_arithmetic { ) } - // #[test] - // #[ignore] - // fn arithmetic_division() { - // // This test takes ages to finish so is #[ignore]d by default. - // // TODO: Re-enable and reimplement when - // // https://github.com/rust-num/num-bigint/issues/60 is finally resolved. - // let modulus = <$field>::ORDER; - // crate::field::field_testing::run_binaryop_test_cases( - // modulus, - // WORD_BITS, - // // Need to help the compiler infer the type of y here - // |x: $field, y: $field| { - // // TODO: Work out how to check that div() panics - // // appropriately when given a zero divisor. - // if !y.is_zero() { - // <$field>::div(x, y) - // } else { - // <$field>::ZERO - // } - // }, - // |x, y| { - // // yinv = y^-1 (mod modulus) - // let exp = modulus - 2u64; - // let yinv = y.modpow(exp, modulus); - // // returns 0 if y was 0 - // x * yinv % modulus - // }, - // ) - // } + #[test] + fn inversion() { + let zero = <$field>::ZERO; + let one = <$field>::ONE; + let order = <$field>::ORDER; + + assert_eq!(zero.try_inverse(), None); + + for &x in &[1, 2, 3, order - 3, order - 2, order - 1] { + let x = <$field>::from_canonical_u64(x); + let inv = x.inverse(); + assert_eq!(x * inv, one); + } + } + + #[test] + fn batch_inversion() { + let xs = (1..=3) + .map(|i| <$field>::from_canonical_u64(i)) + .collect::>(); + let invs = <$field>::batch_multiplicative_inverse(&xs); + for (x, inv) in xs.into_iter().zip(invs) { + assert_eq!(x * inv, <$field>::ONE); + } + } + + #[test] + fn primitive_root_order() { + for n_power in 0..8 { + let root = <$field>::primitive_root_of_unity(n_power); + let order = <$field>::generator_order(root); + assert_eq!(order, 1 << n_power, "2^{}'th primitive root", n_power); + } + } + + #[test] + fn negation() { + let zero = <$field>::ZERO; + let order = <$field>::ORDER; + + for &i in &[0, 1, 2, order - 2, order - 1] { + let i_f = <$field>::from_canonical_u64(i); + assert_eq!(i_f + -i_f, zero); + } + } + + #[test] + fn bits() { + assert_eq!(<$field>::ZERO.bits(), 0); + assert_eq!(<$field>::ONE.bits(), 1); + assert_eq!(<$field>::TWO.bits(), 2); + assert_eq!(<$field>::from_canonical_u64(3).bits(), 2); + assert_eq!(<$field>::from_canonical_u64(4).bits(), 3); + assert_eq!(<$field>::from_canonical_u64(5).bits(), 3); + } } }; } diff --git a/src/polynomial/division.rs b/src/polynomial/division.rs index 41d872d0..31f746f4 100644 --- a/src/polynomial/division.rs +++ b/src/polynomial/division.rs @@ -60,8 +60,38 @@ pub(crate) fn divide_by_z_h(mut a: PolynomialCoeffs, n: usize) -> P #[cfg(test)] mod tests { + use crate::field::crandall_field::CrandallField; + use crate::field::field::Field; + use crate::polynomial::division::divide_by_z_h; + use crate::polynomial::polynomial::PolynomialCoeffs; + + #[test] + fn zero_div_z_h() { + type F = CrandallField; + let zero = PolynomialCoeffs::::zero(16); + let quotient = divide_by_z_h(zero.clone(), 4); + assert_eq!(quotient, zero); + } + #[test] fn division_by_z_h() { - // TODO + type F = CrandallField; + let zero = F::ZERO; + let one = F::ONE; + let two = F::TWO; + let three = F::from_canonical_u64(3); + let four = F::from_canonical_u64(4); + let five = F::from_canonical_u64(5); + let six = F::from_canonical_u64(6); + + // a(x) = Z_4(x) q(x), where + // a(x) = 3 x^7 + 4 x^6 + 5 x^5 + 6 x^4 - 3 x^3 - 4 x^2 - 5 x - 6 + // Z_4(x) = x^4 - 1 + // q(x) = 3 x^3 + 4 x^2 + 5 x + 6 + let a = PolynomialCoeffs::new(vec![-six, -five, -four, -three, six, five, four, three]); + let q = PolynomialCoeffs::new(vec![six, five, four, three, zero, zero, zero, zero]); + + let computed_q = divide_by_z_h(a, 4); + assert_eq!(computed_q, q); } } diff --git a/src/rescue.rs b/src/rescue.rs index 88785a1e..c28f315a 100644 --- a/src/rescue.rs +++ b/src/rescue.rs @@ -502,6 +502,7 @@ fn sbox_layer_b(x: [F; W]) -> [F; W] { #[unroll_for_loops] fn sbox_a(x: F) -> F { // x^{-5}, via Fermat's little theorem + // TODO: This only works for our current field. const EXP: u64 = 7378697628517453005; let mut product = F::ONE; From a5206f97a260af6df6a4ca20a8aa4a7c7654f111 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Thu, 22 Apr 2021 23:59:37 -0700 Subject: [PATCH 03/14] Better generator_order per William's comment --- src/field/field.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/field/field.rs b/src/field/field.rs index 32ee89e7..c9368208 100644 --- a/src/field/field.rs +++ b/src/field/field.rs @@ -122,7 +122,7 @@ pub trait Field: } fn generator_order(generator: Self) -> usize { - Self::cyclic_subgroup_unknown_order(generator).len() + generator.powers().skip(1).position(|y| y.is_one()).unwrap() + 1 } /// Computes a coset of a multiplicative subgroup whose order is known in advance. From 9c50e61f962e98f722301ada928dd0f6e53190b6 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 23 Apr 2021 00:01:41 -0700 Subject: [PATCH 04/14] cargo fmt --- src/circuit_builder.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/circuit_builder.rs b/src/circuit_builder.rs index 06d4d8bf..6c2b3c07 100644 --- a/src/circuit_builder.rs +++ b/src/circuit_builder.rs @@ -267,10 +267,7 @@ impl CircuitBuilder { let k_is = get_unique_coset_shifts(degree, self.config.num_routed_wires); // TODO: This should also include an encoding of gate constraints. - let circuit_digest_parts = [ - constants_root.elements, - sigmas_root.elements, - ]; + let circuit_digest_parts = [constants_root.elements, sigmas_root.elements]; let circuit_digest = hash_n_to_hash(circuit_digest_parts.concat(), false); let common = CommonCircuitData { From c684193033d339d858170faf8cc67a13f99358eb Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 23 Apr 2021 09:24:01 -0700 Subject: [PATCH 05/14] Rename a couple vars --- src/field/fft.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/field/fft.rs b/src/field/fft.rs index fe3d2117..af7a42f6 100644 --- a/src/field/fft.rs +++ b/src/field/fft.rs @@ -41,10 +41,10 @@ pub fn fft(poly: PolynomialCoeffs) -> PolynomialValues { } pub(crate) fn fft_precompute(degree: usize) -> FftPrecomputation { - let degree_pow = log2_ceil(degree); + let degree_log = log2_ceil(degree); let mut subgroups_rev = Vec::new(); - for i in 0..=degree_pow { + for i in 0..=degree_log { let g_i = F::primitive_root_of_unity(i); let subgroup = F::cyclic_subgroup_known_order(g_i, 1 << i); let subgroup_rev = reverse_index_bits(subgroup); @@ -89,14 +89,14 @@ pub(crate) fn fft_with_precomputation_power_of_2( ); let half_degree = poly.len() >> 1; - let degree_pow = poly.log_len(); + let degree_log = poly.log_len(); // In the base layer, we're just evaluating "degree 0 polynomials", i.e. the coefficients // themselves. let PolynomialCoeffs { coeffs } = poly; let mut evaluations = reverse_index_bits(coeffs); - for i in 1..=degree_pow { + for i in 1..=degree_log { // In layer i, we're evaluating a series of polynomials, each at 2^i points. In practice // we evaluate a pair of points together, so we have 2^(i - 1) pairs. let points_per_poly = 1 << i; @@ -198,9 +198,9 @@ mod tests { coefficients: &PolynomialCoeffs, ) -> PolynomialValues { let degree = coefficients.len(); - let degree_pow = log2_strict(degree); + let degree_log = log2_strict(degree); - let g = F::primitive_root_of_unity(degree_pow); + let g = F::primitive_root_of_unity(degree_log); let powers_of_g = F::cyclic_subgroup_known_order(g, degree); let values = powers_of_g From af4c8734cec888befccd9495b2d3d55ed0a4cf20 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 23 Apr 2021 12:35:19 -0700 Subject: [PATCH 06/14] Address some clippy warnings --- src/bin/bench_field_mul_interleaved.rs | 4 ++-- src/bin/bench_ldes.rs | 7 +++---- src/bin/bench_recursion.rs | 12 +---------- src/bin/field_search.rs | 2 +- src/field/crandall_field.rs | 4 ++++ src/field/fft.rs | 4 ++-- src/field/field.rs | 4 ++-- src/fri.rs | 28 ++++++++++++-------------- src/gadgets/arithmetic.rs | 6 ++---- src/gmimc.rs | 4 ++-- src/hash.rs | 9 ++------- src/plonk_challenger.rs | 6 ++++++ src/polynomial/division.rs | 12 +++++------ src/polynomial/polynomial.rs | 4 ++-- src/witness.rs | 6 ++++++ 15 files changed, 54 insertions(+), 58 deletions(-) diff --git a/src/bin/bench_field_mul_interleaved.rs b/src/bin/bench_field_mul_interleaved.rs index d04415f1..737105ad 100644 --- a/src/bin/bench_field_mul_interleaved.rs +++ b/src/bin/bench_field_mul_interleaved.rs @@ -14,8 +14,8 @@ const EXPONENT: usize = 1000000000; fn main() { let mut bases = [F::ZERO; WIDTH]; - for i in 0..WIDTH { - bases[i] = F::rand(); + for base_i in bases.iter_mut() { + *base_i = F::rand(); } let mut state = [F::ONE; WIDTH]; diff --git a/src/bin/bench_ldes.rs b/src/bin/bench_ldes.rs index ecdcd4fb..5620bd80 100644 --- a/src/bin/bench_ldes.rs +++ b/src/bin/bench_ldes.rs @@ -3,9 +3,8 @@ use std::time::Instant; use rayon::prelude::*; use plonky2::field::crandall_field::CrandallField; -use plonky2::field::fft; use plonky2::field::field::Field; -use plonky2::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues}; +use plonky2::polynomial::polynomial::PolynomialValues; type F = CrandallField; @@ -18,9 +17,9 @@ fn main() { let start = Instant::now(); (0usize..PROVER_POLYS).into_par_iter().for_each(|i| { - let mut values = vec![CrandallField::ZERO; DEGREE]; + let mut values = vec![F::ZERO; DEGREE]; for j in 0usize..DEGREE { - values[j] = CrandallField((i * j) as u64); + values[j] = F::from_canonical_u64((i * j) as u64); } let poly_values = PolynomialValues::new(values); let start = Instant::now(); diff --git a/src/bin/bench_recursion.rs b/src/bin/bench_recursion.rs index 81409642..566fb056 100644 --- a/src/bin/bench_recursion.rs +++ b/src/bin/bench_recursion.rs @@ -1,24 +1,14 @@ -use std::thread; -use std::time::Instant; - use env_logger::Env; -use rayon::prelude::*; use plonky2::circuit_builder::CircuitBuilder; use plonky2::circuit_data::CircuitConfig; use plonky2::field::crandall_field::CrandallField; -use plonky2::field::fft; use plonky2::field::field::Field; use plonky2::gates::constant::ConstantGate; use plonky2::gates::gmimc::GMiMCGate; -use plonky2::gmimc::gmimc_permute_array; -use plonky2::hash::{GMIMC_CONSTANTS, GMIMC_ROUNDS}; -use plonky2::polynomial::polynomial::PolynomialCoeffs; +use plonky2::hash::GMIMC_ROUNDS; use plonky2::witness::PartialWitness; -// 113 wire polys, 3 Z polys, 4 parts of quotient poly. -const PROVER_POLYS: usize = 113 + 3 + 4; - fn main() { // Set the default log filter. This can be overridden using the `RUST_LOG` environment variable, // e.g. `RUST_LOG=debug`. diff --git a/src/bin/field_search.rs b/src/bin/field_search.rs index e0fdb7e6..6c433339 100644 --- a/src/bin/field_search.rs +++ b/src/bin/field_search.rs @@ -53,5 +53,5 @@ fn is_prime(n: u64) -> bool { d += 2; } - return true; + true } diff --git a/src/field/crandall_field.rs b/src/field/crandall_field.rs index ae5dd89e..8983ef67 100644 --- a/src/field/crandall_field.rs +++ b/src/field/crandall_field.rs @@ -69,6 +69,7 @@ impl Field for CrandallField { *self * *self * *self } + #[allow(clippy::many_single_char_names)] // The names are from the paper. fn try_inverse(&self) -> Option { if self.is_zero() { return None; @@ -164,6 +165,7 @@ impl Add for CrandallField { type Output = Self; #[inline] + #[allow(clippy::suspicious_arithmetic_impl)] fn add(self, rhs: Self) -> Self { let (sum, over) = self.0.overflowing_add(rhs.0); Self(sum.overflowing_sub((over as u64) * Self::ORDER).0) @@ -180,6 +182,7 @@ impl Sub for CrandallField { type Output = Self; #[inline] + #[allow(clippy::suspicious_arithmetic_impl)] fn sub(self, rhs: Self) -> Self { let (diff, under) = self.0.overflowing_sub(rhs.0); Self(diff.overflowing_add((under as u64) * Self::ORDER).0) @@ -212,6 +215,7 @@ impl MulAssign for CrandallField { impl Div for CrandallField { type Output = Self; + #[allow(clippy::suspicious_arithmetic_impl)] fn div(self, rhs: Self) -> Self::Output { self * rhs.inverse() } diff --git a/src/field/fft.rs b/src/field/fft.rs index af7a42f6..f38ea204 100644 --- a/src/field/fft.rs +++ b/src/field/fft.rs @@ -66,8 +66,8 @@ pub(crate) fn ifft_with_precomputation_power_of_2( fft_with_precomputation_power_of_2(PolynomialCoeffs { coeffs: values }, precomputation); // We reverse all values except the first, and divide each by n. - result[0] = result[0] * n_inv; - result[n / 2] = result[n / 2] * n_inv; + result[0] *= n_inv; + result[n / 2] *= n_inv; for i in 1..(n / 2) { let j = n - i; let result_i = result[j] * n_inv; diff --git a/src/field/field.rs b/src/field/field.rs index c9368208..ed81cb6b 100644 --- a/src/field/field.rs +++ b/src/field/field.rs @@ -105,7 +105,7 @@ pub trait Field: let mut current = Self::ONE; for _i in 0..order { subgroup.push(current); - current = current * generator; + current *= generator; } subgroup } @@ -149,7 +149,7 @@ pub trait Field: for j in 0..power.bits() { if (power.to_canonical_u64() >> j & 1) != 0 { - product = product * current; + product *= current; } current = current.square(); } diff --git a/src/fri.rs b/src/fri.rs index 13885cc9..2bb4935d 100644 --- a/src/fri.rs +++ b/src/fri.rs @@ -126,13 +126,12 @@ fn fri_proof_of_work(current_hash: Hash, config: &FriConfig) -> F { (0u64..) .find(|&i| { hash_n_to_1( - Vec::from_iter( - current_hash - .elements - .iter() - .copied() - .chain(Some(F::from_canonical_u64(i))), - ), + current_hash + .elements + .iter() + .copied() + .chain(Some(F::from_canonical_u64(i))) + .collect(), false, ) .to_canonical_u64() @@ -149,14 +148,13 @@ fn fri_verify_proof_of_work( config: &FriConfig, ) -> Result<()> { let hash = hash_n_to_1( - Vec::from_iter( - challenger - .get_hash() - .elements - .iter() - .copied() - .chain(Some(proof.pow_witness)), - ), + challenger + .get_hash() + .elements + .iter() + .copied() + .chain(Some(proof.pow_witness)) + .collect(), false, ); ensure!( diff --git a/src/gadgets/arithmetic.rs b/src/gadgets/arithmetic.rs index 00885e7e..8fa5a226 100644 --- a/src/gadgets/arithmetic.rs +++ b/src/gadgets/arithmetic.rs @@ -91,10 +91,8 @@ impl CircuitBuilder { return Some(self.constant(x + y)); } - if first_term_zero { - if const_1.is_one() { - return Some(addend); - } + if first_term_zero && const_1.is_one() { + return Some(addend); } if second_term_zero { diff --git a/src/gmimc.rs b/src/gmimc.rs index 3b7d1e2c..9a65d49d 100644 --- a/src/gmimc.rs +++ b/src/gmimc.rs @@ -35,7 +35,7 @@ pub fn gmimc_compress( F::ZERO, F::ZERO, ]; - let state_1 = gmimc_permute::(state_0, constants.clone()); + let state_1 = gmimc_permute::(state_0, constants); [state_1[0], state_1[1], state_1[2], state_1[3]] } @@ -96,7 +96,7 @@ pub fn gmimc_permute_naive( let f = (xs[active] + constants[r]).cube(); for i in 0..W { if i != active { - xs[i] = xs[i] + f; + xs[i] += f; } } } diff --git a/src/hash.rs b/src/hash.rs index d87d3e28..786fc57c 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -173,9 +173,7 @@ impl CircuitBuilder { // Overwrite the first r elements with the inputs. This differs from a standard sponge, // where we would xor or add in the inputs. This is a well-known variant, though, // sometimes called "overwrite mode". - for i in 0..input_chunk.len() { - state[i] = input_chunk[i]; - } + state[..input_chunk.len()].copy_from_slice(input_chunk); state = self.permute(state); } @@ -268,10 +266,7 @@ pub(crate) fn merkle_root(vecs: Vec>) -> Hash { } pub(crate) fn merkle_root_inner(vecs: Vec>) -> Hash { - let mut hashes = vecs - .into_iter() - .map(|leaf_set| hash_or_noop(leaf_set)) - .collect::>(); + let mut hashes = vecs.into_iter().map(hash_or_noop).collect::>(); while hashes.len() > 1 { hashes = hashes .chunks(2) diff --git a/src/plonk_challenger.rs b/src/plonk_challenger.rs index a98a6fc3..a8f5e605 100644 --- a/src/plonk_challenger.rs +++ b/src/plonk_challenger.rs @@ -107,6 +107,12 @@ impl Challenger { } } +impl Default for Challenger { + fn default() -> Self { + Self::new() + } +} + /// A recursive version of `Challenger`. pub(crate) struct RecursiveChallenger { sponge_state: [Target; SPONGE_WIDTH], diff --git a/src/polynomial/division.rs b/src/polynomial/division.rs index 31f746f4..0b5055ef 100644 --- a/src/polynomial/division.rs +++ b/src/polynomial/division.rs @@ -9,15 +9,15 @@ use crate::util::log2_strict; pub(crate) fn divide_by_z_h(mut a: PolynomialCoeffs, n: usize) -> PolynomialCoeffs { // TODO: Is this special case needed? if a.coeffs.iter().all(|p| *p == F::ZERO) { - return a.clone(); + return a; } let g = F::MULTIPLICATIVE_GROUP_GENERATOR; let mut g_pow = F::ONE; // Multiply the i-th coefficient of `a` by `g^i`. Then `new_a(w^j) = old_a(g.w^j)`. a.coeffs.iter_mut().for_each(|x| { - *x = (*x) * g_pow; - g_pow = g * g_pow; + *x *= g_pow; + g_pow *= g; }); let root = F::primitive_root_of_unity(log2_strict(a.len())); @@ -43,7 +43,7 @@ pub(crate) fn divide_by_z_h(mut a: PolynomialCoeffs, n: usize) -> P .iter_mut() .zip(denominators_inv.iter()) .for_each(|(x, &d)| { - *x = (*x) * d; + *x *= d; }); // `p` is the interpolating polynomial of `a_eval` on `{w^i}`. let mut p = ifft(a_eval); @@ -52,8 +52,8 @@ pub(crate) fn divide_by_z_h(mut a: PolynomialCoeffs, n: usize) -> P let g_inv = g.inverse(); let mut g_inv_pow = F::ONE; p.coeffs.iter_mut().for_each(|x| { - *x = (*x) * g_inv_pow; - g_inv_pow = g_inv_pow * g_inv; + *x *= g_inv_pow; + g_inv_pow *= g_inv; }); p } diff --git a/src/polynomial/polynomial.rs b/src/polynomial/polynomial.rs index 11949494..0444b1e8 100644 --- a/src/polynomial/polynomial.rs +++ b/src/polynomial/polynomial.rs @@ -31,7 +31,7 @@ impl PolynomialValues { } pub fn lde(self, rate_bits: usize) -> Self { - let mut coeffs = ifft(self).lde(rate_bits); + let coeffs = ifft(self).lde(rate_bits); fft(coeffs) } } @@ -88,7 +88,7 @@ impl PolynomialCoeffs { polys.into_iter().map(|p| p.lde(rate_bits)).collect() } - pub(crate) fn lde(mut self, rate_bits: usize) -> Self { + pub(crate) fn lde(self, rate_bits: usize) -> Self { let original_size = self.len(); let lde_size = original_size << rate_bits; let Self { mut coeffs } = self; diff --git a/src/witness.rs b/src/witness.rs index 4c5e89a8..42b7150c 100644 --- a/src/witness.rs +++ b/src/witness.rs @@ -79,3 +79,9 @@ impl PartialWitness { } } } + +impl Default for PartialWitness { + fn default() -> Self { + Self::new() + } +} From 80775eadb1810643045109b2325d5239514fd84d Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sat, 10 Apr 2021 14:52:34 -0700 Subject: [PATCH 07/14] Make Rescue a bit faster ... by switching to Rescue Prime (which has a smaller security margin), and precomputing an addition chain for the exponent used in the cubic root calculation. Also adds a benchmark. --- src/bin/bench_gmimc.rs | 4 +- src/bin/bench_rescue.rs | 46 ++++++++++++++++ src/field/crandall_field.rs | 79 +++++++++++++++++++++++++++ src/field/field.rs | 10 ++++ src/rescue.rs | 106 +++--------------------------------- 5 files changed, 145 insertions(+), 100 deletions(-) create mode 100644 src/bin/bench_rescue.rs diff --git a/src/bin/bench_gmimc.rs b/src/bin/bench_gmimc.rs index f234285d..2f81aac0 100644 --- a/src/bin/bench_gmimc.rs +++ b/src/bin/bench_gmimc.rs @@ -14,8 +14,8 @@ const PROVER_POLYS: usize = 113 + 3 + 4; fn main() { const THREADS: usize = 12; const LDE_BITS: i32 = 3; - const W: usize = 13; - const HASHES_PER_POLY: usize = 1 << (13 + LDE_BITS); + const W: usize = 12; + const HASHES_PER_POLY: usize = 1 << (13 + LDE_BITS) / 6; let threads = (0..THREADS) .map(|_i| { diff --git a/src/bin/bench_rescue.rs b/src/bin/bench_rescue.rs new file mode 100644 index 00000000..96334689 --- /dev/null +++ b/src/bin/bench_rescue.rs @@ -0,0 +1,46 @@ +use std::thread; +use std::time::Instant; + +use plonky2::field::crandall_field::CrandallField; +use plonky2::field::field::Field; +use plonky2::rescue::rescue; + +type F = CrandallField; + +// 113 wire polys, 3 Z polys, 4 parts of quotient poly. +const PROVER_POLYS: usize = 113 + 3 + 4; + +fn main() { + const THREADS: usize = 12; + const LDE_BITS: i32 = 3; + const W: usize = 12; + const HASHES_PER_POLY: usize = (1 << (13 + LDE_BITS)) / 6; + + let threads = (0..THREADS) + .map(|_i| { + thread::spawn(move || { + let mut x = [F::ZERO; W]; + for i in 0..W { + x[i] = F::from_canonical_u64((i as u64) * 123456 + 789); + } + + let hashes_per_thread = HASHES_PER_POLY * PROVER_POLYS / THREADS; + let start = Instant::now(); + for _ in 0..hashes_per_thread { + x = rescue(x); + } + let duration = start.elapsed(); + println!("took {:?}", duration); + println!( + "avg {:?}us", + duration.as_secs_f64() * 1e6 / (hashes_per_thread as f64) + ); + println!("result {:?}", x); + }) + }) + .collect::>(); + + for t in threads { + t.join().expect("oops"); + } +} diff --git a/src/field/crandall_field.rs b/src/field/crandall_field.rs index ae5dd89e..0a872f63 100644 --- a/src/field/crandall_field.rs +++ b/src/field/crandall_field.rs @@ -144,6 +144,85 @@ impl Field for CrandallField { fn from_canonical_u64(n: u64) -> Self { Self(n) } + + fn cube_root(&self) -> Self { + let x0 = *self; + let x1 = x0.square(); + let x2 = x1.square(); + let x3 = x2 * x0; + let x4 = x3.square(); + let x5 = x4.square(); + // let x6 = x4.square(); + let x7 = x5.square(); + let x8 = x7.square(); + let x9 = x8.square(); + let x10 = x9.square(); + let x11 = x10 * x5; + let x12 = x11.square(); + let x13 = x12.square(); + let x14 = x13.square(); + // let x15 = x13.square(); + let x16 = x14.square(); + let x17 = x16.square(); + let x18 = x17.square(); + let x19 = x18.square(); + let x20 = x19.square(); + let x21 = x20 * x11; + let x22 = x21.square(); + let x23 = x22.square(); + let x24 = x23.square(); + let x25 = x24.square(); + let x26 = x25.square(); + let x27 = x26.square(); + let x28 = x27.square(); + let x29 = x28.square(); + let x30 = x29.square(); + let x31 = x30.square(); + let x32 = x31.square(); + let x33 = x32 * x14; + let x34 = x33 * x3; + let x35 = x34.square(); + let x36 = x35 * x34; + let x37 = x36 * x5; + let x38 = x37 * x34; + let x39 = x38 * x37; + let x40 = x39.square(); + let x41 = x40.square(); + let x42 = x41 * x38; + let x43 = x42.square(); + let x44 = x43.square(); + let x45 = x44.square(); + let x46 = x45.square(); + let x47 = x46.square(); + let x48 = x47.square(); + let x49 = x48.square(); + let x50 = x49.square(); + let x51 = x50.square(); + let x52 = x51.square(); + let x53 = x52.square(); + let x54 = x53.square(); + let x55 = x54.square(); + let x56 = x55.square(); + let x57 = x56.square(); + let x58 = x57.square(); + let x59 = x58.square(); + let x60 = x59.square(); + let x61 = x60.square(); + let x62 = x61.square(); + let x63 = x62.square(); + let x64 = x63.square(); + let x65 = x64.square(); + let x66 = x65.square(); + let x67 = x66.square(); + let x68 = x67.square(); + let x69 = x68.square(); + let x70 = x69.square(); + let x71 = x70.square(); + let x72 = x71.square(); + let x73 = x72.square(); + let x74 = x73 * x39; + x74 + } } impl Neg for CrandallField { diff --git a/src/field/field.rs b/src/field/field.rs index c9368208..3991a21a 100644 --- a/src/field/field.rs +++ b/src/field/field.rs @@ -160,6 +160,16 @@ pub trait Field: self.exp(Self::from_canonical_usize(power)) } + fn kth_root(&self, k: usize) -> Self { + let p_minus_1 = Self::ORDER - 1; + debug_assert!(p_minus_1 % k as u64 != 0, "Not a permutation in this field"); + todo!() + } + + fn cube_root(&self) -> Self { + self.kth_root(3) + } + fn powers(&self) -> Powers { Powers { base: *self, diff --git a/src/rescue.rs b/src/rescue.rs index c28f315a..f088fdb7 100644 --- a/src/rescue.rs +++ b/src/rescue.rs @@ -1,8 +1,10 @@ +//! Implements Rescue Prime. + use unroll::unroll_for_loops; use crate::field::field::Field; -const ROUNDS: usize = 10; +const ROUNDS: usize = 8; const W: usize = 12; @@ -177,7 +179,7 @@ const MDS: [[u64; W]; W] = [ ], ]; -const RESCUE_CONSTANTS: [[u64; W]; 20] = [ +const RESCUE_CONSTANTS: [[u64; W]; 16] = [ [ 12050887499329086906, 1748247961703512657, @@ -402,66 +404,10 @@ const RESCUE_CONSTANTS: [[u64; W]; 20] = [ 16465224002344550280, 10282380383506806095, ], - [ - 12608209810104211593, - 11808578423511814760, - 16177950852717156460, - 9394439296563712221, - 12586575762376685187, - 17703393198607870393, - 9811861465513647715, - 14126450959506560131, - 12713673607080398908, - 18301828072718562389, - 11180556590297273821, - 4451415492203885059, - ], - [ - 10465807219916311101, - 1213997644391575261, - 17672155373280862521, - 1491206970207330736, - 10977478805896263804, - 13260961975618373124, - 16060889403827043708, - 3223573072465920682, - 17624203443801796697, - 10247205738678800822, - 11100653267668698651, - 14328592975764892571, - ], - [ - 6984072551318461094, - 3416562710010527326, - 12847783919251969270, - 12223185134739244472, - 12073170519625198198, - 6221124633828606855, - 17596623990006806590, - 1153871693574764968, - 2548851681903410721, - 9823373270182377847, - 16708030507924899244, - 9619306826188519218, - ], - [ - 5842685042453818473, - 12400879353954910914, - 647112787845575111, - 4893664959929687347, - 3759391664155971284, - 15871181179823725763, - 3629377713951158273, - 3439101502554162312, - 8325686353010019444, - 10630488935940555500, - 3478529754946055748, - 12681233130980545828, - ], ]; -fn rescue(mut xs: [F; W]) -> [F; W] { - for r in 0..10 { +pub fn rescue(mut xs: [F; W]) -> [F; W] { + for r in 0..8 { xs = sbox_layer_a(xs); xs = mds_layer(xs); xs = constant_layer(xs, &RESCUE_CONSTANTS[r * 2]); @@ -470,62 +416,27 @@ fn rescue(mut xs: [F; W]) -> [F; W] { xs = mds_layer(xs); xs = constant_layer(xs, &RESCUE_CONSTANTS[r * 2 + 1]); } - - // for i in 0..W { - // xs[i] = xs[i].to_canonical(); - // } - xs } -// #[inline(always)] #[unroll_for_loops] fn sbox_layer_a(x: [F; W]) -> [F; W] { let mut result = [F::ZERO; W]; for i in 0..W { - result[i] = sbox_a(x[i]); + result[i] = x[i].cube(); } result } -// #[inline(always)] #[unroll_for_loops] fn sbox_layer_b(x: [F; W]) -> [F; W] { let mut result = [F::ZERO; W]; for i in 0..W { - result[i] = sbox_b(x[i]); + result[i] = x[i].cube_root(); } result } -// #[inline(always)] -#[unroll_for_loops] -fn sbox_a(x: F) -> F { - // x^{-5}, via Fermat's little theorem - // TODO: This only works for our current field. - const EXP: u64 = 7378697628517453005; - - let mut product = F::ONE; - let mut current = x; - - for i in 0..64 { - if ((EXP >> i) & 1) != 0 { - product = product * current; - } - current = current.square(); - } - product -} - -#[inline(always)] -fn sbox_b(x: F) -> F { - // x^5 - let x2 = x.square(); - let x3 = x2 * x; - x2 * x3 -} - -// #[inline(always)] #[unroll_for_loops] fn mds_layer(x: [F; W]) -> [F; W] { let mut result = [F::ZERO; W]; @@ -537,7 +448,6 @@ fn mds_layer(x: [F; W]) -> [F; W] { result } -#[inline(always)] #[unroll_for_loops] fn constant_layer(xs: [F; W], con: &[u64; W]) -> [F; W] { let mut result = [F::ZERO; W]; From b18f152c43e8ad6b6c644dfb8ddb9be4d424d61f Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 23 Apr 2021 13:54:35 -0700 Subject: [PATCH 08/14] Remove access to "next" wire & constant values As discussed, it seems like the batch opening argument will be a significant cost, and we can reduce that cost by not including shifted openings (except for `Z`s which need them). --- src/circuit_builder.rs | 3 +-- src/gates/arithmetic.rs | 1 - src/gates/constant.rs | 1 - src/gates/gate.rs | 1 - src/gates/gmimc.rs | 3 +-- src/gates/gmimc_eval.rs | 1 - src/gates/noop.rs | 1 - src/prover.rs | 2 -- src/vars.rs | 4 ---- 9 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/circuit_builder.rs b/src/circuit_builder.rs index 6c2b3c07..50ec1a95 100644 --- a/src/circuit_builder.rs +++ b/src/circuit_builder.rs @@ -84,8 +84,7 @@ impl CircuitBuilder { let index = self.gate_instances.len(); - // TODO: Not passing next constants for now. Not sure if it's really useful... - self.add_generators(gate_type.0.generators(index, &constants, &[])); + self.add_generators(gate_type.0.generators(index, &constants)); self.gate_instances.push(GateInstance { gate_type, diff --git a/src/gates/arithmetic.rs b/src/gates/arithmetic.rs index 85cc8e08..4a2f63b7 100644 --- a/src/gates/arithmetic.rs +++ b/src/gates/arithmetic.rs @@ -64,7 +64,6 @@ impl Gate for ArithmeticGate { &self, gate_index: usize, local_constants: &[F], - _next_constants: &[F], ) -> Vec>> { let gen = ArithmeticGenerator { gate_index, diff --git a/src/gates/constant.rs b/src/gates/constant.rs index cf2b9e04..8482a6de 100644 --- a/src/gates/constant.rs +++ b/src/gates/constant.rs @@ -45,7 +45,6 @@ impl Gate for ConstantGate { &self, gate_index: usize, local_constants: &[F], - _next_constants: &[F], ) -> Vec>> { let gen = ConstantGenerator { gate_index, diff --git a/src/gates/gate.rs b/src/gates/gate.rs index 58bd9dd9..a340a1bd 100644 --- a/src/gates/gate.rs +++ b/src/gates/gate.rs @@ -37,7 +37,6 @@ pub trait Gate: 'static + Send + Sync { &self, gate_index: usize, local_constants: &[F], - next_constants: &[F], ) -> Vec>>; /// The number of wires used by this gate. diff --git a/src/gates/gmimc.rs b/src/gates/gmimc.rs index 706b4bd1..82539388 100644 --- a/src/gates/gmimc.rs +++ b/src/gates/gmimc.rs @@ -126,7 +126,6 @@ impl Gate for GMiMCGate { &self, gate_index: usize, _local_constants: &[F], - _next_constants: &[F], ) -> Vec>> { let gen = GMiMCGenerator { gate_index, @@ -304,7 +303,7 @@ mod tests { ); } - let generators = gate.0.generators(0, &[], &[]); + let generators = gate.0.generators(0, &[]); generate_partial_witness(&mut witness, &generators); let expected_outputs: [F; W] = diff --git a/src/gates/gmimc_eval.rs b/src/gates/gmimc_eval.rs index 427559ab..e2eb4cb6 100644 --- a/src/gates/gmimc_eval.rs +++ b/src/gates/gmimc_eval.rs @@ -38,7 +38,6 @@ impl Gate for GMiMCEvalGate { &self, gate_index: usize, local_constants: &[F], - _next_constants: &[F], ) -> Vec>> { let gen = GMiMCEvalGenerator:: { gate_index, diff --git a/src/gates/noop.rs b/src/gates/noop.rs index fa261873..edd4e5dd 100644 --- a/src/gates/noop.rs +++ b/src/gates/noop.rs @@ -35,7 +35,6 @@ impl Gate for NoopGate { &self, _gate_index: usize, _local_constants: &[F], - _next_constants: &[F], ) -> Vec>> { Vec::new() } diff --git a/src/prover.rs b/src/prover.rs index 17b52ded..c7b65cfb 100644 --- a/src/prover.rs +++ b/src/prover.rs @@ -197,9 +197,7 @@ fn compute_vanishing_polys( let vars = EvaluationVars { local_constants, - next_constants, local_wires, - next_wires, }; compute_vanishing_poly_entry( common_data, diff --git a/src/vars.rs b/src/vars.rs index 532ddbfe..f2744e8f 100644 --- a/src/vars.rs +++ b/src/vars.rs @@ -4,15 +4,11 @@ use crate::target::Target; #[derive(Copy, Clone)] pub struct EvaluationVars<'a, F: Field> { pub(crate) local_constants: &'a [F], - pub(crate) next_constants: &'a [F], pub(crate) local_wires: &'a [F], - pub(crate) next_wires: &'a [F], } #[derive(Copy, Clone)] pub struct EvaluationTargets<'a> { pub(crate) local_constants: &'a [Target], - pub(crate) next_constants: &'a [Target], pub(crate) local_wires: &'a [Target], - pub(crate) next_wires: &'a [Target], } From 4f9aa8879bc99b2e0be75fdba24fa15b1d7294bf Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 23 Apr 2021 14:25:24 -0700 Subject: [PATCH 09/14] Properly use the three betas and gammas ... for the three different `Z`s we use. Before I was just using the first value as a temporary thing. --- src/prover.rs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/prover.rs b/src/prover.rs index 17b52ded..c600c598 100644 --- a/src/prover.rs +++ b/src/prover.rs @@ -96,18 +96,14 @@ pub(crate) fn prove( let alphas = challenger.get_n_challenges(num_checks); - // TODO - let beta = betas[0]; - let gamma = gammas[0]; - let start_vanishing_polys = Instant::now(); let vanishing_polys = compute_vanishing_polys( common_data, prover_data, wire_ldes_t, plonk_z_ldes_t, - beta, - gamma, + &betas, + &gammas, &alphas, ); info!( @@ -170,8 +166,8 @@ fn compute_vanishing_polys( prover_data: &ProverOnlyCircuitData, wire_ldes_t: Vec>, plonk_z_lde_t: Vec>, - beta: F, - gamma: F, + betas: &[F], + gammas: &[F], alphas: &[F], ) -> Vec> { let lde_size = common_data.lde_size(); @@ -208,8 +204,8 @@ fn compute_vanishing_polys( local_plonk_zs, next_plonk_zs, s_sigmas, - beta, - gamma, + betas, + gammas, alphas, ) }) @@ -231,8 +227,8 @@ fn compute_vanishing_poly_entry( local_plonk_zs: &[F], next_plonk_zs: &[F], s_sigmas: &[F], - beta: F, - gamma: F, + betas: &[F], + gammas: &[F], alphas: &[F], ) -> Vec { let constraint_terms = @@ -255,8 +251,8 @@ fn compute_vanishing_poly_entry( let k_i = common_data.k_is[j]; let s_id = k_i * x; let s_sigma = s_sigmas[j]; - f_prime *= wire_value + beta * s_id + gamma; - g_prime *= wire_value + beta * s_sigma + gamma; + 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); } From 6d164adc6a1a4949112a107a3208d19473578588 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 23 Apr 2021 14:18:03 -0700 Subject: [PATCH 10/14] Have the prover use the new MerkleTree API Before it was storing leaf data and Merkle roots, but nothing in between, since it wasn't yet interacting with intermediate layers (but it will once we hook up the FRI code). --- src/circuit_builder.rs | 21 +++++++++++++-------- src/circuit_data.rs | 26 ++++++++++++++------------ src/fri.rs | 1 - src/hash.rs | 33 --------------------------------- src/merkle_tree.rs | 12 ++++++++---- src/prover.rs | 26 +++++++++++++------------- src/verifier.rs | 2 +- 7 files changed, 49 insertions(+), 72 deletions(-) diff --git a/src/circuit_builder.rs b/src/circuit_builder.rs index 50ec1a95..a796967a 100644 --- a/src/circuit_builder.rs +++ b/src/circuit_builder.rs @@ -13,11 +13,12 @@ use crate::gates::constant::ConstantGate; use crate::gates::gate::{GateInstance, GateRef}; use crate::gates::noop::NoopGate; use crate::generator::{CopyGenerator, WitnessGenerator}; -use crate::hash::{hash_n_to_hash, merkle_root_bit_rev_order}; +use crate::hash::hash_n_to_hash; use crate::polynomial::polynomial::PolynomialValues; use crate::target::Target; use crate::util::{log2_strict, transpose, transpose_poly_values}; use crate::wire::Wire; +use crate::merkle_tree::MerkleTree; pub struct CircuitBuilder { pub(crate) config: CircuitConfig, @@ -236,20 +237,26 @@ impl CircuitBuilder { let constant_vecs = self.constant_polys(); let constant_ldes = PolynomialValues::lde_multiple(constant_vecs, self.config.rate_bits); let constant_ldes_t = transpose_poly_values(constant_ldes); - let constants_root = merkle_root_bit_rev_order(constant_ldes_t.clone()); + let constants_tree = MerkleTree::new(constant_ldes_t, true); let sigma_vecs = self.sigma_vecs(); let sigma_ldes = PolynomialValues::lde_multiple(sigma_vecs, self.config.rate_bits); let sigma_ldes_t = transpose_poly_values(sigma_ldes); - let sigmas_root = merkle_root_bit_rev_order(sigma_ldes_t.clone()); + let sigmas_tree = MerkleTree::new(sigma_ldes_t, true); + + let constants_root = constants_tree.root; + let sigmas_root = sigmas_tree.root; + let verifier_only = VerifierOnlyCircuitData { + constants_root, + sigmas_root, + }; let generators = self.generators; let prover_only = ProverOnlyCircuitData { generators, - constant_ldes_t, - sigma_ldes_t, + constants_tree, + sigmas_tree, }; - let verifier_only = VerifierOnlyCircuitData {}; // The HashSet of gates will have a non-deterministic order. When converting to a Vec, we // sort by ID to make the ordering deterministic. @@ -274,8 +281,6 @@ impl CircuitBuilder { degree_bits, gates, num_gate_constraints, - constants_root, - sigmas_root, k_is, circuit_digest, }; diff --git a/src/circuit_data.rs b/src/circuit_data.rs index bb21e0c6..e009ced9 100644 --- a/src/circuit_data.rs +++ b/src/circuit_data.rs @@ -5,6 +5,7 @@ use crate::proof::{Hash, HashTarget, Proof}; use crate::prover::prove; use crate::verifier::verify; use crate::witness::PartialWitness; +use crate::merkle_tree::MerkleTree; #[derive(Copy, Clone)] pub struct CircuitConfig { @@ -37,7 +38,7 @@ impl CircuitConfig { /// Circuit data required by the prover or the verifier. pub struct CircuitData { pub(crate) prover_only: ProverOnlyCircuitData, - pub(crate) verifier_only: VerifierOnlyCircuitData, + pub(crate) verifier_only: VerifierOnlyCircuitData, pub(crate) common: CommonCircuitData, } @@ -71,7 +72,7 @@ impl ProverCircuitData { /// Circuit data required by the prover. pub struct VerifierCircuitData { - pub(crate) verifier_only: VerifierOnlyCircuitData, + pub(crate) verifier_only: VerifierOnlyCircuitData, pub(crate) common: CommonCircuitData, } @@ -84,13 +85,20 @@ impl VerifierCircuitData { /// Circuit data required by the prover, but not the verifier. pub(crate) struct ProverOnlyCircuitData { pub generators: Vec>>, - pub constant_ldes_t: Vec>, - /// Transpose of LDEs of sigma polynomials (in the context of Plonk's permutation argument). - pub sigma_ldes_t: Vec>, + /// Merkle tree containing LDEs of each constant polynomial. + pub constants_tree: MerkleTree, + /// Merkle tree containing LDEs of each sigma polynomial. + pub sigmas_tree: MerkleTree, } /// Circuit data required by the verifier, but not the prover. -pub(crate) struct VerifierOnlyCircuitData {} +pub(crate) struct VerifierOnlyCircuitData { + /// A commitment to each constant polynomial. + pub(crate) constants_root: Hash, + + /// A commitment to each permutation polynomial. + pub(crate) sigmas_root: Hash, +} /// Circuit data required by both the prover and the verifier. pub(crate) struct CommonCircuitData { @@ -104,12 +112,6 @@ pub(crate) struct CommonCircuitData { /// The largest number of constraints imposed by any gate. pub(crate) num_gate_constraints: usize, - /// A commitment to each constant polynomial. - pub(crate) constants_root: Hash, - - /// A commitment to each permutation polynomial. - pub(crate) sigmas_root: Hash, - /// The `{k_i}` valued used in `S_ID_i` in Plonk's permutation argument. pub(crate) k_is: Vec, diff --git a/src/fri.rs b/src/fri.rs index 2bb4935d..bea556e6 100644 --- a/src/fri.rs +++ b/src/fri.rs @@ -8,7 +8,6 @@ use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues}; use crate::proof::{FriEvaluations, FriMerkleProofs, FriProof, FriQueryRound, Hash}; use crate::util::log2_strict; use anyhow::{ensure, Result}; -use std::iter::FromIterator; /// Somewhat arbitrary. Smaller values will increase delta, but with diminishing returns, /// while increasing L, potentially requiring more challenge points. diff --git a/src/hash.rs b/src/hash.rs index 786fc57c..a51d1f08 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -1,13 +1,10 @@ //! Concrete instantiation of a hash function. -use rayon::prelude::*; - use crate::circuit_builder::CircuitBuilder; use crate::field::field::Field; use crate::gmimc::gmimc_permute_array; use crate::proof::{Hash, HashTarget}; use crate::target::Target; -use crate::util::reverse_index_bits_in_place; pub(crate) const SPONGE_RATE: usize = 8; pub(crate) const SPONGE_CAPACITY: usize = 4; @@ -245,33 +242,3 @@ pub fn hash_n_to_hash(inputs: Vec, pad: bool) -> Hash { pub fn hash_n_to_1(inputs: Vec, pad: bool) -> F { hash_n_to_m(inputs, 1, pad)[0] } - -/// Like `merkle_root`, but first reorders each vector so that `new[i] = old[i.reverse_bits()]`. -pub(crate) fn merkle_root_bit_rev_order(mut vecs: Vec>) -> Hash { - reverse_index_bits_in_place(&mut vecs); - merkle_root(vecs) -} - -/// Given `n` vectors, each of length `l`, constructs a Merkle tree with `l` leaves, where each leaf -/// is a hash obtained by hashing a "leaf set" consisting of `n` elements. If `n <= 4`, this hashing -/// is skipped, as there is no need to compress leaf data. -pub(crate) fn merkle_root(vecs: Vec>) -> Hash { - let elems_per_leaf = vecs[0].len(); - let leaves_per_chunk = (ELEMS_PER_CHUNK / elems_per_leaf).next_power_of_two(); - let subtree_roots: Vec> = vecs - .par_chunks(leaves_per_chunk) - .map(|chunk| merkle_root_inner(chunk.to_vec()).elements.to_vec()) - .collect(); - merkle_root_inner(subtree_roots) -} - -pub(crate) fn merkle_root_inner(vecs: Vec>) -> Hash { - let mut hashes = vecs.into_iter().map(hash_or_noop).collect::>(); - while hashes.len() > 1 { - hashes = hashes - .chunks(2) - .map(|pair| compress(pair[0], pair[1])) - .collect(); - } - hashes[0] -} diff --git a/src/merkle_tree.rs b/src/merkle_tree.rs index f9a6d8f3..f9f9460c 100644 --- a/src/merkle_tree.rs +++ b/src/merkle_tree.rs @@ -1,3 +1,5 @@ +use rayon::prelude::*; + use crate::field::field::Field; use crate::hash::{compress, hash_or_noop}; use crate::merkle_proofs::MerkleProof; @@ -26,7 +28,7 @@ impl MerkleTree { reverse_index_bits_in_place(&mut leaves); } let mut layers = vec![leaves - .iter() + .par_iter() .map(|l| hash_or_noop(l.clone())) .collect::>()]; while let Some(l) = layers.last() { @@ -34,7 +36,7 @@ impl MerkleTree { break; } let next_layer = l - .chunks(2) + .par_chunks(2) .map(|chunk| compress(chunk[0], chunk[1])) .collect::>(); layers.push(next_layer); @@ -80,11 +82,13 @@ impl MerkleTree { #[cfg(test)] mod tests { - use super::*; + use anyhow::Result; + use crate::field::crandall_field::CrandallField; use crate::merkle_proofs::verify_merkle_proof; use crate::polynomial::division::divide_by_z_h; - use anyhow::Result; + + use super::*; #[test] fn test_merkle_trees() -> Result<()> { diff --git a/src/prover.rs b/src/prover.rs index 8aa511b9..088cd00d 100644 --- a/src/prover.rs +++ b/src/prover.rs @@ -7,7 +7,6 @@ use crate::circuit_data::{CommonCircuitData, ProverOnlyCircuitData}; use crate::field::fft::{fft, ifft}; use crate::field::field::Field; use crate::generator::generate_partial_witness; -use crate::hash::merkle_root_bit_rev_order; use crate::plonk_challenger::Challenger; use crate::plonk_common::{eval_l_1, evaluate_gate_constraints, reduce_with_powers_multi}; use crate::polynomial::division::divide_by_z_h; @@ -17,6 +16,7 @@ use crate::util::{transpose, transpose_poly_values}; use crate::vars::EvaluationVars; use crate::wire::Wire; use crate::witness::PartialWitness; +use crate::merkle_tree::MerkleTree; pub(crate) fn prove( prover_data: &ProverOnlyCircuitData, @@ -61,7 +61,7 @@ pub(crate) fn prove( // TODO: Could avoid cloning if it's significant? let start_wires_root = Instant::now(); - let wires_root = merkle_root_bit_rev_order(wire_ldes_t.clone()); + let wires_tree = MerkleTree::new(wire_ldes_t.clone(), true); info!( "{:.3}s to Merklize wire LDEs", start_wires_root.elapsed().as_secs_f32() @@ -72,7 +72,7 @@ pub(crate) fn prove( // TODO: Need to include public inputs as well. challenger.observe_hash(&common_data.circuit_digest); - challenger.observe_hash(&wires_root); + challenger.observe_hash(&wires_tree.root); let betas = challenger.get_n_challenges(num_checks); let gammas = challenger.get_n_challenges(num_checks); @@ -86,13 +86,13 @@ pub(crate) fn prove( ); let start_plonk_z_root = Instant::now(); - let plonk_zs_root = merkle_root_bit_rev_order(plonk_z_ldes_t.clone()); + let plonk_zs_tree = MerkleTree::new(plonk_z_ldes_t.clone(), true); info!( "{:.3}s to Merklize Z's", start_plonk_z_root.elapsed().as_secs_f32() ); - challenger.observe_hash(&plonk_zs_root); + challenger.observe_hash(&plonk_zs_tree.root); let alphas = challenger.get_n_challenges(num_checks); @@ -125,8 +125,8 @@ pub(crate) fn prove( quotient_poly_coeff_ldes.into_par_iter().map(fft).collect(); all_quotient_poly_chunk_ldes.extend(quotient_poly_chunk_ldes); } - let quotient_polys_root = - merkle_root_bit_rev_order(transpose_poly_values(all_quotient_poly_chunk_ldes)); + let quotient_polys_tree = + MerkleTree::new(transpose_poly_values(all_quotient_poly_chunk_ldes), true); info!( "{:.3}s to compute quotient polys and their LDEs", quotient_polys_start.elapsed().as_secs_f32() @@ -142,9 +142,9 @@ pub(crate) fn prove( ); Proof { - wires_root, - plonk_zs_root, - quotient_polys_root, + wires_root: wires_tree.root, + plonk_zs_root: plonk_zs_tree.root, + quotient_polys_root: quotient_polys_tree.root, openings, fri_proofs, } @@ -182,11 +182,11 @@ fn compute_vanishing_polys( let i_next = (i + 1) % lde_size; let local_wires = &wire_ldes_t[i]; let next_wires = &wire_ldes_t[i_next]; - let local_constants = &prover_data.constant_ldes_t[i]; - let next_constants = &prover_data.constant_ldes_t[i_next]; + let local_constants = &prover_data.constants_tree.leaves[i]; + let next_constants = &prover_data.constants_tree.leaves[i_next]; // TODO: "next" is deprecated let local_plonk_zs = &plonk_z_lde_t[i]; let next_plonk_zs = &plonk_z_lde_t[i_next]; - let s_sigmas = &prover_data.sigma_ldes_t[i]; + let s_sigmas = &prover_data.sigmas_tree.leaves[i]; debug_assert_eq!(local_wires.len(), common_data.config.num_wires); debug_assert_eq!(local_plonk_zs.len(), num_checks); diff --git a/src/verifier.rs b/src/verifier.rs index 64ae2aed..c0afc07f 100644 --- a/src/verifier.rs +++ b/src/verifier.rs @@ -2,7 +2,7 @@ use crate::circuit_data::{CommonCircuitData, VerifierOnlyCircuitData}; use crate::field::field::Field; pub(crate) fn verify( - verifier_data: &VerifierOnlyCircuitData, + verifier_data: &VerifierOnlyCircuitData, common_data: &CommonCircuitData, ) { todo!() From a50ba9f59012d5c1cca89385ddbc9adc87a2f932 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sat, 24 Apr 2021 11:20:07 -0700 Subject: [PATCH 11/14] More unnecessary clones --- src/circuit_builder.rs | 2 +- src/circuit_data.rs | 2 +- src/prover.rs | 22 ++++++++++------------ 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/circuit_builder.rs b/src/circuit_builder.rs index a796967a..d21279d2 100644 --- a/src/circuit_builder.rs +++ b/src/circuit_builder.rs @@ -14,11 +14,11 @@ use crate::gates::gate::{GateInstance, GateRef}; use crate::gates::noop::NoopGate; use crate::generator::{CopyGenerator, WitnessGenerator}; use crate::hash::hash_n_to_hash; +use crate::merkle_tree::MerkleTree; use crate::polynomial::polynomial::PolynomialValues; use crate::target::Target; use crate::util::{log2_strict, transpose, transpose_poly_values}; use crate::wire::Wire; -use crate::merkle_tree::MerkleTree; pub struct CircuitBuilder { pub(crate) config: CircuitConfig, diff --git a/src/circuit_data.rs b/src/circuit_data.rs index e009ced9..f75e7275 100644 --- a/src/circuit_data.rs +++ b/src/circuit_data.rs @@ -1,11 +1,11 @@ use crate::field::field::Field; use crate::gates::gate::GateRef; use crate::generator::WitnessGenerator; +use crate::merkle_tree::MerkleTree; use crate::proof::{Hash, HashTarget, Proof}; use crate::prover::prove; use crate::verifier::verify; use crate::witness::PartialWitness; -use crate::merkle_tree::MerkleTree; #[derive(Copy, Clone)] pub struct CircuitConfig { diff --git a/src/prover.rs b/src/prover.rs index 088cd00d..4801b79d 100644 --- a/src/prover.rs +++ b/src/prover.rs @@ -7,6 +7,7 @@ use crate::circuit_data::{CommonCircuitData, ProverOnlyCircuitData}; use crate::field::fft::{fft, ifft}; use crate::field::field::Field; use crate::generator::generate_partial_witness; +use crate::merkle_tree::MerkleTree; use crate::plonk_challenger::Challenger; use crate::plonk_common::{eval_l_1, evaluate_gate_constraints, reduce_with_powers_multi}; use crate::polynomial::division::divide_by_z_h; @@ -16,7 +17,6 @@ use crate::util::{transpose, transpose_poly_values}; use crate::vars::EvaluationVars; use crate::wire::Wire; use crate::witness::PartialWitness; -use crate::merkle_tree::MerkleTree; pub(crate) fn prove( prover_data: &ProverOnlyCircuitData, @@ -61,7 +61,7 @@ pub(crate) fn prove( // TODO: Could avoid cloning if it's significant? let start_wires_root = Instant::now(); - let wires_tree = MerkleTree::new(wire_ldes_t.clone(), true); + let wires_tree = MerkleTree::new(wire_ldes_t, true); info!( "{:.3}s to Merklize wire LDEs", start_wires_root.elapsed().as_secs_f32() @@ -86,7 +86,7 @@ pub(crate) fn prove( ); let start_plonk_z_root = Instant::now(); - let plonk_zs_tree = MerkleTree::new(plonk_z_ldes_t.clone(), true); + let plonk_zs_tree = MerkleTree::new(plonk_z_ldes_t, true); info!( "{:.3}s to Merklize Z's", start_plonk_z_root.elapsed().as_secs_f32() @@ -100,8 +100,8 @@ pub(crate) fn prove( let vanishing_polys = compute_vanishing_polys( common_data, prover_data, - wire_ldes_t, - plonk_z_ldes_t, + &wires_tree, + &plonk_zs_tree, &betas, &gammas, &alphas, @@ -164,8 +164,8 @@ fn compute_z(common_data: &CommonCircuitData, i: usize) -> Polynomi fn compute_vanishing_polys( common_data: &CommonCircuitData, prover_data: &ProverOnlyCircuitData, - wire_ldes_t: Vec>, - plonk_z_lde_t: Vec>, + wires_tree: &MerkleTree, + plonk_zs_tree: &MerkleTree, betas: &[F], gammas: &[F], alphas: &[F], @@ -180,12 +180,10 @@ fn compute_vanishing_polys( .enumerate() .map(|(i, x)| { let i_next = (i + 1) % lde_size; - let local_wires = &wire_ldes_t[i]; - let next_wires = &wire_ldes_t[i_next]; + let local_wires = &wires_tree.leaves[i]; let local_constants = &prover_data.constants_tree.leaves[i]; - let next_constants = &prover_data.constants_tree.leaves[i_next]; // TODO: "next" is deprecated - let local_plonk_zs = &plonk_z_lde_t[i]; - let next_plonk_zs = &plonk_z_lde_t[i_next]; + let local_plonk_zs = &plonk_zs_tree.leaves[i]; + let next_plonk_zs = &plonk_zs_tree.leaves[i_next]; let s_sigmas = &prover_data.sigmas_tree.leaves[i]; debug_assert_eq!(local_wires.len(), common_data.config.num_wires); From 035d15bc3d3598b07cba92ec78d1b5be1ee734e6 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 23 Apr 2021 10:26:57 -0700 Subject: [PATCH 12/14] Interpolants of arbitrary (point, value) lists Closes #10. This combines Lagrange interpolation with FFTs as mentioned there. I was previously thinking that all our polynomial encodings might as well just use power-of-two length vectors, so they'll be "FFT-ready", with no need to trim/pad. This sort of breaks that assumption though, as e.g. I think we'll want to compute interpolants with three coefficients in the batch opening argument. I think we can still skip trimming/padding in most cases, since it the majority of our polynomials will have power-of-two-minus-1 degrees with high probability. But we'll now have one or two uses where that's not the case. --- src/field/crandall_field.rs | 13 ++++ src/field/fft.rs | 2 +- src/field/field.rs | 21 +++++-- src/field/lagrange.rs | 111 +++++++++++++++++++++++++++++++++++ src/field/mod.rs | 1 + src/polynomial/polynomial.rs | 23 ++++++-- 6 files changed, 158 insertions(+), 13 deletions(-) create mode 100644 src/field/lagrange.rs diff --git a/src/field/crandall_field.rs b/src/field/crandall_field.rs index 0ec184e8..657193d9 100644 --- a/src/field/crandall_field.rs +++ b/src/field/crandall_field.rs @@ -6,6 +6,7 @@ use num::Integer; use crate::field::field::Field; use std::hash::{Hash, Hasher}; +use std::iter::{Product, Sum}; /// EPSILON = 9 * 2**28 - 1 const EPSILON: u64 = 2415919103; @@ -257,6 +258,12 @@ impl AddAssign for CrandallField { } } +impl Sum for CrandallField { + fn sum>(iter: I) -> Self { + iter.fold(Self::ZERO, |acc, x| acc + x) + } +} + impl Sub for CrandallField { type Output = Self; @@ -291,6 +298,12 @@ impl MulAssign for CrandallField { } } +impl Product for CrandallField { + fn product>(iter: I) -> Self { + iter.fold(Self::ONE, |acc, x| acc * x) + } +} + impl Div for CrandallField { type Output = Self; diff --git a/src/field/fft.rs b/src/field/fft.rs index f38ea204..8bcde967 100644 --- a/src/field/fft.rs +++ b/src/field/fft.rs @@ -169,7 +169,7 @@ mod tests { for i in 0..degree { coefficients.push(F::from_canonical_usize(i * 1337 % 100)); } - let coefficients = PolynomialCoeffs::pad(coefficients); + let coefficients = PolynomialCoeffs::new_padded(coefficients); let points = fft(coefficients.clone()); assert_eq!(points, evaluate_naive(&coefficients)); diff --git a/src/field/field.rs b/src/field/field.rs index 6c12bf7d..15a1a9f4 100644 --- a/src/field/field.rs +++ b/src/field/field.rs @@ -1,10 +1,13 @@ -use crate::util::bits_u64; -use rand::rngs::OsRng; -use rand::Rng; use std::fmt::{Debug, Display}; use std::hash::Hash; +use std::iter::{Product, Sum}; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use rand::rngs::OsRng; +use rand::Rng; + +use crate::util::bits_u64; + /// A finite field with prime order less than 2^64. pub trait Field: 'static @@ -14,10 +17,12 @@ pub trait Field: + Neg + Add + AddAssign + + Sum + Sub + SubAssign + Mul + MulAssign + + Product + Div + DivAssign + Debug @@ -42,6 +47,10 @@ pub trait Field: *self == Self::ZERO } + fn is_nonzero(&self) -> bool { + *self != Self::ZERO + } + fn is_one(&self) -> bool { *self == Self::ONE } @@ -90,12 +99,12 @@ pub trait Field: x_inv } - fn primitive_root_of_unity(n_power: usize) -> Self { - assert!(n_power <= Self::TWO_ADICITY); + fn primitive_root_of_unity(n_log: usize) -> Self { + assert!(n_log <= Self::TWO_ADICITY); let base = Self::POWER_OF_TWO_GENERATOR; // TODO: Just repeated squaring should be a bit faster, to avoid conditionals. base.exp(Self::from_canonical_u64( - 1u64 << (Self::TWO_ADICITY - n_power), + 1u64 << (Self::TWO_ADICITY - n_log), )) } diff --git a/src/field/lagrange.rs b/src/field/lagrange.rs new file mode 100644 index 00000000..36241da7 --- /dev/null +++ b/src/field/lagrange.rs @@ -0,0 +1,111 @@ +use crate::field::fft::ifft; +use crate::field::field::Field; +use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues}; +use crate::util::log2_ceil; + +/// Computes the interpolant of an arbitrary list of (point, value) pairs. +/// +/// Note that the implementation assumes that `F` is two-adic, in particular that +/// `2^{F::TWO_ADICITY} >= points.len()`. This leads to a simple FFT-based implementation. +pub(crate) fn interpolant(points: &[(F, F)]) -> PolynomialCoeffs { + let n = points.len(); + let n_log = log2_ceil(n); + let n_padded = 1 << n_log; + + let g = F::primitive_root_of_unity(n_log); + let subgroup = F::cyclic_subgroup_known_order(g, n_padded); + let subgroup_evals = subgroup + .into_iter() + .map(|x| interpolate(points, x)) + .collect(); + + let mut coeffs = ifft(PolynomialValues { + values: subgroup_evals, + }); + coeffs.trim(); + coeffs +} + +/// Interpolate the polynomial defined by an arbitrary set of (point, value) pairs at the given +/// point `x`. +fn interpolate(points: &[(F, F)], x: F) -> F { + (0..points.len()) + .map(|i| { + let y_i = points[i].1; + let l_i_x = eval_basis(points, i, x); + y_i * l_i_x + }) + .sum() +} + +/// Evaluate the `i`th Lagrange basis, i.e. the one that vanishes except on the `i`th point. +fn eval_basis(points: &[(F, F)], i: usize, x: F) -> F { + let n = points.len(); + let x_i = points[i].0; + let mut numerator = F::ONE; + let mut denominator_parts = Vec::with_capacity(n - 1); + + for j in 0..n { + if i != j { + let x_j = points[j].0; + numerator *= x - x_j; + denominator_parts.push(x_i - x_j); + } + } + + let denominator_inv = F::batch_multiplicative_inverse(&denominator_parts) + .into_iter() + .product(); + numerator * denominator_inv +} + +#[cfg(test)] +mod tests { + use crate::field::crandall_field::CrandallField; + use crate::field::field::Field; + use crate::field::lagrange::interpolant; + use crate::polynomial::polynomial::PolynomialCoeffs; + + #[test] + fn interpolant_random() { + type F = CrandallField; + + for deg in 0..10 { + let domain = (0..deg).map(|_| F::rand()).collect::>(); + let coeffs = (0..deg).map(|_| F::rand()).collect(); + let coeffs = PolynomialCoeffs { coeffs }; + + let points = eval_naive(&coeffs, &domain); + assert_eq!(interpolant(&points), coeffs); + } + } + + #[test] + fn interpolant_random_overspecified() { + type F = CrandallField; + + for deg in 0..10 { + let points = deg + 5; + let domain = (0..points).map(|_| F::rand()).collect::>(); + let coeffs = (0..deg).map(|_| F::rand()).collect(); + let coeffs = PolynomialCoeffs { coeffs }; + + let points = eval_naive(&coeffs, &domain); + assert_eq!(interpolant(&points), coeffs); + } + } + + fn eval_naive(coeffs: &PolynomialCoeffs, domain: &[F]) -> Vec<(F, F)> { + domain + .iter() + .map(|&x| { + let eval = x + .powers() + .zip(&coeffs.coeffs) + .map(|(x_power, &coeff)| coeff * x_power) + .sum(); + (x, eval) + }) + .collect() + } +} diff --git a/src/field/mod.rs b/src/field/mod.rs index 9f58ef08..6c2828a6 100644 --- a/src/field/mod.rs +++ b/src/field/mod.rs @@ -2,6 +2,7 @@ pub(crate) mod cosets; pub mod crandall_field; pub mod fft; pub mod field; +pub(crate) mod lagrange; #[cfg(test)] mod field_testing; diff --git a/src/polynomial/polynomial.rs b/src/polynomial/polynomial.rs index 0444b1e8..7034a8b9 100644 --- a/src/polynomial/polynomial.rs +++ b/src/polynomial/polynomial.rs @@ -2,7 +2,7 @@ use crate::field::fft::{fft, ifft}; use crate::field::field::Field; use crate::util::log2_strict; -/// A polynomial in point-value form. The number of values must be a power of two. +/// A polynomial in point-value form. /// /// The points are implicitly `g^i`, where `g` generates the subgroup whose size equals the number /// of points. @@ -13,7 +13,6 @@ pub struct PolynomialValues { impl PolynomialValues { pub fn new(values: Vec) -> Self { - assert!(values.len().is_power_of_two()); PolynomialValues { values } } @@ -36,7 +35,7 @@ impl PolynomialValues { } } -/// A polynomial in coefficient form. The number of coefficients must be a power of two. +/// A polynomial in coefficient form. #[derive(Clone, Debug, Eq, PartialEq)] pub struct PolynomialCoeffs { pub(crate) coeffs: Vec, @@ -44,11 +43,11 @@ pub struct PolynomialCoeffs { impl PolynomialCoeffs { pub fn new(coeffs: Vec) -> Self { - assert!(coeffs.len().is_power_of_two()); PolynomialCoeffs { coeffs } } - pub(crate) fn pad(mut coeffs: Vec) -> Self { + /// Create a new polynomial with its coefficient list padded to the next power of two. + pub(crate) fn new_padded(mut coeffs: Vec) -> Self { while !coeffs.len().is_power_of_two() { coeffs.push(F::ZERO); } @@ -70,7 +69,6 @@ impl PolynomialCoeffs { } pub(crate) fn chunks(&self, chunk_size: usize) -> Vec { - assert!(chunk_size.is_power_of_two()); self.coeffs .chunks(chunk_size) .map(|chunk| PolynomialCoeffs::new(chunk.to_vec())) @@ -97,4 +95,17 @@ impl PolynomialCoeffs { } Self { coeffs } } + + /// Removes leading zero coefficients. + pub fn trim(&mut self) { + self.coeffs.drain(self.degree_plus_one()..); + } + + /// Degree of the polynomial + 1. + fn degree_plus_one(&self) -> usize { + (0usize..self.len()) + .rev() + .find(|&i| self.coeffs[i].is_nonzero()) + .map_or(0, |i| i + 1) + } } From 06bb902f23f8cd6a9dd3a6a12dc44fb24182bdf3 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sat, 24 Apr 2021 20:11:00 -0700 Subject: [PATCH 13/14] Barycentric formula --- src/field/lagrange.rs | 77 +++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 28 deletions(-) diff --git a/src/field/lagrange.rs b/src/field/lagrange.rs index 36241da7..06204b60 100644 --- a/src/field/lagrange.rs +++ b/src/field/lagrange.rs @@ -3,7 +3,7 @@ use crate::field::field::Field; use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues}; use crate::util::log2_ceil; -/// Computes the interpolant of an arbitrary list of (point, value) pairs. +/// Computes the unique degree < n interpolant of an arbitrary list of n (point, value) pairs. /// /// Note that the implementation assumes that `F` is two-adic, in particular that /// `2^{F::TWO_ADICITY} >= points.len()`. This leads to a simple FFT-based implementation. @@ -14,9 +14,10 @@ pub(crate) fn interpolant(points: &[(F, F)]) -> PolynomialCoeffs { let g = F::primitive_root_of_unity(n_log); let subgroup = F::cyclic_subgroup_known_order(g, n_padded); + let barycentric_weights = barycentric_weights(points); let subgroup_evals = subgroup .into_iter() - .map(|x| interpolate(points, x)) + .map(|x| interpolate(points, x, &barycentric_weights)) .collect(); let mut coeffs = ifft(PolynomialValues { @@ -28,35 +29,39 @@ pub(crate) fn interpolant(points: &[(F, F)]) -> PolynomialCoeffs { /// Interpolate the polynomial defined by an arbitrary set of (point, value) pairs at the given /// point `x`. -fn interpolate(points: &[(F, F)], x: F) -> F { - (0..points.len()) - .map(|i| { - let y_i = points[i].1; - let l_i_x = eval_basis(points, i, x); - y_i * l_i_x - }) - .sum() -} - -/// Evaluate the `i`th Lagrange basis, i.e. the one that vanishes except on the `i`th point. -fn eval_basis(points: &[(F, F)], i: usize, x: F) -> F { - let n = points.len(); - let x_i = points[i].0; - let mut numerator = F::ONE; - let mut denominator_parts = Vec::with_capacity(n - 1); - - for j in 0..n { - if i != j { - let x_j = points[j].0; - numerator *= x - x_j; - denominator_parts.push(x_i - x_j); +fn interpolate(points: &[(F, F)], x: F, barycentric_weights: &[F]) -> F { + // If x is in the list of points, the Lagrange formula would divide by zero. + for &(x_i, y_i) in points { + if x_i == x { + return y_i; } } - let denominator_inv = F::batch_multiplicative_inverse(&denominator_parts) - .into_iter() - .product(); - numerator * denominator_inv + let l_x: F = points.iter().map(|&(x_i, y_i)| x - x_i).product(); + + let sum = (0..points.len()) + .map(|i| { + let x_i = points[i].0; + let y_i = points[i].1; + let w_i = barycentric_weights[i]; + w_i / (x - x_i) * y_i + }) + .sum(); + + l_x * sum +} + +fn barycentric_weights(points: &[(F, F)]) -> Vec { + let n = points.len(); + (0..n) + .map(|i| { + (0..n) + .filter(|&j| j != i) + .map(|j| points[i].0 - points[j].0) + .product::() + .inverse() + }) + .collect() } #[cfg(test)] @@ -80,6 +85,22 @@ mod tests { } } + #[test] + fn interpolant_random_roots_of_unity() { + type F = CrandallField; + + for deg_log in 0..4 { + let deg = 1 << deg_log; + let g = F::primitive_root_of_unity(deg_log); + let domain = F::cyclic_subgroup_known_order(g, deg); + let coeffs = (0..deg).map(|_| F::rand()).collect(); + let coeffs = PolynomialCoeffs { coeffs }; + + let points = eval_naive(&coeffs, &domain); + assert_eq!(interpolant(&points), coeffs); + } + } + #[test] fn interpolant_random_overspecified() { type F = CrandallField; From 110a7bc6d9168fac3fd3a232dd9d7a02817931f9 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 25 Apr 2021 18:09:43 -0700 Subject: [PATCH 14/14] Fill in a few missing field methods --- src/field/field.rs | 68 ++++++++++++++++++++++++++++++++------ src/field/field_testing.rs | 25 ++++++++++++++ 2 files changed, 82 insertions(+), 11 deletions(-) diff --git a/src/field/field.rs b/src/field/field.rs index 15a1a9f4..cafc536a 100644 --- a/src/field/field.rs +++ b/src/field/field.rs @@ -3,8 +3,9 @@ use std::hash::Hash; use std::iter::{Product, Sum}; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; -use rand::rngs::OsRng; +use num::Integer; use rand::Rng; +use rand::rngs::OsRng; use crate::util::bits_u64; @@ -101,11 +102,11 @@ pub trait Field: fn primitive_root_of_unity(n_log: usize) -> Self { assert!(n_log <= Self::TWO_ADICITY); - let base = Self::POWER_OF_TWO_GENERATOR; - // TODO: Just repeated squaring should be a bit faster, to avoid conditionals. - base.exp(Self::from_canonical_u64( - 1u64 << (Self::TWO_ADICITY - n_log), - )) + let mut base = Self::POWER_OF_TWO_GENERATOR; + for _ in n_log..Self::TWO_ADICITY { + base = base.square(); + } + base } /// Computes a multiplicative subgroup whose order is known in advance. @@ -144,6 +145,10 @@ pub trait Field: fn from_canonical_u64(n: u64) -> Self; + fn from_canonical_u32(n: u32) -> Self { + Self::from_canonical_u64(n as u64) + } + fn from_canonical_usize(n: usize) -> Self { Self::from_canonical_u64(n as u64) } @@ -165,18 +170,59 @@ pub trait Field: product } + fn exp_u32(&self, power: u32) -> Self { + self.exp(Self::from_canonical_u32(power)) + } + fn exp_usize(&self, power: usize) -> Self { self.exp(Self::from_canonical_usize(power)) } - fn kth_root(&self, k: usize) -> Self { - let p_minus_1 = Self::ORDER - 1; - debug_assert!(p_minus_1 % k as u64 != 0, "Not a permutation in this field"); - todo!() + /// Returns whether `x^power` is a permutation of this field. + fn is_monomial_permutation(power: Self) -> bool { + if power.is_zero() { + return false; + } + if power.is_one() { + return true; + } + (Self::ORDER - 1).gcd(&power.to_canonical_u64()) == 1 + } + + fn kth_root(&self, k: Self) -> Self { + let p = Self::ORDER; + let p_minus_1 = p - 1; + debug_assert!( + Self::is_monomial_permutation(k), + "Not a permutation of this field" + ); + let k = k.to_canonical_u64(); + + // By Fermat's little theorem, x^p = x and x^(p - 1) = 1, so x^(p + n(p - 1)) = x for any n. + // Our assumption that the k'th root operation is a permutation implies gcd(p - 1, k) = 1, + // so there exists some n such that p + n(p - 1) is a multiple of k. Once we find such an n, + // we can rewrite the above as + // x^((p + n(p - 1))/k)^k = x, + // implying that x^((p + n(p - 1))/k) is a k'th root of x. + for n in 0..k { + let numerator = p as u128 + n as u128 * p_minus_1 as u128; + if numerator % k as u128 == 0 { + let power = (numerator / k as u128) as u64 % p_minus_1; + return self.exp(Self::from_canonical_u64(power)); + } + } + panic!( + "x^{} and x^(1/{}) are not permutations of this field, or we have a bug!", + k, k + ); + } + + fn kth_root_u32(&self, k: u32) -> Self { + self.kth_root(Self::from_canonical_u32(k)) } fn cube_root(&self) -> Self { - self.kth_root(3) + self.kth_root_u32(3) } fn powers(&self) -> Powers { diff --git a/src/field/field_testing.rs b/src/field/field_testing.rs index 59eee681..bcb190e8 100644 --- a/src/field/field_testing.rs +++ b/src/field/field_testing.rs @@ -280,6 +280,31 @@ macro_rules! test_arithmetic { assert_eq!(<$field>::from_canonical_u64(4).bits(), 3); assert_eq!(<$field>::from_canonical_u64(5).bits(), 3); } + + #[test] + fn exponentiation() { + type F = $field; + + assert_eq!(F::ZERO.exp_u32(0), ::ONE); + assert_eq!(F::ONE.exp_u32(0), ::ONE); + assert_eq!(F::TWO.exp_u32(0), ::ONE); + + assert_eq!(F::ZERO.exp_u32(1), ::ZERO); + assert_eq!(F::ONE.exp_u32(1), ::ONE); + assert_eq!(F::TWO.exp_u32(1), ::TWO); + + assert_eq!(F::ZERO.kth_root_u32(1), ::ZERO); + assert_eq!(F::ONE.kth_root_u32(1), ::ONE); + assert_eq!(F::TWO.kth_root_u32(1), ::TWO); + + for power in 1..10 { + let power = F::from_canonical_u32(power); + if F::is_monomial_permutation(power) { + let x = F::rand(); + assert_eq!(x.exp(power).kth_root(power), x); + } + } + } } }; }