chie_crypto/
hash.rs

1//! Fast hashing using BLAKE3.
2//!
3//! This module provides:
4//! - Simple one-shot hashing
5//! - Incremental hashing for large files
6//! - Keyed hashing for MACs
7//! - Parallel chunk hashing
8
9use std::io::{self, Read};
10
11/// Hash output (256 bits).
12pub type Hash = [u8; 32];
13
14/// Default buffer size for incremental hashing (64 KB).
15pub const HASH_BUFFER_SIZE: usize = 64 * 1024;
16
17/// Compute BLAKE3 hash of data.
18pub fn hash(data: &[u8]) -> Hash {
19    *blake3::hash(data).as_bytes()
20}
21
22/// Compute BLAKE3 hash of multiple data chunks.
23pub fn hash_multi(chunks: &[&[u8]]) -> Hash {
24    let mut hasher = blake3::Hasher::new();
25    for chunk in chunks {
26        hasher.update(chunk);
27    }
28    *hasher.finalize().as_bytes()
29}
30
31/// Verify that data matches the expected hash.
32pub fn verify_hash(data: &[u8], expected: &Hash) -> bool {
33    &hash(data) == expected
34}
35
36/// Compute keyed BLAKE3 hash (for MAC).
37pub fn keyed_hash(key: &[u8; 32], data: &[u8]) -> Hash {
38    *blake3::keyed_hash(key, data).as_bytes()
39}
40
41/// Incremental hasher for streaming large data.
42///
43/// This allows hashing data piece by piece without loading
44/// the entire content into memory.
45///
46/// # Example
47///
48/// ```
49/// use chie_crypto::IncrementalHasher;
50///
51/// let mut hasher = IncrementalHasher::new();
52/// hasher.update(b"Hello, ");
53/// hasher.update(b"World!");
54/// let hash = hasher.finalize();
55/// ```
56pub struct IncrementalHasher {
57    inner: blake3::Hasher,
58    bytes_processed: u64,
59}
60
61impl Default for IncrementalHasher {
62    fn default() -> Self {
63        Self::new()
64    }
65}
66
67impl IncrementalHasher {
68    /// Create a new incremental hasher.
69    pub fn new() -> Self {
70        Self {
71            inner: blake3::Hasher::new(),
72            bytes_processed: 0,
73        }
74    }
75
76    /// Create a keyed incremental hasher.
77    pub fn new_keyed(key: &[u8; 32]) -> Self {
78        Self {
79            inner: blake3::Hasher::new_keyed(key),
80            bytes_processed: 0,
81        }
82    }
83
84    /// Update the hasher with more data.
85    pub fn update(&mut self, data: &[u8]) {
86        self.inner.update(data);
87        self.bytes_processed += data.len() as u64;
88    }
89
90    /// Get the number of bytes processed so far.
91    pub fn bytes_processed(&self) -> u64 {
92        self.bytes_processed
93    }
94
95    /// Finalize and return the hash.
96    pub fn finalize(self) -> Hash {
97        *self.inner.finalize().as_bytes()
98    }
99
100    /// Finalize but keep the hasher state (for XOF usage).
101    pub fn finalize_reset(&mut self) -> Hash {
102        let hash = *self.inner.finalize().as_bytes();
103        self.inner.reset();
104        self.bytes_processed = 0;
105        hash
106    }
107
108    /// Hash data from a reader incrementally.
109    ///
110    /// This is useful for hashing large files without loading
111    /// them entirely into memory.
112    pub fn update_reader<R: Read>(&mut self, reader: &mut R) -> io::Result<u64> {
113        let mut buffer = [0u8; HASH_BUFFER_SIZE];
114        let mut total = 0u64;
115
116        loop {
117            let bytes_read = reader.read(&mut buffer)?;
118            if bytes_read == 0 {
119                break;
120            }
121            self.update(&buffer[..bytes_read]);
122            total += bytes_read as u64;
123        }
124
125        Ok(total)
126    }
127}
128
129/// Hash a reader (file, network stream, etc.) incrementally.
130pub fn hash_reader<R: Read>(reader: &mut R) -> io::Result<Hash> {
131    let mut hasher = IncrementalHasher::new();
132    hasher.update_reader(reader)?;
133    Ok(hasher.finalize())
134}
135
136/// Result of hashing with metadata.
137#[derive(Debug, Clone)]
138pub struct HashResult {
139    /// The computed hash.
140    pub hash: Hash,
141    /// Total bytes processed.
142    pub bytes_processed: u64,
143}
144
145/// Hash multiple chunks and return individual hashes plus root hash.
146///
147/// This is useful for content-addressed storage where we need
148/// both per-chunk hashes and an overall content hash.
149pub struct ChunkHasher {
150    chunk_hashes: Vec<Hash>,
151    root_hasher: IncrementalHasher,
152}
153
154impl Default for ChunkHasher {
155    fn default() -> Self {
156        Self::new()
157    }
158}
159
160impl ChunkHasher {
161    /// Create a new chunk hasher.
162    pub fn new() -> Self {
163        Self {
164            chunk_hashes: Vec::new(),
165            root_hasher: IncrementalHasher::new(),
166        }
167    }
168
169    /// Add a chunk and compute its hash.
170    pub fn add_chunk(&mut self, chunk: &[u8]) -> Hash {
171        let chunk_hash = hash(chunk);
172        self.chunk_hashes.push(chunk_hash);
173        self.root_hasher.update(&chunk_hash);
174        chunk_hash
175    }
176
177    /// Get the number of chunks processed.
178    pub fn chunk_count(&self) -> usize {
179        self.chunk_hashes.len()
180    }
181
182    /// Get all chunk hashes.
183    pub fn chunk_hashes(&self) -> &[Hash] {
184        &self.chunk_hashes
185    }
186
187    /// Finalize and return the root hash (hash of all chunk hashes).
188    pub fn finalize(self) -> ChunkHashResult {
189        ChunkHashResult {
190            chunk_hashes: self.chunk_hashes,
191            root_hash: self.root_hasher.finalize(),
192        }
193    }
194}
195
196/// Result of chunk hashing.
197#[derive(Debug, Clone)]
198pub struct ChunkHashResult {
199    /// Individual chunk hashes.
200    pub chunk_hashes: Vec<Hash>,
201    /// Root hash (hash of all chunk hashes).
202    pub root_hash: Hash,
203}
204
205impl ChunkHashResult {
206    /// Verify a specific chunk.
207    pub fn verify_chunk(&self, index: usize, chunk: &[u8]) -> bool {
208        if index >= self.chunk_hashes.len() {
209            return false;
210        }
211        hash(chunk) == self.chunk_hashes[index]
212    }
213
214    /// Get the number of chunks.
215    pub fn chunk_count(&self) -> usize {
216        self.chunk_hashes.len()
217    }
218}
219
220/// Hash content in chunks from a reader.
221pub fn hash_chunked<R: Read>(reader: &mut R, chunk_size: usize) -> io::Result<ChunkHashResult> {
222    let mut chunk_hasher = ChunkHasher::new();
223    let mut buffer = vec![0u8; chunk_size];
224
225    loop {
226        let mut total_read = 0;
227
228        // Read a full chunk (or until EOF)
229        while total_read < chunk_size {
230            let bytes_read = reader.read(&mut buffer[total_read..])?;
231            if bytes_read == 0 {
232                break;
233            }
234            total_read += bytes_read;
235        }
236
237        if total_read == 0 {
238            break;
239        }
240
241        chunk_hasher.add_chunk(&buffer[..total_read]);
242
243        if total_read < chunk_size {
244            // EOF reached mid-chunk
245            break;
246        }
247    }
248
249    Ok(chunk_hasher.finalize())
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255    use std::io::Cursor;
256
257    #[test]
258    fn test_hash() {
259        let data = b"Hello, CHIE Protocol!";
260        let h = hash(data);
261
262        assert!(verify_hash(data, &h));
263        assert!(!verify_hash(b"Different data", &h));
264    }
265
266    #[test]
267    fn test_hash_multi() {
268        let chunk1 = b"Hello, ";
269        let chunk2 = b"CHIE Protocol!";
270        let combined = b"Hello, CHIE Protocol!";
271
272        let h_multi = hash_multi(&[chunk1, chunk2]);
273        let h_combined = hash(combined);
274
275        assert_eq!(h_multi, h_combined);
276    }
277
278    #[test]
279    fn test_incremental_hasher() {
280        let data = b"Hello, CHIE Protocol!";
281
282        // One-shot hash
283        let h1 = hash(data);
284
285        // Incremental hash
286        let mut hasher = IncrementalHasher::new();
287        hasher.update(b"Hello, ");
288        hasher.update(b"CHIE ");
289        hasher.update(b"Protocol!");
290        let h2 = hasher.finalize();
291
292        assert_eq!(h1, h2);
293    }
294
295    #[test]
296    fn test_hash_reader() {
297        let data = b"Hello, CHIE Protocol!";
298        let mut cursor = Cursor::new(data);
299
300        let h1 = hash(data);
301        let h2 = hash_reader(&mut cursor).unwrap();
302
303        assert_eq!(h1, h2);
304    }
305
306    #[test]
307    fn test_chunk_hasher() {
308        let chunk1 = b"Chunk 1 data";
309        let chunk2 = b"Chunk 2 data";
310        let chunk3 = b"Chunk 3 data";
311
312        let mut hasher = ChunkHasher::new();
313        hasher.add_chunk(chunk1);
314        hasher.add_chunk(chunk2);
315        hasher.add_chunk(chunk3);
316
317        let result = hasher.finalize();
318
319        assert_eq!(result.chunk_count(), 3);
320        assert!(result.verify_chunk(0, chunk1));
321        assert!(result.verify_chunk(1, chunk2));
322        assert!(result.verify_chunk(2, chunk3));
323        assert!(!result.verify_chunk(0, chunk2));
324    }
325
326    #[test]
327    fn test_hash_chunked() {
328        let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
329        let mut cursor = Cursor::new(data);
330
331        let result = hash_chunked(&mut cursor, 10).unwrap();
332
333        // 26 bytes / 10 = 3 chunks (10 + 10 + 6)
334        assert_eq!(result.chunk_count(), 3);
335        assert!(result.verify_chunk(0, b"ABCDEFGHIJ"));
336        assert!(result.verify_chunk(1, b"KLMNOPQRST"));
337        assert!(result.verify_chunk(2, b"UVWXYZ"));
338    }
339
340    #[test]
341    fn test_keyed_hash() {
342        let key = [0u8; 32];
343        let data = b"Hello, CHIE Protocol!";
344
345        let h1 = keyed_hash(&key, data);
346        let h2 = keyed_hash(&key, data);
347        let h3 = keyed_hash(&[1u8; 32], data);
348
349        assert_eq!(h1, h2);
350        assert_ne!(h1, h3); // Different key = different hash
351    }
352}