Browse Source

qt: generalize wizard HWW xpub

master
Sander van Grieken 2 years ago
parent
commit
66e9f502b0
  1. 100
      electrum/gui/qt/wizard/wallet.py
  2. 93
      electrum/plugins/bitbox02/qt.py
  3. 93
      electrum/plugins/jade/qt.py
  4. 81
      electrum/plugins/trezor/qt.py

100
electrum/gui/qt/wizard/wallet.py

@ -86,7 +86,6 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard):
'last': lambda d: d['wallet_exists'] and not d['wallet_needs_hw_unlock'] 'last': lambda d: d['wallet_exists'] and not d['wallet_needs_hw_unlock']
}, },
'hw_unlock': { 'hw_unlock': {
# 'last': True,
'gui': WCChooseHWDevice, 'gui': WCChooseHWDevice,
'next': lambda d: self.on_hardware_device(d, new_wallet=False) 'next': lambda d: self.on_hardware_device(d, new_wallet=False)
} }
@ -830,7 +829,7 @@ class WCMultisig(WizardComponent):
class WCImport(WizardComponent): class WCImport(WizardComponent):
def __init__(self, parent, wizard): def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Import Bitcoin Addresses')) WizardComponent.__init__(self, parent, wizard, title=_('Import Bitcoin Addresses or Private Keys'))
message = _( message = _(
'Enter a list of Bitcoin addresses (this will create a watching-only wallet), or a list of private keys.') 'Enter a list of Bitcoin addresses (this will create a watching-only wallet), or a list of private keys.')
header_layout = QHBoxLayout() header_layout = QHBoxLayout()
@ -1111,7 +1110,7 @@ class WCChooseHWDevice(WizardComponent, Logger):
class WCWalletPasswordHardware(WizardComponent): class WCWalletPasswordHardware(WizardComponent):
def __init__(self, parent, wizard): def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Password HW')) WizardComponent.__init__(self, parent, wizard, title=_('Encrypt using hardware'))
self.plugins = wizard.plugins self.plugins = wizard.plugins
self.playout = PasswordLayoutForHW(MSG_HW_STORAGE_ENCRYPTION) self.playout = PasswordLayoutForHW(MSG_HW_STORAGE_ENCRYPTION)
@ -1133,7 +1132,7 @@ class WCWalletPasswordHardware(WizardComponent):
class WCHWUnlock(WizardComponent, Logger): class WCHWUnlock(WizardComponent, Logger):
def __init__(self, parent, wizard): def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('unlock')) WizardComponent.__init__(self, parent, wizard, title=_('Unlocking hardware'))
Logger.__init__(self) Logger.__init__(self)
self.plugins = wizard.plugins self.plugins = wizard.plugins
self.plugin = None self.plugin = None
@ -1157,19 +1156,14 @@ class WCHWUnlock(WizardComponent, Logger):
try: try:
self.password = client.get_password_for_storage_encryption() self.password = client.get_password_for_storage_encryption()
except Exception as e: except Exception as e:
# TODO: handle user interaction exceptions (e.g. invalid pin) more gracefully self.error = repr(e) # TODO: handle user interaction exceptions (e.g. invalid pin) more gracefully
self.error = repr(e)
self.logger.error(repr(e)) self.logger.error(repr(e))
self.unlock_done() self.busy = False
self.validate()
t = threading.Thread(target=unlock_task, args=(client,), daemon=True) t = threading.Thread(target=unlock_task, args=(client,), daemon=True)
t.start() t.start()
def unlock_done(self):
self.logger.debug(f'Done unlock')
self.busy = False
self.validate()
def validate(self): def validate(self):
if self.password and not self.error: if self.password and not self.error:
self.apply() self.apply()
@ -1180,3 +1174,85 @@ class WCHWUnlock(WizardComponent, Logger):
def apply(self): def apply(self):
if self.valid: if self.valid:
self.wizard_data['password'] = self.password self.wizard_data['password'] = self.password
class WCHWXPub(WizardComponent, Logger):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Retrieving extended public key from hardware'))
Logger.__init__(self)
self.plugins = wizard.plugins
self.plugin = None
self._busy = True
self.xpub = None
self.root_fingerprint = None
self.label = None
self.soft_device_id = None
self.ok_l = WWLabel(_('Hardware keystore added to wallet'))
self.ok_l.setAlignment(Qt.AlignCenter)
self.layout().addWidget(self.ok_l)
def on_ready(self):
_name, _info = self.wizard_data['hardware_device']
self.plugin = self.plugins.get_plugin(_info.plugin_name)
self.title = _('Retrieving extended public key from {} ({})').format(_info.model_name, _info.label)
device_id = _info.device.id_
client = self.plugins.device_manager.client_by_id(device_id, scan_now=False)
if not client.handler:
client.handler = self.plugin.create_handler(self.wizard)
cosigner = self.wizard.current_cosigner(self.wizard_data)
xtype = cosigner['script_type']
derivation = cosigner['derivation_path']
def get_xpub_task(client, derivation, xtype):
try:
self.xpub = self.get_xpub_from_client(client, derivation, xtype)
self.root_fingerprint = client.request_root_fingerprint_from_device()
self.label = client.label()
self.soft_device_id = client.get_soft_device_id()
except Exception as e:
self.error = repr(e) # TODO: handle user interaction exceptions (e.g. invalid pin) more gracefully
self.logger.error(repr(e))
self.logger.debug(f'Done retrieve xpub: {self.xpub}')
self.busy = False
self.validate()
t = threading.Thread(target=get_xpub_task, args=(client, derivation, xtype), daemon=True)
t.start()
def get_xpub_from_client(self, client, derivation, xtype): # override for HWW specific client if needed
return client.get_xpub(derivation, xtype)
def validate(self):
if self.xpub and not self.error:
self.apply()
valid, error = self.wizard.check_multisig_constraints(self.wizard_data)
if not valid:
self.error = '\n'.join([
_('Could not add hardware keystore to wallet'),
error
])
self.valid = valid
else:
self.valid = False
def apply(self):
_name, _info = self.wizard_data['hardware_device']
cosigner_data = self.wizard.current_cosigner(self.wizard_data)
cosigner_data['hw_type'] = _info.plugin_name
cosigner_data['master_key'] = self.xpub
cosigner_data['root_fingerprint'] = self.root_fingerprint
cosigner_data['label'] = self.label
cosigner_data['soft_device_id'] = self.soft_device_id
class WCHWUninitialized(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Hardware not initialized'))
def on_ready(self):
_name, _info = self.wizard_data['hardware_device']
self.layout().addWidget(WWLabel(_('This {} is not initialized. Cannot continue').format(_info.model_name)))

93
electrum/plugins/bitbox02/qt.py

@ -1,4 +1,3 @@
import threading
from functools import partial from functools import partial
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@ -15,7 +14,7 @@ from PyQt5.QtCore import Qt, QMetaObject, Q_RETURN_ARG, pyqtSlot
from electrum.gui.qt.util import ( from electrum.gui.qt.util import (
WindowModalDialog, WindowModalDialog,
OkButton, OkButton,
ButtonsTextEdit, WWLabel, ButtonsTextEdit,
) )
from electrum.i18n import _ from electrum.i18n import _
@ -24,9 +23,7 @@ from electrum.plugin import hook
from .bitbox02 import BitBox02Plugin from .bitbox02 import BitBox02Plugin
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 electrum.gui.qt.wizard.wizard import WizardComponent from electrum.gui.qt.wizard.wallet import WCScriptAndDerivation, WCHWUnlock, WCHWUninitialized, WCHWXPub
from electrum.gui.qt.wizard.wallet import WCScriptAndDerivation, WCHWUnlock
from electrum.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
@ -80,9 +77,9 @@ class Plugin(BitBox02Plugin, QtPluginBase):
def extend_wizard(self, wizard: 'QENewWalletWizard'): def extend_wizard(self, wizard: 'QENewWalletWizard'):
super().extend_wizard(wizard) super().extend_wizard(wizard)
views = { views = {
'bitbox_start': { 'gui': WCScriptAndDerivation }, 'bitbox_start': {'gui': WCScriptAndDerivation},
'bitbox_xpub': { 'gui': WCBitboxXPub }, 'bitbox_xpub': {'gui': WCHWXPub},
'bitbox_not_initialized': {'gui': WCBitboxNope}, 'bitbox_not_initialized': {'gui': WCHWUninitialized},
'bitbox_unlock': {'gui': WCHWUnlock} 'bitbox_unlock': {'gui': WCHWUnlock}
} }
wizard.navmap_merge(views) wizard.navmap_merge(views)
@ -128,83 +125,3 @@ class BitBox02_Handler(QtHandlerBase):
dialog.setLayout(vbox) dialog.setLayout(vbox)
dialog.exec_() dialog.exec_()
return name.text().strip() return name.text().strip()
# TODO: almost verbatim copy of trezor WCTrezorXPub, generalize!
# problem: client.get_xpub is not uniform
class WCBitboxXPub(WizardComponent, Logger):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Hardware wallet information'))
Logger.__init__(self)
self.plugins = wizard.plugins
self.plugin = self.plugins.get_plugin('bitbox02')
self.busy_msg = _('Unlock your Bitbox02')
self._busy = True
self.xpub = None
self.root_fingerprint = None
self.label = None
self.soft_device_id = None
self.ok_l = WWLabel(_('Hardware keystore added to wallet'))
self.ok_l.setAlignment(Qt.AlignCenter)
self.layout().addWidget(self.ok_l)
def on_ready(self):
_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)
if not client.handler:
client.handler = self.plugin.create_handler(self.wizard)
cosigner = self.wizard.current_cosigner(self.wizard_data)
xtype = cosigner['script_type']
derivation = cosigner['derivation_path']
def get_xpub_task(client, derivation, xtype):
try:
self.xpub = client.get_xpub(derivation, xtype)
self.root_fingerprint = client.request_root_fingerprint_from_device()
self.label = client.label()
self.soft_device_id = client.get_soft_device_id()
except Exception as e:
# TODO: handle user interaction exceptions (e.g. invalid pin) more gracefully
self.error = repr(e)
self.logger.error(repr(e))
self.xpub_done()
t = threading.Thread(target=get_xpub_task, args=(client, derivation, xtype), daemon=True)
t.start()
def xpub_done(self):
self.logger.debug(f'Done retrieve xpub: {self.xpub}')
self.busy = False
self.validate()
def validate(self):
if self.xpub and not self.error:
self.apply()
valid, error = self.wizard.check_multisig_constraints(self.wizard_data)
if not valid:
self.error = '\n'.join([
_('Could not add hardware keystore to wallet'),
error
])
self.valid = valid
else:
self.valid = False
def apply(self):
cosigner_data = self.wizard.current_cosigner(self.wizard_data)
cosigner_data['hw_type'] = 'bitbox02'
cosigner_data['master_key'] = self.xpub
cosigner_data['root_fingerprint'] = self.root_fingerprint
cosigner_data['label'] = self.label
cosigner_data['soft_device_id'] = self.soft_device_id
class WCBitboxNope(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Bitbox02 not initialized'))
self.layout().addWidget(WWLabel(_('This Bitbox02 is not initialized. Cannot continue')))

93
electrum/plugins/jade/qt.py

@ -1,19 +1,15 @@
import threading
from functools import partial from functools import partial
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtCore import pyqtSignal
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugin import hook from electrum.plugin import hook
from electrum.wallet import Standard_Wallet from electrum.wallet import Standard_Wallet
from electrum.logging import Logger
from electrum.plugins.hw_wallet.qt import QtHandlerBase, QtPluginBase from electrum.plugins.hw_wallet.qt import QtHandlerBase, QtPluginBase
from electrum.plugins.hw_wallet import plugin from electrum.plugins.hw_wallet import plugin
from electrum.gui.qt.util import WWLabel from electrum.gui.qt.wizard.wallet import WCScriptAndDerivation, WCHWUnlock, WCHWXPub, WCHWUninitialized
from electrum.gui.qt.wizard.wallet import WCScriptAndDerivation, WCHWUnlock
from electrum.gui.qt.wizard.wizard import WizardComponent
from .jade import JadePlugin from .jade import JadePlugin
@ -47,9 +43,9 @@ class Plugin(JadePlugin, QtPluginBase):
def extend_wizard(self, wizard: 'QENewWalletWizard'): def extend_wizard(self, wizard: 'QENewWalletWizard'):
super().extend_wizard(wizard) super().extend_wizard(wizard)
views = { views = {
'jade_start': { 'gui': WCScriptAndDerivation }, 'jade_start': {'gui': WCScriptAndDerivation},
'jade_xpub': { 'gui': WCJadeXPub }, 'jade_xpub': {'gui': WCHWXPub},
'jade_not_initialized': {'gui': WCJadeNope}, 'jade_not_initialized': {'gui': WCHWUninitialized},
'jade_unlock': {'gui': WCHWUnlock} 'jade_unlock': {'gui': WCHWUnlock}
} }
wizard.navmap_merge(views) wizard.navmap_merge(views)
@ -64,82 +60,3 @@ class Jade_Handler(QtHandlerBase):
def __init__(self, win): def __init__(self, win):
super(Jade_Handler, self).__init__(win, 'Jade') super(Jade_Handler, self).__init__(win, 'Jade')
# TODO: almost verbatim copy of trezor WCTrezorXPub, generalize!
# problem: client.get_xpub is not uniform
class WCJadeXPub(WizardComponent, Logger):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Hardware wallet information'))
Logger.__init__(self)
self.plugins = wizard.plugins
self.plugin = self.plugins.get_plugin('jade')
self.busy_msg = _('Unlock your Jade')
self._busy = True
self.xpub = None
self.root_fingerprint = None
self.label = None
self.soft_device_id = None
self.ok_l = WWLabel(_('Hardware keystore added to wallet'))
self.ok_l.setAlignment(Qt.AlignCenter)
self.layout().addWidget(self.ok_l)
def on_ready(self):
_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)
if not client.handler:
client.handler = self.plugin.create_handler(self.wizard)
cosigner = self.wizard.current_cosigner(self.wizard_data)
xtype = cosigner['script_type']
derivation = cosigner['derivation_path']
def get_xpub_task(client, derivation, xtype):
try:
self.xpub = client.get_xpub(derivation, xtype)
self.root_fingerprint = client.request_root_fingerprint_from_device()
self.label = client.label()
self.soft_device_id = client.get_soft_device_id()
except Exception as e:
# TODO: handle user interaction exceptions (e.g. invalid pin) more gracefully
self.error = repr(e)
self.logger.error(repr(e))
self.xpub_done()
t = threading.Thread(target=get_xpub_task, args=(client, derivation, xtype), daemon=True)
t.start()
def xpub_done(self):
self.logger.debug(f'Done retrieve xpub: {self.xpub}')
self.busy = False
self.validate()
def validate(self):
if self.xpub and not self.error:
self.apply()
valid, error = self.wizard.check_multisig_constraints(self.wizard_data)
if not valid:
self.error = '\n'.join([
_('Could not add hardware keystore to wallet'),
error
])
self.valid = valid
else:
self.valid = False
def apply(self):
cosigner_data = self.wizard.current_cosigner(self.wizard_data)
cosigner_data['hw_type'] = 'jade'
cosigner_data['master_key'] = self.xpub
cosigner_data['root_fingerprint'] = self.root_fingerprint
cosigner_data['label'] = self.label
cosigner_data['soft_device_id'] = self.soft_device_id
class WCJadeNope(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Jade not initialized'))
self.layout().addWidget(WWLabel(_('This Jade is not initialized. Cannot continue')))

81
electrum/plugins/trezor/qt.py

@ -17,7 +17,7 @@ from electrum.plugins.hw_wallet.plugin import only_hook_if_libraries_available
from electrum.gui.qt.util import (WindowModalDialog, WWLabel, Buttons, CancelButton, from electrum.gui.qt.util import (WindowModalDialog, WWLabel, Buttons, CancelButton,
OkButton, CloseButton, PasswordLineEdit, getOpenFileName, ChoiceWidget) OkButton, CloseButton, PasswordLineEdit, getOpenFileName, ChoiceWidget)
from electrum.gui.qt.wizard.wallet import WCScriptAndDerivation, WCHWUnlock from electrum.gui.qt.wizard.wallet import WCScriptAndDerivation, WCHWUnlock, WCHWXPub
from electrum.gui.qt.wizard.wizard import WizardComponent from electrum.gui.qt.wizard.wizard import WizardComponent
from .trezor import (TrezorPlugin, TIM_NEW, TIM_RECOVER, TrezorInitSettings, from .trezor import (TrezorPlugin, TIM_NEW, TIM_RECOVER, TrezorInitSettings,
@ -473,11 +473,11 @@ class Plugin(TrezorPlugin, QtPlugin):
def extend_wizard(self, wizard: 'QENewWalletWizard'): def extend_wizard(self, wizard: 'QENewWalletWizard'):
super().extend_wizard(wizard) super().extend_wizard(wizard)
views = { views = {
'trezor_start': { 'gui': WCScriptAndDerivation }, 'trezor_start': {'gui': WCScriptAndDerivation},
'trezor_xpub': { 'gui': WCTrezorXPub }, 'trezor_xpub': {'gui': WCTrezorXPub},
'trezor_not_initialized': { 'gui': WCTrezorInitMethod }, 'trezor_not_initialized': {'gui': WCTrezorInitMethod},
'trezor_choose_new_recover': { 'gui': WCTrezorInitParams }, 'trezor_choose_new_recover': {'gui': WCTrezorInitParams},
'trezor_do_init': { 'gui': WCTrezorInit }, 'trezor_do_init': {'gui': WCTrezorInit},
'trezor_unlock': {'gui': WCHWUnlock}, 'trezor_unlock': {'gui': WCHWUnlock},
} }
wizard.navmap_merge(views) wizard.navmap_merge(views)
@ -801,73 +801,12 @@ class SettingsDialog(WindowModalDialog):
invoke_client(None) invoke_client(None)
class WCTrezorXPub(WizardComponent, Logger): class WCTrezorXPub(WCHWXPub):
def __init__(self, parent, wizard): def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Hardware wallet information')) WCHWXPub.__init__(self, parent, wizard)
Logger.__init__(self)
self.plugins = wizard.plugins
self.plugin = self.plugins.get_plugin('trezor')
self._busy = True
self.xpub = None
self.root_fingerprint = None
self.label = None
self.soft_device_id = None
self.ok_l = WWLabel(_('Hardware keystore added to wallet')) def get_xpub_from_client(self, client, derivation, xtype):
self.ok_l.setAlignment(Qt.AlignCenter) return client.get_xpub(derivation, xtype, True)
self.layout().addWidget(self.ok_l)
def on_ready(self):
_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)
cosigner = self.wizard.current_cosigner(self.wizard_data)
xtype = cosigner['script_type']
derivation = cosigner['derivation_path']
def get_xpub_task(client, derivation, xtype):
try:
self.xpub = client.get_xpub(derivation, xtype, True)
self.root_fingerprint = client.request_root_fingerprint_from_device()
self.label = client.label()
self.soft_device_id = client.get_soft_device_id()
except Exception as e:
# TODO: handle user interaction exceptions (e.g. invalid pin) more gracefully
self.error = repr(e)
self.logger.error(repr(e))
self.xpub_done()
t = threading.Thread(target=get_xpub_task, args=(client, derivation, xtype), daemon=True)
t.start()
def xpub_done(self):
self.logger.debug(f'Done retrieve xpub: {self.xpub}')
self.busy = False
self.validate()
def validate(self):
if self.xpub and not self.error:
self.apply()
valid, error = self.wizard.check_multisig_constraints(self.wizard_data)
if not valid:
self.error = '\n'.join([
_('Could not add hardware keystore to wallet'),
error
])
self.valid = valid
else:
self.valid = False
def apply(self):
cosigner_data = self.wizard.current_cosigner(self.wizard_data)
cosigner_data['hw_type'] = 'trezor'
cosigner_data['master_key'] = self.xpub
cosigner_data['root_fingerprint'] = self.root_fingerprint
cosigner_data['label'] = self.label
cosigner_data['soft_device_id'] = self.soft_device_id
class WCTrezorInitMethod(WizardComponent, Logger): class WCTrezorInitMethod(WizardComponent, Logger):

Loading…
Cancel
Save