use core::marker::PhantomData;
use const_num_traits::{Ct, Nct, Odd, Personality};
use crate::montgomery::basic_mont::{
wide_montgomery_mul, wide_montgomery_mul_acc, wide_montgomery_mul_acc_ct,
wide_montgomery_mul_ct, wide_redc, wide_redc_ct,
};
use crate::montgomery::{
CiosMontMul, CiosMontMulCt, compute_n_prime_newton, compute_r_mod_n, compute_r2_mod_n,
type_bit_width,
};
use crate::parity::Parity;
use crate::wide_mul::WideMul;
#[cfg(feature = "zeroize")]
pub trait MontStorage: zeroize::Zeroize {}
#[cfg(feature = "zeroize")]
impl<T: zeroize::Zeroize> MontStorage for T {}
#[cfg(not(feature = "zeroize"))]
pub trait MontStorage {}
#[cfg(not(feature = "zeroize"))]
impl<T> MontStorage for T {}
#[derive(Clone, Debug)]
pub struct Field<T, P: Personality = Nct> {
modulus: T,
n_prime: T,
r_mod_n: T,
r2_mod_n: T,
_p: PhantomData<fn() -> P>,
}
pub type FieldNct<T> = Field<T, Nct>;
pub type FieldCt<T> = Field<T, Ct>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Residue<'f, T: MontStorage, P: Personality = Nct> {
mont: T,
_brand: PhantomData<&'f ()>,
_p: PhantomData<fn() -> P>,
}
#[cfg(feature = "zeroize")]
impl<'f, T: MontStorage, P: Personality> zeroize::Zeroize for Residue<'f, T, P> {
fn zeroize(&mut self) {
self.mont.zeroize();
}
}
#[cfg(feature = "zeroize")]
impl<'f, T: MontStorage, P: Personality> Drop for Residue<'f, T, P> {
fn drop(&mut self) {
self.mont.zeroize();
}
}
#[cfg(feature = "zeroize")]
impl<'f, T: MontStorage, P: Personality> zeroize::ZeroizeOnDrop for Residue<'f, T, P> {}
pub type ResidueNct<'f, T> = Residue<'f, T, Nct>;
pub type ResidueCt<'f, T> = Residue<'f, T, Ct>;
impl<T: MontStorage, P: Personality> Residue<'_, T, P> {
pub fn mont_value(&self) -> &T {
&self.mont
}
}
impl<T, P: Personality> Field<T, P> {
pub const fn from_precomputed(modulus: T, n_prime: T, r_mod_n: T, r2_mod_n: T) -> Self {
Self {
modulus,
n_prime,
r_mod_n,
r2_mod_n,
_p: PhantomData,
}
}
}
impl<T, P: Personality> Field<T, P>
where
T: Copy
+ PartialEq
+ PartialOrd
+ const_num_traits::Zero
+ const_num_traits::One
+ const_num_traits::WrappingMul
+ const_num_traits::WrappingAdd
+ const_num_traits::WrappingSub
+ const_num_traits::ops::overflowing::OverflowingAdd
+ core::ops::Add<Output = T>
+ core::ops::Sub<Output = T>
+ core::ops::Mul<Output = T>
+ Parity
+ MontStorage,
{
pub fn new_odd(modulus: Odd<T>) -> Self {
let modulus = modulus.get();
let w = type_bit_width::<T>();
let n_prime = compute_n_prime_newton(modulus, w);
let r_mod_n = compute_r_mod_n(modulus, w);
let r2_mod_n = compute_r2_mod_n(r_mod_n, modulus, w);
Self {
modulus,
n_prime,
r_mod_n,
r2_mod_n,
_p: PhantomData,
}
}
pub fn new_odd_ct(modulus: Odd<T>) -> Self
where
T: subtle::ConditionallySelectable + subtle::ConstantTimeLess,
{
let modulus = modulus.get();
let w = type_bit_width::<T>();
let n_prime = compute_n_prime_newton(modulus, w);
let r_mod_n = crate::montgomery::compute_r_mod_n_ct(modulus, w);
let r2_mod_n = crate::montgomery::compute_r2_mod_n_ct(r_mod_n, modulus, w);
Self {
modulus,
n_prime,
r_mod_n,
r2_mod_n,
_p: PhantomData,
}
}
pub fn new(modulus: T) -> Option<Self> {
Odd::new(modulus).map(Self::new_odd)
}
pub fn modulus(&self) -> &T {
&self.modulus
}
pub fn zero(&self) -> Residue<'_, T, P> {
Residue {
mont: T::zero(),
_brand: PhantomData,
_p: PhantomData,
}
}
pub fn one(&self) -> Residue<'_, T, P> {
Residue {
mont: self.r_mod_n,
_brand: PhantomData,
_p: PhantomData,
}
}
pub fn residue_from_mont(&self, mont: T) -> Residue<'_, T, P> {
Residue {
mont,
_brand: PhantomData,
_p: PhantomData,
}
}
}
impl<T> Field<T, Nct>
where
T: Copy
+ PartialEq
+ PartialOrd
+ const_num_traits::Zero
+ const_num_traits::One
+ const_num_traits::WrappingMul
+ const_num_traits::WrappingAdd
+ const_num_traits::WrappingSub
+ const_num_traits::ops::overflowing::OverflowingAdd
+ core::ops::Add<Output = T>
+ core::ops::Sub<Output = T>
+ core::ops::Mul<Output = T>
+ Parity
+ crate::NonCt
+ MontStorage,
{
pub fn reduce(&self, raw: &T) -> Residue<'_, T, Nct>
where
T: WideMul,
{
let mont = wide_montgomery_mul(*raw, self.r2_mod_n, self.modulus, self.n_prime);
Residue {
mont,
_brand: PhantomData,
_p: PhantomData,
}
}
#[allow(clippy::wrong_self_convention)]
pub fn into_raw(&self, r: &Residue<'_, T, Nct>) -> T
where
T: WideMul,
{
wide_redc(r.mont, T::zero(), self.modulus, self.n_prime)
}
pub fn add(&self, a: &Residue<'_, T, Nct>, b: &Residue<'_, T, Nct>) -> Residue<'_, T, Nct> {
let mont = crate::add::basic_mod_add_pr(a.mont, b.mont, self.modulus);
Residue {
mont,
_brand: PhantomData,
_p: PhantomData,
}
}
pub fn sub(&self, a: &Residue<'_, T, Nct>, b: &Residue<'_, T, Nct>) -> Residue<'_, T, Nct> {
let mont = crate::sub::basic_mod_sub_pr(a.mont, b.mont, self.modulus);
Residue {
mont,
_brand: PhantomData,
_p: PhantomData,
}
}
#[inline]
pub fn mul(&self, a: &Residue<'_, T, Nct>, b: &Residue<'_, T, Nct>) -> Residue<'_, T, Nct>
where
T: CiosMontMul,
{
let mont = CiosMontMul::cios_mont_mul(&a.mont, &b.mont, &self.modulus, &self.n_prime);
Residue {
mont,
_brand: PhantomData,
_p: PhantomData,
}
}
pub fn exp(&self, base: &Residue<'_, T, Nct>, exp: &T) -> Residue<'_, T, Nct>
where
T: CiosMontMul + core::ops::ShrAssign<usize>,
{
let mut result = self.r_mod_n;
let mut base_var = base.mont;
let mut exp_val = *exp;
while exp_val > T::zero() {
if exp_val.is_odd() {
result =
CiosMontMul::cios_mont_mul(&result, &base_var, &self.modulus, &self.n_prime);
}
exp_val >>= 1;
if exp_val > T::zero() {
base_var =
CiosMontMul::cios_mont_mul(&base_var, &base_var, &self.modulus, &self.n_prime);
}
}
Residue {
mont: result,
_brand: PhantomData,
_p: PhantomData,
}
}
pub fn mul_acc(&self, acc: (T, T), a: &Residue<'_, T, Nct>, b: &Residue<'_, T, Nct>) -> (T, T)
where
T: WideMul,
{
wide_montgomery_mul_acc(acc.0, acc.1, a.mont, b.mont)
}
pub fn wide_redc(&self, acc: (T, T)) -> Residue<'_, T, Nct>
where
T: WideMul,
{
let mont = wide_redc(acc.0, acc.1, self.modulus, self.n_prime);
Residue {
mont,
_brand: PhantomData,
_p: PhantomData,
}
}
pub fn inv_fermat(&self, a: &Residue<'_, T, Nct>) -> Option<Residue<'_, T, Nct>>
where
T: CiosMontMul + core::ops::ShrAssign<usize>,
{
if a.mont == T::zero() {
return None;
}
let two = T::one().wrapping_add(T::one());
let exp_val = self.modulus.wrapping_sub(two);
Some(self.exp(a, &exp_val))
}
pub fn inv_eea(&self, a: &Residue<'_, T, Nct>) -> Option<Residue<'_, T, Nct>>
where
T: WideMul + core::ops::Div<Output = T> + core::ops::Sub<Output = T>,
{
if a.mont == T::zero() {
return None;
}
let raw_inv = crate::inv::basic_mod_inv(a.mont, self.modulus)?;
let step1 = wide_montgomery_mul(raw_inv, self.r2_mod_n, self.modulus, self.n_prime);
let mont = wide_montgomery_mul(step1, self.r2_mod_n, self.modulus, self.n_prime);
Some(Residue {
mont,
_brand: PhantomData,
_p: PhantomData,
})
}
}
impl<'f, T> Residue<'f, T, Ct>
where
T: subtle::ConditionallySelectable + MontStorage,
{
pub fn cswap(choice: subtle::Choice, a: &mut Self, b: &mut Self) {
T::conditional_swap(&mut a.mont, &mut b.mont, choice);
}
}
impl<'f, T> Residue<'f, T, Ct>
where
T: subtle::ConstantTimeEq + MontStorage,
{
pub fn ct_eq(&self, other: &Self) -> subtle::Choice {
self.mont.ct_eq(&other.mont)
}
}
impl<T> Field<T, Ct>
where
T: Copy
+ PartialEq
+ PartialOrd
+ const_num_traits::Zero
+ const_num_traits::One
+ const_num_traits::WrappingMul
+ const_num_traits::WrappingAdd
+ const_num_traits::WrappingSub
+ const_num_traits::ops::overflowing::OverflowingAdd
+ core::ops::Add<Output = T>
+ core::ops::Sub<Output = T>
+ core::ops::Mul<Output = T>
+ Parity
+ MontStorage,
{
pub fn try_new_odd_ct(modulus: T) -> subtle::CtOption<Self>
where
T: const_num_traits::CtParity + subtle::ConditionallySelectable + subtle::ConstantTimeLess,
{
let is_odd = modulus.ct_is_odd();
let proof = unsafe { Odd::new_unchecked(modulus) };
let field = Self::new_odd_ct(proof);
subtle::CtOption::new(field, is_odd)
}
pub fn reduce(&self, raw: &T) -> Residue<'_, T, Ct>
where
T: WideMul + subtle::ConditionallySelectable + subtle::ConstantTimeLess,
{
let mont = wide_montgomery_mul_ct(*raw, self.r2_mod_n, self.modulus, self.n_prime);
Residue {
mont,
_brand: PhantomData,
_p: PhantomData,
}
}
#[allow(clippy::wrong_self_convention)]
pub fn into_raw(&self, r: &Residue<'_, T, Ct>) -> T
where
T: WideMul + subtle::ConditionallySelectable + subtle::ConstantTimeLess,
{
wide_redc_ct(r.mont, T::zero(), self.modulus, self.n_prime)
}
pub fn add(&self, a: &Residue<'_, T, Ct>, b: &Residue<'_, T, Ct>) -> Residue<'_, T, Ct>
where
T: subtle::ConditionallySelectable + subtle::ConstantTimeLess,
{
let sum = a.mont.wrapping_add(b.mont);
let sub = sum.wrapping_sub(self.modulus);
let carry = sum.ct_lt(&a.mont);
let ge_m = !sum.ct_lt(&self.modulus);
let needs_sub = carry | ge_m;
let mont = T::conditional_select(&sum, &sub, needs_sub);
Residue {
mont,
_brand: PhantomData,
_p: PhantomData,
}
}
pub fn sub(&self, a: &Residue<'_, T, Ct>, b: &Residue<'_, T, Ct>) -> Residue<'_, T, Ct>
where
T: subtle::ConditionallySelectable + subtle::ConstantTimeLess,
{
let diff = a.mont.wrapping_sub(b.mont);
let corrected = diff.wrapping_add(self.modulus);
let borrow = a.mont.ct_lt(&b.mont);
let mont = T::conditional_select(&diff, &corrected, borrow);
Residue {
mont,
_brand: PhantomData,
_p: PhantomData,
}
}
#[inline]
pub fn mul(&self, a: &Residue<'_, T, Ct>, b: &Residue<'_, T, Ct>) -> Residue<'_, T, Ct>
where
T: CiosMontMulCt,
{
let mont = CiosMontMulCt::cios_mont_mul_ct(&a.mont, &b.mont, &self.modulus, &self.n_prime);
Residue {
mont,
_brand: PhantomData,
_p: PhantomData,
}
}
pub fn exp(&self, base: &Residue<'_, T, Ct>, exp: &T) -> Residue<'_, T, Ct>
where
T: CiosMontMulCt
+ const_num_traits::CtIsZero
+ subtle::ConditionallySelectable
+ subtle::ConstantTimeEq
+ core::ops::Shr<usize, Output = T>
+ core::ops::BitAnd<Output = T>,
{
let w = type_bit_width::<T>();
let one = T::one();
let mut result = self.r_mod_n;
for i in (0..w).rev() {
result =
CiosMontMulCt::cios_mont_mul_ct(&result, &result, &self.modulus, &self.n_prime);
let multiplied =
CiosMontMulCt::cios_mont_mul_ct(&result, &base.mont, &self.modulus, &self.n_prime);
let bit_t = (*exp >> i) & one;
let choice = !bit_t.ct_is_zero();
result = T::conditional_select(&result, &multiplied, choice);
}
Residue {
mont: result,
_brand: PhantomData,
_p: PhantomData,
}
}
pub fn exp_public_exp(&self, base: &Residue<'_, T, Ct>, exp: &T) -> Residue<'_, T, Ct>
where
T: CiosMontMulCt + core::ops::Shr<usize, Output = T> + core::ops::BitAnd<Output = T>,
{
let w = type_bit_width::<T>();
let one = T::one();
let zero = T::zero();
let mut hi = w;
while hi > 0 {
if (*exp >> (hi - 1)) & one != zero {
break;
}
hi -= 1;
}
if hi == 0 {
return Residue {
mont: self.r_mod_n,
_brand: PhantomData,
_p: PhantomData,
};
}
let mut result = base.mont;
for i in (0..hi - 1).rev() {
result =
CiosMontMulCt::cios_mont_mul_ct(&result, &result, &self.modulus, &self.n_prime);
if (*exp >> i) & one != zero {
result = CiosMontMulCt::cios_mont_mul_ct(
&result,
&base.mont,
&self.modulus,
&self.n_prime,
);
}
}
Residue {
mont: result,
_brand: PhantomData,
_p: PhantomData,
}
}
pub fn mul_acc(&self, acc: (T, T), a: &Residue<'_, T, Ct>, b: &Residue<'_, T, Ct>) -> (T, T)
where
T: WideMul + subtle::ConditionallySelectable,
{
wide_montgomery_mul_acc_ct(acc.0, acc.1, a.mont, b.mont)
}
pub fn wide_redc(&self, acc: (T, T)) -> Residue<'_, T, Ct>
where
T: WideMul + subtle::ConditionallySelectable + subtle::ConstantTimeLess,
{
let mont = wide_redc_ct(acc.0, acc.1, self.modulus, self.n_prime);
Residue {
mont,
_brand: PhantomData,
_p: PhantomData,
}
}
pub fn inv_fermat(&self, a: &Residue<'_, T, Ct>) -> subtle::CtOption<Residue<'_, T, Ct>>
where
T: CiosMontMulCt
+ const_num_traits::CtIsZero
+ subtle::ConditionallySelectable
+ subtle::ConstantTimeEq
+ core::ops::Shr<usize, Output = T>
+ core::ops::BitAnd<Output = T>,
{
let a_is_nonzero = !a.mont.ct_is_zero();
let two = T::one().wrapping_add(T::one());
let exp_val = self.modulus.wrapping_sub(two);
let result = self.exp(a, &exp_val);
subtle::CtOption::new(result, a_is_nonzero)
}
pub fn inv_safegcd_ct(&self, a: &Residue<'_, T, Ct>) -> subtle::CtOption<Residue<'_, T, Ct>>
where
T: CiosMontMulCt
+ WideMul
+ subtle::ConditionallySelectable
+ subtle::ConstantTimeLess
+ const_num_traits::CtIsZero
+ modmath_cios::CiosRowOps
+ core::ops::Shr<usize, Output = T>
+ core::ops::Shl<usize, Output = T>
+ core::ops::BitOr<Output = T>,
<T as modmath_cios::CiosRowOps>::Word: const_num_traits::CtParity,
{
let inv_raw = crate::inv::safegcd::safegcd_inv_ct(&a.mont, &self.modulus);
let inv_exists = inv_raw.is_some();
let raw_inv = inv_raw.unwrap_or(T::zero());
let m1 = wide_montgomery_mul_ct(raw_inv, self.r2_mod_n, self.modulus, self.n_prime);
let mont = wide_montgomery_mul_ct(m1, self.r2_mod_n, self.modulus, self.n_prime);
let residue = Residue {
mont,
_brand: PhantomData,
_p: PhantomData,
};
subtle::CtOption::new(residue, inv_exists)
}
}
#[cfg(test)]
mod tests {
use super::*;
use fixed_bigint::FixedUInt;
use subtle::Choice;
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
type U16 = FixedUInt<u8, 2>;
type U16Ct = FixedUInt<u8, 2, Ct>;
type U128Ct = FixedUInt<u32, 4, Ct>;
fn u16(n: u16) -> U16 {
U16::from(n)
}
fn u16ct(n: u16) -> U16Ct {
U16Ct::from(n)
}
#[test]
fn round_trip_small() {
let f: Field<U16> = Field::new(u16(13)).unwrap();
for raw in 0u16..13 {
let r = f.reduce(&u16(raw));
assert_eq!(f.into_raw(&r), u16(raw), "round trip failed for {raw}");
}
}
#[test]
fn new_odd_matches_new() {
let m = u16(13);
let modulus_odd = Odd::new(m).expect("13 is odd");
let from_odd: Field<U16> = Field::new_odd(modulus_odd);
let from_opt: Field<U16> = Field::new(m).unwrap();
assert_eq!(from_odd.modulus(), from_opt.modulus());
let a = from_odd.reduce(&u16(7));
let b = from_odd.reduce(&u16(5));
let via_odd = from_odd.into_raw(&from_odd.mul(&a, &b));
let a2 = from_opt.reduce(&u16(7));
let b2 = from_opt.reduce(&u16(5));
let via_opt = from_opt.into_raw(&from_opt.mul(&a2, &b2));
assert_eq!(via_odd, via_opt);
assert_eq!(via_odd, u16(35 % 13));
}
#[test]
fn new_rejects_even_and_zero() {
assert!(Field::<U16>::new(u16(0)).is_none());
assert!(Field::<U16>::new(u16(12)).is_none()); assert!(Field::<U16>::new(u16(13)).is_some()); }
#[test]
fn try_new_odd_ct_masks_parity() {
let even = Field::<u32, Ct>::try_new_odd_ct(12);
assert_eq!(even.is_some().unwrap_u8(), 0);
let zero = Field::<u32, Ct>::try_new_odd_ct(0);
assert_eq!(zero.is_some().unwrap_u8(), 0);
let odd = Field::<u32, Ct>::try_new_odd_ct(13);
assert_eq!(odd.is_some().unwrap_u8(), 1);
let field: Field<u32, Ct> = odd.unwrap();
let baseline = Field::<u32, Ct>::new_odd(Odd::new(13u32).unwrap());
assert_eq!(field.modulus(), baseline.modulus());
}
#[test]
fn new_odd_ct_precompute_matches_new_odd() {
for m in [3u32, 5, 7, 11, 13, 97, 65521, 0x7FFF_FFE7] {
let modulus = Odd::new(m).unwrap();
let f_nct = Field::<u32, Ct>::new_odd(modulus);
let f_ct = Field::<u32, Ct>::new_odd_ct(modulus);
assert_eq!(f_nct.modulus(), f_ct.modulus(), "modulus mismatch at m={m}");
assert_eq!(f_nct.n_prime, f_ct.n_prime, "n_prime mismatch at m={m}");
assert_eq!(f_nct.r_mod_n, f_ct.r_mod_n, "r_mod_n mismatch at m={m}");
assert_eq!(f_nct.r2_mod_n, f_ct.r2_mod_n, "r2_mod_n mismatch at m={m}");
}
}
#[test]
fn new_odd_ct_precompute_matches_new_odd_fixed_bigint() {
let m = U128Ct::from(0xFFFF_FFFF_FFFF_FFE7u64);
let modulus = Odd::new(m).unwrap();
let f_nct = Field::<U128Ct, Ct>::new_odd(modulus);
let f_ct = Field::<U128Ct, Ct>::new_odd_ct(modulus);
assert_eq!(f_nct.modulus(), f_ct.modulus());
assert_eq!(f_nct.n_prime, f_ct.n_prime);
assert_eq!(f_nct.r_mod_n, f_ct.r_mod_n);
assert_eq!(f_nct.r2_mod_n, f_ct.r2_mod_n);
}
#[test]
fn add_sub_mul_small() {
let f: Field<U16> = Field::new(u16(13)).unwrap();
for a_raw in 0u16..13 {
for b_raw in 0u16..13 {
let a = f.reduce(&u16(a_raw));
let b = f.reduce(&u16(b_raw));
assert_eq!(f.into_raw(&f.add(&a, &b)), u16((a_raw + b_raw) % 13));
assert_eq!(
f.into_raw(&f.sub(&a, &b)),
u16((a_raw + 13 - b_raw) % 13),
"sub failed for {a_raw}, {b_raw}"
);
assert_eq!(f.into_raw(&f.mul(&a, &b)), u16((a_raw * b_raw) % 13));
}
}
}
#[test]
fn zero_one_identity_small() {
let f: Field<U16> = Field::new(u16(13)).unwrap();
let z = f.zero();
let o = f.one();
assert_eq!(f.into_raw(&z), u16(0));
assert_eq!(f.into_raw(&o), u16(1));
for raw in 0u16..13 {
let a = f.reduce(&u16(raw));
assert_eq!(f.into_raw(&f.add(&a, &z)), u16(raw));
assert_eq!(f.into_raw(&f.mul(&a, &o)), u16(raw));
}
}
#[test]
fn exp_small() {
let f: Field<U16> = Field::new(u16(13)).unwrap();
let base = f.reduce(&u16(7));
let result = f.exp(&base, &u16(5));
assert_eq!(f.into_raw(&result), u16(11));
let r0 = f.exp(&base, &u16(0));
assert_eq!(f.into_raw(&r0), u16(1));
}
#[test]
fn ct_round_trip_small() {
let f = FieldCt::new(u16ct(13)).unwrap();
for raw in 0u16..13 {
let r = f.reduce(&u16ct(raw));
assert_eq!(f.into_raw(&r), u16ct(raw));
}
}
#[test]
fn ct_matches_nct_small() {
let f: Field<U16> = Field::new(u16(13)).unwrap();
let fc = FieldCt::new(u16ct(13)).unwrap();
for a_raw in 0u16..13 {
for b_raw in 0u16..13 {
let a = f.reduce(&u16(a_raw));
let b = f.reduce(&u16(b_raw));
let ac = fc.reduce(&u16ct(a_raw));
let bc = fc.reduce(&u16ct(b_raw));
assert_eq!(
f.into_raw(&f.add(&a, &b)),
fc.into_raw(&fc.add(&ac, &bc)).forget_ct()
);
assert_eq!(
f.into_raw(&f.sub(&a, &b)),
fc.into_raw(&fc.sub(&ac, &bc)).forget_ct()
);
assert_eq!(
f.into_raw(&f.mul(&a, &b)),
fc.into_raw(&fc.mul(&ac, &bc)).forget_ct()
);
}
}
let base = f.reduce(&u16(7));
let base_ct = fc.reduce(&u16ct(7));
for e in 0u16..20 {
assert_eq!(
f.into_raw(&f.exp(&base, &u16(e))),
fc.into_raw(&fc.exp(&base_ct, &u16ct(e))).forget_ct()
);
}
}
#[test]
fn ct_cswap_small() {
let f = FieldCt::new(u16ct(13)).unwrap();
let mut a = f.reduce(&u16ct(3));
let mut b = f.reduce(&u16ct(7));
ResidueCt::cswap(Choice::from(0), &mut a, &mut b);
assert_eq!(f.into_raw(&a), u16ct(3));
assert_eq!(f.into_raw(&b), u16ct(7));
ResidueCt::cswap(Choice::from(1), &mut a, &mut b);
assert_eq!(f.into_raw(&a), u16ct(7));
assert_eq!(f.into_raw(&b), u16ct(3));
}
#[test]
fn nct_to_ct_upgrade_small() {
let f: Field<U16> = Field::new(u16(13)).unwrap();
let modulus_ct: U16Ct = (*f.modulus()).into();
let fc = FieldCt::new(modulus_ct).unwrap();
let a = fc.reduce(&u16ct(7));
let b = fc.reduce(&u16ct(5));
assert_eq!(fc.into_raw(&fc.mul(&a, &b)), u16ct(9)); }
#[test]
fn exp_public_exp_matches_ct_exp_small() {
let f = FieldCt::new(u16ct(13)).unwrap();
let base = f.reduce(&u16ct(7));
for e in 0u16..32 {
let via_ladder = f.exp(&base, &u16ct(e));
let via_pub = f.exp_public_exp(&base, &u16ct(e));
assert_eq!(
f.into_raw(&via_ladder),
f.into_raw(&via_pub),
"exp_public_exp mismatch at e={e}"
);
}
}
#[test]
fn exp_public_exp_matches_ct_exp_u128() {
let modulus = !U128Ct::from(0u64) - U128Ct::from(58u64);
let f = FieldCt::new(modulus).unwrap();
let base = f.reduce(&U128Ct::from(0xDEAD_BEEF_u64));
let exps = [
U128Ct::from(0u64),
U128Ct::from(1u64),
U128Ct::from(7u64),
U128Ct::from(65537u64), U128Ct::from(0xCAFE_BABEu64),
];
for e in &exps {
let via_ladder = f.exp(&base, e);
let via_pub = f.exp_public_exp(&base, e);
assert_eq!(
f.into_raw(&via_ladder),
f.into_raw(&via_pub),
"exp_public_exp mismatch at e={e:?}"
);
}
}
#[test]
fn brand_round_trip_fixed_bigint_u128() {
let modulus = !U128Ct::from(0u64) - U128Ct::from(58u64);
let f = FieldCt::new(modulus).unwrap();
let raw = U128Ct::from(0xDEAD_BEEF_u64);
let r = f.reduce(&raw);
assert_eq!(f.into_raw(&r), raw);
}
#[test]
fn inv_safegcd_ct_round_trip_prime_modulus() {
let f = FieldCt::new(u16ct(13)).unwrap();
for raw_val in 1u16..13 {
let r = f.reduce(&u16ct(raw_val));
let inv = f.inv_safegcd_ct(&r);
assert_eq!(
inv.is_some().unwrap_u8(),
1,
"expected inverse for {raw_val} mod 13"
);
let inv_residue = inv.unwrap();
let product = f.mul(&r, &inv_residue);
assert_eq!(
f.into_raw(&product),
u16ct(1),
"{raw_val} * inv != 1 mod 13"
);
}
}
#[test]
fn inv_safegcd_ct_composite_modulus() {
let f = FieldCt::new(u16ct(15)).unwrap();
for &raw_val in &[1u16, 2, 4, 7, 8, 11, 13, 14] {
let r = f.reduce(&u16ct(raw_val));
let inv = f.inv_safegcd_ct(&r);
assert_eq!(
inv.is_some().unwrap_u8(),
1,
"expected inverse for {raw_val} mod 15"
);
let product = f.mul(&r, &inv.unwrap());
assert_eq!(
f.into_raw(&product),
u16ct(1),
"{raw_val} * inv != 1 mod 15"
);
}
for &raw_val in &[3u16, 5, 6, 9, 10, 12] {
let r = f.reduce(&u16ct(raw_val));
let inv = f.inv_safegcd_ct(&r);
assert_eq!(
inv.is_some().unwrap_u8(),
0,
"expected None for non-coprime {raw_val} mod 15"
);
}
}
#[test]
fn inv_safegcd_ct_full_width_modulus() {
let modulus = u16ct(0xFFFD);
let f = FieldCt::new(modulus).unwrap();
for raw_val in [1u16, 2, 7, 0xBEEF, 0xFFFC] {
let r = f.reduce(&u16ct(raw_val));
let inv = f.inv_safegcd_ct(&r);
assert_eq!(
inv.is_some().unwrap_u8(),
1,
"expected inverse for {raw_val:#x} mod 0xFFFD"
);
let product = f.mul(&r, &inv.unwrap());
assert_eq!(
f.into_raw(&product),
u16ct(1),
"{raw_val:#x} * inv != 1 mod 0xFFFD"
);
}
let r = f.reduce(&u16ct(13));
assert_eq!(f.inv_safegcd_ct(&r).is_some().unwrap_u8(), 0);
}
#[test]
fn inv_safegcd_ct_composite_modulus_u128() {
let n_raw: u64 = 0x1_0000_0007 * 0x100_0007u64; let modulus = U128Ct::from(n_raw);
let f = FieldCt::new(modulus).unwrap();
let test_vals = [
U128Ct::from(1u64),
U128Ct::from(2u64),
U128Ct::from(3u64),
U128Ct::from(0xCAFE_BABEu64),
U128Ct::from(0xFEED_FACEu64),
];
for v in test_vals {
let r = f.reduce(&v);
let inv = f.inv_safegcd_ct(&r);
assert_eq!(
inv.is_some().unwrap_u8(),
1,
"expected inverse to exist for v={:?}",
v
);
let product = f.mul(&r, &inv.unwrap());
assert_eq!(f.into_raw(&product), U128Ct::from(1u64));
}
}
#[cfg(feature = "zeroize")]
#[test]
fn residue_zeroize_wipes_mont_small() {
fn assert_zeroize_on_drop<T: zeroize::ZeroizeOnDrop>(_: &T) {}
let f = FieldCt::new(u16ct(13)).unwrap();
let mut r = f.reduce(&u16ct(7));
assert_zeroize_on_drop(&r);
assert_ne!(*r.mont_value(), u16ct(0));
r.zeroize();
assert_eq!(*r.mont_value(), u16ct(0));
}
#[test]
fn residue_from_mont_escape_hatch_small() {
let f: Field<U16> = Field::new(u16(13)).unwrap();
for raw in 0u16..13 {
let r = f.reduce(&u16(raw));
let mont = *r.mont_value();
let r2 = f.residue_from_mont(mont);
assert_eq!(f.into_raw(&r2), u16(raw));
}
}
#[test]
fn covariance_mixes_residues_documented_limitation() {
let f1: Field<U16> = Field::new(u16(13)).unwrap();
let f2: Field<U16> = Field::new(u16(13)).unwrap();
let r1 = f1.reduce(&u16(5));
let _ = f2.into_raw(&r1);
}
#[test]
fn field_p_personality_cross_check_small() {
let m_nct = u16(13);
let m_ct: U16Ct = m_nct.into();
let f_nct: Field<U16, Nct> = Field::new(m_nct).unwrap();
let f_ct: Field<U16Ct, Ct> = Field::new(m_ct).unwrap();
let a_nct = f_nct.reduce(&u16(7));
let b_nct = f_nct.reduce(&u16(5));
let a_ct = f_ct.reduce(&u16ct(7));
let b_ct = f_ct.reduce(&u16ct(5));
let mul_nct = f_nct.into_raw(&f_nct.mul(&a_nct, &b_nct));
let mul_ct = f_ct.into_raw(&f_ct.mul(&a_ct, &b_ct));
assert_eq!(mul_nct, mul_ct.forget_ct());
let exp_nct = f_nct.into_raw(&f_nct.exp(&a_nct, &u16(11)));
let exp_ct = f_ct.into_raw(&f_ct.exp(&a_ct, &u16ct(11)));
assert_eq!(exp_nct, exp_ct.forget_ct());
}
#[test]
fn field_nct_alias_resolves_without_annotation() {
let f = FieldNct::new(u16(13)).unwrap();
let r: ResidueNct<'_, U16> = f.reduce(&u16(7));
assert_eq!(f.into_raw(&r), u16(7));
let two = f.reduce(&u16(2));
assert_eq!(f.into_raw(&f.mul(&r, &two)), u16(14 % 13));
}
#[test]
fn from_precomputed_const_construction_u32() {
const F: Field<u32, Nct> = Field::from_precomputed(13u32, 0x4EC4EC4F, 9, 3);
assert_eq!(*F.modulus(), 13u32);
}
#[test]
fn field_mul_acc_round_trip_small() {
let f: Field<U16> = Field::new(u16(13)).unwrap();
for a_raw in 0u16..13 {
for b_raw in 0u16..13 {
let a = f.reduce(&u16(a_raw));
let b = f.reduce(&u16(b_raw));
let direct = f.mul(&a, &b);
let via_acc = f.wide_redc(f.mul_acc((u16(0), u16(0)), &a, &b));
assert_eq!(f.into_raw(&direct), f.into_raw(&via_acc));
}
}
}
#[test]
fn field_mul_acc_dot_product_small() {
let f: Field<U16> = Field::new(u16(13)).unwrap();
let pairs: &[(u16, u16)] = &[(2, 3), (5, 7), (11, 4), (1, 12)];
let mut acc = (u16(0), u16(0));
for &(a_raw, b_raw) in pairs {
let a = f.reduce(&u16(a_raw));
let b = f.reduce(&u16(b_raw));
acc = f.mul_acc(acc, &a, &b);
}
let result = f.wide_redc(acc);
let expected: u16 = pairs
.iter()
.fold(0u16, |s, &(a, b)| (s + (a * b) % 13) % 13);
assert_eq!(f.into_raw(&result), u16(expected));
}
#[test]
fn field_inv_fermat_small() {
let f: Field<U16> = Field::new(u16(13)).unwrap();
for raw in 1u16..13 {
let a = f.reduce(&u16(raw));
let inv = f.inv_fermat(&a).unwrap();
assert_eq!(
f.into_raw(&f.mul(&a, &inv)),
u16(1),
"fermat fails at {raw}"
);
}
assert!(f.inv_fermat(&f.zero()).is_none());
}
#[test]
fn field_inv_eea_small() {
let f: Field<U16> = Field::new(u16(13)).unwrap();
for raw in 1u16..13 {
let a = f.reduce(&u16(raw));
let inv_e = f.inv_eea(&a).unwrap();
let inv_f = f.inv_fermat(&a).unwrap();
assert_eq!(f.into_raw(&f.mul(&a, &inv_e)), u16(1), "eea fails at {raw}");
assert_eq!(
f.into_raw(&inv_e),
f.into_raw(&inv_f),
"fermat/eea disagree at {raw}"
);
}
assert!(f.inv_eea(&f.zero()).is_none());
}
#[test]
fn field_mul_acc_ct_round_trip_small() {
let f = FieldCt::new(u16ct(13)).unwrap();
for a_raw in 0u16..13 {
for b_raw in 0u16..13 {
let a = f.reduce(&u16ct(a_raw));
let b = f.reduce(&u16ct(b_raw));
let direct = f.mul(&a, &b);
let via_acc = f.wide_redc(f.mul_acc((u16ct(0), u16ct(0)), &a, &b));
assert_eq!(f.into_raw(&direct), f.into_raw(&via_acc));
}
}
}
#[test]
fn field_inv_fermat_ct_small() {
let f = FieldCt::new(u16ct(13)).unwrap();
for raw in 1u16..13 {
let a = f.reduce(&u16ct(raw));
let inv = f.inv_fermat(&a).into_option().unwrap();
assert_eq!(
f.into_raw(&f.mul(&a, &inv)),
u16ct(1),
"ct fermat fails at {raw}"
);
}
assert!(f.inv_fermat(&f.zero()).into_option().is_none());
}
#[test]
fn residue_ct_eq_small() {
let f = FieldCt::new(u16ct(13)).unwrap();
let a = f.reduce(&u16ct(7));
let b = f.reduce(&u16ct(7));
let c = f.reduce(&u16ct(8));
let eq_ab: bool = a.ct_eq(&b).into();
let eq_ac: bool = a.ct_eq(&c).into();
assert!(eq_ab);
assert!(!eq_ac);
}
}