diff --git a/Cargo.toml b/Cargo.toml index b26f11440..76c0023fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ abomonation_derive = { version = "0.1.0", package = "abomonation_derive_ng" } tracing = "0.1.37" cfg-if = "1.0.0" once_cell = "1.18.0" -itertools = "0.12.0" # zip_eq +itertools = "0.13.0" # zip_eq rand = "0.8.5" ref-cast = "1.0.20" # allocation-less conversion in multilinear polys derive_more = "0.99.17" # lightens impl macros for pasta diff --git a/examples/hashchain.rs b/examples/hashchain.rs index 469c63ab2..44dc27aa1 100644 --- a/examples/hashchain.rs +++ b/examples/hashchain.rs @@ -96,9 +96,9 @@ impl StepCircuit for HashChainCircuit { let acc = &mut ns; sponge.start(parameter, None, acc); - neptune::sponge::api::SpongeAPI::absorb(&mut sponge, num_absorbs, &elt, acc); + SpongeAPI::absorb(&mut sponge, num_absorbs, &elt, acc); - let output = neptune::sponge::api::SpongeAPI::squeeze(&mut sponge, 1, acc); + let output = SpongeAPI::squeeze(&mut sponge, 1, acc); sponge.finish(acc).unwrap(); Elt::ensure_allocated(&output[0], &mut ns.namespace(|| "ensure allocated"), true)? }; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index a58a147f9..e4205e80b 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # The default profile includes rustc, rust-std, cargo, rust-docs, rustfmt and clippy. profile = "default" -channel = "1.76.0" +channel = "1.78" targets = [ "wasm32-unknown-unknown" ] diff --git a/src/bellpepper/test_shape_cs.rs b/src/bellpepper/test_shape_cs.rs index 7193b885f..03d209509 100644 --- a/src/bellpepper/test_shape_cs.rs +++ b/src/bellpepper/test_shape_cs.rs @@ -14,6 +14,7 @@ use ff::{Field, PrimeField}; #[derive(Clone, Copy)] struct OrderedVariable(Variable); +#[allow(dead_code)] #[derive(Debug)] enum NamedObject { Constraint(usize), diff --git a/src/gadgets/nonnative/mod.rs b/src/gadgets/nonnative/mod.rs index 8167e5a7e..4d611cbb0 100644 --- a/src/gadgets/nonnative/mod.rs +++ b/src/gadgets/nonnative/mod.rs @@ -6,16 +6,12 @@ use ff::PrimeField; trait OptionExt { fn grab(&self) -> Result<&T, SynthesisError>; - fn grab_mut(&mut self) -> Result<&mut T, SynthesisError>; } impl OptionExt for Option { fn grab(&self) -> Result<&T, SynthesisError> { self.as_ref().ok_or(SynthesisError::AssignmentMissing) } - fn grab_mut(&mut self) -> Result<&mut T, SynthesisError> { - self.as_mut().ok_or(SynthesisError::AssignmentMissing) - } } trait BitAccess { diff --git a/src/provider/poseidon.rs b/src/provider/poseidon.rs index 0e2bf170c..0ccc8a4aa 100644 --- a/src/provider/poseidon.rs +++ b/src/provider/poseidon.rs @@ -169,7 +169,7 @@ where assert_eq!(self.num_absorbs, self.state.len()); sponge.start(parameter, None, acc); - neptune::sponge::api::SpongeAPI::absorb( + SpongeAPI::absorb( &mut sponge, self.num_absorbs as u32, &(0..self.state.len()) @@ -178,7 +178,7 @@ where acc, ); - let output = neptune::sponge::api::SpongeAPI::squeeze(&mut sponge, 1, acc); + let output = SpongeAPI::squeeze(&mut sponge, 1, acc); sponge.finish(acc).unwrap(); output }; diff --git a/src/provider/tests/mod.rs b/src/provider/tests/mod.rs index 5e6f00c34..f7b17fee4 100644 --- a/src/provider/tests/mod.rs +++ b/src/provider/tests/mod.rs @@ -26,7 +26,7 @@ use serde_json::{Map, Value}; // ) { // use rand_core::SeedableRng; - // let mut rng = rand::rngs::StdRng::seed_from_u64(num_vars as u64); + // let mut rng = StdRng::seed_from_u64(num_vars as u64); // let (poly, point, eval) = // crate::provider::util::test_utils::random_poly_with_eval::(num_vars, &mut rng); diff --git a/src/provider/util/mod.rs b/src/provider/util/mod.rs index a7d748ca3..98bf9a27f 100644 --- a/src/provider/util/mod.rs +++ b/src/provider/util/mod.rs @@ -147,7 +147,7 @@ use serde::Serialize; // ) { // use rand_core::SeedableRng; - // let mut rng = rand::rngs::StdRng::seed_from_u64(num_vars as u64); + // let mut rng = StdRng::seed_from_u64(num_vars as u64); // let (poly, point, eval) = random_poly_with_eval::(num_vars, &mut rng); diff --git a/src/spartan/batched.rs b/src/spartan/batched.rs index 3604c1282..b68ac33c6 100644 --- a/src/spartan/batched.rs +++ b/src/spartan/batched.rs @@ -490,17 +490,7 @@ impl> BatchedRelaxedR1CSSNARKTrait let evals_Z = zip_with!(iter, (self.evals_W, U, r_y), |eval_W, U, r_y| { let eval_X = { // constant term - let poly_X = iter::once((0, U.u)) - .chain( - //remaining inputs - U.X - .iter() - .enumerate() - // filter_map uses the sparsity of the polynomial, if irrelevant - // we should replace by UniPoly - .filter_map(|(i, x_i)| (!x_i.is_zero_vartime()).then_some((i + 1, *x_i))), - ) - .collect(); + let poly_X = iter::once(U.u).chain(U.X.iter().cloned()).collect(); SparsePolynomial::new(r_y.len() - 1, poly_X).evaluate(&r_y[1..]) }; (E::Scalar::ONE - r_y[0]) * eval_W + r_y[0] * eval_X diff --git a/src/spartan/batched_ppsnark.rs b/src/spartan/batched_ppsnark.rs index f60e562e1..caf604804 100644 --- a/src/spartan/batched_ppsnark.rs +++ b/src/spartan/batched_ppsnark.rs @@ -927,15 +927,7 @@ impl> BatchedRelaxedR1CSSNARKTrait let X = { // constant term - let poly_X = std::iter::once((0, U.u)) - .chain( - //remaining inputs - (0..U.X.len()) - // filter_map uses the sparsity of the polynomial, if irrelevant - // we should replace by UniPoly - .filter_map(|i| (!U.X[i].is_zero_vartime()).then_some((i + 1, U.X[i]))), - ) - .collect(); + let poly_X = std::iter::once(U.u).chain(U.X.iter().cloned()).collect(); SparsePolynomial::new(num_vars_log, poly_X).evaluate(&rand_sc_unpad[1..]) }; diff --git a/src/spartan/math.rs b/src/spartan/math.rs index 4a879b93d..94b35c50f 100644 --- a/src/spartan/math.rs +++ b/src/spartan/math.rs @@ -1,23 +1,8 @@ pub trait Math { - fn pow2(self) -> usize; - fn get_bits(self, num_bits: usize) -> Vec; fn log_2(self) -> usize; } impl Math for usize { - #[inline] - fn pow2(self) -> usize { - let base: Self = 2; - base.pow(self as u32) - } - - /// Returns the `num_bits` from n in a canonical order - fn get_bits(self, num_bits: usize) -> Vec { - (0..num_bits) - .map(|shift_amount| ((self & (1 << (num_bits - shift_amount - 1))) > 0)) - .collect::>() - } - fn log_2(self) -> usize { assert_ne!(self, 0); diff --git a/src/spartan/mod.rs b/src/spartan/mod.rs index 528498d26..975dda73d 100644 --- a/src/spartan/mod.rs +++ b/src/spartan/mod.rs @@ -27,7 +27,6 @@ use crate::{ }; use ff::Field; use itertools::Itertools as _; -use polys::multilinear::SparsePolynomial; use rayon::{iter::IntoParallelRefIterator, prelude::*}; use rayon_scan::ScanParallelIterator as _; use ref_cast::RefCast; diff --git a/src/spartan/polys/multilinear.rs b/src/spartan/polys/multilinear.rs index bdb18e06f..7ce2047c8 100644 --- a/src/spartan/polys/multilinear.rs +++ b/src/spartan/polys/multilinear.rs @@ -8,8 +8,7 @@ use ff::PrimeField; use itertools::Itertools as _; use rand_core::{CryptoRng, RngCore}; use rayon::prelude::{ - IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, - IntoParallelRefMutIterator, ParallelIterator, + IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator, }; use serde::{Deserialize, Serialize}; @@ -130,47 +129,37 @@ impl Index for MultilinearPolynomial { } /// Sparse multilinear polynomial, which means the $Z(\cdot)$ is zero at most points. -/// So we do not have to store every evaluations of $Z(\cdot)$, only store the non-zero points. -/// -/// For example, the evaluations are [0, 0, 0, 1, 0, 1, 0, 2]. -/// The sparse polynomial only store the non-zero values, [(3, 1), (5, 1), (7, 2)]. -/// In the tuple, the first is index, the second is value. +/// In our context, sparse polynomials are non-zeros over the hypercube at locations that map to "small" integers +/// We exploit this property to implement a time-optimal algorithm pub(crate) struct SparsePolynomial { num_vars: usize, - Z: Vec<(usize, Scalar)>, + Z: Vec, } impl SparsePolynomial { - pub fn new(num_vars: usize, Z: Vec<(usize, Scalar)>) -> Self { + pub fn new(num_vars: usize, Z: Vec) -> Self { Self { num_vars, Z } } - /// Computes the $\tilde{eq}$ extension polynomial. - /// return 1 when a == r, otherwise return 0. - fn compute_chi(a: &[bool], r: &[Scalar]) -> Scalar { - assert_eq!(a.len(), r.len()); - let mut chi_i = Scalar::ONE; - for j in 0..r.len() { - if a[j] { - chi_i *= r[j]; - } else { - chi_i *= Scalar::ONE - r[j]; - } - } - chi_i - } - - // Takes O(m log n) where m is the number of non-zero evaluations and n is the number of variables. + // a time-optimal algorithm to evaluate sparse polynomials pub fn evaluate(&self, r: &[Scalar]) -> Scalar { assert_eq!(self.num_vars, r.len()); - (0..self.Z.len()) - .into_par_iter() - .map(|i| { - let bits = (self.Z[i].0).get_bits(r.len()); - Self::compute_chi(&bits, r) * self.Z[i].1 - }) - .sum() + let num_vars_z = self.Z.len().next_power_of_two().log_2(); + let chis = EqPolynomial::evals_from_points(&r[self.num_vars - 1 - num_vars_z..]); + #[allow(clippy::disallowed_methods)] + let eval_partial: Scalar = self + .Z + .iter() + .zip(chis.iter()) + .map(|(z, chi)| *z * *chi) + .sum(); + + let common = (0..self.num_vars - 1 - num_vars_z) + .map(|i| (Scalar::ONE - r[i])) + .product::(); + + common * eval_partial } } @@ -192,7 +181,7 @@ impl Add for MultilinearPolynomial { #[cfg(test)] mod tests { - use crate::provider::{self, bn256_grumpkin::bn256, secp_secq::secp256k1}; + use crate::provider::{bn256_grumpkin::bn256, secp_secq::secp256k1}; use super::*; use rand_chacha::ChaCha20Rng; @@ -232,18 +221,21 @@ mod tests { } fn test_sparse_polynomial_with() { - // Let the polynomial have 3 variables, p(x_1, x_2, x_3) = (x_1 + x_2) * x_3 - // Evaluations of the polynomial at boolean cube are [0, 0, 0, 1, 0, 1, 0, 2]. + // Let the polynomial have 4 variables, but is non-zero at only 3 locations (out of 2^4 = 16) over the hypercube + let mut Z = vec![F::ONE, F::ONE, F::from(2)]; + let m_poly = SparsePolynomial::::new(4, Z.clone()); - let TWO = F::from(2); - let Z = vec![(3, F::ONE), (5, F::ONE), (7, TWO)]; - let m_poly = SparsePolynomial::::new(3, Z); + Z.resize(16, F::ZERO); // append with zeros to make it a dense polynomial + let m_poly_dense = MultilinearPolynomial::new(Z); - let x = vec![F::ONE, F::ONE, F::ONE]; - assert_eq!(m_poly.evaluate(x.as_slice()), TWO); + // evaluation point + let x = vec![F::from(5), F::from(8), F::from(5), F::from(3)]; - let x = vec![F::ONE, F::ZERO, F::ONE]; - assert_eq!(m_poly.evaluate(x.as_slice()), F::ONE); + // check evaluations + assert_eq!( + m_poly.evaluate(x.as_slice()), + m_poly_dense.evaluate(x.as_slice()) + ); } #[test] @@ -301,8 +293,8 @@ mod tests { #[test] fn test_evaluation() { test_evaluation_with::(); - test_evaluation_with::(); - test_evaluation_with::(); + test_evaluation_with::(); + test_evaluation_with::(); } /// This binds the variables of a multilinear polynomial to a provided sequence diff --git a/src/supernova/mod.rs b/src/supernova/mod.rs index 656685a4f..bbffb6484 100644 --- a/src/supernova/mod.rs +++ b/src/supernova/mod.rs @@ -1182,19 +1182,6 @@ where fn secondary_circuit(&self) -> Self::C2; } -/// Extension trait to simplify getting scalar form of initial circuit index. -trait InitialProgramCounter: NonUniformCircuit -where - E1: CurveCycleEquipped, -{ - /// Initial program counter is the initial circuit index as a `Scalar`. - fn initial_program_counter(&self) -> E1::Scalar { - E1::Scalar::from(self.initial_circuit_index() as u64) - } -} - -impl> InitialProgramCounter for T {} - /// Compute the circuit digest of a supernova [`StepCircuit`]. /// /// Note for callers: This function should be called with its performance characteristics in mind.