diff --git a/CHANGELOG.md b/CHANGELOG.md index 716d6901e..fc413312f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,10 @@ and follow [semantic versioning](https://semver.org/) for our releases. - [#144](https://github.com/EspressoSystems/jellyfish/pull/144) (`jf-primitives`) Updated append-only merkle tree gadget with the latest MT API - [#119](https://github.com/EspressoSystems/jellyfish/pull/119) (all) Updated dependencies - Upgraded `criterion` from `0.3.1` to `0.4.0` - +- [#148](https://github.com/EspressoSystems/jellyfish/pull/148), [#156](https://github.com/EspressoSystems/jellyfish/pull/156) (`jf-primitives`) Refactored BLS Signature implementation + - #148 Added trait bounds on associated types of `trait SignatureScheme` + - #156 Improved BLS correctness and API compliance with IRTF standard with better doc + ### Fixed - [#76](https://github.com/EspressoSystems/jellyfish/pull/76) (`jf-plonk`) Splitting polynomials are masked to ensure zero-knowledge of Plonk diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 27677aae5..6c606d56b 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -53,6 +53,7 @@ bincode = "1.0" criterion = "0.4.0" hashbrown = "0.13.1" quickcheck = "1.0.0" +rand_core = { version = "^0.6.0", features = ["getrandom"] } [[bench]] name = "merkle_path" diff --git a/primitives/src/constants.rs b/primitives/src/constants.rs index 0e2f98d20..795d4b2ec 100644 --- a/primitives/src/constants.rs +++ b/primitives/src/constants.rs @@ -9,12 +9,17 @@ /// ciphersuite identifier for schnorr signature pub const CS_ID_SCHNORR: &str = "SCHNORR_WITH_RESCUE_HASH_v01"; -/// ciphersuite identifier for BLS signature -pub const CS_ID_BLS_SIG_NAIVE: &str = "BLS_SIG_WITH_NAIVE_HtG_v01"; +/// ciphersuite identifier for BLS signature, see: +/// +pub const CS_ID_BLS_MIN_SIG: &str = "BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_"; /// Size in bytes of a secret key in our BLS signature scheme. -pub const BLS_SIG_KEY_SIZE: usize = 32; +pub const BLS_SIG_SK_SIZE: usize = 32; /// Size in bytes of a signature in our BLS signature scheme. pub const BLS_SIG_SIGNATURE_SIZE: usize = 96; +/// Size in bytes of a compressed signature in our BLS signature scheme. +pub const BLS_SIG_COMPRESSED_SIGNATURE_SIZE: usize = 48; /// Size in bytes of a verification key in our BLS signature scheme. -pub const BLS_SIG_VERKEY_SIZE: usize = 192; +pub const BLS_SIG_PK_SIZE: usize = 192; +/// Size in bytes of a compressed verification key in our BLS signature scheme. +pub const BLS_SIG_COMPRESSED_PK_SIZE: usize = 96; diff --git a/primitives/src/errors.rs b/primitives/src/errors.rs index 82e1abca2..ec2da68a8 100644 --- a/primitives/src/errors.rs +++ b/primitives/src/errors.rs @@ -8,7 +8,11 @@ use crate::rescue::errors::RescueError; use ark_serialize::SerializationError; -use ark_std::string::String; +use ark_std::{ + format, + string::{String, ToString}, +}; +use blst::BLST_ERROR; use displaydoc::Display; /// A `enum` specifying the possible failure modes of the primitives. @@ -43,5 +47,16 @@ impl From for PrimitivesError { } } -#[cfg(feature = "std")] -impl std::error::Error for PrimitivesError {} +impl From for PrimitivesError { + fn from(e: BLST_ERROR) -> Self { + match e { + BLST_ERROR::BLST_SUCCESS => { + Self::InternalError("Expecting an error, but got a sucess.".to_string()) + }, + BLST_ERROR::BLST_VERIFY_FAIL => Self::VerificationError(format!("{:?}", e)), + _ => Self::ParameterError(format!("{:?}", e)), + } + } +} + +impl ark_std::error::Error for PrimitivesError {} diff --git a/primitives/src/signatures/bls.rs b/primitives/src/signatures/bls.rs index a644f15b9..1d8e0608c 100644 --- a/primitives/src/signatures/bls.rs +++ b/primitives/src/signatures/bls.rs @@ -1,61 +1,128 @@ +// Copyright (c) 2022 Espresso Systems (espressosys.com) +// This file is part of the Jellyfish library. + +// You should have received a copy of the MIT License +// along with the Jellyfish library. If not, see . + //! BLS Signature Scheme +//! +//! Conforming to [IRTF draft][irtf], wrapping [`blst` crate][blst] under the +//! hood. +//! +//! [irtf]: https://datatracker.ietf.org/doc/pdf/draft-irtf-cfrg-bls-signature-05 +//! [blst]: https://github.com/supranational/blst +//! +//! # Examples +//! +//! ``` +//! use rand_core::{RngCore, OsRng}; +//! use jf_primitives::signatures::{SignatureScheme, bls::BLSSignatureScheme}; +//! +//! let pp = BLSSignatureScheme::param_gen::(None)?; +//! +//! // make sure the PRNG passed has good and trusted entropy. +//! // you could use `OsRng` from `rand_core` or `getrandom` crate, +//! // or a `SeedableRng` like `ChaChaRng` with seed generated from good randomness source. +//! let (sk, pk) = BLSSignatureScheme::key_gen(&pp, &mut OsRng)?; +//! +//! let msg = "The quick brown fox jumps over the lazy dog"; +//! let sig = BLSSignatureScheme::sign(&pp, &sk, &msg, &mut OsRng)?; +//! assert!(BLSSignatureScheme::verify(&pp, &pk, &msg, &sig).is_ok()); +//! +//! # Ok::<(), Box>(()) +//! ``` +//! +//! ## Generating independent keys from the same IKM +//! +//! In case you want to keep the IKM for multiple key pairs, and potentially +//! reconstruct them later on from IKM. +//! +//! ``` +//! use rand_core::{RngCore, OsRng}; +//! use sha2::{Sha256, Digest}; +//! use jf_primitives::signatures::{SignatureScheme, bls::BLSSignatureScheme}; +//! +//! let pp = BLSSignatureScheme::param_gen::(None)?; +//! +//! // NOTE: in practice, please use [`zeroize`][zeroize] to wipe sensitive +//! // key materials out of memory. +//! let mut ikm = [0u8; 32]; // should be at least 32 bytes +//! OsRng.fill_bytes(&mut ikm); +//! +//! let mut hasher = Sha256::new(); +//! hasher.update(b"MY-BLS-SIG-KEYGEN-SALT-DOM-SEP"); +//! let salt = hasher.finalize(); +//! +//! let (sk1, pk1) = BLSSignatureScheme::key_gen_v5(&ikm, &salt, b"banking".as_ref())?; +//! let (sk2, pk2) = BLSSignatureScheme::key_gen_v5(&ikm, &salt, b"legal".as_ref())?; +//! +//! let msg = "I authorize transfering 10 dollars to Alice"; +//! let sig = BLSSignatureScheme::sign(&pp, &sk1, &msg, &mut OsRng)?; +//! assert!(BLSSignatureScheme::verify(&pp, &pk1, &msg, &sig).is_ok()); +//! +//! let msg = "I agree to the Terms and Conditions."; +//! let sig = BLSSignatureScheme::sign(&pp, &sk2, &msg, &mut OsRng)?; +//! assert!(BLSSignatureScheme::verify(&pp, &pk2, &msg, &sig).is_ok()); +//! +//! # Ok::<(), Box>(()) +//! ``` +//! +//! [zeroize]: https://github.com/RustCrypto/utils/tree/master/zeroize use super::SignatureScheme; use crate::{ constants::{ - BLS_SIG_KEY_SIZE, BLS_SIG_SIGNATURE_SIZE, BLS_SIG_VERKEY_SIZE, CS_ID_BLS_SIG_NAIVE, + BLS_SIG_COMPRESSED_PK_SIZE, BLS_SIG_COMPRESSED_SIGNATURE_SIZE, BLS_SIG_PK_SIZE, + BLS_SIG_SIGNATURE_SIZE, BLS_SIG_SK_SIZE, CS_ID_BLS_MIN_SIG, }, errors::PrimitivesError, }; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError}; +use ark_serialize::*; use ark_std::{ convert::TryInto, format, + ops::{Deref, DerefMut}, rand::{CryptoRng, RngCore}, }; - -use blst::BLST_ERROR; +use blst::{min_sig::*, BLST_ERROR}; use espresso_systems_common::jellyfish::tag; use tagged_base64::tagged; +use zeroize::{Zeroize, Zeroizing}; -pub use blst::min_sig::{PublicKey, SecretKey, Signature}; -use zeroize::Zeroize; - -/// Newtype wrapper for a BLS Signing Key. #[tagged(tag::BLS_SIGNING_KEY)] #[derive(Clone, Debug, Zeroize)] +#[zeroize(drop)] +/// A BLS Secret Key (Signing Key). pub struct BLSSignKey(SecretKey); -impl core::ops::Deref for BLSSignKey { +impl Deref for BLSSignKey { type Target = SecretKey; - fn deref(&self) -> &Self::Target { &self.0 } } impl CanonicalSerialize for BLSSignKey { - fn serialized_size(&self) -> usize { - BLS_SIG_KEY_SIZE + fn serialize(&self, writer: W) -> Result<(), SerializationError> { + CanonicalSerialize::serialize(&self.to_bytes()[..], writer) } - fn serialize(&self, writer: W) -> Result<(), SerializationError> { - let bytes = &self.0.serialize(); - CanonicalSerialize::serialize(bytes.as_ref(), writer) + fn serialized_size(&self) -> usize { + BLS_SIG_SK_SIZE } } impl CanonicalDeserialize for BLSSignKey { - fn deserialize(mut reader: R) -> Result { + fn deserialize(mut reader: R) -> Result { let len = ::deserialize(&mut reader)?; - if len != BLS_SIG_KEY_SIZE { + if len != BLS_SIG_SK_SIZE { return Err(SerializationError::InvalidData); } - let mut key = [0u8; BLS_SIG_KEY_SIZE]; - reader.read_exact(&mut key)?; - SecretKey::deserialize(&key) + let mut sk_bytes = [0u8; BLS_SIG_SK_SIZE]; + reader.read_exact(&mut sk_bytes)?; + SecretKey::deserialize(&sk_bytes) .map(Self) .map_err(|_| SerializationError::InvalidData) } @@ -69,89 +136,155 @@ impl PartialEq for BLSSignKey { impl Eq for BLSSignKey {} -/// Newtype wrapper for a BLS Signature. -#[tagged(tag::BLS_SIG)] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct BLSSignature(Signature); - -impl core::ops::Deref for BLSSignature { - type Target = Signature; +#[tagged(tag::BLS_VER_KEY)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Copy)] +/// A BLS Public Key (Verification Key). +pub struct BLSVerKey(PublicKey); +impl Deref for BLSVerKey { + type Target = PublicKey; fn deref(&self) -> &Self::Target { &self.0 } } -impl CanonicalSerialize for BLSSignature { +impl CanonicalSerialize for BLSVerKey { + fn serialize(&self, writer: W) -> Result<(), SerializationError> { + CanonicalSerialize::serialize(&self.compress()[..], writer) + } + fn serialized_size(&self) -> usize { - BLS_SIG_SIGNATURE_SIZE + BLS_SIG_COMPRESSED_PK_SIZE } - fn serialize(&self, writer: W) -> Result<(), SerializationError> { - let bytes = &self.0.serialize(); - CanonicalSerialize::serialize(bytes.as_ref(), writer) + fn serialize_uncompressed(&self, writer: W) -> Result<(), SerializationError> { + CanonicalSerialize::serialize(&PublicKey::serialize(self)[..], writer) + } + + fn uncompressed_size(&self) -> usize { + BLS_SIG_PK_SIZE } } -impl CanonicalDeserialize for BLSSignature { - fn deserialize(mut reader: R) -> Result { +// TODO: (alex) update these with combinations of compressed and checked +// when upgrading to use arkwork 0.4.0 +impl CanonicalDeserialize for BLSVerKey { + // compressed + validity checked + fn deserialize(mut reader: R) -> Result { let len = ::deserialize(&mut reader)?; - if len != BLS_SIG_SIGNATURE_SIZE { + if len != BLS_SIG_COMPRESSED_PK_SIZE { + return Err(SerializationError::InvalidData); + } + + let mut pk_bytes = [0u8; BLS_SIG_COMPRESSED_PK_SIZE]; + reader.read_exact(&mut pk_bytes)?; + + let pk = PublicKey::uncompress(&pk_bytes).map_err(|_| SerializationError::InvalidData)?; + PublicKey::validate(&pk).map_err(|_| SerializationError::InvalidData)?; + + Ok(Self(pk)) + } + + // uncompressed + validity checked + fn deserialize_uncompressed(reader: R) -> Result { + let pk: Self = CanonicalDeserialize::deserialize_unchecked(reader)?; + PublicKey::validate(&pk).map_err(|_| SerializationError::InvalidData)?; + + Ok(pk) + } + + // uncompressed + validity unchekced + fn deserialize_unchecked(mut reader: R) -> Result { + let len = ::deserialize(&mut reader)?; + if len != BLS_SIG_PK_SIZE { return Err(SerializationError::InvalidData); } - let mut sig = [0u8; BLS_SIG_SIGNATURE_SIZE]; - reader.read_exact(&mut sig)?; - Signature::deserialize(&sig) + let mut pk_bytes = [0u8; BLS_SIG_PK_SIZE]; + reader.read_exact(&mut pk_bytes)?; + PublicKey::deserialize(&pk_bytes) .map(Self) .map_err(|_| SerializationError::InvalidData) } } -/// Newtype wrapper for a BLS Verification Key. -#[tagged(tag::BLS_VER_KEY)] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct BLSVerKey(PublicKey); - -impl core::ops::Deref for BLSVerKey { - type Target = PublicKey; +/// A BLS Signature. +#[derive(Clone, Debug, PartialEq, Eq, Copy)] +#[tagged(tag::BLS_SIG)] +pub struct BLSSignature(Signature); +impl Deref for BLSSignature { + type Target = Signature; fn deref(&self) -> &Self::Target { &self.0 } } -impl CanonicalSerialize for BLSVerKey { +impl CanonicalSerialize for BLSSignature { + fn serialize(&self, writer: W) -> Result<(), SerializationError> { + CanonicalSerialize::serialize(&self.compress()[..], writer) + } + fn serialized_size(&self) -> usize { - BLS_SIG_VERKEY_SIZE + BLS_SIG_COMPRESSED_SIGNATURE_SIZE } - fn serialize(&self, writer: W) -> Result<(), SerializationError> { - let bytes = &self.0.serialize(); - CanonicalSerialize::serialize(bytes.as_ref(), writer) + fn serialize_uncompressed(&self, writer: W) -> Result<(), SerializationError> { + CanonicalSerialize::serialize(&Signature::serialize(self)[..], writer) + } + + fn uncompressed_size(&self) -> usize { + BLS_SIG_SIGNATURE_SIZE } } -impl CanonicalDeserialize for BLSVerKey { - fn deserialize(mut reader: R) -> Result { +// TODO: (alex) update these with combinations of compressed and checked +// when upgrading to use arkwork 0.4.0 +impl CanonicalDeserialize for BLSSignature { + // compressed + validity checked + fn deserialize(mut reader: R) -> Result { + let len = ::deserialize(&mut reader)?; + if len != BLS_SIG_COMPRESSED_SIGNATURE_SIZE { + return Err(SerializationError::InvalidData); + } + + let mut sig_bytes = [0u8; BLS_SIG_COMPRESSED_SIGNATURE_SIZE]; + reader.read_exact(&mut sig_bytes)?; + + let sig = Signature::uncompress(&sig_bytes).map_err(|_| SerializationError::InvalidData)?; + Signature::validate(&sig, true).map_err(|_| SerializationError::InvalidData)?; + + Ok(Self(sig)) + } + + // uncompressed + validity checked + fn deserialize_uncompressed(reader: R) -> Result { + let sig: Self = CanonicalDeserialize::deserialize_unchecked(reader)?; + Signature::validate(&sig, true).map_err(|_| SerializationError::InvalidData)?; + Ok(sig) + } + + // uncompressed + validity unchekced + fn deserialize_unchecked(mut reader: R) -> Result { let len = ::deserialize(&mut reader)?; - if len != BLS_SIG_VERKEY_SIZE { + if len != BLS_SIG_SIGNATURE_SIZE { return Err(SerializationError::InvalidData); } - let mut key = [0u8; BLS_SIG_VERKEY_SIZE]; - reader.read_exact(&mut key)?; - PublicKey::deserialize(&key) + let mut sig_bytes = [0u8; BLS_SIG_SIGNATURE_SIZE]; + reader.read_exact(&mut sig_bytes)?; + Signature::deserialize(&sig_bytes) .map(Self) .map_err(|_| SerializationError::InvalidData) } } -/// BLS signature scheme. Imports blst library. +/// BLS signature scheme. Wrapping around structs from the `blst` crate. +/// See [module-level documentation](self) for example usage. pub struct BLSSignatureScheme; impl SignatureScheme for BLSSignatureScheme { - const CS_ID: &'static str = CS_ID_BLS_SIG_NAIVE; + const CS_ID: &'static str = CS_ID_BLS_MIN_SIG; /// Public parameter type PublicParameter = (); @@ -176,17 +309,16 @@ impl SignatureScheme for BLSSignatureScheme { Ok(()) } - /// Sample a pair of keys. + /// Generate a BLS key pair. + /// Make sure the `prng` passed in are properly seeded with trusted entropy. fn key_gen( _pp: &Self::PublicParameter, prng: &mut R, ) -> Result<(Self::SigningKey, Self::VerificationKey), PrimitivesError> { - let mut ikm = [0u8; 32]; - prng.fill_bytes(&mut ikm); - let sk = match SecretKey::key_gen(&ikm, &[]) { - Ok(sk) => sk, - Err(e) => return Err(PrimitivesError::InternalError(format!("{:?}", e))), - }; + let mut ikm = Zeroizing::new([0u8; 32]); + prng.fill_bytes(ikm.deref_mut()); + + let sk = SecretKey::key_gen(ikm.deref(), &[])?; let vk = sk.sk_to_pk(); Ok((BLSSignKey(sk), BLSVerKey(vk))) } @@ -219,12 +351,43 @@ impl SignatureScheme for BLSSignatureScheme { } } +impl BLSSignatureScheme { + /// Alternative deterministic key_gen compatible with [IRTF draft v5][v5]. + /// + /// - Secret byte string `ikm` MUST be infeasible to guess, ideally + /// generated by a trusted source of randomness. `ikm` MUST be at least 32 + /// bytes long, but it MAY be longer. + /// - `salt` should either be empty or an unstructured byte string. It is + /// RECOMMENDED to fix a uniformly random byte string of length 32. See + /// details [here][salt]. + /// - `key_info` is optional, it MAY be used to derived multiple independent + /// keys from the same `ikm`. By default, `key_info` is the empty string. + /// + /// [v5]: https://datatracker.ietf.org/doc/pdf/draft-irtf-cfrg-bls-signature-05 + /// [salt]: https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#name-choosing-a-salt-value-for-k + pub fn key_gen_v5( + ikm: &[u8], + salt: &[u8], + key_info: &[u8], + ) -> Result< + ( + ::SigningKey, + ::VerificationKey, + ), + PrimitivesError, + > { + let sk = SecretKey::key_gen_v5(ikm, salt, key_info)?; + let vk = sk.sk_to_pk(); + + Ok((BLSSignKey(sk), BLSVerKey(vk))) + } +} + #[cfg(test)] mod test { - use ark_std::{test_rng, vec::Vec}; - use super::*; use crate::signatures::tests::{failed_verification, sign_and_verify}; + use ark_std::{fmt::Debug, rand::rngs::StdRng, vec}; #[test] fn test_bls_sig() { @@ -235,27 +398,36 @@ mod test { } #[test] - fn test_bls_sig_serde() { - let rng = &mut test_rng(); - let parameters = BLSSignatureScheme::param_gen(Some(rng)).unwrap(); - let (sk, vk) = BLSSignatureScheme::key_gen(¶meters, rng).unwrap(); - - // serde for Verification Key - let mut keypair_bytes = Vec::new(); - vk.serialize(&mut keypair_bytes).unwrap(); - let keypair_de = BLSVerKey::deserialize(&keypair_bytes[..]).unwrap(); - assert_eq!(vk, keypair_de); - // wrong byte length - assert!(BLSVerKey::deserialize(&keypair_bytes[1..]).is_err()); - - // serde for Signature - let message = "this is a test message"; - let sig = BLSSignatureScheme::sign(¶meters, &sk, message.as_bytes(), rng).unwrap(); - let mut sig_bytes = Vec::new(); - sig.serialize(&mut sig_bytes).unwrap(); - let sig_de = BLSSignature::deserialize(&sig_bytes[..]).unwrap(); - assert_eq!(sig, sig_de); - // wrong byte length - assert!(BLSSignature::deserialize(&sig_bytes[1..]).is_err()); + fn test_canonical_serde() { + let mut rng = ark_std::test_rng(); + let pp = BLSSignatureScheme::param_gen::(None).unwrap(); + let (sk, pk) = BLSSignatureScheme::key_gen(&pp, &mut rng).unwrap(); + let msg = "The quick brown fox jumps over the lazy dog"; + let sig = BLSSignatureScheme::sign(&pp, &sk, &msg, &mut rng).unwrap(); + + test_canonical_serde_helper(sk); + test_canonical_serde_helper(pk); + test_canonical_serde_helper(sig); + } + + // TODO: (alex) update this after upgrading to arkwork 0.4.0 + fn test_canonical_serde_helper(data: T) + where + T: CanonicalSerialize + CanonicalDeserialize + Debug + PartialEq, + { + let mut bytes = vec![]; + CanonicalSerialize::serialize(&data, &mut bytes).unwrap(); + let de: T = CanonicalDeserialize::deserialize(&bytes[..]).unwrap(); + assert_eq!(data, de); + + bytes = vec![]; + CanonicalSerialize::serialize_uncompressed(&data, &mut bytes).unwrap(); + let de: T = CanonicalDeserialize::deserialize_uncompressed(&bytes[..]).unwrap(); + assert_eq!(data, de); + + bytes = vec![]; + CanonicalSerialize::serialize_unchecked(&data, &mut bytes).unwrap(); + let de: T = CanonicalDeserialize::deserialize_unchecked(&bytes[..]).unwrap(); + assert_eq!(data, de); } } diff --git a/utilities/src/serialize.rs b/utilities/src/serialize.rs index 5f5e0b7f7..334213c07 100644 --- a/utilities/src/serialize.rs +++ b/utilities/src/serialize.rs @@ -44,9 +44,11 @@ macro_rules! deserialize_canonical_bytes { /// Serializers for finite field elements. /// /// Field elements are typically foreign types that we cannot apply the -/// [macro@tagged_blob] macro to. Instead, use `#[serde(with = "field_elem")]` +/// [tagged] macro to. Instead, use `#[serde(with = "field_elem")]` /// at the point where the field element is used inside a struct or enum /// definition. +/// +/// [tagged]: tagged_base64::tagged pub mod field_elem { use super::*; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};