Merge pull request #6680 from guggero/compute-input-script

Taproot: Fix p2tr support in `ComputeInputScript`
This commit is contained in:
Oliver Gugger 2022-06-30 10:11:11 +02:00 committed by GitHub
commit 9d339f0fe2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 156 additions and 22 deletions

View File

@ -19,6 +19,8 @@
## RPC Server ## RPC Server
* [Add previous_outpoints to listchaintxns](https://github.com/lightningnetwork/lnd/pull/6321) * [Add previous_outpoints to listchaintxns](https://github.com/lightningnetwork/lnd/pull/6321)
* [Fix P2TR support in
`ComputeInputScript`](https://github.com/lightningnetwork/lnd/pull/6680).
## Bug Fixes ## Bug Fixes

View File

@ -25,12 +25,12 @@ type Signer interface {
// ComputeInputScript generates a complete InputIndex for the passed // ComputeInputScript generates a complete InputIndex for the passed
// transaction with the signature as defined within the passed // transaction with the signature as defined within the passed
// SignDescriptor. This method should be capable of generating the // SignDescriptor. This method should be capable of generating the
// proper input script for both regular p2wkh output and p2wkh outputs // proper input script for both regular p2wkh/p2tr outputs and p2wkh
// nested within a regular p2sh output. // outputs nested within a regular p2sh output.
// //
// NOTE: This method will ignore any tweak parameters set within the // NOTE: This method will ignore any tweak parameters set within the
// passed SignDescriptor as it assumes a set of typical script // passed SignDescriptor as it assumes a set of typical script
// templates (p2wkh, np2wkh, etc). // templates (p2wkh, p2tr, np2wkh, etc).
ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*Script, ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*Script,
error) error)
} }

View File

@ -22,8 +22,8 @@ service Signer {
/* /*
ComputeInputScript generates a complete InputIndex for the passed ComputeInputScript generates a complete InputIndex for the passed
transaction with the signature as defined within the passed SignDescriptor. transaction with the signature as defined within the passed SignDescriptor.
This method should be capable of generating the proper input script for This method should be capable of generating the proper input script for both
both regular p2wkh output and p2wkh outputs nested within a regular p2sh regular p2wkh/p2tr outputs and p2wkh outputs nested within a regular p2sh
output. output.
Note that when using this method to sign inputs belonging to the wallet, Note that when using this method to sign inputs belonging to the wallet,

View File

@ -18,7 +18,7 @@
"paths": { "paths": {
"/v2/signer/inputscript": { "/v2/signer/inputscript": {
"post": { "post": {
"summary": "ComputeInputScript generates a complete InputIndex for the passed\ntransaction with the signature as defined within the passed SignDescriptor.\nThis method should be capable of generating the proper input script for\nboth regular p2wkh output and p2wkh outputs nested within a regular p2sh\noutput.", "summary": "ComputeInputScript generates a complete InputIndex for the passed\ntransaction with the signature as defined within the passed SignDescriptor.\nThis method should be capable of generating the proper input script for both\nregular p2wkh/p2tr outputs and p2wkh outputs nested within a regular p2sh\noutput.",
"description": "Note that when using this method to sign inputs belonging to the wallet,\nthe only items of the SignDescriptor that need to be populated are pkScript\nin the TxOut field, the value in that same field, and finally the input\nindex.", "description": "Note that when using this method to sign inputs belonging to the wallet,\nthe only items of the SignDescriptor that need to be populated are pkScript\nin the TxOut field, the value in that same field, and finally the input\nindex.",
"operationId": "Signer_ComputeInputScript", "operationId": "Signer_ComputeInputScript",
"responses": { "responses": {

View File

@ -31,8 +31,8 @@ type SignerClient interface {
// //
//ComputeInputScript generates a complete InputIndex for the passed //ComputeInputScript generates a complete InputIndex for the passed
//transaction with the signature as defined within the passed SignDescriptor. //transaction with the signature as defined within the passed SignDescriptor.
//This method should be capable of generating the proper input script for //This method should be capable of generating the proper input script for both
//both regular p2wkh output and p2wkh outputs nested within a regular p2sh //regular p2wkh/p2tr outputs and p2wkh outputs nested within a regular p2sh
//output. //output.
// //
//Note that when using this method to sign inputs belonging to the wallet, //Note that when using this method to sign inputs belonging to the wallet,
@ -255,8 +255,8 @@ type SignerServer interface {
// //
//ComputeInputScript generates a complete InputIndex for the passed //ComputeInputScript generates a complete InputIndex for the passed
//transaction with the signature as defined within the passed SignDescriptor. //transaction with the signature as defined within the passed SignDescriptor.
//This method should be capable of generating the proper input script for //This method should be capable of generating the proper input script for both
//both regular p2wkh output and p2wkh outputs nested within a regular p2sh //regular p2wkh/p2tr outputs and p2wkh outputs nested within a regular p2sh
//output. //output.
// //
//Note that when using this method to sign inputs belonging to the wallet, //Note that when using this method to sign inputs belonging to the wallet,

View File

@ -489,7 +489,8 @@ func (s *Server) SignOutputRaw(_ context.Context, in *SignReq) (*SignResp,
// ComputeInputScript generates a complete InputIndex for the passed // ComputeInputScript generates a complete InputIndex for the passed
// transaction with the signature as defined within the passed SignDescriptor. // transaction with the signature as defined within the passed SignDescriptor.
// This method should be capable of generating the proper input script for both // This method should be capable of generating the proper input script for both
// regular p2wkh output and p2wkh outputs nested within a regular p2sh output. // regular p2wkh/p2tr outputs and p2wkh outputs nested within a regular p2sh
// output.
// //
// Note that when using this method to sign inputs belonging to the wallet, the // Note that when using this method to sign inputs belonging to the wallet, the
// only items of the SignDescriptor that need to be populated are pkScript in // only items of the SignDescriptor that need to be populated are pkScript in
@ -519,7 +520,33 @@ func (s *Server) ComputeInputScript(ctx context.Context,
return nil, fmt.Errorf("unable to decode tx: %v", err) return nil, fmt.Errorf("unable to decode tx: %v", err)
} }
sigHashCache := input.NewTxSigHashesV0Only(&txToSign) var (
sigHashCache = input.NewTxSigHashesV0Only(&txToSign)
prevOutputFetcher = txscript.NewMultiPrevOutFetcher(nil)
)
// If we're spending one or more SegWit v1 (Taproot) inputs, then we
// need the full UTXO information available.
if len(in.PrevOutputs) > 0 {
if len(in.PrevOutputs) != len(txToSign.TxIn) {
return nil, fmt.Errorf("provided previous outputs " +
"doesn't match number of transaction inputs")
}
// Add all previous inputs to our sighash prev out fetcher so we
// can calculate the sighash correctly.
for idx, txIn := range txToSign.TxIn {
prevOutputFetcher.AddPrevOut(
txIn.PreviousOutPoint, &wire.TxOut{
Value: in.PrevOutputs[idx].Value,
PkScript: in.PrevOutputs[idx].PkScript,
},
)
}
sigHashCache = txscript.NewTxSigHashes(
&txToSign, prevOutputFetcher,
)
}
signDescs := make([]*input.SignDescriptor, 0, len(in.SignDescs)) signDescs := make([]*input.SignDescriptor, 0, len(in.SignDescs))
for _, signDesc := range in.SignDescs { for _, signDesc := range in.SignDescs {
@ -532,9 +559,10 @@ func (s *Server) ComputeInputScript(ctx context.Context,
Value: signDesc.Output.Value, Value: signDesc.Output.Value,
PkScript: signDesc.Output.PkScript, PkScript: signDesc.Output.PkScript,
}, },
HashType: txscript.SigHashType(signDesc.Sighash), HashType: txscript.SigHashType(signDesc.Sighash),
SigHashes: sigHashCache, SigHashes: sigHashCache,
InputIndex: int(signDesc.InputIndex), PrevOutputFetcher: prevOutputFetcher,
InputIndex: int(signDesc.InputIndex),
}) })
} }

View File

@ -126,6 +126,7 @@ func testRemoteSigner(net *lntest.NetworkHarness, t *harnessTest) {
) )
defer cancel() defer cancel()
testTaprootSendCoinsKeySpendBip86(ctxt, tt, wo, net)
testTaprootComputeInputScriptKeySpendBip86( testTaprootComputeInputScriptKeySpendBip86(
ctxt, tt, wo, net, ctxt, tt, wo, net,
) )

View File

@ -46,6 +46,7 @@ func testTaproot(net *lntest.NetworkHarness, t *harnessTest) {
ctxt, cancel := context.WithTimeout(ctxb, 2*defaultTimeout) ctxt, cancel := context.WithTimeout(ctxb, 2*defaultTimeout)
defer cancel() defer cancel()
testTaprootSendCoinsKeySpendBip86(ctxt, t, net.Alice, net)
testTaprootComputeInputScriptKeySpendBip86(ctxt, t, net.Alice, net) testTaprootComputeInputScriptKeySpendBip86(ctxt, t, net.Alice, net)
testTaprootSignOutputRawScriptSpend(ctxt, t, net.Alice, net) testTaprootSignOutputRawScriptSpend(ctxt, t, net.Alice, net)
testTaprootSignOutputRawKeySpendBip86(ctxt, t, net.Alice, net) testTaprootSignOutputRawKeySpendBip86(ctxt, t, net.Alice, net)
@ -56,10 +57,10 @@ func testTaproot(net *lntest.NetworkHarness, t *harnessTest) {
testTaprootMuSig2CombinedLeafKeySpend(ctxt, t, net.Alice, net) testTaprootMuSig2CombinedLeafKeySpend(ctxt, t, net.Alice, net)
} }
// testTaprootComputeInputScriptKeySpendBip86 tests sending to and spending from // testTaprootSendCoinsKeySpendBip86 tests sending to and spending from
// p2tr key spend only (BIP-0086) addresses through the SendCoins RPC which // p2tr key spend only (BIP-0086) addresses through the SendCoins RPC which
// internally uses the ComputeInputScript method for signing. // internally uses the ComputeInputScript method for signing.
func testTaprootComputeInputScriptKeySpendBip86(ctxt context.Context, func testTaprootSendCoinsKeySpendBip86(ctxt context.Context,
t *harnessTest, alice *lntest.HarnessNode, net *lntest.NetworkHarness) { t *harnessTest, alice *lntest.HarnessNode, net *lntest.NetworkHarness) {
// We'll start the test by sending Alice some coins, which she'll use to // We'll start the test by sending Alice some coins, which she'll use to
@ -117,6 +118,108 @@ func testTaprootComputeInputScriptKeySpendBip86(ctxt context.Context,
confirmAddress(ctxt, t, net, alice, p2trResp.Address) confirmAddress(ctxt, t, net, alice, p2trResp.Address)
} }
// testTaprootComputeInputScriptKeySpendBip86 tests sending to and spending
// from p2tr key spend only (BIP-0086) addresses through the ComputeInputScript
// RPC.
func testTaprootComputeInputScriptKeySpendBip86(ctxt context.Context,
t *harnessTest, alice *lntest.HarnessNode, net *lntest.NetworkHarness) {
// We'll start the test by sending Alice some coins, which she'll use to
// send to herself on a p2tr output.
net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, alice)
// Let's create a p2tr address now.
p2trAddr, p2trPkScript := newAddrWithScript(
ctxt, t.t, alice, lnrpc.AddressType_TAPROOT_PUBKEY,
)
// Send the coins from Alice's wallet to her own, but to the new p2tr
// address.
_, err := alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{
Addr: p2trAddr.String(),
Amount: testAmount,
})
require.NoError(t.t, err)
txid, err := waitForTxInMempool(net.Miner.Client, defaultTimeout)
require.NoError(t.t, err)
// Wait until bob has seen the tx and considers it as owned.
p2trOutputIndex := getOutputIndex(t, net.Miner, txid, p2trAddr.String())
op := &lnrpc.OutPoint{
TxidBytes: txid[:],
OutputIndex: uint32(p2trOutputIndex),
}
assertWalletUnspent(t, alice, op)
p2trOutpoint := wire.OutPoint{
Hash: *txid,
Index: uint32(p2trOutputIndex),
}
// Mine a block to clean up the mempool.
mineBlocks(t, net, 1, 1)
// We'll send the coins back to a p2wkh address.
p2wkhAddr, p2wkhPkScript := newAddrWithScript(
ctxt, t.t, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH,
)
// Create fee estimation for a p2tr input and p2wkh output.
feeRate := chainfee.SatPerKWeight(12500)
estimator := input.TxWeightEstimator{}
estimator.AddTaprootKeySpendInput(txscript.SigHashDefault)
estimator.AddP2WKHOutput()
estimatedWeight := int64(estimator.Weight())
requiredFee := feeRate.FeeForWeight(estimatedWeight)
tx := wire.NewMsgTx(2)
tx.TxIn = []*wire.TxIn{{
PreviousOutPoint: p2trOutpoint,
}}
value := int64(testAmount - requiredFee)
tx.TxOut = []*wire.TxOut{{
PkScript: p2wkhPkScript,
Value: value,
}}
var buf bytes.Buffer
require.NoError(t.t, tx.Serialize(&buf))
utxoInfo := []*signrpc.TxOut{{
PkScript: p2trPkScript,
Value: testAmount,
}}
signResp, err := alice.SignerClient.ComputeInputScript(
ctxt, &signrpc.SignReq{
RawTxBytes: buf.Bytes(),
SignDescs: []*signrpc.SignDescriptor{{
Output: utxoInfo[0],
InputIndex: 0,
Sighash: uint32(txscript.SigHashDefault),
}},
PrevOutputs: utxoInfo,
},
)
require.NoError(t.t, err)
tx.TxIn[0].Witness = signResp.InputScripts[0].Witness
// Serialize, weigh and publish the TX now, then make sure the
// coins are sent and confirmed to the final sweep destination address.
publishTxAndConfirmSweep(
ctxt, t, net, alice, tx, estimatedWeight,
&chainrpc.SpendRequest{
Outpoint: &chainrpc.Outpoint{
Hash: p2trOutpoint.Hash[:],
Index: p2trOutpoint.Index,
},
Script: p2trPkScript,
},
p2wkhAddr.String(),
)
}
// testTaprootSignOutputRawScriptSpend tests sending to and spending from p2tr // testTaprootSignOutputRawScriptSpend tests sending to and spending from p2tr
// script addresses using the script path with the SignOutputRaw RPC. // script addresses using the script path with the SignOutputRaw RPC.
func testTaprootSignOutputRawScriptSpend(ctxt context.Context, t *harnessTest, func testTaprootSignOutputRawScriptSpend(ctxt context.Context, t *harnessTest,
@ -1043,20 +1146,20 @@ func newAddrWithScript(ctx context.Context, t *testing.T,
node *lntest.HarnessNode, addrType lnrpc.AddressType) (btcutil.Address, node *lntest.HarnessNode, addrType lnrpc.AddressType) (btcutil.Address,
[]byte) { []byte) {
p2wkhResp, err := node.NewAddress(ctx, &lnrpc.NewAddressRequest{ newAddrResp, err := node.NewAddress(ctx, &lnrpc.NewAddressRequest{
Type: addrType, Type: addrType,
}) })
require.NoError(t, err) require.NoError(t, err)
p2wkhAddr, err := btcutil.DecodeAddress( addr, err := btcutil.DecodeAddress(
p2wkhResp.Address, harnessNetParams, newAddrResp.Address, harnessNetParams,
) )
require.NoError(t, err) require.NoError(t, err)
p2wkhPkScript, err := txscript.PayToAddrScript(p2wkhAddr) pkScript, err := txscript.PayToAddrScript(addr)
require.NoError(t, err) require.NoError(t, err)
return p2wkhAddr, p2wkhPkScript return addr, pkScript
} }
// sendToTaprootOutput sends coins to a p2tr output of the given taproot key and // sendToTaprootOutput sends coins to a p2tr output of the given taproot key and