diff --git a/crypto/src/bls12377/mod.rs b/crypto/src/bls12377/mod.rs index 1b6bfbd30..5ab89a42a 100644 --- a/crypto/src/bls12377/mod.rs +++ b/crypto/src/bls12377/mod.rs @@ -3,7 +3,10 @@ use std::fmt::{self, Display}; -use crate::traits::{AggregateAuthenticator, EncodeDecodeBase64, ToFromBytes, VerifyingKeyBytes}; +use crate::{ + pubkey_bytes::PublicKeyBytes, + traits::{AggregateAuthenticator, EncodeDecodeBase64, ToFromBytes}, +}; use ::ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_bls12_377::{Fr, G1Affine, G1Projective, G2Affine, G2Projective}; use ark_ec::{AffineCurve, ProjectiveCurve}; @@ -16,7 +19,6 @@ use celo_bls::{hash_to_curve::try_and_increment, PublicKey}; use once_cell::sync::OnceCell; use serde::{de, Deserialize, Serialize}; use serde_with::serde_as; -use serde_with::Bytes; use signature::{Signer, Verifier}; use crate::traits::{Authenticator, KeyPair, SigningKey, VerifyingKey}; @@ -40,10 +42,7 @@ pub struct BLS12377PublicKey { pub bytes: OnceCell<[u8; CELO_BLS_PUBLIC_KEY_LENGTH]>, } -#[readonly::make] -#[serde_as] -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd, Copy, Hash)] -pub struct BLS12377PublicKeyBytes(#[serde_as(as = "Bytes")] [u8; CELO_BLS_PUBLIC_KEY_LENGTH]); +pub type BLS12377PublicKeyBytes = PublicKeyBytes; #[readonly::make] #[derive(Debug)] @@ -261,7 +260,6 @@ impl<'a> From<&'a BLS12377PrivateKey> for BLS12377PublicKey { impl VerifyingKey for BLS12377PublicKey { type PrivKey = BLS12377PrivateKey; type Sig = BLS12377Signature; - type Bytes = BLS12377PublicKeyBytes; const LENGTH: usize = CELO_BLS_PUBLIC_KEY_LENGTH; fn verify_batch(msg: &[u8], pks: &[Self], sigs: &[Self::Sig]) -> Result<(), signature::Error> { @@ -395,11 +393,6 @@ impl KeyPair for BLS12377KeyPair { }, } } - - fn public_key_bytes(&self) -> BLS12377PublicKeyBytes { - BLS12377PublicKeyBytes::from_bytes(self.name.as_ref()) - .expect("BLS12-377 serialization invariants violated") - } } impl Signer for BLS12377KeyPair { @@ -557,26 +550,6 @@ impl AggregateAuthenticator for BLS12377AggregateSignature { /// Implement VerifyingKeyBytes /// -impl Default for BLS12377PublicKeyBytes { - fn default() -> Self { - BLS12377PublicKeyBytes([0; CELO_BLS_PUBLIC_KEY_LENGTH]) - } -} - -impl AsRef<[u8]> for BLS12377PublicKeyBytes { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl Display for BLS12377PublicKeyBytes { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - let s = hex::encode(&self.0); - write!(f, "k#{}", s)?; - Ok(()) - } -} - impl TryInto for BLS12377PublicKeyBytes { type Error = signature::Error; @@ -586,16 +559,3 @@ impl TryInto for BLS12377PublicKeyBytes { BLS12377PublicKey::from_bytes(self.as_ref()).map_err(|_| Self::Error::new()) } } - -impl ToFromBytes for BLS12377PublicKeyBytes { - fn from_bytes(bytes: &[u8]) -> Result { - let arr: [u8; CELO_BLS_PUBLIC_KEY_LENGTH] = - bytes.try_into().map_err(|_| signature::Error::new())?; - - Ok(BLS12377PublicKeyBytes(arr)) - } -} - -impl VerifyingKeyBytes for BLS12377PublicKeyBytes { - type PubKey = BLS12377PublicKey; -} diff --git a/crypto/src/bls12381.rs b/crypto/src/bls12381.rs index 7c046b657..d3eb36351 100644 --- a/crypto/src/bls12381.rs +++ b/crypto/src/bls12381.rs @@ -13,19 +13,18 @@ use blst::min_sig as blst; use once_cell::sync::OnceCell; use rand::{rngs::OsRng, RngCore}; -use crate::serde_helpers::BlsSignature; +use crate::{pubkey_bytes::PublicKeyBytes, serde_helpers::BlsSignature}; use serde::{ de::{self}, Deserialize, Serialize, }; use serde_with::serde_as; -use serde_with::Bytes; use signature::{Signature, Signer, Verifier}; use crate::traits::{ AggregateAuthenticator, Authenticator, EncodeDecodeBase64, KeyPair, SigningKey, ToFromBytes, - VerifyingKey, VerifyingKeyBytes, + VerifyingKey, }; pub const BLS_PRIVATE_KEY_LENGTH: usize = 32; @@ -44,10 +43,7 @@ pub struct BLS12381PublicKey { pub bytes: OnceCell<[u8; BLS_PUBLIC_KEY_LENGTH]>, } -#[readonly::make] -#[serde_as] -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd, Copy, Hash)] -pub struct BLS12381PublicKeyBytes(#[serde_as(as = "Bytes")] [u8; BLS_PUBLIC_KEY_LENGTH]); +pub type BLS12381PublicKeyBytes = PublicKeyBytes; #[readonly::make] #[derive(Default, Debug)] @@ -186,7 +182,6 @@ impl<'a> From<&'a BLS12381PrivateKey> for BLS12381PublicKey { impl VerifyingKey for BLS12381PublicKey { type PrivKey = BLS12381PrivateKey; type Sig = BLS12381Signature; - type Bytes = BLS12381PublicKeyBytes; const LENGTH: usize = BLS_PUBLIC_KEY_LENGTH; @@ -411,11 +406,6 @@ impl KeyPair for BLS12381KeyPair { }, } } - - fn public_key_bytes(&self) -> BLS12381PublicKeyBytes { - let pk_arr: [u8; BLS_PUBLIC_KEY_LENGTH] = self.name.pubkey.to_bytes(); - BLS12381PublicKeyBytes(pk_arr) - } } impl Signer for BLS12381KeyPair { @@ -574,26 +564,6 @@ impl AggregateAuthenticator for BLS12381AggregateSignature { /// Implement VerifyingKeyBytes /// -impl Default for BLS12381PublicKeyBytes { - fn default() -> Self { - BLS12381PublicKeyBytes([0; BLS_PUBLIC_KEY_LENGTH]) - } -} - -impl AsRef<[u8]> for BLS12381PublicKeyBytes { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl Display for BLS12381PublicKeyBytes { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - let s = hex::encode(&self.0); - write!(f, "k#{}", s)?; - Ok(()) - } -} - impl TryInto for BLS12381PublicKeyBytes { type Error = signature::Error; @@ -603,16 +573,3 @@ impl TryInto for BLS12381PublicKeyBytes { BLS12381PublicKey::from_bytes(self.as_ref()).map_err(|_| Self::Error::new()) } } - -impl ToFromBytes for BLS12381PublicKeyBytes { - fn from_bytes(bytes: &[u8]) -> Result { - let arr: [u8; BLS_PUBLIC_KEY_LENGTH] = - bytes.try_into().map_err(|_| signature::Error::new())?; - - Ok(BLS12381PublicKeyBytes(arr)) - } -} - -impl VerifyingKeyBytes for BLS12381PublicKeyBytes { - type PubKey = BLS12381PublicKey; -} diff --git a/crypto/src/ed25519.rs b/crypto/src/ed25519.rs index b8c4349eb..a264e67f9 100644 --- a/crypto/src/ed25519.rs +++ b/crypto/src/ed25519.rs @@ -8,9 +8,10 @@ use signature::{Signature, Signer, Verifier}; use std::fmt::{self, Display}; use std::str::FromStr; +use crate::pubkey_bytes::PublicKeyBytes; use crate::traits::{ AggregateAuthenticator, Authenticator, EncodeDecodeBase64, KeyPair, SigningKey, ToFromBytes, - VerifyingKey, VerifyingKeyBytes, + VerifyingKey, }; /// @@ -20,8 +21,7 @@ use crate::traits::{ #[derive(Debug, Clone, PartialEq, Eq)] pub struct Ed25519PublicKey(pub ed25519_dalek::PublicKey); -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Ord, PartialOrd, Copy, Hash)] -pub struct Ed25519PublicKeyBytes([u8; ed25519_dalek::PUBLIC_KEY_LENGTH]); +pub type Ed25519PublicKeyBytes = PublicKeyBytes; #[derive(Debug)] pub struct Ed25519PrivateKey(pub ed25519_dalek::SecretKey); @@ -59,7 +59,6 @@ impl<'a> From<&'a Ed25519PrivateKey> for Ed25519PublicKey { impl VerifyingKey for Ed25519PublicKey { type PrivKey = Ed25519PrivateKey; type Sig = Ed25519Signature; - type Bytes = Ed25519PublicKeyBytes; const LENGTH: usize = ed25519_dalek::PUBLIC_KEY_LENGTH; fn verify_batch(msg: &[u8], pks: &[Self], sigs: &[Self::Sig]) -> Result<(), signature::Error> { @@ -346,11 +345,6 @@ impl KeyPair for Ed25519KeyPair { secret: Ed25519PrivateKey(kp.secret), } } - - fn public_key_bytes(&self) -> Ed25519PublicKeyBytes { - let pk_arr: [u8; ed25519_dalek::PUBLIC_KEY_LENGTH] = self.name.0.to_bytes(); - Ed25519PublicKeyBytes(pk_arr) - } } impl FromStr for Ed25519KeyPair { @@ -388,27 +382,6 @@ impl Signer for Ed25519KeyPair { /// /// Implement VerifyingKeyBytes /// - -impl Default for Ed25519PublicKeyBytes { - fn default() -> Self { - Ed25519PublicKeyBytes([0; ed25519_dalek::PUBLIC_KEY_LENGTH]) - } -} - -impl AsRef<[u8]> for Ed25519PublicKeyBytes { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl Display for Ed25519PublicKeyBytes { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - let s = hex::encode(&self.0); - write!(f, "k#{}", s)?; - Ok(()) - } -} - impl TryInto for Ed25519PublicKeyBytes { type Error = signature::Error; @@ -418,16 +391,3 @@ impl TryInto for Ed25519PublicKeyBytes { Ed25519PublicKey::from_bytes(self.as_ref()).map_err(|_| Self::Error::new()) } } - -impl ToFromBytes for Ed25519PublicKeyBytes { - fn from_bytes(bytes: &[u8]) -> Result { - let arr: [u8; ed25519_dalek::PUBLIC_KEY_LENGTH] = - bytes.try_into().map_err(|_| signature::Error::new())?; - - Ok(Ed25519PublicKeyBytes(arr)) - } -} - -impl VerifyingKeyBytes for Ed25519PublicKeyBytes { - type PubKey = Ed25519PublicKey; -} diff --git a/crypto/src/hkdf.rs b/crypto/src/hkdf.rs new file mode 100644 index 000000000..020117a42 --- /dev/null +++ b/crypto/src/hkdf.rs @@ -0,0 +1,63 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +use crate::traits::{KeyPair, SigningKey, ToFromBytes, DEFAULT_DOMAIN}; +use digest::{ + block_buffer::Eager, + consts::U256, + core_api::{BlockSizeUser, BufferKindUser, CoreProxy, FixedOutputCore, UpdateCore}, + typenum::{IsLess, Le, NonZero, Unsigned}, + HashMarker, OutputSizeUser, +}; +use hkdf::hmac::Hmac; + +/// Creation of a keypair using the [RFC 5869](https://tools.ietf.org/html/rfc5869) HKDF specification. +/// This requires choosing an HMAC function of the correct length (conservatively, the size of a private key for this curve). +/// Despite the unsightly generics (which aim to ensure this works for a wide range of hash functions), this is straightforward to use. +/// +/// Example: +/// ```rust +/// use sha3::Sha3_256; +/// use crypto::ed25519::Ed25519KeyPair; +/// use crypto::traits::hkdf_generate_from_ikm; +/// # fn main() { +/// let ikm = b"some_ikm"; +/// let domain = b"my_app"; +/// let salt = b"some_salt"; +/// let my_keypair = hkdf_generate_from_ikm::(ikm, salt, Some(domain)); +/// +/// let my_keypair_default_domain = hkdf_generate_from_ikm::(ikm, salt, None); +/// # } +/// ``` +pub fn hkdf_generate_from_ikm<'a, H: OutputSizeUser, K: KeyPair>( + ikm: &[u8], // IKM (32 bytes) + salt: &[u8], // Optional salt + info: Option<&'a [u8]>, // Optional domain +) -> Result +where + // This is a tad tedious, because of hkdf's use of a sealed trait. But mostly harmless. + H: CoreProxy + OutputSizeUser, + H::Core: HashMarker + + UpdateCore + + FixedOutputCore + + BufferKindUser + + Default + + Clone, + ::BlockSize: IsLess, + Le<::BlockSize, U256>: NonZero, +{ + let info = info.unwrap_or(&DEFAULT_DOMAIN); + let hk = hkdf::Hkdf::>::new(Some(salt), ikm); + // we need the HKDF to be able to expand precisely to the byte length of a Private key for the chosen KeyPair parameter. + // This check is a tad over constraining (check Hkdf impl for a more relaxed variant) but is always correct. + if H::OutputSize::USIZE != K::PrivKey::LENGTH { + return Err(signature::Error::from_source(hkdf::InvalidLength)); + } + let mut okm = vec![0u8; K::PrivKey::LENGTH]; + hk.expand(info, &mut okm) + .map_err(|_| signature::Error::new())?; + + let secret_key = K::PrivKey::from_bytes(&okm[..]).map_err(|_| signature::Error::new())?; + + let keypair = K::from(secret_key); + Ok(keypair) +} diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index b051d677e..292830ce7 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -38,6 +38,8 @@ pub mod bls12381_tests; pub mod bls12381; pub mod ed25519; +pub mod hkdf; +pub mod pubkey_bytes; pub mod serde_helpers; pub mod traits; diff --git a/crypto/src/pubkey_bytes.rs b/crypto/src/pubkey_bytes.rs new file mode 100644 index 000000000..61f053a8c --- /dev/null +++ b/crypto/src/pubkey_bytes.rs @@ -0,0 +1,84 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use self::sealed::SealedPublicKeyLength; +use crate::traits::{ToFromBytes, VerifyingKey}; +use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, Bytes}; +use std::{fmt::Display, marker::PhantomData}; + +/// A generic construction representing bytes who claim to be the instance of a public key. +#[serde_as] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +pub struct PublicKeyBytes { + #[serde_as(as = "Bytes")] + bytes: [u8; N], + phantom: PhantomData, +} + +impl AsRef<[u8]> for PublicKeyBytes +where + T: VerifyingKey, +{ + fn as_ref(&self) -> &[u8] { + &self.bytes[..] + } +} + +impl Display for PublicKeyBytes { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + let s = hex::encode(&self.bytes); + write!(f, "k#{}", s)?; + Ok(()) + } +} + +impl ToFromBytes for PublicKeyBytes { + fn from_bytes(bytes: &[u8]) -> Result { + let bytes: [u8; N] = bytes.try_into().map_err(signature::Error::from_source)?; + Ok(PublicKeyBytes { + bytes, + phantom: PhantomData, + }) + } +} + +impl PublicKeyBytes { + /// This ensures it's impossible to construct an instance with other than registered lengths + pub fn new(bytes: [u8; N]) -> PublicKeyBytes + where + PublicKeyBytes: SealedPublicKeyLength, + { + PublicKeyBytes { + bytes, + phantom: PhantomData, + } + } +} + +impl Default for PublicKeyBytes { + // this is probably derivable, but we'd rather have it explicitly laid out for instructional purposes, + // see [#34](https://github.com/MystenLabs/narwhal/issues/34) + fn default() -> Self { + Self { + bytes: [0u8; N], + phantom: PhantomData, + } + } +} + +// This guarantees the security of the constructor of a `PublicKeyBytes` instance +// TODO: replace this clunky sealed marker trait once feature(associated_const_equality) stabilizes +mod sealed { + #[cfg(feature = "celo")] + use crate::bls12377::BLS12377PublicKey; + use crate::{bls12381::BLS12381PublicKey, ed25519::Ed25519PublicKey, traits::VerifyingKey}; + + use super::PublicKeyBytes; + + pub trait SealedPublicKeyLength {} + impl SealedPublicKeyLength for PublicKeyBytes {} + impl SealedPublicKeyLength for PublicKeyBytes {} + #[cfg(feature = "celo")] + impl SealedPublicKeyLength for PublicKeyBytes {} +} diff --git a/crypto/src/traits.rs b/crypto/src/traits.rs index 16b6f8123..65e62bd07 100644 --- a/crypto/src/traits.rs +++ b/crypto/src/traits.rs @@ -3,16 +3,9 @@ // SPDX-License-Identifier: Apache-2.0 use base64ct::Encoding; use eyre::eyre; -use hkdf::hmac::Hmac; + use rand::{CryptoRng, RngCore}; -use digest::{ - block_buffer::Eager, - consts::U256, - core_api::{BlockSizeUser, BufferKindUser, CoreProxy, FixedOutputCore, UpdateCore}, - typenum::{IsLess, Le, NonZero, Unsigned}, - HashMarker, OutputSizeUser, -}; use serde::{de::DeserializeOwned, Serialize}; pub use signature::{Error, Signer}; use std::fmt::{Debug, Display}; @@ -99,7 +92,6 @@ pub trait VerifyingKey: { type PrivKey: SigningKey; type Sig: Authenticator; - type Bytes: VerifyingKeyBytes; const LENGTH: usize; // Expected to be overridden by implementations @@ -160,59 +152,6 @@ pub trait KeyPair: Sized + From { fn copy(&self) -> Self; fn generate(rng: &mut R) -> Self; - fn public_key_bytes(&self) -> ::Bytes; -} - -/// Creation of a keypair using the [RFC 5869](https://tools.ietf.org/html/rfc5869) HKDF specification. -/// This requires choosing an HMAC function of the correct length (conservatively, the size of a private key for this curve). -/// Despite the unsightly generics (which aim to ensure this works for a wide range of hash functions), this is straightforward to use. -/// -/// Example: -/// ```rust -/// use sha3::Sha3_256; -/// use crypto::ed25519::Ed25519KeyPair; -/// use crypto::traits::hkdf_generate_from_ikm; -/// # fn main() { -/// let ikm = b"some_ikm"; -/// let domain = b"my_app"; -/// let salt = b"some_salt"; -/// let my_keypair = hkdf_generate_from_ikm::(ikm, salt, Some(domain)); -/// -/// let my_keypair_default_domain = hkdf_generate_from_ikm::(ikm, salt, None); -/// # } -/// ``` -pub fn hkdf_generate_from_ikm<'a, H: OutputSizeUser, K: KeyPair>( - ikm: &[u8], // IKM (32 bytes) - salt: &[u8], // Optional salt - info: Option<&'a [u8]>, // Optional domain -) -> Result -where - // This is a tad tedious, because of hkdf's use of a sealed trait. But mostly harmless. - H: CoreProxy + OutputSizeUser, - H::Core: HashMarker - + UpdateCore - + FixedOutputCore - + BufferKindUser - + Default - + Clone, - ::BlockSize: IsLess, - Le<::BlockSize, U256>: NonZero, -{ - let info = info.unwrap_or(&DEFAULT_DOMAIN); - let hk = hkdf::Hkdf::>::new(Some(salt), ikm); - // we need the HKDF to be able to expand precisely to the byte length of a Private key for the chosen KeyPair parameter. - // This check is a tad over constraining (check Hkdf impl for a more relaxed variant) but is always correct. - if H::OutputSize::USIZE != K::PrivKey::LENGTH { - return Err(signature::Error::from_source(hkdf::InvalidLength)); - } - let mut okm = vec![0u8; K::PrivKey::LENGTH]; - hk.expand(info, &mut okm) - .map_err(|_| signature::Error::new())?; - - let secret_key = K::PrivKey::from_bytes(&okm[..]).map_err(|_| signature::Error::new())?; - - let keypair = K::from(secret_key); - Ok(keypair) } /// Trait impl'd by aggregated signatures in asymmetric cryptography. @@ -245,20 +184,3 @@ pub trait AggregateAuthenticator: messages: &[&[u8]], ) -> Result<(), Error>; } - -/// Trait impl'd byte representations of public keys in asymmetric cryptography. -/// -pub trait VerifyingKeyBytes: - Display - + Default - + AsRef<[u8]> - + TryInto - + Eq - + std::hash::Hash - + Copy - + Ord - + PartialOrd - + ToFromBytes -{ - type PubKey: VerifyingKey; -}