use crate::field::fan_paar::{fp8, fp16};
use crate::field::{gf8b, gf8d, gf16};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ScaleTable {
pub coeff: gf8b::Elem,
pub lo: [u8; 16],
pub hi: [u8; 16],
}
impl ScaleTable {
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub const fn new(coeff: gf8b::Elem) -> Self {
let mut lo = [0u8; 16];
let mut hi = [0u8; 16];
let mut i = 0;
while i < 16 {
lo[i] = gf8b::Elem(i as u8).mul(coeff).0;
hi[i] = gf8b::Elem((i as u8) << 4).mul(coeff).0;
i += 1;
}
Self { coeff, lo, hi }
}
}
static SCALE_TABLE_BANK: [ScaleTable; 256] = build_bank();
#[allow(clippy::cast_possible_truncation)]
const fn build_bank() -> [ScaleTable; 256] {
let mut bank = [ScaleTable::new(gf8b::Elem(0)); 256];
let mut i = 0;
while i < 256 {
bank[i] = ScaleTable::new(gf8b::Elem(i as u8));
i += 1;
}
bank
}
#[inline]
#[must_use]
pub fn scale_table(coeff: gf8b::Elem) -> &'static ScaleTable {
&SCALE_TABLE_BANK[coeff.0 as usize]
}
static SCALE_TABLE_BANK_8D: [ScaleTable; 256] = build_bank_8d();
#[allow(clippy::cast_possible_truncation)]
const fn build_bank_8d() -> [ScaleTable; 256] {
let mut bank = [ScaleTable {
coeff: gf8b::Elem(0),
lo: [0; 16],
hi: [0; 16],
}; 256];
let mut i = 0;
while i < 256 {
let c = gf8d::Elem(i as u8);
let mut lo = [0u8; 16];
let mut hi = [0u8; 16];
let mut j = 0;
while j < 16 {
lo[j] = gf8d::Elem(j as u8).mul(c).0;
hi[j] = gf8d::Elem((j as u8) << 4).mul(c).0;
j += 1;
}
bank[i] = ScaleTable {
coeff: gf8b::Elem(i as u8),
lo,
hi,
};
i += 1;
}
bank
}
#[inline]
#[must_use]
pub fn scale_table_8d(coeff: gf8d::Elem) -> &'static ScaleTable {
&SCALE_TABLE_BANK_8D[coeff.0 as usize]
}
#[allow(dead_code)]
static AFFINE_BANK_8B: [u64; 256] = build_affine_bank_8b();
#[allow(clippy::cast_possible_truncation)]
#[allow(dead_code)]
const fn build_affine_bank_8b() -> [u64; 256] {
let mut bank = [0u64; 256];
let mut i = 0;
while i < 256 {
let c = gf8b::Elem(i as u8);
let mut map = 0u64;
let mut r = 0u8;
while r < 8 {
let mut row = 0u8;
let mut k = 0u8;
while k < 8 {
let column = c.mul(gf8b::Elem(1 << k)).0;
row |= ((column >> (7 - r)) & 1) << k;
k += 1;
}
map |= (row as u64) << (8 * r);
r += 1;
}
bank[i] = map;
i += 1;
}
bank
}
#[inline]
#[must_use]
#[allow(dead_code)]
pub fn affine_8b(coeff: gf8b::Elem) -> u64 {
AFFINE_BANK_8B[coeff.0 as usize]
}
static AFFINE_BANK_8D: [u64; 256] = build_affine_bank_8d();
#[allow(clippy::cast_possible_truncation)]
const fn build_affine_bank_8d() -> [u64; 256] {
let mut bank = [0u64; 256];
let mut i = 0;
while i < 256 {
let c = gf8d::Elem(i as u8);
let mut map = 0u64;
let mut r = 0u8;
while r < 8 {
let mut row = 0u8;
let mut k = 0u8;
while k < 8 {
let column = c.mul(gf8d::Elem(1 << k)).0;
row |= ((column >> (7 - r)) & 1) << k;
k += 1;
}
map |= (row as u64) << (8 * r);
r += 1;
}
bank[i] = map;
i += 1;
}
bank
}
#[inline]
#[must_use]
pub fn affine_8d(coeff: gf8d::Elem) -> u64 {
AFFINE_BANK_8D[coeff.0 as usize]
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TowerCoeff {
pub coeff: gf16::Elem,
pub same: u16,
pub cross: u16,
}
impl TowerCoeff {
#[inline]
#[must_use]
pub const fn new(coeff: gf16::Elem) -> Self {
let (c0, c1) = coeff.to_components();
let same = u16::from_le_bytes([c0.0, c0.add(c1).0]);
let cross = u16::from_le_bytes([gf16::DELTA.mul(c1).0, c1.0]);
Self { coeff, same, cross }
}
#[inline]
#[must_use]
pub const fn factors(self) -> [gf8b::Elem; 4] {
let [s0, s1] = self.same.to_le_bytes();
let [x0, x1] = self.cross.to_le_bytes();
[
gf8b::Elem(s0),
gf8b::Elem(s1),
gf8b::Elem(x0),
gf8b::Elem(x1),
]
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TowerTables {
pub coeff: gf16::Elem,
pub factors: [ScaleTable; 4],
}
impl TowerTables {
#[inline]
#[must_use]
pub fn new(coeff: gf16::Elem) -> Self {
let f = TowerCoeff::new(coeff).factors();
Self {
coeff,
factors: [
*scale_table(f[0]),
*scale_table(f[1]),
*scale_table(f[2]),
*scale_table(f[3]),
],
}
}
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FpScaleTable {
pub lo: [u8; 16],
pub hi: [u8; 16],
}
impl FpScaleTable {
#[must_use]
#[allow(clippy::cast_possible_truncation)]
#[allow(dead_code)]
pub const fn new(coeff: fp8::Elem) -> Self {
let mut lo = [0u8; 16];
let mut hi = [0u8; 16];
let mut i = 0;
while i < 16 {
lo[i] = fp8::Elem(i as u8).mul(coeff).0;
hi[i] = fp8::Elem((i as u8) << 4).mul(coeff).0;
i += 1;
}
Self { lo, hi }
}
}
#[allow(dead_code)]
static FP_SCALE_TABLE_BANK: [FpScaleTable; 256] = build_fp_bank();
#[allow(clippy::cast_possible_truncation)]
#[allow(dead_code)]
const fn build_fp_bank() -> [FpScaleTable; 256] {
let mut bank = [FpScaleTable::new(fp8::Elem(0)); 256];
let mut i = 0;
while i < 256 {
bank[i] = FpScaleTable::new(fp8::Elem(i as u8));
i += 1;
}
bank
}
#[inline]
#[must_use]
#[allow(dead_code)]
pub fn fp_scale_table(coeff: fp8::Elem) -> &'static FpScaleTable {
&FP_SCALE_TABLE_BANK[coeff.0 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
}
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FpTowerTables {
pub coeff: fp16::Elem,
pub factors: [FpScaleTable; 4],
}
impl FpTowerTables {
#[inline]
#[must_use]
#[allow(dead_code)]
pub fn new(coeff: fp16::Elem) -> 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::Elem> 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::gf8b::Elem as E8;
#[test]
fn nibble_tables_reconstruct_the_product() {
for c in 0..=u8::MAX {
let table = scale_table(E8(c));
for x in 0..=u8::MAX {
let split = table.lo[(x & 0x0f) as usize] ^ table.hi[(x >> 4) as usize];
assert_eq!(split, E8(x).mul(E8(c)).0, "coeff {c:#04x} value {x:#04x}");
}
}
}
#[test]
#[allow(clippy::cast_possible_truncation)]
fn affine_bank_matches_the_instruction_model() {
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
}
for c in 0..=u8::MAX {
let aes_map = affine_8b(gf8b::Elem(c));
let rs_map = affine_8d(gf8d::Elem(c));
for x in 0..=u8::MAX {
assert_eq!(
apply(aes_map, x),
gf8b::Elem(c).mul(gf8b::Elem(x)).0,
"0x11B coeff {c:#04x} value {x:#04x}"
);
assert_eq!(
apply(rs_map, x),
gf8d::Elem(c).mul(gf8d::Elem(x)).0,
"0x11D coeff {c:#04x} value {x:#04x}"
);
}
}
}
#[test]
fn tower_factors_reconstruct_the_product() {
for coeff in [0u16, 1, 0x0108, 0x2000, 0xffff, 0x1234] {
let coeff = gf16::Elem(coeff);
let tc = TowerCoeff::new(coeff);
let [f0, f1, f2, f3] = tc.factors();
for value in [0u16, 1, 0x00ff, 0xff00, 0xbeef] {
let value = gf16::Elem(value);
let [a, b] = value.to_bytes();
let (a, b) = (E8(a), E8(b));
let even = f0.mul(a).add(f2.mul(b));
let odd = f1.mul(b).add(f3.mul(a));
let got = gf16::Elem::from_bytes([even.0, odd.0]);
assert_eq!(got, value.mul(coeff), "{value:?} * {coeff:?}");
}
}
}
#[test]
fn const_built_banks_match_their_runtime_builders() {
assert_eq!(build_bank(), SCALE_TABLE_BANK);
assert_eq!(build_bank_8d(), SCALE_TABLE_BANK_8D);
assert_eq!(build_affine_bank_8d(), AFFINE_BANK_8D);
assert_eq!(build_fp_bank(), FP_SCALE_TABLE_BANK);
for c in 0..=u8::MAX {
let runtime = ScaleTable::new(E8(c));
assert_eq!(&runtime, scale_table(E8(c)), "0x11B coeff {c:#04x}");
let runtime = FpScaleTable::new(fp8::Elem(c));
assert_eq!(&runtime, fp_scale_table(fp8::Elem(c)), "fp8 coeff {c:#04x}");
}
}
#[test]
fn experimental_affine_bank_matches_its_runtime_builder() {
assert_eq!(build_affine_bank_8b(), AFFINE_BANK_8B);
}
}