use comnum_core::{Result, ensure};
pub fn primitive_polynomial(m: u32) -> Option<u32> {
const POLYS: [u32; 15] = [
0x7, 0xB, 0x13, 0x25, 0x43, 0x89, 0x11D, 0x211, 0x409, 0x805, 0x1053, 0x201B, 0x4443, 0x8003, 0x1100B, ];
(2..=16).contains(&m).then(|| POLYS[m as usize - 2])
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Field {
m: u32,
exp: Vec<u32>,
log: Vec<u32>,
}
impl Field {
pub fn new(m: u32, poly: u32) -> Result<Field> {
ensure((2..=20).contains(&m), || format!("GF(2^m): the field degree must be 2 to 20, got {m}"))?;
ensure(poly >> m == 1, || format!("GF(2^m): {poly:#x} is not a polynomial of degree {m}"))?;
let q = 1usize << m;
let mut exp = vec![0u32; 2 * q];
let mut log = vec![0u32; q];
let mut x = 1u32;
for (i, e) in exp.iter_mut().enumerate().take(q - 1) {
*e = x;
ensure(i == 0 || x != 1, || format!("GF(2^m): {poly:#x} is not a primitive polynomial of degree {m}"))?;
log[x as usize] = i as u32;
x <<= 1;
if x & (1 << m) != 0 {
x ^= poly;
}
}
ensure(x == 1, || format!("GF(2^m): {poly:#x} is not a primitive polynomial of degree {m}"))?;
for i in q - 1..2 * q {
exp[i] = exp[i - (q - 1)];
}
Ok(Field { m, exp, log })
}
pub fn m(&self) -> u32 {
self.m
}
pub fn order(&self) -> usize {
(1 << self.m) - 1
}
pub fn pow(&self, e: usize) -> u32 {
self.exp[e % self.order()]
}
pub fn log(&self, a: u32) -> usize {
self.log[a as usize] as usize
}
pub fn mul(&self, a: u32, b: u32) -> u32 {
if a == 0 || b == 0 { 0 } else { self.exp[(self.log[a as usize] + self.log[b as usize]) as usize] }
}
pub fn div(&self, a: u32, b: u32) -> u32 {
debug_assert!(b != 0, "division by zero in GF(2^{})", self.m());
if a == 0 {
0
} else {
let n = self.order() as u32;
self.exp[((self.log[a as usize] + n - self.log[b as usize]) % n) as usize]
}
}
pub fn inv(&self, a: u32) -> u32 {
self.div(1, a)
}
pub fn minimal_polynomial(&self, i: usize) -> Vec<u8> {
let n = self.order();
let mut conj = vec![i % n];
let mut e = (2 * i) % n;
while e != i % n {
conj.push(e);
e = (2 * e) % n;
}
let mut p = vec![1u32];
for &c in &conj {
let r = self.pow(c);
let mut next = vec![0u32; p.len() + 1];
for (d, &a) in p.iter().enumerate() {
next[d + 1] ^= a;
next[d] ^= self.mul(a, r);
}
p = next;
}
p.iter().map(|&a| u8::from(a == 1)).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "division by zero")]
fn the_inverse_of_zero_is_a_broken_invariant_in_debug_builds() {
let _ = Field::new(4, 0x13).unwrap().inv(0);
}
#[test]
fn a_polynomial_of_the_wrong_degree_is_an_error_not_a_panic() {
assert!(Field::new(4, 0x11D).is_err());
assert!(Field::new(4, 0xB).is_err());
assert!(Field::new(4, 0).is_err());
assert!(Field::new(8, u32::MAX).is_err());
assert!(Field::new(4, 0x1F).is_err());
assert!(Field::new(4, 0x13).is_ok());
}
#[test]
fn every_default_polynomial_is_primitive_and_the_arithmetic_is_consistent() {
for m in 2..=16 {
let f = Field::new(m, primitive_polynomial(m).unwrap()).unwrap();
assert_eq!(f.order(), (1 << m) - 1);
let a = f.pow(5);
let b = f.pow(f.order() - 2);
assert_eq!(f.div(f.mul(a, b), b), a, "m = {m}");
assert_eq!(f.mul(a, f.inv(a)), 1, "m = {m}");
assert_eq!(f.log(f.pow(5)), 5 % f.order());
}
assert!(primitive_polynomial(1).is_none() && primitive_polynomial(17).is_none());
assert!(Field::new(4, 0b1_1111).is_err());
}
}