What changed, and why it matters
This commit only adds new test code to check that the PSBT (Partially Signed Bitcoin Transaction) parser rejects malformed base64 input such as whitespace, bad padding, or extra bytes. It does not change the actual parser logic. The tests confirm existing strict behavior, so this is a defensive hardening test rather than a fix for a known active vulnerability.
No immediate action required. Treat as routine defensive test coverage. Review the corresponding NewFromRawBytes implementation to ensure it already rejects the tested malformed inputs, since the commit assumes but does not prove that behavior.
Security signals we found
strict base64 decoding validation
PSBT packet format hardening
rejection of non-canonical base64 input
test-only change
Evidence from the diff
The diff adds TestRejectsNonCanonicalBase64Packet in psbt/strict_tx_values_test.go. It base64-encodes a valid PSBT packet, then constructs several malformed variants (trailing LF/CRLF, LF/space/tab inserted between or inside base64 groups, padding inserted mid-string, and extra decoded bytes). Each variant is fed to NewFromRawBytes with strict mode enabled, and the test asserts ErrInvalidPsbtFormat is returned. No production code is modified.
Changed components
psbt/strict_tx_values_test.goInspect captured patch +56 / −0
diff --git a/psbt/strict_tx_values_test.go b/psbt/strict_tx_values_test.go
index 9c7d3ae..83ed6bd 100644
--- a/psbt/strict_tx_values_test.go
+++ b/psbt/strict_tx_values_test.go
@@ -2,6 +2,7 @@ package psbt
import (
"bytes"
+ "encoding/base64"
"testing"
"github.com/btcsuite/btcd/wire/v2"
@@ -171,6 +172,61 @@ func TestRejectsTrailingDataAfterPacket(t *testing.T) {
require.ErrorIs(t, err, ErrInvalidPsbtFormat)
}
+// TestRejectsNonCanonicalBase64Packet verifies that base64 PSBT input rejects
+// whitespace, bad padding, and extra decoded packet bytes.
+func TestRejectsNonCanonicalBase64Packet(t *testing.T) {
+ unsignedTx, prevTx := strictnessTxPair(t)
+ rawPacket := strictnessPSBT(
+ t,
+ serializeTxForStrictness(t, unsignedTx, true),
+ serializeTxForStrictness(t, prevTx, false),
+ )
+ encoded := base64.StdEncoding.EncodeToString(rawPacket)
+ insert := func(idx int, s string) string {
+ return encoded[:idx] + s + encoded[idx:]
+ }
+
+ testCases := []struct {
+ name string
+ encoded string
+ }{{
+ name: "trailing LF",
+ encoded: encoded + "\n",
+ }, {
+ name: "trailing CRLF",
+ encoded: encoded + "\r\n",
+ }, {
+ name: "LF between groups",
+ encoded: insert(4, "\n"),
+ }, {
+ name: "LF inside group",
+ encoded: insert(5, "\n"),
+ }, {
+ name: "space between groups",
+ encoded: insert(4, " "),
+ }, {
+ name: "tab inside group",
+ encoded: insert(5, "\t"),
+ }, {
+ name: "padding in middle",
+ encoded: insert(len(encoded)-4, "="),
+ }, {
+ name: "extra decoded bytes",
+ encoded: base64.StdEncoding.EncodeToString(
+ append(append([]byte{}, rawPacket...), 0x00),
+ ),
+ }}
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ _, err := NewFromRawBytes(
+ bytes.NewReader([]byte(tc.encoded)), true,
+ )
+ require.ErrorIs(t, err, ErrInvalidPsbtFormat)
+ })
+ }
+}
+
// TestParsesWitnessUtxoTxOutStrictly verifies that WitnessUtxo values are
// parsed as exact transaction outputs.
func TestParsesWitnessUtxoTxOutStrictly(t *testing.T) {
Why this scored 28/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.