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 core::hint::black_box;
7use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
8use zeroize::Zeroize;
9
10/// Constant-time comparison for byte slices
11///
12/// Returns true if the slices are equal, false otherwise.
13/// The comparison runs in constant time regardless of where differences occur.
14///
15/// # Security Note
16/// This function is designed to be constant-time to prevent timing attacks.
17/// The length comparison is also performed in constant-time.
18#[must_use]
19pub fn ct_eq(a: &[u8], b: &[u8]) -> bool {
20    // Constant-time length comparison
21    let len_equal = a.len().ct_eq(&b.len());
22
23    // Pad the shorter slice conceptually to avoid early returns
24    // We'll compare up to the maximum length
25    let max_len = a.len().max(b.len());
26    let min_len = a.len().min(b.len());
27
28    // Compare the common portion
29    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 the remaining portion (if lengths differ),
35    // we still need to do work to maintain constant time
36    for _ in min_len..max_len {
37        // Perform dummy operations to maintain constant time
38        // Use black_box to prevent optimization
39        let dummy = black_box(0u8);
40        content_equal &= dummy.ct_eq(&dummy);
41    }
42
43    // Both lengths and contents must be equal
44    let result = len_equal & content_equal;
45
46    // Use black_box to prevent the compiler from optimizing away the comparison
47    black_box(result.into())
48}
49
50/// Constant-time conditional selection
51///
52/// Selects `a` if `choice` is true, `b` otherwise.
53/// The selection happens in constant time.
54#[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/// Constant-time conditional assignment
60///
61/// Assigns `new_val` to `dest` if `choice` is true.
62/// The assignment happens in constant time.
63#[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
68/// Constant-time option type for cryptographic operations
69///
70/// Similar to `Option<T>` but with constant-time operations.
71pub struct CtSecretOption<T> {
72    value: T,
73    is_some: Choice,
74}
75
76impl<T> CtSecretOption<T> {
77    /// Create a new Some variant
78    #[inline]
79    pub fn some(value: T) -> Self {
80        Self {
81            value,
82            is_some: Choice::from(1),
83        }
84    }
85
86    /// Create a new None variant
87    #[inline]
88    pub fn none(default: T) -> Self {
89        Self {
90            value: default,
91            is_some: Choice::from(0),
92        }
93    }
94
95    /// Check if the option contains a value (constant-time)
96    #[inline]
97    pub const fn is_some(&self) -> Choice {
98        self.is_some
99    }
100
101    /// Check if the option is None (constant-time)
102    #[inline]
103    pub fn is_none(&self) -> Choice {
104        !self.is_some
105    }
106
107    /// Unwrap the value with a default if None
108    #[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    /// Map the value if Some
117    #[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
139/// Trait for types that support constant-time equality comparison
140pub trait ConstantTimeEqExt: Sized {
141    /// Perform constant-time equality comparison
142    fn ct_eq(&self, other: &Self) -> Choice;
143
144    /// Perform constant-time inequality comparison
145    fn ct_ne(&self, other: &Self) -> Choice {
146        !self.ct_eq(other)
147    }
148}
149
150/// Implement constant-time comparison for secret key types
151macro_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
161// Import types that need constant-time operations
162use 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
168// Implement constant-time comparison for all sensitive types
169impl_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
177// Implement for signatures
178impl 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/// Perform constant-time verification of a boolean condition
197///
198/// Returns a `CtOption` that is Some(value) if condition is true, None otherwise.
199/// The operation runs in constant time.
200#[inline]
201pub fn ct_verify<T>(condition: bool, value: T) -> CtOption<T> {
202    CtOption::new(value, Choice::from(u8::from(condition)))
203}
204
205/// Constant-time byte array comparison
206///
207/// Compares two fixed-size byte arrays in constant time.
208#[must_use]
209pub fn ct_array_eq<const N: usize>(a: &[u8; N], b: &[u8; N]) -> bool {
210    // Use black_box to prevent optimization
211    let result = a.ct_eq(b);
212    black_box(result.into())
213}
214
215/// Clear sensitive data from memory in constant time
216///
217/// This ensures the compiler doesn't optimize away the clearing operation.
218#[inline]
219pub fn ct_clear<T: Zeroize>(data: &mut T) {
220    data.zeroize();
221}
222
223/// Constant-time conditional copy
224///
225/// Copies `src` to `dest` if `choice` is true.
226/// The operation runs in constant time.
227#[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)); // Different lengths
253    }
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        // This test doesn't verify constant-time execution directly
292        // (that requires specialized tools), but ensures the API works correctly
293
294        let secret1 = vec![0u8; 1000];
295        let secret2 = vec![1u8; 1000];
296
297        // These operations should take the same time regardless of content
298        let _ = ct_eq(&secret1, &secret2);
299        let _ = ct_eq(&secret1, &secret1);
300
301        // The actual constant-time property would be verified with tools like
302        // valgrind, dudect, or specialized timing analysis
303    }
304}