From 808808a43408f62f2a582da03f5e4a7b06e16fdb Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Mon, 10 Jun 2024 21:28:14 +0100 Subject: [PATCH] BFT-457: Remove bn254 --- node/libs/crypto/benches/bench.rs | 20 +- node/libs/crypto/src/bn254/hash.rs | 46 ---- node/libs/crypto/src/bn254/mod.rs | 313 ------------------------- node/libs/crypto/src/bn254/testonly.rs | 45 ---- node/libs/crypto/src/bn254/tests.rs | 187 --------------- node/libs/crypto/src/lib.rs | 1 - 6 files changed, 1 insertion(+), 611 deletions(-) delete mode 100644 node/libs/crypto/src/bn254/hash.rs delete mode 100644 node/libs/crypto/src/bn254/mod.rs delete mode 100644 node/libs/crypto/src/bn254/testonly.rs delete mode 100644 node/libs/crypto/src/bn254/tests.rs diff --git a/node/libs/crypto/benches/bench.rs b/node/libs/crypto/benches/bench.rs index 9d53c383..f84dd6aa 100644 --- a/node/libs/crypto/benches/bench.rs +++ b/node/libs/crypto/benches/bench.rs @@ -5,24 +5,6 @@ use criterion::{criterion_group, criterion_main, Criterion}; use rand::Rng; use std::iter::repeat_with; -fn bench_bn254(c: &mut Criterion) { - use zksync_consensus_crypto::bn254::{AggregateSignature, PublicKey, SecretKey, Signature}; - let mut rng = rand::thread_rng(); - let mut group = c.benchmark_group("bn254"); - group.bench_function("100 sig aggregation", |b| { - b.iter(|| { - let sks: Vec = repeat_with(|| rng.gen::()).take(100).collect(); - let pks: Vec = sks.iter().map(|k| k.public()).collect(); - let msg = rng.gen::<[u8; 32]>(); - let sigs: Vec = sks.iter().map(|k| k.sign(&msg)).collect(); - let agg = AggregateSignature::aggregate(&sigs); - agg.verify(pks.iter().map(|pk| (&msg[..], pk))).unwrap() - }); - }); - - group.finish(); -} - fn bench_bls12_381(c: &mut Criterion) { use zksync_consensus_crypto::bls12_381::{AggregateSignature, PublicKey, SecretKey, Signature}; let mut rng = rand::thread_rng(); @@ -41,5 +23,5 @@ fn bench_bls12_381(c: &mut Criterion) { group.finish(); } -criterion_group!(benches, bench_bls12_381, bench_bn254); +criterion_group!(benches, bench_bls12_381); criterion_main!(benches); diff --git a/node/libs/crypto/src/bn254/hash.rs b/node/libs/crypto/src/bn254/hash.rs deleted file mode 100644 index 30359abd..00000000 --- a/node/libs/crypto/src/bn254/hash.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! Hash operations. - -use ff_ce::{Field, PrimeField, SqrtField}; -use num_bigint::BigUint; -use num_traits::Num; -use pairing::{ - bn256::{fq, Fq, FqRepr, G1Affine}, - CurveAffine, -}; -use sha3::Digest as _; - -/// Hashes an arbitrary message and maps it to an elliptic curve point in G1. -pub(crate) fn hash_to_point(msg: &[u8]) -> (G1Affine, u8) { - let hash: [u8; 32] = sha3::Keccak256::new().chain_update(msg).finalize().into(); - - let hash_num = BigUint::from_bytes_be(&hash); - let prime_field_modulus = BigUint::from_str_radix( - "30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", - 16, - ) - .unwrap(); - let x_num = hash_num % prime_field_modulus; - let x_arr: [u64; 4] = x_num.to_u64_digits().try_into().unwrap(); - let mut x = Fq::from_repr(FqRepr(x_arr)).unwrap(); - - for i in 0..255 { - let p = get_point_from_x(x); - if let Some(p) = p { - return (p, i); - } - x.add_assign(&Fq::one()); - } - - // It should be statistically infeasible to finish the loop without finding a point. - unreachable!() -} - -fn get_point_from_x(x: Fq) -> Option { - // Compute x^3 + b. - let mut x3b = x; - x3b.square(); - x3b.mul_assign(&x); - x3b.add_assign(&fq::B_COEFF); - // Try find the square root. - x3b.sqrt().map(|y| G1Affine::from_xy_unchecked(x, y)) -} diff --git a/node/libs/crypto/src/bn254/mod.rs b/node/libs/crypto/src/bn254/mod.rs deleted file mode 100644 index 730313ce..00000000 --- a/node/libs/crypto/src/bn254/mod.rs +++ /dev/null @@ -1,313 +0,0 @@ -//! This module implements the BLS signature over the BN254 curve. -//! The implementation is based on the [IRTF draft v5](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05). -//! -//! Disclaimer: the implementation of the pairing-friendly elliptic curve does not run in constant time, -//! hence it does not protect the secret key from side-channel attacks. -//! - -use crate::ByteFmt; -use ff_ce::Field as _; -use pairing::{ - bn256::{Bn256, Fq12, Fr, FrRepr, G1Affine, G1Compressed, G2Affine, G2Compressed, G1, G2}, - ff::{PrimeField, PrimeFieldRepr}, - CurveAffine as _, CurveProjective as _, EncodedPoint as _, Engine as _, -}; -use std::{ - collections::HashMap, - fmt::{Debug, Formatter}, - io::Cursor, -}; - -#[cfg(test)] -mod tests; - -pub mod hash; -mod testonly; - -/// Type safety wrapper around a scalar value. -pub struct SecretKey(Fr); - -impl SecretKey { - /// Generates a secret key from a cryptographically-secure entropy source. - /// The secret key must be statistically close to uniformly random in the range 1 <= SK < r. - pub fn generate() -> Self { - loop { - let fr: Fr = rand04::Rand::rand(&mut rand04::OsRng::new().unwrap()); - - if !fr.is_zero() && fr.into_repr() < Fr::char() { - return Self(fr); - } - } - } - - /// Gets the corresponding [`PublicKey`] for this [`SecretKey`] - pub fn public(&self) -> PublicKey { - let p = G2Affine::one().mul(self.0); - let pk = PublicKey(p); - - // Verify public key is valid. Since we already check the validity of a - // secret key when constructing it, this should never fail (in theory). - pk.verify().unwrap(); - - pk - } - - /// Produces a signature using this [`SecretKey`] - pub fn sign(&self, msg: &[u8]) -> Signature { - let (msg_point, _) = hash::hash_to_point(msg); - let sig = msg_point.mul(self.0); - Signature(sig) - } -} - -impl ByteFmt for SecretKey { - fn decode(bytes: &[u8]) -> anyhow::Result { - let sk: [u8; 32] = bytes.try_into()?; - let mut fr_repr = FrRepr::default(); - fr_repr.read_be(Cursor::new(sk))?; - let fr = Fr::from_repr(fr_repr)?; - - // The secret key must be in the range 1 <= SK < r. - anyhow::ensure!(!fr.is_zero(), "Secret key can't be zero"); - anyhow::ensure!( - fr.into_repr() < Fr::char(), - "Secret key can't be more than or equal to r" - ); - - Ok(SecretKey(fr)) - } - - fn encode(&self) -> Vec { - let mut buf = Vec::new(); - self.0.into_repr().write_be(&mut buf).unwrap(); - buf - } -} - -impl Debug for SecretKey { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "SecretKey({:?})", self.public()) - } -} - -impl PartialEq for SecretKey { - fn eq(&self, other: &Self) -> bool { - self.public() == other.public() - } -} - -/// Type safety wrapper around G2. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PublicKey(G2); - -impl PublicKey { - /// Checks if the public key is not the identity element and is in the correct subgroup. Verifying signatures - /// against public keys that are not valid is insecure. - fn verify(&self) -> anyhow::Result<()> { - // Check that the point is not the identity element. - anyhow::ensure!(!self.0.is_zero(), "Public key can't be zero"); - - // We multiply the point by the order and check if the result is the identity element. - // If it is, then the point is on the correct subgroup. - let order = Fr::char(); - let mut p = self.0; - p.mul_assign(order); - anyhow::ensure!(p.is_zero(), "Public key must be in the correct subgroup"); - - Ok(()) - } -} - -impl std::hash::Hash for PublicKey { - fn hash(&self, state: &mut H) { - state.write(&self.encode()); - } -} - -impl ByteFmt for PublicKey { - fn decode(bytes: &[u8]) -> anyhow::Result { - let arr: [u8; 64] = bytes.try_into()?; - let p = G2Compressed::from_fixed_bytes(arr).into_affine()?; - let pk = PublicKey(p.into()); - - pk.verify()?; - - Ok(pk) - } - - fn encode(&self) -> Vec { - self.0.into_affine().into_compressed().as_ref().to_vec() - } -} - -impl PartialOrd for PublicKey { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for PublicKey { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - ByteFmt::encode(self).cmp(&ByteFmt::encode(other)) - } -} - -/// Type safety wrapper around a G1 value. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Signature(G1); - -impl Signature { - /// Verifies a signature against the provided public key. - /// - /// This function is intentionally non-generic and disallow inlining to ensure that compilation optimizations can be effectively applied. - /// This optimization is needed for ensuring that tests can run within a reasonable time frame. - /// - /// Subgroup checks for signatures are unnecessary when using the G1 group because it has a cofactor of 1, - /// ensuring all signatures are in the correct subgroup. - /// Ref: https://hackmd.io/@jpw/bn254#Subgroup-check-for-mathbb-G_1. - #[inline(never)] - pub fn verify(&self, msg: &[u8], pk: &PublicKey) -> anyhow::Result<()> { - // Verify public key is valid. Since we already check the validity of a - // public key when constructing it, this should never fail (in theory). - pk.verify().unwrap(); - - let (msg_point, _) = hash::hash_to_point(msg); - - // First pair: e(H(m): G1, pk: G2) - let a = Bn256::pairing(msg_point, pk.0); - // Second pair: e(sig: G1, generator: G2) - let b = Bn256::pairing(self.0, G2Affine::one()); - - anyhow::ensure!(a == b, "Signature verification failure"); - - Ok(()) - } -} - -impl ByteFmt for Signature { - fn decode(bytes: &[u8]) -> anyhow::Result { - let arr: [u8; 32] = bytes.try_into()?; - let p = G1Compressed::from_fixed_bytes(arr) - .into_affine()? - .into_projective(); - Ok(Signature(p)) - } - - fn encode(&self) -> Vec { - self.0.into_affine().into_compressed().as_ref().to_vec() - } -} - -impl std::hash::Hash for Signature { - fn hash(&self, state: &mut H) { - ByteFmt::encode(self).hash(state) - } -} - -impl PartialOrd for Signature { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Signature { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - ByteFmt::encode(self).cmp(&ByteFmt::encode(other)) - } -} - -/// Type safety wrapper around [Signature] indicating that it is an aggregated signature. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct AggregateSignature(G1); - -impl Default for AggregateSignature { - fn default() -> Self { - Self(G1Affine::zero().into_projective()) - } -} - -impl AggregateSignature { - /// Add a signature to the aggregation. - pub fn add(&mut self, sig: &Signature) { - self.0.add_assign(&sig.0) - } - - /// Generate a new aggregate signature from a list of signatures. - pub fn aggregate<'a>(sigs: impl IntoIterator) -> Self { - let mut agg = Self::default(); - for sig in sigs { - agg.add(sig); - } - agg - } - - /// This function is intentionally non-generic and disallow inlining to ensure that compilation optimizations can be effectively applied. - /// This optimization is needed for ensuring that tests can run within a reasonable time frame. - #[inline(never)] - fn verify_raw(&self, msgs_and_pks: &[(&[u8], &PublicKey)]) -> anyhow::Result<()> { - // Aggregate public keys if they are signing the same hash. Each public key aggregated - // is one fewer pairing to calculate. - let mut pairs: HashMap<&[u8], PublicKey> = HashMap::new(); - - for (msg, pk) in msgs_and_pks { - // Verify public key is valid. Since we already check the validity of a - // public key when constructing it, this should never fail (in theory). - pk.verify().unwrap(); - - pairs - .entry(msg) - .and_modify(|agg_pk| agg_pk.0.add_assign(&pk.0)) - .or_insert((*pk).clone()); - } - - // First pair: e(sig: G1, generator: G2) - let a = Bn256::pairing(self.0, G2::one()); - - // Second pair: e(H(m1): G1, pk1: G2) * ... * e(H(m1000): G1, pk1000: G2) - let mut b = Fq12::one(); - for (msg, pk) in pairs { - let (msg_point, _) = hash::hash_to_point(msg); - b.mul_assign(&Bn256::pairing(msg_point, pk.0)) - } - - anyhow::ensure!(a == b, "Aggregate signature verification failure"); - - Ok(()) - } - - /// Verifies an aggregated signature for multiple messages against the provided list of public keys. - /// This method expects one public key per message, otherwise it will fail. Note however that - /// If there are any duplicate messages, the public keys will be aggregated before verification. - pub fn verify<'a>( - &self, - msgs_and_pks: impl Iterator, - ) -> anyhow::Result<()> { - self.verify_raw(&msgs_and_pks.collect::>()[..]) - } -} - -impl ByteFmt for AggregateSignature { - fn decode(bytes: &[u8]) -> anyhow::Result { - let arr: [u8; 32] = bytes.try_into()?; - let p = G1Compressed::from_fixed_bytes(arr) - .into_affine()? - .into_projective(); - Ok(AggregateSignature(p)) - } - - fn encode(&self) -> Vec { - self.0.into_affine().into_compressed().as_ref().to_vec() - } -} - -impl PartialOrd for AggregateSignature { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for AggregateSignature { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - ByteFmt::encode(self).cmp(&ByteFmt::encode(other)) - } -} diff --git a/node/libs/crypto/src/bn254/testonly.rs b/node/libs/crypto/src/bn254/testonly.rs deleted file mode 100644 index 54de6582..00000000 --- a/node/libs/crypto/src/bn254/testonly.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! Random key generation, intended for use in testing. - -use super::{AggregateSignature, PublicKey, SecretKey, Signature}; -use pairing::bn256::{Fr, G1}; -use rand::{distributions::Standard, prelude::Distribution, Rng, RngCore}; -use rand04::Rand; - -struct RngWrapper(R); - -impl rand04::Rng for RngWrapper { - fn next_u32(&mut self) -> u32 { - self.0.next_u32() - } - - fn next_u64(&mut self) -> u64 { - self.0.next_u64() - } -} - -impl Distribution for Standard { - fn sample(&self, rng: &mut R) -> SecretKey { - let scalar = Fr::rand(&mut RngWrapper(rng)); - SecretKey(scalar) - } -} - -impl Distribution for Standard { - fn sample(&self, rng: &mut R) -> PublicKey { - rng.gen::().public() - } -} - -impl Distribution for Standard { - fn sample(&self, rng: &mut R) -> Signature { - let p = G1::rand(&mut RngWrapper(rng)); - Signature(p) - } -} - -impl Distribution for Standard { - fn sample(&self, rng: &mut R) -> AggregateSignature { - let p = G1::rand(&mut RngWrapper(rng)); - AggregateSignature(p) - } -} diff --git a/node/libs/crypto/src/bn254/tests.rs b/node/libs/crypto/src/bn254/tests.rs deleted file mode 100644 index 858a52c2..00000000 --- a/node/libs/crypto/src/bn254/tests.rs +++ /dev/null @@ -1,187 +0,0 @@ -use crate::{ - bn254::{AggregateSignature, PublicKey, SecretKey, Signature}, - ByteFmt, -}; -use ff_ce::{Field, PrimeField}; -use num_bigint::BigUint; -use pairing::{ - bn256::{Fr, G2Affine, G2}, - CurveAffine, CurveProjective, -}; -use rand::{rngs::StdRng, Rng, SeedableRng}; -use std::iter::repeat_with; - -#[test] -fn signature_smoke() { - let mut rng = StdRng::seed_from_u64(29483920); - let sk = rng.gen::(); - let pk = sk.public(); - - let msg = rng.gen::<[u8; 32]>(); - let sig = sk.sign(&msg); - - sig.verify(&msg, &pk).unwrap() -} - -#[test] -fn test_secret_key_generate() { - let mut rng = rand::thread_rng(); - let sk = rng.gen::(); - // assert 1 <= sk < r - assert!(sk.0.into_repr() < Fr::char()); - assert!(!sk.0.is_zero()); -} - -#[test] -fn test_decode_zero_secret_key_failure() { - let mut bytes: [u8; 1000] = [0; 1000]; - bytes[0] = 1; - bytes[800] = 1; - SecretKey::decode(&bytes).expect_err("Oversized secret key decoded"); - - let mut bytes: [u8; 33] = [0; 33]; - bytes[0] = 1; - bytes[32] = 1; - SecretKey::decode(&bytes).expect_err("Oversized 33 bytes secret key decoded"); - - let bytes: [u8; 31] = [0; 31]; - SecretKey::decode(&bytes).expect_err("Undersized secret key decoded"); - - let bytes: [u8; 32] = [0; 32]; - SecretKey::decode(&bytes).expect_err("zero secret key decoded"); - - // r is taken from https://hackmd.io/@jpw/bn254#Parameter-for-BN254 - let r_decimal_str = - "21888242871839275222246405745257275088548364400416034343698204186575808495617"; - let decimal_biguint = BigUint::parse_bytes(r_decimal_str.as_bytes(), 10).unwrap(); - let bytes = decimal_biguint.to_bytes_be(); - SecretKey::decode(&bytes).expect_err("sk >= r decoded"); -} - -#[test] -fn signature_failure_smoke() { - let mut rng = StdRng::seed_from_u64(29483920); - - let sk1 = rng.gen::(); - let sk2 = rng.gen::(); - let pk2 = sk2.public(); - let msg = rng.gen::<[u8; 32]>(); - let sig = sk1.sign(&msg); - - assert!(sig.verify(&msg, &pk2).is_err()) -} - -#[test] -fn aggregate_signature_smoke() { - let mut rng = StdRng::seed_from_u64(29483920); - - // Use an arbitrary 5 keys for the smoke test - let sks: Vec = repeat_with(|| rng.gen::()).take(5).collect(); - let pks: Vec = sks.iter().map(|k| k.public()).collect(); - let msg = rng.gen::<[u8; 32]>(); - - let sigs: Vec = sks.iter().map(|k| k.sign(&msg)).collect(); - let agg = AggregateSignature::aggregate(&sigs); - - agg.verify(pks.iter().map(|pk| (&msg[..], pk))).unwrap() -} - -#[test] -fn aggregate_signature_distinct_messages() { - let mut rng = StdRng::seed_from_u64(29483920); - let num_keys = 5; - let num_distinct = 2; - - // Use an arbitrary 5 keys for the smoke test - let sks: Vec = repeat_with(|| rng.gen::()) - .take(num_keys) - .collect(); - let pks: Vec = sks.iter().map(|k| k.public()).collect(); - // Create 2 distinct messages - let msgs: Vec<[u8; 32]> = repeat_with(|| rng.gen()).take(num_distinct).collect(); - - let mut sigs: Vec = Vec::new(); - let mut pairs: Vec<(&[u8], &PublicKey)> = Vec::new(); - for (i, sk) in sks.iter().enumerate() { - let msg = &msgs[i % num_distinct]; - sigs.push(sk.sign(msg)); - pairs.push((msg, &pks[i])) - } - - let agg = AggregateSignature::aggregate(&sigs); - - agg.verify(pairs.into_iter()).unwrap() -} - -#[test] -fn aggregate_signature_failure_smoke() { - let mut rng = StdRng::seed_from_u64(29483920); - - // Use an arbitrary 5 keys for the smoke test - let sks: Vec = repeat_with(|| rng.gen::()).take(5).collect(); - let pks: Vec = sks.iter().map(|k| k.public()).collect(); - let msg = rng.gen::<[u8; 32]>(); - - // Take only three signatures for the aggregate - let sigs: Vec = sks.iter().take(3).map(|k| k.sign(&msg)).collect(); - - let agg = AggregateSignature::aggregate(&sigs); - - assert!(agg.verify(pks.iter().map(|pk| (&msg[..], pk))).is_err()) -} - -#[test] -fn byte_fmt_correctness() { - let mut rng = rand::thread_rng(); - - let sk: SecretKey = rng.gen(); - let bytes = sk.encode(); - let sk_decoded = SecretKey::decode(&bytes).unwrap(); - assert_eq!(sk, sk_decoded); - - let pk: PublicKey = rng.gen(); - let bytes = pk.encode(); - let pk_decoded = PublicKey::decode(&bytes).unwrap(); - assert_eq!(pk, pk_decoded); - - let sig: Signature = rng.gen(); - let bytes = sig.encode(); - let sig_decoded = Signature::decode(&bytes).unwrap(); - assert_eq!(sig, sig_decoded); - - let agg: AggregateSignature = rng.gen(); - let bytes = agg.encode(); - let agg_decoded = AggregateSignature::decode(&bytes).unwrap(); - assert_eq!(agg, agg_decoded); -} - -#[test] -fn pk_is_valid_correctness() { - // Check that the null point is invalid. - let mut pk = PublicKey(G2::zero()); - assert!(pk.verify().is_err()); - - // Check that a point in the wrong subgroup is invalid. - let mut rng = rand04::OsRng::new().unwrap(); - - loop { - let x = rand04::Rand::rand(&mut rng); - let greatest = rand04::Rand::rand(&mut rng); - - if let Some(p) = G2Affine::get_point_from_x(x, greatest) { - if !p.is_zero() && p.is_on_curve() { - // Check that it's not on the subgroup. - let order = Fr::char(); - let mut p = p.into_projective(); - p.mul_assign(order); - - if !p.is_zero() { - pk = PublicKey(p); - break; - } - } - } - } - - assert!(pk.verify().is_err()); -} diff --git a/node/libs/crypto/src/lib.rs b/node/libs/crypto/src/lib.rs index c3d6231f..3fd4e8e2 100644 --- a/node/libs/crypto/src/lib.rs +++ b/node/libs/crypto/src/lib.rs @@ -5,7 +5,6 @@ pub use fmt::*; /// Currently replaced by [bn254] and unused. pub mod bls12_381; -pub mod bn254; pub mod ed25519; mod fmt; pub mod keccak256;