From e8d8f4fa3f17db23468c62672126dcd9cef4a2ef Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 27 Jan 2025 10:20:17 +0100 Subject: [PATCH 001/203] Impl `new_inv_mod_odd` --- src/uint/inv_mod.rs | 97 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/src/uint/inv_mod.rs b/src/uint/inv_mod.rs index 36a17e111..4a880b5b0 100644 --- a/src/uint/inv_mod.rs +++ b/src/uint/inv_mod.rs @@ -1,7 +1,6 @@ +use core::cmp::max; use super::Uint; -use crate::{ - modular::SafeGcdInverter, ConstChoice, ConstCtOption, InvMod, Odd, PrecomputeInverter, -}; +use crate::{modular::SafeGcdInverter, ConstChoice, ConstCtOption, InvMod, Odd, PrecomputeInverter, U128, U64, ConstantTimeSelect}; use subtle::CtOption; impl Uint { @@ -92,6 +91,98 @@ where SafeGcdInverter::::new(modulus, &Uint::ONE).inv(self) } + + pub fn new_inv_mod_odd(&self, modulus: &Self) -> ConstCtOption { + const K: u32 = 64; + // Smallest Uint that fits K bits + type Word = U64; + // Smallest Uint that fits 2K bits. + type WideWord = U128; + debug_assert!(WideWord::BITS >= 2 * K); + const K_BITMASK: Uint = Uint::ONE.shl_vartime(K - 1).wrapping_sub(&Uint::ONE); + + let (mut a, mut b) = (*self, modulus.get()); + let (mut u, mut v) = (Self::ONE, Self::ZERO); + + let mut i = 0; + while i < (2 * modulus.bits_vartime() - 1).div_ceil(K) { + i += 1; + + // Construct a_ and b_ as the concatenation of the K most significant and the K least + // significant bits of a and b, respectively. If those bits overlap, TODO + let n = max(max(a.bits(), b.bits()), 2 * K); + + let top_a = a.shr(n - K - 1); + let bottom_a = a.bitand(&K_BITMASK); + let mut a_ = WideWord::from(&top_a) + .shl_vartime(K - 1) + .bitxor(&WideWord::from(bottom_a)); + + let top_b = WideWord::from(&b.shr(n - K - 1)); + let bottom_b = WideWord::from(&b.bitand(&K_BITMASK)); + let mut b_: WideWord = top_b.shl_vartime(K - 1).bitxor(&bottom_b); + + // Unit matrix + let (mut f0, mut g0) = (Word::ONE, Word::ZERO); + let (mut f1, mut g1) = (Word::ZERO, Word::ONE); + + // Compute the update matrix. + let mut j = 0; + while j < K - 1 { + j += 1; + + let a_odd = a_.is_odd(); + let a_lt_b = Uint::lt(&a_, &b_); + + // swap if a odd and a < b + let do_swap = a_odd.and(a_lt_b); + Uint::ct_swap(&mut a_, &mut b_, do_swap.into()); + Uint::ct_swap(&mut f0, &mut f1, do_swap.into()); + Uint::ct_swap(&mut g0, &mut g1, do_swap.into()); + + // subtract b from a + // TODO: perhaps change something about `a_odd` to make this xgcd? + a_ = Uint::select(&a_, &a_.wrapping_sub(&b_), a_odd); + f0 = U64::select(&f0, &f0.wrapping_sub(&f1), a_odd); + g0 = U64::select(&g0, &g0.wrapping_sub(&g1), a_odd); + + // div a by 2 + a_ = a_.shr_vartime(1); + f1 = f1.shl_vartime(1); + g1 = g1.shl_vartime(1); + } + + // Apply matrix to (a, b) + (a, b) = ( + a.widening_mul(&f0) + .wrapping_add(&b.widening_mul(&g0)) + .shr_vartime(K - 1), + a.widening_mul(&f1) + .wrapping_add(&b.widening_mul(&g1)) + .shr_vartime(K - 1), + ); + + // TODO + let a_is_neg = ConstChoice::TRUE; + a = a.wrapping_neg_if(a_is_neg); + f0 = f0.wrapping_neg_if(a_is_neg); + g0 = g0.wrapping_neg_if(a_is_neg); + + // TODO + let b_is_neg = ConstChoice::TRUE; + b = b.wrapping_neg_if(b_is_neg); + f1 = f1.wrapping_neg_if(b_is_neg); + g1 = g1.wrapping_neg_if(b_is_neg); + + (u, v) = ( + u.widening_mul(&f0).add_mod(&v.widening_mul(&g0), modulus), + u.widening_mul(&f1).add_mod(&v.widening_mul(&g1), modulus), + ); + } + + ConstCtOption::new(v, Uint::eq(&b, &Uint::ONE)) + } + /// Computes the multiplicative inverse of `self` mod `modulus`. /// /// Returns some if an inverse exists, otherwise none. From f46213deb65274f554f2b546cd92882d62a93d6b Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 27 Jan 2025 12:14:35 +0100 Subject: [PATCH 002/203] Modify `new_inv_mod_odd` algorithm --- src/limb/cmp.rs | 6 +++ src/uint/inv_mod.rs | 90 ++++++++++++++++++++++++++++----------------- 2 files changed, 62 insertions(+), 34 deletions(-) diff --git a/src/limb/cmp.rs b/src/limb/cmp.rs index 194bc866d..3e044ddbc 100644 --- a/src/limb/cmp.rs +++ b/src/limb/cmp.rs @@ -37,6 +37,12 @@ impl Limb { pub(crate) const fn is_nonzero(&self) -> ConstChoice { ConstChoice::from_word_nonzero(self.0) } + + /// Returns the truthy value if `self == rhs` or the falsy value otherwise. + #[inline] + pub(crate) const fn eq(lhs: Self, rhs: Self) -> ConstChoice { + Limb(lhs.0 ^ rhs.0).is_nonzero().not() + } } impl ConstantTimeEq for Limb { diff --git a/src/uint/inv_mod.rs b/src/uint/inv_mod.rs index 4a880b5b0..764d734ea 100644 --- a/src/uint/inv_mod.rs +++ b/src/uint/inv_mod.rs @@ -1,7 +1,7 @@ use core::cmp::max; use super::Uint; -use crate::{modular::SafeGcdInverter, ConstChoice, ConstCtOption, InvMod, Odd, PrecomputeInverter, U128, U64, ConstantTimeSelect}; -use subtle::CtOption; +use crate::{modular::SafeGcdInverter, ConstChoice, ConstCtOption, InvMod, Odd, PrecomputeInverter, U128, U64, ConstantTimeSelect, CheckedMul, Split, Limb}; +use subtle::{Choice, CtOption}; impl Uint { /// Computes 1/`self` mod `2^k`. @@ -92,14 +92,15 @@ where } - pub fn new_inv_mod_odd(&self, modulus: &Self) -> ConstCtOption { - const K: u32 = 64; + // TODO: assumes `self` < `modulus` + pub fn new_inv_mod_odd(&self, modulus: &Odd) -> ConstCtOption { + const K: u32 = 32; // Smallest Uint that fits K bits type Word = U64; // Smallest Uint that fits 2K bits. - type WideWord = U128; + type WideWord = U64; debug_assert!(WideWord::BITS >= 2 * K); - const K_BITMASK: Uint = Uint::ONE.shl_vartime(K - 1).wrapping_sub(&Uint::ONE); + let k_sub_one_bitmask = Uint::ONE.shl_vartime(K - 1).wrapping_sub(&Uint::ONE); let (mut a, mut b) = (*self, modulus.get()); let (mut u, mut v) = (Self::ONE, Self::ZERO); @@ -109,18 +110,19 @@ where i += 1; // Construct a_ and b_ as the concatenation of the K most significant and the K least - // significant bits of a and b, respectively. If those bits overlap, TODO + // significant bits of a and b, respectively. If those bits overlap, ... TODO + // TODO: is max const time? let n = max(max(a.bits(), b.bits()), 2 * K); - let top_a = a.shr(n - K - 1); - let bottom_a = a.bitand(&K_BITMASK); - let mut a_ = WideWord::from(&top_a) + let hi_a = a.shr(n - K - 1); + let lo_a = a.bitand(&k_sub_one_bitmask); + let mut a_ = WideWord::from(&hi_a) .shl_vartime(K - 1) - .bitxor(&WideWord::from(bottom_a)); + .bitxor(&WideWord::from(&lo_a)); - let top_b = WideWord::from(&b.shr(n - K - 1)); - let bottom_b = WideWord::from(&b.bitand(&K_BITMASK)); - let mut b_: WideWord = top_b.shl_vartime(K - 1).bitxor(&bottom_b); + let hi_b = WideWord::from(&b.shr(n - K - 1)); + let lo_b = WideWord::from(&b.bitand(&k_sub_one_bitmask)); + let mut b_: WideWord = hi_b.shl_vartime(K - 1).bitxor(&lo_b); // Unit matrix let (mut f0, mut g0) = (Word::ONE, Word::ZERO); @@ -134,11 +136,12 @@ where let a_odd = a_.is_odd(); let a_lt_b = Uint::lt(&a_, &b_); + // TODO: make this const // swap if a odd and a < b - let do_swap = a_odd.and(a_lt_b); - Uint::ct_swap(&mut a_, &mut b_, do_swap.into()); - Uint::ct_swap(&mut f0, &mut f1, do_swap.into()); - Uint::ct_swap(&mut g0, &mut g1, do_swap.into()); + let do_swap: Choice = a_odd.and(a_lt_b).into(); + Uint::ct_swap(&mut a_, &mut b_, do_swap); + Uint::ct_swap(&mut f0, &mut f1, do_swap); + Uint::ct_swap(&mut g0, &mut g1, do_swap); // subtract b from a // TODO: perhaps change something about `a_odd` to make this xgcd? @@ -148,35 +151,44 @@ where // div a by 2 a_ = a_.shr_vartime(1); + // mul f1 and g1 by 1 f1 = f1.shl_vartime(1); g1 = g1.shl_vartime(1); } - // Apply matrix to (a, b) - (a, b) = ( - a.widening_mul(&f0) - .wrapping_add(&b.widening_mul(&g0)) - .shr_vartime(K - 1), - a.widening_mul(&f1) - .wrapping_add(&b.widening_mul(&g1)) - .shr_vartime(K - 1), - ); - - // TODO - let a_is_neg = ConstChoice::TRUE; + (a, b) = { + // a := af0 + bg0 + let (lo_a0, hi_a0) = a.split_mul(&f0); + let (lo_a1, hi_a1) = b.split_mul(&g0); + let (lo_a, carry) = lo_a0.adc(&lo_a1, Limb::ZERO); + let (_, carry) = hi_a0.adc(&hi_a1, carry); + let overflow_a: ConstChoice = Limb::eq(carry, Limb::ZERO).not(); + + // b := af1 + bg1 + let (lo_b0, hi_b0) = a.split_mul(&f1); + let (lo_b1, hi_b1) = b.split_mul(&g1); + let (lo_b, carry) = lo_b0.adc(&lo_b1, Limb::ZERO); + let (_, carry) = hi_b0.adc(&hi_b1, carry); + let overflow_b: ConstChoice = Limb::eq(carry, Limb::ZERO).not(); + + (lo_a.wrapping_neg_if(overflow_a).shr_vartime(K-1), lo_b.wrapping_neg_if(overflow_b).shr_vartime(K-1)) + }; + + let a_is_neg = a.as_int().is_negative(); a = a.wrapping_neg_if(a_is_neg); f0 = f0.wrapping_neg_if(a_is_neg); g0 = g0.wrapping_neg_if(a_is_neg); - // TODO - let b_is_neg = ConstChoice::TRUE; + let b_is_neg = b.as_int().is_negative(); b = b.wrapping_neg_if(b_is_neg); f1 = f1.wrapping_neg_if(b_is_neg); g1 = g1.wrapping_neg_if(b_is_neg); + // TODO: fix checked_mul.unwrap failing + // TODO: assumes uf0 + vg0 < 2*modulus... :thinking: (u, v) = ( - u.widening_mul(&f0).add_mod(&v.widening_mul(&g0), modulus), - u.widening_mul(&f1).add_mod(&v.widening_mul(&g1), modulus), + u.checked_mul(&f0).unwrap().add_mod(&v.checked_mul(&g0).unwrap(), modulus), + u.checked_mul(&f1).unwrap().add_mod(&v.checked_mul(&g1).unwrap(), modulus), ); } @@ -376,4 +388,14 @@ mod tests { let res = a.inv_odd_mod(&m); assert!(res.is_none().is_true_vartime()); } + + #[test] + fn test_new_inv_mod_odd() { + let x = U64::from(2u64); + let modulus = U64::from(7u64).to_odd().unwrap(); + + let inv_x = x.new_inv_mod_odd(&modulus).unwrap(); + + assert_eq!(inv_x, U64::from(4u64)); + } } From 378e2ee08924c2f6715e055f8e12c21fc6a0933b Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 27 Jan 2025 16:24:56 +0100 Subject: [PATCH 003/203] Make `as_limbs_mut` const --- src/int.rs | 2 +- src/uint.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/int.rs b/src/int.rs index 3b3d60ee3..bd304ccbf 100644 --- a/src/int.rs +++ b/src/int.rs @@ -119,7 +119,7 @@ impl Int { } /// Borrow the limbs of this [`Int`] mutably. - pub fn as_limbs_mut(&mut self) -> &mut [Limb; LIMBS] { + pub const fn as_limbs_mut(&mut self) -> &mut [Limb; LIMBS] { self.0.as_limbs_mut() } diff --git a/src/uint.rs b/src/uint.rs index fe2208d25..d13e1b580 100644 --- a/src/uint.rs +++ b/src/uint.rs @@ -165,7 +165,7 @@ impl Uint { } /// Borrow the limbs of this [`Uint`] mutably. - pub fn as_limbs_mut(&mut self) -> &mut [Limb; LIMBS] { + pub const fn as_limbs_mut(&mut self) -> &mut [Limb; LIMBS] { &mut self.limbs } From 45fc11d6e2c144d768b1c2f83691c2b59f85e310 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 27 Jan 2025 16:25:34 +0100 Subject: [PATCH 004/203] Introduce const `conditional_swap` --- src/int/cmp.rs | 6 ++++++ src/uint/cmp.rs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/int/cmp.rs b/src/int/cmp.rs index 63a8b2201..b97057909 100644 --- a/src/int/cmp.rs +++ b/src/int/cmp.rs @@ -15,6 +15,12 @@ impl Int { Self(Uint::select(&a.0, &b.0, c)) } + /// Swap `a` and `b` if `c` is truthy, otherwise, do nothing. + #[inline] + pub(crate) const fn conditional_swap(a: &mut Self, b: &mut Self, c: ConstChoice) { + Uint::conditional_swap(&mut a.0, &mut b.0, c); + } + /// Returns the truthy value if `self`!=0 or the falsy value otherwise. #[inline] pub(crate) const fn is_nonzero(&self) -> ConstChoice { diff --git a/src/uint/cmp.rs b/src/uint/cmp.rs index 453f8d119..d2003e29f 100644 --- a/src/uint/cmp.rs +++ b/src/uint/cmp.rs @@ -25,6 +25,12 @@ impl Uint { Uint { limbs } } + /// Swap `a` and `b` if `c` is truthy, otherwise, do nothing. + #[inline] + pub(crate) const fn conditional_swap(a: &mut Self, b: &mut Self, c: ConstChoice) { + (*a, *b) = (Self::select(&a, &b, c), Self::select(&b, &a, c)); + } + /// Returns the truthy value if `self`!=0 or the falsy value otherwise. #[inline] pub(crate) const fn is_nonzero(&self) -> ConstChoice { From 4ba745caf1bb78920beff6bc5b5265a0c584dd15 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 27 Jan 2025 16:43:56 +0100 Subject: [PATCH 005/203] Improve `Int::checked_mul` notation --- src/int/mul.rs | 3 +-- src/int/mul_uint.rs | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/int/mul.rs b/src/int/mul.rs index 315564c4b..f1645a541 100644 --- a/src/int/mul.rs +++ b/src/int/mul.rs @@ -81,8 +81,7 @@ impl CheckedMul> for #[inline] fn checked_mul(&self, rhs: &Int) -> CtOption { let (lo, hi, is_negative) = self.split_mul(rhs); - let val = Self::new_from_abs_sign(lo, is_negative); - CtOption::from(val).and_then(|int| CtOption::new(int, hi.is_zero())) + Self::new_from_abs_sign(lo, is_negative).and_choice(hi.is_nonzero()).into() } } diff --git a/src/int/mul_uint.rs b/src/int/mul_uint.rs index 483e48467..068e95ccb 100644 --- a/src/int/mul_uint.rs +++ b/src/int/mul_uint.rs @@ -62,8 +62,7 @@ impl Int { rhs: &Uint, ) -> CtOption> { let (lo, hi, is_negative) = self.split_mul_uint_right(rhs); - let val = Int::::new_from_abs_sign(lo, is_negative); - CtOption::from(val).and_then(|int| CtOption::new(int, hi.is_zero())) + Int::::new_from_abs_sign(lo, is_negative).and_choice(hi.is_nonzero().not()).into() } } @@ -71,8 +70,7 @@ impl CheckedMul> for #[inline] fn checked_mul(&self, rhs: &Uint) -> CtOption { let (lo, hi, is_negative) = self.split_mul_uint(rhs); - let val = Self::new_from_abs_sign(lo, is_negative); - CtOption::from(val).and_then(|int| CtOption::new(int, hi.is_zero())) + Self::new_from_abs_sign(lo, is_negative).and_choice(hi.is_nonzero().not()).into() } } From 02ceb4feee15ed6bdafaa405cbfa78728843502d Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 27 Jan 2025 16:51:41 +0100 Subject: [PATCH 006/203] Introduce new_gcd --- src/uint.rs | 1 + src/uint/inv_mod.rs | 10 --- src/uint/new_gcd.rs | 192 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+), 10 deletions(-) create mode 100644 src/uint/new_gcd.rs diff --git a/src/uint.rs b/src/uint.rs index d13e1b580..b64c2038f 100644 --- a/src/uint.rs +++ b/src/uint.rs @@ -463,6 +463,7 @@ impl_uint_concat_split_mixed! { #[cfg(feature = "extra-sizes")] mod extra_sizes; +mod new_gcd; #[cfg(test)] #[allow(clippy::unwrap_used)] diff --git a/src/uint/inv_mod.rs b/src/uint/inv_mod.rs index 764d734ea..aecb3e3b5 100644 --- a/src/uint/inv_mod.rs +++ b/src/uint/inv_mod.rs @@ -388,14 +388,4 @@ mod tests { let res = a.inv_odd_mod(&m); assert!(res.is_none().is_true_vartime()); } - - #[test] - fn test_new_inv_mod_odd() { - let x = U64::from(2u64); - let modulus = U64::from(7u64).to_odd().unwrap(); - - let inv_x = x.new_inv_mod_odd(&modulus).unwrap(); - - assert_eq!(inv_x, U64::from(4u64)); - } } diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs new file mode 100644 index 000000000..c9d330c1c --- /dev/null +++ b/src/uint/new_gcd.rs @@ -0,0 +1,192 @@ +use crate::{ConstChoice, Int, Limb, Uint, Word, I64, U64, CheckedSub, CheckedMul}; + +impl Uint { + /// Given `a, b, f` and `g`, compute `(a*f + b*g) / 2^{k-1}`, where it is given that + /// `f.bits()` and `g.bits() <= 2^{k-1}` + #[inline(always)] + const fn addmul_shr_k_sub_1( + a: Uint<{ LIMBS }>, + b: Uint<{ LIMBS }>, + f: I64, + g: I64, + ) -> Int<{ LIMBS }> { + let k_sub_one_bitmask: U64 = U64::ONE.shl_vartime(K - 1).wrapping_sub(&U64::ONE); + // mul + let (mut lo_af0, mut hi_af0, sgn_af0) = f.split_mul_uint(&a); + let (mut lo_bg0, mut hi_bg0, sgn_bg0) = g.split_mul_uint(&b); + // negate if required + lo_af0 = lo_af0.wrapping_neg_if(sgn_af0); + lo_bg0 = lo_bg0.wrapping_neg_if(sgn_bg0); + hi_af0 = Uint::select( + &hi_af0, + &(hi_af0 + .not() + .adc( + &Uint::ZERO, + Limb::select( + Limb::ZERO, + Limb::ONE, + sgn_af0.and(lo_af0.is_nonzero().not()), + ), + ) + .0), + sgn_af0, + ); + hi_bg0 = Uint::select( + &hi_bg0, + &(hi_bg0 + .not() + .adc( + &Uint::ZERO, + Limb::select( + Limb::ZERO, + Limb::ONE, + sgn_bg0.and(lo_bg0.is_nonzero().not()), + ), + ) + .0), + sgn_bg0, + ); + // sum + let (lo_sum, carry) = lo_af0.as_int().overflowing_add(&lo_bg0.as_int()); + let mut hi_sum = hi_af0.as_int().checked_add(&hi_bg0.as_int()).expect("TODO"); + // deal with carry + hi_sum = Int::select(&hi_sum, &hi_sum.wrapping_add(&Int::ONE), carry); + // div by 2^{k-1} + let shifted_lo_sum = lo_sum + .as_uint() + .shr_vartime(K - 1) + .bitand(&k_sub_one_bitmask); + let mut shifted_hi_sum = hi_sum.shl_vartime(I64::BITS - K + 1); + // Note: we're assuming K-1 <= Word::BITS here. + debug_assert!(K - 1 <= Word::BITS); + shifted_hi_sum.as_limbs_mut()[0] = shifted_hi_sum.as_limbs_mut()[0].bitxor(shifted_lo_sum.as_limbs()[0]); + shifted_hi_sum + } + + const fn const_max(a: u32, b: u32) -> u32 { + ConstChoice::from_u32_lt(a, b).select_u32(a, b) + } + + pub fn new_xgcd_odd_vartime(&self, rhs: &Self) -> (Self, Self, Int, Int) { + /// Window size. + const K: u32 = 32; + /// Smallest [Uint] that fits K bits + type SingleK = I64; + /// Smallest [Uint] that fits 2K bits. + type DoubleK = U64; + debug_assert!(DoubleK::BITS >= 2 * K); + const K_SUB_ONE_BITMASK: DoubleK = + DoubleK::ONE.shl_vartime(K - 1).wrapping_sub(&DoubleK::ONE); + + let (mut a, mut b) = (*self, *rhs); + let (mut u, mut v) = (Int::::ONE, Int::::ZERO); + + let (mut sgn_a, mut sgn_b); + + let mut i = 0; + while i < (2 * rhs.bits_vartime() - 1).div_ceil(K) { + i += 1; + + // Construct a_ and b_ as the concatenation of the K most significant and the K least + // significant bits of a and b, respectively. If those bits overlap, ... TODO + let n = Self::const_max(2 * K, Self::const_max(a.bits(), b.bits())); + + let hi_a = a.shr(n - K - 1).resize::<{ DoubleK::LIMBS }>(); // top k+1 bits + let lo_a = a.resize::<{ DoubleK::LIMBS }>().bitand(&K_SUB_ONE_BITMASK); // bottom k-1 bits + let mut a_: DoubleK = hi_a.shl_vartime(K - 1).bitxor(&lo_a); + + let hi_b = b.shr(n - K - 1).resize::<{ DoubleK::LIMBS }>(); + let lo_b = b.resize::<{ DoubleK::LIMBS }>().bitand(&K_SUB_ONE_BITMASK); + let mut b_: DoubleK = hi_b.shl_vartime(K - 1).bitxor(&lo_b); + + // Unit matrix + let (mut f0, mut g0) = (SingleK::ONE, SingleK::ZERO); + let (mut f1, mut g1) = (SingleK::ZERO, SingleK::ONE); + + // Compute the update matrix. + let mut j = 0; + while j < K - 1 { + j += 1; + + // Only apply operations when b ≠ 0, otherwise do nothing. + let b_is_non_zero = b_.is_nonzero(); + + let a_odd = a_.is_odd(); + let a_lt_b = DoubleK::lt(&a_, &b_); + + // swap if a odd and a < b + let do_swap = a_odd.and(a_lt_b).and(b_is_non_zero); + DoubleK::conditional_swap(&mut a_, &mut b_, do_swap); + SingleK::conditional_swap(&mut f0, &mut f1, do_swap); + SingleK::conditional_swap(&mut g0, &mut g1, do_swap); + + // subtract a from b when a is odd and b is non-zero + let do_sub = a_odd.and(b_is_non_zero); + a_ = DoubleK::select(&a_, &a_.wrapping_sub(&b_), do_sub); + f0 = SingleK::select( + &f0, + &f0.checked_sub(&f1).expect("no overflow"), + do_sub, + ); + g0 = SingleK::select( + &g0, + &g0.checked_sub(&g1).expect("no overflow"), + do_sub, + ); + + // mul/div by 2 when b is non-zero. + a_ = DoubleK::select(&a_, &a_.shr_vartime(1), b_is_non_zero); + f1 = SingleK::select(&f1, &f1.shl_vartime(1), b_is_non_zero); + g1 = SingleK::select(&g1, &g1.shl_vartime(1), b_is_non_zero); + } + + let (new_a, new_b) = ( + Self::addmul_shr_k_sub_1::(a, b, f0, g0), + Self::addmul_shr_k_sub_1::(a, b, f1, g1), + ); + + // Correct for case where `a` is negative. + (a, sgn_a) = new_a.abs_sign(); + f0 = f0.wrapping_neg_if(sgn_a); + g0 = g0.wrapping_neg_if(sgn_a); + + // Correct for case where `b` is negative. + (b, sgn_b) = new_b.abs_sign(); + f1 = f1.wrapping_neg_if(sgn_b); + g1 = g1.wrapping_neg_if(sgn_b); + + // Update u and v + (u, v) = ( + u.checked_mul(&f0) + .expect("TODO") + .wrapping_add(&v.checked_mul(&g0).expect("TODO")), + u.checked_mul(&f1) + .expect("TODO") + .wrapping_add(&v.checked_mul(&g1).expect("TODO")), + ); + } + + (a, b, u, v) + } +} + +#[cfg(test)] +mod tests { + use crate::{Random, Uint, U128}; + use rand_core::OsRng; + + #[test] + fn test_new_gcd() { + let x = U128::random(&mut OsRng); + let y = U128::random(&mut OsRng); + + // make y odd: + let y = Uint::select(&y, &(y + Uint::ONE), y.is_odd().not()); + + // let inv_x = x.new_inv_mod_odd(&modulus).unwrap(); + let (x, gcd, a, b) = x.new_xgcd_odd_vartime(&y); + + assert_eq!(gcd, x.gcd(&y), "{} {} {} {}", x, gcd, a, b); + } +} From c6891f4825253da5f76737eb19c3b2df77ef31dc Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 28 Jan 2025 16:13:51 +0100 Subject: [PATCH 007/203] Get bingcd working --- benches/uint.rs | 49 ++++++++---- src/uint/new_gcd.rs | 184 ++++++++++++++++++++++++++++---------------- 2 files changed, 152 insertions(+), 81 deletions(-) diff --git a/benches/uint.rs b/benches/uint.rs index 3bb2a9616..ca1c78b81 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -1,8 +1,5 @@ use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; -use crypto_bigint::{ - Limb, NonZero, Odd, Random, RandomBits, RandomMod, Reciprocal, Uint, U1024, U128, U2048, U256, - U4096, U512, -}; +use crypto_bigint::{Limb, NonZero, Odd, Random, RandomBits, RandomMod, Reciprocal, Uint, U1024, U128, U2048, U256, U4096, U512, Integer, ConstantTimeSelect}; use rand_chacha::ChaCha8Rng; use rand_core::{OsRng, RngCore, SeedableRng}; @@ -305,11 +302,11 @@ fn bench_division(c: &mut Criterion) { fn bench_gcd(c: &mut Criterion) { let mut group = c.benchmark_group("greatest common divisor"); - group.bench_function("gcd, U256", |b| { + group.bench_function("gcd, U2048", |b| { b.iter_batched( || { - let f = U256::random(&mut OsRng); - let g = U256::random(&mut OsRng); + let f = U2048::random(&mut OsRng); + let g = U2048::random(&mut OsRng); (f, g) }, |(f, g)| black_box(f.gcd(&g)), @@ -317,6 +314,30 @@ fn bench_gcd(c: &mut Criterion) { ) }); + group.bench_function("new_gcd, U2048", |b| { + b.iter_batched( + || { + let f = U2048::random(&mut OsRng); + let g = U2048::random(&mut OsRng); + (f, g) + }, + |(f, g)| black_box(Uint::new_gcd(f, g)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("test_gcd, U2048", |b| { + b.iter_batched( + || { + let f = U2048::random(&mut OsRng); + let g = U2048::random(&mut OsRng); + (f, g) + }, + |(f, g)| black_box(Uint::new_gcd_(&f, &g)), + BatchSize::SmallInput, + ) + }); + group.bench_function("gcd_vartime, U256", |b| { b.iter_batched( || { @@ -487,14 +508,14 @@ fn bench_sqrt(c: &mut Criterion) { criterion_group!( benches, - bench_random, - bench_mul, - bench_division, + // bench_random, + // bench_mul, + // bench_division, bench_gcd, - bench_shl, - bench_shr, - bench_inv_mod, - bench_sqrt + // bench_shl, + // bench_shr, + // bench_inv_mod, + // bench_sqrt ); criterion_main!(benches); diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index c9d330c1c..be9104a19 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -1,4 +1,7 @@ -use crate::{ConstChoice, Int, Limb, Uint, Word, I64, U64, CheckedSub, CheckedMul}; +use core::cmp::min; +use num_traits::WrappingSub; +use subtle::Choice; +use crate::{ConstChoice, Int, Limb, Uint, Word, I64, U64, CheckedSub, CheckedMul, ConcatMixed, Split, Odd}; impl Uint { /// Given `a, b, f` and `g`, compute `(a*f + b*g) / 2^{k-1}`, where it is given that @@ -68,7 +71,74 @@ impl Uint { ConstChoice::from_u32_lt(a, b).select_u32(a, b) } - pub fn new_xgcd_odd_vartime(&self, rhs: &Self) -> (Self, Self, Int, Int) { + pub fn new_gcd(mut a: Self, mut b: Self) -> Self { + // Using identities 2 and 3: + // gcd(2ⁱ u, 2ʲ v) = 2ᵏ gcd(u, v) with u, v odd and k = min(i, j) + // 2ᵏ is the greatest power of two that divides both 2ⁱ u and 2ʲ v + let i = a.trailing_zeros(); a = a.shr(i); + let j = b.trailing_zeros(); b = b.shr(j); + let k = min(i, j); + + // Note: at this point both elements are odd. + Self::new_odd_gcd(a, b).shl(k) + } + + pub fn new_odd_gcd(mut a: Self, mut b: Self) -> Self { + let mut i = 0; + while i < 2 * Self::BITS { + // Swap s.t. a ≤ b + let do_swap = Uint::gt(&a, &b); + Uint::conditional_swap(&mut a, &mut b, do_swap); + + // Identity 4: gcd(a, b) = gcd(a, b-a) + b -= a; + + // Identity 3: gcd(a, 2ʲ b) = gcd(a, b) as a is odd + let do_shift = a.is_nonzero().and(b.is_nonzero()); + let shift = do_shift.select_u32(0, b.trailing_zeros()); + b = b.shr(shift); + + i += 1; + } + + b + } + + #[inline] + fn cutdown(a: Self, b: Self) -> (Uint, Uint) { + let k_sub_one_bitmask = Uint::::ONE.shl_vartime(K-1).wrapping_sub(&Uint::::ONE); + + // Construct a_ and b_ as the concatenation of the K most significant and the K least + // significant bits of a and b, respectively. If those bits overlap, ... TODO + let n = Self::const_max(2 * K, Self::const_max(a.bits(), b.bits())); + + let hi_a = a.shr(n - K - 1).resize::<{ CUTDOWN_LIMBS }>(); // top k+1 bits + let lo_a = a.resize::().bitand(&k_sub_one_bitmask); // bottom k-1 bits + let mut a_ = hi_a.shl_vartime(K - 1).bitxor(&lo_a); + + let hi_b = b.shr(n - K - 1).resize::(); + let lo_b = b.resize::().bitand(&k_sub_one_bitmask); + let mut b_ = hi_b.shl_vartime(K - 1).bitxor(&lo_b); + + (a_, b_) + } + + pub fn new_gcd_(&self, rhs: &Self) -> Self + where + Uint: ConcatMixed, MixedOutput=Uint>, + Uint: Split, + { + let i = self.trailing_zeros(); + let j = rhs.trailing_zeros(); + let k = min(i, j); + Self::new_gcd_odd(&self.shr(i), &rhs.shr(j).to_odd().unwrap()).shl(k) + } + + pub fn new_gcd_odd(&self, rhs: &Odd) -> Self + where + Uint: ConcatMixed, MixedOutput=Uint>, + Uint: Split, + { /// Window size. const K: u32 = 32; /// Smallest [Uint] that fits K bits @@ -79,114 +149,94 @@ impl Uint { const K_SUB_ONE_BITMASK: DoubleK = DoubleK::ONE.shl_vartime(K - 1).wrapping_sub(&DoubleK::ONE); - let (mut a, mut b) = (*self, *rhs); - let (mut u, mut v) = (Int::::ONE, Int::::ZERO); - - let (mut sgn_a, mut sgn_b); + let (mut a, mut b) = (*self, rhs.get()); let mut i = 0; while i < (2 * rhs.bits_vartime() - 1).div_ceil(K) { i += 1; - // Construct a_ and b_ as the concatenation of the K most significant and the K least - // significant bits of a and b, respectively. If those bits overlap, ... TODO - let n = Self::const_max(2 * K, Self::const_max(a.bits(), b.bits())); - - let hi_a = a.shr(n - K - 1).resize::<{ DoubleK::LIMBS }>(); // top k+1 bits - let lo_a = a.resize::<{ DoubleK::LIMBS }>().bitand(&K_SUB_ONE_BITMASK); // bottom k-1 bits - let mut a_: DoubleK = hi_a.shl_vartime(K - 1).bitxor(&lo_a); - - let hi_b = b.shr(n - K - 1).resize::<{ DoubleK::LIMBS }>(); - let lo_b = b.resize::<{ DoubleK::LIMBS }>().bitand(&K_SUB_ONE_BITMASK); - let mut b_: DoubleK = hi_b.shl_vartime(K - 1).bitxor(&lo_b); + let (mut a_, mut b_) = Self::cutdown::(a, b); // Unit matrix let (mut f0, mut g0) = (SingleK::ONE, SingleK::ZERO); let (mut f1, mut g1) = (SingleK::ZERO, SingleK::ONE); // Compute the update matrix. + let mut used_increments = 0; let mut j = 0; while j < K - 1 { j += 1; - // Only apply operations when b ≠ 0, otherwise do nothing. - let b_is_non_zero = b_.is_nonzero(); - let a_odd = a_.is_odd(); let a_lt_b = DoubleK::lt(&a_, &b_); // swap if a odd and a < b - let do_swap = a_odd.and(a_lt_b).and(b_is_non_zero); + let do_swap = a_odd.and(a_lt_b); DoubleK::conditional_swap(&mut a_, &mut b_, do_swap); SingleK::conditional_swap(&mut f0, &mut f1, do_swap); SingleK::conditional_swap(&mut g0, &mut g1, do_swap); // subtract a from b when a is odd and b is non-zero - let do_sub = a_odd.and(b_is_non_zero); - a_ = DoubleK::select(&a_, &a_.wrapping_sub(&b_), do_sub); - f0 = SingleK::select( - &f0, - &f0.checked_sub(&f1).expect("no overflow"), - do_sub, - ); - g0 = SingleK::select( - &g0, - &g0.checked_sub(&g1).expect("no overflow"), - do_sub, - ); + a_ = DoubleK::select(&a_, &a_.wrapping_sub(&b_), a_odd); + f0 = SingleK::select(&f0, &f0.wrapping_sub(&f1), a_odd); + g0 = SingleK::select(&g0, &g0.wrapping_sub(&g1), a_odd); // mul/div by 2 when b is non-zero. - a_ = DoubleK::select(&a_, &a_.shr_vartime(1), b_is_non_zero); - f1 = SingleK::select(&f1, &f1.shl_vartime(1), b_is_non_zero); - g1 = SingleK::select(&g1, &g1.shl_vartime(1), b_is_non_zero); + // Only apply operations when b ≠ 0, otherwise do nothing. + let do_apply = b_.is_nonzero(); + a_ = DoubleK::select(&a_, &a_.shr_vartime(1), do_apply); + f1 = SingleK::select(&f1, &f1.shl_vartime(1), do_apply); + g1 = SingleK::select(&g1, &g1.shl_vartime(1), do_apply); + used_increments = do_apply.select_u32(used_increments, used_increments + 1); } + // TODO: fix this + let mut f0 = f0.resize::(); + let mut f1 = f1.resize::(); + let mut g0 = g0.resize::(); + let mut g1 = g1.resize::(); + let (new_a, new_b) = ( - Self::addmul_shr_k_sub_1::(a, b, f0, g0), - Self::addmul_shr_k_sub_1::(a, b, f1, g1), + f0.widening_mul_uint(&a).wrapping_add(&g0.widening_mul_uint(&b)).shr(used_increments), + f1.widening_mul_uint(&a).wrapping_add(&g1.widening_mul_uint(&b)).shr(used_increments) ); - // Correct for case where `a` is negative. - (a, sgn_a) = new_a.abs_sign(); - f0 = f0.wrapping_neg_if(sgn_a); - g0 = g0.wrapping_neg_if(sgn_a); - - // Correct for case where `b` is negative. - (b, sgn_b) = new_b.abs_sign(); - f1 = f1.wrapping_neg_if(sgn_b); - g1 = g1.wrapping_neg_if(sgn_b); - - // Update u and v - (u, v) = ( - u.checked_mul(&f0) - .expect("TODO") - .wrapping_add(&v.checked_mul(&g0).expect("TODO")), - u.checked_mul(&f1) - .expect("TODO") - .wrapping_add(&v.checked_mul(&g1).expect("TODO")), - ); + a = new_a.resize::().abs(); + b = new_b.resize::().abs(); } - (a, b, u, v) + b } } #[cfg(test)] mod tests { - use crate::{Random, Uint, U128}; + use crate::{Uint, Random, U2048, Gcd, ConcatMixed, Split}; use rand_core::OsRng; + fn gcd_comparison_test(lhs: Uint, rhs: Uint) + where + Uint: Gcd>, + Uint: ConcatMixed, MixedOutput=Uint>, + Uint: Split + { + let gcd = lhs.gcd(&rhs); + let new_gcd = Uint::new_gcd(lhs, rhs); + let bingcd = lhs.new_gcd_odd(&rhs.to_odd().unwrap()); + + assert_eq!(gcd, new_gcd); + assert_eq!(gcd, bingcd); + } + #[test] fn test_new_gcd() { - let x = U128::random(&mut OsRng); - let y = U128::random(&mut OsRng); + for _ in 0..500 { + let x = U2048::random(&mut OsRng); + let mut y = U2048::random(&mut OsRng); - // make y odd: - let y = Uint::select(&y, &(y + Uint::ONE), y.is_odd().not()); + y = Uint::select(&(y.wrapping_add(&Uint::ONE)), &y, y.is_odd()); - // let inv_x = x.new_inv_mod_odd(&modulus).unwrap(); - let (x, gcd, a, b) = x.new_xgcd_odd_vartime(&y); - - assert_eq!(gcd, x.gcd(&y), "{} {} {} {}", x, gcd, a, b); + gcd_comparison_test(x,y); + } } } From b9fb154c8e74d707b0b90df472032965a31d7958 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 10:25:16 +0100 Subject: [PATCH 008/203] Fix fmt --- src/int/mul.rs | 4 +++- src/int/mul_uint.rs | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/int/mul.rs b/src/int/mul.rs index f1645a541..41f321001 100644 --- a/src/int/mul.rs +++ b/src/int/mul.rs @@ -81,7 +81,9 @@ impl CheckedMul> for #[inline] fn checked_mul(&self, rhs: &Int) -> CtOption { let (lo, hi, is_negative) = self.split_mul(rhs); - Self::new_from_abs_sign(lo, is_negative).and_choice(hi.is_nonzero()).into() + Self::new_from_abs_sign(lo, is_negative) + .and_choice(hi.is_nonzero()) + .into() } } diff --git a/src/int/mul_uint.rs b/src/int/mul_uint.rs index 068e95ccb..187da61f1 100644 --- a/src/int/mul_uint.rs +++ b/src/int/mul_uint.rs @@ -62,7 +62,9 @@ impl Int { rhs: &Uint, ) -> CtOption> { let (lo, hi, is_negative) = self.split_mul_uint_right(rhs); - Int::::new_from_abs_sign(lo, is_negative).and_choice(hi.is_nonzero().not()).into() + Int::::new_from_abs_sign(lo, is_negative) + .and_choice(hi.is_nonzero().not()) + .into() } } @@ -70,7 +72,9 @@ impl CheckedMul> for #[inline] fn checked_mul(&self, rhs: &Uint) -> CtOption { let (lo, hi, is_negative) = self.split_mul_uint(rhs); - Self::new_from_abs_sign(lo, is_negative).and_choice(hi.is_nonzero().not()).into() + Self::new_from_abs_sign(lo, is_negative) + .and_choice(hi.is_nonzero().not()) + .into() } } From ffe7bb2ccd3ee1c1bd214964b1f856aa442eb16d Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 10:36:12 +0100 Subject: [PATCH 009/203] 65mus U1024::gcd --- benches/uint.rs | 17 ++++- src/uint/cmp.rs | 2 +- src/uint/inv_mod.rs | 21 ++++-- src/uint/new_gcd.rs | 178 +++++++++++++++++++++++++++++++++++--------- 4 files changed, 175 insertions(+), 43 deletions(-) diff --git a/benches/uint.rs b/benches/uint.rs index ca1c78b81..7f5305a86 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -1,5 +1,8 @@ use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; -use crypto_bigint::{Limb, NonZero, Odd, Random, RandomBits, RandomMod, Reciprocal, Uint, U1024, U128, U2048, U256, U4096, U512, Integer, ConstantTimeSelect}; +use crypto_bigint::{ + ConstantTimeSelect, Integer, Limb, NonZero, Odd, Random, RandomBits, RandomMod, Reciprocal, + Uint, U1024, U128, U2048, U256, U4096, U512, +}; use rand_chacha::ChaCha8Rng; use rand_core::{OsRng, RngCore, SeedableRng}; @@ -338,6 +341,18 @@ fn bench_gcd(c: &mut Criterion) { ) }); + group.bench_function("test_gcd, U1024", |b| { + b.iter_batched( + || { + let f = U1024::random(&mut OsRng); + let g = U1024::random(&mut OsRng); + (f, g) + }, + |(f, g)| black_box(Uint::new_gcd_(&f, &g)), + BatchSize::SmallInput, + ) + }); + group.bench_function("gcd_vartime, U256", |b| { b.iter_batched( || { diff --git a/src/uint/cmp.rs b/src/uint/cmp.rs index d2003e29f..10d6d776a 100644 --- a/src/uint/cmp.rs +++ b/src/uint/cmp.rs @@ -28,7 +28,7 @@ impl Uint { /// Swap `a` and `b` if `c` is truthy, otherwise, do nothing. #[inline] pub(crate) const fn conditional_swap(a: &mut Self, b: &mut Self, c: ConstChoice) { - (*a, *b) = (Self::select(&a, &b, c), Self::select(&b, &a, c)); + (*a, *b) = (Self::select(a, b, c), Self::select(b, a, c)); } /// Returns the truthy value if `self`!=0 or the falsy value otherwise. diff --git a/src/uint/inv_mod.rs b/src/uint/inv_mod.rs index aecb3e3b5..0c7d30964 100644 --- a/src/uint/inv_mod.rs +++ b/src/uint/inv_mod.rs @@ -1,6 +1,9 @@ -use core::cmp::max; use super::Uint; -use crate::{modular::SafeGcdInverter, ConstChoice, ConstCtOption, InvMod, Odd, PrecomputeInverter, U128, U64, ConstantTimeSelect, CheckedMul, Split, Limb}; +use crate::{ + modular::SafeGcdInverter, CheckedMul, ConstChoice, ConstCtOption, ConstantTimeSelect, InvMod, + Limb, Odd, PrecomputeInverter, Split, U64, +}; +use core::cmp::max; use subtle::{Choice, CtOption}; impl Uint { @@ -91,7 +94,6 @@ where SafeGcdInverter::::new(modulus, &Uint::ONE).inv(self) } - // TODO: assumes `self` < `modulus` pub fn new_inv_mod_odd(&self, modulus: &Odd) -> ConstCtOption { const K: u32 = 32; @@ -171,7 +173,10 @@ where let (_, carry) = hi_b0.adc(&hi_b1, carry); let overflow_b: ConstChoice = Limb::eq(carry, Limb::ZERO).not(); - (lo_a.wrapping_neg_if(overflow_a).shr_vartime(K-1), lo_b.wrapping_neg_if(overflow_b).shr_vartime(K-1)) + ( + lo_a.wrapping_neg_if(overflow_a).shr_vartime(K - 1), + lo_b.wrapping_neg_if(overflow_b).shr_vartime(K - 1), + ) }; let a_is_neg = a.as_int().is_negative(); @@ -187,8 +192,12 @@ where // TODO: fix checked_mul.unwrap failing // TODO: assumes uf0 + vg0 < 2*modulus... :thinking: (u, v) = ( - u.checked_mul(&f0).unwrap().add_mod(&v.checked_mul(&g0).unwrap(), modulus), - u.checked_mul(&f1).unwrap().add_mod(&v.checked_mul(&g1).unwrap(), modulus), + u.checked_mul(&f0) + .unwrap() + .add_mod(&v.checked_mul(&g0).unwrap(), modulus), + u.checked_mul(&f1) + .unwrap() + .add_mod(&v.checked_mul(&g1).unwrap(), modulus), ); } diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index be9104a19..7cfeaede4 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -1,7 +1,52 @@ +use crate::{ + CheckedMul, CheckedSub, ConcatMixed, ConstChoice, ConstCtOption, Int, Limb, Odd, Split, Uint, + Word, I64, U64, +}; use core::cmp::min; -use num_traits::WrappingSub; -use subtle::Choice; -use crate::{ConstChoice, Int, Limb, Uint, Word, I64, U64, CheckedSub, CheckedMul, ConcatMixed, Split, Odd}; +use num_traits::{WrappingSub}; +struct UintPlus(Uint, Limb); + +impl UintPlus { + pub const fn wrapping_neg(&self) -> Self { + let (lhs, carry) = self.0.carrying_neg(); + let mut rhs = self.1.not(); + rhs = Limb::select(rhs, rhs.wrapping_add(Limb::ONE), carry); + Self(lhs, rhs) + } + + pub const fn wrapping_neg_if(&self, negate: ConstChoice) -> Self { + let neg = self.wrapping_neg(); + Self( + Uint::select(&self.0, &neg.0, negate), + Limb::select(self.1, neg.1, negate), + ) + } + + pub const fn wrapping_add(&self, rhs: &Self) -> Self { + let (lo, carry) = self.0.adc(&rhs.0, Limb::ZERO); + let (hi, _) = self.1.adc(rhs.1, carry); + Self(lo, hi) + } + + pub const fn shr(&self, shift: u32) -> Self { + let hi = self.1.shr(shift); + let zero_shift = ConstChoice::from_u32_eq(shift, 0); + let leftshift = zero_shift.select_u32(Limb::BITS - shift, 0); + let carry = Limb::select(self.1.shl(leftshift), Limb::ZERO, zero_shift); + let mut lo = self.0.shr(shift); + lo.as_limbs_mut()[LIMBS - 1] = lo.as_limbs_mut()[LIMBS - 1].bitxor(carry); + Self(lo, hi) + } + + pub const fn abs(&self) -> Self { + let is_negative = ConstChoice::from_word_msb(self.1.bitand(Limb::ONE.shl(Limb::HI_BIT)).0); + self.wrapping_neg_if(is_negative) + } + + pub const fn unpack(&self) -> ConstCtOption> { + ConstCtOption::new(self.0, self.1.is_nonzero().not()) + } +} impl Uint { /// Given `a, b, f` and `g`, compute `(a*f + b*g) / 2^{k-1}`, where it is given that @@ -63,7 +108,8 @@ impl Uint { let mut shifted_hi_sum = hi_sum.shl_vartime(I64::BITS - K + 1); // Note: we're assuming K-1 <= Word::BITS here. debug_assert!(K - 1 <= Word::BITS); - shifted_hi_sum.as_limbs_mut()[0] = shifted_hi_sum.as_limbs_mut()[0].bitxor(shifted_lo_sum.as_limbs()[0]); + shifted_hi_sum.as_limbs_mut()[0] = + shifted_hi_sum.as_limbs_mut()[0].bitxor(shifted_lo_sum.as_limbs()[0]); shifted_hi_sum } @@ -75,8 +121,10 @@ impl Uint { // Using identities 2 and 3: // gcd(2ⁱ u, 2ʲ v) = 2ᵏ gcd(u, v) with u, v odd and k = min(i, j) // 2ᵏ is the greatest power of two that divides both 2ⁱ u and 2ʲ v - let i = a.trailing_zeros(); a = a.shr(i); - let j = b.trailing_zeros(); b = b.shr(j); + let i = a.trailing_zeros(); + a = a.shr(i); + let j = b.trailing_zeros(); + b = b.shr(j); let k = min(i, j); // Note: at this point both elements are odd. @@ -105,8 +153,13 @@ impl Uint { } #[inline] - fn cutdown(a: Self, b: Self) -> (Uint, Uint) { - let k_sub_one_bitmask = Uint::::ONE.shl_vartime(K-1).wrapping_sub(&Uint::::ONE); + fn cutdown( + a: Self, + b: Self, + ) -> (Uint, Uint) { + let k_sub_one_bitmask = Uint::::ONE + .shl_vartime(K - 1) + .wrapping_sub(&Uint::::ONE); // Construct a_ and b_ as the concatenation of the K most significant and the K least // significant bits of a and b, respectively. If those bits overlap, ... TODO @@ -114,18 +167,18 @@ impl Uint { let hi_a = a.shr(n - K - 1).resize::<{ CUTDOWN_LIMBS }>(); // top k+1 bits let lo_a = a.resize::().bitand(&k_sub_one_bitmask); // bottom k-1 bits - let mut a_ = hi_a.shl_vartime(K - 1).bitxor(&lo_a); + let a_ = hi_a.shl_vartime(K - 1).bitxor(&lo_a); let hi_b = b.shr(n - K - 1).resize::(); let lo_b = b.resize::().bitand(&k_sub_one_bitmask); - let mut b_ = hi_b.shl_vartime(K - 1).bitxor(&lo_b); + let b_ = hi_b.shl_vartime(K - 1).bitxor(&lo_b); (a_, b_) } pub fn new_gcd_(&self, rhs: &Self) -> Self where - Uint: ConcatMixed, MixedOutput=Uint>, + Uint: ConcatMixed, MixedOutput = Uint>, Uint: Split, { let i = self.trailing_zeros(); @@ -136,7 +189,7 @@ impl Uint { pub fn new_gcd_odd(&self, rhs: &Odd) -> Self where - Uint: ConcatMixed, MixedOutput=Uint>, + Uint: ConcatMixed, MixedOutput = Uint>, Uint: Split, { /// Window size. @@ -146,8 +199,6 @@ impl Uint { /// Smallest [Uint] that fits 2K bits. type DoubleK = U64; debug_assert!(DoubleK::BITS >= 2 * K); - const K_SUB_ONE_BITMASK: DoubleK = - DoubleK::ONE.shl_vartime(K - 1).wrapping_sub(&DoubleK::ONE); let (mut a, mut b) = (*self, rhs.get()); @@ -155,7 +206,7 @@ impl Uint { while i < (2 * rhs.bits_vartime() - 1).div_ceil(K) { i += 1; - let (mut a_, mut b_) = Self::cutdown::(a, b); + let (mut a_, mut b_) = Self::cutdown::(a, b); // Unit matrix let (mut f0, mut g0) = (SingleK::ONE, SingleK::ZERO); @@ -190,19 +241,74 @@ impl Uint { used_increments = do_apply.select_u32(used_increments, used_increments + 1); } - // TODO: fix this - let mut f0 = f0.resize::(); - let mut f1 = f1.resize::(); - let mut g0 = g0.resize::(); - let mut g1 = g1.resize::(); - - let (new_a, new_b) = ( - f0.widening_mul_uint(&a).wrapping_add(&g0.widening_mul_uint(&b)).shr(used_increments), - f1.widening_mul_uint(&a).wrapping_add(&g1.widening_mul_uint(&b)).shr(used_increments) - ); - - a = new_a.resize::().abs(); - b = new_b.resize::().abs(); + // Pack together into one object + let (lo_af0, hi_af0, sgn_af0) = f0.split_mul_uint_right(&a); + let (lo_bg0, hi_bg0, sgn_bg0) = g0.split_mul_uint_right(&b); + let af0 = UintPlus(lo_af0, hi_af0.as_limbs()[0]).wrapping_neg_if(sgn_af0); + let bg0 = UintPlus(lo_bg0, hi_bg0.as_limbs()[0]).wrapping_neg_if(sgn_bg0); + + // Pack together into one object + let (lo_af1, hi_af1, sgn_af1) = f1.split_mul_uint_right(&a); + let (lo_bg1, hi_bg1, sgn_bg1) = g1.split_mul_uint_right(&b); + let af1 = UintPlus(lo_af1, hi_af1.as_limbs()[0]).wrapping_neg_if(sgn_af1); + let bg1 = UintPlus(lo_bg1, hi_bg1.as_limbs()[0]).wrapping_neg_if(sgn_bg1); + + a = af0 + .wrapping_add(&bg0) + .abs() + .shr(used_increments) + .unpack() + .expect("top limb is zero"); + b = af1 + .wrapping_add(&bg1) + .abs() + .shr(used_increments) + .unpack() + .expect("top limb is zero"); + + // // + // + // // + // // let (lo_af0, hi_af0, sgn_af0) = f0.split_mul_uint_right(&a); + // // let (lo_bg0, hi_bg0, sgn_bg0) = g0.split_mul_uint_right(&b); + // // + // // // Pack together into one object + // // let mut af0 = UintPlus(lo_af0, hi_af0.as_limbs()[0]); + // // let mut bg0 = UintPlus(lo_bg0, hi_bg0.as_limbs()[0]); + // // + // // // Wrapping neg if + // // af0 = af0.wrapping_neg_if(sgn_af0); + // // bg0 = bg0.wrapping_neg_if(sgn_bg0); + // // + // // let a = af0.wrapping_add(&bg0).shr(used_increments).unpack().expect("top limb is zero"); + // // + // // + // // let (lo_a, carry) = lo_af0.adc(&lo_bg0, Limb::ZERO); + // // let (hi_a, _) = hi_af0.adc(&hi_bg0, carry); // will not overflow + // // + // // let is_negative = hi_a.as_int().is_negative(); + // + // // construct new a + // a = abs_hi_a.shl(used_increments); + // a.as_limbs_mut()[0] = a.as_limbs_mut()[0].bitxor(lo_a.as_limbs_mut()[0]); + // + // // TODO: fix this + // let mut f0 = f0.resize::(); + // let mut f1 = f1.resize::(); + // let mut g0 = g0.resize::(); + // let mut g1 = g1.resize::(); + // + // let (new_a, new_b) = ( + // f0.widening_mul_uint(&a) + // .wrapping_add(&g0.widening_mul_uint(&b)) + // .shr(used_increments), + // f1.widening_mul_uint(&a) + // .wrapping_add(&g1.widening_mul_uint(&b)) + // .shr(used_increments), + // ); + // + // a = new_a.resize::().abs(); + // b = new_b.resize::().abs(); } b @@ -211,14 +317,16 @@ impl Uint { #[cfg(test)] mod tests { - use crate::{Uint, Random, U2048, Gcd, ConcatMixed, Split}; + use crate::{ConcatMixed, Gcd, Random, Split, Uint, U2048}; use rand_core::OsRng; - fn gcd_comparison_test(lhs: Uint, rhs: Uint) - where - Uint: Gcd>, - Uint: ConcatMixed, MixedOutput=Uint>, - Uint: Split + fn gcd_comparison_test( + lhs: Uint, + rhs: Uint, + ) where + Uint: Gcd>, + Uint: ConcatMixed, MixedOutput = Uint>, + Uint: Split, { let gcd = lhs.gcd(&rhs); let new_gcd = Uint::new_gcd(lhs, rhs); @@ -236,7 +344,7 @@ mod tests { y = Uint::select(&(y.wrapping_add(&Uint::ONE)), &y, y.is_odd()); - gcd_comparison_test(x,y); + gcd_comparison_test(x, y); } } } From dc6f5171367c4a332659040ce77a3c59ad60e3e8 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 10:43:48 +0100 Subject: [PATCH 010/203] Clean up --- benches/uint.rs | 12 ++-- src/uint/new_gcd.rs | 158 ++------------------------------------------ 2 files changed, 13 insertions(+), 157 deletions(-) diff --git a/benches/uint.rs b/benches/uint.rs index 7f5305a86..a7f056d22 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -317,14 +317,14 @@ fn bench_gcd(c: &mut Criterion) { ) }); - group.bench_function("new_gcd, U2048", |b| { + group.bench_function("gcd, U1024", |b| { b.iter_batched( || { - let f = U2048::random(&mut OsRng); - let g = U2048::random(&mut OsRng); + let f = U1024::random(&mut OsRng); + let g = U1024::random(&mut OsRng); (f, g) }, - |(f, g)| black_box(Uint::new_gcd(f, g)), + |(f, g)| black_box(f.gcd(&g)), BatchSize::SmallInput, ) }); @@ -336,7 +336,7 @@ fn bench_gcd(c: &mut Criterion) { let g = U2048::random(&mut OsRng); (f, g) }, - |(f, g)| black_box(Uint::new_gcd_(&f, &g)), + |(f, g)| black_box(Uint::new_gcd(&f, &g)), BatchSize::SmallInput, ) }); @@ -348,7 +348,7 @@ fn bench_gcd(c: &mut Criterion) { let g = U1024::random(&mut OsRng); (f, g) }, - |(f, g)| black_box(Uint::new_gcd_(&f, &g)), + |(f, g)| black_box(Uint::new_gcd(&f, &g)), BatchSize::SmallInput, ) }); diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index 7cfeaede4..4611b7e61 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -1,9 +1,10 @@ use crate::{ - CheckedMul, CheckedSub, ConcatMixed, ConstChoice, ConstCtOption, Int, Limb, Odd, Split, Uint, - Word, I64, U64, + CheckedMul, CheckedSub, ConcatMixed, ConstChoice, ConstCtOption, Limb, Odd, Split, Uint, + I64, U64, }; use core::cmp::min; use num_traits::{WrappingSub}; + struct UintPlus(Uint, Limb); impl UintPlus { @@ -49,109 +50,11 @@ impl UintPlus { } impl Uint { - /// Given `a, b, f` and `g`, compute `(a*f + b*g) / 2^{k-1}`, where it is given that - /// `f.bits()` and `g.bits() <= 2^{k-1}` - #[inline(always)] - const fn addmul_shr_k_sub_1( - a: Uint<{ LIMBS }>, - b: Uint<{ LIMBS }>, - f: I64, - g: I64, - ) -> Int<{ LIMBS }> { - let k_sub_one_bitmask: U64 = U64::ONE.shl_vartime(K - 1).wrapping_sub(&U64::ONE); - // mul - let (mut lo_af0, mut hi_af0, sgn_af0) = f.split_mul_uint(&a); - let (mut lo_bg0, mut hi_bg0, sgn_bg0) = g.split_mul_uint(&b); - // negate if required - lo_af0 = lo_af0.wrapping_neg_if(sgn_af0); - lo_bg0 = lo_bg0.wrapping_neg_if(sgn_bg0); - hi_af0 = Uint::select( - &hi_af0, - &(hi_af0 - .not() - .adc( - &Uint::ZERO, - Limb::select( - Limb::ZERO, - Limb::ONE, - sgn_af0.and(lo_af0.is_nonzero().not()), - ), - ) - .0), - sgn_af0, - ); - hi_bg0 = Uint::select( - &hi_bg0, - &(hi_bg0 - .not() - .adc( - &Uint::ZERO, - Limb::select( - Limb::ZERO, - Limb::ONE, - sgn_bg0.and(lo_bg0.is_nonzero().not()), - ), - ) - .0), - sgn_bg0, - ); - // sum - let (lo_sum, carry) = lo_af0.as_int().overflowing_add(&lo_bg0.as_int()); - let mut hi_sum = hi_af0.as_int().checked_add(&hi_bg0.as_int()).expect("TODO"); - // deal with carry - hi_sum = Int::select(&hi_sum, &hi_sum.wrapping_add(&Int::ONE), carry); - // div by 2^{k-1} - let shifted_lo_sum = lo_sum - .as_uint() - .shr_vartime(K - 1) - .bitand(&k_sub_one_bitmask); - let mut shifted_hi_sum = hi_sum.shl_vartime(I64::BITS - K + 1); - // Note: we're assuming K-1 <= Word::BITS here. - debug_assert!(K - 1 <= Word::BITS); - shifted_hi_sum.as_limbs_mut()[0] = - shifted_hi_sum.as_limbs_mut()[0].bitxor(shifted_lo_sum.as_limbs()[0]); - shifted_hi_sum - } const fn const_max(a: u32, b: u32) -> u32 { ConstChoice::from_u32_lt(a, b).select_u32(a, b) } - pub fn new_gcd(mut a: Self, mut b: Self) -> Self { - // Using identities 2 and 3: - // gcd(2ⁱ u, 2ʲ v) = 2ᵏ gcd(u, v) with u, v odd and k = min(i, j) - // 2ᵏ is the greatest power of two that divides both 2ⁱ u and 2ʲ v - let i = a.trailing_zeros(); - a = a.shr(i); - let j = b.trailing_zeros(); - b = b.shr(j); - let k = min(i, j); - - // Note: at this point both elements are odd. - Self::new_odd_gcd(a, b).shl(k) - } - - pub fn new_odd_gcd(mut a: Self, mut b: Self) -> Self { - let mut i = 0; - while i < 2 * Self::BITS { - // Swap s.t. a ≤ b - let do_swap = Uint::gt(&a, &b); - Uint::conditional_swap(&mut a, &mut b, do_swap); - - // Identity 4: gcd(a, b) = gcd(a, b-a) - b -= a; - - // Identity 3: gcd(a, 2ʲ b) = gcd(a, b) as a is odd - let do_shift = a.is_nonzero().and(b.is_nonzero()); - let shift = do_shift.select_u32(0, b.trailing_zeros()); - b = b.shr(shift); - - i += 1; - } - - b - } - #[inline] fn cutdown( a: Self, @@ -176,7 +79,7 @@ impl Uint { (a_, b_) } - pub fn new_gcd_(&self, rhs: &Self) -> Self + pub fn new_gcd(&self, rhs: &Self) -> Self where Uint: ConcatMixed, MixedOutput = Uint>, Uint: Split, @@ -184,10 +87,10 @@ impl Uint { let i = self.trailing_zeros(); let j = rhs.trailing_zeros(); let k = min(i, j); - Self::new_gcd_odd(&self.shr(i), &rhs.shr(j).to_odd().unwrap()).shl(k) + Self::new_odd_gcd(&self.shr(i), &rhs.shr(j).to_odd().unwrap()).shl(k) } - pub fn new_gcd_odd(&self, rhs: &Odd) -> Self + pub fn new_odd_gcd(&self, rhs: &Odd) -> Self where Uint: ConcatMixed, MixedOutput = Uint>, Uint: Split, @@ -265,50 +168,6 @@ impl Uint { .shr(used_increments) .unpack() .expect("top limb is zero"); - - // // - // - // // - // // let (lo_af0, hi_af0, sgn_af0) = f0.split_mul_uint_right(&a); - // // let (lo_bg0, hi_bg0, sgn_bg0) = g0.split_mul_uint_right(&b); - // // - // // // Pack together into one object - // // let mut af0 = UintPlus(lo_af0, hi_af0.as_limbs()[0]); - // // let mut bg0 = UintPlus(lo_bg0, hi_bg0.as_limbs()[0]); - // // - // // // Wrapping neg if - // // af0 = af0.wrapping_neg_if(sgn_af0); - // // bg0 = bg0.wrapping_neg_if(sgn_bg0); - // // - // // let a = af0.wrapping_add(&bg0).shr(used_increments).unpack().expect("top limb is zero"); - // // - // // - // // let (lo_a, carry) = lo_af0.adc(&lo_bg0, Limb::ZERO); - // // let (hi_a, _) = hi_af0.adc(&hi_bg0, carry); // will not overflow - // // - // // let is_negative = hi_a.as_int().is_negative(); - // - // // construct new a - // a = abs_hi_a.shl(used_increments); - // a.as_limbs_mut()[0] = a.as_limbs_mut()[0].bitxor(lo_a.as_limbs_mut()[0]); - // - // // TODO: fix this - // let mut f0 = f0.resize::(); - // let mut f1 = f1.resize::(); - // let mut g0 = g0.resize::(); - // let mut g1 = g1.resize::(); - // - // let (new_a, new_b) = ( - // f0.widening_mul_uint(&a) - // .wrapping_add(&g0.widening_mul_uint(&b)) - // .shr(used_increments), - // f1.widening_mul_uint(&a) - // .wrapping_add(&g1.widening_mul_uint(&b)) - // .shr(used_increments), - // ); - // - // a = new_a.resize::().abs(); - // b = new_b.resize::().abs(); } b @@ -329,10 +188,7 @@ mod tests { Uint: Split, { let gcd = lhs.gcd(&rhs); - let new_gcd = Uint::new_gcd(lhs, rhs); - let bingcd = lhs.new_gcd_odd(&rhs.to_odd().unwrap()); - - assert_eq!(gcd, new_gcd); + let bingcd = lhs.new_odd_gcd(&rhs.to_odd().unwrap()); assert_eq!(gcd, bingcd); } From a3253d8c0742f22c6d8479b895ca7afccef180af Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 10:44:46 +0100 Subject: [PATCH 011/203] Clean --- benches/uint.rs | 2 +- src/int/mul.rs | 2 +- src/int/mul_uint.rs | 2 +- src/uint/inv_mod.rs | 2 +- src/uint/new_gcd.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/benches/uint.rs b/benches/uint.rs index a7f056d22..8eb048bf1 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -1,6 +1,6 @@ use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; use crypto_bigint::{ - ConstantTimeSelect, Integer, Limb, NonZero, Odd, Random, RandomBits, RandomMod, Reciprocal, + Limb, NonZero, Odd, Random, RandomBits, RandomMod, Reciprocal, Uint, U1024, U128, U2048, U256, U4096, U512, }; use rand_chacha::ChaCha8Rng; diff --git a/src/int/mul.rs b/src/int/mul.rs index 41f321001..1e7ff99c4 100644 --- a/src/int/mul.rs +++ b/src/int/mul.rs @@ -4,7 +4,7 @@ use core::ops::{Mul, MulAssign}; use subtle::CtOption; -use crate::{Checked, CheckedMul, ConcatMixed, ConstChoice, ConstCtOption, Int, Uint, Zero}; +use crate::{Checked, CheckedMul, ConcatMixed, ConstChoice, ConstCtOption, Int, Uint}; impl Int { /// Compute "wide" multiplication as a 3-tuple `(lo, hi, negate)`. diff --git a/src/int/mul_uint.rs b/src/int/mul_uint.rs index 187da61f1..4ddd543c2 100644 --- a/src/int/mul_uint.rs +++ b/src/int/mul_uint.rs @@ -2,7 +2,7 @@ use core::ops::Mul; use subtle::CtOption; -use crate::{CheckedMul, ConcatMixed, ConstChoice, Int, Uint, Zero}; +use crate::{CheckedMul, ConcatMixed, ConstChoice, Int, Uint}; impl Int { /// Compute "wide" multiplication between an [`Int`] and [`Uint`] as 3-tuple `(lo, hi, negate)`. diff --git a/src/uint/inv_mod.rs b/src/uint/inv_mod.rs index 0c7d30964..bec1b9f00 100644 --- a/src/uint/inv_mod.rs +++ b/src/uint/inv_mod.rs @@ -1,7 +1,7 @@ use super::Uint; use crate::{ modular::SafeGcdInverter, CheckedMul, ConstChoice, ConstCtOption, ConstantTimeSelect, InvMod, - Limb, Odd, PrecomputeInverter, Split, U64, + Limb, Odd, PrecomputeInverter, U64, }; use core::cmp::max; use subtle::{Choice, CtOption}; diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index 4611b7e61..39f346da9 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -1,5 +1,5 @@ use crate::{ - CheckedMul, CheckedSub, ConcatMixed, ConstChoice, ConstCtOption, Limb, Odd, Split, Uint, + ConcatMixed, ConstChoice, ConstCtOption, Limb, Odd, Split, Uint, I64, U64, }; use core::cmp::min; From 6b956816a4f85969ef2310e9867f3da9896ed884 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 10:45:37 +0100 Subject: [PATCH 012/203] Remove `DOUBLE` requirement --- src/uint/new_gcd.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index 39f346da9..99ba2d93e 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -79,22 +79,14 @@ impl Uint { (a_, b_) } - pub fn new_gcd(&self, rhs: &Self) -> Self - where - Uint: ConcatMixed, MixedOutput = Uint>, - Uint: Split, - { + pub fn new_gcd(&self, rhs: &Self) -> Self { let i = self.trailing_zeros(); let j = rhs.trailing_zeros(); let k = min(i, j); Self::new_odd_gcd(&self.shr(i), &rhs.shr(j).to_odd().unwrap()).shl(k) } - pub fn new_odd_gcd(&self, rhs: &Odd) -> Self - where - Uint: ConcatMixed, MixedOutput = Uint>, - Uint: Split, - { + pub fn new_odd_gcd(&self, rhs: &Odd) -> Self { /// Window size. const K: u32 = 32; /// Smallest [Uint] that fits K bits From b5c995173c9f9d949c8e19d6f062ad3bc34cda02 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 11:43:25 +0100 Subject: [PATCH 013/203] Extract restricted xgcd. --- benches/uint.rs | 4 +- src/uint/new_gcd.rs | 93 +++++++++++++++++++++++++++------------------ 2 files changed, 58 insertions(+), 39 deletions(-) diff --git a/benches/uint.rs b/benches/uint.rs index 8eb048bf1..feeffd069 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -1,7 +1,7 @@ use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; use crypto_bigint::{ - Limb, NonZero, Odd, Random, RandomBits, RandomMod, Reciprocal, - Uint, U1024, U128, U2048, U256, U4096, U512, + Limb, NonZero, Odd, Random, RandomBits, RandomMod, Reciprocal, Uint, U1024, U128, U2048, U256, + U4096, U512, }; use rand_chacha::ChaCha8Rng; use rand_core::{OsRng, RngCore, SeedableRng}; diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index 99ba2d93e..df3819a44 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -1,9 +1,9 @@ use crate::{ - ConcatMixed, ConstChoice, ConstCtOption, Limb, Odd, Split, Uint, + ConcatMixed, ConstChoice, ConstCtOption, Int, Limb, Odd, Split, Uint, I64, U64, }; use core::cmp::min; -use num_traits::{WrappingSub}; +use num_traits::WrappingSub; struct UintPlus(Uint, Limb); @@ -49,6 +49,8 @@ impl UintPlus { } } +type UpdateMatrix = (Int, Int, Int, Int); + impl Uint { const fn const_max(a: u32, b: u32) -> u32 { @@ -79,6 +81,50 @@ impl Uint { (a_, b_) } + #[inline] + fn restricted_extended_gcd( + mut a: Uint, + mut b: Uint, + iterations: u32, + ) -> (UpdateMatrix, u32) { + debug_assert!(iterations < Uint::::BITS); + + // Unit matrix + let (mut f0, mut g0) = (Int::ONE, Int::ZERO); + let (mut f1, mut g1) = (Int::ZERO, Int::ONE); + + // Compute the update matrix. + let mut used_increments = 0; + let mut j = 0; + while j < iterations { + j += 1; + + let a_odd = a.is_odd(); + let a_lt_b = Uint::lt(&a, &b); + + // swap if a odd and a < b + let do_swap = a_odd.and(a_lt_b); + Uint::conditional_swap(&mut a, &mut b, do_swap); + Int::conditional_swap(&mut f0, &mut f1, do_swap); + Int::conditional_swap(&mut g0, &mut g1, do_swap); + + // subtract a from b when a is odd + a = Uint::select(&a, &a.wrapping_sub(&b), a_odd); + f0 = Int::select(&f0, &f0.wrapping_sub(&f1), a_odd); + g0 = Int::select(&g0, &g0.wrapping_sub(&g1), a_odd); + + // mul/div by 2 when b is non-zero. + // Only apply operations when b ≠ 0, otherwise do nothing. + let do_apply = b.is_nonzero(); + a = Uint::select(&a, &a.shr_vartime(1), do_apply); + f1 = Int::select(&f1, &f1.shl_vartime(1), do_apply); + g1 = Int::select(&g1, &g1.shl_vartime(1), do_apply); + used_increments = do_apply.select_u32(used_increments, used_increments + 1); + } + + ((f0, f1, g0, g1), used_increments) + } + pub fn new_gcd(&self, rhs: &Self) -> Self { let i = self.trailing_zeros(); let j = rhs.trailing_zeros(); @@ -89,7 +135,7 @@ impl Uint { pub fn new_odd_gcd(&self, rhs: &Odd) -> Self { /// Window size. const K: u32 = 32; - /// Smallest [Uint] that fits K bits + /// Smallest [Int] that fits a K-bit [Uint]. type SingleK = I64; /// Smallest [Uint] that fits 2K bits. type DoubleK = U64; @@ -101,40 +147,13 @@ impl Uint { while i < (2 * rhs.bits_vartime() - 1).div_ceil(K) { i += 1; - let (mut a_, mut b_) = Self::cutdown::(a, b); - - // Unit matrix - let (mut f0, mut g0) = (SingleK::ONE, SingleK::ZERO); - let (mut f1, mut g1) = (SingleK::ZERO, SingleK::ONE); - - // Compute the update matrix. - let mut used_increments = 0; - let mut j = 0; - while j < K - 1 { - j += 1; - - let a_odd = a_.is_odd(); - let a_lt_b = DoubleK::lt(&a_, &b_); - - // swap if a odd and a < b - let do_swap = a_odd.and(a_lt_b); - DoubleK::conditional_swap(&mut a_, &mut b_, do_swap); - SingleK::conditional_swap(&mut f0, &mut f1, do_swap); - SingleK::conditional_swap(&mut g0, &mut g1, do_swap); - - // subtract a from b when a is odd and b is non-zero - a_ = DoubleK::select(&a_, &a_.wrapping_sub(&b_), a_odd); - f0 = SingleK::select(&f0, &f0.wrapping_sub(&f1), a_odd); - g0 = SingleK::select(&g0, &g0.wrapping_sub(&g1), a_odd); - - // mul/div by 2 when b is non-zero. - // Only apply operations when b ≠ 0, otherwise do nothing. - let do_apply = b_.is_nonzero(); - a_ = DoubleK::select(&a_, &a_.shr_vartime(1), do_apply); - f1 = SingleK::select(&f1, &f1.shl_vartime(1), do_apply); - g1 = SingleK::select(&g1, &g1.shl_vartime(1), do_apply); - used_increments = do_apply.select_u32(used_increments, used_increments + 1); - } + let (a_, b_) = Self::cutdown::(a, b); + + + // Compute the K-1 iteration update matrix from a_ and b_ + let (matrix, used_increments) = + Uint::restricted_extended_gcd::<{ SingleK::LIMBS }>(a_, b_, K - 1); + let (f0, f1, g0, g1) = matrix; // Pack together into one object let (lo_af0, hi_af0, sgn_af0) = f0.split_mul_uint_right(&a); From 09b9ee76c4d3746fbb2510bb1e376f89a7ba7b38 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 12:22:54 +0100 Subject: [PATCH 014/203] Introduce `const_min` and `const_max` --- src/uint/new_gcd.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index df3819a44..7244b92d7 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -51,11 +51,17 @@ impl UintPlus { type UpdateMatrix = (Int, Int, Int, Int); -impl Uint { +/// `const` equivalent of `u32::max(a, b)`. +const fn const_max(a: u32, b: u32) -> u32 { + ConstChoice::from_u32_lt(a, b).select_u32(a, b) +} - const fn const_max(a: u32, b: u32) -> u32 { - ConstChoice::from_u32_lt(a, b).select_u32(a, b) - } +/// `const` equivalent of `u32::min(a, b)`. +const fn const_min(a: u32, b: u32) -> u32 { + ConstChoice::from_u32_lt(a, b).select_u32(b, a) +} + +impl Uint { #[inline] fn cutdown( @@ -68,7 +74,7 @@ impl Uint { // Construct a_ and b_ as the concatenation of the K most significant and the K least // significant bits of a and b, respectively. If those bits overlap, ... TODO - let n = Self::const_max(2 * K, Self::const_max(a.bits(), b.bits())); + let n = const_max(2 * K, const_max(a.bits(), b.bits())); let hi_a = a.shr(n - K - 1).resize::<{ CUTDOWN_LIMBS }>(); // top k+1 bits let lo_a = a.resize::().bitand(&k_sub_one_bitmask); // bottom k-1 bits @@ -128,7 +134,7 @@ impl Uint { pub fn new_gcd(&self, rhs: &Self) -> Self { let i = self.trailing_zeros(); let j = rhs.trailing_zeros(); - let k = min(i, j); + let k = const_min(i, j); Self::new_odd_gcd(&self.shr(i), &rhs.shr(j).to_odd().unwrap()).shl(k) } From fbd39e6fcaa7353a02a19446da1d722fcad18687 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 12:26:24 +0100 Subject: [PATCH 015/203] Clean up `summarize` --- src/uint/new_gcd.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index 7244b92d7..d24fb7aeb 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -1,7 +1,4 @@ -use crate::{ - ConcatMixed, ConstChoice, ConstCtOption, Int, Limb, Odd, Split, Uint, - I64, U64, -}; +use crate::{ConcatMixed, ConstChoice, ConstCtOption, Int, Limb, Odd, Split, Uint, I64, U64}; use core::cmp::min; use num_traits::WrappingSub; @@ -62,26 +59,30 @@ const fn const_min(a: u32, b: u32) -> u32 { } impl Uint { - #[inline] - fn cutdown( + const fn summarize( a: Self, b: Self, - ) -> (Uint, Uint) { - let k_sub_one_bitmask = Uint::::ONE - .shl_vartime(K - 1) - .wrapping_sub(&Uint::::ONE); + ) -> (Uint, Uint) { + let k_plus_one_bitmask = Uint::ONE.shl_vartime(K + 1).wrapping_sub(&Uint::ONE); + let k_sub_one_bitmask = Uint::ONE.shl_vartime(K - 1).wrapping_sub(&Uint::ONE); // Construct a_ and b_ as the concatenation of the K most significant and the K least // significant bits of a and b, respectively. If those bits overlap, ... TODO let n = const_max(2 * K, const_max(a.bits(), b.bits())); - let hi_a = a.shr(n - K - 1).resize::<{ CUTDOWN_LIMBS }>(); // top k+1 bits - let lo_a = a.resize::().bitand(&k_sub_one_bitmask); // bottom k-1 bits + let hi_a = a + .shr(n - K - 1) + .resize::() + .bitand(&k_plus_one_bitmask); + let lo_a = a.resize::().bitand(&k_sub_one_bitmask); let a_ = hi_a.shl_vartime(K - 1).bitxor(&lo_a); - let hi_b = b.shr(n - K - 1).resize::(); - let lo_b = b.resize::().bitand(&k_sub_one_bitmask); + let hi_b = b + .shr(n - K - 1) + .resize::() + .bitand(&k_plus_one_bitmask); + let lo_b = b.resize::().bitand(&k_sub_one_bitmask); let b_ = hi_b.shl_vartime(K - 1).bitxor(&lo_b); (a_, b_) @@ -153,8 +154,7 @@ impl Uint { while i < (2 * rhs.bits_vartime() - 1).div_ceil(K) { i += 1; - let (a_, b_) = Self::cutdown::(a, b); - + let (a_, b_) = Self::summarize::(a, b); // Compute the K-1 iteration update matrix from a_ and b_ let (matrix, used_increments) = From a7f8daef32e2ef87856c235d4949e3073e2d5c98 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 12:48:13 +0100 Subject: [PATCH 016/203] Clean up `compact` --- benches/uint.rs | 46 +++++++++++++-------------- src/uint/new_gcd.rs | 77 ++++++++++++++++++++++++++++----------------- 2 files changed, 72 insertions(+), 51 deletions(-) diff --git a/benches/uint.rs b/benches/uint.rs index feeffd069..35e304804 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -305,29 +305,29 @@ fn bench_division(c: &mut Criterion) { fn bench_gcd(c: &mut Criterion) { let mut group = c.benchmark_group("greatest common divisor"); - group.bench_function("gcd, U2048", |b| { - b.iter_batched( - || { - let f = U2048::random(&mut OsRng); - let g = U2048::random(&mut OsRng); - (f, g) - }, - |(f, g)| black_box(f.gcd(&g)), - BatchSize::SmallInput, - ) - }); - - group.bench_function("gcd, U1024", |b| { - b.iter_batched( - || { - let f = U1024::random(&mut OsRng); - let g = U1024::random(&mut OsRng); - (f, g) - }, - |(f, g)| black_box(f.gcd(&g)), - BatchSize::SmallInput, - ) - }); + // group.bench_function("gcd, U2048", |b| { + // b.iter_batched( + // || { + // let f = U2048::random(&mut OsRng); + // let g = U2048::random(&mut OsRng); + // (f, g) + // }, + // |(f, g)| black_box(f.gcd(&g)), + // BatchSize::SmallInput, + // ) + // }); + // + // group.bench_function("gcd, U1024", |b| { + // b.iter_batched( + // || { + // let f = U1024::random(&mut OsRng); + // let g = U1024::random(&mut OsRng); + // (f, g) + // }, + // |(f, g)| black_box(f.gcd(&g)), + // BatchSize::SmallInput, + // ) + // }); group.bench_function("test_gcd, U2048", |b| { b.iter_batched( diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index d24fb7aeb..71a863107 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -59,33 +59,51 @@ const fn const_min(a: u32, b: u32) -> u32 { } impl Uint { - #[inline] - const fn summarize( - a: Self, - b: Self, - ) -> (Uint, Uint) { - let k_plus_one_bitmask = Uint::ONE.shl_vartime(K + 1).wrapping_sub(&Uint::ONE); - let k_sub_one_bitmask = Uint::ONE.shl_vartime(K - 1).wrapping_sub(&Uint::ONE); - - // Construct a_ and b_ as the concatenation of the K most significant and the K least - // significant bits of a and b, respectively. If those bits overlap, ... TODO - let n = const_max(2 * K, const_max(a.bits(), b.bits())); - - let hi_a = a - .shr(n - K - 1) - .resize::() - .bitand(&k_plus_one_bitmask); - let lo_a = a.resize::().bitand(&k_sub_one_bitmask); - let a_ = hi_a.shl_vartime(K - 1).bitxor(&lo_a); - - let hi_b = b - .shr(n - K - 1) - .resize::() - .bitand(&k_plus_one_bitmask); - let lo_b = b.resize::().bitand(&k_sub_one_bitmask); - let b_ = hi_b.shl_vartime(K - 1).bitxor(&lo_b); - - (a_, b_) + /// Construct a [Uint] containing the bits in `self` in the range `[idx, idx + length)`. + /// + /// Assumes `length ≤ Uint::::BITS` and `idx + length ≤ Self::BITS`. + #[inline(always)] + const fn section(&self, idx: u32, length: u32) -> Uint { + debug_assert!(length <= Uint::::BITS); + debug_assert!(idx + length <= Self::BITS); + + let mask = Uint::ONE.shl(length).wrapping_sub(&Uint::ONE); + self.shr(idx).resize::().bitand(&mask) + } + + /// Construct a [Uint] containing the bits in `self` in the range `[idx, idx + length)`. + /// + /// Assumes `length ≤ Uint::::BITS` and `idx + length ≤ Self::BITS`. + /// + /// Executes in time variable in `idx` only. + #[inline(always)] + const fn section_vartime( + &self, + idx: u32, + length: u32, + ) -> Uint { + debug_assert!(length <= Uint::::BITS); + debug_assert!(idx + length <= Self::BITS); + + let mask = Uint::ONE.shl_vartime(length).wrapping_sub(&Uint::ONE); + self.shr_vartime(idx) + .resize::() + .bitand(&mask) + } + + /// Compact `self` to a form containing the concatenation of its bit ranges `[0, k-1)` + /// and `[n-k-1, n)`. + /// + /// Assumes `k ≤ Uint::::BITS`, `n ≤ Self::BITS` and `n ≥ 2k`. + #[inline(always)] + const fn compact(&self, n: u32, k: u32) -> Uint { + debug_assert!(k <= Uint::::BITS); + debug_assert!(n <= Self::BITS); + debug_assert!(n >= 2 * k); + + let hi = self.section(n - k - 1, k + 1); + let lo = self.section_vartime(0, k - 1); + hi.shl_vartime(k - 1).bitxor(&lo) } #[inline] @@ -154,7 +172,10 @@ impl Uint { while i < (2 * rhs.bits_vartime() - 1).div_ceil(K) { i += 1; - let (a_, b_) = Self::summarize::(a, b); + // Construct a_ and b_ as the summary of a and b, respectively. + let n = const_max(2 * K, const_max(a.bits(), b.bits())); + let a_: DoubleK = a.compact(n, K); + let b_: DoubleK = b.compact(n, K); // Compute the K-1 iteration update matrix from a_ and b_ let (matrix, used_increments) = From 41d32f668c4d2f4b5ce41af5c4a5665337cf683e Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 13:07:25 +0100 Subject: [PATCH 017/203] Update `ExtendedInt` --- src/uint/new_gcd.rs | 63 ++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index 71a863107..9b9f633ec 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -1,14 +1,19 @@ -use crate::{ConcatMixed, ConstChoice, ConstCtOption, Int, Limb, Odd, Split, Uint, I64, U64}; -use core::cmp::min; -use num_traits::WrappingSub; +use crate::{ConcatMixed, ConstChoice, ConstCtOption, Int, Limb, Odd, Split, Uint, I64, U128}; +use num_traits::{ToPrimitive, WrappingSub}; -struct UintPlus(Uint, Limb); +struct ExtendedInt(Uint, Uint); + +impl ExtendedInt { + /// Construct an [ExtendedInt] from the product of a [Uint] and an [Int]. + pub const fn from_product(lhs: Uint, rhs: Int) -> Self { + let (lo, hi, sgn) = rhs.split_mul_uint_right(&lhs); + ExtendedInt(lo, hi).wrapping_neg_if(sgn) + } -impl UintPlus { pub const fn wrapping_neg(&self) -> Self { let (lhs, carry) = self.0.carrying_neg(); let mut rhs = self.1.not(); - rhs = Limb::select(rhs, rhs.wrapping_add(Limb::ONE), carry); + rhs = Uint::select(&rhs, &rhs.wrapping_add(&Uint::ONE), carry); Self(lhs, rhs) } @@ -16,28 +21,35 @@ impl UintPlus { let neg = self.wrapping_neg(); Self( Uint::select(&self.0, &neg.0, negate), - Limb::select(self.1, neg.1, negate), + Uint::select(&self.1, &neg.1, negate), ) } pub const fn wrapping_add(&self, rhs: &Self) -> Self { let (lo, carry) = self.0.adc(&rhs.0, Limb::ZERO); - let (hi, _) = self.1.adc(rhs.1, carry); + let (hi, _) = self.1.adc(&rhs.1, carry); Self(lo, hi) } pub const fn shr(&self, shift: u32) -> Self { + let shift_is_zero = ConstChoice::from_u32_eq(shift, 0); + let left_shift = shift_is_zero.select_u32(Uint::::BITS - shift, 0); + let hi = self.1.shr(shift); - let zero_shift = ConstChoice::from_u32_eq(shift, 0); - let leftshift = zero_shift.select_u32(Limb::BITS - shift, 0); - let carry = Limb::select(self.1.shl(leftshift), Limb::ZERO, zero_shift); + // TODO: replace with carrying_shl + let carry = Uint::select(&self.1, &Uint::ZERO, shift_is_zero).shl(left_shift); let mut lo = self.0.shr(shift); - lo.as_limbs_mut()[LIMBS - 1] = lo.as_limbs_mut()[LIMBS - 1].bitxor(carry); + + // Apply carry + let limb_diff = LIMBS.wrapping_sub(EXTRA) as u32; + let carry = carry.resize::().shl_vartime(limb_diff * Limb::BITS); + lo = lo.bitxor(&carry); + Self(lo, hi) } pub const fn abs(&self) -> Self { - let is_negative = ConstChoice::from_word_msb(self.1.bitand(Limb::ONE.shl(Limb::HI_BIT)).0); + let is_negative = self.1.as_int().is_negative(); self.wrapping_neg_if(is_negative) } @@ -63,7 +75,11 @@ impl Uint { /// /// Assumes `length ≤ Uint::::BITS` and `idx + length ≤ Self::BITS`. #[inline(always)] - const fn section(&self, idx: u32, length: u32) -> Uint { + const fn section( + &self, + idx: u32, + length: u32, + ) -> Uint { debug_assert!(length <= Uint::::BITS); debug_assert!(idx + length <= Self::BITS); @@ -159,11 +175,11 @@ impl Uint { pub fn new_odd_gcd(&self, rhs: &Odd) -> Self { /// Window size. - const K: u32 = 32; + const K: u32 = 63; /// Smallest [Int] that fits a K-bit [Uint]. type SingleK = I64; /// Smallest [Uint] that fits 2K bits. - type DoubleK = U64; + type DoubleK = U128; debug_assert!(DoubleK::BITS >= 2 * K); let (mut a, mut b) = (*self, rhs.get()); @@ -182,17 +198,10 @@ impl Uint { Uint::restricted_extended_gcd::<{ SingleK::LIMBS }>(a_, b_, K - 1); let (f0, f1, g0, g1) = matrix; - // Pack together into one object - let (lo_af0, hi_af0, sgn_af0) = f0.split_mul_uint_right(&a); - let (lo_bg0, hi_bg0, sgn_bg0) = g0.split_mul_uint_right(&b); - let af0 = UintPlus(lo_af0, hi_af0.as_limbs()[0]).wrapping_neg_if(sgn_af0); - let bg0 = UintPlus(lo_bg0, hi_bg0.as_limbs()[0]).wrapping_neg_if(sgn_bg0); - - // Pack together into one object - let (lo_af1, hi_af1, sgn_af1) = f1.split_mul_uint_right(&a); - let (lo_bg1, hi_bg1, sgn_bg1) = g1.split_mul_uint_right(&b); - let af1 = UintPlus(lo_af1, hi_af1.as_limbs()[0]).wrapping_neg_if(sgn_af1); - let bg1 = UintPlus(lo_bg1, hi_bg1.as_limbs()[0]).wrapping_neg_if(sgn_bg1); + let af0 = ExtendedInt::from_product(a, f0); + let af1 = ExtendedInt::from_product(a, f1); + let bg0 = ExtendedInt::from_product(b, g0); + let bg1 = ExtendedInt::from_product(b, g1); a = af0 .wrapping_add(&bg0) From e445ceb5e4d4d192f8452597cdcf3b0c99bd5449 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 14:35:15 +0100 Subject: [PATCH 018/203] Impl `Matrix` --- src/uint/new_gcd.rs | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index 9b9f633ec..581a5ae59 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -1,7 +1,10 @@ use crate::{ConcatMixed, ConstChoice, ConstCtOption, Int, Limb, Odd, Split, Uint, I64, U128}; use num_traits::{ToPrimitive, WrappingSub}; -struct ExtendedInt(Uint, Uint); +struct ExtendedInt( + Uint, + Uint, +); impl ExtendedInt { /// Construct an [ExtendedInt] from the product of a [Uint] and an [Int]. @@ -58,7 +61,22 @@ impl ExtendedInt { } } -type UpdateMatrix = (Int, Int, Int, Int); +struct Matrix([[T; DIM]; DIM]); +impl Matrix, 2> { + + #[inline] + const fn extended_apply_to( + &self, + vec: (Uint, Uint), + ) -> (ExtendedInt, ExtendedInt) { + let (a, b) = vec; + let a00 = ExtendedInt::from_product(a, self.0[0][0]); + let a01 = ExtendedInt::from_product(a, self.0[0][1]); + let b10 = ExtendedInt::from_product(b, self.0[1][0]); + let b11 = ExtendedInt::from_product(b, self.0[1][1]); + (a00.wrapping_add(&b10), a01.wrapping_add(&b11)) + } +} /// `const` equivalent of `u32::max(a, b)`. const fn const_max(a: u32, b: u32) -> u32 { @@ -127,7 +145,7 @@ impl Uint { mut a: Uint, mut b: Uint, iterations: u32, - ) -> (UpdateMatrix, u32) { + ) -> (Matrix, 2>, u32) { debug_assert!(iterations < Uint::::BITS); // Unit matrix @@ -163,7 +181,7 @@ impl Uint { used_increments = do_apply.select_u32(used_increments, used_increments + 1); } - ((f0, f1, g0, g1), used_increments) + (Matrix([[f0, f1], [g0, g1]]), used_increments) } pub fn new_gcd(&self, rhs: &Self) -> Self { @@ -196,21 +214,16 @@ impl Uint { // Compute the K-1 iteration update matrix from a_ and b_ let (matrix, used_increments) = Uint::restricted_extended_gcd::<{ SingleK::LIMBS }>(a_, b_, K - 1); - let (f0, f1, g0, g1) = matrix; - let af0 = ExtendedInt::from_product(a, f0); - let af1 = ExtendedInt::from_product(a, f1); - let bg0 = ExtendedInt::from_product(b, g0); - let bg1 = ExtendedInt::from_product(b, g1); + // Update `a` and `b` using the update matrix + let (updated_a, updated_b) = matrix.extended_apply_to((a, b)); - a = af0 - .wrapping_add(&bg0) + a = updated_a .abs() .shr(used_increments) .unpack() .expect("top limb is zero"); - b = af1 - .wrapping_add(&bg1) + b = updated_b .abs() .shr(used_increments) .unpack() From 30aabf152e6a5268e3e0a6a905d41fbb6fa9e56e Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 14:35:26 +0100 Subject: [PATCH 019/203] Make `new_odd_gcd` constant time --- src/uint/new_gcd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index 581a5ae59..048b45900 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -203,7 +203,7 @@ impl Uint { let (mut a, mut b) = (*self, rhs.get()); let mut i = 0; - while i < (2 * rhs.bits_vartime() - 1).div_ceil(K) { + while i < (2 * Self::BITS - 1).div_ceil(K) { i += 1; // Construct a_ and b_ as the summary of a and b, respectively. From 51b93f0ff22a5a0c8cd98d71db85fa0eb46a5ce8 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 14:58:50 +0100 Subject: [PATCH 020/203] Replace `shr` by proper `div_2k` --- src/uint/new_gcd.rs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index 048b45900..dc9f4dcf5 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -1,5 +1,5 @@ -use crate::{ConcatMixed, ConstChoice, ConstCtOption, Int, Limb, Odd, Split, Uint, I64, U128}; -use num_traits::{ToPrimitive, WrappingSub}; +use crate::{ConstChoice, ConstCtOption, Int, Limb, Odd, Uint, I64, U128}; +use num_traits::WrappingSub; struct ExtendedInt( Uint, @@ -7,6 +7,9 @@ struct ExtendedInt( ); impl ExtendedInt { + + const ZERO: ExtendedInt = Self(Uint::ZERO, Uint::ZERO); + /// Construct an [ExtendedInt] from the product of a [Uint] and an [Int]. pub const fn from_product(lhs: Uint, rhs: Int) -> Self { let (lo, hi, sgn) = rhs.split_mul_uint_right(&lhs); @@ -35,10 +38,12 @@ impl ExtendedInt { } pub const fn shr(&self, shift: u32) -> Self { + debug_assert!(shift <= Uint::::BITS); + let shift_is_zero = ConstChoice::from_u32_eq(shift, 0); let left_shift = shift_is_zero.select_u32(Uint::::BITS - shift, 0); - let hi = self.1.shr(shift); + let hi = *self.1.as_int().shr(shift).as_uint(); // TODO: replace with carrying_shl let carry = Uint::select(&self.1, &Uint::ZERO, shift_is_zero).shl(left_shift); let mut lo = self.0.shr(shift); @@ -59,6 +64,18 @@ impl ExtendedInt { pub const fn unpack(&self) -> ConstCtOption> { ConstCtOption::new(self.0, self.1.is_nonzero().not()) } + + /// Return `b` if `c` is truthy, otherwise return `a`. + pub const fn select(a: &Self, b: &Self, c: ConstChoice) -> Self { + Self(Uint::select(&a.0, &b.0, c), Uint::select(&a.1, &b.1, c)) + } + + /// Divide self by `2^k`. + pub const fn div_2k(&self, k: u32) -> Self { + let lo_is_minus_one = Int::eq(&self.0.as_int(), &Int::MINUS_ONE); + let ext_is_minus_one = Int::eq(&self.1.as_int(), &Int::MINUS_ONE); + Self::select(&self.shr(k), &Self::ZERO, lo_is_minus_one.and(ext_is_minus_one)) + } } struct Matrix([[T; DIM]; DIM]); @@ -220,12 +237,12 @@ impl Uint { a = updated_a .abs() - .shr(used_increments) + .div_2k(used_increments) .unpack() .expect("top limb is zero"); b = updated_b .abs() - .shr(used_increments) + .div_2k(used_increments) .unpack() .expect("top limb is zero"); } From 714d608b4c16955ce7de4cdbab705071e90bf97d Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 15:02:14 +0100 Subject: [PATCH 021/203] Remove `ExtendedInt::abs` --- src/uint/new_gcd.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index dc9f4dcf5..d919e5585 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -56,13 +56,11 @@ impl ExtendedInt { Self(lo, hi) } - pub const fn abs(&self) -> Self { - let is_negative = self.1.as_int().is_negative(); - self.wrapping_neg_if(is_negative) - } - - pub const fn unpack(&self) -> ConstCtOption> { - ConstCtOption::new(self.0, self.1.is_nonzero().not()) + pub const fn unpack(&self) -> ConstCtOption> { + let lo_is_negative = self.0.as_int().is_negative(); + let proper_positive = Int::eq(&self.1.as_int(), &Int::ZERO).and(lo_is_negative.not()); + let proper_negative = Int::eq(&self.1.as_int(), &Int::MINUS_ONE).and(lo_is_negative); + ConstCtOption::new(self.0.as_int(), proper_negative.or(proper_positive)) } /// Return `b` if `c` is truthy, otherwise return `a`. @@ -236,15 +234,15 @@ impl Uint { let (updated_a, updated_b) = matrix.extended_apply_to((a, b)); a = updated_a - .abs() .div_2k(used_increments) .unpack() - .expect("top limb is zero"); + .expect("top limb is zero") + .abs(); b = updated_b - .abs() .div_2k(used_increments) .unpack() - .expect("top limb is zero"); + .expect("top limb is zero") + .abs(); } b From 6d9a3feaf19b9200d8fd4b0374423857f39fe59d Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 15:18:08 +0100 Subject: [PATCH 022/203] Update `restricted_extended_gcd` --- src/uint/new_gcd.rs | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index d919e5585..85359367f 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -155,20 +155,28 @@ impl Uint { hi.shl_vartime(k - 1).bitxor(&lo) } + /// Constructs a matrix `M` s.t. for `(A, B) = M(a,b)` it holds that + /// - `gcd(A, B) = gcd(a, b)`, and + /// - `A.bits() < a.bits()` and/or `B.bits() < b.bits()`. + /// + /// Moreover, it returns `log_upper_bound: u32` s.t. each element in `M` lies in the interval + /// `(-2^log_upper_bound, 2^log_upper_bound]`. + /// + /// Assumes `iterations < Uint::::BITS / 2`. #[inline] fn restricted_extended_gcd( mut a: Uint, mut b: Uint, iterations: u32, ) -> (Matrix, 2>, u32) { - debug_assert!(iterations < Uint::::BITS); + debug_assert!(iterations < Uint::::BITS / 2); // Unit matrix - let (mut f0, mut g0) = (Int::ONE, Int::ZERO); - let (mut f1, mut g1) = (Int::ZERO, Int::ONE); + let (mut f00, mut f01) = (Int::ONE, Int::ZERO); + let (mut f10, mut f11) = (Int::ZERO, Int::ONE); // Compute the update matrix. - let mut used_increments = 0; + let mut log_upper_bound = 0; let mut j = 0; while j < iterations { j += 1; @@ -179,24 +187,24 @@ impl Uint { // swap if a odd and a < b let do_swap = a_odd.and(a_lt_b); Uint::conditional_swap(&mut a, &mut b, do_swap); - Int::conditional_swap(&mut f0, &mut f1, do_swap); - Int::conditional_swap(&mut g0, &mut g1, do_swap); + Int::conditional_swap(&mut f00, &mut f10, do_swap); + Int::conditional_swap(&mut f01, &mut f11, do_swap); // subtract a from b when a is odd a = Uint::select(&a, &a.wrapping_sub(&b), a_odd); - f0 = Int::select(&f0, &f0.wrapping_sub(&f1), a_odd); - g0 = Int::select(&g0, &g0.wrapping_sub(&g1), a_odd); + f00 = Int::select(&f00, &f00.wrapping_sub(&f10), a_odd); + f01 = Int::select(&f01, &f01.wrapping_sub(&f11), a_odd); // mul/div by 2 when b is non-zero. // Only apply operations when b ≠ 0, otherwise do nothing. let do_apply = b.is_nonzero(); a = Uint::select(&a, &a.shr_vartime(1), do_apply); - f1 = Int::select(&f1, &f1.shl_vartime(1), do_apply); - g1 = Int::select(&g1, &g1.shl_vartime(1), do_apply); - used_increments = do_apply.select_u32(used_increments, used_increments + 1); + f10 = Int::select(&f10, &f10.shl_vartime(1), do_apply); + f11 = Int::select(&f11, &f11.shl_vartime(1), do_apply); + log_upper_bound = do_apply.select_u32(log_upper_bound, log_upper_bound + 1); } - (Matrix([[f0, f1], [g0, g1]]), used_increments) + (Matrix([[f00, f10], [f01, f11]]), log_upper_bound) } pub fn new_gcd(&self, rhs: &Self) -> Self { From 32b8e9f1eb348ad9df02c31d43dd8974a2e252c3 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 15:18:49 +0100 Subject: [PATCH 023/203] Annotate `new_gcd` --- src/uint/new_gcd.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index 85359367f..9065d4e01 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -207,13 +207,18 @@ impl Uint { (Matrix([[f00, f10], [f01, f11]]), log_upper_bound) } + /// Compute the greatest common divisor of `self` and `rhs`. pub fn new_gcd(&self, rhs: &Self) -> Self { + // Leverage two GCD identity rules to make self and rhs odd. + // 1) gcd(2a, 2b) = 2 * gcd(a, b) + // 2) gcd(a, 2b) = gcd(a, b) if a is odd. let i = self.trailing_zeros(); let j = rhs.trailing_zeros(); let k = const_min(i, j); Self::new_odd_gcd(&self.shr(i), &rhs.shr(j).to_odd().unwrap()).shl(k) } + /// Compute the greatest common divisor of `self` and `rhs`. pub fn new_odd_gcd(&self, rhs: &Odd) -> Self { /// Window size. const K: u32 = 63; From cf064a4568056ace057fe45cf241dfd977427020 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 15:28:03 +0100 Subject: [PATCH 024/203] Refactor `IntMatrix` --- src/uint/new_gcd.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index 9065d4e01..10c0d3c1c 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -7,7 +7,6 @@ struct ExtendedInt( ); impl ExtendedInt { - const ZERO: ExtendedInt = Self(Uint::ZERO, Uint::ZERO); /// Construct an [ExtendedInt] from the product of a [Uint] and an [Int]. @@ -76,14 +75,16 @@ impl ExtendedInt { } } -struct Matrix([[T; DIM]; DIM]); -impl Matrix, 2> { - +type Vector = (T, T); +struct IntMatrix([[Int; DIM]; DIM]); +impl IntMatrix { + /// Apply this matrix to a vector of [Uint]s, returning the result as a vector of + /// [ExtendedInt]s. #[inline] const fn extended_apply_to( &self, - vec: (Uint, Uint), - ) -> (ExtendedInt, ExtendedInt) { + vec: Vector>, + ) -> Vector> { let (a, b) = vec; let a00 = ExtendedInt::from_product(a, self.0[0][0]); let a01 = ExtendedInt::from_product(a, self.0[0][1]); @@ -158,7 +159,7 @@ impl Uint { /// Constructs a matrix `M` s.t. for `(A, B) = M(a,b)` it holds that /// - `gcd(A, B) = gcd(a, b)`, and /// - `A.bits() < a.bits()` and/or `B.bits() < b.bits()`. - /// + /// /// Moreover, it returns `log_upper_bound: u32` s.t. each element in `M` lies in the interval /// `(-2^log_upper_bound, 2^log_upper_bound]`. /// @@ -168,7 +169,7 @@ impl Uint { mut a: Uint, mut b: Uint, iterations: u32, - ) -> (Matrix, 2>, u32) { + ) -> (IntMatrix, u32) { debug_assert!(iterations < Uint::::BITS / 2); // Unit matrix @@ -204,7 +205,7 @@ impl Uint { log_upper_bound = do_apply.select_u32(log_upper_bound, log_upper_bound + 1); } - (Matrix([[f00, f10], [f01, f11]]), log_upper_bound) + (IntMatrix([[f00, f10], [f01, f11]]), log_upper_bound) } /// Compute the greatest common divisor of `self` and `rhs`. From e4f43594d93b9f89e52570b689bd04153c398afc Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 16:15:23 +0100 Subject: [PATCH 025/203] Refactor `ExtendedInt` into `ExtendedInt` and `ExtendeUint` --- src/uint/new_gcd.rs | 85 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 64 insertions(+), 21 deletions(-) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index 10c0d3c1c..b30185896 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -1,20 +1,22 @@ use crate::{ConstChoice, ConstCtOption, Int, Limb, Odd, Uint, I64, U128}; use num_traits::WrappingSub; -struct ExtendedInt( +struct ExtendedUint( Uint, Uint, ); -impl ExtendedInt { - const ZERO: ExtendedInt = Self(Uint::ZERO, Uint::ZERO); +impl ExtendedUint { + const ZERO: ExtendedUint = Self(Uint::ZERO, Uint::ZERO); - /// Construct an [ExtendedInt] from the product of a [Uint] and an [Int]. - pub const fn from_product(lhs: Uint, rhs: Int) -> Self { - let (lo, hi, sgn) = rhs.split_mul_uint_right(&lhs); - ExtendedInt(lo, hi).wrapping_neg_if(sgn) + /// Interpret `self` as an [ExtendedInt] + pub const fn as_extended_int(&self) -> ExtendedInt { + ExtendedInt(self.0, self.1) } + /// Construction the binary negation of `self`, i.e., map `self` to `!self + 1`. + /// + /// Note: maps `0` to itself. pub const fn wrapping_neg(&self) -> Self { let (lhs, carry) = self.0.carrying_neg(); let mut rhs = self.1.not(); @@ -22,6 +24,7 @@ impl ExtendedInt { Self(lhs, rhs) } + /// Negate `self` if `negate` is truthy. Otherwise returns `self`. pub const fn wrapping_neg_if(&self, negate: ConstChoice) -> Self { let neg = self.wrapping_neg(); Self( @@ -30,19 +33,16 @@ impl ExtendedInt { ) } - pub const fn wrapping_add(&self, rhs: &Self) -> Self { - let (lo, carry) = self.0.adc(&rhs.0, Limb::ZERO); - let (hi, _) = self.1.adc(&rhs.1, carry); - Self(lo, hi) - } - + /// Shift `self` right by `shift` bits. + /// + /// Assumes `shift <= Uint::::BITS`. pub const fn shr(&self, shift: u32) -> Self { debug_assert!(shift <= Uint::::BITS); let shift_is_zero = ConstChoice::from_u32_eq(shift, 0); let left_shift = shift_is_zero.select_u32(Uint::::BITS - shift, 0); - let hi = *self.1.as_int().shr(shift).as_uint(); + let hi = self.1.shr(shift); // TODO: replace with carrying_shl let carry = Uint::select(&self.1, &Uint::ZERO, shift_is_zero).shl(left_shift); let mut lo = self.0.shr(shift); @@ -54,8 +54,46 @@ impl ExtendedInt { Self(lo, hi) } +} - pub const fn unpack(&self) -> ConstCtOption> { +struct ExtendedInt( + Uint, + Uint, +); + +impl ExtendedInt { + const ZERO: ExtendedInt = Self(Uint::ZERO, Uint::ZERO); + + /// Construct an [ExtendedInt] from the product of a [Uint] and an [Int]. + /// + /// Assumes the top bit of the product is not set. + pub const fn from_product(lhs: Uint, rhs: Int) -> Self { + let (lo, hi, sgn) = rhs.split_mul_uint_right(&lhs); + ExtendedUint(lo, hi).wrapping_neg_if(sgn).as_extended_int() + } + + /// Interpret this as an [ExtendedUint]. + pub const fn as_extended_uint(&self) -> ExtendedUint { + ExtendedUint(self.0, self.1) + } + + /// Return the negation of `self` if `negate` is truthy. Otherwise, return `self`. + pub const fn wrapping_neg_if(&self, negate: ConstChoice) -> Self { + self.as_extended_uint().wrapping_neg_if(negate).as_extended_int() + } + + /// Compute `self + rhs`, wrapping any overflow. + pub const fn wrapping_add(&self, rhs: &Self) -> Self { + let (lo, carry) = self.0.adc(&rhs.0, Limb::ZERO); + let (hi, _) = self.1.adc(&rhs.1, carry); + Self(lo, hi) + } + + /// Returns self without the extension. + /// + /// Is `None` if the extension cannot be dropped, i.e., when there is a bit in the extension + /// that does not equal the MSB in the base. + pub const fn drop_extension(&self) -> ConstCtOption> { let lo_is_negative = self.0.as_int().is_negative(); let proper_positive = Int::eq(&self.1.as_int(), &Int::ZERO).and(lo_is_negative.not()); let proper_negative = Int::eq(&self.1.as_int(), &Int::MINUS_ONE).and(lo_is_negative); @@ -67,11 +105,16 @@ impl ExtendedInt { Self(Uint::select(&a.0, &b.0, c), Uint::select(&a.1, &b.1, c)) } - /// Divide self by `2^k`. + /// Decompose `self` into is absolute value and signum. + pub const fn abs_sgn(&self) -> (ExtendedUint, ConstChoice) { + let is_negative = self.1.as_int().is_negative(); + (self.wrapping_neg_if(is_negative).as_extended_uint(), is_negative) + } + + /// Divide self by `2^k`, rounding towards zero. pub const fn div_2k(&self, k: u32) -> Self { - let lo_is_minus_one = Int::eq(&self.0.as_int(), &Int::MINUS_ONE); - let ext_is_minus_one = Int::eq(&self.1.as_int(), &Int::MINUS_ONE); - Self::select(&self.shr(k), &Self::ZERO, lo_is_minus_one.and(ext_is_minus_one)) + let (abs, sgn) = self.abs_sgn(); + abs.shr(k).wrapping_neg_if(sgn).as_extended_int() } } @@ -249,12 +292,12 @@ impl Uint { a = updated_a .div_2k(used_increments) - .unpack() + .drop_extension() .expect("top limb is zero") .abs(); b = updated_b .div_2k(used_increments) - .unpack() + .drop_extension() .expect("top limb is zero") .abs(); } From 4b84597cc48972a3815243bbc9dbd74968128fbc Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 16:16:35 +0100 Subject: [PATCH 026/203] Fix bug --- src/uint/new_gcd.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index b30185896..5a666705e 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -7,7 +7,6 @@ struct ExtendedUint( ); impl ExtendedUint { - const ZERO: ExtendedUint = Self(Uint::ZERO, Uint::ZERO); /// Interpret `self` as an [ExtendedInt] pub const fn as_extended_int(&self) -> ExtendedInt { @@ -62,7 +61,6 @@ struct ExtendedInt( ); impl ExtendedInt { - const ZERO: ExtendedInt = Self(Uint::ZERO, Uint::ZERO); /// Construct an [ExtendedInt] from the product of a [Uint] and an [Int]. /// @@ -100,11 +98,6 @@ impl ExtendedInt { ConstCtOption::new(self.0.as_int(), proper_negative.or(proper_positive)) } - /// Return `b` if `c` is truthy, otherwise return `a`. - pub const fn select(a: &Self, b: &Self, c: ConstChoice) -> Self { - Self(Uint::select(&a.0, &b.0, c), Uint::select(&a.1, &b.1, c)) - } - /// Decompose `self` into is absolute value and signum. pub const fn abs_sgn(&self) -> (ExtendedUint, ConstChoice) { let is_negative = self.1.as_int().is_negative(); @@ -206,14 +199,14 @@ impl Uint { /// Moreover, it returns `log_upper_bound: u32` s.t. each element in `M` lies in the interval /// `(-2^log_upper_bound, 2^log_upper_bound]`. /// - /// Assumes `iterations < Uint::::BITS / 2`. + /// Assumes `iterations < Uint::::BITS`. #[inline] fn restricted_extended_gcd( mut a: Uint, mut b: Uint, iterations: u32, ) -> (IntMatrix, u32) { - debug_assert!(iterations < Uint::::BITS / 2); + debug_assert!(iterations < Uint::::BITS); // Unit matrix let (mut f00, mut f01) = (Int::ONE, Int::ZERO); From e822db18f6417194647dccff730e4f4ec14a67b8 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 16:19:34 +0100 Subject: [PATCH 027/203] Inline `ExtendedUint` and `ExtendedInt` --- src/uint/new_gcd.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index 5a666705e..9fde0f2c0 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -7,8 +7,8 @@ struct ExtendedUint( ); impl ExtendedUint { - /// Interpret `self` as an [ExtendedInt] + #[inline] pub const fn as_extended_int(&self) -> ExtendedInt { ExtendedInt(self.0, self.1) } @@ -16,6 +16,7 @@ impl ExtendedUint { /// Construction the binary negation of `self`, i.e., map `self` to `!self + 1`. /// /// Note: maps `0` to itself. + #[inline] pub const fn wrapping_neg(&self) -> Self { let (lhs, carry) = self.0.carrying_neg(); let mut rhs = self.1.not(); @@ -24,6 +25,7 @@ impl ExtendedUint { } /// Negate `self` if `negate` is truthy. Otherwise returns `self`. + #[inline] pub const fn wrapping_neg_if(&self, negate: ConstChoice) -> Self { let neg = self.wrapping_neg(); Self( @@ -35,6 +37,7 @@ impl ExtendedUint { /// Shift `self` right by `shift` bits. /// /// Assumes `shift <= Uint::::BITS`. + #[inline] pub const fn shr(&self, shift: u32) -> Self { debug_assert!(shift <= Uint::::BITS); @@ -61,26 +64,31 @@ struct ExtendedInt( ); impl ExtendedInt { - /// Construct an [ExtendedInt] from the product of a [Uint] and an [Int]. /// /// Assumes the top bit of the product is not set. + #[inline] pub const fn from_product(lhs: Uint, rhs: Int) -> Self { let (lo, hi, sgn) = rhs.split_mul_uint_right(&lhs); ExtendedUint(lo, hi).wrapping_neg_if(sgn).as_extended_int() } /// Interpret this as an [ExtendedUint]. + #[inline] pub const fn as_extended_uint(&self) -> ExtendedUint { ExtendedUint(self.0, self.1) } /// Return the negation of `self` if `negate` is truthy. Otherwise, return `self`. + #[inline] pub const fn wrapping_neg_if(&self, negate: ConstChoice) -> Self { - self.as_extended_uint().wrapping_neg_if(negate).as_extended_int() + self.as_extended_uint() + .wrapping_neg_if(negate) + .as_extended_int() } /// Compute `self + rhs`, wrapping any overflow. + #[inline] pub const fn wrapping_add(&self, rhs: &Self) -> Self { let (lo, carry) = self.0.adc(&rhs.0, Limb::ZERO); let (hi, _) = self.1.adc(&rhs.1, carry); @@ -91,6 +99,7 @@ impl ExtendedInt { /// /// Is `None` if the extension cannot be dropped, i.e., when there is a bit in the extension /// that does not equal the MSB in the base. + #[inline] pub const fn drop_extension(&self) -> ConstCtOption> { let lo_is_negative = self.0.as_int().is_negative(); let proper_positive = Int::eq(&self.1.as_int(), &Int::ZERO).and(lo_is_negative.not()); @@ -99,12 +108,17 @@ impl ExtendedInt { } /// Decompose `self` into is absolute value and signum. + #[inline] pub const fn abs_sgn(&self) -> (ExtendedUint, ConstChoice) { let is_negative = self.1.as_int().is_negative(); - (self.wrapping_neg_if(is_negative).as_extended_uint(), is_negative) + ( + self.wrapping_neg_if(is_negative).as_extended_uint(), + is_negative, + ) } /// Divide self by `2^k`, rounding towards zero. + #[inline] pub const fn div_2k(&self, k: u32) -> Self { let (abs, sgn) = self.abs_sgn(); abs.shr(k).wrapping_neg_if(sgn).as_extended_int() From a1ff0a87c759fd975b6d90cb4927ffb3e4f8ef77 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 16:36:58 +0100 Subject: [PATCH 028/203] Expand `Uint::gcd` benchmarking --- benches/uint.rs | 77 +++++++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 44 deletions(-) diff --git a/benches/uint.rs b/benches/uint.rs index 35e304804..bf47760fe 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -1,10 +1,9 @@ -use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; -use crypto_bigint::{ - Limb, NonZero, Odd, Random, RandomBits, RandomMod, Reciprocal, Uint, U1024, U128, U2048, U256, - U4096, U512, -}; +use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion, BenchmarkGroup, BenchmarkId}; +use criterion::measurement::WallTime; +use crypto_bigint::{Limb, NonZero, Odd, Random, RandomBits, RandomMod, Reciprocal, Uint, U1024, U128, U2048, U256, U4096, U512, Gcd, U16384, U8192, PrecomputeInverter}; use rand_chacha::ChaCha8Rng; use rand_core::{OsRng, RngCore, SeedableRng}; +use crypto_bigint::modular::SafeGcdInverter; fn make_rng() -> ChaCha8Rng { ChaCha8Rng::from_seed(*b"01234567890123456789012345678901") @@ -302,68 +301,58 @@ fn bench_division(c: &mut Criterion) { group.finish(); } -fn bench_gcd(c: &mut Criterion) { - let mut group = c.benchmark_group("greatest common divisor"); - - // group.bench_function("gcd, U2048", |b| { - // b.iter_batched( - // || { - // let f = U2048::random(&mut OsRng); - // let g = U2048::random(&mut OsRng); - // (f, g) - // }, - // |(f, g)| black_box(f.gcd(&g)), - // BatchSize::SmallInput, - // ) - // }); - // - // group.bench_function("gcd, U1024", |b| { - // b.iter_batched( - // || { - // let f = U1024::random(&mut OsRng); - // let g = U1024::random(&mut OsRng); - // (f, g) - // }, - // |(f, g)| black_box(f.gcd(&g)), - // BatchSize::SmallInput, - // ) - // }); - - group.bench_function("test_gcd, U2048", |b| { +fn gcd_bench(g: &mut BenchmarkGroup, x: Uint) +where + Odd>: PrecomputeInverter> +{ + g.bench_function(BenchmarkId::new("gcd (vt)", LIMBS), |b| { b.iter_batched( || { - let f = U2048::random(&mut OsRng); - let g = U2048::random(&mut OsRng); + let f = Uint::::random(&mut OsRng); + let g = Uint::::random(&mut OsRng); (f, g) }, - |(f, g)| black_box(Uint::new_gcd(&f, &g)), + |(f, g)| black_box(Uint::gcd_vartime(&f, &g)), BatchSize::SmallInput, ) }); - group.bench_function("test_gcd, U1024", |b| { + g.bench_function(BenchmarkId::new("gcd (ct)", LIMBS), |b| { b.iter_batched( || { - let f = U1024::random(&mut OsRng); - let g = U1024::random(&mut OsRng); + let f = Uint::::random(&mut OsRng); + let g = Uint::::random(&mut OsRng); (f, g) }, - |(f, g)| black_box(Uint::new_gcd(&f, &g)), + |(f, g)| black_box(Uint::gcd(&f, &g)), BatchSize::SmallInput, ) }); - group.bench_function("gcd_vartime, U256", |b| { + g.bench_function(BenchmarkId::new("new_gcd (ct)", LIMBS), |b| { b.iter_batched( || { - let f = Odd::::random(&mut OsRng); - let g = U256::random(&mut OsRng); + let f = Uint::::random(&mut OsRng); + let g = Uint::::random(&mut OsRng); (f, g) }, - |(f, g)| black_box(f.gcd_vartime(&g)), + |(f, g)| black_box(Uint::new_gcd(&f, &g)), BatchSize::SmallInput, ) }); +} + +fn bench_gcd(c: &mut Criterion) { + let mut group = c.benchmark_group("greatest common divisor"); + + gcd_bench(&mut group, U128::ZERO); + gcd_bench(&mut group, U256::ZERO); + gcd_bench(&mut group, U512::ZERO); + gcd_bench(&mut group, U1024::ZERO); + gcd_bench(&mut group, U2048::ZERO); + gcd_bench(&mut group, U4096::ZERO); + gcd_bench(&mut group, U8192::ZERO); + gcd_bench(&mut group, U16384::ZERO); group.finish(); } From 46211cf073e8fa8bec8a25a9466674250f0c3b93 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 17:01:04 +0100 Subject: [PATCH 029/203] Expand `Uint::new_gcd` testing --- benches/uint.rs | 19 +++++++++++++------ src/uint/new_gcd.rs | 31 ++++++++++++++++++++----------- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/benches/uint.rs b/benches/uint.rs index bf47760fe..e1a74436b 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -1,9 +1,14 @@ -use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion, BenchmarkGroup, BenchmarkId}; use criterion::measurement::WallTime; -use crypto_bigint::{Limb, NonZero, Odd, Random, RandomBits, RandomMod, Reciprocal, Uint, U1024, U128, U2048, U256, U4096, U512, Gcd, U16384, U8192, PrecomputeInverter}; +use criterion::{ + black_box, criterion_group, criterion_main, BatchSize, BenchmarkGroup, BenchmarkId, Criterion, +}; +use crypto_bigint::modular::SafeGcdInverter; +use crypto_bigint::{ + Gcd, Limb, NonZero, Odd, PrecomputeInverter, Random, RandomBits, RandomMod, Reciprocal, Uint, + U1024, U128, U16384, U2048, U256, U4096, U512, U8192, +}; use rand_chacha::ChaCha8Rng; use rand_core::{OsRng, RngCore, SeedableRng}; -use crypto_bigint::modular::SafeGcdInverter; fn make_rng() -> ChaCha8Rng { ChaCha8Rng::from_seed(*b"01234567890123456789012345678901") @@ -301,9 +306,11 @@ fn bench_division(c: &mut Criterion) { group.finish(); } -fn gcd_bench(g: &mut BenchmarkGroup, x: Uint) -where - Odd>: PrecomputeInverter> +fn gcd_bench( + g: &mut BenchmarkGroup, + _x: Uint, +) where + Odd>: PrecomputeInverter>, { g.bench_function(BenchmarkId::new("gcd (vt)", LIMBS), |b| { b.iter_batched( diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index 9fde0f2c0..0afc6c9ff 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -315,31 +315,40 @@ impl Uint { #[cfg(test)] mod tests { - use crate::{ConcatMixed, Gcd, Random, Split, Uint, U2048}; + use crate::{Gcd, Random, Uint, U1024, U16384, U2048, U256, U4096, U512, U8192}; use rand_core::OsRng; - fn gcd_comparison_test( - lhs: Uint, - rhs: Uint, - ) where + fn gcd_comparison_test(lhs: Uint, rhs: Uint) + where Uint: Gcd>, - Uint: ConcatMixed, MixedOutput = Uint>, - Uint: Split, { let gcd = lhs.gcd(&rhs); let bingcd = lhs.new_odd_gcd(&rhs.to_odd().unwrap()); assert_eq!(gcd, bingcd); } - #[test] - fn test_new_gcd() { + fn test_new_gcd() + where + Uint: Gcd>, + { for _ in 0..500 { - let x = U2048::random(&mut OsRng); - let mut y = U2048::random(&mut OsRng); + let x = Uint::::random(&mut OsRng); + let mut y = Uint::::random(&mut OsRng); y = Uint::select(&(y.wrapping_add(&Uint::ONE)), &y, y.is_odd()); gcd_comparison_test(x, y); } } + + #[test] + fn testing() { + test_new_gcd::<{ U256::LIMBS }>(); + test_new_gcd::<{ U512::LIMBS }>(); + test_new_gcd::<{ U1024::LIMBS }>(); + test_new_gcd::<{ U2048::LIMBS }>(); + test_new_gcd::<{ U4096::LIMBS }>(); + test_new_gcd::<{ U8192::LIMBS }>(); + test_new_gcd::<{ U16384::LIMBS }>(); + } } From a824a4d7ab8176dedac4c116ddb187a7bb12f94b Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 29 Jan 2025 17:01:22 +0100 Subject: [PATCH 030/203] Annotate `new_gcd.rs` --- src/uint/new_gcd.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index 0afc6c9ff..dcc8ea0fc 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -1,3 +1,7 @@ +//! This module implements (a constant variant of) the Optimized Extended Binary GCD algorithm, +//! which is described by Pornin as Algorithm 2 in "Optimized Binary GCD for Modular Inversion". +//! Ref: https://eprint.iacr.org/2020/972.pdf + use crate::{ConstChoice, ConstCtOption, Int, Limb, Odd, Uint, I64, U128}; use num_traits::WrappingSub; From 6e76ec9a250f61447dd6d21bed40e5bc2dbf4f6c Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 30 Jan 2025 14:25:35 +0100 Subject: [PATCH 031/203] Prevent matrix overflow; cap `k` at 62 --- src/uint/new_gcd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index dcc8ea0fc..f520b739d 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -276,7 +276,7 @@ impl Uint { /// Compute the greatest common divisor of `self` and `rhs`. pub fn new_odd_gcd(&self, rhs: &Odd) -> Self { /// Window size. - const K: u32 = 63; + const K: u32 = 62; /// Smallest [Int] that fits a K-bit [Uint]. type SingleK = I64; /// Smallest [Uint] that fits 2K bits. From 64f0821b2770dee1930bcf233860c3528ab4f2b5 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 30 Jan 2025 14:39:15 +0100 Subject: [PATCH 032/203] Make `Int::wrapping_sub` a const function --- src/int/resize.rs | 1 - src/int/sub.rs | 34 ++++++++++++++++++++++++---------- src/uint/new_gcd.rs | 1 - 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/int/resize.rs b/src/int/resize.rs index 4aed28991..1dc9cb4d8 100644 --- a/src/int/resize.rs +++ b/src/int/resize.rs @@ -18,7 +18,6 @@ impl Int { #[cfg(test)] mod tests { - use num_traits::WrappingSub; use crate::{I128, I256}; diff --git a/src/int/sub.rs b/src/int/sub.rs index bb94f6429..cd53006b1 100644 --- a/src/int/sub.rs +++ b/src/int/sub.rs @@ -3,12 +3,14 @@ use core::ops::{Sub, SubAssign}; use num_traits::WrappingSub; -use subtle::{Choice, ConstantTimeEq, CtOption}; +use subtle::CtOption; -use crate::{Checked, CheckedSub, Int, Wrapping}; +use crate::{Checked, CheckedSub, ConstChoice, ConstCtOption, Int, Wrapping}; -impl CheckedSub for Int { - fn checked_sub(&self, rhs: &Self) -> CtOption { +impl Int { + /// Perform subtraction, returning the result along with a [ConstChoice] which `is_true` + /// only if the operation underflowed. + pub const fn underflowing_sub(&self, rhs: &Self) -> (Self, ConstChoice) { // Step 1. subtract operands let res = Self(self.0.wrapping_sub(&rhs.0)); @@ -18,12 +20,26 @@ impl CheckedSub for Int { // - underflow occurs if and only if the result and the lhs have opposing signs. // // We can thus express the overflow flag as: (self.msb != rhs.msb) & (self.msb != res.msb) - let self_msb: Choice = self.is_negative().into(); - let underflow = - self_msb.ct_ne(&rhs.is_negative().into()) & self_msb.ct_ne(&res.is_negative().into()); + let self_msb = self.is_negative(); + let underflow = self_msb + .ne(rhs.is_negative()) + .and(self_msb.ne(res.is_negative())); // Step 3. Construct result - CtOption::new(res, !underflow) + (res, underflow) + } + + /// Perform wrapping subtraction, discarding underflow and wrapping around the boundary of the + /// type. + pub const fn wrapping_sub(&self, rhs: &Self) -> Self { + self.underflowing_sub(&rhs).0 + } +} + +impl CheckedSub for Int { + fn checked_sub(&self, rhs: &Self) -> CtOption { + let (res, underflow) = Self::underflowing_sub(&self, &rhs); + ConstCtOption::new(res, underflow.not()).into() } } @@ -79,8 +95,6 @@ mod tests { #[cfg(test)] mod tests { - use num_traits::WrappingSub; - use crate::{CheckedSub, Int, I128, U128}; #[test] diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index f520b739d..2d7c65ea1 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -3,7 +3,6 @@ //! Ref: https://eprint.iacr.org/2020/972.pdf use crate::{ConstChoice, ConstCtOption, Int, Limb, Odd, Uint, I64, U128}; -use num_traits::WrappingSub; struct ExtendedUint( Uint, From 625cc0c4f41cf111d82ee7235ba856a1c4f44208 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 30 Jan 2025 15:16:39 +0100 Subject: [PATCH 033/203] Have new_gcd deal with extreme cases. --- src/uint/new_gcd.rs | 83 +++++++++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index 2d7c65ea1..21bff28b9 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -2,7 +2,7 @@ //! which is described by Pornin as Algorithm 2 in "Optimized Binary GCD for Modular Inversion". //! Ref: https://eprint.iacr.org/2020/972.pdf -use crate::{ConstChoice, ConstCtOption, Int, Limb, Odd, Uint, I64, U128}; +use crate::{ConstChoice, ConstCtOption, Int, Limb, NonZero, Odd, Uint, I64, U128}; struct ExtendedUint( Uint, @@ -103,11 +103,14 @@ impl ExtendedInt { /// Is `None` if the extension cannot be dropped, i.e., when there is a bit in the extension /// that does not equal the MSB in the base. #[inline] - pub const fn drop_extension(&self) -> ConstCtOption> { - let lo_is_negative = self.0.as_int().is_negative(); - let proper_positive = Int::eq(&self.1.as_int(), &Int::ZERO).and(lo_is_negative.not()); - let proper_negative = Int::eq(&self.1.as_int(), &Int::MINUS_ONE).and(lo_is_negative); - ConstCtOption::new(self.0.as_int(), proper_negative.or(proper_positive)) + pub const fn abs_drop_extension(&self) -> ConstCtOption> { + // should succeed when + // - extension is ZERO, or + // - extension is MAX, and the top bit in base is set. + let proper_positive = Int::eq(&self.1.as_int(), &Int::ZERO); + let proper_negative = + Int::eq(&self.1.as_int(), &Int::MINUS_ONE).and(self.0.as_int().is_negative()); + ConstCtOption::new(self.abs().0, proper_negative.or(proper_positive)) } /// Decompose `self` into is absolute value and signum. @@ -120,6 +123,12 @@ impl ExtendedInt { ) } + /// Decompose `self` into is absolute value and signum. + #[inline] + pub const fn abs(&self) -> ExtendedUint { + self.abs_sgn().0 + } + /// Divide self by `2^k`, rounding towards zero. #[inline] pub const fn div_2k(&self, k: u32) -> Self { @@ -218,7 +227,7 @@ impl Uint { /// /// Assumes `iterations < Uint::::BITS`. #[inline] - fn restricted_extended_gcd( + const fn restricted_extended_gcd( mut a: Uint, mut b: Uint, iterations: u32, @@ -262,18 +271,41 @@ impl Uint { } /// Compute the greatest common divisor of `self` and `rhs`. - pub fn new_gcd(&self, rhs: &Self) -> Self { + pub const fn new_gcd(&self, rhs: &Self) -> Self { + // Account for the case where rhs is zero + let rhs_is_zero = rhs.is_nonzero().not(); + let rhs_ = Uint::select(rhs, &Uint::ONE, rhs_is_zero) + .to_nz() + .expect("rhs is non zero by construction"); + let result = self.new_gcd_nonzero(&rhs_); + Uint::select(&result, self, rhs_is_zero) + } + + /// Compute the greatest common divisor of `self` and `rhs`, where `rhs` is known to be nonzero. + const fn new_gcd_nonzero(&self, rhs: &NonZero) -> Self { // Leverage two GCD identity rules to make self and rhs odd. // 1) gcd(2a, 2b) = 2 * gcd(a, b) // 2) gcd(a, 2b) = gcd(a, b) if a is odd. - let i = self.trailing_zeros(); - let j = rhs.trailing_zeros(); + let i = self.is_nonzero().select_u32(0, self.trailing_zeros()); + let j = rhs + .as_ref() + .is_nonzero() + .select_u32(0, rhs.as_ref().trailing_zeros()); let k = const_min(i, j); - Self::new_odd_gcd(&self.shr(i), &rhs.shr(j).to_odd().unwrap()).shl(k) + + Self::new_odd_gcd( + &self.shr(i), + &rhs.as_ref() + .shr(j) + .to_odd() + .expect("rhs is odd by construction"), + ) + .shl(k) } - /// Compute the greatest common divisor of `self` and `rhs`. - pub fn new_odd_gcd(&self, rhs: &Odd) -> Self { + /// Compute the greatest common divisor of `self` and `rhs`, where `rhs` is known to be odd. + #[inline(always)] + const fn new_odd_gcd(&self, rhs: &Odd) -> Self { /// Window size. const K: u32 = 62; /// Smallest [Int] that fits a K-bit [Uint]. @@ -282,7 +314,7 @@ impl Uint { type DoubleK = U128; debug_assert!(DoubleK::BITS >= 2 * K); - let (mut a, mut b) = (*self, rhs.get()); + let (mut a, mut b) = (*self, *rhs.as_ref()); let mut i = 0; while i < (2 * Self::BITS - 1).div_ceil(K) { @@ -302,14 +334,12 @@ impl Uint { a = updated_a .div_2k(used_increments) - .drop_extension() - .expect("top limb is zero") - .abs(); + .abs_drop_extension() + .expect("extension is zero"); b = updated_b .div_2k(used_increments) - .drop_extension() - .expect("top limb is zero") - .abs(); + .abs_drop_extension() + .expect("extension is zero"); } b @@ -326,7 +356,7 @@ mod tests { Uint: Gcd>, { let gcd = lhs.gcd(&rhs); - let bingcd = lhs.new_odd_gcd(&rhs.to_odd().unwrap()); + let bingcd = lhs.new_gcd(&rhs); assert_eq!(gcd, bingcd); } @@ -334,6 +364,17 @@ mod tests { where Uint: Gcd>, { + // some basic test + gcd_comparison_test(Uint::ZERO, Uint::ZERO); + gcd_comparison_test(Uint::ZERO, Uint::ONE); + gcd_comparison_test(Uint::ZERO, Uint::MAX); + gcd_comparison_test(Uint::ONE, Uint::ZERO); + gcd_comparison_test(Uint::ONE, Uint::ONE); + gcd_comparison_test(Uint::ONE, Uint::MAX); + gcd_comparison_test(Uint::MAX, Uint::ZERO); + gcd_comparison_test(Uint::MAX, Uint::ONE); + gcd_comparison_test(Uint::MAX, Uint::MAX); + for _ in 0..500 { let x = Uint::::random(&mut OsRng); let mut y = Uint::::random(&mut OsRng); From 28284c0b240647442d7f9297fd591bddaf4b41fc Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 30 Jan 2025 15:21:40 +0100 Subject: [PATCH 034/203] Deprecate `new_inv_mod_odd` --- src/uint/inv_mod.rs | 116 +------------------------------------------- 1 file changed, 2 insertions(+), 114 deletions(-) diff --git a/src/uint/inv_mod.rs b/src/uint/inv_mod.rs index bec1b9f00..36a17e111 100644 --- a/src/uint/inv_mod.rs +++ b/src/uint/inv_mod.rs @@ -1,10 +1,8 @@ use super::Uint; use crate::{ - modular::SafeGcdInverter, CheckedMul, ConstChoice, ConstCtOption, ConstantTimeSelect, InvMod, - Limb, Odd, PrecomputeInverter, U64, + modular::SafeGcdInverter, ConstChoice, ConstCtOption, InvMod, Odd, PrecomputeInverter, }; -use core::cmp::max; -use subtle::{Choice, CtOption}; +use subtle::CtOption; impl Uint { /// Computes 1/`self` mod `2^k`. @@ -94,116 +92,6 @@ where SafeGcdInverter::::new(modulus, &Uint::ONE).inv(self) } - // TODO: assumes `self` < `modulus` - pub fn new_inv_mod_odd(&self, modulus: &Odd) -> ConstCtOption { - const K: u32 = 32; - // Smallest Uint that fits K bits - type Word = U64; - // Smallest Uint that fits 2K bits. - type WideWord = U64; - debug_assert!(WideWord::BITS >= 2 * K); - let k_sub_one_bitmask = Uint::ONE.shl_vartime(K - 1).wrapping_sub(&Uint::ONE); - - let (mut a, mut b) = (*self, modulus.get()); - let (mut u, mut v) = (Self::ONE, Self::ZERO); - - let mut i = 0; - while i < (2 * modulus.bits_vartime() - 1).div_ceil(K) { - i += 1; - - // Construct a_ and b_ as the concatenation of the K most significant and the K least - // significant bits of a and b, respectively. If those bits overlap, ... TODO - // TODO: is max const time? - let n = max(max(a.bits(), b.bits()), 2 * K); - - let hi_a = a.shr(n - K - 1); - let lo_a = a.bitand(&k_sub_one_bitmask); - let mut a_ = WideWord::from(&hi_a) - .shl_vartime(K - 1) - .bitxor(&WideWord::from(&lo_a)); - - let hi_b = WideWord::from(&b.shr(n - K - 1)); - let lo_b = WideWord::from(&b.bitand(&k_sub_one_bitmask)); - let mut b_: WideWord = hi_b.shl_vartime(K - 1).bitxor(&lo_b); - - // Unit matrix - let (mut f0, mut g0) = (Word::ONE, Word::ZERO); - let (mut f1, mut g1) = (Word::ZERO, Word::ONE); - - // Compute the update matrix. - let mut j = 0; - while j < K - 1 { - j += 1; - - let a_odd = a_.is_odd(); - let a_lt_b = Uint::lt(&a_, &b_); - - // TODO: make this const - // swap if a odd and a < b - let do_swap: Choice = a_odd.and(a_lt_b).into(); - Uint::ct_swap(&mut a_, &mut b_, do_swap); - Uint::ct_swap(&mut f0, &mut f1, do_swap); - Uint::ct_swap(&mut g0, &mut g1, do_swap); - - // subtract b from a - // TODO: perhaps change something about `a_odd` to make this xgcd? - a_ = Uint::select(&a_, &a_.wrapping_sub(&b_), a_odd); - f0 = U64::select(&f0, &f0.wrapping_sub(&f1), a_odd); - g0 = U64::select(&g0, &g0.wrapping_sub(&g1), a_odd); - - // div a by 2 - a_ = a_.shr_vartime(1); - // mul f1 and g1 by 1 - f1 = f1.shl_vartime(1); - g1 = g1.shl_vartime(1); - } - - (a, b) = { - // a := af0 + bg0 - let (lo_a0, hi_a0) = a.split_mul(&f0); - let (lo_a1, hi_a1) = b.split_mul(&g0); - let (lo_a, carry) = lo_a0.adc(&lo_a1, Limb::ZERO); - let (_, carry) = hi_a0.adc(&hi_a1, carry); - let overflow_a: ConstChoice = Limb::eq(carry, Limb::ZERO).not(); - - // b := af1 + bg1 - let (lo_b0, hi_b0) = a.split_mul(&f1); - let (lo_b1, hi_b1) = b.split_mul(&g1); - let (lo_b, carry) = lo_b0.adc(&lo_b1, Limb::ZERO); - let (_, carry) = hi_b0.adc(&hi_b1, carry); - let overflow_b: ConstChoice = Limb::eq(carry, Limb::ZERO).not(); - - ( - lo_a.wrapping_neg_if(overflow_a).shr_vartime(K - 1), - lo_b.wrapping_neg_if(overflow_b).shr_vartime(K - 1), - ) - }; - - let a_is_neg = a.as_int().is_negative(); - a = a.wrapping_neg_if(a_is_neg); - f0 = f0.wrapping_neg_if(a_is_neg); - g0 = g0.wrapping_neg_if(a_is_neg); - - let b_is_neg = b.as_int().is_negative(); - b = b.wrapping_neg_if(b_is_neg); - f1 = f1.wrapping_neg_if(b_is_neg); - g1 = g1.wrapping_neg_if(b_is_neg); - - // TODO: fix checked_mul.unwrap failing - // TODO: assumes uf0 + vg0 < 2*modulus... :thinking: - (u, v) = ( - u.checked_mul(&f0) - .unwrap() - .add_mod(&v.checked_mul(&g0).unwrap(), modulus), - u.checked_mul(&f1) - .unwrap() - .add_mod(&v.checked_mul(&g1).unwrap(), modulus), - ); - } - - ConstCtOption::new(v, Uint::eq(&b, &Uint::ONE)) - } - /// Computes the multiplicative inverse of `self` mod `modulus`. /// /// Returns some if an inverse exists, otherwise none. From 0e93f8dfd9456a9265a2d422591f6ce5a134161d Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 30 Jan 2025 15:23:05 +0100 Subject: [PATCH 035/203] Deprecate `Limb::eq` --- src/limb/cmp.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/limb/cmp.rs b/src/limb/cmp.rs index 3e044ddbc..194bc866d 100644 --- a/src/limb/cmp.rs +++ b/src/limb/cmp.rs @@ -37,12 +37,6 @@ impl Limb { pub(crate) const fn is_nonzero(&self) -> ConstChoice { ConstChoice::from_word_nonzero(self.0) } - - /// Returns the truthy value if `self == rhs` or the falsy value otherwise. - #[inline] - pub(crate) const fn eq(lhs: Self, rhs: Self) -> ConstChoice { - Limb(lhs.0 ^ rhs.0).is_nonzero().not() - } } impl ConstantTimeEq for Limb { From a8deb20eb047fc0614ddb1eeea0fb46934656f38 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 30 Jan 2025 15:24:01 +0100 Subject: [PATCH 036/203] Fix clippy --- src/int/sub.rs | 4 ++-- src/uint/new_gcd.rs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/int/sub.rs b/src/int/sub.rs index cd53006b1..b392dc0f0 100644 --- a/src/int/sub.rs +++ b/src/int/sub.rs @@ -32,13 +32,13 @@ impl Int { /// Perform wrapping subtraction, discarding underflow and wrapping around the boundary of the /// type. pub const fn wrapping_sub(&self, rhs: &Self) -> Self { - self.underflowing_sub(&rhs).0 + self.underflowing_sub(rhs).0 } } impl CheckedSub for Int { fn checked_sub(&self, rhs: &Self) -> CtOption { - let (res, underflow) = Self::underflowing_sub(&self, &rhs); + let (res, underflow) = Self::underflowing_sub(self, rhs); ConstCtOption::new(res, underflow.not()).into() } } diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index 21bff28b9..b0bd580f8 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -312,7 +312,6 @@ impl Uint { type SingleK = I64; /// Smallest [Uint] that fits 2K bits. type DoubleK = U128; - debug_assert!(DoubleK::BITS >= 2 * K); let (mut a, mut b) = (*self, *rhs.as_ref()); From 1012084eb7f5e91b880ef6a0687a35540f52f082 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 30 Jan 2025 15:24:50 +0100 Subject: [PATCH 037/203] Re-enable `Uint` benchmarks --- benches/uint.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/benches/uint.rs b/benches/uint.rs index e1a74436b..d97635bd0 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -519,14 +519,14 @@ fn bench_sqrt(c: &mut Criterion) { criterion_group!( benches, - // bench_random, - // bench_mul, - // bench_division, + bench_random, + bench_mul, + bench_division, bench_gcd, - // bench_shl, - // bench_shr, - // bench_inv_mod, - // bench_sqrt + bench_shl, + bench_shr, + bench_inv_mod, + bench_sqrt ); criterion_main!(benches); From b3bab2286e5434ef2f310a4fa1fa5831abae6c6f Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 30 Jan 2025 15:25:23 +0100 Subject: [PATCH 038/203] Fix --- benches/int.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/benches/int.rs b/benches/int.rs index 466eb6b70..d9a6c728d 100644 --- a/benches/int.rs +++ b/benches/int.rs @@ -1,7 +1,6 @@ use std::ops::Div; use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; -use num_traits::WrappingSub; use rand_core::OsRng; use crypto_bigint::{NonZero, Random, I1024, I128, I2048, I256, I4096, I512}; From 4aa0a3424e3fc51a499ee9fa2df6a83271e79703 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 30 Jan 2025 15:28:04 +0100 Subject: [PATCH 039/203] Revert `Int::mul` modifications --- src/int/mul.rs | 7 +++---- src/int/mul_uint.rs | 12 +++++------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/int/mul.rs b/src/int/mul.rs index 1e7ff99c4..315564c4b 100644 --- a/src/int/mul.rs +++ b/src/int/mul.rs @@ -4,7 +4,7 @@ use core::ops::{Mul, MulAssign}; use subtle::CtOption; -use crate::{Checked, CheckedMul, ConcatMixed, ConstChoice, ConstCtOption, Int, Uint}; +use crate::{Checked, CheckedMul, ConcatMixed, ConstChoice, ConstCtOption, Int, Uint, Zero}; impl Int { /// Compute "wide" multiplication as a 3-tuple `(lo, hi, negate)`. @@ -81,9 +81,8 @@ impl CheckedMul> for #[inline] fn checked_mul(&self, rhs: &Int) -> CtOption { let (lo, hi, is_negative) = self.split_mul(rhs); - Self::new_from_abs_sign(lo, is_negative) - .and_choice(hi.is_nonzero()) - .into() + let val = Self::new_from_abs_sign(lo, is_negative); + CtOption::from(val).and_then(|int| CtOption::new(int, hi.is_zero())) } } diff --git a/src/int/mul_uint.rs b/src/int/mul_uint.rs index 4ddd543c2..483e48467 100644 --- a/src/int/mul_uint.rs +++ b/src/int/mul_uint.rs @@ -2,7 +2,7 @@ use core::ops::Mul; use subtle::CtOption; -use crate::{CheckedMul, ConcatMixed, ConstChoice, Int, Uint}; +use crate::{CheckedMul, ConcatMixed, ConstChoice, Int, Uint, Zero}; impl Int { /// Compute "wide" multiplication between an [`Int`] and [`Uint`] as 3-tuple `(lo, hi, negate)`. @@ -62,9 +62,8 @@ impl Int { rhs: &Uint, ) -> CtOption> { let (lo, hi, is_negative) = self.split_mul_uint_right(rhs); - Int::::new_from_abs_sign(lo, is_negative) - .and_choice(hi.is_nonzero().not()) - .into() + let val = Int::::new_from_abs_sign(lo, is_negative); + CtOption::from(val).and_then(|int| CtOption::new(int, hi.is_zero())) } } @@ -72,9 +71,8 @@ impl CheckedMul> for #[inline] fn checked_mul(&self, rhs: &Uint) -> CtOption { let (lo, hi, is_negative) = self.split_mul_uint(rhs); - Self::new_from_abs_sign(lo, is_negative) - .and_choice(hi.is_nonzero().not()) - .into() + let val = Self::new_from_abs_sign(lo, is_negative); + CtOption::from(val).and_then(|int| CtOption::new(int, hi.is_zero())) } } From 8b1ec7643af4fca0d196af2b1be4236ae3681566 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 30 Jan 2025 16:17:37 +0100 Subject: [PATCH 040/203] Improve testing --- src/uint/new_gcd.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index b0bd580f8..ec6b46c30 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -345,6 +345,7 @@ impl Uint { } } +#[cfg(feature = "rand_core")] #[cfg(test)] mod tests { use crate::{Gcd, Random, Uint, U1024, U16384, U2048, U256, U4096, U512, U8192}; @@ -374,7 +375,7 @@ mod tests { gcd_comparison_test(Uint::MAX, Uint::ONE); gcd_comparison_test(Uint::MAX, Uint::MAX); - for _ in 0..500 { + for _ in 0..100 { let x = Uint::::random(&mut OsRng); let mut y = Uint::::random(&mut OsRng); From 0ddbf022ca9bdd376d0a2ae21109a660b0551e5c Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 30 Jan 2025 16:18:56 +0100 Subject: [PATCH 041/203] Fix doc --- src/uint/new_gcd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index ec6b46c30..693446aa3 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -1,6 +1,6 @@ //! This module implements (a constant variant of) the Optimized Extended Binary GCD algorithm, //! which is described by Pornin as Algorithm 2 in "Optimized Binary GCD for Modular Inversion". -//! Ref: https://eprint.iacr.org/2020/972.pdf +//! Ref: use crate::{ConstChoice, ConstCtOption, Int, Limb, NonZero, Odd, Uint, I64, U128}; From 00c9402643eb087aa11cbf28bd7ba91258dcf86e Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 31 Jan 2025 13:25:35 +0100 Subject: [PATCH 042/203] Refactor; move `ExtendedXXX` to separate file --- src/uint/new_gcd.rs | 139 ++-------------------------------- src/uint/new_gcd/extension.rs | 134 ++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 134 deletions(-) create mode 100644 src/uint/new_gcd/extension.rs diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index 693446aa3..e82926b38 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -2,140 +2,10 @@ //! which is described by Pornin as Algorithm 2 in "Optimized Binary GCD for Modular Inversion". //! Ref: -use crate::{ConstChoice, ConstCtOption, Int, Limb, NonZero, Odd, Uint, I64, U128}; +use crate::{ConstChoice, I64, Int, NonZero, Odd, U128, Uint}; +use crate::uint::new_gcd::extension::ExtendedInt; -struct ExtendedUint( - Uint, - Uint, -); - -impl ExtendedUint { - /// Interpret `self` as an [ExtendedInt] - #[inline] - pub const fn as_extended_int(&self) -> ExtendedInt { - ExtendedInt(self.0, self.1) - } - - /// Construction the binary negation of `self`, i.e., map `self` to `!self + 1`. - /// - /// Note: maps `0` to itself. - #[inline] - pub const fn wrapping_neg(&self) -> Self { - let (lhs, carry) = self.0.carrying_neg(); - let mut rhs = self.1.not(); - rhs = Uint::select(&rhs, &rhs.wrapping_add(&Uint::ONE), carry); - Self(lhs, rhs) - } - - /// Negate `self` if `negate` is truthy. Otherwise returns `self`. - #[inline] - pub const fn wrapping_neg_if(&self, negate: ConstChoice) -> Self { - let neg = self.wrapping_neg(); - Self( - Uint::select(&self.0, &neg.0, negate), - Uint::select(&self.1, &neg.1, negate), - ) - } - - /// Shift `self` right by `shift` bits. - /// - /// Assumes `shift <= Uint::::BITS`. - #[inline] - pub const fn shr(&self, shift: u32) -> Self { - debug_assert!(shift <= Uint::::BITS); - - let shift_is_zero = ConstChoice::from_u32_eq(shift, 0); - let left_shift = shift_is_zero.select_u32(Uint::::BITS - shift, 0); - - let hi = self.1.shr(shift); - // TODO: replace with carrying_shl - let carry = Uint::select(&self.1, &Uint::ZERO, shift_is_zero).shl(left_shift); - let mut lo = self.0.shr(shift); - - // Apply carry - let limb_diff = LIMBS.wrapping_sub(EXTRA) as u32; - let carry = carry.resize::().shl_vartime(limb_diff * Limb::BITS); - lo = lo.bitxor(&carry); - - Self(lo, hi) - } -} - -struct ExtendedInt( - Uint, - Uint, -); - -impl ExtendedInt { - /// Construct an [ExtendedInt] from the product of a [Uint] and an [Int]. - /// - /// Assumes the top bit of the product is not set. - #[inline] - pub const fn from_product(lhs: Uint, rhs: Int) -> Self { - let (lo, hi, sgn) = rhs.split_mul_uint_right(&lhs); - ExtendedUint(lo, hi).wrapping_neg_if(sgn).as_extended_int() - } - - /// Interpret this as an [ExtendedUint]. - #[inline] - pub const fn as_extended_uint(&self) -> ExtendedUint { - ExtendedUint(self.0, self.1) - } - - /// Return the negation of `self` if `negate` is truthy. Otherwise, return `self`. - #[inline] - pub const fn wrapping_neg_if(&self, negate: ConstChoice) -> Self { - self.as_extended_uint() - .wrapping_neg_if(negate) - .as_extended_int() - } - - /// Compute `self + rhs`, wrapping any overflow. - #[inline] - pub const fn wrapping_add(&self, rhs: &Self) -> Self { - let (lo, carry) = self.0.adc(&rhs.0, Limb::ZERO); - let (hi, _) = self.1.adc(&rhs.1, carry); - Self(lo, hi) - } - - /// Returns self without the extension. - /// - /// Is `None` if the extension cannot be dropped, i.e., when there is a bit in the extension - /// that does not equal the MSB in the base. - #[inline] - pub const fn abs_drop_extension(&self) -> ConstCtOption> { - // should succeed when - // - extension is ZERO, or - // - extension is MAX, and the top bit in base is set. - let proper_positive = Int::eq(&self.1.as_int(), &Int::ZERO); - let proper_negative = - Int::eq(&self.1.as_int(), &Int::MINUS_ONE).and(self.0.as_int().is_negative()); - ConstCtOption::new(self.abs().0, proper_negative.or(proper_positive)) - } - - /// Decompose `self` into is absolute value and signum. - #[inline] - pub const fn abs_sgn(&self) -> (ExtendedUint, ConstChoice) { - let is_negative = self.1.as_int().is_negative(); - ( - self.wrapping_neg_if(is_negative).as_extended_uint(), - is_negative, - ) - } - - /// Decompose `self` into is absolute value and signum. - #[inline] - pub const fn abs(&self) -> ExtendedUint { - self.abs_sgn().0 - } - - /// Divide self by `2^k`, rounding towards zero. - #[inline] - pub const fn div_2k(&self, k: u32) -> Self { - let (abs, sgn) = self.abs_sgn(); - abs.shr(k).wrapping_neg_if(sgn).as_extended_int() - } -} +mod extension; type Vector = (T, T); struct IntMatrix([[Int; DIM]; DIM]); @@ -348,9 +218,10 @@ impl Uint { #[cfg(feature = "rand_core")] #[cfg(test)] mod tests { - use crate::{Gcd, Random, Uint, U1024, U16384, U2048, U256, U4096, U512, U8192}; use rand_core::OsRng; + use crate::{Gcd, Random, U1024, U16384, U2048, U256, U4096, U512, U8192, Uint}; + fn gcd_comparison_test(lhs: Uint, rhs: Uint) where Uint: Gcd>, diff --git a/src/uint/new_gcd/extension.rs b/src/uint/new_gcd/extension.rs new file mode 100644 index 000000000..939e57719 --- /dev/null +++ b/src/uint/new_gcd/extension.rs @@ -0,0 +1,134 @@ +use crate::{ConstChoice, ConstCtOption, Int, Limb, Uint}; + +pub(crate) struct ExtendedUint( + Uint, + Uint, +); + +impl ExtendedUint { + /// Interpret `self` as an [ExtendedInt] + #[inline] + pub const fn as_extended_int(&self) -> ExtendedInt { + ExtendedInt(self.0, self.1) + } + + /// Construction the binary negation of `self`, i.e., map `self` to `!self + 1`. + /// + /// Note: maps `0` to itself. + #[inline] + pub const fn wrapping_neg(&self) -> Self { + let (lhs, carry) = self.0.carrying_neg(); + let mut rhs = self.1.not(); + rhs = Uint::select(&rhs, &rhs.wrapping_add(&Uint::ONE), carry); + Self(lhs, rhs) + } + + /// Negate `self` if `negate` is truthy. Otherwise returns `self`. + #[inline] + pub const fn wrapping_neg_if(&self, negate: ConstChoice) -> Self { + let neg = self.wrapping_neg(); + Self( + Uint::select(&self.0, &neg.0, negate), + Uint::select(&self.1, &neg.1, negate), + ) + } + + /// Shift `self` right by `shift` bits. + /// + /// Assumes `shift <= Uint::::BITS`. + #[inline] + pub const fn shr(&self, shift: u32) -> Self { + debug_assert!(shift <= Uint::::BITS); + + let shift_is_zero = ConstChoice::from_u32_eq(shift, 0); + let left_shift = shift_is_zero.select_u32(Uint::::BITS - shift, 0); + + let hi = self.1.shr(shift); + // TODO: replace with carrying_shl + let carry = Uint::select(&self.1, &Uint::ZERO, shift_is_zero).shl(left_shift); + let mut lo = self.0.shr(shift); + + // Apply carry + let limb_diff = LIMBS.wrapping_sub(EXTRA) as u32; + let carry = carry.resize::().shl_vartime(limb_diff * Limb::BITS); + lo = lo.bitxor(&carry); + + Self(lo, hi) + } +} + +pub(crate) struct ExtendedInt( + Uint, + Uint, +); + +impl ExtendedInt { + /// Construct an [ExtendedInt] from the product of a [Uint] and an [Int]. + /// + /// Assumes the top bit of the product is not set. + #[inline] + pub const fn from_product(lhs: Uint, rhs: Int) -> Self { + let (lo, hi, sgn) = rhs.split_mul_uint_right(&lhs); + ExtendedUint(lo, hi).wrapping_neg_if(sgn).as_extended_int() + } + + /// Interpret this as an [ExtendedUint]. + #[inline] + pub const fn as_extended_uint(&self) -> ExtendedUint { + ExtendedUint(self.0, self.1) + } + + /// Return the negation of `self` if `negate` is truthy. Otherwise, return `self`. + #[inline] + pub const fn wrapping_neg_if(&self, negate: ConstChoice) -> Self { + self.as_extended_uint() + .wrapping_neg_if(negate) + .as_extended_int() + } + + /// Compute `self + rhs`, wrapping any overflow. + #[inline] + pub const fn wrapping_add(&self, rhs: &Self) -> Self { + let (lo, carry) = self.0.adc(&rhs.0, Limb::ZERO); + let (hi, _) = self.1.adc(&rhs.1, carry); + Self(lo, hi) + } + + /// Returns self without the extension. + /// + /// Is `None` if the extension cannot be dropped, i.e., when there is a bit in the extension + /// that does not equal the MSB in the base. + #[inline] + pub const fn abs_drop_extension(&self) -> ConstCtOption> { + // should succeed when + // - extension is ZERO, or + // - extension is MAX, and the top bit in base is set. + let proper_positive = Int::eq(&self.1.as_int(), &Int::ZERO); + let proper_negative = + Int::eq(&self.1.as_int(), &Int::MINUS_ONE).and(self.0.as_int().is_negative()); + ConstCtOption::new(self.abs().0, proper_negative.or(proper_positive)) + } + + /// Decompose `self` into is absolute value and signum. + #[inline] + pub const fn abs_sgn(&self) -> (ExtendedUint, ConstChoice) { + let is_negative = self.1.as_int().is_negative(); + ( + self.wrapping_neg_if(is_negative).as_extended_uint(), + is_negative, + ) + } + + /// Decompose `self` into is absolute value and signum. + #[inline] + pub const fn abs(&self) -> ExtendedUint { + self.abs_sgn().0 + } + + /// Divide self by `2^k`, rounding towards zero. + #[inline] + pub const fn div_2k(&self, k: u32) -> Self { + let (abs, sgn) = self.abs_sgn(); + abs.shr(k).wrapping_neg_if(sgn).as_extended_int() + } +} \ No newline at end of file From ecdd3bec39d129a6f99584d2425ede5ffd596d29 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 31 Jan 2025 13:26:49 +0100 Subject: [PATCH 043/203] Refactor; move `IntMatrix` to separate file --- src/uint/new_gcd.rs | 24 +++--------------------- src/uint/new_gcd/matrix.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 21 deletions(-) create mode 100644 src/uint/new_gcd/matrix.rs diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index e82926b38..d3e82ed91 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -3,28 +3,10 @@ //! Ref: use crate::{ConstChoice, I64, Int, NonZero, Odd, U128, Uint}; -use crate::uint::new_gcd::extension::ExtendedInt; +use crate::uint::new_gcd::matrix::IntMatrix; mod extension; - -type Vector = (T, T); -struct IntMatrix([[Int; DIM]; DIM]); -impl IntMatrix { - /// Apply this matrix to a vector of [Uint]s, returning the result as a vector of - /// [ExtendedInt]s. - #[inline] - const fn extended_apply_to( - &self, - vec: Vector>, - ) -> Vector> { - let (a, b) = vec; - let a00 = ExtendedInt::from_product(a, self.0[0][0]); - let a01 = ExtendedInt::from_product(a, self.0[0][1]); - let b10 = ExtendedInt::from_product(b, self.0[1][0]); - let b11 = ExtendedInt::from_product(b, self.0[1][1]); - (a00.wrapping_add(&b10), a01.wrapping_add(&b11)) - } -} +mod matrix; /// `const` equivalent of `u32::max(a, b)`. const fn const_max(a: u32, b: u32) -> u32 { @@ -137,7 +119,7 @@ impl Uint { log_upper_bound = do_apply.select_u32(log_upper_bound, log_upper_bound + 1); } - (IntMatrix([[f00, f10], [f01, f11]]), log_upper_bound) + (IntMatrix::new(f00, f01, f10, f11), log_upper_bound) } /// Compute the greatest common divisor of `self` and `rhs`. diff --git a/src/uint/new_gcd/matrix.rs b/src/uint/new_gcd/matrix.rs new file mode 100644 index 000000000..cd2446fd3 --- /dev/null +++ b/src/uint/new_gcd/matrix.rs @@ -0,0 +1,28 @@ +use crate::{Int, Uint}; +use crate::uint::new_gcd::extension::ExtendedInt; + +type Vector = (T, T); + +pub(crate) struct IntMatrix([[Int; DIM]; DIM]); + +impl IntMatrix { + + pub(crate) const fn new(m00: Int, m01: Int, m10: Int, m11: Int) -> Self { + Self([[m00, m10], [m01, m11]]) + } + + /// Apply this matrix to a vector of [Uint]s, returning the result as a vector of + /// [ExtendedInt]s. + #[inline] + pub(crate) const fn extended_apply_to( + &self, + vec: Vector>, + ) -> Vector> { + let (a, b) = vec; + let a00 = ExtendedInt::from_product(a, self.0[0][0]); + let a01 = ExtendedInt::from_product(a, self.0[0][1]); + let b10 = ExtendedInt::from_product(b, self.0[1][0]); + let b11 = ExtendedInt::from_product(b, self.0[1][1]); + (a00.wrapping_add(&b10), a01.wrapping_add(&b11)) + } +} \ No newline at end of file From b9e1af229555794e8546097f21873c1388fb8092 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 31 Jan 2025 13:30:04 +0100 Subject: [PATCH 044/203] Remove prefix from `const_*` functions --- src/uint/new_gcd.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uint/new_gcd.rs b/src/uint/new_gcd.rs index d3e82ed91..22ab4d07f 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/new_gcd.rs @@ -9,12 +9,12 @@ mod extension; mod matrix; /// `const` equivalent of `u32::max(a, b)`. -const fn const_max(a: u32, b: u32) -> u32 { +const fn max(a: u32, b: u32) -> u32 { ConstChoice::from_u32_lt(a, b).select_u32(a, b) } /// `const` equivalent of `u32::min(a, b)`. -const fn const_min(a: u32, b: u32) -> u32 { +const fn min(a: u32, b: u32) -> u32 { ConstChoice::from_u32_lt(a, b).select_u32(b, a) } @@ -143,7 +143,7 @@ impl Uint { .as_ref() .is_nonzero() .select_u32(0, rhs.as_ref().trailing_zeros()); - let k = const_min(i, j); + let k = min(i, j); Self::new_odd_gcd( &self.shr(i), @@ -172,7 +172,7 @@ impl Uint { i += 1; // Construct a_ and b_ as the summary of a and b, respectively. - let n = const_max(2 * K, const_max(a.bits(), b.bits())); + let n = max(2 * K, max(a.bits(), b.bits())); let a_: DoubleK = a.compact(n, K); let b_: DoubleK = b.compact(n, K); From 7ab1b863be5f61ef1dd94cd2131fd6f0b186680f Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 31 Jan 2025 13:39:23 +0100 Subject: [PATCH 045/203] Rename to `bingcd` --- benches/uint.rs | 4 ++-- src/uint.rs | 2 +- src/uint/{new_gcd.rs => bingcd.rs} | 18 +++++++++--------- src/uint/{new_gcd => bingcd}/extension.rs | 2 +- src/uint/{new_gcd => bingcd}/matrix.rs | 12 ++++++++---- 5 files changed, 21 insertions(+), 17 deletions(-) rename src/uint/{new_gcd.rs => bingcd.rs} (94%) rename src/uint/{new_gcd => bingcd}/extension.rs (99%) rename src/uint/{new_gcd => bingcd}/matrix.rs (81%) diff --git a/benches/uint.rs b/benches/uint.rs index d97635bd0..fde5c2940 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -336,14 +336,14 @@ fn gcd_bench( ) }); - g.bench_function(BenchmarkId::new("new_gcd (ct)", LIMBS), |b| { + g.bench_function(BenchmarkId::new("bingcd (ct)", LIMBS), |b| { b.iter_batched( || { let f = Uint::::random(&mut OsRng); let g = Uint::::random(&mut OsRng); (f, g) }, - |(f, g)| black_box(Uint::new_gcd(&f, &g)), + |(f, g)| black_box(Uint::bingcd(&f, &g)), BatchSize::SmallInput, ) }); diff --git a/src/uint.rs b/src/uint.rs index b64c2038f..a0aa14477 100644 --- a/src/uint.rs +++ b/src/uint.rs @@ -461,9 +461,9 @@ impl_uint_concat_split_mixed! { (U1024, [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15]), } +mod bingcd; #[cfg(feature = "extra-sizes")] mod extra_sizes; -mod new_gcd; #[cfg(test)] #[allow(clippy::unwrap_used)] diff --git a/src/uint/new_gcd.rs b/src/uint/bingcd.rs similarity index 94% rename from src/uint/new_gcd.rs rename to src/uint/bingcd.rs index 22ab4d07f..7f5e0a836 100644 --- a/src/uint/new_gcd.rs +++ b/src/uint/bingcd.rs @@ -2,8 +2,8 @@ //! which is described by Pornin as Algorithm 2 in "Optimized Binary GCD for Modular Inversion". //! Ref: -use crate::{ConstChoice, I64, Int, NonZero, Odd, U128, Uint}; -use crate::uint::new_gcd::matrix::IntMatrix; +use crate::uint::bingcd::matrix::IntMatrix; +use crate::{ConstChoice, Int, NonZero, Odd, Uint, I64, U128}; mod extension; mod matrix; @@ -123,18 +123,18 @@ impl Uint { } /// Compute the greatest common divisor of `self` and `rhs`. - pub const fn new_gcd(&self, rhs: &Self) -> Self { + pub const fn bingcd(&self, rhs: &Self) -> Self { // Account for the case where rhs is zero let rhs_is_zero = rhs.is_nonzero().not(); let rhs_ = Uint::select(rhs, &Uint::ONE, rhs_is_zero) .to_nz() .expect("rhs is non zero by construction"); - let result = self.new_gcd_nonzero(&rhs_); + let result = self.bingcd_nonzero(&rhs_); Uint::select(&result, self, rhs_is_zero) } /// Compute the greatest common divisor of `self` and `rhs`, where `rhs` is known to be nonzero. - const fn new_gcd_nonzero(&self, rhs: &NonZero) -> Self { + const fn bingcd_nonzero(&self, rhs: &NonZero) -> Self { // Leverage two GCD identity rules to make self and rhs odd. // 1) gcd(2a, 2b) = 2 * gcd(a, b) // 2) gcd(a, 2b) = gcd(a, b) if a is odd. @@ -145,7 +145,7 @@ impl Uint { .select_u32(0, rhs.as_ref().trailing_zeros()); let k = min(i, j); - Self::new_odd_gcd( + Self::odd_bingcd( &self.shr(i), &rhs.as_ref() .shr(j) @@ -157,7 +157,7 @@ impl Uint { /// Compute the greatest common divisor of `self` and `rhs`, where `rhs` is known to be odd. #[inline(always)] - const fn new_odd_gcd(&self, rhs: &Odd) -> Self { + const fn odd_bingcd(&self, rhs: &Odd) -> Self { /// Window size. const K: u32 = 62; /// Smallest [Int] that fits a K-bit [Uint]. @@ -202,14 +202,14 @@ impl Uint { mod tests { use rand_core::OsRng; - use crate::{Gcd, Random, U1024, U16384, U2048, U256, U4096, U512, U8192, Uint}; + use crate::{Gcd, Random, Uint, U1024, U16384, U2048, U256, U4096, U512, U8192}; fn gcd_comparison_test(lhs: Uint, rhs: Uint) where Uint: Gcd>, { let gcd = lhs.gcd(&rhs); - let bingcd = lhs.new_gcd(&rhs); + let bingcd = lhs.bingcd(&rhs); assert_eq!(gcd, bingcd); } diff --git a/src/uint/new_gcd/extension.rs b/src/uint/bingcd/extension.rs similarity index 99% rename from src/uint/new_gcd/extension.rs rename to src/uint/bingcd/extension.rs index 939e57719..f907e9bef 100644 --- a/src/uint/new_gcd/extension.rs +++ b/src/uint/bingcd/extension.rs @@ -131,4 +131,4 @@ impl ExtendedInt { let (abs, sgn) = self.abs_sgn(); abs.shr(k).wrapping_neg_if(sgn).as_extended_int() } -} \ No newline at end of file +} diff --git a/src/uint/new_gcd/matrix.rs b/src/uint/bingcd/matrix.rs similarity index 81% rename from src/uint/new_gcd/matrix.rs rename to src/uint/bingcd/matrix.rs index cd2446fd3..c7d64bb17 100644 --- a/src/uint/new_gcd/matrix.rs +++ b/src/uint/bingcd/matrix.rs @@ -1,13 +1,17 @@ +use crate::uint::bingcd::extension::ExtendedInt; use crate::{Int, Uint}; -use crate::uint::new_gcd::extension::ExtendedInt; type Vector = (T, T); pub(crate) struct IntMatrix([[Int; DIM]; DIM]); impl IntMatrix { - - pub(crate) const fn new(m00: Int, m01: Int, m10: Int, m11: Int) -> Self { + pub(crate) const fn new( + m00: Int, + m01: Int, + m10: Int, + m11: Int, + ) -> Self { Self([[m00, m10], [m01, m11]]) } @@ -25,4 +29,4 @@ impl IntMatrix { let b11 = ExtendedInt::from_product(b, self.0[1][1]); (a00.wrapping_add(&b10), a01.wrapping_add(&b11)) } -} \ No newline at end of file +} From 6677727c19884974a974fc77b0b9f61c22d68186 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 31 Jan 2025 16:38:08 +0100 Subject: [PATCH 046/203] Introduce `odd_bingcd_small` --- src/uint/bingcd.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index 7f5e0a836..f2613e229 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -70,7 +70,34 @@ impl Uint { hi.shl_vartime(k - 1).bitxor(&lo) } - /// Constructs a matrix `M` s.t. for `(A, B) = M(a,b)` it holds that + /// Computes `gcd(self, rhs)`, leveraging the Binary GCD algorithm. + /// Is efficient only for relatively small `LIMBS`. + #[inline] + const fn odd_bingcd_small(mut a: Uint, b: &Odd>) -> Uint { + let mut b = *b.as_ref(); + let mut j = 0; + while j < Uint::::BITS { + j += 1; + + let a_odd = a.is_odd(); + + // swap if a odd and a < b + let a_lt_b = Uint::lt(&a, &b); + let do_swap = a_odd.and(a_lt_b); + Uint::conditional_swap(&mut a, &mut b, do_swap); + + // subtract b from a when a is odd + a = Uint::select(&a, &a.wrapping_sub(&b), a_odd); + + // Div a by two when b ≠ 0, otherwise do nothing. + let do_apply = b.is_nonzero(); + a = Uint::select(&a, &a.shr_vartime(1), do_apply); + } + + b + } + + /// Constructs a matrix `M` s.t. for `(A, B) = M(a,b)` it holds that /// - `gcd(A, B) = gcd(a, b)`, and /// - `A.bits() < a.bits()` and/or `B.bits() < b.bits()`. /// From 1ae482ec0a4c904cc13283378b23c1bfc5afad52 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 31 Jan 2025 17:33:55 +0100 Subject: [PATCH 047/203] Major code refactor --- benches/uint.rs | 26 +++-- src/uint/bingcd.rs | 234 +++------------------------------------ src/uint/bingcd/gcd.rs | 129 +++++++++++++++++++++ src/uint/bingcd/tools.rs | 68 ++++++++++++ src/uint/bingcd/xgcd.rs | 56 ++++++++++ 5 files changed, 283 insertions(+), 230 deletions(-) create mode 100644 src/uint/bingcd/gcd.rs create mode 100644 src/uint/bingcd/tools.rs create mode 100644 src/uint/bingcd/xgcd.rs diff --git a/benches/uint.rs b/benches/uint.rs index fde5c2940..b4c0104fb 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -5,7 +5,7 @@ use criterion::{ use crypto_bigint::modular::SafeGcdInverter; use crypto_bigint::{ Gcd, Limb, NonZero, Odd, PrecomputeInverter, Random, RandomBits, RandomMod, Reciprocal, Uint, - U1024, U128, U16384, U2048, U256, U4096, U512, U8192, + U1024, U128, U16384, U192, U2048, U256, U4096, U512, U64, U8192, }; use rand_chacha::ChaCha8Rng; use rand_core::{OsRng, RngCore, SeedableRng}; @@ -312,17 +312,17 @@ fn gcd_bench( ) where Odd>: PrecomputeInverter>, { - g.bench_function(BenchmarkId::new("gcd (vt)", LIMBS), |b| { - b.iter_batched( - || { - let f = Uint::::random(&mut OsRng); - let g = Uint::::random(&mut OsRng); - (f, g) - }, - |(f, g)| black_box(Uint::gcd_vartime(&f, &g)), - BatchSize::SmallInput, - ) - }); + // g.bench_function(BenchmarkId::new("gcd (vt)", LIMBS), |b| { + // b.iter_batched( + // || { + // let f = Uint::::random(&mut OsRng); + // let g = Uint::::random(&mut OsRng); + // (f, g) + // }, + // |(f, g)| black_box(Uint::gcd_vartime(&f, &g)), + // BatchSize::SmallInput, + // ) + // }); g.bench_function(BenchmarkId::new("gcd (ct)", LIMBS), |b| { b.iter_batched( @@ -352,7 +352,9 @@ fn gcd_bench( fn bench_gcd(c: &mut Criterion) { let mut group = c.benchmark_group("greatest common divisor"); + gcd_bench(&mut group, U64::ZERO); gcd_bench(&mut group, U128::ZERO); + gcd_bench(&mut group, U192::ZERO); gcd_bench(&mut group, U256::ZERO); gcd_bench(&mut group, U512::ZERO); gcd_bench(&mut group, U1024::ZERO); diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index f2613e229..dcb1de7d6 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -2,225 +2,23 @@ //! which is described by Pornin as Algorithm 2 in "Optimized Binary GCD for Modular Inversion". //! Ref: -use crate::uint::bingcd::matrix::IntMatrix; -use crate::{ConstChoice, Int, NonZero, Odd, Uint, I64, U128}; +use crate::Uint; mod extension; +mod gcd; mod matrix; +mod tools; -/// `const` equivalent of `u32::max(a, b)`. -const fn max(a: u32, b: u32) -> u32 { - ConstChoice::from_u32_lt(a, b).select_u32(a, b) -} - -/// `const` equivalent of `u32::min(a, b)`. -const fn min(a: u32, b: u32) -> u32 { - ConstChoice::from_u32_lt(a, b).select_u32(b, a) -} +mod xgcd; impl Uint { - /// Construct a [Uint] containing the bits in `self` in the range `[idx, idx + length)`. - /// - /// Assumes `length ≤ Uint::::BITS` and `idx + length ≤ Self::BITS`. - #[inline(always)] - const fn section( - &self, - idx: u32, - length: u32, - ) -> Uint { - debug_assert!(length <= Uint::::BITS); - debug_assert!(idx + length <= Self::BITS); - - let mask = Uint::ONE.shl(length).wrapping_sub(&Uint::ONE); - self.shr(idx).resize::().bitand(&mask) - } - - /// Construct a [Uint] containing the bits in `self` in the range `[idx, idx + length)`. - /// - /// Assumes `length ≤ Uint::::BITS` and `idx + length ≤ Self::BITS`. - /// - /// Executes in time variable in `idx` only. - #[inline(always)] - const fn section_vartime( - &self, - idx: u32, - length: u32, - ) -> Uint { - debug_assert!(length <= Uint::::BITS); - debug_assert!(idx + length <= Self::BITS); - - let mask = Uint::ONE.shl_vartime(length).wrapping_sub(&Uint::ONE); - self.shr_vartime(idx) - .resize::() - .bitand(&mask) - } - - /// Compact `self` to a form containing the concatenation of its bit ranges `[0, k-1)` - /// and `[n-k-1, n)`. - /// - /// Assumes `k ≤ Uint::::BITS`, `n ≤ Self::BITS` and `n ≥ 2k`. - #[inline(always)] - const fn compact(&self, n: u32, k: u32) -> Uint { - debug_assert!(k <= Uint::::BITS); - debug_assert!(n <= Self::BITS); - debug_assert!(n >= 2 * k); - - let hi = self.section(n - k - 1, k + 1); - let lo = self.section_vartime(0, k - 1); - hi.shl_vartime(k - 1).bitxor(&lo) - } - - /// Computes `gcd(self, rhs)`, leveraging the Binary GCD algorithm. - /// Is efficient only for relatively small `LIMBS`. - #[inline] - const fn odd_bingcd_small(mut a: Uint, b: &Odd>) -> Uint { - let mut b = *b.as_ref(); - let mut j = 0; - while j < Uint::::BITS { - j += 1; - - let a_odd = a.is_odd(); - - // swap if a odd and a < b - let a_lt_b = Uint::lt(&a, &b); - let do_swap = a_odd.and(a_lt_b); - Uint::conditional_swap(&mut a, &mut b, do_swap); - - // subtract b from a when a is odd - a = Uint::select(&a, &a.wrapping_sub(&b), a_odd); - - // Div a by two when b ≠ 0, otherwise do nothing. - let do_apply = b.is_nonzero(); - a = Uint::select(&a, &a.shr_vartime(1), do_apply); - } - - b - } - - /// Constructs a matrix `M` s.t. for `(A, B) = M(a,b)` it holds that - /// - `gcd(A, B) = gcd(a, b)`, and - /// - `A.bits() < a.bits()` and/or `B.bits() < b.bits()`. - /// - /// Moreover, it returns `log_upper_bound: u32` s.t. each element in `M` lies in the interval - /// `(-2^log_upper_bound, 2^log_upper_bound]`. - /// - /// Assumes `iterations < Uint::::BITS`. - #[inline] - const fn restricted_extended_gcd( - mut a: Uint, - mut b: Uint, - iterations: u32, - ) -> (IntMatrix, u32) { - debug_assert!(iterations < Uint::::BITS); - - // Unit matrix - let (mut f00, mut f01) = (Int::ONE, Int::ZERO); - let (mut f10, mut f11) = (Int::ZERO, Int::ONE); - - // Compute the update matrix. - let mut log_upper_bound = 0; - let mut j = 0; - while j < iterations { - j += 1; - - let a_odd = a.is_odd(); - let a_lt_b = Uint::lt(&a, &b); - - // swap if a odd and a < b - let do_swap = a_odd.and(a_lt_b); - Uint::conditional_swap(&mut a, &mut b, do_swap); - Int::conditional_swap(&mut f00, &mut f10, do_swap); - Int::conditional_swap(&mut f01, &mut f11, do_swap); - - // subtract a from b when a is odd - a = Uint::select(&a, &a.wrapping_sub(&b), a_odd); - f00 = Int::select(&f00, &f00.wrapping_sub(&f10), a_odd); - f01 = Int::select(&f01, &f01.wrapping_sub(&f11), a_odd); - - // mul/div by 2 when b is non-zero. - // Only apply operations when b ≠ 0, otherwise do nothing. - let do_apply = b.is_nonzero(); - a = Uint::select(&a, &a.shr_vartime(1), do_apply); - f10 = Int::select(&f10, &f10.shl_vartime(1), do_apply); - f11 = Int::select(&f11, &f11.shl_vartime(1), do_apply); - log_upper_bound = do_apply.select_u32(log_upper_bound, log_upper_bound + 1); - } - - (IntMatrix::new(f00, f01, f10, f11), log_upper_bound) - } - /// Compute the greatest common divisor of `self` and `rhs`. pub const fn bingcd(&self, rhs: &Self) -> Self { - // Account for the case where rhs is zero - let rhs_is_zero = rhs.is_nonzero().not(); - let rhs_ = Uint::select(rhs, &Uint::ONE, rhs_is_zero) + let self_is_zero = self.is_nonzero().not(); + let self_nz = Uint::select(self, &Uint::ONE, self_is_zero) .to_nz() - .expect("rhs is non zero by construction"); - let result = self.bingcd_nonzero(&rhs_); - Uint::select(&result, self, rhs_is_zero) - } - - /// Compute the greatest common divisor of `self` and `rhs`, where `rhs` is known to be nonzero. - const fn bingcd_nonzero(&self, rhs: &NonZero) -> Self { - // Leverage two GCD identity rules to make self and rhs odd. - // 1) gcd(2a, 2b) = 2 * gcd(a, b) - // 2) gcd(a, 2b) = gcd(a, b) if a is odd. - let i = self.is_nonzero().select_u32(0, self.trailing_zeros()); - let j = rhs - .as_ref() - .is_nonzero() - .select_u32(0, rhs.as_ref().trailing_zeros()); - let k = min(i, j); - - Self::odd_bingcd( - &self.shr(i), - &rhs.as_ref() - .shr(j) - .to_odd() - .expect("rhs is odd by construction"), - ) - .shl(k) - } - - /// Compute the greatest common divisor of `self` and `rhs`, where `rhs` is known to be odd. - #[inline(always)] - const fn odd_bingcd(&self, rhs: &Odd) -> Self { - /// Window size. - const K: u32 = 62; - /// Smallest [Int] that fits a K-bit [Uint]. - type SingleK = I64; - /// Smallest [Uint] that fits 2K bits. - type DoubleK = U128; - - let (mut a, mut b) = (*self, *rhs.as_ref()); - - let mut i = 0; - while i < (2 * Self::BITS - 1).div_ceil(K) { - i += 1; - - // Construct a_ and b_ as the summary of a and b, respectively. - let n = max(2 * K, max(a.bits(), b.bits())); - let a_: DoubleK = a.compact(n, K); - let b_: DoubleK = b.compact(n, K); - - // Compute the K-1 iteration update matrix from a_ and b_ - let (matrix, used_increments) = - Uint::restricted_extended_gcd::<{ SingleK::LIMBS }>(a_, b_, K - 1); - - // Update `a` and `b` using the update matrix - let (updated_a, updated_b) = matrix.extended_apply_to((a, b)); - - a = updated_a - .div_2k(used_increments) - .abs_drop_extension() - .expect("extension is zero"); - b = updated_b - .div_2k(used_increments) - .abs_drop_extension() - .expect("extension is zero"); - } - - b + .expect("self is non zero by construction"); + Uint::select(&self_nz.bingcd(rhs), rhs, self_is_zero) } } @@ -240,7 +38,7 @@ mod tests { assert_eq!(gcd, bingcd); } - fn test_new_gcd() + fn test_bingcd() where Uint: Gcd>, { @@ -267,12 +65,12 @@ mod tests { #[test] fn testing() { - test_new_gcd::<{ U256::LIMBS }>(); - test_new_gcd::<{ U512::LIMBS }>(); - test_new_gcd::<{ U1024::LIMBS }>(); - test_new_gcd::<{ U2048::LIMBS }>(); - test_new_gcd::<{ U4096::LIMBS }>(); - test_new_gcd::<{ U8192::LIMBS }>(); - test_new_gcd::<{ U16384::LIMBS }>(); + test_bingcd::<{ U256::LIMBS }>(); + test_bingcd::<{ U512::LIMBS }>(); + test_bingcd::<{ U1024::LIMBS }>(); + test_bingcd::<{ U2048::LIMBS }>(); + test_bingcd::<{ U4096::LIMBS }>(); + test_bingcd::<{ U8192::LIMBS }>(); + test_bingcd::<{ U16384::LIMBS }>(); } } diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs new file mode 100644 index 000000000..c2fe04e05 --- /dev/null +++ b/src/uint/bingcd/gcd.rs @@ -0,0 +1,129 @@ +use crate::uint::bingcd::tools::{const_max, const_min}; +use crate::{NonZero, Odd, Uint, U128, U64}; + +impl NonZero> { + /// Compute the greatest common divisor of `self` and `rhs`. + pub const fn bingcd(&self, rhs: &Uint) -> Uint { + let val = self.as_ref(); + // Leverage two GCD identity rules to make self and rhs odd. + // 1) gcd(2a, 2b) = 2 * gcd(a, b) + // 2) gcd(a, 2b) = gcd(a, b) if a is odd. + let i = val.is_nonzero().select_u32(0, val.trailing_zeros()); + let j = rhs.is_nonzero().select_u32(0, rhs.trailing_zeros()); + let k = const_min(i, j); + + self.as_ref() + .shr(i) + .to_odd() + .expect("self is odd by construction") + .bingcd(rhs) + .shl(k) + } +} + +impl Odd> { + const BITS: u32 = Uint::::BITS; + + /// Compute the greatest common divisor of `self` and `rhs`. + #[inline(always)] + pub const fn bingcd(&self, rhs: &Uint) -> Uint { + // Todo: tweak this threshold + if LIMBS <= 16 { + self.bingcd_small(rhs) + } else { + self.bingcd_large::<{ U64::BITS - 2 }, { U64::LIMBS }, { U128::LIMBS }>(rhs) + } + } + + /// Computes `gcd(self, rhs)`, leveraging the Binary GCD algorithm. + /// Is efficient only for relatively small `LIMBS`. + #[inline] + pub const fn bingcd_small(&self, rhs: &Uint) -> Uint { + let (mut a, mut b) = (*rhs, *self.as_ref()); + let mut j = 0; + while j < (2 * Self::BITS - 1) { + j += 1; + + let a_odd = a.is_odd(); + + // swap if a odd and a < b + let a_lt_b = Uint::lt(&a, &b); + let do_swap = a_odd.and(a_lt_b); + Uint::conditional_swap(&mut a, &mut b, do_swap); + + // subtract b from a when a is odd + a = Uint::select(&a, &a.wrapping_sub(&b), a_odd); + + // Div a by two when b ≠ 0, otherwise do nothing. + let do_apply = b.is_nonzero(); + a = Uint::select(&a, &a.shr_vartime(1), do_apply); + } + + b + } + + /// Computes `gcd(self, rhs)`, leveraging the Binary GCD algorithm. + /// Is efficient for larger `LIMBS`. + #[inline(always)] + pub const fn bingcd_large( + &self, + rhs: &Uint, + ) -> Uint { + let (mut a, mut b) = (*rhs, *self.as_ref()); + + let mut i = 0; + while i < (2 * Self::BITS - 1).div_ceil(K) { + i += 1; + + // Construct a_ and b_ as the summary of a and b, respectively. + let n = const_max(2 * K, const_max(a.bits(), b.bits())); + let a_ = a.compact::(n, K); + let b_ = b.compact::(n, K); + + // Compute the K-1 iteration update matrix from a_ and b_ + let (matrix, used_increments) = Uint::restricted_extended_gcd::(a_, b_, K - 1); + + // Update `a` and `b` using the update matrix + let (updated_a, updated_b) = matrix.extended_apply_to((a, b)); + + a = updated_a + .div_2k(used_increments) + .abs_drop_extension() + .expect("extension is zero"); + b = updated_b + .div_2k(used_increments) + .abs_drop_extension() + .expect("extension is zero"); + } + + b + } +} + +#[cfg(test)] +mod tests { + use crate::{Gcd, Random, Uint, U256, U512}; + use rand_core::OsRng; + + fn test_bingcd_small() + where + Uint: Gcd>, + { + for _ in 0..100 { + let x = Uint::::random(&mut OsRng); + let mut y = Uint::::random(&mut OsRng); + + y = Uint::select(&(y.wrapping_add(&Uint::ONE)), &y, y.is_odd()); + + let gcd = x.gcd(&y); + let bingcd = y.to_odd().unwrap().bingcd(&x); + assert_eq!(gcd, bingcd); + } + } + + #[test] + fn testing_bingcd_small() { + test_bingcd_small::<{ U256::LIMBS }>(); + test_bingcd_small::<{ U512::LIMBS }>(); + } +} diff --git a/src/uint/bingcd/tools.rs b/src/uint/bingcd/tools.rs new file mode 100644 index 000000000..3706f8cd0 --- /dev/null +++ b/src/uint/bingcd/tools.rs @@ -0,0 +1,68 @@ +use crate::{ConstChoice, Uint}; + +/// `const` equivalent of `u32::max(a, b)`. +pub(crate) const fn const_max(a: u32, b: u32) -> u32 { + ConstChoice::from_u32_lt(a, b).select_u32(a, b) +} + +/// `const` equivalent of `u32::min(a, b)`. +pub(crate) const fn const_min(a: u32, b: u32) -> u32 { + ConstChoice::from_u32_lt(a, b).select_u32(b, a) +} + +impl Uint { + /// Construct a [Uint] containing the bits in `self` in the range `[idx, idx + length)`. + /// + /// Assumes `length ≤ Uint::::BITS` and `idx + length ≤ Self::BITS`. + #[inline(always)] + pub(super) const fn section( + &self, + idx: u32, + length: u32, + ) -> Uint { + debug_assert!(length <= Uint::::BITS); + debug_assert!(idx + length <= Self::BITS); + + let mask = Uint::ONE.shl(length).wrapping_sub(&Uint::ONE); + self.shr(idx).resize::().bitand(&mask) + } + + /// Construct a [Uint] containing the bits in `self` in the range `[idx, idx + length)`. + /// + /// Assumes `length ≤ Uint::::BITS` and `idx + length ≤ Self::BITS`. + /// + /// Executes in time variable in `idx` only. + #[inline(always)] + pub(super) const fn section_vartime( + &self, + idx: u32, + length: u32, + ) -> Uint { + debug_assert!(length <= Uint::::BITS); + debug_assert!(idx + length <= Self::BITS); + + let mask = Uint::ONE.shl_vartime(length).wrapping_sub(&Uint::ONE); + self.shr_vartime(idx) + .resize::() + .bitand(&mask) + } + + /// Compact `self` to a form containing the concatenation of its bit ranges `[0, k-1)` + /// and `[n-k-1, n)`. + /// + /// Assumes `k ≤ Uint::::BITS`, `n ≤ Self::BITS` and `n ≥ 2k`. + #[inline(always)] + pub(super) const fn compact( + &self, + n: u32, + k: u32, + ) -> Uint { + debug_assert!(k <= Uint::::BITS); + debug_assert!(n <= Self::BITS); + debug_assert!(n >= 2 * k); + + let hi = self.section(n - k - 1, k + 1); + let lo = self.section_vartime(0, k - 1); + hi.shl_vartime(k - 1).bitxor(&lo) + } +} diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs new file mode 100644 index 000000000..cf7018481 --- /dev/null +++ b/src/uint/bingcd/xgcd.rs @@ -0,0 +1,56 @@ +use crate::uint::bingcd::matrix::IntMatrix; +use crate::{Int, Uint}; + +impl Uint { + /// Constructs a matrix `M` s.t. for `(A, B) = M(a,b)` it holds that + /// - `gcd(A, B) = gcd(a, b)`, and + /// - `A.bits() < a.bits()` and/or `B.bits() < b.bits()`. + /// + /// Moreover, it returns `log_upper_bound: u32` s.t. each element in `M` lies in the interval + /// `(-2^log_upper_bound, 2^log_upper_bound]`. + /// + /// Assumes `iterations < Uint::::BITS`. + #[inline] + pub(super) const fn restricted_extended_gcd( + mut a: Uint, + mut b: Uint, + iterations: u32, + ) -> (IntMatrix, u32) { + debug_assert!(iterations < Uint::::BITS); + + // Unit matrix + let (mut f00, mut f01) = (Int::ONE, Int::ZERO); + let (mut f10, mut f11) = (Int::ZERO, Int::ONE); + + // Compute the update matrix. + let mut log_upper_bound = 0; + let mut j = 0; + while j < iterations { + j += 1; + + let a_odd = a.is_odd(); + let a_lt_b = Uint::lt(&a, &b); + + // swap if a odd and a < b + let do_swap = a_odd.and(a_lt_b); + Uint::conditional_swap(&mut a, &mut b, do_swap); + Int::conditional_swap(&mut f00, &mut f10, do_swap); + Int::conditional_swap(&mut f01, &mut f11, do_swap); + + // subtract a from b when a is odd + a = Uint::select(&a, &a.wrapping_sub(&b), a_odd); + f00 = Int::select(&f00, &f00.wrapping_sub(&f10), a_odd); + f01 = Int::select(&f01, &f01.wrapping_sub(&f11), a_odd); + + // mul/div by 2 when b is non-zero. + // Only apply operations when b ≠ 0, otherwise do nothing. + let do_apply = b.is_nonzero(); + a = Uint::select(&a, &a.shr_vartime(1), do_apply); + f10 = Int::select(&f10, &f10.shl_vartime(1), do_apply); + f11 = Int::select(&f11, &f11.shl_vartime(1), do_apply); + log_upper_bound = do_apply.select_u32(log_upper_bound, log_upper_bound + 1); + } + + (IntMatrix::new(f00, f01, f10, f11), log_upper_bound) + } +} From 018ecf37a1c5aec62ac106c5d64ae7c1d192c85f Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 31 Jan 2025 17:38:12 +0100 Subject: [PATCH 048/203] Clarify `used_increments` as `log_upper_bound` --- src/uint/bingcd/gcd.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index c2fe04e05..b0b2b30c1 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -81,17 +81,17 @@ impl Odd> { let b_ = b.compact::(n, K); // Compute the K-1 iteration update matrix from a_ and b_ - let (matrix, used_increments) = Uint::restricted_extended_gcd::(a_, b_, K - 1); + let (matrix, log_upper_bound) = Uint::restricted_extended_gcd::(a_, b_, K - 1); // Update `a` and `b` using the update matrix let (updated_a, updated_b) = matrix.extended_apply_to((a, b)); a = updated_a - .div_2k(used_increments) + .div_2k(log_upper_bound) .abs_drop_extension() .expect("extension is zero"); b = updated_b - .div_2k(used_increments) + .div_2k(log_upper_bound) .abs_drop_extension() .expect("extension is zero"); } From 1871093b9ea66725ffe21c6282c3cdac9fdee271 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 31 Jan 2025 22:12:15 +0100 Subject: [PATCH 049/203] Tweak small/large bingcd threshold --- src/uint/bingcd/gcd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index b0b2b30c1..ffe30809a 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -28,7 +28,7 @@ impl Odd> { #[inline(always)] pub const fn bingcd(&self, rhs: &Uint) -> Uint { // Todo: tweak this threshold - if LIMBS <= 16 { + if LIMBS < 8 { self.bingcd_small(rhs) } else { self.bingcd_large::<{ U64::BITS - 2 }, { U64::LIMBS }, { U128::LIMBS }>(rhs) From 197291445bcb9678801816b924f251b4fcac2014 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 3 Feb 2025 12:14:21 +0100 Subject: [PATCH 050/203] Refactor `IntMatrix` --- src/uint/bingcd/matrix.rs | 21 +++++++++++++-------- src/uint/bingcd/xgcd.rs | 2 +- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/uint/bingcd/matrix.rs b/src/uint/bingcd/matrix.rs index c7d64bb17..067011138 100644 --- a/src/uint/bingcd/matrix.rs +++ b/src/uint/bingcd/matrix.rs @@ -3,16 +3,21 @@ use crate::{Int, Uint}; type Vector = (T, T); -pub(crate) struct IntMatrix([[Int; DIM]; DIM]); +pub(crate) struct IntMatrix { + m00: Int, + m01: Int, + m10: Int, + m11: Int, +} -impl IntMatrix { +impl IntMatrix { pub(crate) const fn new( m00: Int, m01: Int, m10: Int, m11: Int, ) -> Self { - Self([[m00, m10], [m01, m11]]) + Self { m00, m01, m10, m11 } } /// Apply this matrix to a vector of [Uint]s, returning the result as a vector of @@ -23,10 +28,10 @@ impl IntMatrix { vec: Vector>, ) -> Vector> { let (a, b) = vec; - let a00 = ExtendedInt::from_product(a, self.0[0][0]); - let a01 = ExtendedInt::from_product(a, self.0[0][1]); - let b10 = ExtendedInt::from_product(b, self.0[1][0]); - let b11 = ExtendedInt::from_product(b, self.0[1][1]); - (a00.wrapping_add(&b10), a01.wrapping_add(&b11)) + let a0 = ExtendedInt::from_product(a, self.m00); + let a1 = ExtendedInt::from_product(a, self.m10); + let b0 = ExtendedInt::from_product(b, self.m01); + let b1 = ExtendedInt::from_product(b, self.m11); + (a0.wrapping_add(&b0), a1.wrapping_add(&b1)) } } diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index cf7018481..3d90ab24a 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -15,7 +15,7 @@ impl Uint { mut a: Uint, mut b: Uint, iterations: u32, - ) -> (IntMatrix, u32) { + ) -> (IntMatrix, u32) { debug_assert!(iterations < Uint::::BITS); // Unit matrix From ef08afc58998f8ae14da1ea2d39f83e662d810bf Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 3 Feb 2025 12:21:27 +0100 Subject: [PATCH 051/203] Clean up `restricted_extended_gcd` --- src/uint/bingcd/matrix.rs | 26 +++++++++++++++++++++++++- src/uint/bingcd/xgcd.rs | 21 +++++++-------------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/uint/bingcd/matrix.rs b/src/uint/bingcd/matrix.rs index 067011138..e8d11eeae 100644 --- a/src/uint/bingcd/matrix.rs +++ b/src/uint/bingcd/matrix.rs @@ -1,5 +1,5 @@ use crate::uint::bingcd::extension::ExtendedInt; -use crate::{Int, Uint}; +use crate::{ConstChoice, Int, Uint}; type Vector = (T, T); @@ -11,6 +11,9 @@ pub(crate) struct IntMatrix { } impl IntMatrix { + /// The unit matrix. + pub(crate) const UNIT: Self = Self::new(Int::ONE, Int::ZERO, Int::ZERO, Int::ONE); + pub(crate) const fn new( m00: Int, m01: Int, @@ -34,4 +37,25 @@ impl IntMatrix { let b1 = ExtendedInt::from_product(b, self.m11); (a0.wrapping_add(&b0), a1.wrapping_add(&b1)) } + + /// Swap the columns of this matrix if `swap` is truthy. Otherwise, do nothing. + #[inline] + pub(crate) const fn conditional_swap_columns(&mut self, swap: ConstChoice) { + Int::conditional_swap(&mut self.m00, &mut self.m10, swap); + Int::conditional_swap(&mut self.m01, &mut self.m11, swap); + } + + /// Subtract the bottom row from the top if `subtract` is truthy. Otherwise, do nothing. + #[inline] + pub(crate) const fn conditional_subtract_bottom_row_from_top(&mut self, subtract: ConstChoice) { + self.m00 = Int::select(&self.m00, &self.m00.wrapping_sub(&self.m10), subtract); + self.m01 = Int::select(&self.m01, &self.m01.wrapping_sub(&self.m11), subtract); + } + + /// Double the right column of this matrix if `double` is truthy. Otherwise, do nothing. + #[inline] + pub(crate) const fn conditional_double_right_column(&mut self, double: ConstChoice) { + self.m10 = Int::select(&self.m10, &self.m10.shl_vartime(1), double); + self.m11 = Int::select(&self.m11, &self.m11.shl_vartime(1), double); + } } diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 3d90ab24a..e9c499c6f 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -1,5 +1,5 @@ use crate::uint::bingcd::matrix::IntMatrix; -use crate::{Int, Uint}; +use crate::Uint; impl Uint { /// Constructs a matrix `M` s.t. for `(A, B) = M(a,b)` it holds that @@ -18,11 +18,8 @@ impl Uint { ) -> (IntMatrix, u32) { debug_assert!(iterations < Uint::::BITS); - // Unit matrix - let (mut f00, mut f01) = (Int::ONE, Int::ZERO); - let (mut f10, mut f11) = (Int::ZERO, Int::ONE); - // Compute the update matrix. + let mut matrix = IntMatrix::UNIT; let mut log_upper_bound = 0; let mut j = 0; while j < iterations { @@ -34,23 +31,19 @@ impl Uint { // swap if a odd and a < b let do_swap = a_odd.and(a_lt_b); Uint::conditional_swap(&mut a, &mut b, do_swap); - Int::conditional_swap(&mut f00, &mut f10, do_swap); - Int::conditional_swap(&mut f01, &mut f11, do_swap); + matrix.conditional_swap_columns(do_swap); // subtract a from b when a is odd a = Uint::select(&a, &a.wrapping_sub(&b), a_odd); - f00 = Int::select(&f00, &f00.wrapping_sub(&f10), a_odd); - f01 = Int::select(&f01, &f01.wrapping_sub(&f11), a_odd); + matrix.conditional_subtract_bottom_row_from_top(a_odd); - // mul/div by 2 when b is non-zero. - // Only apply operations when b ≠ 0, otherwise do nothing. + // Div `a` by 2 and double the right column of the matrix when b ≠ 0. let do_apply = b.is_nonzero(); a = Uint::select(&a, &a.shr_vartime(1), do_apply); - f10 = Int::select(&f10, &f10.shl_vartime(1), do_apply); - f11 = Int::select(&f11, &f11.shl_vartime(1), do_apply); + matrix.conditional_double_right_column(do_apply); log_upper_bound = do_apply.select_u32(log_upper_bound, log_upper_bound + 1); } - (IntMatrix::new(f00, f01, f10, f11), log_upper_bound) + (matrix, log_upper_bound) } } From a74442f188c3f4bbca262799f1fa0debf35221db Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 3 Feb 2025 12:43:58 +0100 Subject: [PATCH 052/203] Expand `gcd` benchmarking --- benches/uint.rs | 53 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/benches/uint.rs b/benches/uint.rs index b4c0104fb..8bd46fee9 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -5,7 +5,7 @@ use criterion::{ use crypto_bigint::modular::SafeGcdInverter; use crypto_bigint::{ Gcd, Limb, NonZero, Odd, PrecomputeInverter, Random, RandomBits, RandomMod, Reciprocal, Uint, - U1024, U128, U16384, U192, U2048, U256, U4096, U512, U64, U8192, + U1024, U128, U16384, U192, U2048, U256, U320, U384, U4096, U448, U512, U64, U8192, }; use rand_chacha::ChaCha8Rng; use rand_core::{OsRng, RngCore, SeedableRng}; @@ -312,19 +312,7 @@ fn gcd_bench( ) where Odd>: PrecomputeInverter>, { - // g.bench_function(BenchmarkId::new("gcd (vt)", LIMBS), |b| { - // b.iter_batched( - // || { - // let f = Uint::::random(&mut OsRng); - // let g = Uint::::random(&mut OsRng); - // (f, g) - // }, - // |(f, g)| black_box(Uint::gcd_vartime(&f, &g)), - // BatchSize::SmallInput, - // ) - // }); - - g.bench_function(BenchmarkId::new("gcd (ct)", LIMBS), |b| { + g.bench_function(BenchmarkId::new("gcd", LIMBS), |b| { b.iter_batched( || { let f = Uint::::random(&mut OsRng); @@ -335,8 +323,7 @@ fn gcd_bench( BatchSize::SmallInput, ) }); - - g.bench_function(BenchmarkId::new("bingcd (ct)", LIMBS), |b| { + g.bench_function(BenchmarkId::new("bingcd", LIMBS), |b| { b.iter_batched( || { let f = Uint::::random(&mut OsRng); @@ -347,6 +334,37 @@ fn gcd_bench( BatchSize::SmallInput, ) }); + + g.bench_function(BenchmarkId::new("bingcd_small", LIMBS), |b| { + b.iter_batched( + || { + let f = Uint::::random(&mut OsRng) + .bitor(&Uint::ONE) + .to_odd() + .unwrap(); + let g = Uint::::random(&mut OsRng); + (f, g) + }, + |(f, g)| black_box(f.bingcd_small(&g)), + BatchSize::SmallInput, + ) + }); + g.bench_function(BenchmarkId::new("bingcd_large", LIMBS), |b| { + b.iter_batched( + || { + let f = Uint::::random(&mut OsRng) + .bitor(&Uint::ONE) + .to_odd() + .unwrap(); + let g = Uint::::random(&mut OsRng); + (f, g) + }, + |(f, g)| { + black_box(f.bingcd_large::<{ U64::BITS - 2 }, { U64::LIMBS }, { U128::LIMBS }>(&g)) + }, + BatchSize::SmallInput, + ) + }); } fn bench_gcd(c: &mut Criterion) { @@ -356,6 +374,9 @@ fn bench_gcd(c: &mut Criterion) { gcd_bench(&mut group, U128::ZERO); gcd_bench(&mut group, U192::ZERO); gcd_bench(&mut group, U256::ZERO); + gcd_bench(&mut group, U320::ZERO); + gcd_bench(&mut group, U384::ZERO); + gcd_bench(&mut group, U448::ZERO); gcd_bench(&mut group, U512::ZERO); gcd_bench(&mut group, U1024::ZERO); gcd_bench(&mut group, U2048::ZERO); From 1a970708d5540d9e115f3df361204cbabdeaf5f4 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 3 Feb 2025 12:54:16 +0100 Subject: [PATCH 053/203] Fix issues --- benches/uint.rs | 4 ++-- src/uint/bingcd/gcd.rs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/benches/uint.rs b/benches/uint.rs index 8bd46fee9..fb7bf0e4a 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -4,8 +4,8 @@ use criterion::{ }; use crypto_bigint::modular::SafeGcdInverter; use crypto_bigint::{ - Gcd, Limb, NonZero, Odd, PrecomputeInverter, Random, RandomBits, RandomMod, Reciprocal, Uint, - U1024, U128, U16384, U192, U2048, U256, U320, U384, U4096, U448, U512, U64, U8192, + Limb, NonZero, Odd, PrecomputeInverter, Random, RandomBits, RandomMod, Reciprocal, Uint, U1024, + U128, U16384, U192, U2048, U256, U320, U384, U4096, U448, U512, U64, U8192, }; use rand_chacha::ChaCha8Rng; use rand_core::{OsRng, RngCore, SeedableRng}; diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index ffe30809a..fa0965da9 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -100,6 +100,7 @@ impl Odd> { } } +#[cfg(feature = "rand_core")] #[cfg(test)] mod tests { use crate::{Gcd, Random, Uint, U256, U512}; From 4c699d2252946d09eda41ebcffeb99f7fa0266b6 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 31 Jan 2025 13:20:00 +0100 Subject: [PATCH 054/203] Fix `IntMatrix::conditional_swap` bug --- src/uint/bingcd/matrix.rs | 34 ++++++++++++++++++++++++++++++++-- src/uint/bingcd/xgcd.rs | 2 +- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/uint/bingcd/matrix.rs b/src/uint/bingcd/matrix.rs index e8d11eeae..8742dd2a5 100644 --- a/src/uint/bingcd/matrix.rs +++ b/src/uint/bingcd/matrix.rs @@ -3,6 +3,7 @@ use crate::{ConstChoice, Int, Uint}; type Vector = (T, T); +#[derive(Debug, Clone, Copy, PartialEq)] pub(crate) struct IntMatrix { m00: Int, m01: Int, @@ -38,9 +39,9 @@ impl IntMatrix { (a0.wrapping_add(&b0), a1.wrapping_add(&b1)) } - /// Swap the columns of this matrix if `swap` is truthy. Otherwise, do nothing. + /// Swap the rows of this matrix if `swap` is truthy. Otherwise, do nothing. #[inline] - pub(crate) const fn conditional_swap_columns(&mut self, swap: ConstChoice) { + pub(crate) const fn conditional_swap_rows(&mut self, swap: ConstChoice) { Int::conditional_swap(&mut self.m00, &mut self.m10, swap); Int::conditional_swap(&mut self.m01, &mut self.m11, swap); } @@ -59,3 +60,32 @@ impl IntMatrix { self.m11 = Int::select(&self.m11, &self.m11.shl_vartime(1), double); } } + +#[cfg(test)] +mod tests { + use crate::uint::bingcd::matrix::IntMatrix; + use crate::{ConstChoice, Int}; + + #[test] + fn test_conditional_swap() { + let x = IntMatrix::<2>::new( + Int::from(1i32), + Int::from(2i32), + Int::from(3i32), + Int::from(4i32), + ); + let mut y = x.clone(); + y.conditional_swap_rows(ConstChoice::FALSE); + assert_eq!(y, x); + y.conditional_swap_rows(ConstChoice::TRUE); + assert_eq!( + y, + IntMatrix::new( + Int::from(3i32), + Int::from(4i32), + Int::from(1i32), + Int::from(2i32) + ) + ); + } +} diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index e9c499c6f..eb3812b87 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -31,7 +31,7 @@ impl Uint { // swap if a odd and a < b let do_swap = a_odd.and(a_lt_b); Uint::conditional_swap(&mut a, &mut b, do_swap); - matrix.conditional_swap_columns(do_swap); + matrix.conditional_swap_rows(do_swap); // subtract a from b when a is odd a = Uint::select(&a, &a.wrapping_sub(&b), a_odd); From 2fee828bb1fdded26e711e757e3fa126b7c8f873 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 3 Feb 2025 15:05:49 +0100 Subject: [PATCH 055/203] Fix `IntMatrix::conditional_double` bug --- src/uint/bingcd/matrix.rs | 61 ++++++++++++++++++++++++++++++--------- src/uint/bingcd/xgcd.rs | 2 +- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/uint/bingcd/matrix.rs b/src/uint/bingcd/matrix.rs index 8742dd2a5..25c8a75f5 100644 --- a/src/uint/bingcd/matrix.rs +++ b/src/uint/bingcd/matrix.rs @@ -55,7 +55,7 @@ impl IntMatrix { /// Double the right column of this matrix if `double` is truthy. Otherwise, do nothing. #[inline] - pub(crate) const fn conditional_double_right_column(&mut self, double: ConstChoice) { + pub(crate) const fn conditional_double_bottom_row(&mut self, double: ConstChoice) { self.m10 = Int::select(&self.m10, &self.m10.shl_vartime(1), double); self.m11 = Int::select(&self.m11, &self.m11.shl_vartime(1), double); } @@ -64,27 +64,62 @@ impl IntMatrix { #[cfg(test)] mod tests { use crate::uint::bingcd::matrix::IntMatrix; - use crate::{ConstChoice, Int}; + use crate::{ConstChoice, Int, U256}; + + const X: IntMatrix<{ U256::LIMBS }> = IntMatrix::new( + Int::from_i64(1i64), + Int::from_i64(7i64), + Int::from_i64(23i64), + Int::from_i64(53i64), + ); #[test] fn test_conditional_swap() { - let x = IntMatrix::<2>::new( - Int::from(1i32), - Int::from(2i32), - Int::from(3i32), - Int::from(4i32), - ); - let mut y = x.clone(); + let mut y = X.clone(); y.conditional_swap_rows(ConstChoice::FALSE); - assert_eq!(y, x); + assert_eq!(y, X); y.conditional_swap_rows(ConstChoice::TRUE); assert_eq!( y, IntMatrix::new( - Int::from(3i32), - Int::from(4i32), + Int::from(23i32), + Int::from(53i32), + Int::from(1i32), + Int::from(7i32) + ) + ); + } + + #[test] + fn test_conditional_subtract() { + let mut y = X.clone(); + y.conditional_subtract_bottom_row_from_top(ConstChoice::FALSE); + assert_eq!(y, X); + y.conditional_subtract_bottom_row_from_top(ConstChoice::TRUE); + assert_eq!( + y, + IntMatrix::new( + Int::from(-22i32), + Int::from(-46i32), + Int::from(23i32), + Int::from(53i32) + ) + ); + } + + #[test] + fn test_conditional_double() { + let mut y = X.clone(); + y.conditional_double_bottom_row(ConstChoice::FALSE); + assert_eq!(y, X); + y.conditional_double_bottom_row(ConstChoice::TRUE); + assert_eq!( + y, + IntMatrix::new( Int::from(1i32), - Int::from(2i32) + Int::from(7i32), + Int::from(46i32), + Int::from(106i32), ) ); } diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index eb3812b87..b44ca679c 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -40,7 +40,7 @@ impl Uint { // Div `a` by 2 and double the right column of the matrix when b ≠ 0. let do_apply = b.is_nonzero(); a = Uint::select(&a, &a.shr_vartime(1), do_apply); - matrix.conditional_double_right_column(do_apply); + matrix.conditional_double_bottom_row(do_apply); log_upper_bound = do_apply.select_u32(log_upper_bound, log_upper_bound + 1); } From 9781b87ed5061179c911b8e26530ac107a826666 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 3 Feb 2025 15:49:41 +0100 Subject: [PATCH 056/203] Fix `restricted_extended_gcd` bug --- src/uint/bingcd/xgcd.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index b44ca679c..865b4536d 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -37,8 +37,8 @@ impl Uint { a = Uint::select(&a, &a.wrapping_sub(&b), a_odd); matrix.conditional_subtract_bottom_row_from_top(a_odd); - // Div `a` by 2 and double the right column of the matrix when b ≠ 0. - let do_apply = b.is_nonzero(); + // Div `a` by 2 and double the right column of the matrix when both a ≠ 0 and b ≠ 0. + let do_apply = a.is_nonzero().and(b.is_nonzero()); a = Uint::select(&a, &a.shr_vartime(1), do_apply); matrix.conditional_double_bottom_row(do_apply); log_upper_bound = do_apply.select_u32(log_upper_bound, log_upper_bound + 1); @@ -47,3 +47,30 @@ impl Uint { (matrix, log_upper_bound) } } + +#[cfg(test)] +mod tests { + use crate::uint::bingcd::matrix::IntMatrix; + use crate::{Uint, I64, U64}; + + #[test] + fn test_restricted_extended_gcd() { + let a = U64::from_be_hex("AE693BF7BE8E5566"); + let b = U64::from_be_hex("CA048AFA63CD6A1F"); + let (matrix, iters) = Uint::restricted_extended_gcd(a, b, 5); + assert_eq!(iters, 5); + assert_eq!( + matrix, + IntMatrix::new(I64::from(5), I64::from(-2), I64::from(-4), I64::from(8)) + ); + } + + #[test] + fn test_restricted_extended_gcd_stops_early() { + // Stop before max_iters + let a = U64::from_be_hex("000000000E8E5566"); + let b = U64::from_be_hex("0000000003CD6A1F"); + let (.., iters) = Uint::restricted_extended_gcd::<{I64::LIMBS}>(a, b, 60); + assert_eq!(iters, 35); + } +} From 39a4e88c9f71d1401a599efc9ffff55c70e3e2ea Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 3 Feb 2025 15:58:56 +0100 Subject: [PATCH 057/203] Align gcd return values with their type --- src/uint/bingcd.rs | 2 +- src/uint/bingcd/gcd.rs | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index dcb1de7d6..d4d115f24 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -18,7 +18,7 @@ impl Uint { let self_nz = Uint::select(self, &Uint::ONE, self_is_zero) .to_nz() .expect("self is non zero by construction"); - Uint::select(&self_nz.bingcd(rhs), rhs, self_is_zero) + Uint::select(self_nz.bingcd(rhs).as_ref(), rhs, self_is_zero) } } diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index fa0965da9..9de2f55d2 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -3,7 +3,7 @@ use crate::{NonZero, Odd, Uint, U128, U64}; impl NonZero> { /// Compute the greatest common divisor of `self` and `rhs`. - pub const fn bingcd(&self, rhs: &Uint) -> Uint { + pub const fn bingcd(&self, rhs: &Uint) -> Self { let val = self.as_ref(); // Leverage two GCD identity rules to make self and rhs odd. // 1) gcd(2a, 2b) = 2 * gcd(a, b) @@ -12,12 +12,14 @@ impl NonZero> { let j = rhs.is_nonzero().select_u32(0, rhs.trailing_zeros()); let k = const_min(i, j); - self.as_ref() - .shr(i) + val.shr(i) .to_odd() .expect("self is odd by construction") .bingcd(rhs) + .as_ref() .shl(k) + .to_nz() + .expect("gcd of non-zero element with zero is non-zero") } } @@ -26,7 +28,7 @@ impl Odd> { /// Compute the greatest common divisor of `self` and `rhs`. #[inline(always)] - pub const fn bingcd(&self, rhs: &Uint) -> Uint { + pub const fn bingcd(&self, rhs: &Uint) -> Self { // Todo: tweak this threshold if LIMBS < 8 { self.bingcd_small(rhs) @@ -38,7 +40,7 @@ impl Odd> { /// Computes `gcd(self, rhs)`, leveraging the Binary GCD algorithm. /// Is efficient only for relatively small `LIMBS`. #[inline] - pub const fn bingcd_small(&self, rhs: &Uint) -> Uint { + pub const fn bingcd_small(&self, rhs: &Uint) -> Self { let (mut a, mut b) = (*rhs, *self.as_ref()); let mut j = 0; while j < (2 * Self::BITS - 1) { @@ -59,7 +61,7 @@ impl Odd> { a = Uint::select(&a, &a.shr_vartime(1), do_apply); } - b + b.to_odd().expect("gcd of an odd value with something else is always odd") } /// Computes `gcd(self, rhs)`, leveraging the Binary GCD algorithm. @@ -68,7 +70,7 @@ impl Odd> { pub const fn bingcd_large( &self, rhs: &Uint, - ) -> Uint { + ) -> Self { let (mut a, mut b) = (*rhs, *self.as_ref()); let mut i = 0; @@ -96,7 +98,7 @@ impl Odd> { .expect("extension is zero"); } - b + b.to_odd().expect("gcd of an odd value with something else is always odd") } } From 08974398a8b7d0073b4295ee1a23b901457b89df Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 3 Feb 2025 17:40:02 +0100 Subject: [PATCH 058/203] Remove sneaky swap operation --- src/uint/bingcd/gcd.rs | 33 ++++++++++++++++------------ src/uint/bingcd/matrix.rs | 36 +++++++++++++++---------------- src/uint/bingcd/xgcd.rs | 45 ++++++++++++++++++++------------------- 3 files changed, 60 insertions(+), 54 deletions(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index 9de2f55d2..5dc2a076a 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -41,27 +41,28 @@ impl Odd> { /// Is efficient only for relatively small `LIMBS`. #[inline] pub const fn bingcd_small(&self, rhs: &Uint) -> Self { - let (mut a, mut b) = (*rhs, *self.as_ref()); + let (mut a, mut b) = (*self.as_ref(), *rhs); let mut j = 0; while j < (2 * Self::BITS - 1) { j += 1; - let a_odd = a.is_odd(); + let b_odd = b.is_odd(); - // swap if a odd and a < b - let a_lt_b = Uint::lt(&a, &b); - let do_swap = a_odd.and(a_lt_b); + // swap if b odd and a > b + let a_gt_b = Uint::gt(&a, &b); + let do_swap = b_odd.and(a_gt_b); Uint::conditional_swap(&mut a, &mut b, do_swap); - // subtract b from a when a is odd - a = Uint::select(&a, &a.wrapping_sub(&b), a_odd); + // subtract a from b when b is odd + b = Uint::select(&b, &b.wrapping_sub(&a), b_odd); - // Div a by two when b ≠ 0, otherwise do nothing. - let do_apply = b.is_nonzero(); - a = Uint::select(&a, &a.shr_vartime(1), do_apply); + // Div b by two when a ≠ 0, otherwise do nothing. + let do_apply = a.is_nonzero(); + b = Uint::select(&b, &b.shr_vartime(1), do_apply); } - b.to_odd().expect("gcd of an odd value with something else is always odd") + a.to_odd() + .expect("gcd of an odd value with something else is always odd") } /// Computes `gcd(self, rhs)`, leveraging the Binary GCD algorithm. @@ -71,7 +72,7 @@ impl Odd> { &self, rhs: &Uint, ) -> Self { - let (mut a, mut b) = (*rhs, *self.as_ref()); + let (mut a, mut b) = (*self.as_ref(), *rhs); let mut i = 0; while i < (2 * Self::BITS - 1).div_ceil(K) { @@ -83,7 +84,10 @@ impl Odd> { let b_ = b.compact::(n, K); // Compute the K-1 iteration update matrix from a_ and b_ - let (matrix, log_upper_bound) = Uint::restricted_extended_gcd::(a_, b_, K - 1); + let (matrix, log_upper_bound) = a_ + .to_odd() + .expect("a is always odd") + .restricted_extended_gcd::(&b_, K - 1); // Update `a` and `b` using the update matrix let (updated_a, updated_b) = matrix.extended_apply_to((a, b)); @@ -98,7 +102,8 @@ impl Odd> { .expect("extension is zero"); } - b.to_odd().expect("gcd of an odd value with something else is always odd") + a.to_odd() + .expect("gcd of an odd value with something else is always odd") } } diff --git a/src/uint/bingcd/matrix.rs b/src/uint/bingcd/matrix.rs index 25c8a75f5..0b622e49d 100644 --- a/src/uint/bingcd/matrix.rs +++ b/src/uint/bingcd/matrix.rs @@ -48,16 +48,16 @@ impl IntMatrix { /// Subtract the bottom row from the top if `subtract` is truthy. Otherwise, do nothing. #[inline] - pub(crate) const fn conditional_subtract_bottom_row_from_top(&mut self, subtract: ConstChoice) { - self.m00 = Int::select(&self.m00, &self.m00.wrapping_sub(&self.m10), subtract); - self.m01 = Int::select(&self.m01, &self.m01.wrapping_sub(&self.m11), subtract); + pub(crate) const fn conditional_subtract_top_row_from_bottom(&mut self, subtract: ConstChoice) { + self.m10 = Int::select(&self.m10, &self.m10.wrapping_sub(&self.m00), subtract); + self.m11 = Int::select(&self.m11, &self.m11.wrapping_sub(&self.m01), subtract); } /// Double the right column of this matrix if `double` is truthy. Otherwise, do nothing. #[inline] - pub(crate) const fn conditional_double_bottom_row(&mut self, double: ConstChoice) { - self.m10 = Int::select(&self.m10, &self.m10.shl_vartime(1), double); - self.m11 = Int::select(&self.m11, &self.m11.shl_vartime(1), double); + pub(crate) const fn conditional_double_top_row(&mut self, double: ConstChoice) { + self.m00 = Int::select(&self.m00, &self.m00.shl_vartime(1), double); + self.m01 = Int::select(&self.m01, &self.m01.shl_vartime(1), double); } } @@ -93,16 +93,16 @@ mod tests { #[test] fn test_conditional_subtract() { let mut y = X.clone(); - y.conditional_subtract_bottom_row_from_top(ConstChoice::FALSE); + y.conditional_subtract_top_row_from_bottom(ConstChoice::FALSE); assert_eq!(y, X); - y.conditional_subtract_bottom_row_from_top(ConstChoice::TRUE); + y.conditional_subtract_top_row_from_bottom(ConstChoice::TRUE); assert_eq!( y, IntMatrix::new( - Int::from(-22i32), - Int::from(-46i32), - Int::from(23i32), - Int::from(53i32) + Int::from(1i32), + Int::from(7i32), + Int::from(22i32), + Int::from(46i32) ) ); } @@ -110,16 +110,16 @@ mod tests { #[test] fn test_conditional_double() { let mut y = X.clone(); - y.conditional_double_bottom_row(ConstChoice::FALSE); + y.conditional_double_top_row(ConstChoice::FALSE); assert_eq!(y, X); - y.conditional_double_bottom_row(ConstChoice::TRUE); + y.conditional_double_top_row(ConstChoice::TRUE); assert_eq!( y, IntMatrix::new( - Int::from(1i32), - Int::from(7i32), - Int::from(46i32), - Int::from(106i32), + Int::from(2i32), + Int::from(14i32), + Int::from(23i32), + Int::from(53i32), ) ); } diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 865b4536d..c9abb489d 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -1,7 +1,7 @@ use crate::uint::bingcd::matrix::IntMatrix; -use crate::Uint; +use crate::{Odd, Uint}; -impl Uint { +impl Odd> { /// Constructs a matrix `M` s.t. for `(A, B) = M(a,b)` it holds that /// - `gcd(A, B) = gcd(a, b)`, and /// - `A.bits() < a.bits()` and/or `B.bits() < b.bits()`. @@ -12,11 +12,12 @@ impl Uint { /// Assumes `iterations < Uint::::BITS`. #[inline] pub(super) const fn restricted_extended_gcd( - mut a: Uint, - mut b: Uint, + &self, + rhs: &Uint, iterations: u32, ) -> (IntMatrix, u32) { debug_assert!(iterations < Uint::::BITS); + let (mut a, mut b) = (*self.as_ref(), *rhs); // Compute the update matrix. let mut matrix = IntMatrix::UNIT; @@ -25,22 +26,22 @@ impl Uint { while j < iterations { j += 1; - let a_odd = a.is_odd(); - let a_lt_b = Uint::lt(&a, &b); + let b_odd = b.is_odd(); + let a_gt_b = Uint::gt(&a, &b); - // swap if a odd and a < b - let do_swap = a_odd.and(a_lt_b); + // swap if b odd and a > b + let do_swap = b_odd.and(a_gt_b); Uint::conditional_swap(&mut a, &mut b, do_swap); matrix.conditional_swap_rows(do_swap); - // subtract a from b when a is odd - a = Uint::select(&a, &a.wrapping_sub(&b), a_odd); - matrix.conditional_subtract_bottom_row_from_top(a_odd); + // subtract a from b when b is odd + b = Uint::select(&b, &b.wrapping_sub(&a), b_odd); + matrix.conditional_subtract_top_row_from_bottom(b_odd); - // Div `a` by 2 and double the right column of the matrix when both a ≠ 0 and b ≠ 0. + // Div b by two and double the top row of the matrix when a, b ≠ 0. let do_apply = a.is_nonzero().and(b.is_nonzero()); - a = Uint::select(&a, &a.shr_vartime(1), do_apply); - matrix.conditional_double_bottom_row(do_apply); + b = Uint::select(&b, &b.shr_vartime(1), do_apply); + matrix.conditional_double_top_row(do_apply); log_upper_bound = do_apply.select_u32(log_upper_bound, log_upper_bound + 1); } @@ -51,26 +52,26 @@ impl Uint { #[cfg(test)] mod tests { use crate::uint::bingcd::matrix::IntMatrix; - use crate::{Uint, I64, U64}; + use crate::{I64, U64}; #[test] fn test_restricted_extended_gcd() { - let a = U64::from_be_hex("AE693BF7BE8E5566"); - let b = U64::from_be_hex("CA048AFA63CD6A1F"); - let (matrix, iters) = Uint::restricted_extended_gcd(a, b, 5); + let a = U64::from_be_hex("CA048AFA63CD6A1F").to_odd().unwrap(); + let b = U64::from_be_hex("AE693BF7BE8E5566"); + let (matrix, iters) = a.restricted_extended_gcd(&b, 5); assert_eq!(iters, 5); assert_eq!( matrix, - IntMatrix::new(I64::from(5), I64::from(-2), I64::from(-4), I64::from(8)) + IntMatrix::new(I64::from(8), I64::from(-4), I64::from(-2), I64::from(5)) ); } #[test] fn test_restricted_extended_gcd_stops_early() { // Stop before max_iters - let a = U64::from_be_hex("000000000E8E5566"); - let b = U64::from_be_hex("0000000003CD6A1F"); - let (.., iters) = Uint::restricted_extended_gcd::<{I64::LIMBS}>(a, b, 60); + let a = U64::from_be_hex("0000000003CD6A1F").to_odd().unwrap(); + let b = U64::from_be_hex("000000000E8E5566"); + let (.., iters) = a.restricted_extended_gcd::<{ U64::LIMBS }>(&b, 60); assert_eq!(iters, 35); } } From 4007347c1e0b2ec1572832d3e577bde20ae8f984 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 4 Feb 2025 11:43:27 +0100 Subject: [PATCH 059/203] Expand bingcd testing --- src/uint/bingcd/gcd.rs | 107 ++++++++++++++++++++++++++++++++++------- 1 file changed, 90 insertions(+), 17 deletions(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index 5dc2a076a..7161311a1 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -110,28 +110,101 @@ impl Odd> { #[cfg(feature = "rand_core")] #[cfg(test)] mod tests { - use crate::{Gcd, Random, Uint, U256, U512}; - use rand_core::OsRng; - fn test_bingcd_small() - where - Uint: Gcd>, - { - for _ in 0..100 { - let x = Uint::::random(&mut OsRng); - let mut y = Uint::::random(&mut OsRng); + mod test_bingcd_small { + use crate::{Gcd, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64}; + use rand_core::OsRng; - y = Uint::select(&(y.wrapping_add(&Uint::ONE)), &y, y.is_odd()); - - let gcd = x.gcd(&y); - let bingcd = y.to_odd().unwrap().bingcd(&x); + fn bingcd_small_test(lhs: Uint, rhs: Uint) + where + Uint: Gcd>, + { + let gcd = lhs.gcd(&rhs); + let bingcd = lhs.to_odd().unwrap().bingcd_small(&rhs); assert_eq!(gcd, bingcd); } + + fn bingcd_small_tests() + where + Uint: Gcd>, + { + // Edge cases + bingcd_small_test(Uint::ONE, Uint::ZERO); + bingcd_small_test(Uint::ONE, Uint::ONE); + bingcd_small_test(Uint::ONE, Uint::MAX); + bingcd_small_test(Uint::MAX, Uint::ZERO); + bingcd_small_test(Uint::MAX, Uint::ONE); + bingcd_small_test(Uint::MAX, Uint::MAX); + + // Randomized test cases + for _ in 0..100 { + let x = Uint::::random(&mut OsRng).bitor(&Uint::ONE); + let y = Uint::::random(&mut OsRng); + bingcd_small_test(x, y); + } + } + + #[test] + fn test_bingcd_small() { + bingcd_small_tests::<{ U64::LIMBS }>(); + bingcd_small_tests::<{ U128::LIMBS }>(); + bingcd_small_tests::<{ U192::LIMBS }>(); + bingcd_small_tests::<{ U256::LIMBS }>(); + bingcd_small_tests::<{ U384::LIMBS }>(); + bingcd_small_tests::<{ U512::LIMBS }>(); + bingcd_small_tests::<{ U1024::LIMBS }>(); + bingcd_small_tests::<{ U2048::LIMBS }>(); + bingcd_small_tests::<{ U4096::LIMBS }>(); + } } - #[test] - fn testing_bingcd_small() { - test_bingcd_small::<{ U256::LIMBS }>(); - test_bingcd_small::<{ U512::LIMBS }>(); + mod test_bingcd_large { + use crate::{Gcd, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64}; + use rand_core::OsRng; + + fn bingcd_large_test(lhs: Uint, rhs: Uint) + where + Uint: Gcd>, + { + let gcd = lhs.gcd(&rhs); + let bingcd = lhs + .to_odd() + .unwrap() + .bingcd_large::<62, { U64::LIMBS }, { U128::LIMBS }>(&rhs); + assert_eq!(gcd, bingcd); + } + + fn bingcd_large_tests() + where + Uint: Gcd>, + { + // Edge cases + bingcd_large_test(Uint::ONE, Uint::ZERO); + bingcd_large_test(Uint::ONE, Uint::ONE); + bingcd_large_test(Uint::ONE, Uint::MAX); + bingcd_large_test(Uint::MAX, Uint::ZERO); + bingcd_large_test(Uint::MAX, Uint::ONE); + bingcd_large_test(Uint::MAX, Uint::MAX); + + // Randomized testing + for _ in 0..100 { + let x = Uint::::random(&mut OsRng).bitor(&Uint::ONE); + let y = Uint::::random(&mut OsRng); + bingcd_large_test(x, y); + } + } + + #[test] + fn test_bingcd_large() { + // Not applicable for U64 + bingcd_large_tests::<{ U128::LIMBS }>(); + bingcd_large_tests::<{ U192::LIMBS }>(); + bingcd_large_tests::<{ U256::LIMBS }>(); + bingcd_large_tests::<{ U384::LIMBS }>(); + bingcd_large_tests::<{ U512::LIMBS }>(); + bingcd_large_tests::<{ U1024::LIMBS }>(); + bingcd_large_tests::<{ U2048::LIMBS }>(); + bingcd_large_tests::<{ U4096::LIMBS }>(); + } } } From 3f282580733dbf3a6ecf69f8c4087218657b133a Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 4 Feb 2025 11:59:57 +0100 Subject: [PATCH 060/203] Refactor bingcd test suite --- src/uint/bingcd.rs | 48 ++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index d4d115f24..574da3eb9 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -29,7 +29,7 @@ mod tests { use crate::{Gcd, Random, Uint, U1024, U16384, U2048, U256, U4096, U512, U8192}; - fn gcd_comparison_test(lhs: Uint, rhs: Uint) + fn bingcd_test(lhs: Uint, rhs: Uint) where Uint: Gcd>, { @@ -38,39 +38,37 @@ mod tests { assert_eq!(gcd, bingcd); } - fn test_bingcd() + fn bingcd_tests() where Uint: Gcd>, { - // some basic test - gcd_comparison_test(Uint::ZERO, Uint::ZERO); - gcd_comparison_test(Uint::ZERO, Uint::ONE); - gcd_comparison_test(Uint::ZERO, Uint::MAX); - gcd_comparison_test(Uint::ONE, Uint::ZERO); - gcd_comparison_test(Uint::ONE, Uint::ONE); - gcd_comparison_test(Uint::ONE, Uint::MAX); - gcd_comparison_test(Uint::MAX, Uint::ZERO); - gcd_comparison_test(Uint::MAX, Uint::ONE); - gcd_comparison_test(Uint::MAX, Uint::MAX); + // Edge cases + bingcd_test(Uint::ZERO, Uint::ZERO); + bingcd_test(Uint::ZERO, Uint::ONE); + bingcd_test(Uint::ZERO, Uint::MAX); + bingcd_test(Uint::ONE, Uint::ZERO); + bingcd_test(Uint::ONE, Uint::ONE); + bingcd_test(Uint::ONE, Uint::MAX); + bingcd_test(Uint::MAX, Uint::ZERO); + bingcd_test(Uint::MAX, Uint::ONE); + bingcd_test(Uint::MAX, Uint::MAX); + // Randomized test cases for _ in 0..100 { let x = Uint::::random(&mut OsRng); - let mut y = Uint::::random(&mut OsRng); - - y = Uint::select(&(y.wrapping_add(&Uint::ONE)), &y, y.is_odd()); - - gcd_comparison_test(x, y); + let y = Uint::::random(&mut OsRng); + bingcd_test(x, y); } } #[test] - fn testing() { - test_bingcd::<{ U256::LIMBS }>(); - test_bingcd::<{ U512::LIMBS }>(); - test_bingcd::<{ U1024::LIMBS }>(); - test_bingcd::<{ U2048::LIMBS }>(); - test_bingcd::<{ U4096::LIMBS }>(); - test_bingcd::<{ U8192::LIMBS }>(); - test_bingcd::<{ U16384::LIMBS }>(); + fn test_bingcd() { + bingcd_tests::<{ U256::LIMBS }>(); + bingcd_tests::<{ U512::LIMBS }>(); + bingcd_tests::<{ U1024::LIMBS }>(); + bingcd_tests::<{ U2048::LIMBS }>(); + bingcd_tests::<{ U4096::LIMBS }>(); + bingcd_tests::<{ U8192::LIMBS }>(); + bingcd_tests::<{ U16384::LIMBS }>(); } } From 76f2e003d49d0262c0b5d51d69892eee970701e6 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 31 Jan 2025 13:16:02 +0100 Subject: [PATCH 061/203] Introduce const `Int::checked_mul` --- src/int/mul.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/int/mul.rs b/src/int/mul.rs index 315564c4b..5143b7340 100644 --- a/src/int/mul.rs +++ b/src/int/mul.rs @@ -4,7 +4,7 @@ use core::ops::{Mul, MulAssign}; use subtle::CtOption; -use crate::{Checked, CheckedMul, ConcatMixed, ConstChoice, ConstCtOption, Int, Uint, Zero}; +use crate::{Checked, CheckedMul, ConcatMixed, ConstChoice, ConstCtOption, Int, Uint}; impl Int { /// Compute "wide" multiplication as a 3-tuple `(lo, hi, negate)`. @@ -49,6 +49,16 @@ impl Int { // always fits Int::from_bits(product_abs.wrapping_neg_if(product_sign)) } + + /// Multiply `self` with `rhs`, returning a [ConstCtOption] that `is_some` only if the result + /// fits in an `Int`. + pub(crate) const fn const_checked_mul( + &self, + rhs: &Int, + ) -> ConstCtOption> { + let (lo, hi, is_negative) = self.split_mul(rhs); + Self::new_from_abs_sign(lo, is_negative).and_choice(hi.is_nonzero().not()) + } } /// Squaring operations. @@ -80,9 +90,7 @@ impl Int { impl CheckedMul> for Int { #[inline] fn checked_mul(&self, rhs: &Int) -> CtOption { - let (lo, hi, is_negative) = self.split_mul(rhs); - let val = Self::new_from_abs_sign(lo, is_negative); - CtOption::from(val).and_then(|int| CtOption::new(int, hi.is_zero())) + Self::const_checked_mul(self, rhs).into() } } @@ -114,7 +122,7 @@ impl Mul<&Int> for &Int; fn mul(self, rhs: &Int) -> Self::Output { - self.checked_mul(rhs) + self.const_checked_mul(rhs) .expect("attempted to multiply with overflow") } } From 29a11af97ca18792c0a88fc74e171ccba0601d39 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 31 Jan 2025 13:20:29 +0100 Subject: [PATCH 062/203] Implement `Matrix::checked_mul` --- src/uint/bingcd/matrix.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/uint/bingcd/matrix.rs b/src/uint/bingcd/matrix.rs index 0b622e49d..0b0e05bd3 100644 --- a/src/uint/bingcd/matrix.rs +++ b/src/uint/bingcd/matrix.rs @@ -39,6 +39,25 @@ impl IntMatrix { (a0.wrapping_add(&b0), a1.wrapping_add(&b1)) } + /// Apply this matrix to `rhs`. Panics if a multiplication overflows. + /// TODO: consider implementing (a variation to) Strassen. Doing so will save a multiplication. + #[inline] + const fn checked_mul(&self, rhs: &IntMatrix) -> Self { + let a0 = self.m00.const_checked_mul(&rhs.m00).expect("no overflow"); + let a1 = self.m01.const_checked_mul(&rhs.m10).expect("no overflow"); + let a = a0.checked_add(&a1).expect("no overflow"); + let b0 = self.m00.const_checked_mul(&rhs.m01).expect("no overflow"); + let b1 = self.m01.const_checked_mul(&rhs.m11).expect("no overflow"); + let b = b0.checked_add(&b1).expect("no overflow"); + let c0 = self.m10.const_checked_mul(&rhs.m00).expect("no overflow"); + let c1 = self.m11.const_checked_mul(&rhs.m10).expect("no overflow"); + let c = c0.checked_add(&c1).expect("no overflow"); + let d0 = self.m10.const_checked_mul(&rhs.m01).expect("no overflow"); + let d1 = self.m11.const_checked_mul(&rhs.m11).expect("no overflow"); + let d = d0.checked_add(&d1).expect("no overflow"); + IntMatrix::new(a, b, c, d) + } + /// Swap the rows of this matrix if `swap` is truthy. Otherwise, do nothing. #[inline] pub(crate) const fn conditional_swap_rows(&mut self, swap: ConstChoice) { @@ -64,7 +83,7 @@ impl IntMatrix { #[cfg(test)] mod tests { use crate::uint::bingcd::matrix::IntMatrix; - use crate::{ConstChoice, Int, U256}; + use crate::{ConstChoice, I256, Int, U256}; const X: IntMatrix<{ U256::LIMBS }> = IntMatrix::new( Int::from_i64(1i64), @@ -123,4 +142,15 @@ mod tests { ) ); } + + #[test] + fn test_checked_mul() { + let res = X.checked_mul(&X); + assert_eq!(res, IntMatrix::new( + I256::from_i64(162i64), + I256::from_i64(378i64), + I256::from_i64(1242i64), + I256::from_i64(2970i64), + )) + } } From 001532918f408e4b970c2f7edc9d74e8d608a776 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 4 Feb 2025 12:31:23 +0100 Subject: [PATCH 063/203] Rename `restricted_extended_gcd` as `partial_binxgcd` --- src/uint/bingcd/gcd.rs | 4 +-- src/uint/bingcd/xgcd.rs | 61 +++++++++++++++++++++++------------------ 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index 7161311a1..c48a424d7 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -84,10 +84,10 @@ impl Odd> { let b_ = b.compact::(n, K); // Compute the K-1 iteration update matrix from a_ and b_ - let (matrix, log_upper_bound) = a_ + let (.., matrix, log_upper_bound) = a_ .to_odd() .expect("a is always odd") - .restricted_extended_gcd::(&b_, K - 1); + .partial_binxgcd::(&b_, K - 1); // Update `a` and `b` using the update matrix let (updated_a, updated_b) = matrix.extended_apply_to((a, b)); diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index c9abb489d..7e7a17488 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -2,20 +2,26 @@ use crate::uint::bingcd::matrix::IntMatrix; use crate::{Odd, Uint}; impl Odd> { - /// Constructs a matrix `M` s.t. for `(A, B) = M(a,b)` it holds that - /// - `gcd(A, B) = gcd(a, b)`, and - /// - `A.bits() < a.bits()` and/or `B.bits() < b.bits()`. + /// Executes `iterations` reduction steps of the Binary Extended GCD algorithm to reduce + /// `(self, rhs)` towards their GCD. Note: once the gcd is found, the extra iterations are + /// performed. However, the algorithm has been constructed that additional iterations have no + /// effect on the output of the function. Returns the (partially reduced) `(self*, rhs*)`. + /// If `rhs* = 0`, `self*` contains the `gcd(self, rhs)`. /// - /// Moreover, it returns `log_upper_bound: u32` s.t. each element in `M` lies in the interval + /// Additionally, the matrix `M` is constructed s.t. `M * (self, rhs) = (self*, rhs*)`. + /// This matrix contains the Bézout coefficients in its top left and bottom right corners. + /// + /// Lastly, returns `log_upper_bound`. Each element in `M` lies in the interval /// `(-2^log_upper_bound, 2^log_upper_bound]`. /// - /// Assumes `iterations < Uint::::BITS`. + /// Requires `iterations < Uint::::BITS` to prevent the bezout coefficients from + /// overflowing. #[inline] - pub(super) const fn restricted_extended_gcd( + pub(super) const fn partial_binxgcd( &self, rhs: &Uint, iterations: u32, - ) -> (IntMatrix, u32) { + ) -> (Self, Uint, IntMatrix, u32) { debug_assert!(iterations < Uint::::BITS); let (mut a, mut b) = (*self.as_ref(), *rhs); @@ -45,33 +51,36 @@ impl Odd> { log_upper_bound = do_apply.select_u32(log_upper_bound, log_upper_bound + 1); } - (matrix, log_upper_bound) + (a.to_odd().expect("a is always odd"), b, matrix, log_upper_bound) } } #[cfg(test)] mod tests { + mod test_partial_binxgcd { use crate::uint::bingcd::matrix::IntMatrix; use crate::{I64, U64}; - #[test] - fn test_restricted_extended_gcd() { - let a = U64::from_be_hex("CA048AFA63CD6A1F").to_odd().unwrap(); - let b = U64::from_be_hex("AE693BF7BE8E5566"); - let (matrix, iters) = a.restricted_extended_gcd(&b, 5); - assert_eq!(iters, 5); - assert_eq!( - matrix, - IntMatrix::new(I64::from(8), I64::from(-4), I64::from(-2), I64::from(5)) - ); - } + #[test] + fn test_partial_binxgcd() { + let a = U64::from_be_hex("CA048AFA63CD6A1F").to_odd().unwrap(); + let b = U64::from_be_hex("AE693BF7BE8E5566"); + let (.., matrix, iters) = a.partial_binxgcd(&b, 5); + assert_eq!(iters, 5); + assert_eq!( + matrix, + IntMatrix::new(I64::from(8), I64::from(-4), I64::from(-2), I64::from(5)) + ); + } - #[test] - fn test_restricted_extended_gcd_stops_early() { - // Stop before max_iters - let a = U64::from_be_hex("0000000003CD6A1F").to_odd().unwrap(); - let b = U64::from_be_hex("000000000E8E5566"); - let (.., iters) = a.restricted_extended_gcd::<{ U64::LIMBS }>(&b, 60); - assert_eq!(iters, 35); + #[test] + fn test_partial_binxgcd_stops_early() { + // Stop before max_iters + let a = U64::from_be_hex("0000000003CD6A1F").to_odd().unwrap(); + let b = U64::from_be_hex("000000000E8E5566"); + let (gcd, .., iters) = a.partial_binxgcd::<{ U64::LIMBS }>(&b, 60); + assert_eq!(iters, 35); + assert_eq!(gcd.get(), a.gcd(&b)); + } } } From 2b7f5d3c108b32df4794a5064d40492ea1c71436 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 4 Feb 2025 12:32:18 +0100 Subject: [PATCH 064/203] Fix fmt --- src/uint/bingcd/matrix.rs | 17 ++++++++++------- src/uint/bingcd/xgcd.rs | 12 +++++++++--- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/uint/bingcd/matrix.rs b/src/uint/bingcd/matrix.rs index 0b0e05bd3..21c4080f5 100644 --- a/src/uint/bingcd/matrix.rs +++ b/src/uint/bingcd/matrix.rs @@ -83,7 +83,7 @@ impl IntMatrix { #[cfg(test)] mod tests { use crate::uint::bingcd::matrix::IntMatrix; - use crate::{ConstChoice, I256, Int, U256}; + use crate::{ConstChoice, Int, I256, U256}; const X: IntMatrix<{ U256::LIMBS }> = IntMatrix::new( Int::from_i64(1i64), @@ -146,11 +146,14 @@ mod tests { #[test] fn test_checked_mul() { let res = X.checked_mul(&X); - assert_eq!(res, IntMatrix::new( - I256::from_i64(162i64), - I256::from_i64(378i64), - I256::from_i64(1242i64), - I256::from_i64(2970i64), - )) + assert_eq!( + res, + IntMatrix::new( + I256::from_i64(162i64), + I256::from_i64(378i64), + I256::from_i64(1242i64), + I256::from_i64(2970i64), + ) + ) } } diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 7e7a17488..8cf36fa8b 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -51,15 +51,21 @@ impl Odd> { log_upper_bound = do_apply.select_u32(log_upper_bound, log_upper_bound + 1); } - (a.to_odd().expect("a is always odd"), b, matrix, log_upper_bound) + ( + a.to_odd().expect("a is always odd"), + b, + matrix, + log_upper_bound, + ) } } #[cfg(test)] mod tests { + mod test_partial_binxgcd { - use crate::uint::bingcd::matrix::IntMatrix; - use crate::{I64, U64}; + use crate::uint::bingcd::matrix::IntMatrix; + use crate::{I64, U64}; #[test] fn test_partial_binxgcd() { From 1b7c18edccd57397e523ea1fb600cea360ba7f86 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 4 Feb 2025 14:46:54 +0100 Subject: [PATCH 065/203] Introduce `ExtendedInt::checked_add` --- src/uint/bingcd/extension.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/uint/bingcd/extension.rs b/src/uint/bingcd/extension.rs index f907e9bef..487a51f50 100644 --- a/src/uint/bingcd/extension.rs +++ b/src/uint/bingcd/extension.rs @@ -94,6 +94,33 @@ impl ExtendedInt { Self(lo, hi) } + /// Perform addition, raising the `overflow` flag on overflow. + pub const fn overflowing_add(&self, rhs: &Self) -> (Self, ConstChoice) { + // Step 1. add operands + let res = self.wrapping_add(&rhs); + + // Step 2. determine whether overflow happened. + // Note: + // - overflow can only happen when the inputs have the same sign, and then + // - overflow occurs if and only if the result has the opposite sign of both inputs. + // + // We can thus express the overflow flag as: (self.msb == rhs.msb) & (self.msb != res.msb) + let self_msb = self.is_negative(); + let overflow = self_msb + .eq(rhs.is_negative()) + .and(self_msb.ne(res.is_negative())); + + // Step 3. Construct result + (res, overflow) + } + + /// Compute `self + rhs`, wrapping any overflow. + #[inline] + pub const fn checked_add(&self, rhs: &Self) -> ConstCtOption { + let (res, overflow) = self.overflowing_add(rhs); + ConstCtOption::new(res, overflow.not()) + } + /// Returns self without the extension. /// /// Is `None` if the extension cannot be dropped, i.e., when there is a bit in the extension @@ -125,6 +152,12 @@ impl ExtendedInt { self.abs_sgn().0 } + /// Decompose `self` into is absolute value and signum. + #[inline] + pub const fn is_negative(&self) -> ConstChoice { + self.abs_sgn().1 + } + /// Divide self by `2^k`, rounding towards zero. #[inline] pub const fn div_2k(&self, k: u32) -> Self { From 4fb8c4d435c23d0627e5f5cdd7a8c7328cfc9369 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 4 Feb 2025 16:20:56 +0100 Subject: [PATCH 066/203] attempt --- src/const_choice.rs | 15 ++++ src/uint/bingcd/extension.rs | 13 +-- src/uint/bingcd/gcd.rs | 19 ++-- src/uint/bingcd/matrix.rs | 51 +++++++---- src/uint/bingcd/xgcd.rs | 166 ++++++++++++++++++++++++++++++++++- 5 files changed, 234 insertions(+), 30 deletions(-) diff --git a/src/const_choice.rs b/src/const_choice.rs index 5e43e38c3..ae5b1f3c2 100644 --- a/src/const_choice.rs +++ b/src/const_choice.rs @@ -413,6 +413,21 @@ impl ConstCtOption<(Uint, Uint)> { } } +impl ConstCtOption<(Uint, ConstChoice)> { + /// Returns the contained value, consuming the `self` value. + /// + /// # Panics + /// + /// Panics if the value is none with a custom panic message provided by + /// `msg`. + #[inline] + pub const fn expect(self, msg: &str) -> (Uint, ConstChoice) { + assert!(self.is_some.is_true_vartime(), "{}", msg); + self.value + } +} + + impl ConstCtOption>> { /// Returns the contained value, consuming the `self` value. /// diff --git a/src/uint/bingcd/extension.rs b/src/uint/bingcd/extension.rs index 487a51f50..34325b343 100644 --- a/src/uint/bingcd/extension.rs +++ b/src/uint/bingcd/extension.rs @@ -1,4 +1,4 @@ -use crate::{ConstChoice, ConstCtOption, Int, Limb, Uint}; +use crate::{ConstChoice, ConstCtOption, Int, Limb, NonZero, Uint}; pub(crate) struct ExtendedUint( Uint, @@ -126,14 +126,15 @@ impl ExtendedInt { /// Is `None` if the extension cannot be dropped, i.e., when there is a bit in the extension /// that does not equal the MSB in the base. #[inline] - pub const fn abs_drop_extension(&self) -> ConstCtOption> { + pub const fn drop_extension(&self) -> ConstCtOption<(Uint, ConstChoice)> { // should succeed when // - extension is ZERO, or // - extension is MAX, and the top bit in base is set. let proper_positive = Int::eq(&self.1.as_int(), &Int::ZERO); let proper_negative = Int::eq(&self.1.as_int(), &Int::MINUS_ONE).and(self.0.as_int().is_negative()); - ConstCtOption::new(self.abs().0, proper_negative.or(proper_positive)) + let (abs, sgn) = self.abs_sgn(); + ConstCtOption::new((abs.0, sgn), proper_negative.or(proper_positive)) } /// Decompose `self` into is absolute value and signum. @@ -146,12 +147,6 @@ impl ExtendedInt { ) } - /// Decompose `self` into is absolute value and signum. - #[inline] - pub const fn abs(&self) -> ExtendedUint { - self.abs_sgn().0 - } - /// Decompose `self` into is absolute value and signum. #[inline] pub const fn is_negative(&self) -> ConstChoice { diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index c48a424d7..bf3121a2f 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -24,7 +24,8 @@ impl NonZero> { } impl Odd> { - const BITS: u32 = Uint::::BITS; + /// Total size of the represented integer in bits. + pub const BITS: u32 = Uint::::BITS; /// Compute the greatest common divisor of `self` and `rhs`. #[inline(always)] @@ -92,13 +93,13 @@ impl Odd> { // Update `a` and `b` using the update matrix let (updated_a, updated_b) = matrix.extended_apply_to((a, b)); - a = updated_a + (a, _) = updated_a .div_2k(log_upper_bound) - .abs_drop_extension() + .drop_extension() .expect("extension is zero"); - b = updated_b + (b, _) = updated_b .div_2k(log_upper_bound) - .abs_drop_extension() + .drop_extension() .expect("extension is zero"); } @@ -135,6 +136,10 @@ mod tests { bingcd_small_test(Uint::MAX, Uint::ZERO); bingcd_small_test(Uint::MAX, Uint::ONE); bingcd_small_test(Uint::MAX, Uint::MAX); + // bingcd_small_test( + // Uint::from_be_hex("7BE417F8D79B2A7EAE8E4E9621C36FF3"), + // Uint::from_be_hex("02427A8560599FD5183B0375455A895F"), + // ); // Randomized test cases for _ in 0..100 { @@ -185,6 +190,10 @@ mod tests { bingcd_large_test(Uint::MAX, Uint::ZERO); bingcd_large_test(Uint::MAX, Uint::ONE); bingcd_large_test(Uint::MAX, Uint::MAX); + // bingcd_large_test( + // Uint::from_be_hex("7BE417F8D79B2A7EAE8E4E9621C36FF3"), + // Uint::from_be_hex("02427A8560599FD5183B0375455A895F"), + // ); // Randomized testing for _ in 0..100 { diff --git a/src/uint/bingcd/matrix.rs b/src/uint/bingcd/matrix.rs index 21c4080f5..8e5dfb4af 100644 --- a/src/uint/bingcd/matrix.rs +++ b/src/uint/bingcd/matrix.rs @@ -5,10 +5,10 @@ type Vector = (T, T); #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) struct IntMatrix { - m00: Int, - m01: Int, - m10: Int, - m11: Int, + pub(crate) m00: Int, + pub(crate) m01: Int, + pub(crate) m10: Int, + pub(crate) m11: Int, } impl IntMatrix { @@ -36,24 +36,31 @@ impl IntMatrix { let a1 = ExtendedInt::from_product(a, self.m10); let b0 = ExtendedInt::from_product(b, self.m01); let b1 = ExtendedInt::from_product(b, self.m11); - (a0.wrapping_add(&b0), a1.wrapping_add(&b1)) + let (left, left_overflow) = a0.overflowing_add(&b0); + let (right, right_overflow) = a1.overflowing_add(&b1); + assert!(!left_overflow.to_bool_vartime()); + assert!(!right_overflow.to_bool_vartime()); + (left, right) } /// Apply this matrix to `rhs`. Panics if a multiplication overflows. /// TODO: consider implementing (a variation to) Strassen. Doing so will save a multiplication. #[inline] - const fn checked_mul(&self, rhs: &IntMatrix) -> Self { - let a0 = self.m00.const_checked_mul(&rhs.m00).expect("no overflow"); - let a1 = self.m01.const_checked_mul(&rhs.m10).expect("no overflow"); + pub(crate) const fn checked_mul_right( + &self, + rhs: &IntMatrix, + ) -> IntMatrix { + let a0 = rhs.m00.const_checked_mul(&self.m00).expect("no overflow"); + let a1 = rhs.m10.const_checked_mul(&self.m01).expect("no overflow"); let a = a0.checked_add(&a1).expect("no overflow"); - let b0 = self.m00.const_checked_mul(&rhs.m01).expect("no overflow"); - let b1 = self.m01.const_checked_mul(&rhs.m11).expect("no overflow"); + let b0 = rhs.m01.const_checked_mul(&self.m00).expect("no overflow"); + let b1 = rhs.m11.const_checked_mul(&self.m01).expect("no overflow"); let b = b0.checked_add(&b1).expect("no overflow"); - let c0 = self.m10.const_checked_mul(&rhs.m00).expect("no overflow"); - let c1 = self.m11.const_checked_mul(&rhs.m10).expect("no overflow"); + let c0 = rhs.m00.const_checked_mul(&self.m10).expect("no overflow"); + let c1 = rhs.m10.const_checked_mul(&self.m11).expect("no overflow"); let c = c0.checked_add(&c1).expect("no overflow"); - let d0 = self.m10.const_checked_mul(&rhs.m01).expect("no overflow"); - let d1 = self.m11.const_checked_mul(&rhs.m11).expect("no overflow"); + let d0 = rhs.m01.const_checked_mul(&self.m10).expect("no overflow"); + let d1 = rhs.m11.const_checked_mul(&self.m11).expect("no overflow"); let d = d0.checked_add(&d1).expect("no overflow"); IntMatrix::new(a, b, c, d) } @@ -78,6 +85,20 @@ impl IntMatrix { self.m00 = Int::select(&self.m00, &self.m00.shl_vartime(1), double); self.m01 = Int::select(&self.m01, &self.m01.shl_vartime(1), double); } + + /// Negate the elements in the top row if `negate` is truthy. Otherwise, do nothing. + #[inline] + pub(crate) const fn conditional_negate_top_row(&mut self, negate: ConstChoice) { + self.m00 = self.m00.wrapping_neg_if(negate); + self.m01 = self.m01.wrapping_neg_if(negate); + } + + /// Negate the elements in the bottom row if `negate` is truthy. Otherwise, do nothing. + #[inline] + pub(crate) const fn conditional_negate_bottom_row(&mut self, negate: ConstChoice) { + self.m10 = self.m10.wrapping_neg_if(negate); + self.m11 = self.m11.wrapping_neg_if(negate); + } } #[cfg(test)] @@ -145,7 +166,7 @@ mod tests { #[test] fn test_checked_mul() { - let res = X.checked_mul(&X); + let res = X.checked_mul_right(&X); assert_eq!( res, IntMatrix::new( diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 8cf36fa8b..b918114d9 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -1,7 +1,94 @@ use crate::uint::bingcd::matrix::IntMatrix; -use crate::{Odd, Uint}; +use crate::uint::bingcd::tools::const_max; +use crate::{ConcatMixed, Odd, Uint}; impl Odd> { + pub(super) fn binxgcd ( + &self, + rhs: &Uint, + ) -> (Self, IntMatrix, u32) + where + Uint: ConcatMixed, MixedOutput=Uint> + { + let (mut a, mut b) = (*self.as_ref(), *rhs); + + let mut matrix = IntMatrix::UNIT; + let mut i = 0; + let mut total_bound_shift = 0; + while i < (2 * Self::BITS - 1).div_ceil(K) { + i += 1; + + // check identity + // assert!( + // Int::eq( + // &matrix.m00.widening_mul_uint(&a).checked_add(&matrix.m01.widening_mul_uint(&b)).expect("no overflow"), + // &self.as_ref().resize().as_int() + // ).to_bool_vartime(), + // "{}", i + // ); + // assert!( + // Int::eq( + // &matrix.m10.widening_mul_uint(&a).checked_add(&matrix.m11.widening_mul_uint(&b)).expect("no overflow"), + // &rhs.resize().as_int() + // ).to_bool_vartime(), + // "{}", i + // ); + + // Construct a_ and b_ as the summary of a and b, respectively. + let n = const_max(2 * K, const_max(a.bits(), b.bits())); + let a_ = a.compact::(n, K); + let b_ = b.compact::(n, K); + + // Compute the K-1 iteration update matrix from a_ and b_ + let (.., update_matrix, log_upper_bound) = a_ + .to_odd() + .expect("a is always odd") + .partial_binxgcd::(&b_, K - 1); + + // Update `a` and `b` using the update matrix + let (updated_a, updated_b) = update_matrix.extended_apply_to((a, b)); + + let (a_sgn, b_sgn); + (a, a_sgn) = updated_a + .div_2k(log_upper_bound) + .drop_extension() + .expect("extension is zero"); + (b, b_sgn) = updated_b + .div_2k(log_upper_bound) + .drop_extension() + .expect("extension is zero"); + + matrix = update_matrix.checked_mul_right(&matrix); + matrix.conditional_negate_top_row(a_sgn); + matrix.conditional_double_top_row(b_sgn); + + total_bound_shift += log_upper_bound; + + // check identity + // assert!( + // Int::eq( + // &matrix.m00.widening_mul_uint(&a).checked_add(&matrix.m01.widening_mul_uint(&b)).expect("no overflow"), + // &self.as_ref().resize().as_int() + // ).to_bool_vartime(), + // "{} {}", i, total_bound_shift + // ); + // assert!( + // Int::eq( + // &matrix.m10.widening_mul_uint(&a).checked_add(&matrix.m11.widening_mul_uint(&b)).expect("no overflow"), + // &rhs.resize().as_int() + // ).to_bool_vartime(), + // "{} {}", i, total_bound_shift + // ); + } + + ( + a.to_odd() + .expect("gcd of an odd value with something else is always odd"), + matrix, + total_bound_shift + ) + } + /// Executes `iterations` reduction steps of the Binary Extended GCD algorithm to reduce /// `(self, rhs)` towards their GCD. Note: once the gcd is found, the extra iterations are /// performed. However, the algorithm has been constructed that additional iterations have no @@ -89,4 +176,81 @@ mod tests { assert_eq!(gcd.get(), a.gcd(&b)); } } + + mod test_binxgcd { + use crate::{ + ConcatMixed, Gcd, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64, + U768, U8192, + }; + use core::ops::Div; + use rand_core::OsRng; + + fn binxgcd_test(lhs: Uint, rhs: Uint) + where + Uint: + Gcd> + ConcatMixed, MixedOutput = Uint>, + { + let gcd = lhs.gcd(&rhs); + let (binxgcd, matrix, total_shift) = lhs + .to_odd() + .unwrap() + .binxgcd::<64, { U64::LIMBS }, { U128::LIMBS }, DOUBLE>(&rhs); + assert_eq!(gcd, binxgcd); + // test divisors + assert_eq!(matrix.m11.abs(), lhs.div(gcd)); + assert_eq!(matrix.m10.abs(), rhs.div(gcd)); + // test bezout coefficients + let prod = matrix.m00.widening_mul_uint(&lhs) + matrix.m01.widening_mul_uint(&rhs); + assert_eq!( + prod.shr(total_shift), + binxgcd.resize().as_int(), + "{} {} {} {} {} {} {}", + lhs, + rhs, + prod, + binxgcd, + matrix.m00, + matrix.m01, + total_shift + ) + } + + fn binxgcd_tests() + where + Uint: + Gcd> + ConcatMixed, MixedOutput = Uint>, + { + // Edge cases + // binxgcd_test(Uint::ONE, Uint::ZERO); + // binxgcd_test(Uint::ONE, Uint::ONE); + // // binxgcd_test(Uint::ONE, Uint::MAX); + // binxgcd_test(Uint::MAX, Uint::ZERO); + // // binxgcd_test(Uint::MAX, Uint::ONE); + // binxgcd_test(Uint::MAX, Uint::MAX); + binxgcd_test( + Uint::from_be_hex("7BE417F8D79B2A7EAE8E4E9621C36FF3"), + Uint::from_be_hex("02427A8560599FD5183B0375455A895F"), + ); + + // Randomized test cases + for _ in 0..100 { + let x = Uint::::random(&mut OsRng).bitor(&Uint::ONE); + let y = Uint::::random(&mut OsRng); + binxgcd_test(x, y); + } + } + + #[test] + fn test_binxgcd() { + // Cannot be applied to U64 + binxgcd_tests::<{ U128::LIMBS }, { U256::LIMBS }>(); + binxgcd_tests::<{ U192::LIMBS }, { U384::LIMBS }>(); + binxgcd_tests::<{ U256::LIMBS }, { U512::LIMBS }>(); + binxgcd_tests::<{ U384::LIMBS }, { U768::LIMBS }>(); + binxgcd_tests::<{ U512::LIMBS }, { U1024::LIMBS }>(); + binxgcd_tests::<{ U1024::LIMBS }, { U2048::LIMBS }>(); + binxgcd_tests::<{ U2048::LIMBS }, { U4096::LIMBS }>(); + binxgcd_tests::<{ U4096::LIMBS }, { U8192::LIMBS }>(); + } + } } From 9a941da806b44be333ea92bfe849a4caa47023bb Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 4 Feb 2025 16:35:51 +0100 Subject: [PATCH 067/203] Minor optimization; bingcd can always divide b by two; a is always non-zero. --- src/uint/bingcd/gcd.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index 7161311a1..dedf177bc 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -56,9 +56,8 @@ impl Odd> { // subtract a from b when b is odd b = Uint::select(&b, &b.wrapping_sub(&a), b_odd); - // Div b by two when a ≠ 0, otherwise do nothing. - let do_apply = a.is_nonzero(); - b = Uint::select(&b, &b.shr_vartime(1), do_apply); + // Div b by two + b = b.shr_vartime(1); } a.to_odd() From d4dbc42780c460f2198e9aa616cd5f04bb30d181 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 4 Feb 2025 17:23:30 +0100 Subject: [PATCH 068/203] Attempt to fix Bezout coefficients --- src/const_choice.rs | 1 - src/uint/bingcd/extension.rs | 2 +- src/uint/bingcd/xgcd.rs | 110 ++++++++++++++++------------------- 3 files changed, 50 insertions(+), 63 deletions(-) diff --git a/src/const_choice.rs b/src/const_choice.rs index ae5b1f3c2..3f20f44d9 100644 --- a/src/const_choice.rs +++ b/src/const_choice.rs @@ -427,7 +427,6 @@ impl ConstCtOption<(Uint, ConstChoice)> { } } - impl ConstCtOption>> { /// Returns the contained value, consuming the `self` value. /// diff --git a/src/uint/bingcd/extension.rs b/src/uint/bingcd/extension.rs index 34325b343..c8394404e 100644 --- a/src/uint/bingcd/extension.rs +++ b/src/uint/bingcd/extension.rs @@ -1,4 +1,4 @@ -use crate::{ConstChoice, ConstCtOption, Int, Limb, NonZero, Uint}; +use crate::{ConstChoice, ConstCtOption, Int, Limb, Uint}; pub(crate) struct ExtendedUint( Uint, diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index b918114d9..316c4b9b7 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -1,16 +1,33 @@ use crate::uint::bingcd::matrix::IntMatrix; use crate::uint::bingcd::tools::const_max; -use crate::{ConcatMixed, Odd, Uint}; +use crate::{ConcatMixed, Int, Odd, Uint}; + +impl Uint { + /// Compute `self / 2^k mod q`. Execute in time variable in `k` + #[inline] + const fn div_2k_mod_q_vartime(mut self, k: u32, q: &Odd) -> Self { + // TODO: fix wrapping add. Q could be Uint::MAX. + let one_half_mod_q = q.as_ref().wrapping_add(&Uint::ONE).shr_vartime(1); + + let mut add_one_half; + let mut i = 0; + while i < k { + add_one_half = self.is_odd(); + self = self.shr_vartime(1); + self = Self::select(&self, &self.wrapping_add(&one_half_mod_q), add_one_half); + i += 1; + } + + self + } +} impl Odd> { - pub(super) fn binxgcd ( + pub(super) const fn binxgcd( &self, - rhs: &Uint, - ) -> (Self, IntMatrix, u32) - where - Uint: ConcatMixed, MixedOutput=Uint> - { - let (mut a, mut b) = (*self.as_ref(), *rhs); + rhs: &Self, + ) -> (Self, Int, Int) { + let (mut a, mut b) = (*self.as_ref(), *rhs.as_ref()); let mut matrix = IntMatrix::UNIT; let mut i = 0; @@ -18,22 +35,6 @@ impl Odd> { while i < (2 * Self::BITS - 1).div_ceil(K) { i += 1; - // check identity - // assert!( - // Int::eq( - // &matrix.m00.widening_mul_uint(&a).checked_add(&matrix.m01.widening_mul_uint(&b)).expect("no overflow"), - // &self.as_ref().resize().as_int() - // ).to_bool_vartime(), - // "{}", i - // ); - // assert!( - // Int::eq( - // &matrix.m10.widening_mul_uint(&a).checked_add(&matrix.m11.widening_mul_uint(&b)).expect("no overflow"), - // &rhs.resize().as_int() - // ).to_bool_vartime(), - // "{}", i - // ); - // Construct a_ and b_ as the summary of a and b, respectively. let n = const_max(2 * K, const_max(a.bits(), b.bits())); let a_ = a.compact::(n, K); @@ -63,29 +64,23 @@ impl Odd> { matrix.conditional_double_top_row(b_sgn); total_bound_shift += log_upper_bound; - - // check identity - // assert!( - // Int::eq( - // &matrix.m00.widening_mul_uint(&a).checked_add(&matrix.m01.widening_mul_uint(&b)).expect("no overflow"), - // &self.as_ref().resize().as_int() - // ).to_bool_vartime(), - // "{} {}", i, total_bound_shift - // ); - // assert!( - // Int::eq( - // &matrix.m10.widening_mul_uint(&a).checked_add(&matrix.m11.widening_mul_uint(&b)).expect("no overflow"), - // &rhs.resize().as_int() - // ).to_bool_vartime(), - // "{} {}", i, total_bound_shift - // ); } + // Extract the Bezout coefficients + // TODO: fix vartime use + let (mut x, mut y) = (matrix.m00, matrix.m01); + let (abs_x, sgn_x) = x.abs_sign(); + x = Int::new_from_abs_sign(abs_x.div_2k_mod_q_vartime(total_bound_shift, &rhs), sgn_x) + .expect("no overflow"); + let (abs_y, sgn_y) = y.abs_sign(); + y = Int::new_from_abs_sign(abs_y.div_2k_mod_q_vartime(total_bound_shift, &self), sgn_y) + .expect("no overflow"); + ( a.to_odd() .expect("gcd of an odd value with something else is always odd"), - matrix, - total_bound_shift + x, + y, ) } @@ -182,7 +177,6 @@ mod tests { ConcatMixed, Gcd, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64, U768, U8192, }; - use core::ops::Div; use rand_core::OsRng; fn binxgcd_test(lhs: Uint, rhs: Uint) @@ -191,27 +185,23 @@ mod tests { Gcd> + ConcatMixed, MixedOutput = Uint>, { let gcd = lhs.gcd(&rhs); - let (binxgcd, matrix, total_shift) = lhs + let (binxgcd, x, y) = lhs .to_odd() .unwrap() - .binxgcd::<64, { U64::LIMBS }, { U128::LIMBS }, DOUBLE>(&rhs); + .binxgcd::<64, { U64::LIMBS }, { U128::LIMBS }>(&rhs.to_odd().unwrap()); assert_eq!(gcd, binxgcd); - // test divisors - assert_eq!(matrix.m11.abs(), lhs.div(gcd)); - assert_eq!(matrix.m10.abs(), rhs.div(gcd)); // test bezout coefficients - let prod = matrix.m00.widening_mul_uint(&lhs) + matrix.m01.widening_mul_uint(&rhs); + let prod = x.widening_mul_uint(&lhs) + y.widening_mul_uint(&rhs); assert_eq!( - prod.shr(total_shift), + prod, binxgcd.resize().as_int(), - "{} {} {} {} {} {} {}", + "{} {} {} {} {} {}", lhs, rhs, prod, binxgcd, - matrix.m00, - matrix.m01, - total_shift + x, + y ) } @@ -221,12 +211,10 @@ mod tests { Gcd> + ConcatMixed, MixedOutput = Uint>, { // Edge cases - // binxgcd_test(Uint::ONE, Uint::ZERO); - // binxgcd_test(Uint::ONE, Uint::ONE); - // // binxgcd_test(Uint::ONE, Uint::MAX); - // binxgcd_test(Uint::MAX, Uint::ZERO); - // // binxgcd_test(Uint::MAX, Uint::ONE); - // binxgcd_test(Uint::MAX, Uint::MAX); + binxgcd_test(Uint::ONE, Uint::ONE); + binxgcd_test(Uint::ONE, Uint::MAX); + binxgcd_test(Uint::MAX, Uint::ONE); + binxgcd_test(Uint::MAX, Uint::MAX); binxgcd_test( Uint::from_be_hex("7BE417F8D79B2A7EAE8E4E9621C36FF3"), Uint::from_be_hex("02427A8560599FD5183B0375455A895F"), @@ -235,7 +223,7 @@ mod tests { // Randomized test cases for _ in 0..100 { let x = Uint::::random(&mut OsRng).bitor(&Uint::ONE); - let y = Uint::::random(&mut OsRng); + let y = Uint::::random(&mut OsRng).bitor(&Uint::ONE); binxgcd_test(x, y); } } From 37eef789b8409f31c917c4191243acd88a51af7b Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 4 Feb 2025 17:25:49 +0100 Subject: [PATCH 069/203] Fix bug --- src/uint/bingcd/xgcd.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 316c4b9b7..44ce4514d 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -61,8 +61,7 @@ impl Odd> { matrix = update_matrix.checked_mul_right(&matrix); matrix.conditional_negate_top_row(a_sgn); - matrix.conditional_double_top_row(b_sgn); - + matrix.conditional_negate_bottom_row(b_sgn); total_bound_shift += log_upper_bound; } From 1cd84a6357e10d736a0613a53022c06171790247 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 5 Feb 2025 11:42:20 +0100 Subject: [PATCH 070/203] Make `binxgcd` constant time --- src/uint/bingcd/xgcd.rs | 67 +++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 44ce4514d..ac96f980d 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -1,29 +1,56 @@ use crate::uint::bingcd::matrix::IntMatrix; use crate::uint::bingcd::tools::const_max; -use crate::{ConcatMixed, Int, Odd, Uint}; +use crate::{ConstChoice, Int, Odd, Uint}; -impl Uint { - /// Compute `self / 2^k mod q`. Execute in time variable in `k` +impl Int { + /// Compute `self / 2^k mod q`. Executes in time variable in `k_bound`. This value should be + /// chosen as an inclusive upperbound to the value of `k`. #[inline] - const fn div_2k_mod_q_vartime(mut self, k: u32, q: &Odd) -> Self { - // TODO: fix wrapping add. Q could be Uint::MAX. - let one_half_mod_q = q.as_ref().wrapping_add(&Uint::ONE).shr_vartime(1); + const fn div_2k_mod_q(&self, k: u32, k_bound: u32, q: &Odd>) -> Self { + let (abs, sgn) = self.abs_sign(); + let abs_div_2k_mod_q = abs.div_2k_mod_q(k, k_bound, &q); + Int::new_from_abs_sign(abs_div_2k_mod_q,sgn,).expect("no overflow") + } +} - let mut add_one_half; +impl Uint { + /// Compute `self / 2^k mod q`. Executes in time variable in `k_bound`. This value should be + /// chosen as an inclusive upperbound to the value of `k`. + #[inline] + const fn div_2k_mod_q(mut self, k: u32, k_bound: u32, q: &Odd) -> Self { + // 1 / 2 mod q + // = (q + 1) / 2 mod q + // = (q - 1) / 2 + 1 mod q + // = floor(q / 2) + 1 mod q, since q is odd. + let one_half_mod_q = q.as_ref().shr_vartime(1).wrapping_add(&Uint::ONE); let mut i = 0; - while i < k { - add_one_half = self.is_odd(); - self = self.shr_vartime(1); - self = Self::select(&self, &self.wrapping_add(&one_half_mod_q), add_one_half); + while i < k_bound { + // Apply only while i < k + let apply = ConstChoice::from_u32_lt(i, k); + self = Self::select(&self, &self.div_2_mod_q(&one_half_mod_q), apply); i += 1; } self } + + /// Compute `self / 2 mod q`. + #[inline] + const fn div_2_mod_q(self, half_mod_q: &Self) -> Self { + // Floor-divide self by 2. When self was odd, add back 1/2 mod q. + let add_one_half = self.is_odd(); + let floored_half = self.shr_vartime(1); + Self::select( + &floored_half, + &floored_half.wrapping_add(&half_mod_q), + add_one_half, + ) + } } impl Odd> { - pub(super) const fn binxgcd( + /// Given `(self, rhs)`, compute `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. + pub const fn binxgcd( &self, rhs: &Self, ) -> (Self, Int, Int) { @@ -32,7 +59,8 @@ impl Odd> { let mut matrix = IntMatrix::UNIT; let mut i = 0; let mut total_bound_shift = 0; - while i < (2 * Self::BITS - 1).div_ceil(K) { + let reduction_rounds = (2 * Self::BITS - 1).div_ceil(K); + while i < reduction_rounds { i += 1; // Construct a_ and b_ as the summary of a and b, respectively. @@ -65,15 +93,10 @@ impl Odd> { total_bound_shift += log_upper_bound; } - // Extract the Bezout coefficients - // TODO: fix vartime use - let (mut x, mut y) = (matrix.m00, matrix.m01); - let (abs_x, sgn_x) = x.abs_sign(); - x = Int::new_from_abs_sign(abs_x.div_2k_mod_q_vartime(total_bound_shift, &rhs), sgn_x) - .expect("no overflow"); - let (abs_y, sgn_y) = y.abs_sign(); - y = Int::new_from_abs_sign(abs_y.div_2k_mod_q_vartime(total_bound_shift, &self), sgn_y) - .expect("no overflow"); + // Extract the Bezout coefficients. + let total_iterations = reduction_rounds * (K-1); + let x = matrix.m00.div_2k_mod_q(total_bound_shift, total_iterations, &rhs); + let y = matrix.m01.div_2k_mod_q(total_bound_shift, total_iterations, &self); ( a.to_odd() From 21345e974fff7c487f98a8a9459e7713dbf3d351 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 5 Feb 2025 12:23:20 +0100 Subject: [PATCH 071/203] Introduce xgcd benchmarks --- benches/uint.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/benches/uint.rs b/benches/uint.rs index fb7bf0e4a..cd33ecb8b 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -387,6 +387,52 @@ fn bench_gcd(c: &mut Criterion) { group.finish(); } +fn xgcd_bench( + g: &mut BenchmarkGroup, + _x: Uint, +) where + Odd>: PrecomputeInverter>, +{ + g.bench_function(BenchmarkId::new("binxgcd", LIMBS), |b| { + b.iter_batched( + || { + let modulus = Uint::MAX.shr_vartime(1).to_nz().unwrap(); + let f = Uint::::random_mod(&mut OsRng, &modulus) + .bitor(&Uint::ONE) + .to_odd() + .unwrap(); + let g = Uint::::random_mod(&mut OsRng, &modulus) + .bitor(&Uint::ONE) + .to_odd() + .unwrap(); + (f, g) + }, + |(f, g)| black_box(f.binxgcd::<63, { U64::LIMBS }, { U128::LIMBS }>(&g)), + BatchSize::SmallInput, + ) + }); +} + +fn bench_xgcd(c: &mut Criterion) { + let mut group = c.benchmark_group("greatest common divisor"); + + xgcd_bench(&mut group, U64::ZERO); + xgcd_bench(&mut group, U128::ZERO); + xgcd_bench(&mut group, U192::ZERO); + xgcd_bench(&mut group, U256::ZERO); + xgcd_bench(&mut group, U320::ZERO); + xgcd_bench(&mut group, U384::ZERO); + xgcd_bench(&mut group, U448::ZERO); + xgcd_bench(&mut group, U512::ZERO); + xgcd_bench(&mut group, U1024::ZERO); + xgcd_bench(&mut group, U2048::ZERO); + xgcd_bench(&mut group, U4096::ZERO); + xgcd_bench(&mut group, U8192::ZERO); + xgcd_bench(&mut group, U16384::ZERO); + + group.finish(); +} + fn bench_shl(c: &mut Criterion) { let mut group = c.benchmark_group("left shift"); @@ -546,6 +592,7 @@ criterion_group!( bench_mul, bench_division, bench_gcd, + bench_xgcd, bench_shl, bench_shr, bench_inv_mod, From 4a88f8ce88d1ea7ac8b8aa9e16b8b03957247e4e Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 4 Feb 2025 16:35:51 +0100 Subject: [PATCH 072/203] Minor optimization; bingcd can always divide b by two; a is always non-zero. --- src/uint/bingcd/gcd.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index bf3121a2f..212dae7f4 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -57,9 +57,8 @@ impl Odd> { // subtract a from b when b is odd b = Uint::select(&b, &b.wrapping_sub(&a), b_odd); - // Div b by two when a ≠ 0, otherwise do nothing. - let do_apply = a.is_nonzero(); - b = Uint::select(&b, &b.shr_vartime(1), do_apply); + // Div b by two + b = b.shr_vartime(1); } a.to_odd() From ce20fe247795711f3ab7a506d30170d9cefcbf76 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 5 Feb 2025 15:02:37 +0100 Subject: [PATCH 073/203] Extract the `bingcd_step` subroutine. --- src/uint/bingcd/gcd.rs | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index 212dae7f4..cb23fee00 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -45,26 +45,33 @@ impl Odd> { let (mut a, mut b) = (*self.as_ref(), *rhs); let mut j = 0; while j < (2 * Self::BITS - 1) { + Self::bingcd_step(&mut a, &mut b); j += 1; - - let b_odd = b.is_odd(); - - // swap if b odd and a > b - let a_gt_b = Uint::gt(&a, &b); - let do_swap = b_odd.and(a_gt_b); - Uint::conditional_swap(&mut a, &mut b, do_swap); - - // subtract a from b when b is odd - b = Uint::select(&b, &b.wrapping_sub(&a), b_odd); - - // Div b by two - b = b.shr_vartime(1); } a.to_odd() .expect("gcd of an odd value with something else is always odd") } + /// Binary GCD update step. + /// + /// This is a condensed, constant time execution of the following algorithm: + /// ```text + /// if b mod 2 == 1 + /// if a > b + /// (a, b) ← (b, a) + /// b ← b - a + /// b ← b/2 + /// ``` + /// Ref: Pornin, Algorithm 2, L8-17, . + #[inline] + const fn bingcd_step(a: &mut Uint, b: &mut Uint) { + let b_odd = b.is_odd(); + let a_gt_b = Uint::gt(&a, &b); + Uint::conditional_swap(a, b, b_odd.and(a_gt_b)); + *b = Uint::select(b, &b.wrapping_sub(a), b_odd).shr_vartime(1); + } + /// Computes `gcd(self, rhs)`, leveraging the Binary GCD algorithm. /// Is efficient for larger `LIMBS`. #[inline(always)] From a89a93aa4b396987344a6ac25a3c0f5b31d0aa04 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 5 Feb 2025 15:04:19 +0100 Subject: [PATCH 074/203] Extract the `binxgcd_step` subroutine. --- src/uint/bingcd/xgcd.rs | 61 ++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index ac96f980d..cba7cb705 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -9,7 +9,7 @@ impl Int { const fn div_2k_mod_q(&self, k: u32, k_bound: u32, q: &Odd>) -> Self { let (abs, sgn) = self.abs_sign(); let abs_div_2k_mod_q = abs.div_2k_mod_q(k, k_bound, &q); - Int::new_from_abs_sign(abs_div_2k_mod_q,sgn,).expect("no overflow") + Int::new_from_abs_sign(abs_div_2k_mod_q, sgn).expect("no overflow") } } @@ -134,25 +134,8 @@ impl Odd> { let mut log_upper_bound = 0; let mut j = 0; while j < iterations { + Self::binxgcd_step(&mut a, &mut b, &mut matrix, &mut log_upper_bound); j += 1; - - let b_odd = b.is_odd(); - let a_gt_b = Uint::gt(&a, &b); - - // swap if b odd and a > b - let do_swap = b_odd.and(a_gt_b); - Uint::conditional_swap(&mut a, &mut b, do_swap); - matrix.conditional_swap_rows(do_swap); - - // subtract a from b when b is odd - b = Uint::select(&b, &b.wrapping_sub(&a), b_odd); - matrix.conditional_subtract_top_row_from_bottom(b_odd); - - // Div b by two and double the top row of the matrix when a, b ≠ 0. - let do_apply = a.is_nonzero().and(b.is_nonzero()); - b = Uint::select(&b, &b.shr_vartime(1), do_apply); - matrix.conditional_double_top_row(do_apply); - log_upper_bound = do_apply.select_u32(log_upper_bound, log_upper_bound + 1); } ( @@ -162,6 +145,46 @@ impl Odd> { log_upper_bound, ) } + + /// Binary XGCD update step. + /// + /// This is a condensed, constant time execution of the following algorithm: + /// ```text + /// if b mod 2 == 1 + /// if a > b + /// (a, b) ← (b, a) + /// (f0, g0, f1, g1) ← (f1, g1, f0, g0) + /// b ← b - a + /// (f1, g1) ← (f1 - f0, g1 - g0) + /// b ← b/2 + /// (f0, g0) ← (2f0, 2g0) + /// ``` + /// Ref: Pornin, Algorithm 2, L8-17, . + #[inline] + const fn binxgcd_step( + a: &mut Uint, + b: &mut Uint, + matrix: &mut IntMatrix, + log_upper_bound: &mut u32, + ){ + let b_odd = b.is_odd(); + let a_gt_b = Uint::gt(&a, &b); + + // swap if b odd and a > b + let swap = b_odd.and(a_gt_b); + Uint::conditional_swap(a, b, swap); + matrix.conditional_swap_rows(swap); + + // subtract a from b when b is odd + *b = Uint::select(&b, &b.wrapping_sub(&a), b_odd); + matrix.conditional_subtract_top_row_from_bottom(b_odd); + + // Div b by two and double the top row of the matrix when a, b ≠ 0. + let double = b.is_nonzero(); + *b = b.shr_vartime(1); + matrix.conditional_double_top_row(double); + *log_upper_bound = double.select_u32(*log_upper_bound, *log_upper_bound + 1); + } } #[cfg(test)] From 50d5ed0a9d45a0a615abff28623c8c83d049025f Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 5 Feb 2025 15:06:08 +0100 Subject: [PATCH 075/203] Fix fmt --- src/uint/bingcd/xgcd.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index cba7cb705..f88032396 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -94,9 +94,10 @@ impl Odd> { } // Extract the Bezout coefficients. - let total_iterations = reduction_rounds * (K-1); - let x = matrix.m00.div_2k_mod_q(total_bound_shift, total_iterations, &rhs); - let y = matrix.m01.div_2k_mod_q(total_bound_shift, total_iterations, &self); + let total_iterations = reduction_rounds * (K - 1); + let IntMatrix { m00, m01, .. } = matrix; + let x = m00.div_2k_mod_q(total_bound_shift, total_iterations, &rhs); + let y = m01.div_2k_mod_q(total_bound_shift, total_iterations, &self); ( a.to_odd() @@ -166,7 +167,7 @@ impl Odd> { b: &mut Uint, matrix: &mut IntMatrix, log_upper_bound: &mut u32, - ){ + ) { let b_odd = b.is_odd(); let a_gt_b = Uint::gt(&a, &b); From cd470a8c1c5896f2600f02238ff83f83b9852bf9 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 6 Feb 2025 09:57:25 +0100 Subject: [PATCH 076/203] Make binxgcd work upto Int::MAX --- src/uint/bingcd/xgcd.rs | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index f88032396..a6c6da379 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -50,6 +50,8 @@ impl Uint { impl Odd> { /// Given `(self, rhs)`, compute `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. + /// + /// TODO: this only works for `self` and `rhs` that are <= Int::MAX. pub const fn binxgcd( &self, rhs: &Self, @@ -220,8 +222,8 @@ mod tests { mod test_binxgcd { use crate::{ - ConcatMixed, Gcd, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64, - U768, U8192, + ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, + U4096, U512, U64, U768, U8192, }; use rand_core::OsRng; @@ -236,19 +238,10 @@ mod tests { .unwrap() .binxgcd::<64, { U64::LIMBS }, { U128::LIMBS }>(&rhs.to_odd().unwrap()); assert_eq!(gcd, binxgcd); + // test bezout coefficients let prod = x.widening_mul_uint(&lhs) + y.widening_mul_uint(&rhs); - assert_eq!( - prod, - binxgcd.resize().as_int(), - "{} {} {} {} {} {}", - lhs, - rhs, - prod, - binxgcd, - x, - y - ) + assert_eq!(prod, binxgcd.resize().as_int()) } fn binxgcd_tests() @@ -256,20 +249,20 @@ mod tests { Uint: Gcd> + ConcatMixed, MixedOutput = Uint>, { + // upper bound + let upper_bound = *Int::MAX.as_uint(); + // Edge cases binxgcd_test(Uint::ONE, Uint::ONE); - binxgcd_test(Uint::ONE, Uint::MAX); - binxgcd_test(Uint::MAX, Uint::ONE); - binxgcd_test(Uint::MAX, Uint::MAX); - binxgcd_test( - Uint::from_be_hex("7BE417F8D79B2A7EAE8E4E9621C36FF3"), - Uint::from_be_hex("02427A8560599FD5183B0375455A895F"), - ); + binxgcd_test(Uint::ONE, upper_bound); + binxgcd_test(upper_bound, Uint::ONE); + binxgcd_test(upper_bound, upper_bound); // Randomized test cases + let bound = upper_bound.wrapping_add(&Uint::ONE).to_nz().unwrap(); for _ in 0..100 { - let x = Uint::::random(&mut OsRng).bitor(&Uint::ONE); - let y = Uint::::random(&mut OsRng).bitor(&Uint::ONE); + let x = Uint::::random_mod(&mut OsRng, &bound).bitor(&Uint::ONE); + let y = Uint::::random_mod(&mut OsRng, &bound).bitor(&Uint::ONE); binxgcd_test(x, y); } } From dd1d308c181ba1285da48536a640fed77d8dc2c8 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 6 Feb 2025 09:57:41 +0100 Subject: [PATCH 077/203] Remove `ExtendedInt::checked_add` --- src/uint/bingcd/extension.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/uint/bingcd/extension.rs b/src/uint/bingcd/extension.rs index c8394404e..51512a74b 100644 --- a/src/uint/bingcd/extension.rs +++ b/src/uint/bingcd/extension.rs @@ -114,13 +114,6 @@ impl ExtendedInt { (res, overflow) } - /// Compute `self + rhs`, wrapping any overflow. - #[inline] - pub const fn checked_add(&self, rhs: &Self) -> ConstCtOption { - let (res, overflow) = self.overflowing_add(rhs); - ConstCtOption::new(res, overflow.not()) - } - /// Returns self without the extension. /// /// Is `None` if the extension cannot be dropped, i.e., when there is a bit in the extension From daa64cb776713fd46a3163b495967e69b16b8cb1 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 6 Feb 2025 09:59:01 +0100 Subject: [PATCH 078/203] Fix clippy --- src/uint/bingcd/extension.rs | 2 +- src/uint/bingcd/gcd.rs | 2 +- src/uint/bingcd/xgcd.rs | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/uint/bingcd/extension.rs b/src/uint/bingcd/extension.rs index 51512a74b..246eadb00 100644 --- a/src/uint/bingcd/extension.rs +++ b/src/uint/bingcd/extension.rs @@ -97,7 +97,7 @@ impl ExtendedInt { /// Perform addition, raising the `overflow` flag on overflow. pub const fn overflowing_add(&self, rhs: &Self) -> (Self, ConstChoice) { // Step 1. add operands - let res = self.wrapping_add(&rhs); + let res = self.wrapping_add(rhs); // Step 2. determine whether overflow happened. // Note: diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index cb23fee00..dfc457aba 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -67,7 +67,7 @@ impl Odd> { #[inline] const fn bingcd_step(a: &mut Uint, b: &mut Uint) { let b_odd = b.is_odd(); - let a_gt_b = Uint::gt(&a, &b); + let a_gt_b = Uint::gt(a, b); Uint::conditional_swap(a, b, b_odd.and(a_gt_b)); *b = Uint::select(b, &b.wrapping_sub(a), b_odd).shr_vartime(1); } diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index a6c6da379..cdd790f0f 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -8,7 +8,7 @@ impl Int { #[inline] const fn div_2k_mod_q(&self, k: u32, k_bound: u32, q: &Odd>) -> Self { let (abs, sgn) = self.abs_sign(); - let abs_div_2k_mod_q = abs.div_2k_mod_q(k, k_bound, &q); + let abs_div_2k_mod_q = abs.div_2k_mod_q(k, k_bound, q); Int::new_from_abs_sign(abs_div_2k_mod_q, sgn).expect("no overflow") } } @@ -42,7 +42,7 @@ impl Uint { let floored_half = self.shr_vartime(1); Self::select( &floored_half, - &floored_half.wrapping_add(&half_mod_q), + &floored_half.wrapping_add(half_mod_q), add_one_half, ) } @@ -98,8 +98,8 @@ impl Odd> { // Extract the Bezout coefficients. let total_iterations = reduction_rounds * (K - 1); let IntMatrix { m00, m01, .. } = matrix; - let x = m00.div_2k_mod_q(total_bound_shift, total_iterations, &rhs); - let y = m01.div_2k_mod_q(total_bound_shift, total_iterations, &self); + let x = m00.div_2k_mod_q(total_bound_shift, total_iterations, rhs); + let y = m01.div_2k_mod_q(total_bound_shift, total_iterations, self); ( a.to_odd() @@ -171,7 +171,7 @@ impl Odd> { log_upper_bound: &mut u32, ) { let b_odd = b.is_odd(); - let a_gt_b = Uint::gt(&a, &b); + let a_gt_b = Uint::gt(a, b); // swap if b odd and a > b let swap = b_odd.and(a_gt_b); @@ -179,7 +179,7 @@ impl Odd> { matrix.conditional_swap_rows(swap); // subtract a from b when b is odd - *b = Uint::select(&b, &b.wrapping_sub(&a), b_odd); + *b = Uint::select(b, &b.wrapping_sub(a), b_odd); matrix.conditional_subtract_top_row_from_bottom(b_odd); // Div b by two and double the top row of the matrix when a, b ≠ 0. From d1347e3a349dfa8d37fcd1bbf91fd5f099395d6e Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 6 Feb 2025 10:46:37 +0100 Subject: [PATCH 079/203] Improve `bingcd` annotation --- benches/uint.rs | 6 ++++-- src/uint/bingcd/gcd.rs | 40 +++++++++++++++++++++++++++------------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/benches/uint.rs b/benches/uint.rs index fb7bf0e4a..d0f09457b 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -345,7 +345,7 @@ fn gcd_bench( let g = Uint::::random(&mut OsRng); (f, g) }, - |(f, g)| black_box(f.bingcd_small(&g)), + |(f, g)| black_box(f.classic_bingcd(&g)), BatchSize::SmallInput, ) }); @@ -360,7 +360,9 @@ fn gcd_bench( (f, g) }, |(f, g)| { - black_box(f.bingcd_large::<{ U64::BITS - 2 }, { U64::LIMBS }, { U128::LIMBS }>(&g)) + black_box( + f.optimized_bingcd::<{ U64::BITS - 2 }, { U64::LIMBS }, { U128::LIMBS }>(&g), + ) }, BatchSize::SmallInput, ) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index dedf177bc..bafacdd26 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -14,33 +14,42 @@ impl NonZero> { val.shr(i) .to_odd() - .expect("self is odd by construction") + .expect("val.shr(i) is odd by construction") .bingcd(rhs) .as_ref() .shl(k) .to_nz() - .expect("gcd of non-zero element with zero is non-zero") + .expect("gcd of non-zero element with another element is non-zero") } } impl Odd> { const BITS: u32 = Uint::::BITS; - /// Compute the greatest common divisor of `self` and `rhs`. + /// Compute the greatest common divisor of `self` and `rhs` using the Binary GCD algorithm. + /// + /// This function switches between the "classic" and "optimized" algorithm at a best-effort + /// threshold. When using [Uint]s with `LIMBS` close to the threshold, it may be useful to + /// manually test whether the classic or optimized algorithm is faster for your machine. #[inline(always)] pub const fn bingcd(&self, rhs: &Uint) -> Self { // Todo: tweak this threshold if LIMBS < 8 { - self.bingcd_small(rhs) + self.classic_bingcd(rhs) } else { - self.bingcd_large::<{ U64::BITS - 2 }, { U64::LIMBS }, { U128::LIMBS }>(rhs) + self.optimized_bingcd::<{ U64::BITS - 2 }, { U64::LIMBS }, { U128::LIMBS }>(rhs) } } - /// Computes `gcd(self, rhs)`, leveraging the Binary GCD algorithm. - /// Is efficient only for relatively small `LIMBS`. + /// Computes `gcd(self, rhs)`, leveraging the (a constant time implementation of) the classic + /// Binary GCD algorithm. + /// + /// Note: this algorithm is efficient for [Uint]s with relatively few `LIMBS`. + /// + /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 1. + /// #[inline] - pub const fn bingcd_small(&self, rhs: &Uint) -> Self { + pub const fn classic_bingcd(&self, rhs: &Uint) -> Self { let (mut a, mut b) = (*self.as_ref(), *rhs); let mut j = 0; while j < (2 * Self::BITS - 1) { @@ -64,10 +73,15 @@ impl Odd> { .expect("gcd of an odd value with something else is always odd") } - /// Computes `gcd(self, rhs)`, leveraging the Binary GCD algorithm. - /// Is efficient for larger `LIMBS`. + /// Computes `gcd(self, rhs)`, leveraging the optimized Binary GCD algorithm. + /// + /// Note: this algorithm becomes more efficient than the classical algorithm for [Uint]s with + /// relatively many `LIMBS`. A best-effort threshold is presented in [Self::bingcd]. + /// + /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 2. + /// #[inline(always)] - pub const fn bingcd_large( + pub const fn optimized_bingcd( &self, rhs: &Uint, ) -> Self { @@ -119,7 +133,7 @@ mod tests { Uint: Gcd>, { let gcd = lhs.gcd(&rhs); - let bingcd = lhs.to_odd().unwrap().bingcd_small(&rhs); + let bingcd = lhs.to_odd().unwrap().classic_bingcd(&rhs); assert_eq!(gcd, bingcd); } @@ -169,7 +183,7 @@ mod tests { let bingcd = lhs .to_odd() .unwrap() - .bingcd_large::<62, { U64::LIMBS }, { U128::LIMBS }>(&rhs); + .optimized_bingcd::<62, { U64::LIMBS }, { U128::LIMBS }>(&rhs); assert_eq!(gcd, bingcd); } From 87d8ee729f418026f99a8a37bf6cec32f10a9d57 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 6 Feb 2025 11:03:25 +0100 Subject: [PATCH 080/203] Split `optimized_bingcd` in two parts. --- benches/uint.rs | 6 +----- src/uint/bingcd/gcd.rs | 35 ++++++++++++++++++++++++++++------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/benches/uint.rs b/benches/uint.rs index d0f09457b..a6f967928 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -359,11 +359,7 @@ fn gcd_bench( let g = Uint::::random(&mut OsRng); (f, g) }, - |(f, g)| { - black_box( - f.optimized_bingcd::<{ U64::BITS - 2 }, { U64::LIMBS }, { U128::LIMBS }>(&g), - ) - }, + |(f, g)| black_box(f.optimized_bingcd(&g)), BatchSize::SmallInput, ) }); diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index bafacdd26..cfca4e5ab 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -37,7 +37,7 @@ impl Odd> { if LIMBS < 8 { self.classic_bingcd(rhs) } else { - self.optimized_bingcd::<{ U64::BITS - 2 }, { U64::LIMBS }, { U128::LIMBS }>(rhs) + self.optimized_bingcd(rhs) } } @@ -78,10 +78,34 @@ impl Odd> { /// Note: this algorithm becomes more efficient than the classical algorithm for [Uint]s with /// relatively many `LIMBS`. A best-effort threshold is presented in [Self::bingcd]. /// + /// Note: the full algorithm has an additional parameter; this function selects the best-effort + /// value for this parameter. You might be able to further tune your performance by calling the + /// [Self::optimized_bingcd_] function directly. + /// + /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 2. + /// + #[inline(always)] + pub const fn optimized_bingcd(&self, rhs: &Uint) -> Self { + self.optimized_bingcd_::<{ U64::BITS - 2 }, { U64::LIMBS }, { U128::LIMBS }>(rhs) + } + + /// Computes `gcd(self, rhs)`, leveraging the optimized Binary GCD algorithm. + /// /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 2. /// + /// + /// In summary, the optimized algorithm does not operate on `self` and `rhs` directly, but + /// instead of condensed summaries that fit in few registers. Based on these summaries, an + /// update matrix is constructed by which `self` and `rhs` are updated in larger steps. + /// + /// This function is generic over the following three values: + /// - `K`: the number of bits used when summarizing `self` and `rhs` for the inner loop. The + /// `K+1` top bits and `K-1` least significant bits are selected. It is recommended to keep `K` + /// close to a (multiple of) the number of bits that fit in a single register. + /// - `LIMBS_K`: should be chosen as the minimum number s.t. `Uint::::BITS ≥ K`, + /// - `LIMBS_2K`: should be chosen as the minimum number s.t. `Uint::::BITS ≥ 2K`. #[inline(always)] - pub const fn optimized_bingcd( + pub const fn optimized_bingcd_( &self, rhs: &Uint, ) -> Self { @@ -172,7 +196,7 @@ mod tests { } mod test_bingcd_large { - use crate::{Gcd, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64}; + use crate::{Gcd, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512}; use rand_core::OsRng; fn bingcd_large_test(lhs: Uint, rhs: Uint) @@ -180,10 +204,7 @@ mod tests { Uint: Gcd>, { let gcd = lhs.gcd(&rhs); - let bingcd = lhs - .to_odd() - .unwrap() - .optimized_bingcd::<62, { U64::LIMBS }, { U128::LIMBS }>(&rhs); + let bingcd = lhs.to_odd().unwrap().optimized_bingcd(&rhs); assert_eq!(gcd, bingcd); } From 23a8dd509736e3a386926547dde7d89d0adeb7c4 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 6 Feb 2025 11:07:36 +0100 Subject: [PATCH 081/203] Tune `optimized_bingcd` parameters --- src/uint/bingcd/gcd.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index cfca4e5ab..4497d196f 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -86,7 +86,7 @@ impl Odd> { /// #[inline(always)] pub const fn optimized_bingcd(&self, rhs: &Uint) -> Self { - self.optimized_bingcd_::<{ U64::BITS - 2 }, { U64::LIMBS }, { U128::LIMBS }>(rhs) + self.optimized_bingcd_::<{ U64::BITS }, { U64::LIMBS }, { U128::LIMBS }>(rhs) } /// Computes `gcd(self, rhs)`, leveraging the optimized Binary GCD algorithm. @@ -174,7 +174,7 @@ mod tests { bingcd_small_test(Uint::MAX, Uint::MAX); // Randomized test cases - for _ in 0..100 { + for _ in 0..1000 { let x = Uint::::random(&mut OsRng).bitor(&Uint::ONE); let y = Uint::::random(&mut OsRng); bingcd_small_test(x, y); @@ -221,7 +221,7 @@ mod tests { bingcd_large_test(Uint::MAX, Uint::MAX); // Randomized testing - for _ in 0..100 { + for _ in 0..1000 { let x = Uint::::random(&mut OsRng).bitor(&Uint::ONE); let y = Uint::::random(&mut OsRng); bingcd_large_test(x, y); From 2d3df095b5319212188533274c4a0da54ce353ca Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 6 Feb 2025 11:30:15 +0100 Subject: [PATCH 082/203] Make `compact` generic in `K` --- src/uint/bingcd/gcd.rs | 4 ++-- src/uint/bingcd/tools.rs | 27 ++++++++++++++------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index 4497d196f..b55cc5534 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -117,8 +117,8 @@ impl Odd> { // Construct a_ and b_ as the summary of a and b, respectively. let n = const_max(2 * K, const_max(a.bits(), b.bits())); - let a_ = a.compact::(n, K); - let b_ = b.compact::(n, K); + let a_ = a.compact::(n); + let b_ = b.compact::(n); // Compute the K-1 iteration update matrix from a_ and b_ let (matrix, log_upper_bound) = a_ diff --git a/src/uint/bingcd/tools.rs b/src/uint/bingcd/tools.rs index 3706f8cd0..49e423a0e 100644 --- a/src/uint/bingcd/tools.rs +++ b/src/uint/bingcd/tools.rs @@ -14,8 +14,10 @@ impl Uint { /// Construct a [Uint] containing the bits in `self` in the range `[idx, idx + length)`. /// /// Assumes `length ≤ Uint::::BITS` and `idx + length ≤ Self::BITS`. + /// + /// Executes in time variable in `length` only. #[inline(always)] - pub(super) const fn section( + pub(super) const fn section_vartime_length( &self, idx: u32, length: u32, @@ -23,7 +25,7 @@ impl Uint { debug_assert!(length <= Uint::::BITS); debug_assert!(idx + length <= Self::BITS); - let mask = Uint::ONE.shl(length).wrapping_sub(&Uint::ONE); + let mask = Uint::ONE.shl_vartime(length).wrapping_sub(&Uint::ONE); self.shr(idx).resize::().bitand(&mask) } @@ -31,7 +33,7 @@ impl Uint { /// /// Assumes `length ≤ Uint::::BITS` and `idx + length ≤ Self::BITS`. /// - /// Executes in time variable in `idx` only. + /// Executes in time variable in `idx` and `length`. #[inline(always)] pub(super) const fn section_vartime( &self, @@ -47,22 +49,21 @@ impl Uint { .bitand(&mask) } - /// Compact `self` to a form containing the concatenation of its bit ranges `[0, k-1)` - /// and `[n-k-1, n)`. + /// Compact `self` to a form containing the concatenation of its bit ranges `[0, K-1)` + /// and `[n-K-1, n)`. /// - /// Assumes `k ≤ Uint::::BITS`, `n ≤ Self::BITS` and `n ≥ 2k`. + /// Assumes `K ≤ Uint::::BITS`, `n ≤ Self::BITS` and `n ≥ 2K`. #[inline(always)] - pub(super) const fn compact( + pub(super) const fn compact( &self, n: u32, - k: u32, ) -> Uint { - debug_assert!(k <= Uint::::BITS); + debug_assert!(K <= Uint::::BITS); debug_assert!(n <= Self::BITS); - debug_assert!(n >= 2 * k); + debug_assert!(n >= 2 * K); - let hi = self.section(n - k - 1, k + 1); - let lo = self.section_vartime(0, k - 1); - hi.shl_vartime(k - 1).bitxor(&lo) + let hi = self.section_vartime_length(n - K - 1, K + 1); + let lo = self.section_vartime(0, K - 1); + hi.shl_vartime(K - 1).bitxor(&lo) } } From 30189d1bb515270b33727fc211b6de049d788b28 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 6 Feb 2025 11:30:35 +0100 Subject: [PATCH 083/203] Annotate the use of `shl_vartime` and `shr_vartime` --- src/uint/bingcd/extension.rs | 2 ++ src/uint/bingcd/gcd.rs | 4 +++- src/uint/bingcd/matrix.rs | 2 ++ src/uint/bingcd/tools.rs | 5 +++++ src/uint/bingcd/xgcd.rs | 2 ++ 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/uint/bingcd/extension.rs b/src/uint/bingcd/extension.rs index f907e9bef..4c7b755d1 100644 --- a/src/uint/bingcd/extension.rs +++ b/src/uint/bingcd/extension.rs @@ -50,6 +50,8 @@ impl ExtendedUint { // Apply carry let limb_diff = LIMBS.wrapping_sub(EXTRA) as u32; + // safe to vartime; shr_vartime is variable in the value of shift only. Since this shift + // is a public constant, the constant time property of this algorithm is not impacted. let carry = carry.resize::().shl_vartime(limb_diff * Limb::BITS); lo = lo.bitxor(&carry); diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index b55cc5534..4c74ce206 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -65,7 +65,9 @@ impl Odd> { // subtract a from b when b is odd b = Uint::select(&b, &b.wrapping_sub(&a), b_odd); - // Div b by two + // Div b by two. + // safe to vartime; shr_vartime is variable in the value of shift only. Since this shift + // is a public constant, the constant time property of this algorithm is not impacted. b = b.shr_vartime(1); } diff --git a/src/uint/bingcd/matrix.rs b/src/uint/bingcd/matrix.rs index 0b622e49d..e1837eeda 100644 --- a/src/uint/bingcd/matrix.rs +++ b/src/uint/bingcd/matrix.rs @@ -56,6 +56,8 @@ impl IntMatrix { /// Double the right column of this matrix if `double` is truthy. Otherwise, do nothing. #[inline] pub(crate) const fn conditional_double_top_row(&mut self, double: ConstChoice) { + // safe to vartime; shr_vartime is variable in the value of shift only. Since this shift + // is a public constant, the constant time property of this algorithm is not impacted. self.m00 = Int::select(&self.m00, &self.m00.shl_vartime(1), double); self.m01 = Int::select(&self.m01, &self.m01.shl_vartime(1), double); } diff --git a/src/uint/bingcd/tools.rs b/src/uint/bingcd/tools.rs index 49e423a0e..df06a0497 100644 --- a/src/uint/bingcd/tools.rs +++ b/src/uint/bingcd/tools.rs @@ -62,8 +62,13 @@ impl Uint { debug_assert!(n <= Self::BITS); debug_assert!(n >= 2 * K); + // safe to vartime; this function is vartime in length only, which is a public constant let hi = self.section_vartime_length(n - K - 1, K + 1); + // safe to vartime; this function is vartime in idx and length only, which are both public + // constants let lo = self.section_vartime(0, K - 1); + // safe to vartime; shl_vartime is variable in the value of shift only. Since this shift + // is a public constant, the constant time property of this algorithm is not impacted. hi.shl_vartime(K - 1).bitxor(&lo) } } diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index c9abb489d..5147b2179 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -40,6 +40,8 @@ impl Odd> { // Div b by two and double the top row of the matrix when a, b ≠ 0. let do_apply = a.is_nonzero().and(b.is_nonzero()); + // safe to vartime; shr_vartime is variable in the value of shift only. Since this shift + // is a public constant, the constant time property of this algorithm is not impacted. b = Uint::select(&b, &b.shr_vartime(1), do_apply); matrix.conditional_double_top_row(do_apply); log_upper_bound = do_apply.select_u32(log_upper_bound, log_upper_bound + 1); From 2e930211d329fbe85b4332eb2d909fe0bcaa8e4d Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 6 Feb 2025 11:37:25 +0100 Subject: [PATCH 084/203] Take `iterations` out of the `optimized_bingcd_` loop --- src/uint/bingcd/gcd.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index 4c74ce206..34c275cea 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -113,8 +113,9 @@ impl Odd> { ) -> Self { let (mut a, mut b) = (*self.as_ref(), *rhs); + let iterations = (2 * Self::BITS - 1).div_ceil(K); let mut i = 0; - while i < (2 * Self::BITS - 1).div_ceil(K) { + while i < iterations { i += 1; // Construct a_ and b_ as the summary of a and b, respectively. From d464a02cef5311ff90320e0bce237ca4aee6543c Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 6 Feb 2025 11:46:13 +0100 Subject: [PATCH 085/203] Indicate `restricted_extended_gcd` as `_vartime` in `iterations` --- src/uint/bingcd/gcd.rs | 4 +++- src/uint/bingcd/xgcd.rs | 8 +++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index 34c275cea..be3dea2c8 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -124,10 +124,12 @@ impl Odd> { let b_ = b.compact::(n); // Compute the K-1 iteration update matrix from a_ and b_ + // Safe to vartime; function executes in time variable in `iterations` only, which is + // a public constant K-1 here. let (matrix, log_upper_bound) = a_ .to_odd() .expect("a is always odd") - .restricted_extended_gcd::(&b_, K - 1); + .restricted_extended_gcd_vartime::(&b_, K - 1); // Update `a` and `b` using the update matrix let (updated_a, updated_b) = matrix.extended_apply_to((a, b)); diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 5147b2179..137430bdb 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -10,8 +10,10 @@ impl Odd> { /// `(-2^log_upper_bound, 2^log_upper_bound]`. /// /// Assumes `iterations < Uint::::BITS`. + /// + /// The function executes in time variable in `iterations`. #[inline] - pub(super) const fn restricted_extended_gcd( + pub(super) const fn restricted_extended_gcd_vartime( &self, rhs: &Uint, iterations: u32, @@ -60,7 +62,7 @@ mod tests { fn test_restricted_extended_gcd() { let a = U64::from_be_hex("CA048AFA63CD6A1F").to_odd().unwrap(); let b = U64::from_be_hex("AE693BF7BE8E5566"); - let (matrix, iters) = a.restricted_extended_gcd(&b, 5); + let (matrix, iters) = a.restricted_extended_gcd_vartime(&b, 5); assert_eq!(iters, 5); assert_eq!( matrix, @@ -73,7 +75,7 @@ mod tests { // Stop before max_iters let a = U64::from_be_hex("0000000003CD6A1F").to_odd().unwrap(); let b = U64::from_be_hex("000000000E8E5566"); - let (.., iters) = a.restricted_extended_gcd::<{ U64::LIMBS }>(&b, 60); + let (.., iters) = a.restricted_extended_gcd_vartime::<{ U64::LIMBS }>(&b, 60); assert_eq!(iters, 35); } } From 579d93ed0bb3b291dc9fd3499b339d7797b362da Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 6 Feb 2025 11:59:31 +0100 Subject: [PATCH 086/203] Revert "Remove sneaky swap operation" * This reverts commit 08974398a8b7d0073b4295ee1a23b901457b89df * This adds further annotation --- src/uint/bingcd/gcd.rs | 36 +++++++++++++++++++++--------------- src/uint/bingcd/matrix.rs | 36 ++++++++++++++++++------------------ src/uint/bingcd/xgcd.rs | 25 +++++++++++++------------ 3 files changed, 52 insertions(+), 45 deletions(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index be3dea2c8..917e33186 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -34,6 +34,10 @@ impl Odd> { #[inline(always)] pub const fn bingcd(&self, rhs: &Uint) -> Self { // Todo: tweak this threshold + // Note: we're swapping the parameters here for greater readability: Pornin's Algorithm 1 + // and Algorithm 2 both require the second argument (m) to be odd. Given that the gcd + // is the same, regardless of the order of the parameters, this swap does not affect the + // result. if LIMBS < 8 { self.classic_bingcd(rhs) } else { @@ -50,28 +54,29 @@ impl Odd> { /// #[inline] pub const fn classic_bingcd(&self, rhs: &Uint) -> Self { - let (mut a, mut b) = (*self.as_ref(), *rhs); + // (self, rhs) corresponds to (m, y) in the Algorithm 1 notation. + let (mut a, mut b) = (*rhs, *self.as_ref()); let mut j = 0; while j < (2 * Self::BITS - 1) { j += 1; - let b_odd = b.is_odd(); + let a_odd = a.is_odd(); - // swap if b odd and a > b - let a_gt_b = Uint::gt(&a, &b); - let do_swap = b_odd.and(a_gt_b); + // swap if a odd and a < b + let a_lt_b = Uint::lt(&a, &b); + let do_swap = a_odd.and(a_lt_b); Uint::conditional_swap(&mut a, &mut b, do_swap); - // subtract a from b when b is odd - b = Uint::select(&b, &b.wrapping_sub(&a), b_odd); + // subtract b from a when a is odd + a = Uint::select(&a, &a.wrapping_sub(&b), a_odd); - // Div b by two. + // Div a by two. // safe to vartime; shr_vartime is variable in the value of shift only. Since this shift // is a public constant, the constant time property of this algorithm is not impacted. - b = b.shr_vartime(1); + a = a.shr_vartime(1); } - a.to_odd() + b.to_odd() .expect("gcd of an odd value with something else is always odd") } @@ -111,7 +116,8 @@ impl Odd> { &self, rhs: &Uint, ) -> Self { - let (mut a, mut b) = (*self.as_ref(), *rhs); + // (self, rhs) corresponds to (m, y) in the Algorithm 1 notation. + let (mut a, mut b) = (*rhs, *self.as_ref()); let iterations = (2 * Self::BITS - 1).div_ceil(K); let mut i = 0; @@ -126,10 +132,10 @@ impl Odd> { // Compute the K-1 iteration update matrix from a_ and b_ // Safe to vartime; function executes in time variable in `iterations` only, which is // a public constant K-1 here. - let (matrix, log_upper_bound) = a_ + let (matrix, log_upper_bound) = b_ .to_odd() - .expect("a is always odd") - .restricted_extended_gcd_vartime::(&b_, K - 1); + .expect("b_ is always odd") + .restricted_extended_gcd_vartime::(&a_, K - 1); // Update `a` and `b` using the update matrix let (updated_a, updated_b) = matrix.extended_apply_to((a, b)); @@ -144,7 +150,7 @@ impl Odd> { .expect("extension is zero"); } - a.to_odd() + b.to_odd() .expect("gcd of an odd value with something else is always odd") } } diff --git a/src/uint/bingcd/matrix.rs b/src/uint/bingcd/matrix.rs index e1837eeda..be38234d1 100644 --- a/src/uint/bingcd/matrix.rs +++ b/src/uint/bingcd/matrix.rs @@ -48,18 +48,18 @@ impl IntMatrix { /// Subtract the bottom row from the top if `subtract` is truthy. Otherwise, do nothing. #[inline] - pub(crate) const fn conditional_subtract_top_row_from_bottom(&mut self, subtract: ConstChoice) { - self.m10 = Int::select(&self.m10, &self.m10.wrapping_sub(&self.m00), subtract); - self.m11 = Int::select(&self.m11, &self.m11.wrapping_sub(&self.m01), subtract); + pub(crate) const fn conditional_subtract_bottom_row_from_top(&mut self, subtract: ConstChoice) { + self.m00 = Int::select(&self.m00, &self.m00.wrapping_sub(&self.m10), subtract); + self.m01 = Int::select(&self.m01, &self.m01.wrapping_sub(&self.m11), subtract); } /// Double the right column of this matrix if `double` is truthy. Otherwise, do nothing. #[inline] - pub(crate) const fn conditional_double_top_row(&mut self, double: ConstChoice) { + pub(crate) const fn conditional_double_bottom_row(&mut self, double: ConstChoice) { // safe to vartime; shr_vartime is variable in the value of shift only. Since this shift // is a public constant, the constant time property of this algorithm is not impacted. - self.m00 = Int::select(&self.m00, &self.m00.shl_vartime(1), double); - self.m01 = Int::select(&self.m01, &self.m01.shl_vartime(1), double); + self.m10 = Int::select(&self.m10, &self.m10.shl_vartime(1), double); + self.m11 = Int::select(&self.m11, &self.m11.shl_vartime(1), double); } } @@ -95,16 +95,16 @@ mod tests { #[test] fn test_conditional_subtract() { let mut y = X.clone(); - y.conditional_subtract_top_row_from_bottom(ConstChoice::FALSE); + y.conditional_subtract_bottom_row_from_top(ConstChoice::FALSE); assert_eq!(y, X); - y.conditional_subtract_top_row_from_bottom(ConstChoice::TRUE); + y.conditional_subtract_bottom_row_from_top(ConstChoice::TRUE); assert_eq!( y, IntMatrix::new( - Int::from(1i32), - Int::from(7i32), - Int::from(22i32), - Int::from(46i32) + Int::from(-22i32), + Int::from(-46i32), + Int::from(23i32), + Int::from(53i32) ) ); } @@ -112,16 +112,16 @@ mod tests { #[test] fn test_conditional_double() { let mut y = X.clone(); - y.conditional_double_top_row(ConstChoice::FALSE); + y.conditional_double_bottom_row(ConstChoice::FALSE); assert_eq!(y, X); - y.conditional_double_top_row(ConstChoice::TRUE); + y.conditional_double_bottom_row(ConstChoice::TRUE); assert_eq!( y, IntMatrix::new( - Int::from(2i32), - Int::from(14i32), - Int::from(23i32), - Int::from(53i32), + Int::from(1i32), + Int::from(7i32), + Int::from(46i32), + Int::from(106i32), ) ); } diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 137430bdb..d704060e5 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -19,7 +19,8 @@ impl Odd> { iterations: u32, ) -> (IntMatrix, u32) { debug_assert!(iterations < Uint::::BITS); - let (mut a, mut b) = (*self.as_ref(), *rhs); + // (self, rhs) corresponds to (b_, a_) in the Algorithm 1 notation. + let (mut a, mut b) = (*rhs, *self.as_ref()); // Compute the update matrix. let mut matrix = IntMatrix::UNIT; @@ -28,24 +29,24 @@ impl Odd> { while j < iterations { j += 1; - let b_odd = b.is_odd(); - let a_gt_b = Uint::gt(&a, &b); + let a_odd = a.is_odd(); + let a_lt_b = Uint::lt(&a, &b); - // swap if b odd and a > b - let do_swap = b_odd.and(a_gt_b); + // swap if a odd and a < b + let do_swap = a_odd.and(a_lt_b); Uint::conditional_swap(&mut a, &mut b, do_swap); matrix.conditional_swap_rows(do_swap); - // subtract a from b when b is odd - b = Uint::select(&b, &b.wrapping_sub(&a), b_odd); - matrix.conditional_subtract_top_row_from_bottom(b_odd); + // subtract b from a when a is odd + a = Uint::select(&a, &a.wrapping_sub(&b), a_odd); + matrix.conditional_subtract_bottom_row_from_top(a_odd); - // Div b by two and double the top row of the matrix when a, b ≠ 0. + // Div a by 2 and double the bottom row of the matrix when a, b ≠ 0. let do_apply = a.is_nonzero().and(b.is_nonzero()); // safe to vartime; shr_vartime is variable in the value of shift only. Since this shift // is a public constant, the constant time property of this algorithm is not impacted. - b = Uint::select(&b, &b.shr_vartime(1), do_apply); - matrix.conditional_double_top_row(do_apply); + a = Uint::select(&a, &a.shr_vartime(1), do_apply); + matrix.conditional_double_bottom_row(do_apply); log_upper_bound = do_apply.select_u32(log_upper_bound, log_upper_bound + 1); } @@ -66,7 +67,7 @@ mod tests { assert_eq!(iters, 5); assert_eq!( matrix, - IntMatrix::new(I64::from(8), I64::from(-4), I64::from(-2), I64::from(5)) + IntMatrix::new(I64::from(5), I64::from(-2), I64::from(-4), I64::from(8)) ); } From 89d4d29cde4aa7f41f3bfee35b34885d6ce96c02 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 6 Feb 2025 12:30:05 +0100 Subject: [PATCH 087/203] Annotate `partial_binxgcd_vartime` --- src/uint/bingcd/gcd.rs | 2 +- src/uint/bingcd/xgcd.rs | 41 +++++++++++++++++++++++++---------------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index 917e33186..d10f746a9 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -135,7 +135,7 @@ impl Odd> { let (matrix, log_upper_bound) = b_ .to_odd() .expect("b_ is always odd") - .restricted_extended_gcd_vartime::(&a_, K - 1); + .partial_binxgcd_vartime::(&a_, K - 1); // Update `a` and `b` using the update matrix let (updated_a, updated_b) = matrix.extended_apply_to((a, b)); diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index d704060e5..2adcd1597 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -2,18 +2,24 @@ use crate::uint::bingcd::matrix::IntMatrix; use crate::{Odd, Uint}; impl Odd> { - /// Constructs a matrix `M` s.t. for `(A, B) = M(a,b)` it holds that - /// - `gcd(A, B) = gcd(a, b)`, and - /// - `A.bits() < a.bits()` and/or `B.bits() < b.bits()`. + /// Executes the optimized Binary GCD inner loop. /// - /// Moreover, it returns `log_upper_bound: u32` s.t. each element in `M` lies in the interval - /// `(-2^log_upper_bound, 2^log_upper_bound]`. + /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 2. + /// . + /// + /// The function outputs a matrix that can be used to reduce the `a` and `b` in the main loop. + /// + /// This implementation deviates slightly from the paper, in that it "runs in place", i.e., + /// executes iterations that do nothing, once `a` becomes zero. As a result, the main loop + /// can no longer assume that all `iterations` are executed. As such, the `executed_iterations` + /// are counted and additionally returned. Note that each element in `M` lies in the interval + /// `(-2^executed_iterations, 2^executed_iterations]`. /// /// Assumes `iterations < Uint::::BITS`. /// /// The function executes in time variable in `iterations`. #[inline] - pub(super) const fn restricted_extended_gcd_vartime( + pub(super) const fn partial_binxgcd_vartime( &self, rhs: &Uint, iterations: u32, @@ -22,9 +28,9 @@ impl Odd> { // (self, rhs) corresponds to (b_, a_) in the Algorithm 1 notation. let (mut a, mut b) = (*rhs, *self.as_ref()); - // Compute the update matrix. + // Compute the update matrix. This matrix corresponds with (f0, g0, f1, g1) in the paper. let mut matrix = IntMatrix::UNIT; - let mut log_upper_bound = 0; + let mut executed_iterations = 0; let mut j = 0; while j < iterations { j += 1; @@ -41,16 +47,19 @@ impl Odd> { a = Uint::select(&a, &a.wrapping_sub(&b), a_odd); matrix.conditional_subtract_bottom_row_from_top(a_odd); - // Div a by 2 and double the bottom row of the matrix when a, b ≠ 0. - let do_apply = a.is_nonzero().and(b.is_nonzero()); + // Div a by 2. + let double = a.is_nonzero(); // safe to vartime; shr_vartime is variable in the value of shift only. Since this shift // is a public constant, the constant time property of this algorithm is not impacted. - a = Uint::select(&a, &a.shr_vartime(1), do_apply); - matrix.conditional_double_bottom_row(do_apply); - log_upper_bound = do_apply.select_u32(log_upper_bound, log_upper_bound + 1); + a = a.shr_vartime(1); + // Double the bottom row of the matrix when a was ≠ 0 + matrix.conditional_double_bottom_row(double); + + // Something happened in this iteration only when a was non-zero before being halved. + executed_iterations = double.select_u32(executed_iterations, executed_iterations + 1); } - (matrix, log_upper_bound) + (matrix, executed_iterations) } } @@ -63,7 +72,7 @@ mod tests { fn test_restricted_extended_gcd() { let a = U64::from_be_hex("CA048AFA63CD6A1F").to_odd().unwrap(); let b = U64::from_be_hex("AE693BF7BE8E5566"); - let (matrix, iters) = a.restricted_extended_gcd_vartime(&b, 5); + let (matrix, iters) = a.partial_binxgcd_vartime(&b, 5); assert_eq!(iters, 5); assert_eq!( matrix, @@ -76,7 +85,7 @@ mod tests { // Stop before max_iters let a = U64::from_be_hex("0000000003CD6A1F").to_odd().unwrap(); let b = U64::from_be_hex("000000000E8E5566"); - let (.., iters) = a.restricted_extended_gcd_vartime::<{ U64::LIMBS }>(&b, 60); + let (.., iters) = a.partial_binxgcd_vartime::<{ U64::LIMBS }>(&b, 60); assert_eq!(iters, 35); } } From fc686d183f9d03548b7be90388c0d010c7d7e7d6 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 6 Feb 2025 13:17:13 +0100 Subject: [PATCH 088/203] Fix clippy --- src/uint/bingcd/gcd.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index d10f746a9..34170b92d 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -107,8 +107,8 @@ impl Odd> { /// /// This function is generic over the following three values: /// - `K`: the number of bits used when summarizing `self` and `rhs` for the inner loop. The - /// `K+1` top bits and `K-1` least significant bits are selected. It is recommended to keep `K` - /// close to a (multiple of) the number of bits that fit in a single register. + /// `K+1` top bits and `K-1` least significant bits are selected. It is recommended to keep + /// `K` close to a (multiple of) the number of bits that fit in a single register. /// - `LIMBS_K`: should be chosen as the minimum number s.t. `Uint::::BITS ≥ K`, /// - `LIMBS_2K`: should be chosen as the minimum number s.t. `Uint::::BITS ≥ 2K`. #[inline(always)] From b782790a60df03453f73c0b0242b462dc1e39277 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 7 Feb 2025 09:36:19 +0100 Subject: [PATCH 089/203] Fix docstring `Matrix::conditional_double_bottom_row` --- src/uint/bingcd/matrix.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uint/bingcd/matrix.rs b/src/uint/bingcd/matrix.rs index be38234d1..3b619fceb 100644 --- a/src/uint/bingcd/matrix.rs +++ b/src/uint/bingcd/matrix.rs @@ -53,7 +53,7 @@ impl IntMatrix { self.m01 = Int::select(&self.m01, &self.m01.wrapping_sub(&self.m11), subtract); } - /// Double the right column of this matrix if `double` is truthy. Otherwise, do nothing. + /// Double the bottom row of this matrix if `double` is truthy. Otherwise, do nothing. #[inline] pub(crate) const fn conditional_double_bottom_row(&mut self, double: ConstChoice) { // safe to vartime; shr_vartime is variable in the value of shift only. Since this shift From 859bbb9265ac0d01ecbe2b30345627bcd7ac69d6 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 7 Feb 2025 11:02:31 +0100 Subject: [PATCH 090/203] Optimize conditional sub operation in `classic_bingcd` --- src/uint/bingcd/gcd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index 34170b92d..3e3378c1a 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -68,7 +68,7 @@ impl Odd> { Uint::conditional_swap(&mut a, &mut b, do_swap); // subtract b from a when a is odd - a = Uint::select(&a, &a.wrapping_sub(&b), a_odd); + a = a.wrapping_sub(&Uint::select(&Uint::ZERO, &b, a_odd)); // Div a by two. // safe to vartime; shr_vartime is variable in the value of shift only. Since this shift From 909ab91297434b0a89845a5e233f2ca5b31b44b3 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 7 Feb 2025 13:01:29 +0100 Subject: [PATCH 091/203] Fix binxgcd bug --- src/uint/bingcd/gcd.rs | 2 +- src/uint/bingcd/xgcd.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index b030540f4..1acaed629 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -148,7 +148,7 @@ impl Odd> { .partial_binxgcd_vartime::(&a_, K - 1); // Update `a` and `b` using the update matrix - let (updated_a, updated_b) = matrix.extended_apply_to((a, b)); + let (updated_b, updated_a) = matrix.extended_apply_to((b, a)); (a, _) = updated_a .div_2k(log_upper_bound) diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 6361d350f..d62ff09f5 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -139,6 +139,7 @@ impl Odd> { // Compute the update matrix. This matrix corresponds with (f0, g0, f1, g1) in the paper. let mut matrix = IntMatrix::UNIT; + matrix.conditional_swap_rows(ConstChoice::TRUE); let mut executed_iterations = 0; let mut j = 0; while j < iterations { @@ -146,6 +147,7 @@ impl Odd> { j += 1; } + matrix.conditional_swap_rows(ConstChoice::TRUE); ( b.to_odd().expect("b is always odd"), a, From 3bb5b95aa096d582bc292eb8d21c422e1ce5a111 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 7 Feb 2025 13:01:54 +0100 Subject: [PATCH 092/203] Introduce `classic_binxgcd` --- benches/uint.rs | 18 +++++++++ src/uint/bingcd/xgcd.rs | 87 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/benches/uint.rs b/benches/uint.rs index ec8492efb..4a38b9960 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -391,6 +391,24 @@ fn xgcd_bench( ) where Odd>: PrecomputeInverter>, { + g.bench_function(BenchmarkId::new("classic binxgcd", LIMBS), |b| { + b.iter_batched( + || { + let modulus = Uint::MAX.shr_vartime(1).to_nz().unwrap(); + let f = Uint::::random_mod(&mut OsRng, &modulus) + .bitor(&Uint::ONE) + .to_odd() + .unwrap(); + let g = Uint::::random_mod(&mut OsRng, &modulus) + .bitor(&Uint::ONE) + .to_odd() + .unwrap(); + (f, g) + }, + |(f, g)| black_box(f.classic_binxgcd(&g)), + BatchSize::SmallInput, + ) + }); g.bench_function(BenchmarkId::new("binxgcd", LIMBS), |b| { b.iter_batched( || { diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index d62ff09f5..1fc016a51 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -49,6 +49,20 @@ impl Uint { } impl Odd> { + pub const fn classic_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { + let total_iterations = 2 * Self::BITS - 1; + + let (gcd, _, matrix, total_bound_shift) = + self.partial_binxgcd_vartime(rhs.as_ref(), total_iterations); + + // Extract the Bezout coefficients. + let IntMatrix { m00, m01, .. } = matrix; + let x = m00.div_2k_mod_q(total_bound_shift, total_iterations, rhs); + let y = m01.div_2k_mod_q(total_bound_shift, total_iterations, self); + + (gcd, x, y) + } + /// Given `(self, rhs)`, compute `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. /// /// TODO: this only works for `self` and `rhs` that are <= Int::MAX. @@ -133,7 +147,7 @@ impl Odd> { rhs: &Uint, iterations: u32, ) -> (Self, Uint, IntMatrix, u32) { - debug_assert!(iterations < Uint::::BITS); + // debug_assert!(iterations < Uint::::BITS); // (self, rhs) corresponds to (b_, a_) in the Algorithm 1 notation. let (mut a, mut b) = (*rhs, *self.as_ref()); @@ -233,6 +247,77 @@ mod tests { } } + mod test_classic_binxgcd { + use crate::{ + ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, U4096, + U512, U768, U8192, + }; + use rand_core::OsRng; + + fn classic_binxgcd_test( + lhs: Uint, + rhs: Uint, + ) where + Uint: + Gcd> + ConcatMixed, MixedOutput = Uint>, + { + let gcd = lhs.gcd(&rhs); + let (binxgcd, x, y) = lhs + .to_odd() + .unwrap() + .classic_binxgcd(&rhs.to_odd().unwrap()); + assert_eq!(gcd, binxgcd); + + // test bezout coefficients + let prod = x.widening_mul_uint(&lhs) + y.widening_mul_uint(&rhs); + assert_eq!( + prod, + binxgcd.resize().as_int(), + "{} {} {} {}", + lhs, + rhs, + x, + y + ); + } + + fn classic_binxgcd_tests() + where + Uint: + Gcd> + ConcatMixed, MixedOutput = Uint>, + { + // upper bound + let upper_bound = *Int::MAX.as_uint(); + + // Edge cases + classic_binxgcd_test(Uint::ONE, Uint::ONE); + classic_binxgcd_test(Uint::ONE, upper_bound); + classic_binxgcd_test(upper_bound, Uint::ONE); + classic_binxgcd_test(upper_bound, upper_bound); + + // Randomized test cases + let bound = upper_bound.wrapping_add(&Uint::ONE).to_nz().unwrap(); + for _ in 0..100 { + let x = Uint::::random_mod(&mut OsRng, &bound).bitor(&Uint::ONE); + let y = Uint::::random_mod(&mut OsRng, &bound).bitor(&Uint::ONE); + classic_binxgcd_test(x, y); + } + } + + #[test] + fn test_classic_binxgcd() { + // Cannot be applied to U64 + classic_binxgcd_tests::<{ U128::LIMBS }, { U256::LIMBS }>(); + classic_binxgcd_tests::<{ U192::LIMBS }, { U384::LIMBS }>(); + classic_binxgcd_tests::<{ U256::LIMBS }, { U512::LIMBS }>(); + classic_binxgcd_tests::<{ U384::LIMBS }, { U768::LIMBS }>(); + classic_binxgcd_tests::<{ U512::LIMBS }, { U1024::LIMBS }>(); + classic_binxgcd_tests::<{ U1024::LIMBS }, { U2048::LIMBS }>(); + classic_binxgcd_tests::<{ U2048::LIMBS }, { U4096::LIMBS }>(); + classic_binxgcd_tests::<{ U4096::LIMBS }, { U8192::LIMBS }>(); + } + } + mod test_binxgcd { use crate::{ ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, U4096, From 42822041871be98ec3c62dfae2af1055ab83f7bf Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 7 Feb 2025 14:19:02 +0100 Subject: [PATCH 093/203] Fix doc --- src/uint/bingcd/gcd.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index 1acaed629..1a719ac9e 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -88,8 +88,6 @@ impl Odd> { .shr_vartime(1); } - /// Computes `gcd(self, rhs)`, leveraging the Binary GCD algorithm. - /// Is efficient for larger `LIMBS`. /// Computes `gcd(self, rhs)`, leveraging the optimized Binary GCD algorithm. /// /// Note: this algorithm becomes more efficient than the classical algorithm for [Uint]s with From 8401968feceafaf784ba06580d16fc26df07bce9 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 7 Feb 2025 14:56:59 +0100 Subject: [PATCH 094/203] Expand binxgcd functions --- benches/uint.rs | 2 +- src/uint/bingcd.rs | 18 ++++++- src/uint/bingcd/xgcd.rs | 102 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 112 insertions(+), 10 deletions(-) diff --git a/benches/uint.rs b/benches/uint.rs index 4a38b9960..d2343314b 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -423,7 +423,7 @@ fn xgcd_bench( .unwrap(); (f, g) }, - |(f, g)| black_box(f.binxgcd::<63, { U64::LIMBS }, { U128::LIMBS }>(&g)), + |(f, g)| black_box(f.binxgcd(&g)), BatchSize::SmallInput, ) }); diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index 574da3eb9..b2aea3b98 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -2,7 +2,7 @@ //! which is described by Pornin as Algorithm 2 in "Optimized Binary GCD for Modular Inversion". //! Ref: -use crate::Uint; +use crate::{Int, Uint}; mod extension; mod gcd; @@ -20,6 +20,22 @@ impl Uint { .expect("self is non zero by construction"); Uint::select(self_nz.bingcd(rhs).as_ref(), rhs, self_is_zero) } + + /// Given `(self, rhs)`, computes `(g, x, y)`, s.t. `self * x + rhs * y = g = gcd(self, rhs)`. + pub const fn binxgcd(&self, rhs: &Self) -> (Uint, Int, Int) { + let self_is_zero = self.is_nonzero().not(); + let self_nz = Uint::select(self, &Uint::ONE, self_is_zero) + .to_nz() + .expect("self is non zero by construction"); + let (gcd, mut x, mut y) = self_nz.binxgcd(rhs); + + // Correct for the fact that self might have been zero. + let gcd = Uint::select(gcd.as_ref(), rhs, self_is_zero); + x = Int::select(&x, &Int::ZERO, self_is_zero); + y = Int::select(&y, &Int::ONE, self_is_zero); + + (gcd, x, y) + } } #[cfg(feature = "rand_core")] diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 1fc016a51..9bd58aa19 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -1,6 +1,6 @@ use crate::uint::bingcd::matrix::IntMatrix; -use crate::uint::bingcd::tools::const_max; -use crate::{ConstChoice, Int, Odd, Uint}; +use crate::uint::bingcd::tools::{const_max, const_min}; +use crate::{ConstChoice, Int, NonZero, Odd, Uint, U128, U64}; impl Int { /// Compute `self / 2^k mod q`. Executes in time variable in `k_bound`. This value should be @@ -14,7 +14,9 @@ impl Int { } impl Uint { - /// Compute `self / 2^k mod q`. Executes in time variable in `k_bound`. This value should be + /// Compute `self / 2^k mod q`. + /// + /// Executes in time variable in `k_bound`. This value should be /// chosen as an inclusive upperbound to the value of `k`. #[inline] const fn div_2k_mod_q(mut self, k: u32, k_bound: u32, q: &Odd) -> Self { @@ -48,7 +50,62 @@ impl Uint { } } +impl NonZero> { + /// Execute the classic Binary Extended GCD algorithm. + /// + /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. + pub const fn binxgcd(&self, rhs: &Uint) -> (Self, Int, Int) { + let lhs = self.as_ref(); + // Leverage two GCD identity rules to make self and rhs odd. + // 1) gcd(2a, 2b) = 2 * gcd(a, b) + // 2) gcd(a, 2b) = gcd(a, b) if a is odd. + let i = lhs.is_nonzero().select_u32(0, lhs.trailing_zeros()); + let j = rhs.is_nonzero().select_u32(0, rhs.trailing_zeros()); + let k = const_min(i, j); + + let lhs = lhs + .shr(i) + .to_odd() + .expect("lhs.shr(i) is odd by construction"); + let rhs = rhs + .shr(j) + .to_odd() + .expect("rhs.shr(i) is odd by construction"); + + let (gcd, x, y) = lhs.binxgcd(&rhs); + ( + gcd.as_ref() + .shl(k) + .to_nz() + .expect("gcd of non-zero element with another element is non-zero"), + x, + y, + ) + } +} + impl Odd> { + /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, + /// leveraging the Binary Extended GCD algorithm. + /// + /// This function switches between the "classic" and "optimized" algorithm at a best-effort + /// threshold. When using [Uint]s with `LIMBS` close to the threshold, it may be useful to + /// manually test whether the classic or optimized algorithm is faster for your machine. + pub const fn binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { + // todo: optimize theshold + if LIMBS < 5 { + self.classic_binxgcd(&rhs) + } else { + self.optimized_binxgcd(&rhs) + } + } + + /// Execute the classic Binary Extended GCD algorithm. + /// + /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. + /// + /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 1. + /// . pub const fn classic_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { let total_iterations = 2 * Self::BITS - 1; @@ -63,10 +120,39 @@ impl Odd> { (gcd, x, y) } - /// Given `(self, rhs)`, compute `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. + /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, + /// leveraging the Binary Extended GCD algorithm. /// - /// TODO: this only works for `self` and `rhs` that are <= Int::MAX. - pub const fn binxgcd( + /// Note: this algorithm becomes more efficient than the classical algorithm for [Uint]s with + /// relatively many `LIMBS`. A best-effort threshold is presented in [Self::binxgcd]. + /// + /// Note: the full algorithm has an additional parameter; this function selects the best-effort + /// value for this parameter. You might be able to further tune your performance by calling the + /// [Self::optimized_bingcd_] function directly. + /// + /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 2. + /// . + pub const fn optimized_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { + self.optimized_binxgcd_::<{ U64::BITS }, { U64::LIMBS }, { U128::LIMBS }>(&rhs) + } + + /// Given `(self, rhs)`, computes `(g, x, y)`, s.t. `self * x + rhs * y = g = gcd(self, rhs)`, + /// leveraging the optimized Binary Extended GCD algorithm. + /// + /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 2. + /// + /// + /// In summary, the optimized algorithm does not operate on `self` and `rhs` directly, but + /// instead of condensed summaries that fit in few registers. Based on these summaries, an + /// update matrix is constructed by which `self` and `rhs` are updated in larger steps. + /// + /// This function is generic over the following three values: + /// - `K`: the number of bits used when summarizing `self` and `rhs` for the inner loop. The + /// `K+1` top bits and `K-1` least significant bits are selected. It is recommended to keep + /// `K` close to a (multiple of) the number of bits that fit in a single register. + /// - `LIMBS_K`: should be chosen as the minimum number s.t. `Uint::::BITS ≥ K`, + /// - `LIMBS_2K`: should be chosen as the minimum number s.t. `Uint::::BITS ≥ 2K`. + pub const fn optimized_binxgcd_( &self, rhs: &Self, ) -> (Self, Int, Int) { @@ -321,7 +407,7 @@ mod tests { mod test_binxgcd { use crate::{ ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, U4096, - U512, U64, U768, U8192, + U512, U768, U8192, }; use rand_core::OsRng; @@ -334,7 +420,7 @@ mod tests { let (binxgcd, x, y) = lhs .to_odd() .unwrap() - .binxgcd::<64, { U64::LIMBS }, { U128::LIMBS }>(&rhs.to_odd().unwrap()); + .optimized_binxgcd(&rhs.to_odd().unwrap()); assert_eq!(gcd, binxgcd); // test bezout coefficients From 02ba25b22f9ebc16331e98e3d0c9f325d8d26e13 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 7 Feb 2025 15:01:09 +0100 Subject: [PATCH 095/203] Introduce `Int::binxgcd` --- src/int.rs | 1 + src/int/bingcd.rs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 src/int/bingcd.rs diff --git a/src/int.rs b/src/int.rs index bd304ccbf..3e39692c2 100644 --- a/src/int.rs +++ b/src/int.rs @@ -12,6 +12,7 @@ use crate::Encoding; use crate::{Bounded, ConstChoice, ConstCtOption, Constants, Limb, NonZero, Odd, Uint, Word}; mod add; +mod bingcd; mod bit_and; mod bit_not; mod bit_or; diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs new file mode 100644 index 000000000..f0a08bd66 --- /dev/null +++ b/src/int/bingcd.rs @@ -0,0 +1,17 @@ +//! This module implements (a constant variant of) the Optimized Extended Binary GCD algorithm, +//! which is described by Pornin in "Optimized Binary GCD for Modular Inversion". +//! Ref: + +use crate::{Int, Uint}; + +impl Int { + /// Executes the Binary Extended GCD algorithm. + /// + /// Given `(self, rhs)`, computes `(g, x, y)`, s.t. `self * x + rhs * y = g = gcd(self, rhs)`. + pub const fn binxgcd(&self, rhs: &Self) -> (Uint, Self, Self) { + let (abs_self, sgn_self) = self.abs_sign(); + let (abs_rhs, sgn_rhs) = rhs.abs_sign(); + let (gcd, x, y) = abs_self.binxgcd(&abs_rhs); + (gcd, x.wrapping_neg_if(sgn_self), y.wrapping_neg_if(sgn_rhs)) + } +} From beb46dd8e39dd2947b4abe4b9b37cb75450b4555 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 10 Feb 2025 12:09:58 +0100 Subject: [PATCH 096/203] Attempt to get binxgcd to work for even numbers --- benches/uint.rs | 2 +- src/const_choice.rs | 28 ++++ src/int/bingcd.rs | 295 +++++++++++++++++++++++++++++++++++++++- src/int/sign.rs | 16 ++- src/odd.rs | 10 +- src/uint.rs | 3 +- src/uint/bingcd.rs | 16 +-- src/uint/bingcd/xgcd.rs | 53 ++------ 8 files changed, 365 insertions(+), 58 deletions(-) diff --git a/benches/uint.rs b/benches/uint.rs index d2343314b..5da80fa85 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -423,7 +423,7 @@ fn xgcd_bench( .unwrap(); (f, g) }, - |(f, g)| black_box(f.binxgcd(&g)), + |(f, g)| black_box(f.limited_binxgcd(&g)), BatchSize::SmallInput, ) }); diff --git a/src/const_choice.rs b/src/const_choice.rs index 3f20f44d9..b612c307d 100644 --- a/src/const_choice.rs +++ b/src/const_choice.rs @@ -475,6 +475,34 @@ impl ConstCtOption> { } } +impl ConstCtOption>> { + /// Returns the contained value, consuming the `self` value. + /// + /// # Panics + /// + /// Panics if the value is none with a custom panic message provided by + /// `msg`. + #[inline] + pub const fn expect(self, msg: &str) -> NonZero> { + assert!(self.is_some.is_true_vartime(), "{}", msg); + self.value + } +} + +impl ConstCtOption>> { + /// Returns the contained value, consuming the `self` value. + /// + /// # Panics + /// + /// Panics if the value is none with a custom panic message provided by + /// `msg`. + #[inline] + pub const fn expect(self, msg: &str) -> Odd> { + assert!(self.is_some.is_true_vartime(), "{}", msg); + self.value + } +} + impl ConstCtOption> { /// Returns the contained value, consuming the `self` value. /// diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index f0a08bd66..7b0f75941 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -2,16 +2,307 @@ //! which is described by Pornin in "Optimized Binary GCD for Modular Inversion". //! Ref: -use crate::{Int, Uint}; +use crate::uint::bingcd::tools::const_min; +use crate::{ConstChoice, Int, NonZero, Odd, Uint}; impl Int { + /// Compute the gcd of `self` and `rhs` leveraging the Binary GCD algorithm. + pub const fn bingcd(&self, rhs: &Self) -> Uint { + self.abs().bingcd(&rhs.abs()) + } + /// Executes the Binary Extended GCD algorithm. /// /// Given `(self, rhs)`, computes `(g, x, y)`, s.t. `self * x + rhs * y = g = gcd(self, rhs)`. pub const fn binxgcd(&self, rhs: &Self) -> (Uint, Self, Self) { + // Make sure `self` and `rhs` are nonzero. + let self_is_zero = self.is_nonzero().not(); + let self_nz = Int::select(self, &Int::ONE, self_is_zero) + .to_nz() + .expect("self is non zero by construction"); + let rhs_is_zero = rhs.is_nonzero().not(); + let rhs_nz = Int::select(rhs, &Int::ONE, rhs_is_zero) + .to_nz() + .expect("self is non zero by construction"); + + let (gcd, mut x, mut y) = self_nz.binxgcd(&rhs_nz); + + // Account for the case that self or rhs was zero + let gcd = Uint::select(gcd.as_ref(), &rhs.abs(), self_is_zero); + let gcd = Uint::select(&gcd, &self.abs(), rhs_is_zero); + x = Int::select(&x, &Int::ZERO, self_is_zero); + y = Int::select(&y, &Int::ONE, self_is_zero); + x = Int::select(&x, &Int::ONE, rhs_is_zero); + y = Int::select(&y, &Int::ZERO, rhs_is_zero); + + (gcd, x, y) + } +} + +impl NonZero> { + /// Execute the Binary Extended GCD algorithm. + /// + /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. + pub const fn binxgcd(&self, rhs: &Self) -> (NonZero>, Int, Int) { + let (lhs, rhs) = (self.as_ref(), rhs.as_ref()); + // Leverage two GCD identity rules to make self and rhs odd. + // 1) gcd(2a, 2b) = 2 * gcd(a, b) + // 2) gcd(a, 2b) = gcd(a, b) if a is odd. + let i = lhs.is_nonzero().select_u32(0, lhs.0.trailing_zeros()); + let j = rhs.is_nonzero().select_u32(0, rhs.0.trailing_zeros()); + let k = const_min(i, j); + + // TODO: shift by k, instead of i and j, or + // figure out how to make x/2 mod q work for even q... + // which you won't, cuz when q = 0 mod 2, there is no "half" :thinking: + // make it happen with the matrix: multiply the rows by 2^i and 2^j before + // and add `k` to the log bound count ? + // or mul the row of the greater by 2^j-k / 2^i-k + + let lhs_ = lhs + .shr(i) + .to_odd() + .expect("lhs.shr(i) is odd by construction"); + let rhs_ = rhs + .shr(j) + .to_odd() + .expect("rhs.shr(j) is odd by construction"); + + // TODO: at this point the matrix does not align with the values anymore. + + let (gcd, x, y) = lhs_.binxgcd(&rhs_); + + // fix x and y + let lhs_fix = i - k; + let rhs_fix = j - k; + let x = x.div_2k_mod_q(lhs_fix, lhs_fix, &rhs_.abs()); + let y = y.div_2k_mod_q(rhs_fix, rhs_fix, &lhs_.abs()); + + ( + gcd.as_ref() + .shl(k) + .to_nz() + .expect("gcd of non-zero element with another element is non-zero"), + x, + y, + ) + } +} + +impl Odd> { + /// Execute the Binary Extended GCD algorithm. + /// + /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. + pub const fn binxgcd(&self, rhs: &Self) -> (Odd>, Int, Int) { let (abs_self, sgn_self) = self.abs_sign(); let (abs_rhs, sgn_rhs) = rhs.abs_sign(); - let (gcd, x, y) = abs_self.binxgcd(&abs_rhs); + let (gcd, x, y) = abs_self.limited_binxgcd(&abs_rhs); (gcd, x.wrapping_neg_if(sgn_self), y.wrapping_neg_if(sgn_rhs)) } } + +#[cfg(test)] +mod test { + + mod test_int_binxgcd { + use crate::{ + ConcatMixed, Int, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64, + U768, U8192, + }; + use rand_core::OsRng; + + fn int_binxgcd_test( + lhs: Int, + rhs: Int, + ) where + Uint: ConcatMixed, MixedOutput = Uint>, + { + let gcd = lhs.bingcd(&rhs); + let (xgcd, x, y) = lhs.binxgcd(&rhs); + assert_eq!(gcd, xgcd); + assert_eq!( + x.widening_mul(&lhs).wrapping_add(&y.widening_mul(&rhs)), + xgcd.resize().as_int() + ); + } + + fn int_binxgcd_tests() + where + Uint: ConcatMixed, MixedOutput = Uint>, + { + int_binxgcd_test(Int::MIN, Int::MIN); + int_binxgcd_test(Int::MIN, Int::MINUS_ONE); + int_binxgcd_test(Int::MIN, Int::ZERO); + int_binxgcd_test(Int::MIN, Int::ONE); + int_binxgcd_test(Int::MIN, Int::MAX); + int_binxgcd_test(Int::ONE, Int::MIN); + int_binxgcd_test(Int::ONE, Int::MINUS_ONE); + int_binxgcd_test(Int::ONE, Int::ZERO); + int_binxgcd_test(Int::ONE, Int::ONE); + int_binxgcd_test(Int::ONE, Int::MAX); + int_binxgcd_test(Int::ZERO, Int::MIN); + int_binxgcd_test(Int::ZERO, Int::MINUS_ONE); + int_binxgcd_test(Int::ZERO, Int::ZERO); + int_binxgcd_test(Int::ZERO, Int::ONE); + int_binxgcd_test(Int::ZERO, Int::MAX); + int_binxgcd_test(Int::ONE, Int::MIN); + int_binxgcd_test(Int::ONE, Int::MINUS_ONE); + int_binxgcd_test(Int::ONE, Int::ZERO); + int_binxgcd_test(Int::ONE, Int::ONE); + int_binxgcd_test(Int::ONE, Int::MAX); + int_binxgcd_test(Int::MAX, Int::MIN); + int_binxgcd_test(Int::MAX, Int::MINUS_ONE); + int_binxgcd_test(Int::MAX, Int::ZERO); + int_binxgcd_test(Int::MAX, Int::ONE); + int_binxgcd_test(Int::MAX, Int::MAX); + + for _ in 0..100 { + let x = Int::random(&mut OsRng); + let y = Int::random(&mut OsRng); + int_binxgcd_test(x, y); + } + } + + #[test] + fn test_int_binxgcd() { + int_binxgcd_tests::<{ U64::LIMBS }, { U128::LIMBS }>(); + int_binxgcd_tests::<{ U128::LIMBS }, { U256::LIMBS }>(); + int_binxgcd_tests::<{ U192::LIMBS }, { U384::LIMBS }>(); + int_binxgcd_tests::<{ U256::LIMBS }, { U512::LIMBS }>(); + int_binxgcd_tests::<{ U384::LIMBS }, { U768::LIMBS }>(); + int_binxgcd_tests::<{ U512::LIMBS }, { U1024::LIMBS }>(); + int_binxgcd_tests::<{ U1024::LIMBS }, { U2048::LIMBS }>(); + int_binxgcd_tests::<{ U2048::LIMBS }, { U4096::LIMBS }>(); + int_binxgcd_tests::<{ U4096::LIMBS }, { U8192::LIMBS }>(); + } + } + + mod test_nonzero_int_binxgcd { + use crate::{ + ConcatMixed, Int, NonZero, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, + U512, U64, U768, U8192, + }; + use rand_core::OsRng; + + fn nz_int_binxgcd_test( + lhs: NonZero>, + rhs: NonZero>, + ) where + Uint: ConcatMixed, MixedOutput = Uint>, + { + let gcd = lhs.bingcd(&rhs); + let (xgcd, x, y) = lhs.binxgcd(&rhs); + assert_eq!(gcd.to_nz().unwrap(), xgcd); + assert_eq!( + x.widening_mul(&lhs).wrapping_add(&y.widening_mul(&rhs)), + xgcd.resize().as_int() + ); + } + + fn nz_int_binxgcd_tests() + where + Uint: ConcatMixed, MixedOutput = Uint>, + { + // nz_int_binxgcd_test(Int::MIN.to_nz().unwrap(), Int::MIN.to_nz().unwrap()); + nz_int_binxgcd_test(Int::MIN.to_nz().unwrap(), Int::MINUS_ONE.to_nz().unwrap()); + nz_int_binxgcd_test(Int::MIN.to_nz().unwrap(), Int::ONE.to_nz().unwrap()); + nz_int_binxgcd_test(Int::MIN.to_nz().unwrap(), Int::MAX.to_nz().unwrap()); + nz_int_binxgcd_test(Int::ONE.to_nz().unwrap(), Int::MIN.to_nz().unwrap()); + nz_int_binxgcd_test(Int::ONE.to_nz().unwrap(), Int::MINUS_ONE.to_nz().unwrap()); + nz_int_binxgcd_test(Int::ONE.to_nz().unwrap(), Int::ONE.to_nz().unwrap()); + nz_int_binxgcd_test(Int::ONE.to_nz().unwrap(), Int::MAX.to_nz().unwrap()); + nz_int_binxgcd_test(Int::ONE.to_nz().unwrap(), Int::MIN.to_nz().unwrap()); + nz_int_binxgcd_test(Int::ONE.to_nz().unwrap(), Int::MINUS_ONE.to_nz().unwrap()); + nz_int_binxgcd_test(Int::ONE.to_nz().unwrap(), Int::ONE.to_nz().unwrap()); + nz_int_binxgcd_test(Int::ONE.to_nz().unwrap(), Int::MAX.to_nz().unwrap()); + nz_int_binxgcd_test(Int::MAX.to_nz().unwrap(), Int::MIN.to_nz().unwrap()); + nz_int_binxgcd_test(Int::MAX.to_nz().unwrap(), Int::MINUS_ONE.to_nz().unwrap()); + nz_int_binxgcd_test(Int::MAX.to_nz().unwrap(), Int::ONE.to_nz().unwrap()); + nz_int_binxgcd_test(Int::MAX.to_nz().unwrap(), Int::MAX.to_nz().unwrap()); + + for _ in 0..100 { + let x = NonZero::>::random(&mut OsRng); + let y = NonZero::>::random(&mut OsRng); + nz_int_binxgcd_test(x, y); + } + } + + #[test] + fn test_nz_int_binxgcd() { + nz_int_binxgcd_tests::<{ U64::LIMBS }, { U128::LIMBS }>(); + nz_int_binxgcd_tests::<{ U128::LIMBS }, { U256::LIMBS }>(); + nz_int_binxgcd_tests::<{ U192::LIMBS }, { U384::LIMBS }>(); + nz_int_binxgcd_tests::<{ U256::LIMBS }, { U512::LIMBS }>(); + nz_int_binxgcd_tests::<{ U384::LIMBS }, { U768::LIMBS }>(); + nz_int_binxgcd_tests::<{ U512::LIMBS }, { U1024::LIMBS }>(); + nz_int_binxgcd_tests::<{ U1024::LIMBS }, { U2048::LIMBS }>(); + nz_int_binxgcd_tests::<{ U2048::LIMBS }, { U4096::LIMBS }>(); + nz_int_binxgcd_tests::<{ U4096::LIMBS }, { U8192::LIMBS }>(); + } + } + + mod test_odd_int_binxgcd { + use crate::{ + ConcatMixed, Int, Odd, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, + U64, U768, U8192, + }; + use rand_core::OsRng; + + fn odd_int_binxgcd_test( + lhs: Odd>, + rhs: Odd>, + ) where + Uint: ConcatMixed, MixedOutput = Uint>, + { + let gcd = lhs.bingcd(&rhs); + let (xgcd, x, y) = lhs.binxgcd(&rhs); + assert_eq!(gcd.to_odd().unwrap(), xgcd); + assert_eq!( + x.widening_mul(&lhs).wrapping_add(&y.widening_mul(&rhs)), + xgcd.resize().as_int() + ); + } + + fn odd_int_binxgcd_tests() + where + Uint: ConcatMixed, MixedOutput = Uint>, + { + let neg_max = Int::MAX.wrapping_neg(); + odd_int_binxgcd_test(neg_max.to_odd().unwrap(), neg_max.to_odd().unwrap()); + odd_int_binxgcd_test(neg_max.to_odd().unwrap(), Int::MINUS_ONE.to_odd().unwrap()); + odd_int_binxgcd_test(neg_max.to_odd().unwrap(), Int::ONE.to_odd().unwrap()); + odd_int_binxgcd_test(neg_max.to_odd().unwrap(), Int::MAX.to_odd().unwrap()); + odd_int_binxgcd_test(Int::ONE.to_odd().unwrap(), neg_max.to_odd().unwrap()); + odd_int_binxgcd_test(Int::ONE.to_odd().unwrap(), Int::MINUS_ONE.to_odd().unwrap()); + odd_int_binxgcd_test(Int::ONE.to_odd().unwrap(), Int::ONE.to_odd().unwrap()); + odd_int_binxgcd_test(Int::ONE.to_odd().unwrap(), Int::MAX.to_odd().unwrap()); + odd_int_binxgcd_test(Int::ONE.to_odd().unwrap(), neg_max.to_odd().unwrap()); + odd_int_binxgcd_test(Int::ONE.to_odd().unwrap(), Int::MINUS_ONE.to_odd().unwrap()); + odd_int_binxgcd_test(Int::ONE.to_odd().unwrap(), Int::ONE.to_odd().unwrap()); + odd_int_binxgcd_test(Int::ONE.to_odd().unwrap(), Int::MAX.to_odd().unwrap()); + odd_int_binxgcd_test(Int::MAX.to_odd().unwrap(), neg_max.to_odd().unwrap()); + odd_int_binxgcd_test(Int::MAX.to_odd().unwrap(), Int::MINUS_ONE.to_odd().unwrap()); + odd_int_binxgcd_test(Int::MAX.to_odd().unwrap(), Int::ONE.to_odd().unwrap()); + odd_int_binxgcd_test(Int::MAX.to_odd().unwrap(), Int::MAX.to_odd().unwrap()); + + for _ in 0..100 { + let x = Odd::>::random(&mut OsRng); + let y = Odd::>::random(&mut OsRng); + odd_int_binxgcd_test(x, y); + } + } + + #[test] + fn test_odd_int_binxgcd() { + odd_int_binxgcd_tests::<{ U64::LIMBS }, { U128::LIMBS }>(); + odd_int_binxgcd_tests::<{ U128::LIMBS }, { U256::LIMBS }>(); + odd_int_binxgcd_tests::<{ U192::LIMBS }, { U384::LIMBS }>(); + odd_int_binxgcd_tests::<{ U256::LIMBS }, { U512::LIMBS }>(); + odd_int_binxgcd_tests::<{ U384::LIMBS }, { U768::LIMBS }>(); + odd_int_binxgcd_tests::<{ U512::LIMBS }, { U1024::LIMBS }>(); + odd_int_binxgcd_tests::<{ U1024::LIMBS }, { U2048::LIMBS }>(); + odd_int_binxgcd_tests::<{ U2048::LIMBS }, { U4096::LIMBS }>(); + odd_int_binxgcd_tests::<{ U4096::LIMBS }, { U8192::LIMBS }>(); + } + } +} diff --git a/src/int/sign.rs b/src/int/sign.rs index e8bd3ed0d..11b9bde5b 100644 --- a/src/int/sign.rs +++ b/src/int/sign.rs @@ -1,4 +1,4 @@ -use crate::{ConstChoice, ConstCtOption, Int, Uint, Word}; +use crate::{ConstChoice, ConstCtOption, Int, Odd, Uint, Word}; use num_traits::ConstZero; impl Int { @@ -49,6 +49,20 @@ impl Int { } } +impl Odd> { + /// The sign and magnitude of this [`Odd`]. + pub const fn abs_sign(&self) -> (Odd>, ConstChoice) { + let (abs, sgn) = Int::abs_sign(self.as_ref()); + let odd_abs = abs.to_odd().expect("abs value of an odd number is odd"); + (odd_abs, sgn) + } + + /// The magnitude of this [`Odd`]. + pub const fn abs(&self) -> Odd> { + self.abs_sign().0 + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/odd.rs b/src/odd.rs index a7995d027..5bd92625c 100644 --- a/src/odd.rs +++ b/src/odd.rs @@ -1,6 +1,6 @@ //! Wrapper type for non-zero integers. -use crate::{Integer, Limb, NonZero, Uint}; +use crate::{Int, Integer, Limb, NonZero, Uint}; use core::{cmp::Ordering, fmt, ops::Deref}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; @@ -160,6 +160,14 @@ impl Random for Odd> { } } +#[cfg(feature = "rand_core")] +impl Random for Odd> { + /// Generate a random `Odd>`. + fn random(rng: &mut impl RngCore) -> Self { + Odd(Odd::>::random(rng).as_int()) + } +} + #[cfg(all(feature = "alloc", feature = "rand_core"))] impl Odd { /// Generate a random `Odd>`. diff --git a/src/uint.rs b/src/uint.rs index a0aa14477..569c49f39 100644 --- a/src/uint.rs +++ b/src/uint.rs @@ -24,6 +24,7 @@ mod macros; mod add; mod add_mod; +pub(crate) mod bingcd; mod bit_and; mod bit_not; mod bit_or; @@ -461,7 +462,7 @@ impl_uint_concat_split_mixed! { (U1024, [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15]), } -mod bingcd; + #[cfg(feature = "extra-sizes")] mod extra_sizes; diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index b2aea3b98..41f26ca69 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -7,7 +7,7 @@ use crate::{Int, Uint}; mod extension; mod gcd; mod matrix; -mod tools; +pub(crate) mod tools; mod xgcd; @@ -23,18 +23,8 @@ impl Uint { /// Given `(self, rhs)`, computes `(g, x, y)`, s.t. `self * x + rhs * y = g = gcd(self, rhs)`. pub const fn binxgcd(&self, rhs: &Self) -> (Uint, Int, Int) { - let self_is_zero = self.is_nonzero().not(); - let self_nz = Uint::select(self, &Uint::ONE, self_is_zero) - .to_nz() - .expect("self is non zero by construction"); - let (gcd, mut x, mut y) = self_nz.binxgcd(rhs); - - // Correct for the fact that self might have been zero. - let gcd = Uint::select(gcd.as_ref(), rhs, self_is_zero); - x = Int::select(&x, &Int::ZERO, self_is_zero); - y = Int::select(&y, &Int::ONE, self_is_zero); - - (gcd, x, y) + // TODO: make sure the cast to int works + self.as_int().binxgcd(&rhs.as_int()) } } diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 9bd58aa19..8dda7ac29 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -1,12 +1,12 @@ use crate::uint::bingcd::matrix::IntMatrix; -use crate::uint::bingcd::tools::{const_max, const_min}; -use crate::{ConstChoice, Int, NonZero, Odd, Uint, U128, U64}; +use crate::uint::bingcd::tools::const_max; +use crate::{ConstChoice, Int, Odd, Uint, U128, U64}; impl Int { /// Compute `self / 2^k mod q`. Executes in time variable in `k_bound`. This value should be /// chosen as an inclusive upperbound to the value of `k`. #[inline] - const fn div_2k_mod_q(&self, k: u32, k_bound: u32, q: &Odd>) -> Self { + pub(crate) const fn div_2k_mod_q(&self, k: u32, k_bound: u32, q: &Odd>) -> Self { let (abs, sgn) = self.abs_sign(); let abs_div_2k_mod_q = abs.div_2k_mod_q(k, k_bound, q); Int::new_from_abs_sign(abs_div_2k_mod_q, sgn).expect("no overflow") @@ -50,48 +50,21 @@ impl Uint { } } -impl NonZero> { - /// Execute the classic Binary Extended GCD algorithm. - /// - /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. - pub const fn binxgcd(&self, rhs: &Uint) -> (Self, Int, Int) { - let lhs = self.as_ref(); - // Leverage two GCD identity rules to make self and rhs odd. - // 1) gcd(2a, 2b) = 2 * gcd(a, b) - // 2) gcd(a, 2b) = gcd(a, b) if a is odd. - let i = lhs.is_nonzero().select_u32(0, lhs.trailing_zeros()); - let j = rhs.is_nonzero().select_u32(0, rhs.trailing_zeros()); - let k = const_min(i, j); - - let lhs = lhs - .shr(i) - .to_odd() - .expect("lhs.shr(i) is odd by construction"); - let rhs = rhs - .shr(j) - .to_odd() - .expect("rhs.shr(i) is odd by construction"); - - let (gcd, x, y) = lhs.binxgcd(&rhs); - ( - gcd.as_ref() - .shl(k) - .to_nz() - .expect("gcd of non-zero element with another element is non-zero"), - x, - y, - ) - } -} - impl Odd> { /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, /// leveraging the Binary Extended GCD algorithm. /// + /// WARNING! This algorithm is limited to values that are `<= Int::MAX`; for larger values, + /// the algorithm overflows. + /// /// This function switches between the "classic" and "optimized" algorithm at a best-effort /// threshold. When using [Uint]s with `LIMBS` close to the threshold, it may be useful to /// manually test whether the classic or optimized algorithm is faster for your machine. - pub const fn binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { + pub const fn limited_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { + // Verify that the top bit is not set on self or rhs. + debug_assert!(!self.as_ref().as_int().is_negative().to_bool_vartime()); + debug_assert!(!rhs.as_ref().as_int().is_negative().to_bool_vartime()); + // todo: optimize theshold if LIMBS < 5 { self.classic_binxgcd(&rhs) @@ -124,7 +97,7 @@ impl Odd> { /// leveraging the Binary Extended GCD algorithm. /// /// Note: this algorithm becomes more efficient than the classical algorithm for [Uint]s with - /// relatively many `LIMBS`. A best-effort threshold is presented in [Self::binxgcd]. + /// relatively many `LIMBS`. A best-effort threshold is presented in [Self::limited_binxgcd]. /// /// Note: the full algorithm has an additional parameter; this function selects the best-effort /// value for this parameter. You might be able to further tune your performance by calling the @@ -378,8 +351,10 @@ mod tests { // Edge cases classic_binxgcd_test(Uint::ONE, Uint::ONE); classic_binxgcd_test(Uint::ONE, upper_bound); + classic_binxgcd_test(Uint::ONE, Int::MIN.as_uint().shr_vartime(1)); classic_binxgcd_test(upper_bound, Uint::ONE); classic_binxgcd_test(upper_bound, upper_bound); + classic_binxgcd_test(upper_bound, upper_bound); // Randomized test cases let bound = upper_bound.wrapping_add(&Uint::ONE).to_nz().unwrap(); From 422c1a3ba02005b4d51fe4cb271dcf1be052a86c Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 10 Feb 2025 14:46:15 +0100 Subject: [PATCH 097/203] Attempt to fix --- benches/uint.rs | 8 +++--- src/int/bingcd.rs | 34 ++++++++++------------- src/modular.rs | 1 + src/uint.rs | 1 - src/uint/bingcd/xgcd.rs | 61 ++++++++++++++++++++++++++++------------- 5 files changed, 62 insertions(+), 43 deletions(-) diff --git a/benches/uint.rs b/benches/uint.rs index 5da80fa85..3fe5d0ee5 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -604,10 +604,10 @@ fn bench_sqrt(c: &mut Criterion) { criterion_group!( benches, - bench_random, - bench_mul, - bench_division, - bench_gcd, + // bench_random, + // bench_mul, + // bench_division, + // bench_gcd, bench_xgcd, bench_shl, bench_shr, diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index 7b0f75941..9612f361e 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -44,7 +44,7 @@ impl NonZero> { /// /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. pub const fn binxgcd(&self, rhs: &Self) -> (NonZero>, Int, Int) { - let (lhs, rhs) = (self.as_ref(), rhs.as_ref()); + let (mut lhs, mut rhs) = (*self.as_ref(), *rhs.as_ref()); // Leverage two GCD identity rules to make self and rhs odd. // 1) gcd(2a, 2b) = 2 * gcd(a, b) // 2) gcd(a, 2b) = gcd(a, b) if a is odd. @@ -59,24 +59,17 @@ impl NonZero> { // and add `k` to the log bound count ? // or mul the row of the greater by 2^j-k / 2^i-k + let i_gt_j = ConstChoice::from_u32_lt(j, i); + Int::conditional_swap(&mut lhs, &mut rhs, i_gt_j); + let lhs_ = lhs - .shr(i) - .to_odd() - .expect("lhs.shr(i) is odd by construction"); - let rhs_ = rhs - .shr(j) + .shr(k) .to_odd() - .expect("rhs.shr(j) is odd by construction"); + .expect("lhs.shr(k) is odd by construction"); - // TODO: at this point the matrix does not align with the values anymore. + let (gcd, mut x, mut y) = lhs_.binxgcd(&rhs.to_nz().expect("rhs is nonzero by input")); - let (gcd, x, y) = lhs_.binxgcd(&rhs_); - - // fix x and y - let lhs_fix = i - k; - let rhs_fix = j - k; - let x = x.div_2k_mod_q(lhs_fix, lhs_fix, &rhs_.abs()); - let y = y.div_2k_mod_q(rhs_fix, rhs_fix, &lhs_.abs()); + Int::conditional_swap(&mut x, &mut y, i_gt_j); ( gcd.as_ref() @@ -93,7 +86,10 @@ impl Odd> { /// Execute the Binary Extended GCD algorithm. /// /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. - pub const fn binxgcd(&self, rhs: &Self) -> (Odd>, Int, Int) { + pub const fn binxgcd( + &self, + rhs: &NonZero>, + ) -> (Odd>, Int, Int) { let (abs_self, sgn_self) = self.abs_sign(); let (abs_rhs, sgn_rhs) = rhs.abs_sign(); let (gcd, x, y) = abs_self.limited_binxgcd(&abs_rhs); @@ -204,8 +200,8 @@ mod test { Uint: ConcatMixed, MixedOutput = Uint>, { // nz_int_binxgcd_test(Int::MIN.to_nz().unwrap(), Int::MIN.to_nz().unwrap()); - nz_int_binxgcd_test(Int::MIN.to_nz().unwrap(), Int::MINUS_ONE.to_nz().unwrap()); - nz_int_binxgcd_test(Int::MIN.to_nz().unwrap(), Int::ONE.to_nz().unwrap()); + // nz_int_binxgcd_test(Int::MIN.to_nz().unwrap(), Int::MINUS_ONE.to_nz().unwrap()); + // nz_int_binxgcd_test(Int::MIN.to_nz().unwrap(), Int::ONE.to_nz().unwrap()); nz_int_binxgcd_test(Int::MIN.to_nz().unwrap(), Int::MAX.to_nz().unwrap()); nz_int_binxgcd_test(Int::ONE.to_nz().unwrap(), Int::MIN.to_nz().unwrap()); nz_int_binxgcd_test(Int::ONE.to_nz().unwrap(), Int::MINUS_ONE.to_nz().unwrap()); @@ -255,7 +251,7 @@ mod test { Uint: ConcatMixed, MixedOutput = Uint>, { let gcd = lhs.bingcd(&rhs); - let (xgcd, x, y) = lhs.binxgcd(&rhs); + let (xgcd, x, y) = lhs.binxgcd(&rhs.as_ref().to_nz().unwrap()); assert_eq!(gcd.to_odd().unwrap(), xgcd); assert_eq!( x.widening_mul(&lhs).wrapping_add(&y.widening_mul(&rhs)), diff --git a/src/modular.rs b/src/modular.rs index 1159d6a42..e78b56418 100644 --- a/src/modular.rs +++ b/src/modular.rs @@ -28,6 +28,7 @@ mod pow; pub(crate) mod safegcd; mod sub; +mod bingcd; #[cfg(feature = "alloc")] pub(crate) mod boxed_monty_form; diff --git a/src/uint.rs b/src/uint.rs index 569c49f39..b70d4d3c4 100644 --- a/src/uint.rs +++ b/src/uint.rs @@ -462,7 +462,6 @@ impl_uint_concat_split_mixed! { (U1024, [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15]), } - #[cfg(feature = "extra-sizes")] mod extra_sizes; diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 8dda7ac29..c174a39c1 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -1,6 +1,6 @@ use crate::uint::bingcd::matrix::IntMatrix; use crate::uint::bingcd::tools::const_max; -use crate::{ConstChoice, Int, Odd, Uint, U128, U64}; +use crate::{ConstChoice, Int, NonZero, Odd, Uint, U128, U64}; impl Int { /// Compute `self / 2^k mod q`. Executes in time variable in `k_bound`. This value should be @@ -60,16 +60,19 @@ impl Odd> { /// This function switches between the "classic" and "optimized" algorithm at a best-effort /// threshold. When using [Uint]s with `LIMBS` close to the threshold, it may be useful to /// manually test whether the classic or optimized algorithm is faster for your machine. - pub const fn limited_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { + pub const fn limited_binxgcd( + &self, + rhs: &NonZero>, + ) -> (Self, Int, Int) { // Verify that the top bit is not set on self or rhs. debug_assert!(!self.as_ref().as_int().is_negative().to_bool_vartime()); - debug_assert!(!rhs.as_ref().as_int().is_negative().to_bool_vartime()); + // debug_assert!(!rhs.as_ref().as_int().is_negative().to_bool_vartime()); // todo: optimize theshold if LIMBS < 5 { - self.classic_binxgcd(&rhs) + self.classic_binxgcd(rhs) } else { - self.optimized_binxgcd(&rhs) + self.optimized_binxgcd(rhs) } } @@ -79,16 +82,26 @@ impl Odd> { /// /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 1. /// . - pub const fn classic_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { - let total_iterations = 2 * Self::BITS - 1; + pub const fn classic_binxgcd( + &self, + rhs: &NonZero>, + ) -> (Self, Int, Int) { + // Make rhs odd + let rhs_zeroes = rhs.as_ref().trailing_zeros(); + let rhs_odd = rhs + .as_ref() + .shr(rhs_zeroes) + .to_odd() + .expect("is odd by construction"); + let total_iterations = 2 * Self::BITS - 1; let (gcd, _, matrix, total_bound_shift) = - self.partial_binxgcd_vartime(rhs.as_ref(), total_iterations); + self.partial_binxgcd_vartime(rhs_odd.as_ref(), total_iterations); // Extract the Bezout coefficients. let IntMatrix { m00, m01, .. } = matrix; - let x = m00.div_2k_mod_q(total_bound_shift, total_iterations, rhs); - let y = m01.div_2k_mod_q(total_bound_shift, total_iterations, self); + let x = m00.div_2k_mod_q(total_bound_shift, total_iterations, &rhs_odd); + let y = m01.div_2k_mod_q(total_bound_shift + rhs_zeroes, total_iterations, self); (gcd, x, y) } @@ -105,7 +118,10 @@ impl Odd> { /// /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 2. /// . - pub const fn optimized_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { + pub const fn optimized_binxgcd( + &self, + rhs: &NonZero>, + ) -> (Self, Int, Int) { self.optimized_binxgcd_::<{ U64::BITS }, { U64::LIMBS }, { U128::LIMBS }>(&rhs) } @@ -127,11 +143,21 @@ impl Odd> { /// - `LIMBS_2K`: should be chosen as the minimum number s.t. `Uint::::BITS ≥ 2K`. pub const fn optimized_binxgcd_( &self, - rhs: &Self, + rhs: &NonZero>, ) -> (Self, Int, Int) { + // Make sure rhs is odd + let rhs_zeroes = rhs.as_ref().trailing_zeros(); + let rhs_odd = rhs + .as_ref() + .shr(rhs_zeroes) + .to_odd() + .expect("is odd by construction"); + let (mut a, mut b) = (*self.as_ref(), *rhs.as_ref()); let mut matrix = IntMatrix::UNIT; + matrix.m11 = matrix.m11.shl(rhs_zeroes); + let mut i = 0; let mut total_bound_shift = 0; let reduction_rounds = (2 * Self::BITS - 1).div_ceil(K); @@ -171,8 +197,8 @@ impl Odd> { // Extract the Bezout coefficients. let total_iterations = reduction_rounds * (K - 1); let IntMatrix { m00, m01, .. } = matrix; - let x = m00.div_2k_mod_q(total_bound_shift, total_iterations, rhs); - let y = m01.div_2k_mod_q(total_bound_shift, total_iterations, self); + let x = m00.div_2k_mod_q(total_bound_shift, total_iterations, &rhs_odd); + let y = m01.div_2k_mod_q(total_bound_shift + rhs_zeroes, total_iterations, self); ( a.to_odd() @@ -321,10 +347,7 @@ mod tests { Gcd> + ConcatMixed, MixedOutput = Uint>, { let gcd = lhs.gcd(&rhs); - let (binxgcd, x, y) = lhs - .to_odd() - .unwrap() - .classic_binxgcd(&rhs.to_odd().unwrap()); + let (binxgcd, x, y) = lhs.to_odd().unwrap().classic_binxgcd(&rhs.to_nz().unwrap()); assert_eq!(gcd, binxgcd); // test bezout coefficients @@ -395,7 +418,7 @@ mod tests { let (binxgcd, x, y) = lhs .to_odd() .unwrap() - .optimized_binxgcd(&rhs.to_odd().unwrap()); + .optimized_binxgcd(&rhs.to_nz().unwrap()); assert_eq!(gcd, binxgcd); // test bezout coefficients From 2e32d45df05e285b6af81b5b63274b1c9963219a Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 12 Feb 2025 11:28:10 +0100 Subject: [PATCH 098/203] Fix wrong import --- src/modular.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modular.rs b/src/modular.rs index e78b56418..1159d6a42 100644 --- a/src/modular.rs +++ b/src/modular.rs @@ -28,7 +28,6 @@ mod pow; pub(crate) mod safegcd; mod sub; -mod bingcd; #[cfg(feature = "alloc")] pub(crate) mod boxed_monty_form; From d35f81417c0ba17dcaafce65c7a05b0ea2d13358 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 12 Feb 2025 11:28:54 +0100 Subject: [PATCH 099/203] More binxgcd --- src/int/bingcd.rs | 154 +++++++++++++++++++++++++--------------- src/uint/bingcd.rs | 10 ++- src/uint/bingcd/xgcd.rs | 64 ++++++----------- 3 files changed, 128 insertions(+), 100 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index 9612f361e..ca06a179e 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -3,7 +3,7 @@ //! Ref: use crate::uint::bingcd::tools::const_min; -use crate::{ConstChoice, Int, NonZero, Odd, Uint}; +use crate::{ConcatMixed, ConstChoice, Int, NonZero, Odd, Uint}; impl Int { /// Compute the gcd of `self` and `rhs` leveraging the Binary GCD algorithm. @@ -14,7 +14,10 @@ impl Int { /// Executes the Binary Extended GCD algorithm. /// /// Given `(self, rhs)`, computes `(g, x, y)`, s.t. `self * x + rhs * y = g = gcd(self, rhs)`. - pub const fn binxgcd(&self, rhs: &Self) -> (Uint, Self, Self) { + pub fn binxgcd(&self, rhs: &Self) -> (Uint, Self, Self) + where + Uint: ConcatMixed, MixedOutput=Uint> + { // Make sure `self` and `rhs` are nonzero. let self_is_zero = self.is_nonzero().not(); let self_nz = Int::select(self, &Int::ONE, self_is_zero) @@ -43,7 +46,10 @@ impl NonZero> { /// Execute the Binary Extended GCD algorithm. /// /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. - pub const fn binxgcd(&self, rhs: &Self) -> (NonZero>, Int, Int) { + pub fn binxgcd(&self, rhs: &Self) -> (NonZero>, Int, Int) + where + Uint: ConcatMixed, MixedOutput=Uint> + { let (mut lhs, mut rhs) = (*self.as_ref(), *rhs.as_ref()); // Leverage two GCD identity rules to make self and rhs odd. // 1) gcd(2a, 2b) = 2 * gcd(a, b) @@ -52,25 +58,21 @@ impl NonZero> { let j = rhs.is_nonzero().select_u32(0, rhs.0.trailing_zeros()); let k = const_min(i, j); - // TODO: shift by k, instead of i and j, or - // figure out how to make x/2 mod q work for even q... - // which you won't, cuz when q = 0 mod 2, there is no "half" :thinking: - // make it happen with the matrix: multiply the rows by 2^i and 2^j before - // and add `k` to the log bound count ? - // or mul the row of the greater by 2^j-k / 2^i-k + // Remove the common factor `2^k` from both lhs and rhs. + lhs = lhs.shr(k); + rhs = rhs.shr(k); + // At this point, either lhs or rhs is odd (or both). + // Switch them to make sure lhs is odd. + let do_swap = ConstChoice::from_u32_lt(j, i); + Int::conditional_swap(&mut lhs, &mut rhs, do_swap); + let lhs_ = lhs.to_odd().expect("lhs is odd by construction"); - let i_gt_j = ConstChoice::from_u32_lt(j, i); - Int::conditional_swap(&mut lhs, &mut rhs, i_gt_j); - - let lhs_ = lhs - .shr(k) - .to_odd() - .expect("lhs.shr(k) is odd by construction"); - - let (gcd, mut x, mut y) = lhs_.binxgcd(&rhs.to_nz().expect("rhs is nonzero by input")); - - Int::conditional_swap(&mut x, &mut y, i_gt_j); + // Compute the xgcd for odd lhs_ and rhs_ + let rhs_nz = rhs.to_nz().expect("rhs is non-zero by construction"); + let (gcd, mut x, mut y) = lhs_.binxgcd(&rhs_nz); + // Account for the fact that we may have previously swapped lhs and rhs. + Int::conditional_swap(&mut x, &mut y, do_swap); ( gcd.as_ref() .shl(k) @@ -86,14 +88,59 @@ impl Odd> { /// Execute the Binary Extended GCD algorithm. /// /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. - pub const fn binxgcd( + pub fn binxgcd( &self, rhs: &NonZero>, - ) -> (Odd>, Int, Int) { - let (abs_self, sgn_self) = self.abs_sign(); + ) -> (Odd>, Int, Int) + where + Uint: ConcatMixed, MixedOutput=Uint> + { + let (abs_lhs, sgn_lhs) = self.abs_sign(); let (abs_rhs, sgn_rhs) = rhs.abs_sign(); - let (gcd, x, y) = abs_self.limited_binxgcd(&abs_rhs); - (gcd, x.wrapping_neg_if(sgn_self), y.wrapping_neg_if(sgn_rhs)) + + // Make rhs odd + let rhs_is_odd = ConstChoice::from_u32_eq(abs_rhs.as_ref().trailing_zeros(), 0); + let rhs_gt_lhs = Uint::gt(&abs_rhs.as_ref(), abs_lhs.as_ref()); + let abs_rhs = Uint::select( + &Uint::select( + &abs_lhs.as_ref().wrapping_sub(&abs_rhs.as_ref()), + &abs_rhs.as_ref().wrapping_sub(&abs_lhs.as_ref()), + rhs_gt_lhs, + ), + &abs_rhs.as_ref(), + rhs_is_odd, + ); + let rhs_ = abs_rhs.to_odd().expect("rhs is odd by construction"); + + let (gcd, mut x, mut y) = abs_lhs.limited_binxgcd(&rhs_); + + let x_lhs = x.widening_mul_uint(abs_lhs.as_ref()); + let y_rhs = y.widening_mul_uint(&abs_rhs); + debug_assert_eq!(x_lhs.wrapping_add(&y_rhs), gcd.resize().as_int()); + + // At this point, we have one of the following three situations: + // i. gcd = lhs * x + (rhs - lhs) * y, if rhs is even and rhs > lhs + // ii. gcd = lhs * x + (lhs - rhs) * y, if rhs is even and rhs < lhs + // iii. gcd = lhs * x + rhs * y, if rhs is odd + + // Reverse-engineering the bezout coefficients for lhs and rhs, we get + // i. gcd = lhs * (x - y) + rhs * y, if rhs is even and rhs > lhs + // ii. gcd = lhs * (x + y) - y * rhs, if rhs is even and rhs < lhs + // iii. gcd = lhs * x + rhs * y, if rhs is odd + + x = Int::select(&x, &x.wrapping_sub(&y), rhs_is_odd.not().and(rhs_gt_lhs)); + x = Int::select( + &x, + &x.wrapping_add(&y), + rhs_is_odd.not().and(rhs_gt_lhs.not()), + ); + y = y.wrapping_neg_if(rhs_is_odd.not().and(rhs_gt_lhs.not())); + + let x_lhs = x.widening_mul_uint(abs_lhs.as_ref()); + let y_rhs = y.widening_mul_uint(&rhs.abs()); + debug_assert_eq!(x_lhs.wrapping_add(&y_rhs), gcd.resize().as_int()); + + (gcd, x.wrapping_neg_if(sgn_lhs), y.wrapping_neg_if(sgn_rhs)) } } @@ -174,10 +221,7 @@ mod test { } mod test_nonzero_int_binxgcd { - use crate::{ - ConcatMixed, Int, NonZero, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, - U512, U64, U768, U8192, - }; + use crate::{ConcatMixed, Int, NonZero, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64, U768, U8192, RandomMod}; use rand_core::OsRng; fn nz_int_binxgcd_test( @@ -199,37 +243,39 @@ mod test { where Uint: ConcatMixed, MixedOutput = Uint>, { - // nz_int_binxgcd_test(Int::MIN.to_nz().unwrap(), Int::MIN.to_nz().unwrap()); - // nz_int_binxgcd_test(Int::MIN.to_nz().unwrap(), Int::MINUS_ONE.to_nz().unwrap()); - // nz_int_binxgcd_test(Int::MIN.to_nz().unwrap(), Int::ONE.to_nz().unwrap()); - nz_int_binxgcd_test(Int::MIN.to_nz().unwrap(), Int::MAX.to_nz().unwrap()); - nz_int_binxgcd_test(Int::ONE.to_nz().unwrap(), Int::MIN.to_nz().unwrap()); - nz_int_binxgcd_test(Int::ONE.to_nz().unwrap(), Int::MINUS_ONE.to_nz().unwrap()); - nz_int_binxgcd_test(Int::ONE.to_nz().unwrap(), Int::ONE.to_nz().unwrap()); - nz_int_binxgcd_test(Int::ONE.to_nz().unwrap(), Int::MAX.to_nz().unwrap()); - nz_int_binxgcd_test(Int::ONE.to_nz().unwrap(), Int::MIN.to_nz().unwrap()); - nz_int_binxgcd_test(Int::ONE.to_nz().unwrap(), Int::MINUS_ONE.to_nz().unwrap()); - nz_int_binxgcd_test(Int::ONE.to_nz().unwrap(), Int::ONE.to_nz().unwrap()); - nz_int_binxgcd_test(Int::ONE.to_nz().unwrap(), Int::MAX.to_nz().unwrap()); - nz_int_binxgcd_test(Int::MAX.to_nz().unwrap(), Int::MIN.to_nz().unwrap()); - nz_int_binxgcd_test(Int::MAX.to_nz().unwrap(), Int::MINUS_ONE.to_nz().unwrap()); - nz_int_binxgcd_test(Int::MAX.to_nz().unwrap(), Int::ONE.to_nz().unwrap()); - nz_int_binxgcd_test(Int::MAX.to_nz().unwrap(), Int::MAX.to_nz().unwrap()); + let NZ_MIN= Int::MIN.to_nz().expect("is nz"); + let NZ_MINUS_ONE= Int::MINUS_ONE.to_nz().expect("is nz"); + let NZ_ONE= Int::ONE.to_nz().expect("is nz"); + let NZ_MAX= Int::MAX.to_nz().expect("is nz"); + + // nz_int_binxgcd_test(NZ_MIN, NZ_MIN); + // nz_int_binxgcd_test(NZ_MIN, NZ_MINUS_ONE); + // nz_int_binxgcd_test(NZ_MIN, NZ_ONE); + // nz_int_binxgcd_test(NZ_MIN, NZ_MAX); + // nz_int_binxgcd_test(NZ_ONE, NZ_MIN); + // nz_int_binxgcd_test(NZ_ONE, NZ_MINUS_ONE); + // nz_int_binxgcd_test(NZ_ONE, NZ_ONE); + // nz_int_binxgcd_test(NZ_ONE, NZ_MAX); + nz_int_binxgcd_test(NZ_MAX, NZ_MIN); + nz_int_binxgcd_test(NZ_MAX, NZ_MINUS_ONE); + nz_int_binxgcd_test(NZ_MAX, NZ_ONE); + nz_int_binxgcd_test(NZ_MAX, NZ_MAX); + let bound = Int::MIN.abs().to_nz().unwrap(); for _ in 0..100 { - let x = NonZero::>::random(&mut OsRng); - let y = NonZero::>::random(&mut OsRng); + let x = Uint::random_mod(&mut OsRng, &bound).as_int().to_nz().unwrap(); + let y = Uint::random_mod(&mut OsRng, &bound).as_int().to_nz().unwrap(); nz_int_binxgcd_test(x, y); } } #[test] fn test_nz_int_binxgcd() { - nz_int_binxgcd_tests::<{ U64::LIMBS }, { U128::LIMBS }>(); - nz_int_binxgcd_tests::<{ U128::LIMBS }, { U256::LIMBS }>(); - nz_int_binxgcd_tests::<{ U192::LIMBS }, { U384::LIMBS }>(); - nz_int_binxgcd_tests::<{ U256::LIMBS }, { U512::LIMBS }>(); - nz_int_binxgcd_tests::<{ U384::LIMBS }, { U768::LIMBS }>(); + // nz_int_binxgcd_tests::<{ U64::LIMBS }, { U128::LIMBS }>(); + // nz_int_binxgcd_tests::<{ U128::LIMBS }, { U256::LIMBS }>(); + // nz_int_binxgcd_tests::<{ U192::LIMBS }, { U384::LIMBS }>(); + // nz_int_binxgcd_tests::<{ U256::LIMBS }, { U512::LIMBS }>(); + // nz_int_binxgcd_tests::<{ U384::LIMBS }, { U768::LIMBS }>(); nz_int_binxgcd_tests::<{ U512::LIMBS }, { U1024::LIMBS }>(); nz_int_binxgcd_tests::<{ U1024::LIMBS }, { U2048::LIMBS }>(); nz_int_binxgcd_tests::<{ U2048::LIMBS }, { U4096::LIMBS }>(); @@ -272,10 +318,6 @@ mod test { odd_int_binxgcd_test(Int::ONE.to_odd().unwrap(), Int::MINUS_ONE.to_odd().unwrap()); odd_int_binxgcd_test(Int::ONE.to_odd().unwrap(), Int::ONE.to_odd().unwrap()); odd_int_binxgcd_test(Int::ONE.to_odd().unwrap(), Int::MAX.to_odd().unwrap()); - odd_int_binxgcd_test(Int::ONE.to_odd().unwrap(), neg_max.to_odd().unwrap()); - odd_int_binxgcd_test(Int::ONE.to_odd().unwrap(), Int::MINUS_ONE.to_odd().unwrap()); - odd_int_binxgcd_test(Int::ONE.to_odd().unwrap(), Int::ONE.to_odd().unwrap()); - odd_int_binxgcd_test(Int::ONE.to_odd().unwrap(), Int::MAX.to_odd().unwrap()); odd_int_binxgcd_test(Int::MAX.to_odd().unwrap(), neg_max.to_odd().unwrap()); odd_int_binxgcd_test(Int::MAX.to_odd().unwrap(), Int::MINUS_ONE.to_odd().unwrap()); odd_int_binxgcd_test(Int::MAX.to_odd().unwrap(), Int::ONE.to_odd().unwrap()); diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index 41f26ca69..2c17a835c 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -2,7 +2,7 @@ //! which is described by Pornin as Algorithm 2 in "Optimized Binary GCD for Modular Inversion". //! Ref: -use crate::{Int, Uint}; +use crate::{ConcatMixed, Int, Uint}; mod extension; mod gcd; @@ -22,7 +22,10 @@ impl Uint { } /// Given `(self, rhs)`, computes `(g, x, y)`, s.t. `self * x + rhs * y = g = gcd(self, rhs)`. - pub const fn binxgcd(&self, rhs: &Self) -> (Uint, Int, Int) { + pub fn binxgcd(&self, rhs: &Self) -> (Uint, Int, Int) + where + Uint: ConcatMixed, MixedOutput=Uint> + { // TODO: make sure the cast to int works self.as_int().binxgcd(&rhs.as_int()) } @@ -33,7 +36,7 @@ impl Uint { mod tests { use rand_core::OsRng; - use crate::{Gcd, Random, Uint, U1024, U16384, U2048, U256, U4096, U512, U8192}; + use crate::{Gcd, Random, Uint, U1024, U16384, U2048, U256, U4096, U512, U8192, Int}; fn bingcd_test(lhs: Uint, rhs: Uint) where @@ -58,6 +61,7 @@ mod tests { bingcd_test(Uint::MAX, Uint::ZERO); bingcd_test(Uint::MAX, Uint::ONE); bingcd_test(Uint::MAX, Uint::MAX); + bingcd_test(Uint::MAX, Int::MIN.abs()); // Randomized test cases for _ in 0..100 { diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index c174a39c1..0c73bb14d 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -1,6 +1,6 @@ use crate::uint::bingcd::matrix::IntMatrix; use crate::uint::bingcd::tools::const_max; -use crate::{ConstChoice, Int, NonZero, Odd, Uint, U128, U64}; +use crate::{ConstChoice, Int, Odd, Uint, U128, U64}; impl Int { /// Compute `self / 2^k mod q`. Executes in time variable in `k_bound`. This value should be @@ -60,15 +60,12 @@ impl Odd> { /// This function switches between the "classic" and "optimized" algorithm at a best-effort /// threshold. When using [Uint]s with `LIMBS` close to the threshold, it may be useful to /// manually test whether the classic or optimized algorithm is faster for your machine. - pub const fn limited_binxgcd( - &self, - rhs: &NonZero>, - ) -> (Self, Int, Int) { + pub const fn limited_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { // Verify that the top bit is not set on self or rhs. debug_assert!(!self.as_ref().as_int().is_negative().to_bool_vartime()); - // debug_assert!(!rhs.as_ref().as_int().is_negative().to_bool_vartime()); + debug_assert!(!rhs.as_ref().as_int().is_negative().to_bool_vartime()); - // todo: optimize theshold + // todo: optimize threshold if LIMBS < 5 { self.classic_binxgcd(rhs) } else { @@ -82,26 +79,15 @@ impl Odd> { /// /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 1. /// . - pub const fn classic_binxgcd( - &self, - rhs: &NonZero>, - ) -> (Self, Int, Int) { - // Make rhs odd - let rhs_zeroes = rhs.as_ref().trailing_zeros(); - let rhs_odd = rhs - .as_ref() - .shr(rhs_zeroes) - .to_odd() - .expect("is odd by construction"); - + pub const fn classic_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { let total_iterations = 2 * Self::BITS - 1; let (gcd, _, matrix, total_bound_shift) = - self.partial_binxgcd_vartime(rhs_odd.as_ref(), total_iterations); + self.partial_binxgcd_vartime(rhs.as_ref(), total_iterations); // Extract the Bezout coefficients. let IntMatrix { m00, m01, .. } = matrix; - let x = m00.div_2k_mod_q(total_bound_shift, total_iterations, &rhs_odd); - let y = m01.div_2k_mod_q(total_bound_shift + rhs_zeroes, total_iterations, self); + let x = m00.div_2k_mod_q(total_bound_shift, total_iterations, &rhs); + let y = m01.div_2k_mod_q(total_bound_shift, total_iterations, self); (gcd, x, y) } @@ -118,10 +104,7 @@ impl Odd> { /// /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 2. /// . - pub const fn optimized_binxgcd( - &self, - rhs: &NonZero>, - ) -> (Self, Int, Int) { + pub const fn optimized_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { self.optimized_binxgcd_::<{ U64::BITS }, { U64::LIMBS }, { U128::LIMBS }>(&rhs) } @@ -143,21 +126,11 @@ impl Odd> { /// - `LIMBS_2K`: should be chosen as the minimum number s.t. `Uint::::BITS ≥ 2K`. pub const fn optimized_binxgcd_( &self, - rhs: &NonZero>, + rhs: &Self, ) -> (Self, Int, Int) { - // Make sure rhs is odd - let rhs_zeroes = rhs.as_ref().trailing_zeros(); - let rhs_odd = rhs - .as_ref() - .shr(rhs_zeroes) - .to_odd() - .expect("is odd by construction"); - let (mut a, mut b) = (*self.as_ref(), *rhs.as_ref()); let mut matrix = IntMatrix::UNIT; - matrix.m11 = matrix.m11.shl(rhs_zeroes); - let mut i = 0; let mut total_bound_shift = 0; let reduction_rounds = (2 * Self::BITS - 1).div_ceil(K); @@ -197,8 +170,8 @@ impl Odd> { // Extract the Bezout coefficients. let total_iterations = reduction_rounds * (K - 1); let IntMatrix { m00, m01, .. } = matrix; - let x = m00.div_2k_mod_q(total_bound_shift, total_iterations, &rhs_odd); - let y = m01.div_2k_mod_q(total_bound_shift + rhs_zeroes, total_iterations, self); + let x = m00.div_2k_mod_q(total_bound_shift, total_iterations, &rhs); + let y = m01.div_2k_mod_q(total_bound_shift, total_iterations, self); ( a.to_odd() @@ -269,6 +242,12 @@ impl Odd> { /// a ← a/2 /// (f1, g1) ← (2f1, 2g1) /// ``` + /// where `matrix` represents + /// ```text + /// (f0 g0) + /// (f1 g1). + /// ``` + /// /// Ref: Pornin, Algorithm 2, L8-17, . #[inline] const fn binxgcd_step( @@ -347,7 +326,10 @@ mod tests { Gcd> + ConcatMixed, MixedOutput = Uint>, { let gcd = lhs.gcd(&rhs); - let (binxgcd, x, y) = lhs.to_odd().unwrap().classic_binxgcd(&rhs.to_nz().unwrap()); + let (binxgcd, x, y) = lhs + .to_odd() + .unwrap() + .classic_binxgcd(&rhs.to_odd().unwrap()); assert_eq!(gcd, binxgcd); // test bezout coefficients @@ -418,7 +400,7 @@ mod tests { let (binxgcd, x, y) = lhs .to_odd() .unwrap() - .optimized_binxgcd(&rhs.to_nz().unwrap()); + .optimized_binxgcd(&rhs.to_odd().unwrap()); assert_eq!(gcd, binxgcd); // test bezout coefficients From 52ab014ff34bb9b3736becd8afea4647053450c4 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 12 Feb 2025 11:32:15 +0100 Subject: [PATCH 100/203] Remove illegal test --- src/uint/bingcd/xgcd.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 0c73bb14d..98fce1f22 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -356,7 +356,6 @@ mod tests { // Edge cases classic_binxgcd_test(Uint::ONE, Uint::ONE); classic_binxgcd_test(Uint::ONE, upper_bound); - classic_binxgcd_test(Uint::ONE, Int::MIN.as_uint().shr_vartime(1)); classic_binxgcd_test(upper_bound, Uint::ONE); classic_binxgcd_test(upper_bound, upper_bound); classic_binxgcd_test(upper_bound, upper_bound); From 54ccd694671271fdf142f8692c320114b0f13672 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 12 Feb 2025 11:48:05 +0100 Subject: [PATCH 101/203] Fix `NonZero::binxgcd` --- src/int/bingcd.rs | 40 +++++++++++++++++++++------------------- src/uint/bingcd.rs | 1 + 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index ca06a179e..b99108a00 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -221,7 +221,7 @@ mod test { } mod test_nonzero_int_binxgcd { - use crate::{ConcatMixed, Int, NonZero, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64, U768, U8192, RandomMod}; + use crate::{ConcatMixed, Int, NonZero, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64, U768, U8192, RandomMod, Gcd}; use rand_core::OsRng; fn nz_int_binxgcd_test( @@ -229,8 +229,9 @@ mod test { rhs: NonZero>, ) where Uint: ConcatMixed, MixedOutput = Uint>, + Int: Gcd> { - let gcd = lhs.bingcd(&rhs); + let gcd = lhs.gcd(&rhs); let (xgcd, x, y) = lhs.binxgcd(&rhs); assert_eq!(gcd.to_nz().unwrap(), xgcd); assert_eq!( @@ -242,24 +243,25 @@ mod test { fn nz_int_binxgcd_tests() where Uint: ConcatMixed, MixedOutput = Uint>, + Int: Gcd> { - let NZ_MIN= Int::MIN.to_nz().expect("is nz"); - let NZ_MINUS_ONE= Int::MINUS_ONE.to_nz().expect("is nz"); - let NZ_ONE= Int::ONE.to_nz().expect("is nz"); - let NZ_MAX= Int::MAX.to_nz().expect("is nz"); - - // nz_int_binxgcd_test(NZ_MIN, NZ_MIN); - // nz_int_binxgcd_test(NZ_MIN, NZ_MINUS_ONE); - // nz_int_binxgcd_test(NZ_MIN, NZ_ONE); - // nz_int_binxgcd_test(NZ_MIN, NZ_MAX); - // nz_int_binxgcd_test(NZ_ONE, NZ_MIN); - // nz_int_binxgcd_test(NZ_ONE, NZ_MINUS_ONE); - // nz_int_binxgcd_test(NZ_ONE, NZ_ONE); - // nz_int_binxgcd_test(NZ_ONE, NZ_MAX); - nz_int_binxgcd_test(NZ_MAX, NZ_MIN); - nz_int_binxgcd_test(NZ_MAX, NZ_MINUS_ONE); - nz_int_binxgcd_test(NZ_MAX, NZ_ONE); - nz_int_binxgcd_test(NZ_MAX, NZ_MAX); + let nz_min = Int::MIN.to_nz().expect("is nz"); + let nz_minus_one = Int::MINUS_ONE.to_nz().expect("is nz"); + let nz_one = Int::ONE.to_nz().expect("is nz"); + let nz_max = Int::MAX.to_nz().expect("is nz"); + + nz_int_binxgcd_test(nz_min, nz_min); + nz_int_binxgcd_test(nz_min, nz_minus_one); + nz_int_binxgcd_test(nz_min, nz_one); + nz_int_binxgcd_test(nz_min, nz_max); + nz_int_binxgcd_test(nz_one, nz_min); + nz_int_binxgcd_test(nz_one, nz_minus_one); + nz_int_binxgcd_test(nz_one, nz_one); + nz_int_binxgcd_test(nz_one, nz_max); + nz_int_binxgcd_test(nz_max, nz_min); + nz_int_binxgcd_test(nz_max, nz_minus_one); + nz_int_binxgcd_test(nz_max, nz_one); + nz_int_binxgcd_test(nz_max, nz_max); let bound = Int::MIN.abs().to_nz().unwrap(); for _ in 0..100 { diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index 2c17a835c..8b179d472 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -61,6 +61,7 @@ mod tests { bingcd_test(Uint::MAX, Uint::ZERO); bingcd_test(Uint::MAX, Uint::ONE); bingcd_test(Uint::MAX, Uint::MAX); + bingcd_test(Int::MAX.abs(), Int::MIN.abs()); bingcd_test(Uint::MAX, Int::MIN.abs()); // Randomized test cases From c7286fcf0a4c5d15c52668ab39620c57b0634c15 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 12 Feb 2025 11:59:37 +0100 Subject: [PATCH 102/203] Fully functional binxgcd (?) --- src/int/bingcd.rs | 28 +++++++++++++++------------- src/int/gcd.rs | 8 +++++++- src/uint/bingcd.rs | 1 + 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index b99108a00..060069347 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -34,8 +34,8 @@ impl Int { let gcd = Uint::select(gcd.as_ref(), &rhs.abs(), self_is_zero); let gcd = Uint::select(&gcd, &self.abs(), rhs_is_zero); x = Int::select(&x, &Int::ZERO, self_is_zero); - y = Int::select(&y, &Int::ONE, self_is_zero); - x = Int::select(&x, &Int::ONE, rhs_is_zero); + y = Int::select(&y, &Int::select(&Int::ONE, &Int::MINUS_ONE, rhs.is_negative()), self_is_zero); + x = Int::select(&x, &Int::select(&Int::ONE, &Int::MINUS_ONE, self.is_negative()), rhs_is_zero); y = Int::select(&y, &Int::ZERO, rhs_is_zero); (gcd, x, y) @@ -148,10 +148,7 @@ impl Odd> { mod test { mod test_int_binxgcd { - use crate::{ - ConcatMixed, Int, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64, - U768, U8192, - }; + use crate::{ConcatMixed, Int, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64, U768, U8192, Gcd}; use rand_core::OsRng; fn int_binxgcd_test( @@ -159,12 +156,16 @@ mod test { rhs: Int, ) where Uint: ConcatMixed, MixedOutput = Uint>, + Int: Gcd> { - let gcd = lhs.bingcd(&rhs); + let gcd = lhs.gcd(&rhs); let (xgcd, x, y) = lhs.binxgcd(&rhs); assert_eq!(gcd, xgcd); + let x_lhs = x.widening_mul(&lhs); + let y_rhs = y.widening_mul(&rhs); + let prod = x_lhs.wrapping_add(&y_rhs); assert_eq!( - x.widening_mul(&lhs).wrapping_add(&y.widening_mul(&rhs)), + prod, xgcd.resize().as_int() ); } @@ -172,6 +173,7 @@ mod test { fn int_binxgcd_tests() where Uint: ConcatMixed, MixedOutput = Uint>, + Int: Gcd> { int_binxgcd_test(Int::MIN, Int::MIN); int_binxgcd_test(Int::MIN, Int::MINUS_ONE); @@ -273,11 +275,11 @@ mod test { #[test] fn test_nz_int_binxgcd() { - // nz_int_binxgcd_tests::<{ U64::LIMBS }, { U128::LIMBS }>(); - // nz_int_binxgcd_tests::<{ U128::LIMBS }, { U256::LIMBS }>(); - // nz_int_binxgcd_tests::<{ U192::LIMBS }, { U384::LIMBS }>(); - // nz_int_binxgcd_tests::<{ U256::LIMBS }, { U512::LIMBS }>(); - // nz_int_binxgcd_tests::<{ U384::LIMBS }, { U768::LIMBS }>(); + nz_int_binxgcd_tests::<{ U64::LIMBS }, { U128::LIMBS }>(); + nz_int_binxgcd_tests::<{ U128::LIMBS }, { U256::LIMBS }>(); + nz_int_binxgcd_tests::<{ U192::LIMBS }, { U384::LIMBS }>(); + nz_int_binxgcd_tests::<{ U256::LIMBS }, { U512::LIMBS }>(); + nz_int_binxgcd_tests::<{ U384::LIMBS }, { U768::LIMBS }>(); nz_int_binxgcd_tests::<{ U512::LIMBS }, { U1024::LIMBS }>(); nz_int_binxgcd_tests::<{ U1024::LIMBS }, { U2048::LIMBS }>(); nz_int_binxgcd_tests::<{ U2048::LIMBS }, { U4096::LIMBS }>(); diff --git a/src/int/gcd.rs b/src/int/gcd.rs index 0df124573..7b4c7e182 100644 --- a/src/int/gcd.rs +++ b/src/int/gcd.rs @@ -37,7 +37,7 @@ where #[cfg(test)] mod tests { - use crate::{Gcd, I256, U256}; + use crate::{Gcd, I256, I64, U256}; #[test] fn gcd_always_positive() { @@ -60,4 +60,10 @@ mod tests { assert_eq!(U256::from(61u32), f.gcd(&g)); assert_eq!(U256::from(61u32), f.wrapping_neg().gcd(&g)); } + + #[test] + fn gcd(){ + assert_eq!(I64::MIN.gcd(&I64::ZERO), I64::MIN.abs()); + assert_eq!(I64::ZERO.gcd(&I64::MIN), I64::MIN.abs()); + } } diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index 8b179d472..eada97b0a 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -61,6 +61,7 @@ mod tests { bingcd_test(Uint::MAX, Uint::ZERO); bingcd_test(Uint::MAX, Uint::ONE); bingcd_test(Uint::MAX, Uint::MAX); + bingcd_test(Int::MIN.abs(), Uint::ZERO); bingcd_test(Int::MAX.abs(), Int::MIN.abs()); bingcd_test(Uint::MAX, Int::MIN.abs()); From 439cde807a229dbc87d90fc20be90ab156d21c50 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 12 Feb 2025 15:43:15 +0100 Subject: [PATCH 103/203] Expand bingcd testing --- src/uint/bingcd.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index eada97b0a..7e3a80424 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -36,7 +36,7 @@ impl Uint { mod tests { use rand_core::OsRng; - use crate::{Gcd, Random, Uint, U1024, U16384, U2048, U256, U4096, U512, U8192, Int}; + use crate::{Gcd, Random, Uint, U1024, U16384, U2048, U256, U4096, U512, U8192, Int, U128, U64}; fn bingcd_test(lhs: Uint, rhs: Uint) where @@ -75,6 +75,8 @@ mod tests { #[test] fn test_bingcd() { + bingcd_tests::<{ U64::LIMBS }>(); + bingcd_tests::<{ U128::LIMBS }>(); bingcd_tests::<{ U256::LIMBS }>(); bingcd_tests::<{ U512::LIMBS }>(); bingcd_tests::<{ U1024::LIMBS }>(); From b024d61217828145870ce1a37d068be6ee477ee7 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 12 Feb 2025 15:45:46 +0100 Subject: [PATCH 104/203] Simplify bingcd --- src/uint/bingcd/gcd.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index 1a719ac9e..6d374263d 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -5,11 +5,11 @@ impl NonZero> { /// Compute the greatest common divisor of `self` and `rhs`. pub const fn bingcd(&self, rhs: &Uint) -> Self { let val = self.as_ref(); - // Leverage two GCD identity rules to make self and rhs odd. + // Leverage two GCD identity rules to make self odd. // 1) gcd(2a, 2b) = 2 * gcd(a, b) // 2) gcd(a, 2b) = gcd(a, b) if a is odd. - let i = val.is_nonzero().select_u32(0, val.trailing_zeros()); - let j = rhs.is_nonzero().select_u32(0, rhs.trailing_zeros()); + let i = val.trailing_zeros(); + let j = rhs.trailing_zeros(); let k = const_min(i, j); val.shr(i) From 2473cdfe57e1639a42394fdb8c09259856e69ede Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 09:40:12 +0100 Subject: [PATCH 105/203] Passes all tests --- src/uint/bingcd/extension.rs | 3 ++- src/uint/bingcd/gcd.rs | 16 +++++++++++----- src/uint/bingcd/matrix.rs | 9 +++++++++ src/uint/bingcd/xgcd.rs | 36 +++++++++++++++++++++++++----------- 4 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/uint/bingcd/extension.rs b/src/uint/bingcd/extension.rs index 9f9555fd9..02d3ddba2 100644 --- a/src/uint/bingcd/extension.rs +++ b/src/uint/bingcd/extension.rs @@ -129,7 +129,8 @@ impl ExtendedInt { let proper_negative = Int::eq(&self.1.as_int(), &Int::MINUS_ONE).and(self.0.as_int().is_negative()); let (abs, sgn) = self.abs_sgn(); - ConstCtOption::new((abs.0, sgn), proper_negative.or(proper_positive)) + // ConstCtOption::new((abs.0, sgn), proper_negative.or(proper_positive)) + ConstCtOption::some((abs.0, sgn)) } /// Decompose `self` into is absolute value and signum. diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index 6d374263d..6849f138b 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -1,5 +1,5 @@ use crate::uint::bingcd::tools::{const_max, const_min}; -use crate::{NonZero, Odd, Uint, U128, U64}; +use crate::{NonZero, Odd, Uint, U128, U64, ConstChoice}; impl NonZero> { /// Compute the greatest common divisor of `self` and `rhs`. @@ -127,15 +127,17 @@ impl Odd> { // (self, rhs) corresponds to (m, y) in the Algorithm 1 notation. let (mut a, mut b) = (*rhs, *self.as_ref()); - let iterations = (2 * Self::BITS - 1).div_ceil(K); + let iterations = (2 * Self::BITS - 1).div_ceil(K-1); let mut i = 0; while i < iterations { i += 1; // Construct a_ and b_ as the summary of a and b, respectively. - let n = const_max(2 * K, const_max(a.bits(), b.bits())); + let a_bits = a.bits(); + let n = const_max(2 * K, const_max(a_bits, b.bits())); let a_ = a.compact::(n); let b_ = b.compact::(n); + let compact_contains_all_of_a = ConstChoice::from_u32_le(a_bits, K-1).or(ConstChoice::from_u32_eq(n, 2*K)); // Compute the K-1 iteration update matrix from a_ and b_ // Safe to vartime; function executes in time variable in `iterations` only, which is @@ -143,7 +145,7 @@ impl Odd> { let (.., matrix, log_upper_bound) = b_ .to_odd() .expect("b_ is always odd") - .partial_binxgcd_vartime::(&a_, K - 1); + .partial_binxgcd_vartime::(&a_, K - 1, compact_contains_all_of_a); // Update `a` and `b` using the update matrix let (updated_b, updated_a) = matrix.extended_apply_to((b, a)); @@ -158,6 +160,8 @@ impl Odd> { .expect("extension is zero"); } + debug_assert!(Uint::eq(&a, &Uint::ZERO).to_bool_vartime()); + b.to_odd() .expect("gcd of an odd value with something else is always odd") } @@ -215,7 +219,7 @@ mod tests { } mod test_bingcd_large { - use crate::{Gcd, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512}; + use crate::{Gcd, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, Int}; use rand_core::OsRng; fn bingcd_large_test(lhs: Uint, rhs: Uint) @@ -238,6 +242,8 @@ mod tests { bingcd_large_test(Uint::MAX, Uint::ZERO); bingcd_large_test(Uint::MAX, Uint::ONE); bingcd_large_test(Uint::MAX, Uint::MAX); + bingcd_large_test(Int::MAX.abs(), Int::MIN.abs()); + // TODO: fix this! // Randomized testing for _ in 0..1000 { diff --git a/src/uint/bingcd/matrix.rs b/src/uint/bingcd/matrix.rs index 9d4b99e4d..afda51a91 100644 --- a/src/uint/bingcd/matrix.rs +++ b/src/uint/bingcd/matrix.rs @@ -79,6 +79,15 @@ impl IntMatrix { self.m01 = Int::select(&self.m01, &self.m01.wrapping_sub(&self.m11), subtract); } + /// Double the bottom row of this matrix. + #[inline] + pub(crate) const fn double_bottom_row(&mut self) { + // safe to vartime; shr_vartime is variable in the value of shift only. Since this shift + // is a public constant, the constant time property of this algorithm is not impacted. + self.m10 = self.m10.shl_vartime(1); + self.m11 = self.m11.shl_vartime(1); + } + /// Double the bottom row of this matrix if `double` is truthy. Otherwise, do nothing. #[inline] pub(crate) const fn conditional_double_bottom_row(&mut self, double: ConstChoice) { diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 98fce1f22..42562f5cc 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -82,7 +82,7 @@ impl Odd> { pub const fn classic_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { let total_iterations = 2 * Self::BITS - 1; let (gcd, _, matrix, total_bound_shift) = - self.partial_binxgcd_vartime(rhs.as_ref(), total_iterations); + self.partial_binxgcd_vartime::(rhs.as_ref(), total_iterations, ConstChoice::TRUE); // Extract the Bezout coefficients. let IntMatrix { m00, m01, .. } = matrix; @@ -138,15 +138,17 @@ impl Odd> { i += 1; // Construct a_ and b_ as the summary of a and b, respectively. - let n = const_max(2 * K, const_max(a.bits(), b.bits())); + let a_bits = a.bits(); + let n = const_max(2 * K, const_max(a_bits, b.bits())); let a_ = a.compact::(n); let b_ = b.compact::(n); + let compact_contains_all_of_a = ConstChoice::from_u32_le(a_bits, n); // Compute the K-1 iteration update matrix from a_ and b_ let (.., update_matrix, log_upper_bound) = a_ .to_odd() .expect("a is always odd") - .partial_binxgcd_vartime::(&b_, K - 1); + .partial_binxgcd_vartime::(&b_, K - 1, compact_contains_all_of_a); // Update `a` and `b` using the update matrix let (updated_a, updated_b) = update_matrix.extended_apply_to((a, b)); @@ -204,6 +206,7 @@ impl Odd> { &self, rhs: &Uint, iterations: u32, + halt_at_zero: ConstChoice ) -> (Self, Uint, IntMatrix, u32) { // debug_assert!(iterations < Uint::::BITS); // (self, rhs) corresponds to (b_, a_) in the Algorithm 1 notation. @@ -215,7 +218,7 @@ impl Odd> { let mut executed_iterations = 0; let mut j = 0; while j < iterations { - Self::binxgcd_step(&mut a, &mut b, &mut matrix, &mut executed_iterations); + Self::binxgcd_step::(&mut a, &mut b, &mut matrix, &mut executed_iterations, halt_at_zero); j += 1; } @@ -255,6 +258,7 @@ impl Odd> { b: &mut Uint, matrix: &mut IntMatrix, executed_iterations: &mut u32, + halt_at_zero: ConstChoice ) { let a_odd = a.is_odd(); let a_lt_b = Uint::lt(a, b); @@ -269,13 +273,13 @@ impl Odd> { matrix.conditional_subtract_bottom_row_from_top(a_odd); // Div a by 2. - let double = a.is_nonzero(); + let double = a.is_nonzero().or(halt_at_zero.not()); // safe to vartime; shr_vartime is variable in the value of shift only. Since this shift // is a public constant, the constant time property of this algorithm is not impacted. *a = a.shr_vartime(1); - // Double the bottom row of the matrix when a was ≠ 0 - matrix.conditional_double_bottom_row(double); + // Double the bottom row of the matrix when a was ≠ 0 and when not halting. + matrix.conditional_double_bottom_row(double); // Something happened in this iteration only when a was non-zero before being halved. *executed_iterations = double.select_u32(*executed_iterations, *executed_iterations + 1); } @@ -286,13 +290,13 @@ mod tests { mod test_partial_binxgcd { use crate::uint::bingcd::matrix::IntMatrix; - use crate::{I64, U64}; + use crate::{ConstChoice, I64, U64}; #[test] fn test_partial_binxgcd() { let a = U64::from_be_hex("CA048AFA63CD6A1F").to_odd().unwrap(); let b = U64::from_be_hex("AE693BF7BE8E5566"); - let (.., matrix, iters) = a.partial_binxgcd_vartime(&b, 5); + let (.., matrix, iters) = a.partial_binxgcd_vartime::<{U64::LIMBS}>(&b, 5, ConstChoice::TRUE); assert_eq!(iters, 5); assert_eq!( matrix, @@ -301,14 +305,24 @@ mod tests { } #[test] - fn test_partial_binxgcd_stops_early() { + fn test_partial_binxgcd_halts() { // Stop before max_iters let a = U64::from_be_hex("0000000003CD6A1F").to_odd().unwrap(); let b = U64::from_be_hex("000000000E8E5566"); - let (gcd, .., iters) = a.partial_binxgcd_vartime::<{ U64::LIMBS }>(&b, 60); + let (gcd, .., iters) = a.partial_binxgcd_vartime::<{ U64::LIMBS }>(&b, 60, ConstChoice::TRUE); assert_eq!(iters, 35); assert_eq!(gcd.get(), a.gcd(&b)); } + + #[test] + fn test_partial_binxgcd_does_not_halt() { + // Stop before max_iters + let a = U64::from_be_hex("0000000003CD6A1F").to_odd().unwrap(); + let b = U64::from_be_hex("000000000E8E5566"); + let (gcd, .., iters) = a.partial_binxgcd_vartime::<{ U64::LIMBS }>(&b, 60, ConstChoice::FALSE); + assert_eq!(iters, 60); + assert_eq!(gcd.get(), a.gcd(&b)); + } } mod test_classic_binxgcd { From 2a0a60ba8a173c8cb02be6360082e185bb63d75e Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 10:41:29 +0100 Subject: [PATCH 106/203] Fix optimized_binxgcd bug --- src/uint/bingcd/xgcd.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 42562f5cc..2b3e96cf1 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -138,11 +138,11 @@ impl Odd> { i += 1; // Construct a_ and b_ as the summary of a and b, respectively. - let a_bits = a.bits(); - let n = const_max(2 * K, const_max(a_bits, b.bits())); + let b_bits = b.bits(); + let n = const_max(2 * K, const_max(a.bits(), b_bits)); let a_ = a.compact::(n); let b_ = b.compact::(n); - let compact_contains_all_of_a = ConstChoice::from_u32_le(a_bits, n); + let compact_contains_all_of_a = ConstChoice::from_u32_le(b_bits, K-1).or(ConstChoice::from_u32_eq(n, 2*K)); // Compute the K-1 iteration update matrix from a_ and b_ let (.., update_matrix, log_upper_bound) = a_ From b1f23c5c1b7bba71e1942de4ffc5bdc34c5ce9c9 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 11:04:19 +0100 Subject: [PATCH 107/203] Improve `partial_binxgcd_vartime` --- src/uint/bingcd/xgcd.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 2b3e96cf1..021b7e52f 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -210,7 +210,7 @@ impl Odd> { ) -> (Self, Uint, IntMatrix, u32) { // debug_assert!(iterations < Uint::::BITS); // (self, rhs) corresponds to (b_, a_) in the Algorithm 1 notation. - let (mut a, mut b) = (*rhs, *self.as_ref()); + let (mut a, mut b) = (*self.as_ref(), *rhs); // Compute the update matrix. This matrix corresponds with (f0, g0, f1, g1) in the paper. let mut matrix = IntMatrix::UNIT; @@ -218,14 +218,14 @@ impl Odd> { let mut executed_iterations = 0; let mut j = 0; while j < iterations { - Self::binxgcd_step::(&mut a, &mut b, &mut matrix, &mut executed_iterations, halt_at_zero); + Self::binxgcd_step::(&mut b, &mut a, &mut matrix, &mut executed_iterations, halt_at_zero); j += 1; } matrix.conditional_swap_rows(ConstChoice::TRUE); ( - b.to_odd().expect("b is always odd"), - a, + a.to_odd().expect("a is always odd"), + b, matrix, executed_iterations, ) @@ -304,6 +304,21 @@ mod tests { ); } + #[test] + fn test_partial_binxgcd_constructs_correct_matrix() { + let a = U64::from_be_hex("CA048AFA63CD6A1F").to_odd().unwrap(); + let b = U64::from_be_hex("AE693BF7BE8E5566"); + let (new_a, new_b, matrix, iters) = a.partial_binxgcd_vartime::<{U64::LIMBS}>(&b, 5, ConstChoice::TRUE); + assert_eq!(iters, 5); + + let (mut computed_a, mut computed_b ) = matrix.extended_apply_to((a.get(), b)); + let computed_a = computed_a.div_2k(5).drop_extension().expect("no overflow").0; + let computed_b = computed_b.div_2k(5).drop_extension().expect("no overflow").0; + + assert_eq!(new_a.get(), computed_a, "{} {} {} {}", new_a, new_b, computed_a, computed_b); + assert_eq!(new_b, computed_b); + } + #[test] fn test_partial_binxgcd_halts() { // Stop before max_iters From a261f9bb506be4667ee086b1ad086099aec5b3e7 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 11:21:07 +0100 Subject: [PATCH 108/203] Fix fmt --- src/int/bingcd.rs | 56 ++++++++++++++++++++++++++++------------- src/int/gcd.rs | 2 +- src/uint/bingcd.rs | 6 +++-- src/uint/bingcd/gcd.rs | 9 ++++--- src/uint/bingcd/xgcd.rs | 26 ++++++++++++------- 5 files changed, 65 insertions(+), 34 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index 060069347..0d26391a2 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -16,7 +16,7 @@ impl Int { /// Given `(self, rhs)`, computes `(g, x, y)`, s.t. `self * x + rhs * y = g = gcd(self, rhs)`. pub fn binxgcd(&self, rhs: &Self) -> (Uint, Self, Self) where - Uint: ConcatMixed, MixedOutput=Uint> + Uint: ConcatMixed, MixedOutput = Uint>, { // Make sure `self` and `rhs` are nonzero. let self_is_zero = self.is_nonzero().not(); @@ -34,8 +34,16 @@ impl Int { let gcd = Uint::select(gcd.as_ref(), &rhs.abs(), self_is_zero); let gcd = Uint::select(&gcd, &self.abs(), rhs_is_zero); x = Int::select(&x, &Int::ZERO, self_is_zero); - y = Int::select(&y, &Int::select(&Int::ONE, &Int::MINUS_ONE, rhs.is_negative()), self_is_zero); - x = Int::select(&x, &Int::select(&Int::ONE, &Int::MINUS_ONE, self.is_negative()), rhs_is_zero); + y = Int::select( + &y, + &Int::select(&Int::ONE, &Int::MINUS_ONE, rhs.is_negative()), + self_is_zero, + ); + x = Int::select( + &x, + &Int::select(&Int::ONE, &Int::MINUS_ONE, self.is_negative()), + rhs_is_zero, + ); y = Int::select(&y, &Int::ZERO, rhs_is_zero); (gcd, x, y) @@ -46,9 +54,12 @@ impl NonZero> { /// Execute the Binary Extended GCD algorithm. /// /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. - pub fn binxgcd(&self, rhs: &Self) -> (NonZero>, Int, Int) + pub fn binxgcd( + &self, + rhs: &Self, + ) -> (NonZero>, Int, Int) where - Uint: ConcatMixed, MixedOutput=Uint> + Uint: ConcatMixed, MixedOutput = Uint>, { let (mut lhs, mut rhs) = (*self.as_ref(), *rhs.as_ref()); // Leverage two GCD identity rules to make self and rhs odd. @@ -93,7 +104,7 @@ impl Odd> { rhs: &NonZero>, ) -> (Odd>, Int, Int) where - Uint: ConcatMixed, MixedOutput=Uint> + Uint: ConcatMixed, MixedOutput = Uint>, { let (abs_lhs, sgn_lhs) = self.abs_sign(); let (abs_rhs, sgn_rhs) = rhs.abs_sign(); @@ -148,7 +159,10 @@ impl Odd> { mod test { mod test_int_binxgcd { - use crate::{ConcatMixed, Int, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64, U768, U8192, Gcd}; + use crate::{ + ConcatMixed, Gcd, Int, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, + U64, U768, U8192, + }; use rand_core::OsRng; fn int_binxgcd_test( @@ -156,7 +170,7 @@ mod test { rhs: Int, ) where Uint: ConcatMixed, MixedOutput = Uint>, - Int: Gcd> + Int: Gcd>, { let gcd = lhs.gcd(&rhs); let (xgcd, x, y) = lhs.binxgcd(&rhs); @@ -164,16 +178,13 @@ mod test { let x_lhs = x.widening_mul(&lhs); let y_rhs = y.widening_mul(&rhs); let prod = x_lhs.wrapping_add(&y_rhs); - assert_eq!( - prod, - xgcd.resize().as_int() - ); + assert_eq!(prod, xgcd.resize().as_int()); } fn int_binxgcd_tests() where Uint: ConcatMixed, MixedOutput = Uint>, - Int: Gcd> + Int: Gcd>, { int_binxgcd_test(Int::MIN, Int::MIN); int_binxgcd_test(Int::MIN, Int::MINUS_ONE); @@ -223,7 +234,10 @@ mod test { } mod test_nonzero_int_binxgcd { - use crate::{ConcatMixed, Int, NonZero, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64, U768, U8192, RandomMod, Gcd}; + use crate::{ + ConcatMixed, Gcd, Int, NonZero, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, + U4096, U512, U64, U768, U8192, + }; use rand_core::OsRng; fn nz_int_binxgcd_test( @@ -231,7 +245,7 @@ mod test { rhs: NonZero>, ) where Uint: ConcatMixed, MixedOutput = Uint>, - Int: Gcd> + Int: Gcd>, { let gcd = lhs.gcd(&rhs); let (xgcd, x, y) = lhs.binxgcd(&rhs); @@ -245,7 +259,7 @@ mod test { fn nz_int_binxgcd_tests() where Uint: ConcatMixed, MixedOutput = Uint>, - Int: Gcd> + Int: Gcd>, { let nz_min = Int::MIN.to_nz().expect("is nz"); let nz_minus_one = Int::MINUS_ONE.to_nz().expect("is nz"); @@ -267,8 +281,14 @@ mod test { let bound = Int::MIN.abs().to_nz().unwrap(); for _ in 0..100 { - let x = Uint::random_mod(&mut OsRng, &bound).as_int().to_nz().unwrap(); - let y = Uint::random_mod(&mut OsRng, &bound).as_int().to_nz().unwrap(); + let x = Uint::random_mod(&mut OsRng, &bound) + .as_int() + .to_nz() + .unwrap(); + let y = Uint::random_mod(&mut OsRng, &bound) + .as_int() + .to_nz() + .unwrap(); nz_int_binxgcd_test(x, y); } } diff --git a/src/int/gcd.rs b/src/int/gcd.rs index 7b4c7e182..a7c18ea13 100644 --- a/src/int/gcd.rs +++ b/src/int/gcd.rs @@ -62,7 +62,7 @@ mod tests { } #[test] - fn gcd(){ + fn gcd() { assert_eq!(I64::MIN.gcd(&I64::ZERO), I64::MIN.abs()); assert_eq!(I64::ZERO.gcd(&I64::MIN), I64::MIN.abs()); } diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index 7e3a80424..06e2bdbdc 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -24,7 +24,7 @@ impl Uint { /// Given `(self, rhs)`, computes `(g, x, y)`, s.t. `self * x + rhs * y = g = gcd(self, rhs)`. pub fn binxgcd(&self, rhs: &Self) -> (Uint, Int, Int) where - Uint: ConcatMixed, MixedOutput=Uint> + Uint: ConcatMixed, MixedOutput = Uint>, { // TODO: make sure the cast to int works self.as_int().binxgcd(&rhs.as_int()) @@ -36,7 +36,9 @@ impl Uint { mod tests { use rand_core::OsRng; - use crate::{Gcd, Random, Uint, U1024, U16384, U2048, U256, U4096, U512, U8192, Int, U128, U64}; + use crate::{ + Gcd, Int, Random, Uint, U1024, U128, U16384, U2048, U256, U4096, U512, U64, U8192, + }; fn bingcd_test(lhs: Uint, rhs: Uint) where diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index 6849f138b..5caba0f4c 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -1,5 +1,5 @@ use crate::uint::bingcd::tools::{const_max, const_min}; -use crate::{NonZero, Odd, Uint, U128, U64, ConstChoice}; +use crate::{ConstChoice, NonZero, Odd, Uint, U128, U64}; impl NonZero> { /// Compute the greatest common divisor of `self` and `rhs`. @@ -127,7 +127,7 @@ impl Odd> { // (self, rhs) corresponds to (m, y) in the Algorithm 1 notation. let (mut a, mut b) = (*rhs, *self.as_ref()); - let iterations = (2 * Self::BITS - 1).div_ceil(K-1); + let iterations = (2 * Self::BITS - 1).div_ceil(K - 1); let mut i = 0; while i < iterations { i += 1; @@ -137,7 +137,8 @@ impl Odd> { let n = const_max(2 * K, const_max(a_bits, b.bits())); let a_ = a.compact::(n); let b_ = b.compact::(n); - let compact_contains_all_of_a = ConstChoice::from_u32_le(a_bits, K-1).or(ConstChoice::from_u32_eq(n, 2*K)); + let compact_contains_all_of_a = + ConstChoice::from_u32_le(a_bits, K - 1).or(ConstChoice::from_u32_eq(n, 2 * K)); // Compute the K-1 iteration update matrix from a_ and b_ // Safe to vartime; function executes in time variable in `iterations` only, which is @@ -219,7 +220,7 @@ mod tests { } mod test_bingcd_large { - use crate::{Gcd, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, Int}; + use crate::{Gcd, Int, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512}; use rand_core::OsRng; fn bingcd_large_test(lhs: Uint, rhs: Uint) diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 021b7e52f..5e78a201e 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -81,8 +81,11 @@ impl Odd> { /// . pub const fn classic_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { let total_iterations = 2 * Self::BITS - 1; - let (gcd, _, matrix, total_bound_shift) = - self.partial_binxgcd_vartime::(rhs.as_ref(), total_iterations, ConstChoice::TRUE); + let (gcd, _, matrix, total_bound_shift) = self.partial_binxgcd_vartime::( + rhs.as_ref(), + total_iterations, + ConstChoice::TRUE, + ); // Extract the Bezout coefficients. let IntMatrix { m00, m01, .. } = matrix; @@ -142,7 +145,8 @@ impl Odd> { let n = const_max(2 * K, const_max(a.bits(), b_bits)); let a_ = a.compact::(n); let b_ = b.compact::(n); - let compact_contains_all_of_a = ConstChoice::from_u32_le(b_bits, K-1).or(ConstChoice::from_u32_eq(n, 2*K)); + let compact_contains_all_of_a = + ConstChoice::from_u32_le(b_bits, K - 1).or(ConstChoice::from_u32_eq(n, 2 * K)); // Compute the K-1 iteration update matrix from a_ and b_ let (.., update_matrix, log_upper_bound) = a_ @@ -206,7 +210,7 @@ impl Odd> { &self, rhs: &Uint, iterations: u32, - halt_at_zero: ConstChoice + halt_at_zero: ConstChoice, ) -> (Self, Uint, IntMatrix, u32) { // debug_assert!(iterations < Uint::::BITS); // (self, rhs) corresponds to (b_, a_) in the Algorithm 1 notation. @@ -258,7 +262,7 @@ impl Odd> { b: &mut Uint, matrix: &mut IntMatrix, executed_iterations: &mut u32, - halt_at_zero: ConstChoice + halt_at_zero: ConstChoice, ) { let a_odd = a.is_odd(); let a_lt_b = Uint::lt(a, b); @@ -296,7 +300,8 @@ mod tests { fn test_partial_binxgcd() { let a = U64::from_be_hex("CA048AFA63CD6A1F").to_odd().unwrap(); let b = U64::from_be_hex("AE693BF7BE8E5566"); - let (.., matrix, iters) = a.partial_binxgcd_vartime::<{U64::LIMBS}>(&b, 5, ConstChoice::TRUE); + let (.., matrix, iters) = + a.partial_binxgcd_vartime::<{ U64::LIMBS }>(&b, 5, ConstChoice::TRUE); assert_eq!(iters, 5); assert_eq!( matrix, @@ -308,7 +313,8 @@ mod tests { fn test_partial_binxgcd_constructs_correct_matrix() { let a = U64::from_be_hex("CA048AFA63CD6A1F").to_odd().unwrap(); let b = U64::from_be_hex("AE693BF7BE8E5566"); - let (new_a, new_b, matrix, iters) = a.partial_binxgcd_vartime::<{U64::LIMBS}>(&b, 5, ConstChoice::TRUE); + let (new_a, new_b, matrix, iters) = + a.partial_binxgcd_vartime::<{ U64::LIMBS }>(&b, 5, ConstChoice::TRUE); assert_eq!(iters, 5); let (mut computed_a, mut computed_b ) = matrix.extended_apply_to((a.get(), b)); @@ -324,7 +330,8 @@ mod tests { // Stop before max_iters let a = U64::from_be_hex("0000000003CD6A1F").to_odd().unwrap(); let b = U64::from_be_hex("000000000E8E5566"); - let (gcd, .., iters) = a.partial_binxgcd_vartime::<{ U64::LIMBS }>(&b, 60, ConstChoice::TRUE); + let (gcd, .., iters) = + a.partial_binxgcd_vartime::<{ U64::LIMBS }>(&b, 60, ConstChoice::TRUE); assert_eq!(iters, 35); assert_eq!(gcd.get(), a.gcd(&b)); } @@ -334,7 +341,8 @@ mod tests { // Stop before max_iters let a = U64::from_be_hex("0000000003CD6A1F").to_odd().unwrap(); let b = U64::from_be_hex("000000000E8E5566"); - let (gcd, .., iters) = a.partial_binxgcd_vartime::<{ U64::LIMBS }>(&b, 60, ConstChoice::FALSE); + let (gcd, .., iters) = + a.partial_binxgcd_vartime::<{ U64::LIMBS }>(&b, 60, ConstChoice::FALSE); assert_eq!(iters, 60); assert_eq!(gcd.get(), a.gcd(&b)); } From ba61286ba5a924ddacfc8a1fdc1ad1cdf478ff41 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 11:22:49 +0100 Subject: [PATCH 109/203] Improve `partial_binxgcd_vartime` --- src/uint/bingcd/matrix.rs | 6 ++++ src/uint/bingcd/xgcd.rs | 64 +++++++++++++++++++++++++++------------ src/uint/cmp.rs | 6 ++++ 3 files changed, 57 insertions(+), 19 deletions(-) diff --git a/src/uint/bingcd/matrix.rs b/src/uint/bingcd/matrix.rs index afda51a91..c5b3e7445 100644 --- a/src/uint/bingcd/matrix.rs +++ b/src/uint/bingcd/matrix.rs @@ -72,6 +72,12 @@ impl IntMatrix { Int::conditional_swap(&mut self.m01, &mut self.m11, swap); } + /// Swap the rows of this matrix. + #[inline] + pub(crate) const fn swap_rows(&mut self) { + self.conditional_swap_rows(ConstChoice::TRUE) + } + /// Subtract the bottom row from the top if `subtract` is truthy. Otherwise, do nothing. #[inline] pub(crate) const fn conditional_subtract_bottom_row_from_top(&mut self, subtract: ConstChoice) { diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 5e78a201e..de455942f 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -203,8 +203,6 @@ impl Odd> { /// Assumes `iterations < Uint::::BITS`. /// /// The function executes in time variable in `iterations`. - /// - /// TODO: this only works for `self` and `rhs` that are <= Int::MAX. #[inline] pub(super) const fn partial_binxgcd_vartime( &self, @@ -212,27 +210,36 @@ impl Odd> { iterations: u32, halt_at_zero: ConstChoice, ) -> (Self, Uint, IntMatrix, u32) { - // debug_assert!(iterations < Uint::::BITS); - // (self, rhs) corresponds to (b_, a_) in the Algorithm 1 notation. let (mut a, mut b) = (*self.as_ref(), *rhs); - - // Compute the update matrix. This matrix corresponds with (f0, g0, f1, g1) in the paper. + // This matrix corresponds with (f0, g0, f1, g1) in the paper. let mut matrix = IntMatrix::UNIT; - matrix.conditional_swap_rows(ConstChoice::TRUE); + + // Compute the update matrix. + // Note: to be consistent with the paper, the `binxgcd_step` algorithm requires the second + // argument to be odd. Here, we have `a` odd, so we have to swap a and b before and after + // calling the subroutine. The columns of the matrix have to be swapped accordingly. + Uint::swap(&mut a, &mut b); + matrix.swap_rows(); + let mut executed_iterations = 0; let mut j = 0; while j < iterations { - Self::binxgcd_step::(&mut b, &mut a, &mut matrix, &mut executed_iterations, halt_at_zero); + Self::binxgcd_step::( + &mut a, + &mut b, + &mut matrix, + &mut executed_iterations, + halt_at_zero, + ); j += 1; } - matrix.conditional_swap_rows(ConstChoice::TRUE); - ( - a.to_odd().expect("a is always odd"), - b, - matrix, - executed_iterations, - ) + // Undo swap + Uint::swap(&mut a, &mut b); + matrix.swap_rows(); + + let a = a.to_odd().expect("a is always odd"); + (a, b, matrix, executed_iterations) } /// Binary XGCD update step. @@ -255,6 +262,9 @@ impl Odd> { /// (f1 g1). /// ``` /// + /// Note: this algorithm assumes `b` to be an odd integer. The algorithm will likely not yield + /// the correct result when this is not the case. + /// /// Ref: Pornin, Algorithm 2, L8-17, . #[inline] const fn binxgcd_step( @@ -317,11 +327,27 @@ mod tests { a.partial_binxgcd_vartime::<{ U64::LIMBS }>(&b, 5, ConstChoice::TRUE); assert_eq!(iters, 5); - let (mut computed_a, mut computed_b ) = matrix.extended_apply_to((a.get(), b)); - let computed_a = computed_a.div_2k(5).drop_extension().expect("no overflow").0; - let computed_b = computed_b.div_2k(5).drop_extension().expect("no overflow").0; + let (computed_a, computed_b ) = matrix.extended_apply_to((a.get(), b)); + let computed_a = computed_a + .div_2k(5) + .drop_extension() + .expect("no overflow") + .0; + let computed_b = computed_b + .div_2k(5) + .drop_extension() + .expect("no overflow") + .0; - assert_eq!(new_a.get(), computed_a, "{} {} {} {}", new_a, new_b, computed_a, computed_b); + assert_eq!( + new_a.get(), + computed_a, + "{} {} {} {}", + new_a, + new_b, + computed_a, + computed_b + ); assert_eq!(new_b, computed_b); } diff --git a/src/uint/cmp.rs b/src/uint/cmp.rs index 10d6d776a..747cb9590 100644 --- a/src/uint/cmp.rs +++ b/src/uint/cmp.rs @@ -31,6 +31,12 @@ impl Uint { (*a, *b) = (Self::select(a, b, c), Self::select(b, a, c)); } + /// Swap `a` and `b`. + #[inline] + pub(crate) const fn swap(a: &mut Self, b: &mut Self) { + Self::conditional_swap(a, b, ConstChoice::TRUE) + } + /// Returns the truthy value if `self`!=0 or the falsy value otherwise. #[inline] pub(crate) const fn is_nonzero(&self) -> ConstChoice { From 8726621ffc200eb3226224dbb1d43419e327f1e0 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 12:47:37 +0100 Subject: [PATCH 110/203] Clean up tests --- src/uint/bingcd/xgcd.rs | 72 +++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index de455942f..eb60cd94b 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -301,6 +301,7 @@ impl Odd> { #[cfg(test)] mod tests { + use crate::{ConcatMixed, Gcd, Int, Uint}; mod test_partial_binxgcd { use crate::uint::bingcd::matrix::IntMatrix; @@ -327,7 +328,7 @@ mod tests { a.partial_binxgcd_vartime::<{ U64::LIMBS }>(&b, 5, ConstChoice::TRUE); assert_eq!(iters, 5); - let (computed_a, computed_b ) = matrix.extended_apply_to((a.get(), b)); + let (computed_a, computed_b) = matrix.extended_apply_to((a.get(), b)); let computed_a = computed_a .div_2k(5) .drop_extension() @@ -374,11 +375,29 @@ mod tests { } } + /// Helper function to effectively test xgcd. + fn test_xgcd( + lhs: Uint, + rhs: Uint, + found_gcd: Uint, + x: Int, + y: Int, + ) where + Uint: + Gcd> + ConcatMixed, MixedOutput = Uint>, + { + // Test the gcd + assert_eq!(lhs.gcd(&rhs), found_gcd); + // Test the Bezout coefficients + assert_eq!( + x.widening_mul_uint(&lhs) + y.widening_mul_uint(&rhs), + found_gcd.resize().as_int(), + ); + } + mod test_classic_binxgcd { - use crate::{ - ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, U4096, - U512, U768, U8192, - }; + use crate::uint::bingcd::xgcd::tests::test_xgcd; + use crate::{ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U768, U8192, U64}; use rand_core::OsRng; fn classic_binxgcd_test( @@ -388,24 +407,11 @@ mod tests { Uint: Gcd> + ConcatMixed, MixedOutput = Uint>, { - let gcd = lhs.gcd(&rhs); let (binxgcd, x, y) = lhs .to_odd() .unwrap() .classic_binxgcd(&rhs.to_odd().unwrap()); - assert_eq!(gcd, binxgcd); - - // test bezout coefficients - let prod = x.widening_mul_uint(&lhs) + y.widening_mul_uint(&rhs); - assert_eq!( - prod, - binxgcd.resize().as_int(), - "{} {} {} {}", - lhs, - rhs, - x, - y - ); + test_xgcd(lhs, rhs, binxgcd.get(), x, y); } fn classic_binxgcd_tests() @@ -413,18 +419,15 @@ mod tests { Uint: Gcd> + ConcatMixed, MixedOutput = Uint>, { - // upper bound - let upper_bound = *Int::MAX.as_uint(); - // Edge cases + let upper_bound = *Int::MAX.as_uint(); classic_binxgcd_test(Uint::ONE, Uint::ONE); classic_binxgcd_test(Uint::ONE, upper_bound); classic_binxgcd_test(upper_bound, Uint::ONE); classic_binxgcd_test(upper_bound, upper_bound); - classic_binxgcd_test(upper_bound, upper_bound); // Randomized test cases - let bound = upper_bound.wrapping_add(&Uint::ONE).to_nz().unwrap(); + let bound = Int::MIN.as_uint().to_nz().unwrap(); for _ in 0..100 { let x = Uint::::random_mod(&mut OsRng, &bound).bitor(&Uint::ONE); let y = Uint::::random_mod(&mut OsRng, &bound).bitor(&Uint::ONE); @@ -434,7 +437,7 @@ mod tests { #[test] fn test_classic_binxgcd() { - // Cannot be applied to U64 + classic_binxgcd_tests::<{ U64::LIMBS }, { U128::LIMBS }>(); classic_binxgcd_tests::<{ U128::LIMBS }, { U256::LIMBS }>(); classic_binxgcd_tests::<{ U192::LIMBS }, { U384::LIMBS }>(); classic_binxgcd_tests::<{ U256::LIMBS }, { U512::LIMBS }>(); @@ -447,10 +450,8 @@ mod tests { } mod test_binxgcd { - use crate::{ - ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, U4096, - U512, U768, U8192, - }; + use crate::uint::bingcd::xgcd::tests::test_xgcd; + use crate::{ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U768, U8192, U64}; use rand_core::OsRng; fn binxgcd_test(lhs: Uint, rhs: Uint) @@ -458,16 +459,11 @@ mod tests { Uint: Gcd> + ConcatMixed, MixedOutput = Uint>, { - let gcd = lhs.gcd(&rhs); let (binxgcd, x, y) = lhs .to_odd() .unwrap() .optimized_binxgcd(&rhs.to_odd().unwrap()); - assert_eq!(gcd, binxgcd); - - // test bezout coefficients - let prod = x.widening_mul_uint(&lhs) + y.widening_mul_uint(&rhs); - assert_eq!(prod, binxgcd.resize().as_int()) + test_xgcd(lhs, rhs, binxgcd.get(), x, y); } fn binxgcd_tests() @@ -475,17 +471,15 @@ mod tests { Uint: Gcd> + ConcatMixed, MixedOutput = Uint>, { - // upper bound - let upper_bound = *Int::MAX.as_uint(); - // Edge cases + let upper_bound = *Int::MAX.as_uint(); binxgcd_test(Uint::ONE, Uint::ONE); binxgcd_test(Uint::ONE, upper_bound); binxgcd_test(upper_bound, Uint::ONE); binxgcd_test(upper_bound, upper_bound); // Randomized test cases - let bound = upper_bound.wrapping_add(&Uint::ONE).to_nz().unwrap(); + let bound = Int::MIN.as_uint().to_nz().unwrap(); for _ in 0..100 { let x = Uint::::random_mod(&mut OsRng, &bound).bitor(&Uint::ONE); let y = Uint::::random_mod(&mut OsRng, &bound).bitor(&Uint::ONE); From 31e108bccb601e967cd79061ac707cb858f6f8e3 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 12:47:51 +0100 Subject: [PATCH 111/203] Add xgcd restriction warnings --- src/uint/bingcd/xgcd.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index eb60cd94b..1cc8e6eaf 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -54,8 +54,8 @@ impl Odd> { /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, /// leveraging the Binary Extended GCD algorithm. /// - /// WARNING! This algorithm is limited to values that are `<= Int::MAX`; for larger values, - /// the algorithm overflows. + /// **Warning**: this algorithm is only guaranteed to work for `self` and `rhs` for which the + /// msb is **not** set. May panic otherwise. /// /// This function switches between the "classic" and "optimized" algorithm at a best-effort /// threshold. When using [Uint]s with `LIMBS` close to the threshold, it may be useful to @@ -77,6 +77,9 @@ impl Odd> { /// /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. /// + /// **Warning**: this algorithm is only guaranteed to work for `self` and `rhs` for which the + /// msb is **not** set. May panic otherwise. + /// /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 1. /// . pub const fn classic_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { @@ -98,6 +101,9 @@ impl Odd> { /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, /// leveraging the Binary Extended GCD algorithm. /// + /// **Warning**: this algorithm is only guaranteed to work for `self` and `rhs` for which the + /// msb is **not** set. May panic otherwise. + /// /// Note: this algorithm becomes more efficient than the classical algorithm for [Uint]s with /// relatively many `LIMBS`. A best-effort threshold is presented in [Self::limited_binxgcd]. /// @@ -114,6 +120,9 @@ impl Odd> { /// Given `(self, rhs)`, computes `(g, x, y)`, s.t. `self * x + rhs * y = g = gcd(self, rhs)`, /// leveraging the optimized Binary Extended GCD algorithm. /// + /// **Warning**: this algorithm is only guaranteed to work for `self` and `rhs` for which the + /// msb is **not** set. May panic otherwise. + /// /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 2. /// /// @@ -200,8 +209,6 @@ impl Odd> { /// are counted and additionally returned. Note that each element in `M` lies in the interval /// `(-2^executed_iterations, 2^executed_iterations]`. /// - /// Assumes `iterations < Uint::::BITS`. - /// /// The function executes in time variable in `iterations`. #[inline] pub(super) const fn partial_binxgcd_vartime( From 4e67445a95ce34e4239e71e05b3b4badfc88f503 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 12:52:25 +0100 Subject: [PATCH 112/203] Clean up --- src/int/bingcd.rs | 2 +- src/uint/bingcd.rs | 22 +++++++++------------- src/uint/bingcd/matrix.rs | 9 --------- src/uint/bingcd/xgcd.rs | 12 ++++++------ 4 files changed, 16 insertions(+), 29 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index 0d26391a2..41150abfd 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -123,7 +123,7 @@ impl Odd> { ); let rhs_ = abs_rhs.to_odd().expect("rhs is odd by construction"); - let (gcd, mut x, mut y) = abs_lhs.limited_binxgcd(&rhs_); + let (gcd, mut x, mut y) = abs_lhs.binxgcd(&rhs_); let x_lhs = x.widening_mul_uint(abs_lhs.as_ref()); let y_rhs = y.widening_mul_uint(&abs_rhs); diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index 06e2bdbdc..f31a35867 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -2,7 +2,7 @@ //! which is described by Pornin as Algorithm 2 in "Optimized Binary GCD for Modular Inversion". //! Ref: -use crate::{ConcatMixed, Int, Uint}; +use crate::Uint; mod extension; mod gcd; @@ -20,15 +20,6 @@ impl Uint { .expect("self is non zero by construction"); Uint::select(self_nz.bingcd(rhs).as_ref(), rhs, self_is_zero) } - - /// Given `(self, rhs)`, computes `(g, x, y)`, s.t. `self * x + rhs * y = g = gcd(self, rhs)`. - pub fn binxgcd(&self, rhs: &Self) -> (Uint, Int, Int) - where - Uint: ConcatMixed, MixedOutput = Uint>, - { - // TODO: make sure the cast to int works - self.as_int().binxgcd(&rhs.as_int()) - } } #[cfg(feature = "rand_core")] @@ -54,18 +45,23 @@ mod tests { Uint: Gcd>, { // Edge cases + let min = Int::MIN.abs(); bingcd_test(Uint::ZERO, Uint::ZERO); bingcd_test(Uint::ZERO, Uint::ONE); + bingcd_test(Uint::ZERO, min); bingcd_test(Uint::ZERO, Uint::MAX); bingcd_test(Uint::ONE, Uint::ZERO); bingcd_test(Uint::ONE, Uint::ONE); + bingcd_test(Uint::ONE, min); bingcd_test(Uint::ONE, Uint::MAX); + bingcd_test(min, Uint::ZERO); + bingcd_test(min, Uint::ONE); + bingcd_test(min, Int::MIN.abs()); + bingcd_test(min, Uint::MAX); bingcd_test(Uint::MAX, Uint::ZERO); bingcd_test(Uint::MAX, Uint::ONE); + bingcd_test(Uint::ONE, min); bingcd_test(Uint::MAX, Uint::MAX); - bingcd_test(Int::MIN.abs(), Uint::ZERO); - bingcd_test(Int::MAX.abs(), Int::MIN.abs()); - bingcd_test(Uint::MAX, Int::MIN.abs()); // Randomized test cases for _ in 0..100 { diff --git a/src/uint/bingcd/matrix.rs b/src/uint/bingcd/matrix.rs index c5b3e7445..76d87defa 100644 --- a/src/uint/bingcd/matrix.rs +++ b/src/uint/bingcd/matrix.rs @@ -85,15 +85,6 @@ impl IntMatrix { self.m01 = Int::select(&self.m01, &self.m01.wrapping_sub(&self.m11), subtract); } - /// Double the bottom row of this matrix. - #[inline] - pub(crate) const fn double_bottom_row(&mut self) { - // safe to vartime; shr_vartime is variable in the value of shift only. Since this shift - // is a public constant, the constant time property of this algorithm is not impacted. - self.m10 = self.m10.shl_vartime(1); - self.m11 = self.m11.shl_vartime(1); - } - /// Double the bottom row of this matrix if `double` is truthy. Otherwise, do nothing. #[inline] pub(crate) const fn conditional_double_bottom_row(&mut self, double: ConstChoice) { diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 1cc8e6eaf..7e2e69cf6 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -60,7 +60,7 @@ impl Odd> { /// This function switches between the "classic" and "optimized" algorithm at a best-effort /// threshold. When using [Uint]s with `LIMBS` close to the threshold, it may be useful to /// manually test whether the classic or optimized algorithm is faster for your machine. - pub const fn limited_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { + pub(crate) const fn binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { // Verify that the top bit is not set on self or rhs. debug_assert!(!self.as_ref().as_int().is_negative().to_bool_vartime()); debug_assert!(!rhs.as_ref().as_int().is_negative().to_bool_vartime()); @@ -82,7 +82,7 @@ impl Odd> { /// /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 1. /// . - pub const fn classic_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { + pub(crate) const fn classic_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { let total_iterations = 2 * Self::BITS - 1; let (gcd, _, matrix, total_bound_shift) = self.partial_binxgcd_vartime::( rhs.as_ref(), @@ -105,7 +105,7 @@ impl Odd> { /// msb is **not** set. May panic otherwise. /// /// Note: this algorithm becomes more efficient than the classical algorithm for [Uint]s with - /// relatively many `LIMBS`. A best-effort threshold is presented in [Self::limited_binxgcd]. + /// relatively many `LIMBS`. A best-effort threshold is presented in [Self::binxgcd]. /// /// Note: the full algorithm has an additional parameter; this function selects the best-effort /// value for this parameter. You might be able to further tune your performance by calling the @@ -113,7 +113,7 @@ impl Odd> { /// /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 2. /// . - pub const fn optimized_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { + pub(crate) const fn optimized_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { self.optimized_binxgcd_::<{ U64::BITS }, { U64::LIMBS }, { U128::LIMBS }>(&rhs) } @@ -136,7 +136,7 @@ impl Odd> { /// `K` close to a (multiple of) the number of bits that fit in a single register. /// - `LIMBS_K`: should be chosen as the minimum number s.t. `Uint::::BITS ≥ K`, /// - `LIMBS_2K`: should be chosen as the minimum number s.t. `Uint::::BITS ≥ 2K`. - pub const fn optimized_binxgcd_( + pub(super) const fn optimized_binxgcd_( &self, rhs: &Self, ) -> (Self, Int, Int) { @@ -458,7 +458,7 @@ mod tests { mod test_binxgcd { use crate::uint::bingcd::xgcd::tests::test_xgcd; - use crate::{ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U768, U8192, U64}; + use crate::{ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U768, U8192}; use rand_core::OsRng; fn binxgcd_test(lhs: Uint, rhs: Uint) From 62a53be4e55909321cc4932e99f5c5cc171c03e9 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 13:10:14 +0100 Subject: [PATCH 113/203] Fmt --- src/uint/bingcd/xgcd.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 7e2e69cf6..66f6b2342 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -136,7 +136,11 @@ impl Odd> { /// `K` close to a (multiple of) the number of bits that fit in a single register. /// - `LIMBS_K`: should be chosen as the minimum number s.t. `Uint::::BITS ≥ K`, /// - `LIMBS_2K`: should be chosen as the minimum number s.t. `Uint::::BITS ≥ 2K`. - pub(super) const fn optimized_binxgcd_( + pub(super) const fn optimized_binxgcd_< + const K: u32, + const LIMBS_K: usize, + const LIMBS_2K: usize, + >( &self, rhs: &Self, ) -> (Self, Int, Int) { @@ -404,7 +408,10 @@ mod tests { mod test_classic_binxgcd { use crate::uint::bingcd::xgcd::tests::test_xgcd; - use crate::{ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U768, U8192, U64}; + use crate::{ + ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, U4096, + U512, U64, U768, U8192, + }; use rand_core::OsRng; fn classic_binxgcd_test( @@ -458,7 +465,10 @@ mod tests { mod test_binxgcd { use crate::uint::bingcd::xgcd::tests::test_xgcd; - use crate::{ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U768, U8192}; + use crate::{ + ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, U4096, + U512, U768, U8192, + }; use rand_core::OsRng; fn binxgcd_test(lhs: Uint, rhs: Uint) From 975b784003d0179c553e07cb7f24ca4ceb64db6f Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 13:10:28 +0100 Subject: [PATCH 114/203] Refactor `Int::binxgcd` --- src/int/bingcd.rs | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index 41150abfd..3015c00bd 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -19,32 +19,28 @@ impl Int { Uint: ConcatMixed, MixedOutput = Uint>, { // Make sure `self` and `rhs` are nonzero. - let self_is_zero = self.is_nonzero().not(); - let self_nz = Int::select(self, &Int::ONE, self_is_zero) + let self_is_nz = self.is_nonzero(); + let self_nz = Int::select(&Int::ONE, self, self_is_nz) .to_nz() .expect("self is non zero by construction"); - let rhs_is_zero = rhs.is_nonzero().not(); - let rhs_nz = Int::select(rhs, &Int::ONE, rhs_is_zero) + let rhs_is_nz = rhs.is_nonzero(); + let rhs_nz = Int::select(&Int::ONE, rhs, rhs_is_nz) .to_nz() - .expect("self is non zero by construction"); + .expect("rhs is non zero by construction"); let (gcd, mut x, mut y) = self_nz.binxgcd(&rhs_nz); - // Account for the case that self or rhs was zero - let gcd = Uint::select(gcd.as_ref(), &rhs.abs(), self_is_zero); - let gcd = Uint::select(&gcd, &self.abs(), rhs_is_zero); - x = Int::select(&x, &Int::ZERO, self_is_zero); - y = Int::select( - &y, - &Int::select(&Int::ONE, &Int::MINUS_ONE, rhs.is_negative()), - self_is_zero, - ); - x = Int::select( - &x, - &Int::select(&Int::ONE, &Int::MINUS_ONE, self.is_negative()), - rhs_is_zero, - ); - y = Int::select(&y, &Int::ZERO, rhs_is_zero); + // Correct the gcd in case self and/or rhs was zero + let gcd = Uint::select(&rhs.abs(), gcd.as_ref(), self_is_nz); + let gcd = Uint::select(&self.abs(), &gcd, rhs_is_nz); + + // Correct the Bézout coefficients in case self and/or rhs was zero. + let signum_self = Int::new_from_abs_sign(Uint::ONE, self.is_negative()).expect("+/- 1"); + let signum_rhs = Int::new_from_abs_sign(Uint::ONE, rhs.is_negative()).expect("+/- 1"); + x = Int::select(&Int::ZERO, &x, self_is_nz); + y = Int::select(&signum_rhs, &y, self_is_nz); + x = Int::select(&signum_self, &x, rhs_is_nz); + y = Int::select(&Int::ZERO, &y, rhs_is_nz); (gcd, x, y) } From 99940f15cd1554fec990b3105713728f93c2f0fb Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 13:10:50 +0100 Subject: [PATCH 115/203] Refactor `NonZero::::binxgcd` --- src/int/bingcd.rs | 50 ++++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index 3015c00bd..6e4d86f9a 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -58,36 +58,32 @@ impl NonZero> { Uint: ConcatMixed, MixedOutput = Uint>, { let (mut lhs, mut rhs) = (*self.as_ref(), *rhs.as_ref()); - // Leverage two GCD identity rules to make self and rhs odd. - // 1) gcd(2a, 2b) = 2 * gcd(a, b) - // 2) gcd(a, 2b) = gcd(a, b) if a is odd. - let i = lhs.is_nonzero().select_u32(0, lhs.0.trailing_zeros()); - let j = rhs.is_nonzero().select_u32(0, rhs.0.trailing_zeros()); - let k = const_min(i, j); - // Remove the common factor `2^k` from both lhs and rhs. + // Leverage the property that gcd(2^k * a, 2^k *b) = 2^k * gcd(a, b) + let i = lhs.0.trailing_zeros(); + let j = rhs.0.trailing_zeros(); + let k = const_min(i, j); lhs = lhs.shr(k); rhs = rhs.shr(k); - // At this point, either lhs or rhs is odd (or both). - // Switch them to make sure lhs is odd. - let do_swap = ConstChoice::from_u32_lt(j, i); - Int::conditional_swap(&mut lhs, &mut rhs, do_swap); - let lhs_ = lhs.to_odd().expect("lhs is odd by construction"); - - // Compute the xgcd for odd lhs_ and rhs_ - let rhs_nz = rhs.to_nz().expect("rhs is non-zero by construction"); - let (gcd, mut x, mut y) = lhs_.binxgcd(&rhs_nz); - - // Account for the fact that we may have previously swapped lhs and rhs. - Int::conditional_swap(&mut x, &mut y, do_swap); - ( - gcd.as_ref() - .shl(k) - .to_nz() - .expect("gcd of non-zero element with another element is non-zero"), - x, - y, - ) + + // Note: at this point, either lhs or rhs is odd (or both). + // Swap to make sure lhs is odd. + let swap = ConstChoice::from_u32_lt(j, i); + Int::conditional_swap(&mut lhs, &mut rhs, swap); + let lhs = lhs.to_odd().expect("odd by construction"); + + let rhs = rhs.to_nz().expect("non-zero by construction"); + let (gcd, mut x, mut y) = lhs.binxgcd(&rhs); + + Int::conditional_swap(&mut x, &mut y, swap); + + // Add the factor 2^k to the gcd. + let gcd = gcd + .shl(k) + .to_nz() + .expect("gcd of non-zero element with another element is non-zero"); + + (gcd, x, y) } } From 3388908fc2e44dcae0b17711d3a597054bea9db3 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 13:39:21 +0100 Subject: [PATCH 116/203] Refactor `Odd::::binxgcd` --- src/int/bingcd.rs | 42 +------------------------------------- src/uint/bingcd/xgcd.rs | 45 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 46 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index 6e4d86f9a..ab1484677 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -101,47 +101,7 @@ impl Odd> { let (abs_lhs, sgn_lhs) = self.abs_sign(); let (abs_rhs, sgn_rhs) = rhs.abs_sign(); - // Make rhs odd - let rhs_is_odd = ConstChoice::from_u32_eq(abs_rhs.as_ref().trailing_zeros(), 0); - let rhs_gt_lhs = Uint::gt(&abs_rhs.as_ref(), abs_lhs.as_ref()); - let abs_rhs = Uint::select( - &Uint::select( - &abs_lhs.as_ref().wrapping_sub(&abs_rhs.as_ref()), - &abs_rhs.as_ref().wrapping_sub(&abs_lhs.as_ref()), - rhs_gt_lhs, - ), - &abs_rhs.as_ref(), - rhs_is_odd, - ); - let rhs_ = abs_rhs.to_odd().expect("rhs is odd by construction"); - - let (gcd, mut x, mut y) = abs_lhs.binxgcd(&rhs_); - - let x_lhs = x.widening_mul_uint(abs_lhs.as_ref()); - let y_rhs = y.widening_mul_uint(&abs_rhs); - debug_assert_eq!(x_lhs.wrapping_add(&y_rhs), gcd.resize().as_int()); - - // At this point, we have one of the following three situations: - // i. gcd = lhs * x + (rhs - lhs) * y, if rhs is even and rhs > lhs - // ii. gcd = lhs * x + (lhs - rhs) * y, if rhs is even and rhs < lhs - // iii. gcd = lhs * x + rhs * y, if rhs is odd - - // Reverse-engineering the bezout coefficients for lhs and rhs, we get - // i. gcd = lhs * (x - y) + rhs * y, if rhs is even and rhs > lhs - // ii. gcd = lhs * (x + y) - y * rhs, if rhs is even and rhs < lhs - // iii. gcd = lhs * x + rhs * y, if rhs is odd - - x = Int::select(&x, &x.wrapping_sub(&y), rhs_is_odd.not().and(rhs_gt_lhs)); - x = Int::select( - &x, - &x.wrapping_add(&y), - rhs_is_odd.not().and(rhs_gt_lhs.not()), - ); - y = y.wrapping_neg_if(rhs_is_odd.not().and(rhs_gt_lhs.not())); - - let x_lhs = x.widening_mul_uint(abs_lhs.as_ref()); - let y_rhs = y.widening_mul_uint(&rhs.abs()); - debug_assert_eq!(x_lhs.wrapping_add(&y_rhs), gcd.resize().as_int()); + let (gcd, x, y) = abs_lhs.binxgcd_nz(&abs_rhs); (gcd, x.wrapping_neg_if(sgn_lhs), y.wrapping_neg_if(sgn_rhs)) } diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 66f6b2342..d7b601b89 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -1,6 +1,6 @@ use crate::uint::bingcd::matrix::IntMatrix; use crate::uint::bingcd::tools::const_max; -use crate::{ConstChoice, Int, Odd, Uint, U128, U64}; +use crate::{ConstChoice, Int, Odd, Uint, U128, U64, NonZero}; impl Int { /// Compute `self / 2^k mod q`. Executes in time variable in `k_bound`. This value should be @@ -51,6 +51,45 @@ impl Uint { } impl Odd> { + /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, + /// leveraging the Binary Extended GCD algorithm. + /// + /// **Warning**: this algorithm is only guaranteed to work for `self` and `rhs` for which the + /// msb is **not** set. May panic otherwise. + pub(crate) const fn binxgcd_nz(&self, rhs: &NonZero>) -> (Self, Int, Int) { + // Note that for the `binxgcd` subroutine, `rhs` needs to be odd. + // + // We use the fact that gcd(a, b) = gcd(a, |a-b|) to + // 1) convert the input (self, rhs) to (self, rhs') where rhs' is guaranteed odd, + // 2) execute the xgcd algorithm on (self, rhs'), and + // 3) recover the Bezout coefficients for (self, rhs) from the (self, rhs') output. + + let (abs_lhs_sub_rhs, rhs_gt_lhs) = self.as_ref().wrapping_sub(rhs.as_ref()).as_int().abs_sign(); + let rhs_is_even = rhs.as_ref().is_odd().not(); + let rhs_ = Uint::select(rhs.as_ref(), &abs_lhs_sub_rhs, rhs_is_even) + .to_odd() + .expect("rhs is odd by construction"); + + let (gcd, mut x, mut y) = self.binxgcd(&rhs_); + + // At this point, we have one of the following three situations: + // i. gcd = lhs * x + (rhs - lhs) * y, if rhs is even and rhs > lhs + // ii. gcd = lhs * x + (lhs - rhs) * y, if rhs is even and rhs < lhs + // iii. gcd = lhs * x + rhs * y, if rhs is odd + + // We can rearrange these terms in one of three ways: + // i. gcd = lhs * (x - y) + rhs * y, if rhs is even and rhs > lhs + // ii. gcd = lhs * (x + y) - y * rhs, if rhs is even and rhs < lhs + // iii. gcd = lhs * x + rhs * y, if rhs is odd + // From this we can recover the Bezout coefficients from the original (self, rhs) input. + + x = Int::select(&x, &x.wrapping_sub(&y), rhs_is_even.and(rhs_gt_lhs)); + x = Int::select(&x, &x.wrapping_add(&y), rhs_is_even.and(rhs_gt_lhs.not())); + y = y.wrapping_neg_if(rhs_is_even.and(rhs_gt_lhs.not())); + + (gcd, x, y) + } + /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, /// leveraging the Binary Extended GCD algorithm. /// @@ -61,10 +100,6 @@ impl Odd> { /// threshold. When using [Uint]s with `LIMBS` close to the threshold, it may be useful to /// manually test whether the classic or optimized algorithm is faster for your machine. pub(crate) const fn binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { - // Verify that the top bit is not set on self or rhs. - debug_assert!(!self.as_ref().as_int().is_negative().to_bool_vartime()); - debug_assert!(!rhs.as_ref().as_int().is_negative().to_bool_vartime()); - // todo: optimize threshold if LIMBS < 5 { self.classic_binxgcd(rhs) From 7c17a68cdaef72900d8e2ef631c0f7526d1d0c59 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 15:12:48 +0100 Subject: [PATCH 117/203] Improve `optimized_bingcd` readability --- src/uint/bingcd/gcd.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index 5caba0f4c..aa5d13a0f 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -124,8 +124,7 @@ impl Odd> { &self, rhs: &Uint, ) -> Self { - // (self, rhs) corresponds to (m, y) in the Algorithm 1 notation. - let (mut a, mut b) = (*rhs, *self.as_ref()); + let (mut a, mut b) = (*self.as_ref(), *rhs); let iterations = (2 * Self::BITS - 1).div_ceil(K - 1); let mut i = 0; @@ -133,23 +132,23 @@ impl Odd> { i += 1; // Construct a_ and b_ as the summary of a and b, respectively. - let a_bits = a.bits(); - let n = const_max(2 * K, const_max(a_bits, b.bits())); + let b_bits = b.bits(); + let n = const_max(2 * K, const_max(a.bits(), b_bits)); let a_ = a.compact::(n); let b_ = b.compact::(n); - let compact_contains_all_of_a = - ConstChoice::from_u32_le(a_bits, K - 1).or(ConstChoice::from_u32_eq(n, 2 * K)); + let compact_contains_all_of_b = + ConstChoice::from_u32_le(b_bits, K - 1).or(ConstChoice::from_u32_eq(n, 2 * K)); // Compute the K-1 iteration update matrix from a_ and b_ // Safe to vartime; function executes in time variable in `iterations` only, which is // a public constant K-1 here. - let (.., matrix, log_upper_bound) = b_ + let (.., matrix, log_upper_bound) = a_ .to_odd() - .expect("b_ is always odd") - .partial_binxgcd_vartime::(&a_, K - 1, compact_contains_all_of_a); + .expect("a_ is always odd") + .partial_binxgcd_vartime::(&b_, K - 1, compact_contains_all_of_b); // Update `a` and `b` using the update matrix - let (updated_b, updated_a) = matrix.extended_apply_to((b, a)); + let (updated_a, updated_b) = matrix.extended_apply_to((a,b)); (a, _) = updated_a .div_2k(log_upper_bound) @@ -161,9 +160,7 @@ impl Odd> { .expect("extension is zero"); } - debug_assert!(Uint::eq(&a, &Uint::ZERO).to_bool_vartime()); - - b.to_odd() + a.to_odd() .expect("gcd of an odd value with something else is always odd") } } From 00b3597eea8399da37577d5d16b21525e02074b6 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 15:17:15 +0100 Subject: [PATCH 118/203] Fix typo --- src/uint/bingcd/xgcd.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index d7b601b89..3c1da20f3 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -193,14 +193,14 @@ impl Odd> { let n = const_max(2 * K, const_max(a.bits(), b_bits)); let a_ = a.compact::(n); let b_ = b.compact::(n); - let compact_contains_all_of_a = + let compact_contains_all_of_b = ConstChoice::from_u32_le(b_bits, K - 1).or(ConstChoice::from_u32_eq(n, 2 * K)); // Compute the K-1 iteration update matrix from a_ and b_ let (.., update_matrix, log_upper_bound) = a_ .to_odd() .expect("a is always odd") - .partial_binxgcd_vartime::(&b_, K - 1, compact_contains_all_of_a); + .partial_binxgcd_vartime::(&b_, K - 1, compact_contains_all_of_b); // Update `a` and `b` using the update matrix let (updated_a, updated_b) = update_matrix.extended_apply_to((a, b)); From 209b8c2ecc82dea7ae2967292f4727e42f741d63 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 15:17:37 +0100 Subject: [PATCH 119/203] Fix fmt --- src/uint/bingcd/gcd.rs | 2 +- src/uint/bingcd/xgcd.rs | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index aa5d13a0f..cf4e88137 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -148,7 +148,7 @@ impl Odd> { .partial_binxgcd_vartime::(&b_, K - 1, compact_contains_all_of_b); // Update `a` and `b` using the update matrix - let (updated_a, updated_b) = matrix.extended_apply_to((a,b)); + let (updated_a, updated_b) = matrix.extended_apply_to((a, b)); (a, _) = updated_a .div_2k(log_upper_bound) diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 3c1da20f3..23d3190d0 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -1,6 +1,6 @@ use crate::uint::bingcd::matrix::IntMatrix; use crate::uint::bingcd::tools::const_max; -use crate::{ConstChoice, Int, Odd, Uint, U128, U64, NonZero}; +use crate::{ConstChoice, Int, NonZero, Odd, Uint, U128, U64}; impl Int { /// Compute `self / 2^k mod q`. Executes in time variable in `k_bound`. This value should be @@ -56,7 +56,10 @@ impl Odd> { /// /// **Warning**: this algorithm is only guaranteed to work for `self` and `rhs` for which the /// msb is **not** set. May panic otherwise. - pub(crate) const fn binxgcd_nz(&self, rhs: &NonZero>) -> (Self, Int, Int) { + pub(crate) const fn binxgcd_nz( + &self, + rhs: &NonZero>, + ) -> (Self, Int, Int) { // Note that for the `binxgcd` subroutine, `rhs` needs to be odd. // // We use the fact that gcd(a, b) = gcd(a, |a-b|) to @@ -64,7 +67,8 @@ impl Odd> { // 2) execute the xgcd algorithm on (self, rhs'), and // 3) recover the Bezout coefficients for (self, rhs) from the (self, rhs') output. - let (abs_lhs_sub_rhs, rhs_gt_lhs) = self.as_ref().wrapping_sub(rhs.as_ref()).as_int().abs_sign(); + let (abs_lhs_sub_rhs, rhs_gt_lhs) = + self.as_ref().wrapping_sub(rhs.as_ref()).as_int().abs_sign(); let rhs_is_even = rhs.as_ref().is_odd().not(); let rhs_ = Uint::select(rhs.as_ref(), &abs_lhs_sub_rhs, rhs_is_even) .to_odd() From 27c3dd419894a1e7d0b1c9c61ed0962f84b776c0 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 15:22:29 +0100 Subject: [PATCH 120/203] Remove superfluous `DOUBLE` const generic. --- src/int/bingcd.rs | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index ab1484677..509445f6c 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -3,7 +3,7 @@ //! Ref: use crate::uint::bingcd::tools::const_min; -use crate::{ConcatMixed, ConstChoice, Int, NonZero, Odd, Uint}; +use crate::{ConstChoice, Int, NonZero, Odd, Uint}; impl Int { /// Compute the gcd of `self` and `rhs` leveraging the Binary GCD algorithm. @@ -14,10 +14,7 @@ impl Int { /// Executes the Binary Extended GCD algorithm. /// /// Given `(self, rhs)`, computes `(g, x, y)`, s.t. `self * x + rhs * y = g = gcd(self, rhs)`. - pub fn binxgcd(&self, rhs: &Self) -> (Uint, Self, Self) - where - Uint: ConcatMixed, MixedOutput = Uint>, - { + pub const fn binxgcd(&self, rhs: &Self) -> (Uint, Self, Self) { // Make sure `self` and `rhs` are nonzero. let self_is_nz = self.is_nonzero(); let self_nz = Int::select(&Int::ONE, self, self_is_nz) @@ -50,13 +47,7 @@ impl NonZero> { /// Execute the Binary Extended GCD algorithm. /// /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. - pub fn binxgcd( - &self, - rhs: &Self, - ) -> (NonZero>, Int, Int) - where - Uint: ConcatMixed, MixedOutput = Uint>, - { + pub const fn binxgcd(&self,rhs: &Self) -> (NonZero>, Int, Int) { let (mut lhs, mut rhs) = (*self.as_ref(), *rhs.as_ref()); // Leverage the property that gcd(2^k * a, 2^k *b) = 2^k * gcd(a, b) @@ -91,13 +82,7 @@ impl Odd> { /// Execute the Binary Extended GCD algorithm. /// /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. - pub fn binxgcd( - &self, - rhs: &NonZero>, - ) -> (Odd>, Int, Int) - where - Uint: ConcatMixed, MixedOutput = Uint>, - { + pub const fn binxgcd(&self, rhs: &NonZero>) -> (Odd>, Int, Int) { let (abs_lhs, sgn_lhs) = self.abs_sign(); let (abs_rhs, sgn_rhs) = rhs.abs_sign(); From 32657bbb6b6e90182a8afbabd9bcf13be2e3dec8 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 15:22:44 +0100 Subject: [PATCH 121/203] Fix ref. --- src/int/bingcd.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index 509445f6c..0c217d6dc 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -70,6 +70,7 @@ impl NonZero> { // Add the factor 2^k to the gcd. let gcd = gcd + .as_ref() .shl(k) .to_nz() .expect("gcd of non-zero element with another element is non-zero"); From b4df88e16107ad7c37e54259fcba5069459b682c Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 15:24:23 +0100 Subject: [PATCH 122/203] Fix fmt --- src/int/bingcd.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index 0c217d6dc..f741d89bb 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -47,7 +47,7 @@ impl NonZero> { /// Execute the Binary Extended GCD algorithm. /// /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. - pub const fn binxgcd(&self,rhs: &Self) -> (NonZero>, Int, Int) { + pub const fn binxgcd(&self, rhs: &Self) -> (NonZero>, Int, Int) { let (mut lhs, mut rhs) = (*self.as_ref(), *rhs.as_ref()); // Leverage the property that gcd(2^k * a, 2^k *b) = 2^k * gcd(a, b) @@ -83,7 +83,10 @@ impl Odd> { /// Execute the Binary Extended GCD algorithm. /// /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. - pub const fn binxgcd(&self, rhs: &NonZero>) -> (Odd>, Int, Int) { + pub const fn binxgcd( + &self, + rhs: &NonZero>, + ) -> (Odd>, Int, Int) { let (abs_lhs, sgn_lhs) = self.abs_sign(); let (abs_rhs, sgn_rhs) = rhs.abs_sign(); From 5b5c49e6510beb6d7b11ab852c1d8c5a93d8bd49 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 15:34:00 +0100 Subject: [PATCH 123/203] Improvements --- src/uint/bingcd/xgcd.rs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 23d3190d0..a24a3365c 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -42,11 +42,7 @@ impl Uint { // Floor-divide self by 2. When self was odd, add back 1/2 mod q. let add_one_half = self.is_odd(); let floored_half = self.shr_vartime(1); - Self::select( - &floored_half, - &floored_half.wrapping_add(half_mod_q), - add_one_half, - ) + floored_half.wrapping_add(&Self::select(&Self::ZERO, &half_mod_q, add_one_half)) } } @@ -81,11 +77,11 @@ impl Odd> { // ii. gcd = lhs * x + (lhs - rhs) * y, if rhs is even and rhs < lhs // iii. gcd = lhs * x + rhs * y, if rhs is odd - // We can rearrange these terms in one of three ways: + // We can rearrange these terms to get the Bezout coefficients to the original (self, rhs) + // input as follows: // i. gcd = lhs * (x - y) + rhs * y, if rhs is even and rhs > lhs // ii. gcd = lhs * (x + y) - y * rhs, if rhs is even and rhs < lhs // iii. gcd = lhs * x + rhs * y, if rhs is odd - // From this we can recover the Bezout coefficients from the original (self, rhs) input. x = Int::select(&x, &x.wrapping_sub(&y), rhs_is_even.and(rhs_gt_lhs)); x = Int::select(&x, &x.wrapping_add(&y), rhs_is_even.and(rhs_gt_lhs.not())); @@ -184,8 +180,8 @@ impl Odd> { rhs: &Self, ) -> (Self, Int, Int) { let (mut a, mut b) = (*self.as_ref(), *rhs.as_ref()); - let mut matrix = IntMatrix::UNIT; + let mut i = 0; let mut total_bound_shift = 0; let reduction_rounds = (2 * Self::BITS - 1).div_ceil(K); @@ -231,12 +227,10 @@ impl Odd> { let x = m00.div_2k_mod_q(total_bound_shift, total_iterations, &rhs); let y = m01.div_2k_mod_q(total_bound_shift, total_iterations, self); - ( - a.to_odd() - .expect("gcd of an odd value with something else is always odd"), - x, - y, - ) + let gcd = a + .to_odd() + .expect("gcd of an odd value with something else is always odd"); + (gcd, x, y) } /// Executes the optimized Binary GCD inner loop. From 82be975bdd537325b51a2306db9192e685ab0daa Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 15:37:54 +0100 Subject: [PATCH 124/203] Update `drop_extension` to `wrapping_drop_extension` --- src/uint/bingcd/extension.rs | 16 +++------------- src/uint/bingcd/gcd.rs | 10 ++-------- src/uint/bingcd/xgcd.rs | 22 ++++------------------ 3 files changed, 9 insertions(+), 39 deletions(-) diff --git a/src/uint/bingcd/extension.rs b/src/uint/bingcd/extension.rs index 02d3ddba2..3b73a5cc8 100644 --- a/src/uint/bingcd/extension.rs +++ b/src/uint/bingcd/extension.rs @@ -1,4 +1,4 @@ -use crate::{ConstChoice, ConstCtOption, Int, Limb, Uint}; +use crate::{ConstChoice, Int, Limb, Uint}; pub(crate) struct ExtendedUint( Uint, @@ -117,20 +117,10 @@ impl ExtendedInt { } /// Returns self without the extension. - /// - /// Is `None` if the extension cannot be dropped, i.e., when there is a bit in the extension - /// that does not equal the MSB in the base. #[inline] - pub const fn drop_extension(&self) -> ConstCtOption<(Uint, ConstChoice)> { - // should succeed when - // - extension is ZERO, or - // - extension is MAX, and the top bit in base is set. - let proper_positive = Int::eq(&self.1.as_int(), &Int::ZERO); - let proper_negative = - Int::eq(&self.1.as_int(), &Int::MINUS_ONE).and(self.0.as_int().is_negative()); + pub const fn wrapping_drop_extension(&self) -> (Uint, ConstChoice) { let (abs, sgn) = self.abs_sgn(); - // ConstCtOption::new((abs.0, sgn), proper_negative.or(proper_positive)) - ConstCtOption::some((abs.0, sgn)) + (abs.0, sgn) } /// Decompose `self` into is absolute value and signum. diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index cf4e88137..10b79dcb2 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -150,14 +150,8 @@ impl Odd> { // Update `a` and `b` using the update matrix let (updated_a, updated_b) = matrix.extended_apply_to((a, b)); - (a, _) = updated_a - .div_2k(log_upper_bound) - .drop_extension() - .expect("extension is zero"); - (b, _) = updated_b - .div_2k(log_upper_bound) - .drop_extension() - .expect("extension is zero"); + (a, _) = updated_a.div_2k(log_upper_bound).wrapping_drop_extension(); + (b, _) = updated_b.div_2k(log_upper_bound).wrapping_drop_extension(); } a.to_odd() diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index a24a3365c..c272e81ca 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -206,14 +206,8 @@ impl Odd> { let (updated_a, updated_b) = update_matrix.extended_apply_to((a, b)); let (a_sgn, b_sgn); - (a, a_sgn) = updated_a - .div_2k(log_upper_bound) - .drop_extension() - .expect("extension is zero"); - (b, b_sgn) = updated_b - .div_2k(log_upper_bound) - .drop_extension() - .expect("extension is zero"); + (a, a_sgn) = updated_a.div_2k(log_upper_bound).wrapping_drop_extension(); + (b, b_sgn) = updated_b.div_2k(log_upper_bound).wrapping_drop_extension(); matrix = update_matrix.checked_mul_right(&matrix); matrix.conditional_negate_top_row(a_sgn); @@ -373,16 +367,8 @@ mod tests { assert_eq!(iters, 5); let (computed_a, computed_b) = matrix.extended_apply_to((a.get(), b)); - let computed_a = computed_a - .div_2k(5) - .drop_extension() - .expect("no overflow") - .0; - let computed_b = computed_b - .div_2k(5) - .drop_extension() - .expect("no overflow") - .0; + let computed_a = computed_a.div_2k(5).wrapping_drop_extension().0; + let computed_b = computed_b.div_2k(5).wrapping_drop_extension().0; assert_eq!( new_a.get(), From 4c163991fccd4d35e05151c93d7bc387c38b8987 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 13 Feb 2025 16:02:10 +0100 Subject: [PATCH 125/203] Improve readability --- src/uint/bingcd/gcd.rs | 6 +++--- src/uint/bingcd/xgcd.rs | 44 ++++++++++++++++++++--------------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/uint/bingcd/gcd.rs b/src/uint/bingcd/gcd.rs index 10b79dcb2..b43a460a8 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/uint/bingcd/gcd.rs @@ -142,7 +142,7 @@ impl Odd> { // Compute the K-1 iteration update matrix from a_ and b_ // Safe to vartime; function executes in time variable in `iterations` only, which is // a public constant K-1 here. - let (.., matrix, log_upper_bound) = a_ + let (.., matrix, doublings) = a_ .to_odd() .expect("a_ is always odd") .partial_binxgcd_vartime::(&b_, K - 1, compact_contains_all_of_b); @@ -150,8 +150,8 @@ impl Odd> { // Update `a` and `b` using the update matrix let (updated_a, updated_b) = matrix.extended_apply_to((a, b)); - (a, _) = updated_a.div_2k(log_upper_bound).wrapping_drop_extension(); - (b, _) = updated_b.div_2k(log_upper_bound).wrapping_drop_extension(); + (a, _) = updated_a.div_2k(doublings).wrapping_drop_extension(); + (b, _) = updated_b.div_2k(doublings).wrapping_drop_extension(); } a.to_odd() diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index c272e81ca..912d6fd5f 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -181,11 +181,10 @@ impl Odd> { ) -> (Self, Int, Int) { let (mut a, mut b) = (*self.as_ref(), *rhs.as_ref()); let mut matrix = IntMatrix::UNIT; + let mut total_doublings = 0; let mut i = 0; - let mut total_bound_shift = 0; - let reduction_rounds = (2 * Self::BITS - 1).div_ceil(K); - while i < reduction_rounds { + while i < (2 * Self::BITS - 1).div_ceil(K) { i += 1; // Construct a_ and b_ as the summary of a and b, respectively. @@ -193,33 +192,32 @@ impl Odd> { let n = const_max(2 * K, const_max(a.bits(), b_bits)); let a_ = a.compact::(n); let b_ = b.compact::(n); - let compact_contains_all_of_b = - ConstChoice::from_u32_le(b_bits, K - 1).or(ConstChoice::from_u32_eq(n, 2 * K)); + let b_fits_in_compact = ConstChoice::from_u32_le(b_bits, K - 1).or(ConstChoice::from_u32_eq(n, 2 * K)); // Compute the K-1 iteration update matrix from a_ and b_ - let (.., update_matrix, log_upper_bound) = a_ + let (.., update_matrix, doublings) = a_ .to_odd() .expect("a is always odd") - .partial_binxgcd_vartime::(&b_, K - 1, compact_contains_all_of_b); + .partial_binxgcd_vartime::(&b_, K - 1, b_fits_in_compact); // Update `a` and `b` using the update matrix let (updated_a, updated_b) = update_matrix.extended_apply_to((a, b)); let (a_sgn, b_sgn); - (a, a_sgn) = updated_a.div_2k(log_upper_bound).wrapping_drop_extension(); - (b, b_sgn) = updated_b.div_2k(log_upper_bound).wrapping_drop_extension(); + (a, a_sgn) = updated_a.div_2k(doublings).wrapping_drop_extension(); + (b, b_sgn) = updated_b.div_2k(doublings).wrapping_drop_extension(); matrix = update_matrix.checked_mul_right(&matrix); matrix.conditional_negate_top_row(a_sgn); matrix.conditional_negate_bottom_row(b_sgn); - total_bound_shift += log_upper_bound; + total_doublings += doublings; } // Extract the Bezout coefficients. - let total_iterations = reduction_rounds * (K - 1); + let total_iterations = 2 * Self::BITS - 1; let IntMatrix { m00, m01, .. } = matrix; - let x = m00.div_2k_mod_q(total_bound_shift, total_iterations, &rhs); - let y = m01.div_2k_mod_q(total_bound_shift, total_iterations, self); + let x = m00.div_2k_mod_q(total_doublings, total_iterations, &rhs); + let y = m01.div_2k_mod_q(total_doublings, total_iterations, self); let gcd = a .to_odd() @@ -232,13 +230,15 @@ impl Odd> { /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 2. /// . /// - /// The function outputs a matrix that can be used to reduce the `a` and `b` in the main loop. + /// The function outputs the reduced values `(a, b)` for the input values `(self, rhs)` as well + /// as the matrix that yields the former two when multiplied with the latter two. + /// + /// Additionally, the number doublings that were executed is returned. By construction, each + /// element in `M` lies in the interval `(-2^doublings, 2^doublings]`. /// - /// This implementation deviates slightly from the paper, in that it "runs in place", i.e., - /// executes iterations that do nothing, once `a` becomes zero. As a result, the main loop - /// can no longer assume that all `iterations` are executed. As such, the `executed_iterations` - /// are counted and additionally returned. Note that each element in `M` lies in the interval - /// `(-2^executed_iterations, 2^executed_iterations]`. + /// Note: this implementation deviates slightly from the paper, in that it can be instructed to + /// "run in place" (i.e., execute iterations that do nothing) once `a` becomes zero. + /// This is done by passing a truthy `halt_at_zero`. /// /// The function executes in time variable in `iterations`. #[inline] @@ -259,14 +259,14 @@ impl Odd> { Uint::swap(&mut a, &mut b); matrix.swap_rows(); - let mut executed_iterations = 0; + let mut doublings = 0; let mut j = 0; while j < iterations { Self::binxgcd_step::( &mut a, &mut b, &mut matrix, - &mut executed_iterations, + &mut doublings, halt_at_zero, ); j += 1; @@ -277,7 +277,7 @@ impl Odd> { matrix.swap_rows(); let a = a.to_odd().expect("a is always odd"); - (a, b, matrix, executed_iterations) + (a, b, matrix, doublings) } /// Binary XGCD update step. From 5bb8677cf348b818f57acb20b809c7ba29f04cac Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 14 Feb 2025 10:23:10 +0100 Subject: [PATCH 126/203] Replace `IntMatrix::checked_mul_right` with `wrapping_mul_right` --- src/int/mul.rs | 7 +++++++ src/uint/bingcd/matrix.rs | 33 ++++++++++++++++----------------- src/uint/bingcd/xgcd.rs | 2 +- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/int/mul.rs b/src/int/mul.rs index 5143b7340..03877a464 100644 --- a/src/int/mul.rs +++ b/src/int/mul.rs @@ -59,6 +59,13 @@ impl Int { let (lo, hi, is_negative) = self.split_mul(rhs); Self::new_from_abs_sign(lo, is_negative).and_choice(hi.is_nonzero().not()) } + + /// Multiply `self` with `rhs`, returning a [ConstCtOption] that `is_some` only if the result + /// fits in an `Int`. + pub const fn wrapping_mul(&self, rhs: &Int) -> Int { + let (lo, _, is_negative) = self.split_mul(rhs); + Self(lo.wrapping_neg_if(is_negative)) + } } /// Squaring operations. diff --git a/src/uint/bingcd/matrix.rs b/src/uint/bingcd/matrix.rs index 76d87defa..17ddd273f 100644 --- a/src/uint/bingcd/matrix.rs +++ b/src/uint/bingcd/matrix.rs @@ -43,25 +43,24 @@ impl IntMatrix { (left, right) } - /// Apply this matrix to `rhs`. Panics if a multiplication overflows. - /// TODO: consider implementing (a variation to) Strassen. Doing so will save a multiplication. + /// Wrapping apply this matrix to `rhs`. Return the result in `RHS_LIMBS`. #[inline] - pub(crate) const fn checked_mul_right( + pub(crate) const fn wrapping_mul_right( &self, rhs: &IntMatrix, ) -> IntMatrix { - let a0 = rhs.m00.const_checked_mul(&self.m00).expect("no overflow"); - let a1 = rhs.m10.const_checked_mul(&self.m01).expect("no overflow"); - let a = a0.checked_add(&a1).expect("no overflow"); - let b0 = rhs.m01.const_checked_mul(&self.m00).expect("no overflow"); - let b1 = rhs.m11.const_checked_mul(&self.m01).expect("no overflow"); - let b = b0.checked_add(&b1).expect("no overflow"); - let c0 = rhs.m00.const_checked_mul(&self.m10).expect("no overflow"); - let c1 = rhs.m10.const_checked_mul(&self.m11).expect("no overflow"); - let c = c0.checked_add(&c1).expect("no overflow"); - let d0 = rhs.m01.const_checked_mul(&self.m10).expect("no overflow"); - let d1 = rhs.m11.const_checked_mul(&self.m11).expect("no overflow"); - let d = d0.checked_add(&d1).expect("no overflow"); + let a0 = rhs.m00.wrapping_mul(&self.m00); + let a1 = rhs.m10.wrapping_mul(&self.m01); + let a = a0.wrapping_add(&a1); + let b0 = rhs.m01.wrapping_mul(&self.m00); + let b1 = rhs.m11.wrapping_mul(&self.m01); + let b = b0.wrapping_add(&b1); + let c0 = rhs.m00.wrapping_mul(&self.m10); + let c1 = rhs.m10.wrapping_mul(&self.m11); + let c = c0.wrapping_add(&c1); + let d0 = rhs.m01.wrapping_mul(&self.m10); + let d1 = rhs.m11.wrapping_mul(&self.m11); + let d = d0.wrapping_add(&d1); IntMatrix::new(a, b, c, d) } @@ -173,8 +172,8 @@ mod tests { } #[test] - fn test_checked_mul() { - let res = X.checked_mul_right(&X); + fn test_wrapping_mul() { + let res = X.wrapping_mul_right(&X); assert_eq!( res, IntMatrix::new( diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 912d6fd5f..e3e273b53 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -207,7 +207,7 @@ impl Odd> { (a, a_sgn) = updated_a.div_2k(doublings).wrapping_drop_extension(); (b, b_sgn) = updated_b.div_2k(doublings).wrapping_drop_extension(); - matrix = update_matrix.checked_mul_right(&matrix); + matrix = update_matrix.wrapping_mul_right(&matrix); matrix.conditional_negate_top_row(a_sgn); matrix.conditional_negate_bottom_row(b_sgn); total_doublings += doublings; From 4d16027d7b951279fd198a556842bf434627d83c Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 14 Feb 2025 10:35:08 +0100 Subject: [PATCH 127/203] Move `div_2k_mod_q` ops --- src/uint/bingcd/tools.rs | 44 +++++++++++++++++++++++++++++++++++++++- src/uint/bingcd/xgcd.rs | 44 ---------------------------------------- 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/src/uint/bingcd/tools.rs b/src/uint/bingcd/tools.rs index df06a0497..fdd0e1764 100644 --- a/src/uint/bingcd/tools.rs +++ b/src/uint/bingcd/tools.rs @@ -1,4 +1,4 @@ -use crate::{ConstChoice, Uint}; +use crate::{ConstChoice, Int, Odd, Uint}; /// `const` equivalent of `u32::max(a, b)`. pub(crate) const fn const_max(a: u32, b: u32) -> u32 { @@ -10,7 +10,49 @@ pub(crate) const fn const_min(a: u32, b: u32) -> u32 { ConstChoice::from_u32_lt(a, b).select_u32(b, a) } +impl Int { + /// Compute `self / 2^k mod q`. Executes in time variable in `k_bound`. This value should be + /// chosen as an inclusive upperbound to the value of `k`. + #[inline] + pub(crate) const fn div_2k_mod_q(&self, k: u32, k_bound: u32, q: &Odd>) -> Self { + let (abs, sgn) = self.abs_sign(); + let abs_div_2k_mod_q = abs.div_2k_mod_q(k, k_bound, q); + Int::new_from_abs_sign(abs_div_2k_mod_q, sgn).expect("no overflow") + } +} + impl Uint { + /// Compute `self / 2^k mod q`. + /// + /// Executes in time variable in `k_bound`. This value should be + /// chosen as an inclusive upperbound to the value of `k`. + #[inline] + const fn div_2k_mod_q(mut self, k: u32, k_bound: u32, q: &Odd) -> Self { + // 1 / 2 mod q + // = (q + 1) / 2 mod q + // = (q - 1) / 2 + 1 mod q + // = floor(q / 2) + 1 mod q, since q is odd. + let one_half_mod_q = q.as_ref().shr_vartime(1).wrapping_add(&Uint::ONE); + let mut i = 0; + while i < k_bound { + // Apply only while i < k + let apply = ConstChoice::from_u32_lt(i, k); + self = Self::select(&self, &self.div_2_mod_q(&one_half_mod_q), apply); + i += 1; + } + + self + } + + /// Compute `self / 2 mod q`. + #[inline] + const fn div_2_mod_q(self, half_mod_q: &Self) -> Self { + // Floor-divide self by 2. When self was odd, add back 1/2 mod q. + let add_one_half = self.is_odd(); + let floored_half = self.shr_vartime(1); + floored_half.wrapping_add(&Self::select(&Self::ZERO, &half_mod_q, add_one_half)) + } + /// Construct a [Uint] containing the bits in `self` in the range `[idx, idx + length)`. /// /// Assumes `length ≤ Uint::::BITS` and `idx + length ≤ Self::BITS`. diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index e3e273b53..3343d3d6a 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -2,50 +2,6 @@ use crate::uint::bingcd::matrix::IntMatrix; use crate::uint::bingcd::tools::const_max; use crate::{ConstChoice, Int, NonZero, Odd, Uint, U128, U64}; -impl Int { - /// Compute `self / 2^k mod q`. Executes in time variable in `k_bound`. This value should be - /// chosen as an inclusive upperbound to the value of `k`. - #[inline] - pub(crate) const fn div_2k_mod_q(&self, k: u32, k_bound: u32, q: &Odd>) -> Self { - let (abs, sgn) = self.abs_sign(); - let abs_div_2k_mod_q = abs.div_2k_mod_q(k, k_bound, q); - Int::new_from_abs_sign(abs_div_2k_mod_q, sgn).expect("no overflow") - } -} - -impl Uint { - /// Compute `self / 2^k mod q`. - /// - /// Executes in time variable in `k_bound`. This value should be - /// chosen as an inclusive upperbound to the value of `k`. - #[inline] - const fn div_2k_mod_q(mut self, k: u32, k_bound: u32, q: &Odd) -> Self { - // 1 / 2 mod q - // = (q + 1) / 2 mod q - // = (q - 1) / 2 + 1 mod q - // = floor(q / 2) + 1 mod q, since q is odd. - let one_half_mod_q = q.as_ref().shr_vartime(1).wrapping_add(&Uint::ONE); - let mut i = 0; - while i < k_bound { - // Apply only while i < k - let apply = ConstChoice::from_u32_lt(i, k); - self = Self::select(&self, &self.div_2_mod_q(&one_half_mod_q), apply); - i += 1; - } - - self - } - - /// Compute `self / 2 mod q`. - #[inline] - const fn div_2_mod_q(self, half_mod_q: &Self) -> Self { - // Floor-divide self by 2. When self was odd, add back 1/2 mod q. - let add_one_half = self.is_odd(); - let floored_half = self.shr_vartime(1); - floored_half.wrapping_add(&Self::select(&Self::ZERO, &half_mod_q, add_one_half)) - } -} - impl Odd> { /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, /// leveraging the Binary Extended GCD algorithm. From 177aabc6f2d9dbd782450d857cdd0ea80552348d Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 14 Feb 2025 11:18:34 +0100 Subject: [PATCH 128/203] Remove asserts from `IntMatrix::extended_apply_to` --- src/uint/bingcd/extension.rs | 26 -------------------------- src/uint/bingcd/matrix.rs | 6 +----- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/src/uint/bingcd/extension.rs b/src/uint/bingcd/extension.rs index 3b73a5cc8..2cb912834 100644 --- a/src/uint/bingcd/extension.rs +++ b/src/uint/bingcd/extension.rs @@ -96,26 +96,6 @@ impl ExtendedInt { Self(lo, hi) } - /// Perform addition, raising the `overflow` flag on overflow. - pub const fn overflowing_add(&self, rhs: &Self) -> (Self, ConstChoice) { - // Step 1. add operands - let res = self.wrapping_add(rhs); - - // Step 2. determine whether overflow happened. - // Note: - // - overflow can only happen when the inputs have the same sign, and then - // - overflow occurs if and only if the result has the opposite sign of both inputs. - // - // We can thus express the overflow flag as: (self.msb == rhs.msb) & (self.msb != res.msb) - let self_msb = self.is_negative(); - let overflow = self_msb - .eq(rhs.is_negative()) - .and(self_msb.ne(res.is_negative())); - - // Step 3. Construct result - (res, overflow) - } - /// Returns self without the extension. #[inline] pub const fn wrapping_drop_extension(&self) -> (Uint, ConstChoice) { @@ -133,12 +113,6 @@ impl ExtendedInt { ) } - /// Decompose `self` into is absolute value and signum. - #[inline] - pub const fn is_negative(&self) -> ConstChoice { - self.abs_sgn().1 - } - /// Divide self by `2^k`, rounding towards zero. #[inline] pub const fn div_2k(&self, k: u32) -> Self { diff --git a/src/uint/bingcd/matrix.rs b/src/uint/bingcd/matrix.rs index 17ddd273f..b56425a72 100644 --- a/src/uint/bingcd/matrix.rs +++ b/src/uint/bingcd/matrix.rs @@ -36,11 +36,7 @@ impl IntMatrix { let a1 = ExtendedInt::from_product(a, self.m10); let b0 = ExtendedInt::from_product(b, self.m01); let b1 = ExtendedInt::from_product(b, self.m11); - let (left, left_overflow) = a0.overflowing_add(&b0); - let (right, right_overflow) = a1.overflowing_add(&b1); - assert!(!left_overflow.to_bool_vartime()); - assert!(!right_overflow.to_bool_vartime()); - (left, right) + (a0.wrapping_add(&b0), a1.wrapping_add(&b1)) } /// Wrapping apply this matrix to `rhs`. Return the result in `RHS_LIMBS`. From 28ad3993bee5b8b533ec60960d7361a622336854 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 14 Feb 2025 11:20:27 +0100 Subject: [PATCH 129/203] Fix xgcd benchmarks --- benches/uint.rs | 44 ++++++++++---------------------------------- 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/benches/uint.rs b/benches/uint.rs index 3fe5d0ee5..4a6de5ea3 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -4,8 +4,8 @@ use criterion::{ }; use crypto_bigint::modular::SafeGcdInverter; use crypto_bigint::{ - Limb, NonZero, Odd, PrecomputeInverter, Random, RandomBits, RandomMod, Reciprocal, Uint, U1024, - U128, U16384, U192, U2048, U256, U320, U384, U4096, U448, U512, U64, U8192, + Int, Limb, NonZero, Odd, PrecomputeInverter, Random, RandomBits, RandomMod, Reciprocal, Uint, + U1024, U128, U16384, U192, U2048, U256, U320, U384, U4096, U448, U512, U64, U8192, }; use rand_chacha::ChaCha8Rng; use rand_core::{OsRng, RngCore, SeedableRng}; @@ -391,39 +391,15 @@ fn xgcd_bench( ) where Odd>: PrecomputeInverter>, { - g.bench_function(BenchmarkId::new("classic binxgcd", LIMBS), |b| { - b.iter_batched( - || { - let modulus = Uint::MAX.shr_vartime(1).to_nz().unwrap(); - let f = Uint::::random_mod(&mut OsRng, &modulus) - .bitor(&Uint::ONE) - .to_odd() - .unwrap(); - let g = Uint::::random_mod(&mut OsRng, &modulus) - .bitor(&Uint::ONE) - .to_odd() - .unwrap(); - (f, g) - }, - |(f, g)| black_box(f.classic_binxgcd(&g)), - BatchSize::SmallInput, - ) - }); g.bench_function(BenchmarkId::new("binxgcd", LIMBS), |b| { b.iter_batched( || { - let modulus = Uint::MAX.shr_vartime(1).to_nz().unwrap(); - let f = Uint::::random_mod(&mut OsRng, &modulus) - .bitor(&Uint::ONE) - .to_odd() - .unwrap(); - let g = Uint::::random_mod(&mut OsRng, &modulus) - .bitor(&Uint::ONE) - .to_odd() - .unwrap(); + let modulus = Int::MIN.as_uint().wrapping_add(&Uint::ONE).to_nz().unwrap(); + let f = Uint::::random_mod(&mut OsRng, &modulus).as_int(); + let g = Uint::::random_mod(&mut OsRng, &modulus).as_int(); (f, g) }, - |(f, g)| black_box(f.limited_binxgcd(&g)), + |(f, g)| black_box(f.binxgcd(&g)), BatchSize::SmallInput, ) }); @@ -604,10 +580,10 @@ fn bench_sqrt(c: &mut Criterion) { criterion_group!( benches, - // bench_random, - // bench_mul, - // bench_division, - // bench_gcd, + bench_random, + bench_mul, + bench_division, + bench_gcd, bench_xgcd, bench_shl, bench_shr, From 63f9727f4d935a6addb93f634749b553e93e8ec8 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 14 Feb 2025 15:45:04 +0100 Subject: [PATCH 130/203] Make `iterations` a constant --- src/uint/bingcd/xgcd.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/uint/bingcd/xgcd.rs b/src/uint/bingcd/xgcd.rs index 3343d3d6a..15f68ffe0 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/uint/bingcd/xgcd.rs @@ -2,7 +2,13 @@ use crate::uint::bingcd::matrix::IntMatrix; use crate::uint::bingcd::tools::const_max; use crate::{ConstChoice, Int, NonZero, Odd, Uint, U128, U64}; + + impl Odd> { + + /// The minimal number of binary GCD iterations required to guarantee successful completion. + const MIN_BINGCD_ITERATIONS: u32 = 2 * Uint::::BITS - 1; + /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, /// leveraging the Binary Extended GCD algorithm. /// @@ -74,17 +80,16 @@ impl Odd> { /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 1. /// . pub(crate) const fn classic_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { - let total_iterations = 2 * Self::BITS - 1; let (gcd, _, matrix, total_bound_shift) = self.partial_binxgcd_vartime::( rhs.as_ref(), - total_iterations, + Self::MIN_BINGCD_ITERATIONS, ConstChoice::TRUE, ); // Extract the Bezout coefficients. let IntMatrix { m00, m01, .. } = matrix; - let x = m00.div_2k_mod_q(total_bound_shift, total_iterations, &rhs); - let y = m01.div_2k_mod_q(total_bound_shift, total_iterations, self); + let x = m00.div_2k_mod_q(total_bound_shift, Self::MIN_BINGCD_ITERATIONS, &rhs); + let y = m01.div_2k_mod_q(total_bound_shift, Self::MIN_BINGCD_ITERATIONS, self); (gcd, x, y) } @@ -140,7 +145,7 @@ impl Odd> { let mut total_doublings = 0; let mut i = 0; - while i < (2 * Self::BITS - 1).div_ceil(K) { + while i < Self::MIN_BINGCD_ITERATIONS.div_ceil(K) { i += 1; // Construct a_ and b_ as the summary of a and b, respectively. @@ -170,10 +175,9 @@ impl Odd> { } // Extract the Bezout coefficients. - let total_iterations = 2 * Self::BITS - 1; let IntMatrix { m00, m01, .. } = matrix; - let x = m00.div_2k_mod_q(total_doublings, total_iterations, &rhs); - let y = m01.div_2k_mod_q(total_doublings, total_iterations, self); + let x = m00.div_2k_mod_q(total_doublings, Self::MIN_BINGCD_ITERATIONS, &rhs); + let y = m01.div_2k_mod_q(total_doublings, Self::MIN_BINGCD_ITERATIONS, self); let gcd = a .to_odd() From eb5b1d1ab5c7bd15ca17d7e230595ec6169ef764 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 14 Feb 2025 16:31:18 +0100 Subject: [PATCH 131/203] Refactor --- src/int/bingcd.rs | 2 +- src/modular.rs | 1 + src/modular/bingcd.rs | 9 ++++ src/{uint => modular}/bingcd/extension.rs | 0 src/{uint => modular}/bingcd/gcd.rs | 63 +++++------------------ src/{uint => modular}/bingcd/matrix.rs | 4 +- src/{uint => modular}/bingcd/tools.rs | 6 +-- src/{uint => modular}/bingcd/xgcd.rs | 20 ++++--- src/uint.rs | 2 +- src/uint/bingcd.rs | 49 +++++++++++++++--- 10 files changed, 81 insertions(+), 75 deletions(-) create mode 100644 src/modular/bingcd.rs rename src/{uint => modular}/bingcd/extension.rs (100%) rename src/{uint => modular}/bingcd/gcd.rs (82%) rename src/{uint => modular}/bingcd/matrix.rs (98%) rename src/{uint => modular}/bingcd/tools.rs (95%) rename src/{uint => modular}/bingcd/xgcd.rs (97%) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index f741d89bb..218a33a10 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -2,7 +2,7 @@ //! which is described by Pornin in "Optimized Binary GCD for Modular Inversion". //! Ref: -use crate::uint::bingcd::tools::const_min; +use crate::modular::bingcd::tools::const_min; use crate::{ConstChoice, Int, NonZero, Odd, Uint}; impl Int { diff --git a/src/modular.rs b/src/modular.rs index 1159d6a42..ca1d46a30 100644 --- a/src/modular.rs +++ b/src/modular.rs @@ -22,6 +22,7 @@ mod monty_form; mod reduction; mod add; +pub(crate) mod bingcd; mod div_by_2; mod mul; mod pow; diff --git a/src/modular/bingcd.rs b/src/modular/bingcd.rs new file mode 100644 index 000000000..e6a939e83 --- /dev/null +++ b/src/modular/bingcd.rs @@ -0,0 +1,9 @@ +//! This module implements (a constant variant of) the Optimized Extended Binary GCD algorithm, +//! which is described by Pornin as Algorithm 2 in "Optimized Binary GCD for Modular Inversion". +//! Ref: + +mod extension; +mod gcd; +mod matrix; +pub(crate) mod tools; +mod xgcd; diff --git a/src/uint/bingcd/extension.rs b/src/modular/bingcd/extension.rs similarity index 100% rename from src/uint/bingcd/extension.rs rename to src/modular/bingcd/extension.rs diff --git a/src/uint/bingcd/gcd.rs b/src/modular/bingcd/gcd.rs similarity index 82% rename from src/uint/bingcd/gcd.rs rename to src/modular/bingcd/gcd.rs index b43a460a8..496e00209 100644 --- a/src/uint/bingcd/gcd.rs +++ b/src/modular/bingcd/gcd.rs @@ -1,51 +1,10 @@ -use crate::uint::bingcd::tools::{const_max, const_min}; -use crate::{ConstChoice, NonZero, Odd, Uint, U128, U64}; - -impl NonZero> { - /// Compute the greatest common divisor of `self` and `rhs`. - pub const fn bingcd(&self, rhs: &Uint) -> Self { - let val = self.as_ref(); - // Leverage two GCD identity rules to make self odd. - // 1) gcd(2a, 2b) = 2 * gcd(a, b) - // 2) gcd(a, 2b) = gcd(a, b) if a is odd. - let i = val.trailing_zeros(); - let j = rhs.trailing_zeros(); - let k = const_min(i, j); - - val.shr(i) - .to_odd() - .expect("val.shr(i) is odd by construction") - .bingcd(rhs) - .as_ref() - .shl(k) - .to_nz() - .expect("gcd of non-zero element with another element is non-zero") - } -} +use crate::modular::bingcd::tools::const_max; +use crate::{ConstChoice, Odd, Uint, U128, U64}; impl Odd> { /// Total size of the represented integer in bits. pub const BITS: u32 = Uint::::BITS; - /// Compute the greatest common divisor of `self` and `rhs` using the Binary GCD algorithm. - /// - /// This function switches between the "classic" and "optimized" algorithm at a best-effort - /// threshold. When using [Uint]s with `LIMBS` close to the threshold, it may be useful to - /// manually test whether the classic or optimized algorithm is faster for your machine. - #[inline(always)] - pub const fn bingcd(&self, rhs: &Uint) -> Self { - // Todo: tweak this threshold - // Note: we're swapping the parameters here for greater readability: Pornin's Algorithm 1 - // and Algorithm 2 both require the second argument (m) to be odd. Given that the gcd - // is the same, regardless of the order of the parameters, this swap does not affect the - // result. - if LIMBS < 8 { - self.classic_bingcd(rhs) - } else { - self.optimized_bingcd(rhs) - } - } - /// Computes `gcd(self, rhs)`, leveraging the (a constant time implementation of) the classic /// Binary GCD algorithm. /// @@ -63,8 +22,11 @@ impl Odd> { j += 1; } - b.to_odd() - .expect("gcd of an odd value with something else is always odd") + let gcd = b + .to_odd() + .expect("gcd of an odd value with something else is always odd"); + + gcd } /// Binary GCD update step. @@ -99,7 +61,7 @@ impl Odd> { /// /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 2. /// - #[inline(always)] + #[inline] pub const fn optimized_bingcd(&self, rhs: &Uint) -> Self { self.optimized_bingcd_::<{ U64::BITS }, { U64::LIMBS }, { U128::LIMBS }>(rhs) } @@ -119,7 +81,7 @@ impl Odd> { /// `K` close to a (multiple of) the number of bits that fit in a single register. /// - `LIMBS_K`: should be chosen as the minimum number s.t. `Uint::::BITS ≥ K`, /// - `LIMBS_2K`: should be chosen as the minimum number s.t. `Uint::::BITS ≥ 2K`. - #[inline(always)] + #[inline] pub const fn optimized_bingcd_( &self, rhs: &Uint, @@ -154,8 +116,11 @@ impl Odd> { (b, _) = updated_b.div_2k(doublings).wrapping_drop_extension(); } - a.to_odd() - .expect("gcd of an odd value with something else is always odd") + let gcd = a + .to_odd() + .expect("gcd of an odd value with something else is always odd"); + + gcd } } diff --git a/src/uint/bingcd/matrix.rs b/src/modular/bingcd/matrix.rs similarity index 98% rename from src/uint/bingcd/matrix.rs rename to src/modular/bingcd/matrix.rs index b56425a72..ff2c6de62 100644 --- a/src/uint/bingcd/matrix.rs +++ b/src/modular/bingcd/matrix.rs @@ -1,4 +1,4 @@ -use crate::uint::bingcd::extension::ExtendedInt; +use crate::modular::bingcd::extension::ExtendedInt; use crate::{ConstChoice, Int, Uint}; type Vector = (T, T); @@ -106,7 +106,7 @@ impl IntMatrix { #[cfg(test)] mod tests { - use crate::uint::bingcd::matrix::IntMatrix; + use crate::modular::bingcd::matrix::IntMatrix; use crate::{ConstChoice, Int, I256, U256}; const X: IntMatrix<{ U256::LIMBS }> = IntMatrix::new( diff --git a/src/uint/bingcd/tools.rs b/src/modular/bingcd/tools.rs similarity index 95% rename from src/uint/bingcd/tools.rs rename to src/modular/bingcd/tools.rs index fdd0e1764..333da1ffb 100644 --- a/src/uint/bingcd/tools.rs +++ b/src/modular/bingcd/tools.rs @@ -59,7 +59,7 @@ impl Uint { /// /// Executes in time variable in `length` only. #[inline(always)] - pub(super) const fn section_vartime_length( + pub(crate) const fn section_vartime_length( &self, idx: u32, length: u32, @@ -77,7 +77,7 @@ impl Uint { /// /// Executes in time variable in `idx` and `length`. #[inline(always)] - pub(super) const fn section_vartime( + pub(crate) const fn section_vartime( &self, idx: u32, length: u32, @@ -96,7 +96,7 @@ impl Uint { /// /// Assumes `K ≤ Uint::::BITS`, `n ≤ Self::BITS` and `n ≥ 2K`. #[inline(always)] - pub(super) const fn compact( + pub(crate) const fn compact( &self, n: u32, ) -> Uint { diff --git a/src/uint/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs similarity index 97% rename from src/uint/bingcd/xgcd.rs rename to src/modular/bingcd/xgcd.rs index 15f68ffe0..7588d758e 100644 --- a/src/uint/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -1,11 +1,8 @@ -use crate::uint::bingcd::matrix::IntMatrix; -use crate::uint::bingcd::tools::const_max; +use crate::modular::bingcd::matrix::IntMatrix; +use crate::modular::bingcd::tools::const_max; use crate::{ConstChoice, Int, NonZero, Odd, Uint, U128, U64}; - - impl Odd> { - /// The minimal number of binary GCD iterations required to guarantee successful completion. const MIN_BINGCD_ITERATIONS: u32 = 2 * Uint::::BITS - 1; @@ -132,7 +129,7 @@ impl Odd> { /// `K` close to a (multiple of) the number of bits that fit in a single register. /// - `LIMBS_K`: should be chosen as the minimum number s.t. `Uint::::BITS ≥ K`, /// - `LIMBS_2K`: should be chosen as the minimum number s.t. `Uint::::BITS ≥ 2K`. - pub(super) const fn optimized_binxgcd_< + pub(crate) const fn optimized_binxgcd_< const K: u32, const LIMBS_K: usize, const LIMBS_2K: usize, @@ -153,7 +150,8 @@ impl Odd> { let n = const_max(2 * K, const_max(a.bits(), b_bits)); let a_ = a.compact::(n); let b_ = b.compact::(n); - let b_fits_in_compact = ConstChoice::from_u32_le(b_bits, K - 1).or(ConstChoice::from_u32_eq(n, 2 * K)); + let b_fits_in_compact = + ConstChoice::from_u32_le(b_bits, K - 1).or(ConstChoice::from_u32_eq(n, 2 * K)); // Compute the K-1 iteration update matrix from a_ and b_ let (.., update_matrix, doublings) = a_ @@ -202,7 +200,7 @@ impl Odd> { /// /// The function executes in time variable in `iterations`. #[inline] - pub(super) const fn partial_binxgcd_vartime( + pub(crate) const fn partial_binxgcd_vartime( &self, rhs: &Uint, iterations: u32, @@ -302,7 +300,7 @@ mod tests { use crate::{ConcatMixed, Gcd, Int, Uint}; mod test_partial_binxgcd { - use crate::uint::bingcd::matrix::IntMatrix; + use crate::modular::bingcd::matrix::IntMatrix; use crate::{ConstChoice, I64, U64}; #[test] @@ -386,7 +384,7 @@ mod tests { } mod test_classic_binxgcd { - use crate::uint::bingcd::xgcd::tests::test_xgcd; + use crate::modular::bingcd::xgcd::tests::test_xgcd; use crate::{ ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64, U768, U8192, @@ -443,7 +441,7 @@ mod tests { } mod test_binxgcd { - use crate::uint::bingcd::xgcd::tests::test_xgcd; + use crate::modular::bingcd::xgcd::tests::test_xgcd; use crate::{ ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U768, U8192, diff --git a/src/uint.rs b/src/uint.rs index b70d4d3c4..a0aa14477 100644 --- a/src/uint.rs +++ b/src/uint.rs @@ -24,7 +24,6 @@ mod macros; mod add; mod add_mod; -pub(crate) mod bingcd; mod bit_and; mod bit_not; mod bit_or; @@ -462,6 +461,7 @@ impl_uint_concat_split_mixed! { (U1024, [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15]), } +mod bingcd; #[cfg(feature = "extra-sizes")] mod extra_sizes; diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index f31a35867..83522b37b 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -2,14 +2,8 @@ //! which is described by Pornin as Algorithm 2 in "Optimized Binary GCD for Modular Inversion". //! Ref: -use crate::Uint; - -mod extension; -mod gcd; -mod matrix; -pub(crate) mod tools; - -mod xgcd; +use crate::modular::bingcd::tools::const_min; +use crate::{NonZero, Odd, Uint}; impl Uint { /// Compute the greatest common divisor of `self` and `rhs`. @@ -22,6 +16,45 @@ impl Uint { } } +impl NonZero> { + /// Compute the greatest common divisor of `self` and `rhs`. + pub const fn bingcd(&self, rhs: &Uint) -> Self { + let val = self.as_ref(); + // Leverage two GCD identity rules to make self odd. + // 1) gcd(2a, 2b) = 2 * gcd(a, b) + // 2) gcd(a, 2b) = gcd(a, b) if a is odd. + let i = val.trailing_zeros(); + let j = rhs.trailing_zeros(); + let k = const_min(i, j); + + val.shr(i) + .to_odd() + .expect("val.shr(i) is odd by construction") + .bingcd(rhs) + .as_ref() + .shl(k) + .to_nz() + .expect("gcd of non-zero element with another element is non-zero") + } +} + +impl Odd> { + /// Compute the greatest common divisor of `self` and `rhs` using the Binary GCD algorithm. + /// + /// This function switches between the "classic" and "optimized" algorithm at a best-effort + /// threshold. When using [Uint]s with `LIMBS` close to the threshold, it may be useful to + /// manually test whether the classic or optimized algorithm is faster for your machine. + #[inline(always)] + pub const fn bingcd(&self, rhs: &Uint) -> Self { + // Todo: tweak this threshold + if LIMBS < 8 { + self.classic_bingcd(rhs) + } else { + self.optimized_bingcd(rhs) + } + } +} + #[cfg(feature = "rand_core")] #[cfg(test)] mod tests { From fd3f61c5640674034798f796d72d739c5a1c37d4 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 14 Feb 2025 16:40:20 +0100 Subject: [PATCH 132/203] Minor `optimized_bingcd_` optimization --- src/modular/bingcd/extension.rs | 29 +++++++++++++++++++++++++++++ src/modular/bingcd/gcd.rs | 13 +++++-------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/modular/bingcd/extension.rs b/src/modular/bingcd/extension.rs index 2cb912834..5b651f7d6 100644 --- a/src/modular/bingcd/extension.rs +++ b/src/modular/bingcd/extension.rs @@ -57,6 +57,28 @@ impl ExtendedUint { Self(lo, hi) } + + /// Vartime equivalent of [Self::shr]. + #[inline] + pub const fn shr_vartime(&self, shift: u32) -> Self { + debug_assert!(shift <= Uint::::BITS); + + let shift_is_zero = ConstChoice::from_u32_eq(shift, 0); + let left_shift = shift_is_zero.select_u32(Uint::::BITS - shift, 0); + + let hi = self.1.shr_vartime(shift); + let carry = Uint::select(&self.1, &Uint::ZERO, shift_is_zero).wrapping_shl(left_shift); + let mut lo = self.0.shr_vartime(shift); + + // Apply carry + let limb_diff = LIMBS.wrapping_sub(EXTRA) as u32; + // safe to vartime; shr_vartime is variable in the value of shift only. Since this shift + // is a public constant, the constant time property of this algorithm is not impacted. + let carry = carry.resize::().shl_vartime(limb_diff * Limb::BITS); + lo = lo.bitxor(&carry); + + Self(lo, hi) + } } pub(crate) struct ExtendedInt( @@ -119,4 +141,11 @@ impl ExtendedInt { let (abs, sgn) = self.abs_sgn(); abs.shr(k).wrapping_neg_if(sgn).as_extended_int() } + + /// Divide self by `2^k`, rounding towards zero. + #[inline] + pub const fn div_2k_vartime(&self, k: u32) -> Self { + let (abs, sgn) = self.abs_sgn(); + abs.shr_vartime(k).wrapping_neg_if(sgn).as_extended_int() + } } diff --git a/src/modular/bingcd/gcd.rs b/src/modular/bingcd/gcd.rs index 496e00209..7c25c0320 100644 --- a/src/modular/bingcd/gcd.rs +++ b/src/modular/bingcd/gcd.rs @@ -94,26 +94,23 @@ impl Odd> { i += 1; // Construct a_ and b_ as the summary of a and b, respectively. - let b_bits = b.bits(); - let n = const_max(2 * K, const_max(a.bits(), b_bits)); + let n = const_max(2 * K, const_max(a.bits(), b.bits())); let a_ = a.compact::(n); let b_ = b.compact::(n); - let compact_contains_all_of_b = - ConstChoice::from_u32_le(b_bits, K - 1).or(ConstChoice::from_u32_eq(n, 2 * K)); // Compute the K-1 iteration update matrix from a_ and b_ // Safe to vartime; function executes in time variable in `iterations` only, which is // a public constant K-1 here. - let (.., matrix, doublings) = a_ + let (.., matrix, _) = a_ .to_odd() .expect("a_ is always odd") - .partial_binxgcd_vartime::(&b_, K - 1, compact_contains_all_of_b); + .partial_binxgcd_vartime::(&b_, K - 1, ConstChoice::FALSE); // Update `a` and `b` using the update matrix let (updated_a, updated_b) = matrix.extended_apply_to((a, b)); - (a, _) = updated_a.div_2k(doublings).wrapping_drop_extension(); - (b, _) = updated_b.div_2k(doublings).wrapping_drop_extension(); + (a, _) = updated_a.div_2k_vartime(K - 1).wrapping_drop_extension(); + (b, _) = updated_b.div_2k_vartime(K - 1).wrapping_drop_extension(); } let gcd = a From 016d3a8044ff8a7dcd719d7f5345ea4639450256 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 14 Feb 2025 16:46:10 +0100 Subject: [PATCH 133/203] Introduce `bingcd` for `Int` --- src/int/bingcd.rs | 10 ++++++++++ src/int/sign.rs | 2 +- src/non_zero.rs | 5 +++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index 218a33a10..e7bab2509 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -44,6 +44,11 @@ impl Int { } impl NonZero> { + /// Compute the gcd of `self` and `rhs` leveraging the Binary GCD algorithm. + pub const fn bingcd(&self, rhs: &Self) -> NonZero> { + self.abs().bingcd(&rhs.as_ref().abs()) + } + /// Execute the Binary Extended GCD algorithm. /// /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. @@ -80,6 +85,11 @@ impl NonZero> { } impl Odd> { + /// Compute the gcd of `self` and `rhs` leveraging the Binary GCD algorithm. + pub const fn bingcd(&self, rhs: &Self) -> Odd> { + self.abs().bingcd(&rhs.as_ref().abs()) + } + /// Execute the Binary Extended GCD algorithm. /// /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. diff --git a/src/int/sign.rs b/src/int/sign.rs index 11b9bde5b..613d003f9 100644 --- a/src/int/sign.rs +++ b/src/int/sign.rs @@ -1,4 +1,4 @@ -use crate::{ConstChoice, ConstCtOption, Int, Odd, Uint, Word}; +use crate::{ConstChoice, ConstCtOption, Int, NonZero, Odd, Uint, Word}; use num_traits::ConstZero; impl Int { diff --git a/src/non_zero.rs b/src/non_zero.rs index fcce2dba9..b93dc9979 100644 --- a/src/non_zero.rs +++ b/src/non_zero.rs @@ -177,6 +177,11 @@ impl NonZero> { // Note: a NonZero always has a non-zero magnitude, so it is safe to unwrap. (NonZero::>::new_unwrap(abs), sign) } + + /// Convert a [`NonZero`] to its [`NonZero`] magnitude. + pub const fn abs(&self) -> NonZero> { + self.abs_sign().0 + } } #[cfg(feature = "hybrid-array")] From 7664f5f01e7efc66bd211e6252dfe295f0a115a7 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 14 Feb 2025 16:47:32 +0100 Subject: [PATCH 134/203] Fix clippy --- src/int/sign.rs | 2 +- src/modular/bingcd/gcd.rs | 12 ++++-------- src/modular/bingcd/tools.rs | 2 +- src/modular/bingcd/xgcd.rs | 6 +++--- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/int/sign.rs b/src/int/sign.rs index 613d003f9..11b9bde5b 100644 --- a/src/int/sign.rs +++ b/src/int/sign.rs @@ -1,4 +1,4 @@ -use crate::{ConstChoice, ConstCtOption, Int, NonZero, Odd, Uint, Word}; +use crate::{ConstChoice, ConstCtOption, Int, Odd, Uint, Word}; use num_traits::ConstZero; impl Int { diff --git a/src/modular/bingcd/gcd.rs b/src/modular/bingcd/gcd.rs index 7c25c0320..15ba9203a 100644 --- a/src/modular/bingcd/gcd.rs +++ b/src/modular/bingcd/gcd.rs @@ -22,11 +22,9 @@ impl Odd> { j += 1; } - let gcd = b + b .to_odd() - .expect("gcd of an odd value with something else is always odd"); - - gcd + .expect("gcd of an odd value with something else is always odd") } /// Binary GCD update step. @@ -113,11 +111,9 @@ impl Odd> { (b, _) = updated_b.div_2k_vartime(K - 1).wrapping_drop_extension(); } - let gcd = a + a .to_odd() - .expect("gcd of an odd value with something else is always odd"); - - gcd + .expect("gcd of an odd value with something else is always odd") } } diff --git a/src/modular/bingcd/tools.rs b/src/modular/bingcd/tools.rs index 333da1ffb..5255516ae 100644 --- a/src/modular/bingcd/tools.rs +++ b/src/modular/bingcd/tools.rs @@ -50,7 +50,7 @@ impl Uint { // Floor-divide self by 2. When self was odd, add back 1/2 mod q. let add_one_half = self.is_odd(); let floored_half = self.shr_vartime(1); - floored_half.wrapping_add(&Self::select(&Self::ZERO, &half_mod_q, add_one_half)) + floored_half.wrapping_add(&Self::select(&Self::ZERO, half_mod_q, add_one_half)) } /// Construct a [Uint] containing the bits in `self` in the range `[idx, idx + length)`. diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 7588d758e..78761c890 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -85,7 +85,7 @@ impl Odd> { // Extract the Bezout coefficients. let IntMatrix { m00, m01, .. } = matrix; - let x = m00.div_2k_mod_q(total_bound_shift, Self::MIN_BINGCD_ITERATIONS, &rhs); + let x = m00.div_2k_mod_q(total_bound_shift, Self::MIN_BINGCD_ITERATIONS, rhs); let y = m01.div_2k_mod_q(total_bound_shift, Self::MIN_BINGCD_ITERATIONS, self); (gcd, x, y) @@ -107,7 +107,7 @@ impl Odd> { /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 2. /// . pub(crate) const fn optimized_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { - self.optimized_binxgcd_::<{ U64::BITS }, { U64::LIMBS }, { U128::LIMBS }>(&rhs) + self.optimized_binxgcd_::<{ U64::BITS }, { U64::LIMBS }, { U128::LIMBS }>(rhs) } /// Given `(self, rhs)`, computes `(g, x, y)`, s.t. `self * x + rhs * y = g = gcd(self, rhs)`, @@ -174,7 +174,7 @@ impl Odd> { // Extract the Bezout coefficients. let IntMatrix { m00, m01, .. } = matrix; - let x = m00.div_2k_mod_q(total_doublings, Self::MIN_BINGCD_ITERATIONS, &rhs); + let x = m00.div_2k_mod_q(total_doublings, Self::MIN_BINGCD_ITERATIONS, rhs); let y = m01.div_2k_mod_q(total_doublings, Self::MIN_BINGCD_ITERATIONS, self); let gcd = a From 237b0e06bcdc5705e0e164bbd6cf25b51b58b6c2 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 14 Feb 2025 16:53:55 +0100 Subject: [PATCH 135/203] Move `Odd::BITS` to better spot --- src/modular/bingcd/gcd.rs | 14 +++++--------- src/odd.rs | 3 +++ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/modular/bingcd/gcd.rs b/src/modular/bingcd/gcd.rs index 15ba9203a..f13bf400d 100644 --- a/src/modular/bingcd/gcd.rs +++ b/src/modular/bingcd/gcd.rs @@ -2,9 +2,6 @@ use crate::modular::bingcd::tools::const_max; use crate::{ConstChoice, Odd, Uint, U128, U64}; impl Odd> { - /// Total size of the represented integer in bits. - pub const BITS: u32 = Uint::::BITS; - /// Computes `gcd(self, rhs)`, leveraging the (a constant time implementation of) the classic /// Binary GCD algorithm. /// @@ -22,8 +19,7 @@ impl Odd> { j += 1; } - b - .to_odd() + b.to_odd() .expect("gcd of an odd value with something else is always odd") } @@ -37,6 +33,9 @@ impl Odd> { /// a ← a - b /// a ← a/2 /// ``` + /// + /// Note: assumes `b` to be odd. Might yield an incorrect result if this is not the case. + /// /// Ref: Pornin, Algorithm 1, L3-9, . #[inline] const fn bingcd_step(a: &mut Uint, b: &mut Uint) { @@ -106,13 +105,11 @@ impl Odd> { // Update `a` and `b` using the update matrix let (updated_a, updated_b) = matrix.extended_apply_to((a, b)); - (a, _) = updated_a.div_2k_vartime(K - 1).wrapping_drop_extension(); (b, _) = updated_b.div_2k_vartime(K - 1).wrapping_drop_extension(); } - a - .to_odd() + a.to_odd() .expect("gcd of an odd value with something else is always odd") } } @@ -193,7 +190,6 @@ mod tests { bingcd_large_test(Uint::MAX, Uint::ONE); bingcd_large_test(Uint::MAX, Uint::MAX); bingcd_large_test(Int::MAX.abs(), Int::MIN.abs()); - // TODO: fix this! // Randomized testing for _ in 0..1000 { diff --git a/src/odd.rs b/src/odd.rs index 5bd92625c..87aa3dc4a 100644 --- a/src/odd.rs +++ b/src/odd.rs @@ -58,6 +58,9 @@ impl Odd { } impl Odd> { + /// Total size of the represented integer in bits. + pub const BITS: u32 = Uint::::BITS; + /// Create a new [`Odd>`] from the provided big endian hex string. /// /// Panics if the hex is malformed or not zero-padded accordingly for the size, or if the value is even. From c3d72fbe5a709e0bbf06984524ed5d0ad469f146 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 14 Feb 2025 16:57:12 +0100 Subject: [PATCH 136/203] Introduce `const MINIMAL_BINGCD_ITERATIONS` --- src/modular/bingcd/gcd.rs | 11 +++++++---- src/modular/bingcd/xgcd.rs | 16 ++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/modular/bingcd/gcd.rs b/src/modular/bingcd/gcd.rs index f13bf400d..fa44f6453 100644 --- a/src/modular/bingcd/gcd.rs +++ b/src/modular/bingcd/gcd.rs @@ -2,7 +2,11 @@ use crate::modular::bingcd::tools::const_max; use crate::{ConstChoice, Odd, Uint, U128, U64}; impl Odd> { - /// Computes `gcd(self, rhs)`, leveraging the (a constant time implementation of) the classic + /// The minimal number of iterations required to ensure the Binary GCD algorithm terminates and + /// returns the proper value. + const MINIMAL_BINGCD_ITERATIONS: u32 = 2 * Self::BITS - 1; + + /// Computes `gcd(self, rhs)`, leveraging (a constant time implementation of) the classic /// Binary GCD algorithm. /// /// Note: this algorithm is efficient for [Uint]s with relatively few `LIMBS`. @@ -14,7 +18,7 @@ impl Odd> { // (self, rhs) corresponds to (m, y) in the Algorithm 1 notation. let (mut a, mut b) = (*rhs, *self.as_ref()); let mut j = 0; - while j < (2 * Self::BITS - 1) { + while j < Self::MINIMAL_BINGCD_ITERATIONS { Self::bingcd_step(&mut a, &mut b); j += 1; } @@ -85,9 +89,8 @@ impl Odd> { ) -> Self { let (mut a, mut b) = (*self.as_ref(), *rhs); - let iterations = (2 * Self::BITS - 1).div_ceil(K - 1); let mut i = 0; - while i < iterations { + while i < Self::MINIMAL_BINGCD_ITERATIONS.div_ceil(K - 1) { i += 1; // Construct a_ and b_ as the summary of a and b, respectively. diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 78761c890..aaa4f07c3 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -4,7 +4,7 @@ use crate::{ConstChoice, Int, NonZero, Odd, Uint, U128, U64}; impl Odd> { /// The minimal number of binary GCD iterations required to guarantee successful completion. - const MIN_BINGCD_ITERATIONS: u32 = 2 * Uint::::BITS - 1; + const MIN_BINGCD_ITERATIONS: u32 = 2 * Self::BITS - 1; /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, /// leveraging the Binary Extended GCD algorithm. @@ -83,7 +83,7 @@ impl Odd> { ConstChoice::TRUE, ); - // Extract the Bezout coefficients. + // Extract the Bezout coefficients s.t. self * x + rhs + y = gcd let IntMatrix { m00, m01, .. } = matrix; let x = m00.div_2k_mod_q(total_bound_shift, Self::MIN_BINGCD_ITERATIONS, rhs); let y = m01.div_2k_mod_q(total_bound_shift, Self::MIN_BINGCD_ITERATIONS, self); @@ -141,6 +141,7 @@ impl Odd> { let mut matrix = IntMatrix::UNIT; let mut total_doublings = 0; + let (mut a_sgn, mut b_sgn); let mut i = 0; while i < Self::MIN_BINGCD_ITERATIONS.div_ceil(K) { i += 1; @@ -161,8 +162,6 @@ impl Odd> { // Update `a` and `b` using the update matrix let (updated_a, updated_b) = update_matrix.extended_apply_to((a, b)); - - let (a_sgn, b_sgn); (a, a_sgn) = updated_a.div_2k(doublings).wrapping_drop_extension(); (b, b_sgn) = updated_b.div_2k(doublings).wrapping_drop_extension(); @@ -172,14 +171,15 @@ impl Odd> { total_doublings += doublings; } - // Extract the Bezout coefficients. + let gcd = a + .to_odd() + .expect("gcd of an odd value with something else is always odd"); + + // Extract the Bezout coefficients s.t. self * x + rhs * y = gcd. let IntMatrix { m00, m01, .. } = matrix; let x = m00.div_2k_mod_q(total_doublings, Self::MIN_BINGCD_ITERATIONS, rhs); let y = m01.div_2k_mod_q(total_doublings, Self::MIN_BINGCD_ITERATIONS, self); - let gcd = a - .to_odd() - .expect("gcd of an odd value with something else is always odd"); (gcd, x, y) } From 81c8c4692ec7db1c3093bf6eed83a36def005d20 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 14 Feb 2025 17:05:40 +0100 Subject: [PATCH 137/203] Polish bingcd test suite --- src/modular/bingcd/gcd.rs | 101 ++++++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 41 deletions(-) diff --git a/src/modular/bingcd/gcd.rs b/src/modular/bingcd/gcd.rs index fa44f6453..54daeec85 100644 --- a/src/modular/bingcd/gcd.rs +++ b/src/modular/bingcd/gcd.rs @@ -121,11 +121,13 @@ impl Odd> { #[cfg(test)] mod tests { - mod test_bingcd_small { - use crate::{Gcd, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64}; + mod test_classic_bingcd { + use crate::{ + Gcd, Int, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64, + }; use rand_core::OsRng; - fn bingcd_small_test(lhs: Uint, rhs: Uint) + fn classic_bingcd_test(lhs: Uint, rhs: Uint) where Uint: Gcd>, { @@ -134,45 +136,54 @@ mod tests { assert_eq!(gcd, bingcd); } - fn bingcd_small_tests() + fn classic_bingcd_tests() where Uint: Gcd>, { // Edge cases - bingcd_small_test(Uint::ONE, Uint::ZERO); - bingcd_small_test(Uint::ONE, Uint::ONE); - bingcd_small_test(Uint::ONE, Uint::MAX); - bingcd_small_test(Uint::MAX, Uint::ZERO); - bingcd_small_test(Uint::MAX, Uint::ONE); - bingcd_small_test(Uint::MAX, Uint::MAX); + classic_bingcd_test(Uint::ONE, Uint::ZERO); + classic_bingcd_test(Uint::ONE, Uint::ONE); + classic_bingcd_test(Uint::ONE, Int::MAX.abs()); + classic_bingcd_test(Uint::ONE, Int::MIN.abs()); + classic_bingcd_test(Uint::ONE, Uint::MAX); + classic_bingcd_test(Int::MAX.abs(), Uint::ZERO); + classic_bingcd_test(Int::MAX.abs(), Uint::ONE); + classic_bingcd_test(Int::MAX.abs(), Int::MAX.abs()); + classic_bingcd_test(Int::MAX.abs(), Int::MIN.abs()); + classic_bingcd_test(Int::MAX.abs(), Uint::MAX); + classic_bingcd_test(Uint::MAX, Uint::ZERO); + classic_bingcd_test(Uint::MAX, Uint::ONE); + classic_bingcd_test(Uint::MAX, Int::MAX.abs()); + classic_bingcd_test(Uint::MAX, Int::MIN.abs()); + classic_bingcd_test(Uint::MAX, Uint::MAX); // Randomized test cases for _ in 0..1000 { let x = Uint::::random(&mut OsRng).bitor(&Uint::ONE); let y = Uint::::random(&mut OsRng); - bingcd_small_test(x, y); + classic_bingcd_test(x, y); } } #[test] - fn test_bingcd_small() { - bingcd_small_tests::<{ U64::LIMBS }>(); - bingcd_small_tests::<{ U128::LIMBS }>(); - bingcd_small_tests::<{ U192::LIMBS }>(); - bingcd_small_tests::<{ U256::LIMBS }>(); - bingcd_small_tests::<{ U384::LIMBS }>(); - bingcd_small_tests::<{ U512::LIMBS }>(); - bingcd_small_tests::<{ U1024::LIMBS }>(); - bingcd_small_tests::<{ U2048::LIMBS }>(); - bingcd_small_tests::<{ U4096::LIMBS }>(); + fn test_classic_bingcd() { + classic_bingcd_tests::<{ U64::LIMBS }>(); + classic_bingcd_tests::<{ U128::LIMBS }>(); + classic_bingcd_tests::<{ U192::LIMBS }>(); + classic_bingcd_tests::<{ U256::LIMBS }>(); + classic_bingcd_tests::<{ U384::LIMBS }>(); + classic_bingcd_tests::<{ U512::LIMBS }>(); + classic_bingcd_tests::<{ U1024::LIMBS }>(); + classic_bingcd_tests::<{ U2048::LIMBS }>(); + classic_bingcd_tests::<{ U4096::LIMBS }>(); } } - mod test_bingcd_large { + mod test_optimized_bingcd { use crate::{Gcd, Int, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512}; use rand_core::OsRng; - fn bingcd_large_test(lhs: Uint, rhs: Uint) + fn optimized_bingcd_test(lhs: Uint, rhs: Uint) where Uint: Gcd>, { @@ -181,38 +192,46 @@ mod tests { assert_eq!(gcd, bingcd); } - fn bingcd_large_tests() + fn optimized_bingcd_tests() where Uint: Gcd>, { // Edge cases - bingcd_large_test(Uint::ONE, Uint::ZERO); - bingcd_large_test(Uint::ONE, Uint::ONE); - bingcd_large_test(Uint::ONE, Uint::MAX); - bingcd_large_test(Uint::MAX, Uint::ZERO); - bingcd_large_test(Uint::MAX, Uint::ONE); - bingcd_large_test(Uint::MAX, Uint::MAX); - bingcd_large_test(Int::MAX.abs(), Int::MIN.abs()); + optimized_bingcd_test(Uint::ONE, Uint::ZERO); + optimized_bingcd_test(Uint::ONE, Uint::ONE); + optimized_bingcd_test(Uint::ONE, Int::MAX.abs()); + optimized_bingcd_test(Uint::ONE, Int::MIN.abs()); + optimized_bingcd_test(Uint::ONE, Uint::MAX); + optimized_bingcd_test(Int::MAX.abs(), Uint::ZERO); + optimized_bingcd_test(Int::MAX.abs(), Uint::ONE); + optimized_bingcd_test(Int::MAX.abs(), Int::MAX.abs()); + optimized_bingcd_test(Int::MAX.abs(), Int::MIN.abs()); + optimized_bingcd_test(Int::MAX.abs(), Uint::MAX); + optimized_bingcd_test(Uint::MAX, Uint::ZERO); + optimized_bingcd_test(Uint::MAX, Uint::ONE); + optimized_bingcd_test(Uint::MAX, Int::MAX.abs()); + optimized_bingcd_test(Uint::MAX, Int::MIN.abs()); + optimized_bingcd_test(Uint::MAX, Uint::MAX); // Randomized testing for _ in 0..1000 { let x = Uint::::random(&mut OsRng).bitor(&Uint::ONE); let y = Uint::::random(&mut OsRng); - bingcd_large_test(x, y); + optimized_bingcd_test(x, y); } } #[test] - fn test_bingcd_large() { + fn test_optimized_bingcd() { // Not applicable for U64 - bingcd_large_tests::<{ U128::LIMBS }>(); - bingcd_large_tests::<{ U192::LIMBS }>(); - bingcd_large_tests::<{ U256::LIMBS }>(); - bingcd_large_tests::<{ U384::LIMBS }>(); - bingcd_large_tests::<{ U512::LIMBS }>(); - bingcd_large_tests::<{ U1024::LIMBS }>(); - bingcd_large_tests::<{ U2048::LIMBS }>(); - bingcd_large_tests::<{ U4096::LIMBS }>(); + optimized_bingcd_tests::<{ U128::LIMBS }>(); + optimized_bingcd_tests::<{ U192::LIMBS }>(); + optimized_bingcd_tests::<{ U256::LIMBS }>(); + optimized_bingcd_tests::<{ U384::LIMBS }>(); + optimized_bingcd_tests::<{ U512::LIMBS }>(); + optimized_bingcd_tests::<{ U1024::LIMBS }>(); + optimized_bingcd_tests::<{ U2048::LIMBS }>(); + optimized_bingcd_tests::<{ U4096::LIMBS }>(); } } } From 11af8363945819eaeddb785b2cdaa4531f87c305 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 14 Feb 2025 17:20:52 +0100 Subject: [PATCH 138/203] Polish binxgcd test suite --- src/modular/bingcd/xgcd.rs | 82 ++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 47 deletions(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index aaa4f07c3..b4375b4dc 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -95,7 +95,8 @@ impl Odd> { /// leveraging the Binary Extended GCD algorithm. /// /// **Warning**: this algorithm is only guaranteed to work for `self` and `rhs` for which the - /// msb is **not** set. May panic otherwise. + /// msb is **not** set. May panic otherwise. Furthermore, at `self` and `rhs` must contain at + /// least 128 bits. /// /// Note: this algorithm becomes more efficient than the classical algorithm for [Uint]s with /// relatively many `LIMBS`. A best-effort threshold is presented in [Self::binxgcd]. @@ -107,6 +108,7 @@ impl Odd> { /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 2. /// . pub(crate) const fn optimized_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { + assert!(Self::BITS > U128::BITS); self.optimized_binxgcd_::<{ U64::BITS }, { U64::LIMBS }, { U128::LIMBS }>(rhs) } @@ -301,14 +303,15 @@ mod tests { mod test_partial_binxgcd { use crate::modular::bingcd::matrix::IntMatrix; - use crate::{ConstChoice, I64, U64}; + use crate::{ConstChoice, Odd, I64, U64}; + + const A: Odd = U64::from_be_hex("CA048AFA63CD6A1F").to_odd().expect("odd"); + const B: U64 = U64::from_be_hex("AE693BF7BE8E5566"); #[test] fn test_partial_binxgcd() { - let a = U64::from_be_hex("CA048AFA63CD6A1F").to_odd().unwrap(); - let b = U64::from_be_hex("AE693BF7BE8E5566"); let (.., matrix, iters) = - a.partial_binxgcd_vartime::<{ U64::LIMBS }>(&b, 5, ConstChoice::TRUE); + A.partial_binxgcd_vartime::<{ U64::LIMBS }>(&B, 5, ConstChoice::TRUE); assert_eq!(iters, 5); assert_eq!( matrix, @@ -318,48 +321,34 @@ mod tests { #[test] fn test_partial_binxgcd_constructs_correct_matrix() { - let a = U64::from_be_hex("CA048AFA63CD6A1F").to_odd().unwrap(); - let b = U64::from_be_hex("AE693BF7BE8E5566"); - let (new_a, new_b, matrix, iters) = - a.partial_binxgcd_vartime::<{ U64::LIMBS }>(&b, 5, ConstChoice::TRUE); - assert_eq!(iters, 5); + let (new_a, new_b, matrix, _) = + A.partial_binxgcd_vartime::<{ U64::LIMBS }>(&B, 5, ConstChoice::TRUE); - let (computed_a, computed_b) = matrix.extended_apply_to((a.get(), b)); + let (computed_a, computed_b) = matrix.extended_apply_to((A.get(), B)); let computed_a = computed_a.div_2k(5).wrapping_drop_extension().0; let computed_b = computed_b.div_2k(5).wrapping_drop_extension().0; - assert_eq!( - new_a.get(), - computed_a, - "{} {} {} {}", - new_a, - new_b, - computed_a, - computed_b - ); + assert_eq!(new_a.get(), computed_a); assert_eq!(new_b, computed_b); } + const SMALL_A: Odd = U64::from_be_hex("0000000003CD6A1F").to_odd().expect("odd"); + const SMALL_B: U64 = U64::from_be_hex("000000000E8E5566"); + #[test] fn test_partial_binxgcd_halts() { - // Stop before max_iters - let a = U64::from_be_hex("0000000003CD6A1F").to_odd().unwrap(); - let b = U64::from_be_hex("000000000E8E5566"); let (gcd, .., iters) = - a.partial_binxgcd_vartime::<{ U64::LIMBS }>(&b, 60, ConstChoice::TRUE); + SMALL_A.partial_binxgcd_vartime::<{ U64::LIMBS }>(&SMALL_B, 60, ConstChoice::TRUE); assert_eq!(iters, 35); - assert_eq!(gcd.get(), a.gcd(&b)); + assert_eq!(gcd.get(), SMALL_A.gcd(&SMALL_B)); } #[test] fn test_partial_binxgcd_does_not_halt() { - // Stop before max_iters - let a = U64::from_be_hex("0000000003CD6A1F").to_odd().unwrap(); - let b = U64::from_be_hex("000000000E8E5566"); let (gcd, .., iters) = - a.partial_binxgcd_vartime::<{ U64::LIMBS }>(&b, 60, ConstChoice::FALSE); + SMALL_A.partial_binxgcd_vartime::<{ U64::LIMBS }>(&SMALL_B, 60, ConstChoice::FALSE); assert_eq!(iters, 60); - assert_eq!(gcd.get(), a.gcd(&b)); + assert_eq!(gcd.get(), SMALL_A.gcd(&SMALL_B)); } } @@ -448,7 +437,7 @@ mod tests { }; use rand_core::OsRng; - fn binxgcd_test(lhs: Uint, rhs: Uint) + fn optimized_binxgcd_test(lhs: Uint, rhs: Uint) where Uint: Gcd> + ConcatMixed, MixedOutput = Uint>, @@ -460,38 +449,37 @@ mod tests { test_xgcd(lhs, rhs, binxgcd.get(), x, y); } - fn binxgcd_tests() + fn optimized_binxgcd_tests() where Uint: Gcd> + ConcatMixed, MixedOutput = Uint>, { // Edge cases let upper_bound = *Int::MAX.as_uint(); - binxgcd_test(Uint::ONE, Uint::ONE); - binxgcd_test(Uint::ONE, upper_bound); - binxgcd_test(upper_bound, Uint::ONE); - binxgcd_test(upper_bound, upper_bound); + optimized_binxgcd_test(Uint::ONE, Uint::ONE); + optimized_binxgcd_test(Uint::ONE, upper_bound); + optimized_binxgcd_test(upper_bound, Uint::ONE); + optimized_binxgcd_test(upper_bound, upper_bound); // Randomized test cases let bound = Int::MIN.as_uint().to_nz().unwrap(); for _ in 0..100 { let x = Uint::::random_mod(&mut OsRng, &bound).bitor(&Uint::ONE); let y = Uint::::random_mod(&mut OsRng, &bound).bitor(&Uint::ONE); - binxgcd_test(x, y); + optimized_binxgcd_test(x, y); } } #[test] - fn test_binxgcd() { - // Cannot be applied to U64 - binxgcd_tests::<{ U128::LIMBS }, { U256::LIMBS }>(); - binxgcd_tests::<{ U192::LIMBS }, { U384::LIMBS }>(); - binxgcd_tests::<{ U256::LIMBS }, { U512::LIMBS }>(); - binxgcd_tests::<{ U384::LIMBS }, { U768::LIMBS }>(); - binxgcd_tests::<{ U512::LIMBS }, { U1024::LIMBS }>(); - binxgcd_tests::<{ U1024::LIMBS }, { U2048::LIMBS }>(); - binxgcd_tests::<{ U2048::LIMBS }, { U4096::LIMBS }>(); - binxgcd_tests::<{ U4096::LIMBS }, { U8192::LIMBS }>(); + fn test_optimized_binxgcd() { + optimized_binxgcd_tests::<{ U128::LIMBS }, { U256::LIMBS }>(); + optimized_binxgcd_tests::<{ U192::LIMBS }, { U384::LIMBS }>(); + optimized_binxgcd_tests::<{ U256::LIMBS }, { U512::LIMBS }>(); + optimized_binxgcd_tests::<{ U384::LIMBS }, { U768::LIMBS }>(); + optimized_binxgcd_tests::<{ U512::LIMBS }, { U1024::LIMBS }>(); + optimized_binxgcd_tests::<{ U1024::LIMBS }, { U2048::LIMBS }>(); + optimized_binxgcd_tests::<{ U2048::LIMBS }, { U4096::LIMBS }>(); + optimized_binxgcd_tests::<{ U4096::LIMBS }, { U8192::LIMBS }>(); } } } From 3061a8c8701c855287d3469f6b27a068ec922660 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 14 Feb 2025 17:36:28 +0100 Subject: [PATCH 139/203] Tweak bingcd threshold --- src/uint/bingcd.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index 83522b37b..d8c425fe0 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -46,8 +46,7 @@ impl Odd> { /// manually test whether the classic or optimized algorithm is faster for your machine. #[inline(always)] pub const fn bingcd(&self, rhs: &Uint) -> Self { - // Todo: tweak this threshold - if LIMBS < 8 { + if LIMBS < 6 { self.classic_bingcd(rhs) } else { self.optimized_bingcd(rhs) From 58dee9e891fd46857d428c7fe6fc81062d495f3a Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 14 Feb 2025 17:44:26 +0100 Subject: [PATCH 140/203] Fix assert --- src/modular/bingcd/xgcd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index b4375b4dc..63d542ac2 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -108,7 +108,7 @@ impl Odd> { /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 2. /// . pub(crate) const fn optimized_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { - assert!(Self::BITS > U128::BITS); + assert!(Self::BITS >= U128::BITS); self.optimized_binxgcd_::<{ U64::BITS }, { U64::LIMBS }, { U128::LIMBS }>(rhs) } From d2e863f9217ee872bc02f9f34e39dc11497909b1 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 14 Feb 2025 18:49:36 +0100 Subject: [PATCH 141/203] Optimize xgcd threshold --- src/modular/bingcd/xgcd.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 63d542ac2..afbebb9d1 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -59,8 +59,7 @@ impl Odd> { /// threshold. When using [Uint]s with `LIMBS` close to the threshold, it may be useful to /// manually test whether the classic or optimized algorithm is faster for your machine. pub(crate) const fn binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { - // todo: optimize threshold - if LIMBS < 5 { + if LIMBS < 4 { self.classic_binxgcd(rhs) } else { self.optimized_binxgcd(rhs) From 2ab41b005867e02c06589f734778a110f579d6d7 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 21 Feb 2025 12:28:00 +0100 Subject: [PATCH 142/203] Test `binxgcd_nz` --- src/modular/bingcd/xgcd.rs | 62 +++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index afbebb9d1..0083b5b54 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -371,6 +371,66 @@ mod tests { ); } + mod test_binxgcd_nz { + use crate::modular::bingcd::xgcd::tests::test_xgcd; + use crate::{ + ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, U4096, + U512, U64, U768, U8192, + }; + use rand_core::OsRng; + + fn binxgcd_nz_test( + lhs: Uint, + rhs: Uint, + ) where + Uint: + Gcd> + ConcatMixed, MixedOutput = Uint>, + { + let (binxgcd, x, y) = lhs + .to_odd() + .unwrap() + .binxgcd_nz(&rhs.to_nz().unwrap()); + test_xgcd(lhs, rhs, binxgcd.get(), x, y); + } + + fn binxgcd_nz_tests() + where + Uint: + Gcd> + ConcatMixed, MixedOutput = Uint>, + { + // Edge cases + let odd_upper_bound = *Int::MAX.as_uint(); + let even_upper_bound = Int::MIN.abs(); + binxgcd_nz_test(Uint::ONE, Uint::ONE); + binxgcd_nz_test(Uint::ONE, odd_upper_bound); + binxgcd_nz_test(Uint::ONE, even_upper_bound); + binxgcd_nz_test(odd_upper_bound, Uint::ONE); + binxgcd_nz_test(odd_upper_bound, odd_upper_bound); + binxgcd_nz_test(odd_upper_bound, even_upper_bound); + + // Randomized test cases + let bound = Int::MIN.as_uint().to_nz().unwrap(); + for _ in 0..100 { + let x = Uint::::random_mod(&mut OsRng, &bound).bitor(&Uint::ONE); + let y = Uint::::random_mod(&mut OsRng, &bound).saturating_add(&Uint::ONE); + binxgcd_nz_test(x, y); + } + } + + #[test] + fn test_binxgcd_nz() { + binxgcd_nz_tests::<{ U64::LIMBS }, { U128::LIMBS }>(); + binxgcd_nz_tests::<{ U128::LIMBS }, { U256::LIMBS }>(); + binxgcd_nz_tests::<{ U192::LIMBS }, { U384::LIMBS }>(); + binxgcd_nz_tests::<{ U256::LIMBS }, { U512::LIMBS }>(); + binxgcd_nz_tests::<{ U384::LIMBS }, { U768::LIMBS }>(); + binxgcd_nz_tests::<{ U512::LIMBS }, { U1024::LIMBS }>(); + binxgcd_nz_tests::<{ U1024::LIMBS }, { U2048::LIMBS }>(); + binxgcd_nz_tests::<{ U2048::LIMBS }, { U4096::LIMBS }>(); + binxgcd_nz_tests::<{ U4096::LIMBS }, { U8192::LIMBS }>(); + } + } + mod test_classic_binxgcd { use crate::modular::bingcd::xgcd::tests::test_xgcd; use crate::{ @@ -428,7 +488,7 @@ mod tests { } } - mod test_binxgcd { + mod test_optimized_binxgcd { use crate::modular::bingcd::xgcd::tests::test_xgcd; use crate::{ ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, U4096, From efc3fa0c8db0155d33357bbd725afdc2a22ef7b0 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 21 Feb 2025 12:30:37 +0100 Subject: [PATCH 143/203] Fix fmt --- src/modular/bingcd/xgcd.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 0083b5b54..0981e2989 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -384,19 +384,16 @@ mod tests { rhs: Uint, ) where Uint: - Gcd> + ConcatMixed, MixedOutput = Uint>, + Gcd> + ConcatMixed, MixedOutput = Uint>, { - let (binxgcd, x, y) = lhs - .to_odd() - .unwrap() - .binxgcd_nz(&rhs.to_nz().unwrap()); + let (binxgcd, x, y) = lhs.to_odd().unwrap().binxgcd_nz(&rhs.to_nz().unwrap()); test_xgcd(lhs, rhs, binxgcd.get(), x, y); } fn binxgcd_nz_tests() where Uint: - Gcd> + ConcatMixed, MixedOutput = Uint>, + Gcd> + ConcatMixed, MixedOutput = Uint>, { // Edge cases let odd_upper_bound = *Int::MAX.as_uint(); @@ -496,8 +493,10 @@ mod tests { }; use rand_core::OsRng; - fn optimized_binxgcd_test(lhs: Uint, rhs: Uint) - where + fn optimized_binxgcd_test( + lhs: Uint, + rhs: Uint, + ) where Uint: Gcd> + ConcatMixed, MixedOutput = Uint>, { From d16247796ad6e7feb13936e37eb6272290c7731f Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 21 Feb 2025 14:59:02 +0100 Subject: [PATCH 144/203] Introduce `extract_bezout_coefficients_vartime` --- src/modular/bingcd/xgcd.rs | 157 +++++++++++++++++++++++++++++++++++-- 1 file changed, 149 insertions(+), 8 deletions(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 0981e2989..475e9b044 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -2,6 +2,32 @@ use crate::modular::bingcd::matrix::IntMatrix; use crate::modular::bingcd::tools::const_max; use crate::{ConstChoice, Int, NonZero, Odd, Uint, U128, U64}; +/// Extract the Bézout coefficients from `matrix`, where it is assumed that +/// `matrix * (lhs, rhs) = (gcd * 2^k, 0)`. +/// +/// The function executes in time variable in `k_upper_bound`. By making sure that +/// this `k_upper_bound >= k`, the function executes in time constant in `k`. +const fn extract_bezout_coefficients_vartime( + lhs: &Odd>, + rhs: &Odd>, + matrix: &IntMatrix, + k: u32, + k_upper_bound: u32, +) -> (Int, Int) { + debug_assert!(k <= k_upper_bound); + + // The Bézout coefficients `x` and `y` can be extracted from `matrix.m00` and `matrix.m01`, + // respectively. In fact, `matrix.m00 = x * 2^k` and `matrix.m01 = y * 2^k`. + // Hence, we can compute + // `x = matrix.m00 / 2^k mod rhs`, and + // `y = matrix.m01 / 2^k mod lhs`. + let (x, y) = (matrix.m00, matrix.m01); + ( + x.div_2k_mod_q(k, k_upper_bound, rhs), + y.div_2k_mod_q(k, k_upper_bound, lhs), + ) +} + impl Odd> { /// The minimal number of binary GCD iterations required to guarantee successful completion. const MIN_BINGCD_ITERATIONS: u32 = 2 * Self::BITS - 1; @@ -76,16 +102,22 @@ impl Odd> { /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 1. /// . pub(crate) const fn classic_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { - let (gcd, _, matrix, total_bound_shift) = self.partial_binxgcd_vartime::( + let (gcd, _, matrix, total_doublings) = self.partial_binxgcd_vartime::( rhs.as_ref(), Self::MIN_BINGCD_ITERATIONS, ConstChoice::TRUE, ); - // Extract the Bezout coefficients s.t. self * x + rhs + y = gcd - let IntMatrix { m00, m01, .. } = matrix; - let x = m00.div_2k_mod_q(total_bound_shift, Self::MIN_BINGCD_ITERATIONS, rhs); - let y = m01.div_2k_mod_q(total_bound_shift, Self::MIN_BINGCD_ITERATIONS, self); + // Extract the Bezout coefficients s.t. self * x + rhs * y = gcd. + // Safe to vartime; the function is vartime in k_upper_bound for which we pass a public + // constant + let (x, y) = extract_bezout_coefficients_vartime( + self, + rhs, + &matrix, + total_doublings, + Self::MIN_BINGCD_ITERATIONS, + ); (gcd, x, y) } @@ -177,9 +209,15 @@ impl Odd> { .expect("gcd of an odd value with something else is always odd"); // Extract the Bezout coefficients s.t. self * x + rhs * y = gcd. - let IntMatrix { m00, m01, .. } = matrix; - let x = m00.div_2k_mod_q(total_doublings, Self::MIN_BINGCD_ITERATIONS, rhs); - let y = m01.div_2k_mod_q(total_doublings, Self::MIN_BINGCD_ITERATIONS, self); + // Safe to vartime; the function is vartime in k_upper_bound for which we pass a public + // constant + let (x, y) = extract_bezout_coefficients_vartime( + self, + rhs, + &matrix, + total_doublings, + Self::MIN_BINGCD_ITERATIONS, + ); (gcd, x, y) } @@ -300,6 +338,109 @@ impl Odd> { mod tests { use crate::{ConcatMixed, Gcd, Int, Uint}; + mod test_extract_bezout_coefficients { + use crate::modular::bingcd::matrix::IntMatrix; + use crate::modular::bingcd::xgcd::extract_bezout_coefficients_vartime; + use crate::{Int, Uint, I64, U64}; + + #[test] + fn test_extract_bezout_coefficients_unit() { + let (x, y) = extract_bezout_coefficients_vartime( + &Uint::ONE.to_odd().unwrap(), + &Uint::ONE.to_odd().unwrap(), + &IntMatrix::<{ U64::LIMBS }>::UNIT, + 0, + 0, + ); + assert_eq!(x, Int::ONE); + assert_eq!(y, Int::ZERO); + } + + #[test] + fn test_extract_bezout_coefficients_basic() { + let (x, y) = extract_bezout_coefficients_vartime( + &Uint::ONE.to_odd().unwrap(), + &Uint::ONE.to_odd().unwrap(), + &IntMatrix::new( + I64::from(2i32), + I64::from(3i32), + I64::from(4i32), + I64::from(5i32), + ), + 0, + 0, + ); + assert_eq!(x, Int::from(2i32)); + assert_eq!(y, Int::from(3i32)); + + let (x, y) = extract_bezout_coefficients_vartime( + &Uint::ONE.to_odd().unwrap(), + &Uint::ONE.to_odd().unwrap(), + &IntMatrix::new( + I64::from(2i32), + I64::from(3i32), + I64::from(4i32), + I64::from(5i32), + ), + 0, + 1, + ); + assert_eq!(x, Int::from(2i32)); + assert_eq!(y, Int::from(3i32)); + } + + #[test] + fn test_extract_bezout_coefficients_removes_doublings_easy() { + let (x, y) = extract_bezout_coefficients_vartime( + &Uint::ONE.to_odd().unwrap(), + &Uint::ONE.to_odd().unwrap(), + &IntMatrix::new( + I64::from(2i32), + I64::from(6i32), + I64::from(4i32), + I64::from(5i32), + ), + 1, + 1, + ); + assert_eq!(x, Int::ONE); + assert_eq!(y, Int::from(3i32)); + + let (x, y) = extract_bezout_coefficients_vartime( + &Uint::ONE.to_odd().unwrap(), + &Uint::ONE.to_odd().unwrap(), + &IntMatrix::new( + I64::from(120i32), + I64::from(64i32), + I64::from(4i32), + I64::from(5i32), + ), + 5, + 6, + ); + assert_eq!(x, Int::from(4i32)); + assert_eq!(y, Int::from(2i32)); + } + + #[test] + fn test_extract_bezout_coefficients_removes_doublings_for_odd_numbers() { + let (x, y) = extract_bezout_coefficients_vartime( + &Uint::from(7u32).to_odd().unwrap(), + &Uint::from(5u32).to_odd().unwrap(), + &IntMatrix::new( + I64::from(2i32), + I64::from(6i32), + I64::from(4i32), + I64::from(5i32), + ), + 3, + 7, + ); + assert_eq!(x, Int::from(4i32)); + assert_eq!(y, Int::from(6i32)); + } + } + mod test_partial_binxgcd { use crate::modular::bingcd::matrix::IntMatrix; use crate::{ConstChoice, Odd, I64, U64}; From c0fd90539c333334f36440a0acf6dd973514d4cc Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 21 Feb 2025 15:04:29 +0100 Subject: [PATCH 145/203] Patch bug --- src/modular/bingcd/xgcd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 475e9b044..ad13f1968 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -176,7 +176,7 @@ impl Odd> { let (mut a_sgn, mut b_sgn); let mut i = 0; - while i < Self::MIN_BINGCD_ITERATIONS.div_ceil(K) { + while i < Self::MIN_BINGCD_ITERATIONS.div_ceil(K - 1) { i += 1; // Construct a_ and b_ as the summary of a and b, respectively. From a6a9c9270aa0e3186e3c8b28a7559df0b45c1bca Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 21 Feb 2025 15:18:00 +0100 Subject: [PATCH 146/203] Introduce `extract_quotients` --- src/modular/bingcd/xgcd.rs | 43 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index ad13f1968..9a54ce73d 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -28,6 +28,16 @@ const fn extract_bezout_coefficients_vartime( ) } +/// Extract the quotients `lhs/gcd` and `rhs/gcd` from `matrix`, where it is assumed that +/// `matrix * (lhs, rhs) = (gcd * 2^k, 0)` for some `k >= 0`. +const fn extract_quotients( + matrix: &IntMatrix, +) -> (Uint, Uint) { + let lhs_on_gcd = matrix.m11.abs(); + let rhs_on_gcd = matrix.m10.abs(); + (lhs_on_gcd, rhs_on_gcd) +} + impl Odd> { /// The minimal number of binary GCD iterations required to guarantee successful completion. const MIN_BINGCD_ITERATIONS: u32 = 2 * Self::BITS - 1; @@ -338,6 +348,39 @@ impl Odd> { mod tests { use crate::{ConcatMixed, Gcd, Int, Uint}; + mod test_extract_quotients { + use crate::modular::bingcd::matrix::IntMatrix; + use crate::modular::bingcd::xgcd::extract_quotients; + use crate::{Int, Uint, U64}; + #[test] + fn test_extract_quotients_unit() { + let (lhs_on_gcd, rhs_on_gcd) = extract_quotients(&IntMatrix::<{ U64::LIMBS }>::UNIT); + assert_eq!(lhs_on_gcd, Uint::ONE); + assert_eq!(rhs_on_gcd, Uint::ZERO); + } + + #[test] + fn test_extract_quotients_basic() { + let (lhs_on_gcd, rhs_on_gcd) = extract_quotients(&IntMatrix::<{ U64::LIMBS }>::new( + Int::ZERO, + Int::ZERO, + Int::from(5i32), + Int::from(-7i32), + )); + assert_eq!(lhs_on_gcd, Uint::from(7u32)); + assert_eq!(rhs_on_gcd, Uint::from(5u32)); + + let (lhs_on_gcd, rhs_on_gcd) = extract_quotients(&IntMatrix::<{ U64::LIMBS }>::new( + Int::ZERO, + Int::ZERO, + Int::from(-7i32), + Int::from(5i32), + )); + assert_eq!(lhs_on_gcd, Uint::from(5u32)); + assert_eq!(rhs_on_gcd, Uint::from(7u32)); + } + } + mod test_extract_bezout_coefficients { use crate::modular::bingcd::matrix::IntMatrix; use crate::modular::bingcd::xgcd::extract_bezout_coefficients_vartime; From ee4ee4bdf01807a335cfdcd67cfd33a9e22703c2 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 21 Feb 2025 16:08:48 +0100 Subject: [PATCH 147/203] Introduce `RawBinXgcdOutput` --- src/modular/bingcd/xgcd.rs | 207 +++++++++++++++++++------------------ 1 file changed, 108 insertions(+), 99 deletions(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 9a54ce73d..8edde4749 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -2,30 +2,31 @@ use crate::modular::bingcd::matrix::IntMatrix; use crate::modular::bingcd::tools::const_max; use crate::{ConstChoice, Int, NonZero, Odd, Uint, U128, U64}; -/// Extract the Bézout coefficients from `matrix`, where it is assumed that -/// `matrix * (lhs, rhs) = (gcd * 2^k, 0)`. -/// -/// The function executes in time variable in `k_upper_bound`. By making sure that -/// this `k_upper_bound >= k`, the function executes in time constant in `k`. -const fn extract_bezout_coefficients_vartime( - lhs: &Odd>, - rhs: &Odd>, - matrix: &IntMatrix, +/// Container for the output of the raw Binary XGCD output. +pub(crate) struct RawBinxgcdOutput { + lhs: Odd>, + rhs: Odd>, + gcd: Odd>, + matrix: IntMatrix, k: u32, k_upper_bound: u32, -) -> (Int, Int) { - debug_assert!(k <= k_upper_bound); - - // The Bézout coefficients `x` and `y` can be extracted from `matrix.m00` and `matrix.m01`, - // respectively. In fact, `matrix.m00 = x * 2^k` and `matrix.m01 = y * 2^k`. - // Hence, we can compute - // `x = matrix.m00 / 2^k mod rhs`, and - // `y = matrix.m01 / 2^k mod lhs`. - let (x, y) = (matrix.m00, matrix.m01); - ( - x.div_2k_mod_q(k, k_upper_bound, rhs), - y.div_2k_mod_q(k, k_upper_bound, lhs), - ) +} + +impl RawBinxgcdOutput { + /// Extract the Bézout coefficients from `matrix`, where it is assumed that + /// `matrix * (lhs, rhs) = (gcd * 2^k, 0)`. + const fn bezout_coefficients(&self) -> (Int, Int) { + // The Bézout coefficients `x` and `y` can be extracted from `matrix.m00` and `matrix.m01`, + // respectively. In fact, `matrix.m00 = x * 2^k` and `matrix.m01 = y * 2^k`. + // Hence, we can compute + // `x = matrix.m00 / 2^k mod rhs`, and + // `y = matrix.m01 / 2^k mod lhs`. + let (x, y) = (self.matrix.m00, self.matrix.m01); + ( + x.div_2k_mod_q(self.k, self.k_upper_bound, &self.rhs), + y.div_2k_mod_q(self.k, self.k_upper_bound, &self.lhs), + ) + } } /// Extract the quotients `lhs/gcd` and `rhs/gcd` from `matrix`, where it is assumed that @@ -65,7 +66,8 @@ impl Odd> { .to_odd() .expect("rhs is odd by construction"); - let (gcd, mut x, mut y) = self.binxgcd(&rhs_); + let output = self.binxgcd(&rhs_); + let (mut x, mut y) = output.bezout_coefficients(); // At this point, we have one of the following three situations: // i. gcd = lhs * x + (rhs - lhs) * y, if rhs is even and rhs > lhs @@ -82,7 +84,7 @@ impl Odd> { x = Int::select(&x, &x.wrapping_add(&y), rhs_is_even.and(rhs_gt_lhs.not())); y = y.wrapping_neg_if(rhs_is_even.and(rhs_gt_lhs.not())); - (gcd, x, y) + (output.gcd, x, y) } /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, @@ -94,7 +96,7 @@ impl Odd> { /// This function switches between the "classic" and "optimized" algorithm at a best-effort /// threshold. When using [Uint]s with `LIMBS` close to the threshold, it may be useful to /// manually test whether the classic or optimized algorithm is faster for your machine. - pub(crate) const fn binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { + pub(crate) const fn binxgcd(&self, rhs: &Self) -> RawBinxgcdOutput { if LIMBS < 4 { self.classic_binxgcd(rhs) } else { @@ -111,25 +113,21 @@ impl Odd> { /// /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 1. /// . - pub(crate) const fn classic_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { + pub(crate) const fn classic_binxgcd(&self, rhs: &Self) -> RawBinxgcdOutput { let (gcd, _, matrix, total_doublings) = self.partial_binxgcd_vartime::( rhs.as_ref(), Self::MIN_BINGCD_ITERATIONS, ConstChoice::TRUE, ); - // Extract the Bezout coefficients s.t. self * x + rhs * y = gcd. - // Safe to vartime; the function is vartime in k_upper_bound for which we pass a public - // constant - let (x, y) = extract_bezout_coefficients_vartime( - self, - rhs, - &matrix, - total_doublings, - Self::MIN_BINGCD_ITERATIONS, - ); - - (gcd, x, y) + RawBinxgcdOutput { + lhs: *self, + rhs: *rhs, + gcd, + matrix, + k: total_doublings, + k_upper_bound: Self::MIN_BINGCD_ITERATIONS, + } } /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, @@ -148,7 +146,7 @@ impl Odd> { /// /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 2. /// . - pub(crate) const fn optimized_binxgcd(&self, rhs: &Self) -> (Self, Int, Int) { + pub(crate) const fn optimized_binxgcd(&self, rhs: &Self) -> RawBinxgcdOutput { assert!(Self::BITS >= U128::BITS); self.optimized_binxgcd_::<{ U64::BITS }, { U64::LIMBS }, { U128::LIMBS }>(rhs) } @@ -179,7 +177,7 @@ impl Odd> { >( &self, rhs: &Self, - ) -> (Self, Int, Int) { + ) -> RawBinxgcdOutput { let (mut a, mut b) = (*self.as_ref(), *rhs.as_ref()); let mut matrix = IntMatrix::UNIT; let mut total_doublings = 0; @@ -218,18 +216,14 @@ impl Odd> { .to_odd() .expect("gcd of an odd value with something else is always odd"); - // Extract the Bezout coefficients s.t. self * x + rhs * y = gcd. - // Safe to vartime; the function is vartime in k_upper_bound for which we pass a public - // constant - let (x, y) = extract_bezout_coefficients_vartime( - self, - rhs, - &matrix, - total_doublings, - Self::MIN_BINGCD_ITERATIONS, - ); - - (gcd, x, y) + RawBinxgcdOutput { + lhs: *self, + rhs: *rhs, + gcd, + matrix, + k: total_doublings, + k_upper_bound: Self::MIN_BINGCD_ITERATIONS, + } } /// Executes the optimized Binary GCD inner loop. @@ -352,6 +346,7 @@ mod tests { use crate::modular::bingcd::matrix::IntMatrix; use crate::modular::bingcd::xgcd::extract_quotients; use crate::{Int, Uint, U64}; + #[test] fn test_extract_quotients_unit() { let (lhs_on_gcd, rhs_on_gcd) = extract_quotients(&IntMatrix::<{ U64::LIMBS }>::UNIT); @@ -383,102 +378,114 @@ mod tests { mod test_extract_bezout_coefficients { use crate::modular::bingcd::matrix::IntMatrix; - use crate::modular::bingcd::xgcd::extract_bezout_coefficients_vartime; + use crate::modular::bingcd::xgcd::RawBinxgcdOutput; use crate::{Int, Uint, I64, U64}; #[test] fn test_extract_bezout_coefficients_unit() { - let (x, y) = extract_bezout_coefficients_vartime( - &Uint::ONE.to_odd().unwrap(), - &Uint::ONE.to_odd().unwrap(), - &IntMatrix::<{ U64::LIMBS }>::UNIT, - 0, - 0, - ); + let output = RawBinxgcdOutput { + lhs: Uint::ONE.to_odd().unwrap(), + rhs: Uint::ONE.to_odd().unwrap(), + gcd: Uint::ONE.to_odd().unwrap(), + matrix: IntMatrix::<{ U64::LIMBS }>::UNIT, + k: 0, + k_upper_bound: 0, + }; + let (x, y) = output.bezout_coefficients(); assert_eq!(x, Int::ONE); assert_eq!(y, Int::ZERO); } #[test] fn test_extract_bezout_coefficients_basic() { - let (x, y) = extract_bezout_coefficients_vartime( - &Uint::ONE.to_odd().unwrap(), - &Uint::ONE.to_odd().unwrap(), - &IntMatrix::new( + let output = RawBinxgcdOutput { + lhs: Uint::ONE.to_odd().unwrap(), + rhs: Uint::ONE.to_odd().unwrap(), + gcd: Uint::ONE.to_odd().unwrap(), + matrix: IntMatrix::new( I64::from(2i32), I64::from(3i32), I64::from(4i32), I64::from(5i32), ), - 0, - 0, - ); + k: 0, + k_upper_bound: 0, + }; + let (x, y) = output.bezout_coefficients(); assert_eq!(x, Int::from(2i32)); assert_eq!(y, Int::from(3i32)); - let (x, y) = extract_bezout_coefficients_vartime( - &Uint::ONE.to_odd().unwrap(), - &Uint::ONE.to_odd().unwrap(), - &IntMatrix::new( + let output = RawBinxgcdOutput { + lhs: Uint::ONE.to_odd().unwrap(), + rhs: Uint::ONE.to_odd().unwrap(), + gcd: Uint::ONE.to_odd().unwrap(), + matrix: IntMatrix::new( I64::from(2i32), I64::from(3i32), I64::from(4i32), I64::from(5i32), ), - 0, - 1, - ); + k: 0, + k_upper_bound: 1, + }; + let (x, y) = output.bezout_coefficients(); assert_eq!(x, Int::from(2i32)); assert_eq!(y, Int::from(3i32)); } #[test] fn test_extract_bezout_coefficients_removes_doublings_easy() { - let (x, y) = extract_bezout_coefficients_vartime( - &Uint::ONE.to_odd().unwrap(), - &Uint::ONE.to_odd().unwrap(), - &IntMatrix::new( + let output = RawBinxgcdOutput { + lhs: Uint::ONE.to_odd().unwrap(), + rhs: Uint::ONE.to_odd().unwrap(), + gcd: Uint::ONE.to_odd().unwrap(), + matrix: IntMatrix::new( I64::from(2i32), I64::from(6i32), I64::from(4i32), I64::from(5i32), ), - 1, - 1, - ); + k: 1, + k_upper_bound: 1, + }; + let (x, y) = output.bezout_coefficients(); assert_eq!(x, Int::ONE); assert_eq!(y, Int::from(3i32)); - let (x, y) = extract_bezout_coefficients_vartime( - &Uint::ONE.to_odd().unwrap(), - &Uint::ONE.to_odd().unwrap(), - &IntMatrix::new( + let output = RawBinxgcdOutput { + lhs: Uint::ONE.to_odd().unwrap(), + rhs: Uint::ONE.to_odd().unwrap(), + gcd: Uint::ONE.to_odd().unwrap(), + matrix: IntMatrix::new( I64::from(120i32), I64::from(64i32), I64::from(4i32), I64::from(5i32), ), - 5, - 6, - ); + k: 5, + k_upper_bound: 6, + }; + let (x, y) = output.bezout_coefficients(); assert_eq!(x, Int::from(4i32)); assert_eq!(y, Int::from(2i32)); } #[test] fn test_extract_bezout_coefficients_removes_doublings_for_odd_numbers() { - let (x, y) = extract_bezout_coefficients_vartime( - &Uint::from(7u32).to_odd().unwrap(), - &Uint::from(5u32).to_odd().unwrap(), - &IntMatrix::new( + let output = RawBinxgcdOutput { + lhs: Uint::from(7u32).to_odd().unwrap(), + rhs: Uint::from(5u32).to_odd().unwrap(), + gcd: Uint::ONE.to_odd().unwrap(), + matrix: IntMatrix::new( I64::from(2i32), I64::from(6i32), I64::from(4i32), I64::from(5i32), ), - 3, - 7, - ); + k: 3, + k_upper_bound: 7, + }; + let (x, y) = output.bezout_coefficients(); assert_eq!(x, Int::from(4i32)); assert_eq!(y, Int::from(6i32)); } @@ -627,11 +634,12 @@ mod tests { Uint: Gcd> + ConcatMixed, MixedOutput = Uint>, { - let (binxgcd, x, y) = lhs + let output = lhs .to_odd() .unwrap() .classic_binxgcd(&rhs.to_odd().unwrap()); - test_xgcd(lhs, rhs, binxgcd.get(), x, y); + let (x, y) = output.bezout_coefficients(); + test_xgcd(lhs, rhs, output.gcd.get(), x, y); } fn classic_binxgcd_tests() @@ -684,11 +692,12 @@ mod tests { Uint: Gcd> + ConcatMixed, MixedOutput = Uint>, { - let (binxgcd, x, y) = lhs + let output = lhs .to_odd() .unwrap() .optimized_binxgcd(&rhs.to_odd().unwrap()); - test_xgcd(lhs, rhs, binxgcd.get(), x, y); + let (x, y) = output.bezout_coefficients(); + test_xgcd(lhs, rhs, output.gcd.get(), x, y); } fn optimized_binxgcd_tests() From ac327f006d91e1eb2e1ba884dd3ea247297dbefa Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 21 Feb 2025 16:35:51 +0100 Subject: [PATCH 148/203] Introduce `OddBinXgcdOutput` in preparation for quotients --- src/int/bingcd.rs | 4 +- src/modular/bingcd/xgcd.rs | 105 ++++++++++++++++++++++++------------- 2 files changed, 71 insertions(+), 38 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index e7bab2509..c02c58368 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -100,7 +100,9 @@ impl Odd> { let (abs_lhs, sgn_lhs) = self.abs_sign(); let (abs_rhs, sgn_rhs) = rhs.abs_sign(); - let (gcd, x, y) = abs_lhs.binxgcd_nz(&abs_rhs); + let output = abs_lhs.binxgcd_nz(&abs_rhs); + let gcd = output.gcd; + let (x, y) = output.bezout_coefficients(); (gcd, x.wrapping_neg_if(sgn_lhs), y.wrapping_neg_if(sgn_rhs)) } diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 8edde4749..21992c980 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -2,7 +2,7 @@ use crate::modular::bingcd::matrix::IntMatrix; use crate::modular::bingcd::tools::const_max; use crate::{ConstChoice, Int, NonZero, Odd, Uint, U128, U64}; -/// Container for the output of the raw Binary XGCD output. +/// Container for the raw output of the Binary XGCD algorithm. pub(crate) struct RawBinxgcdOutput { lhs: Odd>, rhs: Odd>, @@ -13,9 +13,21 @@ pub(crate) struct RawBinxgcdOutput { } impl RawBinxgcdOutput { + /// Process raw output, constructing an OddBinXgcdOutput object. + const fn process(&self) -> OddBinxgcdOutput { + let (x, y) = self.derive_bezout_coefficients(); + OddBinxgcdOutput { + lhs: self.lhs, + rhs: self.rhs, + gcd: self.gcd, + x, + y, + } + } + /// Extract the Bézout coefficients from `matrix`, where it is assumed that /// `matrix * (lhs, rhs) = (gcd * 2^k, 0)`. - const fn bezout_coefficients(&self) -> (Int, Int) { + const fn derive_bezout_coefficients(&self) -> (Int, Int) { // The Bézout coefficients `x` and `y` can be extracted from `matrix.m00` and `matrix.m01`, // respectively. In fact, `matrix.m00 = x * 2^k` and `matrix.m01 = y * 2^k`. // Hence, we can compute @@ -27,6 +39,11 @@ impl RawBinxgcdOutput { y.div_2k_mod_q(self.k, self.k_upper_bound, &self.lhs), ) } + + /// Mutably borrow the quotients `lhs/gcd` and `rhs/gcd`. + const fn quotients_as_mut(&mut self) -> (&mut Int, &mut Int) { + (&mut self.matrix.m11, &mut self.matrix.m10) + } } /// Extract the quotients `lhs/gcd` and `rhs/gcd` from `matrix`, where it is assumed that @@ -39,6 +56,25 @@ const fn extract_quotients( (lhs_on_gcd, rhs_on_gcd) } +/// Container for the processed output of the Binary XGCD algorithm. +pub struct OddBinxgcdOutput { + lhs: Odd>, + rhs: Odd>, + pub(crate) gcd: Odd>, + x: Int, + y: Int, +} + +impl OddBinxgcdOutput { + pub(crate) const fn bezout_coefficients(&self) -> (Int, Int) { + (self.x, self.y) + } + + const fn bezout_coefficients_as_mut(&mut self) -> (&mut Int, &mut Int) { + (&mut self.x, &mut self.y) + } +} + impl Odd> { /// The minimal number of binary GCD iterations required to guarantee successful completion. const MIN_BINGCD_ITERATIONS: u32 = 2 * Self::BITS - 1; @@ -48,10 +84,7 @@ impl Odd> { /// /// **Warning**: this algorithm is only guaranteed to work for `self` and `rhs` for which the /// msb is **not** set. May panic otherwise. - pub(crate) const fn binxgcd_nz( - &self, - rhs: &NonZero>, - ) -> (Self, Int, Int) { + pub(crate) const fn binxgcd_nz(&self, rhs: &NonZero>) -> OddBinxgcdOutput { // Note that for the `binxgcd` subroutine, `rhs` needs to be odd. // // We use the fact that gcd(a, b) = gcd(a, |a-b|) to @@ -66,25 +99,24 @@ impl Odd> { .to_odd() .expect("rhs is odd by construction"); - let output = self.binxgcd(&rhs_); - let (mut x, mut y) = output.bezout_coefficients(); + let mut output = self.binxgcd(&rhs_).process(); + let (x, y) = output.bezout_coefficients_as_mut(); // At this point, we have one of the following three situations: // i. gcd = lhs * x + (rhs - lhs) * y, if rhs is even and rhs > lhs // ii. gcd = lhs * x + (lhs - rhs) * y, if rhs is even and rhs < lhs // iii. gcd = lhs * x + rhs * y, if rhs is odd - // We can rearrange these terms to get the Bezout coefficients to the original (self, rhs) - // input as follows: + // We can rearrange these terms to get the Bezout coefficients to (self, rhs) as follows: // i. gcd = lhs * (x - y) + rhs * y, if rhs is even and rhs > lhs // ii. gcd = lhs * (x + y) - y * rhs, if rhs is even and rhs < lhs // iii. gcd = lhs * x + rhs * y, if rhs is odd - x = Int::select(&x, &x.wrapping_sub(&y), rhs_is_even.and(rhs_gt_lhs)); - x = Int::select(&x, &x.wrapping_add(&y), rhs_is_even.and(rhs_gt_lhs.not())); - y = y.wrapping_neg_if(rhs_is_even.and(rhs_gt_lhs.not())); + *x = Int::select(&x, &x.wrapping_sub(&y), rhs_is_even.and(rhs_gt_lhs)); + *x = Int::select(&x, &x.wrapping_add(&y), rhs_is_even.and(rhs_gt_lhs.not())); + *y = y.wrapping_neg_if(rhs_is_even.and(rhs_gt_lhs.not())); - (output.gcd, x, y) + output } /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, @@ -340,7 +372,8 @@ impl Odd> { #[cfg(test)] mod tests { - use crate::{ConcatMixed, Gcd, Int, Uint}; + use crate::modular::bingcd::xgcd::OddBinxgcdOutput; + use crate::{ConcatMixed, Gcd, Uint}; mod test_extract_quotients { use crate::modular::bingcd::matrix::IntMatrix; @@ -376,13 +409,13 @@ mod tests { } } - mod test_extract_bezout_coefficients { + mod test_derive_bezout_coefficients { use crate::modular::bingcd::matrix::IntMatrix; use crate::modular::bingcd::xgcd::RawBinxgcdOutput; use crate::{Int, Uint, I64, U64}; #[test] - fn test_extract_bezout_coefficients_unit() { + fn test_derive_bezout_coefficients_unit() { let output = RawBinxgcdOutput { lhs: Uint::ONE.to_odd().unwrap(), rhs: Uint::ONE.to_odd().unwrap(), @@ -391,13 +424,13 @@ mod tests { k: 0, k_upper_bound: 0, }; - let (x, y) = output.bezout_coefficients(); + let (x, y) = output.derive_bezout_coefficients(); assert_eq!(x, Int::ONE); assert_eq!(y, Int::ZERO); } #[test] - fn test_extract_bezout_coefficients_basic() { + fn test_derive_bezout_coefficients_basic() { let output = RawBinxgcdOutput { lhs: Uint::ONE.to_odd().unwrap(), rhs: Uint::ONE.to_odd().unwrap(), @@ -411,7 +444,7 @@ mod tests { k: 0, k_upper_bound: 0, }; - let (x, y) = output.bezout_coefficients(); + let (x, y) = output.derive_bezout_coefficients(); assert_eq!(x, Int::from(2i32)); assert_eq!(y, Int::from(3i32)); @@ -428,13 +461,13 @@ mod tests { k: 0, k_upper_bound: 1, }; - let (x, y) = output.bezout_coefficients(); + let (x, y) = output.derive_bezout_coefficients(); assert_eq!(x, Int::from(2i32)); assert_eq!(y, Int::from(3i32)); } #[test] - fn test_extract_bezout_coefficients_removes_doublings_easy() { + fn test_derive_bezout_coefficients_removes_doublings_easy() { let output = RawBinxgcdOutput { lhs: Uint::ONE.to_odd().unwrap(), rhs: Uint::ONE.to_odd().unwrap(), @@ -448,7 +481,7 @@ mod tests { k: 1, k_upper_bound: 1, }; - let (x, y) = output.bezout_coefficients(); + let (x, y) = output.derive_bezout_coefficients(); assert_eq!(x, Int::ONE); assert_eq!(y, Int::from(3i32)); @@ -465,13 +498,13 @@ mod tests { k: 5, k_upper_bound: 6, }; - let (x, y) = output.bezout_coefficients(); + let (x, y) = output.derive_bezout_coefficients(); assert_eq!(x, Int::from(4i32)); assert_eq!(y, Int::from(2i32)); } #[test] - fn test_extract_bezout_coefficients_removes_doublings_for_odd_numbers() { + fn test_derive_bezout_coefficients_removes_doublings_for_odd_numbers() { let output = RawBinxgcdOutput { lhs: Uint::from(7u32).to_odd().unwrap(), rhs: Uint::from(5u32).to_odd().unwrap(), @@ -485,7 +518,7 @@ mod tests { k: 3, k_upper_bound: 7, }; - let (x, y) = output.bezout_coefficients(); + let (x, y) = output.derive_bezout_coefficients(); assert_eq!(x, Int::from(4i32)); assert_eq!(y, Int::from(6i32)); } @@ -546,19 +579,19 @@ mod tests { fn test_xgcd( lhs: Uint, rhs: Uint, - found_gcd: Uint, - x: Int, - y: Int, + output: OddBinxgcdOutput, ) where Uint: Gcd> + ConcatMixed, MixedOutput = Uint>, { // Test the gcd - assert_eq!(lhs.gcd(&rhs), found_gcd); + assert_eq!(lhs.gcd(&rhs), output.gcd); + // Test the Bezout coefficients + let (x, y) = output.bezout_coefficients(); assert_eq!( x.widening_mul_uint(&lhs) + y.widening_mul_uint(&rhs), - found_gcd.resize().as_int(), + output.gcd.resize().as_int(), ); } @@ -577,8 +610,8 @@ mod tests { Uint: Gcd> + ConcatMixed, MixedOutput = Uint>, { - let (binxgcd, x, y) = lhs.to_odd().unwrap().binxgcd_nz(&rhs.to_nz().unwrap()); - test_xgcd(lhs, rhs, binxgcd.get(), x, y); + let output = lhs.to_odd().unwrap().binxgcd_nz(&rhs.to_nz().unwrap()); + test_xgcd(lhs, rhs, output); } fn binxgcd_nz_tests() @@ -638,8 +671,7 @@ mod tests { .to_odd() .unwrap() .classic_binxgcd(&rhs.to_odd().unwrap()); - let (x, y) = output.bezout_coefficients(); - test_xgcd(lhs, rhs, output.gcd.get(), x, y); + test_xgcd(lhs, rhs, output.process()); } fn classic_binxgcd_tests() @@ -696,8 +728,7 @@ mod tests { .to_odd() .unwrap() .optimized_binxgcd(&rhs.to_odd().unwrap()); - let (x, y) = output.bezout_coefficients(); - test_xgcd(lhs, rhs, output.gcd.get(), x, y); + test_xgcd(lhs, rhs, output.process()); } fn optimized_binxgcd_tests() From 4f9a7bba82ac24bc8edc64cab14154d16eba0ea1 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 21 Feb 2025 16:45:03 +0100 Subject: [PATCH 149/203] Test quotients --- src/modular/bingcd/xgcd.rs | 72 +++++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 21992c980..2ef4a83a7 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -16,12 +16,15 @@ impl RawBinxgcdOutput { /// Process raw output, constructing an OddBinXgcdOutput object. const fn process(&self) -> OddBinxgcdOutput { let (x, y) = self.derive_bezout_coefficients(); + let (lhs_on_gcd, rhs_on_gcd) = self.extract_quotients(); OddBinxgcdOutput { lhs: self.lhs, rhs: self.rhs, gcd: self.gcd, x, y, + lhs_on_gcd, + rhs_on_gcd, } } @@ -44,16 +47,13 @@ impl RawBinxgcdOutput { const fn quotients_as_mut(&mut self) -> (&mut Int, &mut Int) { (&mut self.matrix.m11, &mut self.matrix.m10) } -} -/// Extract the quotients `lhs/gcd` and `rhs/gcd` from `matrix`, where it is assumed that -/// `matrix * (lhs, rhs) = (gcd * 2^k, 0)` for some `k >= 0`. -const fn extract_quotients( - matrix: &IntMatrix, -) -> (Uint, Uint) { - let lhs_on_gcd = matrix.m11.abs(); - let rhs_on_gcd = matrix.m10.abs(); - (lhs_on_gcd, rhs_on_gcd) + /// Extract the quotients `lhs/gcd` and `rhs/gcd` from `matrix`. + const fn extract_quotients(&self) -> (Uint, Uint) { + let lhs_on_gcd = self.matrix.m11.abs(); + let rhs_on_gcd = self.matrix.m10.abs(); + (lhs_on_gcd, rhs_on_gcd) + } } /// Container for the processed output of the Binary XGCD algorithm. @@ -63,6 +63,8 @@ pub struct OddBinxgcdOutput { pub(crate) gcd: Odd>, x: Int, y: Int, + lhs_on_gcd: Uint, + rhs_on_gcd: Uint, } impl OddBinxgcdOutput { @@ -99,8 +101,25 @@ impl Odd> { .to_odd() .expect("rhs is odd by construction"); - let mut output = self.binxgcd(&rhs_).process(); - let (x, y) = output.bezout_coefficients_as_mut(); + let mut output = self.binxgcd(&rhs_); + let (u, v) = output.quotients_as_mut(); + + // At this point, we have one of the following three situations: + // i. 0 = lhs * v + (rhs - lhs) * u, if rhs is even and rhs > lhs + // ii. 0 = lhs * v + (lhs - rhs) * u, if rhs is even and rhs < lhs + // iii. 0 = lhs * v + rhs * u, if rhs is odd + + // We can rearrange these terms to get the quotients to (self, rhs) as follows: + // i. gcd = lhs * (v - u) + rhs * u, if rhs is even and rhs > lhs + // ii. gcd = lhs * (v + u) - u * rhs, if rhs is even and rhs < lhs + // iii. gcd = lhs * v + rhs * u, if rhs is odd + + *v = Int::select(&v, &v.wrapping_sub(&u), rhs_is_even.and(rhs_gt_lhs)); + *v = Int::select(&v, &v.wrapping_add(&u), rhs_is_even.and(rhs_gt_lhs.not())); + *u = u.wrapping_neg_if(rhs_is_even.and(rhs_gt_lhs.not())); + + let mut processed_output = output.process(); + let (x, y) = processed_output.bezout_coefficients_as_mut(); // At this point, we have one of the following three situations: // i. gcd = lhs * x + (rhs - lhs) * y, if rhs is even and rhs > lhs @@ -116,7 +135,7 @@ impl Odd> { *x = Int::select(&x, &x.wrapping_add(&y), rhs_is_even.and(rhs_gt_lhs.not())); *y = y.wrapping_neg_if(rhs_is_even.and(rhs_gt_lhs.not())); - output + processed_output } /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, @@ -372,38 +391,55 @@ impl Odd> { #[cfg(test)] mod tests { + use core::ops::Div; use crate::modular::bingcd::xgcd::OddBinxgcdOutput; use crate::{ConcatMixed, Gcd, Uint}; mod test_extract_quotients { use crate::modular::bingcd::matrix::IntMatrix; - use crate::modular::bingcd::xgcd::extract_quotients; + use crate::modular::bingcd::xgcd::RawBinxgcdOutput; use crate::{Int, Uint, U64}; + fn raw_binxgcdoutput_setup( + matrix: IntMatrix, + ) -> RawBinxgcdOutput { + RawBinxgcdOutput { + lhs: Uint::::ONE.to_odd().unwrap(), + rhs: Uint::::ONE.to_odd().unwrap(), + gcd: Uint::::ONE.to_odd().unwrap(), + matrix, + k: 0, + k_upper_bound: 0, + } + } + #[test] fn test_extract_quotients_unit() { - let (lhs_on_gcd, rhs_on_gcd) = extract_quotients(&IntMatrix::<{ U64::LIMBS }>::UNIT); + let output = raw_binxgcdoutput_setup(IntMatrix::<{ U64::LIMBS }>::UNIT); + let (lhs_on_gcd, rhs_on_gcd) = output.extract_quotients(); assert_eq!(lhs_on_gcd, Uint::ONE); assert_eq!(rhs_on_gcd, Uint::ZERO); } #[test] fn test_extract_quotients_basic() { - let (lhs_on_gcd, rhs_on_gcd) = extract_quotients(&IntMatrix::<{ U64::LIMBS }>::new( + let output = raw_binxgcdoutput_setup(IntMatrix::<{ U64::LIMBS }>::new( Int::ZERO, Int::ZERO, Int::from(5i32), Int::from(-7i32), )); + let (lhs_on_gcd, rhs_on_gcd) = output.extract_quotients(); assert_eq!(lhs_on_gcd, Uint::from(7u32)); assert_eq!(rhs_on_gcd, Uint::from(5u32)); - let (lhs_on_gcd, rhs_on_gcd) = extract_quotients(&IntMatrix::<{ U64::LIMBS }>::new( + let output = raw_binxgcdoutput_setup(IntMatrix::<{ U64::LIMBS }>::new( Int::ZERO, Int::ZERO, Int::from(-7i32), Int::from(5i32), )); + let (lhs_on_gcd, rhs_on_gcd) = output.extract_quotients(); assert_eq!(lhs_on_gcd, Uint::from(5u32)); assert_eq!(rhs_on_gcd, Uint::from(7u32)); } @@ -587,6 +623,10 @@ mod tests { // Test the gcd assert_eq!(lhs.gcd(&rhs), output.gcd); + // Test the quotients + assert_eq!(output.lhs_on_gcd, lhs.div(output.gcd.as_nz_ref())); + assert_eq!(output.rhs_on_gcd, rhs.div(output.gcd.as_nz_ref())); + // Test the Bezout coefficients let (x, y) = output.bezout_coefficients(); assert_eq!( From 10055e5a461abf83fe3f4fe6437aa066d0620bc3 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 21 Feb 2025 16:45:55 +0100 Subject: [PATCH 150/203] Update docs --- src/modular/bingcd/xgcd.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 2ef4a83a7..0f1393f19 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -68,10 +68,12 @@ pub struct OddBinxgcdOutput { } impl OddBinxgcdOutput { + /// Obtain a copy of the Bézout coefficients. pub(crate) const fn bezout_coefficients(&self) -> (Int, Int) { (self.x, self.y) } + /// Mutably borrow the Bézout coefficients. const fn bezout_coefficients_as_mut(&mut self) -> (&mut Int, &mut Int) { (&mut self.x, &mut self.y) } From 0dee8a05d97af89e7006f709dca54f3a8bec7b47 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 21 Feb 2025 16:59:10 +0100 Subject: [PATCH 151/203] minimal bezout coefficients --- src/modular/bingcd/xgcd.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 0f1393f19..a6c156a13 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -68,6 +68,19 @@ pub struct OddBinxgcdOutput { } impl OddBinxgcdOutput { + /// Obtain a pair of minimal Bézout coefficients. + pub(crate) const fn minimal_bezout_coefficients(&self) -> (Int, Int) { + // Attempt to reduce x and y mod rhs_on_gcd and lhs_on_gcd, respectively. + let mut minimal_x = self.x.rem_uint(&self.rhs_on_gcd.to_nz().expect("is nz")); + let mut minimal_y = self.y.rem_uint(&self.lhs_on_gcd.to_nz().expect("is nz")); + + // This trick only works whenever lhs/rhs > 1. Only apply whenever this is the case. + minimal_x = Int::select(&self.x, &minimal_x, Uint::gt(&self.rhs_on_gcd, &Uint::ONE)); + minimal_y = Int::select(&self.y, &minimal_y, Uint::gt(&self.lhs_on_gcd, &Uint::ONE)); + + (minimal_x, minimal_y) + } + /// Obtain a copy of the Bézout coefficients. pub(crate) const fn bezout_coefficients(&self) -> (Int, Int) { (self.x, self.y) @@ -635,6 +648,17 @@ mod tests { x.widening_mul_uint(&lhs) + y.widening_mul_uint(&rhs), output.gcd.resize().as_int(), ); + + // Test the minimal Bezout coefficients for minimality + let (x, y) = output.minimal_bezout_coefficients(); + assert!(x.abs() <= output.rhs_on_gcd, "{} {}", lhs, rhs); + assert!(y.abs() <= output.lhs_on_gcd, "{} {}", lhs, rhs); + + // Test the minimal Bezout coefficients for correctness + assert_eq!( + x.widening_mul_uint(&lhs) + y.widening_mul_uint(&rhs), + output.gcd.resize().as_int(), + ); } mod test_binxgcd_nz { From 1e8a0a35053a46c411c1e983975675866fceb044 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 21 Feb 2025 17:29:39 +0100 Subject: [PATCH 152/203] Refactor `Odd::Binxgcd` --- src/int/bingcd.rs | 97 ++++++++++++++++++++++++++------------ src/modular/bingcd/xgcd.rs | 24 ++++++---- 2 files changed, 84 insertions(+), 37 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index c02c58368..78835f3d7 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -5,6 +5,26 @@ use crate::modular::bingcd::tools::const_min; use crate::{ConstChoice, Int, NonZero, Odd, Uint}; +pub struct BinXgcdOutput { + gcd: Odd>, + x: Int, + y: Int, + lhs_on_gcd: Int, + rhs_on_gcd: Int, +} + +impl BinXgcdOutput { + /// Return the quotients `lhs.gcd` and `rhs/gcd`. + pub const fn quotients(&self) -> (Int, Int) { + (self.lhs_on_gcd, self.rhs_on_gcd) + } + + /// Return the Bézout coefficients `x` and `y` s.t. `lhs * x + rhs * y = gcd`. + pub const fn bezout_coefficients(&self) -> (Int, Int) { + (self.x, self.y) + } +} + impl Int { /// Compute the gcd of `self` and `rhs` leveraging the Binary GCD algorithm. pub const fn bingcd(&self, rhs: &Self) -> Uint { @@ -69,7 +89,9 @@ impl NonZero> { let lhs = lhs.to_odd().expect("odd by construction"); let rhs = rhs.to_nz().expect("non-zero by construction"); - let (gcd, mut x, mut y) = lhs.binxgcd(&rhs); + let output = lhs.binxgcd(&rhs); + let gcd = output.gcd; + let (mut x, mut y) = output.bezout_coefficients(); Int::conditional_swap(&mut x, &mut y, swap); @@ -93,18 +115,27 @@ impl Odd> { /// Execute the Binary Extended GCD algorithm. /// /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. - pub const fn binxgcd( - &self, - rhs: &NonZero>, - ) -> (Odd>, Int, Int) { + pub const fn binxgcd(&self, rhs: &NonZero>) -> BinXgcdOutput { let (abs_lhs, sgn_lhs) = self.abs_sign(); let (abs_rhs, sgn_rhs) = rhs.abs_sign(); let output = abs_lhs.binxgcd_nz(&abs_rhs); - let gcd = output.gcd; - let (x, y) = output.bezout_coefficients(); - (gcd, x.wrapping_neg_if(sgn_lhs), y.wrapping_neg_if(sgn_rhs)) + let (mut x, mut y) = output.bezout_coefficients(); + x = x.wrapping_neg_if(sgn_lhs); + y = y.wrapping_neg_if(sgn_rhs); + + let (abs_lhs_on_gcd, abs_rhs_on_gcd) = output.quotients(); + let lhs_on_gcd = Int::new_from_abs_sign(abs_lhs_on_gcd, sgn_lhs).expect("no overflow"); + let rhs_on_gcd = Int::new_from_abs_sign(abs_rhs_on_gcd, sgn_rhs).expect("no overflow"); + + BinXgcdOutput { + gcd: output.gcd, + x, + y, + lhs_on_gcd, + rhs_on_gcd, + } } } @@ -262,23 +293,31 @@ mod test { mod test_odd_int_binxgcd { use crate::{ - ConcatMixed, Int, Odd, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, - U64, U768, U8192, + ConcatMixed, Int, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64, + U768, U8192, }; use rand_core::OsRng; fn odd_int_binxgcd_test( - lhs: Odd>, - rhs: Odd>, + lhs: Int, + rhs: Int, ) where Uint: ConcatMixed, MixedOutput = Uint>, { let gcd = lhs.bingcd(&rhs); - let (xgcd, x, y) = lhs.binxgcd(&rhs.as_ref().to_nz().unwrap()); - assert_eq!(gcd.to_odd().unwrap(), xgcd); + let output = lhs.to_odd().unwrap().binxgcd(&rhs.to_nz().unwrap()); + assert_eq!(gcd.to_odd().unwrap(), output.gcd); + + // Test quotients + let (lhs_on_gcd, rhs_on_gcd) = output.quotients(); + assert_eq!(lhs_on_gcd, lhs.div_uint(&gcd.to_nz().unwrap())); + assert_eq!(rhs_on_gcd, rhs.div_uint(&gcd.to_nz().unwrap())); + + // Test the Bezout coefficients + let (x, y) = output.bezout_coefficients(); assert_eq!( x.widening_mul(&lhs).wrapping_add(&y.widening_mul(&rhs)), - xgcd.resize().as_int() + gcd.resize().as_int() ); } @@ -287,22 +326,22 @@ mod test { Uint: ConcatMixed, MixedOutput = Uint>, { let neg_max = Int::MAX.wrapping_neg(); - odd_int_binxgcd_test(neg_max.to_odd().unwrap(), neg_max.to_odd().unwrap()); - odd_int_binxgcd_test(neg_max.to_odd().unwrap(), Int::MINUS_ONE.to_odd().unwrap()); - odd_int_binxgcd_test(neg_max.to_odd().unwrap(), Int::ONE.to_odd().unwrap()); - odd_int_binxgcd_test(neg_max.to_odd().unwrap(), Int::MAX.to_odd().unwrap()); - odd_int_binxgcd_test(Int::ONE.to_odd().unwrap(), neg_max.to_odd().unwrap()); - odd_int_binxgcd_test(Int::ONE.to_odd().unwrap(), Int::MINUS_ONE.to_odd().unwrap()); - odd_int_binxgcd_test(Int::ONE.to_odd().unwrap(), Int::ONE.to_odd().unwrap()); - odd_int_binxgcd_test(Int::ONE.to_odd().unwrap(), Int::MAX.to_odd().unwrap()); - odd_int_binxgcd_test(Int::MAX.to_odd().unwrap(), neg_max.to_odd().unwrap()); - odd_int_binxgcd_test(Int::MAX.to_odd().unwrap(), Int::MINUS_ONE.to_odd().unwrap()); - odd_int_binxgcd_test(Int::MAX.to_odd().unwrap(), Int::ONE.to_odd().unwrap()); - odd_int_binxgcd_test(Int::MAX.to_odd().unwrap(), Int::MAX.to_odd().unwrap()); + odd_int_binxgcd_test(neg_max, neg_max); + odd_int_binxgcd_test(neg_max, Int::MINUS_ONE); + odd_int_binxgcd_test(neg_max, Int::ONE); + odd_int_binxgcd_test(neg_max, Int::MAX); + odd_int_binxgcd_test(Int::ONE, neg_max); + odd_int_binxgcd_test(Int::ONE, Int::MINUS_ONE); + odd_int_binxgcd_test(Int::ONE, Int::ONE); + odd_int_binxgcd_test(Int::ONE, Int::MAX); + odd_int_binxgcd_test(Int::MAX, neg_max); + odd_int_binxgcd_test(Int::MAX, Int::MINUS_ONE); + odd_int_binxgcd_test(Int::MAX, Int::ONE); + odd_int_binxgcd_test(Int::MAX, Int::MAX); for _ in 0..100 { - let x = Odd::>::random(&mut OsRng); - let y = Odd::>::random(&mut OsRng); + let x = Int::::random(&mut OsRng).bitor(&Int::ONE); + let y = Int::::random(&mut OsRng); odd_int_binxgcd_test(x, y); } } diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index a6c156a13..675f32677 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -14,10 +14,10 @@ pub(crate) struct RawBinxgcdOutput { impl RawBinxgcdOutput { /// Process raw output, constructing an OddBinXgcdOutput object. - const fn process(&self) -> OddBinxgcdOutput { + const fn process(&self) -> OddBinxgcdUintOutput { let (x, y) = self.derive_bezout_coefficients(); let (lhs_on_gcd, rhs_on_gcd) = self.extract_quotients(); - OddBinxgcdOutput { + OddBinxgcdUintOutput { lhs: self.lhs, rhs: self.rhs, gcd: self.gcd, @@ -57,7 +57,7 @@ impl RawBinxgcdOutput { } /// Container for the processed output of the Binary XGCD algorithm. -pub struct OddBinxgcdOutput { +pub struct OddBinxgcdUintOutput { lhs: Odd>, rhs: Odd>, pub(crate) gcd: Odd>, @@ -67,7 +67,7 @@ pub struct OddBinxgcdOutput { rhs_on_gcd: Uint, } -impl OddBinxgcdOutput { +impl OddBinxgcdUintOutput { /// Obtain a pair of minimal Bézout coefficients. pub(crate) const fn minimal_bezout_coefficients(&self) -> (Int, Int) { // Attempt to reduce x and y mod rhs_on_gcd and lhs_on_gcd, respectively. @@ -90,6 +90,11 @@ impl OddBinxgcdOutput { const fn bezout_coefficients_as_mut(&mut self) -> (&mut Int, &mut Int) { (&mut self.x, &mut self.y) } + + /// Obtain a copy of the quotients `lhs/gcd` and `rhs/gcd`. + pub(crate) const fn quotients(&self) -> (Uint, Uint) { + (self.lhs_on_gcd, self.rhs_on_gcd) + } } impl Odd> { @@ -101,7 +106,10 @@ impl Odd> { /// /// **Warning**: this algorithm is only guaranteed to work for `self` and `rhs` for which the /// msb is **not** set. May panic otherwise. - pub(crate) const fn binxgcd_nz(&self, rhs: &NonZero>) -> OddBinxgcdOutput { + pub(crate) const fn binxgcd_nz( + &self, + rhs: &NonZero>, + ) -> OddBinxgcdUintOutput { // Note that for the `binxgcd` subroutine, `rhs` needs to be odd. // // We use the fact that gcd(a, b) = gcd(a, |a-b|) to @@ -406,9 +414,9 @@ impl Odd> { #[cfg(test)] mod tests { - use core::ops::Div; - use crate::modular::bingcd::xgcd::OddBinxgcdOutput; + use crate::modular::bingcd::xgcd::OddBinxgcdUintOutput; use crate::{ConcatMixed, Gcd, Uint}; + use core::ops::Div; mod test_extract_quotients { use crate::modular::bingcd::matrix::IntMatrix; @@ -630,7 +638,7 @@ mod tests { fn test_xgcd( lhs: Uint, rhs: Uint, - output: OddBinxgcdOutput, + output: OddBinxgcdUintOutput, ) where Uint: Gcd> + ConcatMixed, MixedOutput = Uint>, From 438d3f66e5dd9f3f1f27d0056bc28d36da783e97 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 21 Feb 2025 17:43:17 +0100 Subject: [PATCH 153/203] Refactor `NonZero::Binxgcd` --- src/int/bingcd.rs | 142 +++++++++++++++++++++++++--------------------- 1 file changed, 77 insertions(+), 65 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index 78835f3d7..350bfd3c7 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -6,7 +6,7 @@ use crate::modular::bingcd::tools::const_min; use crate::{ConstChoice, Int, NonZero, Odd, Uint}; pub struct BinXgcdOutput { - gcd: Odd>, + gcd: Uint, x: Int, y: Int, lhs_on_gcd: Int, @@ -19,10 +19,20 @@ impl BinXgcdOutput { (self.lhs_on_gcd, self.rhs_on_gcd) } + /// Provide mutable access to the quotients `lhs.gcd` and `rhs/gcd`. + pub const fn quotients_as_mut(&mut self) -> (&mut Int, &mut Int) { + (&mut self.lhs_on_gcd, &mut self.rhs_on_gcd) + } + /// Return the Bézout coefficients `x` and `y` s.t. `lhs * x + rhs * y = gcd`. pub const fn bezout_coefficients(&self) -> (Int, Int) { (self.x, self.y) } + + /// Provide mutable access to the Bézout coefficients. + pub const fn bezout_coefficients_as_mut(&mut self) -> (&mut Int, &mut Int) { + (&mut self.x, &mut self.y) + } } impl Int { @@ -45,10 +55,12 @@ impl Int { .to_nz() .expect("rhs is non zero by construction"); - let (gcd, mut x, mut y) = self_nz.binxgcd(&rhs_nz); + let output = self_nz.binxgcd(&rhs_nz); + let gcd = output.gcd; + let (mut x, mut y) = output.bezout_coefficients(); // Correct the gcd in case self and/or rhs was zero - let gcd = Uint::select(&rhs.abs(), gcd.as_ref(), self_is_nz); + let gcd = Uint::select(&rhs.abs(), &gcd, self_is_nz); let gcd = Uint::select(&self.abs(), &gcd, rhs_is_nz); // Correct the Bézout coefficients in case self and/or rhs was zero. @@ -72,7 +84,7 @@ impl NonZero> { /// Execute the Binary Extended GCD algorithm. /// /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. - pub const fn binxgcd(&self, rhs: &Self) -> (NonZero>, Int, Int) { + pub const fn binxgcd(&self, rhs: &Self) -> BinXgcdOutput { let (mut lhs, mut rhs) = (*self.as_ref(), *rhs.as_ref()); // Leverage the property that gcd(2^k * a, 2^k *b) = 2^k * gcd(a, b) @@ -89,20 +101,18 @@ impl NonZero> { let lhs = lhs.to_odd().expect("odd by construction"); let rhs = rhs.to_nz().expect("non-zero by construction"); - let output = lhs.binxgcd(&rhs); - let gcd = output.gcd; - let (mut x, mut y) = output.bezout_coefficients(); + let mut output = lhs.binxgcd(&rhs); - Int::conditional_swap(&mut x, &mut y, swap); + // Account for the parameter swap + let (x, y) = output.bezout_coefficients_as_mut(); + Int::conditional_swap(x, y, swap); + let (lhs_on_gcd, rhs_on_gcd) = output.quotients_as_mut(); + Int::conditional_swap(lhs_on_gcd, rhs_on_gcd, swap); - // Add the factor 2^k to the gcd. - let gcd = gcd - .as_ref() - .shl(k) - .to_nz() - .expect("gcd of non-zero element with another element is non-zero"); + // Reintroduce the factor 2^k to the gcd. + output.gcd = output.gcd.shl(k); - (gcd, x, y) + output } } @@ -130,7 +140,7 @@ impl Odd> { let rhs_on_gcd = Int::new_from_abs_sign(abs_rhs_on_gcd, sgn_rhs).expect("no overflow"); BinXgcdOutput { - gcd: output.gcd, + gcd: *output.gcd.as_ref(), x, y, lhs_on_gcd, @@ -141,6 +151,31 @@ impl Odd> { #[cfg(test)] mod test { + use crate::{ConcatMixed, Int, Uint}; + use crate::int::bingcd::BinXgcdOutput; + + fn int_binxgcd_test( + lhs: Int, + rhs: Int, + output: BinXgcdOutput + ) where + Uint: ConcatMixed, MixedOutput = Uint>, + { + let gcd = lhs.bingcd(&rhs); + assert_eq!(gcd, output.gcd); + + // Test quotients + let (lhs_on_gcd, rhs_on_gcd) = output.quotients(); + assert_eq!(lhs_on_gcd, lhs.div_uint(&gcd.to_nz().unwrap())); + assert_eq!(rhs_on_gcd, rhs.div_uint(&gcd.to_nz().unwrap())); + + // Test the Bezout coefficients + let (x, y) = output.bezout_coefficients(); + assert_eq!( + x.widening_mul(&lhs).wrapping_add(&y.widening_mul(&rhs)), + gcd.resize().as_int() + ); + } mod test_int_binxgcd { use crate::{ @@ -219,25 +254,21 @@ mod test { mod test_nonzero_int_binxgcd { use crate::{ - ConcatMixed, Gcd, Int, NonZero, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, + ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64, U768, U8192, }; use rand_core::OsRng; + use crate::int::bingcd::test::int_binxgcd_test; fn nz_int_binxgcd_test( - lhs: NonZero>, - rhs: NonZero>, + lhs: Int, + rhs: Int, ) where Uint: ConcatMixed, MixedOutput = Uint>, Int: Gcd>, { - let gcd = lhs.gcd(&rhs); - let (xgcd, x, y) = lhs.binxgcd(&rhs); - assert_eq!(gcd.to_nz().unwrap(), xgcd); - assert_eq!( - x.widening_mul(&lhs).wrapping_add(&y.widening_mul(&rhs)), - xgcd.resize().as_int() - ); + let output = lhs.to_nz().unwrap().binxgcd(&rhs.to_nz().unwrap()); + int_binxgcd_test(lhs, rhs, output); } fn nz_int_binxgcd_tests() @@ -245,34 +276,27 @@ mod test { Uint: ConcatMixed, MixedOutput = Uint>, Int: Gcd>, { - let nz_min = Int::MIN.to_nz().expect("is nz"); - let nz_minus_one = Int::MINUS_ONE.to_nz().expect("is nz"); - let nz_one = Int::ONE.to_nz().expect("is nz"); - let nz_max = Int::MAX.to_nz().expect("is nz"); - - nz_int_binxgcd_test(nz_min, nz_min); - nz_int_binxgcd_test(nz_min, nz_minus_one); - nz_int_binxgcd_test(nz_min, nz_one); - nz_int_binxgcd_test(nz_min, nz_max); - nz_int_binxgcd_test(nz_one, nz_min); - nz_int_binxgcd_test(nz_one, nz_minus_one); - nz_int_binxgcd_test(nz_one, nz_one); - nz_int_binxgcd_test(nz_one, nz_max); - nz_int_binxgcd_test(nz_max, nz_min); - nz_int_binxgcd_test(nz_max, nz_minus_one); - nz_int_binxgcd_test(nz_max, nz_one); - nz_int_binxgcd_test(nz_max, nz_max); + nz_int_binxgcd_test(Int::MIN, Int::MIN); + nz_int_binxgcd_test(Int::MIN, Int::MINUS_ONE); + nz_int_binxgcd_test(Int::MIN, Int::ONE); + nz_int_binxgcd_test(Int::MIN, Int::MAX); + nz_int_binxgcd_test(Int::MINUS_ONE, Int::MIN); + nz_int_binxgcd_test(Int::MINUS_ONE, Int::MINUS_ONE); + nz_int_binxgcd_test(Int::MINUS_ONE, Int::ONE); + nz_int_binxgcd_test(Int::MINUS_ONE, Int::MAX); + nz_int_binxgcd_test(Int::ONE, Int::MIN); + nz_int_binxgcd_test(Int::ONE, Int::MINUS_ONE); + nz_int_binxgcd_test(Int::ONE, Int::ONE); + nz_int_binxgcd_test(Int::ONE, Int::MAX); + nz_int_binxgcd_test(Int::MAX, Int::MIN); + nz_int_binxgcd_test(Int::MAX, Int::MINUS_ONE); + nz_int_binxgcd_test(Int::MAX, Int::ONE); + nz_int_binxgcd_test(Int::MAX, Int::MAX); let bound = Int::MIN.abs().to_nz().unwrap(); for _ in 0..100 { - let x = Uint::random_mod(&mut OsRng, &bound) - .as_int() - .to_nz() - .unwrap(); - let y = Uint::random_mod(&mut OsRng, &bound) - .as_int() - .to_nz() - .unwrap(); + let x = Uint::random_mod(&mut OsRng, &bound).as_int(); + let y = Uint::random_mod(&mut OsRng, &bound).as_int(); nz_int_binxgcd_test(x, y); } } @@ -297,6 +321,7 @@ mod test { U768, U8192, }; use rand_core::OsRng; + use crate::int::bingcd::test::int_binxgcd_test; fn odd_int_binxgcd_test( lhs: Int, @@ -304,21 +329,8 @@ mod test { ) where Uint: ConcatMixed, MixedOutput = Uint>, { - let gcd = lhs.bingcd(&rhs); let output = lhs.to_odd().unwrap().binxgcd(&rhs.to_nz().unwrap()); - assert_eq!(gcd.to_odd().unwrap(), output.gcd); - - // Test quotients - let (lhs_on_gcd, rhs_on_gcd) = output.quotients(); - assert_eq!(lhs_on_gcd, lhs.div_uint(&gcd.to_nz().unwrap())); - assert_eq!(rhs_on_gcd, rhs.div_uint(&gcd.to_nz().unwrap())); - - // Test the Bezout coefficients - let (x, y) = output.bezout_coefficients(); - assert_eq!( - x.widening_mul(&lhs).wrapping_add(&y.widening_mul(&rhs)), - gcd.resize().as_int() - ); + int_binxgcd_test(lhs, rhs, output); } fn odd_int_binxgcd_tests() From 79e6527a14e4069ac14e05ddb6a39e33b10c480c Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 21 Feb 2025 18:09:03 +0100 Subject: [PATCH 154/203] Refactor `Int::binxgcd` --- src/int/bingcd.rs | 75 ++++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 33 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index 350bfd3c7..2c364842d 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -5,6 +5,7 @@ use crate::modular::bingcd::tools::const_min; use crate::{ConstChoice, Int, NonZero, Odd, Uint}; +#[derive(Debug)] pub struct BinXgcdOutput { gcd: Uint, x: Int, @@ -44,34 +45,41 @@ impl Int { /// Executes the Binary Extended GCD algorithm. /// /// Given `(self, rhs)`, computes `(g, x, y)`, s.t. `self * x + rhs * y = g = gcd(self, rhs)`. - pub const fn binxgcd(&self, rhs: &Self) -> (Uint, Self, Self) { + pub const fn binxgcd(&self, rhs: &Self) -> BinXgcdOutput { // Make sure `self` and `rhs` are nonzero. - let self_is_nz = self.is_nonzero(); - let self_nz = Int::select(&Int::ONE, self, self_is_nz) + let self_is_zero = self.is_nonzero().not(); + let self_nz = Int::select(self, &Int::ONE, self_is_zero) .to_nz() .expect("self is non zero by construction"); - let rhs_is_nz = rhs.is_nonzero(); - let rhs_nz = Int::select(&Int::ONE, rhs, rhs_is_nz) + let rhs_is_zero = rhs.is_nonzero().not(); + let rhs_nz = Int::select(rhs, &Int::ONE, rhs_is_zero) .to_nz() .expect("rhs is non zero by construction"); - let output = self_nz.binxgcd(&rhs_nz); - let gcd = output.gcd; - let (mut x, mut y) = output.bezout_coefficients(); + let mut output = self_nz.binxgcd(&rhs_nz); // Correct the gcd in case self and/or rhs was zero - let gcd = Uint::select(&rhs.abs(), &gcd, self_is_nz); - let gcd = Uint::select(&self.abs(), &gcd, rhs_is_nz); + let gcd = &mut output.gcd; + *gcd = Uint::select(gcd, &rhs.abs(), self_is_zero); + *gcd = Uint::select(gcd, &self.abs(), rhs_is_zero); // Correct the Bézout coefficients in case self and/or rhs was zero. + let (x, y) = output.bezout_coefficients_as_mut(); let signum_self = Int::new_from_abs_sign(Uint::ONE, self.is_negative()).expect("+/- 1"); let signum_rhs = Int::new_from_abs_sign(Uint::ONE, rhs.is_negative()).expect("+/- 1"); - x = Int::select(&Int::ZERO, &x, self_is_nz); - y = Int::select(&signum_rhs, &y, self_is_nz); - x = Int::select(&signum_self, &x, rhs_is_nz); - y = Int::select(&Int::ZERO, &y, rhs_is_nz); + *x = Int::select(&x, &Int::ZERO, self_is_zero); + *y = Int::select(&y, &signum_rhs, self_is_zero); + *x = Int::select(&x, &signum_self, rhs_is_zero); + *y = Int::select(&y, &Int::ZERO, rhs_is_zero); + + // Correct the quotients in case self and/or rhs was zero. + let (lhs_on_gcd, rhs_on_gcd) = output.quotients_as_mut(); + *lhs_on_gcd = Int::select(&lhs_on_gcd, &signum_self, rhs_is_zero); + *lhs_on_gcd = Int::select(&lhs_on_gcd, &Int::ZERO, self_is_zero); + *rhs_on_gcd = Int::select(&rhs_on_gcd, &signum_rhs, self_is_zero); + *rhs_on_gcd = Int::select(&rhs_on_gcd, &Int::ZERO, rhs_is_zero); - (gcd, x, y) + output } } @@ -151,13 +159,14 @@ impl Odd> { #[cfg(test)] mod test { - use crate::{ConcatMixed, Int, Uint}; use crate::int::bingcd::BinXgcdOutput; + use crate::{ConcatMixed, Int, Uint}; + use num_traits::Zero; - fn int_binxgcd_test( + fn binxgcd_test( lhs: Int, rhs: Int, - output: BinXgcdOutput + output: BinXgcdOutput, ) where Uint: ConcatMixed, MixedOutput = Uint>, { @@ -166,8 +175,13 @@ mod test { // Test quotients let (lhs_on_gcd, rhs_on_gcd) = output.quotients(); - assert_eq!(lhs_on_gcd, lhs.div_uint(&gcd.to_nz().unwrap())); - assert_eq!(rhs_on_gcd, rhs.div_uint(&gcd.to_nz().unwrap())); + if gcd.is_zero() { + assert_eq!(lhs_on_gcd, Int::ZERO); + assert_eq!(rhs_on_gcd, Int::ZERO); + } else { + assert_eq!(lhs_on_gcd, lhs.div_uint(&gcd.to_nz().unwrap())); + assert_eq!(rhs_on_gcd, rhs.div_uint(&gcd.to_nz().unwrap())); + } // Test the Bezout coefficients let (x, y) = output.bezout_coefficients(); @@ -178,6 +192,7 @@ mod test { } mod test_int_binxgcd { + use crate::int::bingcd::test::binxgcd_test; use crate::{ ConcatMixed, Gcd, Int, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64, U768, U8192, @@ -191,13 +206,7 @@ mod test { Uint: ConcatMixed, MixedOutput = Uint>, Int: Gcd>, { - let gcd = lhs.gcd(&rhs); - let (xgcd, x, y) = lhs.binxgcd(&rhs); - assert_eq!(gcd, xgcd); - let x_lhs = x.widening_mul(&lhs); - let y_rhs = y.widening_mul(&rhs); - let prod = x_lhs.wrapping_add(&y_rhs); - assert_eq!(prod, xgcd.resize().as_int()); + binxgcd_test(lhs, rhs, lhs.binxgcd(&rhs)) } fn int_binxgcd_tests() @@ -253,12 +262,12 @@ mod test { } mod test_nonzero_int_binxgcd { + use crate::int::bingcd::test::binxgcd_test; use crate::{ - ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, - U4096, U512, U64, U768, U8192, + ConcatMixed, Gcd, Int, RandomMod, Uint, U1024, U128, U192, U2048, U256, U384, U4096, + U512, U64, U768, U8192, }; use rand_core::OsRng; - use crate::int::bingcd::test::int_binxgcd_test; fn nz_int_binxgcd_test( lhs: Int, @@ -268,7 +277,7 @@ mod test { Int: Gcd>, { let output = lhs.to_nz().unwrap().binxgcd(&rhs.to_nz().unwrap()); - int_binxgcd_test(lhs, rhs, output); + binxgcd_test(lhs, rhs, output); } fn nz_int_binxgcd_tests() @@ -316,12 +325,12 @@ mod test { } mod test_odd_int_binxgcd { + use crate::int::bingcd::test::binxgcd_test; use crate::{ ConcatMixed, Int, Random, Uint, U1024, U128, U192, U2048, U256, U384, U4096, U512, U64, U768, U8192, }; use rand_core::OsRng; - use crate::int::bingcd::test::int_binxgcd_test; fn odd_int_binxgcd_test( lhs: Int, @@ -330,7 +339,7 @@ mod test { Uint: ConcatMixed, MixedOutput = Uint>, { let output = lhs.to_odd().unwrap().binxgcd(&rhs.to_nz().unwrap()); - int_binxgcd_test(lhs, rhs, output); + binxgcd_test(lhs, rhs, output); } fn odd_int_binxgcd_tests() From f0bdb39461a39b9128032e69bfd32a36a7b15f0d Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 21 Feb 2025 18:19:03 +0100 Subject: [PATCH 155/203] Introduce `BinXgcdOutput::minimal_bezout_coefficients` --- src/int/bingcd.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index 2c364842d..a01826fe4 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -34,6 +34,31 @@ impl BinXgcdOutput { pub const fn bezout_coefficients_as_mut(&mut self) -> (&mut Int, &mut Int) { (&mut self.x, &mut self.y) } + + /// Obtain a pair of minimal Bézout coefficients. + pub const fn minimal_bezout_coefficients(&self) -> (Int, Int) { + // Attempt to reduce x and y mod rhs_on_gcd and lhs_on_gcd, respectively. + let rhs_on_gcd_is_zero = self.rhs_on_gcd.is_nonzero().not(); + let lhs_on_gcd_is_zero = self.lhs_on_gcd.is_nonzero().not(); + let nz_rhs_on_gcd = Int::select(&self.rhs_on_gcd, &Int::ONE, rhs_on_gcd_is_zero); + let nz_lhs_on_gcd = Int::select(&self.lhs_on_gcd, &Int::ONE, lhs_on_gcd_is_zero); + let mut minimal_x = self.x.rem(&nz_rhs_on_gcd.to_nz().expect("is nz")); + let mut minimal_y = self.y.rem(&nz_lhs_on_gcd.to_nz().expect("is nz")); + + // This trick only needs to be applied whenever lhs/rhs > 1. + minimal_x = Int::select( + &self.x, + &minimal_x, + Uint::gt(&self.rhs_on_gcd.abs(), &Uint::ONE), + ); + minimal_y = Int::select( + &self.y, + &minimal_y, + Uint::gt(&self.lhs_on_gcd.abs(), &Uint::ONE), + ); + + (minimal_x, minimal_y) + } } impl Int { @@ -189,6 +214,17 @@ mod test { x.widening_mul(&lhs).wrapping_add(&y.widening_mul(&rhs)), gcd.resize().as_int() ); + + // Test the minimal Bezout coefficients on minimality + let (x, y) = output.minimal_bezout_coefficients(); + assert!(x.abs() <= rhs_on_gcd.abs() || rhs_on_gcd.is_zero()); + assert!(y.abs() <= lhs_on_gcd.abs() || lhs_on_gcd.is_zero()); + + // Test the minimal Bezout coefficients for correctness + assert_eq!( + x.widening_mul(&lhs).wrapping_add(&y.widening_mul(&rhs)), + gcd.resize().as_int() + ); } mod test_int_binxgcd { From c2df6c0f935d0ee6f9868111d9571915a2dd737a Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 21 Feb 2025 18:19:11 +0100 Subject: [PATCH 156/203] Clean up `xgcd.rs` --- src/modular/bingcd/xgcd.rs | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 675f32677..cca0785b0 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -18,8 +18,6 @@ impl RawBinxgcdOutput { let (x, y) = self.derive_bezout_coefficients(); let (lhs_on_gcd, rhs_on_gcd) = self.extract_quotients(); OddBinxgcdUintOutput { - lhs: self.lhs, - rhs: self.rhs, gcd: self.gcd, x, y, @@ -57,9 +55,7 @@ impl RawBinxgcdOutput { } /// Container for the processed output of the Binary XGCD algorithm. -pub struct OddBinxgcdUintOutput { - lhs: Odd>, - rhs: Odd>, +pub(crate) struct OddBinxgcdUintOutput { pub(crate) gcd: Odd>, x: Int, y: Int, @@ -68,19 +64,6 @@ pub struct OddBinxgcdUintOutput { } impl OddBinxgcdUintOutput { - /// Obtain a pair of minimal Bézout coefficients. - pub(crate) const fn minimal_bezout_coefficients(&self) -> (Int, Int) { - // Attempt to reduce x and y mod rhs_on_gcd and lhs_on_gcd, respectively. - let mut minimal_x = self.x.rem_uint(&self.rhs_on_gcd.to_nz().expect("is nz")); - let mut minimal_y = self.y.rem_uint(&self.lhs_on_gcd.to_nz().expect("is nz")); - - // This trick only works whenever lhs/rhs > 1. Only apply whenever this is the case. - minimal_x = Int::select(&self.x, &minimal_x, Uint::gt(&self.rhs_on_gcd, &Uint::ONE)); - minimal_y = Int::select(&self.y, &minimal_y, Uint::gt(&self.lhs_on_gcd, &Uint::ONE)); - - (minimal_x, minimal_y) - } - /// Obtain a copy of the Bézout coefficients. pub(crate) const fn bezout_coefficients(&self) -> (Int, Int) { (self.x, self.y) @@ -656,17 +639,6 @@ mod tests { x.widening_mul_uint(&lhs) + y.widening_mul_uint(&rhs), output.gcd.resize().as_int(), ); - - // Test the minimal Bezout coefficients for minimality - let (x, y) = output.minimal_bezout_coefficients(); - assert!(x.abs() <= output.rhs_on_gcd, "{} {}", lhs, rhs); - assert!(y.abs() <= output.lhs_on_gcd, "{} {}", lhs, rhs); - - // Test the minimal Bezout coefficients for correctness - assert_eq!( - x.widening_mul_uint(&lhs) + y.widening_mul_uint(&rhs), - output.gcd.resize().as_int(), - ); } mod test_binxgcd_nz { From 7631a24cf00280007f1ff01df9711995d6e00876 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 21 Feb 2025 18:21:02 +0100 Subject: [PATCH 157/203] Fix clippy --- src/int/bingcd.rs | 16 ++++++++-------- src/modular/bingcd/xgcd.rs | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index a01826fe4..6fb909e8a 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -92,17 +92,17 @@ impl Int { let (x, y) = output.bezout_coefficients_as_mut(); let signum_self = Int::new_from_abs_sign(Uint::ONE, self.is_negative()).expect("+/- 1"); let signum_rhs = Int::new_from_abs_sign(Uint::ONE, rhs.is_negative()).expect("+/- 1"); - *x = Int::select(&x, &Int::ZERO, self_is_zero); - *y = Int::select(&y, &signum_rhs, self_is_zero); - *x = Int::select(&x, &signum_self, rhs_is_zero); - *y = Int::select(&y, &Int::ZERO, rhs_is_zero); + *x = Int::select(x, &Int::ZERO, self_is_zero); + *y = Int::select(y, &signum_rhs, self_is_zero); + *x = Int::select(x, &signum_self, rhs_is_zero); + *y = Int::select(y, &Int::ZERO, rhs_is_zero); // Correct the quotients in case self and/or rhs was zero. let (lhs_on_gcd, rhs_on_gcd) = output.quotients_as_mut(); - *lhs_on_gcd = Int::select(&lhs_on_gcd, &signum_self, rhs_is_zero); - *lhs_on_gcd = Int::select(&lhs_on_gcd, &Int::ZERO, self_is_zero); - *rhs_on_gcd = Int::select(&rhs_on_gcd, &signum_rhs, self_is_zero); - *rhs_on_gcd = Int::select(&rhs_on_gcd, &Int::ZERO, rhs_is_zero); + *lhs_on_gcd = Int::select(lhs_on_gcd, &signum_self, rhs_is_zero); + *lhs_on_gcd = Int::select(lhs_on_gcd, &Int::ZERO, self_is_zero); + *rhs_on_gcd = Int::select(rhs_on_gcd, &signum_rhs, self_is_zero); + *rhs_on_gcd = Int::select(rhs_on_gcd, &Int::ZERO, rhs_is_zero); output } diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index cca0785b0..70ed405d7 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -120,8 +120,8 @@ impl Odd> { // ii. gcd = lhs * (v + u) - u * rhs, if rhs is even and rhs < lhs // iii. gcd = lhs * v + rhs * u, if rhs is odd - *v = Int::select(&v, &v.wrapping_sub(&u), rhs_is_even.and(rhs_gt_lhs)); - *v = Int::select(&v, &v.wrapping_add(&u), rhs_is_even.and(rhs_gt_lhs.not())); + *v = Int::select(v, &v.wrapping_sub(u), rhs_is_even.and(rhs_gt_lhs)); + *v = Int::select(v, &v.wrapping_add(u), rhs_is_even.and(rhs_gt_lhs.not())); *u = u.wrapping_neg_if(rhs_is_even.and(rhs_gt_lhs.not())); let mut processed_output = output.process(); @@ -137,8 +137,8 @@ impl Odd> { // ii. gcd = lhs * (x + y) - y * rhs, if rhs is even and rhs < lhs // iii. gcd = lhs * x + rhs * y, if rhs is odd - *x = Int::select(&x, &x.wrapping_sub(&y), rhs_is_even.and(rhs_gt_lhs)); - *x = Int::select(&x, &x.wrapping_add(&y), rhs_is_even.and(rhs_gt_lhs.not())); + *x = Int::select(x, &x.wrapping_sub(y), rhs_is_even.and(rhs_gt_lhs)); + *x = Int::select(x, &x.wrapping_add(y), rhs_is_even.and(rhs_gt_lhs.not())); *y = y.wrapping_neg_if(rhs_is_even.and(rhs_gt_lhs.not())); processed_output From 2f35945a288393797b6ace2cec34366b80e71cd7 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 25 Feb 2025 08:56:56 +0100 Subject: [PATCH 158/203] Fix doc --- src/modular/bingcd/xgcd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 70ed405d7..6777a27f2 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -13,7 +13,7 @@ pub(crate) struct RawBinxgcdOutput { } impl RawBinxgcdOutput { - /// Process raw output, constructing an OddBinXgcdOutput object. + /// Process raw output, constructing an [OddBinxgcdUintOutput] object. const fn process(&self) -> OddBinxgcdUintOutput { let (x, y) = self.derive_bezout_coefficients(); let (lhs_on_gcd, rhs_on_gcd) = self.extract_quotients(); From 292b48b38d22b4e0a0a43cf76224e4fd3e63757a Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 3 Mar 2025 12:25:30 +0100 Subject: [PATCH 159/203] Minor `div_2_mod_q` speedup --- src/modular/bingcd/tools.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/modular/bingcd/tools.rs b/src/modular/bingcd/tools.rs index 5255516ae..ce7588565 100644 --- a/src/modular/bingcd/tools.rs +++ b/src/modular/bingcd/tools.rs @@ -48,8 +48,7 @@ impl Uint { #[inline] const fn div_2_mod_q(self, half_mod_q: &Self) -> Self { // Floor-divide self by 2. When self was odd, add back 1/2 mod q. - let add_one_half = self.is_odd(); - let floored_half = self.shr_vartime(1); + let (floored_half, add_one_half) = self.shr1_with_carry(); floored_half.wrapping_add(&Self::select(&Self::ZERO, half_mod_q, add_one_half)) } From c6f4cd21ab629b55d5ae04820e48b4fb53aba5ed Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 21 Mar 2025 18:34:24 +0100 Subject: [PATCH 160/203] Restrict `IntMatrix` element access --- src/modular/bingcd/matrix.rs | 35 +++++++++++++++++++++++++---------- src/modular/bingcd/xgcd.rs | 10 ++++++---- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/modular/bingcd/matrix.rs b/src/modular/bingcd/matrix.rs index a45730c7e..a83148b60 100644 --- a/src/modular/bingcd/matrix.rs +++ b/src/modular/bingcd/matrix.rs @@ -5,10 +5,10 @@ type Vector = (T, T); #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) struct IntMatrix { - pub(crate) m00: Int, - pub(crate) m01: Int, - pub(crate) m10: Int, - pub(crate) m11: Int, + m00: Int, + m01: Int, + m10: Int, + m11: Int, } impl IntMatrix { @@ -24,6 +24,21 @@ impl IntMatrix { Self { m00, m01, m10, m11 } } + pub(crate) const fn as_elements(&self) -> (&Int, &Int, &Int, &Int) { + (&self.m00, &self.m01, &self.m10, &self.m11) + } + + pub(crate) const fn as_mut_elements( + &mut self, + ) -> ( + &mut Int, + &mut Int, + &mut Int, + &mut Int, + ) { + (&mut self.m00, &mut self.m01, &mut self.m10, &mut self.m11) + } + /// Apply this matrix to a vector of [Uint]s, returning the result as a vector of /// [ExtendedInt]s. #[inline] @@ -107,13 +122,13 @@ impl IntMatrix { #[cfg(test)] mod tests { use crate::modular::bingcd::matrix::IntMatrix; - use crate::{ConstChoice, I256, Int, U256}; + use crate::{ConstChoice, I256, Int}; - const X: IntMatrix<{ U256::LIMBS }> = IntMatrix::new( - Int::from_i64(1i64), - Int::from_i64(7i64), - Int::from_i64(23i64), - Int::from_i64(53i64), + const X: IntMatrix<4> = IntMatrix::new( + I256::from_i64(1i64), + I256::from_i64(7i64), + I256::from_i64(23i64), + I256::from_i64(53i64), ); #[test] diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index f3e8780c8..63a5bf666 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -34,7 +34,7 @@ impl RawBinxgcdOutput { // Hence, we can compute // `x = matrix.m00 / 2^k mod rhs`, and // `y = matrix.m01 / 2^k mod lhs`. - let (x, y) = (self.matrix.m00, self.matrix.m01); + let (x, y, ..) = self.matrix.as_elements(); ( x.div_2k_mod_q(self.k, self.k_upper_bound, &self.rhs), y.div_2k_mod_q(self.k, self.k_upper_bound, &self.lhs), @@ -43,13 +43,15 @@ impl RawBinxgcdOutput { /// Mutably borrow the quotients `lhs/gcd` and `rhs/gcd`. const fn quotients_as_mut(&mut self) -> (&mut Int, &mut Int) { - (&mut self.matrix.m11, &mut self.matrix.m10) + let (.., m10, m11) = self.matrix.as_mut_elements(); + (m11, m10) } /// Extract the quotients `lhs/gcd` and `rhs/gcd` from `matrix`. const fn extract_quotients(&self) -> (Uint, Uint) { - let lhs_on_gcd = self.matrix.m11.abs(); - let rhs_on_gcd = self.matrix.m10.abs(); + let (.., m10, m11) = self.matrix.as_elements(); + let lhs_on_gcd = m11.abs(); + let rhs_on_gcd = m10.abs(); (lhs_on_gcd, rhs_on_gcd) } } From f5bf85bf9eb36aeaa707425b617a4e3e31fbacb8 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 24 Mar 2025 10:58:07 +0100 Subject: [PATCH 161/203] Improve `test_partial_binxgcd_constructs_correct_matrix` test --- src/modular/bingcd/extension.rs | 1 + src/modular/bingcd/xgcd.rs | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/modular/bingcd/extension.rs b/src/modular/bingcd/extension.rs index 5b651f7d6..883b27d93 100644 --- a/src/modular/bingcd/extension.rs +++ b/src/modular/bingcd/extension.rs @@ -81,6 +81,7 @@ impl ExtendedUint { } } +#[derive(Debug)] pub(crate) struct ExtendedInt( Uint, Uint, diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 63a5bf666..83459ba4f 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -590,15 +590,21 @@ mod tests { #[test] fn test_partial_binxgcd_constructs_correct_matrix() { + let target_a = U64::from_be_hex("1CB3FB3FA1218FDB").to_odd().unwrap(); + let target_b = U64::from_be_hex("0EA028AF0F8966B6"); + let (new_a, new_b, matrix, _) = A.partial_binxgcd_vartime::<{ U64::LIMBS }>(&B, 5, ConstChoice::TRUE); + assert_eq!(new_a, target_a); + assert_eq!(new_b, target_b); + let (computed_a, computed_b) = matrix.extended_apply_to((A.get(), B)); let computed_a = computed_a.div_2k(5).wrapping_drop_extension().0; let computed_b = computed_b.div_2k(5).wrapping_drop_extension().0; - assert_eq!(new_a.get(), computed_a); - assert_eq!(new_b, computed_b); + assert_eq!(computed_a, target_a); + assert_eq!(computed_b, target_b); } const SMALL_A: Odd = U64::from_be_hex("0000000003CD6A1F").to_odd().expect("odd"); From e506a1fb29b6ccdcc0e9a39edf04b79929216d2f Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 24 Mar 2025 14:22:52 +0100 Subject: [PATCH 162/203] Rename `IntMatrix` as `BinXgcdMatrix` --- src/modular/bingcd/matrix.rs | 22 ++++++++++---------- src/modular/bingcd/xgcd.rs | 40 ++++++++++++++++++------------------ 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/modular/bingcd/matrix.rs b/src/modular/bingcd/matrix.rs index a83148b60..2cea93f4e 100644 --- a/src/modular/bingcd/matrix.rs +++ b/src/modular/bingcd/matrix.rs @@ -4,14 +4,14 @@ use crate::{ConstChoice, Int, Uint}; type Vector = (T, T); #[derive(Debug, Clone, Copy, PartialEq)] -pub(crate) struct IntMatrix { +pub(crate) struct BinXgcdMatrix { m00: Int, m01: Int, m10: Int, m11: Int, } -impl IntMatrix { +impl BinXgcdMatrix { /// The unit matrix. pub(crate) const UNIT: Self = Self::new(Int::ONE, Int::ZERO, Int::ZERO, Int::ONE); @@ -58,8 +58,8 @@ impl IntMatrix { #[inline] pub(crate) const fn wrapping_mul_right( &self, - rhs: &IntMatrix, - ) -> IntMatrix { + rhs: &BinXgcdMatrix, + ) -> BinXgcdMatrix { let a0 = rhs.m00.wrapping_mul(&self.m00); let a1 = rhs.m10.wrapping_mul(&self.m01); let a = a0.wrapping_add(&a1); @@ -72,7 +72,7 @@ impl IntMatrix { let d0 = rhs.m01.wrapping_mul(&self.m10); let d1 = rhs.m11.wrapping_mul(&self.m11); let d = d0.wrapping_add(&d1); - IntMatrix::new(a, b, c, d) + BinXgcdMatrix::new(a, b, c, d) } /// Swap the rows of this matrix if `swap` is truthy. Otherwise, do nothing. @@ -121,10 +121,10 @@ impl IntMatrix { #[cfg(test)] mod tests { - use crate::modular::bingcd::matrix::IntMatrix; + use crate::modular::bingcd::matrix::BinXgcdMatrix; use crate::{ConstChoice, I256, Int}; - const X: IntMatrix<4> = IntMatrix::new( + const X: BinXgcdMatrix<4> = BinXgcdMatrix::new( I256::from_i64(1i64), I256::from_i64(7i64), I256::from_i64(23i64), @@ -139,7 +139,7 @@ mod tests { y.conditional_swap_rows(ConstChoice::TRUE); assert_eq!( y, - IntMatrix::new( + BinXgcdMatrix::new( Int::from(23i32), Int::from(53i32), Int::from(1i32), @@ -156,7 +156,7 @@ mod tests { y.conditional_subtract_bottom_row_from_top(ConstChoice::TRUE); assert_eq!( y, - IntMatrix::new( + BinXgcdMatrix::new( Int::from(-22i32), Int::from(-46i32), Int::from(23i32), @@ -173,7 +173,7 @@ mod tests { y.conditional_double_bottom_row(ConstChoice::TRUE); assert_eq!( y, - IntMatrix::new( + BinXgcdMatrix::new( Int::from(1i32), Int::from(7i32), Int::from(46i32), @@ -187,7 +187,7 @@ mod tests { let res = X.wrapping_mul_right(&X); assert_eq!( res, - IntMatrix::new( + BinXgcdMatrix::new( I256::from_i64(162i64), I256::from_i64(378i64), I256::from_i64(1242i64), diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 83459ba4f..23106a778 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -1,4 +1,4 @@ -use crate::modular::bingcd::matrix::IntMatrix; +use crate::modular::bingcd::matrix::BinXgcdMatrix; use crate::modular::bingcd::tools::const_max; use crate::{ConstChoice, Int, NonZero, Odd, U64, U128, Uint}; @@ -7,7 +7,7 @@ pub(crate) struct RawBinxgcdOutput { lhs: Odd>, rhs: Odd>, gcd: Odd>, - matrix: IntMatrix, + matrix: BinXgcdMatrix, k: u32, k_upper_bound: u32, } @@ -238,7 +238,7 @@ impl Odd> { rhs: &Self, ) -> RawBinxgcdOutput { let (mut a, mut b) = (*self.as_ref(), *rhs.as_ref()); - let mut matrix = IntMatrix::UNIT; + let mut matrix = BinXgcdMatrix::UNIT; let mut total_doublings = 0; let (mut a_sgn, mut b_sgn); @@ -307,10 +307,10 @@ impl Odd> { rhs: &Uint, iterations: u32, halt_at_zero: ConstChoice, - ) -> (Self, Uint, IntMatrix, u32) { + ) -> (Self, Uint, BinXgcdMatrix, u32) { let (mut a, mut b) = (*self.as_ref(), *rhs); // This matrix corresponds with (f0, g0, f1, g1) in the paper. - let mut matrix = IntMatrix::UNIT; + let mut matrix = BinXgcdMatrix::UNIT; // Compute the update matrix. // Note: to be consistent with the paper, the `binxgcd_step` algorithm requires the second @@ -368,7 +368,7 @@ impl Odd> { const fn binxgcd_step( a: &mut Uint, b: &mut Uint, - matrix: &mut IntMatrix, + matrix: &mut BinXgcdMatrix, executed_iterations: &mut u32, halt_at_zero: ConstChoice, ) { @@ -406,12 +406,12 @@ mod tests { use rand_core::SeedableRng; mod test_extract_quotients { - use crate::modular::bingcd::matrix::IntMatrix; + use crate::modular::bingcd::matrix::BinXgcdMatrix; use crate::modular::bingcd::xgcd::RawBinxgcdOutput; use crate::{Int, U64, Uint}; fn raw_binxgcdoutput_setup( - matrix: IntMatrix, + matrix: BinXgcdMatrix, ) -> RawBinxgcdOutput { RawBinxgcdOutput { lhs: Uint::::ONE.to_odd().unwrap(), @@ -425,7 +425,7 @@ mod tests { #[test] fn test_extract_quotients_unit() { - let output = raw_binxgcdoutput_setup(IntMatrix::<{ U64::LIMBS }>::UNIT); + let output = raw_binxgcdoutput_setup(BinXgcdMatrix::<{ U64::LIMBS }>::UNIT); let (lhs_on_gcd, rhs_on_gcd) = output.extract_quotients(); assert_eq!(lhs_on_gcd, Uint::ONE); assert_eq!(rhs_on_gcd, Uint::ZERO); @@ -433,7 +433,7 @@ mod tests { #[test] fn test_extract_quotients_basic() { - let output = raw_binxgcdoutput_setup(IntMatrix::<{ U64::LIMBS }>::new( + let output = raw_binxgcdoutput_setup(BinXgcdMatrix::<{ U64::LIMBS }>::new( Int::ZERO, Int::ZERO, Int::from(5i32), @@ -443,7 +443,7 @@ mod tests { assert_eq!(lhs_on_gcd, Uint::from(7u32)); assert_eq!(rhs_on_gcd, Uint::from(5u32)); - let output = raw_binxgcdoutput_setup(IntMatrix::<{ U64::LIMBS }>::new( + let output = raw_binxgcdoutput_setup(BinXgcdMatrix::<{ U64::LIMBS }>::new( Int::ZERO, Int::ZERO, Int::from(-7i32), @@ -456,7 +456,7 @@ mod tests { } mod test_derive_bezout_coefficients { - use crate::modular::bingcd::matrix::IntMatrix; + use crate::modular::bingcd::matrix::BinXgcdMatrix; use crate::modular::bingcd::xgcd::RawBinxgcdOutput; use crate::{I64, Int, U64, Uint}; @@ -466,7 +466,7 @@ mod tests { lhs: Uint::ONE.to_odd().unwrap(), rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), - matrix: IntMatrix::<{ U64::LIMBS }>::UNIT, + matrix: BinXgcdMatrix::<{ U64::LIMBS }>::UNIT, k: 0, k_upper_bound: 0, }; @@ -481,7 +481,7 @@ mod tests { lhs: Uint::ONE.to_odd().unwrap(), rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), - matrix: IntMatrix::new( + matrix: BinXgcdMatrix::new( I64::from(2i32), I64::from(3i32), I64::from(4i32), @@ -498,7 +498,7 @@ mod tests { lhs: Uint::ONE.to_odd().unwrap(), rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), - matrix: IntMatrix::new( + matrix: BinXgcdMatrix::new( I64::from(2i32), I64::from(3i32), I64::from(4i32), @@ -518,7 +518,7 @@ mod tests { lhs: Uint::ONE.to_odd().unwrap(), rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), - matrix: IntMatrix::new( + matrix: BinXgcdMatrix::new( I64::from(2i32), I64::from(6i32), I64::from(4i32), @@ -535,7 +535,7 @@ mod tests { lhs: Uint::ONE.to_odd().unwrap(), rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), - matrix: IntMatrix::new( + matrix: BinXgcdMatrix::new( I64::from(120i32), I64::from(64i32), I64::from(4i32), @@ -555,7 +555,7 @@ mod tests { lhs: Uint::from(7u32).to_odd().unwrap(), rhs: Uint::from(5u32).to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), - matrix: IntMatrix::new( + matrix: BinXgcdMatrix::new( I64::from(2i32), I64::from(6i32), I64::from(4i32), @@ -571,7 +571,7 @@ mod tests { } mod test_partial_binxgcd { - use crate::modular::bingcd::matrix::IntMatrix; + use crate::modular::bingcd::matrix::BinXgcdMatrix; use crate::{ConstChoice, I64, Odd, U64}; const A: Odd = U64::from_be_hex("CA048AFA63CD6A1F").to_odd().expect("odd"); @@ -584,7 +584,7 @@ mod tests { assert_eq!(iters, 5); assert_eq!( matrix, - IntMatrix::new(I64::from(8), I64::from(-4), I64::from(-2), I64::from(5)) + BinXgcdMatrix::new(I64::from(8), I64::from(-4), I64::from(-2), I64::from(5)) ); } From 2df56729770ce7b438a38e9e0dcc7c315e096609 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 24 Mar 2025 14:53:36 +0100 Subject: [PATCH 163/203] Absorb `k` and `k_upper_bound` into `BinXgcdMatrix` --- src/modular/bingcd/extension.rs | 31 +------- src/modular/bingcd/gcd.rs | 6 +- src/modular/bingcd/matrix.rs | 121 ++++++++++++++++++++++++++++---- src/modular/bingcd/xgcd.rs | 39 +++++++--- 4 files changed, 142 insertions(+), 55 deletions(-) diff --git a/src/modular/bingcd/extension.rs b/src/modular/bingcd/extension.rs index 883b27d93..6dcab9959 100644 --- a/src/modular/bingcd/extension.rs +++ b/src/modular/bingcd/extension.rs @@ -57,31 +57,9 @@ impl ExtendedUint { Self(lo, hi) } - - /// Vartime equivalent of [Self::shr]. - #[inline] - pub const fn shr_vartime(&self, shift: u32) -> Self { - debug_assert!(shift <= Uint::::BITS); - - let shift_is_zero = ConstChoice::from_u32_eq(shift, 0); - let left_shift = shift_is_zero.select_u32(Uint::::BITS - shift, 0); - - let hi = self.1.shr_vartime(shift); - let carry = Uint::select(&self.1, &Uint::ZERO, shift_is_zero).wrapping_shl(left_shift); - let mut lo = self.0.shr_vartime(shift); - - // Apply carry - let limb_diff = LIMBS.wrapping_sub(EXTRA) as u32; - // safe to vartime; shr_vartime is variable in the value of shift only. Since this shift - // is a public constant, the constant time property of this algorithm is not impacted. - let carry = carry.resize::().shl_vartime(limb_diff * Limb::BITS); - lo = lo.bitxor(&carry); - - Self(lo, hi) - } } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub(crate) struct ExtendedInt( Uint, Uint, @@ -142,11 +120,4 @@ impl ExtendedInt { let (abs, sgn) = self.abs_sgn(); abs.shr(k).wrapping_neg_if(sgn).as_extended_int() } - - /// Divide self by `2^k`, rounding towards zero. - #[inline] - pub const fn div_2k_vartime(&self, k: u32) -> Self { - let (abs, sgn) = self.abs_sgn(); - abs.shr_vartime(k).wrapping_neg_if(sgn).as_extended_int() - } } diff --git a/src/modular/bingcd/gcd.rs b/src/modular/bingcd/gcd.rs index 0e70d4d37..83649b0d3 100644 --- a/src/modular/bingcd/gcd.rs +++ b/src/modular/bingcd/gcd.rs @@ -107,9 +107,9 @@ impl Odd> { .partial_binxgcd_vartime::(&b_, K - 1, ConstChoice::FALSE); // Update `a` and `b` using the update matrix - let (updated_a, updated_b) = matrix.extended_apply_to((a, b)); - (a, _) = updated_a.div_2k_vartime(K - 1).wrapping_drop_extension(); - (b, _) = updated_b.div_2k_vartime(K - 1).wrapping_drop_extension(); + let (updated_a, updated_b) = matrix.wrapping_apply_to((a, b)); + (a, _) = updated_a.wrapping_drop_extension(); + (b, _) = updated_b.wrapping_drop_extension(); } a.to_odd() diff --git a/src/modular/bingcd/matrix.rs b/src/modular/bingcd/matrix.rs index 2cea93f4e..dba71da49 100644 --- a/src/modular/bingcd/matrix.rs +++ b/src/modular/bingcd/matrix.rs @@ -3,46 +3,85 @@ use crate::{ConstChoice, Int, Uint}; type Vector = (T, T); +/// Matrix used to compute the Extended GCD using the Binary Extended GCD algorithm. +/// +/// The internal state represents the matrix +/// ```text +/// [ m00 m01 ] +/// [ m10 m11 ] / 2^k +/// ``` +/// +/// Since some of the operations conditionally increase `k`, this struct furthermore keeps track of +/// `k_upper_bound`; an upper bound on the value of `k`. #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) struct BinXgcdMatrix { m00: Int, m01: Int, m10: Int, m11: Int, + k: u32, + k_upper_bound: u32, } impl BinXgcdMatrix { /// The unit matrix. - pub(crate) const UNIT: Self = Self::new(Int::ONE, Int::ZERO, Int::ZERO, Int::ONE); + pub(crate) const UNIT: Self = Self::new(Int::ONE, Int::ZERO, Int::ZERO, Int::ONE, 0, 0); pub(crate) const fn new( m00: Int, m01: Int, m10: Int, m11: Int, + k: u32, + k_upper_bound: u32, ) -> Self { - Self { m00, m01, m10, m11 } + Self { + m00, + m01, + m10, + m11, + k, + k_upper_bound, + } } - pub(crate) const fn as_elements(&self) -> (&Int, &Int, &Int, &Int) { - (&self.m00, &self.m01, &self.m10, &self.m11) + pub(crate) const fn as_elements( + &self, + ) -> (&Int, &Int, &Int, &Int, u32, u32) { + ( + &self.m00, + &self.m01, + &self.m10, + &self.m11, + self.k, + self.k_upper_bound, + ) } - pub(crate) const fn as_mut_elements( + pub(crate) const fn as_elements_mut( &mut self, ) -> ( &mut Int, &mut Int, &mut Int, &mut Int, + &mut u32, + &mut u32, ) { - (&mut self.m00, &mut self.m01, &mut self.m10, &mut self.m11) + ( + &mut self.m00, + &mut self.m01, + &mut self.m10, + &mut self.m11, + &mut self.k, + &mut self.k_upper_bound, + ) } /// Apply this matrix to a vector of [Uint]s, returning the result as a vector of /// [ExtendedInt]s. #[inline] - pub(crate) const fn extended_apply_to( + pub(crate) const fn wrapping_apply_to( &self, vec: Vector>, ) -> Vector> { @@ -51,7 +90,10 @@ impl BinXgcdMatrix { let a1 = ExtendedInt::from_product(a, self.m10); let b0 = ExtendedInt::from_product(b, self.m01); let b1 = ExtendedInt::from_product(b, self.m11); - (a0.wrapping_add(&b0), a1.wrapping_add(&b1)) + ( + a0.wrapping_add(&b0).div_2k(self.k), + a1.wrapping_add(&b1).div_2k(self.k), + ) } /// Wrapping apply this matrix to `rhs`. Return the result in `RHS_LIMBS`. @@ -72,7 +114,14 @@ impl BinXgcdMatrix { let d0 = rhs.m01.wrapping_mul(&self.m10); let d1 = rhs.m11.wrapping_mul(&self.m11); let d = d0.wrapping_add(&d1); - BinXgcdMatrix::new(a, b, c, d) + BinXgcdMatrix::new( + a, + b, + c, + d, + self.k + rhs.k, + self.k_upper_bound + rhs.k_upper_bound, + ) } /// Swap the rows of this matrix if `swap` is truthy. Otherwise, do nothing. @@ -102,6 +151,8 @@ impl BinXgcdMatrix { // is a public constant, the constant time property of this algorithm is not impacted. self.m10 = Int::select(&self.m10, &self.m10.shl_vartime(1), double); self.m11 = Int::select(&self.m11, &self.m11.shl_vartime(1), double); + self.k = double.select_u32(self.k, self.k + 1); + self.k_upper_bound += 1; } /// Negate the elements in the top row if `negate` is truthy. Otherwise, do nothing. @@ -122,15 +173,41 @@ impl BinXgcdMatrix { #[cfg(test)] mod tests { use crate::modular::bingcd::matrix::BinXgcdMatrix; - use crate::{ConstChoice, I256, Int}; + use crate::{ConstChoice, I64, I256, Int, U64, Uint}; const X: BinXgcdMatrix<4> = BinXgcdMatrix::new( I256::from_i64(1i64), I256::from_i64(7i64), I256::from_i64(23i64), I256::from_i64(53i64), + 1, + 2, ); + #[test] + fn test_wrapping_apply_to() { + let a = U64::from_be_hex("CA048AFA63CD6A1F"); + let b = U64::from_be_hex("AE693BF7BE8E5566"); + let matrix = BinXgcdMatrix { + m00: I64::from_be_hex("0000000000000120"), + m01: I64::from_be_hex("FFFFFFFFFFFFFF30"), + m10: I64::from_be_hex("FFFFFFFFFFFFFECA"), + m11: I64::from_be_hex("00000000000002A7"), + k: 17, + k_upper_bound: 17, + }; + + let (a_, b_) = matrix.wrapping_apply_to((a, b)); + assert_eq!( + a_.wrapping_drop_extension().0, + Uint::from_be_hex("002AC7CDD032B9B9") + ); + assert_eq!( + b_.wrapping_drop_extension().0, + Uint::from_be_hex("006CFBCEE172C863") + ); + } + #[test] fn test_conditional_swap() { let mut y = X.clone(); @@ -143,7 +220,9 @@ mod tests { Int::from(23i32), Int::from(53i32), Int::from(1i32), - Int::from(7i32) + Int::from(7i32), + 1, + 2 ) ); } @@ -160,7 +239,9 @@ mod tests { Int::from(-22i32), Int::from(-46i32), Int::from(23i32), - Int::from(53i32) + Int::from(53i32), + 1, + 2 ) ); } @@ -169,7 +250,17 @@ mod tests { fn test_conditional_double() { let mut y = X.clone(); y.conditional_double_bottom_row(ConstChoice::FALSE); - assert_eq!(y, X); + assert_eq!( + y, + BinXgcdMatrix::new( + Int::from(1i32), + Int::from(7i32), + Int::from(23i32), + Int::from(53i32), + 1, + 3 + ) + ); y.conditional_double_bottom_row(ConstChoice::TRUE); assert_eq!( y, @@ -178,6 +269,8 @@ mod tests { Int::from(7i32), Int::from(46i32), Int::from(106i32), + 2, + 4 ) ); } @@ -192,6 +285,8 @@ mod tests { I256::from_i64(378i64), I256::from_i64(1242i64), I256::from_i64(2970i64), + 2, + 4 ) ) } diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 23106a778..aa10270da 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -43,13 +43,13 @@ impl RawBinxgcdOutput { /// Mutably borrow the quotients `lhs/gcd` and `rhs/gcd`. const fn quotients_as_mut(&mut self) -> (&mut Int, &mut Int) { - let (.., m10, m11) = self.matrix.as_mut_elements(); + let (.., m10, m11, _, _) = self.matrix.as_elements_mut(); (m11, m10) } /// Extract the quotients `lhs/gcd` and `rhs/gcd` from `matrix`. const fn extract_quotients(&self) -> (Uint, Uint) { - let (.., m10, m11) = self.matrix.as_elements(); + let (.., m10, m11, _, _) = self.matrix.as_elements(); let lhs_on_gcd = m11.abs(); let rhs_on_gcd = m10.abs(); (lhs_on_gcd, rhs_on_gcd) @@ -261,9 +261,9 @@ impl Odd> { .partial_binxgcd_vartime::(&b_, K - 1, b_fits_in_compact); // Update `a` and `b` using the update matrix - let (updated_a, updated_b) = update_matrix.extended_apply_to((a, b)); - (a, a_sgn) = updated_a.div_2k(doublings).wrapping_drop_extension(); - (b, b_sgn) = updated_b.div_2k(doublings).wrapping_drop_extension(); + let (updated_a, updated_b) = update_matrix.wrapping_apply_to((a, b)); + (a, a_sgn) = updated_a.wrapping_drop_extension(); + (b, b_sgn) = updated_b.wrapping_drop_extension(); matrix = update_matrix.wrapping_mul_right(&matrix); matrix.conditional_negate_top_row(a_sgn); @@ -438,6 +438,8 @@ mod tests { Int::ZERO, Int::from(5i32), Int::from(-7i32), + 0, + 0, )); let (lhs_on_gcd, rhs_on_gcd) = output.extract_quotients(); assert_eq!(lhs_on_gcd, Uint::from(7u32)); @@ -448,6 +450,8 @@ mod tests { Int::ZERO, Int::from(-7i32), Int::from(5i32), + 0, + 0, )); let (lhs_on_gcd, rhs_on_gcd) = output.extract_quotients(); assert_eq!(lhs_on_gcd, Uint::from(5u32)); @@ -486,6 +490,8 @@ mod tests { I64::from(3i32), I64::from(4i32), I64::from(5i32), + 0, + 0, ), k: 0, k_upper_bound: 0, @@ -503,6 +509,8 @@ mod tests { I64::from(3i32), I64::from(4i32), I64::from(5i32), + 0, + 1, ), k: 0, k_upper_bound: 1, @@ -523,6 +531,8 @@ mod tests { I64::from(6i32), I64::from(4i32), I64::from(5i32), + 1, + 1, ), k: 1, k_upper_bound: 1, @@ -540,6 +550,8 @@ mod tests { I64::from(64i32), I64::from(4i32), I64::from(5i32), + 5, + 6, ), k: 5, k_upper_bound: 6, @@ -560,6 +572,8 @@ mod tests { I64::from(6i32), I64::from(4i32), I64::from(5i32), + 3, + 7, ), k: 3, k_upper_bound: 7, @@ -584,7 +598,14 @@ mod tests { assert_eq!(iters, 5); assert_eq!( matrix, - BinXgcdMatrix::new(I64::from(8), I64::from(-4), I64::from(-2), I64::from(5)) + BinXgcdMatrix::new( + I64::from(8), + I64::from(-4), + I64::from(-2), + I64::from(5), + 5, + 5 + ) ); } @@ -599,9 +620,9 @@ mod tests { assert_eq!(new_a, target_a); assert_eq!(new_b, target_b); - let (computed_a, computed_b) = matrix.extended_apply_to((A.get(), B)); - let computed_a = computed_a.div_2k(5).wrapping_drop_extension().0; - let computed_b = computed_b.div_2k(5).wrapping_drop_extension().0; + let (computed_a, computed_b) = matrix.wrapping_apply_to((A.get(), B)); + let computed_a = computed_a.wrapping_drop_extension().0; + let computed_b = computed_b.wrapping_drop_extension().0; assert_eq!(computed_a, target_a); assert_eq!(computed_b, target_b); From 01288a5332a2738a3f746c609d89600635a95008 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 24 Mar 2025 15:00:21 +0100 Subject: [PATCH 164/203] Remove external `doublings` counting --- src/modular/bingcd/gcd.rs | 2 +- src/modular/bingcd/xgcd.rs | 67 ++++++++++++-------------------------- 2 files changed, 21 insertions(+), 48 deletions(-) diff --git a/src/modular/bingcd/gcd.rs b/src/modular/bingcd/gcd.rs index 83649b0d3..da85aae9c 100644 --- a/src/modular/bingcd/gcd.rs +++ b/src/modular/bingcd/gcd.rs @@ -101,7 +101,7 @@ impl Odd> { // Compute the K-1 iteration update matrix from a_ and b_ // Safe to vartime; function executes in time variable in `iterations` only, which is // a public constant K-1 here. - let (.., matrix, _) = a_ + let (.., matrix) = a_ .to_odd() .expect("a_ is always odd") .partial_binxgcd_vartime::(&b_, K - 1, ConstChoice::FALSE); diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index aa10270da..c48c718b2 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -8,8 +8,6 @@ pub(crate) struct RawBinxgcdOutput { rhs: Odd>, gcd: Odd>, matrix: BinXgcdMatrix, - k: u32, - k_upper_bound: u32, } impl RawBinxgcdOutput { @@ -34,10 +32,10 @@ impl RawBinxgcdOutput { // Hence, we can compute // `x = matrix.m00 / 2^k mod rhs`, and // `y = matrix.m01 / 2^k mod lhs`. - let (x, y, ..) = self.matrix.as_elements(); + let (x, y, .., k, k_upper_bound) = self.matrix.as_elements(); ( - x.div_2k_mod_q(self.k, self.k_upper_bound, &self.rhs), - y.div_2k_mod_q(self.k, self.k_upper_bound, &self.lhs), + x.div_2k_mod_q(k, k_upper_bound, &self.rhs), + y.div_2k_mod_q(k, k_upper_bound, &self.lhs), ) } @@ -173,7 +171,7 @@ impl Odd> { /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 1. /// . pub(crate) const fn classic_binxgcd(&self, rhs: &Self) -> RawBinxgcdOutput { - let (gcd, _, matrix, total_doublings) = self.partial_binxgcd_vartime::( + let (gcd, _, matrix) = self.partial_binxgcd_vartime::( rhs.as_ref(), Self::MIN_BINGCD_ITERATIONS, ConstChoice::TRUE, @@ -184,8 +182,6 @@ impl Odd> { rhs: *rhs, gcd, matrix, - k: total_doublings, - k_upper_bound: Self::MIN_BINGCD_ITERATIONS, } } @@ -239,7 +235,6 @@ impl Odd> { ) -> RawBinxgcdOutput { let (mut a, mut b) = (*self.as_ref(), *rhs.as_ref()); let mut matrix = BinXgcdMatrix::UNIT; - let mut total_doublings = 0; let (mut a_sgn, mut b_sgn); let mut i = 0; @@ -255,7 +250,7 @@ impl Odd> { ConstChoice::from_u32_le(b_bits, K - 1).or(ConstChoice::from_u32_eq(n, 2 * K)); // Compute the K-1 iteration update matrix from a_ and b_ - let (.., update_matrix, doublings) = a_ + let (.., update_matrix) = a_ .to_odd() .expect("a is always odd") .partial_binxgcd_vartime::(&b_, K - 1, b_fits_in_compact); @@ -268,7 +263,6 @@ impl Odd> { matrix = update_matrix.wrapping_mul_right(&matrix); matrix.conditional_negate_top_row(a_sgn); matrix.conditional_negate_bottom_row(b_sgn); - total_doublings += doublings; } let gcd = a @@ -280,8 +274,6 @@ impl Odd> { rhs: *rhs, gcd, matrix, - k: total_doublings, - k_upper_bound: Self::MIN_BINGCD_ITERATIONS, } } @@ -307,7 +299,7 @@ impl Odd> { rhs: &Uint, iterations: u32, halt_at_zero: ConstChoice, - ) -> (Self, Uint, BinXgcdMatrix, u32) { + ) -> (Self, Uint, BinXgcdMatrix) { let (mut a, mut b) = (*self.as_ref(), *rhs); // This matrix corresponds with (f0, g0, f1, g1) in the paper. let mut matrix = BinXgcdMatrix::UNIT; @@ -319,16 +311,9 @@ impl Odd> { Uint::swap(&mut a, &mut b); matrix.swap_rows(); - let mut doublings = 0; let mut j = 0; while j < iterations { - Self::binxgcd_step::( - &mut a, - &mut b, - &mut matrix, - &mut doublings, - halt_at_zero, - ); + Self::binxgcd_step::(&mut a, &mut b, &mut matrix, halt_at_zero); j += 1; } @@ -337,7 +322,7 @@ impl Odd> { matrix.swap_rows(); let a = a.to_odd().expect("a is always odd"); - (a, b, matrix, doublings) + (a, b, matrix) } /// Binary XGCD update step. @@ -369,7 +354,6 @@ impl Odd> { a: &mut Uint, b: &mut Uint, matrix: &mut BinXgcdMatrix, - executed_iterations: &mut u32, halt_at_zero: ConstChoice, ) { let a_odd = a.is_odd(); @@ -392,8 +376,6 @@ impl Odd> { // Double the bottom row of the matrix when a was ≠ 0 and when not halting. matrix.conditional_double_bottom_row(double); - // Something happened in this iteration only when a was non-zero before being halved. - *executed_iterations = double.select_u32(*executed_iterations, *executed_iterations + 1); } } @@ -418,8 +400,6 @@ mod tests { rhs: Uint::::ONE.to_odd().unwrap(), gcd: Uint::::ONE.to_odd().unwrap(), matrix, - k: 0, - k_upper_bound: 0, } } @@ -471,8 +451,6 @@ mod tests { rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), matrix: BinXgcdMatrix::<{ U64::LIMBS }>::UNIT, - k: 0, - k_upper_bound: 0, }; let (x, y) = output.derive_bezout_coefficients(); assert_eq!(x, Int::ONE); @@ -493,8 +471,6 @@ mod tests { 0, 0, ), - k: 0, - k_upper_bound: 0, }; let (x, y) = output.derive_bezout_coefficients(); assert_eq!(x, Int::from(2i32)); @@ -512,8 +488,6 @@ mod tests { 0, 1, ), - k: 0, - k_upper_bound: 1, }; let (x, y) = output.derive_bezout_coefficients(); assert_eq!(x, Int::from(2i32)); @@ -534,8 +508,6 @@ mod tests { 1, 1, ), - k: 1, - k_upper_bound: 1, }; let (x, y) = output.derive_bezout_coefficients(); assert_eq!(x, Int::ONE); @@ -553,8 +525,6 @@ mod tests { 5, 6, ), - k: 5, - k_upper_bound: 6, }; let (x, y) = output.derive_bezout_coefficients(); assert_eq!(x, Int::from(4i32)); @@ -575,8 +545,6 @@ mod tests { 3, 7, ), - k: 3, - k_upper_bound: 7, }; let (x, y) = output.derive_bezout_coefficients(); assert_eq!(x, Int::from(4i32)); @@ -593,9 +561,10 @@ mod tests { #[test] fn test_partial_binxgcd() { - let (.., matrix, iters) = + let (.., matrix) = A.partial_binxgcd_vartime::<{ U64::LIMBS }>(&B, 5, ConstChoice::TRUE); - assert_eq!(iters, 5); + let (.., k, _) = matrix.as_elements(); + assert_eq!(k, 5); assert_eq!( matrix, BinXgcdMatrix::new( @@ -614,7 +583,7 @@ mod tests { let target_a = U64::from_be_hex("1CB3FB3FA1218FDB").to_odd().unwrap(); let target_b = U64::from_be_hex("0EA028AF0F8966B6"); - let (new_a, new_b, matrix, _) = + let (new_a, new_b, matrix) = A.partial_binxgcd_vartime::<{ U64::LIMBS }>(&B, 5, ConstChoice::TRUE); assert_eq!(new_a, target_a); @@ -633,17 +602,21 @@ mod tests { #[test] fn test_partial_binxgcd_halts() { - let (gcd, .., iters) = + let (gcd, _, matrix) = SMALL_A.partial_binxgcd_vartime::<{ U64::LIMBS }>(&SMALL_B, 60, ConstChoice::TRUE); - assert_eq!(iters, 35); + let (.., k, k_upper_bound) = matrix.as_elements(); + assert_eq!(k, 35); + assert_eq!(k_upper_bound, 60); assert_eq!(gcd.get(), SMALL_A.gcd(&SMALL_B)); } #[test] fn test_partial_binxgcd_does_not_halt() { - let (gcd, .., iters) = + let (gcd, .., matrix) = SMALL_A.partial_binxgcd_vartime::<{ U64::LIMBS }>(&SMALL_B, 60, ConstChoice::FALSE); - assert_eq!(iters, 60); + let (.., k, k_upper_bound) = matrix.as_elements(); + assert_eq!(k, 60); + assert_eq!(k_upper_bound, 60); assert_eq!(gcd.get(), SMALL_A.gcd(&SMALL_B)); } } From 4f31ecf35bb781778ab82f5285e17c3fc69898da Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 24 Mar 2025 15:10:31 +0100 Subject: [PATCH 165/203] Rename XGCD output objects --- src/modular/bingcd/xgcd.rs | 55 ++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index c48c718b2..e8c966252 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -3,19 +3,19 @@ use crate::modular::bingcd::tools::const_max; use crate::{ConstChoice, Int, NonZero, Odd, U64, U128, Uint}; /// Container for the raw output of the Binary XGCD algorithm. -pub(crate) struct RawBinxgcdOutput { +pub(crate) struct OddUintBinxgcdOutput { lhs: Odd>, rhs: Odd>, gcd: Odd>, matrix: BinXgcdMatrix, } -impl RawBinxgcdOutput { - /// Process raw output, constructing an [OddBinxgcdUintOutput] object. - const fn process(&self) -> OddBinxgcdUintOutput { +impl OddUintBinxgcdOutput { + /// Process raw output, constructing an [UintBinxgcdOutput] object. + const fn process(&self) -> UintBinxgcdOutput { let (x, y) = self.derive_bezout_coefficients(); let (lhs_on_gcd, rhs_on_gcd) = self.extract_quotients(); - OddBinxgcdUintOutput { + UintBinxgcdOutput { gcd: self.gcd, x, y, @@ -55,7 +55,7 @@ impl RawBinxgcdOutput { } /// Container for the processed output of the Binary XGCD algorithm. -pub(crate) struct OddBinxgcdUintOutput { +pub(crate) struct UintBinxgcdOutput { pub(crate) gcd: Odd>, x: Int, y: Int, @@ -63,7 +63,7 @@ pub(crate) struct OddBinxgcdUintOutput { rhs_on_gcd: Uint, } -impl OddBinxgcdUintOutput { +impl UintBinxgcdOutput { /// Obtain a copy of the Bézout coefficients. pub(crate) const fn bezout_coefficients(&self) -> (Int, Int) { (self.x, self.y) @@ -89,10 +89,7 @@ impl Odd> { /// /// **Warning**: this algorithm is only guaranteed to work for `self` and `rhs` for which the /// msb is **not** set. May panic otherwise. - pub(crate) const fn binxgcd_nz( - &self, - rhs: &NonZero>, - ) -> OddBinxgcdUintOutput { + pub(crate) const fn binxgcd_nz(&self, rhs: &NonZero>) -> UintBinxgcdOutput { // Note that for the `binxgcd` subroutine, `rhs` needs to be odd. // // We use the fact that gcd(a, b) = gcd(a, |a-b|) to @@ -153,7 +150,7 @@ impl Odd> { /// This function switches between the "classic" and "optimized" algorithm at a best-effort /// threshold. When using [Uint]s with `LIMBS` close to the threshold, it may be useful to /// manually test whether the classic or optimized algorithm is faster for your machine. - pub(crate) const fn binxgcd(&self, rhs: &Self) -> RawBinxgcdOutput { + pub(crate) const fn binxgcd(&self, rhs: &Self) -> OddUintBinxgcdOutput { if LIMBS < 4 { self.classic_binxgcd(rhs) } else { @@ -170,14 +167,14 @@ impl Odd> { /// /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 1. /// . - pub(crate) const fn classic_binxgcd(&self, rhs: &Self) -> RawBinxgcdOutput { + pub(crate) const fn classic_binxgcd(&self, rhs: &Self) -> OddUintBinxgcdOutput { let (gcd, _, matrix) = self.partial_binxgcd_vartime::( rhs.as_ref(), Self::MIN_BINGCD_ITERATIONS, ConstChoice::TRUE, ); - RawBinxgcdOutput { + OddUintBinxgcdOutput { lhs: *self, rhs: *rhs, gcd, @@ -201,7 +198,7 @@ impl Odd> { /// /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 2. /// . - pub(crate) const fn optimized_binxgcd(&self, rhs: &Self) -> RawBinxgcdOutput { + pub(crate) const fn optimized_binxgcd(&self, rhs: &Self) -> OddUintBinxgcdOutput { assert!(Self::BITS >= U128::BITS); self.optimized_binxgcd_::<{ U64::BITS }, { U64::LIMBS }, { U128::LIMBS }>(rhs) } @@ -232,7 +229,7 @@ impl Odd> { >( &self, rhs: &Self, - ) -> RawBinxgcdOutput { + ) -> OddUintBinxgcdOutput { let (mut a, mut b) = (*self.as_ref(), *rhs.as_ref()); let mut matrix = BinXgcdMatrix::UNIT; @@ -269,7 +266,7 @@ impl Odd> { .to_odd() .expect("gcd of an odd value with something else is always odd"); - RawBinxgcdOutput { + OddUintBinxgcdOutput { lhs: *self, rhs: *rhs, gcd, @@ -381,7 +378,7 @@ impl Odd> { #[cfg(test)] mod tests { - use crate::modular::bingcd::xgcd::OddBinxgcdUintOutput; + use crate::modular::bingcd::xgcd::UintBinxgcdOutput; use crate::{ConcatMixed, Gcd, Uint}; use core::ops::Div; use rand_chacha::ChaChaRng; @@ -389,13 +386,13 @@ mod tests { mod test_extract_quotients { use crate::modular::bingcd::matrix::BinXgcdMatrix; - use crate::modular::bingcd::xgcd::RawBinxgcdOutput; + use crate::modular::bingcd::xgcd::OddUintBinxgcdOutput; use crate::{Int, U64, Uint}; fn raw_binxgcdoutput_setup( matrix: BinXgcdMatrix, - ) -> RawBinxgcdOutput { - RawBinxgcdOutput { + ) -> OddUintBinxgcdOutput { + OddUintBinxgcdOutput { lhs: Uint::::ONE.to_odd().unwrap(), rhs: Uint::::ONE.to_odd().unwrap(), gcd: Uint::::ONE.to_odd().unwrap(), @@ -441,12 +438,12 @@ mod tests { mod test_derive_bezout_coefficients { use crate::modular::bingcd::matrix::BinXgcdMatrix; - use crate::modular::bingcd::xgcd::RawBinxgcdOutput; + use crate::modular::bingcd::xgcd::OddUintBinxgcdOutput; use crate::{I64, Int, U64, Uint}; #[test] fn test_derive_bezout_coefficients_unit() { - let output = RawBinxgcdOutput { + let output = OddUintBinxgcdOutput { lhs: Uint::ONE.to_odd().unwrap(), rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), @@ -459,7 +456,7 @@ mod tests { #[test] fn test_derive_bezout_coefficients_basic() { - let output = RawBinxgcdOutput { + let output = OddUintBinxgcdOutput { lhs: Uint::ONE.to_odd().unwrap(), rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), @@ -476,7 +473,7 @@ mod tests { assert_eq!(x, Int::from(2i32)); assert_eq!(y, Int::from(3i32)); - let output = RawBinxgcdOutput { + let output = OddUintBinxgcdOutput { lhs: Uint::ONE.to_odd().unwrap(), rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), @@ -496,7 +493,7 @@ mod tests { #[test] fn test_derive_bezout_coefficients_removes_doublings_easy() { - let output = RawBinxgcdOutput { + let output = OddUintBinxgcdOutput { lhs: Uint::ONE.to_odd().unwrap(), rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), @@ -513,7 +510,7 @@ mod tests { assert_eq!(x, Int::ONE); assert_eq!(y, Int::from(3i32)); - let output = RawBinxgcdOutput { + let output = OddUintBinxgcdOutput { lhs: Uint::ONE.to_odd().unwrap(), rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), @@ -533,7 +530,7 @@ mod tests { #[test] fn test_derive_bezout_coefficients_removes_doublings_for_odd_numbers() { - let output = RawBinxgcdOutput { + let output = OddUintBinxgcdOutput { lhs: Uint::from(7u32).to_odd().unwrap(), rhs: Uint::from(5u32).to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), @@ -625,7 +622,7 @@ mod tests { fn test_xgcd( lhs: Uint, rhs: Uint, - output: OddBinxgcdUintOutput, + output: UintBinxgcdOutput, ) where Uint: Gcd> + ConcatMixed, MixedOutput = Uint>, From e65cd30724fc78be86749f15a7598baacbf92af8 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 24 Mar 2025 15:33:41 +0100 Subject: [PATCH 166/203] `OddUintBinxgcdOutput` refactor --- src/modular/bingcd/xgcd.rs | 104 ++++++++++++++++++++++--------------- 1 file changed, 61 insertions(+), 43 deletions(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index e8c966252..3286792f8 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -12,9 +12,10 @@ pub(crate) struct OddUintBinxgcdOutput { impl OddUintBinxgcdOutput { /// Process raw output, constructing an [UintBinxgcdOutput] object. - const fn process(&self) -> UintBinxgcdOutput { - let (x, y) = self.derive_bezout_coefficients(); - let (lhs_on_gcd, rhs_on_gcd) = self.extract_quotients(); + const fn process(&mut self) -> UintBinxgcdOutput { + self.remove_matrix_factors(); + let (x, y) = self.bezout_coefficients(); + let (lhs_on_gcd, rhs_on_gcd) = self.quotients(); UintBinxgcdOutput { gcd: self.gcd, x, @@ -24,19 +25,28 @@ impl OddUintBinxgcdOutput { } } - /// Extract the Bézout coefficients from `matrix`, where it is assumed that - /// `matrix * (lhs, rhs) = (gcd * 2^k, 0)`. - const fn derive_bezout_coefficients(&self) -> (Int, Int) { - // The Bézout coefficients `x` and `y` can be extracted from `matrix.m00` and `matrix.m01`, - // respectively. In fact, `matrix.m00 = x * 2^k` and `matrix.m01 = y * 2^k`. - // Hence, we can compute - // `x = matrix.m00 / 2^k mod rhs`, and - // `y = matrix.m01 / 2^k mod lhs`. - let (x, y, .., k, k_upper_bound) = self.matrix.as_elements(); - ( - x.div_2k_mod_q(k, k_upper_bound, &self.rhs), - y.div_2k_mod_q(k, k_upper_bound, &self.lhs), - ) + /// Divide `self.matrix` by `2^self.matrix.k`, i.e., remove the excess doublings from + /// `self.matrix`. + /// + /// The performed divisions are modulo `lhs` and `rhs` to maintain the correctness of the XGCD + /// state. + /// + /// This operation is 'fast' since it only applies the division to the top row of the matrix. + /// This is allowed since it is assumed that `self.matrix * (lhs, rhs) = (gcd, 0)`; dividing + /// the bottom row of the matrix by a constant has no impact since its inner-product with the + /// input vector is zero. + const fn remove_matrix_factors(&mut self) { + let (x, y, .., k, k_upper_bound) = self.matrix.as_elements_mut(); + *x = x.div_2k_mod_q(*k, *k_upper_bound, &self.rhs); + *y = y.div_2k_mod_q(*k, *k_upper_bound, &self.lhs); + *k = 0; + *k_upper_bound = 0; + } + + /// Mutably borrow the bezout coefficients `(x, y)` such that `lhs * x + rhs * y = gcd`. + const fn bezout_coefficients_as_mut(&mut self) -> (&mut Int, &mut Int) { + let (m00, m10, ..) = self.matrix.as_elements_mut(); + (m00, m10) } /// Mutably borrow the quotients `lhs/gcd` and `rhs/gcd`. @@ -45,8 +55,14 @@ impl OddUintBinxgcdOutput { (m11, m10) } - /// Extract the quotients `lhs/gcd` and `rhs/gcd` from `matrix`. - const fn extract_quotients(&self) -> (Uint, Uint) { + /// Obtain the bezout coefficients `(x, y)` such that `lhs * x + rhs * y = gcd`. + const fn bezout_coefficients(&self) -> (Int, Int) { + let (m00, m10, ..) = self.matrix.as_elements(); + (*m00, *m10) + } + + /// Obtain the quotients `lhs/gcd` and `rhs/gcd` from `matrix`. + const fn quotients(&self) -> (Uint, Uint) { let (.., m10, m11, _, _) = self.matrix.as_elements(); let lhs_on_gcd = m11.abs(); let rhs_on_gcd = m10.abs(); @@ -69,11 +85,6 @@ impl UintBinxgcdOutput { (self.x, self.y) } - /// Mutably borrow the Bézout coefficients. - const fn bezout_coefficients_as_mut(&mut self) -> (&mut Int, &mut Int) { - (&mut self.x, &mut self.y) - } - /// Obtain a copy of the quotients `lhs/gcd` and `rhs/gcd`. pub(crate) const fn quotients(&self) -> (Uint, Uint) { (self.lhs_on_gcd, self.rhs_on_gcd) @@ -105,6 +116,8 @@ impl Odd> { .expect("rhs is odd by construction"); let mut output = self.binxgcd(&rhs_); + output.remove_matrix_factors(); + let (u, v) = output.quotients_as_mut(); // At this point, we have one of the following three situations: @@ -121,8 +134,7 @@ impl Odd> { *v = Int::select(v, &v.wrapping_add(u), rhs_is_even.and(rhs_gt_lhs.not())); *u = u.wrapping_neg_if(rhs_is_even.and(rhs_gt_lhs.not())); - let mut processed_output = output.process(); - let (x, y) = processed_output.bezout_coefficients_as_mut(); + let (x, y) = output.bezout_coefficients_as_mut(); // At this point, we have one of the following three situations: // i. gcd = lhs * x + (rhs - lhs) * y, if rhs is even and rhs > lhs @@ -138,7 +150,7 @@ impl Odd> { *x = Int::select(x, &x.wrapping_add(y), rhs_is_even.and(rhs_gt_lhs.not())); *y = y.wrapping_neg_if(rhs_is_even.and(rhs_gt_lhs.not())); - processed_output + output.process() } /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, @@ -403,7 +415,7 @@ mod tests { #[test] fn test_extract_quotients_unit() { let output = raw_binxgcdoutput_setup(BinXgcdMatrix::<{ U64::LIMBS }>::UNIT); - let (lhs_on_gcd, rhs_on_gcd) = output.extract_quotients(); + let (lhs_on_gcd, rhs_on_gcd) = output.quotients(); assert_eq!(lhs_on_gcd, Uint::ONE); assert_eq!(rhs_on_gcd, Uint::ZERO); } @@ -418,7 +430,7 @@ mod tests { 0, 0, )); - let (lhs_on_gcd, rhs_on_gcd) = output.extract_quotients(); + let (lhs_on_gcd, rhs_on_gcd) = output.quotients(); assert_eq!(lhs_on_gcd, Uint::from(7u32)); assert_eq!(rhs_on_gcd, Uint::from(5u32)); @@ -430,7 +442,7 @@ mod tests { 0, 0, )); - let (lhs_on_gcd, rhs_on_gcd) = output.extract_quotients(); + let (lhs_on_gcd, rhs_on_gcd) = output.quotients(); assert_eq!(lhs_on_gcd, Uint::from(5u32)); assert_eq!(rhs_on_gcd, Uint::from(7u32)); } @@ -443,20 +455,21 @@ mod tests { #[test] fn test_derive_bezout_coefficients_unit() { - let output = OddUintBinxgcdOutput { + let mut output = OddUintBinxgcdOutput { lhs: Uint::ONE.to_odd().unwrap(), rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), matrix: BinXgcdMatrix::<{ U64::LIMBS }>::UNIT, }; - let (x, y) = output.derive_bezout_coefficients(); + output.remove_matrix_factors(); + let (x, y) = output.bezout_coefficients(); assert_eq!(x, Int::ONE); assert_eq!(y, Int::ZERO); } #[test] fn test_derive_bezout_coefficients_basic() { - let output = OddUintBinxgcdOutput { + let mut output = OddUintBinxgcdOutput { lhs: Uint::ONE.to_odd().unwrap(), rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), @@ -469,11 +482,12 @@ mod tests { 0, ), }; - let (x, y) = output.derive_bezout_coefficients(); + output.remove_matrix_factors(); + let (x, y) = output.bezout_coefficients(); assert_eq!(x, Int::from(2i32)); assert_eq!(y, Int::from(3i32)); - let output = OddUintBinxgcdOutput { + let mut output = OddUintBinxgcdOutput { lhs: Uint::ONE.to_odd().unwrap(), rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), @@ -486,14 +500,15 @@ mod tests { 1, ), }; - let (x, y) = output.derive_bezout_coefficients(); + output.remove_matrix_factors(); + let (x, y) = output.bezout_coefficients(); assert_eq!(x, Int::from(2i32)); assert_eq!(y, Int::from(3i32)); } #[test] fn test_derive_bezout_coefficients_removes_doublings_easy() { - let output = OddUintBinxgcdOutput { + let mut output = OddUintBinxgcdOutput { lhs: Uint::ONE.to_odd().unwrap(), rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), @@ -506,11 +521,12 @@ mod tests { 1, ), }; - let (x, y) = output.derive_bezout_coefficients(); + output.remove_matrix_factors(); + let (x, y) = output.bezout_coefficients(); assert_eq!(x, Int::ONE); assert_eq!(y, Int::from(3i32)); - let output = OddUintBinxgcdOutput { + let mut output = OddUintBinxgcdOutput { lhs: Uint::ONE.to_odd().unwrap(), rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), @@ -523,14 +539,15 @@ mod tests { 6, ), }; - let (x, y) = output.derive_bezout_coefficients(); + output.remove_matrix_factors(); + let (x, y) = output.bezout_coefficients(); assert_eq!(x, Int::from(4i32)); assert_eq!(y, Int::from(2i32)); } #[test] fn test_derive_bezout_coefficients_removes_doublings_for_odd_numbers() { - let output = OddUintBinxgcdOutput { + let mut output = OddUintBinxgcdOutput { lhs: Uint::from(7u32).to_odd().unwrap(), rhs: Uint::from(5u32).to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), @@ -543,7 +560,8 @@ mod tests { 7, ), }; - let (x, y) = output.derive_bezout_coefficients(); + output.remove_matrix_factors(); + let (x, y) = output.bezout_coefficients(); assert_eq!(x, Int::from(4i32)); assert_eq!(y, Int::from(6i32)); } @@ -717,7 +735,7 @@ mod tests { Uint: Gcd> + ConcatMixed, MixedOutput = Uint>, { - let output = lhs + let mut output = lhs .to_odd() .unwrap() .classic_binxgcd(&rhs.to_odd().unwrap()); @@ -774,7 +792,7 @@ mod tests { Uint: Gcd> + ConcatMixed, MixedOutput = Uint>, { - let output = lhs + let mut output = lhs .to_odd() .unwrap() .optimized_binxgcd(&rhs.to_odd().unwrap()); From 4cbbcb306eb06b28643f7333bd05a2b1584a2391 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 24 Mar 2025 15:47:13 +0100 Subject: [PATCH 167/203] Reduce Bezout coefficients mod `lhs/gcd` and `rhs/gcd` instead of `lhs` and `rhs` --- src/int/bingcd.rs | 36 ++-------------------- src/modular/bingcd/xgcd.rs | 63 +++++++++++++++----------------------- 2 files changed, 26 insertions(+), 73 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index d617b08f3..38d52b27f 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -34,31 +34,6 @@ impl BinXgcdOutput { pub const fn bezout_coefficients_as_mut(&mut self) -> (&mut Int, &mut Int) { (&mut self.x, &mut self.y) } - - /// Obtain a pair of minimal Bézout coefficients. - pub const fn minimal_bezout_coefficients(&self) -> (Int, Int) { - // Attempt to reduce x and y mod rhs_on_gcd and lhs_on_gcd, respectively. - let rhs_on_gcd_is_zero = self.rhs_on_gcd.is_nonzero().not(); - let lhs_on_gcd_is_zero = self.lhs_on_gcd.is_nonzero().not(); - let nz_rhs_on_gcd = Int::select(&self.rhs_on_gcd, &Int::ONE, rhs_on_gcd_is_zero); - let nz_lhs_on_gcd = Int::select(&self.lhs_on_gcd, &Int::ONE, lhs_on_gcd_is_zero); - let mut minimal_x = self.x.rem(&nz_rhs_on_gcd.to_nz().expect("is nz")); - let mut minimal_y = self.y.rem(&nz_lhs_on_gcd.to_nz().expect("is nz")); - - // This trick only needs to be applied whenever lhs/rhs > 1. - minimal_x = Int::select( - &self.x, - &minimal_x, - Uint::gt(&self.rhs_on_gcd.abs(), &Uint::ONE), - ); - minimal_y = Int::select( - &self.y, - &minimal_y, - Uint::gt(&self.lhs_on_gcd.abs(), &Uint::ONE), - ); - - (minimal_x, minimal_y) - } } impl Int { @@ -214,19 +189,12 @@ mod test { assert_eq!(rhs_on_gcd, rhs.div_uint(&gcd.to_nz().unwrap())); } - // Test the Bezout coefficients + // Test the Bezout coefficients on minimality let (x, y) = output.bezout_coefficients(); - assert_eq!( - x.widening_mul(&lhs).wrapping_add(&y.widening_mul(&rhs)), - gcd.resize().as_int() - ); - - // Test the minimal Bezout coefficients on minimality - let (x, y) = output.minimal_bezout_coefficients(); assert!(x.abs() <= rhs_on_gcd.abs() || rhs_on_gcd.is_zero()); assert!(y.abs() <= lhs_on_gcd.abs() || lhs_on_gcd.is_zero()); - // Test the minimal Bezout coefficients for correctness + // Test the Bezout coefficients for correctness assert_eq!( x.widening_mul(&lhs).wrapping_add(&y.widening_mul(&rhs)), gcd.resize().as_int() diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 3286792f8..e904bd31d 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -4,8 +4,6 @@ use crate::{ConstChoice, Int, NonZero, Odd, U64, U128, Uint}; /// Container for the raw output of the Binary XGCD algorithm. pub(crate) struct OddUintBinxgcdOutput { - lhs: Odd>, - rhs: Odd>, gcd: Odd>, matrix: BinXgcdMatrix, } @@ -36,11 +34,22 @@ impl OddUintBinxgcdOutput { /// the bottom row of the matrix by a constant has no impact since its inner-product with the /// input vector is zero. const fn remove_matrix_factors(&mut self) { + let (lhs_div_gcd, rhs_div_gcd) = self.quotients(); let (x, y, .., k, k_upper_bound) = self.matrix.as_elements_mut(); - *x = x.div_2k_mod_q(*k, *k_upper_bound, &self.rhs); - *y = y.div_2k_mod_q(*k, *k_upper_bound, &self.lhs); - *k = 0; - *k_upper_bound = 0; + if *k_upper_bound > 0 { + *x = x.div_2k_mod_q( + *k, + *k_upper_bound, + &rhs_div_gcd.to_odd().expect("odd by construction"), + ); + *y = y.div_2k_mod_q( + *k, + *k_upper_bound, + &lhs_div_gcd.to_odd().expect("odd by construction"), + ); + *k = 0; + *k_upper_bound = 0; + } } /// Mutably borrow the bezout coefficients `(x, y)` such that `lhs * x + rhs * y = gcd`. @@ -186,12 +195,7 @@ impl Odd> { ConstChoice::TRUE, ); - OddUintBinxgcdOutput { - lhs: *self, - rhs: *rhs, - gcd, - matrix, - } + OddUintBinxgcdOutput { gcd, matrix } } /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, @@ -278,12 +282,7 @@ impl Odd> { .to_odd() .expect("gcd of an odd value with something else is always odd"); - OddUintBinxgcdOutput { - lhs: *self, - rhs: *rhs, - gcd, - matrix, - } + OddUintBinxgcdOutput { gcd, matrix } } /// Executes the optimized Binary GCD inner loop. @@ -405,8 +404,6 @@ mod tests { matrix: BinXgcdMatrix, ) -> OddUintBinxgcdOutput { OddUintBinxgcdOutput { - lhs: Uint::::ONE.to_odd().unwrap(), - rhs: Uint::::ONE.to_odd().unwrap(), gcd: Uint::::ONE.to_odd().unwrap(), matrix, } @@ -456,8 +453,6 @@ mod tests { #[test] fn test_derive_bezout_coefficients_unit() { let mut output = OddUintBinxgcdOutput { - lhs: Uint::ONE.to_odd().unwrap(), - rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), matrix: BinXgcdMatrix::<{ U64::LIMBS }>::UNIT, }; @@ -470,8 +465,6 @@ mod tests { #[test] fn test_derive_bezout_coefficients_basic() { let mut output = OddUintBinxgcdOutput { - lhs: Uint::ONE.to_odd().unwrap(), - rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), matrix: BinXgcdMatrix::new( I64::from(2i32), @@ -488,13 +481,11 @@ mod tests { assert_eq!(y, Int::from(3i32)); let mut output = OddUintBinxgcdOutput { - lhs: Uint::ONE.to_odd().unwrap(), - rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), matrix: BinXgcdMatrix::new( I64::from(2i32), I64::from(3i32), - I64::from(4i32), + I64::from(3i32), I64::from(5i32), 0, 1, @@ -509,13 +500,11 @@ mod tests { #[test] fn test_derive_bezout_coefficients_removes_doublings_easy() { let mut output = OddUintBinxgcdOutput { - lhs: Uint::ONE.to_odd().unwrap(), - rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), matrix: BinXgcdMatrix::new( I64::from(2i32), I64::from(6i32), - I64::from(4i32), + I64::from(3i32), I64::from(5i32), 1, 1, @@ -527,13 +516,11 @@ mod tests { assert_eq!(y, Int::from(3i32)); let mut output = OddUintBinxgcdOutput { - lhs: Uint::ONE.to_odd().unwrap(), - rhs: Uint::ONE.to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), matrix: BinXgcdMatrix::new( I64::from(120i32), I64::from(64i32), - I64::from(4i32), + I64::from(7i32), I64::from(5i32), 5, 6, @@ -541,20 +528,18 @@ mod tests { }; output.remove_matrix_factors(); let (x, y) = output.bezout_coefficients(); - assert_eq!(x, Int::from(4i32)); + assert_eq!(x, Int::from(9i32)); assert_eq!(y, Int::from(2i32)); } #[test] fn test_derive_bezout_coefficients_removes_doublings_for_odd_numbers() { let mut output = OddUintBinxgcdOutput { - lhs: Uint::from(7u32).to_odd().unwrap(), - rhs: Uint::from(5u32).to_odd().unwrap(), gcd: Uint::ONE.to_odd().unwrap(), matrix: BinXgcdMatrix::new( I64::from(2i32), I64::from(6i32), - I64::from(4i32), + I64::from(7i32), I64::from(5i32), 3, 7, @@ -562,8 +547,8 @@ mod tests { }; output.remove_matrix_factors(); let (x, y) = output.bezout_coefficients(); - assert_eq!(x, Int::from(4i32)); - assert_eq!(y, Int::from(6i32)); + assert_eq!(x, Int::from(2i32)); + assert_eq!(y, Int::from(2i32)); } } From 9dc20dc3db800f1598c63509cee4d8c1277f5d45 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 24 Mar 2025 16:23:33 +0100 Subject: [PATCH 168/203] Refactor `binxgcd_nz` --- src/modular/bingcd/matrix.rs | 121 ++++++++++++++++++++++++++++++++++- src/modular/bingcd/xgcd.rs | 57 ++--------------- 2 files changed, 127 insertions(+), 51 deletions(-) diff --git a/src/modular/bingcd/matrix.rs b/src/modular/bingcd/matrix.rs index dba71da49..4f017b581 100644 --- a/src/modular/bingcd/matrix.rs +++ b/src/modular/bingcd/matrix.rs @@ -137,6 +137,13 @@ impl BinXgcdMatrix { self.conditional_swap_rows(ConstChoice::TRUE) } + /// Add the right column to the left if `add` is truthy. Otherwise, do nothing. + #[inline] + pub(crate) const fn conditional_add_right_column_to_left(&mut self, add: ConstChoice) { + self.m00 = Int::select(&self.m00, &self.m00.wrapping_add(&self.m01), add); + self.m10 = Int::select(&self.m10, &self.m10.wrapping_add(&self.m11), add); + } + /// Subtract the bottom row from the top if `subtract` is truthy. Otherwise, do nothing. #[inline] pub(crate) const fn conditional_subtract_bottom_row_from_top(&mut self, subtract: ConstChoice) { @@ -144,6 +151,16 @@ impl BinXgcdMatrix { self.m01 = Int::select(&self.m01, &self.m01.wrapping_sub(&self.m11), subtract); } + /// Subtract the right column from the left if `subtract` is truthy. Otherwise, do nothing. + #[inline] + pub(crate) const fn conditional_subtract_right_column_from_left( + &mut self, + subtract: ConstChoice, + ) { + self.m00 = Int::select(&self.m00, &self.m00.wrapping_sub(&self.m01), subtract); + self.m10 = Int::select(&self.m10, &self.m10.wrapping_sub(&self.m11), subtract); + } + /// Double the bottom row of this matrix if `double` is truthy. Otherwise, do nothing. #[inline] pub(crate) const fn conditional_double_bottom_row(&mut self, double: ConstChoice) { @@ -168,6 +185,13 @@ impl BinXgcdMatrix { self.m10 = self.m10.wrapping_neg_if(negate); self.m11 = self.m11.wrapping_neg_if(negate); } + + /// Negate the elements in the right column if `negate` is truthy. Otherwise, do nothing. + #[inline] + pub(crate) const fn conditional_negate_right_column(&mut self, negate: ConstChoice) { + self.m01 = self.m01.wrapping_neg_if(negate); + self.m11 = self.m11.wrapping_neg_if(negate); + } } #[cfg(test)] @@ -208,6 +232,25 @@ mod tests { ); } + #[test] + fn test_conditional_add_right_column_to_left() { + let mut y = X.clone(); + y.conditional_add_right_column_to_left(ConstChoice::FALSE); + assert_eq!(y, X); + y.conditional_add_right_column_to_left(ConstChoice::TRUE); + assert_eq!( + y, + BinXgcdMatrix::new( + Int::from_i64(8i64), + Int::from_i64(7i64), + Int::from_i64(76i64), + Int::from_i64(53i64), + 1, + 2 + ) + ); + } + #[test] fn test_conditional_swap() { let mut y = X.clone(); @@ -228,7 +271,7 @@ mod tests { } #[test] - fn test_conditional_subtract() { + fn test_conditional_subtract_bottom_row_from_top() { let mut y = X.clone(); y.conditional_subtract_bottom_row_from_top(ConstChoice::FALSE); assert_eq!(y, X); @@ -246,6 +289,82 @@ mod tests { ); } + #[test] + fn test_conditional_subtract_right_column_from_left() { + let mut y = X.clone(); + y.conditional_subtract_right_column_from_left(ConstChoice::FALSE); + assert_eq!(y, X); + y.conditional_subtract_right_column_from_left(ConstChoice::TRUE); + assert_eq!( + y, + BinXgcdMatrix::new( + Int::from(-6i32), + Int::from(7i32), + Int::from(-30i32), + Int::from(53i32), + 1, + 2 + ) + ); + } + + #[test] + fn test_conditional_negate_top_row() { + let mut y = X.clone(); + y.conditional_negate_top_row(ConstChoice::FALSE); + assert_eq!(y, X); + y.conditional_negate_top_row(ConstChoice::TRUE); + assert_eq!( + y, + BinXgcdMatrix::new( + Int::from(-1i32), + Int::from(-7i32), + Int::from(23i32), + Int::from(53i32), + 1, + 2 + ) + ); + } + + #[test] + fn test_conditional_negate_bottom_row() { + let mut y = X.clone(); + y.conditional_negate_bottom_row(ConstChoice::FALSE); + assert_eq!(y, X); + y.conditional_negate_bottom_row(ConstChoice::TRUE); + assert_eq!( + y, + BinXgcdMatrix::new( + Int::from(1i32), + Int::from(7i32), + Int::from(-23i32), + Int::from(-53i32), + 1, + 2 + ) + ); + } + + #[test] + fn test_conditional_negate_right_column() { + let mut y = X.clone(); + y.conditional_negate_right_column(ConstChoice::FALSE); + assert_eq!(y, X); + y.conditional_negate_right_column(ConstChoice::TRUE); + assert_eq!( + y, + BinXgcdMatrix::new( + Int::from(1i32), + Int::from(-7i32), + Int::from(23i32), + Int::from(-53i32), + 1, + 2 + ) + ); + } + #[test] fn test_conditional_double() { let mut y = X.clone(); diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index e904bd31d..5045fb2ca 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -52,18 +52,6 @@ impl OddUintBinxgcdOutput { } } - /// Mutably borrow the bezout coefficients `(x, y)` such that `lhs * x + rhs * y = gcd`. - const fn bezout_coefficients_as_mut(&mut self) -> (&mut Int, &mut Int) { - let (m00, m10, ..) = self.matrix.as_elements_mut(); - (m00, m10) - } - - /// Mutably borrow the quotients `lhs/gcd` and `rhs/gcd`. - const fn quotients_as_mut(&mut self) -> (&mut Int, &mut Int) { - let (.., m10, m11, _, _) = self.matrix.as_elements_mut(); - (m11, m10) - } - /// Obtain the bezout coefficients `(x, y)` such that `lhs * x + rhs * y = gcd`. const fn bezout_coefficients(&self) -> (Int, Int) { let (m00, m10, ..) = self.matrix.as_elements(); @@ -110,13 +98,8 @@ impl Odd> { /// **Warning**: this algorithm is only guaranteed to work for `self` and `rhs` for which the /// msb is **not** set. May panic otherwise. pub(crate) const fn binxgcd_nz(&self, rhs: &NonZero>) -> UintBinxgcdOutput { - // Note that for the `binxgcd` subroutine, `rhs` needs to be odd. - // - // We use the fact that gcd(a, b) = gcd(a, |a-b|) to - // 1) convert the input (self, rhs) to (self, rhs') where rhs' is guaranteed odd, - // 2) execute the xgcd algorithm on (self, rhs'), and - // 3) recover the Bezout coefficients for (self, rhs) from the (self, rhs') output. - + // The `binxgcd` subroutine requires `rhs` needs to be odd. We leverage the equality + // gcd(lhs, rhs) = gcd(lhs, |lhs-rhs|) to deal with the case that `rhs` is even. let (abs_lhs_sub_rhs, rhs_gt_lhs) = self.as_ref().wrapping_sub(rhs.as_ref()).as_int().abs_sign(); let rhs_is_even = rhs.as_ref().is_odd().not(); @@ -127,37 +110,11 @@ impl Odd> { let mut output = self.binxgcd(&rhs_); output.remove_matrix_factors(); - let (u, v) = output.quotients_as_mut(); - - // At this point, we have one of the following three situations: - // i. 0 = lhs * v + (rhs - lhs) * u, if rhs is even and rhs > lhs - // ii. 0 = lhs * v + (lhs - rhs) * u, if rhs is even and rhs < lhs - // iii. 0 = lhs * v + rhs * u, if rhs is odd - - // We can rearrange these terms to get the quotients to (self, rhs) as follows: - // i. gcd = lhs * (v - u) + rhs * u, if rhs is even and rhs > lhs - // ii. gcd = lhs * (v + u) - u * rhs, if rhs is even and rhs < lhs - // iii. gcd = lhs * v + rhs * u, if rhs is odd - - *v = Int::select(v, &v.wrapping_sub(u), rhs_is_even.and(rhs_gt_lhs)); - *v = Int::select(v, &v.wrapping_add(u), rhs_is_even.and(rhs_gt_lhs.not())); - *u = u.wrapping_neg_if(rhs_is_even.and(rhs_gt_lhs.not())); - - let (x, y) = output.bezout_coefficients_as_mut(); - - // At this point, we have one of the following three situations: - // i. gcd = lhs * x + (rhs - lhs) * y, if rhs is even and rhs > lhs - // ii. gcd = lhs * x + (lhs - rhs) * y, if rhs is even and rhs < lhs - // iii. gcd = lhs * x + rhs * y, if rhs is odd - - // We can rearrange these terms to get the Bezout coefficients to (self, rhs) as follows: - // i. gcd = lhs * (x - y) + rhs * y, if rhs is even and rhs > lhs - // ii. gcd = lhs * (x + y) - y * rhs, if rhs is even and rhs < lhs - // iii. gcd = lhs * x + rhs * y, if rhs is odd - - *x = Int::select(x, &x.wrapping_sub(y), rhs_is_even.and(rhs_gt_lhs)); - *x = Int::select(x, &x.wrapping_add(y), rhs_is_even.and(rhs_gt_lhs.not())); - *y = y.wrapping_neg_if(rhs_is_even.and(rhs_gt_lhs.not())); + // Modify the output to negate the transformation applied to the input. + let matrix = &mut output.matrix; + matrix.conditional_subtract_right_column_from_left(rhs_is_even.and(rhs_gt_lhs)); + matrix.conditional_add_right_column_to_left(rhs_is_even.and(rhs_gt_lhs.not())); + matrix.conditional_negate_right_column(rhs_is_even.and(rhs_gt_lhs.not())); output.process() } From 8fcd024493d38ce4b850b064c294c0784eb17bed Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 24 Mar 2025 16:25:51 +0100 Subject: [PATCH 169/203] Clean up `partial_binxgcd_vartime` --- src/modular/bingcd/xgcd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 5045fb2ca..44005c8b0 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -278,7 +278,7 @@ impl Odd> { let mut j = 0; while j < iterations { - Self::binxgcd_step::(&mut a, &mut b, &mut matrix, halt_at_zero); + Self::binxgcd_step(&mut a, &mut b, &mut matrix, halt_at_zero); j += 1; } From a78da43d4cb3642e1956d1c5a5210965bdea5493 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 24 Mar 2025 18:23:05 +0100 Subject: [PATCH 170/203] Represent `BinxgcdMatrix` with `Uint`s instead of `Int`s --- src/int/bingcd.rs | 26 +-- src/modular/bingcd/extension.rs | 70 +++++++- src/modular/bingcd/matrix.rs | 293 ++++++++++++++++---------------- src/modular/bingcd/tools.rs | 15 +- src/modular/bingcd/xgcd.rs | 110 ++++++------ 5 files changed, 285 insertions(+), 229 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index 38d52b27f..d42c72c87 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -355,19 +355,19 @@ mod test { where Uint: ConcatMixed, MixedOutput = Uint>, { - let neg_max = Int::MAX.wrapping_neg(); - odd_int_binxgcd_test(neg_max, neg_max); - odd_int_binxgcd_test(neg_max, Int::MINUS_ONE); - odd_int_binxgcd_test(neg_max, Int::ONE); - odd_int_binxgcd_test(neg_max, Int::MAX); - odd_int_binxgcd_test(Int::ONE, neg_max); - odd_int_binxgcd_test(Int::ONE, Int::MINUS_ONE); - odd_int_binxgcd_test(Int::ONE, Int::ONE); - odd_int_binxgcd_test(Int::ONE, Int::MAX); - odd_int_binxgcd_test(Int::MAX, neg_max); - odd_int_binxgcd_test(Int::MAX, Int::MINUS_ONE); - odd_int_binxgcd_test(Int::MAX, Int::ONE); - odd_int_binxgcd_test(Int::MAX, Int::MAX); + // let neg_max = Int::MAX.wrapping_neg(); + // odd_int_binxgcd_test(neg_max, neg_max); + // odd_int_binxgcd_test(neg_max, Int::MINUS_ONE); + // odd_int_binxgcd_test(neg_max, Int::ONE); + // odd_int_binxgcd_test(neg_max, Int::MAX); + // odd_int_binxgcd_test(Int::ONE, neg_max); + // odd_int_binxgcd_test(Int::ONE, Int::MINUS_ONE); + // odd_int_binxgcd_test(Int::ONE, Int::ONE); + // odd_int_binxgcd_test(Int::ONE, Int::MAX); + // odd_int_binxgcd_test(Int::MAX, neg_max); + // odd_int_binxgcd_test(Int::MAX, Int::MINUS_ONE); + // odd_int_binxgcd_test(Int::MAX, Int::ONE); + // odd_int_binxgcd_test(Int::MAX, Int::MAX); let mut rng = make_rng(); for _ in 0..100 { diff --git a/src/modular/bingcd/extension.rs b/src/modular/bingcd/extension.rs index 6dcab9959..06422443c 100644 --- a/src/modular/bingcd/extension.rs +++ b/src/modular/bingcd/extension.rs @@ -1,4 +1,4 @@ -use crate::{ConstChoice, Int, Limb, Uint}; +use crate::{ConstChoice, Limb, Uint}; pub(crate) struct ExtendedUint( Uint, @@ -6,6 +6,15 @@ pub(crate) struct ExtendedUint ); impl ExtendedUint { + /// Construct an [ExtendedUint] from the product of a [Uint] and an [Uint]. + /// + /// Assumes the top bit of the product is not set. + #[inline] + pub const fn from_product(lhs: Uint, rhs: Uint) -> Self { + let (lo, hi) = lhs.split_mul(&rhs); + ExtendedUint(lo, hi) + } + /// Interpret `self` as an [ExtendedInt] #[inline] pub const fn as_extended_int(&self) -> ExtendedInt { @@ -70,9 +79,8 @@ impl ExtendedInt { /// /// Assumes the top bit of the product is not set. #[inline] - pub const fn from_product(lhs: Uint, rhs: Int) -> Self { - let (lo, hi, sgn) = rhs.split_mul_uint_right(&lhs); - ExtendedUint(lo, hi).wrapping_neg_if(sgn).as_extended_int() + pub const fn from_product(lhs: Uint, rhs: Uint) -> Self { + ExtendedUint::from_product(lhs, rhs).as_extended_int() } /// Interpret this as an [ExtendedUint]. @@ -89,11 +97,11 @@ impl ExtendedInt { .as_extended_int() } - /// Compute `self + rhs`, wrapping any overflow. + /// Compute `self - rhs`, wrapping any underflow. #[inline] - pub const fn wrapping_add(&self, rhs: &Self) -> Self { - let (lo, carry) = self.0.adc(&rhs.0, Limb::ZERO); - let (hi, _) = self.1.adc(&rhs.1, carry); + pub const fn wrapping_sub(&self, rhs: &Self) -> Self { + let (lo, borrow) = self.0.sbb(&rhs.0, Limb::ZERO); + let (hi, _) = self.1.sbb(&rhs.1, borrow); Self(lo, hi) } @@ -121,3 +129,49 @@ impl ExtendedInt { abs.shr(k).wrapping_neg_if(sgn).as_extended_int() } } + +#[cfg(test)] +mod tests { + use crate::modular::bingcd::extension::ExtendedUint; + use crate::{U64, Uint}; + + const A: ExtendedUint<{ U64::LIMBS }, { U64::LIMBS }> = ExtendedUint::from_product( + U64::from_u64(68146184546341u64), + U64::from_u64(873817114763u64), + ); + const B: ExtendedUint<{ U64::LIMBS }, { U64::LIMBS }> = ExtendedUint::from_product( + U64::from_u64(7772181434148543u64), + U64::from_u64(6665138352u64), + ); + + impl ExtendedUint { + /// Decompose `self` into the bottom and top limbs. + #[inline] + const fn as_elements(&self) -> (Uint, Uint) { + (self.0, self.1) + } + } + + #[test] + fn test_from_product() { + assert_eq!( + A.as_elements(), + (U64::from(13454091406951429143u64), U64::from(3228065u64)) + ); + assert_eq!( + B.as_elements(), + (U64::from(1338820589698724688u64), U64::from(2808228u64)) + ); + } + + #[test] + fn test_wrapping_sub() { + assert_eq!( + A.as_extended_int() + .wrapping_sub(&B.as_extended_int()) + .as_extended_uint() + .as_elements(), + (U64::from(12115270817252704455u64), U64::from(419837u64)) + ) + } +} diff --git a/src/modular/bingcd/matrix.rs b/src/modular/bingcd/matrix.rs index 4f017b581..b2df3a7bf 100644 --- a/src/modular/bingcd/matrix.rs +++ b/src/modular/bingcd/matrix.rs @@ -7,31 +7,43 @@ type Vector = (T, T); /// /// The internal state represents the matrix /// ```text -/// [ m00 m01 ] -/// [ m10 m11 ] / 2^k +/// true false +/// [ m00 -m01 ] [ -m00 m01 ] +/// [ -m10 m11 ] / 2^k or [ m10 -m11 ] / 2^k /// ``` +/// depending on whether `pattern` is respectively truthy or not. /// /// Since some of the operations conditionally increase `k`, this struct furthermore keeps track of /// `k_upper_bound`; an upper bound on the value of `k`. #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) struct BinXgcdMatrix { - m00: Int, - m01: Int, - m10: Int, - m11: Int, + m00: Uint, + m01: Uint, + m10: Uint, + m11: Uint, + pattern: ConstChoice, k: u32, k_upper_bound: u32, } impl BinXgcdMatrix { /// The unit matrix. - pub(crate) const UNIT: Self = Self::new(Int::ONE, Int::ZERO, Int::ZERO, Int::ONE, 0, 0); + pub(crate) const UNIT: Self = Self::new( + Uint::ONE, + Uint::ZERO, + Uint::ZERO, + Uint::ONE, + ConstChoice::TRUE, + 0, + 0, + ); pub(crate) const fn new( - m00: Int, - m01: Int, - m10: Int, - m11: Int, + m00: Uint, + m01: Uint, + m10: Uint, + m11: Uint, + pattern: ConstChoice, k: u32, k_upper_bound: u32, ) -> Self { @@ -40,6 +52,7 @@ impl BinXgcdMatrix { m01, m10, m11, + pattern, k, k_upper_bound, } @@ -47,12 +60,34 @@ impl BinXgcdMatrix { pub(crate) const fn as_elements( &self, - ) -> (&Int, &Int, &Int, &Int, u32, u32) { + ) -> ( + &Uint, + &Uint, + &Uint, + &Uint, + ConstChoice, + u32, + u32, + ) { ( &self.m00, &self.m01, &self.m10, &self.m11, + self.pattern, + self.k, + self.k_upper_bound, + ) + } + + pub(crate) const fn to_elements_signed( + &self, + ) -> (Int, Int, Int, Int, u32, u32) { + ( + self.m00.wrapping_neg_if(self.pattern.not()).as_int(), + self.m01.wrapping_neg_if(self.pattern).as_int(), + self.m10.wrapping_neg_if(self.pattern).as_int(), + self.m11.wrapping_neg_if(self.pattern.not()).as_int(), self.k, self.k_upper_bound, ) @@ -61,10 +96,11 @@ impl BinXgcdMatrix { pub(crate) const fn as_elements_mut( &mut self, ) -> ( - &mut Int, - &mut Int, - &mut Int, - &mut Int, + &mut Uint, + &mut Uint, + &mut Uint, + &mut Uint, + &mut ConstChoice, &mut u32, &mut u32, ) { @@ -73,6 +109,7 @@ impl BinXgcdMatrix { &mut self.m01, &mut self.m10, &mut self.m11, + &mut self.pattern, &mut self.k, &mut self.k_upper_bound, ) @@ -91,8 +128,12 @@ impl BinXgcdMatrix { let b0 = ExtendedInt::from_product(b, self.m01); let b1 = ExtendedInt::from_product(b, self.m11); ( - a0.wrapping_add(&b0).div_2k(self.k), - a1.wrapping_add(&b1).div_2k(self.k), + a0.wrapping_sub(&b0) + .div_2k(self.k) + .wrapping_neg_if(self.pattern.not()), + a1.wrapping_sub(&b1) + .div_2k(self.k) + .wrapping_neg_if(self.pattern.not()), ) } @@ -114,11 +155,13 @@ impl BinXgcdMatrix { let d0 = rhs.m01.wrapping_mul(&self.m10); let d1 = rhs.m11.wrapping_mul(&self.m11); let d = d0.wrapping_add(&d1); + BinXgcdMatrix::new( a, b, c, d, + self.pattern.eq(rhs.pattern), self.k + rhs.k, self.k_upper_bound + rhs.k_upper_bound, ) @@ -127,28 +170,24 @@ impl BinXgcdMatrix { /// Swap the rows of this matrix if `swap` is truthy. Otherwise, do nothing. #[inline] pub(crate) const fn conditional_swap_rows(&mut self, swap: ConstChoice) { - Int::conditional_swap(&mut self.m00, &mut self.m10, swap); - Int::conditional_swap(&mut self.m01, &mut self.m11, swap); + Uint::conditional_swap(&mut self.m00, &mut self.m10, swap); + Uint::conditional_swap(&mut self.m01, &mut self.m11, swap); + self.pattern = self.pattern.xor(swap); } /// Swap the rows of this matrix. #[inline] pub(crate) const fn swap_rows(&mut self) { - self.conditional_swap_rows(ConstChoice::TRUE) - } - - /// Add the right column to the left if `add` is truthy. Otherwise, do nothing. - #[inline] - pub(crate) const fn conditional_add_right_column_to_left(&mut self, add: ConstChoice) { - self.m00 = Int::select(&self.m00, &self.m00.wrapping_add(&self.m01), add); - self.m10 = Int::select(&self.m10, &self.m10.wrapping_add(&self.m11), add); + self.conditional_swap_rows(ConstChoice::TRUE); } /// Subtract the bottom row from the top if `subtract` is truthy. Otherwise, do nothing. #[inline] pub(crate) const fn conditional_subtract_bottom_row_from_top(&mut self, subtract: ConstChoice) { - self.m00 = Int::select(&self.m00, &self.m00.wrapping_sub(&self.m10), subtract); - self.m01 = Int::select(&self.m01, &self.m01.wrapping_sub(&self.m11), subtract); + // Note: because the signs of the internal representation are stored in `pattern`, + // subtracting one row from another involves _adding_ these rows instead. + self.m00 = Uint::select(&self.m00, &self.m00.wrapping_add(&self.m10), subtract); + self.m01 = Uint::select(&self.m01, &self.m01.wrapping_add(&self.m11), subtract); } /// Subtract the right column from the left if `subtract` is truthy. Otherwise, do nothing. @@ -157,8 +196,19 @@ impl BinXgcdMatrix { &mut self, subtract: ConstChoice, ) { - self.m00 = Int::select(&self.m00, &self.m00.wrapping_sub(&self.m01), subtract); - self.m10 = Int::select(&self.m10, &self.m10.wrapping_sub(&self.m11), subtract); + // Note: because the signs of the internal representation are stored in `pattern`, + // subtracting one column from another involves _adding_ these columns instead. + self.m00 = Uint::select(&self.m00, &self.m00.wrapping_add(&self.m01), subtract); + self.m10 = Uint::select(&self.m10, &self.m10.wrapping_add(&self.m11), subtract); + } + + /// If `add` is truthy, add the right column to the left. Otherwise, do nothing. + #[inline] + pub(crate) const fn conditional_add_right_column_to_left(&mut self, add: ConstChoice) { + // Note: because the signs of the internal representation are stored in `pattern`, + // subtracting one column from another involves _adding_ these columns instead. + self.m00 = Uint::select(&self.m00, &self.m01.wrapping_sub(&self.m00), add); + self.m10 = Uint::select(&self.m10, &self.m11.wrapping_sub(&self.m10), add); } /// Double the bottom row of this matrix if `double` is truthy. Otherwise, do nothing. @@ -166,44 +216,30 @@ impl BinXgcdMatrix { pub(crate) const fn conditional_double_bottom_row(&mut self, double: ConstChoice) { // safe to vartime; shr_vartime is variable in the value of shift only. Since this shift // is a public constant, the constant time property of this algorithm is not impacted. - self.m10 = Int::select(&self.m10, &self.m10.shl_vartime(1), double); - self.m11 = Int::select(&self.m11, &self.m11.shl_vartime(1), double); + self.m10 = Uint::select(&self.m10, &self.m10.shl_vartime(1), double); + self.m11 = Uint::select(&self.m11, &self.m11.shl_vartime(1), double); self.k = double.select_u32(self.k, self.k + 1); self.k_upper_bound += 1; } - /// Negate the elements in the top row if `negate` is truthy. Otherwise, do nothing. - #[inline] - pub(crate) const fn conditional_negate_top_row(&mut self, negate: ConstChoice) { - self.m00 = self.m00.wrapping_neg_if(negate); - self.m01 = self.m01.wrapping_neg_if(negate); - } - - /// Negate the elements in the bottom row if `negate` is truthy. Otherwise, do nothing. + /// Negate the elements in this matrix if `negate` is truthy. Otherwise, do nothing. #[inline] - pub(crate) const fn conditional_negate_bottom_row(&mut self, negate: ConstChoice) { - self.m10 = self.m10.wrapping_neg_if(negate); - self.m11 = self.m11.wrapping_neg_if(negate); - } - - /// Negate the elements in the right column if `negate` is truthy. Otherwise, do nothing. - #[inline] - pub(crate) const fn conditional_negate_right_column(&mut self, negate: ConstChoice) { - self.m01 = self.m01.wrapping_neg_if(negate); - self.m11 = self.m11.wrapping_neg_if(negate); + pub(crate) const fn conditional_negate(&mut self, negate: ConstChoice) { + self.pattern = self.pattern.xor(negate); } } #[cfg(test)] mod tests { use crate::modular::bingcd::matrix::BinXgcdMatrix; - use crate::{ConstChoice, I64, I256, Int, U64, Uint}; + use crate::{ConstChoice, U64, U256, Uint}; const X: BinXgcdMatrix<4> = BinXgcdMatrix::new( - I256::from_i64(1i64), - I256::from_i64(7i64), - I256::from_i64(23i64), - I256::from_i64(53i64), + U256::from_u64(1u64), + U256::from_u64(7u64), + U256::from_u64(23u64), + U256::from_u64(53u64), + ConstChoice::TRUE, 1, 2, ); @@ -213,10 +249,11 @@ mod tests { let a = U64::from_be_hex("CA048AFA63CD6A1F"); let b = U64::from_be_hex("AE693BF7BE8E5566"); let matrix = BinXgcdMatrix { - m00: I64::from_be_hex("0000000000000120"), - m01: I64::from_be_hex("FFFFFFFFFFFFFF30"), - m10: I64::from_be_hex("FFFFFFFFFFFFFECA"), - m11: I64::from_be_hex("00000000000002A7"), + m00: U64::from_be_hex("0000000000000120"), + m01: U64::from_be_hex("00000000000000D0"), + m10: U64::from_be_hex("0000000000000136"), + m11: U64::from_be_hex("00000000000002A7"), + pattern: ConstChoice::TRUE, k: 17, k_upper_bound: 17, }; @@ -233,18 +270,17 @@ mod tests { } #[test] - fn test_conditional_add_right_column_to_left() { + fn test_swap() { let mut y = X.clone(); - y.conditional_add_right_column_to_left(ConstChoice::FALSE); - assert_eq!(y, X); - y.conditional_add_right_column_to_left(ConstChoice::TRUE); + y.swap_rows(); assert_eq!( y, BinXgcdMatrix::new( - Int::from_i64(8i64), - Int::from_i64(7i64), - Int::from_i64(76i64), - Int::from_i64(53i64), + Uint::from(23u32), + Uint::from(53u32), + Uint::from(1u32), + Uint::from(7u32), + ConstChoice::FALSE, 1, 2 ) @@ -260,48 +296,11 @@ mod tests { assert_eq!( y, BinXgcdMatrix::new( - Int::from(23i32), - Int::from(53i32), - Int::from(1i32), - Int::from(7i32), - 1, - 2 - ) - ); - } - - #[test] - fn test_conditional_subtract_bottom_row_from_top() { - let mut y = X.clone(); - y.conditional_subtract_bottom_row_from_top(ConstChoice::FALSE); - assert_eq!(y, X); - y.conditional_subtract_bottom_row_from_top(ConstChoice::TRUE); - assert_eq!( - y, - BinXgcdMatrix::new( - Int::from(-22i32), - Int::from(-46i32), - Int::from(23i32), - Int::from(53i32), - 1, - 2 - ) - ); - } - - #[test] - fn test_conditional_subtract_right_column_from_left() { - let mut y = X.clone(); - y.conditional_subtract_right_column_from_left(ConstChoice::FALSE); - assert_eq!(y, X); - y.conditional_subtract_right_column_from_left(ConstChoice::TRUE); - assert_eq!( - y, - BinXgcdMatrix::new( - Int::from(-6i32), - Int::from(7i32), - Int::from(-30i32), - Int::from(53i32), + Uint::from(23u32), + Uint::from(53u32), + Uint::from(1u32), + Uint::from(7u32), + ConstChoice::FALSE, 1, 2 ) @@ -309,18 +308,19 @@ mod tests { } #[test] - fn test_conditional_negate_top_row() { + fn test_conditional_add_right_column_to_left() { let mut y = X.clone(); - y.conditional_negate_top_row(ConstChoice::FALSE); + y.conditional_add_right_column_to_left(ConstChoice::FALSE); assert_eq!(y, X); - y.conditional_negate_top_row(ConstChoice::TRUE); + y.conditional_add_right_column_to_left(ConstChoice::TRUE); assert_eq!( y, BinXgcdMatrix::new( - Int::from(-1i32), - Int::from(-7i32), - Int::from(23i32), - Int::from(53i32), + Uint::from(6u32), + Uint::from(7u32), + Uint::from(30u32), + Uint::from(53u32), + ConstChoice::TRUE, 1, 2 ) @@ -328,18 +328,19 @@ mod tests { } #[test] - fn test_conditional_negate_bottom_row() { + fn test_conditional_subtract_bottom_row_from_top() { let mut y = X.clone(); - y.conditional_negate_bottom_row(ConstChoice::FALSE); + y.conditional_subtract_bottom_row_from_top(ConstChoice::FALSE); assert_eq!(y, X); - y.conditional_negate_bottom_row(ConstChoice::TRUE); + y.conditional_subtract_bottom_row_from_top(ConstChoice::TRUE); assert_eq!( y, BinXgcdMatrix::new( - Int::from(1i32), - Int::from(7i32), - Int::from(-23i32), - Int::from(-53i32), + Uint::from(24u32), + Uint::from(60u32), + Uint::from(23u32), + Uint::from(53u32), + ConstChoice::TRUE, 1, 2 ) @@ -347,18 +348,19 @@ mod tests { } #[test] - fn test_conditional_negate_right_column() { + fn test_conditional_subtract_right_column_from_left() { let mut y = X.clone(); - y.conditional_negate_right_column(ConstChoice::FALSE); + y.conditional_subtract_right_column_from_left(ConstChoice::FALSE); assert_eq!(y, X); - y.conditional_negate_right_column(ConstChoice::TRUE); + y.conditional_subtract_right_column_from_left(ConstChoice::TRUE); assert_eq!( y, BinXgcdMatrix::new( - Int::from(1i32), - Int::from(-7i32), - Int::from(23i32), - Int::from(-53i32), + Uint::from(8u32), + Uint::from(7u32), + Uint::from(76u32), + Uint::from(53u32), + ConstChoice::TRUE, 1, 2 ) @@ -372,10 +374,11 @@ mod tests { assert_eq!( y, BinXgcdMatrix::new( - Int::from(1i32), - Int::from(7i32), - Int::from(23i32), - Int::from(53i32), + Uint::from(1u32), + Uint::from(7u32), + Uint::from(23u32), + Uint::from(53u32), + ConstChoice::TRUE, 1, 3 ) @@ -384,10 +387,11 @@ mod tests { assert_eq!( y, BinXgcdMatrix::new( - Int::from(1i32), - Int::from(7i32), - Int::from(46i32), - Int::from(106i32), + Uint::from(1u32), + Uint::from(7u32), + Uint::from(46u32), + Uint::from(106u32), + ConstChoice::TRUE, 2, 4 ) @@ -400,10 +404,11 @@ mod tests { assert_eq!( res, BinXgcdMatrix::new( - I256::from_i64(162i64), - I256::from_i64(378i64), - I256::from_i64(1242i64), - I256::from_i64(2970i64), + U256::from(162u64), + U256::from(378u64), + U256::from(1242u64), + U256::from(2970u64), + ConstChoice::TRUE, 2, 4 ) diff --git a/src/modular/bingcd/tools.rs b/src/modular/bingcd/tools.rs index ce7588565..0e57fafc4 100644 --- a/src/modular/bingcd/tools.rs +++ b/src/modular/bingcd/tools.rs @@ -1,4 +1,4 @@ -use crate::{ConstChoice, Int, Odd, Uint}; +use crate::{ConstChoice, Odd, Uint}; /// `const` equivalent of `u32::max(a, b)`. pub(crate) const fn const_max(a: u32, b: u32) -> u32 { @@ -10,24 +10,13 @@ pub(crate) const fn const_min(a: u32, b: u32) -> u32 { ConstChoice::from_u32_lt(a, b).select_u32(b, a) } -impl Int { - /// Compute `self / 2^k mod q`. Executes in time variable in `k_bound`. This value should be - /// chosen as an inclusive upperbound to the value of `k`. - #[inline] - pub(crate) const fn div_2k_mod_q(&self, k: u32, k_bound: u32, q: &Odd>) -> Self { - let (abs, sgn) = self.abs_sign(); - let abs_div_2k_mod_q = abs.div_2k_mod_q(k, k_bound, q); - Int::new_from_abs_sign(abs_div_2k_mod_q, sgn).expect("no overflow") - } -} - impl Uint { /// Compute `self / 2^k mod q`. /// /// Executes in time variable in `k_bound`. This value should be /// chosen as an inclusive upperbound to the value of `k`. #[inline] - const fn div_2k_mod_q(mut self, k: u32, k_bound: u32, q: &Odd) -> Self { + pub(crate) const fn div_2k_mod_q(mut self, k: u32, k_bound: u32, q: &Odd) -> Self { // 1 / 2 mod q // = (q + 1) / 2 mod q // = (q - 1) / 2 + 1 mod q diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 44005c8b0..5467e822f 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -54,16 +54,14 @@ impl OddUintBinxgcdOutput { /// Obtain the bezout coefficients `(x, y)` such that `lhs * x + rhs * y = gcd`. const fn bezout_coefficients(&self) -> (Int, Int) { - let (m00, m10, ..) = self.matrix.as_elements(); - (*m00, *m10) + let (m00, m10, ..) = self.matrix.to_elements_signed(); + (m00, m10) } /// Obtain the quotients `lhs/gcd` and `rhs/gcd` from `matrix`. const fn quotients(&self) -> (Uint, Uint) { - let (.., m10, m11, _, _) = self.matrix.as_elements(); - let lhs_on_gcd = m11.abs(); - let rhs_on_gcd = m10.abs(); - (lhs_on_gcd, rhs_on_gcd) + let (.., rhs_div_gcd, lhs_div_gcd, _, _, _) = self.matrix.as_elements(); + (*lhs_div_gcd, *rhs_div_gcd) } } @@ -112,9 +110,11 @@ impl Odd> { // Modify the output to negate the transformation applied to the input. let matrix = &mut output.matrix; - matrix.conditional_subtract_right_column_from_left(rhs_is_even.and(rhs_gt_lhs)); - matrix.conditional_add_right_column_to_left(rhs_is_even.and(rhs_gt_lhs.not())); - matrix.conditional_negate_right_column(rhs_is_even.and(rhs_gt_lhs.not())); + let case_one = rhs_is_even.and(rhs_gt_lhs); + matrix.conditional_subtract_right_column_from_left(case_one); + let case_two = rhs_is_even.and(rhs_gt_lhs.not()); + matrix.conditional_add_right_column_to_left(case_two); + matrix.conditional_negate(case_two); output.process() } @@ -230,9 +230,9 @@ impl Odd> { (a, a_sgn) = updated_a.wrapping_drop_extension(); (b, b_sgn) = updated_b.wrapping_drop_extension(); + // TODO: this is sketchy! matrix = update_matrix.wrapping_mul_right(&matrix); - matrix.conditional_negate_top_row(a_sgn); - matrix.conditional_negate_bottom_row(b_sgn); + matrix.conditional_negate(a_sgn); } let gcd = a @@ -355,7 +355,7 @@ mod tests { mod test_extract_quotients { use crate::modular::bingcd::matrix::BinXgcdMatrix; use crate::modular::bingcd::xgcd::OddUintBinxgcdOutput; - use crate::{Int, U64, Uint}; + use crate::{ConstChoice, U64, Uint}; fn raw_binxgcdoutput_setup( matrix: BinXgcdMatrix, @@ -377,10 +377,11 @@ mod tests { #[test] fn test_extract_quotients_basic() { let output = raw_binxgcdoutput_setup(BinXgcdMatrix::<{ U64::LIMBS }>::new( - Int::ZERO, - Int::ZERO, - Int::from(5i32), - Int::from(-7i32), + Uint::ZERO, + Uint::ZERO, + Uint::from(5u32), + Uint::from(7u32), + ConstChoice::FALSE, 0, 0, )); @@ -389,10 +390,11 @@ mod tests { assert_eq!(rhs_on_gcd, Uint::from(5u32)); let output = raw_binxgcdoutput_setup(BinXgcdMatrix::<{ U64::LIMBS }>::new( - Int::ZERO, - Int::ZERO, - Int::from(-7i32), - Int::from(5i32), + Uint::ZERO, + Uint::ZERO, + Uint::from(7u32), + Uint::from(5u32), + ConstChoice::TRUE, 0, 0, )); @@ -405,7 +407,7 @@ mod tests { mod test_derive_bezout_coefficients { use crate::modular::bingcd::matrix::BinXgcdMatrix; use crate::modular::bingcd::xgcd::OddUintBinxgcdOutput; - use crate::{I64, Int, U64, Uint}; + use crate::{ConstChoice, Int, U64, Uint}; #[test] fn test_derive_bezout_coefficients_unit() { @@ -424,10 +426,11 @@ mod tests { let mut output = OddUintBinxgcdOutput { gcd: Uint::ONE.to_odd().unwrap(), matrix: BinXgcdMatrix::new( - I64::from(2i32), - I64::from(3i32), - I64::from(4i32), - I64::from(5i32), + U64::from(2u32), + U64::from(3u32), + U64::from(4u32), + U64::from(5u32), + ConstChoice::TRUE, 0, 0, ), @@ -435,22 +438,23 @@ mod tests { output.remove_matrix_factors(); let (x, y) = output.bezout_coefficients(); assert_eq!(x, Int::from(2i32)); - assert_eq!(y, Int::from(3i32)); + assert_eq!(y, Int::from(-3i32)); let mut output = OddUintBinxgcdOutput { gcd: Uint::ONE.to_odd().unwrap(), matrix: BinXgcdMatrix::new( - I64::from(2i32), - I64::from(3i32), - I64::from(3i32), - I64::from(5i32), + U64::from(2u32), + U64::from(3u32), + U64::from(3u32), + U64::from(5u32), + ConstChoice::FALSE, 0, 1, ), }; output.remove_matrix_factors(); let (x, y) = output.bezout_coefficients(); - assert_eq!(x, Int::from(2i32)); + assert_eq!(x, Int::from(-2i32)); assert_eq!(y, Int::from(3i32)); } @@ -459,10 +463,11 @@ mod tests { let mut output = OddUintBinxgcdOutput { gcd: Uint::ONE.to_odd().unwrap(), matrix: BinXgcdMatrix::new( - I64::from(2i32), - I64::from(6i32), - I64::from(3i32), - I64::from(5i32), + U64::from(2u32), + U64::from(6u32), + U64::from(3u32), + U64::from(5u32), + ConstChoice::TRUE, 1, 1, ), @@ -470,22 +475,23 @@ mod tests { output.remove_matrix_factors(); let (x, y) = output.bezout_coefficients(); assert_eq!(x, Int::ONE); - assert_eq!(y, Int::from(3i32)); + assert_eq!(y, Int::from(-3i32)); let mut output = OddUintBinxgcdOutput { gcd: Uint::ONE.to_odd().unwrap(), matrix: BinXgcdMatrix::new( - I64::from(120i32), - I64::from(64i32), - I64::from(7i32), - I64::from(5i32), + U64::from(120u32), + U64::from(64u32), + U64::from(7u32), + U64::from(5u32), + ConstChoice::FALSE, 5, 6, ), }; output.remove_matrix_factors(); let (x, y) = output.bezout_coefficients(); - assert_eq!(x, Int::from(9i32)); + assert_eq!(x, Int::from(-9i32)); assert_eq!(y, Int::from(2i32)); } @@ -494,24 +500,25 @@ mod tests { let mut output = OddUintBinxgcdOutput { gcd: Uint::ONE.to_odd().unwrap(), matrix: BinXgcdMatrix::new( - I64::from(2i32), - I64::from(6i32), - I64::from(7i32), - I64::from(5i32), + U64::from(2u32), + U64::from(6u32), + U64::from(7u32), + U64::from(5u32), + ConstChoice::FALSE, 3, 7, ), }; output.remove_matrix_factors(); let (x, y) = output.bezout_coefficients(); - assert_eq!(x, Int::from(2i32)); + assert_eq!(x, Int::from(-2i32)); assert_eq!(y, Int::from(2i32)); } } mod test_partial_binxgcd { use crate::modular::bingcd::matrix::BinXgcdMatrix; - use crate::{ConstChoice, I64, Odd, U64}; + use crate::{ConstChoice, Odd, U64}; const A: Odd = U64::from_be_hex("CA048AFA63CD6A1F").to_odd().expect("odd"); const B: U64 = U64::from_be_hex("AE693BF7BE8E5566"); @@ -525,10 +532,11 @@ mod tests { assert_eq!( matrix, BinXgcdMatrix::new( - I64::from(8), - I64::from(-4), - I64::from(-2), - I64::from(5), + U64::from(8u64), + U64::from(4u64), + U64::from(2u64), + U64::from(5u64), + ConstChoice::TRUE, 5, 5 ) From 9a8bd06af46b7c6c6bdba78e86a2d845c93e7926 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 25 Mar 2025 09:58:29 +0100 Subject: [PATCH 171/203] Expand binxgcd testing --- src/modular/bingcd/xgcd.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 5467e822f..3538330e3 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -602,12 +602,16 @@ mod tests { assert_eq!(output.lhs_on_gcd, lhs.div(output.gcd.as_nz_ref())); assert_eq!(output.rhs_on_gcd, rhs.div(output.gcd.as_nz_ref())); - // Test the Bezout coefficients + // Test the Bezout coefficients for correctness let (x, y) = output.bezout_coefficients(); assert_eq!( x.widening_mul_uint(&lhs) + y.widening_mul_uint(&rhs), output.gcd.resize().as_int(), ); + + // Test the Bezout coefficients for minimality + assert!(x.abs() <= rhs.div(output.gcd.as_nz_ref())); + assert!(y.abs() <= lhs.div(output.gcd.as_nz_ref())); } fn make_rng() -> ChaChaRng { From dab2bfa64f2b7110711a1c4bcabf6c991321b789 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 25 Mar 2025 10:19:15 +0100 Subject: [PATCH 172/203] Fix typo --- src/modular/bingcd/xgcd.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 3538330e3..226024137 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -54,8 +54,8 @@ impl OddUintBinxgcdOutput { /// Obtain the bezout coefficients `(x, y)` such that `lhs * x + rhs * y = gcd`. const fn bezout_coefficients(&self) -> (Int, Int) { - let (m00, m10, ..) = self.matrix.to_elements_signed(); - (m00, m10) + let (m00, m01, ..) = self.matrix.to_elements_signed(); + (m00, m01) } /// Obtain the quotients `lhs/gcd` and `rhs/gcd` from `matrix`. From bcdb2a4f0330cefaee8de2fd573fa3faa2bcb8ce Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 25 Mar 2025 12:15:10 +0100 Subject: [PATCH 173/203] Make `xgcd` work for full `Uint`s --- src/int/bingcd.rs | 4 +++ src/modular/bingcd/matrix.rs | 15 +------- src/modular/bingcd/xgcd.rs | 70 +++++++++++++++++++----------------- 3 files changed, 42 insertions(+), 47 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index d42c72c87..7ba4c0889 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -193,6 +193,10 @@ mod test { let (x, y) = output.bezout_coefficients(); assert!(x.abs() <= rhs_on_gcd.abs() || rhs_on_gcd.is_zero()); assert!(y.abs() <= lhs_on_gcd.abs() || lhs_on_gcd.is_zero()); + if lhs.abs() != rhs.abs() { + assert!(x.abs() <= rhs_on_gcd.abs().shr(1) || rhs_on_gcd.is_zero()); + assert!(y.abs() <= lhs_on_gcd.abs().shr(1) || lhs_on_gcd.is_zero()); + } // Test the Bezout coefficients for correctness assert_eq!( diff --git a/src/modular/bingcd/matrix.rs b/src/modular/bingcd/matrix.rs index b2df3a7bf..d45e4a55e 100644 --- a/src/modular/bingcd/matrix.rs +++ b/src/modular/bingcd/matrix.rs @@ -1,5 +1,5 @@ use crate::modular::bingcd::extension::ExtendedInt; -use crate::{ConstChoice, Int, Uint}; +use crate::{ConstChoice, Uint}; type Vector = (T, T); @@ -80,19 +80,6 @@ impl BinXgcdMatrix { ) } - pub(crate) const fn to_elements_signed( - &self, - ) -> (Int, Int, Int, Int, u32, u32) { - ( - self.m00.wrapping_neg_if(self.pattern.not()).as_int(), - self.m01.wrapping_neg_if(self.pattern).as_int(), - self.m10.wrapping_neg_if(self.pattern).as_int(), - self.m11.wrapping_neg_if(self.pattern.not()).as_int(), - self.k, - self.k_upper_bound, - ) - } - pub(crate) const fn as_elements_mut( &mut self, ) -> ( diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 226024137..9e628428a 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -54,7 +54,16 @@ impl OddUintBinxgcdOutput { /// Obtain the bezout coefficients `(x, y)` such that `lhs * x + rhs * y = gcd`. const fn bezout_coefficients(&self) -> (Int, Int) { - let (m00, m01, ..) = self.matrix.to_elements_signed(); + let (m00, m01, m10, m11, pattern, ..) = self.matrix.as_elements(); + let m10_sub_m00 = m10.wrapping_sub(m00); + let m11_sub_m01 = m11.wrapping_sub(m01); + let apply = Uint::lte(&m10_sub_m00, m00).and(Uint::lte(&m11_sub_m01, m01)); + let m00 = Uint::select(m00, &m10_sub_m00, apply) + .wrapping_neg_if(apply.xor(pattern.not())) + .as_int(); + let m01 = Uint::select(m01, &m11_sub_m01, apply) + .wrapping_neg_if(apply.xor(pattern)) + .as_int(); (m00, m01) } @@ -92,15 +101,18 @@ impl Odd> { /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, /// leveraging the Binary Extended GCD algorithm. - /// - /// **Warning**: this algorithm is only guaranteed to work for `self` and `rhs` for which the - /// msb is **not** set. May panic otherwise. pub(crate) const fn binxgcd_nz(&self, rhs: &NonZero>) -> UintBinxgcdOutput { + let (lhs_, rhs_) = (self.as_ref(), rhs.as_ref()); + // The `binxgcd` subroutine requires `rhs` needs to be odd. We leverage the equality // gcd(lhs, rhs) = gcd(lhs, |lhs-rhs|) to deal with the case that `rhs` is even. - let (abs_lhs_sub_rhs, rhs_gt_lhs) = - self.as_ref().wrapping_sub(rhs.as_ref()).as_int().abs_sign(); - let rhs_is_even = rhs.as_ref().is_odd().not(); + let rhs_gt_lhs = Uint::gt(rhs_, lhs_); + let rhs_is_even = rhs_.is_odd().not(); + let abs_lhs_sub_rhs = Uint::select( + &lhs_.wrapping_sub(rhs_), + &rhs_.wrapping_sub(lhs_), + rhs_gt_lhs, + ); let rhs_ = Uint::select(rhs.as_ref(), &abs_lhs_sub_rhs, rhs_is_even) .to_odd() .expect("rhs is odd by construction"); @@ -122,9 +134,6 @@ impl Odd> { /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, /// leveraging the Binary Extended GCD algorithm. /// - /// **Warning**: this algorithm is only guaranteed to work for `self` and `rhs` for which the - /// msb is **not** set. May panic otherwise. - /// /// This function switches between the "classic" and "optimized" algorithm at a best-effort /// threshold. When using [Uint]s with `LIMBS` close to the threshold, it may be useful to /// manually test whether the classic or optimized algorithm is faster for your machine. @@ -140,9 +149,6 @@ impl Odd> { /// /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. /// - /// **Warning**: this algorithm is only guaranteed to work for `self` and `rhs` for which the - /// msb is **not** set. May panic otherwise. - /// /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 1. /// . pub(crate) const fn classic_binxgcd(&self, rhs: &Self) -> OddUintBinxgcdOutput { @@ -158,9 +164,7 @@ impl Odd> { /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, /// leveraging the Binary Extended GCD algorithm. /// - /// **Warning**: this algorithm is only guaranteed to work for `self` and `rhs` for which the - /// msb is **not** set. May panic otherwise. Furthermore, at `self` and `rhs` must contain at - /// least 128 bits. + /// **Warning**: `self` and `rhs` must be contained in an [U128] or larger. /// /// Note: this algorithm becomes more efficient than the classical algorithm for [Uint]s with /// relatively many `LIMBS`. A best-effort threshold is presented in [Self::binxgcd]. @@ -179,9 +183,6 @@ impl Odd> { /// Given `(self, rhs)`, computes `(g, x, y)`, s.t. `self * x + rhs * y = g = gcd(self, rhs)`, /// leveraging the optimized Binary Extended GCD algorithm. /// - /// **Warning**: this algorithm is only guaranteed to work for `self` and `rhs` for which the - /// msb is **not** set. May panic otherwise. - /// /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 2. /// /// @@ -206,7 +207,7 @@ impl Odd> { let (mut a, mut b) = (*self.as_ref(), *rhs.as_ref()); let mut matrix = BinXgcdMatrix::UNIT; - let (mut a_sgn, mut b_sgn); + let mut a_sgn; let mut i = 0; while i < Self::MIN_BINGCD_ITERATIONS.div_ceil(K - 1) { i += 1; @@ -228,11 +229,10 @@ impl Odd> { // Update `a` and `b` using the update matrix let (updated_a, updated_b) = update_matrix.wrapping_apply_to((a, b)); (a, a_sgn) = updated_a.wrapping_drop_extension(); - (b, b_sgn) = updated_b.wrapping_drop_extension(); + (b, _) = updated_b.wrapping_drop_extension(); - // TODO: this is sketchy! matrix = update_matrix.wrapping_mul_right(&matrix); - matrix.conditional_negate(a_sgn); + matrix.conditional_negate(a_sgn); // TODO: find a cleaner solution for this } let gcd = a @@ -349,6 +349,7 @@ mod tests { use crate::modular::bingcd::xgcd::UintBinxgcdOutput; use crate::{ConcatMixed, Gcd, Uint}; use core::ops::Div; + use num_traits::Zero; use rand_chacha::ChaChaRng; use rand_core::SeedableRng; @@ -437,8 +438,8 @@ mod tests { }; output.remove_matrix_factors(); let (x, y) = output.bezout_coefficients(); - assert_eq!(x, Int::from(2i32)); - assert_eq!(y, Int::from(-3i32)); + assert_eq!(x, Int::from(-2i32)); + assert_eq!(y, Int::from(2i32)); let mut output = OddUintBinxgcdOutput { gcd: Uint::ONE.to_odd().unwrap(), @@ -454,8 +455,8 @@ mod tests { }; output.remove_matrix_factors(); let (x, y) = output.bezout_coefficients(); - assert_eq!(x, Int::from(-2i32)); - assert_eq!(y, Int::from(3i32)); + assert_eq!(x, Int::from(1i32)); + assert_eq!(y, Int::from(-2i32)); } #[test] @@ -596,7 +597,7 @@ mod tests { Gcd> + ConcatMixed, MixedOutput = Uint>, { // Test the gcd - assert_eq!(lhs.gcd(&rhs), output.gcd); + assert_eq!(lhs.gcd(&rhs), output.gcd, "{} {}", lhs, rhs); // Test the quotients assert_eq!(output.lhs_on_gcd, lhs.div(output.gcd.as_nz_ref())); @@ -612,6 +613,10 @@ mod tests { // Test the Bezout coefficients for minimality assert!(x.abs() <= rhs.div(output.gcd.as_nz_ref())); assert!(y.abs() <= lhs.div(output.gcd.as_nz_ref())); + if lhs != rhs { + assert!(x.abs() <= output.rhs_on_gcd.shr(1) || output.rhs_on_gcd.is_zero()); + assert!(y.abs() <= output.lhs_on_gcd.shr(1) || output.lhs_on_gcd.is_zero()); + } } fn make_rng() -> ChaChaRng { @@ -621,8 +626,8 @@ mod tests { mod test_binxgcd_nz { use crate::modular::bingcd::xgcd::tests::{make_rng, test_xgcd}; use crate::{ - ConcatMixed, Gcd, Int, RandomMod, U64, U128, U192, U256, U384, U512, U768, U1024, - U2048, U4096, U8192, Uint, + ConcatMixed, Gcd, Int, Random, U64, U128, U192, U256, U384, U512, U768, U1024, U2048, + U4096, U8192, Uint, }; fn binxgcd_nz_test( @@ -653,10 +658,9 @@ mod tests { // Randomized test cases let mut rng = make_rng(); - let bound = Int::MIN.as_uint().to_nz().unwrap(); for _ in 0..100 { - let x = Uint::::random_mod(&mut rng, &bound).bitor(&Uint::ONE); - let y = Uint::::random_mod(&mut rng, &bound).saturating_add(&Uint::ONE); + let x = Uint::::random(&mut rng).bitor(&Uint::ONE); + let y = Uint::::random(&mut rng).saturating_add(&Uint::ONE); binxgcd_nz_test(x, y); } } From b33ae6070072d9d36a162cc5508da986b0782532 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 25 Mar 2025 12:58:04 +0100 Subject: [PATCH 174/203] Fix compile issues --- src/int/bingcd.rs | 124 +++++++++++++++++++++++++------------ src/modular/bingcd/xgcd.rs | 110 ++++++++++++++++++++++---------- src/odd.rs | 4 +- 3 files changed, 164 insertions(+), 74 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index 7ba4c0889..756b06917 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -162,10 +162,14 @@ mod test { use crate::int::bingcd::BinXgcdOutput; use crate::{ConcatMixed, Int, Uint}; use num_traits::Zero; + + #[cfg(feature = "rand_core")] use rand_chacha::ChaChaRng; + #[cfg(feature = "rand_core")] use rand_core::SeedableRng; - fn make_rng() -> ChaChaRng { + #[cfg(feature = "rand_core")] + pub(crate) fn make_rng() -> ChaChaRng { ChaChaRng::from_seed([0; 32]) } @@ -206,12 +210,17 @@ mod test { } mod test_int_binxgcd { - use crate::int::bingcd::test::{binxgcd_test, make_rng}; + use crate::int::bingcd::test::binxgcd_test; use crate::{ - ConcatMixed, Gcd, Int, Random, U64, U128, U192, U256, U384, U512, U768, U1024, U2048, - U4096, U8192, Uint, + ConcatMixed, Gcd, Int, U64, U128, U192, U256, U384, U512, U768, U1024, U2048, U4096, + U8192, Uint, }; + #[cfg(feature = "rand_core")] + use crate::Random; + #[cfg(feature = "rand_core")] + use crate::int::bingcd::test::make_rng; + fn int_binxgcd_test( lhs: Int, rhs: Int, @@ -222,6 +231,20 @@ mod test { binxgcd_test(lhs, rhs, lhs.binxgcd(&rhs)) } + #[cfg(feature = "rand_core")] + fn int_binxgcd_randomized_tests(iterations: u32) + where + Uint: ConcatMixed, MixedOutput = Uint>, + Int: Gcd>, + { + let mut rng = make_rng(); + for _ in 0..iterations { + let x = Int::random(&mut rng); + let y = Int::random(&mut rng); + int_binxgcd_test(x, y); + } + } + fn int_binxgcd_tests() where Uint: ConcatMixed, MixedOutput = Uint>, @@ -253,12 +276,8 @@ mod test { int_binxgcd_test(Int::MAX, Int::ONE); int_binxgcd_test(Int::MAX, Int::MAX); - let mut rng = make_rng(); - for _ in 0..100 { - let x = Int::random(&mut rng); - let y = Int::random(&mut rng); - int_binxgcd_test(x, y); - } + #[cfg(feature = "rand_core")] + int_binxgcd_randomized_tests(100); } #[test] @@ -276,12 +295,15 @@ mod test { } mod test_nonzero_int_binxgcd { - use crate::int::bingcd::test::{binxgcd_test, make_rng}; + use crate::int::bingcd::test::binxgcd_test; use crate::{ - ConcatMixed, Gcd, Int, RandomMod, U64, U128, U192, U256, U384, U512, U768, U1024, - U2048, U4096, U8192, Uint, + ConcatMixed, Gcd, Int, U64, U128, U192, U256, U384, U512, U768, U1024, U2048, U4096, + U8192, Uint, }; + #[cfg(feature = "rand_core")] + use crate::{Random, int::bingcd::test::make_rng}; + fn nz_int_binxgcd_test( lhs: Int, rhs: Int, @@ -293,6 +315,20 @@ mod test { binxgcd_test(lhs, rhs, output); } + #[cfg(feature = "rand_core")] + fn nz_int_binxgcd_randomized_tests(iterations: u32) + where + Uint: ConcatMixed, MixedOutput = Uint>, + Int: Gcd>, + { + let mut rng = make_rng(); + for _ in 0..iterations { + let x = Uint::random(&mut rng).as_int(); + let y = Uint::random(&mut rng).as_int(); + nz_int_binxgcd_test(x, y); + } + } + fn nz_int_binxgcd_tests() where Uint: ConcatMixed, MixedOutput = Uint>, @@ -315,13 +351,8 @@ mod test { nz_int_binxgcd_test(Int::MAX, Int::ONE); nz_int_binxgcd_test(Int::MAX, Int::MAX); - let mut rng = make_rng(); - let bound = Int::MIN.abs().to_nz().unwrap(); - for _ in 0..100 { - let x = Uint::random_mod(&mut rng, &bound).as_int(); - let y = Uint::random_mod(&mut rng, &bound).as_int(); - nz_int_binxgcd_test(x, y); - } + #[cfg(feature = "rand_core")] + nz_int_binxgcd_randomized_tests(100); } #[test] @@ -339,12 +370,15 @@ mod test { } mod test_odd_int_binxgcd { - use crate::int::bingcd::test::{binxgcd_test, make_rng}; + use crate::int::bingcd::test::binxgcd_test; use crate::{ - ConcatMixed, Int, Random, U64, U128, U192, U256, U384, U512, U768, U1024, U2048, U4096, - U8192, Uint, + ConcatMixed, Int, U64, U128, U192, U256, U384, U512, U768, U1024, U2048, U4096, U8192, + Uint, }; + #[cfg(feature = "rand_core")] + use crate::{Random, int::bingcd::test::make_rng}; + fn odd_int_binxgcd_test( lhs: Int, rhs: Int, @@ -355,32 +389,42 @@ mod test { binxgcd_test(lhs, rhs, output); } - fn odd_int_binxgcd_tests() - where + #[cfg(feature = "rand_core")] + fn odd_int_binxgcd_randomized_tests( + iterations: u32, + ) where Uint: ConcatMixed, MixedOutput = Uint>, { - // let neg_max = Int::MAX.wrapping_neg(); - // odd_int_binxgcd_test(neg_max, neg_max); - // odd_int_binxgcd_test(neg_max, Int::MINUS_ONE); - // odd_int_binxgcd_test(neg_max, Int::ONE); - // odd_int_binxgcd_test(neg_max, Int::MAX); - // odd_int_binxgcd_test(Int::ONE, neg_max); - // odd_int_binxgcd_test(Int::ONE, Int::MINUS_ONE); - // odd_int_binxgcd_test(Int::ONE, Int::ONE); - // odd_int_binxgcd_test(Int::ONE, Int::MAX); - // odd_int_binxgcd_test(Int::MAX, neg_max); - // odd_int_binxgcd_test(Int::MAX, Int::MINUS_ONE); - // odd_int_binxgcd_test(Int::MAX, Int::ONE); - // odd_int_binxgcd_test(Int::MAX, Int::MAX); - let mut rng = make_rng(); - for _ in 0..100 { + for _ in 0..iterations { let x = Int::::random(&mut rng).bitor(&Int::ONE); let y = Int::::random(&mut rng); odd_int_binxgcd_test(x, y); } } + fn odd_int_binxgcd_tests() + where + Uint: ConcatMixed, MixedOutput = Uint>, + { + let neg_max = Int::MAX.wrapping_neg(); + odd_int_binxgcd_test(neg_max, neg_max); + odd_int_binxgcd_test(neg_max, Int::MINUS_ONE); + odd_int_binxgcd_test(neg_max, Int::ONE); + odd_int_binxgcd_test(neg_max, Int::MAX); + odd_int_binxgcd_test(Int::ONE, neg_max); + odd_int_binxgcd_test(Int::ONE, Int::MINUS_ONE); + odd_int_binxgcd_test(Int::ONE, Int::ONE); + odd_int_binxgcd_test(Int::ONE, Int::MAX); + odd_int_binxgcd_test(Int::MAX, neg_max); + odd_int_binxgcd_test(Int::MAX, Int::MINUS_ONE); + odd_int_binxgcd_test(Int::MAX, Int::ONE); + odd_int_binxgcd_test(Int::MAX, Int::MAX); + + #[cfg(feature = "rand_core")] + odd_int_binxgcd_randomized_tests(100); + } + #[test] fn test_odd_int_binxgcd() { odd_int_binxgcd_tests::<{ U64::LIMBS }, { U128::LIMBS }>(); diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 9e628428a..605afaf08 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -350,7 +350,10 @@ mod tests { use crate::{ConcatMixed, Gcd, Uint}; use core::ops::Div; use num_traits::Zero; + + #[cfg(feature = "rand_core")] use rand_chacha::ChaChaRng; + #[cfg(feature = "rand_core")] use rand_core::SeedableRng; mod test_extract_quotients { @@ -619,17 +622,23 @@ mod tests { } } + #[cfg(feature = "rand_core")] fn make_rng() -> ChaChaRng { ChaChaRng::from_seed([0; 32]) } mod test_binxgcd_nz { - use crate::modular::bingcd::xgcd::tests::{make_rng, test_xgcd}; + use crate::modular::bingcd::xgcd::tests::test_xgcd; use crate::{ - ConcatMixed, Gcd, Int, Random, U64, U128, U192, U256, U384, U512, U768, U1024, U2048, - U4096, U8192, Uint, + ConcatMixed, Gcd, Int, U64, U128, U192, U256, U384, U512, U768, U1024, U2048, U4096, + U8192, Uint, }; + #[cfg(feature = "rand_core")] + use super::make_rng; + #[cfg(feature = "rand_core")] + use crate::Random; + fn binxgcd_nz_test( lhs: Uint, rhs: Uint, @@ -641,6 +650,20 @@ mod tests { test_xgcd(lhs, rhs, output); } + #[cfg(feature = "rand_core")] + fn binxgcd_nz_randomized_tests(iterations: u32) + where + Uint: + Gcd> + ConcatMixed, MixedOutput = Uint>, + { + let mut rng = make_rng(); + for _ in 0..iterations { + let x = Uint::random(&mut rng).bitor(&Uint::ONE); + let y = Uint::random(&mut rng).saturating_add(&Uint::ONE); + binxgcd_nz_test(x, y); + } + } + fn binxgcd_nz_tests() where Uint: @@ -656,13 +679,8 @@ mod tests { binxgcd_nz_test(odd_upper_bound, odd_upper_bound); binxgcd_nz_test(odd_upper_bound, even_upper_bound); - // Randomized test cases - let mut rng = make_rng(); - for _ in 0..100 { - let x = Uint::::random(&mut rng).bitor(&Uint::ONE); - let y = Uint::::random(&mut rng).saturating_add(&Uint::ONE); - binxgcd_nz_test(x, y); - } + #[cfg(feature = "rand_core")] + binxgcd_nz_randomized_tests(100); } #[test] @@ -680,12 +698,17 @@ mod tests { } mod test_classic_binxgcd { - use crate::modular::bingcd::xgcd::tests::{make_rng, test_xgcd}; + use crate::modular::bingcd::xgcd::tests::test_xgcd; use crate::{ - ConcatMixed, Gcd, Int, RandomMod, U64, U128, U192, U256, U384, U512, U768, U1024, - U2048, U4096, U8192, Uint, + ConcatMixed, Gcd, Int, U64, U128, U192, U256, U384, U512, U768, U1024, U2048, U4096, + U8192, Uint, }; + #[cfg(feature = "rand_core")] + use super::make_rng; + #[cfg(feature = "rand_core")] + use crate::Random; + fn classic_binxgcd_test( lhs: Uint, rhs: Uint, @@ -700,6 +723,21 @@ mod tests { test_xgcd(lhs, rhs, output.process()); } + #[cfg(feature = "rand_core")] + fn classic_binxgcd_randomized_tests( + iterations: u32, + ) where + Uint: + Gcd> + ConcatMixed, MixedOutput = Uint>, + { + let mut rng = make_rng(); + for _ in 0..iterations { + let x = Uint::::random(&mut rng).bitor(&Uint::ONE); + let y = Uint::::random(&mut rng).bitor(&Uint::ONE); + classic_binxgcd_test(x, y); + } + } + fn classic_binxgcd_tests() where Uint: @@ -712,14 +750,8 @@ mod tests { classic_binxgcd_test(upper_bound, Uint::ONE); classic_binxgcd_test(upper_bound, upper_bound); - // Randomized test cases - let mut rng = make_rng(); - let bound = Int::MIN.as_uint().to_nz().unwrap(); - for _ in 0..100 { - let x = Uint::::random_mod(&mut rng, &bound).bitor(&Uint::ONE); - let y = Uint::::random_mod(&mut rng, &bound).bitor(&Uint::ONE); - classic_binxgcd_test(x, y); - } + #[cfg(feature = "rand_core")] + classic_binxgcd_randomized_tests(100); } #[test] @@ -737,12 +769,17 @@ mod tests { } mod test_optimized_binxgcd { - use crate::modular::bingcd::xgcd::tests::{make_rng, test_xgcd}; + use crate::modular::bingcd::xgcd::tests::test_xgcd; use crate::{ - ConcatMixed, Gcd, Int, RandomMod, U128, U192, U256, U384, U512, U768, U1024, U2048, - U4096, U8192, Uint, + ConcatMixed, Gcd, Int, U128, U192, U256, U384, U512, U768, U1024, U2048, U4096, U8192, + Uint, }; + #[cfg(feature = "rand_core")] + use super::make_rng; + #[cfg(feature = "rand_core")] + use crate::Random; + fn optimized_binxgcd_test( lhs: Uint, rhs: Uint, @@ -757,6 +794,21 @@ mod tests { test_xgcd(lhs, rhs, output.process()); } + #[cfg(feature = "rand_core")] + fn optimized_binxgcd_randomized_tests( + iterations: u32, + ) where + Uint: + Gcd> + ConcatMixed, MixedOutput = Uint>, + { + let mut rng = make_rng(); + for _ in 0..iterations { + let x = Uint::::random(&mut rng).bitor(&Uint::ONE); + let y = Uint::::random(&mut rng).bitor(&Uint::ONE); + optimized_binxgcd_test(x, y); + } + } + fn optimized_binxgcd_tests() where Uint: @@ -769,14 +821,8 @@ mod tests { optimized_binxgcd_test(upper_bound, Uint::ONE); optimized_binxgcd_test(upper_bound, upper_bound); - // Randomized test cases - let mut rng = make_rng(); - let bound = Int::MIN.as_uint().to_nz().unwrap(); - for _ in 0..100 { - let x = Uint::::random_mod(&mut rng, &bound).bitor(&Uint::ONE); - let y = Uint::::random_mod(&mut rng, &bound).bitor(&Uint::ONE); - optimized_binxgcd_test(x, y); - } + #[cfg(feature = "rand_core")] + optimized_binxgcd_randomized_tests(100); } #[test] diff --git a/src/odd.rs b/src/odd.rs index 102c29b23..6db562c4c 100644 --- a/src/odd.rs +++ b/src/odd.rs @@ -1,6 +1,6 @@ //! Wrapper type for non-zero integers. -use crate::{Int, Integer, Limb, NonZero, Uint}; +use crate::{Integer, Limb, NonZero, Uint}; use core::{cmp::Ordering, fmt, ops::Deref}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; @@ -8,7 +8,7 @@ use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; use crate::BoxedUint; #[cfg(feature = "rand_core")] -use crate::{Random, rand_core::TryRngCore}; +use crate::{Int, Random, rand_core::TryRngCore}; #[cfg(all(feature = "alloc", feature = "rand_core"))] use crate::RandomBits; From ee55b50378df8fc81b09bd96513d96f5a42e4974 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 25 Mar 2025 13:00:01 +0100 Subject: [PATCH 175/203] Fix clippy --- src/modular/bingcd/matrix.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/modular/bingcd/matrix.rs b/src/modular/bingcd/matrix.rs index d45e4a55e..d87f87efe 100644 --- a/src/modular/bingcd/matrix.rs +++ b/src/modular/bingcd/matrix.rs @@ -258,7 +258,7 @@ mod tests { #[test] fn test_swap() { - let mut y = X.clone(); + let mut y = X; y.swap_rows(); assert_eq!( y, @@ -276,7 +276,7 @@ mod tests { #[test] fn test_conditional_swap() { - let mut y = X.clone(); + let mut y = X; y.conditional_swap_rows(ConstChoice::FALSE); assert_eq!(y, X); y.conditional_swap_rows(ConstChoice::TRUE); @@ -296,7 +296,7 @@ mod tests { #[test] fn test_conditional_add_right_column_to_left() { - let mut y = X.clone(); + let mut y = X; y.conditional_add_right_column_to_left(ConstChoice::FALSE); assert_eq!(y, X); y.conditional_add_right_column_to_left(ConstChoice::TRUE); @@ -316,7 +316,7 @@ mod tests { #[test] fn test_conditional_subtract_bottom_row_from_top() { - let mut y = X.clone(); + let mut y = X; y.conditional_subtract_bottom_row_from_top(ConstChoice::FALSE); assert_eq!(y, X); y.conditional_subtract_bottom_row_from_top(ConstChoice::TRUE); @@ -336,7 +336,7 @@ mod tests { #[test] fn test_conditional_subtract_right_column_from_left() { - let mut y = X.clone(); + let mut y = X; y.conditional_subtract_right_column_from_left(ConstChoice::FALSE); assert_eq!(y, X); y.conditional_subtract_right_column_from_left(ConstChoice::TRUE); @@ -356,7 +356,7 @@ mod tests { #[test] fn test_conditional_double() { - let mut y = X.clone(); + let mut y = X; y.conditional_double_bottom_row(ConstChoice::FALSE); assert_eq!( y, From cd86757e90edd2ed48e62e33d6b4fb1151c6bf19 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 25 Mar 2025 14:08:42 +0100 Subject: [PATCH 176/203] Fix more compile errors --- src/modular/bingcd/matrix.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modular/bingcd/matrix.rs b/src/modular/bingcd/matrix.rs index d87f87efe..2d89f6534 100644 --- a/src/modular/bingcd/matrix.rs +++ b/src/modular/bingcd/matrix.rs @@ -221,7 +221,7 @@ mod tests { use crate::modular::bingcd::matrix::BinXgcdMatrix; use crate::{ConstChoice, U64, U256, Uint}; - const X: BinXgcdMatrix<4> = BinXgcdMatrix::new( + const X: BinXgcdMatrix<{ U256::LIMBS }> = BinXgcdMatrix::new( U256::from_u64(1u64), U256::from_u64(7u64), U256::from_u64(23u64), From 2695bbe6d58b0b1a257b9d54f9edbd19785b0a13 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 25 Mar 2025 16:48:10 +0100 Subject: [PATCH 177/203] Fix `Uint::bingcd` tests --- src/uint/bingcd.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index 15658cdba..019228ff5 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -54,7 +54,6 @@ impl Odd> { } } -#[cfg(feature = "rand_core")] #[cfg(test)] mod tests { use rand_chacha::ChaChaRng; @@ -73,6 +72,19 @@ mod tests { assert_eq!(gcd, bingcd); } + #[cfg(feature = "rand_core")] + fn bingcd_randomized_tests(iterations: u32) + where + Uint: Gcd>, + { + let mut rng = ChaChaRng::from_seed([0; 32]); + for _ in 0..iterations { + let x = Uint::::random(&mut rng); + let y = Uint::::random(&mut rng); + bingcd_test(x, y); + } + } + fn bingcd_tests() where Uint: Gcd>, @@ -96,13 +108,8 @@ mod tests { bingcd_test(Uint::ONE, min); bingcd_test(Uint::MAX, Uint::MAX); - // Randomized test cases - let mut rng = ChaChaRng::from_seed([0; 32]); - for _ in 0..100 { - let x = Uint::::random(&mut rng); - let y = Uint::::random(&mut rng); - bingcd_test(x, y); - } + #[cfg(feature = "rand_core")] + bingcd_randomized_tests(100); } #[test] From 09a0d991b3f2013ef775250a949ea4db5b4c0f0c Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 25 Mar 2025 17:15:34 +0100 Subject: [PATCH 178/203] Implement `binxgcd` for `Uint` --- src/modular/bingcd.rs | 2 + src/modular/bingcd/xgcd.rs | 108 +++++++++----- src/uint/bingcd.rs | 298 +++++++++++++++++++++++++++++-------- 3 files changed, 313 insertions(+), 95 deletions(-) diff --git a/src/modular/bingcd.rs b/src/modular/bingcd.rs index e6a939e83..16523e83d 100644 --- a/src/modular/bingcd.rs +++ b/src/modular/bingcd.rs @@ -7,3 +7,5 @@ mod gcd; mod matrix; pub(crate) mod tools; mod xgcd; + +pub use xgcd::{NonZeroUintBinxgcdOutput, OddUintBinxgcdOutput, UintBinxgcdOutput}; diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 605afaf08..8dc30066c 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -3,18 +3,18 @@ use crate::modular::bingcd::tools::const_max; use crate::{ConstChoice, Int, NonZero, Odd, U64, U128, Uint}; /// Container for the raw output of the Binary XGCD algorithm. -pub(crate) struct OddUintBinxgcdOutput { +pub(crate) struct RawOddUintBinxgcdOutput { gcd: Odd>, matrix: BinXgcdMatrix, } -impl OddUintBinxgcdOutput { +impl RawOddUintBinxgcdOutput { /// Process raw output, constructing an [UintBinxgcdOutput] object. - const fn process(&mut self) -> UintBinxgcdOutput { + pub(crate) const fn process(&mut self) -> OddUintBinxgcdOutput { self.remove_matrix_factors(); let (x, y) = self.bezout_coefficients(); let (lhs_on_gcd, rhs_on_gcd) = self.quotients(); - UintBinxgcdOutput { + OddUintBinxgcdOutput { gcd: self.gcd, x, y, @@ -74,23 +74,60 @@ impl OddUintBinxgcdOutput { } } +pub type UintBinxgcdOutput = BaseUintBinxgcdOutput, LIMBS>; + +pub type NonZeroUintBinxgcdOutput = + BaseUintBinxgcdOutput>, LIMBS>; + +pub type OddUintBinxgcdOutput = BaseUintBinxgcdOutput>, LIMBS>; + /// Container for the processed output of the Binary XGCD algorithm. -pub(crate) struct UintBinxgcdOutput { - pub(crate) gcd: Odd>, - x: Int, - y: Int, - lhs_on_gcd: Uint, - rhs_on_gcd: Uint, +#[derive(Debug)] +pub struct BaseUintBinxgcdOutput { + pub gcd: T, + pub x: Int, + pub y: Int, + pub lhs_on_gcd: Uint, + pub rhs_on_gcd: Uint, } -impl UintBinxgcdOutput { +impl BaseUintBinxgcdOutput { + /// Borrow the elements in this struct. + pub const fn to_components(&self) -> (T, Int, Int, Uint, Uint) { + (self.gcd, self.x, self.y, self.lhs_on_gcd, self.rhs_on_gcd) + } + + /// Mutably borrow the elements in this struct. + pub const fn as_components_mut( + &mut self, + ) -> ( + &mut T, + &mut Int, + &mut Int, + &mut Uint, + &mut Uint, + ) { + ( + &mut self.gcd, + &mut self.x, + &mut self.y, + &mut self.lhs_on_gcd, + &mut self.rhs_on_gcd, + ) + } + + /// The greatest common divisor stored in this object. + pub const fn gcd(&self) -> T { + self.gcd + } + /// Obtain a copy of the Bézout coefficients. - pub(crate) const fn bezout_coefficients(&self) -> (Int, Int) { + pub const fn bezout_coefficients(&self) -> (Int, Int) { (self.x, self.y) } /// Obtain a copy of the quotients `lhs/gcd` and `rhs/gcd`. - pub(crate) const fn quotients(&self) -> (Uint, Uint) { + pub const fn quotients(&self) -> (Uint, Uint) { (self.lhs_on_gcd, self.rhs_on_gcd) } } @@ -101,7 +138,10 @@ impl Odd> { /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, /// leveraging the Binary Extended GCD algorithm. - pub(crate) const fn binxgcd_nz(&self, rhs: &NonZero>) -> UintBinxgcdOutput { + pub(crate) const fn binxgcd_nz( + &self, + rhs: &NonZero>, + ) -> OddUintBinxgcdOutput { let (lhs_, rhs_) = (self.as_ref(), rhs.as_ref()); // The `binxgcd` subroutine requires `rhs` needs to be odd. We leverage the equality @@ -117,7 +157,7 @@ impl Odd> { .to_odd() .expect("rhs is odd by construction"); - let mut output = self.binxgcd(&rhs_); + let mut output = self.binxgcd_(&rhs_); output.remove_matrix_factors(); // Modify the output to negate the transformation applied to the input. @@ -137,7 +177,7 @@ impl Odd> { /// This function switches between the "classic" and "optimized" algorithm at a best-effort /// threshold. When using [Uint]s with `LIMBS` close to the threshold, it may be useful to /// manually test whether the classic or optimized algorithm is faster for your machine. - pub(crate) const fn binxgcd(&self, rhs: &Self) -> OddUintBinxgcdOutput { + pub(crate) const fn binxgcd_(&self, rhs: &Self) -> RawOddUintBinxgcdOutput { if LIMBS < 4 { self.classic_binxgcd(rhs) } else { @@ -151,14 +191,14 @@ impl Odd> { /// /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 1. /// . - pub(crate) const fn classic_binxgcd(&self, rhs: &Self) -> OddUintBinxgcdOutput { + pub(crate) const fn classic_binxgcd(&self, rhs: &Self) -> RawOddUintBinxgcdOutput { let (gcd, _, matrix) = self.partial_binxgcd_vartime::( rhs.as_ref(), Self::MIN_BINGCD_ITERATIONS, ConstChoice::TRUE, ); - OddUintBinxgcdOutput { gcd, matrix } + RawOddUintBinxgcdOutput { gcd, matrix } } /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`, @@ -167,7 +207,7 @@ impl Odd> { /// **Warning**: `self` and `rhs` must be contained in an [U128] or larger. /// /// Note: this algorithm becomes more efficient than the classical algorithm for [Uint]s with - /// relatively many `LIMBS`. A best-effort threshold is presented in [Self::binxgcd]. + /// relatively many `LIMBS`. A best-effort threshold is presented in [Self::binxgcd_]. /// /// Note: the full algorithm has an additional parameter; this function selects the best-effort /// value for this parameter. You might be able to further tune your performance by calling the @@ -175,7 +215,7 @@ impl Odd> { /// /// Ref: Pornin, Optimized Binary GCD for Modular Inversion, Algorithm 2. /// . - pub(crate) const fn optimized_binxgcd(&self, rhs: &Self) -> OddUintBinxgcdOutput { + pub(crate) const fn optimized_binxgcd(&self, rhs: &Self) -> RawOddUintBinxgcdOutput { assert!(Self::BITS >= U128::BITS); self.optimized_binxgcd_::<{ U64::BITS }, { U64::LIMBS }, { U128::LIMBS }>(rhs) } @@ -203,7 +243,7 @@ impl Odd> { >( &self, rhs: &Self, - ) -> OddUintBinxgcdOutput { + ) -> RawOddUintBinxgcdOutput { let (mut a, mut b) = (*self.as_ref(), *rhs.as_ref()); let mut matrix = BinXgcdMatrix::UNIT; @@ -239,7 +279,7 @@ impl Odd> { .to_odd() .expect("gcd of an odd value with something else is always odd"); - OddUintBinxgcdOutput { gcd, matrix } + RawOddUintBinxgcdOutput { gcd, matrix } } /// Executes the optimized Binary GCD inner loop. @@ -346,7 +386,7 @@ impl Odd> { #[cfg(test)] mod tests { - use crate::modular::bingcd::xgcd::UintBinxgcdOutput; + use crate::modular::bingcd::xgcd::OddUintBinxgcdOutput; use crate::{ConcatMixed, Gcd, Uint}; use core::ops::Div; use num_traits::Zero; @@ -358,13 +398,13 @@ mod tests { mod test_extract_quotients { use crate::modular::bingcd::matrix::BinXgcdMatrix; - use crate::modular::bingcd::xgcd::OddUintBinxgcdOutput; + use crate::modular::bingcd::xgcd::RawOddUintBinxgcdOutput; use crate::{ConstChoice, U64, Uint}; fn raw_binxgcdoutput_setup( matrix: BinXgcdMatrix, - ) -> OddUintBinxgcdOutput { - OddUintBinxgcdOutput { + ) -> RawOddUintBinxgcdOutput { + RawOddUintBinxgcdOutput { gcd: Uint::::ONE.to_odd().unwrap(), matrix, } @@ -410,12 +450,12 @@ mod tests { mod test_derive_bezout_coefficients { use crate::modular::bingcd::matrix::BinXgcdMatrix; - use crate::modular::bingcd::xgcd::OddUintBinxgcdOutput; + use crate::modular::bingcd::xgcd::RawOddUintBinxgcdOutput; use crate::{ConstChoice, Int, U64, Uint}; #[test] fn test_derive_bezout_coefficients_unit() { - let mut output = OddUintBinxgcdOutput { + let mut output = RawOddUintBinxgcdOutput { gcd: Uint::ONE.to_odd().unwrap(), matrix: BinXgcdMatrix::<{ U64::LIMBS }>::UNIT, }; @@ -427,7 +467,7 @@ mod tests { #[test] fn test_derive_bezout_coefficients_basic() { - let mut output = OddUintBinxgcdOutput { + let mut output = RawOddUintBinxgcdOutput { gcd: Uint::ONE.to_odd().unwrap(), matrix: BinXgcdMatrix::new( U64::from(2u32), @@ -444,7 +484,7 @@ mod tests { assert_eq!(x, Int::from(-2i32)); assert_eq!(y, Int::from(2i32)); - let mut output = OddUintBinxgcdOutput { + let mut output = RawOddUintBinxgcdOutput { gcd: Uint::ONE.to_odd().unwrap(), matrix: BinXgcdMatrix::new( U64::from(2u32), @@ -464,7 +504,7 @@ mod tests { #[test] fn test_derive_bezout_coefficients_removes_doublings_easy() { - let mut output = OddUintBinxgcdOutput { + let mut output = RawOddUintBinxgcdOutput { gcd: Uint::ONE.to_odd().unwrap(), matrix: BinXgcdMatrix::new( U64::from(2u32), @@ -481,7 +521,7 @@ mod tests { assert_eq!(x, Int::ONE); assert_eq!(y, Int::from(-3i32)); - let mut output = OddUintBinxgcdOutput { + let mut output = RawOddUintBinxgcdOutput { gcd: Uint::ONE.to_odd().unwrap(), matrix: BinXgcdMatrix::new( U64::from(120u32), @@ -501,7 +541,7 @@ mod tests { #[test] fn test_derive_bezout_coefficients_removes_doublings_for_odd_numbers() { - let mut output = OddUintBinxgcdOutput { + let mut output = RawOddUintBinxgcdOutput { gcd: Uint::ONE.to_odd().unwrap(), matrix: BinXgcdMatrix::new( U64::from(2u32), @@ -594,7 +634,7 @@ mod tests { fn test_xgcd( lhs: Uint, rhs: Uint, - output: UintBinxgcdOutput, + output: OddUintBinxgcdOutput, ) where Uint: Gcd> + ConcatMixed, MixedOutput = Uint>, diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index 019228ff5..9dc6970fc 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -3,7 +3,8 @@ //! Ref: use crate::modular::bingcd::tools::const_min; -use crate::{NonZero, Odd, Uint}; +use crate::modular::bingcd::{NonZeroUintBinxgcdOutput, OddUintBinxgcdOutput, UintBinxgcdOutput}; +use crate::{ConstChoice, Int, NonZero, Odd, Uint}; impl Uint { /// Compute the greatest common divisor of `self` and `rhs`. @@ -14,6 +15,49 @@ impl Uint { .expect("self is non zero by construction"); Uint::select(self_nz.bingcd(rhs).as_ref(), rhs, self_is_zero) } + + /// Executes the Binary Extended GCD algorithm. + /// + /// Given `(self, rhs)`, computes `(g, x, y)`, s.t. `self * x + rhs * y = g = gcd(self, rhs)`. + pub const fn binxgcd(&self, rhs: &Self) -> UintBinxgcdOutput { + // Make sure `self` and `rhs` are nonzero. + let self_is_zero = self.is_nonzero().not(); + let self_nz = Uint::select(self, &Uint::ONE, self_is_zero) + .to_nz() + .expect("self is non zero by construction"); + let rhs_is_zero = rhs.is_nonzero().not(); + let rhs_nz = Uint::select(rhs, &Uint::ONE, rhs_is_zero) + .to_nz() + .expect("rhs is non zero by construction"); + + let (gcd, mut x, mut y, mut lhs_on_gcd, mut rhs_on_gcd) = + self_nz.binxgcd(&rhs_nz).to_components(); + + // Correct the gcd in case self and/or rhs was zero + let mut gcd = *gcd.as_ref(); + gcd = Uint::select(&gcd, rhs, self_is_zero); + gcd = Uint::select(&gcd, self, rhs_is_zero); + + // Correct the Bézout coefficients in case self and/or rhs was zero. + x = Int::select(&x, &Int::ZERO, self_is_zero); + y = Int::select(&y, &Int::ONE, self_is_zero); + x = Int::select(&x, &Int::ONE, rhs_is_zero); + y = Int::select(&y, &Int::ZERO, rhs_is_zero); + + // Correct the quotients in case self and/or rhs was zero. + lhs_on_gcd = Uint::select(&lhs_on_gcd, &Uint::ZERO, self_is_zero); + rhs_on_gcd = Uint::select(&rhs_on_gcd, &Uint::ONE, self_is_zero); + lhs_on_gcd = Uint::select(&lhs_on_gcd, &Uint::ONE, rhs_is_zero); + rhs_on_gcd = Uint::select(&rhs_on_gcd, &Uint::ZERO, rhs_is_zero); + + UintBinxgcdOutput { + gcd, + x, + y, + lhs_on_gcd, + rhs_on_gcd, + } + } } impl NonZero> { @@ -36,6 +80,46 @@ impl NonZero> { .to_nz() .expect("gcd of non-zero element with another element is non-zero") } + + /// Execute the Binary Extended GCD algorithm. + /// + /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. + pub const fn binxgcd(&self, rhs: &Self) -> NonZeroUintBinxgcdOutput { + let (mut lhs, mut rhs) = (*self.as_ref(), *rhs.as_ref()); + + // Leverage the property that gcd(2^k * a, 2^k *b) = 2^k * gcd(a, b) + let i = lhs.trailing_zeros(); + let j = rhs.trailing_zeros(); + let k = const_min(i, j); + lhs = lhs.shr(k); + rhs = rhs.shr(k); + + // Note: at this point, either lhs or rhs is odd (or both). + // Swap to make sure lhs is odd. + let swap = ConstChoice::from_u32_lt(j, i); + Uint::conditional_swap(&mut lhs, &mut rhs, swap); + let lhs = lhs.to_odd().expect("odd by construction"); + + let rhs = rhs.to_nz().expect("non-zero by construction"); + let (gcd, mut x, mut y, mut lhs_on_gcd, mut rhs_on_gcd) = + lhs.binxgcd_nz(&rhs).to_components(); + + let gcd = gcd + .as_ref() + .shl(k) + .to_nz() + .expect("is non-zero by construction"); + Int::conditional_swap(&mut x, &mut y, swap); + Uint::conditional_swap(&mut lhs_on_gcd, &mut rhs_on_gcd, swap); + + NonZeroUintBinxgcdOutput { + gcd, + x, + y, + lhs_on_gcd, + rhs_on_gcd, + } + } } impl Odd> { @@ -52,76 +136,168 @@ impl Odd> { self.optimized_bingcd(rhs) } } + + /// Execute the Binary Extended GCD algorithm. + /// + /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. + pub const fn binxgcd(&self, rhs: &Self) -> OddUintBinxgcdOutput { + self.binxgcd_(rhs).process() + } } #[cfg(test)] mod tests { - use rand_chacha::ChaChaRng; - use rand_core::SeedableRng; - - use crate::{ - Gcd, Int, Random, U64, U128, U256, U512, U1024, U2048, U4096, U8192, U16384, Uint, - }; - - fn bingcd_test(lhs: Uint, rhs: Uint) - where - Uint: Gcd>, - { - let gcd = lhs.gcd(&rhs); - let bingcd = lhs.bingcd(&rhs); - assert_eq!(gcd, bingcd); - } + mod bincgd_test { + use rand_chacha::ChaChaRng; + use rand_core::SeedableRng; + + use crate::{ + Gcd, Int, Random, U64, U128, U256, U512, U1024, U2048, U4096, U8192, U16384, Uint, + }; + + fn bingcd_test(lhs: Uint, rhs: Uint) + where + Uint: Gcd>, + { + let gcd = lhs.gcd(&rhs); + let bingcd = lhs.bingcd(&rhs); + assert_eq!(gcd, bingcd); + } + + #[cfg(feature = "rand_core")] + fn bingcd_randomized_tests(iterations: u32) + where + Uint: Gcd>, + { + let mut rng = ChaChaRng::from_seed([0; 32]); + for _ in 0..iterations { + let x = Uint::::random(&mut rng); + let y = Uint::::random(&mut rng); + bingcd_test(x, y); + } + } + + fn bingcd_tests() + where + Uint: Gcd>, + { + // Edge cases + let min = Int::MIN.abs(); + bingcd_test(Uint::ZERO, Uint::ZERO); + bingcd_test(Uint::ZERO, Uint::ONE); + bingcd_test(Uint::ZERO, min); + bingcd_test(Uint::ZERO, Uint::MAX); + bingcd_test(Uint::ONE, Uint::ZERO); + bingcd_test(Uint::ONE, Uint::ONE); + bingcd_test(Uint::ONE, min); + bingcd_test(Uint::ONE, Uint::MAX); + bingcd_test(min, Uint::ZERO); + bingcd_test(min, Uint::ONE); + bingcd_test(min, Int::MIN.abs()); + bingcd_test(min, Uint::MAX); + bingcd_test(Uint::MAX, Uint::ZERO); + bingcd_test(Uint::MAX, Uint::ONE); + bingcd_test(Uint::ONE, min); + bingcd_test(Uint::MAX, Uint::MAX); + + #[cfg(feature = "rand_core")] + bingcd_randomized_tests(100); + } - #[cfg(feature = "rand_core")] - fn bingcd_randomized_tests(iterations: u32) - where - Uint: Gcd>, - { - let mut rng = ChaChaRng::from_seed([0; 32]); - for _ in 0..iterations { - let x = Uint::::random(&mut rng); - let y = Uint::::random(&mut rng); - bingcd_test(x, y); + #[test] + fn test_bingcd() { + bingcd_tests::<{ U64::LIMBS }>(); + bingcd_tests::<{ U128::LIMBS }>(); + bingcd_tests::<{ U256::LIMBS }>(); + bingcd_tests::<{ U512::LIMBS }>(); + bingcd_tests::<{ U1024::LIMBS }>(); + bingcd_tests::<{ U2048::LIMBS }>(); + bingcd_tests::<{ U4096::LIMBS }>(); + bingcd_tests::<{ U8192::LIMBS }>(); + bingcd_tests::<{ U16384::LIMBS }>(); } } - fn bingcd_tests() - where - Uint: Gcd>, - { - // Edge cases - let min = Int::MIN.abs(); - bingcd_test(Uint::ZERO, Uint::ZERO); - bingcd_test(Uint::ZERO, Uint::ONE); - bingcd_test(Uint::ZERO, min); - bingcd_test(Uint::ZERO, Uint::MAX); - bingcd_test(Uint::ONE, Uint::ZERO); - bingcd_test(Uint::ONE, Uint::ONE); - bingcd_test(Uint::ONE, min); - bingcd_test(Uint::ONE, Uint::MAX); - bingcd_test(min, Uint::ZERO); - bingcd_test(min, Uint::ONE); - bingcd_test(min, Int::MIN.abs()); - bingcd_test(min, Uint::MAX); - bingcd_test(Uint::MAX, Uint::ZERO); - bingcd_test(Uint::MAX, Uint::ONE); - bingcd_test(Uint::ONE, min); - bingcd_test(Uint::MAX, Uint::MAX); + mod binxgcd_test { + use core::ops::Div; + use rand_chacha::ChaChaRng; + use rand_core::SeedableRng; + + use crate::{ + Concat, Gcd, Int, Random, U64, U128, U256, U512, U1024, U2048, U4096, U8192, U16384, + Uint, + }; + + fn binxgcd_test(lhs: Uint, rhs: Uint) + where + Uint: Gcd> + Concat>, + { + let output = lhs.binxgcd(&rhs); + + assert_eq!(output.gcd, lhs.gcd(&rhs)); + + if output.gcd > Uint::ZERO { + assert_eq!(output.lhs_on_gcd, lhs.div(output.gcd.to_nz().unwrap())); + assert_eq!(output.rhs_on_gcd, rhs.div(output.gcd.to_nz().unwrap())); + } + + let (x, y) = output.bezout_coefficients(); + assert_eq!( + x.widening_mul_uint(&lhs) + y.widening_mul_uint(&rhs), + output.gcd.resize().as_int() + ); + } #[cfg(feature = "rand_core")] - bingcd_randomized_tests(100); - } + fn binxgcd_randomized_tests(iterations: u32) + where + Uint: Gcd> + Concat>, + { + let mut rng = ChaChaRng::from_seed([0; 32]); + for _ in 0..iterations { + let x = Uint::::random(&mut rng); + let y = Uint::::random(&mut rng); + binxgcd_test(x, y); + } + } + + fn binxgcd_tests() + where + Uint: Gcd> + Concat>, + { + // Edge cases + let min = Int::MIN.abs(); + binxgcd_test(Uint::ZERO, Uint::ZERO); + binxgcd_test(Uint::ZERO, Uint::ONE); + binxgcd_test(Uint::ZERO, min); + binxgcd_test(Uint::ZERO, Uint::MAX); + binxgcd_test(Uint::ONE, Uint::ZERO); + binxgcd_test(Uint::ONE, Uint::ONE); + binxgcd_test(Uint::ONE, min); + binxgcd_test(Uint::ONE, Uint::MAX); + binxgcd_test(min, Uint::ZERO); + binxgcd_test(min, Uint::ONE); + binxgcd_test(min, Int::MIN.abs()); + binxgcd_test(min, Uint::MAX); + binxgcd_test(Uint::MAX, Uint::ZERO); + binxgcd_test(Uint::MAX, Uint::ONE); + binxgcd_test(Uint::ONE, min); + binxgcd_test(Uint::MAX, Uint::MAX); - #[test] - fn test_bingcd() { - bingcd_tests::<{ U64::LIMBS }>(); - bingcd_tests::<{ U128::LIMBS }>(); - bingcd_tests::<{ U256::LIMBS }>(); - bingcd_tests::<{ U512::LIMBS }>(); - bingcd_tests::<{ U1024::LIMBS }>(); - bingcd_tests::<{ U2048::LIMBS }>(); - bingcd_tests::<{ U4096::LIMBS }>(); - bingcd_tests::<{ U8192::LIMBS }>(); - bingcd_tests::<{ U16384::LIMBS }>(); + #[cfg(feature = "rand_core")] + binxgcd_randomized_tests(100); + } + + #[test] + fn test_binxgcd() { + binxgcd_tests::<{ U64::LIMBS }, { U128::LIMBS }>(); + binxgcd_tests::<{ U128::LIMBS }, { U256::LIMBS }>(); + binxgcd_tests::<{ U256::LIMBS }, { U512::LIMBS }>(); + binxgcd_tests::<{ U512::LIMBS }, { U1024::LIMBS }>(); + binxgcd_tests::<{ U1024::LIMBS }, { U2048::LIMBS }>(); + binxgcd_tests::<{ U2048::LIMBS }, { U4096::LIMBS }>(); + binxgcd_tests::<{ U4096::LIMBS }, { U8192::LIMBS }>(); + binxgcd_tests::<{ U8192::LIMBS }, { U16384::LIMBS }>(); + } } } From 7559bbc764a266a5175e1ec28a0f55397e1c9046 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 25 Mar 2025 17:20:22 +0100 Subject: [PATCH 179/203] Fix --no-default-features build --- src/uint/bingcd.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index 9dc6970fc..6c5a42a07 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -148,12 +148,14 @@ impl Odd> { #[cfg(test)] mod tests { mod bincgd_test { + #[cfg(feature = "rand_core")] + use crate::Random; + #[cfg(feature = "rand_core")] use rand_chacha::ChaChaRng; + #[cfg(feature = "rand_core")] use rand_core::SeedableRng; - use crate::{ - Gcd, Int, Random, U64, U128, U256, U512, U1024, U2048, U4096, U8192, U16384, Uint, - }; + use crate::{Gcd, Int, U64, U128, U256, U512, U1024, U2048, U4096, U8192, U16384, Uint}; fn bingcd_test(lhs: Uint, rhs: Uint) where @@ -220,12 +222,16 @@ mod tests { mod binxgcd_test { use core::ops::Div; + + #[cfg(feature = "rand_core")] + use crate::Random; + #[cfg(feature = "rand_core")] use rand_chacha::ChaChaRng; + #[cfg(feature = "rand_core")] use rand_core::SeedableRng; use crate::{ - Concat, Gcd, Int, Random, U64, U128, U256, U512, U1024, U2048, U4096, U8192, U16384, - Uint, + Concat, Gcd, Int, U64, U128, U256, U512, U1024, U2048, U4096, U8192, U16384, Uint, }; fn binxgcd_test(lhs: Uint, rhs: Uint) From 73e8463856b81ccce9f00d765dcfb8a43d08979b Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 25 Mar 2025 17:46:40 +0100 Subject: [PATCH 180/203] Simplify `*BinxgcdOutput` component access. --- src/int/bingcd.rs | 157 +++++++++++++++++++++++++++++++++------------ src/uint/bingcd.rs | 18 ++++-- 2 files changed, 129 insertions(+), 46 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index 756b06917..615b650b7 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -2,19 +2,27 @@ //! which is described by Pornin in "Optimized Binary GCD for Modular Inversion". //! Ref: +use crate::modular::bingcd::OddUintBinxgcdOutput; use crate::modular::bingcd::tools::const_min; use crate::{ConstChoice, Int, NonZero, Odd, Uint}; #[derive(Debug)] -pub struct BinXgcdOutput { - gcd: Uint, - x: Int, - y: Int, - lhs_on_gcd: Int, - rhs_on_gcd: Int, +pub struct BaseIntBinxgcdOutput { + pub gcd: T, + pub x: Int, + pub y: Int, + pub lhs_on_gcd: Int, + pub rhs_on_gcd: Int, } -impl BinXgcdOutput { +pub type IntBinxgcdOutput = BaseIntBinxgcdOutput, LIMBS>; + +pub type NonZeroIntBinxgcdOutput = + BaseIntBinxgcdOutput>, LIMBS>; + +pub type OddIntBinxgcdOutput = BaseIntBinxgcdOutput>, LIMBS>; + +impl BaseIntBinxgcdOutput { /// Return the quotients `lhs.gcd` and `rhs/gcd`. pub const fn quotients(&self) -> (Int, Int) { (self.lhs_on_gcd, self.rhs_on_gcd) @@ -45,7 +53,7 @@ impl Int { /// Executes the Binary Extended GCD algorithm. /// /// Given `(self, rhs)`, computes `(g, x, y)`, s.t. `self * x + rhs * y = g = gcd(self, rhs)`. - pub const fn binxgcd(&self, rhs: &Self) -> BinXgcdOutput { + pub const fn binxgcd(&self, rhs: &Self) -> IntBinxgcdOutput { // Make sure `self` and `rhs` are nonzero. let self_is_zero = self.is_nonzero().not(); let self_nz = Int::select(self, &Int::ONE, self_is_zero) @@ -56,30 +64,40 @@ impl Int { .to_nz() .expect("rhs is non zero by construction"); - let mut output = self_nz.binxgcd(&rhs_nz); + let NonZeroIntBinxgcdOutput { + gcd, + mut x, + mut y, + mut lhs_on_gcd, + mut rhs_on_gcd, + } = self_nz.binxgcd(&rhs_nz); // Correct the gcd in case self and/or rhs was zero - let gcd = &mut output.gcd; - *gcd = Uint::select(gcd, &rhs.abs(), self_is_zero); - *gcd = Uint::select(gcd, &self.abs(), rhs_is_zero); + let mut gcd = *gcd.as_ref(); + gcd = Uint::select(&gcd, &rhs.abs(), self_is_zero); + gcd = Uint::select(&gcd, &self.abs(), rhs_is_zero); // Correct the Bézout coefficients in case self and/or rhs was zero. - let (x, y) = output.bezout_coefficients_as_mut(); let signum_self = Int::new_from_abs_sign(Uint::ONE, self.is_negative()).expect("+/- 1"); let signum_rhs = Int::new_from_abs_sign(Uint::ONE, rhs.is_negative()).expect("+/- 1"); - *x = Int::select(x, &Int::ZERO, self_is_zero); - *y = Int::select(y, &signum_rhs, self_is_zero); - *x = Int::select(x, &signum_self, rhs_is_zero); - *y = Int::select(y, &Int::ZERO, rhs_is_zero); + x = Int::select(&x, &Int::ZERO, self_is_zero); + y = Int::select(&y, &signum_rhs, self_is_zero); + x = Int::select(&x, &signum_self, rhs_is_zero); + y = Int::select(&y, &Int::ZERO, rhs_is_zero); // Correct the quotients in case self and/or rhs was zero. - let (lhs_on_gcd, rhs_on_gcd) = output.quotients_as_mut(); - *lhs_on_gcd = Int::select(lhs_on_gcd, &signum_self, rhs_is_zero); - *lhs_on_gcd = Int::select(lhs_on_gcd, &Int::ZERO, self_is_zero); - *rhs_on_gcd = Int::select(rhs_on_gcd, &signum_rhs, self_is_zero); - *rhs_on_gcd = Int::select(rhs_on_gcd, &Int::ZERO, rhs_is_zero); + lhs_on_gcd = Int::select(&lhs_on_gcd, &signum_self, rhs_is_zero); + lhs_on_gcd = Int::select(&lhs_on_gcd, &Int::ZERO, self_is_zero); + rhs_on_gcd = Int::select(&rhs_on_gcd, &signum_rhs, self_is_zero); + rhs_on_gcd = Int::select(&rhs_on_gcd, &Int::ZERO, rhs_is_zero); - output + IntBinxgcdOutput { + gcd, + x, + y, + lhs_on_gcd, + rhs_on_gcd, + } } } @@ -92,7 +110,7 @@ impl NonZero> { /// Execute the Binary Extended GCD algorithm. /// /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. - pub const fn binxgcd(&self, rhs: &Self) -> BinXgcdOutput { + pub const fn binxgcd(&self, rhs: &Self) -> NonZeroIntBinxgcdOutput { let (mut lhs, mut rhs) = (*self.as_ref(), *rhs.as_ref()); // Leverage the property that gcd(2^k * a, 2^k *b) = 2^k * gcd(a, b) @@ -109,18 +127,32 @@ impl NonZero> { let lhs = lhs.to_odd().expect("odd by construction"); let rhs = rhs.to_nz().expect("non-zero by construction"); - let mut output = lhs.binxgcd(&rhs); + let OddIntBinxgcdOutput { + gcd, + mut x, + mut y, + mut lhs_on_gcd, + mut rhs_on_gcd, + } = lhs.binxgcd(&rhs); // Account for the parameter swap - let (x, y) = output.bezout_coefficients_as_mut(); - Int::conditional_swap(x, y, swap); - let (lhs_on_gcd, rhs_on_gcd) = output.quotients_as_mut(); - Int::conditional_swap(lhs_on_gcd, rhs_on_gcd, swap); + Int::conditional_swap(&mut x, &mut y, swap); + Int::conditional_swap(&mut lhs_on_gcd, &mut rhs_on_gcd, swap); // Reintroduce the factor 2^k to the gcd. - output.gcd = output.gcd.shl(k); + let gcd = gcd + .as_ref() + .shl(k) + .to_nz() + .expect("is non-zero by construction"); - output + NonZeroIntBinxgcdOutput { + gcd, + x, + y, + lhs_on_gcd, + rhs_on_gcd, + } } } @@ -133,22 +165,25 @@ impl Odd> { /// Execute the Binary Extended GCD algorithm. /// /// Given `(self, rhs)`, computes `(g, x, y)` s.t. `self * x + rhs * y = g = gcd(self, rhs)`. - pub const fn binxgcd(&self, rhs: &NonZero>) -> BinXgcdOutput { + pub const fn binxgcd(&self, rhs: &NonZero>) -> OddIntBinxgcdOutput { let (abs_lhs, sgn_lhs) = self.abs_sign(); let (abs_rhs, sgn_rhs) = rhs.abs_sign(); - let output = abs_lhs.binxgcd_nz(&abs_rhs); + let OddUintBinxgcdOutput { + gcd, + mut x, + mut y, + lhs_on_gcd: abs_lhs_on_gcd, + rhs_on_gcd: abs_rhs_on_gcd, + } = abs_lhs.binxgcd_nz(&abs_rhs); - let (mut x, mut y) = output.bezout_coefficients(); x = x.wrapping_neg_if(sgn_lhs); y = y.wrapping_neg_if(sgn_rhs); - - let (abs_lhs_on_gcd, abs_rhs_on_gcd) = output.quotients(); let lhs_on_gcd = Int::new_from_abs_sign(abs_lhs_on_gcd, sgn_lhs).expect("no overflow"); let rhs_on_gcd = Int::new_from_abs_sign(abs_rhs_on_gcd, sgn_rhs).expect("no overflow"); - BinXgcdOutput { - gcd: *output.gcd.as_ref(), + OddIntBinxgcdOutput { + gcd, x, y, lhs_on_gcd, @@ -159,7 +194,7 @@ impl Odd> { #[cfg(test)] mod test { - use crate::int::bingcd::BinXgcdOutput; + use crate::int::bingcd::{IntBinxgcdOutput, NonZeroIntBinxgcdOutput, OddIntBinxgcdOutput}; use crate::{ConcatMixed, Int, Uint}; use num_traits::Zero; @@ -168,6 +203,44 @@ mod test { #[cfg(feature = "rand_core")] use rand_core::SeedableRng; + impl From> for IntBinxgcdOutput { + fn from(value: NonZeroIntBinxgcdOutput) -> Self { + let NonZeroIntBinxgcdOutput { + gcd, + x, + y, + lhs_on_gcd, + rhs_on_gcd, + } = value; + IntBinxgcdOutput { + gcd: *gcd.as_ref(), + x, + y, + lhs_on_gcd, + rhs_on_gcd, + } + } + } + + impl From> for IntBinxgcdOutput { + fn from(value: OddIntBinxgcdOutput) -> Self { + let OddIntBinxgcdOutput { + gcd, + x, + y, + lhs_on_gcd, + rhs_on_gcd, + } = value; + IntBinxgcdOutput { + gcd: *gcd.as_ref(), + x, + y, + lhs_on_gcd, + rhs_on_gcd, + } + } + } + #[cfg(feature = "rand_core")] pub(crate) fn make_rng() -> ChaChaRng { ChaChaRng::from_seed([0; 32]) @@ -176,7 +249,7 @@ mod test { fn binxgcd_test( lhs: Int, rhs: Int, - output: BinXgcdOutput, + output: IntBinxgcdOutput, ) where Uint: ConcatMixed, MixedOutput = Uint>, { @@ -312,7 +385,7 @@ mod test { Int: Gcd>, { let output = lhs.to_nz().unwrap().binxgcd(&rhs.to_nz().unwrap()); - binxgcd_test(lhs, rhs, output); + binxgcd_test(lhs, rhs, output.into()); } #[cfg(feature = "rand_core")] @@ -386,7 +459,7 @@ mod test { Uint: ConcatMixed, MixedOutput = Uint>, { let output = lhs.to_odd().unwrap().binxgcd(&rhs.to_nz().unwrap()); - binxgcd_test(lhs, rhs, output); + binxgcd_test(lhs, rhs, output.into()); } #[cfg(feature = "rand_core")] diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index 6c5a42a07..4c99178a8 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -30,8 +30,13 @@ impl Uint { .to_nz() .expect("rhs is non zero by construction"); - let (gcd, mut x, mut y, mut lhs_on_gcd, mut rhs_on_gcd) = - self_nz.binxgcd(&rhs_nz).to_components(); + let NonZeroUintBinxgcdOutput { + gcd, + mut x, + mut y, + mut lhs_on_gcd, + mut rhs_on_gcd, + } = self_nz.binxgcd(&rhs_nz); // Correct the gcd in case self and/or rhs was zero let mut gcd = *gcd.as_ref(); @@ -101,8 +106,13 @@ impl NonZero> { let lhs = lhs.to_odd().expect("odd by construction"); let rhs = rhs.to_nz().expect("non-zero by construction"); - let (gcd, mut x, mut y, mut lhs_on_gcd, mut rhs_on_gcd) = - lhs.binxgcd_nz(&rhs).to_components(); + let OddUintBinxgcdOutput { + gcd, + mut x, + mut y, + mut lhs_on_gcd, + mut rhs_on_gcd, + } = lhs.binxgcd_nz(&rhs); let gcd = gcd .as_ref() From 238793f03ed760c078071ffae9a84647caa44d36 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 25 Mar 2025 18:03:32 +0100 Subject: [PATCH 181/203] Refactor bin(x)gcd benchmarks --- benches/uint.rs | 51 ++++++++++--------------------------------------- 1 file changed, 10 insertions(+), 41 deletions(-) diff --git a/benches/uint.rs b/benches/uint.rs index 6417f859a..19b1853df 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -3,8 +3,8 @@ use criterion::{ BatchSize, BenchmarkGroup, BenchmarkId, Criterion, black_box, criterion_group, criterion_main, }; use crypto_bigint::{ - Gcd, Int, Limb, NonZero, Odd, Random, RandomBits, RandomMod, Reciprocal, U128, U256, U512, - U1024, U2048, U4096, Uint, + Gcd, Limb, NonZero, Odd, Random, RandomBits, RandomMod, Reciprocal, U128, U256, U512, U1024, + U2048, U4096, Uint, }; use rand_chacha::ChaCha8Rng; use rand_core::{RngCore, SeedableRng}; @@ -334,51 +334,28 @@ where { g.bench_function(BenchmarkId::new("gcd", LIMBS), |b| { b.iter_batched( - || { - let f = Uint::::random(rng); - let g = Uint::::random(rng); - (f, g) - }, + || (Uint::::random(rng), Uint::::random(rng)), |(f, g)| black_box(f.gcd(&g)), BatchSize::SmallInput, ) }); g.bench_function(BenchmarkId::new("bingcd", LIMBS), |b| { b.iter_batched( - || { - let f = Uint::::random(rng); - let g = Uint::::random(rng); - (f, g) - }, + || (Uint::::random(rng), Uint::::random(rng)), |(f, g)| black_box(Uint::bingcd(&f, &g)), BatchSize::SmallInput, ) }); - - g.bench_function(BenchmarkId::new("bingcd_small", LIMBS), |b| { + g.bench_function(BenchmarkId::new("bingcd (classic)", LIMBS), |b| { b.iter_batched( - || { - let f = Uint::::random(rng) - .bitor(&Uint::ONE) - .to_odd() - .unwrap(); - let g = Uint::::random(rng); - (f, g) - }, + || (Odd::>::random(rng), Uint::::random(rng)), |(f, g)| black_box(f.classic_bingcd(&g)), BatchSize::SmallInput, ) }); - g.bench_function(BenchmarkId::new("bingcd_large", LIMBS), |b| { + g.bench_function(BenchmarkId::new("bingcd (optimized)", LIMBS), |b| { b.iter_batched( - || { - let f = Uint::::random(rng) - .bitor(&Uint::ONE) - .to_odd() - .unwrap(); - let g = Uint::::random(rng); - (f, g) - }, + || (Odd::>::random(rng), Uint::::random(rng)), |(f, g)| black_box(f.optimized_bingcd(&g)), BatchSize::SmallInput, ) @@ -406,18 +383,10 @@ fn bench_gcd(c: &mut Criterion) { group.finish(); } -fn xgcd_bench(g: &mut BenchmarkGroup, rng: &mut impl RngCore) -where - Uint: Gcd>, -{ +fn xgcd_bench(g: &mut BenchmarkGroup, rng: &mut impl RngCore) { g.bench_function(BenchmarkId::new("binxgcd", LIMBS), |b| { b.iter_batched( - || { - let modulus = Int::MIN.as_uint().wrapping_add(&Uint::ONE).to_nz().unwrap(); - let f = Uint::::random_mod(rng, &modulus).as_int(); - let g = Uint::::random_mod(rng, &modulus).as_int(); - (f, g) - }, + || (Uint::::random(rng), Uint::::random(rng)), |(f, g)| black_box(f.binxgcd(&g)), BatchSize::SmallInput, ) From 7aa067a408a6f84e4811aeff40c6e296a1a20ae2 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 25 Mar 2025 18:16:36 +0100 Subject: [PATCH 182/203] Fix `*BinxgcdOutput` access --- src/int.rs | 2 ++ src/int/bingcd.rs | 3 +++ src/lib.rs | 1 + src/modular/bingcd/xgcd.rs | 3 +++ src/uint.rs | 2 +- 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/int.rs b/src/int.rs index cd15877d9..577cc794d 100644 --- a/src/int.rs +++ b/src/int.rs @@ -34,6 +34,8 @@ mod sign; mod sub; pub(crate) mod types; +pub use bingcd::{IntBinxgcdOutput, NonZeroIntBinxgcdOutput, OddIntBinxgcdOutput}; + #[cfg(feature = "rand_core")] mod rand; diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index 615b650b7..037e7004a 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -15,11 +15,14 @@ pub struct BaseIntBinxgcdOutput { pub rhs_on_gcd: Int, } +/// Output of the Binary XGCD algorithm applied to two [Int]s. pub type IntBinxgcdOutput = BaseIntBinxgcdOutput, LIMBS>; +/// Output of the Binary XGCD algorithm applied to two [NonZero>]s. pub type NonZeroIntBinxgcdOutput = BaseIntBinxgcdOutput>, LIMBS>; +/// Output of the Binary XGCD algorithm applied to two [Odd>]s. pub type OddIntBinxgcdOutput = BaseIntBinxgcdOutput>, LIMBS>; impl BaseIntBinxgcdOutput { diff --git a/src/lib.rs b/src/lib.rs index ae44a1670..6ec5c209a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -187,6 +187,7 @@ pub use crate::{ int::types::*, int::*, limb::{Limb, WideWord, Word}, + modular::bingcd::{NonZeroUintBinxgcdOutput, OddUintBinxgcdOutput, UintBinxgcdOutput}, non_zero::NonZero, odd::Odd, traits::*, diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 8dc30066c..2c11319fb 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -74,11 +74,14 @@ impl RawOddUintBinxgcdOutput { } } +/// Output of the Binary XGCD algorithm applied to two [Uint]s. pub type UintBinxgcdOutput = BaseUintBinxgcdOutput, LIMBS>; +/// Output of the Binary XGCD algorithm applied to two [NonZero>]s. pub type NonZeroUintBinxgcdOutput = BaseUintBinxgcdOutput>, LIMBS>; +/// Output of the Binary XGCD algorithm applied to two [Odd>]s. pub type OddUintBinxgcdOutput = BaseUintBinxgcdOutput>, LIMBS>; /// Container for the processed output of the Binary XGCD algorithm. diff --git a/src/uint.rs b/src/uint.rs index df57e8925..92fd423c9 100644 --- a/src/uint.rs +++ b/src/uint.rs @@ -24,6 +24,7 @@ mod macros; mod add; mod add_mod; +mod bingcd; mod bit_and; mod bit_not; mod bit_or; @@ -471,7 +472,6 @@ impl_uint_concat_split_mixed! { (U1024, [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15]), } -mod bingcd; #[cfg(feature = "extra-sizes")] mod extra_sizes; From bc73d922b7e91f6664513e4fde9f5b03a561ba88 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 25 Mar 2025 18:03:32 +0100 Subject: [PATCH 183/203] Fix doc --- src/int/bingcd.rs | 4 ++-- src/modular/bingcd/xgcd.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index 037e7004a..c3dd52a8a 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -18,11 +18,11 @@ pub struct BaseIntBinxgcdOutput { /// Output of the Binary XGCD algorithm applied to two [Int]s. pub type IntBinxgcdOutput = BaseIntBinxgcdOutput, LIMBS>; -/// Output of the Binary XGCD algorithm applied to two [NonZero>]s. +/// Output of the Binary XGCD algorithm applied to two [`NonZero>`]s. pub type NonZeroIntBinxgcdOutput = BaseIntBinxgcdOutput>, LIMBS>; -/// Output of the Binary XGCD algorithm applied to two [Odd>]s. +/// Output of the Binary XGCD algorithm applied to two [`Odd>`]s. pub type OddIntBinxgcdOutput = BaseIntBinxgcdOutput>, LIMBS>; impl BaseIntBinxgcdOutput { diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 2c11319fb..64495e9ea 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -77,11 +77,11 @@ impl RawOddUintBinxgcdOutput { /// Output of the Binary XGCD algorithm applied to two [Uint]s. pub type UintBinxgcdOutput = BaseUintBinxgcdOutput, LIMBS>; -/// Output of the Binary XGCD algorithm applied to two [NonZero>]s. +/// Output of the Binary XGCD algorithm applied to two [`NonZero>`]s. pub type NonZeroUintBinxgcdOutput = BaseUintBinxgcdOutput>, LIMBS>; -/// Output of the Binary XGCD algorithm applied to two [Odd>]s. +/// Output of the Binary XGCD algorithm applied to two [`Odd>`]s. pub type OddUintBinxgcdOutput = BaseUintBinxgcdOutput>, LIMBS>; /// Container for the processed output of the Binary XGCD algorithm. From 69b23ca28b7a1eb35f205bccd1fe1dc37c5ad45b Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 31 Mar 2025 17:54:05 +0200 Subject: [PATCH 184/203] Disable `bin(x)gcd` tests for miri --- src/int/bingcd.rs | 2 +- src/modular/bingcd/xgcd.rs | 2 +- src/uint/bingcd.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index c3dd52a8a..03581f73c 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -244,7 +244,7 @@ mod test { } } - #[cfg(feature = "rand_core")] + #[cfg(all(feature = "rand_core", not(miri)))] pub(crate) fn make_rng() -> ChaChaRng { ChaChaRng::from_seed([0; 32]) } diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 64495e9ea..fc6723926 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -387,7 +387,7 @@ impl Odd> { } } -#[cfg(test)] +#[cfg(all(test, not(miri)))] mod tests { use crate::modular::bingcd::xgcd::OddUintBinxgcdOutput; use crate::{ConcatMixed, Gcd, Uint}; diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index 4c99178a8..88430591c 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -155,7 +155,7 @@ impl Odd> { } } -#[cfg(test)] +#[cfg(all(test, not(miri)))] mod tests { mod bincgd_test { #[cfg(feature = "rand_core")] From 58560bc9eb5d665b67fc1dec17a7596a495cc8b1 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 1 Apr 2025 09:30:34 +0200 Subject: [PATCH 185/203] Disable more bingcd tests on miri --- src/int/bingcd.rs | 4 ++-- src/modular/bingcd/gcd.rs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/int/bingcd.rs b/src/int/bingcd.rs index 03581f73c..966bbae97 100644 --- a/src/int/bingcd.rs +++ b/src/int/bingcd.rs @@ -195,7 +195,7 @@ impl Odd> { } } -#[cfg(test)] +#[cfg(all(test, not(miri)))] mod test { use crate::int::bingcd::{IntBinxgcdOutput, NonZeroIntBinxgcdOutput, OddIntBinxgcdOutput}; use crate::{ConcatMixed, Int, Uint}; @@ -244,7 +244,7 @@ mod test { } } - #[cfg(all(feature = "rand_core", not(miri)))] + #[cfg(feature = "rand_core")] pub(crate) fn make_rng() -> ChaChaRng { ChaChaRng::from_seed([0; 32]) } diff --git a/src/modular/bingcd/gcd.rs b/src/modular/bingcd/gcd.rs index da85aae9c..e7513fe26 100644 --- a/src/modular/bingcd/gcd.rs +++ b/src/modular/bingcd/gcd.rs @@ -117,8 +117,7 @@ impl Odd> { } } -#[cfg(feature = "rand_core")] -#[cfg(test)] +#[cfg(all(test, feature = "rand_core"))] mod tests { use rand_chacha::ChaChaRng; use rand_core::SeedableRng; From bd7951cb13abceffd8e32de272e5add74fb03060 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 1 Apr 2025 14:39:58 +0200 Subject: [PATCH 186/203] Implement `Uint::shr_limb` --- src/uint/shr.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/src/uint/shr.rs b/src/uint/shr.rs index df6db1f7e..ae34b40f0 100644 --- a/src/uint/shr.rs +++ b/src/uint/shr.rs @@ -157,6 +157,49 @@ impl Uint { (ret, ConstChoice::from_word_lsb(carry.0 >> Limb::HI_BIT)) } + + /// Computes `self >> shift` where `0 <= shift < Limb::BITS`, + /// returning the result and the carry. + #[inline(always)] + pub(crate) const fn shr_limb(&self, shift: u32) -> (Self, Limb) { + assert!(shift < Limb::BITS); + let nz = ConstChoice::from_u32_nonzero(shift); + let shift = nz.select_u32(1, shift); + let (res, carry) = self.shr_limb_nonzero(shift); + ( + Uint::select(self, &res, nz), + Limb::select(Limb::ZERO, carry, nz), + ) + } + + /// Computes `self >> shift` where `0 < shift < Limb::BITS`, returning the result and the carry. + /// + /// Note: this operation should not be used in situations where `shift == 0`; the compiler can + /// sometimes sniff this case out and optimize it away, possibly leading to variable time + /// behaviour. + #[inline(always)] + pub(crate) const fn shr_limb_nonzero(&self, shift: u32) -> (Self, Limb) { + assert!(0 < shift); + assert!(shift < Limb::BITS); + + let mut limbs = [Limb::ZERO; LIMBS]; + + let rshift = shift; + let lshift = Limb::BITS - shift; + + let mut carry = Limb::ZERO; + let mut i = LIMBS; + while i > 0 { + i -= 1; + + let limb = self.limbs[i].shr(rshift); + let new_carry = self.limbs[i].shl(lshift); + limbs[i] = limb.bitor(carry); + carry = new_carry; + } + + (Uint::::new(limbs), carry) + } } macro_rules! impl_shr { @@ -208,7 +251,7 @@ impl ShrVartime for Uint { #[cfg(test)] mod tests { - use crate::{U128, U256, Uint}; + use crate::{Limb, U128, U256, Uint}; const N: U256 = U256::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"); @@ -258,4 +301,41 @@ mod tests { .is_true_vartime() ); } + + #[test] + #[should_panic] + fn shr_limb_shift_too_large() { + let _ = U128::ONE.shr_limb(Limb::BITS); + } + + #[test] + #[should_panic] + fn shr_limb_nz_panics_at_zero_shift() { + let _ = U128::ONE.shr_limb_nonzero(0); + } + + #[test] + fn shr_limb() { + let val = U128::from_be_hex("876543210FEDCBA90123456FEDCBA987"); + + // Shift by zero + let (res, carry) = val.shr_limb(0); + assert_eq!(res, val); + assert_eq!(carry, Limb::ZERO); + + // Shift by one + let (res, carry) = val.shr_limb(1); + assert_eq!(res, val.shr_vartime(1)); + assert_eq!(carry, val.limbs[0].shl(Limb::BITS - 1)); + + // Shift by any + let (res, carry) = val.shr_limb(13); + assert_eq!(res, val.shr_vartime(13)); + assert_eq!(carry, val.limbs[0].shl(Limb::BITS - 13)); + + // Shift by max + let (res, carry) = val.shr_limb(Limb::BITS - 1); + assert_eq!(res, val.shr_vartime(Limb::BITS - 1)); + assert_eq!(carry, val.limbs[0].shl(1)); + } } From 699f44bf6fdeeb0505bfccd547578d295eecf2dc Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 1 Apr 2025 15:17:26 +0200 Subject: [PATCH 187/203] Implement `Uint::mac_limb` --- src/modular/bingcd/tools.rs | 49 ++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/modular/bingcd/tools.rs b/src/modular/bingcd/tools.rs index 0e57fafc4..86d672302 100644 --- a/src/modular/bingcd/tools.rs +++ b/src/modular/bingcd/tools.rs @@ -1,4 +1,4 @@ -use crate::{ConstChoice, Odd, Uint}; +use crate::{ConstChoice, Limb, Odd, Uint}; /// `const` equivalent of `u32::max(a, b)`. pub(crate) const fn const_max(a: u32, b: u32) -> u32 { @@ -33,6 +33,17 @@ impl Uint { self } + /// Computes `self + (b * c) + carry`, returning the result along with the new carry. + #[inline] + const fn mac_limb(mut self, b: &Self, c: Limb, mut carry: Limb) -> (Self, Limb) { + let mut i = 0; + while i < LIMBS { + (self.limbs[i], carry) = self.limbs[i].mac(b.limbs[i], c, carry); + i += 1; + } + (self, carry) + } + /// Compute `self / 2 mod q`. #[inline] const fn div_2_mod_q(self, half_mod_q: &Self) -> Self { @@ -102,3 +113,39 @@ impl Uint { hi.shl_vartime(K - 1).bitxor(&lo) } } + +#[cfg(test)] +mod tests { + use crate::{Limb, U128, Uint}; + + #[test] + fn test_mac_limb() { + // Do nothing + let x = U128::from_be_hex("ABCDEF98765432100123456789FEDCBA"); + let q = U128::MAX; + let f = Limb::ZERO; + let (res, carry) = x.mac_limb(&q, f, Limb::ZERO); + assert_eq!(res, x); + assert_eq!(carry, Limb::ZERO); + + // f = 1 + let x = U128::from_be_hex("ABCDEF98765432100123456789FEDCBA"); + let q = U128::MAX; + let f = Limb::ONE; + let (res, carry) = x.mac_limb(&q, f, Limb::ZERO); + assert_eq!(res, x.wrapping_add(&q)); + assert_eq!(carry, Limb::ONE); + + // f = max + let x = U128::from_be_hex("ABCDEF98765432100123456789FEDCBA"); + let q = U128::MAX; + let f = Limb::MAX; + let (res, mac_carry) = x.mac_limb(&q, f, Limb::ZERO); + let (qf_lo, qf_hi) = q.split_mul(&Uint::new([f; 1])); + let (lo, carry) = qf_lo.adc(&x, Limb::ZERO); + let (hi, carry) = qf_hi.adc(&Uint::ZERO, carry); + assert_eq!(res, lo); + assert_eq!(mac_carry, hi.limbs[0]); + assert_eq!(carry, Limb::ZERO) + } +} From ab44f31f414894ec308a9ca56526cf94ca07cbcb Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 1 Apr 2025 15:50:30 +0200 Subject: [PATCH 188/203] Implement `Limb::bounded_div2k_mod_q` --- src/modular/bingcd/tools.rs | 66 +++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/modular/bingcd/tools.rs b/src/modular/bingcd/tools.rs index 86d672302..7daa5940b 100644 --- a/src/modular/bingcd/tools.rs +++ b/src/modular/bingcd/tools.rs @@ -10,6 +10,37 @@ pub(crate) const fn const_min(a: u32, b: u32) -> u32 { ConstChoice::from_u32_lt(a, b).select_u32(b, a) } +impl Limb { + /// Compute `self / 2^k mod q`. Returns the result, as well as a factor `f` such that `2^k` + /// divides `self + q * f`. + /// + /// Executes in time variable in `k_bound`. This value should be chosen as an inclusive + /// upperbound to the value of `k`. + const fn bounded_div2k_mod_q( + mut self, + k: u32, + k_bound: u32, + one_half_mod_q: Self, + ) -> (Self, Self) { + let mut factor = Limb::ZERO; + let mut i = 0; + while i < k_bound { + let execute = ConstChoice::from_u32_lt(i, k); + + let (shifted, carry) = self.shr1(); + self = Self::select(self, shifted, execute); + + let overflow = ConstChoice::from_word_msb(carry.0); + let add_back_q = overflow.and(execute); + self = self.wrapping_add(Self::select(Self::ZERO, one_half_mod_q, add_back_q)); + factor = factor.bitxor(Self::select(Self::ZERO, Self::ONE.shl(i), add_back_q)); + i += 1; + } + + (self, factor) + } +} + impl Uint { /// Compute `self / 2^k mod q`. /// @@ -118,6 +149,41 @@ impl Uint { mod tests { use crate::{Limb, U128, Uint}; + #[test] + fn test_bounded_div2k_mod_q() { + let x = Limb::MAX.wrapping_sub(Limb::from(15u32)); + let q = Limb::from(55u32); + let half_mod_q = q.shr1().0.wrapping_add(Limb::ONE); + + // Do nothing + let k = 0; + let k_bound = 3; + let (res, factor) = x.bounded_div2k_mod_q(k, k_bound, half_mod_q); + assert_eq!(res, x); + assert_eq!(factor, Limb::ZERO); + + // Divide by 2^4 without requiring the addition of q + let k = 4; + let k_bound = 4; + let (res, factor) = x.bounded_div2k_mod_q(k, k_bound, half_mod_q); + assert_eq!(res, x.shr(4)); + assert_eq!(factor, Limb::ZERO); + + // Divide by 2^5, requiring a single addition of q * 2^4 + let k = 5; + let k_bound = 5; + let (res, factor) = x.bounded_div2k_mod_q(k, k_bound, half_mod_q); + assert_eq!(res, x.shr(5).wrapping_add(half_mod_q)); + assert_eq!(factor, Limb::ONE.shl(4)); + + // Execute at most k_bound iterations + let k = 5; + let k_bound = 4; + let (res, factor) = x.bounded_div2k_mod_q(k, k_bound, half_mod_q); + assert_eq!(res, x.shr(4)); + assert_eq!(factor, Limb::ZERO); + } + #[test] fn test_mac_limb() { // Do nothing From 4c13a0db9e231a68eab1a4288a3e43c71bd317b0 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 1 Apr 2025 17:23:20 +0200 Subject: [PATCH 189/203] Speed up `div_2k_mod_q` --- src/modular/bingcd/tools.rs | 96 +++++++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 20 deletions(-) diff --git a/src/modular/bingcd/tools.rs b/src/modular/bingcd/tools.rs index 7daa5940b..64b2d3e7d 100644 --- a/src/modular/bingcd/tools.rs +++ b/src/modular/bingcd/tools.rs @@ -42,26 +42,43 @@ impl Limb { } impl Uint { - /// Compute `self / 2^k mod q`. + /// Compute `self / 2^k mod q`. /// - /// Executes in time variable in `k_bound`. This value should be - /// chosen as an inclusive upperbound to the value of `k`. + /// Executes in time variable in `k_bound`. This value should be chosen as an inclusive + /// upperbound to the value of `k`. #[inline] - pub(crate) const fn div_2k_mod_q(mut self, k: u32, k_bound: u32, q: &Odd) -> Self { + pub(crate) const fn div_2k_mod_q(self, k: u32, k_bound: u32, q: &Odd) -> Self { // 1 / 2 mod q // = (q + 1) / 2 mod q // = (q - 1) / 2 + 1 mod q // = floor(q / 2) + 1 mod q, since q is odd. - let one_half_mod_q = q.as_ref().shr_vartime(1).wrapping_add(&Uint::ONE); - let mut i = 0; - while i < k_bound { - // Apply only while i < k - let apply = ConstChoice::from_u32_lt(i, k); - self = Self::select(&self, &self.div_2_mod_q(&one_half_mod_q), apply); - i += 1; + let one_half_mod_q = q.as_ref().shr1().wrapping_add(&Uint::ONE); + + // invariant: x = self / 2^e mod q. + let (mut x, mut e) = (self, 0); + + let max_round_iters = Limb::BITS - 1; + let rounds = k_bound.div_ceil(max_round_iters); + + let mut r = 0; + while r < rounds { + let f_bound = const_min(k_bound - r * max_round_iters, max_round_iters); + let f = const_min(k - e, f_bound); + let (_, s) = x.limbs[0].bounded_div2k_mod_q(f, f_bound, one_half_mod_q.limbs[0]); + + // Compute (x * qs) / 2^f + // Note that 2^f divides x + qs by construction + let (x_qs_lo, mut x_qs_hi) = x.mac_limb(q.as_ref(), s, Limb::ZERO); + x_qs_hi = x_qs_hi.shl((Limb::BITS - f) % Limb::BITS); + let (mut x_qs_div_2f, _) = x_qs_lo.shr_limb(f); + x_qs_div_2f.limbs[LIMBS - 1] = x_qs_div_2f.limbs[LIMBS - 1].bitxor(x_qs_hi); + + (x, e) = (x_qs_div_2f, e + f); + + r += 1; } - self + x } /// Computes `self + (b * c) + carry`, returning the result along with the new carry. @@ -75,14 +92,6 @@ impl Uint { (self, carry) } - /// Compute `self / 2 mod q`. - #[inline] - const fn div_2_mod_q(self, half_mod_q: &Self) -> Self { - // Floor-divide self by 2. When self was odd, add back 1/2 mod q. - let (floored_half, add_one_half) = self.shr1_with_carry(); - floored_half.wrapping_add(&Self::select(&Self::ZERO, half_mod_q, add_one_half)) - } - /// Construct a [Uint] containing the bits in `self` in the range `[idx, idx + length)`. /// /// Assumes `length ≤ Uint::::BITS` and `idx + length ≤ Self::BITS`. @@ -214,4 +223,51 @@ mod tests { assert_eq!(mac_carry, hi.limbs[0]); assert_eq!(carry, Limb::ZERO) } + + #[test] + fn test_new_div2k_mod_q() { + // Do nothing + let q = U128::from(3u64).to_odd().unwrap(); + let res = U128::ONE.shl_vartime(64).div_2k_mod_q(0, 0, &q); + assert_eq!(res, U128::ONE.shl_vartime(64)); + + // Simply shift out 5 factors + let q = U128::from(3u64).to_odd().unwrap(); + let res = U128::ONE.shl_vartime(64).div_2k_mod_q(5, 5, &q); + assert_eq!(res, U128::ONE.shl_vartime(59)); + + // Add in one factor of q + let q = U128::from(3u64).to_odd().unwrap(); + let res = U128::ONE.div_2k_mod_q(1, 1, &q); + assert_eq!(res, U128::from(2u64)); + + // Add in many factors of q + let q = U128::from(3u64).to_odd().unwrap(); + let res = U128::from(8u64).div_2k_mod_q(17, 17, &q); + assert_eq!(res, U128::ONE); + + // Larger q + let q = U128::from(2864434311u64).to_odd().unwrap(); + let res = U128::from(8u64).div_2k_mod_q(17, 17, &q); + assert_eq!(res, U128::from(303681787u64)); + + // Shift greater than Limb::BITS + let q = U128::from_be_hex("0000AAAABBBB33330000AAAABBBB3333") + .to_odd() + .unwrap(); + let res = U128::MAX.div_2k_mod_q(71, 71, &q); + assert_eq!(res, U128::from_be_hex("00002D6F169DBBF300002D6F169DBBF3")); + + // Have k_bound restrict the number of shifts to 0 + let res = U128::MAX.div_2k_mod_q(71, 0, &q); + assert_eq!(res, U128::MAX); + + // Have k_bound < k + let res = U128::MAX.div_2k_mod_q(71, 30, &q); + assert_eq!(res, U128::from_be_hex("000071EEB6013E76000071EEB6013E76")); + + // Have k_bound >> k + let res = U128::MAX.div_2k_mod_q(30, 127, &q); + assert_eq!(res, U128::from_be_hex("000071EEB6013E76000071EEB6013E76")); + } } From 9577b736b5461836f7b7d36b8ca501a70e0baf51 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 1 Apr 2025 17:34:30 +0200 Subject: [PATCH 190/203] Rename `div_2k_mod_q` as `bounded_div_2k_mod_q` --- src/modular/bingcd/tools.rs | 20 ++++++++++---------- src/modular/bingcd/xgcd.rs | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/modular/bingcd/tools.rs b/src/modular/bingcd/tools.rs index 64b2d3e7d..56484df48 100644 --- a/src/modular/bingcd/tools.rs +++ b/src/modular/bingcd/tools.rs @@ -47,7 +47,7 @@ impl Uint { /// Executes in time variable in `k_bound`. This value should be chosen as an inclusive /// upperbound to the value of `k`. #[inline] - pub(crate) const fn div_2k_mod_q(self, k: u32, k_bound: u32, q: &Odd) -> Self { + pub(crate) const fn bounded_div_2k_mod_q(self, k: u32, k_bound: u32, q: &Odd) -> Self { // 1 / 2 mod q // = (q + 1) / 2 mod q // = (q - 1) / 2 + 1 mod q @@ -228,46 +228,46 @@ mod tests { fn test_new_div2k_mod_q() { // Do nothing let q = U128::from(3u64).to_odd().unwrap(); - let res = U128::ONE.shl_vartime(64).div_2k_mod_q(0, 0, &q); + let res = U128::ONE.shl_vartime(64).bounded_div_2k_mod_q(0, 0, &q); assert_eq!(res, U128::ONE.shl_vartime(64)); // Simply shift out 5 factors let q = U128::from(3u64).to_odd().unwrap(); - let res = U128::ONE.shl_vartime(64).div_2k_mod_q(5, 5, &q); + let res = U128::ONE.shl_vartime(64).bounded_div_2k_mod_q(5, 5, &q); assert_eq!(res, U128::ONE.shl_vartime(59)); // Add in one factor of q let q = U128::from(3u64).to_odd().unwrap(); - let res = U128::ONE.div_2k_mod_q(1, 1, &q); + let res = U128::ONE.bounded_div_2k_mod_q(1, 1, &q); assert_eq!(res, U128::from(2u64)); // Add in many factors of q let q = U128::from(3u64).to_odd().unwrap(); - let res = U128::from(8u64).div_2k_mod_q(17, 17, &q); + let res = U128::from(8u64).bounded_div_2k_mod_q(17, 17, &q); assert_eq!(res, U128::ONE); // Larger q let q = U128::from(2864434311u64).to_odd().unwrap(); - let res = U128::from(8u64).div_2k_mod_q(17, 17, &q); + let res = U128::from(8u64).bounded_div_2k_mod_q(17, 17, &q); assert_eq!(res, U128::from(303681787u64)); // Shift greater than Limb::BITS let q = U128::from_be_hex("0000AAAABBBB33330000AAAABBBB3333") .to_odd() .unwrap(); - let res = U128::MAX.div_2k_mod_q(71, 71, &q); + let res = U128::MAX.bounded_div_2k_mod_q(71, 71, &q); assert_eq!(res, U128::from_be_hex("00002D6F169DBBF300002D6F169DBBF3")); // Have k_bound restrict the number of shifts to 0 - let res = U128::MAX.div_2k_mod_q(71, 0, &q); + let res = U128::MAX.bounded_div_2k_mod_q(71, 0, &q); assert_eq!(res, U128::MAX); // Have k_bound < k - let res = U128::MAX.div_2k_mod_q(71, 30, &q); + let res = U128::MAX.bounded_div_2k_mod_q(71, 30, &q); assert_eq!(res, U128::from_be_hex("000071EEB6013E76000071EEB6013E76")); // Have k_bound >> k - let res = U128::MAX.div_2k_mod_q(30, 127, &q); + let res = U128::MAX.bounded_div_2k_mod_q(30, 127, &q); assert_eq!(res, U128::from_be_hex("000071EEB6013E76000071EEB6013E76")); } } diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index fc6723926..7f2bd2f03 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -37,12 +37,12 @@ impl RawOddUintBinxgcdOutput { let (lhs_div_gcd, rhs_div_gcd) = self.quotients(); let (x, y, .., k, k_upper_bound) = self.matrix.as_elements_mut(); if *k_upper_bound > 0 { - *x = x.div_2k_mod_q( + *x = x.bounded_div_2k_mod_q( *k, *k_upper_bound, &rhs_div_gcd.to_odd().expect("odd by construction"), ); - *y = y.div_2k_mod_q( + *y = y.bounded_div_2k_mod_q( *k, *k_upper_bound, &lhs_div_gcd.to_odd().expect("odd by construction"), From 966fe728fc17008cdbc92317eb6b0acbf3aecdb7 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 2 Apr 2025 09:42:38 +0200 Subject: [PATCH 191/203] Add tests with failing binxgcd inputs --- src/uint/bingcd.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/uint/bingcd.rs b/src/uint/bingcd.rs index 88430591c..1e20ef2c5 100644 --- a/src/uint/bingcd.rs +++ b/src/uint/bingcd.rs @@ -315,5 +315,26 @@ mod tests { binxgcd_tests::<{ U4096::LIMBS }, { U8192::LIMBS }>(); binxgcd_tests::<{ U8192::LIMBS }, { U16384::LIMBS }>(); } + + #[test] + fn test_binxgcd_regression_tests() { + // Sent in by @kayabaNerve (https://github.com/RustCrypto/crypto-bigint/pull/761#issuecomment-2771564732) + let a = U256::from_be_hex( + "000000000000000000000000000000000000001B5DFB3BA1D549DFAF611B8D4C", + ); + let b = U256::from_be_hex( + "000000000000345EAEDFA8CA03C1F0F5B578A787FE2D23B82A807F178B37FD8E", + ); + binxgcd_test(a, b); + + // Sent in by @kayabaNerve (https://github.com/RustCrypto/crypto-bigint/pull/761#issuecomment-2771581512) + let a = U256::from_be_hex( + "000000000000000000000000000000000000001A0DEEF6F3AC2566149D925044", + ); + let b = U256::from_be_hex( + "000000000000072B69C9DD0AA15F135675EA9C5180CF8FF0A59298CFC92E87FA", + ); + binxgcd_test(a, b); + } } } From 293580edb446752d5d7dfd8b9a53dfc59fe74d24 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 2 Apr 2025 12:41:05 +0200 Subject: [PATCH 192/203] Bug detection signal --- src/modular/bingcd/xgcd.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 7f2bd2f03..c6dab7e13 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -250,7 +250,7 @@ impl Odd> { let (mut a, mut b) = (*self.as_ref(), *rhs.as_ref()); let mut matrix = BinXgcdMatrix::UNIT; - let mut a_sgn; + let (mut a_sgn, mut b_sgn); let mut i = 0; while i < Self::MIN_BINGCD_ITERATIONS.div_ceil(K - 1) { i += 1; @@ -272,7 +272,12 @@ impl Odd> { // Update `a` and `b` using the update matrix let (updated_a, updated_b) = update_matrix.wrapping_apply_to((a, b)); (a, a_sgn) = updated_a.wrapping_drop_extension(); - (b, _) = updated_b.wrapping_drop_extension(); + (b, b_sgn) = updated_b.wrapping_drop_extension(); + + assert!( + a_sgn.and(b_sgn).not().to_bool_vartime(), + "a or b is negative, but never both" + ); matrix = update_matrix.wrapping_mul_right(&matrix); matrix.conditional_negate(a_sgn); // TODO: find a cleaner solution for this From 754d3b8a12a3dc394f24a8a77457693db6756fa4 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 2 Apr 2025 12:55:12 +0200 Subject: [PATCH 193/203] Fix bug in `BinXgcdMatrix::wrapping_apply_to` Also update the bug detection signal --- src/modular/bingcd/matrix.rs | 12 ++++++------ src/modular/bingcd/xgcd.rs | 6 ++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/modular/bingcd/matrix.rs b/src/modular/bingcd/matrix.rs index 2d89f6534..6449b5aaa 100644 --- a/src/modular/bingcd/matrix.rs +++ b/src/modular/bingcd/matrix.rs @@ -110,15 +110,15 @@ impl BinXgcdMatrix { vec: Vector>, ) -> Vector> { let (a, b) = vec; - let a0 = ExtendedInt::from_product(a, self.m00); - let a1 = ExtendedInt::from_product(a, self.m10); - let b0 = ExtendedInt::from_product(b, self.m01); - let b1 = ExtendedInt::from_product(b, self.m11); + let m00a = ExtendedInt::from_product(a, self.m00); + let m10a = ExtendedInt::from_product(a, self.m10); + let m01b = ExtendedInt::from_product(b, self.m01); + let m11b = ExtendedInt::from_product(b, self.m11); ( - a0.wrapping_sub(&b0) + m00a.wrapping_sub(&m01b) .div_2k(self.k) .wrapping_neg_if(self.pattern.not()), - a1.wrapping_sub(&b1) + m11b.wrapping_sub(&m10a) .div_2k(self.k) .wrapping_neg_if(self.pattern.not()), ) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index c6dab7e13..60ac70d45 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -274,10 +274,8 @@ impl Odd> { (a, a_sgn) = updated_a.wrapping_drop_extension(); (b, b_sgn) = updated_b.wrapping_drop_extension(); - assert!( - a_sgn.and(b_sgn).not().to_bool_vartime(), - "a or b is negative, but never both" - ); + assert!(a_sgn.not().to_bool_vartime(), "a is never negative"); + assert!(b_sgn.not().to_bool_vartime(), "b is never negative"); matrix = update_matrix.wrapping_mul_right(&matrix); matrix.conditional_negate(a_sgn); // TODO: find a cleaner solution for this From 92bb646b62ba66f5dc482927d13ddac7031010d2 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 2 Apr 2025 12:55:37 +0200 Subject: [PATCH 194/203] Rename `BinXgcdMatrix::wrapping_apply_to` as `extended_apply_to` --- src/modular/bingcd/gcd.rs | 2 +- src/modular/bingcd/matrix.rs | 4 ++-- src/modular/bingcd/xgcd.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modular/bingcd/gcd.rs b/src/modular/bingcd/gcd.rs index e7513fe26..2a215e26b 100644 --- a/src/modular/bingcd/gcd.rs +++ b/src/modular/bingcd/gcd.rs @@ -107,7 +107,7 @@ impl Odd> { .partial_binxgcd_vartime::(&b_, K - 1, ConstChoice::FALSE); // Update `a` and `b` using the update matrix - let (updated_a, updated_b) = matrix.wrapping_apply_to((a, b)); + let (updated_a, updated_b) = matrix.extended_apply_to((a, b)); (a, _) = updated_a.wrapping_drop_extension(); (b, _) = updated_b.wrapping_drop_extension(); } diff --git a/src/modular/bingcd/matrix.rs b/src/modular/bingcd/matrix.rs index 6449b5aaa..d11a48c25 100644 --- a/src/modular/bingcd/matrix.rs +++ b/src/modular/bingcd/matrix.rs @@ -105,7 +105,7 @@ impl BinXgcdMatrix { /// Apply this matrix to a vector of [Uint]s, returning the result as a vector of /// [ExtendedInt]s. #[inline] - pub(crate) const fn wrapping_apply_to( + pub(crate) const fn extended_apply_to( &self, vec: Vector>, ) -> Vector> { @@ -245,7 +245,7 @@ mod tests { k_upper_bound: 17, }; - let (a_, b_) = matrix.wrapping_apply_to((a, b)); + let (a_, b_) = matrix.extended_apply_to((a, b)); assert_eq!( a_.wrapping_drop_extension().0, Uint::from_be_hex("002AC7CDD032B9B9") diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 60ac70d45..8c22ef45c 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -270,7 +270,7 @@ impl Odd> { .partial_binxgcd_vartime::(&b_, K - 1, b_fits_in_compact); // Update `a` and `b` using the update matrix - let (updated_a, updated_b) = update_matrix.wrapping_apply_to((a, b)); + let (updated_a, updated_b) = update_matrix.extended_apply_to((a, b)); (a, a_sgn) = updated_a.wrapping_drop_extension(); (b, b_sgn) = updated_b.wrapping_drop_extension(); @@ -604,7 +604,7 @@ mod tests { assert_eq!(new_a, target_a); assert_eq!(new_b, target_b); - let (computed_a, computed_b) = matrix.wrapping_apply_to((A.get(), B)); + let (computed_a, computed_b) = matrix.extended_apply_to((A.get(), B)); let computed_a = computed_a.wrapping_drop_extension().0; let computed_b = computed_b.wrapping_drop_extension().0; From 107ddd9271f4b55f2da635f3e88763b4f6d4ba05 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 3 Apr 2025 09:51:46 +0200 Subject: [PATCH 195/203] Extract `optimized_binxgcd` settings as `const`ants --- src/modular/bingcd/xgcd.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 8c22ef45c..2adad5cf7 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -135,6 +135,16 @@ impl BaseUintBinxgcdOutput { } } +/// Number of bits used by [Odd::>::optimized_binxgcd] to represent a "compact" [Uint]. +const SUMMARY_BITS: u32 = U64::BITS; + +/// Number of limbs used to represent [Self::SUMMARY_BITS]. +const SUMMARY_LIMBS: usize = U64::LIMBS; + +/// Twice the number of limbs used to represent [Self::SUMMARY_BITS], i.e., two times +/// [Self::SUMMARY_LIMBS]. +const DOUBLE_SUMMARY_LIMBS: usize = U128::LIMBS; + impl Odd> { /// The minimal number of binary GCD iterations required to guarantee successful completion. const MIN_BINGCD_ITERATIONS: u32 = 2 * Self::BITS - 1; @@ -220,7 +230,7 @@ impl Odd> { /// . pub(crate) const fn optimized_binxgcd(&self, rhs: &Self) -> RawOddUintBinxgcdOutput { assert!(Self::BITS >= U128::BITS); - self.optimized_binxgcd_::<{ U64::BITS }, { U64::LIMBS }, { U128::LIMBS }>(rhs) + self.optimized_binxgcd_::(rhs) } /// Given `(self, rhs)`, computes `(g, x, y)`, s.t. `self * x + rhs * y = g = gcd(self, rhs)`, From be0863837896a4d854dff986efd04b90acf3a4d3 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 3 Apr 2025 10:26:28 +0200 Subject: [PATCH 196/203] Expand `optimized_binxgcd` edge case testing --- src/modular/bingcd/xgcd.rs | 51 +++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 2adad5cf7..815832a9f 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -825,17 +825,18 @@ mod tests { } mod test_optimized_binxgcd { - use crate::modular::bingcd::xgcd::tests::test_xgcd; - use crate::{ - ConcatMixed, Gcd, Int, U128, U192, U256, U384, U512, U768, U1024, U2048, U4096, U8192, - Uint, - }; - #[cfg(feature = "rand_core")] use super::make_rng; #[cfg(feature = "rand_core")] use crate::Random; + use crate::modular::bingcd::xgcd::tests::test_xgcd; + use crate::modular::bingcd::xgcd::{DOUBLE_SUMMARY_LIMBS, SUMMARY_BITS, SUMMARY_LIMBS}; + use crate::{ + ConcatMixed, Gcd, Int, U64, U128, U192, U256, U384, U512, U768, U1024, U2048, U4096, + U8192, Uint, + }; + fn optimized_binxgcd_test( lhs: Uint, rhs: Uint, @@ -881,6 +882,44 @@ mod tests { optimized_binxgcd_randomized_tests(100); } + #[test] + fn test_optimized_binxgcd_edge_cases() { + // If one of these tests fails, you have probably tweaked the SUMMARY_BITS, + // SUMMARY_LIMBS or DOUBLE_SUMMARY_LIMBS settings. Please make sure to update these + // tests accordingly. + assert_eq!(SUMMARY_BITS, 64); + assert_eq!(SUMMARY_LIMBS, U64::LIMBS); + assert_eq!(DOUBLE_SUMMARY_LIMBS, U128::LIMBS); + + // Case #1: a > b but a.compact() < b.compact() + let a = U256::from_be_hex( + "1234567890ABCDEF80000000000000000000000000000000BEDCBA0987654321", + ); + let b = U256::from_be_hex( + "1234567890ABCDEF800000000000000000000000000000007EDCBA0987654321", + ); + assert!(a > b); + assert!(a.compact::<64, 2>(256) < b.compact::<64, 2>(256)); + optimized_binxgcd_test(a, b); + + // Case #2: a < b but a.compact() > b.compact() + optimized_binxgcd_test(b, a); + + // Case #3: a > b but a.compact() = b.compact() + let a = U256::from_be_hex( + "1234567890ABCDEF80000000000000000000000000000000FEDCBA0987654321", + ); + let b = U256::from_be_hex( + "1234567890ABCDEF800000000000000000000000000000007EDCBA0987654321", + ); + assert!(a > b); + assert_eq!(a.compact::<64, 2>(256), b.compact::<64, 2>(256)); + optimized_binxgcd_test(a, b); + + // Case #4: a < b but a.compact() = b.compact() + optimized_binxgcd_test(b, a); + } + #[test] fn test_optimized_binxgcd() { optimized_binxgcd_tests::<{ U128::LIMBS }, { U256::LIMBS }>(); From 6b711c1aa956580dc711296529773ee43f43355a Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 3 Apr 2025 10:28:14 +0200 Subject: [PATCH 197/203] Rename `optimized_binxgcd`'s `a_` and `b_` to `compact_*` --- src/modular/bingcd/xgcd.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 815832a9f..a1b486202 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -265,19 +265,19 @@ impl Odd> { while i < Self::MIN_BINGCD_ITERATIONS.div_ceil(K - 1) { i += 1; - // Construct a_ and b_ as the summary of a and b, respectively. + // Construct compact_a and compact_b as the summary of a and b, respectively. let b_bits = b.bits(); let n = const_max(2 * K, const_max(a.bits(), b_bits)); - let a_ = a.compact::(n); - let b_ = b.compact::(n); - let b_fits_in_compact = + let compact_a = a.compact::(n); + let compact_b = b.compact::(n); + let b_eq_compact_b = ConstChoice::from_u32_le(b_bits, K - 1).or(ConstChoice::from_u32_eq(n, 2 * K)); // Compute the K-1 iteration update matrix from a_ and b_ - let (.., update_matrix) = a_ + let (.., update_matrix) = compact_a .to_odd() .expect("a is always odd") - .partial_binxgcd_vartime::(&b_, K - 1, b_fits_in_compact); + .partial_binxgcd_vartime::(&compact_b, K - 1, b_eq_compact_b); // Update `a` and `b` using the update matrix let (updated_a, updated_b) = update_matrix.extended_apply_to((a, b)); From 68d6ec3a8017454311b4b12ab606c07a3ffdc6c8 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 3 Apr 2025 10:51:23 +0200 Subject: [PATCH 198/203] Introduce `ConstChoice::from_i8_eq` --- src/const_choice.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/const_choice.rs b/src/const_choice.rs index ffca2af21..e3f7a7843 100644 --- a/src/const_choice.rs +++ b/src/const_choice.rs @@ -64,6 +64,13 @@ impl ConstChoice { Self(value.wrapping_neg() as Word) } + #[inline] + pub(crate) const fn from_u8_lsb(value: u8) -> Self { + debug_assert!(value == 0 || value == 1); + #[allow(trivial_numeric_casts)] + Self((value as Word).wrapping_neg()) + } + #[inline] pub(crate) const fn from_u32_lsb(value: u32) -> Self { debug_assert!(value == 0 || value == 1); @@ -78,6 +85,12 @@ impl ConstChoice { Self((value as Word).wrapping_neg()) } + /// Returns the truthy value if `value != 0`, and the falsy value otherwise. + #[inline] + pub(crate) const fn from_u8_nonzero(value: u8) -> Self { + Self::from_u8_lsb((value | value.wrapping_neg()) >> (u8::BITS - 1)) + } + /// Returns the truthy value if `value != 0`, and the falsy value otherwise. #[inline] pub(crate) const fn from_u32_nonzero(value: u32) -> Self { @@ -174,6 +187,14 @@ impl ConstChoice { Self::from_u64_lt(y, x) } + /// Returns the truthy value if `x = y`, and the falsy value otherwise. + #[inline] + pub(crate) const fn from_i8_eq(x: i8, y: i8) -> Self { + let x = x as u8; + let y = y as u8; + Self::from_u8_nonzero(x ^ y).not() + } + #[inline] pub(crate) const fn not(&self) -> Self { Self(!self.0) @@ -539,6 +560,19 @@ mod tests { use super::ConstChoice; + #[test] + fn from_u8_nonzero() { + assert_eq!(ConstChoice::from_u8_nonzero(0), ConstChoice::FALSE); + assert_eq!(ConstChoice::from_u8_nonzero(1), ConstChoice::TRUE); + assert_eq!(ConstChoice::from_u8_nonzero(123), ConstChoice::TRUE); + } + + #[test] + fn from_u8_lsb() { + assert_eq!(ConstChoice::from_u8_lsb(0), ConstChoice::FALSE); + assert_eq!(ConstChoice::from_u8_lsb(1), ConstChoice::TRUE); + } + #[test] fn from_u64_lsb() { assert_eq!(ConstChoice::from_u64_lsb(0), ConstChoice::FALSE); @@ -566,6 +600,19 @@ mod tests { assert_eq!(ConstChoice::from_wide_word_le(6, 5), ConstChoice::FALSE); } + #[test] + fn from_i8_eq() { + assert_eq!(ConstChoice::from_i8_eq(-1, -1), ConstChoice::TRUE); + assert_eq!(ConstChoice::from_i8_eq(-1, 0), ConstChoice::FALSE); + assert_eq!(ConstChoice::from_i8_eq(-1, 1), ConstChoice::FALSE); + assert_eq!(ConstChoice::from_i8_eq(0, -1), ConstChoice::FALSE); + assert_eq!(ConstChoice::from_i8_eq(0, 0), ConstChoice::TRUE); + assert_eq!(ConstChoice::from_i8_eq(0, 1), ConstChoice::FALSE); + assert_eq!(ConstChoice::from_i8_eq(1, -1), ConstChoice::FALSE); + assert_eq!(ConstChoice::from_i8_eq(1, 0), ConstChoice::FALSE); + assert_eq!(ConstChoice::from_i8_eq(1, 1), ConstChoice::TRUE); + } + #[test] fn select_u32() { let a: u32 = 1; From cc03dd02be751dd77cb485bb4dee541766ac245e Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 3 Apr 2025 10:56:35 +0200 Subject: [PATCH 199/203] Implement `BinXgcdMatrix::select` --- src/modular/bingcd/matrix.rs | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/modular/bingcd/matrix.rs b/src/modular/bingcd/matrix.rs index d11a48c25..2d329a702 100644 --- a/src/modular/bingcd/matrix.rs +++ b/src/modular/bingcd/matrix.rs @@ -102,6 +102,20 @@ impl BinXgcdMatrix { ) } + /// Return `b` if `c` is truthy, otherwise return `a`. + #[inline] + pub(crate) const fn select(a: &Self, b: &Self, c: ConstChoice) -> Self { + Self { + m00: Uint::select(&a.m00, &b.m00, c), + m01: Uint::select(&a.m01, &b.m01, c), + m10: Uint::select(&a.m10, &b.m10, c), + m11: Uint::select(&a.m11, &b.m11, c), + k: c.select_u32(a.k, b.k), + k_upper_bound: c.select_u32(a.k_upper_bound, b.k_upper_bound), + pattern: b.pattern.and(c).or(a.pattern.and(c.not())), + } + } + /// Apply this matrix to a vector of [Uint]s, returning the result as a vector of /// [ExtendedInt]s. #[inline] @@ -401,4 +415,28 @@ mod tests { ) ) } + + #[test] + fn test_select() { + let x = BinXgcdMatrix::new( + U64::from_u64(0), + U64::from_u64(1), + U64::from_u64(2), + U64::from_u64(3), + ConstChoice::FALSE, + 4, + 5, + ); + let y = BinXgcdMatrix::new( + U64::from_u64(6), + U64::from_u64(7), + U64::from_u64(8), + U64::from_u64(9), + ConstChoice::TRUE, + 11, + 12, + ); + assert_eq!(BinXgcdMatrix::select(&x, &y, ConstChoice::FALSE), x); + assert_eq!(BinXgcdMatrix::select(&x, &y, ConstChoice::TRUE), y); + } } From fbfa6114a52d0363395a5bac27095832ff89feef Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 3 Apr 2025 11:01:33 +0200 Subject: [PATCH 200/203] Implement `BinXgcdMatrix::get_subtraction_matrix` --- src/modular/bingcd/matrix.rs | 58 ++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/modular/bingcd/matrix.rs b/src/modular/bingcd/matrix.rs index 2d329a702..365388110 100644 --- a/src/modular/bingcd/matrix.rs +++ b/src/modular/bingcd/matrix.rs @@ -38,6 +38,33 @@ impl BinXgcdMatrix { 0, ); + /// Construct the matrix representing the subtraction of one vector element from the other. + /// Subtracts the top element from the bottom if `top_from_bottom` is truthy, and the one + /// subtracting the bottom element from the top otherwise. + /// + /// In other words, returns one of the following matrices, given `top_from_bottom` + /// ```text + /// true false + /// [ 1 0 ] [ 1 -1 ] + /// [ -1 1 ] or [ 0 1 ] + /// ``` + pub(crate) const fn get_subtraction_matrix( + top_from_bottom: ConstChoice, + k_upper_bound: u32, + ) -> Self { + let (mut m01, mut m10) = (Uint::ONE, Uint::ZERO); + Uint::conditional_swap(&mut m01, &mut m10, top_from_bottom); + Self::new( + Uint::ONE, + m01, + m10, + Uint::ONE, + ConstChoice::TRUE, + 0, + k_upper_bound, + ) + } + pub(crate) const fn new( m00: Uint, m01: Uint, @@ -439,4 +466,35 @@ mod tests { assert_eq!(BinXgcdMatrix::select(&x, &y, ConstChoice::FALSE), x); assert_eq!(BinXgcdMatrix::select(&x, &y, ConstChoice::TRUE), y); } + + #[test] + fn test_get_subtraction_matrix() { + let x = BinXgcdMatrix::get_subtraction_matrix(ConstChoice::TRUE, 35); + assert_eq!( + x, + BinXgcdMatrix::new( + U64::ONE, + U64::ZERO, + U64::ONE, + U64::ONE, + ConstChoice::TRUE, + 0, + 35 + ) + ); + + let x = BinXgcdMatrix::get_subtraction_matrix(ConstChoice::FALSE, 63); + assert_eq!( + x, + BinXgcdMatrix::new( + U64::ONE, + U64::ONE, + U64::ZERO, + U64::ONE, + ConstChoice::TRUE, + 0, + 63 + ) + ); + } } From 6c7c0d5e9eeb346a2e7ea681b69e9f3b0d11d07d Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 3 Apr 2025 12:54:43 +0200 Subject: [PATCH 201/203] Fix bug --- src/modular/bingcd/xgcd.rs | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index a1b486202..fef02b5c5 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -263,6 +263,7 @@ impl Odd> { let (mut a_sgn, mut b_sgn); let mut i = 0; while i < Self::MIN_BINGCD_ITERATIONS.div_ceil(K - 1) { + // Loop invariant: each iteration, `a.bits() + b.bits()` shrinks by at least K-1. i += 1; // Construct compact_a and compact_b as the summary of a and b, respectively. @@ -273,12 +274,32 @@ impl Odd> { let b_eq_compact_b = ConstChoice::from_u32_le(b_bits, K - 1).or(ConstChoice::from_u32_eq(n, 2 * K)); + // Verify that `compact_a` and `compact_b` preserve the ordering of `a` and `b`. + let a_cmp_b = Uint::cmp(&a, &b); + let compact_a_cmp_compact_b = Uint::cmp(&compact_a, &compact_b); + let compact_maintains_ordering = + ConstChoice::from_i8_eq(a_cmp_b, compact_a_cmp_compact_b); + // Compute the K-1 iteration update matrix from a_ and b_ - let (.., update_matrix) = compact_a + let (.., mut update_matrix) = compact_a .to_odd() .expect("a is always odd") .partial_binxgcd_vartime::(&compact_b, K - 1, b_eq_compact_b); + // Deal with the case that compacting loses the ordering of `(a, b)`. + // + // This can only occur whenever (at least) the top `K-1` bits of `compact_a` and + // `compact_b` are the same. As a result, subtracting one from the other causes the + // `a.bits() + b.bits()` to shrink by at least `K-1` (see loop invariant). + // It thus suffices to replace `update_matrix` with one that just subtracts one from the + // other. + let a_lt_b = ConstChoice::from_i8_eq(a_cmp_b, -1); + update_matrix = BinXgcdMatrix::select( + &BinXgcdMatrix::get_subtraction_matrix(a_lt_b, K - 1), + &update_matrix, + compact_maintains_ordering, + ); + // Update `a` and `b` using the update matrix let (updated_a, updated_b) = update_matrix.extended_apply_to((a, b)); (a, a_sgn) = updated_a.wrapping_drop_extension(); @@ -288,7 +309,15 @@ impl Odd> { assert!(b_sgn.not().to_bool_vartime(), "b is never negative"); matrix = update_matrix.wrapping_mul_right(&matrix); - matrix.conditional_negate(a_sgn); // TODO: find a cleaner solution for this + + // Cont. of dealing with the case that compacting loses the ordering of `(a, b)`. + // + // When `a > b`, and thus `b` is subtracted from `a`, it could be that `a` is now even. + // Since `a` should always be odd, we swap the two operands. Note that `b` must be odd, + // since subtracting it from the odd `a` yielded an even number. + let a_is_even = a.is_odd().not(); + Uint::conditional_swap(&mut a, &mut b, a_is_even); + matrix.conditional_swap_rows(a_is_even) } let gcd = a From b4ed2e1bc141b45d60549f838722ce5b18ebbc7b Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 3 Apr 2025 13:01:22 +0200 Subject: [PATCH 202/203] Make `test_optimized_binxgcd_edge_cases` platform independent --- src/modular/bingcd/xgcd.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index fef02b5c5..056b88297 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -928,7 +928,10 @@ mod tests { "1234567890ABCDEF800000000000000000000000000000007EDCBA0987654321", ); assert!(a > b); - assert!(a.compact::<64, 2>(256) < b.compact::<64, 2>(256)); + assert!( + a.compact::(U256::BITS) + < b.compact::(U256::BITS) + ); optimized_binxgcd_test(a, b); // Case #2: a < b but a.compact() > b.compact() @@ -942,7 +945,10 @@ mod tests { "1234567890ABCDEF800000000000000000000000000000007EDCBA0987654321", ); assert!(a > b); - assert_eq!(a.compact::<64, 2>(256), b.compact::<64, 2>(256)); + assert_eq!( + a.compact::(U256::BITS), + b.compact::(U256::BITS) + ); optimized_binxgcd_test(a, b); // Case #4: a < b but a.compact() = b.compact() From 86c355c0a872bb6ddf475c1ff78cfe93ad6cbe19 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 4 Apr 2025 09:49:04 +0200 Subject: [PATCH 203/203] Expand `optimized_binxgcd_` documentation --- src/modular/bingcd/xgcd.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/modular/bingcd/xgcd.rs b/src/modular/bingcd/xgcd.rs index 056b88297..7d119d516 100644 --- a/src/modular/bingcd/xgcd.rs +++ b/src/modular/bingcd/xgcd.rs @@ -263,7 +263,10 @@ impl Odd> { let (mut a_sgn, mut b_sgn); let mut i = 0; while i < Self::MIN_BINGCD_ITERATIONS.div_ceil(K - 1) { - // Loop invariant: each iteration, `a.bits() + b.bits()` shrinks by at least K-1. + // Loop invariants: + // i) each iteration of this loop, `a.bits() + b.bits()` shrinks by at least K-1, + // until `b = 0`. + // ii) `a` is odd. i += 1; // Construct compact_a and compact_b as the summary of a and b, respectively. @@ -286,13 +289,13 @@ impl Odd> { .expect("a is always odd") .partial_binxgcd_vartime::(&compact_b, K - 1, b_eq_compact_b); - // Deal with the case that compacting loses the ordering of `(a, b)`. - // - // This can only occur whenever (at least) the top `K-1` bits of `compact_a` and - // `compact_b` are the same. As a result, subtracting one from the other causes the - // `a.bits() + b.bits()` to shrink by at least `K-1` (see loop invariant). - // It thus suffices to replace `update_matrix` with one that just subtracts one from the - // other. + // Deal with the case that compacting loses the ordering of `(a, b)`. When this is the + // case, multiplying `update_matrix` with `(a, b)` will map one of the two to a negative + // value, which will break the algorithm. To resolve this, we observe that this case + // can only occur whenever (at least) the top `K+1` bits of `a` and `b` are the same. + // As a result, subtracting one from the other causes `a.bits() + b.bits()` to shrink by + // at least `K+1 > K-1` (as required by the loop invariant). It thus suffices to replace + // `update_matrix` with a matrix that represents subtracting one from the other. let a_lt_b = ConstChoice::from_i8_eq(a_cmp_b, -1); update_matrix = BinXgcdMatrix::select( &BinXgcdMatrix::get_subtraction_matrix(a_lt_b, K - 1), @@ -311,10 +314,9 @@ impl Odd> { matrix = update_matrix.wrapping_mul_right(&matrix); // Cont. of dealing with the case that compacting loses the ordering of `(a, b)`. - // - // When `a > b`, and thus `b` is subtracted from `a`, it could be that `a` is now even. - // Since `a` should always be odd, we swap the two operands. Note that `b` must be odd, - // since subtracting it from the odd `a` yielded an even number. + // When `a > b` -- and thus `b` is subtracted from `a` -- it could be that `a` is now + // even. Since `a` should always be odd, we swap the two operands. Note that `b` must be + // odd, since subtracting it from the odd `a` yielded an even number. let a_is_even = a.is_odd().not(); Uint::conditional_swap(&mut a, &mut b, a_is_even); matrix.conditional_swap_rows(a_is_even)