From 58adbcf88a02f4f31c930865f83d4b7c0392c4eb Mon Sep 17 00:00:00 2001 From: Henry Romp <151henry151@gmail.com> Date: Sat, 22 Nov 2025 14:19:06 -0500 Subject: [PATCH] qt: Defer transaction signing until user clicks Send Fixes #30070 When creating an unsigned PSBT from the GUI, the transaction was already signed during preparation, causing legacy inputs to have non-empty scriptSig fields. The PSBT parser then rejects them. This defers signing until the user clicks "Send" instead of signing during preparation. Fee calculation still works since transactions can be created without signing. Follows the approach suggested by @achow101 in the issue comments. --- src/qt/sendcoinsdialog.cpp | 22 +++++++++++++++++++++- src/qt/walletmodel.cpp | 6 ++++-- src/qt/walletmodel.h | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 3962dd08878..8ffd4533019 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -281,6 +281,8 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa } // prepare transaction for getting txFee earlier + // Create unsigned transaction to support creating unsigned PSBTs. + // Signing is deferred until the user clicks "Send". m_current_transaction = std::make_unique(recipients); WalletModel::SendCoinsReturn prepareStatus; @@ -288,7 +290,7 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa CCoinControl coin_control = *m_coin_control; coin_control.m_allow_other_inputs = !coin_control.HasSelected(); // future, could introduce a checkbox to customize this value. - prepareStatus = model->prepareTransaction(*m_current_transaction, coin_control); + prepareStatus = model->prepareTransaction(*m_current_transaction, coin_control, /*sign=*/false); // process prepareStatus and on error generate message shown to user processSendCoinsReturn(prepareStatus, @@ -540,6 +542,24 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked) presentPSBT(psbtx); } } + } else { + // Sign the transaction now that the user has confirmed they want to send. + CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())}; + PartiallySignedTransaction psbtx(mtx); + bool complete = false; + // Fill and sign the PSBT + const auto err{model->wallet().fillPSBT(std::nullopt, /*sign=*/true, /*bip32derivs=*/false, /*n_signed=*/nullptr, psbtx, complete)}; + if (err || !complete) { + Q_EMIT message(tr("Send Coins"), tr("Failed to sign transaction."), + CClientUIInterface::MSG_ERROR); + send_failure = true; + broadcast = false; + } else { + // Extract the signed transaction + CHECK_NONFATAL(FinalizeAndExtractPSBT(psbtx, mtx)); + const CTransactionRef tx = MakeTransactionRef(mtx); + m_current_transaction->setWtx(tx); + } } // Broadcast the transaction, unless an external signer was used and it diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 694fb535b57..ec1829e5fc7 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -147,7 +147,7 @@ bool WalletModel::validateAddress(const QString& address) const return IsValidDestinationString(address.toStdString()); } -WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl& coinControl) +WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl& coinControl, bool sign) { CAmount total = 0; bool fSubtractFeeFromAmount = false; @@ -203,7 +203,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact int nChangePosRet = -1; auto& newTx = transaction.getWtx(); - const auto& res = m_wallet->createTransaction(vecSend, coinControl, /*sign=*/!wallet().privateKeysDisabled(), nChangePosRet, nFeeRequired); + // Only sign if explicitly requested via the sign parameter. + const bool should_sign = sign && !wallet().privateKeysDisabled(); + const auto& res = m_wallet->createTransaction(vecSend, coinControl, should_sign, nChangePosRet, nFeeRequired); newTx = res ? *res : nullptr; transaction.setTransactionFee(nFeeRequired); if (fSubtractFeeFromAmount && newTx) diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index ece797bfd9e..1743128dcf4 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -96,7 +96,7 @@ class WalletModel : public QObject }; // prepare transaction for getting txfee before sending coins - SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const wallet::CCoinControl& coinControl); + SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const wallet::CCoinControl& coinControl, bool sign = false); // Send coins to a list of recipients void sendCoins(WalletModelTransaction& transaction);