Skip to content

Commit e7bcff7

Browse files
authored
Add ConstantTimeSelect trait (#454)
We can't impl `subtle::ConditionallySelectable` for `Boxed*` types due to a bound on `Copy`. I've proposed various ways to try to fix this upstream, e.g. dalek-cryptography/subtle#118, from which the `ConstantTimeSelect` trait has been extracted. It provides the same API as `ConditionallySelectable` but without the `Copy` bound. A blanket impl of `ConstantTimeSelect` for all `ConditionallySelectable` types means that all stack allocated types can continue to use `ConditionallySelectable` and will receive an impl of `ConstantTimeSelect` as well. A bound on `ConstantTimeSelect` has been added to the `Integer` trait as well, allowing it to be used on all `*Uint` types in this crate with `Integer` as the trait abstraction.
1 parent 3e571c3 commit e7bcff7

File tree

9 files changed

+81
-164
lines changed

9 files changed

+81
-164
lines changed

src/modular/boxed_residue.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use super::{
1212
reduction::{montgomery_reduction_boxed, montgomery_reduction_boxed_mut},
1313
Retrieve,
1414
};
15-
use crate::{BoxedUint, Integer, Limb, NonZero, Word};
15+
use crate::{BoxedUint, ConstantTimeSelect, Integer, Limb, NonZero, Word};
1616
use subtle::CtOption;
1717

1818
#[cfg(feature = "std")]
@@ -49,7 +49,7 @@ impl BoxedResidueParams {
4949

5050
// Use a surrogate value of `1` in case a modulus of `0` is passed.
5151
// This will be rejected by the `is_odd` check above, which will fail and return `None`.
52-
let modulus_nz = NonZero::new(BoxedUint::conditional_select(
52+
let modulus_nz = NonZero::new(BoxedUint::ct_select(
5353
&modulus,
5454
&BoxedUint::one_with_precision(bits_precision),
5555
modulus.is_zero(),
@@ -82,7 +82,7 @@ impl BoxedResidueParams {
8282

8383
// Use a surrogate value of `1` in case a modulus of `0` is passed.
8484
// This will be rejected by the `is_odd` check above, which will fail and return `None`.
85-
let modulus_nz = NonZero::new(BoxedUint::conditional_select(
85+
let modulus_nz = NonZero::new(BoxedUint::ct_select(
8686
&modulus,
8787
&BoxedUint::one_with_precision(bits_precision),
8888
modulus.is_zero(),

src/modular/boxed_residue/pow.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Modular exponentiation support for [`BoxedResidue`].
22
33
use super::{mul::MontgomeryMultiplier, BoxedResidue};
4-
use crate::{BoxedUint, Limb, PowBoundedExp, Word};
4+
use crate::{BoxedUint, ConstantTimeSelect, Limb, PowBoundedExp, Word};
55
use alloc::vec::Vec;
66
use subtle::{ConstantTimeEq, ConstantTimeLess};
77

@@ -103,7 +103,7 @@ fn pow_montgomery_form(
103103
// Constant-time lookup in the array of powers
104104
power.limbs.copy_from_slice(&powers[0].limbs);
105105
for i in 1..(1 << WINDOW) {
106-
power.conditional_assign(&powers[i as usize], i.ct_eq(&idx));
106+
power.ct_assign(&powers[i as usize], i.ct_eq(&idx));
107107
}
108108

109109
multiplier.mul_amm_assign(&mut z, &power);

src/traits.rs

+47-2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,51 @@ pub trait Bounded {
3131
const BYTES: usize;
3232
}
3333

34+
/// Trait for types which are conditionally selectable in constant time, similar to (and blanket impl'd for) `subtle`'s
35+
/// [`ConditionallySelectable`] trait, but without the `Copy` bound which allows it to be impl'd for heap allocated
36+
/// types such as `BoxedUint`.
37+
///
38+
/// It also provides generic implementations of conditional assignment and conditional swaps.
39+
pub trait ConstantTimeSelect: Clone {
40+
/// Select `a` or `b` according to `choice`.
41+
///
42+
/// # Returns
43+
/// - `a` if `choice == Choice(0)`;
44+
/// - `b` if `choice == Choice(1)`.
45+
fn ct_select(a: &Self, b: &Self, choice: Choice) -> Self;
46+
47+
/// Conditionally assign `other` to `self`, according to `choice`.
48+
#[inline]
49+
fn ct_assign(&mut self, other: &Self, choice: Choice) {
50+
*self = Self::ct_select(self, other, choice);
51+
}
52+
53+
/// Conditionally swap `self` and `other` if `choice == 1`; otherwise, reassign both unto themselves.
54+
#[inline]
55+
fn ct_swap(a: &mut Self, b: &mut Self, choice: Choice) {
56+
let t: Self = a.clone();
57+
a.ct_assign(b, choice);
58+
b.ct_assign(&t, choice);
59+
}
60+
}
61+
62+
impl<T: ConditionallySelectable> ConstantTimeSelect for T {
63+
#[inline(always)]
64+
fn ct_select(a: &Self, b: &Self, choice: Choice) -> Self {
65+
T::conditional_select(a, b, choice)
66+
}
67+
68+
#[inline(always)]
69+
fn ct_assign(&mut self, other: &Self, choice: Choice) {
70+
self.conditional_assign(other, choice)
71+
}
72+
73+
#[inline(always)]
74+
fn ct_swap(a: &mut Self, b: &mut Self, choice: Choice) {
75+
T::conditional_swap(a, b, choice)
76+
}
77+
}
78+
3479
/// Integer trait: represents common functionality of integer types provided by this crate.
3580
pub trait Integer:
3681
'static
@@ -55,10 +100,10 @@ pub trait Integer:
55100
+ CheckedMul
56101
+ CheckedDiv
57102
+ Clone
58-
// + ConditionallySelectable (see dalek-cryptography/subtle#94)
59103
+ ConstantTimeEq
60104
+ ConstantTimeGreater
61105
+ ConstantTimeLess
106+
+ ConstantTimeSelect
62107
+ Debug
63108
+ Default
64109
+ Div<NonZero<Self>, Output = Self>
@@ -113,7 +158,7 @@ pub trait Integer:
113158
fn bytes_precision(&self) -> usize;
114159

115160
/// Calculate the number of leading zeros in the binary representation of this number.
116-
fn leading_zeros(&self) -> u32 {
161+
fn leading_zeros(&self) -> u32 {
117162
self.bits_precision() - self.bits()
118163
}
119164

src/uint/boxed/ct.rs

+12-140
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
//! Constant-time helper functions.
22
33
use super::BoxedUint;
4-
use crate::Limb;
5-
use subtle::{Choice, ConditionallySelectable, CtOption};
4+
use crate::{ConstantTimeSelect, Limb};
5+
use subtle::{Choice, ConditionallySelectable};
66

7-
impl BoxedUint {
8-
/// Conditionally select `a` or `b` in constant time depending on [`Choice`].
9-
///
10-
/// NOTE: can't impl `subtle`'s [`ConditionallySelectable`] trait due to its `Copy` bound, so
11-
/// this is an inherent function instead.
12-
///
13-
/// Panics if `a` and `b` don't have the same precision.
14-
pub fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
7+
/// NOTE: can't impl `subtle`'s [`ConditionallySelectable`] trait due to its `Copy` bound
8+
impl ConstantTimeSelect for BoxedUint {
9+
#[inline]
10+
fn ct_select(a: &Self, b: &Self, choice: Choice) -> Self {
1511
debug_assert_eq!(a.bits_precision(), b.bits_precision());
1612
let mut limbs = vec![Limb::ZERO; a.nlimbs()].into_boxed_slice();
1713

@@ -22,161 +18,37 @@ impl BoxedUint {
2218
Self { limbs }
2319
}
2420

25-
/// Conditionally assign `other` to `self`, according to `choice`.
26-
///
27-
/// NOTE: can't impl `subtle`'s [`ConditionallySelectable`] trait due to its `Copy` bound, so
28-
/// this is an inherent function instead.
29-
///
30-
/// Panics if `a` and `b` don't have the same precision.
3121
#[inline]
32-
pub fn conditional_assign(&mut self, other: &Self, choice: Choice) {
22+
fn ct_assign(&mut self, other: &Self, choice: Choice) {
3323
debug_assert_eq!(self.bits_precision(), other.bits_precision());
3424

3525
for i in 0..self.nlimbs() {
3626
self.limbs[i].conditional_assign(&other.limbs[i], choice);
3727
}
3828
}
3929

40-
/// Conditionally swap `self` and `other` if `choice == 1`; otherwise,
41-
/// reassign both unto themselves.
42-
///
43-
/// NOTE: can't impl `subtle`'s [`ConditionallySelectable`] trait due to its `Copy` bound, so
44-
/// this is an inherent function instead.
45-
///
46-
/// Panics if `a` and `b` don't have the same precision.
4730
#[inline]
48-
pub fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) {
31+
fn ct_swap(a: &mut Self, b: &mut Self, choice: Choice) {
4932
debug_assert_eq!(a.bits_precision(), b.bits_precision());
5033

5134
for i in 0..a.nlimbs() {
5235
Limb::conditional_swap(&mut a.limbs[i], &mut b.limbs[i], choice);
5336
}
5437
}
55-
56-
/// Conditional `map`: workaround which provides a [`CtOption::map`]-like API.
57-
///
58-
/// Ensures both functions are called regardless of whether the first returns some/none with an
59-
/// argument whose precision matches `self`. Note this still requires branching on the
60-
/// intermediate [`CtOption`] value and therefore isn't fully constant time, but the best we can
61-
/// do without upstream changes to `subtle` (see dalek-cryptography/subtle#94).
62-
///
63-
/// Workaround due to `Copy` in [`ConditionallySelectable`] supertrait bounds.
64-
pub fn conditional_map<C, F, T>(&self, condition: C, f: F) -> CtOption<T>
65-
where
66-
C: Fn(&Self) -> CtOption<Self>,
67-
F: Fn(Self) -> T,
68-
{
69-
let conditional_val = condition(self);
70-
let is_some = conditional_val.is_some();
71-
72-
let placeholder = Self::zero_with_precision(self.bits_precision());
73-
let value = Option::<Self>::from(conditional_val).unwrap_or(placeholder);
74-
debug_assert_eq!(self.bits_precision(), value.bits_precision());
75-
CtOption::new(f(value), is_some)
76-
}
77-
78-
/// Conditional `and_then`: workaround which provides a [`CtOption::and_then`]-like API.
79-
///
80-
/// Ensures both functions are called regardless of whether the first returns some/none with an
81-
/// argument whose precision matches `self`. Note this still requires branching on the
82-
/// intermediate [`CtOption`] value and therefore isn't fully constant time, but the best we can
83-
/// do without upstream changes to `subtle` (see dalek-cryptography/subtle#94).
84-
///
85-
/// Workaround due to `Copy` in [`ConditionallySelectable`] supertrait bounds.
86-
pub fn conditional_and_then<C, F>(&self, condition: C, f: F) -> CtOption<Self>
87-
where
88-
C: Fn(&Self) -> CtOption<Self>,
89-
F: Fn(Self) -> CtOption<Self>,
90-
{
91-
let conditional_val = condition(self);
92-
let mut is_some = conditional_val.is_some();
93-
94-
let placeholder = Self::zero_with_precision(self.bits_precision());
95-
let value = Option::<Self>::from(conditional_val).unwrap_or(placeholder);
96-
debug_assert_eq!(self.bits_precision(), value.bits_precision());
97-
98-
let conditional_val = f(value);
99-
is_some &= conditional_val.is_some();
100-
101-
let placeholder = Self::zero_with_precision(self.bits_precision());
102-
let value = Option::from(conditional_val).unwrap_or(placeholder);
103-
debug_assert_eq!(self.bits_precision(), value.bits_precision());
104-
105-
CtOption::new(value, is_some)
106-
}
10738
}
10839

10940
#[cfg(test)]
11041
mod tests {
11142
use super::BoxedUint;
112-
use subtle::{Choice, CtOption};
43+
use crate::ConstantTimeSelect;
44+
use subtle::Choice;
11345

11446
#[test]
11547
fn conditional_select() {
11648
let a = BoxedUint::zero_with_precision(128);
11749
let b = BoxedUint::max(128);
11850

119-
assert_eq!(a, BoxedUint::conditional_select(&a, &b, Choice::from(0)));
120-
assert_eq!(b, BoxedUint::conditional_select(&a, &b, Choice::from(1)));
121-
}
122-
123-
#[test]
124-
fn conditional_map_some() {
125-
let n = BoxedUint::one();
126-
127-
let ret = n
128-
.conditional_map(
129-
|n| CtOption::new(n.clone(), 1u8.into()),
130-
|n| n.wrapping_add(&BoxedUint::one()),
131-
)
132-
.unwrap();
133-
134-
assert_eq!(ret, BoxedUint::from(2u8));
51+
assert_eq!(a, BoxedUint::ct_select(&a, &b, Choice::from(0)));
52+
assert_eq!(b, BoxedUint::ct_select(&a, &b, Choice::from(1)));
13553
}
136-
137-
#[test]
138-
fn conditional_map_none() {
139-
let n = BoxedUint::one();
140-
141-
let ret = n.conditional_map(
142-
|n| CtOption::new(n.clone(), 0u8.into()),
143-
|n| n.wrapping_add(&BoxedUint::one()),
144-
);
145-
146-
assert!(bool::from(ret.is_none()));
147-
}
148-
149-
#[test]
150-
fn conditional_and_then_all_some() {
151-
let n = BoxedUint::one();
152-
153-
let ret = n
154-
.conditional_and_then(
155-
|n| CtOption::new(n.clone(), 1u8.into()),
156-
|n| CtOption::new(n.wrapping_add(&BoxedUint::one()), 1u8.into()),
157-
)
158-
.unwrap();
159-
160-
assert_eq!(ret, BoxedUint::from(2u8));
161-
}
162-
163-
macro_rules! conditional_and_then_none_test {
164-
($name:ident, $a:expr, $b:expr) => {
165-
#[test]
166-
fn $name() {
167-
let n = BoxedUint::one();
168-
169-
let ret = n.conditional_and_then(
170-
|n| CtOption::new(n.clone(), $a.into()),
171-
|n| CtOption::new(n.wrapping_add(&BoxedUint::one()), $b.into()),
172-
);
173-
174-
assert!(bool::from(ret.is_none()));
175-
}
176-
};
177-
}
178-
179-
conditional_and_then_none_test!(conditional_and_then_none_some, 0, 1);
180-
conditional_and_then_none_test!(conditional_and_then_some_none, 1, 0);
181-
conditional_and_then_none_test!(conditional_and_then_none_none, 0, 0);
18254
}

src/uint/boxed/div.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! [`BoxedUint`] division operations.
22
3-
use crate::{BoxedUint, CheckedDiv, Limb, NonZero, Wrapping};
3+
use crate::{BoxedUint, CheckedDiv, ConstantTimeSelect, Limb, NonZero, Wrapping};
44
use core::ops::{Div, DivAssign, Rem, RemAssign};
55
use subtle::{Choice, ConstantTimeEq, ConstantTimeLess, CtOption};
66

@@ -42,7 +42,7 @@ impl BoxedUint {
4242

4343
loop {
4444
let (r, borrow) = rem.sbb(&c, Limb::ZERO);
45-
rem = Self::conditional_select(&r, &rem, !borrow.ct_eq(&Limb::ZERO));
45+
rem = Self::ct_select(&r, &rem, !borrow.ct_eq(&Limb::ZERO));
4646
if bd == 0 {
4747
break rem;
4848
}
@@ -84,9 +84,9 @@ impl BoxedUint {
8484

8585
loop {
8686
let (mut r, borrow) = rem.sbb(&c, Limb::ZERO);
87-
rem.conditional_assign(&r, !(Choice::from((borrow.0 & 1) as u8) | done));
87+
rem.ct_assign(&r, !(Choice::from((borrow.0 & 1) as u8) | done));
8888
r = quo.bitor(&Self::one());
89-
quo.conditional_assign(&r, !(Choice::from((borrow.0 & 1) as u8) | done));
89+
quo.ct_assign(&r, !(Choice::from((borrow.0 & 1) as u8) | done));
9090
if i == 0 {
9191
break;
9292
}
@@ -95,7 +95,7 @@ impl BoxedUint {
9595
// aren't modified further (but do the remaining iterations anyway to be constant-time)
9696
done = i.ct_lt(&mb);
9797
c.shr1_assign();
98-
quo.conditional_assign(&quo.shl1(), !done);
98+
quo.ct_assign(&quo.shl1(), !done);
9999
}
100100

101101
(quo, rem)
@@ -117,9 +117,9 @@ impl BoxedUint {
117117
loop {
118118
let (mut r, borrow) = remainder.sbb(&c, Limb::ZERO);
119119
let borrow = Choice::from(borrow.0 as u8 & 1);
120-
remainder = Self::conditional_select(&r, &remainder, borrow);
120+
remainder = Self::ct_select(&r, &remainder, borrow);
121121
r = &quotient | Self::one();
122-
quotient = Self::conditional_select(&r, &quotient, borrow);
122+
quotient = Self::ct_select(&r, &quotient, borrow);
123123
if bd == 0 {
124124
break;
125125
}

src/uint/boxed/inv_mod.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! [`BoxedUint`] modular inverse (i.e. reciprocal) operations.
22
3-
use crate::{BoxedUint, Integer};
3+
use crate::{BoxedUint, ConstantTimeSelect, Integer};
44
use subtle::{Choice, ConstantTimeEq, ConstantTimeLess, CtOption};
55

66
impl BoxedUint {
@@ -56,7 +56,7 @@ impl BoxedUint {
5656
let x_i = b.limbs[0].0 & 1;
5757
let x_i_choice = Choice::from(x_i as u8);
5858
// b_{i+1} = (b_i - a * X_i) / 2
59-
b = Self::conditional_select(&b, &b.wrapping_sub(self), x_i_choice).shr1();
59+
b = Self::ct_select(&b, &b.wrapping_sub(self), x_i_choice).shr1();
6060

6161
// Store the X_i bit in the result (x = x | (1 << X_i))
6262
// Don't change the result in dummy iterations.
@@ -115,13 +115,13 @@ impl BoxedUint {
115115
// Set `self -= b` if `self` is odd.
116116
let swap = a.conditional_sbb_assign(&b, self_odd);
117117
// Set `b += self` if `swap` is true.
118-
b = Self::conditional_select(&b, &b.wrapping_add(&a), swap);
118+
b = Self::ct_select(&b, &b.wrapping_add(&a), swap);
119119
// Negate `self` if `swap` is true.
120120
a = a.conditional_wrapping_neg(swap);
121121

122122
let mut new_u = u.clone();
123123
let mut new_v = v.clone();
124-
Self::conditional_swap(&mut new_u, &mut new_v, swap);
124+
Self::ct_swap(&mut new_u, &mut new_v, swap);
125125
let cy = new_u.conditional_sbb_assign(&new_v, self_odd);
126126
let cyy = new_u.conditional_adc_assign(modulus, cy);
127127
debug_assert!(bool::from(cy.ct_eq(&cyy)));

0 commit comments

Comments
 (0)