Skip to content

Commit e3b5746

Browse files
committed
ctutils: import Choice methods from crypto-bigint
This imports all of the `const fn` constructor and predication/selection methods `crypto-bigint` needs to replace `crypto_bigint::ConstChoice` with `ctutils::Choice`, along with extant tests, and adds additional tests for better coverage.
1 parent f64379d commit e3b5746

File tree

2 files changed

+250
-3
lines changed

2 files changed

+250
-3
lines changed

ctutils/src/choice.rs

Lines changed: 218 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign,
99
/// This is used as a "belt-and-suspenders" defense in addition to mechanisms like
1010
/// constant-time predication intrinsics provided by the `cmov` crate, and is never expected to be
1111
/// the only line of defense.
12-
#[derive(Copy, Clone, Debug)]
12+
// TODO(tarcieri): remove `Eq`/`PartialEq` when `crypto-bigint` is updated
13+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1314
pub struct Choice(u8);
1415

1516
impl Choice {
@@ -59,6 +60,26 @@ impl Choice {
5960
core::hint::black_box(self.0)
6061
}
6162

63+
/// HACK: workaround to allow `const fn` boolean support on Rust 1.85.
64+
///
65+
/// This does not apply `black_box` to the output.
66+
// TODO(tarcieri): deprecate/remove this in favor of `to_bool` when MSRV is Rust 1.86
67+
pub const fn to_bool_vartime(self) -> bool {
68+
self.0 != 0
69+
}
70+
71+
/// HACK: workaround to allow `const fn` boolean support on Rust 1.85.
72+
///
73+
/// This does not apply `black_box` to the output.
74+
// TODO(tarcieri): deprecate/remove this in favor of `to_u8` when MSRV is Rust 1.86
75+
pub const fn to_u8_vartime(self) -> u8 {
76+
self.0
77+
}
78+
79+
//
80+
// Bitwise ops
81+
//
82+
6283
/// Apply an `and` conditional to the given [`Choice`]s.
6384
#[inline]
6485
pub const fn and(self, rhs: Choice) -> Choice {
@@ -83,6 +104,122 @@ impl Choice {
83104
// NOTE: assumes self.0 is `0` or `1` as checked in constructor
84105
Self(self.0 ^ 1)
85106
}
107+
108+
//
109+
// Comparison ops
110+
//
111+
112+
/// `const fn` not equal operation.
113+
#[inline]
114+
pub const fn ne(self, other: Self) -> Self {
115+
Self::xor(self, other)
116+
}
117+
118+
/// `const fn` equality operation.
119+
#[inline]
120+
pub const fn eq(self, other: Self) -> Self {
121+
Self::ne(self, other).not()
122+
}
123+
124+
//
125+
// `const fn` constructor methods
126+
//
127+
128+
/// Returns the truthy value if `x == y`, and the falsy value otherwise.
129+
#[inline]
130+
pub const fn from_i64_eq(x: i64, y: i64) -> Self {
131+
Self::from_u64_nonzero(x as u64 ^ y as u64).not()
132+
}
133+
134+
/// Returns the truthy value if `x == y`, and the falsy value otherwise.
135+
#[inline]
136+
pub const fn from_u32_eq(x: u32, y: u32) -> Self {
137+
Self::from_u32_nonzero(x ^ y).not()
138+
}
139+
140+
/// Returns the truthy value if `x <= y` and the falsy value otherwise.
141+
#[inline]
142+
pub const fn from_u32_le(x: u32, y: u32) -> Self {
143+
// See "Hacker's Delight" 2nd ed, section 2-12 (Comparison predicates)
144+
let bit = (((!x) | y) & ((x ^ y) | !y.wrapping_sub(x))) >> (u32::BITS - 1);
145+
Self::from_u32_lsb(bit)
146+
}
147+
148+
/// Initialize from the least significant bit of a `u32`.
149+
#[inline]
150+
pub const fn from_u32_lsb(value: u32) -> Self {
151+
Self::new((value & 0x1) as u8)
152+
}
153+
154+
/// Returns the truthy value if `x < y`, and the falsy value otherwise.
155+
#[inline]
156+
pub const fn from_u32_lt(x: u32, y: u32) -> Self {
157+
// See "Hacker's Delight" 2nd ed, section 2-12 (Comparison predicates)
158+
let bit = (((!x) & y) | (((!x) | y) & x.wrapping_sub(y))) >> (u32::BITS - 1);
159+
Self::from_u32_lsb(bit)
160+
}
161+
162+
/// Returns the truthy value if `value != 0`, and the falsy value otherwise.
163+
#[inline]
164+
pub const fn from_u32_nonzero(value: u32) -> Self {
165+
Self::from_u32_lsb((value | value.wrapping_neg()) >> (u32::BITS - 1))
166+
}
167+
168+
/// Initialize from the least significant bit of a `u64`.
169+
#[inline]
170+
pub const fn from_u64_lsb(value: u64) -> Self {
171+
Self::new((value & 0x1) as u8)
172+
}
173+
174+
/// Returns the truthy value if `value != 0`, and the falsy value otherwise.
175+
#[inline]
176+
pub const fn from_u64_nonzero(value: u64) -> Self {
177+
Self::from_u64_lsb((value | value.wrapping_neg()) >> (u64::BITS - 1))
178+
}
179+
180+
//
181+
// `const fn` predication methods
182+
//
183+
184+
/// Return `b` if `self` is truthy, otherwise return `a`.
185+
#[inline]
186+
pub const fn select_i64(self, a: i64, b: i64) -> i64 {
187+
self.select_u64(a as u64, b as u64) as i64
188+
}
189+
190+
/// Return `b` if `self` is truthy, otherwise return `a`.
191+
#[inline]
192+
pub const fn select_u32(self, a: u32, b: u32) -> u32 {
193+
a ^ (self.to_u32_mask() & (a ^ b))
194+
}
195+
196+
/// Return `b` if `self` is truthy, otherwise return `a`.
197+
#[inline]
198+
pub const fn select_u64(self, a: u64, b: u64) -> u64 {
199+
a ^ (self.to_u64_mask() & (a ^ b))
200+
}
201+
202+
/// Create a `u32` bitmask.
203+
///
204+
/// # Returns
205+
/// - `0` for `Choice::FALSE`
206+
/// - `u32::MAX` for `Choice::TRUE`
207+
#[inline]
208+
#[allow(trivial_numeric_casts)]
209+
pub(crate) const fn to_u32_mask(self) -> u32 {
210+
(self.0 as u32 & 1).wrapping_neg()
211+
}
212+
213+
/// Create a `u64` bitmask.
214+
///
215+
/// # Returns
216+
/// - `0` for `Choice::FALSE`
217+
/// - `u64::MAX` for `Choice::TRUE`
218+
#[inline]
219+
#[allow(trivial_numeric_casts)]
220+
pub(crate) const fn to_u64_mask(self) -> u64 {
221+
(self.0 as u64 & 1).wrapping_neg()
222+
}
86223
}
87224

88225
impl BitAnd for Choice {
@@ -253,4 +390,84 @@ mod tests {
253390
assert_eq!(Choice::new(0).not().to_u8(), 1);
254391
assert_eq!(Choice::new(1).not().to_u8(), 0);
255392
}
393+
394+
#[test]
395+
fn from_i64_eq() {
396+
assert_eq!(Choice::from_i64_eq(0, 1), Choice::FALSE);
397+
assert_eq!(Choice::from_i64_eq(1, 1), Choice::TRUE);
398+
}
399+
400+
#[test]
401+
fn from_u32_eq() {
402+
assert_eq!(Choice::from_u32_eq(0, 1), Choice::FALSE);
403+
assert_eq!(Choice::from_u32_eq(1, 1), Choice::TRUE);
404+
}
405+
406+
#[test]
407+
fn from_u32_le() {
408+
assert_eq!(Choice::from_u32_le(0, 0), Choice::TRUE);
409+
assert_eq!(Choice::from_u32_le(1, 0), Choice::FALSE);
410+
assert_eq!(Choice::from_u32_le(1, 1), Choice::TRUE);
411+
assert_eq!(Choice::from_u32_le(1, 2), Choice::TRUE);
412+
}
413+
414+
#[test]
415+
fn from_u32_lsb() {
416+
assert_eq!(Choice::from_u32_lsb(0), Choice::FALSE);
417+
assert_eq!(Choice::from_u32_lsb(1), Choice::TRUE);
418+
assert_eq!(Choice::from_u32_lsb(2), Choice::FALSE);
419+
assert_eq!(Choice::from_u32_lsb(3), Choice::TRUE);
420+
}
421+
422+
#[test]
423+
fn from_u32_lt() {
424+
assert_eq!(Choice::from_u32_lt(0, 0), Choice::FALSE);
425+
assert_eq!(Choice::from_u32_lt(1, 0), Choice::FALSE);
426+
assert_eq!(Choice::from_u32_lt(1, 1), Choice::FALSE);
427+
assert_eq!(Choice::from_u32_lt(1, 2), Choice::TRUE);
428+
}
429+
430+
#[test]
431+
fn from_u32_nonzero() {
432+
assert_eq!(Choice::from_u32_nonzero(0), Choice::FALSE);
433+
assert_eq!(Choice::from_u32_nonzero(1), Choice::TRUE);
434+
assert_eq!(Choice::from_u32_nonzero(2), Choice::TRUE);
435+
}
436+
437+
#[test]
438+
fn from_u64_lsb() {
439+
assert_eq!(Choice::from_u64_lsb(0), Choice::FALSE);
440+
assert_eq!(Choice::from_u64_lsb(1), Choice::TRUE);
441+
}
442+
443+
#[test]
444+
fn from_u64_nonzero() {
445+
assert_eq!(Choice::from_u64_nonzero(0), Choice::FALSE);
446+
assert_eq!(Choice::from_u64_nonzero(1), Choice::TRUE);
447+
assert_eq!(Choice::from_u64_nonzero(2), Choice::TRUE);
448+
}
449+
450+
#[test]
451+
fn select_i64() {
452+
let a: i64 = 1;
453+
let b: i64 = 2;
454+
assert_eq!(Choice::TRUE.select_i64(a, b), b);
455+
assert_eq!(Choice::FALSE.select_i64(a, b), a);
456+
}
457+
458+
#[test]
459+
fn select_u32() {
460+
let a: u32 = 1;
461+
let b: u32 = 2;
462+
assert_eq!(Choice::TRUE.select_u32(a, b), b);
463+
assert_eq!(Choice::FALSE.select_u32(a, b), a);
464+
}
465+
466+
#[test]
467+
fn select_u64() {
468+
let a: u64 = 1;
469+
let b: u64 = 2;
470+
assert_eq!(Choice::TRUE.select_u64(a, b), b);
471+
assert_eq!(Choice::FALSE.select_u64(a, b), a);
472+
}
256473
}

ctutils/src/ct_option.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,46 @@ impl<T> CtOption<T> {
6262
/// Return the contained value, consuming the `self` value.
6363
///
6464
/// # Panics
65-
///
6665
/// In the event `self.is_some()` is [`Choice::FALSE`], panics with a custom panic message
6766
/// provided as the `msg` argument.
6867
#[inline]
68+
#[track_caller]
6969
pub fn expect(self, msg: &str) -> T {
7070
assert!(self.is_some().to_bool(), "{}", msg);
7171
self.value
7272
}
7373

74+
/// Return a copy of the contained value without consuming `self`.
75+
///
76+
/// # Panics
77+
/// In the event `self.is_some()` is [`Choice::FALSE`], panics with a custom panic message
78+
/// provided as the `msg` argument.
79+
// TODO(tarcieri): get rid of this when we can make `expect` a `const fn`
80+
// (needs `const_precise_live_drops`)
81+
#[inline]
82+
#[track_caller]
83+
pub const fn expect_copied(&self, msg: &str) -> T
84+
where
85+
T: Copy,
86+
{
87+
*self.expect_ref(msg)
88+
}
89+
90+
/// Borrow the contained value.
91+
///
92+
/// # Panics
93+
/// In the event `self.is_some()` is [`Choice::FALSE`], panics with a custom panic message
94+
/// provided as the `msg` argument.
95+
// TODO(tarcieri): get rid of this when we can make `expect` a `const fn`
96+
// (needs `const_precise_live_drops`)
97+
#[inline]
98+
#[track_caller]
99+
pub const fn expect_ref(&self, msg: &str) -> &T {
100+
// TODO(tarcieri): use `self.is_some().to_bool()` when MSRV is 1.86
101+
assert!(self.is_some.to_bool_vartime(), "{}", msg);
102+
&self.value
103+
}
104+
74105
/// Convert the [`CtOption`] wrapper into an [`Option`], depending on whether
75106
/// [`CtOption::is_some`] is a truthy or falsy [`Choice`].
76107
///
@@ -229,7 +260,6 @@ impl<T> CtOption<T> {
229260
/// of `Option`/`Result` to handle errors)
230261
///
231262
/// # Panics
232-
///
233263
/// In the event `self.is_some()` is [`Choice::FALSE`].
234264
#[inline]
235265
pub fn unwrap(self) -> T {

0 commit comments

Comments
 (0)