1pub type Hash = [u8; 32];
16
17#[inline]
27pub fn compress256_x4(states: &mut [[u32; 8]; 4], blocks: [&[u8; 64]; 4]) {
28 #[cfg(all(target_arch = "aarch64", target_feature = "sha2"))]
29 {
30 unsafe { neon::compress4_states(states, blocks) }
32 }
33 #[cfg(not(all(target_arch = "aarch64", target_feature = "sha2")))]
34 {
35 for (state, block) in states.iter_mut().zip(blocks) {
36 sha2::block_api::compress256(state, std::slice::from_ref(block));
37 }
38 }
39}
40
41#[inline]
50pub fn sha256_x4(inputs: [&[u8]; 4]) -> [Hash; 4] {
51 let len = inputs[0].len();
52 assert!(
53 inputs.iter().all(|input| input.len() == len),
54 "the four inputs must have equal length"
55 );
56
57 #[cfg(all(target_arch = "aarch64", target_feature = "sha2"))]
58 {
59 unsafe { neon::hash4_equal_len(inputs) }
61 }
62 #[cfg(not(all(target_arch = "aarch64", target_feature = "sha2")))]
63 {
64 scalar::hash4_equal_len(inputs)
65 }
66}
67
68#[cfg(not(all(target_arch = "aarch64", target_feature = "sha2")))]
72mod scalar {
73 use sha2::{Digest, Sha256};
74
75 use super::Hash;
76
77 #[inline]
79 pub fn hash4_equal_len(inputs: [&[u8]; 4]) -> [Hash; 4] {
80 inputs.map(|input| Sha256::digest(input).into())
81 }
82}
83
84#[cfg(all(target_arch = "aarch64", target_feature = "sha2"))]
86mod neon {
87 use core::arch::aarch64::{
88 uint32x4_t, vaddq_u32, vdupq_n_u32, vld1q_u8, vld1q_u32, vreinterpretq_u8_u32,
89 vreinterpretq_u32_u8, vrev32q_u8, vsha256h2q_u32, vsha256hq_u32, vsha256su0q_u32,
90 vsha256su1q_u32, vst1q_u8, vst1q_u32,
91 };
92
93 use super::Hash;
94
95 const K: [u32; 64] = [
97 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,
98 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,
99 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,
100 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
101 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
102 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
103 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,
104 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
105 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
106 0xc67178f2,
107 ];
108
109 const IV_ABCD: [u32; 4] = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a];
111 const IV_EFGH: [u32; 4] = [0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
113
114 #[inline(always)]
124 unsafe fn compress4(
125 abcd: &mut [uint32x4_t; 4],
126 efgh: &mut [uint32x4_t; 4],
127 blocks: [*const u8; 4],
128 ) {
129 unsafe {
130 let mut msg0 = [vdupq_n_u32(0); 4];
132 let mut msg1 = [vdupq_n_u32(0); 4];
133 let mut msg2 = [vdupq_n_u32(0); 4];
134 let mut msg3 = [vdupq_n_u32(0); 4];
135 for i in 0..4 {
136 msg0[i] = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(blocks[i])));
137 msg1[i] = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(blocks[i].add(16))));
138 msg2[i] = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(blocks[i].add(32))));
139 msg3[i] = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(blocks[i].add(48))));
140 }
141
142 let abcd_save = *abcd;
144 let efgh_save = *efgh;
145
146 macro_rules! rounds4 {
148 ($msg:expr, $ki:expr) => {{
149 let kv = vld1q_u32(K.as_ptr().add($ki));
150 for i in 0..4 {
151 let wk = vaddq_u32($msg[i], kv);
152 let t = abcd[i];
153 abcd[i] = vsha256hq_u32(abcd[i], efgh[i], wk);
154 efgh[i] = vsha256h2q_u32(efgh[i], t, wk);
155 }
156 }};
157 }
158
159 macro_rules! sched {
161 ($m0:expr, $m1:expr, $m2:expr, $m3:expr) => {
162 for i in 0..4 {
163 $m0[i] = vsha256su1q_u32(vsha256su0q_u32($m0[i], $m1[i]), $m2[i], $m3[i]);
164 }
165 };
166 }
167
168 rounds4!(msg0, 0);
170 rounds4!(msg1, 4);
171 rounds4!(msg2, 8);
172 rounds4!(msg3, 12);
173 for r in 1..4 {
175 sched!(msg0, msg1, msg2, msg3);
176 sched!(msg1, msg2, msg3, msg0);
177 sched!(msg2, msg3, msg0, msg1);
178 sched!(msg3, msg0, msg1, msg2);
179 rounds4!(msg0, 16 * r);
180 rounds4!(msg1, 16 * r + 4);
181 rounds4!(msg2, 16 * r + 8);
182 rounds4!(msg3, 16 * r + 12);
183 }
184
185 for i in 0..4 {
187 abcd[i] = vaddq_u32(abcd[i], abcd_save[i]);
188 efgh[i] = vaddq_u32(efgh[i], efgh_save[i]);
189 }
190 }
191 }
192
193 #[inline]
203 pub unsafe fn compress4_states(states: &mut [[u32; 8]; 4], blocks: [&[u8; 64]; 4]) {
204 unsafe {
205 let mut abcd = [vdupq_n_u32(0); 4];
207 let mut efgh = [vdupq_n_u32(0); 4];
208 for i in 0..4 {
209 abcd[i] = vld1q_u32(states[i].as_ptr());
210 efgh[i] = vld1q_u32(states[i].as_ptr().add(4));
211 }
212
213 compress4(&mut abcd, &mut efgh, blocks.map(|block| block.as_ptr()));
215
216 for i in 0..4 {
218 vst1q_u32(states[i].as_mut_ptr(), abcd[i]);
219 vst1q_u32(states[i].as_mut_ptr().add(4), efgh[i]);
220 }
221 }
222 }
223
224 #[inline]
230 pub unsafe fn hash4_equal_len(inputs: [&[u8]; 4]) -> [Hash; 4] {
231 let len = inputs[0].len();
232
233 unsafe {
234 let mut abcd = [vld1q_u32(IV_ABCD.as_ptr()); 4];
235 let mut efgh = [vld1q_u32(IV_EFGH.as_ptr()); 4];
236
237 let n_full = len / 64;
239 for blk in 0..n_full {
240 let base = blk * 64;
241 compress4(
242 &mut abcd,
243 &mut efgh,
244 [
245 inputs[0].as_ptr().add(base),
246 inputs[1].as_ptr().add(base),
247 inputs[2].as_ptr().add(base),
248 inputs[3].as_ptr().add(base),
249 ],
250 );
251 }
252
253 let rem = len % 64;
256 let bit_len = (len as u64) * 8;
257 let n_tail = if rem < 56 { 1 } else { 2 };
258 let mut tails = [[0u8; 128]; 4];
259 for i in 0..4 {
260 tails[i][..rem].copy_from_slice(&inputs[i][len - rem..]);
261 tails[i][rem] = 0x80;
262 tails[i][n_tail * 64 - 8..n_tail * 64].copy_from_slice(&bit_len.to_be_bytes());
263 }
264 for blk in 0..n_tail {
265 let base = blk * 64;
266 compress4(
267 &mut abcd,
268 &mut efgh,
269 [
270 tails[0].as_ptr().add(base),
271 tails[1].as_ptr().add(base),
272 tails[2].as_ptr().add(base),
273 tails[3].as_ptr().add(base),
274 ],
275 );
276 }
277
278 let mut out = [[0u8; 32]; 4];
280 for i in 0..4 {
281 let be_lo = vrev32q_u8(vreinterpretq_u8_u32(abcd[i]));
282 let be_hi = vrev32q_u8(vreinterpretq_u8_u32(efgh[i]));
283 vst1q_u8(out[i].as_mut_ptr(), be_lo);
284 vst1q_u8(out[i].as_mut_ptr().add(16), be_hi);
285 }
286 out
287 }
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use proptest::prelude::*;
294 use sha2::{Digest, Sha256, block_api::compress256};
295
296 use super::{compress256_x4, sha256_x4};
297
298 proptest! {
299 #[test]
300 fn compress256_x4_matches_the_block_reference(
301 states in prop::array::uniform4(prop::array::uniform8(any::<u32>())),
302 blocks in prop::array::uniform4(any::<[u8; 64]>()),
303 ) {
304 let mut got = states;
307 compress256_x4(&mut got, [&blocks[0], &blocks[1], &blocks[2], &blocks[3]]);
308
309 for i in 0..4 {
311 let mut want = states[i];
312 compress256(&mut want, std::slice::from_ref(&blocks[i]));
313 prop_assert_eq!(got[i], want, "mismatch at lane {}", i);
314 }
315 }
316 }
317
318 proptest! {
319 #[test]
320 fn matches_sha2_reference(
321 inputs in prop::array::uniform4(prop::collection::vec(any::<u8>(), 0..300)),
322 ) {
323 let len = inputs.iter().map(Vec::len).max().unwrap();
325 let padded: [Vec<u8>; 4] = std::array::from_fn(|i| {
326 let mut v = inputs[i].clone();
327 v.resize(len, 0);
328 v
329 });
330 let refs: [&[u8]; 4] = std::array::from_fn(|i| padded[i].as_slice());
331
332 let got = sha256_x4(refs);
333 for i in 0..4 {
334 let want: [u8; 32] = Sha256::digest(refs[i]).into();
335 prop_assert_eq!(got[i], want, "mismatch at lane {}", i);
336 }
337 }
338 }
339
340 #[test]
341 fn matches_sha2_at_padding_boundaries() {
342 for &len in &[0usize, 1, 55, 56, 63, 64, 65, 119, 120, 128, 256] {
343 let data: [Vec<u8>; 4] = std::array::from_fn(|i| {
345 (0..len)
346 .map(|j| (j as u8).wrapping_add(i as u8 * 37))
347 .collect()
348 });
349 let refs: [&[u8]; 4] = std::array::from_fn(|i| data[i].as_slice());
350
351 let got = sha256_x4(refs);
352 for i in 0..4 {
353 let want: [u8; 32] = Sha256::digest(refs[i]).into();
354 assert_eq!(got[i], want, "mismatch at len {len}, lane {i}");
355 }
356 }
357 }
358}