예제 #1
0
    def btc_sign_msg(
        self, coin: btc.BTCCoin, script_config: btc.BTCScriptConfigWithKeypath, msg: bytes
    ) -> Tuple[bytes, int, bytes]:
        """
        Returns a 64 byte sig, the recoverable id, and a 65 byte signature containing
        the recid, compatible with Electrum.
        """
        # pylint: disable=no-member,line-too-long

        self._require_atleast(semver.VersionInfo(9, 2, 0))

        request = btc.BTCRequest()
        request.sign_message.CopyFrom(
            btc.BTCSignMessageRequest(coin=coin, script_config=script_config, msg=msg)
        )

        supports_antiklepto = self.version >= semver.VersionInfo(9, 5, 0)
        if supports_antiklepto:
            host_nonce = os.urandom(32)

            request.sign_message.host_nonce_commitment.commitment = antiklepto_host_commit(
                host_nonce
            )
            signer_commitment = self._btc_msg_query(
                request, expected_response="antiklepto_signer_commitment"
            ).antiklepto_signer_commitment.commitment

            request = btc.BTCRequest()
            request.antiklepto_signature.CopyFrom(
                antiklepto.AntiKleptoSignatureRequest(host_nonce=host_nonce)
            )

            signature = self._btc_msg_query(
                request, expected_response="sign_message"
            ).sign_message.signature
            antiklepto_verify(host_nonce, signer_commitment, signature[:64])

            if self.debug:
                print(f"Antiklepto nonce verification PASSED")

        else:
            signature = self._btc_msg_query(
                request, expected_response="sign_message"
            ).sign_message.signature

        sig, recid = signature[:64], signature[64]

        # See https://github.com/spesmilo/electrum/blob/84dc181b6e7bb20e88ef6b98fb8925c5f645a765/electrum/ecc.py#L521-L523
        compressed = 4  # BitBox02 uses only compressed pubkeys
        electrum_sig65 = bytes([27 + compressed + recid]) + sig

        return (sig, recid, electrum_sig65)
예제 #2
0
    def btc_register_script_config(
        self, coin: btc.BTCCoin, script_config: btc.BTCScriptConfig, keypath: List[int], name: str
    ) -> None:
        """
        Raises Bitbox02Exception with ERR_USER_ABORT on user abort.
        """
        assert len(name) <= 30

        # pylint: disable=no-member
        request = btc.BTCRequest()
        request.register_script_config.CopyFrom(
            btc.BTCRegisterScriptConfigRequest(
                registration=btc.BTCScriptConfigRegistration(
                    coin=coin, script_config=script_config, keypath=keypath
                ),
                name=name,
            )
        )
        try:
            self._btc_msg_query(request, expected_response="success")
        except Bitbox02Exception as err:
            if err.code == ERR_DUPLICATE_ENTRY:
                raise DuplicateEntryException(
                    "A multisig account configuration with this name already exists.\n"
                    "Choose another name."
                )
            raise
예제 #3
0
    def btc_sign_msg(self, coin: btc.BTCCoin,
                     script_config: btc.BTCScriptConfigWithKeypath,
                     msg: bytes) -> Tuple[bytes, int, bytes]:
        """
        Returns a 64 byte sig, the recoverable id, and a 65 byte signature containing
        the recid, compatible with Electrum.
        """
        # pylint: disable=no-member,line-too-long

        self._require_atleast(semver.VersionInfo(9, 2, 0))

        request = btc.BTCRequest()
        request.sign_message.CopyFrom(
            btc.BTCSignMessageRequest(coin=coin,
                                      script_config=script_config,
                                      msg=msg))
        sig = self._btc_msg_query(
            request, expected_response="sign_message").sign_message.signature

        sig, recid = sig[:64], sig[64]

        # See https://github.com/spesmilo/electrum/blob/84dc181b6e7bb20e88ef6b98fb8925c5f645a765/electrum/ecc.py#L521-L523
        compressed = 4  # BitBox02 uses only compressed pubkeys
        electrum_sig65 = bytes([27 + compressed + recid]) + sig

        return (sig, recid, electrum_sig65)
예제 #4
0
 def btc_is_script_config_registered(self, coin: btc.BTCCoin,
                                     script_config: btc.BTCScriptConfig,
                                     keypath: Sequence[int]) -> bool:
     """
     Returns True if the script config / account is already registered.
     """
     # pylint: disable=no-member
     request = btc.BTCRequest()
     request.is_script_config_registered.CopyFrom(
         btc.BTCIsScriptConfigRegisteredRequest(
             registration=btc.BTCScriptConfigRegistration(
                 coin=coin, script_config=script_config, keypath=keypath)))
     return self._btc_msg_query(
         request, expected_response="is_script_config_registered"
     ).is_script_config_registered.is_registered
예제 #5
0
    def btc_register_script_config(
        self,
        coin: btc.BTCCoin,
        script_config: btc.BTCScriptConfig,
        keypath: Sequence[int],
        name: str,
        xpub_type: btc.BTCRegisterScriptConfigRequest.XPubType = btc.BTCRegisterScriptConfigRequest.XPubType.AUTO_ELECTRUM,
    ) -> None:
        """
        Raises Bitbox02Exception with ERR_USER_ABORT on user abort.
        If name is the empty string, it will be prompted on the device.
        """
        # pylint: disable=no-member,too-many-arguments

        if name == "":
            # prompt on device only available since v9.3.0
            self._require_atleast(semver.VersionInfo(9, 3, 0))

        assert len(name) <= 30

        # pylint: disable=no-member
        request = btc.BTCRequest()
        request.register_script_config.CopyFrom(
            btc.BTCRegisterScriptConfigRequest(
                registration=btc.BTCScriptConfigRegistration(
                    coin=coin, script_config=script_config, keypath=keypath
                ),
                name=name,
                xpub_type=xpub_type,
            )
        )
        try:
            self._btc_msg_query(request, expected_response="success")
        except Bitbox02Exception as err:
            if err.code == ERR_DUPLICATE_ENTRY:
                raise DuplicateEntryException(
                    "A multisig account configuration with this name already exists.\n"
                    "Choose another name."
                )
            raise
예제 #6
0
    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
예제 #7
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