use super::extension::{Components, Extension, ExtensionSpec, Relation};
use super::finite_field::FiniteField;
use super::mersenne31;
use super::{Elem, HasGenerator, Mersenne31, Validate};
pub const MODULUS: u32 = mersenne31::MODULUS;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct ComplexMersenne31;
impl ExtensionSpec<2> for ComplexMersenne31 {
type Base = Mersenne31;
type Storage = Components;
const NAME: &'static str = "GF((2^31 - 1)^2)";
const RELATION: Relation = Relation::from_coordinates(&[1, 0]);
}
pub type QuadMersenne31 = FiniteField<0x7FFF_FFFF, 2, Extension<ComplexMersenne31, 2>>;
#[inline]
#[must_use]
const fn limb_canonical(x: u32) -> u32 {
mersenne31::reduce(x)
}
#[inline]
#[must_use]
const fn limb_add(a: u32, b: u32) -> u32 {
let s = a + b;
if s >= MODULUS { s - MODULUS } else { s }
}
#[inline]
#[must_use]
const fn limb_sub(a: u32, b: u32) -> u32 {
let s = a + (MODULUS - b);
if s >= MODULUS { s - MODULUS } else { s }
}
#[inline]
#[must_use]
const fn limb_neg(a: u32) -> u32 {
if a == 0 { 0 } else { MODULUS - a }
}
#[inline]
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_lossless)]
const fn limb_mul(a: u32, b: u32) -> u32 {
let prod = a as u64 * b as u64;
let lo = (prod as u32) & MODULUS;
let hi = (prod >> 31) as u32;
mersenne31::reduce(lo + hi)
}
#[inline]
#[must_use]
pub(crate) const fn raw_canonical(value: [u32; 2]) -> [u32; 2] {
[limb_canonical(value[0]), limb_canonical(value[1])]
}
#[inline]
#[must_use]
pub(crate) const fn raw_add(left: [u32; 2], right: [u32; 2]) -> [u32; 2] {
[limb_add(left[0], right[0]), limb_add(left[1], right[1])]
}
#[inline]
#[must_use]
pub(crate) const fn raw_sub(left: [u32; 2], right: [u32; 2]) -> [u32; 2] {
[limb_sub(left[0], right[0]), limb_sub(left[1], right[1])]
}
#[inline]
#[must_use]
pub(crate) const fn raw_neg(value: [u32; 2]) -> [u32; 2] {
[limb_neg(value[0]), limb_neg(value[1])]
}
#[inline]
#[must_use]
#[allow(clippy::cast_lossless)]
pub(crate) const fn raw_mul(left: [u32; 2], right: [u32; 2]) -> [u32; 2] {
let ar = left[0] as u64;
let ai = left[1] as u64;
let br = right[0] as u64;
let bi = right[1] as u64;
[
reduce_product_sum(ar * br + ai * (MODULUS as u64 - bi)),
reduce_product_sum(ar * bi + ai * br),
]
}
#[inline]
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_lossless)]
const fn reduce_product_sum(value: u64) -> u32 {
let folded = (value & MODULUS as u64) + (value >> 31);
let folded = ((folded as u32) & MODULUS) + (folded >> 31) as u32;
if folded >= MODULUS {
folded - MODULUS
} else {
folded
}
}
#[inline]
#[must_use]
pub(crate) const fn raw_square(value: [u32; 2]) -> [u32; 2] {
let a2 = limb_mul(value[0], value[0]);
let b2 = limb_mul(value[1], value[1]);
let ab = limb_mul(value[0], value[1]);
[limb_sub(a2, b2), limb_add(ab, ab)]
}
#[inline]
#[must_use]
const fn raw_norm(value: [u32; 2]) -> u32 {
let re2 = limb_mul(value[0], value[0]);
let im2 = limb_mul(value[1], value[1]);
limb_add(re2, im2)
}
#[inline]
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_lossless)]
pub(crate) const fn raw_inv(value: [u32; 2]) -> [u32; 2] {
let value = raw_canonical(value);
let norm = raw_norm(value);
if norm == 0 {
return [0, 0];
}
let norm_inv = super::powmod(norm as u64, (MODULUS - 2) as u64, MODULUS as u64) as u32;
[
limb_mul(value[0], norm_inv),
limb_mul(limb_neg(value[1]), norm_inv),
]
}
const fn raw_pow(mut base: [u32; 2], mut exponent: u64) -> [u32; 2] {
base = raw_canonical(base);
let mut result = [1, 0];
while exponent != 0 {
if exponent & 1 != 0 {
result = raw_mul(result, base);
}
base = raw_square(base);
exponent >>= 1;
}
result
}
const fn is_raw_one(value: [u32; 2]) -> bool {
limb_canonical(value[0]) == 1 && limb_canonical(value[1]) == 0
}
const GROUP_FACTORS: [u64; 7] = [2, 3, 7, 11, 31, 151, 331];
const fn is_primitive(candidate: [u32; 2]) -> bool {
let order = (MODULUS as u64) * (MODULUS as u64) - 1;
if !is_raw_one(raw_pow(candidate, order)) {
return false;
}
let mut i = 0;
while i < GROUP_FACTORS.len() {
if is_raw_one(raw_pow(candidate, order / GROUP_FACTORS[i])) {
return false;
}
i += 1;
}
true
}
impl HasGenerator for QuadMersenne31 {
const GENERATOR_RAW: [u32; 2] = {
assert!(
is_primitive([1, 12]),
"QuadMersenne31 generator does not have full order"
);
[1, 12]
};
}
impl Elem<QuadMersenne31> {
pub const I: Self = Elem { raw: [0, 1] };
#[inline]
#[must_use]
pub const fn from_raw(value: [u32; 2]) -> Self {
let () = Validate::<QuadMersenne31>::OK;
Elem {
raw: raw_canonical(value),
}
}
#[inline]
#[must_use]
pub const fn from_bytes(bytes: [u8; 8]) -> Self {
let re = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
let im = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
Self::from_raw([re, im])
}
#[inline]
#[must_use]
pub const fn to_bytes(self) -> [u8; 8] {
let re = self.raw[0].to_le_bytes();
let im = self.raw[1].to_le_bytes();
[re[0], re[1], re[2], re[3], im[0], im[1], im[2], im[3]]
}
#[inline]
#[must_use]
pub const fn from_components(components: [Elem<Mersenne31>; 2]) -> Self {
Elem {
raw: raw_canonical([components[0].raw, components[1].raw]),
}
}
#[inline]
#[must_use]
pub const fn to_components(self) -> [Elem<Mersenne31>; 2] {
[
Elem::<Mersenne31> { raw: self.raw[0] },
Elem::<Mersenne31> { raw: self.raw[1] },
]
}
#[inline]
#[must_use]
pub const fn conjugate(self) -> Self {
Elem {
raw: [self.raw[0], limb_neg(self.raw[1])],
}
}
#[inline]
#[must_use]
pub const fn norm(self) -> Elem<Mersenne31> {
Elem::<Mersenne31> {
raw: raw_norm(self.raw),
}
}
#[inline]
#[must_use]
pub const fn add(self, rhs: Self) -> Self {
Elem {
raw: raw_add(self.raw, rhs.raw),
}
}
#[inline]
#[must_use]
pub const fn sub(self, rhs: Self) -> Self {
Elem {
raw: raw_sub(self.raw, rhs.raw),
}
}
#[inline]
#[must_use]
pub const fn neg(self) -> Self {
Elem {
raw: raw_neg(self.raw),
}
}
#[inline]
#[must_use]
pub const fn mul(self, rhs: Self) -> Self {
Elem {
raw: raw_mul(self.raw, rhs.raw),
}
}
#[inline]
#[must_use]
pub const fn square(self) -> Self {
Elem {
raw: raw_square(self.raw),
}
}
#[inline]
#[must_use]
pub const fn inv(self) -> Self {
Elem {
raw: raw_inv(self.raw),
}
}
#[inline]
#[must_use]
pub const fn div(self, rhs: Self) -> Self {
if rhs.raw[0] == 0 && rhs.raw[1] == 0 {
return Self::ZERO;
}
self.mul(rhs.inv())
}
#[inline]
#[must_use]
pub const fn pow(self, mut exponent: u128) -> Self {
let mut base = self;
let mut result = Self::ONE;
while exponent != 0 {
if exponent & 1 != 0 {
result = result.mul(base);
}
base = base.square();
exponent >>= 1;
}
result
}
#[inline]
#[must_use]
pub const fn canonical(self) -> Self {
self
}
#[inline]
#[must_use]
pub const fn is_zero(self) -> bool {
self.raw[0] == 0 && self.raw[1] == 0
}
#[inline]
#[must_use]
pub const fn is_one(self) -> bool {
self.raw[0] == 1 && self.raw[1] == 0
}
}
#[cfg(test)]
mod tests {
use super::{Elem, HasGenerator, MODULUS, QuadMersenne31, is_primitive};
#[test]
fn deferred_product_sums_match_integer_remainders() {
let p = u64::from(MODULUS);
for value in [0, 1, p - 1, p, p + 1, p * p - 1, p * p, 2 * p * p - 1] {
assert_eq!(
u64::from(super::reduce_product_sum(value)),
value % p,
"product sum {value}"
);
}
}
const fn gcd(mut a: u64, mut b: u64) -> u64 {
while b != 0 {
(a, b) = (b, a % b);
}
a
}
#[test]
fn primitivity_follows_generator_power_coprimality() {
let generator =
Elem::<QuadMersenne31>::from_raw(core::hint::black_box(QuadMersenne31::GENERATOR_RAW));
let order = u64::from(MODULUS) * u64::from(MODULUS) - 1;
for k in 1..=48u64 {
let candidate = generator.pow(u128::from(k)).to_raw();
assert_eq!(
is_primitive(core::hint::black_box(candidate)),
gcd(k, order) == 1,
"(1 + 12i)^{k}"
);
}
}
#[test]
fn primitivity_rejects_small_orders() {
for candidate in [[0, 0], [1, 0], [7, 0], [MODULUS - 1, 0], [0, 1]] {
assert!(
!is_primitive(core::hint::black_box(candidate)),
"{candidate:?}"
);
}
}
}