From 6093393e2ffe4ae9264f0bfa70e3310dfdbf29be Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 14 Oct 2021 15:42:44 +0200 Subject: [PATCH 01/14] multi: refactor SignMessage to specify hashing --- funding/manager.go | 8 ++++---- funding/manager_test.go | 4 ++-- lntest/mock/signer.go | 9 +++++++-- lnwallet/btcwallet/signer.go | 9 +++++++-- lnwallet/interface.go | 6 +++--- netann/channel_update_test.go | 2 +- netann/node_signer.go | 4 ++-- netann/sign.go | 2 +- 8 files changed, 27 insertions(+), 17 deletions(-) diff --git a/funding/manager.go b/funding/manager.go index 2d9df837d..7479d5082 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -327,7 +327,7 @@ type Config struct { // TODO(roasbeef): should instead pass on this responsibility to a // distinct sub-system? SignMessage func(keyLoc keychain.KeyLocator, - msg []byte) (*btcec.Signature, error) + msg []byte, doubleHash bool) (*btcec.Signature, error) // CurrentNodeAnnouncement should return the latest, fully signed node // announcement from the backing Lightning Network node. @@ -2911,7 +2911,7 @@ func (f *Manager) newChanAnnouncement(localPubKey, if err != nil { return nil, err } - sig, err := f.cfg.SignMessage(f.cfg.IDKeyLoc, chanUpdateMsg) + sig, err := f.cfg.SignMessage(f.cfg.IDKeyLoc, chanUpdateMsg, true) if err != nil { return nil, errors.Errorf("unable to generate channel "+ "update announcement signature: %v", err) @@ -2933,13 +2933,13 @@ func (f *Manager) newChanAnnouncement(localPubKey, if err != nil { return nil, err } - nodeSig, err := f.cfg.SignMessage(f.cfg.IDKeyLoc, chanAnnMsg) + nodeSig, err := f.cfg.SignMessage(f.cfg.IDKeyLoc, chanAnnMsg, true) if err != nil { return nil, errors.Errorf("unable to generate node "+ "signature for channel announcement: %v", err) } bitcoinSig, err := f.cfg.SignMessage( - localFundingKey.KeyLocator, chanAnnMsg, + localFundingKey.KeyLocator, chanAnnMsg, true, ) if err != nil { return nil, errors.Errorf("unable to generate bitcoin "+ diff --git a/funding/manager_test.go b/funding/manager_test.go index 7261ab636..bfd47329f 100644 --- a/funding/manager_test.go +++ b/funding/manager_test.go @@ -362,7 +362,7 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey, Notifier: chainNotifier, FeeEstimator: estimator, SignMessage: func(_ keychain.KeyLocator, - _ []byte) (*btcec.Signature, error) { + _ []byte, _ bool) (*btcec.Signature, error) { return testSig, nil }, @@ -510,7 +510,7 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) { Notifier: oldCfg.Notifier, FeeEstimator: oldCfg.FeeEstimator, SignMessage: func(_ keychain.KeyLocator, - _ []byte) (*btcec.Signature, error) { + _ []byte, _ bool) (*btcec.Signature, error) { return testSig, nil }, diff --git a/lntest/mock/signer.go b/lntest/mock/signer.go index ce4dc55f3..a0ce79b92 100644 --- a/lntest/mock/signer.go +++ b/lntest/mock/signer.go @@ -116,7 +116,7 @@ func (s *SingleSigner) ComputeInputScript(tx *wire.MsgTx, // SignMessage takes a public key and a message and only signs the message // with the stored private key if the public key matches the private key. func (s *SingleSigner) SignMessage(keyLoc keychain.KeyLocator, - msg []byte) (*btcec.Signature, error) { + msg []byte, doubleHash bool) (*btcec.Signature, error) { mockKeyLoc := s.KeyLoc if s.KeyLoc.IsEmpty() { @@ -127,7 +127,12 @@ func (s *SingleSigner) SignMessage(keyLoc keychain.KeyLocator, return nil, fmt.Errorf("unknown public key") } - digest := chainhash.DoubleHashB(msg) + var digest []byte + if doubleHash { + digest = chainhash.DoubleHashB(msg) + } else { + digest = chainhash.HashB(msg) + } sign, err := s.Privkey.Sign(digest) if err != nil { return nil, fmt.Errorf("can't sign the message: %v", err) diff --git a/lnwallet/btcwallet/signer.go b/lnwallet/btcwallet/signer.go index a710565e2..277f39132 100644 --- a/lnwallet/btcwallet/signer.go +++ b/lnwallet/btcwallet/signer.go @@ -267,7 +267,7 @@ var _ input.Signer = (*BtcWallet)(nil) // // NOTE: This is a part of the MessageSigner interface. func (b *BtcWallet) SignMessage(keyLoc keychain.KeyLocator, - msg []byte) (*btcec.Signature, error) { + msg []byte, doubleHash bool) (*btcec.Signature, error) { // First attempt to fetch the private key which corresponds to the // specified public key. @@ -279,7 +279,12 @@ func (b *BtcWallet) SignMessage(keyLoc keychain.KeyLocator, } // Double hash and sign the data. - msgDigest := chainhash.DoubleHashB(msg) + var msgDigest []byte + if doubleHash { + msgDigest = chainhash.DoubleHashB(msg) + } else { + msgDigest = chainhash.HashB(msg) + } sign, err := privKey.Sign(msgDigest) if err != nil { return nil, errors.Errorf("unable sign the message: %v", err) diff --git a/lnwallet/interface.go b/lnwallet/interface.go index 9e94cfa0a..b15fa7a03 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -442,9 +442,9 @@ type MessageSigner interface { // SignMessage attempts to sign a target message with the private key // described in the key locator. If the target private key is unable to // be found, then an error will be returned. The actual digest signed is - // the double SHA-256 of the passed message. - SignMessage(keyLoc keychain.KeyLocator, msg []byte) (*btcec.Signature, - error) + // the single or double SHA-256 of the passed message. + SignMessage(keyLoc keychain.KeyLocator, msg []byte, + doubleHash bool) (*btcec.Signature, error) } // WalletDriver represents a "driver" for a particular concrete diff --git a/netann/channel_update_test.go b/netann/channel_update_test.go index 5af21540f..e28f38184 100644 --- a/netann/channel_update_test.go +++ b/netann/channel_update_test.go @@ -18,7 +18,7 @@ type mockSigner struct { } func (m *mockSigner) SignMessage(_ keychain.KeyLocator, - _ []byte) (*btcec.Signature, error) { + _ []byte, _ bool) (*btcec.Signature, error) { if m.err != nil { return nil, m.err diff --git a/netann/node_signer.go b/netann/node_signer.go index eb2d813e8..d35728269 100644 --- a/netann/node_signer.go +++ b/netann/node_signer.go @@ -26,7 +26,7 @@ func NewNodeSigner(keySigner keychain.SingleKeyMessageSigner) *NodeSigner { // resident node's private key described in the key locator. If the target key // locator is _not_ the node's private key, then an error will be returned. func (n *NodeSigner) SignMessage(keyLoc keychain.KeyLocator, - msg []byte) (*btcec.Signature, error) { + msg []byte, doubleHash bool) (*btcec.Signature, error) { // If this isn't our identity public key, then we'll exit early with an // error as we can't sign with this key. @@ -35,7 +35,7 @@ func (n *NodeSigner) SignMessage(keyLoc keychain.KeyLocator, } // Otherwise, we'll sign the double-sha256 of the target message. - sig, err := n.keySigner.SignMessage(msg, true) + sig, err := n.keySigner.SignMessage(msg, doubleHash) if err != nil { return nil, fmt.Errorf("can't sign the message: %v", err) } diff --git a/netann/sign.go b/netann/sign.go index 2ee4d0d08..86634f628 100644 --- a/netann/sign.go +++ b/netann/sign.go @@ -33,5 +33,5 @@ func SignAnnouncement(signer lnwallet.MessageSigner, keyLoc keychain.KeyLocator, return nil, fmt.Errorf("unable to get data to sign: %v", err) } - return signer.SignMessage(keyLoc, data) + return signer.SignMessage(keyLoc, data, true) } From 1309c6afeab73b6ff54f841ad710218cfb097050 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 14 Oct 2021 15:42:45 +0200 Subject: [PATCH 02/14] multi: allow internal wallet to be watch-only --- go.mod | 2 +- go.sum | 4 ++-- keychain/btcwallet.go | 32 +++++++++++++++++++++----------- lnwallet/btcwallet/btcwallet.go | 10 +++++++--- lnwallet/btcwallet/config.go | 4 ++++ 5 files changed, 35 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index d013b6d76..1502026b6 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890 - github.com/btcsuite/btcwallet v0.12.1-0.20210826004415-4ef582f76b02 + github.com/btcsuite/btcwallet v0.12.1-0.20210916213031-d0868cb9dd94 github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0 github.com/btcsuite/btcwallet/wallet/txrules v1.1.0 github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 // indirect diff --git a/go.sum b/go.sum index e71a5fc92..6ad1c41df 100644 --- a/go.sum +++ b/go.sum @@ -91,8 +91,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.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/btcwallet v0.12.1-0.20210826004415-4ef582f76b02 h1:Q8Scm1SXNRyiXzD3a7O1C6bLFIUxUQSnWAd9aat8Xd0= -github.com/btcsuite/btcwallet v0.12.1-0.20210826004415-4ef582f76b02/go.mod h1:SdqXKJoEEi5LJq6zU67PcKiyqF97AcUOfBfyQHC7rqQ= +github.com/btcsuite/btcwallet v0.12.1-0.20210916213031-d0868cb9dd94 h1:Bx+xu606h2sZNn5VaZMWvI0GtlGE+y+dHI4hbL5Ld6k= +github.com/btcsuite/btcwallet v0.12.1-0.20210916213031-d0868cb9dd94/go.mod h1:gHFk6GQ4IP/a8z6mfwK85GagUPxvAxCmgFy/whrBXhI= 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.1-0.20210329233242-e0607006dce6/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU= github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0 h1:8pO0pvPX1rFRfRiol4oV6kX7dY5y4chPwhfVwUfvwtk= diff --git a/keychain/btcwallet.go b/keychain/btcwallet.go index 79260ec94..65c23b78b 100644 --- a/keychain/btcwallet.go +++ b/keychain/btcwallet.go @@ -93,7 +93,7 @@ func (b *BtcWalletKeyRing) keyScope() (*waddrmgr.ScopedKeyManager, error) { // Otherwise, we'll first do a check to ensure that the root manager // isn't locked, as otherwise we won't be able to *use* the scope. - if b.wallet.Manager.IsLocked() { + if !b.wallet.Manager.WatchOnly() && b.wallet.Manager.IsLocked() { return nil, fmt.Errorf("cannot create BtcWalletKeyRing with " + "locked waddrmgr.Manager") } @@ -216,10 +216,16 @@ func (b *BtcWalletKeyRing) DeriveKey(keyLoc KeyLocator) (KeyDescriptor, error) { // If the account doesn't exist, then we may need to create it // for the first time in order to derive the keys that we - // require. - err = b.createAccountIfNotExists(addrmgrNs, keyLoc.Family, scope) - if err != nil { - return err + // require. We skip this if we're using a remote signer in which + // case we _need_ to create all accounts when creating the + // wallet, so it must exist now. + if !b.wallet.Manager.WatchOnly() { + err = b.createAccountIfNotExists( + addrmgrNs, keyLoc.Family, scope, + ) + if err != nil { + return err + } } path := waddrmgr.DerivationPath{ @@ -280,12 +286,16 @@ func (b *BtcWalletKeyRing) DerivePrivKey(keyDesc KeyDescriptor) ( // If the account doesn't exist, then we may need to create it // for the first time in order to derive the keys that we - // require. - err = b.createAccountIfNotExists( - addrmgrNs, keyDesc.Family, scope, - ) - if err != nil { - return err + // require. We skip this if we're using a remote signer in which + // case we _need_ to create all accounts when creating the + // wallet, so it must exist now. + if !b.wallet.Manager.WatchOnly() { + err = b.createAccountIfNotExists( + addrmgrNs, keyDesc.Family, scope, + ) + if err != nil { + return err + } } // If the public key isn't set or they have a non-zero index, diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index 1db9e0b18..0dd38da04 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -291,10 +291,14 @@ func (b *BtcWallet) Start() error { // We'll start by unlocking the wallet and ensuring that the KeyScope: // (1017, 1) exists within the internal waddrmgr. We'll need this in // order to properly generate the keys required for signing various - // contracts. - if err := b.wallet.Unlock(b.cfg.PrivatePass, nil); err != nil { - return err + // contracts. If this is a watch-only wallet, we don't have any private + // keys and therefore unlocking is not necessary. + if !b.cfg.WatchOnly { + if err := b.wallet.Unlock(b.cfg.PrivatePass, nil); err != nil { + return err + } } + _, err := b.wallet.Manager.FetchScopedKeyManager(b.chainKeyScope) if err != nil { // If the scope hasn't yet been created (it wouldn't been diff --git a/lnwallet/btcwallet/config.go b/lnwallet/btcwallet/config.go index a69217ead..f97f2943f 100644 --- a/lnwallet/btcwallet/config.go +++ b/lnwallet/btcwallet/config.go @@ -72,6 +72,10 @@ type Config struct { // CoinSelectionStrategy is the strategy that is used for selecting // coins when funding a transaction. CoinSelectionStrategy wallet.CoinSelectionStrategy + + // WatchOnly indicates that the wallet was initialized with public key + // material only and does not contain any private keys. + WatchOnly bool } // NetworkDir returns the directory name of a network directory to hold wallet From 9fa9dd8e43feca8cd2618da7feef2ec929155427 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 14 Oct 2021 15:42:47 +0200 Subject: [PATCH 03/14] multi: extract key ring creation from chain control To make it possible to supply our own implementation of a secret key ring, we extract that part from the chain control and split the whole chain control creation into two parts. --- chainreg/chainregistry.go | 40 +++---------- config.go | 1 + config_builder.go | 119 +++++++++++++++++++++++++++----------- lnd.go | 11 +++- 4 files changed, 103 insertions(+), 68 deletions(-) diff --git a/chainreg/chainregistry.go b/chainreg/chainregistry.go index 2b3ec4bc5..0980b729e 100644 --- a/chainreg/chainregistry.go +++ b/chainreg/chainregistry.go @@ -30,7 +30,6 @@ import ( "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnwallet" - "github.com/lightningnetwork/lnd/lnwallet/btcwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/chainview" @@ -664,11 +663,17 @@ func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) { // full-node, another backed by a running bitcoind full-node, and the other // backed by a running neutrino light client instance. When running with a // neutrino light client instance, `neutrinoCS` must be non-nil. -func NewChainControl(walletConfig *btcwallet.Config, +func NewChainControl(walletConfig lnwallet.Config, + msgSigner lnwallet.MessageSigner, pcc *PartialChainControl) (*ChainControl, func(), error) { cc := &ChainControl{ PartialChainControl: pcc, + MsgSigner: msgSigner, + Signer: walletConfig.Signer, + ChainIO: walletConfig.ChainIO, + Wc: walletConfig.WalletController, + KeyRing: walletConfig.SecretKeyRing, } ccCleanup := func() { @@ -679,36 +684,7 @@ func NewChainControl(walletConfig *btcwallet.Config, } } - wc, err := btcwallet.New(*walletConfig, pcc.Cfg.BlockCache) - if err != nil { - fmt.Printf("unable to create wallet controller: %v\n", err) - return nil, ccCleanup, err - } - - cc.MsgSigner = wc - cc.Signer = wc - cc.ChainIO = wc - cc.Wc = wc - - keyRing := keychain.NewBtcWalletKeyRing( - wc.InternalWallet(), walletConfig.CoinType, - ) - cc.KeyRing = keyRing - - // Create, and start the lnwallet, which handles the core payment - // channel logic, and exposes control via proxy state machines. - walletCfg := lnwallet.Config{ - Database: pcc.Cfg.ChanStateDB, - Notifier: cc.ChainNotifier, - WalletController: wc, - Signer: cc.Signer, - FeeEstimator: cc.FeeEstimator, - SecretKeyRing: keyRing, - ChainIO: cc.ChainIO, - DefaultConstraints: cc.ChannelConstraints, - NetParams: *walletConfig.NetParams, - } - lnWallet, err := lnwallet.NewLightningWallet(walletCfg) + lnWallet, err := lnwallet.NewLightningWallet(walletConfig) if err != nil { fmt.Printf("unable to create wallet: %v\n", err) return nil, ccCleanup, err diff --git a/config.go b/config.go index 6044ae0c6..0fd4fd3e2 100644 --- a/config.go +++ b/config.go @@ -1547,6 +1547,7 @@ func (c *Config) ImplementationConfig( RestRegistrar: defaultImpl, ExternalValidator: defaultImpl, DatabaseBuilder: NewDefaultDatabaseBuilder(c, ltndLog), + WalletConfigBuilder: defaultImpl, ChainControlBuilder: defaultImpl, } } diff --git a/config_builder.go b/config_builder.go index 525e65f27..a70ece5a6 100644 --- a/config_builder.go +++ b/config_builder.go @@ -86,15 +86,24 @@ type DatabaseBuilder interface { BuildDatabase(ctx context.Context) (*DatabaseInstances, func(), error) } +// WalletConfigBuilder is an interface that must be satisfied by a custom wallet +// implementation. +type WalletConfigBuilder interface { + // BuildWalletConfig is responsible for creating or unlocking and then + // fully initializing a wallet. + BuildWalletConfig(context.Context, *DatabaseInstances, + *rpcperms.InterceptorChain, + []*ListenerWithSignal) (*chainreg.PartialChainControl, + *btcwallet.Config, func(), error) +} + // ChainControlBuilder is an interface that must be satisfied by a custom wallet // implementation. type ChainControlBuilder interface { - // BuildChainControl is responsible for creating or unlocking and then - // fully initializing a wallet and returning it as part of a fully - // populated chain control instance. - BuildChainControl(context.Context, *DatabaseInstances, - *rpcperms.InterceptorChain, - []*ListenerWithSignal) (*chainreg.ChainControl, func(), error) + // BuildChainControl is responsible for creating a fully populated chain + // control instance from a wallet. + BuildChainControl(*chainreg.PartialChainControl, + *btcwallet.Config) (*chainreg.ChainControl, func(), error) } // ImplementationCfg is a struct that holds all configuration items for @@ -116,6 +125,10 @@ type ImplementationCfg struct { // backend instances. DatabaseBuilder + // WalletConfigBuilder is a type that can provide a wallet configuration + // with a fully loaded and unlocked wallet. + WalletConfigBuilder + // ChainControlBuilder is a type that can provide a custom wallet // implementation. ChainControlBuilder @@ -195,15 +208,14 @@ func (d *DefaultWalletImpl) Permissions() map[string][]bakery.Op { return nil } -// BuildChainControl is responsible for creating or unlocking and then fully -// initializing a wallet and returning it as part of a fully populated chain -// control instance. +// BuildWalletConfig is responsible for creating or unlocking and then +// fully initializing a wallet. // -// NOTE: This is part of the ChainControlBuilder interface. -func (d *DefaultWalletImpl) BuildChainControl(ctx context.Context, +// NOTE: This is part of the WalletConfigBuilder interface. +func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context, dbs *DatabaseInstances, interceptorChain *rpcperms.InterceptorChain, - grpcListeners []*ListenerWithSignal) (*chainreg.ChainControl, func(), - error) { + grpcListeners []*ListenerWithSignal) (*chainreg.PartialChainControl, + *btcwallet.Config, func(), error) { // Keep track of our various cleanup functions. We use a defer function // as well to not repeat ourselves with every return statement. @@ -245,7 +257,7 @@ func (d *DefaultWalletImpl) BuildChainControl(ctx context.Context, err := fmt.Errorf("unable to initialize neutrino "+ "backend: %v", err) d.logger.Error(err) - return nil, nil, err + return nil, nil, nil, err } cleanUpTasks = append(cleanUpTasks, neutrinoCleanUp) neutrinoCS = neutrinoBackend @@ -270,7 +282,7 @@ func (d *DefaultWalletImpl) BuildChainControl(ctx context.Context, d.pwService.SetMacaroonDB(dbs.MacaroonDB) walletExists, err := d.pwService.WalletExists() if err != nil { - return nil, nil, err + return nil, nil, nil, err } if !walletExists { @@ -287,9 +299,9 @@ func (d *DefaultWalletImpl) BuildChainControl(ctx context.Context, if d.cfg.WalletUnlockPasswordFile != "" && !walletExists && !d.cfg.WalletUnlockAllowCreate { - return nil, nil, fmt.Errorf("wallet unlock password file was " + - "specified but wallet does not exist; initialize the " + - "wallet before using auto unlocking") + return nil, nil, nil, fmt.Errorf("wallet unlock password file " + + "was specified but wallet does not exist; initialize " + + "the wallet before using auto unlocking") } // What wallet mode are we running in? We've already made sure the no @@ -306,8 +318,8 @@ func (d *DefaultWalletImpl) BuildChainControl(ctx context.Context, "password provided in file") pwBytes, err := ioutil.ReadFile(d.cfg.WalletUnlockPasswordFile) if err != nil { - return nil, nil, fmt.Errorf("error reading password "+ - "from file %s: %v", + return nil, nil, nil, fmt.Errorf("error reading "+ + "password from file %s: %v", d.cfg.WalletUnlockPasswordFile, err) } @@ -322,8 +334,8 @@ func (d *DefaultWalletImpl) BuildChainControl(ctx context.Context, pwBytes, 0, ) if err != nil { - return nil, nil, fmt.Errorf("error unlocking wallet "+ - "with password from file: %v", err) + return nil, nil, nil, fmt.Errorf("error unlocking "+ + "wallet with password from file: %v", err) } cleanUpTasks = append(cleanUpTasks, func() { @@ -343,7 +355,7 @@ func (d *DefaultWalletImpl) BuildChainControl(ctx context.Context, // over RPC. default: if err := d.interceptor.Notifier.NotifyReady(false); err != nil { - return nil, nil, err + return nil, nil, nil, err } params, err := waitForWalletPassword( @@ -354,7 +366,7 @@ func (d *DefaultWalletImpl) BuildChainControl(ctx context.Context, err := fmt.Errorf("unable to set up wallet password "+ "listeners: %v", err) d.logger.Error(err) - return nil, nil, err + return nil, nil, nil, err } walletInitParams = *params @@ -386,7 +398,7 @@ func (d *DefaultWalletImpl) BuildChainControl(ctx context.Context, err := fmt.Errorf("unable to set up macaroon "+ "authentication: %v", err) d.logger.Error(err) - return nil, nil, err + return nil, nil, nil, err } cleanUpTasks = append(cleanUpTasks, func() { if err := macaroonService.Close(); err != nil { @@ -402,7 +414,7 @@ func (d *DefaultWalletImpl) BuildChainControl(ctx context.Context, if err != nil && err != macaroons.ErrAlreadyUnlocked { err := fmt.Errorf("unable to unlock macaroons: %v", err) d.logger.Error(err) - return nil, nil, err + return nil, nil, nil, err } // In case we actually needed to unlock the wallet, we now need @@ -415,7 +427,7 @@ func (d *DefaultWalletImpl) BuildChainControl(ctx context.Context, ctx, macaroonService, adminPermissions(), ) if err != nil { - return nil, nil, err + return nil, nil, nil, err } // The channel is buffered by one element so writing @@ -446,7 +458,7 @@ func (d *DefaultWalletImpl) BuildChainControl(ctx context.Context, err := fmt.Errorf("unable to create macaroons "+ "%v", err) d.logger.Error(err) - return nil, nil, err + return nil, nil, nil, err } } @@ -538,7 +550,7 @@ func (d *DefaultWalletImpl) BuildChainControl(ctx context.Context, err := fmt.Errorf("unable to create partial chain control: %v", err) d.logger.Error(err) - return nil, nil, err + return nil, nil, nil, err } walletConfig := &btcwallet.Config{ @@ -562,23 +574,60 @@ func (d *DefaultWalletImpl) BuildChainControl(ctx context.Context, walletConfig.CoinSelectionStrategy = wallet.CoinSelectionRandom default: - return nil, nil, fmt.Errorf("unknown coin selection strategy "+ - "%v", d.cfg.CoinSelectionStrategy) + return nil, nil, nil, fmt.Errorf("unknown coin selection "+ + "strategy %v", d.cfg.CoinSelectionStrategy) + } + + earlyExit = false + return partialChainControl, walletConfig, cleanUp, nil +} + +// BuildChainControl is responsible for creating a fully populated chain +// control instance from a wallet. +// +// NOTE: This is part of the ChainControlBuilder interface. +func (d *DefaultWalletImpl) BuildChainControl( + partialChainControl *chainreg.PartialChainControl, + walletConfig *btcwallet.Config) (*chainreg.ChainControl, func(), error) { + + walletController, err := btcwallet.New( + *walletConfig, partialChainControl.Cfg.BlockCache, + ) + if err != nil { + fmt.Printf("unable to create wallet controller: %v\n", err) + d.logger.Error(err) + return nil, nil, err + } + + keyRing := keychain.NewBtcWalletKeyRing( + walletController.InternalWallet(), walletConfig.CoinType, + ) + + // Create, and start the lnwallet, which handles the core payment + // channel logic, and exposes control via proxy state machines. + lnWalletConfig := lnwallet.Config{ + Database: partialChainControl.Cfg.ChanStateDB, + Notifier: partialChainControl.ChainNotifier, + WalletController: walletController, + Signer: walletController, + FeeEstimator: partialChainControl.FeeEstimator, + SecretKeyRing: keyRing, + ChainIO: walletController, + DefaultConstraints: partialChainControl.ChannelConstraints, + NetParams: *walletConfig.NetParams, } // We've created the wallet configuration now, so we can finish // initializing the main chain control. - activeChainControl, ccCleanup, err := chainreg.NewChainControl( - walletConfig, partialChainControl, + activeChainControl, cleanUp, err := chainreg.NewChainControl( + lnWalletConfig, walletController, partialChainControl, ) - cleanUpTasks = append(cleanUpTasks, ccCleanup) if err != nil { err := fmt.Errorf("unable to create chain control: %v", err) d.logger.Error(err) return nil, nil, err } - earlyExit = false return activeChainControl, cleanUp, nil } diff --git a/lnd.go b/lnd.go index 6ac66e3ac..83a23c846 100644 --- a/lnd.go +++ b/lnd.go @@ -361,9 +361,18 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, defer cleanUp() - activeChainControl, cleanUp, err := implCfg.BuildChainControl( + partialChainControl, walletConfig, cleanUp, err := implCfg.BuildWalletConfig( ctx, dbs, interceptorChain, grpcListeners, ) + if err != nil { + return fmt.Errorf("error creating wallet config: %v", err) + } + + defer cleanUp() + + activeChainControl, cleanUp, err := implCfg.BuildChainControl( + partialChainControl, walletConfig, + ) if err != nil { return fmt.Errorf("error loading chain control: %v", err) } From 917cf4e99b148b6d8314570e9d03d3535b2bd647 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 14 Oct 2021 15:42:48 +0200 Subject: [PATCH 04/14] signrpc: add compact_sig flag to SignMessage --- lnrpc/signrpc/signer.pb.go | 129 ++++++++++++++++-------------- lnrpc/signrpc/signer.proto | 6 ++ lnrpc/signrpc/signer.swagger.json | 4 + lnrpc/signrpc/signer_server.go | 17 ++++ 4 files changed, 98 insertions(+), 58 deletions(-) diff --git a/lnrpc/signrpc/signer.pb.go b/lnrpc/signrpc/signer.pb.go index 9896191b1..f2096c949 100644 --- a/lnrpc/signrpc/signer.pb.go +++ b/lnrpc/signrpc/signer.pb.go @@ -550,6 +550,10 @@ type SignMessageReq struct { KeyLoc *KeyLocator `protobuf:"bytes,2,opt,name=key_loc,json=keyLoc,proto3" json:"key_loc,omitempty"` // Double-SHA256 hash instead of just the default single round. DoubleHash bool `protobuf:"varint,3,opt,name=double_hash,json=doubleHash,proto3" json:"double_hash,omitempty"` + // + //Use the compact (pubkey recoverable) format instead of the raw lnwire + //format. + CompactSig bool `protobuf:"varint,4,opt,name=compact_sig,json=compactSig,proto3" json:"compact_sig,omitempty"` } func (x *SignMessageReq) Reset() { @@ -605,6 +609,13 @@ func (x *SignMessageReq) GetDoubleHash() bool { return false } +func (x *SignMessageReq) GetCompactSig() bool { + if x != nil { + return x.CompactSig + } + return false +} + type SignMessageResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -948,64 +959,66 @@ var file_signrpc_signer_proto_rawDesc = []byte{ 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, - 0x22, 0x71, 0x0a, 0x0e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, - 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x03, 0x6d, 0x73, 0x67, 0x12, 0x2c, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4c, - 0x6f, 0x63, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x68, 0x61, 0x73, - 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x48, - 0x61, 0x73, 0x68, 0x22, 0x2f, 0x0a, 0x0f, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x22, 0x5a, 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, - 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, - 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, - 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, - 0x22, 0x29, 0x0a, 0x11, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x22, 0xa2, 0x01, 0x0a, 0x10, - 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x29, 0x0a, 0x10, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x70, 0x75, - 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x65, 0x70, 0x68, 0x65, - 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x07, 0x6b, - 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, - 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, - 0x72, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x12, 0x31, 0x0a, - 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, - 0x22, 0x32, 0x0a, 0x11, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x5f, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x68, 0x61, 0x72, 0x65, - 0x64, 0x4b, 0x65, 0x79, 0x32, 0xd4, 0x02, 0x0a, 0x06, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x12, - 0x34, 0x0a, 0x0d, 0x53, 0x69, 0x67, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x61, 0x77, - 0x12, 0x10, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, - 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, - 0x49, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x10, 0x2e, 0x73, 0x69, - 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, - 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x17, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x1a, - 0x18, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x46, 0x0a, 0x0d, 0x56, 0x65, 0x72, - 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x2e, 0x73, 0x69, 0x67, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x12, 0x48, 0x0a, 0x0f, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, - 0x64, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, - 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1a, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, - 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2f, 0x5a, 0x2d, 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, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x22, 0x92, 0x01, 0x0a, 0x0e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x2c, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x06, 0x6b, 0x65, 0x79, + 0x4c, 0x6f, 0x63, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x68, 0x61, + 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, + 0x48, 0x61, 0x73, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x5f, + 0x73, 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x61, + 0x63, 0x74, 0x53, 0x69, 0x67, 0x22, 0x2f, 0x0a, 0x0f, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, + 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x5a, 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, + 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x1c, 0x0a, 0x09, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, + 0x62, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, + 0x65, 0x79, 0x22, 0x29, 0x0a, 0x11, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x22, 0xa2, 0x01, + 0x0a, 0x10, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x5f, + 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x65, 0x70, + 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, + 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, + 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, + 0x74, 0x6f, 0x72, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x12, + 0x31, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x44, 0x65, + 0x73, 0x63, 0x22, 0x32, 0x0a, 0x11, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x68, 0x61, 0x72, 0x65, + 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x68, 0x61, + 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x32, 0xd4, 0x02, 0x0a, 0x06, 0x53, 0x69, 0x67, 0x6e, 0x65, + 0x72, 0x12, 0x34, 0x0a, 0x0d, 0x53, 0x69, 0x67, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, + 0x61, 0x77, 0x12, 0x10, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, + 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x70, 0x75, + 0x74, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x10, 0x2e, + 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x1a, + 0x18, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x53, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, 0x0b, 0x53, 0x69, 0x67, + 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x17, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x71, 0x1a, 0x18, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x46, 0x0a, 0x0d, 0x56, + 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x2e, 0x73, + 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x12, 0x48, 0x0a, 0x0f, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x53, 0x68, 0x61, + 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1a, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x61, 0x72, + 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2f, 0x5a, + 0x2d, 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, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/lnrpc/signrpc/signer.proto b/lnrpc/signrpc/signer.proto index 8693de339..74c38f7e3 100644 --- a/lnrpc/signrpc/signer.proto +++ b/lnrpc/signrpc/signer.proto @@ -193,6 +193,12 @@ message SignMessageReq { // Double-SHA256 hash instead of just the default single round. bool double_hash = 3; + + /* + Use the compact (pubkey recoverable) format instead of the raw lnwire + format. + */ + bool compact_sig = 4; } message SignMessageResp { /* diff --git a/lnrpc/signrpc/signer.swagger.json b/lnrpc/signrpc/signer.swagger.json index cb6c2a887..45db7ee8e 100644 --- a/lnrpc/signrpc/signer.swagger.json +++ b/lnrpc/signrpc/signer.swagger.json @@ -358,6 +358,10 @@ "double_hash": { "type": "boolean", "description": "Double-SHA256 hash instead of just the default single round." + }, + "compact_sig": { + "type": "boolean", + "description": "Use the compact (pubkey recoverable) format instead of the raw lnwire\nformat." } } }, diff --git a/lnrpc/signrpc/signer_server.go b/lnrpc/signrpc/signer_server.go index 10a930ebe..51c2e55ed 100644 --- a/lnrpc/signrpc/signer_server.go +++ b/lnrpc/signrpc/signer_server.go @@ -462,6 +462,23 @@ func (s *Server) SignMessage(_ context.Context, Index: uint32(in.KeyLoc.KeyIndex), } + // To allow a watch-only wallet to forward the SignMessageCompact to an + // endpoint that doesn't add the message prefix, we allow this RPC to + // also return the compact signature format instead of adding a flag to + // the lnrpc.SignMessage call that removes the message prefix. + if in.CompactSig { + sigBytes, err := s.cfg.KeyRing.SignMessageCompact( + keyLocator, in.Msg, in.DoubleHash, + ) + if err != nil { + return nil, fmt.Errorf("can't sign the hash: %v", err) + } + + return &SignMessageResp{ + Signature: sigBytes, + }, nil + } + // Create the raw ECDSA signature first and convert it to the final wire // format after. sig, err := s.cfg.KeyRing.SignMessage( From 9cae7ad3c27be028bf5acdd61103e33d021c8672 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 14 Oct 2021 15:42:49 +0200 Subject: [PATCH 05/14] multi: add remote RPC signing wallet implementation --- config.go | 22 +- config_builder.go | 91 ++++++- lncfg/remotesigner.go | 9 + lnwallet/rpcwallet/rpcwallet.go | 439 ++++++++++++++++++++++++++++++++ sample-lnd.conf | 17 ++ 5 files changed, 576 insertions(+), 2 deletions(-) create mode 100644 lncfg/remotesigner.go create mode 100644 lnwallet/rpcwallet/rpcwallet.go diff --git a/config.go b/config.go index 0fd4fd3e2..b4f14abfc 100644 --- a/config.go +++ b/config.go @@ -385,6 +385,8 @@ type Config struct { RPCMiddleware *lncfg.RPCMiddleware `group:"rpcmiddleware" namespace:"rpcmiddleware"` + RemoteSigner *lncfg.RemoteSigner `group:"remotesigner" namespace:"remotesigner"` + // LogWriter is the root logger that all of the daemon's subloggers are // hooked up to. LogWriter *build.RotatingLogWriter @@ -562,6 +564,7 @@ func DefaultConfig() Config { ChannelCommitInterval: defaultChannelCommitInterval, ChannelCommitBatchSize: defaultChannelCommitBatchSize, CoinSelectionStrategy: defaultCoinSelectionStrategy, + RemoteSigner: &lncfg.RemoteSigner{}, } } @@ -1541,7 +1544,24 @@ func (c *Config) graphDatabaseDir() string { func (c *Config) ImplementationConfig( interceptor signal.Interceptor) *ImplementationCfg { - defaultImpl := NewDefaultWalletImpl(c, ltndLog, interceptor) + // If we're using a remote signer, we still need the base wallet as a + // watch-only source of chain and address data. But we don't need any + // private key material in that btcwallet base wallet. + if c.RemoteSigner.Enable { + rpcImpl := NewRPCSignerWalletImpl(c, ltndLog, interceptor) + return &ImplementationCfg{ + GrpcRegistrar: rpcImpl, + RestRegistrar: rpcImpl, + ExternalValidator: rpcImpl, + DatabaseBuilder: NewDefaultDatabaseBuilder( + c, ltndLog, + ), + WalletConfigBuilder: rpcImpl, + ChainControlBuilder: rpcImpl, + } + } + + defaultImpl := NewDefaultWalletImpl(c, ltndLog, interceptor, false) return &ImplementationCfg{ GrpcRegistrar: defaultImpl, RestRegistrar: defaultImpl, diff --git a/config_builder.go b/config_builder.go index a70ece5a6..8635ee648 100644 --- a/config_builder.go +++ b/config_builder.go @@ -29,6 +29,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/btcwallet" + "github.com/lightningnetwork/lnd/lnwallet/rpcwallet" "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/rpcperms" "github.com/lightningnetwork/lnd/signal" @@ -141,17 +142,19 @@ type DefaultWalletImpl struct { logger btclog.Logger interceptor signal.Interceptor + watchOnly bool pwService *walletunlocker.UnlockerService } // NewDefaultWalletImpl creates a new default wallet implementation. func NewDefaultWalletImpl(cfg *Config, logger btclog.Logger, - interceptor signal.Interceptor) *DefaultWalletImpl { + interceptor signal.Interceptor, watchOnly bool) *DefaultWalletImpl { return &DefaultWalletImpl{ cfg: cfg, logger: logger, interceptor: interceptor, + watchOnly: watchOnly, pwService: createWalletUnlockerService(cfg), } } @@ -563,6 +566,7 @@ func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context, Wallet: walletInitParams.Wallet, LoaderOptions: []btcwallet.LoaderOption{dbs.WalletDB}, ChainSource: partialChainControl.ChainSource, + WatchOnly: d.watchOnly, } // Parse coin selection strategy. @@ -631,6 +635,91 @@ func (d *DefaultWalletImpl) BuildChainControl( return activeChainControl, cleanUp, nil } +// RPCSignerWalletImpl is a wallet implementation that uses a remote signer over +// an RPC interface. +type RPCSignerWalletImpl struct { + // DefaultWalletImpl is the embedded instance of the default + // implementation that the remote signer uses as its watch-only wallet + // for keeping track of addresses and UTXOs. + *DefaultWalletImpl +} + +// NewRPCSignerWalletImpl creates a new instance of the remote signing wallet +// implementation. +func NewRPCSignerWalletImpl(cfg *Config, logger btclog.Logger, + interceptor signal.Interceptor) *RPCSignerWalletImpl { + + return &RPCSignerWalletImpl{ + DefaultWalletImpl: &DefaultWalletImpl{ + cfg: cfg, + logger: logger, + interceptor: interceptor, + watchOnly: true, + pwService: createWalletUnlockerService(cfg), + }, + } +} + +// BuildChainControl is responsible for creating or unlocking and then fully +// initializing a wallet and returning it as part of a fully populated chain +// control instance. +// +// NOTE: This is part of the ChainControlBuilder interface. +func (d *RPCSignerWalletImpl) BuildChainControl( + partialChainControl *chainreg.PartialChainControl, + walletConfig *btcwallet.Config) (*chainreg.ChainControl, func(), error) { + + walletController, err := btcwallet.New( + *walletConfig, partialChainControl.Cfg.BlockCache, + ) + if err != nil { + fmt.Printf("unable to create wallet controller: %v\n", err) + d.logger.Error(err) + return nil, nil, err + } + + baseKeyRing := keychain.NewBtcWalletKeyRing( + walletController.InternalWallet(), walletConfig.CoinType, + ) + + rpcKeyRing, err := rpcwallet.NewRPCKeyRing( + baseKeyRing, d.DefaultWalletImpl.cfg.RemoteSigner, + rpcwallet.DefaultRPCTimeout, + ) + if err != nil { + fmt.Printf("unable to create RPC remote signing wallet %v", err) + d.logger.Error(err) + return nil, nil, err + } + + // Create, and start the lnwallet, which handles the core payment + // channel logic, and exposes control via proxy state machines. + lnWalletConfig := lnwallet.Config{ + Database: partialChainControl.Cfg.ChanStateDB, + Notifier: partialChainControl.ChainNotifier, + WalletController: walletController, + Signer: rpcKeyRing, + FeeEstimator: partialChainControl.FeeEstimator, + SecretKeyRing: rpcKeyRing, + ChainIO: walletController, + DefaultConstraints: partialChainControl.ChannelConstraints, + NetParams: *walletConfig.NetParams, + } + + // We've created the wallet configuration now, so we can finish + // initializing the main chain control. + activeChainControl, cleanUp, err := chainreg.NewChainControl( + lnWalletConfig, rpcKeyRing, partialChainControl, + ) + if err != nil { + err := fmt.Errorf("unable to create chain control: %v", err) + d.logger.Error(err) + return nil, nil, err + } + + return activeChainControl, cleanUp, nil +} + // DatabaseInstances is a struct that holds all instances to the actual // databases that are used in lnd. type DatabaseInstances struct { diff --git a/lncfg/remotesigner.go b/lncfg/remotesigner.go new file mode 100644 index 000000000..90d1988cf --- /dev/null +++ b/lncfg/remotesigner.go @@ -0,0 +1,9 @@ +package lncfg + +// RemoteSigner holds the configuration options for a remote RPC signer. +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."` + 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"` + TLSCertPath string `long:"tlscertpath" description:"The TLS certificate to use for establishing the remote signer's identity"` +} diff --git a/lnwallet/rpcwallet/rpcwallet.go b/lnwallet/rpcwallet/rpcwallet.go new file mode 100644 index 000000000..9dec83c7b --- /dev/null +++ b/lnwallet/rpcwallet/rpcwallet.go @@ -0,0 +1,439 @@ +package rpcwallet + +import ( + "bytes" + "context" + "crypto/x509" + "errors" + "fmt" + "io/ioutil" + "time" + + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lncfg" + "github.com/lightningnetwork/lnd/lnrpc/signrpc" + "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/macaroons" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "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 ( + // ErrRemoteSigningPrivateKeyNotAvailable is the error that is returned + // if an operation is requested from the RPC wallet that is not + // supported in remote signing mode. + ErrRemoteSigningPrivateKeyNotAvailable = errors.New("deriving " + + "private key is not supported by RPC based key ring") +) + +// RPCKeyRing is an implementation of the SecretKeyRing interface that uses a +// local watch-only wallet for keeping track of addresses and transactions but +// delegates any signing or ECDH operations to a remote node through RPC. +type RPCKeyRing struct { + watchOnlyWallet keychain.SecretKeyRing + + rpcTimeout time.Duration + + signerClient signrpc.SignerClient + walletClient walletrpc.WalletKitClient +} + +var _ keychain.SecretKeyRing = (*RPCKeyRing)(nil) +var _ input.Signer = (*RPCKeyRing)(nil) +var _ keychain.MessageSignerRing = (*RPCKeyRing)(nil) + +// NewRPCKeyRing creates a new remote signing secret key ring that uses the +// given watch-only base wallet to keep track of addresses and transactions but +// delegates any signing or ECDH operations to the remove signer through RPC. +func NewRPCKeyRing(watchOnlyWallet keychain.SecretKeyRing, + remoteSigner *lncfg.RemoteSigner, + rpcTimeout time.Duration) (*RPCKeyRing, error) { + + rpcConn, err := connectRPC( + remoteSigner.RPCHost, remoteSigner.TLSCertPath, + remoteSigner.MacaroonPath, + ) + if err != nil { + return nil, fmt.Errorf("error connecting to the remote "+ + "signing node through RPC: %v", err) + } + + return &RPCKeyRing{ + watchOnlyWallet: watchOnlyWallet, + rpcTimeout: rpcTimeout, + signerClient: signrpc.NewSignerClient(rpcConn), + walletClient: walletrpc.NewWalletKitClient(rpcConn), + }, nil +} + +// DeriveNextKey attempts to derive the *next* key within the key family +// (account in BIP43) specified. This method should return the next external +// child within this branch. +// +// NOTE: This method is part of the keychain.KeyRing interface. +func (r *RPCKeyRing) DeriveNextKey( + keyFam keychain.KeyFamily) (keychain.KeyDescriptor, error) { + + ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) + 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.watchOnlyWallet.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 +// KeyLocator. This may be used in several recovery scenarios, or when manually +// rotating something like our current default node key. +// +// NOTE: This method is part of the keychain.KeyRing interface. +func (r *RPCKeyRing) DeriveKey( + keyLoc keychain.KeyLocator) (keychain.KeyDescriptor, error) { + + ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) + 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.watchOnlyWallet.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 +// target key descriptor and remote public key. The output returned will be the +// sha256 of the resulting shared point serialized in compressed format. If k is +// our private key, and P is the public key, we perform the following operation: +// +// sx := k*P +// s := sha256(sx.SerializeCompressed()) +// +// NOTE: This method is part of the keychain.ECDHRing interface. +func (r *RPCKeyRing) ECDH(keyDesc keychain.KeyDescriptor, + pubKey *btcec.PublicKey) ([32]byte, error) { + + ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) + defer cancel() + + key := [32]byte{} + req := &signrpc.SharedKeyRequest{ + EphemeralPubkey: pubKey.SerializeCompressed(), + KeyDesc: &signrpc.KeyDescriptor{ + KeyLoc: &signrpc.KeyLocator{ + KeyFamily: int32(keyDesc.Family), + KeyIndex: int32(keyDesc.Index), + }, + }, + } + + if keyDesc.Index == 0 && keyDesc.PubKey != nil { + req.KeyDesc.RawKeyBytes = keyDesc.PubKey.SerializeCompressed() + } + + resp, err := r.signerClient.DeriveSharedKey(ctxt, req) + if err != nil { + return key, err + } + + copy(key[:], resp.SharedKey) + return key, nil +} + +// SignMessage attempts to sign a target message with the private key described +// in the key locator. If the target private key is unable to be found, then an +// error will be returned. The actual digest signed is the single or double +// SHA-256 of the passed message. +// +// NOTE: This method is part of the keychain.MessageSignerRing interface. +func (r *RPCKeyRing) SignMessage(keyLoc keychain.KeyLocator, + msg []byte, doubleHash bool) (*btcec.Signature, error) { + + ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) + defer cancel() + + resp, err := r.signerClient.SignMessage(ctxt, &signrpc.SignMessageReq{ + Msg: msg, + KeyLoc: &signrpc.KeyLocator{ + KeyFamily: int32(keyLoc.Family), + KeyIndex: int32(keyLoc.Index), + }, + DoubleHash: doubleHash, + }) + if err != nil { + return nil, err + } + + wireSig, err := lnwire.NewSigFromRawSignature(resp.Signature) + if err != nil { + return nil, fmt.Errorf("error parsing raw signature: %v", err) + } + return wireSig.ToSignature() +} + +// SignMessageCompact signs the given message, single or double SHA256 hashing +// it first, with the private key described in the key locator and returns the +// signature in the compact, public key recoverable format. +// +// NOTE: This method is part of the keychain.MessageSignerRing interface. +func (r *RPCKeyRing) SignMessageCompact(keyLoc keychain.KeyLocator, + msg []byte, doubleHash bool) ([]byte, error) { + + if keyLoc.Family != keychain.KeyFamilyNodeKey { + return nil, fmt.Errorf("error compact signing with key "+ + "locator %v, can only sign with node key", keyLoc) + } + + ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) + defer cancel() + + resp, err := r.signerClient.SignMessage(ctxt, &signrpc.SignMessageReq{ + Msg: msg, + KeyLoc: &signrpc.KeyLocator{ + KeyFamily: int32(keyLoc.Family), + KeyIndex: int32(keyLoc.Index), + }, + DoubleHash: doubleHash, + CompactSig: true, + }) + if err != nil { + return nil, err + } + + // The signature in the response is zbase32 encoded, so we need to + // decode it before returning. + return resp.Signature, nil +} + +// DerivePrivKey attempts to derive the private key that corresponds to the +// passed key descriptor. If the public key is set, then this method will +// perform an in-order scan over the key set, with a max of MaxKeyRangeScan +// keys. In order for this to work, the caller MUST set the KeyFamily within the +// partially populated KeyLocator. +// +// NOTE: This method is part of the keychain.SecretKeyRing interface. +func (r *RPCKeyRing) DerivePrivKey(_ keychain.KeyDescriptor) (*btcec.PrivateKey, + error) { + + // This operation is not supported with remote signing. There should be + // no need for invoking this method unless a channel backup (SCB) file + // for pre-0.13.0 channels are attempted to be restored. In that case + // it is recommended to restore the channels using a node with the full + // seed available. + return nil, ErrRemoteSigningPrivateKeyNotAvailable +} + +// SignOutputRaw generates a signature for the passed transaction +// according to the data within the passed SignDescriptor. +// +// NOTE: The resulting signature should be void of a sighash byte. +// +// NOTE: This method is part of the input.Signer interface. +func (r *RPCKeyRing) SignOutputRaw(tx *wire.MsgTx, + signDesc *input.SignDescriptor) (input.Signature, error) { + + ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) + defer cancel() + + rpcSignReq, err := toRPCSignReq(tx, signDesc) + 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 +// transaction with the signature as defined within the passed +// SignDescriptor. This method should be capable of generating the +// proper input script for both regular p2wkh output and p2wkh outputs +// nested within a regular p2sh output. +// +// NOTE: This method will ignore any tweak parameters set within the +// passed SignDescriptor as it assumes a set of typical script +// templates (p2wkh, np2wkh, etc). +// +// NOTE: This method is part of the input.Signer interface. +func (r *RPCKeyRing) ComputeInputScript(tx *wire.MsgTx, + signDesc *input.SignDescriptor) (*input.Script, error) { + + ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) + 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 +} + +// 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 + if err := tx.Serialize(&buf); err != nil { + return nil, err + } + + rpcSignDesc := &signrpc.SignDescriptor{ + KeyDesc: &signrpc.KeyDescriptor{ + KeyLoc: &signrpc.KeyLocator{ + KeyFamily: int32(signDesc.KeyDesc.Family), + KeyIndex: int32(signDesc.KeyDesc.Index), + }, + }, + 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 { + rpcSignDesc.KeyDesc.RawKeyBytes = + signDesc.KeyDesc.PubKey.SerializeCompressed() + } + if signDesc.DoubleTweak != nil { + rpcSignDesc.DoubleTweak = signDesc.DoubleTweak.Serialize() + } + + return &signrpc.SignReq{ + RawTxBytes: buf.Bytes(), + SignDescs: []*signrpc.SignDescriptor{rpcSignDesc}, + }, nil +} + +// connectRPC tries to establish an RPC connection to the given host:port with +// the supplied certificate and macaroon. +func connectRPC(hostPort, tlsCertPath, macaroonPath string) (*grpc.ClientConn, + error) { + + certBytes, err := ioutil.ReadFile(tlsCertPath) + if err != nil { + return nil, fmt.Errorf("error reading TLS cert file %v: %v", + tlsCertPath, err) + } + + cp := x509.NewCertPool() + if !cp.AppendCertsFromPEM(certBytes) { + return nil, fmt.Errorf("credentials: failed to append " + + "certificate") + } + + macBytes, err := ioutil.ReadFile(macaroonPath) + if err != nil { + return nil, fmt.Errorf("error reading macaroon file %v: %v", + macaroonPath, err) + } + mac := &macaroon.Macaroon{} + if err := mac.UnmarshalBinary(macBytes); err != nil { + return nil, fmt.Errorf("error decoding macaroon: %v", err) + } + + macCred, err := macaroons.NewMacaroonCredential(mac) + if err != nil { + return nil, fmt.Errorf("error creating creds: %v", err) + } + + opts := []grpc.DialOption{ + grpc.WithTransportCredentials(credentials.NewClientTLSFromCert( + cp, "", + )), + grpc.WithPerRPCCredentials(macCred), + } + conn, err := grpc.Dial(hostPort, opts...) + if err != nil { + return nil, fmt.Errorf("unable to connect to RPC server: %v", + err) + } + + return conn, nil +} diff --git a/sample-lnd.conf b/sample-lnd.conf index ad0ea1dc7..74ddc1f31 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -1178,6 +1178,23 @@ litecoin.node=ltcd ; rpcmiddleware.addmandatory=my-example-middleware ; rpcmiddleware.addmandatory=other-mandatory-middleware +[remotesigner] + +; 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. +; remotesigner.enable=true + +; The remote signer's RPC host:port. +; remotesigner.rpchost=remote.signer.lnd.host:10009 + +; The macaroon to use for authenticating with the remote signer. +; remotesigner.macaroonpath=/path/to/remote/signer/admin.macaroon + +; The TLS certificate to use for establishing the remote signer's identity. +; remotesigner.tlscertpath=/path/to/remote/signer/tls.cert + [bolt] ; If true, prevents the database from syncing its freelist to disk. From 19db382e247cd8bf99700db62f36dc941e329ded Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 14 Oct 2021 15:42:51 +0200 Subject: [PATCH 06/14] multi: forward address and import calls over RPC In order to support the full range of on-chain functionality, including importing watch-only accounts in the watch-only instance, we need to forward some calls like creating new addresses or importing accounts to the remote signing instance. --- config_builder.go | 5 +- lnrpc/walletrpc/walletkit.pb.go | 882 +++++++++++++------------ lnrpc/walletrpc/walletkit.proto | 10 + lnrpc/walletrpc/walletkit.swagger.json | 8 + lnrpc/walletrpc/walletkit_server.go | 14 +- lnwallet/rpcwallet/rpcwallet.go | 225 ++++++- 6 files changed, 703 insertions(+), 441 deletions(-) diff --git a/config_builder.go b/config_builder.go index 8635ee648..2aa8f9c49 100644 --- a/config_builder.go +++ b/config_builder.go @@ -683,7 +683,8 @@ func (d *RPCSignerWalletImpl) BuildChainControl( ) rpcKeyRing, err := rpcwallet.NewRPCKeyRing( - baseKeyRing, d.DefaultWalletImpl.cfg.RemoteSigner, + baseKeyRing, walletController, + d.DefaultWalletImpl.cfg.RemoteSigner, rpcwallet.DefaultRPCTimeout, ) if err != nil { @@ -697,7 +698,7 @@ func (d *RPCSignerWalletImpl) BuildChainControl( lnWalletConfig := lnwallet.Config{ Database: partialChainControl.Cfg.ChanStateDB, Notifier: partialChainControl.ChainNotifier, - WalletController: walletController, + WalletController: rpcKeyRing, Signer: rpcKeyRing, FeeEstimator: partialChainControl.FeeEstimator, SecretKeyRing: rpcKeyRing, diff --git a/lnrpc/walletrpc/walletkit.pb.go b/lnrpc/walletrpc/walletkit.pb.go index 746b61fc7..0f17d62e2 100644 --- a/lnrpc/walletrpc/walletkit.pb.go +++ b/lnrpc/walletrpc/walletkit.pb.go @@ -601,6 +601,12 @@ type AddrRequest struct { //The name of the account to retrieve the next address of. If empty, the //default wallet account is used. Account string `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"` + // + //The type of address to derive. + Type AddressType `protobuf:"varint,2,opt,name=type,proto3,enum=walletrpc.AddressType" json:"type,omitempty"` + // + //Whether a change address should be derived. + Change bool `protobuf:"varint,3,opt,name=change,proto3" json:"change,omitempty"` } func (x *AddrRequest) Reset() { @@ -642,6 +648,20 @@ func (x *AddrRequest) GetAccount() string { return "" } +func (x *AddrRequest) GetType() AddressType { + if x != nil { + return x.Type + } + return AddressType_UNKNOWN +} + +func (x *AddrRequest) GetChange() bool { + if x != nil { + return x.Change + } + return false +} + type AddrResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2819,378 +2839,383 @@ var file_walletrpc_walletkit_proto_rawDesc = []byte{ 0x72, 0x5f, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x09, 0x6b, 0x65, 0x79, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x22, 0x27, 0x0a, 0x0b, + 0x05, 0x52, 0x09, 0x6b, 0x65, 0x79, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x22, 0x6b, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x22, 0x0a, 0x0c, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x22, 0xe2, 0x02, 0x0a, 0x07, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, - 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x75, 0x62, 0x6c, 0x69, - 0x63, 0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, - 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x46, - 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x65, - 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, - 0x61, 0x74, 0x68, 0x12, 0x2c, 0x0a, 0x12, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, - 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x10, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, - 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x1d, 0x0a, 0x0a, 0x77, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x09, 0x77, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x64, - 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x54, 0x79, 0x70, 0x65, 0x22, 0x46, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x08, - 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0xe4, 0x01, 0x0a, - 0x14, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74, - 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, - 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x6d, 0x61, 0x73, - 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, - 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6d, 0x61, 0x73, 0x74, 0x65, - 0x72, 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, - 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, - 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79, - 0x52, 0x75, 0x6e, 0x22, 0xaf, 0x01, 0x0a, 0x15, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, - 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x16, 0x64, - 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, - 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x64, 0x72, 0x79, - 0x52, 0x75, 0x6e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x73, - 0x12, 0x33, 0x0a, 0x16, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x13, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x41, 0x64, 0x64, 0x72, 0x73, 0x22, 0x72, 0x0a, 0x16, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x39, - 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x22, 0x19, 0x0a, 0x17, 0x49, 0x6d, 0x70, - 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x0a, 0x0b, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x78, 0x5f, 0x68, 0x65, 0x78, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x05, 0x74, 0x78, 0x48, 0x65, 0x78, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, - 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, - 0x22, 0x36, 0x0a, 0x0f, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x65, - 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, - 0x69, 0x73, 0x68, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xbc, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x6e, - 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1c, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x28, 0x0a, - 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, - 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x52, 0x07, - 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1b, 0x0a, - 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, - 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x22, 0x2c, 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64, 0x4f, - 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, - 0x0a, 0x06, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, - 0x72, 0x61, 0x77, 0x54, 0x78, 0x22, 0x35, 0x0a, 0x12, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, - 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x63, - 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0x33, 0x0a, 0x13, - 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, - 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, - 0x77, 0x22, 0xfc, 0x03, 0x0a, 0x0c, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, - 0x65, 0x70, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 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, - 0x39, 0x0a, 0x0c, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x57, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x77, - 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, - 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x61, 0x74, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, - 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, - 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, - 0x2d, 0x0a, 0x12, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x61, 0x74, 0x74, - 0x65, 0x6d, 0x70, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x62, 0x72, 0x6f, - 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x12, 0x32, - 0x0a, 0x15, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, - 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x6e, - 0x65, 0x78, 0x74, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x48, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, - 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x13, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, - 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x37, 0x0a, 0x16, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x65, 0x64, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x65, 0x64, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, - 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, - 0x79, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, - 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x0b, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x53, - 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, - 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, - 0x22, 0x16, 0x0a, 0x14, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x57, 0x0a, 0x15, 0x50, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x77, 0x65, - 0x65, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, - 0x65, 0x70, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, - 0x73, 0x22, 0xbe, 0x01, 0x0a, 0x0e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 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, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, - 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, - 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, - 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x22, - 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, - 0x74, 0x65, 0x22, 0x11, 0x0a, 0x0f, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2d, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, - 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, - 0x72, 0x62, 0x6f, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x76, 0x65, 0x72, - 0x62, 0x6f, 0x73, 0x65, 0x22, 0x80, 0x02, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, - 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x13, 0x74, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, - 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, - 0x69, 0x6c, 0x73, 0x48, 0x00, 0x52, 0x12, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x57, 0x0a, 0x0f, 0x74, 0x72, 0x61, - 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x2c, 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, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x73, - 0x48, 0x00, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, - 0x64, 0x73, 0x1a, 0x39, 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x49, 0x44, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x74, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x42, 0x08, 0x0a, - 0x06, 0x73, 0x77, 0x65, 0x65, 0x70, 0x73, 0x22, 0x61, 0x0a, 0x17, 0x4c, 0x61, 0x62, 0x65, 0x6c, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1c, 0x0a, 0x09, - 0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x09, 0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x22, 0x1a, 0x0a, 0x18, 0x4c, 0x61, - 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x93, 0x02, 0x0a, 0x0f, 0x46, 0x75, 0x6e, 0x64, 0x50, - 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x04, 0x70, 0x73, - 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x04, 0x70, 0x73, 0x62, 0x74, - 0x12, 0x29, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12, 0x21, 0x0a, 0x0b, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, - 0x48, 0x01, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24, - 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, - 0x62, 0x79, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, - 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, - 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, 0x0a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x66, 0x65, 0x65, 0x73, 0x22, 0x9c, 0x01, 0x0a, - 0x10, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, - 0x62, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x6f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x11, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, - 0x65, 0x78, 0x12, 0x37, 0x0a, 0x0c, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x75, 0x74, 0x78, - 0x6f, 0x73, 0x18, 0x03, 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, 0x22, 0xaf, 0x01, 0x0a, 0x0a, - 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x06, 0x69, 0x6e, - 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x69, 0x6e, 0x70, - 0x75, 0x74, 0x73, 0x12, 0x3c, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x2e, 0x4f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, - 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x68, 0x0a, - 0x09, 0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, - 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 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, 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, 0x69, 0x6e, 0x61, 0x6c, - 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, - 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, - 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x59, 0x0a, 0x14, 0x46, 0x69, 0x6e, - 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x73, - 0x62, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x61, 0x77, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, - 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x61, 0x77, 0x46, 0x69, 0x6e, - 0x61, 0x6c, 0x54, 0x78, 0x22, 0x13, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, - 0x65, 0x73, 0x52, 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, 0x03, 0x2a, 0x99, 0x03, 0x0a, 0x0b, 0x57, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, - 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4d, - 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x5f, 0x4c, 0x4f, 0x43, - 0x4b, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, - 0x54, 0x5f, 0x4e, 0x4f, 0x5f, 0x44, 0x45, 0x4c, 0x41, 0x59, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, - 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, - 0x45, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, - 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, - 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, - 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x05, 0x12, 0x25, 0x0a, 0x21, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, - 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x5f, 0x53, - 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x06, 0x12, 0x26, 0x0a, - 0x22, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x53, - 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, - 0x56, 0x45, 0x4c, 0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, - 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, - 0x45, 0x4f, 0x55, 0x54, 0x10, 0x08, 0x12, 0x20, 0x0a, 0x1c, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, - 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x53, - 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x09, 0x12, 0x1c, 0x0a, 0x18, 0x48, 0x54, 0x4c, 0x43, - 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x52, 0x45, - 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, - 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0b, 0x12, 0x1b, 0x0a, 0x17, - 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x4b, - 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0c, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, - 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x10, 0x0d, - 0x32, 0xb2, 0x0b, 0x0a, 0x09, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x4b, 0x69, 0x74, 0x12, 0x4c, - 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x2e, - 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, - 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, - 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, - 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x52, 0x65, - 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1f, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, - 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, - 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, - 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, - 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0d, 0x44, 0x65, 0x72, - 0x69, 0x76, 0x65, 0x4e, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, - 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x38, 0x0a, 0x09, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x4b, - 0x65, 0x79, 0x12, 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, - 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, - 0x3b, 0x0a, 0x08, 0x4e, 0x65, 0x78, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x16, 0x2e, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, - 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, - 0x0d, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, - 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, - 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x22, 0x0a, 0x0c, 0x41, 0x64, 0x64, + 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x22, 0xe2, 0x02, + 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, + 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74, 0x65, + 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x6d, 0x61, 0x73, 0x74, + 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, + 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x27, + 0x0a, 0x0f, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x74, + 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2c, 0x0a, 0x12, 0x65, 0x78, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x10, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x10, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x77, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x6f, 0x6e, 0x6c, + 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x77, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x6e, + 0x6c, 0x79, 0x22, 0x64, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, + 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x22, 0x46, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x2e, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, + 0x22, 0xe4, 0x01, 0x0a, 0x14, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2e, 0x0a, + 0x13, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, + 0x6e, 0x64, 0x65, 0x64, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a, + 0x16, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, + 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6d, + 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, + 0x69, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, + 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, + 0x0a, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x06, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x22, 0xaf, 0x01, 0x0a, 0x15, 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, + 0x65, 0x12, 0x2c, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x33, 0x0a, 0x16, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x13, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, + 0x64, 0x64, 0x72, 0x73, 0x12, 0x33, 0x0a, 0x16, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x5f, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x73, 0x22, 0x72, 0x0a, 0x16, 0x49, 0x6d, 0x70, + 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, + 0x65, 0x79, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, + 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x22, 0x19, 0x0a, + 0x17, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x0a, 0x0b, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x78, 0x5f, 0x68, 0x65, + 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x74, 0x78, 0x48, 0x65, 0x78, 0x12, 0x14, + 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x22, 0x36, 0x0a, 0x0f, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x73, 0x68, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xbc, 0x01, 0x0a, + 0x12, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, + 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, + 0x77, 0x12, 0x28, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x4f, + 0x75, 0x74, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, + 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, + 0x6d, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, + 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x22, 0x2c, 0x0a, 0x13, 0x53, + 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x05, 0x72, 0x61, 0x77, 0x54, 0x78, 0x22, 0x35, 0x0a, 0x12, 0x45, 0x73, 0x74, + 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x22, 0x33, 0x0a, 0x13, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70, + 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x61, 0x74, + 0x50, 0x65, 0x72, 0x4b, 0x77, 0x22, 0xfc, 0x03, 0x0a, 0x0c, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 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, 0x39, 0x0a, 0x0c, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, + 0x65, 0x52, 0x0b, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x09, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x61, 0x74, 0x12, 0x24, 0x0a, + 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, + 0x79, 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, + 0x5f, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x11, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, + 0x74, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x62, 0x72, 0x6f, 0x61, 0x64, + 0x63, 0x61, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x13, 0x6e, 0x65, 0x78, 0x74, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, + 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, + 0x43, 0x6f, 0x6e, 0x66, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x37, 0x0a, 0x16, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, + 0x62, 0x79, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, + 0x79, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, + 0x62, 0x79, 0x74, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, + 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, + 0x74, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x65, 0x64, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, + 0x6f, 0x72, 0x63, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, + 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x57, 0x0a, 0x15, 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, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x5f, 0x73, 0x77, 0x65, 0x65, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, + 0x77, 0x65, 0x65, 0x70, 0x73, 0x22, 0xbe, 0x01, 0x0a, 0x0e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 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, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, + 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, + 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, + 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, + 0x63, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, + 0x79, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, + 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x22, 0x11, 0x0a, 0x0f, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2d, 0x0a, 0x11, 0x4c, 0x69, 0x73, + 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, + 0x0a, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x22, 0x80, 0x02, 0x0a, 0x12, 0x4c, 0x69, 0x73, + 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x4c, 0x0a, 0x13, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x48, 0x00, 0x52, 0x12, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x57, 0x0a, + 0x0f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 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, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x44, 0x73, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x1a, 0x39, 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x73, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x77, 0x65, 0x65, 0x70, 0x73, 0x22, 0x61, 0x0a, 0x17, 0x4c, + 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x09, 0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x22, 0x1a, + 0x0a, 0x18, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x93, 0x02, 0x0a, 0x0f, 0x46, + 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, + 0x0a, 0x04, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x04, + 0x70, 0x73, 0x62, 0x74, 0x12, 0x29, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x15, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, + 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12, + 0x21, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0d, 0x48, 0x01, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, + 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, + 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x0b, 0x73, 0x61, 0x74, + 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, + 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x72, 0x6d, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, + 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, 0x0a, 0x0a, 0x08, + 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x66, 0x65, 0x65, 0x73, + 0x22, 0x9c, 0x01, 0x0a, 0x10, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, + 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, + 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x11, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x37, 0x0a, 0x0c, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, + 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x03, 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, 0x22, + 0xaf, 0x01, 0x0a, 0x0a, 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x27, + 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, + 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x3c, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x2e, + 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0x68, 0x0a, 0x09, 0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, + 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 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, + 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, + 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, + 0x73, 0x62, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x59, 0x0a, + 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, + 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, + 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x61, 0x77, 0x5f, 0x66, 0x69, + 0x6e, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x61, + 0x77, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x54, 0x78, 0x22, 0x13, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, + 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 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, 0x03, 0x2a, 0x99, 0x03, 0x0a, 0x0b, 0x57, 0x69, 0x74, + 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, + 0x4f, 0x57, 0x4e, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x18, 0x0a, + 0x14, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, + 0x5f, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4d, 0x4d, 0x49, + 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x5f, 0x44, 0x45, 0x4c, 0x41, 0x59, 0x10, 0x02, + 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x52, + 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x48, 0x54, 0x4c, 0x43, 0x5f, + 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x04, + 0x12, 0x18, 0x0a, 0x14, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, + 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x05, 0x12, 0x25, 0x0a, 0x21, 0x48, 0x54, + 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, + 0x55, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, + 0x06, 0x12, 0x26, 0x0a, 0x22, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, + 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, + 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x48, 0x54, 0x4c, + 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, + 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x08, 0x12, 0x20, 0x0a, 0x1c, 0x48, 0x54, + 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, + 0x54, 0x45, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x09, 0x12, 0x1c, 0x0a, 0x18, + 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, + 0x4c, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x57, 0x49, + 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0b, + 0x12, 0x1b, 0x0a, 0x17, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, + 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0c, 0x12, 0x15, 0x0a, + 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x4e, 0x43, 0x48, + 0x4f, 0x52, 0x10, 0x0d, 0x32, 0xb2, 0x0b, 0x0a, 0x09, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x4b, + 0x69, 0x74, 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, + 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, + 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, + 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, + 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, + 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, + 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, 0x65, + 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, + 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, + 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, + 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, + 0x0d, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x4e, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x11, + 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x65, + 0x71, 0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x38, 0x0a, 0x09, 0x44, 0x65, 0x72, + 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x1a, 0x16, 0x2e, 0x73, 0x69, + 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x6f, 0x72, 0x12, 0x3b, 0x0a, 0x08, 0x4e, 0x65, 0x78, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, + 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x4f, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, + 0x12, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x77, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x48, 0x0a, 0x12, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1a, 0x2e, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, + 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x53, 0x65, 0x6e, + 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, 0x6d, + 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x07, 0x42, 0x75, 0x6d, + 0x70, 0x46, 0x65, 0x65, 0x12, 0x19, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, + 0x46, 0x65, 0x65, 0x52, 0x65, 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, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x43, 0x0a, 0x08, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1a, 0x2e, 0x77, 0x61, + 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, 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, 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, + 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 ( @@ -3262,68 +3287,69 @@ var file_walletrpc_walletkit_proto_depIdxs = []int32{ 43, // 0: walletrpc.ListUnspentResponse.utxos:type_name -> lnrpc.Utxo 44, // 1: walletrpc.LeaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint 44, // 2: walletrpc.ReleaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint - 0, // 3: walletrpc.Account.address_type:type_name -> walletrpc.AddressType - 0, // 4: walletrpc.ListAccountsRequest.address_type:type_name -> walletrpc.AddressType - 11, // 5: walletrpc.ListAccountsResponse.accounts:type_name -> walletrpc.Account - 0, // 6: walletrpc.ImportAccountRequest.address_type:type_name -> walletrpc.AddressType - 11, // 7: walletrpc.ImportAccountResponse.account:type_name -> walletrpc.Account - 0, // 8: walletrpc.ImportPublicKeyRequest.address_type:type_name -> walletrpc.AddressType - 45, // 9: walletrpc.SendOutputsRequest.outputs:type_name -> signrpc.TxOut - 44, // 10: walletrpc.PendingSweep.outpoint:type_name -> lnrpc.OutPoint - 1, // 11: walletrpc.PendingSweep.witness_type:type_name -> walletrpc.WitnessType - 24, // 12: walletrpc.PendingSweepsResponse.pending_sweeps:type_name -> walletrpc.PendingSweep - 44, // 13: walletrpc.BumpFeeRequest.outpoint:type_name -> lnrpc.OutPoint - 46, // 14: walletrpc.ListSweepsResponse.transaction_details:type_name -> lnrpc.TransactionDetails - 41, // 15: walletrpc.ListSweepsResponse.transaction_ids:type_name -> walletrpc.ListSweepsResponse.TransactionIDs - 35, // 16: walletrpc.FundPsbtRequest.raw:type_name -> walletrpc.TxTemplate - 36, // 17: walletrpc.FundPsbtResponse.locked_utxos:type_name -> walletrpc.UtxoLease - 44, // 18: walletrpc.TxTemplate.inputs:type_name -> lnrpc.OutPoint - 42, // 19: walletrpc.TxTemplate.outputs:type_name -> walletrpc.TxTemplate.OutputsEntry - 44, // 20: walletrpc.UtxoLease.outpoint:type_name -> lnrpc.OutPoint - 36, // 21: walletrpc.ListLeasesResponse.locked_utxos:type_name -> walletrpc.UtxoLease - 2, // 22: walletrpc.WalletKit.ListUnspent:input_type -> walletrpc.ListUnspentRequest - 4, // 23: walletrpc.WalletKit.LeaseOutput:input_type -> walletrpc.LeaseOutputRequest - 6, // 24: walletrpc.WalletKit.ReleaseOutput:input_type -> walletrpc.ReleaseOutputRequest - 39, // 25: walletrpc.WalletKit.ListLeases:input_type -> walletrpc.ListLeasesRequest - 8, // 26: walletrpc.WalletKit.DeriveNextKey:input_type -> walletrpc.KeyReq - 47, // 27: walletrpc.WalletKit.DeriveKey:input_type -> signrpc.KeyLocator - 9, // 28: walletrpc.WalletKit.NextAddr:input_type -> walletrpc.AddrRequest - 12, // 29: walletrpc.WalletKit.ListAccounts:input_type -> walletrpc.ListAccountsRequest - 14, // 30: walletrpc.WalletKit.ImportAccount:input_type -> walletrpc.ImportAccountRequest - 16, // 31: walletrpc.WalletKit.ImportPublicKey:input_type -> walletrpc.ImportPublicKeyRequest - 18, // 32: walletrpc.WalletKit.PublishTransaction:input_type -> walletrpc.Transaction - 20, // 33: walletrpc.WalletKit.SendOutputs:input_type -> walletrpc.SendOutputsRequest - 22, // 34: walletrpc.WalletKit.EstimateFee:input_type -> walletrpc.EstimateFeeRequest - 25, // 35: walletrpc.WalletKit.PendingSweeps:input_type -> walletrpc.PendingSweepsRequest - 27, // 36: walletrpc.WalletKit.BumpFee:input_type -> walletrpc.BumpFeeRequest - 29, // 37: walletrpc.WalletKit.ListSweeps:input_type -> walletrpc.ListSweepsRequest - 31, // 38: walletrpc.WalletKit.LabelTransaction:input_type -> walletrpc.LabelTransactionRequest - 33, // 39: walletrpc.WalletKit.FundPsbt:input_type -> walletrpc.FundPsbtRequest - 37, // 40: walletrpc.WalletKit.FinalizePsbt:input_type -> walletrpc.FinalizePsbtRequest - 3, // 41: walletrpc.WalletKit.ListUnspent:output_type -> walletrpc.ListUnspentResponse - 5, // 42: walletrpc.WalletKit.LeaseOutput:output_type -> walletrpc.LeaseOutputResponse - 7, // 43: walletrpc.WalletKit.ReleaseOutput:output_type -> walletrpc.ReleaseOutputResponse - 40, // 44: walletrpc.WalletKit.ListLeases:output_type -> walletrpc.ListLeasesResponse - 48, // 45: walletrpc.WalletKit.DeriveNextKey:output_type -> signrpc.KeyDescriptor - 48, // 46: walletrpc.WalletKit.DeriveKey:output_type -> signrpc.KeyDescriptor - 10, // 47: walletrpc.WalletKit.NextAddr:output_type -> walletrpc.AddrResponse - 13, // 48: walletrpc.WalletKit.ListAccounts:output_type -> walletrpc.ListAccountsResponse - 15, // 49: walletrpc.WalletKit.ImportAccount:output_type -> walletrpc.ImportAccountResponse - 17, // 50: walletrpc.WalletKit.ImportPublicKey:output_type -> walletrpc.ImportPublicKeyResponse - 19, // 51: walletrpc.WalletKit.PublishTransaction:output_type -> walletrpc.PublishResponse - 21, // 52: walletrpc.WalletKit.SendOutputs:output_type -> walletrpc.SendOutputsResponse - 23, // 53: walletrpc.WalletKit.EstimateFee:output_type -> walletrpc.EstimateFeeResponse - 26, // 54: walletrpc.WalletKit.PendingSweeps:output_type -> walletrpc.PendingSweepsResponse - 28, // 55: walletrpc.WalletKit.BumpFee:output_type -> walletrpc.BumpFeeResponse - 30, // 56: walletrpc.WalletKit.ListSweeps:output_type -> walletrpc.ListSweepsResponse - 32, // 57: walletrpc.WalletKit.LabelTransaction:output_type -> walletrpc.LabelTransactionResponse - 34, // 58: walletrpc.WalletKit.FundPsbt:output_type -> walletrpc.FundPsbtResponse - 38, // 59: walletrpc.WalletKit.FinalizePsbt:output_type -> walletrpc.FinalizePsbtResponse - 41, // [41:60] is the sub-list for method output_type - 22, // [22:41] is the sub-list for method input_type - 22, // [22:22] is the sub-list for extension type_name - 22, // [22:22] is the sub-list for extension extendee - 0, // [0:22] is the sub-list for field type_name + 0, // 3: walletrpc.AddrRequest.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 + 11, // 6: walletrpc.ListAccountsResponse.accounts:type_name -> walletrpc.Account + 0, // 7: walletrpc.ImportAccountRequest.address_type:type_name -> walletrpc.AddressType + 11, // 8: walletrpc.ImportAccountResponse.account:type_name -> walletrpc.Account + 0, // 9: walletrpc.ImportPublicKeyRequest.address_type:type_name -> walletrpc.AddressType + 45, // 10: walletrpc.SendOutputsRequest.outputs:type_name -> signrpc.TxOut + 44, // 11: walletrpc.PendingSweep.outpoint:type_name -> lnrpc.OutPoint + 1, // 12: walletrpc.PendingSweep.witness_type:type_name -> walletrpc.WitnessType + 24, // 13: walletrpc.PendingSweepsResponse.pending_sweeps:type_name -> walletrpc.PendingSweep + 44, // 14: walletrpc.BumpFeeRequest.outpoint:type_name -> lnrpc.OutPoint + 46, // 15: walletrpc.ListSweepsResponse.transaction_details:type_name -> lnrpc.TransactionDetails + 41, // 16: walletrpc.ListSweepsResponse.transaction_ids:type_name -> walletrpc.ListSweepsResponse.TransactionIDs + 35, // 17: walletrpc.FundPsbtRequest.raw:type_name -> walletrpc.TxTemplate + 36, // 18: walletrpc.FundPsbtResponse.locked_utxos:type_name -> walletrpc.UtxoLease + 44, // 19: walletrpc.TxTemplate.inputs:type_name -> lnrpc.OutPoint + 42, // 20: walletrpc.TxTemplate.outputs:type_name -> walletrpc.TxTemplate.OutputsEntry + 44, // 21: walletrpc.UtxoLease.outpoint:type_name -> lnrpc.OutPoint + 36, // 22: walletrpc.ListLeasesResponse.locked_utxos:type_name -> walletrpc.UtxoLease + 2, // 23: walletrpc.WalletKit.ListUnspent:input_type -> walletrpc.ListUnspentRequest + 4, // 24: walletrpc.WalletKit.LeaseOutput:input_type -> walletrpc.LeaseOutputRequest + 6, // 25: walletrpc.WalletKit.ReleaseOutput:input_type -> walletrpc.ReleaseOutputRequest + 39, // 26: walletrpc.WalletKit.ListLeases:input_type -> walletrpc.ListLeasesRequest + 8, // 27: walletrpc.WalletKit.DeriveNextKey:input_type -> walletrpc.KeyReq + 47, // 28: walletrpc.WalletKit.DeriveKey:input_type -> signrpc.KeyLocator + 9, // 29: walletrpc.WalletKit.NextAddr:input_type -> walletrpc.AddrRequest + 12, // 30: walletrpc.WalletKit.ListAccounts:input_type -> walletrpc.ListAccountsRequest + 14, // 31: walletrpc.WalletKit.ImportAccount:input_type -> walletrpc.ImportAccountRequest + 16, // 32: walletrpc.WalletKit.ImportPublicKey:input_type -> walletrpc.ImportPublicKeyRequest + 18, // 33: walletrpc.WalletKit.PublishTransaction:input_type -> walletrpc.Transaction + 20, // 34: walletrpc.WalletKit.SendOutputs:input_type -> walletrpc.SendOutputsRequest + 22, // 35: walletrpc.WalletKit.EstimateFee:input_type -> walletrpc.EstimateFeeRequest + 25, // 36: walletrpc.WalletKit.PendingSweeps:input_type -> walletrpc.PendingSweepsRequest + 27, // 37: walletrpc.WalletKit.BumpFee:input_type -> walletrpc.BumpFeeRequest + 29, // 38: walletrpc.WalletKit.ListSweeps:input_type -> walletrpc.ListSweepsRequest + 31, // 39: walletrpc.WalletKit.LabelTransaction:input_type -> walletrpc.LabelTransactionRequest + 33, // 40: walletrpc.WalletKit.FundPsbt:input_type -> walletrpc.FundPsbtRequest + 37, // 41: walletrpc.WalletKit.FinalizePsbt:input_type -> walletrpc.FinalizePsbtRequest + 3, // 42: walletrpc.WalletKit.ListUnspent:output_type -> walletrpc.ListUnspentResponse + 5, // 43: walletrpc.WalletKit.LeaseOutput:output_type -> walletrpc.LeaseOutputResponse + 7, // 44: walletrpc.WalletKit.ReleaseOutput:output_type -> walletrpc.ReleaseOutputResponse + 40, // 45: walletrpc.WalletKit.ListLeases:output_type -> walletrpc.ListLeasesResponse + 48, // 46: walletrpc.WalletKit.DeriveNextKey:output_type -> signrpc.KeyDescriptor + 48, // 47: walletrpc.WalletKit.DeriveKey:output_type -> signrpc.KeyDescriptor + 10, // 48: walletrpc.WalletKit.NextAddr:output_type -> walletrpc.AddrResponse + 13, // 49: walletrpc.WalletKit.ListAccounts:output_type -> walletrpc.ListAccountsResponse + 15, // 50: walletrpc.WalletKit.ImportAccount:output_type -> walletrpc.ImportAccountResponse + 17, // 51: walletrpc.WalletKit.ImportPublicKey:output_type -> walletrpc.ImportPublicKeyResponse + 19, // 52: walletrpc.WalletKit.PublishTransaction:output_type -> walletrpc.PublishResponse + 21, // 53: walletrpc.WalletKit.SendOutputs:output_type -> walletrpc.SendOutputsResponse + 23, // 54: walletrpc.WalletKit.EstimateFee:output_type -> walletrpc.EstimateFeeResponse + 26, // 55: walletrpc.WalletKit.PendingSweeps:output_type -> walletrpc.PendingSweepsResponse + 28, // 56: walletrpc.WalletKit.BumpFee:output_type -> walletrpc.BumpFeeResponse + 30, // 57: walletrpc.WalletKit.ListSweeps:output_type -> walletrpc.ListSweepsResponse + 32, // 58: walletrpc.WalletKit.LabelTransaction:output_type -> walletrpc.LabelTransactionResponse + 34, // 59: walletrpc.WalletKit.FundPsbt:output_type -> walletrpc.FundPsbtResponse + 38, // 60: walletrpc.WalletKit.FinalizePsbt:output_type -> walletrpc.FinalizePsbtResponse + 42, // [42:61] is the sub-list for method output_type + 23, // [23:42] 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 extendee + 0, // [0:23] is the sub-list for field type_name } func init() { file_walletrpc_walletkit_proto_init() } diff --git a/lnrpc/walletrpc/walletkit.proto b/lnrpc/walletrpc/walletkit.proto index 5354a791d..2d552915e 100644 --- a/lnrpc/walletrpc/walletkit.proto +++ b/lnrpc/walletrpc/walletkit.proto @@ -286,6 +286,16 @@ message AddrRequest { default wallet account is used. */ string account = 1; + + /* + The type of address to derive. + */ + AddressType type = 2; + + /* + Whether a change address should be derived. + */ + bool change = 3; } message AddrResponse { /* diff --git a/lnrpc/walletrpc/walletkit.swagger.json b/lnrpc/walletrpc/walletkit.swagger.json index 0a4f1d1de..46d292072 100644 --- a/lnrpc/walletrpc/walletkit.swagger.json +++ b/lnrpc/walletrpc/walletkit.swagger.json @@ -902,6 +902,14 @@ "account": { "type": "string", "description": "The name of the account to retrieve the next address of. If empty, the\ndefault wallet account is used." + }, + "type": { + "$ref": "#/definitions/walletrpcAddressType", + "description": "The type of address to derive." + }, + "change": { + "type": "boolean", + "description": "Whether a change address should be derived." } } }, diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index 80a5b8e4a..90025fdc0 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -507,9 +507,17 @@ func (w *WalletKit) NextAddr(ctx context.Context, account = req.Account } - addr, err := w.cfg.Wallet.NewAddress( - lnwallet.WitnessPubKey, false, account, - ) + addrType := lnwallet.WitnessPubKey + switch req.Type { + case AddressType_NESTED_WITNESS_PUBKEY_HASH: + addrType = lnwallet.NestedWitnessPubKey + + case AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH: + return nil, fmt.Errorf("invalid address type for next "+ + "address: %v", req.Type) + } + + addr, err := w.cfg.Wallet.NewAddress(addrType, req.Change, account) if err != nil { return nil, err } diff --git a/lnwallet/rpcwallet/rpcwallet.go b/lnwallet/rpcwallet/rpcwallet.go index 9dec83c7b..61c4d9526 100644 --- a/lnwallet/rpcwallet/rpcwallet.go +++ b/lnwallet/rpcwallet/rpcwallet.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "crypto/x509" + "encoding/binary" "errors" "fmt" "io/ioutil" @@ -11,11 +12,15 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcutil/hdkeychain" + "github.com/btcsuite/btcwallet/waddrmgr" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/macaroons" "google.golang.org/grpc" @@ -41,7 +46,12 @@ var ( // local watch-only wallet for keeping track of addresses and transactions but // delegates any signing or ECDH operations to a remote node through RPC. type RPCKeyRing struct { - watchOnlyWallet keychain.SecretKeyRing + // WalletController is the embedded wallet controller of the watch-only + // base wallet. We need to overwrite/shadow certain of the implemented + // methods to make sure we can mirror them to the remote wallet. + lnwallet.WalletController + + watchOnlyKeyRing keychain.SecretKeyRing rpcTimeout time.Duration @@ -52,11 +62,13 @@ type RPCKeyRing struct { var _ keychain.SecretKeyRing = (*RPCKeyRing)(nil) var _ input.Signer = (*RPCKeyRing)(nil) var _ keychain.MessageSignerRing = (*RPCKeyRing)(nil) +var _ lnwallet.WalletController = (*RPCKeyRing)(nil) // NewRPCKeyRing creates a new remote signing secret key ring that uses the // given watch-only base wallet to keep track of addresses and transactions but // delegates any signing or ECDH operations to the remove signer through RPC. -func NewRPCKeyRing(watchOnlyWallet keychain.SecretKeyRing, +func NewRPCKeyRing(watchOnlyKeyRing keychain.SecretKeyRing, + watchOnlyWalletController lnwallet.WalletController, remoteSigner *lncfg.RemoteSigner, rpcTimeout time.Duration) (*RPCKeyRing, error) { @@ -70,13 +82,189 @@ func NewRPCKeyRing(watchOnlyWallet keychain.SecretKeyRing, } return &RPCKeyRing{ - watchOnlyWallet: watchOnlyWallet, - rpcTimeout: rpcTimeout, - signerClient: signrpc.NewSignerClient(rpcConn), - walletClient: walletrpc.NewWalletKitClient(rpcConn), + WalletController: watchOnlyWalletController, + watchOnlyKeyRing: watchOnlyKeyRing, + rpcTimeout: rpcTimeout, + signerClient: signrpc.NewSignerClient(rpcConn), + walletClient: walletrpc.NewWalletKitClient(rpcConn), }, nil } +// NewAddress returns the next external or internal address for the +// wallet dictated by the value of the `change` parameter. If change is +// true, then an internal address should be used, otherwise an external +// address should be returned. The type of address returned is dictated +// by the wallet's capabilities, and may be of type: p2sh, p2wkh, +// p2wsh, etc. The account parameter must be non-empty as it determines +// which account the address should be generated from. +func (r *RPCKeyRing) NewAddress(addrType lnwallet.AddressType, change bool, + account string) (btcutil.Address, error) { + + ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) + 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 +} + +// 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 +} + // DeriveNextKey attempts to derive the *next* key within the key family // (account in BIP43) specified. This method should return the next external // child within this branch. @@ -98,7 +286,7 @@ func (r *RPCKeyRing) DeriveNextKey( "key on remote signer instance: %v", err) } - localDesc, err := r.watchOnlyWallet.DeriveNextKey(keyFam) + localDesc, err := r.watchOnlyKeyRing.DeriveNextKey(keyFam) if err != nil { return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+ "key on local wallet instance: %v", err) @@ -143,7 +331,7 @@ func (r *RPCKeyRing) DeriveKey( "key on remote signer instance: %v", err) } - localDesc, err := r.watchOnlyWallet.DeriveKey(keyLoc) + localDesc, err := r.watchOnlyKeyRing.DeriveKey(keyLoc) if err != nil { return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+ "key on local wallet instance: %v", err) @@ -391,6 +579,27 @@ func toRPCSignReq(tx *wire.MsgTx, }, nil } +// 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 { + case waddrmgr.WitnessPubKey: + return walletrpc.AddressType_WITNESS_PUBKEY_HASH, nil + + 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 // the supplied certificate and macaroon. func connectRPC(hostPort, tlsCertPath, macaroonPath string) (*grpc.ClientConn, From a3addcc9275c55218b5ab3c0a522d692ded4393a Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 14 Oct 2021 15:42:52 +0200 Subject: [PATCH 07/14] multi: forward SendCoins call over RPC --- go.mod | 3 +- go.sum | 6 +- lnrpc/walletrpc/walletkit_server.go | 2 +- lntest/mock/walletcontroller.go | 2 +- lnwallet/btcwallet/btcwallet.go | 7 +- lnwallet/interface.go | 4 +- lnwallet/rpcwallet/rpcwallet.go | 105 ++++++++++++++++++++++++++++ rpcserver.go | 2 +- 8 files changed, 117 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 1502026b6..e99420cc2 100644 --- a/go.mod +++ b/go.mod @@ -9,10 +9,9 @@ require ( github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890 - github.com/btcsuite/btcwallet v0.12.1-0.20210916213031-d0868cb9dd94 + github.com/btcsuite/btcwallet v0.12.1-0.20211008000044-541a8512ccfe github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0 github.com/btcsuite/btcwallet/wallet/txrules v1.1.0 - github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 // indirect github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210822222949-9b5a201c344c github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f diff --git a/go.sum b/go.sum index 6ad1c41df..c8947059f 100644 --- a/go.sum +++ b/go.sum @@ -91,17 +91,15 @@ 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.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/btcwallet v0.12.1-0.20210916213031-d0868cb9dd94 h1:Bx+xu606h2sZNn5VaZMWvI0GtlGE+y+dHI4hbL5Ld6k= -github.com/btcsuite/btcwallet v0.12.1-0.20210916213031-d0868cb9dd94/go.mod h1:gHFk6GQ4IP/a8z6mfwK85GagUPxvAxCmgFy/whrBXhI= +github.com/btcsuite/btcwallet v0.12.1-0.20211008000044-541a8512ccfe h1:G7l/t3Y+B7jhRzcKs9z00KdocqdEqaXstwa0KSxH1bQ= +github.com/btcsuite/btcwallet v0.12.1-0.20211008000044-541a8512ccfe/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.1-0.20210329233242-e0607006dce6/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/go.mod h1:ktYuJyumYtwG+QQ832Q+kqvxWJRAei3Nqs5qhSn4nww= github.com/btcsuite/btcwallet/wallet/txrules v1.0.0/go.mod h1:UwQE78yCerZ313EXZwEiu3jNAtfXj2n2+c8RWiE/WNA= github.com/btcsuite/btcwallet/wallet/txrules v1.1.0 h1:Vg8G8zhNVjaCdwJg2QOmLoWn4RTP7K0J9xlwY8CJnLY= github.com/btcsuite/btcwallet/wallet/txrules v1.1.0/go.mod h1:Zn9UTqpiTH+HOd5BLzSBzULzlOPmcoeyQIA0cp0WbQQ= github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= -github.com/btcsuite/btcwallet/wallet/txsizes v1.0.1-0.20210519225359-6ab9b615576f/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 h1:wZnOolEAeNOHzHTnznw/wQv+j35ftCIokNrnOTOU5o8= github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= github.com/btcsuite/btcwallet/walletdb v1.3.4/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index 90025fdc0..364b373d0 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -1118,7 +1118,7 @@ func (w *WalletKit) FundPsbt(_ context.Context, // lock any coins but might still change the wallet DB by // generating a new change address. changeIndex, err = w.cfg.Wallet.FundPsbt( - packet, feeSatPerKW, account, + packet, minConfs, feeSatPerKW, account, ) if err != nil { return fmt.Errorf("wallet couldn't fund PSBT: %v", err) diff --git a/lntest/mock/walletcontroller.go b/lntest/mock/walletcontroller.go index 50eddd1ce..959fb5382 100644 --- a/lntest/mock/walletcontroller.go +++ b/lntest/mock/walletcontroller.go @@ -169,7 +169,7 @@ func (w *WalletController) ListLeasedOutputs() ([]*wtxmgr.LockedOutput, error) { } // FundPsbt currently does nothing. -func (w *WalletController) FundPsbt(_ *psbt.Packet, +func (w *WalletController) FundPsbt(_ *psbt.Packet, _ int32, _ chainfee.SatPerKWeight, _ string) (int32, error) { return 0, nil diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index 0dd38da04..3a2fb5f0a 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -642,7 +642,8 @@ func (b *BtcWallet) ImportPublicKey(pubKey *btcec.PublicKey, // // This is a part of the WalletController interface. func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut, - feeRate chainfee.SatPerKWeight, minConfs int32, label string) (*wire.MsgTx, error) { + feeRate chainfee.SatPerKWeight, minConfs int32, + label string) (*wire.MsgTx, error) { // Convert our fee rate from sat/kw to sat/kb since it's required by // SendOutputs. @@ -1080,7 +1081,7 @@ func (b *BtcWallet) ListTransactionDetails(startHeight, endHeight int32, // responsibility to lock the inputs before handing them out. // // This is a part of the WalletController interface. -func (b *BtcWallet) FundPsbt(packet *psbt.Packet, +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 @@ -1115,7 +1116,7 @@ func (b *BtcWallet) FundPsbt(packet *psbt.Packet, // 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, accountNum, feeSatPerKB, + packet, keyScope, minConfs, accountNum, feeSatPerKB, b.cfg.CoinSelectionStrategy, ) } diff --git a/lnwallet/interface.go b/lnwallet/interface.go index b15fa7a03..42c2f633a 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -350,8 +350,8 @@ type WalletController interface { // 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. - FundPsbt(packet *psbt.Packet, feeRate chainfee.SatPerKWeight, - account string) (int32, error) + FundPsbt(packet *psbt.Packet, minConfs int32, + feeRate chainfee.SatPerKWeight, account string) (int32, error) // FinalizePsbt expects a partial transaction with all inputs and // outputs fully declared and tries to sign all inputs that belong to diff --git a/lnwallet/rpcwallet/rpcwallet.go b/lnwallet/rpcwallet/rpcwallet.go index 61c4d9526..9b5b12e61 100644 --- a/lnwallet/rpcwallet/rpcwallet.go +++ b/lnwallet/rpcwallet/rpcwallet.go @@ -11,16 +11,20 @@ import ( "time" "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil/hdkeychain" "github.com/btcsuite/btcwallet/waddrmgr" + btcwallet "github.com/btcsuite/btcwallet/wallet" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/lightningnetwork/lnd/lnwallet/chanfunding" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/macaroons" "google.golang.org/grpc" @@ -139,6 +143,26 @@ func (r *RPCKeyRing) NewAddress(addrType lnwallet.AddressType, change bool, 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 @@ -265,6 +289,67 @@ func (r *RPCKeyRing) ImportPublicKey(pubKey *btcec.PublicKey, return nil } +// SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying out to +// the specified outputs. In the case the wallet has insufficient funds, or the +// outputs are non-standard, a non-nil error will be returned. +// +// NOTE: This method requires the global coin selection lock to be held. +// +// This is a part of the WalletController interface. +func (r *RPCKeyRing) SendOutputs(outputs []*wire.TxOut, + feeRate chainfee.SatPerKWeight, minConfs int32, + label string) (*wire.MsgTx, error) { + + tx, err := r.WalletController.SendOutputs( + outputs, feeRate, minConfs, label, + ) + if err != nil && err != btcwallet.ErrTxUnsigned { + return nil, err + } + if err == nil { + // This shouldn't happen since our wallet controller is watch- + // only and can't sign the TX. + return tx, nil + } + + // We know at this point that we only have inputs from our own wallet. + // So we can just compute the input script using the remote signer. + signDesc := input.SignDescriptor{ + HashType: txscript.SigHashAll, + SigHashes: txscript.NewTxSigHashes(tx), + } + for i, txIn := range tx.TxIn { + // We can only sign this input if it's ours, so we'll ask the + // 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 + // is out of sync. + info, err := r.coinFromOutPoint(txIn.PreviousOutPoint) + if err != nil { + return nil, fmt.Errorf("error looking up utxo: %v", err) + } + + // Now that we know the input is ours, we'll populate the + // signDesc with the per input unique information. + signDesc.Output = &wire.TxOut{ + Value: info.Value, + PkScript: info.PkScript, + } + signDesc.InputIndex = i + + // Finally, we'll sign the input as is, and populate the input + // with the witness and sigScript (if needed). + inputScript, err := r.ComputeInputScript(tx, &signDesc) + if err != nil { + return nil, err + } + + txIn.SignatureScript = inputScript.SigScript + txIn.Witness = inputScript.Witness + } + + return tx, r.WalletController.PublishTransaction(tx, label) +} + // DeriveNextKey attempts to derive the *next* key within the key family // (account in BIP43) specified. This method should return the next external // child within this branch. @@ -534,6 +619,26 @@ func (r *RPCKeyRing) ComputeInputScript(tx *wire.MsgTx, }, 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, diff --git a/rpcserver.go b/rpcserver.go index d56067a9e..85346b544 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1037,7 +1037,7 @@ func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64, return nil, err } - // If that checks out, we're failry confident that creating sending to + // If that checks out, we're fairly confident that creating sending to // these outputs will keep the wallet balance above the reserve. tx, err := r.server.cc.Wallet.SendOutputs( outputs, feeRate, minConfs, label, From 6d339f31c00dd7a7d69058f08e8c03b5460c4429 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 14 Oct 2021 15:42:53 +0200 Subject: [PATCH 08/14] rpcwallet: forward FinalizePsbt call over RPC --- lnwallet/rpcwallet/rpcwallet.go | 51 +++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/lnwallet/rpcwallet/rpcwallet.go b/lnwallet/rpcwallet/rpcwallet.go index 9b5b12e61..b31881d15 100644 --- a/lnwallet/rpcwallet/rpcwallet.go +++ b/lnwallet/rpcwallet/rpcwallet.go @@ -15,6 +15,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil/hdkeychain" + "github.com/btcsuite/btcutil/psbt" "github.com/btcsuite/btcwallet/waddrmgr" btcwallet "github.com/btcsuite/btcwallet/wallet" "github.com/lightningnetwork/lnd/input" @@ -350,6 +351,56 @@ func (r *RPCKeyRing) SendOutputs(outputs []*wire.TxOut, return tx, r.WalletController.PublishTransaction(tx, label) } +// 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 (r *RPCKeyRing) FinalizePsbt(packet *psbt.Packet, accountName string) error { + ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) + defer cancel() + + var buf bytes.Buffer + if err := packet.Serialize(&buf); err != nil { + return fmt.Errorf("error serializing PSBT: %v", err) + } + + resp, err := r.walletClient.FinalizePsbt( + ctxt, &walletrpc.FinalizePsbtRequest{ + FundedPsbt: buf.Bytes(), + Account: accountName, + }, + ) + if err != nil { + return fmt.Errorf("error finalizing PSBT in remote signer "+ + "instance: %v", err) + } + + signedPacket, err := psbt.NewFromRawBytes( + bytes.NewReader(resp.SignedPsbt), false, + ) + if err != nil { + return fmt.Errorf("error parsing signed PSBT: %v", err) + } + + // The caller expects the packet to be modified instead of a new + // instance to be returned. So we just overwrite all fields in the + // original packet. + packet.UnsignedTx = signedPacket.UnsignedTx + packet.Inputs = signedPacket.Inputs + packet.Outputs = signedPacket.Outputs + packet.Unknowns = signedPacket.Unknowns + + return nil +} + // DeriveNextKey attempts to derive the *next* key within the key family // (account in BIP43) specified. This method should return the next external // child within this branch. From ceb31f90342cbb64ac98250e1569ce9517bd018b Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 14 Oct 2021 15:42:55 +0200 Subject: [PATCH 09/14] multi: allow watch-only wallet creation --- config_builder.go | 68 +++++ lnrpc/walletunlocker.pb.go | 418 +++++++++++++++++++++++------- lnrpc/walletunlocker.proto | 66 +++++ lnrpc/walletunlocker.swagger.json | 50 ++++ walletunlocker/service.go | 73 ++++++ 5 files changed, 581 insertions(+), 94 deletions(-) diff --git a/config_builder.go b/config_builder.go index 2aa8f9c49..b4f8e2b4e 100644 --- a/config_builder.go +++ b/config_builder.go @@ -8,6 +8,7 @@ import ( "net" "os" "path/filepath" + "sort" "strconv" "strings" "time" @@ -15,6 +16,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btclog" + "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/walletdb" proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" @@ -952,6 +954,7 @@ func waitForWalletPassword(cfg *Config, password := initMsg.Passphrase cipherSeed := initMsg.WalletSeed extendedKey := initMsg.WalletExtendedKey + watchOnlyAccounts := initMsg.WatchOnlyAccounts recoveryWindow := initMsg.RecoveryWindow // Before we proceed, we'll check the internal version of the @@ -1000,6 +1003,26 @@ func waitForWalletPassword(cfg *Config, password, password, extendedKey, birthday, ) + // Neither seed nor extended private key was given, so maybe the + // third option was chosen, the watch-only initialization. In + // this case we need to import each of the xpubs individually. + case watchOnlyAccounts != nil: + if !cfg.RemoteSigner.Enable { + return nil, fmt.Errorf("cannot initialize " + + "watch only wallet with remote " + + "signer config disabled") + } + + birthday = initMsg.ExtendedKeyBirthday + newWallet, err = loader.CreateNewWatchingOnlyWallet( + password, birthday, + ) + if err != nil { + break + } + + err = importWatchOnlyAccounts(newWallet, initMsg) + default: // The unlocker service made sure either the cipher seed // or the extended key is set so, we shouldn't get here. @@ -1065,6 +1088,51 @@ func waitForWalletPassword(cfg *Config, } } +// importWatchOnlyAccounts imports all individual account xpubs into our wallet +// which we created as watch-only. +func importWatchOnlyAccounts(wallet *wallet.Wallet, + initMsg *walletunlocker.WalletInitMsg) error { + + scopes := make([]waddrmgr.ScopedIndex, 0, len(initMsg.WatchOnlyAccounts)) + for scope := range initMsg.WatchOnlyAccounts { + scopes = append(scopes, scope) + } + + // We need to import the accounts in the correct order, otherwise the + // indices will be incorrect. + sort.Slice(scopes, func(i, j int) bool { + return scopes[i].Scope.Purpose < scopes[j].Scope.Purpose || + scopes[i].Index < scopes[j].Index + }) + + for _, scope := range scopes { + addrSchema := waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0084] + if scope.Scope.Purpose == waddrmgr.KeyScopeBIP0049Plus.Purpose { + addrSchema = waddrmgr.ScopeAddrMap[scope.Scope] + } + + // We want a human-readable account name. But for the default + // on-chain wallet we actually need to call it "default" to make + // sure everything works correctly. + name := fmt.Sprintf("%s/%d'", scope.Scope.String(), scope.Index) + if scope.Index == 0 { + name = "default" + } + + _, err := wallet.ImportAccountWithScope( + name, initMsg.WatchOnlyAccounts[scope], + initMsg.WatchOnlyMasterFingerprint, scope.Scope, + addrSchema, + ) + if err != nil { + return fmt.Errorf("could not import account %v: %v", + name, err) + } + } + + return nil +} + // initNeutrinoBackend inits a new instance of the neutrino light client // backend given a target chain directory to store the chain state. func initNeutrinoBackend(cfg *Config, chainDir string, diff --git a/lnrpc/walletunlocker.pb.go b/lnrpc/walletunlocker.pb.go index 2f02d2add..07c169e4f 100644 --- a/lnrpc/walletunlocker.pb.go +++ b/lnrpc/walletunlocker.pb.go @@ -212,6 +212,13 @@ type InitWalletRequest struct { //which case lnd will start scanning from the first SegWit block (481824 on //mainnet). ExtendedMasterKeyBirthdayTimestamp uint64 `protobuf:"varint,8,opt,name=extended_master_key_birthday_timestamp,json=extendedMasterKeyBirthdayTimestamp,proto3" json:"extended_master_key_birthday_timestamp,omitempty"` + // + //watch_only is the third option of initializing a wallet: by importing + //account xpubs only and therefore creating a watch-only wallet that does not + //contain any private keys. That means the wallet won't be able to sign for + //any of the keys and _needs_ to be run with a remote signer that has the + //corresponding private keys and can serve signing RPC requests. + WatchOnly *WatchOnly `protobuf:"bytes,9,opt,name=watch_only,json=watchOnly,proto3" json:"watch_only,omitempty"` } func (x *InitWalletRequest) Reset() { @@ -302,6 +309,13 @@ func (x *InitWalletRequest) GetExtendedMasterKeyBirthdayTimestamp() uint64 { return 0 } +func (x *InitWalletRequest) GetWatchOnly() *WatchOnly { + if x != nil { + return x.WatchOnly + } + return nil +} + type InitWalletResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -355,6 +369,172 @@ func (x *InitWalletResponse) GetAdminMacaroon() []byte { return nil } +type WatchOnly struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The unix timestamp in seconds of when the master key was created. lnd will + //only start scanning for funds in blocks that are after the birthday which + //can speed up the process significantly. If the birthday is not known, this + //should be left at its default value of 0 in which case lnd will start + //scanning from the first SegWit block (481824 on mainnet). + MasterKeyBirthdayTimestamp uint64 `protobuf:"varint,1,opt,name=master_key_birthday_timestamp,json=masterKeyBirthdayTimestamp,proto3" json:"master_key_birthday_timestamp,omitempty"` + // + //The fingerprint of the root key (also known as the key with derivation path + //m/) from which the account public keys were derived from. This may be + //required by some hardware wallets for proper identification and signing. The + //bytes must be in big-endian order. + MasterKeyFingerprint []byte `protobuf:"bytes,2,opt,name=master_key_fingerprint,json=masterKeyFingerprint,proto3" json:"master_key_fingerprint,omitempty"` + // + //The list of accounts to import. There _must_ be an account for all of lnd's + //main key scopes: BIP49/BIP84 (m/49'/0'/0', m/84'/0'/0', note that the + //coin type is always 0, even for testnet/regtest) and lnd's internal key + //scope (m/1017'/'/'), where account is the key family as + //defined in `keychain/derivation.go` (currently indices 0 to 9). + Accounts []*WatchOnlyAccount `protobuf:"bytes,3,rep,name=accounts,proto3" json:"accounts,omitempty"` +} + +func (x *WatchOnly) Reset() { + *x = WatchOnly{} + if protoimpl.UnsafeEnabled { + mi := &file_walletunlocker_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WatchOnly) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchOnly) ProtoMessage() {} + +func (x *WatchOnly) ProtoReflect() protoreflect.Message { + mi := &file_walletunlocker_proto_msgTypes[4] + 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 WatchOnly.ProtoReflect.Descriptor instead. +func (*WatchOnly) Descriptor() ([]byte, []int) { + return file_walletunlocker_proto_rawDescGZIP(), []int{4} +} + +func (x *WatchOnly) GetMasterKeyBirthdayTimestamp() uint64 { + if x != nil { + return x.MasterKeyBirthdayTimestamp + } + return 0 +} + +func (x *WatchOnly) GetMasterKeyFingerprint() []byte { + if x != nil { + return x.MasterKeyFingerprint + } + return nil +} + +func (x *WatchOnly) GetAccounts() []*WatchOnlyAccount { + if x != nil { + return x.Accounts + } + return nil +} + +type WatchOnlyAccount struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //Purpose is the first number in the derivation path, must be either 49, 84 + //or 1017. + Purpose uint32 `protobuf:"varint,1,opt,name=purpose,proto3" json:"purpose,omitempty"` + // + //Coin type is the second number in the derivation path, this is _always_ 0 + //for purposes 49 and 84. It only needs to be set to 1 for purpose 1017 on + //testnet or regtest. + CoinType uint32 `protobuf:"varint,2,opt,name=coin_type,json=coinType,proto3" json:"coin_type,omitempty"` + // + //Account is the third number in the derivation path. For purposes 49 and 84 + //at least the default account (index 0) needs to be created but optional + //additional accounts are allowed. For purpose 1017 there needs to be exactly + //one account for each of the key families defined in `keychain/derivation.go` + //(currently indices 0 to 9) + Account uint32 `protobuf:"varint,3,opt,name=account,proto3" json:"account,omitempty"` + // + //The extended public key at depth 3 for the given account. + Xpub string `protobuf:"bytes,4,opt,name=xpub,proto3" json:"xpub,omitempty"` +} + +func (x *WatchOnlyAccount) Reset() { + *x = WatchOnlyAccount{} + if protoimpl.UnsafeEnabled { + mi := &file_walletunlocker_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WatchOnlyAccount) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchOnlyAccount) ProtoMessage() {} + +func (x *WatchOnlyAccount) ProtoReflect() protoreflect.Message { + mi := &file_walletunlocker_proto_msgTypes[5] + 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 WatchOnlyAccount.ProtoReflect.Descriptor instead. +func (*WatchOnlyAccount) Descriptor() ([]byte, []int) { + return file_walletunlocker_proto_rawDescGZIP(), []int{5} +} + +func (x *WatchOnlyAccount) GetPurpose() uint32 { + if x != nil { + return x.Purpose + } + return 0 +} + +func (x *WatchOnlyAccount) GetCoinType() uint32 { + if x != nil { + return x.CoinType + } + return 0 +} + +func (x *WatchOnlyAccount) GetAccount() uint32 { + if x != nil { + return x.Account + } + return 0 +} + +func (x *WatchOnlyAccount) GetXpub() string { + if x != nil { + return x.Xpub + } + return "" +} + type UnlockWalletRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -389,7 +569,7 @@ type UnlockWalletRequest struct { func (x *UnlockWalletRequest) Reset() { *x = UnlockWalletRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletunlocker_proto_msgTypes[4] + mi := &file_walletunlocker_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -402,7 +582,7 @@ func (x *UnlockWalletRequest) String() string { func (*UnlockWalletRequest) ProtoMessage() {} func (x *UnlockWalletRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletunlocker_proto_msgTypes[4] + mi := &file_walletunlocker_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -415,7 +595,7 @@ func (x *UnlockWalletRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UnlockWalletRequest.ProtoReflect.Descriptor instead. func (*UnlockWalletRequest) Descriptor() ([]byte, []int) { - return file_walletunlocker_proto_rawDescGZIP(), []int{4} + return file_walletunlocker_proto_rawDescGZIP(), []int{6} } func (x *UnlockWalletRequest) GetWalletPassword() []byte { @@ -455,7 +635,7 @@ type UnlockWalletResponse struct { func (x *UnlockWalletResponse) Reset() { *x = UnlockWalletResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletunlocker_proto_msgTypes[5] + mi := &file_walletunlocker_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -468,7 +648,7 @@ func (x *UnlockWalletResponse) String() string { func (*UnlockWalletResponse) ProtoMessage() {} func (x *UnlockWalletResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletunlocker_proto_msgTypes[5] + mi := &file_walletunlocker_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -481,7 +661,7 @@ func (x *UnlockWalletResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UnlockWalletResponse.ProtoReflect.Descriptor instead. func (*UnlockWalletResponse) Descriptor() ([]byte, []int) { - return file_walletunlocker_proto_rawDescGZIP(), []int{5} + return file_walletunlocker_proto_rawDescGZIP(), []int{7} } type ChangePasswordRequest struct { @@ -513,7 +693,7 @@ type ChangePasswordRequest struct { func (x *ChangePasswordRequest) Reset() { *x = ChangePasswordRequest{} if protoimpl.UnsafeEnabled { - mi := &file_walletunlocker_proto_msgTypes[6] + mi := &file_walletunlocker_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -526,7 +706,7 @@ func (x *ChangePasswordRequest) String() string { func (*ChangePasswordRequest) ProtoMessage() {} func (x *ChangePasswordRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletunlocker_proto_msgTypes[6] + mi := &file_walletunlocker_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -539,7 +719,7 @@ func (x *ChangePasswordRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangePasswordRequest.ProtoReflect.Descriptor instead. func (*ChangePasswordRequest) Descriptor() ([]byte, []int) { - return file_walletunlocker_proto_rawDescGZIP(), []int{6} + return file_walletunlocker_proto_rawDescGZIP(), []int{8} } func (x *ChangePasswordRequest) GetCurrentPassword() []byte { @@ -588,7 +768,7 @@ type ChangePasswordResponse struct { func (x *ChangePasswordResponse) Reset() { *x = ChangePasswordResponse{} if protoimpl.UnsafeEnabled { - mi := &file_walletunlocker_proto_msgTypes[7] + mi := &file_walletunlocker_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -601,7 +781,7 @@ func (x *ChangePasswordResponse) String() string { func (*ChangePasswordResponse) ProtoMessage() {} func (x *ChangePasswordResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletunlocker_proto_msgTypes[7] + mi := &file_walletunlocker_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -614,7 +794,7 @@ func (x *ChangePasswordResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangePasswordResponse.ProtoReflect.Descriptor instead. func (*ChangePasswordResponse) Descriptor() ([]byte, []int) { - return file_walletunlocker_proto_rawDescGZIP(), []int{7} + return file_walletunlocker_proto_rawDescGZIP(), []int{9} } func (x *ChangePasswordResponse) GetAdminMacaroon() []byte { @@ -642,7 +822,7 @@ var file_walletunlocker_proto_rawDesc = []byte{ 0x09, 0x52, 0x12, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x65, 0x65, 0x64, 0x4d, 0x6e, 0x65, 0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x6e, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, - 0x65, 0x6e, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x65, 0x64, 0x53, 0x65, 0x65, 0x64, 0x22, 0xb3, + 0x65, 0x6e, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x65, 0x64, 0x53, 0x65, 0x65, 0x64, 0x22, 0xe4, 0x03, 0x0a, 0x11, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x77, @@ -670,63 +850,85 @@ var file_walletunlocker_proto_rawDesc = []byte{ 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x22, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x42, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x22, 0x3b, 0x0a, 0x12, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x64, - 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, - 0x6e, 0x22, 0xd2, 0x01, 0x0a, 0x13, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x0e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, - 0x72, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x77, - 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x63, - 0x6f, 0x76, 0x65, 0x72, 0x79, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x12, 0x42, 0x0a, 0x0f, 0x63, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, - 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, - 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, - 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x5f, 0x69, 0x6e, 0x69, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6c, 0x65, - 0x73, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x22, 0x16, 0x0a, 0x14, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, - 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xbf, - 0x01, 0x0a, 0x15, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, - 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, - 0x6f, 0x72, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x65, 0x77, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, - 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6e, 0x65, 0x77, 0x50, 0x61, - 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6c, - 0x65, 0x73, 0x73, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, - 0x73, 0x74, 0x61, 0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x31, 0x0a, - 0x15, 0x6e, 0x65, 0x77, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x5f, 0x72, 0x6f, - 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x6e, 0x65, - 0x77, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, - 0x22, 0x3f, 0x0a, 0x16, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, - 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x64, - 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, - 0x6e, 0x32, 0xa5, 0x02, 0x0a, 0x0e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x55, 0x6e, 0x6c, 0x6f, - 0x63, 0x6b, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x07, 0x47, 0x65, 0x6e, 0x53, 0x65, 0x65, 0x64, 0x12, - 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x53, 0x65, 0x65, 0x64, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x6e, 0x53, 0x65, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, - 0x0a, 0x0a, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x12, 0x18, 0x2e, 0x6c, + 0x74, 0x61, 0x6d, 0x70, 0x12, 0x2f, 0x0a, 0x0a, 0x77, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x6f, 0x6e, + 0x6c, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x52, 0x09, 0x77, 0x61, 0x74, 0x63, + 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x3b, 0x0a, 0x12, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, + 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, + 0x6f, 0x6e, 0x22, 0xb9, 0x01, 0x0a, 0x09, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x6e, 0x6c, 0x79, + 0x12, 0x41, 0x0a, 0x1d, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, + 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1a, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, + 0x65, 0x79, 0x42, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x12, 0x34, 0x0a, 0x16, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, + 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x46, 0x69, + 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x08, 0x61, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x77, + 0x0a, 0x10, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x07, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, + 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x08, 0x63, 0x6f, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x70, 0x75, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x78, 0x70, 0x75, 0x62, 0x22, 0xd2, 0x01, 0x0a, 0x13, 0x55, 0x6e, 0x6c, 0x6f, + 0x63, 0x6b, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x27, 0x0a, 0x0f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x79, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x0e, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x57, 0x69, 0x6e, 0x64, 0x6f, + 0x77, 0x12, 0x42, 0x0a, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x62, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6c, 0x65, + 0x73, 0x73, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x22, 0x16, 0x0a, 0x14, + 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xbf, 0x01, 0x0a, 0x15, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, + 0x0a, 0x10, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x65, 0x77, + 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0b, 0x6e, 0x65, 0x77, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x25, 0x0a, 0x0e, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x49, + 0x6e, 0x69, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x6e, 0x65, 0x77, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, + 0x6f, 0x6f, 0x6e, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x12, 0x6e, 0x65, 0x77, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, + 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x22, 0x3f, 0x0a, 0x16, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, + 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4d, + 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x32, 0xa5, 0x02, 0x0a, 0x0e, 0x57, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x07, 0x47, 0x65, + 0x6e, 0x53, 0x65, 0x65, 0x64, 0x12, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x6e, 0x53, 0x65, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x53, 0x65, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0a, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x57, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, - 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, - 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1c, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, - 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, - 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 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, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x55, 0x6e, 0x6c, 0x6f, 0x63, + 0x6b, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x6e, 0x6c, 0x6f, + 0x63, 0x6b, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, + 0x27, 0x5a, 0x25, 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, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -741,34 +943,38 @@ func file_walletunlocker_proto_rawDescGZIP() []byte { return file_walletunlocker_proto_rawDescData } -var file_walletunlocker_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_walletunlocker_proto_msgTypes = make([]protoimpl.MessageInfo, 10) var file_walletunlocker_proto_goTypes = []interface{}{ (*GenSeedRequest)(nil), // 0: lnrpc.GenSeedRequest (*GenSeedResponse)(nil), // 1: lnrpc.GenSeedResponse (*InitWalletRequest)(nil), // 2: lnrpc.InitWalletRequest (*InitWalletResponse)(nil), // 3: lnrpc.InitWalletResponse - (*UnlockWalletRequest)(nil), // 4: lnrpc.UnlockWalletRequest - (*UnlockWalletResponse)(nil), // 5: lnrpc.UnlockWalletResponse - (*ChangePasswordRequest)(nil), // 6: lnrpc.ChangePasswordRequest - (*ChangePasswordResponse)(nil), // 7: lnrpc.ChangePasswordResponse - (*ChanBackupSnapshot)(nil), // 8: lnrpc.ChanBackupSnapshot + (*WatchOnly)(nil), // 4: lnrpc.WatchOnly + (*WatchOnlyAccount)(nil), // 5: lnrpc.WatchOnlyAccount + (*UnlockWalletRequest)(nil), // 6: lnrpc.UnlockWalletRequest + (*UnlockWalletResponse)(nil), // 7: lnrpc.UnlockWalletResponse + (*ChangePasswordRequest)(nil), // 8: lnrpc.ChangePasswordRequest + (*ChangePasswordResponse)(nil), // 9: lnrpc.ChangePasswordResponse + (*ChanBackupSnapshot)(nil), // 10: lnrpc.ChanBackupSnapshot } var file_walletunlocker_proto_depIdxs = []int32{ - 8, // 0: lnrpc.InitWalletRequest.channel_backups:type_name -> lnrpc.ChanBackupSnapshot - 8, // 1: lnrpc.UnlockWalletRequest.channel_backups:type_name -> lnrpc.ChanBackupSnapshot - 0, // 2: lnrpc.WalletUnlocker.GenSeed:input_type -> lnrpc.GenSeedRequest - 2, // 3: lnrpc.WalletUnlocker.InitWallet:input_type -> lnrpc.InitWalletRequest - 4, // 4: lnrpc.WalletUnlocker.UnlockWallet:input_type -> lnrpc.UnlockWalletRequest - 6, // 5: lnrpc.WalletUnlocker.ChangePassword:input_type -> lnrpc.ChangePasswordRequest - 1, // 6: lnrpc.WalletUnlocker.GenSeed:output_type -> lnrpc.GenSeedResponse - 3, // 7: lnrpc.WalletUnlocker.InitWallet:output_type -> lnrpc.InitWalletResponse - 5, // 8: lnrpc.WalletUnlocker.UnlockWallet:output_type -> lnrpc.UnlockWalletResponse - 7, // 9: lnrpc.WalletUnlocker.ChangePassword:output_type -> lnrpc.ChangePasswordResponse - 6, // [6:10] is the sub-list for method output_type - 2, // [2:6] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name + 10, // 0: lnrpc.InitWalletRequest.channel_backups:type_name -> lnrpc.ChanBackupSnapshot + 4, // 1: lnrpc.InitWalletRequest.watch_only:type_name -> lnrpc.WatchOnly + 5, // 2: lnrpc.WatchOnly.accounts:type_name -> lnrpc.WatchOnlyAccount + 10, // 3: lnrpc.UnlockWalletRequest.channel_backups:type_name -> lnrpc.ChanBackupSnapshot + 0, // 4: lnrpc.WalletUnlocker.GenSeed:input_type -> lnrpc.GenSeedRequest + 2, // 5: lnrpc.WalletUnlocker.InitWallet:input_type -> lnrpc.InitWalletRequest + 6, // 6: lnrpc.WalletUnlocker.UnlockWallet:input_type -> lnrpc.UnlockWalletRequest + 8, // 7: lnrpc.WalletUnlocker.ChangePassword:input_type -> lnrpc.ChangePasswordRequest + 1, // 8: lnrpc.WalletUnlocker.GenSeed:output_type -> lnrpc.GenSeedResponse + 3, // 9: lnrpc.WalletUnlocker.InitWallet:output_type -> lnrpc.InitWalletResponse + 7, // 10: lnrpc.WalletUnlocker.UnlockWallet:output_type -> lnrpc.UnlockWalletResponse + 9, // 11: lnrpc.WalletUnlocker.ChangePassword:output_type -> lnrpc.ChangePasswordResponse + 8, // [8:12] is the sub-list for method output_type + 4, // [4:8] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_walletunlocker_proto_init() } @@ -827,7 +1033,7 @@ func file_walletunlocker_proto_init() { } } file_walletunlocker_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UnlockWalletRequest); i { + switch v := v.(*WatchOnly); i { case 0: return &v.state case 1: @@ -839,7 +1045,7 @@ func file_walletunlocker_proto_init() { } } file_walletunlocker_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UnlockWalletResponse); i { + switch v := v.(*WatchOnlyAccount); i { case 0: return &v.state case 1: @@ -851,7 +1057,7 @@ func file_walletunlocker_proto_init() { } } file_walletunlocker_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChangePasswordRequest); i { + switch v := v.(*UnlockWalletRequest); i { case 0: return &v.state case 1: @@ -863,6 +1069,30 @@ func file_walletunlocker_proto_init() { } } file_walletunlocker_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UnlockWalletResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletunlocker_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChangePasswordRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_walletunlocker_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ChangePasswordResponse); i { case 0: return &v.state @@ -881,7 +1111,7 @@ func file_walletunlocker_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_walletunlocker_proto_rawDesc, NumEnums: 0, - NumMessages: 8, + NumMessages: 10, NumExtensions: 0, NumServices: 1, }, diff --git a/lnrpc/walletunlocker.proto b/lnrpc/walletunlocker.proto index 730b323c5..55bc31ca1 100644 --- a/lnrpc/walletunlocker.proto +++ b/lnrpc/walletunlocker.proto @@ -176,6 +176,15 @@ message InitWalletRequest { mainnet). */ uint64 extended_master_key_birthday_timestamp = 8; + + /* + watch_only is the third option of initializing a wallet: by importing + account xpubs only and therefore creating a watch-only wallet that does not + contain any private keys. That means the wallet won't be able to sign for + any of the keys and _needs_ to be run with a remote signer that has the + corresponding private keys and can serve signing RPC requests. + */ + WatchOnly watch_only = 9; } message InitWalletResponse { /* @@ -188,6 +197,63 @@ message InitWalletResponse { bytes admin_macaroon = 1; } +message WatchOnly { + /* + The unix timestamp in seconds of when the master key was created. lnd will + only start scanning for funds in blocks that are after the birthday which + can speed up the process significantly. If the birthday is not known, this + should be left at its default value of 0 in which case lnd will start + scanning from the first SegWit block (481824 on mainnet). + */ + uint64 master_key_birthday_timestamp = 1; + + /* + The fingerprint of the root key (also known as the key with derivation path + m/) from which the account public keys were derived from. This may be + required by some hardware wallets for proper identification and signing. The + bytes must be in big-endian order. + */ + bytes master_key_fingerprint = 2; + + /* + The list of accounts to import. There _must_ be an account for all of lnd's + main key scopes: BIP49/BIP84 (m/49'/0'/0', m/84'/0'/0', note that the + coin type is always 0, even for testnet/regtest) and lnd's internal key + scope (m/1017'/'/'), where account is the key family as + defined in `keychain/derivation.go` (currently indices 0 to 9). + */ + repeated WatchOnlyAccount accounts = 3; +} + +message WatchOnlyAccount { + /* + Purpose is the first number in the derivation path, must be either 49, 84 + or 1017. + */ + uint32 purpose = 1; + + /* + Coin type is the second number in the derivation path, this is _always_ 0 + for purposes 49 and 84. It only needs to be set to 1 for purpose 1017 on + testnet or regtest. + */ + uint32 coin_type = 2; + + /* + Account is the third number in the derivation path. For purposes 49 and 84 + at least the default account (index 0) needs to be created but optional + additional accounts are allowed. For purpose 1017 there needs to be exactly + one account for each of the key families defined in `keychain/derivation.go` + (currently indices 0 to 9) + */ + uint32 account = 3; + + /* + The extended public key at depth 3 for the given account. + */ + string xpub = 4; +} + message UnlockWalletRequest { /* wallet_password should be the current valid passphrase for the daemon. This diff --git a/lnrpc/walletunlocker.swagger.json b/lnrpc/walletunlocker.swagger.json index eeb3710e6..8379e6923 100644 --- a/lnrpc/walletunlocker.swagger.json +++ b/lnrpc/walletunlocker.swagger.json @@ -309,6 +309,10 @@ "type": "string", "format": "uint64", "description": "extended_master_key_birthday_timestamp is the optional unix timestamp in\nseconds to use as the wallet's birthday when using an extended master key\nto restore the wallet. lnd will only start scanning for funds in blocks that\nare after the birthday which can speed up the process significantly. If the\nbirthday is not known, this should be left at its default value of 0 in\nwhich case lnd will start scanning from the first SegWit block (481824 on\nmainnet)." + }, + "watch_only": { + "$ref": "#/definitions/lnrpcWatchOnly", + "description": "watch_only is the third option of initializing a wallet: by importing\naccount xpubs only and therefore creating a watch-only wallet that does not\ncontain any private keys. That means the wallet won't be able to sign for\nany of the keys and _needs_ to be run with a remote signer that has the\ncorresponding private keys and can serve signing RPC requests." } } }, @@ -365,6 +369,52 @@ "lnrpcUnlockWalletResponse": { "type": "object" }, + "lnrpcWatchOnly": { + "type": "object", + "properties": { + "master_key_birthday_timestamp": { + "type": "string", + "format": "uint64", + "description": "The unix timestamp in seconds of when the master key was created. lnd will\nonly start scanning for funds in blocks that are after the birthday which\ncan speed up the process significantly. If the birthday is not known, this\nshould be left at its default value of 0 in which case lnd will start\nscanning from the first SegWit block (481824 on mainnet)." + }, + "master_key_fingerprint": { + "type": "string", + "format": "byte", + "description": "The fingerprint of the root key (also known as the key with derivation path\nm/) from which the account public keys were derived from. This may be\nrequired by some hardware wallets for proper identification and signing. The\nbytes must be in big-endian order." + }, + "accounts": { + "type": "array", + "items": { + "$ref": "#/definitions/lnrpcWatchOnlyAccount" + }, + "description": "The list of accounts to import. There _must_ be an account for all of lnd's\nmain key scopes: BIP49/BIP84 (m/49'/0'/0', m/84'/0'/0', note that the\ncoin type is always 0, even for testnet/regtest) and lnd's internal key\nscope (m/1017'/\u003ccoin_type\u003e'/\u003caccount\u003e'), where account is the key family as\ndefined in `keychain/derivation.go` (currently indices 0 to 9)." + } + } + }, + "lnrpcWatchOnlyAccount": { + "type": "object", + "properties": { + "purpose": { + "type": "integer", + "format": "int64", + "description": "Purpose is the first number in the derivation path, must be either 49, 84\nor 1017." + }, + "coin_type": { + "type": "integer", + "format": "int64", + "description": "Coin type is the second number in the derivation path, this is _always_ 0\nfor purposes 49 and 84. It only needs to be set to 1 for purpose 1017 on\ntestnet or regtest." + }, + "account": { + "type": "integer", + "format": "int64", + "title": "Account is the third number in the derivation path. For purposes 49 and 84\nat least the default account (index 0) needs to be created but optional\nadditional accounts are allowed. For purpose 1017 there needs to be exactly\none account for each of the key families defined in `keychain/derivation.go`\n(currently indices 0 to 9)" + }, + "xpub": { + "type": "string", + "description": "The extended public key at depth 3 for the given account." + } + } + }, "protobufAny": { "type": "object", "properties": { diff --git a/walletunlocker/service.go b/walletunlocker/service.go index beb5b6a51..92a4bed37 100644 --- a/walletunlocker/service.go +++ b/walletunlocker/service.go @@ -10,6 +10,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcutil/hdkeychain" + "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wallet" "github.com/lightningnetwork/lnd/aezeed" "github.com/lightningnetwork/lnd/chanbackup" @@ -103,6 +104,18 @@ type WalletInitMsg struct { // through an extended key instead of an aezeed. ExtendedKeyBirthday time.Time + // WatchOnlyAccounts is a map of scoped account extended public keys + // that should be imported to create a watch-only wallet. + WatchOnlyAccounts map[waddrmgr.ScopedIndex]*hdkeychain.ExtendedKey + + // WatchOnlyBirthday is the birthday of the master root key the above + // watch-only account xpubs were derived from. + WatchOnlyBirthday time.Time + + // WatchOnlyMasterFingerprint is the fingerprint of the master root key + // the above watch-only account xpubs were derived from. + WatchOnlyMasterFingerprint uint32 + // RecoveryWindow is the address look-ahead used when restoring a seed // with existing funds. A recovery window zero indicates that no // recovery should be attempted, such as after the wallet's initial @@ -495,6 +508,66 @@ func (u *UnlockerService) InitWallet(ctx context.Context, initMsg.WalletExtendedKey = extendedKey + // The third option for creating a wallet is the watch-only mode: + // Instead of providing the master root key directly, each individual + // account is passed as an extended public key only. Because of the + // hardened derivation path up to the account (depth 3), it is not + // possible to create a master root extended _public_ key. Therefore, an + // xpub must be derived and passed into the unlocker for _every_ account + // lnd expects. + case in.WatchOnly != nil && len(in.WatchOnly.Accounts) > 0: + initMsg.WatchOnlyAccounts = make( + map[waddrmgr.ScopedIndex]*hdkeychain.ExtendedKey, + len(in.WatchOnly.Accounts), + ) + + for _, acct := range in.WatchOnly.Accounts { + scopedIndex := waddrmgr.ScopedIndex{ + Scope: waddrmgr.KeyScope{ + Purpose: acct.Purpose, + Coin: acct.CoinType, + }, + Index: acct.Account, + } + acctKey, err := hdkeychain.NewKeyFromString(acct.Xpub) + if err != nil { + return nil, fmt.Errorf("error parsing xpub "+ + "%v: %v", acct.Xpub, err) + } + + // Just to make sure the user is doing the right thing, + // we expect the public key to be at derivation depth + // three (which is the account level) and the key not to + // contain any private key material. + if acctKey.Depth() != 3 { + return nil, fmt.Errorf("xpub must be at " + + "depth 3") + } + if acctKey.IsPrivate() { + return nil, fmt.Errorf("xpub is not really " + + "an xpub, contains private key") + } + + initMsg.WatchOnlyAccounts[scopedIndex] = acctKey + } + + // When importing a wallet from its extended public keys we + // don't know the birthday as that information is not encoded in + // that format. We therefore must set an arbitrary date to start + // rescanning at if the user doesn't provide an explicit value + // for it. Since lnd only uses SegWit addresses, we pick the + // date of the first block that contained SegWit transactions + // (481824). + initMsg.WatchOnlyBirthday = time.Date( + 2017, time.August, 24, 1, 57, 37, 0, time.UTC, + ) + if in.WatchOnly.MasterKeyBirthdayTimestamp != 0 { + initMsg.WatchOnlyBirthday = time.Unix( + int64(in.WatchOnly.MasterKeyBirthdayTimestamp), + 0, + ) + } + // No key material was set, no wallet can be created. default: return nil, fmt.Errorf("must either specify cipher seed " + From 1541b2ef1b4c6a22da9364389a95f22a5a6ef3aa Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 14 Oct 2021 15:42:56 +0200 Subject: [PATCH 10/14] multi: create and list all default internal accounts --- keychain/derivation.go | 15 +++++++ keychain/interface_test.go | 65 ++++++----------------------- lnrpc/walletrpc/walletkit_server.go | 25 +++++++++-- lnwallet/btcwallet/btcwallet.go | 53 ++++++++++++++++++++++- rpcserver.go | 7 ++++ 5 files changed, 108 insertions(+), 57 deletions(-) diff --git a/keychain/derivation.go b/keychain/derivation.go index 9a0dc390d..b10bb2733 100644 --- a/keychain/derivation.go +++ b/keychain/derivation.go @@ -105,6 +105,21 @@ const ( KeyFamilyTowerID KeyFamily = 9 ) +// VersionZeroKeyFamilies is a slice of all the known key families for first +// version of the key derivation schema defined in this package. +var VersionZeroKeyFamilies = []KeyFamily{ + KeyFamilyMultiSig, + KeyFamilyRevocationBase, + KeyFamilyHtlcBase, + KeyFamilyPaymentBase, + KeyFamilyDelayBase, + KeyFamilyRevocationRoot, + KeyFamilyNodeKey, + KeyFamilyStaticBackup, + KeyFamilyTowerSession, + KeyFamilyTowerID, +} + // KeyLocator is a two-tuple that can be used to derive *any* key that has ever // been used under the key derivation mechanisms described in this file. // Version 0 of our key derivation schema uses the following BIP43-like diff --git a/keychain/interface_test.go b/keychain/interface_test.go index 654ad22ce..3cd345e34 100644 --- a/keychain/interface_test.go +++ b/keychain/interface_test.go @@ -16,25 +16,11 @@ import ( "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/walletdb" "github.com/davecgh/go-spew/spew" + "github.com/stretchr/testify/require" _ "github.com/btcsuite/btcwallet/walletdb/bdb" // Required in order to create the default database. ) -// versionZeroKeyFamilies is a slice of all the known key families for first -// version of the key derivation schema defined in this package. -var versionZeroKeyFamilies = []KeyFamily{ - KeyFamilyMultiSig, - KeyFamilyRevocationBase, - KeyFamilyHtlcBase, - KeyFamilyPaymentBase, - KeyFamilyDelayBase, - KeyFamilyRevocationRoot, - KeyFamilyNodeKey, - KeyFamilyStaticBackup, - KeyFamilyTowerSession, - KeyFamilyTowerID, -} - var ( testHDSeed = chainhash.Hash{ 0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab, @@ -139,9 +125,7 @@ func TestKeyRingDerivation(t *testing.T) { cleanUp, wallet, err := createTestBtcWallet( CoinTypeBitcoin, ) - if err != nil { - t.Fatalf("unable to create wallet: %v", err) - } + require.NoError(t, err) keyRing := NewBtcWalletKeyRing(wallet, CoinTypeBitcoin) @@ -151,9 +135,7 @@ func TestKeyRingDerivation(t *testing.T) { cleanUp, wallet, err := createTestBtcWallet( CoinTypeLitecoin, ) - if err != nil { - t.Fatalf("unable to create wallet: %v", err) - } + require.NoError(t, err) keyRing := NewBtcWalletKeyRing(wallet, CoinTypeLitecoin) @@ -163,9 +145,7 @@ func TestKeyRingDerivation(t *testing.T) { cleanUp, wallet, err := createTestBtcWallet( CoinTypeTestnet, ) - if err != nil { - t.Fatalf("unable to create wallet: %v", err) - } + require.NoError(t, err) keyRing := NewBtcWalletKeyRing(wallet, CoinTypeTestnet) @@ -189,14 +169,11 @@ func TestKeyRingDerivation(t *testing.T) { success := t.Run(fmt.Sprintf("%v", keyRingName), func(t *testing.T) { // First, we'll ensure that we're able to derive keys // from each of the known key families. - for _, keyFam := range versionZeroKeyFamilies { + for _, keyFam := range VersionZeroKeyFamilies { // First, we'll ensure that we can derive the // *next* key in the keychain. keyDesc, err := keyRing.DeriveNextKey(keyFam) - if err != nil { - t.Fatalf("unable to derive next for "+ - "keyFam=%v: %v", keyFam, err) - } + require.NoError(t, err) assertEqualKeyLocator(t, KeyLocator{ Family: keyFam, @@ -212,10 +189,7 @@ func TestKeyRingDerivation(t *testing.T) { Index: 0, } firstKeyDesc, err := keyRing.DeriveKey(keyLoc) - if err != nil { - t.Fatalf("unable to derive first key for "+ - "keyFam=%v: %v", keyFam, err) - } + require.NoError(t, err) if !keyDesc.PubKey.IsEqual(firstKeyDesc.PubKey) { t.Fatalf("mismatched keys: expected %x, "+ "got %x", @@ -240,10 +214,7 @@ func TestKeyRingDerivation(t *testing.T) { Index: uint32(i), } keyDesc, err := keyRing.DeriveKey(keyLoc) - if err != nil { - t.Fatalf("unable to derive first key for "+ - "keyFam=%v: %v", keyFam, err) - } + require.NoError(t, err) // Ensure that the key locator matches // up as well. @@ -260,11 +231,7 @@ func TestKeyRingDerivation(t *testing.T) { Index: randKeyIndex, } keyDesc, err = keyRing.DeriveKey(keyLoc) - if err != nil { - t.Fatalf("unable to derive key_index=%v "+ - "for keyFam=%v: %v", - randKeyIndex, keyFam, err) - } + require.NoError(t, err) assertEqualKeyLocator( t, keyLoc, keyDesc.KeyLocator, ) @@ -293,9 +260,7 @@ func TestSecretKeyRingDerivation(t *testing.T) { cleanUp, wallet, err := createTestBtcWallet( CoinTypeBitcoin, ) - if err != nil { - t.Fatalf("unable to create wallet: %v", err) - } + require.NoError(t, err) keyRing := NewBtcWalletKeyRing(wallet, CoinTypeBitcoin) @@ -305,9 +270,7 @@ func TestSecretKeyRingDerivation(t *testing.T) { cleanUp, wallet, err := createTestBtcWallet( CoinTypeLitecoin, ) - if err != nil { - t.Fatalf("unable to create wallet: %v", err) - } + require.NoError(t, err) keyRing := NewBtcWalletKeyRing(wallet, CoinTypeLitecoin) @@ -317,9 +280,7 @@ func TestSecretKeyRingDerivation(t *testing.T) { cleanUp, wallet, err := createTestBtcWallet( CoinTypeTestnet, ) - if err != nil { - t.Fatalf("unable to create wallet: %v", err) - } + require.NoError(t, err) keyRing := NewBtcWalletKeyRing(wallet, CoinTypeTestnet) @@ -342,7 +303,7 @@ func TestSecretKeyRingDerivation(t *testing.T) { // For, each key family, we'll ensure that we're able // to obtain the private key of a randomly select child // index within the key family. - for _, keyFam := range versionZeroKeyFamilies { + for _, keyFam := range VersionZeroKeyFamilies { randKeyIndex := uint32(rand.Int31()) keyLoc := KeyLocator{ Family: keyFam, diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index 364b373d0..31796972f 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -310,6 +310,14 @@ func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispat return subServer, macPermissions, nil } +// internalScope returns the internal key scope. +func (w *WalletKit) internalScope() waddrmgr.KeyScope { + return waddrmgr.KeyScope{ + Purpose: keychain.BIP0043Purpose, + Coin: w.cfg.ChainParams.HDCoinType, + } +} + // ListUnspent returns useful information about each unspent output owned by the // wallet, as reported by the underlying `ListUnspentWitness`; the information // returned is: outpoint, amount in satoshis, address, address type, @@ -1245,7 +1253,9 @@ func (w *WalletKit) FinalizePsbt(_ context.Context, // marshalWalletAccount converts the properties of an account into its RPC // representation. -func marshalWalletAccount(account *waddrmgr.AccountProperties) (*Account, error) { +func marshalWalletAccount(internalScope waddrmgr.KeyScope, + account *waddrmgr.AccountProperties) (*Account, error) { + var addrType AddressType switch account.KeyScope { case waddrmgr.KeyScopeBIP0049Plus: @@ -1259,6 +1269,10 @@ func marshalWalletAccount(account *waddrmgr.AccountProperties) (*Account, error) switch *account.AddrSchema { case waddrmgr.KeyScopeBIP0049AddrSchema: addrType = AddressType_NESTED_WITNESS_PUBKEY_HASH + + case waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0049Plus]: + addrType = AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH + default: return nil, fmt.Errorf("unsupported address schema %v", *account.AddrSchema) @@ -1267,6 +1281,9 @@ func marshalWalletAccount(account *waddrmgr.AccountProperties) (*Account, error) case waddrmgr.KeyScopeBIP0084: addrType = AddressType_WITNESS_PUBKEY_HASH + case internalScope: + addrType = AddressType_WITNESS_PUBKEY_HASH + default: return nil, fmt.Errorf("account %v has unsupported "+ "key scope %v", account.AccountName, account.KeyScope) @@ -1340,7 +1357,9 @@ func (w *WalletKit) ListAccounts(ctx context.Context, continue } - rpcAccount, err := marshalWalletAccount(account) + rpcAccount, err := marshalWalletAccount( + w.internalScope(), account, + ) if err != nil { return nil, err } @@ -1431,7 +1450,7 @@ func (w *WalletKit) ImportAccount(ctx context.Context, return nil, err } - rpcAccount, err := marshalWalletAccount(accountProps) + rpcAccount, err := marshalWalletAccount(w.internalScope(), accountProps) if err != nil { return nil, err } diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index 3a2fb5f0a..86793d182 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -299,7 +299,7 @@ func (b *BtcWallet) Start() error { } } - _, err := b.wallet.Manager.FetchScopedKeyManager(b.chainKeyScope) + scope, err := b.wallet.Manager.FetchScopedKeyManager(b.chainKeyScope) if err != nil { // If the scope hasn't yet been created (it wouldn't been // loaded by default if it was), then we'll manually create the @@ -307,7 +307,7 @@ func (b *BtcWallet) Start() error { err := walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error { addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) - _, err := b.wallet.Manager.NewScopedKeyManager( + scope, err = b.wallet.Manager.NewScopedKeyManager( addrmgrNs, b.chainKeyScope, lightningAddrSchema, ) return err @@ -317,6 +317,43 @@ func (b *BtcWallet) Start() error { } } + // Now that the wallet is unlocked, we'll go ahead and make sure we + // create accounts for all the key families we're going to use. This + // will make it possible to list all the account/family xpubs in the + // wallet list RPC. + err = walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error { + addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) + + for _, keyFam := range keychain.VersionZeroKeyFamilies { + // If this is the multi-sig key family, then we can + // return early as this is the default account that's + // created. + if keyFam == keychain.KeyFamilyMultiSig { + continue + } + + // Otherwise, we'll check if the account already exists, + // if so, we can once again bail early. + _, err := scope.AccountName(addrmgrNs, uint32(keyFam)) + if err == nil { + continue + } + + // If we reach this point, then the account hasn't yet + // been created, so we'll need to create it before we + // can proceed. + err = scope.NewRawAccount(addrmgrNs, uint32(keyFam)) + if err != nil { + return err + } + } + + return nil + }) + if err != nil { + return err + } + // Establish an RPC connection in addition to starting the goroutines // in the underlying wallet. if err := b.chain.Start(); err != nil { @@ -556,6 +593,18 @@ func (b *BtcWallet) ListAccounts(name string, account := account res = append(res, &account.AccountProperties) } + + accounts, err = b.wallet.Accounts(waddrmgr.KeyScope{ + Purpose: keychain.BIP0043Purpose, + Coin: b.cfg.CoinType, + }) + if err != nil { + return nil, err + } + for _, account := range accounts.Accounts { + account := account + res = append(res, &account.AccountProperties) + } } return res, nil diff --git a/rpcserver.go b/rpcserver.go index 85346b544..db1826f04 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -3019,6 +3019,13 @@ func (r *rpcServer) WalletBalance(ctx context.Context, default: } + // There now also are the accounts for the internal channel + // related keys. We skip those as they'll never have any direct + // balance. + if account.KeyScope.Purpose == keychain.BIP0043Purpose { + continue + } + // Get total balance, from txs that have >= 0 confirmations. totalBal, err := r.server.cc.Wallet.ConfirmedBalance( 0, account.AccountName, From 78b700387c869038dd418241078c8f3e6b462b50 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 14 Oct 2021 15:42:58 +0200 Subject: [PATCH 11/14] cmd/lncli+walletrpc: add createwatchonly command --- cmd/lncli/cmd_walletunlocker.go | 125 +++++++++++++++++++++++++ cmd/lncli/main.go | 1 + lnrpc/walletrpc/walletkit_util.go | 76 +++++++++++++++ lnrpc/walletrpc/walletkit_util_test.go | 74 +++++++++++++++ 4 files changed, 276 insertions(+) create mode 100644 lnrpc/walletrpc/walletkit_util.go create mode 100644 lnrpc/walletrpc/walletkit_util_test.go diff --git a/cmd/lncli/cmd_walletunlocker.go b/cmd/lncli/cmd_walletunlocker.go index 7712cc095..2bc560482 100644 --- a/cmd/lncli/cmd_walletunlocker.go +++ b/cmd/lncli/cmd_walletunlocker.go @@ -10,8 +10,10 @@ import ( "strconv" "strings" + "github.com/lightninglabs/protobuf-hex-display/jsonpb" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/walletunlocker" "github.com/urfave/cli" ) @@ -638,6 +640,129 @@ func changePassword(ctx *cli.Context) error { return nil } +var createWatchOnlyCommand = cli.Command{ + Name: "createwatchonly", + Category: "Startup", + ArgsUsage: "accounts-json-file", + Usage: "Initialize a watch-only wallet after starting lnd for the " + + "first time.", + Description: ` + The create command is used to initialize an lnd wallet from scratch for + the very first time, in watch-only mode. Watch-only means, there will be + no private keys in lnd's wallet. This is only useful in combination with + a remote signer or when lnd should be used as an on-chain wallet with + PSBT interaction only. + + This is an interactive command that takes a JSON file as its first and + only argument. The JSON is in the same format as the output of the + 'lncli wallet accounts list' command. This makes it easy to initialize + the remote signer with the seed, then export the extended public account + keys (xpubs) to import the watch-only wallet. + + Example JSON (non-mandatory or ignored fields are omitted): + { + "accounts": [ + { + "extended_public_key": "upub5Eep7....", + "derivation_path": "m/49'/0'/0'" + }, + { + "extended_public_key": "vpub5ZU1PH...", + "derivation_path": "m/84'/0'/0'" + }, + { + "extended_public_key": "tpubDDXFH...", + "derivation_path": "m/1017'/1'/0'" + }, + ... + { + "extended_public_key": "tpubDDXFH...", + "derivation_path": "m/1017'/1'/9'" + } + ] + } + + There must be an account for each of the existing key families that lnd + uses internally (currently 0-9, see keychain/derivation.go). + + Read the documentation under docs/remote-signing.md for more information + on how to set up a remote signing node over RPC. + `, + Action: actionDecorator(createWatchOnly), +} + +func createWatchOnly(ctx *cli.Context) error { + ctxc := getContext() + client, cleanUp := getWalletUnlockerClient(ctx) + defer cleanUp() + + if ctx.NArg() != 1 { + return cli.ShowCommandHelp(ctx, "createwatchonly") + } + + jsonFile := lncfg.CleanAndExpandPath(ctx.Args().First()) + jsonBytes, err := ioutil.ReadFile(jsonFile) + if err != nil { + return fmt.Errorf("error reading JSON from file %v: %v", + jsonFile, err) + } + + jsonAccts := &walletrpc.ListAccountsResponse{} + err = jsonpb.Unmarshal(bytes.NewReader(jsonBytes), jsonAccts) + if err != nil { + return fmt.Errorf("error parsing JSON: %v", err) + } + if len(jsonAccts.Accounts) == 0 { + return fmt.Errorf("cannot import empty account list") + } + + walletPassword, err := capturePassword( + "Input wallet password: ", false, + walletunlocker.ValidatePassword, + ) + if err != nil { + return err + } + + extendedRootKeyBirthday, err := askBirthdayTimestamp() + if err != nil { + return err + } + + recoveryWindow, err := askRecoveryWindow() + if err != nil { + return err + } + + rpcAccounts, err := walletrpc.AccountsToWatchOnly(jsonAccts.Accounts) + if err != nil { + return err + } + + rpcResp := &lnrpc.WatchOnly{ + MasterKeyBirthdayTimestamp: extendedRootKeyBirthday, + Accounts: rpcAccounts, + } + + // We assume that all accounts were exported from the same master root + // key. So if one is set, we just forward that. If other accounts should + // be watched later on, they should be imported into the watch-only + // node, that then also forwards the import request to the remote + // signer. + for _, acct := range jsonAccts.Accounts { + if len(acct.MasterKeyFingerprint) > 0 { + rpcResp.MasterKeyFingerprint = acct.MasterKeyFingerprint + } + } + + _, err = client.InitWallet(ctxc, &lnrpc.InitWalletRequest{ + WalletPassword: walletPassword, + WatchOnly: rpcResp, + RecoveryWindow: recoveryWindow, + }) + return err +} + // storeOrPrintAdminMac either stores the admin macaroon to a file specified or // prints it to standard out, depending on the user flags set. func storeOrPrintAdminMac(ctx *cli.Context, adminMac []byte) error { diff --git a/cmd/lncli/main.go b/cmd/lncli/main.go index 808b5d7eb..28c3c9bb6 100644 --- a/cmd/lncli/main.go +++ b/cmd/lncli/main.go @@ -327,6 +327,7 @@ func main() { } app.Commands = []cli.Command{ createCommand, + createWatchOnlyCommand, unlockCommand, changePasswordCommand, newAddressCommand, diff --git a/lnrpc/walletrpc/walletkit_util.go b/lnrpc/walletrpc/walletkit_util.go new file mode 100644 index 000000000..e840a9660 --- /dev/null +++ b/lnrpc/walletrpc/walletkit_util.go @@ -0,0 +1,76 @@ +package walletrpc + +import ( + "fmt" + "strconv" + "strings" + + "github.com/lightningnetwork/lnd/lnrpc" +) + +// AccountsToWatchOnly converts the accounts returned by the walletkit's +// ListAccounts RPC into a struct that can be used to create a watch-only +// wallet. +func AccountsToWatchOnly(exported []*Account) ([]*lnrpc.WatchOnlyAccount, + error) { + + result := make([]*lnrpc.WatchOnlyAccount, len(exported)) + for idx, acct := range exported { + parsedPath, err := parseDerivationPath(acct.DerivationPath) + if err != nil { + return nil, fmt.Errorf("error parsing derivation path "+ + "of account %d: %v", idx, err) + } + if len(parsedPath) < 3 { + return nil, fmt.Errorf("derivation path of account %d "+ + "has invalid derivation path, need at least "+ + "path of depth 3, instead has depth %d", idx, + len(parsedPath)) + } + + result[idx] = &lnrpc.WatchOnlyAccount{ + Purpose: parsedPath[0], + CoinType: parsedPath[1], + Account: parsedPath[2], + Xpub: acct.ExtendedPublicKey, + } + } + + return result, nil +} + +// parseDerivationPath parses a path in the form of m/x'/y'/z'/a/b into a slice +// of [x, y, z, a, b], meaning that the apostrophe is ignored and 2^31 is _not_ +// added to the numbers. +func parseDerivationPath(path string) ([]uint32, error) { + path = strings.TrimSpace(path) + if len(path) == 0 { + return nil, fmt.Errorf("path cannot be empty") + } + if !strings.HasPrefix(path, "m/") { + return nil, fmt.Errorf("path must start with m/") + } + + // Just the root key, no path was provided. This is valid but not useful + // in most cases. + rest := strings.ReplaceAll(path, "m/", "") + if rest == "" { + return []uint32{}, nil + } + + parts := strings.Split(rest, "/") + indices := make([]uint32, len(parts)) + for i := 0; i < len(parts); i++ { + part := parts[i] + if strings.Contains(parts[i], "'") { + part = strings.TrimRight(parts[i], "'") + } + parsed, err := strconv.ParseInt(part, 10, 32) + if err != nil { + return nil, fmt.Errorf("could not parse part \"%s\": "+ + "%v", part, err) + } + indices[i] = uint32(parsed) + } + return indices, nil +} diff --git a/lnrpc/walletrpc/walletkit_util_test.go b/lnrpc/walletrpc/walletkit_util_test.go new file mode 100644 index 000000000..fdc8d2e0a --- /dev/null +++ b/lnrpc/walletrpc/walletkit_util_test.go @@ -0,0 +1,74 @@ +package walletrpc + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseDerivationPath(t *testing.T) { + testCases := []struct { + name string + path string + expectedErr string + expectedResult []uint32 + }{{ + name: "empty path", + path: "", + expectedErr: "path cannot be empty", + }, { + name: "just whitespace", + path: " \n\t\r", + expectedErr: "path cannot be empty", + }, { + name: "incorrect prefix", + path: "0/0", + expectedErr: "path must start with m/", + }, { + name: "invalid number", + path: "m/a'/0'", + expectedErr: "could not parse part \"a\": strconv.ParseInt", + }, { + name: "double slash", + path: "m/0'//", + expectedErr: "could not parse part \"\": strconv.ParseInt", + }, { + name: "number too large", + path: "m/99999999999999", + expectedErr: "could not parse part \"99999999999999\": strconv", + }, { + name: "empty path", + path: "m/", + expectedResult: []uint32{}, + }, { + name: "mixed path", + path: "m/0'/1'/2'/3/4/5/6'/7'", + expectedResult: []uint32{0, 1, 2, 3, 4, 5, 6, 7}, + }, { + name: "short path", + path: "m/0'", + expectedResult: []uint32{0}, + }, { + name: "plain path", + path: "m/0/1/2", + expectedResult: []uint32{0, 1, 2}, + }} + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(tt *testing.T) { + result, err := parseDerivationPath(tc.path) + + if tc.expectedErr != "" { + require.Error(tt, err) + require.Contains( + tt, err.Error(), tc.expectedErr, + ) + } else { + require.NoError(tt, err) + require.Equal(tt, tc.expectedResult, result) + } + }) + } +} From 36dc4b3abf7f35fff705a47ca062a9c09e46b926 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 14 Oct 2021 15:42:59 +0200 Subject: [PATCH 12/14] itest: refactor tests to be re-used --- lntest/itest/lnd_onchain_test.go | 22 +++++++++++++++------- lntest/itest/lnd_open_channel_test.go | 20 ++++++++++++++------ lntest/itest/lnd_payment_test.go | 23 +++++++++++++++-------- lntest/itest/lnd_psbt_test.go | 14 +++++++++++--- lntest/itest/lnd_signer_test.go | 20 ++++++++++++++------ lntest/itest/lnd_wallet_import_test.go | 12 +++++++++--- 6 files changed, 78 insertions(+), 33 deletions(-) diff --git a/lntest/itest/lnd_onchain_test.go b/lntest/itest/lnd_onchain_test.go index b59eeb202..da4af2fa0 100644 --- a/lntest/itest/lnd_onchain_test.go +++ b/lntest/itest/lnd_onchain_test.go @@ -22,6 +22,14 @@ import ( // // TODO(wilmer): Add RBF case once btcd supports it. func testCPFP(net *lntest.NetworkHarness, t *harnessTest) { + runCPFP(net, t, net.Alice, net.Bob) +} + +// runCPFP ensures that the daemon can bump an unconfirmed transaction's fee +// rate by broadcasting a Child-Pays-For-Parent (CPFP) transaction. +func runCPFP(net *lntest.NetworkHarness, t *harnessTest, + alice, bob *lntest.HarnessNode) { + // Skip this test for neutrino, as it's not aware of mempool // transactions. if net.BackendCfg.Name() == lntest.NeutrinoBackendName { @@ -31,14 +39,14 @@ func testCPFP(net *lntest.NetworkHarness, t *harnessTest) { // We'll start the test by sending Alice some coins, which she'll use to // send to Bob. ctxb := context.Background() - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, net.Alice) + net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, alice) // Create an address for Bob to send the coins to. addrReq := &lnrpc.NewAddressRequest{ Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, } ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - resp, err := net.Bob.NewAddress(ctxt, addrReq) + resp, err := bob.NewAddress(ctxt, addrReq) if err != nil { t.Fatalf("unable to get new address for bob: %v", err) } @@ -50,7 +58,7 @@ func testCPFP(net *lntest.NetworkHarness, t *harnessTest) { Amount: btcutil.SatoshiPerBitcoin, } ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if _, err = net.Alice.SendCoins(ctxt, sendReq); err != nil { + if _, err = alice.SendCoins(ctxt, sendReq); err != nil { t.Fatalf("unable to send coins to bob: %v", err) } @@ -88,7 +96,7 @@ func testCPFP(net *lntest.NetworkHarness, t *harnessTest) { TxidBytes: txid[:], OutputIndex: uint32(bobOutputIdx), } - assertWalletUnspent(t, net.Bob, op) + assertWalletUnspent(t, bob, op) // We'll attempt to bump the fee of this transaction by performing a // CPFP from Alice's point of view. @@ -99,7 +107,7 @@ func testCPFP(net *lntest.NetworkHarness, t *harnessTest) { ), } ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = net.Bob.WalletKitClient.BumpFee(ctxt, bumpFeeReq) + _, err = bob.WalletKitClient.BumpFee(ctxt, bumpFeeReq) if err != nil { t.Fatalf("unable to bump fee: %v", err) } @@ -115,7 +123,7 @@ func testCPFP(net *lntest.NetworkHarness, t *harnessTest) { // UtxoSweeper. We'll ensure it's using the fee rate specified. pendingSweepsReq := &walletrpc.PendingSweepsRequest{} ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - pendingSweepsResp, err := net.Bob.WalletKitClient.PendingSweeps( + pendingSweepsResp, err := bob.WalletKitClient.PendingSweeps( ctxt, pendingSweepsReq, ) if err != nil { @@ -146,7 +154,7 @@ func testCPFP(net *lntest.NetworkHarness, t *harnessTest) { err = wait.NoError(func() error { req := &walletrpc.PendingSweepsRequest{} ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - resp, err := net.Bob.WalletKitClient.PendingSweeps(ctxt, req) + resp, err := bob.WalletKitClient.PendingSweeps(ctxt, req) if err != nil { return fmt.Errorf("unable to retrieve bob's pending "+ "sweeps: %v", err) diff --git a/lntest/itest/lnd_open_channel_test.go b/lntest/itest/lnd_open_channel_test.go index c7be76adf..2864558c2 100644 --- a/lntest/itest/lnd_open_channel_test.go +++ b/lntest/itest/lnd_open_channel_test.go @@ -259,6 +259,15 @@ func testOpenChannelAfterReorg(net *lntest.NetworkHarness, t *harnessTest) { // and ensures that if a node is subscribed to channel updates they will be // received correctly for both cooperative and force closed channels. func testBasicChannelCreationAndUpdates(net *lntest.NetworkHarness, t *harnessTest) { + runBasicChannelCreationAndUpdates(net, t, net.Alice, net.Bob) +} + +// runBasicChannelCreationAndUpdates tests multiple channel opening and closing, +// and ensures that if a node is subscribed to channel updates they will be +// received correctly for both cooperative and force closed channels. +func runBasicChannelCreationAndUpdates(net *lntest.NetworkHarness, + t *harnessTest, alice, bob *lntest.HarnessNode) { + ctxb := context.Background() const ( numChannels = 2 @@ -266,10 +275,10 @@ func testBasicChannelCreationAndUpdates(net *lntest.NetworkHarness, t *harnessTe ) // Subscribe Bob and Alice to channel event notifications. - bobChanSub := subscribeChannelNotifications(ctxb, t, net.Bob) + bobChanSub := subscribeChannelNotifications(ctxb, t, bob) defer close(bobChanSub.quit) - aliceChanSub := subscribeChannelNotifications(ctxb, t, net.Alice) + aliceChanSub := subscribeChannelNotifications(ctxb, t, alice) defer close(aliceChanSub.quit) // Open the channels between Alice and Bob, asserting that the channels @@ -277,8 +286,7 @@ func testBasicChannelCreationAndUpdates(net *lntest.NetworkHarness, t *harnessTe chanPoints := make([]*lnrpc.ChannelPoint, numChannels) for i := 0; i < numChannels; i++ { chanPoints[i] = openChannelAndAssert( - t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ + t, net, alice, bob, lntest.OpenChannelParams{ Amt: amount, }, ) @@ -346,9 +354,9 @@ func testBasicChannelCreationAndUpdates(net *lntest.NetworkHarness, t *harnessTe for i, chanPoint := range chanPoints { // Force close the first of the two channels. force := i%2 == 0 - closeChannelAndAssert(t, net, net.Alice, chanPoint, force) + closeChannelAndAssert(t, net, alice, chanPoint, force) if force { - cleanupForceClose(t, net, net.Alice, chanPoint) + cleanupForceClose(t, net, alice, chanPoint) } } diff --git a/lntest/itest/lnd_payment_test.go b/lntest/itest/lnd_payment_test.go index 5cf3330c6..aef6158c0 100644 --- a/lntest/itest/lnd_payment_test.go +++ b/lntest/itest/lnd_payment_test.go @@ -243,6 +243,13 @@ func testPaymentFollowingChannelOpen(net *lntest.NetworkHarness, t *harnessTest) // testAsyncPayments tests the performance of the async payments. func testAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { + runAsyncPayments(net, t, net.Alice, net.Bob) +} + +// runAsyncPayments tests the performance of the async payments. +func runAsyncPayments(net *lntest.NetworkHarness, t *harnessTest, alice, + bob *lntest.HarnessNode) { + ctxb := context.Background() const ( @@ -254,13 +261,13 @@ func testAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { // Alice should send all money from her side to Bob. channelCapacity := btcutil.Amount(paymentAmt * 2000) chanPoint := openChannelAndAssert( - t, net, net.Alice, net.Bob, + t, net, alice, bob, lntest.OpenChannelParams{ Amt: channelCapacity, }, ) - info, err := getChanInfo(net.Alice) + info, err := getChanInfo(alice) if err != nil { t.Fatalf("unable to get alice channel info: %v", err) } @@ -278,7 +285,7 @@ func testAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { // With the channel open, we'll create invoices for Bob that Alice // will pay to in order to advance the state of the channel. bobPayReqs, _, _, err := createPayReqs( - net.Bob, paymentAmt, numInvoices, + bob, paymentAmt, numInvoices, ) if err != nil { t.Fatalf("unable to create pay reqs: %v", err) @@ -286,7 +293,7 @@ func testAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { // Wait for Alice to receive the channel edge from the funding manager. ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + err = alice.WaitForNetworkChannelOpen(ctxt, chanPoint) if err != nil { t.Fatalf("alice didn't see the alice->bob channel before "+ "timeout: %v", err) @@ -301,7 +308,7 @@ func testAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { payReq := bobPayReqs[i] go func() { ctxt, _ = context.WithTimeout(ctxb, lntest.AsyncBenchmarkTimeout) - stream, err := net.Alice.RouterClient.SendPaymentV2( + stream, err := alice.RouterClient.SendPaymentV2( ctxt, &routerrpc.SendPaymentRequest{ PaymentRequest: payReq, @@ -344,7 +351,7 @@ func testAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { // htlcs listed and has correct balances. This is needed due to the fact // that we now pipeline the settles. err = wait.Predicate(func() bool { - aliceChan, err := getChanInfo(net.Alice) + aliceChan, err := getChanInfo(alice) if err != nil { return false } @@ -366,7 +373,7 @@ func testAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { // Wait for Bob to receive revocation from Alice. err = wait.NoError(func() error { - bobChan, err := getChanInfo(net.Bob) + bobChan, err := getChanInfo(bob) if err != nil { t.Fatalf("unable to get bob's channel info: %v", err) } @@ -399,7 +406,7 @@ func testAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { // Finally, immediately close the channel. This function will also // block until the channel is closed and will additionally assert the // relevant channel closing post conditions. - closeChannelAndAssert(t, net, net.Alice, chanPoint, false) + closeChannelAndAssert(t, net, alice, chanPoint, false) } // testBidirectionalAsyncPayments tests that nodes are able to send the diff --git a/lntest/itest/lnd_psbt_test.go b/lntest/itest/lnd_psbt_test.go index 478432395..afdef552a 100644 --- a/lntest/itest/lnd_psbt_test.go +++ b/lntest/itest/lnd_psbt_test.go @@ -19,9 +19,6 @@ import ( // by using a Partially Signed Bitcoin Transaction that funds the channel // multisig funding output. func testPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - const chanSize = funding.MaxBtcFundingAmount - // First, we'll create two new nodes that we'll use to open channels // between for this test. Dave gets some coins that will be used to // fund the PSBT, just to make sure that Carol has an empty wallet. @@ -31,6 +28,17 @@ func testPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest) { dave := net.NewNode(t.t, "dave", nil) defer shutdownAndAssert(net, t, dave) + runPsbtChanFunding(net, t, carol, dave) +} + +// runPsbtChanFunding makes sure a channel can be opened between carol and dave +// by using a Partially Signed Bitcoin Transaction that funds the channel +// multisig funding output. +func runPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest, carol, + dave *lntest.HarnessNode) { + + ctxb := context.Background() + const chanSize = funding.MaxBtcFundingAmount net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, dave) // Before we start the test, we'll ensure both sides are connected so diff --git a/lntest/itest/lnd_signer_test.go b/lntest/itest/lnd_signer_test.go index 02402145a..9630b9fdd 100644 --- a/lntest/itest/lnd_signer_test.go +++ b/lntest/itest/lnd_signer_test.go @@ -16,6 +16,14 @@ import ( // the node's pubkey and a customized public key to check the validity of the // result. func testDeriveSharedKey(net *lntest.NetworkHarness, t *harnessTest) { + runDeriveSharedKey(t, net.Alice) +} + +// runDeriveSharedKey checks the ECDH performed by the endpoint +// DeriveSharedKey. It creates an ephemeral private key, performing an ECDH with +// the node's pubkey and a customized public key to check the validity of the +// result. +func runDeriveSharedKey(t *harnessTest, alice *lntest.HarnessNode) { ctxb := context.Background() // Create an ephemeral key, extracts its public key, and make a @@ -32,7 +40,7 @@ func testDeriveSharedKey(net *lntest.NetworkHarness, t *harnessTest) { req *signrpc.SharedKeyRequest) { ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - resp, err := net.Alice.SignerClient.DeriveSharedKey(ctxt, req) + resp, err := alice.SignerClient.DeriveSharedKey(ctxt, req) require.NoError(t.t, err, "calling DeriveSharedKey failed") sharedKey, _ := privKeyECDH.ECDH(pub) @@ -42,13 +50,13 @@ func testDeriveSharedKey(net *lntest.NetworkHarness, t *harnessTest) { ) } - nodePub, err := btcec.ParsePubKey(net.Alice.PubKey[:], btcec.S256()) + nodePub, err := btcec.ParsePubKey(alice.PubKey[:], btcec.S256()) require.NoError(t.t, err, "failed to parse node pubkey") customizedKeyFamily := int32(keychain.KeyFamilyMultiSig) customizedIndex := int32(1) customizedPub, err := deriveCustomizedKey( - ctxb, net.Alice, customizedKeyFamily, customizedIndex, + ctxb, alice, customizedKeyFamily, customizedIndex, ) require.NoError(t.t, err, "failed to create customized pubkey") @@ -85,7 +93,7 @@ func testDeriveSharedKey(net *lntest.NetworkHarness, t *harnessTest) { req = &signrpc.SharedKeyRequest{ EphemeralPubkey: ephemeralPubBytes, KeyDesc: &signrpc.KeyDescriptor{ - RawKeyBytes: net.Alice.PubKey[:], + RawKeyBytes: alice.PubKey[:], KeyLoc: &signrpc.KeyLocator{ KeyFamily: int32(keychain.KeyFamilyNodeKey), }, @@ -135,7 +143,7 @@ func testDeriveSharedKey(net *lntest.NetworkHarness, t *harnessTest) { // params, the expected error is returned. assertErrorMatch := func(match string, req *signrpc.SharedKeyRequest) { ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - _, err := net.Alice.SignerClient.DeriveSharedKey(ctxt, req) + _, err := alice.SignerClient.DeriveSharedKey(ctxt, req) require.Error(t.t, err, "expected to have an error") require.Contains( t.t, err.Error(), match, "error failed to match", @@ -163,7 +171,7 @@ func testDeriveSharedKey(net *lntest.NetworkHarness, t *harnessTest) { req = &signrpc.SharedKeyRequest{ EphemeralPubkey: ephemeralPubBytes, KeyDesc: &signrpc.KeyDescriptor{ - RawKeyBytes: net.Alice.PubKey[:], + RawKeyBytes: alice.PubKey[:], }, } assertErrorMatch("key_desc.key_loc must also be set", req) diff --git a/lntest/itest/lnd_wallet_import_test.go b/lntest/itest/lnd_wallet_import_test.go index 1a62c7da4..db898282d 100644 --- a/lntest/itest/lnd_wallet_import_test.go +++ b/lntest/itest/lnd_wallet_import_test.go @@ -594,9 +594,6 @@ func testWalletImportAccount(net *lntest.NetworkHarness, t *harnessTest) { func testWalletImportAccountScenario(net *lntest.NetworkHarness, t *harnessTest, addrType walletrpc.AddressType) { - ctxb := context.Background() - const utxoAmt int64 = btcutil.SatoshiPerBitcoin - // We'll start our test by having two nodes, Carol and Dave. Carol's // default wallet account will be imported into Dave's node. carol := net.NewNode(t.t, "carol", nil) @@ -605,6 +602,15 @@ func testWalletImportAccountScenario(net *lntest.NetworkHarness, t *harnessTest, dave := net.NewNode(t.t, "dave", nil) defer shutdownAndAssert(net, t, dave) + runWalletImportAccountScenario(net, t, addrType, carol, dave) +} + +func runWalletImportAccountScenario(net *lntest.NetworkHarness, t *harnessTest, + addrType walletrpc.AddressType, carol, dave *lntest.HarnessNode) { + + ctxb := context.Background() + const utxoAmt int64 = btcutil.SatoshiPerBitcoin + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) defer cancel() listReq := &walletrpc.ListAccountsRequest{ From a6ea019f56d160c1dbeccfa1fc439f6ede38e17f Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 14 Oct 2021 15:43:00 +0200 Subject: [PATCH 13/14] lntest: add itest for remote signing --- lntest/harness.go | 33 +++ lntest/itest/lnd_remote_signer_test.go | 266 +++++++++++++++++++++++++ lntest/itest/lnd_test_list_on_test.go | 4 + 3 files changed, 303 insertions(+) create mode 100644 lntest/itest/lnd_remote_signer_test.go diff --git a/lntest/harness.go b/lntest/harness.go index 1c8386d60..3cba2a013 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -427,6 +427,39 @@ func (n *NetworkHarness) newNodeWithSeed(name string, extraArgs []string, return node, genSeedResp.CipherSeedMnemonic, response.AdminMacaroon, nil } +func (n *NetworkHarness) NewNodeRemoteSigner(name string, extraArgs []string, + password []byte, watchOnly *lnrpc.WatchOnly) (*HarnessNode, error) { + + node, err := n.newNode( + name, extraArgs, true, password, n.dbBackend, true, + ) + if err != nil { + return nil, err + } + + ctxb := context.Background() + + // With the seed created, construct the init request to the node, + // including the newly generated seed. + initReq := &lnrpc.InitWalletRequest{ + WalletPassword: password, + WatchOnly: watchOnly, + } + + // Pass the init request via rpc to finish unlocking the node. This will + // also initialize the macaroon-authenticated LightningClient. + _, err = node.Init(ctxb, initReq) + if err != nil { + return nil, err + } + + // With the node started, we can now record its public key within the + // global mapping. + n.RegisterNode(node) + + return node, nil +} + // RestoreNodeWithSeed fully initializes a HarnessNode using a chosen mnemonic, // password, recovery window, and optionally a set of static channel backups. // After providing the initialization request to unlock the node, this method diff --git a/lntest/itest/lnd_remote_signer_test.go b/lntest/itest/lnd_remote_signer_test.go new file mode 100644 index 000000000..b1bae04cb --- /dev/null +++ b/lntest/itest/lnd_remote_signer_test.go @@ -0,0 +1,266 @@ +package itest + +import ( + "context" + "fmt" + "testing" + + "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "github.com/lightningnetwork/lnd/lntest" + "github.com/stretchr/testify/require" +) + +var ( + rootKey = "tprv8ZgxMBicQKsPe6jS4vDm2n7s42Q6MpvghUQqMmSKG7bTZvGKtjrcU3" + + "PGzMNG37yzxywrcdvgkwrr8eYXJmbwdvUNVT4Ucv7ris4jvA7BUmg" + + nodePubKey = "033f55d436d4f7d24aeffb1b976647380f22ebf9e74390e8c76dcff" + + "9fea0093b7a" + + accounts = []*lnrpc.WatchOnlyAccount{{ + Purpose: waddrmgr.KeyScopeBIP0049Plus.Purpose, + // We always use the mainnet coin type for our BIP49/84 + // addresses! + CoinType: 0, + Account: 0, + Xpub: "tpubDDXEYWvGCTytEF6hBog9p4qr2QBUvJhh4P2wM4qHHv9N489khk" + + "QoGkBXDVoquuiyBf8SKBwrYseYdtq9j2v2nttPpE8qbuW3sE2MCk" + + "FPhTq", + }, { + Purpose: waddrmgr.KeyScopeBIP0084.Purpose, + // We always use the mainnet coin type for our BIP49/84 + // addresses! + CoinType: 0, + Account: 0, + Xpub: "tpubDDWAWrSLRSFrG1KdqXMQQyTKYGSKLKaY7gxpvK7RdV3e3Dkhvu" + + "W2GgsFvsPN4RGmuoYtUgZ1LHZE8oftz7T4mzc1BxGt5rt8zJcVQi" + + "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", + }} +) + +// testRemoteSigner tests that a watch-only wallet can use a remote signing +// wallet to perform any signing or ECDH operations. +func testRemoteSigner(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + subTests := []struct { + name string + randomSeed bool + sendCoins bool + fn func(tt *harnessTest, wo, carol *lntest.HarnessNode) + }{{ + name: "random seed", + randomSeed: true, + fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { + // Nothing more to test here. + }, + }, { + name: "account import", + fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { + runWalletImportAccountScenario( + net, tt, + walletrpc.AddressType_WITNESS_PUBKEY_HASH, + carol, wo, + ) + }, + }, { + name: "basic channel open close", + sendCoins: true, + fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { + runBasicChannelCreationAndUpdates( + net, tt, wo, carol, + ) + }, + }, { + name: "async payments", + sendCoins: true, + fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { + runAsyncPayments(net, tt, wo, carol) + }, + }, { + name: "shared key", + fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { + runDeriveSharedKey(tt, wo) + }, + }, { + name: "cpfp", + sendCoins: true, + fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { + runCPFP(net, tt, wo, carol) + }, + }, { + name: "psbt", + fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) { + runPsbtChanFunding(net, tt, carol, wo) + }, + }} + + for _, st := range subTests { + subTest := st + + // Signer is our signing node and has the wallet with the full + // master private key. We test that we can create the watch-only + // wallet from the exported accounts but also from a static key + // to make sure the derivation of the account public keys is + // correct in both cases. + password := []byte("itestpassword") + var ( + signerNodePubKey = nodePubKey + watchOnlyAccounts = accounts + signer *lntest.HarnessNode + err error + ) + if !subTest.randomSeed { + signer, err = net.RestoreNodeWithSeed( + "Signer", nil, password, nil, rootKey, 0, nil, + ) + require.NoError(t.t, err) + } else { + signer = net.NewNode(t.t, "Signer", nil) + signerNodePubKey = signer.PubKeyStr + + rpcAccts, err := signer.WalletKitClient.ListAccounts( + ctxb, &walletrpc.ListAccountsRequest{}, + ) + require.NoError(t.t, err) + + watchOnlyAccounts, err = walletrpc.AccountsToWatchOnly( + rpcAccts.Accounts, + ) + require.NoError(t.t, err) + } + + // WatchOnly is the node that has a watch-only wallet and uses + // the Signer node for any operation that requires access to + // private keys. + watchOnly, err := net.NewNodeRemoteSigner( + "WatchOnly", []string{ + "--remotesigner.enable", + fmt.Sprintf( + "--remotesigner.rpchost=localhost:%d", + signer.Cfg.RPCPort, + ), + fmt.Sprintf( + "--remotesigner.tlscertpath=%s", + signer.Cfg.TLSCertPath, + ), + fmt.Sprintf( + "--remotesigner.macaroonpath=%s", + signer.Cfg.AdminMacPath, + ), + }, password, &lnrpc.WatchOnly{ + MasterKeyBirthdayTimestamp: 0, + MasterKeyFingerprint: nil, + Accounts: watchOnlyAccounts, + }, + ) + require.NoError(t.t, err) + + resp, err := watchOnly.GetInfo(ctxb, &lnrpc.GetInfoRequest{}) + require.NoError(t.t, err) + + require.Equal(t.t, signerNodePubKey, resp.IdentityPubkey) + + if subTest.sendCoins { + net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, watchOnly) + assertAccountBalance( + t.t, signer, "default", + btcutil.SatoshiPerBitcoin, 0, + ) + assertAccountBalance( + t.t, watchOnly, "default", + btcutil.SatoshiPerBitcoin, 0, + ) + } + + carol := net.NewNode(t.t, "carol", nil) + net.EnsureConnected(t.t, watchOnly, carol) + + success := t.t.Run(subTest.name, func(tt *testing.T) { + ht := newHarnessTest(tt, net) + subTest.fn(ht, watchOnly, carol) + }) + + shutdownAndAssert(net, t, carol) + shutdownAndAssert(net, t, watchOnly) + shutdownAndAssert(net, t, signer) + + if !success { + return + } + } +} diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index cd9780502..b5067433d 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -355,4 +355,8 @@ var allTestCases = []*testCase{ name: "wipe forwarding packages", test: testWipeForwardingPackages, }, + { + name: "remote signer", + test: testRemoteSigner, + }, } From d43854aa34ca0c2d0dfa12b06f299def39b512fb Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 14 Oct 2021 15:43:02 +0200 Subject: [PATCH 14/14] docs: add remote signing documentation and release notes --- docs/release-notes/release-notes-0.14.0.md | 7 + docs/remote-signing.md | 281 +++++++++++++++++++++ 2 files changed, 288 insertions(+) create mode 100644 docs/remote-signing.md diff --git a/docs/release-notes/release-notes-0.14.0.md b/docs/release-notes/release-notes-0.14.0.md index 3f0d06fd7..6bb9c59b5 100644 --- a/docs/release-notes/release-notes-0.14.0.md +++ b/docs/release-notes/release-notes-0.14.0.md @@ -164,6 +164,13 @@ documentation](../psbt.md#use-the-batchopenchannel-rpc-for-safe-batch-channel-fu take longer to start a `lnd` node when running in `simnet` or `regtest`, something developers need to watch out from this release. +### Remote signing + +It is now possible to delegate any operation that needs access to private keys +to a [remote signer that serves signing requests over +RPC](https://github.com/lightningnetwork/lnd/pull/5689). More information can be +found [in the new remote signing document](../remote-signing.md). + ## Security * The release signature verification script [was overhauled to fix some possible diff --git a/docs/remote-signing.md b/docs/remote-signing.md new file mode 100644 index 000000000..9e4561526 --- /dev/null +++ b/docs/remote-signing.md @@ -0,0 +1,281 @@ +# Remote signing + +Remote signing refers to an operating mode of `lnd` in which the wallet is +segregated into two parts, each running within its own instance of `lnd`. One +instance is running in watch-only mode which means it only has **public** +keys in its wallet. The second instance (in this document referred to as +"signer" or "remote signer" instance) has the same keys in its wallet, including +the **private** keys. + +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 +connection and the outbound connection to `bitcoind` (or `btcd` or `neutrino`). +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 +outside. + +An example setup could look like: + +```text + xxxxxxxxx + xxxxxxxxx xxxx +xxx xx +x LN p2p network xx +x x +xxx xx + xxxxx xxxxxxxx + xxx + ^ +----------------------------------+ + | p2p traffic | firewalled/offline network zone | + | | | + v | | + +----------------+ gRPC | +----------------+ | + | watch-only lnd +--------------+-->| full seed lnd | | + +-------+--------+ | +------+---------+ | + | | | | + +-------v--------+ | +------v---------+ | + | 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 + +In this example we are going to set up two nodes, the "signer" that has the full +seed and private keys and the "watch-only" node that only has public keys. + +### The "signer" node + +The node "signer" is the hardened node that contains the private key material +and is not connected to the internet or LN P2P network at all. Ideally only a +single RPC based connection (that can be firewalled off specifically) can be +opened to this node from the host on which the node "watch-only" is running. + +Recommended entries in `lnd.conf`: + +```text +# No special configuration required other than basic "hardening" parameters to +# make sure no connections to the internet are opened. + +[Application Options] +# Don't listen on the p2p port. +nolisten=true + +# Don't reach out to the bootstrap nodes, we don't need a synced graph. +nobootstrap=true + +# Just an example, this is the port that needs to be opened in the firewall and +# reachable from the node "watch-only". +rpclisten=10019 +``` + +After successfully starting up "signer", the following command can be run to +export the `xpub`s of the wallet: + +```shell +signer> ⛰ lncli wallet accounts list > accounts-signer.json +``` + +That `accounts-signer.json` file has to be copied to the machine on which +"watch-only" will be running. It contains the extended public keys for all of +`lnd`'s accounts. + +A custom macaroon can be baked for the watch-only node so it only gets the +minimum required permissions on the signer instance: + +```shell +signer> ⛰ lncli bakemacaroon --save_to signer.custom.macaroon \ + message:write signer:generate address:read onchain:write +``` + +Copy this file (`signer.custom.macaroon`) along with the `tls.cert` of the +signer node to the machine where the watch-only node will be running. + +### The "watch-only" node + +The node "watch-only" is the public, internet facing node that does not contain +any private keys in its wallet but delegates all signing operations to the node +"signer" over a single RPC connection. + +Required entries in `lnd.conf`: + +```text +[remotesigner] +remotesigner.enable=true +remotesigner.rpchost=zane.example.internal:10019 +remotesigner.tlscertpath=/home/watch-only/example/signer.tls.cert +remotesigner.macaroonpath=/home/watch-only/example/signer.custom.macaroon +``` + +After starting "watch-only", the wallet can be created in watch-only mode by +running: + +```shell +watch-only> ⛰ lncli createwatchonly accounts-signer.json + +Input wallet password: +Confirm password: + +Input an optional wallet birthday unix timestamp of first block to start scanning from (default 0): + + +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 +through the RPC interface as is described in the next section. + +## Example initialization script + +This section shows an example script that initializes the watch-only wallet of +the public node using NodeJS. + +To use this example, first initialize the "signer" wallet with the root key +`tprv8ZgxMBicQKsPe6jS4vDm2n7s42Q6MpvghUQqMmSKG7bTZvGKtjrcU3PGzMNG37yzxywrcdvgkwrr8eYXJmbwdvUNVT4Ucv7ris4jvA7BUmg` +using the command line. This can be done by using the new `x` option during the +interactive `lncli create` command: + +```bash +signer> ⛰ lncli create +Input wallet password: +Confirm password: + +Do you have an existing cipher seed mnemonic or extended master root key you want to use? +Enter 'y' to use an existing cipher seed mnemonic, 'x' to use an extended master root key +or 'n' to create a new seed (Enter y/x/n): +``` + +Then run this script against the "watch-only" node (after editing the +constants): + +```javascript + +// EDIT ME: +const WATCH_ONLY_LND_DIR = '/home/watch-only/.lnd'; +const WATCH_ONLY_RPC_HOSTPORT = 'localhost:10018'; +const WATCH_ONLY_WALLET_PASSWORD = 'testnet3'; +const LND_SOURCE_DIR = '.'; + +const fs = require('fs'); +const grpc = require('@grpc/grpc-js'); +const protoLoader = require('@grpc/proto-loader'); +const loaderOptions = { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true +}; +const packageDefinition = protoLoader.loadSync([ + LND_SOURCE_DIR + '/lnrpc/walletunlocker.proto', +], loaderOptions); + +process.env.GRPC_SSL_CIPHER_SUITES = 'HIGH+ECDSA' + +// build ssl credentials using the cert the same as before +let lndCert = fs.readFileSync(WATCH_ONLY_LND_DIR + '/tls.cert'); +let sslCreds = grpc.credentials.createSsl(lndCert); + +let lnrpcDescriptor = grpc.loadPackageDefinition(packageDefinition); +let lnrpc = lnrpcDescriptor.lnrpc; +var client = new lnrpc.WalletUnlocker(WATCH_ONLY_RPC_HOSTPORT, sslCreds); + +client.initWallet({ + wallet_password: Buffer.from(WATCH_ONLY_WALLET_PASSWORD, 'utf-8'), + recovery_window: 2500, + watch_only: { + accounts: [{ + purpose: 49, + coin_type: 0, + account: 0, + xpub: 'tpubDDXEYWvGCTytEF6hBog9p4qr2QBUvJhh4P2wM4qHHv9N489khkQoGkBXDVoquuiyBf8SKBwrYseYdtq9j2v2nttPpE8qbuW3sE2MCkFPhTq', + }, { + purpose: 84, + coin_type: 0, + account: 0, + xpub: 'tpubDDWAWrSLRSFrG1KdqXMQQyTKYGSKLKaY7gxpvK7RdV3e3DkhvuW2GgsFvsPN4RGmuoYtUgZ1LHZE8oftz7T4mzc1BxGt5rt8zJcVQiKTPPV', + }, { + purpose: 1017, + coin_type: 1, + account: 0, + xpub: 'tpubDDXFHr67Ro2tHKVWG2gNjjijKUH1Lyv5NKFYdJnuaLGVNBVwyV5AbykhR43iy8wYozEMbw2QfmAqZhb8gnuL5mm9sZh8YsR6FjGAbew1xoT', + }, { + purpose: 1017, + coin_type: 1, + account: 1, + xpub: 'tpubDDXFHr67Ro2tKkccDqNfDqZpd5wCs2n6XRV2Uh185DzCTbkDaEd9v7P837zZTYBNVfaRriuxgGVgxbGjDui4CKxyzBzwz4aAZxjn2PhNcQy', + }, { + purpose: 1017, + coin_type: 1, + account: 2, + xpub: 'tpubDDXFHr67Ro2tNH4KH41i4oTsWfRjFigoH1Ee7urvHow51opH9xJ7mu1qSPMPVtkVqQZ5tE4NTuFJPrbDqno7TQietyUDmPTwyVviJbGCwXk', + }, { + purpose: 1017, + coin_type: 1, + account: 3, + xpub: 'tpubDDXFHr67Ro2tQj5Zvav2ALhkU6dRQAhEtNPnYJVBC8hs2U1A9ecqxRY3XTiJKBDD7e8tudhmTRs8aGWJAiAXJN5kXy3Hi6cmiwGWjXK5Cv5', + }, { + purpose: 1017, + coin_type: 1, + account: 4, + xpub: 'tpubDDXFHr67Ro2tSSR2LLBJtotxx2U45cuESLWKA72YT9td3SzVKHAptzDEx5chsUNZ4WRMY5h6HJxRSebjRatxQKX1uUsux1LvKS1wsfNJ2PH', + }, { + purpose: 1017, + coin_type: 1, + account: 5, + xpub: 'tpubDDXFHr67Ro2tTwzfWvNoMoPpZbxdMEfe1WhbXJxvXikGixPa4ggSRZeGx6T5yxVHTVT3rjVh35Veqsowj7emX8SZfXKDKDKcLduXCeWPUU3', + }, { + purpose: 1017, + coin_type: 1, + account: 6, + xpub: 'tpubDDXFHr67Ro2tYEDS2EByRedfsUoEwBtrzVbS1qdPrX6sAkUYGLrZWvMmQv8KZDZ4zd9r8WzM9bJ2nGp7XuNVC4w2EBtWg7i76gbrmuEWjQh', + }, { + purpose: 1017, + coin_type: 1, + account: 7, + xpub: 'tpubDDXFHr67Ro2tYpwnFJEQaM8eAPM2UV5uY6gFgXeSzS5aC5T9TfzXuawYKBbQMZJn8qHXLafY4tAutoda1aKP5h6Nbgy3swPbnhWbFjS5wnX', + }, { + purpose: 1017, + coin_type: 1, + account: 8, + xpub: 'tpubDDXFHr67Ro2tddKpAjUegXqt7EGxRXnHkeLbUkfuFMGbLJYgRpG4ew5pMmGg2nmcGmHFQ29w3juNhd8N5ZZ8HwJdymC4f5ukQLJ4yg9rEr3', + }, { + purpose: 1017, + coin_type: 1, + account: 9, + xpub: 'tpubDDXFHr67Ro2tgE89V8ZdgMytC2Jq1iT9ttGhdzR1X7haQJNBmXt8kau6taC6DGASYzbrjmo9z9w6JQFcaLNqbhS2h2PVSzKf79j265Zi8hF', + }] + } +}, (err, res) => { + if (err != null) { + console.log(err); + } + console.log(res); +}); +```