clock_hash/hasher.rs
1//! Incremental hasher for ClockHash-256
2//!
3//! Provides a streaming interface for hashing data incrementally.
4
5use crate::constants::IV;
6use crate::padding::{BLOCK_SIZE, pad_message_in_place, MAX_PADDED_SIZE};
7
8#[cfg(feature = "simd")]
9use crate::simd::process_block_simd;
10
11#[cfg(not(feature = "simd"))]
12use crate::clockmix::clock_mix;
13
14#[cfg(not(feature = "simd"))]
15use crate::clockpermute::clock_permute;
16
17#[cfg(not(feature = "simd"))]
18use crate::utils::rotr64;
19
20/// Incremental hasher for ClockHash-256
21///
22/// `ClockHasher` provides a streaming interface for computing ClockHash-256 hashes
23/// incrementally. This is useful when the input data is too large to fit in memory
24/// at once, or when data arrives in chunks (e.g., network streams, file reading).
25///
26/// The hasher maintains internal state and buffers partial blocks until enough
27/// data is available for processing. It automatically handles padding and finalization.
28///
29/// # Examples
30///
31/// Basic incremental hashing:
32/// ```rust
33/// use clock_hash::ClockHasher;
34///
35/// let mut hasher = ClockHasher::new();
36/// hasher.update(b"Hello");
37/// hasher.update(b", ");
38/// hasher.update(b"World!");
39/// let hash = hasher.finalize();
40/// assert_eq!(hash.len(), 32);
41/// ```
42///
43/// Incremental hashing produces the same result as one-shot hashing:
44/// ```rust
45/// # use clock_hash::{ClockHasher, clockhash256};
46/// let data = b"The quick brown fox jumps over the lazy dog";
47///
48/// // Incremental
49/// let mut hasher = ClockHasher::new();
50/// hasher.update(data);
51/// let incremental_hash = hasher.finalize();
52///
53/// // One-shot
54/// let oneshot_hash = clockhash256(data);
55///
56/// assert_eq!(incremental_hash, oneshot_hash);
57/// ```
58///
59/// Hashing large files or streams:
60/// ```rust,no_run
61/// # use clock_hash::ClockHasher;
62/// # use std::fs::File;
63/// # use std::io::Read;
64/// let mut hasher = ClockHasher::new();
65/// let mut file = File::open("large_file.dat")?;
66/// let mut buffer = [0u8; 8192]; // 8KB chunks
67///
68/// while let Ok(n) = file.read(&mut buffer) {
69/// if n == 0 { break; }
70/// hasher.update(&buffer[..n]);
71/// }
72///
73/// let file_hash = hasher.finalize();
74/// # Ok::<(), std::io::Error>(())
75/// ```
76pub struct ClockHasher {
77 /// Internal state (8 u64 words) initialized from IV constants
78 state: [u64; 8],
79 /// Buffer for partial blocks (max 127 bytes before processing)
80 buffer: [u8; BLOCK_SIZE],
81 /// Number of bytes currently in buffer
82 buffer_len: usize,
83 /// Total message length in bytes (used for padding)
84 message_len: u64,
85}
86
87impl ClockHasher {
88 /// Create a new ClockHasher with initial state from IV.
89 ///
90 /// The hasher is initialized with the ClockHash-256 initialization vector
91 /// and is ready to accept data via `update()`.
92 ///
93 /// # Examples
94 ///
95 /// ```rust
96 /// use clock_hash::ClockHasher;
97 ///
98 /// let hasher = ClockHasher::new();
99 /// // Now you can call hasher.update(data) and hasher.finalize()
100 /// ```
101 #[inline]
102 pub fn new() -> Self {
103 Self {
104 state: IV,
105 buffer: [0u8; BLOCK_SIZE],
106 buffer_len: 0,
107 message_len: 0,
108 }
109 }
110
111 /// Process a complete 128-byte block.
112 ///
113 /// Uses SIMD acceleration when available and the "simd" feature is enabled.
114 ///
115 /// # Arguments
116 ///
117 /// * `block` - A 128-byte block (must be exactly BLOCK_SIZE bytes)
118 #[inline]
119 fn process_block(&mut self, block: &[u8; BLOCK_SIZE]) {
120 #[cfg(feature = "simd")]
121 {
122 process_block_simd(block, &mut self.state);
123 }
124
125 #[cfg(not(feature = "simd"))]
126 {
127 // Parse block to 16 u64 words (little-endian)
128 let mut words = [0u64; 16];
129 for i in 0..16 {
130 let offset = i * 8;
131 words[i] = u64::from_le_bytes([
132 block[offset],
133 block[offset + 1],
134 block[offset + 2],
135 block[offset + 3],
136 block[offset + 4],
137 block[offset + 5],
138 block[offset + 6],
139 block[offset + 7],
140 ]);
141 }
142
143 // Apply ClockMix
144 clock_mix(&mut words);
145
146 // Inject into state
147 for i in 0..8 {
148 self.state[i] = self.state[i].wrapping_add(words[i]);
149 let rot_idx = (i + 4) % 8;
150 self.state[i] ^= rotr64(self.state[rot_idx], 17);
151 }
152
153 // Apply ClockPermute
154 clock_permute(&mut self.state);
155 }
156 }
157
158 /// Update the hasher with new data.
159 ///
160 /// This method can be called multiple times to feed data incrementally.
161 /// The hasher will buffer partial blocks and process complete 128-byte
162 /// blocks as they become available.
163 ///
164 /// # Arguments
165 ///
166 /// * `data` - The data to hash (can be any length, including empty slices)
167 ///
168 /// # Examples
169 ///
170 /// Feeding data in chunks:
171 /// ```rust
172 /// # use clock_hash::ClockHasher;
173 /// let mut hasher = ClockHasher::new();
174 ///
175 /// // Feed data in multiple calls
176 /// hasher.update(b"chunk 1");
177 /// hasher.update(b"chunk 2");
178 /// hasher.update(b"chunk 3");
179 ///
180 /// let hash = hasher.finalize();
181 /// ```
182 ///
183 /// Empty updates are allowed:
184 /// ```rust
185 /// # use clock_hash::ClockHasher;
186 /// let mut hasher = ClockHasher::new();
187 /// hasher.update(b""); // Empty update - no effect
188 /// hasher.update(b"data");
189 /// let hash = hasher.finalize();
190 /// ```
191 pub fn update(&mut self, data: &[u8]) {
192 self.message_len = self.message_len.wrapping_add(data.len() as u64);
193
194 let mut data_offset = 0;
195
196 // If we have buffered data, try to complete a block
197 if self.buffer_len > 0 {
198 let needed = BLOCK_SIZE - self.buffer_len;
199 let to_copy = core::cmp::min(needed, data.len());
200
201 self.buffer[self.buffer_len..self.buffer_len + to_copy]
202 .copy_from_slice(&data[0..to_copy]);
203 self.buffer_len += to_copy;
204 data_offset = to_copy;
205
206 // If we completed a block, process it
207 if self.buffer_len == BLOCK_SIZE {
208 let block: [u8; BLOCK_SIZE] = self.buffer;
209 self.process_block(&block);
210 self.buffer_len = 0;
211 }
212 }
213
214 // Process complete blocks from remaining data
215 while data_offset + BLOCK_SIZE <= data.len() {
216 let mut block = [0u8; BLOCK_SIZE];
217 block.copy_from_slice(&data[data_offset..data_offset + BLOCK_SIZE]);
218 self.process_block(&block);
219 data_offset += BLOCK_SIZE;
220 }
221
222 // Buffer remaining partial block
223 if data_offset < data.len() {
224 let remaining = data.len() - data_offset;
225 self.buffer[0..remaining].copy_from_slice(&data[data_offset..]);
226 self.buffer_len = remaining;
227 }
228 }
229
230 /// Finalize the hash and return the result.
231 ///
232 /// This method applies the final padding scheme, processes any remaining
233 /// buffered data, and returns the 32-byte ClockHash-256 hash. After calling
234 /// `finalize()`, the hasher is consumed and cannot be used again.
235 ///
236 /// The padding scheme used is: `message || 0x80 || zeros || length_bits`
237 /// where `length_bits` is the total message length in bits as a 64-bit
238 /// little-endian value.
239 ///
240 /// # Returns
241 ///
242 /// A 32-byte array containing the final ClockHash-256 hash
243 ///
244 /// # Examples
245 ///
246 /// Basic finalization:
247 /// ```rust
248 /// # use clock_hash::ClockHasher;
249 /// let mut hasher = ClockHasher::new();
250 /// hasher.update(b"test data");
251 /// let hash = hasher.finalize();
252 /// assert_eq!(hash.len(), 32);
253 /// ```
254 ///
255 /// Empty hash:
256 /// ```rust
257 /// # use clock_hash::ClockHasher;
258 /// let hasher = ClockHasher::new();
259 /// let empty_hash = hasher.finalize(); // No data added
260 /// // Still produces a valid hash
261 /// assert_eq!(empty_hash.len(), 32);
262 /// ```
263 ///
264 /// # Note
265 ///
266 /// After calling `finalize()`, the hasher cannot be reused.
267 /// Create a new `ClockHasher` instance for additional hashes.
268 pub fn finalize(mut self) -> [u8; 32] {
269 use crate::padding::{MAX_PADDED_SIZE, BLOCK_SIZE};
270
271 // Create padded final block (always maximum size for constant-time)
272 let mut final_block = [0u8; MAX_PADDED_SIZE];
273
274 // Copy buffered data
275 final_block[0..self.buffer_len].copy_from_slice(&self.buffer[0..self.buffer_len]);
276
277 // Apply padding to maximum size
278 pad_message_in_place(
279 &mut final_block,
280 self.buffer_len,
281 0,
282 self.message_len as usize,
283 );
284
285 // Process all blocks (always exactly MAX_PADDED_SIZE / BLOCK_SIZE blocks)
286 let mut offset = 0;
287 while offset + BLOCK_SIZE <= MAX_PADDED_SIZE {
288 let mut block = [0u8; BLOCK_SIZE];
289 block.copy_from_slice(&final_block[offset..offset + BLOCK_SIZE]);
290 self.process_block(&block);
291 offset += BLOCK_SIZE;
292 }
293
294 // Finalization: XOR state with IV
295 for i in 0..8 {
296 self.state[i] ^= IV[i];
297 }
298
299 // Output folding: H[i] = S[i] ⊕ S[i+4] for i=0..3
300 let h0 = self.state[0] ^ self.state[4];
301 let h1 = self.state[1] ^ self.state[5];
302 let h2 = self.state[2] ^ self.state[6];
303 let h3 = self.state[3] ^ self.state[7];
304
305 // Convert to bytes (little-endian)
306 let mut result = [0u8; 32];
307 result[0..8].copy_from_slice(&h0.to_le_bytes());
308 result[8..16].copy_from_slice(&h1.to_le_bytes());
309 result[16..24].copy_from_slice(&h2.to_le_bytes());
310 result[24..32].copy_from_slice(&h3.to_le_bytes());
311
312 result
313 }
314}
315
316impl Default for ClockHasher {
317 fn default() -> Self {
318 Self::new()
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325
326 #[test]
327 fn test_hasher_empty() {
328 let hasher = ClockHasher::new();
329 let hash = hasher.finalize();
330 assert_eq!(hash.len(), 32);
331 }
332
333 #[test]
334 fn test_hasher_small() {
335 let mut hasher = ClockHasher::new();
336 hasher.update(b"abc");
337 let hash = hasher.finalize();
338 assert_eq!(hash.len(), 32);
339 }
340
341 #[test]
342 fn test_hasher_incremental() {
343 let mut hasher1 = ClockHasher::new();
344 hasher1.update(b"hello");
345 hasher1.update(b", ");
346 hasher1.update(b"world");
347 let hash1 = hasher1.finalize();
348
349 let mut hasher2 = ClockHasher::new();
350 hasher2.update(b"hello, world");
351 let hash2 = hasher2.finalize();
352
353 // Incremental and one-shot should produce same result
354 assert_eq!(hash1, hash2);
355 }
356
357 #[test]
358 fn test_hasher_large() {
359 let mut hasher = ClockHasher::new();
360 let data = [0u8; 10000];
361 hasher.update(&data);
362 let hash = hasher.finalize();
363 assert_eq!(hash.len(), 32);
364 }
365}