saorsa_pqc/pqc/
constant_time.rs1use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
7use zeroize::Zeroize;
8
9#[inline]
14#[must_use]
15pub fn ct_eq(a: &[u8], b: &[u8]) -> bool {
16 if a.len() != b.len() {
17 return false;
18 }
19 a.ct_eq(b).into()
20}
21
22#[inline]
27pub fn ct_select<T: ConditionallySelectable>(a: &T, b: &T, choice: bool) -> T {
28 T::conditional_select(b, a, Choice::from(u8::from(choice)))
29}
30
31#[inline]
36pub fn ct_assign<T: ConditionallySelectable>(dest: &mut T, new_val: &T, choice: bool) {
37 dest.conditional_assign(new_val, Choice::from(u8::from(choice)));
38}
39
40pub struct CtSecretOption<T> {
44 value: T,
45 is_some: Choice,
46}
47
48impl<T> CtSecretOption<T> {
49 #[inline]
51 pub fn some(value: T) -> Self {
52 Self {
53 value,
54 is_some: Choice::from(1),
55 }
56 }
57
58 #[inline]
60 pub fn none(default: T) -> Self {
61 Self {
62 value: default,
63 is_some: Choice::from(0),
64 }
65 }
66
67 #[inline]
69 pub const fn is_some(&self) -> Choice {
70 self.is_some
71 }
72
73 #[inline]
75 pub fn is_none(&self) -> Choice {
76 !self.is_some
77 }
78
79 #[inline]
81 pub fn unwrap_or(self, default: T) -> T
82 where
83 T: ConditionallySelectable,
84 {
85 T::conditional_select(&default, &self.value, self.is_some)
86 }
87
88 #[inline]
90 pub fn map<U, F>(self, f: F) -> CtSecretOption<U>
91 where
92 F: FnOnce(T) -> U,
93 U: ConditionallySelectable + Default,
94 {
95 let mapped = f(self.value);
96 let default = U::default();
97 CtSecretOption {
98 value: U::conditional_select(&default, &mapped, self.is_some),
99 is_some: self.is_some,
100 }
101 }
102}
103
104impl<T: Zeroize> Zeroize for CtSecretOption<T> {
105 fn zeroize(&mut self) {
106 self.value.zeroize();
107 self.is_some = Choice::from(0);
108 }
109}
110
111pub trait ConstantTimeEqExt: Sized {
113 fn ct_eq(&self, other: &Self) -> Choice;
115
116 fn ct_ne(&self, other: &Self) -> Choice {
118 !self.ct_eq(other)
119 }
120}
121
122macro_rules! impl_ct_eq_for_secret {
124 ($type:ty) => {
125 impl ConstantTimeEqExt for $type {
126 fn ct_eq(&self, other: &Self) -> Choice {
127 self.as_bytes().ct_eq(other.as_bytes())
128 }
129 }
130 };
131}
132
133use crate::pqc::ml_dsa_44::{MlDsa44SecretKey, MlDsa44Signature};
135use crate::pqc::ml_dsa_87::{MlDsa87SecretKey, MlDsa87Signature};
136use crate::pqc::ml_kem_1024::MlKem1024SecretKey;
137use crate::pqc::ml_kem_512::MlKem512SecretKey;
138use crate::pqc::types::{MlDsaSecretKey, MlDsaSignature, MlKemSecretKey, SharedSecret};
139
140impl_ct_eq_for_secret!(MlKemSecretKey);
142impl_ct_eq_for_secret!(MlDsaSecretKey);
143impl_ct_eq_for_secret!(SharedSecret);
144impl_ct_eq_for_secret!(MlKem512SecretKey);
145impl_ct_eq_for_secret!(MlKem1024SecretKey);
146impl_ct_eq_for_secret!(MlDsa44SecretKey);
147impl_ct_eq_for_secret!(MlDsa87SecretKey);
148
149impl ConstantTimeEqExt for MlDsaSignature {
151 fn ct_eq(&self, other: &Self) -> Choice {
152 self.as_bytes().ct_eq(other.as_bytes())
153 }
154}
155
156impl ConstantTimeEqExt for MlDsa44Signature {
157 fn ct_eq(&self, other: &Self) -> Choice {
158 self.as_bytes().ct_eq(other.as_bytes())
159 }
160}
161
162impl ConstantTimeEqExt for MlDsa87Signature {
163 fn ct_eq(&self, other: &Self) -> Choice {
164 self.as_bytes().ct_eq(other.as_bytes())
165 }
166}
167
168#[inline]
173pub fn ct_verify<T>(condition: bool, value: T) -> CtOption<T> {
174 CtOption::new(value, Choice::from(u8::from(condition)))
175}
176
177#[inline]
181#[must_use]
182pub fn ct_array_eq<const N: usize>(a: &[u8; N], b: &[u8; N]) -> bool {
183 a.ct_eq(b).into()
184}
185
186#[inline]
190pub fn ct_clear<T: Zeroize>(data: &mut T) {
191 data.zeroize();
192}
193
194#[inline]
199pub fn ct_copy_bytes(dest: &mut [u8], src: &[u8], choice: bool) {
200 if dest.len() != src.len() {
201 return;
202 }
203
204 let choice = Choice::from(u8::from(choice));
205 for (d, s) in dest.iter_mut().zip(src.iter()) {
206 d.conditional_assign(s, choice);
207 }
208}
209
210#[cfg(test)]
211#[allow(clippy::unwrap_used, clippy::expect_used)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_ct_eq() {
217 let a = [1u8, 2, 3, 4];
218 let b = [1u8, 2, 3, 4];
219 let c = [1u8, 2, 3, 5];
220
221 assert!(ct_eq(&a, &b));
222 assert!(!ct_eq(&a, &c));
223 assert!(!ct_eq(&a[..3], &b)); }
225
226 #[test]
227 fn test_ct_select() {
228 let a = 42u32;
229 let b = 100u32;
230
231 assert_eq!(ct_select(&a, &b, true), a);
232 assert_eq!(ct_select(&a, &b, false), b);
233 }
234
235 #[test]
236 fn test_ct_option() {
237 let some_val = CtSecretOption::some(42u32);
238 let none_val = CtSecretOption::none(0u32);
239
240 assert_eq!(some_val.is_some().unwrap_u8(), 1);
241 assert_eq!(none_val.is_none().unwrap_u8(), 1);
242
243 assert_eq!(some_val.unwrap_or(100), 42);
244 assert_eq!(none_val.unwrap_or(100), 100);
245 }
246
247 #[test]
248 fn test_ct_copy_bytes() {
249 let src = [1u8, 2, 3, 4];
250 let mut dest1 = [0u8; 4];
251 let mut dest2 = [0u8; 4];
252
253 ct_copy_bytes(&mut dest1, &src, true);
254 ct_copy_bytes(&mut dest2, &src, false);
255
256 assert_eq!(dest1, src);
257 assert_eq!(dest2, [0, 0, 0, 0]);
258 }
259
260 #[test]
261 fn test_constant_time_property() {
262 let secret1 = vec![0u8; 1000];
266 let secret2 = vec![1u8; 1000];
267
268 let _ = ct_eq(&secret1, &secret2);
270 let _ = ct_eq(&secret1, &secret1);
271
272 }
275}