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))]
153pub struct SpongeHash256<const R: usize = DEFAULT_PERMUTE_ROUNDS> {
154 state: (BlockType, BlockType, BlockType),
155 offset: usize,
156}
157
158impl<const R: usize> SpongeHash256<R> {
159 /// Creates a new SpongeHash-AES256 instance and initializes the hash computation.
160 ///
161 /// **Note:** This function implies an *empty* [`info`](Self::with_info()) string.
162 pub fn new() -> Self {
163 Self::with_info(Default::default())
164 }
165
166 /// Creates a new SpongeHash-AES256 instance and initializes the hash computation with the given `info` string.
167 ///
168 /// **Note:** The length of the `info` string **must not** exceed a length of 255 characters!
169 pub fn with_info(info: &str) -> Self {
170 let () = NoneZeroArg::<R>::OK;
171 let mut hash = Self { state: (BlockType::zero(), BlockType::zero(), BlockType::zero()), offset: 0usize };
172 hash.initialize(info.as_bytes());
173 hash
174 }
175
176 /// Initializes the internal state with the given `info` string
177 #[inline]
178 fn initialize(&mut self, info_data: &[u8]) {
179 trace!(self, "initlz::enter");
180
181 match info_data.len().try_into() {
182 Ok(length) => {
183 self.update(u8::to_be_bytes(length));
184 self.update(info_data);
185 }
186 Err(_) => panic!("Info length exceeds the allowable maximum!"),
187 };
188
189 trace!(self, "initlz::leave");
190 }
191
192 /// Processes the next chunk of the message, as given by the `chunk` parameter.
193 ///
194 /// A `chunk` can be of *any* type that implements the [`AsRef<[u8]>`](AsRef<T>) trait, e.g., `&[u8]`, `&str` or `String`.
195 ///
196 /// The internal state of the hash computation is updated by this function.
197 #[inline]
198 pub fn update<T: AsRef<[u8]>>(&mut self, chunk: T) {
199 trace!(self, "update::enter");
200
201 let source = chunk.as_ref().as_ptr_range();
202 if !source.is_empty() {
203 unsafe {
204 self.update_range(source);
205 }
206 }
207
208 trace!(self, "update::leave");
209 }
210
211 /// Processes the next chunk of "raw" bytes, as specified by the [`Range<*const u8>`](slice::as_ptr_range) in the `source` parameter.
212 ///
213 /// The internal state of the hash computation is updated by this function.
214 ///
215 /// # Safety
216 ///
217 /// The caller **must** ensure that *all* byte addresses in the range from `source.start` up to but excluding `source.end` are valid!
218 #[inline]
219 pub unsafe fn update_range(&mut self, source: Range<*const u8>) {
220 let mut source_next = source.start;
221 let mut scratch_buffer = Scratch::default();
222
223 while (self.offset != 0usize) && (source_next < source.end) {
224 self.state.0[self.offset] ^= *source_next;
225 self.offset += 1usize;
226 source_next = source_next.add(1usize);
227
228 if self.offset >= BLOCK_SIZE {
229 self.permute(&mut scratch_buffer);
230 self.offset = 0usize;
231 }
232 }
233
234 if source_next < source.end {
235 debug_assert_eq!(self.offset, 0usize);
236
237 while length(source_next, source.end) >= BLOCK_SIZE {
238 self.state.0.xor_with_u8_ptr(source_next);
239 self.permute(&mut scratch_buffer);
240 source_next = source_next.add(BLOCK_SIZE);
241 }
242
243 while source_next < source.end {
244 self.state.0[self.offset] ^= *source_next;
245 self.offset += 1usize;
246 source_next = source_next.add(1usize);
247 }
248 }
249
250 debug_assert!(self.offset < BLOCK_SIZE);
251 }
252
253 /// Concludes the hash computation and returns the final digest.
254 ///
255 /// The hash value (digest) of the concatenation of all processed message chunks is returned as an new array of size `N`.
256 ///
257 /// The returned array is filled completely, generating a hash value (digest) of the appropriate size.
258 ///
259 /// **Note:** The digest output size `N`, in bytes, must be a *positive* value! 🚨
260 pub fn digest<const N: usize>(self) -> [u8; N] {
261 let () = NoneZeroArg::<N>::OK;
262 let mut digest = [0u8; N];
263 self.digest_to_slice(&mut digest);
264 digest
265 }
266
267 /// Concludes the hash computation and returns the final digest.
268 ///
269 /// The hash value (digest) of the concatenation of all processed message chunks is written into the slice `digest_out`.
270 ///
271 /// The output slice is filled completely, generating a hash value (digest) of the appropriate size.
272 ///
273 /// **Note:** The specified digest output size, i.e., `digest_out.len()`, in bytes, must be a *positive* value! 🚨
274 pub fn digest_to_slice(mut self, digest_out: &mut [u8]) {
275 trace!(self, "digest::enter");
276 assert!(!digest_out.is_empty(), "Digest output size must be positive!");
277
278 let mut scratch_buffer = Scratch::default();
279
280 self.state.0[self.offset] ^= 0x80u8;
281 self.permute(&mut scratch_buffer);
282 self.state.0.xor_with(&ROUND_KEY_Z);
283
284 let mut pos = 0usize;
285
286 while pos < digest_out.len() {
287 self.permute(&mut scratch_buffer);
288 let copy_len = BLOCK_SIZE.min(digest_out.len() - pos);
289 digest_out[pos..(pos + copy_len)].copy_from_slice(&self.state.0[..copy_len]);
290 pos += copy_len;
291 }
292
293 trace!(self, "digest::leave");
294 }
295
296 /// Pseudorandom permutation, based on the AES-256 block cipher
297 #[inline]
298 fn permute(&mut self, work: &mut Scratch) {
299 trace!(self, "permfn::enter");
300
301 for _ in 0..R {
302 work.aes256.encrypt(&mut work.temp.0, &self.state.0, &self.state.1, &self.state.2);
303 work.aes256.encrypt(&mut work.temp.1, &self.state.1, &self.state.2, &self.state.0);
304 work.aes256.encrypt(&mut work.temp.2, &self.state.2, &self.state.0, &self.state.1);
305
306 self.state.0.xor_with(&work.temp.0);
307 self.state.1.xor_with(&work.temp.1);
308 self.state.2.xor_with(&work.temp.2);
309
310 self.state.1.xor_with(&ROUND_KEY_X);
311 self.state.2.xor_with(&ROUND_KEY_Y);
312 }
313
314 trace!(self, "permfn::leave");
315 }
316}
317
318impl Default for SpongeHash256 {
319 fn default() -> Self {
320 Self::new()
321 }
322}
323
324// ---------------------------------------------------------------------------
325// One-Shot API
326// ---------------------------------------------------------------------------
327
328/// Convenience function for “one-shot” SpongeHash-AES256 computation
329///
330/// The hash value (digest) of the given `message` is returned as an new array of type `[u8; N]`.
331///
332/// A `message` can be of *any* type that implements the [`AsRef<[u8]>`](AsRef<T>) trait, e.g., `&[u8]`, `&str` or `String`.
333///
334/// Optionally, an additional `info` string may be specified.
335///
336/// The returned array is filled completely, generating a hash value (digest) of the appropriate size.
337///
338/// This function uses the default number of permutation rounds, as is given by [`DEFAULT_PERMUTE_ROUNDS`].
339///
340/// **Note:** The digest output size `N`, in bytes, must be a *positive* value! 🚨
341///
342/// ### Usage Example
343///
344/// The **`compute()`** function can be used as follows:
345///
346/// ```rust
347/// use hex::encode_to_slice;
348/// use sponge_hash_aes256::{DEFAULT_DIGEST_SIZE, compute};
349///
350/// fn main() {
351/// // Compute the digest using the “one-shot” function
352/// let digest: [u8; DEFAULT_DIGEST_SIZE] = compute(
353/// None,
354/// b"The quick brown fox jumps over the lazy dog");
355///
356/// // Encode to hex
357/// let mut hex_buffer = [0u8; 2usize * DEFAULT_DIGEST_SIZE];
358/// encode_to_slice(&digest, &mut hex_buffer).unwrap();
359///
360/// // Print the digest (hex format)
361/// println!("0x{}", core::str::from_utf8(&hex_buffer).unwrap());
362/// }
363/// ```
364///
365/// ### Context information
366///
367/// Optionally, additional “context” information may be provided via the `info` parameter:
368///
369/// ```rust
370/// use sponge_hash_aes256::{DEFAULT_DIGEST_SIZE, compute};
371///
372/// fn main() {
373/// // Compute the digest using the “one-shot” function with additional “info”
374/// let digest: [u8; DEFAULT_DIGEST_SIZE] = compute(
375/// Some("my_application"),
376/// b"The quick brown fox jumps over the lazy dog");
377/// /* ... */
378/// }
379/// ```
380///
381/// ### Important note
382///
383/// <div class="warning">
384///
385/// 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.
386///
387/// </div>
388pub fn compute<const N: usize, T: AsRef<[u8]>>(info: Option<&str>, message: T) -> [u8; N] {
389 assert!(!info.is_some_and(str::is_empty), "Info must not be empty!");
390 let mut state: SpongeHash256 = SpongeHash256::with_info(info.unwrap_or_default());
391 state.update(message);
392 state.digest()
393}
394
395/// Convenience function for “one-shot” SpongeHash-AES256 computation
396///
397/// The hash value (digest) of the given `message` is written into the slice `digest_out`.
398///
399/// A `message` can be of *any* type that implements the [`AsRef<[u8]>`](AsRef<T>) trait, e.g., `&[u8]`, `&str` or `String`.
400///
401/// Optionally, an additional `info` string may be specified.
402///
403/// The output slice is filled completely, generating a hash value (digest) of the appropriate size.
404///
405/// This function uses the default number of permutation rounds, as is given by [`DEFAULT_PERMUTE_ROUNDS`].
406///
407/// **Note:** The digest output size, i.e., `digest_out.len()`, in bytes, must be a *positive* value! 🚨
408///
409/// ### Usage Example
410///
411/// The **`compute_to_slice()`** function can be used as follows:
412///
413/// ```rust
414/// use hex::encode_to_slice;
415/// use sponge_hash_aes256::{DEFAULT_DIGEST_SIZE, compute_to_slice};
416///
417/// fn main() {
418/// // Compute digest using the “one-shot” function
419/// let mut digest = [0u8; DEFAULT_DIGEST_SIZE];
420/// compute_to_slice(&mut digest, None, b"The quick brown fox jumps over the lazy dog");
421///
422/// // Encode to hex
423/// let mut hex_buffer = [0u8; 2usize * DEFAULT_DIGEST_SIZE];
424/// encode_to_slice(&digest, &mut hex_buffer).unwrap();
425///
426/// // Print the digest (hex format)
427/// println!("0x{}", core::str::from_utf8(&hex_buffer).unwrap());
428/// }
429///
430/// ```
431/// ### Context information
432///
433/// Optionally, additional “context” information may be provided via the `info` parameter:
434///
435/// ```rust
436/// use sponge_hash_aes256::{DEFAULT_DIGEST_SIZE, compute_to_slice};
437///
438/// fn main() {
439/// // Compute digest using the “one-shot” function with additional “info”
440/// let mut digest = [0u8; DEFAULT_DIGEST_SIZE];
441/// compute_to_slice(
442/// &mut digest,
443/// Some("my_application"),
444/// b"The quick brown fox jumps over the lazy dog");
445/// /* ... */
446/// }
447/// ```
448///
449/// ### Important note
450///
451/// <div class="warning">
452///
453/// 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.
454///
455/// </div>
456pub fn compute_to_slice<T: AsRef<[u8]>>(digest_out: &mut [u8], info: Option<&str>, message: T) {
457 assert!(!info.is_some_and(str::is_empty), "Info must not be empty!");
458 let mut state: SpongeHash256 = SpongeHash256::with_info(info.unwrap_or_default());
459 state.update(message);
460 state.digest_to_slice(digest_out);
461}