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}