diff --git a/Cargo.lock b/Cargo.lock index 23981c8c..af9c97fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,7 +77,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", "once_cell", "version_check", ] @@ -265,7 +265,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ "async-lock", - "autocfg 1.1.0", + "autocfg", "cfg-if", "concurrent-queue", "futures-lite", @@ -346,15 +346,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "autocfg" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" -dependencies = [ - "autocfg 1.1.0", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -1119,15 +1110,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags", -] - [[package]] name = "codespan-reporting" version = "0.11.1" @@ -1399,7 +1381,7 @@ version = "0.9.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" dependencies = [ - "autocfg 1.1.0", + "autocfg", "cfg-if", "crossbeam-utils", "memoffset 0.8.0", @@ -2702,12 +2684,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - [[package]] name = "funty" version = "2.0.0" @@ -2889,9 +2865,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", @@ -3216,14 +3192,12 @@ dependencies = [ [[package]] name = "ic-verify-bls-signature" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f1f8d75f50002970cc2136f909287bf1d59024fbc80ebffbee871afcbd237" dependencies = [ "bls12_381", + "getrandom 0.2.10", "hex", "lazy_static", "pairing", - "rand 0.6.5", "sha2 0.9.9", ] @@ -3320,7 +3294,7 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ - "autocfg 1.1.0", + "autocfg", "hashbrown", "serde", ] @@ -3665,7 +3639,7 @@ dependencies = [ "bytes", "futures", "futures-timer", - "getrandom 0.2.9", + "getrandom 0.2.10", "instant", "lazy_static", "libp2p-core", @@ -4114,7 +4088,7 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ - "autocfg 1.1.0", + "autocfg", "scopeguard", ] @@ -4198,7 +4172,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77" dependencies = [ - "autocfg 1.1.0", + "autocfg", "rawpointer", ] @@ -4232,7 +4206,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ - "autocfg 1.1.0", + "autocfg", ] [[package]] @@ -4241,7 +4215,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" dependencies = [ - "autocfg 1.1.0", + "autocfg", ] [[package]] @@ -4587,7 +4561,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-integer", "num-traits", ] @@ -4634,7 +4608,7 @@ version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-traits", ] @@ -4644,7 +4618,7 @@ version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-integer", "num-traits", ] @@ -4655,7 +4629,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-bigint", "num-integer", "num-traits", @@ -4667,7 +4641,7 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ - "autocfg 1.1.0", + "autocfg", "libm 0.2.7", ] @@ -5946,7 +5920,7 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ - "autocfg 1.1.0", + "autocfg", "bitflags", "cfg-if", "concurrent-queue", @@ -6239,25 +6213,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg 0.1.8", - "libc", - "rand_chacha 0.1.1", - "rand_core 0.4.2", - "rand_hc 0.1.0", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg 0.1.2", - "rand_xorshift", - "winapi", -] - [[package]] name = "rand" version = "0.7.3" @@ -6268,7 +6223,7 @@ dependencies = [ "libc", "rand_chacha 0.2.2", "rand_core 0.5.1", - "rand_hc 0.2.0", + "rand_hc", "rand_pcg 0.2.1", ] @@ -6283,16 +6238,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -dependencies = [ - "autocfg 0.1.8", - "rand_core 0.3.1", -] - [[package]] name = "rand_chacha" version = "0.2.2" @@ -6313,21 +6258,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - [[package]] name = "rand_core" version = "0.5.1" @@ -6343,7 +6273,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", ] [[package]] @@ -6356,15 +6286,6 @@ dependencies = [ "rand 0.8.5", ] -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "rand_hc" version = "0.2.0" @@ -6374,50 +6295,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.4.2", - "rdrand", - "winapi", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -dependencies = [ - "autocfg 0.1.8", - "rand_core 0.4.2", -] - [[package]] name = "rand_pcg" version = "0.2.1" @@ -6436,15 +6313,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "rand_xorshift" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "rawpointer" version = "0.2.1" @@ -6473,15 +6341,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "redox_syscall" version = "0.2.16" @@ -6506,7 +6365,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", "redox_syscall 0.2.16", "thiserror", ] @@ -8165,7 +8024,7 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ - "autocfg 1.1.0", + "autocfg", ] [[package]] @@ -9359,7 +9218,7 @@ version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ - "autocfg 1.1.0", + "autocfg", "bytes", "libc", "mio", diff --git a/primitives/enclave-verify/Cargo.toml b/primitives/enclave-verify/Cargo.toml index 76e2318b..6438b2bd 100644 --- a/primitives/enclave-verify/Cargo.toml +++ b/primitives/enclave-verify/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" # bls-signatures = { version = "0.14.0", default-features = false, features = ["pairing"]} # signature_bls = { version = "0.35.0" , default-features = false} -ic-verify-bls-signature = { version = "0.2.0", default-features = false } +ic-verify-bls-signature = { version = "0.2.0", path = '../../utils/verify-bls-signatures', default-features = false } log = { version = "0.4.14", default-features = false } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } @@ -46,4 +46,4 @@ std = [ "frame-support/std", "cp-cess-common/std", "webpki/std", -] \ No newline at end of file +] diff --git a/primitives/enclave-verify/src/lib.rs b/primitives/enclave-verify/src/lib.rs index bfbc0494..11b9dd5c 100644 --- a/primitives/enclave-verify/src/lib.rs +++ b/primitives/enclave-verify/src/lib.rs @@ -19,7 +19,7 @@ use rsa::{ }; use ic_verify_bls_signature::{ - Signature as BLSSignature, + Signature as BLSSignature, PublicKey as BLSPubilc, }; @@ -229,14 +229,9 @@ pub fn verify_rsa(key: &[u8], msg: &[u8], sig: &[u8]) -> bool { pub fn verify_bls(key: &[u8], msg: &[u8], sig: &[u8]) -> Result<(), ()> { let puk = BLSPubilc::deserialize(key).unwrap(); - log::info!("bls puk: {:?}", puk); - let sig = BLSSignature::deserialize(sig).unwrap(); - - let r = puk.verify(&msg, &sig); - - r + puk.verify(&msg, &sig) } // pub fn sig_rsa(key: &[u8], msg: &[u8]) -> &[u8] { @@ -257,4 +252,4 @@ fn cryptos_rsa() { let sig = binding.as_ref(); let result = verify_rsa(&doc.as_bytes(), &msg, &sig); println!("result: {:?}", result); -} \ No newline at end of file +} diff --git a/utils/ring/build.rs b/utils/ring/build.rs index 93a67745..70575df4 100755 --- a/utils/ring/build.rs +++ b/utils/ring/build.rs @@ -364,7 +364,7 @@ fn maybe_set_toolchain(target: &Target) { std::env::set_var("CC_wasm32-unknown-unknown", &clang); std::env::set_var("AR_wasm32-unknown-unknown", &ar); } else if target.arch == "wasm32" { - panic!("One of the compatible llvm toolchain must exist: llvm-{11,10,9}"); + panic!("{}", "One of the compatible llvm toolchain must exist: llvm-{11,10,9}"); } } diff --git a/utils/verify-bls-signatures/.gitignore b/utils/verify-bls-signatures/.gitignore new file mode 100644 index 00000000..96ef6c0b --- /dev/null +++ b/utils/verify-bls-signatures/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/utils/verify-bls-signatures/Cargo.toml b/utils/verify-bls-signatures/Cargo.toml new file mode 100644 index 00000000..0c666bb1 --- /dev/null +++ b/utils/verify-bls-signatures/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "ic-verify-bls-signature" +version = "0.2.0" +edition = "2021" +license = "Apache-2.0" +description = "A library for handling BLS signatures" + +[dependencies] +bls12_381 = { version = "0.7", default-features = false, features = ["groups", "pairings", "alloc", "experimental"] } +pairing = { version = "0.22", default-features = false } +lazy_static = { version = "1", default-features = false } +# rand = { version = "0.8.5", default-features = false, features = ["getrandom"] } + +# Check references: +# - https://docs.rs/getrandom/latest/getrandom/index.html +# - https://rust-random.github.io/book/crates.html +# - https://github.com/rust-random/getrandom/pull/109 // This one explains why "custom" feature +# is added, and we will need to call `register_custom_getrandom` in future. +# - https://docs.rs/getrandom/latest/getrandom/index.html#custom-implementations +getrandom = { version = "0.2.10", default-features = false, features = ["custom"] } +sha2 = { version = "0.9", default-features = false } +hex = { version = "0.4", default-features = false } diff --git a/utils/verify-bls-signatures/README.md b/utils/verify-bls-signatures/README.md new file mode 100644 index 00000000..03dccf83 --- /dev/null +++ b/utils/verify-bls-signatures/README.md @@ -0,0 +1,11 @@ +BLS signature utility crate +============================= + +This is a simple Rust crate which can be used to create and verify BLS signatures +over the BLS12-381 curve. This follows the +[IETF draft for BLS signatures](https://datatracker.ietf.org/doc/draft-irtf-cfrg-bls-signature/), +using the "short signature" variation, where signatures are in G1 and +public keys are in G2. + +For historical reasons, this crate is named `ic-verify-bls-signature`, +but it also supports signature generation. diff --git a/utils/verify-bls-signatures/src/lib.rs b/utils/verify-bls-signatures/src/lib.rs new file mode 100644 index 00000000..21e765ed --- /dev/null +++ b/utils/verify-bls-signatures/src/lib.rs @@ -0,0 +1,247 @@ +#![no_std] +#![forbid(unsafe_code)] +#![forbid(missing_docs)] +#![allow(clippy::result_unit_err)] + +//! Verify BLS signatures +//! +//! This verifies BLS signatures in a manner which is compatible with +//! the Internet Computer. + +use core::fmt; +use core::ops::Neg; + +use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve}; +use bls12_381::{multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, Scalar}; +use pairing::group::{Curve, Group}; +// use rand::RngCore; + +lazy_static::lazy_static! { + static ref G2PREPARED_NEG_G : G2Prepared = G2Affine::generator().neg().into(); +} + +const BLS_SIGNATURE_DOMAIN_SEP: [u8; 43] = *b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_"; + +fn hash_to_g1(msg: &[u8]) -> G1Affine { + >>::hash_to_curve( + msg, + &BLS_SIGNATURE_DOMAIN_SEP, + ) + .to_affine() +} + +/// A BLS12-381 public key usable for signature verification +#[derive(Clone, Eq, PartialEq)] +pub struct PublicKey { + pk: G2Affine, +} + +impl fmt::Debug for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "PublicKey({})", hex::encode(self.serialize())) + } +} + +/// An error indicating an encoded public key was not valid +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum InvalidPublicKey { + /// The public key encoding was not the correct length + WrongLength, + /// The public key was not a valid BLS12-381 G2 point + InvalidPoint, +} + +impl PublicKey { + /// The length of the binary encoding of this type + pub const BYTES: usize = 96; + + fn new(pk: G2Affine) -> Self { + Self { pk } + } + + /// Return the serialization of this BLS12-381 public key + pub fn serialize(&self) -> [u8; Self::BYTES] { + self.pk.to_compressed() + } + + /// Deserialize a BLS12-381 public key + pub fn deserialize(bytes: &[u8]) -> Result { + let bytes: Result<[u8; Self::BYTES], _> = bytes.try_into(); + + match bytes { + Err(_) => Err(InvalidPublicKey::WrongLength), + Ok(b) => { + let pk = G2Affine::from_compressed(&b); + if bool::from(pk.is_some()) { + Ok(Self::new(pk.unwrap())) + } else { + Err(InvalidPublicKey::InvalidPoint) + } + } + } + } + + /// Verify a BLS signature + pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), ()> { + let msg = hash_to_g1(message); + let g2_gen: &G2Prepared = &G2PREPARED_NEG_G; + let pk = G2Prepared::from(self.pk); + + let sig_g2 = (&signature.sig, g2_gen); + let msg_pk = (&msg, &pk); + + let x = multi_miller_loop(&[sig_g2, msg_pk]).final_exponentiation(); + + if bool::from(x.is_identity()) { + Ok(()) + } else { + Err(()) + } + } +} + +/// An error indicating a signature could not be deserialized +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum InvalidSignature { + /// The signature encoding is the wrong length + WrongLength, + /// The signature encoding is not a valid BLS12-381 G1 point + InvalidPoint, +} + +/// A type expressing a BLS12-381 signature +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct Signature { + sig: G1Affine, +} + +impl fmt::Debug for Signature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Signature({})", hex::encode(self.serialize())) + } +} + +impl Signature { + /// The length of the binary encoding of this type + pub const BYTES: usize = 48; + + fn new(sig: G1Affine) -> Self { + Self { sig } + } + + /// Serialize the BLS signature + pub fn serialize(&self) -> [u8; Self::BYTES] { + self.sig.to_compressed() + } + + /// Deserialize a BLS signature + pub fn deserialize(bytes: &[u8]) -> Result { + let bytes: Result<[u8; Self::BYTES], _> = bytes.try_into(); + + match bytes { + Err(_) => Err(InvalidSignature::WrongLength), + Ok(b) => { + let sig = G1Affine::from_compressed(&b); + if bool::from(sig.is_some()) { + Ok(Self::new(sig.unwrap())) + } else { + Err(InvalidSignature::InvalidPoint) + } + } + } + } +} + +/// An error indicating a private key could not be deserialized +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum InvalidPrivateKey { + /// The secret key encoding was the wrong length and not possibly valid + WrongLength, + /// The secret key was outside the valid range of BLS12-381 keys + OutOfRange, +} + +/// A type expressing a BLS12-381 private key +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct PrivateKey { + sk: Scalar, +} + +impl fmt::Debug for PrivateKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "PrivateKey(REDACTED)") + } +} + +impl PrivateKey { + /// The length of the serialized encoding of this type + pub const BYTES: usize = 32; + + fn new(sk: Scalar) -> Self { + Self { sk } + } + + /// Create a new random secret key + pub fn random() -> Self { + // let mut rng = rand::thread_rng(); + loop { + let mut buf = [0u8; 32]; + // rng.fill_bytes(&mut buf); + // note: Handle the `Result` below + getrandom::getrandom(&mut buf); + let s: Option = Scalar::from_bytes(&buf).into(); + + if let Some(s) = s { + return Self::new(s); + } + } + } + + /// Serialize a private key to the binary big-ending encoding + pub fn serialize(&self) -> [u8; Self::BYTES] { + let mut bytes = self.sk.to_bytes(); + bytes.reverse(); + bytes + } + + /// Deserialize a secret key + pub fn deserialize(bytes: &[u8]) -> Result { + let bytes: Result<[u8; Self::BYTES], _> = bytes.try_into(); + + match bytes { + Err(_) => Err(InvalidPrivateKey::WrongLength), + Ok(mut b) => { + b.reverse(); + let sk = Scalar::from_bytes(&b); + if bool::from(sk.is_some()) { + Ok(Self::new(sk.unwrap())) + } else { + Err(InvalidPrivateKey::OutOfRange) + } + } + } + } + + /// Return the public key associated with this secret key + pub fn public_key(&self) -> PublicKey { + PublicKey::new(G2Affine::from(G2Affine::generator() * self.sk)) + } + + /// Sign a message using this private key + /// + /// The message can be of arbitrary length + pub fn sign(&self, message: &[u8]) -> Signature { + let sig = hash_to_g1(message) * self.sk; + Signature::new(sig.to_affine()) + } +} + +/// Verify a BLS signature +/// +/// The signature must be exactly 48 bytes (compressed G1 element) +/// The key must be exactly 96 bytes (compressed G2 element) +pub fn verify_bls_signature(sig: &[u8], msg: &[u8], key: &[u8]) -> Result<(), ()> { + let sig = Signature::deserialize(sig).map_err(|_| ())?; + let pk = PublicKey::deserialize(key).map_err(|_| ())?; + pk.verify(msg, &sig) +} diff --git a/utils/verify-bls-signatures/tests/tests.rs b/utils/verify-bls-signatures/tests/tests.rs new file mode 100644 index 00000000..51cdb2b9 --- /dev/null +++ b/utils/verify-bls-signatures/tests/tests.rs @@ -0,0 +1,112 @@ +use ic_verify_bls_signature::*; +use rand::Rng; + +fn test_bls_signature( + expected_result: bool, + sig: &'static str, + msg: &'static str, + key: &'static str, +) { + let sig = hex::decode(sig).expect("Invalid hex"); + let msg = hex::decode(msg).expect("Invalid hex"); + let key = hex::decode(key).expect("Invalid hex"); + + let result = verify_bls_signature(&sig, &msg, &key); + + assert_eq!(expected_result, result.is_ok()); +} + +#[test] +fn verify_valid() { + // derived from agent-rs tests + test_bls_signature( + true, + "ace9fcdd9bc977e05d6328f889dc4e7c99114c737a494653cb27a1f55c06f4555e0f160980af5ead098acc195010b2f7", + "0d69632d73746174652d726f6f74e6c01e909b4923345ce5970962bcfe3004bfd8474a21dae28f50692502f46d90", + "814c0e6ec71fab583b08bd81373c255c3c371b2e84863c98a4f1e08b74235d14fb5d9c0cd546d9685f913a0c0b2cc5341583bf4b4392e467db96d65b9bb4cb717112f8472e0d5a4d14505ffd7484b01291091c5f87b98883463f98091a0baaae"); + + test_bls_signature( + true, + "89a2be21b5fa8ac9fab1527e041327ce899d7da971436a1f2165393947b4d942365bfe5488710e61a619ba48388a21b1", + "0d69632d73746174652d726f6f74b294b418b11ebe5dd7dd1dcb099e4e0372b9a42aef7a7a37fb4f25667d705ea9", + "9933e1f89e8a3c4d7fdcccdbd518089e2bd4d8180a261f18d9c247a52768ebce98dc7328a39814a8f911086a1dd50cbe015e2a53b7bf78b55288893daa15c346640e8831d72a12bdedd979d28470c34823b8d1c3f4795d9c3984a247132e94fe"); +} + +#[test] +fn reject_invalid() { + // derived from agent-rs tests + test_bls_signature( + false, + "89a2be21b5fa8ac9fab1527e041327ce899d7da971436a1f2165393947b4d942365bfe5488710e61a619ba48388a21b1", + "0d69632d73746174652d726f6f74e6c01e909b4923345ce5970962bcfe3004bfd8474a21dae28f50692502f46d90", + "814c0e6ec71fab583b08bd81373c255c3c371b2e84863c98a4f1e08b74235d14fb5d9c0cd546d9685f913a0c0b2cc5341583bf4b4392e467db96d65b9bb4cb717112f8472e0d5a4d14505ffd7484b01291091c5f87b98883463f98091a0baaae"); + + test_bls_signature( + false, + "ace9fcdd9bc977e05d6328f889dc4e7c99114c737a494653cb27a1f55c06f4555e0f160980af5ead098acc195010b2f7", + "0d69632d73746174652d726f6f74b294b418b11ebe5dd7dd1dcb099e4e0372b9a42aef7a7a37fb4f25667d705ea9", + "9933e1f89e8a3c4d7fdcccdbd518089e2bd4d8180a261f18d9c247a52768ebce98dc7328a39814a8f911086a1dd50cbe015e2a53b7bf78b55288893daa15c346640e8831d72a12bdedd979d28470c34823b8d1c3f4795d9c3984a247132e94fe"); +} + +#[test] +fn reject_invalid_sig() { + // sig is not a valid point + test_bls_signature( + false, + "ace9fcdd9bc977e05d6328f889dc4e7c99114c737a494653cb27a1f55c06f4555e0f160980af5ead098acc195010b2f8", + "0d69632d73746174652d726f6f74e6c01e909b4923345ce5970962bcfe3004bfd8474a21dae28f50692502f46d90", + "814c0e6ec71fab583b08bd81373c255c3c371b2e84863c98a4f1e08b74235d14fb5d9c0cd546d9685f913a0c0b2cc5341583bf4b4392e467db96d65b9bb4cb717112f8472e0d5a4d14505ffd7484b01291091c5f87b98883463f98091a0baaae"); +} + +#[test] +fn reject_invalid_key() { + // key is not a valid point + test_bls_signature( + false, + "ace9fcdd9bc977e05d6328f889dc4e7c99114c737a494653cb27a1f55c06f4555e0f160980af5ead098acc195010b2f7", + "0d69632d73746174652d726f6f74e6c01e909b4923345ce5970962bcfe3004bfd8474a21dae28f50692502f46d90", + "814c0e6ec71fab583b08bd81373c255c3c371b2e84863c98a4f1e08b74235d14fb5d9c0cd546d9685f913a0c0b2cc5341583bf4b4392e467db96d65b9bb4cb717112f8472e0d5a4d14505ffd7484b01291091c5f87b98883463f98091a0baaad"); +} + +#[test] +fn accepts_generated_signatures() { + let mut rng = rand::thread_rng(); + + for _trial in 0..30 { + let sk = PrivateKey::random(); + let pk = sk.public_key(); + let msg = rng.gen::<[u8; 24]>(); + let sig = sk.sign(&msg); + assert!(verify_bls_signature(&sig.serialize(), &msg, &pk.serialize()).is_ok()); + + assert_eq!(sig, Signature::deserialize(&sig.serialize()).unwrap()); + assert_eq!(sk, PrivateKey::deserialize(&sk.serialize()).unwrap()); + assert_eq!(pk, PublicKey::deserialize(&pk.serialize()).unwrap()); + } +} + +#[test] +fn accepts_known_good_signature() { + // Generated using the threshold signature implementation in IC repo + + let public_key = hex::decode("87033f48fd8f327ff5d164e85af31433c6a8c73fc5a65bad5d472127205c73c5168a45e862f5af6d0da5676df45d0a5f1293a530d5498f812a34a280f6bef869e4ca9b7c275554456d8770733d72ac4006777382fa541873fe002adb12184268").unwrap(); + let message = hex::decode("e751fdb69185002b13c8d2954c7d0c39546402ecdde9c2a9a2c624293535a5ca2f560a582f705580448fbe1ccdc0e86af3ba4c487a7f73bc9c312556").unwrap(); + let signature = hex::decode("98733cc2b312d5787cd4dba6ea0e19a1f1850b9e8c6d5112f12e12db8e7413a4ecb4096c23730566c67d9b2694e4e179").unwrap(); + + assert!(verify_bls_signature(&signature, &message, &public_key).is_ok()); +} + +#[test] +fn generates_expected_signature() { + // Generated using the threshold signature implementation in IC repo + + let secret_key = + hex::decode("6f3977f6051e184b2c412daa1b5c0115ef7ab347cac8d808ffa2c26bd0658243").unwrap(); + let message = hex::decode("50484522ad8aede64ec7f86b9273b7ed3940481acf93cdd40a2b77f2be2734a14012b2492b6363b12adaeaf055c573e4611b085d2e0fe2153d72453a95eaebf350ac3ba6a26ba0bc79f4c0bf5664dfdf5865f69f7fc6b58ba7d068e8").unwrap(); + let expected_signature = "8f7ad830632657f7b3eae17fd4c3d9ff5c13365eea8d33fd0a1a6d8fbebc5152e066bb0ad61ab64e8a8541c8e3f96de9"; + + let sk = PrivateKey::deserialize(&secret_key).unwrap(); + let generated_sig = sk.sign(&message); + + assert_eq!(hex::encode(generated_sig.serialize()), expected_signature); +}