diff --git a/legacy/firmware/.changelog.d/15.added b/legacy/firmware/.changelog.d/15.added new file mode 100644 index 000000000..70ce443ce --- /dev/null +++ b/legacy/firmware/.changelog.d/15.added @@ -0,0 +1 @@ +Signed Ethereum network and token definitions from host diff --git a/legacy/firmware/.gitignore b/legacy/firmware/.gitignore index 543117bf3..146fd0add 100644 --- a/legacy/firmware/.gitignore +++ b/legacy/firmware/.gitignore @@ -1,7 +1,8 @@ coin_info.[ch] nem_mosaics.[ch] -ethereum_networks.h -ethereum_tokens.[ch] +ethereum_definitions_constants.h +ethereum_networks.c +ethereum_tokens.c u2f_knownapps.h bl_data.h diff --git a/legacy/firmware/Makefile b/legacy/firmware/Makefile index 755b970d5..d6dd70292 100644 --- a/legacy/firmware/Makefile +++ b/legacy/firmware/Makefile @@ -61,6 +61,8 @@ OBJS += crypto.o ifneq ($(BITCOIN_ONLY),1) OBJS += u2f.o OBJS += ethereum.o +OBJS += ethereum_definitions.o +OBJS += ethereum_networks.o OBJS += ethereum_tokens.o OBJS += nem2.o OBJS += nem_mosaics.o @@ -140,6 +142,7 @@ OBJS += protob/messages-management.pb.o ifneq ($(BITCOIN_ONLY),1) OBJS += ../vendor/trezor-crypto/cash_addr.o OBJS += protob/messages-ethereum.pb.o +OBJS += protob/messages-ethereum-definitions.pb.o OBJS += protob/messages-nem.pb.o OBJS += protob/messages-stellar.pb.o endif diff --git a/legacy/firmware/ethereum.c b/legacy/firmware/ethereum.c index 80fc2cf95..1f30d911c 100644 --- a/legacy/firmware/ethereum.c +++ b/legacy/firmware/ethereum.c @@ -51,6 +51,7 @@ static uint32_t data_total, data_left; static EthereumTxRequest msg_tx_request; static CONFIDENTIAL uint8_t privkey[32]; static uint64_t chain_id; +static const char *chain_suffix; static bool eip1559; struct SHA3_CTX keccak_ctx = {0}; @@ -64,6 +65,7 @@ struct signing_params { bool pubkeyhash_set; uint8_t pubkeyhash[20]; uint64_t chain_id; + const char *chain_suffix; uint32_t data_length; uint32_t data_initial_chunk_size; @@ -72,7 +74,7 @@ struct signing_params { bool has_to; const char *to; - const TokenType *token; + const EthereumTokenInfo *token; uint32_t value_size; const uint8_t *value_bytes; @@ -313,30 +315,28 @@ static void send_signature(void) { * using standard ethereum units. * The buffer must be at least 25 bytes. */ -static void ethereumFormatAmount(const bignum256 *amnt, const TokenType *token, - char *buf, int buflen) { +static void ethereumFormatAmount(const bignum256 *amnt, + const EthereumTokenInfo *token, char *buf, + int buflen) { bignum256 bn1e9 = {0}; bn_read_uint32(1000000000, &bn1e9); - const char *suffix = NULL; + char suffix[50] = {' ', 0}; int decimals = 18; - if (token == UnknownToken) { - strlcpy(buf, "Unknown token value", buflen); - return; - } else if (token != NULL) { - suffix = token->ticker; + if (token) { + strlcpy(suffix + 1, token->symbol, sizeof(suffix) - 1); decimals = token->decimals; } else if (bn_is_less(amnt, &bn1e9)) { - suffix = " Wei"; + strlcpy(suffix + 1, "Wei", sizeof(suffix) - 1); decimals = 0; } else { - ASSIGN_ETHEREUM_SUFFIX(suffix, chain_id); + strlcpy(suffix + 1, chain_suffix, sizeof(suffix) - 1); } bn_format(amnt, NULL, suffix, decimals, 0, false, ',', buf, buflen); } static void layoutEthereumConfirmTx(const uint8_t *to, uint32_t to_len, const uint8_t *value, uint32_t value_len, - const TokenType *token) { + const EthereumTokenInfo *token) { bignum256 val = {0}; uint8_t pad_val[32] = {0}; memzero(pad_val, sizeof(pad_val)); @@ -508,6 +508,7 @@ static bool ethereum_signing_init_common(struct signing_params *params) { return false; } chain_id = params->chain_id; + chain_suffix = params->chain_suffix; if (params->data_length > 0) { if (params->data_initial_chunk_size == 0) { @@ -550,7 +551,8 @@ static bool ethereum_signing_init_common(struct signing_params *params) { return true; } -static void ethereum_signing_handle_erc20(struct signing_params *params) { +static void ethereum_signing_handle_erc20(struct signing_params *params, + const EthereumTokenInfo *token) { if (params->has_to && ethereum_parse(params->to, params->pubkeyhash)) { params->pubkeyhash_set = true; } else { @@ -564,7 +566,7 @@ static void ethereum_signing_handle_erc20(struct signing_params *params) { memcmp(params->data_initial_chunk_bytes, "\xa9\x05\x9c\xbb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16) == 0) { - params->token = tokenByChainAddress(chain_id, params->pubkeyhash); + params->token = token; } } @@ -594,10 +596,11 @@ static bool ethereum_signing_confirm_common( return true; } -void ethereum_signing_init(const EthereumSignTx *msg, const HDNode *node) { +void ethereum_signing_init(const EthereumSignTx *msg, const HDNode *node, + const EthereumDefinitionsDecoded *defs) { struct signing_params params = { .chain_id = msg->chain_id, - + .chain_suffix = defs->network->symbol, .data_length = msg->data_length, .data_initial_chunk_size = msg->data_initial_chunk.size, .data_initial_chunk_bytes = msg->data_initial_chunk.bytes, @@ -634,7 +637,7 @@ void ethereum_signing_init(const EthereumSignTx *msg, const HDNode *node) { } } - ethereum_signing_handle_erc20(¶ms); + ethereum_signing_handle_erc20(¶ms, defs->token); if (!ethereum_signing_confirm_common(¶ms)) { fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); @@ -700,9 +703,11 @@ void ethereum_signing_init(const EthereumSignTx *msg, const HDNode *node) { } void ethereum_signing_init_eip1559(const EthereumSignTxEIP1559 *msg, - const HDNode *node) { + const HDNode *node, + const EthereumDefinitionsDecoded *defs) { struct signing_params params = { .chain_id = msg->chain_id, + .chain_suffix = defs->network->symbol, .data_length = msg->data_length, .data_initial_chunk_size = msg->data_initial_chunk.size, @@ -729,7 +734,7 @@ void ethereum_signing_init_eip1559(const EthereumSignTxEIP1559 *msg, return; } - ethereum_signing_handle_erc20(¶ms); + ethereum_signing_handle_erc20(¶ms, defs->token); if (!ethereum_signing_confirm_common(¶ms)) { fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); @@ -1040,9 +1045,23 @@ bool ethereum_parse(const char *address, uint8_t pubkeyhash[20]) { return true; } +static bool check_ethereum_slip44_unhardened( + uint32_t slip44, const EthereumNetworkInfo *network) { + if (is_unknown_network(network)) { + // Allow Ethereum or testnet paths for unknown networks. + return slip44 == 60 || slip44 == 1; + } else if (network->slip44 != 60 && network->slip44 != 1) { + // Allow cross-signing with Ethereum unless it's testnet. + return (slip44 == network->slip44 || slip44 == 60); + } else { + return (slip44 == network->slip44); + } +} + static bool ethereum_path_check_bip44(uint32_t address_n_count, const uint32_t *address_n, - bool pubkey_export, uint64_t chain) { + bool pubkey_export, + const EthereumNetworkInfo *network) { bool valid = (address_n_count >= 3); valid = valid && (address_n[0] == (PATH_HARDENED | 44)); valid = valid && (address_n[1] & PATH_HARDENED); @@ -1050,20 +1069,7 @@ static bool ethereum_path_check_bip44(uint32_t address_n_count, valid = valid && ((address_n[2] & PATH_UNHARDEN_MASK) <= PATH_MAX_ACCOUNT); uint32_t path_slip44 = address_n[1] & PATH_UNHARDEN_MASK; - if (chain == CHAIN_ID_UNKNOWN) { - valid = valid && (is_ethereum_slip44(path_slip44)); - } else { - uint32_t chain_slip44 = ethereum_slip44_by_chain_id(chain); - if (chain_slip44 == SLIP44_UNKNOWN) { - // Allow Ethereum or testnet paths for unknown networks. - valid = valid && (path_slip44 == 60 || path_slip44 == 1); - } else if (chain_slip44 != 60 && chain_slip44 != 1) { - // Allow cross-signing with Ethereum unless it's testnet. - valid = valid && (path_slip44 == chain_slip44 || path_slip44 == 60); - } else { - valid = valid && (path_slip44 == chain_slip44); - } - } + valid = valid && check_ethereum_slip44_unhardened(path_slip44, network); if (pubkey_export) { // m/44'/coin_type'/account'/* @@ -1101,7 +1107,7 @@ static bool ethereum_path_check_bip44(uint32_t address_n_count, static bool ethereum_path_check_casa45(uint32_t address_n_count, const uint32_t *address_n, - uint64_t chain) { + const EthereumNetworkInfo *network) { bool valid = (address_n_count == 5); valid = valid && (address_n[0] == (PATH_HARDENED | 45)); valid = valid && (address_n[1] < PATH_HARDENED); @@ -1110,35 +1116,23 @@ static bool ethereum_path_check_casa45(uint32_t address_n_count, valid = valid && (address_n[4] <= PATH_MAX_ADDRESS_INDEX); uint32_t path_slip44 = address_n[1]; - if (chain == CHAIN_ID_UNKNOWN) { - valid = valid && (is_ethereum_slip44(path_slip44)); - } else { - uint32_t chain_slip44 = ethereum_slip44_by_chain_id(chain); - if (chain_slip44 == SLIP44_UNKNOWN) { - // Allow Ethereum or testnet paths for unknown networks. - valid = valid && (path_slip44 == 60 || path_slip44 == 1); - } else if (chain_slip44 != 60 && chain_slip44 != 1) { - // Allow cross-signing with Ethereum unless it's testnet. - valid = valid && (path_slip44 == chain_slip44 || path_slip44 == 60); - } else { - valid = valid && (path_slip44 == chain_slip44); - } - } + valid = valid && check_ethereum_slip44_unhardened(path_slip44, network); return valid; } bool ethereum_path_check(uint32_t address_n_count, const uint32_t *address_n, - bool pubkey_export, uint64_t chain) { + bool pubkey_export, + const EthereumNetworkInfo *network) { if (address_n_count == 0) { return false; } if (address_n[0] == (PATH_HARDENED | 44)) { return ethereum_path_check_bip44(address_n_count, address_n, pubkey_export, - chain); + network); } if (address_n[0] == (PATH_HARDENED | 45)) { - return ethereum_path_check_casa45(address_n_count, address_n, chain); + return ethereum_path_check_casa45(address_n_count, address_n, network); } return false; } diff --git a/legacy/firmware/ethereum.h b/legacy/firmware/ethereum.h index bae4c56ba..aa6373213 100644 --- a/legacy/firmware/ethereum.h +++ b/legacy/firmware/ethereum.h @@ -23,13 +23,16 @@ #include #include #include "bip32.h" +#include "ethereum_definitions.h" #include "messages-ethereum.pb.h" #define CHAIN_ID_UNKNOWN UINT64_MAX -void ethereum_signing_init(const EthereumSignTx *msg, const HDNode *node); +void ethereum_signing_init(const EthereumSignTx *msg, const HDNode *node, + const EthereumDefinitionsDecoded *defs); void ethereum_signing_init_eip1559(const EthereumSignTxEIP1559 *msg, - const HDNode *node); + const HDNode *node, + const EthereumDefinitionsDecoded *defs); void ethereum_signing_abort(void); void ethereum_signing_txack(const EthereumTxAck *msg); @@ -42,5 +45,6 @@ void ethereum_typed_hash_sign(const EthereumSignTypedHash *msg, bool ethereum_parse(const char *address, uint8_t pubkeyhash[20]); bool ethereum_path_check(uint32_t address_n_count, const uint32_t *address_n, - bool pubkey_export, uint64_t chain); + bool pubkey_export, + const EthereumNetworkInfo *network); #endif diff --git a/legacy/firmware/ethereum_definitions.c b/legacy/firmware/ethereum_definitions.c new file mode 100644 index 000000000..ac96515cc --- /dev/null +++ b/legacy/firmware/ethereum_definitions.c @@ -0,0 +1,313 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (C) 2022 Martin Novak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include + +#include "crypto.h" +#include "ethereum.h" +#include "ethereum_definitions.h" +#include "ethereum_definitions_constants.h" +#include "ethereum_networks.h" +#include "ethereum_tokens.h" +#include "fsm.h" +#include "gettext.h" +#include "memzero.h" +#include "messages.h" +#include "pb.h" +#include "pb_decode.h" +#include "trezor.h" // because of the "VERSTR" macro used in "fsm_sendFailureDebug" function +#include "util.h" + +typedef pb_byte_t proof_entry[SHA256_DIGEST_LENGTH]; + +struct EncodedDefinition { + // prefix + pb_byte_t format_version[FORMAT_VERSION_LENGTH]; + uint8_t definition_type; + uint32_t data_version; + uint16_t payload_length; + + // payload + const pb_byte_t *payload; + + // suffix + uint8_t proof_length; + const proof_entry *proof; + + const ed25519_signature *signed_root_hash; +}; + +static bool parse_encoded_definition(struct EncodedDefinition *const result, + const pb_size_t size, + const pb_byte_t *bytes) { + // format version + definition type + data version + payload length + payload + // (at least 1B) + proof length + signed Merkle tree root hash + if (size < (FORMAT_VERSION_LENGTH + 1 + 4 + 2 + 1 + 1 + + MERKLE_TREE_SIGNED_ROOT_SIZE)) { + return false; + } + + const pb_byte_t *cursor = bytes; + memcpy(result->format_version, cursor, FORMAT_VERSION_LENGTH); + cursor += FORMAT_VERSION_LENGTH; + + result->definition_type = *cursor; + cursor += 1; + + result->data_version = *(uint32_t *)cursor; + cursor += 4; + + result->payload_length = *(uint16_t *)cursor; + cursor += 2; + + result->payload = cursor; + cursor += result->payload_length; + + if (size <= cursor - bytes) { + return false; + } + result->proof_length = *cursor; + cursor += 1; + + // check the whole size of incoming bytes array + if (size != (cursor - bytes) + result->proof_length * sizeof(proof_entry) + + MERKLE_TREE_SIGNED_ROOT_SIZE) { + return false; + } + result->proof = (proof_entry *)cursor; + cursor += result->proof_length * sizeof(proof_entry); + + result->signed_root_hash = (ed25519_signature *)cursor; + + return true; +} + +static bool decode_definition(const pb_size_t size, const pb_byte_t *bytes, + const EthereumDefinitionType expected_type, + void *definition) { + // parse received definition + static struct EncodedDefinition parsed_def; + const char *error_str = _("Invalid Ethereum definition"); + + memzero(&parsed_def, sizeof(parsed_def)); + if (!parse_encoded_definition(&parsed_def, size, bytes)) { + goto err; + } + + // check definition fields + if (memcmp(FORMAT_VERSION, parsed_def.format_version, + FORMAT_VERSION_LENGTH)) { + goto err; + } + + if (expected_type != parsed_def.definition_type) { + error_str = _("Definition type mismatch"); + goto err; + } + + if (MIN_DATA_VERSION > parsed_def.data_version) { + error_str = _("Definition is outdated"); + goto err; + } + + // compute Merkle tree root hash from proof + uint8_t hash[SHA256_DIGEST_LENGTH] = {0}; + SHA256_CTX context = {0}; + sha256_Init(&context); + + // leaf hash = sha256('\x00' + leaf data) + sha256_Update(&context, (uint8_t[]){0}, 1); + // signed data is everything from start of `bytes` to the end of `payload` + const pb_byte_t *payload_end = parsed_def.payload + parsed_def.payload_length; + size_t signed_data_size = payload_end - bytes; + sha256_Update(&context, bytes, signed_data_size); + + sha256_Final(&context, hash); + + const uint8_t *min, *max; + for (uint8_t i = 0; i < parsed_def.proof_length; i++) { + sha256_Init(&context); + // node hash = sha256('\x01' + min(hash, next_proof) + max(hash, + // next_proof)) + sha256_Update(&context, (uint8_t[]){1}, 1); + if (memcmp(hash, parsed_def.proof[i], SHA256_DIGEST_LENGTH) <= 0) { + min = hash; + max = parsed_def.proof[i]; + } else { + min = parsed_def.proof[i]; + max = hash; + } + sha256_Update(&context, min, SHA256_DIGEST_LENGTH); + sha256_Update(&context, max, SHA256_DIGEST_LENGTH); + sha256_Final(&context, hash); + } + + // and verify its signature + if (ed25519_sign_open(hash, SHA256_DIGEST_LENGTH, DEFINITIONS_PUBLIC_KEY, + *(parsed_def.signed_root_hash)) != 0 +#if DEBUG_LINK + && + ed25519_sign_open(hash, SHA256_DIGEST_LENGTH, DEFINITIONS_DEV_PUBLIC_KEY, + *(parsed_def.signed_root_hash)) != 0 +#endif + ) { + // invalid signature + error_str = _("Invalid definition signature"); + goto err; + } + + // decode message + const pb_msgdesc_t *fields = (expected_type == EthereumDefinitionType_NETWORK + ? EthereumNetworkInfo_fields + : EthereumTokenInfo_fields); + pb_istream_t stream = + pb_istream_from_buffer(parsed_def.payload, parsed_def.payload_length); + bool status = pb_decode(&stream, fields, definition); + if (status) { + return true; + } + + // fallthrough to error handling in case of decoding failure + +err: + memzero(&parsed_def, sizeof(parsed_def)); + fsm_sendFailure(FailureType_Failure_DataError, error_str); + return false; +} + +static const EthereumNetworkInfo *get_network( + const EncodedNetwork *encoded_network, const uint64_t chain_id, + const uint32_t slip44) { + static EthereumNetworkInfo decoded_network; + const EthereumNetworkInfo *network = &UNKNOWN_NETWORK; + + // try to get built-in definition + if (chain_id != CHAIN_ID_UNKNOWN) { + network = ethereum_get_network_by_chain_id(chain_id); + } else if (slip44 != SLIP44_UNKNOWN) { + network = ethereum_get_network_by_slip44(slip44); + } else { + // if both chain_id and slip44 is unspecified, we do not have anything to + // match to the encoded definition, so just short-circuit here + return &UNKNOWN_NETWORK; + } + // if we found built-in definition, or if there's no data to decode, we are + // done + if (!is_unknown_network(network) || encoded_network == NULL) { + return network; + } + + // if we still do not have any network definition try to decode received data + memzero(&decoded_network, sizeof(decoded_network)); + if (!decode_definition(encoded_network->size, encoded_network->bytes, + EthereumDefinitionType_NETWORK, &decoded_network)) { + // error already sent by decode_definition + return NULL; + } + + if (chain_id != CHAIN_ID_UNKNOWN && decoded_network.chain_id != chain_id) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Network definition mismatch")); + return NULL; + } + if (slip44 != SLIP44_UNKNOWN && decoded_network.slip44 != slip44) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Network definition mismatch")); + return NULL; + } + + return &decoded_network; +} + +static const EthereumTokenInfo *get_token(const EncodedToken *encoded_token, + const uint64_t chain_id, + const char *address) { + static EthereumTokenInfo decoded_token; + + // if we do not know the chain_id, we cannot get the token + if (chain_id == CHAIN_ID_UNKNOWN) { + return &UNKNOWN_TOKEN; + } + + // convert address string to bytes + EthereumTokenInfo_address_t address_bytes; + bool address_parsed = address && ethereum_parse(address, address_bytes.bytes); + if (!address_parsed) { + // without a valid address, we cannot get the token + return &UNKNOWN_TOKEN; + } + + // try to get built-in definition + const EthereumTokenInfo *token = + ethereum_token_by_address(chain_id, address_bytes.bytes); + if (!is_unknown_token(token) || encoded_token == NULL) { + // if we found one, or if there's no data to decode, we are done + return token; + } + + // try to decode received definition + memzero(&decoded_token, sizeof(decoded_token)); + if (!decode_definition(encoded_token->size, encoded_token->bytes, + EthereumDefinitionType_TOKEN, &decoded_token)) { + // error already sent by decode_definition + return NULL; + } + + if (decoded_token.chain_id != chain_id || + memcmp(decoded_token.address.bytes, address_bytes.bytes, + sizeof(decoded_token.address.bytes))) { + // receiving a mismatched token is not an error (we expect being able to get + // multiple token definitions in the future, for multiple networks) + // but we must not accept the mismatched definition + memzero(&decoded_token, sizeof(decoded_token)); + return &UNKNOWN_TOKEN; + } + + return &decoded_token; +} + +const EthereumDefinitionsDecoded *ethereum_get_definitions( + const EncodedNetwork *encoded_network, const EncodedToken *encoded_token, + const uint64_t chain_id, const uint32_t slip44, const char *token_address) { + static EthereumDefinitionsDecoded defs; + memzero(&defs, sizeof(defs)); + + const EthereumNetworkInfo *network = + get_network(encoded_network, chain_id, slip44); + if (network == NULL) { + // error while decoding, failure was sent by get_network + return NULL; + } + defs.network = network; + + if (!is_unknown_network(network) && token_address != NULL) { + const EthereumTokenInfo *token = + get_token(encoded_token, network->chain_id, token_address); + if (token == NULL) { + // error while decoding, failure was sent by get_token + return NULL; + } + defs.token = token; + } else { + defs.token = &UNKNOWN_TOKEN; + } + + return &defs; +} diff --git a/legacy/firmware/ethereum_definitions.h b/legacy/firmware/ethereum_definitions.h new file mode 100644 index 000000000..520e7d640 --- /dev/null +++ b/legacy/firmware/ethereum_definitions.h @@ -0,0 +1,37 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (C) 2022 Martin Novak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __ETHEREUM_DEFINITIONS_H__ +#define __ETHEREUM_DEFINITIONS_H__ + +#include "messages-ethereum-definitions.pb.h" + +typedef EthereumDefinitions_encoded_network_t EncodedNetwork; +typedef EthereumDefinitions_encoded_token_t EncodedToken; + +typedef struct { + const EthereumNetworkInfo *network; + const EthereumTokenInfo *token; +} EthereumDefinitionsDecoded; + +const EthereumDefinitionsDecoded *ethereum_get_definitions( + const EncodedNetwork *encoded_network, const EncodedToken *encoded_token, + const uint64_t chain_id, const uint32_t slip44, const char *token_address); + +#endif diff --git a/legacy/firmware/ethereum_definitions_constants.h.mako b/legacy/firmware/ethereum_definitions_constants.h.mako new file mode 100644 index 000000000..1a2a28c65 --- /dev/null +++ b/legacy/firmware/ethereum_definitions_constants.h.mako @@ -0,0 +1,26 @@ +// This file is automatically generated from ethereum_definitions_constants.h.mako +// DO NOT EDIT + +#ifndef __ETHEREUM_DEFINITIONS_CONSTANTS_H__ +#define __ETHEREUM_DEFINITIONS_CONSTANTS_H__ + +#include + +#include "crypto.h" +#include "messages-ethereum-definitions.pb.h" +#include "pb.h" + +static const uint8_t DEFINITIONS_PUBLIC_KEY[] = + " "; // TODO: update +#if DEBUG_LINK +static const uint8_t DEFINITIONS_DEV_PUBLIC_KEY[] = + "\xdb\x99\x5f\xe2\x51\x69\xd1\x41\xca\xb9\xbb\xba\x92\xba\xa0\x1f\x9f\x2e" + "\x1e\xce\x7d\xf4\xcb\x2a\xc0\x51\x90\xf3\x7f\xcc\x1f\x9d"; +#endif + +#define MIN_DATA_VERSION ${ethereum_defs_timestamp} +#define FORMAT_VERSION_LENGTH 5 +#define FORMAT_VERSION (const pb_byte_t *)"trzd1" +#define MERKLE_TREE_SIGNED_ROOT_SIZE sizeof(ed25519_signature) + +#endif diff --git a/legacy/firmware/ethereum_networks.c.mako b/legacy/firmware/ethereum_networks.c.mako new file mode 100644 index 000000000..92e10420d --- /dev/null +++ b/legacy/firmware/ethereum_networks.c.mako @@ -0,0 +1,48 @@ +<% networks = list(supported_on("trezor1", eth)) %>\ +// This file is automatically generated from ethereum_networks.c.mako +// DO NOT EDIT + +#include "ethereum.h" +#include "ethereum_networks.h" + +#define NETWORKS_COUNT ${len(networks)} + +static const EthereumNetworkInfo networks[NETWORKS_COUNT] = { +% for n in networks: + { + .chain_id = ${n.chain_id}, + .slip44 = ${n.slip44}, + .symbol = ${c_str(n.shortcut)}, /* ${n.name} */ + .name = "", + }, +% endfor +}; + +const EthereumNetworkInfo UNKNOWN_NETWORK = { + .chain_id = CHAIN_ID_UNKNOWN, + .slip44 = SLIP44_UNKNOWN, + .symbol = "UNKN", + .name = "", +}; + +const EthereumNetworkInfo *ethereum_get_network_by_chain_id(uint64_t chain_id) { + for (size_t i = 0; i < NETWORKS_COUNT; i++) { + if (networks[i].chain_id == chain_id) { + return &networks[i]; + } + } + return &UNKNOWN_NETWORK; +} + +const EthereumNetworkInfo *ethereum_get_network_by_slip44(uint32_t slip44) { + for (size_t i = 0; i < NETWORKS_COUNT; i++) { + if (networks[i].slip44 == slip44) { + return &networks[i]; + } + } + return &UNKNOWN_NETWORK; +} + +bool is_unknown_network(const EthereumNetworkInfo *network) { + return network->chain_id == CHAIN_ID_UNKNOWN; +} diff --git a/legacy/firmware/ethereum_networks.h b/legacy/firmware/ethereum_networks.h new file mode 100644 index 000000000..c003f1e6f --- /dev/null +++ b/legacy/firmware/ethereum_networks.h @@ -0,0 +1,34 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (C) 2018 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __ETHEREUM_NETWORKS_H__ +#define __ETHEREUM_NETWORKS_H__ + +#include +#include +#include "messages-ethereum-definitions.pb.h" + +extern const EthereumNetworkInfo UNKNOWN_NETWORK; +#define SLIP44_UNKNOWN UINT32_MAX + +const EthereumNetworkInfo *ethereum_get_network_by_chain_id(uint64_t chain_id); +const EthereumNetworkInfo *ethereum_get_network_by_slip44(uint32_t slip44); +bool is_unknown_network(const EthereumNetworkInfo *network); + +#endif diff --git a/legacy/firmware/ethereum_networks.h.mako b/legacy/firmware/ethereum_networks.h.mako deleted file mode 100644 index 5049f883c..000000000 --- a/legacy/firmware/ethereum_networks.h.mako +++ /dev/null @@ -1,61 +0,0 @@ -<% -BKSL = "\\" - -networks = list(supported_on("trezor1", eth)) -max_chain_id_length = 0 -max_slip44_length = 0 -max_suffix_length = 0 -for n in networks: - max_chain_id_length = max(len(str(n.chain_id)), max_chain_id_length) - max_slip44_length = max(len(str(n.slip44)), max_slip44_length) - max_suffix_length = max(len(n.shortcut), max_suffix_length) - -def align_chain_id(n): - return "{:>{w}}".format(n.chain_id, w=max_chain_id_length) - -def align_slip44(n): - return "{:>{w}}".format(n.slip44, w=max_slip44_length) - -def align_suffix(n): - cstr = c_str(" " + n.shortcut) + ";" - # we add two quotes, a space and a semicolon. hence +4 chars - return "{:<{w}}".format(cstr, w=max_suffix_length + 4) - -%>\ -// This file is automatically generated from ethereum_networks.h.mako -// DO NOT EDIT - -#ifndef __ETHEREUM_NETWORKS_H__ -#define __ETHEREUM_NETWORKS_H__ - -#define SLIP44_UNKNOWN UINT32_MAX - -#define ASSIGN_ETHEREUM_SUFFIX(suffix, chain_id) ${BKSL} - switch (chain_id) { ${BKSL} -% for n in networks: - case ${align_chain_id(n)}: suffix = ${align_suffix(n)} break; /* ${n.name} */ ${BKSL} -% endfor - default: suffix = " UNKN"; break; /* unknown chain */ ${BKSL} - } - -static bool is_ethereum_slip44(uint32_t slip44) { - switch (slip44) { -% for slip44 in sorted(set(n.slip44 for n in networks)): - case ${slip44}: -% endfor - return true; - default: - return false; - } -} - -static int32_t ethereum_slip44_by_chain_id(uint64_t chain_id) { - switch (chain_id) { -% for n in networks: - case ${align_chain_id(n)}: return ${align_slip44(n)}; /* ${n.name} */ -% endfor - default: return SLIP44_UNKNOWN; /* unknown chain */ - } -} - -#endif diff --git a/legacy/firmware/ethereum_tokens.c.mako b/legacy/firmware/ethereum_tokens.c.mako index 19737f84d..462dc7085 100644 --- a/legacy/firmware/ethereum_tokens.c.mako +++ b/legacy/firmware/ethereum_tokens.c.mako @@ -2,24 +2,50 @@ // DO NOT EDIT #include +#include "ethereum.h" #include "ethereum_tokens.h" -const TokenType tokens[TOKENS_COUNT] = { -% for t in supported_on("trezor1", erc20): - {${"{:>2}".format(t.chain_id)}, ${c_str(t.address_bytes)}, " ${ascii(t.symbol)}", ${t.decimals}}, // ${t.chain} / ${t.name} +<% erc20_list = list(supported_on("trezor1", erc20)) %>\ +#define TOKENS_COUNT ${len(erc20_list)} + +static const EthereumTokenInfo tokens[TOKENS_COUNT] = { +% for t in sorted(erc20_list, key=lambda token: (token.chain_id, token.name)): + { + .symbol = "${ascii(t.symbol)}", + .decimals = ${t.decimals}, + .address = { + .size = 20, + .bytes = ${c_str(t.address_bytes)} + }, + .chain_id = ${t.chain_id}, +## .name = "${t.name}" + .name = "", + }, % endfor }; -static const TokenType _UnknownToken = { 0, "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", " UNKN", 0 }; -const TokenType *UnknownToken = &_UnknownToken; +const EthereumTokenInfo UNKNOWN_TOKEN = { + .symbol = "Wei UNKN", + .decimals = 0, + .address = { + .size = 20, + .bytes = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", + }, + .chain_id = CHAIN_ID_UNKNOWN, + .name = "", +}; -const TokenType *tokenByChainAddress(uint64_t chain_id, const uint8_t *address) +const EthereumTokenInfo *ethereum_token_by_address(uint64_t chain_id, const uint8_t *address) { - if (!address) return 0; - for (int i = 0; i < TOKENS_COUNT; i++) { - if (chain_id == tokens[i].chain_id && memcmp(address, tokens[i].address, 20) == 0) { - return &(tokens[i]); - } - } - return UnknownToken; + if (!address) return 0; + for (int i = 0; i < TOKENS_COUNT; i++) { + if (chain_id == tokens[i].chain_id && memcmp(address, tokens[i].address.bytes, sizeof(tokens[i].address.bytes)) == 0) { + return &(tokens[i]); + } + } + return &UNKNOWN_TOKEN; +} + +bool is_unknown_token(const EthereumTokenInfo *token) { + return token->chain_id == CHAIN_ID_UNKNOWN; } diff --git a/legacy/firmware/ethereum_tokens.h b/legacy/firmware/ethereum_tokens.h new file mode 100644 index 000000000..e56e9f63b --- /dev/null +++ b/legacy/firmware/ethereum_tokens.h @@ -0,0 +1,32 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (C) 2023 Trezor Company s.r.o. + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __ETHEREUM_TOKENS_H__ +#define __ETHEREUM_TOKENS_H__ + +#include +#include "ethereum_definitions.h" + +extern const EthereumTokenInfo UNKNOWN_TOKEN; + +const EthereumTokenInfo *ethereum_token_by_address(uint64_t chain_id, + const uint8_t *address); +bool is_unknown_token(const EthereumTokenInfo *token); + +#endif diff --git a/legacy/firmware/ethereum_tokens.h.mako b/legacy/firmware/ethereum_tokens.h.mako deleted file mode 100644 index 160506c3d..000000000 --- a/legacy/firmware/ethereum_tokens.h.mako +++ /dev/null @@ -1,25 +0,0 @@ -// This file is automatically generated from ethereum_tokens.h.mako -// DO NOT EDIT - -#ifndef __ETHEREUM_TOKENS_H__ -#define __ETHEREUM_TOKENS_H__ - -#include - -<% erc20_list = list(supported_on("trezor1", erc20)) %>\ -#define TOKENS_COUNT ${len(erc20_list)} - -typedef struct { - uint64_t chain_id; - const char * const address; - const char * const ticker; - int decimals; -} TokenType; - -extern const TokenType tokens[TOKENS_COUNT]; - -extern const TokenType *UnknownToken; - -const TokenType *tokenByChainAddress(uint64_t chain_id, const uint8_t *address); - -#endif diff --git a/legacy/firmware/fsm.c b/legacy/firmware/fsm.c index 9ad8a4cbb..07c4c24b1 100644 --- a/legacy/firmware/fsm.c +++ b/legacy/firmware/fsm.c @@ -56,6 +56,8 @@ #if !BITCOIN_ONLY #include "ethereum.h" +#include "ethereum_definitions.h" +#include "ethereum_networks.h" #include "nem.h" #include "nem2.h" #include "stellar.h" diff --git a/legacy/firmware/fsm_msg_ethereum.h b/legacy/firmware/fsm_msg_ethereum.h index a03b66294..7e2f5c8d4 100644 --- a/legacy/firmware/fsm_msg_ethereum.h +++ b/legacy/firmware/fsm_msg_ethereum.h @@ -19,9 +19,8 @@ static bool fsm_ethereumCheckPath(uint32_t address_n_count, const uint32_t *address_n, bool pubkey_export, - uint64_t chain_id) { - if (ethereum_path_check(address_n_count, address_n, pubkey_export, - chain_id)) { + const EthereumNetworkInfo *network) { + if (ethereum_path_check(address_n_count, address_n, pubkey_export, network)) { return true; } @@ -33,6 +32,38 @@ static bool fsm_ethereumCheckPath(uint32_t address_n_count, return fsm_layoutPathWarning(); } +static const EthereumDefinitionsDecoded *get_definitions( + bool has_definitions, const EthereumDefinitions *definitions, + uint64_t chain_id, const char *to) { + const EncodedNetwork *encoded_network = NULL; + const EncodedToken *encoded_token = NULL; + if (has_definitions && definitions) { + if (definitions->has_encoded_network) { + encoded_network = &definitions->encoded_network; + } + if (definitions->has_encoded_token) { + encoded_token = &definitions->encoded_token; + } + } + + return ethereum_get_definitions(encoded_network, encoded_token, chain_id, + SLIP44_UNKNOWN, to); +} + +static const EthereumNetworkInfo *get_network_definition_only( + bool has_encoded_network, const EncodedNetwork *encoded_network, + const uint32_t slip44) { + const EncodedNetwork *en = NULL; + if (has_encoded_network) { + en = encoded_network; + } + + const EthereumDefinitionsDecoded *defs = + ethereum_get_definitions(en, NULL, CHAIN_ID_UNKNOWN, slip44, NULL); + + return defs ? defs->network : NULL; +} + void fsm_msgEthereumGetPublicKey(const EthereumGetPublicKey *msg) { RESP_INIT(EthereumPublicKey); @@ -44,12 +75,6 @@ void fsm_msgEthereumGetPublicKey(const EthereumGetPublicKey *msg) { const CoinInfo *coin = fsm_getCoin(true, "Bitcoin"); if (!coin) return; - if (!fsm_ethereumCheckPath(msg->address_n_count, msg->address_n, true, - CHAIN_ID_UNKNOWN)) { - layoutHome(); - return; - } - const char *curve = coin->curve_name; uint32_t fingerprint; HDNode *node = fsm_getDerivedNode(curve, msg->address_n, msg->address_n_count, @@ -93,8 +118,12 @@ void fsm_msgEthereumSignTx(const EthereumSignTx *msg) { CHECK_PIN - if (!fsm_ethereumCheckPath(msg->address_n_count, msg->address_n, false, - msg->chain_id)) { + const EthereumDefinitionsDecoded *defs = + get_definitions(msg->has_definitions, &msg->definitions, msg->chain_id, + msg->has_to ? msg->to : NULL); + + if (!defs || !fsm_ethereumCheckPath(msg->address_n_count, msg->address_n, + false, defs->network)) { layoutHome(); return; } @@ -103,7 +132,7 @@ void fsm_msgEthereumSignTx(const EthereumSignTx *msg) { msg->address_n_count, NULL); if (!node) return; - ethereum_signing_init(msg, node); + ethereum_signing_init(msg, node, defs); } void fsm_msgEthereumSignTxEIP1559(const EthereumSignTxEIP1559 *msg) { @@ -111,8 +140,12 @@ void fsm_msgEthereumSignTxEIP1559(const EthereumSignTxEIP1559 *msg) { CHECK_PIN - if (!fsm_ethereumCheckPath(msg->address_n_count, msg->address_n, false, - msg->chain_id)) { + const EthereumDefinitionsDecoded *defs = + get_definitions(msg->has_definitions, &msg->definitions, msg->chain_id, + msg->has_to ? msg->to : NULL); + + if (!defs || !fsm_ethereumCheckPath(msg->address_n_count, msg->address_n, + false, defs->network)) { layoutHome(); return; } @@ -121,7 +154,7 @@ void fsm_msgEthereumSignTxEIP1559(const EthereumSignTxEIP1559 *msg) { msg->address_n_count, NULL); if (!node) return; - ethereum_signing_init_eip1559(msg, node); + ethereum_signing_init_eip1559(msg, node, defs); } void fsm_msgEthereumTxAck(const EthereumTxAck *msg) { @@ -137,8 +170,16 @@ void fsm_msgEthereumGetAddress(const EthereumGetAddress *msg) { CHECK_PIN - if (!fsm_ethereumCheckPath(msg->address_n_count, msg->address_n, false, - CHAIN_ID_UNKNOWN)) { + uint32_t slip44 = (msg->address_n_count > 1) + ? (msg->address_n[1] & PATH_UNHARDEN_MASK) + : SLIP44_UNKNOWN; + + const EthereumNetworkInfo *network = get_network_definition_only( + msg->has_encoded_network, (const EncodedNetwork *)&msg->encoded_network, + slip44); + + if (!network || !fsm_ethereumCheckPath(msg->address_n_count, msg->address_n, + false, network)) { layoutHome(); return; } @@ -153,9 +194,6 @@ void fsm_msgEthereumGetAddress(const EthereumGetAddress *msg) { layoutHome(); return; } - - uint32_t slip44 = - (msg->address_n_count > 1) ? (msg->address_n[1] & PATH_UNHARDEN_MASK) : 0; bool rskip60 = false; uint64_t chain_id = 0; // constants from trezor-common/defs/ethereum/networks.json @@ -195,8 +233,16 @@ void fsm_msgEthereumSignMessage(const EthereumSignMessage *msg) { CHECK_PIN - if (!fsm_ethereumCheckPath(msg->address_n_count, msg->address_n, false, - CHAIN_ID_UNKNOWN)) { + uint32_t slip44 = (msg->address_n_count > 1) + ? (msg->address_n[1] & PATH_UNHARDEN_MASK) + : SLIP44_UNKNOWN; + + const EthereumNetworkInfo *network = get_network_definition_only( + msg->has_encoded_network, (const EncodedNetwork *)&msg->encoded_network, + slip44); + + if (!network || !fsm_ethereumCheckPath(msg->address_n_count, msg->address_n, + false, network)) { layoutHome(); return; } @@ -282,8 +328,16 @@ void fsm_msgEthereumSignTypedHash(const EthereumSignTypedHash *msg) { return; } - if (!fsm_ethereumCheckPath(msg->address_n_count, msg->address_n, false, - CHAIN_ID_UNKNOWN)) { + uint32_t slip44 = (msg->address_n_count > 1) + ? (msg->address_n[1] & PATH_UNHARDEN_MASK) + : SLIP44_UNKNOWN; + + const EthereumNetworkInfo *network = get_network_definition_only( + msg->has_encoded_network, (const EncodedNetwork *)&msg->encoded_network, + slip44); + + if (!network || !fsm_ethereumCheckPath(msg->address_n_count, msg->address_n, + false, network)) { layoutHome(); return; } diff --git a/legacy/firmware/protob/Makefile b/legacy/firmware/protob/Makefile index 3857a3bd6..8b1f36139 100644 --- a/legacy/firmware/protob/Makefile +++ b/legacy/firmware/protob/Makefile @@ -14,7 +14,8 @@ SKIPPED_MESSAGES += Ethereum NEM Stellar endif PROTO_NAMES = messages messages-bitcoin messages-common messages-crypto messages-debug \ - messages-ethereum messages-management messages-nem messages-stellar + messages-ethereum messages-ethereum-definitions messages-management messages-nem \ + messages-stellar PROTO_OPTIONS = $(PROTO_NAMES:=.options) PROTO_COMPILED = $(PROTO_NAMES:=.pb) diff --git a/legacy/firmware/protob/messages-ethereum-definitions.options b/legacy/firmware/protob/messages-ethereum-definitions.options new file mode 100644 index 000000000..1dc15cb05 --- /dev/null +++ b/legacy/firmware/protob/messages-ethereum-definitions.options @@ -0,0 +1,9 @@ +EthereumNetworkInfo.name max_size:256 +EthereumNetworkInfo.symbol max_size:256 + +EthereumTokenInfo.name max_size:256 +EthereumTokenInfo.address max_size:20 +EthereumTokenInfo.symbol max_size:256 + +EthereumDefinitions.encoded_network max_size:1024 +EthereumDefinitions.encoded_token max_size:1024 diff --git a/legacy/firmware/protob/messages-ethereum-definitions.proto b/legacy/firmware/protob/messages-ethereum-definitions.proto new file mode 120000 index 000000000..dd7688aaf --- /dev/null +++ b/legacy/firmware/protob/messages-ethereum-definitions.proto @@ -0,0 +1 @@ +../../vendor/trezor-common/protob/messages-ethereum-definitions.proto \ No newline at end of file diff --git a/legacy/firmware/protob/messages-ethereum.options b/legacy/firmware/protob/messages-ethereum.options index e90950d30..cebf7c1cb 100644 --- a/legacy/firmware/protob/messages-ethereum.options +++ b/legacy/firmware/protob/messages-ethereum.options @@ -26,6 +26,7 @@ EthereumTxAck.data_chunk max_size:1024 EthereumSignMessage.address_n max_count:8 EthereumSignMessage.message max_size:1024 +EthereumSignMessage.encoded_network max_size:1024 EthereumVerifyMessage.address max_size:43 EthereumVerifyMessage.signature max_size:65 @@ -37,11 +38,14 @@ EthereumMessageSignature.signature max_size:65 EthereumSignTypedHash.address_n max_count:8 EthereumSignTypedHash.domain_separator_hash max_size:32 EthereumSignTypedHash.message_hash max_size:32 +EthereumSignTypedHash.encoded_network max_size:1024 EthereumTypedDataSignature.address max_size:43 EthereumTypedDataSignature.signature max_size:65 EthereumGetAddress.address_n max_count:8 +EthereumGetAddress.encoded_network max_size:1024 + EthereumGetPublicKey.address_n max_count:8 EthereumAddress._old_address max_size:20