diff --git a/firmware/Makefile b/firmware/Makefile index 902ebcc4f..b7d737270 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -24,6 +24,7 @@ OBJS += signing.o OBJS += crypto.o OBJS += ethereum.o OBJS += ethereum_tokens.o +OBJS += nem2.o OBJS += debug.o diff --git a/firmware/fsm.c b/firmware/fsm.c index f05e95278..15395425e 100644 --- a/firmware/fsm.c +++ b/firmware/fsm.c @@ -51,6 +51,7 @@ #include #include "ethereum.h" #include "nem.h" +#include "nem2.h" #include "gettext.h" // message methods @@ -1144,6 +1145,46 @@ void fsm_msgNEMGetAddress(NEMGetAddress *msg) layoutHome(); } +void fsm_msgNEMSignTx(NEMSignTx *msg) { + CHECK_PARAM(msg->has_transaction, _("No common provided")); + CHECK_PARAM(msg->has_transfer, _("No transaction provided")); + + const char *reason; + CHECK_PARAM(!(reason = nem_validate_common(&msg->transaction)), reason); + CHECK_PARAM(!(reason = nem_validate_transfer(&msg->transfer, msg->transaction.network)), reason); + + CHECK_INITIALIZED + CHECK_PIN + + if (!nem_askTransfer(&msg->transaction, &msg->transfer)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, _("Signing cancelled by user")); + layoutHome(); + return; + } + + RESP_INIT(NEMSignedTx); + + HDNode *node = fsm_getDerivedNode(ED25519_KECCAK_NAME, msg->transaction.address_n, msg->transaction.address_n_count); + if (!node) return; + + nem_transaction_ctx context; + nem_transaction_start(&context, &node->public_key[1], resp->data.bytes, sizeof(resp->data.bytes)); + + if (!nem_fsmTransfer(&context, node, &msg->transaction, &msg->transfer)) { + layoutHome(); + return; + } + + resp->has_data = true; + resp->data.size = nem_transaction_end(&context, node->private_key, resp->signature.bytes); + + resp->has_signature = true; + resp->signature.size = sizeof(ed25519_signature); + + msg_write(MessageType_MessageType_NEMSignedTx, resp); + layoutHome(); +} + #if DEBUG_LINK void fsm_msgDebugLinkGetState(DebugLinkGetState *msg) diff --git a/firmware/fsm.h b/firmware/fsm.h index e127b386d..3768e1299 100644 --- a/firmware/fsm.h +++ b/firmware/fsm.h @@ -67,6 +67,7 @@ void fsm_msgEthereumSignMessage(EthereumSignMessage *msg); void fsm_msgEthereumVerifyMessage(EthereumVerifyMessage *msg); void fsm_msgNEMGetAddress(NEMGetAddress *msg); +void fsm_msgNEMSignTx(NEMSignTx *msg); // debug message functions #if DEBUG_LINK diff --git a/firmware/layout2.c b/firmware/layout2.c index b26c3be98..ccd9eda6c 100644 --- a/firmware/layout2.c +++ b/firmware/layout2.c @@ -30,6 +30,8 @@ #include "qr_encode.h" #include "timer.h" #include "bignum.h" +#include "nem.h" +#include "secp256k1.h" #include "gettext.h" #define BITCOIN_DIVISIBILITY (8) @@ -465,3 +467,99 @@ void layoutU2FDialog(const char *verb, const char *appname, const BITMAP *appico } layoutDialog(appicon, NULL, verb, NULL, verb, _("U2F security key?"), NULL, appname, NULL, NULL); } + +static inline void nemFormatAmount(bignum256 *amnt, uint64_t quantity, int divisibility, const bignum256 *mul, const char *ticker, char *str_out, size_t size) { + bn_read_uint64(quantity, amnt); + + if (mul) { + bn_multiply(mul, amnt, &secp256k1.prime); + bn_format(amnt, NULL, ticker, divisibility + NEM_XEM_DIVISIBILITY, str_out, size); + } else { + bn_format(amnt, NULL, ticker, divisibility, str_out, size); + } +} + +void layoutNEMTransferXEM(const char *desc, uint64_t quantity, const bignum256 *mul, uint64_t fee) { + char str_out[32], str_fee[32]; + bignum256 amnt; + + nemFormatAmount(&amnt, quantity, NEM_XEM_DIVISIBILITY, mul, " " NEM_XEM_TICKER, str_out, sizeof(str_out)); + + bn_read_uint64(fee, &amnt); + bn_format(&amnt, NULL, " " NEM_XEM_TICKER, NEM_XEM_DIVISIBILITY, str_fee, sizeof(str_fee)); + layoutDialogSwipe(&bmp_icon_question, + _("Cancel"), + _("Next"), + desc, + _("Confirm transfer of"), + str_out, + _("and network fee of"), + str_fee, + NULL, + NULL); +} + +void layoutNEMTransferMosaic(const char *namespace, const char *mosaic, uint64_t quantity, const bignum256 *mul) { + char mosaic_name[256]; + strlcpy(mosaic_name, namespace, sizeof(mosaic_name)); + strlcat(mosaic_name, ".", sizeof(mosaic_name)); + strlcat(mosaic_name, mosaic, sizeof(mosaic_name)); + + char str_out[32]; + bignum256 amnt; + nemFormatAmount(&amnt, quantity, 0, mul, NULL, str_out, sizeof(str_out)); + + char *decimal = strchr(str_out, '.'); + if (decimal != NULL) { + *decimal = '\0'; + } + + layoutDialogSwipe(&bmp_icon_question, + _("Cancel"), + _("I take the risk"), + _("Unknown Mosaic"), + _("Confirm transfer of"), + str_out, + _("raw units of"), + mosaic_name, + NULL, + NULL); +} + +void layoutNEMTransferPayload(const uint8_t *payload, size_t length, bool encrypted) { + if (payload[0] == 0xFE) { + char encoded[(length - 1) * 2 + 1]; + data2hex(&payload[1], length - 1, encoded); + + const char **str = split_message((uint8_t *) encoded, sizeof(encoded) - 1, 16); + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Next"), + encrypted ? _("Encrypted hex data") : _("Unencrypted hex data"), + str[0], str[1], str[2], str[3], NULL, NULL); + } else { + const char **str = split_message(payload, length, 16); + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Next"), + encrypted ? _("Encrypted message") : _("Unencrypted message"), + str[0], str[1], str[2], str[3], NULL, NULL); + } +} + +void layoutNEMTransferTo(const char *desc, const char *address) { + static char first_third[NEM_ADDRESS_SIZE / 3 + 1]; + strlcpy(first_third, address, sizeof(first_third)); + + static char second_third[NEM_ADDRESS_SIZE / 3 + 1]; + strlcpy(second_third, &address[NEM_ADDRESS_SIZE / 3], sizeof(second_third)); + + const char *third_third = &address[NEM_ADDRESS_SIZE * 2 / 3]; + + layoutDialogSwipe(&bmp_icon_question, + _("Cancel"), + _("Confirm"), + desc, + _("Confirm transfer to"), + first_third, + second_third, + third_third, + NULL, + NULL); +} diff --git a/firmware/layout2.h b/firmware/layout2.h index 643aa4624..6364e0541 100644 --- a/firmware/layout2.h +++ b/firmware/layout2.h @@ -23,6 +23,7 @@ #include "layout.h" #include "types.pb.h" #include "bitmaps.h" +#include "bignum.h" extern void *layoutLast; @@ -46,5 +47,9 @@ void layoutPublicKey(const uint8_t *pubkey); void layoutSignIdentity(const IdentityType *identity, const char *challenge); void layoutDecryptIdentity(const IdentityType *identity); void layoutU2FDialog(const char *verb, const char *appname, const BITMAP *appicon); +void layoutNEMTransferXEM(const char *desc, uint64_t quantity, const bignum256 *mul, uint64_t fee); +void layoutNEMTransferMosaic(const char *namespace, const char *mosaic, uint64_t quantity, const bignum256 *mul); +void layoutNEMTransferPayload(const uint8_t *payload, size_t length, bool encrypted); +void layoutNEMTransferTo(const char *desc, const char *address); #endif diff --git a/firmware/nem2.c b/firmware/nem2.c new file mode 100644 index 000000000..62d92549f --- /dev/null +++ b/firmware/nem2.c @@ -0,0 +1,212 @@ +/* + * This file is part of the TREZOR project. + * + * Copyright (C) 2017 Saleem Rashid + * + * 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 . + */ + +#include "nem2.h" + +#include "aes.h" +#include "fsm.h" +#include "gettext.h" +#include "layout2.h" +#include "protect.h" +#include "rng.h" + +const char *nem_validate_common(NEMTransactionCommon *common) { + if (!common->has_network) { + common->has_network = true; + common->network = NEM_NETWORK_MAINNET; + } + + if (nem_network_name(common->network) == NULL) return _("Invalid NEM network"); + if (!common->has_timestamp) return _("No timestamp provided"); + if (!common->has_fee) return _("No fee provided"); + if (!common->has_deadline) return _("No deadline provided"); + + return NULL; +} + +const char *nem_validate_transfer(const NEMTransfer *transfer, uint8_t network) { + if (!transfer->has_recipient) return _("No recipient provided"); + if (!transfer->has_amount) return _("No amount provided"); + if (transfer->has_public_key && transfer->public_key.size != 32) return _("Invalid recipient public key"); + + if (!nem_validate_address(transfer->recipient, network)) return _("Invalid recipient address"); + + for (size_t i = 0; i < transfer->mosaics_count; i++) { + const NEMMosaic *mosaic = &transfer->mosaics[i]; + + if (!mosaic->has_namespace) return "No mosaic namespace provided"; + if (!mosaic->has_mosaic) return "No mosaic name provided"; + if (!mosaic->has_quantity) return "No mosaic quantity provided"; + } + + return NULL; +} + + +bool nem_askTransfer(const NEMTransactionCommon *common, const NEMTransfer *transfer) { + const char *network = nem_network_name(common->network); + if (network == NULL) { + return false; + } + + + if (transfer->mosaics_count) { + bool done[transfer->mosaics_count]; + memset(done, 0, sizeof(done)); + + uint64_t quantity[transfer->mosaics_count]; + uint64_t *xemQuantity = NULL; + + bignum256 mul; + bn_read_uint64(transfer->amount, &mul); + + for (size_t i = 0; i < transfer->mosaics_count; i++) { + // Skip duplicate mosaics + if (done[i]) continue; + + const NEMMosaic *mosaic = &transfer->mosaics[i]; + + // XEM is treated specially + if (strcmp(mosaic->namespace, "nem") == 0 && strcmp(mosaic->mosaic, "xem") == 0) { + done[i] = true; + xemQuantity = &quantity[i]; + } + + quantity[i] = mosaic->quantity; + for (size_t j = i + 1; j < transfer->mosaics_count; j++) { + const NEMMosaic *new_mosaic = &transfer->mosaics[j]; + + if (strcmp(mosaic->namespace, new_mosaic->namespace) == 0 && strcmp(mosaic->mosaic, new_mosaic->mosaic) == 0) { + // Duplicate mosaics are merged into one + done[i] = true; + quantity[i] += transfer->mosaics[j].quantity; + } + } + } + + layoutNEMTransferXEM(network, xemQuantity == NULL ? 0 : *xemQuantity, &mul, common->fee); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + + for (size_t i = 0; i < transfer->mosaics_count; i++) { + // Skip special or duplicate mosaics + if (done[i]) continue; + + const NEMMosaic *mosaic = &transfer->mosaics[i]; + + layoutNEMTransferMosaic(mosaic->namespace, mosaic->mosaic, quantity[i], &mul); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + } + } else { + layoutNEMTransferXEM(network, transfer->amount, NULL, common->fee); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + } + + if (transfer->has_payload) { + layoutNEMTransferPayload(transfer->payload.bytes, transfer->payload.size, transfer->has_public_key); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + } + + layoutNEMTransferTo(network, transfer->recipient); + if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) { + return false; + } + + return true; +} + +bool nem_fsmTransfer(nem_transaction_ctx *context, const HDNode *node, const NEMTransactionCommon *common, const NEMTransfer *transfer) { + static uint8_t encrypted[NEM_ENCRYPTED_PAYLOAD_SIZE(sizeof(transfer->payload.bytes))]; + + const uint8_t *payload = transfer->payload.bytes; + size_t size = transfer->payload.size; + + if (transfer->has_public_key) { + if (node == NULL) { + fsm_sendFailure(FailureType_Failure_ProcessError, _("Private key unavailable for encrypted message")); + return false; + } + + random_buffer(encrypted, NEM_SALT_SIZE + AES_BLOCK_SIZE); + + // hdnode_nem_encrypt mutates the IV + uint8_t iv[AES_BLOCK_SIZE]; + memcpy(iv, &encrypted[NEM_SALT_SIZE], AES_BLOCK_SIZE); + + const uint8_t *salt = encrypted; + uint8_t *buffer = &encrypted[NEM_SALT_SIZE + AES_BLOCK_SIZE]; + + bool ret = hdnode_nem_encrypt(node, + transfer->public_key.bytes, + iv, + salt, + payload, + size, + buffer); + + if (!ret) { + fsm_sendFailure(FailureType_Failure_ProcessError, _("Failed to encrypt payload")); + return false; + } + + payload = encrypted; + size = NEM_ENCRYPTED_PAYLOAD_SIZE(size); + } + + bool ret = nem_transaction_create_transfer(context, + common->network, + common->timestamp, + NULL, + common->fee, + common->deadline, + transfer->recipient, + transfer->amount, + payload, + size, + transfer->has_public_key, + transfer->mosaics_count); + + if (!ret) { + fsm_sendFailure(FailureType_Failure_ProcessError, _("Failed to create transfer transaction")); + return false; + } + + for (size_t i = 0; i < transfer->mosaics_count; i++) { + const NEMMosaic *mosaic = &transfer->mosaics[i]; + + ret = nem_transaction_write_mosaic(context, + mosaic->namespace, + mosaic->mosaic, + mosaic->quantity); + + if (!ret) { + fsm_sendFailure(FailureType_Failure_ProcessError, "Failed to attach mosaics"); + return false; + } + } + + return true; +} diff --git a/firmware/nem2.h b/firmware/nem2.h new file mode 100644 index 000000000..ed394fdab --- /dev/null +++ b/firmware/nem2.h @@ -0,0 +1,36 @@ +/* + * This file is part of the TREZOR project. + * + * Copyright (C) 2017 Saleem Rashid + * + * 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 . + */ + +#ifndef __NEM2_H__ +#define __NEM2_H__ + +#include "nem.h" + +#include "messages.pb.h" +#include "types.pb.h" + +#include + +const char *nem_validate_common(NEMTransactionCommon *common); +const char *nem_validate_transfer(const NEMTransfer *transfer, uint8_t network); + +bool nem_askTransfer(const NEMTransactionCommon *common, const NEMTransfer *transfer); +bool nem_fsmTransfer(nem_transaction_ctx *context, const HDNode *node, const NEMTransactionCommon *common, const NEMTransfer *transfer); + +#endif