Skip to content

Commit 1128ede

Browse files
Fix wrapping shifts and add unbounded shifts (#1160)
This PR makes a couple breaking changes: wrapping shift methods are updated to behave accordingly with `core`, so the shift value itself is wrapped at the capacity of the integer instead of the integer becoming zero when the shift exceeds the capacity. The new `unbounded_shl`/`unbounded_shr` methods provide support for the old behavior. The `ShlVartime` and `ShrVartime` traits also get a new method to support unbounded shifts. The performance of constant-time `shl`/`shr` methods is improved by only performing one sub-limb shift instead of multiple. Fixes #1151 --------- Signed-off-by: Andrew Whitehead <cywolf@gmail.com>
1 parent e8b3a70 commit 1128ede

28 files changed

Lines changed: 1513 additions & 877 deletions

benches/uint.rs

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,26 @@ fn bench_mod_symbols(c: &mut Criterion) {
740740
fn bench_shl(c: &mut Criterion) {
741741
let mut group = c.benchmark_group("left shift");
742742

743+
group.bench_function("shl, U256", |b| {
744+
b.iter_batched(|| U256::ONE, |x| x.shl(30), BatchSize::SmallInput);
745+
});
746+
747+
group.bench_function("unbounded_shl, U256", |b| {
748+
b.iter_batched(|| U256::ONE, |x| x.unbounded_shl(30), BatchSize::SmallInput);
749+
});
750+
751+
group.bench_function("shl, U2048", |b| {
752+
b.iter_batched(|| U2048::ONE, |x| x.shl(1024 + 10), BatchSize::SmallInput);
753+
});
754+
755+
group.bench_function("unbounded_shl, U2048", |b| {
756+
b.iter_batched(
757+
|| U2048::ONE,
758+
|x| x.unbounded_shl(1024 + 10),
759+
BatchSize::SmallInput,
760+
);
761+
});
762+
743763
group.bench_function("shl_vartime, small, U2048", |b| {
744764
b.iter_batched(|| U2048::ONE, |x| x.shl_vartime(10), BatchSize::SmallInput);
745765
});
@@ -760,16 +780,32 @@ fn bench_shl(c: &mut Criterion) {
760780
);
761781
});
762782

763-
group.bench_function("shl, U2048", |b| {
764-
b.iter_batched(|| U2048::ONE, |x| x.shl(1024 + 10), BatchSize::SmallInput);
765-
});
766-
767783
group.finish();
768784
}
769785

770786
fn bench_shr(c: &mut Criterion) {
771787
let mut group = c.benchmark_group("right shift");
772788

789+
group.bench_function("shr, U256", |b| {
790+
b.iter_batched(|| U256::ONE, |x| x.shr(30), BatchSize::SmallInput);
791+
});
792+
793+
group.bench_function("unbounded_shr, U256", |b| {
794+
b.iter_batched(|| U256::ONE, |x| x.unbounded_shr(30), BatchSize::SmallInput);
795+
});
796+
797+
group.bench_function("shr, U2048", |b| {
798+
b.iter_batched(|| U2048::ONE, |x| x.shr(1024 + 10), BatchSize::SmallInput);
799+
});
800+
801+
group.bench_function("unbounded_shr, U2048", |b| {
802+
b.iter_batched(
803+
|| U2048::ONE,
804+
|x| x.unbounded_shr(1024 + 10),
805+
BatchSize::SmallInput,
806+
);
807+
});
808+
773809
group.bench_function("shr_vartime, small, U2048", |b| {
774810
b.iter_batched(|| U2048::ONE, |x| x.shr_vartime(10), BatchSize::SmallInput);
775811
});
@@ -790,10 +826,6 @@ fn bench_shr(c: &mut Criterion) {
790826
);
791827
});
792828

793-
group.bench_function("shr, U2048", |b| {
794-
b.iter_batched(|| U2048::ONE, |x| x.shr(1024 + 10), BatchSize::SmallInput);
795-
});
796-
797829
group.finish();
798830
}
799831

src/int/shl.rs

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,30 @@ impl<const LIMBS: usize> Int<LIMBS> {
99
/// # Panics
1010
/// - if `shift >= Self::BITS`.
1111
#[must_use]
12+
#[track_caller]
1213
pub const fn shl(&self, shift: u32) -> Self {
1314
Self(Uint::shl(&self.0, shift))
1415
}
1516

1617
/// Computes `self << shift` in variable time.
1718
///
19+
/// NOTE: this operation is variable time with respect to `shift` *ONLY*.
20+
///
21+
/// When used with a fixed `shift`, this function is constant-time with respect to `self`.
22+
///
1823
/// # Panics
1924
/// - if `shift >= Self::BITS`.
25+
#[inline(always)]
2026
#[must_use]
27+
#[track_caller]
2128
pub const fn shl_vartime(&self, shift: u32) -> Self {
2229
Self(Uint::shl_vartime(&self.0, shift))
2330
}
2431

2532
/// Computes `self << shift`.
2633
///
2734
/// Returns `None` if `shift >= Self::BITS`.
35+
#[inline(always)]
2836
#[must_use]
2937
pub const fn overflowing_shl(&self, shift: u32) -> CtOption<Self> {
3038
Self::from_uint_opt(self.0.overflowing_shl(shift))
@@ -50,13 +58,38 @@ impl<const LIMBS: usize> Int<LIMBS> {
5058

5159
/// Computes `self << shift` in a panic-free manner, returning zero if the shift exceeds the
5260
/// precision.
61+
#[inline(always)]
5362
#[must_use]
54-
pub const fn wrapping_shl(&self, shift: u32) -> Self {
55-
Self(self.0.wrapping_shl(shift))
63+
pub const fn unbounded_shl(&self, shift: u32) -> Self {
64+
Self(self.0.unbounded_shl(shift))
5665
}
5766

5867
/// Computes `self << shift` in variable-time in a panic-free manner, returning zero if the
5968
/// shift exceeds the precision.
69+
///
70+
/// NOTE: this operation is variable time with respect to `shift` *ONLY*.
71+
///
72+
/// When used with a fixed `shift`, this function is constant-time with respect to `self`.
73+
#[inline(always)]
74+
#[must_use]
75+
pub const fn unbounded_shl_vartime(&self, shift: u32) -> Self {
76+
Self(self.0.unbounded_shl_vartime(shift))
77+
}
78+
79+
/// Computes `self << shift` in a panic-free manner, reducing shift modulo the type's width.
80+
#[inline(always)]
81+
#[must_use]
82+
pub const fn wrapping_shl(&self, shift: u32) -> Self {
83+
Self(self.0.wrapping_shl(shift))
84+
}
85+
86+
/// Computes `self << shift` in variable-time in a panic-free manner, reducing shift modulo
87+
/// the type's width.
88+
///
89+
/// NOTE: this operation is variable time with respect to `shift` *ONLY*.
90+
///
91+
/// When used with a fixed `shift`, this function is constant-time with respect to `self`.
92+
#[inline(always)]
6093
#[must_use]
6194
pub const fn wrapping_shl_vartime(&self, shift: u32) -> Self {
6295
Self(self.0.wrapping_shl_vartime(shift))
@@ -106,6 +139,10 @@ impl<const LIMBS: usize> ShlVartime for Int<LIMBS> {
106139
self.overflowing_shl_vartime(shift)
107140
}
108141

142+
fn unbounded_shl_vartime(&self, shift: u32) -> Self {
143+
self.unbounded_shl_vartime(shift)
144+
}
145+
109146
fn wrapping_shl_vartime(&self, shift: u32) -> Self {
110147
self.wrapping_shl_vartime(shift)
111148
}
@@ -170,8 +207,8 @@ mod tests {
170207
}
171208

172209
#[test]
173-
#[should_panic(expected = "`shift` within the bit size of the integer")]
174-
fn shl256() {
210+
#[should_panic(expected = "`shift` exceeds upper bound")]
211+
fn shl_bounds_panic() {
175212
let _ = N << 256;
176213
}
177214

@@ -180,17 +217,31 @@ mod tests {
180217
assert_eq!(N << 64, SIXTY_FOUR);
181218
}
182219

220+
#[test]
221+
fn unbounded_shl() {
222+
assert_eq!(I256::MAX.unbounded_shl(257), I256::ZERO);
223+
assert_eq!(I256::MIN.unbounded_shl(257), I256::ZERO);
224+
assert_eq!(
225+
ShlVartime::unbounded_shl_vartime(&I256::MAX, 257),
226+
I256::ZERO
227+
);
228+
assert_eq!(
229+
ShlVartime::unbounded_shl_vartime(&I256::MIN, 257),
230+
I256::ZERO
231+
);
232+
}
233+
183234
#[test]
184235
fn wrapping_shl() {
185-
assert_eq!(I256::MAX.wrapping_shl(257), I256::ZERO);
186-
assert_eq!(I256::MIN.wrapping_shl(257), I256::ZERO);
236+
assert_eq!(I256::MAX.wrapping_shl(257), I256::MAX.shl(1));
237+
assert_eq!(I256::MIN.wrapping_shl(257), I256::MIN.shl(1));
187238
assert_eq!(
188239
ShlVartime::wrapping_shl_vartime(&I256::MAX, 257),
189-
I256::ZERO
240+
I256::MAX.shl(1)
190241
);
191242
assert_eq!(
192243
ShlVartime::wrapping_shl_vartime(&I256::MIN, 257),
193-
I256::ZERO
244+
I256::MIN.shl(1)
194245
);
195246
}
196247
}

0 commit comments

Comments
 (0)