use super::description::{BinaryDescription, ByteLogExp};
use super::{BinaryRepr, private};
pub const AES: u128 = 0x11B;
pub const RS: u128 = 0x11D;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
pub struct Polynomial<const P: u128>;
impl<const P: u128> private::Sealed for Polynomial<P> {}
impl<const P: u128> Polynomial<P> {
pub const POLY: u128 = P;
#[allow(dead_code)]
pub(crate) const REDUCTION_LOW: u8 = (P & 0xFF) as u8;
const NAME_BYTES8: [u8; 13] = name_bytes8(P);
const NAME_BYTES4: [u8; 12] = name_bytes4(P);
const NAME_BYTES2: [u8; 11] = name_bytes2(P);
pub(crate) const fn smallest_generator(description: &BinaryDescription) -> u64 {
let limit = 1u64 << description.degree();
let mut candidate = 2u64;
loop {
assert!(candidate < limit, "Polynomial P has no primitive element");
if description.is_generator(candidate) {
break candidate;
}
candidate += 1;
}
}
}
const fn name_bytes8(poly: u128) -> [u8; 13] {
const HEX: &[u8; 16] = b"0123456789ABCDEF";
let mut name = *b"GF(2^8)/0x000";
name[10] = HEX[((poly >> 8) & 0xf) as usize];
name[11] = HEX[((poly >> 4) & 0xf) as usize];
name[12] = HEX[(poly & 0xf) as usize];
name
}
const fn name_bytes4(poly: u128) -> [u8; 12] {
const HEX: &[u8; 16] = b"0123456789ABCDEF";
let mut name = *b"GF(2^4)/0x00";
name[10] = HEX[((poly >> 4) & 0xf) as usize];
name[11] = HEX[(poly & 0xf) as usize];
name
}
const fn name_bytes2(poly: u128) -> [u8; 11] {
const HEX: &[u8; 16] = b"0123456789ABCDEF";
let mut name = *b"GF(2^2)/0x0";
name[10] = HEX[(poly & 0xf) as usize];
name
}
macro_rules! poly_degree8 {
() => {
impl<const P: u128> BinaryRepr<8> for Polynomial<P> {
const NAME: &'static str = match core::str::from_utf8(&Polynomial::<P>::NAME_BYTES8) {
Ok(name) => name,
Err(_) => panic!("Polynomial field names are ASCII"),
};
const DESCRIPTION: &'static BinaryDescription = &BinaryDescription::polynomial(8, P);
const LOG_EXP: Option<&'static ByteLogExp> = Some(&ByteLogExp::build(
&BinaryDescription::polynomial(8, P),
Polynomial::<P>::smallest_generator(&BinaryDescription::polynomial(8, P)),
));
const VALID: () = {
assert!(
super::description::poly_degree(P) == 8,
"polynomial degree does not match field degree"
);
<Self as BinaryRepr<8>>::DESCRIPTION.validate();
assert!(
<Self as BinaryRepr<8>>::DESCRIPTION.is_generator(
Polynomial::<P>::smallest_generator(<Self as BinaryRepr<8>>::DESCRIPTION)
),
"byte representation generator does not have full order"
);
};
}
};
}
poly_degree8!();
impl<const P: u128> BinaryRepr<4> for Polynomial<P> {
const NAME: &'static str = match core::str::from_utf8(&Polynomial::<P>::NAME_BYTES4) {
Ok(name) => name,
Err(_) => panic!("Polynomial field names are ASCII"),
};
const DESCRIPTION: &'static BinaryDescription = &BinaryDescription::polynomial(4, P);
const LOG_EXP: Option<&'static ByteLogExp> = None;
const VALID: () = {
assert!(
super::description::poly_degree(P) == 4,
"polynomial degree does not match field degree"
);
<Self as BinaryRepr<4>>::DESCRIPTION.validate();
};
}
impl<const P: u128> BinaryRepr<2> for Polynomial<P> {
const NAME: &'static str = match core::str::from_utf8(&Polynomial::<P>::NAME_BYTES2) {
Ok(name) => name,
Err(_) => panic!("Polynomial field names are ASCII"),
};
const DESCRIPTION: &'static BinaryDescription = &BinaryDescription::polynomial(2, P);
const LOG_EXP: Option<&'static ByteLogExp> = None;
const VALID: () = {
assert!(
super::description::poly_degree(P) == 2,
"polynomial degree does not match field degree"
);
<Self as BinaryRepr<2>>::DESCRIPTION.validate();
};
}
impl<const P: u128> BinaryRepr<1> for Polynomial<P> {
const NAME: &'static str = "GF(2)";
const DESCRIPTION: &'static BinaryDescription = &BinaryDescription::polynomial(1, P);
const LOG_EXP: Option<&'static ByteLogExp> = None;
const VALID: () = {
assert!(P == 3, "Polynomial<3> is the only degree-one form");
<Self as BinaryRepr<1>>::DESCRIPTION.validate();
};
}
use super::super::HasGenerator;
use super::Binary;
impl<const P: u128> HasGenerator for Binary<8, Polynomial<P>> {
const GENERATOR_RAW: u8 = {
let g = Polynomial::<P>::smallest_generator(<Polynomial<P> as BinaryRepr<8>>::DESCRIPTION);
assert!(
<Polynomial<P> as BinaryRepr<8>>::DESCRIPTION.is_generator(g),
"byte representation generator does not have full order"
);
#[allow(clippy::cast_possible_truncation)]
let generator = g as u8;
generator
};
}
impl<const P: u128> HasGenerator for Binary<4, Polynomial<P>> {
const GENERATOR_RAW: u8 = {
let g = Polynomial::<P>::smallest_generator(<Polynomial<P> as BinaryRepr<4>>::DESCRIPTION);
assert!(
<Polynomial<P> as BinaryRepr<4>>::DESCRIPTION.is_generator(g),
"representation generator does not have full order"
);
#[allow(clippy::cast_possible_truncation)]
let generator = g as u8;
generator
};
}
impl<const P: u128> HasGenerator for Binary<2, Polynomial<P>> {
const GENERATOR_RAW: u8 = {
let g = Polynomial::<P>::smallest_generator(<Polynomial<P> as BinaryRepr<2>>::DESCRIPTION);
assert!(
<Polynomial<P> as BinaryRepr<2>>::DESCRIPTION.is_generator(g),
"representation generator does not have full order"
);
#[allow(clippy::cast_possible_truncation)]
let generator = g as u8;
generator
};
}
impl<const P: u128> HasGenerator for Binary<1, Polynomial<P>> {
const GENERATOR_RAW: u8 = 1;
}
#[cfg(test)]
pub(super) mod tests {
extern crate std;
use core::hint::black_box;
use super::super::description::poly_irreducible;
use super::*;
pub(in crate::field::binary) fn oracle_mul(poly: u128, n: u32, x: u64, y: u64) -> u64 {
let mut acc: u128 = 0;
for k in 0..n {
if (y >> k) & 1 == 1 {
acc ^= u128::from(x) << k;
}
}
for k in (n..2 * n).rev() {
if (acc >> k) & 1 == 1 {
acc ^= poly << (k - n);
}
}
#[allow(clippy::cast_possible_truncation)]
let product = acc as u64;
product
}
pub(in crate::field::binary) fn oracle_smallest_generator(poly: u128, n: u32) -> u64 {
let order = (1u64 << n) - 1;
(2..=order)
.find(|&candidate| {
let mut power = candidate;
let mut steps = 1;
while power != 1 && steps <= order {
power = oracle_mul(poly, n, power, candidate);
steps += 1;
}
steps == order
})
.expect("a field has a primitive element")
}
pub(in crate::field::binary) fn apply_columns(columns: [u8; 8], word: u64) -> u64 {
(0..8)
.filter(|&i| (word >> i) & 1 == 1)
.fold(0, |image, i| image ^ u64::from(columns[i]))
}
fn irreducibles(n: u32) -> impl Iterator<Item = u128> {
(1u128 << n..2u128 << n).filter(move |&p| poly_irreducible(p, n))
}
#[test]
fn smallest_generator_matches_brute_force_order() {
for n in [2u8, 4, 8] {
for poly in irreducibles(u32::from(n)) {
let description = BinaryDescription::polynomial(black_box(n), black_box(poly));
assert_eq!(
Polynomial::<AES>::smallest_generator(&description),
oracle_smallest_generator(poly, u32::from(n)),
"degree {n}, polynomial {poly:#x}"
);
}
}
}
#[test]
fn field_names_round_trip_their_polynomial() {
fn parsed(name: &[u8]) -> u128 {
let name = core::str::from_utf8(name).expect("ASCII name");
let (_, digits) = name.rsplit_once("0x").expect("hexadecimal suffix");
u128::from_str_radix(digits, 16).expect("hexadecimal digits")
}
for poly in irreducibles(8) {
assert_eq!(parsed(&name_bytes8(black_box(poly))), poly);
}
for poly in irreducibles(4) {
assert_eq!(parsed(&name_bytes4(black_box(poly))), poly);
}
for poly in irreducibles(2) {
assert_eq!(parsed(&name_bytes2(black_box(poly))), poly);
}
}
#[test]
fn irreducibility_check_admits_exactly_the_thirty_fields() {
let count = (0x100..=0x1FFu128)
.filter(|&p| super::super::description::poly_irreducible(p, 8))
.count();
assert_eq!(count, 30);
assert!(super::super::description::poly_irreducible(AES, 8));
assert!(super::super::description::poly_irreducible(RS, 8));
assert!(super::super::description::poly_irreducible(0x12D, 8));
assert!(super::super::description::poly_irreducible(0x187, 8));
assert!(!super::super::description::poly_irreducible(0x101, 8));
assert!(!super::super::description::poly_irreducible(0x0FF, 8));
assert!(!super::super::description::poly_irreducible(0x200, 8));
}
}