feat(legacy/ethereum): support for EIP-1559 transactions

pull/1879/head
Martin Milata 3 years ago
parent b2ff86ff3a
commit 076c5b9cbe

@ -0,0 +1 @@
Support for Ethereum EIP-1559 transactions.

@ -41,15 +41,34 @@
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 uint32_t tx_type;
static bool eip1559;
struct SHA3_CTX keccak_ctx = {0};
struct signing_params {
bool pubkeyhash_set;
uint8_t pubkeyhash[20];
uint64_t chain_id;
uint32_t data_length;
uint32_t data_initial_chunk_size;
const uint8_t *data_initial_chunk_bytes;
bool has_to;
const char *to;
const TokenType *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);
}
@ -192,11 +211,15 @@ static void send_signature(void) {
uint8_t v = 0;
layoutProgress(_("Signing"), 1000);
/* 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);
if (eip1559) {
hash_rlp_list_length(0);
} 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,
@ -212,7 +235,7 @@ static void send_signature(void) {
msg_tx_request.has_data_length = false;
msg_tx_request.has_signature_v = true;
if (chain_id > MAX_CHAIN_ID) {
if (eip1559 || chain_id > MAX_CHAIN_ID) {
msg_tx_request.signature_v = v;
} else {
msg_tx_request.signature_v = v + 2 * chain_id + 35;
@ -375,6 +398,34 @@ static void layoutEthereumFee(const uint8_t *value, uint32_t value_len,
_("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));
layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL,
_("Confirm fee"), description, amount_str, NULL, NULL,
NULL);
}
/*
* RLP fields:
* - nonce (0 .. 32)
@ -385,139 +436,158 @@ static void layoutEthereumFee(const uint8_t *value, uint32_t value_len,
* - data (0 ..)
*/
static bool ethereum_signing_check(const EthereumSignTx *msg) {
size_t tolen = msg->has_to ? strlen(msg->to) : 0;
if (tolen != 42 && tolen != 40 && tolen != 0) {
/* Address has wrong length */
return false;
}
// sending transaction to address 0 (contract creation) without a data field
if (tolen == 0 && (!msg->has_data_length || msg->data_length == 0)) {
return false;
}
if (msg->gas_price.size + msg->gas_limit.size > 30) {
// sanity check that fee doesn't overflow
return false;
}
return true;
}
void ethereum_signing_init(EthereumSignTx *msg, const HDNode *node) {
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));
/* set fields to 0, to avoid conditions later */
if (!msg->has_value) msg->value.size = 0;
if (!msg->has_data_initial_chunk) msg->data_initial_chunk.size = 0;
bool toset;
uint8_t pubkeyhash[20] = {0};
if (msg->has_to && ethereum_parse(msg->to, pubkeyhash)) {
toset = true;
} else {
msg->to[0] = 0;
toset = false;
memzero(pubkeyhash, sizeof(pubkeyhash));
}
if (!msg->has_nonce) msg->nonce.size = 0;
/* eip-155 chain id */
if (msg->chain_id < 1) {
if (params->chain_id < 1) {
fsm_sendFailure(FailureType_Failure_DataError, _("Chain ID out of bounds"));
ethereum_signing_abort();
return;
}
chain_id = msg->chain_id;
/* 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;
}
} else {
tx_type = 0;
return false;
}
chain_id = params->chain_id;
if (msg->has_data_length && msg->data_length > 0) {
if (!msg->has_data_initial_chunk || msg->data_initial_chunk.size == 0) {
if (params->data_length > 0) {
if (params->data_initial_chunk_size == 0) {
fsm_sendFailure(FailureType_Failure_DataError,
_("Data length provided, but no initial chunk"));
ethereum_signing_abort();
return;
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 (msg->data_length > 16000000) {
if (params->data_length > 16000000) {
fsm_sendFailure(FailureType_Failure_DataError,
_("Data length exceeds limit"));
ethereum_signing_abort();
return;
return false;
}
data_total = msg->data_length;
data_total = params->data_length;
} else {
data_total = 0;
}
if (msg->data_initial_chunk.size > data_total) {
if (params->data_initial_chunk_size > data_total) {
fsm_sendFailure(FailureType_Failure_DataError,
_("Invalid size of initial chunk"));
ethereum_signing_abort();
return;
return false;
}
// safety checks
if (!ethereum_signing_check(msg)) {
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"));
ethereum_signing_abort();
return;
return false;
}
const TokenType *token = NULL;
return true;
}
static void ethereum_signing_handle_erc20(struct signing_params *params) {
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 (toset && msg->value.size == 0 && data_total == 68 &&
msg->data_initial_chunk.size == 68 &&
memcmp(msg->data_initial_chunk.bytes,
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) {
token = tokenByChainAddress(chain_id, pubkeyhash);
params->token = tokenByChainAddress(chain_id, params->pubkeyhash);
}
}
if (token != NULL) {
layoutEthereumConfirmTx(msg->data_initial_chunk.bytes + 16, 20,
msg->data_initial_chunk.bytes + 36, 32, 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(pubkeyhash, 20, msg->value.bytes, msg->value.size,
NULL);
layoutEthereumConfirmTx(params->pubkeyhash, 20, params->value_bytes,
params->value_size, NULL);
}
if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
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) {
struct signing_params params = {
.chain_id = msg->chain_id,
.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(&params)) {
ethereum_signing_abort();
return;
}
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, NULL);
// 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(&params);
if (!ethereum_signing_confirm_common(&params)) {
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, token != NULL);
msg->gas_limit.size, params.token != NULL);
if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
ethereum_signing_abort();
@ -534,10 +604,11 @@ void ethereum_signing_init(EthereumSignTx *msg, const HDNode *node) {
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(toset ? 20 : 0, pubkeyhash[0]);
rlp_length += rlp_calculate_length(msg->value.size, msg->value.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, msg->data_initial_chunk.bytes[0]);
rlp_calculate_length(data_total, params.data_initial_chunk_bytes[0]);
if (tx_type) {
rlp_length += rlp_calculate_number_length(tx_type);
}
@ -556,11 +627,122 @@ void ethereum_signing_init(EthereumSignTx *msg, const HDNode *node) {
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(pubkeyhash, toset ? 20 : 0);
hash_rlp_field(msg->value.bytes, msg->value.size);
hash_rlp_length(data_total, msg->data_initial_chunk.bytes[0]);
hash_data(msg->data_initial_chunk.bytes, msg->data_initial_chunk.size);
data_left = data_total - msg->data_initial_chunk.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) {
struct signing_params params = {
.chain_id = msg->chain_id,
.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(&params)) {
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(&params);
if (!ethereum_signing_confirm_common(&params)) {
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(0, 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;
memcpy(privkey, node->private_key, 32);

@ -25,7 +25,9 @@
#include "bip32.h"
#include "messages-ethereum.pb.h"
void ethereum_signing_init(EthereumSignTx *msg, const HDNode *node);
void ethereum_signing_init(const EthereumSignTx *msg, const HDNode *node);
void ethereum_signing_init_eip1559(const EthereumSignTxEIP1559 *msg,
const HDNode *node);
void ethereum_signing_abort(void);
void ethereum_signing_txack(const EthereumTxAck *msg);

@ -99,9 +99,7 @@ void fsm_msgDebugLinkFlashErase(const DebugLinkFlashErase *msg);
// ethereum
void fsm_msgEthereumGetAddress(const EthereumGetAddress *msg);
void fsm_msgEthereumGetPublicKey(const EthereumGetPublicKey *msg);
void fsm_msgEthereumSignTx(
EthereumSignTx
*msg); // not const because we mutate transaction during validation
void fsm_msgEthereumSignTx(const EthereumSignTx *msg);
void fsm_msgEthereumSignTxEIP1559(const EthereumSignTxEIP1559 *msg);
void fsm_msgEthereumTxAck(const EthereumTxAck *msg);
void fsm_msgEthereumSignMessage(const EthereumSignMessage *msg);

@ -60,7 +60,7 @@ void fsm_msgEthereumGetPublicKey(const EthereumGetPublicKey *msg) {
layoutHome();
}
void fsm_msgEthereumSignTx(EthereumSignTx *msg) {
void fsm_msgEthereumSignTx(const EthereumSignTx *msg) {
CHECK_INITIALIZED
CHECK_PIN
@ -72,13 +72,20 @@ void fsm_msgEthereumSignTx(EthereumSignTx *msg) {
ethereum_signing_init(msg, node);
}
void fsm_msgEthereumTxAck(const EthereumTxAck *msg) {
ethereum_signing_txack(msg);
void fsm_msgEthereumSignTxEIP1559(const EthereumSignTxEIP1559 *msg) {
CHECK_INITIALIZED
CHECK_PIN
const HDNode *node = fsm_getDerivedNode(SECP256K1_NAME, msg->address_n,
msg->address_n_count, NULL);
if (!node) return;
ethereum_signing_init_eip1559(msg, node);
}
void fsm_msgEthereumSignTxEIP1559(const EthereumSignTxEIP1559 *msg) {
(void)msg;
fsm_sendFailure(FailureType_Failure_UnexpectedMessage, _("Not implemented"));
void fsm_msgEthereumTxAck(const EthereumTxAck *msg) {
ethereum_signing_txack(msg);
}
void fsm_msgEthereumGetAddress(const EthereumGetAddress *msg) {

@ -57,7 +57,6 @@ def test_signtx(client, parameters, result):
@parametrize_using_common_fixtures("ethereum/sign_tx_eip1559.json")
@pytest.mark.skip_t1
def test_signtx_eip1559(client, parameters, result):
with client:
sig_v, sig_r, sig_s = ethereum.sign_tx_eip1559(
@ -213,7 +212,6 @@ def test_signtx_eip1559_access_list(client):
)
@pytest.mark.skip_t1
def test_signtx_eip1559_access_list_larger(client):
with client:
@ -267,7 +265,6 @@ def test_signtx_eip1559_access_list_larger(client):
)
@pytest.mark.skip_t1
def test_sanity_checks_eip1559(client):
"""Is not vectorized because these are internal-only tests that do not
need to be exposed to the public.

Loading…
Cancel
Save