Skip to content

Commit 6fc9a27

Browse files
committed
ctutils: CtOption methods for crypto-bigint
Adds the methods needed to replace `ConstCtOption` in `crypto-bigint` with `ctutils::CtOption`: - `some` - `none` - `into_option_copied` - `and_choice` - `as_inner_unchecked` - `to_inner_unchecked` Also adds `map!` and `unwrap_or!` macros to provide `const fn`-friendly combinator-like functionality.
1 parent 93870ce commit 6fc9a27

1 file changed

Lines changed: 132 additions & 2 deletions

File tree

ctutils/src/ct_option.rs

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
11
use crate::{Choice, CtEq, CtSelect};
22
use core::ops::{Deref, DerefMut};
33

4+
/// Helper macro for providing behavior like the [`CtOption::map`] combinator that works in
5+
/// `const fn` contexts.
6+
///
7+
/// Requires a provided mapping function to convert from one type to another.
8+
#[macro_export]
9+
macro_rules! map {
10+
($opt:expr, $mapper:path) => {{ $crate::CtOption::new($mapper($opt.to_inner_unchecked()), $opt.is_some()) }};
11+
}
12+
13+
/// Helper macro for providing behavior like the [`CtOption::`unwrap_or`] combinator that works in
14+
/// `const fn` contexts.
15+
///
16+
/// Requires a provided selector function to do the constant-time selection.
17+
#[macro_export]
18+
macro_rules! unwrap_or {
19+
($opt:expr, $default:expr, $select:path) => {
20+
$select(&$default, $opt.as_inner_unchecked(), $opt.is_some())
21+
};
22+
}
23+
424
/// Equivalent of [`Option`] but predicated on a [`Choice`] with combinators that allow for
525
/// constant-time operations which always perform the same sequence of instructions regardless of
626
/// the value of `is_some`.
@@ -23,6 +43,21 @@ impl<T> CtOption<T> {
2343
Self { value, is_some }
2444
}
2545

46+
/// Construct a new [`CtOption`] where `self.is_some()` is [`Choice::TRUE`].
47+
#[inline]
48+
pub const fn some(value: T) -> CtOption<T> {
49+
Self::new(value, Choice::TRUE)
50+
}
51+
52+
/// Construct a new [`CtOption`] with the [`Default`] value, and where `self.is_some()` is
53+
/// [`Choice::FALSE`].
54+
pub fn none() -> CtOption<T>
55+
where
56+
T: Default,
57+
{
58+
Self::new(Default::default(), Choice::FALSE)
59+
}
60+
2661
/// Convert from a `&mut CtOption<T>` to `CtOption<&mut T>`.
2762
#[inline]
2863
pub const fn as_mut(&mut self) -> CtOption<&mut T> {
@@ -80,7 +115,7 @@ impl<T> CtOption<T> {
80115
// (needs `const_precise_live_drops`)
81116
#[inline]
82117
#[track_caller]
83-
pub const fn expect_copied(&self, msg: &str) -> T
118+
pub const fn expect_copied(self, msg: &str) -> T
84119
where
85120
T: Copy,
86121
{
@@ -99,7 +134,7 @@ impl<T> CtOption<T> {
99134
pub const fn expect_ref(&self, msg: &str) -> &T {
100135
// TODO(tarcieri): use `self.is_some().to_bool()` when MSRV is 1.86
101136
assert!(self.is_some.to_bool_vartime(), "{}", msg);
102-
&self.value
137+
self.as_inner_unchecked()
103138
}
104139

105140
/// Convert the [`CtOption`] wrapper into an [`Option`], depending on whether
@@ -124,6 +159,29 @@ impl<T> CtOption<T> {
124159
}
125160
}
126161

162+
/// Convert the [`CtOption`] wrapper into an [`Option`] in a `const fn`-friendly manner.
163+
///
164+
/// This is the equivalent of [`CtOption::into_option`] but is `const fn`-friendly by only
165+
/// allowing `Copy` types which are implicitly `!Drop` and don't run into problems with
166+
/// `const fn` and destructors.
167+
///
168+
/// <div class="warning">
169+
/// This implementation doesn't intend to be constant-time nor try to protect the leakage of the
170+
/// `T` value since the [`Option`] will do it anyway.
171+
/// </div>
172+
#[inline]
173+
pub const fn into_option_copied(self) -> Option<T>
174+
where
175+
T: Copy,
176+
{
177+
// TODO(tarcieri): use `self.is_some().to_bool()` when MSRV is 1.86
178+
if self.is_some.to_bool_vartime() {
179+
Some(self.value)
180+
} else {
181+
None
182+
}
183+
}
184+
127185
/// Returns [`Choice::TRUE`] if the option is the equivalent of a `Some`.
128186
#[inline]
129187
#[must_use]
@@ -146,6 +204,13 @@ impl<T> CtOption<T> {
146204
optb
147205
}
148206

207+
/// Apply an additional [`Choice`] requirement to `is_some`.
208+
#[inline]
209+
pub const fn and_choice(mut self, is_some: Choice) -> Self {
210+
self.is_some = self.is_some.and(is_some);
211+
self
212+
}
213+
149214
/// Calls the provided callback with the wrapped inner value, returning the resulting
150215
/// [`CtOption`] value in the event that `self.is_some()` is [`Choice::TRUE`], or if not
151216
/// returns a [`CtOption`] with `self.is_none()`.
@@ -164,6 +229,24 @@ impl<T> CtOption<T> {
164229
ret
165230
}
166231

232+
/// Obtain a reference to the inner value without first checking that `self.is_some()` is
233+
/// [`Choice::TRUE`].
234+
///
235+
/// This method is primarily intended for use in `const fn` scenarios where it's not yet
236+
/// possible to use the safe combinator methods, and returns a reference to avoid issues with
237+
/// `const fn` destructors.
238+
///
239+
/// <div class="warning">
240+
/// <b>Use with care!</b>
241+
///
242+
/// This method does not ensure the `value` is actually valid. Callers of this method should
243+
/// take great care to ensure that `self.is_some()` is checked elsewhere.
244+
/// </div>
245+
#[inline]
246+
pub const fn as_inner_unchecked(&self) -> &T {
247+
&self.value
248+
}
249+
167250
/// Calls the provided callback with the wrapped inner value, which computes a [`Choice`],
168251
/// and updates `self.is_some()`.
169252
///
@@ -247,6 +330,27 @@ impl<T> CtOption<T> {
247330
}
248331
}
249332

333+
/// Obtain a copy of the inner value without first checking that `self.is_some()` is
334+
/// [`Choice::TRUE`].
335+
///
336+
/// This method is primarily intended for use in `const fn` scenarios where it's not yet
337+
/// possible to use the safe combinator methods, and uses a `Copy` bound to avoid issues with
338+
/// `const fn` destructors.
339+
///
340+
/// <div class="warning">
341+
/// <b>Use with care!</b>
342+
///
343+
/// This method does not ensure the `value` is actually valid. Callers of this method should
344+
/// take great care to ensure that `self.is_some()` is checked elsewhere.
345+
/// </div>
346+
#[inline]
347+
pub const fn to_inner_unchecked(self) -> T
348+
where
349+
T: Copy,
350+
{
351+
self.value
352+
}
353+
250354
/// Return the contained value, consuming the `self` value.
251355
///
252356
/// Use of this function is discouraged due to panic potential. Instead, prefer non-panicking
@@ -400,6 +504,12 @@ impl<T: CtSelect> CtSelect for CtOption<T> {
400504
}
401505
}
402506

507+
impl<T: Default> Default for CtOption<T> {
508+
fn default() -> Self {
509+
Self::none()
510+
}
511+
}
512+
403513
/// Convert the [`CtOption`] wrapper into an [`Option`], depending on whether
404514
/// [`CtOption::is_some`] is a truthy or falsy [`Choice`].
405515
///
@@ -460,6 +570,26 @@ mod tests {
460570
#[derive(Debug, Eq, PartialEq)]
461571
struct Error;
462572

573+
#[test]
574+
fn map_macro() {
575+
assert!(map!(NONE, u16::from).is_none().to_bool());
576+
assert_eq!(map!(SOME, u16::from).unwrap(), VALUE as u16);
577+
}
578+
579+
#[test]
580+
fn unwrap_or_macro() {
581+
// Don't actually use this! It's just a test function implemented in variable-time
582+
const fn select_vartime(a: &u8, b: &u8, choice: Choice) -> u8 {
583+
if choice.to_bool_vartime() { *b } else { *a }
584+
}
585+
586+
assert_eq!(
587+
unwrap_or!(NONE, OTHER.unwrap(), select_vartime),
588+
OTHER.unwrap()
589+
);
590+
assert_eq!(unwrap_or!(SOME, OTHER.unwrap(), select_vartime), VALUE);
591+
}
592+
463593
#[test]
464594
fn ct_eq() {
465595
assert!(NONE.ct_eq(&NONE).to_bool());

0 commit comments

Comments
 (0)