Skip to content

[DO NOT MERGE] Attempt at making a constant-time PartialOrd impl #79

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

Closed
Show file tree
Hide file tree
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
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ exclude = [
[badges]
travis-ci = { repository = "dalek-cryptography/subtle", branch = "master"}

[dev-dependencies]
rand = { version = "0.7" }

[features]
default = ["std", "i128"]
std = []
Expand Down
184 changes: 184 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,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 Down Expand Up @@ -664,3 +668,183 @@ impl<T: ConstantTimeEq> ConstantTimeEq for CtOption<T> {
(a & b & self.value.ct_eq(&rhs.value)) | (!a & !b)
}
}

/// A type which can be compared in some manner and be determined to be greater
/// than another of the same type.
pub trait ConstantTimeGreaterThan {
/// Determine whether `self > other`.
///
/// The bitwise-NOT of the return value of this function should be usable to
/// determine if `self <= other`.
///
/// This function should execute in constant time.
///
/// # Returns
///
/// A `Choice` with a set bit if `self > other`, and with no set bits
/// otherwise.
///
/// # Example
///
/// ```
/// # extern crate subtle;
/// use subtle::ConstantTimeGreaterThan;
///
/// let x: u8 = 13;
/// let y: u8 = 42;
///
/// let x_gt_y = x.ct_gt(&y);
///
/// assert_eq!(x_gt_y.unwrap_u8(), 0);
///
/// let y_gt_x = y.ct_gt(&x);
///
/// assert_eq!(y_gt_x.unwrap_u8(), 1);
/// ```
fn ct_gt(&self, other: &Self) -> Choice;
}

macro_rules! generate_unsigned_integer_greater_than {
($t_u: ty, $bit_width: expr) => {
impl ConstantTimeGreaterThan for $t_u {
/// Returns Choice::from(1) iff x > y, and Choice::from(0) iff x <= y.
///
/// # Note
///
/// This algoritm would also work for signed integers if we first
/// flip the top bit, e.g. `let x: u8 = x ^ 0x80`, etc.
#[inline]
fn ct_gt(&self, other: &$t_u) -> Choice {
let gtb = self & !other; // All the bits in self that are greater than their corresponding bits in other.
let mut ltb = !self & other; // All the bits in self that are less than their corresponding bits in other.
let mut pow = 1;

// Less-than operator is okay here because it's dependent on the bit-width.
while pow < $bit_width {
ltb |= ltb >> pow; // Bit-smear the highest set bit to the right.
pow += pow;
}
let mut bit = gtb & !ltb; // Select the highest set bit.
let mut pow = 1;

while pow < $bit_width {
bit |= bit >> pow; // Shift it to the right until we end up with either 0 or 1.
pow += pow;
}
// XXX We should possibly do the above flattening to 0 or 1 in the
// Choice constructor rather than making it a debug error?
Choice::from((bit & 1) as u8)
}
}
}
}

generate_unsigned_integer_greater_than!(u8, 8);
generate_unsigned_integer_greater_than!(u16, 16);
generate_unsigned_integer_greater_than!(u32, 32);
generate_unsigned_integer_greater_than!(u64, 64);
#[cfg(feature = "i128")]
generate_unsigned_integer_greater_than!(u128, 128);

pub trait ConstantTimeLessThan: ConstantTimeEq + ConstantTimeGreaterThan {
#[inline]
fn ct_lt(&self, other: &Self) -> Choice {
!self.ct_gt(other) & !self.ct_eq(other)
}
}

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

pub trait ConstantTimePartialOrd: ConstantTimeEq + ConstantTimeGreaterThan {
fn ct_partial_cmp(&self, other: &Self) -> CtOption<Ordering>;
}

macro_rules! generate_unsigned_integer_partial_ord {
($t_u: ty, $t_i: ty, $bit_width: expr) => {
impl ConstantTimePartialOrd for $t_u
{
fn ct_partial_cmp(&self, other: &$t_u) -> CtOption<Ordering> {
let eq = self.ct_eq(other).unwrap_u8();
let gt = self.ct_gt(other).unwrap_u8();

let cmp = ((gt + gt + eq) as $t_i) - 1;
let ord = Ordering(cmp);

// XXX is this match constant-time?
match ((gt + gt + eq) as $t_i) - 1 {
-1 => CtOption{ value: Ordering::Less, is_some: Choice::from(1u8) },
0 => CtOption{ value: Ordering::Equal, is_some: Choice::from(1u8) },
1 => CtOption{ value: Ordering::Greater, is_some: Choice::from(1u8) },
_ => panic!("Comparison resulted in number other than -1, 0, or 1"),
}
}
}
}
}

generate_unsigned_integer_partial_ord!(u8, i8, 8);
generate_unsigned_integer_partial_ord!(u16, i16, 16);
generate_unsigned_integer_partial_ord!(u32, i32, 32);
generate_unsigned_integer_partial_ord!(u64, i64, 64);
#[cfg(feature = "i128")]
generate_unsigned_integer_partial_ord!(u128, i128, 128);

pub trait ConstantTimeOrd: ConstantTimeEq + ConstantTimePartialOrd {
fn ct_cmp(&self, other: &Self) -> Ordering;
}

#[cfg(test)]
mod test {
use super::*;

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

#[test]
fn partial_ord_u8() {
for _ in 0..100 {
let x = OsRng.next_u32() as u8;
let y = OsRng.next_u32() as u8;
let z: CtOption<Ordering> = x.ct_partial_cmp(&y);

assert!(z.is_some().unwrap_u8() == 1);

println!("{:?}", z.unwrap());

if x < y {
assert!(z.unwrap() == Ordering::Less);
} else if x == y {
assert!(z.unwrap() == Ordering::Equal);
} else if x > y {
assert!(z.unwrap() == Ordering::Greater);
}
}
}

#[test]
fn partial_ord_eq() {
let _32_eq_32 = 32u16.ct_partial_cmp(&32u16);

assert!(_32_eq_32.is_some().unwrap_u8() == 1);

println!("{:?}", _32_eq_32.unwrap());

assert!(_32_eq_32.unwrap() == Ordering::Equal);
}

#[test]
fn partial_ord_gt() {
let _32_gt_15 = 32u16.ct_partial_cmp(&15u16);

assert!(_32_gt_15.is_some().unwrap_u8() == 1);

println!("{:?}", _32_gt_15.unwrap());

assert!(_32_gt_15.unwrap() == Ordering::Greater);
}
}
106 changes: 106 additions & 0 deletions tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
extern crate rand;
extern crate subtle;

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

use subtle::*;

#[test]
Expand Down Expand Up @@ -281,3 +285,105 @@ fn unwrap_none_ctoption() {
// compiler decides to optimize it away.
CtOption::new(10, Choice::from(0)).unwrap();
}

macro_rules! generate_greater_than_test {
($ty: ty) => {
for _ in 0..100 {
let x = OsRng.next_u64() as $ty;
let y = OsRng.next_u64() as $ty;
let z = x.ct_gt(&y);

println!("x={}, y={}, z={:?}", x, y, z);

if x < y {
assert!(z.unwrap_u8() == 0);
} else if x == y {
assert!(z.unwrap_u8() == 0);
} else if x > y {
assert!(z.unwrap_u8() == 1);
}
}
}
}

#[test]
fn greater_than_u8() {
generate_greater_than_test!(u8);
}

#[test]
fn greater_than_u16() {
generate_greater_than_test!(u16);
}

#[test]
fn greater_than_u32() {
generate_greater_than_test!(u32);
}

#[test]
fn greater_than_u64() {
generate_greater_than_test!(u64);
}

#[cfg(feature = "i128")]
#[test]
fn greater_than_u128() {
generate_greater_than_test!(u128);
}

#[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
/// go/crypto/subtle uses.)
fn less_than_twos_compliment_minmax() {
let z = 1u32.ct_lt(&(2u32.pow(31)-1));

assert!(z.unwrap_u8() == 1);
}

macro_rules! generate_less_than_test {
($ty: ty) => {
for _ in 0..100 {
let x = OsRng.next_u64() as $ty;
let y = OsRng.next_u64() as $ty;
let z = x.ct_gt(&y);

println!("x={}, y={}, z={:?}", x, y, z);

if x < y {
assert!(z.unwrap_u8() == 0);
} else if x == y {
assert!(z.unwrap_u8() == 0);
} else if x > y {
assert!(z.unwrap_u8() == 1);
}
}
}
}

#[test]
fn less_than_u8() {
generate_less_than_test!(u8);
}

#[test]
fn less_than_u16() {
generate_less_than_test!(u16);
}

#[test]
fn less_than_u32() {
generate_less_than_test!(u32);
}

#[test]
fn less_than_u64() {
generate_less_than_test!(u64);
}

#[cfg(feature = "i128")]
#[test]
fn less_than_u128() {
generate_less_than_test!(u128);
}