Skip to main content

dcrypt_internal/
constant_time.rs

1//! Constant-time operations to prevent timing attacks.
2//!
3//! These small primitives are owned by dcrypt so the published implementation
4//! does not need an unsafe-containing dependency for masks and comparisons.
5
6use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, Not};
7
8use crate::zeroing::Zeroize;
9
10/// A one-bit value used by constant-time operations.
11#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
12pub struct Choice(u8);
13
14impl Choice {
15    /// Return the normalized value (`0` or `1`).
16    #[inline(always)]
17    pub const fn unwrap_u8(self) -> u8 {
18        self.0
19    }
20}
21
22impl From<u8> for Choice {
23    #[inline(always)]
24    fn from(value: u8) -> Self {
25        Self(value & 1)
26    }
27}
28
29impl From<bool> for Choice {
30    #[inline(always)]
31    fn from(value: bool) -> Self {
32        Self(value as u8)
33    }
34}
35
36impl From<Choice> for bool {
37    #[inline(always)]
38    fn from(value: Choice) -> Self {
39        value.0 == 1
40    }
41}
42
43impl From<Choice> for u8 {
44    #[inline(always)]
45    fn from(value: Choice) -> Self {
46        value.0
47    }
48}
49
50impl Zeroize for Choice {
51    #[inline(never)]
52    fn zeroize(&mut self) {
53        self.0.zeroize();
54    }
55}
56
57impl Not for Choice {
58    type Output = Self;
59
60    #[inline(always)]
61    fn not(self) -> Self::Output {
62        Self(self.0 ^ 1)
63    }
64}
65
66impl BitAnd for Choice {
67    type Output = Self;
68
69    #[inline(always)]
70    fn bitand(self, rhs: Self) -> Self::Output {
71        Self(self.0 & rhs.0)
72    }
73}
74
75impl BitAndAssign for Choice {
76    #[inline(always)]
77    fn bitand_assign(&mut self, rhs: Self) {
78        self.0 &= rhs.0;
79    }
80}
81
82impl BitOr for Choice {
83    type Output = Self;
84
85    #[inline(always)]
86    fn bitor(self, rhs: Self) -> Self::Output {
87        Self(self.0 | rhs.0)
88    }
89}
90
91impl BitOrAssign for Choice {
92    #[inline(always)]
93    fn bitor_assign(&mut self, rhs: Self) {
94        self.0 |= rhs.0;
95    }
96}
97
98impl BitXor for Choice {
99    type Output = Self;
100
101    #[inline(always)]
102    fn bitxor(self, rhs: Self) -> Self::Output {
103        Self(self.0 ^ rhs.0)
104    }
105}
106
107/// Select between two values without branching on `choice`.
108pub trait ConditionallySelectable: Copy {
109    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self;
110}
111
112macro_rules! impl_conditionally_selectable_integer {
113    ($($ty:ty),+ $(,)?) => {$ (
114        impl ConditionallySelectable for $ty {
115            #[inline(always)]
116            fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
117                let mask = (0 as $ty).wrapping_sub(choice.unwrap_u8() as $ty);
118                a ^ (mask & (a ^ b))
119            }
120        }
121    )+ };
122}
123
124impl_conditionally_selectable_integer!(u8, u16, u32, u64, u128, usize);
125
126impl ConditionallySelectable for Choice {
127    #[inline(always)]
128    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
129        Self(u8::conditional_select(&a.0, &b.0, choice))
130    }
131}
132
133/// Compare two values without data-dependent early exit.
134pub trait ConstantTimeEq {
135    fn ct_eq(&self, other: &Self) -> Choice;
136}
137
138macro_rules! impl_constant_time_eq_integer {
139    ($($ty:ty),+ $(,)?) => {$ (
140        impl ConstantTimeEq for $ty {
141            #[inline(always)]
142            fn ct_eq(&self, other: &Self) -> Choice {
143                let difference = self ^ other;
144                let nonzero = difference | difference.wrapping_neg();
145                Choice::from(((nonzero >> (<$ty>::BITS - 1)) as u8) ^ 1)
146            }
147        }
148    )+ };
149}
150
151impl_constant_time_eq_integer!(u8, u16, u32, u64, u128, usize);
152
153impl ConstantTimeEq for Choice {
154    #[inline(always)]
155    fn ct_eq(&self, other: &Self) -> Choice {
156        self.0.ct_eq(&other.0)
157    }
158}
159
160impl<T: ConstantTimeEq, const N: usize> ConstantTimeEq for [T; N] {
161    fn ct_eq(&self, other: &Self) -> Choice {
162        let mut equal = Choice::from(1u8);
163        for index in 0..N {
164            equal &= self[index].ct_eq(&other[index]);
165        }
166        equal
167    }
168}
169
170impl<T: ConstantTimeEq> ConstantTimeEq for [T] {
171    fn ct_eq(&self, other: &Self) -> Choice {
172        if self.len() != other.len() {
173            return Choice::from(0u8);
174        }
175        let mut equal = Choice::from(1u8);
176        for (left, right) in self.iter().zip(other) {
177            equal &= left.ct_eq(right);
178        }
179        equal
180    }
181}
182
183/// An option whose validity is represented by a [`Choice`].
184#[derive(Clone, Copy, Debug)]
185pub struct CtOption<T> {
186    value: T,
187    is_some: Choice,
188}
189
190impl<T> CtOption<T> {
191    pub const fn new(value: T, is_some: Choice) -> Self {
192        Self { value, is_some }
193    }
194
195    pub const fn is_some(&self) -> Choice {
196        self.is_some
197    }
198
199    pub fn is_none(&self) -> Choice {
200        !self.is_some
201    }
202
203    pub fn unwrap(self) -> T {
204        assert!(
205            bool::from(self.is_some),
206            "called CtOption::unwrap on an invalid value"
207        );
208        self.value
209    }
210
211    pub fn unwrap_or(self, default: T) -> T
212    where
213        T: ConditionallySelectable,
214    {
215        T::conditional_select(&default, &self.value, self.is_some)
216    }
217
218    pub fn unwrap_or_else<F>(self, default: F) -> T
219    where
220        T: ConditionallySelectable,
221        F: FnOnce() -> T,
222    {
223        self.unwrap_or(default())
224    }
225
226    pub fn and_then<U, F>(self, function: F) -> CtOption<U>
227    where
228        F: FnOnce(T) -> CtOption<U>,
229    {
230        let next = function(self.value);
231        CtOption::new(next.value, self.is_some & next.is_some)
232    }
233
234    pub fn map<U, F>(self, function: F) -> CtOption<U>
235    where
236        F: FnOnce(T) -> U,
237    {
238        CtOption::new(function(self.value), self.is_some)
239    }
240
241    pub fn into_option(self) -> Option<T> {
242        self.into()
243    }
244
245    pub fn or_else<F>(self, function: F) -> Self
246    where
247        T: ConditionallySelectable,
248        F: FnOnce() -> Self,
249    {
250        let alternative = function();
251        Self::new(
252            T::conditional_select(&alternative.value, &self.value, self.is_some),
253            self.is_some | alternative.is_some,
254        )
255    }
256}
257
258impl<T> From<CtOption<T>> for Option<T> {
259    fn from(value: CtOption<T>) -> Self {
260        if bool::from(value.is_some) {
261            Some(value.value)
262        } else {
263            None
264        }
265    }
266}
267
268/// Constant-time comparison of two byte slices
269///
270/// Returns true if the slices are equal, false otherwise. Length is treated as
271/// public and a mismatch returns early; for equal lengths, comparison has no
272/// data-dependent early exit in this source implementation. Concrete compiler
273/// and target behavior remains subject to the release assembly checks.
274pub fn ct_eq<A, B>(a: A, b: B) -> bool
275where
276    A: AsRef<[u8]>,
277    B: AsRef<[u8]>,
278{
279    let a = a.as_ref();
280    let b = b.as_ref();
281
282    if a.len() != b.len() {
283        return false;
284    }
285
286    bool::from(a.ct_eq(b))
287}