4343 from ..communication .generated import common_pb2 as common
4444 from ..communication .generated import keystore_pb2 as keystore
4545 from ..communication .generated import antiklepto_pb2 as antiklepto
46+ from ..communication .generated import bluetooth_pb2 as bluetooth
4647 import google .protobuf .empty_pb2
4748
48- # pylint: disable=unused-import
49+ # pylint: disable=unused-import,ungrouped-imports
4950 # We export it in __init__.py
5051 from ..communication .generated import system_pb2 as system
5152except ModuleNotFoundError :
@@ -116,18 +117,31 @@ class BTCInputType(TypedDict):
116117
117118class BTCOutputInternal :
118119 # TODO: Use NamedTuple, but not playing well with protobuf types.
120+ """
121+ Internal transaction output (output belongs to BitBox).
122+ """
119123
120- def __init__ (self , keypath : Sequence [int ], value : int , script_config_index : int ):
124+ def __init__ (
125+ self ,
126+ keypath : Sequence [int ],
127+ value : int ,
128+ script_config_index : int ,
129+ output_script_config_index : Optional [int ] = None ,
130+ ):
121131 """
122132 keypath: keypath to the change output.
123133 """
124134 self .keypath = keypath
125135 self .value = value
126136 self .script_config_index = script_config_index
137+ self .output_script_config_index = output_script_config_index
127138
128139
129140class BTCOutputExternal :
130141 # TODO: Use NamedTuple, but not playing well with protobuf types.
142+ """
143+ External transaction output.
144+ """
131145
132146 def __init__ (self , output_type : "btc.BTCOutputType.V" , output_payload : bytes , value : int ):
133147 self .type = output_type
@@ -161,6 +175,14 @@ def device_info(self) -> Dict[str, Any]:
161175 }
162176 if self .version >= semver .VersionInfo (9 , 6 , 0 ):
163177 result ["securechip_model" ] = response .device_info .securechip_model
178+ if response .device_info .bluetooth is not None :
179+ result ["bluetooth" ] = {
180+ "firmware_hash" : response .device_info .bluetooth .firmware_hash ,
181+ "firmware_version" : response .device_info .bluetooth .firmware_version ,
182+ "enabled" : response .device_info .bluetooth .enabled ,
183+ }
184+ else :
185+ result ["bluetooth" ] = None
164186
165187 return result
166188
@@ -390,13 +412,14 @@ def btc_sign(
390412 version : int = 1 ,
391413 locktime : int = 0 ,
392414 format_unit : "btc.BTCSignInitRequest.FormatUnit.V" = btc .BTCSignInitRequest .FormatUnit .DEFAULT ,
415+ output_script_configs : Optional [Sequence [btc .BTCScriptConfigWithKeypath ]] = None ,
393416 ) -> Sequence [Tuple [int , bytes ]]:
394417 """
395418 coin: the first element of all provided keypaths must match the coin:
396419 - BTC: 0 + HARDENED
397420 - Testnets: 1 + HARDENED
398421 - LTC: 2 + HARDENED
399- script_configs: types of all inputs and change outputs . The first element of all provided
422+ script_configs: types of all inputs and outputs belonging to the same account ( change or non-change) . The first element of all provided
400423 keypaths must match this type:
401424 - SCRIPT_P2PKH: 44 + HARDENED
402425 - SCRIPT_P2WPKH_P2SH: 49 + HARDENED
@@ -407,6 +430,8 @@ def btc_sign(
407430 outputs: transaction outputs. Can be an external output
408431 (BTCOutputExternal) or an internal output for change (BTCOutputInternal).
409432 version, locktime: reserved for future use.
433+ format_unit: defines in which unit amounts will be displayed
434+ output_script_configs: script types for outputs belonging to the same keystore
410435 Returns: list of (input index, signature) tuples.
411436 Raises Bitbox02Exception with ERR_USER_ABORT on user abort.
412437 """
@@ -418,6 +443,10 @@ def btc_sign(
418443 if any (map (is_taproot , script_configs )):
419444 self ._require_atleast (semver .VersionInfo (9 , 10 , 0 ))
420445
446+ if output_script_configs :
447+ # Attaching output info supported since v9.22.0.
448+ self ._require_atleast (semver .VersionInfo (9 , 22 , 0 ))
449+
421450 supports_antiklepto = self .version >= semver .VersionInfo (9 , 4 , 0 )
422451
423452 sigs : List [Tuple [int , bytes ]] = []
@@ -433,6 +462,7 @@ def btc_sign(
433462 num_outputs = len (outputs ),
434463 locktime = locktime ,
435464 format_unit = format_unit ,
465+ output_script_configs = output_script_configs ,
436466 )
437467 )
438468 next_response = self ._msg_query (request , expected_response = "btc_sign_next" ).btc_sign_next
@@ -552,12 +582,16 @@ def btc_sign(
552582
553583 request = hww .Request ()
554584 if isinstance (tx_output , BTCOutputInternal ):
585+ if tx_output .output_script_config_index is not None :
586+ # Attaching output info supported since v9.22.0.
587+ self ._require_atleast (semver .VersionInfo (9 , 22 , 0 ))
555588 request .btc_sign_output .CopyFrom (
556589 btc .BTCSignOutputRequest (
557590 ours = True ,
558591 value = tx_output .value ,
559592 keypath = tx_output .keypath ,
560593 script_config_index = tx_output .script_config_index ,
594+ output_script_config_index = tx_output .output_script_config_index ,
561595 )
562596 )
563597 elif isinstance (tx_output , BTCOutputExternal ):
@@ -588,6 +622,8 @@ def btc_sign_msg(
588622 # pylint: disable=no-member
589623
590624 self ._require_atleast (semver .VersionInfo (9 , 2 , 0 ))
625+ if coin in (btc .TBTC , btc .RBTC ):
626+ self ._require_atleast (semver .VersionInfo (9 , 23 , 0 ))
591627
592628 request = btc .BTCRequest ()
593629 request .sign_message .CopyFrom (
@@ -648,16 +684,6 @@ def insert_sdcard(self) -> None:
648684 )
649685 self ._msg_query (request , expected_response = "success" )
650686
651- def remove_sdcard (self ) -> None :
652- # pylint: disable=no-member
653- request = hww .Request ()
654- request .insert_remove_sdcard .CopyFrom (
655- bitbox02_system .InsertRemoveSDCardRequest (
656- action = bitbox02_system .InsertRemoveSDCardRequest .REMOVE_CARD
657- )
658- )
659- self ._msg_query (request , expected_response = "success" )
660-
661687 def root_fingerprint (self ) -> bytes :
662688 """
663689 Get the root fingerprint from the bitbox02
@@ -788,10 +814,18 @@ def eth_pub(
788814 )
789815 return self ._eth_msg_query (request , expected_response = "pub" ).pub .pub
790816
791- def eth_sign (self , transaction : bytes , keypath : Sequence [int ], chain_id : int = 1 ) -> bytes :
817+ def eth_sign (
818+ self ,
819+ transaction : bytes ,
820+ keypath : Sequence [int ],
821+ address_case : eth .ETHAddressCase .ValueType = eth .ETH_ADDRESS_CASE_MIXED ,
822+ chain_id : int = 1 ,
823+ ) -> bytes :
792824 """
793825 transaction should be given as a full rlp encoded eth transaction.
794826 """
827+ # pylint: disable=no-member
828+
795829 is_eip1559 = transaction .startswith (b"\x02 " )
796830
797831 def handle_antiklepto (request : eth .ETHRequest ) -> bytes :
@@ -853,6 +887,7 @@ def handle_antiklepto(request: eth.ETHRequest) -> bytes:
853887 recipient = recipient ,
854888 value = value ,
855889 data = data ,
890+ address_case = address_case ,
856891 )
857892 )
858893 return handle_antiklepto (request )
@@ -871,6 +906,7 @@ def handle_antiklepto(request: eth.ETHRequest) -> bytes:
871906 recipient = recipient ,
872907 value = value ,
873908 data = data ,
909+ address_case = address_case ,
874910 )
875911 )
876912
@@ -1176,7 +1212,69 @@ def cardano_address(self, address: cardano.CardanoAddressRequest) -> str:
11761212 def cardano_sign_transaction (
11771213 self , transaction : cardano .CardanoSignTransactionRequest
11781214 ) -> cardano .CardanoSignTransactionResponse :
1215+ if transaction .tag_cbor_sets :
1216+ self ._require_atleast (semver .VersionInfo (9 , 22 , 0 ))
11791217 request = cardano .CardanoRequest (sign_transaction = transaction )
11801218 return self ._cardano_msg_query (
11811219 request , expected_response = "sign_transaction"
11821220 ).sign_transaction
1221+
1222+ def _bluetooth_msg_query (
1223+ self , bluetooth_request : bluetooth .BluetoothRequest , expected_response : Optional [str ] = None
1224+ ) -> bluetooth .BluetoothResponse :
1225+ """
1226+ Same as _msg_query, but one nesting deeper for bluetooth messages.
1227+ """
1228+ # pylint: disable=no-member
1229+ request = hww .Request ()
1230+ request .bluetooth .CopyFrom (bluetooth_request )
1231+ bluetooth_response = self ._msg_query (request , expected_response = "bluetooth" ).bluetooth
1232+ if (
1233+ expected_response is not None
1234+ and bluetooth_response .WhichOneof ("response" ) != expected_response
1235+ ):
1236+ raise Exception (
1237+ "Unexpected response: {}, expected: {}" .format (
1238+ bluetooth_response .WhichOneof ("response" ), expected_response
1239+ )
1240+ )
1241+ return bluetooth_response
1242+
1243+ def bluetooth_upgrade (self , firmware : bytes ) -> None :
1244+ """
1245+ Install the given Bluetooth firmware.
1246+ """
1247+ # pylint: disable=no-member
1248+ request = bluetooth .BluetoothRequest ()
1249+ request .upgrade_init .CopyFrom (
1250+ bluetooth .BluetoothUpgradeInitRequest (firmware_length = len (firmware ))
1251+ )
1252+
1253+ response = self ._bluetooth_msg_query (request )
1254+ while True :
1255+ response_type = response .WhichOneof ("response" )
1256+ if response_type == "request_chunk" :
1257+ chunk_response = response .request_chunk
1258+ request = bluetooth .BluetoothRequest ()
1259+ request .chunk .CopyFrom (
1260+ bluetooth .BluetoothChunkRequest (
1261+ data = firmware [
1262+ chunk_response .offset : chunk_response .offset + chunk_response .length
1263+ ]
1264+ ),
1265+ )
1266+ response = self ._bluetooth_msg_query (request )
1267+ elif response_type == "success" :
1268+ break
1269+ else :
1270+ raise Exception (f"Unexpected response: f{ response_type } " )
1271+
1272+ def bluetooth_toggle_enabled (self ) -> None :
1273+ """
1274+ Enable/disable Bluetooth in non-volatile storage
1275+ """
1276+ # pylint: disable=no-member
1277+ request = bluetooth .BluetoothRequest ()
1278+ request .toggle_enabled .CopyFrom (bluetooth .BluetoothToggleEnabledRequest ())
1279+
1280+ self ._bluetooth_msg_query (request , expected_response = "success" )
0 commit comments