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 #98

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 137 additions & 13 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 Down Expand Up @@ -87,6 +87,10 @@
#[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 @@ -111,6 +115,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 @@ -236,7 +245,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 All @@ -257,7 +266,6 @@ pub trait ConstantTimeEq {
///
/// * `Choice(1u8)` if `self == other`;
/// * `Choice(0u8)` if `self != other`.
#[inline]
fn ct_eq(&self, other: &Self) -> Choice;
}

Expand Down Expand Up @@ -380,7 +388,6 @@ pub trait ConditionallySelectable: Copy {
/// assert_eq!(z, y);
/// # }
/// ```
#[inline]
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self;

/// Conditionally assign `other` to `self`, according to `choice`.
Expand Down Expand Up @@ -434,7 +441,7 @@ pub trait ConditionallySelectable: Copy {
#[inline]
fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) {
let t: Self = *a;
a.conditional_assign(&b, choice);
a.conditional_assign(b, choice);
b.conditional_assign(&t, choice);
}
}
Expand Down Expand Up @@ -530,7 +537,6 @@ pub trait ConditionallyNegatable {
/// unchanged.
///
/// This function should execute in constant time.
#[inline]
fn conditional_negate(&mut self, choice: Choice);
}

Expand Down Expand Up @@ -599,10 +605,7 @@ impl<T> CtOption<T> {
/// exposed.
#[inline]
pub fn new(value: T, is_some: Choice) -> CtOption<T> {
CtOption {
value: value,
is_some: is_some,
}
CtOption { value, is_some }
}

/// This returns the underlying value but panics if it
Expand Down Expand Up @@ -801,7 +804,7 @@ macro_rules! generate_unsigned_integer_greater {
Choice::from((bit & 1) as u8)
}
}
}
};
}

generate_unsigned_integer_greater!(u8, 8);
Expand All @@ -813,7 +816,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 @@ -852,7 +855,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

@cosmicexplorer cosmicexplorer Jan 4, 2023

Choose a reason for hiding this comment

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

This line here is the motivation for this change. The prior default implementation assumes that Self is totally ordered. We have moved this logic to ConstantTimeOrd's default implementation of ct_cmp(), requiring types to explicitly impl ConstantTimeOrd in order to use this "not equal or greater" check, while ConstantTimePartialOrd has to check all three of equals, less than, and greater.

}
}

Expand All @@ -862,3 +865,124 @@ 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::{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>;
}

impl ConditionallySelectable for Ordering {
/// Delegate to [`i8::conditional_select()`], since [`Ordering`] is `#[repr(i8)]`.
#[inline]
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
let delegated: i8 = i8::conditional_select(&(*a as i8), &(*b as i8), choice);
// TODO: is there a canonical way to construct a repr(i8) enum like Ordering from an i8
// without this unsafe call to transmute?
unsafe { core::mem::transmute::<i8, Self>(delegated) }
}
}

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);

let mut ret = CtOption {
value: Ordering::Equal,
is_some: Choice::of_bool(false),
};
ret.conditional_assign(
&CtOption {
value: Ordering::Equal,
is_some: Choice::of_bool(true),
},
is_eq,
);
ret.conditional_assign(
&CtOption {
value: Ordering::Less,
is_some: Choice::of_bool(true),
},
is_lt,
);
ret.conditional_assign(
&CtOption {
value: Ordering::Greater,
is_some: Choice::of_bool(true),
},
is_gt,
);
ret
}
}

/// An [`Ord`][core::cmp::Ord]-like trait for constant-time comparisons.
///
/// This trait can be automatically implemented for types supporting the "equals" and "greater"
/// comparisons.
///
/// # Example
///
/// ```
/// use std::cmp::Ordering;
/// use subtle::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: ConstantTimeEq + ConstantTimeGreater {
/// This method returns an ordering between `self` and other`.
///
/// Although this method should never need to be overridden, it is exposed as a default method
/// here to force types to explicitly implement this trait. This ensures that types which are
/// *only* partially orderable do not pick up an incorrect `ConstantTimeOrd` impl just by
/// implementing the pairwise comparison operations. Contrast this with
/// [`ConstantTimePartialOrd`], which is automatically implemented for all types implementing
/// [`ConstantTimeGreater`], [`ConstantTimeLess`], and [`ConstantTimeEq`].
///
/// Here 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.
///
/// This method should execute in constant time.
fn ct_cmp(&self, other: &Self) -> Ordering {
let is_gt = self.ct_gt(other);
let is_eq = self.ct_eq(other);

let mut ret = Ordering::Less;
ret.conditional_assign(&Ordering::Equal, is_eq);
ret.conditional_assign(&Ordering::Greater, is_gt);
ret
}
}

impl ConstantTimeOrd for u8 {}
impl ConstantTimeOrd for u16 {}
impl ConstantTimeOrd for u32 {}
impl ConstantTimeOrd for u64 {}
#[cfg(feature = "i128")]
impl ConstantTimeOrd for u128 {}