def btc_sign(
        self,
        coin: btc.BTCCoin,
        script_configs: Sequence[btc.BTCScriptConfigWithKeypath],
        inputs: Sequence[BTCInputType],
        outputs: Sequence[BTCOutputType],
        version: int = 1,
        locktime: int = 0,
    ) -> Sequence[Tuple[int, bytes]]:
        """
        coin: the first element of all provided keypaths must match the coin:
        - BTC: 0 + HARDENED
        - Testnets: 1 + HARDENED
        - LTC: 2 + HARDENED
        script_type: type of all inputs and change outputs. The first element of all provided
        keypaths must match this type:
        - SCRIPT_P2PKH: 44 + HARDENED
        - SCRIPT_P2WPKH_P2SH: 49 + HARDENED
        - SCRIPT_P2WPKH: 84 + HARDENED
        bip44_account: Starting at (0 + HARDENED), must be the third element of all provided
        keypaths.
        inputs: transaction inputs.
        outputs: transaction outputs. Can be an external output
        (BTCOutputExternal) or an internal output for change (BTCOutputInternal).
        version, locktime: reserved for future use.
        Returns: list of (input index, signature) tuples.
        Raises Bitbox02Exception with ERR_USER_ABORT on user abort.
        """
        # pylint: disable=no-member,too-many-locals,too-many-branches,too-many-statements

        # Reserved for future use.
        assert version in (1, 2)

        supports_antiklepto = self.version >= semver.VersionInfo(9, 4, 0)

        sigs: List[Tuple[int, bytes]] = []

        # Init request
        request = hww.Request()
        request.btc_sign_init.CopyFrom(
            btc.BTCSignInitRequest(
                coin=coin,
                script_configs=script_configs,
                version=version,
                num_inputs=len(inputs),
                num_outputs=len(outputs),
                locktime=locktime,
            )
        )
        next_response = self._msg_query(request, expected_response="btc_sign_next").btc_sign_next

        is_inputs_pass2 = False
        while True:
            if next_response.type == btc.BTCSignNextResponse.INPUT:
                input_index = next_response.index
                tx_input = inputs[input_index]

                request = hww.Request()
                request.btc_sign_input.CopyFrom(
                    btc.BTCSignInputRequest(
                        prevOutHash=tx_input["prev_out_hash"],
                        prevOutIndex=tx_input["prev_out_index"],
                        prevOutValue=tx_input["prev_out_value"],
                        sequence=tx_input["sequence"],
                        keypath=tx_input["keypath"],
                        script_config_index=tx_input["script_config_index"],
                    )
                )
                if supports_antiklepto and is_inputs_pass2:
                    host_nonce = os.urandom(32)
                    request.btc_sign_input.host_nonce_commitment.commitment = antiklepto_host_commit(
                        host_nonce
                    )

                next_response = self._msg_query(
                    request, expected_response="btc_sign_next"
                ).btc_sign_next

                if supports_antiklepto and is_inputs_pass2:
                    assert next_response.type == btc.BTCSignNextResponse.HOST_NONCE
                    assert next_response.HasField("anti_klepto_signer_commitment")
                    signer_commitment = next_response.anti_klepto_signer_commitment.commitment

                    btc_request = btc.BTCRequest()
                    btc_request.antiklepto_signature.CopyFrom(
                        antiklepto.AntiKleptoSignatureRequest(host_nonce=host_nonce)
                    )
                    next_response = self._btc_msg_query(
                        btc_request, expected_response="sign_next"
                    ).sign_next

                    if self.debug:
                        print(
                            f"For input {input_index}, the host contributed the nonce {host_nonce.hex()}"
                        )

                    assert next_response.has_signature
                    antiklepto_verify(host_nonce, signer_commitment, next_response.signature)

                    if self.debug:
                        print(f"Antiklepto nonce verification PASSED for input {input_index}")

                if is_inputs_pass2:
                    assert next_response.has_signature
                    sigs.append((input_index, next_response.signature))

                if input_index == len(inputs) - 1:
                    is_inputs_pass2 = True

            elif next_response.type == btc.BTCSignNextResponse.PREVTX_INIT:
                prevtx = inputs[next_response.index]["prev_tx"]
                btc_request = btc.BTCRequest()
                btc_request.prevtx_init.CopyFrom(
                    btc.BTCPrevTxInitRequest(
                        version=prevtx["version"],
                        num_inputs=len(prevtx["inputs"]),
                        num_outputs=len(prevtx["outputs"]),
                        locktime=prevtx["locktime"],
                    )
                )
                next_response = self._btc_msg_query(
                    btc_request, expected_response="sign_next"
                ).sign_next
            elif next_response.type == btc.BTCSignNextResponse.PREVTX_INPUT:
                prevtx_input = inputs[next_response.index]["prev_tx"]["inputs"][
                    next_response.prev_index
                ]
                btc_request = btc.BTCRequest()
                btc_request.prevtx_input.CopyFrom(
                    btc.BTCPrevTxInputRequest(
                        prev_out_hash=prevtx_input["prev_out_hash"],
                        prev_out_index=prevtx_input["prev_out_index"],
                        signature_script=prevtx_input["signature_script"],
                        sequence=prevtx_input["sequence"],
                    )
                )
                next_response = self._btc_msg_query(
                    btc_request, expected_response="sign_next"
                ).sign_next
            elif next_response.type == btc.BTCSignNextResponse.PREVTX_OUTPUT:
                prevtx_output = inputs[next_response.index]["prev_tx"]["outputs"][
                    next_response.prev_index
                ]
                btc_request = btc.BTCRequest()
                btc_request.prevtx_output.CopyFrom(
                    btc.BTCPrevTxOutputRequest(
                        value=prevtx_output["value"], pubkey_script=prevtx_output["pubkey_script"]
                    )
                )
                next_response = self._btc_msg_query(
                    btc_request, expected_response="sign_next"
                ).sign_next
            elif next_response.type == btc.BTCSignNextResponse.OUTPUT:
                output_index = next_response.index
                tx_output = outputs[output_index]

                request = hww.Request()
                if isinstance(tx_output, BTCOutputInternal):
                    request.btc_sign_output.CopyFrom(
                        btc.BTCSignOutputRequest(
                            ours=True,
                            value=tx_output.value,
                            keypath=tx_output.keypath,
                            script_config_index=tx_output.script_config_index,
                        )
                    )
                elif isinstance(tx_output, BTCOutputExternal):
                    request.btc_sign_output.CopyFrom(
                        btc.BTCSignOutputRequest(
                            ours=False,
                            type=tx_output.type,
                            hash=tx_output.hash,
                            value=tx_output.value,
                        )
                    )
                next_response = self._msg_query(
                    request, expected_response="btc_sign_next"
                ).btc_sign_next
            elif next_response.type == btc.BTCSignNextResponse.DONE:
                break
            else:
                raise Exception("unexpected response")
        return sigs
Example #2
0
    def btc_sign(
        self,
        coin: btc.BTCCoin,
        script_configs: List[btc.BTCScriptConfigWithKeypath],
        inputs: List[BTCInputType],
        outputs: List[BTCOutputType],
        version: int = 1,
        locktime: int = 0,
    ) -> List[Tuple[int, bytes]]:
        """
        coin: the first element of all provided keypaths must match the coin:
        - BTC: 0 + HARDENED
        - Testnets: 1 + HARDENED
        - LTC: 2 + HARDENED
        script_type: type of all inputs and change outputs. The first element of all provided
        keypaths must match this type:
        - SCRIPT_P2PKH: 44 + HARDENED
        - SCRIPT_P2WPKH_P2SH: 49 + HARDENED
        - SCRIPT_P2WPKH: 84 + HARDENED
        bip44_account: Starting at (0 + HARDENED), must be the third element of all provided
        keypaths.
        inputs: transaction inputs.
        outputs: transaction outputs. Can be an external output
        (BTCOutputExternal) or an internal output for change (BTCOutputInternal).
        version, locktime: reserved for future use.
        Returns: list of (input index, signature) tuples.
        Raises Bitbox02Exception with ERR_USER_ABORT on user abort.
        """
        # pylint: disable=too-many-locals,no-member

        # Reserved for future use.
        assert version in (1, 2)

        sigs: List[Tuple[int, bytes]] = []

        # Init request
        request = hww.Request()
        request.btc_sign_init.CopyFrom(
            btc.BTCSignInitRequest(
                coin=coin,
                script_configs=script_configs,
                version=version,
                num_inputs=len(inputs),
                num_outputs=len(outputs),
                locktime=locktime,
            ))
        next_response = self._msg_query(
            request, expected_response="btc_sign_next").btc_sign_next
        while True:
            if next_response.type == btc.BTCSignNextResponse.INPUT:
                input_index = next_response.index
                tx_input = inputs[input_index]

                request = hww.Request()
                request.btc_sign_input.CopyFrom(
                    btc.BTCSignInputRequest(
                        prevOutHash=tx_input["prev_out_hash"],
                        prevOutIndex=tx_input["prev_out_index"],
                        prevOutValue=tx_input["prev_out_value"],
                        sequence=tx_input["sequence"],
                        keypath=tx_input["keypath"],
                        script_config_index=tx_input["script_config_index"],
                    ))
                next_response = self._msg_query(
                    request, expected_response="btc_sign_next").btc_sign_next
                if next_response.has_signature:
                    sigs.append((input_index, next_response.signature))
            elif next_response.type == btc.BTCSignNextResponse.PREVTX_INIT:
                prevtx = inputs[next_response.index]["prev_tx"]
                btc_request = btc.BTCRequest()
                btc_request.prevtx_init.CopyFrom(
                    btc.BTCPrevTxInitRequest(
                        version=prevtx["version"],
                        num_inputs=len(prevtx["inputs"]),
                        num_outputs=len(prevtx["outputs"]),
                        locktime=prevtx["locktime"],
                    ))
                next_response = self._btc_msg_query(
                    btc_request, expected_response="sign_next").sign_next
            elif next_response.type == btc.BTCSignNextResponse.PREVTX_INPUT:
                prevtx_input = inputs[next_response.index]["prev_tx"][
                    "inputs"][next_response.prev_index]
                btc_request = btc.BTCRequest()
                btc_request.prevtx_input.CopyFrom(
                    btc.BTCPrevTxInputRequest(
                        prev_out_hash=prevtx_input["prev_out_hash"],
                        prev_out_index=prevtx_input["prev_out_index"],
                        signature_script=prevtx_input["signature_script"],
                        sequence=prevtx_input["sequence"],
                    ))
                next_response = self._btc_msg_query(
                    btc_request, expected_response="sign_next").sign_next
            elif next_response.type == btc.BTCSignNextResponse.PREVTX_OUTPUT:
                prevtx_output = inputs[next_response.index]["prev_tx"][
                    "outputs"][next_response.prev_index]
                btc_request = btc.BTCRequest()
                btc_request.prevtx_output.CopyFrom(
                    btc.BTCPrevTxOutputRequest(
                        value=prevtx_output["value"],
                        pubkey_script=prevtx_output["pubkey_script"]))
                next_response = self._btc_msg_query(
                    btc_request, expected_response="sign_next").sign_next
            elif next_response.type == btc.BTCSignNextResponse.OUTPUT:
                output_index = next_response.index
                tx_output = outputs[output_index]

                request = hww.Request()
                if isinstance(tx_output, BTCOutputInternal):
                    request.btc_sign_output.CopyFrom(
                        btc.BTCSignOutputRequest(
                            ours=True,
                            value=tx_output.value,
                            keypath=tx_output.keypath,
                            script_config_index=tx_output.script_config_index,
                        ))
                elif isinstance(tx_output, BTCOutputExternal):
                    request.btc_sign_output.CopyFrom(
                        btc.BTCSignOutputRequest(
                            ours=False,
                            type=tx_output.type,
                            hash=tx_output.hash,
                            value=tx_output.value,
                        ))
                next_response = self._msg_query(
                    request, expected_response="btc_sign_next").btc_sign_next
            elif next_response.type == btc.BTCSignNextResponse.DONE:
                break
            else:
                raise Exception("unexpected response")
        return sigs