//! AES inverse tables (inverse S-box + InvMixColumns "Td" T-tables), generated //! at compile time from GF(2^8) arithmetic rather than embedded as a transcribed //! blob. These are the standard AES *decryption* tables — not proprietary data — //! so we derive them. The generated bytes are verified byte-identical to the //! original hand-transcribed arrays (CRC32-locked in the test at the bottom). //! //! Each table is 1024 bytes = 256 u32 little-endian, read by //! `primitives::aes_round` via `get_u32(&TABLE, x * 4)`. The byte layout matches //! the original exactly, so `aes_round` is unchanged: //! SBOX[x] = invsbox(x) broadcast to 4 bytes //! COLUMMIX1[x] = [0b*s, 0d*s, 09*s, 0e*s], s = invsbox(x) (Td0, this byte order) //! COLUMMIX2/3/4 = COLUMMIX1's 4-byte group rotated left by 1 / 2 / 3 bytes //! //! Generated the same way as the existing `const fn` CRC-table generation in //! `crc32.rs`. /// GF(2^8) multiply with the AES reduction polynomial (x^8 + x^4 + x^3 + x + 1). const fn gf_mul(mut a: u8, mut b: u8) -> u8 { let mut p: u8 = 0; let mut i = 0; while i < 8 { if b & 1 != 0 { p ^= a; } let hi = a & 0x80; a <<= 1; if hi != 0 { a ^= 0x1B; } b >>= 1; i += 1; } p } /// The AES inverse S-box, derived from the multiplicative inverse in GF(2^8) /// followed by inverting the forward S-box's affine transform. const fn inv_sbox() -> [u8; 256] { // Multiplicative inverse: inv[a] = b such that a*b == 1 (inv[0] stays 0). let mut inv = [0u8; 256]; let mut a = 1usize; while a < 256 { let mut b = 1usize; while b < 256 { if gf_mul(a as u8, b as u8) == 1 { inv[a] = b as u8; break; } b += 1; } a += 1; } // Forward S-box: affine transform over the inverse. let mut sb = [0u8; 256]; let mut i = 0usize; while i < 256 { let mut x = inv[i]; let mut s = inv[i]; let mut r = 0; while r < 4 { s = s.rotate_left(1); x ^= s; r += 1; } sb[i] = x ^ 0x63; i += 1; } // Inverse S-box is the inverse permutation of the forward S-box. let mut isb = [0u8; 256]; let mut i = 0usize; while i < 256 { isb[sb[i] as usize] = i as u8; i += 1; } isb } /// The five generated tables (each 1024 bytes = 256 u32 LE). struct AesTables { cm1: [u8; 1024], cm2: [u8; 1024], cm3: [u8; 1024], cm4: [u8; 1024], sbox: [u8; 1024], } /// Build all five tables in one compile-time pass. const fn build_tables() -> AesTables { let isb = inv_sbox(); let mut cm1 = [0u8; 1024]; let mut cm2 = [0u8; 1024]; let mut cm3 = [0u8; 1024]; let mut cm4 = [0u8; 1024]; let mut sbox = [0u8; 1024]; let mut x = 0usize; while x < 256 { let s = isb[x]; // SBOX: invsbox(x) broadcast to all four lanes. let mut j = 0; while j < 4 { sbox[x * 4 + j] = s; j += 1; } // COLUMMIX1 lane bytes; CM2/3/4 are byte-rotations of the same four. let b = [ gf_mul(0x0b, s), gf_mul(0x0d, s), gf_mul(0x09, s), gf_mul(0x0e, s), ]; let mut j = 0; while j < 4 { cm1[x * 4 + j] = b[j]; cm2[x * 4 + j] = b[(j + 1) % 4]; cm3[x * 4 + j] = b[(j + 2) % 4]; cm4[x * 4 + j] = b[(j + 3) % 4]; j += 1; } x += 1; } AesTables { cm1, cm2, cm3, cm4, sbox, } } const TABLES: AesTables = build_tables(); pub static COLUMMIX1: [u8; 1024] = TABLES.cm1; pub static COLUMMIX2: [u8; 1024] = TABLES.cm2; pub static COLUMMIX3: [u8; 1024] = TABLES.cm3; pub static COLUMMIX4: [u8; 1024] = TABLES.cm4; pub static SBOX: [u8; 1024] = TABLES.sbox; #[cfg(test)] mod tests { use super::*; /// Lock the generated tables to the original hand-transcribed bytes. The /// CRC32 oracles were computed from the previously-committed `tables.rs` /// arrays; any drift in the generator (or the GF math) fails here before it /// can reach the byte-identical corpus goldens. #[test] fn generated_tables_match_committed_bytes() { assert_eq!(COLUMMIX1.len(), 1024); assert_eq!(crate::crc32::compute(&COLUMMIX1), 0x7e8d_5d5f); assert_eq!(crate::crc32::compute(&COLUMMIX2), 0xfcc4_acfc); assert_eq!(crate::crc32::compute(&COLUMMIX3), 0x637a_f0cd); assert_eq!(crate::crc32::compute(&COLUMMIX4), 0x1e7b_c381); assert_eq!(crate::crc32::compute(&SBOX), 0x10fd_6dc1); // Spot-check the first dword of each (matches the original first row). assert_eq!(&COLUMMIX1[..4], &[0x50, 0xa7, 0xf4, 0x51]); assert_eq!(&COLUMMIX2[..4], &[0xa7, 0xf4, 0x51, 0x50]); assert_eq!(&COLUMMIX3[..4], &[0xf4, 0x51, 0x50, 0xa7]); assert_eq!(&COLUMMIX4[..4], &[0x51, 0x50, 0xa7, 0xf4]); assert_eq!(&SBOX[..4], &[0x52, 0x52, 0x52, 0x52]); } }