#ifdef HAVE_EIP712_FULL_SUPPORT #include #include "field_hash.h" #include "encode_field.h" #include "path.h" #include "mem.h" #include "mem_utils.h" #include "eip712.h" #include "shared_context.h" #include "ui_logic.h" #include "ethUtils.h" // KECCAK256_HASH_BYTESIZE #include "context.h" // contract_addr #include "utils.h" // u64_from_BE static s_field_hashing *fh = NULL; bool field_hash_init(void) { if (fh == NULL) { if ((fh = MEM_ALLOC_AND_ALIGN_TO_TYPE(sizeof(*fh), *fh)) == NULL) { return false; } fh->state = FHS_IDLE; } return true; } void field_hash_deinit(void) { fh = NULL; } bool field_hash(const uint8_t *data, uint8_t data_length, bool partial) { const void *field_ptr; e_type field_type; uint8_t *value = NULL; const char *key; uint8_t keylen; #ifdef DEBUG const char *type; uint8_t typelen; #endif if (fh == NULL) { return false; } // get field by path if ((field_ptr = path_get_field()) == NULL) { return false; } key = get_struct_field_keyname(field_ptr, &keylen); field_type = struct_field_type(field_ptr); if (fh->state == FHS_IDLE) // first packet for this frame { fh->remaining_size = (data[0] << 8) | data[1]; // network byte order data += sizeof(uint16_t); data_length -= sizeof(uint16_t); fh->state = FHS_WAITING_FOR_MORE; if (IS_DYN(field_type)) { cx_keccak_init(&global_sha3, 256); // init hash ui_712_new_field(field_ptr, data, data_length); } } fh->remaining_size -= data_length; // if a dynamic type -> continue progressive hash if (IS_DYN(field_type)) { cx_hash((cx_hash_t*)&global_sha3, 0, data, data_length, NULL, 0); } if (fh->remaining_size == 0) { if (partial) // only makes sense if marked as complete { return false; } #ifdef DEBUG PRINTF("=> "); type = get_struct_field_typename(field_ptr, &typelen); fwrite(type, sizeof(char), typelen, stdout); PRINTF(" "); key = get_struct_field_keyname(field_ptr, &keylen); fwrite(key, sizeof(char), keylen, stdout); PRINTF("\n"); #endif if (!IS_DYN(field_type)) { switch (field_type) { case TYPE_SOL_INT: value = encode_int(data, data_length, get_struct_field_typesize(field_ptr)); break; case TYPE_SOL_UINT: value = encode_uint(data, data_length); break; case TYPE_SOL_BYTES_FIX: value = encode_bytes(data, data_length); break; case TYPE_SOL_ADDRESS: value = encode_address(data, data_length); break; case TYPE_SOL_BOOL: value = encode_boolean((bool*)data, data_length); break; case TYPE_CUSTOM: default: PRINTF("Unknown solidity type!\n"); return false; } if (value == NULL) { return false; } ui_712_new_field(field_ptr, data, data_length); } else { if ((value = mem_alloc(KECCAK256_HASH_BYTESIZE)) == NULL) { return false; } // copy hash into memory cx_hash((cx_hash_t*)&global_sha3, CX_LAST, NULL, 0, value, KECCAK256_HASH_BYTESIZE); } // TODO: Move elsewhere uint8_t len = IS_DYN(field_type) ? KECCAK256_HASH_BYTESIZE : EIP_712_ENCODED_FIELD_LENGTH; // last thing in mem is the hash of the previous field // and just before it is the current hash context cx_sha3_t *hash_ctx = (cx_sha3_t*)(value - sizeof(cx_sha3_t)); // start the progressive hash on it cx_hash((cx_hash_t*)hash_ctx, 0, value, len, NULL, 0); // deallocate it mem_dealloc(len); if (path_get_root_type() == ROOT_DOMAIN) { // copy contract address into context if (strncmp(key, "verifyingContract", keylen) == 0) { if (data_length != sizeof(eip712_context->contract_addr)) { PRINTF("Unexpected verifyingContract length!\n"); return false; } memcpy(eip712_context->contract_addr, data, data_length); } else if (strncmp(key, "chainId", keylen) == 0) { uint64_t chainId = u64_from_BE(data, data_length); if (chainId != chainConfig->chainId) { PRINTF("EIP712Domain chain ID mismatch, expected 0x%.*h, got 0x%.*h !\n", sizeof(chainConfig->chainId), &chainConfig->chainId, sizeof(chainId), &chainId); return false; } } } path_advance(); fh->state = FHS_IDLE; ui_712_finalize_field(); } else { if (!partial || !IS_DYN(field_type)) // only makes sense if marked as partial { return false; } G_io_apdu_buffer[0] = 0x90; G_io_apdu_buffer[1] = 0x00; io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); } return true; } #endif // HAVE_EIP712_FULL_SUPPORT