Skip to content

Commit 1c4b456

Browse files
committed
gui: send using external signer
1 parent 24815c6 commit 1c4b456

File tree

3 files changed

+77
-10
lines changed

3 files changed

+77
-10
lines changed

src/qt/sendcoinsdialog.cpp

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,16 @@ void SendCoinsDialog::setModel(WalletModel *_model)
199199
// set default rbf checkbox state
200200
ui->optInRBF->setCheckState(Qt::Checked);
201201

202-
if (model->wallet().privateKeysDisabled()) {
202+
if (model->wallet().hasExternalSigner()) {
203+
ui->sendButton->setText(tr("Sign on device"));
204+
if (gArgs.GetArg("-signer", "") != "") {
205+
ui->sendButton->setEnabled(true);
206+
ui->sendButton->setToolTip(tr("Connect your hardware wallet first."));
207+
} else {
208+
ui->sendButton->setEnabled(false);
209+
ui->sendButton->setToolTip(tr("Set external signer script path in Options -> Wallet"));
210+
}
211+
} else if (model->wallet().privateKeysDisabled()) {
203212
ui->sendButton->setText(tr("Cr&eate Unsigned"));
204213
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));
205214
}
@@ -313,14 +322,14 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
313322
formatted.append(recipientElement);
314323
}
315324

316-
if (model->wallet().privateKeysDisabled()) {
325+
if (model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner()) {
317326
question_string.append(tr("Do you want to draft this transaction?"));
318327
} else {
319328
question_string.append(tr("Are you sure you want to send?"));
320329
}
321330

322331
question_string.append("<br /><span style='font-size:10pt;'>");
323-
if (model->wallet().privateKeysDisabled()) {
332+
if (model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner()) {
324333
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));
325334
} else {
326335
question_string.append(tr("Please, review your transaction."));
@@ -386,8 +395,8 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
386395
if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
387396
assert(m_current_transaction);
388397

389-
const QString confirmation = model->wallet().privateKeysDisabled() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
390-
const QString confirmButtonText = model->wallet().privateKeysDisabled() ? tr("Create Unsigned") : tr("Send");
398+
const QString confirmation = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
399+
const QString confirmButtonText = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Create Unsigned") : tr("Sign and send");
391400
SendConfirmationDialog confirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this);
392401
confirmationDialog.exec();
393402
QMessageBox::StandardButton retval = static_cast<QMessageBox::StandardButton>(confirmationDialog.result());
@@ -403,9 +412,58 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
403412
CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
404413
PartiallySignedTransaction psbtx(mtx);
405414
bool complete = false;
406-
const TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
415+
// Always fill without signing first. This prevents an external signer
416+
// from being called prematurely and is not expensive.
417+
TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
407418
assert(!complete);
408419
assert(err == TransactionError::OK);
420+
if (model->wallet().hasExternalSigner()) {
421+
try {
422+
err = model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
423+
} catch (const std::runtime_error& e) {
424+
QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
425+
send_failure = true;
426+
return;
427+
}
428+
if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) {
429+
QMessageBox::critical(nullptr, tr("External signer not found"), "External signer not found");
430+
send_failure = true;
431+
return;
432+
}
433+
if (err == TransactionError::EXTERNAL_SIGNER_FAILED) {
434+
QMessageBox::critical(nullptr, tr("External signer failure"), "External signer failure");
435+
send_failure = true;
436+
return;
437+
}
438+
if (err != TransactionError::OK) {
439+
tfm::format(std::cerr, "Failed to sign PSBT");
440+
processSendCoinsReturn(WalletModel::TransactionCreationFailed);
441+
send_failure = true;
442+
return;
443+
}
444+
// fillPSBT does not always properly finalize
445+
complete = FinalizeAndExtractPSBT(psbtx, mtx);
446+
}
447+
448+
// Broadcast transaction if complete (even with an external signer this
449+
// is not always the case, e.g. in a multisig wallet).
450+
if (complete) {
451+
const CTransactionRef tx = MakeTransactionRef(mtx);
452+
m_current_transaction->setWtx(tx);
453+
WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
454+
// process sendStatus and on error generate message shown to user
455+
processSendCoinsReturn(sendStatus);
456+
457+
if (sendStatus.status == WalletModel::OK) {
458+
Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
459+
} else {
460+
send_failure = true;
461+
}
462+
return;
463+
}
464+
465+
// Copy PSBT to clipboard and offer to save
466+
assert(!complete);
409467
// Serialize the PSBT
410468
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
411469
ssTx << psbtx;
@@ -447,7 +505,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
447505
break;
448506
default:
449507
assert(false);
450-
}
508+
} // msgBox.exec()
451509
} else {
452510
// now send the prepared transaction
453511
WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
@@ -614,7 +672,9 @@ void SendCoinsDialog::setBalance(const interfaces::WalletBalances& balances)
614672
if(model && model->getOptionsModel())
615673
{
616674
CAmount balance = balances.balance;
617-
if (model->wallet().privateKeysDisabled()) {
675+
if (model->wallet().hasExternalSigner()) {
676+
ui->labelBalanceName->setText(tr("External balance:"));
677+
} else if (model->wallet().privateKeysDisabled()) {
618678
balance = balances.watch_only_balance;
619679
ui->labelBalanceName->setText(tr("Watch-only balance:"));
620680
}
@@ -698,7 +758,7 @@ void SendCoinsDialog::on_buttonMinimizeFee_clicked()
698758
void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry)
699759
{
700760
// Include watch-only for wallets without private key
701-
m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled();
761+
m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner();
702762

703763
// Calculate available amount to send.
704764
CAmount amount = model->wallet().getAvailableBalance(*m_coin_control);
@@ -753,7 +813,7 @@ void SendCoinsDialog::updateCoinControlState()
753813
m_coin_control->m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
754814
m_coin_control->m_signal_bip125_rbf = ui->optInRBF->isChecked();
755815
// Include watch-only for wallets without private key
756-
m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled();
816+
m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner();
757817
}
758818

759819
void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state) {

src/qt/walletmodeltransaction.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ CTransactionRef& WalletModelTransaction::getWtx()
2626
return wtx;
2727
}
2828

29+
void WalletModelTransaction::setWtx(const CTransactionRef& newTx)
30+
{
31+
wtx = newTx;
32+
}
33+
2934
unsigned int WalletModelTransaction::getTransactionSize()
3035
{
3136
return wtx ? GetVirtualTransactionSize(*wtx) : 0;

src/qt/walletmodeltransaction.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ class WalletModelTransaction
2727
QList<SendCoinsRecipient> getRecipients() const;
2828

2929
CTransactionRef& getWtx();
30+
void setWtx(const CTransactionRef&);
31+
3032
unsigned int getTransactionSize();
3133

3234
void setTransactionFee(const CAmount& newFee);

0 commit comments

Comments
 (0)