wallet: prevent input signing for transactions on watch-only accounts

Watch-only accounts don't have any type of private key information
stored, so we avoid populating input signatures in those cases.
This commit is contained in:
Wilmer Paulino 2021-02-17 16:24:17 -08:00
parent 2301069644
commit f5845dfb42
No known key found for this signature in database
GPG key ID: 6DF57B9F9514972F
4 changed files with 97 additions and 6 deletions

View file

@ -405,6 +405,29 @@ func (m *Manager) watchOnly() bool {
return m.watchingOnly
}
// IsWatchOnlyAccount determines if the account with the given key scope is set
// up as watch-only.
func (m *Manager) IsWatchOnlyAccount(ns walletdb.ReadBucket, keyScope KeyScope,
account uint32) (bool, error) {
if m.WatchOnly() {
return true, nil
}
// Assume the default imported account has no private keys.
//
// TODO: Actually check whether it does.
if account == ImportedAddrAccount {
return true, nil
}
scopedMgr, err := m.FetchScopedKeyManager(keyScope)
if err != nil {
return false, err
}
return scopedMgr.IsWatchOnlyAccount(ns, account)
}
// lock performs a best try effort to remove and zero all secret keys associated
// with the address manager.
//

View file

@ -2186,6 +2186,22 @@ func (s *ScopedKeyManager) ForEachInternalActiveAddress(ns walletdb.ReadBucket,
return nil
}
// IsWatchOnlyAccount determines if the given account belonging to this scoped
// manager is set up as watch-only.
func (s *ScopedKeyManager) IsWatchOnlyAccount(ns walletdb.ReadBucket,
account uint32) (bool, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
acctInfo, err := s.loadAccountInfo(ns, account)
if err != nil {
return false, err
}
return acctInfo.acctKeyPriv == nil, nil
}
// cloneKeyWithVersion clones an extended key to use the version corresponding
// to the manager's key scope. This should only be used for non-watch-only
// accounts as they are stored within the database using the legacy BIP-0044

View file

@ -163,14 +163,36 @@ func (w *Wallet) txToOutputs(outputs []*wire.TxOut, keyScope *waddrmgr.KeyScope,
return tx, nil
}
err = tx.AddAllInputScripts(secretSource{w.Manager, addrmgrNs})
// Before committing the transaction, we'll sign our inputs. If the
// inputs are part of a watch-only account, there's no private key
// information stored, so we'll skip signing such.
var watchOnly bool
if keyScope == nil {
// If a key scope wasn't specified, then coin selection was
// performed from the default wallet accounts (NP2WKH, P2WKH),
// so any key scope provided doesn't impact the result of this
// call.
watchOnly, err = w.Manager.IsWatchOnlyAccount(
addrmgrNs, waddrmgr.KeyScopeBIP0084, account,
)
} else {
watchOnly, err = w.Manager.IsWatchOnlyAccount(
addrmgrNs, *keyScope, account,
)
}
if err != nil {
return nil, err
}
if !watchOnly {
err = tx.AddAllInputScripts(secretSource{w.Manager, addrmgrNs})
if err != nil {
return nil, err
}
err = validateMsgTx(tx.Tx, tx.PrevScripts, tx.PrevInputValues)
if err != nil {
return nil, err
err = validateMsgTx(tx.Tx, tx.PrevScripts, tx.PrevInputValues)
if err != nil {
return nil, err
}
}
if err := dbtx.Commit(); err != nil {

View file

@ -15,6 +15,7 @@ import (
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet/txauthor"
"github.com/btcsuite/btcwallet/wallet/txrules"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/btcsuite/btcwallet/wtxmgr"
)
@ -313,8 +314,37 @@ func (w *Wallet) FinalizePsbt(keyScope *waddrmgr.KeyScope, account uint32,
}
}
// Finally, we'll sign the input as is, and populate the input
// with the witness and sigScript (if needed).
// Finally, if the input doesn't belong to a watch-only account,
// then we'll sign it as is, and populate the input with the
// witness and sigScript (if needed).
watchOnly := false
err = walletdb.View(w.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
if keyScope == nil {
// If a key scope wasn't specified, then coin
// selection was performed from the default
// wallet accounts (NP2WKH, P2WKH), so any key
// scope provided doesn't impact the result of
// this call.
watchOnly, err = w.Manager.IsWatchOnlyAccount(
ns, waddrmgr.KeyScopeBIP0084, account,
)
} else {
watchOnly, err = w.Manager.IsWatchOnlyAccount(
ns, *keyScope, account,
)
}
return err
})
if err != nil {
return fmt.Errorf("unable to determine if account is "+
"watch-only: %v", err)
}
if watchOnly {
continue
}
witness, sigScript, err := w.ComputeInputScript(
tx, signOutput, idx, sigHashes, in.SighashType, nil,
)