Skip to content

Commit cd90fbe

Browse files
committed
BoxedUint: add cond_map and cond_and_then
Unfortunately `BoxedUint` can't impl `subtle::ConditionallySelectable` due to a supertrait bound on `Copy`. See dalek-cryptography/subtle#94 This bound is required by `CtOption::map` and `CtOption::and_then` which are important for writing constant-time code. As a workaround which makes it still possible to leverate `CtOption`, this adds special `BoxedUint`-specialized combinators that are able to work around this issue by generating a placeholder (zero) value to pass to the provided callbacks in the event `CtOption` is none. This requires branching on the output of `CtOption` (which is unavoidable without an upstream fix in `subtle` itself), but still ensures that the provided callback function is called with a `BoxedUint` of a matching number of limbs regardless of whether the `CtOption` is some or none, which is the best we can do for now (and really quite close to what `subtle` is doing under the hood anyway).
1 parent 68777f5 commit cd90fbe

File tree

2 files changed

+121
-0
lines changed

2 files changed

+121
-0
lines changed

src/uint/boxed.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mod bit_and;
66
mod bit_or;
77
mod bits;
88
mod cmp;
9+
mod ct;
910
mod div;
1011
pub(crate) mod encoding;
1112
mod inv_mod;

src/uint/boxed/ct.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//! Constant-time helper functions.
2+
//!
3+
//! These largely exist as a workaround for the `Copy` bound on [`subtle::ConditionallySelectable`].
4+
5+
use super::BoxedUint;
6+
use subtle::CtOption;
7+
8+
impl BoxedUint {
9+
/// Conditional `map`: workaround which provides a [`CtOption::map`]-like API.
10+
///
11+
/// Ensures both functions are called regardless of whether the first returns some/none with an
12+
/// argument whose precision matches `self`.
13+
///
14+
/// Workaround due to `Copy` in [`subtle::ConditionallySelectable`] supertrait bounds.
15+
pub fn cond_map<C, F, T>(&self, condition: C, f: F) -> CtOption<T>
16+
where
17+
C: Fn(&Self) -> CtOption<Self>,
18+
F: Fn(Self) -> T,
19+
{
20+
let placeholder = Self::zero_with_precision(self.bits_precision());
21+
let cond_val = condition(self);
22+
let is_some = cond_val.is_some();
23+
24+
let value = Option::<Self>::from(cond_val).unwrap_or(placeholder);
25+
debug_assert_eq!(self.bits_precision(), value.bits_precision());
26+
CtOption::new(f(value), is_some)
27+
}
28+
29+
/// Conditional `and_then`: workaround which provides a [`CtOption::and_then`]-like API.
30+
///
31+
/// Ensures both functions are called regardless of whether they return some/none with an
32+
/// argument whose precision matches `self`.
33+
///
34+
/// Workaround due to `Copy` in [`subtle::ConditionallySelectable`] supertrait bounds.
35+
pub fn cond_and_then<C, F>(&self, condition: C, f: F) -> CtOption<Self>
36+
where
37+
C: Fn(&Self) -> CtOption<Self>,
38+
F: Fn(Self) -> CtOption<Self>,
39+
{
40+
let cond_val = condition(self);
41+
let mut is_some = cond_val.is_some();
42+
43+
let placeholder = Self::zero_with_precision(self.bits_precision());
44+
let value = Option::<Self>::from(cond_val).unwrap_or(placeholder);
45+
debug_assert_eq!(self.bits_precision(), value.bits_precision());
46+
47+
let cond_val = f(value);
48+
is_some &= cond_val.is_some();
49+
50+
let placeholder = Self::zero_with_precision(self.bits_precision());
51+
let value = Option::from(cond_val).unwrap_or(placeholder);
52+
CtOption::new(value, is_some)
53+
}
54+
}
55+
56+
#[cfg(test)]
57+
mod tests {
58+
use super::BoxedUint;
59+
use subtle::CtOption;
60+
61+
#[test]
62+
fn cond_map_some() {
63+
let n = BoxedUint::one();
64+
65+
let ret = n
66+
.cond_map(
67+
|n| CtOption::new(n.clone(), 1u8.into()),
68+
|n| n.wrapping_add(&BoxedUint::one()),
69+
)
70+
.unwrap();
71+
72+
assert_eq!(ret, BoxedUint::from(2u8));
73+
}
74+
75+
#[test]
76+
fn cond_map_none() {
77+
let n = BoxedUint::one();
78+
79+
let ret = n.cond_map(
80+
|n| CtOption::new(n.clone(), 0u8.into()),
81+
|n| n.wrapping_add(&BoxedUint::one()),
82+
);
83+
84+
assert!(bool::from(ret.is_none()));
85+
}
86+
87+
#[test]
88+
fn cond_and_then_all_some() {
89+
let n = BoxedUint::one();
90+
91+
let ret = n
92+
.cond_and_then(
93+
|n| CtOption::new(n.clone(), 1u8.into()),
94+
|n| CtOption::new(n.wrapping_add(&BoxedUint::one()), 1u8.into()),
95+
)
96+
.unwrap();
97+
98+
assert_eq!(ret, BoxedUint::from(2u8));
99+
}
100+
101+
macro_rules! cond_and_then_none_test {
102+
($name:ident, $a:expr, $b:expr) => {
103+
#[test]
104+
fn $name() {
105+
let n = BoxedUint::one();
106+
107+
let ret = n.cond_and_then(
108+
|n| CtOption::new(n.clone(), $a.into()),
109+
|n| CtOption::new(n.wrapping_add(&BoxedUint::one()), $b.into()),
110+
);
111+
112+
assert!(bool::from(ret.is_none()));
113+
}
114+
};
115+
}
116+
117+
cond_and_then_none_test!(cond_and_then_none_some, 0, 1);
118+
cond_and_then_none_test!(cond_and_then_some_none, 1, 0);
119+
cond_and_then_none_test!(cond_and_then_none_none, 0, 0);
120+
}

0 commit comments

Comments
 (0)