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
}
