Skip to content

Commit e25b054

Browse files
Fixes for exact division edge cases (#1310)
This adds fixes for a couple types of inputs that were not producing the correct result: - Zero dividends should always divide exactly, even when the divisor has more limbs - Inexact division could be undetected when the dividend had one or more zero-valued lower limbs The suite of proptests for BoxedUint are updated to explicitly create inputs with a single bit set, only high bits set, and only low bits set (which also detects these issues). Some tests are expanded to test differently-sized inputs where this was not being exercised before. --------- Signed-off-by: Andrew Whitehead <cywolf@gmail.com>
1 parent dc7ee98 commit e25b054

3 files changed

Lines changed: 183 additions & 112 deletions

File tree

src/uint/ref_type/div.rs

Lines changed: 80 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -606,30 +606,28 @@ impl UintRef {
606606
/// If the divisor is zero.
607607
#[inline(always)]
608608
pub(crate) const fn div_exact(&mut self, rhs: &mut UintRef) -> Choice {
609-
let x_prec = self.bits_precision();
610609
let y_bits = rhs.bits();
611610
assert!(y_bits > 0, "zero divisor");
612611

613-
let tz = rhs.trailing_zeros();
614-
// Track whether there are more zeros in the divisor than bits in the dividend
615-
let excess_z = Choice::from_u32_lt(x_prec, tz);
616-
let tz = excess_z.select_u32(tz, x_prec);
612+
let x_tz = self.trailing_zeros();
613+
let x_zero = Choice::from_u32_eq(x_tz, self.bits_precision());
614+
let y_tz = rhs.trailing_zeros();
617615

618616
// Shift the divisor such that it is odd
619-
rhs.shr_assign(tz);
617+
rhs.shr_assign(y_tz);
620618

621-
// Check that the dividend evenly divides by 2^tz, and shift it to match the divisor
622-
let div2s_exact = self.ensure_trailing_zeros(tz).and(excess_z.not());
623-
self.unbounded_shr_assign(tz);
619+
// Check that the dividend evenly divides by 2^y+tz, and shift it to match the divisor
620+
let div2s_exact = Choice::from_u32_le(y_tz, x_tz).or(x_zero);
621+
self.unbounded_shr_assign(y_tz);
624622

625623
let y = Odd::new_ref_unchecked(rhs);
626624
let y_inv = y.invert_mod_limb();
627-
let ywords = bitlen::to_limbs(y_bits - tz);
625+
let ywords = bitlen::to_limbs(y_bits - y_tz);
628626
let is_exact =
629627
Self::div_exact_odd_with_inverse::<false>(self, y, y_inv, ywords).and(div2s_exact);
630628

631629
// Restore the divisor
632-
rhs.shl_assign(tz);
630+
rhs.shl_assign(y_tz);
633631

634632
is_exact
635633
}
@@ -643,32 +641,28 @@ impl UintRef {
643641
/// If the divisor is zero.
644642
#[inline(always)]
645643
pub(crate) const fn div_exact_vartime(&mut self, rhs: &mut UintRef) -> Choice {
646-
let x_prec = self.bits_precision();
647644
let y_bits = rhs.bits_vartime();
648645
assert!(y_bits > 0, "zero divisor");
649646

650-
let tz = rhs.trailing_zeros_vartime();
651-
if tz > x_prec {
652-
// The divisor exceeds the dividend precision. Short circuit based on public
653-
// information (the divisor and the input size in limbs)
654-
return Choice::FALSE;
655-
}
656-
// Reduce the divisor to its populated limbs and shift it such that it is odd
657-
let rhs = rhs.leading_mut(bitlen::to_limbs(y_bits));
658-
rhs.unbounded_shr_assign_vartime(tz);
647+
let x_tz = self.trailing_zeros();
648+
let x_zero = Choice::from_u32_eq(x_tz, self.bits_precision());
649+
let y_tz = rhs.trailing_zeros_vartime();
650+
651+
// Shift the divisor to be odd
652+
rhs.unbounded_shr_assign_vartime(y_tz);
659653

660-
// Check that the dividend evenly divides by 2^tz, and shift it to match the divisor
661-
let div2s_exact = self.ensure_trailing_zeros(tz);
662-
self.unbounded_shr_assign_vartime(tz);
654+
// Check that the dividend evenly divides by 2^y_tz, and shift it to match the divisor
655+
let div2s_exact = Choice::from_u32_le(y_tz, x_tz).or(x_zero);
656+
self.unbounded_shr_assign_vartime(y_tz);
663657

664-
let ywords = bitlen::to_limbs(y_bits - tz);
665-
let y = Odd::new_ref_unchecked(rhs.leading(ywords));
658+
let ywords = bitlen::to_limbs(y_bits - y_tz);
659+
let y = Odd::new_ref_unchecked(rhs.leading_mut(ywords));
666660
let y_inv = y.invert_mod_limb();
667661
let is_exact =
668662
Self::div_exact_odd_with_inverse::<true>(self, y, y_inv, ywords).and(div2s_exact);
669663

670664
// Restore the divisor
671-
rhs.unbounded_shl_assign_vartime(tz);
665+
rhs.unbounded_shl_assign_vartime(y_tz);
672666

673667
is_exact
674668
}
@@ -696,22 +690,26 @@ impl UintRef {
696690
let y = y.as_ref();
697691
let yc = y.nlimbs();
698692
let mut meta_carry = Limb::ZERO;
693+
let mut zero_hi = Choice::TRUE;
699694
let mut xi = 0;
700695

701696
while xi < xc {
702697
let y_remain = if yc < (xc - xi) { yc } else { xc - xi };
703698
// This loop is a no-op once there are fewer words remaining than the size of the divisor
704699
let done = usize_lt(y_remain, ywords);
705700
if VARTIME && done.to_bool_vartime() {
706-
// Set the upper limbs to zero
707-
x.trailing_mut(xi).fill(Limb::ZERO);
701+
zero_hi = x.trailing(xi).is_zero();
708702
break;
709703
}
710704

711705
// Compute the quotient limb that will clear the low dividend limb
712-
let quo = Limb::select(x.limbs[xi].wrapping_mul(y_inv), Limb::ZERO, done);
706+
let quo = Limb::select(x.limbs[xi].wrapping_mul(y_inv), x.limbs[xi], done);
713707
x.limbs[xi] = quo;
714708

709+
if !VARTIME {
710+
zero_hi = zero_hi.and(Limb::select(Limb::ZERO, quo, done).is_zero());
711+
}
712+
715713
let (_, mut carry) = quo.widening_mul(y.limbs[0]);
716714
let mut sub;
717715
let mut borrow = Limb::ZERO;
@@ -735,20 +733,60 @@ impl UintRef {
735733
xi += 1;
736734
}
737735

738-
meta_carry.is_zero()
736+
meta_carry.is_zero().and(zero_hi)
739737
}
738+
}
740739

741-
// Check that `self` can be cleanly divided by 2^zs: the bottom zs bits are zero.
742-
#[inline(always)]
743-
const fn ensure_trailing_zeros(&self, zs: u32) -> Choice {
744-
let z_words = (zs >> Limb::LOG2_BITS) as usize;
745-
let z_bits = zs & (Limb::BITS - 1);
746-
if z_words >= self.nlimbs() {
747-
self.is_zero()
748-
} else {
749-
self.leading(z_words)
750-
.is_zero()
751-
.and(self.limbs[z_words].restrict_bits(z_bits).is_zero())
740+
#[cfg(test)]
741+
mod tests {
742+
use crate::{U64, U128, U192, Uint};
743+
744+
#[test]
745+
fn div_exact_inexact() {
746+
fn check<const L: usize, const R: usize>(lhs: Uint<L>, rhs: Uint<R>, exact: bool) {
747+
let (mut q, mut r) = (lhs, rhs);
748+
let actual = q
749+
.as_mut_uint_ref()
750+
.div_exact(r.as_mut_uint_ref())
751+
.to_bool_vartime();
752+
assert_eq!(actual, exact, "{lhs} / {rhs}: exact={actual}");
753+
let (mut q, mut r) = (lhs, rhs);
754+
let actual = q
755+
.as_mut_uint_ref()
756+
.div_exact_vartime(r.as_mut_uint_ref())
757+
.to_bool_vartime();
758+
assert_eq!(actual, exact, "{lhs} / {rhs}: exact={actual}");
752759
}
760+
761+
check(
762+
U64::from_be_hex("0000000000000010"),
763+
U64::from_be_hex("0000000000000010"),
764+
true,
765+
);
766+
check(
767+
U64::from_be_hex("0000000000000010"),
768+
U128::from_be_hex("00000000000000000000000000000001"),
769+
true,
770+
);
771+
check(
772+
U64::from_be_hex("0000000000000000"),
773+
U128::from_be_hex("10000000000000000000000000000000"),
774+
true,
775+
);
776+
check(
777+
U64::from_be_hex("0000000000000001"),
778+
U64::from_be_hex("0000000000000010"),
779+
false,
780+
);
781+
check(
782+
U64::from_be_hex("0000000000000001"),
783+
U128::from_be_hex("00000000000000100000000000000000"),
784+
false,
785+
);
786+
check(
787+
U192::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000"),
788+
U192::from_be_hex("000000000000000100000000000000003ACEDC010F13471D"),
789+
false,
790+
);
753791
}
754792
}

0 commit comments

Comments
 (0)