diff --git a/firmware/Makefile b/firmware/Makefile index b7d737270..99422c441 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -25,6 +25,7 @@ OBJS += crypto.o OBJS += ethereum.o OBJS += ethereum_tokens.o OBJS += nem2.o +OBJS += nem_mosaics.o OBJS += debug.o diff --git a/firmware/layout2.c b/firmware/layout2.c index d6e50c4ce..9190eb9f8 100644 --- a/firmware/layout2.c +++ b/firmware/layout2.c @@ -30,8 +30,8 @@ #include "qr_encode.h" #include "timer.h" #include "bignum.h" -#include "nem.h" #include "secp256k1.h" +#include "nem2.h" #include "gettext.h" #define BITCOIN_DIVISIBILITY (8) @@ -468,18 +468,7 @@ 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 layoutNEMDialog(const BITMAP *icon, const char *btnNo, const char *btnYes, const char *desc, const char *line1, const char *address, const char *line5, const char *line6) { +void layoutNEMDialog(const BITMAP *icon, const char *btnNo, const char *btnYes, const char *desc, const char *line1, const char *address) { static char first_third[NEM_ADDRESS_SIZE / 3 + 1]; strlcpy(first_third, address, sizeof(first_third)); @@ -496,18 +485,16 @@ void layoutNEMDialog(const BITMAP *icon, const char *btnNo, const char *btnYes, first_third, second_third, third_third, - line5, - line6); + NULL, + NULL); } -void layoutNEMTransferXEM(const char *desc, uint64_t quantity, const bignum256 *mul, uint64_t fee) { +void layoutNEMTransferXEM(const char *desc, uint64_t quantity, const bignum256 *multiplier, 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)); + nem_mosaicFormatAmount(NEM_MOSAIC_DEFINITION_XEM, quantity, multiplier, str_out, sizeof(str_out)); + nem_mosaicFormatAmount(NEM_MOSAIC_DEFINITION_XEM, fee, NULL, str_fee, sizeof(str_fee)); - 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"), @@ -522,14 +509,11 @@ void layoutNEMTransferXEM(const char *desc, uint64_t quantity, const bignum256 * void layoutNEMNetworkFee(const char *desc, bool confirm, const char *fee1_desc, uint64_t fee1, const char *fee2_desc, uint64_t fee2) { char str_fee1[32], str_fee2[32]; - bignum256 amnt; - bn_read_uint64(fee1, &amnt); - bn_format(&amnt, NULL, " " NEM_XEM_TICKER, NEM_XEM_DIVISIBILITY, str_fee1, sizeof(str_fee1)); + nem_mosaicFormatAmount(NEM_MOSAIC_DEFINITION_XEM, fee1, NULL, str_fee1, sizeof(str_fee1)); if (fee2_desc) { - bn_read_uint64(fee2, &amnt); - bn_format(&amnt, NULL, " " NEM_XEM_TICKER, NEM_XEM_DIVISIBILITY, str_fee2, sizeof(str_fee2)); + nem_mosaicFormatAmount(NEM_MOSAIC_DEFINITION_XEM, fee2, NULL, str_fee2, sizeof(str_fee2)); } layoutDialogSwipe(&bmp_icon_question, @@ -544,15 +528,35 @@ void layoutNEMNetworkFee(const char *desc, bool confirm, const char *fee1_desc, NULL); } -void layoutNEMTransferMosaic(const char *namespace, const char *mosaic, uint64_t quantity, const bignum256 *mul) { +void layoutNEMTransferMosaic(const NEMMosaicDefinition *definition, uint64_t quantity, const bignum256 *multiplier) { + char str_out[32], str_levy[32]; + + nem_mosaicFormatAmount(definition, quantity, multiplier, str_out, sizeof(str_out)); + + if (definition->has_levy) { + nem_mosaicFormatLevy(definition, quantity, multiplier, str_levy, sizeof(str_levy)); + } + + layoutDialogSwipe(&bmp_icon_question, + _("Cancel"), + _("Next"), + definition->has_name ? definition->name : _("Mosaic"), + _("Confirm transfer of"), + str_out, + definition->has_levy ? _("and levy of") : NULL, + definition->has_levy ? str_levy : NULL, + NULL, + NULL); +} + +void layoutNEMTransferUnknownMosaic(const char *namespace, const char *mosaic, uint64_t quantity, const bignum256 *multiplier) { 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)); + nem_mosaicFormatAmount(NULL, quantity, multiplier, str_out, sizeof(str_out)); char *decimal = strchr(str_out, '.'); if (decimal != NULL) { @@ -587,24 +591,3 @@ void layoutNEMTransferPayload(const uint8_t *payload, size_t length, bool encryp 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 aa6675f0a..86b02ffe7 100644 --- a/firmware/layout2.h +++ b/firmware/layout2.h @@ -47,10 +47,12 @@ 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 layoutNEMDialog(const BITMAP *icon, const char *btnNo, const char *btnYes, const char *desc, const char *line1, const char *address, const char *line5, const char *line6); -void layoutNEMTransferXEM(const char *desc, uint64_t quantity, const bignum256 *mul, uint64_t fee); + +void layoutNEMDialog(const BITMAP *icon, const char *btnNo, const char *btnYes, const char *desc, const char *line1, const char *address); +void layoutNEMTransferXEM(const char *desc, uint64_t quantity, const bignum256 *multiplier, uint64_t fee); void layoutNEMNetworkFee(const char *desc, bool confirm, const char *fee1_desc, uint64_t fee1, const char *fee2_desc, uint64_t fee2); -void layoutNEMTransferMosaic(const char *namespace, const char *mosaic, uint64_t quantity, const bignum256 *mul); +void layoutNEMTransferMosaic(const NEMMosaicDefinition *definition, uint64_t quantity, const bignum256 *multiplier); +void layoutNEMTransferUnknownMosaic(const char *namespace, const char *mosaic, uint64_t quantity, const bignum256 *multiplier); void layoutNEMTransferPayload(const uint8_t *payload, size_t length, bool encrypted); #endif diff --git a/firmware/nem2.c b/firmware/nem2.c index 3f7336b97..48f8f22e4 100644 --- a/firmware/nem2.c +++ b/firmware/nem2.c @@ -25,6 +25,9 @@ #include "layout2.h" #include "protect.h" #include "rng.h" +#include "secp256k1.h" + +static void format_amount(const NEMMosaicDefinition *definition, uint64_t quantity, const bignum256 *multiplier, const bignum256 *multiplier2, int divisor, char *str_out, size_t size); const char *nem_validate_common(NEMTransactionCommon *common, bool inner) { if (!common->has_network) { @@ -92,51 +95,61 @@ const char *nem_validate_provision_namespace(const NEMProvisionNamespace *provis bool nem_askTransfer(const NEMTransactionCommon *common, const NEMTransfer *transfer, const char *desc) { if (transfer->mosaics_count) { - bool done[transfer->mosaics_count]; - memset(done, 0, sizeof(done)); - - uint64_t quantity[transfer->mosaics_count]; - uint64_t *xemQuantity = NULL; + struct { + bool skip; + uint64_t quantity; + const NEMMosaicDefinition *definition; + } mosaics[transfer->mosaics_count], *xem = NULL; - bignum256 mul; - bn_read_uint64(transfer->amount, &mul); + memset(mosaics, 0, sizeof(mosaics)); for (size_t i = 0; i < transfer->mosaics_count; i++) { // Skip duplicate mosaics - if (done[i]) continue; + if (mosaics[i].skip) 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]; + // XEM is displayed separately + if ((mosaics[i].definition = nem_mosaicByName(mosaic->namespace, mosaic->mosaic))) { + if (mosaics[i].definition == NEM_MOSAIC_DEFINITION_XEM) { + // Do not display as a mosaic + mosaics[i].skip = true; + xem = &mosaics[i]; + } } - quantity[i] = mosaic->quantity; + mosaics[i].quantity = 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; + if (nem_mosaicMatches(mosaics[i].definition, new_mosaic->namespace, new_mosaic->mosaic)) { + // Merge duplicate mosaics + mosaics[j].skip = true; + mosaics[i].quantity += new_mosaic->quantity; } } } - layoutNEMTransferXEM(desc, xemQuantity == NULL ? 0 : *xemQuantity, &mul, common->fee); + bignum256 multiplier; + bn_read_uint64(transfer->amount, &multiplier); + + layoutNEMTransferXEM(desc, xem ? xem->quantity : 0, &multiplier, 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; + // Skip duplicate mosaics or XEM + if (mosaics[i].skip) continue; const NEMMosaic *mosaic = &transfer->mosaics[i]; - layoutNEMTransferMosaic(mosaic->namespace, mosaic->mosaic, quantity[i], &mul); + if (mosaics[i].definition) { + layoutNEMTransferMosaic(mosaics[i].definition, mosaics[i].quantity, &multiplier); + } else { + layoutNEMTransferUnknownMosaic(mosaic->namespace, mosaic->mosaic, mosaics[i].quantity, &multiplier); + } + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { return false; } @@ -160,9 +173,7 @@ bool nem_askTransfer(const NEMTransactionCommon *common, const NEMTransfer *tran _("Confirm"), desc, _("Confirm transfer to"), - transfer->recipient, - NULL, - NULL); + transfer->recipient); if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) { return false; } @@ -285,16 +296,12 @@ bool nem_askMultisig(const char *address, const char *desc, bool cosigning, uint _("Next"), desc, cosigning ? _("Cosign transaction for") : _("Initiate transaction for"), - address, - NULL, - NULL); - + address); if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { return false; } layoutNEMNetworkFee(desc, false, _("Confirm multisig fee"), fee, NULL, 0); - if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { return false; } @@ -329,3 +336,85 @@ bool nem_fsmMultisig(nem_transaction_ctx *context, const NEMTransactionCommon *c return true; } + +const NEMMosaicDefinition *nem_mosaicByName(const char *namespace, const char *mosaic) { + for (size_t i = 0; i < NEM_MOSAIC_DEFINITIONS_COUNT; i++) { + const NEMMosaicDefinition *definition = &NEM_MOSAIC_DEFINITIONS[i]; + + if (nem_mosaicMatches(definition, namespace, mosaic)) { + return definition; + } + } + + return NULL; +} + +void format_amount(const NEMMosaicDefinition *definition, uint64_t quantity, const bignum256 *multiplier, const bignum256 *multiplier2, int divisor, char *str_out, size_t size) { + uint32_t divisibility = definition && definition->has_divisibility ? definition->divisibility : 0; + const char *ticker = definition && definition->has_ticker ? definition->ticker : NULL; + + bignum256 amnt; + bn_read_uint64(quantity, &amnt); + + if (multiplier2) { + bn_multiply(multiplier2, &amnt, &secp256k1.prime); + } + + // Do not use prefix/suffix with bn_format, it messes with the truncation code + if (multiplier) { + bn_multiply(multiplier, &amnt, &secp256k1.prime); + divisor += NEM_MOSAIC_DEFINITION_XEM->divisibility; + } + + // bn_format(amnt / (10 ^ divisor), divisibility) + bn_format(&amnt, NULL, NULL, divisibility + divisor, str_out, size); + + // Truncate as if we called bn_format with (divisibility) instead of (divisibility + divisor) + char *decimal = strchr(str_out, '.'); + if (decimal != NULL) { + const char *terminator = strchr(str_out, '\0'); + + if (divisibility == 0) { + // Truncate as an integer + *decimal = '\0'; + } else { + char *end = decimal + divisibility + 1; + if (end < terminator) { + *end = '\0'; + } + } + } + + if (ticker) { + strlcat(str_out, " ", size); + strlcat(str_out, ticker, size); + } +} + +void nem_mosaicFormatAmount(const NEMMosaicDefinition *definition, uint64_t quantity, const bignum256 *multiplier, char *str_out, size_t size) { + format_amount(definition, quantity, multiplier, NULL, 0, str_out, size); +} + +bool nem_mosaicFormatLevy(const NEMMosaicDefinition *definition, uint64_t quantity, const bignum256 *multiplier, char *str_out, size_t size) { + bignum256 multiplier2; + + if (!definition->has_levy || !definition->has_fee) { + return false; + } + + switch (definition->levy) { + case NEMMosaicLevy_MosaicLevy_Absolute: + format_amount(definition, definition->fee, NULL, NULL, 0, str_out, size); + break; + + case NEMMosaicLevy_MosaicLevy_Percentile: + bn_read_uint64(definition->fee, &multiplier2); + format_amount(definition, quantity, multiplier, &multiplier2, NEM_LEVY_PERCENTILE_DIVISOR, str_out, size); + break; + + default: + return false; + } + + return true; +} diff --git a/firmware/nem2.h b/firmware/nem2.h index db73380f9..dc3f3721d 100644 --- a/firmware/nem2.h +++ b/firmware/nem2.h @@ -21,6 +21,7 @@ #define __NEM2_H__ #include "nem.h" +#include "nem_mosaics.h" #include "messages.pb.h" #include "types.pb.h" @@ -43,4 +44,12 @@ bool nem_fsmProvisionNamespace(nem_transaction_ctx *context, const NEMTransactio bool nem_askMultisig(const char *address, const char *desc, bool cosigning, uint64_t fee); bool nem_fsmMultisig(nem_transaction_ctx *context, const NEMTransactionCommon *common, const nem_transaction_ctx *inner, bool cosigning); +const NEMMosaicDefinition *nem_mosaicByName(const char *namespace, const char *mosaic); +void nem_mosaicFormatAmount(const NEMMosaicDefinition *definition, uint64_t quantity, const bignum256 *multiplier, char *str_out, size_t size); +bool nem_mosaicFormatLevy(const NEMMosaicDefinition *definition, uint64_t quantity, const bignum256 *multiplier, char *str_out, size_t size); + +static inline bool nem_mosaicMatches(const NEMMosaicDefinition *definition, const char *namespace, const char *mosaic) { + return strcmp(namespace, definition->namespace) == 0 && strcmp(mosaic, definition->mosaic) == 0; +} + #endif diff --git a/firmware/nem_mosaics.c b/firmware/nem_mosaics.c new file mode 100644 index 000000000..d9faea3a7 --- /dev/null +++ b/firmware/nem_mosaics.c @@ -0,0 +1,16 @@ +// This file is automatically generated by nem_mosaics.py -- DO NOT EDIT! + +#include "nem_mosaics.h" + +const NEMMosaicDefinition NEM_MOSAIC_DEFINITIONS[NEM_MOSAIC_DEFINITIONS_COUNT] = {{ + .has_ticker = true, + .ticker = " XEM", + .has_namespace = true, + .namespace = "nem", + .has_mosaic = true, + .mosaic = "xem", + .has_divisibility = true, + .divisibility = 6, +}}; + +const NEMMosaicDefinition *NEM_MOSAIC_DEFINITION_XEM = NEM_MOSAIC_DEFINITIONS; diff --git a/firmware/nem_mosaics.h b/firmware/nem_mosaics.h new file mode 100644 index 000000000..5ce599ed6 --- /dev/null +++ b/firmware/nem_mosaics.h @@ -0,0 +1,13 @@ +// This file is automatically generated by nem_mosaics.py -- DO NOT EDIT! + +#ifndef __NEM_MOSAICS_H__ +#define __NEM_MOSAICS_H__ + +#include "types.pb.h" + +#define NEM_MOSAIC_DEFINITIONS_COUNT (1) + +extern const NEMMosaicDefinition NEM_MOSAIC_DEFINITIONS[NEM_MOSAIC_DEFINITIONS_COUNT]; +extern const NEMMosaicDefinition *NEM_MOSAIC_DEFINITION_XEM; + +#endif diff --git a/firmware/nem_mosaics.json b/firmware/nem_mosaics.json new file mode 100644 index 000000000..a5432a417 --- /dev/null +++ b/firmware/nem_mosaics.json @@ -0,0 +1,8 @@ +[ + { + "ticker": " XEM", + "namespace": "nem", + "mosaic": "xem", + "divisibility": 6 + } +] diff --git a/firmware/nem_mosaics.py b/firmware/nem_mosaics.py new file mode 100755 index 000000000..243568958 --- /dev/null +++ b/firmware/nem_mosaics.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +import json, os +from google.protobuf import json_format +from itertools import chain +import protob.types_pb2 as types + +HEADER_TEMPLATE = """ +// This file is automatically generated by nem_mosaics.py -- DO NOT EDIT! + +#ifndef __NEM_MOSAICS_H__ +#define __NEM_MOSAICS_H__ + +#include "types.pb.h" + +#define NEM_MOSAIC_DEFINITIONS_COUNT ({count}) + +extern const NEMMosaicDefinition NEM_MOSAIC_DEFINITIONS[NEM_MOSAIC_DEFINITIONS_COUNT]; +extern const NEMMosaicDefinition *NEM_MOSAIC_DEFINITION_XEM; + +#endif +""".lstrip() + +CODE_TEMPLATE = """ +// This file is automatically generated by nem_mosaics.py -- DO NOT EDIT! + +#include "nem_mosaics.h" + +const NEMMosaicDefinition NEM_MOSAIC_DEFINITIONS[NEM_MOSAIC_DEFINITIONS_COUNT] = {code}; + +const NEMMosaicDefinition *NEM_MOSAIC_DEFINITION_XEM = NEM_MOSAIC_DEFINITIONS; +""".lstrip() + +def format_primitive(value): + if type(value) is bool: + return ("false", "true")[value] + elif type(value) in (int, float): + return str(value) + elif type(value) is str: + return json.dumps(value) + elif type(value) is list: + return "{ " + ", ".join( + format_primitive(item) for item in value + ) + " }" + else: + raise TypeError + +def format_struct(struct): + return "{\n" + "\n".join( + "\t.{0} = {1},".format(member, value) for member, value in struct.items() + ) + "\n}" + + +def format_field(field, value): + if field.message_type is not None: + raise TypeError + elif field.enum_type: + return "{0}_{1}".format(field.enum_type.name, field.enum_type.values_by_number[value].name) + elif hasattr(value, "_values"): + return format_primitive(value._values) + else: + return format_primitive(value) + +def field_to_meta(field, value): + if hasattr(value, "_values"): + return ("{}_count".format(field.name), format_primitive(len(value._values))) + else: + return ("has_{}".format(field.name), format_primitive(True)) + +def message_to_struct(_message, proto): + message = json_format.ParseDict(_message, proto()) + return dict(chain.from_iterable( + ( + field_to_meta(field, value), + (field.name, format_field(field, value)), + ) for field, value in message.ListFields() + )) + +def format_message(message, proto): + return format_struct(message_to_struct(message, proto)) + +def format_messages(messages, proto): + return "{" + ",\n".join( + format_message(message, proto) for message in messages + ) + "}" + +if __name__ == "__main__": + os.chdir(os.path.abspath(os.path.dirname(__file__))) + + messages = json.load(open("nem_mosaics.json")) + + with open("nem_mosaics.h", "w+") as f: + f.write(HEADER_TEMPLATE.format(count=format_primitive(len(messages)))) + + with open("nem_mosaics.c", "w+") as f: + f.write(CODE_TEMPLATE.format(code=format_messages(messages, types.NEMMosaicDefinition)))