mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-08-05 13:26:57 +00:00
283 lines
12 KiB
C
283 lines
12 KiB
C
/*
|
|
* This file is part of the Trezor project, https://trezor.io/
|
|
*
|
|
* Copyright (C) 2022 Martin Novak <martin.novak@satoshilabs.com>
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
#include "crypto.h"
|
|
#include "ethereum.h"
|
|
#include "ethereum_definitions.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"
|
|
|
|
static const uint8_t DEFINITIONS_PUBLIC_KEY[] = " ";
|
|
#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 1
|
|
#define FORMAT_VERSION_LENGTH 8
|
|
#define FORMAT_VERSION (const pb_byte_t *) "trzd1\x00\x00\x00"
|
|
#define MERKLE_TREE_SIGNED_ROOT_SIZE 64
|
|
|
|
#define HASH_DATA_BUFFER_SIZE (1 + MAX(EthereumEncodedDefinitions_size / 2, 2 * SHA256_DIGEST_LENGTH))
|
|
|
|
|
|
typedef struct {
|
|
// prefix
|
|
pb_size_t format_version_start;
|
|
uint8_t definition_type;
|
|
uint32_t data_version;
|
|
uint16_t payload_length_in_bytes;
|
|
|
|
// payload
|
|
pb_size_t payload_start;
|
|
|
|
// suffix
|
|
uint8_t proof_length;
|
|
pb_size_t proof_start;
|
|
|
|
ed25519_signature signed_root_hash;
|
|
} ParsedEncodedEthereumDefinitions;
|
|
|
|
const ParsedEncodedEthereumDefinitions *_parse_encoded_EthereumDefinitions(const pb_size_t size, const pb_byte_t *bytes) {
|
|
static ParsedEncodedEthereumDefinitions parsed;
|
|
|
|
// 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 (const ParsedEncodedEthereumDefinitions * const) NULL;
|
|
}
|
|
|
|
pb_size_t actual_position = 0;
|
|
parsed.format_version_start = actual_position;
|
|
actual_position += FORMAT_VERSION_LENGTH;
|
|
|
|
parsed.definition_type = bytes[actual_position];
|
|
actual_position += 1;
|
|
|
|
parsed.data_version = read_be(&bytes[actual_position]);
|
|
actual_position += 4;
|
|
|
|
parsed.payload_length_in_bytes = (((uint16_t)bytes[actual_position]) << 8) | (((uint16_t)bytes[actual_position+1]));
|
|
actual_position += 2;
|
|
|
|
if (size < actual_position - 1) {
|
|
return (const ParsedEncodedEthereumDefinitions * const) NULL;
|
|
}
|
|
parsed.payload_start = actual_position;
|
|
actual_position += parsed.payload_length_in_bytes;
|
|
|
|
if (size < actual_position - 1) {
|
|
return (const ParsedEncodedEthereumDefinitions * const) NULL;
|
|
}
|
|
parsed.proof_length = bytes[actual_position];
|
|
actual_position += 1;
|
|
|
|
if (size < actual_position - 1) {
|
|
return (const ParsedEncodedEthereumDefinitions * const) NULL;
|
|
}
|
|
parsed.proof_start = actual_position;
|
|
actual_position += parsed.proof_length * SHA256_DIGEST_LENGTH;
|
|
|
|
if (size < actual_position + MERKLE_TREE_SIGNED_ROOT_SIZE - 1) {
|
|
return (const ParsedEncodedEthereumDefinitions * const) NULL;
|
|
}
|
|
memcpy(&parsed.signed_root_hash, &bytes[actual_position], MERKLE_TREE_SIGNED_ROOT_SIZE);
|
|
|
|
return (const ParsedEncodedEthereumDefinitions * const) &parsed;
|
|
}
|
|
|
|
|
|
bool _decode_definition(const pb_size_t size, const pb_byte_t *bytes, const EthereumDefinitionType expected_type, void *definition) {
|
|
// parse received definition
|
|
const ParsedEncodedEthereumDefinitions *parsed_def = _parse_encoded_EthereumDefinitions(size, bytes);
|
|
if (!parsed_def) {
|
|
fsm_sendFailure(FailureType_Failure_DataError, _("Invalid Ethereum definition."));
|
|
return false;
|
|
}
|
|
|
|
// check definition fields
|
|
if (memcmp(FORMAT_VERSION, &bytes[parsed_def->format_version_start], FORMAT_VERSION_LENGTH)) {
|
|
fsm_sendFailure(FailureType_Failure_DataError, _("Used different Ethereum definition format version."));
|
|
return false;
|
|
}
|
|
|
|
if (expected_type != parsed_def->definition_type) {
|
|
fsm_sendFailure(FailureType_Failure_DataError, _("Definition of invalid type for Ethereum."));
|
|
return false;
|
|
}
|
|
|
|
if (MIN_DATA_VERSION > parsed_def->data_version) {
|
|
fsm_sendFailure(FailureType_Failure_DataError, _("Used Ethereum definition data version too low."));
|
|
return false;
|
|
}
|
|
|
|
// compute Merkle tree root hash from proof
|
|
uint8_t hash[SHA256_DIGEST_LENGTH] = {0};
|
|
uint8_t hash_data[HASH_DATA_BUFFER_SIZE];
|
|
memzero(hash_data, HASH_DATA_BUFFER_SIZE);
|
|
// leaf hash = sha256('\x00' + leaf data)
|
|
memcpy(&hash_data[1], bytes, parsed_def->payload_start + parsed_def->payload_length_in_bytes);
|
|
sha256_Raw(hash_data, 1 + parsed_def->payload_start + parsed_def->payload_length_in_bytes, hash);
|
|
|
|
pb_size_t index = parsed_def->proof_start;
|
|
int cmp = 0;
|
|
const void *min, *max;
|
|
for (uint8_t i = 0; i < parsed_def->proof_length; i++) {
|
|
memzero(hash_data, HASH_DATA_BUFFER_SIZE);
|
|
// node hash = sha256('\x01' + min(hash, next_proof) + max(hash, next_proof))
|
|
hash_data[0] = '\x01';
|
|
cmp = memcmp(hash, &bytes[index], SHA256_DIGEST_LENGTH);
|
|
min = cmp < 1 ? hash : &bytes[index];
|
|
max = cmp > 0 ? hash : &bytes[index];
|
|
memcpy(&hash_data[1], min, SHA256_DIGEST_LENGTH);
|
|
memcpy(&hash_data[1 + SHA256_DIGEST_LENGTH], max, SHA256_DIGEST_LENGTH);
|
|
sha256_Raw(hash_data, 1 + SHA256_DIGEST_LENGTH * 2, hash);
|
|
index += SHA256_DIGEST_LENGTH;
|
|
}
|
|
|
|
// 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
|
|
fsm_sendFailure(FailureType_Failure_DataError, _("Ethereum definition signature is invalid."));
|
|
return false;
|
|
}
|
|
|
|
// decode message
|
|
const pb_msgdesc_t *fields = (expected_type == EthereumDefinitionType_NETWORK ? EthereumNetworkInfo_fields : EthereumTokenInfo_fields);
|
|
memzero(definition, sizeof(definition));
|
|
pb_istream_t stream = pb_istream_from_buffer(&bytes[parsed_def->payload_start], parsed_def->payload_length_in_bytes);
|
|
bool status = pb_decode(&stream, fields, definition);
|
|
if (!status) {
|
|
// invalid message
|
|
fsm_sendFailure(FailureType_Failure_DataError, stream.errmsg);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void _set_EthereumNetworkInfo_to_builtin(const uint64_t ref_chain_id, EthereumNetworkInfo *network) {
|
|
network->chain_id = ref_chain_id;
|
|
network->slip44 = ethereum_slip44_by_chain_id(ref_chain_id);
|
|
memzero(network->shortcut, sizeof(network->shortcut));
|
|
const char *sc = get_ethereum_suffix(ref_chain_id);
|
|
strncpy(network->shortcut, sc, sizeof(network->shortcut) - 1);
|
|
memzero(network->name, sizeof(network->name));
|
|
// network->rskip60 is skipped, it is resolved where it is needed (using switch with hardcoded values)
|
|
}
|
|
|
|
bool _get_EthereumNetworkInfo(const EthereumEncodedDefinitions_encoded_network_t *encoded_network, const uint64_t ref_chain_id, EthereumNetworkInfo *network) {
|
|
// try to get built-in definition
|
|
_set_EthereumNetworkInfo_to_builtin(ref_chain_id, network);
|
|
|
|
// if we still do not have any network definition try to decode the received one
|
|
if (strncmp(network->shortcut, UNKNOWN_NETWORK_SHORTCUT, sizeof(network->shortcut)) == 0 && encoded_network != NULL) {
|
|
if (_decode_definition(encoded_network->size, encoded_network->bytes, EthereumDefinitionType_NETWORK, (void*) network)) {
|
|
if (ref_chain_id != CHAIN_ID_UNKNOWN && network->chain_id != ref_chain_id) {
|
|
// chain_id mismatch - error and reset definition
|
|
fsm_sendFailure(FailureType_Failure_DataError, _("Invalid network definition - chain IDs not equal."));
|
|
_set_EthereumNetworkInfo_to_builtin(ref_chain_id, network);
|
|
} else {
|
|
// chain_id does match the reference one (if provided) so prepend one space character to symbol, terminate it
|
|
// (encoded definitions does not have space prefix) and return the decoded data
|
|
memmove(&network->shortcut[1], &network->shortcut, sizeof(network->shortcut) - 2);
|
|
network->shortcut[0] = ' ';
|
|
network->shortcut[sizeof(network->shortcut) - 1] = 0;
|
|
return true;
|
|
}
|
|
} else {
|
|
// decoding failed - reset network definition
|
|
_set_EthereumNetworkInfo_to_builtin(ref_chain_id, network);
|
|
}
|
|
}
|
|
|
|
return network->chain_id == CHAIN_ID_UNKNOWN ? false : true;
|
|
}
|
|
|
|
void _get_EthereumTokenInfo(const EthereumEncodedDefinitions_encoded_token_t *encoded_token, const uint64_t ref_chain_id, const char *ref_address, EthereumTokenInfo* token) {
|
|
EthereumTokenInfo_address_t ref_address_bytes;
|
|
const EthereumTokenInfo* builtin = UnknownToken;
|
|
|
|
// convert ref_address string to bytes
|
|
bool address_parsed = ref_address && ethereum_parse(ref_address, ref_address_bytes.bytes);
|
|
|
|
// try to get built-in definition
|
|
if (address_parsed) {
|
|
builtin = tokenByChainAddress(ref_chain_id, ref_address_bytes.bytes);
|
|
}
|
|
|
|
// if we do not have any token definition try to decode the received one
|
|
if (builtin == UnknownToken && encoded_token != NULL) {
|
|
if (_decode_definition(encoded_token->size, encoded_token->bytes, EthereumDefinitionType_TOKEN, (void*) token)) {
|
|
|
|
if ((ref_chain_id == CHAIN_ID_UNKNOWN || token->chain_id == ref_chain_id) && (!address_parsed || !memcmp(token->address.bytes, ref_address_bytes.bytes, sizeof(token->address.bytes)))) {
|
|
// chain_id and/or address does match the reference ones (if provided) so prepend one space character to symbol, terminate it
|
|
// (encoded definitions does not have space prefix) and return the decoded data
|
|
memmove(&token->symbol[1], &token->symbol, sizeof(token->symbol) - 2);
|
|
token->symbol[0] = ' ';
|
|
token->symbol[sizeof(token->symbol) - 1] = 0;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// decoding did not happen or failed, so we have to copy the data to the result
|
|
// reset token definition
|
|
memzero(token->symbol, sizeof(token->symbol));
|
|
token->decimals = 0;
|
|
memzero(token->address.bytes, sizeof(token->address.bytes));
|
|
token->address.size = 0;
|
|
token->chain_id = CHAIN_ID_UNKNOWN;
|
|
memzero(token->name, sizeof(token->name));
|
|
|
|
// copy data to token definition
|
|
strncpy(token->symbol, builtin->symbol, sizeof(token->symbol) - 1);
|
|
token->decimals = builtin->decimals;
|
|
memcpy(token->address.bytes, builtin->address.bytes, sizeof(token->address.bytes));
|
|
token->address.size = sizeof(token->address.bytes);
|
|
token->chain_id = builtin->chain_id;
|
|
}
|
|
|
|
const EthereumDefinitions *get_EthereumDefinitions(const EthereumEncodedDefinitions_encoded_network_t *encoded_network, const EthereumEncodedDefinitions_encoded_token_t *encoded_token, const uint64_t ref_chain_id, const char *ref_address) {
|
|
static EthereumDefinitions defs;
|
|
|
|
if (_get_EthereumNetworkInfo(encoded_network, ref_chain_id, &defs.network)) {
|
|
// we have found network definition, we can try to load token definition
|
|
_get_EthereumTokenInfo(encoded_token, ref_chain_id, ref_address, &defs.token);
|
|
} else {
|
|
// if we did not find any network definition, set token definition to unknown token
|
|
_get_EthereumTokenInfo(NULL, CHAIN_ID_UNKNOWN, NULL, &defs.token);
|
|
}
|
|
return &defs;
|
|
}
|