qt: safe_t device init
Note: untested, don't have device
This commit is contained in:
@@ -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)
|
||||||
|
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:"))
|
label = QLabel(_("Enter a label to name your device:"))
|
||||||
name = QLineEdit()
|
self.label_e = QLineEdit()
|
||||||
hl = QHBoxLayout()
|
hl = QHBoxLayout()
|
||||||
hl.addWidget(label)
|
hl.addWidget(label)
|
||||||
hl.addWidget(name)
|
hl.addWidget(self.label_e)
|
||||||
hl.addStretch(1)
|
hl.addStretch(1)
|
||||||
vbox.addLayout(hl)
|
self.addLayout(hl)
|
||||||
|
|
||||||
def clean_text(widget):
|
|
||||||
text = widget.toPlainText().strip()
|
|
||||||
return ' '.join(text.split())
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
vbox.addWidget(QLabel(msg))
|
self.addWidget(QLabel(msg))
|
||||||
vbox.addWidget(text)
|
self.addWidget(self.text_e)
|
||||||
pin = QLineEdit()
|
self.pin = QLineEdit()
|
||||||
pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}')))
|
self.pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}')))
|
||||||
pin.setMaximumWidth(100)
|
self.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
|
||||||
|
|||||||
@@ -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
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user