lnd/lnwallet/chanfunding/psbt_assembler_test.go

772 lines
22 KiB
Go

package chanfunding
import (
"bytes"
"crypto/sha256"
"fmt"
"reflect"
"strings"
"sync"
"testing"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/stretchr/testify/require"
)
var (
localPrivkey = []byte{1, 2, 3, 4, 5, 6}
remotePrivkey = []byte{6, 5, 4, 3, 2, 1}
chanCapacity btcutil.Amount = 644000
params = chaincfg.RegressionNetParams
defaultTimeout = 50 * time.Millisecond
)
// TestPsbtIntent tests the basic happy path of the PSBT assembler and intent.
func TestPsbtIntent(t *testing.T) {
t.Parallel()
// Create a simple assembler and ask it to provision a channel to get
// the funding intent.
a := NewPsbtAssembler(chanCapacity, nil, &params, true)
intent, err := a.ProvisionChannel(&Request{LocalAmt: chanCapacity})
require.NoError(t, err, "error provisioning channel")
psbtIntent, ok := intent.(*PsbtIntent)
if !ok {
t.Fatalf("intent was not a PsbtIntent")
}
if psbtIntent.State != PsbtShimRegistered {
t.Fatalf("unexpected state. got %d wanted %d", psbtIntent.State,
PsbtShimRegistered)
}
// The first step with the intent is that the funding manager starts
// negotiating with the remote peer and they accept. By accepting, they
// send over their multisig key that's going to be used for the funding
// output. With that known, we can start crafting a PSBT.
_, localPubkey := btcec.PrivKeyFromBytes(localPrivkey)
_, remotePubkey := btcec.PrivKeyFromBytes(remotePrivkey)
psbtIntent.BindKeys(
&keychain.KeyDescriptor{PubKey: localPubkey}, remotePubkey,
)
if psbtIntent.State != PsbtOutputKnown {
t.Fatalf("unexpected state. got %d wanted %d", psbtIntent.State,
PsbtOutputKnown)
}
// Make sure the output script address is correct.
script, _, err := input.GenFundingPkScript(
localPubkey.SerializeCompressed(),
remotePubkey.SerializeCompressed(), int64(chanCapacity),
)
require.NoError(t, err, "error calculating script")
witnessScriptHash := sha256.Sum256(script)
addr, err := btcutil.NewAddressWitnessScriptHash(
witnessScriptHash[:], &params,
)
require.NoError(t, err, "unable to encode address")
fundingAddr, amt, pendingPsbt, err := psbtIntent.FundingParams()
require.NoError(t, err, "unable to get funding params")
if addr.EncodeAddress() != fundingAddr.EncodeAddress() {
t.Fatalf("unexpected address. got %s wanted %s", fundingAddr,
addr)
}
if amt != int64(chanCapacity) {
t.Fatalf("unexpected amount. got %d wanted %d", amt,
chanCapacity)
}
// Parse and check the returned PSBT packet.
if pendingPsbt == nil {
t.Fatalf("expected pending PSBT to be returned")
}
if len(pendingPsbt.UnsignedTx.TxOut) != 1 {
t.Fatalf("unexpected number of outputs. got %d wanted %d",
len(pendingPsbt.UnsignedTx.TxOut), 1)
}
txOut := pendingPsbt.UnsignedTx.TxOut[0]
if !bytes.Equal(txOut.PkScript[2:], witnessScriptHash[:]) {
t.Fatalf("unexpected PK script in output. got %x wanted %x",
txOut.PkScript[2:], witnessScriptHash)
}
if txOut.Value != int64(chanCapacity) {
t.Fatalf("unexpected value in output. got %d wanted %d",
txOut.Value, chanCapacity)
}
// Add an input to the pending TX to simulate it being funded.
pendingPsbt.UnsignedTx.TxIn = []*wire.TxIn{
{PreviousOutPoint: wire.OutPoint{Index: 0}},
}
pendingPsbt.Inputs = []psbt.PInput{
{WitnessUtxo: &wire.TxOut{Value: int64(chanCapacity + 1)}},
}
// Verify the dummy PSBT with the intent.
err = psbtIntent.Verify(pendingPsbt, false)
require.NoError(t, err, "error verifying pending PSBT")
if psbtIntent.State != PsbtVerified {
t.Fatalf("unexpected state. got %d wanted %d", psbtIntent.State,
PsbtVerified)
}
// Add some fake witness data to the transaction so it thinks it's
// signed.
pendingPsbt.Inputs[0].WitnessUtxo = &wire.TxOut{
Value: int64(chanCapacity) * 2,
PkScript: []byte{99, 99, 99},
}
pendingPsbt.Inputs[0].FinalScriptSig = []byte{88, 88, 88}
pendingPsbt.Inputs[0].FinalScriptWitness = []byte{2, 0, 0}
// If we call Finalize, the intent will signal to the funding manager
// that it can continue with the funding flow. We want to make sure
// the signal arrives.
var wg sync.WaitGroup
errChan := make(chan error, 1)
wg.Add(1)
go func() {
defer wg.Done()
select {
case err := <-psbtIntent.PsbtReady:
errChan <- err
case <-time.After(defaultTimeout):
errChan <- fmt.Errorf("timed out")
}
}()
err = psbtIntent.Finalize(pendingPsbt)
require.NoError(t, err, "error finalizing pending PSBT")
wg.Wait()
// We should have a nil error in our channel now.
err = <-errChan
require.NoError(t, err, "unexpected error after finalize")
if psbtIntent.State != PsbtFinalized {
t.Fatalf("unexpected state. got %d wanted %d", psbtIntent.State,
PsbtFinalized)
}
// Make sure the funding transaction can be compiled.
_, err = psbtIntent.CompileFundingTx()
require.NoError(t, err, "error compiling funding TX from PSBT")
if psbtIntent.State != PsbtFundingTxCompiled {
t.Fatalf("unexpected state. got %d wanted %d", psbtIntent.State,
PsbtFundingTxCompiled)
}
}
// TestPsbtIntentBasePsbt tests that a channel funding output can be appended to
// a given base PSBT in the funding flow.
func TestPsbtIntentBasePsbt(t *testing.T) {
t.Parallel()
// First create a dummy PSBT with a single output.
pendingPsbt, err := psbt.New(
[]*wire.OutPoint{{}}, []*wire.TxOut{
{Value: 999, PkScript: []byte{99, 88, 77}},
}, 2, 0, []uint32{0},
)
if err != nil {
t.Fatalf("unable to create dummy PSBT")
}
// Generate the funding multisig keys and the address so we can compare
// it to the output of the intent.
_, localPubkey := btcec.PrivKeyFromBytes(localPrivkey)
_, remotePubkey := btcec.PrivKeyFromBytes(remotePrivkey)
// Make sure the output script address is correct.
script, _, err := input.GenFundingPkScript(
localPubkey.SerializeCompressed(),
remotePubkey.SerializeCompressed(), int64(chanCapacity),
)
require.NoError(t, err, "error calculating script")
witnessScriptHash := sha256.Sum256(script)
addr, err := btcutil.NewAddressWitnessScriptHash(
witnessScriptHash[:], &params,
)
require.NoError(t, err, "unable to encode address")
// Now as the next step, create a new assembler/intent pair with a base
// PSBT to see that we can add an additional output to it.
a := NewPsbtAssembler(chanCapacity, pendingPsbt, &params, true)
intent, err := a.ProvisionChannel(&Request{LocalAmt: chanCapacity})
require.NoError(t, err, "error provisioning channel")
psbtIntent, ok := intent.(*PsbtIntent)
if !ok {
t.Fatalf("intent was not a PsbtIntent")
}
psbtIntent.BindKeys(
&keychain.KeyDescriptor{PubKey: localPubkey}, remotePubkey,
)
newAddr, amt, twoOutPsbt, err := psbtIntent.FundingParams()
require.NoError(t, err, "unable to get funding params")
if addr.EncodeAddress() != newAddr.EncodeAddress() {
t.Fatalf("unexpected address. got %s wanted %s", newAddr,
addr)
}
if amt != int64(chanCapacity) {
t.Fatalf("unexpected amount. got %d wanted %d", amt,
chanCapacity)
}
if len(twoOutPsbt.UnsignedTx.TxOut) != 2 {
t.Fatalf("unexpected number of outputs. got %d wanted %d",
len(twoOutPsbt.UnsignedTx.TxOut), 2)
}
if len(twoOutPsbt.UnsignedTx.TxIn) != 1 {
t.Fatalf("unexpected number of inputs. got %d wanted %d",
len(twoOutPsbt.UnsignedTx.TxIn), 1)
}
txOld := pendingPsbt.UnsignedTx
txNew := twoOutPsbt.UnsignedTx
prevoutEqual := reflect.DeepEqual(
txOld.TxIn[0].PreviousOutPoint, txNew.TxIn[0].PreviousOutPoint,
)
if !prevoutEqual {
t.Fatalf("inputs changed. got %s wanted %s",
spew.Sdump(txOld.TxIn[0].PreviousOutPoint),
spew.Sdump(txNew.TxIn[0].PreviousOutPoint))
}
if !reflect.DeepEqual(txOld.TxOut[0], txNew.TxOut[0]) {
t.Fatalf("existing output changed. got %v wanted %v",
txOld.TxOut[0], txNew.TxOut[0])
}
}
// TestPsbtVerify tests the PSBT verification process more deeply than just
// the happy path.
func TestPsbtVerify(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
expectedErr string
shouldPublish bool
doVerify func(int64, *psbt.Packet, *PsbtIntent) error
}{
{
name: "nil packet",
expectedErr: "PSBT is nil",
shouldPublish: true,
doVerify: func(amt int64, p *psbt.Packet,
i *PsbtIntent) error {
return i.Verify(nil, false)
},
},
{
name: "wrong state",
shouldPublish: true,
expectedErr: "invalid state. got user_canceled " +
"expected output_known",
doVerify: func(amt int64, p *psbt.Packet,
i *PsbtIntent) error {
i.State = PsbtInitiatorCanceled
return i.Verify(p, false)
},
},
{
name: "output not found, value wrong",
shouldPublish: true,
expectedErr: "funding output not found in PSBT",
doVerify: func(amt int64, p *psbt.Packet,
i *PsbtIntent) error {
p.UnsignedTx.TxOut[0].Value = 123
return i.Verify(p, false)
},
},
{
name: "output not found, pk script wrong",
shouldPublish: true,
expectedErr: "funding output not found in PSBT",
doVerify: func(amt int64, p *psbt.Packet,
i *PsbtIntent) error {
p.UnsignedTx.TxOut[0].PkScript = []byte{1, 2, 3}
return i.Verify(p, false)
},
},
{
name: "no inputs",
shouldPublish: true,
expectedErr: "PSBT has no inputs",
doVerify: func(amt int64, p *psbt.Packet,
i *PsbtIntent) error {
return i.Verify(p, false)
},
},
{
name: "input(s) too small",
shouldPublish: true,
expectedErr: "input amount sum must be larger than " +
"output amount sum",
doVerify: func(amt int64, p *psbt.Packet,
i *PsbtIntent) error {
p.UnsignedTx.TxIn = []*wire.TxIn{{}}
p.Inputs = []psbt.PInput{{
WitnessUtxo: &wire.TxOut{
Value: int64(chanCapacity),
},
}}
return i.Verify(p, false)
},
},
{
name: "missing witness-utxo field",
shouldPublish: true,
expectedErr: "cannot use TX for channel funding, not " +
"all inputs are SegWit spends, risk of " +
"malleability: input 1 is non-SegWit spend " +
"or missing redeem script",
doVerify: func(amt int64, p *psbt.Packet,
i *PsbtIntent) error {
txOut := &wire.TxOut{
Value: int64(chanCapacity/2) + 1,
}
p.UnsignedTx.TxIn = []*wire.TxIn{
{},
{
PreviousOutPoint: wire.OutPoint{
Index: 0,
},
},
}
p.Inputs = []psbt.PInput{
{
WitnessUtxo: txOut,
},
{
NonWitnessUtxo: &wire.MsgTx{
TxOut: []*wire.TxOut{
txOut,
},
},
},
}
return i.Verify(p, false)
},
},
{
name: "skip verify",
shouldPublish: false,
expectedErr: "",
doVerify: func(amt int64, p *psbt.Packet,
i *PsbtIntent) error {
txOut := &wire.TxOut{
Value: int64(chanCapacity/2) + 1,
}
p.UnsignedTx.TxIn = []*wire.TxIn{
{},
{
PreviousOutPoint: wire.OutPoint{
Index: 0,
},
},
}
p.Inputs = []psbt.PInput{
{
WitnessUtxo: txOut,
},
{
WitnessUtxo: txOut,
},
}
if err := i.Verify(p, true); err != nil {
return err
}
if i.FinalTX != p.UnsignedTx {
return fmt.Errorf("expected final TX " +
"to be set")
}
if i.State != PsbtFinalized {
return fmt.Errorf("expected state to " +
"be finalized")
}
select {
case <-i.PsbtReady:
case <-time.After(50 * time.Millisecond):
return fmt.Errorf("expected PSBT " +
"ready to be signaled")
}
return nil
},
},
{
name: "input correct",
shouldPublish: true,
expectedErr: "",
doVerify: func(amt int64, p *psbt.Packet,
i *PsbtIntent) error {
txOut := &wire.TxOut{
Value: int64(chanCapacity/2) + 1,
}
p.UnsignedTx.TxIn = []*wire.TxIn{
{},
{
PreviousOutPoint: wire.OutPoint{
Index: 0,
},
},
}
p.Inputs = []psbt.PInput{
{
WitnessUtxo: txOut,
},
{
WitnessUtxo: txOut,
}}
return i.Verify(p, false)
},
},
}
// Create a simple assembler and ask it to provision a channel to get
// the funding intent.
a := NewPsbtAssembler(chanCapacity, nil, &params, true)
intent, err := a.ProvisionChannel(&Request{LocalAmt: chanCapacity})
require.NoError(t, err, "error provisioning channel")
psbtIntent := intent.(*PsbtIntent)
// Bind our test keys to get the funding parameters.
_, localPubkey := btcec.PrivKeyFromBytes(localPrivkey)
_, remotePubkey := btcec.PrivKeyFromBytes(remotePrivkey)
psbtIntent.BindKeys(
&keychain.KeyDescriptor{PubKey: localPubkey}, remotePubkey,
)
// Loop through all our test cases.
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
// Reset the state from a previous test and create a new
// pending PSBT that we can manipulate.
psbtIntent.shouldPublish = tc.shouldPublish
psbtIntent.State = PsbtOutputKnown
_, amt, pendingPsbt, err := psbtIntent.FundingParams()
if err != nil {
t.Fatalf("unable to get funding params: %v", err)
}
err = tc.doVerify(amt, pendingPsbt, psbtIntent)
if err != nil && tc.expectedErr == "" {
t.Fatalf("unexpected error, got '%v' wanted "+
"'%v'", err, tc.expectedErr)
}
if err != nil && err.Error() != tc.expectedErr {
t.Fatalf("unexpected error, got '%v' wanted "+
"'%v'", err, tc.expectedErr)
}
})
}
}
// TestPsbtFinalize tests the PSBT finalization process more deeply than just
// the happy path.
func TestPsbtFinalize(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
expectedErr string
doFinalize func(int64, *psbt.Packet, *PsbtIntent) error
}{
{
name: "nil packet",
expectedErr: "PSBT is nil",
doFinalize: func(amt int64, p *psbt.Packet,
i *PsbtIntent) error {
return i.Finalize(nil)
},
},
{
name: "wrong state",
expectedErr: "invalid state. got user_canceled " +
"expected verified",
doFinalize: func(amt int64, p *psbt.Packet,
i *PsbtIntent) error {
i.State = PsbtInitiatorCanceled
return i.Finalize(p)
},
},
{
name: "not verified first",
expectedErr: "PSBT was not verified first",
doFinalize: func(amt int64, p *psbt.Packet,
i *PsbtIntent) error {
i.State = PsbtVerified
i.PendingPsbt = nil
return i.Finalize(p)
},
},
{
name: "output value changed",
expectedErr: "outputs differ from verified PSBT: " +
"output 0 is different",
doFinalize: func(amt int64, p *psbt.Packet,
i *PsbtIntent) error {
p.UnsignedTx.TxOut[0].Value = 123
return i.Finalize(p)
},
},
{
name: "output pk script changed",
expectedErr: "outputs differ from verified PSBT: " +
"output 0 is different",
doFinalize: func(amt int64, p *psbt.Packet,
i *PsbtIntent) error {
p.UnsignedTx.TxOut[0].PkScript = []byte{3, 2, 1}
return i.Finalize(p)
},
},
{
name: "input previous outpoint index changed",
expectedErr: "inputs differ from verified PSBT: " +
"previous outpoint of input 0 is different",
doFinalize: func(amt int64, p *psbt.Packet,
i *PsbtIntent) error {
p.UnsignedTx.TxIn[0].PreviousOutPoint.Index = 0
return i.Finalize(p)
},
},
{
name: "input previous outpoint hash changed",
expectedErr: "inputs differ from verified PSBT: " +
"previous outpoint of input 0 is different",
doFinalize: func(amt int64, p *psbt.Packet,
i *PsbtIntent) error {
prevout := &p.UnsignedTx.TxIn[0].PreviousOutPoint
prevout.Hash = chainhash.Hash{77, 88, 99, 11}
return i.Finalize(p)
},
},
{
name: "raw tx - nil transaction",
expectedErr: "raw transaction is nil",
doFinalize: func(amt int64, p *psbt.Packet,
i *PsbtIntent) error {
return i.FinalizeRawTX(nil)
},
},
{
name: "raw tx - no witness data in raw tx",
expectedErr: "inputs not signed: input 0 has no " +
"signature data attached",
doFinalize: func(amt int64, p *psbt.Packet,
i *PsbtIntent) error {
rawTx, err := psbt.Extract(p)
require.NoError(t, err)
rawTx.TxIn[0].Witness = nil
return i.FinalizeRawTX(rawTx)
},
},
{
name: "happy path",
expectedErr: "",
doFinalize: func(amt int64, p *psbt.Packet,
i *PsbtIntent) error {
err := i.Finalize(p)
require.NoError(t, err)
require.Equal(t, PsbtFinalized, i.State)
require.NotNil(t, i.FinalTX)
return nil
},
},
}
// Create a simple assembler and ask it to provision a channel to get
// the funding intent.
a := NewPsbtAssembler(chanCapacity, nil, &params, true)
intent, err := a.ProvisionChannel(&Request{LocalAmt: chanCapacity})
require.NoError(t, err, "error provisioning channel")
psbtIntent := intent.(*PsbtIntent)
// Bind our test keys to get the funding parameters.
_, localPubkey := btcec.PrivKeyFromBytes(localPrivkey)
_, remotePubkey := btcec.PrivKeyFromBytes(remotePrivkey)
psbtIntent.BindKeys(
&keychain.KeyDescriptor{PubKey: localPubkey}, remotePubkey,
)
// Loop through all our test cases.
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
// Reset the state from a previous test and create a new
// pending PSBT that we can manipulate.
psbtIntent.State = PsbtOutputKnown
_, amt, pendingPsbt, err := psbtIntent.FundingParams()
if err != nil {
t.Fatalf("unable to get funding params: %v", err)
}
// We need to have a simulated transaction here that is
// fully funded and signed.
pendingPsbt.UnsignedTx.TxIn = []*wire.TxIn{{
PreviousOutPoint: wire.OutPoint{
Index: 1,
Hash: chainhash.Hash{1, 2, 3},
},
}}
pendingPsbt.Inputs = []psbt.PInput{{
WitnessUtxo: &wire.TxOut{
Value: int64(chanCapacity) + 1,
PkScript: []byte{1, 2, 3},
},
FinalScriptWitness: []byte{0x01, 0x00},
}}
err = psbtIntent.Verify(pendingPsbt, false)
if err != nil {
t.Fatalf("error verifying PSBT: %v", err)
}
// Deep clone the PSBT so we don't modify the pending
// one that was registered during Verify.
pendingPsbt = clonePsbt(t, pendingPsbt)
err = tc.doFinalize(amt, pendingPsbt, psbtIntent)
if (err == nil && tc.expectedErr != "") ||
(err != nil && err.Error() != tc.expectedErr) {
t.Fatalf("unexpected error, got '%v' wanted "+
"'%v'", err, tc.expectedErr)
}
})
}
}
func TestVerifyAllInputsSegWit(t *testing.T) {
testCases := []struct {
name string
packet string
shouldFail bool
}{{
// Electrum takes the spec literally and does not include the
// WitnessUtxo field in the PSBT.
name: "Electrum np2wkh spend",
packet: "cHNidP8BAFMCAAAAAcptbRo6Ez2R8WclfL4IAW+XEWIl8sNOHnw" +
"eXfeU3dTwAQAAAAD9////Ab26GQAAAAAAF6kUf+wXsa7Z4ejieW" +
"CSdKaGFwJmfbyHQyMeAAABAOACAAAAAAEBiS1A2NE4KW00wLQ1B" +
"9R05jFMINs6egYFNL/5vLseeXMBAAAAAP7///8CNl1m1AEAAAAX" +
"qRR/7Bexrtnh6OJ5YJJ0poYXAmZ9vIdDuxkAAAAAABepFMD8Ngx" +
"k/Z7M5nJM2ltpszQZ4KGahwJHMEQCIAD4EIg16hjWEnEf2pilRF" +
"UmWSk5UsMD7tTrR2FbFCurAiAiNvqpFQSr/FCCXknOV1HzeDVxe" +
"B9gXeDZ62tmZv04/AEhAxGTJ9E/ImiOp2Ti3w/l/JFHa95RvXCB" +
"UXFKifC/7f9dQyMeAAEEFgAU9pqV6WX0D/HEMfdw9yzU30MmkV4" +
"iBgP4kZAAjhAKuixOLJczugvMmlS0xxPc2eD+4IJPHVOeRgxGbt" +
"z0AAAAAAAAAAAAAA==",
}, {
name: "Electrum p2wkh spend",
packet: "cHNidP8BAFMCAAAAASaMwGRzF7QWjHMX9WaGyUp7f3jznuNigHF" +
"4l57hW0CgAAAAAAD9////AU+6GQAAAAAAF6kUf+wXsa7Z4ejieW" +
"CSdKaGFwJmfbyHRSMeAAABANYCAAAAAAEBym1tGjoTPZHxZyV8v" +
"ggBb5cRYiXyw04efB5d95Td1PABAAAAFxYAFPaalell9A/xxDH3" +
"cPcs1N9DJpFe/f///wG+uhkAAAAAABYAFPaalell9A/xxDH3cPc" +
"s1N9DJpFeAkcwRAIgRwfN0qpcM5DHR+YrgXFxhEi6F0qTf4dmQc" +
"c1cu3TnnICIH5wc0kkMbHZ2mqZUFEITRUXExR7US3wohctQK1lf" +
"q/oASED+JGQAI4QCrosTiyXM7oLzJpUtMcT3Nng/uCCTx1TnkZF" +
"Ix4AIgYD+JGQAI4QCrosTiyXM7oLzJpUtMcT3Nng/uCCTx1TnkY" +
"MRm7c9AAAAAAAAAAAAAA=",
}, {
name: "Bitcoind p2wkh spend",
packet: "cHNidP8BAHICAAAAAeEw64k3k+YEk5EgNTytvkGlpiF6iMSsm0o" +
"dCSm03dt7AAAAAAD+////AsSvp8oAAAAAFgAUzWRW/Ccwf/Cosy" +
"dy0uYe5fa/Gb0A4fUFAAAAABepFMD8Ngxk/Z7M5nJM2ltpszQZ4" +
"KGahwAAAAAAAQBxAgAAAAEtMrUZuqjRoaSEMU9sc7pS2zKlS8X3" +
"/CUaENX1IHOICgEAAAAA/v///wLcm53QAAAAABYAFB1zJOaGOpk" +
"NHVkIguiZ2Zh46dlmAGXNHQAAAAAWABTbjTIbrBMD+sWxE6mahK" +
"tKGa//O1wAAAABAR/cm53QAAAAABYAFB1zJOaGOpkNHVkIguiZ2" +
"Zh46dlmIgYDjVRSrQ7d5Rld1nDBTY/uA7Xp+SKUfaFpB0SQiM8O" +
"3usQBp+HNQAAAIABAACADwAAgAAiAgJ9LOXb4MwHRoq31B9fQ2U" +
"0K+Uj8BBJzy+MWc0OOOVuQxAGn4c1AAAAgAEAAIARAACAAAA=",
}, {
name: "Bitcoind p2pkh spend",
packet: "cHNidP8BAHICAAAAAdp3QE/zv3Q7RhkAR0JyzBWLttUqRHJ" +
"pAYmHLIqJzGXOAQAAAAD/////AoDw+gIAAAAAF6kUwPw2DGT9ns" +
"zmckzaW2mzNBngoZqHUN/6AgAAAAAWABRLzlCRi8BozkTClLcIN" +
"tnz8zYTtAAAAAAAAQB0AgAAAAHhMOuJN5PmBJORIDU8rb5BpaYh" +
"eojErJtKHQkptN3bewAAAAAA/v///wKcr6fKAAAAABYAFOvePfh" +
"lztKeDuQRgcGsHLS5BPhTAOH1BQAAAAAZdqkUt9QziR33rxhwcg" +
"ipzwrDti93vTWIrAAAAAAiBgNKzHC4j7KaSKeH9RYx3Ur3dHL5w" +
"YRjXpOAi3SI12WNFxAGn4c1AAAAgAAAAIAGAACAAAAiAgK8Fq3O" +
"5nnASvhn9LJMIJOkGBMYQFd5DcbvOCbBCX/VBRAGn4c1AAAAgAE" +
"AAIAVAACAAA==",
shouldFail: true,
}, {
name: "Bitcoind p2sh multisig spend",
packet: "cHNidP8BAHICAAAAAQvO6z5f4wghsQ2c5+Zcw2qdZ4FOYkyWBFe" +
"U/jiIKcwdAAAAAAD/////AkTc+gIAAAAAFgAU2kWEYfMLfgwVQ0" +
"2wJwFNsOmorBWA8PoCAAAAABepFMD8Ngxk/Z7M5nJM2ltpszQZ4" +
"KGahwAAAAAAAQByAgAAAAHad0BP8790O0YZAEdCcswVi7bVKkRy" +
"aQGJhyyKicxlzgAAAAAA/v///wIA4fUFAAAAABepFJdSg2Xdeo3" +
"mYbTqbcZnZIH8oWbPh4TDscQAAAAAFgAUs5d3GhxrF5Zdi8fQHy" +
"A05BSZr4t3AAAAAQRHUSEDdy41G190ATg/VnhXHE4dufESLLl53" +
"RewoYB2ZYRJ/4AhA6MR4qMgHUkIyqhXW0jEECV8cHg/DCiuLgUk" +
"YvQeub1zUq4AAAA=",
shouldFail: true,
}}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
r := strings.NewReader(tc.packet)
packet, err := psbt.NewFromRawBytes(r, true)
require.NoError(t, err)
txIns := packet.UnsignedTx.TxIn
ins := packet.Inputs
err = verifyAllInputsSegWit(txIns, ins)
if tc.shouldFail {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
// clonePsbt creates a clone of a PSBT packet by serializing then de-serializing
// it.
func clonePsbt(t *testing.T, p *psbt.Packet) *psbt.Packet {
var buf bytes.Buffer
err := p.Serialize(&buf)
require.NoError(t, err, "error serializing PSBT")
newPacket, err := psbt.NewFromRawBytes(&buf, false)
require.NoError(t, err, "error unserializing PSBT")
return newPacket
}