mirror of
https://github.com/btcsuite/btcd.git
synced 2026-08-13 12:32:51 +02:00
psbt: reject nil taproot pointer fields
In this commit, we extend taproot PSBT serialization and finalization to reject nil script spend signatures, leaf scripts, and BIP32 derivations before any sort or dereference. We return wrapped ErrInvalidPsbtFormat errors so callers can identify malformed packet state without parsing strings. We also make FindLeafScript reject nil inputs and leaf entries. This keeps both the direct finalizer and MaybeFinalize paths from panicking on malformed in-memory packets.
This commit is contained in:
parent
1a4dea6ab0
commit
1ead5ba38a
5 changed files with 171 additions and 16 deletions
|
|
@ -55,6 +55,10 @@ func isFinalizableWitnessInput(pInput *PInput) bool {
|
|||
// For each of the script spend signatures we need a
|
||||
// corresponding tap script leaf with the control block.
|
||||
for _, sig := range pInput.TaprootScriptSpendSig {
|
||||
if sig == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, err := FindLeafScript(pInput, sig.LeafHash)
|
||||
if err != nil {
|
||||
return false
|
||||
|
|
@ -516,6 +520,13 @@ func finalizeTaprootInput(p *Packet, inIndex int) error {
|
|||
pInput = &p.Inputs[inIndex]
|
||||
)
|
||||
|
||||
for idx, scriptSpendSig := range pInput.TaprootScriptSpendSig {
|
||||
if scriptSpendSig == nil {
|
||||
return fmt.Errorf("nil taproot script spend signature "+
|
||||
"at index %d: %w", idx, ErrInvalidPsbtFormat)
|
||||
}
|
||||
}
|
||||
|
||||
// What spend path did we take?
|
||||
switch {
|
||||
// Key spend path.
|
||||
|
|
@ -547,8 +558,8 @@ func finalizeTaprootInput(p *Packet, inIndex int) error {
|
|||
targetLeafHash := pInput.TaprootScriptSpendSig[0].LeafHash
|
||||
leafScript, err := FindLeafScript(pInput, targetLeafHash)
|
||||
if err != nil {
|
||||
return fmt.Errorf("control block for script spend " +
|
||||
"signature not found")
|
||||
return fmt.Errorf("control block for script spend "+
|
||||
"signature not found: %w", err)
|
||||
}
|
||||
|
||||
// The witness stack will contain all signatures, followed by
|
||||
|
|
|
|||
|
|
@ -495,6 +495,13 @@ func (pi *PInput) serialize(w io.Writer) error {
|
|||
}
|
||||
}
|
||||
|
||||
for idx, scriptSpend := range pi.TaprootScriptSpendSig {
|
||||
if scriptSpend == nil {
|
||||
return fmt.Errorf("nil taproot script spend "+
|
||||
"signature at index %d: %w", idx,
|
||||
ErrInvalidPsbtFormat)
|
||||
}
|
||||
}
|
||||
sort.Slice(pi.TaprootScriptSpendSig, func(i, j int) bool {
|
||||
return pi.TaprootScriptSpendSig[i].SortBefore(
|
||||
pi.TaprootScriptSpendSig[j],
|
||||
|
|
@ -518,7 +525,8 @@ func (pi *PInput) serialize(w io.Writer) error {
|
|||
|
||||
for idx, leafScript := range pi.TaprootLeafScript {
|
||||
if leafScript == nil {
|
||||
return fmt.Errorf("nil taproot leaf script at index %d", idx)
|
||||
return fmt.Errorf("nil taproot leaf script at "+
|
||||
"index %d: %w", idx, ErrInvalidPsbtFormat)
|
||||
}
|
||||
}
|
||||
sort.Slice(pi.TaprootLeafScript, func(i, j int) bool {
|
||||
|
|
@ -538,6 +546,12 @@ func (pi *PInput) serialize(w io.Writer) error {
|
|||
}
|
||||
}
|
||||
|
||||
for idx, derivation := range pi.TaprootBip32Derivation {
|
||||
if derivation == nil {
|
||||
return fmt.Errorf("nil taproot BIP32 derivation at "+
|
||||
"index %d: %w", idx, ErrInvalidPsbtFormat)
|
||||
}
|
||||
}
|
||||
sort.Slice(pi.TaprootBip32Derivation, func(i, j int) bool {
|
||||
return pi.TaprootBip32Derivation[i].SortBefore(
|
||||
pi.TaprootBip32Derivation[j],
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package psbt
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
|
|
@ -225,6 +226,12 @@ func (po *POutput) serialize(w io.Writer) error {
|
|||
}
|
||||
}
|
||||
|
||||
for idx, derivation := range po.TaprootBip32Derivation {
|
||||
if derivation == nil {
|
||||
return fmt.Errorf("nil taproot BIP32 derivation at "+
|
||||
"index %d: %w", idx, ErrInvalidPsbtFormat)
|
||||
}
|
||||
}
|
||||
sort.Slice(po.TaprootBip32Derivation, func(i, j int) bool {
|
||||
return po.TaprootBip32Derivation[i].SortBefore(
|
||||
po.TaprootBip32Derivation[j],
|
||||
|
|
|
|||
|
|
@ -1338,24 +1338,138 @@ func TestFromUnsigned(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestB64EncodeRejectsNilTaprootLeafScript(t *testing.T) {
|
||||
tx := wire.NewMsgTx(2)
|
||||
tx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{},
|
||||
Index: 0,
|
||||
func TestB64EncodeRejectsNilTaprootFields(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
expectedErr string
|
||||
setNil func(*Packet)
|
||||
}{
|
||||
{
|
||||
name: "input script spend signature",
|
||||
expectedErr: "nil taproot script spend signature at index 0",
|
||||
setNil: func(packet *Packet) {
|
||||
packet.Inputs[0].TaprootScriptSpendSig =
|
||||
[]*TaprootScriptSpendSig{nil}
|
||||
},
|
||||
},
|
||||
})
|
||||
tx.AddTxOut(wire.NewTxOut(1, []byte{txscript.OP_TRUE}))
|
||||
{
|
||||
name: "input leaf script",
|
||||
expectedErr: "nil taproot leaf script at index 0",
|
||||
setNil: func(packet *Packet) {
|
||||
packet.Inputs[0].TaprootLeafScript =
|
||||
[]*TaprootTapLeafScript{nil}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "input BIP32 derivation",
|
||||
expectedErr: "nil taproot BIP32 derivation at index 0",
|
||||
setNil: func(packet *Packet) {
|
||||
packet.Inputs[0].TaprootBip32Derivation =
|
||||
[]*TaprootBip32Derivation{nil}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "output BIP32 derivation",
|
||||
expectedErr: "nil taproot BIP32 derivation at index 0",
|
||||
setNil: func(packet *Packet) {
|
||||
packet.Outputs[0].TaprootBip32Derivation =
|
||||
[]*TaprootBip32Derivation{nil}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
packet, err := NewFromUnsignedTx(tx)
|
||||
require.NoError(t, err)
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
tx := wire.NewMsgTx(2)
|
||||
tx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: wire.OutPoint{},
|
||||
})
|
||||
tx.AddTxOut(wire.NewTxOut(
|
||||
1, []byte{txscript.OP_TRUE},
|
||||
))
|
||||
|
||||
packet.Inputs[0].TaprootLeafScript = []*TaprootTapLeafScript{nil}
|
||||
_, err = packet.B64Encode()
|
||||
packet, err := NewFromUnsignedTx(tx)
|
||||
require.NoError(t, err)
|
||||
testCase.setNil(packet)
|
||||
|
||||
_, err = packet.B64Encode()
|
||||
require.ErrorIs(t, err, ErrInvalidPsbtFormat)
|
||||
require.ErrorContains(t, err, testCase.expectedErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindLeafScriptRejectsNilLeaf(t *testing.T) {
|
||||
_, err := FindLeafScript(nil, make([]byte, chainhash.HashSize))
|
||||
require.ErrorIs(t, err, ErrInvalidPsbtFormat)
|
||||
require.ErrorContains(t, err, "nil PSBT input")
|
||||
|
||||
input := &PInput{
|
||||
TaprootLeafScript: []*TaprootTapLeafScript{nil},
|
||||
}
|
||||
|
||||
_, err = FindLeafScript(input, make([]byte, chainhash.HashSize))
|
||||
require.ErrorIs(t, err, ErrInvalidPsbtFormat)
|
||||
require.ErrorContains(t, err, "nil taproot leaf script at index 0")
|
||||
}
|
||||
|
||||
func TestFinalizeRejectsNilTaprootFields(t *testing.T) {
|
||||
newPacket := func() *Packet {
|
||||
tx := wire.NewMsgTx(2)
|
||||
tx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: wire.OutPoint{},
|
||||
})
|
||||
tx.AddTxOut(wire.NewTxOut(
|
||||
1, []byte{txscript.OP_TRUE},
|
||||
))
|
||||
|
||||
packet, err := NewFromUnsignedTx(tx)
|
||||
require.NoError(t, err)
|
||||
packet.Inputs[0].WitnessUtxo = wire.NewTxOut(
|
||||
1, append(
|
||||
[]byte{txscript.OP_1, txscript.OP_DATA_32},
|
||||
make([]byte, 32)...,
|
||||
),
|
||||
)
|
||||
|
||||
return packet
|
||||
}
|
||||
|
||||
t.Run("script spend signature", func(t *testing.T) {
|
||||
packet := newPacket()
|
||||
packet.Inputs[0].TaprootScriptSpendSig =
|
||||
[]*TaprootScriptSpendSig{nil}
|
||||
|
||||
finalized, err := MaybeFinalize(packet, 0)
|
||||
require.False(t, finalized)
|
||||
require.ErrorIs(t, err, ErrNotFinalizable)
|
||||
|
||||
err = Finalize(packet, 0)
|
||||
require.ErrorIs(t, err, ErrInvalidPsbtFormat)
|
||||
require.ErrorContains(
|
||||
t, err, "nil taproot script spend signature at index 0",
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("leaf script", func(t *testing.T) {
|
||||
packet := newPacket()
|
||||
packet.Inputs[0].TaprootScriptSpendSig =
|
||||
[]*TaprootScriptSpendSig{{
|
||||
LeafHash: make([]byte, chainhash.HashSize),
|
||||
}}
|
||||
packet.Inputs[0].TaprootLeafScript =
|
||||
[]*TaprootTapLeafScript{nil}
|
||||
|
||||
finalized, err := MaybeFinalize(packet, 0)
|
||||
require.False(t, finalized)
|
||||
require.ErrorIs(t, err, ErrNotFinalizable)
|
||||
|
||||
err = Finalize(packet, 0)
|
||||
require.ErrorIs(t, err, ErrInvalidPsbtFormat)
|
||||
require.ErrorContains(t, err, "nil taproot leaf script at index 0")
|
||||
})
|
||||
}
|
||||
|
||||
func TestNonWitnessToWitness(t *testing.T) {
|
||||
// We'll start with a PSBT produced by Core for which
|
||||
// the first input is signed and we'll provided the signatures for
|
||||
|
|
|
|||
|
|
@ -464,7 +464,16 @@ func NewFromSignedTx(tx *wire.MsgTx) (*Packet, [][]byte,
|
|||
func FindLeafScript(pInput *PInput,
|
||||
targetLeafHash []byte) (*TaprootTapLeafScript, error) {
|
||||
|
||||
for _, leaf := range pInput.TaprootLeafScript {
|
||||
if pInput == nil {
|
||||
return nil, fmt.Errorf("nil PSBT input: %w", ErrInvalidPsbtFormat)
|
||||
}
|
||||
|
||||
for idx, leaf := range pInput.TaprootLeafScript {
|
||||
if leaf == nil {
|
||||
return nil, fmt.Errorf("nil taproot leaf script at index "+
|
||||
"%d: %w", idx, ErrInvalidPsbtFormat)
|
||||
}
|
||||
|
||||
leafHash := txscript.TapLeaf{
|
||||
LeafVersion: leaf.LeafVersion,
|
||||
Script: leaf.Script,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue