From 86d21a34baacb7d317b8774b3409781bb3dde037 Mon Sep 17 00:00:00 2001 From: Dusan Klinec Date: Sat, 11 Apr 2020 12:43:21 +0200 Subject: [PATCH] trezor: support new passphrase entry mechanism - passphrase can be prompted also when wallet is running (thus added to the wallet listener) - device/host decision is now made on the host --- components/DevicePassphraseDialog.qml | 104 +++++++++++++ main.qml | 48 +++--- monero-wallet-gui.pro | 3 + qml.qrc | 1 + src/CMakeLists.txt | 3 + src/libwalletqt/PassphraseHelper.cpp | 70 +++++++++ src/libwalletqt/PassphraseHelper.h | 74 +++++++++ src/libwalletqt/Wallet.cpp | 73 ++------- src/libwalletqt/Wallet.h | 11 +- src/libwalletqt/WalletListenerImpl.cpp | 97 ++++++++++++ src/libwalletqt/WalletListenerImpl.h | 68 ++++++++ src/libwalletqt/WalletManager.cpp | 74 ++++----- src/libwalletqt/WalletManager.h | 17 +- src/qt/ScopeGuard.h | 205 +++++++++++++++++++++++++ wizard/WizardController.qml | 17 +- 15 files changed, 732 insertions(+), 133 deletions(-) create mode 100644 components/DevicePassphraseDialog.qml create mode 100644 src/libwalletqt/PassphraseHelper.cpp create mode 100644 src/libwalletqt/PassphraseHelper.h create mode 100644 src/libwalletqt/WalletListenerImpl.cpp create mode 100644 src/libwalletqt/WalletListenerImpl.h create mode 100644 src/qt/ScopeGuard.h diff --git a/components/DevicePassphraseDialog.qml b/components/DevicePassphraseDialog.qml new file mode 100644 index 00000000..4bbe2fab --- /dev/null +++ b/components/DevicePassphraseDialog.qml @@ -0,0 +1,104 @@ +// Copyright (c) 2014-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import QtQuick 2.9 +import "." as MoneroComponents + +Item { + id: root + + property var onAcceptedCallback + property var onWalletEntryCallback + property var onRejectedCallback + + function open(canEnterOnDevice_) { + var canEnterOnDevice = canEnterOnDevice_ !== null ? canEnterOnDevice_ : canEnterOnDevice + root.visible = true; + + if (canEnterOnDevice) { + entryChooserDialog.okText = qsTr("Hardware wallet") + entryChooserDialog.cancelText = qsTr("Computer") + entryChooserDialog.open() + } else { + openPassphraseDialog() + } + } + + function openPassphraseDialog() { + root.visible = true + passphraseDialog.openPassphraseDialog() + } + + function close() { + root.visible = false; + if (entryChooserDialog.visible) + entryChooserDialog.close() + if (passphraseDialog.visible) + passphraseDialog.close() + } + + StandardDialog { + id: entryChooserDialog + title: qsTr("Hardware wallet passphrase") + translationManager.emptyString + text: qsTr("Please select where you want to enter passphrase.\nIt is recommended to enter passphrase on the hardware wallet for better security.") + translationManager.emptyString + + onAccepted: { + if (onWalletEntryCallback){ + onWalletEntryCallback() + } + } + + onRejected: { + openPassphraseDialog() + } + + onCloseCallback: { + root.close() + } + } + + PasswordDialog { + id: passphraseDialog + anchors.fill: parent + passphraseDialogMode: true + + onAcceptedPassphrase: { + if (onAcceptedCallback) + onAcceptedCallback(passphraseDialog.password); + } + + onRejectedPassphrase: { + if (onRejectedCallback) + onRejectedCallback(); + } + + onCloseCallback: { + root.close() + } + } +} diff --git a/main.qml b/main.qml index ace2064a..bbb6f2fa 100644 --- a/main.qml +++ b/main.qml @@ -293,6 +293,7 @@ ApplicationWindow { currentWallet.connectionStatusChanged.disconnect(onWalletConnectionStatusChanged) currentWallet.deviceButtonRequest.disconnect(onDeviceButtonRequest); currentWallet.deviceButtonPressed.disconnect(onDeviceButtonPressed); + currentWallet.walletPassphraseNeeded.disconnect(onWalletPassphraseNeededWallet); currentWallet.transactionCommitted.disconnect(onTransactionCommitted); middlePanel.paymentClicked.disconnect(handlePayment); middlePanel.sweepUnmixableClicked.disconnect(handleSweepUnmixable); @@ -360,6 +361,7 @@ ApplicationWindow { currentWallet.connectionStatusChanged.connect(onWalletConnectionStatusChanged) currentWallet.deviceButtonRequest.connect(onDeviceButtonRequest); currentWallet.deviceButtonPressed.connect(onDeviceButtonPressed); + currentWallet.walletPassphraseNeeded.connect(onWalletPassphraseNeededWallet); currentWallet.transactionCommitted.connect(onTransactionCommitted); middlePanel.paymentClicked.connect(handlePayment); middlePanel.sweepUnmixableClicked.connect(handleSweepUnmixable); @@ -558,19 +560,32 @@ ApplicationWindow { } } - function onWalletPassphraseNeeded(){ + function onWalletPassphraseNeededManager(on_device){ + onWalletPassphraseNeeded(walletManager, on_device) + } + + function onWalletPassphraseNeededWallet(on_device){ + onWalletPassphraseNeeded(currentWallet, on_device) + } + + function onWalletPassphraseNeeded(handler, on_device){ hideProcessingSplash(); console.log(">>> wallet passphrase needed: ") - passwordDialog.onAcceptedPassphraseCallback = function() { - walletManager.onPassphraseEntered(passwordDialog.password); + devicePassphraseDialog.onAcceptedCallback = function(passphrase) { + handler.onPassphraseEntered(passphrase, false, false); appWindow.onWalletOpening(); } - passwordDialog.onRejectedPassphraseCallback = function() { - walletManager.onPassphraseEntered("", true); + devicePassphraseDialog.onWalletEntryCallback = function() { + handler.onPassphraseEntered("", true, false); appWindow.onWalletOpening(); } - passwordDialog.openPassphraseDialog() + devicePassphraseDialog.onRejectedCallback = function() { + handler.onPassphraseEntered("", false, true); + appWindow.onWalletOpening(); + } + + devicePassphraseDialog.open(on_device) } function onWalletUpdate() { @@ -1295,7 +1310,7 @@ ApplicationWindow { walletManager.deviceButtonRequest.connect(onDeviceButtonRequest); walletManager.deviceButtonPressed.connect(onDeviceButtonPressed); walletManager.checkUpdatesComplete.connect(onWalletCheckUpdatesComplete); - walletManager.walletPassphraseNeeded.connect(onWalletPassphraseNeeded); + walletManager.walletPassphraseNeeded.connect(onWalletPassphraseNeededManager); IPC.uriHandler.connect(onUriHandler); if(typeof daemonManager != "undefined") { @@ -1512,8 +1527,6 @@ ApplicationWindow { anchors.fill: parent property var onAcceptedCallback property var onRejectedCallback - property var onAcceptedPassphraseCallback - property var onRejectedPassphraseCallback onAccepted: { if (onAcceptedCallback) onAcceptedCallback(); @@ -1537,14 +1550,13 @@ ApplicationWindow { informationPopup.open(); } onRejectedNewPassword: {} - onAcceptedPassphrase: { - if (onAcceptedPassphraseCallback) - onAcceptedPassphraseCallback(); - } - onRejectedPassphrase: { - if (onRejectedPassphraseCallback) - onRejectedPassphraseCallback(); - } + } + + DevicePassphraseDialog { + id: devicePassphraseDialog + visible: false + z: parent.z + 1 + anchors.fill: parent } InputDialog { @@ -1708,7 +1720,7 @@ ApplicationWindow { anchors.fill: blurredArea source: blurredArea radius: 64 - visible: passwordDialog.visible || inputDialog.visible || splash.visible || updateDialog.visible + visible: passwordDialog.visible || inputDialog.visible || splash.visible || updateDialog.visible || devicePassphraseDialog.visible } diff --git a/monero-wallet-gui.pro b/monero-wallet-gui.pro index 68158bbb..c2a9ea4a 100644 --- a/monero-wallet-gui.pro +++ b/monero-wallet-gui.pro @@ -53,6 +53,7 @@ HEADERS += \ src/main/oscursor.h \ src/libwalletqt/WalletManager.h \ src/libwalletqt/Wallet.h \ + src/libwalletqt/PassphraseHelper.h \ src/libwalletqt/PendingTransaction.h \ src/libwalletqt/TransactionHistory.h \ src/libwalletqt/TransactionInfo.h \ @@ -91,7 +92,9 @@ SOURCES += src/main/main.cpp \ src/main/clipboardAdapter.cpp \ src/main/oscursor.cpp \ src/libwalletqt/WalletManager.cpp \ + src/libwalletqt/WalletListenerImpl.cpp \ src/libwalletqt/Wallet.cpp \ + src/libwalletqt/PassphraseHelper.cpp \ src/libwalletqt/PendingTransaction.cpp \ src/libwalletqt/TransactionHistory.cpp \ src/libwalletqt/TransactionInfo.cpp \ diff --git a/qml.qrc b/qml.qrc index eb24cc20..84e1c1cc 100644 --- a/qml.qrc +++ b/qml.qrc @@ -99,6 +99,7 @@ components/ProcessingSplash.qml components/ProgressBar.qml components/StandardDialog.qml + components/DevicePassphraseDialog.qml pages/Sign.qml components/DaemonManagerDialog.qml version.js diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index de84824b..3c45c5c4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,7 +15,9 @@ file(GLOB SOURCE_FILES "main/*.h" "main/*.cpp" "libwalletqt/WalletManager.cpp" + "libwalletqt/WalletListenerImpl.cpp" "libwalletqt/Wallet.cpp" + "libwalletqt/PassphraseHelper.cpp" "libwalletqt/PendingTransaction.cpp" "libwalletqt/TransactionHistory.cpp" "libwalletqt/TransactionInfo.cpp" @@ -29,6 +31,7 @@ file(GLOB SOURCE_FILES "libwalletqt/UnsignedTransaction.cpp" "libwalletqt/WalletManager.h" "libwalletqt/Wallet.h" + "libwalletqt/PassphraseHelper.h" "libwalletqt/PendingTransaction.h" "libwalletqt/TransactionHistory.h" "libwalletqt/TransactionInfo.h" diff --git a/src/libwalletqt/PassphraseHelper.cpp b/src/libwalletqt/PassphraseHelper.cpp new file mode 100644 index 00000000..9e851f1b --- /dev/null +++ b/src/libwalletqt/PassphraseHelper.cpp @@ -0,0 +1,70 @@ +// Copyright (c) 2014-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "PassphraseHelper.h" +#include +#include + +Monero::optional PassphraseHelper::onDevicePassphraseRequest(bool & on_device) +{ + qDebug() << __FUNCTION__; + QMutexLocker locker(&m_mutex_pass); + m_passphrase_on_device = true; + m_passphrase_abort = false; + + if (m_prompter != nullptr){ + m_prompter->onWalletPassphraseNeeded(on_device); + } + + m_cond_pass.wait(&m_mutex_pass); + + if (m_passphrase_abort) + { + throw std::runtime_error("Passphrase entry abort"); + } + + on_device = m_passphrase_on_device; + if (!on_device) { + auto tmpPass = m_passphrase.toStdString(); + m_passphrase = QString(); + return Monero::optional(tmpPass); + } else { + return Monero::optional(); + } +} + +void PassphraseHelper::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) +{ + qDebug() << __FUNCTION__; + QMutexLocker locker(&m_mutex_pass); + m_passphrase = passphrase; + m_passphrase_abort = entry_abort; + m_passphrase_on_device = enter_on_device; + + m_cond_pass.wakeAll(); +} diff --git a/src/libwalletqt/PassphraseHelper.h b/src/libwalletqt/PassphraseHelper.h new file mode 100644 index 00000000..03274992 --- /dev/null +++ b/src/libwalletqt/PassphraseHelper.h @@ -0,0 +1,74 @@ +// Copyright (c) 2014-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef MONERO_GUI_PASSPHRASEHELPER_H +#define MONERO_GUI_PASSPHRASEHELPER_H + +#include +#include +#include +#include +#include +#include + +/** + * Implements component responsible for showing entry prompt to the user, + * typically Wallet / Wallet manager. + */ +class PassprasePrompter { +public: + virtual void onWalletPassphraseNeeded(bool onDevice) = 0; +}; + +/** + * Implements receiver of the passphrase responsible for passing it back to the wallet, + * typically wallet listener. + */ +class PassphraseReceiver { +public: + virtual void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) = 0; +}; + +class PassphraseHelper { +public: + PassphraseHelper(PassprasePrompter * prompter=nullptr): m_prompter(prompter) {}; + PassphraseHelper(const PassphraseHelper & h): PassphraseHelper(h.m_prompter) {}; + Monero::optional onDevicePassphraseRequest(bool & on_device); + void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort); + +private: + PassprasePrompter * m_prompter; + QWaitCondition m_cond_pass; + QMutex m_mutex_pass; + QString m_passphrase; + bool m_passphrase_abort; + bool m_passphrase_on_device; + +}; + +#endif //MONERO_GUI_PASSPHRASEHELPER_H diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index 355ce6a9..2d7f6722 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -58,66 +58,6 @@ namespace { static constexpr char ATTRIBUTE_SUBADDRESS_ACCOUNT[] ="gui.subaddress_account"; } -class WalletListenerImpl : public Monero::WalletListener -{ -public: - WalletListenerImpl(Wallet * w) - : m_wallet(w) - { - - } - - virtual void moneySpent(const std::string &txId, uint64_t amount) override - { - qDebug() << __FUNCTION__; - emit m_wallet->moneySpent(QString::fromStdString(txId), amount); - } - - - virtual void moneyReceived(const std::string &txId, uint64_t amount) override - { - qDebug() << __FUNCTION__; - emit m_wallet->moneyReceived(QString::fromStdString(txId), amount); - } - - virtual void unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) override - { - qDebug() << __FUNCTION__; - emit m_wallet->unconfirmedMoneyReceived(QString::fromStdString(txId), amount); - } - - virtual void newBlock(uint64_t height) override - { - // qDebug() << __FUNCTION__; - emit m_wallet->newBlock(height, m_wallet->daemonBlockChainTargetHeight()); - } - - virtual void updated() override - { - emit m_wallet->updated(); - } - - // called when wallet refreshed by background thread or explicitly - virtual void refreshed() override - { - qDebug() << __FUNCTION__; - emit m_wallet->refreshed(); - } - - virtual void onDeviceButtonRequest(uint64_t code) override - { - emit m_wallet->deviceButtonRequest(code); - } - - virtual void onDeviceButtonPressed() override - { - emit m_wallet->deviceButtonPressed(); - } - -private: - Wallet * m_wallet; -}; - Wallet::Wallet(QObject * parent) : Wallet(nullptr, parent) { @@ -1021,6 +961,19 @@ void Wallet::keyReuseMitigation2(bool mitigation) m_walletImpl->keyReuseMitigation2(mitigation); } +void Wallet::onWalletPassphraseNeeded(bool on_device) +{ + emit this->walletPassphraseNeeded(on_device); +} + +void Wallet::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) +{ + if (m_walletListener != nullptr) + { + m_walletListener->onPassphraseEntered(passphrase, enter_on_device, entry_abort); + } +} + Wallet::Wallet(Monero::Wallet *w, QObject *parent) : QObject(parent) , m_walletImpl(w) diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 3450b1ce..f333f935 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -41,6 +41,8 @@ #include "PendingTransaction.h" // we need to have an access to the PendingTransaction::Priority enum here; #include "UnsignedTransaction.h" #include "NetworkType.h" +#include "PassphraseHelper.h" +#include "WalletListenerImpl.h" namespace Monero { struct Wallet; // forward declaration @@ -57,7 +59,7 @@ class SubaddressModel; class SubaddressAccount; class SubaddressAccountModel; -class Wallet : public QObject +class Wallet : public QObject, public PassprasePrompter { Q_OBJECT Q_PROPERTY(bool disconnected READ disconnected NOTIFY disconnectedChanged) @@ -348,6 +350,10 @@ public: Q_INVOKABLE void segregationHeight(quint64 height); Q_INVOKABLE void keyReuseMitigation2(bool mitigation); + // Passphrase entry for hardware wallets + Q_INVOKABLE void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort=false); + virtual void onWalletPassphraseNeeded(bool on_device) override; + // TODO: setListenter() when it implemented in API signals: // emitted on every event happened with wallet @@ -367,6 +373,7 @@ signals: void walletCreationHeightChanged(); void deviceButtonRequest(quint64 buttonCode); void deviceButtonPressed(); + void walletPassphraseNeeded(bool onDevice); void transactionCommitted(bool status, PendingTransaction *t, const QStringList& txid); void heightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight) const; void deviceShowAddressShowed(); @@ -432,7 +439,7 @@ private: bool m_connectionStatusRunning; QString m_daemonUsername; QString m_daemonPassword; - Monero::WalletListener *m_walletListener; + WalletListenerImpl *m_walletListener; FutureScheduler m_scheduler; QMutex m_storeMutex; }; diff --git a/src/libwalletqt/WalletListenerImpl.cpp b/src/libwalletqt/WalletListenerImpl.cpp new file mode 100644 index 00000000..efcfa112 --- /dev/null +++ b/src/libwalletqt/WalletListenerImpl.cpp @@ -0,0 +1,97 @@ +// Copyright (c) 2014-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "WalletListenerImpl.h" +#include "Wallet.h" + +WalletListenerImpl::WalletListenerImpl(Wallet * w) + : m_wallet(w) + , m_phelper(w) +{ + +} + +void WalletListenerImpl::moneySpent(const std::string &txId, uint64_t amount) +{ + qDebug() << __FUNCTION__; + emit m_wallet->moneySpent(QString::fromStdString(txId), amount); +} + +void WalletListenerImpl::moneyReceived(const std::string &txId, uint64_t amount) +{ + qDebug() << __FUNCTION__; + emit m_wallet->moneyReceived(QString::fromStdString(txId), amount); +} + +void WalletListenerImpl::unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) +{ + qDebug() << __FUNCTION__; + emit m_wallet->unconfirmedMoneyReceived(QString::fromStdString(txId), amount); +} + +void WalletListenerImpl::newBlock(uint64_t height) +{ + // qDebug() << __FUNCTION__; + emit m_wallet->newBlock(height, m_wallet->daemonBlockChainTargetHeight()); +} + +void WalletListenerImpl::updated() +{ + emit m_wallet->updated(); +} + +// called when wallet refreshed by background thread or explicitly +void WalletListenerImpl::refreshed() +{ + qDebug() << __FUNCTION__; + emit m_wallet->refreshed(); +} + +void WalletListenerImpl::onDeviceButtonRequest(uint64_t code) +{ + qDebug() << __FUNCTION__; + emit m_wallet->deviceButtonRequest(code); +} + +void WalletListenerImpl::onDeviceButtonPressed() +{ + qDebug() << __FUNCTION__; + emit m_wallet->deviceButtonPressed(); +} + +void WalletListenerImpl::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) +{ + qDebug() << __FUNCTION__; + m_phelper.onPassphraseEntered(passphrase, enter_on_device, entry_abort); +} + +Monero::optional WalletListenerImpl::onDevicePassphraseRequest(bool & on_device) +{ + qDebug() << __FUNCTION__; + return m_phelper.onDevicePassphraseRequest(on_device); +} diff --git a/src/libwalletqt/WalletListenerImpl.h b/src/libwalletqt/WalletListenerImpl.h new file mode 100644 index 00000000..9a134547 --- /dev/null +++ b/src/libwalletqt/WalletListenerImpl.h @@ -0,0 +1,68 @@ +// Copyright (c) 2014-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef MONERO_GUI_WALLETLISTENERIMPL_H +#define MONERO_GUI_WALLETLISTENERIMPL_H + +#include "wallet/api/wallet2_api.h" +#include "PassphraseHelper.h" + +class Wallet; + +class WalletListenerImpl : public Monero::WalletListener, public PassphraseReceiver +{ +public: + WalletListenerImpl(Wallet * w); + + virtual void moneySpent(const std::string &txId, uint64_t amount) override; + + virtual void moneyReceived(const std::string &txId, uint64_t amount) override; + + virtual void unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) override; + + virtual void newBlock(uint64_t height) override; + + virtual void updated() override; + + // called when wallet refreshed by background thread or explicitly + virtual void refreshed() override; + + virtual void onDeviceButtonRequest(uint64_t code) override; + + virtual void onDeviceButtonPressed() override; + + virtual void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) override; + + virtual Monero::optional onDevicePassphraseRequest(bool & on_device) override; + +private: + Wallet * m_wallet; + PassphraseHelper m_phelper; +}; + +#endif //MONERO_GUI_WALLETLISTENERIMPL_H diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index a5c9e0ff..62836149 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -42,11 +42,12 @@ #include #include "qt/updater.h" +#include "qt/ScopeGuard.h" -class WalletPassphraseListenerImpl : public Monero::WalletListener +class WalletPassphraseListenerImpl : public Monero::WalletListener, public PassphraseReceiver { public: - WalletPassphraseListenerImpl(WalletManager * mgr): m_mgr(mgr), m_wallet(nullptr) {} + WalletPassphraseListenerImpl(WalletManager * mgr): m_mgr(mgr), m_phelper(mgr) {} virtual void moneySpent(const std::string &txId, uint64_t amount) override { (void)txId; (void)amount; }; virtual void moneyReceived(const std::string &txId, uint64_t amount) override { (void)txId; (void)amount; }; @@ -55,43 +56,33 @@ public: virtual void updated() override {}; virtual void refreshed() override {}; - virtual Monero::optional onDevicePassphraseRequest(bool on_device) override + virtual void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) override { qDebug() << __FUNCTION__; - if (on_device) return Monero::optional(); - - m_mgr->onWalletPassphraseNeeded(m_wallet); - - if (m_mgr->m_passphrase_abort) - { - throw std::runtime_error("Passphrase entry abort"); - } - - auto tmpPass = m_mgr->m_passphrase.toStdString(); - m_mgr->m_passphrase = QString(); - - return Monero::optional(tmpPass); + m_phelper.onPassphraseEntered(passphrase, enter_on_device, entry_abort); } - virtual void onDeviceButtonRequest(uint64_t code) override + virtual Monero::optional onDevicePassphraseRequest(bool & on_device) override { - emit m_mgr->deviceButtonRequest(code); + qDebug() << __FUNCTION__; + return m_phelper.onDevicePassphraseRequest(on_device); } - virtual void onDeviceButtonPressed() override + virtual void onDeviceButtonRequest(uint64_t code) override { - emit m_mgr->deviceButtonPressed(); + qDebug() << __FUNCTION__; + emit m_mgr->deviceButtonRequest(code); } - virtual void onSetWallet(Monero::Wallet * wallet) override + virtual void onDeviceButtonPressed() override { qDebug() << __FUNCTION__; - m_wallet = wallet; + emit m_mgr->deviceButtonPressed(); } private: WalletManager * m_mgr; - Monero::Wallet * m_wallet; + PassphraseHelper m_phelper; }; WalletManager * WalletManager::m_instance = nullptr; @@ -123,6 +114,13 @@ Wallet *WalletManager::openWallet(const QString &path, const QString &password, { QMutexLocker locker(&m_mutex); WalletPassphraseListenerImpl tmpListener(this); + m_mutex_passphraseReceiver.lock(); + m_passphraseReceiver = &tmpListener; + m_mutex_passphraseReceiver.unlock(); + const auto cleanup = sg::make_scope_guard([this]() noexcept { + QMutexLocker passphrase_locker(&m_mutex_passphraseReceiver); + this->m_passphraseReceiver = nullptr; + }); if (m_currentWallet) { qDebug() << "Closing open m_currentWallet" << m_currentWallet; @@ -186,6 +184,13 @@ Wallet *WalletManager::createWalletFromDevice(const QString &path, const QString { QMutexLocker locker(&m_mutex); WalletPassphraseListenerImpl tmpListener(this); + m_mutex_passphraseReceiver.lock(); + m_passphraseReceiver = &tmpListener; + m_mutex_passphraseReceiver.unlock(); + const auto cleanup = sg::make_scope_guard([this]() noexcept { + QMutexLocker passphrase_locker(&m_mutex_passphraseReceiver); + this->m_passphraseReceiver = nullptr; + }); if (m_currentWallet) { qDebug() << "Closing open m_currentWallet" << m_currentWallet; @@ -529,6 +534,7 @@ bool WalletManager::clearWalletCache(const QString &wallet_path) const WalletManager::WalletManager(QObject *parent) : QObject(parent) + , m_passphraseReceiver(nullptr) , m_scheduler(this) { m_pimpl = Monero::WalletManagerFactory::getWalletManager(); @@ -539,22 +545,16 @@ WalletManager::~WalletManager() m_scheduler.shutdownWaitForFinished(); } -void WalletManager::onWalletPassphraseNeeded(Monero::Wallet *) +void WalletManager::onWalletPassphraseNeeded(bool on_device) { - m_mutex_pass.lock(); - m_passphrase_abort = false; - emit this->walletPassphraseNeeded(); - - m_cond_pass.wait(&m_mutex_pass); - m_mutex_pass.unlock(); + emit this->walletPassphraseNeeded(on_device); } -void WalletManager::onPassphraseEntered(const QString &passphrase, bool entry_abort) +void WalletManager::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) { - m_mutex_pass.lock(); - m_passphrase = passphrase; - m_passphrase_abort = entry_abort; - - m_cond_pass.wakeAll(); - m_mutex_pass.unlock(); + QMutexLocker locker(&m_mutex_passphraseReceiver); + if (m_passphraseReceiver != nullptr) + { + m_passphraseReceiver->onPassphraseEntered(passphrase, enter_on_device, entry_abort); + } } diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h index 01767716..3564ad61 100644 --- a/src/libwalletqt/WalletManager.h +++ b/src/libwalletqt/WalletManager.h @@ -38,13 +38,14 @@ #include #include "qt/FutureScheduler.h" #include "NetworkType.h" +#include "PassphraseHelper.h" class Wallet; namespace Monero { struct WalletManager; } -class WalletManager : public QObject +class WalletManager : public QObject, public PassprasePrompter { Q_OBJECT Q_PROPERTY(bool connected READ connected) @@ -185,14 +186,14 @@ public: // clear/rename wallet cache Q_INVOKABLE bool clearWalletCache(const QString &fileName) const; - Q_INVOKABLE void onWalletPassphraseNeeded(Monero::Wallet * wallet); - Q_INVOKABLE void onPassphraseEntered(const QString &passphrase, bool entry_abort=false); + Q_INVOKABLE void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort=false); + virtual void onWalletPassphraseNeeded(bool on_device) override; signals: void walletOpened(Wallet * wallet); void walletCreated(Wallet * wallet); - void walletPassphraseNeeded(); + void walletPassphraseNeeded(bool onDevice); void deviceButtonRequest(quint64 buttonCode); void deviceButtonPressed(); void checkUpdatesComplete( @@ -216,12 +217,8 @@ private: Monero::WalletManager * m_pimpl; mutable QMutex m_mutex; QPointer m_currentWallet; - - QWaitCondition m_cond_pass; - QMutex m_mutex_pass; - QString m_passphrase; - bool m_passphrase_abort; - + PassphraseReceiver * m_passphraseReceiver; + QMutex m_mutex_passphraseReceiver; FutureScheduler m_scheduler; }; diff --git a/src/qt/ScopeGuard.h b/src/qt/ScopeGuard.h new file mode 100644 index 00000000..6d5df044 --- /dev/null +++ b/src/qt/ScopeGuard.h @@ -0,0 +1,205 @@ +// Author: ricab +// Source: https://github.com/ricab/scope_guard +// +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or +// distribute this software, either in source code form or as a compiled +// binary, for any purpose, commercial or non-commercial, and by any +// means. +// +// In jurisdictions that recognize copyright laws, the author or authors +// of this software dedicate any and all copyright interest in the +// software to the public domain. We make this dedication for the benefit +// of the public at large and to the detriment of our heirs and +// successors. We intend this dedication to be an overt act of +// relinquishment in perpetuity of all present and future rights to this +// software under copyright law. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// For more information, please refer to + +#ifndef SCOPE_GUARD_HPP_ +#define SCOPE_GUARD_HPP_ + +#include +#include + +#if __cplusplus >= 201703L && defined(SG_REQUIRE_NOEXCEPT_IN_CPP17) +#define SG_REQUIRE_NOEXCEPT +#endif + +namespace sg +{ + namespace detail + { + /* --- Some custom type traits --- */ + + // Type trait determining whether a type is callable with no arguments + template + struct is_noarg_callable_t + : public std::false_type + {}; // in general, false + + template + struct is_noarg_callable_t()())> + : public std::true_type + {}; // only true when call expression valid + + // Type trait determining whether a no-argument callable returns void + template + struct returns_void_t + : public std::is_same()())> + {}; + + /* Type trait determining whether a no-arg callable is nothrow invocable if + required. This is where SG_REQUIRE_NOEXCEPT logic is encapsulated. */ + template + struct is_nothrow_invocable_if_required_t + : public +#ifdef SG_REQUIRE_NOEXCEPT + std::is_nothrow_invocable /* Note: _r variants not enough to + confirm void return: any return can be + discarded so all returns are + compatible with void */ +#else + std::true_type +#endif + {}; + + // logic AND of two or more type traits + template + struct and_t : public and_t> + {}; // for more than two arguments + + template + struct and_t : public std::conditional::type + {}; // for two arguments + + // Type trait determining whether a type is a proper scope_guard callback. + template + struct is_proper_sg_callback_t + : public and_t, + returns_void_t, + is_nothrow_invocable_if_required_t, + std::is_nothrow_destructible> + {}; + + + /* --- The actual scope_guard template --- */ + + template::value>::type> + class scope_guard; + + + /* --- Now the friend maker --- */ + + template + detail::scope_guard make_scope_guard(Callback&& callback) + noexcept(std::is_nothrow_constructible::value); /* + we need this in the inner namespace due to MSVC bugs preventing + sg::detail::scope_guard from befriending a sg::make_scope_guard + template instance in the parent namespace (see https://is.gd/xFfFhE). */ + + + /* --- The template specialization that actually defines the class --- */ + + template + class scope_guard final + { + public: + typedef Callback callback_type; + + scope_guard(scope_guard&& other) + noexcept(std::is_nothrow_constructible::value); + + ~scope_guard() noexcept; // highlight noexcept dtor + + void dismiss() noexcept; + + public: + scope_guard() = delete; + scope_guard(const scope_guard&) = delete; + scope_guard& operator=(const scope_guard&) = delete; + scope_guard& operator=(scope_guard&&) = delete; + + private: + explicit scope_guard(Callback&& callback) + noexcept(std::is_nothrow_constructible::value); /* + meant for friends only */ + + friend scope_guard make_scope_guard(Callback&&) + noexcept(std::is_nothrow_constructible::value); /* + only make_scope_guard can create scope_guards from scratch (i.e. non-move) + */ + + private: + Callback m_callback; + bool m_active; + + }; + +} // namespace detail + + +/* --- Now the single public maker function --- */ + +using detail::make_scope_guard; // see comment on declaration above + +} // namespace sg + +//////////////////////////////////////////////////////////////////////////////// +template +sg::detail::scope_guard::scope_guard(Callback&& callback) +noexcept(std::is_nothrow_constructible::value) + : m_callback(std::forward(callback)) /* use () instead of {} because + of DR 1467 (https://is.gd/WHmWuo), which still impacts older compilers + (e.g. GCC 4.x and clang <=3.6, see https://godbolt.org/g/TE9tPJ and + https://is.gd/Tsmh8G) */ + , m_active{true} +{} + +//////////////////////////////////////////////////////////////////////////////// +template +sg::detail::scope_guard::~scope_guard() noexcept +{ + if(m_active) + m_callback(); +} + +//////////////////////////////////////////////////////////////////////////////// +template +sg::detail::scope_guard::scope_guard(scope_guard&& other) +noexcept(std::is_nothrow_constructible::value) + : m_callback(std::forward(other.m_callback)) // idem + , m_active{std::move(other.m_active)} +{ + other.m_active = false; +} + +//////////////////////////////////////////////////////////////////////////////// +template +inline void sg::detail::scope_guard::dismiss() noexcept +{ + m_active = false; +} + +//////////////////////////////////////////////////////////////////////////////// +template +inline auto sg::detail::make_scope_guard(Callback&& callback) +noexcept(std::is_nothrow_constructible::value) +-> detail::scope_guard +{ + return detail::scope_guard{std::forward(callback)}; +} + +#endif /* SCOPE_GUARD_HPP_ */ diff --git a/wizard/WizardController.qml b/wizard/WizardController.qml index a1628b4f..b38d079f 100644 --- a/wizard/WizardController.qml +++ b/wizard/WizardController.qml @@ -490,19 +490,24 @@ Rectangle { walletCreatedFromDevice(success); } - function onWalletPassphraseNeeded(){ + function onWalletPassphraseNeeded(on_device){ splash.close() console.log(">>> wallet passphrase needed: "); - passwordDialog.onAcceptedPassphraseCallback = function() { - walletManager.onPassphraseEntered(passwordDialog.password); + devicePassphraseDialog.onAcceptedCallback = function(passphrase) { + walletManager.onPassphraseEntered(passphrase, false, false); creatingWalletDeviceSplash(); } - passwordDialog.onRejectedPassphraseCallback = function() { - walletManager.onPassphraseEntered("", true); + devicePassphraseDialog.onWalletEntryCallback = function() { + walletManager.onPassphraseEntered("", true, false); creatingWalletDeviceSplash(); } - passwordDialog.openPassphraseDialog() + devicePassphraseDialog.onRejectedCallback = function() { + walletManager.onPassphraseEntered("", false, true); + creatingWalletDeviceSplash(); + } + + devicePassphraseDialog.open(on_device) } function onDeviceButtonRequest(code){