electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 66173077302a8fc8ca8a86ec812941b8d1778dfa
parent 11733d6bc271646a00b69ff07657119598874da4
Author: ghost43 <somber.night@protonmail.com>
Date:   Fri, 22 Feb 2019 18:59:52 +0100

Merge pull request #5118 from SomberNight/trezor_init_20190213

Trezor: implement "seedless" mode
Diffstat:
Melectrum/plugins/keepkey/keepkey.py | 2++
Melectrum/plugins/safe_t/safe_t.py | 2++
Melectrum/plugins/trezor/qt.py | 79++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Melectrum/plugins/trezor/trezor.py | 45+++++++++++++++++++++++++++------------------
4 files changed, 91 insertions(+), 37 deletions(-)

diff --git a/electrum/plugins/keepkey/keepkey.py b/electrum/plugins/keepkey/keepkey.py @@ -206,6 +206,8 @@ class KeepKeyPlugin(HW_PluginBase): language = 'english' devmgr = self.device_manager() client = devmgr.client_by_id(device_id) + if not client: + raise Exception(_("The device was disconnected.")) if method == TIM_NEW: strength = 64 * (item + 2) # 128, 192 or 256 diff --git a/electrum/plugins/safe_t/safe_t.py b/electrum/plugins/safe_t/safe_t.py @@ -220,6 +220,8 @@ class SafeTPlugin(HW_PluginBase): language = 'english' devmgr = self.device_manager() client = devmgr.client_by_id(device_id) + if not client: + raise Exception(_("The device was disconnected.")) if method == TIM_NEW: strength = 64 * (item + 2) # 128, 192 or 256 diff --git a/electrum/plugins/trezor/qt.py b/electrum/plugins/trezor/qt.py @@ -15,7 +15,7 @@ from electrum.util import bh2u from ..hw_wallet.qt import QtHandlerBase, QtPluginBase from ..hw_wallet.plugin import only_hook_if_libraries_available -from .trezor import (TrezorPlugin, TIM_NEW, TIM_RECOVER, +from .trezor import (TrezorPlugin, TIM_NEW, TIM_RECOVER, TrezorInitSettings, RECOVERY_TYPE_SCRAMBLED_WORDS, RECOVERY_TYPE_MATRIX) @@ -38,6 +38,10 @@ MATRIX_RECOVERY = _( "Enter the recovery words by pressing the buttons according to what " "the device shows on its display. You can also use your NUMPAD.\n" "Press BACKSPACE to go back a choice or word.\n") +SEEDLESS_MODE_WARNING = _( + "In seedless mode, the mnemonic seed words are never shown to the user.\n" + "There is no backup, and the user has a proof of this.\n" + "This is an advanced feature, only suggested to be used in redundant multisig setups.") class MatrixDialog(WindowModalDialog): @@ -185,9 +189,18 @@ class QtPlugin(QtPluginBase): if device_id: SettingsDialog(window, self, keystore, device_id).exec_() - def request_trezor_init_settings(self, wizard, method, model): + def request_trezor_init_settings(self, wizard, method, device_id): vbox = QVBoxLayout() next_enabled = True + + devmgr = self.device_manager() + client = devmgr.client_by_id(device_id) + if not client: + raise Exception(_("The device was disconnected.")) + model = client.get_trezor_model() + fw_version = client.client.version + + # label label = QLabel(_("Enter a label to name your device:")) name = QLineEdit() hl = QHBoxLayout() @@ -196,44 +209,57 @@ class QtPlugin(QtPluginBase): hl.addStretch(1) vbox.addLayout(hl) - def clean_text(widget): - text = widget.toPlainText().strip() - return ' '.join(text.split()) - + # word count gb = QGroupBox() hbox1 = QHBoxLayout() gb.setLayout(hbox1) vbox.addWidget(gb) gb.setTitle(_("Select your seed length:")) bg_numwords = QButtonGroup() - for i, count in enumerate([12, 18, 24]): + word_counts = (12, 18, 24) + for i, count in enumerate(word_counts): rb = QRadioButton(gb) rb.setText(_("{:d} words").format(count)) bg_numwords.addButton(rb) bg_numwords.setId(rb, i) hbox1.addWidget(rb) rb.setChecked(True) + + # PIN cb_pin = QCheckBox(_('Enable PIN protection')) cb_pin.setChecked(True) - vbox.addWidget(WWLabel(RECOMMEND_PIN)) vbox.addWidget(cb_pin) + # "expert settings" button + expert_vbox = QVBoxLayout() + expert_widget = QWidget() + expert_widget.setLayout(expert_vbox) + expert_widget.setVisible(False) + expert_button = QPushButton(_("Show expert settings")) + def show_expert_settings(): + expert_button.setVisible(False) + expert_widget.setVisible(True) + expert_button.clicked.connect(show_expert_settings) + vbox.addWidget(expert_button) + + # passphrase passphrase_msg = WWLabel(PASSPHRASE_HELP_SHORT) passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN) passphrase_warning.setStyleSheet("color: red") cb_phrase = QCheckBox(_('Enable passphrases')) cb_phrase.setChecked(False) - vbox.addWidget(passphrase_msg) - vbox.addWidget(passphrase_warning) - vbox.addWidget(cb_phrase) + expert_vbox.addWidget(passphrase_msg) + expert_vbox.addWidget(passphrase_warning) + expert_vbox.addWidget(cb_phrase) # ask for recovery type (random word order OR matrix) + bg_rectype = None if method == TIM_RECOVER and not model == 'T': gb_rectype = QGroupBox() hbox_rectype = QHBoxLayout() gb_rectype.setLayout(hbox_rectype) - vbox.addWidget(gb_rectype) + expert_vbox.addWidget(gb_rectype) gb_rectype.setTitle(_("Select recovery type:")) bg_rectype = QButtonGroup() @@ -249,16 +275,31 @@ class QtPlugin(QtPluginBase): bg_rectype.addButton(rb2) bg_rectype.setId(rb2, RECOVERY_TYPE_MATRIX) hbox_rectype.addWidget(rb2) - else: - bg_rectype = None - wizard.exec_layout(vbox, next_enabled=next_enabled) + # no backup + cb_no_backup = None + if method == TIM_NEW: + cb_no_backup = QCheckBox(f'''{_('Enable seedless mode')}''') + cb_no_backup.setChecked(False) + if (model == '1' and fw_version >= (1, 7, 1) + or model == 'T' and fw_version >= (2, 0, 9)): + cb_no_backup.setToolTip(SEEDLESS_MODE_WARNING) + else: + cb_no_backup.setEnabled(False) + cb_no_backup.setToolTip(_('Firmware version too old.')) + expert_vbox.addWidget(cb_no_backup) - item = bg_numwords.checkedId() - pin = cb_pin.isChecked() - recovery_type = bg_rectype.checkedId() if bg_rectype else None + vbox.addWidget(expert_widget) + wizard.exec_layout(vbox, next_enabled=next_enabled) - return (item, name.text(), pin, cb_phrase.isChecked(), recovery_type) + return TrezorInitSettings( + word_count=word_counts[bg_numwords.checkedId()], + label=name.text(), + pin_enabled=cb_pin.isChecked(), + passphrase_enabled=cb_phrase.isChecked(), + recovery_type=bg_rectype.checkedId() if bg_rectype else None, + no_backup=cb_no_backup.isChecked() if cb_no_backup else False, + ) class Plugin(TrezorPlugin, QtPlugin): diff --git a/electrum/plugins/trezor/trezor.py b/electrum/plugins/trezor/trezor.py @@ -1,5 +1,6 @@ import traceback import sys +from typing import NamedTuple, Any from electrum.util import bfh, bh2u, versiontuple, UserCancelled, UserFacingException from electrum.bitcoin import TYPE_ADDRESS, TYPE_SCRIPT @@ -86,6 +87,15 @@ class TrezorKeyStore(Hardware_KeyStore): self.plugin.sign_transaction(self, tx, prev_tx, xpub_path) +class TrezorInitSettings(NamedTuple): + word_count: int + label: str + pin_enabled: bool + passphrase_enabled: bool + recovery_type: Any = None + no_backup: bool = False + + class TrezorPlugin(HW_PluginBase): # Derived classes provide: # @@ -177,12 +187,9 @@ class TrezorPlugin(HW_PluginBase): (TIM_NEW, _("Let the device generate a completely new seed randomly")), (TIM_RECOVER, _("Recover from a seed you have previously written down")), ] - devmgr = self.device_manager() - client = devmgr.client_by_id(device_id) - model = client.get_trezor_model() def f(method): import threading - settings = self.request_trezor_init_settings(wizard, method, model) + settings = self.request_trezor_init_settings(wizard, method, device_id) t = threading.Thread(target=self._initialize_device_safe, args=(settings, method, device_id, wizard, handler)) t.setDaemon(True) t.start() @@ -207,10 +214,8 @@ class TrezorPlugin(HW_PluginBase): finally: wizard.loop.exit(exit_code) - def _initialize_device(self, settings, method, device_id, wizard, handler): - item, label, pin_protection, passphrase_protection, recovery_type = settings - - if method == TIM_RECOVER and recovery_type == RECOVERY_TYPE_SCRAMBLED_WORDS: + def _initialize_device(self, settings: TrezorInitSettings, method, device_id, wizard, handler): + if method == TIM_RECOVER and settings.recovery_type == RECOVERY_TYPE_SCRAMBLED_WORDS: handler.show_error(_( "You will be asked to enter 24 words regardless of your " "seed's actual length. If you enter a word incorrectly or " @@ -221,21 +226,25 @@ class TrezorPlugin(HW_PluginBase): devmgr = self.device_manager() client = devmgr.client_by_id(device_id) + if not client: + raise Exception(_("The device was disconnected.")) if method == TIM_NEW: + strength_from_word_count = {12: 128, 18: 192, 24: 256} client.reset_device( - strength=64 * (item + 2), # 128, 192 or 256 - passphrase_protection=passphrase_protection, - pin_protection=pin_protection, - label=label) + strength=strength_from_word_count[settings.word_count], + passphrase_protection=settings.passphrase_enabled, + pin_protection=settings.pin_enabled, + label=settings.label, + no_backup=settings.no_backup) elif method == TIM_RECOVER: client.recover_device( - recovery_type=recovery_type, - word_count=6 * (item + 2), # 12, 18 or 24 - passphrase_protection=passphrase_protection, - pin_protection=pin_protection, - label=label) - if recovery_type == RECOVERY_TYPE_MATRIX: + recovery_type=settings.recovery_type, + word_count=settings.word_count, + passphrase_protection=settings.passphrase_enabled, + pin_protection=settings.pin_enabled, + label=settings.label) + if settings.recovery_type == RECOVERY_TYPE_MATRIX: handler.close_matrix_dialog() else: raise RuntimeError("Unsupported recovery method")