1use ic_core::traits::{Algorithm, Digest, SelfTest, Xof};
4use ic_core::{ensure, Result, Zeroize};
5
6const ROUNDS: usize = 24;
7
8const RC: [u64; ROUNDS] = [
9 0x0000000000000001,
10 0x0000000000008082,
11 0x800000000000808a,
12 0x8000000080008000,
13 0x000000000000808b,
14 0x0000000080000001,
15 0x8000000080008081,
16 0x8000000000008009,
17 0x000000000000008a,
18 0x0000000000000088,
19 0x0000000080008009,
20 0x000000008000000a,
21 0x000000008000808b,
22 0x800000000000008b,
23 0x8000000000008089,
24 0x8000000000008003,
25 0x8000000000008002,
26 0x8000000000000080,
27 0x000000000000800a,
28 0x800000008000000a,
29 0x8000000080008081,
30 0x8000000000008080,
31 0x0000000080000001,
32 0x8000000080008008,
33];
34
35#[cfg(test)]
42const RHO: [u32; 24] = [
43 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44,
44];
45
46#[cfg(test)]
48const PI: [usize; 24] = [
49 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1,
50];
51
52fn keccak_f1600(a: &mut [u64; 25]) {
73 for round in RC.iter().take(ROUNDS) {
74 let c0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20];
76 let c1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21];
77 let c2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22];
78 let c3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23];
79 let c4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24];
80 let d = [
81 c4 ^ c1.rotate_left(1),
82 c0 ^ c2.rotate_left(1),
83 c1 ^ c3.rotate_left(1),
84 c2 ^ c4.rotate_left(1),
85 c3 ^ c0.rotate_left(1),
86 ];
87
88 let mut b = [0u64; 25];
90 b[0] = a[0] ^ d[0];
91 b[1] = (a[6] ^ d[1]).rotate_left(44);
92 b[2] = (a[12] ^ d[2]).rotate_left(43);
93 b[3] = (a[18] ^ d[3]).rotate_left(21);
94 b[4] = (a[24] ^ d[4]).rotate_left(14);
95 b[5] = (a[3] ^ d[3]).rotate_left(28);
96 b[6] = (a[9] ^ d[4]).rotate_left(20);
97 b[7] = (a[10] ^ d[0]).rotate_left(3);
98 b[8] = (a[16] ^ d[1]).rotate_left(45);
99 b[9] = (a[22] ^ d[2]).rotate_left(61);
100 b[10] = (a[1] ^ d[1]).rotate_left(1);
101 b[11] = (a[7] ^ d[2]).rotate_left(6);
102 b[12] = (a[13] ^ d[3]).rotate_left(25);
103 b[13] = (a[19] ^ d[4]).rotate_left(8);
104 b[14] = (a[20] ^ d[0]).rotate_left(18);
105 b[15] = (a[4] ^ d[4]).rotate_left(27);
106 b[16] = (a[5] ^ d[0]).rotate_left(36);
107 b[17] = (a[11] ^ d[1]).rotate_left(10);
108 b[18] = (a[17] ^ d[2]).rotate_left(15);
109 b[19] = (a[23] ^ d[3]).rotate_left(56);
110 b[20] = (a[2] ^ d[2]).rotate_left(62);
111 b[21] = (a[8] ^ d[3]).rotate_left(55);
112 b[22] = (a[14] ^ d[4]).rotate_left(39);
113 b[23] = (a[15] ^ d[0]).rotate_left(41);
114 b[24] = (a[21] ^ d[1]).rotate_left(2);
115
116 a[0] = b[0] ^ (!b[1] & b[2]);
118 a[1] = b[1] ^ (!b[2] & b[3]);
119 a[2] = b[2] ^ (!b[3] & b[4]);
120 a[3] = b[3] ^ (!b[4] & b[0]);
121 a[4] = b[4] ^ (!b[0] & b[1]);
122 a[5] = b[5] ^ (!b[6] & b[7]);
123 a[6] = b[6] ^ (!b[7] & b[8]);
124 a[7] = b[7] ^ (!b[8] & b[9]);
125 a[8] = b[8] ^ (!b[9] & b[5]);
126 a[9] = b[9] ^ (!b[5] & b[6]);
127 a[10] = b[10] ^ (!b[11] & b[12]);
128 a[11] = b[11] ^ (!b[12] & b[13]);
129 a[12] = b[12] ^ (!b[13] & b[14]);
130 a[13] = b[13] ^ (!b[14] & b[10]);
131 a[14] = b[14] ^ (!b[10] & b[11]);
132 a[15] = b[15] ^ (!b[16] & b[17]);
133 a[16] = b[16] ^ (!b[17] & b[18]);
134 a[17] = b[17] ^ (!b[18] & b[19]);
135 a[18] = b[18] ^ (!b[19] & b[15]);
136 a[19] = b[19] ^ (!b[15] & b[16]);
137 a[20] = b[20] ^ (!b[21] & b[22]);
138 a[21] = b[21] ^ (!b[22] & b[23]);
139 a[22] = b[22] ^ (!b[23] & b[24]);
140 a[23] = b[23] ^ (!b[24] & b[20]);
141 a[24] = b[24] ^ (!b[20] & b[21]);
142
143 a[0] ^= *round;
145 }
146}
147
148#[derive(Clone)]
150pub(crate) struct Sponge {
151 state: [u64; 25],
152 rate: usize,
153 pos: usize,
154 pad: u8,
155}
156
157impl Sponge {
158 pub(crate) const fn new(rate: usize, pad: u8) -> Self {
159 Self {
160 state: [0u64; 25],
161 rate,
162 pos: 0,
163 pad,
164 }
165 }
166
167 pub(crate) fn absorb(&mut self, mut data: &[u8]) {
180 let lane_aligned = self.rate % 8 == 0;
185
186 while !data.is_empty() {
187 if lane_aligned && self.pos == 0 && data.len() >= self.rate {
188 let (block, rest) = data.split_at(self.rate);
189 for (lane, chunk) in self.state.iter_mut().zip(block.chunks_exact(8)) {
190 let mut b = [0u8; 8];
191 b.copy_from_slice(chunk);
192 *lane ^= u64::from_le_bytes(b);
193 }
194 keccak_f1600(&mut self.state);
195 data = rest;
196 continue;
197 }
198
199 let byte = data[0];
200 let lane = self.pos / 8;
201 let shift = 8 * (self.pos % 8);
202 self.state[lane] ^= (byte as u64) << shift;
203 self.pos += 1;
204 if self.pos == self.rate {
205 keccak_f1600(&mut self.state);
206 self.pos = 0;
207 }
208 data = &data[1..];
209 }
210 }
211
212 pub(crate) fn finish(&mut self) {
213 let lane = self.pos / 8;
214 let shift = 8 * (self.pos % 8);
215 self.state[lane] ^= (self.pad as u64) << shift;
216 let last = self.rate - 1;
217 self.state[last / 8] ^= 0x80u64 << (8 * (last % 8));
218 keccak_f1600(&mut self.state);
219 self.pos = 0;
220 }
221
222 pub(crate) fn squeeze(&mut self, out: &mut [u8]) {
223 let mut produced = 0;
224 while produced < out.len() {
225 if self.pos == self.rate {
226 keccak_f1600(&mut self.state);
227 self.pos = 0;
228 }
229 let lane = self.pos / 8;
230 let shift = 8 * (self.pos % 8);
231 out[produced] = (self.state[lane] >> shift) as u8;
232 self.pos += 1;
233 produced += 1;
234 }
235 }
236}
237
238impl Drop for Sponge {
239 fn drop(&mut self) {
240 self.state.zeroize();
241 }
242}
243
244macro_rules! sha3_hash {
245 ($name:ident, $id:literal, $disp:literal, $out:literal, $kat:literal) => {
246 #[doc = concat!("FIPS 202 ", $disp, ".")]
247 #[derive(Clone)]
248 pub struct $name(Sponge);
249
250 impl Default for $name {
251 fn default() -> Self {
252 Self(Sponge::new(200 - 2 * $out, 0x06))
253 }
254 }
255
256 impl Algorithm for $name {
257 const ID: &'static str = $id;
258 const NAME: &'static str = $disp;
259 }
260
261 impl Digest for $name {
262 type Output = [u8; $out];
263 const OUTPUT_LEN: usize = $out;
264 const BLOCK_LEN: usize = 200 - 2 * $out;
265
266 fn update(&mut self, data: &[u8]) {
267 self.0.absorb(data);
268 }
269
270 fn finalize(mut self) -> Self::Output {
271 let mut out = [0u8; $out];
272 self.0.finish();
273 self.0.squeeze(&mut out);
274 out
275 }
276 }
277
278 impl SelfTest for $name {
279 fn self_test() -> Result<()> {
280 let got = <Self as Digest>::digest(b"abc");
281 let mut want = [0u8; $out];
282 ic_core::codec::hex_decode($kat.as_bytes(), &mut want)?;
283 ensure!(
284 ic_core::ct::verify(&want, got.as_ref()),
285 SelfTestFailed,
286 $id
287 );
288 Ok(())
289 }
290 }
291 };
292}
293
294pub struct XofReader {
306 sponge: Sponge,
307}
308
309impl XofReader {
310 pub fn read(&mut self, out: &mut [u8]) {
312 self.sponge.squeeze(out);
313 }
314}
315
316macro_rules! shake {
317 ($name:ident, $id:literal, $disp:literal, $cap:literal, $kat:literal) => {
318 #[doc = concat!("FIPS 202 ", $disp, " extendable-output function.")]
319 #[derive(Clone)]
320 pub struct $name(Sponge);
321
322 impl Default for $name {
323 fn default() -> Self {
324 Self(Sponge::new(200 - $cap / 4, 0x1f))
325 }
326 }
327
328 impl Algorithm for $name {
329 const ID: &'static str = $id;
330 const NAME: &'static str = $disp;
331 }
332
333 impl Xof for $name {
334 const BLOCK_LEN: usize = 200 - $cap / 4;
335
336 fn update(&mut self, data: &[u8]) {
337 self.0.absorb(data);
338 }
339
340 fn finalize_xof(mut self, out: &mut [u8]) {
341 self.0.finish();
342 self.0.squeeze(out);
343 }
344 }
345
346 impl $name {
347 pub fn finalize_reader(mut self) -> XofReader {
352 self.0.finish();
353 XofReader { sponge: self.0 }
354 }
355
356 pub fn xof(data: &[u8], out: &mut [u8]) {
358 let mut x = Self::default();
359 <Self as Xof>::update(&mut x, data);
360 x.finalize_xof(out);
361 }
362 }
363
364 impl SelfTest for $name {
365 fn self_test() -> Result<()> {
366 let mut got = [0u8; 32];
367 Self::xof(b"abc", &mut got);
368 let mut want = [0u8; 32];
369 ic_core::codec::hex_decode($kat.as_bytes(), &mut want)?;
370 ensure!(ic_core::ct::verify(&want, &got), SelfTestFailed, $id);
371 Ok(())
372 }
373 }
374 };
375}
376
377sha3_hash!(
378 Sha3_224,
379 "sha3-224",
380 "SHA3-224",
381 28,
382 "e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf"
383);
384sha3_hash!(
385 Sha3_256,
386 "sha3-256",
387 "SHA3-256",
388 32,
389 "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"
390);
391sha3_hash!(
392 Sha3_384,
393 "sha3-384",
394 "SHA3-384",
395 48,
396 "ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b298d88cea927ac7f539f1edf228376d25"
397);
398sha3_hash!(
399 Sha3_512,
400 "sha3-512",
401 "SHA3-512",
402 64,
403 "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0"
404);
405
406shake!(
407 Shake128,
408 "shake128",
409 "SHAKE128",
410 128,
411 "5881092dd818bf5cf8a3ddb793fbcba74097d5c526a6d35f97b83351940f2cc8"
412);
413shake!(
414 Shake256,
415 "shake256",
416 "SHAKE256",
417 256,
418 "483366601360a8771c6863080cc4114d8db44530f8f1e1ee4f94ea37e78b5739"
419);
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424 use ic_core::codec::hex;
425
426 #[test]
427 fn sha3_abc_vectors() {
428 assert_eq!(
429 hex(Sha3_224::digest(b"abc").as_ref()),
430 "e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf"
431 );
432 assert_eq!(
433 hex(Sha3_256::digest(b"abc").as_ref()),
434 "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"
435 );
436 assert_eq!(
437 hex(Sha3_384::digest(b"abc").as_ref()),
438 "ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b298d88cea927ac7f539f1edf228376d25"
439 );
440 assert_eq!(
441 hex(Sha3_512::digest(b"abc").as_ref()),
442 "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0"
443 );
444 }
445
446 #[test]
447 fn sha3_empty_vectors() {
448 assert_eq!(
449 hex(Sha3_256::digest(b"").as_ref()),
450 "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a"
451 );
452 assert_eq!(
453 hex(Sha3_512::digest(b"").as_ref()),
454 "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26"
455 );
456 }
457
458 #[test]
459 fn shake_vectors() {
460 let mut out = [0u8; 32];
461 Shake128::xof(b"", &mut out);
462 assert_eq!(
463 hex(&out),
464 "7f9c2ba4e88f827d616045507605853ed73b8093f6efbc88eb1a6eacfa66ef26"
465 );
466 Shake256::xof(b"", &mut out);
467 assert_eq!(
468 hex(&out),
469 "46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762f"
470 );
471 }
472
473 #[test]
476 fn shake_long_squeeze_is_prefix_consistent() {
477 let mut short = [0u8; 16];
478 let mut long = [0u8; 512];
479 Shake128::xof(b"agentic", &mut short);
480 Shake128::xof(b"agentic", &mut long);
481 assert_eq!(&long[..16], &short[..]);
482 }
483
484 #[test]
485 fn streaming_matches_one_shot() {
486 let data: [u8; 400] = core::array::from_fn(|i| (i * 7) as u8);
487 for split in [0usize, 1, 135, 136, 137, 200, 400] {
488 let mut h = Sha3_256::new();
489 h.update(&data[..split]);
490 h.update(&data[split..]);
491 assert_eq!(h.finalize(), Sha3_256::digest(&data), "split at {split}");
492 }
493 }
494
495 #[test]
496 fn all_self_tests_pass() {
497 Sha3_224::self_test().unwrap();
498 Sha3_256::self_test().unwrap();
499 Sha3_384::self_test().unwrap();
500 Sha3_512::self_test().unwrap();
501 Shake128::self_test().unwrap();
502 Shake256::self_test().unwrap();
503 }
504
505 fn keccak_f1600_by_lane_cycle(a: &mut [u64; 25]) {
512 for round in RC.iter().take(ROUNDS) {
513 let mut c = [0u64; 5];
514 for x in 0..5 {
515 c[x] = a[x] ^ a[x + 5] ^ a[x + 10] ^ a[x + 15] ^ a[x + 20];
516 }
517 for x in 0..5 {
518 let d = c[(x + 4) % 5] ^ c[(x + 1) % 5].rotate_left(1);
519 for y in 0..5 {
520 a[x + 5 * y] ^= d;
521 }
522 }
523 let mut last = a[1];
524 for i in 0..24 {
525 let j = PI[i];
526 let tmp = a[j];
527 a[j] = last.rotate_left(RHO[i]);
528 last = tmp;
529 }
530 for y in 0..5 {
531 let row = [
532 a[5 * y],
533 a[5 * y + 1],
534 a[5 * y + 2],
535 a[5 * y + 3],
536 a[5 * y + 4],
537 ];
538 for x in 0..5 {
539 a[5 * y + x] = row[x] ^ ((!row[(x + 1) % 5]) & row[(x + 2) % 5]);
540 }
541 }
542 a[0] ^= *round;
543 }
544 }
545
546 #[test]
554 fn rho_and_pi_agree_with_the_lane_cycle() {
555 let mut state = 0x0123_4567_89ab_cdefu64;
556 let mut next = || {
557 state ^= state >> 12;
558 state ^= state << 25;
559 state ^= state >> 27;
560 state.wrapping_mul(0x2545_f491_4f6c_dd1d)
561 };
562 for case in 0..2_000 {
563 let mut a = [0u64; 25];
564 for lane in a.iter_mut() {
565 *lane = next();
566 }
567 if case < 25 {
570 a = [0u64; 25];
571 a[case] = 0x8000_0000_0000_0001;
572 }
573 let mut want = a;
574 keccak_f1600_by_lane_cycle(&mut want);
575 let mut got = a;
576 keccak_f1600(&mut got);
577 assert_eq!(got, want, "permutations disagree on state {a:?}");
578 }
579 }
580
581 #[test]
592 #[ignore = "diagnostic, not a test"]
593 fn permutation_ab() {
594 use std::time::Instant;
595 let mut seed = 0x1234_5678_9abc_def0u64;
596 let mut a = [0u64; 25];
597 for lane in a.iter_mut() {
598 seed ^= seed >> 12;
599 seed ^= seed << 25;
600 seed ^= seed >> 27;
601 *lane = seed.wrapping_mul(0x2545_f491_4f6c_dd1d);
602 }
603
604 let n = 20_000;
605 let (mut best_new, mut best_old) = (f64::INFINITY, f64::INFINITY);
606 for _ in 0..40 {
607 let mut s1 = a;
608 let t = Instant::now();
609 for _ in 0..n {
610 keccak_f1600(core::hint::black_box(&mut s1));
611 }
612 let e = t.elapsed().as_secs_f64() / n as f64 * 1e9;
613 best_new = best_new.min(e);
614
615 let mut s2 = a;
616 let t = Instant::now();
617 for _ in 0..n {
618 keccak_f1600_by_lane_cycle(core::hint::black_box(&mut s2));
619 }
620 let e = t.elapsed().as_secs_f64() / n as f64 * 1e9;
621 best_old = best_old.min(e);
622 }
623 println!(
624 "
625 keccak-f[1600] straight-line {best_new:>8.1} ns"
626 );
627 println!(" keccak-f[1600] lane cycle {best_old:>8.1} ns");
628 println!(
629 " ratio {:>8.2}x",
630 best_old / best_new
631 );
632 }
633
634 #[test]
645 fn chunking_the_input_does_not_change_the_digest() {
646 use ic_core::traits::Digest;
647
648 for len in [0usize, 1, 7, 8, 9, 135, 136, 137, 271, 272, 273, 400] {
649 let msg: Vec<u8> = (0..len).map(|i| (i * 31 + 7) as u8).collect();
650 let want = Sha3_256::digest(&msg);
651
652 for chunk in [1usize, 2, 3, 7, 8, 17, 64, 135, 136, 137, 200] {
653 let mut h = Sha3_256::default();
654 for piece in msg.chunks(chunk.max(1)) {
655 h.update(piece);
656 }
657 assert_eq!(
658 h.finalize().as_ref(),
659 want.as_ref(),
660 "len {len} split into {chunk}-byte pieces"
661 );
662 }
663
664 if len > 3 {
666 let mut h = Sha3_256::default();
667 h.update(&msg[..1]);
668 h.update(&msg[1..len - 2]);
669 h.update(&msg[len - 2..]);
670 assert_eq!(
671 h.finalize().as_ref(),
672 want.as_ref(),
673 "len {len} split unevenly"
674 );
675 }
676 }
677 }
678
679 #[test]
681 fn chunking_the_input_does_not_change_the_xof_output() {
682 use ic_core::traits::Xof;
683
684 for len in [0usize, 1, 167, 168, 169, 337, 500] {
685 let msg: Vec<u8> = (0..len).map(|i| (i * 17 + 3) as u8).collect();
686 let mut want = [0u8; 137];
687 {
688 let mut x = Shake128::default();
689 x.update(&msg);
690 x.finalize_xof(&mut want);
691 }
692 for chunk in [1usize, 5, 8, 64, 167, 168, 169] {
693 let mut x = Shake128::default();
694 for piece in msg.chunks(chunk) {
695 x.update(piece);
696 }
697 let mut got = [0u8; 137];
698 x.finalize_xof(&mut got);
699 assert_eq!(got, want, "xof len {len} split into {chunk}-byte pieces");
700 }
701 }
702 }
703}