Skip to content

Commit 533ac35

Browse files
authored
ctutils: CtOption methods for crypto-bigint (#1274)
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 a `Default` impl for `CtOption`. Also adds `map!` and `unwrap_or!` macros to provide `const fn`-friendly combinator-like functionality.
1 parent 93870ce commit 533ac35

File tree

1 file changed

+166
-2
lines changed

1 file changed

+166
-2
lines changed

ctutils/src/ct_option.rs

Lines changed: 166 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,36 @@
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 `$mapper` function to convert from one type to another, e.g.
8+
///
9+
/// ```ignore
10+
/// const fn mapper(value: T) -> U
11+
/// ```
12+
#[macro_export]
13+
macro_rules! map {
14+
($opt:expr, $mapper:path) => {{ $crate::CtOption::new($mapper($opt.to_inner_unchecked()), $opt.is_some()) }};
15+
}
16+
17+
/// Helper macro for providing behavior like the [`CtOption::unwrap_or`] combinator that works in
18+
/// `const fn` contexts.
19+
///
20+
/// Requires a provided selector function `$select` to perform constant-time selection which takes
21+
/// two `T` values by reference along with a [`Choice`], returning the first `T` for
22+
/// [`Choice::FALSE`], and the second for [`Choice::TRUE`], e.g.:
23+
///
24+
/// ```ignore
25+
/// const fn ct_select(a: &T, b: &T, condition: Choice) -> T
26+
/// ```
27+
#[macro_export]
28+
macro_rules! unwrap_or {
29+
($opt:expr, $default:expr, $select:path) => {
30+
$select(&$default, $opt.as_inner_unchecked(), $opt.is_some())
31+
};
32+
}
33+
434
/// Equivalent of [`Option`] but predicated on a [`Choice`] with combinators that allow for
535
/// constant-time operations which always perform the same sequence of instructions regardless of
636
/// the value of `is_some`.
@@ -23,6 +53,21 @@ impl<T> CtOption<T> {
2353
Self { value, is_some }
2454
}
2555

56+
/// Construct a new [`CtOption`] where `self.is_some()` is [`Choice::TRUE`].
57+
#[inline]
58+
pub const fn some(value: T) -> CtOption<T> {
59+
Self::new(value, Choice::TRUE)
60+
}
61+
62+
/// Construct a new [`CtOption`] with the [`Default`] value, and where `self.is_some()` is
63+
/// [`Choice::FALSE`].
64+
pub fn none() -> CtOption<T>
65+
where
66+
T: Default,
67+
{
68+
Self::new(Default::default(), Choice::FALSE)
69+
}
70+
2671
/// Convert from a `&mut CtOption<T>` to `CtOption<&mut T>`.
2772
#[inline]
2873
pub const fn as_mut(&mut self) -> CtOption<&mut T> {
@@ -80,7 +125,7 @@ impl<T> CtOption<T> {
80125
// (needs `const_precise_live_drops`)
81126
#[inline]
82127
#[track_caller]
83-
pub const fn expect_copied(&self, msg: &str) -> T
128+
pub const fn expect_copied(self, msg: &str) -> T
84129
where
85130
T: Copy,
86131
{
@@ -99,7 +144,7 @@ impl<T> CtOption<T> {
99144
pub const fn expect_ref(&self, msg: &str) -> &T {
100145
// TODO(tarcieri): use `self.is_some().to_bool()` when MSRV is 1.86
101146
assert!(self.is_some.to_bool_vartime(), "{}", msg);
102-
&self.value
147+
self.as_inner_unchecked()
103148
}
104149

105150
/// Convert the [`CtOption`] wrapper into an [`Option`], depending on whether
@@ -124,6 +169,29 @@ impl<T> CtOption<T> {
124169
}
125170
}
126171

172+
/// Convert the [`CtOption`] wrapper into an [`Option`] in a `const fn`-friendly manner.
173+
///
174+
/// This is the equivalent of [`CtOption::into_option`] but is `const fn`-friendly by only
175+
/// allowing `Copy` types which are implicitly `!Drop` and don't run into problems with
176+
/// `const fn` and destructors.
177+
///
178+
/// <div class="warning">
179+
/// This implementation doesn't intend to be constant-time nor try to protect the leakage of the
180+
/// `T` value since the [`Option`] will do it anyway.
181+
/// </div>
182+
#[inline]
183+
pub const fn into_option_copied(self) -> Option<T>
184+
where
185+
T: Copy,
186+
{
187+
// TODO(tarcieri): use `self.is_some().to_bool()` when MSRV is 1.86
188+
if self.is_some.to_bool_vartime() {
189+
Some(self.value)
190+
} else {
191+
None
192+
}
193+
}
194+
127195
/// Returns [`Choice::TRUE`] if the option is the equivalent of a `Some`.
128196
#[inline]
129197
#[must_use]
@@ -164,6 +232,24 @@ impl<T> CtOption<T> {
164232
ret
165233
}
166234

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

267+
/// Apply an additional [`Choice`] requirement to `is_some`.
268+
#[inline]
269+
pub const fn filter_by(mut self, is_some: Choice) -> Self {
270+
self.is_some = self.is_some.and(is_some);
271+
self
272+
}
273+
181274
/// Maps a `CtOption<T>` to a `CtOption<U>` by unconditionally applying a function to the
182275
/// contained `value`, but returning a new option value which inherits `self.is_some()`.
183276
#[inline]
@@ -247,6 +340,27 @@ impl<T> CtOption<T> {
247340
}
248341
}
249342

343+
/// Obtain a copy of the inner value without first checking that `self.is_some()` is
344+
/// [`Choice::TRUE`].
345+
///
346+
/// This method is primarily intended for use in `const fn` scenarios where it's not yet
347+
/// possible to use the safe combinator methods, and uses a `Copy` bound to avoid issues with
348+
/// `const fn` destructors.
349+
///
350+
/// <div class="warning">
351+
/// <b>Use with care!</b>
352+
///
353+
/// This method does not ensure the `value` is actually valid. Callers of this method should
354+
/// take great care to ensure that `self.is_some()` is checked elsewhere.
355+
/// </div>
356+
#[inline]
357+
pub const fn to_inner_unchecked(self) -> T
358+
where
359+
T: Copy,
360+
{
361+
self.value
362+
}
363+
250364
/// Return the contained value, consuming the `self` value.
251365
///
252366
/// Use of this function is discouraged due to panic potential. Instead, prefer non-panicking
@@ -400,6 +514,12 @@ impl<T: CtSelect> CtSelect for CtOption<T> {
400514
}
401515
}
402516

517+
impl<T: Default> Default for CtOption<T> {
518+
fn default() -> Self {
519+
Self::none()
520+
}
521+
}
522+
403523
/// Convert the [`CtOption`] wrapper into an [`Option`], depending on whether
404524
/// [`CtOption::is_some`] is a truthy or falsy [`Choice`].
405525
///
@@ -460,6 +580,26 @@ mod tests {
460580
#[derive(Debug, Eq, PartialEq)]
461581
struct Error;
462582

583+
#[test]
584+
fn map_macro() {
585+
assert!(map!(NONE, u16::from).is_none().to_bool());
586+
assert_eq!(map!(SOME, u16::from).unwrap(), VALUE as u16);
587+
}
588+
589+
#[test]
590+
fn unwrap_or_macro() {
591+
// Don't actually use this! It's just a test function implemented in variable-time
592+
const fn select_vartime(a: &u8, b: &u8, choice: Choice) -> u8 {
593+
if choice.to_bool_vartime() { *b } else { *a }
594+
}
595+
596+
assert_eq!(
597+
unwrap_or!(NONE, OTHER.unwrap(), select_vartime),
598+
OTHER.unwrap()
599+
);
600+
assert_eq!(unwrap_or!(SOME, OTHER.unwrap(), select_vartime), VALUE);
601+
}
602+
463603
#[test]
464604
fn ct_eq() {
465605
assert!(NONE.ct_eq(&NONE).to_bool());
@@ -477,6 +617,11 @@ mod tests {
477617
assert!(SOME.ct_select(&NONE, Choice::TRUE).is_none().to_bool());
478618
}
479619

620+
#[test]
621+
fn default() {
622+
assert!(NONE.ct_eq(&CtOption::default()).to_bool());
623+
}
624+
480625
#[test]
481626
fn expect_some() {
482627
assert_eq!(SOME.expect("should succeed"), VALUE);
@@ -494,6 +639,12 @@ mod tests {
494639
assert_eq!(NONE.into_option(), None)
495640
}
496641

642+
#[test]
643+
fn into_option_copied() {
644+
assert_eq!(SOME.into_option_copied(), Some(VALUE));
645+
assert_eq!(NONE.into_option_copied(), None)
646+
}
647+
497648
#[test]
498649
fn is_some() {
499650
assert!(SOME.is_some().to_bool());
@@ -537,6 +688,14 @@ mod tests {
537688
assert_eq!(ret.unwrap(), VALUE);
538689
}
539690

691+
#[test]
692+
fn filter_by() {
693+
assert!(NONE.filter_by(Choice::FALSE).is_none().to_bool());
694+
assert!(NONE.filter_by(Choice::TRUE).is_none().to_bool());
695+
assert!(SOME.filter_by(Choice::FALSE).ct_eq(&NONE).to_bool());
696+
assert_eq!(SOME.filter_by(Choice::TRUE).unwrap(), VALUE);
697+
}
698+
540699
#[test]
541700
fn map() {
542701
assert!(NONE.map(|value| value + 1).ct_eq(&NONE).to_bool());
@@ -576,6 +735,11 @@ mod tests {
576735
assert!(SOME.or(OTHER).ct_eq(&SOME).to_bool());
577736
}
578737

738+
#[test]
739+
fn some() {
740+
assert!(CtOption::some(VALUE).ct_eq(&SOME).to_bool());
741+
}
742+
579743
#[test]
580744
fn unwrap_some() {
581745
assert_eq!(SOME.unwrap(), VALUE);

0 commit comments

Comments
 (0)