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}