use core::marker::PhantomData;
use fixed_bigint::{Ct, Nct, Personality};
use crate::montgomery::basic_mont::{
wide_montgomery_mul, 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
+ num_traits::Zero
+ num_traits::One
+ num_traits::WrappingMul
+ num_traits::WrappingAdd
+ num_traits::WrappingSub
+ num_traits::ops::overflowing::OverflowingAdd
+ Parity
+ MontStorage,
{
pub fn new(modulus: T) -> Option<Self> {
if modulus == T::zero() || modulus.is_even() {
return None;
}
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);
Some(Self {
modulus,
n_prime,
r_mod_n,
r2_mod_n,
_p: PhantomData,
})
}
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
+ num_traits::Zero
+ num_traits::One
+ num_traits::WrappingMul
+ num_traits::WrappingAdd
+ num_traits::WrappingSub
+ num_traits::ops::overflowing::OverflowingAdd
+ Parity
+ 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)
.expect("CIOS mul cannot fail with valid Montgomery parameters");
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)
.expect("CIOS mul cannot fail with valid Montgomery parameters");
}
exp_val >>= 1;
if exp_val > T::zero() {
base_var =
CiosMontMul::cios_mont_mul(&base_var, &base_var, &self.modulus, &self.n_prime)
.expect("CIOS mul cannot fail with valid Montgomery parameters");
}
}
Residue {
mont: result,
_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<T> Field<T, Ct>
where
T: Copy
+ PartialEq
+ PartialOrd
+ num_traits::Zero
+ num_traits::One
+ num_traits::WrappingMul
+ num_traits::WrappingAdd
+ num_traits::WrappingSub
+ num_traits::ops::overflowing::OverflowingAdd
+ Parity
+ MontStorage,
{
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)
.expect("CIOS-CT mul cannot fail with valid Montgomery parameters");
Residue {
mont,
_brand: PhantomData,
_p: PhantomData,
}
}
pub fn exp(&self, base: &Residue<'_, T, Ct>, exp: &T) -> Residue<'_, T, Ct>
where
T: CiosMontMulCt
+ 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)
.expect("CIOS-CT mul cannot fail with valid Montgomery parameters");
let multiplied =
CiosMontMulCt::cios_mont_mul_ct(&result, &base.mont, &self.modulus, &self.n_prime)
.expect("CIOS-CT mul cannot fail with valid Montgomery parameters");
let bit_t = (*exp >> i) & one;
let choice = bit_t.ct_eq(&one);
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)
.expect("CIOS-CT mul cannot fail with valid Montgomery parameters");
if (*exp >> i) & one != zero {
result = CiosMontMulCt::cios_mont_mul_ct(
&result,
&base.mont,
&self.modulus,
&self.n_prime,
)
.expect("CIOS-CT mul cannot fail with valid Montgomery parameters");
}
}
Residue {
mont: result,
_brand: PhantomData,
_p: PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use fixed_bigint::FixedUInt;
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 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() {
use subtle::Choice;
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);
}
#[cfg(feature = "zeroize")]
#[test]
fn residue_zeroize_wipes_mont_small() {
use zeroize::Zeroize;
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);
}
}