From 394175fdf57866729ef6fbf6977ac6e6ae8a6bd6 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 10 Sep 2025 11:05:12 +0200 Subject: [PATCH 01/16] require `Default` implementation for state --- blake2/src/impl_digest_trait.rs | 14 +++++++++++--- blake2/tests/blake2.rs | 10 ++-------- traits/src/digest.rs | 7 ++----- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/blake2/src/impl_digest_trait.rs b/blake2/src/impl_digest_trait.rs index 6a530cd6c..5f5e9ecef 100644 --- a/blake2/src/impl_digest_trait.rs +++ b/blake2/src/impl_digest_trait.rs @@ -2,7 +2,12 @@ use crate::impl_hacl::*; use libcrux_traits::digest::{arrayref, slice, DigestIncrementalBase, Hasher, UpdateError}; macro_rules! impl_digest_traits { - ($out_size:ident, $type:ty, $blake2:ty, $hasher:ty) => { + ($out_size:ident, $type:ty, $blake2:ty, $hasher:ty, $builder:ty) => { + impl Default for $blake2 { + fn default() -> Self { + <$builder>::new_unkeyed().build_const_digest_len().unwrap() + } + } impl DigestIncrementalBase for $type { type IncrementalState = $blake2; @@ -50,13 +55,15 @@ macro_rules! impl_digest_traits { /// A struct that implements [`libcrux_traits::digest`] traits. /// /// [`Blake2bHasher`] is a convenience hasher for this struct. +#[derive(Default)] pub struct Blake2bHash; impl_digest_traits!( OUT_SIZE, Blake2bHash, Blake2b>, - Blake2bHasher + Blake2bHasher, + Blake2bBuilder<'_, &_> ); /// A hasher for [`Blake2bHash`]. @@ -70,7 +77,8 @@ impl_digest_traits!( OUT_SIZE, Blake2sHash, Blake2s>, - Blake2sHasher + Blake2sHasher, + Blake2sBuilder<'_, &_> ); /// A hasher for [`Blake2sHash`]. diff --git a/blake2/tests/blake2.rs b/blake2/tests/blake2.rs index 24f33b700..09263ac04 100644 --- a/blake2/tests/blake2.rs +++ b/blake2/tests/blake2.rs @@ -227,10 +227,7 @@ 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::default(); hasher.update(b"this is a test").unwrap(); hasher.finish(&mut got_hash); @@ -256,10 +253,7 @@ 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::default(); hasher.update(b"this is a test").unwrap(); hasher.finish(&mut got_hash); diff --git a/traits/src/digest.rs b/traits/src/digest.rs index a7181b358..116d92cb6 100644 --- a/traits/src/digest.rs +++ b/traits/src/digest.rs @@ -44,7 +44,7 @@ mod error_in_core { /// - [`owned::DigestIncremental`] pub trait DigestIncrementalBase { /// The digest state. - type IncrementalState; + type IncrementalState: Default; /// Reset the digest state. fn reset(state: &mut Self::IncrementalState); /// Update the digest state with the `payload`. @@ -58,10 +58,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(), From 09a73d61decd2b855f9645869adb7927d08d3f41 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 10 Sep 2025 11:29:52 +0200 Subject: [PATCH 02/16] implement oneshot API for Blake2 --- blake2/src/impl_digest_trait.rs | 17 +++++++++++++++++ traits/src/digest.rs | 14 +++++++------- traits/src/digest/arrayref.rs | 3 +++ traits/src/digest/slice.rs | 4 ++++ 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/blake2/src/impl_digest_trait.rs b/blake2/src/impl_digest_trait.rs index 5f5e9ecef..f0a6113ca 100644 --- a/blake2/src/impl_digest_trait.rs +++ b/blake2/src/impl_digest_trait.rs @@ -44,6 +44,23 @@ macro_rules! impl_digest_traits { } } + impl arrayref::Hash<$out_size> for $type { + fn hash( + digest: &mut [u8; $out_size], + payload: &[u8], + ) -> Result<(), arrayref::HashError> { + let mut hasher = <$hasher>::default(); + hasher.update(payload).map_err(|e| match e { + UpdateError::InvalidPayloadLength => arrayref::HashError::InvalidPayloadLength, + UpdateError::MaximumLengthExceeded => arrayref::HashError::InvalidPayloadLength, + UpdateError::Unknown => arrayref::HashError::Unknown, + })?; + hasher.finish(digest); + + Ok(()) + } + } + impl From<$blake2> for $hasher { fn from(state: $blake2) -> Self { Self { state } diff --git a/traits/src/digest.rs b/traits/src/digest.rs index 116d92cb6..e9fd75272 100644 --- a/traits/src/digest.rs +++ b/traits/src/digest.rs @@ -66,13 +66,6 @@ impl Default for 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 { /// Finalize and write into a digest buffer, provided as a `&mut [u8]` slice. pub fn finish_slice(&mut self, digest: &mut [u8]) -> Result { @@ -102,6 +95,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..8a9a59cc8 100644 --- a/traits/src/digest/arrayref.rs +++ b/traits/src/digest/arrayref.rs @@ -7,12 +7,15 @@ pub enum HashError { /// The length of the provided payload is invalid. InvalidPayloadLength, + /// 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::Unknown => "indicates an unknown error", }; f.write_str(text) diff --git a/traits/src/digest/slice.rs b/traits/src/digest/slice.rs index 498e6906e..02f0e26c9 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,7 @@ impl From for HashError { fn from(e: arrayref::HashError) -> Self { match e { arrayref::HashError::InvalidPayloadLength => Self::InvalidPayloadLength, + arrayref::HashError::Unknown => Self::Unknown, } } } From 9bec03e4c84a82cecff352cc19dbd952f265c3b5 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 10 Sep 2025 13:37:37 +0200 Subject: [PATCH 03/16] begin implementing standalone `libcrux-digest` crate --- Cargo.toml | 1 + crates/primitives/digest/Cargo.toml | 23 +++++++++++++++++++++++ crates/primitives/digest/src/lib.rs | 20 ++++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 crates/primitives/digest/Cargo.toml create mode 100644 crates/primitives/digest/src/lib.rs 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/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..ebaedc595 --- /dev/null +++ b/crates/primitives/digest/src/lib.rs @@ -0,0 +1,20 @@ +#[cfg(feature = "blake2")] +pub mod blake2 { + + pub use libcrux_blake2::{Blake2bHasher, Blake2sHasher}; +} + +#[cfg(feature = "sha2")] +pub mod sha2 { + + pub use libcrux_sha2::{ + Sha224Hasher as Sha2_224Hasher, Sha256Hasher as Sha2_256Hasher, + Sha384Hasher as Sha2_384Hasher, Sha512Hasher as Sha2_512Hasher, + }; +} + +#[cfg(feature = "sha3")] +pub mod sha3 { + + pub use libcrux_sha3::{Sha3_224Hasher, Sha3_256Hasher, Sha3_384Hasher, Sha3_512Hasher}; +} From e3e529e88e033b3e6630e305d69e394d1644391a Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 10 Sep 2025 13:43:13 +0200 Subject: [PATCH 04/16] derive `Default` for `Blake2sHash` --- blake2/src/impl_digest_trait.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/blake2/src/impl_digest_trait.rs b/blake2/src/impl_digest_trait.rs index f0a6113ca..864dc87d1 100644 --- a/blake2/src/impl_digest_trait.rs +++ b/blake2/src/impl_digest_trait.rs @@ -89,6 +89,7 @@ pub type Blake2bHasher = Hasher; impl_digest_traits!( OUT_SIZE, From 1530ffbf93761ec051d895e6377299de563a7445 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 10 Sep 2025 14:24:11 +0200 Subject: [PATCH 05/16] require `new()` initialization with error handling instead of `Default` --- blake2/src/impl_digest_trait.rs | 26 +++++++++++++++++--------- blake2/tests/blake2.rs | 13 +++++++++++-- traits/src/digest.rs | 32 ++++++++++++++++++++++++++++++-- traits/src/digest/arrayref.rs | 3 +++ traits/src/digest/slice.rs | 1 + 5 files changed, 62 insertions(+), 13 deletions(-) diff --git a/blake2/src/impl_digest_trait.rs b/blake2/src/impl_digest_trait.rs index 864dc87d1..5b1d92596 100644 --- a/blake2/src/impl_digest_trait.rs +++ b/blake2/src/impl_digest_trait.rs @@ -1,16 +1,21 @@ use crate::impl_hacl::*; -use libcrux_traits::digest::{arrayref, slice, DigestIncrementalBase, Hasher, UpdateError}; +use libcrux_traits::digest::{ + arrayref, slice, DigestIncrementalBase, Hasher, InitializeError, UpdateError, +}; macro_rules! impl_digest_traits { ($out_size:ident, $type:ty, $blake2:ty, $hasher:ty, $builder:ty) => { - impl Default for $blake2 { - fn default() -> Self { - <$builder>::new_unkeyed().build_const_digest_len().unwrap() - } - } 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 { @@ -49,7 +54,12 @@ macro_rules! impl_digest_traits { digest: &mut [u8; $out_size], payload: &[u8], ) -> Result<(), arrayref::HashError> { - let mut hasher = <$hasher>::default(); + let mut hasher = <$hasher>::new().map_err(|e| match e { + InitializeError::InvalidDigestLength => { + arrayref::HashError::InvalidDigestLength + } + InitializeError::Unknown => arrayref::HashError::Unknown, + })?; hasher.update(payload).map_err(|e| match e { UpdateError::InvalidPayloadLength => arrayref::HashError::InvalidPayloadLength, UpdateError::MaximumLengthExceeded => arrayref::HashError::InvalidPayloadLength, @@ -72,7 +82,6 @@ macro_rules! impl_digest_traits { /// A struct that implements [`libcrux_traits::digest`] traits. /// /// [`Blake2bHasher`] is a convenience hasher for this struct. -#[derive(Default)] pub struct Blake2bHash; impl_digest_traits!( @@ -89,7 +98,6 @@ pub type Blake2bHasher = Hasher; impl_digest_traits!( OUT_SIZE, diff --git a/blake2/tests/blake2.rs b/blake2/tests/blake2.rs index 09263ac04..7c3913d46 100644 --- a/blake2/tests/blake2.rs +++ b/blake2/tests/blake2.rs @@ -227,11 +227,16 @@ 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::default(); + 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 + ); let mut too_short = vec![0; 31]; let err = hasher.finish_slice(&mut too_short).unwrap_err(); @@ -253,11 +258,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::default(); + 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/traits/src/digest.rs b/traits/src/digest.rs index e9fd75272..b7a1f7fc3 100644 --- a/traits/src/digest.rs +++ b/traits/src/digest.rs @@ -18,6 +18,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 +39,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 {} } @@ -44,7 +65,9 @@ mod error_in_core { /// - [`owned::DigestIncremental`] pub trait DigestIncrementalBase { /// The digest state. - type IncrementalState: Default; + 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,7 +81,7 @@ pub struct Hasher { pub state: D::IncrementalState, } -impl Default for Hasher { +impl> Default for Hasher { fn default() -> Self { Self { state: Default::default(), @@ -74,6 +97,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) diff --git a/traits/src/digest/arrayref.rs b/traits/src/digest/arrayref.rs index 8a9a59cc8..2063085d4 100644 --- a/traits/src/digest/arrayref.rs +++ b/traits/src/digest/arrayref.rs @@ -7,6 +7,8 @@ pub enum HashError { /// The length of the provided payload is invalid. InvalidPayloadLength, + /// The length of the provided digest is invalid. + InvalidDigestLength, /// Unknown error. Unknown, } @@ -15,6 +17,7 @@ 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", }; diff --git a/traits/src/digest/slice.rs b/traits/src/digest/slice.rs index 02f0e26c9..638bfa6cb 100644 --- a/traits/src/digest/slice.rs +++ b/traits/src/digest/slice.rs @@ -73,6 +73,7 @@ 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, } } From 08e4baf1e9fbea0c752e0300cba80661fb08194a Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 10 Sep 2025 14:26:54 +0200 Subject: [PATCH 06/16] add documentation --- blake2/src/impl_digest_trait.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/blake2/src/impl_digest_trait.rs b/blake2/src/impl_digest_trait.rs index 5b1d92596..86d8d745b 100644 --- a/blake2/src/impl_digest_trait.rs +++ b/blake2/src/impl_digest_trait.rs @@ -54,17 +54,20 @@ macro_rules! impl_digest_traits { 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(()) From d505f387f7b19ca12ad7d3c4819479b1d43beb73 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Wed, 10 Sep 2025 14:32:03 +0200 Subject: [PATCH 07/16] implement `new()` for sha2 digest state initialization --- sha2/src/impl_digest_trait.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sha2/src/impl_digest_trait.rs b/sha2/src/impl_digest_trait.rs index e3ebda82d..6c8e03b95 100644 --- a/sha2/src/impl_digest_trait.rs +++ b/sha2/src/impl_digest_trait.rs @@ -2,7 +2,9 @@ use crate::impl_hacl::*; use libcrux_traits::Digest; -use libcrux_traits::digest::{arrayref, slice, DigestIncrementalBase, UpdateError}; +use libcrux_traits::digest::{ + arrayref, slice, DigestIncrementalBase, InitializeError, UpdateError, +}; // Streaming API - This is the recommended one. // For implementations based on hacl_rs (over hacl-c) @@ -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. From d2f56e1020f96018492c941ae1b5c607cfe68293 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Thu, 11 Sep 2025 13:45:29 +0200 Subject: [PATCH 08/16] begin implementing new Digest public API --- traits/src/digest.rs | 4 ++ traits/src/digest/consts.rs | 3 + traits/src/digest/typed_owned.rs | 99 ++++++++++++++++++++++++++++++++ traits/src/digest/typed_refs.rs | 80 ++++++++++++++++++++++++++ 4 files changed, 186 insertions(+) create mode 100644 traits/src/digest/consts.rs create mode 100644 traits/src/digest/typed_owned.rs create mode 100644 traits/src/digest/typed_refs.rs diff --git a/traits/src/digest.rs b/traits/src/digest.rs index b7a1f7fc3..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; 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/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..0068e2835 --- /dev/null +++ b/traits/src/digest/typed_refs.rs @@ -0,0 +1,80 @@ +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; + +impl<'a, Algo: Hash> DigestMut<'a, Algo> { + pub fn new_for_algo(algo: Algo, digest: &'a mut [u8]) -> Result { + (digest.len() == algo.digest_len()) + .then_some(DigestMut { + algorithm: algo, + digest, + }) + .ok_or(WrongLengthError) + } +} + +impl AsMut<[u8]> for DigestMut<'_, Algo> { + fn as_mut(&mut self) -> &mut [u8] { + self.digest + } +} + +pub trait Hash: Copy + PartialEq { + fn digest_len(&self) -> usize; + + fn hash<'a>( + &self, + digest: DigestMut<'a, Self>, + payload: &[u8], + ) -> Result<(), super::slice::HashError>; +} + +impl< + const DIGEST_LEN: usize, + Algo: super::typed_owned::Hash + Copy + PartialEq, + > Hash for Algo +{ + fn digest_len(&self) -> usize { + DIGEST_LEN + } + fn hash<'a>( + &self, + mut digest: DigestMut<'a, Self>, + payload: &[u8], + ) -> Result<(), super::slice::HashError> { + if self.digest_len() != digest.digest.len() { + return Err(todo!()); + } + + let digest: &mut [u8; DIGEST_LEN] = digest + .as_mut() + .try_into() + .map_err(|_| super::slice::HashError::Unknown)?; + + ::hash(digest.into(), payload) + .map_err(super::slice::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 }) + } +} From 8388168e9a94126941191bdeb1ac5ff80992c46c Mon Sep 17 00:00:00 2001 From: wysiwys Date: Thu, 11 Sep 2025 13:46:18 +0200 Subject: [PATCH 09/16] public API implementation in digest crates --- blake2/src/impl_digest_trait.rs | 14 +++++++++++++- libcrux-sha3/src/impl_digest_trait.rs | 5 +++++ sha2/src/impl_digest_trait.rs | 9 +++++++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/blake2/src/impl_digest_trait.rs b/blake2/src/impl_digest_trait.rs index 86d8d745b..92786ed30 100644 --- a/blake2/src/impl_digest_trait.rs +++ b/blake2/src/impl_digest_trait.rs @@ -1,6 +1,10 @@ use crate::impl_hacl::*; use libcrux_traits::digest::{ - arrayref, slice, DigestIncrementalBase, Hasher, InitializeError, UpdateError, + arrayref, + consts::HashConsts, + slice, + typed_owned::{impl_digest_incremental_typed_owned, impl_hash_typed_owned}, + DigestIncrementalBase, Hasher, InitializeError, UpdateError, }; macro_rules! impl_digest_traits { @@ -79,12 +83,19 @@ macro_rules! impl_digest_traits { 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); }; } /// A struct that implements [`libcrux_traits::digest`] traits. /// /// [`Blake2bHasher`] is a convenience hasher for this struct. +#[derive(Clone, Copy, Default, PartialEq)] pub struct Blake2bHash; impl_digest_traits!( @@ -101,6 +112,7 @@ pub type Blake2bHasher = Hasher; impl_digest_traits!( OUT_SIZE, 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 6c8e03b95..5c6a4b2e7 100644 --- a/sha2/src/impl_digest_trait.rs +++ b/sha2/src/impl_digest_trait.rs @@ -3,14 +3,14 @@ use crate::impl_hacl::*; use libcrux_traits::Digest; use libcrux_traits::digest::{ - arrayref, slice, DigestIncrementalBase, InitializeError, UpdateError, + 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")] @@ -75,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); + }; } From f77fe2809c6e77db37d1dbefee2162727878c470 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Thu, 11 Sep 2025 13:47:07 +0200 Subject: [PATCH 10/16] reexport hashers and digest algorithm implementations --- crates/primitives/digest/src/lib.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/primitives/digest/src/lib.rs b/crates/primitives/digest/src/lib.rs index ebaedc595..c3b6f02df 100644 --- a/crates/primitives/digest/src/lib.rs +++ b/crates/primitives/digest/src/lib.rs @@ -1,20 +1,26 @@ #[cfg(feature = "blake2")] pub mod blake2 { - pub use libcrux_blake2::{Blake2bHasher, Blake2sHasher}; + pub use libcrux_blake2::{ + Blake2bHash as Blake2b, Blake2bHasher, Blake2sHash as Blake2s, Blake2sHasher, + }; } #[cfg(feature = "sha2")] pub mod sha2 { pub use libcrux_sha2::{ - Sha224Hasher as Sha2_224Hasher, Sha256Hasher as Sha2_256Hasher, - Sha384Hasher as Sha2_384Hasher, Sha512Hasher as Sha2_512Hasher, + 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_224Hasher, Sha3_256Hasher, Sha3_384Hasher, Sha3_512Hasher}; + pub use libcrux_sha3::{ + Sha3_224, Sha3_224Hasher, Sha3_256, Sha3_256Hasher, Sha3_384, Sha3_384Hasher, Sha3_512, + Sha3_512Hasher, + }; } From c80d83671376c84ae340aad4639ab8a6e4d8ab3a Mon Sep 17 00:00:00 2001 From: wysiwys Date: Thu, 11 Sep 2025 16:26:46 +0200 Subject: [PATCH 11/16] check digest length validity and implement `HashError` --- traits/src/digest/typed_refs.rs | 73 +++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/traits/src/digest/typed_refs.rs b/traits/src/digest/typed_refs.rs index 0068e2835..ece61fb18 100644 --- a/traits/src/digest/typed_refs.rs +++ b/traits/src/digest/typed_refs.rs @@ -12,14 +12,44 @@ impl DigestMut<'_, Algo> { #[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 { - (digest.len() == algo.digest_len()) - .then_some(DigestMut { - algorithm: algo, - digest, - }) - .ok_or(WrongLengthError) + // check that digest length matches, if available + if !algo.digest_len_is_valid(digest.len()) { + return Err(WrongLengthError); + } + + Ok(DigestMut { + algorithm: algo, + digest, + }) } } @@ -30,13 +60,9 @@ impl AsMut<[u8]> for DigestMut<'_, Algo> { } pub trait Hash: Copy + PartialEq { - fn digest_len(&self) -> usize; + fn digest_len_is_valid(&self, len: usize) -> bool; - fn hash<'a>( - &self, - digest: DigestMut<'a, Self>, - payload: &[u8], - ) -> Result<(), super::slice::HashError>; + fn hash<'a>(&self, digest: DigestMut<'a, Self>, payload: &[u8]) -> Result<(), HashError>; } impl< @@ -44,25 +70,18 @@ impl< Algo: super::typed_owned::Hash + Copy + PartialEq, > Hash for Algo { - fn digest_len(&self) -> usize { - DIGEST_LEN + 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<(), super::slice::HashError> { - if self.digest_len() != digest.digest.len() { - return Err(todo!()); + 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(|_| super::slice::HashError::Unknown)?; + let digest: &mut [u8; DIGEST_LEN] = + digest.as_mut().try_into().map_err(|_| HashError::Unknown)?; - ::hash(digest.into(), payload) - .map_err(super::slice::HashError::from) + ::hash(digest.into(), payload).map_err(HashError::from) } } From cdee4c7bf7b57ecb9393fed4c9ef6dd79af07e93 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Thu, 11 Sep 2025 16:28:33 +0200 Subject: [PATCH 12/16] separate Blake2 implementations into const digest size and runtime digest size --- blake2/src/impl_digest_trait.rs | 80 ++++++++++++++++++++++++----- blake2/src/lib.rs | 4 +- blake2/tests/blake2.rs | 13 ++++- crates/primitives/digest/src/lib.rs | 5 ++ 4 files changed, 88 insertions(+), 14 deletions(-) diff --git a/blake2/src/impl_digest_trait.rs b/blake2/src/impl_digest_trait.rs index 92786ed30..a0ad5fd79 100644 --- a/blake2/src/impl_digest_trait.rs +++ b/blake2/src/impl_digest_trait.rs @@ -4,10 +4,51 @@ use libcrux_traits::digest::{ consts::HashConsts, slice, typed_owned::{impl_digest_incremental_typed_owned, impl_hash_typed_owned}, - DigestIncrementalBase, Hasher, InitializeError, UpdateError, + typed_refs, DigestIncrementalBase, Hasher, InitializeError, UpdateError, }; -macro_rules! impl_digest_traits { +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; @@ -92,35 +133,50 @@ macro_rules! impl_digest_traits { }; } +#[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. -#[derive(Clone, Copy, Default, PartialEq)] -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, 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. -#[derive(Clone, Copy, Default, PartialEq)] -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, 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 7c3913d46..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() { @@ -238,6 +241,14 @@ fn test_digest_traits_2s() { 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(); assert_eq!( diff --git a/crates/primitives/digest/src/lib.rs b/crates/primitives/digest/src/lib.rs index c3b6f02df..da84065f8 100644 --- a/crates/primitives/digest/src/lib.rs +++ b/crates/primitives/digest/src/lib.rs @@ -1,8 +1,13 @@ +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, }; } From 538920684e1b22c3fafa2b81226b2cc8e00b9261 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Thu, 11 Sep 2025 16:29:49 +0200 Subject: [PATCH 13/16] implement multiplexed `Hash` --- crates/primitives/digest/src/multiplexed.rs | 180 ++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 crates/primitives/digest/src/multiplexed.rs diff --git a/crates/primitives/digest/src/multiplexed.rs b/crates/primitives/digest/src/multiplexed.rs new file mode 100644 index 000000000..220b4ef8d --- /dev/null +++ b/crates/primitives/digest/src/multiplexed.rs @@ -0,0 +1,180 @@ +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); +} From 1d74fd0d0dd8d2f5335c412b27e7c84778041d22 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Thu, 11 Sep 2025 16:52:14 +0200 Subject: [PATCH 14/16] add `new_digest()` method --- traits/src/digest/typed_refs.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/traits/src/digest/typed_refs.rs b/traits/src/digest/typed_refs.rs index ece61fb18..49d6ecc43 100644 --- a/traits/src/digest/typed_refs.rs +++ b/traits/src/digest/typed_refs.rs @@ -63,6 +63,10 @@ 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< From eaa63ace70705a0c5d8f4b34d44718a31aee5c84 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Thu, 11 Sep 2025 16:52:26 +0200 Subject: [PATCH 15/16] derive `Debug` --- traits/src/digest/typed_refs.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/traits/src/digest/typed_refs.rs b/traits/src/digest/typed_refs.rs index 49d6ecc43..6ac04af38 100644 --- a/traits/src/digest/typed_refs.rs +++ b/traits/src/digest/typed_refs.rs @@ -1,3 +1,4 @@ +#[derive(Debug)] pub struct DigestMut<'a, Algo> { algorithm: Algo, digest: &'a mut [u8], From 49b2f211f3e1685e823ff58e0c4a09fb17050dd9 Mon Sep 17 00:00:00 2001 From: wysiwys Date: Thu, 11 Sep 2025 16:52:55 +0200 Subject: [PATCH 16/16] add tests for multiplexed hash --- crates/primitives/digest/src/multiplexed.rs | 57 +++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/crates/primitives/digest/src/multiplexed.rs b/crates/primitives/digest/src/multiplexed.rs index 220b4ef8d..0982c3f07 100644 --- a/crates/primitives/digest/src/multiplexed.rs +++ b/crates/primitives/digest/src/multiplexed.rs @@ -178,3 +178,60 @@ mod sha3_impl { 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 + } +}