Skip to main content

ic_hash/
sha3.rs

1//! FIPS 202 SHA-3 and SHAKE, built on the Keccak-f[1600] permutation.
2
3use 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
43/// The Keccak-f[1600] permutation over a 25-lane state.
44fn keccak_f1600(a: &mut [u64; 25]) {
45    for round in RC.iter().take(ROUNDS) {
46        // theta
47        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        // rho and pi
58        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        // chi
66        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        // iota
79        a[0] ^= *round;
80    }
81}
82
83/// A sponge over Keccak-f[1600] with a configurable rate and domain separator.
84#[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
197/// A finished sponge that can be squeezed repeatedly.
198///
199/// [`Xof::finalize_xof`] consumes the hasher and produces a fixed number of
200/// bytes, which is the right shape for most callers. Rejection sampling is the
201/// exception: it cannot know in advance how much output it needs, because that
202/// depends on how many candidates it throws away. ML-KEM's `SampleNTT` is
203/// exactly this case.
204///
205/// Reading is continuous — reading 32 bytes twice gives the same stream as
206/// reading 64 once — which is what makes the sampler's output independent of
207/// the chunk size it happens to ask for.
208pub struct XofReader {
209    sponge: Sponge,
210}
211
212impl XofReader {
213    /// Squeeze the next `out.len()` bytes.
214    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            /// Finish absorbing and return a reader for an unbounded stream.
251            ///
252            /// For callers that cannot size their output in advance; see
253            /// [`XofReader`].
254            pub fn finalize_reader(mut self) -> XofReader {
255                self.0.finish();
256                XofReader { sponge: self.0 }
257            }
258
259            /// One-shot: absorb `data` and squeeze `out.len()` bytes.
260            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    /// A long squeeze must agree with a short one on its prefix, proving the
377    /// sponge rate boundary is handled correctly.
378    #[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}