Skip to content

Commit c18700e

Browse files
Fix vartime division edge case (#641)
Signed-off-by: Andrew Whitehead <[email protected]>
1 parent 7513449 commit c18700e

File tree

4 files changed

+106
-52
lines changed

4 files changed

+106
-52
lines changed

src/uint/boxed/div.rs

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
//! [`BoxedUint`] division operations.
22
33
use crate::{
4-
uint::{boxed, div_limb::div2by1},
4+
uint::{boxed, div_limb::div3by2},
55
BoxedUint, CheckedDiv, ConstChoice, ConstantTimeSelect, DivRemLimb, Limb, NonZero, Reciprocal,
6-
RemLimb, WideWord, Word, Wrapping,
6+
RemLimb, Wrapping,
77
};
88
use core::ops::{Div, DivAssign, Rem, RemAssign};
99
use subtle::{Choice, ConstantTimeLess, CtOption};
@@ -360,19 +360,7 @@ pub(crate) fn div_rem_vartime_in_place(x: &mut [Limb], y: &mut [Limb]) {
360360

361361
for xi in (yc - 1..xc).rev() {
362362
// Divide high dividend words by the high divisor word to estimate the quotient word
363-
let (mut quo, mut rem) = div2by1(x_hi.0, x[xi].0, &reciprocal);
364-
365-
for _ in 0..2 {
366-
let qy = (quo as WideWord) * (y[yc - 2].0 as WideWord);
367-
let rx = ((rem as WideWord) << Word::BITS) | (x[xi - 1].0 as WideWord);
368-
// Constant-time check for q*y[-2] < r*x[-1], based on ConstChoice::from_word_lt
369-
let diff = ConstChoice::from_word_lsb(
370-
((((!rx) & qy) | (((!rx) | qy) & (rx.wrapping_sub(qy)))) >> (WideWord::BITS - 1))
371-
as Word,
372-
);
373-
quo = diff.select_word(quo, quo.saturating_sub(1));
374-
rem = diff.select_word(rem, rem.saturating_add(y[yc - 1].0));
375-
}
363+
let (mut quo, _) = div3by2(x_hi.0, x[xi].0, x[xi - 1].0, &reciprocal, y[yc - 2].0);
376364

377365
// Subtract q*divisor from the dividend
378366
carry = Limb::ZERO;

src/uint/div.rs

Lines changed: 31 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
//! [`Uint`] division operations.
22
33
use super::div_limb::{
4-
div2by1, div_rem_limb_with_reciprocal, rem_limb_with_reciprocal, rem_limb_with_reciprocal_wide,
4+
div3by2, div_rem_limb_with_reciprocal, rem_limb_with_reciprocal, rem_limb_with_reciprocal_wide,
55
Reciprocal,
66
};
7-
use crate::{
8-
CheckedDiv, ConstChoice, DivRemLimb, Limb, NonZero, RemLimb, Uint, WideWord, Word, Wrapping,
9-
};
7+
use crate::{CheckedDiv, ConstChoice, DivRemLimb, Limb, NonZero, RemLimb, Uint, Word, Wrapping};
108
use core::ops::{Div, DivAssign, Rem, RemAssign};
119
use subtle::CtOption;
1210

@@ -135,21 +133,7 @@ impl<const LIMBS: usize> Uint<LIMBS> {
135133

136134
loop {
137135
// Divide high dividend words by the high divisor word to estimate the quotient word
138-
let (mut quo, mut rem) = div2by1(x_hi.0, x[xi].0, &reciprocal);
139-
140-
i = 0;
141-
while i < 2 {
142-
let qy = (quo as WideWord) * (y[yc - 2].0 as WideWord);
143-
let rx = ((rem as WideWord) << Word::BITS) | (x[xi - 1].0 as WideWord);
144-
// Constant-time check for q*y[-2] < r*x[-1], based on ConstChoice::from_word_lt
145-
let diff = ConstChoice::from_word_lsb(
146-
((((!rx) & qy) | (((!rx) | qy) & (rx.wrapping_sub(qy))))
147-
>> (WideWord::BITS - 1)) as Word,
148-
);
149-
quo = diff.select_word(quo, quo.saturating_sub(1));
150-
rem = diff.select_word(rem, rem.saturating_add(y[yc - 1].0));
151-
i += 1;
152-
}
136+
let (mut quo, _) = div3by2(x_hi.0, x[xi].0, x[xi - 1].0, &reciprocal, y[yc - 2].0);
153137

154138
// Subtract q*divisor from the dividend
155139
carry = Limb::ZERO;
@@ -283,21 +267,7 @@ impl<const LIMBS: usize> Uint<LIMBS> {
283267

284268
loop {
285269
// Divide high dividend words by the high divisor word to estimate the quotient word
286-
let (mut quo, mut rem) = div2by1(x_hi.0, x[xi].0, &reciprocal);
287-
288-
i = 0;
289-
while i < 2 {
290-
let qy = (quo as WideWord) * (y[yc - 2].0 as WideWord);
291-
let rx = ((rem as WideWord) << Word::BITS) | (x[xi - 1].0 as WideWord);
292-
// Constant-time check for q*y[-2] < r*x[-1], based on ConstChoice::from_word_lt
293-
let diff = ConstChoice::from_word_lsb(
294-
((((!rx) & qy) | (((!rx) | qy) & (rx.wrapping_sub(qy))))
295-
>> (WideWord::BITS - 1)) as Word,
296-
);
297-
quo = diff.select_word(quo, quo.saturating_sub(1));
298-
rem = diff.select_word(rem, rem.saturating_add(y[yc - 1].0));
299-
i += 1;
300-
}
270+
let (quo, _) = div3by2(x_hi.0, x[xi].0, x[xi - 1].0, &reciprocal, y[yc - 2].0);
301271

302272
// Subtract q*divisor from the dividend
303273
carry = Limb::ZERO;
@@ -357,6 +327,12 @@ impl<const LIMBS: usize> Uint<LIMBS> {
357327
(x[i], carry) = (Limb((x[i].0 >> lshift) | carry.0), Limb(x[i].0 << rshift));
358328
}
359329
}
330+
// Clear upper limbs
331+
i = LIMBS - 1;
332+
while i >= yc {
333+
x[i] = Limb::ZERO;
334+
i -= 1;
335+
}
360336

361337
Uint::new(x)
362338
}
@@ -864,7 +840,7 @@ impl<const LIMBS: usize> RemLimb for Uint<LIMBS> {
864840

865841
#[cfg(test)]
866842
mod tests {
867-
use crate::{Limb, NonZero, Uint, Word, U256};
843+
use crate::{Limb, NonZero, Uint, Word, U128, U256, U64};
868844

869845
#[cfg(feature = "rand")]
870846
use {
@@ -940,6 +916,26 @@ mod tests {
940916
assert_eq!(r, U256::ZERO);
941917
}
942918

919+
#[test]
920+
fn div_edge() {
921+
let lo = U128::from_be_hex("00000000000000000000000000000001");
922+
let hi = U128::from_be_hex("00000000000000000000000000000001");
923+
let y = U128::from_be_hex("00000000000000010000000000000001");
924+
let x = U256::from((lo, hi));
925+
let expect = (U64::MAX.resize::<{ U256::LIMBS }>(), U256::from(2u64));
926+
927+
let (q1, r1) = Uint::div_rem(&x, &NonZero::new(y.resize()).unwrap());
928+
assert_eq!((q1, r1), expect);
929+
let (q2, r2) = Uint::div_rem_vartime(&x, &NonZero::new(y).unwrap());
930+
assert_eq!((q2, r2.resize()), expect);
931+
let r3 = Uint::rem(&x, &NonZero::new(y.resize()).unwrap());
932+
assert_eq!(r3, expect.1);
933+
let r4 = Uint::rem_vartime(&x, &NonZero::new(y.resize()).unwrap());
934+
assert_eq!(r4, expect.1);
935+
let r5 = Uint::rem_wide_vartime((lo, hi), &NonZero::new(y).unwrap());
936+
assert_eq!(r5.resize(), expect.1);
937+
}
938+
943939
#[test]
944940
fn reduce_one() {
945941
let r = U256::from(10u8).rem_vartime(&NonZero::new(U256::ONE).unwrap());

src/uint/div_limb.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use subtle::{Choice, ConditionallySelectable};
55

66
use crate::{
77
primitives::{addhilo, mulhilo},
8-
ConstChoice, Limb, NonZero, Uint, Word,
8+
ConstChoice, Limb, NonZero, Uint, WideWord, Word,
99
};
1010

1111
/// Calculates the reciprocal of the given 32-bit divisor with the highmost bit set.
@@ -145,6 +145,45 @@ pub(crate) const fn div2by1(u1: Word, u0: Word, reciprocal: &Reciprocal) -> (Wor
145145
(q1, r)
146146
}
147147

148+
/// Calculate the quotient and the remainder of the division of a 3-word
149+
/// dividend `u`, with a precalculated leading-word reciprocal `v` and a second
150+
/// divisor word `v0`. The dividend and the lower divisor word must be left-shifted
151+
/// according to the `shift` value of the reciprocal.
152+
#[inline(always)]
153+
pub(crate) const fn div3by2(
154+
u2: Word,
155+
u1: Word,
156+
u0: Word,
157+
reciprocal: &Reciprocal,
158+
v0: Word,
159+
) -> (Word, Word) {
160+
// This method corresponds to Algorithm Q:
161+
// https://janmr.com/blog/2014/04/basic-multiple-precision-long-division/
162+
163+
let maxed = ConstChoice::from_word_eq(u2, reciprocal.divisor_normalized);
164+
let (mut quo, mut rem) = div2by1(maxed.select_word(u2, 0), u1, reciprocal);
165+
// When the leading dividend word equals the leading divisor word, cap the quotient
166+
// at Word::MAX and set the remainder to the sum of the top dividend words.
167+
quo = maxed.select_word(quo, Word::MAX);
168+
rem = maxed.select_word(rem, u2.saturating_add(u1));
169+
170+
let mut i = 0;
171+
while i < 2 {
172+
let qy = (quo as WideWord) * (v0 as WideWord);
173+
let rx = ((rem as WideWord) << Word::BITS) | (u0 as WideWord);
174+
// Constant-time check for q*y[-2] < r*x[-1], based on ConstChoice::from_word_lt
175+
let diff = ConstChoice::from_word_lsb(
176+
((((!rx) & qy) | (((!rx) | qy) & (rx.wrapping_sub(qy)))) >> (WideWord::BITS - 1))
177+
as Word,
178+
);
179+
quo = diff.select_word(quo, quo.saturating_sub(1));
180+
rem = diff.select_word(rem, rem.saturating_add(reciprocal.divisor_normalized));
181+
i += 1;
182+
}
183+
184+
(quo, rem)
185+
}
186+
148187
/// A pre-calculated reciprocal for division by a single limb.
149188
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
150189
pub struct Reciprocal {

tests/uint.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ mod common;
55
use common::to_biguint;
66
use crypto_bigint::{
77
modular::{MontyForm, MontyParams},
8-
Encoding, Integer, Limb, NonZero, Odd, Word, U256,
8+
Encoding, Integer, Limb, NonZero, Odd, Uint, Word, U256,
99
};
1010
use num_bigint::BigUint;
1111
use num_integer::Integer as _;
@@ -250,6 +250,37 @@ proptest! {
250250
}
251251
}
252252

253+
#[test]
254+
fn div_rem(a in uint(), b in uint()) {
255+
let a_bi = to_biguint(&a);
256+
let b_bi = to_biguint(&b);
257+
258+
if !b_bi.is_zero() {
259+
let (q, r) = a_bi.div_rem(&b_bi);
260+
let expected = (to_uint(q), to_uint(r));
261+
let b_nz = NonZero::new(b).unwrap();
262+
let actual = a.div_rem(&b_nz);
263+
assert_eq!(expected, actual);
264+
let actual_vartime = a.div_rem_vartime(&b_nz);
265+
assert_eq!(expected, actual_vartime);
266+
}
267+
}
268+
269+
270+
#[test]
271+
fn rem_wide(a in uint(), b in uint(), c in uint()) {
272+
let ab_bi = to_biguint(&a) * to_biguint(&b);
273+
let c_bi = to_biguint(&c);
274+
275+
if !c_bi.is_zero() {
276+
let expected = to_uint(ab_bi.div_rem(&c_bi).1);
277+
let (lo, hi) = a.split_mul(&b);
278+
let c_nz = NonZero::new(c).unwrap();
279+
let actual = Uint::rem_wide_vartime((lo, hi), &c_nz);
280+
assert_eq!(expected, actual);
281+
}
282+
}
283+
253284
#[test]
254285
fn div_rem_limb(a in uint(), b in nonzero_limb()) {
255286
let a_bi = to_biguint(&a);

0 commit comments

Comments
 (0)