Skip to main content

primitives/hashing/
hashing_utils.rs

1use blake3;
2use hybrid_array::Array;
3
4use crate::{
5    algebra::field::FieldExtension,
6    constants::CollisionResistanceBytes,
7    types::SessionId,
8};
9
10pub type Digest = Array<u8, CollisionResistanceBytes>;
11
12/// A generic trait for hashing with a tag.
13pub trait HashWith: AsRef<[u8]> + From<Digest> {
14    /// Hash the current value with the given tag, returning a new value.
15    fn hash_with(&self, tag: &[u8]) -> Self {
16        hash(&[self.as_ref(), tag]).into()
17    }
18}
19
20impl<T: AsRef<[u8]> + From<Digest>> HashWith for T {}
21
22/// Utility function to hash a list of byte slices into a fixed-size digest. Uses BLAKE3.
23pub fn hash(slices: &[&[u8]]) -> Digest {
24    let mut hasher = blake3::Hasher::new();
25    for slice in slices {
26        hasher.update(slice);
27    }
28    Into::<[u8; 32]>::into(hasher.finalize()).into()
29}
30
31/// Utility function to hash a list of byte slices into the provided output buffer. Uses BLAKE3.
32pub fn hash_into<T: AsRef<[u8]>, I: IntoIterator<Item = T>>(slices: I, out: &mut [u8]) {
33    let mut hasher = blake3::Hasher::new();
34    for slice in slices {
35        hasher.update(slice.as_ref());
36    }
37    hasher.finalize_xof().fill(out.as_mut());
38}
39
40/// Hashes the given session ID and seed into a field element of type F.
41pub fn hash_to_field<T: AsRef<[u8]>, F: FieldExtension>(session_id: &SessionId, seed: &T) -> F {
42    let mut hasher = blake3::Hasher::new();
43    let mut output = Array::<u8, F::UniformBytes>::default();
44
45    hasher.update(session_id.as_ref());
46    hasher.update(seed.as_ref());
47    hasher.finalize_xof().fill(&mut output);
48
49    F::from_uniform_bytes(&output)
50}
51
52/// Utility function to flatten a list of byte slices into a single contiguous vector.
53pub fn flatten_slices<T: AsRef<[u8]>>(slices: &[T]) -> Vec<u8> {
54    let total_len = slices.iter().map(|slice| slice.as_ref().len()).sum();
55
56    let mut flattened = Vec::with_capacity(total_len);
57    slices.iter().for_each(|slice| {
58        flattened.extend_from_slice(slice.as_ref());
59    });
60
61    flattened
62}
63
64/// Utility function to flatten a list of length-prepended byte slices into a single contiguous
65/// vector.
66pub fn flatten_slices_with_length_prefixes<T: AsRef<[u8]>>(slices: &[T]) -> Vec<u8> {
67    let mut flattened = Vec::new();
68    slices.iter().for_each(|slice| {
69        let slice_ref = slice.as_ref();
70        // Cast length to u64 to remove platform-dependence and ensure a fixed 8-byte prefix
71        let len_prefix = (slice_ref.len() as u64).to_le_bytes();
72        flattened.extend_from_slice(&len_prefix);
73        flattened.extend_from_slice(slice_ref);
74    });
75
76    flattened
77}
78
79#[cfg(test)]
80mod tests {
81    use crate::hashing::{flatten_slices, flatten_slices_with_length_prefixes, hash_into};
82
83    #[test]
84    fn test_hash_into_different_results() {
85        let (mut seed0, mut seed1, mut seed2, mut seed3) = ([0; 16], [0; 16], [0; 16], [0; 16]);
86        hash_into([b"0", b"1"], &mut seed0);
87        hash_into([b"0", b"12".as_slice()], &mut seed1);
88        hash_into([b"01", b"12"], &mut seed2);
89        hash_into([b"01", b"1".as_slice()], &mut seed3);
90
91        assert_ne!(seed0, seed1);
92        assert_ne!(seed0, seed2);
93        assert_ne!(seed0, seed3);
94        assert_ne!(seed1, seed2);
95        assert_ne!(seed1, seed3);
96        assert_ne!(seed2, seed3);
97    }
98
99    // Verify that length-prefixed flattening prevents collisions that plain concatenation allows.
100    // flatten_slices(["AB", "CD"]) == flatten_slices(["A", "BCD"]) == flatten_slices(["ABCD"])
101    // but the length-prefixed variants must all differ.
102    #[test]
103    fn test_length_prefixes_prevent_concatenation_collisions() {
104        let splits: Vec<Vec<&[u8]>> = vec![
105            vec![b"AB", b"CD"],
106            vec![b"A", b"BCD"],
107            vec![b"ABCD"],
108            vec![b"ABC", b"D"],
109        ];
110
111        // Plain concatenation produces the same bytes for all splits
112        let plain: Vec<_> = splits.iter().map(|s| flatten_slices(s)).collect();
113        for p in &plain {
114            assert_eq!(p, &plain[0], "plain concatenation should be identical");
115        }
116
117        // Length-prefixed flattening must produce distinct outputs
118        let prefixed: Vec<_> = splits
119            .iter()
120            .map(|s| flatten_slices_with_length_prefixes(s))
121            .collect();
122        for i in 0..prefixed.len() {
123            for j in (i + 1)..prefixed.len() {
124                assert_ne!(
125                    prefixed[i], prefixed[j],
126                    "length-prefixed outputs for splits {i} and {j} should differ"
127                );
128            }
129        }
130    }
131
132    #[test]
133    fn test_length_prefixes_empty_slices() {
134        // Empty slice vs no slices
135        let a = flatten_slices_with_length_prefixes::<&[u8]>(&[]);
136        let b = flatten_slices_with_length_prefixes(&[b"".as_slice()]);
137        let c = flatten_slices_with_length_prefixes(&[b"".as_slice(), b"".as_slice()]);
138        assert!(a.is_empty());
139        assert_ne!(a, b, "zero slices vs one empty slice must differ");
140        assert_ne!(b, c, "one empty slice vs two empty slices must differ");
141    }
142
143    #[test]
144    fn test_length_prefixes_roundtrip_structure() {
145        let slices: &[&[u8]] = &[b"hello", b"", b"world"];
146        let prefixed = flatten_slices_with_length_prefixes(slices);
147
148        // Manually decode and verify structure
149        let mut cursor = 0;
150        for original in slices {
151            let len_bytes = &prefixed[cursor..cursor + 8];
152            let len = u64::from_le_bytes(len_bytes.try_into().unwrap());
153            assert_eq!(len, original.len() as u64);
154            cursor += 8;
155
156            let data = &prefixed[cursor..cursor + len as usize];
157            assert_eq!(data, *original);
158            cursor += len as usize;
159        }
160        assert_eq!(cursor, prefixed.len(), "no trailing bytes");
161    }
162}