Files
app-ethereum/src_features/signMessageEIP712/ui_logic.c

552 lines
15 KiB
C
Raw Normal View History

#ifdef HAVE_EIP712_FULL_SUPPORT
2022-05-12 17:29:35 +02:00
#include <stdlib.h>
#include <stdbool.h>
#include "ui_logic.h"
#include "mem.h"
#include "mem_utils.h"
#include "os_io.h"
#include "shared_context.h"
2024-01-16 13:51:36 +01:00
#include "common_utils.h" // uint256_to_decimal
2022-05-12 17:30:26 +02:00
#include "common_712.h"
#include "context_712.h" // eip712_context_deinit
2022-07-19 11:49:18 +02:00
#include "uint256.h" // tostring256 && tostring256_signed
#include "path.h" // path_get_root_type
#include "apdu_constants.h" // APDU response codes
#include "typed_data.h"
#include "commands_712.h"
#include "common_ui.h"
2022-05-12 17:30:26 +02:00
static t_ui_context *ui_ctx = NULL;
2022-05-12 17:29:35 +02:00
/**
* Checks on the UI context to determine if the next EIP 712 field should be shown
*
* @return whether the next field should be shown
*/
2022-07-19 11:49:18 +02:00
static bool ui_712_field_shown(void) {
bool ret = false;
2022-07-19 11:49:18 +02:00
if (ui_ctx->filtering_mode == EIP712_FILTERING_BASIC) {
if (N_storage.verbose_eip712 || (path_get_root_type() == ROOT_DOMAIN)) {
ret = true;
}
2022-07-19 11:49:18 +02:00
} else // EIP712_FILTERING_FULL
{
2022-07-19 11:49:18 +02:00
if (ui_ctx->field_flags & UI_712_FIELD_SHOWN) {
ret = true;
}
}
return ret;
}
2022-07-11 17:12:58 +02:00
/**
* Set UI buffer
*
* @param[in] src source buffer
* @param[in] src_length source buffer size
* @param[in] dst destination buffer
* @param[in] dst_length destination buffer length
* @param[in] explicit_trunc if truncation should be explicitely shown
*/
static void ui_712_set_buf(const char *const src,
size_t src_length,
char *const dst,
size_t dst_length,
2022-07-19 11:49:18 +02:00
bool explicit_trunc) {
uint8_t cpy_length;
2022-07-19 11:49:18 +02:00
if (src_length < dst_length) {
cpy_length = src_length;
2022-07-19 11:49:18 +02:00
} else {
cpy_length = dst_length - 1;
}
memcpy(dst, src, cpy_length);
dst[cpy_length] = '\0';
2022-10-26 14:15:09 +02:00
if (explicit_trunc && (cpy_length < src_length)) {
memcpy(dst + cpy_length - 3, "...", 3);
}
}
2022-07-11 17:12:58 +02:00
/**
* Skip the field if needed and reset its UI flags
*/
2022-07-19 11:49:18 +02:00
void ui_712_finalize_field(void) {
if (!ui_712_field_shown()) {
ui_712_next_field();
}
ui_712_field_flags_reset();
}
/**
* Set a new title for the EIP-712 generic UX_STEP
*
* @param[in] str the new title
* @param[in] length its length
*/
2022-07-19 11:49:18 +02:00
void ui_712_set_title(const char *const str, uint8_t length) {
ui_712_set_buf(str, length, strings.tmp.tmp2, sizeof(strings.tmp.tmp2), false);
}
/**
* Set a new value for the EIP-712 generic UX_STEP
*
* @param[in] str the new value
* @param[in] length its length
*/
2022-07-19 11:49:18 +02:00
void ui_712_set_value(const char *const str, uint8_t length) {
ui_712_set_buf(str, length, strings.tmp.tmp, sizeof(strings.tmp.tmp), true);
}
2022-05-12 17:30:26 +02:00
2022-07-11 17:12:58 +02:00
/**
* Redraw the dynamic UI step that shows EIP712 information
*/
2022-07-19 11:49:18 +02:00
void ui_712_redraw_generic_step(void) {
if (!ui_ctx->shown) // Initialize if it is not already
{
ui_712_start();
ui_ctx->shown = true;
2022-07-19 11:49:18 +02:00
} else {
ui_712_switch_to_message();
}
}
2022-05-12 17:29:35 +02:00
/**
* Called to fetch the next field if they have not all been processed yet
*
* Also handles the special "Review struct" screen of the verbose mode
*
* @return the next field state
2022-05-12 17:29:35 +02:00
*/
e_eip712_nfs ui_712_next_field(void) {
e_eip712_nfs state = EIP712_NO_MORE_FIELD;
2022-07-19 11:49:18 +02:00
if (ui_ctx == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
2022-07-19 11:49:18 +02:00
} else {
if (ui_ctx->structs_to_review > 0) {
ui_712_review_struct(path_get_nth_field_to_last(ui_ctx->structs_to_review));
ui_ctx->structs_to_review -= 1;
state = EIP712_FIELD_LATER;
} else if (!ui_ctx->end_reached) {
handle_eip712_return_code(true);
state = EIP712_FIELD_INCOMING;
2022-05-12 17:29:35 +02:00
}
}
return state;
2022-05-12 17:29:35 +02:00
}
/**
* Used to notify of a new struct to review
2022-05-12 17:30:26 +02:00
*
2022-07-11 17:12:58 +02:00
* @param[in] struct_ptr pointer to the structure to be shown
2022-05-12 17:29:35 +02:00
*/
2022-07-19 11:49:18 +02:00
void ui_712_review_struct(const void *const struct_ptr) {
const char *struct_name;
uint8_t struct_name_length;
const char *const title = "Review struct";
2022-07-19 11:49:18 +02:00
if (ui_ctx == NULL) {
return;
}
ui_712_set_title(title, strlen(title));
2022-07-19 11:49:18 +02:00
if ((struct_name = get_struct_name(struct_ptr, &struct_name_length)) != NULL) {
ui_712_set_value(struct_name, struct_name_length);
2022-05-12 17:29:35 +02:00
}
ui_712_redraw_generic_step();
2022-05-12 17:29:35 +02:00
}
2022-07-11 17:12:58 +02:00
/**
* Show the hash of the message on the generic UI step
*/
2022-07-19 11:49:18 +02:00
void ui_712_message_hash(void) {
const char *const title = "Message hash";
2022-06-13 12:50:55 +02:00
ui_712_set_title(title, strlen(title));
snprintf(strings.tmp.tmp,
sizeof(strings.tmp.tmp),
"0x%.*H",
KECCAK256_HASH_BYTESIZE,
tmpCtx.messageSigningContext712.messageHash);
ui_712_redraw_generic_step();
}
/**
* Format a given data as a string
*
* @param[in] data the data that needs formatting
* @param[in] length its length
*/
2022-07-19 11:49:18 +02:00
static void ui_712_format_str(const uint8_t *const data, uint8_t length) {
if (ui_712_field_shown()) {
ui_712_set_value((char *) data, length);
}
}
/**
* Format a given data as a string representation of an address
*
* @param[in] data the data that needs formatting
* @param[in] length its length
* @return if the formatting was successful
*/
2022-07-19 11:49:18 +02:00
static bool ui_712_format_addr(const uint8_t *const data, uint8_t length) {
if (length != ADDRESS_LENGTH) {
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
2022-07-19 11:49:18 +02:00
if (ui_712_field_shown()) {
if (!getEthDisplayableAddress((uint8_t *) data,
strings.tmp.tmp,
sizeof(strings.tmp.tmp),
&global_sha3,
chainConfig->chainId)) {
THROW(APDU_RESPONSE_ERROR_NO_INFO);
}
}
return true;
}
2022-07-11 17:12:58 +02:00
/**
* Format given data as a string representation of a boolean
*
* @param[in] data the data that needs formatting
* @param[in] length its length
* @return if the formatting was successful
*/
2022-07-19 11:49:18 +02:00
static bool ui_712_format_bool(const uint8_t *const data, uint8_t length) {
const char *const true_str = "true";
const char *const false_str = "false";
const char *str;
2022-07-19 11:49:18 +02:00
if (length != 1) {
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
2022-07-08 18:06:18 +02:00
return false;
}
str = *data ? true_str : false_str;
2022-07-19 11:49:18 +02:00
if (ui_712_field_shown()) {
ui_712_set_value(str, strlen(str));
2022-07-08 18:06:18 +02:00
}
return true;
}
2022-07-11 17:12:58 +02:00
/**
* Format given data as a string representation of bytes
*
* @param[in] data the data that needs formatting
* @param[in] length its length
*/
2022-07-19 11:49:18 +02:00
static void ui_712_format_bytes(const uint8_t *const data, uint8_t length) {
if (ui_712_field_shown()) {
snprintf(strings.tmp.tmp, sizeof(strings.tmp.tmp), "0x%.*H", length, data);
// +2 for the "0x"
// x2 for each byte value is represented by 2 ASCII characters
2022-07-19 11:49:18 +02:00
if ((2 + (length * 2)) > (sizeof(strings.tmp.tmp) - 1)) {
strings.tmp.tmp[sizeof(strings.tmp.tmp) - 1 - 3] = '\0';
strcat(strings.tmp.tmp, "...");
}
2022-07-08 18:06:18 +02:00
}
}
2022-07-11 17:12:58 +02:00
/**
* Format given data as a string representation of an integer
*
* @param[in] data the data that needs formatting
* @param[in] length its length
* @return if the formatting was successful
*/
2022-07-08 18:06:18 +02:00
static bool ui_712_format_int(const uint8_t *const data,
uint8_t length,
2022-07-19 11:49:18 +02:00
const void *const field_ptr) {
2022-07-08 18:06:18 +02:00
uint256_t value256;
uint128_t value128;
int32_t value32;
int16_t value16;
2022-07-19 11:49:18 +02:00
switch (get_struct_field_typesize(field_ptr) * 8) {
2022-07-08 18:06:18 +02:00
case 256:
convertUint256BE(data, length, &value256);
2022-07-19 11:49:18 +02:00
if (ui_712_field_shown()) {
tostring256_signed(&value256, 10, strings.tmp.tmp, sizeof(strings.tmp.tmp));
}
2022-07-08 18:06:18 +02:00
break;
case 128:
convertUint128BE(data, length, &value128);
2022-07-19 11:49:18 +02:00
if (ui_712_field_shown()) {
tostring128_signed(&value128, 10, strings.tmp.tmp, sizeof(strings.tmp.tmp));
}
2022-07-08 18:06:18 +02:00
break;
case 64:
convertUint64BEto128(data, length, &value128);
2022-07-19 11:49:18 +02:00
if (ui_712_field_shown()) {
tostring128_signed(&value128, 10, strings.tmp.tmp, sizeof(strings.tmp.tmp));
}
2022-07-08 18:06:18 +02:00
break;
case 32:
value32 = 0;
2022-07-19 11:49:18 +02:00
for (int i = 0; i < length; ++i) {
((uint8_t *) &value32)[length - 1 - i] = data[i];
2022-07-08 18:06:18 +02:00
}
2022-07-19 11:49:18 +02:00
if (ui_712_field_shown()) {
snprintf(strings.tmp.tmp, sizeof(strings.tmp.tmp), "%d", value32);
}
2022-07-08 18:06:18 +02:00
break;
case 16:
value16 = 0;
2022-07-19 11:49:18 +02:00
for (int i = 0; i < length; ++i) {
((uint8_t *) &value16)[length - 1 - i] = data[i];
2022-07-08 18:06:18 +02:00
}
2022-07-19 11:49:18 +02:00
if (ui_712_field_shown()) {
snprintf(strings.tmp.tmp,
sizeof(strings.tmp.tmp),
"%d",
2022-07-19 11:49:18 +02:00
value16); // expanded to 32 bits
}
2022-07-08 18:06:18 +02:00
break;
case 8:
2022-07-19 11:49:18 +02:00
if (ui_712_field_shown()) {
snprintf(strings.tmp.tmp,
sizeof(strings.tmp.tmp),
"%d",
2022-07-19 11:49:18 +02:00
((int8_t *) data)[0]); // expanded to 32 bits
}
2022-07-08 18:06:18 +02:00
break;
default:
PRINTF("Unhandled field typesize\n");
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
2022-07-08 18:06:18 +02:00
return false;
}
return true;
}
2022-07-11 17:12:58 +02:00
/**
* Format given data as a string representation of an unsigned integer
*
* @param[in] data the data that needs formatting
* @param[in] length its length
*/
2022-07-19 11:49:18 +02:00
static void ui_712_format_uint(const uint8_t *const data, uint8_t length) {
2022-07-08 18:06:18 +02:00
uint256_t value256;
convertUint256BE(data, length, &value256);
2022-07-19 11:49:18 +02:00
if (ui_712_field_shown()) {
tostring256(&value256, 10, strings.tmp.tmp, sizeof(strings.tmp.tmp));
}
2022-07-08 18:06:18 +02:00
}
2022-05-12 17:29:35 +02:00
/**
* Used to notify of a new field to review in the current struct (key + value)
*
* @param[in] field_ptr pointer to the new struct field
* @param[in] data pointer to the field's raw value
* @param[in] length field's raw value byte-length
*/
2022-07-19 11:49:18 +02:00
bool ui_712_new_field(const void *const field_ptr, const uint8_t *const data, uint8_t length) {
2022-05-12 17:29:35 +02:00
const char *key;
uint8_t key_len;
2022-07-19 11:49:18 +02:00
if (ui_ctx == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
2022-07-08 18:06:18 +02:00
return false;
}
// Key
2022-07-19 11:49:18 +02:00
if ((key = get_struct_field_keyname(field_ptr, &key_len)) == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return false;
}
2022-07-19 11:49:18 +02:00
if (ui_712_field_shown() && !(ui_ctx->field_flags & UI_712_FIELD_NAME_PROVIDED)) {
ui_712_set_title(key, key_len);
2022-05-12 17:29:35 +02:00
}
// Value
2022-07-19 11:49:18 +02:00
switch (struct_field_type(field_ptr)) {
case TYPE_SOL_STRING:
ui_712_format_str(data, length);
break;
case TYPE_SOL_ADDRESS:
2022-07-19 11:49:18 +02:00
if (ui_712_format_addr(data, length) == false) {
return false;
}
break;
case TYPE_SOL_BOOL:
2022-07-19 11:49:18 +02:00
if (ui_712_format_bool(data, length) == false) {
2022-07-08 18:06:18 +02:00
return false;
}
break;
case TYPE_SOL_BYTES_FIX:
case TYPE_SOL_BYTES_DYN:
2022-07-08 18:06:18 +02:00
ui_712_format_bytes(data, length);
break;
case TYPE_SOL_INT:
2022-07-19 11:49:18 +02:00
if (ui_712_format_int(data, length, field_ptr) == false) {
2022-07-08 18:06:18 +02:00
return false;
}
break;
case TYPE_SOL_UINT:
2022-07-08 18:06:18 +02:00
ui_712_format_uint(data, length);
break;
default:
PRINTF("Unhandled type\n");
2022-07-08 18:06:18 +02:00
return false;
}
// Check if this field is supposed to be displayed
2022-07-19 11:49:18 +02:00
if (ui_712_field_shown()) {
ui_712_redraw_generic_step();
}
2022-07-08 18:06:18 +02:00
return true;
2022-05-12 17:29:35 +02:00
}
/**
* Used to signal that we are done with reviewing the structs and we can now have
* the option to approve or reject the signature
*/
2022-07-19 11:49:18 +02:00
void ui_712_end_sign(void) {
if (ui_ctx == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return;
}
2022-05-12 17:29:35 +02:00
ui_ctx->end_reached = true;
2022-07-19 11:49:18 +02:00
if (N_storage.verbose_eip712 || (ui_ctx->filtering_mode == EIP712_FILTERING_FULL)) {
ui_712_switch_to_sign();
}
2022-05-12 17:29:35 +02:00
}
/**
* Initializes the UI context structure in memory
*/
2022-07-19 11:49:18 +02:00
bool ui_712_init(void) {
if ((ui_ctx = MEM_ALLOC_AND_ALIGN_TYPE(*ui_ctx))) {
2022-05-12 17:29:35 +02:00
ui_ctx->shown = false;
ui_ctx->end_reached = false;
2022-06-13 12:50:55 +02:00
ui_ctx->filtering_mode = EIP712_FILTERING_BASIC;
2022-07-19 11:49:18 +02:00
} else {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
}
2022-05-12 17:29:35 +02:00
return ui_ctx != NULL;
}
2022-05-12 17:30:26 +02:00
/**
* Deinit function that simply unsets the struct pointer to NULL
*/
2022-07-19 11:49:18 +02:00
void ui_712_deinit(void) {
2022-05-12 17:30:26 +02:00
ui_ctx = NULL;
}
/**
* Approve button handling, calls the common handler function then
* deinitializes the EIP712 context altogether.
* @param[in] e unused here, just needed to match the UI function signature
* @return unused here, just needed to match the UI function signature
*/
2022-08-25 10:26:48 +02:00
unsigned int ui_712_approve() {
ui_712_approve_cb();
2022-05-12 17:30:26 +02:00
eip712_context_deinit();
return 0;
}
/**
* Reject button handling, calls the common handler function then
* deinitializes the EIP712 context altogether.
2022-07-11 17:12:58 +02:00
2022-05-12 17:30:26 +02:00
* @param[in] e unused here, just needed to match the UI function signature
* @return unused here, just needed to match the UI function signature
*/
2022-08-25 10:26:48 +02:00
unsigned int ui_712_reject() {
ui_712_reject_cb();
2022-05-12 17:30:26 +02:00
eip712_context_deinit();
return 0;
}
2022-07-11 17:12:58 +02:00
/**
* Set a structure field's UI flags
*
* @param[in] show if this field should be shown on the device
* @param[in] name_provided if a substitution name has been provided
*/
2022-07-19 11:49:18 +02:00
void ui_712_flag_field(bool show, bool name_provided) {
if (show) {
2022-06-13 12:50:55 +02:00
ui_ctx->field_flags |= UI_712_FIELD_SHOWN;
}
2022-07-19 11:49:18 +02:00
if (name_provided) {
2022-06-13 12:50:55 +02:00
ui_ctx->field_flags |= UI_712_FIELD_NAME_PROVIDED;
}
}
2022-07-11 17:12:58 +02:00
/**
* Set the UI filtering mode
*
* @param[in] the new filtering mode
*/
2022-07-19 11:49:18 +02:00
void ui_712_set_filtering_mode(e_eip712_filtering_mode mode) {
ui_ctx->filtering_mode = mode;
}
2022-07-11 17:12:58 +02:00
/**
* Get the UI filtering mode
*
* @return current filtering mode
*/
2022-07-19 11:49:18 +02:00
e_eip712_filtering_mode ui_712_get_filtering_mode(void) {
return ui_ctx->filtering_mode;
}
2022-08-19 18:31:00 +02:00
/**
* 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;
}
2022-07-11 17:12:58 +02:00
/**
* Reset all the UI struct field flags
*/
2022-07-19 11:49:18 +02:00
void ui_712_field_flags_reset(void) {
2022-06-13 12:50:55 +02:00
ui_ctx->field_flags = 0;
}
2022-07-11 17:12:58 +02:00
/**
* Add a struct to the UI review queue
*
* Makes it so the user will have to go through a "Review struct" screen
*/
2022-07-19 11:49:18 +02:00
void ui_712_queue_struct_to_review(void) {
if (N_storage.verbose_eip712) {
ui_ctx->structs_to_review += 1;
}
}
2022-08-19 18:31:00 +02:00
/**
* 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;
}
}
}
}
}
2022-07-19 11:49:18 +02:00
#endif // HAVE_EIP712_FULL_SUPPORT