mirror of
https://github.com/feather-wallet/feather.git
synced 2024-12-22 11:39:25 +00:00
Trezor support
This commit is contained in:
parent
531cc5f980
commit
062022f9d1
19 changed files with 156 additions and 23 deletions
|
@ -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)
|
||||
|
|
|
@ -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 && \
|
||||
|
|
2
Makefile
2
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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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("");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
||||
|
|
BIN
src/assets/images/trezor_unpaired_white.png
Normal file
BIN
src/assets/images/trezor_unpaired_white.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
src/assets/images/trezor_white.png
Normal file
BIN
src/assets/images/trezor_white.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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++;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = "");
|
||||
|
||||
|
|
Loading…
Reference in a new issue