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
35const RHO: [u32; 24] = [
36 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44,
37];
38
39const PI: [usize; 24] = [
40 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1,
41];
42
43fn keccak_f1600(a: &mut [u64; 25]) {
45 for round in RC.iter().take(ROUNDS) {
46 let mut c = [0u64; 5];
48 for x in 0..5 {
49 c[x] = a[x] ^ a[x + 5] ^ a[x + 10] ^ a[x + 15] ^ a[x + 20];
50 }
51 for x in 0..5 {
52 let d = c[(x + 4) % 5] ^ c[(x + 1) % 5].rotate_left(1);
53 for y in 0..5 {
54 a[x + 5 * y] ^= d;
55 }
56 }
57 let mut last = a[1];
59 for i in 0..24 {
60 let j = PI[i];
61 let tmp = a[j];
62 a[j] = last.rotate_left(RHO[i]);
63 last = tmp;
64 }
65 for y in 0..5 {
67 let row = [
68 a[5 * y],
69 a[5 * y + 1],
70 a[5 * y + 2],
71 a[5 * y + 3],
72 a[5 * y + 4],
73 ];
74 for x in 0..5 {
75 a[5 * y + x] = row[x] ^ ((!row[(x + 1) % 5]) & row[(x + 2) % 5]);
76 }
77 }
78 a[0] ^= *round;
80 }
81}
82
83#[derive(Clone)]
85pub(crate) struct Sponge {
86 state: [u64; 25],
87 rate: usize,
88 pos: usize,
89 pad: u8,
90}
91
92impl Sponge {
93 pub(crate) const fn new(rate: usize, pad: u8) -> Self {
94 Self {
95 state: [0u64; 25],
96 rate,
97 pos: 0,
98 pad,
99 }
100 }
101
102 pub(crate) fn absorb(&mut self, data: &[u8]) {
103 for &byte in data {
104 let lane = self.pos / 8;
105 let shift = 8 * (self.pos % 8);
106 self.state[lane] ^= (byte as u64) << shift;
107 self.pos += 1;
108 if self.pos == self.rate {
109 keccak_f1600(&mut self.state);
110 self.pos = 0;
111 }
112 }
113 }
114
115 pub(crate) fn finish(&mut self) {
116 let lane = self.pos / 8;
117 let shift = 8 * (self.pos % 8);
118 self.state[lane] ^= (self.pad as u64) << shift;
119 let last = self.rate - 1;
120 self.state[last / 8] ^= 0x80u64 << (8 * (last % 8));
121 keccak_f1600(&mut self.state);
122 self.pos = 0;
123 }
124
125 pub(crate) fn squeeze(&mut self, out: &mut [u8]) {
126 let mut produced = 0;
127 while produced < out.len() {
128 if self.pos == self.rate {
129 keccak_f1600(&mut self.state);
130 self.pos = 0;
131 }
132 let lane = self.pos / 8;
133 let shift = 8 * (self.pos % 8);
134 out[produced] = (self.state[lane] >> shift) as u8;
135 self.pos += 1;
136 produced += 1;
137 }
138 }
139}
140
141impl Drop for Sponge {
142 fn drop(&mut self) {
143 self.state.zeroize();
144 }
145}
146
147macro_rules! sha3_hash {
148 ($name:ident, $id:literal, $disp:literal, $out:literal, $kat:literal) => {
149 #[doc = concat!("FIPS 202 ", $disp, ".")]
150 #[derive(Clone)]
151 pub struct $name(Sponge);
152
153 impl Default for $name {
154 fn default() -> Self {
155 Self(Sponge::new(200 - 2 * $out, 0x06))
156 }
157 }
158
159 impl Algorithm for $name {
160 const ID: &'static str = $id;
161 const NAME: &'static str = $disp;
162 }
163
164 impl Digest for $name {
165 type Output = [u8; $out];
166 const OUTPUT_LEN: usize = $out;
167 const BLOCK_LEN: usize = 200 - 2 * $out;
168
169 fn update(&mut self, data: &[u8]) {
170 self.0.absorb(data);
171 }
172
173 fn finalize(mut self) -> Self::Output {
174 let mut out = [0u8; $out];
175 self.0.finish();
176 self.0.squeeze(&mut out);
177 out
178 }
179 }
180
181 impl SelfTest for $name {
182 fn self_test() -> Result<()> {
183 let got = <Self as Digest>::digest(b"abc");
184 let mut want = [0u8; $out];
185 ic_core::codec::hex_decode($kat.as_bytes(), &mut want)?;
186 ensure!(
187 ic_core::ct::verify(&want, got.as_ref()),
188 SelfTestFailed,
189 $id
190 );
191 Ok(())
192 }
193 }
194 };
195}
196
197pub struct XofReader {
209 sponge: Sponge,
210}
211
212impl XofReader {
213 pub fn read(&mut self, out: &mut [u8]) {
215 self.sponge.squeeze(out);
216 }
217}
218
219macro_rules! shake {
220 ($name:ident, $id:literal, $disp:literal, $cap:literal, $kat:literal) => {
221 #[doc = concat!("FIPS 202 ", $disp, " extendable-output function.")]
222 #[derive(Clone)]
223 pub struct $name(Sponge);
224
225 impl Default for $name {
226 fn default() -> Self {
227 Self(Sponge::new(200 - $cap / 4, 0x1f))
228 }
229 }
230
231 impl Algorithm for $name {
232 const ID: &'static str = $id;
233 const NAME: &'static str = $disp;
234 }
235
236 impl Xof for $name {
237 const BLOCK_LEN: usize = 200 - $cap / 4;
238
239 fn update(&mut self, data: &[u8]) {
240 self.0.absorb(data);
241 }
242
243 fn finalize_xof(mut self, out: &mut [u8]) {
244 self.0.finish();
245 self.0.squeeze(out);
246 }
247 }
248
249 impl $name {
250 pub fn finalize_reader(mut self) -> XofReader {
255 self.0.finish();
256 XofReader { sponge: self.0 }
257 }
258
259 pub fn xof(data: &[u8], out: &mut [u8]) {
261 let mut x = Self::default();
262 <Self as Xof>::update(&mut x, data);
263 x.finalize_xof(out);
264 }
265 }
266
267 impl SelfTest for $name {
268 fn self_test() -> Result<()> {
269 let mut got = [0u8; 32];
270 Self::xof(b"abc", &mut got);
271 let mut want = [0u8; 32];
272 ic_core::codec::hex_decode($kat.as_bytes(), &mut want)?;
273 ensure!(ic_core::ct::verify(&want, &got), SelfTestFailed, $id);
274 Ok(())
275 }
276 }
277 };
278}
279
280sha3_hash!(
281 Sha3_224,
282 "sha3-224",
283 "SHA3-224",
284 28,
285 "e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf"
286);
287sha3_hash!(
288 Sha3_256,
289 "sha3-256",
290 "SHA3-256",
291 32,
292 "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"
293);
294sha3_hash!(
295 Sha3_384,
296 "sha3-384",
297 "SHA3-384",
298 48,
299 "ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b298d88cea927ac7f539f1edf228376d25"
300);
301sha3_hash!(
302 Sha3_512,
303 "sha3-512",
304 "SHA3-512",
305 64,
306 "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0"
307);
308
309shake!(
310 Shake128,
311 "shake128",
312 "SHAKE128",
313 128,
314 "5881092dd818bf5cf8a3ddb793fbcba74097d5c526a6d35f97b83351940f2cc8"
315);
316shake!(
317 Shake256,
318 "shake256",
319 "SHAKE256",
320 256,
321 "483366601360a8771c6863080cc4114d8db44530f8f1e1ee4f94ea37e78b5739"
322);
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use ic_core::codec::hex;
328
329 #[test]
330 fn sha3_abc_vectors() {
331 assert_eq!(
332 hex(Sha3_224::digest(b"abc").as_ref()),
333 "e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf"
334 );
335 assert_eq!(
336 hex(Sha3_256::digest(b"abc").as_ref()),
337 "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"
338 );
339 assert_eq!(
340 hex(Sha3_384::digest(b"abc").as_ref()),
341 "ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b298d88cea927ac7f539f1edf228376d25"
342 );
343 assert_eq!(
344 hex(Sha3_512::digest(b"abc").as_ref()),
345 "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0"
346 );
347 }
348
349 #[test]
350 fn sha3_empty_vectors() {
351 assert_eq!(
352 hex(Sha3_256::digest(b"").as_ref()),
353 "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a"
354 );
355 assert_eq!(
356 hex(Sha3_512::digest(b"").as_ref()),
357 "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26"
358 );
359 }
360
361 #[test]
362 fn shake_vectors() {
363 let mut out = [0u8; 32];
364 Shake128::xof(b"", &mut out);
365 assert_eq!(
366 hex(&out),
367 "7f9c2ba4e88f827d616045507605853ed73b8093f6efbc88eb1a6eacfa66ef26"
368 );
369 Shake256::xof(b"", &mut out);
370 assert_eq!(
371 hex(&out),
372 "46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762f"
373 );
374 }
375
376 #[test]
379 fn shake_long_squeeze_is_prefix_consistent() {
380 let mut short = [0u8; 16];
381 let mut long = [0u8; 512];
382 Shake128::xof(b"agentic", &mut short);
383 Shake128::xof(b"agentic", &mut long);
384 assert_eq!(&long[..16], &short[..]);
385 }
386
387 #[test]
388 fn streaming_matches_one_shot() {
389 let data: [u8; 400] = core::array::from_fn(|i| (i * 7) as u8);
390 for split in [0usize, 1, 135, 136, 137, 200, 400] {
391 let mut h = Sha3_256::new();
392 h.update(&data[..split]);
393 h.update(&data[split..]);
394 assert_eq!(h.finalize(), Sha3_256::digest(&data), "split at {split}");
395 }
396 }
397
398 #[test]
399 fn all_self_tests_pass() {
400 Sha3_224::self_test().unwrap();
401 Sha3_256::self_test().unwrap();
402 Sha3_384::self_test().unwrap();
403 Sha3_512::self_test().unwrap();
404 Shake128::self_test().unwrap();
405 Shake256::self_test().unwrap();
406 }
407}