From b8f1541800aae681525602ad35a23e720ee9cfc4 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Mon, 27 Jun 2022 21:59:48 +0200 Subject: [PATCH 1/6] signrpc: fix p2tr support in ComputeInputScript --- lnrpc/signrpc/signer.proto | 4 ++-- lnrpc/signrpc/signer.swagger.json | 2 +- lnrpc/signrpc/signer_grpc.pb.go | 8 +++---- lnrpc/signrpc/signer_server.go | 38 +++++++++++++++++++++++++++---- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/lnrpc/signrpc/signer.proto b/lnrpc/signrpc/signer.proto index 9085c6f67..07e2db3b1 100644 --- a/lnrpc/signrpc/signer.proto +++ b/lnrpc/signrpc/signer.proto @@ -22,8 +22,8 @@ service Signer { /* ComputeInputScript generates a complete InputIndex for the passed transaction with the signature as defined within the passed SignDescriptor. - This method should be capable of generating the proper input script for - both regular p2wkh output and p2wkh outputs nested within a regular p2sh + This method should be capable of generating the proper input script for both + 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, diff --git a/lnrpc/signrpc/signer.swagger.json b/lnrpc/signrpc/signer.swagger.json index edd0afa2e..5a7a424a9 100644 --- a/lnrpc/signrpc/signer.swagger.json +++ b/lnrpc/signrpc/signer.swagger.json @@ -18,7 +18,7 @@ "paths": { "/v2/signer/inputscript": { "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.", "operationId": "Signer_ComputeInputScript", "responses": { diff --git a/lnrpc/signrpc/signer_grpc.pb.go b/lnrpc/signrpc/signer_grpc.pb.go index 2c0a01899..e8017b4a8 100644 --- a/lnrpc/signrpc/signer_grpc.pb.go +++ b/lnrpc/signrpc/signer_grpc.pb.go @@ -31,8 +31,8 @@ type SignerClient interface { // //ComputeInputScript generates a complete InputIndex for the passed //transaction with the signature as defined within the passed SignDescriptor. - //This method should be capable of generating the proper input script for - //both regular p2wkh output and p2wkh outputs nested within a regular p2sh + //This method should be capable of generating the proper input script for both + //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, @@ -255,8 +255,8 @@ type SignerServer interface { // //ComputeInputScript generates a complete InputIndex for the passed //transaction with the signature as defined within the passed SignDescriptor. - //This method should be capable of generating the proper input script for - //both regular p2wkh output and p2wkh outputs nested within a regular p2sh + //This method should be capable of generating the proper input script for both + //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, diff --git a/lnrpc/signrpc/signer_server.go b/lnrpc/signrpc/signer_server.go index 7dbdece22..b71d14f4a 100644 --- a/lnrpc/signrpc/signer_server.go +++ b/lnrpc/signrpc/signer_server.go @@ -489,7 +489,8 @@ func (s *Server) SignOutputRaw(_ context.Context, in *SignReq) (*SignResp, // ComputeInputScript generates a complete InputIndex for the passed // transaction with the signature as defined within the passed SignDescriptor. // 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 // 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) } - 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)) for _, signDesc := range in.SignDescs { @@ -532,9 +559,10 @@ func (s *Server) ComputeInputScript(ctx context.Context, Value: signDesc.Output.Value, PkScript: signDesc.Output.PkScript, }, - HashType: txscript.SigHashType(signDesc.Sighash), - SigHashes: sigHashCache, - InputIndex: int(signDesc.InputIndex), + HashType: txscript.SigHashType(signDesc.Sighash), + SigHashes: sigHashCache, + PrevOutputFetcher: prevOutputFetcher, + InputIndex: int(signDesc.InputIndex), }) } From 7bd19d5cdf03783af6f000f223aea5dcbed219ec Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Mon, 27 Jun 2022 22:00:07 +0200 Subject: [PATCH 2/6] signer: fix comment about p2tr in ComputeInputScript --- input/signer.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/input/signer.go b/input/signer.go index fb3609115..1cf4003b0 100644 --- a/input/signer.go +++ b/input/signer.go @@ -25,12 +25,12 @@ type Signer interface { // ComputeInputScript generates a complete InputIndex for the passed // transaction with the signature as defined within the passed // SignDescriptor. 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. + // proper input script for both regular p2wkh/p2tr outputs and p2wkh + // outputs nested within a regular p2sh output. // // NOTE: This method will ignore any tweak parameters set within the // 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, error) } From 21f69192be81c2e02f082fcde92f4d26ed05dec1 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Mon, 27 Jun 2022 22:00:55 +0200 Subject: [PATCH 3/6] itest: rename too specific variables --- lntest/itest/lnd_taproot_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lntest/itest/lnd_taproot_test.go b/lntest/itest/lnd_taproot_test.go index 1d28248d0..40aed956f 100644 --- a/lntest/itest/lnd_taproot_test.go +++ b/lntest/itest/lnd_taproot_test.go @@ -1043,20 +1043,20 @@ func newAddrWithScript(ctx context.Context, t *testing.T, node *lntest.HarnessNode, addrType lnrpc.AddressType) (btcutil.Address, []byte) { - p2wkhResp, err := node.NewAddress(ctx, &lnrpc.NewAddressRequest{ + newAddrResp, err := node.NewAddress(ctx, &lnrpc.NewAddressRequest{ Type: addrType, }) require.NoError(t, err) - p2wkhAddr, err := btcutil.DecodeAddress( - p2wkhResp.Address, harnessNetParams, + addr, err := btcutil.DecodeAddress( + newAddrResp.Address, harnessNetParams, ) require.NoError(t, err) - p2wkhPkScript, err := txscript.PayToAddrScript(p2wkhAddr) + pkScript, err := txscript.PayToAddrScript(addr) require.NoError(t, err) - return p2wkhAddr, p2wkhPkScript + return addr, pkScript } // sendToTaprootOutput sends coins to a p2tr output of the given taproot key and From 6ee4c533f4c6ba6331feb6fb1a6c8338a9e702e4 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Mon, 27 Jun 2022 22:03:37 +0200 Subject: [PATCH 4/6] itest: fix incorrect naming --- lntest/itest/lnd_remote_signer_test.go | 4 +--- lntest/itest/lnd_taproot_test.go | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lntest/itest/lnd_remote_signer_test.go b/lntest/itest/lnd_remote_signer_test.go index 1c5426a77..80c855fa9 100644 --- a/lntest/itest/lnd_remote_signer_test.go +++ b/lntest/itest/lnd_remote_signer_test.go @@ -126,9 +126,7 @@ func testRemoteSigner(net *lntest.NetworkHarness, t *harnessTest) { ) defer cancel() - testTaprootComputeInputScriptKeySpendBip86( - ctxt, tt, wo, net, - ) + testTaprootSendCoinsKeySpendBip86(ctxt, tt, wo, net) testTaprootSignOutputRawScriptSpend(ctxt, tt, wo, net) testTaprootSignOutputRawKeySpendBip86(ctxt, tt, wo, net) testTaprootSignOutputRawKeySpendRootHash( diff --git a/lntest/itest/lnd_taproot_test.go b/lntest/itest/lnd_taproot_test.go index 40aed956f..9999a553c 100644 --- a/lntest/itest/lnd_taproot_test.go +++ b/lntest/itest/lnd_taproot_test.go @@ -46,7 +46,7 @@ func testTaproot(net *lntest.NetworkHarness, t *harnessTest) { ctxt, cancel := context.WithTimeout(ctxb, 2*defaultTimeout) defer cancel() - testTaprootComputeInputScriptKeySpendBip86(ctxt, t, net.Alice, net) + testTaprootSendCoinsKeySpendBip86(ctxt, t, net.Alice, net) testTaprootSignOutputRawScriptSpend(ctxt, t, net.Alice, net) testTaprootSignOutputRawKeySpendBip86(ctxt, t, net.Alice, net) testTaprootSignOutputRawKeySpendRootHash(ctxt, t, net.Alice, net) @@ -56,10 +56,10 @@ func testTaproot(net *lntest.NetworkHarness, t *harnessTest) { 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 // 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) { // We'll start the test by sending Alice some coins, which she'll use to From 825ff22eec296753429034721ccf65a0050f03bc Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Mon, 27 Jun 2022 22:01:08 +0200 Subject: [PATCH 5/6] itest: add itest for p2tr via ComputeInputScript --- lntest/itest/lnd_remote_signer_test.go | 3 + lntest/itest/lnd_taproot_test.go | 103 +++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/lntest/itest/lnd_remote_signer_test.go b/lntest/itest/lnd_remote_signer_test.go index 80c855fa9..7a30e5c74 100644 --- a/lntest/itest/lnd_remote_signer_test.go +++ b/lntest/itest/lnd_remote_signer_test.go @@ -127,6 +127,9 @@ func testRemoteSigner(net *lntest.NetworkHarness, t *harnessTest) { defer cancel() testTaprootSendCoinsKeySpendBip86(ctxt, tt, wo, net) + testTaprootComputeInputScriptKeySpendBip86( + ctxt, tt, wo, net, + ) testTaprootSignOutputRawScriptSpend(ctxt, tt, wo, net) testTaprootSignOutputRawKeySpendBip86(ctxt, tt, wo, net) testTaprootSignOutputRawKeySpendRootHash( diff --git a/lntest/itest/lnd_taproot_test.go b/lntest/itest/lnd_taproot_test.go index 9999a553c..1b9e6b6a9 100644 --- a/lntest/itest/lnd_taproot_test.go +++ b/lntest/itest/lnd_taproot_test.go @@ -47,6 +47,7 @@ func testTaproot(net *lntest.NetworkHarness, t *harnessTest) { defer cancel() testTaprootSendCoinsKeySpendBip86(ctxt, t, net.Alice, net) + testTaprootComputeInputScriptKeySpendBip86(ctxt, t, net.Alice, net) testTaprootSignOutputRawScriptSpend(ctxt, t, net.Alice, net) testTaprootSignOutputRawKeySpendBip86(ctxt, t, net.Alice, net) testTaprootSignOutputRawKeySpendRootHash(ctxt, t, net.Alice, net) @@ -117,6 +118,108 @@ func testTaprootSendCoinsKeySpendBip86(ctxt context.Context, 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 // script addresses using the script path with the SignOutputRaw RPC. func testTaprootSignOutputRawScriptSpend(ctxt context.Context, t *harnessTest, From cac09098e186fc745fe57a03e0932213a198ee66 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Mon, 27 Jun 2022 22:02:17 +0200 Subject: [PATCH 6/6] docs: add release notes --- docs/release-notes/release-notes-0.15.1.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/release-notes/release-notes-0.15.1.md b/docs/release-notes/release-notes-0.15.1.md index 410eb29ae..28a590c02 100644 --- a/docs/release-notes/release-notes-0.15.1.md +++ b/docs/release-notes/release-notes-0.15.1.md @@ -19,6 +19,8 @@ ## RPC Server * [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