mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-02-23 06:35:07 +01:00
itest: add Taproot PSBT signing tests
This commit is contained in:
parent
fe30b9e987
commit
6c495c1bbe
2 changed files with 214 additions and 0 deletions
|
@ -6,13 +6,17 @@ import (
|
|||
"crypto/rand"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"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/signrpc"
|
||||
"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.
|
||||
func testSignPsbt(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
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
|
||||
|
@ -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
|
||||
// attempts to create a sweep transaction that is signed using the SignPsbt RPC
|
||||
// that spends that output again.
|
||||
|
@ -836,6 +1019,34 @@ func assertPsbtSpend(ctx context.Context, t *harnessTest,
|
|||
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
|
||||
// the passed channel funding parameters. If the passed context has a timeout,
|
||||
// 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) {
|
||||
runPsbtChanFunding(net, tt, carol, wo)
|
||||
runSignPsbtSegWitV0P2WKH(tt, net, wo)
|
||||
runSignPsbtSegWitV1KeySpendBip86(tt, net, wo)
|
||||
runSignPsbtSegWitV1KeySpendRootHash(tt, net, wo)
|
||||
runSignPsbtSegWitV1ScriptSpend(tt, net, wo)
|
||||
},
|
||||
}, {
|
||||
name: "sign output raw",
|
||||
|
|
Loading…
Add table
Reference in a new issue