hashes: add several methods to sha256::Midstate for computing them in const contexts
What changed, and why it matters
This commit adds new helper methods to the SHA-256 'Midstate' type in the rust-bitcoin hashes library. These helpers let callers compute SHA-256 intermediate states inside 'const' contexts (compile-time constants). It is a routine feature addition with no obvious security bug, no fix of a vulnerability, and no disclosed security relevance.
No security action required. Review as normal code-quality/API change if desired.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff introduces update_64, update_64_unoptimized, and update_2x32_unoptimized methods on sha256::Midstate. They reuse existing HashEngine block-processing logic and an existing private update_midstate_unoptimized helper. The change also refactors hash_tag to use the new update_2x32_unoptimized helper. Tests assert that the new methods produce the same midstate/hash results as the existing engine path. There is no change to cryptographic constants, padding, length handling, or public API behavior beyond adding methods.
Changed components
hashes/src/sha256/mod.rshashes/src/sha256/tests.rsInspect captured patch +80 / −7
diff --git a/hashes/src/sha256/mod.rs b/hashes/src/sha256/mod.rs
index e57f5b45..2cbdf5e5 100644
--- a/hashes/src/sha256/mod.rs
+++ b/hashes/src/sha256/mod.rs
@@ -239,6 +239,46 @@ impl Midstate {
HashEngine { buffer: [0; BLOCK_SIZE], h: ret, bytes_hashed: self.bytes_hashed }
}
+ /// Updates a [`Midstate`] by hashing exactly 64 bytes (one SHA256 block).
+ #[must_use]
+ pub fn update_64(self, bytes: &[u8; 64]) -> Self {
+ let mut eng = self.to_engine();
+ HashEngine::process_blocks(&mut eng.h, bytes);
+ eng.bytes_hashed += 64;
+ eng.midstate_unchecked()
+ }
+
+ /// Updates a [`Midstate`] by hashing exactly 64 bytes (one SHA256 block).
+ ///
+ /// Warning: this function is inefficient. It should be only used in `const` context. In
+ /// other contexts, use [`Self::update_64`].
+ #[must_use]
+ pub const fn update_64_unoptimized(self, bytes: &[u8; 64]) -> Self {
+ self.update_midstate_unoptimized(bytes, false)
+ }
+
+ /// Updates a [`Midstate`] by hashing exactly 64 bytes (one SHA256 block), split into halves.
+ ///
+ /// Warning: this function is inefficient. It should be only used in `const` context. In
+ /// other contexts, concatenate your arrays and then use [`Self::update_64`].
+ #[must_use]
+ pub const fn update_2x32_unoptimized(self, left: &[u8; 32], right: &[u8; 32]) -> Self {
+ // This method basically only exists because it's extremely hard to put two arrays
+ // together into one in a const context, so we can't tell users to call update_64.
+ //
+ // In Rust 1.83 we will be able to use split_at_mut to define a 64-byte array and
+ // split it into two 32-byte slices, then in 1.87 we can use copy_from_slice to
+ // copy into each of those. Though that might not actually save any LOC..
+ let mut bytes = [0; 64];
+ let mut i = 0;
+ while i < 32 {
+ bytes[i] = left[i];
+ bytes[i + 32] = right[i];
+ i += 1;
+ }
+ self.update_64_unoptimized(&bytes)
+ }
+
/// Constructs a new midstate for tagged hashes.
///
/// Warning: this function is inefficient. It should be only used in `const` context.
@@ -248,13 +288,7 @@ impl Midstate {
#[must_use]
pub const fn hash_tag(tag: &[u8]) -> Self {
let hash = Hash::hash_unoptimized(tag);
- let mut buf = [0u8; 64];
- let mut i = 0usize;
- while i < buf.len() {
- buf[i] = hash.0[i % hash.0.len()];
- i += 1;
- }
- Self::SHA256_IV.update_midstate_unoptimized(&buf, false)
+ Self::SHA256_IV.update_2x32_unoptimized(&hash.0, &hash.0)
}
}
diff --git a/hashes/src/sha256/tests.rs b/hashes/src/sha256/tests.rs
index d56251ce..565bf99b 100644
--- a/hashes/src/sha256/tests.rs
+++ b/hashes/src/sha256/tests.rs
@@ -250,3 +250,42 @@ fn initial_midstate() {
let mid2 = super::HashEngine::new().midstate().unwrap();
assert_eq!(mid1, mid2);
}
+
+#[test]
+fn midstate_updates() {
+ #[rustfmt::skip]
+ static BLOB: [u8; 64] = [
+ 0xb4, 0x9c, 0x4e, 0xa4, 0x9a, 0xe6, 0x23, 0xa8,
+ 0xaa, 0x63, 0x15, 0x64, 0xd5, 0xd7, 0x89, 0xc2,
+ 0x82, 0x52, 0x65, 0x29, 0xa9, 0xb6, 0x3d, 0x97,
+ 0x18, 0x84, 0xe4, 0x72, 0x40, 0x4e, 0xf4, 0x5a,
+ 0xb7, 0x65, 0x44, 0x8c, 0x86, 0x35, 0xfb, 0x6c,
+ 0x88, 0x52, 0x7f, 0x7d, 0x8a, 0x06, 0x94, 0x20,
+ 0xef, 0x53, 0x7f, 0x25, 0xc8, 0x95, 0xbf, 0xa7,
+ 0x8f, 0xf1, 0xf7, 0xa9, 0xd5, 0x69, 0x09, 0x59,
+ ];
+ let (blob1, blob2) = BLOB.split_at(32);
+ let (blob1, blob2) =
+ (<&[u8; 32]>::try_from(blob1).unwrap(), <&[u8; 32]>::try_from(blob2).unwrap());
+
+ let midstate1 = Midstate::SHA256_IV.update_2x32_unoptimized(blob1, blob2);
+ let midstate2 = Midstate::SHA256_IV.update_64_unoptimized(&BLOB);
+ let midstate3 = Midstate::SHA256_IV.update_64(&BLOB);
+
+ assert_eq!(midstate1, midstate2);
+ assert_eq!(midstate1, midstate3);
+
+ let hash1 = midstate1.to_engine().finalize();
+ let hash2 = super::HashEngine::new().with_input(&BLOB).finalize();
+ assert_eq!(hash1, hash2);
+
+ let final1 = midstate1.update_2x32_unoptimized(blob1, blob2);
+ let final2 = midstate1.update_64_unoptimized(&BLOB);
+ let final3 = midstate1.update_64(&BLOB);
+
+ assert_eq!(final1, final2);
+ assert_eq!(final1, final3);
+ let hash1 = final1.to_engine().finalize();
+ let hash2 = super::HashEngine::new().with_input(&BLOB).with_input(&BLOB).finalize();
+ assert_eq!(hash1, hash2);
+}
Why this scored 15/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.