Skip to content

Commit

Permalink
BoxedUint: add constant-time division implementation (#398)
Browse files Browse the repository at this point in the history
Adapts the implementation originally from #277 to `BoxedUint`, adding
the following methods:

- `BoxedUint::div_rem`
- `BoxedUint::rem`

Additionally, `wrapping_div` and `checked_div` have been changed to use
the constant-time versions, rather than `*_vartime`.
  • Loading branch information
tarcieri authored Dec 6, 2023
1 parent 41ba936 commit f6d0374
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 18 deletions.
6 changes: 2 additions & 4 deletions src/modular/boxed_residue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,18 @@ impl BoxedResidueParams {
.expect("modulus ensured non-zero");

let r = BoxedUint::max(bits_precision)
.rem_vartime(&modulus_nz)
.rem(&modulus_nz)
.wrapping_add(&BoxedUint::one());

let r2 = r
.square()
.rem_vartime(&modulus_nz.widen(bits_precision * 2)) // TODO(tarcieri): constant time
.rem(&modulus_nz.widen(bits_precision * 2))
.shorten(bits_precision);

// Since we are calculating the inverse modulo (Word::MAX+1),
// we can take the modulo right away and calculate the inverse of the first limb only.
let modulus_lo = BoxedUint::from(modulus.limbs.get(0).copied().unwrap_or_default());

let mod_neg_inv = Limb(Word::MIN.wrapping_sub(modulus_lo.inv_mod2k(Word::BITS).limbs[0].0));

let r3 = montgomery_reduction_boxed(&mut r2.square(), &modulus, mod_neg_inv);

let params = Self {
Expand Down
84 changes: 70 additions & 14 deletions src/uint/boxed/div.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,25 @@

use crate::{BoxedUint, CheckedDiv, Limb, NonZero, Wrapping};
use core::ops::{Div, DivAssign, Rem, RemAssign};
use subtle::{Choice, ConstantTimeEq, CtOption};
use subtle::{Choice, ConstantTimeEq, ConstantTimeLess, CtOption};

impl BoxedUint {
/// Computes self / rhs, returns the quotient, remainder.
pub fn div_rem(&self, rhs: &NonZero<Self>) -> (Self, Self) {
// Since `rhs` is nonzero, this should always hold.
self.div_rem_unchecked(rhs.as_ref())
}

/// Computes self % rhs, returns the remainder.
pub fn rem(&self, rhs: &NonZero<Self>) -> Self {
self.div_rem(rhs).1
}

/// Computes self / rhs, returns the quotient, remainder.
///
/// Variable-time with respect to `rhs`
pub fn div_rem_vartime(&self, rhs: &NonZero<Self>) -> (Self, Self) {
// Since `rhs` is nonzero, this should always hold.
self.div_rem_vartime_unchecked(rhs.as_ref())
}

Expand Down Expand Up @@ -45,23 +57,60 @@ impl BoxedUint {
///
/// Panics if `rhs == 0`.
pub fn wrapping_div(&self, rhs: &NonZero<Self>) -> Self {
self.div_rem_vartime(rhs).0
self.div_rem(rhs).0
}

/// Perform checked division, returning a [`CtOption`] which `is_some`
/// only if the rhs != 0
pub fn checked_div(&self, rhs: &Self) -> CtOption<Self> {
CtOption::new(self.div_rem_vartime_unchecked(rhs).0, rhs.is_zero())
let q = self.div_rem_unchecked(rhs).0;
CtOption::new(q, !rhs.is_zero())
}

/// Compute divison and remainder without checking `rhs` is zero.
fn div_rem_vartime_unchecked(&self, rhs: &Self) -> (Self, Self) {
/// Computes `self` / `rhs`, returns the quotient (q), remainder (r) without checking if `rhs`
/// is zero.
///
/// This function is constant-time with respect to both `self` and `rhs`.
fn div_rem_unchecked(&self, rhs: &Self) -> (Self, Self) {
debug_assert_eq!(self.bits_precision(), rhs.bits_precision());
let mb = rhs.bits();
let bits_precision = self.bits_precision();
let mut rem = self.clone();
let mut quo = Self::zero_with_precision(bits_precision);
let mut c = rhs.shl(bits_precision - mb);
let mut i = bits_precision;
let mut done = Choice::from(0u8);

loop {
let (mut r, borrow) = rem.sbb(&c, Limb::ZERO);
rem = Self::conditional_select(&r, &rem, Choice::from((borrow.0 & 1) as u8) | done);
r = quo.bitor(&Self::one());
quo = Self::conditional_select(&r, &quo, Choice::from((borrow.0 & 1) as u8) | done);
if i == 0 {
break;
}
i -= 1;
// when `i < mb`, the computation is actually done, so we ensure `quo` and `rem`
// aren't modified further (but do the remaining iterations anyway to be constant-time)
done = i.ct_lt(&mb);
c.shr1_assign();
quo = Self::conditional_select(&quo.shl1(), &quo, done);
}

(quo, rem)
}

/// Computes `self` / `rhs`, returns the quotient (q), remainder (r) without checking if `rhs`
/// is zero.
///
/// This function operates in variable-time.
fn div_rem_vartime_unchecked(&self, rhs: &Self) -> (Self, Self) {
debug_assert_eq!(self.bits_precision(), rhs.bits_precision());
let mb = rhs.bits_vartime();
let mut bd = self.bits_precision() - mb;
let mut remainder = self.clone();
let mut quotient = Self::zero_with_precision(self.bits_precision());
let mut c = rhs.shl(bd);
let mut c = rhs.shl_vartime(bd);

loop {
let (mut r, borrow) = remainder.sbb(&c, Limb::ZERO);
Expand All @@ -74,7 +123,7 @@ impl BoxedUint {
}
bd -= 1;
c.shr1_assign();
quotient = quotient.shl(1);
quotient.shl1_assign();
}

(quotient, remainder)
Expand Down Expand Up @@ -125,7 +174,7 @@ impl Div<NonZero<BoxedUint>> for BoxedUint {
type Output = BoxedUint;

fn div(self, rhs: NonZero<BoxedUint>) -> Self::Output {
self.div_rem_vartime(&rhs).0
self.div_rem(&rhs).0
}
}

Expand Down Expand Up @@ -190,7 +239,7 @@ impl Rem<&NonZero<BoxedUint>> for &BoxedUint {

#[inline]
fn rem(self, rhs: &NonZero<BoxedUint>) -> Self::Output {
self.rem_vartime(rhs)
self.rem(rhs)
}
}

Expand All @@ -199,7 +248,7 @@ impl Rem<&NonZero<BoxedUint>> for BoxedUint {

#[inline]
fn rem(self, rhs: &NonZero<BoxedUint>) -> Self::Output {
self.rem_vartime(rhs)
Self::rem(&self, rhs)
}
}

Expand All @@ -208,7 +257,7 @@ impl Rem<NonZero<BoxedUint>> for &BoxedUint {

#[inline]
fn rem(self, rhs: NonZero<BoxedUint>) -> Self::Output {
self.rem_vartime(&rhs)
self.rem(&rhs)
}
}

Expand All @@ -217,26 +266,33 @@ impl Rem<NonZero<BoxedUint>> for BoxedUint {

#[inline]
fn rem(self, rhs: NonZero<BoxedUint>) -> Self::Output {
self.rem_vartime(&rhs)
self.rem(&rhs)
}
}

impl RemAssign<&NonZero<BoxedUint>> for BoxedUint {
fn rem_assign(&mut self, rhs: &NonZero<BoxedUint>) {
*self = self.rem_vartime(rhs)
*self = Self::rem(self, rhs)
}
}

impl RemAssign<NonZero<BoxedUint>> for BoxedUint {
fn rem_assign(&mut self, rhs: NonZero<BoxedUint>) {
*self = self.rem_vartime(&rhs)
*self = Self::rem(self, &rhs)
}
}

#[cfg(test)]
mod tests {
use super::{BoxedUint, NonZero};

#[test]
fn rem() {
let n = BoxedUint::from(0xFFEECCBBAA99887766u128);
let p = NonZero::new(BoxedUint::from(997u128)).unwrap();
assert_eq!(BoxedUint::from(648u128), n.rem(&p));
}

#[test]
fn rem_vartime() {
let n = BoxedUint::from(0xFFEECCBBAA99887766u128);
Expand Down
12 changes: 12 additions & 0 deletions src/uint/boxed/shl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,18 @@ impl BoxedUint {

(Self { limbs }, Limb(carry))
}

/// Computes `self >> 1` in constant-time.
pub(crate) fn shl1(&self) -> Self {
// TODO(tarcieri): optimized implementation
self.shl_vartime(1)
}

/// Computes `self >> 1` in-place in constant-time.
pub(crate) fn shl1_assign(&mut self) {
// TODO(tarcieri): optimized implementation
*self = self.shl1();
}
}

impl Shl<u32> for BoxedUint {
Expand Down
43 changes: 43 additions & 0 deletions tests/boxed_uint_proptests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,20 @@ proptest! {
}
}

#[test]
fn checked_div((a, b) in uint_pair()) {
let actual = a.checked_div(&b);

if b.is_zero().into() {
prop_assert!(bool::from(actual.is_none()));
} else {
let a_bi = to_biguint(&a);
let b_bi = to_biguint(&b);
let expected = &a_bi / &b_bi;
prop_assert_eq!(expected, to_biguint(&actual.unwrap()));
}
}

#[test]
fn div_rem((a, mut b) in uint_pair()) {
if b.is_zero().into() {
Expand All @@ -105,6 +119,22 @@ proptest! {
let expected_quotient = &a_bi / &b_bi;
let expected_remainder = a_bi % b_bi;

let (actual_quotient, actual_remainder) = a.div_rem(&NonZero::new(b).unwrap());
prop_assert_eq!(expected_quotient, to_biguint(&actual_quotient));
prop_assert_eq!(expected_remainder, to_biguint(&actual_remainder));
}

#[test]
fn div_rem_vartime((a, mut b) in uint_pair()) {
if b.is_zero().into() {
b = b.wrapping_add(&BoxedUint::one());
}

let a_bi = to_biguint(&a);
let b_bi = to_biguint(&b);
let expected_quotient = &a_bi / &b_bi;
let expected_remainder = a_bi % b_bi;

let (actual_quotient, actual_remainder) = a.div_rem_vartime(&NonZero::new(b).unwrap());
prop_assert_eq!(expected_quotient, to_biguint(&actual_quotient));
prop_assert_eq!(expected_remainder, to_biguint(&actual_remainder));
Expand Down Expand Up @@ -157,6 +187,19 @@ proptest! {
prop_assert_eq!(expected, to_biguint(&actual));
}

#[test]
fn rem((a, b) in uint_pair()) {
if bool::from(!b.is_zero()) {
let a_bi = to_biguint(&a);
let b_bi = to_biguint(&b);

let expected = a_bi % b_bi;
let actual = a.rem(&NonZero::new(b).unwrap());

prop_assert_eq!(expected, to_biguint(&actual));
}
}

#[test]
fn rem_vartime((a, b) in uint_pair()) {
if bool::from(!b.is_zero()) {
Expand Down

0 comments on commit f6d0374

Please sign in to comment.