class Device(object): """ Implements a command set wrapper around the USB dongle """ USB_VID = 0x0483 def __init__(self, pid): # TODO: do it right self._context = USBContext() self._handle = self._context.openByVendorIDAndProductID( Device.USB_VID, pid, skip_on_error=True) if self._handle is None: raise MemplugException( "USB device not found; check lsusb and/or the top board type") self._iface = self._handle.claimInterface(0) self._handle.setConfiguration(1) # 500ms timeouts by default self.cmd_timeout = 500 self.data_timeout = 500 def close(self): if self._handle is not None: self._handle.releaseInterface(0) self._handle.close() if self._context is not None: self._context.close() def _command(self, id, args=b"", dataOut=None, countIn=None, cmd_timeout=None, data_timeout=None): if cmd_timeout is None: cmd_timeout = self.cmd_timeout if data_timeout is None: data_timeout = self.data_timeout try: self._handle.controlWrite(0x21, id, 0x0000, 0x0000, args, cmd_timeout) except USBErrorPipe: raise MemplugException("command failed") if dataOut is not None: self._handle.bulkWrite(1, dataOut, data_timeout) if countIn is not None: return self._handle.bulkRead(1, countIn, data_timeout) return None def get_status(self): return self._handle.controlRead(0x21, 0, 0x0000, 0x0000, 64, self.cmd_timeout) def recover(self): self._handle.controlWrite(0x21, 1, 0x0000, 0x0000, b"", self.cmd_timeout)
def discover(self): ret = [] ctx = USBContext() try: for device in ctx.getDeviceList(): vid = device.getVendorID() if vid == 1240 or vid == 1155 or vid == 10842: ret.append((str(device), )) finally: ctx.exit() return ret
def __init__(self, name): super().__init__(name) self.libraries_available = KEEPKEYLIB if KEEPKEYLIB: try: self.usb_context = USBContext() self.usb_context.open() except Exception: self.libraries_available = False self.main_thread = threading.current_thread()
def __init__(self, pid): # TODO: do it right self._context = USBContext() self._handle = self._context.openByVendorIDAndProductID( Device.USB_VID, pid, skip_on_error=True) if self._handle is None: raise MemplugException( "USB device not found; check lsusb and/or the top board type") self._iface = self._handle.claimInterface(0) self._handle.setConfiguration(1) # 500ms timeouts by default self.cmd_timeout = 500 self.data_timeout = 500
def __init__(self, devstr): self._dev = None self._data_out = None self._data_in = None self._ctx = USBContext() for device in self._ctx.getDeviceList(): if devstr == str(device): self._dev = device break if self._dev is None: raise RuntimeError('Failed to find %s', devstr) self._usb_handle = self._dev.open() for iface in self._dev.iterSettings(): iface_num = iface.getNumber() if iface_num in (0, 1): if self._usb_handle.kernelDriverActive(iface_num): self._usb_handle.detachKernelDriver(iface_num) self._usb_handle.claimInterface(iface_num) if iface_num == 1: self._data_out = iface[1].getAddress() self._data_in = iface[0].getAddress() if self._data_in is None or self._data_out is None: raise RuntimeError('Failed to find bulk transfer for %s', devstr) # config = self._dev.get_active_configuration() # self.control_iface = config[(0, 0)] # self.data_iface = config[(1, 0)] # # self.control_out = \ # usb.util.find_descriptor( # self.control_iface, custom_match=lambda e: endpoint_direction(e.bEndpointAddress) == ENDPOINT_OUT) # self.control_in = \ # usb.util.find_descriptor( # self.control_iface, custom_match=lambda e: endpoint_direction(e.bEndpointAddress) == ENDPOINT_IN) # # self.data_out = \ # usb.util.find_descriptor( # self.data_iface, custom_match=lambda e: endpoint_direction(e.bEndpointAddress) == ENDPOINT_OUT) # self.data_in = \ # usb.util.find_descriptor( # self.data_iface, custom_match=lambda e: endpoint_direction(e.bEndpointAddress) == ENDPOINT_IN) self._init(115200, 8, 0, 0)
def __init__(self, name): super().__init__(name) try: from . import client import keepkeylib import keepkeylib.ckd_public from usb1 import USBContext self.client_class = client.KeepKeyClient self.ckd_public = keepkeylib.ckd_public self.types = keepkeylib.client.types self.DEVICE_IDS = (KEEPKEY_PRODUCT_KEY,) self.usb_context = USBContext() self.usb_context.open() self.libraries_available = True except ImportError: self.libraries_available = False self.logger = logs.get_logger("plugin.keepkey") self.main_thread = threading.current_thread()
def __init__(self, parent, config, name): HW_PluginBase.__init__(self, parent, config, name) try: from electroncash_plugins.keepkey import client import keepkeylib import keepkeylib.ckd_public import keepkeylib.transport_hid import keepkeylib.transport_webusb from usb1 import USBContext self.client_class = client.KeepKeyClient self.ckd_public = keepkeylib.ckd_public self.types = keepkeylib.client.types self.DEVICE_IDS = (keepkeylib.transport_hid.DEVICE_IDS + keepkeylib.transport_webusb.DEVICE_IDS) self.device_manager().register_devices(self.DEVICE_IDS) self.device_manager().register_enumerate_func(self.enumerate) self.usb_context = USBContext() self.usb_context.open() self.libraries_available = True except ImportError: self.libraries_available = False
def main(ctx): opt = ctx.opt with USBContext() as context: found = list(find_device(context, opt.vid, opt.pid)) if len(found) - 1 < opt.nth: raise Exception("No USB device found") # ux == USB Device/Configuration/Interface/Setting/Endpoint ud = found[opt.nth] uc = ud[0] ui = uc[0] us = ui[0] ue = us[0] with CyUSB(ud, CY_TYPE.MFG, index=opt.scb) as dev: #embed() ctx.dev = dev cmd = opt.args[0] if cmd == "save": do_save(ctx, *opt.args[1:]) if cmd == "load": do_load(ctx, *opt.args[1:]) if cmd == "mode": do_mode(ctx, *opt.args[1:])
class UsbCdcAcmConnection(Connection): def __init__(self, devstr): self._dev = None self._data_out = None self._data_in = None self._ctx = USBContext() for device in self._ctx.getDeviceList(): if devstr == str(device): self._dev = device break if self._dev is None: raise RuntimeError('Failed to find %s', devstr) self._usb_handle = self._dev.open() for iface in self._dev.iterSettings(): iface_num = iface.getNumber() if iface_num in (0, 1): if self._usb_handle.kernelDriverActive(iface_num): self._usb_handle.detachKernelDriver(iface_num) self._usb_handle.claimInterface(iface_num) if iface_num == 1: self._data_out = iface[1].getAddress() self._data_in = iface[0].getAddress() if self._data_in is None or self._data_out is None: raise RuntimeError('Failed to find bulk transfer for %s', devstr) # config = self._dev.get_active_configuration() # self.control_iface = config[(0, 0)] # self.data_iface = config[(1, 0)] # # self.control_out = \ # usb.util.find_descriptor( # self.control_iface, custom_match=lambda e: endpoint_direction(e.bEndpointAddress) == ENDPOINT_OUT) # self.control_in = \ # usb.util.find_descriptor( # self.control_iface, custom_match=lambda e: endpoint_direction(e.bEndpointAddress) == ENDPOINT_IN) # # self.data_out = \ # usb.util.find_descriptor( # self.data_iface, custom_match=lambda e: endpoint_direction(e.bEndpointAddress) == ENDPOINT_OUT) # self.data_in = \ # usb.util.find_descriptor( # self.data_iface, custom_match=lambda e: endpoint_direction(e.bEndpointAddress) == ENDPOINT_IN) self._init(115200, 8, 0, 0) def _init(self, baud_rate, data_bits, stop_bits, parity): msg = struct.pack('<IBBB', baud_rate, stop_bits, data_bits, parity) self._usb_handle.controlWrite(0x20 | 0x01, 0x20, 0, 0, msg) def _send(self, message): data = message.write() self._usb_handle.bulkWrite(self._data_out, data, 1000) def _recv(self, message): buf = '' # Read until we have enough bytes for the message while True: try: buf += self._usb_handle.bulkRead(self._data_in, 8192, 1000) except USBError as err: if hasattr(err, 'value') and err.value == -7: continue if hasattr(err, 'errno') or err.errno == 110: continue raise err try: message.read(buf) except BufferUnderflowError: continue return message def close(self): if self._usb_handle is not None: self._usb_handle.close() if self._ctx is not None: self._ctx.exit()
class KeepKeyPlugin(HW_PluginBase): MAX_LABEL_LEN = 32 firmware_URL = 'https://www.keepkey.com' libraries_URL = 'https://github.com/keepkey/python-keepkey' minimum_firmware = (4, 0, 0) keystore_class = KeepKey_KeyStore def __init__(self, name): super().__init__(name) try: from . import client import keepkeylib import keepkeylib.ckd_public from usb1 import USBContext self.client_class = client.KeepKeyClient self.ckd_public = keepkeylib.ckd_public self.types = keepkeylib.client.types self.DEVICE_IDS = (KEEPKEY_PRODUCT_KEY,) self.usb_context = USBContext() self.usb_context.open() self.libraries_available = True except ImportError: self.libraries_available = False self.logger = logs.get_logger("plugin.keepkey") self.main_thread = threading.current_thread() def get_coin_name(self, client): # No testnet support yet if client.features.major_version < 6: return "BitcoinCash" return "BitcoinSV" def _libusb_enumerate(self): from keepkeylib.transport_webusb import DEVICE_IDS for dev in self.usb_context.getDeviceIterator(skip_on_error=True): usb_id = (dev.getVendorID(), dev.getProductID()) if usb_id in DEVICE_IDS: yield dev def _enumerate_hid(self): if self.libraries_available: from keepkeylib.transport_hid import HidTransport return HidTransport.enumerate() return [] def _enumerate_web_usb(self): if self.libraries_available: from keepkeylib.transport_webusb import WebUsbTransport return self._libusb_enumerate() return [] def _get_transport(self, device): self.logger.debug("Trying to connect over USB...") if device.path.startswith('web_usb'): for d in self._enumerate_web_usb(): if self._web_usb_path(d) == device.path: from keepkeylib.transport_webusb import WebUsbTransport return WebUsbTransport(d) else: for d in self._enumerate_hid(): if str(d[0]) == device.path: from keepkeylib.transport_hid import HidTransport return HidTransport(d) raise RuntimeError(f'device {device} not found') def _device_for_path(self, path): return Device( path=path, interface_number=-1, id_=path, product_key=KEEPKEY_PRODUCT_KEY, usage_page=0, transport_ui_string=path, ) def _web_usb_path(self, device): return f'web_usb:{device.getBusNumber()}:{device.getPortNumberList()}' def enumerate_devices(self): devices = [] for device in self._enumerate_web_usb(): devices.append(self._device_for_path(self._web_usb_path(device))) for device in self._enumerate_hid(): # Cast needed for older firmware devices.append(self._device_for_path(str(device[0]))) return devices def create_client(self, device, handler): # disable bridge because it seems to never returns if keepkey is plugged try: transport = self._get_transport(device) except Exception as e: self.logger.error("cannot connect to device") raise self.logger.debug("connected to device at %s", device.path) client = self.client_class(transport, handler, self) # Try a ping for device sanity try: client.ping('t') except Exception as e: self.logger.error("ping failed %s", e) return None if not client.atleast_version(*self.minimum_firmware): msg = (_('Outdated {} firmware for device labelled {}. Please ' 'download the updated firmware from {}') .format(self.device, client.label(), self.firmware_URL)) self.logger.error(msg) handler.show_error(msg) return None return client def get_client(self, keystore, force_pair=True): client = app_state.device_manager.client_for_keystore(self, keystore, force_pair) # returns the client for a given keystore. can use xpub if client: client.used() return client def initialize_device(self, device_id, wizard, handler): # Initialization method msg = _("Choose how you want to initialize your {}.\n\n" "The first two methods are secure as no secret information " "is entered into your computer.\n\n" "For the last two methods you input secrets on your keyboard " "and upload them to your {}, and so you should " "only do those on a computer you know to be trustworthy " "and free of malware." ).format(self.device, self.device) choices = [ # Must be short as QT doesn't word-wrap radio button text (TIM_NEW, _("Let the device generate a completely new seed randomly")), (TIM_RECOVER, _("Recover from a seed you have previously written down")), (TIM_MNEMONIC, _("Upload a BIP39 mnemonic to generate the seed")), (TIM_PRIVKEY, _("Upload a master private key")) ] def f(method): settings = self.request_trezor_init_settings(wizard, method, self.device) t = threading.Thread(target = self._initialize_device_safe, args=(settings, method, device_id, wizard, handler)) t.setDaemon(True) t.start() wizard.loop.exec_() wizard.choice_dialog(title=_('Initialize Device'), message=msg, choices=choices, run_next=f) def _initialize_device_safe(self, settings, method, device_id, wizard, handler): exit_code = 0 try: self._initialize_device(settings, method, device_id, wizard, handler) except UserCancelled: exit_code = 1 except Exception as e: handler.show_error(str(e)) exit_code = 1 finally: wizard.loop.exit(exit_code) def _initialize_device(self, settings, method, device_id, wizard, handler): item, label, pin_protection, passphrase_protection = settings language = 'english' client = app_state.device_manager.client_by_id(device_id) if method == TIM_NEW: strength = 64 * (item + 2) # 128, 192 or 256 client.reset_device(True, strength, passphrase_protection, pin_protection, label, language) elif method == TIM_RECOVER: word_count = 6 * (item + 2) # 12, 18 or 24 client.step = 0 client.recovery_device(False, word_count, passphrase_protection, pin_protection, label, language) elif method == TIM_MNEMONIC: pin = pin_protection # It's the pin, not a boolean client.load_device_by_mnemonic(str(item), pin, passphrase_protection, label, language) else: pin = pin_protection # It's the pin, not a boolean client.load_device_by_xprv(item, pin, passphrase_protection, label, language) def setup_device(self, device_info, wizard): '''Called when creating a new wallet. Select the device to use. If the device is uninitialized, go through the intialization process.''' device_id = device_info.device.id_ client = app_state.device_manager.client_by_id(device_id) client.handler = self.create_handler(wizard) if not device_info.initialized: self.initialize_device(device_id, wizard, client.handler) client.get_master_public_key('m') def get_master_public_key(self, device_id, derivation, wizard): client = app_state.device_manager.client_by_id(device_id) client.handler = self.create_handler(wizard) return client.get_master_public_key(derivation) def sign_transaction(self, keystore, tx, xpub_path): self.xpub_path = xpub_path client = self.get_client(keystore) inputs = self.tx_inputs(tx) outputs = self.tx_outputs(keystore.get_derivation(), tx) signatures = client.sign_tx(self.get_coin_name(client), inputs, outputs, lock_time=tx.locktime)[0] tx.update_signatures(signatures) def show_address(self, wallet, address): keystore = wallet.get_keystore() client = self.get_client(keystore) change, index = wallet.get_address_index(address) derivation = keystore.derivation address_path = "%s/%d/%d"%(derivation, change, index) address_n = bip32_decompose_chain_string(address_path) script_type = self.types.SPENDADDRESS client.get_address(Net.KEEPKEY_DISPLAY_COIN_NAME, address_n, True, script_type=script_type) def tx_inputs(self, tx): inputs = [] for txin in tx.inputs: txinputtype = self.types.TxInputType() txinputtype.prev_hash = bytes(reversed(txin.prev_hash)) txinputtype.prev_index = txin.prev_idx txinputtype.sequence = txin.sequence txinputtype.amount = txin.value x_pubkeys = txin.x_pubkeys if len(x_pubkeys) == 1: x_pubkey = x_pubkeys[0] xpub, path = x_pubkey.bip32_extended_key_and_path() xpub_n = bip32_decompose_chain_string(self.xpub_path[xpub]) txinputtype.address_n.extend(xpub_n + path) txinputtype.script_type = self.types.SPENDADDRESS else: def f(x_pubkey): if x_pubkey.is_bip32_key(): xpub, path = x_pubkey.bip32_extended_key_and_path() else: xpub = BIP32PublicKey(bfh(x_pubkey), NULL_DERIVATION, Net.COIN) xpub = xpub.to_extended_key_string() path = [] node = self.ckd_public.deserialize(xpub) return self.types.HDNodePathType(node=node, address_n=path) pubkeys = [f(x) for x in x_pubkeys] multisig = self.types.MultisigRedeemScriptType( pubkeys=pubkeys, signatures=txin.stripped_signatures_with_blanks(), m=txin.threshold, ) script_type = self.types.SPENDMULTISIG txinputtype = self.types.TxInputType( script_type=script_type, multisig=multisig ) # find which key is mine for x_pubkey in x_pubkeys: if x_pubkey.is_bip32_key(): xpub, path = x_pubkey.bip32_extended_key_and_path() if xpub in self.xpub_path: xpub_n = bip32_decompose_chain_string(self.xpub_path[xpub]) txinputtype.address_n.extend(xpub_n + path) break inputs.append(txinputtype) return inputs def tx_outputs(self, derivation, tx): outputs = [] has_change = False for tx_output, info in zip(tx.outputs, tx.output_info): if info is not None and not has_change: has_change = True # no more than one change address index, xpubs, m = info if len(xpubs) == 1: script_type = self.types.PAYTOADDRESS address_n = bip32_decompose_chain_string(derivation + "/%d/%d"%index) txoutputtype = self.types.TxOutputType( amount = tx_output.value, script_type = script_type, address_n = address_n, ) else: script_type = self.types.PAYTOMULTISIG address_n = bip32_decompose_chain_string("/%d/%d"%index) nodes = [self.ckd_public.deserialize(xpub) for xpub in xpubs] pubkeys = [self.types.HDNodePathType(node=node, address_n=address_n) for node in nodes] multisig = self.types.MultisigRedeemScriptType( pubkeys = pubkeys, signatures = [b''] * len(pubkeys), m = m) txoutputtype = self.types.TxOutputType( multisig = multisig, amount = tx_output.value, address_n = bip32_decompose_chain_string(derivation + "/%d/%d"%index), script_type = script_type) else: txoutputtype = self.types.TxOutputType() txoutputtype.amount = tx_output.value address = classify_tx_output(tx_output) if isinstance(address, Address): txoutputtype.script_type = self.types.PAYTOADDRESS txoutputtype.address = address.to_string(coin=Net.COIN) elif isinstance(address, OP_RETURN_Output): txoutputtype.script_type = self.types.PAYTOOPRETURN txoutputtype.op_return_data = bytes(tx_output.script_pubkey)[2:] outputs.append(txoutputtype) return outputs
class KeepKeyPlugin(HW_PluginBase): # Derived classes provide: # # class-static variables: client_class, firmware_URL, handler_class, # libraries_available, libraries_URL, minimum_firmware, # wallet_class, ckd_public, types, HidTransport firmware_URL = 'https://www.keepkey.com' libraries_URL = 'https://github.com/keepkey/python-keepkey' minimum_firmware = (1, 0, 0) keystore_class = KeepKey_KeyStore usb_context = None SUPPORTED_XTYPES = ('standard', 'p2wsh-p2sh', 'p2wsh') MAX_LABEL_LEN = 32 def __init__(self, parent, config, name): HW_PluginBase.__init__(self, parent, config, name) try: from electroncash_plugins.keepkey import client import keepkeylib import keepkeylib.ckd_public import keepkeylib.transport_hid import keepkeylib.transport_webusb from usb1 import USBContext self.client_class = client.KeepKeyClient self.ckd_public = keepkeylib.ckd_public self.types = keepkeylib.client.types self.DEVICE_IDS = (keepkeylib.transport_hid.DEVICE_IDS + keepkeylib.transport_webusb.DEVICE_IDS) self.device_manager().register_devices(self.DEVICE_IDS) self.device_manager().register_enumerate_func(self.enumerate) self.usb_context = USBContext() self.usb_context.open() self.libraries_available = True except ImportError: self.libraries_available = False def libusb_enumerate(self): from keepkeylib.transport_webusb import DEVICE_IDS for dev in self.usb_context.getDeviceIterator(skip_on_error=True): usb_id = (dev.getVendorID(), dev.getProductID()) if usb_id in DEVICE_IDS: yield dev def _USBDevice_getPath(self, dev): return ":".join(str(x) for x in ["%03i" % (dev.getBusNumber(),)] + dev.getPortNumberList()) def enumerate(self): for dev in self.libusb_enumerate(): path = self._USBDevice_getPath(dev) usb_id = (dev.getVendorID(), dev.getProductID()) yield Device(path=path, interface_number=-1, id_=path, product_key=usb_id, usage_page=0) def hid_transport(self, pair): from keepkeylib.transport_hid import HidTransport return HidTransport(pair) def webusb_transport(self, device): from keepkeylib.transport_webusb import WebUsbTransport for dev in self.libusb_enumerate(): path = self._USBDevice_getPath(dev) if path == device.path: return WebUsbTransport(dev) self.print_error(f"cannot connect at {device.path}: not found") return None def _try_hid(self, device): self.print_error("Trying to connect over USB...") if device.interface_number == 1: pair = [None, device.path] else: pair = [device.path, None] try: return self.hid_transport(pair) except BaseException as e: # see fdb810ba622dc7dbe1259cbafb5b28e19d2ab114 # raise self.print_error(f"cannot connect at {device.path} {e}") return None def _try_webusb(self, device): self.print_error("Trying to connect over WebUSB...") try: return self.webusb_transport(device) except BaseException as e: self.print_error(f"cannot connect at {device.path} {e}") return None def create_client(self, device, handler): if device.product_key[1] == 2: transport = self._try_webusb(device) else: transport = self._try_hid(device) if not transport: self.print_error("cannot connect to device") return self.print_error(f"connected to device at {device.path}") client = self.client_class(transport, handler, self) # Try a ping for device sanity try: client.ping('t') except BaseException as e: self.print_error(f"ping failed {e}") return None if not client.atleast_version(*self.minimum_firmware): msg = (_('Outdated {} firmware for device labelled {}. Please ' 'download the updated firmware from {}') .format(self.device, client.label(), self.firmware_URL)) self.print_error(msg) if handler: handler.show_error(msg) else: raise RuntimeError(msg) return None return client def get_client(self, keystore, force_pair=True): devmgr = self.device_manager() handler = keystore.handler with devmgr.hid_lock: client = devmgr.client_for_keystore(self, handler, keystore, force_pair) # returns the client for a given keystore. can use xpub if client: client.used() return client def get_coin_name(self): # Doesn't support testnet addresses return "BitcoinCash" def initialize_device(self, device_id, wizard, handler): # Initialization method msg = _("Choose how you want to initialize your {}.\n\n" "The first two methods are secure as no secret information " "is entered into your computer.\n\n" "For the last two methods you input secrets on your keyboard " "and upload them to your {}, and so you should " "only do those on a computer you know to be trustworthy " "and free of malware." ).format(self.device, self.device) choices = [ # Must be short as QT doesn't word-wrap radio button text (TIM_NEW, _("Let the device generate a completely new seed randomly")), (TIM_RECOVER, _("Recover from a seed you have previously written down")), (TIM_MNEMONIC, _("Upload a BIP39 mnemonic to generate the seed")), (TIM_PRIVKEY, _("Upload a master private key")) ] def f(method): import threading settings = self.request_trezor_init_settings(wizard, method, self.device) t = threading.Thread(target=self._initialize_device_safe, args=(settings, method, device_id, wizard, handler)) t.setDaemon(True) t.start() exit_code = wizard.loop.exec_() if exit_code != 0: # this method (initialize_device) was called with the expectation # of leaving the device in an initialized state when finishing. # signal that this is not the case: raise UserCancelled() wizard.choice_dialog(title=_('Initialize Device'), message=msg, choices=choices, run_next=f) def _initialize_device_safe(self, settings, method, device_id, wizard, handler): exit_code = 0 try: self._initialize_device(settings, method, device_id, wizard, handler) except UserCancelled: exit_code = 1 except BaseException as e: self.print_error(str(e)) handler.show_error(str(e)) exit_code = 1 finally: wizard.loop.exit(exit_code) def _initialize_device(self, settings, method, device_id, wizard, handler): item, label, pin_protection, passphrase_protection = settings language = 'english' devmgr = self.device_manager() client = devmgr.client_by_id(device_id) if not client: raise Exception(_("The device was disconnected.")) if method == TIM_NEW: strength = 64 * (item + 2) # 128, 192 or 256 client.reset_device(True, strength, passphrase_protection, pin_protection, label, language) elif method == TIM_RECOVER: word_count = 6 * (item + 2) # 12, 18 or 24 client.step = 0 client.recovery_device(word_count, passphrase_protection, pin_protection, label, language) elif method == TIM_MNEMONIC: item = str(item).strip() if not len(item.split()) in [12, 18, 24]: raise Exception(_("The mnemonic needs to be 12, 18 or 24 words.")) pin = pin_protection # It's the pin, not a boolean client.load_device_by_mnemonic(item, pin, passphrase_protection, label, language) else: pin = pin_protection # It's the pin, not a boolean client.load_device_by_xprv(item, pin, passphrase_protection, label, language) def _make_node_path(self, xpub, address_n): _, depth, fingerprint, child_num, chain_code, key = deserialize_xpub(xpub) node = self.types.HDNodeType( depth=depth, fingerprint=int.from_bytes(fingerprint, 'big'), child_num=int.from_bytes(child_num, 'big'), chain_code=chain_code, public_key=key, ) return self.types.HDNodePathType(node=node, address_n=address_n) def setup_device(self, device_info, wizard, purpose): devmgr = self.device_manager() device_id = device_info.device.id_ client = devmgr.client_by_id(device_id) if client is None: raise RuntimeError(_('Failed to create a client for this device.') + '\n' + _('Make sure it is in the correct state.')) # fixme: we should use: client.handler = wizard client.handler = self.create_handler(wizard) if not device_info.initialized: self.initialize_device(device_id, wizard, client.handler) client.get_xpub('m', 'standard') client.used() def get_xpub(self, device_id, derivation, xtype, wizard): if xtype not in self.SUPPORTED_XTYPES: raise RuntimeError(_('This type of script is not supported with {}.').format(self.device)) devmgr = self.device_manager() client = devmgr.client_by_id(device_id) client.handler = wizard xpub = client.get_xpub(derivation, xtype) client.used() return xpub def get_keepkey_input_script_type(self, electrum_txin_type: str): if electrum_txin_type == "p2pkh": return self.types.SPENDADDRESS if electrum_txin_type == "p2sh": return self.types.SPENDMULTISIG raise ValueError(f"unexpected txin type: {electrum_txin_type}") def get_keepkey_output_script_type(self, electrum_txin_type: str): if electrum_txin_type == "p2pkh": return self.types.PAYTOADDRESS if electrum_txin_type == "p2sh": return self.types.PAYTOMULTISIG raise ValueError(f"unexpected txin type: {electrum_txin_type}") def sign_transaction(self, keystore, tx, prev_tx, xpub_path): self.prev_tx = prev_tx self.xpub_path = xpub_path client = self.get_client(keystore) inputs = self.tx_inputs(tx, True) outputs = self.tx_outputs(keystore.get_derivation(), tx) signatures, signed_tx = client.sign_tx(self.get_coin_name(), inputs, outputs, lock_time=tx.locktime, version=tx.version) signatures = [bh2u(x) for x in signatures] tx.update_signatures(signatures) def show_address(self, wallet, address, keystore=None): if keystore is None: keystore = wallet.get_keystore() if not self.show_address_helper(wallet, address, keystore): return client = self.get_client(keystore) if not client.atleast_version(1, 3): keystore.handler.show_error(_("Your device firmware is too old")) return change, index = wallet.get_address_index(address) derivation = keystore.derivation address_path = "%s/%d/%d"%(derivation, change, index) address_n = client.expand_path(address_path) xpubs = wallet.get_master_public_keys() if len(xpubs) == 1: script_type = self.get_keepkey_input_script_type(wallet.txin_type) client.get_address(self.get_coin_name(), address_n, True, script_type=script_type) else: def f(xpub): return self._make_node_path(xpub, [change, index]) pubkeys = wallet.get_public_keys(address) # sort xpubs using the order of pubkeys sorted_pubkeys, sorted_xpubs = zip(*sorted(zip(pubkeys, xpubs))) pubkeys = list(map(f, sorted_xpubs)) multisig = self.types.MultisigRedeemScriptType( pubkeys=pubkeys, signatures=[b''] * wallet.n, m=wallet.m, ) script_type = self.get_keepkey_input_script_type(wallet.txin_type) client.get_address(self.get_coin_name(), address_n, True, multisig=multisig, script_type=script_type) def tx_inputs(self, tx, for_sig=False): inputs = [] for txin in tx.inputs(): txinputtype = self.types.TxInputType() if txin['type'] == 'coinbase': prev_hash = b"\x00"*32 prev_index = 0xffffffff # signed int -1 else: if for_sig: x_pubkeys = txin['x_pubkeys'] if len(x_pubkeys) == 1: x_pubkey = x_pubkeys[0] xpub, s = parse_xpubkey(x_pubkey) xpub_n = self.client_class.expand_path(self.xpub_path[xpub]) txinputtype.address_n.extend(xpub_n + s) txinputtype.script_type = self.get_keepkey_input_script_type(txin['type']) else: def f(x_pubkey): xpub, s = parse_xpubkey(x_pubkey) return self._make_node_path(xpub, s) pubkeys = list(map(f, x_pubkeys)) multisig = self.types.MultisigRedeemScriptType( pubkeys=pubkeys, signatures=map(lambda x: bfh(x)[:-1] if x else b'', txin.get('signatures')), m=txin.get('num_sig'), ) script_type = self.get_keepkey_input_script_type(txin['type']) txinputtype = self.types.TxInputType( script_type=script_type, multisig=multisig ) # find which key is mine for x_pubkey in x_pubkeys: if is_xpubkey(x_pubkey): xpub, s = parse_xpubkey(x_pubkey) if xpub in self.xpub_path: xpub_n = self.client_class.expand_path(self.xpub_path[xpub]) txinputtype.address_n.extend(xpub_n + s) break prev_hash = unhexlify(txin['prevout_hash']) prev_index = txin['prevout_n'] if 'value' in txin: txinputtype.amount = txin['value'] txinputtype.prev_hash = prev_hash txinputtype.prev_index = prev_index if txin.get('scriptSig') is not None: script_sig = bfh(txin['scriptSig']) txinputtype.script_sig = script_sig txinputtype.sequence = txin.get('sequence', 0xffffffff - 1) inputs.append(txinputtype) return inputs def tx_outputs(self, derivation, tx): def create_output_by_derivation(): keepkey_script_type = self.get_keepkey_output_script_type(script_type) if len(xpubs) == 1: address_n = self.client_class.expand_path(derivation + "/%d/%d" % index) txoutputtype = self.types.TxOutputType( amount=amount, script_type=keepkey_script_type, address_n=address_n, ) else: address_n = self.client_class.expand_path("/%d/%d" % index) pubkeys = [self._make_node_path(xpub, address_n) for xpub in xpubs] multisig = self.types.MultisigRedeemScriptType( pubkeys=pubkeys, signatures=[b''] * len(pubkeys), m=m) txoutputtype = self.types.TxOutputType( multisig=multisig, amount=amount, address_n=self.client_class.expand_path(derivation + "/%d/%d" % index), script_type=keepkey_script_type) return txoutputtype def create_output_by_address(): txoutputtype = self.types.TxOutputType() txoutputtype.amount = amount if _type == TYPE_SCRIPT: txoutputtype.script_type = self.types.PAYTOOPRETURN txoutputtype.op_return_data = validate_op_return_output_and_get_data(o, max_pushes=1) elif _type == TYPE_ADDRESS: txoutputtype.script_type = self.types.PAYTOADDRESS txoutputtype.address = address.to_full_string(Address.FMT_CASHADDR_BCH, net=networks.MainNet) return txoutputtype outputs = [] has_change = False any_output_on_change_branch = is_any_tx_output_on_change_branch(tx) for o in tx.outputs(): _type, address, amount = o use_create_by_derivation = False info = tx.output_info.get(address) if info is not None and not has_change: index, xpubs, m, script_type = info on_change_branch = index[0] == 1 # prioritise hiding outputs on the 'change' branch from user # because no more than one change address allowed if on_change_branch == any_output_on_change_branch: use_create_by_derivation = True has_change = True if use_create_by_derivation: txoutputtype = create_output_by_derivation() else: txoutputtype = create_output_by_address() outputs.append(txoutputtype) return outputs def electrum_tx_to_txtype(self, tx): t = self.types.TransactionType() if tx is None: # probably for segwit input and we don't need this prev txn return t d = deserialize(tx.raw) t.version = d['version'] t.lock_time = d['lockTime'] inputs = self.tx_inputs(tx) t.inputs.extend(inputs) for vout in d['outputs']: o = t.bin_outputs.add() o.amount = vout['value'] o.script_pubkey = bfh(vout['scriptPubKey']) return t # This function is called from the TREZOR libraries (via tx_api) def get_tx(self, tx_hash): tx = self.prev_tx[tx_hash] return self.electrum_tx_to_txtype(tx)