1use core::hint::black_box;
7use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
8use zeroize::Zeroize;
9
10#[must_use]
25pub fn ct_eq(a: &[u8], b: &[u8]) -> bool {
26 if a.len() != b.len() {
29 let min_len = a.len().min(b.len());
32 if min_len > 0 {
33 #[allow(clippy::indexing_slicing)]
36 let _ = black_box(a[..min_len].ct_eq(&b[..min_len]));
37 }
38 return black_box(false);
39 }
40
41 let result = a.ct_eq(b);
44 black_box(result.into())
45}
46
47#[inline]
52pub fn ct_select<T: ConditionallySelectable>(a: &T, b: &T, choice: bool) -> T {
53 T::conditional_select(b, a, Choice::from(u8::from(choice)))
54}
55
56#[inline]
61pub fn ct_assign<T: ConditionallySelectable>(dest: &mut T, new_val: &T, choice: bool) {
62 dest.conditional_assign(new_val, Choice::from(u8::from(choice)));
63}
64
65pub struct CtSecretOption<T> {
69 value: T,
70 is_some: Choice,
71}
72
73impl<T> CtSecretOption<T> {
74 #[inline]
76 pub fn some(value: T) -> Self {
77 Self {
78 value,
79 is_some: Choice::from(1),
80 }
81 }
82
83 #[inline]
85 pub fn none(default: T) -> Self {
86 Self {
87 value: default,
88 is_some: Choice::from(0),
89 }
90 }
91
92 #[inline]
94 pub const fn is_some(&self) -> Choice {
95 self.is_some
96 }
97
98 #[inline]
100 pub fn is_none(&self) -> Choice {
101 !self.is_some
102 }
103
104 #[inline]
106 pub fn unwrap_or(self, default: T) -> T
107 where
108 T: ConditionallySelectable,
109 {
110 T::conditional_select(&default, &self.value, self.is_some)
111 }
112
113 #[inline]
115 pub fn map<U, F>(self, f: F) -> CtSecretOption<U>
116 where
117 F: FnOnce(T) -> U,
118 U: ConditionallySelectable + Default,
119 {
120 let mapped = f(self.value);
121 let default = U::default();
122 CtSecretOption {
123 value: U::conditional_select(&default, &mapped, self.is_some),
124 is_some: self.is_some,
125 }
126 }
127}
128
129impl<T: Zeroize> Zeroize for CtSecretOption<T> {
130 fn zeroize(&mut self) {
131 self.value.zeroize();
132 self.is_some = Choice::from(0);
133 }
134}
135
136pub trait ConstantTimeEqExt: Sized {
138 fn ct_eq(&self, other: &Self) -> Choice;
140
141 fn ct_ne(&self, other: &Self) -> Choice {
143 !self.ct_eq(other)
144 }
145}
146
147macro_rules! impl_ct_eq_for_secret {
149 ($type:ty) => {
150 impl ConstantTimeEqExt for $type {
151 fn ct_eq(&self, other: &Self) -> Choice {
152 self.as_bytes().ct_eq(other.as_bytes())
153 }
154 }
155 };
156}
157
158use crate::pqc::ml_dsa_44::{MlDsa44SecretKey, MlDsa44Signature};
160use crate::pqc::ml_dsa_87::{MlDsa87SecretKey, MlDsa87Signature};
161use crate::pqc::ml_kem_1024::MlKem1024SecretKey;
162use crate::pqc::ml_kem_512::MlKem512SecretKey;
163use crate::pqc::types::{MlDsaSecretKey, MlDsaSignature, MlKemSecretKey, SharedSecret};
164
165impl_ct_eq_for_secret!(MlKemSecretKey);
167impl_ct_eq_for_secret!(MlDsaSecretKey);
168impl_ct_eq_for_secret!(SharedSecret);
169impl_ct_eq_for_secret!(MlKem512SecretKey);
170impl_ct_eq_for_secret!(MlKem1024SecretKey);
171impl_ct_eq_for_secret!(MlDsa44SecretKey);
172impl_ct_eq_for_secret!(MlDsa87SecretKey);
173
174impl ConstantTimeEqExt for MlDsaSignature {
176 fn ct_eq(&self, other: &Self) -> Choice {
177 self.as_bytes().ct_eq(other.as_bytes())
178 }
179}
180
181impl ConstantTimeEqExt for MlDsa44Signature {
182 fn ct_eq(&self, other: &Self) -> Choice {
183 self.as_bytes().ct_eq(other.as_bytes())
184 }
185}
186
187impl ConstantTimeEqExt for MlDsa87Signature {
188 fn ct_eq(&self, other: &Self) -> Choice {
189 self.as_bytes().ct_eq(other.as_bytes())
190 }
191}
192
193#[inline]
198pub fn ct_verify<T>(condition: bool, value: T) -> CtOption<T> {
199 CtOption::new(value, Choice::from(u8::from(condition)))
200}
201
202#[must_use]
206pub fn ct_array_eq<const N: usize>(a: &[u8; N], b: &[u8; N]) -> bool {
207 let result = a.ct_eq(b);
209 black_box(result.into())
210}
211
212#[inline]
216pub fn ct_clear<T: Zeroize>(data: &mut T) {
217 data.zeroize();
218}
219
220#[inline]
253#[must_use]
254pub fn ct_copy_bytes(dest: &mut [u8], src: &[u8], choice: bool) -> bool {
255 if dest.len() != src.len() {
257 return false;
258 }
259
260 let should_copy = Choice::from(u8::from(choice));
262
263 for (d, s) in dest.iter_mut().zip(src.iter()) {
265 d.conditional_assign(s, should_copy);
266 }
267
268 true
269}
270
271#[cfg(test)]
272#[allow(clippy::unwrap_used, clippy::expect_used)]
273mod tests {
274 use super::*;
275
276 #[test]
277 fn test_ct_eq() {
278 let a = [1u8, 2, 3, 4];
279 let b = [1u8, 2, 3, 4];
280 let c = [1u8, 2, 3, 5];
281
282 assert!(ct_eq(&a, &b));
283 assert!(!ct_eq(&a, &c));
284 assert!(!ct_eq(&a[..3], &b)); }
286
287 #[test]
288 fn test_ct_select() {
289 let a = 42u32;
290 let b = 100u32;
291
292 assert_eq!(ct_select(&a, &b, true), a);
293 assert_eq!(ct_select(&a, &b, false), b);
294 }
295
296 #[test]
297 fn test_ct_option() {
298 let some_val = CtSecretOption::some(42u32);
299 let none_val = CtSecretOption::none(0u32);
300
301 assert_eq!(some_val.is_some().unwrap_u8(), 1);
302 assert_eq!(none_val.is_none().unwrap_u8(), 1);
303
304 assert_eq!(some_val.unwrap_or(100), 42);
305 assert_eq!(none_val.unwrap_or(100), 100);
306 }
307
308 #[test]
309 fn test_ct_copy_bytes() {
310 let src = [1u8, 2, 3, 4];
311 let mut dest1 = [0u8; 4];
312 let mut dest2 = [0u8; 4];
313
314 let success1 = ct_copy_bytes(&mut dest1, &src, true);
315 let success2 = ct_copy_bytes(&mut dest2, &src, false);
316
317 assert!(success1, "Copy with choice=true should succeed");
318 assert!(success2, "Copy with choice=false should succeed (no-op)");
319 assert_eq!(dest1, src);
320 assert_eq!(dest2, [0, 0, 0, 0]);
321 }
322
323 #[test]
324 fn test_ct_copy_bytes_mismatched_length() {
325 let src_short = [1u8, 2];
328 let src_long = [1u8, 2, 3, 4, 5, 6];
329 let mut dest = [0u8; 4];
330
331 let result1 = ct_copy_bytes(&mut dest, &src_short, true);
333 assert!(!result1, "Mismatched length should return false");
334 assert_eq!(
335 dest,
336 [0, 0, 0, 0],
337 "Dest should be unchanged on length mismatch"
338 );
339
340 let result2 = ct_copy_bytes(&mut dest, &src_long, true);
341 assert!(!result2, "Mismatched length should return false");
342 assert_eq!(
343 dest,
344 [0, 0, 0, 0],
345 "Dest should be unchanged on length mismatch"
346 );
347 }
348
349 #[test]
350 fn test_constant_time_property() {
351 let secret1 = vec![0u8; 1000];
355 let secret2 = vec![1u8; 1000];
356
357 let _ = ct_eq(&secret1, &secret2);
359 let _ = ct_eq(&secret1, &secret1);
360
361 }
364}