Beispiel #1
0
    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")
Beispiel #2
0
 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
Beispiel #3
0
 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))
Beispiel #4
0
    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
Beispiel #6
0
 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
Beispiel #7
0
 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
Beispiel #8
0
 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
Beispiel #9
0
 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.
Beispiel #10
0
    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")}
Beispiel #11
0
    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
Beispiel #12
0
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}")
Beispiel #13
0
 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")
Beispiel #14
0
 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
Beispiel #15
0
    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
Beispiel #16
0
 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
Beispiel #17
0
 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}
Beispiel #18
0
 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
Beispiel #19
0
 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
Beispiel #20
0
    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()
Beispiel #21
0
 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)
Beispiel #22
0
    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")
Beispiel #23
0
    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")
Beispiel #24
0
    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
Beispiel #25
0
    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
Beispiel #26
0
    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)
Beispiel #27
0
    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()}
Beispiel #28
0
    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()}
Beispiel #29
0
    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()}