Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
redshiftzero committed Aug 14, 2024
1 parent 3a65884 commit d53e273
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 25 deletions.
42 changes: 30 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
#![deny(missing_docs)]
#![doc(html_root_url = "https://docs.rs/subtle-ng/2.5.0")]


#[cfg(feature = "std")]
#[macro_use]
extern crate std;
Expand All @@ -24,6 +23,9 @@ extern crate rand;
use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Neg, Not};
use core::option::Option;

#[cfg(feature = "core_hint_black_box")]
use core::hint::black_box;

/// The `Choice` struct represents a choice for use in conditional assignment.
///
/// It is a wrapper around a `u8`, which should have the value either `1` (true)
Expand Down Expand Up @@ -145,25 +147,23 @@ impl Not for Choice {
/// code may break in a non-destructive way in the future, “constant-time” code
/// is a continually moving target, and this is better than doing nothing.
#[inline(never)]
fn black_box(input: u8) -> u8 {
debug_assert!((input == 0u8) | (input == 1u8));

fn black_box<T: Copy>(input: T) -> T {
unsafe {
// Optimization barrier
//
// Unsafe is ok, because:
// - &input is not NULL;
// - size of input is not zero;
// - u8 is neither Sync, nor Send;
// - u8 is Copy, so input is always live;
// - u8 type is always properly aligned.
core::ptr::read_volatile(&input as *const u8)
// SAFETY:
// - &input is not NULL because we own input;
// - input is Copy and always live;
// - input is always properly aligned.
core::ptr::read_volatile(&input)
}
}

impl From<u8> for Choice {
#[inline]
fn from(input: u8) -> Choice {
debug_assert!((input == 0u8) | (input == 1u8));

// Our goal is to prevent the compiler from inferring that the value held inside the
// resulting `Choice` struct is really an `i1` instead of an `i8`.
Choice(black_box(input))
Expand Down Expand Up @@ -728,7 +728,7 @@ macro_rules! generate_unsigned_integer_greater {
Choice::from((bit & 1) as u8)
}
}
}
};
}

generate_unsigned_integer_greater!(u8, 8);
Expand Down Expand Up @@ -788,3 +788,21 @@ impl ConstantTimeLess for u32 {}
impl ConstantTimeLess for u64 {}
#[cfg(feature = "i128")]
impl ConstantTimeLess for u128 {}

/// Wrapper type which implements an optimization barrier for all accesses.
#[derive(Clone, Copy, Debug)]
pub struct BlackBox<T: Copy>(T);

impl<T: Copy> BlackBox<T> {
/// Constructs a new instance of `BlackBox` which will wrap the specified value.
///
/// All access to the inner value will be mediated by a `black_box` optimization barrier.
pub fn new(value: T) -> Self {
Self(value)
}

/// Read the inner value, applying an optimization barrier on access.
pub fn get(self) -> T {
black_box(self.0)
}
}
83 changes: 70 additions & 13 deletions tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,16 +266,66 @@ fn test_ctoption() {
));

// Test (in)equality
assert!(CtOption::new(1, Choice::from(0)).ct_eq(&CtOption::new(1, Choice::from(1))).unwrap_u8() == 0);
assert!(CtOption::new(1, Choice::from(1)).ct_eq(&CtOption::new(1, Choice::from(0))).unwrap_u8() == 0);
assert!(CtOption::new(1, Choice::from(0)).ct_eq(&CtOption::new(2, Choice::from(1))).unwrap_u8() == 0);
assert!(CtOption::new(1, Choice::from(1)).ct_eq(&CtOption::new(2, Choice::from(0))).unwrap_u8() == 0);
assert!(CtOption::new(1, Choice::from(0)).ct_eq(&CtOption::new(1, Choice::from(0))).unwrap_u8() == 1);
assert!(CtOption::new(1, Choice::from(0)).ct_eq(&CtOption::new(2, Choice::from(0))).unwrap_u8() == 1);
assert!(CtOption::new(1, Choice::from(1)).ct_eq(&CtOption::new(2, Choice::from(1))).unwrap_u8() == 0);
assert!(CtOption::new(1, Choice::from(1)).ct_eq(&CtOption::new(2, Choice::from(1))).unwrap_u8() == 0);
assert!(CtOption::new(1, Choice::from(1)).ct_eq(&CtOption::new(1, Choice::from(1))).unwrap_u8() == 1);
assert!(CtOption::new(1, Choice::from(1)).ct_eq(&CtOption::new(1, Choice::from(1))).unwrap_u8() == 1);
assert!(
CtOption::new(1, Choice::from(0))
.ct_eq(&CtOption::new(1, Choice::from(1)))
.unwrap_u8()
== 0
);
assert!(
CtOption::new(1, Choice::from(1))
.ct_eq(&CtOption::new(1, Choice::from(0)))
.unwrap_u8()
== 0
);
assert!(
CtOption::new(1, Choice::from(0))
.ct_eq(&CtOption::new(2, Choice::from(1)))
.unwrap_u8()
== 0
);
assert!(
CtOption::new(1, Choice::from(1))
.ct_eq(&CtOption::new(2, Choice::from(0)))
.unwrap_u8()
== 0
);
assert!(
CtOption::new(1, Choice::from(0))
.ct_eq(&CtOption::new(1, Choice::from(0)))
.unwrap_u8()
== 1
);
assert!(
CtOption::new(1, Choice::from(0))
.ct_eq(&CtOption::new(2, Choice::from(0)))
.unwrap_u8()
== 1
);
assert!(
CtOption::new(1, Choice::from(1))
.ct_eq(&CtOption::new(2, Choice::from(1)))
.unwrap_u8()
== 0
);
assert!(
CtOption::new(1, Choice::from(1))
.ct_eq(&CtOption::new(2, Choice::from(1)))
.unwrap_u8()
== 0
);
assert!(
CtOption::new(1, Choice::from(1))
.ct_eq(&CtOption::new(1, Choice::from(1)))
.unwrap_u8()
== 1
);
assert!(
CtOption::new(1, Choice::from(1))
.ct_eq(&CtOption::new(1, Choice::from(1)))
.unwrap_u8()
== 1
);
}

#[test]
Expand Down Expand Up @@ -303,7 +353,7 @@ macro_rules! generate_greater_than_test {
assert!(z.unwrap_u8() == 1);
}
}
}
};
}

#[test]
Expand Down Expand Up @@ -337,7 +387,7 @@ fn greater_than_u128() {
/// 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));
let z = 1u32.ct_lt(&(2u32.pow(31) - 1));

assert!(z.unwrap_u8() == 1);
}
Expand All @@ -359,7 +409,7 @@ macro_rules! generate_less_than_test {
assert!(z.unwrap_u8() == 1);
}
}
}
};
}

#[test]
Expand Down Expand Up @@ -387,3 +437,10 @@ fn less_than_u64() {
fn less_than_u128() {
generate_less_than_test!(u128);
}

#[test]
fn black_box_round_trip() {
let n = 42u64;
let black_box = BlackBox::new(n);
assert_eq!(n, black_box.get());
}

0 comments on commit d53e273

Please sign in to comment.