hashes: add SHA256 ARM hardware acceleration
What changed, and why it matters
This commit adds a faster way to calculate SHA-256 hashes on modern 64-bit ARM processors (like Apple's M chips, AWS Graviton, and recent smartphones) by using built-in CPU instructions called ARM SHA2 crypto extensions. It mirrors an earlier change that did the same thing for x86 processors. There is no indication in the commit that this fixes a security bug; it is a performance improvement.
Treat as a routine performance optimization, not a security patch. If reviewing for security, verify that the unsafe block's preconditions (16-word state buffer, 64-byte input block, correct alignment) are upheld by the surrounding `HashEngine` code and that the runtime feature detection correctly prevents executing sha2 instructions on CPUs without the extension. Consider adding targeted test vectors that exercise both the ARM intrinsic and fallback paths on aarch64 CI runners.
Security signals we found
New unsafe code using architecture-specific intrinsics
Runtime CPU feature detection before executing target-feature-specific code
Translation of third-party cryptographic reference code into Rust
No bounds checking visible inside the unsafe intrinsic block (relies on caller invariants and fixed-size buffer)
Evidence from the diff
The patch introduces process_block_simd_arm_intrinsics() in hashes/src/sha256/crypto.rs, gated by #[cfg(all(feature = "std", target_arch = "aarch64"))] and #[target_feature(enable = "sha2")]. At runtime it checks std::arch::is_aarch64_feature_detected!("sha2") before calling the unsafe intrinsic-based implementation, falling back to the existing software implementation if the feature is unavailable. The implementation is a direct translation of the public-domain SHA-Intrinsics sha256-arm.c reference and follows the same 64-round SHA-256 schedule using vsha256su0q_u32, vsha256su1q_u32, vsha256hq_u32, and vsha256h2q_u32. No cryptographic constants, buffer sizes, or control flow outside the new ARM path were changed.
Changed components
hashes/src/sha256/crypto.rsSHA-256 hash engine on aarch64 with std featureInspect captured patch +197 / −0
diff --git a/hashes/src/sha256/crypto.rs b/hashes/src/sha256/crypto.rs
index 905a5848..49fde45b 100644
--- a/hashes/src/sha256/crypto.rs
+++ b/hashes/src/sha256/crypto.rs
@@ -4,6 +4,8 @@
use core::arch::x86::*;
#[cfg(all(feature = "std", target_arch = "x86_64"))]
use core::arch::x86_64::*;
+#[cfg(all(feature = "std", target_arch = "aarch64"))]
+use core::arch::aarch64::*;
use internals::slice::SliceExt;
@@ -260,6 +262,13 @@ impl HashEngine {
}
}
+ #[cfg(all(feature = "std", target_arch = "aarch64"))]
+ {
+ if std::arch::is_aarch64_feature_detected!("sha2") {
+ return unsafe { self.process_block_simd_arm_intrinsics() };
+ }
+ }
+
// fallback implementation without using any intrinsics
self.software_process_block()
}
@@ -523,6 +532,194 @@ impl HashEngine {
_mm_storeu_si128(self.h.as_mut_ptr().add(4).cast::<__m128i>(), state1);
}
+ #[cfg(all(feature = "std", target_arch = "aarch64"))]
+ #[target_feature(enable = "sha2")]
+ unsafe fn process_block_simd_arm_intrinsics(&mut self) {
+ // Code translated and based on from
+ // https://github.com/noloader/SHA-Intrinsics/blob/4e754bec921a9f281b69bd681ca0065763aa911c/sha256-arm.c
+
+ /* sha256-arm.c - ARMv8 SHA extensions using C intrinsics */
+ /* Written and placed in public domain by Jeffrey Walton */
+ /* Based on code from ARM, and by Johannes Schneiders, Skip */
+ /* Hovsmith and Barry O'Rourke for the mbedTLS project. */
+
+ // SHA256 round constants
+ #[rustfmt::skip]
+ const K: [u32; 64] = [
+ 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5,
+ 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,
+ 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3,
+ 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174,
+ 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC,
+ 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA,
+ 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7,
+ 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967,
+ 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13,
+ 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85,
+ 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3,
+ 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070,
+ 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5,
+ 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3,
+ 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208,
+ 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2,
+ ];
+
+ let (mut state0, mut state1);
+ let (abcd_save, efgh_save);
+
+ let (mut msg0, mut msg1, mut msg2, mut msg3);
+ let (mut tmp0, mut tmp1, mut tmp2);
+
+ // Load state
+ state0 = vld1q_u32(self.h.as_ptr().add(0));
+ state1 = vld1q_u32(self.h.as_ptr().add(4));
+
+ // Save state
+ abcd_save = state0;
+ efgh_save = state1;
+
+ // Load message
+ msg0 = vld1q_u32(self.buffer.as_ptr().add(0).cast::<u32>());
+ msg1 = vld1q_u32(self.buffer.as_ptr().add(16).cast::<u32>());
+ msg2 = vld1q_u32(self.buffer.as_ptr().add(32).cast::<u32>());
+ msg3 = vld1q_u32(self.buffer.as_ptr().add(48).cast::<u32>());
+
+ // Reverse for little endian
+ msg0 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(msg0)));
+ msg1 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(msg1)));
+ msg2 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(msg2)));
+ msg3 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(msg3)));
+
+ tmp0 = vaddq_u32(msg0, vld1q_u32(K.as_ptr().add(0x00)));
+
+ // Rounds 0-3
+ msg0 = vsha256su0q_u32(msg0, msg1);
+ tmp2 = state0;
+ tmp1 = vaddq_u32(msg1, vld1q_u32(K.as_ptr().add(0x04)));
+ state0 = vsha256hq_u32(state0, state1, tmp0);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp0);
+ msg0 = vsha256su1q_u32(msg0, msg2, msg3);
+
+ // Rounds 4-7
+ msg1 = vsha256su0q_u32(msg1, msg2);
+ tmp2 = state0;
+ tmp0 = vaddq_u32(msg2, vld1q_u32(K.as_ptr().add(0x08)));
+ state0 = vsha256hq_u32(state0, state1, tmp1);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp1);
+ msg1 = vsha256su1q_u32(msg1, msg3, msg0);
+
+ // Rounds 8-11
+ msg2 = vsha256su0q_u32(msg2, msg3);
+ tmp2 = state0;
+ tmp1 = vaddq_u32(msg3, vld1q_u32(K.as_ptr().add(0x0c)));
+ state0 = vsha256hq_u32(state0, state1, tmp0);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp0);
+ msg2 = vsha256su1q_u32(msg2, msg0, msg1);
+
+ // Rounds 12-15
+ msg3 = vsha256su0q_u32(msg3, msg0);
+ tmp2 = state0;
+ tmp0 = vaddq_u32(msg0, vld1q_u32(K.as_ptr().add(0x10)));
+ state0 = vsha256hq_u32(state0, state1, tmp1);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp1);
+ msg3 = vsha256su1q_u32(msg3, msg1, msg2);
+
+ // Rounds 16-19
+ msg0 = vsha256su0q_u32(msg0, msg1);
+ tmp2 = state0;
+ tmp1 = vaddq_u32(msg1, vld1q_u32(K.as_ptr().add(0x14)));
+ state0 = vsha256hq_u32(state0, state1, tmp0);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp0);
+ msg0 = vsha256su1q_u32(msg0, msg2, msg3);
+
+ // Rounds 20-23
+ msg1 = vsha256su0q_u32(msg1, msg2);
+ tmp2 = state0;
+ tmp0 = vaddq_u32(msg2, vld1q_u32(K.as_ptr().add(0x18)));
+ state0 = vsha256hq_u32(state0, state1, tmp1);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp1);
+ msg1 = vsha256su1q_u32(msg1, msg3, msg0);
+
+ // Rounds 24-27
+ msg2 = vsha256su0q_u32(msg2, msg3);
+ tmp2 = state0;
+ tmp1 = vaddq_u32(msg3, vld1q_u32(K.as_ptr().add(0x1c)));
+ state0 = vsha256hq_u32(state0, state1, tmp0);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp0);
+ msg2 = vsha256su1q_u32(msg2, msg0, msg1);
+
+ // Rounds 28-31
+ msg3 = vsha256su0q_u32(msg3, msg0);
+ tmp2 = state0;
+ tmp0 = vaddq_u32(msg0, vld1q_u32(K.as_ptr().add(0x20)));
+ state0 = vsha256hq_u32(state0, state1, tmp1);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp1);
+ msg3 = vsha256su1q_u32(msg3, msg1, msg2);
+
+ // Rounds 32-35
+ msg0 = vsha256su0q_u32(msg0, msg1);
+ tmp2 = state0;
+ tmp1 = vaddq_u32(msg1, vld1q_u32(K.as_ptr().add(0x24)));
+ state0 = vsha256hq_u32(state0, state1, tmp0);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp0);
+ msg0 = vsha256su1q_u32(msg0, msg2, msg3);
+
+ // Rounds 36-39
+ msg1 = vsha256su0q_u32(msg1, msg2);
+ tmp2 = state0;
+ tmp0 = vaddq_u32(msg2, vld1q_u32(K.as_ptr().add(0x28)));
+ state0 = vsha256hq_u32(state0, state1, tmp1);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp1);
+ msg1 = vsha256su1q_u32(msg1, msg3, msg0);
+
+ // Rounds 40-43
+ msg2 = vsha256su0q_u32(msg2, msg3);
+ tmp2 = state0;
+ tmp1 = vaddq_u32(msg3, vld1q_u32(K.as_ptr().add(0x2c)));
+ state0 = vsha256hq_u32(state0, state1, tmp0);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp0);
+ msg2 = vsha256su1q_u32(msg2, msg0, msg1);
+
+ // Rounds 44-47
+ msg3 = vsha256su0q_u32(msg3, msg0);
+ tmp2 = state0;
+ tmp0 = vaddq_u32(msg0, vld1q_u32(K.as_ptr().add(0x30)));
+ state0 = vsha256hq_u32(state0, state1, tmp1);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp1);
+ msg3 = vsha256su1q_u32(msg3, msg1, msg2);
+
+ // Rounds 48-51
+ tmp2 = state0;
+ tmp1 = vaddq_u32(msg1, vld1q_u32(K.as_ptr().add(0x34)));
+ state0 = vsha256hq_u32(state0, state1, tmp0);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp0);
+
+ // Rounds 52-55
+ tmp2 = state0;
+ tmp0 = vaddq_u32(msg2, vld1q_u32(K.as_ptr().add(0x38)));
+ state0 = vsha256hq_u32(state0, state1, tmp1);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp1);
+
+ // Rounds 56-59
+ tmp2 = state0;
+ tmp1 = vaddq_u32(msg3, vld1q_u32(K.as_ptr().add(0x3c)));
+ state0 = vsha256hq_u32(state0, state1, tmp0);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp0);
+
+ // Rounds 60-63
+ tmp2 = state0;
+ state0 = vsha256hq_u32(state0, state1, tmp1);
+ state1 = vsha256h2q_u32(state1, tmp2, tmp1);
+
+ // Combine state
+ state0 = vaddq_u32(state0, abcd_save);
+ state1 = vaddq_u32(state1, efgh_save);
+
+ // Save state
+ vst1q_u32(self.h.as_mut_ptr().add(0), state0);
+ vst1q_u32(self.h.as_mut_ptr().add(4), state1);
+ }
+
// Algorithm copied from libsecp256k1
fn software_process_block(&mut self) {
debug_assert_eq!(self.buffer.len(), BLOCK_SIZE);
Why this scored 16/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.