From 3fe7535d3dbbefd0c5bfd1a665f0a87413618723 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 8 Mar 2026 13:55:05 -0600 Subject: [PATCH] Add `ctutils` support Adds an optional feature similar to the `subtle` feature which adds impls of `CtAssign`, `CtEq`, and `CtSelect` to `Array`. --- .github/workflows/hybrid-array.yml | 4 +-- Cargo.lock | 16 +++++++++ Cargo.toml | 1 + src/lib.rs | 55 ++++++++++++++++++++++++------ tests/ctutils.rs | 42 +++++++++++++++++++++++ 5 files changed, 105 insertions(+), 13 deletions(-) create mode 100644 tests/ctutils.rs diff --git a/.github/workflows/hybrid-array.yml b/.github/workflows/hybrid-array.yml index bf0b046..70df483 100644 --- a/.github/workflows/hybrid-array.yml +++ b/.github/workflows/hybrid-array.yml @@ -37,7 +37,7 @@ jobs: toolchain: ${{ matrix.rust }} targets: ${{ matrix.target }} - uses: RustCrypto/actions/cargo-hack-install@master - - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-all-features --optional-deps bytemuck,serde,subtle,zeroize + - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-all-features --optional-deps bytemuck,ctutils,serde,subtle,zeroize careful: runs-on: ubuntu-latest @@ -105,5 +105,5 @@ jobs: with: toolchain: ${{ matrix.toolchain }} - uses: RustCrypto/actions/cargo-hack-install@master - - run: cargo hack test --feature-powerset --optional-deps arbitrary,bytemuck,serde,subtle,zeroize --group-features arbitrary,bytemuck,serde,subtle,zeroize + - run: cargo hack test --feature-powerset --optional-deps arbitrary,bytemuck,serde,subtle,zeroize --group-features arbitrary,bytemuck,ctutils,serde,subtle,zeroize - run: cargo test --all-features --release diff --git a/Cargo.lock b/Cargo.lock index ff9e57c..ed33f33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,12 +14,28 @@ version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +[[package]] +name = "cmov" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0758edba32d61d1fd9f4d69491b47604b91ee2f7e6b33de7e54ca4ebe55dc3" + +[[package]] +name = "ctutils" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1005a6d4446f5120ef475ad3d2af2b30c49c2c9c6904258e3bb30219bebed5e4" +dependencies = [ + "cmov", +] + [[package]] name = "hybrid-array" version = "0.4.7" dependencies = [ "arbitrary", "bytemuck", + "ctutils", "serde", "subtle", "typenum", diff --git a/Cargo.toml b/Cargo.toml index 08bb5d1..b8cd77e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ typenum = { version = "1.17", features = ["const-generics"] } # optional dependencies arbitrary = { version = "1", optional = true } bytemuck = { version = "1", optional = true, default-features = false } +ctutils = { version = "0.4", optional = true } serde = { version = "1", optional = true, default-features = false } subtle = { version = "2", optional = true, default-features = false, features = ["const-generics"] } zeroize = { version = "1.8", optional = true, default-features = false } diff --git a/src/lib.rs b/src/lib.rs index c0edca8..5f6119e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -134,9 +134,6 @@ use arbitrary::Arbitrary; #[cfg(feature = "bytemuck")] use bytemuck::{Pod, Zeroable}; -#[cfg(feature = "subtle")] -use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; - #[cfg(feature = "zeroize")] use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -1087,21 +1084,57 @@ where { } +#[cfg(feature = "ctutils")] +impl ctutils::CtAssign for Array +where + [T]: ctutils::CtAssign, + U: ArraySize, +{ + #[inline] + fn ct_assign(&mut self, other: &Self, choice: ctutils::Choice) { + self.as_mut_slice().ct_assign(other.as_slice(), choice); + } +} + +#[cfg(feature = "ctutils")] +impl ctutils::CtSelect for Array +where + U: ArraySize, + U::ArrayType: ctutils::CtSelect, +{ + #[inline] + fn ct_select(&self, other: &Self, choice: ctutils::Choice) -> Self { + Self(self.0.ct_select(&other.0, choice)) + } +} + +#[cfg(feature = "ctutils")] +impl ctutils::CtEq for Array +where + U: ArraySize, + U::ArrayType: ctutils::CtEq, +{ + #[inline] + fn ct_eq(&self, other: &Self) -> ctutils::Choice { + self.0.ct_eq(&other.0) + } +} + #[cfg(feature = "subtle")] -impl ConditionallySelectable for Array +impl subtle::ConditionallySelectable for Array where Self: Copy, - T: ConditionallySelectable, + T: subtle::ConditionallySelectable, U: ArraySize, { #[inline] - fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self { let mut output = *a; output.conditional_assign(b, choice); output } - fn conditional_assign(&mut self, other: &Self, choice: Choice) { + fn conditional_assign(&mut self, other: &Self, choice: subtle::Choice) { for (a_i, b_i) in self.iter_mut().zip(other) { a_i.conditional_assign(b_i, choice); } @@ -1109,16 +1142,16 @@ where } #[cfg(feature = "subtle")] -impl ConstantTimeEq for Array +impl subtle::ConstantTimeEq for Array where - T: ConstantTimeEq, + T: subtle::ConstantTimeEq, U: ArraySize, { #[inline] - fn ct_eq(&self, other: &Self) -> Choice { + fn ct_eq(&self, other: &Self) -> subtle::Choice { self.iter() .zip(other.iter()) - .fold(Choice::from(1), |acc, (a, b)| acc & a.ct_eq(b)) + .fold(subtle::Choice::from(1), |acc, (a, b)| acc & a.ct_eq(b)) } } diff --git a/tests/ctutils.rs b/tests/ctutils.rs new file mode 100644 index 0000000..86896b0 --- /dev/null +++ b/tests/ctutils.rs @@ -0,0 +1,42 @@ +//! Tests for `ctutils` integration. + +#![cfg(feature = "ctutils")] + +use ctutils::{Choice, CtAssign, CtEq, CtSelect}; +use hybrid_array::{Array, typenum::U3}; + +#[test] +fn ct_assign() { + let a: Array = Array([0, 0, 0]); + let b: Array = Array([1, 2, 3]); + let mut c = a; + + c.ct_assign(&b, Choice::FALSE); + assert_eq!(a, c); + + c.ct_assign(&b, Choice::TRUE); + assert_eq!(b, c); +} + +#[test] +fn ct_eq() { + let a: Array = Array([0, 0, 0]); + let b: Array = Array([1, 2, 3]); + + assert!(a.ct_eq(&a).to_bool()); + assert!(!a.ct_ne(&a).to_bool()); + assert!(!a.ct_eq(&b).to_bool()); + assert!(a.ct_ne(&b).to_bool()); +} + +#[test] +fn ct_select() { + let a: Array = Array([0, 0, 0]); + let b: Array = Array([1, 2, 3]); + + let c = a.ct_select(&b, Choice::FALSE); + assert_eq!(a, c); + + let d = a.ct_select(&b, Choice::TRUE); + assert_eq!(b, d); +}