1use std::{array, iter};
3
4use binius_core::word::Word;
5use binius_frontend::{ChipGadget, CircuitBuilder, Hint, Wire};
6
7pub const RC: [u64; 24] = [
9 0x0000_0000_0000_0001,
10 0x0000_0000_0000_8082,
11 0x8000_0000_0000_808A,
12 0x8000_0000_8000_8000,
13 0x0000_0000_0000_808B,
14 0x0000_0000_8000_0001,
15 0x8000_0000_8000_8081,
16 0x8000_0000_0000_8009,
17 0x0000_0000_0000_008A,
18 0x0000_0000_0000_0088,
19 0x0000_0000_8000_8009,
20 0x0000_0000_8000_000A,
21 0x0000_0000_8000_808B,
22 0x8000_0000_0000_008B,
23 0x8000_0000_0000_8089,
24 0x8000_0000_0000_8003,
25 0x8000_0000_0000_8002,
26 0x8000_0000_0000_0080,
27 0x0000_0000_0000_800A,
28 0x8000_0000_8000_000A,
29 0x8000_0000_8000_8081,
30 0x8000_0000_0000_8080,
31 0x0000_0000_8000_0001,
32 0x8000_0000_8000_8008,
33];
34
35#[rustfmt::skip]
37pub const R: [u32; 25] = [
38 0, 1, 62, 28, 27,
39 36, 44, 6, 55, 20,
40 3, 10, 43, 25, 39,
41 41, 45, 15, 21, 8,
42 18, 2, 61, 56, 14,
43];
44
45#[inline(always)]
46pub const fn idx(x: usize, y: usize) -> usize {
47 x + 5 * y
48}
49
50pub fn keccak_f1600(b: &CircuitBuilder, state: &mut [Wire; 25]) {
65 let outputs = b.build_gadget(KeccakF1600, &[], state);
66 state.copy_from_slice(&outputs);
67}
68
69pub struct KeccakF1600;
74
75impl Hint for KeccakF1600 {
76 const NAME: &'static str = "binius.keccak_f1600";
77
78 fn shape(&self, _dimensions: &[usize]) -> (usize, usize) {
79 (25, 25)
80 }
81
82 fn execute(&self, _dimensions: &[usize], inputs: &[Word], outputs: &mut [Word]) {
83 let mut state: [u64; 25] = array::from_fn(|i| inputs[i].as_u64());
84 ref_keccak_f1600(&mut state);
85
86 for (slot, lane) in iter::zip(outputs, state) {
87 *slot = Word(lane);
88 }
89 }
90}
91
92impl ChipGadget for KeccakF1600 {
93 fn build(&self, builder: &CircuitBuilder, _dimensions: &[usize], inputs: &[Wire]) -> Vec<Wire> {
94 let mut state: [Wire; 25] = array::from_fn(|i| inputs[i]);
95 f1600_gates(builder, &mut state);
96 state.to_vec()
97 }
98}
99
100fn f1600_gates(b: &CircuitBuilder, state: &mut [Wire; 25]) {
102 for round in 0..24 {
103 keccak_permutation_round(b, state, round);
104 }
105}
106
107pub fn keccak_permutation_round(b: &CircuitBuilder, state: &mut [Wire; 25], round: usize) {
108 theta(b, state);
109 rho_pi(b, state);
110 chi_iota(b, state, round);
111}
112
113fn theta(b: &CircuitBuilder, state: &mut [Wire; 25]) {
114 let c0 = b.bxor_multi(&array::from_fn::<_, 5, _>(|y| state[idx(0, y)]));
115 let c1 = b.bxor_multi(&array::from_fn::<_, 5, _>(|y| state[idx(1, y)]));
116 let c2 = b.bxor_multi(&array::from_fn::<_, 5, _>(|y| state[idx(2, y)]));
117 let c3 = b.bxor_multi(&array::from_fn::<_, 5, _>(|y| state[idx(3, y)]));
118 let c4 = b.bxor_multi(&array::from_fn::<_, 5, _>(|y| state[idx(4, y)]));
119
120 let d0 = b.bxor(c4, b.rotl(c1, 1));
122 let d1 = b.bxor(c0, b.rotl(c2, 1));
123 let d2 = b.bxor(c1, b.rotl(c3, 1));
124 let d3 = b.bxor(c2, b.rotl(c4, 1));
125 let d4 = b.bxor(c3, b.rotl(c0, 1));
126
127 for y in 0..5 {
129 state[idx(0, y)] = b.bxor(state[idx(0, y)], d0);
130 state[idx(1, y)] = b.bxor(state[idx(1, y)], d1);
131 state[idx(2, y)] = b.bxor(state[idx(2, y)], d2);
132 state[idx(3, y)] = b.bxor(state[idx(3, y)], d3);
133 state[idx(4, y)] = b.bxor(state[idx(4, y)], d4);
134 }
135}
136
137fn chi_iota(b: &CircuitBuilder, state: &mut [Wire; 25], round: usize) {
138 let rc = b.add_constant(Word(RC[round]));
139
140 for y in 0..5 {
141 let a0 = state[idx(0, y)];
142 let a1 = state[idx(1, y)];
143 let a2 = state[idx(2, y)];
144 let a3 = state[idx(3, y)];
145 let a4 = state[idx(4, y)];
146
147 let a0_iota = if y == 0 { b.bxor(a0, rc) } else { a0 };
150
151 state[idx(0, y)] = b.fax(b.bnot(a1), a2, a0_iota);
152 state[idx(1, y)] = b.fax(b.bnot(a2), a3, a1);
153 state[idx(2, y)] = b.fax(b.bnot(a3), a4, a2);
154 state[idx(3, y)] = b.fax(b.bnot(a4), a0, a3);
155 state[idx(4, y)] = b.fax(b.bnot(a0), a1, a4);
156 }
157}
158
159fn rho_pi(b: &CircuitBuilder, state: &mut [Wire; 25]) {
160 let mut temp = [state[0]; 25];
161 for y in 0..5 {
162 for x in 0..5 {
163 if R[idx(x, y)] == 0 {
165 continue;
166 }
167 temp[idx(y, (2 * x + 3 * y) % 5)] = b.rotl(state[idx(x, y)], R[idx(x, y)]);
168 }
169 }
170 *state = temp;
171}
172
173fn ref_theta(state: &mut [u64; 25]) {
179 let mut c = [0u64; 5];
180 for x in 0..5 {
181 c[x] = state[idx(x, 0)]
182 ^ state[idx(x, 1)]
183 ^ state[idx(x, 2)]
184 ^ state[idx(x, 3)]
185 ^ state[idx(x, 4)];
186 }
187 let d = [
188 c[4] ^ c[1].rotate_left(1),
189 c[0] ^ c[2].rotate_left(1),
190 c[1] ^ c[3].rotate_left(1),
191 c[2] ^ c[4].rotate_left(1),
192 c[3] ^ c[0].rotate_left(1),
193 ];
194
195 for y in 0..5 {
196 for x in 0..5 {
197 state[idx(x, y)] ^= d[x];
198 }
199 }
200}
201
202fn ref_rho_pi(state: &mut [u64; 25]) {
203 let mut temp = [state[0]; 25];
204 for y in 0..5 {
205 for x in 0..5 {
206 temp[idx(y, (2 * x + 3 * y) % 5)] = state[idx(x, y)].rotate_left(R[idx(x, y)]);
207 }
208 }
209 *state = temp;
210}
211
212fn ref_chi(state: &mut [u64; 25]) {
213 for y in 0..5 {
214 let a0 = state[idx(0, y)];
215 let a1 = state[idx(1, y)];
216 let a2 = state[idx(2, y)];
217 let a3 = state[idx(3, y)];
218 let a4 = state[idx(4, y)];
219 state[idx(0, y)] = a0 ^ ((!a1) & a2);
220 state[idx(1, y)] = a1 ^ ((!a2) & a3);
221 state[idx(2, y)] = a2 ^ ((!a3) & a4);
222 state[idx(3, y)] = a3 ^ ((!a4) & a0);
223 state[idx(4, y)] = a4 ^ ((!a0) & a1);
224 }
225}
226
227const fn ref_iota(state: &mut [u64; 25], round: usize) {
228 state[0] ^= RC[round];
229}
230
231fn ref_keccak_permutation_round(state: &mut [u64; 25], round: usize) {
232 ref_theta(state);
233 ref_rho_pi(state);
234 ref_chi(state);
235 ref_iota(state, round);
236}
237
238pub fn ref_keccak_f1600(state: &mut [u64; 25]) {
243 for round in 0..24 {
244 ref_keccak_permutation_round(state, round);
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use binius_core::word::Word;
251 use binius_frontend::CircuitBuilder;
252 use proptest::prelude::*;
253 use rand::prelude::*;
254
255 use super::*;
256
257 fn validate_circuit_component(
258 circuit_fn: impl FnOnce(&CircuitBuilder, &mut [Wire; 25]),
259 reference_fn: impl FnOnce(&mut [u64; 25]),
260 input_state: [u64; 25],
261 ) {
262 let builder = CircuitBuilder::new();
263
264 let input_wires: [Wire; 25] = std::array::from_fn(|_| builder.add_inout());
265
266 let mut state_wires = input_wires;
267 circuit_fn(&builder, &mut state_wires);
268 for &wire in &state_wires {
270 builder.force_commit(wire);
271 }
272 let circuit = builder.build();
273
274 let mut expected_output = input_state;
275 reference_fn(&mut expected_output);
276
277 let mut w = circuit.new_witness_filler();
278 for i in 0..25 {
279 w[input_wires[i]] = Word(input_state[i]);
280 }
281 circuit.populate_wire_witness(&mut w).unwrap();
282
283 for i in 0..25 {
284 assert_eq!(
285 w[state_wires[i]],
286 Word(expected_output[i]),
287 "Output mismatch at index {}: circuit={:?}, expected={:?}",
288 i,
289 w[state_wires[i]],
290 Word(expected_output[i])
291 );
292 }
293
294 let cs = circuit.constraint_system();
295 cs.verify(&w.into_value_vec()).unwrap();
296 }
297
298 #[test]
299 fn test_keccak_f1600() {
300 let mut rng = StdRng::seed_from_u64(0);
301 let input_state = rng.random::<[u64; 25]>();
302
303 validate_circuit_component(keccak_f1600, ref_keccak_f1600, input_state);
304 }
305
306 #[test]
307 fn test_keccak_permutation_round() {
308 let mut rng = StdRng::seed_from_u64(0);
309 let input_state = rng.random::<[u64; 25]>();
310
311 validate_circuit_component(
312 |b, state| keccak_permutation_round(b, state, 0),
313 |state| ref_keccak_permutation_round(state, 0),
314 input_state,
315 );
316 }
317
318 #[test]
319 fn test_theta() {
320 let mut rng = StdRng::seed_from_u64(0);
321 let input_state = rng.random::<[u64; 25]>();
322
323 validate_circuit_component(theta, ref_theta, input_state);
324 }
325
326 #[test]
327 fn test_rho_pi() {
328 let mut rng = StdRng::seed_from_u64(0);
329 let input_state = rng.random::<[u64; 25]>();
330
331 validate_circuit_component(rho_pi, ref_rho_pi, input_state);
332 }
333
334 #[test]
335 fn test_chi_iota() {
336 let mut rng = StdRng::seed_from_u64(0);
337 let input_state = rng.random::<[u64; 25]>();
338
339 const ROUND: usize = 6;
343
344 validate_circuit_component(
345 |b, state| chi_iota(b, state, ROUND),
346 |state| {
347 ref_chi(state);
348 ref_iota(state, ROUND);
349 },
350 input_state,
351 );
352 }
353
354 fn run_f1600_words(inputs: [u64; 25]) -> [u64; 25] {
356 let builder = CircuitBuilder::new();
357 let wires: [Wire; 25] = std::array::from_fn(|_| builder.add_witness());
358 let mut out = wires;
359 f1600_gates(&builder, &mut out);
360 for wire in out {
361 builder.mark_inout(wire);
362 }
363
364 let circuit = builder.build();
365 let mut w = circuit.new_witness_filler();
366 for (wire, word) in std::iter::zip(wires, inputs) {
367 w[wire] = Word(word);
368 }
369 circuit.populate_wire_witness(&mut w).unwrap();
370
371 std::array::from_fn(|i| w[out[i]].as_u64())
372 }
373
374 proptest! {
375 #[test]
379 fn f1600_hint_matches_its_gates(words in prop::collection::vec(any::<u64>(), 25)) {
380 let inputs: [u64; 25] = std::array::from_fn(|i| words[i]);
381
382 let mut hinted = [Word::ZERO; 25];
383 KeccakF1600.execute(&[], &inputs.map(Word), &mut hinted);
384
385 prop_assert_eq!(hinted.map(|word| word.as_u64()), run_f1600_words(inputs));
386 }
387 }
388}