itest: add Taproot PSBT signing tests
This commit is contained in:
2 files changed
+214
No files matched your search
@@ -6,13 +6,17 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
||||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/lightningnetwork/lnd/funding"
|
"github.com/lightningnetwork/lnd/funding"
|
||||||
|
"github.com/lightningnetwork/lnd/input"
|
||||||
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
||||||
@@ -635,6 +639,9 @@ func testPsbtChanFundingSingleStep(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
// testSignPsbt tests that the SignPsbt RPC works correctly.
|
// testSignPsbt tests that the SignPsbt RPC works correctly.
|
||||||
func testSignPsbt(net *lntest.NetworkHarness, t *harnessTest) {
|
func testSignPsbt(net *lntest.NetworkHarness, t *harnessTest) {
|
||||||
runSignPsbtSegWitV0P2WKH(t, net, net.Alice)
|
runSignPsbtSegWitV0P2WKH(t, net, net.Alice)
|
||||||
|
runSignPsbtSegWitV1KeySpendBip86(t, net, net.Alice)
|
||||||
|
runSignPsbtSegWitV1KeySpendRootHash(t, net, net.Alice)
|
||||||
|
runSignPsbtSegWitV1ScriptSpend(t, net, net.Alice)
|
||||||
}
|
}
|
||||||
|
|
||||||
// runSignPsbtSegWitV0P2WKH tests that the SignPsbt RPC works correctly for a
|
// runSignPsbtSegWitV0P2WKH tests that the SignPsbt RPC works correctly for a
|
||||||
@@ -727,6 +734,182 @@ func runSignPsbtSegWitV0P2WKH(t *harnessTest, net *lntest.NetworkHarness,
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runSignPsbtSegWitV1KeySpendBip86 tests that the SignPsbt RPC works correctly
|
||||||
|
// for a SegWit v1 p2tr key spend BIP-0086 input.
|
||||||
|
func runSignPsbtSegWitV1KeySpendBip86(t *harnessTest, net *lntest.NetworkHarness,
|
||||||
|
alice *lntest.HarnessNode) {
|
||||||
|
|
||||||
|
// Everything we do here should be done within a second or two, so we
|
||||||
|
// can just keep a single timeout context around for all calls.
|
||||||
|
ctxb := context.Background()
|
||||||
|
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Derive a key we can use for signing.
|
||||||
|
keyDesc, internalKey, fullDerivationPath := deriveInternalKey(
|
||||||
|
ctxt, t, alice,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Our taproot key is a BIP0086 key spend only construction that just
|
||||||
|
// commits to the internal key and no root hash.
|
||||||
|
taprootKey := txscript.ComputeTaprootKeyNoScript(internalKey)
|
||||||
|
tapScriptAddr, err := btcutil.NewAddressTaproot(
|
||||||
|
schnorr.SerializePubKey(taprootKey), harnessNetParams,
|
||||||
|
)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
p2trPkScript, err := txscript.PayToAddrScript(tapScriptAddr)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
// Send some funds to the output and then try to get a signature through
|
||||||
|
// the SignPsbt RPC to spend that output again.
|
||||||
|
assertPsbtSpend(
|
||||||
|
ctxt, t, net, alice, p2trPkScript,
|
||||||
|
func(packet *psbt.Packet) {
|
||||||
|
in := &packet.Inputs[0]
|
||||||
|
in.Bip32Derivation = []*psbt.Bip32Derivation{{
|
||||||
|
PubKey: keyDesc.RawKeyBytes,
|
||||||
|
Bip32Path: fullDerivationPath,
|
||||||
|
}}
|
||||||
|
in.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{
|
||||||
|
XOnlyPubKey: keyDesc.RawKeyBytes[1:],
|
||||||
|
Bip32Path: fullDerivationPath,
|
||||||
|
}}
|
||||||
|
in.SighashType = txscript.SigHashDefault
|
||||||
|
},
|
||||||
|
func(packet *psbt.Packet) {
|
||||||
|
require.Len(t.t, packet.Inputs, 1)
|
||||||
|
require.Len(
|
||||||
|
t.t, packet.Inputs[0].TaprootKeySpendSig, 64,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runSignPsbtSegWitV1KeySpendRootHash tests that the SignPsbt RPC works
|
||||||
|
// correctly for a SegWit v1 p2tr key spend that also commits to a script tree
|
||||||
|
// root hash.
|
||||||
|
func runSignPsbtSegWitV1KeySpendRootHash(t *harnessTest,
|
||||||
|
net *lntest.NetworkHarness, alice *lntest.HarnessNode) {
|
||||||
|
|
||||||
|
// Everything we do here should be done within a second or two, so we
|
||||||
|
// can just keep a single timeout context around for all calls.
|
||||||
|
ctxb := context.Background()
|
||||||
|
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Derive a key we can use for signing.
|
||||||
|
keyDesc, internalKey, fullDerivationPath := deriveInternalKey(
|
||||||
|
ctxt, t, alice,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Let's create a taproot script output now. This is a hash lock with a
|
||||||
|
// simple preimage of "foobar".
|
||||||
|
leaf1 := testScriptHashLock(t.t, []byte("foobar"))
|
||||||
|
|
||||||
|
rootHash := leaf1.TapHash()
|
||||||
|
taprootKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash[:])
|
||||||
|
tapScriptAddr, err := btcutil.NewAddressTaproot(
|
||||||
|
schnorr.SerializePubKey(taprootKey), harnessNetParams,
|
||||||
|
)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
p2trPkScript, err := txscript.PayToAddrScript(tapScriptAddr)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
// Send some funds to the output and then try to get a signature through
|
||||||
|
// the SignPsbt RPC to spend that output again.
|
||||||
|
assertPsbtSpend(
|
||||||
|
ctxt, t, net, alice, p2trPkScript,
|
||||||
|
func(packet *psbt.Packet) {
|
||||||
|
in := &packet.Inputs[0]
|
||||||
|
in.Bip32Derivation = []*psbt.Bip32Derivation{{
|
||||||
|
PubKey: keyDesc.RawKeyBytes,
|
||||||
|
Bip32Path: fullDerivationPath,
|
||||||
|
}}
|
||||||
|
in.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{
|
||||||
|
XOnlyPubKey: keyDesc.RawKeyBytes[1:],
|
||||||
|
Bip32Path: fullDerivationPath,
|
||||||
|
}}
|
||||||
|
in.TaprootMerkleRoot = rootHash[:]
|
||||||
|
in.SighashType = txscript.SigHashDefault
|
||||||
|
},
|
||||||
|
func(packet *psbt.Packet) {
|
||||||
|
require.Len(t.t, packet.Inputs, 1)
|
||||||
|
require.Len(
|
||||||
|
t.t, packet.Inputs[0].TaprootKeySpendSig, 64,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runSignPsbtSegWitV1ScriptSpend tests that the SignPsbt RPC works correctly
|
||||||
|
// for a SegWit v1 p2tr script spend.
|
||||||
|
func runSignPsbtSegWitV1ScriptSpend(t *harnessTest,
|
||||||
|
net *lntest.NetworkHarness, alice *lntest.HarnessNode) {
|
||||||
|
|
||||||
|
// Everything we do here should be done within a second or two, so we
|
||||||
|
// can just keep a single timeout context around for all calls.
|
||||||
|
ctxb := context.Background()
|
||||||
|
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Derive a key we can use for signing.
|
||||||
|
keyDesc, internalKey, fullDerivationPath := deriveInternalKey(
|
||||||
|
ctxt, t, alice,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Let's create a taproot script output now. This is a hash lock with a
|
||||||
|
// simple preimage of "foobar".
|
||||||
|
leaf1 := testScriptSchnorrSig(t.t, internalKey)
|
||||||
|
|
||||||
|
rootHash := leaf1.TapHash()
|
||||||
|
taprootKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash[:])
|
||||||
|
tapScriptAddr, err := btcutil.NewAddressTaproot(
|
||||||
|
schnorr.SerializePubKey(taprootKey), harnessNetParams,
|
||||||
|
)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
p2trPkScript, err := txscript.PayToAddrScript(tapScriptAddr)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
// We need to assemble the control block to be able to spend through the
|
||||||
|
// script path.
|
||||||
|
tapscript := input.TapscriptPartialReveal(internalKey, leaf1, nil)
|
||||||
|
controlBlockBytes, err := tapscript.ControlBlock.ToBytes()
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
// Send some funds to the output and then try to get a signature through
|
||||||
|
// the SignPsbt RPC to spend that output again.
|
||||||
|
assertPsbtSpend(
|
||||||
|
ctxt, t, net, alice, p2trPkScript,
|
||||||
|
func(packet *psbt.Packet) {
|
||||||
|
in := &packet.Inputs[0]
|
||||||
|
in.Bip32Derivation = []*psbt.Bip32Derivation{{
|
||||||
|
PubKey: keyDesc.RawKeyBytes,
|
||||||
|
Bip32Path: fullDerivationPath,
|
||||||
|
}}
|
||||||
|
in.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{
|
||||||
|
XOnlyPubKey: keyDesc.RawKeyBytes[1:],
|
||||||
|
Bip32Path: fullDerivationPath,
|
||||||
|
LeafHashes: [][]byte{rootHash[:]},
|
||||||
|
}}
|
||||||
|
in.SighashType = txscript.SigHashDefault
|
||||||
|
in.TaprootLeafScript = []*psbt.TaprootTapLeafScript{{
|
||||||
|
ControlBlock: controlBlockBytes,
|
||||||
|
Script: leaf1.Script,
|
||||||
|
LeafVersion: leaf1.LeafVersion,
|
||||||
|
}}
|
||||||
|
},
|
||||||
|
func(packet *psbt.Packet) {
|
||||||
|
require.Len(t.t, packet.Inputs, 1)
|
||||||
|
require.Len(
|
||||||
|
t.t, packet.Inputs[0].TaprootScriptSpendSig, 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
scriptSpendSig := packet.Inputs[0].TaprootScriptSpendSig[0]
|
||||||
|
require.Len(t.t, scriptSpendSig.Signature, 64)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// assertPsbtSpend creates an output with the given pkScript on chain and then
|
// assertPsbtSpend creates an output with the given pkScript on chain and then
|
||||||
// attempts to create a sweep transaction that is signed using the SignPsbt RPC
|
// attempts to create a sweep transaction that is signed using the SignPsbt RPC
|
||||||
// that spends that output again.
|
// that spends that output again.
|
||||||
@@ -836,6 +1019,34 @@ func assertPsbtSpend(ctx context.Context, t *harnessTest,
|
|||||||
assertTxInBlock(t, block, &secondTxHash)
|
assertTxInBlock(t, block, &secondTxHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deriveInternalKey derives a signing key and returns its descriptor, full
|
||||||
|
// derivation path and parsed public key.
|
||||||
|
func deriveInternalKey(ctx context.Context, t *harnessTest,
|
||||||
|
alice *lntest.HarnessNode) (*signrpc.KeyDescriptor, *btcec.PublicKey,
|
||||||
|
[]uint32) {
|
||||||
|
|
||||||
|
// For the next step, we need a public key. Let's use a special family
|
||||||
|
// for this.
|
||||||
|
keyDesc, err := alice.WalletKitClient.DeriveNextKey(
|
||||||
|
ctx, &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily},
|
||||||
|
)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
// The DeriveNextKey returns a key from the internal 1017 scope.
|
||||||
|
fullDerivationPath := []uint32{
|
||||||
|
hdkeychain.HardenedKeyStart + keychain.BIP0043Purpose,
|
||||||
|
hdkeychain.HardenedKeyStart + harnessNetParams.HDCoinType,
|
||||||
|
hdkeychain.HardenedKeyStart + uint32(keyDesc.KeyLoc.KeyFamily),
|
||||||
|
0,
|
||||||
|
uint32(keyDesc.KeyLoc.KeyIndex),
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedPubKey, err := btcec.ParsePubKey(keyDesc.RawKeyBytes)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
return keyDesc, parsedPubKey, fullDerivationPath
|
||||||
|
}
|
||||||
|
|
||||||
// openChannelPsbt attempts to open a channel between srcNode and destNode with
|
// openChannelPsbt attempts to open a channel between srcNode and destNode with
|
||||||
// the passed channel funding parameters. If the passed context has a timeout,
|
// the passed channel funding parameters. If the passed context has a timeout,
|
||||||
// then if the timeout is reached before the channel pending notification is
|
// then if the timeout is reached before the channel pending notification is
|
||||||
|
|||||||
@@ -107,6 +107,9 @@ func testRemoteSigner(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) {
|
fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) {
|
||||||
runPsbtChanFunding(net, tt, carol, wo)
|
runPsbtChanFunding(net, tt, carol, wo)
|
||||||
runSignPsbtSegWitV0P2WKH(tt, net, wo)
|
runSignPsbtSegWitV0P2WKH(tt, net, wo)
|
||||||
|
runSignPsbtSegWitV1KeySpendBip86(tt, net, wo)
|
||||||
|
runSignPsbtSegWitV1KeySpendRootHash(tt, net, wo)
|
||||||
|
runSignPsbtSegWitV1ScriptSpend(tt, net, wo)
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
name: "sign output raw",
|
name: "sign output raw",
|
||||||
|
|||||||
Reference in new issue
Block a user