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
2 changes: 2 additions & 0 deletions ctutils/src/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ where
/// Generic implementation of constant-time equality testing for arrays which works with any type
/// which impls `CtEq`. Useful in the event there isn't a `CtEq` impl for `[T; N]`.
#[inline]
#[must_use]
pub fn ct_eq<T, const N: usize>(a: &[T; N], b: &[T; N]) -> Choice
where
T: CtEq,
Expand All @@ -42,6 +43,7 @@ where
/// Unfortunately we can't provide this as a trait impl without specialization, since it would
/// overlap with the optimized type-specific impls we provide.
#[inline]
#[must_use]
pub fn ct_select<T, const N: usize>(a: &[T; N], b: &[T; N], choice: Choice) -> [T; N]
where
T: CtSelect,
Expand Down
6 changes: 6 additions & 0 deletions ctutils/src/choice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,28 @@ impl Choice {

/// Apply an `and` conditional to the given [`Choice`]s.
#[inline]
#[must_use]
pub const fn and(self, rhs: Choice) -> Choice {
Self(self.0 & rhs.0)
}

/// Apply an `or` conditional to the given [`Choice`]s.
#[inline]
#[must_use]
pub const fn or(self, rhs: Choice) -> Choice {
Self(self.0 | rhs.0)
}

/// Apply an `xor` conditional to the given [`Choice`]s.
#[inline]
#[must_use]
pub const fn xor(self, rhs: Choice) -> Choice {
Self(self.0 ^ rhs.0)
}

/// Compute the boolean inverse of `self`.
#[inline]
#[must_use]
pub const fn not(self) -> Choice {
// NOTE: assumes self.0 is `0` or `1` as checked in constructor
Self(self.0 ^ 1)
Expand All @@ -80,12 +84,14 @@ impl Choice {

/// `const fn` equality operation.
#[inline]
#[must_use]
pub const fn eq(self, other: Self) -> Self {
Self::ne(self, other).not()
}

/// `const fn` not equal operation.
#[inline]
#[must_use]
pub const fn ne(self, other: Self) -> Self {
Self::xor(self, other)
}
Expand Down
31 changes: 29 additions & 2 deletions ctutils/src/ct_option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,22 @@ impl<T> CtOption<T> {
/// Construct a new [`CtOption`], with a [`Choice`] parameter `is_some` as a stand-in for
/// `Some` or `None` enum variants of a typical [`Option`] type.
#[inline]
#[must_use]
pub const fn new(value: T, is_some: Choice) -> CtOption<T> {
Self { value, is_some }
}

/// Construct a new [`CtOption`] where `self.is_some()` is [`Choice::TRUE`].
#[inline]
#[must_use]
pub const fn some(value: T) -> CtOption<T> {
Self::new(value, Choice::TRUE)
}

/// Construct a new [`CtOption`] with the [`Default`] value, and where `self.is_some()` is
/// [`Choice::FALSE`].
#[inline]
#[must_use]
pub fn none() -> CtOption<T>
where
T: Default,
Expand All @@ -70,6 +74,7 @@ impl<T> CtOption<T> {

/// Convert from a `&mut CtOption<T>` to `CtOption<&mut T>`.
#[inline]
#[must_use]
pub const fn as_mut(&mut self) -> CtOption<&mut T> {
CtOption {
value: &mut self.value,
Expand All @@ -79,6 +84,7 @@ impl<T> CtOption<T> {

/// Convert from a `&CtOption<T>` to `CtOption<&T>`.
#[inline]
#[must_use]
pub const fn as_ref(&self) -> CtOption<&T> {
CtOption {
value: &self.value,
Expand All @@ -88,6 +94,8 @@ impl<T> CtOption<T> {

/// Convert from `CtOption<T>` (or `&CtOption<T>`) to `CtOption<&T::Target>`, for types which
/// impl the [`Deref`] trait.
#[inline]
#[must_use]
pub fn as_deref(&self) -> CtOption<&T::Target>
where
T: Deref,
Expand All @@ -97,6 +105,8 @@ impl<T> CtOption<T> {

/// Convert from `CtOption<T>` (or `&mut CtOption<T>`) to `CtOption<&mut T::Target>`, for types
/// which impl the [`DerefMut`] trait.
#[inline]
#[must_use]
pub fn as_deref_mut(&mut self) -> CtOption<&mut T::Target>
where
T: DerefMut,
Expand All @@ -110,6 +120,7 @@ impl<T> CtOption<T> {
/// In the event `self.is_some()` is [`Choice::FALSE`], panics with a custom panic message
/// provided as the `msg` argument.
#[inline]
#[must_use]
#[track_caller]
pub fn expect(self, msg: &str) -> T {
assert!(self.is_some().to_bool(), "{}", msg);
Expand All @@ -127,6 +138,7 @@ impl<T> CtOption<T> {
// TODO(tarcieri): get rid of this when we can make `expect` a `const fn`
// (needs `const_precise_live_drops`)
#[inline]
#[must_use]
#[track_caller]
pub const fn expect_copied(self, msg: &str) -> T
where
Expand All @@ -143,6 +155,7 @@ impl<T> CtOption<T> {
// TODO(tarcieri): get rid of this when we can make `expect` a `const fn`
// (needs `const_precise_live_drops`)
#[inline]
#[must_use]
#[track_caller]
pub const fn expect_ref(&self, msg: &str) -> &T {
// TODO(tarcieri): use `self.is_some().to_bool()` when MSRV is 1.86
Expand Down Expand Up @@ -234,6 +247,7 @@ impl<T> CtOption<T> {
/// Returns `optb` if `self.is_some()` is [`Choice::TRUE`], otherwise returns a [`CtOption`]
/// where `self.is_some()` is [`Choice::FALSE`].
#[inline]
#[must_use]
pub fn and<U>(self, mut optb: CtOption<U>) -> CtOption<U> {
optb.is_some &= self.is_some;
optb
Expand All @@ -248,6 +262,7 @@ impl<T> CtOption<T> {
/// (e.g. if the [`CtOption`] was constructed with a supplied placeholder value and
/// [`Choice::FALSE`], the placeholder value will be provided).
#[inline]
#[must_use]
pub fn and_then<U, F>(self, f: F) -> CtOption<U>
where
F: FnOnce(T) -> CtOption<U>,
Expand All @@ -271,6 +286,7 @@ impl<T> CtOption<T> {
/// take great care to ensure that `self.is_some()` is checked elsewhere.
/// </div>
#[inline]
#[must_use]
pub const fn as_inner_unchecked(&self) -> &T {
&self.value
}
Expand All @@ -281,6 +297,7 @@ impl<T> CtOption<T> {
/// It updates it to be [`Choice::FALSE`] in the event the returned choice is also false.
/// If it was [`Choice::FALSE`] to begin with, it will unconditionally remain that way.
#[inline]
#[must_use]
pub fn filter<P>(mut self, predicate: P) -> Self
where
P: FnOnce(&T) -> Choice,
Expand All @@ -291,6 +308,7 @@ impl<T> CtOption<T> {

/// Apply an additional [`Choice`] requirement to `is_some`.
#[inline]
#[must_use]
pub const fn filter_by(mut self, is_some: Choice) -> Self {
self.is_some = self.is_some.and(is_some);
self
Expand All @@ -299,6 +317,7 @@ impl<T> CtOption<T> {
/// Maps a `CtOption<T>` to a `CtOption<U>` by unconditionally applying a function to the
/// contained `value`, but returning a new option value which inherits `self.is_some()`.
#[inline]
#[must_use]
pub fn map<U, F>(self, f: F) -> CtOption<U>
where
F: FnOnce(T) -> U,
Expand All @@ -322,6 +341,7 @@ impl<T> CtOption<T> {
/// `U::default()` using the [`Default`] trait, and returning it in the event `self.is_some()`
/// is [`Choice::FALSE`].
#[inline]
#[must_use]
pub fn map_or_default<U, F>(self, f: F) -> U
where
U: CtSelect + Default,
Expand Down Expand Up @@ -364,6 +384,7 @@ impl<T> CtOption<T> {

/// Returns `self` if `self.is_some()` is [`Choice::TRUE`], otherwise returns `optb`.
#[inline]
#[must_use]
pub fn or(self, optb: CtOption<T>) -> CtOption<T>
where
T: CtSelect,
Expand All @@ -388,6 +409,7 @@ impl<T> CtOption<T> {
/// take great care to ensure that `self.is_some()` is checked elsewhere.
/// </div>
#[inline]
#[must_use]
pub const fn to_inner_unchecked(self) -> T
where
T: Copy,
Expand All @@ -410,6 +432,8 @@ impl<T> CtOption<T> {
/// # Panics
/// In the event `self.is_some()` is [`Choice::FALSE`].
#[inline]
#[must_use]
#[track_caller]
pub fn unwrap(self) -> T {
assert!(
self.is_some.to_bool(),
Expand All @@ -421,6 +445,7 @@ impl<T> CtOption<T> {
/// Return the contained value in the event `self.is_some()` is [`Choice::TRUE`], or if not,
/// uses a provided default.
#[inline]
#[must_use]
pub fn unwrap_or(self, default: T) -> T
where
T: CtSelect,
Expand All @@ -432,6 +457,7 @@ impl<T> CtOption<T> {
/// the contained value if `self.is_some()` is [`Choice::TRUE`], or if it's [`Choice::FALSE`]
/// returns the previously computed default.
#[inline]
#[must_use]
pub fn unwrap_or_default(self) -> T
where
T: CtSelect + Default,
Expand All @@ -443,6 +469,7 @@ impl<T> CtOption<T> {
/// the event exactly one of them has `self.is_some()` set to [`Choice::TRUE`], or else returns
/// a [`CtOption`] with `self.is_some()` set to [`Choice::FALSE`].
#[inline]
#[must_use]
pub fn xor(self, optb: CtOption<T>) -> CtOption<T>
where
T: CtSelect,
Expand Down Expand Up @@ -694,7 +721,7 @@ mod tests {
#[test]
#[should_panic]
fn expect_none() {
NONE.expect("should panic");
let _ = NONE.expect("should panic");
}

#[test]
Expand Down Expand Up @@ -834,7 +861,7 @@ mod tests {
#[test]
#[should_panic]
fn unwrap_none() {
NONE.unwrap();
let _ = NONE.unwrap();
}

#[test]
Expand Down
1 change: 1 addition & 0 deletions ctutils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
clippy::mod_module_files,
clippy::panic,
clippy::panic_in_result_fn,
clippy::return_self_not_must_use,
clippy::std_instead_of_alloc,
clippy::std_instead_of_core,
clippy::undocumented_unsafe_blocks,
Expand Down
1 change: 1 addition & 0 deletions ctutils/src/slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ where
target_pointer_width = "64"
))]
#[inline]
#[must_use]
pub fn ct_eq<T>(a: &[T], b: &[T]) -> Choice
where
T: CtEq,
Expand Down
2 changes: 2 additions & 0 deletions ctutils/src/traits/ct_eq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ where
Rhs: ?Sized,
{
/// Determine if `self` is equal to `other` in constant-time.
#[must_use]
fn ct_eq(&self, other: &Rhs) -> Choice;

/// Determine if `self` is NOT equal to `other` in constant-time.
#[must_use]
fn ct_ne(&self, other: &Rhs) -> Choice {
!self.ct_eq(other)
}
Expand Down
1 change: 1 addition & 0 deletions ctutils/src/traits/ct_find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub trait CtFind<T: CtAssign> {
///
/// The first item where `predicate` returns [`Choice::TRUE`] is selected, or the [`CtOption`]
/// equivalent of `None` is returned if the `predicate` returns [`Choice::FALSE`] for all items.
#[must_use]
fn ct_find<P>(&self, predicate: P) -> CtOption<T>
where
P: Fn(&T) -> Choice;
Expand Down
1 change: 1 addition & 0 deletions ctutils/src/traits/ct_gt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use core::{
/// Constant time greater than.
pub trait CtGt {
/// Compute whether `self > other` in constant time.
#[must_use]
fn ct_gt(&self, other: &Self) -> Choice;
}

Expand Down
1 change: 1 addition & 0 deletions ctutils/src/traits/ct_lookup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub trait CtLookup<Idx> {

/// Attempt to retrieve the item at the given `index`, either returning it or the [`CtOption`]
/// equivalent of [`None`] if the `index` was out-of-bounds.
#[must_use]
fn ct_lookup(&self, index: Idx) -> CtOption<Self::Output>;
}

Expand Down
1 change: 1 addition & 0 deletions ctutils/src/traits/ct_lt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use core::{
/// Constant time less than.
pub trait CtLt {
/// Compute whether `self < other` in constant time.
#[must_use]
fn ct_lt(&self, other: &Self) -> Choice;
}

Expand Down
1 change: 1 addition & 0 deletions ctutils/src/traits/ct_neg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use core::num::{
pub trait CtNeg: Sized {
/// Conditionally negate `self`, returning `-self` if `choice` is [`Choice::TRUE`], or `self`
/// otherwise.
#[must_use]
fn ct_neg(&self, choice: Choice) -> Self;

/// Conditionally negate `self` in-place, replacing it with `-self` if `choice` is
Expand Down
1 change: 1 addition & 0 deletions ctutils/src/traits/ct_select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub trait CtSelect: Sized {
/// # Returns
/// - `self` if `choice` is [`Choice::FALSE`].
/// - `other` if `choice` is [`Choice::TRUE`].
#[must_use]
fn ct_select(&self, other: &Self, choice: Choice) -> Self;

/// Conditionally swap `self` and `other` if `choice` is [`Choice::TRUE`].
Expand Down