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 functools import partial
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@ -9,14 +10,17 @@ from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton,
QMessageBox, QFileDialog, QSlider, QTabWidget) QMessageBox, QFileDialog, QSlider, QTabWidget)
from electrum.gui.qt.util import (WindowModalDialog, WWLabel, Buttons, CancelButton, from electrum.gui.qt.util import (WindowModalDialog, WWLabel, Buttons, CancelButton,
OkButton, CloseButton, getOpenFileName) OkButton, CloseButton, getOpenFileName, ChoiceWidget)
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugin import hook from electrum.plugin import hook
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 .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.wallet import WCScriptAndDerivation, WCHWUninitialized, WCHWUnlock, WCHWXPub
from electrum.gui.qt.wizard.wizard import WizardComponent
from ...logging import Logger
if TYPE_CHECKING: if TYPE_CHECKING:
from electrum.gui.qt.wizard.wallet import QENewWalletWizard from electrum.gui.qt.wizard.wallet import QENewWalletWizard
@ -95,84 +99,99 @@ class QtPlugin(QtPluginBase):
keystore.thread.add(connect, on_success=show_dialog) keystore.thread.add(connect, on_success=show_dialog)
def request_safe_t_init_settings(self, wizard, method, device): def request_safe_t_init_settings(self, wizard, method, device):
vbox = QVBoxLayout() safe_t_init_layout = SafeTInitLayout(method, device)
next_enabled = True safe_t_init_layout.validChanged.connect(wizard.next_button.setEnabled)
label = QLabel(_("Enter a label to name your device:")) next_enabled = method != TIM_PRIVKEY
name = QLineEdit() wizard.exec_layout(safe_t_init_layout, next_enabled=next_enabled)
hl = QHBoxLayout()
hl.addWidget(label) return safe_t_init_layout.get_settings()
hl.addWidget(name)
hl.addStretch(1)
vbox.addLayout(hl)
def clean_text(widget): def clean_text(widget):
text = widget.toPlainText().strip() text = widget.toPlainText().strip()
return ' '.join(text.split()) 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:"))
self.label_e = QLineEdit()
hl = QHBoxLayout()
hl.addWidget(label)
hl.addWidget(self.label_e)
hl.addStretch(1)
self.addLayout(hl)
if method in [TIM_NEW, TIM_RECOVER]: if method in [TIM_NEW, TIM_RECOVER]:
gb = QGroupBox() gb = QGroupBox()
hbox1 = QHBoxLayout() hbox1 = QHBoxLayout()
gb.setLayout(hbox1) gb.setLayout(hbox1)
vbox.addWidget(gb) self.addWidget(gb)
gb.setTitle(_("Select your seed length:")) gb.setTitle(_("Select your seed length:"))
bg = QButtonGroup() self.bg = QButtonGroup()
for i, count in enumerate([12, 18, 24]): for i, count in enumerate([12, 18, 24]):
rb = QRadioButton(gb) rb = QRadioButton(gb)
rb.setText(_("{:d} words").format(count)) rb.setText(_("{:d} words").format(count))
bg.addButton(rb) self.bg.addButton(rb)
bg.setId(rb, i) self.bg.setId(rb, i)
hbox1.addWidget(rb) hbox1.addWidget(rb)
rb.setChecked(True) rb.setChecked(True)
cb_pin = QCheckBox(_('Enable PIN protection')) self.cb_pin = QCheckBox(_('Enable PIN protection'))
cb_pin.setChecked(True) self.cb_pin.setChecked(True)
else: else:
text = QTextEdit() self.text_e = QTextEdit()
text.setMaximumHeight(60) self.text_e.setMaximumHeight(60)
if method == TIM_MNEMONIC: if method == TIM_MNEMONIC:
msg = _("Enter your BIP39 mnemonic:") msg = _("Enter your BIP39 mnemonic:")
# TODO: no validation?
else: else:
msg = _("Enter the master private key beginning with xprv:") msg = _("Enter the master private key beginning with xprv:")
def set_enabled(): def set_enabled():
from electrum.bip32 import is_xprv from electrum.bip32 import is_xprv
wizard.next_button.setEnabled(is_xprv(clean_text(text))) self.validChanged.emit(is_xprv(clean_text(self.text_e)))
text.textChanged.connect(set_enabled) self.text_e.textChanged.connect(set_enabled)
next_enabled = False
self.addWidget(QLabel(msg))
vbox.addWidget(QLabel(msg)) self.addWidget(self.text_e)
vbox.addWidget(text) self.pin = QLineEdit()
pin = QLineEdit() self.pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}')))
pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}'))) self.pin.setMaximumWidth(100)
pin.setMaximumWidth(100)
hbox_pin = QHBoxLayout() hbox_pin = QHBoxLayout()
hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):"))) hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):")))
hbox_pin.addWidget(pin) hbox_pin.addWidget(self.pin)
hbox_pin.addStretch(1) hbox_pin.addStretch(1)
if method in [TIM_NEW, TIM_RECOVER]: if method in [TIM_NEW, TIM_RECOVER]:
vbox.addWidget(WWLabel(RECOMMEND_PIN)) self.addWidget(WWLabel(RECOMMEND_PIN))
vbox.addWidget(cb_pin) self.addWidget(self.cb_pin)
else: else:
vbox.addLayout(hbox_pin) self.addLayout(hbox_pin)
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")
cb_phrase = QCheckBox(_('Enable passphrases')) self.cb_phrase = QCheckBox(_('Enable passphrases'))
cb_phrase.setChecked(False) self.cb_phrase.setChecked(False)
vbox.addWidget(passphrase_msg) self.addWidget(passphrase_msg)
vbox.addWidget(passphrase_warning) self.addWidget(passphrase_warning)
vbox.addWidget(cb_phrase) self.addWidget(self.cb_phrase)
wizard.exec_layout(vbox, next_enabled=next_enabled) def get_settings(self):
if self.method in [TIM_NEW, TIM_RECOVER]:
if method in [TIM_NEW, TIM_RECOVER]: item = self.bg.checkedId()
item = bg.checkedId() pin = self.cb_pin.isChecked()
pin = cb_pin.isChecked()
else: else:
item = ' '.join(str(clean_text(text)).split()) item = ' '.join(str(clean_text(self.text_e)).split())
pin = str(pin.text()) 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): class Plugin(SafeTPlugin, QtPlugin):
@ -193,12 +212,13 @@ class Plugin(SafeTPlugin, QtPlugin):
# insert safe_t pages in new wallet wizard # insert safe_t pages in new wallet wizard
def extend_wizard(self, wizard: 'QENewWalletWizard'): def extend_wizard(self, wizard: 'QENewWalletWizard'):
# TODO: device initialization not ported yet to new wizard
super().extend_wizard(wizard) super().extend_wizard(wizard)
views = { views = {
'safet_start': {'gui': WCScriptAndDerivation}, 'safet_start': {'gui': WCScriptAndDerivation},
'safet_xpub': {'gui': WCHWXPub}, '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} 'safet_unlock': {'gui': WCHWUnlock}
} }
wizard.navmap_merge(views) wizard.navmap_merge(views)
@ -520,3 +540,90 @@ class SettingsDialog(WindowModalDialog):
# Update information # Update information
invoke_client(None) 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, 'accept': wizard.maybe_master_pubkey,
'last': lambda d: wizard.is_single_password() and wizard.last_cosigner(d) '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': { 'safet_unlock': {
'last': True 'last': True
}, },

Loading…
Cancel
Save