saorsa_pqc/pqc/
constant_time.rs1use core::hint::black_box;
7use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
8use zeroize::Zeroize;
9
10#[must_use]
19pub fn ct_eq(a: &[u8], b: &[u8]) -> bool {
20 let len_equal = a.len().ct_eq(&b.len());
22
23 let max_len = a.len().max(b.len());
26 let min_len = a.len().min(b.len());
27
28 let mut content_equal = Choice::from(1u8);
30 for i in 0..min_len {
31 content_equal &= a[i].ct_eq(&b[i]);
32 }
33
34 for _ in min_len..max_len {
37 let dummy = black_box(0u8);
40 content_equal &= dummy.ct_eq(&dummy);
41 }
42
43 let result = len_equal & content_equal;
45
46 black_box(result.into())
48}
49
50#[inline]
55pub fn ct_select<T: ConditionallySelectable>(a: &T, b: &T, choice: bool) -> T {
56 T::conditional_select(b, a, Choice::from(u8::from(choice)))
57}
58
59#[inline]
64pub fn ct_assign<T: ConditionallySelectable>(dest: &mut T, new_val: &T, choice: bool) {
65 dest.conditional_assign(new_val, Choice::from(u8::from(choice)));
66}
67
68pub struct CtSecretOption<T> {
72 value: T,
73 is_some: Choice,
74}
75
76impl<T> CtSecretOption<T> {
77 #[inline]
79 pub fn some(value: T) -> Self {
80 Self {
81 value,
82 is_some: Choice::from(1),
83 }
84 }
85
86 #[inline]
88 pub fn none(default: T) -> Self {
89 Self {
90 value: default,
91 is_some: Choice::from(0),
92 }
93 }
94
95 #[inline]
97 pub const fn is_some(&self) -> Choice {
98 self.is_some
99 }
100
101 #[inline]
103 pub fn is_none(&self) -> Choice {
104 !self.is_some
105 }
106
107 #[inline]
109 pub fn unwrap_or(self, default: T) -> T
110 where
111 T: ConditionallySelectable,
112 {
113 T::conditional_select(&default, &self.value, self.is_some)
114 }
115
116 #[inline]
118 pub fn map<U, F>(self, f: F) -> CtSecretOption<U>
119 where
120 F: FnOnce(T) -> U,
121 U: ConditionallySelectable + Default,
122 {
123 let mapped = f(self.value);
124 let default = U::default();
125 CtSecretOption {
126 value: U::conditional_select(&default, &mapped, self.is_some),
127 is_some: self.is_some,
128 }
129 }
130}
131
132impl<T: Zeroize> Zeroize for CtSecretOption<T> {
133 fn zeroize(&mut self) {
134 self.value.zeroize();
135 self.is_some = Choice::from(0);
136 }
137}
138
139pub trait ConstantTimeEqExt: Sized {
141 fn ct_eq(&self, other: &Self) -> Choice;
143
144 fn ct_ne(&self, other: &Self) -> Choice {
146 !self.ct_eq(other)
147 }
148}
149
150macro_rules! impl_ct_eq_for_secret {
152 ($type:ty) => {
153 impl ConstantTimeEqExt for $type {
154 fn ct_eq(&self, other: &Self) -> Choice {
155 self.as_bytes().ct_eq(other.as_bytes())
156 }
157 }
158 };
159}
160
161use crate::pqc::ml_dsa_44::{MlDsa44SecretKey, MlDsa44Signature};
163use crate::pqc::ml_dsa_87::{MlDsa87SecretKey, MlDsa87Signature};
164use crate::pqc::ml_kem_1024::MlKem1024SecretKey;
165use crate::pqc::ml_kem_512::MlKem512SecretKey;
166use crate::pqc::types::{MlDsaSecretKey, MlDsaSignature, MlKemSecretKey, SharedSecret};
167
168impl_ct_eq_for_secret!(MlKemSecretKey);
170impl_ct_eq_for_secret!(MlDsaSecretKey);
171impl_ct_eq_for_secret!(SharedSecret);
172impl_ct_eq_for_secret!(MlKem512SecretKey);
173impl_ct_eq_for_secret!(MlKem1024SecretKey);
174impl_ct_eq_for_secret!(MlDsa44SecretKey);
175impl_ct_eq_for_secret!(MlDsa87SecretKey);
176
177impl ConstantTimeEqExt for MlDsaSignature {
179 fn ct_eq(&self, other: &Self) -> Choice {
180 self.as_bytes().ct_eq(other.as_bytes())
181 }
182}
183
184impl ConstantTimeEqExt for MlDsa44Signature {
185 fn ct_eq(&self, other: &Self) -> Choice {
186 self.as_bytes().ct_eq(other.as_bytes())
187 }
188}
189
190impl ConstantTimeEqExt for MlDsa87Signature {
191 fn ct_eq(&self, other: &Self) -> Choice {
192 self.as_bytes().ct_eq(other.as_bytes())
193 }
194}
195
196#[inline]
201pub fn ct_verify<T>(condition: bool, value: T) -> CtOption<T> {
202 CtOption::new(value, Choice::from(u8::from(condition)))
203}
204
205#[must_use]
209pub fn ct_array_eq<const N: usize>(a: &[u8; N], b: &[u8; N]) -> bool {
210 let result = a.ct_eq(b);
212 black_box(result.into())
213}
214
215#[inline]
219pub fn ct_clear<T: Zeroize>(data: &mut T) {
220 data.zeroize();
221}
222
223#[inline]
228pub fn ct_copy_bytes(dest: &mut [u8], src: &[u8], choice: bool) {
229 if dest.len() != src.len() {
230 return;
231 }
232
233 let choice = Choice::from(u8::from(choice));
234 for (d, s) in dest.iter_mut().zip(src.iter()) {
235 d.conditional_assign(s, choice);
236 }
237}
238
239#[cfg(test)]
240#[allow(clippy::unwrap_used, clippy::expect_used)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn test_ct_eq() {
246 let a = [1u8, 2, 3, 4];
247 let b = [1u8, 2, 3, 4];
248 let c = [1u8, 2, 3, 5];
249
250 assert!(ct_eq(&a, &b));
251 assert!(!ct_eq(&a, &c));
252 assert!(!ct_eq(&a[..3], &b)); }
254
255 #[test]
256 fn test_ct_select() {
257 let a = 42u32;
258 let b = 100u32;
259
260 assert_eq!(ct_select(&a, &b, true), a);
261 assert_eq!(ct_select(&a, &b, false), b);
262 }
263
264 #[test]
265 fn test_ct_option() {
266 let some_val = CtSecretOption::some(42u32);
267 let none_val = CtSecretOption::none(0u32);
268
269 assert_eq!(some_val.is_some().unwrap_u8(), 1);
270 assert_eq!(none_val.is_none().unwrap_u8(), 1);
271
272 assert_eq!(some_val.unwrap_or(100), 42);
273 assert_eq!(none_val.unwrap_or(100), 100);
274 }
275
276 #[test]
277 fn test_ct_copy_bytes() {
278 let src = [1u8, 2, 3, 4];
279 let mut dest1 = [0u8; 4];
280 let mut dest2 = [0u8; 4];
281
282 ct_copy_bytes(&mut dest1, &src, true);
283 ct_copy_bytes(&mut dest2, &src, false);
284
285 assert_eq!(dest1, src);
286 assert_eq!(dest2, [0, 0, 0, 0]);
287 }
288
289 #[test]
290 fn test_constant_time_property() {
291 let secret1 = vec![0u8; 1000];
295 let secret2 = vec![1u8; 1000];
296
297 let _ = ct_eq(&secret1, &secret2);
299 let _ = ct_eq(&secret1, &secret1);
300
301 }
304}