primitives/hashing/
hashing_utils.rs1use 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
12pub trait HashWith: AsRef<[u8]> + From<Digest> {
14 fn hash_with(&self, tag: &[u8]) -> Self {
16 self.hash_with_many(&[tag])
17 }
18
19 fn hash_with_many(&self, tags: &[&[u8]]) -> Self {
20 hash1(self.as_ref(), tags).into()
21 }
22}
23
24impl<T: AsRef<[u8]> + From<Digest>> HashWith for T {}
25
26pub fn hash(slices: &[&[u8]]) -> Digest {
28 let mut hasher = blake3::Hasher::new();
29 for slice in slices {
30 hasher.update(slice);
31 }
32 Into::<[u8; 32]>::into(hasher.finalize()).into()
33}
34
35fn hash1(data: &[u8], slices: &[&[u8]]) -> Digest {
38 let mut hasher = blake3::Hasher::new();
39 hasher.update(data);
40 for slice in slices {
41 hasher.update(slice);
42 }
43 Into::<[u8; 32]>::into(hasher.finalize()).into()
44}
45
46pub fn hash_into<T: AsRef<[u8]>, I: IntoIterator<Item = T>>(slices: I, out: &mut [u8]) {
48 let mut hasher = blake3::Hasher::new();
49 for slice in slices {
50 hasher.update(slice.as_ref());
51 }
52 hasher.finalize_xof().fill(out.as_mut());
53}
54
55pub fn hash_to_field<T: AsRef<[u8]>, F: FieldExtension>(session_id: &SessionId, seed: &T) -> F {
57 let mut hasher = blake3::Hasher::new();
58 let mut output = Array::<u8, F::UniformBytes>::default();
59
60 hasher.update(session_id.as_ref());
61 hasher.update(seed.as_ref());
62 hasher.finalize_xof().fill(&mut output);
63
64 F::from_uniform_bytes(&output)
65}
66
67pub fn flatten_slices<T: AsRef<[u8]>>(slices: &[T]) -> Vec<u8> {
69 let total_len = slices.iter().map(|slice| slice.as_ref().len()).sum();
70
71 let mut flattened = Vec::with_capacity(total_len);
72 slices.iter().for_each(|slice| {
73 flattened.extend_from_slice(slice.as_ref());
74 });
75
76 flattened
77}
78
79pub fn flatten_slices_with_length_prefixes<T: AsRef<[u8]>>(slices: &[T]) -> Vec<u8> {
82 let mut flattened = Vec::new();
83 slices.iter().for_each(|slice| {
84 let slice_ref = slice.as_ref();
85 let len_prefix = (slice_ref.len() as u64).to_le_bytes();
87 flattened.extend_from_slice(&len_prefix);
88 flattened.extend_from_slice(slice_ref);
89 });
90
91 flattened
92}
93
94#[cfg(test)]
95mod tests {
96 use crate::hashing::{flatten_slices, flatten_slices_with_length_prefixes, hash_into};
97
98 #[test]
99 fn test_hash_into_different_results() {
100 let (mut seed0, mut seed1, mut seed2, mut seed3) = ([0; 16], [0; 16], [0; 16], [0; 16]);
101 hash_into([b"0", b"1"], &mut seed0);
102 hash_into([b"0", b"12".as_slice()], &mut seed1);
103 hash_into([b"01", b"12"], &mut seed2);
104 hash_into([b"01", b"1".as_slice()], &mut seed3);
105
106 assert_ne!(seed0, seed1);
107 assert_ne!(seed0, seed2);
108 assert_ne!(seed0, seed3);
109 assert_ne!(seed1, seed2);
110 assert_ne!(seed1, seed3);
111 assert_ne!(seed2, seed3);
112 }
113
114 #[test]
118 fn test_length_prefixes_prevent_concatenation_collisions() {
119 let splits: Vec<Vec<&[u8]>> = vec![
120 vec![b"AB", b"CD"],
121 vec![b"A", b"BCD"],
122 vec![b"ABCD"],
123 vec![b"ABC", b"D"],
124 ];
125
126 let plain: Vec<_> = splits.iter().map(|s| flatten_slices(s)).collect();
128 for p in &plain {
129 assert_eq!(p, &plain[0], "plain concatenation should be identical");
130 }
131
132 let prefixed: Vec<_> = splits
134 .iter()
135 .map(|s| flatten_slices_with_length_prefixes(s))
136 .collect();
137 for i in 0..prefixed.len() {
138 for j in (i + 1)..prefixed.len() {
139 assert_ne!(
140 prefixed[i], prefixed[j],
141 "length-prefixed outputs for splits {i} and {j} should differ"
142 );
143 }
144 }
145 }
146
147 #[test]
148 fn test_length_prefixes_empty_slices() {
149 let a = flatten_slices_with_length_prefixes::<&[u8]>(&[]);
151 let b = flatten_slices_with_length_prefixes(&[b"".as_slice()]);
152 let c = flatten_slices_with_length_prefixes(&[b"".as_slice(), b"".as_slice()]);
153 assert!(a.is_empty());
154 assert_ne!(a, b, "zero slices vs one empty slice must differ");
155 assert_ne!(b, c, "one empty slice vs two empty slices must differ");
156 }
157
158 #[test]
159 fn test_length_prefixes_roundtrip_structure() {
160 let slices: &[&[u8]] = &[b"hello", b"", b"world"];
161 let prefixed = flatten_slices_with_length_prefixes(slices);
162
163 let mut cursor = 0;
165 for original in slices {
166 let len_bytes = &prefixed[cursor..cursor + 8];
167 let len = u64::from_le_bytes(len_bytes.try_into().unwrap());
168 assert_eq!(len, original.len() as u64);
169 cursor += 8;
170
171 let data = &prefixed[cursor..cursor + len as usize];
172 assert_eq!(data, *original);
173 cursor += len as usize;
174 }
175 assert_eq!(cursor, prefixed.len(), "no trailing bytes");
176 }
177}