1 | #![no_std ] |
2 | |
3 | #[cfg (any(target_arch = "x86" , target_arch = "x86_64" ))] |
4 | #[cfg (not(miri))] |
5 | #[inline ] |
6 | #[must_use ] |
7 | fn 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)] |
25 | fn 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 ] |
46 | fn 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 ] |
63 | fn 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: usize = a.len(); |
69 | let a: &[u8] = &a[..len]; |
70 | let b: &[u8] = &b[..len]; |
71 | |
72 | let mut tmp: u8 = 0; |
73 | for i: usize 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 ] |
98 | pub 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 ] |
106 | fn constant_time_ne_n<const N: usize>(a: &[u8; N], b: &[u8; N]) -> u8 { |
107 | let mut tmp: u8 = 0; |
108 | for i: usize 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 ] |
127 | pub 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 ] |
145 | pub 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 ] |
161 | pub 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 ] |
177 | pub fn constant_time_eq_64(a: &[u8; 64], b: &[u8; 64]) -> bool { |
178 | constant_time_eq_n(a, b) |
179 | } |
180 | |
181 | #[cfg (test)] |
182 | mod 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 | } |
233 | |