Skip to content

Commit

Permalink
[WIP] subtle: breaking changes for a hypothetical v3.0
Browse files Browse the repository at this point in the history
This commit contains a minimal set of proposed breaking changes, as an
alternative to trying to make additive changes which may be "subtly"
breaking:

- Add `ConstantTimeClone` trait and use it as
  `ConditionallySelectable`'s supertrait bound (alternative to dalek-cryptography#118):
  this makes it possible to implement `ConditionallySelectable` on
  heap-allocated types (fixes dalek-cryptography#94)
- Remove `Default` bound on `CtOption::map` and `CtOption::and_then`:
  allows using these methods with types that don't impl `Default` and
  avoids potential timing sidechannels if there might be timing
  variability in `Default` types (fixes dalek-cryptography#63)
  • Loading branch information
tarcieri committed Aug 3, 2024
1 parent f1f8e53 commit 18241c2
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 29 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ name = "subtle"
# - update html_root_url
# - update README if necessary by semver
# - if any updates were made to the README, also update the module documentation in src/lib.rs
version = "2.6.0"
version = "3.0.0-pre"
edition = "2018"
authors = ["Isis Lovecruft <isis@patternsinthevoid.net>",
"Henry de Valence <hdevalence@hdevalence.ca>"]
Expand Down
53 changes: 25 additions & 28 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ impl Choice {
///
/// This function only exists as an **escape hatch** for the rare case
/// where it's not possible to use one of the `subtle`-provided
/// where it's not possible to use one of the `subtle`-provided
/// trait impls.
///
/// **To convert a `Choice` to a `bool`, use the `From` implementation instead.**
Expand Down Expand Up @@ -383,14 +384,19 @@ impl ConstantTimeEq for cmp::Ordering {
}
}

/// Marker trait for types whose [`Clone`] impl operates in constant-time.
pub trait ConstantTimeClone: Clone {}

impl<T: Copy> ConstantTimeClone for T {}

/// A type which can be conditionally selected in constant time.
///
/// This trait also provides generic implementations of conditional
/// assignment and conditional swaps.
//
// #[inline] is specified on these function prototypes to signify that they
#[allow(unused_attributes)] // should be in the actual implementation
pub trait ConditionallySelectable: Copy {
pub trait ConditionallySelectable: ConstantTimeClone {
/// Select `a` or `b` according to `choice`.
///
/// # Returns
Expand Down Expand Up @@ -467,7 +473,7 @@ pub trait ConditionallySelectable: Copy {
/// ```
#[inline]
fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) {
let t: Self = *a;
let t: Self = a.clone();
a.conditional_assign(&b, choice);
b.conditional_assign(&t, choice);
}
Expand Down Expand Up @@ -575,7 +581,7 @@ impl ConditionallySelectable for Choice {
#[cfg(feature = "const-generics")]
impl<T, const N: usize> ConditionallySelectable for [T; N]
where
T: ConditionallySelectable,
T: ConditionallySelectable + Copy,
{
#[inline]
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
Expand Down Expand Up @@ -741,49 +747,34 @@ impl<T> CtOption<T> {

/// Returns a `None` value if the option is `None`, otherwise
/// returns a `CtOption` enclosing the value of the provided closure.
/// The closure is given the enclosed value or, if the option is
/// `None`, it is provided a dummy value computed using
/// `Default::default()`.
/// The closure is given the enclosed value.
///
/// This operates in constant time, because the provided closure
/// is always called.
#[inline]
pub fn map<U, F>(self, f: F) -> CtOption<U>
where
T: Default + ConditionallySelectable,
T: ConditionallySelectable,
F: FnOnce(T) -> U,
{
CtOption::new(
f(T::conditional_select(
&T::default(),
&self.value,
self.is_some,
)),
self.is_some,
)
CtOption::new(f(self.value), self.is_some)
}

/// Returns a `None` value if the option is `None`, otherwise
/// returns the result of the provided closure. The closure is
/// given the enclosed value or, if the option is `None`, it
/// is provided a dummy value computed using `Default::default()`.
/// given the enclosed value.
///
/// This operates in constant time, because the provided closure
/// is always called.
#[inline]
pub fn and_then<U, F>(self, f: F) -> CtOption<U>
where
T: Default + ConditionallySelectable,
T: ConditionallySelectable,
F: FnOnce(T) -> CtOption<U>,
{
let mut tmp = f(T::conditional_select(
&T::default(),
&self.value,
self.is_some,
));
tmp.is_some &= self.is_some;

tmp
let mut ret = f(self.value);
ret.is_some &= self.is_some;
ret
}

/// Returns `self` if it contains a value, and otherwise returns the result of
Expand All @@ -797,7 +788,10 @@ impl<T> CtOption<T> {
let is_none = self.is_none();
let f = f();

Self::conditional_select(&self, &f, is_none)
CtOption::new(
T::conditional_select(&self.value, &f.value, is_none),
Choice::conditional_select(&self.is_some, &f.is_some, is_none),
)
}

/// Convert the `CtOption<T>` wrapper into an `Option<T>`, depending on whether
Expand All @@ -817,7 +811,10 @@ impl<T> CtOption<T> {
}
}

impl<T: ConditionallySelectable> ConditionallySelectable for CtOption<T> {
impl<T> ConditionallySelectable for CtOption<T>
where
T: ConditionallySelectable + Copy
{
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
CtOption::new(
T::conditional_select(&a.value, &b.value, choice),
Expand Down

0 comments on commit 18241c2

Please sign in to comment.