trezor-mcu/firmware/ethereum.c

385 lines
9.1 KiB
C
Raw Normal View History

2016-05-24 14:22:30 -07:00
/*
* This file is part of the TREZOR project.
*
* Copyright (C) 2016 Alex Beregszaszi <alex@rtfs.hu>
*
* 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 "fsm.h"
#include "layout2.h"
#include "messages.h"
#include "transaction.h"
#include "ecdsa.h"
#include "protect.h"
#include "crypto.h"
#include "secp256k1.h"
#include "sha3.h"
#include "util.h"
2016-05-24 14:22:30 -07:00
static bool signing = false;
static size_t data_left;
static EthereumTxRequest resp;
static uint8_t hash[32], sig[64], privkey[32];
// FIXME: this is currently 400 bytes. Could be probably improved.
struct SHA3_CTX keccak_ctx;
2016-05-24 14:22:30 -07:00
/*
* Encode length according to RLP.
* FIXME: improve
*/
static int rlp_encode_length(uint8_t *buf, int length, uint8_t firstbyte, bool list)
{
if (!list && (length == 1 && firstbyte <= 0x7f)) {
/* Extra-special case: null is encoded differently */
buf[0] = !firstbyte ? 0x80 : firstbyte;
return 1;
} else if (length <= 55) {
buf[0] = (list ? 0xc0 : 0x80) + length;
return 1;
} else if (length <= 0xff) {
buf[0] = (list ? 0xf7 : 0xb7) + 1;
buf[1] = length;
return 2;
} else if (length <= 0xffff) {
buf[0] = (list ? 0xf7 : 0xb7) + 2;
buf[1] = length >> 8;
buf[2] = length & 0xff;
return 3;
} else {
buf[0] = (list ? 0xf7 : 0xb7) + 3;
buf[1] = length >> 16;
buf[2] = length >> 8;
buf[3] = length & 0xff;
return 4;
}
}
/*
* 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) {
2016-05-25 01:28:53 -07:00
return 1 + length;
} else if (length <= 0xff) {
2016-05-25 01:28:53 -07:00
return 2 + length;
} else if (length <= 0xffff) {
2016-05-25 01:28:53 -07:00
return 3 + length;
2016-06-28 11:44:54 -07:00
} else {
2016-05-25 01:28:53 -07:00
return 4 + length;
2016-06-28 11:44:54 -07:00
}
}
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(int length, uint8_t firstbyte)
{
uint8_t buf[4];
size_t size = rlp_encode_length(buf, length, firstbyte, false);
hash_data(buf, size);
}
/*
* Push an RLP encoded list length to the hash buffer.
*/
static void hash_rlp_list_length(int length)
2016-05-24 14:22:30 -07:00
{
uint8_t buf[4];
size_t size = rlp_encode_length(buf, length, 0, true);
hash_data(buf, size);
}
/*
* 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]);
/* FIXME: this special case should be handled more nicely */
2016-06-28 11:44:54 -07:00
if (!(size == 1 && buf[0] <= 0x7f)) {
hash_data(buf, size);
2016-06-28 11:44:54 -07:00
}
}
2016-05-25 07:40:23 -07:00
static void send_request_chunk(void)
{
2016-05-25 07:40:23 -07:00
resp.data_length = data_left <= 1024 ? data_left : 1024;
msg_write(MessageType_MessageType_EthereumTxRequest, &resp);
}
2016-05-24 14:22:30 -07:00
static void send_signature(void)
{
keccak_Final(&keccak_ctx, hash);
uint8_t v;
if (ecdsa_sign_digest(&secp256k1, privkey, hash, sig, &v) != 0) {
fsm_sendFailure(FailureType_Failure_Other, "Signing failed");
ethereum_signing_abort();
return;
}
memset(privkey, 0, sizeof(privkey));
/* Send back the result */
resp.has_data_length = false;
resp.has_signature_v = true;
resp.signature_v = v + 27;
resp.has_signature_r = true;
resp.signature_r.size = 32;
memcpy(resp.signature_r.bytes, sig, 32);
resp.has_signature_s = true;
resp.signature_s.size = 32;
memcpy(resp.signature_s.bytes, sig + 32, 32);
msg_write(MessageType_MessageType_EthereumTxRequest, &resp);
ethereum_signing_abort();
}
/* FIXME */
static void layoutEthereumConfirmTx(const uint8_t *to, const uint8_t *value)
{
static char _value[21] = {0};
static char _to1[21] = {0};
static char _to2[21] = {0};
data2hex(value, 10, _value);
data2hex(to, 10, _to1);
data2hex(to + 10, 10, _to2);
layoutDialogSwipe(DIALOG_ICON_QUESTION,
"Cancel",
"Confirm",
NULL,
"Really send",
_value,
"from your wallet?",
"To:",
_to1,
_to2
);
}
/*
* RLP fields:
* - nonce (0 .. 32)
* - gas_price (0 .. 32)
* - gas_limit (0 .. 32)
* - to (0, 20)
* - value (0 .. 32)
* - data (0 ..)
*/
void ethereum_signing_init(EthereumSignTx *msg, const HDNode *node)
{
2016-05-24 14:22:30 -07:00
signing = true;
sha3_256_Init(&keccak_ctx);
memset(&resp, 0, sizeof(EthereumTxRequest));
/* NOTE: in the first stage we'll always request more data */
resp.has_data_length = true;
/* FIXME: simplify this check */
if (msg->has_data_initial_chunk) {
if (msg->has_data_length) {
if (msg->data_initial_chunk.size != 1024) {
fsm_sendFailure(FailureType_Failure_Other, "Data length provided, but initial chunk too small");
ethereum_signing_abort();
return;
}
if (msg->data_length == 0) {
fsm_sendFailure(FailureType_Failure_Other, "Invalid data length provided");
ethereum_signing_abort();
return;
}
}
} else if (msg->has_data_length) {
fsm_sendFailure(FailureType_Failure_Other, "Data length provided, but no initial chunk");
ethereum_signing_abort();
return;
}
/* FIXME: include default values (0 / 0) */
layoutEthereumConfirmTx(msg->has_to ? msg->to.bytes : NULL, msg->has_value ? msg->value.bytes : NULL);
if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled by user");
ethereum_signing_abort();
return;
}
/* Stage 1: Calculate total RLP length */
int total_rlp_length = 0;
int total_data_length = 0;
2016-06-28 11:44:54 -07:00
if (msg->has_data_initial_chunk) {
total_data_length += msg->data_initial_chunk.size;
2016-06-28 11:44:54 -07:00
}
if (msg->has_data_length) {
total_data_length += msg->data_length;
2016-06-28 11:44:54 -07:00
}
layoutProgress("Signing Eth", 1);
2016-06-28 11:44:54 -07:00
if (msg->has_nonce) {
total_rlp_length += rlp_calculate_length(msg->nonce.size, msg->nonce.bytes[0]);
2016-06-28 11:44:54 -07:00
} else {
total_rlp_length++;
2016-06-28 11:44:54 -07:00
}
layoutProgress("Signing Eth", 2);
2016-06-28 11:44:54 -07:00
if (msg->has_gas_price) {
total_rlp_length += rlp_calculate_length(msg->gas_price.size, msg->gas_price.bytes[0]);
2016-06-28 11:44:54 -07:00
} else {
total_rlp_length++;
2016-06-28 11:44:54 -07:00
}
layoutProgress("Signing Eth", 3);
2016-06-28 11:44:54 -07:00
if (msg->has_gas_limit) {
total_rlp_length += rlp_calculate_length(msg->gas_limit.size, msg->gas_limit.bytes[0]);
2016-06-28 11:44:54 -07:00
} else {
total_rlp_length++;
2016-06-28 11:44:54 -07:00
}
layoutProgress("Signing Eth", 4);
2016-06-28 11:44:54 -07:00
if (msg->has_to) {
total_rlp_length += rlp_calculate_length(msg->to.size, msg->to.bytes[0]);
2016-06-28 11:44:54 -07:00
} else {
total_rlp_length++;
2016-06-28 11:44:54 -07:00
}
layoutProgress("Signing Eth", 5);
2016-06-28 11:44:54 -07:00
if (msg->has_value) {
total_rlp_length += rlp_calculate_length(msg->value.size, msg->value.bytes[0]);
2016-06-28 11:44:54 -07:00
} else {
total_rlp_length++;
2016-06-28 11:44:54 -07:00
}
layoutProgress("Signing Eth", 6);
2016-06-28 11:44:54 -07:00
if (msg->has_data_initial_chunk) {
total_rlp_length += rlp_calculate_length(total_data_length, msg->data_initial_chunk.bytes[0]);
2016-06-28 11:44:54 -07:00
} else {
total_rlp_length++;
2016-06-28 11:44:54 -07:00
}
layoutProgress("Signing Eth", 7);
/* Stage 2: Store header fields */
hash_rlp_list_length(total_rlp_length);
layoutProgress("Signing Eth", 8);
2016-06-28 11:44:54 -07:00
if (msg->has_nonce) {
hash_rlp_field(msg->nonce.bytes, msg->nonce.size);
2016-06-28 11:44:54 -07:00
} else {
hash_rlp_length(1, 0);
2016-06-28 11:44:54 -07:00
}
2016-06-28 11:44:54 -07:00
if (msg->has_gas_price) {
hash_rlp_field(msg->gas_price.bytes, msg->gas_price.size);
2016-06-28 11:44:54 -07:00
} else {
hash_rlp_length(1, 0);
2016-06-28 11:44:54 -07:00
}
2016-06-28 11:44:54 -07:00
if (msg->has_gas_limit) {
hash_rlp_field(msg->gas_limit.bytes, msg->gas_limit.size);
2016-06-28 11:44:54 -07:00
} else {
hash_rlp_length(1, 0);
2016-06-28 11:44:54 -07:00
}
2016-06-28 11:44:54 -07:00
if (msg->has_to) {
hash_rlp_field(msg->to.bytes, msg->to.size);
2016-06-28 11:44:54 -07:00
} else {
hash_rlp_length(1, 0);
2016-06-28 11:44:54 -07:00
}
2016-06-28 11:44:54 -07:00
if (msg->has_value) {
hash_rlp_field(msg->value.bytes, msg->value.size);
2016-06-28 11:44:54 -07:00
} else {
hash_rlp_length(1, 0);
2016-06-28 11:44:54 -07:00
}
if (msg->has_data_initial_chunk) {
hash_rlp_length(total_data_length, msg->data_initial_chunk.bytes[0]);
hash_data(msg->data_initial_chunk.bytes, msg->data_initial_chunk.size);
2016-06-28 11:44:54 -07:00
} else {
hash_rlp_length(1, 0);
2016-06-28 11:44:54 -07:00
}
layoutProgress("Signing Eth", 9);
2016-05-24 14:22:30 -07:00
/* FIXME: probably this shouldn't be done here, but at a later stage */
memcpy(privkey, node->private_key, 32);
if (msg->has_data_length && msg->data_length > 0) {
layoutProgress("Signing Eth", 20);
data_left = msg->data_length;
2016-05-25 07:40:23 -07:00
send_request_chunk();
} else {
layoutProgress("Signing Eth", 50);
send_signature();
}
2016-05-24 14:22:30 -07:00
}
void ethereum_signing_txack(EthereumTxAck *tx)
{
if (!signing) {
fsm_sendFailure(FailureType_Failure_UnexpectedMessage, "Not in Signing mode");
layoutHome();
return;
}
if (data_left > 0 && (!tx->has_data_chunk || tx->data_chunk.size == 0)) {
fsm_sendFailure(FailureType_Failure_Other, "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) {
2016-05-25 07:40:23 -07:00
send_request_chunk();
} else {
send_signature();
}
2016-05-24 14:22:30 -07:00
}
void ethereum_signing_abort(void)
{
if (signing) {
memset(privkey, 0, sizeof(privkey));
2016-05-24 14:22:30 -07:00
layoutHome();
signing = false;
}
}