diff --git a/app/Makefile b/app/Makefile index 38bed6b1..78708f24 100755 --- a/app/Makefile +++ b/app/Makefile @@ -26,6 +26,8 @@ include $(CURDIR)/../deps/ledger-zxlib/makefiles/Makefile.installer_script include $(BOLOS_SDK)/Makefile.defines +DEFINES += HAVE_SWAP + DEFINES += APP_SECRET_MODE_ENABLED $(info ************ TARGET_NAME = [$(TARGET_NAME)]) @@ -77,7 +79,7 @@ endif APP_LOAD_PARAMS = --curve ed25519 $(COMMON_LOAD_PARAMS) --path $(APPPATH) -NANOS_STACK_SIZE := 3216 +NANOS_STACK_SIZE := 3160 include $(CURDIR)/../deps/ledger-zxlib/makefiles/Makefile.devices diff --git a/app/Makefile.version b/app/Makefile.version index 9687c6a7..8e874b98 100644 --- a/app/Makefile.version +++ b/app/Makefile.version @@ -3,4 +3,4 @@ APPVERSION_M=20 # This is the `spec_version` field of `Runtime` APPVERSION_N=9370 # This is the patch version of this release -APPVERSION_P=0 +APPVERSION_P=1 diff --git a/app/src/apdu_handler.c b/app/src/apdu_handler.c index bab37de1..13db6f9e 100644 --- a/app/src/apdu_handler.c +++ b/app/src/apdu_handler.c @@ -31,6 +31,8 @@ #include "zxmacros.h" #include "secret.h" #include "app_mode.h" +#include "swap.h" +#include "view_internal.h" static bool tx_initialized = false; @@ -144,7 +146,7 @@ __Z_INLINE void handleGetAddr(volatile uint32_t *flags, volatile uint32_t *tx, u } if (requireConfirmation) { view_review_init(addr_getItem, addr_getNumItems, app_reply_address); - view_review_show(0x03); + view_review_show(REVIEW_ADDRESS); *flags |= IO_ASYNCH_REPLY; return; } @@ -172,9 +174,15 @@ __Z_INLINE void handleSignSr25519(volatile uint32_t *flags, volatile uint32_t *t THROW(APDU_CODE_DATA_INVALID); } - view_review_init(tx_getItem, tx_getNumItems, app_return_sr25519); - view_review_show(0x03); - *flags |= IO_ASYNCH_REPLY; + if (G_swap_state.called_from_swap) { + G_swap_state.should_exit = 1; + app_sign_sr25519(); + app_return_sr25519(); + } else { + view_review_init(tx_getItem, tx_getNumItems, app_return_sr25519); + view_review_show(REVIEW_TXN); + *flags |= IO_ASYNCH_REPLY; + } } #endif @@ -188,9 +196,14 @@ __Z_INLINE void handleSignEd25519(volatile uint32_t *flags, volatile uint32_t *t THROW(APDU_CODE_DATA_INVALID); } - view_review_init(tx_getItem, tx_getNumItems, app_sign_ed25519); - view_review_show(0x03); - *flags |= IO_ASYNCH_REPLY; + if (G_swap_state.called_from_swap) { + G_swap_state.should_exit = 1; + app_sign_ed25519(); + } else { + view_review_init(tx_getItem, tx_getNumItems, app_sign_ed25519); + view_review_show(REVIEW_TXN); + *flags |= IO_ASYNCH_REPLY; + } } __Z_INLINE void handleSign(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { diff --git a/app/src/common/main.c b/app/src/common/main.c index 87150f07..9e52617f 100644 --- a/app/src/common/main.c +++ b/app/src/common/main.c @@ -16,17 +16,42 @@ ********************************************************************************/ #include "app_main.h" #include "view.h" +#include "swap.h" #include +static void app_exit(void) { + BEGIN_TRY_L(exit) { + TRY_L(exit) { + os_sched_exit(-1); + } + FINALLY_L(exit) { + } + } + END_TRY_L(exit); +} + __attribute__((section(".boot"))) int -main(void) { +main(int arg0) { // exit critical section __asm volatile("cpsie i"); view_init(); os_boot(); + if (arg0) { + libargs_s *args = (libargs_s *) arg0; + if (args->id != 0x100) { + app_exit(); + return 0; + } + + swap_handle_command(args); + if (!G_swap_state.called_from_swap) { + os_lib_end(); + } + } + BEGIN_TRY { TRY diff --git a/app/src/common/tx.c b/app/src/common/tx.c index 7a979b98..04125217 100644 --- a/app/src/common/tx.c +++ b/app/src/common/tx.c @@ -20,6 +20,7 @@ #include "parser.h" #include #include "zxmacros.h" +#include "swap.h" #if defined(TARGET_NANOX) || defined(TARGET_NANOS2) #define RAM_BUFFER_SIZE 8192 @@ -89,6 +90,15 @@ const char *tx_parse() { return parser_getErrorDescription(err); } + // If in swap mode, compare swap tx parameters with stored info. + if (G_swap_state.called_from_swap) { + err = check_swap_conditions(&ctx_parsed_tx); + CHECK_APP_CANARY() + if (err != parser_ok) { + return parser_getErrorDescription(err); + } + } + return NULL; } diff --git a/app/src/swap.c b/app/src/swap.c new file mode 100644 index 00000000..0bdfd44e --- /dev/null +++ b/app/src/swap.c @@ -0,0 +1,292 @@ +/******************************************************************************* +* (c) 2016 Ledger +* (c) 2018 - 2023 Zondax AG +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +#include "swap.h" +#include "crypto.h" +#include "bignum.h" +#include "zxformat.h" +#include "substrate_dispatch.h" + +static zxerr_t extractHDPath(uint8_t* params, uint8_t paramsSize); +static zxerr_t readU32BE(uint8_t* input, uint32_t *output); +static zxerr_t readU64BE(uint8_t* input, uint64_t *output); +static zxerr_t bytesAmountToStringBalance(uint8_t *amount, uint8_t amount_len, char *out, uint8_t out_len); + +static void handle_check_address(check_address_parameters_t* params); +static void handle_get_printable_amount(get_printable_amount_parameters_t* params); + +static bool copy_transaction_parameters(create_transaction_parameters_t* sign_transaction_params); + +swap_globals_t G_swap_state; + +void swap_handle_command(libargs_s *args) { + switch (args->command) { + case check_address: + handle_check_address(args->check_address); + G_swap_state.called_from_swap = 0; + break; + + case sign_transaction: + if (copy_transaction_parameters(args->create_transaction)) { + G_swap_state.called_from_swap = 1; + } else { + G_swap_state.called_from_swap = 0; + } + break; + + case get_printable_amount: + handle_get_printable_amount(args->get_printable_amount); + G_swap_state.called_from_swap = 0; + break; + + case run_application: + default: + break; + } +} + +void handle_check_address(check_address_parameters_t* params) { + if (params == NULL) { + return; + } + params->result = 0; + uint8_t buffer[100] = {0}; + uint16_t replyLen = 0; + + // address parameters have the following structure + // address kind (1 byte) | path length (1 byte) | bip44 path (4 * pathLength bytes) + const key_kind_e add_kind = (key_kind_e) *params->address_parameters; + zxerr_t err = extractHDPath((uint8_t*) params->address_parameters + 2, (uint8_t) params->address_parameters_length - 2); + if (params->address_to_check == 0 || err != zxerr_ok) { + return; + } + + err = crypto_fillAddress(add_kind, buffer, sizeof(buffer), &replyLen); + + if (err != zxerr_ok || replyLen <= PK_LEN_25519) { + MEMZERO(buffer, sizeof(buffer)); + return; + } + + const uint8_t *address = buffer + PK_LEN_25519; + const uint8_t addressLen = replyLen - PK_LEN_25519; + const uint8_t addressToCheckLen = strlen(params->address_to_check); + + if (addressLen == addressToCheckLen && + memcmp(address, params->address_to_check, addressLen) == 0) { + params->result = 1; + } +} + +void handle_get_printable_amount( get_printable_amount_parameters_t* params) { + if (params == NULL) { + return; + } + uint8_t amount[16]; + + MEMZERO(amount, sizeof(amount)); + MEMZERO(params->printable_amount, sizeof(params->printable_amount)); + + memcpy(amount + 16 - params->amount_length, params->amount, params->amount_length); + + char tmp_amount[100] = {0}; + const zxerr_t zxerr = bytesAmountToStringBalance(amount, sizeof(amount), tmp_amount, sizeof(tmp_amount)); + + if (zxerr != zxerr_ok || strnlen(tmp_amount, sizeof(tmp_amount)) > sizeof(params->printable_amount)) { + return; + } + strncpy(params->printable_amount, tmp_amount, sizeof(params->printable_amount) - 1); +} + +bool copy_transaction_parameters(create_transaction_parameters_t* sign_transaction_params) { + if (sign_transaction_params == NULL) { + return false; + } + + // First copy parameters to stack, and then to global data. + // We need this "trick" as the input data position can overlap with globals + char destination_address[65] = {0}; + uint8_t amount[16] = {0}; + uint8_t amount_length = {0}; + uint8_t fees[8] = {0}; + + strncpy(destination_address, + sign_transaction_params->destination_address, + sizeof(destination_address) - 1); + + if ((destination_address[sizeof(destination_address) - 1] != '\0') || + (sign_transaction_params->amount_length > 8) || + (sign_transaction_params->fee_amount_length > 8)) { + return false; + } + + // store amount as big endian in 16 bytes, so the passed data should be alligned to right + // input {0xEE, 0x00, 0xFF} should be stored like {0x00, 0x00, 0x00, 0x00, 0x00, 0xEE, 0x00, 0xFF} + memcpy(amount + 16 - sign_transaction_params->amount_length, + sign_transaction_params->amount, + sign_transaction_params->amount_length); + + memcpy(fees + 8 - sign_transaction_params->fee_amount_length, + sign_transaction_params->fee_amount, + sign_transaction_params->fee_amount_length); + + amount_length = sign_transaction_params->amount_length; + + MEMZERO(&G_swap_state, sizeof(G_swap_state)); + + G_swap_state.amount_length = amount_length; + memcpy(G_swap_state.amount,amount,sizeof(amount)); + memcpy(G_swap_state.destination_address, + destination_address, + sizeof(G_swap_state.destination_address)); + readU64BE(fees, &G_swap_state.fees); + + return true; +} + +parser_error_t check_swap_conditions(const parser_context_t *ctx) { + parser_error_t err = parser_unexpected_error; + if (ctx == NULL) { + return err; + } + // Check method. + const char * valid_tx_method = "Balances Transfer"; + char tmp_str[80] = {0}; + snprintf(tmp_str, sizeof(tmp_str), "%s %s", _getMethod_ModuleName(ctx->tx_obj->transactionVersion, + ctx->tx_obj->callIndex.moduleIdx), + _getMethod_Name(ctx->tx_obj->transactionVersion, + ctx->tx_obj->callIndex.moduleIdx, + ctx->tx_obj->callIndex.idx)); + + if (strncmp(tmp_str, &valid_tx_method[0], strlen(valid_tx_method)) != 0) { + PRINTF("Wrong swap tx method (%s, should be : %s).\n",tmp_str,valid_tx_method); + ZEMU_LOGF(100, "Wrong swap tx method (%s, should be : %s).\n",tmp_str,valid_tx_method) + return parser_swap_tx_wrong_method; + } + + // Check transaction method arguments number. Balance transfer Should be 2 (for tx v18). + if (_getMethod_NumItems(ctx->tx_obj->transactionVersion, + ctx->tx_obj->callIndex.moduleIdx, + ctx->tx_obj->callIndex.idx) != 2) { + ZEMU_LOGF(50, "Wrong swap tx method arguments count.\n") + return parser_swap_tx_wrong_method_args_num; + } + + // Check destination address. + MEMZERO(tmp_str,sizeof(tmp_str)); + uint8_t pageCount = 0; + err = _getMethod_ItemValue(ctx->tx_obj->transactionVersion, + &ctx->tx_obj->method, + ctx->tx_obj->callIndex.moduleIdx, ctx->tx_obj->callIndex.idx, 0, + tmp_str, sizeof(tmp_str), + 0, &pageCount); + + if (err != parser_ok) { + ZEMU_LOGF(50, "Could not parse swap tx destination address."); + return err; + } + + if (strncmp(tmp_str, &(G_swap_state.destination_address[0]), sizeof(G_swap_state.destination_address)) != 0) { + ZEMU_LOGF(100, "Wrong swap tx destination address (%s, should be : %s).\n", tmp_str, G_swap_state.destination_address) + return parser_swap_tx_wrong_dest_addr; + } + + // Check amount. + MEMZERO(tmp_str, sizeof(tmp_str)); + err = _getMethod_ItemValue(ctx->tx_obj->transactionVersion, + &ctx->tx_obj->method, + ctx->tx_obj->callIndex.moduleIdx, ctx->tx_obj->callIndex.idx, 1, + tmp_str, sizeof(tmp_str), + 0, &pageCount); + + if(err != parser_ok) + { + ZEMU_LOGF(50, "Could not parse swap tx amount."); + return err; + } + + char tmp_amount[100] = {0}; + const zxerr_t zxerr = bytesAmountToStringBalance(G_swap_state.amount, sizeof(G_swap_state.amount), tmp_amount, sizeof(tmp_amount)); + + const size_t strLen = strlen(tmp_str); + const size_t amountLen = strlen(tmp_amount); + if (zxerr != zxerr_ok || strLen != amountLen || strncmp(tmp_str, tmp_amount, strLen)) { + ZEMU_LOGF(100, "Wrong swap tx amount (%s, should be : %s).\n", tmp_str, tmp_amount) + return parser_swap_tx_wrong_amount; + } + + ZEMU_LOGF(50, "Swap parameters verified by current tx\n") + return err; +} + + +//////////////////////////////////////////////////////////////// +zxerr_t extractHDPath(uint8_t* params, uint8_t paramsSize) { + if (paramsSize != (sizeof(uint32_t) * HDPATH_LEN_DEFAULT)) { + return zxerr_invalid_crypto_settings; + } + + for (uint32_t i = 0; i < HDPATH_LEN_DEFAULT; i++) { + CHECK_ZXERR(readU32BE(params + (i * 4), &hdPath[i])) + } + + return zxerr_ok; +} + +zxerr_t readU32BE(uint8_t* input, uint32_t *output) { + if (input == NULL || output == NULL) { + return zxerr_no_data; + } + + *output = 0; + for(uint8_t i = 0; i < 4; i++) { + *output += (uint32_t) *(input + i) << (32 - (8*(i+1))); + } + return zxerr_ok; +} + +zxerr_t readU64BE(uint8_t* input, uint64_t *output) { + if (input == NULL || output == NULL) { + return zxerr_no_data; + } + + *output = 0; + for(uint8_t i = 0; i < 8; i++) { + *output += (uint64_t) *(input + i) << (64 - (8*(i+1))); + } + return zxerr_ok; +} + +zxerr_t bytesAmountToStringBalance(uint8_t *amount, uint8_t amount_len, char *out, uint8_t out_len) { + uint8_t tmpBuf[50] = {0}; + + //Convert byte array (up to 128bits/16bytes) to decimal string + bignumBigEndian_to_bcd(tmpBuf, sizeof(tmpBuf), amount, amount_len); + bignumBigEndian_bcdprint(out, out_len, tmpBuf, sizeof(tmpBuf)); + + // Format number. + if (!intstr_to_fpstr_inplace(out, out_len, COIN_AMOUNT_DECIMAL_PLACES)) { + return zxerr_encoding_failed; + } + + // Add ticker prefix. + CHECK_ZXERR(z_str3join(out, out_len, COIN_TICKER, "")) + + // Trim trailing zeros + number_inplace_trimming(out, 1); + + return zxerr_ok; +} diff --git a/app/src/swap.h b/app/src/swap.h new file mode 100644 index 00000000..eff8d5d9 --- /dev/null +++ b/app/src/swap.h @@ -0,0 +1,100 @@ +/******************************************************************************* +* (c) 2016 Ledger +* (c) 2018 - 2023 Zondax AG +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +#pragma once + +#include "stdbool.h" +#include "stdint.h" +#include "parser_common.h" + +#define RUN_APPLICATION 1 +#define SIGN_TRANSACTION 2 +#define CHECK_ADDRESS 3 +#define GET_PRINTABLE_AMOUNT 4 + +typedef enum { + run_application = 1, + sign_transaction, + check_address, + get_printable_amount, +} swap_options_e; + +// structure that should be send to specific coin application to get address +typedef struct { + // IN + unsigned char* coin_configuration; + unsigned char coin_configuration_length; + // serialized path, segwit, version prefix, hash used, dictionary etc. + // fields and serialization format depends on spesific coin app + unsigned char* address_parameters; + unsigned char address_parameters_length; + char *address_to_check; + char *extra_id_to_check; + // OUT + int result; +} check_address_parameters_t; + +// structure that should be send to specific coin application to get printable amount +typedef struct { + // IN + unsigned char* coin_configuration; + unsigned char coin_configuration_length; + unsigned char* amount; + unsigned char amount_length; + bool is_fee; + // OUT + char printable_amount[30]; + // int result; +} get_printable_amount_parameters_t; + +typedef struct { + unsigned char* coin_configuration; + unsigned char coin_configuration_length; + unsigned char* amount; + unsigned char amount_length; + unsigned char* fee_amount; + unsigned char fee_amount_length; + char *destination_address; + char *destination_address_extra_id; +} create_transaction_parameters_t; + + +typedef struct { + uint8_t amount[16]; + uint8_t amount_length; + uint64_t fees; + char destination_address[65]; + /*Is swap mode*/ + unsigned char called_from_swap; + unsigned char should_exit; +} swap_globals_t; + + +typedef struct { + unsigned int id; + unsigned int command; + void *coin_config_legacy; // This is unused but kept for compatibility + union { + check_address_parameters_t *check_address; + create_transaction_parameters_t *create_transaction; + get_printable_amount_parameters_t *get_printable_amount; + }; +} libargs_s; + + +extern swap_globals_t G_swap_state; +void swap_handle_command(libargs_s *args); +parser_error_t check_swap_conditions(const parser_context_t *ctx); diff --git a/deps/ledger-zxlib b/deps/ledger-zxlib index 21c713d2..bb075d8c 160000 --- a/deps/ledger-zxlib +++ b/deps/ledger-zxlib @@ -1 +1 @@ -Subproject commit 21c713d23293c0de88240f18e173644efa690fdb +Subproject commit bb075d8cb91f6c5a5bd8027068657dc65c47434e diff --git a/tests_zemu/snapshots/s-mainmenu/00004.png b/tests_zemu/snapshots/s-mainmenu/00004.png index 56bce837..b2e86ad1 100644 Binary files a/tests_zemu/snapshots/s-mainmenu/00004.png and b/tests_zemu/snapshots/s-mainmenu/00004.png differ diff --git a/tests_zemu/snapshots/s-mainmenu/00010.png b/tests_zemu/snapshots/s-mainmenu/00010.png index 56bce837..b2e86ad1 100644 Binary files a/tests_zemu/snapshots/s-mainmenu/00010.png and b/tests_zemu/snapshots/s-mainmenu/00010.png differ diff --git a/tests_zemu/snapshots/sp-mainmenu/00004.png b/tests_zemu/snapshots/sp-mainmenu/00004.png index 3f069404..e103f6cd 100644 Binary files a/tests_zemu/snapshots/sp-mainmenu/00004.png and b/tests_zemu/snapshots/sp-mainmenu/00004.png differ diff --git a/tests_zemu/snapshots/sp-mainmenu/00010.png b/tests_zemu/snapshots/sp-mainmenu/00010.png index 3f069404..e103f6cd 100644 Binary files a/tests_zemu/snapshots/sp-mainmenu/00010.png and b/tests_zemu/snapshots/sp-mainmenu/00010.png differ diff --git a/tests_zemu/snapshots/x-mainmenu/00004.png b/tests_zemu/snapshots/x-mainmenu/00004.png index 3f069404..e103f6cd 100644 Binary files a/tests_zemu/snapshots/x-mainmenu/00004.png and b/tests_zemu/snapshots/x-mainmenu/00004.png differ diff --git a/tests_zemu/snapshots/x-mainmenu/00010.png b/tests_zemu/snapshots/x-mainmenu/00010.png index 3f069404..e103f6cd 100644 Binary files a/tests_zemu/snapshots/x-mainmenu/00010.png and b/tests_zemu/snapshots/x-mainmenu/00010.png differ