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 mut chunk in out[rate_remainder..].chunks_mut(self.rate) {
104            self.squeeze_chunk(&mut 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 mut state: &mut [u64; 200 / 8] = unsafe { core::mem::transmute(&mut self.state) };
141        p1600::<ROUNDS>(&mut state);
142        self.pos = 0;
143    }
144}
145
146/// The Keccak-p permutation a 1600-bit state
147#[allow(unreachable_code)]
148pub fn p1600<const ROUNDS: usize>(state: &mut [u64; 25]) {
149    const {
150        assert!(ROUNDS <= 24, "A round_count greater than 24 is not supported.");
151    }
152
153    // we assume that the SHA-3 instructions are always preseent for aarch64
154    #[cfg(target_arch = "aarch64")]
155    unsafe {
156        super::keccak_arm64::p1600_armv8::<ROUNDS>(state);
157        return;
158    }
159
160    // https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf#page=25
161    // "the rounds of KECCAK-p[b, nr] match the last rounds of KECCAK-f[b]"
162    let round_consts: &[u64] = &ROUND_CONSTANTS[(24 - ROUNDS)..];
163
164    // not unrolling this loop may results in a smaller function, plus
165    // it may positively influences performance due to the smaller number of instructions
166    for &rc in round_consts {
167        let mut array = [0u64; 5];
168
169        // Theta
170        for x in 0..5 {
171            for y in 0..5 {
172                array[x] ^= state[5 * y + x];
173            }
174        }
175
176        for x in 0..5 {
177            let t1 = array[(x + 4) % 5];
178            let t2 = array[(x + 1) % 5].rotate_left(1);
179            for y in 0..5 {
180                state[5 * y + x] ^= t1 ^ t2;
181            }
182        }
183
184        // Rho and pi
185        let mut last = state[1];
186        for x in 0..24 {
187            array[0] = state[PI[x]];
188            state[PI[x]] = last.rotate_left(RHO[x]);
189            last = array[0];
190        }
191
192        // Chi
193        for y_step in 0..5 {
194            let y = 5 * y_step;
195
196            array.copy_from_slice(&state[y..][..5]);
197
198            for x in 0..5 {
199                let t1 = !array[(x + 1) % 5];
200                let t2 = array[(x + 2) % 5];
201                state[y + x] = array[x] ^ (t1 & t2);
202            }
203        }
204
205        // Iota
206        state[0] ^= rc;
207    }
208}
209
210/// xor dest with source. source is not modified.
211#[inline(always)]
212fn xor(dest: &mut [u8], source: &[u8]) {
213    dest.iter_mut()
214        .zip(source.iter())
215        .for_each(|(dest, source)| *dest ^= *source);
216}
217
218#[cfg(test)]
219mod tests {
220    use super::p1600;
221
222    fn keccak_f(state_first: [u64; 25], state_second: [u64; 25]) {
223        let mut state = [0u64; 25];
224
225        p1600::<24>(&mut state);
226        assert_eq!(state, state_first);
227
228        p1600::<24>(&mut state);
229        assert_eq!(state, state_second);
230    }
231
232    #[test]
233    fn keccak_f1600() {
234        // Test vectors are copied from XKCP (eXtended Keccak Code Package)
235        // https://github.com/XKCP/XKCP/blob/master/tests/TestVectors/KeccakF-1600-IntermediateValues.txt
236        let state_first = [
237            0xF1258F7940E1DDE7,
238            0x84D5CCF933C0478A,
239            0xD598261EA65AA9EE,
240            0xBD1547306F80494D,
241            0x8B284E056253D057,
242            0xFF97A42D7F8E6FD4,
243            0x90FEE5A0A44647C4,
244            0x8C5BDA0CD6192E76,
245            0xAD30A6F71B19059C,
246            0x30935AB7D08FFC64,
247            0xEB5AA93F2317D635,
248            0xA9A6E6260D712103,
249            0x81A57C16DBCF555F,
250            0x43B831CD0347C826,
251            0x01F22F1A11A5569F,
252            0x05E5635A21D9AE61,
253            0x64BEFEF28CC970F2,
254            0x613670957BC46611,
255            0xB87C5A554FD00ECB,
256            0x8C3EE88A1CCF32C8,
257            0x940C7922AE3A2614,
258            0x1841F924A2C509E4,
259            0x16F53526E70465C2,
260            0x75F644E97F30A13B,
261            0xEAF1FF7B5CECA249,
262        ];
263        let state_second = [
264            0x2D5C954DF96ECB3C,
265            0x6A332CD07057B56D,
266            0x093D8D1270D76B6C,
267            0x8A20D9B25569D094,
268            0x4F9C4F99E5E7F156,
269            0xF957B9A2DA65FB38,
270            0x85773DAE1275AF0D,
271            0xFAF4F247C3D810F7,
272            0x1F1B9EE6F79A8759,
273            0xE4FECC0FEE98B425,
274            0x68CE61B6B9CE68A1,
275            0xDEEA66C4BA8F974F,
276            0x33C43D836EAFB1F5,
277            0xE00654042719DBD9,
278            0x7CF8A9F009831265,
279            0xFD5449A6BF174743,
280            0x97DDAD33D8994B40,
281            0x48EAD5FC5D0BE774,
282            0xE3B8C8EE55B7B03C,
283            0x91A0226E649E42E9,
284            0x900E3129E7BADD7B,
285            0x202A9EC5FAA3CCE8,
286            0x5B3402464E1C3DB6,
287            0x609F4E62A44C1059,
288            0x20D06CD26A8FBF5C,
289        ];
290
291        keccak_f(state_first, state_second);
292    }
293}