Skip to content

Commit 52fee04

Browse files
authored
Some simplifications of vartime division (#661)
Second attempt at #646
1 parent 325e16b commit 52fee04

File tree

3 files changed

+250
-201
lines changed

3 files changed

+250
-201
lines changed

src/uint/boxed/div.rs

Lines changed: 78 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
//! [`BoxedUint`] division operations.
22
33
use crate::{
4-
uint::{boxed, div_limb::div3by2},
4+
uint::{
5+
boxed,
6+
div_limb::{div2by1, div3by2},
7+
},
58
BoxedUint, CheckedDiv, ConstChoice, ConstantTimeSelect, DivRemLimb, Limb, NonZero, Reciprocal,
6-
RemLimb, Word, Wrapping,
9+
RemLimb, Wrapping,
710
};
811
use core::ops::{Div, DivAssign, Rem, RemAssign};
912
use subtle::CtOption;
@@ -166,7 +169,7 @@ impl BoxedUint {
166169

167170
while xi > 0 {
168171
// Divide high dividend words by the high divisor word to estimate the quotient word
169-
let (mut quo, _) = div3by2(x_hi.0, x_lo.0, x[xi - 1].0, &reciprocal, y[size - 2].0);
172+
let mut quo = div3by2(x_hi.0, x_lo.0, x[xi - 1].0, &reciprocal, y[size - 2].0);
170173

171174
// This loop is a no-op once xi is smaller than the number of words in the divisor
172175
let done = ConstChoice::from_u32_lt(xi as u32, dwords - 1);
@@ -206,14 +209,20 @@ impl BoxedUint {
206209
}
207210

208211
let limb_div = ConstChoice::from_u32_eq(1, dwords);
212+
209213
// Calculate quotient and remainder for the case where the divisor is a single word
210-
let (quo2, rem2) = div3by2(x_hi.0, x_lo.0, 0, &reciprocal, 0);
214+
// Note that `div2by1()` will panic if `x_hi >= reciprocal.divisor_normalized`,
215+
// but this can only be the case if `limb_div` is falsy,
216+
// in which case we discard the result anyway,
217+
// so we conditionally set `x_hi` to zero for this branch.
218+
let x_hi_adjusted = Limb::select(Limb::ZERO, x_hi, limb_div);
219+
let (quo2, rem2) = div2by1(x_hi_adjusted.0, x_lo.0, &reciprocal);
211220

212221
// Adjust the quotient for single limb division
213222
x[0] = Limb::select(x[0], Limb(quo2), limb_div);
214223

215224
// Copy out the remainder
216-
y[0] = Limb::select(x[0], Limb(rem2 as Word), limb_div);
225+
y[0] = Limb::select(x[0], Limb(rem2), limb_div);
217226
i = 1;
218227
while i < size {
219228
y[i] = Limb::select(Limb::ZERO, x[i], ConstChoice::from_u32_lt(i as u32, dwords));
@@ -382,6 +391,42 @@ impl RemLimb for BoxedUint {
382391
}
383392
}
384393

394+
/// Computes `limbs << shift` inplace, where `0 <= shift < Limb::BITS`, returning the carry.
395+
fn shl_limb_vartime(limbs: &mut [Limb], shift: u32) -> Limb {
396+
if shift == 0 {
397+
return Limb::ZERO;
398+
}
399+
400+
let lshift = shift;
401+
let rshift = Limb::BITS - shift;
402+
let limbs_num = limbs.len();
403+
404+
let carry = limbs[limbs_num - 1] >> rshift;
405+
for i in (1..limbs_num).rev() {
406+
limbs[i] = (limbs[i] << lshift) | (limbs[i - 1] >> rshift);
407+
}
408+
limbs[0] <<= lshift;
409+
410+
carry
411+
}
412+
413+
/// Computes `limbs >> shift` inplace, where `0 <= shift < Limb::BITS`.
414+
fn shr_limb_vartime(limbs: &mut [Limb], shift: u32) {
415+
if shift == 0 {
416+
return;
417+
}
418+
419+
let lshift = Limb::BITS - shift;
420+
let rshift = shift;
421+
422+
let limbs_num = limbs.len();
423+
424+
for i in 0..limbs_num - 1 {
425+
limbs[i] = (limbs[i] >> rshift) | (limbs[i + 1] << lshift);
426+
}
427+
limbs[limbs_num - 1] >>= rshift;
428+
}
429+
385430
/// Computes `x` / `y`, returning the quotient in `x` and the remainder in `y`.
386431
///
387432
/// This function operates in variable-time. It will panic if the divisor is zero
@@ -408,51 +453,44 @@ pub(crate) fn div_rem_vartime_in_place(x: &mut [Limb], y: &mut [Limb]) {
408453
}
409454

410455
let lshift = y[yc - 1].leading_zeros();
411-
let rshift = if lshift == 0 { 0 } else { Limb::BITS - lshift };
412-
let mut x_hi = Limb::ZERO;
413-
let mut carry;
414-
415-
if lshift != 0 {
416-
// Shift divisor such that it has no leading zeros
417-
// This means that div2by1 requires no extra shifts, and ensures that the high word >= b/2
418-
carry = Limb::ZERO;
419-
for i in 0..yc {
420-
(y[i], carry) = (Limb((y[i].0 << lshift) | carry.0), Limb(y[i].0 >> rshift));
421-
}
422456

423-
// Shift the dividend to match
424-
carry = Limb::ZERO;
425-
for i in 0..xc {
426-
(x[i], carry) = (Limb((x[i].0 << lshift) | carry.0), Limb(x[i].0 >> rshift));
427-
}
428-
x_hi = carry;
429-
}
457+
// Shift divisor such that it has no leading zeros
458+
// This means that div2by1 requires no extra shifts, and ensures that the high word >= b/2
459+
shl_limb_vartime(y, lshift);
460+
461+
// Shift the dividend to match
462+
let mut x_hi = shl_limb_vartime(x, lshift);
430463

431464
let reciprocal = Reciprocal::new(y[yc - 1].to_nz().expect("zero divisor"));
432465

433466
for xi in (yc - 1..xc).rev() {
434467
// Divide high dividend words by the high divisor word to estimate the quotient word
435-
let (mut quo, _) = div3by2(x_hi.0, x[xi].0, x[xi - 1].0, &reciprocal, y[yc - 2].0);
468+
let mut quo = div3by2(x_hi.0, x[xi].0, x[xi - 1].0, &reciprocal, y[yc - 2].0);
436469

437470
// Subtract q*divisor from the dividend
438-
carry = Limb::ZERO;
439-
let mut borrow = Limb::ZERO;
440-
let mut tmp;
441-
for i in 0..yc {
442-
(tmp, carry) = Limb::ZERO.mac(y[i], Limb(quo), carry);
443-
(x[xi + i + 1 - yc], borrow) = x[xi + i + 1 - yc].sbb(tmp, borrow);
444-
}
445-
(_, borrow) = x_hi.sbb(carry, borrow);
471+
let borrow = {
472+
let mut carry = Limb::ZERO;
473+
let mut borrow = Limb::ZERO;
474+
let mut tmp;
475+
for i in 0..yc {
476+
(tmp, carry) = Limb::ZERO.mac(y[i], Limb(quo), carry);
477+
(x[xi + i + 1 - yc], borrow) = x[xi + i + 1 - yc].sbb(tmp, borrow);
478+
}
479+
(_, borrow) = x_hi.sbb(carry, borrow);
480+
borrow
481+
};
446482

447483
// If the subtraction borrowed, then decrement q and add back the divisor
448484
// The probability of this being needed is very low, about 2/(Limb::MAX+1)
449-
let ct_borrow = ConstChoice::from_word_mask(borrow.0);
450-
carry = Limb::ZERO;
451-
for i in 0..yc {
452-
(x[xi + i + 1 - yc], carry) =
453-
x[xi + i + 1 - yc].adc(Limb::select(Limb::ZERO, y[i], ct_borrow), carry);
454-
}
455-
quo = ct_borrow.select_word(quo, quo.saturating_sub(1));
485+
quo = {
486+
let ct_borrow = ConstChoice::from_word_mask(borrow.0);
487+
let mut carry = Limb::ZERO;
488+
for i in 0..yc {
489+
(x[xi + i + 1 - yc], carry) =
490+
x[xi + i + 1 - yc].adc(Limb::select(Limb::ZERO, y[i], ct_borrow), carry);
491+
}
492+
ct_borrow.select_word(quo, quo.wrapping_sub(1))
493+
};
456494

457495
// Store the quotient within dividend and set x_hi to the current highest word
458496
x_hi = x[xi];
@@ -464,12 +502,7 @@ pub(crate) fn div_rem_vartime_in_place(x: &mut [Limb], y: &mut [Limb]) {
464502
y[yc - 1] = x_hi;
465503

466504
// Unshift the remainder from the earlier adjustment
467-
if lshift != 0 {
468-
carry = Limb::ZERO;
469-
for i in (0..yc).rev() {
470-
(y[i], carry) = (Limb((y[i].0 >> lshift) | carry.0), Limb(y[i].0 << rshift));
471-
}
472-
}
505+
shr_limb_vartime(y, lshift);
473506

474507
// Shift the quotient to the low limbs within dividend
475508
// let x_size = xc - yc + 1;

0 commit comments

Comments
 (0)