python-aes-sbox-gf28-inverse
The S-box is S[x] = affine(ginv(x)). Two classic bugs are addressed explicitly:
0x00 (0 has no inverse; AES defines 0 → 0), never a division-by-zero or None.a << 1 creates a degree-8 term. Reduce by XORing with 0x1B (x⁸ ≡ x⁴+x³+x+1 mod 0x11B) — and check the bit before shifting, or the reduction fires too late.REDUC = 0x11B # x^8 + x^4 + x^3 + x + 1
def gmul(a: int, b: int) -> int:
"""GF(2^8) multiply, reducing mod 0x11B."""
p = 0
while b:
if b & 1:
p ^= a
b >>= 1
hi = a & 0x80 # degree-8 overflow about to happen
a = (a << 1) & 0xFF
if hi:
a ^= 0x1B # x^8 == 0x1B (mod 0x11B)
return p
def gpow(a: int, e: int) -> int:
r, base = 1, a
while e:
if e & 1:
r = gmul(r, base)
base = gmul(base, base)
e >>= 1
return r
def ginv(a: int) -> int:
"""Multiplicative inverse; Fermat: a^254. 0x00 -> 0x00 (not div-by-zero)."""
if a == 0:
return 0
return gpow(a, 254) # a^(2^8 - 2)
def _rotl8(x: int, n: int) -> int:
return ((x << n) | (x >> (8 - n))) & 0xFF
def affine(x: int) -> int:
"""b'_i = b_i ^ b_(i+4) ^ b_(i+5) ^ b_(i+6) ^ b_(i+7) ^ c_i, c = 0x63."""
return (x ^ _rotl8(x, 1) ^ _rotl8(x, 2) ^ _rotl8(x, 3) ^ _rotl8(x, 4) ^ 0x63) & 0xFF
def sbox() -> list[int]:
return [affine(ginv(b)) for b in range(256)]
The affine rotation trick: b' = b ^ rotl(b,1) ^ rotl(b,2) ^ rotl(b,3) ^ rotl(b,4) ^ 0x63, which implements the 8×8 circulant bit-matrix plus constant.
Ran `/tmp/aes_sbox.py` under Python 3.14 (pure Python, 3.11-compatible) — **523/523 tests passed**: | Check | Result | |---|---| | `ginv(0x00) == 0x00` (edge case) | ✓ | | `S[0x00] == 0x63` (boundary) | ✓ | | `S[0xFF] == 0x16` (boundary) | ✓ | | Overflow: `gmul(0x7F, 2) == 0xFE`, `gmul(0x80, 2) == 0x1B`, `gmul(0xFF, 2) == 0xE5` | ✓ | | Order of x mod 0x11B = 51, `2^255 == 1` (reduction stays in field) | ✓ | | `gmul` matches an independent reference (reduce-after-shift) on all 65,536 pairs | ✓ | | `x * ginv(x) == 1` for all 255 nonzero bytes; `ginv` is an involution | ✓ | | S-box is a bijection of 0..255 | ✓ | | **Full 256-entry match vs. published FIPS-197 table** | ✓ | Rows 0 and F: `63 7c 77 7b f2 6b 6f c5 30 01 67 2b fe d7 ab 76` and `8c a1 89 0d bf e6 42 68 41 99 2d 0f b0 54 bb 16` — matching the standard. One notable finding during testing: `0x11B` is **irreducible but not primitive** — the class of `x` has order 51, not 255. A naive "x is a generator" sanity test fails even with correct code, so verification was done via the inverse property, the independent reduction cross-check, and the full published table instead.
{"model": "deepseek-v4-flash", "problem_class": "python-aes-sbox-gf28-inverse", "result": "passed", "tests": 523}