use std::ffi::CString;
use std::fmt;
use std::sync::Arc;
use super::error::FiniteFieldError;
use crate::flint::ffi;
pub const MAX_EXTENSION_DEGREE: i64 = 64;
struct FqCtx {
buf: Box<ffi::FqNmodCtxBuf>,
}
unsafe impl Send for FqCtx {}
unsafe impl Sync for FqCtx {}
impl FqCtx {
const SENTINEL: u8 = 0xAA;
fn blank() -> Box<ffi::FqNmodCtxBuf> {
Box::new(ffi::FqNmodCtxBuf([Self::SENTINEL; 512]))
}
fn conway(p: u64, k: i64, var: &CString) -> Self {
let mut buf = Self::blank();
crate::flint::note_thread_uses_flint();
unsafe { ffi::fq_nmod_ctx_init_ui(&mut *buf, p, k, var.as_ptr()) };
Self { buf }
}
fn from_modulus(modulus: &ffi::NmodPolyStruct, var: &CString) -> Self {
let mut buf = Self::blank();
crate::flint::note_thread_uses_flint();
unsafe { ffi::fq_nmod_ctx_init_modulus(&mut *buf, modulus, var.as_ptr()) };
Self { buf }
}
fn as_ptr(&self) -> *const ffi::FqNmodCtxBuf {
&*self.buf
}
#[cfg(test)]
fn untouched_tail(&self) -> usize {
self.buf
.0
.iter()
.rev()
.take_while(|&&b| b == Self::SENTINEL)
.count()
}
}
impl Drop for FqCtx {
fn drop(&mut self) {
unsafe { ffi::fq_nmod_ctx_clear(&mut *self.buf) };
}
}
struct FieldInner {
p: u64,
k: usize,
ctx: Option<FqCtx>,
modulus: Vec<u64>,
var: String,
}
#[derive(Clone)]
pub struct FiniteField {
inner: Arc<FieldInner>,
}
impl FiniteField {
pub fn prime(p: u64) -> Result<Self, FiniteFieldError> {
if p < 2 || unsafe { ffi::n_is_prime(p) } == 0 {
return Err(FiniteFieldError::NotPrime {
modulus: p.to_string(),
});
}
Ok(Self {
inner: Arc::new(FieldInner {
p,
k: 1,
ctx: None,
modulus: Vec::new(),
var: "a".to_string(),
}),
})
}
pub fn prime_wide(p: u128) -> Result<Self, FiniteFieldError> {
Self::prime_from_decimal(&p.to_string())
}
pub fn prime_from_decimal(decimal: &str) -> Result<Self, FiniteFieldError> {
let trimmed = decimal.trim();
match trimmed.parse::<u64>() {
Ok(p) => Self::prime(p),
Err(_) if trimmed.starts_with('-') || !trimmed.bytes().all(|b| b.is_ascii_digit()) => {
Err(FiniteFieldError::NotPrime {
modulus: trimmed.to_string(),
})
}
Err(_) => Err(FiniteFieldError::ModulusTooLarge {
modulus: trimmed.to_string(),
}),
}
}
pub fn extension(p: u64, k: u32) -> Result<Self, FiniteFieldError> {
let kd = i64::from(k);
if !(1..=MAX_EXTENSION_DEGREE).contains(&kd) {
return Err(FiniteFieldError::DegreeOutOfRange { degree: kd });
}
let base = Self::prime(p)?;
if kd == 1 {
return Ok(base);
}
let var = CString::new("a").expect("literal has no NUL");
let ctx = FqCtx::conway(p, kd, &var);
let modulus = read_ctx_modulus(&ctx);
Ok(Self {
inner: Arc::new(FieldInner {
p,
k: k as usize,
ctx: Some(ctx),
modulus,
var: "a".to_string(),
}),
})
}
pub fn with_defining_polynomial(p: u64, modulus: &[u64]) -> Result<Self, FiniteFieldError> {
let base = Self::prime(p)?;
let mut coeffs: Vec<u64> = modulus.iter().map(|c| c % p).collect();
while coeffs.last() == Some(&0) {
coeffs.pop();
}
if coeffs.len() < 2 {
return Err(FiniteFieldError::BadDefiningPolynomial {
reason: format!(
"degree {} after reduction mod {p}; a defining polynomial must have degree >= 1",
coeffs.len() as i64 - 1
),
});
}
let deg = (coeffs.len() - 1) as i64;
if deg > MAX_EXTENSION_DEGREE {
return Err(FiniteFieldError::DegreeOutOfRange { degree: deg });
}
if deg == 1 {
return Ok(base);
}
let lead = *coeffs.last().expect("length >= 2 was checked");
if lead != 1 {
return Err(FiniteFieldError::BadDefiningPolynomial {
reason: format!(
"leading coefficient is {lead}, not 1; scale the polynomial by the \
inverse of {lead} mod {p} to make it monic"
),
});
}
let mut poly = NmodPoly::new(p);
for (i, &c) in coeffs.iter().enumerate() {
poly.set_coeff(i, c);
}
if unsafe { ffi::nmod_poly_is_irreducible(poly.as_ptr()) } == 0 {
return Err(FiniteFieldError::BadDefiningPolynomial {
reason: format!(
"{} is reducible over GF({p}); the quotient ring would have zero divisors",
render_poly(&coeffs, "x")
),
});
}
let var = CString::new("a").expect("literal has no NUL");
let ctx = FqCtx::from_modulus(poly.as_ptr_ref(), &var);
let stored = read_ctx_modulus(&ctx);
Ok(Self {
inner: Arc::new(FieldInner {
p,
k: deg as usize,
ctx: Some(ctx),
modulus: stored,
var: "a".to_string(),
}),
})
}
pub fn characteristic(&self) -> u64 {
self.inner.p
}
pub fn degree(&self) -> usize {
self.inner.k
}
pub fn order(&self) -> Option<u128> {
u128::from(self.inner.p).checked_pow(self.inner.k as u32)
}
pub fn is_prime_field(&self) -> bool {
self.inner.k == 1
}
pub fn defining_polynomial(&self) -> &[u64] {
&self.inner.modulus
}
pub(crate) fn ctx_ptr(&self) -> *const ffi::FqNmodCtxBuf {
self.inner
.ctx
.as_ref()
.expect("ctx_ptr called on a prime field")
.as_ptr()
}
#[cfg(test)]
pub(crate) fn ctx_untouched_tail(&self) -> Option<usize> {
self.inner.ctx.as_ref().map(FqCtx::untouched_tail)
}
pub fn element(&self, coeffs: &[u64]) -> Result<FieldElement, FiniteFieldError> {
if coeffs.len() > self.inner.k {
return Err(FiniteFieldError::MalformedElement {
got: coeffs.len(),
degree: self.inner.k,
});
}
let mut v: Vec<u64> = coeffs.iter().map(|c| c % self.inner.p).collect();
while v.last() == Some(&0) {
v.pop();
}
Ok(FieldElement { coeffs: v })
}
pub fn scalar(&self, x: u64) -> FieldElement {
let c = x % self.inner.p;
FieldElement {
coeffs: if c == 0 { Vec::new() } else { vec![c] },
}
}
pub fn zero(&self) -> FieldElement {
FieldElement { coeffs: Vec::new() }
}
pub fn one(&self) -> FieldElement {
FieldElement { coeffs: vec![1] }
}
pub fn generator(&self) -> Option<FieldElement> {
(self.inner.k > 1).then(|| FieldElement { coeffs: vec![0, 1] })
}
pub fn render(&self, e: &FieldElement) -> String {
if self.inner.k == 1 {
return e.coeffs.first().copied().unwrap_or(0).to_string();
}
render_poly(&e.coeffs, &self.inner.var)
}
}
impl PartialEq for FiniteField {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
|| (self.inner.p == other.inner.p
&& self.inner.k == other.inner.k
&& self.inner.modulus == other.inner.modulus)
}
}
impl Eq for FiniteField {}
impl fmt::Display for FiniteField {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.inner.k == 1 {
write!(f, "GF({})", self.inner.p)
} else {
write!(f, "GF({}^{})", self.inner.p, self.inner.k)
}
}
}
impl fmt::Debug for FiniteField {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.inner.k == 1 {
write!(f, "GF({})", self.inner.p)
} else {
write!(
f,
"GF({}^{}) [{}]",
self.inner.p,
self.inner.k,
render_poly(&self.inner.modulus, "x")
)
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct FieldElement {
coeffs: Vec<u64>,
}
impl FieldElement {
pub fn coefficients(&self) -> &[u64] {
&self.coeffs
}
pub fn coefficients_padded(&self, k: usize) -> Vec<u64> {
let mut v = self.coeffs.clone();
v.resize(k, 0);
v
}
pub fn is_zero(&self) -> bool {
self.coeffs.is_empty()
}
pub fn as_u64(&self) -> Option<u64> {
match self.coeffs.len() {
0 => Some(0),
1 => Some(self.coeffs[0]),
_ => None,
}
}
}
pub(crate) struct NmodPoly {
inner: ffi::NmodPolyStruct,
}
unsafe impl Send for NmodPoly {}
unsafe impl Sync for NmodPoly {}
impl NmodPoly {
pub(crate) fn new(p: u64) -> Self {
let mut inner: ffi::NmodPolyStruct = unsafe { std::mem::zeroed() };
crate::flint::note_thread_uses_flint();
unsafe { ffi::nmod_poly_init(&mut inner, p) };
Self { inner }
}
pub(crate) fn set_coeff(&mut self, i: usize, c: u64) {
unsafe { ffi::nmod_poly_set_coeff_ui(&mut self.inner, i as ffi::slong, c) };
}
pub(crate) fn as_ptr(&self) -> *const ffi::NmodPolyStruct {
&self.inner
}
pub(crate) fn as_ptr_ref(&self) -> &ffi::NmodPolyStruct {
&self.inner
}
pub(crate) fn as_mut_ptr(&mut self) -> *mut ffi::NmodPolyStruct {
&mut self.inner
}
pub(crate) fn degree(&self) -> i64 {
unsafe { ffi::nmod_poly_degree(&self.inner) }
}
pub(crate) fn coeff(&self, i: i64) -> u64 {
unsafe { ffi::nmod_poly_get_coeff_ui(&self.inner, i) }
}
pub(crate) fn to_coefficients(&self) -> Vec<u64> {
let d = self.degree();
if d < 0 {
return Vec::new();
}
(0..=d).map(|i| self.coeff(i)).collect()
}
}
impl Drop for NmodPoly {
fn drop(&mut self) {
unsafe { ffi::nmod_poly_clear(&mut self.inner) };
}
}
fn read_ctx_modulus(ctx: &FqCtx) -> Vec<u64> {
unsafe {
let m = ffi::fq_nmod_ctx_modulus(ctx.as_ptr());
let d = ffi::nmod_poly_degree(m);
if d < 0 {
return Vec::new();
}
(0..=d).map(|i| ffi::nmod_poly_get_coeff_ui(m, i)).collect()
}
}
fn render_poly(coeffs: &[u64], v: &str) -> String {
let mut parts: Vec<String> = Vec::new();
for (i, &c) in coeffs.iter().enumerate().rev() {
if c == 0 {
continue;
}
parts.push(match (i, c) {
(0, _) => c.to_string(),
(1, 1) => v.to_string(),
(1, _) => format!("{c}*{v}"),
(_, 1) => format!("{v}^{i}"),
_ => format!("{c}*{v}^{i}"),
});
}
if parts.is_empty() {
"0".to_string()
} else {
parts.join(" + ")
}
}