Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

define and implement ConstantTime{Partial,}Ord traits #5

Closed
137 changes: 131 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// -*- mode: rust; -*-
//
// This file is part of subtle, part of the dalek cryptography project.
// Copyright (c) 2016-2018 isis lovecruft, Henry de Valence
// Copyright (c) 2016-2022 isis lovecruft, Henry de Valence
// See LICENSE for licensing information.
//
// Authors:
Expand All @@ -13,14 +13,14 @@
#![deny(missing_docs)]
#![doc(html_root_url = "https://docs.rs/subtle-ng/2.5.0")]


#[cfg(feature = "std")]
#[macro_use]
extern crate std;

#[cfg(test)]
extern crate rand;

use core::cmp::Ordering;
use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Neg, Not};
use core::option::Option;

Expand All @@ -45,6 +45,11 @@ use core::option::Option;
pub struct Choice(u8);

impl Choice {
/// Create an instance in `const` context.
pub const fn of_bool(of: bool) -> Self {
Self(of as u8)
}

/// Unwrap the `Choice` wrapper to reveal the underlying `u8`.
///
/// # Note
Expand Down Expand Up @@ -170,7 +175,7 @@ impl From<u8> for Choice {
}
}

/// An `Eq`-like trait that produces a `Choice` instead of a `bool`.
/// An [`Eq`]-like trait that produces a `Choice` instead of a `bool`.
///
/// # Example
///
Expand Down Expand Up @@ -728,7 +733,7 @@ macro_rules! generate_unsigned_integer_greater {
Choice::from((bit & 1) as u8)
}
}
}
};
}

generate_unsigned_integer_greater!(u8, 8);
Expand All @@ -740,7 +745,7 @@ generate_unsigned_integer_greater!(u128, 128);

/// A type which can be compared in some manner and be determined to be less
/// than another of the same type.
pub trait ConstantTimeLess: ConstantTimeEq + ConstantTimeGreater {
pub trait ConstantTimeLess: ConstantTimeGreater {
/// Determine whether `self < other`.
///
/// The bitwise-NOT of the return value of this function should be usable to
Expand Down Expand Up @@ -778,7 +783,7 @@ pub trait ConstantTimeLess: ConstantTimeEq + ConstantTimeGreater {
/// ```
#[inline]
fn ct_lt(&self, other: &Self) -> Choice {
!self.ct_gt(other) & !self.ct_eq(other)
other.ct_gt(self)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current default definition assumes a total ordering over Self, which could introduce sneaky logic errors for users! It also makes two calls to comparison functions instead of one. I believe this new default definition will now be correct for partially-ordered Self and will only ever need to be modified for cases where x < y does not imply y > x (which I believe is a strict subset of partially ordered sets).

}
}

Expand All @@ -788,3 +793,123 @@ impl ConstantTimeLess for u32 {}
impl ConstantTimeLess for u64 {}
#[cfg(feature = "i128")]
impl ConstantTimeLess for u128 {}

/// A [`PartialOrd`][core::cmp::PartialOrd]-like trait for constant-time comparisons.
///
/// This trait is automatically implemented for types supporting the "equals", "less", and
/// "greater" comparisons.
///
/// # Example
///
/// ```
/// use std::cmp::Ordering;
/// use subtle_ng::{ConstantTimePartialOrd, CtOption};
/// let x: u8 = 5;
/// let y: u8 = 13;
///
/// assert_eq!(x.ct_partial_cmp(&x).unwrap(), Ordering::Equal);
/// assert_eq!(x.ct_partial_cmp(&y).unwrap(), Ordering::Less);
/// assert_eq!(y.ct_partial_cmp(&x).unwrap(), Ordering::Greater);
/// ```
pub trait ConstantTimePartialOrd {
/// This method returns an ordering between `self` and `other`, if it exists.
///
/// This method should execute in constant time.
fn ct_partial_cmp(&self, other: &Self) -> CtOption<Ordering>;
}

/// Select among `N + 1` results given `N` logical values, of which at most one should be true.
///
/// This method requires a whole set of logical checks to be performed before evaluating their
/// result, and uses a lookup table to avoid branching in a `match` expression.
///
///```
/// use subtle_ng::index_mutually_exclusive_logical_results;
///
/// let r = [0xA, 0xB, 0xC];
///
/// let a = index_mutually_exclusive_logical_results(&r, [0.into(), 0.into()]);
/// assert_eq!(*a, 0xA);
/// let b = index_mutually_exclusive_logical_results(&r, [1.into(), 0.into()]);
/// assert_eq!(*b, 0xB);
/// let c = index_mutually_exclusive_logical_results(&r, [0.into(), 1.into()]);
/// assert_eq!(*c, 0xC);
///```
pub fn index_mutually_exclusive_logical_results<T, const N: usize>(
results: &[T],
logicals: [Choice; N],
) -> &T {
assert_eq!(results.len(), N + 1);
let combined_result: u8 = logicals.iter().enumerate().fold(0u8, |x, (i, choice)| {
x + ((i as u8) + 1) * choice.unwrap_u8()
});
results
.get(combined_result as usize)
.expect("multiple inconsistent mutually exclusive logical operations returned true")
}

impl<T: ConstantTimeGreater + ConstantTimeLess + ConstantTimeEq> ConstantTimePartialOrd for T {
/// We do not assume a total ordering for `T`, so we have to individually check "less than",
/// "equal", and "greater". This also respects non-default implementations of `ct_lt()`.
fn ct_partial_cmp(&self, other: &Self) -> CtOption<Ordering> {
let is_eq = self.ct_eq(other);
let is_lt = self.ct_lt(other);
let is_gt = self.ct_gt(other);

const PARTIAL_ORDERS: [CtOption<Ordering>; 4] = [
CtOption {
value: Ordering::Equal,
is_some: Choice::of_bool(false),
},
CtOption {
value: Ordering::Equal,
is_some: Choice::of_bool(true),
},
CtOption {
value: Ordering::Less,
is_some: Choice::of_bool(true),
},
CtOption {
value: Ordering::Greater,
is_some: Choice::of_bool(true),
},
];
*index_mutually_exclusive_logical_results(&PARTIAL_ORDERS, [is_eq, is_lt, is_gt])
}
}

/// An [`Ord`][core::cmp::Ord]-like trait for constant-time comparisons.
///
/// This trait is automatically implemented for types supporting the "equals" and
/// "greater" comparisons.
///
/// # Example
///
/// ```
/// use std::cmp::Ordering;
/// use subtle_ng::ConstantTimeOrd;
/// let x: u8 = 5;
/// let y: u8 = 13;
///
/// assert_eq!(x.ct_cmp(&x), Ordering::Equal);
/// assert_eq!(x.ct_cmp(&y), Ordering::Less);
/// assert_eq!(y.ct_cmp(&x), Ordering::Greater);
/// ```
pub trait ConstantTimeOrd {
/// This method returns an ordering between `self` and other`.
///
/// This method should execute in constant time.
fn ct_cmp(&self, other: &Self) -> Ordering;
}

impl<T: ConstantTimeEq + ConstantTimeGreater> ConstantTimeOrd for T {
/// We assume a total ordering for `T`, so we need to check only "equal" and "greater", and can
/// assume "less" if both `ct_eq()` and `ct_gt()` are false.
fn ct_cmp(&self, other: &Self) -> Ordering {
let is_gt = self.ct_gt(other);
let is_eq = self.ct_eq(other);

const ORDERS: [Ordering; 3] = [Ordering::Less, Ordering::Greater, Ordering::Equal];
*index_mutually_exclusive_logical_results(&ORDERS, [is_gt, is_eq])
}
}