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: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cmov/src/slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const _: () = assert!(size_of::<usize>() <= WORD_SIZE, "unexpected word size");
/// - if slices have unequal lengths
impl Cmov for [u8] {
#[inline]
#[track_caller]
fn cmovnz(&mut self, value: &Self, condition: Condition) {
assert_eq!(
self.len(),
Expand Down
4 changes: 2 additions & 2 deletions ctutils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Constant-time utility library with selection and equality testing support target
applications. Supports `const fn` where appropriate. Built on the `cmov` crate which provides
architecture-specific predication intrinsics. Heavily inspired by the `subtle` crate.
"""
version = "0.3.2"
version = "0.4.0-pre"
authors = ["RustCrypto Developers"]
license = "Apache-2.0 OR MIT"
homepage = "https://github.com/RustCrypto/utils/tree/master/ctselect"
Expand All @@ -17,7 +17,7 @@ edition = "2024"
rust-version = "1.85"

[dependencies]
cmov = "=0.5.0-pre.0"
cmov = "0.5.0-pre.0"

# optional dependencies
subtle = { version = "2", optional = true, default-features = false }
Expand Down
65 changes: 52 additions & 13 deletions ctutils/src/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,23 @@ use crate::Choice;
use cmov::{Cmov, CmovEq};

#[cfg(doc)]
use crate::{CtEq, CtSelect};
use crate::{CtAssign, CtEq, CtSelect};

/// [`CtAssign`]-like trait impl'd for `[u8]` and `[u8; N]` providing optimized implementations
/// which perform better than the generic impl of [`CtAssign`] for `[T]` and `[T; N]`
/// where `T = u8`.
///
/// Ideally we would use [specialization] to provide more specific impls of these traits for these
/// types, but it's unstable and unlikely to be stabilized soon.
///
/// [specialization]: https://rust-lang.github.io/rfcs/1210-impl-specialization.html
pub trait BytesCtAssign: sealed::Sealed {
/// Conditionally assign `other` to `self` if `choice` is [`Choice::TRUE`].
fn bytes_ct_assign(&mut self, other: &Self, choice: Choice);
}

/// [`CtEq`]-like trait impl'd for `[u8]` and `[u8; N]` providing optimized implementations which
/// perform than the generic impl of [`CtEq`] for `[T; N]` where `T = u8`.
/// perform better than the generic impl of [`CtEq`] for `[T; N]` where `T = u8`.
///
/// Ideally we would use [specialization] to provide more specific impls of these traits for these
/// types, but it's unstable and unlikely to be stabilized soon.
Expand All @@ -33,16 +46,13 @@ pub trait BytesCtEq<Rhs: ?Sized = Self>: sealed::Sealed {
}

/// [`CtSelect`]-like trait impl'd for `[u8]` and `[u8; N]` providing optimized implementations
/// which perform than the generic impl of [`CtSelect`] for `[T; N]` where `T = u8`.
/// which perform better than the generic impl of [`CtSelect`] for `[T; N]` where `T = u8`.
///
/// Ideally we would use [specialization] to provide more specific impls of these traits for these
/// types, but it's unstable and unlikely to be stabilized soon.
///
/// [specialization]: https://rust-lang.github.io/rfcs/1210-impl-specialization.html
pub trait BytesCtSelect: Sized + sealed::Sealed {
/// Conditionally assign `other` to `self` if `choice` is [`Choice::TRUE`].
fn bytes_ct_assign(&mut self, other: &Self, choice: Choice);

pub trait BytesCtSelect: BytesCtAssign + Sized {
/// Select between `self` and `other` based on `choice`, returning a copy of the value.
///
/// # Returns
Expand All @@ -51,6 +61,22 @@ pub trait BytesCtSelect: Sized + sealed::Sealed {
fn bytes_ct_select(&self, other: &Self, choice: Choice) -> Self;
}

impl BytesCtAssign for [u8] {
#[inline]
#[track_caller]
fn bytes_ct_assign(&mut self, other: &Self, choice: Choice) {
assert_eq!(
self.len(),
other.len(),
"source slice length ({}) does not match destination slice length ({})",
other.len(),
self.len()
);

self.cmovnz(other, choice.into());
}
}

impl BytesCtEq for [u8] {
#[inline]
fn bytes_ct_eq(&self, other: &[u8]) -> Choice {
Expand All @@ -67,6 +93,13 @@ impl<const N: usize> BytesCtEq for [u8; N] {
}
}

impl<const N: usize> BytesCtAssign for [u8; N] {
#[inline]
fn bytes_ct_assign(&mut self, other: &Self, choice: Choice) {
self.cmovnz(other, choice.into());
}
}

impl<const N: usize> BytesCtEq<[u8]> for [u8; N] {
#[inline]
fn bytes_ct_eq(&self, other: &[u8]) -> Choice {
Expand All @@ -77,11 +110,6 @@ impl<const N: usize> BytesCtEq<[u8]> for [u8; N] {
}

impl<const N: usize> BytesCtSelect for [u8; N] {
#[inline]
fn bytes_ct_assign(&mut self, other: &Self, choice: Choice) {
self.cmovnz(other, choice.into());
}

#[inline]
fn bytes_ct_select(&self, other: &Self, choice: Choice) -> Self {
let mut ret = *self;
Expand All @@ -100,7 +128,7 @@ mod sealed {

#[cfg(test)]
mod tests {
use super::{BytesCtEq, BytesCtSelect, Choice};
use super::{BytesCtAssign, BytesCtEq, BytesCtSelect, Choice};

mod array {
use super::*;
Expand Down Expand Up @@ -137,6 +165,17 @@ mod tests {
const EXAMPLE_B: &[u8] = &[2, 2, 3];
const EXAMPLE_C: &[u8] = &[1, 2];

#[test]
fn bytes_ct_assign() {
let mut bytes = [0u8; 3];
let slice = bytes.as_mut();

slice.bytes_ct_assign(EXAMPLE_A, Choice::FALSE);
assert_eq!(slice, &[0u8; 3]);
slice.bytes_ct_assign(EXAMPLE_A, Choice::TRUE);
assert_eq!(slice, EXAMPLE_A);
}

#[test]
fn bytes_ct_eq() {
assert!(EXAMPLE_A.bytes_ct_eq(EXAMPLE_A).to_bool());
Expand Down
9 changes: 8 additions & 1 deletion ctutils/src/choice.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{CtEq, CtSelect};
use crate::{CtAssign, CtEq, CtSelect};
use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not};

/// Bitwise less-than-or equal: returns `1` if `x <= y`, and otherwise returns `0`.
Expand Down Expand Up @@ -476,6 +476,13 @@ impl BitXorAssign for Choice {
}
}

impl CtAssign for Choice {
#[inline]
fn ct_assign(&mut self, other: &Self, choice: Choice) {
self.0.ct_assign(&other.0, choice);
}
}

impl CtEq for Choice {
#[inline]
fn ct_eq(&self, other: &Self) -> Self {
Expand Down
9 changes: 8 additions & 1 deletion ctutils/src/ct_option.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Choice, CtEq, CtSelect};
use crate::{Choice, CtAssign, CtEq, CtSelect};
use core::ops::{Deref, DerefMut};

/// Helper macro for providing behavior like the [`CtOption::map`] combinator that works in
Expand Down Expand Up @@ -532,6 +532,13 @@ impl<T> CtOption<&mut T> {
}
}

impl<T: CtAssign> CtAssign for CtOption<T> {
fn ct_assign(&mut self, other: &Self, choice: Choice) {
self.value.ct_assign(&other.value, choice);
self.is_some.ct_assign(&other.is_some, choice);
}
}

impl<T: CtEq> CtEq for CtOption<T> {
#[inline]
fn ct_eq(&self, other: &CtOption<T>) -> Choice {
Expand Down
6 changes: 3 additions & 3 deletions ctutils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ mod choice;
mod ct_option;
mod traits;

pub use bytes::{BytesCtEq, BytesCtSelect};
pub use bytes::{BytesCtAssign, BytesCtEq, BytesCtSelect};
pub use choice::Choice;
pub use ct_option::CtOption;
pub use traits::{
ct_eq::CtEq, ct_find::CtFind, ct_gt::CtGt, ct_lookup::CtLookup, ct_lt::CtLt, ct_neg::CtNeg,
ct_select::CtSelect,
ct_assign::CtAssign, ct_eq::CtEq, ct_find::CtFind, ct_gt::CtGt, ct_lookup::CtLookup,
ct_lt::CtLt, ct_neg::CtNeg, ct_select::CtSelect,
};
1 change: 1 addition & 0 deletions ctutils/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! These are each in their own module so we can also define tests for the core types they're impl'd
//! on in the same module.

pub(crate) mod ct_assign;
pub(crate) mod ct_eq;
pub(crate) mod ct_find;
pub(crate) mod ct_gt;
Expand Down
115 changes: 115 additions & 0 deletions ctutils/src/traits/ct_assign.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use crate::{Choice, CtSelect};
use cmov::Cmov;
use core::cmp;

/// Constant-time conditional assignment: assign a given value to another based on a [`Choice`].
pub trait CtAssign {
/// Conditionally assign `other` to `self` if `choice` is [`Choice::TRUE`].
fn ct_assign(&mut self, other: &Self, choice: Choice);
}

// Impl `CtAssign` using the `cmov::Cmov` trait
macro_rules! impl_ct_assign_with_cmov {
( $($ty:ty),+ ) => {
$(
impl CtAssign for $ty {
#[inline]
fn ct_assign(&mut self, other: &Self, choice: Choice) {
self.cmovnz(other, choice.into());
}
}
)+
};
}

impl_ct_assign_with_cmov!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);

#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
impl CtAssign for isize {
#[cfg(target_pointer_width = "32")]
#[inline]
fn ct_assign(&mut self, other: &Self, choice: Choice) {
*self = Self::ct_select(self, other, choice);
}

#[cfg(target_pointer_width = "64")]
#[allow(clippy::cast_possible_truncation)]
#[inline]
fn ct_assign(&mut self, other: &Self, choice: Choice) {
*self = Self::ct_select(self, other, choice);
}
}

#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
impl CtAssign for usize {
#[cfg(target_pointer_width = "32")]
#[inline]
fn ct_assign(&mut self, other: &Self, choice: Choice) {
*self = Self::ct_select(self, other, choice);
}

#[cfg(target_pointer_width = "64")]
#[allow(clippy::cast_possible_truncation)]
#[inline]
fn ct_assign(&mut self, other: &Self, choice: Choice) {
*self = Self::ct_select(self, other, choice);
}
}

impl CtAssign for cmp::Ordering {
#[inline]
fn ct_assign(&mut self, other: &Self, choice: Choice) {
*self = Self::ct_select(self, other, choice);
}
}

impl<T> CtAssign for [T]
where
T: CtAssign,
{
#[inline]
#[track_caller]
fn ct_assign(&mut self, other: &Self, choice: Choice) {
assert_eq!(
self.len(),
other.len(),
"source slice length ({}) does not match destination slice length ({})",
other.len(),
self.len()
);

for (a, b) in self.iter_mut().zip(other) {
a.ct_assign(b, choice)
}
}
}

impl<T, const N: usize> CtAssign for [T; N]
where
T: CtAssign,
{
#[inline]
fn ct_assign(&mut self, other: &Self, choice: Choice) {
self.as_mut_slice().ct_assign(other, choice);
}
}

#[cfg(feature = "subtle")]
impl CtAssign for subtle::Choice {
#[inline]
fn ct_assign(&mut self, other: &Self, choice: Choice) {
*self = Self::ct_select(self, other, choice);
}
}

#[cfg(feature = "subtle")]
impl<T> CtAssign for subtle::CtOption<T>
where
T: Default + subtle::ConditionallySelectable,
{
#[inline]
fn ct_assign(&mut self, other: &Self, choice: Choice) {
use subtle::ConditionallySelectable as _;
self.conditional_assign(other, choice.into());
}
}
2 changes: 1 addition & 1 deletion ctutils/src/traits/ct_neg.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Choice, CtSelect};
use crate::{Choice, CtAssign, CtSelect};

/// Constant-time conditional negation: negates a value when `choice` is [`Choice::TRUE`].
pub trait CtNeg: Sized {
Expand Down
Loading