Skip to main content

cryptography/hash/
sha3.rs

1//! SHA-3 (`Keccak-f[1600]`) from FIPS 202.
2//!
3//! This module implements the fixed-output SHA-3 family:
4//!
5//! - `Sha3_224`
6//! - `Sha3_256`
7//! - `Sha3_384`
8//! - `Sha3_512`
9//!
10//! The core is the Keccak sponge over the 1600-bit permutation with the SHA-3
11//! domain-separation suffix `0x06`.
12//!
13//! `Shake128` and `Shake256` are built on the same permutation, but use the
14//! SHAKE domain suffix (`0x1f`) and expose the sponge's natural
15//! absorb-then-squeeze interface through the `Xof` trait.
16
17use super::{Digest, Xof};
18
19// Keccak-f[1600] rho-step rotation offsets for lanes A[x,y] (FIPS 202,
20// Keccak-p permutation definition).
21const RHO: [u32; 25] = [
22    0, 1, 62, 28, 27, 36, 44, 6, 55, 20, 3, 10, 43, 25, 39, 41, 45, 15, 21, 8, 18, 2, 61, 56, 14,
23];
24
25// Keccak-f[1600] pi-step lane permutation, flattened as index x + 5*y where
26// (x, y) -> (y, (2x + 3y) mod 5).
27const PI: [usize; 25] = [
28    0, 10, 20, 5, 15, 16, 1, 11, 21, 6, 7, 17, 2, 12, 22, 23, 8, 18, 3, 13, 14, 24, 9, 19, 4,
29];
30
31// Keccak-f[1600] iota-step round constants for rounds 0..23.
32const RC: [u64; 24] = [
33    0x0000_0000_0000_0001,
34    0x0000_0000_0000_8082,
35    0x8000_0000_0000_808A,
36    0x8000_0000_8000_8000,
37    0x0000_0000_0000_808B,
38    0x0000_0000_8000_0001,
39    0x8000_0000_8000_8081,
40    0x8000_0000_0000_8009,
41    0x0000_0000_0000_008A,
42    0x0000_0000_0000_0088,
43    0x0000_0000_8000_8009,
44    0x0000_0000_8000_000A,
45    0x0000_0000_8000_808B,
46    0x8000_0000_0000_008B,
47    0x8000_0000_0000_8089,
48    0x8000_0000_0000_8003,
49    0x8000_0000_0000_8002,
50    0x8000_0000_0000_0080,
51    0x0000_0000_0000_800A,
52    0x8000_0000_8000_000A,
53    0x8000_0000_8000_8081,
54    0x8000_0000_0000_8080,
55    0x0000_0000_8000_0001,
56    0x8000_0000_8000_8008,
57];
58
59// Runtime-dispatching entry point: hardware on aarch64 + FEAT_SHA3, else soft.
60#[inline]
61fn keccak_f1600(state: &mut [u64; 25]) {
62    #[cfg(target_arch = "aarch64")]
63    {
64        if std::arch::is_aarch64_feature_detected!("sha3") {
65            // SAFETY: feature detection confirms FEAT_SHA3 is present.
66            unsafe {
67                return keccak_f1600_sha3(state);
68            }
69        }
70    }
71    keccak_f1600_soft(state);
72}
73
74// Pure-Rust fallback — 24 rounds of theta → rho → pi → chi → iota.
75fn keccak_f1600_soft(state: &mut [u64; 25]) {
76    for &rc in &RC {
77        // theta: parity of each column.
78        let mut c = [0u64; 5];
79        for x in 0..5 {
80            c[x] = state[x] ^ state[x + 5] ^ state[x + 10] ^ state[x + 15] ^ state[x + 20];
81        }
82
83        // theta: mix neighboring column parities.
84        let mut d = [0u64; 5];
85        for x in 0..5 {
86            d[x] = c[(x + 4) % 5] ^ c[(x + 1) % 5].rotate_left(1);
87        }
88
89        // theta: xor D[x] into each lane of column x.
90        for y in 0..5 {
91            for x in 0..5 {
92                state[x + 5 * y] ^= d[x];
93            }
94        }
95
96        // rho + pi: per-lane rotate, then permute lane positions.
97        let mut b = [0u64; 25];
98        for i in 0..25 {
99            b[PI[i]] = state[i].rotate_left(RHO[i]);
100        }
101
102        // chi: nonlinear row step.
103        for y in 0..5 {
104            let row = 5 * y;
105            for x in 0..5 {
106                state[row + x] = b[row + x] ^ ((!b[row + ((x + 1) % 5)]) & b[row + ((x + 2) % 5)]);
107            }
108        }
109
110        // iota: inject round constant.
111        state[0] ^= rc;
112    }
113}
114
115// FEAT_SHA3 hardware path: uses EOR3 (3-way XOR), RAX1 (rotate-and-XOR),
116// and BCAX (bit-clear-and-XOR) intrinsics.  Theta column parities and D
117// vectors are computed with EOR3/RAX1; chi replaces the scalar NOT+AND+XOR
118// triple with a single BCAX per lane pair.  Rho+Pi remain scalar (each of
119// the 24 non-zero lanes has a distinct rotation, so XAR offers no advantage
120// when lanes are processed sequentially).
121#[cfg(target_arch = "aarch64")]
122#[target_feature(enable = "sha3")]
123unsafe fn keccak_f1600_sha3(state: &mut [u64; 25]) {
124    use core::arch::aarch64::*;
125
126    // Pack two scalars into a 128-bit SIMD register [a, b].
127    #[inline(always)]
128    unsafe fn u64x2(a: u64, b: u64) -> uint64x2_t {
129        vcombine_u64(vdup_n_u64(a), vdup_n_u64(b))
130    }
131
132    for &rc in &RC {
133        // === Theta ===
134        // Column parities: c[x] = XOR of 5 lanes in column x.
135        // EOR3(a,b,c) = a^b^c; chaining two EOR3s gives a 5-way XOR.
136
137        // c[0] and c[1] in one SIMD register.
138        let c01 = {
139            let t = veor3q_u64(
140                u64x2(state[0], state[1]),
141                u64x2(state[5], state[6]),
142                u64x2(state[10], state[11]),
143            );
144            veor3q_u64(t, u64x2(state[15], state[16]), u64x2(state[20], state[21]))
145        };
146        // c[2] and c[3].
147        let c23 = {
148            let t = veor3q_u64(
149                u64x2(state[2], state[3]),
150                u64x2(state[7], state[8]),
151                u64x2(state[12], state[13]),
152            );
153            veor3q_u64(t, u64x2(state[17], state[18]), u64x2(state[22], state[23]))
154        };
155        // c[4] is the odd column; compute it scalar.
156        let c4 = state[4] ^ state[9] ^ state[14] ^ state[19] ^ state[24];
157
158        let c0 = vgetq_lane_u64::<0>(c01);
159        let c1 = vgetq_lane_u64::<1>(c01);
160        let c2 = vgetq_lane_u64::<0>(c23);
161        let c3 = vgetq_lane_u64::<1>(c23);
162
163        // D[x] = C[(x+4)%5] ^ rotate_left(C[(x+1)%5], 1).
164        // vrax1q_u64(a, b) = a ^ rotate_left(b, 1), elementwise.
165        //   D[0] = c4 ^ rotl(c1, 1)    D[1] = c0 ^ rotl(c2, 1)
166        //   D[2] = c1 ^ rotl(c3, 1)    D[3] = c2 ^ rotl(c4, 1)
167        let d01 = vrax1q_u64(u64x2(c4, c0), u64x2(c1, c2));
168        let d23 = vrax1q_u64(u64x2(c1, c2), u64x2(c3, c4));
169        let d4 = c3 ^ c0.rotate_left(1); // scalar
170
171        let d = [
172            vgetq_lane_u64::<0>(d01),
173            vgetq_lane_u64::<1>(d01),
174            vgetq_lane_u64::<0>(d23),
175            vgetq_lane_u64::<1>(d23),
176            d4,
177        ];
178
179        // Apply D[x] to every lane in column x.
180        for y in 0..5 {
181            for x in 0..5 {
182                state[x + 5 * y] ^= d[x];
183            }
184        }
185
186        // === Rho + Pi (scalar) ===
187        // Each of the 24 non-zero-rotation lanes has a unique RHO value, so
188        // XAR (which applies the same rotation to both elements of a pair)
189        // gives no advantage.  Keep the existing scalar loop.
190        let mut b = [0u64; 25];
191        for i in 0..25 {
192            b[PI[i]] = state[i].rotate_left(RHO[i]);
193        }
194
195        // === Chi using BCAX ===
196        // chi[x] = b[x] ^ (!b[(x+1)%5] & b[(x+2)%5])
197        //        = BCAX(b[x], b[(x+2)%5], b[(x+1)%5])
198        // vbcaxq_u64(a, b, c) = a ^ (b & !c), elementwise.
199        // Process each row of 5 lanes as two SIMD pairs + one scalar.
200        for y in 0..5 {
201            let r = y * 5;
202            // x=0: BCAX(b[0], b[2], b[1])    x=1: BCAX(b[1], b[3], b[2])
203            let chi01 = vbcaxq_u64(
204                u64x2(b[r], b[r + 1]),
205                u64x2(b[r + 2], b[r + 3]),
206                u64x2(b[r + 1], b[r + 2]),
207            );
208            // x=2: BCAX(b[2], b[4], b[3])    x=3: BCAX(b[3], b[0], b[4])
209            let chi23 = vbcaxq_u64(
210                u64x2(b[r + 2], b[r + 3]),
211                u64x2(b[r + 4], b[r]),
212                u64x2(b[r + 3], b[r + 4]),
213            );
214            state[r] = vgetq_lane_u64::<0>(chi01);
215            state[r + 1] = vgetq_lane_u64::<1>(chi01);
216            state[r + 2] = vgetq_lane_u64::<0>(chi23);
217            state[r + 3] = vgetq_lane_u64::<1>(chi23);
218            // x=4: BCAX(b[4], b[1], b[0]) = b[4] ^ (b[1] & !b[0])
219            state[r + 4] = b[r + 4] ^ (b[r + 1] & !b[r]);
220        }
221
222        // === Iota ===
223        state[0] ^= rc;
224    }
225}
226
227#[inline]
228fn absorb_block<const RATE: usize>(state: &mut [u64; 25], block: &[u8; RATE]) {
229    debug_assert_eq!(RATE % 8, 0, "Keccak rate must be lane-aligned");
230    let lanes = RATE / 8;
231    let mut i = 0usize;
232    while i < lanes {
233        let lane = u64::from_le_bytes(block[i * 8..i * 8 + 8].try_into().unwrap());
234        state[i] ^= lane;
235        i += 1;
236    }
237    keccak_f1600(state);
238}
239
240#[derive(Clone)]
241struct Keccak<const RATE: usize> {
242    state: [u64; 25],
243    block: [u8; RATE],
244    pos: usize,
245}
246
247#[derive(Clone)]
248struct KeccakSponge<const RATE: usize> {
249    state: [u64; 25],
250    block: [u8; RATE],
251    offset: usize,
252}
253
254#[derive(Clone)]
255enum XofState<const RATE: usize> {
256    Absorbing(Keccak<RATE>),
257    Squeezing(KeccakSponge<RATE>),
258}
259
260impl<const RATE: usize> XofState<RATE> {
261    fn zeroize(&mut self) {
262        match self {
263            XofState::Absorbing(inner) => {
264                crate::ct::zeroize_slice(inner.state.as_mut_slice());
265                crate::ct::zeroize_slice(inner.block.as_mut_slice());
266                inner.pos = 0;
267            }
268            XofState::Squeezing(sponge) => {
269                crate::ct::zeroize_slice(sponge.state.as_mut_slice());
270                crate::ct::zeroize_slice(sponge.block.as_mut_slice());
271                sponge.offset = 0;
272            }
273        }
274    }
275}
276
277#[inline]
278fn state_to_rate_bytes<const RATE: usize>(state: &[u64; 25]) -> [u8; RATE] {
279    let mut rate_bytes = [0u8; RATE];
280    let lanes = RATE / 8;
281    let mut i = 0usize;
282    while i < lanes {
283        rate_bytes[i * 8..i * 8 + 8].copy_from_slice(&state[i].to_le_bytes());
284        i += 1;
285    }
286    rate_bytes
287}
288
289impl<const RATE: usize> Keccak<RATE> {
290    fn new() -> Self {
291        Self {
292            state: [0u64; 25],
293            block: [0u8; RATE],
294            pos: 0,
295        }
296    }
297
298    fn update(&mut self, mut data: &[u8]) {
299        while !data.is_empty() {
300            let take = (RATE - self.pos).min(data.len());
301            self.block[self.pos..self.pos + take].copy_from_slice(&data[..take]);
302            self.pos += take;
303            data = &data[take..];
304
305            if self.pos == RATE {
306                absorb_block(&mut self.state, &self.block);
307                self.block = [0u8; RATE];
308                self.pos = 0;
309            }
310        }
311    }
312
313    fn finalize_sponge(mut self, suffix: u8) -> KeccakSponge<RATE> {
314        self.block[self.pos] ^= suffix;
315        self.block[RATE - 1] ^= 0x80;
316        absorb_block(&mut self.state, &self.block);
317        KeccakSponge {
318            block: state_to_rate_bytes(&self.state),
319            state: self.state,
320            offset: 0,
321        }
322    }
323
324    fn finalize<const OUT: usize>(self) -> [u8; OUT] {
325        let mut sponge = self.finalize_sponge(0x06);
326        let mut out = [0u8; OUT];
327        sponge.squeeze(&mut out);
328        out
329    }
330
331    fn finalize_into_reset<const OUT: usize>(&mut self, suffix: u8, out: &mut [u8; OUT]) {
332        self.block[self.pos] ^= suffix;
333        self.block[RATE - 1] ^= 0x80;
334        absorb_block(&mut self.state, &self.block);
335
336        let mut sponge: KeccakSponge<RATE> = KeccakSponge {
337            block: state_to_rate_bytes(&self.state),
338            state: self.state,
339            offset: 0,
340        };
341        sponge.squeeze(out);
342
343        crate::ct::zeroize_slice(sponge.state.as_mut_slice());
344        crate::ct::zeroize_slice(sponge.block.as_mut_slice());
345
346        crate::ct::zeroize_slice(self.state.as_mut_slice());
347        crate::ct::zeroize_slice(self.block.as_mut_slice());
348        self.pos = 0;
349    }
350}
351
352impl<const RATE: usize> KeccakSponge<RATE> {
353    fn squeeze(&mut self, out: &mut [u8]) {
354        let mut produced = 0usize;
355        while produced < out.len() {
356            if self.offset == RATE {
357                keccak_f1600(&mut self.state);
358                self.block = state_to_rate_bytes(&self.state);
359                self.offset = 0;
360            }
361
362            let take = (out.len() - produced).min(RATE - self.offset);
363            out[produced..produced + take]
364                .copy_from_slice(&self.block[self.offset..self.offset + take]);
365            produced += take;
366            self.offset += take;
367        }
368    }
369}
370
371macro_rules! define_sha3 {
372    ($name:ident, $rate:expr, $out_len:expr) => {
373        #[derive(Clone)]
374        pub struct $name {
375            inner: Keccak<$rate>,
376        }
377
378        impl Default for $name {
379            fn default() -> Self {
380                Self::new()
381            }
382        }
383
384        impl $name {
385            pub const BLOCK_LEN: usize = $rate;
386            pub const OUTPUT_LEN: usize = $out_len;
387
388            #[must_use]
389            pub fn new() -> Self {
390                Self {
391                    inner: Keccak::new(),
392                }
393            }
394
395            pub fn update(&mut self, data: &[u8]) {
396                self.inner.update(data);
397            }
398
399            #[must_use]
400            pub fn finalize(self) -> [u8; $out_len] {
401                self.inner.finalize()
402            }
403
404            #[must_use]
405            pub fn digest(data: &[u8]) -> [u8; $out_len] {
406                let mut h = Self::new();
407                h.update(data);
408                h.finalize()
409            }
410        }
411
412        impl Digest for $name {
413            const BLOCK_LEN: usize = $rate;
414            const OUTPUT_LEN: usize = $out_len;
415
416            fn new() -> Self {
417                $name {
418                    inner: Keccak::new(),
419                }
420            }
421
422            fn update(&mut self, data: &[u8]) {
423                self.inner.update(data);
424            }
425
426            fn finalize_into(self, out: &mut [u8]) {
427                assert_eq!(out.len(), $out_len, "wrong digest length");
428                let digest = self.inner.finalize::<$out_len>();
429                out.copy_from_slice(&digest);
430            }
431
432            fn finalize_reset(&mut self, out: &mut [u8]) {
433                let out: &mut [u8; $out_len] = out.try_into().expect("wrong digest length");
434                self.inner.finalize_into_reset::<$out_len>(0x06, out);
435            }
436
437            fn zeroize(&mut self) {
438                crate::ct::zeroize_slice(self.inner.state.as_mut_slice());
439                crate::ct::zeroize_slice(self.inner.block.as_mut_slice());
440                self.inner.pos = 0;
441            }
442        }
443    };
444}
445
446define_sha3!(Sha3_224, 144, 28);
447define_sha3!(Sha3_256, 136, 32);
448define_sha3!(Sha3_384, 104, 48);
449define_sha3!(Sha3_512, 72, 64);
450
451macro_rules! define_shake {
452    ($name:ident, $rate:expr) => {
453        #[derive(Clone)]
454        pub struct $name {
455            inner: XofState<$rate>,
456        }
457
458        impl Default for $name {
459            fn default() -> Self {
460                Self::new()
461            }
462        }
463
464        impl $name {
465            pub const BLOCK_LEN: usize = $rate;
466
467            #[must_use]
468            pub fn new() -> Self {
469                Self {
470                    inner: XofState::Absorbing(Keccak::new()),
471                }
472            }
473
474            pub fn digest(data: &[u8], out: &mut [u8]) {
475                let mut xof = Self::new();
476                xof.update(data);
477                xof.squeeze(out);
478            }
479        }
480
481        impl Xof for $name {
482            fn update(&mut self, data: &[u8]) {
483                match &mut self.inner {
484                    XofState::Absorbing(inner) => inner.update(data),
485                    XofState::Squeezing(_) => panic!("cannot absorb after squeezing"),
486                }
487            }
488
489            fn squeeze(&mut self, out: &mut [u8]) {
490                if let XofState::Absorbing(_) = self.inner {
491                    let prev =
492                        core::mem::replace(&mut self.inner, XofState::Absorbing(Keccak::new()));
493                    let sponge = match prev {
494                        XofState::Absorbing(inner) => inner.finalize_sponge(0x1f),
495                        XofState::Squeezing(sponge) => sponge,
496                    };
497                    self.inner = XofState::Squeezing(sponge);
498                }
499
500                match &mut self.inner {
501                    XofState::Absorbing(_) => unreachable!(),
502                    XofState::Squeezing(sponge) => sponge.squeeze(out),
503                }
504            }
505        }
506
507        impl Drop for $name {
508            fn drop(&mut self) {
509                self.inner.zeroize();
510            }
511        }
512    };
513}
514
515define_shake!(Shake128, 168);
516define_shake!(Shake256, 136);
517
518#[cfg(test)]
519mod tests {
520    use super::*;
521
522    fn hex(bytes: &[u8]) -> String {
523        let mut out = String::with_capacity(bytes.len() * 2);
524        for b in bytes {
525            use core::fmt::Write;
526            let _ = write!(&mut out, "{b:02x}");
527        }
528        out
529    }
530
531    #[test]
532    fn sha3_224_empty() {
533        assert_eq!(
534            hex(&Sha3_224::digest(b"")),
535            "6b4e03423667dbb73b6e15454f0eb1ab".to_owned() + "d4597f9a1b078e3f5b5a6bc7"
536        );
537    }
538
539    #[test]
540    fn sha3_256_empty() {
541        assert_eq!(
542            hex(&Sha3_256::digest(b"")),
543            "a7ffc6f8bf1ed76651c14756a061d662".to_owned() + "f580ff4de43b49fa82d80a4b80f8434a"
544        );
545    }
546
547    #[test]
548    fn sha3_256_abc_streaming() {
549        let mut h = Sha3_256::new();
550        h.update(b"a");
551        h.update(b"b");
552        h.update(b"c");
553        assert_eq!(
554            hex(&h.finalize()),
555            "3a985da74fe225b2045c172d6bd390bd".to_owned() + "855f086e3e9d525b46bfe24511431532"
556        );
557    }
558
559    #[test]
560    fn sha3_384_empty() {
561        assert_eq!(
562            hex(&Sha3_384::digest(b"")),
563            "0c63a75b845e4f7d01107d852e4c2485".to_owned()
564                + "c51a50aaaa94fc61995e71bbee983a2a"
565                + "c3713831264adb47fb6bd1e058d5f004"
566        );
567    }
568
569    #[test]
570    fn sha3_512_empty() {
571        assert_eq!(
572            hex(&Sha3_512::digest(b"")),
573            "a69f73cca23a9ac5c8b567dc185a756e".to_owned()
574                + "97c982164fe25859e0d1dcc1475c80a6"
575                + "15b2123af1f5f94c11e3e9402c3ac558"
576                + "f500199d95b6d3e301758586281dcd26"
577        );
578    }
579
580    #[test]
581    fn shake128_empty_32() {
582        let mut out = [0u8; 32];
583        Shake128::digest(b"", &mut out);
584        assert_eq!(
585            hex(&out),
586            "7f9c2ba4e88f827d616045507605853e".to_owned() + "d73b8093f6efbc88eb1a6eacfa66ef26"
587        );
588    }
589
590    #[test]
591    fn shake128_abc_streaming_32() {
592        let mut xof = Shake128::new();
593        xof.update(b"a");
594        xof.update(b"b");
595        xof.update(b"c");
596        let mut out = [0u8; 32];
597        xof.squeeze(&mut out);
598        assert_eq!(
599            hex(&out),
600            "5881092dd818bf5cf8a3ddb793fbcba7".to_owned() + "4097d5c526a6d35f97b83351940f2cc8"
601        );
602    }
603
604    #[test]
605    fn shake128_chunked_squeeze_matches_one_shot() {
606        let mut one_shot = Shake128::new();
607        one_shot.update(b"abc");
608        let mut full = [0u8; 64];
609        one_shot.squeeze(&mut full);
610
611        let mut chunked = Shake128::new();
612        chunked.update(b"abc");
613        let mut left = [0u8; 32];
614        let mut right = [0u8; 32];
615        chunked.squeeze(&mut left);
616        chunked.squeeze(&mut right);
617
618        assert_eq!([left.as_slice(), right.as_slice()].concat(), full);
619    }
620
621    #[test]
622    fn shake256_empty_64() {
623        let mut out = [0u8; 64];
624        Shake256::digest(b"", &mut out);
625        assert_eq!(
626            hex(&out),
627            "46b9dd2b0ba88d13233b3feb743eeb24".to_owned()
628                + "3fcd52ea62b81b82b50c27646ed5762f"
629                + "d75dc4ddd8c0f200cb05019d67b592f6"
630                + "fc821c49479ab48640292eacb3b7c4be"
631        );
632    }
633
634    #[test]
635    fn sha3_224_matches_openssl() {
636        let msg = b"The quick brown fox jumps over the lazy dog";
637        let Some(expected) = crate::test_utils::run_openssl(&["dgst", "-sha3-224", "-binary"], msg)
638        else {
639            return;
640        };
641        assert_eq!(Sha3_224::digest(msg).as_slice(), expected.as_slice());
642    }
643
644    #[test]
645    fn sha3_256_matches_openssl() {
646        let msg = b"The quick brown fox jumps over the lazy dog";
647        let Some(expected) = crate::test_utils::run_openssl(&["dgst", "-sha3-256", "-binary"], msg)
648        else {
649            return;
650        };
651        assert_eq!(Sha3_256::digest(msg).as_slice(), expected.as_slice());
652    }
653
654    #[test]
655    fn sha3_384_matches_openssl() {
656        let msg = b"The quick brown fox jumps over the lazy dog";
657        let Some(expected) = crate::test_utils::run_openssl(&["dgst", "-sha3-384", "-binary"], msg)
658        else {
659            return;
660        };
661        assert_eq!(Sha3_384::digest(msg).as_slice(), expected.as_slice());
662    }
663
664    #[test]
665    fn sha3_512_matches_openssl() {
666        let msg = b"The quick brown fox jumps over the lazy dog";
667        let Some(expected) = crate::test_utils::run_openssl(&["dgst", "-sha3-512", "-binary"], msg)
668        else {
669            return;
670        };
671        assert_eq!(Sha3_512::digest(msg).as_slice(), expected.as_slice());
672    }
673}