diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 0a3c970a..72b4d6b5 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -4,17 +4,19 @@ on: [push, pull_request] jobs: job_build_nano_debug: - name: Build application for NanoS, X and S+ + name: Build application for NanoS+, X, Stax, Flex runs-on: ubuntu-latest strategy: matrix: include: - - SDK: "$NANOS_SDK" - artifact: ergo-app-debug-nanos - SDK: "$NANOX_SDK" artifact: ergo-app-debug-nanox - SDK: "$NANOSP_SDK" artifact: ergo-app-debug-nanosp + - SDK: "$STAX_SDK" + artifact: ergo-app-debug-stax + - SDK: "$FLEX_SDK" + artifact: ergo-app-debug-flex container: image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest @@ -102,6 +104,11 @@ jobs: job_scan_build: name: Clang Static Analyzer runs-on: ubuntu-latest + strategy: + matrix: + include: + - SDK: "$NANOSP_SDK" + - SDK: "$STAX_SDK" container: image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest @@ -112,6 +119,7 @@ jobs: - name: Build with Clang Static Analyzer run: | + export BOLOS_SDK=${{ matrix.SDK }} make clean scan-build --use-cc=clang -analyze-headers -enable-checker security -enable-checker unix -enable-checker valist -o scan-build --status-bugs make default @@ -128,12 +136,10 @@ jobs: strategy: matrix: include: - - model: nanos - SDK: "2.1" - # - model: nanox - # SDK: "2.0.2" - # - model: nanosp - # SDK: "1.0.4" + - model: nanox + - model: nanosp + #- model: stax + #- model: flex needs: job_build_nano_debug runs-on: ubuntu-latest @@ -161,13 +167,13 @@ jobs: curl -fsSL https://deb.nodesource.com/setup_18.x | bash - apt-get install -qy nodejs export SEED=`cat tests/seed.txt` - nohup bash -c "python /speculos/speculos.py bin/app.elf --apdu-port 9999 --api-port 5000 --display headless --model=${{ matrix.model }} --sdk=${{ matrix.SDK }} --seed \"${SEED}\"" > speculos.log 2<&1 & + nohup bash -c "python /speculos/speculos.py bin/app.elf --apdu-port 9999 --api-port 5000 --display headless --model=${{ matrix.model }} --seed \"${SEED}\"" > speculos.log 2<&1 & cd tests && npm install until `nc -w5 -z -v 127.0.0.1 9999`; do sleep 1; done; npm --model=${{matrix.model}} --port=5000 run test - name: Upload Speculos log - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: speculos-${{matrix.model}}-log path: speculos.log diff --git a/.github/workflows/lint-workflow.yml b/.github/workflows/lint-workflow.yml index e6133700..85e94ae7 100644 --- a/.github/workflows/lint-workflow.yml +++ b/.github/workflows/lint-workflow.yml @@ -12,7 +12,7 @@ jobs: uses: actions/checkout@v4 - name: Lint - uses: DoozyX/clang-format-lint-action@v0.12 + uses: DoozyX/clang-format-lint-action@v0.18.2 with: source: './src' extensions: 'h,c' diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 5e967a31..5a740d1f 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -1,25 +1,27 @@ { "env": { - "BOLOS_SDK": "~/.ledger/nanos-sdk", + "BOLOS_SDK": "~/.ledger/nanosplus-sdk", "ARM_GCC": "~/.ledger/gcc-arm-none-eabi-13.2-2023.10" }, "configurations": [ { - "name": "Nano S", + "name": "Nano S+", "includePath": [ "${workspaceFolder}/src", - "${workspaceFolder}/build/nanos/gen_src", + "${workspaceFolder}/build/stax/gen_src", "${env:ARM_GCC}/arm-none-eabi/include/*", "${env:ARM_GCC}/lib/gcc/arm-none-eabi/13.2.1/include/*", "${env:BOLOS_SDK}/include/*", "${env:BOLOS_SDK}/lib_ux/include/*", + "${env:BOLOS_SDK}/lib_nbgl/include/*", "${env:BOLOS_SDK}/lib_cxng/include/*", "${env:BOLOS_SDK}/lib_standard_app/*" ], "defines": [ - "TARGET_NANOS", + "TARGET_NANOS2", "OS_IO_SEPROXYHAL", - "HAVE_BAGL", + "HAVE_NBGL", + "HAVE_SE_TOUCH", "HAVE_ECC", "HAVE_ECC_WEIERSTRASS", "HAVE_SECP_CURVES", @@ -50,7 +52,7 @@ "PRINTF=screen_printf", "_DEFAULT_SOURCE" ], - "compilerPath": "${env:ARM_GCC}/bin/arm-none-eabi-gcc", + "compilerPath": "/usr/bin/gcc", "cStandard": "c11", "cppStandard": "c++17", "intelliSenseMode": "gcc-arm", diff --git a/CHANGELOG.md b/CHANGELOG.md index 99cb5a13..f434cb3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.0.6] - 2024-06-10 + +- Added Stax/Flex +- Removed Nano S + ## [0.0.5] - 2024-06-10 - Updated to the latest Ledger SDK diff --git a/Makefile b/Makefile index 6b780222..9b59ddc4 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ APPNAME = "Ergo" # Application version APPVERSION_M = 0 APPVERSION_N = 0 -APPVERSION_P = 5 +APPVERSION_P = 6 APPVERSION = "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)" # Application source files @@ -38,10 +38,10 @@ APP_SOURCE_PATH += src # Application icons following guidelines: # https://developers.ledger.com/docs/embedded-app/design-requirements/#device-icon -ICON_NANOS = icons/app_16px.gif ICON_NANOX = icons/app_14px.gif ICON_NANOSP = icons/app_14px.gif -#ICON_STAX = icons/app_32px.gif +ICON_STAX = icons/app_32px.gif +ICON_FLEX = icons/app_40px.gif # Application allowed derivation curves. # Possibles curves are: secp256k1, secp256r1, ed25519 and bls12381g1 diff --git a/README.md b/README.md index 50c0c652..0f3bac1b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Ledger Ergo Application -This is a Ergo application for the Ledger Nano S/X. +This is a Ergo application for the Ledger Nano S+/X/Stax/Flex. ## Prerequisite @@ -15,7 +15,7 @@ BOLOS_ENV=/opt/bolos-devenv and do the same with `BOLOS_SDK` environment variable ``` -BOLOS_SDK=/opt/bolos-sdk/nanos-secure-sdk +BOLOS_SDK=/opt/bolos-sdk/nanosplus-secure-sdk ``` ## Compilation @@ -42,7 +42,7 @@ the process outputs HTML and LaTeX documentations in `doc/html` and `doc/latex` The flow processed in [GitHub Actions](https://github.com/features/actions) is the following: - Code formatting with [clang-format](http://clang.llvm.org/docs/ClangFormat.html) -- Compilation of the application for Ledger Nano S in [ledger-app-builder](https://github.com/LedgerHQ/ledger-app-builder) +- Compilation of the application for Ledger Nano S+ in [ledger-app-builder](https://github.com/LedgerHQ/ledger-app-builder) - Unit tests of C functions with [cmocka](https://cmocka.org/) (see [unit-tests/](unit-tests/)) - End-to-end tests with [Speculos](https://github.com/LedgerHQ/speculos) emulator (see [tests/](tests/)) - Code coverage with [gcov](https://gcc.gnu.org/onlinedocs/gcc/Gcov.html)/[lcov](http://ltp.sourceforge.net/coverage/lcov.php) and upload to [codecov.io](https://about.codecov.io) diff --git a/glyphs/app_logo.gif b/glyphs/app_logo_16px.gif similarity index 100% rename from glyphs/app_logo.gif rename to glyphs/app_logo_16px.gif diff --git a/glyphs/app_logo_64px.gif b/glyphs/app_logo_64px.gif new file mode 100644 index 00000000..d7305978 Binary files /dev/null and b/glyphs/app_logo_64px.gif differ diff --git a/icons/app_32px.gif b/icons/app_32px.gif new file mode 100644 index 00000000..44dc4994 Binary files /dev/null and b/icons/app_32px.gif differ diff --git a/icons/app_40px.gif b/icons/app_40px.gif new file mode 100644 index 00000000..ff3a6227 Binary files /dev/null and b/icons/app_40px.gif differ diff --git a/ledger_app.toml b/ledger_app.toml index 7d77593a..a3689635 100644 --- a/ledger_app.toml +++ b/ledger_app.toml @@ -1,7 +1,7 @@ [app] build_directory = "./" sdk = "C" -devices = ["nanos", "nanox", "nanos+"] +devices = ["nanox", "nanos+", "stax", "flex"] [tests] unit_directory = "./unit-tests/" diff --git a/src/commands/attestinput/ainpt_ui.c b/src/commands/attestinput/ainpt_ui_bagl.c similarity index 98% rename from src/commands/attestinput/ainpt_ui.c rename to src/commands/attestinput/ainpt_ui_bagl.c index 7f99f07f..86467886 100644 --- a/src/commands/attestinput/ainpt_ui.c +++ b/src/commands/attestinput/ainpt_ui_bagl.c @@ -1,3 +1,5 @@ +#ifdef HAVE_BAGL + #include #include #include @@ -48,4 +50,6 @@ int ui_display_access_token(uint32_t app_access_token, attest_input_ctx_t* conte ui_display_screens(&screen); return 0; -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/src/commands/attestinput/ainpt_ui_nbgl.c b/src/commands/attestinput/ainpt_ui_nbgl.c new file mode 100644 index 00000000..66a0cde7 --- /dev/null +++ b/src/commands/attestinput/ainpt_ui_nbgl.c @@ -0,0 +1,57 @@ +#ifdef HAVE_NBGL + +#include +#include +#include +#include + +#include "ainpt_ui.h" +#include "ainpt_response.h" +#include "../../context.h" +#include "../../common/macros_ext.h" +#include "../../helpers/response.h" +#include "../../ui/ui_application_id.h" +#include "../../ui/ui_menu.h" +#include "../../ui/ui_main.h" +#include "../../ui/display.h" + +#define APPLICATION_ID_SUBLEN APPLICATION_ID_STR_LEN + 13 +static char sub_message[APPLICATION_ID_SUBLEN]; + +void ui_action_attest_input(bool approved) { + set_flow_response(approved); +} + +int ui_display_access_token(uint32_t app_access_token, attest_input_ctx_t* context) { + context->ui.app_token_value = app_access_token; + + if (app_access_token != 0) { + ui_application_id_screen(app_access_token, context->ui.app_token); + memset(sub_message, 0, APPLICATION_ID_SUBLEN); + snprintf(sub_message, APPLICATION_ID_SUBLEN, "Application: 0x%08x", app_access_token); + } + + nbgl_useCaseChoice(&VALIDATE_ICON, + "Confirm Attest Input", + sub_message, + "Confirm", + "Reject", + ui_action_attest_input); + bool approved = io_ui_process(); + + if (approved) { + app_set_connected_app_id(context->ui.app_token_value); + context->state = ATTEST_INPUT_STATE_APPROVED; + send_response_attested_input_session_id(context->session); + + } else { + app_set_current_command(CMD_NONE); + res_deny(); + + ui_menu_main(); + } + + return 0; +} + +#endif \ No newline at end of file diff --git a/src/commands/deriveaddress/da_ui.h b/src/commands/deriveaddress/da_ui.h index 4c039634..d6b660aa 100644 --- a/src/commands/deriveaddress/da_ui.h +++ b/src/commands/deriveaddress/da_ui.h @@ -4,6 +4,8 @@ #include // bool #include "da_context.h" #include "../../constants.h" +#include "../../context.h" +#include "../../helpers/response.h" /** * Display account on the device and ask confirmation to export. @@ -16,4 +18,9 @@ int ui_display_address(derive_address_ctx_t* ctx, uint32_t app_access_token, uint32_t* bip32_path, uint8_t bip32_path_len, - uint8_t raw_address[static P2PK_ADDRESS_LEN]); \ No newline at end of file + uint8_t raw_address[static P2PK_ADDRESS_LEN]); + +static inline int send_error(uint16_t err) { + app_set_current_command(CMD_NONE); + return res_error(err); +} \ No newline at end of file diff --git a/src/commands/deriveaddress/da_ui.c b/src/commands/deriveaddress/da_ui_bagl.c similarity index 97% rename from src/commands/deriveaddress/da_ui.c rename to src/commands/deriveaddress/da_ui_bagl.c index 8218d36c..67a2f30d 100644 --- a/src/commands/deriveaddress/da_ui.c +++ b/src/commands/deriveaddress/da_ui_bagl.c @@ -1,3 +1,5 @@ +#ifdef HAVE_BAGL + #include #include #include @@ -49,11 +51,6 @@ static NOINLINE void ui_action_derive_address(bool approved, void* context) { ui_menu_main(); } -static inline int send_error(uint16_t err) { - app_set_current_command(CMD_NONE); - return res_error(err); -} - // Display int ui_display_address(derive_address_ctx_t* ctx, bool send, @@ -117,3 +114,5 @@ int ui_display_address(derive_address_ctx_t* ctx, return 0; } + +#endif \ No newline at end of file diff --git a/src/commands/deriveaddress/da_ui_nbgl.c b/src/commands/deriveaddress/da_ui_nbgl.c new file mode 100644 index 00000000..f89fa96f --- /dev/null +++ b/src/commands/deriveaddress/da_ui_nbgl.c @@ -0,0 +1,129 @@ +#ifdef HAVE_NBGL + +#include +#include +#include +#include + +#include "da_ui.h" +#include "da_response.h" +#include "da_context.h" +#include "../../sw.h" +#include "../../context.h" +#include "../../common/macros_ext.h" +#include "../../ergo/address.h" +#include "../../helpers/response.h" +#include "../../ui/ui_bip32_path.h" +#include "../../ui/ui_application_id.h" +#include "../../ui/ui_approve_reject.h" +#include "../../ui/ui_menu.h" +#include "../../ui/ui_main.h" +#include "../../ui/display.h" + +#define PK_APPID_ADDR_SIZE \ + MAX_BIP32_STRING_LEN + APPLICATION_ID_STR_LEN + 13 + 1 + P2PK_ADDRESS_STRING_MAX_LEN + 9 + 1 +char pk_appid_addr[PK_APPID_ADDR_SIZE]; + +void ui_display_address_confirm(bool approved) { + set_flow_response(approved); +} + +int ui_display_address(derive_address_ctx_t* ctx, + bool send, + uint32_t app_access_token, + uint32_t* bip32_path, + uint8_t bip32_path_len, + uint8_t raw_address[static P2PK_ADDRESS_LEN]) { + if (!bip32_path_validate(bip32_path, + bip32_path_len, + BIP32_HARDENED(44), + BIP32_HARDENED(BIP32_ERGO_COIN), + BIP32_PATH_VALIDATE_ADDRESS_GE5)) { + return send_error(SW_BIP32_BAD_PATH); + } + + ctx->app_token_value = app_access_token; + ctx->send = send; + + memset(ctx->address, 0, MEMBER_SIZE(derive_address_ctx_t, address)); + if (!send) { + int result = base58_encode(raw_address, + P2PK_ADDRESS_LEN, + ctx->address, + MEMBER_SIZE(derive_address_ctx_t, address)); + + if (result == -1 || result >= P2PK_ADDRESS_STRING_MAX_LEN) { + return send_error(SW_ADDRESS_FORMATTING_FAILED); + } + } + + if (!ui_bip32_path_screen(bip32_path, + bip32_path_len, + ctx->bip32_path, + MEMBER_SIZE(derive_address_ctx_t, bip32_path))) { + return res_error(SW_BIP32_BAD_PATH); + } + + int bip32_str_len = strlen(ctx->bip32_path); + + memset(pk_appid_addr, 0, PK_APPID_ADDR_SIZE); + strncpy(pk_appid_addr, ctx->bip32_path, bip32_str_len); + int offset = 0; + if (app_access_token != 0) { + pk_appid_addr[bip32_str_len] = '\n'; + offset += APPLICATION_ID_STR_LEN + 13; + snprintf(*(&pk_appid_addr) + bip32_str_len + 1, + APPLICATION_ID_STR_LEN + 13, + "Application: 0x%08x", + app_access_token); + } + + if (!send) { + pk_appid_addr[bip32_str_len + offset] = '\n'; + strncpy(*(&pk_appid_addr) + bip32_str_len + offset + 1, "Address: ", 9); + strncpy(*(&pk_appid_addr) + bip32_str_len + offset + 1 + 9, + ctx->address, + MEMBER_SIZE(derive_address_ctx_t, address)); + } + + if (send) { + // Confirm Send Address + nbgl_useCaseChoice(&WHEEL_ICON, + "Address Export", + pk_appid_addr, + "Confirm", + "Cancel", + ui_display_address_confirm); + } else { + // Confirm Address + nbgl_useCaseChoice(&INFO_I_ICON, + "Confirm Address", + pk_appid_addr, + "Confirm", + "Cancel", + ui_display_address_confirm); + } + + memmove(ctx->raw_address, raw_address, P2PK_ADDRESS_LEN); + + bool approved = io_ui_process(); + + if (approved) { + app_set_connected_app_id(ctx->app_token_value); + if (ctx->send) { + send_response_address(ctx->raw_address); + } else { + app_set_current_command(CMD_NONE); + res_ok(); + } + } else { + app_set_current_command(CMD_NONE); + res_deny(); + } + + ui_menu_main(); + + return 0; +} + +#endif \ No newline at end of file diff --git a/src/commands/extpubkey/epk_ui.c b/src/commands/extpubkey/epk_ui_bagl.c similarity index 99% rename from src/commands/extpubkey/epk_ui.c rename to src/commands/extpubkey/epk_ui_bagl.c index c1e12f3b..5f5ff48b 100644 --- a/src/commands/extpubkey/epk_ui.c +++ b/src/commands/extpubkey/epk_ui_bagl.c @@ -1,3 +1,5 @@ +#ifdef HAVE_BAGL + #include #include #include @@ -85,3 +87,5 @@ int ui_display_account(extended_public_key_ctx_t* ctx, return 0; } + +#endif \ No newline at end of file diff --git a/src/commands/extpubkey/epk_ui_nbgl.c b/src/commands/extpubkey/epk_ui_nbgl.c new file mode 100644 index 00000000..38130815 --- /dev/null +++ b/src/commands/extpubkey/epk_ui_nbgl.c @@ -0,0 +1,90 @@ +#ifdef HAVE_NBGL + +#include +#include +#include +#include + +#include "epk_ui.h" +#include "epk_response.h" +#include "../../context.h" +#include "../../sw.h" +#include "../../common/bip32_ext.h" +#include "../../common/macros_ext.h" +#include "../../helpers/response.h" +#include "../../ui/ui_bip32_path.h" +#include "../../ui/ui_application_id.h" +#include "../../ui/ui_approve_reject.h" +#include "../../ui/ui_menu.h" +#include "../../ui/ui_main.h" +#include "../../ui/display.h" + +void ui_display_account_confirm(bool approved) { + set_flow_response(approved); +} + +#define PK_APPID_SIZE MAX_BIP32_STRING_LEN + APPLICATION_ID_STR_LEN + 13 + 1 +char pk_appid[PK_APPID_SIZE]; + +int ui_display_account(extended_public_key_ctx_t* ctx, + uint32_t app_access_token, + uint32_t* bip32_path, + uint8_t bip32_path_len, + uint8_t raw_pub_key[static PUBLIC_KEY_LEN], + uint8_t chain_code[static CHAIN_CODE_LEN]) { + if (!bip32_path_validate(bip32_path, + bip32_path_len, + BIP32_HARDENED(44), + BIP32_HARDENED(BIP32_ERGO_COIN), + BIP32_PATH_VALIDATE_ACCOUNT_GE3)) { + return res_error(SW_BIP32_BAD_PATH); + } + + if (!ui_bip32_path_screen(bip32_path, + bip32_path_len, + ctx->bip32_path, + MEMBER_SIZE(derive_address_ctx_t, bip32_path))) { + return res_error(SW_BIP32_BAD_PATH); + } + + int bip32_str_len = strlen(ctx->bip32_path); + + memset(pk_appid, 0, PK_APPID_SIZE); + strncpy(pk_appid, ctx->bip32_path, bip32_str_len); + if (app_access_token != 0) { + pk_appid[bip32_str_len] = '\n'; + snprintf(*(&pk_appid) + bip32_str_len + 1, + APPLICATION_ID_STR_LEN + 13, + "Application: 0x%08x", + app_access_token); + } + + ctx->app_token_value = app_access_token; + memmove(ctx->raw_public_key, raw_pub_key, PUBLIC_KEY_LEN); + memmove(ctx->chain_code, chain_code, CHAIN_CODE_LEN); + + nbgl_useCaseChoice(&WARNING_ICON, + "Ext PubKey Export", + pk_appid, + "Confirm", + "Cancel", + ui_display_account_confirm); + bool approved = io_ui_process(); + + if (approved) { + app_set_connected_app_id(ctx->app_token_value); + send_response_extended_pubkey(ctx->raw_public_key, ctx->chain_code); + explicit_bzero(ctx, sizeof(extended_public_key_ctx_t)); + } else { + explicit_bzero(ctx, sizeof(extended_public_key_ctx_t)); + res_deny(); + } + + app_set_current_command(CMD_NONE); + + ui_menu_main(); + + return 0; +} + +#endif \ No newline at end of file diff --git a/src/commands/signtx/operations/stx_op_p2pk.c b/src/commands/signtx/operations/stx_op_p2pk.c index bc858917..a2c61bd9 100644 --- a/src/commands/signtx/operations/stx_op_p2pk.c +++ b/src/commands/signtx/operations/stx_op_p2pk.c @@ -10,6 +10,8 @@ #include "../../../ergo/network_id.h" #include "../stx_ui.h" #include "../../../ui/ui_main.h" +#include "../../../ui/display.h" +#include "../../../ui/ui_menu.h" #define COMMAND_ERROR_HANDLER handler_err #include "../../../helpers/cmd_macros.h" @@ -322,17 +324,25 @@ bool stx_operation_p2pk_should_show_output_confirm_screen( // =========================== // UI - +#ifdef HAVE_BAGL static NOINLINE void ui_stx_operation_p2pk_approve_action(void *context) { sign_transaction_ui_aprove_ctx_t *ctx = (sign_transaction_ui_aprove_ctx_t *) context; ui_stx_operation_approve_reject(true, ctx); } +#endif + +#ifdef HAVE_NBGL +void p2pk_review(bool approved) { + set_flow_response(approved); +} +#endif uint16_t ui_stx_operation_p2pk_show_token_and_path(sign_transaction_operation_p2pk_ctx_t *ctx, uint32_t app_access_token, bool is_known_application, void *sign_tx_ctx) { uint8_t screen = 0; +#ifdef HAVE_BAGL const ux_flow_step_t *b32_step = ui_bip32_path_screen( ctx->bip32.path, ctx->bip32.len, @@ -345,6 +355,30 @@ uint16_t ui_stx_operation_p2pk_show_token_and_path(sign_transaction_operation_p2 return SW_BIP32_FORMATTING_FAILED; } ui_add_screen(b32_step, &screen); +#elif HAVE_NBGL + bool res = ui_bip32_path_screen( + ctx->bip32.path, + ctx->bip32.len, + ctx->ui_approve.bip32_path, + MEMBER_SIZE(sign_transaction_operation_p2pk_ui_approve_data_ctx_t, bip32_path)); + if (!res) { + return SW_BIP32_FORMATTING_FAILED; + } + // pairs_global[0].item = "P2PK Signing"; + // pairs_global[0].value = ctx->ui_approve.bip32_path; + // screen++; + nbgl_useCaseReviewStreamingStart(TYPE_TRANSACTION, + &C_app_logo_64px, + "P2PK Signing", + ctx->ui_approve.bip32_path, + p2pk_review); + bool approved = io_ui_process(); + if (!approved) { + res_deny(); + ui_menu_main(); + return SW_BIP32_FORMATTING_FAILED; + } +#endif if (!ui_stx_add_operation_approve_screens(&ctx->ui_approve.ui_approve, &screen, @@ -353,9 +387,11 @@ uint16_t ui_stx_operation_p2pk_show_token_and_path(sign_transaction_operation_p2 sign_tx_ctx)) { return SW_SCREENS_BUFFER_OVERFLOW; } +#ifdef HAVE_BAGL if (!ui_stx_display_screens(screen)) { return SW_SCREENS_BUFFER_OVERFLOW; } +#endif return SW_OK; } @@ -365,6 +401,7 @@ uint16_t ui_stx_operation_p2pk_show_output_confirm_screen( SIGN_TRANSACTION_OPERATION_P2PK_STATE_OUTPUTS_STARTED, SIGN_TRANSACTION_OPERATION_P2PK_STATE_TX_FINISHED); uint8_t screen = 0; + if (!ui_stx_add_output_screens(&ctx->transaction.ui.ui, &screen, &ctx->transaction.ui.output, @@ -372,9 +409,11 @@ uint16_t ui_stx_operation_p2pk_show_output_confirm_screen( ctx->network_id)) { return SW_SCREENS_BUFFER_OVERFLOW; } +#ifdef HAVE_BAGL if (!ui_stx_display_screens(screen)) { return SW_SCREENS_BUFFER_OVERFLOW; } +#endif return SW_OK; } @@ -431,6 +470,7 @@ uint16_t ui_stx_operation_p2pk_show_confirm_screen(sign_transaction_operation_p2 CHECK_PROPER_STATE(ctx, SIGN_TRANSACTION_OPERATION_P2PK_STATE_TX_FINISHED); ctx->state = SIGN_TRANSACTION_OPERATION_P2PK_STATE_FINALIZED; uint8_t screen = 0; + if (!ui_stx_add_transaction_screens(&ctx->ui_confirm, &screen, &ctx->amounts, @@ -440,8 +480,10 @@ uint16_t ui_stx_operation_p2pk_show_confirm_screen(sign_transaction_operation_p2 (void *) ctx)) { return SW_SCREENS_BUFFER_OVERFLOW; } +#ifdef HAVE_BAGL if (!ui_stx_display_screens(screen)) { return SW_SCREENS_BUFFER_OVERFLOW; } +#endif return SW_OK; } diff --git a/src/commands/signtx/stx_ui.c b/src/commands/signtx/stx_ui.c deleted file mode 100644 index a8e26def..00000000 --- a/src/commands/signtx/stx_ui.c +++ /dev/null @@ -1,405 +0,0 @@ -#include -#include -#include -#include -#include - -#include "stx_ui.h" -#include "stx_response.h" - -#include "../../context.h" -#include "../../common/macros_ext.h" -#include "../../helpers/response.h" -#include "../../common/safeint.h" -#include "../../ergo/address.h" -#include "../../ui/ui_application_id.h" -#include "../../ui/ui_approve_reject.h" -#include "../../ui/ui_dynamic_flow.h" -#include "../../ui/ui_menu.h" -#include "../../ui/ui_main.h" - -#ifdef TARGET_NANOS -#define ERGO_ID_UI_CHARACTERS_HALF 7 -#else -#define ERGO_ID_UI_CHARACTERS_HALF 26 -#endif - -#define STRING_ADD_STATIC_TEXT(str, slen, text) \ - strncpy(str, text, slen); \ - slen -= sizeof(text) - 1; \ - str += sizeof(text) - 1 - -static inline void id_string_remove_middle(char* str, size_t len) { - if (len <= 2 * ERGO_ID_UI_CHARACTERS_HALF + 3) return; - str[ERGO_ID_UI_CHARACTERS_HALF] = str[ERGO_ID_UI_CHARACTERS_HALF + 1] = - str[ERGO_ID_UI_CHARACTERS_HALF + 2] = '.'; - memmove(str + ERGO_ID_UI_CHARACTERS_HALF + 3, - str + len - ERGO_ID_UI_CHARACTERS_HALF, - ERGO_ID_UI_CHARACTERS_HALF); - str[2 * ERGO_ID_UI_CHARACTERS_HALF + 3] = '\0'; -} - -static inline bool format_hex_id(const uint8_t* id, size_t id_len, char* out, size_t out_len) { - int len = format_hex(id, id_len, out, out_len); - if (len <= 0) return false; - id_string_remove_middle(out, len - 1); - return true; -} - -static inline bool format_b58_id(const uint8_t* id, size_t id_len, char* out, size_t out_len) { - int len = base58_encode(id, id_len, out, out_len); - if (len <= 0) return false; - return true; -} - -static inline bool format_erg_amount(uint64_t amount, char* out, size_t out_len) { - if (!format_fpu64(out, out_len, amount, ERGO_ERG_FRACTION_DIGIT_COUNT)) { - return false; - } - size_t out_bytes = strlen(out); - if (out_len < 5 || out_bytes > out_len - 5) return false; - out_len -= out_bytes; - out += out_bytes; - STRING_ADD_STATIC_TEXT(out, out_len, " ERG"); - return true; -} - -// ----- OPERATION APPROVE / REJECT FLOW - -void ui_stx_operation_approve_reject(bool approved, sign_transaction_ui_aprove_ctx_t* ctx) { - sign_transaction_ctx_t* sign_tx = (sign_transaction_ctx_t*) ctx->sign_tx_context; - - app_set_ui_busy(false); - - if (approved) { - app_set_connected_app_id(ctx->app_token_value); - sign_tx->state = SIGN_TRANSACTION_STATE_APPROVED; - send_response_sign_transaction_session_id(sign_tx->session); - } else { - app_set_current_command(CMD_NONE); - res_deny(); - } - - ui_menu_main(); -} - -static NOINLINE void ui_stx_operation_approve_action(bool approved, void* context) { - sign_transaction_ui_aprove_ctx_t* ctx = (sign_transaction_ui_aprove_ctx_t*) context; - ui_stx_operation_approve_reject(approved, ctx); -} - -bool ui_stx_add_operation_approve_screens(sign_transaction_ui_aprove_ctx_t* ctx, - uint8_t* screen, - uint32_t app_access_token, - bool is_known_application, - sign_transaction_ctx_t* sign_tx) { - if (MAX_NUMBER_OF_SCREENS - *screen < 3) return false; - - if (!is_known_application) { - ui_add_screen(ui_application_id_screen(app_access_token, ctx->app_token), screen); - } - ctx->app_token_value = app_access_token; - ctx->sign_tx_context = sign_tx; - ctx->is_known_application = is_known_application; - - ui_approve_reject_screens(ui_stx_operation_approve_action, - ctx, - ui_next_sreen_ptr(screen), - ui_next_sreen_ptr(screen)); - - return true; -} - -// --- OUTPUT APPROVE / REJECT FLOW - -// Fist flow step with icon and text -UX_STEP_NOCB(ux_stx_display_output_confirm_step, pn, {&C_icon_warning, "Confirm Output"}); - -static inline uint16_t output_info_print_address(const sign_transaction_output_info_ctx_t* ctx, - uint8_t network_id, - char* title, - uint8_t title_len, - char* address, - uint8_t address_len) { - if (!stx_output_info_is_finished(ctx)) return SW_BAD_STATE; - switch (stx_output_info_type(ctx)) { - case SIGN_TRANSACTION_OUTPUT_INFO_TYPE_BIP32: { - strncpy(title, "Change", title_len); - if (!bip32_path_format(ctx->bip32_path.path, - ctx->bip32_path.len, - address, - address_len)) { - return SW_BIP32_FORMATTING_FAILED; - } - break; - } - case SIGN_TRANSACTION_OUTPUT_INFO_TYPE_ADDRESS: { - uint8_t raw_address[P2PK_ADDRESS_LEN]; - strncpy(title, "Address", title_len); - if (!ergo_address_from_compressed_pubkey(network_id, ctx->public_key, raw_address)) { - return SW_ADDRESS_GENERATION_FAILED; - } - if (!format_b58_id(raw_address, P2PK_ADDRESS_LEN, address, address_len)) { - return SW_ADDRESS_FORMATTING_FAILED; - } - break; - } - case SIGN_TRANSACTION_OUTPUT_INFO_TYPE_SCRIPT: - case SIGN_TRANSACTION_OUTPUT_INFO_TYPE_SCRIPT_HASH: { - strncpy(title, "Script Hash", title_len); - uint8_t raw_address[P2SH_ADDRESS_LEN]; - if (!ergo_address_from_script_hash(network_id, ctx->tree_hash, raw_address)) { - return SW_ADDRESS_GENERATION_FAILED; - } - if (!format_b58_id(raw_address, P2SH_ADDRESS_LEN, address, address_len)) { - return SW_ADDRESS_FORMATTING_FAILED; - } - break; - } - case SIGN_TRANSACTION_OUTPUT_INFO_TYPE_MINERS_FEE: { - strncpy(title, "Fee", title_len); - strncpy(address, "Miners Fee", address_len); - break; - } - default: - return SW_BAD_STATE; - } - return SW_OK; -} - -static NOINLINE uint16_t ui_stx_display_output_state(uint8_t screen, - char* title, - char* text, - void* context) { - sign_transaction_ui_output_confirm_ctx_t* ctx = - (sign_transaction_ui_output_confirm_ctx_t*) context; - uint8_t title_len = MEMBER_SIZE(sign_transaction_ui_output_confirm_ctx_t, title); - uint8_t text_len = MEMBER_SIZE(sign_transaction_ui_output_confirm_ctx_t, text); - memset(title, 0, title_len); - memset(text, 0, text_len); - - switch (screen) { - case 0: // Output Address Info - return output_info_print_address(ctx->output, - ctx->network_id, - title, - title_len, - text, - text_len); - case 1: { // Output Value - strncpy(title, "Output Value", title_len); - if (!format_erg_amount(ctx->output->value, text, text_len)) { - return SW_BUFFER_ERROR; - } - break; - } - default: { // Tokens - screen -= 2; // Decrease index for info screens - uint8_t token_idx = stx_output_info_used_token_index(ctx->output, screen / 2); - if (!IS_ELEMENT_FOUND(token_idx)) { // error. bad index state - return SW_BAD_TOKEN_INDEX; - } - if (screen % 2 == 0) { // Token ID - snprintf(title, title_len, "Token [%d]", (int) (screen / 2) + 1); - if (!format_hex_id(ctx->output->tokens_table->tokens[token_idx], - ERGO_ID_LEN, - text, - text_len)) { - return SW_ADDRESS_FORMATTING_FAILED; - } - } else { // Token Value - snprintf(title, title_len, "Token [%d] Value", (int) (screen / 2) + 1); - format_u64(text, text_len, ctx->output->tokens[token_idx]); - } - break; - } - } - return SW_OK; -} - -static NOINLINE void ui_stx_operation_output_confirm_action(bool approved, void* context) { - sign_transaction_ui_output_confirm_ctx_t* ctx = - (sign_transaction_ui_output_confirm_ctx_t*) context; - app_set_ui_busy(false); - - explicit_bzero(ctx->last_approved_change, sizeof(sign_transaction_bip32_path_t)); - - if (approved) { - // store last approved change address - if (stx_output_info_type(ctx->output) == SIGN_TRANSACTION_OUTPUT_INFO_TYPE_BIP32) { - memmove(ctx->last_approved_change, - &ctx->output->bip32_path, - sizeof(sign_transaction_bip32_path_t)); - } - res_ok(); - } else { - app_set_current_command(CMD_NONE); - res_deny(); - } - - ui_menu_main(); -} - -bool ui_stx_add_output_screens(sign_transaction_ui_output_confirm_ctx_t* ctx, - uint8_t* screen, - const sign_transaction_output_info_ctx_t* output, - sign_transaction_bip32_path_t* last_approved_change, - uint8_t network_id) { - if (MAX_NUMBER_OF_SCREENS - *screen < 6) return false; - - memset(ctx, 0, sizeof(sign_transaction_ui_output_confirm_ctx_t)); - memset(last_approved_change, 0, sizeof(sign_transaction_bip32_path_t)); - - ui_add_screen(&ux_stx_display_output_confirm_step, screen); - - uint8_t info_screen_count = 1; // Address screen - if (stx_output_info_type(output) != SIGN_TRANSACTION_OUTPUT_INFO_TYPE_BIP32) { - uint8_t tokens_count = stx_output_info_used_tokens_count(output); - info_screen_count += 1 + (2 * tokens_count); // value screen + tokens (2 for each) - } - - if (!ui_add_dynamic_flow_screens(screen, - info_screen_count, - ctx->title, - ctx->text, - &ui_stx_display_output_state, - (void*) ctx)) - return false; - - if (MAX_NUMBER_OF_SCREENS - *screen < 2) return false; - - ui_approve_reject_screens(ui_stx_operation_output_confirm_action, - (void*) ctx, - ui_next_sreen_ptr(screen), - ui_next_sreen_ptr(screen)); - - ctx->network_id = network_id; - ctx->output = output; - ctx->last_approved_change = last_approved_change; - - return true; -} - -// --- TX ACCEPT / REJECT FLOW - -// Fist flow step with icon and text -UX_STEP_NOCB(ux_stx_display_sign_confirm_step, pn, {&C_icon_warning, "Approve Signing"}); - -// Callback for TX UI rendering -static NOINLINE uint16_t ui_stx_display_tx_state(uint8_t screen, - char* title, - char* text, - void* context) { - sign_transaction_ui_sign_confirm_ctx_t* ctx = (sign_transaction_ui_sign_confirm_ctx_t*) context; - uint8_t title_len = MEMBER_SIZE(sign_transaction_ui_sign_confirm_ctx_t, title); - uint8_t text_len = MEMBER_SIZE(sign_transaction_ui_sign_confirm_ctx_t, text); - memset(title, 0, title_len); - memset(text, 0, text_len); - - if (screen < ctx->op_screen_count) { // Showing operation screen - return ctx->op_screen_cb(screen, title, title_len, text, text_len, ctx->op_cb_context); - } - screen -= ctx->op_screen_count; - switch (screen) { - case 0: { // TX Value - strncpy(title, "Transaction Amount", title_len); - if (!format_erg_amount(ctx->amounts->value, text, text_len)) { - return SW_BUFFER_ERROR; - } - break; - } - case 1: { // TX Fee - strncpy(title, "Transaction Fee", title_len); - if (!format_erg_amount(ctx->amounts->fee, text, text_len)) { - return SW_BUFFER_ERROR; - } - break; - } - default: { // Tokens - screen -= 2; // Decrease index for info screens - uint8_t token_idx = stx_amounts_non_zero_token_index(ctx->amounts, screen / 2); - if (!IS_ELEMENT_FOUND(token_idx)) { // error. bad index state - return SW_BAD_TOKEN_INDEX; - } - if (screen % 2 == 0) { // Token ID - snprintf(title, title_len, "Token [%d]", (int) (screen / 2) + 1); - if (!format_hex_id(ctx->amounts->tokens_table.tokens[token_idx], - ERGO_ID_LEN, - text, - text_len)) { - return SW_ADDRESS_FORMATTING_FAILED; - } - } else { // Token Value - snprintf(title, title_len, "Token [%d] Value", (int) (screen / 2) + 1); - int64_t value = ctx->amounts->tokens[token_idx]; - if (value < 0) { // output > inputs - STRING_ADD_STATIC_TEXT(text, text_len, "Minting: "); - format_u64(text, text_len, -value); - } else { // inputs > outputs - STRING_ADD_STATIC_TEXT(text, text_len, "Burning: "); - format_u64(text, text_len, value); - } - } - break; - } - } - return SW_OK; -} - -// TX approve/reject callback -static NOINLINE void ui_stx_operation_execute_action(bool approved, void* context) { - app_set_ui_busy(false); - - sign_transaction_ui_sign_confirm_ctx_t* ctx = (sign_transaction_ui_sign_confirm_ctx_t*) context; - if (approved) { - ctx->op_response_cb(ctx->op_cb_context); - } else { - res_deny(); - } - - app_set_current_command(CMD_NONE); - ui_menu_main(); -} - -bool ui_stx_add_transaction_screens(sign_transaction_ui_sign_confirm_ctx_t* ctx, - uint8_t* screen, - const sign_transaction_amounts_ctx_t* amounts, - uint8_t op_screen_count, - ui_sign_transaction_operation_show_screen_cb screen_cb, - ui_sign_transaction_operation_send_response_cb response_cb, - void* cb_context) { - if (MAX_NUMBER_OF_SCREENS - *screen < 6) return false; - - memset(ctx, 0, sizeof(sign_transaction_ui_sign_confirm_ctx_t)); - - uint8_t tokens_count = stx_amounts_non_zero_tokens_count(amounts); - - ui_add_screen(&ux_stx_display_sign_confirm_step, screen); - - if (!ui_add_dynamic_flow_screens(screen, - op_screen_count + 2 + (2 * tokens_count), - ctx->title, - ctx->text, - &ui_stx_display_tx_state, - (void*) ctx)) - return false; - - if (MAX_NUMBER_OF_SCREENS - *screen < 2) return false; - - ui_approve_reject_screens(ui_stx_operation_execute_action, - ctx, - ui_next_sreen_ptr(screen), - ui_next_sreen_ptr(screen)); - - ctx->op_screen_count = op_screen_count; - ctx->op_screen_cb = screen_cb; - ctx->op_response_cb = response_cb; - ctx->op_cb_context = cb_context; - ctx->amounts = amounts; - - return true; -} - -bool ui_stx_display_screens(uint8_t screen_count) { - return ui_display_screens(&screen_count); -} diff --git a/src/commands/signtx/stx_ui.h b/src/commands/signtx/stx_ui.h index 6b632a7f..4f60e220 100644 --- a/src/commands/signtx/stx_ui.h +++ b/src/commands/signtx/stx_ui.h @@ -46,6 +46,7 @@ bool ui_stx_add_transaction_screens(sign_transaction_ui_sign_confirm_ctx_t* ctx, ui_sign_transaction_operation_send_response_cb response_cb, void* cb_context); +#ifdef HAVE_BAGL /** * Finalizes screen flow and pushes it to the screen. * @@ -53,6 +54,7 @@ bool ui_stx_add_transaction_screens(sign_transaction_ui_sign_confirm_ctx_t* ctx, * */ bool ui_stx_display_screens(uint8_t screen_count); +#endif /** * Approve or reject operation programmatically. diff --git a/src/commands/signtx/stx_ui_bagl.c b/src/commands/signtx/stx_ui_bagl.c new file mode 100644 index 00000000..07c67c18 --- /dev/null +++ b/src/commands/signtx/stx_ui_bagl.c @@ -0,0 +1,201 @@ +#ifdef HAVE_BAGL + +#include +#include +#include +#include +#include + +#include "stx_ui.h" +#include "stx_ui_common.h" +#include "stx_response.h" + +#include "../../context.h" +#include "../../common/macros_ext.h" +#include "../../helpers/response.h" +#include "../../common/safeint.h" +#include "../../ergo/address.h" +#include "../../ui/ui_application_id.h" +#include "../../ui/ui_approve_reject.h" +#include "../../ui/ui_dynamic_flow.h" +#include "../../ui/ui_menu.h" +#include "../../ui/ui_main.h" + +// ----- OPERATION APPROVE / REJECT FLOW + +void ui_stx_operation_approve_reject(bool approved, sign_transaction_ui_aprove_ctx_t* ctx) { + sign_transaction_ctx_t* sign_tx = (sign_transaction_ctx_t*) ctx->sign_tx_context; + + app_set_ui_busy(false); + + if (approved) { + app_set_connected_app_id(ctx->app_token_value); + sign_tx->state = SIGN_TRANSACTION_STATE_APPROVED; + send_response_sign_transaction_session_id(sign_tx->session); + } else { + app_set_current_command(CMD_NONE); + res_deny(); + } + + ui_menu_main(); +} + +static NOINLINE void ui_stx_operation_approve_action(bool approved, void* context) { + sign_transaction_ui_aprove_ctx_t* ctx = (sign_transaction_ui_aprove_ctx_t*) context; + ui_stx_operation_approve_reject(approved, ctx); +} + +bool ui_stx_add_operation_approve_screens(sign_transaction_ui_aprove_ctx_t* ctx, + uint8_t* screen, + uint32_t app_access_token, + bool is_known_application, + sign_transaction_ctx_t* sign_tx) { + if (MAX_NUMBER_OF_SCREENS - *screen < 3) return false; + + if (!is_known_application && app_access_token != 0) { + ui_add_screen(ui_application_id_screen(app_access_token, ctx->app_token), screen); + } + ctx->app_token_value = app_access_token; + ctx->sign_tx_context = sign_tx; + ctx->is_known_application = is_known_application; + + ui_approve_reject_screens(ui_stx_operation_approve_action, + ctx, + ui_next_sreen_ptr(screen), + ui_next_sreen_ptr(screen)); + + return true; +} + +// --- OUTPUT APPROVE / REJECT FLOW + +// Fist flow step with icon and text +UX_STEP_NOCB(ux_stx_display_output_confirm_step, pn, {&C_icon_warning, "Confirm Output"}); + +static NOINLINE void ui_stx_operation_output_confirm_action(bool approved, void* context) { + sign_transaction_ui_output_confirm_ctx_t* ctx = + (sign_transaction_ui_output_confirm_ctx_t*) context; + app_set_ui_busy(false); + + explicit_bzero(ctx->last_approved_change, sizeof(sign_transaction_bip32_path_t)); + + if (approved) { + // store last approved change address + if (stx_output_info_type(ctx->output) == SIGN_TRANSACTION_OUTPUT_INFO_TYPE_BIP32) { + memmove(ctx->last_approved_change, + &ctx->output->bip32_path, + sizeof(sign_transaction_bip32_path_t)); + } + res_ok(); + } else { + app_set_current_command(CMD_NONE); + res_deny(); + } + + ui_menu_main(); +} + +bool ui_stx_add_output_screens(sign_transaction_ui_output_confirm_ctx_t* ctx, + uint8_t* screen, + const sign_transaction_output_info_ctx_t* output, + sign_transaction_bip32_path_t* last_approved_change, + uint8_t network_id) { + if (MAX_NUMBER_OF_SCREENS - *screen < 6) return false; + + memset(ctx, 0, sizeof(sign_transaction_ui_output_confirm_ctx_t)); + memset(last_approved_change, 0, sizeof(sign_transaction_bip32_path_t)); + + ui_add_screen(&ux_stx_display_output_confirm_step, screen); + + uint8_t info_screen_count = 1; // Address screen + if (stx_output_info_type(output) != SIGN_TRANSACTION_OUTPUT_INFO_TYPE_BIP32) { + uint8_t tokens_count = stx_output_info_used_tokens_count(output); + info_screen_count += 1 + (2 * tokens_count); // value screen + tokens (2 for each) + } + + if (!ui_add_dynamic_flow_screens(screen, + info_screen_count, + ctx->title, + ctx->text, + &ui_stx_display_output_state, + (void*) ctx)) + return false; + + if (MAX_NUMBER_OF_SCREENS - *screen < 2) return false; + + ui_approve_reject_screens(ui_stx_operation_output_confirm_action, + (void*) ctx, + ui_next_sreen_ptr(screen), + ui_next_sreen_ptr(screen)); + + ctx->network_id = network_id; + ctx->output = output; + ctx->last_approved_change = last_approved_change; + + return true; +} + +// --- TX ACCEPT / REJECT FLOW + +// Fist flow step with icon and text +UX_STEP_NOCB(ux_stx_display_sign_confirm_step, pn, {&C_icon_warning, "Approve Signing"}); + +// TX approve/reject callback +static NOINLINE void ui_stx_operation_execute_action(bool approved, void* context) { + app_set_ui_busy(false); + + sign_transaction_ui_sign_confirm_ctx_t* ctx = (sign_transaction_ui_sign_confirm_ctx_t*) context; + if (approved) { + ctx->op_response_cb(ctx->op_cb_context); + } else { + res_deny(); + } + + app_set_current_command(CMD_NONE); + ui_menu_main(); +} + +bool ui_stx_add_transaction_screens(sign_transaction_ui_sign_confirm_ctx_t* ctx, + uint8_t* screen, + const sign_transaction_amounts_ctx_t* amounts, + uint8_t op_screen_count, + ui_sign_transaction_operation_show_screen_cb screen_cb, + ui_sign_transaction_operation_send_response_cb response_cb, + void* cb_context) { + if (MAX_NUMBER_OF_SCREENS - *screen < 6) return false; + + memset(ctx, 0, sizeof(sign_transaction_ui_sign_confirm_ctx_t)); + + uint8_t tokens_count = stx_amounts_non_zero_tokens_count(amounts); + + ui_add_screen(&ux_stx_display_sign_confirm_step, screen); + + if (!ui_add_dynamic_flow_screens(screen, + op_screen_count + 2 + (2 * tokens_count), + ctx->title, + ctx->text, + &ui_stx_display_tx_state, + (void*) ctx)) + return false; + + if (MAX_NUMBER_OF_SCREENS - *screen < 2) return false; + + ui_approve_reject_screens(ui_stx_operation_execute_action, + ctx, + ui_next_sreen_ptr(screen), + ui_next_sreen_ptr(screen)); + + ctx->op_screen_count = op_screen_count; + ctx->op_screen_cb = screen_cb; + ctx->op_response_cb = response_cb; + ctx->op_cb_context = cb_context; + ctx->amounts = amounts; + + return true; +} + +bool ui_stx_display_screens(uint8_t screen_count) { + return ui_display_screens(&screen_count); +} + +#endif \ No newline at end of file diff --git a/src/commands/signtx/stx_ui_common.c b/src/commands/signtx/stx_ui_common.c new file mode 100644 index 00000000..8cdfb017 --- /dev/null +++ b/src/commands/signtx/stx_ui_common.c @@ -0,0 +1,113 @@ +#include +#include +#include +#include + +#include "stx_ui_common.h" + +#include "../../ergo/address.h" + +uint16_t ui_stx_display_output_state(uint8_t screen, char* title, char* text, void* context) { + sign_transaction_ui_output_confirm_ctx_t* ctx = + (sign_transaction_ui_output_confirm_ctx_t*) context; + uint8_t title_len = MEMBER_SIZE(sign_transaction_ui_output_confirm_ctx_t, title); + uint8_t text_len = MEMBER_SIZE(sign_transaction_ui_output_confirm_ctx_t, text); + memset(title, 0, title_len); + memset(text, 0, text_len); + + switch (screen) { + case 0: // Output Address Info + return output_info_print_address(ctx->output, + ctx->network_id, + title, + title_len, + text, + text_len); + case 1: { // Output Value + strncpy(title, "Output Value", title_len); + if (!format_erg_amount(ctx->output->value, text, text_len)) { + return SW_BUFFER_ERROR; + } + break; + } + default: { // Tokens + screen -= 2; // Decrease index for info screens + uint8_t token_idx = stx_output_info_used_token_index(ctx->output, screen / 2); + if (!IS_ELEMENT_FOUND(token_idx)) { // error. bad index state + return SW_BAD_TOKEN_INDEX; + } + if (screen % 2 == 0) { // Token ID + snprintf(title, title_len, "Token [%d]", (int) (screen / 2) + 1); + if (!format_hex_id(ctx->output->tokens_table->tokens[token_idx], + ERGO_ID_LEN, + text, + text_len)) { + return SW_ADDRESS_FORMATTING_FAILED; + } + } else { // Token Value + snprintf(title, title_len, "Token [%d] Value", (int) (screen / 2) + 1); + format_u64(text, text_len, ctx->output->tokens[token_idx]); + } + break; + } + } + return SW_OK; +} + +// Callback for TX UI rendering +uint16_t ui_stx_display_tx_state(uint8_t screen, char* title, char* text, void* context) { + sign_transaction_ui_sign_confirm_ctx_t* ctx = (sign_transaction_ui_sign_confirm_ctx_t*) context; + uint8_t title_len = MEMBER_SIZE(sign_transaction_ui_sign_confirm_ctx_t, title); + uint8_t text_len = MEMBER_SIZE(sign_transaction_ui_sign_confirm_ctx_t, text); + memset(title, 0, title_len); + memset(text, 0, text_len); + + if (screen < ctx->op_screen_count) { // Showing operation screen + return ctx->op_screen_cb(screen, title, title_len, text, text_len, ctx->op_cb_context); + } + screen -= ctx->op_screen_count; + switch (screen) { + case 0: { // TX Value + strncpy(title, "Transaction Amount", title_len); + if (!format_erg_amount(ctx->amounts->value, text, text_len)) { + return SW_BUFFER_ERROR; + } + break; + } + case 1: { // TX Fee + strncpy(title, "Transaction Fee", title_len); + if (!format_erg_amount(ctx->amounts->fee, text, text_len)) { + return SW_BUFFER_ERROR; + } + break; + } + default: { // Tokens + screen -= 2; // Decrease index for info screens + uint8_t token_idx = stx_amounts_non_zero_token_index(ctx->amounts, screen / 2); + if (!IS_ELEMENT_FOUND(token_idx)) { // error. bad index state + return SW_BAD_TOKEN_INDEX; + } + if (screen % 2 == 0) { // Token ID + snprintf(title, title_len, "Token [%d]", (int) (screen / 2) + 1); + if (!format_hex_id(ctx->amounts->tokens_table.tokens[token_idx], + ERGO_ID_LEN, + text, + text_len)) { + return SW_ADDRESS_FORMATTING_FAILED; + } + } else { // Token Value + snprintf(title, title_len, "Token [%d] Value", (int) (screen / 2) + 1); + int64_t value = ctx->amounts->tokens[token_idx]; + if (value < 0) { // output > inputs + STRING_ADD_STATIC_TEXT(text, text_len, "Minting: "); + format_u64(text, text_len, -value); + } else { // inputs > outputs + STRING_ADD_STATIC_TEXT(text, text_len, "Burning: "); + format_u64(text, text_len, value); + } + } + break; + } + } + return SW_OK; +} \ No newline at end of file diff --git a/src/commands/signtx/stx_ui_common.h b/src/commands/signtx/stx_ui_common.h new file mode 100644 index 00000000..847063ed --- /dev/null +++ b/src/commands/signtx/stx_ui_common.h @@ -0,0 +1,108 @@ +#pragma once + +#include +#include +#include +#include + +#include "../../ergo/address.h" + +#include "stx_context.h" + +#define ERGO_ID_UI_CHARACTERS_HALF 26 +#define STRING_ADD_STATIC_TEXT(str, slen, text) \ + strncpy(str, text, slen); \ + slen -= sizeof(text) - 1; \ + str += sizeof(text) - 1 + +static inline void id_string_remove_middle(char* str, size_t len) { + if (len <= 2 * ERGO_ID_UI_CHARACTERS_HALF + 3) return; + str[ERGO_ID_UI_CHARACTERS_HALF] = str[ERGO_ID_UI_CHARACTERS_HALF + 1] = + str[ERGO_ID_UI_CHARACTERS_HALF + 2] = '.'; + memmove(str + ERGO_ID_UI_CHARACTERS_HALF + 3, + str + len - ERGO_ID_UI_CHARACTERS_HALF, + ERGO_ID_UI_CHARACTERS_HALF); + str[2 * ERGO_ID_UI_CHARACTERS_HALF + 3] = '\0'; +} + +static inline bool format_hex_id(const uint8_t* id, size_t id_len, char* out, size_t out_len) { + int len = format_hex(id, id_len, out, out_len); + if (len <= 0) return false; + id_string_remove_middle(out, len - 1); + return true; +} + +static inline bool format_b58_id(const uint8_t* id, size_t id_len, char* out, size_t out_len) { + int len = base58_encode(id, id_len, out, out_len); + if (len <= 0) return false; + return true; +} + +static inline bool format_erg_amount(uint64_t amount, char* out, size_t out_len) { + if (!format_fpu64(out, out_len, amount, ERGO_ERG_FRACTION_DIGIT_COUNT)) { + return false; + } + size_t out_bytes = strlen(out); + if (out_len < 5 || out_bytes > out_len - 5) return false; + out_len -= out_bytes; + out += out_bytes; + STRING_ADD_STATIC_TEXT(out, out_len, " ERG"); + return true; +} + +static inline uint16_t output_info_print_address(const sign_transaction_output_info_ctx_t* ctx, + uint8_t network_id, + char* title, + uint8_t title_len, + char* address, + uint8_t address_len) { + if (!stx_output_info_is_finished(ctx)) return SW_BAD_STATE; + switch (stx_output_info_type(ctx)) { + case SIGN_TRANSACTION_OUTPUT_INFO_TYPE_BIP32: { + strncpy(title, "Change", title_len); + if (!bip32_path_format(ctx->bip32_path.path, + ctx->bip32_path.len, + address, + address_len)) { + return SW_BIP32_FORMATTING_FAILED; + } + break; + } + case SIGN_TRANSACTION_OUTPUT_INFO_TYPE_ADDRESS: { + uint8_t raw_address[P2PK_ADDRESS_LEN]; + strncpy(title, "Address", title_len); + if (!ergo_address_from_compressed_pubkey(network_id, ctx->public_key, raw_address)) { + return SW_ADDRESS_GENERATION_FAILED; + } + if (!format_b58_id(raw_address, P2PK_ADDRESS_LEN, address, address_len)) { + return SW_ADDRESS_FORMATTING_FAILED; + } + break; + } + case SIGN_TRANSACTION_OUTPUT_INFO_TYPE_SCRIPT: + case SIGN_TRANSACTION_OUTPUT_INFO_TYPE_SCRIPT_HASH: { + strncpy(title, "Script Hash", title_len); + uint8_t raw_address[P2SH_ADDRESS_LEN]; + if (!ergo_address_from_script_hash(network_id, ctx->tree_hash, raw_address)) { + return SW_ADDRESS_GENERATION_FAILED; + } + if (!format_b58_id(raw_address, P2SH_ADDRESS_LEN, address, address_len)) { + return SW_ADDRESS_FORMATTING_FAILED; + } + break; + } + case SIGN_TRANSACTION_OUTPUT_INFO_TYPE_MINERS_FEE: { + strncpy(title, "Fee", title_len); + strncpy(address, "Miners Fee", address_len); + break; + } + default: + return SW_BAD_STATE; + } + return SW_OK; +} + +uint16_t ui_stx_display_output_state(uint8_t screen, char* title, char* text, void* context); + +// Callback for TX UI rendering +uint16_t ui_stx_display_tx_state(uint8_t screen, char* title, char* text, void* context); \ No newline at end of file diff --git a/src/commands/signtx/stx_ui_nbgl.c b/src/commands/signtx/stx_ui_nbgl.c new file mode 100644 index 00000000..b0f454c0 --- /dev/null +++ b/src/commands/signtx/stx_ui_nbgl.c @@ -0,0 +1,235 @@ +#ifdef HAVE_NBGL + +#include +#include +#include +#include +#include +#include + +#include "stx_ui.h" +#include "stx_ui_common.h" +#include "stx_response.h" + +#include "../../context.h" +#include "../../common/bip32_ext.h" +#include "../../common/macros_ext.h" +#include "../../common/safeint.h" +#include "../../helpers/response.h" +#include "../../sw.h" +#include "../../ergo/address.h" +#include "../../ui/ui_bip32_path.h" +#include "../../ui/ui_application_id.h" +#include "../../ui/ui_menu.h" +#include "../../ui/ui_main.h" +#include "../../ui/display.h" + +static NOINLINE void ui_stx_operation_approve_action(bool approved) { + set_flow_response(approved); +} + +static NOINLINE void quit_callback(void) { + set_flow_response(true); +} + +bool ui_stx_add_operation_approve_screens(sign_transaction_ui_aprove_ctx_t* ctx, + uint8_t* screen, + uint32_t app_access_token, + bool is_known_application, + sign_transaction_ctx_t* sign_tx) { + if (MAX_NUMBER_OF_SCREENS - *screen < 3) return false; + + int n_pairs = *screen; + + if (!is_known_application && app_access_token != 0) { + pairs_global[n_pairs++] = ui_application_id_screen(app_access_token, ctx->app_token); + } + + ctx->app_token_value = app_access_token; + ctx->sign_tx_context = sign_tx; + ctx->is_known_application = is_known_application; + + pair_list.nbMaxLinesForValue = 0; + pair_list.nbPairs = n_pairs; + pair_list.pairs = pairs_global; + + bool approved = true; + if (n_pairs > 0) { + nbgl_useCaseReviewStreamingContinue(&pair_list, ui_stx_operation_approve_action); + approved = io_ui_process(); + } + + ui_stx_operation_approve_reject(approved, ctx); + + return true; +} + +static nbgl_layoutTagValue_t pair; +static sign_transaction_ui_output_confirm_ctx_t* output_screen_ctx = NULL; + +static nbgl_layoutTagValue_t* getOutputPair(uint8_t index) { + if ((index + 1) % 3 == 0) { + pair.item = ""; + pair.value = ""; + } else { + pair.item = pair_mem_title[index]; + pair.value = pair_mem_text[index]; + + ui_stx_display_output_state(index - index / 3, + pair_mem_title[index], + pair_mem_text[index], + (void*) output_screen_ctx); + } + return &pair; +} + +bool ui_stx_add_output_screens(sign_transaction_ui_output_confirm_ctx_t* ctx, + uint8_t* screen, + const sign_transaction_output_info_ctx_t* output, + sign_transaction_bip32_path_t* last_approved_change, + uint8_t network_id) { + if (MAX_NUMBER_OF_SCREENS - *screen < 6) return false; + + memset(ctx, 0, sizeof(sign_transaction_ui_output_confirm_ctx_t)); + memset(last_approved_change, 0, sizeof(sign_transaction_bip32_path_t)); + + uint8_t info_screen_count = 1; // Address screen + if (stx_output_info_type(output) != SIGN_TRANSACTION_OUTPUT_INFO_TYPE_BIP32) { + uint8_t tokens_count = stx_output_info_used_tokens_count(output); + info_screen_count += 1 + (2 * tokens_count); // value screen + tokens (2 for each) + } + + if (MAX_NUMBER_OF_SCREENS - *screen < 2) return false; + + ctx->network_id = network_id; + ctx->output = output; + ctx->last_approved_change = last_approved_change; + + pair_list.nbMaxLinesForValue = 0; + pair_list.pairs = NULL; + pair_list.nbPairs = info_screen_count + info_screen_count / 2; + pair_list.callback = getOutputPair; + pair_list.startIndex = 0; + + output_screen_ctx = ctx; + + nbgl_useCaseReviewStreamingContinue(&pair_list, ui_stx_operation_approve_action); + + bool approved = io_ui_process(); + app_set_ui_busy(false); + output_screen_ctx = NULL; + + if (!approved) { + app_set_current_command(CMD_NONE); + res_deny(); + ui_menu_main(); + return true; + } + + explicit_bzero(ctx->last_approved_change, sizeof(sign_transaction_bip32_path_t)); + + if (approved) { + // store last approved change address + if (stx_output_info_type(ctx->output) == SIGN_TRANSACTION_OUTPUT_INFO_TYPE_BIP32) { + memmove(ctx->last_approved_change, + &ctx->output->bip32_path, + sizeof(sign_transaction_bip32_path_t)); + } + res_ok(); + } + + return true; +} + +static sign_transaction_ui_sign_confirm_ctx_t* sign_confirm_screen_ctx = NULL; + +static nbgl_layoutTagValue_t* getSignConfirmPair(uint8_t index) { + pair.item = pair_mem_title[index]; + pair.value = pair_mem_text[index]; + + ui_stx_display_tx_state(index, + pair_mem_title[index], + pair_mem_text[index], + (void*) sign_confirm_screen_ctx); + return &pair; +} + +bool ui_stx_add_transaction_screens(sign_transaction_ui_sign_confirm_ctx_t* ctx, + uint8_t* screen, + const sign_transaction_amounts_ctx_t* amounts, + uint8_t op_screen_count, + ui_sign_transaction_operation_show_screen_cb screen_cb, + ui_sign_transaction_operation_send_response_cb response_cb, + void* cb_context) { + if (MAX_NUMBER_OF_SCREENS - *screen < 6) return false; + + memset(ctx, 0, sizeof(sign_transaction_ui_sign_confirm_ctx_t)); + + uint8_t tokens_count = stx_amounts_non_zero_tokens_count(amounts); + + ctx->op_screen_count = op_screen_count; + ctx->op_screen_cb = screen_cb; + ctx->op_response_cb = response_cb; + ctx->op_cb_context = cb_context; + ctx->amounts = amounts; + + pair_list.nbMaxLinesForValue = 0; + pair_list.pairs = NULL; + pair_list.nbPairs = op_screen_count + 2 + (2 * tokens_count); + pair_list.callback = getSignConfirmPair; + pair_list.startIndex = 0; + + sign_confirm_screen_ctx = ctx; + + nbgl_useCaseReviewStreamingContinue(&pair_list, ui_stx_operation_approve_action); + + bool approved = io_ui_process(); + app_set_ui_busy(false); + sign_confirm_screen_ctx = NULL; + + if (!approved) { + res_deny(); + app_set_current_command(CMD_NONE); + ui_menu_main(); + return true; + } + + if (MAX_NUMBER_OF_SCREENS - *screen < 2) return false; + + nbgl_useCaseReviewStreamingFinish("Approve Signing", ui_stx_operation_approve_action); + approved = io_ui_process(); + + if (approved) { + ctx->op_response_cb(ctx->op_cb_context); + app_set_current_command(CMD_NONE); + nbgl_useCaseReviewStatus(STATUS_TYPE_TRANSACTION_SIGNED, quit_callback); + } else { + res_deny(); + app_set_current_command(CMD_NONE); + nbgl_useCaseReviewStatus(STATUS_TYPE_TRANSACTION_REJECTED, quit_callback); + } + + io_ui_process(); + ui_menu_main(); + + return true; +} + +void ui_stx_operation_approve_reject(bool approved, sign_transaction_ui_aprove_ctx_t* ctx) { + sign_transaction_ctx_t* sign_tx = (sign_transaction_ctx_t*) ctx->sign_tx_context; + + app_set_ui_busy(false); + + if (approved) { + app_set_connected_app_id(ctx->app_token_value); + sign_tx->state = SIGN_TRANSACTION_STATE_APPROVED; + send_response_sign_transaction_session_id(sign_tx->session); + } else { + app_set_current_command(CMD_NONE); + res_deny(); + + ui_menu_main(); + } +} + +#endif \ No newline at end of file diff --git a/src/constants.h b/src/constants.h index cad8ad55..8f0290ac 100644 --- a/src/constants.h +++ b/src/constants.h @@ -38,7 +38,7 @@ /** * Maximum number of tokens in TX. */ -#define TOKEN_MAX_COUNT 20 +#define TOKEN_MAX_COUNT 100 /** * Length of Session Key. diff --git a/src/ui/display.c b/src/ui/display.c new file mode 100644 index 00000000..a0dd49af --- /dev/null +++ b/src/ui/display.c @@ -0,0 +1,37 @@ +#include "display.h" +#include "../context.h" + +#ifdef HAVE_NBGL + +nbgl_layoutTagValue_t pairs_global[N_UX_PAIRS]; +#endif + +bool flow_response = false; + +void set_flow_response(bool response) { + flow_response = response; + app_set_ui_busy(false); +} + +void io_common_process() { + io_seproxyhal_general_status(); + do { + io_seproxyhal_spi_recv(G_io_seproxyhal_spi_buffer, sizeof(G_io_seproxyhal_spi_buffer), 0); + io_seproxyhal_handle_event(); + io_seproxyhal_general_status(); + } while (io_seproxyhal_spi_is_status_sent() && app_is_ui_busy()); +} + +bool io_ui_process() { + // We are not waiting for the client's input, nor we are doing computations on the device + // io_clear_processing_timeout(); + + app_set_ui_busy(true); + + io_common_process(); + + // We're back at work, we want to show the "Processing..." screen when appropriate + // io_start_processing_timeout(); + + return flow_response; +} \ No newline at end of file diff --git a/src/ui/display.h b/src/ui/display.h new file mode 100644 index 00000000..c9240758 --- /dev/null +++ b/src/ui/display.h @@ -0,0 +1,22 @@ + +#pragma once + +#include +#include "../context.h" +#include "../constants.h" + +extern bool flow_response; + +void set_flow_response(bool response); +void io_common_process(); +bool io_ui_process(); + +#ifdef HAVE_NBGL +#define N_UX_PAIRS 10 + +static nbgl_layoutTagValueList_t pair_list; +extern nbgl_layoutTagValue_t pairs_global[N_UX_PAIRS]; + +static char pair_mem_title[N_UX_PAIRS][20]; +static char pair_mem_text[N_UX_PAIRS][70]; +#endif \ No newline at end of file diff --git a/src/ui/ui_application_id.h b/src/ui/ui_application_id.h index 83ac6854..510316a0 100644 --- a/src/ui/ui_application_id.h +++ b/src/ui/ui_application_id.h @@ -5,5 +5,17 @@ #define APPLICATION_ID_STR_LEN 11 +#ifdef HAVE_BAGL + const ux_flow_step_t* ui_application_id_screen(uint32_t app_id, - char buffer[static APPLICATION_ID_STR_LEN]); \ No newline at end of file + char buffer[static APPLICATION_ID_STR_LEN]); + +#endif + +#ifdef HAVE_NBGL +#include + +nbgl_layoutTagValue_t ui_application_id_screen(uint32_t app_id, + char buffer[static APPLICATION_ID_STR_LEN]); + +#endif \ No newline at end of file diff --git a/src/ui/ui_application_id.c b/src/ui/ui_application_id_bagl.c similarity index 96% rename from src/ui/ui_application_id.c rename to src/ui/ui_application_id_bagl.c index 4d808669..c2921f2a 100644 --- a/src/ui/ui_application_id.c +++ b/src/ui/ui_application_id_bagl.c @@ -1,3 +1,4 @@ +#ifdef HAVE_BAGL #include "ui_application_id.h" #include #include @@ -19,4 +20,5 @@ const ux_flow_step_t* ui_application_id_screen(uint32_t app_id, memset(buffer, 0, APPLICATION_ID_STR_LEN); snprintf(buffer, APPLICATION_ID_STR_LEN, "0x%08x", app_id); return &ux_app_token_step; -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/ui/ui_application_id_nbgl.c b/src/ui/ui_application_id_nbgl.c new file mode 100644 index 00000000..ee81c030 --- /dev/null +++ b/src/ui/ui_application_id_nbgl.c @@ -0,0 +1,15 @@ +#ifdef HAVE_NBGL + +#include "ui_application_id.h" + +nbgl_layoutTagValue_t ui_application_id_screen(uint32_t app_id, + char buffer[static APPLICATION_ID_STR_LEN]) { + memset(buffer, 0, APPLICATION_ID_STR_LEN); + snprintf(buffer, APPLICATION_ID_STR_LEN, "0x%08x", app_id); + + nbgl_layoutTagValue_t tag = (nbgl_layoutTagValue_t){.item = "Application", .value = buffer}; + + return tag; +} + +#endif \ No newline at end of file diff --git a/src/ui/ui_approve_reject.c b/src/ui/ui_approve_reject.c index 80bc1d4d..283daed6 100644 --- a/src/ui/ui_approve_reject.c +++ b/src/ui/ui_approve_reject.c @@ -1,3 +1,4 @@ +#ifdef HAVE_BAGL #include #include "ui_approve_reject.h" @@ -29,4 +30,5 @@ void ui_approve_reject_screens(ui_approve_reject_callback cb, G_ui_approve_reject_callback_context = context; *approve = &ux_approve_step; *reject = &ux_reject_step; -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/ui/ui_approve_reject.h b/src/ui/ui_approve_reject.h index 9faed384..350d2c39 100644 --- a/src/ui/ui_approve_reject.h +++ b/src/ui/ui_approve_reject.h @@ -1,5 +1,5 @@ #pragma once - +#ifdef HAVE_BAGL #include #include @@ -8,4 +8,6 @@ typedef void (*ui_approve_reject_callback)(bool, void*); void ui_approve_reject_screens(ui_approve_reject_callback cb, void* context, const ux_flow_step_t** approve, - const ux_flow_step_t** reject); \ No newline at end of file + const ux_flow_step_t** reject); + +#endif \ No newline at end of file diff --git a/src/ui/ui_bip32_path.h b/src/ui/ui_bip32_path.h index 88a61ed8..00d08ba1 100644 --- a/src/ui/ui_bip32_path.h +++ b/src/ui/ui_bip32_path.h @@ -3,6 +3,8 @@ #include #include +#ifdef HAVE_BAGL + typedef void (*ui_bip32_approve_callback)(void*); const ux_flow_step_t* ui_bip32_path_screen(uint32_t* path, @@ -11,4 +13,10 @@ const ux_flow_step_t* ui_bip32_path_screen(uint32_t* path, char* buffer, uint8_t buffer_len, ui_bip32_approve_callback cb, - void* cb_context); \ No newline at end of file + void* cb_context); + +#endif + +#ifdef HAVE_NBGL +bool ui_bip32_path_screen(uint32_t* path, uint8_t path_len, char* buffer, uint8_t buffer_len); +#endif \ No newline at end of file diff --git a/src/ui/ui_bip32_path.c b/src/ui/ui_bip32_path_bagl.c similarity index 98% rename from src/ui/ui_bip32_path.c rename to src/ui/ui_bip32_path_bagl.c index f00a50d4..58e18908 100644 --- a/src/ui/ui_bip32_path.c +++ b/src/ui/ui_bip32_path_bagl.c @@ -1,3 +1,5 @@ +#ifdef HAVE_BAGL + #include "ui_bip32_path.h" #include "../common/bip32_ext.h" @@ -42,4 +44,6 @@ const ux_flow_step_t* ui_bip32_path_screen(uint32_t* path, G_ui_bip32_approve_callback = cb; G_ui_bip32_approve_callback_context = cb_context; return &ux_bip32_path_step; -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/src/ui/ui_bip32_path_nbgl.c b/src/ui/ui_bip32_path_nbgl.c new file mode 100644 index 00000000..1925dc20 --- /dev/null +++ b/src/ui/ui_bip32_path_nbgl.c @@ -0,0 +1,15 @@ +#ifdef HAVE_NBGL + +#include "ui_bip32_path.h" +#include "../common/bip32_ext.h" + +bool ui_bip32_path_screen(uint32_t* path, uint8_t path_len, char* buffer, uint8_t buffer_len) { + memset(buffer, 0, buffer_len); + if (!bip32_path_format(path, path_len, buffer, buffer_len)) { + return false; + } + + return true; +} + +#endif \ No newline at end of file diff --git a/src/ui/ui_dynamic_flow.c b/src/ui/ui_dynamic_flow.c index 25b7b9ee..d1088cdc 100644 --- a/src/ui/ui_dynamic_flow.c +++ b/src/ui/ui_dynamic_flow.c @@ -1,3 +1,5 @@ +#ifdef HAVE_BAGL + #include "ui_dynamic_flow.h" #include "../constants.h" @@ -107,3 +109,5 @@ bool ui_add_dynamic_flow_screens(uint8_t *screen, return true; } + +#endif \ No newline at end of file diff --git a/src/ui/ui_dynamic_flow.h b/src/ui/ui_dynamic_flow.h index 01e869c6..a4aeb0db 100644 --- a/src/ui/ui_dynamic_flow.h +++ b/src/ui/ui_dynamic_flow.h @@ -1,5 +1,5 @@ #pragma once - +#ifdef HAVE_BAGL #include #include #include @@ -12,4 +12,6 @@ bool ui_add_dynamic_flow_screens(uint8_t *screen, char *title_storage, char *text_storage, ui_dynamic_flow_show_screen_cb show_cb, - void *cb_ctx); \ No newline at end of file + void *cb_ctx); + +#endif \ No newline at end of file diff --git a/src/ui/ui_main.c b/src/ui/ui_main_bagl.c similarity index 100% rename from src/ui/ui_main.c rename to src/ui/ui_main_bagl.c diff --git a/src/ui/ui_menu.c b/src/ui/ui_menu_bagl.c similarity index 94% rename from src/ui/ui_menu.c rename to src/ui/ui_menu_bagl.c index c11aaa5f..e4439f95 100644 --- a/src/ui/ui_menu.c +++ b/src/ui/ui_menu_bagl.c @@ -15,6 +15,8 @@ * limitations under the License. *****************************************************************************/ +#ifdef HAVE_BAGL + #include #include #include @@ -22,7 +24,7 @@ #include "ui_menu.h" #include "ui_main.h" -UX_STEP_NOCB(ux_menu_ready_step, pnn, {&C_app_logo, APPNAME, "is ready"}); +UX_STEP_NOCB(ux_menu_ready_step, pnn, {&C_app_logo_16px, APPNAME, "is ready"}); UX_STEP_CB(ux_menu_about_step, pb, ui_menu_about(), {&C_icon_certificate, "About"}); UX_STEP_CB(ux_menu_exit_step, pb, os_sched_exit(-1), {&C_icon_dashboard_x, "Quit"}); @@ -53,3 +55,5 @@ void ui_menu_about() { app_set_ui_busy(false); } + +#endif \ No newline at end of file diff --git a/src/ui/ui_menu_nbgl.c b/src/ui/ui_menu_nbgl.c new file mode 100644 index 00000000..26d10af4 --- /dev/null +++ b/src/ui/ui_menu_nbgl.c @@ -0,0 +1,39 @@ +#ifdef HAVE_NBGL + +#include "ui_menu.h" +#include +#include +#include + +#define APPTAGLINE "Ergo app for ledger" +#define APPCOPYRIGHT "Ergo App (c) 2024" + +#define SETTING_INFO_NB 2 +static const char* const INFO_TYPES[SETTING_INFO_NB] = {"Version", "Copyright"}; +static const char* const INFO_CONTENTS[SETTING_INFO_NB] = {APPVERSION, APPCOPYRIGHT}; +static const nbgl_contentInfoList_t infoList = { + .nbInfos = SETTING_INFO_NB, + .infoTypes = INFO_TYPES, + .infoContents = INFO_CONTENTS, +}; + +void app_quit(void) { + // exit app here + os_sched_exit(-1); +} + +void ui_menu_main() { + nbgl_useCaseHomeAndSettings(APPNAME, + &C_app_logo_64px, + APPTAGLINE, + INIT_HOME_PAGE, + NULL, + &infoList, + NULL, + app_quit); +} + +void ui_menu_about() { +} + +#endif \ No newline at end of file diff --git a/tests/README.md b/tests/README.md index 85d4a90b..77eb8074 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,6 +1,6 @@ # End-to-end tests -These tests are implemented in NodeJS and can be executed either using the [Speculos](https://github.com/LedgerHQ/speculos) emulator or a Ledger Nano S/X. +These tests are implemented in NodeJS and can be executed either using the [Speculos](https://github.com/LedgerHQ/speculos) emulator or a Ledger Nano S+/X. NodeJS dependencies are listed in [package.json](package.json), install them using `npm` or `yarn`. ``` @@ -21,7 +21,7 @@ then run npm run test ``` -### Launch with your Nano S/X +### Launch with your Nano S+/X Be sure to have you device connected through USB (without any other software interacting with it) and run diff --git a/tests/helpers/hooks.js b/tests/helpers/hooks.js index c8c9f1f2..22f31b98 100644 --- a/tests/helpers/hooks.js +++ b/tests/helpers/hooks.js @@ -17,7 +17,7 @@ exports.mochaHooks = { if (!this.model) { throw new Error("No model. Provide model with --model= parameter"); } - if (["nanos", "nanox", "nanosp", "hid"].indexOf(this.model) < 0) { + if (["nanox", "nanosp", "flex", "stax", "hid"].indexOf(this.model) < 0) { throw new Error("Unknown model: " + this.model + ", supports: nanos, nanox, nanosp, hid"); } if (this.model === "hid") { diff --git a/tests/package.json b/tests/package.json index 096e40c9..d63db48f 100644 --- a/tests/package.json +++ b/tests/package.json @@ -13,9 +13,9 @@ "mocha": "^10.1.0" }, "dependencies": { - "@ledgerhq/hw-transport-node-hid": "6.28.5", - "@ledgerhq/hw-transport-node-speculos-http": "6.28.5", - "ergo-lib-wasm-nodejs": "v0.26.0", - "ledger-ergo-js": "^0.1.14" + "@ledgerhq/hw-transport-node-hid": "6.29.5", + "@ledgerhq/hw-transport-node-speculos-http": "6.29.4", + "ergo-lib-wasm-nodejs": "v0.28.0", + "ledger-ergo-js": "^0.1.18" } } diff --git a/tests/transaction-tests.js b/tests/transaction-tests.js index 88f3cd2d..66f42e2d 100644 --- a/tests/transaction-tests.js +++ b/tests/transaction-tests.js @@ -18,9 +18,9 @@ function signTxFlows({ device }, auth, from, to, change, tokens_to = undefined, flows[i++].push({ header: null, body: 'Approve' }, { header: null, body: 'Reject' }); // accept tx screen flows[i] = [{ header: 'P2PK Signing', body: removeMasterNode(from.path.toString()) }]; - if (!auth) { + /*if (!auth) { flows[i].push({ header: 'Application', body: '0x00000000' }); - } + }*/ flows[i++].push({ header: null, body: 'Approve' }, { header: null, body: 'Reject' }); // output screen if (to) { @@ -76,12 +76,12 @@ describe("Transaction Tests", function () { expect(attestedBox.frames).to.have.length(1); const frame = attestedBox.frames[0]; expect(frame.boxId).to.exist; - expect(frame.framesCount).to.be.equal(1); - expect(frame.frameIndex).to.be.equal(0); + expect(frame.count).to.be.equal(1); + expect(frame.index).to.be.equal(0); expect(frame.amount).to.be.equal('1000000000'); expect(frame.tokens).to.be.empty; expect(frame.attestation).to.exist; - expect(frame.buffer).to.exist; + expect(frame.bytes).to.exist; }) .run(({test, unsignedBox}) => test.device.attestInput(unsignedBox));