chie_core/
streaming_verification.rs

1//! Incremental content verification with streaming hash.
2//!
3//! This module provides streaming hash verification for large content,
4//! allowing verification to proceed incrementally as chunks arrive without
5//! needing to buffer the entire content in memory.
6//!
7//! # Features
8//!
9//! - Streaming hash computation (BLAKE3)
10//! - Incremental verification without full buffering
11//! - Merkle tree-based chunk verification
12//! - Progress tracking for long-running verification
13//! - Memory-efficient verification of large files
14//! - Resumable verification from checkpoints
15//!
16//! # Example
17//!
18//! ```
19//! use chie_core::streaming_verification::{StreamingVerifier, VerificationProgress};
20//!
21//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
22//! // Create a verifier with expected root hash
23//! let expected_hash = [0u8; 32];
24//! let mut verifier = StreamingVerifier::new(expected_hash);
25//!
26//! // Feed chunks incrementally
27//! let chunk1 = b"Hello, ";
28//! let chunk2 = b"World!";
29//!
30//! verifier.update(chunk1);
31//! verifier.update(chunk2);
32//!
33//! // Finalize and verify
34//! let result = verifier.finalize()?;
35//! if result.verified {
36//!     println!("Content verified successfully!");
37//! }
38//! # Ok(())
39//! # }
40//! ```
41
42use chie_crypto::hash::{IncrementalHasher, hash};
43use serde::{Deserialize, Serialize};
44use std::collections::HashMap;
45use thiserror::Error;
46
47/// Default chunk size for Merkle tree construction (256 KB)
48const MERKLE_CHUNK_SIZE: usize = 256 * 1024;
49
50/// Errors that can occur during streaming verification
51#[derive(Debug, Error)]
52pub enum VerificationError {
53    #[error("Hash mismatch: expected {expected:?}, got {actual:?}")]
54    HashMismatch {
55        expected: [u8; 32],
56        actual: [u8; 32],
57    },
58
59    #[error("Incomplete verification: {0} bytes processed, {1} bytes expected")]
60    Incomplete(u64, u64),
61
62    #[error("Chunk {0} failed verification")]
63    ChunkFailed(u64),
64
65    #[error("Merkle tree error: {0}")]
66    MerkleError(String),
67}
68
69/// Result of verification
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct VerificationResult {
72    /// Whether the content passed verification
73    pub verified: bool,
74    /// Total bytes verified
75    pub bytes_verified: u64,
76    /// Actual hash computed
77    pub actual_hash: [u8; 32],
78    /// Expected hash
79    pub expected_hash: [u8; 32],
80    /// Number of chunks verified
81    pub chunks_verified: u64,
82}
83
84impl VerificationResult {
85    /// Check if verification succeeded
86    #[must_use]
87    #[inline]
88    pub const fn is_verified(&self) -> bool {
89        self.verified
90    }
91
92    /// Get the hash mismatch if verification failed
93    #[must_use]
94    #[inline]
95    pub fn hash_mismatch(&self) -> Option<([u8; 32], [u8; 32])> {
96        if !self.verified {
97            Some((self.expected_hash, self.actual_hash))
98        } else {
99            None
100        }
101    }
102}
103
104/// Progress information for streaming verification
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct VerificationProgress {
107    /// Bytes processed so far
108    pub bytes_processed: u64,
109    /// Total bytes expected (None if unknown)
110    pub total_bytes: Option<u64>,
111    /// Number of chunks processed
112    pub chunks_processed: u64,
113    /// Percentage complete (0.0 to 100.0)
114    pub percentage: f64,
115}
116
117impl VerificationProgress {
118    /// Check if verification is complete
119    #[must_use]
120    #[inline]
121    pub fn is_complete(&self) -> bool {
122        if let Some(total) = self.total_bytes {
123            self.bytes_processed >= total
124        } else {
125            false
126        }
127    }
128}
129
130/// Streaming content verifier using BLAKE3
131pub struct StreamingVerifier {
132    /// BLAKE3 hasher for streaming hash computation
133    hasher: IncrementalHasher,
134    /// Expected root hash
135    expected_hash: [u8; 32],
136    /// Total bytes processed
137    bytes_processed: u64,
138    /// Expected total bytes (None if unknown)
139    total_bytes: Option<u64>,
140    /// Chunks processed
141    chunks_processed: u64,
142}
143
144impl StreamingVerifier {
145    /// Create a new streaming verifier with expected hash
146    #[must_use]
147    pub fn new(expected_hash: [u8; 32]) -> Self {
148        Self {
149            hasher: IncrementalHasher::new(),
150            expected_hash,
151            bytes_processed: 0,
152            total_bytes: None,
153            chunks_processed: 0,
154        }
155    }
156
157    /// Create a verifier with known total size
158    #[must_use]
159    pub fn with_size(expected_hash: [u8; 32], total_bytes: u64) -> Self {
160        Self {
161            hasher: IncrementalHasher::new(),
162            expected_hash,
163            bytes_processed: 0,
164            total_bytes: Some(total_bytes),
165            chunks_processed: 0,
166        }
167    }
168
169    /// Update the hash with new data
170    pub fn update(&mut self, data: &[u8]) {
171        self.hasher.update(data);
172        self.bytes_processed += data.len() as u64;
173        self.chunks_processed += 1;
174    }
175
176    /// Get current verification progress
177    #[must_use]
178    #[inline]
179    pub fn progress(&self) -> VerificationProgress {
180        let percentage = if let Some(total) = self.total_bytes {
181            if total > 0 {
182                (self.bytes_processed as f64 / total as f64) * 100.0
183            } else {
184                100.0
185            }
186        } else {
187            0.0
188        };
189
190        VerificationProgress {
191            bytes_processed: self.bytes_processed,
192            total_bytes: self.total_bytes,
193            chunks_processed: self.chunks_processed,
194            percentage,
195        }
196    }
197
198    /// Finalize the hash and verify
199    pub fn finalize(self) -> Result<VerificationResult, VerificationError> {
200        let actual_hash: [u8; 32] = self.hasher.finalize();
201        let verified = actual_hash == self.expected_hash;
202
203        if !verified {
204            return Err(VerificationError::HashMismatch {
205                expected: self.expected_hash,
206                actual: actual_hash,
207            });
208        }
209
210        Ok(VerificationResult {
211            verified,
212            bytes_verified: self.bytes_processed,
213            actual_hash,
214            expected_hash: self.expected_hash,
215            chunks_verified: self.chunks_processed,
216        })
217    }
218
219    /// Reset the verifier to initial state
220    pub fn reset(&mut self) {
221        self.hasher = IncrementalHasher::new();
222        self.bytes_processed = 0;
223        self.chunks_processed = 0;
224    }
225}
226
227/// Merkle tree-based chunk verifier for parallel verification
228pub struct MerkleVerifier {
229    /// Expected root hash
230    expected_root: [u8; 32],
231    /// Chunk hashes (index -> hash)
232    chunk_hashes: HashMap<u64, [u8; 32]>,
233    /// Chunk size
234    chunk_size: usize,
235    /// Total chunks expected
236    total_chunks: u64,
237}
238
239impl MerkleVerifier {
240    /// Create a new Merkle verifier
241    #[must_use]
242    pub fn new(expected_root: [u8; 32], chunk_size: usize, total_chunks: u64) -> Self {
243        Self {
244            expected_root,
245            chunk_hashes: HashMap::new(),
246            chunk_size,
247            total_chunks,
248        }
249    }
250
251    /// Create a Merkle verifier with default chunk size (256 KB)
252    #[must_use]
253    pub fn with_default_chunk_size(expected_root: [u8; 32], total_chunks: u64) -> Self {
254        Self::new(expected_root, MERKLE_CHUNK_SIZE, total_chunks)
255    }
256
257    /// Verify a single chunk and record its hash
258    pub fn verify_chunk(&mut self, chunk_index: u64, data: &[u8]) -> Result<(), VerificationError> {
259        // Compute chunk hash
260        let chunk_hash: [u8; 32] = hash(data);
261
262        // Store the hash for later Merkle tree verification
263        self.chunk_hashes.insert(chunk_index, chunk_hash);
264
265        Ok(())
266    }
267
268    /// Build Merkle tree from chunk hashes and verify root
269    pub fn verify_merkle_root(&self) -> Result<VerificationResult, VerificationError> {
270        if self.chunk_hashes.len() as u64 != self.total_chunks {
271            return Err(VerificationError::Incomplete(
272                self.chunk_hashes.len() as u64,
273                self.total_chunks,
274            ));
275        }
276
277        // Build Merkle tree bottom-up
278        let mut current_level: Vec<[u8; 32]> = (0..self.total_chunks)
279            .map(|i| self.chunk_hashes.get(&i).copied().unwrap_or([0u8; 32]))
280            .collect();
281
282        // Build tree upward
283        while current_level.len() > 1 {
284            let mut next_level = Vec::new();
285
286            for chunk in current_level.chunks(2) {
287                let combined_hash = if chunk.len() == 2 {
288                    // Combine two hashes
289                    let mut combined = [0u8; 64];
290                    combined[..32].copy_from_slice(&chunk[0]);
291                    combined[32..].copy_from_slice(&chunk[1]);
292                    hash(&combined)
293                } else {
294                    // Odd number of nodes, promote the last one
295                    chunk[0]
296                };
297                next_level.push(combined_hash);
298            }
299
300            current_level = next_level;
301        }
302
303        let actual_root = current_level[0];
304        let verified = actual_root == self.expected_root;
305
306        if !verified {
307            return Err(VerificationError::HashMismatch {
308                expected: self.expected_root,
309                actual: actual_root,
310            });
311        }
312
313        Ok(VerificationResult {
314            verified,
315            bytes_verified: (self.total_chunks * self.chunk_size as u64),
316            actual_hash: actual_root,
317            expected_hash: self.expected_root,
318            chunks_verified: self.total_chunks,
319        })
320    }
321
322    /// Get the number of chunks verified so far
323    #[must_use]
324    #[inline]
325    pub fn chunks_verified(&self) -> u64 {
326        self.chunk_hashes.len() as u64
327    }
328
329    /// Check if all chunks have been verified
330    #[must_use]
331    #[inline]
332    pub fn is_complete(&self) -> bool {
333        self.chunk_hashes.len() as u64 == self.total_chunks
334    }
335
336    /// Get verification progress
337    #[must_use]
338    pub fn progress(&self) -> VerificationProgress {
339        let chunks_verified = self.chunk_hashes.len() as u64;
340        let percentage = if self.total_chunks > 0 {
341            (chunks_verified as f64 / self.total_chunks as f64) * 100.0
342        } else {
343            0.0
344        };
345
346        VerificationProgress {
347            bytes_processed: chunks_verified * self.chunk_size as u64,
348            total_bytes: Some(self.total_chunks * self.chunk_size as u64),
349            chunks_processed: chunks_verified,
350            percentage,
351        }
352    }
353}
354
355/// Checkpoint for resumable verification
356#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct VerificationCheckpoint {
358    /// Bytes processed so far
359    pub bytes_processed: u64,
360    /// Chunks processed
361    pub chunks_processed: u64,
362    /// Partial hash state (serialized)
363    pub hash_state: Vec<u8>,
364}
365
366impl VerificationCheckpoint {
367    /// Create a checkpoint from current state
368    #[must_use]
369    pub fn new(bytes_processed: u64, chunks_processed: u64) -> Self {
370        Self {
371            bytes_processed,
372            chunks_processed,
373            hash_state: Vec::new(), // BLAKE3 doesn't support state serialization
374        }
375    }
376}
377
378#[cfg(test)]
379mod tests {
380    use super::*;
381
382    #[test]
383    fn test_streaming_verifier_success() {
384        let data = b"Hello, World!";
385        let expected_hash: [u8; 32] = hash(data);
386
387        let mut verifier = StreamingVerifier::new(expected_hash);
388        verifier.update(data);
389
390        let result = verifier.finalize().unwrap();
391        assert!(result.verified);
392        assert_eq!(result.bytes_verified, data.len() as u64);
393    }
394
395    #[test]
396    fn test_streaming_verifier_incremental() {
397        let part1 = b"Hello, ";
398        let part2 = b"World!";
399        let full_data = b"Hello, World!";
400        let expected_hash: [u8; 32] = hash(full_data);
401
402        let mut verifier = StreamingVerifier::new(expected_hash);
403        verifier.update(part1);
404        verifier.update(part2);
405
406        let result = verifier.finalize().unwrap();
407        assert!(result.verified);
408        assert_eq!(result.bytes_verified, full_data.len() as u64);
409        assert_eq!(result.chunks_verified, 2);
410    }
411
412    #[test]
413    fn test_streaming_verifier_mismatch() {
414        let data = b"Hello, World!";
415        let wrong_hash = [0u8; 32];
416
417        let mut verifier = StreamingVerifier::new(wrong_hash);
418        verifier.update(data);
419
420        let result = verifier.finalize();
421        assert!(result.is_err());
422    }
423
424    #[test]
425    fn test_streaming_verifier_progress() {
426        let data = b"Hello, World!";
427        let expected_hash: [u8; 32] = hash(data);
428
429        let mut verifier = StreamingVerifier::with_size(expected_hash, data.len() as u64);
430        verifier.update(&data[..5]);
431
432        let progress = verifier.progress();
433        assert_eq!(progress.bytes_processed, 5);
434        assert_eq!(progress.total_bytes, Some(data.len() as u64));
435        assert!(!progress.is_complete());
436
437        verifier.update(&data[5..]);
438
439        let progress = verifier.progress();
440        assert!(progress.is_complete());
441        assert_eq!(progress.percentage, 100.0);
442    }
443
444    #[test]
445    fn test_merkle_verifier_single_chunk() {
446        let data = b"Hello, World!";
447        let chunk_hash: [u8; 32] = hash(data);
448
449        let mut verifier = MerkleVerifier::new(chunk_hash, 1024, 1);
450        verifier.verify_chunk(0, data).unwrap();
451
452        assert!(verifier.is_complete());
453        let result = verifier.verify_merkle_root().unwrap();
454        assert!(result.verified);
455    }
456
457    #[test]
458    fn test_merkle_verifier_multiple_chunks() {
459        let chunk1 = b"Hello, ";
460        let chunk2 = b"World!";
461
462        // Compute expected root
463        let hash1: [u8; 32] = hash(chunk1);
464        let hash2: [u8; 32] = hash(chunk2);
465        let mut combined = [0u8; 64];
466        combined[..32].copy_from_slice(&hash1);
467        combined[32..].copy_from_slice(&hash2);
468        let root: [u8; 32] = hash(&combined);
469
470        let mut verifier = MerkleVerifier::new(root, 1024, 2);
471        verifier.verify_chunk(0, chunk1).unwrap();
472        verifier.verify_chunk(1, chunk2).unwrap();
473
474        assert_eq!(verifier.chunks_verified(), 2);
475        assert!(verifier.is_complete());
476
477        let result = verifier.verify_merkle_root().unwrap();
478        assert!(result.verified);
479    }
480
481    #[test]
482    fn test_merkle_verifier_incomplete() {
483        let data = b"Hello, World!";
484        let chunk_hash: [u8; 32] = hash(data);
485
486        let mut verifier = MerkleVerifier::new(chunk_hash, 1024, 2);
487        verifier.verify_chunk(0, data).unwrap();
488
489        assert!(!verifier.is_complete());
490        assert_eq!(verifier.chunks_verified(), 1);
491
492        let result = verifier.verify_merkle_root();
493        assert!(result.is_err());
494    }
495
496    #[test]
497    fn test_merkle_verifier_progress() {
498        let chunk1 = b"Hello";
499        let chunk_hash: [u8; 32] = hash(chunk1);
500
501        let mut verifier = MerkleVerifier::with_default_chunk_size(chunk_hash, 4);
502        verifier.verify_chunk(0, chunk1).unwrap();
503
504        let progress = verifier.progress();
505        assert_eq!(progress.chunks_processed, 1);
506        assert_eq!(progress.percentage, 25.0);
507    }
508
509    #[test]
510    fn test_streaming_verifier_reset() {
511        let data = b"Hello, World!";
512        let expected_hash: [u8; 32] = hash(data);
513
514        let mut verifier = StreamingVerifier::new(expected_hash);
515        verifier.update(data);
516
517        verifier.reset();
518
519        assert_eq!(verifier.bytes_processed, 0);
520        assert_eq!(verifier.chunks_processed, 0);
521    }
522
523    #[test]
524    fn test_verification_result_helpers() {
525        let result = VerificationResult {
526            verified: false,
527            bytes_verified: 100,
528            actual_hash: [1u8; 32],
529            expected_hash: [2u8; 32],
530            chunks_verified: 10,
531        };
532
533        assert!(!result.is_verified());
534        assert!(result.hash_mismatch().is_some());
535        let (expected, actual) = result.hash_mismatch().unwrap();
536        assert_eq!(expected, [2u8; 32]);
537        assert_eq!(actual, [1u8; 32]);
538    }
539}