Skip to content

Commit f6d0374

Browse files
authored
BoxedUint: add constant-time division implementation (#398)
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`.
1 parent 41ba936 commit f6d0374

File tree

4 files changed

+127
-18
lines changed

4 files changed

+127
-18
lines changed

src/modular/boxed_residue.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,20 +58,18 @@ impl BoxedResidueParams {
5858
.expect("modulus ensured non-zero");
5959

6060
let r = BoxedUint::max(bits_precision)
61-
.rem_vartime(&modulus_nz)
61+
.rem(&modulus_nz)
6262
.wrapping_add(&BoxedUint::one());
6363

6464
let r2 = r
6565
.square()
66-
.rem_vartime(&modulus_nz.widen(bits_precision * 2)) // TODO(tarcieri): constant time
66+
.rem(&modulus_nz.widen(bits_precision * 2))
6767
.shorten(bits_precision);
6868

6969
// Since we are calculating the inverse modulo (Word::MAX+1),
7070
// we can take the modulo right away and calculate the inverse of the first limb only.
7171
let modulus_lo = BoxedUint::from(modulus.limbs.get(0).copied().unwrap_or_default());
72-
7372
let mod_neg_inv = Limb(Word::MIN.wrapping_sub(modulus_lo.inv_mod2k(Word::BITS).limbs[0].0));
74-
7573
let r3 = montgomery_reduction_boxed(&mut r2.square(), &modulus, mod_neg_inv);
7674

7775
let params = Self {

src/uint/boxed/div.rs

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,25 @@
22
33
use crate::{BoxedUint, CheckedDiv, Limb, NonZero, Wrapping};
44
use core::ops::{Div, DivAssign, Rem, RemAssign};
5-
use subtle::{Choice, ConstantTimeEq, CtOption};
5+
use subtle::{Choice, ConstantTimeEq, ConstantTimeLess, CtOption};
66

77
impl BoxedUint {
8+
/// Computes self / rhs, returns the quotient, remainder.
9+
pub fn div_rem(&self, rhs: &NonZero<Self>) -> (Self, Self) {
10+
// Since `rhs` is nonzero, this should always hold.
11+
self.div_rem_unchecked(rhs.as_ref())
12+
}
13+
14+
/// Computes self % rhs, returns the remainder.
15+
pub fn rem(&self, rhs: &NonZero<Self>) -> Self {
16+
self.div_rem(rhs).1
17+
}
18+
819
/// Computes self / rhs, returns the quotient, remainder.
920
///
1021
/// Variable-time with respect to `rhs`
1122
pub fn div_rem_vartime(&self, rhs: &NonZero<Self>) -> (Self, Self) {
23+
// Since `rhs` is nonzero, this should always hold.
1224
self.div_rem_vartime_unchecked(rhs.as_ref())
1325
}
1426

@@ -45,23 +57,60 @@ impl BoxedUint {
4557
///
4658
/// Panics if `rhs == 0`.
4759
pub fn wrapping_div(&self, rhs: &NonZero<Self>) -> Self {
48-
self.div_rem_vartime(rhs).0
60+
self.div_rem(rhs).0
4961
}
5062

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

57-
/// Compute divison and remainder without checking `rhs` is zero.
58-
fn div_rem_vartime_unchecked(&self, rhs: &Self) -> (Self, Self) {
70+
/// Computes `self` / `rhs`, returns the quotient (q), remainder (r) without checking if `rhs`
71+
/// is zero.
72+
///
73+
/// This function is constant-time with respect to both `self` and `rhs`.
74+
fn div_rem_unchecked(&self, rhs: &Self) -> (Self, Self) {
5975
debug_assert_eq!(self.bits_precision(), rhs.bits_precision());
6076
let mb = rhs.bits();
77+
let bits_precision = self.bits_precision();
78+
let mut rem = self.clone();
79+
let mut quo = Self::zero_with_precision(bits_precision);
80+
let mut c = rhs.shl(bits_precision - mb);
81+
let mut i = bits_precision;
82+
let mut done = Choice::from(0u8);
83+
84+
loop {
85+
let (mut r, borrow) = rem.sbb(&c, Limb::ZERO);
86+
rem = Self::conditional_select(&r, &rem, Choice::from((borrow.0 & 1) as u8) | done);
87+
r = quo.bitor(&Self::one());
88+
quo = Self::conditional_select(&r, &quo, Choice::from((borrow.0 & 1) as u8) | done);
89+
if i == 0 {
90+
break;
91+
}
92+
i -= 1;
93+
// when `i < mb`, the computation is actually done, so we ensure `quo` and `rem`
94+
// aren't modified further (but do the remaining iterations anyway to be constant-time)
95+
done = i.ct_lt(&mb);
96+
c.shr1_assign();
97+
quo = Self::conditional_select(&quo.shl1(), &quo, done);
98+
}
99+
100+
(quo, rem)
101+
}
102+
103+
/// Computes `self` / `rhs`, returns the quotient (q), remainder (r) without checking if `rhs`
104+
/// is zero.
105+
///
106+
/// This function operates in variable-time.
107+
fn div_rem_vartime_unchecked(&self, rhs: &Self) -> (Self, Self) {
108+
debug_assert_eq!(self.bits_precision(), rhs.bits_precision());
109+
let mb = rhs.bits_vartime();
61110
let mut bd = self.bits_precision() - mb;
62111
let mut remainder = self.clone();
63112
let mut quotient = Self::zero_with_precision(self.bits_precision());
64-
let mut c = rhs.shl(bd);
113+
let mut c = rhs.shl_vartime(bd);
65114

66115
loop {
67116
let (mut r, borrow) = remainder.sbb(&c, Limb::ZERO);
@@ -74,7 +123,7 @@ impl BoxedUint {
74123
}
75124
bd -= 1;
76125
c.shr1_assign();
77-
quotient = quotient.shl(1);
126+
quotient.shl1_assign();
78127
}
79128

80129
(quotient, remainder)
@@ -125,7 +174,7 @@ impl Div<NonZero<BoxedUint>> for BoxedUint {
125174
type Output = BoxedUint;
126175

127176
fn div(self, rhs: NonZero<BoxedUint>) -> Self::Output {
128-
self.div_rem_vartime(&rhs).0
177+
self.div_rem(&rhs).0
129178
}
130179
}
131180

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

191240
#[inline]
192241
fn rem(self, rhs: &NonZero<BoxedUint>) -> Self::Output {
193-
self.rem_vartime(rhs)
242+
self.rem(rhs)
194243
}
195244
}
196245

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

200249
#[inline]
201250
fn rem(self, rhs: &NonZero<BoxedUint>) -> Self::Output {
202-
self.rem_vartime(rhs)
251+
Self::rem(&self, rhs)
203252
}
204253
}
205254

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

209258
#[inline]
210259
fn rem(self, rhs: NonZero<BoxedUint>) -> Self::Output {
211-
self.rem_vartime(&rhs)
260+
self.rem(&rhs)
212261
}
213262
}
214263

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

218267
#[inline]
219268
fn rem(self, rhs: NonZero<BoxedUint>) -> Self::Output {
220-
self.rem_vartime(&rhs)
269+
self.rem(&rhs)
221270
}
222271
}
223272

224273
impl RemAssign<&NonZero<BoxedUint>> for BoxedUint {
225274
fn rem_assign(&mut self, rhs: &NonZero<BoxedUint>) {
226-
*self = self.rem_vartime(rhs)
275+
*self = Self::rem(self, rhs)
227276
}
228277
}
229278

230279
impl RemAssign<NonZero<BoxedUint>> for BoxedUint {
231280
fn rem_assign(&mut self, rhs: NonZero<BoxedUint>) {
232-
*self = self.rem_vartime(&rhs)
281+
*self = Self::rem(self, &rhs)
233282
}
234283
}
235284

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

289+
#[test]
290+
fn rem() {
291+
let n = BoxedUint::from(0xFFEECCBBAA99887766u128);
292+
let p = NonZero::new(BoxedUint::from(997u128)).unwrap();
293+
assert_eq!(BoxedUint::from(648u128), n.rem(&p));
294+
}
295+
240296
#[test]
241297
fn rem_vartime() {
242298
let n = BoxedUint::from(0xFFEECCBBAA99887766u128);

src/uint/boxed/shl.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,18 @@ impl BoxedUint {
7676

7777
(Self { limbs }, Limb(carry))
7878
}
79+
80+
/// Computes `self >> 1` in constant-time.
81+
pub(crate) fn shl1(&self) -> Self {
82+
// TODO(tarcieri): optimized implementation
83+
self.shl_vartime(1)
84+
}
85+
86+
/// Computes `self >> 1` in-place in constant-time.
87+
pub(crate) fn shl1_assign(&mut self) {
88+
// TODO(tarcieri): optimized implementation
89+
*self = self.shl1();
90+
}
7991
}
8092

8193
impl Shl<u32> for BoxedUint {

tests/boxed_uint_proptests.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,20 @@ proptest! {
9494
}
9595
}
9696

97+
#[test]
98+
fn checked_div((a, b) in uint_pair()) {
99+
let actual = a.checked_div(&b);
100+
101+
if b.is_zero().into() {
102+
prop_assert!(bool::from(actual.is_none()));
103+
} else {
104+
let a_bi = to_biguint(&a);
105+
let b_bi = to_biguint(&b);
106+
let expected = &a_bi / &b_bi;
107+
prop_assert_eq!(expected, to_biguint(&actual.unwrap()));
108+
}
109+
}
110+
97111
#[test]
98112
fn div_rem((a, mut b) in uint_pair()) {
99113
if b.is_zero().into() {
@@ -105,6 +119,22 @@ proptest! {
105119
let expected_quotient = &a_bi / &b_bi;
106120
let expected_remainder = a_bi % b_bi;
107121

122+
let (actual_quotient, actual_remainder) = a.div_rem(&NonZero::new(b).unwrap());
123+
prop_assert_eq!(expected_quotient, to_biguint(&actual_quotient));
124+
prop_assert_eq!(expected_remainder, to_biguint(&actual_remainder));
125+
}
126+
127+
#[test]
128+
fn div_rem_vartime((a, mut b) in uint_pair()) {
129+
if b.is_zero().into() {
130+
b = b.wrapping_add(&BoxedUint::one());
131+
}
132+
133+
let a_bi = to_biguint(&a);
134+
let b_bi = to_biguint(&b);
135+
let expected_quotient = &a_bi / &b_bi;
136+
let expected_remainder = a_bi % b_bi;
137+
108138
let (actual_quotient, actual_remainder) = a.div_rem_vartime(&NonZero::new(b).unwrap());
109139
prop_assert_eq!(expected_quotient, to_biguint(&actual_quotient));
110140
prop_assert_eq!(expected_remainder, to_biguint(&actual_remainder));
@@ -157,6 +187,19 @@ proptest! {
157187
prop_assert_eq!(expected, to_biguint(&actual));
158188
}
159189

190+
#[test]
191+
fn rem((a, b) in uint_pair()) {
192+
if bool::from(!b.is_zero()) {
193+
let a_bi = to_biguint(&a);
194+
let b_bi = to_biguint(&b);
195+
196+
let expected = a_bi % b_bi;
197+
let actual = a.rem(&NonZero::new(b).unwrap());
198+
199+
prop_assert_eq!(expected, to_biguint(&actual));
200+
}
201+
}
202+
160203
#[test]
161204
fn rem_vartime((a, b) in uint_pair()) {
162205
if bool::from(!b.is_zero()) {

0 commit comments

Comments
 (0)