URI: 
       tbitbox02.py - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
       tbitbox02.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_)