use core::fmt;
use super::description::{BinaryDescription, ByteLogExp};
use super::poly::{AES, Polynomial};
use super::{Binary, BinaryDegree, BinaryField, BinaryRepr, private};
use crate::field::{Elem, Field, FieldBuffer, HasGenerator};
#[allow(private_bounds)]
pub trait TowerSpec: Copy + 'static {
type Base: BinaryField;
const LINEAR_COEFFICIENT: u64;
const CONSTANT_COEFFICIENT: u64;
const NAME: &'static str;
}
#[allow(private_bounds)]
pub trait TowerGeneratorSpec: TowerSpec {
const GENERATOR: u64;
}
pub struct Tower<S: TowerSpec>(core::marker::PhantomData<fn() -> S>);
impl<S: TowerSpec> private::Sealed for Tower<S> {}
#[allow(clippy::expl_impl_clone_on_copy)]
impl<S: TowerSpec> Clone for Tower<S> {
#[inline]
fn clone(&self) -> Self {
*self
}
}
impl<S: TowerSpec> Copy for Tower<S> {}
impl<S: TowerSpec> fmt::Debug for Tower<S> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(S::NAME)
}
}
impl<S: TowerSpec> Default for Tower<S> {
#[inline]
fn default() -> Self {
Self(core::marker::PhantomData)
}
}
#[inline]
const fn fp_low_mask(bits: u32) -> u64 {
if bits == 64 {
u64::MAX
} else {
(1u64 << bits) - 1
}
}
#[must_use]
pub const fn fp_mul_alpha(value: u64, bits: u32) -> u64 {
if bits == 1 {
return value & 1;
}
let half = bits / 2;
let mask = fp_low_mask(half);
let a0 = value & mask;
let a1 = value >> half;
a1 | ((a0 ^ fp_mul_alpha(a1, half)) << half)
}
const fn fp_multiply_rec(lhs: u64, rhs: u64, bits: u32) -> u64 {
if bits == 1 {
return lhs & rhs & 1;
}
let half = bits / 2;
let mask = fp_low_mask(half);
let a0 = lhs & mask;
let a1 = lhs >> half;
let b0 = rhs & mask;
let b1 = rhs >> half;
let z0 = fp_multiply_rec(a0, b0, half);
let z2 = fp_multiply_rec(a1, b1, half);
let z1 = fp_multiply_rec(a0 ^ a1, b0 ^ b1, half) ^ z0 ^ z2;
(z0 ^ z2) | ((z1 ^ fp_mul_alpha(z2, half)) << half)
}
const fn fp_build_exp8() -> [u8; 255] {
let mut table = [0u8; 255];
let mut value = 1u8;
let mut i = 0;
while i < table.len() {
table[i] = value;
#[allow(clippy::cast_lossless)]
let next = fp_multiply_rec(value as u64, 0x2d, 8);
#[allow(clippy::cast_possible_truncation)]
let narrowed = next as u8;
value = narrowed;
i += 1;
}
table
}
const FP_EXP8: [u8; 255] = fp_build_exp8();
#[allow(clippy::cast_possible_truncation)]
const fn fp_build_log8() -> [u8; 256] {
let mut table = [0u8; 256];
let mut i = 0;
while i < FP_EXP8.len() {
table[FP_EXP8[i] as usize] = i as u8;
i += 1;
}
table
}
const FP_LOG8: [u8; 256] = fp_build_log8();
#[must_use]
pub const fn fp_multiply(lhs: u64, rhs: u64, bits: u32) -> u64 {
if bits == 8 {
if lhs == 0 || rhs == 0 {
return 0;
}
#[allow(clippy::cast_possible_truncation)]
let log = FP_LOG8[lhs as usize] as usize + FP_LOG8[rhs as usize] as usize;
return FP_EXP8[log % 255] as u64;
}
fp_multiply_rec(lhs, rhs, bits)
}
#[must_use]
pub const fn fp_square(value: u64, bits: u32) -> u64 {
if bits == 1 {
return value & 1;
}
let half = bits / 2;
let mask = fp_low_mask(half);
let a0 = value & mask;
let a1 = value >> half;
let z0 = fp_square(a0, half);
let z2 = fp_square(a1, half);
(z0 ^ z2) | (fp_mul_alpha(z2, half) << half)
}
#[must_use]
pub const fn fp_invert(value: u64, bits: u32) -> u64 {
if bits == 1 {
return value & 1;
}
let half = bits / 2;
let mask = fp_low_mask(half);
let a0 = value & mask;
let a1 = value >> half;
let a0z1 = a0 ^ fp_mul_alpha(a1, half);
let norm = fp_multiply(a0, a0z1, half) ^ fp_square(a1, half);
let norm_inv = fp_invert(norm, half);
fp_multiply(norm_inv, a0z1, half) | (fp_multiply(norm_inv, a1, half) << half)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct Rijndael16;
impl TowerSpec for Rijndael16 {
type Base = Binary<8, Polynomial<AES>>;
const LINEAR_COEFFICIENT: u64 = 1;
const CONSTANT_COEFFICIENT: u64 = 0x20;
const NAME: &'static str = "GF(2^16)";
}
impl TowerGeneratorSpec for Rijndael16 {
const GENERATOR: u64 = 0x0108;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct Rijndael32;
impl TowerSpec for Rijndael32 {
type Base = Binary<16, Tower<Rijndael16>>;
const LINEAR_COEFFICIENT: u64 = 1;
const CONSTANT_COEFFICIENT: u64 = 0x2000;
const NAME: &'static str = "GF(2^32)";
}
impl TowerGeneratorSpec for Rijndael32 {
const GENERATOR: u64 = 0x0001_0002;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct Rijndael64;
impl TowerSpec for Rijndael64 {
type Base = Binary<32, Tower<Rijndael32>>;
const LINEAR_COEFFICIENT: u64 = 1;
const CONSTANT_COEFFICIENT: u64 = 0x2000_0000;
const NAME: &'static str = "GF(2^64)";
}
impl TowerGeneratorSpec for Rijndael64 {
const GENERATOR: u64 = 0x0000_0001_0000_0004;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct FanPaar2;
impl TowerSpec for FanPaar2 {
type Base = Binary<1, Polynomial<3>>;
const LINEAR_COEFFICIENT: u64 = 1;
const CONSTANT_COEFFICIENT: u64 = 1;
const NAME: &'static str = "Fan-Paar GF(2^2)";
}
impl TowerGeneratorSpec for FanPaar2 {
const GENERATOR: u64 = 0x2;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct FanPaar4;
impl TowerSpec for FanPaar4 {
type Base = Binary<2, Tower<FanPaar2>>;
const LINEAR_COEFFICIENT: u64 = 0x2;
const CONSTANT_COEFFICIENT: u64 = 1;
const NAME: &'static str = "Fan-Paar GF(2^4)";
}
impl TowerGeneratorSpec for FanPaar4 {
const GENERATOR: u64 = 0x7;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct FanPaar8;
impl TowerSpec for FanPaar8 {
type Base = Binary<4, Tower<FanPaar4>>;
const LINEAR_COEFFICIENT: u64 = 0x4;
const CONSTANT_COEFFICIENT: u64 = 1;
const NAME: &'static str = "Fan-Paar GF(2^8)";
}
impl TowerGeneratorSpec for FanPaar8 {
const GENERATOR: u64 = 0x2d;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct FanPaar16;
impl TowerSpec for FanPaar16 {
type Base = Binary<8, Tower<FanPaar8>>;
const LINEAR_COEFFICIENT: u64 = 0x10;
const CONSTANT_COEFFICIENT: u64 = 1;
const NAME: &'static str = "Fan-Paar GF(2^16)";
}
impl TowerGeneratorSpec for FanPaar16 {
const GENERATOR: u64 = 0xe2de;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct FanPaar32;
impl TowerSpec for FanPaar32 {
type Base = Binary<16, Tower<FanPaar16>>;
const LINEAR_COEFFICIENT: u64 = 0x0100;
const CONSTANT_COEFFICIENT: u64 = 1;
const NAME: &'static str = "Fan-Paar GF(2^32)";
}
impl TowerGeneratorSpec for FanPaar32 {
const GENERATOR: u64 = 0x03e2_1cea;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct FanPaar64;
impl TowerSpec for FanPaar64 {
type Base = Binary<32, Tower<FanPaar32>>;
const LINEAR_COEFFICIENT: u64 = 0x0001_0000;
const CONSTANT_COEFFICIENT: u64 = 1;
const NAME: &'static str = "Fan-Paar GF(2^64)";
}
impl TowerGeneratorSpec for FanPaar64 {
const GENERATOR: u64 = 0x070f_870d_cd9c_1d88;
}
macro_rules! tower_repr_row {
($n:literal, $h:literal) => {
impl<S: TowerSpec> BinaryRepr<$n> for Tower<S>
where
S::Base: BinaryDegree<$h>,
{
const NAME: &'static str = S::NAME;
const DESCRIPTION: &'static BinaryDescription = &BinaryDescription::append_quadratic(
<S::Base as BinaryField>::DESCRIPTION,
S::LINEAR_COEFFICIENT,
S::CONSTANT_COEFFICIENT,
);
const LOG_EXP: Option<&'static ByteLogExp> = None;
const VALID: () = {
let () = <S::Base as Field>::VALID;
<Self as BinaryRepr<$n>>::DESCRIPTION.validate();
};
}
};
}
tower_repr_row!(2, 1);
tower_repr_row!(4, 2);
tower_repr_row!(8, 4);
tower_repr_row!(16, 8);
tower_repr_row!(32, 16);
tower_repr_row!(64, 32);
pub(crate) const RIJNDAEL16_DESC: &BinaryDescription =
<Tower<Rijndael16> as BinaryRepr<16>>::DESCRIPTION;
pub(crate) const RIJNDAEL32_DESC: &BinaryDescription =
<Tower<Rijndael32> as BinaryRepr<32>>::DESCRIPTION;
pub(crate) const RIJNDAEL64_DESC: &BinaryDescription =
<Tower<Rijndael64> as BinaryRepr<64>>::DESCRIPTION;
pub(crate) const FANPAAR16_DESC: &BinaryDescription =
<Tower<FanPaar16> as BinaryRepr<16>>::DESCRIPTION;
pub(crate) const FANPAAR32_DESC: &BinaryDescription =
<Tower<FanPaar32> as BinaryRepr<32>>::DESCRIPTION;
pub(crate) const FANPAAR64_DESC: &BinaryDescription =
<Tower<FanPaar64> as BinaryRepr<64>>::DESCRIPTION;
const fn aes_mul(a: u8, b: u8) -> u8 {
Elem::<Binary<8, Polynomial<AES>>>::from_raw(a)
.mul(Elem::<Binary<8, Polynomial<AES>>>::from_raw(b))
.to_raw()
}
const fn aes_square(a: u8) -> u8 {
let e = Elem::<Binary<8, Polynomial<AES>>>::from_raw(a);
e.mul(e).to_raw()
}
const fn aes_inv(a: u8) -> u8 {
Elem::<Binary<8, Polynomial<AES>>>::from_raw(a)
.inv()
.to_raw()
}
#[allow(clippy::cast_possible_truncation)]
pub(crate) const fn rijndael16_mul(a: u16, b: u16) -> u16 {
let (x0, x1) = (a as u8, (a >> 8) as u8);
let (y0, y1) = (b as u8, (b >> 8) as u8);
let ac = aes_mul(x0, y0);
let bd = aes_mul(x1, y1);
let lo = ac ^ aes_mul(0x20, bd);
let hi = aes_mul(x0 ^ x1, y0 ^ y1) ^ ac;
(lo as u16) | ((hi as u16) << 8)
}
#[allow(clippy::cast_possible_truncation)]
pub(crate) const fn rijndael16_square(a: u16) -> u16 {
let (x0, x1) = (a as u8, (a >> 8) as u8);
let x02 = aes_square(x0);
let x12 = aes_square(x1);
let lo = x02 ^ aes_mul(0x20, x12);
(lo as u16) | ((x12 as u16) << 8)
}
#[allow(clippy::cast_possible_truncation)]
pub(crate) const fn rijndael16_inv(a: u16) -> u16 {
if a == 0 {
return 0;
}
let (x0, x1) = (a as u8, (a >> 8) as u8);
let x02 = aes_square(x0);
let x12 = aes_square(x1);
let middle = aes_mul(x0, x1);
let norm = x02 ^ middle ^ aes_mul(0x20, x12);
let ni = aes_inv(norm);
let lo = aes_mul(x0 ^ x1, ni);
let hi = aes_mul(x1, ni);
(lo as u16) | ((hi as u16) << 8)
}
#[allow(clippy::cast_possible_truncation)]
pub(crate) const fn rijndael32_mul(a: u32, b: u32) -> u32 {
let x0 = Elem::<Binary<16, Tower<Rijndael16>>>::from_raw(a as u16);
let x1 = Elem::<Binary<16, Tower<Rijndael16>>>::from_raw((a >> 16) as u16);
let y0 = Elem::<Binary<16, Tower<Rijndael16>>>::from_raw(b as u16);
let y1 = Elem::<Binary<16, Tower<Rijndael16>>>::from_raw((b >> 16) as u16);
let ac = x0.mul(y0);
let bd = x1.mul(y1);
let lo = ac.add(Elem::<Binary<16, Tower<Rijndael16>>>::from_raw(0x2000).mul(bd));
let hi = x0.add(x1).mul(y0.add(y1)).add(ac);
(lo.to_raw() as u32) | ((hi.to_raw() as u32) << 16)
}
#[allow(clippy::cast_possible_truncation)]
pub(crate) const fn rijndael32_square(a: u32) -> u32 {
let x0 = Elem::<Binary<16, Tower<Rijndael16>>>::from_raw(a as u16);
let x1 = Elem::<Binary<16, Tower<Rijndael16>>>::from_raw((a >> 16) as u16);
let x02 = x0.square();
let x12 = x1.square();
let lo = x02.add(Elem::<Binary<16, Tower<Rijndael16>>>::from_raw(0x2000).mul(x12));
(lo.to_raw() as u32) | ((x12.to_raw() as u32) << 16)
}
#[allow(clippy::cast_possible_truncation)]
pub(crate) const fn rijndael32_inv(a: u32) -> u32 {
if a == 0 {
return 0;
}
let x0 = Elem::<Binary<16, Tower<Rijndael16>>>::from_raw(a as u16);
let x1 = Elem::<Binary<16, Tower<Rijndael16>>>::from_raw((a >> 16) as u16);
let delta = Elem::<Binary<16, Tower<Rijndael16>>>::from_raw(0x2000);
let norm = x0.square().add(x0.mul(x1)).add(delta.mul(x1.square()));
let ni = norm.inv();
let lo = x0.add(x1).mul(ni);
let hi = x1.mul(ni);
(lo.to_raw() as u32) | ((hi.to_raw() as u32) << 16)
}
#[allow(clippy::cast_possible_truncation)]
pub(crate) const fn rijndael64_mul(a: u64, b: u64) -> u64 {
let x0 = Elem::<Binary<32, Tower<Rijndael32>>>::from_raw(a as u32);
let x1 = Elem::<Binary<32, Tower<Rijndael32>>>::from_raw((a >> 32) as u32);
let y0 = Elem::<Binary<32, Tower<Rijndael32>>>::from_raw(b as u32);
let y1 = Elem::<Binary<32, Tower<Rijndael32>>>::from_raw((b >> 32) as u32);
let ac = x0.mul(y0);
let bd = x1.mul(y1);
let lo = ac.add(Elem::<Binary<32, Tower<Rijndael32>>>::from_raw(0x2000_0000).mul(bd));
let hi = x0.add(x1).mul(y0.add(y1)).add(ac);
(lo.to_raw() as u64) | ((hi.to_raw() as u64) << 32)
}
#[allow(clippy::cast_possible_truncation)]
pub(crate) const fn rijndael64_square(a: u64) -> u64 {
let x0 = Elem::<Binary<32, Tower<Rijndael32>>>::from_raw(a as u32);
let x1 = Elem::<Binary<32, Tower<Rijndael32>>>::from_raw((a >> 32) as u32);
let x02 = x0.square();
let x12 = x1.square();
let lo = x02.add(Elem::<Binary<32, Tower<Rijndael32>>>::from_raw(0x2000_0000).mul(x12));
(lo.to_raw() as u64) | ((x12.to_raw() as u64) << 32)
}
#[allow(clippy::cast_possible_truncation)]
pub(crate) const fn rijndael64_inv(a: u64) -> u64 {
if a == 0 {
return 0;
}
let x0 = Elem::<Binary<32, Tower<Rijndael32>>>::from_raw(a as u32);
let x1 = Elem::<Binary<32, Tower<Rijndael32>>>::from_raw((a >> 32) as u32);
let delta = Elem::<Binary<32, Tower<Rijndael32>>>::from_raw(0x2000_0000);
let norm = x0.square().add(x0.mul(x1)).add(delta.mul(x1.square()));
let ni = norm.inv();
let lo = x0.add(x1).mul(ni);
let hi = x1.mul(ni);
(lo.to_raw() as u64) | ((hi.to_raw() as u64) << 32)
}
macro_rules! tower_wide_row {
($n:literal, $h:literal, $raw:ty, $baseraw:ty, $half:literal,
$bytes:literal, $order:expr, $factors:expr) => {
impl<S: TowerGeneratorSpec> HasGenerator for Binary<$n, Tower<S>>
where
S::Base: BinaryDegree<$h>,
{
#[allow(clippy::cast_possible_truncation)]
const GENERATOR_RAW: $raw = {
assert!(
$n == 64 || S::GENERATOR >> $n == 0,
"tower generator has excess bits"
);
let g = Elem::<Binary<$n, Tower<S>>> {
raw: S::GENERATOR as $raw,
};
let one = <Binary<$n, Tower<S>> as Field>::ONE_RAW;
let order: u128 = $order - 1;
assert!(
g.pow(order).to_raw() == one,
"tower generator does not have full order"
);
let mut i = 0;
while i < $factors.len() {
assert!(
g.pow(order / $factors[i] as u128).to_raw() != one,
"tower generator does not have full order"
);
i += 1;
}
S::GENERATOR as $raw
};
}
impl<S: TowerSpec> FieldBuffer for Binary<$n, Tower<S>>
where
S::Base: BinaryDegree<$h>,
{
const BYTES: usize = $bytes;
const STORAGE_BITS: u32 = 8 * $bytes;
#[inline]
fn decode(bytes: &[u8]) -> Elem<Self> {
let bytes: [u8; $bytes] = bytes
.try_into()
.expect("tower element has the wrong byte width");
Elem::<Self>::from_bytes(bytes)
}
#[inline]
fn encode(bytes: &mut [u8], value: Elem<Self>) {
assert_eq!(
bytes.len(),
$bytes,
"tower element has the wrong byte width"
);
bytes.copy_from_slice(&value.to_bytes());
}
}
impl<S: TowerSpec> Elem<Binary<$n, Tower<S>>>
where
S::Base: BinaryDegree<$h>,
{
#[inline]
#[must_use]
pub const fn from_components(a: Elem<S::Base>, b: Elem<S::Base>) -> Self
where
S::Base: Field<Raw = $baseraw>,
{
#[allow(clippy::cast_lossless)]
let raw = (a.to_raw() as $raw) | ((b.to_raw() as $raw) << $half);
Elem { raw }
}
#[inline]
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub const fn to_components(self) -> (Elem<S::Base>, Elem<S::Base>)
where
S::Base: Field<Raw = $baseraw>,
{
(
Elem::<S::Base> {
raw: self.raw as $baseraw,
},
Elem::<S::Base> {
raw: (self.raw >> $half) as $baseraw,
},
)
}
}
};
}
tower_wide_row!(16, 8, u16, u8, 8, 2, 1u128 << 16, &[3u64, 5, 17, 257]);
tower_wide_row!(
32,
16,
u32,
u16,
16,
4,
1u128 << 32,
&[3u64, 5, 17, 257, 65_537]
);
tower_wide_row!(
64,
32,
u64,
u32,
32,
8,
1u128 << 64,
&[3u64, 5, 17, 257, 641, 65_537, 6_700_417]
);
macro_rules! tower_small_generator {
($n:literal, $h:literal) => {
impl<S: TowerGeneratorSpec> HasGenerator for Binary<$n, Tower<S>>
where
S::Base: BinaryDegree<$h>,
{
#[allow(clippy::cast_possible_truncation)]
const GENERATOR_RAW: u8 = {
assert!(S::GENERATOR >> $n == 0, "tower generator has excess bits");
assert!(
<Tower<S> as BinaryRepr<$n>>::DESCRIPTION.is_generator(S::GENERATOR),
"tower generator does not have full order"
);
S::GENERATOR as u8
};
}
};
}
tower_small_generator!(2, 1);
tower_small_generator!(4, 2);
tower_small_generator!(8, 4);
impl Elem<Binary<8, Tower<FanPaar8>>> {
#[allow(clippy::cast_possible_truncation)]
#[inline]
#[must_use]
pub const fn mul_alpha(self) -> Self {
Elem {
raw: fp_mul_alpha(self.raw as u64, 8) as u8,
}
}
}
impl Elem<Binary<16, Tower<FanPaar16>>> {
#[allow(clippy::cast_possible_truncation)]
#[inline]
#[must_use]
pub const fn mul_alpha(self) -> Self {
Elem {
raw: fp_mul_alpha(self.raw as u64, 16) as u16,
}
}
}
impl Elem<Binary<32, Tower<FanPaar32>>> {
#[allow(clippy::cast_possible_truncation)]
#[inline]
#[must_use]
pub const fn mul_alpha(self) -> Self {
Elem {
raw: fp_mul_alpha(self.raw as u64, 32) as u32,
}
}
}
#[cfg(test)]
mod tests {
extern crate std;
use std::vec;
use std::vec::Vec;
use super::*;
#[allow(clippy::cast_possible_truncation)]
fn schoolbook16<S: TowerSpec>(x: u16, y: u16, a: u64, b: u64) -> u16
where
S::Base: BinaryDegree<8>,
{
let mul = |p: u8, q: u8| {
let desc = <S::Base as BinaryField>::DESCRIPTION;
desc.mul(u64::from(p), u64::from(q)) as u8
};
let (a0, a1) = (x as u8, (x >> 8) as u8);
let (b0, b1) = (y as u8, (y >> 8) as u8);
let ac = mul(a0, b0);
let bd = mul(a1, b1);
let ad = mul(a0, b1);
let bc = mul(a1, b0);
let lo = ac ^ mul(b as u8, bd);
let hi = ad ^ bc ^ mul(a as u8, bd);
u16::from_le_bytes([lo, hi])
}
#[allow(clippy::cast_possible_truncation)]
fn tower_sample16(seeds: &[u16]) -> Vec<u16> {
let mut values = vec![0u16, 1, 2, 0x00ff, 0x0100, 0xff00, 0xffff];
values.extend_from_slice(seeds);
let mut state = 0x0123_4567_89ab_cdefu64;
for _ in 0..64 {
state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1);
values.push((state >> 32) as u16);
}
values
}
fn check_matches_schoolbook16<S: TowerSpec>(a: u64, b: u64, generator: u16)
where
S::Base: BinaryDegree<8>,
{
for &x in &tower_sample16(&[generator]) {
for &y in &tower_sample16(&[generator]) {
let got = Elem::<Binary<16, Tower<S>>>::from_raw(x)
.mul(Elem::<Binary<16, Tower<S>>>::from_raw(y))
.to_raw();
assert_eq!(got, schoolbook16::<S>(x, y, a, b), "{x:04x} * {y:04x}");
}
}
}
fn check_field_laws16<S: TowerSpec>(generator: u16)
where
S::Base: BinaryDegree<8>,
{
let one = Elem::<Binary<16, Tower<S>>>::ONE;
for &x in &tower_sample16(&[generator]) {
let a = Elem::<Binary<16, Tower<S>>>::from_raw(x);
assert_eq!(a.add(a), Elem::<Binary<16, Tower<S>>>::ZERO);
assert_eq!(a.mul(one), a);
assert_eq!(a.square(), a.mul(a));
if !a.is_zero() {
assert_eq!(a.mul(a.inv()), one);
}
assert_eq!(
a.div(Elem::<Binary<16, Tower<S>>>::ZERO),
Elem::<Binary<16, Tower<S>>>::ZERO
);
assert_eq!(a.pow(0), one);
}
}
fn check_generator_order16<S: TowerGeneratorSpec>(factors: &[u128])
where
S::Base: BinaryDegree<8>,
{
let g = Elem::<Binary<16, Tower<S>>>::GENERATOR;
assert_eq!(g.pow(65_535), Elem::<Binary<16, Tower<S>>>::ONE);
for &q in factors {
assert_ne!(g.pow(65_535 / q), Elem::<Binary<16, Tower<S>>>::ONE);
}
}
#[test]
fn rijndael16_matches_schoolbook() {
check_matches_schoolbook16::<Rijndael16>(1, 0x20, 0x0108);
}
#[test]
fn rijndael16_field_laws() {
check_field_laws16::<Rijndael16>(0x0108);
}
#[test]
fn rijndael16_generator_order() {
check_generator_order16::<Rijndael16>(&[3, 5, 17, 257]);
}
#[test]
fn fanpaar16_matches_schoolbook() {
check_matches_schoolbook16::<FanPaar16>(0x10, 1, 0xe2de);
}
#[test]
fn fanpaar16_field_laws() {
check_field_laws16::<FanPaar16>(0xe2de);
}
#[test]
fn fanpaar16_generator_order() {
check_generator_order16::<FanPaar16>(&[3, 5, 17, 257]);
}
#[test]
fn fanpaar_small_levels_match_recurrence() {
assert_eq!(fp_multiply(0b10, 0b11, 2), 0b01);
assert_eq!(fp_square(0x10, 8), 0x41);
assert_eq!(fp_invert(0b0100, 4), 0b0110);
assert_eq!(fp_invert(0x10, 8), 0x14);
for bits in [2u32, 4, 8, 16, 32, 64] {
let half = bits / 2;
let generator = 1u64 << half;
let sub = if half == 1 { 1 } else { 1u64 << (half / 2) };
assert_eq!(fp_square(generator, bits), 1 | (sub << half));
}
for (x, y) in [(0x2u8, 0x3u8), (0x1u8, 0x1u8), (0x0u8, 0x2u8)] {
let got = Elem::<Binary<2, Tower<FanPaar2>>>::from_raw(x)
.mul(Elem::<Binary<2, Tower<FanPaar2>>>::from_raw(y))
.to_raw();
assert_eq!(u64::from(got), fp_multiply(u64::from(x), u64::from(y), 2));
}
let g2 = Elem::<Binary<2, Tower<FanPaar2>>>::GENERATOR;
assert_eq!(g2.pow(3), Elem::<Binary<2, Tower<FanPaar2>>>::ONE);
let g4 = Elem::<Binary<4, Tower<FanPaar4>>>::GENERATOR;
assert_eq!(g4.pow(15), Elem::<Binary<4, Tower<FanPaar4>>>::ONE);
let g8 = Elem::<Binary<8, Tower<FanPaar8>>>::GENERATOR;
assert_eq!(g8.pow(255), Elem::<Binary<8, Tower<FanPaar8>>>::ONE);
}
#[test]
fn rijndael32_matches_schoolbook() {
#[allow(clippy::cast_possible_truncation, clippy::cast_lossless)]
let mul = |x: u32, y: u32| {
let (a0, a1) = (x as u16, (x >> 16) as u16);
let (b0, b1) = (y as u16, (y >> 16) as u16);
let ac = rijndael16_mul(a0, b0);
let bd = rijndael16_mul(a1, b1);
let ad = rijndael16_mul(a0, b1);
let bc = rijndael16_mul(a1, b0);
let lo = ac ^ rijndael16_mul(0x2000, bd);
let hi = ad ^ bc ^ bd;
(lo as u32) | ((hi as u32) << 16)
};
let mut state = 0x0123_4567_89ab_cdefu64;
let mut values = vec![
0u32,
1,
2,
0x00ff,
0x0001_0000,
0xffff_0000,
0xffff_ffff,
0x0001_0002,
];
for _ in 0..64 {
state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1);
values.push((state >> 32) as u32);
}
for &x in &values {
for &y in &values {
let got = Elem::<Binary<32, Tower<Rijndael32>>>::from_raw(x)
.mul(Elem::<Binary<32, Tower<Rijndael32>>>::from_raw(y))
.to_raw();
assert_eq!(got, mul(x, y), "{x:08x} * {y:08x}");
}
}
for &x in &values {
let a = Elem::<Binary<32, Tower<Rijndael32>>>::from_raw(x);
assert_eq!(a.square(), a.mul(a));
if !a.is_zero() {
assert_eq!(a.mul(a.inv()), Elem::<Binary<32, Tower<Rijndael32>>>::ONE);
}
}
let g = Elem::<Binary<32, Tower<Rijndael32>>>::GENERATOR;
assert_eq!(
g.pow(u128::from(u32::MAX)),
Elem::<Binary<32, Tower<Rijndael32>>>::ONE
);
for q in [3u128, 5, 17, 257, 65_537] {
assert_ne!(
g.pow(u128::from(u32::MAX) / q),
Elem::<Binary<32, Tower<Rijndael32>>>::ONE
);
}
}
#[test]
fn fanpaar32_matches_schoolbook() {
#[allow(clippy::cast_possible_truncation, clippy::cast_lossless)]
let base_mul = |x: u32, y: u32| (fp_multiply(x as u64, y as u64, 16) & 0xffff) as u16;
#[allow(clippy::cast_possible_truncation, clippy::cast_lossless)]
let mul = |x: u32, y: u32| {
let (a0, a1) = (x & 0xffff, x >> 16);
let (b0, b1) = (y & 0xffff, y >> 16);
let ac = u32::from(base_mul(a0, b0));
let bd = u32::from(base_mul(a1, b1));
let ad = u32::from(base_mul(a0, b1));
let bc = u32::from(base_mul(a1, b0));
let alpha_bd = u32::from(base_mul(0x0100, bd));
let lo = ac ^ bd;
let hi = ad ^ bc ^ alpha_bd;
(lo & 0xffff) | ((hi & 0xffff) << 16)
};
let mut state = 0x0245_68ac_e135_79bdu64;
let mut values = vec![
0u32,
1,
2,
0x00ff,
0x0001_0000,
0xffff_0000,
0xffff_ffff,
0x03e2_1cea,
];
for _ in 0..64 {
state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1);
values.push((state >> 32) as u32);
}
for &x in &values {
for &y in &values {
let got = Elem::<Binary<32, Tower<FanPaar32>>>::from_raw(x)
.mul(Elem::<Binary<32, Tower<FanPaar32>>>::from_raw(y))
.to_raw();
assert_eq!(got, mul(x, y), "{x:08x} * {y:08x}");
}
}
let g = Elem::<Binary<32, Tower<FanPaar32>>>::GENERATOR;
assert_eq!(
g.pow(u128::from(u32::MAX)),
Elem::<Binary<32, Tower<FanPaar32>>>::ONE
);
for q in [3u128, 5, 17, 257, 65_537] {
assert_ne!(
g.pow(u128::from(u32::MAX) / q),
Elem::<Binary<32, Tower<FanPaar32>>>::ONE
);
}
}
#[test]
fn wide_field_facts() {
assert_eq!(<Binary<64, Tower<Rijndael64>> as Field>::DEGREE, 64);
assert_eq!(<Binary<64, Tower<Rijndael64>> as Field>::ORDER, 1u128 << 64);
assert_eq!(<Binary<64, Tower<Rijndael64>> as FieldBuffer>::BYTES, 8);
assert_eq!(<Binary<32, Tower<Rijndael32>> as FieldBuffer>::BYTES, 4);
let g = Elem::<Binary<64, Tower<Rijndael64>>>::GENERATOR;
assert_eq!(
g.pow(u128::from(u64::MAX)),
Elem::<Binary<64, Tower<Rijndael64>>>::ONE
);
for q in [3u128, 5, 17, 257, 641, 65_537, 6_700_417] {
assert_ne!(
g.pow(u128::from(u64::MAX) / q),
Elem::<Binary<64, Tower<Rijndael64>>>::ONE
);
}
let max = <Binary<64, Tower<Rijndael64>> as BinaryField>::from_coordinates(u64::MAX)
.expect("full-width word");
assert_eq!(
<Binary<64, Tower<Rijndael64>> as BinaryField>::to_coordinates(max),
u64::MAX
);
assert!(
<Binary<32, Tower<Rijndael32>> as BinaryField>::from_coordinates(1u64 << 32).is_err()
);
assert!(
<Binary<16, Tower<Rijndael16>> as BinaryField>::from_coordinates(1u64 << 16).is_err()
);
assert!(<Binary<64, Tower<Rijndael64>> as BinaryField>::from_coordinates(u64::MAX).is_ok());
}
#[test]
fn fanpaar64_matches_reference_pair() {
assert_eq!(
fp_multiply(0xc84d_6191_1083_1cef, 0x0000_0000_0000_a14f, 64),
0x3565_086d_6b9e_f595
);
let g = Elem::<Binary<64, Tower<FanPaar64>>>::GENERATOR;
assert_eq!(
g.pow(u128::from(u64::MAX)),
Elem::<Binary<64, Tower<FanPaar64>>>::ONE
);
}
#[derive(Clone, Copy)]
struct CustomNormal8;
impl TowerSpec for CustomNormal8 {
type Base = Binary<8, crate::field::Normal<0x11B, 0x20>>;
const LINEAR_COEFFICIENT: u64 = 0xFF;
const CONSTANT_COEFFICIENT: u64 = transport_poly(0x20);
const NAME: &'static str = "custom normal tower";
}
const fn transport_poly(word: u64) -> u64 {
let from = crate::field::binary::normal::inverse_columns(
crate::field::binary::normal::to_base::<0x11B, 0x20, 8>(),
8,
);
let mut image = 0u64;
let mut i = 0;
while i < 8 {
if (word >> i) & 1 == 1 {
image ^= from[i] as u64;
}
i += 1;
}
image
}
#[test]
fn custom_normal_tower_zero_one_inverse() {
type F = Binary<16, Tower<CustomNormal8>>;
assert_eq!(Elem::<F>::ZERO.to_raw(), 0);
let base_one = <CustomNormal8 as TowerSpec>::Base::DESCRIPTION.one();
assert_eq!(base_one, 0xFF);
assert_eq!(Elem::<F>::ONE.to_raw(), 0xFF);
let mut state = 0x1357_9bdf_0246_8aceu64;
for _ in 0..32 {
state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1);
#[allow(clippy::cast_possible_truncation)]
let a = Elem::<F>::from_raw(((state >> 32) as u16).max(1));
assert_eq!(a.mul(a.inv()), Elem::<F>::ONE, "{:04x}", a.to_raw());
}
}
#[test]
fn transport_poly_is_a_field_isomorphism() {
type Poly8 = Binary<8, crate::field::Polynomial<0x11B>>;
type Normal8 = Binary<8, crate::field::Normal<0x11B, 0x20>>;
#[allow(clippy::cast_possible_truncation)]
let transport = |word: u8| transport_poly(core::hint::black_box(u64::from(word))) as u8;
assert_eq!(transport(1), Elem::<Normal8>::ONE.to_raw());
let mut images = [false; 256];
for a in 0..=u8::MAX {
let image = transport(a);
assert!(!images[usize::from(image)], "{a:#04x} collides");
images[usize::from(image)] = true;
for b in [0u8, 1, 2, 0x20, 0x53, 0xca, 0xff] {
let product = Elem::<Poly8>::from_raw(a)
.mul(Elem::<Poly8>::from_raw(b))
.to_raw();
let image_product = Elem::<Normal8>::from_raw(image)
.mul(Elem::<Normal8>::from_raw(transport(b)))
.to_raw();
assert_eq!(transport(product), image_product, "{a:#04x} * {b:#04x}");
}
}
}
fn wiedemann_schoolbook(lhs: u64, rhs: u64, bits: u32) -> u64 {
if bits == 1 {
return lhs & rhs & 1;
}
let half = bits / 2;
let mask = (1u64 << half) - 1;
let (a0, a1) = (lhs & mask, lhs >> half);
let (b0, b1) = (rhs & mask, rhs >> half);
let high = wiedemann_schoolbook(a1, b1, half);
let alpha_high = if half == 1 {
high
} else {
wiedemann_schoolbook(high, 1u64 << (half / 2), half)
};
let low = wiedemann_schoolbook(a0, b0, half) ^ high;
let cross =
wiedemann_schoolbook(a0, b1, half) ^ wiedemann_schoolbook(a1, b0, half) ^ alpha_high;
low | (cross << half)
}
#[test]
fn byte_log_tables_enumerate_the_generator_powers() {
let exp = fp_build_exp8();
let log = fp_build_log8();
let mut seen = [false; 256];
let mut power = 1u64;
for (i, &entry) in exp.iter().enumerate() {
assert_eq!(u64::from(entry), power, "0x2d^{i}");
assert!(!seen[usize::from(entry)], "0x2d^{i} repeats");
seen[usize::from(entry)] = true;
assert_eq!(usize::from(log[usize::from(entry)]), i, "log 0x2d^{i}");
power = wiedemann_schoolbook(power, 0x2d, 8);
}
assert!(!seen[0]);
assert_eq!(power, 1);
}
}