mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-28 00:58:09 +00:00
18f7820d47
[no changelog]
1129 lines
35 KiB
C
1129 lines
35 KiB
C
/*
|
|
* This file is part of the Trezor project, https://trezor.io/
|
|
*
|
|
* Copyright (C) 2016 Alex Beregszaszi <alex@rtfs.hu>
|
|
* Copyright (C) 2016 Pavol Rusnak <stick@satoshilabs.com>
|
|
* Copyright (C) 2016 Jochen Hoenicke <hoenicke@gmail.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 "ethereum.h"
|
|
#include "address.h"
|
|
#include "crypto.h"
|
|
#include "ecdsa.h"
|
|
#include "ethereum_networks.h"
|
|
#include "ethereum_tokens.h"
|
|
#include "fsm.h"
|
|
#include "gettext.h"
|
|
#include "layout2.h"
|
|
#include "memzero.h"
|
|
#include "messages.h"
|
|
#include "messages.pb.h"
|
|
#include "protect.h"
|
|
#include "secp256k1.h"
|
|
#include "sha3.h"
|
|
#include "transaction.h"
|
|
#include "util.h"
|
|
|
|
/* Maximum chain_id which returns the full signature_v (which must fit into an
|
|
uint32). chain_ids larger than this will only return one bit and the caller must
|
|
recalculate the full value: v = 2 * chain_id + 35 + v_bit */
|
|
#define MAX_CHAIN_ID ((0xFFFFFFFF - 36) >> 1)
|
|
#define EIP1559_TX_TYPE 2
|
|
|
|
static bool ethereum_signing = false;
|
|
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};
|
|
|
|
static uint32_t signing_access_list_count;
|
|
static EthereumAccessList signing_access_list[8];
|
|
_Static_assert(sizeof(signing_access_list) ==
|
|
sizeof(((EthereumSignTxEIP1559 *)NULL)->access_list),
|
|
"access_list buffer size mismatch");
|
|
|
|
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;
|
|
const uint8_t *data_initial_chunk_bytes;
|
|
|
|
bool has_to;
|
|
const char *to;
|
|
|
|
const EthereumTokenInfo *token;
|
|
|
|
uint32_t value_size;
|
|
const uint8_t *value_bytes;
|
|
};
|
|
|
|
static inline void hash_data(const uint8_t *buf, size_t size) {
|
|
sha3_Update(&keccak_ctx, buf, size);
|
|
}
|
|
|
|
/*
|
|
* Push an RLP encoded length to the hash buffer.
|
|
*/
|
|
static void hash_rlp_length(uint32_t length, uint8_t firstbyte) {
|
|
uint8_t buf[4] = {0};
|
|
if (length == 1 && firstbyte <= 0x7f) {
|
|
/* empty length header */
|
|
} else if (length <= 55) {
|
|
buf[0] = 0x80 + length;
|
|
hash_data(buf, 1);
|
|
} else if (length <= 0xff) {
|
|
buf[0] = 0xb7 + 1;
|
|
buf[1] = length;
|
|
hash_data(buf, 2);
|
|
} else if (length <= 0xffff) {
|
|
buf[0] = 0xb7 + 2;
|
|
buf[1] = length >> 8;
|
|
buf[2] = length & 0xff;
|
|
hash_data(buf, 3);
|
|
} else {
|
|
buf[0] = 0xb7 + 3;
|
|
buf[1] = length >> 16;
|
|
buf[2] = length >> 8;
|
|
buf[3] = length & 0xff;
|
|
hash_data(buf, 4);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Push an RLP encoded list length to the hash buffer.
|
|
*/
|
|
static void hash_rlp_list_length(uint32_t length) {
|
|
uint8_t buf[4] = {0};
|
|
if (length <= 55) {
|
|
buf[0] = 0xc0 + length;
|
|
hash_data(buf, 1);
|
|
} else if (length <= 0xff) {
|
|
buf[0] = 0xf7 + 1;
|
|
buf[1] = length;
|
|
hash_data(buf, 2);
|
|
} else if (length <= 0xffff) {
|
|
buf[0] = 0xf7 + 2;
|
|
buf[1] = length >> 8;
|
|
buf[2] = length & 0xff;
|
|
hash_data(buf, 3);
|
|
} else {
|
|
buf[0] = 0xf7 + 3;
|
|
buf[1] = length >> 16;
|
|
buf[2] = length >> 8;
|
|
buf[3] = length & 0xff;
|
|
hash_data(buf, 4);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Push an RLP encoded length field and data to the hash buffer.
|
|
*/
|
|
static void hash_rlp_field(const uint8_t *buf, size_t size) {
|
|
hash_rlp_length(size, buf[0]);
|
|
hash_data(buf, size);
|
|
}
|
|
|
|
/*
|
|
* Push an RLP encoded number to the hash buffer.
|
|
* Ethereum yellow paper says to convert to big endian and strip leading zeros.
|
|
*/
|
|
static void hash_rlp_number(uint64_t number) {
|
|
if (!number) {
|
|
return;
|
|
}
|
|
uint8_t data[8] = {0};
|
|
data[0] = (number >> 56) & 0xff;
|
|
data[1] = (number >> 48) & 0xff;
|
|
data[2] = (number >> 40) & 0xff;
|
|
data[3] = (number >> 32) & 0xff;
|
|
data[4] = (number >> 24) & 0xff;
|
|
data[5] = (number >> 16) & 0xff;
|
|
data[6] = (number >> 8) & 0xff;
|
|
data[7] = (number) & 0xff;
|
|
int offset = 0;
|
|
while (!data[offset]) {
|
|
offset++;
|
|
}
|
|
hash_rlp_field(data + offset, 8 - offset);
|
|
}
|
|
|
|
/*
|
|
* Calculate the number of bytes needed for an RLP length header.
|
|
* NOTE: supports up to 16MB of data (how unlikely...)
|
|
* FIXME: improve
|
|
*/
|
|
static int rlp_calculate_length(int length, uint8_t firstbyte) {
|
|
if (length == 1 && firstbyte <= 0x7f) {
|
|
return 1;
|
|
} else if (length <= 55) {
|
|
return 1 + length;
|
|
} else if (length <= 0xff) {
|
|
return 2 + length;
|
|
} else if (length <= 0xffff) {
|
|
return 3 + length;
|
|
} else {
|
|
return 4 + length;
|
|
}
|
|
}
|
|
|
|
/* If number is less than 0x80 the RLP encoding is iteself (1 byte).
|
|
* If it is 0x80 or larger, RLP encoding is 1 + length in bytes.
|
|
*/
|
|
static int rlp_calculate_number_length(uint64_t number) {
|
|
int length = 1;
|
|
if (number >= 0x80) {
|
|
while (number) {
|
|
length++;
|
|
number = number >> 8;
|
|
}
|
|
}
|
|
return length;
|
|
}
|
|
|
|
static uint32_t rlp_calculate_access_list_keys_length(
|
|
const EthereumAccessList_storage_keys_t *keys, uint32_t keys_count) {
|
|
uint32_t keys_length = 0;
|
|
for (size_t i = 0; i < keys_count; i++) {
|
|
keys_length += rlp_calculate_length(keys[i].size, keys[i].bytes[0]);
|
|
}
|
|
return keys_length;
|
|
}
|
|
|
|
static uint32_t rlp_calculate_access_list_length(
|
|
const EthereumAccessList access_list[8], uint32_t access_list_count) {
|
|
uint32_t length = 0;
|
|
for (size_t i = 0; i < access_list_count; i++) {
|
|
uint32_t address_length = rlp_calculate_length(20, 0xff);
|
|
uint32_t keys_length = rlp_calculate_access_list_keys_length(
|
|
access_list[i].storage_keys, access_list[i].storage_keys_count);
|
|
length += rlp_calculate_length(
|
|
address_length + rlp_calculate_length(keys_length, 0xff), 0xff);
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
static void send_request_chunk(void) {
|
|
int progress = 1000 - (data_total > 1000000 ? data_left / (data_total / 800)
|
|
: data_left * 800 / data_total);
|
|
layoutProgress(_("Signing"), progress);
|
|
msg_tx_request.has_data_length = true;
|
|
msg_tx_request.data_length = data_left <= 1024 ? data_left : 1024;
|
|
msg_write(MessageType_MessageType_EthereumTxRequest, &msg_tx_request);
|
|
}
|
|
|
|
static int ethereum_is_canonic(uint8_t v, uint8_t signature[64]) {
|
|
(void)signature;
|
|
return (v & 2) == 0;
|
|
}
|
|
|
|
static void send_signature(void) {
|
|
uint8_t hash[32] = {0}, sig[64] = {0};
|
|
uint8_t v = 0;
|
|
layoutProgress(_("Signing"), 1000);
|
|
|
|
if (eip1559) {
|
|
hash_rlp_list_length(rlp_calculate_access_list_length(
|
|
signing_access_list, signing_access_list_count));
|
|
for (size_t i = 0; i < signing_access_list_count; i++) {
|
|
uint8_t address[20] = {0};
|
|
if (!ethereum_parse(signing_access_list[i].address, address)) {
|
|
fsm_sendFailure(FailureType_Failure_DataError, _("Malformed address"));
|
|
ethereum_signing_abort();
|
|
return;
|
|
}
|
|
|
|
uint32_t address_length =
|
|
rlp_calculate_length(sizeof(address), address[0]);
|
|
uint32_t keys_length = rlp_calculate_access_list_keys_length(
|
|
signing_access_list[i].storage_keys,
|
|
signing_access_list[i].storage_keys_count);
|
|
|
|
hash_rlp_list_length(address_length +
|
|
rlp_calculate_length(keys_length, 0xff));
|
|
hash_rlp_field(address, sizeof(address));
|
|
hash_rlp_list_length(keys_length);
|
|
for (size_t j = 0; j < signing_access_list[i].storage_keys_count; j++) {
|
|
hash_rlp_field(signing_access_list[i].storage_keys[j].bytes,
|
|
signing_access_list[i].storage_keys[j].size);
|
|
}
|
|
}
|
|
} else {
|
|
/* eip-155 replay protection */
|
|
/* hash v=chain_id, r=0, s=0 */
|
|
hash_rlp_number(chain_id);
|
|
hash_rlp_length(0, 0);
|
|
hash_rlp_length(0, 0);
|
|
}
|
|
|
|
keccak_Final(&keccak_ctx, hash);
|
|
if (ecdsa_sign_digest(&secp256k1, privkey, hash, sig, &v,
|
|
ethereum_is_canonic) != 0) {
|
|
fsm_sendFailure(FailureType_Failure_ProcessError, _("Signing failed"));
|
|
ethereum_signing_abort();
|
|
return;
|
|
}
|
|
|
|
memzero(privkey, sizeof(privkey));
|
|
|
|
/* Send back the result */
|
|
msg_tx_request.has_data_length = false;
|
|
|
|
msg_tx_request.has_signature_v = true;
|
|
if (eip1559 || chain_id > MAX_CHAIN_ID) {
|
|
msg_tx_request.signature_v = v;
|
|
} else {
|
|
msg_tx_request.signature_v = v + 2 * chain_id + 35;
|
|
}
|
|
|
|
msg_tx_request.has_signature_r = true;
|
|
msg_tx_request.signature_r.size = 32;
|
|
memcpy(msg_tx_request.signature_r.bytes, sig, 32);
|
|
|
|
msg_tx_request.has_signature_s = true;
|
|
msg_tx_request.signature_s.size = 32;
|
|
memcpy(msg_tx_request.signature_s.bytes, sig + 32, 32);
|
|
|
|
msg_write(MessageType_MessageType_EthereumTxRequest, &msg_tx_request);
|
|
|
|
ethereum_signing_abort();
|
|
}
|
|
/* Format a 256 bit number (amount in wei) into a human readable format
|
|
* using standard ethereum units.
|
|
* The buffer must be at least 25 bytes.
|
|
*/
|
|
static void ethereumFormatAmount(const bignum256 *amnt,
|
|
const EthereumTokenInfo *token, char *buf,
|
|
int buflen) {
|
|
bignum256 bn1e9 = {0};
|
|
bn_read_uint32(1000000000, &bn1e9);
|
|
char suffix[50] = {' ', 0};
|
|
int decimals = 18;
|
|
if (token) {
|
|
strlcpy(suffix + 1, token->symbol, sizeof(suffix) - 1);
|
|
decimals = token->decimals;
|
|
} else if (bn_is_less(amnt, &bn1e9)) {
|
|
strlcpy(suffix + 1, "Wei", sizeof(suffix) - 1);
|
|
decimals = 0;
|
|
} else {
|
|
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 EthereumTokenInfo *token) {
|
|
bignum256 val = {0};
|
|
uint8_t pad_val[32] = {0};
|
|
memzero(pad_val, sizeof(pad_val));
|
|
memcpy(pad_val + (32 - value_len), value, value_len);
|
|
bn_read_be(pad_val, &val);
|
|
|
|
char amount[64] = {0};
|
|
if (token == NULL) {
|
|
if (bn_is_zero(&val)) {
|
|
strcpy(amount, _("message"));
|
|
} else {
|
|
ethereumFormatAmount(&val, NULL, amount, sizeof(amount));
|
|
}
|
|
} else {
|
|
ethereumFormatAmount(&val, token, amount, sizeof(amount));
|
|
}
|
|
|
|
char _to1[] = "to ____________";
|
|
char _to2[] = "_______________";
|
|
char _to3[] = "_______________?";
|
|
|
|
if (to_len) {
|
|
char to_str[43] = {0};
|
|
|
|
bool rskip60 = false;
|
|
// constants from trezor-common/defs/ethereum/networks.json
|
|
switch (chain_id) {
|
|
case 30:
|
|
rskip60 = true;
|
|
break;
|
|
case 31:
|
|
rskip60 = true;
|
|
break;
|
|
}
|
|
|
|
ethereum_address_checksum(to, to_str, rskip60, chain_id);
|
|
memcpy(_to1 + 3, to_str, 12);
|
|
memcpy(_to2, to_str + 12, 15);
|
|
memcpy(_to3, to_str + 27, 15);
|
|
} else {
|
|
strlcpy(_to1, _("to new contract?"), sizeof(_to1));
|
|
strlcpy(_to2, "", sizeof(_to2));
|
|
strlcpy(_to3, "", sizeof(_to3));
|
|
}
|
|
|
|
layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL,
|
|
_("Send"), amount, _to1, _to2, _to3, NULL);
|
|
}
|
|
|
|
static void layoutEthereumData(const uint8_t *data, uint32_t len,
|
|
uint32_t total_len) {
|
|
char hexdata[3][17] = {0};
|
|
char summary[20] = {0};
|
|
uint32_t printed = 0;
|
|
for (int i = 0; i < 3; i++) {
|
|
uint32_t linelen = len - printed;
|
|
if (linelen > 8) {
|
|
linelen = 8;
|
|
}
|
|
data2hex(data, linelen, hexdata[i]);
|
|
data += linelen;
|
|
printed += linelen;
|
|
}
|
|
|
|
strcpy(summary, "... bytes");
|
|
char *p = summary + 11;
|
|
uint32_t number = total_len;
|
|
while (number > 0) {
|
|
*p-- = '0' + number % 10;
|
|
number = number / 10;
|
|
}
|
|
char *summarystart = summary;
|
|
if (total_len == printed) summarystart = summary + 4;
|
|
|
|
layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL,
|
|
_("Transaction data:"), hexdata[0], hexdata[1], hexdata[2],
|
|
summarystart, NULL);
|
|
}
|
|
|
|
static void layoutEthereumFee(const uint8_t *value, uint32_t value_len,
|
|
const uint8_t *gas_price, uint32_t gas_price_len,
|
|
const uint8_t *gas_limit, uint32_t gas_limit_len,
|
|
bool is_token) {
|
|
bignum256 val = {0}, gas = {0};
|
|
uint8_t pad_val[32] = {0};
|
|
char tx_value[32] = {0};
|
|
char gas_value[32] = {0};
|
|
|
|
memzero(tx_value, sizeof(tx_value));
|
|
memzero(gas_value, sizeof(gas_value));
|
|
|
|
memzero(pad_val, sizeof(pad_val));
|
|
memcpy(pad_val + (32 - gas_price_len), gas_price, gas_price_len);
|
|
bn_read_be(pad_val, &val);
|
|
|
|
memzero(pad_val, sizeof(pad_val));
|
|
memcpy(pad_val + (32 - gas_limit_len), gas_limit, gas_limit_len);
|
|
bn_read_be(pad_val, &gas);
|
|
bn_multiply(&val, &gas, &secp256k1.prime);
|
|
|
|
ethereumFormatAmount(&gas, NULL, gas_value, sizeof(gas_value));
|
|
|
|
memzero(pad_val, sizeof(pad_val));
|
|
memcpy(pad_val + (32 - value_len), value, value_len);
|
|
bn_read_be(pad_val, &val);
|
|
|
|
if (bn_is_zero(&val)) {
|
|
strcpy(tx_value, is_token ? _("token") : _("message"));
|
|
} else {
|
|
ethereumFormatAmount(&val, NULL, tx_value, sizeof(tx_value));
|
|
}
|
|
|
|
layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL,
|
|
_("Really send"), tx_value, _("paying up to"), gas_value,
|
|
_("for gas?"), NULL);
|
|
}
|
|
|
|
static void layoutEthereumFeeEIP1559(const char *description,
|
|
const uint8_t *amount_bytes,
|
|
uint32_t amount_len,
|
|
const uint8_t *multiplier_bytes,
|
|
uint32_t multiplier_len) {
|
|
bignum256 amount_val = {0};
|
|
uint8_t padded[32] = {0};
|
|
char amount_str[32] = {0};
|
|
|
|
memcpy(padded + (32 - amount_len), amount_bytes, amount_len);
|
|
bn_read_be(padded, &amount_val);
|
|
|
|
if (multiplier_len > 0) {
|
|
bignum256 multiplier_val = {0};
|
|
|
|
memzero(padded, sizeof(padded));
|
|
memcpy(padded + (32 - multiplier_len), multiplier_bytes, multiplier_len);
|
|
bn_read_be(padded, &multiplier_val);
|
|
bn_multiply(&multiplier_val, &amount_val, &secp256k1.prime);
|
|
}
|
|
|
|
ethereumFormatAmount(&amount_val, NULL, amount_str, sizeof(amount_str));
|
|
|
|
layoutDialogSwipeWrapping(&bmp_icon_question, _("Cancel"), _("Confirm"),
|
|
_("Confirm fee"), description, amount_str);
|
|
}
|
|
|
|
/*
|
|
* RLP fields:
|
|
* - nonce (0 .. 32)
|
|
* - gas_price (0 .. 32)
|
|
* - gas_limit (0 .. 32)
|
|
* - to (0, 20)
|
|
* - value (0 .. 32)
|
|
* - data (0 ..)
|
|
*/
|
|
|
|
static bool ethereum_signing_init_common(struct signing_params *params) {
|
|
ethereum_signing = true;
|
|
sha3_256_Init(&keccak_ctx);
|
|
|
|
data_total = data_left = 0;
|
|
chain_id = 0;
|
|
|
|
memzero(&msg_tx_request, sizeof(EthereumTxRequest));
|
|
memzero(signing_access_list, sizeof(signing_access_list));
|
|
signing_access_list_count = 0;
|
|
|
|
/* eip-155 chain id */
|
|
if (params->chain_id < 1) {
|
|
fsm_sendFailure(FailureType_Failure_DataError, _("Chain ID out of bounds"));
|
|
return false;
|
|
}
|
|
chain_id = params->chain_id;
|
|
chain_suffix = params->chain_suffix;
|
|
|
|
if (params->data_length > 0) {
|
|
if (params->data_initial_chunk_size == 0) {
|
|
fsm_sendFailure(FailureType_Failure_DataError,
|
|
_("Data length provided, but no initial chunk"));
|
|
return false;
|
|
}
|
|
/* Our encoding only supports transactions up to 2^24 bytes. To
|
|
* prevent exceeding the limit we use a stricter limit on data length.
|
|
*/
|
|
if (params->data_length > 16000000) {
|
|
fsm_sendFailure(FailureType_Failure_DataError,
|
|
_("Data length exceeds limit"));
|
|
return false;
|
|
}
|
|
data_total = params->data_length;
|
|
} else {
|
|
data_total = 0;
|
|
}
|
|
if (params->data_initial_chunk_size > data_total) {
|
|
fsm_sendFailure(FailureType_Failure_DataError,
|
|
_("Invalid size of initial chunk"));
|
|
return false;
|
|
}
|
|
|
|
// safety checks
|
|
|
|
size_t tolen = params->has_to ? strlen(params->to) : 0;
|
|
/* Address has wrong length */
|
|
bool wrong_length = (tolen != 42 && tolen != 40 && tolen != 0);
|
|
|
|
// sending transaction to address 0 (contract creation) without a data field
|
|
bool contract_without_data = (tolen == 0 && params->data_length == 0);
|
|
|
|
if (wrong_length || contract_without_data) {
|
|
fsm_sendFailure(FailureType_Failure_DataError, _("Safety check failed"));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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 {
|
|
params->pubkeyhash_set = false;
|
|
memzero(params->pubkeyhash, sizeof(params->pubkeyhash));
|
|
}
|
|
|
|
// detect ERC-20 token
|
|
if (params->pubkeyhash_set && params->value_size == 0 && data_total == 68 &&
|
|
params->data_initial_chunk_size == 68 &&
|
|
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 = token;
|
|
}
|
|
}
|
|
|
|
static bool ethereum_signing_confirm_common(
|
|
const struct signing_params *params) {
|
|
if (params->token != NULL) {
|
|
layoutEthereumConfirmTx(params->data_initial_chunk_bytes + 16, 20,
|
|
params->data_initial_chunk_bytes + 36, 32,
|
|
params->token);
|
|
} else {
|
|
layoutEthereumConfirmTx(params->pubkeyhash, 20, params->value_bytes,
|
|
params->value_size, NULL);
|
|
}
|
|
|
|
if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) {
|
|
return false;
|
|
}
|
|
|
|
if (params->token == NULL && data_total > 0) {
|
|
layoutEthereumData(params->data_initial_chunk_bytes,
|
|
params->data_initial_chunk_size, data_total);
|
|
if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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,
|
|
|
|
.has_to = msg->has_to,
|
|
.to = msg->to,
|
|
|
|
.value_size = msg->value.size,
|
|
.value_bytes = msg->value.bytes,
|
|
};
|
|
|
|
eip1559 = false;
|
|
if (!ethereum_signing_init_common(¶ms)) {
|
|
ethereum_signing_abort();
|
|
return;
|
|
}
|
|
|
|
// sanity check that fee doesn't overflow
|
|
if (msg->gas_price.size + msg->gas_limit.size > 30) {
|
|
fsm_sendFailure(FailureType_Failure_DataError, _("Safety check failed"));
|
|
ethereum_signing_abort();
|
|
return;
|
|
}
|
|
|
|
uint32_t tx_type = 0;
|
|
/* Wanchain txtype */
|
|
if (msg->has_tx_type) {
|
|
if (msg->tx_type == 1 || msg->tx_type == 6) {
|
|
tx_type = msg->tx_type;
|
|
} else {
|
|
fsm_sendFailure(FailureType_Failure_DataError, _("Txtype out of bounds"));
|
|
ethereum_signing_abort();
|
|
return;
|
|
}
|
|
}
|
|
|
|
ethereum_signing_handle_erc20(¶ms, defs->token);
|
|
|
|
if (!ethereum_signing_confirm_common(¶ms)) {
|
|
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
|
ethereum_signing_abort();
|
|
return;
|
|
}
|
|
|
|
layoutEthereumFee(msg->value.bytes, msg->value.size, msg->gas_price.bytes,
|
|
msg->gas_price.size, msg->gas_limit.bytes,
|
|
msg->gas_limit.size, params.token != NULL);
|
|
if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) {
|
|
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
|
ethereum_signing_abort();
|
|
return;
|
|
}
|
|
|
|
/* Stage 1: Calculate total RLP length */
|
|
uint32_t rlp_length = 0;
|
|
|
|
layoutProgress(_("Signing"), 0);
|
|
|
|
rlp_length += rlp_calculate_length(msg->nonce.size, msg->nonce.bytes[0]);
|
|
rlp_length +=
|
|
rlp_calculate_length(msg->gas_price.size, msg->gas_price.bytes[0]);
|
|
rlp_length +=
|
|
rlp_calculate_length(msg->gas_limit.size, msg->gas_limit.bytes[0]);
|
|
rlp_length += rlp_calculate_length(params.pubkeyhash_set ? 20 : 0,
|
|
params.pubkeyhash[0]);
|
|
rlp_length += rlp_calculate_length(params.value_size, params.value_bytes[0]);
|
|
rlp_length +=
|
|
rlp_calculate_length(data_total, params.data_initial_chunk_bytes[0]);
|
|
if (tx_type) {
|
|
rlp_length += rlp_calculate_number_length(tx_type);
|
|
}
|
|
rlp_length += rlp_calculate_number_length(chain_id);
|
|
rlp_length += rlp_calculate_length(0, 0);
|
|
rlp_length += rlp_calculate_length(0, 0);
|
|
|
|
/* Stage 2: Store header fields */
|
|
hash_rlp_list_length(rlp_length);
|
|
|
|
layoutProgress(_("Signing"), 100);
|
|
|
|
if (tx_type) {
|
|
hash_rlp_number(tx_type);
|
|
}
|
|
hash_rlp_field(msg->nonce.bytes, msg->nonce.size);
|
|
hash_rlp_field(msg->gas_price.bytes, msg->gas_price.size);
|
|
hash_rlp_field(msg->gas_limit.bytes, msg->gas_limit.size);
|
|
hash_rlp_field(params.pubkeyhash, params.pubkeyhash_set ? 20 : 0);
|
|
hash_rlp_field(params.value_bytes, params.value_size);
|
|
hash_rlp_length(data_total, params.data_initial_chunk_bytes[0]);
|
|
hash_data(params.data_initial_chunk_bytes, params.data_initial_chunk_size);
|
|
data_left = data_total - params.data_initial_chunk_size;
|
|
|
|
memcpy(privkey, node->private_key, 32);
|
|
|
|
if (data_left > 0) {
|
|
send_request_chunk();
|
|
} else {
|
|
send_signature();
|
|
}
|
|
}
|
|
|
|
void ethereum_signing_init_eip1559(const EthereumSignTxEIP1559 *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,
|
|
|
|
.has_to = msg->has_to,
|
|
.to = msg->to,
|
|
|
|
.value_size = msg->value.size,
|
|
.value_bytes = msg->value.bytes,
|
|
};
|
|
|
|
eip1559 = true;
|
|
if (!ethereum_signing_init_common(¶ms)) {
|
|
ethereum_signing_abort();
|
|
return;
|
|
}
|
|
|
|
// sanity check that fee doesn't overflow
|
|
if (msg->max_gas_fee.size + msg->gas_limit.size > 30 ||
|
|
msg->max_priority_fee.size + msg->gas_limit.size > 30) {
|
|
fsm_sendFailure(FailureType_Failure_DataError, _("Safety check failed"));
|
|
ethereum_signing_abort();
|
|
return;
|
|
}
|
|
|
|
ethereum_signing_handle_erc20(¶ms, defs->token);
|
|
|
|
if (!ethereum_signing_confirm_common(¶ms)) {
|
|
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
|
ethereum_signing_abort();
|
|
return;
|
|
}
|
|
|
|
layoutEthereumFeeEIP1559(_("Maximum fee per gas"), msg->max_gas_fee.bytes,
|
|
msg->max_gas_fee.size, NULL, 0);
|
|
if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) {
|
|
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
|
ethereum_signing_abort();
|
|
return;
|
|
}
|
|
|
|
layoutEthereumFeeEIP1559(_("Priority fee per gas"),
|
|
msg->max_priority_fee.bytes,
|
|
msg->max_priority_fee.size, NULL, 0);
|
|
if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) {
|
|
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
|
ethereum_signing_abort();
|
|
return;
|
|
}
|
|
|
|
layoutEthereumFeeEIP1559(_("Maximum fee"), msg->gas_limit.bytes,
|
|
msg->gas_limit.size, msg->max_gas_fee.bytes,
|
|
msg->max_gas_fee.size);
|
|
if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) {
|
|
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
|
ethereum_signing_abort();
|
|
return;
|
|
}
|
|
|
|
/* Stage 1: Calculate total RLP length */
|
|
uint32_t rlp_length = 0;
|
|
|
|
layoutProgress(_("Signing"), 0);
|
|
|
|
rlp_length += rlp_calculate_number_length(chain_id);
|
|
rlp_length += rlp_calculate_length(msg->nonce.size, msg->nonce.bytes[0]);
|
|
rlp_length += rlp_calculate_length(msg->max_priority_fee.size,
|
|
msg->max_priority_fee.bytes[0]);
|
|
rlp_length +=
|
|
rlp_calculate_length(msg->max_gas_fee.size, msg->max_gas_fee.bytes[0]);
|
|
rlp_length +=
|
|
rlp_calculate_length(msg->gas_limit.size, msg->gas_limit.bytes[0]);
|
|
rlp_length += rlp_calculate_length(params.pubkeyhash_set ? 20 : 0,
|
|
params.pubkeyhash[0]);
|
|
rlp_length += rlp_calculate_length(params.value_size, params.value_bytes[0]);
|
|
rlp_length +=
|
|
rlp_calculate_length(data_total, params.data_initial_chunk_bytes[0]);
|
|
|
|
rlp_length +=
|
|
rlp_calculate_length(rlp_calculate_access_list_length(
|
|
msg->access_list, msg->access_list_count),
|
|
0xff);
|
|
|
|
/* Stage 2: Store header fields */
|
|
hash_rlp_number(EIP1559_TX_TYPE);
|
|
hash_rlp_list_length(rlp_length);
|
|
|
|
layoutProgress(_("Signing"), 100);
|
|
|
|
hash_rlp_number(chain_id);
|
|
hash_rlp_field(msg->nonce.bytes, msg->nonce.size);
|
|
hash_rlp_field(msg->max_priority_fee.bytes, msg->max_priority_fee.size);
|
|
hash_rlp_field(msg->max_gas_fee.bytes, msg->max_gas_fee.size);
|
|
hash_rlp_field(msg->gas_limit.bytes, msg->gas_limit.size);
|
|
hash_rlp_field(params.pubkeyhash, params.pubkeyhash_set ? 20 : 0);
|
|
hash_rlp_field(params.value_bytes, params.value_size);
|
|
hash_rlp_length(data_total, params.data_initial_chunk_bytes[0]);
|
|
hash_data(params.data_initial_chunk_bytes, params.data_initial_chunk_size);
|
|
data_left = data_total - params.data_initial_chunk_size;
|
|
|
|
/* make a copy of access_list, hash it after data is processed */
|
|
memcpy(signing_access_list, msg->access_list, sizeof(signing_access_list));
|
|
signing_access_list_count = msg->access_list_count;
|
|
|
|
memcpy(privkey, node->private_key, 32);
|
|
|
|
if (data_left > 0) {
|
|
send_request_chunk();
|
|
} else {
|
|
send_signature();
|
|
}
|
|
}
|
|
|
|
void ethereum_signing_txack(const EthereumTxAck *tx) {
|
|
if (!ethereum_signing) {
|
|
fsm_sendFailure(FailureType_Failure_UnexpectedMessage,
|
|
_("Not in Ethereum signing mode"));
|
|
layoutHome();
|
|
return;
|
|
}
|
|
|
|
if (tx->data_chunk.size > data_left) {
|
|
fsm_sendFailure(FailureType_Failure_DataError, _("Too much data"));
|
|
ethereum_signing_abort();
|
|
return;
|
|
}
|
|
|
|
if (data_left > 0 && tx->data_chunk.size == 0) {
|
|
fsm_sendFailure(FailureType_Failure_DataError,
|
|
_("Empty data chunk received"));
|
|
ethereum_signing_abort();
|
|
return;
|
|
}
|
|
|
|
hash_data(tx->data_chunk.bytes, tx->data_chunk.size);
|
|
|
|
data_left -= tx->data_chunk.size;
|
|
|
|
if (data_left > 0) {
|
|
send_request_chunk();
|
|
} else {
|
|
send_signature();
|
|
}
|
|
}
|
|
|
|
void ethereum_signing_abort(void) {
|
|
if (ethereum_signing) {
|
|
memzero(privkey, sizeof(privkey));
|
|
layoutHome();
|
|
ethereum_signing = false;
|
|
}
|
|
}
|
|
|
|
static void ethereum_message_hash(const uint8_t *message, size_t message_len,
|
|
uint8_t hash[32]) {
|
|
struct SHA3_CTX ctx = {0};
|
|
sha3_256_Init(&ctx);
|
|
sha3_Update(&ctx, (const uint8_t *)"\x19" "Ethereum Signed Message:\n", 26);
|
|
uint8_t c = 0;
|
|
if (message_len >= 1000000000) {
|
|
c = '0' + message_len / 1000000000 % 10;
|
|
sha3_Update(&ctx, &c, 1);
|
|
}
|
|
if (message_len >= 100000000) {
|
|
c = '0' + message_len / 100000000 % 10;
|
|
sha3_Update(&ctx, &c, 1);
|
|
}
|
|
if (message_len >= 10000000) {
|
|
c = '0' + message_len / 10000000 % 10;
|
|
sha3_Update(&ctx, &c, 1);
|
|
}
|
|
if (message_len >= 1000000) {
|
|
c = '0' + message_len / 1000000 % 10;
|
|
sha3_Update(&ctx, &c, 1);
|
|
}
|
|
if (message_len >= 100000) {
|
|
c = '0' + message_len / 100000 % 10;
|
|
sha3_Update(&ctx, &c, 1);
|
|
}
|
|
if (message_len >= 10000) {
|
|
c = '0' + message_len / 10000 % 10;
|
|
sha3_Update(&ctx, &c, 1);
|
|
}
|
|
if (message_len >= 1000) {
|
|
c = '0' + message_len / 1000 % 10;
|
|
sha3_Update(&ctx, &c, 1);
|
|
}
|
|
if (message_len >= 100) {
|
|
c = '0' + message_len / 100 % 10;
|
|
sha3_Update(&ctx, &c, 1);
|
|
}
|
|
if (message_len >= 10) {
|
|
c = '0' + message_len / 10 % 10;
|
|
sha3_Update(&ctx, &c, 1);
|
|
}
|
|
c = '0' + message_len % 10;
|
|
sha3_Update(&ctx, &c, 1);
|
|
sha3_Update(&ctx, message, message_len);
|
|
keccak_Final(&ctx, hash);
|
|
}
|
|
|
|
void ethereum_message_sign(const EthereumSignMessage *msg, const HDNode *node,
|
|
EthereumMessageSignature *resp) {
|
|
uint8_t hash[32] = {0};
|
|
ethereum_message_hash(msg->message.bytes, msg->message.size, hash);
|
|
|
|
uint8_t v = 0;
|
|
if (ecdsa_sign_digest(&secp256k1, node->private_key, hash,
|
|
resp->signature.bytes, &v, ethereum_is_canonic) != 0) {
|
|
fsm_sendFailure(FailureType_Failure_ProcessError, _("Signing failed"));
|
|
return;
|
|
}
|
|
|
|
resp->signature.bytes[64] = 27 + v;
|
|
resp->signature.size = 65;
|
|
msg_write(MessageType_MessageType_EthereumMessageSignature, resp);
|
|
}
|
|
|
|
int ethereum_message_verify(const EthereumVerifyMessage *msg) {
|
|
if (msg->signature.size != 65) {
|
|
fsm_sendFailure(FailureType_Failure_DataError, _("Malformed signature"));
|
|
return 1;
|
|
}
|
|
|
|
uint8_t pubkeyhash[20] = {0};
|
|
if (!ethereum_parse(msg->address, pubkeyhash)) {
|
|
fsm_sendFailure(FailureType_Failure_DataError, _("Malformed address"));
|
|
return 1;
|
|
}
|
|
|
|
uint8_t pubkey[65] = {0};
|
|
uint8_t hash[32] = {0};
|
|
|
|
ethereum_message_hash(msg->message.bytes, msg->message.size, hash);
|
|
|
|
/* v should be 27, 28 but some implementations use 0,1. We are
|
|
* compatible with both.
|
|
*/
|
|
uint8_t v = msg->signature.bytes[64];
|
|
if (v >= 27) {
|
|
v -= 27;
|
|
}
|
|
|
|
if (v >= 2) {
|
|
return 2;
|
|
}
|
|
|
|
if (ecdsa_recover_pub_from_sig(&secp256k1, pubkey, msg->signature.bytes, hash,
|
|
v) != 0) {
|
|
return 2;
|
|
}
|
|
|
|
struct SHA3_CTX ctx = {0};
|
|
sha3_256_Init(&ctx);
|
|
sha3_Update(&ctx, pubkey + 1, 64);
|
|
keccak_Final(&ctx, hash);
|
|
|
|
/* result are the least significant 160 bits */
|
|
if (memcmp(pubkeyhash, hash + 12, 20) != 0) {
|
|
return 2;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* EIP-712 hashes might have no message_hash if primaryType="EIP712Domain".
|
|
* In this case, set has_message_hash=false.
|
|
*/
|
|
static void ethereum_typed_hash(const uint8_t domain_separator_hash[32],
|
|
const uint8_t message_hash[32],
|
|
bool has_message_hash, uint8_t hash[32]) {
|
|
struct SHA3_CTX ctx = {0};
|
|
sha3_256_Init(&ctx);
|
|
sha3_Update(&ctx, (const uint8_t *)"\x19\x01", 2);
|
|
sha3_Update(&ctx, domain_separator_hash, 32);
|
|
if (has_message_hash) {
|
|
sha3_Update(&ctx, message_hash, 32);
|
|
}
|
|
keccak_Final(&ctx, hash);
|
|
}
|
|
|
|
void ethereum_typed_hash_sign(const EthereumSignTypedHash *msg,
|
|
const HDNode *node,
|
|
EthereumTypedDataSignature *resp) {
|
|
uint8_t hash[32] = {0};
|
|
|
|
ethereum_typed_hash(msg->domain_separator_hash.bytes, msg->message_hash.bytes,
|
|
msg->has_message_hash, hash);
|
|
|
|
uint8_t v = 0;
|
|
if (ecdsa_sign_digest(&secp256k1, node->private_key, hash,
|
|
resp->signature.bytes, &v, ethereum_is_canonic) != 0) {
|
|
fsm_sendFailure(FailureType_Failure_ProcessError, _("Signing failed"));
|
|
return;
|
|
}
|
|
|
|
resp->signature.bytes[64] = 27 + v;
|
|
resp->signature.size = 65;
|
|
msg_write(MessageType_MessageType_EthereumTypedDataSignature, resp);
|
|
}
|
|
|
|
bool ethereum_parse(const char *address, uint8_t pubkeyhash[20]) {
|
|
memzero(pubkeyhash, 20);
|
|
size_t len = strlen(address);
|
|
if (len == 40) {
|
|
// do nothing
|
|
} else if (len == 42) {
|
|
// check for "0x" prefix and strip it when required
|
|
if (address[0] != '0') return false;
|
|
if (address[1] != 'x' && address[1] != 'X') return false;
|
|
address += 2;
|
|
len -= 2;
|
|
} else {
|
|
return false;
|
|
}
|
|
for (size_t i = 0; i < len; i++) {
|
|
if (address[i] >= '0' && address[i] <= '9') {
|
|
pubkeyhash[i / 2] |= (address[i] - '0') << ((1 - (i % 2)) * 4);
|
|
} else if (address[i] >= 'a' && address[i] <= 'f') {
|
|
pubkeyhash[i / 2] |= ((address[i] - 'a') + 10) << ((1 - (i % 2)) * 4);
|
|
} else if (address[i] >= 'A' && address[i] <= 'F') {
|
|
pubkeyhash[i / 2] |= ((address[i] - 'A') + 10) << ((1 - (i % 2)) * 4);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
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,
|
|
const EthereumNetworkInfo *network) {
|
|
bool valid = (address_n_count >= 3);
|
|
valid = valid && (address_n[0] == (PATH_HARDENED | 44));
|
|
valid = valid && (address_n[1] & PATH_HARDENED);
|
|
valid = valid && (address_n[2] & PATH_HARDENED);
|
|
valid = valid && ((address_n[2] & PATH_UNHARDEN_MASK) <= PATH_MAX_ACCOUNT);
|
|
|
|
uint32_t path_slip44 = address_n[1] & PATH_UNHARDEN_MASK;
|
|
valid = valid && check_ethereum_slip44_unhardened(path_slip44, network);
|
|
|
|
if (pubkey_export) {
|
|
// m/44'/coin_type'/account'/*
|
|
return valid;
|
|
}
|
|
|
|
if (address_n_count == 3) {
|
|
// SEP-0005 for non-UTXO-based currencies, defined by Stellar:
|
|
// https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0005.md
|
|
// m/44'/coin_type'/account'
|
|
return valid;
|
|
}
|
|
|
|
if (address_n_count == 4) {
|
|
// Also to support "Ledger Live" legacy paths
|
|
// https://github.com/trezor/trezor-firmware/issues/1749
|
|
// m/44'/coin_type'/0'/account
|
|
valid = valid && (address_n[2] == (PATH_HARDENED | 0));
|
|
valid = valid && (address_n[3] <= PATH_MAX_ACCOUNT);
|
|
return valid;
|
|
}
|
|
|
|
// We believe Ethereum should use the SEP-0005 scheme for everything, because
|
|
// it is account-based, rather than UTXO-based. Unfortunately, a lot of
|
|
// Ethereum tools (MEW, Metamask) do not use such scheme and set account = 0
|
|
// and then iterate the address index. For compatibility, we allow this scheme
|
|
// as well.
|
|
// m/44'/coin_type'/account'/change/address_index
|
|
valid = valid && (address_n_count == 5);
|
|
valid = valid && (address_n[3] <= PATH_MAX_CHANGE);
|
|
valid = valid && (address_n[4] <= PATH_MAX_ADDRESS_INDEX);
|
|
|
|
return valid;
|
|
}
|
|
|
|
static bool ethereum_path_check_casa45(uint32_t address_n_count,
|
|
const uint32_t *address_n,
|
|
const EthereumNetworkInfo *network) {
|
|
bool valid = (address_n_count == 5);
|
|
valid = valid && (address_n[0] == (PATH_HARDENED | 45));
|
|
valid = valid && (address_n[1] < PATH_HARDENED);
|
|
valid = valid && (address_n[2] <= PATH_MAX_ACCOUNT);
|
|
valid = valid && (address_n[3] <= PATH_MAX_CHANGE);
|
|
valid = valid && (address_n[4] <= PATH_MAX_ADDRESS_INDEX);
|
|
|
|
uint32_t path_slip44 = address_n[1];
|
|
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,
|
|
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,
|
|
network);
|
|
}
|
|
if (address_n[0] == (PATH_HARDENED | 45)) {
|
|
return ethereum_path_check_casa45(address_n_count, address_n, network);
|
|
}
|
|
return false;
|
|
}
|