Skip to main content

fluentbase_runtime/syscall_handler/hashing/
keccak256_permute.rs

1use crate::RuntimeContext;
2use rwasm::{StoreTr, TrapCode, Value};
3
4pub(crate) const STATE_SIZE: u32 = 25;
5
6// The permutation state is 25 u64's. Our word size is 32 bits, so it is 50 words.
7pub const STATE_NUM_WORDS: u32 = STATE_SIZE * 2;
8
9#[repr(C, align(8))]
10struct Aligned200([u8; 200]);
11
12impl Aligned200 {
13    /// Consume self and reinterpret bytes as 25 little-endian u64 lanes.
14    #[inline]
15    pub fn into_lanes_le(self) -> [u64; 25] {
16        // Compile-time sanity checks
17        const _: () = assert!(size_of::<[u8; 200]>() == size_of::<[u64; 25]>());
18        #[cfg(not(target_endian = "little"))]
19        const _: () = panic!("into_lanes_le requires little-endian platform");
20        // All bit patterns are valid u64, and sizes match.
21        // Endianness: this does NOT swap bytes; your bytes must already be LE per lane.
22        unsafe { core::mem::transmute::<[u8; 200], [u64; 25]>(self.0) }
23    }
24
25    /// Produce a wrapper from lanes (inverse of above).
26    #[inline]
27    pub fn from_lanes_le(lanes: [u64; 25]) -> Self {
28        #[cfg(not(target_endian = "little"))]
29        const _: () = panic!("from_lanes_le requires little-endian platform");
30
31        let bytes: [u8; 200] = unsafe { core::mem::transmute(lanes) };
32        Self(bytes)
33    }
34}
35
36pub fn syscall_hashing_keccak256_permute_handler(
37    ctx: &mut impl StoreTr<RuntimeContext>,
38    params: &[Value],
39    _result: &mut [Value],
40) -> Result<(), TrapCode> {
41    let state_ptr = params[0].i32().unwrap() as u32;
42    let mut state = [0u8; 200];
43    ctx.memory_read(state_ptr as usize, &mut state)?;
44    let mut state = Aligned200(state).into_lanes_le();
45    syscall_hashing_keccak256_permute_impl(&mut state);
46    let state = Aligned200::from_lanes_le(state);
47    ctx.memory_write(state_ptr as usize, &state.0)?;
48    Ok(())
49}
50
51pub fn syscall_hashing_keccak256_permute_impl(state: &mut [u64; 25]) {
52    use tiny_keccak::keccakf;
53    keccakf(state);
54}
55
56#[cfg(test)]
57mod tests {
58    use crate::syscall_handler::syscall_hashing_keccak256_permute_impl;
59    use fluentbase_types::hex;
60
61    const RATE: usize = 136; // 1088 bits
62    const LANES: usize = 25; // 25 * 8 = 200 bytes
63
64    pub fn tiny_keccak256(inp: &[u8]) -> [u8; 32] {
65        let mut s = [0u64; 25];
66        let mut i = 0;
67        // absorb full blocks
68        while i + RATE <= inp.len() {
69            for (j, &b) in inp[i..i + RATE].iter().enumerate() {
70                s[j / 8] ^= (b as u64) << ((j % 8) * 8);
71            }
72            syscall_hashing_keccak256_permute_impl(&mut s);
73            i += RATE;
74        }
75        // last block + padding (Keccak: 0x01 ... 0x80)
76        let r = inp.len() - i;
77        for (j, &b) in inp[i..].iter().enumerate() {
78            s[j / 8] ^= (b as u64) << ((j % 8) * 8);
79        }
80        s[r / 8] ^= 1u64 << ((r % 8) * 8);
81        s[(RATE - 1) / 8] ^= 0x80u64 << (((RATE - 1) % 8) * 8);
82        // permute
83        syscall_hashing_keccak256_permute_impl(&mut s);
84        // squeeze 32 bytes
85        let mut out = [0u8; 32];
86        for k in 0..32 {
87            out[k] = ((s[k / 8] >> ((k % 8) * 8)) & 0xFF) as u8;
88        }
89        out
90    }
91
92    #[test]
93    fn test_keccak256_permute() {
94        let hash = tiny_keccak256("Hello, World".as_bytes());
95        assert_eq!(
96            hash,
97            hex!("a04a451028d0f9284ce82243755e245238ab1e4ecf7b9dd8bf4734d9ecfd0529")
98        );
99        let hash = tiny_keccak256(&[]);
100        assert_eq!(
101            hash,
102            hex!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")
103        );
104        let hash = tiny_keccak256("abc".as_bytes());
105        assert_eq!(
106            hash,
107            hex!("4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45")
108        );
109    }
110
111    fn ref_keccak256(data: &[u8]) -> [u8; 32] {
112        use tiny_keccak::{Hasher, Keccak};
113        let mut hasher = Keccak::v256(); // Ethereum Keccak-256 (pad10*1, domain 0x01)
114        hasher.update(data);
115        let mut out = [0u8; 32];
116        hasher.finalize(&mut out);
117        out
118    }
119
120    #[test]
121    fn corner_lengths_near_rate() {
122        // RATE = 136. Hit the boundaries around padding.
123        for &len in &[
124            0usize, 1, 2, 3, 7, 8, 15, 16, 31, 32, 63, 64, 127, 135, 136, 137,
125        ] {
126            let msg: Vec<u8> = (0..len as u64)
127                .map(|x| (x as u8).wrapping_mul(0x9d))
128                .collect();
129            assert_eq!(tiny_keccak256(&msg), ref_keccak256(&msg), "len={}", len);
130        }
131    }
132
133    #[test]
134    fn unicode_bytes() {
135        // Non-ASCII payloads to catch any stray UTF handling assumptions.
136        let s1 = "Привет, мир! 👋🌍".as_bytes();
137        let s2 = "ちりも積もれば山となる".as_bytes();
138        let s3 = "مرحبا بالعالم".as_bytes();
139        for (i, m) in [s1, s2, s3].into_iter().enumerate() {
140            assert_eq!(tiny_keccak256(m), ref_keccak256(m), "unicode idx={}", i);
141        }
142    }
143
144    #[test]
145    fn long_messages() {
146        // Large messages: near-rate tails and multi-MiB to shake the loop
147        let mut m1 = vec![0u8; 10_000]; // 10 KB zeros
148        for (i, b) in m1.iter_mut().enumerate() {
149            *b = (i as u8).wrapping_mul(17);
150        }
151        assert_eq!(tiny_keccak256(&m1), ref_keccak256(&m1));
152
153        // 1,000,000 'a' (classic torture test but not too slow)
154        let m2 = vec![b'a'; 1_000_000];
155        assert_eq!(tiny_keccak256(&m2), ref_keccak256(&m2));
156    }
157
158    #[test]
159    fn every_tail_len_0_to_200() {
160        // Brutal: fixed 512 bytes + all possible tail lengths up to 200.
161        let head: Vec<u8> = (0..512u16).map(|x| (x as u8).wrapping_mul(73)).collect();
162        for tail_len in 0..=200 {
163            let mut m = head.clone();
164            m.extend((0..tail_len as u16).map(|x| (x as u8).wrapping_add(5)));
165            assert_eq!(
166                tiny_keccak256(&m),
167                ref_keccak256(&m),
168                "tail_len={}",
169                tail_len
170            );
171        }
172    }
173}