From 73987178acd0c00d1dcd656df690d993ed6587c9 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Wed, 8 Sep 2021 13:52:06 +0200 Subject: [PATCH 1/2] wallet: don't hold unlock in watch-only mode If we're running in watch-only mode, there is no unlock possible. Therefore, we also don't need to prevent any unlocks from happening when doing coin selection in that mode. --- wallet/wallet.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/wallet/wallet.go b/wallet/wallet.go index a0e9fc2..beac81e 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -1169,17 +1169,27 @@ out: for { select { case txr := <-w.createTxRequests: - heldUnlock, err := w.holdUnlock() - if err != nil { - txr.resp <- createTxResponse{nil, err} - continue + // If the wallet can be locked because it contains + // private key material, we need to prevent it from + // doing so while we are assembling the transaction. + release := func() {} + if !w.Manager.WatchOnly() { + heldUnlock, err := w.holdUnlock() + if err != nil { + txr.resp <- createTxResponse{nil, err} + continue + } + + release = heldUnlock.release } + tx, err := w.txToOutputs( txr.outputs, txr.keyScope, txr.account, txr.minconf, txr.feeSatPerKB, txr.coinSelectionStrategy, txr.dryRun, ) - heldUnlock.release() + + release() txr.resp <- createTxResponse{tx, err} case <-quit: break out From 21eec962661b18b6cb761a1a9d9c686587aefe8a Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Wed, 8 Sep 2021 13:52:08 +0200 Subject: [PATCH 2/2] wallet: add ImportAccountWithScope When creating a fully watch-only wallet, we know exactly what accounts we are importing and what scope should be used for them. --- wallet/import.go | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/wallet/import.go b/wallet/import.go index 9d2dcfa..4528875 100644 --- a/wallet/import.go +++ b/wallet/import.go @@ -204,6 +204,30 @@ func (w *Wallet) ImportAccount(name string, accountPubKey *hdkeychain.ExtendedKe return accountProps, err } +// ImportAccountWithScope imports an account backed by an account extended +// public key for a specific key scope which is known in advance. +// 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. +func (w *Wallet) ImportAccountWithScope(name string, + accountPubKey *hdkeychain.ExtendedKey, masterKeyFingerprint uint32, + keyScope waddrmgr.KeyScope, addrSchema waddrmgr.ScopeAddrSchema) ( + *waddrmgr.AccountProperties, error) { + + var accountProps *waddrmgr.AccountProperties + err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { + ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) + var err error + accountProps, err = w.importAccountScope( + ns, name, accountPubKey, masterKeyFingerprint, keyScope, + &addrSchema, + ) + return err + }) + return accountProps, err +} + // importAccount is the internal implementation of ImportAccount -- one should // reference its documentation for this method. func (w *Wallet) importAccount(ns walletdb.ReadWriteBucket, name string, @@ -221,9 +245,27 @@ func (w *Wallet) importAccount(ns walletdb.ReadWriteBucket, name string, if err != nil { return nil, err } + + return w.importAccountScope( + ns, name, accountPubKey, masterKeyFingerprint, keyScope, + addrSchema, + ) +} + +// importAccountScope imports a watch-only account for a given scope. +func (w *Wallet) importAccountScope(ns walletdb.ReadWriteBucket, name string, + accountPubKey *hdkeychain.ExtendedKey, masterKeyFingerprint uint32, + keyScope waddrmgr.KeyScope, addrSchema *waddrmgr.ScopeAddrSchema) ( + *waddrmgr.AccountProperties, error) { + scopedMgr, err := w.Manager.FetchScopedKeyManager(keyScope) if err != nil { - return nil, err + scopedMgr, err = w.Manager.NewScopedKeyManager( + ns, keyScope, *addrSchema, + ) + if err != nil { + return nil, err + } } account, err := scopedMgr.NewAccountWatchingOnly(