Skip to content

Commit 5b79f27

Browse files
committed
qt: Defer transaction signing until user clicks Send
This fixes issue #30070 where creating unsigned PSBTs from the GUI would fail because the transaction was already signed during preparation, causing legacy inputs to have non-empty scriptSig fields. The fix defers signing until the user explicitly clicks 'Send', allowing truly unsigned PSBTs to be created while still supporting fee calculation.
1 parent 91a8e9b commit 5b79f27

2 files changed

Lines changed: 32 additions & 2 deletions

File tree

src/qt/sendcoinsdialog.cpp

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,8 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
281281
}
282282

283283
// prepare transaction for getting txFee earlier
284+
// Create unsigned transaction to support creating unsigned PSBTs.
285+
// Signing is deferred until the user clicks "Send".
284286
m_current_transaction = std::make_unique<WalletModelTransaction>(recipients);
285287
WalletModel::SendCoinsReturn prepareStatus;
286288

@@ -357,7 +359,7 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
357359

358360
// append transaction size
359361
//: When reviewing a newly created PSBT (via Send flow), the transaction fee is shown, with "virtual size" of the transaction displayed for context
360-
question_string.append(" (" + tr("%1 kvB", "PSBT transaction creation").arg((double)m_current_transaction->getTransactionSize() / 1000, 0, 'g', 3) + "): ");
362+
question_string.append(" (" + tr("%1 kvB (unsigned)", "PSBT transaction creation").arg((double)m_current_transaction->getTransactionSize() / 1000, 0, 'g', 3) + "): ");
361363

362364
// append transaction fee value
363365
question_string.append("<span style='color:#aa0000; font-weight:bold;'>");
@@ -515,6 +517,12 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
515517
presentPSBT(psbtx);
516518
} else {
517519
// "Send" clicked
520+
WalletModel::UnlockContext ctx(model->requestUnlock());
521+
if (!ctx.isValid()) {
522+
fNewRecipientAllowed = true;
523+
return;
524+
}
525+
518526
assert(!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner());
519527
bool broadcast = true;
520528
if (model->wallet().hasExternalSigner()) {
@@ -540,6 +548,24 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
540548
presentPSBT(psbtx);
541549
}
542550
}
551+
} else {
552+
// Sign the transaction now that the user has confirmed they want to send.
553+
CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
554+
PartiallySignedTransaction psbtx(mtx);
555+
bool complete = false;
556+
// Fill and sign the PSBT
557+
const auto err{model->wallet().fillPSBT(std::nullopt, /*sign=*/true, /*bip32derivs=*/false, /*n_signed=*/nullptr, psbtx, complete)};
558+
if (err || !complete) {
559+
Q_EMIT message(tr("Send Coins"), tr("Failed to sign transaction."),
560+
CClientUIInterface::MSG_ERROR);
561+
send_failure = true;
562+
broadcast = false;
563+
} else {
564+
// Extract the signed transaction
565+
CHECK_NONFATAL(FinalizeAndExtractPSBT(psbtx, mtx));
566+
const CTransactionRef tx = MakeTransactionRef(mtx);
567+
m_current_transaction->setWtx(tx);
568+
}
543569
}
544570

545571
// Broadcast the transaction, unless an external signer was used and it

src/qt/walletmodel.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
203203

204204
try {
205205
auto& newTx = transaction.getWtx();
206-
const auto& res = m_wallet->createTransaction(vecSend, coinControl, /*sign=*/!wallet().privateKeysDisabled(), /*change_pos=*/std::nullopt);
206+
const auto& res = m_wallet->createTransaction(vecSend, coinControl, /*sign=*/false, /*change_pos=*/std::nullopt);
207207
if (!res) {
208208
Q_EMIT message(tr("Send Coins"), QString::fromStdString(util::ErrorString(res).translated),
209209
CClientUIInterface::MSG_ERROR);
@@ -217,6 +217,10 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
217217
transaction.reassignAmounts(static_cast<int>(res->change_pos.value_or(-1)));
218218
}
219219

220+
if (!fSubtractFeeFromAmount && (total + nFeeRequired) > nBalance) {
221+
return SendCoinsReturn(AmountExceedsBalance);
222+
}
223+
220224
// Reject absurdly high fee. (This can never happen because the
221225
// wallet never creates transactions with fee greater than
222226
// m_default_max_tx_fee. This merely a belt-and-suspenders check).

0 commit comments

Comments
 (0)