def display_singlesig_address( self, keypath: str, addr_type: AddressType, ) -> str: self._check_unlocked() # Script type if addr_type == AddressType.SH_WIT: script_type = messages.InputScriptType.SPENDP2SHWITNESS elif addr_type == AddressType.WIT: script_type = messages.InputScriptType.SPENDWITNESS elif addr_type == AddressType.LEGACY: script_type = messages.InputScriptType.SPENDADDRESS else: raise BadArgumentError("Unknown address type") expanded_path = parse_path(keypath) try: address = btc.get_address( self.client, self.coin_name, expanded_path, show_display=True, script_type=script_type, multisig=None, ) assert isinstance(address, str) return address except Exception: pass raise BadArgumentError("No path supplied matched device keys")
def func(*args, **kwargs): try: return f(*args, **kwargs) except ValueError as e: raise BadArgumentError(str(e)) except JadeError as e: if e.code == -32000: # CBOR_RPC_USER_CANCELLED raise ActionCanceledError("{} canceled by user".format( f.__name__)) elif e.code == -32602: # CBOR_RPC_BAD_PARAMETERS raise BadArgumentError(e.message) elif e.code == -32603: # CBOR_RPC_INTERNAL_ERROR raise DeviceFailureError(e.message) elif e.code == -32002: # CBOR_RPC_HW_LOCKED raise DeviceConnectionError("Device is locked") elif e.code == -32003: # CBOR_RPC_NETWORK_MISMATCH raise DeviceConnectionError("Network/chain selection error") elif e.code in [ -32600, -32601, -32001, ]: # CBOR_RPC_INVALID_REQUEST, CBOR_RPC_UNKNOWN_METHOD, CBOR_RPC_PROTOCOL_ERROR raise DeviceConnectionError("Messaging/communiciation error") else: raise e
def get_simple_type( output: CTxOut, redeem_script: bytes ) -> bitbox02.btc.BTCScriptConfig.SimpleType: if is_p2pkh(output.scriptPubKey): raise BadArgumentError( "The BitBox02 does not support legacy p2pkh scripts") if is_p2wpkh(output.scriptPubKey): return bitbox02.btc.BTCScriptConfig.P2WPKH if output.is_p2sh() and is_p2wpkh(redeem_script): return bitbox02.btc.BTCScriptConfig.P2WPKH_P2SH raise BadArgumentError( "Input script type not recognized of input {}.".format( input_index))
def display_address(self, keypath, p2sh_p2wpkh, bech32, redeem_script=None): self._check_unlocked() # redeem_script means p2sh/multisig if redeem_script: # Get multisig object required by Trezor's get_address multisig = parse_multisig(bytes.fromhex(redeem_script)) if not multisig[0]: raise BadArgumentError( "The redeem script provided is not a multisig. Only multisig scripts can be displayed." ) multisig = multisig[1] else: multisig = None # Script type if p2sh_p2wpkh: script_type = proto.InputScriptType.SPENDP2SHWITNESS elif bech32: script_type = proto.InputScriptType.SPENDWITNESS elif redeem_script: script_type = proto.InputScriptType.SPENDMULTISIG else: script_type = proto.InputScriptType.SPENDADDRESS # convert device fingerprint to 'm' if exists in path keypath = keypath.replace(self.get_master_fingerprint_hex(), "m") for path in keypath.split(","): if len(path.split("/")[0]) == 8: path = path.split("/", 1)[1] expanded_path = tools.parse_path(path) try: address = btc.get_address( self.client, self.coin_name, expanded_path, show_display=True, script_type=script_type, multisig=multisig, ) return {"address": address} except: pass raise BadArgumentError("No path supplied matched device keys")
def display_multisig_address( self, addr_type: AddressType, multisig: MultisigDescriptor, ) -> str: """ Display and return the multisig address of specified type given the descriptor. :param addr_type: The address type :param multisig: A :class:`~hwilib.descriptor.MultisigDescriptor` that describes the multisig to display. :return: The retrieved address also being shown by the device """ # prepare a request of the form like # `showaddr sh-wsh m/1h/2h/3 descriptor` if addr_type == AddressType.LEGACY: script_type = "sh" elif addr_type == AddressType.SH_WIT: script_type = "sh-wsh" elif addr_type == AddressType.WIT: script_type = "wsh" else: raise BadArgumentError("Invalid address type") script, *_ = multisig.expand(0) bip32_path = (multisig.pubkeys[0].origin.to_string() + multisig.pubkeys[0].deriv_path) request = f"showaddr {script_type} {bip32_path} {script.hex()}" address = self.query(request) return address
def query(self, data, timeout=None): if self.simulator: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(self.sock_settings) s.send((data+'\r\n').encode('utf-8')) s.setblocking(False) res = "" t0 = time.time() while not ("\r\n" in res): try: raw = s.recv(10) res += raw.decode("utf-8") except Exception as e: time.sleep(0.1) if timeout is not None and time.time() > t0+timeout: s.close() raise DeviceBusyError("Timeout") s.close() res = res[:-2] else: self.ser.timeout=timeout self.ser.open() self.ser.write((data+'\r').encode('utf-8')) res = self.ser.read_until(b'\r').decode('utf-8')[:-1] self.ser.close() if res == 'user cancel': raise ActionCanceledError("User didn't confirm action") if "error" in res: raise BadArgumentError(res) return res
def send_pin(self, pin: str) -> bool: self.client.open() if not pin.isdigit(): raise BadArgumentError("Non-numeric PIN provided") resp = self.client.call_raw(messages.PinMatrixAck(pin=pin)) if isinstance(resp, messages.Failure): self.client.features = self.client.call_raw(messages.GetFeatures()) if isinstance(self.client.features, messages.Features): if not self.client.features.pin_protection: raise DeviceAlreadyUnlockedError("This device does not need a PIN") if self.client.features.unlocked: raise DeviceAlreadyUnlockedError( "The PIN has already been sent to this device" ) return False elif isinstance(resp, messages.PassphraseRequest): pass_resp = self.client.call_raw( messages.PassphraseAck( passphrase=self.client.ui.get_passphrase(available_on_device=False), on_device=False, ) ) if isinstance(pass_resp, messages.Deprecated_PassphraseStateRequest): self.client.call_raw(messages.Deprecated_PassphraseStateAck()) return True
def func(*args, **kwargs): try: return f(*args, **kwargs) except ValueError as e: raise BadArgumentError(str(e)) except BTChipException as e: if e.sw in bad_args: raise BadArgumentError("Bad argument") elif e.sw == 0x6F00: # BTCHIP_SW_TECHNICAL_PROBLEM raise DeviceFailureError(e.message) elif e.sw == 0x6FAA: # BTCHIP_SW_HALTED raise DeviceConnectionError("Device is asleep") elif e.sw in cancels: raise ActionCanceledError("{} canceled".format(f.__name__)) else: raise e
def display_address(self, keypath, p2sh_p2wpkh, bech32, redeem_script=None, descriptor=None): if not check_keypath(keypath): raise BadArgumentError("Invalid keypath") if redeem_script is not None: raise BadArgumentError( "The Ledger Nano S and X do not support P2SH address display") output = self.app.getWalletPublicKey(keypath[2:], True, (p2sh_p2wpkh or bech32), bech32) return { "address": output["address"][12:-2] } # HACK: A bug in getWalletPublicKey results in the address being returned as the string "bytearray(b'<address>')". This extracts the actual address to work around this.
def sign_message(self, message: Union[str, bytes], keypath: str) -> Dict[str, str]: if not check_keypath(keypath): raise BadArgumentError("Invalid keypath") if isinstance(message, str): message = bytearray(message, "utf-8") else: message = bytearray(message) keypath = keypath[2:] # First display on screen what address you're signing for self.app.getWalletPublicKey(keypath, True) self.app.signMessagePrepare(keypath, message) signature = self.app.signMessageSign() # Make signature into standard bitcoin format rLength = signature[3] r = signature[4:4 + rLength] sLength = signature[4 + rLength + 1] s = signature[4 + rLength + 2:] if rLength == 33: r = r[1:] if sLength == 33: s = s[1:] sig = bytearray(chr(27 + 4 + (signature[0] & 0x01)), "utf8") + r + s return {"signature": base64.b64encode(sig).decode("utf-8")}
def display_singlesig_address( self, bip32_path: str, addr_type: AddressType, ) -> str: """ Display and return the single sig address of specified type at the given derivation path. :param bip32_path: The BIP 32 derivation path to get the address for :param addr_type: The address type :return: The retrieved address also being shown by the device """ if addr_type == AddressType.LEGACY: script_type = "pkh" elif addr_type == AddressType.SH_WIT: script_type = "sh-wpkh" elif addr_type == AddressType.WIT: script_type = "wpkh" else: raise BadArgumentError("Invalid address type") # prepare a request of the form like # `showaddr sh-wsh m/1h/2h/3 descriptor` request = f"showaddr {script_type} {bip32_path}" address = self.query(request) return address
def get_path_transport(path: str) -> Device: devs = hid.HidTransport.enumerate(usb_ids=HID_IDS) devs.extend(webusb.WebUsbTransport.enumerate(usb_ids=WEBUSB_IDS)) devs.extend(udp.UdpTransport.enumerate()) for dev in devs: if path == dev.get_path(): return dev raise BadArgumentError(f"Could not find device by path: {path}")
def func(*args: Any, **kwargs: Any) -> Any: try: return f(*args, **kwargs) except ValueError as e: raise BadArgumentError(str(e)) except Cancelled: raise ActionCanceledError("{} canceled".format(f.__name__)) except USBErrorNoDevice: raise DeviceConnectionError("Device disconnected")
def query(self, data: str, timeout: Optional[float] = None) -> str: """Send a text-based query to the device and get back the response""" res = self.dev.query(data, timeout) if res == "error: User cancelled": raise ActionCanceledError("User didn't confirm action") elif res.startswith("error: Unknown command"): raise UnavailableActionError(res[7:]) elif res.startswith("error: "): raise BadArgumentError(res[7:]) return res
def get_pubkey_at_path(self, path): if not check_keypath(path): raise BadArgumentError("Invalid keypath") path = path[2:] path = path.replace("h", "'") path = path.replace("H", "'") # This call returns raw uncompressed pubkey, chaincode pubkey = self.app.getWalletPublicKey(path) if path != "": parent_path = "" for ind in path.split("/")[:-1]: parent_path += ind + "/" parent_path = parent_path[:-1] # Get parent key fingerprint parent = self.app.getWalletPublicKey(parent_path) fpr = hash160(compress_public_key(parent["publicKey"]))[:4] # Compute child info childstr = path.split("/")[-1] hard = 0 if childstr[-1] == "'" or childstr[-1] == "h" or childstr[ -1] == "H": childstr = childstr[:-1] hard = 0x80000000 child = struct.pack(">I", int(childstr) + hard) # Special case for m else: child = bytearray.fromhex("00000000") fpr = child chainCode = pubkey["chainCode"] publicKey = compress_public_key(pubkey["publicKey"]) depth = len(path.split("/")) if len(path) > 0 else 0 depth = struct.pack("B", depth) if self.is_testnet: version = bytearray.fromhex("043587CF") else: version = bytearray.fromhex("0488B21E") extkey = version + depth + fpr + child + chainCode + publicKey checksum = hash256(extkey)[:4] xpub = base58.encode(extkey + checksum) result = {"xpub": xpub} if self.expert: xpub_obj = ExtendedKey() xpub_obj.deserialize(xpub) result.update(xpub_obj.get_printable_dict()) return result
def get_pubkey_at_path(self, path: str) -> ExtendedKey: self._check_unlocked() try: expanded_path = parse_path(path) except ValueError as e: raise BadArgumentError(str(e)) output = btc.get_public_node( self.client, expanded_path, coin_name=self.coin_name ) xpub = ExtendedKey.deserialize(output.xpub) if self.chain != Chain.MAIN: xpub.version = ExtendedKey.TESTNET_PUBLIC return xpub
def send_pin(self, pin): self.client.open() if not pin.isdigit(): raise BadArgumentError("Non-numeric PIN provided") resp = self.client.call_raw(proto.PinMatrixAck(pin=pin)) if isinstance(resp, proto.Failure): self.client.features = self.client.call_raw(proto.GetFeatures()) if isinstance(self.client.features, proto.Features): if not self.client.features.pin_protection: raise DeviceAlreadyUnlockedError('This device does not need a PIN') if self.client.features.pin_cached: raise DeviceAlreadyUnlockedError('The PIN has already been sent to this device') return {'success': False} return {'success': True}
def func(*args: Any, **kwargs: Any) -> Any: try: return f(*args, **kwargs) except ValueError as e: raise BadArgumentError(str(e)) except JadeError as e: if e.code == JadeError.USER_CANCELLED: raise ActionCanceledError(f"{f.__name__} canceled by user") elif e.code == JadeError.BAD_PARAMETERS: raise BadArgumentError(e.message) elif e.code == JadeError.INTERNAL_ERROR: raise DeviceFailureError(e.message) elif e.code == JadeError.HW_LOCKED: raise DeviceConnectionError("Device is locked") elif e.code == JadeError.NETWORK_MISMATCH: raise DeviceConnectionError("Network/chain selection error") elif e.code in [ JadeError.INVALID_REQUEST, JadeError.UNKNOWN_METHOD, JadeError.PROTOCOL_ERROR, ]: raise DeviceConnectionError("Messaging/communiciation error") else: raise e
def get_pubkey_at_path(self, path): self._check_unlocked() try: expanded_path = tools.parse_path(path) except ValueError as e: raise BadArgumentError(str(e)) output = btc.get_public_node(self.client, expanded_path, coin_name=self.coin_name) if self.is_testnet: result = {'xpub': xpub_main_2_test(output.xpub)} else: result = {'xpub': output.xpub} if self.expert: xpub_obj = ExtendedKey() xpub_obj.deserialize(output.xpub) result.update(xpub_obj.get_printable_dict()) return result
def __init__(self, path: str, password: str = "", expert: bool = False) -> None: """ Initializes a new BitBox02 client instance. """ super().__init__(path, password=password, expert=expert) if password: raise BadArgumentError( "The BitBox02 does not accept a passphrase from the host. Please enable the passphrase option and enter the passphrase on the device during unlock." ) hid_device = hid.device() hid_device.open_path(path.encode()) self.transport = u2fhid.U2FHid(hid_device) self.device_path = path # use self.init() to access self.bb02. self.bb02: Optional[bitbox02.BitBox02] = None self.noise_config: BitBoxNoiseConfig = CLINoiseConfig()
def get_random(self, num_bytes: int = 32): if num_bytes < 0 or num_bytes > 10000: raise BadArgumentError("We can only get up to 10k bytes of random data") res = self.query("getrandom %d" % num_bytes) return bytes.fromhex(res)
def display_address(self, keypath, p2sh_p2wpkh, bech32, redeem_script=None, descriptor=None): self._check_unlocked() # descriptor means multisig with xpubs if descriptor: pubkeys = [] xpub = ExtendedKey() for i in range(0, descriptor.multisig_N): xpub.deserialize(descriptor.base_key[i]) hd_node = proto.HDNodeType( depth=xpub.depth, fingerprint=int.from_bytes(xpub.parent_fingerprint, "big"), child_num=xpub.child_num, chain_code=xpub.chaincode, public_key=xpub.pubkey, ) pubkeys.append( proto.HDNodePathType( node=hd_node, address_n=tools.parse_path("m" + descriptor.path_suffix[i]), )) multisig = proto.MultisigRedeemScriptType( m=int(descriptor.multisig_M), signatures=[b""] * int(descriptor.multisig_N), pubkeys=pubkeys, ) # redeem_script means p2sh/multisig elif redeem_script: # Get multisig object required by Trezor's get_address multisig = parse_multisig(bytes.fromhex(redeem_script)) if not multisig[0]: raise BadArgumentError( "The redeem script provided is not a multisig. Only multisig scripts can be displayed." ) multisig = multisig[1] else: multisig = None # Script type if p2sh_p2wpkh: script_type = proto.InputScriptType.SPENDP2SHWITNESS elif bech32: script_type = proto.InputScriptType.SPENDWITNESS elif redeem_script: script_type = proto.InputScriptType.SPENDMULTISIG else: script_type = proto.InputScriptType.SPENDADDRESS # convert device fingerprint to 'm' if exists in path keypath = keypath.replace(self.get_master_fingerprint_hex(), "m") for path in keypath.split(","): if len(path.split("/")[0]) == 8: path = path.split("/", 1)[1] expanded_path = tools.parse_path(path) try: address = btc.get_address( self.client, self.coin_name, expanded_path, show_display=True, script_type=script_type, multisig=multisig, ) return {"address": address} except: pass raise BadArgumentError("No path supplied matched device keys")
def display_multisig_address( self, addr_type: AddressType, multisig: MultisigDescriptor, ) -> str: self._check_unlocked() der_pks = list( zip([p.get_pubkey_bytes(0) for p in multisig.pubkeys], multisig.pubkeys) ) if multisig.is_sorted: der_pks = sorted(der_pks) pubkey_objs = [] for pk, p in der_pks: if p.extkey is not None: xpub = p.extkey hd_node = messages.HDNodeType( depth=xpub.depth, fingerprint=int.from_bytes(xpub.parent_fingerprint, "big"), child_num=xpub.child_num, chain_code=xpub.chaincode, public_key=xpub.pubkey, ) pubkey_objs.append( messages.HDNodePathType( node=hd_node, address_n=parse_path( "m" + p.deriv_path if p.deriv_path is not None else "" ), ) ) else: hd_node = messages.HDNodeType( depth=0, fingerprint=0, child_num=0, chain_code=b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", public_key=pk, ) pubkey_objs.append(messages.HDNodePathType(node=hd_node, address_n=[])) trezor_ms = messages.MultisigRedeemScriptType( m=multisig.thresh, signatures=[b""] * len(pubkey_objs), pubkeys=pubkey_objs ) # Script type if addr_type == AddressType.SH_WIT: script_type = messages.InputScriptType.SPENDP2SHWITNESS elif addr_type == AddressType.WIT: script_type = messages.InputScriptType.SPENDWITNESS elif addr_type == AddressType.LEGACY: script_type = messages.InputScriptType.SPENDMULTISIG else: raise BadArgumentError("Unknown address type") for p in multisig.pubkeys: keypath = p.origin.get_derivation_path() if p.origin is not None else "m/" keypath += p.deriv_path if p.deriv_path is not None else "" path = parse_path(keypath) try: address = btc.get_address( self.client, self.coin_name, path, show_display=True, script_type=script_type, multisig=trezor_ms, ) assert isinstance(address, str) return address except Exception: pass raise BadArgumentError("No path supplied matched device keys")
def sign_tx(self, tx: PSBT) -> PSBT: """ Sign a transaction with the Blockstream Jade. - Jade can only be used to sign single-key inputs at this time. It cannot sign multisig or arbitrary scripts. """ c_txn = CTransaction(tx.tx) master_fp = self.get_master_fingerprint() signing_pubkeys = [None] * len(tx.inputs) # Signing input details jade_inputs = [] for n_vin, (txin, psbtin) in py_enumerate(zip(c_txn.vin, tx.inputs)): # Get bip32 path to use to sign, if required for this input path = None for pubkey, origin in psbtin.hd_keypaths.items(): if origin.fingerprint == master_fp and len(origin.path) > 0: # Our input if (pubkey not in psbtin.partial_sigs or not psbtin.partial_sigs[pubkey]): # hw to sign this input - it is not already signed signing_pubkeys[n_vin] = pubkey path = origin.path # Get the tx and prevout/scriptcode utxo = None input_txn_bytes = None if psbtin.witness_utxo: utxo = psbtin.witness_utxo if psbtin.non_witness_utxo: if txin.prevout.hash != psbtin.non_witness_utxo.sha256: raise BadArgumentError( "Input {} has a non_witness_utxo with the wrong hash". format(n_vin)) utxo = psbtin.non_witness_utxo.vout[txin.prevout.n] input_txn_bytes = psbtin.non_witness_utxo.serialize_without_witness( ) scriptcode = utxo.scriptPubKey if is_p2sh(scriptcode): scriptcode = psbtin.redeem_script witness_input, witness_version, witness_program = is_witness( scriptcode) if witness_input: if is_p2wsh(scriptcode): scriptcode = psbtin.witness_script elif is_p2wpkh(scriptcode): scriptcode = b"\x76\xa9\x14" + witness_program + b"\x88\xac" else: scriptcode = None # Build the input and add to the list jade_inputs.append({ "is_witness": witness_input, "input_tx": input_txn_bytes, "script": scriptcode, "path": path, }) # Change output details # This is optional, in that if we send it Jade validates the change output script # and the user need not confirm that ouptut. If not passed the change output must # be confirmed by the user on the hwwallet screen, like any other spend output. change = [None] * len(tx.outputs) for n_vout, (txout, psbtout) in py_enumerate(zip(c_txn.vout, tx.outputs)): for pubkey, origin in psbtout.hd_keypaths.items(): # Considers 'our' outputs as change as far as Jade is concerned # ie. can be auto-confirmed. # Is this ok, or should check path also, assuming bip44-like ? if origin.fingerprint == master_fp and len(origin.path) > 0: addr_type = None if txout.is_p2pkh(): addr_type = AddressType.LEGACY elif txout.is_witness()[0] and not txout.is_p2wsh(): addr_type = AddressType.WIT elif txout.is_p2sh(): addr_type = AddressType.SH_WIT # is it really though ? if addr_type: addr_type = self._convertAddrType(addr_type) change[n_vout] = { "path": origin.path, "variant": addr_type } # The txn itself txn_bytes = c_txn.serialize_without_witness() # Request Jade generate the signatures for our inputs. # Change details are passed to be validated on the hw (user does not confirm) signatures = self.jade.sign_tx(self._network(), txn_bytes, jade_inputs, change) # Push sigs into PSBT structure as appropriate for psbtin, pubkey, sig in zip(tx.inputs, signing_pubkeys, signatures): if pubkey and sig: psbtin.partial_sigs[pubkey] = sig # Return the updated psbt return tx
def sign_tx(self, tx: PSBT) -> PSBT: """ Sign a transaction with the Trezor. There are some limitations to what transactions can be signed. - Multisig inputs are limited to at most n-of-15 multisigs. This is a firmware limitation. - Transactions with arbitrary input scripts (scriptPubKey, redeemScript, or witnessScript) and arbitrary output scripts cannot be signed. This is a firmware limitation. - Send-to-self transactions will result in no prompt for outputs as all outputs will be detected as change. """ self._check_unlocked() # Get this devices master key fingerprint master_key = btc.get_public_node(self.client, [0x80000000], coin_name="Bitcoin") master_fp = get_xpub_fingerprint(master_key.xpub) # Do multiple passes for multisig passes = 1 p = 0 while p < passes: # Prepare inputs inputs = [] to_ignore = ( [] ) # Note down which inputs whose signatures we're going to ignore for input_num, (psbt_in, txin) in py_enumerate( list(zip(tx.inputs, tx.tx.vin)) ): txinputtype = messages.TxInputType( prev_hash=ser_uint256(txin.prevout.hash)[::-1], prev_index=txin.prevout.n, sequence=txin.nSequence, ) # Detrermine spend type scriptcode = b"" utxo = None if psbt_in.witness_utxo: utxo = psbt_in.witness_utxo if psbt_in.non_witness_utxo: if txin.prevout.hash != psbt_in.non_witness_utxo.sha256: raise BadArgumentError( "Input {} has a non_witness_utxo with the wrong hash".format( input_num ) ) utxo = psbt_in.non_witness_utxo.vout[txin.prevout.n] if utxo is None: continue scriptcode = utxo.scriptPubKey # Check if P2SH p2sh = False if is_p2sh(scriptcode): # Look up redeemscript if len(psbt_in.redeem_script) == 0: continue scriptcode = psbt_in.redeem_script p2sh = True # Check segwit is_wit, _, _ = is_witness(scriptcode) if is_wit: if p2sh: txinputtype.script_type = ( messages.InputScriptType.SPENDP2SHWITNESS ) else: txinputtype.script_type = messages.InputScriptType.SPENDWITNESS else: txinputtype.script_type = messages.InputScriptType.SPENDADDRESS txinputtype.amount = utxo.nValue # Check if P2WSH p2wsh = False if is_p2wsh(scriptcode): # Look up witnessscript if len(psbt_in.witness_script) == 0: continue scriptcode = psbt_in.witness_script p2wsh = True def ignore_input() -> None: txinputtype.address_n = [ 0x80000000 | 84, 0x80000000 | (0 if self.chain == Chain.MAIN else 1), 0x80000000, 0, 0, ] txinputtype.multisig = None txinputtype.script_type = messages.InputScriptType.SPENDWITNESS inputs.append(txinputtype) to_ignore.append(input_num) # Check for multisig is_ms, multisig = parse_multisig(scriptcode, tx.xpub, psbt_in) if is_ms: # Add to txinputtype txinputtype.multisig = multisig if not is_wit: if utxo.is_p2sh: txinputtype.script_type = ( messages.InputScriptType.SPENDMULTISIG ) else: # Cannot sign bare multisig, ignore it ignore_input() continue elif not is_ms and not is_wit and not is_p2pkh(scriptcode): # Cannot sign unknown spk, ignore it ignore_input() continue elif not is_ms and is_wit and p2wsh: # Cannot sign unknown witness script, ignore it ignore_input() continue # Find key to sign with found = False # Whether we have found a key to sign with found_in_sigs = ( False # Whether we have found one of our keys in the signatures ) our_keys = 0 for key in psbt_in.hd_keypaths.keys(): keypath = psbt_in.hd_keypaths[key] if keypath.fingerprint == master_fp: if ( key in psbt_in.partial_sigs ): # This key already has a signature found_in_sigs = True continue if ( not found ): # This key does not have a signature and we don't have a key to sign with yet txinputtype.address_n = keypath.path found = True our_keys += 1 # Determine if we need to do more passes to sign everything if our_keys > passes: passes = our_keys if ( not found and not found_in_sigs ): # None of our keys were in hd_keypaths or in partial_sigs # This input is not one of ours ignore_input() continue elif ( not found and found_in_sigs ): # All of our keys are in partial_sigs, ignore whatever signature is produced for this input ignore_input() continue # append to inputs inputs.append(txinputtype) # address version byte if self.chain != Chain.MAIN: p2pkh_version = b"\x6f" p2sh_version = b"\xc4" bech32_hrp = "tb" else: p2pkh_version = b"\x00" p2sh_version = b"\x05" bech32_hrp = "bc" # prepare outputs outputs = [] for i, out in py_enumerate(tx.tx.vout): txoutput = messages.TxOutputType(amount=out.nValue) txoutput.script_type = messages.OutputScriptType.PAYTOADDRESS if out.is_p2pkh(): txoutput.address = to_address(out.scriptPubKey[3:23], p2pkh_version) elif out.is_p2sh(): txoutput.address = to_address(out.scriptPubKey[2:22], p2sh_version) elif out.is_opreturn(): txoutput.script_type = messages.OutputScriptType.PAYTOOPRETURN txoutput.op_return_data = out.scriptPubKey[2:] else: wit, ver, prog = out.is_witness() if wit: txoutput.address = bech32.encode(bech32_hrp, ver, prog) else: raise BadArgumentError("Output is not an address") # Add the derivation path for change psbt_out = tx.outputs[i] for _, keypath in psbt_out.hd_keypaths.items(): if keypath.fingerprint != master_fp: continue wit, ver, prog = out.is_witness() if out.is_p2pkh(): txoutput.address_n = keypath.path txoutput.address = None elif wit: txoutput.script_type = messages.OutputScriptType.PAYTOWITNESS txoutput.address_n = keypath.path txoutput.address = None elif out.is_p2sh() and psbt_out.redeem_script: wit, ver, prog = CTxOut(0, psbt_out.redeem_script).is_witness() if wit and len(prog) in [20, 32]: txoutput.script_type = ( messages.OutputScriptType.PAYTOP2SHWITNESS ) txoutput.address_n = keypath.path txoutput.address = None # add multisig info is_ms, multisig = parse_multisig( psbt_out.witness_script or psbt_out.redeem_script, tx.xpub, psbt_out ) if is_ms: txoutput.multisig = multisig # append to outputs outputs.append(txoutput) # Prepare prev txs prevtxs = {} for psbt_in in tx.inputs: if psbt_in.non_witness_utxo: prev = psbt_in.non_witness_utxo t = messages.TransactionType() t.version = prev.nVersion t.lock_time = prev.nLockTime for vin in prev.vin: i = messages.TxInputType( prev_hash=ser_uint256(vin.prevout.hash)[::-1], prev_index=vin.prevout.n, script_sig=vin.scriptSig, sequence=vin.nSequence, ) t.inputs.append(i) for vout in prev.vout: o = messages.TxOutputBinType( amount=vout.nValue, script_pubkey=vout.scriptPubKey, ) t.bin_outputs.append(o) logging.debug(psbt_in.non_witness_utxo.hash) assert psbt_in.non_witness_utxo.sha256 is not None prevtxs[ser_uint256(psbt_in.non_witness_utxo.sha256)[::-1]] = t # Sign the transaction signed_tx = btc.sign_tx( client=self.client, coin_name=self.coin_name, inputs=inputs, outputs=outputs, prev_txes=prevtxs, version=tx.tx.nVersion, lock_time=tx.tx.nLockTime, ) # Each input has one signature for input_num, (psbt_in, sig) in py_enumerate( list(zip(tx.inputs, signed_tx[0])) ): if input_num in to_ignore: continue for pubkey in psbt_in.hd_keypaths.keys(): fp = psbt_in.hd_keypaths[pubkey].fingerprint if fp == master_fp and pubkey not in psbt_in.partial_sigs: psbt_in.partial_sigs[pubkey] = sig + b"\x01" break p += 1 return tx
def display_multisig_address( self, addr_type: AddressType, multisig: MultisigDescriptor, ) -> str: signer_origins = [] signers = [] paths = [] for pubkey in multisig.pubkeys: if pubkey.extkey is None: raise BadArgumentError( "Blockstream Jade can only generate addresses for multisigs with full extended keys" ) if pubkey.origin is None: raise BadArgumentError( "Blockstream Jade can only generate addresses for multisigs with key origin information" ) if pubkey.deriv_path is None: raise BadArgumentError( "Blockstream Jade can only generate addresses for multisigs with key origin derivation path information" ) # Tuple to derive deterministic name for the registrtion signer_origins.append((pubkey.origin.fingerprint, pubkey.origin.path)) # We won't include the additional path in the multisig registration signers.append( { "fingerprint": pubkey.origin.fingerprint, "derivation": pubkey.origin.path, "xpub": pubkey.pubkey, "path": [], } ) # Instead hold it as the address path path = ( pubkey.deriv_path[1:] if pubkey.deriv_path[0] == "/" else pubkey.deriv_path ) paths.append(parse_path(path)) # sort origins, signers and paths according to origins (like in _get_multisig_name) signer_origins, signers, paths = [ list(a) for a in zip(*sorted(zip(signer_origins, signers, paths))) ] # Get a deterministic name for this multisig wallet script_variant = self._convertAddrType(addr_type, multisig=True) multisig_name = self._get_multisig_name( script_variant, multisig.thresh, signer_origins ) # Need to ensure this multisig wallet is registered first # (Note: 're-registering' is a no-op) self.jade.register_multisig( self._network(), multisig_name, script_variant, True, # always use sorted multisig.thresh, signers, ) address = self.jade.get_receive_address( self._network(), paths, multisig_name=multisig_name ) return str(address)
def sign_tx(self, psbt: PSBT) -> Dict[str, str]: def find_our_key( keypaths: Dict[bytes, Sequence[int]] ) -> Tuple[Optional[bytes], Optional[Sequence[int]]]: """ Keypaths is a map of pubkey to hd keypath, where the first element in the keypath is the master fingerprint. We attempt to find the key which belongs to the BitBox02 by matching the fingerprint, and then matching the pubkey. Returns the pubkey and the keypath, without the fingerprint. """ for pubkey, keypath_with_fingerprint in keypaths.items(): fp, keypath = keypath_with_fingerprint[0], keypath_with_fingerprint[1:] # Cheap check if the key is ours. if fp != master_fp: continue # Expensive check if the key is ours. # TODO: check for fingerprint collision # keypath_account = keypath[:-2] return pubkey, keypath return None, None def get_simple_type( output: CTxOut, redeem_script: bytes ) -> bitbox02.btc.BTCScriptConfig.SimpleType: if is_p2pkh(output.scriptPubKey): raise BadArgumentError( "The BitBox02 does not support legacy p2pkh scripts" ) if is_p2wpkh(output.scriptPubKey): return bitbox02.btc.BTCScriptConfig.P2WPKH if output.is_p2sh() and is_p2wpkh(redeem_script): return bitbox02.btc.BTCScriptConfig.P2WPKH_P2SH raise BadArgumentError( "Input script type not recognized of input {}.".format(input_index) ) master_fp = struct.unpack("<I", unhexlify(self.get_master_fingerprint_hex()))[0] inputs: List[bitbox02.BTCInputType] = [] bip44_account = None # One pubkey per input. The pubkey identifies the key per input with which we sign. There # must be exactly one pubkey per input that belongs to the BitBox02. found_pubkeys: List[bytes] = [] for input_index, (psbt_in, tx_in) in builtins.enumerate( zip(psbt.inputs, psbt.tx.vin) ): if psbt_in.sighash and psbt_in.sighash != 1: raise BadArgumentError( "The BitBox02 only supports SIGHASH_ALL. Found sighash: {}".format( psbt_in.sighash ) ) utxo = None prevtx = None # psbt_in.witness_utxo was originally used for segwit utxo's, but since it was # discovered that the amounts are not correctly committed to in the segwit sighash, the # full prevtx (non_witness_utxo) is supplied for both segwit and non-segwit inputs. # See # - https://medium.com/shiftcrypto/bitbox-app-firmware-update-6-2020-c70f733a5330 # - https://blog.trezor.io/details-of-firmware-updates-for-trezor-one-version-1-9-1-and-trezor-model-t-version-2-3-1-1eba8f60f2dd. # - https://github.com/zkSNACKs/WalletWasabi/pull/3822 # The BitBox02 for now requires the prevtx, at least until Taproot activates. if psbt_in.non_witness_utxo: if tx_in.prevout.hash != psbt_in.non_witness_utxo.sha256: raise BadArgumentError( "Input {} has a non_witness_utxo with the wrong hash".format( input_index ) ) utxo = psbt_in.non_witness_utxo.vout[tx_in.prevout.n] prevtx = psbt_in.non_witness_utxo elif psbt_in.witness_utxo: utxo = psbt_in.witness_utxo if utxo is None: raise BadArgumentError("No utxo found for input {}".format(input_index)) if prevtx is None: raise BadArgumentError( "Previous transaction missing for input {}".format(input_index) ) found_pubkey, keypath = find_our_key(psbt_in.hd_keypaths) if not found_pubkey: raise BadArgumentError("No key found for input {}".format(input_index)) assert keypath is not None found_pubkeys.append(found_pubkey) # TOOD: validate keypath if bip44_account is None: bip44_account = keypath[2] elif bip44_account != keypath[2]: raise BadArgumentError( "The bip44 account index must be the same for all inputs and changes" ) simple_type = get_simple_type(utxo, psbt_in.redeem_script) script_config_index_map = { bitbox02.btc.BTCScriptConfig.P2WPKH: 0, bitbox02.btc.BTCScriptConfig.P2WPKH_P2SH: 1, } inputs.append( { "prev_out_hash": ser_uint256(tx_in.prevout.hash), "prev_out_index": tx_in.prevout.n, "prev_out_value": utxo.nValue, "sequence": tx_in.nSequence, "keypath": keypath, "script_config_index": script_config_index_map[simple_type], "prev_tx": { "version": prevtx.nVersion, "locktime": prevtx.nLockTime, "inputs": [ { "prev_out_hash": ser_uint256(prev_in.prevout.hash), "prev_out_index": prev_in.prevout.n, "signature_script": prev_in.scriptSig, "sequence": prev_in.nSequence, } for prev_in in prevtx.vin ], "outputs": [ { "value": prev_out.nValue, "pubkey_script": prev_out.scriptPubKey, } for prev_out in prevtx.vout ], }, } ) outputs: List[bitbox02.BTCOutputType] = [] for output_index, (psbt_out, tx_out) in builtins.enumerate( zip(psbt.outputs, psbt.tx.vout) ): _, keypath = find_our_key(psbt_out.hd_keypaths) is_change = keypath and keypath[-2] == 1 if is_change: assert keypath is not None simple_type = get_simple_type(tx_out, psbt_out.redeem_script) outputs.append( bitbox02.BTCOutputInternal( keypath=keypath, value=tx_out.nValue, script_config_index=script_config_index_map[simple_type], ) ) else: if tx_out.is_p2pkh(): output_type = bitbox02.btc.P2PKH output_hash = tx_out.scriptPubKey[3:23] elif is_p2wpkh(tx_out.scriptPubKey): output_type = bitbox02.btc.P2WPKH output_hash = tx_out.scriptPubKey[2:] elif tx_out.is_p2sh(): output_type = bitbox02.btc.P2SH output_hash = tx_out.scriptPubKey[2:22] elif is_p2wsh(tx_out.scriptPubKey): output_type = bitbox02.btc.P2WSH output_hash = tx_out.scriptPubKey[2:] else: raise BadArgumentError( "Output type not recognized of output {}".format(output_index) ) outputs.append( bitbox02.BTCOutputExternal( output_type=output_type, output_hash=output_hash, value=tx_out.nValue, ) ) assert bip44_account is not None bip44_network = 1 + HARDENED if self.is_testnet else 0 + HARDENED sigs = self.init().btc_sign( bitbox02.btc.TBTC if self.is_testnet else bitbox02.btc.BTC, [ bitbox02.btc.BTCScriptConfigWithKeypath( script_config=bitbox02.btc.BTCScriptConfig( simple_type=bitbox02.btc.BTCScriptConfig.P2WPKH ), keypath=[84 + HARDENED, bip44_network, bip44_account], ), bitbox02.btc.BTCScriptConfigWithKeypath( script_config=bitbox02.btc.BTCScriptConfig( simple_type=bitbox02.btc.BTCScriptConfig.P2WPKH_P2SH ), keypath=[49 + HARDENED, bip44_network, bip44_account], ), ], inputs=inputs, outputs=outputs, locktime=psbt.tx.nLockTime, version=psbt.tx.nVersion, ) for (_, sig), pubkey, psbt_in in zip(sigs, found_pubkeys, psbt.inputs): r, s = sig[:32], sig[32:64] # ser_sig_der() adds SIGHASH_ALL psbt_in.partial_sigs[pubkey] = ser_sig_der(r, s) return {"psbt": psbt.serialize()}
def sign_tx(self, tx): self._check_unlocked() # Get this devices master key fingerprint master_key = btc.get_public_node(self.client, [0x80000000], coin_name="Bitcoin") master_fp = get_xpub_fingerprint(master_key.xpub) # Do multiple passes for multisig passes = 1 p = 0 while p < passes: # Prepare inputs inputs = [] to_ignore = ( [] ) # Note down which inputs whose signatures we're going to ignore for input_num, (psbt_in, txin) in py_enumerate( list(zip(tx.inputs, tx.tx.vin))): txinputtype = proto.TxInputType() # Set the input stuff txinputtype.prev_hash = ser_uint256(txin.prevout.hash)[::-1] txinputtype.prev_index = txin.prevout.n txinputtype.sequence = txin.nSequence # Detrermine spend type scriptcode = b"" utxo = None if psbt_in.witness_utxo: utxo = psbt_in.witness_utxo if psbt_in.non_witness_utxo: if txin.prevout.hash != psbt_in.non_witness_utxo.sha256: raise BadArgumentError( "Input {} has a non_witness_utxo with the wrong hash" .format(input_num)) utxo = psbt_in.non_witness_utxo.vout[txin.prevout.n] if utxo is None: continue scriptcode = utxo.scriptPubKey # Check if P2SH p2sh = False if is_p2sh(scriptcode): # Look up redeemscript if len(psbt_in.redeem_script) == 0: continue scriptcode = psbt_in.redeem_script p2sh = True # Check segwit is_wit, _, _ = is_witness(scriptcode) if is_wit: if p2sh: txinputtype.script_type = proto.InputScriptType.SPENDP2SHWITNESS else: txinputtype.script_type = proto.InputScriptType.SPENDWITNESS else: txinputtype.script_type = proto.InputScriptType.SPENDADDRESS txinputtype.amount = utxo.nValue # Check if P2WSH p2wsh = False if is_p2wsh(scriptcode): # Look up witnessscript if len(psbt_in.witness_script) == 0: continue scriptcode = psbt_in.witness_script p2wsh = True def ignore_input(): txinputtype.address_n = [ 0x80000000 | 84, 0x80000000 | (1 if self.is_testnet else 0), ] txinputtype.multisig = None txinputtype.script_type = proto.InputScriptType.SPENDWITNESS inputs.append(txinputtype) to_ignore.append(input_num) # Check for multisig is_ms, multisig = parse_multisig(scriptcode) if is_ms: txinputtype.multisig = parse_multisig_xpubs( tx, psbt_in, multisig) if not is_wit: if utxo.is_p2sh: txinputtype.script_type = ( proto.InputScriptType.SPENDMULTISIG) else: # Cannot sign bare multisig, ignore it ignore_input() continue elif not is_ms and not is_wit and not is_p2pkh(scriptcode): # Cannot sign unknown spk, ignore it ignore_input() continue elif not is_ms and is_wit and p2wsh: # Cannot sign unknown witness script, ignore it ignore_input() continue # Find key to sign with found = False # Whether we have found a key to sign with found_in_sigs = ( False # Whether we have found one of our keys in the signatures ) our_keys = 0 for key in psbt_in.hd_keypaths.keys(): keypath = psbt_in.hd_keypaths[key] if keypath[0] == master_fp: if (key in psbt_in.partial_sigs ): # This key already has a signature found_in_sigs = True continue if ( not found ): # This key does not have a signature and we don't have a key to sign with yet txinputtype.address_n = keypath[1:] found = True our_keys += 1 # Determine if we need to do more passes to sign everything if our_keys > passes: passes = our_keys if ( not found and not found_in_sigs ): # None of our keys were in hd_keypaths or in partial_sigs # This input is not one of ours ignore_input() continue elif ( not found and found_in_sigs ): # All of our keys are in partial_sigs, ignore whatever signature is produced for this input ignore_input() continue # append to inputs inputs.append(txinputtype) # address version byte if self.is_testnet: p2pkh_version = b"\x6f" p2sh_version = b"\xc4" bech32_hrp = "tb" else: p2pkh_version = b"\x00" p2sh_version = b"\x05" bech32_hrp = "bc" # prepare outputs outputs = [] for i, out in py_enumerate(tx.tx.vout): txoutput = proto.TxOutputType() txoutput.amount = out.nValue txoutput.script_type = proto.OutputScriptType.PAYTOADDRESS if out.is_p2pkh(): txoutput.address = to_address(out.scriptPubKey[3:23], p2pkh_version) elif out.is_p2sh(): txoutput.address = to_address(out.scriptPubKey[2:22], p2sh_version) else: wit, ver, prog = out.is_witness() if wit: txoutput.address = bech32.encode(bech32_hrp, ver, prog) else: raise BadArgumentError("Output is not an address") # Add the derivation path for change psbt_out = tx.outputs[i] for _, keypath in psbt_out.hd_keypaths.items(): if keypath[0] == master_fp: wit, ver, prog = out.is_witness() if out.is_p2pkh(): txoutput.address_n = keypath[1:] txoutput.address = None elif wit: txoutput.script_type = proto.OutputScriptType.PAYTOWITNESS txoutput.address_n = keypath[1:] txoutput.address = None elif out.is_p2sh() and psbt_out.redeem_script: wit, ver, prog = CTxOut( 0, psbt_out.redeem_script).is_witness() if wit and len(prog) == 20: txoutput.script_type = ( proto.OutputScriptType.PAYTOP2SHWITNESS) txoutput.address_n = keypath[1:] txoutput.address = None is_ms, multisig = parse_multisig( psbt_out.witness_script if wit else psbt_out. redeem_script) if is_ms: txoutput.multisig = parse_multisig_xpubs( tx, psbt_out, multisig) # append to outputs outputs.append(txoutput) # Prepare prev txs prevtxs = {} for psbt_in in tx.inputs: if psbt_in.non_witness_utxo: prev = psbt_in.non_witness_utxo t = proto.TransactionType() t.version = prev.nVersion t.lock_time = prev.nLockTime for vin in prev.vin: i = proto.TxInputType() i.prev_hash = ser_uint256(vin.prevout.hash)[::-1] i.prev_index = vin.prevout.n i.script_sig = vin.scriptSig i.sequence = vin.nSequence t.inputs.append(i) for vout in prev.vout: o = proto.TxOutputBinType() o.amount = vout.nValue o.script_pubkey = vout.scriptPubKey t.bin_outputs.append(o) logging.debug(psbt_in.non_witness_utxo.hash) prevtxs[ser_uint256( psbt_in.non_witness_utxo.sha256)[::-1]] = t # Sign the transaction tx_details = proto.SignTx() tx_details.version = tx.tx.nVersion tx_details.lock_time = tx.tx.nLockTime signed_tx = btc.sign_tx(self.client, self.coin_name, inputs, outputs, tx_details, prevtxs) # Each input has one signature for input_num, (psbt_in, sig) in py_enumerate( list(zip(tx.inputs, signed_tx[0]))): if input_num in to_ignore: continue for pubkey in psbt_in.hd_keypaths.keys(): fp = psbt_in.hd_keypaths[pubkey][0] if fp == master_fp and pubkey not in psbt_in.partial_sigs: psbt_in.partial_sigs[pubkey] = sig + b"\x01" break p += 1 return {"psbt": tx.serialize()}
def sign_tx(self, tx): c_tx = CTransaction(tx.tx) tx_bytes = c_tx.serialize_with_witness() # Master key fingerprint master_fpr = hash160( compress_public_key( self.app.getWalletPublicKey("")["publicKey"]))[:4] # An entry per input, each with 0 to many keys to sign with all_signature_attempts = [[]] * len(c_tx.vin) # Get the app version to determine whether to use Trusted Input for segwit version = self.app.getFirmwareVersion() use_trusted_segwit = ( version["major_version"] == 1 and version["minor_version"] >= 4) or version["major_version"] > 1 # NOTE: We only support signing Segwit inputs, where we can skip over non-segwit # inputs, or non-segwit inputs, where *all* inputs are non-segwit. This is due # to Ledger's mutually exclusive signing steps for each type. segwit_inputs = [] # Legacy style inputs legacy_inputs = [] has_segwit = False has_legacy = False script_codes = [[]] * len(c_tx.vin) # Detect changepath, (p2sh-)p2(w)pkh only change_path = "" for txout, i_num in zip(c_tx.vout, range(len(c_tx.vout))): # Find which wallet key could be change based on hdsplit: m/.../1/k # Wallets shouldn't be sending to change address as user action # otherwise this will get confused for pubkey, path in tx.outputs[i_num].hd_keypaths.items(): if (struct.pack("<I", path[0]) == master_fpr and len(path) > 2 and path[-2] == 1): # For possible matches, check if pubkey matches possible template if (hash160(pubkey) in txout.scriptPubKey or hash160( bytearray.fromhex("0014") + hash160(pubkey)) in txout.scriptPubKey): change_path = "" for index in path[1:]: change_path += str(index) + "/" change_path = change_path[:-1] for txin, psbt_in, i_num in zip(c_tx.vin, tx.inputs, range(len(c_tx.vin))): seq = format(txin.nSequence, "x") seq = seq.zfill(8) seq = bytearray.fromhex(seq) seq.reverse() seq_hex = "".join("{:02x}".format(x) for x in seq) scriptcode = b"" utxo = None if psbt_in.witness_utxo: utxo = psbt_in.witness_utxo if psbt_in.non_witness_utxo: if txin.prevout.hash != psbt_in.non_witness_utxo.sha256: raise BadArgumentError( "Input {} has a non_witness_utxo with the wrong hash". format(i_num)) utxo = psbt_in.non_witness_utxo.vout[txin.prevout.n] if utxo is None: raise Exception( "PSBT is missing input utxo information, cannot sign") scriptcode = utxo.scriptPubKey if is_p2sh(scriptcode): if len(psbt_in.redeem_script) == 0: continue scriptcode = psbt_in.redeem_script is_wit, _, _ = is_witness(scriptcode) segwit_inputs.append({ "value": txin.prevout.serialize() + struct.pack("<Q", utxo.nValue), "witness": True, "sequence": seq_hex, }) if is_wit: if is_p2wsh(scriptcode): if len(psbt_in.witness_script) == 0: continue scriptcode = psbt_in.witness_script elif is_p2wpkh(scriptcode): _, _, wit_prog = is_witness(scriptcode) scriptcode = b"\x76\xa9\x14" + wit_prog + b"\x88\xac" else: continue has_segwit = True else: # We only need legacy inputs in the case where all inputs are legacy, we check # later ledger_prevtx = bitcoinTransaction( psbt_in.non_witness_utxo.serialize()) legacy_inputs.append( self.app.getTrustedInput(ledger_prevtx, txin.prevout.n)) legacy_inputs[-1]["sequence"] = seq_hex has_legacy = True if psbt_in.non_witness_utxo and use_trusted_segwit: ledger_prevtx = bitcoinTransaction( psbt_in.non_witness_utxo.serialize()) segwit_inputs[-1].update( self.app.getTrustedInput(ledger_prevtx, txin.prevout.n)) pubkeys = [] signature_attempts = [] # Save scriptcode for later signing script_codes[i_num] = scriptcode # Find which pubkeys could sign this input (should be all?) for pubkey in psbt_in.hd_keypaths.keys(): if hash160(pubkey) in scriptcode or pubkey in scriptcode: pubkeys.append(pubkey) # Figure out which keys in inputs are from our wallet for pubkey in pubkeys: keypath = psbt_in.hd_keypaths[pubkey] if master_fpr == struct.pack("<I", keypath[0]): # Add the keypath strings keypath_str = "" for index in keypath[1:]: keypath_str += str(index) + "/" keypath_str = keypath_str[:-1] signature_attempts.append([keypath_str, pubkey]) all_signature_attempts[i_num] = signature_attempts # Sign any segwit inputs if has_segwit: # Process them up front with all scriptcodes blank blank_script_code = bytearray() for i in range(len(segwit_inputs)): self.app.startUntrustedTransaction( i == 0, i, segwit_inputs, script_codes[i] if use_trusted_segwit else blank_script_code, c_tx.nVersion, ) # Number of unused fields for Nano S, only changepath and transaction in bytes req self.app.finalizeInput(b"DUMMY", -1, -1, change_path, tx_bytes) # For each input we control do segwit signature for i in range(len(segwit_inputs)): for signature_attempt in all_signature_attempts[i]: self.app.startUntrustedTransaction(False, 0, [segwit_inputs[i]], script_codes[i], c_tx.nVersion) tx.inputs[i].partial_sigs[ signature_attempt[1]] = self.app.untrustedHashSign( signature_attempt[0], "", c_tx.nLockTime, 0x01) elif has_legacy: first_input = True # Legacy signing if all inputs are legacy for i in range(len(legacy_inputs)): for signature_attempt in all_signature_attempts[i]: assert tx.inputs[i].non_witness_utxo is not None self.app.startUntrustedTransaction(first_input, i, legacy_inputs, script_codes[i], c_tx.nVersion) self.app.finalizeInput(b"DUMMY", -1, -1, change_path, tx_bytes) tx.inputs[i].partial_sigs[ signature_attempt[1]] = self.app.untrustedHashSign( signature_attempt[0], "", c_tx.nLockTime, 0x01) first_input = False # Send PSBT back return {"psbt": tx.serialize()}