From d53e273057514e462ce7c3386b71462b86ee4e24 Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Wed, 14 Aug 2024 09:33:09 -0400 Subject: [PATCH 1/2] backport https://github.com/dalek-cryptography/subtle/pull/123 --- src/lib.rs | 42 ++++++++++++++++++-------- tests/mod.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 100 insertions(+), 25 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 143212b..9e846c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; @@ -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) @@ -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(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 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)) @@ -728,7 +728,7 @@ macro_rules! generate_unsigned_integer_greater { Choice::from((bit & 1) as u8) } } - } + }; } generate_unsigned_integer_greater!(u8, 8); @@ -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); + +impl BlackBox { + /// 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) + } +} diff --git a/tests/mod.rs b/tests/mod.rs index 4185480..cb6219a 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -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] @@ -303,7 +353,7 @@ macro_rules! generate_greater_than_test { assert!(z.unwrap_u8() == 1); } } - } + }; } #[test] @@ -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); } @@ -359,7 +409,7 @@ macro_rules! generate_less_than_test { assert!(z.unwrap_u8() == 1); } } - } + }; } #[test] @@ -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()); +} From 2c0099e67a5c7f4bb5c6abe9c4647599330b4fba Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Wed, 14 Aug 2024 09:37:06 -0400 Subject: [PATCH 2/2] cargo: remove feature to use core black_box --- src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9e846c5..989c632 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,9 +23,6 @@ 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)