1use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
4use zeroize::Zeroize;
5
6#[cfg(feature = "memquota-memcost")]
7use {derive_deftly::Deftly, tor_memquota::derive_deftly_template_HasMemoryCost};
8
9#[allow(clippy::derived_hash_with_manual_eq)]
20#[derive(Clone, Copy, Debug, Hash, Zeroize, derive_more::Deref)]
21#[cfg_attr(
22 feature = "memquota-memcost",
23 derive(Deftly),
24 derive_deftly(HasMemoryCost)
25)]
26pub struct CtByteArray<const N: usize>([u8; N]);
27
28impl<const N: usize> ConstantTimeEq for CtByteArray<N> {
29 fn ct_eq(&self, other: &Self) -> Choice {
30 self.0.ct_eq(&other.0)
31 }
32}
33
34impl<const N: usize> PartialEq for CtByteArray<N> {
35 fn eq(&self, other: &Self) -> bool {
36 self.ct_eq(other).into()
37 }
38}
39impl<const N: usize> Eq for CtByteArray<N> {}
40
41impl<const N: usize> From<[u8; N]> for CtByteArray<N> {
42 fn from(value: [u8; N]) -> Self {
43 Self(value)
44 }
45}
46
47impl<const N: usize> From<CtByteArray<N>> for [u8; N] {
48 fn from(value: CtByteArray<N>) -> Self {
49 value.0
50 }
51}
52
53impl<const N: usize> Ord for CtByteArray<N> {
54 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
55 let mut first_nonzero_difference = 0_i16;
59
60 for (a, b) in self.0.iter().zip(other.0.iter()) {
61 let difference = i16::from(*a) - i16::from(*b);
62
63 first_nonzero_difference
70 .conditional_assign(&difference, first_nonzero_difference.ct_eq(&0));
71 }
72
73 first_nonzero_difference.cmp(&0)
76 }
77}
78
79impl<const N: usize> PartialOrd for CtByteArray<N> {
80 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
81 Some(self.cmp(other))
82 }
83}
84
85impl<const N: usize> AsRef<[u8; N]> for CtByteArray<N> {
86 fn as_ref(&self) -> &[u8; N] {
87 &self.0
88 }
89}
90
91impl<const N: usize> AsMut<[u8; N]> for CtByteArray<N> {
92 fn as_mut(&mut self) -> &mut [u8; N] {
93 &mut self.0
94 }
95}
96
97pub fn ct_lookup<T, F>(array: &[T], matches: F) -> Option<&T>
113where
114 F: Fn(&T) -> Choice,
115{
116 let mut idx: u64 = 0;
119 let mut found: Choice = 0.into();
120
121 for (i, x) in array.iter().enumerate() {
122 let equal = matches(x);
123 idx.conditional_assign(&(i as u64), equal);
124 found.conditional_assign(&equal, equal);
125 }
126
127 if found.into() {
128 Some(&array[idx as usize])
129 } else {
130 None
131 }
132}
133
134#[cfg(test)]
135mod test {
136 #![allow(clippy::bool_assert_comparison)]
138 #![allow(clippy::clone_on_copy)]
139 #![allow(clippy::dbg_macro)]
140 #![allow(clippy::mixed_attributes_style)]
141 #![allow(clippy::print_stderr)]
142 #![allow(clippy::print_stdout)]
143 #![allow(clippy::single_char_pattern)]
144 #![allow(clippy::unwrap_used)]
145 #![allow(clippy::unchecked_duration_subtraction)]
146 #![allow(clippy::useless_vec)]
147 #![allow(clippy::needless_pass_by_value)]
148 use super::*;
151 use rand::Rng;
152 use tor_basic_utils::test_rng;
153
154 #[allow(clippy::nonminimal_bool)]
155 #[test]
156 fn test_comparisons() {
157 let num = 200;
158 let mut rng = test_rng::testing_rng();
159
160 let mut array: Vec<CtByteArray<32>> =
161 (0..num).map(|_| rng.random::<[u8; 32]>().into()).collect();
162 array.sort();
163
164 for i in 0..num {
165 assert_eq!(array[i], array[i]);
166 assert!(!(array[i] < array[i]));
167 assert!(!(array[i] > array[i]));
168
169 for j in (i + 1)..num {
170 assert!(array[i] < array[j]);
174 assert_ne!(array[i], array[j]);
175 assert!(array[j] > array[i]);
176 assert_eq!(
177 array[i].cmp(&array[j]),
178 array[j].as_ref().cmp(array[i].as_ref()).reverse()
179 );
180 }
181 }
182 }
183
184 #[test]
185 fn test_lookup() {
186 use super::ct_lookup as lookup;
187 use subtle::ConstantTimeEq;
188 let items = vec![
189 "One".to_string(),
190 "word".to_string(),
191 "of".to_string(),
192 "every".to_string(),
193 "length".to_string(),
194 ];
195 let of_word = lookup(&items[..], |i| i.len().ct_eq(&2));
196 let every_word = lookup(&items[..], |i| i.len().ct_eq(&5));
197 let no_word = lookup(&items[..], |i| i.len().ct_eq(&99));
198 assert_eq!(of_word.unwrap(), "of");
199 assert_eq!(every_word.unwrap(), "every");
200 assert_eq!(no_word, None);
201 }
202}