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:
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")