diff --git a/waddrmgr/manager.go b/waddrmgr/manager.go index f8acb34..6706703 100644 --- a/waddrmgr/manager.go +++ b/waddrmgr/manager.go @@ -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. // diff --git a/waddrmgr/scoped_manager.go b/waddrmgr/scoped_manager.go index 962ab8e..0d0d70f 100644 --- a/waddrmgr/scoped_manager.go +++ b/waddrmgr/scoped_manager.go @@ -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 diff --git a/wallet/createtx.go b/wallet/createtx.go index 5fd8ea1..4df8237 100644 --- a/wallet/createtx.go +++ b/wallet/createtx.go @@ -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 { diff --git a/wallet/psbt.go b/wallet/psbt.go index 2a7fbd5..58d8f71 100644 --- a/wallet/psbt.go +++ b/wallet/psbt.go @@ -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, )