primitives: fix witness commitment check (BIP141)
What changed, and why it matters
This commit fixes a bug in how the rust-bitcoin library validates witness commitments in Bitcoin blocks. Under BIP141 rules, if a block contains a witness commitment, the coinbase transaction must also contain exactly one 32-byte witness reserved value, even when no other transactions use SegWit. The old code skipped this requirement when no SegWit transactions were present, which could let an invalid block appear valid. The fix moves the 'no SegWit transactions' shortcut after the commitment/reserved-value checks, matching Bitcoin Core and btcd behavior.
Review any downstream code that relies on this validation for consensus-critical decisions, and upgrade to a version containing this fix. If the library is used in a full-node or block-relay context, treat this as a priority patch. No immediate user action is required for wallet-only usage.
Security signals we found
Consensus-adjacent validation bug in block witness commitment check
Bypass of required coinbase witness reserved value when no SegWit transactions present
Behavior now aligned with Bitcoin Core and btcd
Potential for accepting invalid blocks or rejecting valid ones depending on network usage
Evidence from the diff
The function check_witness_commitment in primitives/src/block.rs previously returned (true, None) early when no transaction inputs had a non-empty witness. This short-circuit bypassed the coinbase witness commitment and witness reserved value checks in blocks that contained a commitment but no SegWit-using transactions. The patch removes the early return, performs the coinbase commitment/reserved-value validation first, and only then applies the optional-commitment shortcut. It also adds a return (false, None) when a commitment exists but the reserved value is missing/invalid, ensuring the function fails closed.
Changed components
primitives/src/block.rsBlock::check_witness_commitmentBIP141 witness commitment validationInspect captured patch +7 / −5
diff --git a/primitives/src/block.rs b/primitives/src/block.rs
index d800e9c9..efe89d61 100644
--- a/primitives/src/block.rs
+++ b/primitives/src/block.rs
@@ -192,11 +192,6 @@ impl Block<Unchecked> {
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) {
@@ -211,9 +206,16 @@ impl Block<Unchecked> {
}
}
}
+
+ 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);
+ }
+
(false, None)
}
}
Why this scored 59/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.