diff --git a/src/circuit_builder.rs b/src/circuit_builder.rs index b9d6a909..bcabe077 100644 --- a/src/circuit_builder.rs +++ b/src/circuit_builder.rs @@ -10,7 +10,7 @@ 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::partition::get_subgroup_shift; +use crate::field::cosets::get_unique_coset_shifts; use crate::polynomial::polynomial::PolynomialValues; use crate::target::Target; use crate::util::{log2_strict, transpose, transpose_poly_values}; @@ -188,13 +188,12 @@ impl CircuitBuilder { .max() .expect("No gates?"); - let k_is = (0..self.config.num_routed_wires) - .map(get_subgroup_shift) - .collect(); + let degree_bits = log2_strict(degree); + let k_is = get_unique_coset_shifts(degree_bits, self.config.num_routed_wires); let common = CommonCircuitData { config: self.config, - degree_bits: log2_strict(degree), + degree_bits, gates, num_gate_constraints, constants_root, diff --git a/src/circuit_data.rs b/src/circuit_data.rs index 6d14ce79..845bfe2f 100644 --- a/src/circuit_data.rs +++ b/src/circuit_data.rs @@ -1,10 +1,8 @@ -use crate::circuit_builder::CircuitBuilder; use crate::field::field::Field; use crate::gates::gate::GateRef; use crate::generator::WitnessGenerator; -use crate::proof::{Hash, Proof, HashTarget}; +use crate::proof::{Hash, HashTarget, Proof}; use crate::prover::prove; -use crate::target::Target; use crate::verifier::verify; use crate::witness::PartialWitness; @@ -112,7 +110,7 @@ pub(crate) struct CommonCircuitData { /// A commitment to each permutation polynomial. pub(crate) sigmas_root: Hash, - /// {k_i}. See `get_subgroup_shift`. + /// The `{k_i}` valued used in `S_ID_i` in Plonk's permutation argument. pub(crate) k_is: Vec, } diff --git a/src/field/cosets.rs b/src/field/cosets.rs new file mode 100644 index 00000000..7b0d5bcc --- /dev/null +++ b/src/field/cosets.rs @@ -0,0 +1,81 @@ +use std::collections::HashSet; + +use rand::SeedableRng; +use rand_chacha::ChaCha8Rng; + +use crate::field::field::Field; + +/// Finds a set of shifts that result in unique cosets for the subgroup of size `2^subgroup_bits`. +pub(crate) fn get_unique_coset_shifts( + subgroup_bits: usize, + num_shifts: usize, +) -> Vec { + let mut rng = ChaCha8Rng::seed_from_u64(0); + + let generator = F::primitive_root_of_unity(subgroup_bits); + let subgroup_size = 1 << subgroup_bits; + + let mut shifts = Vec::with_capacity(num_shifts); + + // We start with the trivial coset. This isn't necessary, but there may be a slight cost + // savings, since multiplication by 1 can be free in some settings. + shifts.push(F::ONE); + + let subgroup = F::cyclic_subgroup_known_order(generator, subgroup_size) + .into_iter() + .collect::>(); + + while shifts.len() < num_shifts { + let candidate_shift = F::rand_from_rng(&mut rng); + if candidate_shift.is_zero() { + continue; + } + let candidate_shift_inv = candidate_shift.inverse(); + + // If this coset was not disjoint from the others, then there would exist some i, j with + // candidate_shift g^i = existing_shift g^j + // or + // existing_shift / candidate_shift = g^(i - j). + // In other words, `existing_shift / candidate_shift` would be in the subgroup. + let quotients = shifts.iter() + .map(|&shift| shift * candidate_shift_inv) + .collect::>(); + + if quotients.is_disjoint(&subgroup) { + shifts.push(candidate_shift); + } + } + + shifts +} + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + + use crate::field::cosets::get_unique_coset_shifts; + use crate::field::crandall_field::CrandallField; + use crate::field::field::Field; + + #[test] + fn distinct_cosets() { + // TODO: Switch to a smaller test field so that collision rejection is likely to occur. + + type F = CrandallField; + const SUBGROUP_BITS: usize = 5; + const NUM_SHIFTS: usize = 50; + + let generator = F::primitive_root_of_unity(SUBGROUP_BITS); + let subgroup_size = 1 << SUBGROUP_BITS; + + let shifts = get_unique_coset_shifts::(SUBGROUP_BITS, NUM_SHIFTS); + + let mut union = HashSet::new(); + for shift in shifts { + let coset = F::cyclic_subgroup_coset_known_order(generator, shift, subgroup_size); + assert!( + coset.into_iter().all(|x| union.insert(x)), + "Duplicate element!"); + } + } +} diff --git a/src/field/crandall_field.rs b/src/field/crandall_field.rs index 4fc839bf..2267e128 100644 --- a/src/field/crandall_field.rs +++ b/src/field/crandall_field.rs @@ -5,6 +5,7 @@ use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssi use num::Integer; use crate::field::field::Field; +use std::hash::{Hash, Hasher}; /// EPSILON = 9 * 2**28 - 1 const EPSILON: u64 = 2415919103; @@ -17,7 +18,6 @@ const EPSILON: u64 = 2415919103; /// = 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); @@ -29,6 +29,12 @@ impl PartialEq for CrandallField { impl Eq for CrandallField {} +impl Hash for CrandallField { + fn hash(&self, state: &mut H) { + state.write_u64(self.to_canonical_u64()) + } +} + impl Display for CrandallField { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { Display::fmt(&self.0, f) @@ -64,59 +70,69 @@ impl Field for CrandallField { } fn try_inverse(&self) -> Option { - if *self == Self::ZERO { + if self.is_zero() { return None; } // Based on Algorithm 16 of "Efficient Software-Implementation of Finite Fields with // Applications to Cryptography". - let mut u = self.0; - let mut v = Self::ORDER; - let mut b = 1; - let mut c = 0; + let p = Self::ORDER; + let mut u = self.to_canonical_u64(); + let mut v = p; + let mut b = 1u64; + let mut c = 0u64; while u != 1 && v != 1 { while u.is_even() { - u >>= 1; + u /= 2; if b.is_even() { - b >>= 1; + b /= 2; } else { // b = (b + p)/2, avoiding overflow - b = (b >> 1) + (Self::ORDER >> 1) + 1; + b = (b / 2) + (p / 2) + 1; } } while v.is_even() { - v >>= 1; + v /= 2; if c.is_even() { - c >>= 1; + c /= 2; } else { // c = (c + p)/2, avoiding overflow - c = (c >> 1) + (Self::ORDER >> 1) + 1; + c = (c / 2) + (p / 2) + 1; } } - if u < v { - v -= u; - if c < b { - c += Self::ORDER; - } - c -= b; - } else { + if u >= v { u -= v; - if b < c { - b += Self::ORDER; + // b -= c + let (mut diff, under) = b.overflowing_sub(c); + if under { + diff = diff.overflowing_add(p).0; } - b -= c; + b = diff; + } else { + v -= u; + // c -= b + let (mut diff, under) = c.overflowing_sub(b); + if under { + diff = diff.overflowing_add(p).0; + } + c = diff; } } - Some(Self(if u == 1 { + let inverse = Self(if u == 1 { b } else { c - })) + }); + + // Should change to debug_assert_eq; using assert_eq as an extra precaution for now until + // we're more confident the impl is correct. + assert_eq!(*self * inverse, Self::ONE); + Some(inverse) } #[inline] diff --git a/src/field/field.rs b/src/field/field.rs index 7cffab50..86e809eb 100644 --- a/src/field/field.rs +++ b/src/field/field.rs @@ -3,11 +3,13 @@ use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssi use rand::Rng; use rand::rngs::OsRng; use crate::util::bits_u64; +use std::hash::Hash; /// A finite field with prime order less than 2^64. pub trait Field: 'static + Copy + Eq ++ Hash + Neg + Add + AddAssign @@ -92,8 +94,9 @@ pub trait Field: 'static base.exp(Self::from_canonical_u64(1u64 << (Self::TWO_ADICITY - n_power))) } + /// Computes a multiplicative subgroup whose order is known in advance. fn cyclic_subgroup_known_order(generator: Self, order: usize) -> Vec { - let mut subgroup = Vec::new(); + let mut subgroup = Vec::with_capacity(order); let mut current = Self::ONE; for _i in 0..order { subgroup.push(current); @@ -102,6 +105,14 @@ pub trait Field: 'static subgroup } + /// 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); + subgroup.into_iter() + .map(|x| x * shift) + .collect() + } + fn to_canonical_u64(&self) -> u64; fn from_canonical_u64(n: u64) -> Self; diff --git a/src/field/mod.rs b/src/field/mod.rs index 38fab717..f377301d 100644 --- a/src/field/mod.rs +++ b/src/field/mod.rs @@ -2,6 +2,7 @@ pub(crate) mod crandall_field; pub(crate) mod field; pub(crate) mod field_search; pub(crate) mod fft; +pub(crate) mod cosets; #[cfg(test)] mod field_testing; diff --git a/src/main.rs b/src/main.rs index b2726cb7..32ae538f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,7 +26,6 @@ mod gates; mod generator; mod gmimc; mod hash; -mod partition; mod plonk_challenger; mod plonk_common; mod polynomial; diff --git a/src/partition.rs b/src/partition.rs deleted file mode 100644 index a01eeefa..00000000 --- a/src/partition.rs +++ /dev/null @@ -1,23 +0,0 @@ -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) -}