Skip to main content

keccak_rhdl/
round.rs

1//! One `Keccak-f[1600]` round in RHDL and in an independent software model.
2
3use rhdl::prelude::*;
4
5use crate::KeccakState;
6
7/// The 24 `Keccak-f[1600]` Iota constants, in round order.
8pub const ROUND_CONSTANTS: [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/// `Keccak-f[1600]` Rho offsets indexed as `[x][y]`.
36pub const ROTATION_OFFSETS: [[u32; 5]; 5] = [
37    [0, 36, 3, 41, 18],
38    [1, 44, 10, 45, 2],
39    [62, 6, 43, 15, 61],
40    [28, 55, 25, 21, 56],
41    [27, 20, 39, 8, 14],
42];
43
44// Each Rho rotation has its own kernel so the emitted RTL contains only fixed
45// wiring shifts.  In particular, the datapath has no barrel shifter.
46macro_rules! fixed_rotate {
47    ($name:ident, $left:literal, $right:literal) => {
48        #[kernel]
49        fn $name(value: b64) -> b64 {
50            (value << $left) | (value >> $right)
51        }
52    };
53}
54
55fixed_rotate!(rol01, 1, 63);
56fixed_rotate!(rol02, 2, 62);
57fixed_rotate!(rol03, 3, 61);
58fixed_rotate!(rol06, 6, 58);
59fixed_rotate!(rol08, 8, 56);
60fixed_rotate!(rol10, 10, 54);
61fixed_rotate!(rol14, 14, 50);
62fixed_rotate!(rol15, 15, 49);
63fixed_rotate!(rol18, 18, 46);
64fixed_rotate!(rol20, 20, 44);
65fixed_rotate!(rol21, 21, 43);
66fixed_rotate!(rol25, 25, 39);
67fixed_rotate!(rol27, 27, 37);
68fixed_rotate!(rol28, 28, 36);
69fixed_rotate!(rol36, 36, 28);
70fixed_rotate!(rol39, 39, 25);
71fixed_rotate!(rol41, 41, 23);
72fixed_rotate!(rol43, 43, 21);
73fixed_rotate!(rol44, 44, 20);
74fixed_rotate!(rol45, 45, 19);
75fixed_rotate!(rol55, 55, 9);
76fixed_rotate!(rol56, 56, 8);
77fixed_rotate!(rol61, 61, 3);
78fixed_rotate!(rol62, 62, 2);
79
80#[kernel]
81#[allow(clippy::match_same_arms)] // Constants 5/22 and 6/20 repeat in the standard table.
82fn iota_constant(round: b5) -> b64 {
83    match round {
84        Bits::<5>(0) => b64(0x0000_0000_0000_0001),
85        Bits::<5>(1) => b64(0x0000_0000_0000_8082),
86        Bits::<5>(2) => b64(0x8000_0000_0000_808a),
87        Bits::<5>(3) => b64(0x8000_0000_8000_8000),
88        Bits::<5>(4) => b64(0x0000_0000_0000_808b),
89        Bits::<5>(5) => b64(0x0000_0000_8000_0001),
90        Bits::<5>(6) => b64(0x8000_0000_8000_8081),
91        Bits::<5>(7) => b64(0x8000_0000_0000_8009),
92        Bits::<5>(8) => b64(0x0000_0000_0000_008a),
93        Bits::<5>(9) => b64(0x0000_0000_0000_0088),
94        Bits::<5>(10) => b64(0x0000_0000_8000_8009),
95        Bits::<5>(11) => b64(0x0000_0000_8000_000a),
96        Bits::<5>(12) => b64(0x0000_0000_8000_808b),
97        Bits::<5>(13) => b64(0x8000_0000_0000_008b),
98        Bits::<5>(14) => b64(0x8000_0000_0000_8089),
99        Bits::<5>(15) => b64(0x8000_0000_0000_8003),
100        Bits::<5>(16) => b64(0x8000_0000_0000_8002),
101        Bits::<5>(17) => b64(0x8000_0000_0000_0080),
102        Bits::<5>(18) => b64(0x0000_0000_0000_800a),
103        Bits::<5>(19) => b64(0x8000_0000_8000_000a),
104        Bits::<5>(20) => b64(0x8000_0000_8000_8081),
105        Bits::<5>(21) => b64(0x8000_0000_0000_8080),
106        Bits::<5>(22) => b64(0x0000_0000_8000_0001),
107        _ => b64(0x8000_0000_8000_8008),
108    }
109}
110
111#[kernel]
112fn keccak_round_with_constant(mut a: KeccakState, round_constant: b64) -> KeccakState {
113    // Theta.
114    let c0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20];
115    let c1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21];
116    let c2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22];
117    let c3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23];
118    let c4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24];
119    let d0 = c4 ^ rol01(c1);
120    let d1 = c0 ^ rol01(c2);
121    let d2 = c1 ^ rol01(c3);
122    let d3 = c2 ^ rol01(c4);
123    let d4 = c3 ^ rol01(c0);
124    a[0] ^= d0;
125    a[5] ^= d0;
126    a[10] ^= d0;
127    a[15] ^= d0;
128    a[20] ^= d0;
129    a[1] ^= d1;
130    a[6] ^= d1;
131    a[11] ^= d1;
132    a[16] ^= d1;
133    a[21] ^= d1;
134    a[2] ^= d2;
135    a[7] ^= d2;
136    a[12] ^= d2;
137    a[17] ^= d2;
138    a[22] ^= d2;
139    a[3] ^= d3;
140    a[8] ^= d3;
141    a[13] ^= d3;
142    a[18] ^= d3;
143    a[23] ^= d3;
144    a[4] ^= d4;
145    a[9] ^= d4;
146    a[14] ^= d4;
147    a[19] ^= d4;
148    a[24] ^= d4;
149
150    // Rho and Pi.  Destination is B[y][(2*x + 3*y) mod 5].
151    let mut b = [b64(0); 25];
152    b[0] = a[0];
153    b[10] = rol01(a[1]);
154    b[20] = rol62(a[2]);
155    b[5] = rol28(a[3]);
156    b[15] = rol27(a[4]);
157    b[16] = rol36(a[5]);
158    b[1] = rol44(a[6]);
159    b[11] = rol06(a[7]);
160    b[21] = rol55(a[8]);
161    b[6] = rol20(a[9]);
162    b[7] = rol03(a[10]);
163    b[17] = rol10(a[11]);
164    b[2] = rol43(a[12]);
165    b[12] = rol25(a[13]);
166    b[22] = rol39(a[14]);
167    b[23] = rol41(a[15]);
168    b[8] = rol45(a[16]);
169    b[18] = rol15(a[17]);
170    b[3] = rol21(a[18]);
171    b[13] = rol08(a[19]);
172    b[14] = rol18(a[20]);
173    b[24] = rol02(a[21]);
174    b[9] = rol61(a[22]);
175    b[19] = rol56(a[23]);
176    b[4] = rol14(a[24]);
177
178    // Chi, written out so every neighbor selection is static.
179    let mut out = [b64(0); 25];
180    out[0] = b[0] ^ ((!b[1]) & b[2]);
181    out[1] = b[1] ^ ((!b[2]) & b[3]);
182    out[2] = b[2] ^ ((!b[3]) & b[4]);
183    out[3] = b[3] ^ ((!b[4]) & b[0]);
184    out[4] = b[4] ^ ((!b[0]) & b[1]);
185    out[5] = b[5] ^ ((!b[6]) & b[7]);
186    out[6] = b[6] ^ ((!b[7]) & b[8]);
187    out[7] = b[7] ^ ((!b[8]) & b[9]);
188    out[8] = b[8] ^ ((!b[9]) & b[5]);
189    out[9] = b[9] ^ ((!b[5]) & b[6]);
190    out[10] = b[10] ^ ((!b[11]) & b[12]);
191    out[11] = b[11] ^ ((!b[12]) & b[13]);
192    out[12] = b[12] ^ ((!b[13]) & b[14]);
193    out[13] = b[13] ^ ((!b[14]) & b[10]);
194    out[14] = b[14] ^ ((!b[10]) & b[11]);
195    out[15] = b[15] ^ ((!b[16]) & b[17]);
196    out[16] = b[16] ^ ((!b[17]) & b[18]);
197    out[17] = b[17] ^ ((!b[18]) & b[19]);
198    out[18] = b[18] ^ ((!b[19]) & b[15]);
199    out[19] = b[19] ^ ((!b[15]) & b[16]);
200    out[20] = b[20] ^ ((!b[21]) & b[22]);
201    out[21] = b[21] ^ ((!b[22]) & b[23]);
202    out[22] = b[22] ^ ((!b[23]) & b[24]);
203    out[23] = b[23] ^ ((!b[24]) & b[20]);
204    out[24] = b[24] ^ ((!b[20]) & b[21]);
205
206    // Iota.
207    out[0] ^= round_constant;
208    out
209}
210
211/// Apply one `Keccak-f[1600]` round selected at runtime.
212///
213/// `round` must be in `0..24`.  Values 24 through 31 intentionally select the
214/// last constant; the iterative engine never generates those values.  The Rho
215/// shifts and Pi destinations are all literal/static; only the 64-bit Iota
216/// constant is selected by `round`.
217#[kernel]
218pub fn keccak_round(state: KeccakState, round: b5) -> KeccakState {
219    keccak_round_with_constant(state, iota_constant(round))
220}
221
222// These entry points bind Iota at compile time.  They let the complete
223// permutation avoid a round-constant mux and give synthesis a clean isolated
224// round top.  Item-level macro expansion occurs before RHDL processes the
225// generated kernel attribute.
226macro_rules! fixed_round {
227    ($name:ident, $number:literal, $constant:literal) => {
228        #[doc = concat!(
229                                                    "Apply compile-time `Keccak-f[1600]` round ",
230                                                    stringify!($number),
231                                                    "."
232                                                )]
233        #[kernel]
234        pub fn $name(state: KeccakState) -> KeccakState {
235            keccak_round_with_constant(state, b64($constant))
236        }
237    };
238}
239
240fixed_round!(keccak_round_00, 0, 0x0000_0000_0000_0001);
241fixed_round!(keccak_round_01, 1, 0x0000_0000_0000_8082);
242fixed_round!(keccak_round_02, 2, 0x8000_0000_0000_808a);
243fixed_round!(keccak_round_03, 3, 0x8000_0000_8000_8000);
244fixed_round!(keccak_round_04, 4, 0x0000_0000_0000_808b);
245fixed_round!(keccak_round_05, 5, 0x0000_0000_8000_0001);
246fixed_round!(keccak_round_06, 6, 0x8000_0000_8000_8081);
247fixed_round!(keccak_round_07, 7, 0x8000_0000_0000_8009);
248fixed_round!(keccak_round_08, 8, 0x0000_0000_0000_008a);
249fixed_round!(keccak_round_09, 9, 0x0000_0000_0000_0088);
250fixed_round!(keccak_round_10, 10, 0x0000_0000_8000_8009);
251fixed_round!(keccak_round_11, 11, 0x0000_0000_8000_000a);
252fixed_round!(keccak_round_12, 12, 0x0000_0000_8000_808b);
253fixed_round!(keccak_round_13, 13, 0x8000_0000_0000_008b);
254fixed_round!(keccak_round_14, 14, 0x8000_0000_0000_8089);
255fixed_round!(keccak_round_15, 15, 0x8000_0000_0000_8003);
256fixed_round!(keccak_round_16, 16, 0x8000_0000_0000_8002);
257fixed_round!(keccak_round_17, 17, 0x8000_0000_0000_0080);
258fixed_round!(keccak_round_18, 18, 0x0000_0000_0000_800a);
259fixed_round!(keccak_round_19, 19, 0x8000_0000_8000_000a);
260fixed_round!(keccak_round_20, 20, 0x8000_0000_8000_8081);
261fixed_round!(keccak_round_21, 21, 0x8000_0000_0000_8080);
262fixed_round!(keccak_round_22, 22, 0x0000_0000_8000_0001);
263fixed_round!(keccak_round_23, 23, 0x8000_0000_8000_8008);
264
265/// Independent native-Rust implementation of one `Keccak-f[1600]` round.
266///
267/// This implementation deliberately uses loops and `u64::rotate_left`; it is
268/// the oracle for the explicitly wired RHDL implementation above.
269///
270/// # Panics
271///
272/// Panics when `round` is not in `0..24`.
273#[must_use]
274pub fn software_keccak_round(mut a: [u64; 25], round: usize) -> [u64; 25] {
275    assert!(
276        round < ROUND_CONSTANTS.len(),
277        "Keccak round must be below 24"
278    );
279    let mut c = [0_u64; 5];
280    for x in 0..5 {
281        c[x] = a[x] ^ a[x + 5] ^ a[x + 10] ^ a[x + 15] ^ a[x + 20];
282    }
283    for x in 0..5 {
284        let d = c[(x + 4) % 5] ^ c[(x + 1) % 5].rotate_left(1);
285        for y in 0..5 {
286            a[x + 5 * y] ^= d;
287        }
288    }
289
290    let mut b = [0_u64; 25];
291    for x in 0..5 {
292        for y in 0..5 {
293            b[y + 5 * ((2 * x + 3 * y) % 5)] = a[x + 5 * y].rotate_left(ROTATION_OFFSETS[x][y]);
294        }
295    }
296
297    for x in 0..5 {
298        for y in 0..5 {
299            a[x + 5 * y] = b[x + 5 * y] ^ ((!b[(x + 1) % 5 + 5 * y]) & b[(x + 2) % 5 + 5 * y]);
300        }
301    }
302    a[0] ^= ROUND_CONSTANTS[round];
303    a
304}