From 09736b3cecebb5febe10b42fce86b1cff96531e5 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Mon, 14 Feb 2022 14:26:02 +0100 Subject: [PATCH] multi: support P2TR addresses --- cmd/lncli/commands.go | 5 +- config_builder.go | 9 +- contractcourt/breacharbiter.go | 32 ++- input/input.go | 31 ++- input/script_utils.go | 22 ++ input/signdescriptor.go | 5 + input/size.go | 6 + input/witnessgen.go | 21 ++ lnrpc/lightning.pb.go | 28 ++- lnrpc/lightning.proto | 3 + lnrpc/lightning.swagger.json | 10 +- lnrpc/marshall_utils.go | 3 + lnrpc/walletrpc/walletkit.pb.go | 268 ++++++++++++----------- lnrpc/walletrpc/walletkit.proto | 1 + lnrpc/walletrpc/walletkit.swagger.json | 12 +- lnrpc/walletrpc/walletkit_server.go | 11 + lntest/itest/lnd_taproot_test.go | 81 +++++++ lntest/itest/lnd_test_list_on_test.go | 4 + lntest/itest/utils.go | 30 +++ lntest/mock/walletcontroller.go | 10 + lnwallet/btcwallet/btcwallet.go | 26 ++- lnwallet/btcwallet/psbt.go | 4 +- lnwallet/btcwallet/signer.go | 60 ++++- lnwallet/btcwallet/signer_test.go | 25 +++ lnwallet/chanfunding/assembler.go | 5 + lnwallet/chanfunding/wallet_assembler.go | 7 +- lnwallet/interface.go | 8 + lnwallet/rpcwallet/rpcwallet.go | 7 +- lnwallet/wallet.go | 15 ++ rpcserver.go | 16 ++ sweep/sweeper_test.go | 2 +- sweep/txgenerator.go | 22 +- sweep/txgenerator_test.go | 2 +- sweep/walletsweep.go | 3 + watchtower/lookout/justice_descriptor.go | 44 +++- watchtower/wtclient/backup_task.go | 10 +- 36 files changed, 648 insertions(+), 200 deletions(-) create mode 100644 lntest/itest/lnd_taproot_test.go diff --git a/cmd/lncli/commands.go b/cmd/lncli/commands.go index bf54c7650ebd..80601d4c64cb 100644 --- a/cmd/lncli/commands.go +++ b/cmd/lncli/commands.go @@ -139,7 +139,8 @@ var newAddressCommand = cli.Command{ Description: ` Generate a wallet new address. Address-types has to be one of: - p2wkh: Pay to witness key hash - - np2wkh: Pay to nested witness key hash`, + - np2wkh: Pay to nested witness key hash + - p2tr: Pay to taproot pubkey`, Action: actionDecorator(newAddress), } @@ -161,6 +162,8 @@ func newAddress(ctx *cli.Context) error { addrType = lnrpc.AddressType_WITNESS_PUBKEY_HASH case "np2wkh": addrType = lnrpc.AddressType_NESTED_PUBKEY_HASH + case "p2tr": + addrType = lnrpc.AddressType_TAPROOT_PUBKEY default: return fmt.Errorf("invalid address type %v, support address type "+ "are: p2wkh and np2wkh", stringAddrType) diff --git a/config_builder.go b/config_builder.go index 0e8ddfa8d832..b7bfeec00914 100644 --- a/config_builder.go +++ b/config_builder.go @@ -1112,7 +1112,14 @@ func importWatchOnlyAccounts(wallet *wallet.Wallet, for _, scope := range scopes { addrSchema := waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0084] - if scope.Scope.Purpose == waddrmgr.KeyScopeBIP0049Plus.Purpose { + + // We want witness pubkey hash by default, except for BIP49 + // where we want mixed and BIP86 where we want taproot address + // formats. + switch scope.Scope.Purpose { + case waddrmgr.KeyScopeBIP0049Plus.Purpose, + waddrmgr.KeyScopeBIP0086.Purpose: + addrSchema = waddrmgr.ScopeAddrMap[scope.Scope] } diff --git a/contractcourt/breacharbiter.go b/contractcourt/breacharbiter.go index 8ebb36c4ad13..126726242dbb 100644 --- a/contractcourt/breacharbiter.go +++ b/contractcourt/breacharbiter.go @@ -1097,11 +1097,15 @@ func (bo *breachedOutput) SignDesc() *input.SignDescriptor { // sign descriptor. The method then returns the witness computed by invoking // this function on the first and subsequent calls. func (bo *breachedOutput) CraftInputScript(signer input.Signer, txn *wire.MsgTx, - hashCache *txscript.TxSigHashes, txinIdx int) (*input.Script, error) { + hashCache *txscript.TxSigHashes, + prevOutputFetcher txscript.PrevOutputFetcher, + txinIdx int) (*input.Script, error) { // First, we ensure that the witness generation function has been // initialized for this breached output. - bo.witnessFunc = bo.witnessType.WitnessGenerator(signer, bo.SignDesc()) + signDesc := bo.SignDesc() + signDesc.PrevOutputFetcher = prevOutputFetcher + bo.witnessFunc = bo.witnessType.WitnessGenerator(signer, signDesc) // Now that we have ensured that the witness generation function has // been initialized, we can proceed to execute it and generate the @@ -1398,8 +1402,8 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64, // Compute the total amount contained in the inputs. var totalAmt btcutil.Amount - for _, input := range inputs { - totalAmt += btcutil.Amount(input.SignDesc().Output.Value) + for _, inp := range inputs { + totalAmt += btcutil.Amount(inp.SignDesc().Output.Value) } // We'll actually attempt to target inclusion within the next two @@ -1425,11 +1429,15 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64, // Next, we add all of the spendable outputs as inputs to the // transaction. - for _, input := range inputs { + prevOutputFetcher := txscript.NewMultiPrevOutFetcher(nil) + for _, inp := range inputs { txn.AddTxIn(&wire.TxIn{ - PreviousOutPoint: *input.OutPoint(), - Sequence: input.BlocksToMaturity(), + PreviousOutPoint: *inp.OutPoint(), + Sequence: inp.BlocksToMaturity(), }) + prevOutputFetcher.AddPrevOut( + *inp.OutPoint(), inp.RequiredTxOut(), + ) } // Before signing the transaction, check to ensure that it meets some @@ -1441,7 +1449,7 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64, // Create a sighash cache to improve the performance of hashing and // signing SigHashAll inputs. - hashCache := txscript.NewTxSigHashesV0Only(txn) + hashCache := txscript.NewTxSigHashes(txn, prevOutputFetcher) // Create a closure that encapsulates the process of initializing a // particular output's witness generation function, computing the @@ -1453,7 +1461,7 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64, // transaction using the SpendableOutput's witness generation // function. inputScript, err := so.CraftInputScript( - b.cfg.Signer, txn, hashCache, idx, + b.cfg.Signer, txn, hashCache, prevOutputFetcher, idx, ) if err != nil { return err @@ -1468,8 +1476,8 @@ func (b *BreachArbiter) sweepSpendableOutputsTxn(txWeight int64, // Finally, generate a witness for each output and attach it to the // transaction. - for i, input := range inputs { - if err := addWitness(i, input); err != nil { + for i, inp := range inputs { + if err := addWitness(i, inp); err != nil { return nil, err } } @@ -1649,7 +1657,7 @@ func (ret *retributionInfo) Encode(w io.Writer) error { return nil } -// Dencode deserializes a retribution from the passed byte stream. +// Decode deserializes a retribution from the passed byte stream. func (ret *retributionInfo) Decode(r io.Reader) error { var scratch [32]byte diff --git a/input/input.go b/input/input.go index e72b9739bd75..98ae2c056c38 100644 --- a/input/input.go +++ b/input/input.go @@ -42,6 +42,7 @@ type Input interface { // also nested p2sh outputs. CraftInputScript(signer Signer, txn *wire.MsgTx, hashCache *txscript.TxSigHashes, + prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script, error) // BlocksToMaturity returns the relative timelock, as a number of @@ -221,9 +222,13 @@ func NewCsvInputWithCltv(outpoint *wire.OutPoint, witnessType WitnessType, // txIndex within the passed transaction. The input scripts generated by this // method support spending p2wkh, p2wsh, and also nested p2sh outputs. func (bi *BaseInput) CraftInputScript(signer Signer, txn *wire.MsgTx, - hashCache *txscript.TxSigHashes, txinIdx int) (*Script, error) { + hashCache *txscript.TxSigHashes, + prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script, + error) { - witnessFunc := bi.witnessType.WitnessGenerator(signer, bi.SignDesc()) + signDesc := bi.SignDesc() + signDesc.PrevOutputFetcher = prevOutputFetcher + witnessFunc := bi.witnessType.WitnessGenerator(signer, signDesc) return witnessFunc(txn, hashCache, txinIdx) } @@ -260,11 +265,14 @@ func MakeHtlcSucceedInput(outpoint *wire.OutPoint, // txIndex within the passed transaction. The input scripts generated by this // method support spending p2wkh, p2wsh, and also nested p2sh outputs. func (h *HtlcSucceedInput) CraftInputScript(signer Signer, txn *wire.MsgTx, - hashCache *txscript.TxSigHashes, txinIdx int) (*Script, error) { + hashCache *txscript.TxSigHashes, + prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script, + error) { desc := h.signDesc desc.SigHashes = hashCache desc.InputIndex = txinIdx + desc.PrevOutputFetcher = prevOutputFetcher witness, err := SenderHtlcSpendRedeem( signer, &desc, txn, h.preimage, @@ -291,7 +299,9 @@ type HtlcSecondLevelAnchorInput struct { // createWitness creates a witness allowing the passed transaction to // spend the input. createWitness func(signer Signer, txn *wire.MsgTx, - hashCache *txscript.TxSigHashes, txinIdx int) (wire.TxWitness, error) + hashCache *txscript.TxSigHashes, + prevOutputFetcher txscript.PrevOutputFetcher, + txinIdx int) (wire.TxWitness, error) } // RequiredTxOut returns the tx out needed to be present on the sweep tx for @@ -313,9 +323,12 @@ func (i *HtlcSecondLevelAnchorInput) RequiredLockTime() (uint32, bool) { // method support spending p2wkh, p2wsh, and also nested p2sh outputs. func (i *HtlcSecondLevelAnchorInput) CraftInputScript(signer Signer, txn *wire.MsgTx, hashCache *txscript.TxSigHashes, - txinIdx int) (*Script, error) { + prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script, + error) { - witness, err := i.createWitness(signer, txn, hashCache, txinIdx) + witness, err := i.createWitness( + signer, txn, hashCache, prevOutputFetcher, txinIdx, + ) if err != nil { return nil, err } @@ -335,11 +348,13 @@ func MakeHtlcSecondLevelTimeoutAnchorInput(signedTx *wire.MsgTx, // 2nd timeout transaction. createWitness := func(signer Signer, txn *wire.MsgTx, hashCache *txscript.TxSigHashes, + prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (wire.TxWitness, error) { desc := signDetails.SignDesc - desc.SigHashes = txscript.NewTxSigHashesV0Only(txn) + desc.SigHashes = txscript.NewTxSigHashes(txn, prevOutputFetcher) desc.InputIndex = txinIdx + desc.PrevOutputFetcher = prevOutputFetcher return SenderHtlcSpendTimeout( signDetails.PeerSig, signDetails.SigHashType, signer, @@ -373,11 +388,13 @@ func MakeHtlcSecondLevelSuccessAnchorInput(signedTx *wire.MsgTx, // success transaction. createWitness := func(signer Signer, txn *wire.MsgTx, hashCache *txscript.TxSigHashes, + prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (wire.TxWitness, error) { desc := signDetails.SignDesc desc.SigHashes = hashCache desc.InputIndex = txinIdx + desc.PrevOutputFetcher = prevOutputFetcher return ReceiverHtlcSpendRedeem( signDetails.PeerSig, signDetails.SigHashType, diff --git a/input/script_utils.go b/input/script_utils.go index c748588c9aaf..1dd4121e6c4e 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -1558,3 +1558,25 @@ func ComputeCommitmentPoint(commitSecret []byte) *btcec.PublicKey { &commitPointJacobian.X, &commitPointJacobian.Y, ) } + +// MultiPrevOutFetcher returns a txscript.MultiPrevOutFetcher for the given set +// of inputs. +func MultiPrevOutFetcher(inputs []Input) (*txscript.MultiPrevOutFetcher, error) { + fetcher := txscript.NewMultiPrevOutFetcher(nil) + for _, inp := range inputs { + op := inp.OutPoint() + desc := inp.SignDesc() + + if op == nil { + return nil, fmt.Errorf("missing input outpoint") + } + + if desc == nil || desc.Output == nil { + return nil, fmt.Errorf("missing input utxo information") + } + + fetcher.AddPrevOut(*op, desc.Output) + } + + return fetcher, nil +} diff --git a/input/signdescriptor.go b/input/signdescriptor.go index bfcd50fe25ef..b97fbb5de068 100644 --- a/input/signdescriptor.go +++ b/input/signdescriptor.go @@ -74,6 +74,11 @@ type SignDescriptor struct { // generating the final sighash for signing. SigHashes *txscript.TxSigHashes + // PrevOutputFetcher is an interface that can return the output + // information on all UTXOs that are being spent in this transaction. + // This MUST be set when spending Taproot outputs. + PrevOutputFetcher txscript.PrevOutputFetcher + // InputIndex is the target input within the transaction that should be // signed. InputIndex int diff --git a/input/size.go b/input/size.go index ed6d4cb719a1..b6108f865336 100644 --- a/input/size.go +++ b/input/size.go @@ -519,6 +519,12 @@ const ( // - witness_script_length: 1 byte // - witness_script (anchor_script) AnchorWitnessSize = 1 + 1 + 73 + 1 + AnchorScriptSize + + // TaprootKeyPathWitnessSize 66 bytes + // - NumberOfWitnessElements: 1 byte + // - sigLength: 1 byte + // - sig: 64 bytes + TaprootKeyPathWitnessSize = 1 + 1 + 64 ) // EstimateCommitTxWeight estimate commitment transaction weight depending on diff --git a/input/witnessgen.go b/input/witnessgen.go index 9fea9179934e..f982e0213e5e 100644 --- a/input/witnessgen.go +++ b/input/witnessgen.go @@ -175,6 +175,10 @@ const ( // and CLTV locktime as part of the script enforced lease commitment // type. LeaseHtlcAcceptedSuccessSecondLevel StandardWitnessType = 20 + + TaprootPubkeySpend StandardWitnessType = 21 + + TaprootScriptSpend StandardWitnessType = 22 ) // String returns a human readable version of the target WitnessType. @@ -245,6 +249,12 @@ func (wt StandardWitnessType) String() string { case LeaseHtlcAcceptedSuccessSecondLevel: return "LeaseHtlcAcceptedSuccessSecondLevel" + case TaprootPubkeySpend: + return "TaprootPubkeySpend" + + case TaprootScriptSpend: + return "TaprootScriptSpend" + default: return fmt.Sprintf("Unknown WitnessType: %v", uint32(wt)) } @@ -388,6 +398,10 @@ func (wt StandardWitnessType) WitnessGenerator(signer Signer, case WitnessKeyHash: fallthrough + case TaprootPubkeySpend: + fallthrough + case TaprootScriptSpend: + fallthrough case NestedWitnessKeyHash: return signer.ComputeInputScript(tx, desc) @@ -494,6 +508,13 @@ func (wt StandardWitnessType) SizeUpperBound() (int, bool, error) { // The revocation output of a second level output of an HTLC. case HtlcSecondLevelRevoke: return ToLocalPenaltyWitnessSize, false, nil + + case TaprootPubkeySpend: + return TaprootKeyPathWitnessSize, false, nil + + case TaprootScriptSpend: + return 0, false, fmt.Errorf("taproot script send not " + + "implemented yet") } return 0, false, fmt.Errorf("unexpected witness type: %v", wt) diff --git a/lnrpc/lightning.pb.go b/lnrpc/lightning.pb.go index 962dd9dd825a..e853ccc99b17 100644 --- a/lnrpc/lightning.pb.go +++ b/lnrpc/lightning.pb.go @@ -25,6 +25,7 @@ const ( // //- `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0) //- `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1) +//- `p2tr`: Pay to taproot pubkey (`TAPROOT_PUBKEY` = 4) type AddressType int32 const ( @@ -32,6 +33,8 @@ const ( AddressType_NESTED_PUBKEY_HASH AddressType = 1 AddressType_UNUSED_WITNESS_PUBKEY_HASH AddressType = 2 AddressType_UNUSED_NESTED_PUBKEY_HASH AddressType = 3 + AddressType_TAPROOT_PUBKEY AddressType = 4 + AddressType_UNUSED_TAPROOT_PUBKEY AddressType = 5 ) // Enum value maps for AddressType. @@ -41,12 +44,16 @@ var ( 1: "NESTED_PUBKEY_HASH", 2: "UNUSED_WITNESS_PUBKEY_HASH", 3: "UNUSED_NESTED_PUBKEY_HASH", + 4: "TAPROOT_PUBKEY", + 5: "UNUSED_TAPROOT_PUBKEY", } AddressType_value = map[string]int32{ "WITNESS_PUBKEY_HASH": 0, "NESTED_PUBKEY_HASH": 1, "UNUSED_WITNESS_PUBKEY_HASH": 2, "UNUSED_NESTED_PUBKEY_HASH": 3, + "TAPROOT_PUBKEY": 4, + "UNUSED_TAPROOT_PUBKEY": 5, } ) @@ -18742,15 +18749,18 @@ var file_lightning_proto_rawDesc = []byte{ 0x12, 0x35, 0x0a, 0x16, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, - 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x2a, 0x7d, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, - 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x00, 0x12, - 0x16, 0x0a, 0x12, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, - 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x55, 0x4e, 0x55, 0x53, 0x45, - 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, - 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x02, 0x12, 0x1d, 0x0a, 0x19, 0x55, 0x4e, 0x55, 0x53, 0x45, - 0x44, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, - 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x2a, 0x78, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x2a, 0xac, 0x01, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x57, 0x49, 0x54, 0x4e, 0x45, + 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x00, + 0x12, 0x16, 0x0a, 0x12, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, + 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x55, 0x4e, 0x55, 0x53, + 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, + 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x02, 0x12, 0x1d, 0x0a, 0x19, 0x55, 0x4e, 0x55, 0x53, + 0x45, 0x44, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, + 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x41, 0x50, 0x52, 0x4f, + 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x04, 0x12, 0x19, 0x0a, 0x15, 0x55, + 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, + 0x42, 0x4b, 0x45, 0x59, 0x10, 0x05, 0x2a, 0x78, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, diff --git a/lnrpc/lightning.proto b/lnrpc/lightning.proto index c2c670e75471..ef5f6fedce45 100644 --- a/lnrpc/lightning.proto +++ b/lnrpc/lightning.proto @@ -1102,12 +1102,15 @@ message ListUnspentResponse { - `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0) - `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1) +- `p2tr`: Pay to taproot pubkey (`TAPROOT_PUBKEY` = 4) */ enum AddressType { WITNESS_PUBKEY_HASH = 0; NESTED_PUBKEY_HASH = 1; UNUSED_WITNESS_PUBKEY_HASH = 2; UNUSED_NESTED_PUBKEY_HASH = 3; + TAPROOT_PUBKEY = 4; + UNUSED_TAPROOT_PUBKEY = 5; } message NewAddressRequest { diff --git a/lnrpc/lightning.swagger.json b/lnrpc/lightning.swagger.json index 10220e66127a..f228ae722996 100644 --- a/lnrpc/lightning.swagger.json +++ b/lnrpc/lightning.swagger.json @@ -1801,7 +1801,9 @@ "WITNESS_PUBKEY_HASH", "NESTED_PUBKEY_HASH", "UNUSED_WITNESS_PUBKEY_HASH", - "UNUSED_NESTED_PUBKEY_HASH" + "UNUSED_NESTED_PUBKEY_HASH", + "TAPROOT_PUBKEY", + "UNUSED_TAPROOT_PUBKEY" ], "default": "WITNESS_PUBKEY_HASH" }, @@ -2923,10 +2925,12 @@ "WITNESS_PUBKEY_HASH", "NESTED_PUBKEY_HASH", "UNUSED_WITNESS_PUBKEY_HASH", - "UNUSED_NESTED_PUBKEY_HASH" + "UNUSED_NESTED_PUBKEY_HASH", + "TAPROOT_PUBKEY", + "UNUSED_TAPROOT_PUBKEY" ], "default": "WITNESS_PUBKEY_HASH", - "description": "- `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0)\n- `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1)", + "description": "- `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0)\n- `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1)\n- `p2tr`: Pay to taproot pubkey (`TAPROOT_PUBKEY` = 4)", "title": "`AddressType` has to be one of:" }, "lnrpcAmount": { diff --git a/lnrpc/marshall_utils.go b/lnrpc/marshall_utils.go index 0af5b43f0282..2d7171552647 100644 --- a/lnrpc/marshall_utils.go +++ b/lnrpc/marshall_utils.go @@ -94,6 +94,9 @@ func MarshalUtxos(utxos []*lnwallet.Utxo, activeNetParams *chaincfg.Params) ( case lnwallet.NestedWitnessPubKey: addrType = AddressType_NESTED_PUBKEY_HASH + case lnwallet.TaprootPubkey: + addrType = AddressType_TAPROOT_PUBKEY + case lnwallet.UnknownAddressType: continue diff --git a/lnrpc/walletrpc/walletkit.pb.go b/lnrpc/walletrpc/walletkit.pb.go index 4a1686da275a..26c7f8a88a0d 100644 --- a/lnrpc/walletrpc/walletkit.pb.go +++ b/lnrpc/walletrpc/walletkit.pb.go @@ -29,6 +29,7 @@ const ( AddressType_WITNESS_PUBKEY_HASH AddressType = 1 AddressType_NESTED_WITNESS_PUBKEY_HASH AddressType = 2 AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH AddressType = 3 + AddressType_TAPROOT_PUBKEY AddressType = 4 ) // Enum value maps for AddressType. @@ -38,12 +39,14 @@ var ( 1: "WITNESS_PUBKEY_HASH", 2: "NESTED_WITNESS_PUBKEY_HASH", 3: "HYBRID_NESTED_WITNESS_PUBKEY_HASH", + 4: "TAPROOT_PUBKEY", } AddressType_value = map[string]int32{ "UNKNOWN": 0, "WITNESS_PUBKEY_HASH": 1, "NESTED_WITNESS_PUBKEY_HASH": 2, "HYBRID_NESTED_WITNESS_PUBKEY_HASH": 3, + "TAPROOT_PUBKEY": 4, } ) @@ -3191,140 +3194,141 @@ var file_walletrpc_walletkit_proto_rawDesc = []byte{ 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x52, 0x0b, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, - 0x55, 0x74, 0x78, 0x6f, 0x73, 0x2a, 0x7a, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, - 0x00, 0x12, 0x17, 0x0a, 0x13, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, - 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x4e, 0x45, - 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, - 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x02, 0x12, 0x25, 0x0a, 0x21, 0x48, 0x59, - 0x42, 0x52, 0x49, 0x44, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, - 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, - 0x03, 0x2a, 0x99, 0x03, 0x0a, 0x0b, 0x57, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x57, 0x49, 0x54, - 0x4e, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, - 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x5f, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x01, - 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x4e, - 0x4f, 0x5f, 0x44, 0x45, 0x4c, 0x41, 0x59, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, - 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x03, - 0x12, 0x17, 0x0a, 0x13, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, - 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x48, 0x54, 0x4c, - 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, - 0x45, 0x10, 0x05, 0x12, 0x25, 0x0a, 0x21, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, - 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f, - 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x06, 0x12, 0x26, 0x0a, 0x22, 0x48, 0x54, - 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, - 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, - 0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, - 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, - 0x54, 0x10, 0x08, 0x12, 0x20, 0x0a, 0x1c, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, - 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x53, 0x55, 0x43, 0x43, - 0x45, 0x53, 0x53, 0x10, 0x09, 0x12, 0x1c, 0x0a, 0x18, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, - 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, - 0x45, 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x4b, - 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0b, 0x12, 0x1b, 0x0a, 0x17, 0x4e, 0x45, 0x53, - 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, - 0x48, 0x41, 0x53, 0x48, 0x10, 0x0c, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, - 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x10, 0x0d, 0x32, 0xf7, 0x0b, - 0x0a, 0x09, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x4b, 0x69, 0x74, 0x12, 0x4c, 0x0a, 0x0b, 0x4c, - 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x65, 0x61, - 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, 0x61, - 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x4c, - 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0d, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, - 0x4e, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x6f, 0x72, 0x12, 0x38, 0x0a, 0x09, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x12, - 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, - 0x61, 0x74, 0x6f, 0x72, 0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, - 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x3b, 0x0a, 0x08, - 0x4e, 0x65, 0x78, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x17, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, - 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x4c, 0x69, 0x73, - 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x49, 0x6d, - 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, - 0x0a, 0x0f, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, - 0x79, 0x12, 0x21, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, - 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x12, 0x50, 0x75, 0x62, 0x6c, - 0x69, 0x73, 0x68, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, - 0x73, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, - 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, - 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4c, 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, - 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, - 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, - 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, - 0x0a, 0x0d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x12, - 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x40, 0x0a, 0x07, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x12, 0x19, 0x2e, + 0x55, 0x74, 0x78, 0x6f, 0x73, 0x2a, 0x8e, 0x01, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, + 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, + 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x4e, + 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, + 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x02, 0x12, 0x25, 0x0a, 0x21, 0x48, + 0x59, 0x42, 0x52, 0x49, 0x44, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, + 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, + 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, + 0x42, 0x4b, 0x45, 0x59, 0x10, 0x04, 0x2a, 0x99, 0x03, 0x0a, 0x0b, 0x57, 0x69, 0x74, 0x6e, 0x65, + 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, + 0x4e, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x43, + 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x5f, 0x4c, + 0x4f, 0x43, 0x4b, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, + 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x5f, 0x44, 0x45, 0x4c, 0x41, 0x59, 0x10, 0x02, 0x12, 0x15, + 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x56, + 0x4f, 0x4b, 0x45, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, + 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x04, 0x12, 0x18, + 0x0a, 0x14, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, + 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x05, 0x12, 0x25, 0x0a, 0x21, 0x48, 0x54, 0x4c, 0x43, + 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, + 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x06, 0x12, + 0x26, 0x0a, 0x22, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, + 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, + 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x48, 0x54, 0x4c, 0x43, 0x5f, + 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x54, + 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x08, 0x12, 0x20, 0x0a, 0x1c, 0x48, 0x54, 0x4c, 0x43, + 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, + 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x09, 0x12, 0x1c, 0x0a, 0x18, 0x48, 0x54, + 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, + 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x57, 0x49, 0x54, 0x4e, + 0x45, 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0b, 0x12, 0x1b, + 0x0a, 0x17, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, + 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0c, 0x12, 0x15, 0x0a, 0x11, 0x43, + 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, + 0x10, 0x0d, 0x32, 0xf7, 0x0b, 0x0a, 0x09, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x4b, 0x69, 0x74, + 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, + 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, + 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, + 0x0a, 0x0b, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1d, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, + 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1f, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, + 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, + 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x49, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1c, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, + 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, + 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0d, 0x44, + 0x65, 0x72, 0x69, 0x76, 0x65, 0x4e, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x1a, + 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x38, 0x0a, 0x09, 0x44, 0x65, 0x72, 0x69, 0x76, + 0x65, 0x4b, 0x65, 0x79, 0x12, 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, + 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, + 0x72, 0x12, 0x3b, 0x0a, 0x08, 0x4e, 0x65, 0x78, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x16, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, + 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x1e, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x52, 0x0a, 0x0d, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, + 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, + 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, + 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, + 0x12, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1a, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x4f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, + 0x65, 0x46, 0x65, 0x65, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, + 0x65, 0x65, 0x70, 0x73, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x07, 0x42, 0x75, 0x6d, 0x70, 0x46, + 0x65, 0x65, 0x12, 0x19, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, + 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, - 0x70, 0x73, 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x5b, 0x0a, 0x10, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x08, - 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x43, 0x0a, 0x08, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1a, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x4c, 0x69, 0x73, + 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x10, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x43, 0x0a, 0x08, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1a, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, - 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, - 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x08, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, + 0x62, 0x74, 0x12, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x50, + 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x46, + 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1e, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, + 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, + 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x31, 0x5a, 0x2f, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, + 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/lnrpc/walletrpc/walletkit.proto b/lnrpc/walletrpc/walletkit.proto index 84ff7a688854..40f7f840a37c 100644 --- a/lnrpc/walletrpc/walletkit.proto +++ b/lnrpc/walletrpc/walletkit.proto @@ -324,6 +324,7 @@ enum AddressType { WITNESS_PUBKEY_HASH = 1; NESTED_WITNESS_PUBKEY_HASH = 2; HYBRID_NESTED_WITNESS_PUBKEY_HASH = 3; + TAPROOT_PUBKEY = 4; } message Account { // The name used to identify the account. diff --git a/lnrpc/walletrpc/walletkit.swagger.json b/lnrpc/walletrpc/walletkit.swagger.json index c40c6669123c..00a528f216ab 100644 --- a/lnrpc/walletrpc/walletkit.swagger.json +++ b/lnrpc/walletrpc/walletkit.swagger.json @@ -52,7 +52,8 @@ "UNKNOWN", "WITNESS_PUBKEY_HASH", "NESTED_WITNESS_PUBKEY_HASH", - "HYBRID_NESTED_WITNESS_PUBKEY_HASH" + "HYBRID_NESTED_WITNESS_PUBKEY_HASH", + "TAPROOT_PUBKEY" ], "default": "UNKNOWN" } @@ -695,10 +696,12 @@ "WITNESS_PUBKEY_HASH", "NESTED_PUBKEY_HASH", "UNUSED_WITNESS_PUBKEY_HASH", - "UNUSED_NESTED_PUBKEY_HASH" + "UNUSED_NESTED_PUBKEY_HASH", + "TAPROOT_PUBKEY", + "UNUSED_TAPROOT_PUBKEY" ], "default": "WITNESS_PUBKEY_HASH", - "description": "- `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0)\n- `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1)", + "description": "- `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0)\n- `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1)\n- `p2tr`: Pay to taproot pubkey (`TAPROOT_PUBKEY` = 4)", "title": "`AddressType` has to be one of:" }, "lnrpcOutPoint": { @@ -962,7 +965,8 @@ "UNKNOWN", "WITNESS_PUBKEY_HASH", "NESTED_WITNESS_PUBKEY_HASH", - "HYBRID_NESTED_WITNESS_PUBKEY_HASH" + "HYBRID_NESTED_WITNESS_PUBKEY_HASH", + "TAPROOT_PUBKEY" ], "default": "UNKNOWN" }, diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index da952a743d2e..1be863edd552 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -1326,6 +1326,9 @@ func marshalWalletAccount(internalScope waddrmgr.KeyScope, case waddrmgr.KeyScopeBIP0084: addrType = AddressType_WITNESS_PUBKEY_HASH + case waddrmgr.KeyScopeBIP0086: + addrType = AddressType_TAPROOT_PUBKEY + case internalScope: addrType = AddressType_WITNESS_PUBKEY_HASH @@ -1384,6 +1387,10 @@ func (w *WalletKit) ListAccounts(ctx context.Context, keyScope := waddrmgr.KeyScopeBIP0049Plus keyScopeFilter = &keyScope + case AddressType_TAPROOT_PUBKEY: + keyScope := waddrmgr.KeyScopeBIP0086 + keyScopeFilter = &keyScope + default: return nil, fmt.Errorf("unhandled address type %v", req.AddressType) } @@ -1439,6 +1446,10 @@ func parseAddrType(addrType AddressType, addrTyp := waddrmgr.WitnessPubKey return &addrTyp, nil + case AddressType_TAPROOT_PUBKEY: + addrTyp := waddrmgr.TaprootPubKey + return &addrTyp, nil + default: return nil, fmt.Errorf("unhandled address type %v", addrType) } diff --git a/lntest/itest/lnd_taproot_test.go b/lntest/itest/lnd_taproot_test.go new file mode 100644 index 000000000000..a821f2bcfb0c --- /dev/null +++ b/lntest/itest/lnd_taproot_test.go @@ -0,0 +1,81 @@ +package itest + +import ( + "context" + "time" + + "github.com/btcsuite/btcd/btcutil" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lntest" + "github.com/stretchr/testify/require" +) + +// testTaproot ensures that the daemon can send to and spend from taproot (p2tr) +// outputs. +func testTaproot(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + + // 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, net.Alice) + + // Let's create a p2tr address now. + p2trResp, err := net.Alice.NewAddress(ctxt, &lnrpc.NewAddressRequest{ + Type: lnrpc.AddressType_TAPROOT_PUBKEY, + }) + require.NoError(t.t, err) + + // Assert this is a segwit v1 address that starts with bcrt1p. + require.Contains( + t.t, p2trResp.Address, net.Miner.ActiveNet.Bech32HRPSegwit+"1p", + ) + + // Send the coins from Alice's wallet to her own, but to the new p2tr + // address. + _, err = net.Alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{ + Addr: p2trResp.Address, + Amount: 0.5 * btcutil.SatoshiPerBitcoin, + }) + require.NoError(t.t, err) + + txid, err := waitForTxInMempool(net.Miner.Client, 5*time.Second) + require.NoError(t.t, err) + + // Wait until bob has seen the tx and considers it as owned. + p2trOutputIndex := getOutputIndex(t, net, txid, p2trResp.Address) + op := &lnrpc.OutPoint{ + TxidBytes: txid[:], + OutputIndex: uint32(p2trOutputIndex), + } + assertWalletUnspent(t, net.Alice, op) + + // Mine a block to clean up the mempool. + mineBlocks(t, net, 1, 1) + + // Let's sweep the whole wallet to a new p2tr address, making sure we + // can sign transactions with v0 and v1 inputs. + p2trResp, err = net.Alice.NewAddress(ctxt, &lnrpc.NewAddressRequest{ + Type: lnrpc.AddressType_TAPROOT_PUBKEY, + }) + require.NoError(t.t, err) + + _, err = net.Alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{ + Addr: p2trResp.Address, + SendAll: true, + }) + require.NoError(t.t, err) + + // Wait until the wallet cleaning sweep tx is found. + txid, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) + require.NoError(t.t, err) + + // Wait until bob has seen the tx and considers it as owned. + p2trOutputIndex = getOutputIndex(t, net, txid, p2trResp.Address) + op = &lnrpc.OutPoint{ + TxidBytes: txid[:], + OutputIndex: uint32(p2trOutputIndex), + } + assertWalletUnspent(t, net.Alice, op) +} diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index b53e751e8a53..064d212513bf 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -379,4 +379,8 @@ var allTestCases = []*testCase{ name: "remote signer", test: testRemoteSigner, }, + { + name: "taproot", + test: testTaproot, + }, } diff --git a/lntest/itest/utils.go b/lntest/itest/utils.go index 5c11f9b9bcd9..503cfd4309d6 100644 --- a/lntest/itest/utils.go +++ b/lntest/itest/utils.go @@ -8,7 +8,9 @@ import ( "time" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/input" @@ -463,3 +465,31 @@ func findTxAtHeight(t *harnessTest, height int32, return nil } + +// getOutputIndex returns the output index of the given address in the given +// transaction. +func getOutputIndex(t *harnessTest, net *lntest.NetworkHarness, + txid *chainhash.Hash, addr string) int { + + t.t.Helper() + + // We'll then extract the raw transaction from the mempool in order to + // determine the index of the p2tr output. + tx, err := net.Miner.Client.GetRawTransaction(txid) + require.NoError(t.t, err) + + p2trOutputIndex := -1 + for i, txOut := range tx.MsgTx().TxOut { + _, addrs, _, err := txscript.ExtractPkScriptAddrs( + txOut.PkScript, net.Miner.ActiveNet, + ) + require.NoError(t.t, err) + + if addrs[0].String() == addr { + p2trOutputIndex = i + } + } + require.Greater(t.t, p2trOutputIndex, -1) + + return p2trOutputIndex +} diff --git a/lntest/mock/walletcontroller.go b/lntest/mock/walletcontroller.go index 81abefbdf75a..3831cc27a62d 100644 --- a/lntest/mock/walletcontroller.go +++ b/lntest/mock/walletcontroller.go @@ -52,6 +52,16 @@ func (w *WalletController) FetchInputInfo( return utxo, nil } +// FetchPrevOutput attempts to fetch the previous output referenced by +// the passed outpoint. A nil value will be returned if the passed +// outpoint doesn't exist. +func (w *WalletController) FetchPrevOutput(wire.OutPoint) *wire.TxOut { + return &wire.TxOut{ + Value: 10 * btcutil.SatoshiPerBitcoin, + PkScript: []byte("dummy"), + } +} + // ScriptForOutput returns the address, witness program and redeem script for a // given UTXO. An error is returned if the UTXO does not belong to our wallet or // it is not a managed pubKey address. diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index c71693ff8d9c..decf73027915 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -457,6 +457,8 @@ func (b *BtcWallet) keyScopeForAccountAddr(accountName string, addrKeyScope = waddrmgr.KeyScopeBIP0084 case lnwallet.NestedWitnessPubKey: addrKeyScope = waddrmgr.KeyScopeBIP0049Plus + case lnwallet.TaprootPubkey: + addrKeyScope = waddrmgr.KeyScopeBIP0086 default: return waddrmgr.KeyScope{}, 0, fmt.Errorf("unknown address type") @@ -561,7 +563,7 @@ func (b *BtcWallet) ListAccounts(name string, // Only the name filter was provided. case name != "" && keyScope == nil: // If the name corresponds to the default or imported accounts, - // we'll return them for both of our supported key scopes. + // we'll return them for all our supported key scopes. if name == lnwallet.DefaultAccountName || name == waddrmgr.ImportedAddrAccountName { @@ -580,6 +582,14 @@ func (b *BtcWallet) ListAccounts(name string, return nil, err } res = append(res, a2) + + a3, err := b.wallet.AccountPropertiesByName( + waddrmgr.KeyScopeBIP0086, name, + ) + if err != nil { + return nil, err + } + res = append(res, a3) break } @@ -628,6 +638,15 @@ func (b *BtcWallet) ListAccounts(name string, res = append(res, &account.AccountProperties) } + accounts, err = b.wallet.Accounts(waddrmgr.KeyScopeBIP0086) + if err != nil { + return nil, err + } + for _, account := range accounts.Accounts { + account := account + res = append(res, &account.AccountProperties) + } + accounts, err = b.wallet.Accounts(waddrmgr.KeyScope{ Purpose: keychain.BIP0043Purpose, Coin: b.cfg.CoinType, @@ -898,10 +917,13 @@ func (b *BtcWallet) ListUnspentWitness(minConfs, maxConfs int32, // wallet are nested p2pkh. We can't check the redeem script because // the btcwallet service does not include it. addressType = lnwallet.NestedWitnessPubKey + } else if txscript.IsPayToTaproot(pkScript) { + addressType = lnwallet.TaprootPubkey } if addressType == lnwallet.WitnessPubKey || - addressType == lnwallet.NestedWitnessPubKey { + addressType == lnwallet.NestedWitnessPubKey || + addressType == lnwallet.TaprootPubkey { txid, err := chainhash.NewHashFromStr(output.TxID) if err != nil { diff --git a/lnwallet/btcwallet/psbt.go b/lnwallet/btcwallet/psbt.go index 0878fe71787d..ca19e1325097 100644 --- a/lnwallet/btcwallet/psbt.go +++ b/lnwallet/btcwallet/psbt.go @@ -9,6 +9,7 @@ import ( "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/btcsuite/btcwallet/wallet" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -107,7 +108,8 @@ func (b *BtcWallet) SignPsbt(packet *psbt.Packet) error { // there are inputs that we don't know how to sign, we won't return any // error. So it's possible we're not the final signer. tx := packet.UnsignedTx - sigHashes := txscript.NewTxSigHashesV0Only(tx) + prevOutputFetcher := wallet.PsbtPrevOutputFetcher(packet) + sigHashes := txscript.NewTxSigHashes(tx, prevOutputFetcher) for idx := range tx.TxIn { in := packet.Inputs[idx] diff --git a/lnwallet/btcwallet/signer.go b/lnwallet/btcwallet/signer.go index c9c3c6c44f61..4a92efb179df 100644 --- a/lnwallet/btcwallet/signer.go +++ b/lnwallet/btcwallet/signer.go @@ -5,6 +5,7 @@ import ( "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/chaincfg/chainhash" @@ -51,6 +52,18 @@ func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo, erro }, nil } +// FetchPrevOutput attempts to fetch the previous output referenced by +// the passed outpoint. A nil value will be returned if the passed +// outpoint doesn't exist. +func (b *BtcWallet) FetchPrevOutput(op wire.OutPoint) *wire.TxOut { + _, txOut, _, _, err := b.wallet.FetchInputInfo(&op) + if err != nil { + return nil + } + + return txOut +} + // ScriptForOutput returns the address, witness program and redeem script for a // given UTXO. An error is returned if the UTXO does not belong to our wallet or // it is not a managed pubKey address. @@ -129,7 +142,8 @@ func (b *BtcWallet) deriveKeyByBIP32Path(path []uint32) (*btcec.PrivateKey, // Is it a standard, BIP defined purpose that the wallet understands? case waddrmgr.KeyScopeBIP0044.Purpose, waddrmgr.KeyScopeBIP0049Plus.Purpose, - waddrmgr.KeyScopeBIP0084.Purpose: + waddrmgr.KeyScopeBIP0084.Purpose, + waddrmgr.KeyScopeBIP0086.Purpose: // We're going to continue below the switch statement to avoid // unnecessary indentation for this default case. @@ -379,6 +393,50 @@ func (b *BtcWallet) SignOutputRaw(tx *wire.MsgTx, func (b *BtcWallet) ComputeInputScript(tx *wire.MsgTx, signDesc *input.SignDescriptor) (*input.Script, error) { + if txscript.IsPayToTaproot(signDesc.Output.PkScript) { + _, addrs, _, err := txscript.ExtractPkScriptAddrs( + signDesc.Output.PkScript, b.netParams, + ) + if err != nil { + return nil, fmt.Errorf("error parsing addr: %v", err) + } + privKey, err := b.wallet.PrivKeyForAddress(addrs[0]) + if err != nil { + return nil, fmt.Errorf("error fetching priv key: %v", + err) + } + pubKey := privKey.PubKey() + + tapKey := txscript.ComputeTaprootKeyNoScript(pubKey) + p2trAddr, err := btcutil.NewAddressTaproot( + schnorr.SerializePubKey(tapKey), b.netParams, + ) + if err != nil { + return nil, err + } + + // With the concrete address type, we can now generate the + // corresponding witness program to be used to generate a valid + // witness which will allow us to spend this output. + witnessProgram, err := txscript.PayToAddrScript(p2trAddr) + if err != nil { + return nil, err + } + witnessScript, err := txscript.TaprootWitnessSignature( + tx, signDesc.SigHashes, signDesc.InputIndex, + signDesc.Output.Value, witnessProgram, + txscript.SigHashDefault, privKey, + ) + if err != nil { + return nil, err + } + + return &input.Script{ + Witness: witnessScript, + SigScript: nil, + }, nil + } + // If a tweak (single or double) is specified, then we'll need to use // this tweak to derive the final private key to be used for signing // this output. diff --git a/lnwallet/btcwallet/signer_test.go b/lnwallet/btcwallet/signer_test.go index b946f3acfa73..030c576a4706 100644 --- a/lnwallet/btcwallet/signer_test.go +++ b/lnwallet/btcwallet/signer_test.go @@ -12,6 +12,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcwallet/chain" + "github.com/btcsuite/btcwallet/waddrmgr" "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/lnwallet" "github.com/stretchr/testify/require" @@ -32,6 +33,11 @@ var ( // which is a special case for the BIP49/84 addresses in btcwallet). firstAddress = "bcrt1qgdlgjc5ede7fjv350wcjqat80m0zsmfaswsj9p" + // firstAddressTaproot is the first address that we should get from the + // wallet when deriving a taproot address. + firstAddressTaproot = "bcrt1ps8c222fgysvnsj2m8hxk8khy6wthcrhv9va9z3t4" + + "h3qeyz65sh4qqwvdgc" + testCases = []struct { name string path []uint32 @@ -167,6 +173,25 @@ func TestBip32KeyDerivation(t *testing.T) { } } +func TestScriptImport(t *testing.T) { + netParams := &chaincfg.RegressionNetParams + w, cleanup := newTestWallet(t, netParams, seedBytes) + defer cleanup() + + firstDerivedAddr, err := w.NewAddress( + lnwallet.TaprootPubkey, false, lnwallet.DefaultAccountName, + ) + require.NoError(t, err) + require.Equal(t, firstAddressTaproot, firstDerivedAddr.String()) + + _, err = w.InternalWallet().Manager.FetchScopedKeyManager( + waddrmgr.KeyScopeBIP0086, + ) + require.NoError(t, err) + + // mgr.ImportWitnessScript() +} + func newTestWallet(t *testing.T, netParams *chaincfg.Params, seedBytes []byte) (*BtcWallet, func()) { diff --git a/lnwallet/chanfunding/assembler.go b/lnwallet/chanfunding/assembler.go index 4e6e62d259fc..0806f08f1d0b 100644 --- a/lnwallet/chanfunding/assembler.go +++ b/lnwallet/chanfunding/assembler.go @@ -17,6 +17,11 @@ type CoinSource interface { // based on its outpoint. If the coin isn't under the control of the // backing CoinSource, then an error should be returned. CoinFromOutPoint(wire.OutPoint) (*Coin, error) + + // FetchPrevOutput attempts to fetch the previous output referenced by + // the passed outpoint. A nil value will be returned if the passed + // outpoint doesn't exist. + FetchPrevOutput(wire.OutPoint) *wire.TxOut } // CoinSelectionLocker is an interface that allows the caller to perform an diff --git a/lnwallet/chanfunding/wallet_assembler.go b/lnwallet/chanfunding/wallet_assembler.go index c3e0be8609c1..fa204e12f5c4 100644 --- a/lnwallet/chanfunding/wallet_assembler.go +++ b/lnwallet/chanfunding/wallet_assembler.go @@ -108,8 +108,11 @@ func (f *FullIntent) CompileFundingTx(extraInputs []*wire.TxIn, // Next, sign all inputs that are ours, collecting the signatures in // order of the inputs. signDesc := input.SignDescriptor{ - HashType: txscript.SigHashAll, - SigHashes: txscript.NewTxSigHashesV0Only(fundingTx), + HashType: txscript.SigHashAll, + SigHashes: txscript.NewTxSigHashes( + fundingTx, f.coinSource, + ), + PrevOutputFetcher: f.coinSource, } for i, txIn := range fundingTx.TxIn { // We can only sign this input if it's ours, so we'll ask the diff --git a/lnwallet/interface.go b/lnwallet/interface.go index b19066583822..1cf2b56a6025 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -41,6 +41,9 @@ const ( // NestedWitnessPubKey represents a p2sh output which is itself a // nested p2wkh output. NestedWitnessPubKey + + // TaprootPubkey represents a p2tr key path spending address. + TaprootPubkey ) var ( @@ -161,6 +164,11 @@ type WalletController interface { // a non-nil error value of ErrNotMine should be returned instead. FetchInputInfo(prevOut *wire.OutPoint) (*Utxo, error) + // FetchPrevOutput attempts to fetch the previous output referenced by + // the passed outpoint. A nil value will be returned if the passed + // outpoint doesn't exist. + FetchPrevOutput(wire.OutPoint) *wire.TxOut + // ScriptForOutput returns the address, witness program and redeem // script for a given UTXO. An error is returned if the UTXO does not // belong to our wallet or it is not a managed pubKey address. diff --git a/lnwallet/rpcwallet/rpcwallet.go b/lnwallet/rpcwallet/rpcwallet.go index 280f6067db4e..0366a0bfd335 100644 --- a/lnwallet/rpcwallet/rpcwallet.go +++ b/lnwallet/rpcwallet/rpcwallet.go @@ -131,8 +131,11 @@ func (r *RPCKeyRing) SendOutputs(outputs []*wire.TxOut, // We know at this point that we only have inputs from our own wallet. // So we can just compute the input script using the remote signer. signDesc := input.SignDescriptor{ - HashType: txscript.SigHashAll, - SigHashes: txscript.NewTxSigHashesV0Only(tx), + HashType: txscript.SigHashAll, + SigHashes: txscript.NewTxSigHashes( + tx, r.WalletController, + ), + PrevOutputFetcher: r.WalletController, } for i, txIn := range tx.TxIn { // We can only sign this input if it's ours, so we'll ask the diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 9f8cc5a9e0e5..d5da9378cba6 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -2219,6 +2219,21 @@ func (c *CoinSource) CoinFromOutPoint(op wire.OutPoint) (*chanfunding.Coin, erro }, nil } +// FetchPrevOutput attempts to fetch the previous output referenced by +// the passed outpoint. A nil value will be returned if the passed +// outpoint doesn't exist. +func (c *CoinSource) FetchPrevOutput(op wire.OutPoint) *wire.TxOut { + inputInfo, err := c.wallet.FetchInputInfo(&op) + if err != nil { + return nil + } + + return &wire.TxOut{ + Value: int64(inputInfo.Value), + PkScript: inputInfo.PkScript, + } +} + // shimKeyRing is a wrapper struct that's used to provide the proper multi-sig // key for an initiated external funding flow. type shimKeyRing struct { diff --git a/rpcserver.go b/rpcserver.go index b367172a09ac..74c0446257ec 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1496,6 +1496,14 @@ func (r *rpcServer) NewAddress(ctx context.Context, return nil, err } + case lnrpc.AddressType_TAPROOT_PUBKEY: + addr, err = r.server.cc.Wallet.NewAddress( + lnwallet.TaprootPubkey, false, account, + ) + if err != nil { + return nil, err + } + case lnrpc.AddressType_UNUSED_WITNESS_PUBKEY_HASH: addr, err = r.server.cc.Wallet.LastUnusedAddress( lnwallet.WitnessPubKey, account, @@ -1511,6 +1519,14 @@ func (r *rpcServer) NewAddress(ctx context.Context, if err != nil { return nil, err } + + case lnrpc.AddressType_UNUSED_TAPROOT_PUBKEY: + addr, err = r.server.cc.Wallet.LastUnusedAddress( + lnwallet.TaprootPubkey, account, + ) + if err != nil { + return nil, err + } } rpcsLog.Debugf("[newaddress] account=%v type=%v addr=%v", account, diff --git a/sweep/sweeper_test.go b/sweep/sweeper_test.go index 5ae22748d38d..351641fd4a92 100644 --- a/sweep/sweeper_test.go +++ b/sweep/sweeper_test.go @@ -355,7 +355,7 @@ func assertTxFeeRate(t *testing.T, tx *wire.MsgTx, outputAmt := tx.TxOut[0].Value fee := btcutil.Amount(inputAmt - outputAmt) - _, estimator := getWeightEstimate(inputs, nil, 0) + _, estimator := getWeightEstimate(inputs, nil, 0, nil) txWeight := estimator.weight() expectedFee := expectedFeeRate.FeeForWeight(int64(txWeight)) diff --git a/sweep/txgenerator.go b/sweep/txgenerator.go index 02d958c48130..2efcf83a07f9 100644 --- a/sweep/txgenerator.go +++ b/sweep/txgenerator.go @@ -139,7 +139,9 @@ func createSweepTx(inputs []input.Input, outputs []*wire.TxOut, feePerKw chainfee.SatPerKWeight, signer input.Signer) (*wire.MsgTx, error) { - inputs, estimator := getWeightEstimate(inputs, outputs, feePerKw) + inputs, estimator := getWeightEstimate( + inputs, outputs, feePerKw, changePkScript, + ) txFee := estimator.fee() var ( @@ -262,13 +264,18 @@ func createSweepTx(inputs []input.Input, outputs []*wire.TxOut, return nil, err } - hashCache := txscript.NewTxSigHashesV0Only(sweepTx) + prevInputFetcher, err := input.MultiPrevOutFetcher(inputs) + if err != nil { + return nil, fmt.Errorf("error creating prev input fetcher "+ + "for hash cache: %v", err) + } + hashCache := txscript.NewTxSigHashes(sweepTx, prevInputFetcher) // With all the inputs in place, use each output's unique input script // function to generate the final witness required for spending. addInputScript := func(idx int, tso input.Input) error { inputScript, err := tso.CraftInputScript( - signer, sweepTx, hashCache, idx, + signer, sweepTx, hashCache, prevInputFetcher, idx, ) if err != nil { return err @@ -305,7 +312,8 @@ func createSweepTx(inputs []input.Input, outputs []*wire.TxOut, // getWeightEstimate returns a weight estimate for the given inputs. // Additionally, it returns counts for the number of csv and cltv inputs. func getWeightEstimate(inputs []input.Input, outputs []*wire.TxOut, - feeRate chainfee.SatPerKWeight) ([]input.Input, *weightEstimator) { + feeRate chainfee.SatPerKWeight, outputPkScript []byte) ([]input.Input, + *weightEstimator) { // We initialize a weight estimator so we can accurately asses the // amount of fees we need to pay for this sweep transaction. @@ -327,7 +335,11 @@ func getWeightEstimate(inputs []input.Input, outputs []*wire.TxOut, // change output to the weight estimate regardless, since the estimated // fee will just be subtracted from this already dust output, and // trimmed. - weightEstimate.addP2WKHOutput() + if txscript.IsPayToTaproot(outputPkScript) { + weightEstimate.addOutput(&wire.TxOut{PkScript: outputPkScript}) + } else { + weightEstimate.addP2WKHOutput() + } // For each output, use its witness type to determine the estimate // weight of its witness, and add it to the proper set of spendable diff --git a/sweep/txgenerator_test.go b/sweep/txgenerator_test.go index f8a6b8ac4f0c..960da63d31e8 100644 --- a/sweep/txgenerator_test.go +++ b/sweep/txgenerator_test.go @@ -39,7 +39,7 @@ func TestWeightEstimate(t *testing.T) { )) } - _, estimator := getWeightEstimate(inputs, nil, 0) + _, estimator := getWeightEstimate(inputs, nil, 0, nil) weight := int64(estimator.weight()) if weight != expectedWeight { t.Fatalf("unexpected weight. expected %d but got %d.", diff --git a/sweep/walletsweep.go b/sweep/walletsweep.go index e541c190e4e5..795e163eb880 100644 --- a/sweep/walletsweep.go +++ b/sweep/walletsweep.go @@ -255,6 +255,9 @@ func CraftSweepAllTx(feeRate chainfee.SatPerKWeight, blockHeight uint32, case lnwallet.NestedWitnessPubKey: witnessType = input.NestedWitnessKeyHash + case lnwallet.TaprootPubkey: + witnessType = input.TaprootPubkeySpend + // All other output types we count as unknown and will fail to // sweep. default: diff --git a/watchtower/lookout/justice_descriptor.go b/watchtower/lookout/justice_descriptor.go index 5e21cda6bb0d..dac18b048d1b 100644 --- a/watchtower/lookout/justice_descriptor.go +++ b/watchtower/lookout/justice_descriptor.go @@ -2,6 +2,7 @@ package lookout import ( "errors" + "fmt" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" @@ -177,11 +178,11 @@ func (p *JusticeDescriptor) assembleJusticeTxn(txWeight int64, // First, construct add the breached inputs to our justice transaction // and compute the total amount that will be swept. var totalAmt btcutil.Amount - for _, input := range inputs { - totalAmt += btcutil.Amount(input.txOut.Value) + for _, inp := range inputs { + totalAmt += btcutil.Amount(inp.txOut.Value) justiceTxn.AddTxIn(&wire.TxIn{ - PreviousOutPoint: input.outPoint, - Sequence: input.sequence, + PreviousOutPoint: inp.outPoint, + Sequence: inp.sequence, }) } @@ -217,20 +218,22 @@ func (p *JusticeDescriptor) assembleJusticeTxn(txWeight int64, } // Attach each of the provided witnesses to the transaction. - for _, input := range inputs { + prevOutFetcher, err := prevOutFetcher(inputs) + if err != nil { + return nil, fmt.Errorf("error creating previous output "+ + "fetcher: %v", err) + } + for _, inp := range inputs { // Lookup the input's new post-sort position. - i := inputIndex[input.outPoint] - justiceTxn.TxIn[i].Witness = input.witness + i := inputIndex[inp.outPoint] + justiceTxn.TxIn[i].Witness = inp.witness // Validate the reconstructed witnesses to ensure they are valid // for the breached inputs. vm, err := txscript.NewEngine( - input.txOut.PkScript, justiceTxn, i, + inp.txOut.PkScript, justiceTxn, i, txscript.StandardVerifyFlags, - nil, nil, input.txOut.Value, - txscript.NewCannedPrevOutputFetcher( - input.txOut.PkScript, input.txOut.Value, - ), + nil, nil, inp.txOut.Value, prevOutFetcher, ) if err != nil { return nil, err @@ -345,3 +348,20 @@ func buildWitness(witnessStack [][]byte, witnessScript []byte) [][]byte { return witness } + +// prevOutFetcher returns a txscript.MultiPrevOutFetcher for the given set +// of inputs. +func prevOutFetcher(inputs []*breachedInput) (*txscript.MultiPrevOutFetcher, + error) { + + fetcher := txscript.NewMultiPrevOutFetcher(nil) + for _, inp := range inputs { + if inp.txOut == nil { + return nil, fmt.Errorf("missing input utxo information") + } + + fetcher.AddPrevOut(inp.outPoint, inp.txOut) + } + + return fetcher, nil +} diff --git a/watchtower/wtclient/backup_task.go b/watchtower/wtclient/backup_task.go index 8dbdcf09dc28..5c291d4c3745 100644 --- a/watchtower/wtclient/backup_task.go +++ b/watchtower/wtclient/backup_task.go @@ -263,10 +263,12 @@ func (t *backupTask) craftSessionPayload( // information. This will either be contain both the to-local and // to-remote outputs, or only be the to-local output. inputs := t.inputs() - for prevOutPoint, input := range inputs { + prevOutputFetcher := txscript.NewMultiPrevOutFetcher(nil) + for prevOutPoint, inp := range inputs { + prevOutputFetcher.AddPrevOut(prevOutPoint, inp.RequiredTxOut()) justiceTxn.AddTxIn(&wire.TxIn{ PreviousOutPoint: prevOutPoint, - Sequence: input.BlocksToMaturity(), + Sequence: inp.BlocksToMaturity(), }) } @@ -285,7 +287,7 @@ func (t *backupTask) craftSessionPayload( } // Construct a sighash cache to improve signing performance. - hashCache := txscript.NewTxSigHashesV0Only(justiceTxn) + hashCache := txscript.NewTxSigHashes(justiceTxn, prevOutputFetcher) // Since the transaction inputs could have been reordered as a result of // the BIP69 sort, create an index mapping each prevout to it's new @@ -304,7 +306,7 @@ func (t *backupTask) craftSessionPayload( // Construct the full witness required to spend this input. inputScript, err := inp.CraftInputScript( - signer, justiceTxn, hashCache, i, + signer, justiceTxn, hashCache, prevOutputFetcher, i, ) if err != nil { return hint, nil, err