constant_time_eq/lib.rs
1#![no_std]
2
3#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
4#[cfg(not(miri))]
5#[inline]
6#[must_use]
7fn optimizer_hide(mut value: u8) -> u8 {
8 // SAFETY: the input value is passed unchanged to the output, the inline assembly does nothing.
9 unsafe {
10 core::arch::asm!("/* {0} */", inout(reg_byte) value, options(pure, nomem, nostack, preserves_flags));
11 value
12 }
13}
14
15#[cfg(any(
16 target_arch = "arm",
17 target_arch = "aarch64",
18 target_arch = "riscv32",
19 target_arch = "riscv64"
20))]
21#[cfg(not(miri))]
22#[inline]
23#[must_use]
24#[allow(asm_sub_register)]
25fn optimizer_hide(mut value: u8) -> u8 {
26 // SAFETY: the input value is passed unchanged to the output, the inline assembly does nothing.
27 unsafe {
28 core::arch::asm!("/* {0} */", inout(reg) value, options(pure, nomem, nostack, preserves_flags));
29 value
30 }
31}
32
33#[cfg(any(
34 not(any(
35 target_arch = "x86",
36 target_arch = "x86_64",
37 target_arch = "arm",
38 target_arch = "aarch64",
39 target_arch = "riscv32",
40 target_arch = "riscv64",
41 )),
42 miri,
43))]
44#[inline(never)]
45#[must_use]
46fn optimizer_hide(value: u8) -> u8 {
47 // The current implementation of black_box in the main codegen backends is similar to
48 // {
49 // let result = value;
50 // asm!("", in(reg) &result);
51 // result
52 // }
53 // which round-trips the value through the stack, instead of leaving it in a register.
54 // Experimental codegen backends might implement black_box as a pure identity function,
55 // without the expected optimization barrier, so it's less guaranteed than inline asm.
56 // For that reason, we also use the #[inline(never)] hint, which makes it harder for an
57 // optimizer to look inside this function.
58 core::hint::black_box(value)
59}
60
61#[inline]
62#[must_use]
63fn constant_time_ne(a: &[u8], b: &[u8]) -> u8 {
64 assert!(a.len() == b.len());
65
66 // These useless slices make the optimizer elide the bounds checks.
67 // See the comment in clone_from_slice() added on Rust commit 6a7bc47.
68 let len = a.len();
69 let a = &a[..len];
70 let b = &b[..len];
71
72 let mut tmp = 0;
73 for i in 0..len {
74 tmp |= a[i] ^ b[i];
75 }
76
77 // The compare with 0 must happen outside this function.
78 optimizer_hide(tmp)
79}
80
81/// Compares two equal-sized byte strings in constant time.
82///
83/// # Examples
84///
85/// ```
86/// use constant_time_eq::constant_time_eq;
87///
88/// assert!(constant_time_eq(b"foo", b"foo"));
89/// assert!(!constant_time_eq(b"foo", b"bar"));
90/// assert!(!constant_time_eq(b"bar", b"baz"));
91/// # assert!(constant_time_eq(b"", b""));
92///
93/// // Not equal-sized, so won't take constant time.
94/// assert!(!constant_time_eq(b"foo", b""));
95/// assert!(!constant_time_eq(b"foo", b"quux"));
96/// ```
97#[must_use]
98pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
99 a.len() == b.len() && constant_time_ne(a, b) == 0
100}
101
102// Fixed-size array variant.
103
104#[inline]
105#[must_use]
106fn constant_time_ne_n<const N: usize>(a: &[u8; N], b: &[u8; N]) -> u8 {
107 let mut tmp = 0;
108 for i in 0..N {
109 tmp |= a[i] ^ b[i];
110 }
111
112 // The compare with 0 must happen outside this function.
113 optimizer_hide(tmp)
114}
115
116/// Compares two fixed-size byte strings in constant time.
117///
118/// # Examples
119///
120/// ```
121/// use constant_time_eq::constant_time_eq_n;
122///
123/// assert!(constant_time_eq_n(&[3; 20], &[3; 20]));
124/// assert!(!constant_time_eq_n(&[3; 20], &[7; 20]));
125/// ```
126#[must_use]
127pub fn constant_time_eq_n<const N: usize>(a: &[u8; N], b: &[u8; N]) -> bool {
128 constant_time_ne_n(a, b) == 0
129}
130
131// Fixed-size variants for the most common sizes.
132
133/// Compares two 128-bit byte strings in constant time.
134///
135/// # Examples
136///
137/// ```
138/// use constant_time_eq::constant_time_eq_16;
139///
140/// assert!(constant_time_eq_16(&[3; 16], &[3; 16]));
141/// assert!(!constant_time_eq_16(&[3; 16], &[7; 16]));
142/// ```
143#[inline]
144#[must_use]
145pub fn constant_time_eq_16(a: &[u8; 16], b: &[u8; 16]) -> bool {
146 constant_time_eq_n(a, b)
147}
148
149/// Compares two 256-bit byte strings in constant time.
150///
151/// # Examples
152///
153/// ```
154/// use constant_time_eq::constant_time_eq_32;
155///
156/// assert!(constant_time_eq_32(&[3; 32], &[3; 32]));
157/// assert!(!constant_time_eq_32(&[3; 32], &[7; 32]));
158/// ```
159#[inline]
160#[must_use]
161pub fn constant_time_eq_32(a: &[u8; 32], b: &[u8; 32]) -> bool {
162 constant_time_eq_n(a, b)
163}
164
165/// Compares two 512-bit byte strings in constant time.
166///
167/// # Examples
168///
169/// ```
170/// use constant_time_eq::constant_time_eq_64;
171///
172/// assert!(constant_time_eq_64(&[3; 64], &[3; 64]));
173/// assert!(!constant_time_eq_64(&[3; 64], &[7; 64]));
174/// ```
175#[inline]
176#[must_use]
177pub fn constant_time_eq_64(a: &[u8; 64], b: &[u8; 64]) -> bool {
178 constant_time_eq_n(a, b)
179}
180
181#[cfg(test)]
182mod tests {
183 #[cfg(feature = "count_instructions_test")]
184 extern crate std;
185
186 #[cfg(feature = "count_instructions_test")]
187 #[test]
188 fn count_optimizer_hide_instructions() -> std::io::Result<()> {
189 use super::optimizer_hide;
190 use count_instructions::count_instructions;
191
192 fn count() -> std::io::Result<usize> {
193 // If optimizer_hide does not work, constant propagation and folding
194 // will make this identical to count_optimized() below.
195 let mut count = 0;
196 assert_eq!(
197 10u8,
198 count_instructions(
199 || optimizer_hide(1)
200 + optimizer_hide(2)
201 + optimizer_hide(3)
202 + optimizer_hide(4),
203 |_| count += 1
204 )?
205 );
206 Ok(count)
207 }
208
209 fn count_optimized() -> std::io::Result<usize> {
210 #[inline]
211 fn inline_identity(value: u8) -> u8 {
212 value
213 }
214
215 let mut count = 0;
216 assert_eq!(
217 10u8,
218 count_instructions(
219 || inline_identity(1)
220 + inline_identity(2)
221 + inline_identity(3)
222 + inline_identity(4),
223 |_| count += 1
224 )?
225 );
226 Ok(count)
227 }
228
229 assert!(count()? > count_optimized()?);
230 Ok(())
231 }
232}