EIP712 Filtering count checking

This commit is contained in:
Alexandre Paillier
2022-08-19 18:31:00 +02:00
parent 93b0bb08f7
commit ed479128fd
12 changed files with 139 additions and 67 deletions

View File

@@ -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%"]
|==========================================

View File

@@ -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

View File

@@ -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)) {

View File

@@ -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"

View File

@@ -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
{

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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,