From 44c3fb951ce25e4868406c28dd89769994379948 Mon Sep 17 00:00:00 2001 From: Francois Beutin Date: Tue, 26 Sep 2023 11:44:11 +0200 Subject: [PATCH] Fix BSS handling in SWAP mode Split swap features in several files Reuse SWAP interface from SDK Add a modal on Stax startup in SWAP mode Return signing status to Exchange after a SWAP completion Remove global hdPath usage in CHECK_ADDRESS Improve security against double signing attempts --- app/src/apdu_handler.c | 14 +- app/src/common/main.c | 90 +++++-- app/src/common/tx.c | 9 + app/src/crypto.c | 30 ++- app/src/crypto.h | 2 + app/src/swap.c | 296 --------------------- app/src/swap.h | 100 ------- app/src/swap/handle_check_address.c | 55 ++++ app/src/swap/handle_get_printable_amount.c | 46 ++++ app/src/swap/handle_sign_transaction.c | 159 +++++++++++ app/src/swap/swap.h | 47 ++++ app/src/swap/swap_utils.c | 69 +++++ 12 files changed, 493 insertions(+), 424 deletions(-) delete mode 100644 app/src/swap.c delete mode 100644 app/src/swap.h create mode 100644 app/src/swap/handle_check_address.c create mode 100644 app/src/swap/handle_get_printable_amount.c create mode 100644 app/src/swap/handle_sign_transaction.c create mode 100644 app/src/swap/swap.h create mode 100644 app/src/swap/swap_utils.c diff --git a/app/src/apdu_handler.c b/app/src/apdu_handler.c index 6872e69e..4f9daeee 100644 --- a/app/src/apdu_handler.c +++ b/app/src/apdu_handler.c @@ -36,7 +36,7 @@ static bool tx_initialized = false; -void extractHDPath(uint32_t rx, uint32_t offset) { +static void extractHDPath(uint32_t rx, uint32_t offset) { tx_initialized = false; if ((rx - offset) < sizeof(uint32_t) * HDPATH_LEN_DEFAULT) { @@ -175,7 +175,6 @@ __Z_INLINE void handleSign(volatile uint32_t *flags, volatile uint32_t *tx, uint switch (key_type) { case key_ed25519: { 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); @@ -191,7 +190,6 @@ __Z_INLINE void handleSign(volatile uint32_t *flags, volatile uint32_t *tx, uint THROW(APDU_CODE_DATA_INVALID); } if (G_swap_state.called_from_swap) { - G_swap_state.should_exit = 1; app_return_sr25519(); } else { view_review_init(tx_getItem, tx_getNumItems, app_return_sr25519); @@ -329,6 +327,16 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { } FINALLY { + #ifdef HAVE_SWAP + if (G_swap_state.called_from_swap && G_swap_state.should_exit) { + // Swap checking failed, send reply now and exit, don't wait next cycle + if (sw != 0) { + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, *tx); + } + // Go back to exchange and report our status + finalize_exchange_sign_transaction(sw == 0); + } + #endif } } END_TRY; diff --git a/app/src/common/main.c b/app/src/common/main.c index 9e52617f..3385f280 100644 --- a/app/src/common/main.c +++ b/app/src/common/main.c @@ -17,9 +17,11 @@ #include "app_main.h" #include "view.h" #include "swap.h" +#include "lib_standard_app/swap_lib_calls.h" #include +// Helper to quit the application in a limited THROW context static void app_exit(void) { BEGIN_TRY_L(exit) { TRY_L(exit) { @@ -31,38 +33,84 @@ static void app_exit(void) { END_TRY_L(exit); } +// Helper to handle the different library commands +static void library_main(libargs_t *args) { + BEGIN_TRY { + TRY { + switch (args->command) { + case SIGN_TRANSACTION: { + // Backup up transaction parameters and wipe BSS to avoid collusion with + // app-exchange BSS data. + bool success = copy_transaction_parameters(args->create_transaction); + if (success) { + // BSS was wiped, we can now init these globals + G_swap_state.called_from_swap = true; + G_swap_state.should_exit = false; + +#ifdef HAVE_NBGL + // On Stax, display a modal + nbgl_useCaseSpinner("Signing"); +#endif // HAVE_NBGL + + view_init(); + app_init(); + app_main(); + } + break; + } + case CHECK_ADDRESS: + handle_check_address(args->check_address); + break; + case GET_PRINTABLE_AMOUNT: + handle_get_printable_amount(args->get_printable_amount); + break; + default: + break; + } + } + CATCH_OTHER(e) + { + } + FINALLY + { + os_lib_end(); + } + } + END_TRY; +} + __attribute__((section(".boot"))) int main(int arg0) { // exit critical section __asm volatile("cpsie i"); - view_init(); os_boot(); - if (arg0) { - libargs_s *args = (libargs_s *) arg0; + if (arg0 != 0) { + // The app has been started in library mode + libargs_t *args = (libargs_t *) arg0; if (args->id != 0x100) { + // Invalid mode ID app_exit(); - return 0; - } - - swap_handle_command(args); - if (!G_swap_state.called_from_swap) { - os_lib_end(); + } else { + library_main(args); } - } - - BEGIN_TRY - { - TRY + } else { + // The app has been launched from the dashboard + G_swap_state.called_from_swap = false; + BEGIN_TRY { - app_init(); - app_main(); + TRY + { + view_init(); + app_init(); + app_main(); + } + CATCH_OTHER(e) + {} + FINALLY + {} } - CATCH_OTHER(e) - {} - FINALLY - {} + END_TRY; } - END_TRY; } diff --git a/app/src/common/tx.c b/app/src/common/tx.c index b901faed..e733fc01 100644 --- a/app/src/common/tx.c +++ b/app/src/common/tx.c @@ -118,6 +118,15 @@ const char *tx_parse() { // If in swap mode, compare swap tx parameters with stored info. if (G_swap_state.called_from_swap) { + if (G_swap_state.should_exit == 1) { + // Safety against trying to make the app sign multiple TX + // This panic quit is a failsafe that should never trigger, as the app is supposed to + // exit after the first send when started in swap mode + os_sched_exit(-1); + } else { + // We will quit the app after this transaction, whether it succeeds or fails + G_swap_state.should_exit = 1; + } err = check_swap_conditions(&ctx_parsed_tx); CHECK_APP_CANARY() if (err != parser_ok) { diff --git a/app/src/crypto.c b/app/src/crypto.c index 123a0553..5ebc1260 100644 --- a/app/src/crypto.c +++ b/app/src/crypto.c @@ -18,6 +18,7 @@ #include "base58.h" #include "coin.h" #include "cx.h" +#include "swap.h" #include "zxmacros.h" #include "ristretto.h" #include "crypto_helper.h" @@ -28,7 +29,7 @@ uint32_t hdPath[HDPATH_LEN_DEFAULT]; -static zxerr_t crypto_extractPublicKey(key_kind_e addressKind, uint8_t *pubKey, uint16_t pubKeyLen) { +static zxerr_t crypto_extractPublicKey(key_kind_e addressKind, uint8_t *pubKey, uint16_t pubKeyLen, uint32_t *hdPath_to_use) { if (pubKey == NULL || pubKeyLen < PK_LEN_25519) { return zxerr_invalid_crypto_settings; } @@ -41,7 +42,7 @@ static zxerr_t crypto_extractPublicKey(key_kind_e addressKind, uint8_t *pubKey, // Generate keys CATCH_CXERROR(os_derive_bip32_with_seed_no_throw(HDW_NORMAL, CX_CURVE_Ed25519, - hdPath, + hdPath_to_use, HDPATH_LEN_DEFAULT, privateKeyData, NULL, @@ -215,12 +216,13 @@ zxerr_t crypto_sign_sr25519(const uint8_t *message, size_t messageLen) { } #endif -zxerr_t crypto_fillAddress(key_kind_e addressKind, uint8_t *buffer, uint16_t bufferLen, uint16_t *addrResponseLen) { +// Helper function to fill a crypto address using a given hdPath +static zxerr_t crypto_fillAddress_helper(key_kind_e addressKind, uint8_t *buffer, uint16_t bufferLen, uint16_t *addrResponseLen, uint32_t *hdPath_to_use) { if (bufferLen < PK_LEN_25519 + SS58_ADDRESS_MAX_LEN) { return zxerr_unknown; } MEMZERO(buffer, bufferLen); - CHECK_ZXERR(crypto_extractPublicKey(addressKind, buffer, bufferLen)) + CHECK_ZXERR(crypto_extractPublicKey(addressKind, buffer, bufferLen, hdPath_to_use)) size_t outLen = crypto_SS58EncodePubkey(buffer + PK_LEN_25519, bufferLen - PK_LEN_25519, @@ -233,3 +235,23 @@ zxerr_t crypto_fillAddress(key_kind_e addressKind, uint8_t *buffer, uint16_t buf *addrResponseLen = PK_LEN_25519 + outLen; return zxerr_ok; } + +// fill a crypto address using the global hdpath +zxerr_t crypto_fillAddress(key_kind_e addressKind, uint8_t *buffer, uint16_t bufferLen, uint16_t *addrResponseLen) { + return crypto_fillAddress_helper(addressKind, buffer, bufferLen, addrResponseLen, hdPath); +} + +// fill a crypto address using a locally computed hdpath +zxerr_t crypto_fillAddress_standalone(uint8_t* params, uint8_t paramsSize, key_kind_e addressKind, uint8_t *buffer, uint16_t bufferLen, uint16_t *addrResponseLen) { + uint32_t local_hdPath[HDPATH_LEN_DEFAULT]; + + 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), &local_hdPath[i])) + } + + return crypto_fillAddress_helper(addressKind, buffer, bufferLen, addrResponseLen, local_hdPath); +} diff --git a/app/src/crypto.h b/app/src/crypto.h index afe59558..73a9e22b 100644 --- a/app/src/crypto.h +++ b/app/src/crypto.h @@ -39,6 +39,8 @@ uint8_t crypto_SS58EncodePubkey(uint8_t *buffer, uint16_t buffer_len, zxerr_t crypto_fillAddress(key_kind_e addressKind, uint8_t *buffer, uint16_t bufferLen, uint16_t *addrResponseLen); +zxerr_t crypto_fillAddress_standalone(uint8_t* params, uint8_t paramsSize, key_kind_e addressKind, uint8_t *buffer, uint16_t bufferLen, uint16_t *addrResponseLen); + zxerr_t crypto_sign_ed25519(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t *message, uint16_t messageLen); #ifdef SUPPORT_SR25519 diff --git a/app/src/swap.c b/app/src/swap.c deleted file mode 100644 index 11832e6f..00000000 --- a/app/src/swap.c +++ /dev/null @@ -1,296 +0,0 @@ -/******************************************************************************* -* (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)); - - if (params->amount_length > 16) { - return; - } - - 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 > 16) || - (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 deleted file mode 100644 index eff8d5d9..00000000 --- a/app/src/swap.h +++ /dev/null @@ -1,100 +0,0 @@ -/******************************************************************************* -* (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/app/src/swap/handle_check_address.c b/app/src/swap/handle_check_address.c new file mode 100644 index 00000000..60892614 --- /dev/null +++ b/app/src/swap/handle_check_address.c @@ -0,0 +1,55 @@ +/******************************************************************************* +* (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 "zxformat.h" +#include "substrate_dispatch.h" +#include "lib_standard_app/swap_lib_calls.h" + + +void handle_check_address(check_address_parameters_t* params) { + if (params == NULL || params->address_to_check == 0) { + 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 = crypto_fillAddress_standalone((uint8_t*) params->address_parameters + 2, + (uint8_t) params->address_parameters_length - 2, + 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; + } +} diff --git a/app/src/swap/handle_get_printable_amount.c b/app/src/swap/handle_get_printable_amount.c new file mode 100644 index 00000000..3fe76b22 --- /dev/null +++ b/app/src/swap/handle_get_printable_amount.c @@ -0,0 +1,46 @@ +/******************************************************************************* +* (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" +#include "lib_standard_app/swap_lib_calls.h" + +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)); + + if (params->amount_length > 16) { + return; + } + + 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); +} diff --git a/app/src/swap/handle_sign_transaction.c b/app/src/swap/handle_sign_transaction.c new file mode 100644 index 00000000..0706d5f9 --- /dev/null +++ b/app/src/swap/handle_sign_transaction.c @@ -0,0 +1,159 @@ +/******************************************************************************* +* (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" +#include "lib_standard_app/swap_lib_calls.h" + +swap_globals_t G_swap_state; + +// Save the BSS address where we will write the return value when finished +static uint8_t* G_swap_sign_return_value_address; + +//////////////////////////////////////////////////////////////// + +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 > 16) || + (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; + + // Full reset the global variables + os_explicit_zero_BSS_segment(); + + // Keep the address at which we'll reply the signing status + G_swap_sign_return_value_address = &sign_transaction_params->result; + + // Commit the values read from exchange to the clean global space + 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) { + ZEMU_LOGF(200, "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_log("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_log("Could not parse swap tx destination address.\n"); + return err; + } + + if (strncmp(tmp_str, &(G_swap_state.destination_address[0]), sizeof(G_swap_state.destination_address)) != 0) { + ZEMU_LOGF(200, "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_log("Could not parse swap tx amount.\n"); + 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(200, "Wrong swap tx amount (%s, should be : %s).\n", tmp_str, tmp_amount); + return parser_swap_tx_wrong_amount; + } + + zemu_log("Swap parameters verified by current tx\n"); + return err; +} + +void __attribute__((noreturn)) finalize_exchange_sign_transaction(bool is_success) { + *G_swap_sign_return_value_address = is_success; + os_lib_end(); +} diff --git a/app/src/swap/swap.h b/app/src/swap/swap.h new file mode 100644 index 00000000..71cd5cfc --- /dev/null +++ b/app/src/swap/swap.h @@ -0,0 +1,47 @@ +/******************************************************************************* +* (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 "zxerror.h" +#include "parser_common.h" +#include "lib_standard_app/swap_lib_calls.h" + +typedef struct { + uint8_t amount[16]; + uint8_t amount_length; + uint64_t fees; + char destination_address[65]; + /* Is swap mode */ + uint8_t called_from_swap; + uint8_t should_exit; +} swap_globals_t; + +extern swap_globals_t G_swap_state; + +// Handler for swap features +parser_error_t check_swap_conditions(const parser_context_t *ctx); +void handle_check_address(check_address_parameters_t* params); +void handle_get_printable_amount(get_printable_amount_parameters_t* params); +bool copy_transaction_parameters(create_transaction_parameters_t* sign_transaction_params); +void __attribute__((noreturn)) finalize_exchange_sign_transaction(bool is_success); + +// Helper functions for swap handlers +zxerr_t bytesAmountToStringBalance(uint8_t *amount, uint8_t amount_len, char *out, uint8_t out_len); +zxerr_t readU64BE(uint8_t* input, uint64_t *output); +zxerr_t readU32BE(uint8_t* input, uint32_t *output); diff --git a/app/src/swap/swap_utils.c b/app/src/swap/swap_utils.c new file mode 100644 index 00000000..f45c7ecc --- /dev/null +++ b/app/src/swap/swap_utils.c @@ -0,0 +1,69 @@ +/******************************************************************************* +* (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" +#include "lib_standard_app/swap_lib_calls.h" + +//////////////////////////////////////////////////////////////// + +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; +} + +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; +}