diff --git a/Cargo.lock b/Cargo.lock index 86df9e1a13b..7fadf637e31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -453,9 +453,11 @@ version = "0.28.0" dependencies = [ "acir", "acvm_blackbox_solver", + "ark-ec", + "ark-ff", "flate2", "getrandom", - "hex", + "grumpkin", "js-sys", "num-bigint", "pkg-config", @@ -1828,6 +1830,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "grumpkin" +version = "0.1.0" +source = "git+https://github.com/noir-lang/grumpkin?rev=56d99799381f79e42148aaef0de2b0cf9a4b9a5d#56d99799381f79e42148aaef0de2b0cf9a4b9a5d" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-std", +] + [[package]] name = "h2" version = "0.3.20" diff --git a/acvm-repo/acir_field/src/generic_ark.rs b/acvm-repo/acir_field/src/generic_ark.rs index 63e0d3d0d98..0f4be21ad54 100644 --- a/acvm-repo/acir_field/src/generic_ark.rs +++ b/acvm-repo/acir_field/src/generic_ark.rs @@ -248,6 +248,10 @@ impl FieldElement { self.0.inverse_in_place().map(|f| FieldElement(*f)) } + pub fn from_repr(field: F) -> Self { + Self(field) + } + // XXX: This method is used while this field element // implementation is not generic. pub fn into_repr(self) -> F { diff --git a/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs b/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs index 975025971dc..582ed56584b 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs @@ -2,11 +2,9 @@ use acir::{ circuit::opcodes::FunctionInput, native_types::{Witness, WitnessMap}, }; +use acvm_blackbox_solver::BlackBoxFunctionSolver; -use crate::{ - pwg::{insert_value, witness_to_value, OpcodeResolutionError}, - BlackBoxFunctionSolver, -}; +use crate::pwg::{insert_value, witness_to_value, OpcodeResolutionError}; pub(super) fn fixed_base_scalar_mul( backend: &impl BlackBoxFunctionSolver, diff --git a/acvm-repo/barretenberg_blackbox_solver/Cargo.toml b/acvm-repo/barretenberg_blackbox_solver/Cargo.toml index acecb24c142..97e58c2804b 100644 --- a/acvm-repo/barretenberg_blackbox_solver/Cargo.toml +++ b/acvm-repo/barretenberg_blackbox_solver/Cargo.toml @@ -16,8 +16,6 @@ repository.workspace = true acir.workspace = true acvm_blackbox_solver.workspace = true thiserror.workspace = true -hex.workspace = true -num-bigint.workspace = true rust-embed = { version = "6.6.0", features = [ "debug-embed", @@ -25,6 +23,12 @@ rust-embed = { version = "6.6.0", features = [ "include-exclude", ] } +# BN254 fixed base scalar multiplication solver +grumpkin = { git = "https://github.com/noir-lang/grumpkin", rev = "56d99799381f79e42148aaef0de2b0cf9a4b9a5d", features = ["std"] } +ark-ec = { version = "^0.4.0", default-features = false } +ark-ff = { version = "^0.4.0", default-features = false } +num-bigint.workspace = true + [target.'cfg(target_arch = "wasm32")'.dependencies] wasmer = { version = "3.3", default-features = false, features = [ "js-default", diff --git a/acvm-repo/barretenberg_blackbox_solver/src/fixed_base_scalar_mul.rs b/acvm-repo/barretenberg_blackbox_solver/src/fixed_base_scalar_mul.rs new file mode 100644 index 00000000000..7f004de0fe9 --- /dev/null +++ b/acvm-repo/barretenberg_blackbox_solver/src/fixed_base_scalar_mul.rs @@ -0,0 +1,115 @@ +use ark_ec::AffineRepr; +use ark_ff::MontConfig; +use num_bigint::BigUint; + +use acir::{BlackBoxFunc, FieldElement}; + +use crate::BlackBoxResolutionError; + +pub fn fixed_base_scalar_mul( + low: &FieldElement, + high: &FieldElement, +) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { + let low: u128 = low.try_into_u128().ok_or_else(|| { + BlackBoxResolutionError::Failed( + BlackBoxFunc::FixedBaseScalarMul, + format!("Limb {} is not less than 2^128", low.to_hex()), + ) + })?; + + let high: u128 = high.try_into_u128().ok_or_else(|| { + BlackBoxResolutionError::Failed( + BlackBoxFunc::FixedBaseScalarMul, + format!("Limb {} is not less than 2^128", high.to_hex()), + ) + })?; + + let mut bytes = high.to_be_bytes().to_vec(); + bytes.extend_from_slice(&low.to_be_bytes()); + + // Check if this is smaller than the grumpkin modulus + let grumpkin_integer = BigUint::from_bytes_be(&bytes); + + if grumpkin_integer >= grumpkin::FrConfig::MODULUS.into() { + return Err(BlackBoxResolutionError::Failed( + BlackBoxFunc::FixedBaseScalarMul, + format!("{} is not a valid grumpkin scalar", grumpkin_integer.to_str_radix(16)), + )); + } + + let result = grumpkin::SWAffine::from( + grumpkin::SWAffine::generator().mul_bigint(grumpkin_integer.to_u64_digits()), + ); + if let Some((res_x, res_y)) = result.xy() { + Ok((FieldElement::from_repr(*res_x), FieldElement::from_repr(*res_y))) + } else { + Ok((FieldElement::zero(), FieldElement::zero())) + } +} + +#[cfg(test)] +mod grumpkin_fixed_base_scalar_mul { + use ark_ff::BigInteger; + + use super::*; + #[test] + fn smoke_test() -> Result<(), BlackBoxResolutionError> { + let input = FieldElement::one(); + + let res = fixed_base_scalar_mul(&input, &FieldElement::zero())?; + let x = "0000000000000000000000000000000000000000000000000000000000000001"; + let y = "0000000000000002cf135e7506a45d632d270d45f1181294833fc48d823f272c"; + + assert_eq!(x, res.0.to_hex()); + assert_eq!(y, res.1.to_hex()); + Ok(()) + } + #[test] + fn low_high_smoke_test() -> Result<(), BlackBoxResolutionError> { + let low = FieldElement::one(); + let high = FieldElement::from(2u128); + + let res = fixed_base_scalar_mul(&low, &high)?; + let x = "0702ab9c7038eeecc179b4f209991bcb68c7cb05bf4c532d804ccac36199c9a9"; + let y = "23f10e9e43a3ae8d75d24154e796aae12ae7af546716e8f81a2564f1b5814130"; + + assert_eq!(x, res.0.to_hex()); + assert_eq!(y, res.1.to_hex()); + Ok(()) + } + + #[test] + fn rejects_invalid_limbs() { + let max_limb = FieldElement::from(u128::MAX); + let invalid_limb = max_limb + FieldElement::one(); + + let expected_error = Err(BlackBoxResolutionError::Failed( + BlackBoxFunc::FixedBaseScalarMul, + "Limb 0000000000000000000000000000000100000000000000000000000000000000 is not less than 2^128".into() + )); + + let res = fixed_base_scalar_mul(&invalid_limb, &FieldElement::zero()); + assert_eq!(res, expected_error); + + let res = fixed_base_scalar_mul(&FieldElement::zero(), &invalid_limb); + assert_eq!(res, expected_error); + } + + #[test] + fn rejects_grumpkin_modulus() { + let x = grumpkin::FrConfig::MODULUS.to_bytes_be(); + + let high = FieldElement::from_be_bytes_reduce(&x[0..16]); + let low = FieldElement::from_be_bytes_reduce(&x[16..32]); + + let res = fixed_base_scalar_mul(&low, &high); + + assert_eq!( + res, + Err(BlackBoxResolutionError::Failed( + BlackBoxFunc::FixedBaseScalarMul, + "30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 is not a valid grumpkin scalar".into() + )) + ); + } +} diff --git a/acvm-repo/barretenberg_blackbox_solver/src/lib.rs b/acvm-repo/barretenberg_blackbox_solver/src/lib.rs index c3a1d457897..b9486e97bd9 100644 --- a/acvm-repo/barretenberg_blackbox_solver/src/lib.rs +++ b/acvm-repo/barretenberg_blackbox_solver/src/lib.rs @@ -5,11 +5,13 @@ use acir::{BlackBoxFunc, FieldElement}; use acvm_blackbox_solver::{BlackBoxFunctionSolver, BlackBoxResolutionError}; +mod fixed_base_scalar_mul; mod wasm; +pub use fixed_base_scalar_mul::fixed_base_scalar_mul; use wasm::Barretenberg; -use self::wasm::{Pedersen, ScalarMul, SchnorrSig}; +use self::wasm::{Pedersen, SchnorrSig}; #[deprecated = "The `BarretenbergSolver` is a temporary solution and will be removed in future."] pub struct BarretenbergSolver { @@ -77,9 +79,6 @@ impl BlackBoxFunctionSolver for BarretenbergSolver { low: &FieldElement, high: &FieldElement, ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { - #[allow(deprecated)] - self.blackbox_vendor.fixed_base(low, high).map_err(|err| { - BlackBoxResolutionError::Failed(BlackBoxFunc::FixedBaseScalarMul, err.to_string()) - }) + fixed_base_scalar_mul(low, high) } } diff --git a/acvm-repo/barretenberg_blackbox_solver/src/wasm/mod.rs b/acvm-repo/barretenberg_blackbox_solver/src/wasm/mod.rs index 22520725dca..10b1ab22a8d 100644 --- a/acvm-repo/barretenberg_blackbox_solver/src/wasm/mod.rs +++ b/acvm-repo/barretenberg_blackbox_solver/src/wasm/mod.rs @@ -6,13 +6,11 @@ mod barretenberg_structures; mod pedersen; -mod scalar_mul; mod schnorr; use barretenberg_structures::Assignments; pub(crate) use pedersen::Pedersen; -pub(crate) use scalar_mul::ScalarMul; pub(crate) use schnorr::SchnorrSig; /// The number of bytes necessary to store a `FieldElement`. @@ -34,10 +32,6 @@ pub(crate) enum FeatureError { NoValue, #[error("Value expected to be i32")] InvalidI32, - #[error("Value {scalar_as_hex} is not a valid grumpkin scalar")] - InvalidGrumpkinScalar { scalar_as_hex: String }, - #[error("Limb {limb_as_hex} is not less than 2^128")] - InvalidGrumpkinScalarLimb { limb_as_hex: String }, #[error("Could not convert value {value} from i32 to u32")] InvalidU32 { value: i32, source: std::num::TryFromIntError }, #[error("Could not convert value {value} from i32 to usize")] diff --git a/acvm-repo/barretenberg_blackbox_solver/src/wasm/scalar_mul.rs b/acvm-repo/barretenberg_blackbox_solver/src/wasm/scalar_mul.rs deleted file mode 100644 index 71e1701bc30..00000000000 --- a/acvm-repo/barretenberg_blackbox_solver/src/wasm/scalar_mul.rs +++ /dev/null @@ -1,98 +0,0 @@ -use acir::FieldElement; -use num_bigint::BigUint; - -use crate::wasm::FeatureError; - -use super::{Barretenberg, Error, FIELD_BYTES}; - -pub(crate) trait ScalarMul { - fn fixed_base( - &self, - low: &FieldElement, - high: &FieldElement, - ) -> Result<(FieldElement, FieldElement), Error>; -} - -impl ScalarMul for Barretenberg { - fn fixed_base( - &self, - low: &FieldElement, - high: &FieldElement, - ) -> Result<(FieldElement, FieldElement), Error> { - let lhs_ptr: usize = 0; - let result_ptr: usize = lhs_ptr + FIELD_BYTES; - - let low: u128 = low.try_into_u128().ok_or_else(|| { - Error::FromFeature(FeatureError::InvalidGrumpkinScalarLimb { - limb_as_hex: low.to_hex(), - }) - })?; - - let high: u128 = high.try_into_u128().ok_or_else(|| { - Error::FromFeature(FeatureError::InvalidGrumpkinScalarLimb { - limb_as_hex: high.to_hex(), - }) - })?; - - let mut bytes = high.to_be_bytes().to_vec(); - bytes.extend_from_slice(&low.to_be_bytes()); - - // Check if this is smaller than the grumpkin modulus - let grumpkin_integer = BigUint::from_bytes_be(&bytes); - let grumpkin_modulus = BigUint::from_bytes_be(&[ - 48, 100, 78, 114, 225, 49, 160, 41, 184, 80, 69, 182, 129, 129, 88, 93, 151, 129, 106, - 145, 104, 113, 202, 141, 60, 32, 140, 22, 216, 124, 253, 71, - ]); - - if grumpkin_integer >= grumpkin_modulus { - return Err(Error::FromFeature(FeatureError::InvalidGrumpkinScalar { - scalar_as_hex: hex::encode(grumpkin_integer.to_bytes_be()), - })); - } - - self.transfer_to_heap(&bytes, lhs_ptr); - self.call_multiple("compute_public_key", vec![&lhs_ptr.into(), &result_ptr.into()])?; - - let result_bytes: [u8; 2 * FIELD_BYTES] = self.read_memory(result_ptr); - let (pubkey_x_bytes, pubkey_y_bytes) = result_bytes.split_at(FIELD_BYTES); - - assert!(pubkey_x_bytes.len() == FIELD_BYTES); - assert!(pubkey_y_bytes.len() == FIELD_BYTES); - - let pubkey_x = FieldElement::from_be_bytes_reduce(pubkey_x_bytes); - let pubkey_y = FieldElement::from_be_bytes_reduce(pubkey_y_bytes); - Ok((pubkey_x, pubkey_y)) - } -} - -#[cfg(test)] -mod test { - use super::*; - #[test] - fn smoke_test() -> Result<(), Error> { - let barretenberg = Barretenberg::new(); - let input = FieldElement::one(); - - let res = barretenberg.fixed_base(&input, &FieldElement::zero())?; - let x = "0000000000000000000000000000000000000000000000000000000000000001"; - let y = "0000000000000002cf135e7506a45d632d270d45f1181294833fc48d823f272c"; - - assert_eq!(x, res.0.to_hex()); - assert_eq!(y, res.1.to_hex()); - Ok(()) - } - #[test] - fn low_high_smoke_test() -> Result<(), Error> { - let barretenberg = Barretenberg::new(); - let low = FieldElement::one(); - let high = FieldElement::from(2u128); - - let res = barretenberg.fixed_base(&low, &high)?; - let x = "0702ab9c7038eeecc179b4f209991bcb68c7cb05bf4c532d804ccac36199c9a9"; - let y = "23f10e9e43a3ae8d75d24154e796aae12ae7af546716e8f81a2564f1b5814130"; - - assert_eq!(x, res.0.to_hex()); - assert_eq!(y, res.1.to_hex()); - Ok(()) - } -} diff --git a/acvm-repo/blackbox_solver/Cargo.toml b/acvm-repo/blackbox_solver/Cargo.toml index 2e09452c492..f0295456977 100644 --- a/acvm-repo/blackbox_solver/Cargo.toml +++ b/acvm-repo/blackbox_solver/Cargo.toml @@ -34,6 +34,7 @@ p256 = { version = "0.11.0", features = [ "arithmetic", ] } + [features] default = ["bn254"] bn254 = ["acir/bn254"] diff --git a/cspell.json b/cspell.json index a667fc6d7aa..ac7953e0653 100644 --- a/cspell.json +++ b/cspell.json @@ -49,6 +49,7 @@ "fxhash", "getrandom", "gloo", + "grumpkin", "Guillaume", "hasher", "hexdigit", diff --git a/deny.toml b/deny.toml index e19406592d8..fc60154c628 100644 --- a/deny.toml +++ b/deny.toml @@ -99,4 +99,7 @@ unknown-registry = "warn" # Lint level for what to happen when a crate from a git repository that is not # in the allow list is encountered unknown-git = "deny" -allow-git = ["https://github.com/jfecher/chumsky"] +allow-git = [ + "https://github.com/noir-lang/grumpkin", + "https://github.com/jfecher/chumsky" +]