Skip to content

Commit f2e4a2b

Browse files
authored
ctutils: add CtLookup trait (#1362)
Adds a trait useful for retrieving items from an array or slice by index, similar to the `Index` trait, but iterating over all items in the array/slice and returning an owned value as a `CtOption`, or the `CtOption` equivalent of `None` if the index was out-of-bounds. Generic over any index type which impls the following: AddAssign + CtEq + Default + From<u8> ...which is useful because our support for `usize` is limited to 32-bit and 64-bit platforms.
1 parent 513a1a5 commit f2e4a2b

File tree

4 files changed

+109
-1
lines changed

4 files changed

+109
-1
lines changed

ctutils/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ library incorporated into `subtle` for a potential v3.0.
3939
with a portable "best effort" fallback on other platforms using bitwise arithmetic and `black_box`
4040
- No `Copy` (or even `Clone`) bounds, which means all functionality can work with heap-allocated
4141
types in addition to stack-allocated
42+
- Expanded selection of traits: `CtFind` and `CtLookup` for arrays and slices
4243

4344
Many features of this crate are extractions from the [`crypto-bigint`] crate, where we implement all
4445
core logic as `const fn` and needed solutions for implementing constant-time code despite the

ctutils/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,5 +95,6 @@ pub use bytes::{BytesCtEq, BytesCtSelect};
9595
pub use choice::Choice;
9696
pub use ct_option::CtOption;
9797
pub use traits::{
98-
ct_eq::CtEq, ct_find::CtFind, ct_gt::CtGt, ct_lt::CtLt, ct_neg::CtNeg, ct_select::CtSelect,
98+
ct_eq::CtEq, ct_find::CtFind, ct_gt::CtGt, ct_lookup::CtLookup, ct_lt::CtLt, ct_neg::CtNeg,
99+
ct_select::CtSelect,
99100
};

ctutils/src/traits.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
pub(crate) mod ct_eq;
77
pub(crate) mod ct_find;
88
pub(crate) mod ct_gt;
9+
pub(crate) mod ct_lookup;
910
pub(crate) mod ct_lt;
1011
pub(crate) mod ct_neg;
1112
pub(crate) mod ct_select;

ctutils/src/traits/ct_lookup.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
use crate::{CtEq, CtOption, CtSelect};
2+
use core::ops::AddAssign;
3+
4+
#[cfg(doc)]
5+
use core::ops::Index;
6+
7+
/// Constant-time lookup by index, similar to the [`Index`] trait, but returning an owned result in
8+
/// constant-time.
9+
pub trait CtLookup<Idx> {
10+
/// Output type returned by the lookup operation.
11+
type Output: CtSelect;
12+
13+
/// Attempt to retrieve the item at the given `index`, either returning it or the [`CtOption`]
14+
/// equivalent of [`None`] if the `index` was out-of-bounds.
15+
fn ct_lookup(&self, index: Idx) -> CtOption<Self::Output>;
16+
}
17+
18+
impl<T, Idx> CtLookup<Idx> for [T]
19+
where
20+
T: CtSelect + Default,
21+
Idx: AddAssign + CtEq + Default + From<u8>,
22+
{
23+
type Output = T;
24+
25+
fn ct_lookup(&self, index: Idx) -> CtOption<T> {
26+
let mut ret = CtOption::none();
27+
let mut i = Idx::default();
28+
29+
for item in self {
30+
ret.insert_if(item, i.ct_eq(&index));
31+
32+
// TODO(tarcieri): ideally we'd prevent overflow here but there's no core `CheckedAdd`
33+
i += Idx::from(1u8);
34+
}
35+
36+
ret
37+
}
38+
}
39+
40+
impl<T, Idx, const N: usize> CtLookup<Idx> for [T; N]
41+
where
42+
T: CtSelect + Default,
43+
Idx: AddAssign + CtEq + Default + From<u8>,
44+
{
45+
type Output = T;
46+
47+
fn ct_lookup(&self, index: Idx) -> CtOption<T> {
48+
self.as_slice().ct_lookup(index)
49+
}
50+
}
51+
52+
#[cfg(test)]
53+
mod tests {
54+
mod array {
55+
use crate::CtLookup;
56+
57+
const EXAMPLE: [u8; 3] = [1, 2, 3];
58+
59+
#[test]
60+
fn ct_lookup_u32() {
61+
assert_eq!(EXAMPLE.ct_lookup(0u32).unwrap(), 1);
62+
assert_eq!(EXAMPLE.ct_lookup(1u32).unwrap(), 2);
63+
assert_eq!(EXAMPLE.ct_lookup(2u32).unwrap(), 3);
64+
assert!(EXAMPLE.ct_lookup(3u32).is_none().to_bool());
65+
assert!(EXAMPLE.ct_lookup(4u32).is_none().to_bool());
66+
}
67+
68+
// usize only has a `CtEq` impl on 32-bit and 64-bit targets currently
69+
#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
70+
#[test]
71+
fn ct_lookup_usize() {
72+
assert_eq!(EXAMPLE.ct_lookup(0usize).unwrap(), 1);
73+
assert_eq!(EXAMPLE.ct_lookup(1usize).unwrap(), 2);
74+
assert_eq!(EXAMPLE.ct_lookup(2usize).unwrap(), 3);
75+
assert!(EXAMPLE.ct_lookup(3usize).is_none().to_bool());
76+
assert!(EXAMPLE.ct_lookup(4usize).is_none().to_bool());
77+
}
78+
}
79+
80+
mod slice {
81+
use crate::CtLookup;
82+
83+
const EXAMPLE: &[u8] = &[1, 2, 3];
84+
85+
#[test]
86+
fn ct_lookup_u32() {
87+
assert_eq!(EXAMPLE.ct_lookup(0u32).unwrap(), 1);
88+
assert_eq!(EXAMPLE.ct_lookup(1u32).unwrap(), 2);
89+
assert_eq!(EXAMPLE.ct_lookup(2u32).unwrap(), 3);
90+
assert!(EXAMPLE.ct_lookup(3u32).is_none().to_bool());
91+
assert!(EXAMPLE.ct_lookup(4u32).is_none().to_bool());
92+
}
93+
94+
// usize only has a `CtEq` impl on 32-bit and 64-bit targets currently
95+
#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
96+
#[test]
97+
fn ct_lookup_usize() {
98+
assert_eq!(EXAMPLE.ct_lookup(0usize).unwrap(), 1);
99+
assert_eq!(EXAMPLE.ct_lookup(1usize).unwrap(), 2);
100+
assert_eq!(EXAMPLE.ct_lookup(2usize).unwrap(), 3);
101+
assert!(EXAMPLE.ct_lookup(3usize).is_none().to_bool());
102+
assert!(EXAMPLE.ct_lookup(4usize).is_none().to_bool());
103+
}
104+
}
105+
}

0 commit comments

Comments
 (0)