Skip to main content

uselesskey_core_hash/
lib.rs

1#![forbid(unsafe_code)]
2#![cfg_attr(not(feature = "std"), no_std)]
3//! Length-prefixed hashing helpers for deterministic fixture derivation.
4//!
5//! Wraps BLAKE3 to provide collision-resistant, deterministic digests used
6//! throughout the `uselesskey` workspace for seed derivation and artifact identity.
7
8pub use blake3::{Hash, Hasher};
9
10/// Compute a BLAKE3 digest over input bytes.
11pub fn hash32(bytes: &[u8]) -> Hash {
12    blake3::hash(bytes)
13}
14
15/// Write a length-prefixed byte slice into a BLAKE3 hasher.
16///
17/// This preserves tuple boundaries when multiple fields are concatenated.
18pub fn write_len_prefixed(hasher: &mut Hasher, bytes: &[u8]) {
19    let len = u32::try_from(bytes.len()).unwrap_or(u32::MAX);
20    hasher.update(&len.to_be_bytes());
21    hasher.update(bytes);
22}
23
24#[cfg(test)]
25mod tests {
26    use super::{hash32, write_len_prefixed};
27    use blake3::Hasher;
28    use proptest::prelude::*;
29
30    #[test]
31    fn hash32_matches_blake3_hash() {
32        let data = b"deterministic-fixture-hash";
33        assert_eq!(hash32(data), blake3::hash(data));
34    }
35
36    #[test]
37    fn write_len_prefixed_uses_big_endian_length() {
38        let data = b"abc";
39        let expected_len = 3u32.to_be_bytes();
40
41        let mut hasher = Hasher::new();
42        hasher.update(&expected_len);
43        hasher.update(data);
44        let expected = hasher.finalize();
45
46        let mut hasher2 = Hasher::new();
47        write_len_prefixed(&mut hasher2, data);
48        let actual = hasher2.finalize();
49
50        assert_eq!(actual, expected);
51    }
52
53    #[test]
54    fn len_prefix_changes_on_boundary_change() {
55        let mut a_left = Hasher::new();
56        write_len_prefixed(&mut a_left, b"a");
57        write_len_prefixed(&mut a_left, b"bc");
58
59        let mut b_left = Hasher::new();
60        write_len_prefixed(&mut b_left, b"ab");
61        write_len_prefixed(&mut b_left, b"c");
62
63        assert_ne!(a_left.finalize(), b_left.finalize());
64    }
65
66    #[test]
67    fn hash32_empty_input() {
68        let h = hash32(b"");
69        assert_eq!(h, blake3::hash(b""));
70    }
71
72    #[test]
73    fn write_len_prefixed_empty_data() {
74        let mut hasher = Hasher::new();
75        write_len_prefixed(&mut hasher, b"");
76        let actual = hasher.finalize();
77
78        let mut expected_hasher = Hasher::new();
79        expected_hasher.update(&0u32.to_be_bytes());
80        assert_eq!(actual, expected_hasher.finalize());
81    }
82
83    #[test]
84    fn hash32_different_inputs_differ() {
85        assert_ne!(hash32(b"alpha"), hash32(b"beta"));
86    }
87
88    proptest! {
89        #![proptest_config(ProptestConfig { cases: 64, ..ProptestConfig::default() })]
90
91        #[test]
92        fn hash32_is_deterministic_for_random_inputs(data in any::<Vec<u8>>()) {
93            let first = hash32(&data);
94            let second = hash32(&data);
95            assert_eq!(first, second);
96        }
97
98        #[test]
99        fn write_len_prefixed_matches_direct_length_encoding(data in any::<Vec<u8>>()) {
100            let expected_len = (u32::try_from(data.len()).unwrap_or(u32::MAX)).to_be_bytes();
101
102            let mut direct = Hasher::new();
103            direct.update(&expected_len);
104            direct.update(&data);
105            let expected = direct.finalize();
106
107            let mut prefixed = Hasher::new();
108            write_len_prefixed(&mut prefixed, &data);
109            let actual = prefixed.finalize();
110
111            assert_eq!(actual, expected);
112        }
113    }
114}