itest: add Taproot PSBT signing tests

This commit is contained in:
Oliver Gugger 2022-04-28 13:39:28 +02:00
parent fe30b9e987
commit 6c495c1bbe
No known key found for this signature in database
GPG key ID: 8E4256593F177720
2 changed files with 214 additions and 0 deletions

View file

@ -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

View file

@ -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",