Skip to main content

crypto/sha3/
keccak.rs

1use core::cmp::min;
2
3#[cfg(feature = "zeroize")]
4use zeroize::{Zeroize, ZeroizeOnDrop};
5
6pub const RHO: [u32; 24] = [
7    1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44,
8];
9
10pub const PI: [usize; 24] = [
11    10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1,
12];
13
14pub const ROUND_CONSTANTS: [u64; 24] = [
15    0x0000000000000001,
16    0x0000000000008082,
17    0x800000000000808a,
18    0x8000000080008000,
19    0x000000000000808b,
20    0x0000000080000001,
21    0x8000000080008081,
22    0x8000000000008009,
23    0x000000000000008a,
24    0x0000000000000088,
25    0x0000000080008009,
26    0x000000008000000a,
27    0x000000008000808b,
28    0x800000000000008b,
29    0x8000000000008089,
30    0x8000000000008003,
31    0x8000000000008002,
32    0x8000000000000080,
33    0x000000000000800a,
34    0x800000008000000a,
35    0x8000000080008081,
36    0x8000000000008080,
37    0x0000000080000001,
38    0x8000000080008008,
39];
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
43enum SpongeMode {
44    Absorbing,
45    Squeezing,
46}
47
48/// A sponge construction based on Keccak p1600
49#[derive(Clone)]
50#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
51// the struct requires 8-byte alignment for `ptr::copy_nonoverlapping` UB check on wasm32:
52// `state` is transmuted to `&mut [u64; 25]` in `permute_and_reset_pos`, which needs 8-byte alignment.
53#[repr(align(8))]
54pub struct KeccakSponge<const ROUNDS: usize> {
55    state: [u8; 200],
56    rate: usize,
57    padding: u8,
58    /// the current position in the state buffer
59    pos: usize,
60    mode: SpongeMode,
61}
62
63impl<const ROUNDS: usize> KeccakSponge<ROUNDS> {
64    #[inline]
65    pub fn new(rate: usize, delimiter: u8) -> Self {
66        debug_assert!(rate > 0 && rate < 200);
67        return KeccakSponge {
68            state: [0u8; 200],
69            rate,
70            padding: delimiter,
71            pos: 0,
72            mode: SpongeMode::Absorbing,
73        };
74    }
75
76    #[inline]
77    pub fn absorb(&mut self, data: &[u8]) {
78        assert_eq!(self.mode, SpongeMode::Absorbing, "absorb can't be called after squeezing");
79
80        // we first need to prevent `data` to overflow into `capacity`
81        let rate_remainder = min(self.rate - self.pos, data.len());
82        self.absorb_chunk(&data[..rate_remainder]);
83
84        // then we can absorbe `RATE`-sized chunks
85        for chunk in data[rate_remainder..].chunks(self.rate) {
86            self.absorb_chunk(chunk);
87        }
88    }
89
90    #[inline]
91    pub fn squeeze(&mut self, out: &mut [u8]) {
92        // if we're still absorbing, pad and apply the permutation
93        if self.mode == SpongeMode::Absorbing {
94            self.pad_and_permute();
95            self.mode = SpongeMode::Squeezing;
96        }
97
98        // we first need to prevent `out` to overflow into `capacity`
99        let rate_remainder = min(self.rate - self.pos, out.len());
100        self.squeeze_chunk(&mut out[..rate_remainder]);
101
102        // then we can squeeze `RATE`-sized chunks
103        for chunk in out[rate_remainder..].chunks_mut(self.rate) {
104            self.squeeze_chunk(chunk);
105        }
106    }
107
108    #[inline]
109    fn absorb_chunk(&mut self, chunk: &[u8]) {
110        xor(&mut self.state[self.pos..self.pos + chunk.len()], chunk);
111        self.pos += chunk.len();
112
113        // if the sponge is full, apply the permutation
114        if self.pos == self.rate {
115            self.permute_and_reset_pos();
116        }
117    }
118
119    #[inline]
120    fn squeeze_chunk(&mut self, out: &mut [u8]) {
121        if self.pos == self.rate {
122            self.permute_and_reset_pos();
123        }
124
125        out.copy_from_slice(&self.state[self.pos..self.pos + out.len()]);
126        self.pos += out.len();
127    }
128
129    #[inline]
130    fn pad_and_permute(&mut self) {
131        self.state[self.pos] ^= self.padding;
132        self.state[self.rate - 1] ^= 0x80;
133        self.permute_and_reset_pos();
134    }
135
136    #[inline]
137    fn permute_and_reset_pos(&mut self) {
138        // this is totally safe as long as state.len() == 200 and state remains a [u8]. We are just
139        // playing with the memory representation of the array, from [u8] to [u64].
140        let state: &mut [u64; 200 / 8] = unsafe { core::mem::transmute(&mut self.state) };
141        p1600::<ROUNDS>(state);
142        self.pos = 0;
143    }
144}
145
146/// The Keccak-p permutation of a 1600-bit state.
147///
148/// On aarch64 the ARMv8.4-A SHA-3 instructions are used when the running CPU
149/// implements the optional FEAT_SHA3 extension; otherwise the portable
150/// implementation is used.
151pub fn p1600<const ROUNDS: usize>(state: &mut [u64; 25]) {
152    const {
153        assert!(ROUNDS <= 24, "A round_count greater than 24 is not supported.");
154    }
155
156    // SAFETY: `p1600_armv8` requires the `sha3` target feature, which
157    // is dynamically or statically detected.
158    #[cfg(target_arch = "aarch64")]
159    unsafe {
160        // FEAT_SHA3 is optional on aarch64, so it must be detected before the
161        // hardware permutation is used. Without `std` the feature is known at
162        // compile time instead.
163        #[cfg(feature = "std")]
164        if std::arch::is_aarch64_feature_detected!("sha3") {
165            return super::keccak_arm64::p1600_armv8::<ROUNDS>(state);
166        }
167
168        #[cfg(all(not(feature = "std"), target_feature = "sha3"))]
169        return super::keccak_arm64::p1600_armv8::<ROUNDS>(state);
170    }
171
172    p1600_soft::<ROUNDS>(state);
173}
174
175/// Portable implementation of the Keccak-p permutation of a 1600-bit state.
176#[inline(always)]
177fn p1600_soft<const ROUNDS: usize>(state: &mut [u64; 25]) {
178    // https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf#page=25
179    // "the rounds of KECCAK-p[b, nr] match the last rounds of KECCAK-f[b]"
180    let round_consts: &[u64] = &ROUND_CONSTANTS[(24 - ROUNDS)..];
181
182    // not unrolling this loop may results in a smaller function, plus
183    // it may positively influences performance due to the smaller number of instructions
184    for &rc in round_consts {
185        let mut array = [0u64; 5];
186
187        // Theta
188        for x in 0..5 {
189            for y in 0..5 {
190                array[x] ^= state[5 * y + x];
191            }
192        }
193
194        for x in 0..5 {
195            let t1 = array[(x + 4) % 5];
196            let t2 = array[(x + 1) % 5].rotate_left(1);
197            for y in 0..5 {
198                state[5 * y + x] ^= t1 ^ t2;
199            }
200        }
201
202        // Rho and pi
203        let mut last = state[1];
204        for x in 0..24 {
205            array[0] = state[PI[x]];
206            state[PI[x]] = last.rotate_left(RHO[x]);
207            last = array[0];
208        }
209
210        // Chi
211        for y_step in 0..5 {
212            let y = 5 * y_step;
213
214            array.copy_from_slice(&state[y..][..5]);
215
216            for x in 0..5 {
217                let t1 = !array[(x + 1) % 5];
218                let t2 = array[(x + 2) % 5];
219                state[y + x] = array[x] ^ (t1 & t2);
220            }
221        }
222
223        // Iota
224        state[0] ^= rc;
225    }
226}
227
228/// xor dest with source. source is not modified.
229#[inline(always)]
230fn xor(dest: &mut [u8], source: &[u8]) {
231    dest.iter_mut()
232        .zip(source.iter())
233        .for_each(|(dest, source)| *dest ^= *source);
234}
235
236#[cfg(test)]
237mod tests {
238    use super::{p1600, p1600_soft};
239
240    fn keccak_f(permute: impl Fn(&mut [u64; 25]), state_first: [u64; 25], state_second: [u64; 25]) {
241        let mut state = [0u64; 25];
242
243        permute(&mut state);
244        assert_eq!(state, state_first);
245
246        permute(&mut state);
247        assert_eq!(state, state_second);
248    }
249
250    // Test vectors are copied from XKCP (eXtended Keccak Code Package)
251    // https://github.com/XKCP/XKCP/blob/master/tests/TestVectors/KeccakF-1600-IntermediateValues.txt
252    fn keccak_f1600_vectors() -> ([u64; 25], [u64; 25]) {
253        let state_first = [
254            0xF1258F7940E1DDE7,
255            0x84D5CCF933C0478A,
256            0xD598261EA65AA9EE,
257            0xBD1547306F80494D,
258            0x8B284E056253D057,
259            0xFF97A42D7F8E6FD4,
260            0x90FEE5A0A44647C4,
261            0x8C5BDA0CD6192E76,
262            0xAD30A6F71B19059C,
263            0x30935AB7D08FFC64,
264            0xEB5AA93F2317D635,
265            0xA9A6E6260D712103,
266            0x81A57C16DBCF555F,
267            0x43B831CD0347C826,
268            0x01F22F1A11A5569F,
269            0x05E5635A21D9AE61,
270            0x64BEFEF28CC970F2,
271            0x613670957BC46611,
272            0xB87C5A554FD00ECB,
273            0x8C3EE88A1CCF32C8,
274            0x940C7922AE3A2614,
275            0x1841F924A2C509E4,
276            0x16F53526E70465C2,
277            0x75F644E97F30A13B,
278            0xEAF1FF7B5CECA249,
279        ];
280        let state_second = [
281            0x2D5C954DF96ECB3C,
282            0x6A332CD07057B56D,
283            0x093D8D1270D76B6C,
284            0x8A20D9B25569D094,
285            0x4F9C4F99E5E7F156,
286            0xF957B9A2DA65FB38,
287            0x85773DAE1275AF0D,
288            0xFAF4F247C3D810F7,
289            0x1F1B9EE6F79A8759,
290            0xE4FECC0FEE98B425,
291            0x68CE61B6B9CE68A1,
292            0xDEEA66C4BA8F974F,
293            0x33C43D836EAFB1F5,
294            0xE00654042719DBD9,
295            0x7CF8A9F009831265,
296            0xFD5449A6BF174743,
297            0x97DDAD33D8994B40,
298            0x48EAD5FC5D0BE774,
299            0xE3B8C8EE55B7B03C,
300            0x91A0226E649E42E9,
301            0x900E3129E7BADD7B,
302            0x202A9EC5FAA3CCE8,
303            0x5B3402464E1C3DB6,
304            0x609F4E62A44C1059,
305            0x20D06CD26A8FBF5C,
306        ];
307
308        (state_first, state_second)
309    }
310
311    #[test]
312    fn keccak_f1600() {
313        let (state_first, state_second) = keccak_f1600_vectors();
314        keccak_f(|state| p1600::<24>(state), state_first, state_second);
315    }
316
317    #[test]
318    fn keccak_f1600_soft() {
319        let (state_first, state_second) = keccak_f1600_vectors();
320        keccak_f(|state| p1600_soft::<24>(state), state_first, state_second);
321    }
322}