219 lines
6.5 KiB
C
219 lines
6.5 KiB
C
#ifdef HAVE_EIP712_FULL_SUPPORT
|
|
|
|
#include <stdlib.h>
|
|
#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
|
|
#include "apdu_constants.h" // APDU response codes
|
|
#include "typed_data.h"
|
|
|
|
static s_field_hashing *fh = NULL;
|
|
|
|
bool field_hash_init(void)
|
|
{
|
|
if (fh == NULL)
|
|
{
|
|
if ((fh = MEM_ALLOC_AND_ALIGN_TYPE(*fh)) == NULL)
|
|
{
|
|
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
|
|
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)
|
|
{
|
|
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
|
|
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 = __builtin_bswap16(*(uint16_t*)&data[0]); // 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
|
|
{
|
|
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
|
|
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:
|
|
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
|
|
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)
|
|
{
|
|
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
|
|
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))
|
|
{
|
|
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
|
|
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)
|
|
{
|
|
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
|
|
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
|
|
{
|
|
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
|
|
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
|