base_d/features/
hashing.rs

1use blake2::{Blake2b512, Blake2s256};
2use blake3::Hasher as Blake3Hasher;
3use md5::Md5;
4use sha2::{Digest, Sha224, Sha256, Sha384, Sha512};
5use sha3::{Keccak224, Keccak256, Keccak384, Keccak512, Sha3_224, Sha3_256, Sha3_384, Sha3_512};
6use std::hash::Hasher;
7use twox_hash::xxhash3_64::Hasher as Xxh3Hash64;
8use twox_hash::xxhash3_128::Hasher as Xxh3Hash128;
9use twox_hash::{XxHash32, XxHash64};
10
11/// Configuration for xxHash algorithms.
12#[derive(Debug, Clone, Default)]
13pub struct XxHashConfig {
14    /// Seed value (0-u64::MAX)
15    pub seed: u64,
16    /// Secret for XXH3 variants (must be >= 136 bytes)
17    pub secret: Option<Vec<u8>>,
18}
19
20impl XxHashConfig {
21    /// Create config with a custom seed.
22    pub fn with_seed(seed: u64) -> Self {
23        Self { seed, secret: None }
24    }
25
26    /// Create config with seed and secret for XXH3 variants.
27    /// Secret must be at least 136 bytes.
28    pub fn with_secret(seed: u64, secret: Vec<u8>) -> Result<Self, String> {
29        if secret.len() < 136 {
30            return Err(format!(
31                "XXH3 secret must be >= 136 bytes, got {}",
32                secret.len()
33            ));
34        }
35        Ok(Self {
36            seed,
37            secret: Some(secret),
38        })
39    }
40}
41
42/// Supported hash algorithms.
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum HashAlgorithm {
45    Md5,
46    Sha224,
47    Sha256,
48    Sha384,
49    Sha512,
50    Sha3_224,
51    Sha3_256,
52    Sha3_384,
53    Sha3_512,
54    Keccak224,
55    Keccak256,
56    Keccak384,
57    Keccak512,
58    Blake2b,
59    Blake2s,
60    Blake3,
61    // CRC variants
62    Crc32,
63    Crc32c,
64    Crc16,
65    Crc64,
66    // xxHash variants
67    XxHash32,
68    XxHash64,
69    XxHash3_64,
70    XxHash3_128,
71}
72
73impl HashAlgorithm {
74    /// Returns all available hash algorithms.
75    pub fn all() -> Vec<HashAlgorithm> {
76        vec![
77            HashAlgorithm::Md5,
78            HashAlgorithm::Sha224,
79            HashAlgorithm::Sha256,
80            HashAlgorithm::Sha384,
81            HashAlgorithm::Sha512,
82            HashAlgorithm::Sha3_224,
83            HashAlgorithm::Sha3_256,
84            HashAlgorithm::Sha3_384,
85            HashAlgorithm::Sha3_512,
86            HashAlgorithm::Keccak224,
87            HashAlgorithm::Keccak256,
88            HashAlgorithm::Keccak384,
89            HashAlgorithm::Keccak512,
90            HashAlgorithm::Blake2b,
91            HashAlgorithm::Blake2s,
92            HashAlgorithm::Blake3,
93            HashAlgorithm::Crc32,
94            HashAlgorithm::Crc32c,
95            HashAlgorithm::Crc16,
96            HashAlgorithm::Crc64,
97            HashAlgorithm::XxHash32,
98            HashAlgorithm::XxHash64,
99            HashAlgorithm::XxHash3_64,
100            HashAlgorithm::XxHash3_128,
101        ]
102    }
103
104    /// Select a random hash algorithm.
105    pub fn random() -> HashAlgorithm {
106        use rand::prelude::IndexedRandom;
107        let all = Self::all();
108        *all.choose(&mut rand::rng()).unwrap()
109    }
110
111    /// Parse hash algorithm from string.
112    #[allow(clippy::should_implement_trait)]
113    pub fn from_str(s: &str) -> Result<Self, String> {
114        match s.to_lowercase().as_str() {
115            "md5" => Ok(HashAlgorithm::Md5),
116            "sha224" | "sha-224" => Ok(HashAlgorithm::Sha224),
117            "sha256" | "sha-256" => Ok(HashAlgorithm::Sha256),
118            "sha384" | "sha-384" => Ok(HashAlgorithm::Sha384),
119            "sha512" | "sha-512" => Ok(HashAlgorithm::Sha512),
120            "sha3-224" | "sha3_224" => Ok(HashAlgorithm::Sha3_224),
121            "sha3-256" | "sha3_256" => Ok(HashAlgorithm::Sha3_256),
122            "sha3-384" | "sha3_384" => Ok(HashAlgorithm::Sha3_384),
123            "sha3-512" | "sha3_512" => Ok(HashAlgorithm::Sha3_512),
124            "keccak224" | "keccak-224" => Ok(HashAlgorithm::Keccak224),
125            "keccak256" | "keccak-256" => Ok(HashAlgorithm::Keccak256),
126            "keccak384" | "keccak-384" => Ok(HashAlgorithm::Keccak384),
127            "keccak512" | "keccak-512" => Ok(HashAlgorithm::Keccak512),
128            "blake2b" | "blake2b-512" => Ok(HashAlgorithm::Blake2b),
129            "blake2s" | "blake2s-256" => Ok(HashAlgorithm::Blake2s),
130            "blake3" => Ok(HashAlgorithm::Blake3),
131            "crc32" => Ok(HashAlgorithm::Crc32),
132            "crc32c" => Ok(HashAlgorithm::Crc32c),
133            "crc16" => Ok(HashAlgorithm::Crc16),
134            "crc64" => Ok(HashAlgorithm::Crc64),
135            "xxhash32" | "xxh32" => Ok(HashAlgorithm::XxHash32),
136            "xxhash64" | "xxh64" => Ok(HashAlgorithm::XxHash64),
137            "xxhash3" | "xxh3" | "xxhash3-64" | "xxh3-64" => Ok(HashAlgorithm::XxHash3_64),
138            "xxhash3-128" | "xxh3-128" => Ok(HashAlgorithm::XxHash3_128),
139            _ => Err(format!("Unknown hash algorithm: {}", s)),
140        }
141    }
142
143    pub fn as_str(&self) -> &str {
144        match self {
145            HashAlgorithm::Md5 => "md5",
146            HashAlgorithm::Sha224 => "sha224",
147            HashAlgorithm::Sha256 => "sha256",
148            HashAlgorithm::Sha384 => "sha384",
149            HashAlgorithm::Sha512 => "sha512",
150            HashAlgorithm::Sha3_224 => "sha3-224",
151            HashAlgorithm::Sha3_256 => "sha3-256",
152            HashAlgorithm::Sha3_384 => "sha3-384",
153            HashAlgorithm::Sha3_512 => "sha3-512",
154            HashAlgorithm::Keccak224 => "keccak224",
155            HashAlgorithm::Keccak256 => "keccak256",
156            HashAlgorithm::Keccak384 => "keccak384",
157            HashAlgorithm::Keccak512 => "keccak512",
158            HashAlgorithm::Blake2b => "blake2b",
159            HashAlgorithm::Blake2s => "blake2s",
160            HashAlgorithm::Blake3 => "blake3",
161            HashAlgorithm::Crc32 => "crc32",
162            HashAlgorithm::Crc32c => "crc32c",
163            HashAlgorithm::Crc16 => "crc16",
164            HashAlgorithm::Crc64 => "crc64",
165            HashAlgorithm::XxHash32 => "xxhash32",
166            HashAlgorithm::XxHash64 => "xxhash64",
167            HashAlgorithm::XxHash3_64 => "xxhash3-64",
168            HashAlgorithm::XxHash3_128 => "xxhash3-128",
169        }
170    }
171
172    /// Get the output size in bytes for this algorithm.
173    pub fn output_size(&self) -> usize {
174        match self {
175            HashAlgorithm::Md5 => 16,
176            HashAlgorithm::Sha224 => 28,
177            HashAlgorithm::Sha256 => 32,
178            HashAlgorithm::Sha384 => 48,
179            HashAlgorithm::Sha512 => 64,
180            HashAlgorithm::Sha3_224 => 28,
181            HashAlgorithm::Sha3_256 => 32,
182            HashAlgorithm::Sha3_384 => 48,
183            HashAlgorithm::Sha3_512 => 64,
184            HashAlgorithm::Keccak224 => 28,
185            HashAlgorithm::Keccak256 => 32,
186            HashAlgorithm::Keccak384 => 48,
187            HashAlgorithm::Keccak512 => 64,
188            HashAlgorithm::Blake2b => 64,
189            HashAlgorithm::Blake2s => 32,
190            HashAlgorithm::Blake3 => 32,
191            HashAlgorithm::Crc16 => 2,
192            HashAlgorithm::Crc32 => 4,
193            HashAlgorithm::Crc32c => 4,
194            HashAlgorithm::Crc64 => 8,
195            HashAlgorithm::XxHash32 => 4,
196            HashAlgorithm::XxHash64 => 8,
197            HashAlgorithm::XxHash3_64 => 8,
198            HashAlgorithm::XxHash3_128 => 16,
199        }
200    }
201}
202
203/// Compute hash of data using the specified algorithm.
204/// Uses default configuration (seed = 0, no secret).
205pub fn hash(data: &[u8], algorithm: HashAlgorithm) -> Vec<u8> {
206    hash_with_config(data, algorithm, &XxHashConfig::default())
207}
208
209/// Compute hash of data using the specified algorithm with custom configuration.
210pub fn hash_with_config(data: &[u8], algorithm: HashAlgorithm, config: &XxHashConfig) -> Vec<u8> {
211    match algorithm {
212        HashAlgorithm::Md5 => {
213            let mut hasher = Md5::new();
214            hasher.update(data);
215            hasher.finalize().to_vec()
216        }
217        HashAlgorithm::Sha224 => {
218            let mut hasher = Sha224::new();
219            hasher.update(data);
220            hasher.finalize().to_vec()
221        }
222        HashAlgorithm::Sha256 => {
223            let mut hasher = Sha256::new();
224            hasher.update(data);
225            hasher.finalize().to_vec()
226        }
227        HashAlgorithm::Sha384 => {
228            let mut hasher = Sha384::new();
229            hasher.update(data);
230            hasher.finalize().to_vec()
231        }
232        HashAlgorithm::Sha512 => {
233            let mut hasher = Sha512::new();
234            hasher.update(data);
235            hasher.finalize().to_vec()
236        }
237        HashAlgorithm::Sha3_224 => {
238            let mut hasher = Sha3_224::new();
239            hasher.update(data);
240            hasher.finalize().to_vec()
241        }
242        HashAlgorithm::Sha3_256 => {
243            let mut hasher = Sha3_256::new();
244            hasher.update(data);
245            hasher.finalize().to_vec()
246        }
247        HashAlgorithm::Sha3_384 => {
248            let mut hasher = Sha3_384::new();
249            hasher.update(data);
250            hasher.finalize().to_vec()
251        }
252        HashAlgorithm::Sha3_512 => {
253            let mut hasher = Sha3_512::new();
254            hasher.update(data);
255            hasher.finalize().to_vec()
256        }
257        HashAlgorithm::Keccak224 => {
258            let mut hasher = Keccak224::new();
259            hasher.update(data);
260            hasher.finalize().to_vec()
261        }
262        HashAlgorithm::Keccak256 => {
263            let mut hasher = Keccak256::new();
264            hasher.update(data);
265            hasher.finalize().to_vec()
266        }
267        HashAlgorithm::Keccak384 => {
268            let mut hasher = Keccak384::new();
269            hasher.update(data);
270            hasher.finalize().to_vec()
271        }
272        HashAlgorithm::Keccak512 => {
273            let mut hasher = Keccak512::new();
274            hasher.update(data);
275            hasher.finalize().to_vec()
276        }
277        HashAlgorithm::Blake2b => {
278            let mut hasher = Blake2b512::new();
279            hasher.update(data);
280            hasher.finalize().to_vec()
281        }
282        HashAlgorithm::Blake2s => {
283            let mut hasher = Blake2s256::new();
284            hasher.update(data);
285            hasher.finalize().to_vec()
286        }
287        HashAlgorithm::Blake3 => {
288            let mut hasher = Blake3Hasher::new();
289            hasher.update(data);
290            hasher.finalize().as_bytes().to_vec()
291        }
292        HashAlgorithm::Crc16 => {
293            let crc = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC);
294            let result = crc.checksum(data);
295            result.to_be_bytes().to_vec()
296        }
297        HashAlgorithm::Crc32 => {
298            let crc = crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC);
299            let result = crc.checksum(data);
300            result.to_be_bytes().to_vec()
301        }
302        HashAlgorithm::Crc32c => {
303            let crc = crc::Crc::<u32>::new(&crc::CRC_32_ISCSI);
304            let result = crc.checksum(data);
305            result.to_be_bytes().to_vec()
306        }
307        HashAlgorithm::Crc64 => {
308            let crc = crc::Crc::<u64>::new(&crc::CRC_64_ECMA_182);
309            let result = crc.checksum(data);
310            result.to_be_bytes().to_vec()
311        }
312        HashAlgorithm::XxHash32 => {
313            let mut hasher = XxHash32::with_seed(config.seed as u32);
314            hasher.write(data);
315            (hasher.finish() as u32).to_be_bytes().to_vec()
316        }
317        HashAlgorithm::XxHash64 => {
318            let mut hasher = XxHash64::with_seed(config.seed);
319            hasher.write(data);
320            hasher.finish().to_be_bytes().to_vec()
321        }
322        HashAlgorithm::XxHash3_64 => {
323            let mut hasher = if let Some(ref secret) = config.secret {
324                Xxh3Hash64::with_seed_and_secret(config.seed, secret.as_slice()).expect(
325                    "XXH3 secret validation should have been done in XxHashConfig::with_secret",
326                )
327            } else {
328                Xxh3Hash64::with_seed(config.seed)
329            };
330            hasher.write(data);
331            hasher.finish().to_be_bytes().to_vec()
332        }
333        HashAlgorithm::XxHash3_128 => {
334            let mut hasher = if let Some(ref secret) = config.secret {
335                Xxh3Hash128::with_seed_and_secret(config.seed, secret.as_slice()).expect(
336                    "XXH3 secret validation should have been done in XxHashConfig::with_secret",
337                )
338            } else {
339                Xxh3Hash128::with_seed(config.seed)
340            };
341            hasher.write(data);
342            hasher.finish_128().to_be_bytes().to_vec()
343        }
344    }
345}
346
347#[cfg(test)]
348#[allow(deprecated)]
349mod tests {
350    use super::*;
351
352    #[test]
353    fn test_md5() {
354        let data = b"hello world";
355        let hash = hash(data, HashAlgorithm::Md5);
356        assert_eq!(hash.len(), 16);
357        // MD5 of "hello world" is 5eb63bbbe01eeed093cb22bb8f5acdc3
358        assert_eq!(hex::encode(&hash), "5eb63bbbe01eeed093cb22bb8f5acdc3");
359    }
360
361    #[test]
362    fn test_sha256() {
363        let data = b"hello world";
364        let hash = hash(data, HashAlgorithm::Sha256);
365        assert_eq!(hash.len(), 32);
366        // SHA-256 of "hello world"
367        assert_eq!(
368            hex::encode(&hash),
369            "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
370        );
371    }
372
373    #[test]
374    fn test_sha512() {
375        let data = b"hello world";
376        let hash = hash(data, HashAlgorithm::Sha512);
377        assert_eq!(hash.len(), 64);
378    }
379
380    #[test]
381    fn test_sha3_256() {
382        let data = b"hello world";
383        let hash = hash(data, HashAlgorithm::Sha3_256);
384        assert_eq!(hash.len(), 32);
385    }
386
387    #[test]
388    fn test_blake2b() {
389        let data = b"hello world";
390        let hash = hash(data, HashAlgorithm::Blake2b);
391        assert_eq!(hash.len(), 64);
392    }
393
394    #[test]
395    fn test_blake2s() {
396        let data = b"hello world";
397        let hash = hash(data, HashAlgorithm::Blake2s);
398        assert_eq!(hash.len(), 32);
399    }
400
401    #[test]
402    fn test_blake3() {
403        let data = b"hello world";
404        let hash = hash(data, HashAlgorithm::Blake3);
405        assert_eq!(hash.len(), 32);
406    }
407
408    #[test]
409    fn test_empty_input() {
410        let data = b"";
411        let hash = hash(data, HashAlgorithm::Sha256);
412        assert_eq!(hash.len(), 32);
413        // SHA-256 of empty string
414        assert_eq!(
415            hex::encode(&hash),
416            "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
417        );
418    }
419
420    #[test]
421    fn test_output_sizes() {
422        assert_eq!(HashAlgorithm::Md5.output_size(), 16);
423        assert_eq!(HashAlgorithm::Sha256.output_size(), 32);
424        assert_eq!(HashAlgorithm::Sha512.output_size(), 64);
425        assert_eq!(HashAlgorithm::Blake3.output_size(), 32);
426        assert_eq!(HashAlgorithm::Crc16.output_size(), 2);
427        assert_eq!(HashAlgorithm::Crc32.output_size(), 4);
428        assert_eq!(HashAlgorithm::Crc64.output_size(), 8);
429        assert_eq!(HashAlgorithm::XxHash32.output_size(), 4);
430        assert_eq!(HashAlgorithm::XxHash64.output_size(), 8);
431        assert_eq!(HashAlgorithm::XxHash3_64.output_size(), 8);
432        assert_eq!(HashAlgorithm::XxHash3_128.output_size(), 16);
433    }
434
435    #[test]
436    fn test_crc32() {
437        let data = b"hello world";
438        let result = hash(data, HashAlgorithm::Crc32);
439        assert_eq!(result.len(), 4);
440        // CRC32 is deterministic
441        let result2 = hash(data, HashAlgorithm::Crc32);
442        assert_eq!(result, result2);
443    }
444
445    #[test]
446    fn test_crc32c() {
447        let data = b"hello world";
448        let result = hash(data, HashAlgorithm::Crc32c);
449        assert_eq!(result.len(), 4);
450    }
451
452    #[test]
453    fn test_crc16() {
454        let data = b"hello world";
455        let result = hash(data, HashAlgorithm::Crc16);
456        assert_eq!(result.len(), 2);
457    }
458
459    #[test]
460    fn test_crc64() {
461        let data = b"hello world";
462        let result = hash(data, HashAlgorithm::Crc64);
463        assert_eq!(result.len(), 8);
464    }
465
466    #[test]
467    fn test_xxhash32() {
468        let data = b"hello world";
469        let result = hash(data, HashAlgorithm::XxHash32);
470        assert_eq!(result.len(), 4);
471        // xxHash is deterministic with same seed
472        let result2 = hash(data, HashAlgorithm::XxHash32);
473        assert_eq!(result, result2);
474    }
475
476    #[test]
477    fn test_xxhash64() {
478        let data = b"hello world";
479        let result = hash(data, HashAlgorithm::XxHash64);
480        assert_eq!(result.len(), 8);
481    }
482
483    #[test]
484    fn test_xxhash3_64() {
485        let data = b"hello world";
486        let result = hash(data, HashAlgorithm::XxHash3_64);
487        assert_eq!(result.len(), 8);
488    }
489
490    #[test]
491    fn test_xxhash3_128() {
492        let data = b"hello world";
493        let result = hash(data, HashAlgorithm::XxHash3_128);
494        assert_eq!(result.len(), 16);
495    }
496
497    #[test]
498    fn test_xxhash_config_default() {
499        let config = XxHashConfig::default();
500        assert_eq!(config.seed, 0);
501        assert!(config.secret.is_none());
502    }
503
504    #[test]
505    fn test_xxhash_config_secret_too_short() {
506        let result = XxHashConfig::with_secret(0, vec![0u8; 100]);
507        assert!(result.is_err());
508        assert!(result.unwrap_err().contains("136 bytes"));
509    }
510
511    #[test]
512    fn test_xxhash_config_secret_valid() {
513        let result = XxHashConfig::with_secret(42, vec![0u8; 136]);
514        assert!(result.is_ok());
515        let config = result.unwrap();
516        assert_eq!(config.seed, 42);
517        assert_eq!(config.secret.as_ref().unwrap().len(), 136);
518    }
519
520    #[test]
521    fn test_hash_seed_changes_output() {
522        let data = b"test";
523        let h1 = hash_with_config(data, HashAlgorithm::XxHash64, &XxHashConfig::with_seed(0));
524        let h2 = hash_with_config(data, HashAlgorithm::XxHash64, &XxHashConfig::with_seed(42));
525        assert_ne!(h1, h2);
526    }
527
528    #[test]
529    fn test_backward_compatibility() {
530        let data = b"test";
531        let old = hash(data, HashAlgorithm::XxHash64);
532        let new = hash_with_config(data, HashAlgorithm::XxHash64, &XxHashConfig::default());
533        assert_eq!(old, new);
534    }
535
536    #[test]
537    fn test_xxhash3_with_seed() {
538        let data = b"test data for secret hashing";
539
540        // Test that different seeds produce different hashes
541        let h1 = hash_with_config(data, HashAlgorithm::XxHash3_64, &XxHashConfig::with_seed(0));
542        let h2 = hash_with_config(
543            data,
544            HashAlgorithm::XxHash3_64,
545            &XxHashConfig::with_seed(123),
546        );
547        assert_ne!(h1, h2, "Different seeds should produce different hashes");
548
549        // Test that same seed produces same hash
550        let h3 = hash_with_config(
551            data,
552            HashAlgorithm::XxHash3_64,
553            &XxHashConfig::with_seed(123),
554        );
555        assert_eq!(h2, h3, "Same seed should produce same hash");
556    }
557
558    #[test]
559    fn test_xxhash32_with_seed() {
560        let data = b"test";
561        let h1 = hash_with_config(data, HashAlgorithm::XxHash32, &XxHashConfig::with_seed(0));
562        let h2 = hash_with_config(data, HashAlgorithm::XxHash32, &XxHashConfig::with_seed(999));
563        assert_ne!(h1, h2);
564    }
565}