Skip to content
Merged
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
1 change: 1 addition & 0 deletions ctutils/checkct/driver/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ macro_rules! private_integer {
}};
}

mod predicates;
mod primitives;

macro_rules! scalar_checks {
Expand Down
135 changes: 135 additions & 0 deletions ctutils/checkct/driver/src/driver/predicates.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//! Comparison specialization and loop-carried predicates used by crypto-bigint.

use super::*;

macro_rules! predicate_checks {
($module:ident, $ty:ty, $eq:ident, $le:ident, $lt:ident, $nz:ident, $select:ident,
$leading:ident, $trailing:ident) => {
mod $module {
use super::*;

// Inline these operations into the driver: an artificial function boundary
// would hide the caller specialization which previously introduced branches.
fn private() -> $ty {
black_box(PublicRng.next_u32());
private_integer!($ty)
}

#[checkct]
pub fn lt_constant() {
black_box(Choice::$lt(2, private() & 63).to_u8());
}

#[checkct]
pub fn le_constant() {
black_box(Choice::$le(2, private() & 63).to_u8());
}

#[checkct]
pub fn eq_constant() {
black_box(Choice::$eq(1, private() & 63).to_u8());
}

#[checkct]
pub fn nz() {
black_box(Choice::$nz(private()).to_u8());
}

#[checkct]
pub fn min_constant() {
let value = private() & 63;
black_box(Choice::$lt(2, value).$select(value, 2));
}

#[checkct]
pub fn and_loop() {
let mut all_zero = Choice::TRUE;
for value in [private(), private(), private()] {
// A raw byte predicate also covers callers that do not use a
// comparison constructor, such as crypto-bigint's Limb helpers.
let bit = (value | value.wrapping_neg()) >> (<$ty>::BITS - 1);
let zero = Choice::from_u8_lsb(bit as u8).not();
all_zero = all_zero.and(zero);
}
black_box(all_zero.to_u8());
}

fn limbs() -> [$ty; 16] {
let mut limbs = [0; 16];
for limb in &mut limbs {
*limb = private();
}
limbs
}

#[checkct]
pub fn leading_zeros_choice_loop() {
black_box(leading_zeros(&limbs()));
}

// Keep the whole limb scan intact, as in a crypto-bigint method. Each
// scalar zero count remains inlined into the loop.
#[inline(never)]
fn leading_zeros(limbs: &[$ty; 16]) -> u32 {
let mut count = 0;
let mut all_zero = Choice::TRUE;
let mut i = limbs.len();
while i > 0 {
i -= 1;
let value = limbs[i];
count += all_zero.select_u32(0, ctutils::primitives::$leading(value));
// Match callers with their own scalar predicate helpers: do not
// rely on the comparison constructor's separate hardening here.
let bit = (value | value.wrapping_neg()) >> (<$ty>::BITS - 1);
all_zero = all_zero.and(Choice::from_u8_lsb(bit as u8).not());
}
count
}

#[checkct]
pub fn trailing_zeros_choice_loop() {
black_box(trailing_zeros(&limbs()));
}

#[inline(never)]
fn trailing_zeros(limbs: &[$ty; 16]) -> u32 {
let mut count = 0;
let mut all_zero = Choice::TRUE;
let mut i = 0;
while i < limbs.len() {
let value = limbs[i];
count += all_zero.select_u32(0, ctutils::primitives::$trailing(value));
// Match callers with their own scalar predicate helpers: do not
// rely on the comparison constructor's separate hardening here.
let bit = (value | value.wrapping_neg()) >> (<$ty>::BITS - 1);
all_zero = all_zero.and(Choice::from_u8_lsb(bit as u8).not());
i += 1;
}
count
}
}
};
}

predicate_checks!(
u32_ops,
u32,
from_u32_eq,
from_u32_le,
from_u32_lt,
from_u32_nz,
select_u32,
leading_zeros_u32,
trailing_zeros_u32
);
predicate_checks!(
u64_ops,
u64,
from_u64_eq,
from_u64_le,
from_u64_lt,
from_u64_nz,
select_u64,
leading_zeros_u64,
trailing_zeros_u64
);
48 changes: 16 additions & 32 deletions ctutils/src/choice.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,10 @@
use crate::primitives::{bitle, bitlt, bitnz};
use crate::{CtAssign, CtAssignSlice, CtEq, CtEqSlice, CtSelectUsingCtAssign};
use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not};

#[cfg(feature = "subtle")]
use crate::CtSelect;

/// Bitwise less-than-or equal: returns `1` if `x <= y`, and otherwise returns `0`.
///
/// See "Hacker's Delight" 2nd edition, section 2-12 (Comparison predicates)
macro_rules! bitle {
($x:expr, $y:expr, $bits:expr) => {
(((!$x) | $y) & (($x ^ $y) | !($y.wrapping_sub($x)))) >> ($bits - 1)
};
}

/// Bitwise less-than: returns `1` if `x < y`, and otherwise returns `0`.
///
/// See "Hacker's Delight" 2nd edition, section 2-12 (Comparison predicates)
macro_rules! bitlt {
($x:expr, $y:expr, $bits:expr) => {
(((!$x) & $y) | (((!$x) | $y) & $x.wrapping_sub($y))) >> ($bits - 1)
};
}

/// Bitwise non-zero: returns `1` if `x != 0`, and otherwise returns `0`.
macro_rules! bitnz {
($value:expr, $bits:expr) => {
($value | $value.wrapping_neg()) >> ($bits - 1)
};
}

/// Constant-time analogue of `bool` providing a "best effort" optimization barrier.
///
/// This type attempts to hint to the compiler and its codegen backends that optimizations should
Expand Down Expand Up @@ -56,7 +32,15 @@ impl Choice {
#[inline]
#[must_use]
pub const fn and(self, rhs: Choice) -> Choice {
Self(self.0 & rhs.0)
#[cfg(all(target_arch = "arm", not(target_has_atomic = "ptr")))]
{
// Keep loop-carried predicates from becoming a short-circuiting boolean.
Self(self.to_u8() & rhs.to_u8())
}
#[cfg(not(all(target_arch = "arm", not(target_has_atomic = "ptr"))))]
{
Self(self.0 & rhs.0)
}
}

/// Apply an `or` conditional to the given [`Choice`]s.
Expand Down Expand Up @@ -199,7 +183,7 @@ impl Choice {
#[inline]
#[must_use]
pub const fn from_u32_le(x: u32, y: u32) -> Self {
Self::from_u32_lsb(bitle!(x, y, u32::BITS))
crate::primitives::le_u32(x, y)
}

/// Initialize from the least significant bit of a `u32`.
Expand All @@ -213,14 +197,14 @@ impl Choice {
#[inline]
#[must_use]
pub const fn from_u32_lt(x: u32, y: u32) -> Self {
Self::from_u32_lsb(bitlt!(x, y, u32::BITS))
crate::primitives::lt_u32(x, y)
}

/// Returns [`Choice::TRUE`] if `value != 0`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u32_nz(value: u32) -> Self {
Self::from_u32_lsb(bitnz!(value, u32::BITS))
crate::primitives::nz_u32(value)
}

// u64
Expand All @@ -236,7 +220,7 @@ impl Choice {
#[inline]
#[must_use]
pub const fn from_u64_le(x: u64, y: u64) -> Self {
Self::from_u64_lsb(bitle!(x, y, u64::BITS))
crate::primitives::le_u64(x, y)
}

/// Initialize from the least significant bit of a `u64`.
Expand All @@ -250,14 +234,14 @@ impl Choice {
#[inline]
#[must_use]
pub const fn from_u64_lt(x: u64, y: u64) -> Self {
Self::from_u64_lsb(bitlt!(x, y, u64::BITS))
crate::primitives::lt_u64(x, y)
}

/// Returns [`Choice::TRUE`] if `value != 0`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u64_nz(value: u64) -> Self {
Self::from_u64_lsb(bitnz!(value, u64::BITS))
crate::primitives::nz_u64(value)
}

// u128
Expand Down
68 changes: 68 additions & 0 deletions ctutils/src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,74 @@
//! Native zero counts are used when supported and advertised by the target configuration.
use crate::Choice;

/// Bitwise less-than-or equal: returns `1` if `x <= y`, and otherwise returns `0`.
///
/// See "Hacker's Delight" 2nd edition, section 2-12 (Comparison predicates)
macro_rules! bitle {
($x:expr, $y:expr, $bits:expr) => {
(((!$x) | $y) & (($x ^ $y) | !($y.wrapping_sub($x)))) >> ($bits - 1)
};
}

/// Bitwise less-than: returns `1` if `x < y`, and otherwise returns `0`.
///
/// See "Hacker's Delight" 2nd edition, section 2-12 (Comparison predicates)
macro_rules! bitlt {
($x:expr, $y:expr, $bits:expr) => {
(((!$x) & $y) | (((!$x) | $y) & $x.wrapping_sub($y))) >> ($bits - 1)
};
}

/// Bitwise non-zero: returns `1` if `x != 0`, and otherwise returns `0`.
macro_rules! bitnz {
($value:expr, $bits:expr) => {
($value | $value.wrapping_neg()) >> ($bits - 1)
};
}

pub(crate) use {bitle, bitlt, bitnz};

// Keep the comparison bit opaque before narrowing it to Choice's byte representation.
// ARM targets without pointer-width atomics cover Thumb-1 on Rust 1.87, which does not
// advertise `thumb2` as a target feature. The other targets retain the original lowering.
macro_rules! comparison_primitives {
($ty:ty, $le:ident, $lt:ident, $nz:ident, $from_lsb:ident) => {
#[inline(always)]
pub(crate) const fn $le(lhs: $ty, rhs: $ty) -> Choice {
let bit = bitle!(lhs, rhs, <$ty>::BITS);
#[cfg(all(target_arch = "arm", not(target_has_atomic = "ptr")))]
let bit = core::hint::black_box(bit);
Choice::$from_lsb(bit)
}

#[cfg_attr(
all(target_arch = "arm", not(target_has_atomic = "ptr")),
inline(never)
)]
#[cfg_attr(
not(all(target_arch = "arm", not(target_has_atomic = "ptr"))),
inline(always)
)]
pub(crate) const fn $lt(lhs: $ty, rhs: $ty) -> Choice {
let bit = bitlt!(lhs, rhs, <$ty>::BITS);
#[cfg(all(target_arch = "arm", not(target_has_atomic = "ptr")))]
let bit = core::hint::black_box(bit);
Choice::$from_lsb(bit)
}

#[inline(always)]
pub(crate) const fn $nz(value: $ty) -> Choice {
let bit = bitnz!(value, <$ty>::BITS);
#[cfg(all(target_arch = "arm", not(target_has_atomic = "ptr")))]
let bit = core::hint::black_box(bit);
Choice::$from_lsb(bit)
}
};
}

comparison_primitives!(u32, le_u32, lt_u32, nz_u32, from_u32_lsb);
comparison_primitives!(u64, le_u64, lt_u64, nz_u64, from_u64_lsb);

/// Count leading zero bits, returning 32 if `value` is zero.
#[inline]
#[must_use]
Expand Down
16 changes: 16 additions & 0 deletions ctutils/tests/proptests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,22 @@ macro_rules! int_proptests {
int_proptests!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);

proptest::proptest! {
#[test]
fn from_u32_comparison(x in any::<u32>(), y in any::<u32>()) {
prop_assert_eq!(x == y, Choice::from_u32_eq(x, y).to_bool());
prop_assert_eq!(x <= y, Choice::from_u32_le(x, y).to_bool());
prop_assert_eq!(x < y, Choice::from_u32_lt(x, y).to_bool());
prop_assert_eq!(x != 0, Choice::from_u32_nz(x).to_bool());
}

#[test]
fn from_u64_comparison(x in any::<u64>(), y in any::<u64>()) {
prop_assert_eq!(x == y, Choice::from_u64_eq(x, y).to_bool());
prop_assert_eq!(x <= y, Choice::from_u64_le(x, y).to_bool());
prop_assert_eq!(x < y, Choice::from_u64_lt(x, y).to_bool());
prop_assert_eq!(x != 0, Choice::from_u64_nz(x).to_bool());
}

#[test]
fn from_u128_comparison(
x in any::<u128>(),
Expand Down
Loading