sponge_hash_aes256/sponge_hash.rs
1// SPDX-License-Identifier: 0BSD
2// SpongeHash-AES256
3// Copyright (C) 2025-2026 by LoRd_MuldeR <mulder2@gmx.de>
4
5use crate::utilities::{length, Aes256Crypto, BlockType, BLOCK_SIZE};
6use core::ops::Range;
7
8/// Default digest size, in bytes
9///
10/// The default digest size is currently defined as **32** bytes, i.e., **256** bits.
11pub const DEFAULT_DIGEST_SIZE: usize = 2usize * BLOCK_SIZE;
12
13/// Default number of permutation rounds to be performed
14///
15/// The default number of permutation rounds is currently defined as **1**.
16pub const DEFAULT_PERMUTE_ROUNDS: usize = 1usize;
17
18/// Pre-define round keys
19static ROUND_KEY_X: BlockType = BlockType::new::<0x5Cu8>();
20static ROUND_KEY_Y: BlockType = BlockType::new::<0x36u8>();
21static ROUND_KEY_Z: BlockType = BlockType::new::<0x6Au8>();
22
23// ---------------------------------------------------------------------------
24// Tracing
25// ---------------------------------------------------------------------------
26
27#[cfg(feature = "tracing")]
28macro_rules! trace {
29 ($self:tt, $arg:tt) => {
30 log::trace!("SpongeHash256@{:p}: {} --> {:02X?} {:02X?} {:02X?}", &$self, $arg, &$self.state.0, &$self.state.1, &$self.state.2);
31 };
32}
33
34#[cfg(not(feature = "tracing"))]
35macro_rules! trace {
36 ($self:tt, $arg:tt) => {};
37}
38
39// ---------------------------------------------------------------------------
40// Non-zero argument constraint
41// ---------------------------------------------------------------------------
42
43/// Validates that the const generic parameter is non-zero
44struct NoneZeroArg<const N: usize>;
45
46impl<const N: usize> NoneZeroArg<N> {
47 const OK: () = assert!(N > 0, "Const generic argument must be a non-zero value!");
48}
49
50// ---------------------------------------------------------------------------
51// Scratch buffer
52// ---------------------------------------------------------------------------
53
54/// Encapsulates the temporary computation state.
55#[repr(align(32))]
56struct Scratch {
57 aes256: Aes256Crypto,
58 temp: (BlockType, BlockType, BlockType),
59}
60
61impl Default for Scratch {
62 fn default() -> Self {
63 Self { aes256: Aes256Crypto::default(), temp: (BlockType::uninit(), BlockType::uninit(), BlockType::uninit()) }
64 }
65}
66
67// ---------------------------------------------------------------------------
68// Streaming API
69// ---------------------------------------------------------------------------
70
71/// This struct encapsulates the state for a “streaming” (incremental) SpongeHash-AES256 computation.
72///
73/// The const generic parameter `R` specifies the number of permutation rounds to be performed, which must be a *positive* value. The default number of permutation rounds is given by [`DEFAULT_PERMUTE_ROUNDS`]. Using a greater value slows down the hash calculation, which helps to increase the security in some usage scenarios, e.g., password hashing.
74///
75/// ### Usage Example
76///
77/// The easiest way to use the **`SpongeHash256`** structure is as follows:
78///
79/// ```rust
80/// use hex::encode_to_slice;
81/// use sponge_hash_aes256::{DEFAULT_DIGEST_SIZE, SpongeHash256};
82///
83/// fn main() {
84/// // Create new hash instance
85/// let mut hash = SpongeHash256::default();
86///
87/// // Process message
88/// hash.update(b"The quick brown fox jumps over the lazy dog");
89///
90/// // Retrieve the final digest
91/// let digest = hash.digest::<DEFAULT_DIGEST_SIZE>();
92///
93/// // Encode to hex
94/// let mut hex_buffer = [0u8; 2usize * DEFAULT_DIGEST_SIZE];
95/// encode_to_slice(&digest, &mut hex_buffer).unwrap();
96///
97/// // Print the digest (hex format)
98/// println!("0x{}", core::str::from_utf8(&hex_buffer).unwrap());
99/// }
100/// ```
101///
102/// ### Context information
103///
104/// Optionally, additional “context” information may be provided via the `info` parameter:
105///
106/// ```rust
107/// use sponge_hash_aes256::{DEFAULT_DIGEST_SIZE, SpongeHash256};
108///
109/// fn main() {
110/// // Create new hash instance with “info”
111/// let mut hash: SpongeHash256 = SpongeHash256::with_info("my_application");
112///
113/// /* ... */
114/// }
115/// ```
116///
117/// ### Important note
118///
119/// <div class="warning">
120///
121/// The [`compute()`] and [`compute_to_slice()`] convenience functions may be used as an alternative to working with the `SpongeHash256` struct directly. This is especially useful, if *all* data to be hashed is available at once.
122///
123/// </div>
124///
125/// ### Algorithm
126///
127/// This section provides additional details about the SpongeHash-AES256 algorithm.
128///
129/// #### Internal state
130///
131/// The state has a total size of 384 bits, consisting of three 128-bit blocks, and is initialized to all zeros at the start of the computation. Only the upper 128 bits are directly used for input and output operations, as described below.
132///
133/// #### Update function
134///
135/// The “update” function, which *absorbs* input blocks into the state and *squeezes* the corresponding output from it, is defined as follows, where `input[i]` denotes the *i*-th input block and `output[k]` the *k*-th output block:
136///
137/// 
138///
139/// #### Permutation function
140///
141/// The “permutation” function, applied to scramble the state after each absorbing or squeezing step, is defined as follows, where `AES-256` denotes the ordinary [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) block cipher with a key size of 256 bits and a block size of 128 bits.
142///
143/// 
144///
145/// The constants `const_0` and `const_1` are defined as full blocks filled with `0x5C` and `0x36`, respectively.
146///
147/// ### Finalization
148///
149/// The padding of the final input block is performed by first appending a single `1` bit, followed by the minimal number of `0` bits needed to make the total message length a multiple of the block size.
150///
151/// Following the final input block, a 128-bit block filled entirely with `0x6A` bytes is absorbed into the state.
152#[repr(align(32))]
153#[derive(Clone, Debug)]
154pub struct SpongeHash256<const R: usize = DEFAULT_PERMUTE_ROUNDS> {
155 state: (BlockType, BlockType, BlockType),
156 offset: usize,
157}
158
159impl<const R: usize> SpongeHash256<R> {
160 /// Creates a new SpongeHash-AES256 instance and initializes the hash computation.
161 ///
162 /// **Note:** This function implies an *empty* [`info`](Self::with_info()) string.
163 #[inline]
164 pub fn new() -> Self {
165 Self::with_info(Default::default())
166 }
167
168 /// Creates a new SpongeHash-AES256 instance and initializes the hash computation with the given `info` string.
169 ///
170 /// **Note:** The length of the `info` string **must not** exceed a length of 255 characters!
171 #[inline]
172 pub fn with_info(info: &str) -> Self {
173 let () = NoneZeroArg::<R>::OK;
174 let mut hash = Self { state: (BlockType::zero(), BlockType::zero(), BlockType::zero()), offset: 0usize };
175 hash.initialize(info.as_bytes());
176 hash
177 }
178
179 /// Initializes the internal state with the given `info` string
180 #[inline]
181 fn initialize(&mut self, info_data: &[u8]) {
182 trace!(self, "initlz::enter");
183
184 match info_data.len().try_into() {
185 Ok(length) => {
186 self.update(u8::to_be_bytes(length));
187 self.update(info_data);
188 }
189 Err(_) => panic!("Info length exceeds the allowable maximum!"),
190 };
191
192 trace!(self, "initlz::leave");
193 }
194
195 /// Processes the next chunk of the message, as given by the `chunk` parameter.
196 ///
197 /// A `chunk` can be of *any* type that implements the [`AsRef<[u8]>`](AsRef<T>) trait, e.g., `&[u8]`, `&str` or `String`.
198 ///
199 /// The internal state of the hash computation is updated by this function.
200 #[inline]
201 pub fn update<T: AsRef<[u8]>>(&mut self, chunk: T) {
202 trace!(self, "update::enter");
203
204 let source = chunk.as_ref().as_ptr_range();
205 if !source.is_empty() {
206 unsafe {
207 self.update_range(source);
208 }
209 }
210
211 trace!(self, "update::leave");
212 }
213
214 /// Processes the next chunk of "raw" bytes, as specified by the [`Range<*const u8>`](slice::as_ptr_range) in the `source` parameter.
215 ///
216 /// The internal state of the hash computation is updated by this function.
217 ///
218 /// # Safety
219 ///
220 /// The caller **must** ensure that *all* byte addresses in the range from `source.start` up to but excluding `source.end` are valid!
221 #[inline]
222 pub unsafe fn update_range(&mut self, source: Range<*const u8>) {
223 let mut source_next = source.start;
224 let mut scratch_buffer = Scratch::default();
225
226 while (self.offset != 0usize) && (source_next < source.end) {
227 self.state.0[self.offset] ^= *source_next;
228 self.offset += 1usize;
229 source_next = source_next.add(1usize);
230
231 if self.offset >= BLOCK_SIZE {
232 self.permute(&mut scratch_buffer);
233 self.offset = 0usize;
234 }
235 }
236
237 if source_next < source.end {
238 debug_assert_eq!(self.offset, 0usize);
239
240 while length(source_next, source.end) >= BLOCK_SIZE {
241 self.state.0.xor_with_u8_ptr(source_next);
242 self.permute(&mut scratch_buffer);
243 source_next = source_next.add(BLOCK_SIZE);
244 }
245
246 while source_next < source.end {
247 self.state.0[self.offset] ^= *source_next;
248 self.offset += 1usize;
249 source_next = source_next.add(1usize);
250 }
251 }
252
253 debug_assert!(self.offset < BLOCK_SIZE);
254 }
255
256 /// Concludes the hash computation and returns the final digest.
257 ///
258 /// The hash value (digest) of the concatenation of all processed message chunks is returned as an new array of size `N`.
259 ///
260 /// The returned array is filled completely, generating a hash value (digest) of the appropriate size.
261 ///
262 /// **Note:** The digest output size `N`, in bytes, must be a *positive* value! 🚨
263 pub fn digest<const N: usize>(self) -> [u8; N] {
264 let () = NoneZeroArg::<N>::OK;
265 let mut digest = [0u8; N];
266 self.digest_to_slice(&mut digest);
267 digest
268 }
269
270 /// Concludes the hash computation and returns the final digest.
271 ///
272 /// The hash value (digest) of the concatenation of all processed message chunks is written into the slice `digest_out`.
273 ///
274 /// The output slice is filled completely, generating a hash value (digest) of the appropriate size.
275 ///
276 /// **Note:** The specified digest output size, i.e., `digest_out.len()`, in bytes, must be a *positive* value! 🚨
277 pub fn digest_to_slice(mut self, digest_out: &mut [u8]) {
278 trace!(self, "digest::enter");
279 assert!(!digest_out.is_empty(), "Digest output size must be positive!");
280
281 let mut scratch_buffer = Scratch::default();
282
283 self.state.0[self.offset] ^= 0x80u8;
284 self.permute(&mut scratch_buffer);
285 self.state.0.xor_with(&ROUND_KEY_Z);
286
287 let mut pos = 0usize;
288
289 while pos < digest_out.len() {
290 self.permute(&mut scratch_buffer);
291 let copy_len = BLOCK_SIZE.min(digest_out.len() - pos);
292 digest_out[pos..(pos + copy_len)].copy_from_slice(&self.state.0[..copy_len]);
293 pos += copy_len;
294 }
295
296 trace!(self, "digest::leave");
297 }
298
299 /// Pseudorandom permutation, based on the AES-256 block cipher
300 #[inline]
301 fn permute(&mut self, work: &mut Scratch) {
302 trace!(self, "permfn::enter");
303
304 for _ in 0..R {
305 work.aes256.encrypt(&mut work.temp.0, &self.state.0, &self.state.1, &self.state.2);
306 work.aes256.encrypt(&mut work.temp.1, &self.state.1, &self.state.2, &self.state.0);
307 work.aes256.encrypt(&mut work.temp.2, &self.state.2, &self.state.0, &self.state.1);
308
309 self.state.0.xor_with(&work.temp.0);
310 self.state.1.xor_with(&work.temp.1);
311 self.state.2.xor_with(&work.temp.2);
312
313 self.state.1.xor_with(&ROUND_KEY_X);
314 self.state.2.xor_with(&ROUND_KEY_Y);
315 }
316
317 trace!(self, "permfn::leave");
318 }
319}
320
321impl Default for SpongeHash256 {
322 #[inline]
323 fn default() -> Self {
324 Self::new()
325 }
326}
327
328// ---------------------------------------------------------------------------
329// One-Shot API
330// ---------------------------------------------------------------------------
331
332/// Convenience function for “one-shot” SpongeHash-AES256 computation
333///
334/// The hash value (digest) of the given `message` is returned as an new array of type `[u8; N]`.
335///
336/// A `message` can be of *any* type that implements the [`AsRef<[u8]>`](AsRef<T>) trait, e.g., `&[u8]`, `&str` or `String`.
337///
338/// Optionally, an additional `info` string may be specified.
339///
340/// The returned array is filled completely, generating a hash value (digest) of the appropriate size.
341///
342/// This function uses the default number of permutation rounds, as is given by [`DEFAULT_PERMUTE_ROUNDS`].
343///
344/// **Note:** The digest output size `N`, in bytes, must be a *positive* value! 🚨
345///
346/// ### Usage Example
347///
348/// The **`compute()`** function can be used as follows:
349///
350/// ```rust
351/// use hex::encode_to_slice;
352/// use sponge_hash_aes256::{DEFAULT_DIGEST_SIZE, compute};
353///
354/// fn main() {
355/// // Compute the digest using the “one-shot” function
356/// let digest: [u8; DEFAULT_DIGEST_SIZE] = compute(
357/// None,
358/// b"The quick brown fox jumps over the lazy dog");
359///
360/// // Encode to hex
361/// let mut hex_buffer = [0u8; 2usize * DEFAULT_DIGEST_SIZE];
362/// encode_to_slice(&digest, &mut hex_buffer).unwrap();
363///
364/// // Print the digest (hex format)
365/// println!("0x{}", core::str::from_utf8(&hex_buffer).unwrap());
366/// }
367/// ```
368///
369/// ### Context information
370///
371/// Optionally, additional “context” information may be provided via the `info` parameter:
372///
373/// ```rust
374/// use sponge_hash_aes256::{DEFAULT_DIGEST_SIZE, compute};
375///
376/// fn main() {
377/// // Compute the digest using the “one-shot” function with additional “info”
378/// let digest: [u8; DEFAULT_DIGEST_SIZE] = compute(
379/// Some("my_application"),
380/// b"The quick brown fox jumps over the lazy dog");
381/// /* ... */
382/// }
383/// ```
384///
385/// ### Important note
386///
387/// <div class="warning">
388///
389/// Applications that need to process *large* messages are recommended to use the [streaming API](SpongeHash256), which does **not** require *all* message data to be held in memory at once and which allows for an *incremental* hash computation.
390///
391/// </div>
392pub fn compute<const N: usize, T: AsRef<[u8]>>(info: Option<&str>, message: T) -> [u8; N] {
393 assert!(!info.is_some_and(str::is_empty), "Info must not be empty!");
394 let mut state: SpongeHash256 = SpongeHash256::with_info(info.unwrap_or_default());
395 state.update(message);
396 state.digest()
397}
398
399/// Convenience function for “one-shot” SpongeHash-AES256 computation
400///
401/// The hash value (digest) of the given `message` is written into the slice `digest_out`.
402///
403/// A `message` can be of *any* type that implements the [`AsRef<[u8]>`](AsRef<T>) trait, e.g., `&[u8]`, `&str` or `String`.
404///
405/// Optionally, an additional `info` string may be specified.
406///
407/// The output slice is filled completely, generating a hash value (digest) of the appropriate size.
408///
409/// This function uses the default number of permutation rounds, as is given by [`DEFAULT_PERMUTE_ROUNDS`].
410///
411/// **Note:** The digest output size, i.e., `digest_out.len()`, in bytes, must be a *positive* value! 🚨
412///
413/// ### Usage Example
414///
415/// The **`compute_to_slice()`** function can be used as follows:
416///
417/// ```rust
418/// use hex::encode_to_slice;
419/// use sponge_hash_aes256::{DEFAULT_DIGEST_SIZE, compute_to_slice};
420///
421/// fn main() {
422/// // Compute digest using the “one-shot” function
423/// let mut digest = [0u8; DEFAULT_DIGEST_SIZE];
424/// compute_to_slice(&mut digest, None, b"The quick brown fox jumps over the lazy dog");
425///
426/// // Encode to hex
427/// let mut hex_buffer = [0u8; 2usize * DEFAULT_DIGEST_SIZE];
428/// encode_to_slice(&digest, &mut hex_buffer).unwrap();
429///
430/// // Print the digest (hex format)
431/// println!("0x{}", core::str::from_utf8(&hex_buffer).unwrap());
432/// }
433///
434/// ```
435/// ### Context information
436///
437/// Optionally, additional “context” information may be provided via the `info` parameter:
438///
439/// ```rust
440/// use sponge_hash_aes256::{DEFAULT_DIGEST_SIZE, compute_to_slice};
441///
442/// fn main() {
443/// // Compute digest using the “one-shot” function with additional “info”
444/// let mut digest = [0u8; DEFAULT_DIGEST_SIZE];
445/// compute_to_slice(
446/// &mut digest,
447/// Some("my_application"),
448/// b"The quick brown fox jumps over the lazy dog");
449/// /* ... */
450/// }
451/// ```
452///
453/// ### Important note
454///
455/// <div class="warning">
456///
457/// Applications that need to process *large* messages are recommended to use the [streaming API](SpongeHash256), which does **not** require *all* message data to be held in memory at once and which allows for an *incremental* hash computation.
458///
459/// </div>
460pub fn compute_to_slice<T: AsRef<[u8]>>(digest_out: &mut [u8], info: Option<&str>, message: T) {
461 assert!(!info.is_some_and(str::is_empty), "Info must not be empty!");
462 let mut state: SpongeHash256 = SpongeHash256::with_info(info.unwrap_or_default());
463 state.update(message);
464 state.digest_to_slice(digest_out);
465}