diff --git a/doc/ethapp.adoc b/doc/ethapp.adoc index 7a30e33..758e761 100644 --- a/doc/ethapp.adoc +++ b/doc/ethapp.adoc @@ -810,22 +810,21 @@ Full filtering is disabled by default and has to be changed with this APDU (defa Field substitution will be ignored if the full filtering is not activated. -If activated, fields will be by default hidden unless they receive a field name substitution. +This command should come before the domain & message implementations. If activated, fields will be by default hidden unless they receive a field name substitution. -##### Contract name substitution +##### Message info -Name substitution commands should come right after the contract address from the domain has been sent with a *SEND STRUCT IMPLEMENTATION*. -Perfect moment to do it is when the domain implementation has been sent, just before sending the message implementation. +This command should come right after the implementation of the domain has been sent with *SEND STRUCT IMPLEMENTATION*, just before sending the message implementation. The first byte is used so that a signature of one type cannot be valid as another type. The signature is computed on : -183 || chain ID (BE) || contract address || schema hash || display name +183 || chain ID (BE) || contract address || schema hash || filters count || display name -##### Field name substitution +##### Show field -Name substitution commands should come before the corresponding *SEND STRUCT IMPLEMENTATION* and are only usable for message fields (and not domain ones). +These commands should come before the corresponding *SEND STRUCT IMPLEMENTATION* and are only usable for message fields (and not domain ones). The first byte is used so that a signature of one type cannot be valid as another type. The signature is computed on : @@ -842,19 +841,31 @@ _Command_ | E0 | 1E | 00 | 00 : activate - 0F : contract name + 0F : message info - FF : field name + FF : show field | variable | variable |========================================================================= _Input data_ -##### If P1 == activate +##### If P2 == activate None -##### If P1 == contract name OR P1 == field name +##### If P2 == message info + +[width="80%"] +|========================================== +| *Description* | *Length (byte)* +| Display name length | 1 +| Display name | variable +| Filters count | 1 +| Signature length | 1 +| Signature | variable +|========================================== + +##### If P2 == show field [width="80%"] |========================================== diff --git a/src/apdu_constants.h b/src/apdu_constants.h index 55ff4c9..234294e 100644 --- a/src/apdu_constants.h +++ b/src/apdu_constants.h @@ -41,7 +41,7 @@ #define APDU_RESPONSE_INVALID_INS 0x6d00 #define APDU_RESPONSE_INVALID_P1_P2 0x6b00 #define APDU_RESPONSE_CONDITION_NOT_SATISFIED 0x6985 -#define APDU_RESPONSE_REF_DATA_NOT_USABLE 0x6a88 +#define APDU_RESPONSE_REF_DATA_NOT_FOUND 0x6a88 #ifdef HAVE_STARKWARE diff --git a/src_features/signMessageEIP712/commands_712.c b/src_features/signMessageEIP712/commands_712.c index b51f519..fe2fb03 100644 --- a/src_features/signMessageEIP712/commands_712.c +++ b/src_features/signMessageEIP712/commands_712.c @@ -139,14 +139,15 @@ bool handle_eip712_filtering(const uint8_t *const apdu_buf) { ret = compute_schema_hash(); } break; - case P2_FILT_CONTRACT_NAME: - case P2_FILT_FIELD_NAME: - type = (apdu_buf[OFFSET_P2] == P2_FILT_CONTRACT_NAME) ? FILTERING_CONTRACT_NAME - : FILTERING_STRUCT_FIELD; + case P2_FILT_MESSAGE_INFO: + case P2_FILT_SHOW_FIELD: + type = (apdu_buf[OFFSET_P2] == P2_FILT_MESSAGE_INFO) + ? FILTERING_PROVIDE_MESSAGE_INFO + : FILTERING_SHOW_FIELD; if (ui_712_get_filtering_mode() == EIP712_FILTERING_FULL) { ret = provide_filtering_info(&apdu_buf[OFFSET_CDATA], apdu_buf[OFFSET_LC], type); - if ((apdu_buf[OFFSET_P2] == P2_FILT_CONTRACT_NAME) && ret) { + if ((apdu_buf[OFFSET_P2] == P2_FILT_MESSAGE_INFO) && ret) { reply_apdu = false; } } @@ -177,6 +178,10 @@ bool handle_eip712_sign(const uint8_t *const apdu_buf) { if (eip712_context == NULL) { apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED; + } else if ((ui_712_get_filtering_mode() == EIP712_FILTERING_FULL) && + (ui_712_remaining_filters() != 0)) { + PRINTF("%d EIP712 filters are missing\n", ui_712_remaining_filters()); + apdu_response_code = APDU_RESPONSE_REF_DATA_NOT_FOUND; } else if (parseBip32(&apdu_buf[OFFSET_CDATA], &length, &tmpCtx.messageSigningContext.bip32) != NULL) { if (!N_storage.verbose_eip712 && (ui_712_get_filtering_mode() == EIP712_FILTERING_BASIC)) { diff --git a/src_features/signMessageEIP712/commands_712.h b/src_features/signMessageEIP712/commands_712.h index 8cfb3a1..e97d77b 100644 --- a/src_features/signMessageEIP712/commands_712.h +++ b/src_features/signMessageEIP712/commands_712.h @@ -11,14 +11,14 @@ #define P1_PARTIAL 0xFF // APDUs P2 -#define P2_DEF_NAME 0x00 -#define P2_DEF_FIELD 0xFF -#define P2_IMPL_NAME P2_DEF_NAME -#define P2_IMPL_ARRAY 0x0F -#define P2_IMPL_FIELD P2_DEF_FIELD -#define P2_FILT_ACTIVATE 0x00 -#define P2_FILT_CONTRACT_NAME 0x0F -#define P2_FILT_FIELD_NAME 0xFF +#define P2_DEF_NAME 0x00 +#define P2_DEF_FIELD 0xFF +#define P2_IMPL_NAME P2_DEF_NAME +#define P2_IMPL_ARRAY 0x0F +#define P2_IMPL_FIELD P2_DEF_FIELD +#define P2_FILT_ACTIVATE 0x00 +#define P2_FILT_MESSAGE_INFO 0x0F +#define P2_FILT_SHOW_FIELD 0xFF #define DOMAIN_STRUCT_NAME "EIP712Domain" diff --git a/src_features/signMessageEIP712/filtering.c b/src_features/signMessageEIP712/filtering.c index 71a87cb..a43c788 100644 --- a/src_features/signMessageEIP712/filtering.c +++ b/src_features/signMessageEIP712/filtering.c @@ -67,10 +67,10 @@ static bool verify_filtering_signature(uint8_t dname_length, // Magic number, makes it so a signature of one type can't be used as another switch (type) { - case FILTERING_STRUCT_FIELD: + case FILTERING_SHOW_FIELD: hash_byte(FILTERING_MAGIC_STRUCT_FIELD, (cx_hash_t *) &hash_ctx); break; - case FILTERING_CONTRACT_NAME: + case FILTERING_PROVIDE_MESSAGE_INFO: hash_byte(FILTERING_MAGIC_CONTRACT_NAME, (cx_hash_t *) &hash_ctx); break; default: @@ -93,8 +93,11 @@ static bool verify_filtering_signature(uint8_t dname_length, sizeof(eip712_context->schema_hash), (cx_hash_t *) &hash_ctx); - if (type == FILTERING_STRUCT_FIELD) { + if (type == FILTERING_SHOW_FIELD) { hash_filtering_path((cx_hash_t *) &hash_ctx); + } else // FILTERING_PROVIDE_MESSAGE_INFO + { + hash_byte(ui_712_remaining_filters(), (cx_hash_t *) &hash_ctx); } // Display name @@ -131,13 +134,14 @@ bool provide_filtering_info(const uint8_t *const payload, uint8_t length, e_filt const char *dname; uint8_t sig_len; const uint8_t *sig; + uint8_t offset = 0; - if (type == FILTERING_CONTRACT_NAME) { + if (type == FILTERING_PROVIDE_MESSAGE_INFO) { if (path_get_root_type() != ROOT_DOMAIN) { apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED; return false; } - } else // FILTERING_STRUCT_FIELD + } else // FILTERING_SHOW_FIELD { if (path_get_root_type() != ROOT_MESSAGE) { apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED; @@ -145,20 +149,25 @@ bool provide_filtering_info(const uint8_t *const payload, uint8_t length, e_filt } } if (length > 0) { - dname_len = payload[0]; + dname_len = payload[offset++]; if ((1 + dname_len) < length) { - dname = (char *) &payload[1]; - sig_len = payload[1 + dname_len]; - sig = &payload[1 + dname_len + 1]; - if ((sig_len > 0) && ((1 + dname_len + 1 + sig_len) == length)) { + dname = (char *) &payload[offset]; + offset += dname_len; + if (type == FILTERING_PROVIDE_MESSAGE_INFO) { + ui_712_set_filters_count(payload[offset++]); + } + sig_len = payload[offset++]; + sig = &payload[offset]; + offset += sig_len; + if ((sig_len > 0) && (offset == length)) { if ((ret = verify_filtering_signature(dname_len, dname, sig_len, sig, type))) { - if (type == FILTERING_CONTRACT_NAME) { + if (type == FILTERING_PROVIDE_MESSAGE_INFO) { if (!N_storage.verbose_eip712) { ui_712_set_title("Contract", 8); ui_712_set_value(dname, dname_len); ui_712_redraw_generic_step(); } - } else // FILTERING_STRUCT_FIELD + } else // FILTERING_SHOW_FIELD { if (dname_len > 0) // don't substitute for an empty name { diff --git a/src_features/signMessageEIP712/filtering.h b/src_features/signMessageEIP712/filtering.h index 04d20dc..3e33b9a 100644 --- a/src_features/signMessageEIP712/filtering.h +++ b/src_features/signMessageEIP712/filtering.h @@ -9,7 +9,7 @@ #define FILTERING_MAGIC_CONTRACT_NAME 0b10110111 // 183 #define FILTERING_MAGIC_STRUCT_FIELD 0b01001000 // ~183 = 72 -typedef enum { FILTERING_CONTRACT_NAME, FILTERING_STRUCT_FIELD } e_filtering_type; +typedef enum { FILTERING_PROVIDE_MESSAGE_INFO, FILTERING_SHOW_FIELD } e_filtering_type; bool provide_filtering_info(const uint8_t *const payload, uint8_t length, e_filtering_type type); diff --git a/src_features/signMessageEIP712/path.c b/src_features/signMessageEIP712/path.c index b4dd32b..aa6fad1 100644 --- a/src_features/signMessageEIP712/path.c +++ b/src_features/signMessageEIP712/path.c @@ -486,6 +486,7 @@ static bool path_advance_in_struct(void) { } if (path_struct->depth_count > 0) { *depth += 1; + ui_712_notify_filter_change(); end_reached = (*depth == fields_count); } if (end_reached) { diff --git a/src_features/signMessageEIP712/ui_logic.c b/src_features/signMessageEIP712/ui_logic.c index bd054a3..0099936 100644 --- a/src_features/signMessageEIP712/ui_logic.c +++ b/src_features/signMessageEIP712/ui_logic.c @@ -503,6 +503,24 @@ e_eip712_filtering_mode ui_712_get_filtering_mode(void) { return ui_ctx->filtering_mode; } +/** + * Set the number of filters this message should process + * + * @param[in] count number of filters + */ +void ui_712_set_filters_count(uint8_t count) { + ui_ctx->filters_to_process = count; +} + +/** + * Get the number of filters left to process + * + * @return number of filters + */ +uint8_t ui_712_remaining_filters(void) { + return ui_ctx->filters_to_process; +} + /** * Reset all the UI struct field flags */ @@ -521,4 +539,21 @@ void ui_712_queue_struct_to_review(void) { } } +/** + * Notify of a filter change from a path advance + * + * This function figures out by itself if there is anything to do + */ +void ui_712_notify_filter_change(void) { + if (path_get_root_type() == ROOT_MESSAGE) { + if (ui_ctx->filtering_mode == EIP712_FILTERING_FULL) { + if (ui_ctx->filters_to_process > 0) { + if (ui_ctx->field_flags & UI_712_FIELD_SHOWN) { + ui_ctx->filters_to_process -= 1; + } + } + } + } +} + #endif // HAVE_EIP712_FULL_SUPPORT diff --git a/src_features/signMessageEIP712/ui_logic.h b/src_features/signMessageEIP712/ui_logic.h index 8d3cdf1..47a2b7e 100644 --- a/src_features/signMessageEIP712/ui_logic.h +++ b/src_features/signMessageEIP712/ui_logic.h @@ -18,6 +18,7 @@ typedef struct { bool end_reached; e_ui_position pos; uint8_t filtering_mode; + uint8_t filters_to_process; uint8_t field_flags; uint8_t structs_to_review; } t_ui_context; @@ -39,7 +40,10 @@ void ui_712_field_flags_reset(void); void ui_712_finalize_field(void); void ui_712_set_filtering_mode(e_eip712_filtering_mode mode); e_eip712_filtering_mode ui_712_get_filtering_mode(void); +void ui_712_set_filters_count(uint8_t count); +uint8_t ui_712_remaining_filters(void); void ui_712_queue_struct_to_review(void); +void ui_712_notify_filter_change(void); #endif // HAVE_EIP712_FULL_SUPPORT diff --git a/tests/ragger/eip712/InputData.py b/tests/ragger/eip712/InputData.py index 359ca69..89e877c 100644 --- a/tests/ragger/eip712/InputData.py +++ b/tests/ragger/eip712/InputData.py @@ -192,7 +192,7 @@ def send_struct_impl_field(value, field): if filtering_paths: path = ".".join(current_path) if path in filtering_paths.keys(): - send_filtering_field_name(filtering_paths[path]) + send_filtering_show_field(filtering_paths[path]) app_client.eip712_send_struct_impl_struct_field(data) @@ -242,37 +242,38 @@ def send_struct_impl(structs, data, structname): return True # ledgerjs doesn't actually sign anything, and instead uses already pre-computed signatures -def send_filtering_contract_name(display_name: str): +def send_filtering_message_info(display_name: str, filters_count: int): global sig_ctx - msg = bytearray() - msg.append(183) - msg += sig_ctx["chainid"] - msg += sig_ctx["caddr"] - msg += sig_ctx["schema_hash"] + to_sign = bytearray() + to_sign.append(183) + to_sign += sig_ctx["chainid"] + to_sign += sig_ctx["caddr"] + to_sign += sig_ctx["schema_hash"] + to_sign.append(filters_count) for char in display_name: - msg.append(ord(char)) + to_sign.append(ord(char)) - sig = sig_ctx["key"].sign_deterministic(msg, sigencode=sigencode_der) - app_client.eip712_filtering_send_contract_name(display_name, sig) + sig = sig_ctx["key"].sign_deterministic(to_sign, sigencode=sigencode_der) + app_client.eip712_filtering_message_info(display_name, filters_count, sig) # ledgerjs doesn't actually sign anything, and instead uses already pre-computed signatures -def send_filtering_field_name(display_name): +def send_filtering_show_field(display_name): global sig_ctx path_str = ".".join(current_path) - msg = bytearray() - msg.append(72) - msg += sig_ctx["chainid"] - msg += sig_ctx["caddr"] - msg += sig_ctx["schema_hash"] + to_sign = bytearray() + to_sign.append(72) + to_sign += sig_ctx["chainid"] + to_sign += sig_ctx["caddr"] + to_sign += sig_ctx["schema_hash"] for char in path_str: - msg.append(ord(char)) + to_sign.append(ord(char)) for char in display_name: - msg.append(ord(char)) - sig = sig_ctx["key"].sign_deterministic(msg, sigencode=sigencode_der) - app_client.eip712_filtering_send_field_name(display_name, sig) + to_sign.append(ord(char)) + sig = sig_ctx["key"].sign_deterministic(to_sign, sigencode=sigencode_der) + app_client.eip712_filtering_show_field(display_name, sig) def read_filtering_file(domain, message, filtering_file_path): data_json = None @@ -349,9 +350,9 @@ def process_file(aclient: EthereumClient, input_file_path: str, filtering_file_p if filtering_file_path: if filtr and "name" in filtr: - send_filtering_contract_name(filtr["name"]) + send_filtering_message_info(filtr["name"], len(filtering_paths)) else: - send_filtering_contract_name(domain["name"]) + send_filtering_message_info(domain["name"], len(filtering_paths)) # send message implementation app_client.eip712_send_struct_impl_root_struct(message_typename) diff --git a/tests/ragger/ethereum_client/client.py b/tests/ragger/ethereum_client/client.py index 6ac352e..61b2012 100644 --- a/tests/ragger/ethereum_client/client.py +++ b/tests/ragger/ethereum_client/client.py @@ -144,13 +144,13 @@ class EthereumClient: self._eip712_filtering = True assert self._recv().status == 0x9000 - def eip712_filtering_send_contract_name(self, name: str, sig: bytes): - with self._send(self._cmd_builder.eip712_filtering_send_contract_name(name, sig)): + def eip712_filtering_message_info(self, name: str, filters_count: int, sig: bytes): + with self._send(self._cmd_builder.eip712_filtering_message_info(name, filters_count, sig)): self._enable_click_until_response() self._disable_click_until_response() assert self._recv().status == 0x9000 - def eip712_filtering_send_field_name(self, name: str, sig: bytes): - with self._send(self._cmd_builder.eip712_filtering_send_field_name(name, sig)): + def eip712_filtering_show_field(self, name: str, sig: bytes): + with self._send(self._cmd_builder.eip712_filtering_show_field(name, sig)): pass assert self._recv().status == 0x9000 diff --git a/tests/ragger/ethereum_client/command_builder.py b/tests/ragger/ethereum_client/command_builder.py index 5024a3c..134405f 100644 --- a/tests/ragger/ethereum_client/command_builder.py +++ b/tests/ragger/ethereum_client/command_builder.py @@ -151,13 +151,19 @@ class EthereumCmdBuilder: data += sig return data - def eip712_filtering_send_contract_name(self, name: str, sig: bytes) -> bytes: + def eip712_filtering_message_info(self, name: str, filters_count: int, sig: bytes) -> bytes: + data = bytearray() + data.append(len(name)) + data += self._string_to_bytes(name) + data.append(filters_count) + data.append(len(sig)) + data += sig return self._serialize(InsType.EIP712_SEND_FILTERING, P1Type.COMPLETE_SEND, P2Type.FILTERING_CONTRACT_NAME, - self._eip712_filtering_send_name(name, sig)) + data) - def eip712_filtering_send_field_name(self, name: str, sig: bytes) -> bytes: + def eip712_filtering_show_field(self, name: str, sig: bytes) -> bytes: return self._serialize(InsType.EIP712_SEND_FILTERING, P1Type.COMPLETE_SEND, P2Type.FILTERING_FIELD_NAME,