From 4b077bcab17f2bfb589775adaa99f6942978b555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= <4142+huitseeker@users.noreply.github.com> Date: Wed, 5 Jul 2023 19:10:05 -0400 Subject: [PATCH] Add Grumpkin cycle implementation (#181) * bn256+grumpkin from halo2curves * chore: Integrate halo2curves more extensively - Extend existing tests with additional test cases using the new curve types * fix: Assign correct orders to bn256 and grumpkin scalar fields - Swap scalar orders between grumpkin and bn256 in `impl_traits!` implementation * test: Finish improving test integration with halo2curves - Enhances test coverage for `pasta_curves` and `halo2curves` - Cleans up commented code in `test_ivc_nontrivial` and `test_ivc_nontrivial_with_compression` tests - Updates relevant test cases in `src/lib.rs` to include new curve tests * chore: Remove commented-out/uneeded code in bn254_grumpkin.rs * test: reproduce test_from_label for bn254_grumpkin - Implement the `from_label_serial` function in bn254_grumpkin provider - Add a test to compare parallel and serial implementations of `from_label` function * refactor: Clean up to_coordinate & summarize changes * refactor: rename bn254_grumpkin -> bn256_grumpkin * test: Expand testing for public params digest using bn256 and grumpkin * chore: Update halo2curves dependency in Cargo.toml - Updated the `halo2curves` dependency in `Cargo.toml` to the latest version `0.1.0` from a specific git branch. * refactor: Refactor multi-exponentiation methods across providers - Updated bn256_grumpkin.rs to use the cpu_best_multiexp function from pasta provider instead of its native function. - Modified visibility of cpu_best_multiexp function in pasta.rs from private to crate level. * chore: set up dependencies to import the correct getrandom feature on Wasm --------- Co-authored-by: Leo Alt --- Cargo.toml | 5 + src/bellperson/mod.rs | 4 +- src/circuit.rs | 18 ++- src/gadgets/ecc.rs | 14 +- src/lib.rs | 25 ++++ src/nifs.rs | 3 + src/provider/bn256_grumpkin.rs | 255 +++++++++++++++++++++++++++++++++ src/provider/keccak.rs | 24 ++-- src/provider/mod.rs | 1 + src/provider/pasta.rs | 2 +- src/provider/poseidon.rs | 6 +- src/spartan/pp.rs | 2 + 12 files changed, 340 insertions(+), 19 deletions(-) create mode 100644 src/provider/bn256_grumpkin.rs diff --git a/Cargo.toml b/Cargo.toml index 56b9693..47a5c71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,10 +32,15 @@ flate2 = "1.0" bitvec = "1.0" byteorder = "1.4.3" thiserror = "1.0" +halo2curves = { version="0.1.0", features = [ "derive_serde" ] } [target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64"))'.dependencies] pasta-msm = { version = "0.1.4" } +[target.wasm32-unknown-unknown.dependencies] +# see https://github.com/rust-random/rand/pull/948 +getrandom = { version = "0.2.0", default-features = false, features = ["js"]} + [dev-dependencies] criterion = { version = "0.4", features = ["html_reports"] } rand = "0.8.4" diff --git a/src/bellperson/mod.rs b/src/bellperson/mod.rs index 0e0d8c8..74588d8 100644 --- a/src/bellperson/mod.rs +++ b/src/bellperson/mod.rs @@ -62,7 +62,7 @@ mod tests { #[test] fn test_alloc_bit() { - type G = pasta_curves::pallas::Point; - test_alloc_bit_with::(); + test_alloc_bit_with::(); + test_alloc_bit_with::(); } } diff --git a/src/circuit.rs b/src/circuit.rs index d94fee1..60744f0 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -376,6 +376,7 @@ mod tests { type PastaG2 = pasta_curves::vesta::Point; use crate::constants::{BN_LIMB_WIDTH, BN_N_LIMBS}; + use crate::provider; use crate::{ bellperson::r1cs::{NovaShape, NovaWitness}, gadgets::utils::scalar_as_base, @@ -471,7 +472,7 @@ mod tests { } #[test] - fn test_recursive_circuit() { + fn test_recursive_circuit_pasta() { let params1 = NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, true); let params2 = NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, false); let ro_consts1: ROConstantsCircuit = PoseidonConstantsCircuit::new(); @@ -481,4 +482,19 @@ mod tests { params1, params2, ro_consts1, ro_consts2, 9815, 10347, ); } + + #[test] + fn test_recursive_circuit_grumpkin() { + let params1 = NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, true); + let params2 = NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, false); + let ro_consts1: ROConstantsCircuit = + PoseidonConstantsCircuit::new(); + let ro_consts2: ROConstantsCircuit = + PoseidonConstantsCircuit::new(); + + test_recursive_circuit_with::< + provider::bn256_grumpkin::bn256::Point, + provider::bn256_grumpkin::grumpkin::Point, + >(params1, params2, ro_consts1, ro_consts2, 9983, 10536); + } } diff --git a/src/gadgets/ecc.rs b/src/gadgets/ecc.rs index 86dc61e..0f982cc 100644 --- a/src/gadgets/ecc.rs +++ b/src/gadgets/ecc.rs @@ -754,6 +754,7 @@ mod tests { r1cs::{NovaShape, NovaWitness}, {shape_cs::ShapeCS, solver::SatisfyingAssignment}, }; + use crate::provider::bn256_grumpkin::{bn256, grumpkin}; use ff::{Field, PrimeFieldBits}; use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas, vesta}; use rand::rngs::OsRng; @@ -768,7 +769,6 @@ mod tests { is_infinity: bool, } - #[cfg(test)] impl Point where G: Group, @@ -896,6 +896,9 @@ mod tests { fn test_ecc_ops() { test_ecc_ops_with::(); test_ecc_ops_with::(); + + test_ecc_ops_with::(); + test_ecc_ops_with::(); } fn test_ecc_ops_with() @@ -977,6 +980,9 @@ mod tests { fn test_ecc_circuit_ops() { test_ecc_circuit_ops_with::(); test_ecc_circuit_ops_with::(); + + test_ecc_circuit_ops_with::(); + test_ecc_circuit_ops_with::(); } fn test_ecc_circuit_ops_with() @@ -1027,6 +1033,9 @@ mod tests { fn test_ecc_circuit_add_equal() { test_ecc_circuit_add_equal_with::(); test_ecc_circuit_add_equal_with::(); + + test_ecc_circuit_add_equal_with::(); + test_ecc_circuit_add_equal_with::(); } fn test_ecc_circuit_add_equal_with() @@ -1081,6 +1090,9 @@ mod tests { fn test_ecc_circuit_add_negation() { test_ecc_circuit_add_negation_with::(); test_ecc_circuit_add_negation_with::(); + + test_ecc_circuit_add_negation_with::(); + test_ecc_circuit_add_negation_with::(); } fn test_ecc_circuit_add_negation_with() diff --git a/src/lib.rs b/src/lib.rs index 405f0da..d20c8d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -793,6 +793,7 @@ fn compute_digest(o: &T) -> G::Scalar { #[cfg(test)] mod tests { + use crate::provider::bn256_grumpkin::{bn256, grumpkin}; use crate::provider::pedersen::CommitmentKeyExtTrait; use super::*; @@ -895,6 +896,23 @@ mod tests { trivial_circuit2, "3f7b25f589f2da5ab26254beba98faa54f6442ebf5fa5860caf7b08b576cab00", ); + + let trivial_circuit1_grumpkin = + TrivialTestCircuit::<::Scalar>::default(); + let trivial_circuit2_grumpkin = + TrivialTestCircuit::<::Scalar>::default(); + let cubic_circuit1_grumpkin = CubicCircuit::<::Scalar>::default(); + + test_pp_digest_with::( + trivial_circuit1_grumpkin, + trivial_circuit2_grumpkin.clone(), + "967acca1d6b4731cd65d4072c12bbaca9648f24d7bcc2877aee720e4265d4302", + ); + test_pp_digest_with::( + cubic_circuit1_grumpkin, + trivial_circuit2_grumpkin, + "44629f26a78bf6c4e3077f940232050d1793d304fdba5e221d0cf66f76a37903", + ); } fn test_ivc_trivial_with() @@ -949,6 +967,8 @@ mod tests { type G1 = pasta_curves::pallas::Point; type G2 = pasta_curves::vesta::Point; test_ivc_trivial_with::(); + + test_ivc_trivial_with::(); } fn test_ivc_nontrivial_with() @@ -1030,6 +1050,7 @@ mod tests { type G2 = pasta_curves::vesta::Point; test_ivc_nontrivial_with::(); + test_ivc_nontrivial_with::(); } fn test_ivc_nontrivial_with_compression_with() @@ -1124,6 +1145,7 @@ mod tests { type G2 = pasta_curves::vesta::Point; test_ivc_nontrivial_with_compression_with::(); + test_ivc_nontrivial_with_compression_with::(); } fn test_ivc_nontrivial_with_spark_compression_with() @@ -1221,6 +1243,7 @@ mod tests { type G2 = pasta_curves::vesta::Point; test_ivc_nontrivial_with_spark_compression_with::(); + test_ivc_nontrivial_with_spark_compression_with::(); } fn test_ivc_nondet_with_compression_with() @@ -1377,6 +1400,7 @@ mod tests { type G2 = pasta_curves::vesta::Point; test_ivc_nondet_with_compression_with::(); + test_ivc_nondet_with_compression_with::(); } fn test_ivc_base_with() @@ -1443,5 +1467,6 @@ mod tests { type G2 = pasta_curves::vesta::Point; test_ivc_base_with::(); + test_ivc_base_with::(); } } diff --git a/src/nifs.rs b/src/nifs.rs index 6dd775a..89633e7 100644 --- a/src/nifs.rs +++ b/src/nifs.rs @@ -210,6 +210,8 @@ mod tests { #[test] fn test_tiny_r1cs_bellperson() { test_tiny_r1cs_bellperson_with::(); + + test_tiny_r1cs_bellperson_with::(); } #[allow(clippy::too_many_arguments)] @@ -384,5 +386,6 @@ mod tests { #[test] fn test_tiny_r1cs() { test_tiny_r1cs_with::(); + test_tiny_r1cs_with::(); } } diff --git a/src/provider/bn256_grumpkin.rs b/src/provider/bn256_grumpkin.rs new file mode 100644 index 0000000..b591e24 --- /dev/null +++ b/src/provider/bn256_grumpkin.rs @@ -0,0 +1,255 @@ +//! This module implements the Nova traits for bn256::Point, bn256::Scalar, grumpkin::Point, grumpkin::Scalar. +use crate::{ + provider::{ + keccak::Keccak256Transcript, + pedersen::CommitmentEngine, + poseidon::{PoseidonRO, PoseidonROCircuit}, + }, + traits::{CompressedGroup, Group, PrimeFieldExt, TranscriptReprTrait}, +}; +use digest::{ExtendableOutput, Input}; +use ff::{FromUniformBytes, PrimeField}; +use num_bigint::BigInt; +use num_traits::Num; +use pasta_curves::{ + self, + arithmetic::{CurveAffine, CurveExt}, + group::{cofactor::CofactorCurveAffine, Curve, Group as AnotherGroup, GroupEncoding}, +}; +use rayon::prelude::*; +use sha3::Shake256; +use std::io::Read; + +use halo2curves::bn256::{ + G1Affine as Bn256Affine, G1Compressed as Bn256Compressed, G1 as Bn256Point, +}; +use halo2curves::grumpkin::{ + G1Affine as GrumpkinAffine, G1Compressed as GrumpkinCompressed, G1 as GrumpkinPoint, +}; + +/// 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, + }; +} + +/// 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, + }; +} + +// This implementation behaves in ways specific to the bn256/grumpkin curves in: +// - to_coordinates, +// - vartime_multiscalar_mul, where it does not call into accelerated implementations. +macro_rules! impl_traits { + ( + $name:ident, + $name_compressed:ident, + $name_curve:ident, + $name_curve_affine:ident, + $order_str:literal + ) => { + impl Group for $name::Point { + type Base = $name::Base; + type Scalar = $name::Scalar; + type CompressedGroupElement = $name_compressed; + type PreprocessedGroupElement = $name::Affine; + type RO = PoseidonRO; + type ROCircuit = PoseidonROCircuit; + type TE = Keccak256Transcript; + type CE = CommitmentEngine; + + 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.input(label); + let mut reader = shake.xof_result(); + 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) + .collect::>() + .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) + .collect::>() + .into_par_iter() + .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::>>() + .into_par_iter() + .flatten() + .collect() + } else { + let mut gens = vec![$name_curve_affine::identity(); n]; + ::batch_normalize(&gens_proj, &mut gens); + gens + } + } + + fn to_coordinates(&self) -> (Self::Base, Self::Base, bool) { + let coordinates = self.to_affine().coordinates(); + if coordinates.is_some().unwrap_u8() == 1 + // The bn256/grumpkin convention is to define and return the identity point's affine encoding (not None) + && (Self::PreprocessedGroupElement::identity() != self.to_affine()) + { + (*coordinates.unwrap().x(), *coordinates.unwrap().y(), false) + } else { + (Self::Base::zero(), Self::Base::zero(), true) + } + } + + fn get_curve_params() -> (Self::Base, Self::Base, BigInt) { + let A = $name::Point::a(); + let B = $name::Point::b(); + let order = BigInt::from_str_radix($order_str, 16).unwrap(); + + (A, B, order) + } + + fn zero() -> Self { + $name::Point::identity() + } + + fn get_generator() -> Self { + $name::Point::generator() + } + } + + 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 grumpkin::Base { + fn to_transcript_bytes(&self) -> Vec { + self.to_repr().to_vec() + } +} + +impl TranscriptReprTrait for grumpkin::Scalar { + fn to_transcript_bytes(&self) -> Vec { + self.to_repr().to_vec() + } +} + +impl_traits!( + bn256, + Bn256Compressed, + Bn256Point, + Bn256Affine, + "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001" +); + +impl_traits!( + grumpkin, + GrumpkinCompressed, + GrumpkinPoint, + GrumpkinAffine, + "30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47" +); + +/// 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 +// TODO: update once https://github.com/privacy-scaling-explorations/halo2curves/pull/29 +// (or a successor thereof) is merged +fn cpu_best_multiexp(coeffs: &[C::Scalar], bases: &[C]) -> C::Curve { + crate::provider::pasta::cpu_best_multiexp(coeffs, bases) +} + +#[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.input(label); + let mut reader = shake.xof_result(); + 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/keccak.rs b/src/provider/keccak.rs index a4daf18..e2e8661 100644 --- a/src/provider/keccak.rs +++ b/src/provider/keccak.rs @@ -95,13 +95,14 @@ impl TranscriptEngineTrait for Keccak256Transcript { #[cfg(test)] mod tests { use crate::{ + provider::bn256_grumpkin::bn256, provider::keccak::Keccak256Transcript, traits::{Group, TranscriptEngineTrait}, }; use ff::PrimeField; use sha3::{Digest, Keccak256}; - fn test_keccak_transcript_with() { + fn test_keccak_transcript_with(expected_h1: &'static str, expected_h2: &'static str) { let mut transcript: Keccak256Transcript = Keccak256Transcript::new(b"test"); // two scalars @@ -114,10 +115,7 @@ mod tests { // make a challenge let c1: ::Scalar = transcript.squeeze(b"c1").unwrap(); - assert_eq!( - hex::encode(c1.to_repr().as_ref()), - "432d5811c8be3d44d47f52108a8749ae18482efd1a37b830f966456b5d75340c" - ); + assert_eq!(hex::encode(c1.to_repr().as_ref()), expected_h1); // a scalar let s3 = ::Scalar::from(128u64); @@ -127,16 +125,20 @@ mod tests { // make a challenge let c2: ::Scalar = transcript.squeeze(b"c2").unwrap(); - assert_eq!( - hex::encode(c2.to_repr().as_ref()), - "65f7908d53abcd18f3b1d767456ef9009b91c7566a635e9ca7be26e21d4d7a10" - ); + assert_eq!(hex::encode(c2.to_repr().as_ref()), expected_h2); } #[test] fn test_keccak_transcript() { - type G = pasta_curves::pallas::Point; - test_keccak_transcript_with::() + test_keccak_transcript_with::( + "432d5811c8be3d44d47f52108a8749ae18482efd1a37b830f966456b5d75340c", + "65f7908d53abcd18f3b1d767456ef9009b91c7566a635e9ca7be26e21d4d7a10", + ); + + test_keccak_transcript_with::( + "93f9160d5501865b399ee4ff0ffe17b697a4023e33e931e2597d36e6cc4ac602", + "bca8bdb96608a8277a7cb34bd493dfbc5baf2a080d1d6c9d32d7ab4f238eb803", + ); } #[test] diff --git a/src/provider/mod.rs b/src/provider/mod.rs index ab55001..45e24c8 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -4,6 +4,7 @@ //! `RO` traits with Poseidon //! `EvaluationEngine` with an IPA-based polynomial evaluation argument +pub mod bn256_grumpkin; pub mod ipa_pc; pub mod keccak; pub mod pasta; diff --git a/src/provider/pasta.rs b/src/provider/pasta.rs index 0fa2525..25bf8d5 100644 --- a/src/provider/pasta.rs +++ b/src/provider/pasta.rs @@ -320,7 +320,7 @@ fn cpu_multiexp_serial(coeffs: &[C::Scalar], bases: &[C], acc: & /// /// This will use multithreading if beneficial. /// Adapted from zcash/halo2 -fn cpu_best_multiexp(coeffs: &[C::Scalar], bases: &[C]) -> C::Curve { +pub(crate) fn cpu_best_multiexp(coeffs: &[C::Scalar], bases: &[C]) -> C::Curve { assert_eq!(coeffs.len(), bases.len()); let num_threads = rayon::current_num_threads(); diff --git a/src/provider/poseidon.rs b/src/provider/poseidon.rs index a37ad9c..11fbf7e 100644 --- a/src/provider/poseidon.rs +++ b/src/provider/poseidon.rs @@ -201,6 +201,7 @@ where #[cfg(test)] mod tests { use super::*; + use crate::provider::bn256_grumpkin::bn256; use crate::{ bellperson::solver::SatisfyingAssignment, constants::NUM_CHALLENGE_BITS, gadgets::utils::le_bits_to_num, traits::Group, @@ -242,8 +243,7 @@ mod tests { #[test] fn test_poseidon_ro() { - type G = pasta_curves::pallas::Point; - - test_poseidon_ro_with::() + test_poseidon_ro_with::(); + test_poseidon_ro_with::(); } } diff --git a/src/spartan/pp.rs b/src/spartan/pp.rs index 6845cf4..5c9018c 100644 --- a/src/spartan/pp.rs +++ b/src/spartan/pp.rs @@ -2185,6 +2185,7 @@ impl, C: StepCircuit; test_spartan_snark_with::(); + test_spartan_snark_with::<_, crate::provider::ipa_pc::EvaluationEngine>(); } fn test_spartan_snark_with>() {