Browse Source

qt: keepkey device init

Note: untested, don't have device
master
Sander van Grieken 2 years ago
parent
commit
5ab083b87e
  1. 12
      electrum/plugins/keepkey/keepkey.py
  2. 195
      electrum/plugins/keepkey/qt.py

12
electrum/plugins/keepkey/keepkey.py

@ -219,7 +219,7 @@ class KeepKeyPlugin(HW_PluginBase):
]
def f(method):
import threading
settings = self.request_trezor_init_settings(wizard, method, self.device)
settings = self.request_keepkey_init_settings(wizard, method, self.device)
t = threading.Thread(target=self._initialize_device_safe, args=(settings, method, device_id, wizard, handler))
t.daemon = True
t.start()
@ -510,7 +510,15 @@ class KeepKeyPlugin(HW_PluginBase):
'accept': wizard.maybe_master_pubkey,
'last': lambda d: wizard.is_single_password() and wizard.last_cosigner(d)
},
'keepkey_not_initialized': {},
'keepkey_not_initialized': {
'next': 'keepkey_choose_new_recover',
},
'keepkey_choose_new_recover': {
'next': 'keepkey_do_init',
},
'keepkey_do_init': {
'next': 'keepkey_start',
},
'keepkey_unlock': {
'last': True
},

195
electrum/plugins/keepkey/qt.py

@ -1,3 +1,4 @@
import threading
from functools import partial
from typing import TYPE_CHECKING
@ -6,18 +7,19 @@ from PyQt5.QtGui import QRegExpValidator
from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton,
QHBoxLayout, QButtonGroup, QGroupBox, QDialog,
QTextEdit, QLineEdit, QRadioButton, QCheckBox, QWidget,
QMessageBox, QFileDialog, QSlider, QTabWidget)
QMessageBox, QSlider, QTabWidget)
from electrum.gui.qt.util import (WindowModalDialog, WWLabel, Buttons, CancelButton,
OkButton, CloseButton)
OkButton, CloseButton, 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 .keepkey import KeepKeyPlugin, TIM_NEW, TIM_RECOVER, TIM_MNEMONIC
from .keepkey import KeepKeyPlugin, 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, WCHWUnlock, WCHWXPub
from electrum.gui.qt.wizard.wizard import WizardComponent
if TYPE_CHECKING:
from electrum.gui.qt.wizard.wallet import QENewWalletWizard
@ -47,6 +49,7 @@ CHARACTER_RECOVERY = (
"Press ENTER or the Seed Entered button once the last word in your "
"seed is auto-completed.")
class CharacterButton(QPushButton):
def __init__(self, text=None):
QPushButton.__init__(self, text)
@ -138,7 +141,6 @@ class CharacterDialog(WindowModalDialog):
class QtHandler(QtHandlerBase):
char_signal = pyqtSignal(object)
pin_signal = pyqtSignal(object, object)
close_char_dialog_signal = pyqtSignal()
@ -192,7 +194,6 @@ class QtHandler(QtHandlerBase):
self.done.set()
class QtPlugin(QtPluginBase):
# Derived classes must provide the following class-static variables:
# icon_file
@ -219,54 +220,68 @@ class QtPlugin(QtPluginBase):
SettingsDialog(window, self, keystore, device_id).exec_()
keystore.thread.add(connect, on_success=show_dialog)
def request_trezor_init_settings(self, wizard, method, device):
vbox = QVBoxLayout()
next_enabled = True
def request_keepkey_init_settings(self, wizard, method, device):
keepkey_init_layout = KeepkeyInitLayout(method, device)
keepkey_init_layout.validChanged.connect(wizard.next_button.setEnabled)
next_enabled = method != TIM_PRIVKEY
wizard.exec_layout(keepkey_init_layout, next_enabled=next_enabled)
return keepkey_init_layout.get_settings()
def clean_text(widget):
text = widget.toPlainText().strip()
return ' '.join(text.split())
class KeepkeyInitLayout(QVBoxLayout):
validChanged = pyqtSignal([bool], arguments=['valid'])
def __init__(self, method, device):
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]:
if self.method in [TIM_NEW, TIM_RECOVER]:
gb = QGroupBox()
hbox1 = QHBoxLayout()
gb.setLayout(hbox1)
# KeepKey recovery doesn't need a word count
if method == TIM_NEW:
vbox.addWidget(gb)
if self.method == TIM_NEW:
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(_("{} 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)
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: 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
self.validChanged.emit(is_xprv(clean_text(self.text_e)))
self.text_e.textChanged.connect(set_enabled)
vbox.addWidget(QLabel(msg))
vbox.addWidget(text)
self.addWidget(QLabel(msg))
self.addWidget(self.text_e)
pin = QLineEdit()
pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}')))
pin.setMaximumWidth(100)
@ -276,30 +291,29 @@ class QtPlugin(QtPluginBase):
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(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())
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(KeepKeyPlugin, QtPlugin):
@ -324,7 +338,9 @@ class Plugin(KeepKeyPlugin, QtPlugin):
views = {
'keepkey_start': {'gui': WCScriptAndDerivation},
'keepkey_xpub': {'gui': WCHWXPub},
'keepkey_not_initialized': {'gui': WCHWUninitialized},
'safet_not_initialized': {'gui': WCKeepkeyInitMethod},
'safet_choose_new_recover': {'gui': WCKeepkeyInitParams},
'safet_do_init': {'gui': WCKeepkeyInit},
'keepkey_unlock': {'gui': WCHWUnlock}
}
wizard.navmap_merge(views)
@ -590,3 +606,90 @@ class SettingsDialog(WindowModalDialog):
# Update information
invoke_client(None)
class WCKeepkeyInitMethod(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['keepkey_init'] = self.choice_w.selected_item[0]
class WCKeepkeyInitParams(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Set-up keepkey'))
self.plugins = wizard.plugins
self._busy = True
def on_ready(self):
_name, _info = self.wizard_data['hardware_device']
self.settings_layout = KeepkeyInitLayout(self.plugins.device_manager, self.wizard_data['keepkey_init'], _info.device.id_)
self.layout().addLayout(self.settings_layout)
self.layout().addStretch(1)
self.valid = self.wizard_data['keepkey_init'] != TIM_PRIVKEY
self.busy = False
def apply(self):
self.wizard_data['keepkey_settings'] = self.settings_layout.get_settings()
class WCKeepkeyInit(WizardComponent, Logger):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Set-up Keepkey'))
Logger.__init__(self)
self.plugins = wizard.plugins
self.plugin = self.plugins.get_plugin('keepkey')
self.layout().addWidget(WWLabel('Done'))
self._busy = True
def on_ready(self):
settings = self.wizard_data['keepkey_settings']
method = self.wizard_data['keepkey_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

Loading…
Cancel
Save