Skip to content

Commit 320db33

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 4b91316 commit 320db33

2 files changed

Lines changed: 25 additions & 2 deletions

File tree

src/qt/sendcoinsdialog.cpp

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,8 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
268268
}
269269

270270
// prepare transaction for getting txFee earlier
271+
// Create unsigned transaction to support creating unsigned PSBTs.
272+
// Signing is deferred until the user clicks "Send".
271273
m_current_transaction = std::make_unique<WalletModelTransaction>(recipients);
272274
WalletModel::SendCoinsReturn prepareStatus;
273275

@@ -344,7 +346,7 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
344346

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

349351
// append transaction fee value
350352
question_string.append("<span style='color:#aa0000; font-weight:bold;'>");
@@ -497,6 +499,12 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
497499
presentPSBT(psbtx);
498500
} else {
499501
// "Send" clicked
502+
WalletModel::UnlockContext ctx(model->requestUnlock());
503+
if (!ctx.isValid()) {
504+
fNewRecipientAllowed = true;
505+
return;
506+
}
507+
500508
assert(!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner());
501509
bool broadcast = true;
502510
if (model->wallet().hasExternalSigner()) {
@@ -522,6 +530,21 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
522530
presentPSBT(psbtx);
523531
}
524532
}
533+
} else {
534+
// Sign the transaction now that the user has confirmed they want to send.
535+
CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
536+
PartiallySignedTransaction psbtx(mtx);
537+
bool complete = false;
538+
const auto err{model->wallet().fillPSBT({.sign = true, .bip32_derivs = false}, /*n_signed=*/nullptr, psbtx, complete)};
539+
if (err || !complete) {
540+
Q_EMIT message(tr("Send Coins"), tr("Failed to sign transaction."),
541+
CClientUIInterface::MSG_ERROR);
542+
send_failure = true;
543+
broadcast = false;
544+
} else {
545+
CHECK_NONFATAL(FinalizeAndExtractPSBT(psbtx, mtx));
546+
m_current_transaction->setWtx(MakeTransactionRef(mtx));
547+
}
525548
}
526549

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

src/qt/walletmodel.cpp

Lines changed: 1 addition & 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);

0 commit comments

Comments
 (0)