|
|
|
@ -1,4 +1,6 @@ |
|
|
|
|
|
|
|
import threading |
|
|
|
from functools import partial |
|
|
|
from functools import partial |
|
|
|
|
|
|
|
from typing import TYPE_CHECKING |
|
|
|
|
|
|
|
|
|
|
|
from PyQt5.QtWidgets import ( |
|
|
|
from PyQt5.QtWidgets import ( |
|
|
|
QPushButton, |
|
|
|
QPushButton, |
|
|
|
@ -13,7 +15,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, |
|
|
|
ButtonsTextEdit, WWLabel, |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
from electrum.i18n import _ |
|
|
|
from electrum.i18n import _ |
|
|
|
@ -22,6 +24,12 @@ 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 |
|
|
|
|
|
|
|
from electrum.logging import Logger |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if TYPE_CHECKING: |
|
|
|
|
|
|
|
from electrum.gui.qt.wizard.wallet import QENewWalletWizard |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Plugin(BitBox02Plugin, QtPluginBase): |
|
|
|
class Plugin(BitBox02Plugin, QtPluginBase): |
|
|
|
@ -64,6 +72,21 @@ class Plugin(BitBox02Plugin, QtPluginBase): |
|
|
|
device_name = "{} ({})".format(self.device, keystore.label) |
|
|
|
device_name = "{} ({})".format(self.device, keystore.label) |
|
|
|
mpk_text.addButton("eye1.png", on_button_click, _("Show on {}").format(device_name)) |
|
|
|
mpk_text.addButton("eye1.png", on_button_click, _("Show on {}").format(device_name)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@hook |
|
|
|
|
|
|
|
def init_wallet_wizard(self, wizard: 'QENewWalletWizard'): |
|
|
|
|
|
|
|
self.extend_wizard(wizard) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# insert trezor pages in new wallet wizard |
|
|
|
|
|
|
|
def extend_wizard(self, wizard: 'QENewWalletWizard'): |
|
|
|
|
|
|
|
super().extend_wizard(wizard) |
|
|
|
|
|
|
|
views = { |
|
|
|
|
|
|
|
'bitbox_start': { 'gui': WCScriptAndDerivation }, |
|
|
|
|
|
|
|
'bitbox_xpub': { 'gui': WCBitboxXPub }, |
|
|
|
|
|
|
|
'bitbox_not_initialized': {'gui': WCBitboxNope}, |
|
|
|
|
|
|
|
'bitbox_unlock': {'gui': WCHWUnlock} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
wizard.navmap_merge(views) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BitBox02_Handler(QtHandlerBase): |
|
|
|
class BitBox02_Handler(QtHandlerBase): |
|
|
|
MESSAGE_DIALOG_TITLE = _("BitBox02 Status") |
|
|
|
MESSAGE_DIALOG_TITLE = _("BitBox02 Status") |
|
|
|
@ -105,3 +128,83 @@ 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'))) |
|
|
|
|
|
|
|
|
|
|
|
|