Skip to main content

dcrypt_algorithms/hash/keccak/
mod.rs

1//! Keccak-256 hash function implementation (Ethereum compatible)
2//!
3//! This module implements the Keccak-256 hash function as used by Ethereum.
4//! It differs from NIST SHA3-256 only in the padding rule (domain separator).
5//!
6//! - **SHA3-256**: `0x06` domain separator.
7//! - **Keccak-256**: `0x01` domain separator.
8
9#[cfg(feature = "alloc")]
10use crate::alloc_prelude::*;
11use dcrypt_internal::zeroing::{Zeroize, ZeroizeOnDrop, Zeroizing};
12
13use crate::error::{validate, Result};
14use crate::hash::{HashAlgorithm, HashFunction};
15use crate::types::Digest;
16
17use core::sync::atomic::{compiler_fence, Ordering};
18
19use dcrypt_params::utils::hash::{KECCAK256_BLOCK_SIZE, KECCAK256_OUTPUT_SIZE};
20
21const KECCAK_ROUNDS: usize = 24;
22const KECCAK_STATE_SIZE: usize = 25; // 5 × 5 u64
23const KECCAK256_RATE: usize = KECCAK256_BLOCK_SIZE;
24
25/// Keccak round constants.
26const RC: [u64; KECCAK_ROUNDS] = [
27    0x0000_0000_0000_0001,
28    0x0000_0000_0000_8082,
29    0x8000_0000_0000_808A,
30    0x8000_0000_8000_8000,
31    0x0000_0000_0000_808B,
32    0x0000_0000_8000_0001,
33    0x8000_0000_8000_8081,
34    0x8000_0000_0000_8009,
35    0x0000_0000_0000_008A,
36    0x0000_0000_0000_0088,
37    0x0000_0000_8000_8009,
38    0x0000_0000_8000_000A,
39    0x0000_0000_8000_808B,
40    0x8000_0000_0000_008B,
41    0x8000_0000_0000_8089,
42    0x8000_0000_0000_8003,
43    0x8000_0000_0000_8002,
44    0x8000_0000_0000_0080,
45    0x0000_0000_0000_800A,
46    0x8000_0000_8000_000A,
47    0x8000_0000_8000_8081,
48    0x8000_0000_0000_8080,
49    0x0000_0000_8000_0001,
50    0x8000_0000_8000_8008,
51];
52
53/// Rotation offsets for the ρ step.
54const RHO: [u32; 24] = [
55    1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44,
56];
57
58/// π-mapping indexes.
59const PI: [usize; 24] = [
60    10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1,
61];
62
63// ────────────────────────── constant-time helpers ─────────────────────────
64
65#[inline(always)]
66fn get_byte_from_state(state: &[u64; KECCAK_STATE_SIZE], pos: usize) -> u8 {
67    let word = pos / 8;
68    let shift = (pos % 8) * 8;
69    ((state[word] >> shift) & 0xFF) as u8
70}
71
72#[inline(always)]
73fn xor_byte_in_state(state: &mut [u64; KECCAK_STATE_SIZE], pos: usize, val: u8) {
74    let word = pos / 8;
75    let shift = (pos % 8) * 8;
76    let mask = (val as u64) << shift;
77
78    let before = state[word];
79    state[word] = before ^ mask;
80
81    compiler_fence(Ordering::SeqCst);
82}
83
84// ──────────────────────── marker algorithm types ──────────────────────────
85
86/// Marker type for **Keccak-256** (Ethereum compatible).
87pub enum Keccak256Algorithm {}
88
89impl HashAlgorithm for Keccak256Algorithm {
90    const OUTPUT_SIZE: usize = KECCAK256_OUTPUT_SIZE;
91    const BLOCK_SIZE: usize = KECCAK256_RATE;
92    const ALGORITHM_ID: &'static str = "Keccak-256";
93}
94
95// ───────────────────── engine structs (state + pointer) ───────────────────
96
97/// Streaming **Keccak-256** engine.
98#[derive(Clone)]
99pub struct Keccak256 {
100    state: [u64; KECCAK_STATE_SIZE],
101    pt: usize,
102}
103
104impl Zeroize for Keccak256 {
105    fn zeroize(&mut self) {
106        self.state.zeroize();
107        self.pt.zeroize();
108    }
109}
110
111impl Drop for Keccak256 {
112    fn drop(&mut self) {
113        self.zeroize();
114    }
115}
116
117impl ZeroizeOnDrop for Keccak256 {}
118
119impl Keccak256 {
120    #[inline(always)]
121    fn init() -> Self {
122        Self {
123            state: [0u64; KECCAK_STATE_SIZE],
124            pt: 0,
125        }
126    }
127    #[inline(always)]
128    fn rate() -> usize {
129        KECCAK256_RATE
130    }
131
132    fn update_internal(&mut self, data: &[u8]) -> Result<()> {
133        validate::parameter(
134            self.pt.checked_add(data.len()).is_some(),
135            "data_length",
136            "Integer overflow",
137        )?;
138        let r = Self::rate();
139        for &b in data {
140            xor_byte_in_state(&mut self.state, self.pt, b);
141            self.pt += 1;
142            if self.pt == r {
143                keccak_f1600(&mut self.state);
144                self.pt = 0;
145            }
146        }
147        Ok(())
148    }
149
150    fn finalize_internal(&mut self) -> Result<Zeroizing<[u8; KECCAK256_OUTPUT_SIZE]>> {
151        let r = Self::rate();
152        // Keccak padding: domain separator is 0x01
153        xor_byte_in_state(&mut self.state, self.pt, 0x01);
154        xor_byte_in_state(&mut self.state, r - 1, 0x80);
155        keccak_f1600(&mut self.state);
156
157        let mut out = Zeroizing::new([0u8; KECCAK256_OUTPUT_SIZE]);
158        for i in 0..KECCAK256_OUTPUT_SIZE {
159            out[i] = get_byte_from_state(&self.state, i);
160        }
161
162        self.zeroize();
163        Ok(out)
164    }
165}
166
167impl HashFunction for Keccak256 {
168    type Algorithm = Keccak256Algorithm;
169    type Output = Digest<KECCAK256_OUTPUT_SIZE>;
170
171    fn new() -> Self {
172        Self::init()
173    }
174
175    fn update(&mut self, data: &[u8]) -> Result<&mut Self> {
176        self.update_internal(data)?;
177        Ok(self)
178    }
179
180    fn finalize(&mut self) -> Result<Self::Output> {
181        let h = self.finalize_internal()?;
182        let mut digest = Digest::<KECCAK256_OUTPUT_SIZE>::zeroed();
183        digest.as_mut().copy_from_slice(&h[..]);
184        Ok(digest)
185    }
186
187    #[inline(always)]
188    fn output_size() -> usize {
189        <Keccak256Algorithm as HashAlgorithm>::OUTPUT_SIZE
190    }
191    #[inline(always)]
192    fn block_size() -> usize {
193        <Keccak256Algorithm as HashAlgorithm>::BLOCK_SIZE
194    }
195    #[inline(always)]
196    fn name() -> String {
197        <Keccak256Algorithm as HashAlgorithm>::ALGORITHM_ID.to_string()
198    }
199}
200
201// ───────────────────────────── permutation ────────────────────────────────
202
203fn keccak_f1600(state: &mut [u64; KECCAK_STATE_SIZE]) {
204    for &rc in RC.iter().take(KECCAK_ROUNDS) {
205        // θ
206        let mut c = Zeroizing::new([0u64; 5]);
207        for x in 0..5 {
208            c[x] = state[x] ^ state[x + 5] ^ state[x + 10] ^ state[x + 15] ^ state[x + 20];
209        }
210        for x in 0..5 {
211            let mut d = c[(x + 4) % 5] ^ c[(x + 1) % 5].rotate_left(1);
212            for y in 0..5 {
213                state[x + 5 * y] ^= d;
214            }
215            d.zeroize();
216        }
217        // ρ + π
218        let mut t = state[1];
219        for i in 0..24 {
220            let j = PI[i];
221            let tmp = state[j];
222            state[j] = t.rotate_left(RHO[i]);
223            t = tmp;
224        }
225        t.zeroize();
226        // χ
227        for y in 0..5 {
228            let mut row = Zeroizing::new([0u64; 5]);
229            for x in 0..5 {
230                row[x] = state[x + 5 * y];
231            }
232            for x in 0..5 {
233                state[x + 5 * y] ^= (!row[(x + 1) % 5]) & row[(x + 2) % 5];
234            }
235        }
236        // ι
237        state[0] ^= rc;
238    }
239}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244
245    #[test]
246    fn test_keccak256_empty() {
247        // Empty string hash
248        let digest = Keccak256::digest(b"").unwrap();
249        // Known vector for Keccak-256("")
250        let expected = "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470";
251        assert_eq!(digest.to_hex(), expected);
252    }
253
254    #[test]
255    fn test_keccak256_string() {
256        // "Hello, world!"
257        let digest = Keccak256::digest(b"Hello, world!").unwrap();
258        // Note: Different from SHA3-256("Hello, world!")
259        let expected = "b6e16d27ac5ab427a7f68900ac5559ce272dc6c37c82b3e052246c82244c50e4";
260        assert_eq!(digest.to_hex(), expected);
261    }
262}