#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
use crate::field::binary::tower::FanPaar8;
use crate::field::binary::tower::{FanPaar16, Rijndael16, Tower, TowerSpec};
use crate::field::binary::{AES, Binary, BinaryDegree, BinaryRepr, Polynomial};
use crate::field::{Cantor, Elem, Normal};
const fn xtime_mul(poly: u128, mut a: u8, b: u8) -> u8 {
let reduction = (poly & 0xFF) as u8;
let mut acc: u8 = 0;
let mut i = 0;
while i < 8 {
if (b >> i) & 1 == 1 {
acc ^= a;
}
let overflow = a & 0x80 != 0;
a <<= 1;
if overflow {
a ^= reduction;
}
i += 1;
}
acc
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ScaleTable {
pub coeff: u8,
pub lo: [u8; 16],
pub hi: [u8; 16],
}
impl ScaleTable {
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub const fn new<const POLY: u128>(coeff: Elem<Binary<8, Polynomial<POLY>>>) -> Self {
let mut lo = [0u8; 16];
let mut hi = [0u8; 16];
let mut i = 0;
while i < 16 {
lo[i] = xtime_mul(POLY, i as u8, coeff.to_raw());
hi[i] = xtime_mul(POLY, (i as u8) << 4, coeff.to_raw());
i += 1;
}
Self {
coeff: coeff.to_raw(),
lo,
hi,
}
}
#[cfg(all(feature = "simd", any(target_arch = "aarch64", target_arch = "wasm32")))]
#[inline]
#[must_use]
pub const fn zeroed() -> Self {
Self {
coeff: 0,
lo: [0; 16],
hi: [0; 16],
}
}
}
#[must_use]
pub const fn build_affine_map<const POLY: u128>(coeff: Elem<Binary<8, Polynomial<POLY>>>) -> u64 {
let mut columns = [0u8; 8];
let mut k = 0;
while k < 8 {
columns[k] = xtime_mul(POLY, coeff.to_raw(), 1 << k);
k += 1;
}
linear_map(columns)
}
const fn linear_map(columns: [u8; 8]) -> u64 {
let mut map = 0u64;
let mut r = 0;
while r < 8 {
let mut row = 0u8;
let mut k = 0;
while k < 8 {
row |= ((columns[k] >> (7 - r)) & 1) << k;
k += 1;
}
map |= (row as u64) << (8 * r);
r += 1;
}
map
}
struct Bank<const POLY: u128>;
impl<const POLY: u128> Bank<POLY> {
const SCALE: &'static [ScaleTable; 256] = &build_bank::<POLY>();
const AFFINE: &'static [u64; 256] = &build_affine_bank::<POLY>();
}
pub(crate) trait ByteBanks: BinaryRepr<8> {
const SCALE: &'static [ScaleTable; 256];
#[cfg(all(
feature = "simd",
any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "wasm32"
)
))]
const REDUCTION_LOW: u8;
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
pub(crate) const AES_BANK: &[ScaleTable; 256] = Bank::<AES>::SCALE;
#[cfg(all(
feature = "simd",
any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "wasm32"
)
))]
pub(crate) const RIJNDAEL16_B: u8 = 0x20;
impl<const POLY: u128> ByteBanks for Polynomial<POLY> {
const SCALE: &'static [ScaleTable; 256] = Bank::<POLY>::SCALE;
#[cfg(all(
feature = "simd",
any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "wasm32"
)
))]
const REDUCTION_LOW: u8 = (POLY & 0xFF) as u8;
}
#[allow(clippy::cast_possible_truncation)]
const fn build_bank<const POLY: u128>() -> [ScaleTable; 256] {
let mut bank = [ScaleTable::new::<POLY>(Elem::<Binary<8, Polynomial<POLY>>>::ZERO); 256];
let mut i = 0;
while i < 256 {
bank[i] = ScaleTable::new::<POLY>(Elem::<Binary<8, Polynomial<POLY>>>::from_raw(i as u8));
i += 1;
}
bank
}
#[allow(clippy::cast_possible_truncation)]
const fn build_affine_bank<const POLY: u128>() -> [u64; 256] {
let mut bank = [0u64; 256];
let mut i = 0;
while i < 256 {
bank[i] = build_affine_map::<POLY>(Elem::<Binary<8, Polynomial<POLY>>>::from_raw(i as u8));
i += 1;
}
bank
}
#[allow(dead_code)]
pub(crate) static ZERO_TABLE: ScaleTable = ScaleTable {
coeff: 0,
lo: [0; 16],
hi: [0; 16],
};
#[allow(dead_code)]
#[inline]
#[must_use]
pub fn scale_table<const POLY: u128>(
coeff: Elem<Binary<8, Polynomial<POLY>>>,
) -> &'static ScaleTable {
&Bank::<POLY>::SCALE[coeff.to_raw() as usize]
}
#[allow(dead_code)]
#[inline]
#[must_use]
pub fn affine_map<const POLY: u128>(coeff: Elem<Binary<8, Polynomial<POLY>>>) -> u64 {
Bank::<POLY>::AFFINE[coeff.to_raw() as usize]
}
pub(crate) trait Gf8Data: BinaryRepr<8> {
const AES_NATIVE: bool;
const SCALE: &'static [ScaleTable; 256];
const AFFINE: &'static [u64; 256];
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
const ISOMORPHISM: Isomorphism;
#[allow(dead_code)]
#[cfg(all(
feature = "simd",
any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "wasm32"
)
))]
const REDUCTION_LOW: u8;
}
const DUMMY_AFFINE: [u64; 256] = [0u64; 256];
pub(crate) static DUMMY_SCALE: [ScaleTable; 256] = {
let mut bank = [ScaleTable {
coeff: 0,
lo: [0; 16],
hi: [0; 16],
}; 256];
let mut i = 0;
while i < 256 {
#[allow(clippy::cast_possible_truncation)]
let byte = i as u8;
bank[i].coeff = byte;
i += 1;
}
bank
};
impl<const POLY: u128> Gf8Data for Polynomial<POLY> {
const AES_NATIVE: bool = POLY == AES;
const SCALE: &'static [ScaleTable; 256] = Bank::<POLY>::SCALE;
const AFFINE: &'static [u64; 256] = Bank::<POLY>::AFFINE;
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
const ISOMORPHISM: Isomorphism = isomorphism::<POLY>();
#[cfg(all(
feature = "simd",
any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "wasm32"
)
))]
const REDUCTION_LOW: u8 = Polynomial::<POLY>::REDUCTION_LOW;
}
impl<const P: u128, const E: u8> Gf8Data for Normal<P, E> {
const AES_NATIVE: bool = false;
const SCALE: &'static [ScaleTable; 256] = &DUMMY_SCALE;
const AFFINE: &'static [u64; 256] = &DUMMY_AFFINE;
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
const ISOMORPHISM: Isomorphism = Isomorphism {
forward: 0x0102_0408_1020_4080,
inverse: 0x0102_0408_1020_4080,
};
#[cfg(all(
feature = "simd",
any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "wasm32"
)
))]
const REDUCTION_LOW: u8 = 0;
}
impl<S: TowerSpec> Gf8Data for Tower<S>
where
S::Base: BinaryDegree<4>,
{
const AES_NATIVE: bool = false;
const SCALE: &'static [ScaleTable; 256] = &DUMMY_SCALE;
const AFFINE: &'static [u64; 256] = &DUMMY_AFFINE;
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
const ISOMORPHISM: Isomorphism = Isomorphism {
forward: 0x0102_0408_1020_4080,
inverse: 0x0102_0408_1020_4080,
};
#[cfg(all(
feature = "simd",
any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "wasm32"
)
))]
const REDUCTION_LOW: u8 = 0;
}
impl<const P: u128, const SEED: u8> Gf8Data for Cantor<P, SEED> {
const AES_NATIVE: bool = false;
const SCALE: &'static [ScaleTable; 256] = &DUMMY_SCALE;
const AFFINE: &'static [u64; 256] = &DUMMY_AFFINE;
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
const ISOMORPHISM: Isomorphism = Isomorphism {
forward: 0x0102_0408_1020_4080,
inverse: 0x0102_0408_1020_4080,
};
#[cfg(all(
feature = "simd",
any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "wasm32"
)
))]
const REDUCTION_LOW: u8 = 0;
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Isomorphism {
pub forward: u64,
pub inverse: u64,
}
#[allow(dead_code)]
#[inline]
#[must_use]
pub const fn isomorphism_to_aes<const POLY: u128>() -> Isomorphism {
isomorphism::<POLY>()
}
#[allow(dead_code)]
const fn isomorphism<const POLY: u128>() -> Isomorphism {
let to_aes = root_powers::<AES>(POLY);
let from_aes = inverse_columns(to_aes);
Isomorphism {
forward: linear_map(to_aes),
inverse: linear_map(from_aes),
}
}
#[allow(dead_code)]
const fn inverse_columns(columns: [u8; 8]) -> [u8; 8] {
let mut inverse = [0u8; 8];
let mut basis = 0;
while basis < 8 {
let mut candidate = 0u16;
loop {
assert!(candidate < 256, "a field isomorphism is bijective");
let value = candidate.to_le_bytes()[0];
let mut image = 0u8;
let mut bit = 0;
while bit < 8 {
if (value >> bit) & 1 != 0 {
image ^= columns[bit];
}
bit += 1;
}
if image == 1 << basis {
inverse[basis] = value;
break;
}
candidate += 1;
}
basis += 1;
}
inverse
}
#[allow(dead_code)]
#[allow(clippy::cast_possible_truncation)]
const fn root_powers<const FIELD: u128>(poly: u128) -> [u8; 8] {
let mut candidate = 2u32;
while candidate < 256 {
let r = candidate.to_le_bytes()[0];
let mut value = 0u8;
let mut bit = 8;
loop {
value = xtime_mul(FIELD, value, r);
if (poly >> bit) & 1 == 1 {
value ^= 1;
}
if bit == 0 {
break;
}
bit -= 1;
}
if value == 0 {
let mut powers = [0u8; 8];
let mut power = 1u8;
let mut k = 0;
while k < 8 {
powers[k] = power;
power = xtime_mul(FIELD, power, r);
k += 1;
}
return powers;
}
candidate += 1;
}
panic!("an irreducible degree-8 polynomial splits in every GF(2^8)")
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TowerCoeff {
pub coeff: u16,
pub same: u16,
pub cross: u16,
}
impl TowerCoeff {
#[inline]
#[must_use]
pub const fn new(coeff: Elem<Binary<16, Tower<Rijndael16>>>) -> Self {
#[allow(clippy::cast_possible_truncation)]
let con =
Elem::<Binary<8, Polynomial<AES>>>::from_raw(Rijndael16::CONSTANT_COEFFICIENT as u8);
let (c0, c1) = coeff.to_components();
let same_high = c0.add(c1);
let same = u16::from_le_bytes([c0.to_raw(), same_high.to_raw()]);
let cross = u16::from_le_bytes([con.mul(c1).to_raw(), c1.to_raw()]);
Self {
coeff: coeff.to_raw(),
same,
cross,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TowerTables {
pub coeff: u16,
pub factors: [ScaleTable; 4],
}
impl TowerTables {
#[inline]
#[must_use]
pub fn new(coeff: Elem<Binary<16, Tower<Rijndael16>>>) -> Self {
let compact = TowerCoeff::new(coeff);
let [f0, f1, f2, f3] = compact.factor_bytes();
let bank = <Polynomial<AES> as ByteBanks>::SCALE;
Self {
coeff: compact.coeff,
factors: [
bank[f0 as usize],
bank[f1 as usize],
bank[f2 as usize],
bank[f3 as usize],
],
}
}
#[cfg(all(feature = "simd", any(target_arch = "aarch64", target_arch = "wasm32")))]
#[inline]
#[must_use]
pub const fn zero() -> Self {
Self {
coeff: 0,
factors: [ScaleTable::zeroed(); 4],
}
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
#[inline]
#[must_use]
pub const fn compact(&self) -> TowerCoeff {
let [s0, s1] = self.same_bytes();
let [x0, x1] = self.cross_bytes();
TowerCoeff {
coeff: self.coeff,
same: u16::from_le_bytes([s0, s1]),
cross: u16::from_le_bytes([x0, x1]),
}
}
#[inline]
#[must_use]
pub const fn is_zero(&self) -> bool {
self.coeff == 0
}
#[inline]
#[must_use]
pub fn is_one(&self) -> bool {
self.factors[3].coeff == 0
&& self.factors[0].lo == IDENTITY_LO
&& self.factors[0].hi == IDENTITY_HI
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
const fn same_bytes(&self) -> [u8; 2] {
[self.factors[0].coeff, self.factors[1].coeff]
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
const fn cross_bytes(&self) -> [u8; 2] {
[self.factors[2].coeff, self.factors[3].coeff]
}
}
const IDENTITY_LO: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
const IDENTITY_HI: [u8; 16] = [
0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240,
];
impl TowerCoeff {
#[inline]
#[must_use]
pub const fn factor_bytes(self) -> [u8; 4] {
let [s0, s1] = self.same.to_le_bytes();
let [x0, x1] = self.cross.to_le_bytes();
[s0, s1, x0, x1]
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FpScaleTable {
pub lo: [u8; 16],
pub hi: [u8; 16],
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
impl FpScaleTable {
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub const fn new(coeff: Elem<Binary<8, Tower<FanPaar8>>>) -> Self {
use crate::field::binary::tower::fp_multiply;
let mut lo = [0u8; 16];
let mut hi = [0u8; 16];
let mut i = 0;
while i < 16 {
lo[i] = fp_multiply(i as u64, coeff.to_raw() as u64, 8) as u8;
hi[i] = fp_multiply((i as u64) << 4, coeff.to_raw() as u64, 8) as u8;
i += 1;
}
Self { lo, hi }
}
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
static FP_SCALE_TABLE_BANK: [FpScaleTable; 256] = build_fp_bank();
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
#[allow(clippy::cast_possible_truncation)]
const fn build_fp_bank() -> [FpScaleTable; 256] {
let mut bank = [FpScaleTable::new(Elem::<Binary<8, Tower<FanPaar8>>>::from_raw(0)); 256];
let mut i = 0;
while i < 256 {
bank[i] = FpScaleTable::new(Elem::<Binary<8, Tower<FanPaar8>>>::from_raw(i as u8));
i += 1;
}
bank
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
#[inline]
#[must_use]
pub fn fp_scale_table(coeff: Elem<Binary<8, Tower<FanPaar8>>>) -> &'static FpScaleTable {
&FP_SCALE_TABLE_BANK[coeff.to_raw() as usize]
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
pub(crate) trait NibbleFactors {
fn lo(&self, i: usize) -> &[u8; 16];
fn hi(&self, i: usize) -> &[u8; 16];
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
impl NibbleFactors for TowerTables {
#[inline]
fn lo(&self, i: usize) -> &[u8; 16] {
&self.factors[i].lo
}
#[inline]
fn hi(&self, i: usize) -> &[u8; 16] {
&self.factors[i].hi
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FpTowerTables {
pub coeff: Elem<Binary<16, Tower<FanPaar16>>>,
pub factors: [FpScaleTable; 4],
}
impl FpTowerTables {
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
#[inline]
#[must_use]
pub fn new(coeff: Elem<Binary<16, Tower<FanPaar16>>>) -> Self {
let (c0, c1) = coeff.to_components();
let b = c0.add(c1.mul_alpha());
let [f0, f1, f2, f3] = [c0, b, c1, c1];
Self {
coeff,
factors: [
*fp_scale_table(f0),
*fp_scale_table(f1),
*fp_scale_table(f2),
*fp_scale_table(f3),
],
}
}
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
impl NibbleFactors for FpTowerTables {
#[inline]
fn lo(&self, i: usize) -> &[u8; 16] {
&self.factors[i].lo
}
#[inline]
fn hi(&self, i: usize) -> &[u8; 16] {
&self.factors[i].hi
}
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Tower2Coeff<E> {
pub same: [E; 2],
pub cross: [E; 2],
}
impl<E: crate::field::FieldElem> Tower2Coeff<E> {
#[inline]
#[must_use]
#[allow(dead_code)]
pub fn derive(c0: E, c1: E, delta: E) -> Self {
Self {
same: [c0, c0.add(c1)],
cross: [delta.mul(c1), c1],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::field::RS;
type E8 = Elem<Binary<8, Polynomial<AES>>>;
fn check_bank<const P: u128>() {
let runtime_scale = build_bank::<P>();
let runtime_affine = build_affine_bank::<P>();
for raw in 0..=u8::MAX {
let coeff = Elem::<Binary<8, Polynomial<P>>>::from_raw(raw);
for x in 0..=u8::MAX {
let product = Elem::<Binary<8, Polynomial<P>>>::from_raw(x)
.mul(coeff)
.to_raw();
for table in [
&Bank::<P>::SCALE[usize::from(raw)],
&runtime_scale[usize::from(raw)],
] {
let split = table.lo[(x & 0x0f) as usize] ^ table.hi[(x >> 4) as usize];
assert_eq!(split, product, "{coeff:?} nibbles at {x:#04x}");
}
for map in [affine_map::<P>(coeff), runtime_affine[usize::from(raw)]] {
assert_eq!(apply(map, x), product, "{coeff:?} affine at {x:#04x}");
}
}
}
for iso in [isomorphism_to_aes::<P>(), isomorphism::<P>()] {
for a in 0..=u8::MAX {
assert_eq!(
apply(iso.inverse, apply(iso.forward, a)),
a,
"φ⁻¹φ at {a:#04x}"
);
for b in 0..=u8::MAX {
let product = Elem::<Binary<8, Polynomial<P>>>::from_raw(a)
.mul(Elem::<Binary<8, Polynomial<P>>>::from_raw(b))
.to_raw();
let conjugated = apply(
iso.inverse,
E8::from_raw(apply(iso.forward, a))
.mul(E8::from_raw(apply(iso.forward, b)))
.to_raw(),
);
assert_eq!(conjugated, product, "φ-conjugated {a:#04x} * {b:#04x}");
}
}
}
}
#[allow(clippy::cast_possible_truncation)]
fn apply(map: u64, x: u8) -> u8 {
let mut out = 0u8;
for b in 0..8u8 {
let row = (map >> (8 * (7 - b))) as u8;
let parity = (row & x).count_ones() & 1;
out |= u8::try_from(parity).unwrap() << b;
}
out
}
#[test]
fn banks_reproduce_every_product_under_the_instruction_model() {
check_bank::<AES>();
check_bank::<RS>();
check_bank::<0x12D>();
check_bank::<0x187>();
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
#[test]
fn fan_paar_nibble_tables_match_the_tower_recurrence() {
use crate::field::binary::tower::fp_multiply;
let runtime = build_fp_bank();
for bank in [&runtime, &FP_SCALE_TABLE_BANK] {
for c in 0..=u8::MAX {
let table = &bank[usize::from(c)];
for x in 0..=u8::MAX {
let product = fp_multiply(u64::from(c), u64::from(x), 8);
let split = table.lo[usize::from(x & 0x0f)] ^ table.hi[usize::from(x >> 4)];
assert_eq!(u64::from(split), product, "{c:#04x} * {x:#04x}");
}
}
}
}
#[test]
fn the_aes_isomorphism_is_the_identity() {
let iso = isomorphism_to_aes::<AES>();
for x in 0..=u8::MAX {
assert_eq!(apply(iso.forward, x), x);
assert_eq!(apply(iso.inverse, x), x);
}
}
#[test]
fn the_reed_solomon_isomorphism_is_the_frozen_involution() {
let iso = isomorphism_to_aes::<RS>();
assert_eq!(iso.forward, 0xffaa_cc88_f0a0_c080);
assert_eq!(iso.inverse, iso.forward);
}
#[test]
#[should_panic(expected = "splits in every GF(2^8)")]
fn root_search_rejects_a_polynomial_without_a_root() {
let rootless = core::hint::black_box(0x147u128);
let _ = root_powers::<AES>(rootless);
}
#[test]
fn tower_factors_reconstruct_the_product() {
use crate::field::binary::tower::{Rijndael16 as TowerRijndael16, Tower};
type TowerGf16 = crate::field::Binary<16, Tower<TowerRijndael16>>;
for coeff in [0u16, 1, 0x0108, 0x2000, 0xffff, 0x1234] {
let coeff = Elem::<TowerGf16>::from_raw(coeff);
let tc = TowerCoeff::new(coeff);
let [f0, f1, f2, f3] = tc.factor_bytes();
let (f0, f1, f2, f3) = (
E8::from_raw(f0),
E8::from_raw(f1),
E8::from_raw(f2),
E8::from_raw(f3),
);
for value in [0u16, 1, 0x00ff, 0xff00, 0xbeef] {
let value = Elem::<TowerGf16>::from_raw(value);
let [a, b] = value.to_bytes();
let (a, b) = (E8::from_raw(a), E8::from_raw(b));
let even = f0.mul(a).add(f2.mul(b));
let odd = f1.mul(b).add(f3.mul(a));
let got = Elem::<TowerGf16>::from_bytes([even.to_raw(), odd.to_raw()]);
assert_eq!(got, value.mul(coeff), "{value:?} * {coeff:?}");
}
}
let _ = TowerRijndael16::NAME;
}
}