Trezor support

This commit is contained in:
tobtoht 2021-07-01 23:00:47 +02:00
parent 531cc5f980
commit 062022f9d1
No known key found for this signature in database
GPG key ID: 1CADD27F41F45C3C
19 changed files with 156 additions and 23 deletions

View 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)

View 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 && \

View file

@ -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"

View 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

View 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("");

View 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;

View 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);

View 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();

View 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);

View 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:

View file

@ -107,7 +107,9 @@
<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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View 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);
}

View 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;
}

View file

@ -22,13 +22,7 @@
</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">

View 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++;

View 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;
}

View 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 = "");