mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-23 06:48:16 +00:00
ethereum: support for ERC-20 tokens
This commit is contained in:
parent
391e3940e5
commit
ba9aae143e
@ -18,6 +18,7 @@ OBJS += reset.o
|
||||
OBJS += signing.o
|
||||
OBJS += crypto.o
|
||||
OBJS += ethereum.o
|
||||
OBJS += ethereum_tokens.o
|
||||
|
||||
OBJS += debug.o
|
||||
|
||||
|
@ -39,8 +39,7 @@ const CoinType coins[COINS_COUNT] = {
|
||||
const CoinType *coinByShortcut(const char *shortcut)
|
||||
{
|
||||
if (!shortcut) return 0;
|
||||
int i;
|
||||
for (i = 0; i < COINS_COUNT; i++) {
|
||||
for (int i = 0; i < COINS_COUNT; i++) {
|
||||
if (strcmp(shortcut, coins[i].coin_shortcut) == 0) {
|
||||
return &(coins[i]);
|
||||
}
|
||||
@ -51,8 +50,7 @@ const CoinType *coinByShortcut(const char *shortcut)
|
||||
const CoinType *coinByName(const char *name)
|
||||
{
|
||||
if (!name) return 0;
|
||||
int i;
|
||||
for (i = 0; i < COINS_COUNT; i++) {
|
||||
for (int i = 0; i < COINS_COUNT; i++) {
|
||||
if (strcmp(name, coins[i].coin_name) == 0) {
|
||||
return &(coins[i]);
|
||||
}
|
||||
@ -62,8 +60,7 @@ const CoinType *coinByName(const char *name)
|
||||
|
||||
const CoinType *coinByAddressType(uint32_t address_type)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < COINS_COUNT; i++) {
|
||||
for (int i = 0; i < COINS_COUNT; i++) {
|
||||
if (address_type == coins[i].address_type) {
|
||||
return &(coins[i]);
|
||||
}
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "secp256k1.h"
|
||||
#include "sha3.h"
|
||||
#include "util.h"
|
||||
#include "ethereum_tokens.h"
|
||||
|
||||
static bool ethereum_signing = false;
|
||||
static uint32_t data_total, data_left;
|
||||
@ -196,8 +197,9 @@ static void send_signature(void)
|
||||
* using standard ethereum units.
|
||||
* The buffer must be at least 25 bytes.
|
||||
*/
|
||||
static void ethereumFormatAmount(bignum256 *val, char buffer[25])
|
||||
static void ethereumFormatAmount(bignum256 *val, char buffer[25], const TokenType *token)
|
||||
{
|
||||
// TODO: use token->decimals to properly format amount
|
||||
char value[25] = {0};
|
||||
char *value_ptr = value;
|
||||
|
||||
@ -242,36 +244,40 @@ static void ethereumFormatAmount(bignum256 *val, char buffer[25])
|
||||
// remove trailing dot.
|
||||
if (value_ptr[-1] == '.')
|
||||
value_ptr--;
|
||||
switch (chain_id) {
|
||||
case 61:
|
||||
strcpy(value_ptr, " ETC"); // Ethereum Classic Mainnet
|
||||
break;
|
||||
case 62:
|
||||
strcpy(value_ptr, " tETC"); // Ethereum Classic Testnet
|
||||
break;
|
||||
case 30:
|
||||
strcpy(value_ptr, " RSK"); // Rootstock Mainnet
|
||||
break;
|
||||
case 31:
|
||||
strcpy(value_ptr, " tRSK"); // Rootstock Testnet
|
||||
break;
|
||||
case 3:
|
||||
strcpy(value_ptr, " tETH"); // Ethereum Testnet: Ropsten
|
||||
break;
|
||||
case 4:
|
||||
strcpy(value_ptr, " tETH"); // Ethereum Testnet: Rinkeby
|
||||
break;
|
||||
case 42:
|
||||
strcpy(value_ptr, " tETH"); // Ethereum Testnet: Kovan
|
||||
break;
|
||||
default:
|
||||
strcpy(value_ptr, " ETH"); // Ethereum Mainnet
|
||||
break;
|
||||
if (token) {
|
||||
strcpy(value_ptr, token->ticker); // ERC-20 Token
|
||||
} else {
|
||||
switch (chain_id) {
|
||||
case 61:
|
||||
strcpy(value_ptr, " ETC"); // Ethereum Classic Mainnet
|
||||
break;
|
||||
case 62:
|
||||
strcpy(value_ptr, " tETC"); // Ethereum Classic Testnet
|
||||
break;
|
||||
case 30:
|
||||
strcpy(value_ptr, " RSK"); // Rootstock Mainnet
|
||||
break;
|
||||
case 31:
|
||||
strcpy(value_ptr, " tRSK"); // Rootstock Testnet
|
||||
break;
|
||||
case 3:
|
||||
strcpy(value_ptr, " tETH"); // Ethereum Testnet: Ropsten
|
||||
break;
|
||||
case 4:
|
||||
strcpy(value_ptr, " tETH"); // Ethereum Testnet: Rinkeby
|
||||
break;
|
||||
case 42:
|
||||
strcpy(value_ptr, " tETH"); // Ethereum Testnet: Kovan
|
||||
break;
|
||||
default:
|
||||
strcpy(value_ptr, " ETH"); // Ethereum Mainnet
|
||||
break;
|
||||
}
|
||||
}
|
||||
// value is at most 16 + 4 + 1 characters long
|
||||
} else {
|
||||
// value is bigger than 1e9 ETH => won't fit on display (probably won't happen unless you are Vitalik)
|
||||
strlcpy(value, "trillions of ETH", sizeof(value));
|
||||
strlcpy(value, "gazillions of money", sizeof(value));
|
||||
}
|
||||
|
||||
// skip leading zeroes
|
||||
@ -284,7 +290,7 @@ static void ethereumFormatAmount(bignum256 *val, char buffer[25])
|
||||
strcpy(buffer, value_ptr);
|
||||
}
|
||||
|
||||
static void layoutEthereumConfirmTx(const uint8_t *to, uint32_t to_len, const uint8_t *value, uint32_t value_len)
|
||||
static void layoutEthereumConfirmTx(const uint8_t *to, uint32_t to_len, const uint8_t *value, uint32_t value_len, const TokenType *token)
|
||||
{
|
||||
bignum256 val;
|
||||
uint8_t pad_val[32];
|
||||
@ -293,10 +299,14 @@ static void layoutEthereumConfirmTx(const uint8_t *to, uint32_t to_len, const ui
|
||||
bn_read_be(pad_val, &val);
|
||||
|
||||
char amount[25];
|
||||
if (bn_is_zero(&val)) {
|
||||
strcpy(amount, "message");
|
||||
if (token == NULL) {
|
||||
if (bn_is_zero(&val)) {
|
||||
strcpy(amount, "message");
|
||||
} else {
|
||||
ethereumFormatAmount(&val, amount, NULL);
|
||||
}
|
||||
} else {
|
||||
ethereumFormatAmount(&val, amount);
|
||||
ethereumFormatAmount(&val, amount, token);
|
||||
}
|
||||
|
||||
static char _to1[17] = {0};
|
||||
@ -369,7 +379,8 @@ static void layoutEthereumData(const uint8_t *data, uint32_t len, uint32_t total
|
||||
|
||||
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)
|
||||
const uint8_t *gas_limit, uint32_t gas_limit_len,
|
||||
bool is_token)
|
||||
{
|
||||
bignum256 val, gas;
|
||||
uint8_t pad_val[32];
|
||||
@ -385,16 +396,16 @@ static void layoutEthereumFee(const uint8_t *value, uint32_t value_len,
|
||||
bn_read_be(pad_val, &gas);
|
||||
bn_multiply(&val, &gas, &secp256k1.prime);
|
||||
|
||||
ethereumFormatAmount(&gas, gas_value);
|
||||
ethereumFormatAmount(&gas, gas_value, NULL);
|
||||
|
||||
memset(pad_val, 0, 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, "message");
|
||||
strcpy(tx_value, is_token ? "token" : "message");
|
||||
} else {
|
||||
ethereumFormatAmount(&val, tx_value);
|
||||
ethereumFormatAmount(&val, tx_value, NULL);
|
||||
}
|
||||
|
||||
layoutDialogSwipe(&bmp_icon_question,
|
||||
@ -503,14 +514,27 @@ void ethereum_signing_init(EthereumSignTx *msg, const HDNode *node)
|
||||
return;
|
||||
}
|
||||
|
||||
layoutEthereumConfirmTx(msg->to.bytes, msg->to.size, msg->value.bytes, msg->value.size);
|
||||
const TokenType *token = NULL;
|
||||
|
||||
// detect ERC-20 token
|
||||
if (msg->to.size == 20 && msg->value.size == 0 && data_total == 68 && msg->data_initial_chunk.size == 68
|
||||
&& memcmp(msg->data_initial_chunk.bytes, "\xa9\x05\x9c\xbb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16) == 0) {
|
||||
token = tokenByAddress(msg->to.bytes);
|
||||
}
|
||||
|
||||
if (token != NULL) {
|
||||
layoutEthereumConfirmTx(msg->data_initial_chunk.bytes + 16, 20, msg->data_initial_chunk.bytes + 36, 32, token);
|
||||
} else {
|
||||
layoutEthereumConfirmTx(msg->to.bytes, msg->to.size, msg->value.bytes, msg->value.size, NULL);
|
||||
}
|
||||
|
||||
if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) {
|
||||
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled by user");
|
||||
ethereum_signing_abort();
|
||||
return;
|
||||
}
|
||||
|
||||
if (data_total > 0) {
|
||||
if (token == NULL && data_total > 0) {
|
||||
layoutEthereumData(msg->data_initial_chunk.bytes, msg->data_initial_chunk.size, data_total);
|
||||
if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) {
|
||||
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled by user");
|
||||
@ -521,7 +545,7 @@ void ethereum_signing_init(EthereumSignTx *msg, const HDNode *node)
|
||||
|
||||
layoutEthereumFee(msg->value.bytes, msg->value.size,
|
||||
msg->gas_price.bytes, msg->gas_price.size,
|
||||
msg->gas_limit.bytes, msg->gas_limit.size);
|
||||
msg->gas_limit.bytes, msg->gas_limit.size, token != NULL);
|
||||
if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) {
|
||||
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled by user");
|
||||
ethereum_signing_abort();
|
||||
|
24
firmware/ethereum_tokens-gen.py
Executable file
24
firmware/ethereum_tokens-gen.py
Executable file
@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
import requests
|
||||
|
||||
def get_tokens():
|
||||
URL = 'https://raw.githubusercontent.com/kvhnuke/etherwallet/mercury/app/scripts/tokens/ethTokens.json'
|
||||
r = requests.get(URL)
|
||||
return r.json()
|
||||
|
||||
tokens = get_tokens()
|
||||
|
||||
subst = {
|
||||
'BeerCoin \U0001F37A': 'BEER',
|
||||
'CryptoCarbon': 'CCRB',
|
||||
'DAO_extraBalance': 'DAOe',
|
||||
'DGX 1.0': 'DGX1',
|
||||
'Unicorn \U0001F984': 'UNCRN',
|
||||
}
|
||||
|
||||
for t in tokens:
|
||||
address, symbol, decimal = t['address'], t['symbol'], t['decimal']
|
||||
if symbol in subst:
|
||||
symbol = subst[symbol]
|
||||
address = '\\x'.join([address[i:i + 2] for i in range(0, len(address), 2)])[2:].lower()
|
||||
print('\t{"%s", " %s", %d},' % (address, symbol, decimal))
|
59
firmware/ethereum_tokens.c
Normal file
59
firmware/ethereum_tokens.c
Normal file
@ -0,0 +1,59 @@
|
||||
#include <string.h>
|
||||
#include "ethereum_tokens.h"
|
||||
|
||||
const TokenType tokens[TOKENS_COUNT] = {
|
||||
{"\xaf\x30\xd2\xa7\xe9\x0d\x7d\xc3\x61\xc8\xc4\x58\x5e\x9b\xb7\xd2\xf6\xf1\x5b\xc7", " 1ST", 18},
|
||||
{"\x96\x0b\x23\x6a\x07\xcf\x12\x26\x63\xc4\x30\x33\x50\x60\x9a\x66\xa7\xb2\x88\xc0", " ANT", 18},
|
||||
{"\xac\x70\x9f\xcb\x44\xa4\x3c\x35\xf0\xda\x4e\x31\x63\xb1\x17\xa1\x7f\x37\x70\xf5", " ARC", 18},
|
||||
{"\x74\xc1\xe4\xb8\xca\xe5\x92\x69\xec\x1d\x85\xd3\xd4\xf3\x24\x39\x60\x48\xf4\xac", " BEER", 0},
|
||||
{"\x1e\x79\x7c\xe9\x86\xc3\xcf\xf4\x47\x2f\x7d\x38\xd5\xc4\xab\xa5\x5d\xfe\xfe\x40", " BCDN", 15},
|
||||
{"\xae\xf3\x8f\xbf\xbf\x93\x2d\x1a\xef\x3b\x80\x8b\xc8\xfb\xd8\xcd\x8e\x1f\x8b\xc5", " CRB", 8},
|
||||
{"\xe4\xc9\x4d\x45\xf7\xae\xf7\x01\x8a\x5d\x66\xf4\x4a\xf7\x80\xec\x60\x23\x37\x8e", " CCRB", 6},
|
||||
{"\xbb\x9b\xc2\x44\xd7\x98\x12\x3f\xde\x78\x3f\xcc\x1c\x72\xd3\xbb\x8c\x18\x94\x13", " DAO", 16},
|
||||
{"\x5c\x40\xef\x6f\x52\x7f\x4f\xba\x68\x36\x87\x74\xe6\x13\x0c\xe6\x51\x51\x23\xf2", " DAOe", 0},
|
||||
{"\xe0\xb7\x92\x7c\x4a\xf2\x37\x65\xcb\x51\x31\x4a\x0e\x05\x21\xa9\x64\x5f\x0e\x2a", " DGD", 9},
|
||||
{"\x55\xb9\xa1\x1c\x2e\x83\x51\xb4\xff\xc7\xb1\x15\x61\x14\x8b\xfa\xc9\x97\x78\x55", " DGX1", 9},
|
||||
{"\x08\x71\x1d\x3b\x02\xc8\x75\x8f\x2f\xb3\xab\x4e\x80\x22\x84\x18\xa7\xf8\xe3\x9c", " EDG", 0},
|
||||
{"\xb8\x02\xb2\x4e\x06\x37\xc2\xb8\x7d\x2e\x8b\x77\x84\xc0\x55\xbb\xe9\x21\x01\x1a", " EMV", 2},
|
||||
{"\x68\x10\xe7\x76\x88\x0c\x02\x93\x3d\x47\xdb\x1b\x9f\xc0\x59\x08\xe5\x38\x6b\x96", " GNO", 18},
|
||||
{"\xa7\x44\x76\x44\x31\x19\xa9\x42\xde\x49\x85\x90\xfe\x1f\x24\x54\xd7\xd4\xac\x0d", " GNT", 18},
|
||||
{"\xf7\xb0\x98\x29\x8f\x7c\x69\xfc\x14\x61\x0b\xf7\x1d\x5e\x02\xc6\x07\x92\x89\x4c", " GUP", 3},
|
||||
{"\x1d\x92\x1e\xed\x55\xa6\xa9\xcc\xaa\x9c\x79\xb1\xa4\xf7\xb2\x55\x56\xe4\x43\x65", " GT", 0},
|
||||
{"\x14\xf3\x7b\x57\x42\x42\xd3\x66\x55\x8d\xb6\x1f\x33\x35\x28\x9a\x50\x35\xc5\x06", " HKG", 3},
|
||||
{"\xcb\xcc\x0f\x03\x6e\xd4\x78\x8f\x63\xfc\x0f\xee\x32\x87\x3d\x6a\x74\x87\xb9\x08", " HMQ", 8},
|
||||
{"\x88\x86\x66\xca\x69\xe0\xf1\x78\xde\xd6\xd7\x5b\x57\x26\xce\xe9\x9a\x87\xd6\x98", " ICN", 18},
|
||||
{"\xc1\xe6\xc6\xc6\x81\xb2\x86\xfb\x50\x3b\x36\xa9\xdd\x6c\x1d\xbf\xf8\x5e\x73\xcf", " JET", 18},
|
||||
{"\xfa\x05\xa7\x3f\xfe\x78\xef\x8f\x1a\x73\x94\x73\xe4\x62\xc5\x4b\xae\x65\x67\xd9", " LUN", 18},
|
||||
{"\xe2\x3c\xd1\x60\x76\x1f\x63\xfc\x3a\x1c\xf7\x8a\xa0\x34\xb6\xcd\xf9\x7d\x3e\x0c", " MIT", 18},
|
||||
{"\xc6\x6e\xa8\x02\x71\x7b\xfb\x98\x33\x40\x02\x64\xdd\x12\xc2\xbc\xea\xa3\x4a\x6d", " MKR", 18},
|
||||
{"\xbe\xb9\xef\x51\x4a\x37\x9b\x99\x7e\x07\x98\xfd\xcc\x90\x1e\xe4\x74\xb6\xd9\xa1", " MLN", 18},
|
||||
{"\x1a\x95\xb2\x71\xb0\x53\x5d\x15\xfa\x49\x93\x2d\xab\xa3\x1b\xa6\x12\xb5\x29\x46", " MNE", 8},
|
||||
{"\x45\xe4\x2d\x65\x9d\x9f\x94\x66\xcd\x5d\xf6\x22\x50\x60\x33\x14\x5a\x9b\x89\xbc", " NxC", 3},
|
||||
{"\xd8\x91\x2c\x10\x68\x1d\x8b\x21\xfd\x37\x42\x24\x4f\x44\x65\x8d\xba\x12\x26\x4e", " PLU", 18},
|
||||
{"\x48\xc8\x0f\x1f\x4d\x53\xd5\x95\x1e\x5d\x54\x38\xb5\x4c\xba\x84\xf2\x9f\x32\xa5", " REP", 18},
|
||||
{"\x60\x7f\x4c\x5b\xb6\x72\x23\x0e\x86\x72\x08\x55\x32\xf7\xe9\x01\x54\x4a\x73\x75", " RLC", 9},
|
||||
{"\x2e\x07\x1d\x29\x66\xaa\x7d\x8d\xec\xb1\x00\x58\x85\xba\x19\x77\xd6\x03\x8a\x65", " ROL", 16},
|
||||
{"\x49\x93\xcb\x95\xc7\x44\x3b\xdc\x06\x15\x5c\x5f\x56\x88\xbe\x9d\x8f\x69\x99\xa5", " ROUND", 18},
|
||||
{"\xae\xc2\xe8\x7e\x0a\x23\x52\x66\xd9\xc5\xad\xc9\xde\xb4\xb2\xe2\x9b\x54\xd0\x09", " SNGLS", 0},
|
||||
{"\x1d\xce\x4f\xa0\x36\x39\xb7\xf0\xc3\x8e\xe5\xbb\x60\x65\x04\x5e\xdc\xf9\x81\x9a", " SRC", 8},
|
||||
{"\xb9\xe7\xf8\x56\x8e\x08\xd5\x65\x9f\x5d\x29\xc4\x99\x71\x73\xd8\x4c\xdf\x26\x07", " SWT", 18},
|
||||
{"\xe7\x77\x5a\x6e\x9b\xcf\x90\x4e\xb3\x9d\xa2\xb6\x8c\x5e\xfb\x4f\x93\x60\xe0\x8c", " TaaS", 6},
|
||||
{"\x65\x31\xf1\x33\xe6\xde\xeb\xe7\xf2\xdc\xe5\xa0\x44\x1a\xa7\xef\x33\x0b\x4e\x53", " TIME", 8},
|
||||
{"\xaa\xaf\x91\xd9\xb9\x0d\xf8\x00\xdf\x4f\x55\xc2\x05\xfd\x69\x89\xc9\x77\xe7\x3a", " TKN", 8},
|
||||
{"\xcb\x94\xbe\x6f\x13\xa1\x18\x2e\x4a\x4b\x61\x40\xcb\x7b\xf2\x02\x5d\x28\xe4\x1b", " TRST", 6},
|
||||
{"\x89\x20\x5a\x3a\x3b\x2a\x69\xde\x6d\xbf\x7f\x01\xed\x13\xb2\x10\x8b\x2c\x43\xe7", " UNCRN ", 0},
|
||||
{"\x5c\x54\x3e\x7a\xe0\xa1\x10\x4f\x78\x40\x6c\x34\x0e\x9c\x64\xfd\x9f\xce\x51\x70", " VSL", 18},
|
||||
{"\x66\x70\x88\xb2\x12\xce\x3d\x06\xa1\xb5\x53\xa7\x22\x1e\x1f\xd1\x90\x00\xd9\xaf", " WINGS", 18},
|
||||
{"\x4d\xf8\x12\xf6\x06\x4d\xef\x1e\x5e\x02\x9f\x1c\xa8\x58\x77\x7c\xc9\x8d\x2d\x81", " XAUR", 8},
|
||||
};
|
||||
|
||||
const TokenType *tokenByAddress(const uint8_t *address)
|
||||
{
|
||||
if (!address) return 0;
|
||||
for (int i = 0; i < TOKENS_COUNT; i++) {
|
||||
if (memcmp(address, tokens[i].address, 20) == 0) {
|
||||
return &(tokens[i]);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
37
firmware/ethereum_tokens.h
Normal file
37
firmware/ethereum_tokens.h
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* This file is part of the TREZOR project.
|
||||
*
|
||||
* Copyright (C) 2014 Pavol Rusnak <stick@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/>.
|
||||
*/
|
||||
|
||||
#ifndef __ETHEREUM_TOKENS_H__
|
||||
#define __ETHEREUM_TOKENS_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define TOKENS_COUNT 43
|
||||
|
||||
typedef struct {
|
||||
const char * const address;
|
||||
const char * const ticker;
|
||||
int decimals;
|
||||
} TokenType;
|
||||
|
||||
extern const TokenType tokens[TOKENS_COUNT];
|
||||
|
||||
const TokenType *tokenByAddress(const uint8_t *address);
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user