Skip to main content

crypto/sha3/
keccak.rs

1use std::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))]
43pub(crate) enum SpongeMode {
44    Absorbing,
45    Squeezing,
46}
47
48#[derive(Clone)]
49#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
50pub(crate) struct Keccak<const ROUNDS: usize> {
51    state: [u8; 200],
52    rate: usize,
53    padding: u8,
54    /// the current position in the state buffer
55    pos: usize,
56    mode: SpongeMode,
57}
58
59// A sponge construction based on Keccak p1600
60impl<const ROUNDS: usize> Keccak<ROUNDS> {
61    #[inline]
62    pub(crate) fn new(rate: usize, delimiter: u8) -> Self {
63        debug_assert!(rate > 0 && rate < 200);
64        return Keccak {
65            state: [0u8; 200],
66            rate,
67            padding: delimiter,
68            pos: 0,
69            mode: SpongeMode::Absorbing,
70        };
71    }
72
73    #[inline]
74    pub(crate) fn absorb(&mut self, data: &[u8]) {
75        assert_eq!(self.mode, SpongeMode::Absorbing, "absorb can't be called after squeezing");
76
77        // we first need to prevent `data` to overflow into `capacity`
78        let rate_remainder = min(self.rate - self.pos, data.len());
79        self.absorb_chunk(&data[..rate_remainder]);
80
81        // then we can absorbe `RATE`-sized chunks
82        for chunk in data[rate_remainder..].chunks(self.rate) {
83            self.absorb_chunk(chunk);
84        }
85    }
86
87    #[inline]
88    fn absorb_chunk(&mut self, chunk: &[u8]) {
89        // xor(&mut self.state[self.pos..RATE], &chunk);
90        xor(&mut self.state[self.pos..self.pos + chunk.len()], &chunk);
91        self.pos += chunk.len();
92
93        // if the sponge is full, apply the permutation
94        if self.pos == self.rate {
95            self.permute_and_reset_pos();
96        }
97    }
98
99    #[inline]
100    pub fn squeeze(&mut self, out: &mut [u8]) {
101        // if we're still absorbing, pad and apply the permutation
102        if self.mode == SpongeMode::Absorbing {
103            self.pad_and_permute();
104            self.mode = SpongeMode::Squeezing;
105        }
106
107        // we first need to prevent `out` to overflow into `capacity`
108        let rate_remainder = min(self.rate - self.pos, out.len());
109        self.squeeze_chunk(&mut out[..rate_remainder]);
110
111        // then we can squeeze `RATE`-sized chunks
112        for mut chunk in out[rate_remainder..].chunks_mut(self.rate) {
113            self.squeeze_chunk(&mut chunk);
114        }
115    }
116
117    #[inline]
118    fn squeeze_chunk(&mut self, out: &mut [u8]) {
119        if self.pos == self.rate {
120            self.permute_and_reset_pos();
121        }
122
123        out.copy_from_slice(&self.state[self.pos..self.pos + out.len()]);
124        self.pos += out.len();
125    }
126
127    #[inline]
128    fn pad_and_permute(&mut self) {
129        self.state[self.pos] ^= self.padding;
130        self.state[self.rate - 1] ^= 0x80;
131        self.permute_and_reset_pos();
132    }
133
134    #[inline]
135    fn permute_and_reset_pos(&mut self) {
136        // this is totally safe as long as state.len() == 200 and state remains a [u8]. We are just
137        // playing with the memory representation of the array, from [u8] to [u64].
138        let mut state: &mut [u64; 200 / 8] = unsafe { core::mem::transmute(&mut self.state) };
139        p1600::<ROUNDS>(&mut state);
140        self.pos = 0;
141    }
142}
143
144/// The Keccak-p sponge function for a 1600-bit state
145#[allow(unreachable_code)]
146pub fn p1600<const ROUNDS: usize>(state: &mut [u64; 25]) {
147    debug_assert!(ROUNDS <= 24, "A round_count greater than 24 is not supported.");
148
149    // we assume that the SHA-3 instructions are always preseent for aarch64
150    #[cfg(target_arch = "aarch64")]
151    unsafe {
152        super::keccak_arm64::p1600_armv8::<ROUNDS>(state);
153        return;
154    }
155
156    // https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf#page=25
157    // "the rounds of KECCAK-p[b, nr] match the last rounds of KECCAK-f[b]"
158    let round_consts: &[u64] = &ROUND_CONSTANTS[(24 - ROUNDS)..];
159
160    // not unrolling this loop may results in a smaller function, plus
161    // it may positively influences performance due to the smaller number of instructions
162    for &rc in round_consts {
163        let mut array = [0u64; 5];
164
165        // Theta
166        for x in 0..5 {
167            for y in 0..5 {
168                array[x] ^= state[5 * y + x];
169            }
170        }
171
172        for x in 0..5 {
173            let t1 = array[(x + 4) % 5];
174            let t2 = array[(x + 1) % 5].rotate_left(1);
175            for y in 0..5 {
176                state[5 * y + x] ^= t1 ^ t2;
177            }
178        }
179
180        // Rho and pi
181        let mut last = state[1];
182        for x in 0..24 {
183            array[0] = state[PI[x]];
184            state[PI[x]] = last.rotate_left(RHO[x]);
185            last = array[0];
186        }
187
188        // Chi
189        for y_step in 0..5 {
190            let y = 5 * y_step;
191
192            array.copy_from_slice(&state[y..][..5]);
193
194            for x in 0..5 {
195                let t1 = !array[(x + 1) % 5];
196                let t2 = array[(x + 2) % 5];
197                state[y + x] = array[x] ^ (t1 & t2);
198            }
199        }
200
201        // Iota
202        state[0] ^= rc;
203    }
204}
205
206/// xor dest with source. source is not modified.
207#[inline(always)]
208pub fn xor(dest: &mut [u8], source: &[u8]) {
209    dest.iter_mut()
210        .zip(source.iter())
211        .for_each(|(dest, source)| *dest ^= *source);
212}
213
214#[cfg(test)]
215mod tests {
216    use super::p1600;
217
218    fn keccak_f(state_first: [u64; 25], state_second: [u64; 25]) {
219        let mut state = [0u64; 25];
220
221        p1600::<24>(&mut state);
222        assert_eq!(state, state_first);
223
224        p1600::<24>(&mut state);
225        assert_eq!(state, state_second);
226    }
227
228    #[test]
229    fn keccak_f1600() {
230        // Test vectors are copied from XKCP (eXtended Keccak Code Package)
231        // https://github.com/XKCP/XKCP/blob/master/tests/TestVectors/KeccakF-1600-IntermediateValues.txt
232        let state_first = [
233            0xF1258F7940E1DDE7,
234            0x84D5CCF933C0478A,
235            0xD598261EA65AA9EE,
236            0xBD1547306F80494D,
237            0x8B284E056253D057,
238            0xFF97A42D7F8E6FD4,
239            0x90FEE5A0A44647C4,
240            0x8C5BDA0CD6192E76,
241            0xAD30A6F71B19059C,
242            0x30935AB7D08FFC64,
243            0xEB5AA93F2317D635,
244            0xA9A6E6260D712103,
245            0x81A57C16DBCF555F,
246            0x43B831CD0347C826,
247            0x01F22F1A11A5569F,
248            0x05E5635A21D9AE61,
249            0x64BEFEF28CC970F2,
250            0x613670957BC46611,
251            0xB87C5A554FD00ECB,
252            0x8C3EE88A1CCF32C8,
253            0x940C7922AE3A2614,
254            0x1841F924A2C509E4,
255            0x16F53526E70465C2,
256            0x75F644E97F30A13B,
257            0xEAF1FF7B5CECA249,
258        ];
259        let state_second = [
260            0x2D5C954DF96ECB3C,
261            0x6A332CD07057B56D,
262            0x093D8D1270D76B6C,
263            0x8A20D9B25569D094,
264            0x4F9C4F99E5E7F156,
265            0xF957B9A2DA65FB38,
266            0x85773DAE1275AF0D,
267            0xFAF4F247C3D810F7,
268            0x1F1B9EE6F79A8759,
269            0xE4FECC0FEE98B425,
270            0x68CE61B6B9CE68A1,
271            0xDEEA66C4BA8F974F,
272            0x33C43D836EAFB1F5,
273            0xE00654042719DBD9,
274            0x7CF8A9F009831265,
275            0xFD5449A6BF174743,
276            0x97DDAD33D8994B40,
277            0x48EAD5FC5D0BE774,
278            0xE3B8C8EE55B7B03C,
279            0x91A0226E649E42E9,
280            0x900E3129E7BADD7B,
281            0x202A9EC5FAA3CCE8,
282            0x5B3402464E1C3DB6,
283            0x609F4E62A44C1059,
284            0x20D06CD26A8FBF5C,
285        ];
286
287        keccak_f(state_first, state_second);
288    }
289}