mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-18 13:38:12 +00:00
35f4e1ce19
Previously, an amount whose decimal representation with suffix does not fit in buffer would not be rendered at all. This looks weird in "Send to address 0xblabla". Enlarging the buffer lets the amount be stringified. Then it won't fit on screen so this is not a perfect fix, but "Send 25000000... to address" is better than before.
1139 lines
36 KiB
C
1139 lines
36 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"
|
|
#ifdef USE_SECP256K1_ZKP_ECDSA
|
|
#include "zkp_ecdsa.h"
|
|
#endif
|
|
|
|
/* 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;
|
|
}
|
|
|
|
int ret = 0;
|
|
#ifdef USE_SECP256K1_ZKP_ECDSA
|
|
ret = zkp_ecdsa_recover_pub_from_sig(&secp256k1, pubkey, msg->signature.bytes,
|
|
hash, v);
|
|
#else
|
|
ret = ecdsa_recover_pub_from_sig(&secp256k1, pubkey, msg->signature.bytes,
|
|
hash, v);
|
|
#endif
|
|
if (ret != 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;
|
|
}
|