miden_stateful_hasher/lib.rs
1//! Stateful sponge-like hashers for cryptographic hashing.
2//!
3//! This crate provides the [`StatefulHasher`] trait and implementations that maintain
4//! an evolving state during hashing. This interface is used by commitment schemes and
5//! Merkle trees to incrementally absorb data and squeeze out digests.
6//!
7//! # Implementations
8//!
9//! - [`StatefulSponge`]: Wraps a permutation with proper sponge semantics
10//! - [`SerializingStatefulSponge`]: Serializes field elements to binary before absorbing
11//! - [`ChainingHasher`]: Uses chaining mode `H(state || input)` with a regular hasher
12//! - [`TruncatingHasher`]: Wraps a hasher and returns a shorter fixed digest (prefix)
13
14#![no_std]
15
16#[cfg(test)]
17extern crate alloc;
18
19mod chaining;
20mod field_sponge;
21mod serializing_sponge;
22mod truncating;
23
24#[cfg(test)]
25pub mod testing;
26
27pub use chaining::*;
28pub use field_sponge::*;
29pub use serializing_sponge::*;
30pub use truncating::*;
31
32/// Trait for stateful sponge-like hashers.
33///
34/// A stateful hasher maintains an external state value that evolves as input is
35/// absorbed, and from which fixed-size outputs can be squeezed. This interface
36/// is used pervasively by commitment schemes and Merkle trees to incrementally
37/// absorb rows of matrices and later read out the final digest.
38///
39/// # Alignment
40///
41/// Alignment semantics are defined by the separate [`Alignable`] trait.
42/// Types implementing `StatefulHasher` typically also implement `Alignable`
43/// to expose their alignment characteristics. Callers needing alignment
44/// information should require `H: StatefulHasher<...> + Alignable<...>`.
45pub trait StatefulHasher<Item, Out>: Clone {
46 /// The internal state type that evolves during absorption.
47 type State;
48
49 /// Absorb elements into the state with overwrite-mode and zero-padding semantics if applicable.
50 fn absorb_into(&self, state: &mut Self::State, input: impl IntoIterator<Item = Item>);
51
52 /// Squeeze an output from the current state.
53 fn squeeze(&self, state: &Self::State) -> Out;
54
55 /// One-shot hash of multiple row slices.
56 ///
57 /// Creates a fresh state, absorbs all rows, and squeezes the result.
58 fn hash_rows<'a>(&self, rows: impl IntoIterator<Item = &'a [Item]>) -> Out
59 where
60 Item: Copy + 'a,
61 Self::State: Default,
62 {
63 let mut state = Self::State::default();
64 for row in rows {
65 self.absorb_into(&mut state, row.iter().copied());
66 }
67 self.squeeze(&state)
68 }
69}
70
71/// Defines alignment for stateful hashers.
72///
73/// `ALIGNMENT` is the maximum number of "virtual zero input elements" that could
74/// be added due to padding. Padding always uses `Default::default()` (zero).
75///
76/// - `ALIGNMENT = 1` means no padding (always aligned)
77/// - `ALIGNMENT = N` means up to `N-1` virtual zero elements could be added
78///
79/// # Type Parameters
80///
81/// - `Input`: The type being absorbed (e.g., field element `F`)
82/// - `Target`: The type underlying the hasher's state. For a field-native sponge this equals
83/// `Input` (e.g., `Alignable<F, F>`); for a serializing sponge it is the binary word type of the
84/// inner hasher (e.g., `u32`, `u64`)
85///
86/// The two type parameters allow distinguishing between different serialization
87/// targets. For example, `SerializingStatefulSponge` can implement both
88/// `Alignable<F, u32>` and `Alignable<F, u64>` with different alignment values.
89///
90/// # Examples
91///
92/// - A field-native sponge with rate `R` implements `Alignable<T, T>` with `ALIGNMENT = R`
93/// - A chaining hasher implements `Alignable<F, Target>` with `ALIGNMENT = 1` (no padding)
94/// - A serializing wrapper implements `Alignable<F, u32>` and `Alignable<F, u64>` with alignment
95/// derived from field size and inner hasher's alignment
96pub trait Alignable<Input, Target> {
97 /// The alignment width in units of `Input`.
98 ///
99 /// This represents the maximum number of virtual zero input elements that
100 /// could be added due to padding when absorbing input.
101 const ALIGNMENT: usize;
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::testing::{MockBinaryHasher, MockBinaryPermutation};
108
109 /// Compile-time verification that all StatefulHasher implementations
110 /// satisfy their generic bounds with mock types.
111 #[test]
112 fn types_instantiate() {
113 // Native sponge: T -> [T; WIDTH] -> [T; OUT]
114 let _sponge = StatefulSponge::<_, 8, 4, 2>::new(MockBinaryPermutation::<u64, 8>::default());
115 let _: [u64; 8] = Default::default();
116
117 // Serializing sponge: F -> [binary; WIDTH] -> [binary; OUT]
118 let inner = StatefulSponge::<_, 8, 4, 2>::new(MockBinaryPermutation::<u64, 8>::default());
119 let _serializing: SerializingStatefulSponge<_> = SerializingStatefulSponge::new(inner);
120 let _: [u64; 8] = Default::default();
121
122 // Chaining hasher: F -> [binary; N] (digest = state)
123 let _chaining: ChainingHasher<MockBinaryHasher> = ChainingHasher::new(MockBinaryHasher);
124 let _: [u64; 4] = Default::default();
125 }
126}