Skip to content

Commit

Permalink
Merge remote-tracking branch 'tarcieri/ordering-trait-impls' into dev…
Browse files Browse the repository at this point in the history
…elop
  • Loading branch information
isislovecruft committed Feb 27, 2023
2 parents 01f8d48 + c319c36 commit 1ac233f
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 0 deletions.
49 changes: 49 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
#[macro_use]
extern crate std;

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

Expand Down Expand Up @@ -371,6 +372,14 @@ generate_integer_equal!(u64, i64, 64);
generate_integer_equal!(u128, i128, 128);
generate_integer_equal!(usize, isize, ::core::mem::size_of::<usize>() * 8);

/// `Ordering` is `#[repr(i8)]` making it possible to leverage `i8::ct_eq`.
impl ConstantTimeEq for cmp::Ordering {
#[inline]
fn ct_eq(&self, other: &Self) -> Choice {
(*self as i8).ct_eq(&(*other as i8))
}
}

/// A type which can be conditionally selected in constant time.
///
/// This trait also provides generic implementations of conditional
Expand Down Expand Up @@ -529,6 +538,26 @@ generate_integer_conditional_select!( u64 i64);
#[cfg(feature = "i128")]
generate_integer_conditional_select!(u128 i128);

/// `Ordering` is `#[repr(i8)]` where:
///
/// - `Less` => -1
/// - `Equal` => 0
/// - `Greater` => 1
///
/// Given this, it's possible to operate on orderings as if they're integers,
/// which allows leveraging conditional masking for predication.
impl ConditionallySelectable for cmp::Ordering {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
let a = *a as i8;
let b = *b as i8;
let ret = i8::conditional_select(&a, &b, choice);

// SAFETY: `Ordering` is `#[repr(i8)]` and `ret` has been assigned to
// a value which was originally a valid `Ordering` then cast to `i8`
unsafe { *((&ret as *const _) as *const cmp::Ordering) }
}
}

impl ConditionallySelectable for Choice {
#[inline]
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
Expand Down Expand Up @@ -843,6 +872,16 @@ generate_unsigned_integer_greater!(u64, 64);
#[cfg(feature = "i128")]
generate_unsigned_integer_greater!(u128, 128);

impl ConstantTimeGreater for cmp::Ordering {
#[inline]
fn ct_gt(&self, other: &Self) -> Choice {
// No impl of `ConstantTimeGreater` for `i8`, so use `u8`
let a = (*self as i8) + 1;
let b = (*other as i8) + 1;
(a as u8).ct_gt(&(b as u8))
}
}

/// 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 {
Expand Down Expand Up @@ -893,3 +932,13 @@ impl ConstantTimeLess for u32 {}
impl ConstantTimeLess for u64 {}
#[cfg(feature = "i128")]
impl ConstantTimeLess for u128 {}

impl ConstantTimeLess for cmp::Ordering {
#[inline]
fn ct_lt(&self, other: &Self) -> Choice {
// No impl of `ConstantTimeLess` for `i8`, so use `u8`
let a = (*self as i8) + 1;
let b = (*other as i8) + 1;
(a as u8).ct_lt(&(b as u8))
}
}
37 changes: 37 additions & 0 deletions tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::cmp;

use rand::rngs::OsRng;
use rand::RngCore;

Expand Down Expand Up @@ -93,6 +95,19 @@ fn custom_conditional_select_i16() {
assert_eq!(i16::conditional_select(&x, &y, 1.into()), 514);
}

#[test]
fn ordering_conditional_select() {
assert_eq!(
cmp::Ordering::conditional_select(&cmp::Ordering::Less, &cmp::Ordering::Greater, 0.into()),
cmp::Ordering::Less
);

assert_eq!(
cmp::Ordering::conditional_select(&cmp::Ordering::Less, &cmp::Ordering::Greater, 1.into()),
cmp::Ordering::Greater
);
}

macro_rules! generate_integer_equal_tests {
($($t:ty),*) => ($(
let y: $t = 0; // all 0 bits
Expand Down Expand Up @@ -146,6 +161,16 @@ fn choice_equal() {
assert!(Choice::from(1).ct_eq(&Choice::from(1)).unwrap_u8() == 1);
}

#[test]
fn ordering_equal() {
let a = cmp::Ordering::Equal;
let b = cmp::Ordering::Greater;
let c = a;

assert_eq!(a.ct_eq(&b).unwrap_u8(), 0);
assert_eq!(a.ct_eq(&c).unwrap_u8(), 1);
}

#[test]
fn test_ctoption() {
let a = CtOption::new(10, Choice::from(1));
Expand Down Expand Up @@ -331,6 +356,12 @@ fn greater_than_u128() {
generate_greater_than_test!(u128);
}

#[test]
fn greater_than_ordering() {
assert_eq!(cmp::Ordering::Less.ct_gt(&cmp::Ordering::Greater).unwrap_u8(), 0);
assert_eq!(cmp::Ordering::Greater.ct_gt(&cmp::Ordering::Less).unwrap_u8(), 1);
}

#[test]
/// Test that the two's compliment min and max, i.e. 0000...0001 < 1111...1110,
/// gives the correct result. (This fails using the bit-twiddling algorithm that
Expand Down Expand Up @@ -386,3 +417,9 @@ fn less_than_u64() {
fn less_than_u128() {
generate_less_than_test!(u128);
}

#[test]
fn less_than_ordering() {
assert_eq!(cmp::Ordering::Greater.ct_lt(&cmp::Ordering::Less).unwrap_u8(), 0);
assert_eq!(cmp::Ordering::Less.ct_lt(&cmp::Ordering::Greater).unwrap_u8(), 1);
}

0 comments on commit 1ac233f

Please sign in to comment.