Skip to main content

sumchain_primitives/
hash.rs

1//! Cryptographic hash types for SUM Chain.
2//!
3//! We use Blake3 for all hashing operations. Blake3 is:
4//! - Extremely fast (faster than MD5 while being cryptographically secure)
5//! - Designed by world-class cryptographers (same team as Blake2)
6//! - The reference implementation is written in Rust
7//! - Parallelizable and SIMD-optimized
8
9use serde::{Deserialize, Serialize};
10use std::fmt;
11
12use crate::{PrimitiveError, Result};
13
14/// 32-byte hash (Blake3 output)
15#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
16pub struct Hash([u8; 32]);
17
18impl Hash {
19    /// Size of the hash in bytes
20    pub const SIZE: usize = 32;
21
22    /// Zero hash (all zeros)
23    pub const ZERO: Hash = Hash([0u8; 32]);
24
25    /// Create a new hash from bytes
26    pub fn new(bytes: [u8; 32]) -> Self {
27        Hash(bytes)
28    }
29
30    /// Create hash from a slice
31    pub fn from_slice(slice: &[u8]) -> Result<Self> {
32        if slice.len() != Self::SIZE {
33            return Err(PrimitiveError::InvalidLength {
34                expected: Self::SIZE,
35                got: slice.len(),
36            });
37        }
38        let mut bytes = [0u8; 32];
39        bytes.copy_from_slice(slice);
40        Ok(Hash(bytes))
41    }
42
43    /// Create hash from hex string
44    pub fn from_hex(s: &str) -> Result<Self> {
45        let s = s.strip_prefix("0x").unwrap_or(s);
46        let bytes = hex::decode(s).map_err(|e| PrimitiveError::InvalidHex(e.to_string()))?;
47        Self::from_slice(&bytes)
48    }
49
50    /// Hash arbitrary data using Blake3
51    pub fn hash(data: &[u8]) -> Self {
52        let result = blake3::hash(data);
53        Hash(*result.as_bytes())
54    }
55
56    /// Hash multiple pieces of data
57    pub fn hash_many(data: &[&[u8]]) -> Self {
58        let mut hasher = blake3::Hasher::new();
59        for d in data {
60            hasher.update(d);
61        }
62        let result = hasher.finalize();
63        Hash(*result.as_bytes())
64    }
65
66    /// Get the raw bytes
67    pub fn as_bytes(&self) -> &[u8; 32] {
68        &self.0
69    }
70
71    /// Convert to hex string (with 0x prefix)
72    pub fn to_hex(&self) -> String {
73        format!("0x{}", hex::encode(self.0))
74    }
75
76    /// Check if this is the zero hash
77    pub fn is_zero(&self) -> bool {
78        self.0 == [0u8; 32]
79    }
80
81    /// Compute merkle root from a list of hashes
82    /// Uses a simple binary merkle tree construction
83    pub fn merkle_root(hashes: &[Hash]) -> Hash {
84        if hashes.is_empty() {
85            return Hash::ZERO;
86        }
87        if hashes.len() == 1 {
88            return hashes[0];
89        }
90
91        let mut current_level: Vec<Hash> = hashes.to_vec();
92
93        while current_level.len() > 1 {
94            let mut next_level = Vec::new();
95
96            for chunk in current_level.chunks(2) {
97                let left = &chunk[0];
98                let right = chunk.get(1).unwrap_or(left); // Duplicate last if odd
99
100                let combined = Hash::hash_many(&[left.as_bytes(), right.as_bytes()]);
101                next_level.push(combined);
102            }
103
104            current_level = next_level;
105        }
106
107        current_level[0]
108    }
109}
110
111impl fmt::Debug for Hash {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        write!(f, "Hash({})", self.to_hex())
114    }
115}
116
117impl fmt::Display for Hash {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        write!(f, "{}", self.to_hex())
120    }
121}
122
123impl AsRef<[u8]> for Hash {
124    fn as_ref(&self) -> &[u8] {
125        &self.0
126    }
127}
128
129impl From<[u8; 32]> for Hash {
130    fn from(bytes: [u8; 32]) -> Self {
131        Hash(bytes)
132    }
133}
134
135impl PartialOrd for Hash {
136    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
137        Some(self.cmp(other))
138    }
139}
140
141impl Ord for Hash {
142    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
143        self.0.cmp(&other.0)
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn test_hash_consistency() {
153        let data = b"hello world";
154        let h1 = Hash::hash(data);
155        let h2 = Hash::hash(data);
156        assert_eq!(h1, h2);
157    }
158
159    #[test]
160    fn test_hash_different_data() {
161        let h1 = Hash::hash(b"hello");
162        let h2 = Hash::hash(b"world");
163        assert_ne!(h1, h2);
164    }
165
166    #[test]
167    fn test_hex_roundtrip() {
168        let h = Hash::hash(b"test data");
169        let hex = h.to_hex();
170        let h2 = Hash::from_hex(&hex).unwrap();
171        assert_eq!(h, h2);
172    }
173
174    #[test]
175    fn test_merkle_root_empty() {
176        assert_eq!(Hash::merkle_root(&[]), Hash::ZERO);
177    }
178
179    #[test]
180    fn test_merkle_root_single() {
181        let h = Hash::hash(b"single");
182        assert_eq!(Hash::merkle_root(&[h]), h);
183    }
184
185    #[test]
186    fn test_merkle_root_multiple() {
187        let hashes: Vec<Hash> = (0..4).map(|i| Hash::hash(&[i])).collect();
188        let root = Hash::merkle_root(&hashes);
189        assert_ne!(root, Hash::ZERO);
190
191        // Verify determinism
192        let root2 = Hash::merkle_root(&hashes);
193        assert_eq!(root, root2);
194    }
195}