def eth_sign_msg( self, msg: bytes, keypath: Sequence[int], coin: eth.ETHCoin = eth.ETH ) -> bytes: """ Signs message, the msg will be prefixed with "\x19Ethereum message\n" + len(msg) in the hardware """ request = eth.ETHRequest() # pylint: disable=no-member request.sign_msg.CopyFrom(eth.ETHSignMessageRequest(coin=coin, keypath=keypath, msg=msg)) supports_antiklepto = self.version >= semver.VersionInfo(9, 5, 0) if supports_antiklepto: host_nonce = os.urandom(32) request.sign_msg.host_nonce_commitment.commitment = antiklepto_host_commit(host_nonce) signer_commitment = self._eth_msg_query( request, expected_response="antiklepto_signer_commitment" ).antiklepto_signer_commitment.commitment request = eth.ETHRequest() request.antiklepto_signature.CopyFrom( antiklepto.AntiKleptoSignatureRequest(host_nonce=host_nonce) ) signature = self._eth_msg_query(request, expected_response="sign").sign.signature antiklepto_verify(host_nonce, signer_commitment, signature[:64]) if self.debug: print(f"Antiklepto nonce verification PASSED") return signature return self._eth_msg_query(request, expected_response="sign").sign.signature
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 eth_sign_msg(self, msg: bytes, keypath: Sequence[int], chain_id: int = 1) -> bytes: """ Signs message, the msg will be prefixed with "\x19Ethereum message\n" + len(msg) in the hardware. 27 is added to the recID to denote an uncompressed pubkey. """ def format_as_uncompressed(sig: bytes) -> bytes: # 27 is the magic constant to add to the recoverable ID to denote an uncompressed # pubkey. modified_signature = list(sig) modified_signature[64] += 27 return bytes(modified_signature) request = eth.ETHRequest() # pylint: disable=no-member request.sign_msg.CopyFrom( eth.ETHSignMessageRequest(coin=self._eth_coin(chain_id), chain_id=chain_id, keypath=keypath, msg=msg)) supports_antiklepto = self.version >= semver.VersionInfo(9, 5, 0) if supports_antiklepto: host_nonce = os.urandom(32) request.sign_msg.host_nonce_commitment.commitment = antiklepto_host_commit( host_nonce) signer_commitment = self._eth_msg_query( request, expected_response="antiklepto_signer_commitment" ).antiklepto_signer_commitment.commitment request = eth.ETHRequest() request.antiklepto_signature.CopyFrom( antiklepto.AntiKleptoSignatureRequest(host_nonce=host_nonce)) signature = self._eth_msg_query( request, expected_response="sign").sign.signature antiklepto_verify(host_nonce, signer_commitment, signature[:64]) if self.debug: print("Antiklepto nonce verification PASSED") return format_as_uncompressed(signature) signature = self._eth_msg_query( request, expected_response="sign").sign.signature return format_as_uncompressed(signature)
def eth_sign(self, transaction: bytes, keypath: Sequence[int], chain_id: int = 1) -> bytes: """ transaction should be given as a full rlp encoded eth transaction. """ nonce, gas_price, gas_limit, recipient, value, data, _, _, _ = rlp.decode( transaction) request = eth.ETHRequest() # pylint: disable=no-member request.sign.CopyFrom( eth.ETHSignRequest( coin=self._eth_coin(chain_id), chain_id=chain_id, keypath=keypath, nonce=nonce, gas_price=gas_price, gas_limit=gas_limit, recipient=recipient, value=value, data=data, )) supports_antiklepto = self.version >= semver.VersionInfo(9, 5, 0) if supports_antiklepto: host_nonce = os.urandom(32) request.sign.host_nonce_commitment.commitment = antiklepto_host_commit( host_nonce) signer_commitment = self._eth_msg_query( request, expected_response="antiklepto_signer_commitment" ).antiklepto_signer_commitment.commitment request = eth.ETHRequest() request.antiklepto_signature.CopyFrom( antiklepto.AntiKleptoSignatureRequest(host_nonce=host_nonce)) signature = self._eth_msg_query( request, expected_response="sign").sign.signature antiklepto_verify(host_nonce, signer_commitment, signature[:64]) if self.debug: print("Antiklepto nonce verification PASSED") return signature return self._eth_msg_query(request, expected_response="sign").sign.signature
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