Skip to main content

vitaminc_protected/timing_safe/
mod.rs

1pub use subtle::Choice;
2use subtle::ConstantTimeEq;
3
4/// This module defines the [`TimingSafeEq`] trait for performing equality
5/// checks in constant-time with respect to secret data, and the
6/// [`TimingSafeEq`] derive macro to automatically implement it for structs and enums.
7///
8/// ## Overview
9///
10/// * [`TimingSafeEq`] – compare two values in constant-time, returning a
11///   [`Choice`] instead of a `bool`.
12/// * [`TimingSafeEq`] – derive macro that implements [`TimingSafeEq`] and
13///   [`PartialEq`]/[`Eq`] by combining all fields’ `ts_eq` results with bitwise AND.
14///
15/// The derive requires that all fields of the type implement
16/// [`TimingSafeEq`], which you can do manually for your own field types
17/// or via blanket impls (e.g., for arrays and references).
18///
19/// Implementations are provided for the integer primitives, `bool`, `f32` and
20/// `f64` (bit-pattern equality, see the impl docs), `str`, `String`, `[u8]`,
21/// `Vec<u8>`, `[T; N]`, and `&T` for any `T: TimingSafeEq`.
22///
23/// The variable-length impls (`str`, `String`, `[u8]`, `Vec<u8>`) report a
24/// length mismatch as unequal without comparing contents, as `subtle`'s
25/// slice comparison does, so a length is never treated as secret.
26///
27/// Implementations of [`TimingSafeEq`] for many common types are included and internally use the [subtle](https://crates.io/crates/subtle) crate.
28///
29/// ## Examples
30///
31/// ### Implementing `TimingSafeEq` manually
32///
33/// ```
34/// # mod vitaminc { pub mod protected { pub use vitaminc_protected::*; } }
35/// use vitaminc::protected::{TimingSafeEq, Choice};
36///
37/// #[derive(Clone, Copy)]
38/// struct CtU8(u8);
39///
40/// impl TimingSafeEq for CtU8 {
41///     fn ts_eq(&self, other: &Self) -> Choice {
42///         // Branchless equality: (x ^ y) == 0
43///         let x = self.0 ^ other.0;
44///         Choice::from((x == 0) as u8)
45///     }
46/// }
47/// ```
48///
49/// ### Using `#[derive(TimingSafeEq)]`
50///
51/// ```
52/// # mod vitaminc { pub mod protected { pub use vitaminc_protected::*; } }
53/// use vitaminc::protected::{TimingSafeEq, Choice};
54///
55/// #[derive(TimingSafeEq)]
56/// struct Key<const N: usize>([u8; N]);
57///
58/// let k1 = Key::<3>([1, 2, 3]);
59/// let k2 = Key::<3>([1, 2, 3]);
60/// let k3 = Key::<3>([1, 2, 4]);
61///
62/// assert!(k1 == k2); // uses constant-time compare internally
63/// assert!(bool::from(k1.ts_eq(&k2)));
64///
65/// assert!(k1 != k3);
66/// assert!(!bool::from(k1.ts_eq(&k3)));
67/// ```
68///
69/// ### Naming the crate
70///
71/// The generated impls name `TimingSafeEq` and `Choice` by path, so no `use`
72/// is needed where the derive is applied. The derive finds that path in your
73/// `Cargo.toml`: a dependency on `vitaminc-protected` (under any name), or on
74/// `vitaminc` with the `protected` feature (`vitaminc::protected`). If you
75/// reach the crate some other way, such as a re-export, name it yourself:
76///
77/// ```
78/// mod reexport { pub mod protected { pub use vitaminc_protected::*; } }
79///
80/// #[derive(vitaminc_protected::TimingSafeEq)]
81/// #[timing_safe(crate = "reexport::protected")]
82/// struct Key([u8; 4]);
83///
84/// assert!(Key([1, 2, 3, 4]) == Key([1, 2, 3, 4]));
85/// ```
86///
87/// ## Notes
88///
89/// * The derive performs a field-wise AND of all `ts_eq` results; if any field differs, the result is `Choice(0)`.
90/// * All fields must implement `TimingSafeEq`, or the derive will fail to compile.
91/// * `PartialEq`/`Eq` are implemented in terms of `ts_eq` and return `bool`.
92/// * Do not derive `PartialOrd` or `Ord` from std beside this derive on a type
93///   with `f32` or `f64` fields. The derived `PartialEq` is bit-pattern
94///   equality (see the float impls), while std's `PartialOrd` for floats is
95///   IEEE ordering, so `a == b` would not imply `partial_cmp(a, b) ==
96///   Some(Equal)` (two equal `NaN`s, or `0.0` and `-0.0`), which `PartialOrd`
97///   requires. Nothing warns about it. Write the ordering by hand if the type
98///   needs one.
99/// * The derive works with generics and const generics; your field types must still satisfy `TimingSafeEq`.
100///
101pub trait TimingSafeEq {
102    /// Must be constant-time with respect to secret data.
103    fn ts_eq(&self, other: &Self) -> Choice;
104
105    fn ts_ne(&self, other: &Self) -> Choice {
106        !self.ts_eq(other)
107    }
108}
109
110/// Forwards to `T`, so a reference compares exactly as the value it points at.
111///
112/// This is what lets `#[derive(TimingSafeEq)]` handle borrowed fields such as
113/// `&'a str` and `&'a [u8]`: the derive compares fields by reference, so a
114/// `&'a [u8]` field is compared as `&&[u8]`, which needs `&[u8]: TimingSafeEq`.
115///
116/// A blanket impl is a one-way door (a downstream crate can no longer write
117/// its own `impl TimingSafeEq for &Theirs`), which is why there is no `&mut T`
118/// impl: nothing needs one yet.
119impl<T: TimingSafeEq + ?Sized> TimingSafeEq for &T {
120    fn ts_eq(&self, other: &Self) -> Choice {
121        T::ts_eq(*self, *other)
122    }
123}
124
125/// Compares the UTF-8 bytes in constant time.
126impl TimingSafeEq for str {
127    fn ts_eq(&self, other: &Self) -> subtle::Choice {
128        subtle::ConstantTimeEq::ct_eq(self.as_bytes(), other.as_bytes())
129    }
130}
131
132impl TimingSafeEq for String {
133    fn ts_eq(&self, other: &Self) -> subtle::Choice {
134        subtle::ConstantTimeEq::ct_eq(self.as_bytes(), other.as_bytes())
135    }
136}
137
138impl TimingSafeEq for [u8] {
139    fn ts_eq(&self, other: &Self) -> subtle::Choice {
140        subtle::ConstantTimeEq::ct_eq(self, other)
141    }
142}
143
144impl TimingSafeEq for Vec<u8> {
145    fn ts_eq(&self, other: &Self) -> subtle::Choice {
146        subtle::ConstantTimeEq::ct_eq(self.as_slice(), other.as_slice())
147    }
148}
149
150impl<T, const N: usize> TimingSafeEq for [T; N]
151where
152    [T]: TimingSafeEq,
153{
154    fn ts_eq(&self, other: &Self) -> subtle::Choice {
155        TimingSafeEq::ts_eq(self.as_slice(), other.as_slice())
156    }
157}
158
159macro_rules! impl_timing_safe_eq {
160    ($($type:ty),+) => {
161        $(
162            impl TimingSafeEq for $type {
163                fn ts_eq(&self, other: &Self) -> subtle::Choice {
164                    self.ct_eq(other)
165                }
166            }
167        )+
168    };
169}
170
171impl_timing_safe_eq!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize);
172
173/// Compares the two booleans as `u8` (`0` or `1`) in constant time.
174impl TimingSafeEq for bool {
175    fn ts_eq(&self, other: &Self) -> Choice {
176        (*self as u8).ct_eq(&(*other as u8))
177    }
178}
179
180/// Bit-pattern equality, **not** IEEE 754 `==`.
181///
182/// The two values are equal when [`f32::to_bits`] returns the same `u32` for
183/// both, compared in constant time. This differs from `==` in two places:
184///
185/// * a `NaN` equals itself (and any `NaN` with the same sign and payload),
186///   whereas `NaN == NaN` is `false`; two `NaN`s with different payloads are
187///   unequal;
188/// * `-0.0` and `0.0` are unequal, whereas `-0.0 == 0.0` is `true`.
189///
190/// Bit-pattern equality is the only definition that is both constant time and
191/// well defined for every value: IEEE `==` needs data-dependent special cases
192/// for `NaN` and signed zero, and is not reflexive, so it cannot back
193/// [`Eq`]. It is also what a sealed float needs, since the bits are what gets
194/// encrypted, and two floats that encrypt from different bits are different
195/// plaintexts.
196///
197/// Because of this, do not derive `PartialOrd` or `Ord` from std on a type
198/// whose `PartialEq` comes from `#[derive(TimingSafeEq)]` and holds a float:
199/// std's float ordering is IEEE, so `a == b` would not imply
200/// `partial_cmp(a, b) == Some(Equal)`.
201impl TimingSafeEq for f32 {
202    fn ts_eq(&self, other: &Self) -> Choice {
203        self.to_bits().ct_eq(&other.to_bits())
204    }
205}
206
207/// Bit-pattern equality, **not** IEEE 754 `==`.
208///
209/// The two values are equal when [`f64::to_bits`] returns the same `u64` for
210/// both, compared in constant time. This differs from `==` in two places:
211///
212/// * a `NaN` equals itself (and any `NaN` with the same sign and payload),
213///   whereas `NaN == NaN` is `false`; two `NaN`s with different payloads are
214///   unequal;
215/// * `-0.0` and `0.0` are unequal, whereas `-0.0 == 0.0` is `true`.
216///
217/// Bit-pattern equality is the only definition that is both constant time and
218/// well defined for every value: IEEE `==` needs data-dependent special cases
219/// for `NaN` and signed zero, and is not reflexive, so it cannot back
220/// [`Eq`]. It is also what a sealed float needs, since the bits are what gets
221/// encrypted, and two floats that encrypt from different bits are different
222/// plaintexts.
223///
224/// Because of this, do not derive `PartialOrd` or `Ord` from std on a type
225/// whose `PartialEq` comes from `#[derive(TimingSafeEq)]` and holds a float:
226/// std's float ordering is IEEE, so `a == b` would not imply
227/// `partial_cmp(a, b) == Some(Equal)`.
228impl TimingSafeEq for f64 {
229    fn ts_eq(&self, other: &Self) -> Choice {
230        self.to_bits().ct_eq(&other.to_bits())
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use super::TimingSafeEq;
237
238    // `ts_ne` is the negation of `ts_eq`; without this, deleting the `!` in the
239    // default impl (so `ts_ne` == `ts_eq`) goes unnoticed. See issue #207.
240    #[test]
241    fn ts_ne_negates_ts_eq() {
242        let a: u8 = 5;
243        let equal: u8 = 5;
244        let different: u8 = 6;
245
246        // Equal values: ts_eq true, ts_ne false.
247        assert!(bool::from(a.ts_eq(&equal)));
248        assert!(!bool::from(a.ts_ne(&equal)));
249
250        // Different values: ts_eq false, ts_ne true.
251        assert!(!bool::from(a.ts_eq(&different)));
252        assert!(bool::from(a.ts_ne(&different)));
253    }
254
255    fn eq<T: TimingSafeEq + ?Sized>(a: &T, b: &T) -> bool {
256        bool::from(a.ts_eq(b))
257    }
258
259    #[test]
260    fn bool_compares_by_value() {
261        assert!(eq(&true, &true));
262        assert!(eq(&false, &false));
263        assert!(!eq(&true, &false));
264        assert!(!eq(&false, &true));
265    }
266
267    #[test]
268    fn f32_is_bit_pattern_equality() {
269        assert!(eq(&1.5f32, &1.5f32));
270        assert!(!eq(&1.5f32, &2.5f32));
271
272        // Same NaN bits are equal, unlike IEEE `==`.
273        let nan = f32::NAN;
274        assert!(eq(&nan, &nan));
275
276        // NaNs with different payloads are unequal.
277        let other_nan = f32::from_bits(nan.to_bits() ^ 1);
278        assert!(other_nan.is_nan());
279        assert!(!eq(&nan, &other_nan));
280
281        // Signed zeros are unequal, unlike IEEE `==`.
282        assert!(!eq(&-0.0f32, &0.0f32));
283        assert!(eq(&-0.0f32, &-0.0f32));
284    }
285
286    #[test]
287    fn f64_is_bit_pattern_equality() {
288        assert!(eq(&1.5f64, &1.5f64));
289        assert!(!eq(&1.5f64, &2.5f64));
290
291        let nan = f64::NAN;
292        assert!(eq(&nan, &nan));
293
294        let other_nan = f64::from_bits(nan.to_bits() ^ 1);
295        assert!(other_nan.is_nan());
296        assert!(!eq(&nan, &other_nan));
297
298        assert!(!eq(&-0.0f64, &0.0f64));
299        assert!(eq(&-0.0f64, &-0.0f64));
300    }
301
302    #[test]
303    fn str_compares_bytes() {
304        assert!(eq("abc", "abc"));
305        assert!(!eq("abc", "abd"));
306        assert!(!eq("abc", "abcd"));
307    }
308
309    #[test]
310    fn shared_reference_forwards() {
311        let a: &[u8] = &[1, 2, 3];
312        let b: &[u8] = &[1, 2, 3];
313        let c: &[u8] = &[1, 2, 4];
314        let d: &[u8] = &[1, 2];
315        assert!(eq(&a, &b));
316        assert!(!eq(&a, &c));
317        assert!(!eq(&a, &d));
318
319        assert!(eq(&"abc", &"abc"));
320        assert!(!eq(&"abc", &"abd"));
321    }
322}