What changed, and why it matters
This commit tightens how a Bitcoin-related library reads transaction outputs stored inside PSBT (Partially Signed Bitcoin Transaction) data. Previously, the code read the output value and script in a loose, hand-rolled way that ignored extra trailing bytes and misinterpreted the script length byte. The change now uses the project's standard parser and insists every byte is consumed. That removes a class of parsing inconsistencies that could, in theory, let a malformed PSBT slip past validation or be interpreted differently by different software.
Review whether the old readTxOut behavior could have caused PSBT validation to accept malformed witness UTXOs, and consider adding regression tests with trailing bytes, short buffers, and incorrect script-length prefixes. If this was reported by a security researcher, request a CVE and advisory from the project maintainers.
Security signals we found
Strict canonical parsing of serialized transaction outputs
Removal of hand-rolled length handling that ignored script length byte
Addition of full-consumption check on parsed witness UTXO data
Potential for consensus/validation disagreement between strict and loose parsers
Evidence from the diff
The patch replaces a custom readTxOut implementation in psbt/utils.go with a call to wire.ReadTxOut plus assertFullyConsumed. The old code used binary.LittleEndian.Uint64 on the first 8 bytes, skipped byte 8 (the compact-varint script length), and returned the remainder as the script, without checking that the length matched the actual data or that no bytes remained. The new code parses the serialized TxOut through the canonical wire decoder and rejects any input that is not fully consumed. This prevents truncated, overlong, or mislength-prefixed witness UTXO records from being accepted.
Changed components
btcd/psbt/utils.goPSBT witness UTXO parsingreadTxOut helperInspect captured patch +11 / −9
diff --git a/psbt/utils.go b/psbt/utils.go
index 0d99bfe..baf7558 100644
--- a/psbt/utils.go
+++ b/psbt/utils.go
@@ -6,7 +6,6 @@ package psbt
import (
"bytes"
- "encoding/binary"
"errors"
"fmt"
"io"
@@ -303,17 +302,20 @@ func assertFullyConsumed(r io.Reader) error {
}
}
-// readTxOut is a limited version of wire.ReadTxOut, because the latter is not
-// exported.
+// readTxOut parses a transaction output value and requires the full value to
+// be consumed.
func readTxOut(txout []byte) (*wire.TxOut, error) {
- if len(txout) < 10 {
- return nil, ErrInvalidPsbtFormat
- }
+ txOut := &wire.TxOut{}
+ reader := bytes.NewReader(txout)
- valueSer := binary.LittleEndian.Uint64(txout[:8])
- scriptPubKey := txout[9:]
+ if err := wire.ReadTxOut(reader, 0, 0, txOut); err != nil {
+ return nil, err
+ }
+ if err := assertFullyConsumed(reader); err != nil {
+ return nil, err
+ }
- return wire.NewTxOut(int64(valueSer), scriptPubKey), nil
+ return txOut, nil
}
// readTransaction parses a transaction value and requires the full value to be
Why this scored 46/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.