Skip to content

Commit 0e32cdb

Browse files
committed
Improve BoxedUint::shl/shr performance by performing operations inplace when possible
1 parent baa2e83 commit 0e32cdb

File tree

4 files changed

+108
-44
lines changed

4 files changed

+108
-44
lines changed

src/uint/boxed.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,11 @@ impl BoxedUint {
253253

254254
limbs.into()
255255
}
256+
257+
/// Set the value of `self` to zero in-place.
258+
pub(crate) fn set_to_zero(&mut self) {
259+
self.limbs.as_mut().fill(Limb::ZERO)
260+
}
256261
}
257262

258263
impl NonZero<BoxedUint> {

src/uint/boxed/shl.rs

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@ impl BoxedUint {
1414
let overflow = !shift.ct_lt(&self.bits_precision());
1515
let shift = shift % self.bits_precision();
1616
let mut result = self.clone();
17+
let mut temp = self.clone();
1718

1819
for i in 0..shift_bits {
1920
let bit = Choice::from(((shift >> i) & 1) as u8);
20-
result = Self::conditional_select(
21-
&result,
22-
// Will not overflow by construction
23-
&result.shl_vartime(1 << i).expect("shift within range"),
24-
bit,
25-
);
21+
temp.set_to_zero();
22+
// Will not overflow by construction
23+
result
24+
.shl_vartime_into(&mut temp, 1 << i)
25+
.expect("shift within range");
26+
result.conditional_assign(&temp, bit);
2627
}
2728

2829
(
@@ -35,45 +36,55 @@ impl BoxedUint {
3536
)
3637
}
3738

38-
/// Computes `self << shift`.
39+
/// Computes `self << shift` and writes the result into `dest`.
3940
/// Returns `None` if `shift >= Self::BITS`.
4041
///
42+
/// WARNING: for performance reasons, `dest` is assumed to be pre-zeroized.
43+
///
4144
/// NOTE: this operation is variable time with respect to `shift` *ONLY*.
4245
///
4346
/// When used with a fixed `shift`, this function is constant-time with respect to `self`.
4447
#[inline(always)]
45-
pub fn shl_vartime(&self, shift: u32) -> Option<Self> {
48+
fn shl_vartime_into(&self, dest: &mut Self, shift: u32) -> Option<()> {
4649
if shift >= self.bits_precision() {
4750
return None;
4851
}
4952

5053
let nlimbs = self.nlimbs();
51-
let mut limbs = vec![Limb::ZERO; nlimbs].into_boxed_slice();
52-
5354
let shift_num = (shift / Limb::BITS) as usize;
5455
let rem = shift % Limb::BITS;
5556

56-
let mut i = nlimbs;
57-
while i > shift_num {
58-
i -= 1;
59-
limbs[i] = self.limbs[i - shift_num];
57+
for i in shift_num..nlimbs {
58+
dest.limbs[i] = self.limbs[i - shift_num];
6059
}
6160

6261
if rem == 0 {
63-
return Some(Self { limbs });
62+
return Some(());
6463
}
6564

6665
let mut carry = Limb::ZERO;
6766

68-
while i < nlimbs {
69-
let shifted = limbs[i].shl(rem);
70-
let new_carry = limbs[i].shr(Limb::BITS - rem);
71-
limbs[i] = shifted.bitor(carry);
67+
for i in shift_num..nlimbs {
68+
let shifted = dest.limbs[i].shl(rem);
69+
let new_carry = dest.limbs[i].shr(Limb::BITS - rem);
70+
dest.limbs[i] = shifted.bitor(carry);
7271
carry = new_carry;
73-
i += 1;
7472
}
7573

76-
Some(Self { limbs })
74+
Some(())
75+
}
76+
77+
/// Computes `self << shift`.
78+
/// Returns `None` if `shift >= Self::BITS`.
79+
///
80+
/// NOTE: this operation is variable time with respect to `shift` *ONLY*.
81+
///
82+
/// When used with a fixed `shift`, this function is constant-time with respect to `self`.
83+
#[inline(always)]
84+
pub fn shl_vartime(&self, shift: u32) -> Option<Self> {
85+
let mut result = Self::zero_with_precision(self.bits_precision());
86+
let success = self.shl_vartime_into(&mut result, shift);
87+
success.map(|_| result)
7788
}
7889

7990
/// Computes `self >> 1` in constant-time.
@@ -118,6 +129,18 @@ impl ShlAssign<u32> for BoxedUint {
118129
mod tests {
119130
use super::BoxedUint;
120131

132+
#[test]
133+
fn shl() {
134+
let one = BoxedUint::one_with_precision(128);
135+
136+
assert_eq!(BoxedUint::from(2u8), one.shl(1).0);
137+
assert_eq!(BoxedUint::from(4u8), one.shl(2).0);
138+
assert_eq!(
139+
BoxedUint::from(0x80000000000000000u128),
140+
one.shl_vartime(67).unwrap()
141+
);
142+
}
143+
121144
#[test]
122145
fn shl_vartime() {
123146
let one = BoxedUint::one_with_precision(128);

src/uint/boxed/shr.rs

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@ impl BoxedUint {
1414
let overflow = !shift.ct_lt(&self.bits_precision());
1515
let shift = shift % self.bits_precision();
1616
let mut result = self.clone();
17+
let mut temp = self.clone();
1718

1819
for i in 0..shift_bits {
1920
let bit = Choice::from(((shift >> i) & 1) as u8);
20-
result = Self::conditional_select(
21-
&result,
22-
// Will not overflow by construction
23-
&result.shr_vartime(1 << i).expect("shift within range"),
24-
bit,
25-
);
21+
temp.set_to_zero();
22+
// Will not overflow by construction
23+
result
24+
.shr_vartime_into(&mut temp, 1 << i)
25+
.expect("shift within range");
26+
result.conditional_assign(&temp, bit);
2627
}
2728

2829
(
@@ -38,42 +39,50 @@ impl BoxedUint {
3839
/// Computes `self >> shift`.
3940
/// Returns `None` if `shift >= Self::BITS`.
4041
///
42+
/// WARNING: for performance reasons, `dest` is assumed to be pre-zeroized.
43+
///
4144
/// NOTE: this operation is variable time with respect to `shift` *ONLY*.
4245
///
4346
/// When used with a fixed `shift`, this function is constant-time with respect to `self`.
4447
#[inline(always)]
45-
pub fn shr_vartime(&self, shift: u32) -> Option<Self> {
48+
fn shr_vartime_into(&self, dest: &mut Self, shift: u32) -> Option<()> {
4649
if shift >= self.bits_precision() {
4750
return None;
4851
}
4952

5053
let nlimbs = self.nlimbs();
51-
let mut limbs = vec![Limb::ZERO; nlimbs].into_boxed_slice();
52-
5354
let shift_num = (shift / Limb::BITS) as usize;
5455
let rem = shift % Limb::BITS;
5556

56-
let mut i = 0;
57-
while i < nlimbs - shift_num {
58-
limbs[i] = self.limbs[i + shift_num];
59-
i += 1;
57+
for i in 0..nlimbs - shift_num {
58+
dest.limbs[i] = self.limbs[i + shift_num];
6059
}
6160

6261
if rem == 0 {
63-
return Some(Self { limbs });
62+
return Some(());
6463
}
6564

66-
let mut carry = Limb::ZERO;
67-
68-
while i > 0 {
69-
i -= 1;
70-
let shifted = limbs[i].shr(rem);
71-
let new_carry = limbs[i].shl(Limb::BITS - rem);
72-
limbs[i] = shifted.bitor(carry);
73-
carry = new_carry;
65+
for i in 0..nlimbs - shift_num - 1 {
66+
let shifted = dest.limbs[i].shr(rem);
67+
let carry = dest.limbs[i + 1].shl(Limb::BITS - rem);
68+
dest.limbs[i] = shifted.bitor(carry);
7469
}
70+
dest.limbs[nlimbs - shift_num - 1] = dest.limbs[nlimbs - shift_num - 1].shr(rem);
7571

76-
Some(Self { limbs })
72+
Some(())
73+
}
74+
75+
/// Computes `self >> shift`.
76+
/// Returns `None` if `shift >= Self::BITS`.
77+
///
78+
/// NOTE: this operation is variable time with respect to `shift` *ONLY*.
79+
///
80+
/// When used with a fixed `shift`, this function is constant-time with respect to `self`.
81+
#[inline(always)]
82+
pub fn shr_vartime(&self, shift: u32) -> Option<Self> {
83+
let mut result = Self::zero_with_precision(self.bits_precision());
84+
let success = self.shr_vartime_into(&mut result, shift);
85+
success.map(|_| result)
7786
}
7887

7988
/// Computes `self >> 1` in constant-time, returning a true [`Choice`] if the overflowing bit
@@ -142,6 +151,15 @@ mod tests {
142151
assert_eq!(n, n_shr1);
143152
}
144153

154+
#[test]
155+
fn shr() {
156+
let n = BoxedUint::from(0x80000000000000000u128);
157+
assert_eq!(BoxedUint::zero(), n.shr(68).0);
158+
assert_eq!(BoxedUint::one(), n.shr(67).0);
159+
assert_eq!(BoxedUint::from(2u8), n.shr(66).0);
160+
assert_eq!(BoxedUint::from(4u8), n.shr(65).0);
161+
}
162+
145163
#[test]
146164
fn shr_vartime() {
147165
let n = BoxedUint::from(0x80000000000000000u128);

tests/boxed_uint_proptests.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use core::cmp::Ordering;
66
use crypto_bigint::{BoxedUint, CheckedAdd, Limb, NonZero};
77
use num_bigint::{BigUint, ModInverse};
8+
use num_traits::identities::One;
89
use proptest::prelude::*;
910

1011
fn to_biguint(uint: &BoxedUint) -> BigUint {
@@ -212,4 +213,21 @@ proptest! {
212213
prop_assert_eq!(expected, to_biguint(&actual));
213214
}
214215
}
216+
217+
#[test]
218+
fn boxed_shl(a in uint(), shift in any::<u16>()) {
219+
let a_bi = to_biguint(&a);
220+
221+
// Add a 50% probability of overflow.
222+
let shift = u32::from(shift) % (a.bits_precision() * 2);
223+
224+
let expected = to_uint((a_bi << shift as usize) & ((BigUint::one() << a.bits_precision() as usize) - BigUint::one()));
225+
let (actual, overflow) = a.shl(shift);
226+
227+
assert_eq!(expected, actual);
228+
if shift >= a.bits_precision() {
229+
assert_eq!(actual, BoxedUint::zero());
230+
assert!(bool::from(overflow));
231+
}
232+
}
215233
}

0 commit comments

Comments
 (0)