3232
3333from .secp256k1 import antiklepto_host_commit , antiklepto_verify
3434
35- from ..communication .generated import hww_pb2 as hww
36- from ..communication .generated import eth_pb2 as eth
37- from ..communication .generated import btc_pb2 as btc
38- from ..communication .generated import cardano_pb2 as cardano
39- from ..communication .generated import mnemonic_pb2 as mnemonic
40- from ..communication .generated import bitbox02_system_pb2 as bitbox02_system
41- from ..communication .generated import backup_commands_pb2 as backup
42- from ..communication .generated import common_pb2 as common
43- from ..communication .generated import keystore_pb2 as keystore
44- from ..communication .generated import antiklepto_pb2 as antiklepto
45-
46- # pylint: disable=unused-import
47- # We export it in __init__.py
48- from ..communication .generated import system_pb2 as system
35+ try :
36+ from ..communication .generated import hww_pb2 as hww
37+ from ..communication .generated import eth_pb2 as eth
38+ from ..communication .generated import btc_pb2 as btc
39+ from ..communication .generated import cardano_pb2 as cardano
40+ from ..communication .generated import mnemonic_pb2 as mnemonic
41+ from ..communication .generated import bitbox02_system_pb2 as bitbox02_system
42+ from ..communication .generated import backup_commands_pb2 as backup
43+ from ..communication .generated import common_pb2 as common
44+ from ..communication .generated import keystore_pb2 as keystore
45+ from ..communication .generated import antiklepto_pb2 as antiklepto
46+ import google .protobuf .empty_pb2
47+
48+ # pylint: disable=unused-import
49+ # We export it in __init__.py
50+ from ..communication .generated import system_pb2 as system
51+ except ModuleNotFoundError :
52+ print ("Run `make py` to generate the protobuf messages" )
53+ sys .exit ()
4954
5055try :
5156 # Optional rlp dependency only needed to sign ethereum transactions.
@@ -674,6 +679,40 @@ def electrum_encryption_key(self, keypath: Sequence[int]) -> str:
674679 )
675680 return self ._msg_query (request ).electrum_encryption_key .key
676681
682+ def bip85_bip39 (self ) -> None :
683+ """Invokes the BIP85-BIP39 workflow on the device"""
684+ self ._require_atleast (semver .VersionInfo (9 , 18 , 0 ))
685+
686+ # pylint: disable=no-member
687+ request = hww .Request ()
688+ request .bip85 .CopyFrom (
689+ keystore .BIP85Request (
690+ bip39 = google .protobuf .empty_pb2 .Empty (),
691+ )
692+ )
693+ response = self ._msg_query (request , expected_response = "bip85" ).bip85
694+ assert response .WhichOneof ("app" ) == "bip39"
695+
696+ def bip85_ln (self ) -> bytes :
697+ """
698+ Generates and returns a mnemonic for a hot Lightning wallet from the device using BIP-85.
699+ """
700+ self ._require_atleast (semver .VersionInfo (9 , 17 , 0 ))
701+
702+ # Only account_number=0 is allowed for now.
703+ account_number = 0
704+
705+ # pylint: disable=no-member
706+ request = hww .Request ()
707+ request .bip85 .CopyFrom (
708+ keystore .BIP85Request (
709+ ln = keystore .BIP85Request .AppLn (account_number = account_number ),
710+ )
711+ )
712+ response = self ._msg_query (request , expected_response = "bip85" ).bip85
713+ assert response .WhichOneof ("app" ) == "ln"
714+ return response .ln
715+
677716 def enable_mnemonic_passphrase (self ) -> None :
678717 """
679718 Enable the bip39 passphrase.
@@ -753,28 +792,17 @@ def eth_sign(self, transaction: bytes, keypath: Sequence[int], chain_id: int = 1
753792 """
754793 transaction should be given as a full rlp encoded eth transaction.
755794 """
756- nonce , gas_price , gas_limit , recipient , value , data , _ , _ , _ = rlp .decode (transaction )
757- request = eth .ETHRequest ()
758- # pylint: disable=no-member
759- request .sign .CopyFrom (
760- eth .ETHSignRequest (
761- coin = self ._eth_coin (chain_id ),
762- chain_id = chain_id ,
763- keypath = keypath ,
764- nonce = nonce ,
765- gas_price = gas_price ,
766- gas_limit = gas_limit ,
767- recipient = recipient ,
768- value = value ,
769- data = data ,
770- )
771- )
795+ is_eip1559 = transaction .startswith (b"\x02 " )
772796
773- supports_antiklepto = self .version >= semver .VersionInfo (9 , 5 , 0 )
774- if supports_antiklepto :
797+ def handle_antiklepto (request : eth .ETHRequest ) -> bytes :
775798 host_nonce = os .urandom (32 )
799+ if is_eip1559 :
800+ request .sign_eip1559 .host_nonce_commitment .commitment = antiklepto_host_commit (
801+ host_nonce
802+ )
803+ else :
804+ request .sign .host_nonce_commitment .commitment = antiklepto_host_commit (host_nonce )
776805
777- request .sign .host_nonce_commitment .commitment = antiklepto_host_commit (host_nonce )
778806 signer_commitment = self ._eth_msg_query (
779807 request , expected_response = "antiklepto_signer_commitment"
780808 ).antiklepto_signer_commitment .commitment
@@ -792,6 +820,64 @@ def eth_sign(self, transaction: bytes, keypath: Sequence[int], chain_id: int = 1
792820
793821 return signature
794822
823+ if is_eip1559 :
824+ self ._require_atleast (semver .VersionInfo (9 , 16 , 0 ))
825+ (
826+ decoded_chain_id ,
827+ nonce ,
828+ priority_fee ,
829+ max_fee ,
830+ gas_limit ,
831+ recipient ,
832+ value ,
833+ data ,
834+ _ ,
835+ _ ,
836+ _ ,
837+ ) = rlp .decode (transaction [1 :])
838+ decoded_chain_id_int = int .from_bytes (decoded_chain_id , byteorder = "big" )
839+ if decoded_chain_id_int != chain_id :
840+ raise Exception (
841+ f"chainID argument ({ chain_id } ) does not match chainID encoded in transaction ({ decoded_chain_id_int } )"
842+ )
843+ request = eth .ETHRequest ()
844+ # pylint: disable=no-member
845+ request .sign_eip1559 .CopyFrom (
846+ eth .ETHSignEIP1559Request (
847+ chain_id = chain_id ,
848+ keypath = keypath ,
849+ nonce = nonce ,
850+ max_priority_fee_per_gas = priority_fee ,
851+ max_fee_per_gas = max_fee ,
852+ gas_limit = gas_limit ,
853+ recipient = recipient ,
854+ value = value ,
855+ data = data ,
856+ )
857+ )
858+ return handle_antiklepto (request )
859+
860+ nonce , gas_price , gas_limit , recipient , value , data , _ , _ , _ = rlp .decode (transaction )
861+ request = eth .ETHRequest ()
862+ # pylint: disable=no-member
863+ request .sign .CopyFrom (
864+ eth .ETHSignRequest (
865+ coin = self ._eth_coin (chain_id ),
866+ chain_id = chain_id ,
867+ keypath = keypath ,
868+ nonce = nonce ,
869+ gas_price = gas_price ,
870+ gas_limit = gas_limit ,
871+ recipient = recipient ,
872+ value = value ,
873+ data = data ,
874+ )
875+ )
876+
877+ supports_antiklepto = self .version >= semver .VersionInfo (9 , 5 , 0 )
878+ if supports_antiklepto :
879+ return handle_antiklepto (request )
880+
795881 return self ._eth_msg_query (request , expected_response = "sign" ).sign .signature
796882
797883 def eth_sign_msg (self , msg : bytes , keypath : Sequence [int ], chain_id : int = 1 ) -> bytes :
@@ -945,7 +1031,10 @@ def get_value(
9451031 return value
9461032 if typ .type == eth .ETHSignTypedMessageRequest .DataType .UINT :
9471033 if isinstance (value , str ):
948- value = int (value )
1034+ if value [:2 ].lower () == "0x" :
1035+ value = int (value [2 :], 16 )
1036+ else :
1037+ value = int (value )
9491038 assert isinstance (value , int )
9501039 return value .to_bytes (typ .size , "big" )
9511040 if typ .type == eth .ETHSignTypedMessageRequest .DataType .INT :
0 commit comments