Browse Source

wizard: add bitbox02 new wallet init and checks to new wizard

master
Sander van Grieken 2 years ago
parent
commit
dd64b5c628
  1. 4
      electrum/gui/qt/wizard/wallet.py
  2. 2
      electrum/gui/qt/wizard/wizard.py
  3. 33
      electrum/plugins/bitbox02/bitbox02.py
  4. 75
      electrum/plugins/bitbox02/qt.py

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

@ -720,9 +720,9 @@ class WCScriptAndDerivation(WizardComponent, Logger):
self.apply() self.apply()
cosigner_data = self.wizard.current_cosigner(self.wizard_data) cosigner_data = self.wizard.current_cosigner(self.wizard_data)
derivation_valid = is_bip32_derivation(cosigner_data['derivation_path']) valid = is_bip32_derivation(cosigner_data['derivation_path'])
if derivation_valid: if valid:
valid, error = self.wizard.check_multisig_constraints(self.wizard_data) valid, error = self.wizard.check_multisig_constraints(self.wizard_data)
if not valid: if not valid:
# TODO: user feedback # TODO: user feedback

2
electrum/gui/qt/wizard/wizard.py

@ -167,7 +167,7 @@ class QEAbstractWizard(QDialog, MessageBoxMixin):
self.back_button.setText(_('Back') if self.can_go_back() else _('Cancel')) self.back_button.setText(_('Back') if self.can_go_back() else _('Cancel'))
self.back_button.setEnabled(not page.busy) self.back_button.setEnabled(not page.busy)
self.next_button.setText(_('Next') if not self.is_last(page.wizard_data) else _('Finish')) self.next_button.setText(_('Next') if not self.is_last(page.wizard_data) else _('Finish'))
self.next_button.setEnabled(page.valid) self.next_button.setEnabled(not page.busy and page.valid)
self.main_widget.setVisible(not page.busy and not bool(page.error)) self.main_widget.setVisible(not page.busy and not bool(page.error))
self.please_wait.setVisible(page.busy) self.please_wait.setVisible(page.busy)
self.please_wait_l.setText(page.busy_msg if page.busy_msg else _("Please wait...")) self.please_wait_l.setText(page.busy_msg if page.busy_msg else _("Please wait..."))

33
electrum/plugins/bitbox02/bitbox02.py

@ -31,13 +31,8 @@ _logger = get_logger(__name__)
try: try:
from bitbox02 import bitbox02 from bitbox02 import bitbox02
from bitbox02 import util from bitbox02 import util
from bitbox02.communication import ( from bitbox02.communication import (devices, HARDENED, u2fhid, bitbox_api_protocol,
devices, FirmwareVersionOutdatedException)
HARDENED,
u2fhid,
bitbox_api_protocol,
FirmwareVersionOutdatedException,
)
requirements_ok = True requirements_ok = True
except ImportError as e: except ImportError as e:
if not (isinstance(e, ModuleNotFoundError) and e.name == 'bitbox02'): if not (isinstance(e, ModuleNotFoundError) and e.name == 'bitbox02'):
@ -45,6 +40,10 @@ except ImportError as e:
requirements_ok = False requirements_ok = False
class BitBox02NotInitialized(UserFacingException):
pass
class BitBox02Client(HardwareClientBase): class BitBox02Client(HardwareClientBase):
# handler is a BitBox02_Handler, importing it would lead to a circular dependency # handler is a BitBox02_Handler, importing it would lead to a circular dependency
def __init__(self, handler: HardwareHandlerBase, device: Device, config: SimpleConfig, *, plugin: HW_PluginBase): def __init__(self, handler: HardwareHandlerBase, device: Device, config: SimpleConfig, *, plugin: HW_PluginBase):
@ -119,7 +118,7 @@ class BitBox02Client(HardwareClientBase):
bitbox02_config = self.config.get("bitbox02") bitbox02_config = self.config.get("bitbox02")
noise_keys = bitbox02_config.get("remote_static_noise_keys") noise_keys = bitbox02_config.get("remote_static_noise_keys")
if noise_keys is not None: if noise_keys is not None:
if pubkey.hex() in [noise_key for noise_key in noise_keys]: if pubkey.hex() in noise_keys:
return True return True
return False return False
@ -192,7 +191,7 @@ class BitBox02Client(HardwareClientBase):
def fail_if_not_initialized(self) -> None: def fail_if_not_initialized(self) -> None:
assert self.bitbox02_device assert self.bitbox02_device
if not self.bitbox02_device.device_info()["initialized"]: if not self.bitbox02_device.device_info()["initialized"]:
raise Exception( raise BitBox02NotInitialized(
"Please initialize the BitBox02 using the BitBox app first before using the BitBox02 in electrum" "Please initialize the BitBox02 using the BitBox app first before using the BitBox02 in electrum"
) )
@ -248,12 +247,8 @@ class BitBox02Client(HardwareClientBase):
else: else:
raise Exception("invalid xtype:{}".format(xtype)) raise Exception("invalid xtype:{}".format(xtype))
return self.bitbox02_device.btc_xpub( return self.bitbox02_device.btc_xpub(keypath=xpub_keypath, xpub_type=out_type, coin=coin_network,
keypath=xpub_keypath, display=display)
xpub_type=out_type,
coin=coin_network,
display=display,
)
@runs_in_hwd_thread @runs_in_hwd_thread
def label(self) -> str: def label(self) -> str:
@ -565,6 +560,7 @@ class BitBox02Client(HardwareClientBase):
) )
return signature return signature
class BitBox02_KeyStore(Hardware_KeyStore): class BitBox02_KeyStore(Hardware_KeyStore):
hw_type = "bitbox02" hw_type = "bitbox02"
device = "BitBox02" device = "BitBox02"
@ -600,7 +596,6 @@ class BitBox02_KeyStore(Hardware_KeyStore):
keypath = self.get_derivation_prefix() + "/%d/%d" % sequence keypath = self.get_derivation_prefix() + "/%d/%d" % sequence
return client.sign_message(keypath, message.encode("utf-8"), script_type) return client.sign_message(keypath, message.encode("utf-8"), script_type)
@runs_in_hwd_thread @runs_in_hwd_thread
def sign_transaction(self, tx: PartialTransaction, password: str): def sign_transaction(self, tx: PartialTransaction, password: str):
if tx.is_complete(): if tx.is_complete():
@ -612,7 +607,6 @@ class BitBox02_KeyStore(Hardware_KeyStore):
try: try:
self.handler.show_message("Authorize Transaction...") self.handler.show_message("Authorize Transaction...")
client.sign_transaction(self, tx, self.handler.get_wallet()) client.sign_transaction(self, tx, self.handler.get_wallet())
finally: finally:
self.handler.finished() self.handler.finished()
@ -639,6 +633,7 @@ class BitBox02_KeyStore(Hardware_KeyStore):
self.logger.exception("") self.logger.exception("")
self.handler.show_error(e) self.handler.show_error(e)
class BitBox02Plugin(HW_PluginBase): class BitBox02Plugin(HW_PluginBase):
keystore_class = BitBox02_KeyStore keystore_class = BitBox02_KeyStore
minimum_library = (6, 2, 0) minimum_library = (6, 2, 0)
@ -700,9 +695,9 @@ class BitBox02Plugin(HW_PluginBase):
id_ = str(d['path']) id_ = str(d['path'])
return device._replace(id_=id_) return device._replace(id_=id_)
# new wizard
def wizard_entry_for_device(self, device_info: 'DeviceInfo', *, new_wallet=True) -> str: def wizard_entry_for_device(self, device_info: 'DeviceInfo', *, new_wallet=True) -> str:
# Note: device_info.initialized for this hardware doesn't imply a seed is present,
# only that it has firmware installed
if new_wallet: if new_wallet:
return 'bitbox02_start' if device_info.initialized else 'bitbox02_not_initialized' return 'bitbox02_start' if device_info.initialized else 'bitbox02_not_initialized'
else: else:

75
electrum/plugins/bitbox02/qt.py

@ -1,29 +1,20 @@
import threading
from functools import partial from functools import partial
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtWidgets import ( from PyQt5.QtCore import Qt, QMetaObject, Q_RETURN_ARG, pyqtSlot, pyqtSignal
QPushButton, from PyQt5.QtWidgets import QLabel, QVBoxLayout, QLineEdit, QHBoxLayout
QLabel,
QVBoxLayout,
QLineEdit,
QHBoxLayout,
)
from PyQt5.QtCore import Qt, QMetaObject, Q_RETURN_ARG, pyqtSlot
from electrum.gui.qt.util import (
WindowModalDialog,
OkButton,
ButtonsTextEdit,
)
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugin import hook from electrum.plugin import hook
from electrum.util import UserCancelled, UserFacingException
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, OperationCancelled
from electrum.gui.qt.wizard.wallet import WCScriptAndDerivation, WCHWUnlock, WCHWUninitialized, WCHWXPub from electrum.gui.qt.wizard.wallet import WCScriptAndDerivation, WCHWUnlock, WCHWUninitialized, WCHWXPub
from electrum.gui.qt.util import WindowModalDialog, OkButton, ButtonsTextEdit
if TYPE_CHECKING: if TYPE_CHECKING:
from electrum.gui.qt.wizard.wallet import QENewWalletWizard from electrum.gui.qt.wizard.wallet import QENewWalletWizard
@ -77,7 +68,7 @@ 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 = {
'bitbox02_start': {'gui': WCScriptAndDerivation}, 'bitbox02_start': {'gui': WCBitbox02ScriptAndDerivation},
'bitbox02_xpub': {'gui': WCHWXPub}, 'bitbox02_xpub': {'gui': WCHWXPub},
'bitbox02_not_initialized': {'gui': WCHWUninitialized}, 'bitbox02_not_initialized': {'gui': WCHWUninitialized},
'bitbox02_unlock': {'gui': WCHWUnlock} 'bitbox02_unlock': {'gui': WCHWUnlock}
@ -92,12 +83,7 @@ class BitBox02_Handler(QtHandlerBase):
super(BitBox02_Handler, self).__init__(win, "BitBox02") super(BitBox02_Handler, self).__init__(win, "BitBox02")
def name_multisig_account(self): def name_multisig_account(self):
return QMetaObject.invokeMethod( return QMetaObject.invokeMethod(self, "_name_multisig_account", Qt.BlockingQueuedConnection, Q_RETURN_ARG(str))
self,
"_name_multisig_account",
Qt.BlockingQueuedConnection,
Q_RETURN_ARG(str),
)
@pyqtSlot(result=str) @pyqtSlot(result=str)
def _name_multisig_account(self): def _name_multisig_account(self):
@ -125,3 +111,46 @@ class BitBox02_Handler(QtHandlerBase):
dialog.setLayout(vbox) dialog.setLayout(vbox)
dialog.exec_() dialog.exec_()
return name.text().strip() return name.text().strip()
class WCBitbox02ScriptAndDerivation(WCScriptAndDerivation):
def __init__(self, parent, wizard):
WCScriptAndDerivation.__init__(self, parent, wizard)
self._busy = True
self.title = ''
self.client = None
def on_ready(self):
super().on_ready()
_name, _info = self.wizard_data['hardware_device']
plugin = self.wizard.plugins.get_plugin(_info.plugin_name)
device_id = _info.device.id_
self.client = self.wizard.plugins.device_manager.client_by_id(device_id, scan_now=False)
if not self.client.handler:
self.client.handler = plugin.create_handler(self.wizard)
self.client.setupRunning = True
self.check_device()
def check_device(self):
self.error = None
self.valid = False
self.busy = True
def check_task():
try:
self.client.pairing_dialog()
self.title = _('Script type and Derivation path')
self.valid = True
except (UserCancelled, OperationCancelled):
self.error = _('Cancelled')
self.wizard.requestPrev.emit()
except UserFacingException as e:
self.error = str(e)
except Exception as e:
self.error = repr(e)
finally:
self.busy = False
t = threading.Thread(target=check_task, daemon=True)
t.start()

Loading…
Cancel
Save