]> Nutra Git (v2) - gamesguru/feather.git/commitdiff
Trezor support
authortobtoht <thotbot@protonmail.com>
Thu, 1 Jul 2021 21:00:47 +0000 (23:00 +0200)
committertobtoht <thotbot@protonmail.com>
Thu, 1 Jul 2021 21:00:47 +0000 (23:00 +0200)
19 files changed:
CMakeLists.txt
Dockerfile
Makefile
src/CMakeLists.txt
src/MainWindow.cpp
src/MainWindow.h
src/WindowManager.cpp
src/WindowManager.h
src/appcontext.cpp
src/appcontext.h
src/assets.qrc
src/assets/images/trezor_unpaired_white.png [new file with mode: 0644]
src/assets/images/trezor_white.png [new file with mode: 0644]
src/dialog/SignVerifyDialog.cpp
src/wizard/PageHardwareDevice.cpp
src/wizard/PageHardwareDevice.ui
src/wizard/PageWalletFile.cpp
src/wizard/WalletWizard.cpp
src/wizard/WalletWizard.h

index 5e52e002203d6269fef5f3ec002436d05d6cfd7c..4a730edbb02a193529337d47541de83e80a44f7c 100644 (file)
@@ -17,7 +17,7 @@ option(LOCALMONERO "Include LocalMonero module" ON)
 option(XMRIG "Include XMRig module" ON)
 option(TOR_BIN "Path to Tor binary to embed inside Feather" OFF)
 option(CHECK_UPDATES "Enable checking for application updates" OFF)
-option(USE_DEVICE_TREZOR "Trezor support compilation" OFF)
+option(USE_DEVICE_TREZOR "Trezor support compilation" ON)
 option(DONATE_BEG "Prompt donation window every once in a while" ON)
 option(WITH_SCANNER "Enable webcam QR scanner" OFF)
 
@@ -34,7 +34,7 @@ if(DEBUG)
     set(CMAKE_VERBOSE_MAKEFILE ON)
 endif()
 
-set(MONERO_HEAD "36fb05da3394505f8033ceb8806b28909617696f")
+set(MONERO_HEAD "9e7caf0efe035da08293232a73b41021911d1b5f")
 set(BUILD_GUI_DEPS ON)
 set(ARCH "x86-64")
 set(BUILD_64 ON)
@@ -73,6 +73,8 @@ add_subdirectory(monero)
 set_property(TARGET wallet_merged PROPERTY FOLDER "monero")
 get_directory_property(ARCH_WIDTH DIRECTORY "monero" DEFINITION ARCH_WIDTH)
 get_directory_property(UNBOUND_LIBRARY DIRECTORY "monero" DEFINITION UNBOUND_LIBRARY)
+get_directory_property(DEVICE_TREZOR_READY DIRECTORY "monero" DEFINITION DEVICE_TREZOR_READY)
+get_directory_property(TREZOR_DEP_LIBS DIRECTORY "monero" DEFINITION TREZOR_DEP_LIBS)
 
 include(CMakePackageConfigHelpers)
 include(VersionMonero)
index a16236162f1679ad232ddb73aba9f5109d0812f1..d77723a47e5f1311dc83599035d2180d40b40c65 100644 (file)
@@ -266,9 +266,11 @@ RUN git clone -b v4.1.1 --depth 1 https://github.com/fukuchi/libqrencode.git &&
 
 # monero-seed: Required for Feather
 # Tevador's 14 word seed library
+ADD contrib/monero-seed.patch .
 RUN git clone https://git.featherwallet.org/feather/monero-seed.git && \
     cd monero-seed && \
     git reset --hard 4674ef09b6faa6fe602ab5ae0b9ca8e1fd7d5e1b && \
+    git apply /monero-seed.patch && \
     cmake -DCMAKE_BUILD_TYPE=Release -Bbuild && \
     make -Cbuild -j$THREADS && \
     make -Cbuild install && \
index dc0c2a308d7664536a584aaffc47e5795c5bf205..c17ab0ef420ca131919d07f75e79ab522134e397 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -39,7 +39,7 @@ CMAKEFLAGS = \
        -DINSTALL_VENDORED_LIBUNBOUND=Off \
        -DMANUAL_SUBMODULES=1 \
        -DSTATIC=On \
-       -DUSE_DEVICE_TREZOR=Off \
+       -DUSE_DEVICE_TREZOR=On \
        $(CMAKEFLAGS_EXTRA)
 
 release-static: CMAKEFLAGS += -DBUILD_TAG="linux-x64"
index 56d93145166b24c38fc84bdf4ca4bb88d6f01142..4c369d86320804ba6a6b56c4a735f0df1553ae54 100644 (file)
@@ -250,6 +250,10 @@ target_link_libraries(feather
         ${LIBZIP_LIBRARIES}
         )
 
+if(DEVICE_TREZOR_READY)
+    target_link_libraries(feather ${TREZOR_DEP_LIBS})
+endif()
+
 if (WITH_SCANNER)
     target_link_libraries(feather
             Qt5::Multimedia
index 50d13c0c68ca48edde221af03166e43a3be06e9b..732b140d0bc641698bedce27942f896affab40ad 100644 (file)
@@ -151,7 +151,7 @@ void MainWindow::initStatusBar() {
     connect(m_statusBtnTor, &StatusBarButton::clicked, this, &MainWindow::menuTorClicked);
     this->statusBar()->addPermanentWidget(m_statusBtnTor);
 
-    m_statusBtnHwDevice = new StatusBarButton(icons()->icon("ledger.png"), "Ledger", this);
+    m_statusBtnHwDevice = new StatusBarButton(this->hardwareDevicePairedIcon(), this->getHardwareDevice(), this);
     connect(m_statusBtnHwDevice, &StatusBarButton::clicked, this, &MainWindow::menuHwDeviceClicked);
     this->statusBar()->addPermanentWidget(m_statusBtnHwDevice);
     m_statusBtnHwDevice->hide();
@@ -356,6 +356,8 @@ void MainWindow::initWalletContext() {
     connect(m_ctx.get(), &AppContext::createTransactionSuccess, this, &MainWindow::onCreateTransactionSuccess);
     connect(m_ctx.get(), &AppContext::transactionCommitted,     this, &MainWindow::onTransactionCommitted);
     connect(m_ctx.get(), &AppContext::deviceError,              this, &MainWindow::onDeviceError);
+    connect(m_ctx.get(), &AppContext::deviceButtonRequest,      this, &MainWindow::onDeviceButtonRequest);
+    connect(m_ctx.get(), &AppContext::deviceButtonPressed,      this, &MainWindow::onDeviceButtonPressed);
     connect(m_ctx.get(), &AppContext::initiateTransaction,      this, &MainWindow::onInitiateTransaction);
     connect(m_ctx.get(), &AppContext::endTransaction,           this, &MainWindow::onEndTransaction);
     connect(m_ctx.get(), &AppContext::customRestoreHeightSet,   this, &MainWindow::onCustomRestoreHeightSet);
@@ -801,6 +803,26 @@ void MainWindow::updateWidgetIcons() {
     m_localMoneroWidget->skinChanged();
 #endif
     ui->conversionWidget->skinChanged();
+
+    m_statusBtnHwDevice->setIcon(this->hardwareDevicePairedIcon());
+}
+
+QIcon MainWindow::hardwareDevicePairedIcon() {
+    QString filename;
+    if (m_ctx->wallet->isLedger())
+        filename = "ledger.png";
+    else if (m_ctx->wallet->isTrezor())
+        filename = ColorScheme::darkScheme ? "trezor_white.png" : "trezor.png";
+    return icons()->icon(filename);
+}
+
+QIcon MainWindow::hardwareDeviceUnpairedIcon() {
+    QString filename;
+    if (m_ctx->wallet->isLedger())
+        filename = "ledger_unpaired.png";
+    else if (m_ctx->wallet->isTrezor())
+        filename = ColorScheme::darkScheme ? "trezor_unpaired_white.png" : "trezor_unpaired.png";
+    return icons()->icon(filename);
 }
 
 void MainWindow::closeEvent(QCloseEvent *event) {
@@ -1065,7 +1087,8 @@ void MainWindow::onDeviceError(const QString &error) {
     if (m_showDeviceError) {
         return;
     }
-    m_statusBtnHwDevice->setIcon(icons()->icon("ledger_unpaired.png"));
+
+    m_statusBtnHwDevice->setIcon(this->hardwareDeviceUnpairedIcon());
     while (true) {
         m_showDeviceError = true;
         auto result = QMessageBox::question(this, "Hardware device", "Lost connection to hardware device. Attempt to reconnect?");
@@ -1080,11 +1103,38 @@ void MainWindow::onDeviceError(const QString &error) {
             return;
         }
     }
-    m_statusBtnHwDevice->setIcon(icons()->icon("ledger.png"));
+    m_statusBtnHwDevice->setIcon(this->hardwareDevicePairedIcon());
     m_ctx->wallet->startRefresh();
     m_showDeviceError = false;
 }
 
+void MainWindow::onDeviceButtonRequest(quint64 code) {
+    if (m_ctx->wallet->isTrezor()) {
+        switch (code) {
+            case 8: // Confirm refresh: Do you really want to start refresh?
+            {
+                if (m_constructingTransaction) { // This code is also used when signing a tx...
+                    break;
+                }
+
+                m_splashDialog->setMessage("Confirm refresh on device to proceed.");
+                m_splashDialog->setIcon(QPixmap(":/assets/images/confirmed.png"));
+                m_splashDialog->show();
+                m_splashDialog->setEnabled(true);
+                break;
+            }
+        }
+    }
+}
+
+void MainWindow::onDeviceButtonPressed() {
+    if (m_constructingTransaction) {
+        return;
+    }
+
+    m_splashDialog->hide();
+}
+
 void MainWindow::updateNetStats() {
     if (m_ctx->wallet == nullptr) {
         m_statusLabelNetStats->setText("");
index 6d33806dc07770d974de1c8a7702e58c4dc514b5..3d8557df4ae3afd691ac117f8b14c4e16583e0f9 100644 (file)
@@ -171,6 +171,8 @@ private slots:
     void showRestoreHeightDialog();
     void importTransaction();
     void onDeviceError(const QString &error);
+    void onDeviceButtonRequest(quint64 code);
+    void onDeviceButtonPressed();
     void menuHwDeviceClicked();
     void onUpdatesAvailable(const QJsonObject &updates);
     void toggleSearchbar(bool enabled);
@@ -205,6 +207,9 @@ private:
     void updateRecentlyOpened(const QString &filename);
     void updateWidgetIcons();
 
+    QIcon hardwareDevicePairedIcon();
+    QIcon hardwareDeviceUnpairedIcon();
+
     QScopedPointer<Ui::MainWindow> ui;
     WindowManager *m_windowManager;
     QSharedPointer<AppContext> m_ctx;
index 82d19baa68755f9833f58d12b7f87ba42bd3bd7b..52177bcfbee6a93242bda349324d68e3ad6519fa 100644 (file)
@@ -228,7 +228,7 @@ void WindowManager::tryCreateWallet(FeatherSeed seed, const QString &path, const
     this->onWalletOpened(wallet);
 }
 
-void WindowManager::tryCreateWalletFromDevice(const QString &path, const QString &password, int restoreHeight)
+void WindowManager::tryCreateWalletFromDevice(const QString &path, const QString &password, const QString &deviceName, int restoreHeight)
 {
     if (Utils::fileExists(path)) {
         auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path);
@@ -237,7 +237,7 @@ void WindowManager::tryCreateWalletFromDevice(const QString &path, const QString
     }
 
     m_openingWallet = true;
-    m_walletManager->createWalletFromDeviceAsync(path, password, constants::networkType, "Ledger", restoreHeight);
+    m_walletManager->createWalletFromDeviceAsync(path, password, constants::networkType, deviceName, restoreHeight);
 }
 
 void WindowManager::tryCreateWalletFromKeys(const QString &path, const QString &password, const QString &address,
@@ -294,16 +294,36 @@ void WindowManager::handleWalletError(const QString &message) {
 }
 
 void WindowManager::displayWalletErrorMessage(const QString &message) {
-    QString errMsg = message;
+    QString errMsg = QString("Error: %1").arg(message);
+
+    // Ledger
     if (message.contains("No device found")) {
-        errMsg += "\n\nThis wallet is backed by a hardware device. Make sure the Monero app is opened on the device.\n"
+        errMsg += "\n\nThis wallet is backed by a Ledger hardware device. Make sure the Monero app is opened on the device.\n"
                   "You may need to restart Feather before the device can get detected.";
     }
     if (message.contains("Unable to open device")) {
         errMsg += "\n\nThe device might be in use by a different application.";
 #if defined(Q_OS_LINUX)
         errMsg += "\n\nNote: On Linux you may need to follow the instructions in the link below before the device can be opened:\n"
-                  "<a>https://support.ledger.com/hc/en-us/articles/115005165269-Fix-connection-issues</a>";
+                  "https://support.ledger.com/hc/en-us/articles/115005165269-Fix-connection-issues";
+#endif
+    }
+
+    // TREZOR
+    if (message.contains("Unable to claim libusb device")) {
+        errMsg += "\n\nThis wallet is backed by a Trezor hardware device. Feather was unable to access the device. "
+                  "Please make sure it is not opened by another program and try again.";
+    }
+    if (message.contains("Cannot get a device address")) {
+        errMsg += "\n\nRestart the Trezor hardware device and try again.";
+    }
+
+    if (message.contains("Could not connect to the device Trezor") || message.contains("Device connect failed")) {
+        errMsg += "\n\nThis wallet is backed by a Trezor hardware device. Make sure the device is connected to your computer and unlocked.\n"
+                  "You may need to restart Feather before the device can be detected.";
+#if defined(Q_OS_LINUX)
+        errMsg += "\n\nNote: On Linux you may need to follow the instructions in the link below before the device can be opened:\n"
+                  "https://wiki.trezor.io/Udev_rules";
 #endif
     }
 
@@ -330,7 +350,19 @@ void WindowManager::displayWalletErrorMessage(const QString &message) {
 // ######################## DEVICE ########################
 
 void WindowManager::onDeviceButtonRequest(quint64 code) {
-    m_splashDialog->setMessage("Action required on device: Export the view key to open the wallet.");
+    QString message;
+    switch (code) {
+        case 1: // Trezor
+            message = "Action required on device: enter your PIN to continue.";
+            break;
+        case 8: // Trezor
+            message = "Action required on device: Export watch-only credentials to open the wallet.";
+            break;
+        default:
+            message = "Action required on device: Export the view key to open the wallet.";
+    }
+
+    m_splashDialog->setMessage(message);
     m_splashDialog->setIcon(QPixmap(":/assets/images/key.png"));
     m_splashDialog->show();
     m_splashDialog->setEnabled(true);
index 4a1acb22adb4d968ee0d76ccc86c144e719d3511..2a87469c97305cbb305d9ab1ccef648b382eedef 100644 (file)
@@ -43,7 +43,7 @@ private slots:
 
 private:
     void tryCreateWallet(FeatherSeed seed, const QString &path, const QString &password, const QString &seedOffset);
-    void tryCreateWalletFromDevice(const QString &path, const QString &password, int restoreHeight);
+    void tryCreateWalletFromDevice(const QString &path, const QString &password, const QString &deviceName, int restoreHeight);
     void tryCreateWalletFromKeys(const QString &path, const QString &password, const QString &address, const QString &viewkey, const QString &spendkey, quint64 restoreHeight);
 
     bool autoOpenWallet();
index 006f8c9f89a2e865d06769f2f91523772e155dc4..df6630070e649431e2c89a7a345ffdcecd891ce0 100644 (file)
@@ -38,6 +38,7 @@ AppContext::AppContext(Wallet *wallet)
     connect(this->wallet.get(), &Wallet::transactionCreated,       this, &AppContext::onTransactionCreated);
     connect(this->wallet.get(), &Wallet::deviceError,              this, &AppContext::onDeviceError);
     connect(this->wallet.get(), &Wallet::deviceButtonRequest,      this, &AppContext::onDeviceButtonRequest);
+    connect(this->wallet.get(), &Wallet::deviceButtonPressed,      this, &AppContext::onDeviceButtonPressed);
     connect(this->wallet.get(), &Wallet::connectionStatusChanged, [this]{
         this->nodes->autoConnect();
     });
@@ -190,6 +191,10 @@ void AppContext::onDeviceButtonRequest(quint64 code) {
     emit deviceButtonRequest(code);
 }
 
+void AppContext::onDeviceButtonPressed() {
+    emit deviceButtonPressed();
+}
+
 void AppContext::onDeviceError(const QString &message) {
     qCritical() << "Device error: " << message;
     emit deviceError(message);
index 1a7d4dbec29f4baa8c995f07f7af2f466148044b..093c01c2ecdac813735b60c7999995b1cd2eea7d 100644 (file)
@@ -59,6 +59,7 @@ public slots:
     void onAmountPrecisionChanged(int precision);
     void onMultiBroadcast(PendingTransaction *tx);
     void onDeviceButtonRequest(quint64 code);
+    void onDeviceButtonPressed();
     void onDeviceError(const QString &message);
 
     void onTorSettingsChanged(); // should not be here
@@ -92,6 +93,7 @@ signals:
     void initiateTransaction();
     void endTransaction();
     void deviceButtonRequest(quint64 code);
+    void deviceButtonPressed();
     void deviceError(const QString &message);
 
 private:
index 9956d8be740aef1300191ffa6c4ac05aa89d6b37..4e813206d6e7ca134add733f31818a93805aabe0 100644 (file)
     <file>assets/images/tor_logo_disabled.png</file>
     <file>assets/images/tor_logo.png</file>
     <file>assets/images/trezor.png</file>
+    <file>assets/images/trezor_white.png</file>
     <file>assets/images/trezor_unpaired.png</file>
+    <file>assets/images/trezor_unpaired_white.png</file>
     <file>assets/images/unconfirmed.png</file>
     <file>assets/images/unlock.png</file>
     <file>assets/images/unlock.svg</file>
diff --git a/src/assets/images/trezor_unpaired_white.png b/src/assets/images/trezor_unpaired_white.png
new file mode 100644 (file)
index 0000000..751f663
Binary files /dev/null and b/src/assets/images/trezor_unpaired_white.png differ
diff --git a/src/assets/images/trezor_white.png b/src/assets/images/trezor_white.png
new file mode 100644 (file)
index 0000000..30a52a9
Binary files /dev/null and b/src/assets/images/trezor_white.png differ
index 65ff1e46c3acf8b092766a3008563eea85f63cb8..96244e9d3711c5377042f71a24de817ad95b12f1 100644 (file)
@@ -26,6 +26,12 @@ SignVerifyDialog::SignVerifyDialog(Wallet *wallet, QWidget *parent)
     ui->address->setText(m_wallet->address(0, 0));
     ui->address->setCursorPosition(0);
 
+    if (m_wallet->isHwBacked()) {
+        // We don't have the secret spend key to sign messages
+        ui->btn_Sign->setEnabled(false);
+        ui->btn_Sign->setToolTip("Message signing is not supported on this hardware device.");
+    }
+
     ui->btn_Copy->setVisible(false);
 }
 
index 6bdb48fdcd112fa91a05ed4d5a2f953359f5409d..83a371c646bbf0e959a0147d14a29152d2cad93d 100644 (file)
@@ -13,6 +13,10 @@ PageHardwareDevice::PageHardwareDevice(WizardFields *fields, QWidget *parent)
         , m_fields(fields)
 {
     ui->setupUi(this);
+
+    ui->combo_deviceType->addItem("Ledger Nano S", DeviceType::LEDGER_NANO_S);
+    ui->combo_deviceType->addItem("Ledger Nano X", DeviceType::LEDGER_NANO_X);
+    ui->combo_deviceType->addItem("Trezor Model T", DeviceType::TREZOR_MODEL_T);
 }
 
 void PageHardwareDevice::initializePage() {
@@ -28,6 +32,7 @@ int PageHardwareDevice::nextId() const {
 }
 
 bool PageHardwareDevice::validatePage() {
+    m_fields->deviceType = static_cast<DeviceType>(ui->combo_deviceType->currentData().toInt());
     return true;
 }
 
index 736edea49e5b562f0127160dcfb4b24997b88d19..9d2cc95162b59a0b29118b4d4724a335db3aa7df 100644 (file)
     </widget>
    </item>
    <item>
-    <widget class="QComboBox" name="comboBox">
-     <item>
-      <property name="text">
-       <string>Ledger Nano S/X</string>
-      </property>
-     </item>
-    </widget>
+    <widget class="QComboBox" name="combo_deviceType"/>
    </item>
    <item>
     <widget class="Line" name="line">
index 4171ae45701676e93c497cc93db56a31f93680e2..2a9373d6c71cd6e8d6c34b47e76f538504f5df96 100644 (file)
@@ -98,7 +98,14 @@ QString PageWalletFile::defaultWalletName() {
     do {
         QString walletStr = QString("wallet_%1");
         if (m_fields->mode == WizardMode::CreateWalletFromDevice) {
-            walletStr = QString("ledger_%1");
+            switch (m_fields->deviceType) {
+                case DeviceType::LEDGER_NANO_S:
+                case DeviceType::LEDGER_NANO_X:
+                    walletStr = QString("ledger_%1");
+                    break;
+                case DeviceType::TREZOR_MODEL_T:
+                    walletStr = QString("trezor_%1");
+            }
         }
         walletName = walletStr.arg(count);
         count++;
index 5501b6d9583e2144f150b2e468c99f3f1fed6089..f169937ad5aebd56b0085703f7dfaabf213795d2 100644 (file)
@@ -93,7 +93,17 @@ void WalletWizard::onCreateWallet() {
             restoreHeight = m_wizardFields.restoreHeight;
         }
 
-        emit createWalletFromDevice(walletPath, m_wizardFields.password, restoreHeight);
+        QString deviceName;
+        switch (m_wizardFields.deviceType) {
+            case DeviceType::LEDGER_NANO_S:
+            case DeviceType::LEDGER_NANO_X:
+                deviceName = "Ledger";
+                break;
+            case DeviceType::TREZOR_MODEL_T:
+                deviceName = "Trezor";
+        }
+
+        emit createWalletFromDevice(walletPath, m_wizardFields.password, deviceName, restoreHeight);
         return;
     }
 
index 74893cf66153878eecca38c9341aa8162270b20d..377ea3897f77bf15c8444fe758fd101dc87ed0fb 100644 (file)
@@ -21,6 +21,12 @@ enum WizardMode {
     CreateWalletFromDevice
 };
 
+enum DeviceType {
+    LEDGER_NANO_S = 0,
+    LEDGER_NANO_X,
+    TREZOR_MODEL_T
+};
+
 struct WizardFields {
     QString walletName;
     QString walletDir;
@@ -34,6 +40,7 @@ struct WizardFields {
     WizardMode mode;
     int restoreHeight = 0;
     SeedType seedType;
+    DeviceType deviceType;
 };
 
 class WalletWizard : public QWizard
@@ -63,7 +70,7 @@ signals:
     void openWallet(QString path, QString password);
     void defaultWalletDirChanged(QString walletDir);
 
-    void createWalletFromDevice(const QString &path, const QString &password, int restoreHeight);
+    void createWalletFromDevice(const QString &path, const QString &password, const QString &deviceName, int restoreHeight);
     void createWalletFromKeys(const QString &path, const QString &password, const QString &address, const QString &viewkey, const QString &spendkey, quint64 restoreHeight, bool deterministic = false);
     void createWallet(FeatherSeed seed, const QString &path, const QString &password, const QString &seedOffset = "");