From 3a4f97a8565270e55e5512ea72864dc5ff994033 Mon Sep 17 00:00:00 2001 From: field-worker <151173028+field-worker@users.noreply.github.com> Date: Fri, 17 Nov 2023 21:16:03 +0100 Subject: [PATCH 1/3] Small code improvement to the minroot example (#264) about 10% improvement for the non-release mode --- examples/minroot.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/minroot.rs b/examples/minroot.rs index dca39b34f..ef007c9ce 100644 --- a/examples/minroot.rs +++ b/examples/minroot.rs @@ -47,10 +47,12 @@ impl MinRootIteration { let x_i_plus_1 = (x_i + y_i).pow_vartime(exp.to_u64_digits()); // computes the fifth root of x_i + y_i // sanity check - let sq = x_i_plus_1 * x_i_plus_1; - let quad = sq * sq; - let fifth = quad * x_i_plus_1; - debug_assert_eq!(fifth, x_i + y_i); + if cfg!(debug_assertions) { + let sq = x_i_plus_1 * x_i_plus_1; + let quad = sq * sq; + let fifth = quad * x_i_plus_1; + assert_eq!(fifth, x_i + y_i); + } let y_i_plus_1 = x_i; @@ -230,7 +232,7 @@ fn main() { ) .unwrap(); - for (i, circuit_primary) in minroot_circuits.iter().take(num_steps).enumerate() { + for (i, circuit_primary) in minroot_circuits.iter().enumerate() { let start = Instant::now(); let res = recursive_snark.prove_step(&pp, circuit_primary, &circuit_secondary); assert!(res.is_ok()); From e4bb90c376aee384ddba108bf7302f15ebcf039d Mon Sep 17 00:00:00 2001 From: ashWhiteHat Date: Tue, 21 Nov 2023 05:35:34 +0900 Subject: [PATCH 2/3] Reduce duplicate code across different curve cycle providers (#255) * refactor: impl folding macro * refactor: generalize curve test * chore: rename impl_folding to impl_engine --- src/provider/bn256_grumpkin.rs | 36 +-------- src/provider/mod.rs | 136 +++++++++++++++++++++++++-------- src/provider/pasta.rs | 99 ++++-------------------- src/provider/secp_secq.rs | 36 +-------- 4 files changed, 121 insertions(+), 186 deletions(-) diff --git a/src/provider/bn256_grumpkin.rs b/src/provider/bn256_grumpkin.rs index d1831bb8d..f19649b38 100644 --- a/src/provider/bn256_grumpkin.rs +++ b/src/provider/bn256_grumpkin.rs @@ -1,6 +1,6 @@ //! This module implements the Nova traits for `bn256::Point`, `bn256::Scalar`, `grumpkin::Point`, `grumpkin::Scalar`. use crate::{ - impl_traits, + impl_engine, impl_traits, provider::{ cpu_best_multiexp, keccak::Keccak256Transcript, @@ -69,37 +69,3 @@ impl_traits!( "30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001" ); - -#[cfg(test)] -mod tests { - use super::*; - type G = bn256::Point; - - fn from_label_serial(label: &'static [u8], n: usize) -> Vec { - let mut shake = Shake256::default(); - shake.update(label); - let mut reader = shake.finalize_xof(); - let mut ck = Vec::new(); - for _ in 0..n { - let mut uniform_bytes = [0u8; 32]; - reader.read_exact(&mut uniform_bytes).unwrap(); - let hash = bn256::Point::hash_to_curve("from_uniform_bytes"); - ck.push(hash(&uniform_bytes).to_affine()); - } - ck - } - - #[test] - fn test_from_label() { - let label = b"test_from_label"; - for n in [ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 1021, - ] { - let ck_par = ::from_label(label, n); - let ck_ser = from_label_serial(label, n); - assert_eq!(ck_par.len(), n); - assert_eq!(ck_ser.len(), n); - assert_eq!(ck_par, ck_ser); - } - } -} diff --git a/src/provider/mod.rs b/src/provider/mod.rs index fa4b9e07b..292737434 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -229,29 +229,14 @@ macro_rules! impl_traits { $order_str:literal, $base_str:literal ) => { - impl Engine for $engine { - type Base = $name::Base; - type Scalar = $name::Scalar; - type GE = $name::Point; - type RO = PoseidonRO; - type ROCircuit = PoseidonROCircuit; - type TE = Keccak256Transcript; - type CE = CommitmentEngine; - } - - impl Group for $name::Point { - type Base = $name::Base; - type Scalar = $name::Scalar; - - fn group_params() -> (Self::Base, Self::Base, BigInt, BigInt) { - let A = $name::Point::a(); - let B = $name::Point::b(); - let order = BigInt::from_str_radix($order_str, 16).unwrap(); - let base = BigInt::from_str_radix($base_str, 16).unwrap(); - - (A, B, order, base) - } - } + impl_engine!( + $engine, + $name, + $name_compressed, + $name_curve, + $order_str, + $base_str + ); impl DlogGroup for $name::Point { type CompressedGroupElement = $name_compressed; @@ -335,10 +320,11 @@ macro_rules! impl_traits { } } - impl PrimeFieldExt for $name::Scalar { - fn from_uniform(bytes: &[u8]) -> Self { - let bytes_arr: [u8; 64] = bytes.try_into().unwrap(); - $name::Scalar::from_uniform_bytes(&bytes_arr) + impl CompressedGroup for $name_compressed { + type GroupElement = $name::Point; + + fn decompress(&self) -> Option<$name::Point> { + Some($name_curve::from_bytes(&self).unwrap()) } } @@ -347,12 +333,48 @@ macro_rules! impl_traits { self.as_ref().to_vec() } } + }; +} - impl CompressedGroup for $name_compressed { - type GroupElement = $name::Point; +/// Nova folding circuit engine and curve group ops +#[macro_export] +macro_rules! impl_engine { + ( + $engine:ident, + $name:ident, + $name_compressed:ident, + $name_curve:ident, + $order_str:literal, + $base_str:literal + ) => { + impl Engine for $engine { + type Base = $name::Base; + type Scalar = $name::Scalar; + type GE = $name::Point; + type RO = PoseidonRO; + type ROCircuit = PoseidonROCircuit; + type TE = Keccak256Transcript; + type CE = CommitmentEngine; + } - fn decompress(&self) -> Option<$name::Point> { - Some($name_curve::from_bytes(&self).unwrap()) + impl Group for $name::Point { + type Base = $name::Base; + type Scalar = $name::Scalar; + + fn group_params() -> (Self::Base, Self::Base, BigInt, BigInt) { + let A = $name::Point::a(); + let B = $name::Point::b(); + let order = BigInt::from_str_radix($order_str, 16).unwrap(); + let base = BigInt::from_str_radix($base_str, 16).unwrap(); + + (A, B, order, base) + } + } + + impl PrimeFieldExt for $name::Scalar { + fn from_uniform(bytes: &[u8]) -> Self { + let bytes_arr: [u8; 64] = bytes.try_into().unwrap(); + $name::Scalar::from_uniform_bytes(&bytes_arr) } } @@ -371,11 +393,44 @@ mod tests { use crate::provider::{ bn256_grumpkin::{bn256, grumpkin}, secp_secq::{secp256k1, secq256k1}, + DlogGroup, }; - use group::{ff::Field, Group}; - use halo2curves::CurveAffine; + use digest::{ExtendableOutput, Update}; + use group::{ff::Field, Curve, Group}; + use halo2curves::{CurveAffine, CurveExt}; use pasta_curves::{pallas, vesta}; use rand_core::OsRng; + use sha3::Shake256; + use std::io::Read; + + macro_rules! impl_cycle_pair_test { + ($curve:ident) => { + fn from_label_serial(label: &'static [u8], n: usize) -> Vec<$curve::Affine> { + let mut shake = Shake256::default(); + shake.update(label); + let mut reader = shake.finalize_xof(); + (0..n) + .map(|_| { + let mut uniform_bytes = [0u8; 32]; + reader.read_exact(&mut uniform_bytes).unwrap(); + let hash = $curve::Point::hash_to_curve("from_uniform_bytes"); + hash(&uniform_bytes).to_affine() + }) + .collect() + } + + let label = b"test_from_label"; + for n in [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 1021, + ] { + let ck_par = <$curve::Point as DlogGroup>::from_label(label, n); + let ck_ser = from_label_serial(label, n); + assert_eq!(ck_par.len(), n); + assert_eq!(ck_ser.len(), n); + assert_eq!(ck_par, ck_ser); + } + }; + } fn test_msm_with>() { let n = 8; @@ -403,4 +458,19 @@ mod tests { test_msm_with::(); test_msm_with::(); } + + #[test] + fn test_bn256_from_label() { + impl_cycle_pair_test!(bn256); + } + + #[test] + fn test_pallas_from_label() { + impl_cycle_pair_test!(pallas); + } + + #[test] + fn test_secp256k1_from_label() { + impl_cycle_pair_test!(secp256k1); + } } diff --git a/src/provider/pasta.rs b/src/provider/pasta.rs index 0cb0cb5ae..7d4770e25 100644 --- a/src/provider/pasta.rs +++ b/src/provider/pasta.rs @@ -1,5 +1,6 @@ //! This module implements the Nova traits for `pallas::Point`, `pallas::Scalar`, `vesta::Point`, `vesta::Scalar`. use crate::{ + impl_engine, provider::{ cpu_best_multiexp, keccak::Keccak256Transcript, @@ -68,35 +69,19 @@ macro_rules! impl_traits { $order_str:literal, $base_str:literal ) => { - impl Engine for $engine { - type Base = $name::Base; - type Scalar = $name::Scalar; - type GE = $name::Point; - type RO = PoseidonRO; - type ROCircuit = PoseidonROCircuit; - type TE = Keccak256Transcript; - type CE = CommitmentEngine; - } - - impl Group for $name::Point { - type Base = $name::Base; - type Scalar = $name::Scalar; - - fn group_params() -> (Self::Base, Self::Base, BigInt, BigInt) { - let A = $name::Point::a(); - let B = $name::Point::b(); - let order = BigInt::from_str_radix($order_str, 16).unwrap(); - let base = BigInt::from_str_radix($base_str, 16).unwrap(); - - (A, B, order, base) - } - } + impl_engine!( + $engine, + $name, + $name_compressed, + $name_curve, + $order_str, + $base_str + ); impl DlogGroup for $name::Point { type CompressedGroupElement = $name_compressed; type PreprocessedGroupElement = $name::Affine; - #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] #[tracing::instrument( skip_all, level = "trace", @@ -106,29 +91,24 @@ macro_rules! impl_traits { scalars: &[Self::Scalar], bases: &[Self::PreprocessedGroupElement], ) -> Self { + #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] if scalars.len() >= 128 { pasta_msm::$name(bases, scalars) } else { cpu_best_multiexp(scalars, bases) } + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + cpu_best_multiexp(scalars, bases) } - #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] - fn vartime_multiscalar_mul( - scalars: &[Self::Scalar], - bases: &[Self::PreprocessedGroupElement], - ) -> Self { - cpu_best_multiexp(scalars, bases) + fn preprocessed(&self) -> Self::PreprocessedGroupElement { + self.to_affine() } fn compress(&self) -> Self::CompressedGroupElement { $name_compressed::new(self.to_bytes()) } - fn preprocessed(&self) -> Self::PreprocessedGroupElement { - self.to_affine() - } - fn from_label(label: &'static [u8], n: usize) -> Vec { let mut shake = Shake256::default(); shake.update(label); @@ -189,19 +169,6 @@ macro_rules! impl_traits { } } - impl PrimeFieldExt for $name::Scalar { - fn from_uniform(bytes: &[u8]) -> Self { - let bytes_arr: [u8; 64] = bytes.try_into().unwrap(); - $name::Scalar::from_uniform_bytes(&bytes_arr) - } - } - - impl TranscriptReprTrait for $name_compressed { - fn to_transcript_bytes(&self) -> Vec { - self.repr.to_vec() - } - } - impl CompressedGroup for $name_compressed { type GroupElement = $name::Point; @@ -210,9 +177,9 @@ macro_rules! impl_traits { } } - impl TranscriptReprTrait for $name::Scalar { + impl TranscriptReprTrait for $name_compressed { fn to_transcript_bytes(&self) -> Vec { - self.to_repr().to_vec() + self.repr.to_vec() } } }; @@ -237,37 +204,3 @@ impl_traits!( "40000000000000000000000000000000224698fc094cf91b992d30ed00000001", "40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001" ); - -#[cfg(test)] -mod tests { - use super::*; - type G = ::GE; - - fn from_label_serial(label: &'static [u8], n: usize) -> Vec { - let mut shake = Shake256::default(); - shake.update(label); - let mut reader = shake.finalize_xof(); - let mut ck = Vec::new(); - for _ in 0..n { - let mut uniform_bytes = [0u8; 32]; - reader.read_exact(&mut uniform_bytes).unwrap(); - let hash = Ep::hash_to_curve("from_uniform_bytes"); - ck.push(hash(&uniform_bytes).to_affine()); - } - ck - } - - #[test] - fn test_from_label() { - let label = b"test_from_label"; - for n in [ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 1021, - ] { - let ck_par = ::from_label(label, n); - let ck_ser = from_label_serial(label, n); - assert_eq!(ck_par.len(), n); - assert_eq!(ck_ser.len(), n); - assert_eq!(ck_par, ck_ser); - } - } -} diff --git a/src/provider/secp_secq.rs b/src/provider/secp_secq.rs index ef6251859..4f2aa71ea 100644 --- a/src/provider/secp_secq.rs +++ b/src/provider/secp_secq.rs @@ -1,6 +1,6 @@ //! This module implements the Nova traits for `secp::Point`, `secp::Scalar`, `secq::Point`, `secq::Scalar`. use crate::{ - impl_traits, + impl_engine, impl_traits, provider::{ cpu_best_multiexp, keccak::Keccak256Transcript, @@ -66,37 +66,3 @@ impl_traits!( "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141" ); - -#[cfg(test)] -mod tests { - use super::*; - type G = secp256k1::Point; - - fn from_label_serial(label: &'static [u8], n: usize) -> Vec { - let mut shake = Shake256::default(); - shake.update(label); - let mut reader = shake.finalize_xof(); - let mut ck = Vec::new(); - for _ in 0..n { - let mut uniform_bytes = [0u8; 32]; - reader.read_exact(&mut uniform_bytes).unwrap(); - let hash = secp256k1::Point::hash_to_curve("from_uniform_bytes"); - ck.push(hash(&uniform_bytes).to_affine()); - } - ck - } - - #[test] - fn test_from_label() { - let label = b"test_from_label"; - for n in [ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 1021, - ] { - let ck_par = ::from_label(label, n); - let ck_ser = from_label_serial(label, n); - assert_eq!(ck_par.len(), n); - assert_eq!(ck_ser.len(), n); - assert_eq!(ck_par, ck_ser); - } - } -} From 23e4a126edf63c49a8992705d98a1cb588400ed4 Mon Sep 17 00:00:00 2001 From: Srinath Setty Date: Mon, 20 Nov 2023 17:22:37 -0800 Subject: [PATCH 3/3] reorganize provider module (#267) --- benches/compressed-snark.rs | 2 +- benches/compute-digest.rs | 2 +- benches/recursive-snark-supernova.rs | 2 +- benches/recursive-snark.rs | 2 +- benches/sha256.rs | 2 +- examples/minroot.rs | 2 +- examples/minroot_serde.rs | 2 +- src/bellpepper/mod.rs | 2 +- src/circuit.rs | 5 +- src/digest.rs | 2 +- src/gadgets/ecc.rs | 6 +- src/lib.rs | 8 +- src/nifs.rs | 2 +- src/provider/bn256_grumpkin.rs | 29 +- src/provider/ipa_pc.rs | 2 +- src/provider/keccak.rs | 4 +- src/provider/mod.rs | 477 ++++++--------------------- src/provider/msm.rs | 151 +++++++++ src/provider/pasta.rs | 59 ++-- src/provider/pedersen.rs | 2 +- src/provider/poseidon.rs | 4 +- src/provider/secp_secq.rs | 23 +- src/provider/traits.rs | 228 +++++++++++++ src/r1cs/mod.rs | 2 +- src/r1cs/sparse.rs | 2 +- src/spartan/direct.rs | 4 +- src/supernova/test.rs | 12 +- src/supernova/utils.rs | 2 +- 28 files changed, 552 insertions(+), 488 deletions(-) create mode 100644 src/provider/msm.rs create mode 100644 src/provider/traits.rs diff --git a/benches/compressed-snark.rs b/benches/compressed-snark.rs index 95fdccd01..ff7802241 100644 --- a/benches/compressed-snark.rs +++ b/benches/compressed-snark.rs @@ -5,7 +5,7 @@ use core::marker::PhantomData; use criterion::*; use ff::PrimeField; use nova_snark::{ - provider::pasta::{PallasEngine, VestaEngine}, + provider::{PallasEngine, VestaEngine}, traits::{ circuit::{StepCircuit, TrivialCircuit}, snark::RelaxedR1CSSNARKTrait, diff --git a/benches/compute-digest.rs b/benches/compute-digest.rs index 4f47e7b0b..2bc62f329 100644 --- a/benches/compute-digest.rs +++ b/benches/compute-digest.rs @@ -4,7 +4,7 @@ use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use ff::PrimeField; use nova_snark::{ - provider::pasta::{PallasEngine, VestaEngine}, + provider::{PallasEngine, VestaEngine}, traits::{ circuit::{StepCircuit, TrivialCircuit}, snark::default_ck_hint, diff --git a/benches/recursive-snark-supernova.rs b/benches/recursive-snark-supernova.rs index e630b09d8..b8fedd6b1 100644 --- a/benches/recursive-snark-supernova.rs +++ b/benches/recursive-snark-supernova.rs @@ -5,7 +5,7 @@ use core::marker::PhantomData; use criterion::*; use ff::PrimeField; use nova_snark::{ - provider::pasta::{PallasEngine, VestaEngine}, + provider::{PallasEngine, VestaEngine}, supernova::NonUniformCircuit, supernova::{PublicParams, RecursiveSNARK}, traits::{ diff --git a/benches/recursive-snark.rs b/benches/recursive-snark.rs index 67f817b79..018b3a280 100644 --- a/benches/recursive-snark.rs +++ b/benches/recursive-snark.rs @@ -5,7 +5,7 @@ use core::marker::PhantomData; use criterion::*; use ff::PrimeField; use nova_snark::{ - provider::pasta::{PallasEngine, VestaEngine}, + provider::{PallasEngine, VestaEngine}, traits::{ circuit::{StepCircuit, TrivialCircuit}, snark::default_ck_hint, diff --git a/benches/sha256.rs b/benches/sha256.rs index 43d3c3215..d2becf8ba 100644 --- a/benches/sha256.rs +++ b/benches/sha256.rs @@ -14,7 +14,7 @@ use core::time::Duration; use criterion::*; use ff::{PrimeField, PrimeFieldBits}; use nova_snark::{ - provider::pasta::{PallasEngine, VestaEngine}, + provider::{PallasEngine, VestaEngine}, traits::{ circuit::{StepCircuit, TrivialCircuit}, snark::default_ck_hint, diff --git a/examples/minroot.rs b/examples/minroot.rs index ef007c9ce..f5dc5e3db 100644 --- a/examples/minroot.rs +++ b/examples/minroot.rs @@ -5,7 +5,7 @@ use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; use ff::PrimeField; use flate2::{write::ZlibEncoder, Compression}; use nova_snark::{ - provider::pasta::{PallasEngine, VestaEngine}, + provider::{PallasEngine, VestaEngine}, traits::{ circuit::{StepCircuit, TrivialCircuit}, snark::default_ck_hint, diff --git a/examples/minroot_serde.rs b/examples/minroot_serde.rs index 33c7166ec..2ca7ecb6f 100644 --- a/examples/minroot_serde.rs +++ b/examples/minroot_serde.rs @@ -8,7 +8,7 @@ use abomonation_derive::Abomonation; use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; use ff::PrimeField; use nova_snark::{ - provider::pasta::{PallasEngine, VestaEngine}, + provider::{PallasEngine, VestaEngine}, traits::{ circuit::{StepCircuit, TrivialCircuit}, snark::default_ck_hint, diff --git a/src/bellpepper/mod.rs b/src/bellpepper/mod.rs index 456f6cb5a..6660c4dcd 100644 --- a/src/bellpepper/mod.rs +++ b/src/bellpepper/mod.rs @@ -15,7 +15,7 @@ mod tests { shape_cs::ShapeCS, solver::SatisfyingAssignment, }, - provider::{bn256_grumpkin::Bn256Engine, pasta::PallasEngine, secp_secq::Secp256k1Engine}, + provider::{Bn256Engine, PallasEngine, Secp256k1Engine}, traits::{snark::default_ck_hint, Engine}, }; use bellpepper_core::{num::AllocatedNum, ConstraintSystem}; diff --git a/src/circuit.rs b/src/circuit.rs index 332e4abf9..7e7fadcbf 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -372,10 +372,9 @@ mod tests { constants::{BN_LIMB_WIDTH, BN_N_LIMBS}, gadgets::utils::scalar_as_base, provider::{ - bn256_grumpkin::{Bn256Engine, GrumpkinEngine}, - pasta::{PallasEngine, VestaEngine}, poseidon::PoseidonConstantsCircuit, - secp_secq::{Secp256k1Engine, Secq256k1Engine}, + {Bn256Engine, GrumpkinEngine}, {PallasEngine, VestaEngine}, + {Secp256k1Engine, Secq256k1Engine}, }, traits::{circuit::TrivialCircuit, snark::default_ck_hint}, }; diff --git a/src/digest.rs b/src/digest.rs index fccf17b83..15526fcb1 100644 --- a/src/digest.rs +++ b/src/digest.rs @@ -81,7 +81,7 @@ impl<'a, F: PrimeField, T: Digestible> DigestComputer<'a, F, T> { #[cfg(test)] mod tests { use super::{DigestComputer, SimpleDigestible}; - use crate::{provider::pasta::PallasEngine, traits::Engine}; + use crate::{provider::PallasEngine, traits::Engine}; use ff::Field; use once_cell::sync::OnceCell; use serde::{Deserialize, Serialize}; diff --git a/src/gadgets/ecc.rs b/src/gadgets/ecc.rs index 4c2121fdb..37e199e7a 100644 --- a/src/gadgets/ecc.rs +++ b/src/gadgets/ecc.rs @@ -788,9 +788,9 @@ mod tests { {solver::SatisfyingAssignment, test_shape_cs::TestShapeCS}, }, provider::{ - bn256_grumpkin::{bn256, grumpkin, Bn256Engine, GrumpkinEngine}, - pasta::{PallasEngine, VestaEngine}, - secp_secq::{secp256k1, secq256k1, Secp256k1Engine, Secq256k1Engine}, + bn256_grumpkin::{bn256, grumpkin}, + secp_secq::{secp256k1, secq256k1}, + Bn256Engine, GrumpkinEngine, Secp256k1Engine, Secq256k1Engine, {PallasEngine, VestaEngine}, }, traits::snark::default_ck_hint, }; diff --git a/src/lib.rs b/src/lib.rs index b61470974..d393f4ed2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -161,7 +161,7 @@ where /// ```rust /// # use nova_snark::spartan::ppsnark::RelaxedR1CSSNARK; /// # use nova_snark::provider::ipa_pc::EvaluationEngine; - /// # use nova_snark::provider::pasta::{PallasEngine, VestaEngine}; + /// # use nova_snark::provider::{PallasEngine, VestaEngine}; /// # use nova_snark::traits::{circuit::TrivialCircuit, Engine, snark::RelaxedR1CSSNARKTrait}; /// use nova_snark::PublicParams; /// @@ -945,10 +945,8 @@ mod tests { use super::*; use crate::{ provider::{ - bn256_grumpkin::{Bn256Engine, GrumpkinEngine}, - pasta::{PallasEngine, VestaEngine}, - secp_secq::{Secp256k1Engine, Secq256k1Engine}, - DlogGroup, + traits::DlogGroup, Bn256Engine, GrumpkinEngine, PallasEngine, Secp256k1Engine, + Secq256k1Engine, VestaEngine, }, traits::{evaluation::EvaluationEngineTrait, snark::default_ck_hint}, }; diff --git a/src/nifs.rs b/src/nifs.rs index fe37d8aca..d93523ccb 100644 --- a/src/nifs.rs +++ b/src/nifs.rs @@ -121,7 +121,7 @@ mod tests { solver::SatisfyingAssignment, test_shape_cs::TestShapeCS, }, - provider::{bn256_grumpkin::Bn256Engine, pasta::PallasEngine, secp_secq::Secp256k1Engine}, + provider::{Bn256Engine, PallasEngine, Secp256k1Engine}, r1cs::{commitment_key, SparseMatrix}, traits::{snark::default_ck_hint, Engine}, }; diff --git a/src/provider/bn256_grumpkin.rs b/src/provider/bn256_grumpkin.rs index f19649b38..88f7bec45 100644 --- a/src/provider/bn256_grumpkin.rs +++ b/src/provider/bn256_grumpkin.rs @@ -1,14 +1,11 @@ //! This module implements the Nova traits for `bn256::Point`, `bn256::Scalar`, `grumpkin::Point`, `grumpkin::Scalar`. use crate::{ - impl_engine, impl_traits, + impl_traits, provider::{ - cpu_best_multiexp, - keccak::Keccak256Transcript, - pedersen::CommitmentEngine, - poseidon::{PoseidonRO, PoseidonROCircuit}, - CompressedGroup, DlogGroup, + msm::cpu_best_msm, + traits::{CompressedGroup, DlogGroup}, }, - traits::{Engine, Group, PrimeFieldExt, TranscriptReprTrait}, + traits::{Group, PrimeFieldExt, TranscriptReprTrait}, }; use digest::{ExtendableOutput, Update}; use ff::{FromUniformBytes, PrimeField}; @@ -30,28 +27,15 @@ use halo2curves::grumpkin::{ /// Re-exports that give access to the standard aliases used in the code base, for bn256 pub mod bn256 { - pub use halo2curves::bn256::{ - Fq as Base, Fr as Scalar, G1Affine as Affine, G1Compressed as Compressed, G1 as Point, - }; + pub use halo2curves::bn256::{Fq as Base, Fr as Scalar, G1Affine as Affine, G1 as Point}; } /// Re-exports that give access to the standard aliases used in the code base, for grumpkin pub mod grumpkin { - pub use halo2curves::grumpkin::{ - Fq as Base, Fr as Scalar, G1Affine as Affine, G1Compressed as Compressed, G1 as Point, - }; + pub use halo2curves::grumpkin::{Fq as Base, Fr as Scalar, G1Affine as Affine, G1 as Point}; } -/// An implementation of the Nova `Engine` trait with BN254 curve and Pedersen commitment scheme -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct Bn256Engine; - -/// An implementation of the Nova `Engine` trait with Grumpkin curve and Pedersen commitment scheme -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct GrumpkinEngine; - impl_traits!( - Bn256Engine, bn256, Bn256Compressed, Bn256Point, @@ -61,7 +45,6 @@ impl_traits!( ); impl_traits!( - GrumpkinEngine, grumpkin, GrumpkinCompressed, GrumpkinPoint, diff --git a/src/provider/ipa_pc.rs b/src/provider/ipa_pc.rs index 07e5eea16..b4f4a2d6f 100644 --- a/src/provider/ipa_pc.rs +++ b/src/provider/ipa_pc.rs @@ -1,7 +1,7 @@ //! This module implements `EvaluationEngine` using an IPA-based polynomial commitment scheme use crate::{ errors::NovaError, - provider::{pedersen::CommitmentKeyExtTrait, DlogGroup}, + provider::{pedersen::CommitmentKeyExtTrait, traits::DlogGroup}, spartan::polys::eq::EqPolynomial, traits::{ commitment::{CommitmentEngineTrait, CommitmentTrait}, diff --git a/src/provider/keccak.rs b/src/provider/keccak.rs index 0cc06217c..92e1b17e3 100644 --- a/src/provider/keccak.rs +++ b/src/provider/keccak.rs @@ -101,9 +101,7 @@ mod tests { use crate::{ provider::keccak::Keccak256Transcript, provider::{ - bn256_grumpkin::{Bn256Engine, GrumpkinEngine}, - pasta::{PallasEngine, VestaEngine}, - secp_secq::{Secp256k1Engine, Secq256k1Engine}, + Bn256Engine, GrumpkinEngine, PallasEngine, Secp256k1Engine, Secq256k1Engine, VestaEngine, }, traits::{Engine, PrimeFieldExt, TranscriptEngineTrait, TranscriptReprTrait}, }; diff --git a/src/provider/mod.rs b/src/provider/mod.rs index 292737434..6b4887216 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -1,399 +1,123 @@ -//! This module implements Nova's traits using the following configuration: -//! `CommitmentEngine` with Pedersen's commitments -//! `Engine` with pasta curves and BN256/Grumpkin -//! `RO` traits with Poseidon -//! `EvaluationEngine` with an IPA-based polynomial evaluation argument -use crate::traits::{commitment::ScalarMul, Group, TranscriptReprTrait}; -use core::{ - fmt::Debug, - ops::{Add, AddAssign, Sub, SubAssign}, -}; -use serde::{Deserialize, Serialize}; +//! This module implements Nova's traits using the following several different combinations -/// Represents a compressed version of a group element -pub trait CompressedGroup: - Clone - + Copy - + Debug - + Eq - + Send - + Sync - + TranscriptReprTrait - + Serialize - + for<'de> Deserialize<'de> - + 'static -{ - /// A type that holds the decompressed version of the compressed group element - type GroupElement: DlogGroup; +// public modules to be used as an evaluation engine with Spartan +pub mod ipa_pc; - /// Decompresses the compressed group element - fn decompress(&self) -> Option; -} +// crate-public modules, made crate-public mostly for tests +pub(crate) mod bn256_grumpkin; +pub(crate) mod pasta; +pub(crate) mod pedersen; +pub(crate) mod poseidon; +pub(crate) mod secp_secq; +pub(crate) mod traits; -/// A helper trait for types with a group operation. -pub trait GroupOps: - Add + Sub + AddAssign + SubAssign -{ -} +// crate-private modules +mod keccak; +mod msm; -impl GroupOps for T where - T: Add + Sub + AddAssign + SubAssign -{ +use crate::{ + provider::{ + bn256_grumpkin::{bn256, grumpkin}, + keccak::Keccak256Transcript, + pedersen::CommitmentEngine as PedersenCommitmentEngine, + poseidon::{PoseidonRO, PoseidonROCircuit}, + secp_secq::{secp256k1, secq256k1}, + }, + traits::Engine, +}; +use pasta_curves::{pallas, vesta}; + +/// An implementation of the Nova `Engine` trait with BN254 curve and Pedersen commitment scheme +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Bn256Engine; + +/// An implementation of the Nova `Engine` trait with Grumpkin curve and Pedersen commitment scheme +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct GrumpkinEngine; + +impl Engine for Bn256Engine { + type Base = bn256::Base; + type Scalar = bn256::Scalar; + type GE = bn256::Point; + type RO = PoseidonRO; + type ROCircuit = PoseidonROCircuit; + type TE = Keccak256Transcript; + type CE = PedersenCommitmentEngine; } -/// A helper trait for references with a group operation. -pub trait GroupOpsOwned: for<'r> GroupOps<&'r Rhs, Output> {} -impl GroupOpsOwned for T where T: for<'r> GroupOps<&'r Rhs, Output> {} - -/// A helper trait for references implementing group scalar multiplication. -pub trait ScalarMulOwned: for<'r> ScalarMul<&'r Rhs, Output> {} -impl ScalarMulOwned for T where T: for<'r> ScalarMul<&'r Rhs, Output> {} - -/// A trait that defines extensions to the Group trait -pub trait DlogGroup: - Group - + Serialize - + for<'de> Deserialize<'de> - + GroupOps - + GroupOpsOwned - + ScalarMul<::Scalar> - + ScalarMulOwned<::Scalar> -{ - /// A type representing the compressed version of the group element - type CompressedGroupElement: CompressedGroup; - - /// A type representing preprocessed group element - type PreprocessedGroupElement: Clone - + Debug - + PartialEq - + Eq - + Send - + Sync - + Serialize - + for<'de> Deserialize<'de>; - - /// A method to compute a multiexponentation - fn vartime_multiscalar_mul( - scalars: &[Self::Scalar], - bases: &[Self::PreprocessedGroupElement], - ) -> Self; - - /// Produce a vector of group elements using a static label - fn from_label(label: &'static [u8], n: usize) -> Vec; - - /// Compresses the group element - fn compress(&self) -> Self::CompressedGroupElement; - - /// Produces a preprocessed element - fn preprocessed(&self) -> Self::PreprocessedGroupElement; - - /// Returns an element that is the additive identity of the group - fn zero() -> Self; - - /// Returns the affine coordinates (x, y, infinty) for the point - fn to_coordinates(&self) -> (::Base, ::Base, bool); +impl Engine for GrumpkinEngine { + type Base = grumpkin::Base; + type Scalar = grumpkin::Scalar; + type GE = grumpkin::Point; + type RO = PoseidonRO; + type ROCircuit = PoseidonROCircuit; + type TE = Keccak256Transcript; + type CE = PedersenCommitmentEngine; } -pub mod bn256_grumpkin; -pub mod ipa_pc; -pub mod keccak; -pub mod pasta; -pub mod pedersen; -pub mod poseidon; -pub mod secp_secq; - -use ff::PrimeField; -use pasta_curves::{self, arithmetic::CurveAffine, group::Group as AnotherGroup}; -use rayon::{current_num_threads, prelude::*}; - -/// Native implementation of fast multiexp -/// Adapted from zcash/halo2 -fn cpu_multiexp_serial(coeffs: &[C::Scalar], bases: &[C]) -> C::Curve { - let c = if bases.len() < 4 { - 1 - } else if bases.len() < 32 { - 3 - } else { - (f64::from(bases.len() as u32)).ln().ceil() as usize - }; - - fn get_at(segment: usize, c: usize, bytes: &F::Repr) -> usize { - let skip_bits = segment * c; - let skip_bytes = skip_bits / 8; - - if skip_bytes >= 32 { - return 0; - } - - let mut v = [0; 8]; - for (v, o) in v.iter_mut().zip(bytes.as_ref()[skip_bytes..].iter()) { - *v = *o; - } - - let mut tmp = u64::from_le_bytes(v); - tmp >>= skip_bits - (skip_bytes * 8); - tmp %= 1 << c; - - tmp as usize - } - - let segments = (256 / c) + 1; - - (0..segments) - .rev() - .fold(C::Curve::identity(), |mut acc, segment| { - (0..c).for_each(|_| acc = acc.double()); - - #[derive(Clone, Copy)] - enum Bucket { - None, - Affine(C), - Projective(C::Curve), - } - - impl Bucket { - fn add_assign(&mut self, other: &C) { - *self = match *self { - Bucket::None => Bucket::Affine(*other), - Bucket::Affine(a) => Bucket::Projective(a + *other), - Bucket::Projective(a) => Bucket::Projective(a + other), - } - } - - fn add(self, other: C::Curve) -> C::Curve { - match self { - Bucket::None => other, - Bucket::Affine(a) => other + a, - Bucket::Projective(a) => other + a, - } - } - } - - let mut buckets = vec![Bucket::None; (1 << c) - 1]; - - for (coeff, base) in coeffs.iter().zip(bases.iter()) { - let coeff = get_at::(segment, c, &coeff.to_repr()); - if coeff != 0 { - buckets[coeff - 1].add_assign(base); - } - } - - // Summation by parts - // e.g. 3a + 2b + 1c = a + - // (a) + b + - // ((a) + b) + c - let mut running_sum = C::Curve::identity(); - for exp in buckets.into_iter().rev() { - running_sum = exp.add(running_sum); - acc += &running_sum; - } - acc - }) +/// An implementation of the Nova `Engine` trait with Secp256k1 curve and Pedersen commitment scheme +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Secp256k1Engine; + +/// An implementation of the Nova `Engine` trait with Secp256k1 curve and Pedersen commitment scheme +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Secq256k1Engine; + +impl Engine for Secp256k1Engine { + type Base = secp256k1::Base; + type Scalar = secp256k1::Scalar; + type GE = secp256k1::Point; + type RO = PoseidonRO; + type ROCircuit = PoseidonROCircuit; + type TE = Keccak256Transcript; + type CE = PedersenCommitmentEngine; } -/// Performs a multi-exponentiation operation without GPU acceleration. -/// -/// This function will panic if coeffs and bases have a different length. -/// -/// This will use multithreading if beneficial. -/// Adapted from zcash/halo2 -pub(crate) fn cpu_best_multiexp(coeffs: &[C::Scalar], bases: &[C]) -> C::Curve { - assert_eq!(coeffs.len(), bases.len()); - - let num_threads = current_num_threads(); - if coeffs.len() > num_threads { - let chunk = coeffs.len() / num_threads; - coeffs - .par_chunks(chunk) - .zip(bases.par_chunks(chunk)) - .map(|(coeffs, bases)| cpu_multiexp_serial(coeffs, bases)) - .reduce(C::Curve::identity, |sum, evl| sum + evl) - } else { - cpu_multiexp_serial(coeffs, bases) - } +impl Engine for Secq256k1Engine { + type Base = secq256k1::Base; + type Scalar = secq256k1::Scalar; + type GE = secq256k1::Point; + type RO = PoseidonRO; + type ROCircuit = PoseidonROCircuit; + type TE = Keccak256Transcript; + type CE = PedersenCommitmentEngine; } -/// Curve ops -/// This implementation behaves in ways specific to the halo2curves suite of curves in: -// - to_coordinates, -// - vartime_multiscalar_mul, where it does not call into accelerated implementations. -// A specific reimplementation exists for the pasta curves in their own module. -#[macro_export] -macro_rules! impl_traits { - ( - $engine:ident, - $name:ident, - $name_compressed:ident, - $name_curve:ident, - $name_curve_affine:ident, - $order_str:literal, - $base_str:literal - ) => { - impl_engine!( - $engine, - $name, - $name_compressed, - $name_curve, - $order_str, - $base_str - ); - - impl DlogGroup for $name::Point { - type CompressedGroupElement = $name_compressed; - type PreprocessedGroupElement = $name::Affine; - - fn vartime_multiscalar_mul( - scalars: &[Self::Scalar], - bases: &[Self::PreprocessedGroupElement], - ) -> Self { - cpu_best_multiexp(scalars, bases) - } - - fn preprocessed(&self) -> Self::PreprocessedGroupElement { - self.to_affine() - } - - fn compress(&self) -> Self::CompressedGroupElement { - self.to_bytes() - } - - fn from_label(label: &'static [u8], n: usize) -> Vec { - let mut shake = Shake256::default(); - shake.update(label); - let mut reader = shake.finalize_xof(); - let mut uniform_bytes_vec = Vec::new(); - for _ in 0..n { - let mut uniform_bytes = [0u8; 32]; - reader.read_exact(&mut uniform_bytes).unwrap(); - uniform_bytes_vec.push(uniform_bytes); - } - let gens_proj: Vec<$name_curve> = (0..n) - .into_par_iter() - .map(|i| { - let hash = $name_curve::hash_to_curve("from_uniform_bytes"); - hash(&uniform_bytes_vec[i]) - }) - .collect(); - - let num_threads = rayon::current_num_threads(); - if gens_proj.len() > num_threads { - let chunk = (gens_proj.len() as f64 / num_threads as f64).ceil() as usize; - (0..num_threads) - .into_par_iter() - .flat_map(|i| { - let start = i * chunk; - let end = if i == num_threads - 1 { - gens_proj.len() - } else { - core::cmp::min((i + 1) * chunk, gens_proj.len()) - }; - if end > start { - let mut gens = vec![$name_curve_affine::identity(); end - start]; - ::batch_normalize(&gens_proj[start..end], &mut gens); - gens - } else { - vec![] - } - }) - .collect() - } else { - let mut gens = vec![$name_curve_affine::identity(); n]; - ::batch_normalize(&gens_proj, &mut gens); - gens - } - } - - fn zero() -> Self { - $name::Point::identity() - } - - fn to_coordinates(&self) -> (Self::Base, Self::Base, bool) { - // see: grumpkin implementation at src/provider/bn256_grumpkin.rs - let coordinates = self.to_affine().coordinates(); - if coordinates.is_some().unwrap_u8() == 1 - && ($name_curve_affine::identity() != self.to_affine()) - { - (*coordinates.unwrap().x(), *coordinates.unwrap().y(), false) - } else { - (Self::Base::zero(), Self::Base::zero(), true) - } - } - } - - impl CompressedGroup for $name_compressed { - type GroupElement = $name::Point; - - fn decompress(&self) -> Option<$name::Point> { - Some($name_curve::from_bytes(&self).unwrap()) - } - } - - impl TranscriptReprTrait for $name_compressed { - fn to_transcript_bytes(&self) -> Vec { - self.as_ref().to_vec() - } - } - }; +/// An implementation of the Nova `Engine` trait with Pallas curve and Pedersen commitment scheme +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct PallasEngine; + +/// An implementation of the Nova `Engine` trait with Vesta curve and Pedersen commitment scheme +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct VestaEngine; + +impl Engine for PallasEngine { + type Base = pallas::Base; + type Scalar = pallas::Scalar; + type GE = pallas::Point; + type RO = PoseidonRO; + type ROCircuit = PoseidonROCircuit; + type TE = Keccak256Transcript; + type CE = PedersenCommitmentEngine; } -/// Nova folding circuit engine and curve group ops -#[macro_export] -macro_rules! impl_engine { - ( - $engine:ident, - $name:ident, - $name_compressed:ident, - $name_curve:ident, - $order_str:literal, - $base_str:literal - ) => { - impl Engine for $engine { - type Base = $name::Base; - type Scalar = $name::Scalar; - type GE = $name::Point; - type RO = PoseidonRO; - type ROCircuit = PoseidonROCircuit; - type TE = Keccak256Transcript; - type CE = CommitmentEngine; - } - - impl Group for $name::Point { - type Base = $name::Base; - type Scalar = $name::Scalar; - - fn group_params() -> (Self::Base, Self::Base, BigInt, BigInt) { - let A = $name::Point::a(); - let B = $name::Point::b(); - let order = BigInt::from_str_radix($order_str, 16).unwrap(); - let base = BigInt::from_str_radix($base_str, 16).unwrap(); - - (A, B, order, base) - } - } - - impl PrimeFieldExt for $name::Scalar { - fn from_uniform(bytes: &[u8]) -> Self { - let bytes_arr: [u8; 64] = bytes.try_into().unwrap(); - $name::Scalar::from_uniform_bytes(&bytes_arr) - } - } - - impl TranscriptReprTrait for $name::Scalar { - fn to_transcript_bytes(&self) -> Vec { - self.to_repr().to_vec() - } - } - }; +impl Engine for VestaEngine { + type Base = vesta::Base; + type Scalar = vesta::Scalar; + type GE = vesta::Point; + type RO = PoseidonRO; + type ROCircuit = PoseidonROCircuit; + type TE = Keccak256Transcript; + type CE = PedersenCommitmentEngine; } #[cfg(test)] mod tests { - use super::cpu_best_multiexp; - use crate::provider::{ bn256_grumpkin::{bn256, grumpkin}, + msm::cpu_best_msm, secp_secq::{secp256k1, secq256k1}, - DlogGroup, + traits::DlogGroup, }; use digest::{ExtendableOutput, Update}; use group::{ff::Field, Curve, Group}; @@ -444,9 +168,8 @@ mod tests { .fold(A::CurveExt::identity(), |acc, (coeff, base)| { acc + *base * coeff }); - let msm = cpu_best_multiexp(&coeffs, &bases); - assert_eq!(naive, msm) + assert_eq!(naive, cpu_best_msm(&coeffs, &bases)) } #[test] diff --git a/src/provider/msm.rs b/src/provider/msm.rs new file mode 100644 index 000000000..4c499a4fc --- /dev/null +++ b/src/provider/msm.rs @@ -0,0 +1,151 @@ +//! This module provides a multi-scalar multiplication routine +/// Adapted from zcash/halo2 +use ff::PrimeField; +use pasta_curves::{self, arithmetic::CurveAffine, group::Group as AnotherGroup}; +use rayon::{current_num_threads, prelude::*}; + +fn cpu_msm_serial(coeffs: &[C::Scalar], bases: &[C]) -> C::Curve { + let c = if bases.len() < 4 { + 1 + } else if bases.len() < 32 { + 3 + } else { + (f64::from(bases.len() as u32)).ln().ceil() as usize + }; + + fn get_at(segment: usize, c: usize, bytes: &F::Repr) -> usize { + let skip_bits = segment * c; + let skip_bytes = skip_bits / 8; + + if skip_bytes >= 32 { + return 0; + } + + let mut v = [0; 8]; + for (v, o) in v.iter_mut().zip(bytes.as_ref()[skip_bytes..].iter()) { + *v = *o; + } + + let mut tmp = u64::from_le_bytes(v); + tmp >>= skip_bits - (skip_bytes * 8); + tmp %= 1 << c; + + tmp as usize + } + + let segments = (256 / c) + 1; + + (0..segments) + .rev() + .fold(C::Curve::identity(), |mut acc, segment| { + (0..c).for_each(|_| acc = acc.double()); + + #[derive(Clone, Copy)] + enum Bucket { + None, + Affine(C), + Projective(C::Curve), + } + + impl Bucket { + fn add_assign(&mut self, other: &C) { + *self = match *self { + Bucket::None => Bucket::Affine(*other), + Bucket::Affine(a) => Bucket::Projective(a + *other), + Bucket::Projective(a) => Bucket::Projective(a + other), + } + } + + fn add(self, other: C::Curve) -> C::Curve { + match self { + Bucket::None => other, + Bucket::Affine(a) => other + a, + Bucket::Projective(a) => other + a, + } + } + } + + let mut buckets = vec![Bucket::None; (1 << c) - 1]; + + for (coeff, base) in coeffs.iter().zip(bases.iter()) { + let coeff = get_at::(segment, c, &coeff.to_repr()); + if coeff != 0 { + buckets[coeff - 1].add_assign(base); + } + } + + // Summation by parts + // e.g. 3a + 2b + 1c = a + + // (a) + b + + // ((a) + b) + c + let mut running_sum = C::Curve::identity(); + for exp in buckets.into_iter().rev() { + running_sum = exp.add(running_sum); + acc += &running_sum; + } + acc + }) +} + +/// Performs a multi-scalar-multiplication operation without GPU acceleration. +/// +/// This function will panic if coeffs and bases have a different length. +/// +/// This will use multithreading if beneficial. +/// Adapted from zcash/halo2 +pub(crate) fn cpu_best_msm(coeffs: &[C::Scalar], bases: &[C]) -> C::Curve { + assert_eq!(coeffs.len(), bases.len()); + + let num_threads = current_num_threads(); + if coeffs.len() > num_threads { + let chunk = coeffs.len() / num_threads; + coeffs + .par_chunks(chunk) + .zip(bases.par_chunks(chunk)) + .map(|(coeffs, bases)| cpu_msm_serial(coeffs, bases)) + .reduce(C::Curve::identity, |sum, evl| sum + evl) + } else { + cpu_msm_serial(coeffs, bases) + } +} + +#[cfg(test)] +mod tests { + use super::cpu_best_msm; + + use crate::provider::{ + bn256_grumpkin::{bn256, grumpkin}, + secp_secq::{secp256k1, secq256k1}, + }; + use group::{ff::Field, Group}; + use halo2curves::CurveAffine; + use pasta_curves::{pallas, vesta}; + use rand_core::OsRng; + + fn test_msm_with>() { + let n = 8; + let coeffs = (0..n).map(|_| F::random(OsRng)).collect::>(); + let bases = (0..n) + .map(|_| A::from(A::generator() * F::random(OsRng))) + .collect::>(); + let naive = coeffs + .iter() + .zip(bases.iter()) + .fold(A::CurveExt::identity(), |acc, (coeff, base)| { + acc + *base * coeff + }); + let msm = cpu_best_msm(&coeffs, &bases); + + assert_eq!(naive, msm) + } + + #[test] + fn test_msm() { + test_msm_with::(); + test_msm_with::(); + test_msm_with::(); + test_msm_with::(); + test_msm_with::(); + test_msm_with::(); + } +} diff --git a/src/provider/pasta.rs b/src/provider/pasta.rs index 7d4770e25..0adfe4db3 100644 --- a/src/provider/pasta.rs +++ b/src/provider/pasta.rs @@ -1,14 +1,10 @@ //! This module implements the Nova traits for `pallas::Point`, `pallas::Scalar`, `vesta::Point`, `vesta::Scalar`. use crate::{ - impl_engine, provider::{ - cpu_best_multiexp, - keccak::Keccak256Transcript, - pedersen::CommitmentEngine, - poseidon::{PoseidonRO, PoseidonROCircuit}, - CompressedGroup, DlogGroup, + msm::cpu_best_msm, + traits::{CompressedGroup, DlogGroup}, }, - traits::{Engine, Group, PrimeFieldExt, TranscriptReprTrait}, + traits::{Group, PrimeFieldExt, TranscriptReprTrait}, }; use digest::{ExtendableOutput, Update}; use ff::{FromUniformBytes, PrimeField}; @@ -51,17 +47,8 @@ impl VestaCompressedElementWrapper { } } -/// An implementation of the Nova `Engine` trait with Pallas curve and Pedersen commitment scheme -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct PallasEngine; - -/// An implementation of the Nova `Engine` trait with Vesta curve and Pedersen commitment scheme -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct VestaEngine; - macro_rules! impl_traits { ( - $engine:ident, $name:ident, $name_compressed:ident, $name_curve:ident, @@ -69,14 +56,19 @@ macro_rules! impl_traits { $order_str:literal, $base_str:literal ) => { - impl_engine!( - $engine, - $name, - $name_compressed, - $name_curve, - $order_str, - $base_str - ); + impl Group for $name::Point { + type Base = $name::Base; + type Scalar = $name::Scalar; + + fn group_params() -> (Self::Base, Self::Base, BigInt, BigInt) { + let A = $name::Point::a(); + let B = $name::Point::b(); + let order = BigInt::from_str_radix($order_str, 16).unwrap(); + let base = BigInt::from_str_radix($base_str, 16).unwrap(); + + (A, B, order, base) + } + } impl DlogGroup for $name::Point { type CompressedGroupElement = $name_compressed; @@ -95,10 +87,10 @@ macro_rules! impl_traits { if scalars.len() >= 128 { pasta_msm::$name(bases, scalars) } else { - cpu_best_multiexp(scalars, bases) + cpu_best_msm(scalars, bases) } #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] - cpu_best_multiexp(scalars, bases) + cpu_best_msm(scalars, bases) } fn preprocessed(&self) -> Self::PreprocessedGroupElement { @@ -169,6 +161,13 @@ macro_rules! impl_traits { } } + impl PrimeFieldExt for $name::Scalar { + fn from_uniform(bytes: &[u8]) -> Self { + let bytes_arr: [u8; 64] = bytes.try_into().unwrap(); + $name::Scalar::from_uniform_bytes(&bytes_arr) + } + } + impl CompressedGroup for $name_compressed { type GroupElement = $name::Point; @@ -182,11 +181,16 @@ macro_rules! impl_traits { self.repr.to_vec() } } + + impl TranscriptReprTrait for $name::Scalar { + fn to_transcript_bytes(&self) -> Vec { + self.to_repr().to_vec() + } + } }; } impl_traits!( - PallasEngine, pallas, PallasCompressedElementWrapper, Ep, @@ -196,7 +200,6 @@ impl_traits!( ); impl_traits!( - VestaEngine, vesta, VestaCompressedElementWrapper, Eq, diff --git a/src/provider/pedersen.rs b/src/provider/pedersen.rs index 65f173dcb..daea2594c 100644 --- a/src/provider/pedersen.rs +++ b/src/provider/pedersen.rs @@ -1,7 +1,7 @@ //! This module provides an implementation of a commitment engine use crate::{ errors::NovaError, - provider::{CompressedGroup, DlogGroup}, + provider::traits::{CompressedGroup, DlogGroup}, traits::{ commitment::{CommitmentEngineTrait, CommitmentTrait, Len}, AbsorbInROTrait, Engine, ROTrait, TranscriptReprTrait, diff --git a/src/provider/poseidon.rs b/src/provider/poseidon.rs index cae48d350..cd98a7f85 100644 --- a/src/provider/poseidon.rs +++ b/src/provider/poseidon.rs @@ -204,9 +204,7 @@ where mod tests { use super::*; use crate::provider::{ - bn256_grumpkin::{Bn256Engine, GrumpkinEngine}, - pasta::{PallasEngine, VestaEngine}, - secp_secq::{Secp256k1Engine, Secq256k1Engine}, + Bn256Engine, GrumpkinEngine, PallasEngine, Secp256k1Engine, Secq256k1Engine, VestaEngine, }; use crate::{ bellpepper::solver::SatisfyingAssignment, constants::NUM_CHALLENGE_BITS, diff --git a/src/provider/secp_secq.rs b/src/provider/secp_secq.rs index 4f2aa71ea..39b582096 100644 --- a/src/provider/secp_secq.rs +++ b/src/provider/secp_secq.rs @@ -1,14 +1,11 @@ //! This module implements the Nova traits for `secp::Point`, `secp::Scalar`, `secq::Point`, `secq::Scalar`. use crate::{ - impl_engine, impl_traits, + impl_traits, provider::{ - cpu_best_multiexp, - keccak::Keccak256Transcript, - pedersen::CommitmentEngine, - poseidon::{PoseidonRO, PoseidonROCircuit}, - CompressedGroup, DlogGroup, + msm::cpu_best_msm, + traits::{CompressedGroup, DlogGroup}, }, - traits::{Engine, Group, PrimeFieldExt, TranscriptReprTrait}, + traits::{Group, PrimeFieldExt, TranscriptReprTrait}, }; use digest::{ExtendableOutput, Update}; use ff::{FromUniformBytes, PrimeField}; @@ -27,7 +24,6 @@ use halo2curves::secq256k1::{Secq256k1, Secq256k1Affine, Secq256k1Compressed}; pub mod secp256k1 { pub use halo2curves::secp256k1::{ Fp as Base, Fq as Scalar, Secp256k1 as Point, Secp256k1Affine as Affine, - Secp256k1Compressed as Compressed, }; } @@ -35,20 +31,10 @@ pub mod secp256k1 { pub mod secq256k1 { pub use halo2curves::secq256k1::{ Fp as Base, Fq as Scalar, Secq256k1 as Point, Secq256k1Affine as Affine, - Secq256k1Compressed as Compressed, }; } -/// An implementation of the Nova `Engine` trait with Secp256k1 curve and Pedersen commitment scheme -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct Secp256k1Engine; - -/// An implementation of the Nova `Engine` trait with Secp256k1 curve and Pedersen commitment scheme -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct Secq256k1Engine; - impl_traits!( - Secp256k1Engine, secp256k1, Secp256k1Compressed, Secp256k1, @@ -58,7 +44,6 @@ impl_traits!( ); impl_traits!( - Secq256k1Engine, secq256k1, Secq256k1Compressed, Secq256k1, diff --git a/src/provider/traits.rs b/src/provider/traits.rs new file mode 100644 index 000000000..aca4d2903 --- /dev/null +++ b/src/provider/traits.rs @@ -0,0 +1,228 @@ +use crate::traits::{commitment::ScalarMul, Group, TranscriptReprTrait}; +use core::{ + fmt::Debug, + ops::{Add, AddAssign, Sub, SubAssign}, +}; +use serde::{Deserialize, Serialize}; + +/// Represents a compressed version of a group element +pub trait CompressedGroup: + Clone + + Copy + + Debug + + Eq + + Send + + Sync + + TranscriptReprTrait + + Serialize + + for<'de> Deserialize<'de> + + 'static +{ + /// A type that holds the decompressed version of the compressed group element + type GroupElement: DlogGroup; + + /// Decompresses the compressed group element + fn decompress(&self) -> Option; +} + +/// A helper trait for types with a group operation. +pub trait GroupOps: + Add + Sub + AddAssign + SubAssign +{ +} + +impl GroupOps for T where + T: Add + Sub + AddAssign + SubAssign +{ +} + +/// A helper trait for references with a group operation. +pub trait GroupOpsOwned: for<'r> GroupOps<&'r Rhs, Output> {} +impl GroupOpsOwned for T where T: for<'r> GroupOps<&'r Rhs, Output> {} + +/// A helper trait for references implementing group scalar multiplication. +pub trait ScalarMulOwned: for<'r> ScalarMul<&'r Rhs, Output> {} +impl ScalarMulOwned for T where T: for<'r> ScalarMul<&'r Rhs, Output> {} + +/// A trait that defines extensions to the Group trait +pub trait DlogGroup: + Group + + Serialize + + for<'de> Deserialize<'de> + + GroupOps + + GroupOpsOwned + + ScalarMul<::Scalar> + + ScalarMulOwned<::Scalar> +{ + /// A type representing the compressed version of the group element + type CompressedGroupElement: CompressedGroup; + + /// A type representing preprocessed group element + type PreprocessedGroupElement: Clone + + Debug + + PartialEq + + Eq + + Send + + Sync + + Serialize + + for<'de> Deserialize<'de>; + + /// A method to compute a multiexponentation + fn vartime_multiscalar_mul( + scalars: &[Self::Scalar], + bases: &[Self::PreprocessedGroupElement], + ) -> Self; + + /// Produce a vector of group elements using a static label + fn from_label(label: &'static [u8], n: usize) -> Vec; + + /// Compresses the group element + fn compress(&self) -> Self::CompressedGroupElement; + + /// Produces a preprocessed element + fn preprocessed(&self) -> Self::PreprocessedGroupElement; + + /// Returns an element that is the additive identity of the group + fn zero() -> Self; + + /// Returns the affine coordinates (x, y, infinty) for the point + fn to_coordinates(&self) -> (::Base, ::Base, bool); +} + +/// This implementation behaves in ways specific to the halo2curves suite of curves in: +// - to_coordinates, +// - vartime_multiscalar_mul, where it does not call into accelerated implementations. +// A specific reimplementation exists for the pasta curves in their own module. +#[macro_export] +macro_rules! impl_traits { + ( + $name:ident, + $name_compressed:ident, + $name_curve:ident, + $name_curve_affine:ident, + $order_str:literal, + $base_str:literal + ) => { + impl Group for $name::Point { + type Base = $name::Base; + type Scalar = $name::Scalar; + + fn group_params() -> (Self::Base, Self::Base, BigInt, BigInt) { + let A = $name::Point::a(); + let B = $name::Point::b(); + let order = BigInt::from_str_radix($order_str, 16).unwrap(); + let base = BigInt::from_str_radix($base_str, 16).unwrap(); + + (A, B, order, base) + } + } + + impl DlogGroup for $name::Point { + type CompressedGroupElement = $name_compressed; + type PreprocessedGroupElement = $name::Affine; + + fn vartime_multiscalar_mul( + scalars: &[Self::Scalar], + bases: &[Self::PreprocessedGroupElement], + ) -> Self { + cpu_best_msm(scalars, bases) + } + + fn preprocessed(&self) -> Self::PreprocessedGroupElement { + self.to_affine() + } + + fn compress(&self) -> Self::CompressedGroupElement { + self.to_bytes() + } + + fn from_label(label: &'static [u8], n: usize) -> Vec { + let mut shake = Shake256::default(); + shake.update(label); + let mut reader = shake.finalize_xof(); + let mut uniform_bytes_vec = Vec::new(); + for _ in 0..n { + let mut uniform_bytes = [0u8; 32]; + reader.read_exact(&mut uniform_bytes).unwrap(); + uniform_bytes_vec.push(uniform_bytes); + } + let gens_proj: Vec<$name_curve> = (0..n) + .into_par_iter() + .map(|i| { + let hash = $name_curve::hash_to_curve("from_uniform_bytes"); + hash(&uniform_bytes_vec[i]) + }) + .collect(); + + let num_threads = rayon::current_num_threads(); + if gens_proj.len() > num_threads { + let chunk = (gens_proj.len() as f64 / num_threads as f64).ceil() as usize; + (0..num_threads) + .into_par_iter() + .flat_map(|i| { + let start = i * chunk; + let end = if i == num_threads - 1 { + gens_proj.len() + } else { + core::cmp::min((i + 1) * chunk, gens_proj.len()) + }; + if end > start { + let mut gens = vec![$name_curve_affine::identity(); end - start]; + ::batch_normalize(&gens_proj[start..end], &mut gens); + gens + } else { + vec![] + } + }) + .collect() + } else { + let mut gens = vec![$name_curve_affine::identity(); n]; + ::batch_normalize(&gens_proj, &mut gens); + gens + } + } + + fn zero() -> Self { + $name::Point::identity() + } + + fn to_coordinates(&self) -> (Self::Base, Self::Base, bool) { + let coordinates = self.to_affine().coordinates(); + if coordinates.is_some().unwrap_u8() == 1 + && ($name_curve_affine::identity() != self.to_affine()) + { + (*coordinates.unwrap().x(), *coordinates.unwrap().y(), false) + } else { + (Self::Base::zero(), Self::Base::zero(), true) + } + } + } + + impl PrimeFieldExt for $name::Scalar { + fn from_uniform(bytes: &[u8]) -> Self { + let bytes_arr: [u8; 64] = bytes.try_into().unwrap(); + $name::Scalar::from_uniform_bytes(&bytes_arr) + } + } + + impl TranscriptReprTrait for $name_compressed { + fn to_transcript_bytes(&self) -> Vec { + self.as_ref().to_vec() + } + } + + impl CompressedGroup for $name_compressed { + type GroupElement = $name::Point; + + fn decompress(&self) -> Option<$name::Point> { + Some($name_curve::from_bytes(&self).unwrap()) + } + } + + impl TranscriptReprTrait for $name::Scalar { + fn to_transcript_bytes(&self) -> Vec { + self.to_repr().to_vec() + } + } + }; +} diff --git a/src/r1cs/mod.rs b/src/r1cs/mod.rs index ce5ab0f4d..c05e82cba 100644 --- a/src/r1cs/mod.rs +++ b/src/r1cs/mod.rs @@ -590,7 +590,7 @@ mod tests { use super::*; use crate::{ - provider::{bn256_grumpkin::Bn256Engine, pasta::PallasEngine, secp_secq::Secp256k1Engine}, + provider::{Bn256Engine, PallasEngine, Secp256k1Engine}, r1cs::sparse::SparseMatrix, traits::Engine, }; diff --git a/src/r1cs/sparse.rs b/src/r1cs/sparse.rs index 530378df5..964a425fc 100644 --- a/src/r1cs/sparse.rs +++ b/src/r1cs/sparse.rs @@ -186,7 +186,7 @@ impl<'a, F: PrimeField> Iterator for Iter<'a, F> { mod tests { use super::SparseMatrix; use crate::{ - provider::pasta::PallasEngine, + provider::PallasEngine, r1cs::util::FWrap, traits::{Engine, Group}, }; diff --git a/src/spartan/direct.rs b/src/spartan/direct.rs index b57d68121..6a7fb5094 100644 --- a/src/spartan/direct.rs +++ b/src/spartan/direct.rs @@ -164,9 +164,7 @@ impl, C: StepCircuit> DirectSN #[cfg(test)] mod tests { use super::*; - use crate::provider::{ - bn256_grumpkin::Bn256Engine, pasta::PallasEngine, secp_secq::Secp256k1Engine, - }; + use crate::provider::{Bn256Engine, PallasEngine, Secp256k1Engine}; use ::bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; use core::marker::PhantomData; use ff::PrimeField; diff --git a/src/supernova/test.rs b/src/supernova/test.rs index d20ee09c1..ce8826984 100644 --- a/src/supernova/test.rs +++ b/src/supernova/test.rs @@ -1,11 +1,11 @@ use crate::gadgets::utils::alloc_zero; -use crate::provider::bn256_grumpkin::Bn256Engine; -use crate::provider::bn256_grumpkin::GrumpkinEngine; -use crate::provider::pasta::PallasEngine; -use crate::provider::pasta::VestaEngine; use crate::provider::poseidon::PoseidonConstantsCircuit; -use crate::provider::secp_secq::Secp256k1Engine; -use crate::provider::secp_secq::Secq256k1Engine; +use crate::provider::Bn256Engine; +use crate::provider::GrumpkinEngine; +use crate::provider::PallasEngine; +use crate::provider::Secp256k1Engine; +use crate::provider::Secq256k1Engine; +use crate::provider::VestaEngine; use crate::traits::circuit_supernova::{ EnforcingStepCircuit, StepCircuit, TrivialSecondaryCircuit, TrivialTestCircuit, }; diff --git a/src/supernova/utils.rs b/src/supernova/utils.rs index d3eacda41..e50261b65 100644 --- a/src/supernova/utils.rs +++ b/src/supernova/utils.rs @@ -106,7 +106,7 @@ pub fn get_selector_vec_from_index>( #[cfg(test)] mod test { - use crate::provider::pasta::PallasEngine; + use crate::provider::PallasEngine; use super::*; use bellpepper_core::test_cs::TestConstraintSystem;