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