Skip to content

Commit 5d2fc4d

Browse files
committed
[WIP] 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. To make everything work, at least for now, a `word` feature has been added to `ctutils` in order to have methods on `Choice` which operate on `Word` and `WideWord` that use the same definition as `crypto-bigint`.
1 parent f64379d commit 5d2fc4d

File tree

5 files changed

+386
-3
lines changed

5 files changed

+386
-3
lines changed

ctutils/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,9 @@ cmov = "0.4"
2222
# optional dependencies
2323
subtle = { version = "2", optional = true, default-features = false }
2424

25+
[features]
26+
subtle = ["dep:subtle"]
27+
word = []
28+
2529
[package.metadata.docs.rs]
2630
all-features = true

ctutils/src/choice.rs

Lines changed: 166 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,128 @@ 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 `x` if `self` is truthy, otherwise return 0.
185+
#[inline]
186+
pub const fn if_true_u32(self, x: u32) -> u32 {
187+
self.select_u32(0, x)
188+
}
189+
190+
/// Return `b` if `self` is truthy, otherwise return `a`.
191+
#[inline]
192+
pub const fn select_i64(self, a: i64, b: i64) -> i64 {
193+
self.select_u64(a as u64, b as u64) as i64
194+
}
195+
196+
/// Return `b` if `self` is truthy, otherwise return `a`.
197+
#[inline]
198+
pub const fn select_u32(self, a: u32, b: u32) -> u32 {
199+
a ^ (self.to_u32_mask() & (a ^ b))
200+
}
201+
202+
/// Return `b` if `self` is truthy, otherwise return `a`.
203+
#[inline]
204+
pub const fn select_u64(self, a: u64, b: u64) -> u64 {
205+
a ^ (self.to_u64_mask() & (a ^ b))
206+
}
207+
208+
/// Create a `u32` bitmask.
209+
///
210+
/// # Returns
211+
/// - `0` for `Choice::FALSE`
212+
/// - `u32::MAX` for `Choice::TRUE`
213+
#[inline]
214+
#[allow(trivial_numeric_casts)]
215+
pub(crate) const fn to_u32_mask(self) -> u32 {
216+
(self.0 as u32 & 1).wrapping_neg()
217+
}
218+
219+
/// Create a `u64` bitmask.
220+
///
221+
/// # Returns
222+
/// - `0` for `Choice::FALSE`
223+
/// - `u64::MAX` for `Choice::TRUE`
224+
#[inline]
225+
#[allow(trivial_numeric_casts)]
226+
pub(crate) const fn to_u64_mask(self) -> u64 {
227+
(self.0 as u64 & 1).wrapping_neg()
228+
}
86229
}
87230

88231
impl BitAnd for Choice {
@@ -253,4 +396,26 @@ mod tests {
253396
assert_eq!(Choice::new(0).not().to_u8(), 1);
254397
assert_eq!(Choice::new(1).not().to_u8(), 0);
255398
}
399+
400+
#[test]
401+
fn from_u64_lsb() {
402+
assert_eq!(Choice::from_u64_lsb(0), Choice::FALSE);
403+
assert_eq!(Choice::from_u64_lsb(1), Choice::TRUE);
404+
}
405+
406+
#[test]
407+
fn select_u32() {
408+
let a: u32 = 1;
409+
let b: u32 = 2;
410+
assert_eq!(Choice::TRUE.select_u32(a, b), b);
411+
assert_eq!(Choice::FALSE.select_u32(a, b), a);
412+
}
413+
414+
#[test]
415+
fn select_u64() {
416+
let a: u64 = 1;
417+
let b: u64 = 2;
418+
assert_eq!(Choice::TRUE.select_u64(a, b), b);
419+
assert_eq!(Choice::FALSE.select_u64(a, b), a);
420+
}
256421
}

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 {

ctutils/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,11 @@
7878
mod choice;
7979
mod ct_option;
8080
mod traits;
81+
#[cfg(feature = "word")]
82+
mod word;
8183

8284
pub use choice::Choice;
8385
pub use ct_option::CtOption;
8486
pub use traits::{ct_eq::CtEq, ct_gt::CtGt, ct_lt::CtLt, ct_select::CtSelect};
87+
#[cfg(feature = "word")]
88+
pub use word::{WideWord, Word};

0 commit comments

Comments
 (0)