From af16416ac962a1d83ed08484381f936ef1817a8c Mon Sep 17 00:00:00 2001 From: Joe Doyle Date: Wed, 10 Jun 2026 00:03:23 -0400 Subject: [PATCH 1/3] Fix BoxedUint GCD with mixed-precision zeros Handle zero operands symmetrically before calling the nonzero GCD helper. The helper assumes the left operand is nonzero and is not valid for a mixed-precision zero right operand, because zero's trailing-zero count is bounded by its allocated precision. Replace either zero operand with one for the constant-time path, compute the dummy GCD, then conditionally correct the result with the original operand. For the variable-time path, return the nonzero operand directly, and return zero for gcd(0, 0). Co-authored-by: GPT 5.5 --- src/modular/safegcd/boxed.rs | 22 +++++--- src/uint/boxed/gcd.rs | 101 ++++++++++++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 9 deletions(-) diff --git a/src/modular/safegcd/boxed.rs b/src/modular/safegcd/boxed.rs index d8ace2e0..9030d733 100644 --- a/src/modular/safegcd/boxed.rs +++ b/src/modular/safegcd/boxed.rs @@ -129,17 +129,23 @@ fn invert_odd_mod_precomp( /// Calculate the greatest common denominator of `f` and `g`. pub fn gcd(f: &BoxedUint, g: &BoxedUint) -> BoxedUint { if VARTIME { - if let Some(f_nz) = f.as_nz_vartime() { - gcd_nz::(f_nz, g).get() - } else { - // gcd of (0, g) is g - g.clone() + match (f.as_nz_vartime(), g.as_nz_vartime()) { + (Some(f_nz), Some(_)) => gcd_nz::(f_nz, g).get(), + (Some(_), None) => f.clone(), + (None, Some(_)) => g.clone(), + (None, None) => { + BoxedUint::zero_with_precision(u32_max(f.bits_precision(), g.bits_precision())) + } } } else { let (f_nz, f_is_nonzero) = f.to_nz_or_one(); - // gcd of (0, g) is g - let mut r = gcd_nz::(&f_nz, g).get(); - r.ct_assign(g, !f_is_nonzero); + let (g_nz, g_is_nonzero) = g.to_nz_or_one(); + let mut r = gcd_nz::(&f_nz, g_nz.as_ref()).get(); + let bits_precision = r.bits_precision(); + let f = f.clone().resize_unchecked(bits_precision); + let g = g.clone().resize_unchecked(bits_precision); + r.ct_assign(&g, !f_is_nonzero); + r.ct_assign(&f, !g_is_nonzero); r } } diff --git a/src/uint/boxed/gcd.rs b/src/uint/boxed/gcd.rs index 43f6f9b5..e9657ccb 100644 --- a/src/uint/boxed/gcd.rs +++ b/src/uint/boxed/gcd.rs @@ -42,7 +42,7 @@ impl Gcd for Odd { #[cfg(test)] mod tests { - use crate::{BoxedUint, Gcd, Resize}; + use crate::{BoxedUint, Gcd, Limb, Resize}; #[test] fn gcd_relatively_prime() { @@ -71,6 +71,105 @@ mod tests { assert_eq!(one.gcd(&zero), one); } + #[test] + fn gcd_zero_lhs_wider_than_rhs() { + let zero = BoxedUint::zero_with_precision(2 * Limb::BITS); + let one = BoxedUint::one(); + + let gcd = zero.gcd(&one); + assert_eq!(gcd, one); + assert_eq!(gcd.bits_precision(), zero.bits_precision()); + } + + #[test] + fn gcd_zero_rhs_wider_than_lhs() { + let one = BoxedUint::one(); + let zero = BoxedUint::zero_with_precision(2 * Limb::BITS); + + let gcd = one.gcd(&zero); + assert_eq!(gcd, one); + } + + #[test] + fn gcd_zero_rhs_narrower_than_power_of_two_lhs() { + let x = BoxedUint::one_with_precision(3 * Limb::BITS).wrapping_shl_vartime(Limb::BITS + 1); + let zero = BoxedUint::zero_with_precision(Limb::BITS); + + let gcd = x.gcd(&zero); + assert_eq!(gcd, x); + } + + #[test] + fn gcd_vartime_zero_rhs_narrower_than_power_of_two_lhs() { + let x = BoxedUint::one_with_precision(3 * Limb::BITS).wrapping_shl_vartime(Limb::BITS + 1); + let zero = BoxedUint::zero_with_precision(Limb::BITS); + + let gcd = x.gcd_vartime(&zero); + assert_eq!(gcd, x); + } + + #[test] + fn gcd_nonzero_lhs_wider_than_rhs_precision() { + let wide = BoxedUint::from(6u8).resize(2 * Limb::BITS); + let narrow = BoxedUint::from(4u8); + + let gcd = wide.gcd(&narrow); + assert_eq!(gcd, BoxedUint::from(2u8)); + assert_eq!(gcd.bits_precision(), wide.bits_precision()); + } + + #[test] + fn gcd_nonzero_rhs_wider_than_lhs_precision() { + let narrow = BoxedUint::from(4u8); + let wide = BoxedUint::from(6u8).resize(2 * Limb::BITS); + + let gcd = narrow.gcd(&wide); + assert_eq!(gcd, BoxedUint::from(2u8)); + assert_eq!(gcd.bits_precision(), wide.bits_precision()); + } + + #[test] + fn gcd_vartime_zero_lhs_wider_than_rhs() { + let zero = BoxedUint::zero_with_precision(2 * Limb::BITS); + let one = BoxedUint::one(); + + let gcd = zero.gcd_vartime(&one); + assert_eq!(gcd, one); + assert_eq!(gcd.bits_precision(), one.bits_precision()); + } + + #[test] + fn gcd_vartime_zero_rhs_wider_than_lhs() { + let one = BoxedUint::one(); + let zero = BoxedUint::zero_with_precision(2 * Limb::BITS); + + let gcd = one.gcd_vartime(&zero); + assert_eq!(gcd, one); + assert_eq!(gcd.bits_precision(), one.bits_precision()); + } + + #[test] + fn gcd_zero_zero_mixed_precision() { + let narrow = BoxedUint::zero_with_precision(Limb::BITS); + let wide = BoxedUint::zero_with_precision(2 * Limb::BITS); + + let gcd = narrow.gcd(&wide); + assert_eq!(gcd, narrow); + assert_eq!(gcd.bits_precision(), wide.bits_precision()); + assert_eq!(gcd, wide.gcd(&narrow)); + } + + #[test] + fn gcd_vartime_zero_zero_mixed_precision() { + let narrow = BoxedUint::zero_with_precision(Limb::BITS); + let wide = BoxedUint::zero_with_precision(2 * Limb::BITS); + + let gcd = narrow.gcd_vartime(&wide); + assert_eq!(gcd, narrow); + assert_eq!(gcd.bits_precision(), wide.bits_precision()); + assert_eq!(gcd, wide.gcd_vartime(&narrow)); + } + #[test] fn gcd_one() { let f = BoxedUint::from(1u32); From 0b69d071506322b6805f8e4459b20acd44576555 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 25 Jun 2026 16:17:50 -0600 Subject: [PATCH 2/3] handle smaller rhs argument in BoxedUint::ct_assign, UintRef::ct_assign Signed-off-by: Andrew Whitehead --- src/uint/boxed/ct.rs | 4 ++-- src/uint/ref_type/ct.rs | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/uint/boxed/ct.rs b/src/uint/boxed/ct.rs index a7d1e78c..6b30b75f 100644 --- a/src/uint/boxed/ct.rs +++ b/src/uint/boxed/ct.rs @@ -7,8 +7,8 @@ use ctutils::{CtAssignSlice, CtEqSlice}; impl CtAssign for BoxedUint { #[inline] fn ct_assign(&mut self, other: &Self, choice: Choice) { - assert_eq!(self.bits_precision(), other.bits_precision()); - self.limbs.ct_assign(&other.limbs, choice); + self.as_mut_uint_ref() + .ct_assign(other.as_uint_ref(), choice); } } impl CtAssignSlice for BoxedUint {} diff --git a/src/uint/ref_type/ct.rs b/src/uint/ref_type/ct.rs index caf3a6ca..cfd353f4 100644 --- a/src/uint/ref_type/ct.rs +++ b/src/uint/ref_type/ct.rs @@ -6,8 +6,10 @@ use crate::{Choice, CtAssign, CtEq, CtLt}; impl CtAssign for UintRef { #[inline] fn ct_assign(&mut self, other: &Self, choice: Choice) { - debug_assert_eq!(self.bits_precision(), other.bits_precision()); - self.limbs.ct_assign(&other.limbs, choice); + assert!(self.bits_precision() >= other.bits_precision()); + let (lo, hi) = self.split_at_mut(other.nlimbs()); + lo.limbs.ct_assign(&other.limbs, choice); + hi.conditional_set_zero(choice); } } From 9d898658f1f11f111e545ef01c3b721d714a50ea Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 25 Jun 2026 16:19:41 -0600 Subject: [PATCH 3/3] improve handling of mixed-size operands in BoxedUint GCD; consistently return highest bit width Signed-off-by: Andrew Whitehead --- src/modular/safegcd/boxed.rs | 26 +++++++++++--------------- src/uint/boxed/gcd.rs | 4 ++-- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/modular/safegcd/boxed.rs b/src/modular/safegcd/boxed.rs index 9030d733..09f4695a 100644 --- a/src/modular/safegcd/boxed.rs +++ b/src/modular/safegcd/boxed.rs @@ -129,23 +129,16 @@ fn invert_odd_mod_precomp( /// Calculate the greatest common denominator of `f` and `g`. pub fn gcd(f: &BoxedUint, g: &BoxedUint) -> BoxedUint { if VARTIME { - match (f.as_nz_vartime(), g.as_nz_vartime()) { - (Some(f_nz), Some(_)) => gcd_nz::(f_nz, g).get(), - (Some(_), None) => f.clone(), - (None, Some(_)) => g.clone(), - (None, None) => { - BoxedUint::zero_with_precision(u32_max(f.bits_precision(), g.bits_precision())) - } + if let Some(f_nz) = f.as_nz_vartime() { + gcd_nz::(f_nz, g).get() + } else { + // gcd of (0, g) is g + g.resize(u32_max(f.bits_precision(), g.bits_precision())) } } else { let (f_nz, f_is_nonzero) = f.to_nz_or_one(); - let (g_nz, g_is_nonzero) = g.to_nz_or_one(); - let mut r = gcd_nz::(&f_nz, g_nz.as_ref()).get(); - let bits_precision = r.bits_precision(); - let f = f.clone().resize_unchecked(bits_precision); - let g = g.clone().resize_unchecked(bits_precision); - r.ct_assign(&g, !f_is_nonzero); - r.ct_assign(&f, !g_is_nonzero); + let mut r = gcd_nz::(&f_nz, g).get(); + r.ct_assign(g, !f_is_nonzero); r } } @@ -169,7 +162,10 @@ pub fn gcd_nz(f: &NonZero, g: &BoxedUint) -> Non // 4) 2^k•gcd(f, g) = 2^k•gcd(a, 2^j•b) let i = f.as_ref().trailing_zeros(); - let k = u32_min(i, g.trailing_zeros()); + let j = g + .is_nonzero() + .select_u32(f.bits_precision(), g.trailing_zeros()); + let k = u32_min(i, j); let f_odd = Odd::new_unchecked(f.as_ref().shr(i)); let mut r = gcd_odd::(&f_odd, g).get(); diff --git a/src/uint/boxed/gcd.rs b/src/uint/boxed/gcd.rs index e9657ccb..b802520c 100644 --- a/src/uint/boxed/gcd.rs +++ b/src/uint/boxed/gcd.rs @@ -135,7 +135,7 @@ mod tests { let gcd = zero.gcd_vartime(&one); assert_eq!(gcd, one); - assert_eq!(gcd.bits_precision(), one.bits_precision()); + assert_eq!(gcd.bits_precision(), zero.bits_precision()); } #[test] @@ -145,7 +145,7 @@ mod tests { let gcd = one.gcd_vartime(&zero); assert_eq!(gcd, one); - assert_eq!(gcd.bits_precision(), one.bits_precision()); + assert_eq!(gcd.bits_precision(), zero.bits_precision()); } #[test]