diff --git a/client/src/ledger_app_clients/ethereum/client.py b/client/src/ledger_app_clients/ethereum/client.py index 36dad1e0fd..75434f7d8a 100644 --- a/client/src/ledger_app_clients/ethereum/client.py +++ b/client/src/ledger_app_clients/ethereum/client.py @@ -57,12 +57,17 @@ class FieldTag(IntEnum): ADDRESS = 0x22 CHAIN_ID = 0x23 TICKER = 0x24 + TX_HASH = 0x27 BLOCKCHAIN_FAMILY = 0x51 NETWORK_NAME = 0x52 NETWORK_ICON_HASH = 0x53 TRUSTED_NAME_TYPE = 0x70 TRUSTED_NAME_SOURCE = 0x71 TRUSTED_NAME_NFT_ID = 0x72 + W3C_NORMALIZED_RISK = 0x80 + W3C_NORMALIZED_CATEGORY = 0x81 + W3C_PROVIDER_MSG = 0x82 + W3C_TINY_URL = 0x83 class PKIPubKeyUsage(IntEnum): @@ -503,8 +508,7 @@ def _prepare_network_info(self, # Network Icon payload += format_tlv(FieldTag.NETWORK_ICON_HASH, sha256(icon).digest()) # Append the data Signature - payload += format_tlv(FieldTag.DER_SIGNATURE, - sign_data(Key.CAL, payload)) + payload += format_tlv(FieldTag.DER_SIGNATURE, sign_data(Key.CAL, payload)) return payload def provide_network_information(self, @@ -540,3 +544,57 @@ def provide_network_information(self, assert response.status == StatusWord.OK response = self._exchange(chunks[-1]) assert response.status == StatusWord.OK + + def _prepare_tx_simulation(self, + tx_hash: bytes, + risk: int, + category: int, + message: str, + url: str, + chain_id: int) -> bytes: + + # Construct the TLV payload + payload: bytes = format_tlv(FieldTag.STRUCT_TYPE, 9) + payload += format_tlv(FieldTag.STRUCT_VERSION, 1) + payload += format_tlv(FieldTag.CHAIN_ID, chain_id.to_bytes(8, 'big')) + payload += format_tlv(FieldTag.TX_HASH, tx_hash) + payload += format_tlv(FieldTag.W3C_NORMALIZED_RISK, risk.to_bytes(2, 'big')) + payload += format_tlv(FieldTag.W3C_NORMALIZED_CATEGORY, category) + payload += format_tlv(FieldTag.W3C_PROVIDER_MSG, message.encode('utf-8')) + payload += format_tlv(FieldTag.W3C_TINY_URL, url.encode('utf-8')) + # Append the data Signature + payload += format_tlv(FieldTag.DER_SIGNATURE, sign_data(Key.CAL, payload)) + return payload + + def provide_tx_simulation(self, + tx_hash: bytes, + risk: int, + category: int, + message: str, + url: str, + chain_id: int, + demo: bool) -> RAPDU: + + if self._pki_client is None: + print(f"Ledger-PKI Not supported on '{self._firmware.name}'") + else: + # pylint: disable=line-too-long + if self._firmware == Firmware.NANOSP: + cert_apdu = "01010102010211040000000212010013020002140101160400000000200B45524332305F546F6B656E300200063101083201213321024CCA8FAD496AA5040A00A7EB2F5CC3B85376D88BA147A7D7054A99C64056188734010135010310040102000015473045022100C15795C2AE41E6FAE6B1362EE1AE216428507D7C1D6939B928559CC7A1F6425C02206139CF2E133DD62F3E00F183E42109C9853AC62B6B70C5079B9A80DBB9D54AB5" # noqa: E501 + elif self._firmware == Firmware.NANOX: + cert_apdu = "01010102010211040000000212010013020002140101160400000000200B45524332305F546F6B656E300200063101083201213321024CCA8FAD496AA5040A00A7EB2F5CC3B85376D88BA147A7D7054A99C64056188734010135010215473045022100E3B956F93FBFF0D41908483888F0F75D4714662A692F7A38DC6C41A13294F9370220471991BECB3CA4F43413CADC8FF738A8CC03568BFA832B4DCFE8C469080984E5" # noqa: E501 + elif self._firmware == Firmware.STAX: + cert_apdu = "01010102010211040000000212010013020002140101160400000000200B45524332305F546F6B656E300200063101083201213321024CCA8FAD496AA5040A00A7EB2F5CC3B85376D88BA147A7D7054A99C6405618873401013501041546304402206731FCD3E2432C5CA162381392FD17AD3A41EEF852E1D706F21A656AB165263602204B89FAE8DBAF191E2D79FB00EBA80D613CB7EDF0BE960CB6F6B29D96E1437F5F" # noqa: E501 + elif self._firmware == Firmware.FLEX: + cert_apdu = "01010102010211040000000212010013020002140101160400000000200B45524332305F546F6B656E300200063101083201213321024CCA8FAD496AA5040A00A7EB2F5CC3B85376D88BA147A7D7054A99C64056188734010135010515473045022100B59EA8B958AA40578A6FBE9BBFB761020ACD5DBD8AA863C11DA17F42B2AFDE790220186316059EFA58811337D47C7F815F772EA42BBBCEA4AE123D1118C80588F5CB" # noqa: E501 + else: + print(f"Invalid device '{self._firmware.name}'") + cert_apdu = "" + # pylint: enable=line-too-long + if cert_apdu: + self._pki_client.send_certificate(PKIPubKeyUsage.PUBKEY_USAGE_COIN_META, bytes.fromhex(cert_apdu)) + + # Add the network info + payload = self._prepare_tx_simulation(tx_hash, risk, category, message, url, chain_id) + response = self._exchange(self._cmd_builder.provide_tx_simulation(demo, payload)) + return response diff --git a/client/src/ledger_app_clients/ethereum/command_builder.py b/client/src/ledger_app_clients/ethereum/command_builder.py index f918ce0f8f..fd0865cc66 100644 --- a/client/src/ledger_app_clients/ethereum/command_builder.py +++ b/client/src/ledger_app_clients/ethereum/command_builder.py @@ -26,6 +26,7 @@ class InsType(IntEnum): PROVIDE_TRUSTED_NAME = 0x22 EXTERNAL_PLUGIN_SETUP = 0x12 PROVIDE_NETWORK_INFORMATION = 0x30 + PROVIDE_TX_SIMULATION = 0x32 class P1Type(IntEnum): @@ -35,6 +36,8 @@ class P1Type(IntEnum): SIGN_SUBSQT_CHUNK = 0x80 FIRST_CHUNK = 0x01 FOLLOWING_CHUNK = 0x00 + TX_SIMU_NORMAL = 0x00 + TX_SIMU_DEMO = 0x01 class P2Type(IntEnum): @@ -427,3 +430,10 @@ def provide_network_information(self, icon = icon[0xff:] p1 = P1Type.FOLLOWING_CHUNK return chunks + + def provide_tx_simulation(self, demo: bool, tlv_payload: bytes) -> bytes: + # Check if the TLV payload is larger than 0xff + assert len(tlv_payload) < 0xff, "Payload too large" + # Serialize the payload + p1 = P1Type.TX_SIMU_DEMO if demo else P1Type.TX_SIMU_NORMAL + return self._serialize(InsType.PROVIDE_TX_SIMULATION, p1, 0x00, tlv_payload) diff --git a/client/src/ledger_app_clients/ethereum/settings.py b/client/src/ledger_app_clients/ethereum/settings.py index 10a745ce0a..a039f8f7aa 100644 --- a/client/src/ledger_app_clients/ethereum/settings.py +++ b/client/src/ledger_app_clients/ethereum/settings.py @@ -10,6 +10,7 @@ class SettingID(Enum): NONCE = auto() VERBOSE_EIP712 = auto() DEBUG_DATA = auto() + W3C = auto() def get_device_settings(firmware: Firmware) -> list[SettingID]: @@ -19,12 +20,21 @@ def get_device_settings(firmware: Firmware) -> list[SettingID]: SettingID.NONCE, SettingID.DEBUG_DATA, ] + if firmware.is_nano: + return [ + SettingID.BLIND_SIGNING, + SettingID.VERBOSE_ENS, + SettingID.NONCE, + SettingID.VERBOSE_EIP712, + SettingID.DEBUG_DATA, + ] return [ SettingID.BLIND_SIGNING, SettingID.VERBOSE_ENS, SettingID.NONCE, SettingID.VERBOSE_EIP712, SettingID.DEBUG_DATA, + SettingID.W3C, ] @@ -67,6 +77,10 @@ def settings_toggle(firmware: Firmware, nav: Navigator, to_toggle: list[SettingI if (setting_idx > 0) and (setting_idx % settings_per_page) == 0: moves += [NavInsID.USE_CASE_SETTINGS_NEXT] if setting in to_toggle: - moves += [NavIns(NavInsID.TOUCH, get_setting_position(firmware, setting_idx, settings_per_page))] + if setting == SettingID.W3C and firmware == Firmware.STAX: + moves += [NavInsID.USE_CASE_SETTINGS_NEXT] + moves += [NavIns(NavInsID.TOUCH, get_setting_position(firmware, 0, settings_per_page))] + else: + moves += [NavIns(NavInsID.TOUCH, get_setting_position(firmware, setting_idx, settings_per_page))] moves += [NavInsID.USE_CASE_SETTINGS_MULTI_PAGE_EXIT] nav.navigate(moves, screen_change_before_first_instruction=False)