Browse Source

qt: safe_t device init

Note: untested, don't have device
master
Sander van Grieken 2 years ago
parent
commit
ecc3004aee
  1. 203
      electrum/plugins/safe_t/qt.py
  2. 10
      electrum/plugins/safe_t/safe_t.py

203
electrum/plugins/safe_t/qt.py

@ -1,3 +1,4 @@
import threading
from functools import partial
from typing import TYPE_CHECKING
@ -9,14 +10,17 @@ from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton,
QMessageBox, QFileDialog, QSlider, QTabWidget)
from electrum.gui.qt.util import (WindowModalDialog, WWLabel, Buttons, CancelButton,
OkButton, CloseButton, getOpenFileName)
OkButton, CloseButton, getOpenFileName, ChoiceWidget)
from electrum.i18n import _
from electrum.plugin import hook
from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
from ..hw_wallet.plugin import only_hook_if_libraries_available
from .safe_t import SafeTPlugin, TIM_NEW, TIM_RECOVER, TIM_MNEMONIC
from .safe_t import SafeTPlugin, TIM_NEW, TIM_RECOVER, TIM_MNEMONIC, TIM_PRIVKEY
from electrum.gui.qt.wizard.wallet import WCScriptAndDerivation, WCHWUninitialized, WCHWUnlock, WCHWXPub
from electrum.gui.qt.wizard.wizard import WizardComponent
from ...logging import Logger
if TYPE_CHECKING:
from electrum.gui.qt.wizard.wallet import QENewWalletWizard
@ -95,84 +99,99 @@ class QtPlugin(QtPluginBase):
keystore.thread.add(connect, on_success=show_dialog)
def request_safe_t_init_settings(self, wizard, method, device):
vbox = QVBoxLayout()
next_enabled = True
safe_t_init_layout = SafeTInitLayout(method, device)
safe_t_init_layout.validChanged.connect(wizard.next_button.setEnabled)
next_enabled = method != TIM_PRIVKEY
wizard.exec_layout(safe_t_init_layout, next_enabled=next_enabled)
return safe_t_init_layout.get_settings()
def clean_text(widget):
text = widget.toPlainText().strip()
return ' '.join(text.split())
class SafeTInitLayout(QVBoxLayout):
validChanged = pyqtSignal([bool], arguments=['valid'])
def __init__(self, method, device):
super().__init__()
self.method = method
label = QLabel(_("Enter a label to name your device:"))
name = QLineEdit()
self.label_e = QLineEdit()
hl = QHBoxLayout()
hl.addWidget(label)
hl.addWidget(name)
hl.addWidget(self.label_e)
hl.addStretch(1)
vbox.addLayout(hl)
def clean_text(widget):
text = widget.toPlainText().strip()
return ' '.join(text.split())
self.addLayout(hl)
if method in [TIM_NEW, TIM_RECOVER]:
gb = QGroupBox()
hbox1 = QHBoxLayout()
gb.setLayout(hbox1)
vbox.addWidget(gb)
self.addWidget(gb)
gb.setTitle(_("Select your seed length:"))
bg = QButtonGroup()
self.bg = QButtonGroup()
for i, count in enumerate([12, 18, 24]):
rb = QRadioButton(gb)
rb.setText(_("{:d} words").format(count))
bg.addButton(rb)
bg.setId(rb, i)
self.bg.addButton(rb)
self.bg.setId(rb, i)
hbox1.addWidget(rb)
rb.setChecked(True)
cb_pin = QCheckBox(_('Enable PIN protection'))
cb_pin.setChecked(True)
self.cb_pin = QCheckBox(_('Enable PIN protection'))
self.cb_pin.setChecked(True)
else:
text = QTextEdit()
text.setMaximumHeight(60)
self.text_e = QTextEdit()
self.text_e.setMaximumHeight(60)
if method == TIM_MNEMONIC:
msg = _("Enter your BIP39 mnemonic:")
# TODO: no validation?
else:
msg = _("Enter the master private key beginning with xprv:")
def set_enabled():
from electrum.bip32 import is_xprv
wizard.next_button.setEnabled(is_xprv(clean_text(text)))
text.textChanged.connect(set_enabled)
next_enabled = False
vbox.addWidget(QLabel(msg))
vbox.addWidget(text)
pin = QLineEdit()
pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}')))
pin.setMaximumWidth(100)
self.validChanged.emit(is_xprv(clean_text(self.text_e)))
self.text_e.textChanged.connect(set_enabled)
self.addWidget(QLabel(msg))
self.addWidget(self.text_e)
self.pin = QLineEdit()
self.pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}')))
self.pin.setMaximumWidth(100)
hbox_pin = QHBoxLayout()
hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):")))
hbox_pin.addWidget(pin)
hbox_pin.addWidget(self.pin)
hbox_pin.addStretch(1)
if method in [TIM_NEW, TIM_RECOVER]:
vbox.addWidget(WWLabel(RECOMMEND_PIN))
vbox.addWidget(cb_pin)
self.addWidget(WWLabel(RECOMMEND_PIN))
self.addWidget(self.cb_pin)
else:
vbox.addLayout(hbox_pin)
self.addLayout(hbox_pin)
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)
wizard.exec_layout(vbox, next_enabled=next_enabled)
if method in [TIM_NEW, TIM_RECOVER]:
item = bg.checkedId()
pin = cb_pin.isChecked()
self.cb_phrase = QCheckBox(_('Enable passphrases'))
self.cb_phrase.setChecked(False)
self.addWidget(passphrase_msg)
self.addWidget(passphrase_warning)
self.addWidget(self.cb_phrase)
def get_settings(self):
if self.method in [TIM_NEW, TIM_RECOVER]:
item = self.bg.checkedId()
pin = self.cb_pin.isChecked()
else:
item = ' '.join(str(clean_text(text)).split())
pin = str(pin.text())
item = ' '.join(str(clean_text(self.text_e)).split())
pin = str(self.pin.text())
return (item, name.text(), pin, cb_phrase.isChecked())
return item, self.label_e.text(), pin, self.cb_phrase.isChecked()
class Plugin(SafeTPlugin, QtPlugin):
@ -193,12 +212,13 @@ class Plugin(SafeTPlugin, QtPlugin):
# insert safe_t pages in new wallet wizard
def extend_wizard(self, wizard: 'QENewWalletWizard'):
# TODO: device initialization not ported yet to new wizard
super().extend_wizard(wizard)
views = {
'safet_start': {'gui': WCScriptAndDerivation},
'safet_xpub': {'gui': WCHWXPub},
'safet_not_initialized': {'gui': WCHWUninitialized},
'safet_not_initialized': {'gui': WCSafeTInitMethod},
'safet_choose_new_recover': {'gui': WCSafeTInitParams},
'safet_do_init': {'gui': WCSafeTInit},
'safet_unlock': {'gui': WCHWUnlock}
}
wizard.navmap_merge(views)
@ -520,3 +540,90 @@ class SettingsDialog(WindowModalDialog):
# Update information
invoke_client(None)
class WCSafeTInitMethod(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('HW Setup'))
def on_ready(self):
_name, _info = self.wizard_data['hardware_device']
msg = _("Choose how you want to initialize your {}.\n\n"
"The first two methods are secure as no secret information "
"is entered into your computer.\n\n"
"For the last two methods you input secrets on your keyboard "
"and upload them to your {}, and so you should "
"only do those on a computer you know to be trustworthy "
"and free of malware."
).format(_info.model_name, _info.model_name)
choices = [
# Must be short as QT doesn't word-wrap radio button text
(TIM_NEW, _("Let the device generate a completely new seed randomly")),
(TIM_RECOVER, _("Recover from a seed you have previously written down")),
(TIM_MNEMONIC, _("Upload a BIP39 mnemonic to generate the seed")),
(TIM_PRIVKEY, _("Upload a master private key"))
]
self.choice_w = ChoiceWidget(message=msg, choices=choices)
self.layout().addWidget(self.choice_w)
self.layout().addStretch(1)
self._valid = True
def apply(self):
self.wizard_data['safe_t_init'] = self.choice_w.selected_item[0]
class WCSafeTInitParams(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Set-up safe-t'))
self.plugins = wizard.plugins
self._busy = True
def on_ready(self):
_name, _info = self.wizard_data['hardware_device']
self.settings_layout = SafeTInitLayout(self.plugins.device_manager, self.wizard_data['safe_t_init'], _info.device.id_)
self.layout().addLayout(self.settings_layout)
self.layout().addStretch(1)
self.valid = self.wizard_data['safe_t_init'] != TIM_PRIVKEY
self.busy = False
def apply(self):
self.wizard_data['safe_t_settings'] = self.settings_layout.get_settings()
class WCSafeTInit(WizardComponent, Logger):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Set-up safe-t'))
Logger.__init__(self)
self.plugins = wizard.plugins
self.plugin = self.plugins.get_plugin('safe_t')
self.layout().addWidget(WWLabel('Done'))
self._busy = True
def on_ready(self):
settings = self.wizard_data['safe_t_settings']
method = self.wizard_data['safe_t_init']
_name, _info = self.wizard_data['hardware_device']
device_id = _info.device.id_
client = self.plugins.device_manager.client_by_id(device_id, scan_now=False)
client.handler = self.plugin.create_handler(self.wizard)
def initialize_device_task(settings, method, device_id, wizard, handler):
self.plugin._initialize_device(settings, method, device_id, wizard, handler)
self.init_done()
t = threading.Thread(
target=initialize_device_task,
args=(settings, method, device_id, None, client.handler),
daemon=True)
t.start()
def init_done(self):
self.logger.info('Done initialize device')
self.busy = False
def apply(self):
pass

10
electrum/plugins/safe_t/safe_t.py

@ -482,7 +482,15 @@ class SafeTPlugin(HW_PluginBase):
'accept': wizard.maybe_master_pubkey,
'last': lambda d: wizard.is_single_password() and wizard.last_cosigner(d)
},
'safet_not_initialized': {},
'safet_not_initialized': {
'next': 'safet_choose_new_recover',
},
'safet_choose_new_recover': {
'next': 'safet_do_init',
},
'safet_do_init': {
'next': 'safet_start',
},
'safet_unlock': {
'last': True
},

Loading…
Cancel
Save