Skip to content

Commit bb30a74

Browse files
authored
ctutils: delegate CtAssign/CtEq for [T] to cmov (#1376)
`cmov` now contains optimized implementations of its `Cmov` and `CmovEq` traits for the following slice types: - `[i8]`, `[i16]`, `[i32]`, `[i64]`, `[i128]` - `[u8]`, `[u16]`, `[u32]`, `[u64]`, `[u128]` These impls coalesce the inputs into word-size chunks and perform the underlying predication/equality testing operation on words, whereas the generic implementation in this crate previously would widen each array element to a word and perform the operation, which is much less efficient. We'd previously added duplicated specialized traits for operating on bytes which called the fast path for `[u8]` in #1359, but not only are the redundant traits annoying but such an optimization is useful for any integer type smaller than the word size. This commit removes the generic trait impls of `CtAssign`, `CtEq`, and `CtSelect` for arrays and slices, factoring them into free functions in the `array` and `slice` modules so they're still available, just not via a trait-based interface. Next, the existing macros for delegating to `cmov` like `impl_ct_assign_with_cmov!` are used to delegate to the above slice types. The array impls have been changed to be generic around when the underlying slice type impls the same trait. This means we have a bunch of specialized impls of these traits for the core integer types which are always fast, instead of a leaky generic implementation which is potentially quite slow. It means users may need to add their own concrete impls where the generic impl would've previously sufficed, but they can call into the generic implementations in `array` and `slice` for that, and if it's too annoying we can add a macro to write the impl for them. The impl of `CtSelect` for `[T; N]` has been changed to bound on `T: Clone` and `[T]: CtAssign`, but should perform well, and this is actually more consistent with the impls on `Box` and `Vec`. The old generic implementation which avoids a clone bound by using `core::array::from_fn` is retained in the new `array` module. Since the impls of `CtAssign` and `CtEq` for `[T]` and `[T; N]` should now be fast (along with the `CtSelect` impl for `[T; N]`), this removes the `BytesCt*` traits since they're no longer needed.
1 parent eaeb331 commit bb30a74

File tree

10 files changed

+267
-333
lines changed

10 files changed

+267
-333
lines changed

ctutils/src/array.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//! Array helpers: generic selection/equality operations for arrays.
2+
//!
3+
//! Use these in the event there isn't an impl of a given trait for `[T; N]`. If there is, however,
4+
//! you should prefer that for performance reasons.
5+
6+
use crate::{Choice, CtAssign, CtEq, CtSelect, slice};
7+
8+
/// Generic implementation of conditional assignment for arrays which works with any type which
9+
/// impls `CtSelect`. Useful in the event there isn't a `CtAssign` impl for `[T; N]`.
10+
///
11+
/// Assigns `src` to `dst` in constant-time when `choice` is [`Choice::TRUE`].
12+
///
13+
/// Unfortunately we can't provide this as a trait impl without specialization, since it would
14+
/// overlap with the optimized type-specific impls we provide.
15+
#[inline]
16+
pub fn ct_assign<T, const N: usize>(dst: &mut [T; N], src: &[T; N], choice: Choice)
17+
where
18+
T: CtAssign,
19+
{
20+
slice::ct_assign(dst, src, choice);
21+
}
22+
23+
/// Generic implementation of constant-time equality testing for arrays which works with any type
24+
/// which impls `CtEq`. Useful in the event there isn't a `CtEq` impl for `[T; N]`.
25+
#[inline]
26+
pub fn ct_eq<T, const N: usize>(a: &[T; N], b: &[T; N]) -> Choice
27+
where
28+
T: CtEq,
29+
{
30+
let mut ret = Choice::TRUE;
31+
for (a, b) in a.iter().zip(b.iter()) {
32+
ret &= a.ct_eq(b);
33+
}
34+
ret
35+
}
36+
37+
/// Generic implementation of conditional selection for arrays which works with any type which
38+
/// impls `CtSelect`. Useful in the event there isn't a `CtSelect` impl for `[T; N]`.
39+
///
40+
/// Selects `a` if `choice` is `Choice::FALSE`, and `b` if `choice` is `Choice::TRUE`.
41+
///
42+
/// Unfortunately we can't provide this as a trait impl without specialization, since it would
43+
/// overlap with the optimized type-specific impls we provide.
44+
#[inline]
45+
pub fn ct_select<T, const N: usize>(a: &[T; N], b: &[T; N], choice: Choice) -> [T; N]
46+
where
47+
T: CtSelect,
48+
{
49+
core::array::from_fn(|i| T::ct_select(&a[i], &b[i], choice))
50+
}
51+
52+
#[cfg(test)]
53+
mod tests {
54+
use crate::Choice;
55+
56+
// Note: this violates our own advice not to use these functions with e.g. `[u8; N]` but this
57+
// is just for testing purposes
58+
const EXAMPLE_A: [u8; 3] = [1, 2, 3];
59+
const EXAMPLE_B: [u8; 3] = [4, 5, 6];
60+
61+
#[test]
62+
fn ct_assign() {
63+
let mut x = EXAMPLE_A;
64+
65+
super::ct_assign(&mut x, &EXAMPLE_B, Choice::FALSE);
66+
assert_eq!(EXAMPLE_A, x);
67+
68+
super::ct_assign(&mut x, &EXAMPLE_B, Choice::TRUE);
69+
assert_eq!(EXAMPLE_B, x);
70+
}
71+
72+
#[test]
73+
fn ct_eq() {
74+
assert!(super::ct_eq(&EXAMPLE_A, &EXAMPLE_A).to_bool());
75+
assert!(!super::ct_eq(&EXAMPLE_A, &EXAMPLE_B).to_bool());
76+
}
77+
78+
#[test]
79+
fn ct_select() {
80+
assert_eq!(
81+
EXAMPLE_A,
82+
super::ct_select(&EXAMPLE_A, &EXAMPLE_B, Choice::FALSE)
83+
);
84+
assert_eq!(
85+
EXAMPLE_B,
86+
super::ct_select(&EXAMPLE_A, &EXAMPLE_B, Choice::TRUE)
87+
);
88+
}
89+
}

ctutils/src/bytes.rs

Lines changed: 0 additions & 195 deletions
This file was deleted.

ctutils/src/ct_option.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ impl<T> CtOption<T> {
162162
/// Conditionally inserts `value` into the [`CtOption`] if the given condition holds.
163163
pub fn insert_if(&mut self, value: &T, condition: Choice)
164164
where
165-
T: CtSelect,
165+
T: CtAssign,
166166
{
167167
self.value.ct_assign(value, condition);
168168
self.is_some.ct_assign(&Choice::TRUE, condition);

ctutils/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,13 @@
9191
#[cfg(feature = "alloc")]
9292
extern crate alloc;
9393

94-
mod bytes;
94+
pub mod array;
95+
pub mod slice;
96+
9597
mod choice;
9698
mod ct_option;
9799
mod traits;
98100

99-
pub use bytes::{BytesCtAssign, BytesCtEq, BytesCtSelect};
100101
pub use choice::Choice;
101102
pub use ct_option::CtOption;
102103
pub use traits::{

ctutils/src/slice.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//! Slice helpers: generic selection/equality operations for slices.
2+
//!
3+
//! Use these in the event there isn't an impl of a given trait for `[T]`. If there is, however,
4+
//! you should prefer that for performance reasons.
5+
6+
use crate::{Choice, CtAssign, CtEq};
7+
8+
/// Generic implementation of conditional assignment for slices which works with any type which
9+
/// impls `CtSelect`. Useful in the event there isn't a `CtAssign` impl for `[T]`.
10+
///
11+
/// Assigns `src` to `dst` in constant-time when `choice` is [`Choice::TRUE`], like a conditional
12+
/// version of `[T]::copy_from_slice`.
13+
///
14+
/// Unfortunately we can't provide this as a trait impl without specialization, since it would
15+
/// overlap with the optimized type-specific impls we provide.
16+
///
17+
/// # Panics
18+
/// - If the two slices have unequal lengths
19+
#[inline]
20+
pub fn ct_assign<T>(dst: &mut [T], src: &[T], choice: Choice)
21+
where
22+
T: CtAssign,
23+
{
24+
assert_eq!(
25+
dst.len(),
26+
src.len(),
27+
"source slice length ({}) does not match destination slice length ({})",
28+
src.len(),
29+
dst.len()
30+
);
31+
32+
for (a, b) in dst.iter_mut().zip(src) {
33+
a.ct_assign(b, choice)
34+
}
35+
}
36+
37+
/// Generic implementation of constant-time equality testing for slices which works with any type
38+
/// which impls `CtEq`. Useful in the event there isn't a `CtEq` impl for `[T]`.
39+
// NOTE: `cfg` gated because it uses the `CtEq` impl on `usize`
40+
#[cfg(any(
41+
target_pointer_width = "16",
42+
target_pointer_width = "32",
43+
target_pointer_width = "64"
44+
))]
45+
#[inline]
46+
pub fn ct_eq<T>(a: &[T], b: &[T]) -> Choice
47+
where
48+
T: CtEq,
49+
{
50+
let mut ret = a.len().ct_eq(&b.len());
51+
for (a, b) in a.iter().zip(b.iter()) {
52+
ret &= a.ct_eq(b);
53+
}
54+
ret
55+
}
56+
57+
#[cfg(test)]
58+
mod tests {
59+
use crate::Choice;
60+
61+
// Note: this violates our own advice not to use these functions with e.g. `[u8]` but this
62+
// is just for testing purposes
63+
const EXAMPLE_A: &[u8] = &[1, 2, 3];
64+
const EXAMPLE_B: &[u8] = &[4, 5, 6];
65+
66+
#[test]
67+
fn ct_assign() {
68+
let mut x = [0u8; 3];
69+
70+
super::ct_assign(&mut x, EXAMPLE_A, Choice::FALSE);
71+
assert_eq!([0u8; 3], x);
72+
73+
super::ct_assign(&mut x, EXAMPLE_A, Choice::TRUE);
74+
assert_eq!(EXAMPLE_A, x);
75+
}
76+
77+
#[test]
78+
fn ct_eq() {
79+
assert!(super::ct_eq(EXAMPLE_A, EXAMPLE_A).to_bool());
80+
assert!(!super::ct_eq(EXAMPLE_A, EXAMPLE_B).to_bool());
81+
}
82+
}

0 commit comments

Comments
 (0)