use std::io::{self, Read};
pub type Hash = [u8; 32];
pub const HASH_BUFFER_SIZE: usize = 64 * 1024;
pub fn hash(data: &[u8]) -> Hash {
*blake3::hash(data).as_bytes()
}
pub fn hash_multi(chunks: &[&[u8]]) -> Hash {
let mut hasher = blake3::Hasher::new();
for chunk in chunks {
hasher.update(chunk);
}
*hasher.finalize().as_bytes()
}
pub fn verify_hash(data: &[u8], expected: &Hash) -> bool {
&hash(data) == expected
}
pub fn keyed_hash(key: &[u8; 32], data: &[u8]) -> Hash {
*blake3::keyed_hash(key, data).as_bytes()
}
pub struct IncrementalHasher {
inner: blake3::Hasher,
bytes_processed: u64,
}
impl Default for IncrementalHasher {
fn default() -> Self {
Self::new()
}
}
impl IncrementalHasher {
pub fn new() -> Self {
Self {
inner: blake3::Hasher::new(),
bytes_processed: 0,
}
}
pub fn new_keyed(key: &[u8; 32]) -> Self {
Self {
inner: blake3::Hasher::new_keyed(key),
bytes_processed: 0,
}
}
pub fn update(&mut self, data: &[u8]) {
self.inner.update(data);
self.bytes_processed += data.len() as u64;
}
pub fn bytes_processed(&self) -> u64 {
self.bytes_processed
}
pub fn finalize(self) -> Hash {
*self.inner.finalize().as_bytes()
}
pub fn finalize_reset(&mut self) -> Hash {
let hash = *self.inner.finalize().as_bytes();
self.inner.reset();
self.bytes_processed = 0;
hash
}
pub fn update_reader<R: Read>(&mut self, reader: &mut R) -> io::Result<u64> {
let mut buffer = [0u8; HASH_BUFFER_SIZE];
let mut total = 0u64;
loop {
let bytes_read = reader.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
self.update(&buffer[..bytes_read]);
total += bytes_read as u64;
}
Ok(total)
}
}
pub fn hash_reader<R: Read>(reader: &mut R) -> io::Result<Hash> {
let mut hasher = IncrementalHasher::new();
hasher.update_reader(reader)?;
Ok(hasher.finalize())
}
#[derive(Debug, Clone)]
pub struct HashResult {
pub hash: Hash,
pub bytes_processed: u64,
}
pub struct ChunkHasher {
chunk_hashes: Vec<Hash>,
root_hasher: IncrementalHasher,
}
impl Default for ChunkHasher {
fn default() -> Self {
Self::new()
}
}
impl ChunkHasher {
pub fn new() -> Self {
Self {
chunk_hashes: Vec::new(),
root_hasher: IncrementalHasher::new(),
}
}
pub fn add_chunk(&mut self, chunk: &[u8]) -> Hash {
let chunk_hash = hash(chunk);
self.chunk_hashes.push(chunk_hash);
self.root_hasher.update(&chunk_hash);
chunk_hash
}
pub fn chunk_count(&self) -> usize {
self.chunk_hashes.len()
}
pub fn chunk_hashes(&self) -> &[Hash] {
&self.chunk_hashes
}
pub fn finalize(self) -> ChunkHashResult {
ChunkHashResult {
chunk_hashes: self.chunk_hashes,
root_hash: self.root_hasher.finalize(),
}
}
}
#[derive(Debug, Clone)]
pub struct ChunkHashResult {
pub chunk_hashes: Vec<Hash>,
pub root_hash: Hash,
}
impl ChunkHashResult {
pub fn verify_chunk(&self, index: usize, chunk: &[u8]) -> bool {
if index >= self.chunk_hashes.len() {
return false;
}
hash(chunk) == self.chunk_hashes[index]
}
pub fn chunk_count(&self) -> usize {
self.chunk_hashes.len()
}
}
pub fn hash_chunked<R: Read>(reader: &mut R, chunk_size: usize) -> io::Result<ChunkHashResult> {
let mut chunk_hasher = ChunkHasher::new();
let mut buffer = vec![0u8; chunk_size];
loop {
let mut total_read = 0;
while total_read < chunk_size {
let bytes_read = reader.read(&mut buffer[total_read..])?;
if bytes_read == 0 {
break;
}
total_read += bytes_read;
}
if total_read == 0 {
break;
}
chunk_hasher.add_chunk(&buffer[..total_read]);
if total_read < chunk_size {
break;
}
}
Ok(chunk_hasher.finalize())
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_hash() {
let data = b"Hello, CHIE Protocol!";
let h = hash(data);
assert!(verify_hash(data, &h));
assert!(!verify_hash(b"Different data", &h));
}
#[test]
fn test_hash_multi() {
let chunk1 = b"Hello, ";
let chunk2 = b"CHIE Protocol!";
let combined = b"Hello, CHIE Protocol!";
let h_multi = hash_multi(&[chunk1, chunk2]);
let h_combined = hash(combined);
assert_eq!(h_multi, h_combined);
}
#[test]
fn test_incremental_hasher() {
let data = b"Hello, CHIE Protocol!";
let h1 = hash(data);
let mut hasher = IncrementalHasher::new();
hasher.update(b"Hello, ");
hasher.update(b"CHIE ");
hasher.update(b"Protocol!");
let h2 = hasher.finalize();
assert_eq!(h1, h2);
}
#[test]
fn test_hash_reader() {
let data = b"Hello, CHIE Protocol!";
let mut cursor = Cursor::new(data);
let h1 = hash(data);
let h2 = hash_reader(&mut cursor).unwrap();
assert_eq!(h1, h2);
}
#[test]
fn test_chunk_hasher() {
let chunk1 = b"Chunk 1 data";
let chunk2 = b"Chunk 2 data";
let chunk3 = b"Chunk 3 data";
let mut hasher = ChunkHasher::new();
hasher.add_chunk(chunk1);
hasher.add_chunk(chunk2);
hasher.add_chunk(chunk3);
let result = hasher.finalize();
assert_eq!(result.chunk_count(), 3);
assert!(result.verify_chunk(0, chunk1));
assert!(result.verify_chunk(1, chunk2));
assert!(result.verify_chunk(2, chunk3));
assert!(!result.verify_chunk(0, chunk2));
}
#[test]
fn test_hash_chunked() {
let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
let mut cursor = Cursor::new(data);
let result = hash_chunked(&mut cursor, 10).unwrap();
assert_eq!(result.chunk_count(), 3);
assert!(result.verify_chunk(0, b"ABCDEFGHIJ"));
assert!(result.verify_chunk(1, b"KLMNOPQRST"));
assert!(result.verify_chunk(2, b"UVWXYZ"));
}
#[test]
fn test_keyed_hash() {
let key = [0u8; 32];
let data = b"Hello, CHIE Protocol!";
let h1 = keyed_hash(&key, data);
let h2 = keyed_hash(&key, data);
let h3 = keyed_hash(&[1u8; 32], data);
assert_eq!(h1, h2);
assert_ne!(h1, h3); }
}