pub mod binary;
pub mod embedding;
pub mod extension;
pub mod finite_field;
pub mod goldilocks;
pub mod mersenne31;
pub mod prime;
pub mod quad_mersenne31;
pub use binary::cantor;
pub use binary::description;
pub use binary::normal;
pub use binary::poly;
pub use binary::tower;
pub use binary::tower::{
FanPaar2, FanPaar4, FanPaar8, FanPaar16, FanPaar32, FanPaar64, Rijndael16, Rijndael32,
Rijndael64,
};
pub use binary::{
AES, BinaryDegree, BinaryDescription, BinaryField, BinaryRepr, ByteLogExp, Cantor, Normal,
Polynomial, RS,
};
pub use embedding::{Embedding, EmbeddingError, Isomorphism};
pub use extension::{
Components, Extension, ExtensionField, ExtensionSpec, ExtensionStorage, Packed, PackedPadded,
PackedWord, Relation,
};
pub use finite_field::{FiniteField, FiniteFieldRepr, Residue};
pub use goldilocks::{Goldilocks, GoldilocksFold};
pub use mersenne31::{Mersenne31, MersenneFold};
pub use prime::PrimeField;
pub use quad_mersenne31::{ComplexMersenne31, QuadMersenne31};
mod private {
use super::Field;
use super::extension::Descriptor;
pub trait Sealed {}
pub trait FieldSeal {}
pub trait PrimeFieldSeal {}
pub(crate) trait RowAccess: 'static {
const DESCRIPTOR: &'static Descriptor;
const BINARY_DESCRIPTION: Option<&'static super::binary::description::BinaryDescription>;
fn prime_coordinates_into(dst: &mut [u64], raw: <Self as Field>::Raw)
where
Self: Field;
fn from_prime_coordinates(src: &[u64]) -> <Self as Field>::Raw
where
Self: Field;
fn from_prime(value: u64) -> <Self as Field>::Raw
where
Self: Field;
fn to_prime(raw: <Self as Field>::Raw) -> Option<u64>
where
Self: Field;
}
}
pub(crate) const fn check_residue(value: u64, characteristic: u64) -> Result<u64, RangeError> {
if value < characteristic {
Ok(value)
} else {
Err(RangeError)
}
}
pub(crate) fn coordinates_index(coordinates: &[u64], characteristic: u64) -> u128 {
let mut index = 0u128;
let mut place = 1u128;
let mut i = 0;
while i < coordinates.len() {
index += u128::from(coordinates[i]) * place;
place *= u128::from(characteristic);
i += 1;
}
index
}
pub(crate) fn index_coordinates(index: u128, degree: u32, characteristic: u64) -> [u64; 64] {
let mut coordinates = [0u64; 64];
let mut rest = index;
let mut i = 0;
while i < degree {
let modulus = u128::from(characteristic);
#[allow(clippy::cast_possible_truncation)]
let digit = (rest % modulus) as u64;
coordinates[i as usize] = digit;
rest /= modulus;
i += 1;
}
coordinates
}
mod element_private {
pub trait Sealed {}
}
#[allow(clippy::cast_possible_truncation, clippy::cast_lossless)]
pub(crate) const fn powmod(mut base: u64, mut exponent: u64, modulus: u64) -> u64 {
let mut result = 1 % modulus;
base %= modulus;
while exponent > 0 {
if exponent & 1 == 1 {
result = ((result as u128 * base as u128) % modulus as u128) as u64;
}
base = ((base as u128 * base as u128) % modulus as u128) as u64;
exponent >>= 1;
}
result
}
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_lossless)]
pub const fn is_prime(candidate: u64) -> bool {
const SMALL: [u64; 12] = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37];
const WITNESSES: [u64; 7] = [2, 325, 9375, 28_178, 450_775, 9_780_504, 1_795_265_022];
if candidate < 2 {
return false;
}
let mut i = 0;
while i < SMALL.len() {
let prime = SMALL[i];
if candidate == prime {
return true;
}
if candidate.is_multiple_of(prime) {
return false;
}
i += 1;
}
let mut odd = candidate - 1;
let mut shifts = 0u32;
while odd.is_multiple_of(2) {
odd /= 2;
shifts += 1;
}
let mut i = 0;
while i < WITNESSES.len() {
let residue = WITNESSES[i] % candidate;
i += 1;
if residue == 0 {
continue;
}
let mut trial = powmod(residue, odd, candidate);
if trial == 1 || trial == candidate - 1 {
continue;
}
let mut round = 1;
let mut composite = true;
while round < shifts {
trial = ((trial as u128 * trial as u128) % candidate as u128) as u64;
if trial == candidate - 1 {
composite = false;
break;
}
round += 1;
}
if composite {
return false;
}
}
true
}
pub trait PrimeIdentity: private::Sealed + Copy + 'static {
const CHARACTERISTIC: u64;
const VALID: ();
const BITS: u32;
const TWO_ADICITY: u32;
const RESIDUE_MOD_8: u8;
}
#[derive(Clone, Copy, Debug)]
pub struct PrimeCharacteristic<const P: u64>;
impl<const P: u64> private::Sealed for PrimeCharacteristic<P> {}
impl<const P: u64> PrimeIdentity for PrimeCharacteristic<P> {
const CHARACTERISTIC: u64 = {
assert!(is_prime(P), "characteristic is not prime");
P
};
const VALID: () = assert!(is_prime(P), "characteristic is not prime");
#[allow(clippy::cast_possible_truncation)]
const BITS: u32 = {
assert!(is_prime(P), "characteristic is not prime");
64 - P.leading_zeros()
};
const TWO_ADICITY: u32 = {
assert!(is_prime(P), "characteristic is not prime");
(P - 1).trailing_zeros()
};
#[allow(clippy::cast_possible_truncation)]
const RESIDUE_MOD_8: u8 = {
assert!(is_prime(P), "characteristic is not prime");
(P % 8) as u8
};
}
#[allow(private_bounds)]
pub trait Field: private::RowAccess + Copy + core::fmt::Debug + 'static {
type Raw: Copy + Eq + Ord + core::hash::Hash + core::fmt::Debug + 'static;
type Characteristic: PrimeIdentity;
const NAME: &'static str;
const DEGREE: u32;
const ORDER: u128;
const ZERO_RAW: Self::Raw;
const ONE_RAW: Self::Raw;
const VALID: ();
fn canonical_raw(raw: Self::Raw) -> Self::Raw;
fn add_raw(left: Self::Raw, right: Self::Raw) -> Self::Raw;
fn sub_raw(left: Self::Raw, right: Self::Raw) -> Self::Raw;
fn neg_raw(value: Self::Raw) -> Self::Raw;
fn mul_raw(left: Self::Raw, right: Self::Raw) -> Self::Raw;
fn inv_raw(value: Self::Raw) -> Self::Raw;
}
pub(crate) struct Validate<F>(core::marker::PhantomData<F>);
impl<F: Field> Validate<F> {
pub(crate) const OK: () = {
let characteristic = <F::Characteristic as PrimeIdentity>::CHARACTERISTIC;
let order = finite_field::metadata_order(characteristic, F::DEGREE);
assert!(order == F::ORDER, "ORDER is not CHARACTERISTIC^DEGREE");
let () = F::VALID;
};
}
pub struct Elem<F: Field> {
pub(crate) raw: F::Raw,
}
#[allow(clippy::expl_impl_clone_on_copy)]
impl<F: Field> Clone for Elem<F> {
#[inline]
fn clone(&self) -> Self {
*self
}
}
impl<F: Field> Copy for Elem<F> {}
impl<F: Field> PartialEq for Elem<F> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.raw == other.raw
}
}
impl<F: Field> Eq for Elem<F> {}
impl<F: Field> core::hash::Hash for Elem<F> {
#[inline]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.raw.hash(state);
}
}
impl<F: Field> PartialOrd for Elem<F> {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<F: Field> Ord for Elem<F> {
#[inline]
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.raw.cmp(&other.raw)
}
}
impl<F: Field> core::fmt::Debug for Elem<F> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(formatter, "{}({:?})", F::NAME, self.raw)
}
}
impl<F: Field> Default for Elem<F> {
#[inline]
fn default() -> Self {
Self::ZERO
}
}
impl<F: Field> Elem<F> {
pub const ZERO: Self = {
let () = Validate::<F>::OK;
Elem { raw: F::ZERO_RAW }
};
pub const ONE: Self = {
let () = Validate::<F>::OK;
Elem { raw: F::ONE_RAW }
};
#[inline]
#[must_use]
pub const fn to_raw(self) -> F::Raw {
self.raw
}
}
pub trait HasGenerator: Field + private::FieldSeal {
const GENERATOR_RAW: Self::Raw;
}
impl<F: HasGenerator> Elem<F> {
pub const GENERATOR: Self = {
let () = Validate::<F>::OK;
Elem {
raw: F::GENERATOR_RAW,
}
};
}
#[allow(private_bounds)]
pub trait FieldElem:
element_private::Sealed + Copy + Eq + core::fmt::Debug + core::hash::Hash + Default + 'static
{
type Field: Field;
fn from_raw(raw: <Self::Field as Field>::Raw) -> Self;
const ZERO: Self;
const ONE: Self;
#[must_use]
fn add(self, rhs: Self) -> Self;
#[must_use]
fn sub(self, rhs: Self) -> Self;
#[must_use]
fn neg(self) -> Self;
#[must_use]
fn mul(self, rhs: Self) -> Self;
#[must_use]
fn inv(self) -> Self;
#[must_use]
fn div(self, rhs: Self) -> Self;
#[must_use]
fn square(self) -> Self;
#[must_use]
fn pow(self, exponent: u128) -> Self;
fn is_zero(self) -> bool;
fn is_one(self) -> bool;
fn from_residue(value: u64) -> Result<Self, RangeError>;
#[must_use]
fn to_residue(self) -> Option<u64>;
fn from_index(index: u128) -> Result<Self, RangeError>;
#[must_use]
fn to_index(self) -> u128;
}
impl<F: Field> element_private::Sealed for Elem<F> {}
impl<F: Field> FieldElem for Elem<F> {
type Field = F;
#[inline]
fn from_raw(raw: F::Raw) -> Self {
let () = Validate::<F>::OK;
Elem {
raw: F::canonical_raw(raw),
}
}
const ZERO: Self = Self::ZERO;
const ONE: Self = Self::ONE;
#[inline]
fn add(self, rhs: Self) -> Self {
Elem {
raw: F::add_raw(self.raw, rhs.raw),
}
}
#[inline]
fn sub(self, rhs: Self) -> Self {
Elem {
raw: F::sub_raw(self.raw, rhs.raw),
}
}
#[inline]
fn neg(self) -> Self {
Elem {
raw: F::neg_raw(self.raw),
}
}
#[inline]
fn mul(self, rhs: Self) -> Self {
Elem {
raw: F::mul_raw(self.raw, rhs.raw),
}
}
#[inline]
fn inv(self) -> Self {
Elem {
raw: F::inv_raw(self.raw),
}
}
#[inline]
fn div(self, rhs: Self) -> Self {
if rhs.raw == F::ZERO_RAW {
Self::ZERO
} else {
Elem {
raw: F::mul_raw(self.raw, F::inv_raw(rhs.raw)),
}
}
}
#[inline]
fn square(self) -> Self {
Elem {
raw: F::mul_raw(self.raw, self.raw),
}
}
#[inline]
fn pow(self, mut exponent: u128) -> Self {
let mut base = self.raw;
let mut result = F::ONE_RAW;
while exponent != 0 {
if exponent & 1 != 0 {
result = F::mul_raw(result, base);
}
base = F::mul_raw(base, base);
exponent >>= 1;
}
Elem { raw: result }
}
#[inline]
fn is_zero(self) -> bool {
self.raw == F::ZERO_RAW
}
#[inline]
fn is_one(self) -> bool {
self.raw == F::ONE_RAW
}
#[inline]
fn from_residue(value: u64) -> Result<Self, RangeError> {
let characteristic = <F::Characteristic as PrimeIdentity>::CHARACTERISTIC;
let () = <F::Characteristic as PrimeIdentity>::VALID;
check_residue(value, characteristic)?;
Ok(Elem {
raw: F::canonical_raw(F::from_prime(value)),
})
}
#[inline]
fn to_residue(self) -> Option<u64> {
F::to_prime(self.raw)
}
#[inline]
fn from_index(index: u128) -> Result<Self, RangeError> {
if index >= F::ORDER {
return Err(RangeError);
}
let characteristic = <F::Characteristic as PrimeIdentity>::CHARACTERISTIC;
let coordinates = index_coordinates(index, F::DEGREE, characteristic);
Ok(Elem {
raw: F::canonical_raw(F::from_prime_coordinates(
&coordinates[..F::DEGREE as usize],
)),
})
}
#[inline]
fn to_index(self) -> u128 {
let characteristic = <F::Characteristic as PrimeIdentity>::CHARACTERISTIC;
let mut coordinates = [0u64; 64];
F::prime_coordinates_into(&mut coordinates[..F::DEGREE as usize], self.raw);
coordinates_index(&coordinates[..F::DEGREE as usize], characteristic)
}
}
impl<F: Field> core::ops::Add for Elem<F> {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
Elem {
raw: F::add_raw(self.raw, rhs.raw),
}
}
}
impl<F: Field> core::ops::Sub for Elem<F> {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
Elem {
raw: F::sub_raw(self.raw, rhs.raw),
}
}
}
impl<F: Field> core::ops::Neg for Elem<F> {
type Output = Self;
#[inline]
fn neg(self) -> Self {
Elem {
raw: F::neg_raw(self.raw),
}
}
}
impl<F: Field> core::ops::Mul for Elem<F> {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self {
Elem {
raw: F::mul_raw(self.raw, rhs.raw),
}
}
}
impl<F: Field> core::ops::Div for Elem<F> {
type Output = Self;
#[inline]
fn div(self, rhs: Self) -> Self {
<Self as FieldElem>::div(self, rhs)
}
}
impl<F: Field> core::ops::AddAssign for Elem<F> {
#[inline]
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
impl<F: Field> core::ops::SubAssign for Elem<F> {
#[inline]
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
impl<F: Field> core::ops::MulAssign for Elem<F> {
#[inline]
fn mul_assign(&mut self, rhs: Self) {
*self = *self * rhs;
}
}
impl<F: Field> core::ops::DivAssign for Elem<F> {
#[inline]
fn div_assign(&mut self, rhs: Self) {
*self = *self / rhs;
}
}
impl<F: Field> core::iter::Sum for Elem<F> {
#[inline]
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Self::ZERO, |left, right| left + right)
}
}
impl<'a, F: Field> core::iter::Sum<&'a Elem<F>> for Elem<F> {
#[inline]
fn sum<I: Iterator<Item = &'a Elem<F>>>(iter: I) -> Self {
iter.fold(Self::ZERO, |left, &right| left + right)
}
}
impl<F: Field> core::iter::Product for Elem<F> {
#[inline]
fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Self::ONE, |left, right| left * right)
}
}
impl<'a, F: Field> core::iter::Product<&'a Elem<F>> for Elem<F> {
#[inline]
fn product<I: Iterator<Item = &'a Elem<F>>>(iter: I) -> Self {
iter.fold(Self::ONE, |left, &right| left * right)
}
}
pub trait FieldBuffer: Field + private::FieldSeal {
const BYTES: usize;
const STORAGE_BITS: u32;
fn decode(bytes: &[u8]) -> Elem<Self>;
fn encode(bytes: &mut [u8], value: Elem<Self>);
}
impl<const P: u64, const N: u32, R: FiniteFieldRepr<P, N>> private::FieldSeal
for FiniteField<P, N, R>
{
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RangeError;
impl core::fmt::Display for RangeError {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(formatter, "value is outside the field's range")
}
}
impl core::error::Error for RangeError {}
#[cfg(test)]
mod tests {
use super::binary::tower::{
FanPaar2Field, FanPaar4Field, FanPaar8Field, FanPaar16Field, FanPaar32Field,
FanPaar64Field, Rijndael16Field, Rijndael32Field, Rijndael64Field,
};
use super::extension::Descriptor;
use super::extension::pinned;
use super::private::RowAccess as _;
use super::*;
const WIDEST_PRIME: u64 = 0xFFFF_FFFF_FFFF_FFC5;
fn coordinates_round_trip<F: Field>(raw: F::Raw, expected: &[u64], canonical: F::Raw) {
let degree = F::DEGREE as usize;
assert_eq!(expected.len(), degree, "expectation arity");
let mut buffer = [u64::MAX; 64];
F::prime_coordinates_into(&mut buffer[..degree], raw);
assert_eq!(&buffer[..degree], expected, "coordinates of {raw:?}");
let rebuilt = F::from_prime_coordinates(&buffer[..degree]);
assert_eq!(rebuilt, canonical, "round trip of {raw:?}");
}
macro_rules! residue_row_exhaustive {
($p:literal) => {{
type Row = FiniteField<$p, 1>;
for value in 0..$p {
coordinates_round_trip::<Row>(value, &[value], value);
assert_eq!(Row::to_prime(value), Some(value), "prime readout");
let alias = value + $p;
coordinates_round_trip::<Row>(alias, &[value], value);
assert_eq!(Row::to_prime(alias), Some(value), "alias readout");
}
let widest = u64::MAX % $p;
coordinates_round_trip::<Row>(u64::MAX, &[widest], widest);
assert_eq!(Row::to_prime(u64::MAX), Some(widest), "widest readout");
let large = $p + 5 * $p;
assert_eq!(Row::from_prime(large), large % $p, "from_prime reduces");
assert_eq!(Row::from_prime(u64::MAX), widest, "from_prime widest");
}};
}
#[test]
fn residue_rows_round_trip_every_element() {
residue_row_exhaustive!(2);
residue_row_exhaustive!(3);
residue_row_exhaustive!(5);
residue_row_exhaustive!(7);
residue_row_exhaustive!(251);
}
macro_rules! binary_row_exhaustive {
($row:ty, $n:literal) => {{
type Row = $row;
let one = Elem::<Row>::ONE.to_raw();
let mut expected = [0u64; 64];
for word in 0..(1u64 << $n) {
let element =
Elem::<Row>::from_index(u128::from(word)).expect("word fits the degree");
let raw = element.to_raw();
for (i, coordinate) in expected.iter_mut().enumerate().take($n) {
*coordinate = (word >> i) & 1;
}
coordinates_round_trip::<Row>(raw, &expected[..$n], raw);
let readout = Row::to_prime(raw);
let prime_member = if element == Elem::<Row>::ZERO {
Some(0)
} else if element == Elem::<Row>::ONE {
Some(1)
} else {
None
};
assert_eq!(readout, prime_member, "prime readout of word {word}");
if let Some(c) = prime_member {
assert_eq!(Elem::<Row>::from_raw(Row::from_prime(c)), element);
}
}
assert_eq!(Row::from_prime(0), Elem::<Row>::ZERO.to_raw());
assert_eq!(Row::from_prime(2), Elem::<Row>::ZERO.to_raw());
assert_eq!(Row::from_prime(1), one);
assert_eq!(Row::from_prime(u64::MAX), one, "odd values reduce to one");
}};
}
#[test]
fn binary_rows_round_trip_every_element() {
binary_row_exhaustive!(FiniteField<2, 1, Polynomial<3>>, 1);
binary_row_exhaustive!(FiniteField<2, 2, Polynomial<7>>, 2);
binary_row_exhaustive!(FiniteField<2, 2, Normal<7, 2>>, 2);
binary_row_exhaustive!(FiniteField<2, 2, Cantor<7, 2>>, 2);
binary_row_exhaustive!(FiniteField<2, 4, Polynomial<0x13>>, 4);
binary_row_exhaustive!(FiniteField<2, 4, Normal<0x13, 8>>, 4);
binary_row_exhaustive!(FiniteField<2, 4, Cantor<0x13, 8>>, 4);
binary_row_exhaustive!(FiniteField<2, 8, Polynomial<AES>>, 8);
binary_row_exhaustive!(FiniteField<2, 8, Polynomial<RS>>, 8);
binary_row_exhaustive!(FiniteField<2, 8, Polynomial<0x12D>>, 8);
binary_row_exhaustive!(FiniteField<2, 8, Polynomial<0x187>>, 8);
binary_row_exhaustive!(FiniteField<2, 8, Normal<0x11B, 0x20>>, 8);
binary_row_exhaustive!(FiniteField<2, 8, Normal<0x11D, 0x20>>, 8);
binary_row_exhaustive!(FiniteField<2, 8, Cantor<0x11B, 0x20>>, 8);
binary_row_exhaustive!(FiniteField<2, 8, Cantor<0x11D, 0x20>>, 8);
}
fn sub_byte_aliases<F: Field<Raw = u8>>() {
let degree = F::DEGREE;
let high = !((1u8 << degree) - 1);
let mut expected = [0u64; 8];
for word in 0..(1u8 << degree) {
for (i, coordinate) in expected.iter_mut().enumerate().take(degree as usize) {
*coordinate = u64::from(word >> i) & 1;
}
let alias = word | high;
coordinates_round_trip::<F>(alias, &expected[..degree as usize], word);
let member = if word == 0 {
Some(0)
} else if word == F::ONE_RAW {
Some(1)
} else {
None
};
assert_eq!(F::to_prime(alias), member, "readout of alias {alias:#x}");
}
}
#[test]
fn sub_byte_binary_rows_read_aliases_canonically() {
sub_byte_aliases::<FiniteField<2, 1, Polynomial<3>>>();
sub_byte_aliases::<FiniteField<2, 2, Polynomial<7>>>();
sub_byte_aliases::<FiniteField<2, 2, Normal<7, 2>>>();
sub_byte_aliases::<FiniteField<2, 2, Cantor<7, 2>>>();
sub_byte_aliases::<FiniteField<2, 4, Polynomial<0x13>>>();
sub_byte_aliases::<FiniteField<2, 4, Normal<0x13, 8>>>();
sub_byte_aliases::<FiniteField<2, 4, Cantor<0x13, 8>>>();
sub_byte_aliases::<FanPaar2Field>();
sub_byte_aliases::<FanPaar4Field>();
}
#[test]
fn tower_rows_round_trip_every_element() {
binary_row_exhaustive!(FanPaar2Field, 2);
binary_row_exhaustive!(FanPaar4Field, 4);
binary_row_exhaustive!(FanPaar8Field, 8);
binary_row_exhaustive!(Rijndael16Field, 16);
binary_row_exhaustive!(FanPaar16Field, 16);
}
macro_rules! binary_row_samples {
($row:ty, $n:literal, [$($word:expr),+]) => {{
type Row = $row;
let one = Elem::<Row>::ONE.to_raw();
let mut expected = [0u64; 64];
$(
let word: u64 = $word;
let element = Elem::<Row>::from_index(u128::from(word)).expect("sample fits the degree");
let raw = element.to_raw();
for (i, coordinate) in expected.iter_mut().enumerate().take($n) {
*coordinate = (word >> i) & 1;
}
coordinates_round_trip::<Row>(raw, &expected[..$n], raw);
let prime_member = if element == Elem::<Row>::ZERO {
Some(0)
} else if element == Elem::<Row>::ONE {
Some(1)
} else {
None
};
assert_eq!(Row::to_prime(raw), prime_member, "word {word}");
)+
assert_eq!(Row::from_prime(u64::MAX), one, "odd values reduce to one");
}};
}
#[test]
fn wide_binary_rows_round_trip_samples() {
binary_row_samples!(
Rijndael32Field,
32,
[
0,
1,
2,
0x0001_0002,
0xa55a_1234,
0x7fff_ffff,
u64::from(u32::MAX)
]
);
binary_row_samples!(
Rijndael64Field,
64,
[
0,
1,
2,
0x0000_0001_0000_0004,
0xa55a_1234_dead_beef,
u64::MAX
]
);
binary_row_samples!(FanPaar32Field, 32, [0, 1, 0x0100, 0x03e2_1cea, 0xffff_ffff]);
binary_row_samples!(
FanPaar64Field,
64,
[0, 1, 0x0001_0000, 0x070f_870d_cd9c_1d88, u64::MAX]
);
}
#[test]
fn odd_rows_read_unreduced_lanes_by_remainder() {
type M31 = Mersenne31;
type Gld = Goldilocks;
type Qm = QuadMersenne31;
const M31_MODULUS: u64 = 0x7FFF_FFFF;
const GLD_MODULUS: u64 = 0xFFFF_FFFF_0000_0001;
for sample in [0u32, 1, 2, 0x5555_5555, 0x7FFF_FFFE, 0x7FFF_FFFF, u32::MAX] {
let expected = u64::from(sample) % M31_MODULUS;
#[allow(clippy::cast_possible_truncation)]
let canonical = expected as u32;
coordinates_round_trip::<M31>(sample, &[expected], canonical);
assert_eq!(M31::to_prime(sample), Some(expected), "lane {sample:#x}");
}
#[allow(clippy::cast_lossless)]
let widest_m31 = M31::from_prime(u64::MAX) as u64;
assert_eq!(widest_m31, u64::MAX % M31_MODULUS);
for sample in [
0u64,
1,
2,
GLD_MODULUS - 1,
GLD_MODULUS,
GLD_MODULUS + 1,
u64::MAX,
] {
let expected = sample % GLD_MODULUS;
coordinates_round_trip::<Gld>(sample, &[expected], expected);
assert_eq!(Gld::to_prime(sample), Some(expected), "lane {sample:#x}");
}
assert_eq!(Gld::from_prime(u64::MAX), u64::MAX % GLD_MODULUS);
for [re, im] in [
[0u32, 0],
[1, 0],
[0, 1],
[0x7FFF_FFFE, 0],
[0x7FFF_FFFF, 0x7FFF_FFFF],
[0x5555_5555, 0x5555_5555],
[u32::MAX, 0],
[u32::MAX, u32::MAX],
] {
let expected = [u64::from(re) % M31_MODULUS, u64::from(im) % M31_MODULUS];
#[allow(clippy::cast_possible_truncation)]
let canonical = [expected[0] as u32, expected[1] as u32];
coordinates_round_trip::<Qm>([re, im], &expected, canonical);
let readout = if expected[1] == 0 {
Some(expected[0])
} else {
None
};
assert_eq!(Qm::to_prime([re, im]), readout, "lane [{re:#x}, {im:#x}]");
}
let widest_qm = Qm::from_prime(u64::MAX);
assert_eq!(u64::from(widest_qm[0]), u64::MAX % M31_MODULUS);
assert_eq!(widest_qm[1], 0);
}
#[test]
fn extension_rows_round_trip_components_and_prime_readout() {
struct Spec;
impl ExtensionSpec<2> for Spec {
type Base = FiniteField<7, 1>;
type Storage = Components;
const NAME: &'static str = "GF(49) hook row";
const RELATION: Relation = Relation::from_coordinates(&[4, 0]);
}
type Row = FiniteField<7, 2, Extension<Spec, 2>>;
for a0 in 0..7u64 {
for a1 in 0..7u64 {
let raw = [a0, a1];
coordinates_round_trip::<Row>(raw, &[a0, a1], raw);
let readout = (a1 == 0).then_some(a0);
assert_eq!(Row::to_prime(raw), readout, "components ({a0}, {a1})");
}
}
assert_eq!(Row::from_prime(9), [2, 0]);
assert_eq!(Row::from_prime(u64::MAX), [u64::MAX % 7, 0]);
}
#[test]
fn extension_row_hooks_canonicalize_the_prime_readout() {
struct Gf49Spec;
impl ExtensionSpec<2> for Gf49Spec {
type Base = FiniteField<7, 1>;
type Storage = Components;
const NAME: &'static str = "GF(49) hook row";
const RELATION: Relation = Relation::from_coordinates(&[4, 0]);
}
type Gf49 = FiniteField<7, 2, Extension<Gf49Spec, 2>>;
struct Gf4Spec;
impl ExtensionSpec<2> for Gf4Spec {
type Base = FiniteField<2, 1, Polynomial<3>>;
type Storage = PackedPadded<u8>;
const NAME: &'static str = "GF(4) hook row";
const RELATION: Relation =
Relation::from_binary_words::<FiniteField<2, 1, Polynomial<3>>>(&[1, 1]);
}
type Gf4Row = FiniteField<2, 2, Extension<Gf4Spec, 2>>;
struct Gf8Spec;
impl ExtensionSpec<3> for Gf8Spec {
type Base = FiniteField<2, 1, Polynomial<3>>;
type Storage = PackedPadded<u8>;
const NAME: &'static str = "GF(8) hook row";
const RELATION: Relation =
Relation::from_binary_words::<FiniteField<2, 1, Polynomial<3>>>(&[1, 1, 0]);
}
type Gf8Row = FiniteField<2, 3, Extension<Gf8Spec, 3>>;
struct Gf64Spec;
impl ExtensionSpec<2> for Gf64Spec {
type Base = Gf8Row;
type Storage = PackedPadded<u8>;
const NAME: &'static str = "GF(64) hook row";
const RELATION: Relation = Relation::from_binary_words::<Gf8Row>(&[1, 2]);
}
type Gf64Row = FiniteField<2, 6, Extension<Gf64Spec, 2>>;
assert_eq!(Gf49::to_prime([3, 7]), Some(3), "aliased base component");
assert_eq!(Gf49::to_prime([7, 0]), Some(0), "aliased whole word");
assert_eq!(Gf49::to_prime([3, 1]), None, "off the prime subfield");
assert_eq!(Gf4Row::to_prime(0b101), Some(1), "padding above the degree");
assert_eq!(
Gf4Row::to_prime(0b10),
None,
"generator off the prime subfield"
);
assert_eq!(
Gf64Row::to_prime(0b1100_0001),
Some(1),
"padding above six bits"
);
assert_eq!(Gf64Row::to_prime(0b000_010), None, "base generator readout");
assert_eq!(
Gf64Row::to_prime(0b001_001),
None,
"nonzero upper component"
);
}
#[test]
fn widest_residue_row_round_trip_boundaries() {
type Row = FiniteField<WIDEST_PRIME, 1>;
for sample in [
0u64,
1,
2,
WIDEST_PRIME - 1,
WIDEST_PRIME,
WIDEST_PRIME + 1,
u64::MAX,
] {
let residue = sample % WIDEST_PRIME;
coordinates_round_trip::<Row>(sample, &[residue], residue);
assert_eq!(Row::to_prime(sample), Some(residue));
}
assert_eq!(Row::from_prime(u64::MAX), u64::MAX % WIDEST_PRIME);
}
macro_rules! descriptor_agreement {
($row:ty $(, expected $expected:expr)?) => {{
type Row = $row;
assert_eq!(Row::DESCRIPTOR.degree(), Row::DEGREE);
assert_eq!(
Row::DESCRIPTOR.characteristic(),
<Row as Field>::Characteristic::CHARACTERISTIC
);
$(
let expected: Descriptor = $expected;
assert!(
Row::DESCRIPTOR.same_structure(&expected),
"descriptor is not the expected shipped instance"
);
)?
}};
}
#[test]
fn row_descriptors_agree_with_their_rows_and_the_instance_table() {
descriptor_agreement!(FiniteField<7, 1>);
descriptor_agreement!(FiniteField<WIDEST_PRIME, 1>);
descriptor_agreement!(Mersenne31);
descriptor_agreement!(Goldilocks);
descriptor_agreement!(
QuadMersenne31,
expected pinned::shipped_instances::quad_mersenne31()
);
descriptor_agreement!(FiniteField<2, 1, Polynomial<3>>);
descriptor_agreement!(FiniteField<2, 2, Polynomial<7>>);
descriptor_agreement!(FiniteField<2, 2, Normal<7, 2>>);
descriptor_agreement!(FiniteField<2, 2, Cantor<7, 2>>);
descriptor_agreement!(FiniteField<2, 4, Polynomial<0x13>>);
descriptor_agreement!(FiniteField<2, 4, Normal<0x13, 8>>);
descriptor_agreement!(FiniteField<2, 4, Cantor<0x13, 8>>);
descriptor_agreement!(FiniteField<2, 8, Polynomial<AES>>);
descriptor_agreement!(FiniteField<2, 8, Polynomial<RS>>);
descriptor_agreement!(
Rijndael16Field,
expected pinned::shipped_instances::rijndael16()
);
descriptor_agreement!(
Rijndael32Field,
expected pinned::shipped_instances::rijndael32()
);
descriptor_agreement!(
Rijndael64Field,
expected pinned::shipped_instances::rijndael64()
);
descriptor_agreement!(
FanPaar2Field,
expected pinned::shipped_instances::fanpaar2()
);
descriptor_agreement!(
FanPaar4Field,
expected pinned::shipped_instances::fanpaar4()
);
descriptor_agreement!(
FanPaar8Field,
expected pinned::shipped_instances::fanpaar8()
);
descriptor_agreement!(
FanPaar16Field,
expected pinned::shipped_instances::fanpaar16()
);
descriptor_agreement!(
FanPaar32Field,
expected pinned::shipped_instances::fanpaar32()
);
descriptor_agreement!(
FanPaar64Field,
expected pinned::shipped_instances::fanpaar64()
);
assert!(!FiniteField::<0x7FFF_FFFF, 1>::DESCRIPTOR.same_structure(Mersenne31::DESCRIPTOR));
assert!(
FiniteField::<0x7FFF_FFFF, 1>::DESCRIPTOR
.same_structure(FiniteField::<0x7FFF_FFFF, 1>::DESCRIPTOR)
);
}
#[test]
#[should_panic(expected = "prime coordinate count differs from the field degree")]
fn coordinate_write_rejects_a_wrong_buffer_length() {
FiniteField::<7, 1>::prime_coordinates_into(&mut [0u64; 2], 3);
}
#[test]
#[should_panic(expected = "prime coordinate count differs from the field degree")]
fn coordinate_read_rejects_a_wrong_buffer_length() {
QuadMersenne31::from_prime_coordinates(&[0u64; 1]);
}
#[test]
#[should_panic(expected = "prime coordinate is not below the characteristic")]
fn coordinate_read_rejects_a_coordinate_at_the_characteristic() {
FiniteField::<7, 1>::from_prime_coordinates(&[7]);
}
#[test]
#[should_panic(expected = "prime coordinate is not below the characteristic")]
fn coordinate_read_rejects_a_binary_coordinate_above_one() {
FiniteField::<2, 4, Polynomial<0x13>>::from_prime_coordinates(&[0, 2, 0, 0]);
}
}