btcutil: reject trailing data in byte constructors
What changed, and why it matters
This commit tightens two helper functions that create Bitcoin block and transaction objects from raw bytes. Previously, extra bytes after a valid block or transaction were silently ignored. Now the functions return an error if any unexpected trailing data remains. This is a defensive correctness fix that can prevent subtle bugs or attacks where extra data is smuggled alongside a valid block or transaction.
Treat as a low-to-moderate hardening fix. Review downstream callers that previously relied on silent truncation of trailing bytes, as they may now receive errors. No immediate emergency response is indicated, but include in the next maintenance release and consider whether a CVE or advisory is warranted if a concrete exploit scenario is identified.
Security signals we found
Strict input validation: rejection of trailing bytes after deserialization
Potential malleability reduction: prevents extra payload from being accepted as part of a block/tx wrapper
Defensive hardening of public API constructors used by downstream consumers
Evidence from the diff
The patch adds trailing-byte checks to btcutil.NewBlockFromBytes and btcutil.NewTxFromBytes. Both functions now verify, after successful deserialization via their respective Reader-based constructors, that the bytes.Reader has been fully consumed (br.Len() == 0). If not, they return a descriptive error. NewTxFromReader and NewBlockFromReader are left unchanged, preserving support for streaming contexts where trailing data may be intentional. The change is minimal and does not alter wire parsing itself.
Changed components
btcutil/block.go: NewBlockFromBytesbtcutil/tx.go: NewTxFromBytesInspect captured patch +14 / −1
diff --git a/btcutil/block.go b/btcutil/block.go
index da7a47e..4b36dc7 100644
--- a/btcutil/block.go
+++ b/btcutil/block.go
@@ -253,6 +253,9 @@ func NewBlockFromBytes(serializedBlock []byte) (*Block, error) {
if err != nil {
return nil, err
}
+ if br.Len() > 0 {
+ return nil, fmt.Errorf("block has %d trailing bytes", br.Len())
+ }
b.serializedBlock = serializedBlock
// This initializes []btcutil.Tx to have the serialized raw
diff --git a/btcutil/tx.go b/btcutil/tx.go
index c66f81b..f47a82e 100644
--- a/btcutil/tx.go
+++ b/btcutil/tx.go
@@ -6,6 +6,7 @@ package btcutil
import (
"bytes"
+ "fmt"
"io"
"github.com/btcsuite/btcd/chainhash/v2"
@@ -173,7 +174,16 @@ func (t *Tx) setBytes(bytes []byte) {
// serialized bytes. See Tx.
func NewTxFromBytes(serializedTx []byte) (*Tx, error) {
br := bytes.NewReader(serializedTx)
- return NewTxFromReader(br)
+ tx, err := NewTxFromReader(br)
+ if err != nil {
+ return nil, err
+ }
+ if br.Len() > 0 {
+ return nil, fmt.Errorf("transaction has %d trailing bytes",
+ br.Len())
+ }
+
+ return tx, nil
}
// NewTxFromReader returns a new instance of a bitcoin transaction given a
Why this scored 49/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.