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)
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
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)
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
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
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
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