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}