From 590e532f26c73b1568eaef20b011099e9da9c35a Mon Sep 17 00:00:00 2001 From: matejcik Date: Tue, 31 Mar 2020 12:22:59 +0200 Subject: [PATCH] trezor: bump lib version, implement new passphrase-on-device UI --- .../deterministic-build/requirements-hw.txt | 6 +- contrib/requirements/requirements-hw.txt | 2 +- plugins/trezor/__init__.py | 8 +-- plugins/trezor/clientbase.py | 36 +++++++--- plugins/trezor/cmdline.py | 17 ++++- plugins/trezor/qt.py | 70 ++++++++++++++++++- plugins/trezor/trezor.py | 8 ++- 7 files changed, 124 insertions(+), 23 deletions(-) diff --git a/contrib/deterministic-build/requirements-hw.txt b/contrib/deterministic-build/requirements-hw.txt index 5f8d4646c72c..d0eb064b0499 100644 --- a/contrib/deterministic-build/requirements-hw.txt +++ b/contrib/deterministic-build/requirements-hw.txt @@ -118,9 +118,9 @@ setuptools==41.0.1 \ six==1.12.0 \ --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 -trezor==0.12.0 \ - --hash=sha256:da5b750ada03830fd1f0b9010f7d5d30e77ec3e1458230e3d08fe4588a0741b2 \ - --hash=sha256:f6bc821bddec06e67a1abd0be1d9fbc61c59b08272c736522ae2f6b225bf9579 +trezor==0.12.1 \ + --hash=sha256:293cfc3c5d13fa62a42d23f85daa0c98e3eb2e3c44ab0ca54eb314cd69aa29f0 \ + --hash=sha256:8cc5359977502bd0f4bdcbdee18e5e7879c83fd5e1812d29444574d71b837be0 typing-extensions==3.7.4.2 \ --hash=sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5 \ --hash=sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae \ diff --git a/contrib/requirements/requirements-hw.txt b/contrib/requirements/requirements-hw.txt index b2b8dcd86462..4a9adadcbb6c 100644 --- a/contrib/requirements/requirements-hw.txt +++ b/contrib/requirements/requirements-hw.txt @@ -1,5 +1,5 @@ Cython>=0.27 -trezor[hidapi]>=0.11.0 +trezor[hidapi]>=0.12.0 keepkey>=6.1 btchip-python hidapi diff --git a/plugins/trezor/__init__.py b/plugins/trezor/__init__.py index 46a5add33695..36b2e431bd8a 100644 --- a/plugins/trezor/__init__.py +++ b/plugins/trezor/__init__.py @@ -1,8 +1,8 @@ from electroncash.i18n import _ -fullname = _('TREZOR Wallet') -description = _('Provides support for TREZOR hardware wallet') -requires = [('trezorlib','github.com/trezor/python-trezor')] -registers_keystore = ('hardware', 'trezor', _("TREZOR wallet")) +fullname = 'Trezor Wallet' +description = _('Provides support for Trezor hardware wallet') +requires = [('trezorlib','pypi.org/project/trezor/')] +registers_keystore = ('hardware', 'trezor', _("Trezor wallet")) available_for = ['qt', 'cmdline'] diff --git a/plugins/trezor/clientbase.py b/plugins/trezor/clientbase.py index 067dc5caee3d..285371992553 100644 --- a/plugins/trezor/clientbase.py +++ b/plugins/trezor/clientbase.py @@ -6,21 +6,31 @@ from electroncash.keystore import bip39_normalize_passphrase from electroncash.bitcoin import serialize_xpub -from trezorlib.client import TrezorClient +from trezorlib.client import TrezorClient, PASSPHRASE_ON_DEVICE from trezorlib.exceptions import TrezorFailure, Cancelled, OutdatedFirmwareError, TrezorException -from trezorlib.messages import WordRequestType, FailureType, RecoveryDeviceType +from trezorlib.messages import WordRequestType, FailureType, RecoveryDeviceType, ButtonRequestType import trezorlib.btc import trezorlib.device MESSAGES = { - 3: _("Confirm the transaction output on your {} device"), - 4: _("Confirm internal entropy on your {} device to begin"), - 5: _("Write down the seed word shown on your {}"), - 6: _("Confirm on your {} that you want to wipe it clean"), - 7: _("Confirm the operation on on your {} device"), - 8: _("Confirm the total amount spent and the transaction fee on your {} device"), - 10: _("Confirm wallet address on your {} device"), - 14: _("Choose on your {} device where to enter your passphrase"), + ButtonRequestType.ConfirmOutput: + _("Confirm the transaction output on your {} device"), + ButtonRequestType.ResetDevice: + _("Complete the initialization process on your {} device"), + ButtonRequestType.ConfirmWord: + _("Write down the seed word shown on your {}"), + ButtonRequestType.WipeDevice: + _("Confirm on your {} that you want to wipe it clean"), + ButtonRequestType.ProtectCall: + _("Confirm on your {} device the message to sign"), + ButtonRequestType.SignTx: + _("Confirm the total amount spent and the transaction fee on your {} device"), + ButtonRequestType.Address: + _("Confirm wallet address on your {} device"), + ButtonRequestType._Deprecated_ButtonRequest_PassphraseType: + _("Choose on your {} device where to enter your passphrase"), + ButtonRequestType.PassphraseEntry: + _("Please enter your passphrase on the {} device"), 'default': _("Check your {} device to continue"), } @@ -284,7 +294,7 @@ def get_pin(self, code=None): raise Cancelled return pin - def get_passphrase(self): + def get_passphrase(self, available_on_device): if self.creating_wallet: msg = _("Enter a passphrase to generate this wallet. Each time " "you use this wallet your {} will prompt you for the " @@ -292,7 +302,11 @@ def get_passphrase(self): "access the bitcoins in the wallet.").format(self.device) else: msg = _("Enter the passphrase to unlock this wallet:") + + self.handler.passphrase_on_device = available_on_device passphrase = self.handler.get_passphrase(msg, self.creating_wallet) + if passphrase is PASSPHRASE_ON_DEVICE: + return passphrase if passphrase is None: raise Cancelled passphrase = bip39_normalize_passphrase(passphrase) diff --git a/plugins/trezor/cmdline.py b/plugins/trezor/cmdline.py index 223ffddff281..d976c26bb547 100644 --- a/plugins/trezor/cmdline.py +++ b/plugins/trezor/cmdline.py @@ -1,7 +1,22 @@ from electroncash.plugins import hook -from .trezor import TrezorPlugin +from electroncash.i18n import _ +from electroncash.util import print_stderr +from .trezor import TrezorPlugin, PASSPHRASE_ON_DEVICE from ..hw_wallet import CmdLineHandler +class TrezorCmdLineHandler(CmdLineHandler): + def __init__(self): + self.passphrase_on_device = False + super().__init__() + + def get_passphrase(self, msg, confirm): + import getpass + print_stderr(msg) + if self.passphrase_on_device and self.yes_no_question(_('Enter passphrase on device?')): + return PASSPHRASE_ON_DEVICE + else: + return getpass.getpass('') + class Plugin(TrezorPlugin): handler = CmdLineHandler() @hook diff --git a/plugins/trezor/qt.py b/plugins/trezor/qt.py index a70dd870bca8..0d46c24cad0a 100644 --- a/plugins/trezor/qt.py +++ b/plugins/trezor/qt.py @@ -15,7 +15,8 @@ from ..hw_wallet.qt import QtHandlerBase, QtPluginBase from .trezor import (TrezorPlugin, TIM_NEW, TIM_RECOVER, - RECOVERY_TYPE_SCRAMBLED_WORDS, RECOVERY_TYPE_MATRIX) + RECOVERY_TYPE_SCRAMBLED_WORDS, RECOVERY_TYPE_MATRIX, + PASSPHRASE_ON_DEVICE) PASSPHRASE_HELP_SHORT =_( @@ -114,6 +115,7 @@ def __init__(self, win, pin_matrix_widget_class, device): self.close_matrix_dialog_signal.connect(self._close_matrix_dialog) self.pin_matrix_widget_class = pin_matrix_widget_class self.matrix_dialog = None + self.passphrase_on_device = False def get_pin(self, msg): self.done.clear() @@ -158,6 +160,72 @@ def matrix_recovery_dialog(self, msg): self.matrix_dialog.get_matrix(msg) self.done.set() + def passphrase_dialog(self, msg, confirm): + # If confirm is true, require the user to enter the passphrase twice + parent = self.top_level_window() + d = WindowModalDialog(parent, _('Enter Passphrase')) + + OK_button = OkButton(d, _('Enter Passphrase')) + OnDevice_button = QPushButton(_('Enter Passphrase on Device')) + + new_pw = QLineEdit() + new_pw.setEchoMode(2) + conf_pw = QLineEdit() + conf_pw.setEchoMode(2) + + vbox = QVBoxLayout() + label = QLabel(msg + "\n") + label.setWordWrap(True) + + grid = QGridLayout() + grid.setSpacing(8) + grid.setColumnMinimumWidth(0, 150) + grid.setColumnMinimumWidth(1, 100) + grid.setColumnStretch(1,1) + + vbox.addWidget(label) + + grid.addWidget(QLabel(_('Passphrase:')), 0, 0) + grid.addWidget(new_pw, 0, 1) + + if confirm: + grid.addWidget(QLabel(_('Confirm Passphrase:')), 1, 0) + grid.addWidget(conf_pw, 1, 1) + + vbox.addLayout(grid) + + def enable_OK(): + if not confirm: + ok = True + else: + ok = new_pw.text() == conf_pw.text() + OK_button.setEnabled(ok) + + new_pw.textChanged.connect(enable_OK) + conf_pw.textChanged.connect(enable_OK) + + vbox.addWidget(OK_button) + + if self.passphrase_on_device: + vbox.addWidget(OnDevice_button) + + d.setLayout(vbox) + + self.passphrase = None + + def ok_clicked(): + self.passphrase = new_pw.text() + + def on_device_clicked(): + self.passphrase = PASSPHRASE_ON_DEVICE + + OK_button.clicked.connect(ok_clicked) + OnDevice_button.clicked.connect(on_device_clicked) + OnDevice_button.clicked.connect(d.accept) + + d.exec_() + self.done.set() + class QtPlugin(QtPluginBase): # Derived classes must provide the following class-static variables: diff --git a/plugins/trezor/trezor.py b/plugins/trezor/trezor.py index 7635b689408e..1ca49f63c615 100644 --- a/plugins/trezor/trezor.py +++ b/plugins/trezor/trezor.py @@ -30,6 +30,8 @@ RECOVERY_TYPE_SCRAMBLED_WORDS = RecoveryDeviceType.ScrambledWords RECOVERY_TYPE_MATRIX = RecoveryDeviceType.Matrix + from trezorlib.client import PASSPHRASE_ON_DEVICE + TREZORLIB = True except Exception as e: import traceback @@ -38,6 +40,8 @@ RECOVERY_TYPE_SCRAMBLED_WORDS, RECOVERY_TYPE_MATRIX = range(2) + PASSPHRASE_ON_DEVICE = object() + # TREZOR initialization methods TIM_NEW, TIM_RECOVER = range(2) @@ -104,10 +108,10 @@ class TrezorPlugin(HW_PluginBase): # wallet_class, types firmware_URL = 'https://wallet.trezor.io' - libraries_URL = 'https://github.com/trezor/python-trezor' + libraries_URL = 'https://pypi.org/project/trezor/' minimum_firmware = (1, 5, 2) keystore_class = TrezorKeyStore - minimum_library = (0, 11, 0) + minimum_library = (0, 12, 0) maximum_library = (0, 13) DEVICE_IDS = (TREZOR_PRODUCT_KEY,)