Move BlockUncheckedExt functions to Block<Unchecked> impl
What changed, and why it matters
This commit is a code reorganization, not a security fix. It moves block validation helpers (Merkle root checks, witness commitment checks, and the validate() method) from an extension trait in the higher-level bitcoin crate directly onto the Block<Unchecked> type in the lower-level primitives crate. The goal is to simplify the public API and remove a workaround trait that existed only because of crate-dependency ordering. The actual validation logic is preserved, with only minor implementation changes such as using a different helper to encode the witness root into the hash engine.
No security action required. Treat as a normal API refactor. Downstream users importing BlockUncheckedExt will need to update to call the inherent methods on Block<Unchecked> instead. Reviewers may want to verify that the moved functions are feature-gated consistently (alloc-only and all-features API files show the new exports) and that no callers still reference the removed trait.
Security signals we found
Refactor only: no new validation rules or relaxed checks
Merkle root and witness commitment logic preserved from previous extension trait
CVE-2012-2459 duplicate-transaction guard retained in compute_merkle_root and compute_witness_root
New test coverage added for empty blocks, invalid coinbase, optional witness commitment, and witness commitment extraction
Public API surface changes: BlockUncheckedExt removed; new methods and InvalidBlockError exported from primitives
Evidence from the diff
The patch removes BlockUncheckedExt from bitcoin/src/blockdata/block.rs and re-implements validate(), check_merkle_root(), check_witness_commitment(), and compute_witness_commitment() as inherent methods on Block
Changed components
bitcoin/src/blockdata/block.rsbitcoin/src/lib.rsbitcoin/src/merkle_tree/block.rsprimitives/src/block.rsfuzz/fuzz_targets/bitcoin/arbitrary_block.rsapi/primitives/all-features.txtapi/primitives/alloc-only.txtInspect captured patch +461 / −253
diff --git a/api/primitives/all-features.txt b/api/primitives/all-features.txt
index d56a98e5..bd392765 100644
--- a/api/primitives/all-features.txt
+++ b/api/primitives/all-features.txt
@@ -1,4 +1,5 @@
#[non_exhaustive] pub enum bitcoin_primitives::block::HeaderDecoderError
+#[non_exhaustive] pub enum bitcoin_primitives::block::InvalidBlockError
#[non_exhaustive] pub enum bitcoin_primitives::transaction::ParseOutPointError
#[repr(transparent)] pub struct bitcoin_primitives::script::Script<T>(_, _)
impl bitcoin_consensus_encoding::decode::Decodable for bitcoin_primitives::BlockHash
@@ -115,6 +116,7 @@ impl core::clone::Clone for bitcoin_primitives::block::BlockHashDecoderError
impl core::clone::Clone for bitcoin_primitives::block::Checked
impl core::clone::Clone for bitcoin_primitives::block::Header
impl core::clone::Clone for bitcoin_primitives::block::HeaderDecoderError
+impl core::clone::Clone for bitcoin_primitives::block::InvalidBlockError
impl core::clone::Clone for bitcoin_primitives::block::Unchecked
impl core::clone::Clone for bitcoin_primitives::block::Version
impl core::clone::Clone for bitcoin_primitives::block::VersionDecoderError
@@ -157,6 +159,7 @@ impl core::cmp::Eq for bitcoin_primitives::block::BlockHashDecoderError
impl core::cmp::Eq for bitcoin_primitives::block::Checked
impl core::cmp::Eq for bitcoin_primitives::block::Header
impl core::cmp::Eq for bitcoin_primitives::block::HeaderDecoderError
+impl core::cmp::Eq for bitcoin_primitives::block::InvalidBlockError
impl core::cmp::Eq for bitcoin_primitives::block::Unchecked
impl core::cmp::Eq for bitcoin_primitives::block::Version
impl core::cmp::Eq for bitcoin_primitives::block::VersionDecoderError
@@ -224,6 +227,7 @@ impl core::cmp::PartialEq for bitcoin_primitives::block::BlockHashDecoderError
impl core::cmp::PartialEq for bitcoin_primitives::block::Checked
impl core::cmp::PartialEq for bitcoin_primitives::block::Header
impl core::cmp::PartialEq for bitcoin_primitives::block::HeaderDecoderError
+impl core::cmp::PartialEq for bitcoin_primitives::block::InvalidBlockError
impl core::cmp::PartialEq for bitcoin_primitives::block::Unchecked
impl core::cmp::PartialEq for bitcoin_primitives::block::Version
impl core::cmp::PartialEq for bitcoin_primitives::block::VersionDecoderError
@@ -323,6 +327,7 @@ impl core::convert::From<bitcoin_units::locktime::absolute::error::LockTimeDecod
impl core::convert::From<core::convert::Infallible> for bitcoin_primitives::block::BlockDecoderError
impl core::convert::From<core::convert::Infallible> for bitcoin_primitives::block::BlockHashDecoderError
impl core::convert::From<core::convert::Infallible> for bitcoin_primitives::block::HeaderDecoderError
+impl core::convert::From<core::convert::Infallible> for bitcoin_primitives::block::InvalidBlockError
impl core::convert::From<core::convert::Infallible> for bitcoin_primitives::block::VersionDecoderError
impl core::convert::From<core::convert::Infallible> for bitcoin_primitives::pow::CompactTargetDecoderError
impl core::convert::From<core::convert::Infallible> for bitcoin_primitives::script::RedeemScriptSizeError
@@ -351,6 +356,7 @@ impl core::default::Default for bitcoin_primitives::witness::WitnessDecoder
impl core::error::Error for bitcoin_primitives::block::BlockDecoderError
impl core::error::Error for bitcoin_primitives::block::BlockHashDecoderError
impl core::error::Error for bitcoin_primitives::block::HeaderDecoderError
+impl core::error::Error for bitcoin_primitives::block::InvalidBlockError
impl core::error::Error for bitcoin_primitives::block::VersionDecoderError
impl core::error::Error for bitcoin_primitives::pow::CompactTargetDecoderError
impl core::error::Error for bitcoin_primitives::script::RedeemScriptSizeError
@@ -377,6 +383,7 @@ impl core::fmt::Debug for bitcoin_primitives::block::BlockHashDecoderError
impl core::fmt::Debug for bitcoin_primitives::block::Checked
impl core::fmt::Debug for bitcoin_primitives::block::Header
impl core::fmt::Debug for bitcoin_primitives::block::HeaderDecoderError
+impl core::fmt::Debug for bitcoin_primitives::block::InvalidBlockError
impl core::fmt::Debug for bitcoin_primitives::block::Unchecked
impl core::fmt::Debug for bitcoin_primitives::block::Version
impl core::fmt::Debug for bitcoin_primitives::block::VersionDecoderError
@@ -413,6 +420,7 @@ impl core::fmt::Display for bitcoin_primitives::block::BlockDecoderError
impl core::fmt::Display for bitcoin_primitives::block::BlockHashDecoderError
impl core::fmt::Display for bitcoin_primitives::block::Header
impl core::fmt::Display for bitcoin_primitives::block::HeaderDecoderError
+impl core::fmt::Display for bitcoin_primitives::block::InvalidBlockError
impl core::fmt::Display for bitcoin_primitives::block::VersionDecoderError
impl core::fmt::Display for bitcoin_primitives::pow::CompactTargetDecoderError
impl core::fmt::Display for bitcoin_primitives::script::RedeemScriptSizeError
@@ -508,6 +516,7 @@ impl core::marker::Freeze for bitcoin_primitives::block::Header
impl core::marker::Freeze for bitcoin_primitives::block::HeaderDecoder
impl core::marker::Freeze for bitcoin_primitives::block::HeaderDecoderError
impl core::marker::Freeze for bitcoin_primitives::block::HeaderEncoder
+impl core::marker::Freeze for bitcoin_primitives::block::InvalidBlockError
impl core::marker::Freeze for bitcoin_primitives::block::Unchecked
impl core::marker::Freeze for bitcoin_primitives::block::Version
impl core::marker::Freeze for bitcoin_primitives::block::VersionDecoder
@@ -568,6 +577,7 @@ impl core::marker::Send for bitcoin_primitives::block::Header
impl core::marker::Send for bitcoin_primitives::block::HeaderDecoder
impl core::marker::Send for bitcoin_primitives::block::HeaderDecoderError
impl core::marker::Send for bitcoin_primitives::block::HeaderEncoder
+impl core::marker::Send for bitcoin_primitives::block::InvalidBlockError
impl core::marker::Send for bitcoin_primitives::block::Unchecked
impl core::marker::Send for bitcoin_primitives::block::Version
impl core::marker::Send for bitcoin_primitives::block::VersionDecoder
@@ -623,6 +633,7 @@ impl core::marker::StructuralPartialEq for bitcoin_primitives::block::BlockHashD
impl core::marker::StructuralPartialEq for bitcoin_primitives::block::Checked
impl core::marker::StructuralPartialEq for bitcoin_primitives::block::Header
impl core::marker::StructuralPartialEq for bitcoin_primitives::block::HeaderDecoderError
+impl core::marker::StructuralPartialEq for bitcoin_primitives::block::InvalidBlockError
impl core::marker::StructuralPartialEq for bitcoin_primitives::block::Unchecked
impl core::marker::StructuralPartialEq for bitcoin_primitives::block::Version
impl core::marker::StructuralPartialEq for bitcoin_primitives::block::VersionDecoderError
@@ -670,6 +681,7 @@ impl core::marker::Sync for bitcoin_primitives::block::Header
impl core::marker::Sync for bitcoin_primitives::block::HeaderDecoder
impl core::marker::Sync for bitcoin_primitives::block::HeaderDecoderError
impl core::marker::Sync for bitcoin_primitives::block::HeaderEncoder
+impl core::marker::Sync for bitcoin_primitives::block::InvalidBlockError
impl core::marker::Sync for bitcoin_primitives::block::Unchecked
impl core::marker::Sync for bitcoin_primitives::block::Version
impl core::marker::Sync for bitcoin_primitives::block::VersionDecoder
@@ -730,6 +742,7 @@ impl core::marker::Unpin for bitcoin_primitives::block::Header
impl core::marker::Unpin for bitcoin_primitives::block::HeaderDecoder
impl core::marker::Unpin for bitcoin_primitives::block::HeaderDecoderError
impl core::marker::Unpin for bitcoin_primitives::block::HeaderEncoder
+impl core::marker::Unpin for bitcoin_primitives::block::InvalidBlockError
impl core::marker::Unpin for bitcoin_primitives::block::Unchecked
impl core::marker::Unpin for bitcoin_primitives::block::Version
impl core::marker::Unpin for bitcoin_primitives::block::VersionDecoder
@@ -791,6 +804,7 @@ impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::Head
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::HeaderDecoder
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::HeaderDecoderError
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::HeaderEncoder
+impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::InvalidBlockError
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::Unchecked
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::Version
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::VersionDecoder
@@ -851,6 +865,7 @@ impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::Header
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::HeaderDecoder
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::HeaderDecoderError
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::HeaderEncoder
+impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::InvalidBlockError
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::Unchecked
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::Version
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::VersionDecoder
@@ -1174,6 +1189,10 @@ pub bitcoin_primitives::block::HeaderDecoderError::Nonce(bitcoin_consensus_encod
pub bitcoin_primitives::block::HeaderDecoderError::PrevBlockhash(bitcoin_primitives::block::BlockHashDecoderError)
pub bitcoin_primitives::block::HeaderDecoderError::Time(bitcoin_units::time::BlockTimeDecoderError)
pub bitcoin_primitives::block::HeaderDecoderError::Version(bitcoin_primitives::block::VersionDecoderError)
+pub bitcoin_primitives::block::InvalidBlockError::InvalidCoinbase
+pub bitcoin_primitives::block::InvalidBlockError::InvalidMerkleRoot
+pub bitcoin_primitives::block::InvalidBlockError::InvalidWitnessCommitment
+pub bitcoin_primitives::block::InvalidBlockError::NoTransactions
pub bitcoin_primitives::transaction::OutPoint::txid: bitcoin_primitives::Txid
pub bitcoin_primitives::transaction::OutPoint::vout: u32
pub bitcoin_primitives::transaction::ParseOutPointError::Format
@@ -1422,8 +1441,12 @@ pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Checked>::cac
pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Checked>::header(&self) -> &bitcoin_primitives::block::Header
pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Checked>::transactions(&self) -> &[bitcoin_primitives::transaction::Transaction]
pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Unchecked>::assume_checked(self, witness_root: core::option::Option<bitcoin_primitives::WitnessMerkleNode>) -> bitcoin_primitives::block::Block<bitcoin_primitives::block::Checked>
+pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Unchecked>::check_merkle_root(&self) -> bool
+pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Unchecked>::check_witness_commitment(&self) -> (bool, core::option::Option<bitcoin_primitives::WitnessMerkleNode>)
+pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Unchecked>::compute_witness_commitment(&self, witness_reserved_value: &[u8]) -> core::option::Option<(bitcoin_primitives::WitnessMerkleNode, bitcoin_primitives::WitnessCommitment)>
pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Unchecked>::into_parts(self) -> (bitcoin_primitives::block::Header, alloc::vec::Vec<bitcoin_primitives::transaction::Transaction>)
pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Unchecked>::new_unchecked(header: bitcoin_primitives::block::Header, transactions: alloc::vec::Vec<bitcoin_primitives::transaction::Transaction>) -> Self
+pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Unchecked>::validate(self) -> core::result::Result<bitcoin_primitives::block::Block<bitcoin_primitives::block::Checked>, bitcoin_primitives::block::InvalidBlockError>
pub fn bitcoin_primitives::block::BlockDecoder::end(self) -> core::result::Result<Self::Output, Self::Error>
pub fn bitcoin_primitives::block::BlockDecoder::push_bytes(&mut self, bytes: &mut &[u8]) -> core::result::Result<bool, Self::Error>
pub fn bitcoin_primitives::block::BlockDecoder::read_limit(&self) -> usize
@@ -1472,6 +1495,10 @@ pub fn bitcoin_primitives::block::HeaderDecoderError::from(never: core::convert:
pub fn bitcoin_primitives::block::HeaderDecoderError::source(&self) -> core::option::Option<&(dyn core::error::Error + 'static)>
pub fn bitcoin_primitives::block::HeaderEncoder::advance(&mut self) -> bool
pub fn bitcoin_primitives::block::HeaderEncoder::current_chunk(&self) -> &[u8]
+pub fn bitcoin_primitives::block::InvalidBlockError::clone(&self) -> bitcoin_primitives::block::InvalidBlockError
+pub fn bitcoin_primitives::block::InvalidBlockError::eq(&self, other: &bitcoin_primitives::block::InvalidBlockError) -> bool
+pub fn bitcoin_primitives::block::InvalidBlockError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub fn bitcoin_primitives::block::InvalidBlockError::from(never: core::convert::Infallible) -> Self
pub fn bitcoin_primitives::block::Unchecked::clone(&self) -> bitcoin_primitives::block::Unchecked
pub fn bitcoin_primitives::block::Unchecked::cmp(&self, other: &bitcoin_primitives::block::Unchecked) -> core::cmp::Ordering
pub fn bitcoin_primitives::block::Unchecked::eq(&self, other: &bitcoin_primitives::block::Unchecked) -> bool
@@ -1503,6 +1530,8 @@ pub fn bitcoin_primitives::block::VersionDecoderError::from(never: core::convert
pub fn bitcoin_primitives::block::VersionDecoderError::source(&self) -> core::option::Option<&(dyn core::error::Error + 'static)>
pub fn bitcoin_primitives::block::VersionEncoder::advance(&mut self) -> bool
pub fn bitcoin_primitives::block::VersionEncoder::current_chunk(&self) -> &[u8]
+pub fn bitcoin_primitives::block::compute_merkle_root(transactions: &[bitcoin_primitives::transaction::Transaction]) -> core::option::Option<bitcoin_primitives::TxMerkleNode>
+pub fn bitcoin_primitives::block::compute_witness_root(transactions: &[bitcoin_primitives::transaction::Transaction]) -> core::option::Option<bitcoin_primitives::WitnessMerkleNode>
pub fn bitcoin_primitives::merkle_tree::TxMerkleNodeEncoder::advance(&mut self) -> bool
pub fn bitcoin_primitives::merkle_tree::TxMerkleNodeEncoder::current_chunk(&self) -> &[u8]
pub fn bitcoin_primitives::pow::CompactTarget::clone(&self) -> bitcoin_primitives::pow::CompactTarget
diff --git a/api/primitives/alloc-only.txt b/api/primitives/alloc-only.txt
index caa641ad..dc3dc482 100644
--- a/api/primitives/alloc-only.txt
+++ b/api/primitives/alloc-only.txt
@@ -1,4 +1,5 @@
#[non_exhaustive] pub enum bitcoin_primitives::block::HeaderDecoderError
+#[non_exhaustive] pub enum bitcoin_primitives::block::InvalidBlockError
#[repr(transparent)] pub struct bitcoin_primitives::script::Script<T>(_, _)
impl bitcoin_consensus_encoding::decode::Decodable for bitcoin_primitives::BlockHash
impl bitcoin_consensus_encoding::decode::Decodable for bitcoin_primitives::TxMerkleNode
@@ -114,6 +115,7 @@ impl core::clone::Clone for bitcoin_primitives::block::BlockHashDecoderError
impl core::clone::Clone for bitcoin_primitives::block::Checked
impl core::clone::Clone for bitcoin_primitives::block::Header
impl core::clone::Clone for bitcoin_primitives::block::HeaderDecoderError
+impl core::clone::Clone for bitcoin_primitives::block::InvalidBlockError
impl core::clone::Clone for bitcoin_primitives::block::Unchecked
impl core::clone::Clone for bitcoin_primitives::block::Version
impl core::clone::Clone for bitcoin_primitives::block::VersionDecoderError
@@ -155,6 +157,7 @@ impl core::cmp::Eq for bitcoin_primitives::block::BlockHashDecoderError
impl core::cmp::Eq for bitcoin_primitives::block::Checked
impl core::cmp::Eq for bitcoin_primitives::block::Header
impl core::cmp::Eq for bitcoin_primitives::block::HeaderDecoderError
+impl core::cmp::Eq for bitcoin_primitives::block::InvalidBlockError
impl core::cmp::Eq for bitcoin_primitives::block::Unchecked
impl core::cmp::Eq for bitcoin_primitives::block::Version
impl core::cmp::Eq for bitcoin_primitives::block::VersionDecoderError
@@ -221,6 +224,7 @@ impl core::cmp::PartialEq for bitcoin_primitives::block::BlockHashDecoderError
impl core::cmp::PartialEq for bitcoin_primitives::block::Checked
impl core::cmp::PartialEq for bitcoin_primitives::block::Header
impl core::cmp::PartialEq for bitcoin_primitives::block::HeaderDecoderError
+impl core::cmp::PartialEq for bitcoin_primitives::block::InvalidBlockError
impl core::cmp::PartialEq for bitcoin_primitives::block::Unchecked
impl core::cmp::PartialEq for bitcoin_primitives::block::Version
impl core::cmp::PartialEq for bitcoin_primitives::block::VersionDecoderError
@@ -319,6 +323,7 @@ impl core::convert::From<bitcoin_units::locktime::absolute::error::LockTimeDecod
impl core::convert::From<core::convert::Infallible> for bitcoin_primitives::block::BlockDecoderError
impl core::convert::From<core::convert::Infallible> for bitcoin_primitives::block::BlockHashDecoderError
impl core::convert::From<core::convert::Infallible> for bitcoin_primitives::block::HeaderDecoderError
+impl core::convert::From<core::convert::Infallible> for bitcoin_primitives::block::InvalidBlockError
impl core::convert::From<core::convert::Infallible> for bitcoin_primitives::block::VersionDecoderError
impl core::convert::From<core::convert::Infallible> for bitcoin_primitives::pow::CompactTargetDecoderError
impl core::convert::From<core::convert::Infallible> for bitcoin_primitives::script::RedeemScriptSizeError
@@ -355,6 +360,7 @@ impl core::fmt::Debug for bitcoin_primitives::block::BlockHashDecoderError
impl core::fmt::Debug for bitcoin_primitives::block::Checked
impl core::fmt::Debug for bitcoin_primitives::block::Header
impl core::fmt::Debug for bitcoin_primitives::block::HeaderDecoderError
+impl core::fmt::Debug for bitcoin_primitives::block::InvalidBlockError
impl core::fmt::Debug for bitcoin_primitives::block::Unchecked
impl core::fmt::Debug for bitcoin_primitives::block::Version
impl core::fmt::Debug for bitcoin_primitives::block::VersionDecoderError
@@ -382,6 +388,7 @@ impl core::fmt::Debug for bitcoin_primitives::witness::WitnessDecoderError
impl core::fmt::Display for bitcoin_primitives::block::BlockDecoderError
impl core::fmt::Display for bitcoin_primitives::block::BlockHashDecoderError
impl core::fmt::Display for bitcoin_primitives::block::HeaderDecoderError
+impl core::fmt::Display for bitcoin_primitives::block::InvalidBlockError
impl core::fmt::Display for bitcoin_primitives::block::VersionDecoderError
impl core::fmt::Display for bitcoin_primitives::pow::CompactTargetDecoderError
impl core::fmt::Display for bitcoin_primitives::script::RedeemScriptSizeError
@@ -455,6 +462,7 @@ impl core::marker::Freeze for bitcoin_primitives::block::Header
impl core::marker::Freeze for bitcoin_primitives::block::HeaderDecoder
impl core::marker::Freeze for bitcoin_primitives::block::HeaderDecoderError
impl core::marker::Freeze for bitcoin_primitives::block::HeaderEncoder
+impl core::marker::Freeze for bitcoin_primitives::block::InvalidBlockError
impl core::marker::Freeze for bitcoin_primitives::block::Unchecked
impl core::marker::Freeze for bitcoin_primitives::block::Version
impl core::marker::Freeze for bitcoin_primitives::block::VersionDecoder
@@ -514,6 +522,7 @@ impl core::marker::Send for bitcoin_primitives::block::Header
impl core::marker::Send for bitcoin_primitives::block::HeaderDecoder
impl core::marker::Send for bitcoin_primitives::block::HeaderDecoderError
impl core::marker::Send for bitcoin_primitives::block::HeaderEncoder
+impl core::marker::Send for bitcoin_primitives::block::InvalidBlockError
impl core::marker::Send for bitcoin_primitives::block::Unchecked
impl core::marker::Send for bitcoin_primitives::block::Version
impl core::marker::Send for bitcoin_primitives::block::VersionDecoder
@@ -568,6 +577,7 @@ impl core::marker::StructuralPartialEq for bitcoin_primitives::block::BlockHashD
impl core::marker::StructuralPartialEq for bitcoin_primitives::block::Checked
impl core::marker::StructuralPartialEq for bitcoin_primitives::block::Header
impl core::marker::StructuralPartialEq for bitcoin_primitives::block::HeaderDecoderError
+impl core::marker::StructuralPartialEq for bitcoin_primitives::block::InvalidBlockError
impl core::marker::StructuralPartialEq for bitcoin_primitives::block::Unchecked
impl core::marker::StructuralPartialEq for bitcoin_primitives::block::Version
impl core::marker::StructuralPartialEq for bitcoin_primitives::block::VersionDecoderError
@@ -614,6 +624,7 @@ impl core::marker::Sync for bitcoin_primitives::block::Header
impl core::marker::Sync for bitcoin_primitives::block::HeaderDecoder
impl core::marker::Sync for bitcoin_primitives::block::HeaderDecoderError
impl core::marker::Sync for bitcoin_primitives::block::HeaderEncoder
+impl core::marker::Sync for bitcoin_primitives::block::InvalidBlockError
impl core::marker::Sync for bitcoin_primitives::block::Unchecked
impl core::marker::Sync for bitcoin_primitives::block::Version
impl core::marker::Sync for bitcoin_primitives::block::VersionDecoder
@@ -673,6 +684,7 @@ impl core::marker::Unpin for bitcoin_primitives::block::Header
impl core::marker::Unpin for bitcoin_primitives::block::HeaderDecoder
impl core::marker::Unpin for bitcoin_primitives::block::HeaderDecoderError
impl core::marker::Unpin for bitcoin_primitives::block::HeaderEncoder
+impl core::marker::Unpin for bitcoin_primitives::block::InvalidBlockError
impl core::marker::Unpin for bitcoin_primitives::block::Unchecked
impl core::marker::Unpin for bitcoin_primitives::block::Version
impl core::marker::Unpin for bitcoin_primitives::block::VersionDecoder
@@ -733,6 +745,7 @@ impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::Head
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::HeaderDecoder
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::HeaderDecoderError
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::HeaderEncoder
+impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::InvalidBlockError
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::Unchecked
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::Version
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::VersionDecoder
@@ -792,6 +805,7 @@ impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::Header
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::HeaderDecoder
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::HeaderDecoderError
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::HeaderEncoder
+impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::InvalidBlockError
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::Unchecked
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::Version
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::VersionDecoder
@@ -1048,6 +1062,10 @@ pub bitcoin_primitives::block::HeaderDecoderError::Nonce(bitcoin_consensus_encod
pub bitcoin_primitives::block::HeaderDecoderError::PrevBlockhash(bitcoin_primitives::block::BlockHashDecoderError)
pub bitcoin_primitives::block::HeaderDecoderError::Time(bitcoin_units::time::BlockTimeDecoderError)
pub bitcoin_primitives::block::HeaderDecoderError::Version(bitcoin_primitives::block::VersionDecoderError)
+pub bitcoin_primitives::block::InvalidBlockError::InvalidCoinbase
+pub bitcoin_primitives::block::InvalidBlockError::InvalidMerkleRoot
+pub bitcoin_primitives::block::InvalidBlockError::InvalidWitnessCommitment
+pub bitcoin_primitives::block::InvalidBlockError::NoTransactions
pub bitcoin_primitives::transaction::OutPoint::txid: bitcoin_primitives::Txid
pub bitcoin_primitives::transaction::OutPoint::vout: u32
pub bitcoin_primitives::transaction::Transaction::inputs: alloc::vec::Vec<bitcoin_primitives::transaction::TxIn>
@@ -1259,8 +1277,12 @@ pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Checked>::cac
pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Checked>::header(&self) -> &bitcoin_primitives::block::Header
pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Checked>::transactions(&self) -> &[bitcoin_primitives::transaction::Transaction]
pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Unchecked>::assume_checked(self, witness_root: core::option::Option<bitcoin_primitives::WitnessMerkleNode>) -> bitcoin_primitives::block::Block<bitcoin_primitives::block::Checked>
+pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Unchecked>::check_merkle_root(&self) -> bool
+pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Unchecked>::check_witness_commitment(&self) -> (bool, core::option::Option<bitcoin_primitives::WitnessMerkleNode>)
+pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Unchecked>::compute_witness_commitment(&self, witness_reserved_value: &[u8]) -> core::option::Option<(bitcoin_primitives::WitnessMerkleNode, bitcoin_primitives::WitnessCommitment)>
pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Unchecked>::into_parts(self) -> (bitcoin_primitives::block::Header, alloc::vec::Vec<bitcoin_primitives::transaction::Transaction>)
pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Unchecked>::new_unchecked(header: bitcoin_primitives::block::Header, transactions: alloc::vec::Vec<bitcoin_primitives::transaction::Transaction>) -> Self
+pub fn bitcoin_primitives::block::Block<bitcoin_primitives::block::Unchecked>::validate(self) -> core::result::Result<bitcoin_primitives::block::Block<bitcoin_primitives::block::Checked>, bitcoin_primitives::block::InvalidBlockError>
pub fn bitcoin_primitives::block::BlockDecoder::end(self) -> core::result::Result<Self::Output, Self::Error>
pub fn bitcoin_primitives::block::BlockDecoder::push_bytes(&mut self, bytes: &mut &[u8]) -> core::result::Result<bool, Self::Error>
pub fn bitcoin_primitives::block::BlockDecoder::read_limit(&self) -> usize
@@ -1305,6 +1327,10 @@ pub fn bitcoin_primitives::block::HeaderDecoderError::fmt(&self, f: &mut core::f
pub fn bitcoin_primitives::block::HeaderDecoderError::from(never: core::convert::Infallible) -> Self
pub fn bitcoin_primitives::block::HeaderEncoder::advance(&mut self) -> bool
pub fn bitcoin_primitives::block::HeaderEncoder::current_chunk(&self) -> &[u8]
+pub fn bitcoin_primitives::block::InvalidBlockError::clone(&self) -> bitcoin_primitives::block::InvalidBlockError
+pub fn bitcoin_primitives::block::InvalidBlockError::eq(&self, other: &bitcoin_primitives::block::InvalidBlockError) -> bool
+pub fn bitcoin_primitives::block::InvalidBlockError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub fn bitcoin_primitives::block::InvalidBlockError::from(never: core::convert::Infallible) -> Self
pub fn bitcoin_primitives::block::Unchecked::clone(&self) -> bitcoin_primitives::block::Unchecked
pub fn bitcoin_primitives::block::Unchecked::cmp(&self, other: &bitcoin_primitives::block::Unchecked) -> core::cmp::Ordering
pub fn bitcoin_primitives::block::Unchecked::eq(&self, other: &bitcoin_primitives::block::Unchecked) -> bool
@@ -1332,6 +1358,8 @@ pub fn bitcoin_primitives::block::VersionDecoderError::from(e: bitcoin_consensus
pub fn bitcoin_primitives::block::VersionDecoderError::from(never: core::convert::Infallible) -> Self
pub fn bitcoin_primitives::block::VersionEncoder::advance(&mut self) -> bool
pub fn bitcoin_primitives::block::VersionEncoder::current_chunk(&self) -> &[u8]
+pub fn bitcoin_primitives::block::compute_merkle_root(transactions: &[bitcoin_primitives::transaction::Transaction]) -> core::option::Option<bitcoin_primitives::TxMerkleNode>
+pub fn bitcoin_primitives::block::compute_witness_root(transactions: &[bitcoin_primitives::transaction::Transaction]) -> core::option::Option<bitcoin_primitives::WitnessMerkleNode>
pub fn bitcoin_primitives::merkle_tree::TxMerkleNodeEncoder::advance(&mut self) -> bool
pub fn bitcoin_primitives::merkle_tree::TxMerkleNodeEncoder::current_chunk(&self) -> &[u8]
pub fn bitcoin_primitives::pow::CompactTarget::clone(&self) -> bitcoin_primitives::pow::CompactTarget
diff --git a/bitcoin/src/blockdata/block.rs b/bitcoin/src/blockdata/block.rs
index c970db19..ef954879 100644
--- a/bitcoin/src/blockdata/block.rs
+++ b/bitcoin/src/blockdata/block.rs
@@ -10,7 +10,6 @@
use core::convert::Infallible;
use core::fmt;
-use hashes::{sha256d, HashEngine};
use internals::{compact_size, ToU64};
use io::{BufRead, Write};
@@ -19,12 +18,15 @@ use crate::merkle_tree::{TxMerkleNode, WitnessMerkleNode};
use crate::network::Params;
use crate::prelude::Vec;
use crate::script::{self, ScriptIntError, ScriptExt as _};
-use crate::transaction::{Coinbase, Transaction, TransactionExt as _, Wtxid};
+use crate::transaction::{Coinbase, Transaction, TransactionExt as _};
use crate::{internal_macros, BlockTime, Target, Weight, Work};
#[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)]
-pub use primitives::block::{Block, Checked, Unchecked, Validation, Version, BlockHash, Header, WitnessCommitment};
+pub use primitives::block::{
+ Block, Checked, Unchecked, Validation, Version, BlockHash, Header,
+ WitnessCommitment, compute_merkle_root, compute_witness_root, InvalidBlockError,
+};
#[doc(no_inline)]
pub use units::block::TooBigForRelativeHeightError;
#[doc(inline)]
@@ -111,155 +113,6 @@ impl Decodable for BlockTime {
}
}
-/// Extension functionality for the [`Block<Unchecked>`] type.
-pub trait BlockUncheckedExt: sealed::Sealed {
- /// Validates (or checks) a block.
- ///
- /// We define valid as:
- ///
- /// * The Merkle root of the header matches Merkle root of the transaction list.
- /// * The witness commitment in coinbase matches the transaction list.
- fn validate(self) -> Result<Block<Checked>, InvalidBlockError>;
-}
-
-impl BlockUncheckedExt for Block<Unchecked> {
- fn validate(self) -> Result<Block<Checked>, InvalidBlockError> {
- let (header, transactions) = self.into_parts();
-
- if transactions.is_empty() {
- return Err(InvalidBlockError::NoTransactions);
- }
-
- if !transactions[0].is_coinbase() {
- return Err(InvalidBlockError::InvalidCoinbase);
- }
-
- if !check_merkle_root(&header, &transactions) {
- return Err(InvalidBlockError::InvalidMerkleRoot);
- }
-
- match check_witness_commitment(&transactions) {
- (false, _) => Err(InvalidBlockError::InvalidWitnessCommitment),
- (true, witness_root) => {
- let block = Self::new_unchecked(header, transactions);
- Ok(block.assume_checked(witness_root))
- }
- }
- }
-}
-
-/// Computes the Merkle root for a list of transactions.
-///
-/// Returns `None` if the iterator was empty, or if the transaction list contains
-/// consecutive duplicates which would trigger CVE 2012-2459. Blocks with duplicate
-/// transactions will always be invalid, so there is no harm in us refusing to
-/// compute their merkle roots.
-///
-/// Unless you are certain your transaction list is nonempty and has no duplicates,
-/// you should not unwrap the `Option` returned by this method!
-pub fn compute_merkle_root(transactions: &[Transaction]) -> Option<TxMerkleNode> {
- let hashes = transactions.iter().map(|obj| obj.compute_txid());
- TxMerkleNode::calculate_root(hashes)
-}
-
-/// Computes the witness commitment for a list of transactions.
-pub fn compute_witness_commitment(
- transactions: &[Transaction],
- witness_reserved_value: &[u8],
-) -> Option<(WitnessMerkleNode, WitnessCommitment)> {
- compute_witness_root(transactions).map(|witness_root| {
- let mut encoder = sha256d::Hash::engine();
- witness_root.consensus_encode(&mut encoder).expect("engines don't error");
- encoder.input(witness_reserved_value);
- let witness_commitment =
- WitnessCommitment::from_byte_array(sha256d::Hash::from_engine(encoder).to_byte_array());
- (witness_root, witness_commitment)
- })
-}
-
-/// Computes the Merkle root of transactions hashed for witness.
-///
-/// Returns `None` if the iterator was empty, or if the transaction list contains
-/// consecutive duplicates which would trigger CVE 2012-2459. Blocks with duplicate
-/// transactions will always be invalid, so there is no harm in us refusing to
-/// compute their merkle roots.
-///
-/// Unless you are certain your transaction list is nonempty and has no duplicates,
-/// you should not unwrap the `Option` returned by this method!
-pub fn compute_witness_root(transactions: &[Transaction]) -> Option<WitnessMerkleNode> {
- let hashes = transactions.iter().enumerate().map(|(i, t)| {
- if i == 0 {
- // Replace the first hash with zeroes.
- Wtxid::COINBASE
- } else {
- t.compute_wtxid()
- }
- });
- WitnessMerkleNode::calculate_root(hashes)
-}
-
-/// Checks if Merkle root of header matches Merkle root of the transaction list.
-fn check_merkle_root(header: &Header, transactions: &[Transaction]) -> bool {
- match compute_merkle_root(transactions) {
- Some(merkle_root) => header.merkle_root == merkle_root,
- None => false,
- }
-}
-
-/// Checks if witness commitment in coinbase matches the transaction list.
-// Returns the Merkle root if it was computed (so it can be cached in `assume_checked`).
-fn check_witness_commitment(transactions: &[Transaction]) -> (bool, Option<WitnessMerkleNode>) {
- // Witness commitment is optional if there are no transactions using SegWit in the block.
- if transactions.iter().all(|t| t.inputs.iter().all(|i| i.witness.is_empty())) {
- return (true, None);
- }
-
- if transactions.is_empty() {
- return (false, None);
- }
-
- if transactions[0].is_coinbase() {
- let coinbase = transactions[0].clone();
- if let Some(commitment) = witness_commitment_from_coinbase(&coinbase) {
- // Witness reserved value is in coinbase input witness.
- let witness_vec: Vec<_> = coinbase.inputs[0].witness.iter().collect();
- if witness_vec.len() == 1 && witness_vec[0].len() == 32 {
- if let Some((witness_root, witness_commitment)) =
- compute_witness_commitment(transactions, witness_vec[0])
- {
- if commitment == witness_commitment {
- return (true, Some(witness_root));
- }
- }
- }
- }
- }
-
- (false, None)
-}
-
-fn witness_commitment_from_coinbase(coinbase: &Transaction) -> Option<WitnessCommitment> {
- // Consists of OP_RETURN, OP_PUSHBYTES_36, and four "witness header" bytes.
- const MAGIC: [u8; 6] = [0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed];
-
- if !coinbase.is_coinbase() {
- return None;
- }
-
- // Commitment is in the last output that starts with magic bytes.
- if let Some(pos) = coinbase
- .outputs
- .iter()
- .rposition(|o| o.script_pubkey.len() >= 38 && o.script_pubkey.as_bytes()[0..6] == MAGIC)
- {
- let bytes =
- <[u8; 32]>::try_from(&coinbase.outputs[pos].script_pubkey.as_bytes()[6..38]).unwrap();
- Some(WitnessCommitment::from_byte_array(bytes))
- } else {
- None
- }
-}
-
/// Extension functionality for the [`Block<Checked>`] type.
pub trait BlockCheckedExt: sealed::Sealed {
/// Constructs a new [`Block`].
@@ -433,40 +286,6 @@ mod sealed {
impl<V: super::Validation> Sealed for super::Block<V> {}
}
-/// Invalid block error.
-#[derive(Debug, Clone, PartialEq, Eq)]
-#[non_exhaustive]
-pub enum InvalidBlockError {
- /// Header Merkle root does not match the calculated Merkle root.
- InvalidMerkleRoot,
- /// The witness commitment in coinbase transaction does not match the calculated witness_root.
- InvalidWitnessCommitment,
- /// Block has no transactions (missing coinbase).
- NoTransactions,
- /// The first transaction is not a valid coinbase transaction.
- InvalidCoinbase,
-}
-
-impl From<Infallible> for InvalidBlockError {
- fn from(never: Infallible) -> Self { match never {} }
-}
-
-impl fmt::Display for InvalidBlockError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- Self::InvalidMerkleRoot =>
- write!(f, "header Merkle root does not match the calculated Merkle root"),
- Self::InvalidWitnessCommitment => write!(f, "the witness commitment in coinbase transaction does not match the calculated witness_root"),
- Self::NoTransactions => write!(f, "block has no transactions (missing coinbase)"),
- Self::InvalidCoinbase =>
- write!(f, "the first transaction is not a valid coinbase transaction"),
- }
- }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for InvalidBlockError {}
-
/// An error when looking up a BIP-0034 block height.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
@@ -560,6 +379,7 @@ impl std::error::Error for ValidationError {
mod tests {
use hex_lit::hex;
use internals::ToU64 as _;
+ use primitives::Wtxid;
use super::*;
use crate::consensus::encode::{deserialize, serialize};
@@ -573,9 +393,9 @@ mod tests {
// testnet block 000000000000045e0b1660b6445b5e5c5ab63c9a4f956be7e1e69be04fa4497b
let segwit_block = include_bytes!("../../tests/data/testnet_block_000000000000045e0b1660b6445b5e5c5ab63c9a4f956be7e1e69be04fa4497b.raw");
let block: Block = deserialize(&segwit_block[..]).expect("failed to deserialize block");
- let (header, transactions) = block.into_parts();
+ assert!(block.check_merkle_root());
- assert!(block::check_merkle_root(&header, &transactions));
+ let (header, transactions) = block.into_parts();
let block = Block::new_unchecked(header, transactions).assume_checked(None);
// Same as `block.check_merkle_root` but do it explicitly.
@@ -650,12 +470,13 @@ mod tests {
assert!(decode.is_ok());
assert!(bad_decode.is_err());
- let (header, transactions) = decode.unwrap().into_parts();
+ let block = decode.unwrap();
// should be also ok for a non-witness block as commitment is optional in that case
let (witness_commitment_matches, witness_root) =
- block::check_witness_commitment(&transactions);
+ block.check_witness_commitment();
assert!(witness_commitment_matches);
+ let (header, transactions) = block.into_parts();
let real_decode =
Block::new_unchecked(header, transactions.clone()).assume_checked(witness_root);
@@ -699,11 +520,12 @@ mod tests {
assert!(decode.is_ok());
- let (header, transactions) = decode.unwrap().into_parts();
+ let block = decode.unwrap();
let (witness_commitment_matches, witness_root) =
- block::check_witness_commitment(&transactions);
+ block.check_witness_commitment();
assert!(witness_commitment_matches);
+ let (header, transactions) = block.into_parts();
let real_decode =
Block::new_unchecked(header, transactions.clone()).assume_checked(witness_root);
@@ -812,47 +634,6 @@ mod tests {
assert!(!segwit_signal.is_signalling_soft_fork(2));
}
- #[test]
- fn block_validation_no_transactions() {
- let header = header();
- let transactions = Vec::new(); // Empty transactions
-
- let block = Block::new_unchecked(header, transactions);
- match block.validate() {
- Err(InvalidBlockError::NoTransactions) => (),
- other => panic!("Expected NoTransactions error, got: {:?}", other),
- }
- }
-
- #[test]
- fn block_validation_invalid_coinbase() {
- let header = header();
-
- // Create a non-coinbase transaction (has a real previous output, not all zeros)
- let non_coinbase_tx = Transaction {
- version: primitives::transaction::Version::TWO,
- lock_time: crate::absolute::LockTime::ZERO,
- inputs: vec![TxIn {
- previous_output: OutPoint {
- txid: Txid::from_byte_array([1; 32]), // Not all zeros
- vout: 0,
- },
- script_sig: ScriptSigBuf::new(),
- sequence: Sequence::ENABLE_LOCKTIME_AND_RBF,
- witness: Witness::new(),
- }],
- outputs: vec![TxOut { amount: Amount::ONE_BTC, script_pubkey: ScriptPubKeyBuf::new() }],
- };
-
- let transactions = vec![non_coinbase_tx];
- let block = Block::new_unchecked(header, transactions);
-
- match block.validate() {
- Err(InvalidBlockError::InvalidCoinbase) => (),
- other => panic!("Expected InvalidCoinbase error, got: {:?}", other),
- }
- }
-
#[test]
fn block_validation_success_with_coinbase() {
use crate::constants;
@@ -945,19 +726,4 @@ mod tests {
let cb_txid = "d574f343976d8e70d91cb278d21044dd8a396019e6db70755a0a50e4783dba38";
assert_eq!(coinbase.compute_txid().to_string(), cb_txid);
}
-
- // Test vector provided by tm0 in issue #5023
- #[test]
- fn merkle_tree_hash_collision() {
- // https://learnmeabitcoin.com/explorer/block/00000000000008a662b4a95a46e4c54cb04852525ac0ef67d1bcac85238416d4
- // this block has 7 transactions
- const BLOCK_128461_HEX: &str = "01000000166208c96de305f2a304130a1b53727abf8fb77e8a3cfe2a831e000000000000d4fd086755b4d46221362a09a4228bed60d729d22362b87803ff44b72c138ec04a8ce94d2194261af9551f720701000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08042194261a026005ffffffff018076242a01000000434104390e51c3d66d5ee10327395872e33bc232e9e1660225c9f88fa594fdcdcd785d86b1152fb380a63cdf57d8cf2345a55878412a6864656b158704e0b734b3fd9dac000000000100000001f591edc180a889b21a45b6bd5b5e0017d4137dae9695703107ac1e6e878c9f02000000008b483045022100e066df28b29bf18bfcd8da11ea576a6f502f59e7b1d37e2e849ee4648008962b022023be840ec01ffa6860b5577bf0b8546541f40c287eb57b8b421a1396c7aea583014104add16286f51f68cee1b436d0c29a41a59fa8bd224eb6bec34b073512303c70fc3d630cb4952416ef02340c56bee2eef294659b4023ea8a3d90a297bdb54321f9ffffffff02508470b5000000001976a91472579bbeaeca0802fde07ce88f946b64da63989388ac40aeeb02000000001976a914d2a7410246b5ece345aa821af89bff0b6fa3bcaa88ac0000000001000000016197cb143d4cef51389076fdee3f62c294b65bc9aff217a6c71b9dd987e22754000000008c493046022100bf174e942e4619f4e470b5d8b1c0c8ded9e2f7a6616c073c5ab05cc9d699ede3022100a642fa9d0bcc89523635f9468e4813a120b233a249678de0ebf7ba398a4205f6014104122979c0ac1c3af2aa84b4c1d6a9b3b6fa491827f1a2ba37c4b58bdecd644438da715497a44b16aedbadbd18cf9765cdb36851284f643ed743c4365798dd314affffffff02c0404384000000001976a91443cd8fbad7421a53f9e899a2c9761259705d465b88acc0f4f50e000000001976a9142f6c963506b0a2c93a09a92171957e9e7e11a7a388ac00000000010000000228a11f953c26d558a8299ad9dc61279d7abc9a4059820b614bf403c05e471c481d0000008b48304502205baff189016e6fee8e0faa9eebdc8f150d2d3815007719ceccabd995607bb0b0022100f4cc49ef0b29561e976bf6f6f7ae135f665b8dd38a67634bb6bbe74c0da9c1f7014104dd5920aedc3f79ace9c8061f3724812f5b218ea81d175dd990071175874d6c79025f9db516ab23975e510645aabc4ee699cc5c24358a403d15a7736a504399f8ffffffff191b06773a7cec0bb30539f185edbf1d139f9756071c6ae395c1c29f3e2484f6010000008c493046022100c7123436476f923cd8dacbe132f5128b529baa194c9aedc570402d8d2d7902ac02210094e6974695265d96d5859ab493df00c90b62a84dcc33a05753aea23b38c249670141041d878bc5438ff439490e71d059e6b687e511336c0aa53e0d129663c91db71cfe20008891f1e4780bf1139ec9c9e81bfd2e3ea9009608a78d96a5a3a5bf7812baffffffff0200093d00000000001976a914fd0d4c3d0963db8358bd01ba6f386d4c5ef2e30288ac0084d717000000001976a914dcb1e8e699eb9f07a1ddfd5d764aa74359ddd93088ac00000000010000000118e2286c42643e6146669b0f5ee35454fe256aac2b1401dbeefd941f2e6d2074000000008b483045022100edec1c5078fed29d808282d62f167eb3f0ea6a6655f3869c12eca9c63d8463c2022031a3ae430be137932059b4a3e3fb7f1e1f2a05065dbc47c3142972de45c76daa01410423162e5ac10ec46c4a142fea3197cc66e614b9f28f014882ebc8271c4ab6022e474ccdc246445dd2479f9de217e8aaf4d770da15aff1078d329c02e0f4de8d77ffffffff02b00ac165000000001976a914f543a7f0dfcd621a05c646810ba94da791ed14c488ac80de8002000000001976a9144763f6309b3aca0bff49ed6365ffbd791b1afc5d88ac0000000001000000014e3632994e6cbcae4122bf9e8de242aa1d7c13bf6d045392fa69fa92353f13cf000000008c493046022100c6879938322e9945dae2404a2b104b534df7fdab5927a30a57a12418d619c3b8022100c53331f402010cbdc8297d7a827154e42263fc2f6cef6e56b85bbc061d5e30810141047e717e70b8c5e928bc2c482662dbe9007113f7a5fb0360da1d2f193add960fed97ab3163e85c02b127829d694ab4a796326918d4f639d0b19345f7558406667dffffffff0270c8b165000000001976a9146c908731300d5c0a4215ba3bb3041b4f313d14f688ac40420f00000000001976a91457b01e2a6bf178a10a0e36cd3e301a41ac58b68b88ac000000000100000001a2e94f26db15d7098104a3616b650cc7490eca961a23111c12c3d94f593ab3bc000000008c493046022100b355076f2c956d7565d44fdf589ebdbdff70abcd806c71845b47d31c3579cbc00221008352a03c5276ba481ae92a2327307ad1ce9b234be7386c105fb914ceb9c63341014104872ee8390f11c8ac309df772362614ff7c99f98e1fd68888c5e8765d630c93ae86fcd33922b17f5da490ea14a9f9002ef4e7fb11166ba399f9794296ca02e401ffffffff02f07d5460000000001976a914ff1da11fbd50b9906e78c694169c19902d2ee20388ac804a5d05000000001976a91444d5774b8277c59a07ed9dce1225e2d24a3faab188ac00000000";
- let valid_block: Block<Unchecked> = deserialize(&hex!(BLOCK_128461_HEX)).unwrap();
- let (header, mut transactions) = valid_block.clone().into_parts();
- transactions.push(transactions[6].clone());
- let forged_block = Block::new_unchecked(header, transactions);
-
- assert!(valid_block.validate().is_ok());
- assert!(forged_block.validate().is_err());
- }
}
diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs
index efbe394f..66ba05b7 100644
--- a/bitcoin/src/lib.rs
+++ b/bitcoin/src/lib.rs
@@ -105,7 +105,7 @@ pub mod ext {
//! ```
#[rustfmt::skip] // Use terse custom grouping.
pub use crate::{
- block::{BlockUncheckedExt as _, BlockCheckedExt as _, HeaderExt as _},
+ block::{BlockCheckedExt as _, HeaderExt as _},
pow::CompactTargetExt as _,
script::{ScriptExt as _, ScriptBufExt as _, TapScriptExt as _, ScriptPubKeyExt as _, ScriptPubKeyBufExt as _, WitnessScriptExt as _, ScriptSigExt as _},
transaction::{TxidExt as _, WtxidExt as _, OutPointExt as _, TxInExt as _, TxOutExt as _, TransactionExt as _},
@@ -141,7 +141,7 @@ pub use primitives::{
block::{
Block, BlockHash, Checked as BlockChecked, Header as BlockHeader,
Unchecked as BlockUnchecked, Validation as BlockValidation, Version as BlockVersion,
- WitnessCommitment,
+ WitnessCommitment, compute_merkle_root, compute_witness_root, InvalidBlockError,
},
merkle_tree::{TxMerkleNode, WitnessMerkleNode},
pow::CompactTarget, // No `pow` module outside of `primitives`.
diff --git a/bitcoin/src/merkle_tree/block.rs b/bitcoin/src/merkle_tree/block.rs
index 9b068220..04223129 100644
--- a/bitcoin/src/merkle_tree/block.rs
+++ b/bitcoin/src/merkle_tree/block.rs
@@ -45,7 +45,6 @@ impl MerkleBlock {
/// # Examples
///
/// ```rust
- /// use bitcoin::block::BlockUncheckedExt as _;
/// use bitcoin::hex::FromHex;
/// use bitcoin::{Block, MerkleBlock, Txid};
///
@@ -540,7 +539,7 @@ mod tests {
use {core::cmp, secp256k1::rand::prelude::*};
use super::*;
- use crate::block::{BlockUncheckedExt as _, Unchecked};
+ use crate::block::Unchecked;
use crate::consensus::encode;
use crate::Txid;
diff --git a/fuzz/fuzz_targets/bitcoin/arbitrary_block.rs b/fuzz/fuzz_targets/bitcoin/arbitrary_block.rs
index f8f38478..6c66bbc6 100644
--- a/fuzz/fuzz_targets/bitcoin/arbitrary_block.rs
+++ b/fuzz/fuzz_targets/bitcoin/arbitrary_block.rs
@@ -14,7 +14,7 @@ fn do_test(data: &[u8]) {
let (header, transactions) = block.clone().into_parts();
block::compute_merkle_root(&transactions);
// Use 32-byte zero array as witness_reserved_value per BIP-0141 requirement.
- block::compute_witness_commitment(&transactions, &[0u8; 32]);
+ block.compute_witness_commitment(&[0u8; 32]);
block::compute_witness_root(&transactions);
if let Ok(block) = Block::new_checked(header, transactions) {
diff --git a/primitives/src/block.rs b/primitives/src/block.rs
index 09c30c18..8bb15166 100644
--- a/primitives/src/block.rs
+++ b/primitives/src/block.rs
@@ -108,6 +108,86 @@ impl Block<Unchecked> {
/// Decomposes block into its constituent parts.
#[inline]
pub fn into_parts(self) -> (Header, Vec<Transaction>) { (self.header, self.transactions) }
+
+ /// Validates (or checks) a block.
+ ///
+ /// We define valid as:
+ ///
+ /// * The Merkle root of the header matches Merkle root of the transaction list.
+ /// * The witness commitment in coinbase matches the transaction list.
+ pub fn validate(self) -> Result<Block<Checked>, InvalidBlockError> {
+ if self.transactions.is_empty() {
+ return Err(InvalidBlockError::NoTransactions);
+ }
+
+ if !self.transactions[0].is_coinbase() {
+ return Err(InvalidBlockError::InvalidCoinbase);
+ }
+
+ if !self.check_merkle_root() {
+ return Err(InvalidBlockError::InvalidMerkleRoot);
+ }
+
+ match self.check_witness_commitment() {
+ (false, _) => Err(InvalidBlockError::InvalidWitnessCommitment),
+ (true, witness_root) => {
+ let block = Self::new_unchecked(self.header, self.transactions);
+ Ok(block.assume_checked(witness_root))
+ }
+ }
+ }
+
+ /// Checks if Merkle root of header matches Merkle root of the transaction list.
+ pub fn check_merkle_root(&self) -> bool {
+ match compute_merkle_root(&self.transactions) {
+ Some(merkle_root) => self.header.merkle_root == merkle_root,
+ None => false,
+ }
+ }
+
+ /// Computes the witness commitment for a list of transactions.
+ pub fn compute_witness_commitment(&self, witness_reserved_value: &[u8]) -> Option<(WitnessMerkleNode, WitnessCommitment)> {
+ compute_witness_root(&self.transactions).map(|witness_root| {
+ let mut encoder = sha256d::Hash::engine();
+ encoder = hashes::encode_to_engine(&witness_root, encoder);
+ encoder.input(witness_reserved_value);
+ let witness_commitment =
+ WitnessCommitment::from_byte_array(sha256d::Hash::from_engine(encoder).to_byte_array());
+ (witness_root, witness_commitment)
+ })
+ }
+
+ /// Checks if witness commitment in coinbase matches the transaction list.
+ // Returns the Merkle root if it was computed (so it can be cached in `assume_checked`).
+ pub fn check_witness_commitment(&self) -> (bool, Option<WitnessMerkleNode>) {
+ if self.transactions.is_empty() {
+ return (false, None);
+ }
+
+ // Witness commitment is optional if there are no transactions using SegWit in the block.
+ if self.transactions.iter().all(|t| t.inputs.iter().all(|i| i.witness.is_empty())) {
+ return (true, None);
+ }
+
+ if self.transactions[0].is_coinbase() {
+ let coinbase = self.transactions[0].clone();
+ if let Some(commitment) = witness_commitment_from_coinbase(&coinbase) {
+ // Witness reserved value is in coinbase input witness.
+ let witness_vec: Vec<_> = coinbase.inputs[0].witness.iter().collect();
+ if witness_vec.len() == 1 && witness_vec[0].len() == 32 {
+ if let Some((witness_root, witness_commitment)) =
+ self.compute_witness_commitment(witness_vec[0])
+ {
+ if commitment == witness_commitment {
+ return (true, Some(witness_root));
+ }
+ }
+ }
+ }
+ }
+
+ (false, None)
+ }
}
#[cfg(feature = "alloc")]
@@ -272,6 +352,104 @@ impl std::error::Error for BlockDecoderError {
}
}
+/// Invalid block error.
+#[cfg(feature = "alloc")]
+#[derive(Debug, Clone, PartialEq, Eq)]
+#[non_exhaustive]
+pub enum InvalidBlockError {
+ /// Header Merkle root does not match the calculated Merkle root.
+ InvalidMerkleRoot,
+ /// The witness commitment in coinbase transaction does not match the calculated `witness_root`.
+ InvalidWitnessCommitment,
+ /// Block has no transactions (missing coinbase).
+ NoTransactions,
+ /// The first transaction is not a valid coinbase transaction.
+ InvalidCoinbase,
+}
+
+#[cfg(feature = "alloc")]
+impl From<Infallible> for InvalidBlockError {
+ fn from(never: Infallible) -> Self { match never {} }
+}
+
+#[cfg(feature = "alloc")]
+impl fmt::Display for InvalidBlockError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::InvalidMerkleRoot =>
+ write!(f, "header Merkle root does not match the calculated Merkle root"),
+ Self::InvalidWitnessCommitment => write!(f, "the witness commitment in coinbase transaction does not match the calculated witness_root"),
+ Self::NoTransactions => write!(f, "block has no transactions (missing coinbase)"),
+ Self::InvalidCoinbase =>
+ write!(f, "the first transaction is not a valid coinbase transaction"),
+ }
+ }
+}
+
+#[cfg(feature = "alloc")]
+#[cfg(feature = "std")]
+impl std::error::Error for InvalidBlockError {}
+
+/// Computes the Merkle root for a list of transactions.
+///
+/// Returns `None` if the iterator was empty, or if the transaction list contains
+/// consecutive duplicates which would trigger CVE 2012-2459. Blocks with duplicate
+/// transactions will always be invalid, so there is no harm in us refusing to
+/// compute their merkle roots.
+///
+/// Unless you are certain your transaction list is nonempty and has no duplicates,
+/// you should not unwrap the `Option` returned by this method!
+#[cfg(feature = "alloc")]
+pub fn compute_merkle_root(transactions: &[Transaction]) -> Option<TxMerkleNode> {
+ let hashes = transactions.iter().map(Transaction::compute_txid);
+ TxMerkleNode::calculate_root(hashes)
+}
+
+/// Computes the Merkle root of transactions hashed for witness.
+///
+/// Returns `None` if the iterator was empty, or if the transaction list contains
+/// consecutive duplicates which would trigger CVE 2012-2459. Blocks with duplicate
+/// transactions will always be invalid, so there is no harm in us refusing to
+/// compute their merkle roots.
+///
+/// Unless you are certain your transaction list is nonempty and has no duplicates,
+/// you should not unwrap the `Option` returned by this method!
+#[cfg(feature = "alloc")]
+pub fn compute_witness_root(transactions: &[Transaction]) -> Option<WitnessMerkleNode> {
+ let hashes = transactions.iter().enumerate().map(|(i, t)| {
+ if i == 0 {
+ // Replace the first hash with zeroes.
+ crate::Wtxid::COINBASE
+ } else {
+ t.compute_wtxid()
+ }
+ });
+ WitnessMerkleNode::calculate_root(hashes)
+}
+
+#[cfg(feature = "alloc")]
+fn witness_commitment_from_coinbase(coinbase: &Transaction) -> Option<WitnessCommitment> {
+ // Consists of OP_RETURN, OP_PUSHBYTES_36, and four "witness header" bytes.
+ const MAGIC: [u8; 6] = [0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed];
+
+ if !coinbase.is_coinbase() {
+ return None;
+ }
+
+ // Commitment is in the last output that starts with magic bytes.
+ if let Some(pos) = coinbase
+ .outputs
+ .iter()
+ .rposition(|o| o.script_pubkey.len() >= 38 && o.script_pubkey.as_bytes()[0..6] == MAGIC)
+ {
+ let bytes =
+ <[u8; 32]>::try_from(&coinbase.outputs[pos].script_pubkey.as_bytes()[6..38]).unwrap();
+ Some(WitnessCommitment::from_byte_array(bytes))
+ } else {
+ None
+ }
+}
+
/// Bitcoin block header.
///
/// Contains all the block's information except the actual transactions, but
@@ -807,6 +985,69 @@ mod tests {
assert_eq!(checked_block.cached_witness_root(), witness_root);
}
+ #[test]
+ #[cfg(feature = "alloc")]
+ fn block_validation_no_transactions() {
+ let header = dummy_header();
+ let transactions = Vec::new(); // Empty transactions
+
+ let block = Block::new_unchecked(header, transactions);
+ match block.validate() {
+ Err(InvalidBlockError::NoTransactions) => (),
+ other => panic!("Expected NoTransactions error, got: {:?}", other),
+ }
+ }
+
+ #[test]
+ #[cfg(feature = "alloc")]
+ fn block_validation_invalid_coinbase() {
+ let header = dummy_header();
+
+ // Create a non-coinbase transaction (has a real previous output, not all zeros)
+ let non_coinbase_tx = Transaction {
+ version: crate::transaction::Version::TWO,
+ lock_time: crate::absolute::LockTime::ZERO,
+ inputs: vec![crate::TxIn {
+ previous_output: crate::OutPoint {
+ txid: crate::Txid::from_byte_array([1; 32]), // Not all zeros
+ vout: 0,
+ },
+ script_sig: crate::ScriptSigBuf::new(),
+ sequence: units::Sequence::ENABLE_LOCKTIME_AND_RBF,
+ witness: crate::Witness::new(),
+ }],
+ outputs: vec![crate::TxOut { amount: units::Amount::ONE_BTC, script_pubkey: crate::ScriptPubKeyBuf::new() }],
+ };
+
+ let transactions = vec![non_coinbase_tx];
+ let block = Block::new_unchecked(header, transactions);
+
+ match block.validate() {
+ Err(InvalidBlockError::InvalidCoinbase) => (),
+ other => panic!("Expected InvalidCoinbase error, got: {:?}", other),
+ }
+ }
+
+ #[test]
+ #[cfg(feature = "alloc")]
+ fn block_check_witness_commitment_optional() {
+ // Valid block with optional witness commitment
+ let mut header = dummy_header();
+ header.merkle_root = TxMerkleNode::from_byte_array([0u8; 32]);
+ let coinbase = Transaction {
+ version: crate::transaction::Version::ONE,
+ lock_time: crate::absolute::LockTime::ZERO,
+ inputs: vec![crate::TxIn::EMPTY_COINBASE],
+ outputs: vec![],
+ };
+
+ let transactions = vec![coinbase];
+ let block = Block::new_unchecked(header, transactions);
+
+ let result = block.check_witness_commitment();
+ assert_eq!(result, (true, None));
+ }
+
#[test]
#[cfg(feature = "alloc")]
fn block_block_hash() {
@@ -941,4 +1182,149 @@ mod tests {
assert_eq!(original_block, decoded_block);
}
+
+ // Test vector provided by tm0 in issue #5023
+ #[test]
+ #[cfg(all(feature = "alloc", feature = "hex"))]
+ fn merkle_tree_hash_collision() {
+ // https://learnmeabitcoin.com/explorer/block/00000000000008a662b4a95a46e4c54cb04852525ac0ef67d1bcac85238416d4
+ // this block has 7 transactions
+ const BLOCK_128461_HEX: &str = "01000000166208c96de305f2a304130a1b53727abf8fb77e8a3cfe2a831e000000000000d4fd086755b4d46221362a09a4228bed60d729d22362b87803ff44b72c138ec04a8ce94d2194261af9551f720701000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08042194261a026005ffffffff018076242a01000000434104390e51c3d66d5ee10327395872e33bc232e9e1660225c9f88fa594fdcdcd785d86b1152fb380a63cdf57d8cf2345a55878412a6864656b158704e0b734b3fd9dac000000000100000001f591edc180a889b21a45b6bd5b5e0017d4137dae9695703107ac1e6e878c9f02000000008b483045022100e066df28b29bf18bfcd8da11ea576a6f502f59e7b1d37e2e849ee4648008962b022023be840ec01ffa6860b5577bf0b8546541f40c287eb57b8b421a1396c7aea583014104add16286f51f68cee1b436d0c29a41a59fa8bd224eb6bec34b073512303c70fc3d630cb4952416ef02340c56bee2eef294659b4023ea8a3d90a297bdb54321f9ffffffff02508470b5000000001976a91472579bbeaeca0802fde07ce88f946b64da63989388ac40aeeb02000000001976a914d2a7410246b5ece345aa821af89bff0b6fa3bcaa88ac0000000001000000016197cb143d4cef51389076fdee3f62c294b65bc9aff217a6c71b9dd987e22754000000008c493046022100bf174e942e4619f4e470b5d8b1c0c8ded9e2f7a6616c073c5ab05cc9d699ede3022100a642fa9d0bcc89523635f9468e4813a120b233a249678de0ebf7ba398a4205f6014104122979c0ac1c3af2aa84b4c1d6a9b3b6fa491827f1a2ba37c4b58bdecd644438da715497a44b16aedbadbd18cf9765cdb36851284f643ed743c4365798dd314affffffff02c0404384000000001976a91443cd8fbad7421a53f9e899a2c9761259705d465b88acc0f4f50e000000001976a9142f6c963506b0a2c93a09a92171957e9e7e11a7a388ac00000000010000000228a11f953c26d558a8299ad9dc61279d7abc9a4059820b614bf403c05e471c481d0000008b48304502205baff189016e6fee8e0faa9eebdc8f150d2d3815007719ceccabd995607bb0b0022100f4cc49ef0b29561e976bf6f6f7ae135f665b8dd38a67634bb6bbe74c0da9c1f7014104dd5920aedc3f79ace9c8061f3724812f5b218ea81d175dd990071175874d6c79025f9db516ab23975e510645aabc4ee699cc5c24358a403d15a7736a504399f8ffffffff191b06773a7cec0bb30539f185edbf1d139f9756071c6ae395c1c29f3e2484f6010000008c493046022100c7123436476f923cd8dacbe132f5128b529baa194c9aedc570402d8d2d7902ac02210094e6974695265d96d5859ab493df00c90b62a84dcc33a05753aea23b38c249670141041d878bc5438ff439490e71d059e6b687e511336c0aa53e0d129663c91db71cfe20008891f1e4780bf1139ec9c9e81bfd2e3ea9009608a78d96a5a3a5bf7812baffffffff0200093d00000000001976a914fd0d4c3d0963db8358bd01ba6f386d4c5ef2e30288ac0084d717000000001976a914dcb1e8e699eb9f07a1ddfd5d764aa74359ddd93088ac00000000010000000118e2286c42643e6146669b0f5ee35454fe256aac2b1401dbeefd941f2e6d2074000000008b483045022100edec1c5078fed29d808282d62f167eb3f0ea6a6655f3869c12eca9c63d8463c2022031a3ae430be137932059b4a3e3fb7f1e1f2a05065dbc47c3142972de45c76daa01410423162e5ac10ec46c4a142fea3197cc66e614b9f28f014882ebc8271c4ab6022e474ccdc246445dd2479f9de217e8aaf4d770da15aff1078d329c02e0f4de8d77ffffffff02b00ac165000000001976a914f543a7f0dfcd621a05c646810ba94da791ed14c488ac80de8002000000001976a9144763f6309b3aca0bff49ed6365ffbd791b1afc5d88ac0000000001000000014e3632994e6cbcae4122bf9e8de242aa1d7c13bf6d045392fa69fa92353f13cf000000008c493046022100c6879938322e9945dae2404a2b104b534df7fdab5927a30a57a12418d619c3b8022100c53331f402010cbdc8297d7a827154e42263fc2f6cef6e56b85bbc061d5e30810141047e717e70b8c5e928bc2c482662dbe9007113f7a5fb0360da1d2f193add960fed97ab3163e85c02b127829d694ab4a796326918d4f639d0b19345f7558406667dffffffff0270c8b165000000001976a9146c908731300d5c0a4215ba3bb3041b4f313d14f688ac40420f00000000001976a91457b01e2a6bf178a10a0e36cd3e301a41ac58b68b88ac000000000100000001a2e94f26db15d7098104a3616b650cc7490eca961a23111c12c3d94f593ab3bc000000008c493046022100b355076f2c956d7565d44fdf589ebdbdff70abcd806c71845b47d31c3579cbc00221008352a03c5276ba481ae92a2327307ad1ce9b234be7386c105fb914ceb9c63341014104872ee8390f11c8ac309df772362614ff7c99f98e1fd68888c5e8765d630c93ae86fcd33922b17f5da490ea14a9f9002ef4e7fb11166ba399f9794296ca02e401ffffffff02f07d5460000000001976a914ff1da11fbd50b9906e78c694169c19902d2ee20388ac804a5d05000000001976a91444d5774b8277c59a07ed9dce1225e2d24a3faab188ac00000000";
+ let bytes: [u8; 1948] = hex_unstable::FromHex::from_hex(BLOCK_128461_HEX).unwrap();
+ let valid_block: Block<Unchecked> = encoding::decode_from_slice(&bytes).unwrap();
+ let (header, mut transactions) = valid_block.clone().into_parts();
+ transactions.push(transactions[6].clone());
+ let forged_block = Block::new_unchecked(header, transactions);
+
+ assert!(valid_block.validate().is_ok());
+ assert!(forged_block.validate().is_err());
+ }
+
+ #[test]
+ #[cfg(feature = "alloc")]
+ fn witness_commitment_from_coinbase_simple() {
+ // Add witness commitment to the coinbase
+ let magic = [0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed];
+ let mut pubkey_bytes = [0; 38];
+ pubkey_bytes[0..6].copy_from_slice(&magic);
+ let witness_commitment = WitnessCommitment::from_byte_array(pubkey_bytes[6..38].try_into().unwrap());
+ let commitment_script = crate::script::ScriptBuf::from_bytes(pubkey_bytes.to_vec());
+
+ // Create a coinbase transaction with witness commitment
+ let tx = Transaction {
+ version: crate::transaction::Version::ONE,
+ lock_time: crate::absolute::LockTime::ZERO,
+ inputs: vec![crate::TxIn::EMPTY_COINBASE],
+ outputs: vec![crate::TxOut { amount: units::Amount::MIN, script_pubkey: commitment_script }],
+ };
+
+ // Test if the witness commitment is extracted properly
+ let extracted = witness_commitment_from_coinbase(&tx);
+ assert_eq!(extracted, Some(witness_commitment));
+ }
+
+ #[test]
+ #[cfg(feature = "alloc")]
+ fn block_check_witness_commitment_empty_script_pubkey() {
+ let mut txin = crate::TxIn::EMPTY_COINBASE;
+ let push = [11_u8];
+ txin.witness.push(push);
+
+ let tx = Transaction {
+ version: crate::transaction::Version::ONE,
+ lock_time: crate::absolute::LockTime::ZERO,
+ inputs: vec![txin],
+ outputs: vec![crate::TxOut {
+ amount: units::Amount::MIN,
+ // Empty scriptbuf means there is no witness commitment due to no magic bytes.
+ script_pubkey: crate::script::ScriptBuf::new(),
+ }],
+ };
+
+ let block = Block::new_unchecked(dummy_header(), vec![tx]);
+ let result = block.check_witness_commitment();
+ assert_eq!(result, (false, None)); // (false, None) since there's no valid witness commitment
+ }
+
+ #[test]
+ #[cfg(feature = "alloc")]
+ fn block_check_witness_commitment_no_transactions() {
+ // Test case of block with no transactions
+ let empty_block = Block::new_unchecked(dummy_header(), vec![]);
+ let result = empty_block.check_witness_commitment();
+ assert_eq!(result, (false, None));
+ }
+
+ #[test]
+ #[cfg(all(feature = "alloc", feature = "hex"))]
+ fn block_check_witness_commitment_with_witness() {
+ let mut txin = crate::TxIn::EMPTY_COINBASE;
+ // Single witness item of 32 bytes.
+ let witness_bytes: [u8; 32] = [11u8; 32];
+ txin.witness.push(witness_bytes);
+
+ // pubkey bytes must match the magic bytes followed by the hash of the witness bytes.
+ let script_pubkey_bytes: [u8; 38] = hex_unstable::FromHex::from_hex("6a24aa21a9ed3cde9e0b9f4ad8f9d0fd66d6b9326cd68597c04fa22ab64b8e455f08d2e31ceb").unwrap();
+ let tx1 = Transaction {
+ version: crate::transaction::Version::ONE,
+ lock_time: crate::absolute::LockTime::ZERO,
+ inputs: vec![txin],
+ outputs: vec![crate::TxOut {
+ amount: units::Amount::MIN,
+ script_pubkey: crate::script::ScriptBuf::from_bytes(script_pubkey_bytes.to_vec())
+ }],
+ };
+
+ let tx2 = Transaction {
+ version: crate::transaction::Version::ONE,
+ lock_time: crate::absolute::LockTime::ZERO,
+ inputs: vec![crate::TxIn::EMPTY_COINBASE],
+ outputs: vec![crate::TxOut {
+ amount: units::Amount::MIN,
+ script_pubkey: crate::script::ScriptBuf::new()
+ }],
+ };
+
+ let block = Block::new_unchecked(dummy_header(), vec![tx1, tx2]);
+ let result = block.check_witness_commitment();
+
+ let exp_bytes: [u8; 32] = hex_unstable::FromHex::from_hex("fb848679079938b249a12f14b72d56aeb116df79254e17cdf72b46523bcb49db").unwrap();
+ let expected = WitnessMerkleNode::from_byte_array(exp_bytes);
+ assert_eq!(result, (true, Some(expected)));
+ }
+
+ #[test]
+ #[cfg(all(feature = "alloc", feature = "hex"))]
+ fn block_check_witness_commitment_invalid_witness() {
+ let mut txin = crate::TxIn::EMPTY_COINBASE;
+ let witness_bytes: [u8; 32] = [11u8; 32];
+ // First witness item is 32 bytes, but there are two witness elements.
+ txin.witness.push(witness_bytes);
+ txin.witness.push([12u8]);
+
+ let script_pubkey_bytes: [u8; 38] = hex_unstable::FromHex::from_hex("6a24aa21a9ed3cde9e0b9f4ad8f9d0fd66d6b9326cd68597c04fa22ab64b8e455f08d2e31ceb").unwrap();
+ let tx1 = Transaction {
+ version: crate::transaction::Version::ONE,
+ lock_time: crate::absolute::LockTime::ZERO,
+ inputs: vec![txin],
+ outputs: vec![crate::TxOut {
+ amount: units::Amount::MIN,
+ script_pubkey: crate::script::ScriptBuf::from_bytes(script_pubkey_bytes.to_vec())
+ }],
+ };
+
+ let tx2 = Transaction {
+ version: crate::transaction::Version::ONE,
+ lock_time: crate::absolute::LockTime::ZERO,
+ inputs: vec![crate::TxIn::EMPTY_COINBASE],
+ outputs: vec![crate::TxOut {
+ amount: units::Amount::MIN,
+ script_pubkey: crate::script::ScriptBuf::new()
+ }],
+ };
+
+ let block = Block::new_unchecked(dummy_header(), vec![tx1, tx2]);
+ let result = block.check_witness_commitment();
+ assert_eq!(result, (false, None));
+ }
}
Why this scored 17/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.