Skip to main content

dcrypt_algorithms/hash/sha3/
mod.rs

1//! SHA-3 hash function implementations
2//!
3//! Constant-time & side-channel-hardened Keccak sponge (FIPS 202).
4
5#[cfg(feature = "alloc")]
6use crate::alloc_prelude::*;
7use dcrypt_internal::zeroing::{Zeroize, ZeroizeOnDrop, Zeroizing};
8
9use crate::error::{validate, Result};
10use crate::hash::{HashAlgorithm, HashFunction};
11use crate::types::Digest;
12
13use core::sync::atomic::{compiler_fence, Ordering};
14
15// ──────────────────────────────── constants ────────────────────────────────
16
17use dcrypt_params::utils::hash::{
18    SHA3_224_OUTPUT_SIZE, SHA3_256_OUTPUT_SIZE, SHA3_384_OUTPUT_SIZE, SHA3_512_OUTPUT_SIZE,
19};
20
21const KECCAK_ROUNDS: usize = 24;
22const KECCAK_STATE_SIZE: usize = 25; // 5 × 5 u64
23const SHA3_224_RATE: usize = 144; // 1152 bits
24const SHA3_256_RATE: usize = 136; // 1088 bits
25const SHA3_384_RATE: usize = 104; // 832 bits
26const SHA3_512_RATE: usize = 72; // 576 bits
27
28/// Keccak round constants.
29const RC: [u64; KECCAK_ROUNDS] = [
30    0x0000_0000_0000_0001,
31    0x0000_0000_0000_8082,
32    0x8000_0000_0000_808A,
33    0x8000_0000_8000_8000,
34    0x0000_0000_0000_808B,
35    0x0000_0000_8000_0001,
36    0x8000_0000_8000_8081,
37    0x8000_0000_0000_8009,
38    0x0000_0000_0000_008A,
39    0x0000_0000_0000_0088,
40    0x0000_0000_8000_8009,
41    0x0000_0000_8000_000A,
42    0x0000_0000_8000_808B,
43    0x8000_0000_0000_008B,
44    0x8000_0000_0000_8089,
45    0x8000_0000_0000_8003,
46    0x8000_0000_0000_8002,
47    0x8000_0000_0000_0080,
48    0x0000_0000_0000_800A,
49    0x8000_0000_8000_000A,
50    0x8000_0000_8000_8081,
51    0x8000_0000_0000_8080,
52    0x0000_0000_8000_0001,
53    0x8000_0000_8000_8008,
54];
55
56/// Rotation offsets for the ρ step.
57const RHO: [u32; 24] = [
58    1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44,
59];
60
61/// π-mapping indexes.
62const PI: [usize; 24] = [
63    10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1,
64];
65
66// ────────────────────────── constant-time helpers ─────────────────────────
67
68#[inline(always)]
69fn get_byte_from_state(state: &[u64; KECCAK_STATE_SIZE], pos: usize) -> u8 {
70    let word = pos / 8;
71    let shift = (pos % 8) * 8;
72    ((state[word] >> shift) & 0xFF) as u8
73}
74
75#[inline(always)]
76fn xor_byte_in_state(state: &mut [u64; KECCAK_STATE_SIZE], pos: usize, val: u8) {
77    // Perform an unconditionally-executed, explicit read-modify-write so
78    // hashing an all-zero block still incurs the same memory traffic as
79    // hashing random data (mitigates store-elimination optimisations).
80    let word = pos / 8;
81    let shift = (pos % 8) * 8;
82    let mask = (val as u64) << shift;
83
84    let before = state[word];
85    state[word] = before ^ mask;
86
87    // Prevent the compiler from hoisting or eliminating the store.
88    compiler_fence(Ordering::SeqCst);
89}
90
91#[inline(always)]
92fn xor_bit_in_state(state: &mut [u64; KECCAK_STATE_SIZE], pos: usize, bit: u8) {
93    let byte_pos = pos / 8;
94    let bit_in_byte = pos % 8;
95    xor_byte_in_state(state, byte_pos, (bit & 1) << bit_in_byte);
96}
97
98fn validate_bit_string(data: &[u8], bit_len: usize) -> Result<(usize, usize)> {
99    let rounded_len = bit_len
100        .checked_add(7)
101        .ok_or_else(|| crate::error::Error::param("bit_length", "Bit length is too large"))?
102        / 8;
103    validate::length("bit-oriented SHA-3 input", data.len(), rounded_len)?;
104
105    let partial_bits = bit_len % 8;
106    if partial_bits != 0 {
107        let unused_mask = (1u8 << (8 - partial_bits)) - 1;
108        validate::parameter(
109            data[rounded_len - 1] & unused_mask == 0,
110            "data",
111            "Unused low bits in the final byte must be zero",
112        )?;
113    }
114
115    Ok((bit_len / 8, partial_bits))
116}
117
118// ──────────────────────── marker algorithm types ──────────────────────────
119
120/// Marker type for **SHA3-224**.
121pub enum Sha3_224Algorithm {}
122/// Marker type for **SHA3-256**.
123pub enum Sha3_256Algorithm {}
124/// Marker type for **SHA3-384**.
125pub enum Sha3_384Algorithm {}
126/// Marker type for **SHA3-512**.
127pub enum Sha3_512Algorithm {}
128
129impl HashAlgorithm for Sha3_224Algorithm {
130    const OUTPUT_SIZE: usize = SHA3_224_OUTPUT_SIZE;
131    const BLOCK_SIZE: usize = SHA3_224_RATE;
132    const ALGORITHM_ID: &'static str = "SHA3-224";
133}
134impl HashAlgorithm for Sha3_256Algorithm {
135    const OUTPUT_SIZE: usize = SHA3_256_OUTPUT_SIZE;
136    const BLOCK_SIZE: usize = SHA3_256_RATE;
137    const ALGORITHM_ID: &'static str = "SHA3-256";
138}
139impl HashAlgorithm for Sha3_384Algorithm {
140    const OUTPUT_SIZE: usize = SHA3_384_OUTPUT_SIZE;
141    const BLOCK_SIZE: usize = SHA3_384_RATE;
142    const ALGORITHM_ID: &'static str = "SHA3-384";
143}
144impl HashAlgorithm for Sha3_512Algorithm {
145    const OUTPUT_SIZE: usize = SHA3_512_OUTPUT_SIZE;
146    const BLOCK_SIZE: usize = SHA3_512_RATE;
147    const ALGORITHM_ID: &'static str = "SHA3-512";
148}
149
150// ───────────────────── engine structs (state + pointer) ───────────────────
151
152/// Streaming **SHA3-224** engine.
153#[derive(Clone)]
154pub struct Sha3_224 {
155    state: [u64; KECCAK_STATE_SIZE],
156    pt: usize,
157}
158
159/// Streaming **SHA3-256** engine.
160#[derive(Clone)]
161pub struct Sha3_256 {
162    state: [u64; KECCAK_STATE_SIZE],
163    pt: usize,
164}
165
166/// Streaming **SHA3-384** engine.
167#[derive(Clone)]
168pub struct Sha3_384 {
169    state: [u64; KECCAK_STATE_SIZE],
170    pt: usize,
171}
172
173/// Streaming **SHA3-512** engine.
174#[derive(Clone)]
175pub struct Sha3_512 {
176    state: [u64; KECCAK_STATE_SIZE],
177    pt: usize,
178}
179
180macro_rules! impl_sha3_zeroize {
181    ($name:ident) => {
182        impl Zeroize for $name {
183            fn zeroize(&mut self) {
184                self.state.zeroize();
185                self.pt.zeroize();
186            }
187        }
188
189        impl Drop for $name {
190            fn drop(&mut self) {
191                self.zeroize();
192            }
193        }
194
195        impl ZeroizeOnDrop for $name {}
196    };
197}
198
199impl_sha3_zeroize!(Sha3_224);
200impl_sha3_zeroize!(Sha3_256);
201impl_sha3_zeroize!(Sha3_384);
202impl_sha3_zeroize!(Sha3_512);
203
204// ─────────────────────── shared engine-helper macro ───────────────────────
205
206macro_rules! impl_sha3_variant {
207    ($name:ident, $rate:expr, $out:expr, $alg:ty) => {
208        impl $name {
209            #[inline(always)]
210            fn init() -> Self {
211                Self {
212                    state: [0u64; KECCAK_STATE_SIZE],
213                    pt: 0,
214                }
215            }
216            #[inline(always)]
217            fn rate() -> usize {
218                $rate
219            }
220
221            fn update_internal(&mut self, data: &[u8]) -> Result<()> {
222                validate::parameter(
223                    self.pt.checked_add(data.len()).is_some(),
224                    "data_length",
225                    "Integer overflow",
226                )?;
227                let r = Self::rate();
228                for &b in data {
229                    xor_byte_in_state(&mut self.state, self.pt, b);
230                    self.pt += 1;
231                    if self.pt == r {
232                        keccak_f1600(&mut self.state);
233                        self.pt = 0;
234                    }
235                }
236                Ok(())
237            }
238
239            fn finalize_internal(&mut self) -> Result<Zeroizing<[u8; $out]>> {
240                let r = Self::rate();
241                xor_byte_in_state(&mut self.state, self.pt, 0x06);
242                xor_byte_in_state(&mut self.state, r - 1, 0x80);
243                keccak_f1600(&mut self.state);
244
245                let mut out = Zeroizing::new([0u8; $out]);
246                for i in 0..$out {
247                    out[i] = get_byte_from_state(&self.state, i);
248                }
249
250                self.zeroize();
251                Ok(out)
252            }
253
254            fn finalize_bits_internal(
255                &mut self,
256                mut partial_byte: u8,
257                partial_bits: usize,
258            ) -> Result<Zeroizing<[u8; $out]>> {
259                let rate_bits = Self::rate() * 8;
260                let mut bit_pos = self.pt * 8;
261
262                // FIPS 202 numbers bits within each byte least-significant bit
263                // first.  The SHA-3 delimited suffix is 0x06, i.e. the three
264                // bits 0, 1, 1 following the message.
265                for i in 0..partial_bits {
266                    xor_bit_in_state(&mut self.state, bit_pos, (partial_byte >> i) & 1);
267                    bit_pos += 1;
268                    if bit_pos == rate_bits {
269                        keccak_f1600(&mut self.state);
270                        bit_pos = 0;
271                    }
272                }
273                partial_byte.zeroize();
274                for i in 0..3 {
275                    xor_bit_in_state(&mut self.state, bit_pos, (0x06 >> i) & 1);
276                    bit_pos += 1;
277                    if bit_pos == rate_bits {
278                        keccak_f1600(&mut self.state);
279                        bit_pos = 0;
280                    }
281                }
282
283                // The final bit of pad10*1 terminates the current rate block.
284                xor_bit_in_state(&mut self.state, rate_bits - 1, 1);
285                keccak_f1600(&mut self.state);
286
287                let mut out = Zeroizing::new([0u8; $out]);
288                for i in 0..$out {
289                    out[i] = get_byte_from_state(&self.state, i);
290                }
291
292                self.zeroize();
293                Ok(out)
294            }
295
296            /// Hash a bit string without changing the byte-oriented streaming
297            /// API. Bits in a partial final byte occupy its most-significant
298            /// positions, matching the hexadecimal representation used by
299            /// NIST ACVP SHA-3 vectors.
300            ///
301            /// The input must contain exactly `ceil(bit_len / 8)` bytes, and
302            /// any unused low bits in the final byte must be zero.
303            #[doc(hidden)]
304            pub fn digest_bits(data: &[u8], bit_len: usize) -> Result<Digest<$out>> {
305                let (full_bytes, partial_bits) = validate_bit_string(data, bit_len)?;
306                let mut hasher = Self::init();
307                hasher.update_internal(&data[..full_bytes])?;
308                let mut partial_byte = if partial_bits == 0 {
309                    0
310                } else {
311                    data[full_bytes] >> (8 - partial_bits)
312                };
313                let hash = hasher.finalize_bits_internal(partial_byte, partial_bits)?;
314                partial_byte.zeroize();
315                let mut digest = Digest::<$out>::zeroed();
316                digest.as_mut().copy_from_slice(&hash[..]);
317                Ok(digest)
318            }
319        }
320
321        impl HashFunction for $name {
322            type Algorithm = $alg;
323            type Output = Digest<$out>;
324
325            fn new() -> Self {
326                Self::init()
327            }
328
329            fn update(&mut self, data: &[u8]) -> Result<&mut Self> {
330                self.update_internal(data)?;
331                Ok(self)
332            }
333
334            fn finalize(&mut self) -> Result<Self::Output> {
335                let h = self.finalize_internal()?;
336                let mut digest = Digest::<$out>::zeroed();
337                digest.as_mut().copy_from_slice(&h[..]);
338                Ok(digest)
339            }
340
341            #[inline(always)]
342            fn output_size() -> usize {
343                <$alg as HashAlgorithm>::OUTPUT_SIZE
344            }
345            #[inline(always)]
346            fn block_size() -> usize {
347                <$alg as HashAlgorithm>::BLOCK_SIZE
348            }
349            #[inline(always)]
350            fn name() -> String {
351                <$alg as HashAlgorithm>::ALGORITHM_ID.to_string()
352            }
353        }
354    };
355}
356
357impl_sha3_variant!(
358    Sha3_224,
359    SHA3_224_RATE,
360    SHA3_224_OUTPUT_SIZE,
361    Sha3_224Algorithm
362);
363impl_sha3_variant!(
364    Sha3_256,
365    SHA3_256_RATE,
366    SHA3_256_OUTPUT_SIZE,
367    Sha3_256Algorithm
368);
369impl_sha3_variant!(
370    Sha3_384,
371    SHA3_384_RATE,
372    SHA3_384_OUTPUT_SIZE,
373    Sha3_384Algorithm
374);
375impl_sha3_variant!(
376    Sha3_512,
377    SHA3_512_RATE,
378    SHA3_512_OUTPUT_SIZE,
379    Sha3_512Algorithm
380);
381
382// ───────────────────────────── permutation ────────────────────────────────
383
384fn keccak_f1600(state: &mut [u64; KECCAK_STATE_SIZE]) {
385    for &rc in RC.iter().take(KECCAK_ROUNDS) {
386        // θ
387        let mut c = Zeroizing::new([0u64; 5]);
388        for x in 0..5 {
389            c[x] = state[x] ^ state[x + 5] ^ state[x + 10] ^ state[x + 15] ^ state[x + 20];
390        }
391        for x in 0..5 {
392            let mut d = c[(x + 4) % 5] ^ c[(x + 1) % 5].rotate_left(1);
393            for y in 0..5 {
394                state[x + 5 * y] ^= d;
395            }
396            d.zeroize();
397        }
398        // ρ + π
399        let mut t = state[1];
400        for i in 0..24 {
401            let j = PI[i];
402            let tmp = state[j];
403            state[j] = t.rotate_left(RHO[i]);
404            t = tmp;
405        }
406        t.zeroize();
407        // χ
408        for y in 0..5 {
409            let mut row = Zeroizing::new([0u64; 5]);
410            for x in 0..5 {
411                row[x] = state[x + 5 * y];
412            }
413            for x in 0..5 {
414                state[x + 5 * y] ^= (!row[(x + 1) % 5]) & row[(x + 2) % 5];
415            }
416        }
417        // ι
418        state[0] ^= rc;
419    }
420}
421
422#[cfg(test)]
423mod tests;