From bb061f9ad34283496d6139cccb47c3e0014fc060 Mon Sep 17 00:00:00 2001 From: Elder Ryan Date: Wed, 29 May 2024 18:27:33 +0800 Subject: [PATCH] feat: support BLS (Boneh-Lynn-Shacham) signature (#579) Map privatekey to bls primefield Map publickey to projective point of G2 Implemented sign and verify of bls implementation of bls aggregate The msg is extending with prefix "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_ which is as same as filecoin's implementation. --- .github/workflows/qaci.yml | 4 +- Cargo.lock | 186 ++++++++++++++++- crates/core/Cargo.toml | 5 + crates/core/src/ecc/elgamal.rs | 6 +- crates/core/src/ecc/mod.rs | 45 ++-- crates/core/src/ecc/signers/bip137.rs | 2 +- crates/core/src/ecc/signers/bls.rs | 248 +++++++++++++++++++++++ crates/core/src/ecc/signers/ed25519.rs | 6 +- crates/core/src/ecc/signers/eip191.rs | 2 +- crates/core/src/ecc/signers/mod.rs | 1 + crates/core/src/ecc/signers/secp256k1.rs | 2 +- crates/core/src/ecc/signers/secp256r1.rs | 10 +- crates/core/src/ecc/types.rs | 24 +-- crates/core/src/error.rs | 19 +- crates/core/src/session.rs | 18 +- 15 files changed, 505 insertions(+), 73 deletions(-) create mode 100644 crates/core/src/ecc/signers/bls.rs diff --git a/.github/workflows/qaci.yml b/.github/workflows/qaci.yml index e486ad84e..2505a0a91 100644 --- a/.github/workflows/qaci.yml +++ b/.github/workflows/qaci.yml @@ -53,7 +53,7 @@ jobs: - name: Run core browser tests uses: coactions/setup-xvfb@v1 with: - run: cargo test -p rings-core --target=wasm32-unknown-unknown --features wasm --no-default-features + run: cargo test -p rings-core --release --target=wasm32-unknown-unknown --features wasm --no-default-features working-directory: ./ - name: Run node browser tests @@ -64,7 +64,7 @@ jobs: build: name: Build and test - timeout-minutes: 25 + timeout-minutes: 30 strategy: matrix: os: ["ubuntu-latest"] diff --git a/Cargo.lock b/Cargo.lock index 0ff56fb68..998578cb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,6 +74,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -158,6 +170,124 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +[[package]] +name = "ark-bls12-381" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint 0.4.4", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint 0.4.4", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest 0.10.7", + "num-bigint 0.4.4", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "arrayref" version = "0.3.7" @@ -1939,7 +2069,16 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.7", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.11", ] [[package]] @@ -2821,9 +2960,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -3775,8 +3914,13 @@ dependencies = [ [[package]] name = "rings-core" -version = "0.6.1" +version = "0.7.0" dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", "arrayref", "async-lock", "async-recursion", @@ -3838,7 +3982,7 @@ dependencies = [ [[package]] name = "rings-derive" -version = "0.6.1" +version = "0.7.0" dependencies = [ "proc-macro2", "quote", @@ -3848,7 +3992,7 @@ dependencies = [ [[package]] name = "rings-native-example" -version = "0.6.1" +version = "0.7.0" dependencies = [ "async-trait", "rings-core", @@ -3860,7 +4004,7 @@ dependencies = [ [[package]] name = "rings-node" -version = "0.6.1" +version = "0.7.0" dependencies = [ "anyhow", "arrayref", @@ -3920,7 +4064,7 @@ dependencies = [ [[package]] name = "rings-rpc" -version = "0.6.1" +version = "0.7.0" dependencies = [ "async-trait", "base64 0.13.1", @@ -3940,7 +4084,7 @@ dependencies = [ [[package]] name = "rings-snark" -version = "0.6.1" +version = "0.7.0" dependencies = [ "bellman_ce", "bellpepper-core", @@ -3969,7 +4113,7 @@ dependencies = [ [[package]] name = "rings-snark-example" -version = "0.6.1" +version = "0.7.0" dependencies = [ "async-trait", "rings-core", @@ -3981,7 +4125,7 @@ dependencies = [ [[package]] name = "rings-transport" -version = "0.6.1" +version = "0.7.0" dependencies = [ "async-trait", "bincode", @@ -6157,6 +6301,26 @@ dependencies = [ "time", ] +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "zeroize" version = "1.7.0" diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 87b96ee8a..099ff21ea 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -42,6 +42,11 @@ browser_chrome_test = ["wasm"] [dependencies] # global +ark-bls12-381 = "0.4.0" +ark-ec = "0.4.2" +ark-ff = "0.4.2" +ark-serialize = "0.4.2" +ark-std = "0.4.0" arrayref = "0.3.6" async-lock = "2.5.0" async-recursion = "1.0.0" diff --git a/crates/core/src/ecc/elgamal.rs b/crates/core/src/ecc/elgamal.rs index 175cbeb46..3cc90c3ba 100644 --- a/crates/core/src/ecc/elgamal.rs +++ b/crates/core/src/ecc/elgamal.rs @@ -117,7 +117,7 @@ pub fn affine_to_str(a: &[Affine]) -> Result { field_to_str(a.iter().map(|x| x.x).collect::>().as_slice()) } -pub fn encrypt(s: &str, k: PublicKey) -> Result> { +pub fn encrypt(s: &str, k: PublicKey<33>) -> Result, CurveEle<33>)>> { let random_sar: Scalar = SecretKey::random().into(); let mut h: Affine = k.try_into()?; h.y.normalize(); @@ -143,14 +143,14 @@ pub fn encrypt(s: &str, k: PublicKey) -> Result> { (a_c1, a_c2) }) .collect(); - let mut ret: Vec<(CurveEle, CurveEle)> = vec![]; + let mut ret: Vec<(CurveEle<33>, CurveEle<33>)> = vec![]; for (c1, c2) in affines { ret.push((c1.try_into()?, c2.try_into()?)) } Ok(ret) } -pub fn decrypt(m: &[(CurveEle, CurveEle)], k: SecretKey) -> Result { +pub fn decrypt(m: &[(CurveEle<33>, CurveEle<33>)], k: SecretKey) -> Result { let sar: Scalar = k.into(); let cxt = ECMultContext::new_boxed(); affine_to_str( diff --git a/crates/core/src/ecc/mod.rs b/crates/core/src/ecc/mod.rs index a896458ab..321980053 100644 --- a/crates/core/src/ecc/mod.rs +++ b/crates/core/src/ecc/mod.rs @@ -33,11 +33,12 @@ pub use types::PublicKey; /// length r: 32, length s: 32, length v(recovery_id): 1 pub type SigBytes = [u8; 65]; /// Alias PublicKey. -pub type CurveEle = PublicKey; +pub type CurveEle = PublicKey; /// PublicKeyAddress is H160. pub type PublicKeyAddress = H160; /// Wrap libsecp256k1::SecretKey. +/// which is a A 256-bit scalar value, present as [u32; 4] #[derive(PartialEq, Eq, Debug, Clone, Copy)] pub struct SecretKey(libsecp256k1::SecretKey); @@ -79,22 +80,23 @@ impl From for libsecp256k1::SecretKey { } } -impl TryFrom for libsecp256k1::PublicKey { +impl TryFrom> for libsecp256k1::PublicKey { type Error = Error; - fn try_from(key: PublicKey) -> Result { - Self::parse_compressed(&key.0).map_err(|_| Error::ECDSAPublicKeyBadFormat) + fn try_from(key: PublicKey<33>) -> Result { + let data: [u8; 33] = key.0; + Self::parse_compressed(&data).map_err(|_| Error::ECDSAPublicKeyBadFormat) } } -impl TryFrom for ed25519_dalek::PublicKey { +impl TryFrom> for ed25519_dalek::PublicKey { type Error = Error; - fn try_from(key: PublicKey) -> Result { + fn try_from(key: PublicKey<33>) -> Result { // pubkey[0] == 0 Self::from_bytes(&key.0[1..]).map_err(|_| Error::EdDSAPublicKeyBadFormat) } } -impl AffineCoordinates for PublicKey { +impl AffineCoordinates for PublicKey<33> { type FieldRepr = GenericArray; fn x(&self) -> Self::FieldRepr { @@ -111,7 +113,7 @@ impl AffineCoordinates for PublicKey { } } -impl PublicKey { +impl PublicKey<33> { /// Map a PublicKey into secp256r1 affine point, /// This function is an constant-time cryptographic implementations pub fn ct_into_secp256r1_affine(self) -> CtOption> { @@ -140,7 +142,7 @@ impl From for FieldBytes { } } -impl From for PublicKey { +impl From for PublicKey<33> { fn from(key: ed25519_dalek::PublicKey) -> Self { // [u8;32] here // ref: https://docs.rs/ed25519-dalek/latest/ed25519_dalek/struct.PublicKey.html @@ -153,14 +155,14 @@ impl From for PublicKey { } } -impl TryFrom for libsecp256k1::curve::Affine { +impl TryFrom> for libsecp256k1::curve::Affine { type Error = Error; - fn try_from(key: PublicKey) -> Result { + fn try_from(key: PublicKey<33>) -> Result { Ok(TryInto::::try_into(key)?.into()) } } -impl TryFrom for PublicKey { +impl TryFrom for PublicKey<33> { type Error = Error; fn try_from(a: libsecp256k1::curve::Affine) -> Result { let pubkey: libsecp256k1::PublicKey = a.try_into().map_err(|_| Error::InvalidPublicKey)?; @@ -180,13 +182,13 @@ impl From for SecretKey { } } -impl From for PublicKey { +impl From for PublicKey<33> { fn from(key: libsecp256k1::PublicKey) -> Self { Self(key.serialize_compressed()) } } -impl From for PublicKey { +impl From for PublicKey<33> { fn from(secret_key: SecretKey) -> Self { libsecp256k1::PublicKey::from_secret_key(&secret_key.0).into() } @@ -213,10 +215,7 @@ impl TryFrom<&str> for SecretKey { fn try_from(s: &str) -> Result { let key = hex::decode(s)?; let key_arr: [u8; 32] = key.as_slice().try_into()?; - match libsecp256k1::SecretKey::parse(&key_arr) { - Ok(key) => Ok(key.into()), - Err(e) => Err(Error::Libsecp256k1SecretKeyParse(format!("{:?}", e))), - } + Ok(libsecp256k1::SecretKey::parse(&key_arr)?.into()) } } @@ -262,7 +261,7 @@ impl Serialize for SecretKey { } } -fn public_key_address(pubkey: &PublicKey) -> PublicKeyAddress { +fn public_key_address(pubkey: &PublicKey<33>) -> PublicKeyAddress { let hash = match TryInto::::try_into(*pubkey) { // if pubkey is ecdsa key Ok(pk) => { @@ -310,7 +309,7 @@ impl SecretKey { sig_bytes } - pub fn pubkey(&self) -> PublicKey { + pub fn pubkey(&self) -> PublicKey<33> { libsecp256k1::PublicKey::from_secret_key(&(*self).into()).into() } @@ -319,14 +318,14 @@ impl SecretKey { } } -impl PublicKey { +impl PublicKey<33> { pub fn address(&self) -> PublicKeyAddress { public_key_address(self) } } /// Recover PublicKey from RawMessage using signature. -pub fn recover(message: &[u8], signature: S) -> Result +pub fn recover(message: &[u8], signature: S) -> Result> where S: AsRef<[u8]> { let sig_bytes: SigBytes = signature.as_ref().try_into()?; let message_hash: [u8; 32] = keccak256(message); @@ -334,7 +333,7 @@ where S: AsRef<[u8]> { } /// Recover PublicKey from HashMessage using signature. -pub fn recover_hash(message_hash: &[u8; 32], sig: &[u8; 65]) -> Result { +pub fn recover_hash(message_hash: &[u8; 32], sig: &[u8; 65]) -> Result> { let r_s_signature: [u8; 64] = sig[..64].try_into()?; let recovery_id: u8 = sig[64]; Ok(libsecp256k1::recover( diff --git a/crates/core/src/ecc/signers/bip137.rs b/crates/core/src/ecc/signers/bip137.rs index eb497e3d1..8a946fba0 100644 --- a/crates/core/src/ecc/signers/bip137.rs +++ b/crates/core/src/ecc/signers/bip137.rs @@ -20,7 +20,7 @@ use crate::error::Result; /// | odd | less than n | true | 1 | 32 | /// | even | more than n | true | 2 | 33 | /// | odd | more than n | true | 3 | 34 | -pub fn recover(msg: &[u8], sig: impl AsRef<[u8]>) -> Result { +pub fn recover(msg: &[u8], sig: impl AsRef<[u8]>) -> Result> { let mut sig = sig.as_ref().to_vec(); sig.rotate_left(1); let sig = sig.as_mut_slice(); diff --git a/crates/core/src/ecc/signers/bls.rs b/crates/core/src/ecc/signers/bls.rs new file mode 100644 index 000000000..780c99c9f --- /dev/null +++ b/crates/core/src/ecc/signers/bls.rs @@ -0,0 +1,248 @@ +//! signer of bls +//! A module for signing messages using BLS (Boneh-Lynn-Shacham) and interfacing with secp256k1 cryptographic libraries. +//! +//! This module provides functionality for generating private and public keys, signing messages, +//! and verifying signatures using both BLS and secp256k1 cryptographic standards. +//! It integrates the use of random number generation for key creation and provides conversions +//! between different key types. + +use ark_bls12_381::fr::Fr; +use ark_bls12_381::g2::Config as G2Config; +use ark_bls12_381::Bls12_381; +use ark_bls12_381::G1Projective; +use ark_bls12_381::G2Projective; +use ark_ec::hashing::curve_maps::wb::WBMap; +use ark_ec::hashing::map_to_curve_hasher::MapToCurveBasedHasher; +use ark_ec::hashing::HashToCurve; +use ark_ec::pairing::Pairing; +use ark_ec::Group; +use ark_ff::fields::field_hashers::DefaultFieldHasher; +use ark_serialize::CanonicalDeserialize; +use ark_serialize::CanonicalSerialize; +use ark_std::UniformRand; +use libsecp256k1; +use rand::SeedableRng; +use rand_hc::Hc128Rng; + +use crate::ecc::PublicKey; +use crate::ecc::SecretKey; +use crate::error::Error; +use crate::error::Result; + +/// this is from `` +const CSUITE: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; + +/// Represents a BLS signature, stored as a 96-byte array. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Signature(pub [u8; 96]); + +/// this function is used to generate a random secret key +pub fn random_sk() -> Result { + let mut rng = Hc128Rng::from_entropy(); + Fr::rand(&mut rng).try_into() +} + +fn from_compressed(a: &[u8; S]) -> Result { + T::deserialize_compressed(&a[..]).map_err(|_| Error::EccDeserializeFailed) +} + +fn to_compressed(s: &T) -> Result<[u8; S]> { + let mut data: Vec = vec![]; + s.serialize_compressed(&mut data) + .map_err(|_| Error::EccSerializeFailed)?; + assert_eq!(s.compressed_size(), S); + assert_eq!(data.len(), S); + let ret: [u8; S] = data.try_into().map_err(|_| Error::EccSerializeFailed)?; + Ok(ret) +} + +impl TryFrom for Fr { + type Error = Error; + fn try_from(sk: SecretKey) -> Result { + let data: [u8; 32] = sk.0.serialize(); + let ret: Fr = from_compressed(&data)?; + Ok(ret) + } +} + +impl TryFrom for SecretKey { + type Error = Error; + fn try_from(sk: Fr) -> Result { + let data: [u8; 32] = to_compressed(&sk)?; + let sk = libsecp256k1::SecretKey::parse(&data)?; + Ok(SecretKey(sk)) + } +} + +impl TryFrom for G2Projective { + type Error = Error; + fn try_from(s: Signature) -> Result { + from_compressed(&s.0) + } +} + +impl TryFrom for Signature { + type Error = Error; + fn try_from(s: G2Projective) -> Result { + Ok(Signature(to_compressed(&s)?)) + } +} + +impl TryFrom for PublicKey<48> { + type Error = Error; + fn try_from(p: G1Projective) -> Result { + Ok(PublicKey(to_compressed::(&p)?)) + } +} + +impl TryFrom> for G1Projective { + type Error = Error; + fn try_from(pk: PublicKey<48>) -> Result { + let data: [u8; 48] = pk.0; + let ret: Self = from_compressed(&data)?; + Ok(ret) + } +} + +/// Hashes a message to a 96-byte array using BLS +/// `` +pub fn hash_to_curve(msg: &[u8]) -> Result<[u8; 96]> { + // let swu_map: WBMap = WBMap::new().unwrap(); + let hasher = MapToCurveBasedHasher::< + G2Projective, + DefaultFieldHasher, + WBMap, + >::new(CSUITE) + .map_err(|_| Error::CurveHasherInitFailed)?; + let hashed = hasher.hash(msg).map_err(|_| Error::CurveHasherFailed)?; + let ret: [u8; 96] = to_compressed(&hashed)?; + Ok(ret) +} + +/// Sign hashed message with bls privatekey +pub fn sign_hash(sk: SecretKey, hashed_msg: &[u8; 96]) -> Result { + let sk: Fr = sk.try_into()?; + let msg: G2Projective = from_compressed(hashed_msg).unwrap(); + Ok(Signature(to_compressed(&(msg * sk))?)) +} + +/// Sign message with bls privatekey +/// signature = hash_into_g2(message) * sk +pub fn sign(sk: SecretKey, msg: &[u8]) -> Result { + let sk: Fr = sk.try_into()?; + let hashed_msg = hash_to_curve(msg)?; + let msg: G2Projective = from_compressed(&hashed_msg).unwrap(); + Ok(Signature(to_compressed(&(msg * sk))?)) +} + +/// Verifies that the signature is the actual aggregated signature of hashes - pubkeys. Calculated by +/// e(g1, signature) == \prod_{i = 0}^n e(pk_i, hash_i). +pub fn verify_hash(hashes: &[[u8; 96]], sig: &Signature, pks: &[PublicKey<48>]) -> Result { + let sig: G2Projective = sig.clone().try_into()?; + let g1 = G1Projective::generator(); + let e1 = Bls12_381::pairing(g1, sig); + + let hashes: Vec = hashes + .iter() + .map(from_compressed) + .collect::>>()?; + + let pks: Vec = pks + .iter() + .map(|pk| (*pk).try_into()) + .collect::>>()?; + + let mm_out = Bls12_381::multi_miller_loop(pks, hashes); + if let Some(e2) = Bls12_381::final_exponentiation(mm_out) { + Ok(e1 == e2) + } else { + Ok(false) + } +} + +/// Verifies that the signature is the actual aggregated signature of messages - pubkeys. Calculated by +/// e(g1, signature) == \prod_{i = 0}^n e(pk_i, hash_to_curve(message_i)). +pub fn verify(msgs: &[&[u8]], sig: &Signature, pks: &[PublicKey<48>]) -> Result { + let hashes: Vec<[u8; 96]> = msgs + .iter() + .map(|msg| hash_to_curve(msg)) + .collect::>>()?; + verify_hash(hashes.as_slice(), sig, pks) +} + +// Aggregate signatures by multiplying them together. Calculated by signature = \sum_{i = 0}^n signature_i. +pub fn aggregate(signatures: &[Signature]) -> Result { + signatures + .iter() + .map(|sig| sig.clone().try_into()) + .collect::>>()? + .iter() + .sum::() + .try_into() +} + +/// Converts a BLS private key to a BLS public key. +/// Get the public key for this private key. Calculated by pk = g1 * sk. +pub fn public_key(key: &SecretKey) -> Result> { + let sk: Fr = (*key).try_into()?; + let g1 = G1Projective::generator(); + (g1 * sk).try_into() +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_sign_and_verify() { + let key = random_sk().unwrap(); + let msg = "hello world"; + let pk = public_key(&key).unwrap(); + let h = hash_to_curve(msg.as_bytes()).unwrap(); + let sig = sign_hash(key, &h).unwrap(); + assert!(super::verify_hash(vec![h].as_slice(), &sig, vec![pk].as_slice()).unwrap()); + assert!(super::verify(vec![msg.as_bytes()].as_slice(), &sig, vec![pk].as_slice()).unwrap()); + } + + #[test] + fn test_hash_result() { + // this is from hash("hello world") via bls_signature + // ` + let hashed_data: [u8; 96] = [ + 138, 203, 106, 10, 25, 0, 11, 120, 167, 254, 109, 207, 27, 42, 63, 46, 108, 179, 30, + 196, 146, 10, 94, 148, 237, 209, 198, 48, 23, 211, 67, 188, 147, 170, 94, 52, 176, 113, + 111, 214, 28, 35, 235, 16, 215, 69, 185, 65, 15, 66, 199, 2, 245, 101, 145, 144, 209, + 52, 71, 179, 27, 209, 127, 155, 231, 9, 235, 11, 82, 89, 83, 171, 47, 179, 253, 128, + 26, 104, 238, 91, 182, 207, 152, 70, 243, 206, 65, 226, 81, 113, 69, 125, 85, 142, 27, + 254, + ]; + let msg = "hello world"; + let h = hash_to_curve(msg.as_bytes()).unwrap(); + assert_eq!(h, hashed_data); + } + + #[test] + fn test_aggregate() { + let key1 = random_sk().unwrap(); + let key2 = random_sk().unwrap(); + + let msg1 = "hello alice"; + let msg2 = "hello bob"; + + let pk1 = public_key(&key1).unwrap(); + let pk2 = public_key(&key2).unwrap(); + + let h1 = hash_to_curve(msg1.as_bytes()).unwrap(); + let h2 = hash_to_curve(msg2.as_bytes()).unwrap(); + + let sig1 = sign_hash(key1, &h1).unwrap(); + let sig2 = sign_hash(key2, &h2).unwrap(); + + let sig_agg = aggregate(&[sig1, sig2]).unwrap(); + + assert!( + super::verify_hash(vec![h1, h2].as_slice(), &sig_agg, vec![pk1, pk2].as_slice()) + .unwrap() + ); + } +} diff --git a/crates/core/src/ecc/signers/ed25519.rs b/crates/core/src/ecc/signers/ed25519.rs index bea05c535..be2c9caf8 100644 --- a/crates/core/src/ecc/signers/ed25519.rs +++ b/crates/core/src/ecc/signers/ed25519.rs @@ -9,7 +9,7 @@ pub fn verify( msg: &[u8], address: &PublicKeyAddress, sig: impl AsRef<[u8]>, - pubkey: PublicKey, + pubkey: &PublicKey<33>, ) -> bool { if pubkey.address() != *address { return false; @@ -19,7 +19,7 @@ pub fn verify( } let sig_data: [u8; 64] = sig.as_ref().try_into().unwrap(); if let (Ok(p), Ok(s)) = ( - TryInto::::try_into(pubkey), + TryInto::::try_into(*pubkey), ed25519_dalek::Signature::from_bytes(&sig_data), ) { match p.verify(msg, &s) { @@ -58,7 +58,7 @@ mod test { msg.as_bytes(), &signer.address(), sig.as_slice(), - signer + &signer )) } } diff --git a/crates/core/src/ecc/signers/eip191.rs b/crates/core/src/ecc/signers/eip191.rs index 959470d31..72e41b560 100644 --- a/crates/core/src/ecc/signers/eip191.rs +++ b/crates/core/src/ecc/signers/eip191.rs @@ -27,7 +27,7 @@ pub fn hash(msg: &[u8]) -> [u8; 32] { } /// recover pubkey according to signature. -pub fn recover(msg: &[u8], sig: impl AsRef<[u8]>) -> Result { +pub fn recover(msg: &[u8], sig: impl AsRef<[u8]>) -> Result> { let sig_byte: [u8; 65] = sig.as_ref().try_into()?; let hash = hash(msg); let mut sig712 = sig_byte; diff --git a/crates/core/src/ecc/signers/mod.rs b/crates/core/src/ecc/signers/mod.rs index 5b604b9ee..2ab473b20 100644 --- a/crates/core/src/ecc/signers/mod.rs +++ b/crates/core/src/ecc/signers/mod.rs @@ -1,4 +1,5 @@ pub mod bip137; +pub mod bls; pub mod ed25519; pub mod eip191; pub mod secp256k1; diff --git a/crates/core/src/ecc/signers/secp256k1.rs b/crates/core/src/ecc/signers/secp256k1.rs index 25b2f2b77..b943a804b 100644 --- a/crates/core/src/ecc/signers/secp256k1.rs +++ b/crates/core/src/ecc/signers/secp256k1.rs @@ -22,7 +22,7 @@ pub fn hash(msg: &[u8]) -> [u8; 32] { } /// recover public key from message and signature. -pub fn recover(msg: &[u8], sig: impl AsRef<[u8]>) -> Result { +pub fn recover(msg: &[u8], sig: impl AsRef<[u8]>) -> Result> { let sig_byte: [u8; 65] = sig.as_ref().try_into()?; crate::ecc::recover(msg, sig_byte) } diff --git a/crates/core/src/ecc/signers/secp256r1.rs b/crates/core/src/ecc/signers/secp256r1.rs index 9f31174eb..48fc59074 100644 --- a/crates/core/src/ecc/signers/secp256r1.rs +++ b/crates/core/src/ecc/signers/secp256r1.rs @@ -94,7 +94,7 @@ pub fn verify( msg: &[u8], address: &PublicKeyAddress, sig: impl AsRef<[u8]>, - pubkey: PublicKey, + pubkey: &PublicKey<33>, ) -> bool { if pubkey.address() != *address { return false; @@ -103,7 +103,7 @@ pub fn verify( return false; } let ct_pk: CtOption>> = - pubkey.ct_try_into_secp256r1_pubkey(); + (*pubkey).ct_try_into_secp256r1_pubkey(); let msg_hash = hash(msg); if ct_pk.is_some().into() { let res: Result<()> = ct_pk.unwrap().and_then(|pk| { @@ -176,7 +176,7 @@ mod test { /// ``` #[test] fn test_secp256r1_sign_and_verify() { - let pk: PublicKey = PublicKey::from_hex_string( + let pk: PublicKey<33> = PublicKey::<33>::from_hex_string( "17a6afd392fcbe4ac9270a599a9c5732c4f838ce35ea2234d389d8f0c367f3f5dcab906352e27289002c7f2c96039ddce7c1b5aad8b87ba94984d4c8b4f95702" ).unwrap(); let sk = @@ -194,7 +194,7 @@ mod test { // Check our sign and verify work right let our_sig = sign(sk, &hash(msg.as_bytes())); - assert!(verify(msg.as_bytes(), &pk.address(), our_sig, pk)); + assert!(verify(msg.as_bytes(), &pk.address(), our_sig, &pk)); let hash_msg: [u8; 32] = hex::decode("5e230abb2ae1cb0717986854d6e16b998da03b827b736c9ac32f6ec9e47e3670") @@ -204,6 +204,6 @@ mod test { let hashed = hash(msg.as_bytes()); assert_eq!(hashed, hash_msg, "hash ret not equal"); - assert!(verify(msg.as_bytes(), &pk.address(), sig, pk)); + assert!(verify(msg.as_bytes(), &pk.address(), sig, &pk)); } } diff --git a/crates/core/src/ecc/types.rs b/crates/core/src/ecc/types.rs index 0dc26caf3..00b57de56 100644 --- a/crates/core/src/ecc/types.rs +++ b/crates/core/src/ecc/types.rs @@ -8,11 +8,11 @@ use crate::error::Result; /// PublicKey for ECDSA and EdDSA. #[derive(PartialEq, Eq, Debug, Clone, Copy)] -pub struct PublicKey(pub [u8; 33]); +pub struct PublicKey(pub [u8; SIZE]); -struct PublicKeyVisitor; +struct PublicKeyVisitor; // /// twist from https://docs.rs/libsecp256k1/latest/src/libsecp256k1/lib.rs.html#335-344 -impl Serialize for PublicKey { +impl Serialize for PublicKey { fn serialize(&self, serializer: S) -> std::result::Result where S: serde::ser::Serializer { serializer.serialize_str( @@ -21,23 +21,23 @@ impl Serialize for PublicKey { } } -impl PublicKey { +impl PublicKey<33> { /// trezor style b58 - pub fn try_from_b58t(value: &str) -> Result { + pub fn try_from_b58t(value: &str) -> Result> { let value: Vec = base58::FromBase58::from_base58(value).map_err(|_| Error::PublicKeyBadFormat)?; Self::from_u8(value.as_slice()) } /// monero and bitcoin style b58 - pub fn try_from_b58m(value: &str) -> Result { + pub fn try_from_b58m(value: &str) -> Result> { let value: &[u8] = &base58_monero::decode_check(value).map_err(|_| Error::PublicKeyBadFormat)?; Self::from_u8(value) } /// monero style uncheck base56 - pub fn try_from_b58m_uncheck(value: &str) -> Result { + pub fn try_from_b58m_uncheck(value: &str) -> Result> { let value: &[u8] = &base58_monero::decode(value).map_err(|_| Error::PublicKeyBadFormat)?; Self::from_u8(value) } @@ -47,7 +47,7 @@ impl PublicKey { /// The format is /// For 32 bytes case, we mark the odd flas as 00 (unknown) /// For 64 bytes case, we compress the public key into compressed public key - pub fn from_u8(value: &[u8]) -> Result { + pub fn from_u8(value: &[u8]) -> Result> { let mut s = value.to_vec(); let data: Vec = match s.len() { 32 => { @@ -79,14 +79,14 @@ impl PublicKey { } /// convert public_key from hex string - pub fn from_hex_string(value: &str) -> Result { + pub fn from_hex_string(value: &str) -> Result> { let v = hex::decode(value)?; Self::from_u8(v.as_slice()) } } -impl<'de> serde::de::Visitor<'de> for PublicKeyVisitor { - type Value = PublicKey; +impl<'de> serde::de::Visitor<'de> for PublicKeyVisitor<33> { + type Value = PublicKey<33>; fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { formatter.write_str("a bytestring of in length 33") @@ -97,7 +97,7 @@ impl<'de> serde::de::Visitor<'de> for PublicKeyVisitor { } } -impl<'de> Deserialize<'de> for PublicKey { +impl<'de> Deserialize<'de> for PublicKey<33> { fn deserialize(deserializer: D) -> std::result::Result where D: serde::de::Deserializer<'de> { deserializer.deserialize_str(PublicKeyVisitor) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 0da63eb54..62f78e2f0 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -7,6 +7,15 @@ pub type Result = std::result::Result; #[derive(thiserror::Error, Debug)] #[non_exhaustive] pub enum Error { + #[error("Serialize affine failed")] + EccSerializeFailed, + #[error("desrialize affine failed")] + EccDeserializeFailed, + #[error("Failed to initialize Curve hasher")] + CurveHasherInitFailed, + #[error("Failed to hash data into cruve")] + CurveHasherFailed, + #[error("Ed25519/EdDSA pubkey bad format")] EdDSAPublicKeyBadFormat, @@ -19,6 +28,12 @@ pub enum Error { #[error("ECDSA or EdDSA pubkey bad format")] PublicKeyBadFormat, + #[error("Failed to decode vector to bls affine")] + BlsAffineDecodeFailed, + + #[error("private bad format")] + PrivateKeyBadFormat, + #[error("Invalid Transport")] InvalidTransport, @@ -109,8 +124,8 @@ pub enum Error { #[error("Ice server get url without host")] IceServerURLMissHost, - #[error("SecretKey parse error, {0}")] - Libsecp256k1SecretKeyParse(String), + #[error("Libsecp256k1 error")] + Libsecp256k1Error(#[from] libsecp256k1::Error), #[error("Signature standard parse failed, {0}")] Libsecp256k1SignatureParseStandard(String), diff --git a/crates/core/src/session.rs b/crates/core/src/session.rs index ef5982765..3fdb7c6eb 100644 --- a/crates/core/src/session.rs +++ b/crates/core/src/session.rs @@ -157,13 +157,13 @@ pub enum Account { /// ecdsa Secp256k1(Did), /// ref: - Secp256r1(PublicKey), + Secp256r1(PublicKey<33>), /// ref: EIP191(Did), /// bitcoin bip137 ref: BIP137(Did), /// ed25519 - Ed25519(PublicKey), + Ed25519(PublicKey<33>), } impl TryFrom<(String, String)> for Account { @@ -287,10 +287,10 @@ impl Session { } Account::EIP191(did) => signers::eip191::verify(&auth_bytes, &did.into(), &self.sig), Account::BIP137(did) => signers::bip137::verify(&auth_bytes, &did.into(), &self.sig), - Account::Ed25519(pk) => { + Account::Ed25519(ref pk) => { signers::ed25519::verify(&auth_bytes, &pk.address(), &self.sig, pk) } - Account::Secp256r1(pk) => { + Account::Secp256r1(ref pk) => { signers::secp256r1::verify(&auth_bytes, &pk.address(), &self.sig, pk) } }) { @@ -310,14 +310,14 @@ impl Session { } /// Get public key from session for encryption. - pub fn account_pubkey(&self) -> Result { + pub fn account_pubkey(&self) -> Result> { let auth_bytes = self.pack(); match self.account { Account::Secp256k1(_) => signers::secp256k1::recover(&auth_bytes, &self.sig), Account::BIP137(_) => signers::bip137::recover(&auth_bytes, &self.sig), Account::EIP191(_) => signers::eip191::recover(&auth_bytes, &self.sig), - Account::Ed25519(pk) => Ok(pk), - Account::Secp256r1(pk) => Ok(pk), + Account::Ed25519(ref pk) => Ok(*pk), + Account::Secp256r1(ref pk) => Ok(*pk), } } @@ -327,8 +327,8 @@ impl Session { Account::Secp256k1(did) => did, Account::BIP137(did) => did, Account::EIP191(did) => did, - Account::Ed25519(pk) => pk.address().into(), - Account::Secp256r1(pk) => pk.address().into(), + Account::Ed25519(ref pk) => pk.address().into(), + Account::Secp256r1(ref pk) => pk.address().into(), } } }