mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-15 11:36:00 -05:00
gui: send using external signer
This commit is contained in:
parent
24815c6309
commit
1c4b456e1a
3 changed files with 77 additions and 10 deletions
|
@ -199,7 +199,16 @@ void SendCoinsDialog::setModel(WalletModel *_model)
|
||||||
// set default rbf checkbox state
|
// set default rbf checkbox state
|
||||||
ui->optInRBF->setCheckState(Qt::Checked);
|
ui->optInRBF->setCheckState(Qt::Checked);
|
||||||
|
|
||||||
if (model->wallet().privateKeysDisabled()) {
|
if (model->wallet().hasExternalSigner()) {
|
||||||
|
ui->sendButton->setText(tr("Sign on device"));
|
||||||
|
if (gArgs.GetArg("-signer", "") != "") {
|
||||||
|
ui->sendButton->setEnabled(true);
|
||||||
|
ui->sendButton->setToolTip(tr("Connect your hardware wallet first."));
|
||||||
|
} else {
|
||||||
|
ui->sendButton->setEnabled(false);
|
||||||
|
ui->sendButton->setToolTip(tr("Set external signer script path in Options -> Wallet"));
|
||||||
|
}
|
||||||
|
} else if (model->wallet().privateKeysDisabled()) {
|
||||||
ui->sendButton->setText(tr("Cr&eate Unsigned"));
|
ui->sendButton->setText(tr("Cr&eate Unsigned"));
|
||||||
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));
|
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));
|
||||||
}
|
}
|
||||||
|
@ -313,14 +322,14 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
|
||||||
formatted.append(recipientElement);
|
formatted.append(recipientElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model->wallet().privateKeysDisabled()) {
|
if (model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner()) {
|
||||||
question_string.append(tr("Do you want to draft this transaction?"));
|
question_string.append(tr("Do you want to draft this transaction?"));
|
||||||
} else {
|
} else {
|
||||||
question_string.append(tr("Are you sure you want to send?"));
|
question_string.append(tr("Are you sure you want to send?"));
|
||||||
}
|
}
|
||||||
|
|
||||||
question_string.append("<br /><span style='font-size:10pt;'>");
|
question_string.append("<br /><span style='font-size:10pt;'>");
|
||||||
if (model->wallet().privateKeysDisabled()) {
|
if (model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner()) {
|
||||||
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));
|
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));
|
||||||
} else {
|
} else {
|
||||||
question_string.append(tr("Please, review your transaction."));
|
question_string.append(tr("Please, review your transaction."));
|
||||||
|
@ -386,8 +395,8 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
|
||||||
if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
|
if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
|
||||||
assert(m_current_transaction);
|
assert(m_current_transaction);
|
||||||
|
|
||||||
const QString confirmation = model->wallet().privateKeysDisabled() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
|
const QString confirmation = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
|
||||||
const QString confirmButtonText = model->wallet().privateKeysDisabled() ? tr("Create Unsigned") : tr("Send");
|
const QString confirmButtonText = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Create Unsigned") : tr("Sign and send");
|
||||||
SendConfirmationDialog confirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this);
|
SendConfirmationDialog confirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this);
|
||||||
confirmationDialog.exec();
|
confirmationDialog.exec();
|
||||||
QMessageBox::StandardButton retval = static_cast<QMessageBox::StandardButton>(confirmationDialog.result());
|
QMessageBox::StandardButton retval = static_cast<QMessageBox::StandardButton>(confirmationDialog.result());
|
||||||
|
@ -403,9 +412,58 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
|
||||||
CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
|
CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
|
||||||
PartiallySignedTransaction psbtx(mtx);
|
PartiallySignedTransaction psbtx(mtx);
|
||||||
bool complete = false;
|
bool complete = false;
|
||||||
const TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
|
// Always fill without signing first. This prevents an external signer
|
||||||
|
// from being called prematurely and is not expensive.
|
||||||
|
TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
|
||||||
assert(!complete);
|
assert(!complete);
|
||||||
assert(err == TransactionError::OK);
|
assert(err == TransactionError::OK);
|
||||||
|
if (model->wallet().hasExternalSigner()) {
|
||||||
|
try {
|
||||||
|
err = model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
|
||||||
|
} catch (const std::runtime_error& e) {
|
||||||
|
QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
|
||||||
|
send_failure = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) {
|
||||||
|
QMessageBox::critical(nullptr, tr("External signer not found"), "External signer not found");
|
||||||
|
send_failure = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err == TransactionError::EXTERNAL_SIGNER_FAILED) {
|
||||||
|
QMessageBox::critical(nullptr, tr("External signer failure"), "External signer failure");
|
||||||
|
send_failure = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err != TransactionError::OK) {
|
||||||
|
tfm::format(std::cerr, "Failed to sign PSBT");
|
||||||
|
processSendCoinsReturn(WalletModel::TransactionCreationFailed);
|
||||||
|
send_failure = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// fillPSBT does not always properly finalize
|
||||||
|
complete = FinalizeAndExtractPSBT(psbtx, mtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broadcast transaction if complete (even with an external signer this
|
||||||
|
// is not always the case, e.g. in a multisig wallet).
|
||||||
|
if (complete) {
|
||||||
|
const CTransactionRef tx = MakeTransactionRef(mtx);
|
||||||
|
m_current_transaction->setWtx(tx);
|
||||||
|
WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
|
||||||
|
// process sendStatus and on error generate message shown to user
|
||||||
|
processSendCoinsReturn(sendStatus);
|
||||||
|
|
||||||
|
if (sendStatus.status == WalletModel::OK) {
|
||||||
|
Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
|
||||||
|
} else {
|
||||||
|
send_failure = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy PSBT to clipboard and offer to save
|
||||||
|
assert(!complete);
|
||||||
// Serialize the PSBT
|
// Serialize the PSBT
|
||||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
ssTx << psbtx;
|
ssTx << psbtx;
|
||||||
|
@ -447,7 +505,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
} // msgBox.exec()
|
||||||
} else {
|
} else {
|
||||||
// now send the prepared transaction
|
// now send the prepared transaction
|
||||||
WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
|
WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
|
||||||
|
@ -614,7 +672,9 @@ void SendCoinsDialog::setBalance(const interfaces::WalletBalances& balances)
|
||||||
if(model && model->getOptionsModel())
|
if(model && model->getOptionsModel())
|
||||||
{
|
{
|
||||||
CAmount balance = balances.balance;
|
CAmount balance = balances.balance;
|
||||||
if (model->wallet().privateKeysDisabled()) {
|
if (model->wallet().hasExternalSigner()) {
|
||||||
|
ui->labelBalanceName->setText(tr("External balance:"));
|
||||||
|
} else if (model->wallet().privateKeysDisabled()) {
|
||||||
balance = balances.watch_only_balance;
|
balance = balances.watch_only_balance;
|
||||||
ui->labelBalanceName->setText(tr("Watch-only balance:"));
|
ui->labelBalanceName->setText(tr("Watch-only balance:"));
|
||||||
}
|
}
|
||||||
|
@ -698,7 +758,7 @@ void SendCoinsDialog::on_buttonMinimizeFee_clicked()
|
||||||
void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry)
|
void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry)
|
||||||
{
|
{
|
||||||
// Include watch-only for wallets without private key
|
// Include watch-only for wallets without private key
|
||||||
m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled();
|
m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner();
|
||||||
|
|
||||||
// Calculate available amount to send.
|
// Calculate available amount to send.
|
||||||
CAmount amount = model->wallet().getAvailableBalance(*m_coin_control);
|
CAmount amount = model->wallet().getAvailableBalance(*m_coin_control);
|
||||||
|
@ -753,7 +813,7 @@ void SendCoinsDialog::updateCoinControlState()
|
||||||
m_coin_control->m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
|
m_coin_control->m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
|
||||||
m_coin_control->m_signal_bip125_rbf = ui->optInRBF->isChecked();
|
m_coin_control->m_signal_bip125_rbf = ui->optInRBF->isChecked();
|
||||||
// Include watch-only for wallets without private key
|
// Include watch-only for wallets without private key
|
||||||
m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled();
|
m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state) {
|
void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state) {
|
||||||
|
|
|
@ -26,6 +26,11 @@ CTransactionRef& WalletModelTransaction::getWtx()
|
||||||
return wtx;
|
return wtx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WalletModelTransaction::setWtx(const CTransactionRef& newTx)
|
||||||
|
{
|
||||||
|
wtx = newTx;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned int WalletModelTransaction::getTransactionSize()
|
unsigned int WalletModelTransaction::getTransactionSize()
|
||||||
{
|
{
|
||||||
return wtx ? GetVirtualTransactionSize(*wtx) : 0;
|
return wtx ? GetVirtualTransactionSize(*wtx) : 0;
|
||||||
|
|
|
@ -27,6 +27,8 @@ public:
|
||||||
QList<SendCoinsRecipient> getRecipients() const;
|
QList<SendCoinsRecipient> getRecipients() const;
|
||||||
|
|
||||||
CTransactionRef& getWtx();
|
CTransactionRef& getWtx();
|
||||||
|
void setWtx(const CTransactionRef&);
|
||||||
|
|
||||||
unsigned int getTransactionSize();
|
unsigned int getTransactionSize();
|
||||||
|
|
||||||
void setTransactionFee(const CAmount& newFee);
|
void setTransactionFee(const CAmount& newFee);
|
||||||
|
|
Loading…
Add table
Reference in a new issue