Browse Source

trezor: implement "seedless" mode (option during initialization)

sqlite_db
SomberNight 6 years ago
parent
commit
7bbec04a06
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 2
      electrum/plugins/keepkey/keepkey.py
  2. 2
      electrum/plugins/safe_t/safe_t.py
  3. 58
      electrum/plugins/trezor/qt.py
  4. 45
      electrum/plugins/trezor/trezor.py

2
electrum/plugins/keepkey/keepkey.py

@ -206,6 +206,8 @@ class KeepKeyPlugin(HW_PluginBase):
language = 'english' language = 'english'
devmgr = self.device_manager() devmgr = self.device_manager()
client = devmgr.client_by_id(device_id) client = devmgr.client_by_id(device_id)
if not client:
raise Exception(_("The device was disconnected."))
if method == TIM_NEW: if method == TIM_NEW:
strength = 64 * (item + 2) # 128, 192 or 256 strength = 64 * (item + 2) # 128, 192 or 256

2
electrum/plugins/safe_t/safe_t.py

@ -220,6 +220,8 @@ class SafeTPlugin(HW_PluginBase):
language = 'english' language = 'english'
devmgr = self.device_manager() devmgr = self.device_manager()
client = devmgr.client_by_id(device_id) client = devmgr.client_by_id(device_id)
if not client:
raise Exception(_("The device was disconnected."))
if method == TIM_NEW: if method == TIM_NEW:
strength = 64 * (item + 2) # 128, 192 or 256 strength = 64 * (item + 2) # 128, 192 or 256

58
electrum/plugins/trezor/qt.py

@ -15,7 +15,7 @@ from electrum.util import bh2u
from ..hw_wallet.qt import QtHandlerBase, QtPluginBase from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
from ..hw_wallet.plugin import only_hook_if_libraries_available 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) RECOVERY_TYPE_SCRAMBLED_WORDS, RECOVERY_TYPE_MATRIX)
@ -38,6 +38,10 @@ MATRIX_RECOVERY = _(
"Enter the recovery words by pressing the buttons according to what " "Enter the recovery words by pressing the buttons according to what "
"the device shows on its display. You can also use your NUMPAD.\n" "the device shows on its display. You can also use your NUMPAD.\n"
"Press BACKSPACE to go back a choice or word.\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): class MatrixDialog(WindowModalDialog):
@ -185,9 +189,18 @@ class QtPlugin(QtPluginBase):
if device_id: if device_id:
SettingsDialog(window, self, keystore, device_id).exec_() 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() vbox = QVBoxLayout()
next_enabled = True 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:")) label = QLabel(_("Enter a label to name your device:"))
name = QLineEdit() name = QLineEdit()
hl = QHBoxLayout() hl = QHBoxLayout()
@ -196,29 +209,29 @@ class QtPlugin(QtPluginBase):
hl.addStretch(1) hl.addStretch(1)
vbox.addLayout(hl) vbox.addLayout(hl)
def clean_text(widget): # word count
text = widget.toPlainText().strip()
return ' '.join(text.split())
gb = QGroupBox() gb = QGroupBox()
hbox1 = QHBoxLayout() hbox1 = QHBoxLayout()
gb.setLayout(hbox1) gb.setLayout(hbox1)
vbox.addWidget(gb) vbox.addWidget(gb)
gb.setTitle(_("Select your seed length:")) gb.setTitle(_("Select your seed length:"))
bg_numwords = QButtonGroup() 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 = QRadioButton(gb)
rb.setText(_("{:d} words").format(count)) rb.setText(_("{:d} words").format(count))
bg_numwords.addButton(rb) bg_numwords.addButton(rb)
bg_numwords.setId(rb, i) bg_numwords.setId(rb, i)
hbox1.addWidget(rb) hbox1.addWidget(rb)
rb.setChecked(True) rb.setChecked(True)
# PIN
cb_pin = QCheckBox(_('Enable PIN protection')) cb_pin = QCheckBox(_('Enable PIN protection'))
cb_pin.setChecked(True) cb_pin.setChecked(True)
vbox.addWidget(WWLabel(RECOMMEND_PIN)) vbox.addWidget(WWLabel(RECOMMEND_PIN))
vbox.addWidget(cb_pin) vbox.addWidget(cb_pin)
# passphrase
passphrase_msg = WWLabel(PASSPHRASE_HELP_SHORT) passphrase_msg = WWLabel(PASSPHRASE_HELP_SHORT)
passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN) passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)
passphrase_warning.setStyleSheet("color: red") passphrase_warning.setStyleSheet("color: red")
@ -229,6 +242,7 @@ class QtPlugin(QtPluginBase):
vbox.addWidget(cb_phrase) vbox.addWidget(cb_phrase)
# ask for recovery type (random word order OR matrix) # ask for recovery type (random word order OR matrix)
bg_rectype = None
if method == TIM_RECOVER and not model == 'T': if method == TIM_RECOVER and not model == 'T':
gb_rectype = QGroupBox() gb_rectype = QGroupBox()
hbox_rectype = QHBoxLayout() hbox_rectype = QHBoxLayout()
@ -249,16 +263,30 @@ class QtPlugin(QtPluginBase):
bg_rectype.addButton(rb2) bg_rectype.addButton(rb2)
bg_rectype.setId(rb2, RECOVERY_TYPE_MATRIX) bg_rectype.setId(rb2, RECOVERY_TYPE_MATRIX)
hbox_rectype.addWidget(rb2) 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.'))
vbox.addWidget(cb_no_backup)
item = bg_numwords.checkedId() wizard.exec_layout(vbox, next_enabled=next_enabled)
pin = cb_pin.isChecked()
recovery_type = bg_rectype.checkedId() if bg_rectype else None
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): class Plugin(TrezorPlugin, QtPlugin):

45
electrum/plugins/trezor/trezor.py

@ -1,5 +1,6 @@
import traceback import traceback
import sys import sys
from typing import NamedTuple, Any
from electrum.util import bfh, bh2u, versiontuple, UserCancelled, UserFacingException from electrum.util import bfh, bh2u, versiontuple, UserCancelled, UserFacingException
from electrum.bitcoin import TYPE_ADDRESS, TYPE_SCRIPT 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) 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): class TrezorPlugin(HW_PluginBase):
# Derived classes provide: # Derived classes provide:
# #
@ -177,12 +187,9 @@ class TrezorPlugin(HW_PluginBase):
(TIM_NEW, _("Let the device generate a completely new seed randomly")), (TIM_NEW, _("Let the device generate a completely new seed randomly")),
(TIM_RECOVER, _("Recover from a seed you have previously written down")), (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): def f(method):
import threading 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 = threading.Thread(target=self._initialize_device_safe, args=(settings, method, device_id, wizard, handler))
t.setDaemon(True) t.setDaemon(True)
t.start() t.start()
@ -207,10 +214,8 @@ class TrezorPlugin(HW_PluginBase):
finally: finally:
wizard.loop.exit(exit_code) wizard.loop.exit(exit_code)
def _initialize_device(self, settings, method, device_id, wizard, handler): def _initialize_device(self, settings: TrezorInitSettings, method, device_id, wizard, handler):
item, label, pin_protection, passphrase_protection, recovery_type = settings if method == TIM_RECOVER and settings.recovery_type == RECOVERY_TYPE_SCRAMBLED_WORDS:
if method == TIM_RECOVER and recovery_type == RECOVERY_TYPE_SCRAMBLED_WORDS:
handler.show_error(_( handler.show_error(_(
"You will be asked to enter 24 words regardless of your " "You will be asked to enter 24 words regardless of your "
"seed's actual length. If you enter a word incorrectly or " "seed's actual length. If you enter a word incorrectly or "
@ -221,21 +226,25 @@ class TrezorPlugin(HW_PluginBase):
devmgr = self.device_manager() devmgr = self.device_manager()
client = devmgr.client_by_id(device_id) client = devmgr.client_by_id(device_id)
if not client:
raise Exception(_("The device was disconnected."))
if method == TIM_NEW: if method == TIM_NEW:
strength_from_word_count = {12: 128, 18: 192, 24: 256}
client.reset_device( client.reset_device(
strength=64 * (item + 2), # 128, 192 or 256 strength=strength_from_word_count[settings.word_count],
passphrase_protection=passphrase_protection, passphrase_protection=settings.passphrase_enabled,
pin_protection=pin_protection, pin_protection=settings.pin_enabled,
label=label) label=settings.label,
no_backup=settings.no_backup)
elif method == TIM_RECOVER: elif method == TIM_RECOVER:
client.recover_device( client.recover_device(
recovery_type=recovery_type, recovery_type=settings.recovery_type,
word_count=6 * (item + 2), # 12, 18 or 24 word_count=settings.word_count,
passphrase_protection=passphrase_protection, passphrase_protection=settings.passphrase_enabled,
pin_protection=pin_protection, pin_protection=settings.pin_enabled,
label=label) label=settings.label)
if recovery_type == RECOVERY_TYPE_MATRIX: if settings.recovery_type == RECOVERY_TYPE_MATRIX:
handler.close_matrix_dialog() handler.close_matrix_dialog()
else: else:
raise RuntimeError("Unsupported recovery method") raise RuntimeError("Unsupported recovery method")

Loading…
Cancel
Save