saorsa_pqc/pqc/
constant_time.rs

1//! Constant-time operations for cryptographic primitives
2//!
3//! This module provides constant-time comparison and conditional operations
4//! to prevent timing attacks on sensitive cryptographic data.
5
6use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
7use zeroize::Zeroize;
8
9/// Constant-time comparison for byte slices
10///
11/// Returns true if the slices are equal, false otherwise.
12/// The comparison runs in constant time regardless of where differences occur.
13#[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/// Constant-time conditional selection
23///
24/// Selects `a` if `choice` is true, `b` otherwise.
25/// The selection happens in constant time.
26#[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/// Constant-time conditional assignment
32///
33/// Assigns `new_val` to `dest` if `choice` is true.
34/// The assignment happens in constant time.
35#[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
40/// Constant-time option type for cryptographic operations
41///
42/// Similar to `Option<T>` but with constant-time operations.
43pub struct CtSecretOption<T> {
44    value: T,
45    is_some: Choice,
46}
47
48impl<T> CtSecretOption<T> {
49    /// Create a new Some variant
50    #[inline]
51    pub fn some(value: T) -> Self {
52        Self {
53            value,
54            is_some: Choice::from(1),
55        }
56    }
57
58    /// Create a new None variant
59    #[inline]
60    pub fn none(default: T) -> Self {
61        Self {
62            value: default,
63            is_some: Choice::from(0),
64        }
65    }
66
67    /// Check if the option contains a value (constant-time)
68    #[inline]
69    pub const fn is_some(&self) -> Choice {
70        self.is_some
71    }
72
73    /// Check if the option is None (constant-time)
74    #[inline]
75    pub fn is_none(&self) -> Choice {
76        !self.is_some
77    }
78
79    /// Unwrap the value with a default if None
80    #[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    /// Map the value if Some
89    #[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
111/// Trait for types that support constant-time equality comparison
112pub trait ConstantTimeEqExt: Sized {
113    /// Perform constant-time equality comparison
114    fn ct_eq(&self, other: &Self) -> Choice;
115
116    /// Perform constant-time inequality comparison
117    fn ct_ne(&self, other: &Self) -> Choice {
118        !self.ct_eq(other)
119    }
120}
121
122/// Implement constant-time comparison for secret key types
123macro_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
133// Import types that need constant-time operations
134use 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
140// Implement constant-time comparison for all sensitive types
141impl_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
149// Implement for signatures
150impl 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/// Perform constant-time verification of a boolean condition
169///
170/// Returns a `CtOption` that is Some(value) if condition is true, None otherwise.
171/// The operation runs in constant time.
172#[inline]
173pub fn ct_verify<T>(condition: bool, value: T) -> CtOption<T> {
174    CtOption::new(value, Choice::from(u8::from(condition)))
175}
176
177/// Constant-time byte array comparison
178///
179/// Compares two fixed-size byte arrays in constant time.
180#[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/// Clear sensitive data from memory in constant time
187///
188/// This ensures the compiler doesn't optimize away the clearing operation.
189#[inline]
190pub fn ct_clear<T: Zeroize>(data: &mut T) {
191    data.zeroize();
192}
193
194/// Constant-time conditional copy
195///
196/// Copies `src` to `dest` if `choice` is true.
197/// The operation runs in constant time.
198#[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)); // Different lengths
224    }
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        // This test doesn't verify constant-time execution directly
263        // (that requires specialized tools), but ensures the API works correctly
264
265        let secret1 = vec![0u8; 1000];
266        let secret2 = vec![1u8; 1000];
267
268        // These operations should take the same time regardless of content
269        let _ = ct_eq(&secret1, &secret2);
270        let _ = ct_eq(&secret1, &secret1);
271
272        // The actual constant-time property would be verified with tools like
273        // valgrind, dudect, or specialized timing analysis
274    }
275}