diff --git a/Cargo.toml b/Cargo.toml index 528ebd552..2ef7724c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ members = [ "fstar-helpers/core-models", "test-utils", "crates/primitives/aead", + "crates/primitives/digest", ] [workspace.package] diff --git a/blake2/src/impl_digest_trait.rs b/blake2/src/impl_digest_trait.rs index 6a530cd6c..a0ad5fd79 100644 --- a/blake2/src/impl_digest_trait.rs +++ b/blake2/src/impl_digest_trait.rs @@ -1,11 +1,66 @@ use crate::impl_hacl::*; -use libcrux_traits::digest::{arrayref, slice, DigestIncrementalBase, Hasher, UpdateError}; +use libcrux_traits::digest::{ + arrayref, + consts::HashConsts, + slice, + typed_owned::{impl_digest_incremental_typed_owned, impl_hash_typed_owned}, + typed_refs, DigestIncrementalBase, Hasher, InitializeError, UpdateError, +}; -macro_rules! impl_digest_traits { - ($out_size:ident, $type:ty, $blake2:ty, $hasher:ty) => { +macro_rules! impl_runtime_digest_traits { + ($type:ty, $builder:ty, $max:expr) => { + impl slice::Hash for $type { + fn hash(digest: &mut [u8], payload: &[u8]) -> Result { + let digest_len: u8 = digest + .len() + .try_into() + .map_err(|_| slice::HashError::InvalidDigestLength)?; + + let mut hasher = <$builder>::new_unkeyed() + .build_var_digest_len(digest_len) + .map_err(|_| slice::HashError::InvalidDigestLength)?; + + hasher.update(payload).map_err(|e| match e { + Error::InvalidChunkLength | Error::MaximumLengthExceeded => { + slice::HashError::InvalidPayloadLength + } + _ => slice::HashError::Unknown, + })?; + + hasher + .finalize(digest.as_mut()) + .map_err(|_| slice::HashError::InvalidDigestLength) + } + } + impl typed_refs::Hash for $type { + fn digest_len_is_valid(&self, len: usize) -> bool { + (1..=$max).contains(&len) + } + fn hash<'a>( + &self, + mut digest: typed_refs::DigestMut<'a, Self>, + payload: &[u8], + ) -> Result<(), typed_refs::HashError> { + <$type as slice::Hash>::hash(digest.as_mut(), payload)?; + Ok(()) + } + } + }; +} + +macro_rules! impl_const_digest_traits { + ($out_size:ident, $type:ty, $blake2:ty, $hasher:ty, $builder:ty) => { impl DigestIncrementalBase for $type { type IncrementalState = $blake2; + fn new() -> Result { + <$builder>::new_unkeyed() + .build_const_digest_len() + .map_err(|e| match e { + Error::InvalidDigestLength => InitializeError::InvalidDigestLength, + _ => InitializeError::Unknown, + }) + } fn update(state: &mut Self::IncrementalState, chunk: &[u8]) -> Result<(), UpdateError> { // maps all known errors returned by this function state.update(chunk).map_err(|e| match e { @@ -39,39 +94,89 @@ macro_rules! impl_digest_traits { } } + impl arrayref::Hash<$out_size> for $type { + fn hash( + digest: &mut [u8; $out_size], + payload: &[u8], + ) -> Result<(), arrayref::HashError> { + // Initialize a new incremental hasher + let mut hasher = <$hasher>::new().map_err(|e| match e { + InitializeError::InvalidDigestLength => { + arrayref::HashError::InvalidDigestLength + } + InitializeError::Unknown => arrayref::HashError::Unknown, + })?; + // Update the hasher with the payload + hasher.update(payload).map_err(|e| match e { + UpdateError::InvalidPayloadLength => arrayref::HashError::InvalidPayloadLength, + UpdateError::MaximumLengthExceeded => arrayref::HashError::InvalidPayloadLength, + UpdateError::Unknown => arrayref::HashError::Unknown, + })?; + // Finalize and write to digest + hasher.finish(digest); + + Ok(()) + } + } + impl From<$blake2> for $hasher { fn from(state: $blake2) -> Self { Self { state } } } + + impl HashConsts for $type { + const DIGEST_SIZE: usize = $out_size; + } + impl_hash_typed_owned!($type, $out_size, generic); + impl_digest_incremental_typed_owned!($type, $out_size, generic); }; } +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct ConstDigestLen; +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct RuntimeDigestLen; + /// A struct that implements [`libcrux_traits::digest`] traits. /// /// [`Blake2bHasher`] is a convenience hasher for this struct. -pub struct Blake2bHash; +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct Blake2bHash { + _marker: core::marker::PhantomData, +} -impl_digest_traits!( +impl_const_digest_traits!( OUT_SIZE, - Blake2bHash, + Blake2bHash>, Blake2b>, - Blake2bHasher + Blake2bHasher, + Blake2bBuilder<'_, &_> ); +impl_runtime_digest_traits!(Blake2bHash, Blake2bBuilder<'_, &_>, 64); + /// A hasher for [`Blake2bHash`]. -pub type Blake2bHasher = Hasher>; +pub type Blake2bHasher = + Hasher>>; /// A struct that implements [`libcrux_traits::digest`] traits. /// /// [`Blake2sHasher`] is a convenience hasher for this struct. -pub struct Blake2sHash; -impl_digest_traits!( +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct Blake2sHash { + _marker: core::marker::PhantomData, +} +impl_const_digest_traits!( OUT_SIZE, - Blake2sHash, + Blake2sHash>, Blake2s>, - Blake2sHasher + Blake2sHasher, + Blake2sBuilder<'_, &_> ); +impl_runtime_digest_traits!(Blake2sHash, Blake2sBuilder<'_, &_>, 32); + /// A hasher for [`Blake2sHash`]. -pub type Blake2sHasher = Hasher>; +pub type Blake2sHasher = + Hasher>>; diff --git a/blake2/src/lib.rs b/blake2/src/lib.rs index d2281e383..186293004 100644 --- a/blake2/src/lib.rs +++ b/blake2/src/lib.rs @@ -12,5 +12,7 @@ mod impl_hacl; mod impl_digest_trait; -pub use impl_digest_trait::{Blake2bHash, Blake2bHasher, Blake2sHash, Blake2sHasher}; +pub use impl_digest_trait::{ + Blake2bHash, Blake2bHasher, Blake2sHash, Blake2sHasher, ConstDigestLen, RuntimeDigestLen, +}; pub use impl_hacl::{Blake2b, Blake2bBuilder, Blake2s, Blake2sBuilder, Error}; diff --git a/blake2/tests/blake2.rs b/blake2/tests/blake2.rs index 24f33b700..9ce35fd41 100644 --- a/blake2/tests/blake2.rs +++ b/blake2/tests/blake2.rs @@ -1,4 +1,7 @@ -use libcrux_blake2::{Blake2bBuilder, Blake2bHasher, Blake2sBuilder, Blake2sHasher}; +use libcrux_blake2::{ + Blake2bBuilder, Blake2bHash, Blake2bHasher, Blake2sBuilder, Blake2sHash, Blake2sHasher, + RuntimeDigestLen, +}; #[test] fn test_blake2b() { @@ -227,14 +230,24 @@ fn test_digest_traits_2s() { // test unkeyed, with const key and digest len let expected_hash = b"\xf2\x01\x46\xc0\x54\xf9\xdd\x6b\x67\x64\xb6\xc0\x93\x57\xf7\xcd\x75\x51\xdf\xbc\xba\x54\x59\x72\xa4\xc8\x16\x6d\xf8\xaf\xde\x60"; - let mut hasher: Blake2sHasher<_> = Blake2sBuilder::new_unkeyed() - .build_const_digest_len() - .unwrap() - .into(); + let mut hasher = Blake2sHasher::new().unwrap(); hasher.update(b"this is a test").unwrap(); hasher.finish(&mut got_hash); assert_eq!(&got_hash, expected_hash); + // compare to result from oneshot hasher + assert_eq!( + &Blake2sHasher::<32>::hash_to_owned(b"this is a test").unwrap(), + expected_hash + ); + + // compare to result from varlen hasher + use libcrux_traits::digest::typed_refs::*; + let mut digest = [0; 32]; + let algo = Blake2sHash::::default(); + let digest_mut = DigestMut::new_for_algo(algo, &mut digest).unwrap(); + algo.hash(digest_mut, b"this is a test").unwrap(); + assert_eq!(&digest, expected_hash); let mut too_short = vec![0; 31]; let err = hasher.finish_slice(&mut too_short).unwrap_err(); @@ -256,14 +269,15 @@ fn test_digest_traits_2b() { // test unkeyed, with const key and digest len let expected_hash = b"\xe9\xed\x14\x1d\xf1\xce\xbf\xc8\x9e\x46\x6c\xe0\x89\xee\xdd\x4f\x12\x5a\xa7\x57\x15\x01\xa0\xaf\x87\x1f\xab\x60\x59\x71\x17\xb7"; - let mut hasher: Blake2bHasher<_> = Blake2bBuilder::new_unkeyed() - .build_const_digest_len() - .unwrap() - .into(); + let mut hasher = Blake2bHasher::new().unwrap(); hasher.update(b"this is a test").unwrap(); hasher.finish(&mut got_hash); assert_eq!(&got_hash, expected_hash); + assert_eq!( + &Blake2bHasher::<32>::hash_to_owned(b"this is a test").unwrap(), + expected_hash + ); let mut too_short = vec![0; 31]; let err = hasher.finish_slice(&mut too_short).unwrap_err(); diff --git a/crates/primitives/digest/Cargo.toml b/crates/primitives/digest/Cargo.toml new file mode 100644 index 000000000..82ce0da55 --- /dev/null +++ b/crates/primitives/digest/Cargo.toml @@ -0,0 +1,23 @@ +[package] +description = "Formally verified digest library" +name = "libcrux-digest" +readme = "Readme.md" +version = "0.0.3" + +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +libcrux-blake2 = { version = "0.0.3", path = "../../../blake2", optional = true } +libcrux-sha2 = { version = "0.0.3", path = "../../../sha2", optional = true } +libcrux-sha3 = { version = "0.0.3", path = "../../../libcrux-sha3", optional = true } +libcrux-traits = { version = "0.0.3", path = "../../../traits", optional = true } + +[features] +blake2 = ["dep:libcrux-blake2", "dep:libcrux-traits"] +default = ["blake2", "sha2", "sha3"] +sha2 = ["dep:libcrux-sha2", "dep:libcrux-traits"] +sha3 = ["dep:libcrux-sha3", "dep:libcrux-traits"] diff --git a/crates/primitives/digest/src/lib.rs b/crates/primitives/digest/src/lib.rs new file mode 100644 index 000000000..da84065f8 --- /dev/null +++ b/crates/primitives/digest/src/lib.rs @@ -0,0 +1,31 @@ +mod multiplexed; + +pub use multiplexed::*; + +#[cfg(feature = "blake2")] +pub mod blake2 { + + pub use libcrux_blake2::{ + Blake2bHash as Blake2b, Blake2bHasher, Blake2sHash as Blake2s, Blake2sHasher, + ConstDigestLen, RuntimeDigestLen, + }; +} + +#[cfg(feature = "sha2")] +pub mod sha2 { + + pub use libcrux_sha2::{ + Sha224Hash as Sha2_224, Sha224Hasher as Sha2_224Hasher, Sha256Hash as Sha2_256, + Sha256Hasher as Sha2_256Hasher, Sha384Hash as Sha2_384, Sha384Hasher as Sha2_384Hasher, + Sha512Hash as Sha2_512, Sha512Hasher as Sha2_512Hasher, + }; +} + +#[cfg(feature = "sha3")] +pub mod sha3 { + + pub use libcrux_sha3::{ + Sha3_224, Sha3_224Hasher, Sha3_256, Sha3_256Hasher, Sha3_384, Sha3_384Hasher, Sha3_512, + Sha3_512Hasher, + }; +} diff --git a/crates/primitives/digest/src/multiplexed.rs b/crates/primitives/digest/src/multiplexed.rs new file mode 100644 index 000000000..0982c3f07 --- /dev/null +++ b/crates/primitives/digest/src/multiplexed.rs @@ -0,0 +1,237 @@ +use libcrux_traits::digest::typed_refs::{DigestMut, Hash, HashError, MultiplexesHash}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum HashAlgorithm { + // Sha2 variants + #[cfg(feature = "sha2")] + Sha2_224, + #[cfg(feature = "sha2")] + Sha2_256, + #[cfg(feature = "sha2")] + Sha2_384, + #[cfg(feature = "sha2")] + Sha2_512, + + // Sha3 variants + #[cfg(feature = "sha3")] + Sha3_224, + #[cfg(feature = "sha3")] + Sha3_256, + #[cfg(feature = "sha3")] + Sha3_384, + #[cfg(feature = "sha3")] + Sha3_512, + + // Blake2 variants + #[cfg(feature = "blake2")] + Blake2b, + #[cfg(feature = "blake2")] + Blake2s, +} + +impl Hash for HashAlgorithm { + fn digest_len_is_valid(&self, len: usize) -> bool { + match *self { + // Sha2 variants + #[cfg(feature = "sha2")] + HashAlgorithm::Sha2_224 => crate::sha2::Sha2_224.digest_len_is_valid(len), + #[cfg(feature = "sha2")] + HashAlgorithm::Sha2_256 => crate::sha2::Sha2_256.digest_len_is_valid(len), + #[cfg(feature = "sha2")] + HashAlgorithm::Sha2_384 => crate::sha2::Sha2_384.digest_len_is_valid(len), + #[cfg(feature = "sha2")] + HashAlgorithm::Sha2_512 => crate::sha2::Sha2_512.digest_len_is_valid(len), + + // Sha3 variants + #[cfg(feature = "sha3")] + HashAlgorithm::Sha3_224 => crate::sha3::Sha3_224.digest_len_is_valid(len), + #[cfg(feature = "sha3")] + HashAlgorithm::Sha3_256 => crate::sha3::Sha3_256.digest_len_is_valid(len), + #[cfg(feature = "sha3")] + HashAlgorithm::Sha3_384 => crate::sha3::Sha3_384.digest_len_is_valid(len), + #[cfg(feature = "sha3")] + HashAlgorithm::Sha3_512 => crate::sha3::Sha3_512.digest_len_is_valid(len), + + // Blake2 variants + #[cfg(feature = "blake2")] + HashAlgorithm::Blake2b => { + crate::blake2::Blake2b::::default() + .digest_len_is_valid(len) + } + #[cfg(feature = "blake2")] + HashAlgorithm::Blake2s => { + crate::blake2::Blake2s::::default() + .digest_len_is_valid(len) + } + } + } + fn hash<'a>(&self, digest: DigestMut<'a, Self>, payload: &[u8]) -> Result<(), HashError> { + match self { + // Sha2 variants + #[cfg(feature = "sha2")] + HashAlgorithm::Sha2_224 => { + let digest = Self::mux_digest(digest).ok_or(HashError::WrongDigest)?; + + crate::sha2::Sha2_224.hash(digest, payload) + } + #[cfg(feature = "sha2")] + HashAlgorithm::Sha2_256 => { + let digest = Self::mux_digest(digest).ok_or(HashError::WrongDigest)?; + + crate::sha2::Sha2_256.hash(digest, payload) + } + #[cfg(feature = "sha2")] + HashAlgorithm::Sha2_384 => { + let digest = Self::mux_digest(digest).ok_or(HashError::WrongDigest)?; + + crate::sha2::Sha2_384.hash(digest, payload) + } + #[cfg(feature = "sha2")] + HashAlgorithm::Sha2_512 => { + let digest = Self::mux_digest(digest).ok_or(HashError::WrongDigest)?; + + crate::sha2::Sha2_512.hash(digest, payload) + } + + // Sha3 variants + #[cfg(feature = "sha3")] + HashAlgorithm::Sha3_224 => { + let digest = Self::mux_digest(digest).ok_or(HashError::WrongDigest)?; + + crate::sha3::Sha3_224.hash(digest, payload) + } + #[cfg(feature = "sha3")] + HashAlgorithm::Sha3_256 => { + let digest = Self::mux_digest(digest).ok_or(HashError::WrongDigest)?; + + crate::sha3::Sha3_256.hash(digest, payload) + } + #[cfg(feature = "sha3")] + HashAlgorithm::Sha3_384 => { + let digest = Self::mux_digest(digest).ok_or(HashError::WrongDigest)?; + + crate::sha3::Sha3_384.hash(digest, payload) + } + #[cfg(feature = "sha3")] + HashAlgorithm::Sha3_512 => { + let digest = Self::mux_digest(digest).ok_or(HashError::WrongDigest)?; + + crate::sha3::Sha3_512.hash(digest, payload) + } + // Blake2 variants + #[cfg(feature = "blake2")] + HashAlgorithm::Blake2b => { + use crate::blake2::*; + let digest = Self::mux_digest(digest).ok_or(HashError::WrongDigest)?; + Blake2b::::default().hash(digest, payload) + } + #[cfg(feature = "blake2")] + HashAlgorithm::Blake2s => { + use crate::blake2::*; + let digest = Self::mux_digest(digest).ok_or(HashError::WrongDigest)?; + Blake2s::::default().hash(digest, payload) + } + } + } +} +macro_rules! impl_mux { + ($module:ident, $algo:ident) => { + impl_mux!($module, $algo, $algo); + }; + ($module:ident, $algo:ident, $type:ty) => { + impl MultiplexesHash<$type> for HashAlgorithm { + fn mux_algo(&self) -> Option<$type> { + Some(<$type>::default()) + } + + fn wrap_algo(_algo: $type) -> Self { + Self::$algo + } + } + }; +} + +#[cfg(feature = "sha2")] +mod sha2_impl { + use super::*; + use crate::sha2::*; + impl_mux!(sha2, Sha2_224); + impl_mux!(sha2, Sha2_256); + impl_mux!(sha2, Sha2_384); + impl_mux!(sha2, Sha2_512); +} + +#[cfg(feature = "blake2")] +mod blake2_impl { + use super::*; + use crate::blake2::*; + impl_mux!(blake2, Blake2b, Blake2b); + impl_mux!(blake2, Blake2s, Blake2s); +} + +#[cfg(feature = "sha3")] +mod sha3_impl { + use super::*; + use crate::sha3::*; + impl_mux!(sha3, Sha3_224); + impl_mux!(sha3, Sha3_256); + impl_mux!(sha3, Sha3_384); + impl_mux!(sha3, Sha3_512); +} +#[cfg(any(feature = "sha2", feature = "sha3", feature = "blake2"))] +#[cfg(test)] +mod tests { + + use libcrux_traits::digest::typed_refs; + use typed_refs::Hash as _; + + use super::HashAlgorithm; + + #[test] + #[cfg(feature = "sha2")] + fn test_multiplexed_sha2_hash() { + let algo = HashAlgorithm::Sha2_224; + let _digest = algo + .new_digest(&mut [0; 32]) + .expect_err("length should mismatch"); + + let mut digest = [0; 28]; + let digest_mut = algo.new_digest(&mut digest).expect("length should match"); + algo.hash(digest_mut, b"data").unwrap(); + + // TODO: check hash + } + #[test] + #[cfg(feature = "sha3")] + fn test_multiplexed_sha3_hash() { + let algo = HashAlgorithm::Sha3_224; + let _digest = algo + .new_digest(&mut [0; 32]) + .expect_err("length should mismatch"); + + let mut digest = [0; 28]; + let digest_mut = algo.new_digest(&mut digest).expect("length should match"); + algo.hash(digest_mut, b"data").unwrap(); + + // TODO: check hash + } + #[test] + #[cfg(feature = "blake2")] + fn test_multiplexed_blake2_hash() { + let algo = HashAlgorithm::Blake2b; + let _digest = algo + .new_digest(&mut [0; 65]) + .expect_err("length should be invalid"); + let _digest = algo + .new_digest(&mut [0; 0]) + .expect_err("length should be invalid"); + + let mut digest = [0; 1]; + let digest_mut = algo + .new_digest(&mut digest) + .expect("length should be valid"); + algo.hash(digest_mut, b"data").unwrap(); + + // TODO: check hash + } +} diff --git a/libcrux-sha3/src/impl_digest_trait.rs b/libcrux-sha3/src/impl_digest_trait.rs index b1ed99133..cbc34c633 100644 --- a/libcrux-sha3/src/impl_digest_trait.rs +++ b/libcrux-sha3/src/impl_digest_trait.rs @@ -10,6 +10,7 @@ macro_rules! impl_hash_traits { #[doc = concat!("A struct that implements [`libcrux_traits::digest`] traits.")] #[doc = concat!("\n\n")] #[doc = concat!("[`",stringify!($hasher), "`] is a convenient hasher for this struct.")] + #[derive(Clone, Copy, Default, PartialEq)] pub struct $type; #[doc = concat!("A hasher for [`",stringify!($type), "`].")] @@ -30,6 +31,10 @@ macro_rules! impl_hash_traits { Ok(()) } } + impl libcrux_traits::digest::consts::HashConsts for $type { + const DIGEST_SIZE: usize = $len; + } + libcrux_traits::digest::typed_owned::impl_hash_typed_owned!($type, $len); }; } diff --git a/sha2/src/impl_digest_trait.rs b/sha2/src/impl_digest_trait.rs index e3ebda82d..5c6a4b2e7 100644 --- a/sha2/src/impl_digest_trait.rs +++ b/sha2/src/impl_digest_trait.rs @@ -2,13 +2,15 @@ use crate::impl_hacl::*; use libcrux_traits::Digest; -use libcrux_traits::digest::{arrayref, slice, DigestIncrementalBase, UpdateError}; +use libcrux_traits::digest::{ + arrayref, consts, slice, typed_owned, DigestIncrementalBase, InitializeError, UpdateError, +}; // Streaming API - This is the recommended one. // For implementations based on hacl_rs (over hacl-c) macro_rules! impl_hash { ($hasher_name:ident, $name:ident, $state_name:ty, $digest_size:literal) => { - #[derive(Clone, Default)] + #[derive(Clone, Copy, Default, PartialEq)] #[doc = concat!("A struct that implements [`libcrux_traits::digest`] traits.")] #[doc = concat!("\n\n")] @@ -39,6 +41,11 @@ macro_rules! impl_hash { } impl DigestIncrementalBase for $name { type IncrementalState = $state_name; + + /// Initialize a new incremental state. + fn new() -> Result { + Ok(Self::IncrementalState::default()) + } /// Add the `payload` to the digest. /// Will return an error if `payload` is longer than `u32::MAX` to ensure that hacl-rs can /// process it. @@ -68,6 +75,11 @@ macro_rules! impl_hash { slice::impl_hash_trait!($name => $digest_size); slice::impl_digest_incremental_trait!($name => $state_name, $digest_size); + impl consts::HashConsts for $name { + const DIGEST_SIZE: usize = $digest_size; + } + typed_owned::impl_hash_typed_owned!($name, $digest_size); + }; } diff --git a/traits/src/digest.rs b/traits/src/digest.rs index a7181b358..52261436b 100644 --- a/traits/src/digest.rs +++ b/traits/src/digest.rs @@ -4,6 +4,10 @@ pub mod arrayref; pub mod owned; pub mod slice; +pub mod consts; +pub mod typed_owned; +pub mod typed_refs; + #[cfg(feature = "generic-tests")] pub mod tests; @@ -18,6 +22,15 @@ pub enum UpdateError { Unknown, } +/// Error indicating that initializing the digest state failed. +#[derive(Debug, PartialEq)] +pub enum InitializeError { + /// The provided digest length is invalid. + InvalidDigestLength, + /// Unknown error. + Unknown, +} + impl core::fmt::Display for UpdateError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let text = match self { @@ -30,9 +43,21 @@ impl core::fmt::Display for UpdateError { } } +impl core::fmt::Display for InitializeError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let text = match self { + InitializeError::InvalidDigestLength => "the provided digest length is invalid", + InitializeError::Unknown => "indicates an unknown error", + }; + + f.write_str(text) + } +} + #[cfg(feature = "error-in-core")] mod error_in_core { + impl core::error::Error for super::InitializeError {} impl core::error::Error for super::UpdateError {} } @@ -45,6 +70,8 @@ mod error_in_core { pub trait DigestIncrementalBase { /// The digest state. type IncrementalState; + /// Initialize a new digest state. + fn new() -> Result; /// Reset the digest state. fn reset(state: &mut Self::IncrementalState); /// Update the digest state with the `payload`. @@ -58,10 +85,7 @@ pub struct Hasher { pub state: D::IncrementalState, } -impl> Default for Hasher -where - D::IncrementalState: Default, -{ +impl> Default for Hasher { fn default() -> Self { Self { state: Default::default(), @@ -69,13 +93,6 @@ where } } -impl Hasher { - /// Oneshot API. Hash into a digest buffer, provided as a `&mut [u8]` slice. - pub fn hash_slice(digest: &mut [u8], payload: &[u8]) -> Result { - D::hash(digest, payload) - } -} - impl Hasher { /// Finalize and write into a digest buffer, provided as a `&mut [u8]` slice. pub fn finish_slice(&mut self, digest: &mut [u8]) -> Result { @@ -84,6 +101,11 @@ impl Hasher { } impl Hasher { + /// Initialize a new hasher. + pub fn new() -> Result { + D::new().map(|state| Self { state }) + } + /// Update the digest state with the `payload`. pub fn update(&mut self, payload: &[u8]) -> Result<(), UpdateError> { D::update(&mut self.state, payload) @@ -105,6 +127,13 @@ impl> Hasher { } } +impl Hasher { + /// Oneshot API. Hash into a digest buffer, provided as a `&mut [u8]` slice. + pub fn hash_slice(digest: &mut [u8], payload: &[u8]) -> Result { + D::hash(digest, payload) + } +} + impl> Hasher { /// Oneshot API. Hash into a digest buffer, provided as a `&mut [u8; N]` array reference. pub fn hash(digest: &mut [u8; N], payload: &[u8]) -> Result<(), arrayref::HashError> { diff --git a/traits/src/digest/arrayref.rs b/traits/src/digest/arrayref.rs index 9d2df185e..2063085d4 100644 --- a/traits/src/digest/arrayref.rs +++ b/traits/src/digest/arrayref.rs @@ -7,12 +7,18 @@ pub enum HashError { /// The length of the provided payload is invalid. InvalidPayloadLength, + /// The length of the provided digest is invalid. + InvalidDigestLength, + /// Unknown error. + Unknown, } impl core::fmt::Display for HashError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let text = match self { HashError::InvalidPayloadLength => "the length of the provided payload is invalid", + HashError::InvalidDigestLength => "the length of the provided digest is invalid", + HashError::Unknown => "indicates an unknown error", }; f.write_str(text) diff --git a/traits/src/digest/consts.rs b/traits/src/digest/consts.rs new file mode 100644 index 000000000..5e57121cf --- /dev/null +++ b/traits/src/digest/consts.rs @@ -0,0 +1,3 @@ +pub trait HashConsts { + const DIGEST_SIZE: usize; +} diff --git a/traits/src/digest/slice.rs b/traits/src/digest/slice.rs index 498e6906e..638bfa6cb 100644 --- a/traits/src/digest/slice.rs +++ b/traits/src/digest/slice.rs @@ -46,6 +46,8 @@ pub enum HashError { InvalidDigestLength, /// The length of the provided payload is invalid. InvalidPayloadLength, + /// Unknown error. + Unknown, } impl core::fmt::Display for HashError { @@ -53,6 +55,7 @@ impl core::fmt::Display for HashError { let text = match self { HashError::InvalidDigestLength => "the length of the provided digest buffer is invalid", HashError::InvalidPayloadLength => "the length of the provided payload is invalid", + HashError::Unknown => "indicates an unknown error", }; f.write_str(text) @@ -70,6 +73,8 @@ impl From for HashError { fn from(e: arrayref::HashError) -> Self { match e { arrayref::HashError::InvalidPayloadLength => Self::InvalidPayloadLength, + arrayref::HashError::InvalidDigestLength => Self::InvalidDigestLength, + arrayref::HashError::Unknown => Self::Unknown, } } } diff --git a/traits/src/digest/typed_owned.rs b/traits/src/digest/typed_owned.rs new file mode 100644 index 000000000..7951dd4cb --- /dev/null +++ b/traits/src/digest/typed_owned.rs @@ -0,0 +1,99 @@ +#[repr(transparent)] +pub struct Digest(Algo::Digest); + +#[repr(transparent)] +pub struct Hasher(Algo::Hasher); + +pub trait Hash: Sized + super::consts::HashConsts { + type Digest; + + fn hash(digest: &mut Digest, payload: &[u8]) -> Result<(), super::arrayref::HashError>; +} + +pub trait DigestIncremental: Sized + super::consts::HashConsts { + type Digest; + type Hasher; + fn hasher() -> Result, super::InitializeError>; +} + +#[macro_export] +macro_rules! impl_hash_typed_owned { + // literal arm + ($ty: ty, $digest_len:expr) => { + impl $crate::digest::typed_owned::Hash for $ty { + type Digest = [u8; $digest_len]; + fn hash( + digest: &mut $crate::digest::typed_owned::Digest, + payload: &[u8], + ) -> Result<(), $crate::digest::arrayref::HashError> { + <$ty as $crate::digest::arrayref::Hash<$digest_len>>::hash(digest.as_mut(), payload) + } + } + }; + // const generic arm + ($ty: ty, $digest_len:ident, generic) => { + impl $crate::digest::typed_owned::Hash for $ty { + type Digest = [u8; $digest_len]; + fn hash( + digest: &mut $crate::digest::typed_owned::Digest, + payload: &[u8], + ) -> Result<(), $crate::digest::arrayref::HashError> { + <$ty as $crate::digest::arrayref::Hash<$digest_len>>::hash(digest.as_mut(), payload) + } + } + }; +} +pub use impl_hash_typed_owned; + +#[macro_export] +macro_rules! impl_digest_incremental_typed_owned { + // literal arm + ($ty: ty, $digest_len:expr) => { + impl $crate::digest::typed_owned::DigestIncremental for $ty { + type Digest = [u8; $digest_len]; + type Hasher = $crate::digest::Hasher<$digest_len, Self>; + fn hasher( + ) -> Result<$crate::digest::typed_owned::Hasher, $crate::digest::InitializeError> + { + Self::Hasher::new() + } + } + }; + // const generic arm + ($ty: ty, $digest_len:ident, generic) => { + impl $crate::digest::typed_owned::DigestIncremental for $ty { + type Digest = [u8; $digest_len]; + type Hasher = $crate::digest::Hasher<$digest_len, Self>; + fn hasher( + ) -> Result<$crate::digest::typed_owned::Hasher, $crate::digest::InitializeError> + { + Self::Hasher::new().map(|hasher| hasher.into()) + } + } + }; +} +pub use impl_digest_incremental_typed_owned; + +impl AsMut for Digest { + fn as_mut(&mut self) -> &mut Algo::Digest { + &mut self.0 + } +} + +impl> From<&mut [u8; N]> for &mut Digest { + fn from(bytes: &mut Algo::Digest) -> Self { + unsafe { core::mem::transmute(bytes) } + } +} + +// hasher conversion +impl< + const N: usize, + Algo: DigestIncremental> + + super::DigestIncrementalBase, + > From> for Hasher +{ + fn from(hasher: super::Hasher) -> Self { + Self(hasher) + } +} diff --git a/traits/src/digest/typed_refs.rs b/traits/src/digest/typed_refs.rs new file mode 100644 index 000000000..6ac04af38 --- /dev/null +++ b/traits/src/digest/typed_refs.rs @@ -0,0 +1,104 @@ +#[derive(Debug)] +pub struct DigestMut<'a, Algo> { + algorithm: Algo, + digest: &'a mut [u8], +} + +impl DigestMut<'_, Algo> { + pub fn algo(&self) -> &Algo { + &self.algorithm + } +} + +#[derive(Debug, Clone, Copy)] +pub struct WrongLengthError; + +#[derive(Debug, Clone, Copy)] +pub enum HashError { + InvalidDigestLength, + WrongDigest, + InvalidPayloadLength, + Unknown, +} + +impl From for HashError { + fn from(e: super::slice::HashError) -> Self { + match e { + super::slice::HashError::InvalidDigestLength => HashError::InvalidDigestLength, + super::slice::HashError::InvalidPayloadLength => HashError::InvalidPayloadLength, + super::slice::HashError::Unknown => HashError::Unknown, + } + } +} +impl From for HashError { + fn from(e: super::arrayref::HashError) -> Self { + match e { + super::arrayref::HashError::InvalidDigestLength => HashError::InvalidDigestLength, + super::arrayref::HashError::InvalidPayloadLength => HashError::InvalidPayloadLength, + super::arrayref::HashError::Unknown => HashError::Unknown, + } + } +} + +impl<'a, Algo: Hash> DigestMut<'a, Algo> { + pub fn new_for_algo(algo: Algo, digest: &'a mut [u8]) -> Result { + // check that digest length matches, if available + if !algo.digest_len_is_valid(digest.len()) { + return Err(WrongLengthError); + } + + Ok(DigestMut { + algorithm: algo, + digest, + }) + } +} + +impl AsMut<[u8]> for DigestMut<'_, Algo> { + fn as_mut(&mut self) -> &mut [u8] { + self.digest + } +} + +pub trait Hash: Copy + PartialEq { + fn digest_len_is_valid(&self, len: usize) -> bool; + + fn hash<'a>(&self, digest: DigestMut<'a, Self>, payload: &[u8]) -> Result<(), HashError>; + + fn new_digest<'a>(self, digest: &'a mut [u8]) -> Result, WrongLengthError> { + DigestMut::new_for_algo(self, digest) + } +} + +impl< + const DIGEST_LEN: usize, + Algo: super::typed_owned::Hash + Copy + PartialEq, + > Hash for Algo +{ + fn digest_len_is_valid(&self, digest_len: usize) -> bool { + digest_len == DIGEST_LEN + } + fn hash<'a>(&self, mut digest: DigestMut<'a, Self>, payload: &[u8]) -> Result<(), HashError> { + if DIGEST_LEN != digest.digest.len() { + return Err(HashError::InvalidDigestLength); + } + + let digest: &mut [u8; DIGEST_LEN] = + digest.as_mut().try_into().map_err(|_| HashError::Unknown)?; + + ::hash(digest.into(), payload).map_err(HashError::from) + } +} + +pub trait MultiplexesHash: Hash + Sized { + fn mux_algo(&self) -> Option; + + fn wrap_algo(algo: Algo) -> Self; + + fn mux_digest<'a>(digest: DigestMut<'a, Self>) -> Option> { + let DigestMut { algorithm, digest } = digest; + algorithm + .mux_algo() + .map(|algorithm| DigestMut { algorithm, digest }) + } +}