use crate::error::{Error, Result};
use crate::utils::{factor_prime_power, is_prime};
#[derive(Debug, Clone)]
pub struct GfTables {
order: u32,
characteristic: u32,
degree: u32,
mul: Vec<u32>,
add: Vec<u32>,
inv: Vec<u32>,
neg: Vec<u32>,
}
impl GfTables {
pub fn new_prime(p: u32) -> Result<Self> {
if !is_prime(p) {
return Err(Error::NotPrimePower(p));
}
let order = p;
let size = (order * order) as usize;
let mut add = vec![0u32; size];
let mut mul = vec![0u32; size];
let mut inv = vec![0u32; order as usize];
let mut neg = vec![0u32; order as usize];
for a in 0..order {
for b in 0..order {
add[(a * order + b) as usize] = (a + b) % order;
}
neg[a as usize] = if a == 0 { 0 } else { order - a };
}
for a in 0..order {
for b in 0..order {
mul[(a * order + b) as usize] = ((a as u64 * b as u64) % order as u64) as u32;
}
}
inv[0] = 0; for a in 1..order {
inv[a as usize] = mod_pow(a, order - 2, order);
}
Ok(Self {
order,
characteristic: p,
degree: 1,
mul,
add,
inv,
neg,
})
}
pub fn new_extension(q: u32) -> Result<Self> {
let factorization = factor_prime_power(q).ok_or(Error::NotPrimePower(q))?;
if factorization.exponent == 1 {
return Self::new_prime(q);
}
let p = factorization.prime;
let n = factorization.exponent;
let irr_poly =
super::poly::get_irreducible_poly(p, n).ok_or(Error::NoIrreduciblePolynomial(q))?;
Self::build_extension_tables(p, n, &irr_poly)
}
fn build_extension_tables(p: u32, n: u32, irr_poly: &[u32]) -> Result<Self> {
let order = p.pow(n);
let size = (order * order) as usize;
let mut add = vec![0u32; size];
let mut mul = vec![0u32; size];
let mut inv = vec![0u32; order as usize];
let mut neg = vec![0u32; order as usize];
for a in 0..order {
for b in 0..order {
add[(a * order + b) as usize] = poly_add(a, b, p, n);
}
neg[a as usize] = poly_neg(a, p, n);
}
for a in 0..order {
for b in 0..order {
mul[(a * order + b) as usize] = poly_mul(a, b, p, n, irr_poly);
}
}
inv[0] = 0;
for a in 1..order {
inv[a as usize] = poly_inv(a, p, n, &mul, order);
}
Ok(Self {
order,
characteristic: p,
degree: n,
mul,
add,
inv,
neg,
})
}
#[must_use]
pub fn order(&self) -> u32 {
self.order
}
#[must_use]
pub fn characteristic(&self) -> u32 {
self.characteristic
}
#[must_use]
pub fn degree(&self) -> u32 {
self.degree
}
#[must_use]
pub fn add(&self, a: u32, b: u32) -> u32 {
if self.characteristic == 2 {
a ^ b
} else {
self.add[(a * self.order + b) as usize]
}
}
#[must_use]
pub fn sub(&self, a: u32, b: u32) -> u32 {
if self.characteristic == 2 {
a ^ b
} else {
self.add[(a * self.order + self.neg[b as usize]) as usize]
}
}
#[must_use]
pub fn mul(&self, a: u32, b: u32) -> u32 {
self.mul[(a * self.order + b) as usize]
}
#[must_use]
pub fn div(&self, a: u32, b: u32) -> u32 {
assert!(b != 0, "division by zero");
self.mul[(a * self.order + self.inv[b as usize]) as usize]
}
#[must_use]
pub fn neg(&self, a: u32) -> u32 {
self.neg[a as usize]
}
#[must_use]
pub fn inv(&self, a: u32) -> u32 {
assert!(a != 0, "inverse of zero");
self.inv[a as usize]
}
#[must_use]
pub fn has_inv(&self, a: u32) -> bool {
a != 0
}
#[must_use]
pub fn pow(&self, mut base: u32, mut exp: u32) -> u32 {
let mut result = 1u32;
while exp > 0 {
if exp & 1 == 1 {
result = self.mul(result, base);
}
exp >>= 1;
base = self.mul(base, base);
}
result
}
}
fn mod_pow(mut base: u32, mut exp: u32, modulus: u32) -> u32 {
let mut result = 1u32;
base %= modulus;
while exp > 0 {
if exp & 1 == 1 {
result = ((result as u64 * base as u64) % modulus as u64) as u32;
}
exp >>= 1;
base = ((base as u64 * base as u64) % modulus as u64) as u32;
}
result
}
fn poly_add(a: u32, b: u32, p: u32, n: u32) -> u32 {
let mut result = 0u32;
let mut pow_p = 1u32;
let mut a = a;
let mut b = b;
for _ in 0..n {
let coef_a = a % p;
let coef_b = b % p;
let sum = (coef_a + coef_b) % p;
result += sum * pow_p;
a /= p;
b /= p;
pow_p *= p;
}
result
}
fn poly_neg(a: u32, p: u32, n: u32) -> u32 {
let mut result = 0u32;
let mut pow_p = 1u32;
let mut a = a;
for _ in 0..n {
let coef = a % p;
let neg_coef = if coef == 0 { 0 } else { p - coef };
result += neg_coef * pow_p;
a /= p;
pow_p *= p;
}
result
}
fn poly_mul(a: u32, b: u32, p: u32, n: u32, irr_poly: &[u32]) -> u32 {
let mut a_coeffs = vec![0u32; n as usize];
let mut b_coeffs = vec![0u32; n as usize];
let mut temp_a = a;
let mut temp_b = b;
for i in 0..n as usize {
a_coeffs[i] = temp_a % p;
b_coeffs[i] = temp_b % p;
temp_a /= p;
temp_b /= p;
}
let mut product = vec![0u32; (2 * n - 1) as usize];
for i in 0..n as usize {
for j in 0..n as usize {
product[i + j] = (product[i + j] + a_coeffs[i] * b_coeffs[j]) % p;
}
}
for i in ((n as usize)..product.len()).rev() {
if product[i] != 0 {
let coef = product[i];
product[i] = 0;
for j in 0..n as usize {
let sub = (coef * irr_poly[j]) % p;
product[i - n as usize + j] = (product[i - n as usize + j] + p - sub) % p;
}
}
}
let mut result = 0u32;
let mut pow_p = 1u32;
for i in 0..n as usize {
result += product[i] * pow_p;
pow_p *= p;
}
result
}
fn poly_inv(a: u32, _p: u32, _n: u32, mul_table: &[u32], order: u32) -> u32 {
if a == 0 {
return 0;
}
for x in 1..order {
if mul_table[(a * order + x) as usize] == 1 {
return x;
}
}
0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_prime_field_tables() {
let gf7 = GfTables::new_prime(7).unwrap();
assert_eq!(gf7.add(3, 5), 1); assert_eq!(gf7.add(6, 1), 0);
assert_eq!(gf7.mul(3, 5), 1); assert_eq!(gf7.mul(2, 4), 1);
for a in 1..7u32 {
let inv_a = gf7.inv(a);
assert_eq!(gf7.mul(a, inv_a), 1, "a={}, inv={}", a, inv_a);
}
for a in 0..7u32 {
let neg_a = gf7.neg(a);
assert_eq!(gf7.add(a, neg_a), 0, "a={}, neg={}", a, neg_a);
}
}
#[test]
fn test_gf4_extension() {
let gf4 = GfTables::new_extension(4).unwrap();
assert_eq!(gf4.order(), 4);
assert_eq!(gf4.characteristic(), 2);
assert_eq!(gf4.degree(), 2);
assert_eq!(gf4.add(0, 0), 0);
assert_eq!(gf4.add(1, 1), 0);
assert_eq!(gf4.add(2, 2), 0);
assert_eq!(gf4.add(3, 3), 0);
for a in 1..4u32 {
let inv_a = gf4.inv(a);
assert_eq!(gf4.mul(a, inv_a), 1, "a={}, inv={}", a, inv_a);
}
}
#[test]
fn test_gf9_extension() {
let gf9 = GfTables::new_extension(9).unwrap();
assert_eq!(gf9.order(), 9);
assert_eq!(gf9.characteristic(), 3);
assert_eq!(gf9.degree(), 2);
for a in 0..9u32 {
assert_eq!(gf9.add(a, 0), a);
assert_eq!(gf9.add(a, gf9.neg(a)), 0);
assert_eq!(gf9.mul(a, 1), a);
if a != 0 {
assert_eq!(gf9.mul(a, gf9.inv(a)), 1);
}
}
}
#[test]
fn test_not_prime_power() {
assert!(GfTables::new_prime(6).is_err());
assert!(GfTables::new_extension(6).is_err());
assert!(GfTables::new_extension(10).is_err());
}
}