Skip to content

Commit beda8d9

Browse files
committed
[gui] send: use external signer
1 parent 256b770 commit beda8d9

File tree

1 file changed

+86
-47
lines changed

1 file changed

+86
-47
lines changed

src/qt/sendcoinsdialog.cpp

Lines changed: 86 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,16 @@ void SendCoinsDialog::setModel(WalletModel *_model)
188188
// set default rbf checkbox state
189189
ui->optInRBF->setCheckState(Qt::Checked);
190190

191-
if (model->wallet().privateKeysDisabled()) {
191+
if (model->wallet().hasExternalSigner()) {
192+
ui->sendButton->setText(tr("Sign on device"));
193+
if (gArgs.GetArg("-signer", "") != "") {
194+
ui->sendButton->setEnabled(true);
195+
ui->sendButton->setToolTip(tr("Connect your hardware wallet first."));
196+
} else {
197+
ui->sendButton->setEnabled(false);
198+
ui->sendButton->setToolTip(tr("Set external signer script path in Options -> Wallet"));
199+
}
200+
} else if (model->wallet().privateKeysDisabled()) {
192201
ui->sendButton->setText(tr("Cr&eate Unsigned"));
193202
ui->sendButton->setToolTip(tr("Creates a Partially Signed Bitcoin Transaction (PSBT) for use with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.").arg(PACKAGE_NAME));
194203
}
@@ -302,14 +311,14 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
302311
formatted.append(recipientElement);
303312
}
304313

305-
if (model->wallet().privateKeysDisabled()) {
314+
if (model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner()) {
306315
question_string.append(tr("Do you want to draft this transaction?"));
307316
} else {
308317
question_string.append(tr("Are you sure you want to send?"));
309318
}
310319

311320
question_string.append("<br /><span style='font-size:10pt;'>");
312-
if (model->wallet().privateKeysDisabled()) {
321+
if (model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner()) {
313322
question_string.append(tr("Please, review your transaction proposal. This will produce a Partially Signed Bitcoin Transaction (PSBT) which you can save or copy and then sign with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.").arg(PACKAGE_NAME));
314323
} else {
315324
question_string.append(tr("Please, review your transaction."));
@@ -375,8 +384,8 @@ void SendCoinsDialog::on_sendButton_clicked()
375384
if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
376385
assert(m_current_transaction);
377386

378-
const QString confirmation = model->wallet().privateKeysDisabled() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
379-
const QString confirmButtonText = model->wallet().privateKeysDisabled() ? tr("Create Unsigned") : tr("Send");
387+
const QString confirmation = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
388+
const QString confirmButtonText = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Create Unsigned") : tr("Send");
380389
SendConfirmationDialog confirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this);
381390
confirmationDialog.exec();
382391
QMessageBox::StandardButton retval = static_cast<QMessageBox::StandardButton>(confirmationDialog.result());
@@ -392,49 +401,77 @@ void SendCoinsDialog::on_sendButton_clicked()
392401
CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
393402
PartiallySignedTransaction psbtx(mtx);
394403
bool complete = false;
395-
const TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
404+
// Always fill without signing first, to prevents an external signer
405+
// from being called prematurely. This is not expensive.
406+
TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
396407
assert(!complete);
397-
assert(err == TransactionError::OK);
398-
// Serialize the PSBT
399-
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
400-
ssTx << psbtx;
401-
GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
402-
QMessageBox msgBox;
403-
msgBox.setText("Unsigned Transaction");
404-
msgBox.setInformativeText("The PSBT has been copied to the clipboard. You can also save it.");
405-
msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
406-
msgBox.setDefaultButton(QMessageBox::Discard);
407-
switch (msgBox.exec()) {
408-
case QMessageBox::Save: {
409-
QString selectedFilter;
410-
QString fileNameSuggestion = "";
411-
bool first = true;
412-
for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) {
413-
if (!first) {
414-
fileNameSuggestion.append(" - ");
415-
}
416-
QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;
417-
QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
418-
fileNameSuggestion.append(labelOrAddress + "-" + amount);
419-
first = false;
420-
}
421-
fileNameSuggestion.append(".psbt");
422-
QString filename = GUIUtil::getSaveFileName(this,
423-
tr("Save Transaction Data"), fileNameSuggestion,
424-
tr("Partially Signed Transaction (Binary) (*.psbt)"), &selectedFilter);
425-
if (filename.isEmpty()) {
408+
if (model->wallet().hasExternalSigner()) {
409+
try {
410+
err = model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
411+
} catch (const ExternalSignerException& e) {
412+
QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
413+
send_failure = true;
426414
return;
427415
}
428-
std::ofstream out(filename.toLocal8Bit().data());
429-
out << ssTx.str();
430-
out.close();
431-
Q_EMIT message(tr("PSBT saved"), "PSBT saved to disk", CClientUIInterface::MSG_INFORMATION);
432-
break;
433416
}
434-
case QMessageBox::Discard:
435-
break;
436-
default:
437-
assert(false);
417+
// fillPSBT does not always properly finalize
418+
complete = FinalizeAndExtractPSBT(psbtx, mtx);
419+
if (complete) {
420+
std::string err_string;
421+
TransactionError result = BroadcastTransaction(*clientModel->node().context(), MakeTransactionRef(mtx), err_string, COIN / 10, /* relay */ true, /* await_callback */ false);
422+
423+
if (result == TransactionError::OK) {
424+
Q_EMIT coinsSent(mtx.GetHash());
425+
} else {
426+
processSendCoinsReturn(WalletModel::TransactionCreationFailed);
427+
send_failure = true;
428+
}
429+
} else if (err == TransactionError::OK) {
430+
// Serialize the PSBT
431+
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
432+
ssTx << psbtx;
433+
GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
434+
QMessageBox msgBox;
435+
msgBox.setText("Unsigned Transaction");
436+
msgBox.setInformativeText("The PSBT has been copied to the clipboard. You can also save it.");
437+
msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
438+
msgBox.setDefaultButton(QMessageBox::Discard);
439+
switch (msgBox.exec()) {
440+
case QMessageBox::Save: {
441+
QString selectedFilter;
442+
QString fileNameSuggestion = "";
443+
bool first = true;
444+
for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) {
445+
if (!first) {
446+
fileNameSuggestion.append(" - ");
447+
}
448+
QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;
449+
QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
450+
fileNameSuggestion.append(labelOrAddress + "-" + amount);
451+
first = false;
452+
}
453+
fileNameSuggestion.append(".psbt");
454+
QString filename = GUIUtil::getSaveFileName(this,
455+
tr("Save Transaction Data"), fileNameSuggestion,
456+
tr("Partially Signed Transaction (Binary) (*.psbt)"), &selectedFilter);
457+
if (filename.isEmpty()) {
458+
return;
459+
}
460+
std::ofstream out(filename.toLocal8Bit().data());
461+
out << ssTx.str();
462+
out.close();
463+
Q_EMIT message(tr("PSBT saved"), "PSBT saved to disk", CClientUIInterface::MSG_INFORMATION);
464+
break;
465+
}
466+
case QMessageBox::Discard:
467+
break;
468+
default:
469+
assert(false);
470+
}
471+
} else {
472+
// TODO: process error
473+
processSendCoinsReturn(WalletModel::TransactionCreationFailed);
474+
send_failure = true;
438475
}
439476
} else {
440477
// now send the prepared transaction
@@ -602,7 +639,9 @@ void SendCoinsDialog::setBalance(const interfaces::WalletBalances& balances)
602639
if(model && model->getOptionsModel())
603640
{
604641
CAmount balance = balances.balance;
605-
if (model->wallet().privateKeysDisabled()) {
642+
if (model->wallet().hasExternalSigner()) {
643+
ui->labelBalanceName->setText(tr("External balance:"));
644+
} else if (model->wallet().privateKeysDisabled()) {
606645
balance = balances.watch_only_balance;
607646
ui->labelBalanceName->setText(tr("Watch-only balance:"));
608647
}
@@ -686,7 +725,7 @@ void SendCoinsDialog::on_buttonMinimizeFee_clicked()
686725
void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry)
687726
{
688727
// Include watch-only for wallets without private key
689-
m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled();
728+
m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner();
690729

691730
// Calculate available amount to send.
692731
CAmount amount = model->wallet().getAvailableBalance(*m_coin_control);
@@ -741,7 +780,7 @@ void SendCoinsDialog::updateCoinControlState(CCoinControl& ctrl)
741780
ctrl.m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
742781
ctrl.m_signal_bip125_rbf = ui->optInRBF->isChecked();
743782
// Include watch-only for wallets without private key
744-
ctrl.fAllowWatchOnly = model->wallet().privateKeysDisabled();
783+
ctrl.fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner();
745784
}
746785

747786
void SendCoinsDialog::updateSmartFeeLabel()

0 commit comments

Comments
 (0)