primitives: use pull-encoding to compute block hash
What changed, and why it matters
This commit refactors how Bitcoin block hashes are computed inside the rust-bitcoin library. Instead of manually feeding each block header field into the SHA-256 hashing engine one by one, the code now uses a shared 'pull-encoding' helper that walks through the header's encoded bytes and feeds them into the hash engine automatically. The change is a code-quality and maintainability improvement; there is no indication it fixes a security bug.
Treat as a routine refactor. If this commit is being backported or included in a release, verify via existing tests that block hashes for known blocks still match expected values, since the change centralizes the encoding path but should not alter output.
Security signals we found
Refactor of cryptographic hash input path for block header
No change to hash algorithm or output format
No bounds-checking, input-validation, or memory-safety changes visible
No disclosure, CVE, or advisory references present in commit or supplied materials
Evidence from the diff
The patch introduces consensus_encoding::encode::encode_to_hash_engine<T: Encodable, H: hashes::HashEngine>, which pulls chunks from an Encoder and writes them into a hash engine. It then rewrites primitives::block::Header::block_hash to use this helper rather than the previous inline manual engine.input(...) calls for version, prev_blockhash, merkle_root, time, bits, and nonce. The consensus_encoding crate gains a dependency on bitcoin_hashes. The observable behavior—hashing the consensus serialization of the block header with double SHA-256—is intended to remain identical.
Changed components
consensus_encoding/src/encode/mod.rsconsensus_encoding/src/lib.rsconsensus_encoding/Cargo.tomlprimitives/src/block.rsInspect captured patch +23 / −10
diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock
index 0c6a09c5..437436fc 100644
--- a/Cargo-minimal.lock
+++ b/Cargo-minimal.lock
@@ -207,6 +207,9 @@ dependencies = [
[[package]]
name = "consensus-encoding"
version = "0.0.0"
+dependencies = [
+ "bitcoin_hashes 0.16.0",
+]
[[package]]
name = "getrandom"
diff --git a/Cargo-recent.lock b/Cargo-recent.lock
index 99135ba0..80aa7d8d 100644
--- a/Cargo-recent.lock
+++ b/Cargo-recent.lock
@@ -209,6 +209,9 @@ dependencies = [
[[package]]
name = "consensus-encoding"
version = "0.0.0"
+dependencies = [
+ "bitcoin_hashes 0.16.0",
+]
[[package]]
name = "getrandom"
diff --git a/consensus_encoding/Cargo.toml b/consensus_encoding/Cargo.toml
index 899631d3..fcb7b85e 100644
--- a/consensus_encoding/Cargo.toml
+++ b/consensus_encoding/Cargo.toml
@@ -18,6 +18,7 @@ std = ["alloc"]
alloc = []
[dependencies]
+hashes = { package = "bitcoin_hashes", path = "../hashes", default-features = false }
[package.metadata.docs.rs]
all-features = true
diff --git a/consensus_encoding/src/encode/mod.rs b/consensus_encoding/src/encode/mod.rs
index 901b03fb..831f9d81 100644
--- a/consensus_encoding/src/encode/mod.rs
+++ b/consensus_encoding/src/encode/mod.rs
@@ -59,3 +59,16 @@ macro_rules! encoder_newtype{
}
}
}
+
+/// Encode an object into a hash engine.
+///
+/// Consumes and returns the hash engine to make it easier to call
+/// [`hashes::HashEngine::finalize`] directly on the result.
+pub fn encode_to_hash_engine<T: Encodable, H: hashes::HashEngine>(object: &T, mut engine: H) -> H {
+ let mut encoder = object.encoder();
+ while let Some(sl) = encoder.current_chunk() {
+ engine.input(sl);
+ encoder.advance();
+ }
+ engine
+}
diff --git a/consensus_encoding/src/lib.rs b/consensus_encoding/src/lib.rs
index 8f7e7551..f8190886 100644
--- a/consensus_encoding/src/lib.rs
+++ b/consensus_encoding/src/lib.rs
@@ -19,4 +19,4 @@ mod encode;
pub use self::encode::encoders::{
ArrayEncoder, BytesEncoder, Encoder2, Encoder3, Encoder4, Encoder6,
};
-pub use self::encode::{Encodable, Encoder};
+pub use self::encode::{encode_to_hash_engine, Encodable, Encoder};
diff --git a/primitives/src/block.rs b/primitives/src/block.rs
index 6bad8f11..fa193f22 100644
--- a/primitives/src/block.rs
+++ b/primitives/src/block.rs
@@ -200,15 +200,8 @@ impl Header {
/// Returns the block hash.
// This is the same as `Encodable` but done manually because `Encodable` isn't in `primitives`.
pub fn block_hash(&self) -> BlockHash {
- let mut engine = sha256d::Hash::engine();
- engine.input(&self.version.to_consensus().to_le_bytes());
- engine.input(self.prev_blockhash.as_byte_array());
- engine.input(self.merkle_root.as_byte_array());
- engine.input(&self.time.to_u32().to_le_bytes());
- engine.input(&self.bits.to_consensus().to_le_bytes());
- engine.input(&self.nonce.to_le_bytes());
-
- BlockHash::from_byte_array(sha256d::Hash::from_engine(engine).to_byte_array())
+ let bare_hash = encoding::encode_to_hash_engine(self, sha256d::Hash::engine()).finalize();
+ BlockHash::from_byte_array(bare_hash.to_byte_array())
}
}
Why this scored 19/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.