1use crate::error::Error;
35use crate::keccak::{RATE_128, RATE_256, ROUNDS_FULL, ROUNDS_TURBO};
36use crate::xof::XofCore;
37
38macro_rules! crypto_xof {
41 (
42 $(#[$state_meta:meta])*
43 state: $state:ident($rate:expr, $rounds:expr),
44 rustaceous: $xof:ty,
45 algorithm: $algo:literal,
46 oneshot: $oneshot:ident,
47 init: $init:ident,
48 init_with_domain: $init_with_domain:ident,
49 update: $update:ident,
50 squeeze: $squeeze:ident $(,)?
51 ) => {
52 $(#[$state_meta])*
53 #[derive(Clone)]
54 pub struct $state {
55 core: XofCore<$rate, $rounds>,
56 }
57
58 #[doc = concat!("Computes ", $algo, " of `input`, filling `output`.")]
59 pub fn $oneshot(output: &mut [u8], input: &[u8]) {
60 <$xof>::compute_into_bytes(output, input)
61 }
62
63 #[doc = concat!("Initializes ", $algo, " with the standard domain.")]
64 #[must_use]
65 pub fn $init() -> $state {
66 $state {
67 core: XofCore::new(),
68 }
69 }
70
71 #[doc = concat!("Initializes ", $algo, " with a custom `domain` byte.")]
72 pub fn $init_with_domain(domain: u8) -> Result<$state, Error> {
77 Ok($state {
78 core: XofCore::with_domain(domain)?,
79 })
80 }
81
82 #[doc = concat!("Absorbs `input` into the ", $algo, " `state`.")]
83 pub fn $update(state: &mut $state, input: &[u8]) -> Result<(), Error> {
89 state.core.update(input)
90 }
91
92 #[doc = concat!(
93 "Fills `output` with the next bytes of the ", $algo, " output stream,\n",
94 "finishing absorption on the first call."
95 )]
96 pub fn $squeeze(state: &mut $state, output: &mut [u8]) {
97 state.core.squeeze(output)
98 }
99 };
100}
101
102crypto_xof! {
103 state: Shake128State(RATE_128, ROUNDS_FULL),
105 rustaceous: crate::xof::Shake128,
106 algorithm: "SHAKE128",
107 oneshot: crypto_xof_shake128,
108 init: crypto_xof_shake128_init,
109 init_with_domain: crypto_xof_shake128_init_with_domain,
110 update: crypto_xof_shake128_update,
111 squeeze: crypto_xof_shake128_squeeze,
112}
113
114crypto_xof! {
115 state: Shake256State(RATE_256, ROUNDS_FULL),
117 rustaceous: crate::xof::Shake256,
118 algorithm: "SHAKE256",
119 oneshot: crypto_xof_shake256,
120 init: crypto_xof_shake256_init,
121 init_with_domain: crypto_xof_shake256_init_with_domain,
122 update: crypto_xof_shake256_update,
123 squeeze: crypto_xof_shake256_squeeze,
124}
125
126crypto_xof! {
127 state: TurboShake128State(RATE_128, ROUNDS_TURBO),
129 rustaceous: crate::xof::TurboShake128,
130 algorithm: "TurboSHAKE128",
131 oneshot: crypto_xof_turboshake128,
132 init: crypto_xof_turboshake128_init,
133 init_with_domain: crypto_xof_turboshake128_init_with_domain,
134 update: crypto_xof_turboshake128_update,
135 squeeze: crypto_xof_turboshake128_squeeze,
136}
137
138crypto_xof! {
139 state: TurboShake256State(RATE_256, ROUNDS_TURBO),
141 rustaceous: crate::xof::TurboShake256,
142 algorithm: "TurboSHAKE256",
143 oneshot: crypto_xof_turboshake256,
144 init: crypto_xof_turboshake256_init,
145 init_with_domain: crypto_xof_turboshake256_init_with_domain,
146 update: crypto_xof_turboshake256_update,
147 squeeze: crypto_xof_turboshake256_squeeze,
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use crate::error::ErrorContext;
154 #[cfg(dryoc_native_tests)]
155 use crate::test_prelude::*;
156 use crate::xof::test_vectors::*;
157
158 macro_rules! check_known_answers {
162 ($vectors:expr, $oneshot:ident, $init_with_domain:ident, $update:ident, $squeeze:ident) => {
163 for Vector {
164 message,
165 domain,
166 skip,
167 output,
168 } in $vectors
169 {
170 let len = message.len();
171 if domain == 0x1f && skip == 0 {
172 let mut out = vec![0u8; output.len()];
173 $oneshot(&mut out, &message);
174 assert_eq!(out, output, "one-shot len {len}");
175 }
176
177 let mut state = $init_with_domain(domain).expect("valid domain");
178 if len <= 1024 {
179 $update(&mut state, b"").expect("update failed");
180 for byte in &message {
181 $update(&mut state, core::slice::from_ref(byte)).expect("update failed");
182 $update(&mut state, b"").expect("update failed");
183 }
184 } else {
185 $update(&mut state, &message).expect("update failed");
186 }
187 let mut all = vec![0u8; skip + output.len()];
188 for chunk in all.chunks_mut(7) {
189 $squeeze(&mut state, chunk);
190 }
191 assert_eq!(&all[skip..], output, "streamed len {len}");
192 }
193 };
194 }
195
196 #[test]
197 fn test_crypto_xof_shake128_known_answers() {
198 check_known_answers!(
199 shake128(),
200 crypto_xof_shake128,
201 crypto_xof_shake128_init_with_domain,
202 crypto_xof_shake128_update,
203 crypto_xof_shake128_squeeze
204 );
205 }
206
207 #[test]
208 fn test_crypto_xof_shake256_known_answers() {
209 check_known_answers!(
210 shake256(),
211 crypto_xof_shake256,
212 crypto_xof_shake256_init_with_domain,
213 crypto_xof_shake256_update,
214 crypto_xof_shake256_squeeze
215 );
216 }
217
218 #[test]
219 fn test_crypto_xof_turboshake128_known_answers() {
220 check_known_answers!(
221 turboshake128(),
222 crypto_xof_turboshake128,
223 crypto_xof_turboshake128_init_with_domain,
224 crypto_xof_turboshake128_update,
225 crypto_xof_turboshake128_squeeze
226 );
227 }
228
229 #[test]
230 fn test_crypto_xof_turboshake256_known_answers() {
231 check_known_answers!(
232 turboshake256(),
233 crypto_xof_turboshake256,
234 crypto_xof_turboshake256_init_with_domain,
235 crypto_xof_turboshake256_update,
236 crypto_xof_turboshake256_squeeze
237 );
238 }
239
240 #[test]
243 fn test_update_after_squeeze_is_rejected() {
244 let mut state = crypto_xof_turboshake256_init();
245 crypto_xof_turboshake256_update(&mut state, b"abc").expect("update failed");
246 let mut first = [0u8; 16];
247 crypto_xof_turboshake256_squeeze(&mut state, &mut first);
248 assert!(matches!(
249 crypto_xof_turboshake256_update(&mut state, b"more"),
250 Err(Error::InvalidState {
251 context: ErrorContext::Xof
252 })
253 ));
254 let mut rest = [0u8; 48];
255 crypto_xof_turboshake256_squeeze(&mut state, &mut rest);
256
257 let mut expected = [0u8; 64];
258 crypto_xof_turboshake256(&mut expected, b"abc");
259 assert_eq!([&first[..], &rest[..]].concat(), expected);
260
261 assert!(crypto_xof_shake128_init_with_domain(0x80).is_err());
262 assert!(crypto_xof_shake128_init_with_domain(0x00).is_err());
263 }
264
265 #[cfg(dryoc_native_tests)]
273 #[test]
274 fn test_crypto_xof_matches_libsodium() {
275 use crate::keccak::{RATE_128, RATE_256};
276 use crate::native_test_util as sodium;
277
278 fn pattern(len: usize) -> Vec<u8> {
279 (0..len as u32).map(|i| (i * 31 % 251) as u8).collect()
280 }
281
282 macro_rules! check {
283 (
284 $rate:expr,
285 $oneshot:ident,
286 $init:ident,
287 $init_with_domain:ident,
288 $update:ident,
289 $squeeze:ident,
290 $theirs:ident
291 ) => {
292 let rate = $rate;
293 let input_lens = [0, 1, rate - 1, rate, rate + 1, 2 * rate + 3];
294 let output_lens = [1, 32, rate - 1, rate, rate + 1, 2 * rate + 5, 3 * rate + 1];
295 for len in input_lens {
296 let message = pattern(len);
297 for out_len in output_lens {
298 let mut ours = vec![0u8; out_len];
299 $oneshot(&mut ours, &message);
300 assert_eq!(
301 ours,
302 sodium::$oneshot(&message, out_len),
303 "one-shot len {len}, output {out_len}"
304 );
305 }
306
307 let squeezes = [1, 7, rate - 1, rate + 3, 2 * rate + 1];
308 for domain in [None, Some(0x01), Some(0x07), Some(0x7f)] {
309 let (mut ours, mut theirs) = match domain {
310 None => ($init(), sodium::$theirs::new()),
311 Some(domain) => (
312 $init_with_domain(domain).expect("valid domain"),
313 sodium::$theirs::with_domain(domain).expect("valid domain"),
314 ),
315 };
316 for chunk in message.chunks(61) {
318 for piece in chunk.chunks(7) {
319 $update(&mut ours, piece).expect("update failed");
320 theirs.update(piece);
321 $update(&mut ours, b"").expect("update failed");
322 theirs.update(b"");
323 }
324 }
325 let mut stream = Vec::new();
326 for squeeze_len in squeezes {
327 let mut piece = vec![0u8; squeeze_len];
328 $squeeze(&mut ours, &mut piece);
329 assert_eq!(
330 piece,
331 theirs.squeeze(squeeze_len),
332 "len {len}, domain {domain:?}, squeeze {squeeze_len}"
333 );
334 stream.extend_from_slice(&piece);
335 }
336 if domain.is_none() {
337 assert_eq!(
338 stream,
339 sodium::$oneshot(&message, stream.len()),
340 "len {len}: squeezes continue the one-shot stream"
341 );
342 }
343 }
344 }
345 };
346 }
347
348 check!(
349 RATE_128,
350 crypto_xof_shake128,
351 crypto_xof_shake128_init,
352 crypto_xof_shake128_init_with_domain,
353 crypto_xof_shake128_update,
354 crypto_xof_shake128_squeeze,
355 XofShake128State
356 );
357 check!(
358 RATE_256,
359 crypto_xof_shake256,
360 crypto_xof_shake256_init,
361 crypto_xof_shake256_init_with_domain,
362 crypto_xof_shake256_update,
363 crypto_xof_shake256_squeeze,
364 XofShake256State
365 );
366 check!(
367 RATE_128,
368 crypto_xof_turboshake128,
369 crypto_xof_turboshake128_init,
370 crypto_xof_turboshake128_init_with_domain,
371 crypto_xof_turboshake128_update,
372 crypto_xof_turboshake128_squeeze,
373 XofTurboShake128State
374 );
375 check!(
376 RATE_256,
377 crypto_xof_turboshake256,
378 crypto_xof_turboshake256_init,
379 crypto_xof_turboshake256_init_with_domain,
380 crypto_xof_turboshake256_update,
381 crypto_xof_turboshake256_squeeze,
382 XofTurboShake256State
383 );
384 }
385}