Skip to main content

saorsa_pqc/api/
hash.rs

1//! Hash function implementations
2//!
3//! Provides quantum-resistant hash functions including:
4//! - BLAKE3 (256-bit)
5//! - SHA3-256 and SHA3-512
6//! - SHAKE256 (extensible output)
7
8use crate::api::traits::Hash;
9use blake3;
10use sha3::{Digest, Sha3_256, Sha3_512};
11
12/// BLAKE3 hasher - high performance, quantum-resistant
13pub struct Blake3Hasher {
14    hasher: blake3::Hasher,
15}
16
17/// BLAKE3 hash output
18#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct Blake3Output(blake3::Hash);
20
21impl AsRef<[u8]> for Blake3Output {
22    fn as_ref(&self) -> &[u8] {
23        self.0.as_bytes()
24    }
25}
26
27impl Hash for Blake3Hasher {
28    type Output = Blake3Output;
29
30    fn new() -> Self {
31        Self {
32            hasher: blake3::Hasher::new(),
33        }
34    }
35
36    fn update(&mut self, data: &[u8]) {
37        self.hasher.update(data);
38    }
39
40    fn finalize(self) -> Self::Output {
41        Blake3Output(self.hasher.finalize())
42    }
43
44    fn output_size() -> usize {
45        32 // 256 bits
46    }
47
48    fn name() -> &'static str {
49        "BLAKE3"
50    }
51}
52
53/// SHA3-256 hasher
54pub struct Sha3_256Hasher {
55    hasher: Sha3_256,
56}
57
58/// SHA3-256 output
59#[derive(Clone, Debug, PartialEq, Eq)]
60pub struct Sha3_256Output([u8; 32]);
61
62impl AsRef<[u8]> for Sha3_256Output {
63    fn as_ref(&self) -> &[u8] {
64        &self.0
65    }
66}
67
68impl Hash for Sha3_256Hasher {
69    type Output = Sha3_256Output;
70
71    fn new() -> Self {
72        Self {
73            hasher: Sha3_256::new(),
74        }
75    }
76
77    fn update(&mut self, data: &[u8]) {
78        self.hasher.update(data);
79    }
80
81    fn finalize(self) -> Self::Output {
82        let result = self.hasher.finalize();
83        let mut output = [0u8; 32];
84        output.copy_from_slice(&result);
85        Sha3_256Output(output)
86    }
87
88    fn output_size() -> usize {
89        32 // 256 bits
90    }
91
92    fn name() -> &'static str {
93        "SHA3-256"
94    }
95}
96
97/// SHA3-512 hasher
98pub struct Sha3_512Hasher {
99    hasher: Sha3_512,
100}
101
102/// SHA3-512 output
103#[derive(Clone, Debug, PartialEq, Eq)]
104pub struct Sha3_512Output([u8; 64]);
105
106impl AsRef<[u8]> for Sha3_512Output {
107    fn as_ref(&self) -> &[u8] {
108        &self.0
109    }
110}
111
112impl Hash for Sha3_512Hasher {
113    type Output = Sha3_512Output;
114
115    fn new() -> Self {
116        Self {
117            hasher: Sha3_512::new(),
118        }
119    }
120
121    fn update(&mut self, data: &[u8]) {
122        self.hasher.update(data);
123    }
124
125    fn finalize(self) -> Self::Output {
126        let result = self.hasher.finalize();
127        let mut output = [0u8; 64];
128        output.copy_from_slice(&result);
129        Sha3_512Output(output)
130    }
131
132    fn output_size() -> usize {
133        64 // 512 bits
134    }
135
136    fn name() -> &'static str {
137        "SHA3-512"
138    }
139}
140
141/// SHAKE256 extensible output function helper
142/// Note: We provide a simpler interface for SHAKE256 without the complexity
143pub struct Shake256Xof;
144
145impl Shake256Xof {
146    /// One-shot SHAKE256 - simplified version
147    #[must_use]
148    pub fn shake256(data: &[u8], output_len: usize) -> Vec<u8> {
149        use sha3::digest::{ExtendableOutput, Update, XofReader};
150        use sha3::Shake256;
151
152        let mut hasher = Shake256::default();
153        Update::update(&mut hasher, data);
154        let mut reader = hasher.finalize_xof();
155        let mut output = vec![0u8; output_len];
156        XofReader::read(&mut reader, &mut output);
157        output
158    }
159}
160
161/// High-level hash function selector
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub enum HashAlgorithm {
164    /// BLAKE3 (256-bit)
165    Blake3,
166    /// SHA3-256
167    Sha3_256,
168    /// SHA3-512
169    Sha3_512,
170}
171
172impl HashAlgorithm {
173    /// Hash data with the selected algorithm
174    #[must_use]
175    pub fn hash(&self, data: &[u8]) -> Vec<u8> {
176        match self {
177            Self::Blake3 => Blake3Hasher::hash(data).as_ref().to_vec(),
178            Self::Sha3_256 => Sha3_256Hasher::hash(data).as_ref().to_vec(),
179            Self::Sha3_512 => Sha3_512Hasher::hash(data).as_ref().to_vec(),
180        }
181    }
182
183    /// Get the output size in bytes
184    #[must_use]
185    pub fn output_size(&self) -> usize {
186        match self {
187            Self::Blake3 => Blake3Hasher::output_size(),
188            Self::Sha3_256 => Sha3_256Hasher::output_size(),
189            Self::Sha3_512 => Sha3_512Hasher::output_size(),
190        }
191    }
192
193    /// Get the algorithm name
194    #[must_use]
195    pub const fn name(&self) -> &'static str {
196        match self {
197            Self::Blake3 => "BLAKE3",
198            Self::Sha3_256 => "SHA3-256",
199            Self::Sha3_512 => "SHA3-512",
200        }
201    }
202}
203
204/// Helper functions for common hashing operations
205pub mod helpers {
206    use super::{blake3, Blake3Hasher, Hash, Sha3_256Hasher, Sha3_512Hasher, Shake256Xof};
207
208    /// Hash data with BLAKE3
209    #[must_use]
210    pub fn blake3(data: &[u8]) -> [u8; 32] {
211        let output = Blake3Hasher::hash(data);
212        let mut result = [0u8; 32];
213        result.copy_from_slice(output.as_ref());
214        result
215    }
216
217    /// Hash data with SHA3-256
218    #[must_use]
219    pub fn sha3_256(data: &[u8]) -> [u8; 32] {
220        let output = Sha3_256Hasher::hash(data);
221        let mut result = [0u8; 32];
222        result.copy_from_slice(output.as_ref());
223        result
224    }
225
226    /// Hash data with SHA3-512
227    #[must_use]
228    pub fn sha3_512(data: &[u8]) -> [u8; 64] {
229        let output = Sha3_512Hasher::hash(data);
230        let mut result = [0u8; 64];
231        result.copy_from_slice(output.as_ref());
232        result
233    }
234
235    /// SHAKE256 with custom output length
236    #[must_use]
237    pub fn shake256(data: &[u8], output_len: usize) -> Vec<u8> {
238        Shake256Xof::shake256(data, output_len)
239    }
240
241    /// Derive a key from a password using BLAKE3
242    #[must_use]
243    pub fn derive_key_blake3(context: &str, key_material: &[u8]) -> [u8; 32] {
244        let mut hasher = blake3::Hasher::new_derive_key(context);
245        hasher.update(key_material);
246        let hash = hasher.finalize();
247        *hash.as_bytes()
248    }
249}
250
251#[cfg(test)]
252#[allow(clippy::indexing_slicing)]
253#[allow(clippy::unwrap_used, clippy::expect_used)]
254mod tests {
255    use super::*;
256
257    #[test]
258    fn test_blake3_basic() {
259        let data = b"test data";
260        let hash1 = Blake3Hasher::hash(data);
261        let hash2 = helpers::blake3(data);
262        assert_eq!(hash1.as_ref(), &hash2);
263        assert_eq!(Blake3Hasher::output_size(), 32);
264        assert_eq!(Blake3Hasher::name(), "BLAKE3");
265    }
266
267    #[test]
268    fn test_sha3_256_basic() {
269        let data = b"test data";
270        let hash1 = Sha3_256Hasher::hash(data);
271        let hash2 = helpers::sha3_256(data);
272        assert_eq!(hash1.as_ref(), &hash2);
273        assert_eq!(Sha3_256Hasher::output_size(), 32);
274        assert_eq!(Sha3_256Hasher::name(), "SHA3-256");
275    }
276
277    #[test]
278    fn test_sha3_512_basic() {
279        let data = b"test data";
280        let hash1 = Sha3_512Hasher::hash(data);
281        let hash2 = helpers::sha3_512(data);
282        assert_eq!(hash1.as_ref(), &hash2);
283        assert_eq!(Sha3_512Hasher::output_size(), 64);
284        assert_eq!(Sha3_512Hasher::name(), "SHA3-512");
285    }
286
287    #[test]
288    fn test_shake256_variable_output() {
289        let data = b"test data";
290        let output_32 = helpers::shake256(data, 32);
291        let output_64 = helpers::shake256(data, 64);
292        let output_128 = helpers::shake256(data, 128);
293
294        assert_eq!(output_32.len(), 32);
295        assert_eq!(output_64.len(), 64);
296        assert_eq!(output_128.len(), 128);
297
298        // First 32 bytes should match
299        assert_eq!(&output_64[..32], &output_32[..]);
300        assert_eq!(&output_128[..32], &output_32[..]);
301    }
302
303    #[test]
304    fn test_hash_algorithm_enum() {
305        let data = b"test data";
306
307        let blake3_hash = HashAlgorithm::Blake3.hash(data);
308        assert_eq!(blake3_hash.len(), 32);
309        assert_eq!(HashAlgorithm::Blake3.output_size(), 32);
310        assert_eq!(HashAlgorithm::Blake3.name(), "BLAKE3");
311
312        let sha3_256_hash = HashAlgorithm::Sha3_256.hash(data);
313        assert_eq!(sha3_256_hash.len(), 32);
314
315        let sha3_512_hash = HashAlgorithm::Sha3_512.hash(data);
316        assert_eq!(sha3_512_hash.len(), 64);
317    }
318
319    #[test]
320    fn test_blake3_key_derivation() {
321        let context = "test context";
322        let key_material = b"secret key material";
323
324        let key1 = helpers::derive_key_blake3(context, key_material);
325        let key2 = helpers::derive_key_blake3(context, key_material);
326        assert_eq!(key1, key2); // Should be deterministic
327
328        let key3 = helpers::derive_key_blake3("different context", key_material);
329        assert_ne!(key1, key3); // Different context should give different key
330    }
331
332    #[test]
333    fn test_incremental_hashing() {
334        let data1 = b"first part";
335        let data2 = b"second part";
336        let combined = b"first partsecond part";
337
338        // BLAKE3 incremental
339        let mut hasher = Blake3Hasher::new();
340        hasher.update(data1);
341        hasher.update(data2);
342        let incremental_hash = hasher.finalize();
343
344        let direct_hash = Blake3Hasher::hash(combined);
345        assert_eq!(incremental_hash.as_ref(), direct_hash.as_ref());
346
347        // SHA3-256 incremental
348        let mut hasher = Sha3_256Hasher::new();
349        hasher.update(data1);
350        hasher.update(data2);
351        let incremental_hash = hasher.finalize();
352
353        let direct_hash = Sha3_256Hasher::hash(combined);
354        assert_eq!(incremental_hash.as_ref(), direct_hash.as_ref());
355    }
356}