electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

bitbox02.py (27254B)


      1 #
      2 # BitBox02 Electrum plugin code.
      3 #
      4 
      5 import hid
      6 from typing import TYPE_CHECKING, Dict, Tuple, Optional, List, Any, Callable
      7 
      8 from electrum import bip32, constants
      9 from electrum.i18n import _
     10 from electrum.keystore import Hardware_KeyStore
     11 from electrum.transaction import PartialTransaction
     12 from electrum.wallet import Standard_Wallet, Multisig_Wallet, Deterministic_Wallet
     13 from electrum.util import bh2u, UserFacingException
     14 from electrum.base_wizard import ScriptTypeNotSupported, BaseWizard
     15 from electrum.logging import get_logger
     16 from electrum.plugin import Device, DeviceInfo, runs_in_hwd_thread
     17 from electrum.simple_config import SimpleConfig
     18 from electrum.json_db import StoredDict
     19 from electrum.storage import get_derivation_used_for_hw_device_encryption
     20 from electrum.bitcoin import OnchainOutputType
     21 
     22 import electrum.bitcoin as bitcoin
     23 import electrum.ecc as ecc
     24 
     25 from ..hw_wallet import HW_PluginBase, HardwareClientBase
     26 
     27 
     28 _logger = get_logger(__name__)
     29 
     30 
     31 try:
     32     from bitbox02 import bitbox02
     33     from bitbox02 import util
     34     from bitbox02.communication import (
     35         devices,
     36         HARDENED,
     37         u2fhid,
     38         bitbox_api_protocol,
     39         FirmwareVersionOutdatedException,
     40     )
     41     requirements_ok = True
     42 except ImportError as e:
     43     if not (isinstance(e, ModuleNotFoundError) and e.name == 'bitbox02'):
     44         _logger.exception('error importing bitbox02 plugin deps')
     45     requirements_ok = False
     46 
     47 
     48 class BitBox02Client(HardwareClientBase):
     49     # handler is a BitBox02_Handler, importing it would lead to a circular dependency
     50     def __init__(self, handler: Any, device: Device, config: SimpleConfig, *, plugin: HW_PluginBase):
     51         HardwareClientBase.__init__(self, plugin=plugin)
     52         self.bitbox02_device = None  # type: Optional[bitbox02.BitBox02]
     53         self.handler = handler
     54         self.device_descriptor = device
     55         self.config = config
     56         self.bitbox_hid_info = None
     57         if self.config.get("bitbox02") is None:
     58             bitbox02_config: dict = {
     59                 "remote_static_noise_keys": [],
     60                 "noise_privkey": None,
     61             }
     62             self.config.set_key("bitbox02", bitbox02_config)
     63 
     64         bitboxes = devices.get_any_bitbox02s()
     65         for bitbox in bitboxes:
     66             if (
     67                 bitbox["path"] == self.device_descriptor.path
     68                 and bitbox["interface_number"]
     69                 == self.device_descriptor.interface_number
     70             ):
     71                 self.bitbox_hid_info = bitbox
     72         if self.bitbox_hid_info is None:
     73             raise Exception("No BitBox02 detected")
     74 
     75     def is_initialized(self) -> bool:
     76         return True
     77 
     78     @runs_in_hwd_thread
     79     def close(self):
     80         try:
     81             self.bitbox02_device.close()
     82         except:
     83             pass
     84 
     85     def has_usable_connection_with_device(self) -> bool:
     86         if self.bitbox_hid_info is None:
     87             return False
     88         return True
     89 
     90     @runs_in_hwd_thread
     91     def get_soft_device_id(self) -> Optional[str]:
     92         if self.handler is None:
     93             # Can't do the pairing without the handler. This happens at wallet creation time, when
     94             # listing the devices.
     95             return None
     96         if self.bitbox02_device is None:
     97             self.pairing_dialog()
     98         return self.bitbox02_device.root_fingerprint().hex()
     99 
    100     @runs_in_hwd_thread
    101     def pairing_dialog(self):
    102         def pairing_step(code: str, device_response: Callable[[], bool]) -> bool:
    103             msg = "Please compare and confirm the pairing code on your BitBox02:\n" + code
    104             self.handler.show_message(msg)
    105             try:
    106                 res = device_response()
    107             except:
    108                 # Close the hid device on exception
    109                 hid_device.close()
    110                 raise
    111             finally:
    112                 self.handler.finished()
    113             return res
    114 
    115         def exists_remote_static_pubkey(pubkey: bytes) -> bool:
    116             bitbox02_config = self.config.get("bitbox02")
    117             noise_keys = bitbox02_config.get("remote_static_noise_keys")
    118             if noise_keys is not None:
    119                 if pubkey.hex() in [noise_key for noise_key in noise_keys]:
    120                     return True
    121             return False
    122 
    123         def set_remote_static_pubkey(pubkey: bytes) -> None:
    124             if not exists_remote_static_pubkey(pubkey):
    125                 bitbox02_config = self.config.get("bitbox02")
    126                 if bitbox02_config.get("remote_static_noise_keys") is not None:
    127                     bitbox02_config["remote_static_noise_keys"].append(pubkey.hex())
    128                 else:
    129                     bitbox02_config["remote_static_noise_keys"] = [pubkey.hex()]
    130                 self.config.set_key("bitbox02", bitbox02_config)
    131 
    132         def get_noise_privkey() -> Optional[bytes]:
    133             bitbox02_config = self.config.get("bitbox02")
    134             privkey = bitbox02_config.get("noise_privkey")
    135             if privkey is not None:
    136                 return bytes.fromhex(privkey)
    137             return None
    138 
    139         def set_noise_privkey(privkey: bytes) -> None:
    140             bitbox02_config = self.config.get("bitbox02")
    141             bitbox02_config["noise_privkey"] = privkey.hex()
    142             self.config.set_key("bitbox02", bitbox02_config)
    143 
    144         def attestation_warning() -> None:
    145             self.handler.show_error(
    146                 "The BitBox02 attestation failed.\nTry reconnecting the BitBox02.\nWarning: The device might not be genuine, if the\n problem persists please contact Shift support.",
    147                 blocking=True
    148             )
    149 
    150         class NoiseConfig(bitbox_api_protocol.BitBoxNoiseConfig):
    151             """NoiseConfig extends BitBoxNoiseConfig"""
    152 
    153             def show_pairing(self, code: str, device_response: Callable[[], bool]) -> bool:
    154                 return pairing_step(code, device_response)
    155 
    156             def attestation_check(self, result: bool) -> None:
    157                 if not result:
    158                     attestation_warning()
    159 
    160             def contains_device_static_pubkey(self, pubkey: bytes) -> bool:
    161                 return exists_remote_static_pubkey(pubkey)
    162 
    163             def add_device_static_pubkey(self, pubkey: bytes) -> None:
    164                 return set_remote_static_pubkey(pubkey)
    165 
    166             def get_app_static_privkey(self) -> Optional[bytes]:
    167                 return get_noise_privkey()
    168 
    169             def set_app_static_privkey(self, privkey: bytes) -> None:
    170                 return set_noise_privkey(privkey)
    171 
    172         if self.bitbox02_device is None:
    173             hid_device = hid.device()
    174             hid_device.open_path(self.bitbox_hid_info["path"])
    175 
    176             bitbox02_device = bitbox02.BitBox02(
    177                 transport=u2fhid.U2FHid(hid_device),
    178                 device_info=self.bitbox_hid_info,
    179                 noise_config=NoiseConfig(),
    180             )
    181             try:
    182                 bitbox02_device.check_min_version()
    183             except FirmwareVersionOutdatedException:
    184                 raise
    185             self.bitbox02_device = bitbox02_device
    186 
    187         self.fail_if_not_initialized()
    188 
    189     def fail_if_not_initialized(self) -> None:
    190         assert self.bitbox02_device
    191         if not self.bitbox02_device.device_info()["initialized"]:
    192             raise Exception(
    193                 "Please initialize the BitBox02 using the BitBox app first before using the BitBox02 in electrum"
    194             )
    195 
    196     def coin_network_from_electrum_network(self) -> int:
    197         if constants.net.TESTNET:
    198             return bitbox02.btc.TBTC
    199         return bitbox02.btc.BTC
    200 
    201     @runs_in_hwd_thread
    202     def get_password_for_storage_encryption(self) -> str:
    203         derivation = get_derivation_used_for_hw_device_encryption()
    204         derivation_list = bip32.convert_bip32_path_to_list_of_uint32(derivation)
    205         xpub = self.bitbox02_device.electrum_encryption_key(derivation_list)
    206         node = bip32.BIP32Node.from_xkey(xpub, net = constants.BitcoinMainnet()).subkey_at_public_derivation(())
    207         return node.eckey.get_public_key_bytes(compressed=True).hex()
    208 
    209     @runs_in_hwd_thread
    210     def get_xpub(self, bip32_path: str, xtype: str, *, display: bool = False) -> str:
    211         if self.bitbox02_device is None:
    212             self.pairing_dialog()
    213 
    214         if self.bitbox02_device is None:
    215             raise Exception(
    216                 "Need to setup communication first before attempting any BitBox02 calls"
    217             )
    218 
    219         self.fail_if_not_initialized()
    220 
    221         xpub_keypath = bip32.convert_bip32_path_to_list_of_uint32(bip32_path)
    222         coin_network = self.coin_network_from_electrum_network()
    223 
    224         if xtype == "p2wpkh":
    225             if coin_network == bitbox02.btc.BTC:
    226                 out_type = bitbox02.btc.BTCPubRequest.ZPUB
    227             else:
    228                 out_type = bitbox02.btc.BTCPubRequest.VPUB
    229         elif xtype == "p2wpkh-p2sh":
    230             if coin_network == bitbox02.btc.BTC:
    231                 out_type = bitbox02.btc.BTCPubRequest.YPUB
    232             else:
    233                 out_type = bitbox02.btc.BTCPubRequest.UPUB
    234         elif xtype == "p2wsh-p2sh":
    235             if coin_network == bitbox02.btc.BTC:
    236                 out_type = bitbox02.btc.BTCPubRequest.CAPITAL_YPUB
    237             else:
    238                 out_type = bitbox02.btc.BTCPubRequest.CAPITAL_UPUB
    239         elif xtype == "p2wsh":
    240             if coin_network == bitbox02.btc.BTC:
    241                 out_type = bitbox02.btc.BTCPubRequest.CAPITAL_ZPUB
    242             else:
    243                 out_type = bitbox02.btc.BTCPubRequest.CAPITAL_VPUB
    244         # The other legacy types are not supported
    245         else:
    246             raise Exception("invalid xtype:{}".format(xtype))
    247 
    248         return self.bitbox02_device.btc_xpub(
    249             keypath=xpub_keypath,
    250             xpub_type=out_type,
    251             coin=coin_network,
    252             display=display,
    253         )
    254 
    255     @runs_in_hwd_thread
    256     def label(self) -> str:
    257         if self.handler is None:
    258             # Can't do the pairing without the handler. This happens at wallet creation time, when
    259             # listing the devices.
    260             return super().label()
    261         if self.bitbox02_device is None:
    262             self.pairing_dialog()
    263         # We add the fingerprint to the label, as if there are two devices with the same label, the
    264         # device manager can mistake one for another and fail.
    265         return "%s (%s)" % (
    266             self.bitbox02_device.device_info()["name"],
    267             self.bitbox02_device.root_fingerprint().hex(),
    268         )
    269 
    270     @runs_in_hwd_thread
    271     def request_root_fingerprint_from_device(self) -> str:
    272         if self.bitbox02_device is None:
    273             raise Exception(
    274                 "Need to setup communication first before attempting any BitBox02 calls"
    275             )
    276 
    277         return self.bitbox02_device.root_fingerprint().hex()
    278 
    279     def is_pairable(self) -> bool:
    280         if self.bitbox_hid_info is None:
    281             return False
    282         return True
    283 
    284     @runs_in_hwd_thread
    285     def btc_multisig_config(
    286         self, coin, bip32_path: List[int], wallet: Multisig_Wallet, xtype: str,
    287     ):
    288         """
    289         Set and get a multisig config with the current device and some other arbitrary xpubs.
    290         Registers it on the device if not already registered.
    291         xtype: 'p2wsh' | 'p2wsh-p2sh'
    292         """
    293         assert xtype in ("p2wsh", "p2wsh-p2sh")
    294         if self.bitbox02_device is None:
    295             raise Exception(
    296                 "Need to setup communication first before attempting any BitBox02 calls"
    297             )
    298         account_keypath = bip32_path[:-2]
    299         xpubs = wallet.get_master_public_keys()
    300         our_xpub = self.get_xpub(
    301             bip32.convert_bip32_intpath_to_strpath(account_keypath), xtype
    302         )
    303 
    304         multisig_config = bitbox02.btc.BTCScriptConfig(
    305             multisig=bitbox02.btc.BTCScriptConfig.Multisig(
    306                 threshold=wallet.m,
    307                 xpubs=[util.parse_xpub(xpub) for xpub in xpubs],
    308                 our_xpub_index=xpubs.index(our_xpub),
    309                 script_type={
    310                     "p2wsh": bitbox02.btc.BTCScriptConfig.Multisig.P2WSH,
    311                     "p2wsh-p2sh": bitbox02.btc.BTCScriptConfig.Multisig.P2WSH_P2SH,
    312                 }[xtype]
    313             )
    314         )
    315 
    316         is_registered = self.bitbox02_device.btc_is_script_config_registered(
    317             coin, multisig_config, account_keypath
    318         )
    319         if not is_registered:
    320             name = self.handler.name_multisig_account()
    321             try:
    322                 self.bitbox02_device.btc_register_script_config(
    323                     coin=coin,
    324                     script_config=multisig_config,
    325                     keypath=account_keypath,
    326                     name=name,
    327                 )
    328             except bitbox02.DuplicateEntryException:
    329                 raise
    330             except:
    331                 raise UserFacingException("Failed to register multisig\naccount configuration on BitBox02")
    332         return multisig_config
    333 
    334     @runs_in_hwd_thread
    335     def show_address(
    336         self, bip32_path: str, address_type: str, wallet: Deterministic_Wallet
    337     ) -> str:
    338 
    339         if self.bitbox02_device is None:
    340             raise Exception(
    341                 "Need to setup communication first before attempting any BitBox02 calls"
    342             )
    343 
    344         address_keypath = bip32.convert_bip32_path_to_list_of_uint32(bip32_path)
    345         coin_network = self.coin_network_from_electrum_network()
    346 
    347         if address_type == "p2wpkh":
    348             script_config = bitbox02.btc.BTCScriptConfig(
    349                 simple_type=bitbox02.btc.BTCScriptConfig.P2WPKH
    350             )
    351         elif address_type == "p2wpkh-p2sh":
    352             script_config = bitbox02.btc.BTCScriptConfig(
    353                 simple_type=bitbox02.btc.BTCScriptConfig.P2WPKH_P2SH
    354             )
    355         elif address_type in ("p2wsh-p2sh", "p2wsh"):
    356             if type(wallet) is Multisig_Wallet:
    357                 script_config = self.btc_multisig_config(
    358                     coin_network, address_keypath, wallet, address_type,
    359                 )
    360             else:
    361                 raise Exception("Can only use p2wsh-p2sh or p2wsh with multisig wallets")
    362         else:
    363             raise Exception(
    364                 "invalid address xtype: {} is not supported by the BitBox02".format(
    365                     address_type
    366                 )
    367             )
    368 
    369         return self.bitbox02_device.btc_address(
    370             keypath=address_keypath,
    371             coin=coin_network,
    372             script_config=script_config,
    373             display=True,
    374         )
    375 
    376     def _get_coin(self):
    377         return bitbox02.btc.TBTC if constants.net.TESTNET else bitbox02.btc.BTC
    378 
    379     @runs_in_hwd_thread
    380     def sign_transaction(
    381         self,
    382         keystore: Hardware_KeyStore,
    383         tx: PartialTransaction,
    384         wallet: Deterministic_Wallet,
    385     ):
    386         if tx.is_complete():
    387             return
    388 
    389         if self.bitbox02_device is None:
    390             raise Exception(
    391                 "Need to setup communication first before attempting any BitBox02 calls"
    392             )
    393 
    394         coin = self._get_coin()
    395         tx_script_type = None
    396 
    397         # Build BTCInputType list
    398         inputs = []
    399         for txin in tx.inputs():
    400             my_pubkey, full_path = keystore.find_my_pubkey_in_txinout(txin)
    401 
    402             if full_path is None:
    403                 raise Exception(
    404                     "A wallet owned pubkey was not found in the transaction input to be signed"
    405                 )
    406 
    407             prev_tx = txin.utxo
    408             if prev_tx is None:
    409                 raise UserFacingException(_('Missing previous tx.'))
    410 
    411             prev_inputs: List[bitbox02.BTCPrevTxInputType] = []
    412             prev_outputs: List[bitbox02.BTCPrevTxOutputType] = []
    413             for prev_txin in prev_tx.inputs():
    414                 prev_inputs.append(
    415                     {
    416                         "prev_out_hash": prev_txin.prevout.txid[::-1],
    417                         "prev_out_index": prev_txin.prevout.out_idx,
    418                         "signature_script": prev_txin.script_sig,
    419                         "sequence": prev_txin.nsequence,
    420                     }
    421                 )
    422             for prev_txout in prev_tx.outputs():
    423                 prev_outputs.append(
    424                     {
    425                         "value": prev_txout.value,
    426                         "pubkey_script": prev_txout.scriptpubkey,
    427                     }
    428                 )
    429 
    430             inputs.append(
    431                 {
    432                     "prev_out_hash": txin.prevout.txid[::-1],
    433                     "prev_out_index": txin.prevout.out_idx,
    434                     "prev_out_value": txin.value_sats(),
    435                     "sequence": txin.nsequence,
    436                     "keypath": full_path,
    437                     "script_config_index": 0,
    438                     "prev_tx": {
    439                         "version": prev_tx.version,
    440                         "locktime": prev_tx.locktime,
    441                         "inputs": prev_inputs,
    442                         "outputs": prev_outputs,
    443                     },
    444                 }
    445             )
    446 
    447             if tx_script_type == None:
    448                 tx_script_type = txin.script_type
    449             elif tx_script_type != txin.script_type:
    450                 raise Exception("Cannot mix different input script types")
    451 
    452         if tx_script_type == "p2wpkh":
    453             tx_script_type = bitbox02.btc.BTCScriptConfig(
    454                 simple_type=bitbox02.btc.BTCScriptConfig.P2WPKH
    455             )
    456         elif tx_script_type == "p2wpkh-p2sh":
    457             tx_script_type = bitbox02.btc.BTCScriptConfig(
    458                 simple_type=bitbox02.btc.BTCScriptConfig.P2WPKH_P2SH
    459             )
    460         elif tx_script_type in ("p2wsh-p2sh", "p2wsh"):
    461             if type(wallet) is Multisig_Wallet:
    462                 tx_script_type = self.btc_multisig_config(coin, full_path, wallet, tx_script_type)
    463             else:
    464                 raise Exception("Can only use p2wsh-p2sh or p2wsh with multisig wallets")
    465         else:
    466             raise UserFacingException(
    467                 "invalid input script type: {} is not supported by the BitBox02".format(
    468                     tx_script_type
    469                 )
    470             )
    471 
    472         # Build BTCOutputType list
    473         outputs = []
    474         for txout in tx.outputs():
    475             assert txout.address
    476             # check for change
    477             if txout.is_change:
    478                 my_pubkey, change_pubkey_path = keystore.find_my_pubkey_in_txinout(txout)
    479                 outputs.append(
    480                     bitbox02.BTCOutputInternal(
    481                         keypath=change_pubkey_path, value=txout.value, script_config_index=0,
    482                     )
    483                 )
    484             else:
    485                 addrtype, pubkey_hash = bitcoin.address_to_hash(txout.address)
    486                 if addrtype == OnchainOutputType.P2PKH:
    487                     output_type = bitbox02.btc.P2PKH
    488                 elif addrtype == OnchainOutputType.P2SH:
    489                     output_type = bitbox02.btc.P2SH
    490                 elif addrtype == OnchainOutputType.WITVER0_P2WPKH:
    491                     output_type = bitbox02.btc.P2WPKH
    492                 elif addrtype == OnchainOutputType.WITVER0_P2WSH:
    493                     output_type = bitbox02.btc.P2WSH
    494                 else:
    495                     raise UserFacingException(
    496                         "Received unsupported output type during transaction signing: {} is not supported by the BitBox02".format(
    497                             addrtype
    498                         )
    499                     )
    500                 outputs.append(
    501                     bitbox02.BTCOutputExternal(
    502                         output_type=output_type,
    503                         output_hash=pubkey_hash,
    504                         value=txout.value,
    505                     )
    506                 )
    507 
    508         keypath_account = full_path[:-2]
    509         sigs = self.bitbox02_device.btc_sign(
    510             coin,
    511             [bitbox02.btc.BTCScriptConfigWithKeypath(
    512                 script_config=tx_script_type,
    513                 keypath=keypath_account,
    514             )],
    515             inputs=inputs,
    516             outputs=outputs,
    517             locktime=tx.locktime,
    518             version=tx.version,
    519         )
    520 
    521         # Fill signatures
    522         if len(sigs) != len(tx.inputs()):
    523             raise Exception("Incorrect number of inputs signed.")  # Should never occur
    524         signatures = [bh2u(ecc.der_sig_from_sig_string(x[1])) + "01" for x in sigs]
    525         tx.update_signatures(signatures)
    526 
    527     def sign_message(self, keypath: str, message: bytes, xtype: str) -> bytes:
    528         if self.bitbox02_device is None:
    529             raise Exception(
    530                 "Need to setup communication first before attempting any BitBox02 calls"
    531             )
    532 
    533         try:
    534             simple_type = {
    535                 "p2wpkh-p2sh":bitbox02.btc.BTCScriptConfig.P2WPKH_P2SH,
    536                 "p2wpkh": bitbox02.btc.BTCScriptConfig.P2WPKH,
    537             }[xtype]
    538         except KeyError:
    539             raise UserFacingException("The BitBox02 does not support signing messages for this address type: {}".format(xtype))
    540 
    541         _, _, signature = self.bitbox02_device.btc_sign_msg(
    542             self._get_coin(),
    543             bitbox02.btc.BTCScriptConfigWithKeypath(
    544                 script_config=bitbox02.btc.BTCScriptConfig(
    545                     simple_type=simple_type,
    546                 ),
    547                 keypath=bip32.convert_bip32_path_to_list_of_uint32(keypath),
    548             ),
    549             message,
    550         )
    551         return signature
    552 
    553 class BitBox02_KeyStore(Hardware_KeyStore):
    554     hw_type = "bitbox02"
    555     device = "BitBox02"
    556     plugin: "BitBox02Plugin"
    557 
    558     def __init__(self, d: dict):
    559         super().__init__(d)
    560         self.force_watching_only = False
    561         self.ux_busy = False
    562 
    563     def get_client(self):
    564         return self.plugin.get_client(self)
    565 
    566     def give_error(self, message: Exception, clear_client: bool = False):
    567         self.logger.info(message)
    568         if not self.ux_busy:
    569             self.handler.show_error(message)
    570         else:
    571             self.ux_busy = False
    572         if clear_client:
    573             self.client = None
    574         raise UserFacingException(message)
    575 
    576     def decrypt_message(self, pubkey, message, password):
    577         raise UserFacingException(
    578             _(
    579                 "Message encryption, decryption and signing are currently not supported for {}"
    580             ).format(self.device)
    581         )
    582 
    583     def sign_message(self, sequence, message, password):
    584         if password:
    585             raise Exception("BitBox02 does not accept a password from the host")
    586         client = self.get_client()
    587         keypath = self.get_derivation_prefix() + "/%d/%d" % sequence
    588         xtype = self.get_bip32_node_for_xpub().xtype
    589         return client.sign_message(keypath, message.encode("utf-8"), xtype)
    590 
    591 
    592     @runs_in_hwd_thread
    593     def sign_transaction(self, tx: PartialTransaction, password: str):
    594         if tx.is_complete():
    595             return
    596         client = self.get_client()
    597         assert isinstance(client, BitBox02Client)
    598 
    599         try:
    600             try:
    601                 self.handler.show_message("Authorize Transaction...")
    602                 client.sign_transaction(self, tx, self.handler.get_wallet())
    603 
    604             finally:
    605                 self.handler.finished()
    606 
    607         except Exception as e:
    608             self.logger.exception("")
    609             self.give_error(e, True)
    610             return
    611 
    612     @runs_in_hwd_thread
    613     def show_address(
    614         self, sequence: Tuple[int, int], txin_type: str, wallet: Deterministic_Wallet
    615     ):
    616         client = self.get_client()
    617         address_path = "{}/{}/{}".format(
    618             self.get_derivation_prefix(), sequence[0], sequence[1]
    619         )
    620         try:
    621             try:
    622                 self.handler.show_message(_("Showing address ..."))
    623                 dev_addr = client.show_address(address_path, txin_type, wallet)
    624             finally:
    625                 self.handler.finished()
    626         except Exception as e:
    627             self.logger.exception("")
    628             self.handler.show_error(e)
    629 
    630 class BitBox02Plugin(HW_PluginBase):
    631     keystore_class = BitBox02_KeyStore
    632     minimum_library = (5, 0, 0)
    633     DEVICE_IDS = [(0x03EB, 0x2403)]
    634 
    635     SUPPORTED_XTYPES = ("p2wpkh-p2sh", "p2wpkh", "p2wsh", "p2wsh-p2sh")
    636 
    637     def __init__(self, parent: HW_PluginBase, config: SimpleConfig, name: str):
    638         super().__init__(parent, config, name)
    639 
    640         self.libraries_available = self.check_libraries_available()
    641         if not self.libraries_available:
    642             return
    643         self.device_manager().register_devices(self.DEVICE_IDS, plugin=self)
    644 
    645     def get_library_version(self):
    646         try:
    647             from bitbox02 import bitbox02
    648             version = bitbox02.__version__
    649         except:
    650             version = "unknown"
    651         if requirements_ok:
    652             return version
    653         else:
    654             raise ImportError()
    655 
    656     # handler is a BitBox02_Handler
    657     @runs_in_hwd_thread
    658     def create_client(self, device: Device, handler: Any) -> BitBox02Client:
    659         if not handler:
    660             self.handler = handler
    661         return BitBox02Client(handler, device, self.config, plugin=self)
    662 
    663     def setup_device(
    664         self, device_info: DeviceInfo, wizard: BaseWizard, purpose: int
    665     ):
    666         device_id = device_info.device.id_
    667         client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard)
    668         assert isinstance(client, BitBox02Client)
    669         if client.bitbox02_device is None:
    670             wizard.run_task_without_blocking_gui(
    671                 task=lambda client=client: client.pairing_dialog())
    672         client.fail_if_not_initialized()
    673         return client
    674 
    675     def get_xpub(
    676         self, device_id: str, derivation: str, xtype: str, wizard: BaseWizard
    677     ):
    678         if xtype not in self.SUPPORTED_XTYPES:
    679             raise ScriptTypeNotSupported(
    680                 _("This type of script is not supported with {}: {}").format(self.device, xtype)
    681             )
    682         client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard)
    683         assert isinstance(client, BitBox02Client)
    684         assert client.bitbox02_device is not None
    685         return client.get_xpub(derivation, xtype)
    686 
    687     @runs_in_hwd_thread
    688     def show_address(
    689         self,
    690         wallet: Deterministic_Wallet,
    691         address: str,
    692         keystore: BitBox02_KeyStore = None,
    693     ):
    694         if keystore is None:
    695             keystore = wallet.get_keystore()
    696         if not self.show_address_helper(wallet, address, keystore):
    697             return
    698 
    699         txin_type = wallet.get_txin_type(address)
    700         sequence = wallet.get_address_index(address)
    701         keystore.show_address(sequence, txin_type, wallet)
    702 
    703     @runs_in_hwd_thread
    704     def show_xpub(self, keystore: BitBox02_KeyStore):
    705         client = keystore.get_client()
    706         assert isinstance(client, BitBox02Client)
    707         derivation = keystore.get_derivation_prefix()
    708         xtype = keystore.get_bip32_node_for_xpub().xtype
    709         client.get_xpub(derivation, xtype, display=True)
    710 
    711     def create_device_from_hid_enumeration(self, d: dict, *, product_key) -> 'Device':
    712         device = super().create_device_from_hid_enumeration(d, product_key=product_key)
    713         # The BitBox02's product_id is not unique per device, thus use the path instead to
    714         # distinguish devices.
    715         id_ = str(d['path'])
    716         return device._replace(id_=id_)