Skip to main content

cryptography/ciphers/
aes.rs

1//! AES (Rijndael, 128-bit block) — AES-128, AES-192, AES-256.
2//!
3//! Implemented from FIPS PUB 197 (2001), the complete Rijndael specification
4//! for a 128-bit block width with 10, 12, or 14 rounds depending on key length.
5//!
6//! # Default path — fast software T-tables
7//!
8//! The active encrypt/decrypt path uses the classic T-table software design:
9//! each middle round folds `SubBytes`, `ShiftRows`, `MixColumns`, and `AddRoundKey`
10//! into four 256-entry `u32` lookup tables computed at compile time from the
11//! FIPS 197 S-boxes.
12//!
13//! This software path is intentionally optimized for throughput, not
14//! constant-time behavior.  Use `Aes128Ct`, `Aes192Ct`, or `Aes256Ct` for the
15//! software-only Boyar-Peralta path when constant-time behavior matters.
16//! Hardware AES (for example AES-NI or `ARMv8` Crypto Extensions) is still the
17//! preferred option when it is available.
18//!
19//! # Tests
20//! All vectors are from NIST CAVP `KAT_AES.zip` (CAVS 11.1, 2011-04-22),
21//! downloaded directly from csrc.nist.gov.
22
23// ─────────────────────────────────────────────────────────────────────────────
24// FIPS 197 S-boxes  (§ 4.2.1)
25// ─────────────────────────────────────────────────────────────────────────────
26
27/// Forward S-box — FIPS 197, Figure 7.
28/// SBOX[x] = `affine_transform(gf_inv(x))`  in GF(2⁸) mod 0x11b.
29const SBOX: [u8; 256] = [
30    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
31    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
32    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
33    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
34    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
35    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
36    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
37    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
38    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
39    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
40    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
41    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
42    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
43    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
44    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
45    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
46];
47
48/// Inverse S-box — FIPS 197, Figure 14.
49const INV_SBOX: [u8; 256] = [
50    0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
51    0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
52    0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
53    0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
54    0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
55    0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
56    0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
57    0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
58    0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
59    0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
60    0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
61    0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
62    0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
63    0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
64    0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
65    0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d,
66];
67
68/// Key schedule round constants — FIPS 197, § 5.2.
69/// RCON[i] = [x^i in GF(2⁸), 0, 0, 0] packed big-endian into a u32.
70const RCON: [u32; 10] = [
71    0x0100_0000,
72    0x0200_0000,
73    0x0400_0000,
74    0x0800_0000,
75    0x1000_0000,
76    0x2000_0000,
77    0x4000_0000,
78    0x8000_0000,
79    0x1b00_0000,
80    0x3600_0000,
81];
82
83// ─────────────────────────────────────────────────────────────────────────────
84// GF(2⁸) arithmetic  (mod x⁸ + x⁴ + x³ + x + 1 = 0x11b)
85//
86// All `const fn`s so the compiler evaluates them at compile time when building
87// the T-tables.  None of these appear in any hot path.
88// ─────────────────────────────────────────────────────────────────────────────
89
90/// Multiply by x (the polynomial generator) — equivalent to a left shift
91/// followed by a conditional XOR to reduce modulo the irreducible polynomial.
92const fn xtime(a: u8) -> u8 {
93    (a << 1) ^ (0x1b & 0u8.wrapping_sub(a >> 7))
94}
95
96const fn mul3(a: u8) -> u8 {
97    xtime(a) ^ a
98}
99const fn mul4(a: u8) -> u8 {
100    xtime(xtime(a))
101}
102const fn mul8(a: u8) -> u8 {
103    xtime(xtime(xtime(a)))
104}
105const fn mul9(a: u8) -> u8 {
106    mul8(a) ^ a
107}
108const fn mul11(a: u8) -> u8 {
109    mul8(a) ^ xtime(a) ^ a
110}
111const fn mul13(a: u8) -> u8 {
112    mul8(a) ^ mul4(a) ^ a
113}
114const fn mul14(a: u8) -> u8 {
115    mul8(a) ^ mul4(a) ^ xtime(a)
116}
117
118// ─────────────────────────────────────────────────────────────────────────────
119// Encryption T-tables  (computed at compile time from SBOX)
120//
121// Each table folds SubBytes + one column of MixColumns into a single u32
122// lookup.  TE1–TE3 are right-rotations of TE0, so together they cover all
123// four output-byte positions.
124//
125// Derivation for TE0 (FIPS 197, § 5.1.3 MixColumns matrix row 0):
126//
127//   TE0[a] = [ 2·S[a],   S[a],   S[a], 3·S[a] ]   (big-endian bytes)
128//           = (xtime(s) << 24) | (s << 16) | (s << 8) | mul3(s)
129//   TE1[a] = TE0[a].rotate_right(8)    ← row 1 contribution
130//   TE2[a] = TE0[a].rotate_right(16)   ← row 2
131//   TE3[a] = TE0[a].rotate_right(24)   ← row 3
132//
133// One middle round (r = 1..NR-1), column 0 of output:
134//   t0 = TE0[s0>>24] ^ TE1[(s1>>16)&0xff] ^ TE2[(s2>>8)&0xff] ^ TE3[s3&0xff] ^ rk[4r]
135//   (ShiftRows is implicit: column j of output samples rows from columns
136//    j, j+1, j+2, j+3 mod 4 of the current state.)
137// ─────────────────────────────────────────────────────────────────────────────
138
139const TE0: [u32; 256] = {
140    let mut t = [0u32; 256];
141    let mut i = 0usize;
142    while i < 256 {
143        let s = SBOX[i];
144        t[i] =
145            ((xtime(s) as u32) << 24) | ((s as u32) << 16) | ((s as u32) << 8) | (mul3(s) as u32);
146        i += 1;
147    }
148    t
149};
150
151const TE1: [u32; 256] = {
152    let mut t = [0u32; 256];
153    let mut i = 0usize;
154    while i < 256 {
155        t[i] = TE0[i].rotate_right(8);
156        i += 1;
157    }
158    t
159};
160const TE2: [u32; 256] = {
161    let mut t = [0u32; 256];
162    let mut i = 0usize;
163    while i < 256 {
164        t[i] = TE0[i].rotate_right(16);
165        i += 1;
166    }
167    t
168};
169const TE3: [u32; 256] = {
170    let mut t = [0u32; 256];
171    let mut i = 0usize;
172    while i < 256 {
173        t[i] = TE0[i].rotate_right(24);
174        i += 1;
175    }
176    t
177};
178
179// ─────────────────────────────────────────────────────────────────────────────
180// Decryption T-tables  (computed at compile time from INV_SBOX)
181//
182// Derivation for TD0 (InvMixColumns matrix, FIPS 197, § 5.3.3):
183//
184//   TD0[a] = [ 14·Si[a],  9·Si[a], 13·Si[a], 11·Si[a] ]  (big-endian bytes)
185//   TD1..TD3 are right-rotations of TD0.
186//
187// One inverse middle round, column 0:
188//   t0 = TD0[s0>>24] ^ TD1[(s3>>16)&0xff] ^ TD2[(s2>>8)&0xff] ^ TD3[s1&0xff] ^ rk[4r]
189//   (InvShiftRows is implicit in the column sampling pattern; note the indices
190//    s3,s2,s1 rather than the s1,s2,s3 of the forward direction.)
191//
192// Round keys are pre-transformed by make_dec_rk() for the equivalent inverse
193// cipher, so the decryption loop can mirror the forward T-table structure.
194// ─────────────────────────────────────────────────────────────────────────────
195
196const TD0: [u32; 256] = {
197    let mut t = [0u32; 256];
198    let mut i = 0usize;
199    while i < 256 {
200        let s = INV_SBOX[i];
201        t[i] = ((mul14(s) as u32) << 24)
202            | ((mul9(s) as u32) << 16)
203            | ((mul13(s) as u32) << 8)
204            | (mul11(s) as u32);
205        i += 1;
206    }
207    t
208};
209
210const TD1: [u32; 256] = {
211    let mut t = [0u32; 256];
212    let mut i = 0usize;
213    while i < 256 {
214        t[i] = TD0[i].rotate_right(8);
215        i += 1;
216    }
217    t
218};
219const TD2: [u32; 256] = {
220    let mut t = [0u32; 256];
221    let mut i = 0usize;
222    while i < 256 {
223        t[i] = TD0[i].rotate_right(16);
224        i += 1;
225    }
226    t
227};
228const TD3: [u32; 256] = {
229    let mut t = [0u32; 256];
230    let mut i = 0usize;
231    while i < 256 {
232        t[i] = TD0[i].rotate_right(24);
233        i += 1;
234    }
235    t
236};
237
238// ─────────────────────────────────────────────────────────────────────────────
239// Key expansion — FIPS 197, § 5.2
240//
241// SubWord applies SBOX to each byte of a 32-bit word.
242// RotWord is a left rotation by one byte (≡ rotate_left(8)).
243// RCON[i] = [x^i, 0, 0, 0] in GF(2⁸), pre-tabulated above.
244//
245// The number of round-key words is NR + 1 columns × 4 words/column:
246//   AES-128: NK=4, NR=10 → 44 words
247//   AES-192: NK=6, NR=12 → 52 words
248//   AES-256: NK=8, NR=14 → 60 words
249// ─────────────────────────────────────────────────────────────────────────────
250
251fn sub_word(w: u32) -> u32 {
252    u32::from(SBOX[(w >> 24) as usize]) << 24
253        | u32::from(SBOX[((w >> 16) & 0xff) as usize]) << 16
254        | u32::from(SBOX[((w >> 8) & 0xff) as usize]) << 8
255        | u32::from(SBOX[(w & 0xff) as usize])
256}
257
258fn expand_128(key: &[u8; 16]) -> [u32; 44] {
259    let mut w = [0u32; 44];
260    for i in 0..4 {
261        w[i] = u32::from_be_bytes(key[4 * i..4 * i + 4].try_into().unwrap());
262    }
263    for i in 4..44 {
264        let mut t = w[i - 1];
265        if i % 4 == 0 {
266            t = sub_word(t.rotate_left(8)) ^ RCON[i / 4 - 1];
267        }
268        w[i] = w[i - 4] ^ t;
269    }
270    w
271}
272
273fn expand_192(key: &[u8; 24]) -> [u32; 52] {
274    let mut w = [0u32; 52];
275    for i in 0..6 {
276        w[i] = u32::from_be_bytes(key[4 * i..4 * i + 4].try_into().unwrap());
277    }
278    for i in 6..52 {
279        let mut t = w[i - 1];
280        if i % 6 == 0 {
281            t = sub_word(t.rotate_left(8)) ^ RCON[i / 6 - 1];
282        }
283        w[i] = w[i - 6] ^ t;
284    }
285    w
286}
287
288fn expand_256(key: &[u8; 32]) -> [u32; 60] {
289    let mut w = [0u32; 60];
290    for i in 0..8 {
291        w[i] = u32::from_be_bytes(key[4 * i..4 * i + 4].try_into().unwrap());
292    }
293    for i in 8..60 {
294        let mut t = w[i - 1];
295        if i % 8 == 0 {
296            t = sub_word(t.rotate_left(8)) ^ RCON[i / 8 - 1];
297        } else if i % 8 == 4 {
298            t = sub_word(t);
299        } // extra SubWord for 256-bit
300        w[i] = w[i - 8] ^ t;
301    }
302    w
303}
304
305// ─────────────────────────────────────────────────────────────────────────────
306// Decryption key schedule — equivalent inverse cipher (FIPS 197 § 5.3.5)
307//
308// The T-table decryption path folds InvSubBytes + InvMixColumns into the TD
309// tables, so the middle-round keys must be pre-transformed with
310// InvMixColumns.
311// ─────────────────────────────────────────────────────────────────────────────
312
313fn inv_mix_col(w: u32) -> u32 {
314    TD0[SBOX[(w >> 24) as usize] as usize]
315        ^ TD1[SBOX[((w >> 16) & 0xff) as usize] as usize]
316        ^ TD2[SBOX[((w >> 8) & 0xff) as usize] as usize]
317        ^ TD3[SBOX[(w & 0xff) as usize] as usize]
318}
319
320/// Build the decryption round-key schedule from the forward expanded key.
321/// `enc_rk` and `dec_rk` must have the same length (NR+1)*4.
322fn make_dec_rk(enc_rk: &[u32], dec_rk: &mut [u32], nr: usize) {
323    dec_rk[0..4].copy_from_slice(&enc_rk[nr * 4..nr * 4 + 4]);
324    for r in 1..nr {
325        for j in 0..4 {
326            dec_rk[r * 4 + j] = inv_mix_col(enc_rk[(nr - r) * 4 + j]);
327        }
328    }
329    dec_rk[nr * 4..nr * 4 + 4].copy_from_slice(&enc_rk[0..4]);
330}
331
332// ─────────────────────────────────────────────────────────────────────────────
333// Cipher core — pure, safe Rust T-table implementation
334//
335// State is held as four u32 words, one per column (big-endian byte order):
336//   s0 = state[0][0]<<24 | state[1][0]<<16 | state[2][0]<<8 | state[3][0]
337//
338// `rk`  — flat slice of forward round-key words, length (NR+1)×4.
339// `dk`  — flat slice of decryption round-key words (from make_dec_rk).
340// `nr`  — number of rounds (10 / 12 / 14).
341// ─────────────────────────────────────────────────────────────────────────────
342
343fn aes_encrypt(block: &[u8; 16], rk: &[u32], nr: usize) -> [u8; 16] {
344    let mut s0 = u32::from_be_bytes(block[0..4].try_into().unwrap()) ^ rk[0];
345    let mut s1 = u32::from_be_bytes(block[4..8].try_into().unwrap()) ^ rk[1];
346    let mut s2 = u32::from_be_bytes(block[8..12].try_into().unwrap()) ^ rk[2];
347    let mut s3 = u32::from_be_bytes(block[12..16].try_into().unwrap()) ^ rk[3];
348
349    for r in 1..nr {
350        let k = 4 * r;
351        let t0 = TE0[(s0 >> 24) as usize]
352            ^ TE1[((s1 >> 16) & 0xff) as usize]
353            ^ TE2[((s2 >> 8) & 0xff) as usize]
354            ^ TE3[(s3 & 0xff) as usize]
355            ^ rk[k];
356        let t1 = TE0[(s1 >> 24) as usize]
357            ^ TE1[((s2 >> 16) & 0xff) as usize]
358            ^ TE2[((s3 >> 8) & 0xff) as usize]
359            ^ TE3[(s0 & 0xff) as usize]
360            ^ rk[k + 1];
361        let t2 = TE0[(s2 >> 24) as usize]
362            ^ TE1[((s3 >> 16) & 0xff) as usize]
363            ^ TE2[((s0 >> 8) & 0xff) as usize]
364            ^ TE3[(s1 & 0xff) as usize]
365            ^ rk[k + 2];
366        let t3 = TE0[(s3 >> 24) as usize]
367            ^ TE1[((s0 >> 16) & 0xff) as usize]
368            ^ TE2[((s1 >> 8) & 0xff) as usize]
369            ^ TE3[(s2 & 0xff) as usize]
370            ^ rk[k + 3];
371        s0 = t0;
372        s1 = t1;
373        s2 = t2;
374        s3 = t3;
375    }
376
377    let k = 4 * nr;
378    let c0 = u32::from(SBOX[(s0 >> 24) as usize]) << 24
379        | u32::from(SBOX[((s1 >> 16) & 0xff) as usize]) << 16
380        | u32::from(SBOX[((s2 >> 8) & 0xff) as usize]) << 8
381        | u32::from(SBOX[(s3 & 0xff) as usize]);
382    let c1 = u32::from(SBOX[(s1 >> 24) as usize]) << 24
383        | u32::from(SBOX[((s2 >> 16) & 0xff) as usize]) << 16
384        | u32::from(SBOX[((s3 >> 8) & 0xff) as usize]) << 8
385        | u32::from(SBOX[(s0 & 0xff) as usize]);
386    let c2 = u32::from(SBOX[(s2 >> 24) as usize]) << 24
387        | u32::from(SBOX[((s3 >> 16) & 0xff) as usize]) << 16
388        | u32::from(SBOX[((s0 >> 8) & 0xff) as usize]) << 8
389        | u32::from(SBOX[(s1 & 0xff) as usize]);
390    let c3 = u32::from(SBOX[(s3 >> 24) as usize]) << 24
391        | u32::from(SBOX[((s0 >> 16) & 0xff) as usize]) << 16
392        | u32::from(SBOX[((s1 >> 8) & 0xff) as usize]) << 8
393        | u32::from(SBOX[(s2 & 0xff) as usize]);
394
395    let mut out = [0u8; 16];
396    out[0..4].copy_from_slice(&(c0 ^ rk[k]).to_be_bytes());
397    out[4..8].copy_from_slice(&(c1 ^ rk[k + 1]).to_be_bytes());
398    out[8..12].copy_from_slice(&(c2 ^ rk[k + 2]).to_be_bytes());
399    out[12..16].copy_from_slice(&(c3 ^ rk[k + 3]).to_be_bytes());
400    out
401}
402
403fn aes_decrypt(block: &[u8; 16], dk: &[u32], nr: usize) -> [u8; 16] {
404    let mut s0 = u32::from_be_bytes(block[0..4].try_into().unwrap()) ^ dk[0];
405    let mut s1 = u32::from_be_bytes(block[4..8].try_into().unwrap()) ^ dk[1];
406    let mut s2 = u32::from_be_bytes(block[8..12].try_into().unwrap()) ^ dk[2];
407    let mut s3 = u32::from_be_bytes(block[12..16].try_into().unwrap()) ^ dk[3];
408
409    for r in 1..nr {
410        let k = 4 * r;
411        let t0 = TD0[(s0 >> 24) as usize]
412            ^ TD1[((s3 >> 16) & 0xff) as usize]
413            ^ TD2[((s2 >> 8) & 0xff) as usize]
414            ^ TD3[(s1 & 0xff) as usize]
415            ^ dk[k];
416        let t1 = TD0[(s1 >> 24) as usize]
417            ^ TD1[((s0 >> 16) & 0xff) as usize]
418            ^ TD2[((s3 >> 8) & 0xff) as usize]
419            ^ TD3[(s2 & 0xff) as usize]
420            ^ dk[k + 1];
421        let t2 = TD0[(s2 >> 24) as usize]
422            ^ TD1[((s1 >> 16) & 0xff) as usize]
423            ^ TD2[((s0 >> 8) & 0xff) as usize]
424            ^ TD3[(s3 & 0xff) as usize]
425            ^ dk[k + 2];
426        let t3 = TD0[(s3 >> 24) as usize]
427            ^ TD1[((s2 >> 16) & 0xff) as usize]
428            ^ TD2[((s1 >> 8) & 0xff) as usize]
429            ^ TD3[(s0 & 0xff) as usize]
430            ^ dk[k + 3];
431        s0 = t0;
432        s1 = t1;
433        s2 = t2;
434        s3 = t3;
435    }
436
437    let k = 4 * nr;
438    let p0 = u32::from(INV_SBOX[(s0 >> 24) as usize]) << 24
439        | u32::from(INV_SBOX[((s3 >> 16) & 0xff) as usize]) << 16
440        | u32::from(INV_SBOX[((s2 >> 8) & 0xff) as usize]) << 8
441        | u32::from(INV_SBOX[(s1 & 0xff) as usize]);
442    let p1 = u32::from(INV_SBOX[(s1 >> 24) as usize]) << 24
443        | u32::from(INV_SBOX[((s0 >> 16) & 0xff) as usize]) << 16
444        | u32::from(INV_SBOX[((s3 >> 8) & 0xff) as usize]) << 8
445        | u32::from(INV_SBOX[(s2 & 0xff) as usize]);
446    let p2 = u32::from(INV_SBOX[(s2 >> 24) as usize]) << 24
447        | u32::from(INV_SBOX[((s1 >> 16) & 0xff) as usize]) << 16
448        | u32::from(INV_SBOX[((s0 >> 8) & 0xff) as usize]) << 8
449        | u32::from(INV_SBOX[(s3 & 0xff) as usize]);
450    let p3 = u32::from(INV_SBOX[(s3 >> 24) as usize]) << 24
451        | u32::from(INV_SBOX[((s2 >> 16) & 0xff) as usize]) << 16
452        | u32::from(INV_SBOX[((s1 >> 8) & 0xff) as usize]) << 8
453        | u32::from(INV_SBOX[(s0 & 0xff) as usize]);
454
455    let mut out = [0u8; 16];
456    out[0..4].copy_from_slice(&(p0 ^ dk[k]).to_be_bytes());
457    out[4..8].copy_from_slice(&(p1 ^ dk[k + 1]).to_be_bytes());
458    out[8..12].copy_from_slice(&(p2 ^ dk[k + 2]).to_be_bytes());
459    out[12..16].copy_from_slice(&(p3 ^ dk[k + 3]).to_be_bytes());
460    out
461}
462
463// ─────────────────────────────────────────────────────────────────────────────
464// Alternate software-only constant-time path — Boyar-Peralta S-box circuits
465//
466// This path keeps the AES round structure bytewise, but replaces S-box table
467// lookups with the depth-16 Boyar-Peralta straight-line circuits from
468// "A depth-16 circuit for the AES S-box" (NIST / IACR ePrint 2011/332).
469//
470// The key idea is to pre-synthesize the AES S-box into a fixed boolean
471// network. Instead of computing the usual "GF(2^8) inverse, then affine
472// transform" directly at runtime, the published circuit rewrites the same
473// function as a sequence of XOR, XNOR, and AND gates. That gives a
474// software-only constant-time S-box without table lookups.
475//
476// This implementation evaluates the circuit one byte at a time. Each byte is
477// treated as eight 0/1 "wires" (`u0..u7`), the published intermediate nodes
478// are transcribed as local temporaries, and the final eight output bits are
479// packed back into a byte. The temporary names intentionally mirror the paper:
480// `t*` for the first linear layer, `m*` for the nonlinear core, and `l*`/`p*`
481// for the output linear layer.
482// ─────────────────────────────────────────────────────────────────────────────
483
484#[inline]
485fn xnor(a: u8, b: u8) -> u8 {
486    (a ^ b) ^ 1
487}
488
489#[inline]
490fn bit(input: u8, idx: u8) -> u8 {
491    (input >> (7 - idx)) & 1
492}
493
494// The Boyar-Peralta circuit treats one AES byte as eight single-bit wires in
495// MSB-first order. `bit()` extracts those wires, and `pack_bits()` reassembles
496// the resulting output wires into the normal AES byte layout.
497#[inline]
498fn pack_bits(bits: [u8; 8]) -> u8 {
499    (bits[0] << 7)
500        | (bits[1] << 6)
501        | (bits[2] << 5)
502        | (bits[3] << 4)
503        | (bits[4] << 3)
504        | (bits[5] << 2)
505        | (bits[6] << 1)
506        | bits[7]
507}
508
509/// Forward AES S-box via the published Boyar-Peralta straight-line circuit.
510///
511/// The operation is exactly the same S-box as `SBOX[input]`; it is just
512/// represented as boolean logic instead of a lookup table. The `^ 1` terms in
513/// the final output stage encode the affine constant from the AES S-box.
514#[inline]
515fn sbox_bool(input: u8) -> u8 {
516    let (bits, linear_terms) = sbox_bool_linear(input);
517    let non_linear_terms = sbox_bool_nonlinear(bits, linear_terms);
518    sbox_bool_output(non_linear_terms)
519}
520
521fn sbox_bool_linear(input: u8) -> ([u8; 8], [u8; 27]) {
522    let bits = [
523        bit(input, 0),
524        bit(input, 1),
525        bit(input, 2),
526        bit(input, 3),
527        bit(input, 4),
528        bit(input, 5),
529        bit(input, 6),
530        bit(input, 7),
531    ];
532    let [u0, u1, u2, u3, u4, u5, u6, u7] = bits;
533
534    let mut t = [0u8; 27];
535    t[0] = u0 ^ u3;
536    t[1] = u0 ^ u5;
537    t[2] = u0 ^ u6;
538    t[3] = u3 ^ u5;
539    t[4] = u4 ^ u6;
540    t[5] = t[0] ^ t[4];
541    t[6] = u1 ^ u2;
542    t[7] = u7 ^ t[5];
543    t[8] = u7 ^ t[6];
544    t[9] = t[5] ^ t[6];
545    t[10] = u1 ^ u5;
546    t[11] = u2 ^ u5;
547    t[12] = t[2] ^ t[3];
548    t[13] = t[5] ^ t[10];
549    t[14] = t[4] ^ t[10];
550    t[15] = t[4] ^ t[11];
551    t[16] = t[8] ^ t[15];
552    t[17] = u3 ^ u7;
553    t[18] = t[6] ^ t[17];
554    t[19] = t[0] ^ t[18];
555    t[20] = u6 ^ u7;
556    t[21] = t[6] ^ t[20];
557    t[22] = t[1] ^ t[21];
558    t[23] = t[1] ^ t[9];
559    t[24] = t[19] ^ t[16];
560    t[25] = t[2] ^ t[15];
561    t[26] = t[0] ^ t[11];
562
563    (bits, t)
564}
565
566fn sbox_bool_nonlinear(bits: [u8; 8], t: [u8; 27]) -> [u8; 63] {
567    let u7 = bits[7];
568    let mut m = [0u8; 63];
569    m[0] = t[12] & t[5];
570    m[1] = t[22] & t[7];
571    m[2] = t[13] ^ m[0];
572    m[3] = t[18] & u7;
573    m[4] = m[3] ^ m[0];
574    m[5] = t[2] & t[15];
575    m[6] = t[21] & t[8];
576    m[7] = t[25] ^ m[5];
577    m[8] = t[19] & t[16];
578    m[9] = m[8] ^ m[5];
579    m[10] = t[0] & t[14];
580    m[11] = t[3] & t[26];
581    m[12] = m[11] ^ m[10];
582    m[13] = t[1] & t[9];
583    m[14] = m[13] ^ m[10];
584    m[15] = m[2] ^ m[1];
585    m[16] = m[4] ^ t[23];
586    m[17] = m[7] ^ m[6];
587    m[18] = m[9] ^ m[14];
588    m[19] = m[15] ^ m[12];
589    m[20] = m[16] ^ m[14];
590    m[21] = m[17] ^ m[12];
591    m[22] = m[18] ^ t[24];
592    m[23] = m[21] ^ m[22];
593    m[24] = m[21] & m[19];
594    m[25] = m[20] ^ m[24];
595    m[26] = m[19] ^ m[20];
596    m[27] = m[22] ^ m[24];
597    m[28] = m[27] & m[26];
598    m[29] = m[25] & m[23];
599    m[30] = m[19] & m[22];
600    m[31] = m[26] & m[30];
601    m[32] = m[26] ^ m[24];
602    m[33] = m[20] & m[21];
603    m[34] = m[23] & m[33];
604    m[35] = m[23] ^ m[24];
605    m[36] = m[20] ^ m[28];
606    m[37] = m[31] ^ m[32];
607    m[38] = m[22] ^ m[29];
608    m[39] = m[34] ^ m[35];
609    m[40] = m[37] ^ m[39];
610    m[41] = m[36] ^ m[38];
611    m[42] = m[36] ^ m[37];
612    m[43] = m[38] ^ m[39];
613    m[44] = m[41] ^ m[40];
614    m[45] = m[43] & t[5];
615    m[46] = m[39] & t[7];
616    m[47] = m[38] & u7;
617    m[48] = m[42] & t[15];
618    m[49] = m[37] & t[8];
619    m[50] = m[36] & t[16];
620    m[51] = m[41] & t[14];
621    m[52] = m[44] & t[26];
622    m[53] = m[40] & t[9];
623    m[54] = m[43] & t[12];
624    m[55] = m[39] & t[22];
625    m[56] = m[38] & t[18];
626    m[57] = m[42] & t[2];
627    m[58] = m[37] & t[21];
628    m[59] = m[36] & t[19];
629    m[60] = m[41] & t[0];
630    m[61] = m[44] & t[3];
631    m[62] = m[40] & t[1];
632    m
633}
634
635fn sbox_bool_output(m: [u8; 63]) -> u8 {
636    let l0 = m[60] ^ m[61];
637    let l1 = m[49] ^ m[55];
638    let l2 = m[45] ^ m[47];
639    let l3 = m[46] ^ m[54];
640    let l4 = m[53] ^ m[57];
641    let l5 = m[48] ^ m[60];
642    let l6 = m[61] ^ l5;
643    let l7 = m[45] ^ l3;
644    let l8 = m[50] ^ m[58];
645    let l9 = m[51] ^ m[52];
646    let l10 = m[52] ^ l4;
647    let l11 = m[59] ^ l2;
648    let l12 = m[47] ^ m[50];
649    let l13 = m[49] ^ l0;
650    let l14 = m[51] ^ m[60];
651    let l15 = m[54] ^ l1;
652    let l16 = m[55] ^ l0;
653    let l17 = m[56] ^ l1;
654    let l18 = m[57] ^ l8;
655    let l19 = m[62] ^ l4;
656    let l20 = l0 ^ l1;
657    let l21 = l1 ^ l7;
658    let l22 = l3 ^ l12;
659    let l23 = l18 ^ l2;
660    let l24 = l15 ^ l9;
661    let l25 = l6 ^ l10;
662    let l26 = l7 ^ l9;
663    let l27 = l8 ^ l10;
664    let l28 = l11 ^ l14;
665    let l29 = l11 ^ l17;
666
667    pack_bits([
668        l6 ^ l24,
669        (l16 ^ l26) ^ 1,
670        (l19 ^ l28) ^ 1,
671        l6 ^ l21,
672        l20 ^ l22,
673        l25 ^ l29,
674        (l13 ^ l27) ^ 1,
675        (l6 ^ l23) ^ 1,
676    ])
677}
678
679/// Inverse AES S-box via the companion Boyar-Peralta straight-line circuit.
680///
681/// As above, this computes the same mapping as `INV_SBOX[input]` without using
682/// a secret-indexed lookup. The variable names follow the published circuit so
683/// the source can be checked against the paper directly.
684#[inline]
685fn inv_sbox_bool(input: u8) -> u8 {
686    let linear_terms = inv_sbox_bool_linear(input);
687    let non_linear_terms = inv_sbox_bool_nonlinear(linear_terms);
688    inv_sbox_bool_output(non_linear_terms)
689}
690
691fn inv_sbox_bool_linear(input: u8) -> [u8; 27] {
692    let u0 = bit(input, 0);
693    let u1 = bit(input, 1);
694    let u2 = bit(input, 2);
695    let u3 = bit(input, 3);
696    let u4 = bit(input, 4);
697    let u5 = bit(input, 5);
698    let u6 = bit(input, 6);
699    let u7 = bit(input, 7);
700
701    let mut t = [0u8; 27];
702    t[0] = u0 ^ u3;
703    t[1] = xnor(u1, u3);
704    t[2] = xnor(u0, u1);
705    t[3] = u3 ^ u4;
706    t[4] = xnor(u4, u7);
707    t[5] = u6 ^ u7;
708    t[6] = xnor(u1, t[0]);
709    t[7] = t[1] ^ t[5];
710    t[8] = xnor(u7, t[3]);
711    t[9] = t[2] ^ t[4];
712    t[10] = t[2] ^ t[5];
713    t[11] = t[3] ^ t[5];
714    t[12] = xnor(u2, t[3]);
715    t[13] = u1 ^ u6;
716    t[14] = xnor(u2, t[7]);
717    t[15] = t[4] ^ t[13];
718    t[16] = u4 ^ t[6];
719    t[17] = xnor(u2, u5);
720    t[18] = xnor(u5, u6);
721    t[19] = xnor(u2, u4);
722    t[20] = u0 ^ t[17];
723    t[21] = t[1] ^ t[17];
724    t[22] = t[13] ^ t[19];
725    t[23] = t[3] ^ t[18];
726    t[24] = t[9] ^ t[23];
727    t[25] = t[9] ^ t[18];
728    t[26] = t[11] ^ t[22];
729    t
730}
731
732fn inv_sbox_bool_nonlinear(t: [u8; 27]) -> [u8; 63] {
733    let mut m = [0u8; 63];
734    m[0] = t[10] & t[21];
735    m[1] = t[0] & t[6];
736    m[2] = t[25] ^ m[0];
737    m[3] = t[7] & t[20];
738    m[4] = m[3] ^ m[0];
739    m[5] = t[11] & t[22];
740    m[6] = t[1] & t[8];
741    m[7] = t[26] ^ m[5];
742    m[8] = t[15] & t[14];
743    m[9] = m[8] ^ m[5];
744    m[10] = t[3] & t[24];
745    m[11] = t[16] & t[23];
746    m[12] = m[11] ^ m[10];
747    m[13] = t[2] & t[9];
748    m[14] = m[13] ^ m[10];
749    m[15] = m[2] ^ m[1];
750    m[16] = m[4] ^ t[4];
751    m[17] = m[7] ^ m[6];
752    m[18] = m[9] ^ m[14];
753    m[19] = m[15] ^ m[12];
754    m[20] = m[16] ^ m[14];
755    m[21] = m[17] ^ m[12];
756    m[22] = m[18] ^ t[12];
757    m[23] = m[21] ^ m[22];
758    m[24] = m[21] & m[19];
759    m[25] = m[20] ^ m[24];
760    m[26] = m[19] ^ m[20];
761    m[27] = m[22] ^ m[24];
762    m[28] = m[27] & m[26];
763    m[29] = m[25] & m[23];
764    m[30] = m[19] & m[22];
765    m[31] = m[26] & m[30];
766    m[32] = m[26] ^ m[24];
767    m[33] = m[20] & m[21];
768    m[34] = m[23] & m[33];
769    m[35] = m[23] ^ m[24];
770    m[36] = m[20] ^ m[28];
771    m[37] = m[31] ^ m[32];
772    m[38] = m[22] ^ m[29];
773    m[39] = m[34] ^ m[35];
774    m[40] = m[37] ^ m[39];
775    m[41] = m[36] ^ m[38];
776    m[42] = m[36] ^ m[37];
777    m[43] = m[38] ^ m[39];
778    m[44] = m[41] ^ m[40];
779    m[45] = m[43] & t[21];
780    m[46] = m[39] & t[6];
781    m[47] = m[38] & t[20];
782    m[48] = m[42] & t[22];
783    m[49] = m[37] & t[8];
784    m[50] = m[36] & t[14];
785    m[51] = m[41] & t[24];
786    m[52] = m[44] & t[23];
787    m[53] = m[40] & t[9];
788    m[54] = m[43] & t[10];
789    m[55] = m[39] & t[0];
790    m[56] = m[38] & t[7];
791    m[57] = m[42] & t[11];
792    m[58] = m[37] & t[1];
793    m[59] = m[36] & t[15];
794    m[60] = m[41] & t[3];
795    m[61] = m[44] & t[16];
796    m[62] = m[40] & t[2];
797    m
798}
799
800fn inv_sbox_bool_output(m: [u8; 63]) -> u8 {
801    let p0 = m[51] ^ m[60];
802    let p1 = m[57] ^ m[58];
803    let p2 = m[53] ^ m[61];
804    let p3 = m[46] ^ m[49];
805    let p4 = m[47] ^ m[55];
806    let p5 = m[45] ^ m[50];
807    let p6 = m[48] ^ m[59];
808    let p7 = p0 ^ p1;
809    let p8 = m[49] ^ m[52];
810    let p9 = m[54] ^ m[62];
811    let p10 = m[56] ^ p4;
812    let p11 = p0 ^ p3;
813    let p12 = m[45] ^ m[47];
814    let p13 = m[48] ^ m[50];
815    let p14 = m[48] ^ m[61];
816    let p15 = m[53] ^ m[58];
817    let p16 = m[56] ^ m[60];
818    let p17 = m[57] ^ p2;
819    let p18 = m[62] ^ p5;
820    let p19 = p2 ^ p3;
821    let p20 = p4 ^ p6;
822    let p22 = p2 ^ p7;
823    let p23 = p7 ^ p8;
824    let p24 = p5 ^ p7;
825    let p25 = p6 ^ p10;
826    let p26 = p9 ^ p11;
827    let p27 = p10 ^ p18;
828    let p28 = p11 ^ p25;
829    let p29 = p15 ^ p20;
830
831    pack_bits([
832        p13 ^ p22,
833        p26 ^ p29,
834        p17 ^ p28,
835        p12 ^ p22,
836        p23 ^ p27,
837        p19 ^ p24,
838        p14 ^ p23,
839        p9 ^ p16,
840    ])
841}
842
843// `SubWord` for the Ct key schedule: identical AES key expansion logic, but
844// with the Boyar-Peralta S-box replacing the table lookup.
845fn sub_word_bool(w: u32) -> u32 {
846    u32::from(sbox_bool((w >> 24) as u8)) << 24
847        | u32::from(sbox_bool(((w >> 16) & 0xff) as u8)) << 16
848        | u32::from(sbox_bool(((w >> 8) & 0xff) as u8)) << 8
849        | u32::from(sbox_bool((w & 0xff) as u8))
850}
851
852fn expand_128_bool(key: &[u8; 16]) -> [u32; 44] {
853    let mut w = [0u32; 44];
854    for i in 0..4 {
855        w[i] = u32::from_be_bytes(key[4 * i..4 * i + 4].try_into().unwrap());
856    }
857    for i in 4..44 {
858        let mut t = w[i - 1];
859        if i % 4 == 0 {
860            t = sub_word_bool(t.rotate_left(8)) ^ RCON[i / 4 - 1];
861        }
862        w[i] = w[i - 4] ^ t;
863    }
864    w
865}
866
867fn expand_192_bool(key: &[u8; 24]) -> [u32; 52] {
868    let mut w = [0u32; 52];
869    for i in 0..6 {
870        w[i] = u32::from_be_bytes(key[4 * i..4 * i + 4].try_into().unwrap());
871    }
872    for i in 6..52 {
873        let mut t = w[i - 1];
874        if i % 6 == 0 {
875            t = sub_word_bool(t.rotate_left(8)) ^ RCON[i / 6 - 1];
876        }
877        w[i] = w[i - 6] ^ t;
878    }
879    w
880}
881
882fn expand_256_bool(key: &[u8; 32]) -> [u32; 60] {
883    let mut w = [0u32; 60];
884    for i in 0..8 {
885        w[i] = u32::from_be_bytes(key[4 * i..4 * i + 4].try_into().unwrap());
886    }
887    for i in 8..60 {
888        let mut t = w[i - 1];
889        if i % 8 == 0 {
890            t = sub_word_bool(t.rotate_left(8)) ^ RCON[i / 8 - 1];
891        } else if i % 8 == 4 {
892            t = sub_word_bool(t);
893        }
894        w[i] = w[i - 8] ^ t;
895    }
896    w
897}
898
899// The bytewise Ct decrypt path uses the direct inverse round functions, so it
900// only needs the encryption round keys in reverse order. Unlike the fast
901// T-table path, there is no equivalent-inverse table transform here.
902fn make_dec_rk_ct(enc_rk: &[u32], dec_rk: &mut [u32], nr: usize) {
903    for r in 0..=nr {
904        let src = (nr - r) * 4;
905        let dst = r * 4;
906        dec_rk[dst..dst + 4].copy_from_slice(&enc_rk[src..src + 4]);
907    }
908}
909
910#[inline]
911fn add_round_key_ct(state: &mut [u8; 16], rk: &[u32]) {
912    for c in 0..4 {
913        let word = rk[c].to_be_bytes();
914        for r in 0..4 {
915            state[4 * c + r] ^= word[r];
916        }
917    }
918}
919
920#[inline]
921fn sub_bytes_ct(state: &mut [u8; 16]) {
922    for b in state.iter_mut() {
923        *b = sbox_bool(*b);
924    }
925}
926
927#[inline]
928fn inv_sub_bytes_ct(state: &mut [u8; 16]) {
929    for b in state.iter_mut() {
930        *b = inv_sbox_bool(*b);
931    }
932}
933
934#[inline]
935fn shift_rows_ct(state: &mut [u8; 16]) {
936    let t = *state;
937    state[0] = t[0];
938    state[1] = t[5];
939    state[2] = t[10];
940    state[3] = t[15];
941    state[4] = t[4];
942    state[5] = t[9];
943    state[6] = t[14];
944    state[7] = t[3];
945    state[8] = t[8];
946    state[9] = t[13];
947    state[10] = t[2];
948    state[11] = t[7];
949    state[12] = t[12];
950    state[13] = t[1];
951    state[14] = t[6];
952    state[15] = t[11];
953}
954
955#[inline]
956fn inv_shift_rows_ct(state: &mut [u8; 16]) {
957    let t = *state;
958    state[0] = t[0];
959    state[1] = t[13];
960    state[2] = t[10];
961    state[3] = t[7];
962    state[4] = t[4];
963    state[5] = t[1];
964    state[6] = t[14];
965    state[7] = t[11];
966    state[8] = t[8];
967    state[9] = t[5];
968    state[10] = t[2];
969    state[11] = t[15];
970    state[12] = t[12];
971    state[13] = t[9];
972    state[14] = t[6];
973    state[15] = t[3];
974}
975
976#[inline]
977fn mix_columns_ct(state: &mut [u8; 16]) {
978    for c in 0..4 {
979        let i = 4 * c;
980        let a0 = state[i];
981        let a1 = state[i + 1];
982        let a2 = state[i + 2];
983        let a3 = state[i + 3];
984        let t = a0 ^ a1 ^ a2 ^ a3;
985
986        state[i] = a0 ^ t ^ xtime(a0 ^ a1);
987        state[i + 1] = a1 ^ t ^ xtime(a1 ^ a2);
988        state[i + 2] = a2 ^ t ^ xtime(a2 ^ a3);
989        state[i + 3] = a3 ^ t ^ xtime(a3 ^ a0);
990    }
991}
992
993#[inline]
994fn inv_mix_columns_ct(state: &mut [u8; 16]) {
995    for c in 0..4 {
996        let i = 4 * c;
997        let a0 = state[i];
998        let a1 = state[i + 1];
999        let a2 = state[i + 2];
1000        let a3 = state[i + 3];
1001        state[i] = mul14(a0) ^ mul11(a1) ^ mul13(a2) ^ mul9(a3);
1002        state[i + 1] = mul9(a0) ^ mul14(a1) ^ mul11(a2) ^ mul13(a3);
1003        state[i + 2] = mul13(a0) ^ mul9(a1) ^ mul14(a2) ^ mul11(a3);
1004        state[i + 3] = mul11(a0) ^ mul13(a1) ^ mul9(a2) ^ mul14(a3);
1005    }
1006}
1007
1008fn aes_encrypt_ct(block: &[u8; 16], rk: &[u32], nr: usize) -> [u8; 16] {
1009    let mut state = *block;
1010    add_round_key_ct(&mut state, &rk[0..4]);
1011
1012    for r in 1..nr {
1013        sub_bytes_ct(&mut state);
1014        shift_rows_ct(&mut state);
1015        mix_columns_ct(&mut state);
1016        add_round_key_ct(&mut state, &rk[4 * r..4 * r + 4]);
1017    }
1018
1019    sub_bytes_ct(&mut state);
1020    shift_rows_ct(&mut state);
1021    add_round_key_ct(&mut state, &rk[4 * nr..4 * nr + 4]);
1022    state
1023}
1024
1025fn aes_decrypt_ct(block: &[u8; 16], dk: &[u32], nr: usize) -> [u8; 16] {
1026    let mut state = *block;
1027    add_round_key_ct(&mut state, &dk[0..4]);
1028
1029    for r in 1..nr {
1030        inv_shift_rows_ct(&mut state);
1031        inv_sub_bytes_ct(&mut state);
1032        add_round_key_ct(&mut state, &dk[4 * r..4 * r + 4]);
1033        inv_mix_columns_ct(&mut state);
1034    }
1035
1036    inv_shift_rows_ct(&mut state);
1037    inv_sub_bytes_ct(&mut state);
1038    add_round_key_ct(&mut state, &dk[4 * nr..4 * nr + 4]);
1039    state
1040}
1041
1042// ─────────────────────────────────────────────────────────────────────────────
1043// Public API
1044// ─────────────────────────────────────────────────────────────────────────────
1045
1046/// AES-128 cipher: 128-bit key, 10 rounds.
1047pub struct Aes128 {
1048    enc_rk: [u32; 44],
1049    dec_rk: [u32; 44],
1050}
1051impl Aes128 {
1052    #[must_use]
1053    pub fn new(key: &[u8; 16]) -> Self {
1054        let enc_rk = expand_128(key);
1055        let mut dec_rk = [0u32; 44];
1056        make_dec_rk(&enc_rk, &mut dec_rk, 10);
1057        Self { enc_rk, dec_rk }
1058    }
1059    pub fn new_wiping(key: &mut [u8; 16]) -> Self {
1060        let out = Self::new(key);
1061        crate::ct::zeroize_slice(key.as_mut_slice());
1062        out
1063    }
1064    #[must_use]
1065    pub fn encrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
1066        aes_encrypt(block, &self.enc_rk, 10)
1067    }
1068    #[must_use]
1069    pub fn decrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
1070        aes_decrypt(block, &self.dec_rk, 10)
1071    }
1072}
1073
1074/// AES-192 cipher: 192-bit key, 12 rounds.
1075pub struct Aes192 {
1076    enc_rk: [u32; 52],
1077    dec_rk: [u32; 52],
1078}
1079impl Aes192 {
1080    #[must_use]
1081    pub fn new(key: &[u8; 24]) -> Self {
1082        let enc_rk = expand_192(key);
1083        let mut dec_rk = [0u32; 52];
1084        make_dec_rk(&enc_rk, &mut dec_rk, 12);
1085        Self { enc_rk, dec_rk }
1086    }
1087    pub fn new_wiping(key: &mut [u8; 24]) -> Self {
1088        let out = Self::new(key);
1089        crate::ct::zeroize_slice(key.as_mut_slice());
1090        out
1091    }
1092    #[must_use]
1093    pub fn encrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
1094        aes_encrypt(block, &self.enc_rk, 12)
1095    }
1096    #[must_use]
1097    pub fn decrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
1098        aes_decrypt(block, &self.dec_rk, 12)
1099    }
1100}
1101
1102/// AES-256 cipher: 256-bit key, 14 rounds.
1103pub struct Aes256 {
1104    enc_rk: [u32; 60],
1105    dec_rk: [u32; 60],
1106}
1107impl Aes256 {
1108    #[must_use]
1109    pub fn new(key: &[u8; 32]) -> Self {
1110        let enc_rk = expand_256(key);
1111        let mut dec_rk = [0u32; 60];
1112        make_dec_rk(&enc_rk, &mut dec_rk, 14);
1113        Self { enc_rk, dec_rk }
1114    }
1115    pub fn new_wiping(key: &mut [u8; 32]) -> Self {
1116        let out = Self::new(key);
1117        crate::ct::zeroize_slice(key.as_mut_slice());
1118        out
1119    }
1120    #[must_use]
1121    pub fn encrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
1122        aes_encrypt(block, &self.enc_rk, 14)
1123    }
1124    #[must_use]
1125    pub fn decrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
1126        aes_decrypt(block, &self.dec_rk, 14)
1127    }
1128}
1129
1130/// AES-128 constant-time software path.
1131///
1132/// This keeps the same external API as `Aes128`, but swaps the T-table round
1133/// core for a bytewise implementation whose S-box is an explicit
1134/// Boyar-Peralta-style boolean circuit. The separate type keeps the default
1135/// `Aes128` fast while still offering a software-only constant-time option.
1136pub struct Aes128Ct {
1137    enc_rk: [u32; 44],
1138    dec_rk: [u32; 44],
1139}
1140impl Aes128Ct {
1141    #[must_use]
1142    pub fn new(key: &[u8; 16]) -> Self {
1143        let enc_rk = expand_128_bool(key);
1144        let mut dec_rk = [0u32; 44];
1145        make_dec_rk_ct(&enc_rk, &mut dec_rk, 10);
1146        Self { enc_rk, dec_rk }
1147    }
1148    pub fn new_wiping(key: &mut [u8; 16]) -> Self {
1149        let out = Self::new(key);
1150        crate::ct::zeroize_slice(key.as_mut_slice());
1151        out
1152    }
1153    #[must_use]
1154    pub fn encrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
1155        aes_encrypt_ct(block, &self.enc_rk, 10)
1156    }
1157    #[must_use]
1158    pub fn decrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
1159        aes_decrypt_ct(block, &self.dec_rk, 10)
1160    }
1161}
1162
1163/// AES-192 constant-time software path.
1164///
1165/// This is the software-only constant-time counterpart to `Aes192`, using the
1166/// same Boyar-Peralta-style boolean S-box strategy as `Aes128Ct`.
1167pub struct Aes192Ct {
1168    enc_rk: [u32; 52],
1169    dec_rk: [u32; 52],
1170}
1171impl Aes192Ct {
1172    #[must_use]
1173    pub fn new(key: &[u8; 24]) -> Self {
1174        let enc_rk = expand_192_bool(key);
1175        let mut dec_rk = [0u32; 52];
1176        make_dec_rk_ct(&enc_rk, &mut dec_rk, 12);
1177        Self { enc_rk, dec_rk }
1178    }
1179    pub fn new_wiping(key: &mut [u8; 24]) -> Self {
1180        let out = Self::new(key);
1181        crate::ct::zeroize_slice(key.as_mut_slice());
1182        out
1183    }
1184    #[must_use]
1185    pub fn encrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
1186        aes_encrypt_ct(block, &self.enc_rk, 12)
1187    }
1188    #[must_use]
1189    pub fn decrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
1190        aes_decrypt_ct(block, &self.dec_rk, 12)
1191    }
1192}
1193
1194/// AES-256 constant-time software path.
1195///
1196/// This is the software-only constant-time counterpart to `Aes256`, using the
1197/// same Boyar-Peralta-style boolean S-box strategy as `Aes128Ct`.
1198pub struct Aes256Ct {
1199    enc_rk: [u32; 60],
1200    dec_rk: [u32; 60],
1201}
1202impl Aes256Ct {
1203    #[must_use]
1204    pub fn new(key: &[u8; 32]) -> Self {
1205        let enc_rk = expand_256_bool(key);
1206        let mut dec_rk = [0u32; 60];
1207        make_dec_rk_ct(&enc_rk, &mut dec_rk, 14);
1208        Self { enc_rk, dec_rk }
1209    }
1210    pub fn new_wiping(key: &mut [u8; 32]) -> Self {
1211        let out = Self::new(key);
1212        crate::ct::zeroize_slice(key.as_mut_slice());
1213        out
1214    }
1215    #[must_use]
1216    pub fn encrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
1217        aes_encrypt_ct(block, &self.enc_rk, 14)
1218    }
1219    #[must_use]
1220    pub fn decrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
1221        aes_decrypt_ct(block, &self.dec_rk, 14)
1222    }
1223}
1224
1225// ─────────────────────────────────────────────────────────────────────────────
1226// BlockCipher trait implementations
1227// ─────────────────────────────────────────────────────────────────────────────
1228
1229macro_rules! impl_block_cipher_aes {
1230    ($Name:ident) => {
1231        impl crate::BlockCipher for $Name {
1232            const BLOCK_LEN: usize = 16;
1233            fn encrypt(&self, block: &mut [u8]) {
1234                let arr: &[u8; 16] = (&*block).try_into().expect("wrong block length");
1235                block.copy_from_slice(&self.encrypt_block(arr));
1236            }
1237            fn decrypt(&self, block: &mut [u8]) {
1238                let arr: &[u8; 16] = (&*block).try_into().expect("wrong block length");
1239                block.copy_from_slice(&self.decrypt_block(arr));
1240            }
1241        }
1242    };
1243}
1244
1245impl_block_cipher_aes!(Aes128);
1246impl_block_cipher_aes!(Aes192);
1247impl_block_cipher_aes!(Aes256);
1248impl_block_cipher_aes!(Aes128Ct);
1249impl_block_cipher_aes!(Aes192Ct);
1250impl_block_cipher_aes!(Aes256Ct);
1251
1252macro_rules! impl_drop_aes {
1253    ($Name:ident) => {
1254        impl Drop for $Name {
1255            fn drop(&mut self) {
1256                // AES retains both forward and reverse schedules for reuse.
1257                crate::ct::zeroize_slice(self.enc_rk.as_mut_slice());
1258                crate::ct::zeroize_slice(self.dec_rk.as_mut_slice());
1259            }
1260        }
1261    };
1262}
1263
1264impl_drop_aes!(Aes128);
1265impl_drop_aes!(Aes192);
1266impl_drop_aes!(Aes256);
1267impl_drop_aes!(Aes128Ct);
1268impl_drop_aes!(Aes192Ct);
1269impl_drop_aes!(Aes256Ct);
1270
1271// ─────────────────────────────────────────────────────────────────────────────
1272// Tests — NIST CAVP KAT_AES vectors (CAVS 11.1, csrc.nist.gov)
1273// ─────────────────────────────────────────────────────────────────────────────
1274
1275#[cfg(test)]
1276mod tests {
1277    use super::*;
1278
1279    fn parse<const N: usize>(s: &str) -> [u8; N] {
1280        let v: Vec<u8> = (0..s.len())
1281            .step_by(2)
1282            .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
1283            .collect();
1284        v.try_into().unwrap()
1285    }
1286
1287    fn kat128(key: &str, pt: &str, ct: &str) {
1288        let c = Aes128::new(&parse(key));
1289        assert_eq!(
1290            c.encrypt_block(&parse(pt)),
1291            parse::<16>(ct),
1292            "enc {key}/{pt}"
1293        );
1294        assert_eq!(
1295            c.decrypt_block(&parse(ct)),
1296            parse::<16>(pt),
1297            "dec {key}/{ct}"
1298        );
1299    }
1300    fn kat192(key: &str, pt: &str, ct: &str) {
1301        let c = Aes192::new(&parse(key));
1302        assert_eq!(
1303            c.encrypt_block(&parse(pt)),
1304            parse::<16>(ct),
1305            "enc {key}/{pt}"
1306        );
1307        assert_eq!(
1308            c.decrypt_block(&parse(ct)),
1309            parse::<16>(pt),
1310            "dec {key}/{ct}"
1311        );
1312    }
1313    fn kat256(key: &str, pt: &str, ct: &str) {
1314        let c = Aes256::new(&parse(key));
1315        assert_eq!(
1316            c.encrypt_block(&parse(pt)),
1317            parse::<16>(ct),
1318            "enc {key}/{pt}"
1319        );
1320        assert_eq!(
1321            c.decrypt_block(&parse(ct)),
1322            parse::<16>(pt),
1323            "dec {key}/{ct}"
1324        );
1325    }
1326
1327    #[test]
1328    fn bool_sbox_matches_tables() {
1329        for x in 0u16..=255 {
1330            let b = u8::try_from(x).expect("table index fits in u8");
1331            assert_eq!(sbox_bool(b), SBOX[x as usize], "sbox {x:02x}");
1332            assert_eq!(inv_sbox_bool(b), INV_SBOX[x as usize], "inv_sbox {x:02x}");
1333        }
1334    }
1335
1336    const KEYSBOX_128_CASES: [(&str, &str, &str); 21] = [
1337        (
1338            "10a58869d74be5a374cf867cfb473859",
1339            "00000000000000000000000000000000",
1340            "6d251e6944b051e04eaa6fb4dbf78465",
1341        ),
1342        (
1343            "caea65cdbb75e9169ecd22ebe6e54675",
1344            "00000000000000000000000000000000",
1345            "6e29201190152df4ee058139def610bb",
1346        ),
1347        (
1348            "a2e2fa9baf7d20822ca9f0542f764a41",
1349            "00000000000000000000000000000000",
1350            "c3b44b95d9d2f25670eee9a0de099fa3",
1351        ),
1352        (
1353            "b6364ac4e1de1e285eaf144a2415f7a0",
1354            "00000000000000000000000000000000",
1355            "5d9b05578fc944b3cf1ccf0e746cd581",
1356        ),
1357        (
1358            "64cf9c7abc50b888af65f49d521944b2",
1359            "00000000000000000000000000000000",
1360            "f7efc89d5dba578104016ce5ad659c05",
1361        ),
1362        (
1363            "47d6742eefcc0465dc96355e851b64d9",
1364            "00000000000000000000000000000000",
1365            "0306194f666d183624aa230a8b264ae7",
1366        ),
1367        (
1368            "3eb39790678c56bee34bbcdeccf6cdb5",
1369            "00000000000000000000000000000000",
1370            "858075d536d79ccee571f7d7204b1f67",
1371        ),
1372        (
1373            "64110a924f0743d500ccadae72c13427",
1374            "00000000000000000000000000000000",
1375            "35870c6a57e9e92314bcb8087cde72ce",
1376        ),
1377        (
1378            "18d8126516f8a12ab1a36d9f04d68e51",
1379            "00000000000000000000000000000000",
1380            "6c68e9be5ec41e22c825b7c7affb4363",
1381        ),
1382        (
1383            "f530357968578480b398a3c251cd1093",
1384            "00000000000000000000000000000000",
1385            "f5df39990fc688f1b07224cc03e86cea",
1386        ),
1387        (
1388            "da84367f325d42d601b4326964802e8e",
1389            "00000000000000000000000000000000",
1390            "bba071bcb470f8f6586e5d3add18bc66",
1391        ),
1392        (
1393            "e37b1c6aa2846f6fdb413f238b089f23",
1394            "00000000000000000000000000000000",
1395            "43c9f7e62f5d288bb27aa40ef8fe1ea8",
1396        ),
1397        (
1398            "6c002b682483e0cabcc731c253be5674",
1399            "00000000000000000000000000000000",
1400            "3580d19cff44f1014a7c966a69059de5",
1401        ),
1402        (
1403            "143ae8ed6555aba96110ab58893a8ae1",
1404            "00000000000000000000000000000000",
1405            "806da864dd29d48deafbe764f8202aef",
1406        ),
1407        (
1408            "b69418a85332240dc82492353956ae0c",
1409            "00000000000000000000000000000000",
1410            "a303d940ded8f0baff6f75414cac5243",
1411        ),
1412        (
1413            "71b5c08a1993e1362e4d0ce9b22b78d5",
1414            "00000000000000000000000000000000",
1415            "c2dabd117f8a3ecabfbb11d12194d9d0",
1416        ),
1417        (
1418            "e234cdca2606b81f29408d5f6da21206",
1419            "00000000000000000000000000000000",
1420            "fff60a4740086b3b9c56195b98d91a7b",
1421        ),
1422        (
1423            "13237c49074a3da078dc1d828bb78c6f",
1424            "00000000000000000000000000000000",
1425            "8146a08e2357f0caa30ca8c94d1a0544",
1426        ),
1427        (
1428            "3071a2a48fe6cbd04f1a129098e308f8",
1429            "00000000000000000000000000000000",
1430            "4b98e06d356deb07ebb824e5713f7be3",
1431        ),
1432        (
1433            "90f42ec0f68385f2ffc5dfc03a654dce",
1434            "00000000000000000000000000000000",
1435            "7a20a53d460fc9ce0423a7a0764c6cf2",
1436        ),
1437        (
1438            "febd9a24d8b65c1c787d50a4ed3619a9",
1439            "00000000000000000000000000000000",
1440            "f4a70d8af877f9b02b4c40df57d45b17",
1441        ),
1442    ];
1443
1444    #[test]
1445    fn ct_128_kat() {
1446        let key = parse::<16>("00000000000000000000000000000000");
1447        let pt = parse::<16>("f34481ec3cc627bacd5dc3fb08f273e6");
1448        let ct = parse::<16>("0336763e966d92595a567cc9ce537f5e");
1449        let fast = Aes128::new(&key);
1450        let slow = Aes128Ct::new(&key);
1451        assert_eq!(slow.encrypt_block(&pt), ct);
1452        assert_eq!(slow.decrypt_block(&ct), pt);
1453        assert_eq!(slow.encrypt_block(&pt), fast.encrypt_block(&pt));
1454    }
1455
1456    #[test]
1457    fn ct_192_kat() {
1458        let key = parse::<24>("000000000000000000000000000000000000000000000000");
1459        let pt = parse::<16>("1b077a6af4b7f98229de786d7516b639");
1460        let ct = parse::<16>("275cfc0413d8ccb70513c3859b1d0f72");
1461        let fast = Aes192::new(&key);
1462        let slow = Aes192Ct::new(&key);
1463        assert_eq!(slow.encrypt_block(&pt), ct);
1464        assert_eq!(slow.decrypt_block(&ct), pt);
1465        assert_eq!(slow.encrypt_block(&pt), fast.encrypt_block(&pt));
1466    }
1467
1468    #[test]
1469    fn ct_256_kat() {
1470        let key = parse::<32>("0000000000000000000000000000000000000000000000000000000000000000");
1471        let pt = parse::<16>("014730f80ac625fe84f026c60bfd547d");
1472        let ct = parse::<16>("5c9d844ed46f9885085e5d6a4f94c7d7");
1473        let fast = Aes256::new(&key);
1474        let slow = Aes256Ct::new(&key);
1475        assert_eq!(slow.encrypt_block(&pt), ct);
1476        assert_eq!(slow.decrypt_block(&ct), pt);
1477        assert_eq!(slow.encrypt_block(&pt), fast.encrypt_block(&pt));
1478    }
1479
1480    // ── ECBGFSbox: key=0, plaintext chosen to stress the S-box ───────────────
1481    #[test]
1482    fn gfsbox_128() {
1483        let v = [
1484            (
1485                "00000000000000000000000000000000",
1486                "f34481ec3cc627bacd5dc3fb08f273e6",
1487                "0336763e966d92595a567cc9ce537f5e",
1488            ),
1489            (
1490                "00000000000000000000000000000000",
1491                "9798c4640bad75c7c3227db910174e72",
1492                "a9a1631bf4996954ebc093957b234589",
1493            ),
1494            (
1495                "00000000000000000000000000000000",
1496                "96ab5c2ff612d9dfaae8c31f30c42168",
1497                "ff4f8391a6a40ca5b25d23bedd44a597",
1498            ),
1499            (
1500                "00000000000000000000000000000000",
1501                "6a118a874519e64e9963798a503f1d35",
1502                "dc43be40be0e53712f7e2bf5ca707209",
1503            ),
1504            (
1505                "00000000000000000000000000000000",
1506                "cb9fceec81286ca3e989bd979b0cb284",
1507                "92beedab1895a94faa69b632e5cc47ce",
1508            ),
1509            (
1510                "00000000000000000000000000000000",
1511                "b26aeb1874e47ca8358ff22378f09144",
1512                "459264f4798f6a78bacb89c15ed3d601",
1513            ),
1514            (
1515                "00000000000000000000000000000000",
1516                "58c8e00b2631686d54eab84b91f0aca1",
1517                "08a4e2efec8a8e3312ca7460b9040bbf",
1518            ),
1519        ];
1520        for (k, p, c) in v {
1521            kat128(k, p, c);
1522        }
1523    }
1524
1525    #[test]
1526    fn gfsbox_192() {
1527        let v = [
1528            (
1529                "000000000000000000000000000000000000000000000000",
1530                "1b077a6af4b7f98229de786d7516b639",
1531                "275cfc0413d8ccb70513c3859b1d0f72",
1532            ),
1533            (
1534                "000000000000000000000000000000000000000000000000",
1535                "9c2d8842e5f48f57648205d39a239af1",
1536                "c9b8135ff1b5adc413dfd053b21bd96d",
1537            ),
1538            (
1539                "000000000000000000000000000000000000000000000000",
1540                "bff52510095f518ecca60af4205444bb",
1541                "4a3650c3371ce2eb35e389a171427440",
1542            ),
1543            (
1544                "000000000000000000000000000000000000000000000000",
1545                "51719783d3185a535bd75adc65071ce1",
1546                "4f354592ff7c8847d2d0870ca9481b7c",
1547            ),
1548            (
1549                "000000000000000000000000000000000000000000000000",
1550                "26aa49dcfe7629a8901a69a9914e6dfd",
1551                "d5e08bf9a182e857cf40b3a36ee248cc",
1552            ),
1553            (
1554                "000000000000000000000000000000000000000000000000",
1555                "941a4773058224e1ef66d10e0a6ee782",
1556                "067cd9d3749207791841562507fa9626",
1557            ),
1558        ];
1559        for (k, p, c) in v {
1560            kat192(k, p, c);
1561        }
1562    }
1563
1564    #[test]
1565    fn gfsbox_256() {
1566        let v = [
1567            (
1568                "0000000000000000000000000000000000000000000000000000000000000000",
1569                "014730f80ac625fe84f026c60bfd547d",
1570                "5c9d844ed46f9885085e5d6a4f94c7d7",
1571            ),
1572            (
1573                "0000000000000000000000000000000000000000000000000000000000000000",
1574                "0b24af36193ce4665f2825d7b4749c98",
1575                "a9ff75bd7cf6613d3731c77c3b6d0c04",
1576            ),
1577            (
1578                "0000000000000000000000000000000000000000000000000000000000000000",
1579                "761c1fe41a18acf20d241650611d90f1",
1580                "623a52fcea5d443e48d9181ab32c7421",
1581            ),
1582            (
1583                "0000000000000000000000000000000000000000000000000000000000000000",
1584                "8a560769d605868ad80d819bdba03771",
1585                "38f2c7ae10612415d27ca190d27da8b4",
1586            ),
1587            (
1588                "0000000000000000000000000000000000000000000000000000000000000000",
1589                "91fbef2d15a97816060bee1feaa49afe",
1590                "1bc704f1bce135ceb810341b216d7abe",
1591            ),
1592        ];
1593        for (k, p, c) in v {
1594            kat256(k, p, c);
1595        }
1596    }
1597
1598    // ── ECBKeySbox: plaintext=0, key chosen to stress the key schedule ────────
1599    #[test]
1600    fn keysbox_128() {
1601        for (k, p, c) in KEYSBOX_128_CASES {
1602            kat128(k, p, c);
1603        }
1604    }
1605
1606    #[test]
1607    fn keysbox_192() {
1608        let v = [
1609            (
1610                "e9f065d7c13573587f7875357dfbb16c53489f6a4bd0f7cd",
1611                "00000000000000000000000000000000",
1612                "0956259c9cd5cfd0181cca53380cde06",
1613            ),
1614            (
1615                "15d20f6ebc7e649fd95b76b107e6daba967c8a9484797f29",
1616                "00000000000000000000000000000000",
1617                "8e4e18424e591a3d5b6f0876f16f8594",
1618            ),
1619            (
1620                "a8a282ee31c03fae4f8e9b8930d5473c2ed695a347e88b7c",
1621                "00000000000000000000000000000000",
1622                "93f3270cfc877ef17e106ce938979cb0",
1623            ),
1624            (
1625                "cd62376d5ebb414917f0c78f05266433dc9192a1ec943300",
1626                "00000000000000000000000000000000",
1627                "7f6c25ff41858561bb62f36492e93c29",
1628            ),
1629            (
1630                "502a6ab36984af268bf423c7f509205207fc1552af4a91e5",
1631                "00000000000000000000000000000000",
1632                "8e06556dcbb00b809a025047cff2a940",
1633            ),
1634        ];
1635        for (k, p, c) in v {
1636            kat192(k, p, c);
1637        }
1638    }
1639
1640    #[test]
1641    fn keysbox_256() {
1642        let v = [
1643            (
1644                "c47b0294dbbbee0fec4757f22ffeee3587ca4730c3d33b691df38bab076bc558",
1645                "00000000000000000000000000000000",
1646                "46f2fb342d6f0ab477476fc501242c5f",
1647            ),
1648            (
1649                "28d46cffa158533194214a91e712fc2b45b518076675affd910edeca5f41ac64",
1650                "00000000000000000000000000000000",
1651                "4bf3b0a69aeb6657794f2901b1440ad4",
1652            ),
1653            (
1654                "c1cc358b449909a19436cfbb3f852ef8bcb5ed12ac7058325f56e6099aab1a1c",
1655                "00000000000000000000000000000000",
1656                "352065272169abf9856843927d0674fd",
1657            ),
1658            (
1659                "984ca75f4ee8d706f46c2d98c0bf4a45f5b00d791c2dfeb191b5ed8e420fd627",
1660                "00000000000000000000000000000000",
1661                "4307456a9e67813b452e15fa8fffe398",
1662            ),
1663            (
1664                "b43d08a447ac8609baadae4ff12918b9f68fc1653f1269222f123981ded7a92f",
1665                "00000000000000000000000000000000",
1666                "4663446607354989477a5c6f0f007ef4",
1667            ),
1668            (
1669                "1d85a181b54cde51f0e098095b2962fdc93b51fe9b88602b3f54130bf76a5bd9",
1670                "00000000000000000000000000000000",
1671                "531c2c38344578b84d50b3c917bbb6e1",
1672            ),
1673            (
1674                "dc0eba1f2232a7879ded34ed8428eeb8769b056bbaf8ad77cb65c3541430b4cf",
1675                "00000000000000000000000000000000",
1676                "fc6aec906323480005c58e7e1ab004ad",
1677            ),
1678            (
1679                "f8be9ba615c5a952cabbca24f68f8593039624d524c816acda2c9183bd917cb9",
1680                "00000000000000000000000000000000",
1681                "a3944b95ca0b52043584ef02151926a8",
1682            ),
1683            (
1684                "797f8b3d176dac5b7e34a2d539c4ef367a16f8635f6264737591c5c07bf57a3e",
1685                "00000000000000000000000000000000",
1686                "a74289fe73a4c123ca189ea1e1b49ad5",
1687            ),
1688            (
1689                "6838d40caf927749c13f0329d331f448e202c73ef52c5f73a37ca635d4c47707",
1690                "00000000000000000000000000000000",
1691                "b91d4ea4488644b56cf0812fa7fcf5fc",
1692            ),
1693        ];
1694        for (k, p, c) in v {
1695            kat256(k, p, c);
1696        }
1697    }
1698
1699    // ── ECBVarKey: one bit set in key, zero plaintext ─────────────────────────
1700    #[test]
1701    fn varkey_128() {
1702        let v = [
1703            (
1704                "80000000000000000000000000000000",
1705                "00000000000000000000000000000000",
1706                "0edd33d3c621e546455bd8ba1418bec8",
1707            ),
1708            (
1709                "c0000000000000000000000000000000",
1710                "00000000000000000000000000000000",
1711                "4bc3f883450c113c64ca42e1112a9e87",
1712            ),
1713            (
1714                "e0000000000000000000000000000000",
1715                "00000000000000000000000000000000",
1716                "72a1da770f5d7ac4c9ef94d822affd97",
1717            ),
1718            (
1719                "f0000000000000000000000000000000",
1720                "00000000000000000000000000000000",
1721                "970014d634e2b7650777e8e84d03ccd8",
1722            ),
1723            (
1724                "f8000000000000000000000000000000",
1725                "00000000000000000000000000000000",
1726                "f17e79aed0db7e279e955b5f493875a7",
1727            ),
1728            (
1729                "fc000000000000000000000000000000",
1730                "00000000000000000000000000000000",
1731                "9ed5a75136a940d0963da379db4af26a",
1732            ),
1733            (
1734                "fe000000000000000000000000000000",
1735                "00000000000000000000000000000000",
1736                "c4295f83465c7755e8fa364bac6a7ea5",
1737            ),
1738            (
1739                "ff000000000000000000000000000000",
1740                "00000000000000000000000000000000",
1741                "b1d758256b28fd850ad4944208cf1155",
1742            ),
1743        ];
1744        for (k, p, c) in v {
1745            kat128(k, p, c);
1746        }
1747    }
1748
1749    #[test]
1750    fn varkey_192() {
1751        let v = [
1752            (
1753                "800000000000000000000000000000000000000000000000",
1754                "00000000000000000000000000000000",
1755                "de885dc87f5a92594082d02cc1e1b42c",
1756            ),
1757            (
1758                "c00000000000000000000000000000000000000000000000",
1759                "00000000000000000000000000000000",
1760                "132b074e80f2a597bf5febd8ea5da55e",
1761            ),
1762            (
1763                "e00000000000000000000000000000000000000000000000",
1764                "00000000000000000000000000000000",
1765                "6eccedf8de592c22fb81347b79f2db1f",
1766            ),
1767            (
1768                "f00000000000000000000000000000000000000000000000",
1769                "00000000000000000000000000000000",
1770                "180b09f267c45145db2f826c2582d35c",
1771            ),
1772            (
1773                "f80000000000000000000000000000000000000000000000",
1774                "00000000000000000000000000000000",
1775                "edd807ef7652d7eb0e13c8b5e15b3bc0",
1776            ),
1777            (
1778                "fc0000000000000000000000000000000000000000000000",
1779                "00000000000000000000000000000000",
1780                "9978bcf8dd8fd72241223ad24b31b8a4",
1781            ),
1782        ];
1783        for (k, p, c) in v {
1784            kat192(k, p, c);
1785        }
1786    }
1787
1788    #[test]
1789    fn varkey_256() {
1790        let v = [
1791            (
1792                "8000000000000000000000000000000000000000000000000000000000000000",
1793                "00000000000000000000000000000000",
1794                "e35a6dcb19b201a01ebcfa8aa22b5759",
1795            ),
1796            (
1797                "c000000000000000000000000000000000000000000000000000000000000000",
1798                "00000000000000000000000000000000",
1799                "b29169cdcf2d83e838125a12ee6aa400",
1800            ),
1801            (
1802                "e000000000000000000000000000000000000000000000000000000000000000",
1803                "00000000000000000000000000000000",
1804                "d8f3a72fc3cdf74dfaf6c3e6b97b2fa6",
1805            ),
1806            (
1807                "f000000000000000000000000000000000000000000000000000000000000000",
1808                "00000000000000000000000000000000",
1809                "1c777679d50037c79491a94da76a9a35",
1810            ),
1811            (
1812                "f800000000000000000000000000000000000000000000000000000000000000",
1813                "00000000000000000000000000000000",
1814                "9cf4893ecafa0a0247a898e040691559",
1815            ),
1816            (
1817                "fc00000000000000000000000000000000000000000000000000000000000000",
1818                "00000000000000000000000000000000",
1819                "8fbb413703735326310a269bd3aa94b2",
1820            ),
1821        ];
1822        for (k, p, c) in v {
1823            kat256(k, p, c);
1824        }
1825    }
1826
1827    // ── ECBVarTxt: zero key, one bit set in plaintext ─────────────────────────
1828    #[test]
1829    fn vartxt_128() {
1830        let v = [
1831            (
1832                "00000000000000000000000000000000",
1833                "80000000000000000000000000000000",
1834                "3ad78e726c1ec02b7ebfe92b23d9ec34",
1835            ),
1836            (
1837                "00000000000000000000000000000000",
1838                "c0000000000000000000000000000000",
1839                "aae5939c8efdf2f04e60b9fe7117b2c2",
1840            ),
1841            (
1842                "00000000000000000000000000000000",
1843                "e0000000000000000000000000000000",
1844                "f031d4d74f5dcbf39daaf8ca3af6e527",
1845            ),
1846            (
1847                "00000000000000000000000000000000",
1848                "f0000000000000000000000000000000",
1849                "96d9fd5cc4f07441727df0f33e401a36",
1850            ),
1851            (
1852                "00000000000000000000000000000000",
1853                "f8000000000000000000000000000000",
1854                "30ccdb044646d7e1f3ccea3dca08b8c0",
1855            ),
1856            (
1857                "00000000000000000000000000000000",
1858                "fc000000000000000000000000000000",
1859                "16ae4ce5042a67ee8e177b7c587ecc82",
1860            ),
1861            (
1862                "00000000000000000000000000000000",
1863                "fe000000000000000000000000000000",
1864                "b6da0bb11a23855d9c5cb1b4c6412e0a",
1865            ),
1866            (
1867                "00000000000000000000000000000000",
1868                "ff000000000000000000000000000000",
1869                "db4f1aa530967d6732ce4715eb0ee24b",
1870            ),
1871        ];
1872        for (k, p, c) in v {
1873            kat128(k, p, c);
1874        }
1875    }
1876
1877    // ── Compile-time table spot-checks (verify GF arithmetic) ────────────────
1878    #[test]
1879    fn te0_spot_check() {
1880        // SBOX[0]=0x63; xtime(0x63)=0xc6; mul3(0x63)=0xc6^0x63=0xa5
1881        // TE0[0] = 0xc6_63_63_a5
1882        assert_eq!(TE0[0], 0xc663_63a5);
1883        // SBOX[1]=0x7c; xtime(0x7c)=0xf8; mul3(0x7c)=0xf8^0x7c=0x84
1884        assert_eq!(TE0[1], 0xf87c_7c84);
1885    }
1886
1887    #[test]
1888    fn td0_spot_check() {
1889        // INV_SBOX[0]=0x52; mul14=0x51, mul9=0xf4, mul13=0xa7, mul11=0x50
1890        assert_eq!(TD0[0], 0x51f4_a750);
1891    }
1892
1893    #[test]
1894    fn aes128_matches_openssl_ecb() {
1895        let key_hex = "000102030405060708090a0b0c0d0e0f";
1896        let pt_hex = "00112233445566778899aabbccddeeff";
1897        let Some(expected) =
1898            crate::test_utils::run_openssl_enc("-aes-128-ecb", key_hex, None, &parse::<16>(pt_hex))
1899        else {
1900            return;
1901        };
1902
1903        let cipher = Aes128::new(&parse(key_hex));
1904        assert_eq!(
1905            cipher.encrypt_block(&parse(pt_hex)).as_slice(),
1906            expected.as_slice()
1907        );
1908    }
1909}