From 76d1605626e58291e9dbdefb251fc84bf5b2dda4 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Wed, 5 Nov 2025 14:12:16 -0700 Subject: [PATCH] Revert "digest: remove `VariableOutput` trait (#2043)" This reverts commit b0d40cd1ae3f4adf07769436e661434f6333816c. This is used by `argon2` --- digest/CHANGELOG.md | 3 - digest/src/block_api.rs | 6 - digest/src/block_api/ct_variable.rs | 6 +- digest/src/buffer_macros.rs | 3 +- .../{variable.rs => variable_ct.rs} | 0 digest/src/buffer_macros/variable_rt.rs | 188 ++++++++++++++++++ digest/src/dev.rs | 2 + digest/src/lib.rs | 85 +++++++- 8 files changed, 277 insertions(+), 16 deletions(-) rename digest/src/buffer_macros/{variable.rs => variable_ct.rs} (100%) create mode 100644 digest/src/buffer_macros/variable_rt.rs diff --git a/digest/CHANGELOG.md b/digest/CHANGELOG.md index 22d55f912..5e2f14e17 100644 --- a/digest/CHANGELOG.md +++ b/digest/CHANGELOG.md @@ -8,7 +8,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 0.11.0 (UNRELEASED) ### Added - `CustomizedInit` trait ([#1334]) -- `VariableOutputCoreCustomized` trait ([#1787], [#2043]) - `buffer_fixed`, `buffer_ct_variable`, `buffer_rt_variable`, and `buffer_xof` macros ([#1799]) - `CollisionResistance` trait ([#1820]) @@ -25,7 +24,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `CoreWrapper`, `RtVariableCoreWrapper`, and `XofReaderCoreWrapper` types ([#1799]) - `HashReader` and `HashWriter` are moved to the `digest-io` crate ([#1809]) - `io::Write/Read` implementations in favor of the `digest_io::IoWrapper` type ([#1809]) -- `VariableOutput` trait ([#2043]) [#1173]: https://github.com/RustCrypto/traits/pull/1173 [#1334]: https://github.com/RustCrypto/traits/pull/1334 @@ -35,7 +33,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#1820]: https://github.com/RustCrypto/traits/pull/1820 [#1953]: https://github.com/RustCrypto/traits/pull/1953 [#1958]: https://github.com/RustCrypto/traits/pull/1958 -[#2043]: https://github.com/RustCrypto/traits/pull/2043 ## 0.10.7 (2023-05-19) ### Changed diff --git a/digest/src/block_api.rs b/digest/src/block_api.rs index 472220f0e..56dda9cb5 100644 --- a/digest/src/block_api.rs +++ b/digest/src/block_api.rs @@ -113,12 +113,6 @@ pub trait VariableOutputCore: UpdateCore + OutputSizeUser + BufferKindUser + Siz fn finalize_variable_core(&mut self, buffer: &mut Buffer, out: &mut Output); } -/// Trait adding customization string to hash functions with variable output. -pub trait VariableOutputCoreCustomized: VariableOutputCore { - /// Create new hasher instance with the given customization string and output size. - fn new_customized(customization: &[u8], output_size: usize) -> Self; -} - /// Type which used for defining truncation side in the [`VariableOutputCore`] /// trait. #[derive(Copy, Clone, Debug)] diff --git a/digest/src/block_api/ct_variable.rs b/digest/src/block_api/ct_variable.rs index 813a97fe2..0e6459932 100644 --- a/digest/src/block_api/ct_variable.rs +++ b/digest/src/block_api/ct_variable.rs @@ -1,10 +1,10 @@ use super::{ AlgorithmName, Buffer, BufferKindUser, FixedOutputCore, Reset, TruncSide, UpdateCore, - VariableOutputCore, VariableOutputCoreCustomized, + VariableOutputCore, }; #[cfg(feature = "mac")] use crate::MacMarker; -use crate::{CollisionResistance, CustomizedInit, HashMarker}; +use crate::{CollisionResistance, CustomizedInit, HashMarker, VarOutputCustomized}; use core::{fmt, marker::PhantomData}; use crypto_common::{ Block, BlockSizeUser, OutputSizeUser, @@ -120,7 +120,7 @@ where impl CustomizedInit for CtOutWrapper where - T: VariableOutputCoreCustomized, + T: VariableOutputCore + VarOutputCustomized, OutSize: ArraySize + IsLessOrEqual, { #[inline] diff --git a/digest/src/buffer_macros.rs b/digest/src/buffer_macros.rs index b0412f4fe..f03fec8d8 100644 --- a/digest/src/buffer_macros.rs +++ b/digest/src/buffer_macros.rs @@ -1,3 +1,4 @@ mod fixed; -mod variable; +mod variable_ct; +mod variable_rt; mod xof; diff --git a/digest/src/buffer_macros/variable.rs b/digest/src/buffer_macros/variable_ct.rs similarity index 100% rename from digest/src/buffer_macros/variable.rs rename to digest/src/buffer_macros/variable_ct.rs diff --git a/digest/src/buffer_macros/variable_rt.rs b/digest/src/buffer_macros/variable_rt.rs new file mode 100644 index 000000000..e39ff5adb --- /dev/null +++ b/digest/src/buffer_macros/variable_rt.rs @@ -0,0 +1,188 @@ +/// Creates a buffered wrapper around block-level "core" type which implements variable output size traits +/// with output size selected at run time. +#[macro_export] +macro_rules! buffer_rt_variable { + ( + $(#[$attr:meta])* + $vis:vis struct $name:ident($core_ty:ty); + exclude: SerializableState; + ) => { + $(#[$attr])* + $vis struct $name { + core: $core_ty, + buffer: $crate::block_api::Buffer<$core_ty>, + output_size: u8, + } + + impl core::fmt::Debug for $name { + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(concat!(stringify!($name), " { ... }")) + } + } + + impl $crate::crypto_common::AlgorithmName for $name { + #[inline] + fn write_alg_name(f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + <$core_ty as $crate::crypto_common::AlgorithmName>::write_alg_name(f) + } + } + + impl Clone for $name { + #[inline] + fn clone(&self) -> Self { + Self { + core: Clone::clone(&self.core), + buffer: Clone::clone(&self.buffer), + output_size: self.output_size, + } + } + } + + impl $crate::Reset for $name { + #[inline] + fn reset(&mut self) { + let size = self.output_size.into(); + self.core = <$core_ty as $crate::block_api::VariableOutputCore>::new(size).unwrap(); + self.buffer.reset(); + } + } + + impl $crate::block_api::BlockSizeUser for $name { + type BlockSize = <$core_ty as $crate::crypto_common::BlockSizeUser>::BlockSize; + } + + impl $crate::HashMarker for $name {} + + // Verify that `$wrapped_ty` implements `HashMarker` + const _: () = { + fn check(v: &$core_ty) { + v as &dyn $crate::HashMarker; + } + }; + + impl $crate::Update for $name { + #[inline] + fn update(&mut self, data: &[u8]) { + let Self { core, buffer, .. } = self; + buffer.digest_blocks(data, |blocks| { + $crate::block_api::UpdateCore::update_blocks(core, blocks); + }); + } + } + + impl $name { + #[inline] + fn finalize_dirty(&mut self, out: &mut [u8]) -> Result<(), $crate::InvalidBufferSize> { + let Self { + core, + buffer, + output_size, + } = self; + let size_u8 = u8::try_from(out.len()).map_err(|_| $crate::InvalidBufferSize)?; + + let max_size = ::MAX_OUTPUT_SIZE; + if out.len() > max_size || size_u8 != *output_size { + return Err($crate::InvalidBufferSize); + } + let mut full_res = Default::default(); + $crate::block_api::VariableOutputCore::finalize_variable_core(core, buffer, &mut full_res); + let n = out.len(); + let m = full_res.len() - n; + use $crate::block_api::TruncSide::{Left, Right}; + let side = <$core_ty as $crate::block_api::VariableOutputCore>::TRUNC_SIDE; + match side { + Left => out.copy_from_slice(&full_res[..n]), + Right => out.copy_from_slice(&full_res[m..]), + } + Ok(()) + } + } + + impl $crate::VariableOutput for $name { + const MAX_OUTPUT_SIZE: usize = < + <$core_ty as $crate::block_api::OutputSizeUser>::OutputSize + as $crate::typenum::Unsigned + >::USIZE; + + #[inline] + fn new(output_size: usize) -> Result { + let output_size = u8::try_from(output_size).map_err(|_| $crate::InvalidOutputSize)?; + let buffer = Default::default(); + let core = <$core_ty as $crate::block_api::VariableOutputCore>::new(output_size.into())?; + Ok(Self { + core, + buffer, + output_size, + }) + } + + #[inline] + fn output_size(&self) -> usize { + self.output_size.into() + } + + #[inline] + fn finalize_variable(mut self, out: &mut [u8]) -> Result<(), $crate::InvalidBufferSize> { + self.finalize_dirty(out) + } + } + + impl $crate::VariableOutputReset for $name { + #[inline] + fn finalize_variable_reset( + &mut self, + out: &mut [u8], + ) -> Result<(), $crate::InvalidBufferSize> { + self.finalize_dirty(out)?; + $crate::Reset::reset(self); + Ok(()) + } + } + + impl Drop for $name { + #[inline] + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + { + use $crate::zeroize::Zeroize; + self.buffer.zeroize(); + self.output_size.zeroize(); + } + } + } + + #[cfg(feature = "zeroize")] + impl $crate::zeroize::ZeroizeOnDrop for $name {} + }; + + ( + $(#[$attr:meta])* + $vis:vis struct $name:ident($core_ty:ty); + ) => { + $crate::buffer_rt_variable!( + $(#[$attr])* + $vis struct $name($core_ty); + exclude: SerializableState; + ); + + impl $crate::crypto_common::hazmat::SerializableState for $name { + type SerializedStateSize = $crate::typenum::Add1<$crate::typenum::Sum< + <$core_ty as $crate::crypto_common::hazmat::SerializableState>::SerializedStateSize, + <$core_ty as $crate::block_api::BlockSizeUser>::BlockSize, + >>; + + #[inline] + fn serialize(&self) -> $crate::crypto_common::hazmat::SerializedState { + todo!() + } + + #[inline] + fn deserialize( + serialized_state: &$crate::crypto_common::hazmat::SerializedState, + ) -> Result { + todo!() + } + } + }; +} diff --git a/digest/src/dev.rs b/digest/src/dev.rs index 217bb0f9a..da08409d5 100644 --- a/digest/src/dev.rs +++ b/digest/src/dev.rs @@ -6,11 +6,13 @@ mod fixed; #[cfg(feature = "mac")] mod mac; mod rng; +mod variable; mod xof; pub use fixed::*; #[cfg(feature = "mac")] pub use mac::*; +pub use variable::*; pub use xof::*; /// Test vector for hash functions diff --git a/digest/src/lib.rs b/digest/src/lib.rs index a198c8e24..8e1dfb314 100644 --- a/digest/src/lib.rs +++ b/digest/src/lib.rs @@ -6,9 +6,10 @@ //! - **High-level convenience traits**: [`Digest`], [`DynDigest`], [`Mac`]. //! Wrappers around lower-level traits for most common use-cases. Users should //! usually prefer using these traits. -//! - **Mid-level traits**: [`Update`], [`FixedOutput`], [`FixedOutputReset`], [`ExtendableOutput`], -//! [`ExtendableOutputReset`], [`XofReader`], [`Reset`], [`KeyInit`], and [`InnerInit`]. -//! These traits atomically describe available functionality of an algorithm. +//! - **Mid-level traits**: [`Update`], [`FixedOutput`], [`FixedOutputReset`], +//! [`ExtendableOutput`], [`ExtendableOutputReset`], [`XofReader`], +//! [`VariableOutput`], [`Reset`], [`KeyInit`], and [`InnerInit`]. These +//! traits atomically describe available functionality of an algorithm. //! - **Marker traits**: [`HashMarker`], [`MacMarker`]. Used to distinguish //! different algorithm classes. //! - **Low-level traits** defined in the [`block_api`] module. These traits @@ -206,12 +207,90 @@ pub trait ExtendableOutputReset: ExtendableOutput + Reset { } } +/// Trait for hash functions with variable-size output. +pub trait VariableOutput: Sized + Update { + /// Maximum size of output hash in bytes. + const MAX_OUTPUT_SIZE: usize; + + /// Create new hasher instance with the given output size in bytes. + /// + /// It will return `Err(InvalidOutputSize)` in case if hasher can not return + /// hash of the specified output size. + fn new(output_size: usize) -> Result; + + /// Get output size in bytes of the hasher instance provided to the `new` method + fn output_size(&self) -> usize; + + /// Write result into the output buffer. + /// + /// Returns `Err(InvalidOutputSize)` if `out` size is not equal to + /// `self.output_size()`. + fn finalize_variable(self, out: &mut [u8]) -> Result<(), InvalidBufferSize>; + + /// Compute hash of `data` and write it to `output`. + /// + /// Length of the output hash is determined by `output`. If `output` is + /// bigger than `Self::MAX_OUTPUT_SIZE`, this method returns + /// `InvalidOutputSize`. + fn digest_variable( + input: impl AsRef<[u8]>, + output: &mut [u8], + ) -> Result<(), InvalidOutputSize> { + let mut hasher = Self::new(output.len())?; + hasher.update(input.as_ref()); + hasher + .finalize_variable(output) + .map_err(|_| InvalidOutputSize) + } + + /// Retrieve result into a boxed slice and consume hasher. + /// + /// `Box<[u8]>` is used instead of `Vec` to save stack space, since + /// they have size of 2 and 3 words respectively. + #[cfg(feature = "alloc")] + fn finalize_boxed(self) -> Box<[u8]> { + let n = self.output_size(); + let mut buf = vec![0u8; n].into_boxed_slice(); + self.finalize_variable(&mut buf) + .expect("buf length is equal to output_size"); + buf + } +} + +/// Trait for hash functions with variable-size output able to reset themselves. +pub trait VariableOutputReset: VariableOutput + Reset { + /// Write result into the output buffer and reset the hasher state. + /// + /// Returns `Err(InvalidOutputSize)` if `out` size is not equal to + /// `self.output_size()`. + fn finalize_variable_reset(&mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize>; + + /// Retrieve result into a boxed slice and reset the hasher state. + /// + /// `Box<[u8]>` is used instead of `Vec` to save stack space, since + /// they have size of 2 and 3 words respectively. + #[cfg(feature = "alloc")] + fn finalize_boxed_reset(&mut self) -> Box<[u8]> { + let n = self.output_size(); + let mut buf = vec![0u8; n].into_boxed_slice(); + self.finalize_variable_reset(&mut buf) + .expect("buf length is equal to output_size"); + buf + } +} + /// Trait for hash functions with customization string for domain separation. pub trait CustomizedInit: Sized { /// Create new hasher instance with the given customization string. fn new_customized(customization: &[u8]) -> Self; } +/// Trait adding customization string to hash functions with variable output. +pub trait VarOutputCustomized: Sized { + /// Create new hasher instance with the given customization string and output size. + fn new_customized(customization: &[u8], output_size: usize) -> Self; +} + /// Types with a certain collision resistance. pub trait CollisionResistance { /// Collision resistance in bytes.