From 347206d16186fe2d839a7f6046168cf4cfbe325a Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Tue, 30 Mar 2021 23:12:47 -0700 Subject: [PATCH] Add Z terms in vanishing poly --- Cargo.toml | 2 ++ src/circuit_builder.rs | 13 ++++++--- src/circuit_data.rs | 5 ++++ src/field/crandall_field.rs | 33 ++++++++++++----------- src/field/field.rs | 22 +++++++++++++-- src/main.rs | 1 + src/partition.rs | 23 ++++++++++++++++ src/plonk_common.rs | 20 ++++++++++++++ src/prover.rs | 54 ++++++++++++++++++++++++++++++++----- 9 files changed, 146 insertions(+), 27 deletions(-) create mode 100644 src/partition.rs diff --git a/Cargo.toml b/Cargo.toml index 82e21110..7b9a54fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,8 @@ edition = "2018" env_logger = "0.8.3" log = "0.4.14" num = "0.3" +rand = "0.7.3" +rand_chacha = "0.2.2" rayon = "1.5.0" unroll = "0.1.5" diff --git a/src/circuit_builder.rs b/src/circuit_builder.rs index 8a242db5..49360c65 100644 --- a/src/circuit_builder.rs +++ b/src/circuit_builder.rs @@ -14,6 +14,7 @@ use crate::polynomial::polynomial::PolynomialValues; use crate::target::Target; use crate::util::{log2_strict, transpose, transpose_poly_values}; use crate::wire::Wire; +use crate::partition::get_subgroup_shift; pub struct CircuitBuilder { pub(crate) config: CircuitConfig, @@ -148,7 +149,7 @@ impl CircuitBuilder { } fn sigma_vecs(&self) -> Vec> { - vec![PolynomialValues::zero(self.gate_instances.len())] // TODO + vec![PolynomialValues::zero(self.gate_instances.len()); self.config.num_routed_wires] // TODO } /// Builds a "full circuit", with both prover and verifier data. @@ -166,10 +167,11 @@ impl CircuitBuilder { let sigma_vecs = self.sigma_vecs(); let sigma_ldes = PolynomialValues::lde_multiple(sigma_vecs, self.config.rate_bits); - let sigmas_root = merkle_root_bit_rev_order(transpose_poly_values(sigma_ldes)); + let sigma_ldes_t = transpose_poly_values(sigma_ldes); + let sigmas_root = merkle_root_bit_rev_order(sigma_ldes_t.clone()); let generators = self.get_generators(); - let prover_only = ProverOnlyCircuitData { generators, constant_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 @@ -182,6 +184,10 @@ impl CircuitBuilder { .max() .expect("No gates?"); + let k_is = (0..self.config.num_routed_wires) + .map(get_subgroup_shift) + .collect(); + let common = CommonCircuitData { config: self.config, degree_bits: log2_strict(degree), @@ -189,6 +195,7 @@ impl CircuitBuilder { num_gate_constraints, constants_root, sigmas_root, + k_is, }; info!("Building circuit took {}s", start.elapsed().as_secs_f32()); diff --git a/src/circuit_data.rs b/src/circuit_data.rs index 74bf2fcb..857dbbb4 100644 --- a/src/circuit_data.rs +++ b/src/circuit_data.rs @@ -81,6 +81,8 @@ impl VerifierCircuitData { 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>, } /// Circuit data required by the verifier, but not the prover. @@ -102,6 +104,9 @@ pub(crate) struct CommonCircuitData { /// A commitment to each permutation polynomial. pub(crate) sigmas_root: Hash, + + /// {k_i}. See `get_subgroup_shift`. + pub(crate) k_is: Vec, } impl CommonCircuitData { diff --git a/src/field/crandall_field.rs b/src/field/crandall_field.rs index b0fb2b01..f4f61699 100644 --- a/src/field/crandall_field.rs +++ b/src/field/crandall_field.rs @@ -6,11 +6,6 @@ use num::Integer; use crate::field::field::Field; -/// P = 2**64 - EPSILON -/// = 2**64 - 9 * 2**28 + 1 -/// = 2**28 * (2**36 - 9) + 1 -const P: u64 = 18446744071293632513; - /// EPSILON = 9 * 2**28 - 1 const EPSILON: u64 = 2415919103; @@ -19,6 +14,13 @@ const TWO_ADICITY: usize = 28; const POWER_OF_TWO_GENERATOR: CrandallField = CrandallField(10281950781551402419); /// A field designed for use with the Crandall reduction algorithm. +/// +/// Its order is +/// ``` +/// P = 2**64 - EPSILON +/// = 2**64 - 9 * 2**28 + 1 +/// = 2**28 * (2**36 - 9) + 1 +/// ``` // TODO: [Partial]Eq should compare canonical representations. #[derive(Copy, Clone)] pub struct CrandallField(pub u64); @@ -47,8 +49,9 @@ impl Field for CrandallField { const ZERO: Self = Self(0); const ONE: Self = Self(1); const TWO: Self = Self(2); - const NEG_ONE: Self = Self(P - 1); + const NEG_ONE: Self = Self(Self::ORDER - 1); + const ORDER: u64 = 18446744071293632513; const MULTIPLICATIVE_SUBGROUP_GENERATOR: Self = Self(5); // TODO: Double check. #[inline(always)] @@ -70,7 +73,7 @@ impl Field for CrandallField { // Applications to Cryptography". let mut u = self.0; - let mut v = P; + let mut v = Self::ORDER; let mut b = 1; let mut c = 0; @@ -78,7 +81,7 @@ impl Field for CrandallField { while u.is_even() { u >>= 1; if b.is_odd() { - b += P; + b += Self::ORDER; } b >>= 1; } @@ -86,7 +89,7 @@ impl Field for CrandallField { while v.is_even() { v >>= 1; if c.is_odd() { - c += P; + c += Self::ORDER; } c >>= 1; } @@ -94,13 +97,13 @@ impl Field for CrandallField { if u < v { v -= u; if c < b { - c += P; + c += Self::ORDER; } c -= b; } else { u -= v; if b < c { - b += P; + b += Self::ORDER; } b -= c; } @@ -145,8 +148,8 @@ impl Neg for CrandallField { #[inline] fn neg(self) -> Self { - let (diff, under) = P.overflowing_sub(self.0); - Self(diff.overflowing_add((under as u64) * P).0) + let (diff, under) = Self::ORDER.overflowing_sub(self.0); + Self(diff.overflowing_add((under as u64) * Self::ORDER).0) } } @@ -156,7 +159,7 @@ impl Add for CrandallField { #[inline] fn add(self, rhs: Self) -> Self { let (sum, over) = self.0.overflowing_add(rhs.0); - Self(sum.overflowing_sub((over as u64) * P).0) + Self(sum.overflowing_sub((over as u64) * Self::ORDER).0) } } @@ -172,7 +175,7 @@ impl Sub for CrandallField { #[inline] fn sub(self, rhs: Self) -> Self { let (diff, under) = self.0.overflowing_sub(rhs.0); - Self(diff.overflowing_add((under as u64) * P).0) + Self(diff.overflowing_add((under as u64) * Self::ORDER).0) } } diff --git a/src/field/field.rs b/src/field/field.rs index 07dd79f8..0037783e 100644 --- a/src/field/field.rs +++ b/src/field/field.rs @@ -1,5 +1,6 @@ use std::fmt::{Debug, Display}; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use rand::Rng; /// A finite field with prime order less than 2^64. pub trait Field: 'static @@ -23,11 +24,24 @@ pub trait Field: 'static const TWO: Self; const NEG_ONE: Self; + const ORDER: u64; const MULTIPLICATIVE_SUBGROUP_GENERATOR: Self; - fn sq(&self) -> Self; + fn is_zero(&self) -> bool { + *self == Self::ZERO + } - fn cube(&self) -> Self; + fn is_one(&self) -> bool { + *self == Self::ONE + } + + fn sq(&self) -> Self { + *self * *self + } + + fn cube(&self) -> Self { + *self * *self * *self + } /// Compute the multiplicative inverse of this field element. fn try_inverse(&self) -> Option; @@ -97,4 +111,8 @@ pub trait Field: 'static fn exp_usize(&self, power: usize) -> Self { self.exp(Self::from_canonical_usize(power)) } + + fn rand_from_rng(rng: &mut R) -> Self { + Self::from_canonical_u64(rng.gen_range(0, Self::ORDER)) + } } diff --git a/src/main.rs b/src/main.rs index 875c6391..9a4bf678 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,7 @@ mod gates; mod generator; mod gmimc; mod hash; +mod partition; mod plonk_common; mod polynomial; mod proof; diff --git a/src/partition.rs b/src/partition.rs new file mode 100644 index 00000000..a01eeefa --- /dev/null +++ b/src/partition.rs @@ -0,0 +1,23 @@ +use rand::SeedableRng; +use rand_chacha::ChaCha8Rng; + +use crate::field::field::Field; + +/// Returns `k_i`, the multiplier used in `S_ID_i` in the context of Plonk's permutation argument. +// TODO: This is copied from plonky1, but may need revisiting. Is the random approach still OK now +// that our field is smaller? +pub(crate) fn get_subgroup_shift(i: usize) -> F { + // The optimized variant of Plonk's permutation argument calls for NUM_ROUTED_WIRES shifts, + // k_1, ..., k_n, which result in distinct cosets. The paper suggests a method which is + // fairly straightforward when only three shifts are needed, but seems a bit complex and + // expensive if more are needed. + + // We will "cheat" and just use random field elements. Since our subgroup has |F*|/degree + // possible cosets, the probability of a collision is negligible for large fields. + + // Unlike what's shown in the Plonk paper, we do not set k_1=1 to "randomize" the + // sigmas polynomials evaluations and making them fit in both fields with high probability. + // TODO: Go back to k_1=1 if we change the way we deal with values not fitting in both fields. + let mut rng = ChaCha8Rng::seed_from_u64(i as u64); + F::rand_from_rng(&mut rng) +} diff --git a/src/plonk_common.rs b/src/plonk_common.rs index a4d08930..6c5c9384 100644 --- a/src/plonk_common.rs +++ b/src/plonk_common.rs @@ -2,6 +2,26 @@ use crate::circuit_builder::CircuitBuilder; use crate::field::field::Field; use crate::target::Target; +/// Evaluate the polynomial which vanishes on any multiplicative subgroup of a given order `n`. +pub(crate) fn eval_zero_poly(n: usize, x: F) -> F { + // Z(x) = x^n - 1 + x.exp_usize(n) - F::ONE +} + +/// Evaluate the Lagrange basis `L_1` with `L_1(1) = 1`, and `L_1(x) = 0` for other members of an +/// order `n` multiplicative subgroup. +pub(crate) fn eval_l_1(n: usize, x: F) -> F { + if x.is_one() { + // The code below would divide by zero, since we have (x - 1) in both the numerator and + // denominator. + return F::ONE; + } + + // L_1(x) = (x^n - 1) / (n * (x - 1)) + // = Z(x) / (n * (x - 1)) + eval_zero_poly(n, x) / (F::from_canonical_usize(n) * (x - F::ONE)) +} + pub(crate) fn reduce_with_powers(terms: Vec, alpha: F) -> F { let mut sum = F::ZERO; for &term in terms.iter().rev() { diff --git a/src/prover.rs b/src/prover.rs index 070883d7..a0d6979d 100644 --- a/src/prover.rs +++ b/src/prover.rs @@ -9,7 +9,7 @@ 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_common::reduce_with_powers; +use crate::plonk_common::{reduce_with_powers, eval_l_1}; use crate::polynomial::division::divide_by_z_h; use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues}; use crate::proof::Proof; @@ -56,11 +56,13 @@ pub(crate) fn prove( let plonk_z_ldes_t = transpose_poly_values(plonk_z_ldes); let plonk_z_root = merkle_root_bit_rev_order(plonk_z_ldes_t.clone()); + let beta = F::ZERO; // TODO + let gamma = F::ZERO; // TODO let alpha = F::ZERO; // TODO let start_vanishing_poly = Instant::now(); let vanishing_poly = compute_vanishing_poly( - common_data, prover_data, wire_ldes_t, plonk_z_ldes_t, alpha); + common_data, prover_data, wire_ldes_t, plonk_z_ldes_t, beta, gamma, alpha); info!("Computing vanishing poly took {}s", start_vanishing_poly.elapsed().as_secs_f32()); let quotient_poly_start = Instant::now(); @@ -102,6 +104,8 @@ fn compute_vanishing_poly( prover_data: &ProverOnlyCircuitData, wire_ldes_t: Vec>, plonk_z_lde_t: Vec>, + beta: F, + gamma: F, alpha: F, ) -> PolynomialValues { let lde_size = common_data.lde_size(); @@ -119,6 +123,7 @@ fn compute_vanishing_poly( let next_constants = &prover_data.constant_ldes_t[i_next]; 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]; debug_assert_eq!(local_wires.len(), common_data.config.num_wires); debug_assert_eq!(local_plonk_zs.len(), common_data.config.num_checks); @@ -130,7 +135,7 @@ fn compute_vanishing_poly( next_wires, }; result.push(compute_vanishing_poly_entry( - common_data, vars, local_plonk_zs, next_plonk_zs, alpha)); + common_data, point, vars, local_plonk_zs, next_plonk_zs, s_sigmas, beta, gamma, alpha)); point *= lde_gen; } @@ -138,17 +143,52 @@ fn compute_vanishing_poly( PolynomialValues::new(result) } +/// Evaluate the vanishing polynomial at `x`. In this context, the vanishing polynomial is a random +/// linear combination of gate constraints, plus some other terms relating to the permutation +/// argument. All such terms should vanish on `H`. fn compute_vanishing_poly_entry( common_data: &CommonCircuitData, + x: F, vars: EvaluationVars, local_plonk_zs: &[F], next_plonk_zs: &[F], + s_sigmas: &[F], + beta: F, + gamma: F, alpha: F, ) -> F { - let mut constraints = Vec::with_capacity(common_data.total_constraints()); - // TODO: Add Z constraints. - constraints.extend(common_data.evaluate(vars)); - reduce_with_powers(constraints, alpha) + let constraint_terms = common_data.evaluate(vars); + + // The L_1(x) (Z(x) - 1) vanishing terms. + let mut vanishing_z_1_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_checks { + let z_x = local_plonk_zs[i]; + let z_gz = next_plonk_zs[i]; + vanishing_z_1_terms.push(eval_l_1(common_data.degree(), 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 + beta * s_id + gamma; + g_prime *= wire_value + beta * s_sigma + gamma; + } + vanishing_v_shift_terms.push(f_prime * z_x - g_prime * z_gz); + } + + let vanishing_terms = [ + vanishing_z_1_terms, + vanishing_v_shift_terms, + constraint_terms, + ].concat(); + + reduce_with_powers(vanishing_terms, alpha) } fn compute_wire_lde(