Merge pull request #6006 from guggero/remote-signer-no-chain

Allow remote signer to run without chain backend
This commit is contained in:
Olaoluwa Osuntokun authored and GitHub committed 2022-01-07 17:37:16 -08:00
commit fd63301dc5
40 files changed
+2960 -934

No files matched your search

+16
View File
@@ -690,6 +690,22 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) {
return nil, nil, err return nil, nil, err
} }
} }
case "nochainbackend":
backend := &NoChainBackend{}
source := &NoChainSource{
BestBlockTime: time.Now(),
}
cc.ChainNotifier = backend
cc.ChainView = backend
cc.FeeEstimator = backend
cc.ChainSource = source
cc.HealthCheck = func() error {
return nil
}
default: default:
return nil, nil, fmt.Errorf("unknown node type: %s", return nil, nil, fmt.Errorf("unknown node type: %s",
homeChainConfig.Node) homeChainConfig.Node)
+215
View File
@@ -0,0 +1,215 @@
package chainreg
import (
"errors"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/routing/chainview"
)
var (
// defaultFee is the fee that is returned by NoChainBackend.
defaultFee = chainfee.FeePerKwFloor
// noChainBackendName is the backend name returned by NoChainBackend.
noChainBackendName = "nochainbackend"
// errNotImplemented is the error that is returned by NoChainBackend for
// any operation that is not supported by it. Such paths should in
// practice never been hit, so seeing this error either means a remote
// signing instance was used for an unsupported purpose or a previously
// forgotten edge case path was hit.
errNotImplemented = errors.New("not implemented in nochainbackend " +
"mode")
// noChainBackendBestHash is the chain hash of the chain tip that is
// returned by NoChainBackend.
noChainBackendBestHash = &chainhash.Hash{0x01}
// noChainBackendBestHeight is the best height that is returned by
// NoChainBackend.
noChainBackendBestHeight int32 = 1
)
// NoChainBackend is a mock implementation of the following interfaces:
// - chainview.FilteredChainView
// - chainntnfs.ChainNotifier
// - chainfee.Estimator
type NoChainBackend struct {
}
func (n *NoChainBackend) EstimateFeePerKW(uint32) (chainfee.SatPerKWeight,
error) {
return defaultFee, nil
}
func (n *NoChainBackend) RelayFeePerKW() chainfee.SatPerKWeight {
return defaultFee
}
func (n *NoChainBackend) RegisterConfirmationsNtfn(*chainhash.Hash, []byte,
uint32, uint32) (*chainntnfs.ConfirmationEvent, error) {
return nil, errNotImplemented
}
func (n *NoChainBackend) RegisterSpendNtfn(*wire.OutPoint, []byte,
uint32) (*chainntnfs.SpendEvent, error) {
return nil, errNotImplemented
}
func (n *NoChainBackend) RegisterBlockEpochNtfn(
*chainntnfs.BlockEpoch) (*chainntnfs.BlockEpochEvent, error) {
epochChan := make(chan *chainntnfs.BlockEpoch)
return &chainntnfs.BlockEpochEvent{
Epochs: epochChan,
Cancel: func() {
close(epochChan)
},
}, nil
}
func (n *NoChainBackend) Started() bool {
return true
}
func (n *NoChainBackend) FilteredBlocks() <-chan *chainview.FilteredBlock {
return make(chan *chainview.FilteredBlock)
}
func (n *NoChainBackend) DisconnectedBlocks() <-chan *chainview.FilteredBlock {
return make(chan *chainview.FilteredBlock)
}
func (n *NoChainBackend) UpdateFilter([]channeldb.EdgePoint, uint32) error {
return nil
}
func (n *NoChainBackend) FilterBlock(*chainhash.Hash) (*chainview.FilteredBlock,
error) {
return nil, errNotImplemented
}
func (n *NoChainBackend) Start() error {
return nil
}
func (n *NoChainBackend) Stop() error {
return nil
}
var _ chainview.FilteredChainView = (*NoChainBackend)(nil)
var _ chainntnfs.ChainNotifier = (*NoChainBackend)(nil)
var _ chainfee.Estimator = (*NoChainBackend)(nil)
// NoChainSource is a mock implementation of chain.Interface.
// The mock is designed to return static values where necessary to make any
// caller believe the chain is fully synced to virtual block height 1 (hash
// 0x0000..0001). That should avoid calls to other methods completely since they
// are only used for advancing the chain forward.
type NoChainSource struct {
notifChan chan interface{}
BestBlockTime time.Time
}
func (n *NoChainSource) Start() error {
n.notifChan = make(chan interface{})
go func() {
n.notifChan <- &chain.RescanFinished{
Hash: noChainBackendBestHash,
Height: noChainBackendBestHeight,
Time: n.BestBlockTime,
}
}()
return nil
}
func (n *NoChainSource) Stop() {
}
func (n *NoChainSource) WaitForShutdown() {
}
func (n *NoChainSource) GetBestBlock() (*chainhash.Hash, int32, error) {
return noChainBackendBestHash, noChainBackendBestHeight, nil
}
func (n *NoChainSource) GetBlock(*chainhash.Hash) (*wire.MsgBlock, error) {
return &wire.MsgBlock{
Header: wire.BlockHeader{
Timestamp: n.BestBlockTime,
},
Transactions: []*wire.MsgTx{},
}, nil
}
func (n *NoChainSource) GetBlockHash(int64) (*chainhash.Hash, error) {
return noChainBackendBestHash, nil
}
func (n *NoChainSource) GetBlockHeader(*chainhash.Hash) (*wire.BlockHeader,
error) {
return &wire.BlockHeader{
Timestamp: n.BestBlockTime,
}, nil
}
func (n *NoChainSource) IsCurrent() bool {
return true
}
func (n *NoChainSource) FilterBlocks(
*chain.FilterBlocksRequest) (*chain.FilterBlocksResponse, error) {
return nil, errNotImplemented
}
func (n *NoChainSource) BlockStamp() (*waddrmgr.BlockStamp, error) {
return nil, errNotImplemented
}
func (n *NoChainSource) SendRawTransaction(*wire.MsgTx, bool) (*chainhash.Hash,
error) {
return nil, errNotImplemented
}
func (n *NoChainSource) Rescan(*chainhash.Hash, []btcutil.Address,
map[wire.OutPoint]btcutil.Address) error {
return nil
}
func (n *NoChainSource) NotifyReceived([]btcutil.Address) error {
return nil
}
func (n *NoChainSource) NotifyBlocks() error {
return nil
}
func (n *NoChainSource) Notifications() <-chan interface{} {
return n.notifChan
}
func (n *NoChainSource) BackEnd() string {
return noChainBackendName
}
var _ chain.Interface = (*NoChainSource)(nil)
+28 -2
View File
@@ -148,6 +148,16 @@ const (
defaultTCBackoff = time.Minute defaultTCBackoff = time.Minute
defaultTCAttempts = 0 defaultTCAttempts = 0
// Set defaults for a health check which ensures that the remote signer
// RPC connection is alive. Although this check is off by default (only
// active when remote signing is turned on), we still set the other
// default values so that the health check can be easily enabled with
// sane defaults.
defaultRSInterval = time.Minute
defaultRSTimeout = time.Second * 1
defaultRSBackoff = time.Second * 30
defaultRSAttempts = 1
// defaultRemoteMaxHtlcs specifies the default limit for maximum // defaultRemoteMaxHtlcs specifies the default limit for maximum
// concurrent HTLCs the remote party may add to commitment transactions. // concurrent HTLCs the remote party may add to commitment transactions.
// This value can be overridden with --default-remote-max-htlcs. // This value can be overridden with --default-remote-max-htlcs.
@@ -558,6 +568,12 @@ func DefaultConfig() Config {
Attempts: defaultTCAttempts, Attempts: defaultTCAttempts,
Backoff: defaultTCBackoff, Backoff: defaultTCBackoff,
}, },
RemoteSigner: &lncfg.CheckConfig{
Interval: defaultRSInterval,
Timeout: defaultRSTimeout,
Attempts: defaultRSAttempts,
Backoff: defaultRSBackoff,
},
}, },
Gossip: &lncfg.Gossip{ Gossip: &lncfg.Gossip{
MaxChannelUpdateBurst: discovery.DefaultMaxChannelUpdateBurst, MaxChannelUpdateBurst: discovery.DefaultMaxChannelUpdateBurst,
@@ -579,7 +595,9 @@ func DefaultConfig() Config {
ChannelCommitInterval: defaultChannelCommitInterval, ChannelCommitInterval: defaultChannelCommitInterval,
ChannelCommitBatchSize: defaultChannelCommitBatchSize, ChannelCommitBatchSize: defaultChannelCommitBatchSize,
CoinSelectionStrategy: defaultCoinSelectionStrategy, CoinSelectionStrategy: defaultCoinSelectionStrategy,
RemoteSigner: &lncfg.RemoteSigner{}, RemoteSigner: &lncfg.RemoteSigner{
Timeout: lncfg.DefaultRemoteSignerRPCTimeout,
},
} }
} }
@@ -1212,6 +1230,10 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
case "neutrino": case "neutrino":
// No need to get RPC parameters. // No need to get RPC parameters.
case "nochainbackend":
// Nothing to configure, we're running without any chain
// backend whatsoever (pure signing mode).
default: default:
str := "only btcd, bitcoind, and neutrino mode " + str := "only btcd, bitcoind, and neutrino mode " +
"supported for bitcoin at this time" "supported for bitcoin at this time"
@@ -1554,6 +1576,7 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
cfg.Cluster, cfg.Cluster,
cfg.HealthChecks, cfg.HealthChecks,
cfg.RPCMiddleware, cfg.RPCMiddleware,
cfg.RemoteSigner,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@@ -1589,7 +1612,10 @@ func (c *Config) ImplementationConfig(
// watch-only source of chain and address data. But we don't need any // watch-only source of chain and address data. But we don't need any
// private key material in that btcwallet base wallet. // private key material in that btcwallet base wallet.
if c.RemoteSigner.Enable { if c.RemoteSigner.Enable {
rpcImpl := NewRPCSignerWalletImpl(c, ltndLog, interceptor) rpcImpl := NewRPCSignerWalletImpl(
c, ltndLog, interceptor,
c.RemoteSigner.MigrateWatchOnly,
)
return &ImplementationCfg{ return &ImplementationCfg{
GrpcRegistrar: rpcImpl, GrpcRegistrar: rpcImpl,
RestRegistrar: rpcImpl, RestRegistrar: rpcImpl,
+23 -20
View File
@@ -144,8 +144,9 @@ type DefaultWalletImpl struct {
logger btclog.Logger logger btclog.Logger
interceptor signal.Interceptor interceptor signal.Interceptor
watchOnly bool watchOnly bool
pwService *walletunlocker.UnlockerService migrateWatchOnly bool
pwService *walletunlocker.UnlockerService
} }
// NewDefaultWalletImpl creates a new default wallet implementation. // NewDefaultWalletImpl creates a new default wallet implementation.
@@ -560,16 +561,17 @@ func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context,
} }
walletConfig := &btcwallet.Config{ walletConfig := &btcwallet.Config{
PrivatePass: privateWalletPw, PrivatePass: privateWalletPw,
PublicPass: publicWalletPw, PublicPass: publicWalletPw,
Birthday: walletInitParams.Birthday, Birthday: walletInitParams.Birthday,
RecoveryWindow: walletInitParams.RecoveryWindow, RecoveryWindow: walletInitParams.RecoveryWindow,
NetParams: d.cfg.ActiveNetParams.Params, NetParams: d.cfg.ActiveNetParams.Params,
CoinType: d.cfg.ActiveNetParams.CoinType, CoinType: d.cfg.ActiveNetParams.CoinType,
Wallet: walletInitParams.Wallet, Wallet: walletInitParams.Wallet,
LoaderOptions: []btcwallet.LoaderOption{dbs.WalletDB}, LoaderOptions: []btcwallet.LoaderOption{dbs.WalletDB},
ChainSource: partialChainControl.ChainSource, ChainSource: partialChainControl.ChainSource,
WatchOnly: d.watchOnly, WatchOnly: d.watchOnly,
MigrateWatchOnly: d.migrateWatchOnly,
} }
// Parse coin selection strategy. // Parse coin selection strategy.
@@ -650,15 +652,17 @@ type RPCSignerWalletImpl struct {
// NewRPCSignerWalletImpl creates a new instance of the remote signing wallet // NewRPCSignerWalletImpl creates a new instance of the remote signing wallet
// implementation. // implementation.
func NewRPCSignerWalletImpl(cfg *Config, logger btclog.Logger, func NewRPCSignerWalletImpl(cfg *Config, logger btclog.Logger,
interceptor signal.Interceptor) *RPCSignerWalletImpl { interceptor signal.Interceptor,
migrateWatchOnly bool) *RPCSignerWalletImpl {
return &RPCSignerWalletImpl{ return &RPCSignerWalletImpl{
DefaultWalletImpl: &DefaultWalletImpl{ DefaultWalletImpl: &DefaultWalletImpl{
cfg: cfg, cfg: cfg,
logger: logger, logger: logger,
interceptor: interceptor, interceptor: interceptor,
watchOnly: true, watchOnly: true,
pwService: createWalletUnlockerService(cfg), migrateWatchOnly: migrateWatchOnly,
pwService: createWalletUnlockerService(cfg),
}, },
} }
} }
@@ -687,8 +691,7 @@ func (d *RPCSignerWalletImpl) BuildChainControl(
rpcKeyRing, err := rpcwallet.NewRPCKeyRing( rpcKeyRing, err := rpcwallet.NewRPCKeyRing(
baseKeyRing, walletController, baseKeyRing, walletController,
d.DefaultWalletImpl.cfg.RemoteSigner, d.DefaultWalletImpl.cfg.RemoteSigner, walletConfig.CoinType,
rpcwallet.DefaultRPCTimeout,
) )
if err != nil { if err != nil {
err := fmt.Errorf("unable to create RPC remote signing wallet "+ err := fmt.Errorf("unable to create RPC remote signing wallet "+
+16 -5
View File
@@ -1,12 +1,22 @@
# Release Notes # Release Notes
## Remote signing
The [remote signing](../remote-signing.md) setup was simplified in that the
signing node now [does not need to be hooked up to its own chain
backend](https://github.com/lightningnetwork/lnd/pull/6006). A new mock chain
backend can be specified with `--bitcoin.node=nochainbackend`. That way a wallet
will be created and all signing RPCs work but the node will not look at any
chain data. It can therefore be fully offline except for a single incoming gRPC
connection from the watch-only node.
## Wallet ## Wallet
* A bug that prevented opening anchor-based channels from external wallets when * A bug that prevented opening anchor-based channels from external wallets when
the internal wallet was empty even though the transaction contained a the internal wallet was empty even though the transaction contained a
sufficiently large output belonging to the internal wallet sufficiently large output belonging to the internal wallet
[was fixed](https://github.com/lightningnetwork/lnd/pull/5539) [was fixed](https://github.com/lightningnetwork/lnd/pull/5539).
In other words, freshly-installed LND can now be initailized with multiple In other words, freshly-installed LND can now be initialized with multiple
channels from an external (e.g. hardware) wallet *in a single transaction*. channels from an external (e.g. hardware) wallet *in a single transaction*.
## Build System ## Build System
@@ -32,10 +42,11 @@
* [We now _always_ set a channel type if the other party signals the feature * [We now _always_ set a channel type if the other party signals the feature
bit](https://github.com/lightningnetwork/lnd/pull/6075). bit](https://github.com/lightningnetwork/lnd/pull/6075).
* [Add json flag to * [Add `--json` flag to
trackpayment](https://github.com/lightningnetwork/lnd/pull/6060) `trackpayment`](https://github.com/lightningnetwork/lnd/pull/6060).
* [Clarify invalid config timeout * [Clarify invalid config timeout
constraints](https://github.com/lightningnetwork/lnd/pull/6073) constraints](https://github.com/lightningnetwork/lnd/pull/6073).
* [Fix memory corruption in Mission Control * [Fix memory corruption in Mission Control
Store](https://github.com/lightningnetwork/lnd/pull/6068) Store](https://github.com/lightningnetwork/lnd/pull/6068)
+37 -35
View File
@@ -9,7 +9,7 @@ the **private** keys.
The advantage of such a setup is that the `lnd` instance containing the private The advantage of such a setup is that the `lnd` instance containing the private
keys (the "signer") can be completely offline except for a single inbound gRPC keys (the "signer") can be completely offline except for a single inbound gRPC
connection and the outbound connection to `bitcoind` (or `btcd` or `neutrino`). connection.
The signer instance can run on a different machine with more tightly locked down The signer instance can run on a different machine with more tightly locked down
network security, optimally only allowing the single gRPC connection from the network security, optimally only allowing the single gRPC connection from the
outside. outside.
@@ -31,40 +31,14 @@ xxx xx
v | | v | |
+----------------+ gRPC | +----------------+ | +----------------+ gRPC | +----------------+ |
| watch-only lnd +--------------+-->| full seed lnd | | | watch-only lnd +--------------+-->| full seed lnd | |
+-------+--------+ | +------+---------+ | +-------+--------+ | +----------------+ |
| | | | | | |
+-------v--------+ | +------v---------+ | +-------v--------+ +----------------------------------+
| bitcoind/btcd | | | bitcoind/btcd | | | bitcoind/btcd |
+----------------+ | +----------------+ | +----------------+
| |
+----------------------------------+
``` ```
NOTE: Offline in this context means that `lnd` itself does not need to be
reachable from the internet and itself doesn't connect out. If the `bitcoind`
or other chain backend is indeed running within this same firewall/offline zone
then that component will need at least _outbound_ internet access. But it is
also possible to use a chain backend that is running outside this zone.
## Restrictions / limitations
The current implementation of the remote signing mode comes with a few
restrictions and limitations:
- Both `lnd` instances need a connection to a chain backend:
- The type of chain backend (`bitcoind`, `btcd` or `neutrino`) **must**
match.
- Both instances can point to the same chain backend node, though limits
apply with the number of `lnd` instances that can use the same `bitcoind`
node over ZMQ.
See ["running multiple lnd nodes" in the safety guide](safety.md#running-multiple-lnd-nodes)
.
- Using a pruned chain backend is not recommended as that increases the
chance of the two wallets falling out of sync with each other.
- The wallet of the "signer" instance **must not be used** for anything.
Especially generating new addresses manually on the "signer" will lead to the
two wallets falling out of sync!
## Example setup ## Example setup
In this example we are going to set up two nodes, the "signer" that has the full In this example we are going to set up two nodes, the "signer" that has the full
@@ -80,8 +54,8 @@ opened to this node from the host on which the node "watch-only" is running.
Recommended entries in `lnd.conf`: Recommended entries in `lnd.conf`:
```text ```text
# No special configuration required other than basic "hardening" parameters to # We apply some basic "hardening" parameters to make sure no connections to the
# make sure no connections to the internet are opened. # internet are opened.
[Application Options] [Application Options]
# Don't listen on the p2p port. # Don't listen on the p2p port.
@@ -93,6 +67,19 @@ nobootstrap=true
# Just an example, this is the port that needs to be opened in the firewall and # Just an example, this is the port that needs to be opened in the firewall and
# reachable from the node "watch-only". # reachable from the node "watch-only".
rpclisten=10019 rpclisten=10019
# The signer node will not look at the chain at all, it only needs to sign
# things with the keys contained in its wallet. So we don't need to hook it up
# to any chain backend.
[bitcoin]
# We still need to signal that we're using the Bitcoin chain.
bitcoin.active=true
# And we're making sure mainnet parameters are used.
bitcoin.mainnet=true
# But we aren't using a "real" chain backed but a mocked one.
bitcoin.node=nochainbackend
``` ```
After successfully starting up "signer", the following command can be run to After successfully starting up "signer", the following command can be run to
@@ -151,6 +138,21 @@ Input an optional address look-ahead used to scan for used keys (default 2500):
Alternatively a script can be used for initializing the watch-only wallet Alternatively a script can be used for initializing the watch-only wallet
through the RPC interface as is described in the next section. through the RPC interface as is described in the next section.
## Migrating an existing setup to remote signing
It is possible to migrate a node that is currently a standalone, normal node
with all private keys in its wallet to a setup that uses remote signing (with
a watch-only and a remote signer node).
To migrate an existing node, follow these steps:
1. Create a new "signer" node using the same seed as the existing node,
following the steps [mentioned above](#the-signer-node).
2. In the configuration of the existing node, add the configuration entries as
[shown above](#the-watch-only-node). But instead of creating a new wallet
(since one already exists), instruct `lnd` to migrate the existing wallet to
a watch-only one (by purging all private key material from it) by adding the
`remotesigner.migrate-wallet-to-watch-only=true` configuration entry.
## Example initialization script ## Example initialization script
This section shows an example script that initializes the watch-only wallet of This section shows an example script that initializes the watch-only wallet of
+1 -1
View File
@@ -9,7 +9,7 @@ require (
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890
github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890 github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890
github.com/btcsuite/btcwallet v0.13.0 github.com/btcsuite/btcwallet v0.13.1-0.20211201210108-79de92f527dc
github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0 github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0
github.com/btcsuite/btcwallet/wallet/txrules v1.1.0 github.com/btcsuite/btcwallet/wallet/txrules v1.1.0
github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec
+2 -2
View File
@@ -97,8 +97,8 @@ github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890/go.mod h1:0DVlH
github.com/btcsuite/btcutil/psbt v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ= github.com/btcsuite/btcutil/psbt v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ=
github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890 h1:0xUNvvwJ7RjzBs4nCF+YrK28S5P/b4uHkpPxY1ovGY4= github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890 h1:0xUNvvwJ7RjzBs4nCF+YrK28S5P/b4uHkpPxY1ovGY4=
github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ= github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ=
github.com/btcsuite/btcwallet v0.13.0 h1:gtLWwueRm27KQiHJpycybv3uMdK1eo87JexfTfzvEhk= github.com/btcsuite/btcwallet v0.13.1-0.20211201210108-79de92f527dc h1:lAbAEAp4eWvsSwJfcpdHXpKz78X2sVF9aDK4nJveXmY=
github.com/btcsuite/btcwallet v0.13.0/go.mod h1:iLN1lG1MW0eREm+SikmPO8AZPz5NglBTEK/ErqkjGpo= github.com/btcsuite/btcwallet v0.13.1-0.20211201210108-79de92f527dc/go.mod h1:iLN1lG1MW0eREm+SikmPO8AZPz5NglBTEK/ErqkjGpo=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU= github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU=
github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0 h1:8pO0pvPX1rFRfRiol4oV6kX7dY5y4chPwhfVwUfvwtk= github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0 h1:8pO0pvPX1rFRfRiol4oV6kX7dY5y4chPwhfVwUfvwtk=
github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0/go.mod h1:ktYuJyumYtwG+QQ832Q+kqvxWJRAei3Nqs5qhSn4nww= github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0/go.mod h1:ktYuJyumYtwG+QQ832Q+kqvxWJRAei3Nqs5qhSn4nww=
+4 -3
View File
@@ -14,8 +14,8 @@ type Signer interface {
// according to the data within the passed SignDescriptor. // according to the data within the passed SignDescriptor.
// //
// NOTE: The resulting signature should be void of a sighash byte. // NOTE: The resulting signature should be void of a sighash byte.
SignOutputRaw(tx *wire.MsgTx, SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) (Signature,
signDesc *SignDescriptor) (Signature, error) error)
// ComputeInputScript generates a complete InputIndex for the passed // ComputeInputScript generates a complete InputIndex for the passed
// transaction with the signature as defined within the passed // transaction with the signature as defined within the passed
@@ -26,7 +26,8 @@ type Signer interface {
// NOTE: This method will ignore any tweak parameters set within the // NOTE: This method will ignore any tweak parameters set within the
// passed SignDescriptor as it assumes a set of typical script // passed SignDescriptor as it assumes a set of typical script
// templates (p2wkh, np2wkh, etc). // templates (p2wkh, np2wkh, etc).
ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*Script, error) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*Script,
error)
} }
// Script represents any script inputs required to redeem a previous // Script represents any script inputs required to redeem a previous
+1 -1
View File
@@ -11,7 +11,7 @@ type Chain struct {
Active bool `long:"active" description:"If the chain should be active or not."` Active bool `long:"active" description:"If the chain should be active or not."`
ChainDir string `long:"chaindir" description:"The directory to store the chain's data within."` ChainDir string `long:"chaindir" description:"The directory to store the chain's data within."`
Node string `long:"node" description:"The blockchain interface to use." choice:"btcd" choice:"bitcoind" choice:"neutrino" choice:"ltcd" choice:"litecoind"` Node string `long:"node" description:"The blockchain interface to use." choice:"btcd" choice:"bitcoind" choice:"neutrino" choice:"ltcd" choice:"litecoind" choice:"nochainbackend"`
MainNet bool `long:"mainnet" description:"Use the main network"` MainNet bool `long:"mainnet" description:"Use the main network"`
TestNet3 bool `long:"testnet" description:"Use the test network"` TestNet3 bool `long:"testnet" description:"Use the test network"`
+2
View File
@@ -30,6 +30,8 @@ type HealthCheckConfig struct {
TLSCheck *CheckConfig `group:"tls" namespace:"tls"` TLSCheck *CheckConfig `group:"tls" namespace:"tls"`
TorConnection *CheckConfig `group:"torconnection" namespace:"torconnection"` TorConnection *CheckConfig `group:"torconnection" namespace:"torconnection"`
RemoteSigner *CheckConfig `group:"remotesigner" namespace:"remotesigner"`
} }
// Validate checks the values configured for our health checks. // Validate checks the values configured for our health checks.
+38 -4
View File
@@ -1,9 +1,43 @@
package lncfg package lncfg
import (
"fmt"
"time"
)
const (
// DefaultRemoteSignerRPCTimeout is the default timeout that is used
// when forwarding a request to the remote signer through RPC.
DefaultRemoteSignerRPCTimeout = 5 * time.Second
)
// RemoteSigner holds the configuration options for a remote RPC signer. // RemoteSigner holds the configuration options for a remote RPC signer.
type RemoteSigner struct { type RemoteSigner struct {
Enable bool `long:"enable" description:"Use a remote signer for signing any on-chain related transactions or messages. Only recommended if local wallet is initialized as watch-only. Remote signer must use the same seed/root key as the local watch-only wallet but must have private keys."` Enable bool `long:"enable" description:"Use a remote signer for signing any on-chain related transactions or messages. Only recommended if local wallet is initialized as watch-only. Remote signer must use the same seed/root key as the local watch-only wallet but must have private keys."`
RPCHost string `long:"rpchost" description:"The remote signer's RPC host:port"` RPCHost string `long:"rpchost" description:"The remote signer's RPC host:port"`
MacaroonPath string `long:"macaroonpath" description:"The macaroon to use for authenticating with the remote signer"` MacaroonPath string `long:"macaroonpath" description:"The macaroon to use for authenticating with the remote signer"`
TLSCertPath string `long:"tlscertpath" description:"The TLS certificate to use for establishing the remote signer's identity"` TLSCertPath string `long:"tlscertpath" description:"The TLS certificate to use for establishing the remote signer's identity"`
Timeout time.Duration `long:"timeout" description:"The timeout for connecting to and signing requests with the remote signer. Valid time units are {s, m, h}."`
MigrateWatchOnly bool `long:"migrate-wallet-to-watch-only" description:"If a wallet with private key material already exists, migrate it into a watch-only wallet on first startup. WARNING: This cannot be undone! Make sure you have backed up your seed before you use this flag! All private keys will be purged from the wallet after first unlock with this flag!"`
}
// Validate checks the values configured for our remote RPC signer.
func (r *RemoteSigner) Validate() error {
if !r.Enable {
return nil
}
if r.Timeout < time.Millisecond {
return fmt.Errorf("remote signer: timeout of %v is invalid, "+
"cannot be smaller than %v", r.Timeout,
time.Millisecond)
}
if r.MigrateWatchOnly && !r.Enable {
return fmt.Errorf("remote signer: cannot turn on wallet " +
"migration to watch-only if remote signing is not " +
"enabled")
}
return nil
} }
+7 -3
View File
@@ -238,7 +238,8 @@ func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispat
// provides an invalid transaction, then we'll return with an error. // provides an invalid transaction, then we'll return with an error.
// //
// NOTE: The resulting signature should be void of a sighash byte. // NOTE: The resulting signature should be void of a sighash byte.
func (s *Server) SignOutputRaw(ctx context.Context, in *SignReq) (*SignResp, error) { func (s *Server) SignOutputRaw(ctx context.Context, in *SignReq) (*SignResp,
error) {
switch { switch {
// If the client doesn't specify a transaction, then there's nothing to // If the client doesn't specify a transaction, then there's nothing to
@@ -309,9 +310,12 @@ func (s *Server) SignOutputRaw(ctx context.Context, in *SignReq) (*SignResp, err
// If a witness script isn't passed, then we can't proceed, as // If a witness script isn't passed, then we can't proceed, as
// in the p2wsh case, we can't properly generate the sighash. // in the p2wsh case, we can't properly generate the sighash.
// A P2WKH doesn't need a witness script. But SignOutputRaw
// still needs to know the PK script that was used for the
// output. We'll send it in the WitnessScript field, the
// SignOutputRaw RPC will know what to do with it when creating
// the sighash.
if len(signDesc.WitnessScript) == 0 { if len(signDesc.WitnessScript) == 0 {
// TODO(roasbeef): if regualr p2wkh, then at times
// internally we allow script to go by
return nil, fmt.Errorf("witness script MUST be " + return nil, fmt.Errorf("witness script MUST be " +
"specified") "specified")
} }
+348 -211
View File
@@ -2542,6 +2542,104 @@ func (x *UtxoLease) GetExpiration() uint64 {
return 0 return 0
} }
type SignPsbtRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
//
//The PSBT that should be signed. The PSBT must contain all required inputs,
//outputs, UTXO data and custom fields required to identify the signing key.
FundedPsbt []byte `protobuf:"bytes,1,opt,name=funded_psbt,json=fundedPsbt,proto3" json:"funded_psbt,omitempty"`
}
func (x *SignPsbtRequest) Reset() {
*x = SignPsbtRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[35]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SignPsbtRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SignPsbtRequest) ProtoMessage() {}
func (x *SignPsbtRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[35]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SignPsbtRequest.ProtoReflect.Descriptor instead.
func (*SignPsbtRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{35}
}
func (x *SignPsbtRequest) GetFundedPsbt() []byte {
if x != nil {
return x.FundedPsbt
}
return nil
}
type SignPsbtResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The signed transaction in PSBT format.
SignedPsbt []byte `protobuf:"bytes,1,opt,name=signed_psbt,json=signedPsbt,proto3" json:"signed_psbt,omitempty"`
}
func (x *SignPsbtResponse) Reset() {
*x = SignPsbtResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[36]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SignPsbtResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SignPsbtResponse) ProtoMessage() {}
func (x *SignPsbtResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[36]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SignPsbtResponse.ProtoReflect.Descriptor instead.
func (*SignPsbtResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{36}
}
func (x *SignPsbtResponse) GetSignedPsbt() []byte {
if x != nil {
return x.SignedPsbt
}
return nil
}
type FinalizePsbtRequest struct { type FinalizePsbtRequest struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -2561,7 +2659,7 @@ type FinalizePsbtRequest struct {
func (x *FinalizePsbtRequest) Reset() { func (x *FinalizePsbtRequest) Reset() {
*x = FinalizePsbtRequest{} *x = FinalizePsbtRequest{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[35] mi := &file_walletrpc_walletkit_proto_msgTypes[37]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -2574,7 +2672,7 @@ func (x *FinalizePsbtRequest) String() string {
func (*FinalizePsbtRequest) ProtoMessage() {} func (*FinalizePsbtRequest) ProtoMessage() {}
func (x *FinalizePsbtRequest) ProtoReflect() protoreflect.Message { func (x *FinalizePsbtRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[35] mi := &file_walletrpc_walletkit_proto_msgTypes[37]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -2587,7 +2685,7 @@ func (x *FinalizePsbtRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use FinalizePsbtRequest.ProtoReflect.Descriptor instead. // Deprecated: Use FinalizePsbtRequest.ProtoReflect.Descriptor instead.
func (*FinalizePsbtRequest) Descriptor() ([]byte, []int) { func (*FinalizePsbtRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{35} return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{37}
} }
func (x *FinalizePsbtRequest) GetFundedPsbt() []byte { func (x *FinalizePsbtRequest) GetFundedPsbt() []byte {
@@ -2618,7 +2716,7 @@ type FinalizePsbtResponse struct {
func (x *FinalizePsbtResponse) Reset() { func (x *FinalizePsbtResponse) Reset() {
*x = FinalizePsbtResponse{} *x = FinalizePsbtResponse{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[36] mi := &file_walletrpc_walletkit_proto_msgTypes[38]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -2631,7 +2729,7 @@ func (x *FinalizePsbtResponse) String() string {
func (*FinalizePsbtResponse) ProtoMessage() {} func (*FinalizePsbtResponse) ProtoMessage() {}
func (x *FinalizePsbtResponse) ProtoReflect() protoreflect.Message { func (x *FinalizePsbtResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[36] mi := &file_walletrpc_walletkit_proto_msgTypes[38]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -2644,7 +2742,7 @@ func (x *FinalizePsbtResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use FinalizePsbtResponse.ProtoReflect.Descriptor instead. // Deprecated: Use FinalizePsbtResponse.ProtoReflect.Descriptor instead.
func (*FinalizePsbtResponse) Descriptor() ([]byte, []int) { func (*FinalizePsbtResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{36} return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{38}
} }
func (x *FinalizePsbtResponse) GetSignedPsbt() []byte { func (x *FinalizePsbtResponse) GetSignedPsbt() []byte {
@@ -2670,7 +2768,7 @@ type ListLeasesRequest struct {
func (x *ListLeasesRequest) Reset() { func (x *ListLeasesRequest) Reset() {
*x = ListLeasesRequest{} *x = ListLeasesRequest{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[37] mi := &file_walletrpc_walletkit_proto_msgTypes[39]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -2683,7 +2781,7 @@ func (x *ListLeasesRequest) String() string {
func (*ListLeasesRequest) ProtoMessage() {} func (*ListLeasesRequest) ProtoMessage() {}
func (x *ListLeasesRequest) ProtoReflect() protoreflect.Message { func (x *ListLeasesRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[37] mi := &file_walletrpc_walletkit_proto_msgTypes[39]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -2696,7 +2794,7 @@ func (x *ListLeasesRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListLeasesRequest.ProtoReflect.Descriptor instead. // Deprecated: Use ListLeasesRequest.ProtoReflect.Descriptor instead.
func (*ListLeasesRequest) Descriptor() ([]byte, []int) { func (*ListLeasesRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{37} return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{39}
} }
type ListLeasesResponse struct { type ListLeasesResponse struct {
@@ -2711,7 +2809,7 @@ type ListLeasesResponse struct {
func (x *ListLeasesResponse) Reset() { func (x *ListLeasesResponse) Reset() {
*x = ListLeasesResponse{} *x = ListLeasesResponse{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[38] mi := &file_walletrpc_walletkit_proto_msgTypes[40]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -2724,7 +2822,7 @@ func (x *ListLeasesResponse) String() string {
func (*ListLeasesResponse) ProtoMessage() {} func (*ListLeasesResponse) ProtoMessage() {}
func (x *ListLeasesResponse) ProtoReflect() protoreflect.Message { func (x *ListLeasesResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[38] mi := &file_walletrpc_walletkit_proto_msgTypes[40]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -2737,7 +2835,7 @@ func (x *ListLeasesResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListLeasesResponse.ProtoReflect.Descriptor instead. // Deprecated: Use ListLeasesResponse.ProtoReflect.Descriptor instead.
func (*ListLeasesResponse) Descriptor() ([]byte, []int) { func (*ListLeasesResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{38} return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{40}
} }
func (x *ListLeasesResponse) GetLockedUtxos() []*UtxoLease { func (x *ListLeasesResponse) GetLockedUtxos() []*UtxoLease {
@@ -2762,7 +2860,7 @@ type ListSweepsResponse_TransactionIDs struct {
func (x *ListSweepsResponse_TransactionIDs) Reset() { func (x *ListSweepsResponse_TransactionIDs) Reset() {
*x = ListSweepsResponse_TransactionIDs{} *x = ListSweepsResponse_TransactionIDs{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[39] mi := &file_walletrpc_walletkit_proto_msgTypes[41]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -2775,7 +2873,7 @@ func (x *ListSweepsResponse_TransactionIDs) String() string {
func (*ListSweepsResponse_TransactionIDs) ProtoMessage() {} func (*ListSweepsResponse_TransactionIDs) ProtoMessage() {}
func (x *ListSweepsResponse_TransactionIDs) ProtoReflect() protoreflect.Message { func (x *ListSweepsResponse_TransactionIDs) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[39] mi := &file_walletrpc_walletkit_proto_msgTypes[41]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -3069,153 +3167,164 @@ var file_walletrpc_walletkit_proto_rawDesc = []byte{
0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e,
0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x65,
0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52,
0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x50, 0x0a, 0x13, 0x46, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x32, 0x0a, 0x0f, 0x53,
0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f,
0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20,
0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x22,
0x73, 0x62, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x33, 0x0a, 0x10, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x59, 0x0a, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x73,
0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x50, 0x73, 0x62, 0x74, 0x22, 0x50, 0x0a, 0x13, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65,
0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x66,
0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x61, 0x77, 0x5f, 0x66, 0x69, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
0x6e, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x61, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x18, 0x0a, 0x07,
0x77, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x54, 0x78, 0x22, 0x13, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61,
0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4d, 0x0a, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x59, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69,
0x12, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f,
0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0c, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x75, 0x74, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20,
0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12,
0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x52, 0x20, 0x0a, 0x0c, 0x72, 0x61, 0x77, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x18,
0x0b, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x2a, 0x7a, 0x0a, 0x0b, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x61, 0x77, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x54,
0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x78, 0x22, 0x13, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52,
0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x57, 0x49, 0x54, 0x4e, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4d, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65,
0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0c,
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, 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, 0x03, 0x2a, 0x99, 0x03, 0x0a, 0x0b, 0x57, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70,
0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x57, 0x49, 0x54,
0x02, 0x12, 0x25, 0x0a, 0x21, 0x48, 0x59, 0x42, 0x52, 0x49, 0x44, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54,
0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x5f, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x01,
0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x2a, 0x99, 0x03, 0x0a, 0x0b, 0x57, 0x69, 0x74, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x4e,
0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x5f, 0x44, 0x45, 0x4c, 0x41, 0x59, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d,
0x4f, 0x57, 0x4e, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x03,
0x14, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x12, 0x17, 0x0a, 0x13, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44,
0x5f, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x48, 0x54, 0x4c,
0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x5f, 0x44, 0x45, 0x4c, 0x41, 0x59, 0x10, 0x02, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b,
0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x10, 0x05, 0x12, 0x25, 0x0a, 0x21, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45,
0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f,
0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x04, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x06, 0x12, 0x26, 0x0a, 0x22, 0x48, 0x54,
0x12, 0x18, 0x0a, 0x14, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43,
0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x05, 0x12, 0x25, 0x0a, 0x21, 0x48, 0x54, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c,
0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52,
0x55, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55,
0x06, 0x12, 0x26, 0x0a, 0x22, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x54, 0x10, 0x08, 0x12, 0x20, 0x0a, 0x1c, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45,
0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x53, 0x55, 0x43, 0x43,
0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x48, 0x54, 0x4c, 0x45, 0x53, 0x53, 0x10, 0x09, 0x12, 0x1c, 0x0a, 0x18, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45,
0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b,
0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x08, 0x12, 0x20, 0x0a, 0x1c, 0x48, 0x54, 0x45, 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x4b,
0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0b, 0x12, 0x1b, 0x0a, 0x17, 0x4e, 0x45, 0x53,
0x54, 0x45, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x09, 0x12, 0x1c, 0x0a, 0x18, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f,
0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0c, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54,
0x4c, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x57, 0x49, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x10, 0x0d, 0x32, 0xf7, 0x0b,
0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0b, 0x0a, 0x09, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x4b, 0x69, 0x74, 0x12, 0x4c, 0x0a, 0x0b, 0x4c,
0x12, 0x1b, 0x0a, 0x17, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c,
0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0c, 0x12, 0x15, 0x0a, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65,
0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x4e, 0x43, 0x48, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
0x4f, 0x52, 0x10, 0x0d, 0x32, 0xb2, 0x0b, 0x0a, 0x09, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x4b, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e,
0x69, 0x74, 0x12, 0x4c, 0x0a, 0x0b, 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,
0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 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, 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, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, 0x61,
0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x4c,
0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 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, 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, 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, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x10, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0d, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65,
0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x4e, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67,
0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x6f, 0x72, 0x12, 0x38, 0x0a, 0x09, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x12,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63,
0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x08, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x61, 0x74, 0x6f, 0x72, 0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b,
0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x3b, 0x0a, 0x08,
0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77, 0x61, 0x4e, 0x65, 0x78, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x46, 0x69, 0x6e, 0x61, 0x1a, 0x17, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64,
0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x4c, 0x69, 0x73,
0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x49, 0x6d,
0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x2e, 0x77, 0x61,
0x70, 0x63, 0x2f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63,
0x6f, 0x74, 0x6f, 0x33, 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, 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,
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 ( var (
@@ -3231,7 +3340,7 @@ func file_walletrpc_walletkit_proto_rawDescGZIP() []byte {
} }
var file_walletrpc_walletkit_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_walletrpc_walletkit_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_walletrpc_walletkit_proto_msgTypes = make([]protoimpl.MessageInfo, 41) var file_walletrpc_walletkit_proto_msgTypes = make([]protoimpl.MessageInfo, 43)
var file_walletrpc_walletkit_proto_goTypes = []interface{}{ var file_walletrpc_walletkit_proto_goTypes = []interface{}{
(AddressType)(0), // 0: walletrpc.AddressType (AddressType)(0), // 0: walletrpc.AddressType
(WitnessType)(0), // 1: walletrpc.WitnessType (WitnessType)(0), // 1: walletrpc.WitnessType
@@ -3270,23 +3379,25 @@ var file_walletrpc_walletkit_proto_goTypes = []interface{}{
(*FundPsbtResponse)(nil), // 34: walletrpc.FundPsbtResponse (*FundPsbtResponse)(nil), // 34: walletrpc.FundPsbtResponse
(*TxTemplate)(nil), // 35: walletrpc.TxTemplate (*TxTemplate)(nil), // 35: walletrpc.TxTemplate
(*UtxoLease)(nil), // 36: walletrpc.UtxoLease (*UtxoLease)(nil), // 36: walletrpc.UtxoLease
(*FinalizePsbtRequest)(nil), // 37: walletrpc.FinalizePsbtRequest (*SignPsbtRequest)(nil), // 37: walletrpc.SignPsbtRequest
(*FinalizePsbtResponse)(nil), // 38: walletrpc.FinalizePsbtResponse (*SignPsbtResponse)(nil), // 38: walletrpc.SignPsbtResponse
(*ListLeasesRequest)(nil), // 39: walletrpc.ListLeasesRequest (*FinalizePsbtRequest)(nil), // 39: walletrpc.FinalizePsbtRequest
(*ListLeasesResponse)(nil), // 40: walletrpc.ListLeasesResponse (*FinalizePsbtResponse)(nil), // 40: walletrpc.FinalizePsbtResponse
(*ListSweepsResponse_TransactionIDs)(nil), // 41: walletrpc.ListSweepsResponse.TransactionIDs (*ListLeasesRequest)(nil), // 41: walletrpc.ListLeasesRequest
nil, // 42: walletrpc.TxTemplate.OutputsEntry (*ListLeasesResponse)(nil), // 42: walletrpc.ListLeasesResponse
(*lnrpc.Utxo)(nil), // 43: lnrpc.Utxo (*ListSweepsResponse_TransactionIDs)(nil), // 43: walletrpc.ListSweepsResponse.TransactionIDs
(*lnrpc.OutPoint)(nil), // 44: lnrpc.OutPoint nil, // 44: walletrpc.TxTemplate.OutputsEntry
(*signrpc.TxOut)(nil), // 45: signrpc.TxOut (*lnrpc.Utxo)(nil), // 45: lnrpc.Utxo
(*lnrpc.TransactionDetails)(nil), // 46: lnrpc.TransactionDetails (*lnrpc.OutPoint)(nil), // 46: lnrpc.OutPoint
(*signrpc.KeyLocator)(nil), // 47: signrpc.KeyLocator (*signrpc.TxOut)(nil), // 47: signrpc.TxOut
(*signrpc.KeyDescriptor)(nil), // 48: signrpc.KeyDescriptor (*lnrpc.TransactionDetails)(nil), // 48: lnrpc.TransactionDetails
(*signrpc.KeyLocator)(nil), // 49: signrpc.KeyLocator
(*signrpc.KeyDescriptor)(nil), // 50: signrpc.KeyDescriptor
} }
var file_walletrpc_walletkit_proto_depIdxs = []int32{ var file_walletrpc_walletkit_proto_depIdxs = []int32{
43, // 0: walletrpc.ListUnspentResponse.utxos:type_name -> lnrpc.Utxo 45, // 0: walletrpc.ListUnspentResponse.utxos:type_name -> lnrpc.Utxo
44, // 1: walletrpc.LeaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint 46, // 1: walletrpc.LeaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint
44, // 2: walletrpc.ReleaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint 46, // 2: walletrpc.ReleaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint
0, // 3: walletrpc.AddrRequest.type:type_name -> walletrpc.AddressType 0, // 3: walletrpc.AddrRequest.type:type_name -> walletrpc.AddressType
0, // 4: walletrpc.Account.address_type:type_name -> walletrpc.AddressType 0, // 4: walletrpc.Account.address_type:type_name -> walletrpc.AddressType
0, // 5: walletrpc.ListAccountsRequest.address_type:type_name -> walletrpc.AddressType 0, // 5: walletrpc.ListAccountsRequest.address_type:type_name -> walletrpc.AddressType
@@ -3294,25 +3405,25 @@ var file_walletrpc_walletkit_proto_depIdxs = []int32{
0, // 7: walletrpc.ImportAccountRequest.address_type:type_name -> walletrpc.AddressType 0, // 7: walletrpc.ImportAccountRequest.address_type:type_name -> walletrpc.AddressType
11, // 8: walletrpc.ImportAccountResponse.account:type_name -> walletrpc.Account 11, // 8: walletrpc.ImportAccountResponse.account:type_name -> walletrpc.Account
0, // 9: walletrpc.ImportPublicKeyRequest.address_type:type_name -> walletrpc.AddressType 0, // 9: walletrpc.ImportPublicKeyRequest.address_type:type_name -> walletrpc.AddressType
45, // 10: walletrpc.SendOutputsRequest.outputs:type_name -> signrpc.TxOut 47, // 10: walletrpc.SendOutputsRequest.outputs:type_name -> signrpc.TxOut
44, // 11: walletrpc.PendingSweep.outpoint:type_name -> lnrpc.OutPoint 46, // 11: walletrpc.PendingSweep.outpoint:type_name -> lnrpc.OutPoint
1, // 12: walletrpc.PendingSweep.witness_type:type_name -> walletrpc.WitnessType 1, // 12: walletrpc.PendingSweep.witness_type:type_name -> walletrpc.WitnessType
24, // 13: walletrpc.PendingSweepsResponse.pending_sweeps:type_name -> walletrpc.PendingSweep 24, // 13: walletrpc.PendingSweepsResponse.pending_sweeps:type_name -> walletrpc.PendingSweep
44, // 14: walletrpc.BumpFeeRequest.outpoint:type_name -> lnrpc.OutPoint 46, // 14: walletrpc.BumpFeeRequest.outpoint:type_name -> lnrpc.OutPoint
46, // 15: walletrpc.ListSweepsResponse.transaction_details:type_name -> lnrpc.TransactionDetails 48, // 15: walletrpc.ListSweepsResponse.transaction_details:type_name -> lnrpc.TransactionDetails
41, // 16: walletrpc.ListSweepsResponse.transaction_ids:type_name -> walletrpc.ListSweepsResponse.TransactionIDs 43, // 16: walletrpc.ListSweepsResponse.transaction_ids:type_name -> walletrpc.ListSweepsResponse.TransactionIDs
35, // 17: walletrpc.FundPsbtRequest.raw:type_name -> walletrpc.TxTemplate 35, // 17: walletrpc.FundPsbtRequest.raw:type_name -> walletrpc.TxTemplate
36, // 18: walletrpc.FundPsbtResponse.locked_utxos:type_name -> walletrpc.UtxoLease 36, // 18: walletrpc.FundPsbtResponse.locked_utxos:type_name -> walletrpc.UtxoLease
44, // 19: walletrpc.TxTemplate.inputs:type_name -> lnrpc.OutPoint 46, // 19: walletrpc.TxTemplate.inputs:type_name -> lnrpc.OutPoint
42, // 20: walletrpc.TxTemplate.outputs:type_name -> walletrpc.TxTemplate.OutputsEntry 44, // 20: walletrpc.TxTemplate.outputs:type_name -> walletrpc.TxTemplate.OutputsEntry
44, // 21: walletrpc.UtxoLease.outpoint:type_name -> lnrpc.OutPoint 46, // 21: walletrpc.UtxoLease.outpoint:type_name -> lnrpc.OutPoint
36, // 22: walletrpc.ListLeasesResponse.locked_utxos:type_name -> walletrpc.UtxoLease 36, // 22: walletrpc.ListLeasesResponse.locked_utxos:type_name -> walletrpc.UtxoLease
2, // 23: walletrpc.WalletKit.ListUnspent:input_type -> walletrpc.ListUnspentRequest 2, // 23: walletrpc.WalletKit.ListUnspent:input_type -> walletrpc.ListUnspentRequest
4, // 24: walletrpc.WalletKit.LeaseOutput:input_type -> walletrpc.LeaseOutputRequest 4, // 24: walletrpc.WalletKit.LeaseOutput:input_type -> walletrpc.LeaseOutputRequest
6, // 25: walletrpc.WalletKit.ReleaseOutput:input_type -> walletrpc.ReleaseOutputRequest 6, // 25: walletrpc.WalletKit.ReleaseOutput:input_type -> walletrpc.ReleaseOutputRequest
39, // 26: walletrpc.WalletKit.ListLeases:input_type -> walletrpc.ListLeasesRequest 41, // 26: walletrpc.WalletKit.ListLeases:input_type -> walletrpc.ListLeasesRequest
8, // 27: walletrpc.WalletKit.DeriveNextKey:input_type -> walletrpc.KeyReq 8, // 27: walletrpc.WalletKit.DeriveNextKey:input_type -> walletrpc.KeyReq
47, // 28: walletrpc.WalletKit.DeriveKey:input_type -> signrpc.KeyLocator 49, // 28: walletrpc.WalletKit.DeriveKey:input_type -> signrpc.KeyLocator
9, // 29: walletrpc.WalletKit.NextAddr:input_type -> walletrpc.AddrRequest 9, // 29: walletrpc.WalletKit.NextAddr:input_type -> walletrpc.AddrRequest
12, // 30: walletrpc.WalletKit.ListAccounts:input_type -> walletrpc.ListAccountsRequest 12, // 30: walletrpc.WalletKit.ListAccounts:input_type -> walletrpc.ListAccountsRequest
14, // 31: walletrpc.WalletKit.ImportAccount:input_type -> walletrpc.ImportAccountRequest 14, // 31: walletrpc.WalletKit.ImportAccount:input_type -> walletrpc.ImportAccountRequest
@@ -3325,28 +3436,30 @@ var file_walletrpc_walletkit_proto_depIdxs = []int32{
29, // 38: walletrpc.WalletKit.ListSweeps:input_type -> walletrpc.ListSweepsRequest 29, // 38: walletrpc.WalletKit.ListSweeps:input_type -> walletrpc.ListSweepsRequest
31, // 39: walletrpc.WalletKit.LabelTransaction:input_type -> walletrpc.LabelTransactionRequest 31, // 39: walletrpc.WalletKit.LabelTransaction:input_type -> walletrpc.LabelTransactionRequest
33, // 40: walletrpc.WalletKit.FundPsbt:input_type -> walletrpc.FundPsbtRequest 33, // 40: walletrpc.WalletKit.FundPsbt:input_type -> walletrpc.FundPsbtRequest
37, // 41: walletrpc.WalletKit.FinalizePsbt:input_type -> walletrpc.FinalizePsbtRequest 37, // 41: walletrpc.WalletKit.SignPsbt:input_type -> walletrpc.SignPsbtRequest
3, // 42: walletrpc.WalletKit.ListUnspent:output_type -> walletrpc.ListUnspentResponse 39, // 42: walletrpc.WalletKit.FinalizePsbt:input_type -> walletrpc.FinalizePsbtRequest
5, // 43: walletrpc.WalletKit.LeaseOutput:output_type -> walletrpc.LeaseOutputResponse 3, // 43: walletrpc.WalletKit.ListUnspent:output_type -> walletrpc.ListUnspentResponse
7, // 44: walletrpc.WalletKit.ReleaseOutput:output_type -> walletrpc.ReleaseOutputResponse 5, // 44: walletrpc.WalletKit.LeaseOutput:output_type -> walletrpc.LeaseOutputResponse
40, // 45: walletrpc.WalletKit.ListLeases:output_type -> walletrpc.ListLeasesResponse 7, // 45: walletrpc.WalletKit.ReleaseOutput:output_type -> walletrpc.ReleaseOutputResponse
48, // 46: walletrpc.WalletKit.DeriveNextKey:output_type -> signrpc.KeyDescriptor 42, // 46: walletrpc.WalletKit.ListLeases:output_type -> walletrpc.ListLeasesResponse
48, // 47: walletrpc.WalletKit.DeriveKey:output_type -> signrpc.KeyDescriptor 50, // 47: walletrpc.WalletKit.DeriveNextKey:output_type -> signrpc.KeyDescriptor
10, // 48: walletrpc.WalletKit.NextAddr:output_type -> walletrpc.AddrResponse 50, // 48: walletrpc.WalletKit.DeriveKey:output_type -> signrpc.KeyDescriptor
13, // 49: walletrpc.WalletKit.ListAccounts:output_type -> walletrpc.ListAccountsResponse 10, // 49: walletrpc.WalletKit.NextAddr:output_type -> walletrpc.AddrResponse
15, // 50: walletrpc.WalletKit.ImportAccount:output_type -> walletrpc.ImportAccountResponse 13, // 50: walletrpc.WalletKit.ListAccounts:output_type -> walletrpc.ListAccountsResponse
17, // 51: walletrpc.WalletKit.ImportPublicKey:output_type -> walletrpc.ImportPublicKeyResponse 15, // 51: walletrpc.WalletKit.ImportAccount:output_type -> walletrpc.ImportAccountResponse
19, // 52: walletrpc.WalletKit.PublishTransaction:output_type -> walletrpc.PublishResponse 17, // 52: walletrpc.WalletKit.ImportPublicKey:output_type -> walletrpc.ImportPublicKeyResponse
21, // 53: walletrpc.WalletKit.SendOutputs:output_type -> walletrpc.SendOutputsResponse 19, // 53: walletrpc.WalletKit.PublishTransaction:output_type -> walletrpc.PublishResponse
23, // 54: walletrpc.WalletKit.EstimateFee:output_type -> walletrpc.EstimateFeeResponse 21, // 54: walletrpc.WalletKit.SendOutputs:output_type -> walletrpc.SendOutputsResponse
26, // 55: walletrpc.WalletKit.PendingSweeps:output_type -> walletrpc.PendingSweepsResponse 23, // 55: walletrpc.WalletKit.EstimateFee:output_type -> walletrpc.EstimateFeeResponse
28, // 56: walletrpc.WalletKit.BumpFee:output_type -> walletrpc.BumpFeeResponse 26, // 56: walletrpc.WalletKit.PendingSweeps:output_type -> walletrpc.PendingSweepsResponse
30, // 57: walletrpc.WalletKit.ListSweeps:output_type -> walletrpc.ListSweepsResponse 28, // 57: walletrpc.WalletKit.BumpFee:output_type -> walletrpc.BumpFeeResponse
32, // 58: walletrpc.WalletKit.LabelTransaction:output_type -> walletrpc.LabelTransactionResponse 30, // 58: walletrpc.WalletKit.ListSweeps:output_type -> walletrpc.ListSweepsResponse
34, // 59: walletrpc.WalletKit.FundPsbt:output_type -> walletrpc.FundPsbtResponse 32, // 59: walletrpc.WalletKit.LabelTransaction:output_type -> walletrpc.LabelTransactionResponse
38, // 60: walletrpc.WalletKit.FinalizePsbt:output_type -> walletrpc.FinalizePsbtResponse 34, // 60: walletrpc.WalletKit.FundPsbt:output_type -> walletrpc.FundPsbtResponse
42, // [42:61] is the sub-list for method output_type 38, // 61: walletrpc.WalletKit.SignPsbt:output_type -> walletrpc.SignPsbtResponse
23, // [23:42] is the sub-list for method input_type 40, // 62: walletrpc.WalletKit.FinalizePsbt:output_type -> walletrpc.FinalizePsbtResponse
43, // [43:63] is the sub-list for method output_type
23, // [23:43] is the sub-list for method input_type
23, // [23:23] is the sub-list for extension type_name 23, // [23:23] is the sub-list for extension type_name
23, // [23:23] is the sub-list for extension extendee 23, // [23:23] is the sub-list for extension extendee
0, // [0:23] is the sub-list for field type_name 0, // [0:23] is the sub-list for field type_name
@@ -3779,7 +3892,7 @@ func file_walletrpc_walletkit_proto_init() {
} }
} }
file_walletrpc_walletkit_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { file_walletrpc_walletkit_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FinalizePsbtRequest); i { switch v := v.(*SignPsbtRequest); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -3791,7 +3904,7 @@ func file_walletrpc_walletkit_proto_init() {
} }
} }
file_walletrpc_walletkit_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { file_walletrpc_walletkit_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FinalizePsbtResponse); i { switch v := v.(*SignPsbtResponse); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -3803,7 +3916,7 @@ func file_walletrpc_walletkit_proto_init() {
} }
} }
file_walletrpc_walletkit_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { file_walletrpc_walletkit_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListLeasesRequest); i { switch v := v.(*FinalizePsbtRequest); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -3815,7 +3928,7 @@ func file_walletrpc_walletkit_proto_init() {
} }
} }
file_walletrpc_walletkit_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { file_walletrpc_walletkit_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListLeasesResponse); i { switch v := v.(*FinalizePsbtResponse); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -3827,6 +3940,30 @@ func file_walletrpc_walletkit_proto_init() {
} }
} }
file_walletrpc_walletkit_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { file_walletrpc_walletkit_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListLeasesRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListLeasesResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListSweepsResponse_TransactionIDs); i { switch v := v.(*ListSweepsResponse_TransactionIDs); i {
case 0: case 0:
return &v.state return &v.state
@@ -3855,7 +3992,7 @@ func file_walletrpc_walletkit_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_walletrpc_walletkit_proto_rawDesc, RawDescriptor: file_walletrpc_walletkit_proto_rawDesc,
NumEnums: 2, NumEnums: 2,
NumMessages: 41, NumMessages: 43,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },
+81
View File
@@ -634,6 +634,40 @@ func local_request_WalletKit_FundPsbt_0(ctx context.Context, marshaler runtime.M
} }
func request_WalletKit_SignPsbt_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SignPsbtRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SignPsbt(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_SignPsbt_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SignPsbtRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SignPsbt(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_FinalizePsbt_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { func request_WalletKit_FinalizePsbt_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq FinalizePsbtRequest var protoReq FinalizePsbtRequest
var metadata runtime.ServerMetadata var metadata runtime.ServerMetadata
@@ -1088,6 +1122,29 @@ func RegisterWalletKitHandlerServer(ctx context.Context, mux *runtime.ServeMux,
}) })
mux.Handle("POST", pattern_WalletKit_SignPsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/SignPsbt", runtime.WithHTTPPathPattern("/v2/wallet/psbt/sign"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_SignPsbt_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_SignPsbt_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_FinalizePsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle("POST", pattern_WalletKit_FinalizePsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() defer cancel()
@@ -1512,6 +1569,26 @@ func RegisterWalletKitHandlerClient(ctx context.Context, mux *runtime.ServeMux,
}) })
mux.Handle("POST", pattern_WalletKit_SignPsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/SignPsbt", runtime.WithHTTPPathPattern("/v2/wallet/psbt/sign"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_SignPsbt_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_SignPsbt_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_FinalizePsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle("POST", pattern_WalletKit_FinalizePsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() defer cancel()
@@ -1572,6 +1649,8 @@ var (
pattern_WalletKit_FundPsbt_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "psbt", "fund"}, "")) pattern_WalletKit_FundPsbt_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "psbt", "fund"}, ""))
pattern_WalletKit_SignPsbt_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "psbt", "sign"}, ""))
pattern_WalletKit_FinalizePsbt_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "psbt", "finalize"}, "")) pattern_WalletKit_FinalizePsbt_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "psbt", "finalize"}, ""))
) )
@@ -1612,5 +1691,7 @@ var (
forward_WalletKit_FundPsbt_0 = runtime.ForwardResponseMessage forward_WalletKit_FundPsbt_0 = runtime.ForwardResponseMessage
forward_WalletKit_SignPsbt_0 = runtime.ForwardResponseMessage
forward_WalletKit_FinalizePsbt_0 = runtime.ForwardResponseMessage forward_WalletKit_FinalizePsbt_0 = runtime.ForwardResponseMessage
) )
+25
View File
@@ -474,6 +474,31 @@ func RegisterWalletKitJSONCallbacks(registry map[string]func(ctx context.Context
callback(string(respBytes), nil) callback(string(respBytes), nil)
} }
registry["walletrpc.WalletKit.SignPsbt"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SignPsbtRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.SignPsbt(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.FinalizePsbt"] = func(ctx context.Context, registry["walletrpc.WalletKit.FinalizePsbt"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
+28
View File
@@ -200,6 +200,21 @@ service WalletKit {
*/ */
rpc FundPsbt (FundPsbtRequest) returns (FundPsbtResponse); rpc FundPsbt (FundPsbtRequest) returns (FundPsbtResponse);
/*
SignPsbt expects a partial transaction with all inputs and outputs fully
declared and tries to sign all unsigned inputs that have all required fields
(UTXO information, BIP32 derivation information, witness or sig scripts)
set.
If no error is returned, the PSBT is ready to be given to the next signer or
to be finalized if lnd was the last signer.
NOTE: This RPC only signs inputs (and only those it can sign), it does not
perform any other tasks (such as coin selection, UTXO locking or
input/output/fee value validation, PSBT finalization). Any input that is
incomplete will be skipped.
*/
rpc SignPsbt (SignPsbtRequest) returns (SignPsbtResponse);
/* /*
FinalizePsbt expects a partial transaction with all inputs and outputs fully FinalizePsbt expects a partial transaction with all inputs and outputs fully
declared and tries to sign all inputs that belong to the wallet. Lnd must be declared and tries to sign all inputs that belong to the wallet. Lnd must be
@@ -820,6 +835,19 @@ message UtxoLease {
uint64 expiration = 3; uint64 expiration = 3;
} }
message SignPsbtRequest {
/*
The PSBT that should be signed. The PSBT must contain all required inputs,
outputs, UTXO data and custom fields required to identify the signing key.
*/
bytes funded_psbt = 1;
}
message SignPsbtResponse {
// The signed transaction in PSBT format.
bytes signed_psbt = 1;
}
message FinalizePsbtRequest { message FinalizePsbtRequest {
/* /*
A PSBT that should be signed and finalized. The PSBT must contain all A PSBT that should be signed and finalized. The PSBT must contain all
+54
View File
@@ -364,6 +364,40 @@
] ]
} }
}, },
"/v2/wallet/psbt/sign": {
"post": {
"summary": "SignPsbt expects a partial transaction with all inputs and outputs fully\ndeclared and tries to sign all unsigned inputs that have all required fields\n(UTXO information, BIP32 derivation information, witness or sig scripts)\nset.\nIf no error is returned, the PSBT is ready to be given to the next signer or\nto be finalized if lnd was the last signer.",
"description": "NOTE: This RPC only signs inputs (and only those it can sign), it does not\nperform any other tasks (such as coin selection, UTXO locking or\ninput/output/fee value validation, PSBT finalization). Any input that is\nincomplete will be skipped.",
"operationId": "WalletKit_SignPsbt",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/walletrpcSignPsbtResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/walletrpcSignPsbtRequest"
}
}
],
"tags": [
"WalletKit"
]
}
},
"/v2/wallet/send": { "/v2/wallet/send": {
"post": { "post": {
"summary": "SendOutputs is similar to the existing sendmany call in Bitcoind, and\nallows the caller to create a transaction that sends to several outputs at\nonce. This is ideal when wanting to batch create a set of transactions.", "summary": "SendOutputs is similar to the existing sendmany call in Bitcoind, and\nallows the caller to create a transaction that sends to several outputs at\nonce. This is ideal when wanting to batch create a set of transactions.",
@@ -1392,6 +1426,26 @@
} }
} }
}, },
"walletrpcSignPsbtRequest": {
"type": "object",
"properties": {
"funded_psbt": {
"type": "string",
"format": "byte",
"description": "The PSBT that should be signed. The PSBT must contain all required inputs,\noutputs, UTXO data and custom fields required to identify the signing key."
}
}
},
"walletrpcSignPsbtResponse": {
"type": "object",
"properties": {
"signed_psbt": {
"type": "string",
"format": "byte",
"description": "The signed transaction in PSBT format."
}
}
},
"walletrpcTransaction": { "walletrpcTransaction": {
"type": "object", "type": "object",
"properties": { "properties": {
+3
View File
@@ -47,6 +47,9 @@ http:
- selector: walletrpc.WalletKit.FundPsbt - selector: walletrpc.WalletKit.FundPsbt
post: "/v2/wallet/psbt/fund" post: "/v2/wallet/psbt/fund"
body: "*" body: "*"
- selector: walletrpc.WalletKit.SignPsbt
post: "/v2/wallet/psbt/sign"
body: "*"
- selector: walletrpc.WalletKit.FinalizePsbt - selector: walletrpc.WalletKit.FinalizePsbt
post: "/v2/wallet/psbt/finalize" post: "/v2/wallet/psbt/finalize"
body: "*" body: "*"
+60
View File
@@ -172,6 +172,19 @@ type WalletKitClient interface {
//an error on the caller's side. //an error on the caller's side.
FundPsbt(ctx context.Context, in *FundPsbtRequest, opts ...grpc.CallOption) (*FundPsbtResponse, error) FundPsbt(ctx context.Context, in *FundPsbtRequest, opts ...grpc.CallOption) (*FundPsbtResponse, error)
// //
//SignPsbt expects a partial transaction with all inputs and outputs fully
//declared and tries to sign all unsigned inputs that have all required fields
//(UTXO information, BIP32 derivation information, witness or sig scripts)
//set.
//If no error is returned, the PSBT is ready to be given to the next signer or
//to be finalized if lnd was the last signer.
//
//NOTE: This RPC only signs inputs (and only those it can sign), it does not
//perform any other tasks (such as coin selection, UTXO locking or
//input/output/fee value validation, PSBT finalization). Any input that is
//incomplete will be skipped.
SignPsbt(ctx context.Context, in *SignPsbtRequest, opts ...grpc.CallOption) (*SignPsbtResponse, error)
//
//FinalizePsbt expects a partial transaction with all inputs and outputs fully //FinalizePsbt expects a partial transaction with all inputs and outputs fully
//declared and tries to sign all inputs that belong to the wallet. Lnd must be //declared and tries to sign all inputs that belong to the wallet. Lnd must be
//the last signer of the transaction. That means, if there are any unsigned //the last signer of the transaction. That means, if there are any unsigned
@@ -356,6 +369,15 @@ func (c *walletKitClient) FundPsbt(ctx context.Context, in *FundPsbtRequest, opt
return out, nil return out, nil
} }
func (c *walletKitClient) SignPsbt(ctx context.Context, in *SignPsbtRequest, opts ...grpc.CallOption) (*SignPsbtResponse, error) {
out := new(SignPsbtResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/SignPsbt", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) FinalizePsbt(ctx context.Context, in *FinalizePsbtRequest, opts ...grpc.CallOption) (*FinalizePsbtResponse, error) { func (c *walletKitClient) FinalizePsbt(ctx context.Context, in *FinalizePsbtRequest, opts ...grpc.CallOption) (*FinalizePsbtResponse, error) {
out := new(FinalizePsbtResponse) out := new(FinalizePsbtResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/FinalizePsbt", in, out, opts...) err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/FinalizePsbt", in, out, opts...)
@@ -522,6 +544,19 @@ type WalletKitServer interface {
//an error on the caller's side. //an error on the caller's side.
FundPsbt(context.Context, *FundPsbtRequest) (*FundPsbtResponse, error) FundPsbt(context.Context, *FundPsbtRequest) (*FundPsbtResponse, error)
// //
//SignPsbt expects a partial transaction with all inputs and outputs fully
//declared and tries to sign all unsigned inputs that have all required fields
//(UTXO information, BIP32 derivation information, witness or sig scripts)
//set.
//If no error is returned, the PSBT is ready to be given to the next signer or
//to be finalized if lnd was the last signer.
//
//NOTE: This RPC only signs inputs (and only those it can sign), it does not
//perform any other tasks (such as coin selection, UTXO locking or
//input/output/fee value validation, PSBT finalization). Any input that is
//incomplete will be skipped.
SignPsbt(context.Context, *SignPsbtRequest) (*SignPsbtResponse, error)
//
//FinalizePsbt expects a partial transaction with all inputs and outputs fully //FinalizePsbt expects a partial transaction with all inputs and outputs fully
//declared and tries to sign all inputs that belong to the wallet. Lnd must be //declared and tries to sign all inputs that belong to the wallet. Lnd must be
//the last signer of the transaction. That means, if there are any unsigned //the last signer of the transaction. That means, if there are any unsigned
@@ -595,6 +630,9 @@ func (UnimplementedWalletKitServer) LabelTransaction(context.Context, *LabelTran
func (UnimplementedWalletKitServer) FundPsbt(context.Context, *FundPsbtRequest) (*FundPsbtResponse, error) { func (UnimplementedWalletKitServer) FundPsbt(context.Context, *FundPsbtRequest) (*FundPsbtResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method FundPsbt not implemented") return nil, status.Errorf(codes.Unimplemented, "method FundPsbt not implemented")
} }
func (UnimplementedWalletKitServer) SignPsbt(context.Context, *SignPsbtRequest) (*SignPsbtResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SignPsbt not implemented")
}
func (UnimplementedWalletKitServer) FinalizePsbt(context.Context, *FinalizePsbtRequest) (*FinalizePsbtResponse, error) { func (UnimplementedWalletKitServer) FinalizePsbt(context.Context, *FinalizePsbtRequest) (*FinalizePsbtResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method FinalizePsbt not implemented") return nil, status.Errorf(codes.Unimplemented, "method FinalizePsbt not implemented")
} }
@@ -935,6 +973,24 @@ func _WalletKit_FundPsbt_Handler(srv interface{}, ctx context.Context, dec func(
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _WalletKit_SignPsbt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SignPsbtRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).SignPsbt(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/SignPsbt",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).SignPsbt(ctx, req.(*SignPsbtRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_FinalizePsbt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _WalletKit_FinalizePsbt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(FinalizePsbtRequest) in := new(FinalizePsbtRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@@ -1032,6 +1088,10 @@ var WalletKit_ServiceDesc = grpc.ServiceDesc{
MethodName: "FundPsbt", MethodName: "FundPsbt",
Handler: _WalletKit_FundPsbt_Handler, Handler: _WalletKit_FundPsbt_Handler,
}, },
{
MethodName: "SignPsbt",
Handler: _WalletKit_SignPsbt_Handler,
},
{ {
MethodName: "FinalizePsbt", MethodName: "FinalizePsbt",
Handler: _WalletKit_FinalizePsbt_Handler, Handler: _WalletKit_FinalizePsbt_Handler,
+45
View File
@@ -122,6 +122,10 @@ var (
Entity: "onchain", Entity: "onchain",
Action: "write", Action: "write",
}}, }},
"/walletrpc.WalletKit/SignPsbt": {{
Entity: "onchain",
Action: "write",
}},
"/walletrpc.WalletKit/FinalizePsbt": {{ "/walletrpc.WalletKit/FinalizePsbt": {{
Entity: "onchain", Entity: "onchain",
Action: "write", Action: "write",
@@ -1188,6 +1192,47 @@ func marshallLeases(locks []*wtxmgr.LockedOutput) []*UtxoLease {
return rpcLocks return rpcLocks
} }
// SignPsbt expects a partial transaction with all inputs and outputs fully
// declared and tries to sign all unsigned inputs that have all required fields
// (UTXO information, BIP32 derivation information, witness or sig scripts)
// set.
// If no error is returned, the PSBT is ready to be given to the next signer or
// to be finalized if lnd was the last signer.
//
// NOTE: This RPC only signs inputs (and only those it can sign), it does not
// perform any other tasks (such as coin selection, UTXO locking or
// input/output/fee value validation, PSBT finalization). Any input that is
// incomplete will be skipped.
func (w *WalletKit) SignPsbt(_ context.Context, req *SignPsbtRequest) (
*SignPsbtResponse, error) {
packet, err := psbt.NewFromRawBytes(
bytes.NewReader(req.FundedPsbt), false,
)
if err != nil {
return nil, fmt.Errorf("error parsing PSBT: %v", err)
}
// Let the wallet do the heavy lifting. This will sign all inputs that
// we have the UTXO for. If some inputs can't be signed and don't have
// witness data attached, they will just be skipped.
err = w.cfg.Wallet.SignPsbt(packet)
if err != nil {
return nil, fmt.Errorf("error signing PSBT: %v", err)
}
// Serialize the signed PSBT in both the packet and wire format.
var signedPsbtBytes bytes.Buffer
err = packet.Serialize(&signedPsbtBytes)
if err != nil {
return nil, fmt.Errorf("error serializing PSBT: %v", err)
}
return &SignPsbtResponse{
SignedPsbt: signedPsbtBytes.Bytes(),
}, nil
}
// FinalizePsbt expects a partial transaction with all inputs and outputs fully // FinalizePsbt expects a partial transaction with all inputs and outputs fully
// declared and tries to sign all inputs that belong to the wallet. Lnd must be // declared and tries to sign all inputs that belong to the wallet. Lnd must be
// the last signer of the transaction. That means, if there are any unsigned // the last signer of the transaction. That means, if there are any unsigned
+195 -38
View File
@@ -6,12 +6,15 @@ import (
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcutil/psbt" "github.com/btcsuite/btcutil/psbt"
"github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -40,6 +43,12 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol,
dave *lntest.HarnessNode) { dave *lntest.HarnessNode) {
ctxb := context.Background() ctxb := context.Background()
// Everything we do here should be done within a second or two, so we
// can just keep a single timeout context around for all calls.
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
const chanSize = funding.MaxBtcFundingAmount const chanSize = funding.MaxBtcFundingAmount
net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, dave) net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, dave)
@@ -63,8 +72,6 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol,
// Now that we have the pending channel ID, Carol will open the channel // Now that we have the pending channel ID, Carol will open the channel
// by specifying a PSBT shim. We use the NoPublish flag here to avoid // by specifying a PSBT shim. We use the NoPublish flag here to avoid
// publishing the whole batch TX too early. // publishing the whole batch TX too early.
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
chanUpdates, tempPsbt, err := openChannelPsbt( chanUpdates, tempPsbt, err := openChannelPsbt(
ctxt, carol, dave, lntest.OpenChannelParams{ ctxt, carol, dave, lntest.OpenChannelParams{
Amt: chanSize, Amt: chanSize,
@@ -83,8 +90,6 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol,
// Let's add a second channel to the batch. This time between Carol and // Let's add a second channel to the batch. This time between Carol and
// Alice. We will publish the batch TX once this channel funding is // Alice. We will publish the batch TX once this channel funding is
// complete. // complete.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
chanUpdates2, psbtBytes2, err := openChannelPsbt( chanUpdates2, psbtBytes2, err := openChannelPsbt(
ctxt, carol, net.Alice, lntest.OpenChannelParams{ ctxt, carol, net.Alice, lntest.OpenChannelParams{
Amt: chanSize, Amt: chanSize,
@@ -104,8 +109,6 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol,
// We'll now ask Dave's wallet to fund the PSBT for us. This will return // We'll now ask Dave's wallet to fund the PSBT for us. This will return
// a packet with inputs and outputs set but without any witness data. // a packet with inputs and outputs set but without any witness data.
// This is exactly what we need for the next step. // This is exactly what we need for the next step.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
fundReq := &walletrpc.FundPsbtRequest{ fundReq := &walletrpc.FundPsbtRequest{
Template: &walletrpc.FundPsbtRequest_Psbt{ Template: &walletrpc.FundPsbtRequest_Psbt{
Psbt: psbtBytes2, Psbt: psbtBytes2,
@@ -140,8 +143,6 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol,
// Now we'll ask Dave's wallet to sign the PSBT so we can finish the // Now we'll ask Dave's wallet to sign the PSBT so we can finish the
// funding flow. // funding flow.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
finalizeReq := &walletrpc.FinalizePsbtRequest{ finalizeReq := &walletrpc.FinalizePsbtRequest{
FundedPsbt: fundResp.FundedPsbt, FundedPsbt: fundResp.FundedPsbt,
} }
@@ -161,8 +162,6 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol,
// Consume the "channel pending" update. This waits until the funding // Consume the "channel pending" update. This waits until the funding
// transaction was fully compiled. // transaction was fully compiled.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
updateResp, err := receiveChanUpdate(ctxt, chanUpdates) updateResp, err := receiveChanUpdate(ctxt, chanUpdates)
require.NoError(t.t, err) require.NoError(t.t, err)
upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending) upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
@@ -195,8 +194,6 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol,
// Consume the "channel pending" update for the second channel. This // Consume the "channel pending" update for the second channel. This
// waits until the funding transaction was fully compiled and in this // waits until the funding transaction was fully compiled and in this
// case published. // case published.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
updateResp2, err := receiveChanUpdate(ctxt, chanUpdates2) updateResp2, err := receiveChanUpdate(ctxt, chanUpdates2)
require.NoError(t.t, err) require.NoError(t.t, err)
upd2, ok := updateResp2.Update.(*lnrpc.OpenStatusUpdate_ChanPending) upd2, ok := updateResp2.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
@@ -225,8 +222,6 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol,
// With the channel open, ensure that it is counted towards Carol's // With the channel open, ensure that it is counted towards Carol's
// total channel balance. // total channel balance.
balReq := &lnrpc.ChannelBalanceRequest{} balReq := &lnrpc.ChannelBalanceRequest{}
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
balRes, err := carol.ChannelBalance(ctxt, balReq) balRes, err := carol.ChannelBalance(ctxt, balReq)
require.NoError(t.t, err) require.NoError(t.t, err)
require.NotEqual(t.t, int64(0), balRes.LocalBalance.Sat) require.NotEqual(t.t, int64(0), balRes.LocalBalance.Sat)
@@ -238,7 +233,6 @@ func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol,
Memo: "new chans", Memo: "new chans",
Value: int64(payAmt), Value: int64(payAmt),
} }
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err := dave.AddInvoice(ctxt, invoice) resp, err := dave.AddInvoice(ctxt, invoice)
require.NoError(t.t, err) require.NoError(t.t, err)
err = completePaymentRequests( err = completePaymentRequests(
@@ -261,6 +255,11 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background() ctxb := context.Background()
const chanSize = funding.MaxBtcFundingAmount const chanSize = funding.MaxBtcFundingAmount
// Everything we do here should be done within a second or two, so we
// can just keep a single timeout context around for all calls.
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
// First, we'll create two new nodes that we'll use to open channels // First, we'll create two new nodes that we'll use to open channels
// between for this test. Both these nodes have an empty wallet as Alice // between for this test. Both these nodes have an empty wallet as Alice
// will be funding the channel. // will be funding the channel.
@@ -290,8 +289,6 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) {
// Now that we have the pending channel ID, Carol will open the channel // Now that we have the pending channel ID, Carol will open the channel
// by specifying a PSBT shim. We use the NoPublish flag here to avoid // by specifying a PSBT shim. We use the NoPublish flag here to avoid
// publishing the whole batch TX too early. // publishing the whole batch TX too early.
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
chanUpdates, tempPsbt, err := openChannelPsbt( chanUpdates, tempPsbt, err := openChannelPsbt(
ctxt, carol, dave, lntest.OpenChannelParams{ ctxt, carol, dave, lntest.OpenChannelParams{
Amt: chanSize, Amt: chanSize,
@@ -310,8 +307,6 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) {
// Let's add a second channel to the batch. This time between Carol and // Let's add a second channel to the batch. This time between Carol and
// Alice. We will publish the batch TX once this channel funding is // Alice. We will publish the batch TX once this channel funding is
// complete. // complete.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
chanUpdates2, psbtBytes2, err := openChannelPsbt( chanUpdates2, psbtBytes2, err := openChannelPsbt(
ctxt, carol, net.Alice, lntest.OpenChannelParams{ ctxt, carol, net.Alice, lntest.OpenChannelParams{
Amt: chanSize, Amt: chanSize,
@@ -331,8 +326,6 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) {
// We'll now ask Alice's wallet to fund the PSBT for us. This will // We'll now ask Alice's wallet to fund the PSBT for us. This will
// return a packet with inputs and outputs set but without any witness // return a packet with inputs and outputs set but without any witness
// data. This is exactly what we need for the next step. // data. This is exactly what we need for the next step.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
fundReq := &walletrpc.FundPsbtRequest{ fundReq := &walletrpc.FundPsbtRequest{
Template: &walletrpc.FundPsbtRequest_Psbt{ Template: &walletrpc.FundPsbtRequest_Psbt{
Psbt: psbtBytes2, Psbt: psbtBytes2,
@@ -375,8 +368,6 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) {
// Consume the "channel pending" update. This waits until the funding // Consume the "channel pending" update. This waits until the funding
// transaction was fully compiled for both channels. // transaction was fully compiled for both channels.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
updateResp, err := receiveChanUpdate(ctxt, chanUpdates) updateResp, err := receiveChanUpdate(ctxt, chanUpdates)
require.NoError(t.t, err) require.NoError(t.t, err)
upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending) upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
@@ -403,8 +394,6 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) {
// Now we'll ask Alice's wallet to sign the PSBT so we can finish the // Now we'll ask Alice's wallet to sign the PSBT so we can finish the
// funding flow. // funding flow.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
finalizeReq := &walletrpc.FinalizePsbtRequest{ finalizeReq := &walletrpc.FinalizePsbtRequest{
FundedPsbt: fundResp.FundedPsbt, FundedPsbt: fundResp.FundedPsbt,
} }
@@ -439,8 +428,6 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) {
// With the channel open, ensure that it is counted towards Carol's // With the channel open, ensure that it is counted towards Carol's
// total channel balance. // total channel balance.
balReq := &lnrpc.ChannelBalanceRequest{} balReq := &lnrpc.ChannelBalanceRequest{}
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
balRes, err := carol.ChannelBalance(ctxt, balReq) balRes, err := carol.ChannelBalance(ctxt, balReq)
require.NoError(t.t, err) require.NoError(t.t, err)
require.NotEqual(t.t, int64(0), balRes.LocalBalance.Sat) require.NotEqual(t.t, int64(0), balRes.LocalBalance.Sat)
@@ -452,7 +439,6 @@ func testPsbtChanFundingExternal(net *lntest.NetworkHarness, t *harnessTest) {
Memo: "new chans", Memo: "new chans",
Value: int64(payAmt), Value: int64(payAmt),
} }
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err := dave.AddInvoice(ctxt, invoice) resp, err := dave.AddInvoice(ctxt, invoice)
require.NoError(t.t, err) require.NoError(t.t, err)
err = completePaymentRequests( err = completePaymentRequests(
@@ -476,6 +462,11 @@ func testPsbtChanFundingSingleStep(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background() ctxb := context.Background()
const chanSize = funding.MaxBtcFundingAmount const chanSize = funding.MaxBtcFundingAmount
// Everything we do here should be done within a second or two, so we
// can just keep a single timeout context around for all calls.
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
args := nodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS) args := nodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS)
// First, we'll create two new nodes that we'll use to open channels // First, we'll create two new nodes that we'll use to open channels
@@ -513,8 +504,6 @@ func testPsbtChanFundingSingleStep(net *lntest.NetworkHarness, t *harnessTest) {
// Now that we have the pending channel ID, Carol will open the channel // Now that we have the pending channel ID, Carol will open the channel
// by specifying a PSBT shim. // by specifying a PSBT shim.
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
chanUpdates, tempPsbt, err := openChannelPsbt( chanUpdates, tempPsbt, err := openChannelPsbt(
ctxt, carol, dave, lntest.OpenChannelParams{ ctxt, carol, dave, lntest.OpenChannelParams{
Amt: chanSize, Amt: chanSize,
@@ -547,8 +536,6 @@ func testPsbtChanFundingSingleStep(net *lntest.NetworkHarness, t *harnessTest) {
err = decodedPsbt.Serialize(&psbtBytes) err = decodedPsbt.Serialize(&psbtBytes)
require.NoError(t.t, err) require.NoError(t.t, err)
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
fundReq := &walletrpc.FundPsbtRequest{ fundReq := &walletrpc.FundPsbtRequest{
Template: &walletrpc.FundPsbtRequest_Psbt{ Template: &walletrpc.FundPsbtRequest_Psbt{
Psbt: psbtBytes.Bytes(), Psbt: psbtBytes.Bytes(),
@@ -583,8 +570,6 @@ func testPsbtChanFundingSingleStep(net *lntest.NetworkHarness, t *harnessTest) {
// Now we'll ask Alice's wallet to sign the PSBT so we can finish the // Now we'll ask Alice's wallet to sign the PSBT so we can finish the
// funding flow. // funding flow.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
finalizeReq := &walletrpc.FinalizePsbtRequest{ finalizeReq := &walletrpc.FinalizePsbtRequest{
FundedPsbt: fundResp.FundedPsbt, FundedPsbt: fundResp.FundedPsbt,
} }
@@ -604,8 +589,6 @@ func testPsbtChanFundingSingleStep(net *lntest.NetworkHarness, t *harnessTest) {
// Consume the "channel pending" update. This waits until the funding // Consume the "channel pending" update. This waits until the funding
// transaction was fully compiled. // transaction was fully compiled.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
updateResp, err := receiveChanUpdate(ctxt, chanUpdates) updateResp, err := receiveChanUpdate(ctxt, chanUpdates)
require.NoError(t.t, err) require.NoError(t.t, err)
upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending) upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
@@ -634,8 +617,6 @@ func testPsbtChanFundingSingleStep(net *lntest.NetworkHarness, t *harnessTest) {
Memo: "new chans", Memo: "new chans",
Value: int64(payAmt), Value: int64(payAmt),
} }
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
resp, err := dave.AddInvoice(ctxt, invoice) resp, err := dave.AddInvoice(ctxt, invoice)
require.NoError(t.t, err) require.NoError(t.t, err)
err = completePaymentRequests( err = completePaymentRequests(
@@ -651,6 +632,182 @@ func testPsbtChanFundingSingleStep(net *lntest.NetworkHarness, t *harnessTest) {
closeChannelAndAssert(t, net, carol, chanPoint, false) closeChannelAndAssert(t, net, carol, chanPoint, false)
} }
// runSignPsbt tests that the SignPsbt RPC works correctly.
func testSignPsbt(net *lntest.NetworkHarness, t *harnessTest) {
runSignPsbt(t, net, net.Alice)
}
// runSignPsbt tests that the SignPsbt RPC works correctly.
func runSignPsbt(t *harnessTest, net *lntest.NetworkHarness,
alice *lntest.HarnessNode) {
ctxb := context.Background()
// Everything we do here should be done within a second or two, so we
// can just keep a single timeout context around for all calls.
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
// We test that we can sign a PSBT that spends funds from an input that
// the wallet doesn't know about. To set up that test case, we first
// derive an address manually that the wallet won't be watching on
// chain. We can do that by exporting the account xpub of lnd's main
// account.
accounts, err := alice.WalletKitClient.ListAccounts(
ctxt, &walletrpc.ListAccountsRequest{},
)
require.NoError(t.t, err)
require.NotEmpty(t.t, accounts.Accounts)
// We also need to parse the accounts, so we have easy access to the
// parsed derivation paths.
parsedAccounts, err := walletrpc.AccountsToWatchOnly(accounts.Accounts)
require.NoError(t.t, err)
account := parsedAccounts[0]
xpub, err := hdkeychain.NewKeyFromString(account.Xpub)
require.NoError(t.t, err)
const (
changeIndex = 1
addrIndex = 1337
)
fullDerivationPath := []uint32{
hdkeychain.HardenedKeyStart + account.Purpose,
hdkeychain.HardenedKeyStart + account.CoinType,
hdkeychain.HardenedKeyStart + account.Account,
changeIndex,
addrIndex,
}
// Let's simulate a change address.
change, err := xpub.DeriveNonStandard(changeIndex) // nolint:staticcheck
require.NoError(t.t, err)
// At an index that we are certainly not watching in the wallet.
addrKey, err := change.DeriveNonStandard(addrIndex) // nolint:staticcheck
require.NoError(t.t, err)
addrPubKey, err := addrKey.ECPubKey()
require.NoError(t.t, err)
pubKeyHash := btcutil.Hash160(addrPubKey.SerializeCompressed())
witnessAddr, err := btcutil.NewAddressWitnessPubKeyHash(
pubKeyHash, harnessNetParams,
)
require.NoError(t.t, err)
pkScript, err := txscript.PayToAddrScript(witnessAddr)
require.NoError(t.t, err)
// Let's send some coins to that address now.
utxo := &wire.TxOut{
Value: 600_000,
PkScript: pkScript,
}
resp, err := alice.WalletKitClient.SendOutputs(
ctxt, &walletrpc.SendOutputsRequest{
Outputs: []*signrpc.TxOut{{
Value: utxo.Value,
PkScript: utxo.PkScript,
}},
MinConfs: 0,
SpendUnconfirmed: true,
SatPerKw: 2500,
},
)
require.NoError(t.t, err)
prevTx := wire.NewMsgTx(2)
err = prevTx.Deserialize(bytes.NewReader(resp.RawTx))
require.NoError(t.t, err)
prevOut := -1
for idx, txOut := range prevTx.TxOut {
if bytes.Equal(txOut.PkScript, pkScript) {
prevOut = idx
}
}
require.Greater(t.t, prevOut, -1)
// Okay, we have everything we need to create a PSBT now.
pendingTx := &wire.MsgTx{
Version: 2,
TxIn: []*wire.TxIn{{
PreviousOutPoint: wire.OutPoint{
Hash: prevTx.TxHash(),
Index: uint32(prevOut),
},
}},
// We send to the same address again, but deduct some fees.
TxOut: []*wire.TxOut{{
Value: utxo.Value - 600,
PkScript: utxo.PkScript,
}},
}
packet, err := psbt.NewFromUnsignedTx(pendingTx)
require.NoError(t.t, err)
// Now let's add the meta information that we need for signing.
packet.Inputs[0].Bip32Derivation = []*psbt.Bip32Derivation{{
PubKey: addrPubKey.SerializeCompressed(),
Bip32Path: fullDerivationPath,
}}
packet.Inputs[0].WitnessUtxo = utxo
packet.Inputs[0].NonWitnessUtxo = prevTx
packet.Inputs[0].SighashType = txscript.SigHashAll
// That's it, we should be able to sign the PSBT now.
var buf bytes.Buffer
err = packet.Serialize(&buf)
require.NoError(t.t, err)
signResp, err := alice.WalletKitClient.SignPsbt(
ctxt, &walletrpc.SignPsbtRequest{
FundedPsbt: buf.Bytes(),
},
)
require.NoError(t.t, err)
// Let's make sure we have a partial signature.
signedPacket, err := psbt.NewFromRawBytes(
bytes.NewReader(signResp.SignedPsbt), false,
)
require.NoError(t.t, err)
require.Len(t.t, signedPacket.Inputs, 1)
require.Len(t.t, signedPacket.Inputs[0].PartialSigs, 1)
partialSig := signedPacket.Inputs[0].PartialSigs[0]
require.Equal(t.t, partialSig.PubKey, addrPubKey.SerializeCompressed())
require.Greater(t.t, len(partialSig.Signature), btcec.MinSigLen)
// We should be able to finalize the PSBT and extract the final TX now.
err = psbt.MaybeFinalizeAll(signedPacket)
require.NoError(t.t, err)
finalTx, err := psbt.Extract(signedPacket)
require.NoError(t.t, err)
buf.Reset()
err = finalTx.Serialize(&buf)
require.NoError(t.t, err)
// Publish the second transaction and then mine both of them.
_, err = alice.WalletKitClient.PublishTransaction(
ctxt, &walletrpc.Transaction{
TxHex: buf.Bytes(),
},
)
require.NoError(t.t, err)
// Mine one block which should contain two transactions.
block := mineBlocks(t, net, 1, 2)[0]
firstTxHash := prevTx.TxHash()
secondTxHash := finalTx.TxHash()
assertTxInBlock(t, block, &firstTxHash)
assertTxInBlock(t, block, &secondTxHash)
}
// openChannelPsbt attempts to open a channel between srcNode and destNode with // openChannelPsbt attempts to open a channel between srcNode and destNode with
// the passed channel funding parameters. If the passed context has a timeout, // the passed channel funding parameters. If the passed context has a timeout,
// then if the timeout is reached before the channel pending notification is // then if the timeout is reached before the channel pending notification is
+55 -75
View File
@@ -6,6 +6,7 @@ import (
"testing" "testing"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
@@ -39,76 +40,6 @@ var (
Xpub: "tpubDDWAWrSLRSFrG1KdqXMQQyTKYGSKLKaY7gxpvK7RdV3e3Dkhvu" + Xpub: "tpubDDWAWrSLRSFrG1KdqXMQQyTKYGSKLKaY7gxpvK7RdV3e3Dkhvu" +
"W2GgsFvsPN4RGmuoYtUgZ1LHZE8oftz7T4mzc1BxGt5rt8zJcVQi" + "W2GgsFvsPN4RGmuoYtUgZ1LHZE8oftz7T4mzc1BxGt5rt8zJcVQi" +
"KTPPV", "KTPPV",
}, {
Purpose: keychain.BIP0043Purpose,
CoinType: harnessNetParams.HDCoinType,
Account: uint32(keychain.KeyFamilyMultiSig),
Xpub: "tpubDDXFHr67Ro2tHKVWG2gNjjijKUH1Lyv5NKFYdJnuaLGVNBVwyV" +
"5AbykhR43iy8wYozEMbw2QfmAqZhb8gnuL5mm9sZh8YsR6FjGAbe" +
"w1xoT",
}, {
Purpose: keychain.BIP0043Purpose,
CoinType: harnessNetParams.HDCoinType,
Account: uint32(keychain.KeyFamilyRevocationBase),
Xpub: "tpubDDXFHr67Ro2tKkccDqNfDqZpd5wCs2n6XRV2Uh185DzCTbkDaE" +
"d9v7P837zZTYBNVfaRriuxgGVgxbGjDui4CKxyzBzwz4aAZxjn2P" +
"hNcQy",
}, {
Purpose: keychain.BIP0043Purpose,
CoinType: harnessNetParams.HDCoinType,
Account: uint32(keychain.KeyFamilyHtlcBase),
Xpub: "tpubDDXFHr67Ro2tNH4KH41i4oTsWfRjFigoH1Ee7urvHow51opH9x" +
"J7mu1qSPMPVtkVqQZ5tE4NTuFJPrbDqno7TQietyUDmPTwyVviJb" +
"GCwXk",
}, {
Purpose: keychain.BIP0043Purpose,
CoinType: harnessNetParams.HDCoinType,
Account: uint32(keychain.KeyFamilyPaymentBase),
Xpub: "tpubDDXFHr67Ro2tQj5Zvav2ALhkU6dRQAhEtNPnYJVBC8hs2U1A9e" +
"cqxRY3XTiJKBDD7e8tudhmTRs8aGWJAiAXJN5kXy3Hi6cmiwGWjX" +
"K5Cv5",
}, {
Purpose: keychain.BIP0043Purpose,
CoinType: harnessNetParams.HDCoinType,
Account: uint32(keychain.KeyFamilyDelayBase),
Xpub: "tpubDDXFHr67Ro2tSSR2LLBJtotxx2U45cuESLWKA72YT9td3SzVKH" +
"AptzDEx5chsUNZ4WRMY5h6HJxRSebjRatxQKX1uUsux1LvKS1wsf" +
"NJ2PH",
}, {
Purpose: keychain.BIP0043Purpose,
CoinType: harnessNetParams.HDCoinType,
Account: uint32(keychain.KeyFamilyRevocationRoot),
Xpub: "tpubDDXFHr67Ro2tTwzfWvNoMoPpZbxdMEfe1WhbXJxvXikGixPa4g" +
"gSRZeGx6T5yxVHTVT3rjVh35Veqsowj7emX8SZfXKDKDKcLduXCe" +
"WPUU3",
}, {
Purpose: keychain.BIP0043Purpose,
CoinType: harnessNetParams.HDCoinType,
Account: uint32(keychain.KeyFamilyNodeKey),
Xpub: "tpubDDXFHr67Ro2tYEDS2EByRedfsUoEwBtrzVbS1qdPrX6sAkUYGL" +
"rZWvMmQv8KZDZ4zd9r8WzM9bJ2nGp7XuNVC4w2EBtWg7i76gbrmu" +
"EWjQh",
}, {
Purpose: keychain.BIP0043Purpose,
CoinType: harnessNetParams.HDCoinType,
Account: uint32(keychain.KeyFamilyStaticBackup),
Xpub: "tpubDDXFHr67Ro2tYpwnFJEQaM8eAPM2UV5uY6gFgXeSzS5aC5T9Tf" +
"zXuawYKBbQMZJn8qHXLafY4tAutoda1aKP5h6Nbgy3swPbnhWbFj" +
"S5wnX",
}, {
Purpose: keychain.BIP0043Purpose,
CoinType: harnessNetParams.HDCoinType,
Account: uint32(keychain.KeyFamilyTowerSession),
Xpub: "tpubDDXFHr67Ro2tddKpAjUegXqt7EGxRXnHkeLbUkfuFMGbLJYgRp" +
"G4ew5pMmGg2nmcGmHFQ29w3juNhd8N5ZZ8HwJdymC4f5ukQLJ4yg" +
"9rEr3",
}, {
Purpose: keychain.BIP0043Purpose,
CoinType: harnessNetParams.HDCoinType,
Account: uint32(keychain.KeyFamilyTowerID),
Xpub: "tpubDDXFHr67Ro2tgE89V8ZdgMytC2Jq1iT9ttGhdzR1X7haQJNBmX" +
"t8kau6taC6DGASYzbrjmo9z9w6JQFcaLNqbhS2h2PVSzKf79j265" +
"Zi8hF",
}} }}
) )
@@ -166,6 +97,7 @@ func testRemoteSigner(net *lntest.NetworkHarness, t *harnessTest) {
name: "psbt", name: "psbt",
fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) {
runPsbtChanFunding(net, tt, carol, wo) runPsbtChanFunding(net, tt, carol, wo)
runSignPsbt(tt, net, wo)
}, },
}} }}
@@ -180,7 +112,7 @@ func testRemoteSigner(net *lntest.NetworkHarness, t *harnessTest) {
password := []byte("itestpassword") password := []byte("itestpassword")
var ( var (
signerNodePubKey = nodePubKey signerNodePubKey = nodePubKey
watchOnlyAccounts = accounts watchOnlyAccounts = deriveCustomScopeAccounts(t.t)
signer *lntest.HarnessNode signer *lntest.HarnessNode
err error err error
) )
@@ -237,10 +169,6 @@ func testRemoteSigner(net *lntest.NetworkHarness, t *harnessTest) {
if subTest.sendCoins { if subTest.sendCoins {
net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, watchOnly) net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, watchOnly)
assertAccountBalance(
t.t, signer, "default",
btcutil.SatoshiPerBitcoin, 0,
)
assertAccountBalance( assertAccountBalance(
t.t, watchOnly, "default", t.t, watchOnly, "default",
btcutil.SatoshiPerBitcoin, 0, btcutil.SatoshiPerBitcoin, 0,
@@ -264,3 +192,55 @@ func testRemoteSigner(net *lntest.NetworkHarness, t *harnessTest) {
} }
} }
} }
// deriveCustomScopeAccounts derives the first 255 default accounts of the custom lnd
// internal key scope.
func deriveCustomScopeAccounts(t *testing.T) []*lnrpc.WatchOnlyAccount {
allAccounts := make([]*lnrpc.WatchOnlyAccount, 0, 255+len(accounts))
allAccounts = append(allAccounts, accounts...)
extendedRootKey, err := hdkeychain.NewKeyFromString(rootKey)
require.NoError(t, err)
path := []uint32{
keychain.BIP0043Purpose + hdkeychain.HardenedKeyStart,
harnessNetParams.HDCoinType + hdkeychain.HardenedKeyStart,
}
coinTypeKey, err := derivePath(extendedRootKey, path)
require.NoError(t, err)
for idx := uint32(0); idx <= 255; idx++ {
accountPath := []uint32{idx + hdkeychain.HardenedKeyStart}
accountKey, err := derivePath(coinTypeKey, accountPath)
require.NoError(t, err)
accountXPub, err := accountKey.Neuter()
require.NoError(t, err)
allAccounts = append(allAccounts, &lnrpc.WatchOnlyAccount{
Purpose: keychain.BIP0043Purpose,
CoinType: harnessNetParams.HDCoinType,
Account: idx,
Xpub: accountXPub.String(),
})
}
return allAccounts
}
// derivePath derives the given path from an extended key.
func derivePath(key *hdkeychain.ExtendedKey, path []uint32) (
*hdkeychain.ExtendedKey, error) {
var (
currentKey = key
err error
)
for _, pathPart := range path {
currentKey, err = currentKey.Derive(pathPart)
if err != nil {
return nil, err
}
}
return currentKey, nil
}
+4
View File
@@ -283,6 +283,10 @@ var allTestCases = []*testCase{
name: "psbt channel funding external", name: "psbt channel funding external",
test: testPsbtChanFundingExternal, test: testPsbtChanFundingExternal,
}, },
{
name: "sign psbt",
test: testSignPsbt,
},
{ {
name: "batch channel funding", name: "batch channel funding",
test: testBatchChanFunding, test: testBatchChanFunding,
+40 -21
View File
@@ -53,15 +53,25 @@ func (w *WalletController) FetchInputInfo(
return utxo, nil return utxo, nil
} }
// 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.
func (w *WalletController) ScriptForOutput(*wire.TxOut) (
waddrmgr.ManagedPubKeyAddress, []byte, []byte, error) {
return nil, nil, nil, nil
}
// ConfirmedBalance currently returns dummy values. // ConfirmedBalance currently returns dummy values.
func (w *WalletController) ConfirmedBalance(confs int32, func (w *WalletController) ConfirmedBalance(int32, string) (btcutil.Amount,
_ string) (btcutil.Amount, error) { error) {
return 0, nil return 0, nil
} }
// NewAddress is called to get new addresses for delivery, change etc. // NewAddress is called to get new addresses for delivery, change etc.
func (w *WalletController) NewAddress(addrType lnwallet.AddressType, func (w *WalletController) NewAddress(lnwallet.AddressType, bool,
change bool, _ string) (btcutil.Address, error) { string) (btcutil.Address, error) {
addr, _ := btcutil.NewAddressPubKey( addr, _ := btcutil.NewAddressPubKey(
w.RootKey.PubKey().SerializeCompressed(), &chaincfg.MainNetParams, w.RootKey.PubKey().SerializeCompressed(), &chaincfg.MainNetParams,
@@ -70,19 +80,21 @@ func (w *WalletController) NewAddress(addrType lnwallet.AddressType,
} }
// LastUnusedAddress currently returns dummy values. // LastUnusedAddress currently returns dummy values.
func (w *WalletController) LastUnusedAddress(addrType lnwallet.AddressType, func (w *WalletController) LastUnusedAddress(lnwallet.AddressType,
_ string) (btcutil.Address, error) { string) (btcutil.Address, error) {
return nil, nil return nil, nil
} }
// IsOurAddress currently returns a dummy value. // IsOurAddress currently returns a dummy value.
func (w *WalletController) IsOurAddress(a btcutil.Address) bool { func (w *WalletController) IsOurAddress(btcutil.Address) bool {
return false return false
} }
// ListAccounts currently returns a dummy value. // ListAccounts currently returns a dummy value.
func (w *WalletController) ListAccounts(_ string, func (w *WalletController) ListAccounts(string,
_ *waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties, error) { *waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties, error) {
return nil, nil return nil, nil
} }
@@ -90,33 +102,35 @@ func (w *WalletController) ListAccounts(_ string,
func (w *WalletController) ImportAccount(string, *hdkeychain.ExtendedKey, func (w *WalletController) ImportAccount(string, *hdkeychain.ExtendedKey,
uint32, *waddrmgr.AddressType, bool) (*waddrmgr.AccountProperties, uint32, *waddrmgr.AddressType, bool) (*waddrmgr.AccountProperties,
[]btcutil.Address, []btcutil.Address, error) { []btcutil.Address, []btcutil.Address, error) {
return nil, nil, nil, nil return nil, nil, nil, nil
} }
// ImportPublicKey currently returns a dummy value. // ImportPublicKey currently returns a dummy value.
func (w *WalletController) ImportPublicKey(*btcec.PublicKey, func (w *WalletController) ImportPublicKey(*btcec.PublicKey,
waddrmgr.AddressType) error { waddrmgr.AddressType) error {
return nil return nil
} }
// SendOutputs currently returns dummy values. // SendOutputs currently returns dummy values.
func (w *WalletController) SendOutputs(outputs []*wire.TxOut, func (w *WalletController) SendOutputs([]*wire.TxOut,
_ chainfee.SatPerKWeight, _ int32, _ string) (*wire.MsgTx, error) { chainfee.SatPerKWeight, int32, string) (*wire.MsgTx, error) {
return nil, nil return nil, nil
} }
// CreateSimpleTx currently returns dummy values. // CreateSimpleTx currently returns dummy values.
func (w *WalletController) CreateSimpleTx(outputs []*wire.TxOut, func (w *WalletController) CreateSimpleTx([]*wire.TxOut,
_ chainfee.SatPerKWeight, _ int32, _ bool) (*txauthor.AuthoredTx, error) { chainfee.SatPerKWeight, int32, bool) (*txauthor.AuthoredTx, error) {
return nil, nil return nil, nil
} }
// ListUnspentWitness is called by the wallet when doing coin selection. We just // ListUnspentWitness is called by the wallet when doing coin selection. We just
// need one unspent for the funding transaction. // need one unspent for the funding transaction.
func (w *WalletController) ListUnspentWitness(minConfs, func (w *WalletController) ListUnspentWitness(int32, int32,
maxConfs int32, _ string) ([]*lnwallet.Utxo, error) { string) ([]*lnwallet.Utxo, error) {
// If the mock already has a list of utxos, return it. // If the mock already has a list of utxos, return it.
if w.Utxos != nil { if w.Utxos != nil {
@@ -140,8 +154,8 @@ func (w *WalletController) ListUnspentWitness(minConfs,
} }
// ListTransactionDetails currently returns dummy values. // ListTransactionDetails currently returns dummy values.
func (w *WalletController) ListTransactionDetails(_, func (w *WalletController) ListTransactionDetails(int32, int32,
_ int32, _ string) ([]*lnwallet.TransactionDetail, error) { string) ([]*lnwallet.TransactionDetail, error) {
return nil, nil return nil, nil
} }
@@ -169,12 +183,17 @@ func (w *WalletController) ListLeasedOutputs() ([]*wtxmgr.LockedOutput, error) {
} }
// FundPsbt currently does nothing. // FundPsbt currently does nothing.
func (w *WalletController) FundPsbt(_ *psbt.Packet, _ int32, func (w *WalletController) FundPsbt(*psbt.Packet, int32, chainfee.SatPerKWeight,
_ chainfee.SatPerKWeight, _ string) (int32, error) { string) (int32, error) {
return 0, nil return 0, nil
} }
// SignPsbt currently does nothing.
func (w *WalletController) SignPsbt(*psbt.Packet) error {
return nil
}
// FinalizePsbt currently does nothing. // FinalizePsbt currently does nothing.
func (w *WalletController) FinalizePsbt(_ *psbt.Packet, _ string) error { func (w *WalletController) FinalizePsbt(_ *psbt.Packet, _ string) error {
return nil return nil
@@ -187,8 +206,8 @@ func (w *WalletController) PublishTransaction(tx *wire.MsgTx, _ string) error {
} }
// LabelTransaction currently does nothing. // LabelTransaction currently does nothing.
func (w *WalletController) LabelTransaction(_ chainhash.Hash, _ string, func (w *WalletController) LabelTransaction(chainhash.Hash, string,
_ bool) error { bool) error {
return nil return nil
} }
+47 -110
View File
@@ -16,7 +16,6 @@ import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain" "github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcutil/psbt"
"github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/wallet"
@@ -288,15 +287,38 @@ func (b *BtcWallet) InternalWallet() *base.Wallet {
// //
// This is a part of the WalletController interface. // This is a part of the WalletController interface.
func (b *BtcWallet) Start() error { func (b *BtcWallet) Start() error {
// Is the wallet (according to its database) currently watch-only
// already? If it is, we won't need to convert it later.
walletIsWatchOnly := b.wallet.Manager.WatchOnly()
// If the wallet is watch-only, but we don't expect it to be, then we
// are in an unexpected state and cannot continue.
if walletIsWatchOnly && !b.cfg.WatchOnly {
return fmt.Errorf("wallet is watch-only but we expect it " +
"not to be; check if remote signing was disabled by " +
"accident")
}
// We'll start by unlocking the wallet and ensuring that the KeyScope: // We'll start by unlocking the wallet and ensuring that the KeyScope:
// (1017, 1) exists within the internal waddrmgr. We'll need this in // (1017, 1) exists within the internal waddrmgr. We'll need this in
// order to properly generate the keys required for signing various // order to properly generate the keys required for signing various
// contracts. If this is a watch-only wallet, we don't have any private // contracts. If this is a watch-only wallet, we don't have any private
// keys and therefore unlocking is not necessary. // keys and therefore unlocking is not necessary.
if !b.cfg.WatchOnly { if !walletIsWatchOnly {
if err := b.wallet.Unlock(b.cfg.PrivatePass, nil); err != nil { if err := b.wallet.Unlock(b.cfg.PrivatePass, nil); err != nil {
return err return err
} }
// If the wallet isn't about to be converted, we need to inform
// the user that this wallet still contains all private key
// material and that they need to migrate the existing wallet.
if b.cfg.WatchOnly && !b.cfg.MigrateWatchOnly {
log.Warnf("Wallet is expected to be in watch-only " +
"mode but hasn't been migrated to watch-only " +
"yet, it still contains private keys; " +
"consider turning on the watch-only wallet " +
"migration in remote signing mode")
}
} }
scope, err := b.wallet.Manager.FetchScopedKeyManager(b.chainKeyScope) scope, err := b.wallet.Manager.FetchScopedKeyManager(b.chainKeyScope)
@@ -324,17 +346,13 @@ func (b *BtcWallet) Start() error {
err = walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error { err = walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error {
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
for _, keyFam := range keychain.VersionZeroKeyFamilies { // Generate all accounts that we could ever need. This includes
// If this is the multi-sig key family, then we can // all lnd key families as well as some key families used in
// return early as this is the default account that's // external liquidity tools.
// created. for keyFam := uint32(1); keyFam <= 255; keyFam++ {
if keyFam == keychain.KeyFamilyMultiSig {
continue
}
// Otherwise, we'll check if the account already exists, // Otherwise, we'll check if the account already exists,
// if so, we can once again bail early. // if so, we can once again bail early.
_, err := scope.AccountName(addrmgrNs, uint32(keyFam)) _, err := scope.AccountName(addrmgrNs, keyFam)
if err == nil { if err == nil {
continue continue
} }
@@ -342,7 +360,24 @@ func (b *BtcWallet) Start() error {
// If we reach this point, then the account hasn't yet // If we reach this point, then the account hasn't yet
// been created, so we'll need to create it before we // been created, so we'll need to create it before we
// can proceed. // can proceed.
err = scope.NewRawAccount(addrmgrNs, uint32(keyFam)) err = scope.NewRawAccount(addrmgrNs, keyFam)
if err != nil {
return err
}
}
// If this is the first startup with remote signing and wallet
// migration turned on and the wallet wasn't previously
// migrated, we can do that now that we made sure all accounts
// that we need were derived correctly.
if !walletIsWatchOnly && b.cfg.WatchOnly &&
b.cfg.MigrateWatchOnly {
log.Infof("Migrating wallet to watch-only mode, " +
"purging all private key material")
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
err = b.wallet.Manager.ConvertToWatchingOnly(ns)
if err != nil { if err != nil {
return err return err
} }
@@ -1114,104 +1149,6 @@ func (b *BtcWallet) ListTransactionDetails(startHeight, endHeight int32,
return txDetails, nil return txDetails, nil
} }
// FundPsbt creates a fully populated PSBT packet that contains enough inputs to
// fund the outputs specified in the passed in packet with the specified fee
// rate. If there is change left, a change output from the internal wallet is
// added and the index of the change output is returned. Otherwise no additional
// output is created and the index -1 is returned.
//
// NOTE: If the packet doesn't contain any inputs, coin selection is performed
// automatically. The account parameter must be non-empty as it determines which
// set of coins are eligible for coin selection. If the packet does contain any
// inputs, it is assumed that full coin selection happened externally and no
// additional inputs are added. If the specified inputs aren't enough to fund
// the outputs with the given fee rate, an error is returned. No lock lease is
// acquired for any of the selected/validated inputs. It is in the caller's
// responsibility to lock the inputs before handing them out.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) FundPsbt(packet *psbt.Packet, minConfs int32,
feeRate chainfee.SatPerKWeight, accountName string) (int32, error) {
// The fee rate is passed in using units of sat/kw, so we'll convert
// this to sat/KB as the CreateSimpleTx method requires this unit.
feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
var (
keyScope *waddrmgr.KeyScope
accountNum uint32
)
switch accountName {
// If the default/imported account name was specified, we'll provide a
// nil key scope to FundPsbt, allowing it to select inputs from both key
// scopes (NP2WKH, P2WKH).
case lnwallet.DefaultAccountName:
accountNum = defaultAccount
case waddrmgr.ImportedAddrAccountName:
accountNum = importedAccount
// Otherwise, map the account name to its key scope and internal account
// number to only select inputs from said account.
default:
scope, account, err := b.wallet.LookupAccount(accountName)
if err != nil {
return 0, err
}
keyScope = &scope
accountNum = account
}
// Let the wallet handle coin selection and/or fee estimation based on
// the partial TX information in the packet.
return b.wallet.FundPsbt(
packet, keyScope, minConfs, accountNum, feeSatPerKB,
b.cfg.CoinSelectionStrategy,
)
}
// FinalizePsbt expects a partial transaction with all inputs and outputs fully
// declared and tries to sign all inputs that belong to the specified account.
// Lnd must be the last signer of the transaction. That means, if there are any
// unsigned non-witness inputs or inputs without UTXO information attached or
// inputs without witness data that do not belong to lnd's wallet, this method
// will fail. If no error is returned, the PSBT is ready to be extracted and the
// final TX within to be broadcast.
//
// NOTE: This method does NOT publish the transaction after it's been
// finalized successfully.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) FinalizePsbt(packet *psbt.Packet, accountName string) error {
var (
keyScope *waddrmgr.KeyScope
accountNum uint32
)
switch accountName {
// If the default/imported account name was specified, we'll provide a
// nil key scope to FundPsbt, allowing it to sign inputs from both key
// scopes (NP2WKH, P2WKH).
case lnwallet.DefaultAccountName:
accountNum = defaultAccount
case waddrmgr.ImportedAddrAccountName:
accountNum = importedAccount
// Otherwise, map the account name to its key scope and internal account
// number to determine if the inputs belonging to this account should be
// signed.
default:
scope, account, err := b.wallet.LookupAccount(accountName)
if err != nil {
return err
}
keyScope = &scope
accountNum = account
}
return b.wallet.FinalizePsbt(keyScope, accountNum, packet)
}
// txSubscriptionClient encapsulates the transaction notification client from // txSubscriptionClient encapsulates the transaction notification client from
// the base wallet. Notifications received from the client will be proxied over // the base wallet. Notifications received from the client will be proxied over
// two distinct channels. // two distinct channels.
+7
View File
@@ -76,6 +76,13 @@ type Config struct {
// WatchOnly indicates that the wallet was initialized with public key // WatchOnly indicates that the wallet was initialized with public key
// material only and does not contain any private keys. // material only and does not contain any private keys.
WatchOnly bool WatchOnly bool
// MigrateWatchOnly indicates that if a wallet with private key material
// already exists, it should be attempted to be converted into a
// watch-only wallet on first startup. This flag has no effect if no
// wallet exists and a watch-only one is created directly, or, if the
// wallet was previously converted to a watch-only already.
MigrateWatchOnly bool
} }
// NetworkDir returns the directory name of a network directory to hold wallet // NetworkDir returns the directory name of a network directory to hold wallet
+32
View File
@@ -0,0 +1,32 @@
package btcwallet
import (
"github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/build"
)
// Subsystem defines the logging code for this subsystem.
const Subsystem = "BTWL"
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
+298
View File
@@ -0,0 +1,298 @@
package btcwallet
import (
"bytes"
"fmt"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/psbt"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
var (
// PsbtKeyTypeInputSignatureTweakSingle is a custom/proprietary PSBT key
// for an input that specifies what single tweak should be applied to
// the key before signing the input. The value 51 is leet speak for
// "si", short for "single".
PsbtKeyTypeInputSignatureTweakSingle = []byte{0x51}
// PsbtKeyTypeInputSignatureTweakDouble is a custom/proprietary PSBT key
// for an input that specifies what double tweak should be applied to
// the key before signing the input. The value d0 is leet speak for
// "do", short for "double".
PsbtKeyTypeInputSignatureTweakDouble = []byte{0xd0}
)
// FundPsbt creates a fully populated PSBT packet that contains enough inputs to
// fund the outputs specified in the passed in packet with the specified fee
// rate. If there is change left, a change output from the internal wallet is
// added and the index of the change output is returned. Otherwise no additional
// output is created and the index -1 is returned.
//
// NOTE: If the packet doesn't contain any inputs, coin selection is performed
// automatically. The account parameter must be non-empty as it determines which
// set of coins are eligible for coin selection. If the packet does contain any
// inputs, it is assumed that full coin selection happened externally and no
// additional inputs are added. If the specified inputs aren't enough to fund
// the outputs with the given fee rate, an error is returned. No lock lease is
// acquired for any of the selected/validated inputs. It is in the caller's
// responsibility to lock the inputs before handing them out.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) FundPsbt(packet *psbt.Packet, minConfs int32,
feeRate chainfee.SatPerKWeight, accountName string) (int32, error) {
// The fee rate is passed in using units of sat/kw, so we'll convert
// this to sat/KB as the CreateSimpleTx method requires this unit.
feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
var (
keyScope *waddrmgr.KeyScope
accountNum uint32
)
switch accountName {
// If the default/imported account name was specified, we'll provide a
// nil key scope to FundPsbt, allowing it to select inputs from both key
// scopes (NP2WKH, P2WKH).
case lnwallet.DefaultAccountName:
accountNum = defaultAccount
case waddrmgr.ImportedAddrAccountName:
accountNum = importedAccount
// Otherwise, map the account name to its key scope and internal account
// number to only select inputs from said account.
default:
scope, account, err := b.wallet.LookupAccount(accountName)
if err != nil {
return 0, err
}
keyScope = &scope
accountNum = account
}
// Let the wallet handle coin selection and/or fee estimation based on
// the partial TX information in the packet.
return b.wallet.FundPsbt(
packet, keyScope, minConfs, accountNum, feeSatPerKB,
b.cfg.CoinSelectionStrategy,
)
}
// SignPsbt expects a partial transaction with all inputs and outputs fully
// declared and tries to sign all unsigned inputs that have all required fields
// (UTXO information, BIP32 derivation information, witness or sig scripts) set.
// If no error is returned, the PSBT is ready to be given to the next signer or
// to be finalized if lnd was the last signer.
//
// NOTE: This method only signs inputs (and only those it can sign), it does not
// perform any other tasks (such as coin selection, UTXO locking or
// input/output/fee value validation, PSBT finalization). Any input that is
// incomplete will be skipped.
func (b *BtcWallet) SignPsbt(packet *psbt.Packet) error {
// Let's check that this is actually something we can and want to sign.
// We need at least one input and one output.
err := psbt.VerifyInputOutputLen(packet, true, true)
if err != nil {
return err
}
// Go through each input that doesn't have final witness data attached
// to it already and try to sign it. If there is nothing more to sign or
// 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.NewTxSigHashes(tx)
for idx := range tx.TxIn {
in := packet.Inputs[idx]
// We can only sign if we have UTXO information available. Since
// we don't finalize, we just skip over any input that we know
// we can't do anything with. Since we only support signing
// witness inputs, we only look at the witness UTXO being set.
if in.WitnessUtxo == nil {
continue
}
// Skip this input if it's got final witness data attached.
if len(in.FinalScriptWitness) > 0 {
continue
}
// Skip this input if there is no BIP32 derivation info
// available.
if len(in.Bip32Derivation) == 0 {
continue
}
// TODO(guggero): For multisig, we'll need to find out what key
// to use and there should be multiple derivation paths in the
// BIP32 derivation field.
// Let's try and derive the key now. This method will decide if
// it's a BIP49/84 key for normal on-chain funds or a key of the
// custom purpose 1017 key scope.
derivationInfo := in.Bip32Derivation[0]
privKey, err := b.deriveKeyByBIP32Path(derivationInfo.Bip32Path)
if err != nil {
log.Warnf("SignPsbt: Skipping input %d, error "+
"deriving signing key: %v", idx, err)
continue
}
// We need to make sure we actually derived the key that was
// expected to be derived.
pubKeysEqual := bytes.Equal(
derivationInfo.PubKey,
privKey.PubKey().SerializeCompressed(),
)
if !pubKeysEqual {
log.Warnf("SignPsbt: Skipping input %d, derived "+
"public key %x does not match bip32 "+
"derivation info public key %x", idx,
privKey.PubKey().SerializeCompressed(),
derivationInfo.PubKey)
continue
}
// Do we need to tweak anything? Single or double tweaks are
// sent as custom/proprietary fields in the PSBT input section.
privKey = maybeTweakPrivKeyPsbt(in.Unknowns, privKey)
pubKeyBytes := privKey.PubKey().SerializeCompressed()
// Extract the correct witness and/or legacy scripts now,
// depending on the type of input we sign. The txscript package
// has the peculiar requirement that the PkScript of a P2PKH
// must be given as the witness script in order for it to arrive
// at the correct sighash. That's why we call it subScript here
// instead of witness script.
subScript, scriptSig, err := prepareScripts(in)
if err != nil {
// We derived the correct key so we _are_ expected to
// sign this. Not being able to sign at this point means
// there's something wrong.
return fmt.Errorf("error deriving script for input "+
"%d: %v", idx, err)
}
// We have everything we need for signing the input now.
sig, err := txscript.RawTxInWitnessSignature(
tx, sigHashes, idx, in.WitnessUtxo.Value, subScript,
in.SighashType, privKey,
)
if err != nil {
return fmt.Errorf("error signing input %d: %v", idx,
err)
}
packet.Inputs[idx].FinalScriptSig = scriptSig
packet.Inputs[idx].PartialSigs = append(
packet.Inputs[idx].PartialSigs, &psbt.PartialSig{
PubKey: pubKeyBytes,
Signature: sig,
},
)
}
return nil
}
// prepareScripts returns the appropriate witness and/or legacy scripts,
// depending on the type of input that should be signed.
func prepareScripts(in psbt.PInput) ([]byte, []byte, error) {
switch {
// It's a NP2WKH input:
case len(in.RedeemScript) > 0:
builder := txscript.NewScriptBuilder()
builder.AddData(in.RedeemScript)
sigScript, err := builder.Script()
if err != nil {
return nil, nil, fmt.Errorf("error building np2wkh "+
"script: %v", err)
}
return in.RedeemScript, sigScript, nil
// It's a P2WSH input:
case len(in.WitnessScript) > 0:
return in.WitnessScript, nil, nil
// It's a P2WKH input:
default:
return in.WitnessUtxo.PkScript, nil, nil
}
}
// maybeTweakPrivKeyPsbt examines if there are any tweak parameters given in the
// custom/proprietary PSBT fields and may perform a mapping on the passed
// private key in order to utilize the tweaks, if populated.
func maybeTweakPrivKeyPsbt(unknowns []*psbt.Unknown,
privKey *btcec.PrivateKey) *btcec.PrivateKey {
// There can be other custom/unknown keys in a PSBT that we just ignore.
// Key tweaking is optional and only one tweak (single _or_ double) can
// ever be applied (at least for any use cases described in the BOLT
// spec).
for _, u := range unknowns {
if bytes.Equal(u.Key, PsbtKeyTypeInputSignatureTweakSingle) {
return input.TweakPrivKey(privKey, u.Value)
}
if bytes.Equal(u.Key, PsbtKeyTypeInputSignatureTweakDouble) {
doubleTweakKey, _ := btcec.PrivKeyFromBytes(
btcec.S256(), u.Value,
)
return input.DeriveRevocationPrivKey(
privKey, doubleTweakKey,
)
}
}
return privKey
}
// FinalizePsbt expects a partial transaction with all inputs and outputs fully
// declared and tries to sign all inputs that belong to the specified account.
// Lnd must be the last signer of the transaction. That means, if there are any
// unsigned non-witness inputs or inputs without UTXO information attached or
// inputs without witness data that do not belong to lnd's wallet, this method
// will fail. If no error is returned, the PSBT is ready to be extracted and the
// final TX within to be broadcast.
//
// NOTE: This method does NOT publish the transaction after it's been
// finalized successfully.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) FinalizePsbt(packet *psbt.Packet, accountName string) error {
var (
keyScope *waddrmgr.KeyScope
accountNum uint32
)
switch accountName {
// If the default/imported account name was specified, we'll provide a
// nil key scope to FundPsbt, allowing it to sign inputs from both key
// scopes (NP2WKH, P2WKH).
case lnwallet.DefaultAccountName:
accountNum = defaultAccount
case waddrmgr.ImportedAddrAccountName:
accountNum = importedAccount
// Otherwise, map the account name to its key scope and internal account
// number to determine if the inputs belonging to this account should be
// signed.
default:
scope, account, err := b.wallet.LookupAccount(accountName)
if err != nil {
return err
}
keyScope = &scope
accountNum = account
}
return b.wallet.FinalizePsbt(keyScope, accountNum, packet)
}
+337
View File
@@ -0,0 +1,337 @@
package btcwallet
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"fmt"
"testing"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/psbt"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/stretchr/testify/require"
)
var (
netParams = &chaincfg.RegressionNetParams
testValue int64 = 345678
testCSVTimeout uint32 = 2016
testCommitSecretBytes, _ = hex.DecodeString(
"9f1f0db609718cf70c580aec6a0e570c3f086ec85a2a6119295b1d64240d" +
"aca5",
)
testCommitSecret, testCommitPoint = btcec.PrivKeyFromBytes(
btcec.S256(), testCommitSecretBytes,
)
remoteRevocationBasePubKeyBytes, _ = hex.DecodeString(
"02baf067bfd1a6cf7229c7c459b106d384ad33e948ea1d561f2034475ff1" +
"7359fb",
)
remoteRevocationBasePubKey, _ = btcec.ParsePubKey(
remoteRevocationBasePubKeyBytes, btcec.S256(),
)
testTweakSingle, _ = hex.DecodeString(
"020143a30cf6b71ca2af01efbd1758a04b4c7f5c2299f2ea63a8a6b58107" +
"63b1ed",
)
)
// testInputType is a type that represents different types of inputs that are
// signed within a PSBT.
type testInputType uint8
const (
plainP2WKH testInputType = 0
tweakedP2WKH testInputType = 1
nestedP2WKH testInputType = 2
singleKeyP2WSH testInputType = 3
singleKeyDoubleTweakedP2WSH testInputType = 4
)
func (i testInputType) keyPath() []uint32 {
switch i {
case nestedP2WKH:
return []uint32{
hardenedKey(waddrmgr.KeyScopeBIP0049Plus.Purpose),
hardenedKey(0),
hardenedKey(0),
0, 0,
}
case singleKeyP2WSH:
return []uint32{
hardenedKey(keychain.BIP0043Purpose),
hardenedKey(netParams.HDCoinType),
hardenedKey(uint32(keychain.KeyFamilyPaymentBase)),
0, 7,
}
case singleKeyDoubleTweakedP2WSH:
return []uint32{
hardenedKey(keychain.BIP0043Purpose),
hardenedKey(netParams.HDCoinType),
hardenedKey(uint32(keychain.KeyFamilyDelayBase)),
0, 9,
}
default:
return []uint32{
hardenedKey(waddrmgr.KeyScopeBIP0084.Purpose),
hardenedKey(0),
hardenedKey(0),
0, 0,
}
}
}
func (i testInputType) output(t *testing.T,
privKey *btcec.PrivateKey) (*wire.TxOut, []byte) {
var (
addr btcutil.Address
witnessScript []byte
err error
)
switch i {
case plainP2WKH:
h := btcutil.Hash160(privKey.PubKey().SerializeCompressed())
addr, err = btcutil.NewAddressWitnessPubKeyHash(h, netParams)
require.NoError(t, err)
case tweakedP2WKH:
privKey = input.TweakPrivKey(privKey, testTweakSingle)
h := btcutil.Hash160(privKey.PubKey().SerializeCompressed())
addr, err = btcutil.NewAddressWitnessPubKeyHash(h, netParams)
require.NoError(t, err)
case nestedP2WKH:
h := btcutil.Hash160(privKey.PubKey().SerializeCompressed())
witnessAddr, err := btcutil.NewAddressWitnessPubKeyHash(
h, netParams,
)
require.NoError(t, err)
witnessProgram, err := txscript.PayToAddrScript(witnessAddr)
require.NoError(t, err)
addr, err = btcutil.NewAddressScriptHash(
witnessProgram, netParams,
)
require.NoError(t, err)
case singleKeyP2WSH:
// We're simulating a delay-to-self script which we're going to
// spend through the time lock path. We don't actually need to
// know the private key of the remote revocation base key.
revokeKey := input.DeriveRevocationPubkey(
remoteRevocationBasePubKey, testCommitPoint,
)
witnessScript, err = input.CommitScriptToSelf(
testCSVTimeout, privKey.PubKey(), revokeKey,
)
require.NoError(t, err)
h := sha256.Sum256(witnessScript)
addr, err = btcutil.NewAddressWitnessScriptHash(h[:], netParams)
require.NoError(t, err)
case singleKeyDoubleTweakedP2WSH:
// We're simulating breaching a remote party's delay-to-self
// output which we're going to spend through the revocation
// path. In that case the self key is the other party's self key
// and, we only know the revocation base private key and commit
// secret.
revokeKey := input.DeriveRevocationPubkey(
privKey.PubKey(), testCommitPoint,
)
witnessScript, err = input.CommitScriptToSelf(
testCSVTimeout, remoteRevocationBasePubKey, revokeKey,
)
require.NoError(t, err)
h := sha256.Sum256(witnessScript)
addr, err = btcutil.NewAddressWitnessScriptHash(h[:], netParams)
require.NoError(t, err)
default:
t.Fatalf("invalid input type")
}
pkScript, err := txscript.PayToAddrScript(addr)
require.NoError(t, err)
return &wire.TxOut{
Value: testValue,
PkScript: pkScript,
}, witnessScript
}
func (i testInputType) decorateInput(t *testing.T, privKey *btcec.PrivateKey,
in *psbt.PInput) {
switch i {
case tweakedP2WKH:
in.Unknowns = []*psbt.Unknown{{
Key: PsbtKeyTypeInputSignatureTweakSingle,
Value: testTweakSingle,
}}
case nestedP2WKH:
h := btcutil.Hash160(privKey.PubKey().SerializeCompressed())
witnessAddr, err := btcutil.NewAddressWitnessPubKeyHash(
h, netParams,
)
require.NoError(t, err)
witnessProgram, err := txscript.PayToAddrScript(witnessAddr)
require.NoError(t, err)
in.RedeemScript = witnessProgram
case singleKeyDoubleTweakedP2WSH:
in.Unknowns = []*psbt.Unknown{{
Key: PsbtKeyTypeInputSignatureTweakDouble,
Value: testCommitSecret.Serialize(),
}}
}
}
func (i testInputType) beforeFinalize(t *testing.T, packet *psbt.Packet) {
in := &packet.Inputs[0]
sigBytes := in.PartialSigs[0].Signature
pubKeyBytes := in.PartialSigs[0].PubKey
var witnessStack wire.TxWitness
switch i {
case singleKeyP2WSH:
witnessStack = make([][]byte, 3)
witnessStack[0] = sigBytes
witnessStack[1] = nil
witnessStack[2] = in.WitnessScript
case singleKeyDoubleTweakedP2WSH:
// Place a 1 as the first item in the evaluated witness stack to
// force script execution to the revocation clause.
witnessStack = make([][]byte, 3)
witnessStack[0] = sigBytes
witnessStack[1] = []byte{1}
witnessStack[2] = in.WitnessScript
default:
witnessStack = make([][]byte, 2)
witnessStack[0] = sigBytes
witnessStack[1] = pubKeyBytes
}
var err error
in.FinalScriptWitness, err = serializeTxWitness(witnessStack)
require.NoError(t, err)
}
// serializeTxWitness return the wire witness stack into raw bytes.
func serializeTxWitness(txWitness wire.TxWitness) ([]byte, error) {
var witnessBytes bytes.Buffer
err := psbt.WriteTxWitness(&witnessBytes, txWitness)
if err != nil {
return nil, fmt.Errorf("error serializing witness: %v", err)
}
return witnessBytes.Bytes(), nil
}
// TestSignPsbt tests the PSBT signing functionality.
func TestSignPsbt(t *testing.T) {
w, cleanup := newTestWallet(t, netParams, seedBytes)
defer cleanup()
testCases := []struct {
name string
inputType testInputType
}{{
name: "plain P2WKH",
inputType: plainP2WKH,
}, {
name: "tweaked P2WKH",
inputType: tweakedP2WKH,
}, {
name: "nested P2WKH",
inputType: nestedP2WKH,
}, {
name: "single key P2WSH",
inputType: singleKeyP2WSH,
}, {
name: "single key double tweaked P2WSH",
inputType: singleKeyDoubleTweakedP2WSH,
}}
for _, tc := range testCases {
tc := tc
// This is the private key we're going to sign with.
privKey, err := w.deriveKeyByBIP32Path(tc.inputType.keyPath())
require.NoError(t, err)
txOut, witnessScript := tc.inputType.output(t, privKey)
// Create the reference transaction that has the input that is
// going to be spent by our PSBT.
refTx := wire.NewMsgTx(2)
refTx.AddTxIn(&wire.TxIn{})
refTx.AddTxOut(txOut)
// Create the unsigned spend transaction that is going to be the
// main content of our PSBT.
spendTx := wire.NewMsgTx(2)
spendTx.LockTime = testCSVTimeout
spendTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: wire.OutPoint{
Hash: refTx.TxHash(),
Index: 0,
},
Sequence: testCSVTimeout,
})
spendTx.AddTxOut(txOut)
// Convert it to a PSBT now and add all required signing
// metadata to it.
packet, err := psbt.NewFromUnsignedTx(spendTx)
require.NoError(t, err)
packet.Inputs[0].WitnessScript = witnessScript
packet.Inputs[0].SighashType = txscript.SigHashAll
packet.Inputs[0].WitnessUtxo = refTx.TxOut[0]
packet.Inputs[0].Bip32Derivation = []*psbt.Bip32Derivation{{
PubKey: privKey.PubKey().SerializeCompressed(),
Bip32Path: tc.inputType.keyPath(),
}}
tc.inputType.decorateInput(t, privKey, &packet.Inputs[0])
// Let the wallet do its job. We expect to be the only signer
// for this PSBT, so we'll be able to finalize it later.
err = w.SignPsbt(packet)
require.NoError(t, err)
// If the witness stack needs to be assembled, give the caller
// the option to do that now.
tc.inputType.beforeFinalize(t, packet)
finalTx, err := psbt.Extract(packet)
require.NoError(t, err)
vm, err := txscript.NewEngine(
refTx.TxOut[0].PkScript, finalTx, 0,
txscript.StandardVerifyFlags, nil, nil,
refTx.TxOut[0].Value,
)
require.NoError(t, err)
require.NoError(t, vm.Execute())
}
}
+147 -2
View File
@@ -1,11 +1,14 @@
package btcwallet package btcwallet
import ( import (
"fmt"
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/walletdb"
"github.com/go-errors/errors" "github.com/go-errors/errors"
@@ -21,7 +24,9 @@ import (
// //
// This is a part of the WalletController interface. // This is a part of the WalletController interface.
func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo, error) { func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo, error) {
_, txOut, _, confirmations, err := b.wallet.FetchInputInfo(prevOut) prevTx, txOut, bip32, confirmations, err := b.wallet.FetchInputInfo(
prevOut,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -41,9 +46,20 @@ func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo, erro
PkScript: txOut.PkScript, PkScript: txOut.PkScript,
Confirmations: confirmations, Confirmations: confirmations,
OutPoint: *prevOut, OutPoint: *prevOut,
Derivation: bip32,
PrevTx: prevTx,
}, nil }, nil
} }
// 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.
func (b *BtcWallet) ScriptForOutput(output *wire.TxOut) (
waddrmgr.ManagedPubKeyAddress, []byte, []byte, error) {
return b.wallet.ScriptForOutput(output)
}
// deriveFromKeyLoc attempts to derive a private key using a fully specified // deriveFromKeyLoc attempts to derive a private key using a fully specified
// KeyLocator. // KeyLocator.
func deriveFromKeyLoc(scopedMgr *waddrmgr.ScopedKeyManager, func deriveFromKeyLoc(scopedMgr *waddrmgr.ScopedKeyManager,
@@ -64,9 +80,138 @@ func deriveFromKeyLoc(scopedMgr *waddrmgr.ScopedKeyManager,
return addr.(waddrmgr.ManagedPubKeyAddress).PrivKey() return addr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
} }
// deriveKeyByBIP32Path derives a key described by a BIP32 path. We expect the
// first three elements of the path to be hardened according to BIP44, so they
// must be a number >= 2^31.
func (b *BtcWallet) deriveKeyByBIP32Path(path []uint32) (*btcec.PrivateKey,
error) {
// Make sure we get a full path with exactly 5 elements. A path is
// either custom purpose one with 4 dynamic and one static elements:
// m/1017'/coinType'/keyFamily'/0/index
// Or a default BIP49/89 one with 5 elements:
// m/purpose'/coinType'/account'/change/index
const expectedDerivationPathDepth = 5
if len(path) != expectedDerivationPathDepth {
return nil, fmt.Errorf("invalid BIP32 derivation path, "+
"expected path length %d, instead was %d",
expectedDerivationPathDepth, len(path))
}
// Assert that the first three parts of the path are actually hardened
// to avoid under-flowing the uint32 type.
if err := assertHardened(path[0], path[1], path[2]); err != nil {
return nil, fmt.Errorf("invalid BIP32 derivation path, "+
"expected first three elements to be hardened: %w", err)
}
purpose := path[0] - hdkeychain.HardenedKeyStart
coinType := path[1] - hdkeychain.HardenedKeyStart
account := path[2] - hdkeychain.HardenedKeyStart
change, index := path[3], path[4]
// Is this a custom lnd internal purpose key?
switch purpose {
case keychain.BIP0043Purpose:
// Make sure it's for the same coin type as our wallet's
// keychain scope.
if coinType != b.chainKeyScope.Coin {
return nil, fmt.Errorf("invalid BIP32 derivation "+
"path, expected coin type %d, instead was %d",
b.chainKeyScope.Coin, coinType)
}
return b.deriveKeyByLocator(keychain.KeyLocator{
Family: keychain.KeyFamily(account),
Index: index,
})
// Is it a standard, BIP defined purpose that the wallet understands?
case waddrmgr.KeyScopeBIP0044.Purpose,
waddrmgr.KeyScopeBIP0049Plus.Purpose,
waddrmgr.KeyScopeBIP0084.Purpose:
// We're going to continue below the switch statement to avoid
// unnecessary indentation for this default case.
// Currently, there is no way to import any other key scopes than the
// one custom purpose or three standard ones into lnd's wallet. So we
// shouldn't accept any other scopes to sign for.
default:
return nil, fmt.Errorf("invalid BIP32 derivation path, "+
"unknown purpose %d", purpose)
}
// Okay, we made sure it's a BIP49/84 key, so we need to derive it now.
// Interestingly, the btcwallet never actually uses a coin type other
// than 0 for those keys, so we need to make sure this behavior is
// replicated here.
if coinType != 0 {
return nil, fmt.Errorf("invalid BIP32 derivation path, coin " +
"type must be 0 for BIP49/84 btcwallet keys")
}
// We only expect to be asked to sign with key scopes that we know
// about. So if the scope doesn't exist, we don't create it.
scope := waddrmgr.KeyScope{
Purpose: purpose,
Coin: coinType,
}
scopedMgr, err := b.wallet.Manager.FetchScopedKeyManager(scope)
if err != nil {
return nil, fmt.Errorf("error fetching manager for scope %v: "+
"%w", scope, err)
}
// Let's see if we can hit the private key cache.
keyPath := waddrmgr.DerivationPath{
InternalAccount: account,
Account: account,
Branch: change,
Index: index,
}
privKey, err := scopedMgr.DeriveFromKeyPathCache(keyPath)
if err == nil {
return privKey, nil
}
// The key wasn't in the cache, let's fully derive it now.
err = walletdb.View(b.db, func(tx walletdb.ReadTx) error {
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
addr, err := scopedMgr.DeriveFromKeyPath(addrmgrNs, keyPath)
if err != nil {
return fmt.Errorf("error deriving private key: %w", err)
}
privKey, err = addr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
return err
})
if err != nil {
return nil, fmt.Errorf("error deriving key from path %#v: %w",
keyPath, err)
}
return privKey, nil
}
// assertHardened makes sure each given element is >= 2^31.
func assertHardened(elements ...uint32) error {
for idx, element := range elements {
if element < hdkeychain.HardenedKeyStart {
return fmt.Errorf("element at index %d is not hardened",
idx)
}
}
return nil
}
// deriveKeyByLocator attempts to derive a key stored in the wallet given a // deriveKeyByLocator attempts to derive a key stored in the wallet given a
// valid key locator. // valid key locator.
func (b *BtcWallet) deriveKeyByLocator(keyLoc keychain.KeyLocator) (*btcec.PrivateKey, error) { func (b *BtcWallet) deriveKeyByLocator(
keyLoc keychain.KeyLocator) (*btcec.PrivateKey, error) {
// We'll assume the special lightning key scope in this case. // We'll assume the special lightning key scope in this case.
scopedMgr, err := b.wallet.Manager.FetchScopedKeyManager( scopedMgr, err := b.wallet.Manager.FetchScopedKeyManager(
b.chainKeyScope, b.chainKeyScope,
+241
View File
@@ -0,0 +1,241 @@
package btcwallet
import (
"encoding/hex"
"io/ioutil"
"os"
"testing"
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/integration/rpctest"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcwallet/chain"
"github.com/lightningnetwork/lnd/blockcache"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/stretchr/testify/require"
)
var (
// seedBytes is the raw entropy of the aezeed:
// able promote dizzy mixture sword myth share public find tattoo
// catalog cousin bulb unfair machine alarm cool large promote kick
// shop rug mean year
// Which corresponds to the master root key:
// xprv9s21ZrQH143K2KADjED57FvNbptdKLp4sqKzssegwEGKQMGoDkbyhUeCKe5m3A
// MU44z4vqkmGswwQVKrv599nFG16PPZDEkNrogwoDGeCmZ
seedBytes, _ = hex.DecodeString("4a7611b6979ba7c4bc5c5cd2239b2973")
// firstAddress is the first address that we should get from the wallet,
// corresponding to the derivation path m/84'/0'/0'/0/0 (even on regtest
// which is a special case for the BIP49/84 addresses in btcwallet).
firstAddress = "bcrt1qgdlgjc5ede7fjv350wcjqat80m0zsmfaswsj9p"
testCases = []struct {
name string
path []uint32
err string
wif string
}{{
name: "m/84'/0'/0'/0/0",
path: []uint32{
hardenedKey(84), hardenedKey(0), hardenedKey(0), 0, 0,
},
wif: "cPp3XUewCBQVg3pgwVWbtpzDwhWTQpHhu8saN3SdGRTkiLpu1R6h",
}, {
name: "m/84'/0'/0'/1/0",
path: []uint32{
hardenedKey(84), hardenedKey(0), hardenedKey(0), 1, 0,
},
wif: "cPUR1nFAeYAtSWSkKoWB6WbzRTbDSGdrGRmv1kVLRPyo7QXph2gt",
}, {
name: "m/84'/0'/0'/0/12345",
path: []uint32{
hardenedKey(84), hardenedKey(0), hardenedKey(0), 0,
12345,
},
wif: "cQCdGxqKeGZKiC2uRYMAGenJHkDvajiPieT4Yg7k1BKawjKkywvz",
}, {
name: "m/49'/0'/0'/0/0",
path: []uint32{
hardenedKey(49), hardenedKey(0), hardenedKey(0), 0, 0,
},
wif: "cMwVK2bcTzivPZfcCH585rBGghqsJAP9MdVy8inRti1wZvLn5DvY",
}, {
name: "m/49'/0'/0'/1/0",
path: []uint32{
hardenedKey(49), hardenedKey(0), hardenedKey(0), 1, 0,
},
wif: "cNPW9bMtdc2YGBzWzSCXFN4excjrT34nZzGYtfkzkazUrt3dXuv7",
}, {
name: "m/49'/0'/0'/1/12345",
path: []uint32{
hardenedKey(49), hardenedKey(0), hardenedKey(0), 1,
12345,
},
wif: "cNdJt2fSNUJYVSb8JFjhosPcQgNvJ92SjNeNpsf1gUwDVDv2KVRa",
}, {
name: "m/1017'/1'/0'/0/0",
path: []uint32{
hardenedKey(1017), hardenedKey(1), hardenedKey(0), 0, 0,
},
wif: "cPsCmbWQENgptj3eTiyd85QSAD1xqYKPM9jUkfvm7vgN3SoVPWSP",
}, {
name: "m/1017'/1'/6'/0/0",
path: []uint32{
hardenedKey(1017), hardenedKey(1), hardenedKey(6), 0, 0,
},
wif: "cPeQdpcGJmLqpdmvokh3DK9ZtjYAXxiw4p4ELNUWkWt6bMRqArEV",
}, {
name: "m/1017'/1'/7'/0/123",
path: []uint32{
hardenedKey(1017), hardenedKey(1), hardenedKey(7), 0,
123,
},
wif: "cPcWZMqY4YErkcwjtFJaYoXkzd7bKxrfxAVzhDgy3n5BGH8CU8sn",
}, {
name: "m/84'/1'/0'/0/0",
path: []uint32{
hardenedKey(84), hardenedKey(1), hardenedKey(0), 0, 0,
},
err: "coin type must be 0 for BIP49/84 btcwallet keys",
}, {
name: "m/1017'/0'/0'/0/0",
path: []uint32{
hardenedKey(1017), hardenedKey(0), hardenedKey(0), 0, 0,
},
err: "expected coin type 1, instead was 0",
}, {
name: "m/84'/0'/1'/0/0",
path: []uint32{
hardenedKey(84), hardenedKey(0), hardenedKey(1), 0, 0,
},
err: "account 1 not found",
}, {
name: "m/49'/0'/1'/0/0",
path: []uint32{
hardenedKey(49), hardenedKey(0), hardenedKey(1), 0, 0,
},
err: "account 1 not found",
}, {
name: "non-hardened purpose m/84/0/0/0/0",
path: []uint32{84, 0, 0, 0, 0},
err: "element at index 0 is not hardened",
}, {
name: "non-hardened account m/84'/0'/0/0/0",
path: []uint32{hardenedKey(84), hardenedKey(0), 0, 0, 0},
err: "element at index 2 is not hardened",
}}
)
// TestBip32KeyDerivation makes sure that private keys can be derived from a
// BIP32 key path correctly.
func TestBip32KeyDerivation(t *testing.T) {
netParams := &chaincfg.RegressionNetParams
w, cleanup := newTestWallet(t, netParams, seedBytes)
defer cleanup()
// This is just a sanity check that the wallet was initialized
// correctly. We make sure the first derived address is the expected
// one.
firstDerivedAddr, err := w.NewAddress(
lnwallet.WitnessPubKey, false, lnwallet.DefaultAccountName,
)
require.NoError(t, err)
require.Equal(t, firstAddress, firstDerivedAddr.String())
// Let's go through the test cases now that we know our wallet is ready.
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
privKey, err := w.deriveKeyByBIP32Path(tc.path)
if tc.err == "" {
require.NoError(t, err)
wif, err := btcutil.NewWIF(
privKey, netParams, true,
)
require.NoError(t, err)
require.Equal(t, tc.wif, wif.String())
} else {
require.Error(t, err)
require.Contains(t, err.Error(), tc.err)
}
})
}
}
func newTestWallet(t *testing.T, netParams *chaincfg.Params,
seedBytes []byte) (*BtcWallet, func()) {
tempDir, err := ioutil.TempDir("", "lnwallet")
if err != nil {
_ = os.RemoveAll(tempDir)
t.Fatalf("creating temp dir failed: %v", err)
}
chainBackend, backendCleanup := getChainBackend(t, netParams)
cleanup := func() {
_ = os.RemoveAll(tempDir)
backendCleanup()
}
loaderOpt := LoaderWithLocalWalletDB(tempDir, false, time.Minute)
config := Config{
PrivatePass: []byte("some-pass"),
HdSeed: seedBytes,
NetParams: netParams,
CoinType: netParams.HDCoinType,
ChainSource: chainBackend,
// wallet starts in recovery mode
RecoveryWindow: 2,
LoaderOptions: []LoaderOption{loaderOpt},
}
blockCache := blockcache.NewBlockCache(10000)
w, err := New(config, blockCache)
if err != nil {
cleanup()
t.Fatalf("creating wallet failed: %v", err)
}
err = w.Start()
if err != nil {
cleanup()
t.Fatalf("starting wallet failed: %v", err)
}
return w, cleanup
}
// getChainBackend returns a simple btcd based chain backend to back the wallet.
func getChainBackend(t *testing.T, netParams *chaincfg.Params) (chain.Interface,
func()) {
miningNode, err := rpctest.New(netParams, nil, nil, "")
require.NoError(t, err)
require.NoError(t, miningNode.SetUp(true, 25))
// Next, mine enough blocks in order for SegWit and the CSV package
// soft-fork to activate on RegNet.
numBlocks := netParams.MinerConfirmationWindow * 2
_, err = miningNode.Client.Generate(numBlocks)
require.NoError(t, err)
rpcConfig := miningNode.RPCConfig()
chainClient, err := chain.NewRPCClient(
netParams, rpcConfig.Host, rpcConfig.User, rpcConfig.Pass,
rpcConfig.Certificates, false, 20,
)
require.NoError(t, err)
return chainClient, func() {
_ = miningNode.TearDown()
}
}
// hardenedKey returns a key of a hardened derivation key path.
func hardenedKey(part uint32) uint32 {
return part + hdkeychain.HardenedKeyStart
}
+29 -5
View File
@@ -78,6 +78,8 @@ type Utxo struct {
Confirmations int64 Confirmations int64
PkScript []byte PkScript []byte
wire.OutPoint wire.OutPoint
Derivation *psbt.Bip32Derivation
PrevTx *wire.MsgTx
} }
// TransactionDetail describes a transaction with either inputs which belong to // TransactionDetail describes a transaction with either inputs which belong to
@@ -158,6 +160,12 @@ type WalletController interface {
// a non-nil error value of ErrNotMine should be returned instead. // a non-nil error value of ErrNotMine should be returned instead.
FetchInputInfo(prevOut *wire.OutPoint) (*Utxo, error) FetchInputInfo(prevOut *wire.OutPoint) (*Utxo, error)
// 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.
ScriptForOutput(output *wire.TxOut) (waddrmgr.ManagedPubKeyAddress,
[]byte, []byte, error)
// ConfirmedBalance returns the sum of all the wallet's unspent outputs // ConfirmedBalance returns the sum of all the wallet's unspent outputs
// that have at least confs confirmations. If confs is set to zero, // that have at least confs confirmations. If confs is set to zero,
// then all unspent outputs, including those currently in the mempool // then all unspent outputs, including those currently in the mempool
@@ -168,7 +176,8 @@ type WalletController interface {
// NOTE: Only witness outputs should be included in the computation of // NOTE: Only witness outputs should be included in the computation of
// the total spendable balance of the wallet. We require this as only // the total spendable balance of the wallet. We require this as only
// witness inputs can be used for funding channels. // witness inputs can be used for funding channels.
ConfirmedBalance(confs int32, accountFilter string) (btcutil.Amount, error) ConfirmedBalance(confs int32, accountFilter string) (btcutil.Amount,
error)
// NewAddress returns the next external or internal address for the // NewAddress returns the next external or internal address for the
// wallet dictated by the value of the `change` parameter. If change is // wallet dictated by the value of the `change` parameter. If change is
@@ -197,7 +206,8 @@ type WalletController interface {
// ListAccounts retrieves all accounts belonging to the wallet by // ListAccounts retrieves all accounts belonging to the wallet by
// default. A name and key scope filter can be provided to filter // default. A name and key scope filter can be provided to filter
// through all of the wallet accounts and return only those matching. // through all of the wallet accounts and return only those matching.
ListAccounts(string, *waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties, error) ListAccounts(string, *waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties,
error)
// ImportAccount imports an account backed by an account extended public // ImportAccount imports an account backed by an account extended public
// key. The master key fingerprint denotes the fingerprint of the root // key. The master key fingerprint denotes the fingerprint of the root
@@ -228,7 +238,8 @@ type WalletController interface {
// in the case of legacy versions (xpub, tpub), an address type must be // in the case of legacy versions (xpub, tpub), an address type must be
// specified as we intend to not support importing BIP-44 keys into the // specified as we intend to not support importing BIP-44 keys into the
// wallet using the legacy pay-to-pubkey-hash (P2PKH) scheme. // wallet using the legacy pay-to-pubkey-hash (P2PKH) scheme.
ImportPublicKey(pubKey *btcec.PublicKey, addrType waddrmgr.AddressType) error ImportPublicKey(pubKey *btcec.PublicKey,
addrType waddrmgr.AddressType) error
// SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying // SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying
// out to the specified outputs. In the case the wallet has insufficient // out to the specified outputs. In the case the wallet has insufficient
@@ -237,8 +248,8 @@ type WalletController interface {
// be used when crafting the transaction. // be used when crafting the transaction.
// //
// NOTE: This method requires the global coin selection lock to be held. // NOTE: This method requires the global coin selection lock to be held.
SendOutputs(outputs []*wire.TxOut, SendOutputs(outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight,
feeRate chainfee.SatPerKWeight, minConfs int32, label string) (*wire.MsgTx, error) minConfs int32, label string) (*wire.MsgTx, error)
// CreateSimpleTx creates a Bitcoin transaction paying to the specified // CreateSimpleTx creates a Bitcoin transaction paying to the specified
// outputs. The transaction is not broadcasted to the network. In the // outputs. The transaction is not broadcasted to the network. In the
@@ -353,6 +364,19 @@ type WalletController interface {
FundPsbt(packet *psbt.Packet, minConfs int32, FundPsbt(packet *psbt.Packet, minConfs int32,
feeRate chainfee.SatPerKWeight, account string) (int32, error) feeRate chainfee.SatPerKWeight, account string) (int32, error)
// SignPsbt expects a partial transaction with all inputs and outputs
// fully declared and tries to sign all unsigned inputs that have all
// required fields (UTXO information, BIP32 derivation information,
// witness or sig scripts) set.
// If no error is returned, the PSBT is ready to be given to the next
// signer or to be finalized if lnd was the last signer.
//
// NOTE: This method only signs inputs (and only those it can sign), it
// does not perform any other tasks (such as coin selection, UTXO
// locking or input/output/fee value validation, PSBT finalization). Any
// input that is incomplete will be skipped.
SignPsbt(packet *psbt.Packet) error
// FinalizePsbt expects a partial transaction with all inputs and // FinalizePsbt expects a partial transaction with all inputs and
// outputs fully declared and tries to sign all inputs that belong to // outputs fully declared and tries to sign all inputs that belong to
// the specified account. Lnd must be the last signer of the // the specified account. Lnd must be the last signer of the
-1
View File
@@ -5,7 +5,6 @@ import (
"github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/chain"
btcwallet "github.com/btcsuite/btcwallet/wallet" btcwallet "github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/wtxmgr" "github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chainfee"
) )
+24
View File
@@ -0,0 +1,24 @@
package rpcwallet
import (
"fmt"
"time"
"github.com/lightningnetwork/lnd/lncfg"
)
// HealthCheck returns a health check function for the given remote signing
// configuration.
func HealthCheck(cfg *lncfg.RemoteSigner, timeout time.Duration) func() error {
return func() error {
_, err := connectRPC(
cfg.RPCHost, cfg.TLSCertPath, cfg.MacaroonPath, timeout,
)
if err != nil {
return fmt.Errorf("error connecting to the remote "+
"signing node through RPC: %v", err)
}
return nil
}
}
+32
View File
@@ -0,0 +1,32 @@
package rpcwallet
import (
"github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/build"
)
// Subsystem defines the logging code for this subsystem.
const Subsystem = "RPWL"
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
+380 -395
View File
@@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"context" "context"
"crypto/x509" "crypto/x509"
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@@ -16,16 +15,15 @@ import (
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain" "github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcutil/psbt" "github.com/btcsuite/btcutil/psbt"
"github.com/btcsuite/btcwallet/waddrmgr" basewallet "github.com/btcsuite/btcwallet/wallet"
btcwallet "github.com/btcsuite/btcwallet/wallet"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/macaroons"
"google.golang.org/grpc" "google.golang.org/grpc"
@@ -33,12 +31,6 @@ import (
"gopkg.in/macaroon.v2" "gopkg.in/macaroon.v2"
) )
const (
// DefaultRPCTimeout is the default timeout that is used when forwarding
// a request to the remote signer through RPC.
DefaultRPCTimeout = 5 * time.Second
)
var ( var (
// ErrRemoteSigningPrivateKeyNotAvailable is the error that is returned // ErrRemoteSigningPrivateKeyNotAvailable is the error that is returned
// if an operation is requested from the RPC wallet that is not // if an operation is requested from the RPC wallet that is not
@@ -58,6 +50,8 @@ type RPCKeyRing struct {
watchOnlyKeyRing keychain.SecretKeyRing watchOnlyKeyRing keychain.SecretKeyRing
coinType uint32
rpcTimeout time.Duration rpcTimeout time.Duration
signerClient signrpc.SignerClient signerClient signrpc.SignerClient
@@ -74,12 +68,11 @@ var _ lnwallet.WalletController = (*RPCKeyRing)(nil)
// delegates any signing or ECDH operations to the remove signer through RPC. // delegates any signing or ECDH operations to the remove signer through RPC.
func NewRPCKeyRing(watchOnlyKeyRing keychain.SecretKeyRing, func NewRPCKeyRing(watchOnlyKeyRing keychain.SecretKeyRing,
watchOnlyWalletController lnwallet.WalletController, watchOnlyWalletController lnwallet.WalletController,
remoteSigner *lncfg.RemoteSigner, remoteSigner *lncfg.RemoteSigner, coinType uint32) (*RPCKeyRing, error) {
rpcTimeout time.Duration) (*RPCKeyRing, error) {
rpcConn, err := connectRPC( rpcConn, err := connectRPC(
remoteSigner.RPCHost, remoteSigner.TLSCertPath, remoteSigner.RPCHost, remoteSigner.TLSCertPath,
remoteSigner.MacaroonPath, remoteSigner.MacaroonPath, remoteSigner.Timeout,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("error connecting to the remote "+ return nil, fmt.Errorf("error connecting to the remote "+
@@ -89,7 +82,8 @@ func NewRPCKeyRing(watchOnlyKeyRing keychain.SecretKeyRing,
return &RPCKeyRing{ return &RPCKeyRing{
WalletController: watchOnlyWalletController, WalletController: watchOnlyWalletController,
watchOnlyKeyRing: watchOnlyKeyRing, watchOnlyKeyRing: watchOnlyKeyRing,
rpcTimeout: rpcTimeout, coinType: coinType,
rpcTimeout: remoteSigner.Timeout,
signerClient: signrpc.NewSignerClient(rpcConn), signerClient: signrpc.NewSignerClient(rpcConn),
walletClient: walletrpc.NewWalletKitClient(rpcConn), walletClient: walletrpc.NewWalletKitClient(rpcConn),
}, nil }, nil
@@ -105,189 +99,7 @@ func NewRPCKeyRing(watchOnlyKeyRing keychain.SecretKeyRing,
func (r *RPCKeyRing) NewAddress(addrType lnwallet.AddressType, change bool, func (r *RPCKeyRing) NewAddress(addrType lnwallet.AddressType, change bool,
account string) (btcutil.Address, error) { account string) (btcutil.Address, error) {
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) return r.WalletController.NewAddress(addrType, change, account)
defer cancel()
rpcAddrType := walletrpc.AddressType_WITNESS_PUBKEY_HASH
if addrType == lnwallet.NestedWitnessPubKey {
rpcAddrType = walletrpc.AddressType_NESTED_WITNESS_PUBKEY_HASH
}
remoteAddr, err := r.walletClient.NextAddr(ctxt, &walletrpc.AddrRequest{
Account: account,
Type: rpcAddrType,
Change: change,
})
if err != nil {
return nil, fmt.Errorf("error deriving address on remote "+
"signer instance: %v", err)
}
localAddr, err := r.WalletController.NewAddress(
addrType, change, account,
)
if err != nil {
return nil, fmt.Errorf("error deriving address on local "+
"wallet instance: %v", err)
}
// We need to make sure we've derived the same address on the remote
// signing machine, otherwise we don't know whether we're at the same
// address index (and therefore the same wallet state in general).
if localAddr.String() != remoteAddr.Addr {
return nil, fmt.Errorf("error deriving address on remote "+
"signing instance, got different address (%s) than "+
"on local wallet instance (%s)", remoteAddr.Addr,
localAddr.String())
}
return localAddr, nil
}
// LastUnusedAddress returns the last *unused* address known by the wallet. An
// address is unused if it hasn't received any payments. This can be useful in
// UIs in order to continually show the "freshest" address without having to
// worry about "address inflation" caused by continual refreshing. Similar to
// NewAddress it can derive a specified address type, and also optionally a
// change address. The account parameter must be non-empty as it determines
// which account the address should be generated from.
func (r *RPCKeyRing) LastUnusedAddress(lnwallet.AddressType,
string) (btcutil.Address, error) {
// Because the underlying wallet will create a new address if the last
// derived address has been used in the meantime, we would need to proxy
// that call as well. But since that's deep within the btcwallet code,
// we cannot easily proxy it without more refactoring. Since this is an
// address type that is probably not widely used we can probably get
// away with not supporting it.
return nil, fmt.Errorf("unused address types are not supported when " +
"remote signing is enabled")
}
// ImportAccount imports an account backed by an account extended public key.
// The master key fingerprint denotes the fingerprint of the root key
// corresponding to the account public key (also known as the key with
// derivation path m/). This may be required by some hardware wallets for proper
// identification and signing.
//
// The address type can usually be inferred from the key's version, but may be
// required for certain keys to map them into the proper scope.
//
// For BIP-0044 keys, an address type must be specified as we intend to not
// support importing BIP-0044 keys into the wallet using the legacy
// pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force
// the standard BIP-0049 derivation scheme, while a witness address type will
// force the standard BIP-0084 derivation scheme.
//
// For BIP-0049 keys, an address type must also be specified to make a
// distinction between the standard BIP-0049 address schema (nested witness
// pubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys
// externally, witness pubkeys internally).
func (r *RPCKeyRing) ImportAccount(name string,
accountPubKey *hdkeychain.ExtendedKey, masterKeyFingerprint uint32,
addrType *waddrmgr.AddressType,
dryRun bool) (*waddrmgr.AccountProperties, []btcutil.Address,
[]btcutil.Address, error) {
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
var masterKeyFingerprintBytes [4]byte
binary.BigEndian.PutUint32(
masterKeyFingerprintBytes[:], masterKeyFingerprint,
)
rpcAddrType, err := toRPCAddrType(addrType)
if err != nil {
return nil, nil, nil, fmt.Errorf("error converting address "+
"type: %v", err)
}
remoteAcct, err := r.walletClient.ImportAccount(
ctxt, &walletrpc.ImportAccountRequest{
Name: name,
ExtendedPublicKey: accountPubKey.String(),
MasterKeyFingerprint: masterKeyFingerprintBytes[:],
AddressType: rpcAddrType,
DryRun: dryRun,
},
)
if err != nil {
return nil, nil, nil, fmt.Errorf("error importing account on "+
"remote signer instance: %v", err)
}
props, extAddrs, intAddrs, err := r.WalletController.ImportAccount(
name, accountPubKey, masterKeyFingerprint, addrType, dryRun,
)
if err != nil {
return nil, nil, nil, fmt.Errorf("error importing account on "+
"local wallet instance: %v", err)
}
mismatchErr := fmt.Errorf("error importing account on remote signing "+
"instance, got different external addresses (%v) than on "+
"local wallet instance (%s)", remoteAcct.DryRunExternalAddrs,
extAddrs)
if len(remoteAcct.DryRunExternalAddrs) != len(extAddrs) {
return nil, nil, nil, mismatchErr
}
for idx, remoteExtAddr := range remoteAcct.DryRunExternalAddrs {
if extAddrs[idx].String() != remoteExtAddr {
return nil, nil, nil, mismatchErr
}
}
mismatchErr = fmt.Errorf("error importing account on remote signing "+
"instance, got different internal addresses (%v) than on "+
"local wallet instance (%s)", remoteAcct.DryRunInternalAddrs,
intAddrs)
if len(remoteAcct.DryRunInternalAddrs) != len(intAddrs) {
return nil, nil, nil, mismatchErr
}
for idx, remoteIntAddr := range remoteAcct.DryRunInternalAddrs {
if intAddrs[idx].String() != remoteIntAddr {
return nil, nil, nil, mismatchErr
}
}
return props, extAddrs, intAddrs, nil
}
// ImportPublicKey imports a single derived public key into the wallet. The
// address type can usually be inferred from the key's version, but in the case
// of legacy versions (xpub, tpub), an address type must be specified as we
// intend to not support importing BIP-44 keys into the wallet using the legacy
// pay-to-pubkey-hash (P2PKH) scheme.
func (r *RPCKeyRing) ImportPublicKey(pubKey *btcec.PublicKey,
addrType waddrmgr.AddressType) error {
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
rpcAddrType, err := toRPCAddrType(&addrType)
if err != nil {
return fmt.Errorf("error converting address type: %v", err)
}
_, err = r.walletClient.ImportPublicKey(
ctxt, &walletrpc.ImportPublicKeyRequest{
PublicKey: pubKey.SerializeCompressed(),
AddressType: rpcAddrType,
},
)
if err != nil {
return fmt.Errorf("error importing pubkey on remote signer "+
"instance: %v", err)
}
err = r.WalletController.ImportPublicKey(pubKey, addrType)
if err != nil {
return fmt.Errorf("error importing pubkey on local signer "+
"instance: %v", err)
}
return nil
} }
// SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying out to // SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying out to
@@ -296,7 +108,9 @@ func (r *RPCKeyRing) ImportPublicKey(pubKey *btcec.PublicKey,
// //
// NOTE: This method requires the global coin selection lock to be held. // NOTE: This method requires the global coin selection lock to be held.
// //
// This is a part of the WalletController interface. // NOTE: This is a part of the WalletController interface.
//
// NOTE: This method only signs with BIP49/84 keys.
func (r *RPCKeyRing) SendOutputs(outputs []*wire.TxOut, func (r *RPCKeyRing) SendOutputs(outputs []*wire.TxOut,
feeRate chainfee.SatPerKWeight, minConfs int32, feeRate chainfee.SatPerKWeight, minConfs int32,
label string) (*wire.MsgTx, error) { label string) (*wire.MsgTx, error) {
@@ -304,7 +118,7 @@ func (r *RPCKeyRing) SendOutputs(outputs []*wire.TxOut,
tx, err := r.WalletController.SendOutputs( tx, err := r.WalletController.SendOutputs(
outputs, feeRate, minConfs, label, outputs, feeRate, minConfs, label,
) )
if err != nil && err != btcwallet.ErrTxUnsigned { if err != nil && err != basewallet.ErrTxUnsigned {
return nil, err return nil, err
} }
if err == nil { if err == nil {
@@ -324,7 +138,9 @@ func (r *RPCKeyRing) SendOutputs(outputs []*wire.TxOut,
// watch-only wallet if it can map this outpoint into a coin we // watch-only wallet if it can map this outpoint into a coin we
// own. If not, then we can't continue because our wallet state // own. If not, then we can't continue because our wallet state
// is out of sync. // is out of sync.
info, err := r.coinFromOutPoint(txIn.PreviousOutPoint) info, err := r.WalletController.FetchInputInfo(
&txIn.PreviousOutPoint,
)
if err != nil { if err != nil {
return nil, fmt.Errorf("error looking up utxo: %v", err) return nil, fmt.Errorf("error looking up utxo: %v", err)
} }
@@ -332,7 +148,7 @@ func (r *RPCKeyRing) SendOutputs(outputs []*wire.TxOut,
// Now that we know the input is ours, we'll populate the // Now that we know the input is ours, we'll populate the
// signDesc with the per input unique information. // signDesc with the per input unique information.
signDesc.Output = &wire.TxOut{ signDesc.Output = &wire.TxOut{
Value: info.Value, Value: int64(info.Value),
PkScript: info.PkScript, PkScript: info.PkScript,
} }
signDesc.InputIndex = i signDesc.InputIndex = i
@@ -351,19 +167,17 @@ func (r *RPCKeyRing) SendOutputs(outputs []*wire.TxOut,
return tx, r.WalletController.PublishTransaction(tx, label) return tx, r.WalletController.PublishTransaction(tx, label)
} }
// FinalizePsbt expects a partial transaction with all inputs and outputs fully // SignPsbt expects a partial transaction with all inputs and outputs fully
// declared and tries to sign all inputs that belong to the specified account. // declared and tries to sign all unsigned inputs that have all required fields
// Lnd must be the last signer of the transaction. That means, if there are any // (UTXO information, BIP32 derivation information, witness or sig scripts) set.
// unsigned non-witness inputs or inputs without UTXO information attached or // If no error is returned, the PSBT is ready to be given to the next signer or
// inputs without witness data that do not belong to lnd's wallet, this method // to be finalized if lnd was the last signer.
// will fail. If no error is returned, the PSBT is ready to be extracted and the
// final TX within to be broadcast.
// //
// NOTE: This method does NOT publish the transaction after it's been // NOTE: This RPC only signs inputs (and only those it can sign), it does not
// finalized successfully. // perform any other tasks (such as coin selection, UTXO locking or
// // input/output/fee value validation, PSBT finalization). Any input that is
// This is a part of the WalletController interface. // incomplete will be skipped.
func (r *RPCKeyRing) FinalizePsbt(packet *psbt.Packet, accountName string) error { func (r *RPCKeyRing) SignPsbt(packet *psbt.Packet) error {
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel() defer cancel()
@@ -372,15 +186,16 @@ func (r *RPCKeyRing) FinalizePsbt(packet *psbt.Packet, accountName string) error
return fmt.Errorf("error serializing PSBT: %v", err) return fmt.Errorf("error serializing PSBT: %v", err)
} }
resp, err := r.walletClient.FinalizePsbt( resp, err := r.walletClient.SignPsbt(ctxt, &walletrpc.SignPsbtRequest{
ctxt, &walletrpc.FinalizePsbtRequest{ FundedPsbt: buf.Bytes(),
FundedPsbt: buf.Bytes(), })
Account: accountName,
},
)
if err != nil { if err != nil {
return fmt.Errorf("error finalizing PSBT in remote signer "+ err = fmt.Errorf("error signing PSBT in remote signer "+
"instance: %v", err) "instance: %v", err)
// Log as critical as we should shut down if there is no signer.
log.Criticalf("RPC signer error: %v", err)
return err
} }
signedPacket, err := psbt.NewFromRawBytes( signedPacket, err := psbt.NewFromRawBytes(
@@ -401,6 +216,137 @@ func (r *RPCKeyRing) FinalizePsbt(packet *psbt.Packet, accountName string) error
return nil return nil
} }
// FinalizePsbt expects a partial transaction with all inputs and outputs fully
// declared and tries to sign all inputs that belong to the specified account.
// Lnd must be the last signer of the transaction. That means, if there are any
// unsigned non-witness inputs or inputs without UTXO information attached or
// inputs without witness data that do not belong to lnd's wallet, this method
// will fail. If no error is returned, the PSBT is ready to be extracted and the
// final TX within to be broadcast.
//
// NOTE: This method does NOT publish the transaction after it's been
// finalized successfully.
//
// NOTE: This is a part of the WalletController interface.
//
// NOTE: We need to overwrite this method because we need to redirect the call
// to ComputeInputScript to the RPC key ring's implementation. If we forward
// the call to the default WalletController implementation, we get an error
// since that wallet is watch-only. If we forward the call to the remote signer,
// we get an error because the signer doesn't know the UTXO information required
// in ComputeInputScript.
//
// TODO(guggero): Refactor btcwallet to accept ComputeInputScript as a function
// parameter in FinalizePsbt so we can get rid of this code duplication.
func (r *RPCKeyRing) FinalizePsbt(packet *psbt.Packet, _ string) error {
// Let's check that this is actually something we can and want to sign.
// We need at least one input and one output.
err := psbt.VerifyInputOutputLen(packet, true, true)
if err != nil {
return err
}
// Go through each input that doesn't have final witness data attached
// to it already and try to sign it. We do expect that we're the last
// ones to sign. If there is any input without witness data that we
// cannot sign because it's not our UTXO, this will be a hard failure.
tx := packet.UnsignedTx
sigHashes := txscript.NewTxSigHashes(tx)
for idx, txIn := range tx.TxIn {
in := packet.Inputs[idx]
// We can only sign if we have UTXO information available. We
// can just continue here as a later step will fail with a more
// precise error message.
if in.WitnessUtxo == nil && in.NonWitnessUtxo == nil {
continue
}
// Skip this input if it's got final witness data attached.
if len(in.FinalScriptWitness) > 0 {
continue
}
// We can only sign this input if it's ours, so we try to map it
// to a coin we own. If we can't, then we'll continue as it
// isn't our input.
utxo, err := r.FetchInputInfo(&txIn.PreviousOutPoint)
if err != nil {
continue
}
fullTx := utxo.PrevTx
signDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{},
Output: &wire.TxOut{
Value: int64(utxo.Value),
PkScript: utxo.PkScript,
},
HashType: in.SighashType,
SigHashes: sigHashes,
InputIndex: idx,
}
// Find out what UTXO we are signing. Wallets _should_ always
// provide the full non-witness UTXO for segwit v0.
var signOutput *wire.TxOut
if in.NonWitnessUtxo != nil {
prevIndex := txIn.PreviousOutPoint.Index
signOutput = in.NonWitnessUtxo.TxOut[prevIndex]
if !psbt.TxOutsEqual(signDesc.Output, signOutput) {
return fmt.Errorf("found UTXO %#v but it "+
"doesn't match PSBT's input %v",
signDesc.Output, signOutput)
}
if fullTx.TxHash() != txIn.PreviousOutPoint.Hash {
return fmt.Errorf("found UTXO tx %v but it "+
"doesn't match PSBT's input %v",
fullTx.TxHash(),
txIn.PreviousOutPoint.Hash)
}
}
// Fall back to witness UTXO only for older wallets.
if in.WitnessUtxo != nil {
signOutput = in.WitnessUtxo
if !psbt.TxOutsEqual(signDesc.Output, signOutput) {
return fmt.Errorf("found UTXO %#v but it "+
"doesn't match PSBT's input %v",
signDesc.Output, signOutput)
}
}
// Do the actual signing in ComputeInputScript which in turn
// will invoke the remote signer.
script, err := r.ComputeInputScript(tx, signDesc)
if err != nil {
return fmt.Errorf("error computing input script for "+
"input %d: %v", idx, err)
}
// Serialize the witness format from the stack representation to
// the wire representation.
var witnessBytes bytes.Buffer
err = psbt.WriteTxWitness(&witnessBytes, script.Witness)
if err != nil {
return fmt.Errorf("error serializing witness: %v", err)
}
packet.Inputs[idx].FinalScriptWitness = witnessBytes.Bytes()
packet.Inputs[idx].FinalScriptSig = script.SigScript
}
// Make sure the PSBT itself thinks it's finalized and ready to be
// broadcast.
err = psbt.MaybeFinalizeAll(packet)
if err != nil {
return fmt.Errorf("error finalizing PSBT: %v", err)
}
return nil
}
// DeriveNextKey attempts to derive the *next* key within the key family // DeriveNextKey attempts to derive the *next* key within the key family
// (account in BIP43) specified. This method should return the next external // (account in BIP43) specified. This method should return the next external
// child within this branch. // child within this branch.
@@ -409,40 +355,7 @@ func (r *RPCKeyRing) FinalizePsbt(packet *psbt.Packet, accountName string) error
func (r *RPCKeyRing) DeriveNextKey( func (r *RPCKeyRing) DeriveNextKey(
keyFam keychain.KeyFamily) (keychain.KeyDescriptor, error) { keyFam keychain.KeyFamily) (keychain.KeyDescriptor, error) {
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) return r.watchOnlyKeyRing.DeriveNextKey(keyFam)
defer cancel()
// We need to keep the local and remote wallet in sync. That's why we
// first attempt to also derive the next key on the remote wallet.
remoteDesc, err := r.walletClient.DeriveNextKey(ctxt, &walletrpc.KeyReq{
KeyFamily: int32(keyFam),
})
if err != nil {
return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+
"key on remote signer instance: %v", err)
}
localDesc, err := r.watchOnlyKeyRing.DeriveNextKey(keyFam)
if err != nil {
return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+
"key on local wallet instance: %v", err)
}
// We never know if the administrator of the remote signing wallet does
// manual calls to next address or whatever. So we cannot be certain
// that we're always fully in sync. But as long as our local index is
// lower or equal to the remote index we know the remote wallet should
// have all keys we have locally. Only if the remote wallet falls behind
// the local we might have problems that the remote wallet won't know
// outputs we're giving it to sign.
if uint32(remoteDesc.KeyLoc.KeyIndex) < localDesc.Index {
return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+
"key on remote signer instance, derived index %d was "+
"lower than local index %d", remoteDesc.KeyLoc.KeyIndex,
localDesc.Index)
}
return localDesc, nil
} }
// DeriveKey attempts to derive an arbitrary key specified by the passed // DeriveKey attempts to derive an arbitrary key specified by the passed
@@ -453,41 +366,7 @@ func (r *RPCKeyRing) DeriveNextKey(
func (r *RPCKeyRing) DeriveKey( func (r *RPCKeyRing) DeriveKey(
keyLoc keychain.KeyLocator) (keychain.KeyDescriptor, error) { keyLoc keychain.KeyLocator) (keychain.KeyDescriptor, error) {
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) return r.watchOnlyKeyRing.DeriveKey(keyLoc)
defer cancel()
// We need to keep the local and remote wallet in sync. That's why we
// first attempt to also derive the same key on the remote wallet.
remoteDesc, err := r.walletClient.DeriveKey(ctxt, &signrpc.KeyLocator{
KeyFamily: int32(keyLoc.Family),
KeyIndex: int32(keyLoc.Index),
})
if err != nil {
return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+
"key on remote signer instance: %v", err)
}
localDesc, err := r.watchOnlyKeyRing.DeriveKey(keyLoc)
if err != nil {
return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+
"key on local wallet instance: %v", err)
}
// We never know if the administrator of the remote signing wallet does
// manual calls to next address or whatever. So we cannot be certain
// that we're always fully in sync. But as long as our local index is
// lower or equal to the remote index we know the remote wallet should
// have all keys we have locally. Only if the remote wallet falls behind
// the local we might have problems that the remote wallet won't know
// outputs we're giving it to sign.
if uint32(remoteDesc.KeyLoc.KeyIndex) < localDesc.Index {
return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+
"key on remote signer instance, derived index %d was "+
"lower than local index %d", remoteDesc.KeyLoc.KeyIndex,
localDesc.Index)
}
return localDesc, nil
} }
// ECDH performs a scalar multiplication (ECDH-like operation) between the // ECDH performs a scalar multiplication (ECDH-like operation) between the
@@ -522,6 +401,11 @@ func (r *RPCKeyRing) ECDH(keyDesc keychain.KeyDescriptor,
resp, err := r.signerClient.DeriveSharedKey(ctxt, req) resp, err := r.signerClient.DeriveSharedKey(ctxt, req)
if err != nil { if err != nil {
err = fmt.Errorf("error deriving shared key in remote signer "+
"instance: %v", err)
// Log as critical as we should shut down if there is no signer.
log.Criticalf("RPC signer error: %v", err)
return key, err return key, err
} }
@@ -550,6 +434,11 @@ func (r *RPCKeyRing) SignMessage(keyLoc keychain.KeyLocator,
DoubleHash: doubleHash, DoubleHash: doubleHash,
}) })
if err != nil { if err != nil {
err = fmt.Errorf("error signing message in remote signer "+
"instance: %v", err)
// Log as critical as we should shut down if there is no signer.
log.Criticalf("RPC signer error: %v", err)
return nil, err return nil, err
} }
@@ -586,6 +475,11 @@ func (r *RPCKeyRing) SignMessageCompact(keyLoc keychain.KeyLocator,
CompactSig: true, CompactSig: true,
}) })
if err != nil { if err != nil {
err = fmt.Errorf("error signing message in remote signer "+
"instance: %v", err)
// Log as critical as we should shut down if there is no signer.
log.Criticalf("RPC signer error: %v", err)
return nil, err return nil, err
} }
@@ -618,23 +512,15 @@ func (r *RPCKeyRing) DerivePrivKey(_ keychain.KeyDescriptor) (*btcec.PrivateKey,
// NOTE: The resulting signature should be void of a sighash byte. // NOTE: The resulting signature should be void of a sighash byte.
// //
// NOTE: This method is part of the input.Signer interface. // NOTE: This method is part of the input.Signer interface.
//
// NOTE: This method only signs with BIP1017 (internal) keys!
func (r *RPCKeyRing) SignOutputRaw(tx *wire.MsgTx, func (r *RPCKeyRing) SignOutputRaw(tx *wire.MsgTx,
signDesc *input.SignDescriptor) (input.Signature, error) { signDesc *input.SignDescriptor) (input.Signature, error) {
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) // Forward the call to the remote signing instance. This call is only
defer cancel() // ever called for signing witness (p2pkh or p2wsh) inputs and never
// nested witness inputs, so the sigScript is always nil.
rpcSignReq, err := toRPCSignReq(tx, signDesc) return r.remoteSign(tx, signDesc, nil)
if err != nil {
return nil, err
}
resp, err := r.signerClient.SignOutputRaw(ctxt, rpcSignReq)
if err != nil {
return nil, err
}
return btcec.ParseDERSignature(resp.RawSigs[0], btcec.S256())
} }
// ComputeInputScript generates a complete InputIndex for the passed // ComputeInputScript generates a complete InputIndex for the passed
@@ -651,115 +537,172 @@ func (r *RPCKeyRing) SignOutputRaw(tx *wire.MsgTx,
func (r *RPCKeyRing) ComputeInputScript(tx *wire.MsgTx, func (r *RPCKeyRing) ComputeInputScript(tx *wire.MsgTx,
signDesc *input.SignDescriptor) (*input.Script, error) { signDesc *input.SignDescriptor) (*input.Script, error) {
addr, witnessProgram, sigScript, err := r.WalletController.ScriptForOutput(
signDesc.Output,
)
if err != nil {
return nil, err
}
signDesc.WitnessScript = witnessProgram
// Let's give the TX to the remote instance now, so it can sign the
// input.
sig, err := r.remoteSign(tx, signDesc, sigScript)
if err != nil {
return nil, fmt.Errorf("error signing with remote instance: %v",
err)
}
// ComputeInputScript currently is only used for P2WKH and NP2WKH
// addresses. So the last item on the stack is always the compressed
// public key.
return &input.Script{
Witness: wire.TxWitness{
append(sig.Serialize(), byte(signDesc.HashType)),
addr.PubKey().SerializeCompressed(),
},
SigScript: sigScript,
}, nil
}
// remoteSign signs the input specified in signDesc of the given transaction tx
// using the remote signing instance.
func (r *RPCKeyRing) remoteSign(tx *wire.MsgTx, signDesc *input.SignDescriptor,
sigScript []byte) (input.Signature, error) {
packet, err := packetFromTx(tx)
if err != nil {
return nil, fmt.Errorf("error converting TX into PSBT: %v", err)
}
// Catch incorrect signing input index, just in case.
if signDesc.InputIndex < 0 || signDesc.InputIndex >= len(packet.Inputs) {
return nil, fmt.Errorf("invalid input index in sign descriptor")
}
in := &packet.Inputs[signDesc.InputIndex]
txIn := tx.TxIn[signDesc.InputIndex]
// Make sure we actually know about the input. We either have been
// watching the UTXO on-chain or we have been given all the required
// info in the sign descriptor.
info, err := r.WalletController.FetchInputInfo(&txIn.PreviousOutPoint)
switch {
// No error, we do have the full UTXO and derivation info available.
case err == nil:
in.WitnessUtxo = &wire.TxOut{
Value: int64(info.Value),
PkScript: info.PkScript,
}
in.NonWitnessUtxo = info.PrevTx
in.Bip32Derivation = []*psbt.Bip32Derivation{info.Derivation}
// The wallet doesn't know about this UTXO, so it's probably a TX that
// we haven't published yet (e.g. a channel funding TX). So we need to
// assemble everything from the sign descriptor. We won't be able to
// supply a non-witness UTXO (=full TX of the input being spent) in this
// case. That is no problem if the signing instance is another lnd
// instance since we don't require it for pure witness inputs. But a
// hardware wallet might require it for security reasons.
case signDesc.KeyDesc.PubKey != nil && signDesc.Output != nil:
in.WitnessUtxo = signDesc.Output
in.Bip32Derivation = []*psbt.Bip32Derivation{{
Bip32Path: []uint32{
keychain.BIP0043Purpose +
hdkeychain.HardenedKeyStart,
r.coinType + hdkeychain.HardenedKeyStart,
uint32(signDesc.KeyDesc.Family) +
hdkeychain.HardenedKeyStart,
0,
signDesc.KeyDesc.Index,
},
PubKey: signDesc.KeyDesc.PubKey.SerializeCompressed(),
}}
default:
return nil, fmt.Errorf("error assembling UTXO information, "+
"wallet returned err='%v' and sign descriptor is "+
"incomplete", err)
}
// Assemble all other information about the input we have.
in.RedeemScript = sigScript
in.SighashType = signDesc.HashType
in.WitnessScript = signDesc.WitnessScript
if len(signDesc.SingleTweak) > 0 {
in.Unknowns = append(in.Unknowns, &psbt.Unknown{
Key: btcwallet.PsbtKeyTypeInputSignatureTweakSingle,
Value: signDesc.SingleTweak,
})
}
if signDesc.DoubleTweak != nil {
in.Unknowns = append(in.Unknowns, &psbt.Unknown{
Key: btcwallet.PsbtKeyTypeInputSignatureTweakDouble,
Value: signDesc.DoubleTweak.Serialize(),
})
}
// Okay, let's sign the input by the remote signer now.
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel() defer cancel()
rpcSignReq, err := toRPCSignReq(tx, signDesc)
if err != nil {
return nil, err
}
resp, err := r.signerClient.ComputeInputScript(ctxt, rpcSignReq)
if err != nil {
return nil, err
}
return &input.Script{
Witness: resp.InputScripts[0].Witness,
SigScript: resp.InputScripts[0].SigScript,
}, nil
}
// coinFromOutPoint attempts to locate details pertaining to a coin based on
// its outpoint. If the coin isn't under the control of the backing watch-only
// wallet, then an error is returned.
func (r *RPCKeyRing) coinFromOutPoint(op wire.OutPoint) (*chanfunding.Coin,
error) {
inputInfo, err := r.WalletController.FetchInputInfo(&op)
if err != nil {
return nil, err
}
return &chanfunding.Coin{
TxOut: wire.TxOut{
Value: int64(inputInfo.Value),
PkScript: inputInfo.PkScript,
},
OutPoint: inputInfo.OutPoint,
}, nil
}
// toRPCSignReq converts the given raw transaction and sign descriptors into
// their corresponding RPC counterparts.
func toRPCSignReq(tx *wire.MsgTx,
signDesc *input.SignDescriptor) (*signrpc.SignReq, error) {
if signDesc.Output == nil {
return nil, fmt.Errorf("need output to sign")
}
var buf bytes.Buffer var buf bytes.Buffer
if err := tx.Serialize(&buf); err != nil { if err := packet.Serialize(&buf); err != nil {
return nil, fmt.Errorf("error serializing PSBT: %v", err)
}
resp, err := r.walletClient.SignPsbt(
ctxt, &walletrpc.SignPsbtRequest{FundedPsbt: buf.Bytes()},
)
if err != nil {
err = fmt.Errorf("error signing PSBT in remote signer "+
"instance: %v", err)
// Log as critical as we should shut down if there is no signer.
log.Criticalf("RPC signer error: %v", err)
return nil, err return nil, err
} }
rpcSignDesc := &signrpc.SignDescriptor{ signedPacket, err := psbt.NewFromRawBytes(
KeyDesc: &signrpc.KeyDescriptor{ bytes.NewReader(resp.SignedPsbt), false,
KeyLoc: &signrpc.KeyLocator{ )
KeyFamily: int32(signDesc.KeyDesc.Family), if err != nil {
KeyIndex: int32(signDesc.KeyDesc.Index), return nil, fmt.Errorf("error parsing signed PSBT: %v", err)
},
},
SingleTweak: signDesc.SingleTweak,
WitnessScript: signDesc.WitnessScript,
Output: &signrpc.TxOut{
Value: signDesc.Output.Value,
PkScript: signDesc.Output.PkScript,
},
Sighash: uint32(signDesc.HashType),
InputIndex: int32(signDesc.InputIndex),
} }
if signDesc.KeyDesc.PubKey != nil { // We expect a signature in the input now.
rpcSignDesc.KeyDesc.RawKeyBytes = if signDesc.InputIndex >= len(signedPacket.Inputs) {
signDesc.KeyDesc.PubKey.SerializeCompressed() return nil, fmt.Errorf("remote signer returned invalid PSBT")
} }
if signDesc.DoubleTweak != nil { in = &signedPacket.Inputs[signDesc.InputIndex]
rpcSignDesc.DoubleTweak = signDesc.DoubleTweak.Serialize() if len(in.PartialSigs) != 1 {
return nil, fmt.Errorf("remote signer returned invalid "+
"partial signature, wanted 1, got %d",
len(in.PartialSigs))
}
sigWithSigHash := in.PartialSigs[0]
if sigWithSigHash == nil {
return nil, fmt.Errorf("remote signer returned nil signature")
} }
return &signrpc.SignReq{ // The remote signer always adds the sighash type, so we need to account
RawTxBytes: buf.Bytes(), // for that.
SignDescs: []*signrpc.SignDescriptor{rpcSignDesc}, if len(sigWithSigHash.Signature) < btcec.MinSigLen+1 {
}, nil return nil, fmt.Errorf("remote signer returned invalid "+
} "partial signature: signature too short with %d bytes",
len(sigWithSigHash.Signature))
// toRPCAddrType converts the given address type to its RPC counterpart.
func toRPCAddrType(addrType *waddrmgr.AddressType) (walletrpc.AddressType,
error) {
if addrType == nil {
return walletrpc.AddressType_UNKNOWN, nil
} }
switch *addrType { // Parse the signature, but chop off the last byte which is the sighash
case waddrmgr.WitnessPubKey: // type.
return walletrpc.AddressType_WITNESS_PUBKEY_HASH, nil sig := sigWithSigHash.Signature[0 : len(sigWithSigHash.Signature)-1]
return btcec.ParseDERSignature(sig, btcec.S256())
case waddrmgr.NestedWitnessPubKey:
return walletrpc.AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH,
nil
default:
return 0, fmt.Errorf("unhandled address type %v", *addrType)
}
} }
// connectRPC tries to establish an RPC connection to the given host:port with // connectRPC tries to establish an RPC connection to the given host:port with
// the supplied certificate and macaroon. // the supplied certificate and macaroon.
func connectRPC(hostPort, tlsCertPath, macaroonPath string) (*grpc.ClientConn, func connectRPC(hostPort, tlsCertPath, macaroonPath string,
error) { timeout time.Duration) (*grpc.ClientConn, error) {
certBytes, err := ioutil.ReadFile(tlsCertPath) certBytes, err := ioutil.ReadFile(tlsCertPath)
if err != nil { if err != nil {
@@ -793,8 +736,11 @@ func connectRPC(hostPort, tlsCertPath, macaroonPath string) (*grpc.ClientConn,
cp, "", cp, "",
)), )),
grpc.WithPerRPCCredentials(macCred), grpc.WithPerRPCCredentials(macCred),
grpc.WithBlock(),
} }
conn, err := grpc.Dial(hostPort, opts...) ctxt, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
conn, err := grpc.DialContext(ctxt, hostPort, opts...)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to connect to RPC server: %v", return nil, fmt.Errorf("unable to connect to RPC server: %v",
err) err)
@@ -802,3 +748,42 @@ func connectRPC(hostPort, tlsCertPath, macaroonPath string) (*grpc.ClientConn,
return conn, nil return conn, nil
} }
// packetFromTx creates a PSBT from a tx that potentially already contains
// signed inputs.
func packetFromTx(original *wire.MsgTx) (*psbt.Packet, error) {
// The psbt.NewFromUnsignedTx function complains if there are any
// scripts or witness content on a TX. So we create a copy of the TX and
// nil out all the offending data, but also keep a backup around that we
// add to the PSBT afterwards.
noSigs := original.Copy()
for idx := range noSigs.TxIn {
noSigs.TxIn[idx].SignatureScript = nil
noSigs.TxIn[idx].Witness = nil
}
// With all the data that is seen as "signed", we can now create the
// empty packet.
packet, err := psbt.NewFromUnsignedTx(noSigs)
if err != nil {
return nil, err
}
var buf bytes.Buffer
for idx, txIn := range original.TxIn {
if len(txIn.SignatureScript) > 0 {
packet.Inputs[idx].FinalScriptSig = txIn.SignatureScript
}
if len(txIn.Witness) > 0 {
buf.Reset()
err = psbt.WriteTxWitness(&buf, txIn.Witness)
if err != nil {
return nil, err
}
packet.Inputs[idx].FinalScriptWitness = buf.Bytes()
}
}
return packet, nil
}
+4
View File
@@ -29,8 +29,10 @@ import (
"github.com/lightningnetwork/lnd/lnrpc/verrpc" "github.com/lightningnetwork/lnd/lnrpc/verrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/lightningnetwork/lnd/lnwallet/chancloser" "github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding" "github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"github.com/lightningnetwork/lnd/lnwallet/rpcwallet"
"github.com/lightningnetwork/lnd/monitoring" "github.com/lightningnetwork/lnd/monitoring"
"github.com/lightningnetwork/lnd/netann" "github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/peer" "github.com/lightningnetwork/lnd/peer"
@@ -163,6 +165,8 @@ func SetupLoggers(root *build.RotatingLogWriter, interceptor signal.Interceptor)
AddSubLogger(root, cluster.Subsystem, interceptor, cluster.UseLogger) AddSubLogger(root, cluster.Subsystem, interceptor, cluster.UseLogger)
AddSubLogger(root, rpcperms.Subsystem, interceptor, rpcperms.UseLogger) AddSubLogger(root, rpcperms.Subsystem, interceptor, rpcperms.UseLogger)
AddSubLogger(root, tor.Subsystem, interceptor, tor.UseLogger) AddSubLogger(root, tor.Subsystem, interceptor, tor.UseLogger)
AddSubLogger(root, btcwallet.Subsystem, interceptor, btcwallet.UseLogger)
AddSubLogger(root, rpcwallet.Subsystem, interceptor, rpcwallet.UseLogger)
} }
// AddSubLogger is a helper method to conveniently create and register the // AddSubLogger is a helper method to conveniently create and register the
+27
View File
@@ -1006,6 +1006,23 @@ litecoin.node=ltcd
; value must be >= 1m. ; value must be >= 1m.
; healthcheck.torconnection.interval=1m ; healthcheck.torconnection.interval=1m
; The number of times we should attempt to check our remote signer RPC
; connection before gracefully shutting down. Set this value to 0 to disable
; this health check.
; healthcheck.remotesigner.attempts=1
; The amount of time we allow a call to our remote signer RPC connection to take
; before we fail the attempt. This value must be >= 1s.
; healthcheck.remotesigner.timeout=1s
; The amount of time we should backoff between failed attempts to check remote
; signer RPC connection. This value must be >= 1s.
; healthcheck.remotesigner.backoff=30s
; The amount of time we should wait between remote signer RPC connection health
; checks. This value must be >= 1m.
; healthcheck.remotesigner.interval=1m
[signrpc] [signrpc]
@@ -1253,6 +1270,16 @@ litecoin.node=ltcd
; The TLS certificate to use for establishing the remote signer's identity. ; The TLS certificate to use for establishing the remote signer's identity.
; remotesigner.tlscertpath=/path/to/remote/signer/tls.cert ; remotesigner.tlscertpath=/path/to/remote/signer/tls.cert
; The timeout for connecting to and signing requests with the remote signer.
; Valid time units are {s, m, h}.
; remotesigner.timeout=5s
; If a wallet with private key material already exists, migrate it into a
; watch-only wallet on first startup.
; WARNING: This cannot be undone! Make sure you have backed up your seed before
; you use this flag! All private keys will be purged from the wallet after first
; unlock with this flag!
; remotesigner.migrate-wallet-to-watch-only=true
[gossip] [gossip]
+27
View File
@@ -52,6 +52,7 @@ import (
"github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/rpcwallet"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/nat" "github.com/lightningnetwork/lnd/nat"
"github.com/lightningnetwork/lnd/netann" "github.com/lightningnetwork/lnd/netann"
@@ -1584,6 +1585,32 @@ func (s *server) createLivenessMonitor(cfg *Config, cc *chainreg.ChainControl) {
checks = append(checks, torConnectionCheck) checks = append(checks, torConnectionCheck)
} }
// If remote signing is enabled, add the healthcheck for the remote
// signing RPC interface.
if s.cfg.RemoteSigner != nil && s.cfg.RemoteSigner.Enable {
// Because we have two cascading timeouts here, we need to add
// some slack to the "outer" one of them in case the "inner"
// returns exactly on time.
overhead := time.Millisecond * 10
remoteSignerConnectionCheck := healthcheck.NewObservation(
"remote signer connection",
rpcwallet.HealthCheck(
s.cfg.RemoteSigner,
// For the health check we might to be even
// stricter than the initial/normal connect, so
// we use the health check timeout here.
cfg.HealthChecks.RemoteSigner.Timeout,
),
cfg.HealthChecks.RemoteSigner.Interval,
cfg.HealthChecks.RemoteSigner.Timeout+overhead,
cfg.HealthChecks.RemoteSigner.Backoff,
cfg.HealthChecks.RemoteSigner.Attempts,
)
checks = append(checks, remoteSignerConnectionCheck)
}
// If we have not disabled all of our health checks, we create a // If we have not disabled all of our health checks, we create a
// liveliness monitor with our configured checks. // liveliness monitor with our configured checks.
s.livelinessMonitor = healthcheck.NewMonitor( s.livelinessMonitor = healthcheck.NewMonitor(