From f067364419032ad6f26c7e6ff132609707fa3ed1 Mon Sep 17 00:00:00 2001 From: Friedel Ziegelmayer Date: Tue, 25 Feb 2020 14:50:19 -0800 Subject: [PATCH] feat: implement hasing based on the new rfc --- Cargo.lock | 136 ++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 6 ++- rust-toolchain | 2 +- src/key.rs | 117 ++++++++++++++++++++++++++++++++-------- src/signature.rs | 20 +++++-- 5 files changed, 250 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e2c9eea..0af10cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,17 +29,41 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + [[package]] name = "bls-signatures" version = "0.4.0" dependencies = [ "fff", "groupy", + "hex", + "hkdf", "paired", "rand", "rand_core", "rand_xorshift", "rayon", + "sha2ni", "thiserror", ] @@ -54,6 +78,12 @@ dependencies = [ "rayon", ] +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "byteorder" version = "1.3.4" @@ -150,12 +180,37 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array", +] + [[package]] name = "either" version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + [[package]] name = "fff" version = "0.2.1" @@ -183,6 +238,15 @@ dependencies = [ "syn", ] +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +dependencies = [ + "typenum", +] + [[package]] name = "getrandom" version = "0.1.14" @@ -215,6 +279,32 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" + +[[package]] +name = "hkdf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fa08a006102488bd9cd5b8013aabe84955cf5ae22e304c2caf655b633aefae3" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "hmac" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" +dependencies = [ + "crypto-mac", + "digest", +] + [[package]] name = "itoa" version = "0.4.5" @@ -226,6 +316,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -297,17 +390,25 @@ dependencies = [ "libc", ] +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + [[package]] name = "paired" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a743cac4a3f8019673ffc3990e18b7fdac3b8d8e4f201132f91e1a74fbeb38" +checksum = "dd8e00a5c3f72cfb17fc051c179470610a3888e463b66ba753ea22fee3972d72" dependencies = [ "blake2b_simd", "byteorder", "fff", "groupy", + "hkdf", "rand_core", + "sha2ni", ] [[package]] @@ -490,6 +591,31 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2ni" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac61b2114bd4f97a98e4a2b1f1078605d29ef22abc37e6d997afa0ecc2ad13f" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "lazy_static", + "opaque-debug", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + [[package]] name = "syn" version = "1.0.15" @@ -544,6 +670,12 @@ dependencies = [ "serde", ] +[[package]] +name = "typenum" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" + [[package]] name = "unicode-xid" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 6f8b8c3..d374d18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,10 +18,12 @@ pro-release-commit-message = "chore(release): starting development cycle for {{n [dependencies] ff = { version = "0.2", package = "fff" } groupy = "0.3.0" -paired = "0.17.0" +paired = "0.18.0" rayon = "1" rand_core = "0.5.1" thiserror = "1.0" +sha2ni = "0.8.1" +hkdf = "0.8.0" [workspace] members = [ @@ -34,3 +36,5 @@ default = [] [dev-dependencies] rand_xorshift = "0.2.0" rand = "0.7" +hex = "0.4.2" + diff --git a/rust-toolchain b/rust-toolchain index bb68f08..7fa5b2d 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2020-01-26 +nightly-2020-02-17 diff --git a/src/key.rs b/src/key.rs index 7ec82d6..5548567 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1,31 +1,34 @@ use std::io::{self, Cursor, Read}; use ff::{Field, PrimeField}; -use groupy::{CurveProjective, EncodedPoint, Wnaf}; -use paired::bls12_381::{Fr, FrRepr, G1Affine, G1Compressed, G1}; +use groupy::{CurveAffine, CurveProjective, EncodedPoint}; +use hkdf::Hkdf; +use paired::bls12_381::{Bls12, Fq12, Fr, FrRepr, G1Affine, G1Compressed, G2Affine, G1}; +use paired::{BaseFromRO, Engine, PairingCurveAffine}; use rand_core::RngCore; +use sha2ni::digest::generic_array::typenum::U48; +use sha2ni::digest::generic_array::GenericArray; +use sha2ni::Sha256; use crate::error::Error; use crate::signature::*; -#[derive(Debug, Clone, PartialEq)] -pub struct PublicKey(G1Affine); +// "BLS-SIG-KEYGEN-SALT-" +const SALT: &[u8] = &[ + 66, 76, 83, 45, 83, 73, 71, 45, 75, 69, 89, 71, 69, 78, 45, 83, 65, 76, 84, 45, +]; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct PublicKey(G1); + +#[derive(Debug, Copy, Clone, PartialEq)] pub struct PrivateKey(Fr); impl From for PublicKey { fn from(val: G1) -> Self { - PublicKey(val.into_affine()) - } -} - -impl From for PublicKey { - fn from(val: G1Affine) -> Self { PublicKey(val) } } - impl From for PrivateKey { fn from(val: Fr) -> Self { PrivateKey(val) @@ -59,27 +62,39 @@ pub trait Serialize: ::std::fmt::Debug + Sized { } impl PrivateKey { + /// Generate a deterministic private key from the given bytes. + /// + /// They must be at least 32 bytes long to be secure. + pub fn new>(msg: T) -> Self { + PrivateKey(key_gen(msg)) + } + /// Generate a new private key. pub fn generate(rng: &mut R) -> Self { - let key: Fr = Fr::random(rng); + // IKM must be at least 32 bytes long: + // https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-00#section-2.3 + let mut ikm = [0u8; 32]; + rng.fill_bytes(&mut ikm); - key.into() + Self::new(ikm) } /// Sign the given message. /// Calculated by `signature = hash_into_g2(message) * sk` - pub fn sign(&self, message: &[u8]) -> Signature { - // TODO: cache these - let g = hash(message); + pub fn sign>(&self, message: T) -> Signature { + let mut p = hash(message.as_ref()); + p.mul_assign(self.0); - // compute g * sk - Wnaf::new().scalar(self.into()).base(g).into() + p.into() } /// Get the public key for this private key. /// Calculated by `pk = g1 * sk`. pub fn public_key(&self) -> PublicKey { - Wnaf::new().scalar(self.into()).base(G1::one()).into() + let mut pk = G1::one(); + pk.mul_assign(self.0); + + PublicKey(pk) } } @@ -107,13 +122,33 @@ impl Serialize for PrivateKey { impl PublicKey { pub fn as_affine(&self) -> G1Affine { - self.0 + self.0.into_affine() + } + + pub fn verify>(&self, sig: Signature, message: T) -> bool { + let p = hash(message.as_ref()).into_affine().prepare(); + let g1gen = { + let mut tmp = G1::one(); + tmp.negate(); + tmp.into_affine().prepare() + }; + + let sig_affine: G2Affine = sig.into(); + match Bls12::final_exponentiation(&Bls12::miller_loop(&[ + (&self.0.into_affine().prepare(), &p), + (&g1gen, &sig_affine.prepare()), + ])) { + None => false, + Some(res) => res == Fq12::one(), + } } } impl Serialize for PublicKey { fn write_bytes(&self, dest: &mut impl io::Write) -> io::Result<()> { - dest.write_all(G1Compressed::from_affine(self.0).as_ref())?; + let t = self.0.into_affine(); + let tmp = G1Compressed::from_affine(t); + dest.write_all(tmp.as_ref())?; Ok(()) } @@ -125,11 +160,23 @@ impl Serialize for PublicKey { let mut res = G1Compressed::empty(); res.as_mut().copy_from_slice(raw); + let affine = res.into_affine()?; - Ok(res.into_affine()?.into()) + Ok(PublicKey(affine.into_projective())) } } +/// Hash a secret key sk to the secret exponent x'; then (PK, SK) = (g^{x'}, x'). +fn key_gen>(data: T) -> Fr { + let mut result = GenericArray::::default(); + + // `result` has enough length to hold the output from HKDF expansion + assert!(Hkdf::::new(Some(SALT), data.as_ref()) + .expand(&[], &mut result) + .is_ok()); + Fr::from_okm(&result) +} + #[cfg(test)] mod tests { use super::*; @@ -155,4 +202,28 @@ mod tests { assert_eq!(pk_bytes.len(), 48); assert_eq!(PublicKey::from_bytes(&pk_bytes).unwrap(), pk); } + + #[test] + fn test_key_gen() { + let fr_val = key_gen("hello world (it's a secret!)"); + let expect = FrRepr([ + 0x12760642e26dd0b2u64, + 0x577f0ddcee74cc5fu64, + 0xd6b63edfcad22ccu64, + 0x55b3719e3864a1acu64, + ]); + assert_eq!(fr_val, Fr::from_repr(expect).unwrap()); + } + + #[test] + fn test_sig() { + let msg = "this is the message"; + let sk = "this is the key"; + + let sk = PrivateKey::new(sk); + let sig = sk.sign(msg); + let pk = sk.public_key(); + + assert!(pk.verify(sig, msg)); + } } diff --git a/src/signature.rs b/src/signature.rs index fa58886..77fc67f 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -3,13 +3,19 @@ use std::io; use ff::Field; use groupy::{CurveAffine, CurveProjective, EncodedPoint}; use paired::bls12_381::{Bls12, Fq12, G1Affine, G2Affine, G2Compressed, G2}; -use paired::{Engine, PairingCurveAffine}; +use paired::{Engine, HashToCurve, PairingCurveAffine}; use rayon::prelude::*; use crate::error::Error; use crate::key::*; -#[derive(Debug, Clone, PartialEq)] +// BLS_SIG_BLS12381G2-SHA256-SSWU-RO-_NUL_ +const CSUITE: &'static [u8] = &[ + 66, 76, 83, 95, 83, 73, 71, 95, 66, 76, 83, 49, 50, 51, 56, 49, 71, 50, 45, 83, 72, 65, 50, 53, + 54, 45, 83, 83, 87, 85, 45, 82, 79, 45, 95, 78, 85, 76, 95, +]; + +#[derive(Debug, Copy, Clone, PartialEq)] pub struct Signature(G2Affine); impl From for Signature { @@ -24,6 +30,12 @@ impl From for Signature { } } +impl From for G2Affine { + fn from(val: Signature) -> Self { + val.0 + } +} + impl Serialize for Signature { fn write_bytes(&self, dest: &mut impl io::Write) -> io::Result<()> { dest.write_all(G2Compressed::from_affine(self.0).as_ref())?; @@ -45,7 +57,7 @@ impl Serialize for Signature { /// Hash the given message, as used in the signature. pub fn hash(msg: &[u8]) -> G2 { - G2::hash(msg) + G2::hash_to_curve(msg, CSUITE) } /// Aggregate signatures by multiplying them together. @@ -128,7 +140,7 @@ mod tests { let hashes = messages .iter() - .map(|message| G2::hash(message)) + .map(|message| hash(message)) .collect::>(); let public_keys = private_keys .iter()