Merge rust-bitcoin/rust-bitcoin#6646: hashes: add several methods to `Midstate` and release 1.2.0
What changed, and why it matters
This commit is a routine feature release for the bitcoin_hashes crate. It adds new public methods and a constant to the SHA-256 Midstate type so developers can compute SHA-256 midstates in const contexts. There is no indication of a security bug, fix, or vulnerability. The change expands the public API surface slightly, which is normal for a library release.
No security action required. Treat as a normal dependency/API update; review downstream usage of the new Midstate methods only if your project exposes SHA-256 midstates to untrusted input.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The merge releases bitcoin_hashes 1.2.0 and adds SHA256_IV, update_64, update_64_unoptimized, and update_2x32_unoptimized to sha256::Midstate. These allow ratcheting SHA-256 internal state forward by exactly one block (64 bytes) in both runtime and const contexts. The implementation reuses existing crypto code, updates bytes_hashed correctly, and adds tests verifying equivalence with the normal HashEngine path. No security defect or patch is present in the diff.
Changed components
hashes/src/sha256/mod.rshashes/src/sha256/crypto/mod.rshashes/src/sha256/tests.rshashes/Cargo.tomlhashes/CHANGELOG.mdCargo-minimal.lockCargo-recent.lockInspect captured patch +157 / −42
### Cargo-minimal.lock
@@ -28,7 +28,7 @@ name = "base58ck"
version = "0.4.0"
dependencies = [
"bitcoin-internals 0.6.0",
- "bitcoin_hashes 1.1.0",
+ "bitcoin_hashes 1.2.0",
"hex-conservative 1.1.0",
]
@@ -91,7 +91,7 @@ dependencies = [
"bitcoin-primitives",
"bitcoin-taproot-primitives",
"bitcoin-units 0.5.0",
- "bitcoin_hashes 1.1.0",
+ "bitcoin_hashes 1.2.0",
"bitcoinconsensus",
"hex-conservative 1.1.0",
"secp256k1 0.32.0-beta.2",
@@ -111,7 +111,7 @@ dependencies = [
"bitcoin-network-kind",
"bitcoin-primitives",
"bitcoin-taproot-primitives",
- "bitcoin_hashes 1.1.0",
+ "bitcoin_hashes 1.2.0",
"serde",
"serde_json",
]
@@ -148,7 +148,7 @@ dependencies = [
"bitcoin-internals 0.6.0",
"bitcoin-network-kind",
"bitcoin-primitives",
- "bitcoin_hashes 1.1.0",
+ "bitcoin_hashes 1.2.0",
"hex-conservative 1.1.0",
"secp256k1 0.32.0-beta.2",
"serde",
@@ -204,7 +204,7 @@ version = "0.6.0"
dependencies = [
"bitcoin-consensus-encoding 1.1.0",
"bitcoin-internals 0.6.0",
- "bitcoin_hashes 1.1.0",
+ "bitcoin_hashes 1.2.0",
]
[[package]]
@@ -217,7 +217,7 @@ dependencies = [
"bitcoin-crypto",
"bitcoin-internals 0.6.0",
"bitcoin-network-kind",
- "bitcoin_hashes 1.1.0",
+ "bitcoin_hashes 1.2.0",
"hex-conservative 1.1.0",
"secp256k1 0.32.0-beta.2",
"serde",
@@ -245,7 +245,7 @@ dependencies = [
"bitcoin-network-kind",
"bitcoin-primitives",
"bitcoin-units 0.5.0",
- "bitcoin_hashes 1.1.0",
+ "bitcoin_hashes 1.2.0",
"hex-conservative 1.1.0",
"serde",
]
@@ -259,7 +259,7 @@ dependencies = [
"bitcoin-consensus-encoding 1.1.0",
"bitcoin-internals 0.6.0",
"bitcoin-units 0.5.0",
- "bitcoin_hashes 1.1.0",
+ "bitcoin_hashes 1.2.0",
"hex-conservative 1.1.0",
"serde",
"serde_json",
@@ -279,7 +279,7 @@ dependencies = [
"bitcoin-consensus-encoding 1.1.0",
"bitcoin-crypto",
"bitcoin-internals 0.6.0",
- "bitcoin_hashes 1.1.0",
+ "bitcoin_hashes 1.2.0",
"secp256k1 0.32.0-beta.2",
"serde",
]
@@ -329,7 +329,7 @@ dependencies = [
[[package]]
name = "bitcoin_hashes"
-version = "1.1.0"
+version = "1.2.0"
dependencies = [
"arbitrary",
"bitcoin-consensus-encoding 1.1.0",
### Cargo-recent.lock
@@ -28,7 +28,7 @@ name = "base58ck"
version = "0.4.0"
dependencies = [
"bitcoin-internals 0.6.0",
- "bitcoin_hashes 1.1.0",
+ "bitcoin_hashes 1.2.0",
"hex-conservative 1.1.0",
]
@@ -90,7 +90,7 @@ dependencies = [
"bitcoin-primitives",
"bitcoin-taproot-primitives",
"bitcoin-units 0.5.0",
- "bitcoin_hashes 1.1.0",
+ "bitcoin_hashes 1.2.0",
"bitcoinconsensus",
"hex-conservative 1.1.0",
"secp256k1 0.32.0-beta.2",
@@ -110,7 +110,7 @@ dependencies = [
"bitcoin-network-kind",
"bitcoin-primitives",
"bitcoin-taproot-primitives",
- "bitcoin_hashes 1.1.0",
+ "bitcoin_hashes 1.2.0",
"serde",
"serde_json",
]
@@ -147,7 +147,7 @@ dependencies = [
"bitcoin-internals 0.6.0",
"bitcoin-network-kind",
"bitcoin-primitives",
- "bitcoin_hashes 1.1.0",
+ "bitcoin_hashes 1.2.0",
"hex-conservative 1.1.0",
"secp256k1 0.32.0-beta.2",
"serde",
@@ -203,7 +203,7 @@ version = "0.6.0"
dependencies = [
"bitcoin-consensus-encoding 1.1.0",
"bitcoin-internals 0.6.0",
- "bitcoin_hashes 1.1.0",
+ "bitcoin_hashes 1.2.0",
]
[[package]]
@@ -216,7 +216,7 @@ dependencies = [
"bitcoin-crypto",
"bitcoin-internals 0.6.0",
"bitcoin-network-kind",
- "bitcoin_hashes 1.1.0",
+ "bitcoin_hashes 1.2.0",
"hex-conservative 1.1.0",
"secp256k1 0.32.0-beta.2",
"serde",
@@ -244,7 +244,7 @@ dependencies = [
"bitcoin-network-kind",
"bitcoin-primitives",
"bitcoin-units 0.5.0",
- "bitcoin_hashes 1.1.0",
+ "bitcoin_hashes 1.2.0",
"hex-conservative 1.1.0",
"serde",
]
@@ -258,7 +258,7 @@ dependencies = [
"bitcoin-consensus-encoding 1.1.0",
"bitcoin-internals 0.6.0",
"bitcoin-units 0.5.0",
- "bitcoin_hashes 1.1.0",
+ "bitcoin_hashes 1.2.0",
"hex-conservative 1.1.0",
"serde",
"serde_json",
@@ -272,7 +272,7 @@ dependencies = [
"bitcoin-consensus-encoding 1.1.0",
"bitcoin-crypto",
"bitcoin-internals 0.6.0",
- "bitcoin_hashes 1.1.0",
+ "bitcoin_hashes 1.2.0",
"secp256k1 0.32.0-beta.2",
"serde",
]
@@ -313,7 +313,7 @@ dependencies = [
[[package]]
name = "bitcoin_hashes"
-version = "1.1.0"
+version = "1.2.0"
dependencies = [
"arbitrary",
"bitcoin-consensus-encoding 1.1.0",
### hashes/CHANGELOG.md
@@ -2,6 +2,10 @@
## [Unreleased]
+## [1.2.0] - 2026-07-30
+
+* Add several methods for computing `sha256::Midstate` values in const contexts [#6646](https://github.com/rust-bitcoin/rust-bitcoin/pull/6646)
+
## [1.1.0] - 2026-07-15
* Add `with_input` method to `HashEngine` for convenient one-shot hashing [#6411](https://github.com/rust-bitcoin/rust-bitcoin/pull/6411)
@@ -373,7 +377,8 @@ Note that we have stopped re-exporting the `core` crate when compiling without `
* Initial release
-[Unreleased]: https://github.com/rust-bitcoin/rust-bitcoin/compare/bitcoin_hashes-1.1.0...HEAD
+[Unreleased]: https://github.com/rust-bitcoin/rust-bitcoin/compare/bitcoin_hashes-1.2.0...HEAD
+[1.2.0]: https://github.com/rust-bitcoin/rust-bitcoin/compare/bitcoin_hashes-1.1.0...bitcoin_hashes-1.2.0
[1.1.0]: https://github.com/rust-bitcoin/rust-bitcoin/compare/bitcoin_hashes-1.0.0...bitcoin_hashes-1.1.0
[1.0.0]: https://github.com/rust-bitcoin/rust-bitcoin/compare/bitcoin_hashes-0.21.0...bitcoin_hashes-1.0.0
[0.21.0]: https://github.com/rust-bitcoin/rust-bitcoin/compare/bitcoin_hashes-0.20.0...bitcoin_hashes-0.21.0
### hashes/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "bitcoin_hashes"
-version = "1.1.0"
+version = "1.2.0"
authors = ["Andrew Poelstra <apoelstra@wpsoftware.net>"]
license = "CC0-1.0"
repository = "https://github.com/rust-bitcoin/rust-bitcoin"
### hashes/api/all-features.txt
@@ -956,11 +956,15 @@ impl<T> core::convert::From<T> for bitcoin_hashes::sha256::HashEngine
pub fn bitcoin_hashes::sha256::HashEngine::from(t: T) -> T [impl: impl<T> core::convert::From<T> for bitcoin_hashes::sha256::HashEngine]
pub struct bitcoin_hashes::sha256::Midstate
impl bitcoin_hashes::sha256::Midstate
+pub const bitcoin_hashes::sha256::Midstate::SHA256_IV: Self
pub const fn bitcoin_hashes::sha256::Midstate::as_parts(&self) -> (&[u8; 32], u64)
pub const fn bitcoin_hashes::sha256::Midstate::hash_tag(tag: &[u8]) -> Self
pub const fn bitcoin_hashes::sha256::Midstate::new(state: [u8; 32], bytes_hashed: u64) -> Self
pub fn bitcoin_hashes::sha256::Midstate::to_engine(self) -> bitcoin_hashes::sha256::HashEngine
pub const fn bitcoin_hashes::sha256::Midstate::to_parts(self) -> ([u8; 32], u64)
+pub const fn bitcoin_hashes::sha256::Midstate::update_2x32_unoptimized(self, left: &[u8; 32], right: &[u8; 32]) -> Self
+pub fn bitcoin_hashes::sha256::Midstate::update_64(self, bytes: &[u8; 64]) -> Self
+pub const fn bitcoin_hashes::sha256::Midstate::update_64_unoptimized(self, bytes: &[u8; 64]) -> Self
impl core::clone::Clone for bitcoin_hashes::sha256::Midstate
pub fn bitcoin_hashes::sha256::Midstate::clone(&self) -> bitcoin_hashes::sha256::Midstate [impl: impl core::clone::Clone for bitcoin_hashes::sha256::Midstate]
impl core::cmp::Eq for bitcoin_hashes::sha256::Midstate
### hashes/api/alloc-only.txt
@@ -838,11 +838,15 @@ impl<T> core::convert::From<T> for bitcoin_hashes::sha256::HashEngine
pub fn bitcoin_hashes::sha256::HashEngine::from(t: T) -> T [impl: impl<T> core::convert::From<T> for bitcoin_hashes::sha256::HashEngine]
pub struct bitcoin_hashes::sha256::Midstate
impl bitcoin_hashes::sha256::Midstate
+pub const bitcoin_hashes::sha256::Midstate::SHA256_IV: Self
pub const fn bitcoin_hashes::sha256::Midstate::as_parts(&self) -> (&[u8; 32], u64)
pub const fn bitcoin_hashes::sha256::Midstate::hash_tag(tag: &[u8]) -> Self
pub const fn bitcoin_hashes::sha256::Midstate::new(state: [u8; 32], bytes_hashed: u64) -> Self
pub fn bitcoin_hashes::sha256::Midstate::to_engine(self) -> bitcoin_hashes::sha256::HashEngine
pub const fn bitcoin_hashes::sha256::Midstate::to_parts(self) -> ([u8; 32], u64)
+pub const fn bitcoin_hashes::sha256::Midstate::update_2x32_unoptimized(self, left: &[u8; 32], right: &[u8; 32]) -> Self
+pub fn bitcoin_hashes::sha256::Midstate::update_64(self, bytes: &[u8; 64]) -> Self
+pub const fn bitcoin_hashes::sha256::Midstate::update_64_unoptimized(self, bytes: &[u8; 64]) -> Self
impl core::clone::Clone for bitcoin_hashes::sha256::Midstate
pub fn bitcoin_hashes::sha256::Midstate::clone(&self) -> bitcoin_hashes::sha256::Midstate [impl: impl core::clone::Clone for bitcoin_hashes::sha256::Midstate]
impl core::cmp::Eq for bitcoin_hashes::sha256::Midstate
### hashes/api/no-features.txt
@@ -769,11 +769,15 @@ impl<T> core::convert::From<T> for bitcoin_hashes::sha256::HashEngine
pub fn bitcoin_hashes::sha256::HashEngine::from(t: T) -> T [impl: impl<T> core::convert::From<T> for bitcoin_hashes::sha256::HashEngine]
pub struct bitcoin_hashes::sha256::Midstate
impl bitcoin_hashes::sha256::Midstate
+pub const bitcoin_hashes::sha256::Midstate::SHA256_IV: Self
pub const fn bitcoin_hashes::sha256::Midstate::as_parts(&self) -> (&[u8; 32], u64)
pub const fn bitcoin_hashes::sha256::Midstate::hash_tag(tag: &[u8]) -> Self
pub const fn bitcoin_hashes::sha256::Midstate::new(state: [u8; 32], bytes_hashed: u64) -> Self
pub fn bitcoin_hashes::sha256::Midstate::to_engine(self) -> bitcoin_hashes::sha256::HashEngine
pub const fn bitcoin_hashes::sha256::Midstate::to_parts(self) -> ([u8; 32], u64)
+pub const fn bitcoin_hashes::sha256::Midstate::update_2x32_unoptimized(self, left: &[u8; 32], right: &[u8; 32]) -> Self
+pub fn bitcoin_hashes::sha256::Midstate::update_64(self, bytes: &[u8; 64]) -> Self
+pub const fn bitcoin_hashes::sha256::Midstate::update_64_unoptimized(self, bytes: &[u8; 64]) -> Self
impl core::clone::Clone for bitcoin_hashes::sha256::Midstate
pub fn bitcoin_hashes::sha256::Midstate::clone(&self) -> bitcoin_hashes::sha256::Midstate [impl: impl core::clone::Clone for bitcoin_hashes::sha256::Midstate]
impl core::cmp::Eq for bitcoin_hashes::sha256::Midstate
### hashes/src/sha256/crypto/mod.rs
@@ -149,17 +149,21 @@ impl Midstate {
w
}
- pub(super) const fn compute_midstate_unoptimized(bytes: &[u8], finalize: bool) -> Self {
- let mut state = [
- 0x6a09e667u32,
- 0xbb67ae85,
- 0x3c6ef372,
- 0xa54ff53a,
- 0x510e527f,
- 0x9b05688c,
- 0x1f83d9ab,
- 0x5be0cd19,
- ];
+ #[rustfmt::skip]
+ const fn bytes_to_state(&self) -> [u32; 8] {
+ const fn be_bytes_to_u32(bytes: &[u8], offs: usize) -> u32 {
+ u32::from_be_bytes([bytes[offs], bytes[offs + 1], bytes[offs + 2], bytes[offs + 3]])
+ }
+ [
+ be_bytes_to_u32(&self.bytes, 0), be_bytes_to_u32(&self.bytes, 4),
+ be_bytes_to_u32(&self.bytes, 8), be_bytes_to_u32(&self.bytes, 12),
+ be_bytes_to_u32(&self.bytes, 16), be_bytes_to_u32(&self.bytes, 20),
+ be_bytes_to_u32(&self.bytes, 24), be_bytes_to_u32(&self.bytes, 28),
+ ]
+ }
+
+ pub(super) const fn update_midstate_unoptimized(self, bytes: &[u8], finalize: bool) -> Self {
+ let mut state = self.bytes_to_state();
let num_chunks = (bytes.len() + 9).div_ceil(64);
let mut chunk = 0;
@@ -294,7 +298,7 @@ impl Midstate {
output[i * 4 + 3] = (state[i + 0] >> 0) as u8;
i += 1;
}
- Self { bytes: output, bytes_hashed: bytes.len() as u64 }
+ Self { bytes: output, bytes_hashed: self.bytes_hashed + bytes.len() as u64 }
}
}
### hashes/src/sha256/mod.rs
@@ -70,7 +70,7 @@ impl Hash {
///
/// Warning: this function is inefficient. It should be only used in `const` context.
pub const fn hash_unoptimized(bytes: &[u8]) -> Self {
- Self(Midstate::compute_midstate_unoptimized(bytes, true).bytes)
+ Self(Midstate::SHA256_IV.update_midstate_unoptimized(bytes, true).bytes)
}
}
@@ -197,6 +197,20 @@ pub struct Midstate {
}
impl Midstate {
+ /// The midstate obtained by creating a new hash engine and immediately extracting its midstate.
+ #[rustfmt::skip]
+ pub const SHA256_IV: Self = Self {
+ // You can visually verify this value by just squishing the groups of 4 bytes together into
+ // u32s then comparing the result to the first line of HashEngine::new.
+ bytes: [
+ 0x6a, 0x09, 0xe6, 0x67, 0xbb, 0x67, 0xae, 0x85,
+ 0x3c, 0x6e, 0xf3, 0x72, 0xa5, 0x4f, 0xf5, 0x3a,
+ 0x51, 0x0e, 0x52, 0x7f, 0x9b, 0x05, 0x68, 0x8c,
+ 0x1f, 0x83, 0xd9, 0xab, 0x5b, 0xe0, 0xcd, 0x19,
+ ],
+ bytes_hashed: 0,
+ };
+
/// Constructs a new [`Midstate`] from the `state` and the `bytes_hashed` to get to that state.
///
/// # Panics
@@ -225,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.
@@ -234,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::compute_midstate_unoptimized(&buf, false)
+ Self::SHA256_IV.update_2x32_unoptimized(&hash.0, &hash.0)
}
}
### hashes/src/sha256/tests.rs
@@ -243,3 +243,49 @@ mod wasm_tests {
engine_with_state();
}
}
+
+#[test]
+fn initial_midstate() {
+ let mid1 = Midstate::SHA256_IV;
+ 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.