1#[cfg(test)]
45mod bootstrap;
46mod constants;
47mod encoding;
48pub(crate) mod field;
49mod params;
50mod permutation;
51mod sponge;
52pub mod tree;
53
54#[cfg(feature = "gpu")]
55pub mod gpu;
56
57pub use params::{
59 CAPACITY, CHUNK_SIZE, COLLISION_BITS, OUTPUT_BYTES, OUTPUT_ELEMENTS, RATE, RATE_BYTES,
60 ROUNDS_F, ROUNDS_P, SBOX_DEGREE, WIDTH,
61};
62pub use sponge::{Hash, Hasher, OutputReader};
63
64pub fn hash(input: &[u8]) -> Hash {
66 let mut hasher = Hasher::new();
67 hasher.update(input);
68 hasher.finalize()
69}
70
71pub fn keyed_hash(key: &[u8; OUTPUT_BYTES], input: &[u8]) -> Hash {
73 let mut hasher = Hasher::new_keyed(key);
74 hasher.update(input);
75 hasher.finalize()
76}
77
78pub fn derive_key(context: &str, key_material: &[u8]) -> [u8; OUTPUT_BYTES] {
84 let ctx_hasher = Hasher::new_derive_key_context(context);
85 let ctx_hash = ctx_hasher.finalize();
86 let mut material_hasher = Hasher::new_derive_key_material(&ctx_hash);
87 material_hasher.update(key_material);
88 let result = material_hasher.finalize();
89 *result.as_bytes()
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn hash_basic() {
98 let h = hash(b"hello");
99 assert_ne!(h.as_bytes(), &[0u8; OUTPUT_BYTES]);
100 }
101
102 #[test]
103 fn hash_deterministic() {
104 let h1 = hash(b"test");
105 let h2 = hash(b"test");
106 assert_eq!(h1, h2);
107 }
108
109 #[test]
110 fn hash_different_inputs() {
111 assert_ne!(hash(b""), hash(b"a"));
112 assert_ne!(hash(b"a"), hash(b"b"));
113 assert_ne!(hash(b"ab"), hash(b"ba"));
114 }
115
116 #[test]
117 fn hash_matches_streaming() {
118 let data = b"streaming consistency test with enough data to cross boundaries!!";
119 let direct = hash(data);
120 let streamed = {
121 let mut h = Hasher::new();
122 h.update(&data[..10]);
123 h.update(&data[10..]);
124 h.finalize()
125 };
126 assert_eq!(direct, streamed);
127 }
128
129 #[test]
130 fn keyed_hash_differs_from_plain() {
131 let data = b"test";
132 assert_ne!(hash(data), keyed_hash(&[0u8; OUTPUT_BYTES], data));
133 }
134
135 #[test]
136 fn keyed_hash_different_keys() {
137 let data = b"test";
138 let h1 = keyed_hash(&[0u8; OUTPUT_BYTES], data);
139 let h2 = keyed_hash(&[1u8; OUTPUT_BYTES], data);
140 assert_ne!(h1, h2);
141 }
142
143 #[test]
144 fn derive_key_basic() {
145 let key = derive_key("my context", b"material");
146 assert_ne!(key, [0u8; OUTPUT_BYTES]);
147 }
148
149 #[test]
150 fn derive_key_differs_from_hash() {
151 let data = b"material";
152 let h = hash(data);
153 let k = derive_key("context", data);
154 assert_ne!(h.as_bytes(), &k);
155 }
156
157 #[test]
158 fn derive_key_different_contexts() {
159 let k1 = derive_key("context A", b"material");
160 let k2 = derive_key("context B", b"material");
161 assert_ne!(k1, k2);
162 }
163
164 #[test]
165 fn derive_key_different_materials() {
166 let k1 = derive_key("context", b"material A");
167 let k2 = derive_key("context", b"material B");
168 assert_ne!(k1, k2);
169 }
170
171 #[test]
172 fn xof_extends_hash() {
173 let mut xof = Hasher::new().update(b"xof test").finalize_xof();
174 let mut out = [0u8; OUTPUT_BYTES * 2];
175 xof.fill(&mut out);
176 let h = hash(b"xof test");
178 assert_eq!(&out[..OUTPUT_BYTES], h.as_bytes());
179 }
180
181 #[test]
182 fn large_input() {
183 let data = vec![0x42u8; 10_000];
184 let h = hash(&data);
185 assert_ne!(h.as_bytes(), &[0u8; OUTPUT_BYTES]);
186
187 let mut hasher = Hasher::new();
189 for chunk in data.chunks(137) {
190 hasher.update(chunk);
191 }
192 assert_eq!(h, hasher.finalize());
193 }
194
195 #[test]
196 fn hash_empty() {
197 let h = hash(b"");
198 assert_ne!(h.as_bytes(), &[0u8; OUTPUT_BYTES]);
199 }
200
201 #[test]
202 fn hash_single_byte_avalanche() {
203 let hashes: Vec<_> = (0..=255u8).map(|b| hash(&[b])).collect();
205 for i in 0..256 {
206 for j in (i + 1)..256 {
207 assert_ne!(hashes[i], hashes[j], "collision at bytes {i} and {j}");
208 }
209 }
210 }
211
212 #[test]
213 fn keyed_hash_empty_input() {
214 let h = keyed_hash(&[0u8; OUTPUT_BYTES], b"");
215 assert_ne!(h.as_bytes(), &[0u8; OUTPUT_BYTES]);
216 }
217
218 #[test]
219 fn derive_key_long_context() {
220 let long_ctx = "a]".repeat(100);
222 let k = derive_key(&long_ctx, b"material");
223 assert_ne!(k, [0u8; OUTPUT_BYTES]);
224 }
225
226 #[test]
227 fn derive_key_long_material() {
228 let material = vec![0x42u8; 1000];
230 let k = derive_key("ctx", &material);
231 assert_ne!(k, [0u8; OUTPUT_BYTES]);
232 }
233}
234
235#[cfg(test)]
237mod proptests {
238 use super::*;
239 use proptest::prelude::*;
240
241 proptest! {
242 #[test]
243 fn hash_is_deterministic(data in proptest::collection::vec(any::<u8>(), 0..500)) {
244 prop_assert_eq!(hash(&data), hash(&data));
245 }
246
247 #[test]
248 fn streaming_matches_oneshot(data in proptest::collection::vec(any::<u8>(), 0..500)) {
249 let oneshot = hash(&data);
250 let mut hasher = Hasher::new();
251 let mut pos = 0;
253 let mut chunk_size = 1;
254 while pos < data.len() {
255 let end = (pos + chunk_size).min(data.len());
256 hasher.update(&data[pos..end]);
257 pos = end;
258 chunk_size = (chunk_size * 3 + 1) % 71; if chunk_size == 0 { chunk_size = 1; }
260 }
261 prop_assert_eq!(oneshot, hasher.finalize());
262 }
263
264 #[test]
265 fn xof_prefix_matches_finalize(data in proptest::collection::vec(any::<u8>(), 0..200)) {
266 let hash_result = hash(&data);
267 let mut xof = {
268 let mut h = Hasher::new();
269 h.update(&data);
270 h.finalize_xof()
271 };
272 let mut xof_bytes = [0u8; OUTPUT_BYTES];
273 xof.fill(&mut xof_bytes);
274 prop_assert_eq!(hash_result.as_bytes(), &xof_bytes);
275 }
276
277 #[test]
278 fn keyed_hash_differs_from_plain(
279 data in proptest::collection::vec(any::<u8>(), 1..200),
280 key in proptest::collection::vec(any::<u8>(), OUTPUT_BYTES..=OUTPUT_BYTES),
281 ) {
282 let key_arr: [u8; OUTPUT_BYTES] = key.try_into().unwrap();
283 let plain = hash(&data);
284 let keyed = keyed_hash(&key_arr, &data);
285 prop_assert_ne!(plain, keyed);
286 }
287
288 #[test]
289 fn clone_consistency(
290 prefix in proptest::collection::vec(any::<u8>(), 0..100),
291 suffix in proptest::collection::vec(any::<u8>(), 0..100),
292 ) {
293 let mut h1 = Hasher::new();
294 h1.update(&prefix);
295 let mut h2 = h1.clone();
296 h1.update(&suffix);
297 h2.update(&suffix);
298 prop_assert_eq!(h1.finalize(), h2.finalize());
299 }
300 }
301}