mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
772 lines
22 KiB
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, ¶ms, 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[:], ¶ms,
|
|
)
|
|
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[:], ¶ms,
|
|
)
|
|
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, ¶ms, 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, ¶ms, 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, ¶ms, 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
|
|
}
|