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
52 changes: 52 additions & 0 deletions ctutils/checkct/driver/src/driver/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,55 @@ macro_rules! addition_checks {

addition_checks!(u64_add, u64, overflowing_add_u64, to_u64_mask);
addition_checks!(u128_add, u128, overflowing_add_u128, to_u128_mask);

macro_rules! subtraction_checks {
($module:ident, $ty:ty, $sub:ident, $mask:ident) => {
mod $module {
use super::*;

#[checkct]
pub fn borrowing_sub() {
black_box(PublicRng.next_u32());
let incoming = Choice::from_u8_lsb(private_integer!(u8));
let (difference, borrow) =
primitives::$sub(private_integer!($ty), private_integer!($ty), incoming);
black_box((difference, borrow.to_u8()));
}

#[checkct]
pub fn borrowing_sub_zero_borrow() {
black_box(PublicRng.next_u32());
let (difference, borrow) =
primitives::$sub(private_integer!($ty), private_integer!($ty), Choice::FALSE);
black_box((difference, borrow.to_u8()));
}

#[checkct]
pub fn borrowing_sub_limbs() {
black_box(PublicRng.next_u32());
let lhs = [private_integer!($ty), private_integer!($ty)];
let rhs = [private_integer!($ty), private_integer!($ty)];
let mut borrow = Choice::from_u8_lsb(private_integer!(u8));
let mut difference = [0; 2];
for i in 0..2 {
(difference[i], borrow) = primitives::$sub(lhs[i], rhs[i], borrow);
}
black_box((difference, borrow.to_u8()));
}

/// Division conditionally adds back the divisor using the outgoing borrow mask.
#[checkct]
pub fn borrowing_sub_add_back() {
black_box(PublicRng.next_u32());
let lhs = private_integer!($ty);
let rhs = private_integer!($ty);
let incoming = Choice::from_u8_lsb(private_integer!(u8));
let (difference, borrow) = primitives::$sub(lhs, rhs, incoming);
black_box(difference.wrapping_add(rhs & borrow.$mask()));
}
}
};
}

subtraction_checks!(u32_sub, u32, borrowing_sub_u32, to_u32_mask);
subtraction_checks!(u64_sub, u64, borrowing_sub_u64, to_u64_mask);
69 changes: 67 additions & 2 deletions ctutils/src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,19 @@ macro_rules! bitle {
};
}

/// Outgoing borrow bit for a wrapping `x - y - borrow_in` result.
macro_rules! bitborrow {
($x:expr, $y:expr, $result:expr, $bits:expr) => {
(((!$x) & $y) | (((!$x) | $y) & $result)) >> ($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)
$crate::primitives::bitborrow!($x, $y, ($x).wrapping_sub($y), $bits)
};
}

Expand All @@ -34,7 +41,7 @@ macro_rules! bitnz {
};
}

pub(crate) use {bitle, bitlt, bitnz};
pub(crate) use {bitborrow, 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
Expand Down Expand Up @@ -77,6 +84,29 @@ macro_rules! comparison_primitives {
comparison_primitives!(u32, le_u32, lt_u32, nz_u32, from_u32_lsb);
comparison_primitives!(u64, le_u64, lt_u64, nz_u64, from_u64_lsb);

macro_rules! borrowing_sub_primitives {
($ty:ty, $sub:ident, $from_lsb:ident) => {
/// Subtract `rhs` and an incoming borrow from `lhs`.
///
/// Returns the wrapping difference and [`Choice::TRUE`] if the subtraction underflows.
/// The incoming `borrow` subtracts one when true and zero when false.
#[inline]
#[must_use]
pub const fn $sub(lhs: $ty, rhs: $ty, borrow: Choice) -> ($ty, Choice) {
let difference = lhs.wrapping_sub(rhs).wrapping_sub(borrow.to_u8() as $ty);
// Keep all arithmetic in the operand type: widening can trigger incorrect
// optimization on older compilers: https://github.com/rust-lang/rust/issues/149522
// Keep the full-width bit opaque before narrowing it to Choice, so a caller's
// masked add-back cannot be folded into a secret-dependent branch.
let borrow = core::hint::black_box(bitborrow!(lhs, rhs, difference, <$ty>::BITS));
(difference, Choice::$from_lsb(borrow))
}
};
}

borrowing_sub_primitives!(u32, borrowing_sub_u32, from_u32_lsb);
borrowing_sub_primitives!(u64, borrowing_sub_u64, from_u64_lsb);

/// Count leading zero bits, returning 32 if `value` is zero.
#[inline]
#[must_use]
Expand Down Expand Up @@ -236,6 +266,12 @@ mod tests {

// Keep all public entrypoints usable by const callers such as crypto-bigint.
const _: () = {
assert!(borrowing_sub_u32(0, 0, Choice::TRUE).0 == u32::MAX);
assert!(borrowing_sub_u32(0, 0, Choice::TRUE).1.to_bool());
assert!(borrowing_sub_u64(0, 0, Choice::TRUE).0 == u64::MAX);
assert!(borrowing_sub_u64(0, 0, Choice::TRUE).1.to_bool());
assert!(!borrowing_sub_u32(1, 0, Choice::TRUE).1.to_bool());
assert!(!borrowing_sub_u64(1, 0, Choice::TRUE).1.to_bool());
assert!(leading_zeros_u32(0) == 32);
assert!(trailing_zeros_u32(0) == 32);
assert!(leading_zeros_u64(0) == 64);
Expand Down Expand Up @@ -270,6 +306,35 @@ mod tests {
}
}

macro_rules! subtraction_tests {
($test:ident, $ty:ty, $sub:ident) => {
#[test]
fn $test() {
let half = 1 << (<$ty>::BITS >> 1);
let high = 1 << (<$ty>::BITS - 1);
let values = [0, 1, half - 1, half, half + 1, high - 1, high, <$ty>::MAX];
for lhs in values {
for rhs in values {
for incoming in [false, true] {
let (expected, first_borrow) = lhs.overflowing_sub(rhs);
let (expected, second_borrow) =
expected.overflowing_sub(<$ty>::from(incoming));
let (difference, borrow) =
$sub(lhs, rhs, Choice::from_u8_lsb(u8::from(incoming)));
assert_eq!(
(difference, borrow.to_bool()),
(expected, first_borrow | second_borrow)
);
}
}
}
}
};
}

subtraction_tests!(sub_u32, u32, borrowing_sub_u32);
subtraction_tests!(sub_u64, u64, borrowing_sub_u64);

macro_rules! addition_tests {
($test:ident, $ty:ty, $add:ident) => {
#[test]
Expand Down
18 changes: 17 additions & 1 deletion ctutils/tests/primitives.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
//! Differential tests against the core integer operations.

use ctutils::primitives::*;
use ctutils::{Choice, primitives::*};
use proptest::prelude::*;

proptest! {
#[test]
fn sub_u32(lhs in any::<u32>(), rhs in any::<u32>(), incoming in any::<bool>()) {
let (expected, first_borrow) = lhs.overflowing_sub(rhs);
let (expected, second_borrow) = expected.overflowing_sub(u32::from(incoming));
let (difference, borrow) = borrowing_sub_u32(lhs, rhs, Choice::from_u8_lsb(u8::from(incoming)));
prop_assert_eq!((difference, borrow.to_bool()), (expected, first_borrow | second_borrow));
}

#[test]
fn sub_u64(lhs in any::<u64>(), rhs in any::<u64>(), incoming in any::<bool>()) {
let (expected, first_borrow) = lhs.overflowing_sub(rhs);
let (expected, second_borrow) = expected.overflowing_sub(u64::from(incoming));
let (difference, borrow) = borrowing_sub_u64(lhs, rhs, Choice::from_u8_lsb(u8::from(incoming)));
prop_assert_eq!((difference, borrow.to_bool()), (expected, first_borrow | second_borrow));
}

#[test]
fn zero_counts_u32(value in any::<u32>()) {
prop_assert_eq!(leading_zeros_u32(value), value.leading_zeros());
Expand Down
Loading