diff --git a/Cargo.lock b/Cargo.lock index e12a64a7f..00597ae3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -849,6 +849,7 @@ dependencies = [ "bls-crypto", "blst", "criterion", + "digest 0.10.3", "ed25519-dalek", "eyre", "hex", diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index 6651dd431..5db0dce0b 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -18,8 +18,7 @@ zeroize = "1.5.6" signature = "1.5.0" tokio = { version = "1.19.2", features = ["sync", "rt", "macros"] } ark-bls12-377 = { version = "0.3.0", features = ["std"], optional = true } -hkdf = "0.12.3" -sha3 = "0.10.1" +hkdf = { version = "0.12.3", features = ["std"] } serde_with = "1.14.0" schemars ="0.8.10" @@ -37,6 +36,7 @@ once_cell = "1.13.0" readonly = "0.2.1" serde_bytes = "0.11.6" workspace-hack = { version = "0.1", path = "../workspace-hack" } +digest = "0.10.3" [[bench]] name = "crypto" @@ -53,3 +53,4 @@ copy_key = [] [dev-dependencies] bincode = "1.3.3" criterion = "0.3.5" +sha3 = "0.10.1" diff --git a/crypto/src/traits.rs b/crypto/src/traits.rs index 5a1ca1a1f..16b6f8123 100644 --- a/crypto/src/traits.rs +++ b/crypto/src/traits.rs @@ -3,10 +3,17 @@ // 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}; -use sha3::{digest::typenum::Unsigned, digest::OutputSizeUser, Sha3_256}; pub use signature::{Error, Signer}; use std::fmt::{Debug, Display}; @@ -154,37 +161,58 @@ pub trait KeyPair: Sized + From { fn generate(rng: &mut R) -> Self; fn public_key_bytes(&self) -> ::Bytes; +} - fn hkdf_generate( - // The Input Key Message defined by the HKDF specification. 32 bytes in length. - ikm: &[u8], - // A salt for key generation - salt: &[u8], - // An optional 'info' field as defined by the HKDF specification - info: Option<&[u8]>, - ) -> Result { - if ikm.len() != 32 { - return Err(signature::Error::new()); - } - Self::hkdf_generate_from_ikm(ikm, salt, info.unwrap_or(&DEFAULT_DOMAIN)) +/// 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())?; - fn hkdf_generate_from_ikm( - ikm: &[u8], // IKM (32 bytes) - salt: &[u8], // Optional salt - info: &[u8], // Optional domain - ) -> Result { - let hk = hkdf::Hkdf::::new(Some(salt), ikm); - const SHA3_OUTPUT_SIZE: usize = ::OutputSize::USIZE; - let mut okm = [0u8; SHA3_OUTPUT_SIZE]; - hk.expand(info, &mut okm) - .map_err(|_| signature::Error::new())?; - - let secret_key = Self::PrivKey::from_bytes(ikm).map_err(|_| signature::Error::new())?; - - let keypair = Self::from(secret_key); - Ok(keypair) - } + 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.