use crate::scalar::{ExactFieldScalar, Fp, Fpn};
use std::collections::BTreeSet;
use std::fmt;
pub trait FiniteFieldInvariantField: ExactFieldScalar + Copy {
fn characteristic_prime() -> u128;
fn absolute_degree() -> usize;
fn field_order_checked() -> Option<u128>;
}
impl<const P: u128> FiniteFieldInvariantField for Fp<P> {
fn characteristic_prime() -> u128 {
P
}
fn absolute_degree() -> usize {
1
}
fn field_order_checked() -> Option<u128> {
Fp::<P>::modulus_is_prime().then_some(P)
}
}
impl<const P: u128, const N: usize> FiniteFieldInvariantField for Fpn<P, N> {
fn characteristic_prime() -> u128 {
P
}
fn absolute_degree() -> usize {
N
}
fn field_order_checked() -> Option<u128> {
Fpn::<P, N>::field_order_checked()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FiniteFieldNumericInvariants {
pub characteristic: u128,
pub absolute_degree: usize,
pub field_order: u128,
pub level: usize,
pub pythagoras_number: usize,
pub u_invariant: usize,
}
impl FiniteFieldNumericInvariants {
pub fn display(&self) -> String {
self.to_string()
}
}
impl fmt::Display for FiniteFieldNumericInvariants {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"FiniteFieldNumericInvariants(characteristic={}, absolute_degree={}, field_order={}, level={}, pythagoras_number={}, u_invariant={})",
self.characteristic,
self.absolute_degree,
self.field_order,
self.level,
self.pythagoras_number,
self.u_invariant,
)
}
}
pub fn finite_field_numeric_invariants<F: FiniteFieldInvariantField>(
) -> Option<FiniteFieldNumericInvariants> {
let characteristic = F::characteristic_prime();
let absolute_degree = F::absolute_degree();
let field_order = F::field_order_checked()?;
let even = characteristic == 2;
Some(FiniteFieldNumericInvariants {
characteristic,
absolute_degree,
field_order,
level: if even || field_order % 4 == 1 { 1 } else { 2 },
pythagoras_number: if even { 1 } else { 2 },
u_invariant: 2,
})
}
pub fn level_finite<F: FiniteFieldInvariantField>() -> Option<usize> {
finite_field_numeric_invariants::<F>().map(|report| report.level)
}
pub fn pythagoras_number_finite<F: FiniteFieldInvariantField>() -> Option<usize> {
finite_field_numeric_invariants::<F>().map(|report| report.pythagoras_number)
}
pub fn u_invariant_finite<F: FiniteFieldInvariantField>() -> Option<usize> {
finite_field_numeric_invariants::<F>().map(|report| report.u_invariant)
}
fn squares_mod<const P: u128>() -> Vec<u128> {
(0..P).map(|x| (x * x) % P).collect()
}
fn sums_of_n_squares<const P: u128>(n: usize) -> BTreeSet<u128> {
if n == 0 {
return BTreeSet::from([0]);
}
let squares = squares_mod::<P>();
let mut cur: BTreeSet<u128> = squares.iter().copied().collect();
for _ in 1..n {
let mut next = BTreeSet::new();
for &a in &cur {
for &s in &squares {
next.insert((a + s) % P);
}
}
cur = next;
}
cur
}
pub fn is_sum_of_n_squares<const P: u128>(x: Fp<P>, n: usize) -> bool {
sums_of_n_squares::<P>(n).contains(&(x.value() % P))
}
pub fn level<const P: u128>() -> Option<usize> {
level_finite::<Fp<P>>()
}
pub fn pythagoras_number<const P: u128>() -> Option<usize> {
pythagoras_number_finite::<Fp<P>>()
}
#[cfg(test)]
fn is_anisotropic<const P: u128>(qs: &[u128]) -> bool {
let dim = qs.len();
let mut total = 1u128;
for _ in 0..dim {
total *= P;
}
for code in 1..total {
let mut c = code;
let mut s = 0u128;
for &q in qs {
let xi = c % P;
c /= P;
s = (s + q * ((xi * xi) % P)) % P;
}
if s == 0 {
return false; }
}
true
}
#[cfg(test)]
fn exists_anisotropic_form<const P: u128>(dim: usize) -> bool {
let mut total = 1u128;
for _ in 0..dim {
total *= P - 1;
}
for code in 0..total {
let mut c = code;
let mut qs = Vec::with_capacity(dim);
for _ in 0..dim {
qs.push(1 + c % (P - 1)); c /= P - 1;
}
if is_anisotropic::<P>(&qs) {
return true;
}
}
false
}
pub fn u_invariant<const P: u128>() -> Option<usize> {
u_invariant_finite::<Fp<P>>()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn level_of_finite_fields() {
assert_eq!(level::<2>(), Some(1)); assert_eq!(level::<3>(), Some(2)); assert_eq!(level::<5>(), Some(1)); assert_eq!(level::<7>(), Some(2)); assert_eq!(level::<13>(), Some(1)); assert_eq!(level::<9>(), None); }
#[test]
fn pythagoras_number_of_finite_fields() {
assert_eq!(pythagoras_number::<2>(), Some(1)); assert_eq!(pythagoras_number::<3>(), Some(2));
assert_eq!(pythagoras_number::<5>(), Some(2));
assert_eq!(pythagoras_number::<7>(), Some(2));
}
#[test]
fn u_invariant_of_finite_fields_is_two() {
assert_eq!(u_invariant::<3>(), Some(2));
assert_eq!(u_invariant::<5>(), Some(2));
assert_eq!(u_invariant::<7>(), Some(2));
assert_eq!(u_invariant::<2>(), Some(2));
}
#[test]
fn sum_of_squares_spot_checks() {
assert!(is_sum_of_n_squares::<3>(Fp::<3>::from_u128(2), 2)); assert!(!is_sum_of_n_squares::<3>(Fp::<3>::from_u128(2), 1)); assert!(is_sum_of_n_squares::<5>(Fp::<5>::from_u128(4), 1)); }
#[test]
fn extension_field_reports_use_field_order_not_characteristic_alone() {
let f4 = finite_field_numeric_invariants::<Fpn<2, 2>>().unwrap();
assert_eq!((f4.level, f4.pythagoras_number, f4.u_invariant), (1, 1, 2));
assert_eq!((f4.absolute_degree, f4.field_order), (2, 4));
let f9 = finite_field_numeric_invariants::<Fpn<3, 2>>().unwrap();
assert_eq!((f9.level, f9.pythagoras_number, f9.u_invariant), (1, 2, 2));
let f27 = finite_field_numeric_invariants::<Fpn<3, 3>>().unwrap();
assert_eq!(
(f27.level, f27.pythagoras_number, f27.u_invariant),
(2, 2, 2)
);
}
#[test]
fn degree_one_extension_and_prime_reports_agree() {
assert_eq!(
finite_field_numeric_invariants::<Fp<5>>(),
finite_field_numeric_invariants::<Fpn<5, 1>>()
);
}
#[test]
fn formula_reports_agree_with_small_prime_field_searches() {
for (p, expected_level, expected_pythagoras) in [(2, 1, 1), (3, 2, 2), (5, 1, 2), (7, 2, 2)]
{
let searched_level = (1..=4)
.find(|&n| match p {
2 => sums_of_n_squares::<2>(n).contains(&1),
3 => sums_of_n_squares::<3>(n).contains(&2),
5 => sums_of_n_squares::<5>(n).contains(&4),
7 => sums_of_n_squares::<7>(n).contains(&6),
_ => unreachable!(),
})
.unwrap();
assert_eq!(searched_level, expected_level);
assert_eq!(level_runtime_for_test(p), Some(expected_level));
assert_eq!(pythagoras_runtime_for_test(p), Some(expected_pythagoras));
}
assert!(exists_anisotropic_form::<3>(2));
assert!(!exists_anisotropic_form::<3>(3));
}
fn level_runtime_for_test(p: u128) -> Option<usize> {
match p {
2 => level::<2>(),
3 => level::<3>(),
5 => level::<5>(),
7 => level::<7>(),
_ => None,
}
}
fn pythagoras_runtime_for_test(p: u128) -> Option<usize> {
match p {
2 => searched_pythagoras::<2>(),
3 => searched_pythagoras::<3>(),
5 => searched_pythagoras::<5>(),
7 => searched_pythagoras::<7>(),
_ => None,
}
}
fn searched_pythagoras<const P: u128>() -> Option<usize> {
if !Fp::<P>::modulus_is_prime() {
return None;
}
let mut previous = sums_of_n_squares::<P>(1);
for n in 1..=(P as usize + 1) {
let next = sums_of_n_squares::<P>(n + 1);
if next == previous {
return Some(n);
}
previous = next;
}
None
}
#[test]
fn unsupported_field_parameters_return_none() {
assert_eq!(finite_field_numeric_invariants::<Fp<9>>(), None);
assert_eq!(finite_field_numeric_invariants::<Fpn<9, 2>>(), None);
assert_eq!(finite_field_numeric_invariants::<Fpn<3, 0>>(), None);
}
#[test]
fn numeric_report_has_canonical_display() {
let report = finite_field_numeric_invariants::<Fpn<3, 2>>().unwrap();
assert_eq!(
report.to_string(),
"FiniteFieldNumericInvariants(characteristic=3, absolute_degree=2, field_order=9, level=1, pythagoras_number=2, u_invariant=2)"
);
assert_eq!(report.display(), report.to_string());
}
}