diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index cbe4878932a4..127d3f308c27 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2323,6 +2323,12 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp } const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); LOCK(cs_wallet); + if (sign && IsLocked()) { + // A mixing-only unlock still exposes private keys to the + // ScriptPubKeyMans, so refuse to sign here and report zero signed inputs. + sign = false; + n_signed = nullptr; + } // Get all of the previous transactions for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { const CTxIn& txin = psbtx.tx->vin[i]; diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 7cd93f499aab..bc2c3bb719bf 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -77,6 +77,10 @@ def run_test(self): unsigned_tx = self.nodes[0].walletprocesspsbt(psbtx, False) assert_equal(unsigned_tx['complete'], False) + # Unlocking for mixing only must not allow signing either + self.nodes[0].walletpassphrase("password", 1000000, True) + assert_equal(self.nodes[0].send({self.nodes[2].getnewaddress(): 10})['complete'], False) + self.nodes[0].walletpassphrase(passphrase="password", timeout=1000000) # Sign the transaction but don't finalize