Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BoxedUint: add constant-time division implementation #398

Merged
merged 1 commit into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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