From be029c642d42f41d70771995f796fe855ecf0dce Mon Sep 17 00:00:00 2001 From: Clement Bouvet Date: Tue, 21 Feb 2023 11:01:18 +0100 Subject: [PATCH] stax: add UI implementation --- src/handle_swap_sign_transaction.c | 6 + src/main.c | 25 ++- src/ui_callbacks.h | 4 + src_nbgl/ui_approve_tx.c | 235 ++++++++++++++++++++++++++++ src_nbgl/ui_confirm_parameter.c | 56 +++++++ src_nbgl/ui_confirm_selector.c | 56 +++++++ src_nbgl/ui_display_privacy.c | 68 ++++++++ src_nbgl/ui_get_eth2_public_key.c | 30 ++++ src_nbgl/ui_get_public_key.c | 28 ++++ src_nbgl/ui_get_stark_public_key.c | 29 ++++ src_nbgl/ui_idle.c | 61 ++++++++ src_nbgl/ui_nbgl.h | 19 +++ src_nbgl/ui_settings.c | 92 +++++++++++ src_nbgl/ui_sign_712.c | 80 ++++++++++ src_nbgl/ui_sign_712_v0.c | 66 ++++++++ src_nbgl/ui_sign_message.c | 166 ++++++++++++++++++++ src_nbgl/ui_stark_limit_order.c | 67 ++++++++ src_nbgl/ui_stark_transfer.c | 127 +++++++++++++++ src_nbgl/ui_stark_unsafe_sign.c | 70 +++++++++ src_nbgl/ui_warning_contract_data.c | 21 +++ 20 files changed, 1298 insertions(+), 8 deletions(-) create mode 100644 src_nbgl/ui_approve_tx.c create mode 100644 src_nbgl/ui_confirm_parameter.c create mode 100644 src_nbgl/ui_confirm_selector.c create mode 100644 src_nbgl/ui_display_privacy.c create mode 100644 src_nbgl/ui_get_eth2_public_key.c create mode 100644 src_nbgl/ui_get_public_key.c create mode 100644 src_nbgl/ui_get_stark_public_key.c create mode 100644 src_nbgl/ui_idle.c create mode 100644 src_nbgl/ui_nbgl.h create mode 100644 src_nbgl/ui_settings.c create mode 100644 src_nbgl/ui_sign_712.c create mode 100644 src_nbgl/ui_sign_712_v0.c create mode 100644 src_nbgl/ui_sign_message.c create mode 100644 src_nbgl/ui_stark_limit_order.c create mode 100644 src_nbgl/ui_stark_transfer.c create mode 100644 src_nbgl/ui_stark_unsafe_sign.c create mode 100644 src_nbgl/ui_warning_contract_data.c diff --git a/src/handle_swap_sign_transaction.c b/src/handle_swap_sign_transaction.c index 39268f5..7eb0e27 100644 --- a/src/handle_swap_sign_transaction.c +++ b/src/handle_swap_sign_transaction.c @@ -67,7 +67,13 @@ void handle_swap_sign_transaction(chain_config_t* config) { nvm_write((void*) &N_storage, (void*) &storage, sizeof(internalStorage_t)); } +#ifdef HAVE_BAGL UX_INIT(); +#endif // HAVE_BAGL +#ifdef HAVE_NBGL + nbgl_objInit(); +#endif // HAVE_NBGL + USB_power(0); USB_power(1); // ui_idle(); diff --git a/src/main.c b/src/main.c index d66dbb1..67442b0 100644 --- a/src/main.c +++ b/src/main.c @@ -877,9 +877,11 @@ void app_main(void) { } // override point, but nothing more to do +#ifdef HAVE_BAGL void io_seproxyhal_display(const bagl_element_t *element) { io_seproxyhal_display_default((bagl_element_t *) element); } +#endif unsigned char io_event(__attribute__((unused)) unsigned char channel) { // nothing done with the event, throw an error on the transport layer if @@ -890,10 +892,11 @@ unsigned char io_event(__attribute__((unused)) unsigned char channel) { case SEPROXYHAL_TAG_FINGER_EVENT: UX_FINGER_EVENT(G_io_seproxyhal_spi_buffer); break; - +#ifdef HAVE_BAGL case SEPROXYHAL_TAG_BUTTON_PUSH_EVENT: UX_BUTTON_PUSH_EVENT(G_io_seproxyhal_spi_buffer); break; +#endif // HAVE_BAGL case SEPROXYHAL_TAG_STATUS_EVENT: if (G_io_apdu_media == IO_APDU_MEDIA_USB_HID && @@ -907,16 +910,17 @@ unsigned char io_event(__attribute__((unused)) unsigned char channel) { break; case SEPROXYHAL_TAG_DISPLAY_PROCESSED_EVENT: +#ifdef HAVE_BAGL UX_DISPLAYED_EVENT({}); +#endif // HAVE_BAGL +#ifdef HAVE_NBGL + UX_DEFAULT_EVENT(); +#endif // HAVE_NBGL break; -#if 0 - case SEPROXYHAL_TAG_TICKER_EVENT: - UX_TICKER_EVENT(G_io_seproxyhal_spi_buffer, - { - }); - break; -#endif + case SEPROXYHAL_TAG_TICKER_EVENT: + UX_TICKER_EVENT(G_io_seproxyhal_spi_buffer, {}); + break; } // close the event if not done previously (by a display or whatever) @@ -958,7 +962,12 @@ void coin_main(chain_config_t *coin_config) { tmpCtx.transactionContext.currentItemIndex = 0; for (;;) { +#ifdef HAVE_BAGL UX_INIT(); +#endif // HAVE_BAGL +#ifdef HAVE_NBGL + nbgl_objInit(); +#endif // HAVE_NBGL BEGIN_TRY { TRY { diff --git a/src/ui_callbacks.h b/src/ui_callbacks.h index 2035c27..041f717 100644 --- a/src/ui_callbacks.h +++ b/src/ui_callbacks.h @@ -4,6 +4,10 @@ #include "shared_context.h" #include "ux.h" +#ifdef HAVE_NBGL +typedef int bagl_element_t; +#endif + unsigned int io_seproxyhal_touch_settings(const bagl_element_t *e); unsigned int io_seproxyhal_touch_exit(const bagl_element_t *e); unsigned int io_seproxyhal_touch_tx_ok(const bagl_element_t *e); diff --git a/src_nbgl/ui_approve_tx.c b/src_nbgl/ui_approve_tx.c new file mode 100644 index 0000000..e808cf2 --- /dev/null +++ b/src_nbgl/ui_approve_tx.c @@ -0,0 +1,235 @@ + +#include +#include "shared_context.h" +#include "ui_callbacks.h" +#include "ui_nbgl.h" +#include "network.h" +#include "plugins.h" + +// 1 more than actually displayed on screen, because of calculations in StaticReview +#define MAX_PLUGIN_ITEMS_PER_SCREEN 4 +#define TAG_MAX_LEN 43 +#define VALUE_MAX_LEN 79 +enum { + REJECT_TOKEN, + START_REVIEW_TOKEN, +}; + +static nbgl_layoutTagValue_t tlv; +// these buffers are used as circular +static char title_buffer[MAX_PLUGIN_ITEMS_PER_SCREEN][TAG_MAX_LEN]; +static char msg_buffer[MAX_PLUGIN_ITEMS_PER_SCREEN][VALUE_MAX_LEN]; +static char transaction_type[100]; +static nbgl_layoutTagValueList_t useCaseTagValueList; +static nbgl_pageInfoLongPress_t infoLongPress; + +struct tx_approval_context_t { + bool fromPlugin; + bool blindSigning; + bool displayNetwork; +}; + +static struct tx_approval_context_t tx_approval_context; + +static void reviewContinueCommon(void); + +static void reviewReject(void) { + io_seproxyhal_touch_tx_cancel(NULL); +} + +static void confirmTransation(void) { + io_seproxyhal_touch_tx_ok(NULL); +} + +static void onConfirmAbandon(void) { + nbgl_useCaseStatus("Transaction rejected", false, reviewReject); +} + +static void rejectTransactionQuestion(void) { + nbgl_useCaseConfirm("Reject transaction?", + NULL, + "Yes, reject", + "Go back to transaction", + onConfirmAbandon); +} + +static void reviewChoice(bool confirm) { + if (confirm) { + nbgl_useCaseStatus("TRANSACTION\nSIGNED", true, confirmTransation); + } else { + rejectTransactionQuestion(); + } +} + +// called by NBGL to get the tag/value pair corresponding to pairIndex +static nbgl_layoutTagValue_t *getTagValuePair(uint8_t pairIndex) { + static int counter = 0; + + if (tx_approval_context.fromPlugin) { + if (pairIndex < dataContext.tokenContext.pluginUiMaxItems) { + // for the next dataContext.tokenContext.pluginUiMaxItems items, get tag/value from + // plugin_ui_get_item_internal() + dataContext.tokenContext.pluginUiCurrentItem = pairIndex; + plugin_ui_get_item_internal((uint8_t *) title_buffer[counter], + TAG_MAX_LEN, + (uint8_t *) msg_buffer[counter], + VALUE_MAX_LEN); + tlv.item = title_buffer[counter]; + tlv.value = msg_buffer[counter]; + } else { + pairIndex -= dataContext.tokenContext.pluginUiMaxItems; + // for the last 1 (or 2), tags are fixed + if (tx_approval_context.displayNetwork && (pairIndex == 0)) { + tlv.item = "Network"; + tlv.value = strings.common.network_name; + } else { + tlv.item = "Max fees"; + tlv.value = strings.common.maxFee; + } + } + } else { + // if displayNonce is false, we skip index 2 + if ((pairIndex > 1) && (!N_storage.displayNonce)) { + pairIndex++; + } + + switch (pairIndex) { + case 0: + tlv.item = "Amount"; + tlv.value = strings.common.fullAmount; + break; + case 1: + tlv.item = "Address"; + tlv.value = strings.common.fullAddress; + break; + case 2: + tlv.item = "Nonce"; + tlv.value = strings.common.nonce; + break; + case 3: + tlv.item = "Max fees"; + tlv.value = strings.common.maxFee; + break; + case 4: + tlv.item = "Network"; + tlv.value = strings.common.network_name; + break; + } + } + // counter is used as index to circular buffer + counter++; + if (counter == MAX_PLUGIN_ITEMS_PER_SCREEN) { + counter = 0; + } + return &tlv; +} + +static void pageCallback(int token, uint8_t index) { + (void) index; + nbgl_pageRelease(pageContext); + if (token == REJECT_TOKEN) { + reviewReject(); + } else if (token == START_REVIEW_TOKEN) { + reviewContinueCommon(); + } +} + +static void reviewContinue(void) { + if (tx_approval_context.blindSigning) { + nbgl_pageInfoDescription_t info = { + .centeredInfo.icon = &C_round_warning_64px, + .centeredInfo.text1 = "Blind Signing", + .centeredInfo.text2 = + "This transaction cannot be\nsecurely interpreted by Ledger\nStax. It might put " + "your assets\nat risk.", + .centeredInfo.text3 = NULL, + .centeredInfo.style = LARGE_CASE_INFO, + .centeredInfo.offsetY = -32, + .footerText = "Reject transaction", + .footerToken = REJECT_TOKEN, + .tapActionText = "Tap to continue", + .tapActionToken = START_REVIEW_TOKEN, + .topRightStyle = NO_BUTTON_STYLE, + .actionButtonText = NULL, + .tuneId = TUNE_TAP_CASUAL}; + + if (pageContext != NULL) { + nbgl_pageRelease(pageContext); + pageContext = NULL; + } + pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info); + } else { + reviewContinueCommon(); + } +} + +static void reviewContinueCommon(void) { + uint8_t nbPairs = 0; + + if (tx_approval_context.fromPlugin) { + // plugin id + max items + fees + nbPairs += dataContext.tokenContext.pluginUiMaxItems + 1; + if (tx_approval_context.displayNetwork) { + nbPairs++; + } + } else { + nbPairs += 3; + if (N_storage.displayNonce) { + nbPairs++; + } + if (tx_approval_context.displayNetwork) { + nbPairs++; + } + } + + useCaseTagValueList.pairs = NULL; + useCaseTagValueList.callback = getTagValuePair; + useCaseTagValueList.startIndex = 0; + useCaseTagValueList.nbPairs = nbPairs; ///< number of pairs in pairs array + useCaseTagValueList.smallCaseForValue = false; + useCaseTagValueList.wrapping = false; + infoLongPress.icon = get_app_chain_icon(); + infoLongPress.text = tx_approval_context.fromPlugin ? transaction_type : "Review transaction"; + infoLongPress.longPressText = "Hold to sign"; + nbgl_useCaseStaticReview(&useCaseTagValueList, + &infoLongPress, + "Reject transaction", + reviewChoice); +} + +static void buildFirstPage(void) { + if (tx_approval_context.fromPlugin) { + plugin_ui_get_id(); + SPRINTF(transaction_type, + "Review %s\ntransaction:\n%s", + strings.common.fullAddress, + strings.common.fullAmount); + nbgl_useCaseReviewStart(get_app_chain_icon(), + transaction_type, + NULL, + "Reject transaction", + reviewContinue, + rejectTransactionQuestion); + } else { + nbgl_useCaseReviewStart(get_app_chain_icon(), + "Review transaction", + NULL, + "Reject transaction", + reviewContinue, + rejectTransactionQuestion); + } +} + +void ux_approve_tx(bool fromPlugin) { + tx_approval_context.blindSigning = + !fromPlugin && tmpContent.txContent.dataPresent && !N_storage.contractDetails; + tx_approval_context.fromPlugin = fromPlugin; + tx_approval_context.displayNetwork = false; + + uint64_t chain_id = get_tx_chain_id(); + if (chainConfig->chainId == ETHEREUM_MAINNET_CHAINID && chain_id != chainConfig->chainId) { + tx_approval_context.displayNetwork = true; + } + + buildFirstPage(); +} diff --git a/src_nbgl/ui_confirm_parameter.c b/src_nbgl/ui_confirm_parameter.c new file mode 100644 index 0000000..81d2a8a --- /dev/null +++ b/src_nbgl/ui_confirm_parameter.c @@ -0,0 +1,56 @@ +#include "common_ui.h" +#include "ui_nbgl.h" +#include "network.h" + +static nbgl_layoutTagValue_t tlv; + +static void reviewReject(void) { + io_seproxyhal_touch_data_cancel(NULL); +} + +static void confirmTransation(void) { + io_seproxyhal_touch_data_ok(NULL); +} + +static void reviewChoice(bool confirm) { + if (confirm) { + confirmTransation(); + } else { + reviewReject(); + } +} + +static bool displayTransactionPage(uint8_t page, nbgl_pageContent_t *content) { + if (page == 0) { + tlv.item = "Parameter"; + tlv.value = strings.tmp.tmp; + content->type = TAG_VALUE_LIST; + content->tagValueList.nbPairs = 1; + content->tagValueList.pairs = (nbgl_layoutTagValue_t *) &tlv; + } else if (page == 1) { + content->type = INFO_LONG_PRESS, content->infoLongPress.icon = get_app_chain_icon(); + content->infoLongPress.text = "Confirm parameter"; + content->infoLongPress.longPressText = "Hold to confirm"; + } else { + return false; + } + // valid page so return true + return true; +} + +static void reviewContinue(void) { + nbgl_useCaseRegularReview(0, 2, "Reject parameter", NULL, displayTransactionPage, reviewChoice); +} + +static void buildScreen(void) { + nbgl_useCaseReviewStart(get_app_chain_icon(), + "Verify parameter", + NULL, + "Reject", + reviewContinue, + reviewReject); +} + +void ui_confirm_parameter(void) { + buildScreen(); +} diff --git a/src_nbgl/ui_confirm_selector.c b/src_nbgl/ui_confirm_selector.c new file mode 100644 index 0000000..3bf5a11 --- /dev/null +++ b/src_nbgl/ui_confirm_selector.c @@ -0,0 +1,56 @@ +#include "common_ui.h" +#include "ui_nbgl.h" +#include "network.h" + +static nbgl_layoutTagValue_t tlv; + +static void reviewReject(void) { + io_seproxyhal_touch_data_cancel(NULL); +} + +static void confirmTransation(void) { + io_seproxyhal_touch_data_ok(NULL); +} + +static void reviewChoice(bool confirm) { + if (confirm) { + confirmTransation(); + } else { + reviewReject(); + } +} + +static bool displayTransactionPage(uint8_t page, nbgl_pageContent_t *content) { + if (page == 0) { + tlv.item = "Parameter"; + tlv.value = strings.tmp.tmp; + content->type = TAG_VALUE_LIST; + content->tagValueList.nbPairs = 1; + content->tagValueList.pairs = (nbgl_layoutTagValue_t *) &tlv; + } else if (page == 1) { + content->type = INFO_LONG_PRESS, content->infoLongPress.icon = get_app_chain_icon(); + content->infoLongPress.text = "Confirm selector"; + content->infoLongPress.longPressText = "Hold to confirm"; + } else { + return false; + } + // valid page so return true + return true; +} + +static void reviewContinue(void) { + nbgl_useCaseRegularReview(0, 2, "Reject selector", NULL, displayTransactionPage, reviewChoice); +} + +static void buildScreen(void) { + nbgl_useCaseReviewStart(get_app_chain_icon(), + "Verify selector", + NULL, + "Reject", + reviewContinue, + reviewReject); +} + +void ui_confirm_selector(void) { + buildScreen(); +} diff --git a/src_nbgl/ui_display_privacy.c b/src_nbgl/ui_display_privacy.c new file mode 100644 index 0000000..b4a9d1c --- /dev/null +++ b/src_nbgl/ui_display_privacy.c @@ -0,0 +1,68 @@ +#include "common_ui.h" +#include "ui_nbgl.h" +#include "ui_callbacks.h" +#include "nbgl_use_case.h" +#include "network.h" + +static nbgl_layoutTagValue_t tlv[2]; +static char *review_string; + +static void reviewReject(void) { + io_seproxyhal_touch_privacy_cancel(NULL); +} + +static void confirmTransation(void) { + io_seproxyhal_touch_privacy_ok(NULL); +} + +static void reviewChoice(bool confirm) { + if (confirm) { + confirmTransation(); + } else { + reviewReject(); + } +} + +static bool displayTransactionPage(uint8_t page, nbgl_pageContent_t *content) { + if (page == 0) { + tlv[0].item = "Address"; + tlv[0].value = strings.common.fullAddress; + tlv[1].item = "Key"; + tlv[1].value = strings.common.fullAmount; + + content->type = TAG_VALUE_LIST; + content->tagValueList.nbPairs = 2; + content->tagValueList.pairs = (nbgl_layoutTagValue_t *) tlv; + } else if (page == 1) { + content->type = INFO_LONG_PRESS, content->infoLongPress.icon = get_app_chain_icon(); + content->infoLongPress.text = review_string; + content->infoLongPress.longPressText = "Hold to approve"; + } else { + return false; + } + // valid page so return true + return true; +} + +static void reviewContinue(void) { + nbgl_useCaseRegularReview(0, 2, "Reject", NULL, displayTransactionPage, reviewChoice); +} + +static void buildFirstPage(void) { + nbgl_useCaseReviewStart(get_app_chain_icon(), + review_string, + NULL, + "Reject", + reviewContinue, + reviewReject); +} + +void ui_display_privacy_public_key(void) { + review_string = "Provide public\nprivacy key"; + buildFirstPage(); +} + +void ui_display_privacy_shared_secret(void) { + review_string = "Provide public\nsecret key"; + buildFirstPage(); +} diff --git a/src_nbgl/ui_get_eth2_public_key.c b/src_nbgl/ui_get_eth2_public_key.c new file mode 100644 index 0000000..9f388df --- /dev/null +++ b/src_nbgl/ui_get_eth2_public_key.c @@ -0,0 +1,30 @@ +#include +#include "shared_context.h" +#include "ui_callbacks.h" +#include "ui_nbgl.h" + +static void reviewReject(void) { + io_seproxyhal_touch_address_cancel(NULL); +} + +static void confirmTransation(void) { + io_seproxyhal_touch_address_ok(NULL); +} + +static void reviewChoice(bool confirm) { + if (confirm) { + // display a status page and go back to main + nbgl_useCaseStatus("ADDRESS\nVERIFIED", true, confirmTransation); + } else { + nbgl_useCaseStatus("Address verification\ncancelled", false, reviewReject); + } +} + +static void buildScreen(void) { + snprintf(strings.tmp.tmp, 100, "0x%.*H", 48, tmpCtx.publicKeyContext.publicKey.W); + nbgl_useCaseAddressConfirmation(strings.tmp.tmp, reviewChoice); +} + +void ui_display_public_eth2(void) { + buildScreen(); +} \ No newline at end of file diff --git a/src_nbgl/ui_get_public_key.c b/src_nbgl/ui_get_public_key.c new file mode 100644 index 0000000..5966737 --- /dev/null +++ b/src_nbgl/ui_get_public_key.c @@ -0,0 +1,28 @@ +#include +#include "shared_context.h" +#include "ui_callbacks.h" +#include "ui_nbgl.h" + +static void reviewReject(void) { + io_seproxyhal_touch_address_cancel(NULL); +} + +static void confirmTransation(void) { + io_seproxyhal_touch_address_ok(NULL); +} + +static void reviewChoice(bool confirm) { + if (confirm) { + // display a status page and go back to main + nbgl_useCaseStatus("ADDRESS\nVERIFIED", true, confirmTransation); + } else { + nbgl_useCaseStatus("Address verification\ncancelled", false, reviewReject); + } +} + +static void buildScreen(void) { + nbgl_useCaseAddressConfirmation(strings.common.fullAddress, reviewChoice); +} +void ui_display_public_key(void) { + buildScreen(); +} \ No newline at end of file diff --git a/src_nbgl/ui_get_stark_public_key.c b/src_nbgl/ui_get_stark_public_key.c new file mode 100644 index 0000000..ce57787 --- /dev/null +++ b/src_nbgl/ui_get_stark_public_key.c @@ -0,0 +1,29 @@ +#include +#include "shared_context.h" +#include "ui_callbacks.h" +#include "ui_nbgl.h" + +static void reviewReject(void) { + io_seproxyhal_touch_address_cancel(NULL); +} + +static void confirmTransation(void) { + io_seproxyhal_touch_stark_pubkey_ok(NULL); +} + +static void reviewChoice(bool confirm) { + if (confirm) { + // display a status page and go back to main + nbgl_useCaseStatus("ADDRESS\nVERIFIED", true, confirmTransation); + } else { + nbgl_useCaseStatus("Address verification\ncancelled", false, reviewReject); + } +} + +static void buildScreen(void) { + nbgl_useCaseAddressConfirmation(strings.tmp.tmp, reviewChoice); +} + +void ui_display_stark_public(void) { + buildScreen(); +} \ No newline at end of file diff --git a/src_nbgl/ui_idle.c b/src_nbgl/ui_idle.c new file mode 100644 index 0000000..7e02101 --- /dev/null +++ b/src_nbgl/ui_idle.c @@ -0,0 +1,61 @@ +#include "common_ui.h" +#include "shared_context.h" +#include "ui_nbgl.h" +#include "nbgl_use_case.h" +#include "glyphs.h" +#include "network.h" + +uint8_t staxSharedBuffer[SHARED_BUFFER_SIZE] = {0}; + +nbgl_page_t* pageContext; + +void releaseContext(void) { + if (pageContext != NULL) { + nbgl_pageRelease(pageContext); + pageContext = NULL; + } +} +enum { BACK_TOKEN = 0, INFO_TOKEN, NEXT_TOKEN, CANCEL_TOKEN, QUIT_INFO_TOKEN, QUIT_APP_TOKEN }; + +void app_quit(void) { + // exit app here + os_sched_exit(-1); +} + +void ui_idle(void) { + if (plugin_name != NULL) { // plugin + nbgl_useCasePlugInHome((char*) plugin_name, + APPNAME, + &ICONGLYPH_SMALL, + NULL, + NULL, + true, + ui_menu_settings, + app_quit); + } else { + char* app_name = (char*) get_app_network_name(); + + switch (get_app_chain_id()) { + // Standalone apps + case 1: // Mainnet + case 3: // Ropsten + case 5: // Goerli + nbgl_useCaseHome(app_name, + get_app_chain_icon(), + NULL, + true, + ui_menu_settings, + app_quit); + break; + // Clones + default: + nbgl_useCaseHome(app_name, + get_app_chain_icon(), + NULL, + true, + ui_menu_settings, + app_quit); + break; + } + } +} diff --git a/src_nbgl/ui_nbgl.h b/src_nbgl/ui_nbgl.h new file mode 100644 index 0000000..66ffd4f --- /dev/null +++ b/src_nbgl/ui_nbgl.h @@ -0,0 +1,19 @@ +#ifndef _UI_NBGL_H_ +#define _UI_NBGL_H_ + +#include +#include +#include + +#define SHARED_BUFFER_SIZE SHARED_CTX_FIELD_1_SIZE +extern uint8_t staxSharedBuffer[SHARED_BUFFER_SIZE]; + +extern nbgl_page_t* pageContext; + +void releaseContext(void); + +void ui_idle(void); +void ui_menu_settings(void); +void ui_menu_about(void); + +#endif // _UI_NBGL_H_ \ No newline at end of file diff --git a/src_nbgl/ui_settings.c b/src_nbgl/ui_settings.c new file mode 100644 index 0000000..482d220 --- /dev/null +++ b/src_nbgl/ui_settings.c @@ -0,0 +1,92 @@ +#include "common_ui.h" +#include "ui_nbgl.h" +#include "nbgl_use_case.h" + +static const char* const infoTypes[] = {"Version", APPNAME " App"}; +static const char* const infoContents[] = {APPVERSION, "(c) 2022 Ledger"}; + +enum { BLIND_SIGNING_TOKEN = FIRST_USER_TOKEN, DEBUG_TOKEN, NONCE_TOKEN, EIP712_VERBOSE_TOKEN }; + +static nbgl_layoutSwitch_t switches[4]; + +static bool navCallback(uint8_t page, nbgl_pageContent_t* content) { + switch (page) { + case 0: + switches[0] = + (nbgl_layoutSwitch_t){.initState = N_storage.dataAllowed ? ON_STATE : OFF_STATE, + .text = "Blind signing", + .subText = "Enable transaction blind signing", + .token = BLIND_SIGNING_TOKEN, + .tuneId = TUNE_TAP_CASUAL}; + switches[1] = + (nbgl_layoutSwitch_t){.initState = N_storage.contractDetails ? ON_STATE : OFF_STATE, + .text = "Debug", + .subText = "Display contract data details", + .token = DEBUG_TOKEN, + .tuneId = TUNE_TAP_CASUAL}; + switches[2] = + (nbgl_layoutSwitch_t){.initState = N_storage.displayNonce ? ON_STATE : OFF_STATE, + .text = "Nonce", + .subText = "Display account nonce\nin transaction", + .token = NONCE_TOKEN, + .tuneId = TUNE_TAP_CASUAL}; + + content->type = SWITCHES_LIST; + content->switchesList.nbSwitches = 3; + content->switchesList.switches = (nbgl_layoutSwitch_t*) switches; + break; + + case 1: + switches[0] = + (nbgl_layoutSwitch_t){.initState = N_storage.verbose_eip712 ? ON_STATE : OFF_STATE, + .text = "Verbose EIP712", + .subText = "Ignore filtering and\ndisplay raw content", + .token = EIP712_VERBOSE_TOKEN, + .tuneId = TUNE_TAP_CASUAL}; + + content->type = SWITCHES_LIST; + content->switchesList.nbSwitches = 1; + content->switchesList.switches = (nbgl_layoutSwitch_t*) switches; + break; + + case 2: + content->type = INFOS_LIST; + content->infosList.nbInfos = 2; + content->infosList.infoTypes = (const char**) infoTypes; + content->infosList.infoContents = (const char**) infoContents; + break; + + default: + return false; + break; + } + + return true; +} + +static void controlsCallback(int token, uint8_t index) { + (void) index; + uint8_t value; + switch (token) { + case BLIND_SIGNING_TOKEN: + value = (N_storage.dataAllowed ? 0 : 1); + nvm_write((void*) &N_storage.dataAllowed, (void*) &value, sizeof(uint8_t)); + break; + case DEBUG_TOKEN: + value = (N_storage.contractDetails ? 0 : 1); + nvm_write((void*) &N_storage.contractDetails, (void*) &value, sizeof(uint8_t)); + break; + case NONCE_TOKEN: + value = (N_storage.displayNonce ? 0 : 1); + nvm_write((void*) &N_storage.displayNonce, (void*) &value, sizeof(uint8_t)); + break; + case EIP712_VERBOSE_TOKEN: + value = (N_storage.verbose_eip712 ? 0 : 1); + nvm_write((void*) &N_storage.verbose_eip712, (void*) &value, sizeof(uint8_t)); + break; + } +} + +void ui_menu_settings(void) { + nbgl_useCaseSettings(APPNAME " settings", 0, 3, true, ui_idle, navCallback, controlsCallback); +} diff --git a/src_nbgl/ui_sign_712.c b/src_nbgl/ui_sign_712.c new file mode 100644 index 0000000..c13a78a --- /dev/null +++ b/src_nbgl/ui_sign_712.c @@ -0,0 +1,80 @@ +#include "common_ui.h" +#include "ui_nbgl.h" +#include "ui_logic.h" +#include "common_712.h" +#include "nbgl_use_case.h" +#include "network.h" + +// 4 pairs of tag/value to display +static nbgl_layoutTagValue_t tlv; + +static void reject_message(void) { + ui_712_reject(NULL); +} + +static void sign_message() { + ui_712_approve(NULL); +} + +static void reviewChoice(bool confirm) { + if (confirm) { + sign_message(); + } else { + reject_message(); + } +} +static bool displaySignPage(uint8_t page, nbgl_pageContent_t *content) { + (void) page; + content->type = INFO_LONG_PRESS, content->infoLongPress.icon = get_app_chain_icon(); + content->infoLongPress.text = "Sign typed message"; + content->infoLongPress.longPressText = "Hold to sign"; + return true; +} + +static uint32_t stringsIdx = 0; + +static bool displayTransactionPage(uint8_t page, nbgl_pageContent_t *content) { + uint16_t len = 0; + if (stringsIdx < strlen(strings.tmp.tmp)) { + bool reached = nbgl_getTextMaxLenInNbLines(BAGL_FONT_INTER_REGULAR_32px, + strings.tmp.tmp + stringsIdx, + SCREEN_WIDTH - (2 * BORDER_MARGIN), + 9, + &len); + memset(staxSharedBuffer, 0, sizeof(staxSharedBuffer)); + memcpy(staxSharedBuffer, strings.tmp.tmp + stringsIdx, len); + stringsIdx += len; + tlv.item = strings.tmp.tmp2; + tlv.value = staxSharedBuffer; + content->type = TAG_VALUE_LIST; + content->tagValueList.nbPairs = 1; + content->tagValueList.pairs = &tlv; + return true; + } else { + stringsIdx = 0; + switch (ui_712_next_field()) { + case EIP712_NO_MORE_FIELD: + return displaySignPage(page, content); + break; + case EIP712_FIELD_INCOMING: + case EIP712_FIELD_LATER: + default: + break; + } + return false; + } +} + +void ui_712_switch_to_sign(void) { + nbgl_useCaseRegularReview(0, 0, "Reject", NULL, displaySignPage, reviewChoice); +} + +void ui_712_start(void) { + stringsIdx = 0; + nbgl_useCaseRegularReview(0, 0, "Reject", NULL, displayTransactionPage, reviewChoice); +} + +void ui_712_switch_to_message(void) { + stringsIdx = 0; + nbgl_useCaseRegularReview(0, 0, "Reject", NULL, displayTransactionPage, reviewChoice); +} diff --git a/src_nbgl/ui_sign_712_v0.c b/src_nbgl/ui_sign_712_v0.c new file mode 100644 index 0000000..34b529c --- /dev/null +++ b/src_nbgl/ui_sign_712_v0.c @@ -0,0 +1,66 @@ +#include "common_ui.h" +#include "ui_nbgl.h" +#include "common_712.h" +#include "network.h" + +static nbgl_layoutTagValue_t tlv[2]; + +static char domain_hash[70]; +static char message_hash[70]; + +static void reviewReject(void) { + ui_712_approve_cb(NULL); +} + +static void confirmTransation(void) { + ui_712_reject_cb(NULL); +} + +static void reviewChoice(bool confirm) { + if (confirm) { + // display a status page and go back to main + nbgl_useCaseStatus("MESSAGE\nSIGNED", true, confirmTransation); + } else { + nbgl_useCaseStatus("Message signing\ncancelled", false, reviewReject); + } +} + +static bool displayTransactionPage(uint8_t page, nbgl_pageContent_t *content) { + snprintf(domain_hash, 70, "0x%.*H", 32, tmpCtx.messageSigningContext712.domainHash); + snprintf(message_hash, 70, "0x%.*H", 32, tmpCtx.messageSigningContext712.messageHash); + + if (page == 0) { + tlv[0].item = "Domain hash"; + tlv[0].value = domain_hash; + tlv[1].item = "Message hash"; + tlv[1].value = message_hash; + + content->type = TAG_VALUE_LIST; + content->tagValueList.nbPairs = 2; + content->tagValueList.pairs = (nbgl_layoutTagValue_t *) tlv; + } else if (page == 1) { + content->type = INFO_LONG_PRESS, content->infoLongPress.icon = get_app_chain_icon(); + content->infoLongPress.text = "Sign typed message"; + content->infoLongPress.longPressText = "Hold to sign"; + } else { + return false; + } + // valid page so return true + return true; +} +static void reviewContinue(void) { + nbgl_useCaseRegularReview(0, 2, "Reject", NULL, displayTransactionPage, reviewChoice); +} + +static void buildFirstPage(void) { + nbgl_useCaseReviewStart(get_app_chain_icon(), + "Sign typed message", + NULL, + "Reject", + reviewContinue, + reviewReject); +} + +void ui_sign_712_v0(void) { + buildFirstPage(); +} diff --git a/src_nbgl/ui_sign_message.c b/src_nbgl/ui_sign_message.c new file mode 100644 index 0000000..7bcf541 --- /dev/null +++ b/src_nbgl/ui_sign_message.c @@ -0,0 +1,166 @@ +#include +#include "shared_context.h" +#include "ui_callbacks.h" +#include "ui_nbgl.h" +#include "sign_message.h" +#include "glyphs.h" +#include "nbgl_use_case.h" +#include "common_ui.h" + +typedef enum { + UI_191_NBGL_START_REVIEW_DISPLAYED = 0, + UI_191_NBGL_GO_TO_NEXT_CONTENT, + UI_191_NBGL_BACK_FROM_REJECT_CANCEL, + UI_191_NBGL_GO_TO_SIGN, + UI_191_NBGL_SIGN_DISPLAYED, +} e_ui_nbgl_191_state; + +static e_ui_nbgl_191_state state; +static e_ui_nbgl_191_state state_before_reject_cancel; + +static nbgl_layoutTagValue_t pair; + +// +static uint32_t eip191MessageIdx = 0; +static uint32_t stringsTmpTmpIdx = 0; + +static void reject_message(void) { + io_seproxyhal_touch_signMessage_cancel(); +} + +static void sign_message() { + io_seproxyhal_touch_signMessage_ok(); +} + +static bool nav_callback(uint8_t page, nbgl_pageContent_t *content) { + UNUSED(page); + + if ((state != UI_191_NBGL_GO_TO_SIGN) && (state != UI_191_NBGL_SIGN_DISPLAYED)) { + if (state != UI_191_NBGL_BACK_FROM_REJECT_CANCEL) { + memset(staxSharedBuffer + eip191MessageIdx, 0, SHARED_BUFFER_SIZE - eip191MessageIdx); + memcpy( + staxSharedBuffer + eip191MessageIdx, + strings.tmp.tmp + stringsTmpTmpIdx, + MIN(SHARED_BUFFER_SIZE - eip191MessageIdx, SHARED_BUFFER_SIZE - stringsTmpTmpIdx)); + uint16_t len = 0; + bool reached = nbgl_getTextMaxLenInNbLines(BAGL_FONT_INTER_REGULAR_32px, + staxSharedBuffer, + SCREEN_WIDTH - (2 * BORDER_MARGIN), + 9, + &len); + + stringsTmpTmpIdx = len - eip191MessageIdx; + eip191MessageIdx = len; + staxSharedBuffer[eip191MessageIdx] = '\0'; + + if (!reached && eip191MessageIdx < SHARED_BUFFER_SIZE) { + stringsTmpTmpIdx = 0; + question_switcher(); + + if (state != UI_191_NBGL_GO_TO_SIGN) { + return false; + } + } else if (reached || eip191MessageIdx == SHARED_BUFFER_SIZE) { + eip191MessageIdx = 0; + } + } + + pair.value = staxSharedBuffer; + pair.item = "Message"; + content->type = TAG_VALUE_LIST; + content->tagValueList.nbPairs = 1; + content->tagValueList.pairs = &pair; + content->tagValueList.smallCaseForValue = false; + content->tagValueList.nbMaxLinesForValue = 9; + content->tagValueList.wrapping = false; + + if (state == UI_191_NBGL_BACK_FROM_REJECT_CANCEL) { + // We come back from Reject screen. + // The previously displayed content must be redisplayed. + // Do not call question_switcher() to avoid replacing + // string.tmp.tmp content. + state = state_before_reject_cancel; + } else if (stringsTmpTmpIdx >= strlen(strings.tmp.tmp)) { + // Fetch the next content to display into strings.tmp.tmp buffer. + stringsTmpTmpIdx = 0; + question_switcher(); + return true; + } + } else { + // the last page must contain a long press button + content->type = INFO_LONG_PRESS, content->infoLongPress.icon = &C_Message_64px; + content->infoLongPress.text = "Sign Message?"; + content->infoLongPress.longPressText = "Hold to sign"; + state = UI_191_NBGL_SIGN_DISPLAYED; + } + return true; +} + +static void choice_callback(bool confirm) { + if (confirm) { + nbgl_useCaseStatus("MESSAGE\nSIGNED", true, sign_message); + sign_message(); + } +} + +static void continue_review(void) { + nbgl_useCaseForwardOnlyReview("Reject", NULL, nav_callback, choice_callback); +} + +static void confirm_transaction_rejection_choice(bool confirm) { + if (confirm) { + reject_message(); + } else { + // Go to previous screen accordingly + if (state == UI_191_NBGL_START_REVIEW_DISPLAYED) { + ui_191_start(); + } else { + if (state != UI_191_NBGL_SIGN_DISPLAYED) { + state_before_reject_cancel = state; + state = UI_191_NBGL_BACK_FROM_REJECT_CANCEL; + } + continue_review(); + } + } +} + +static void confirm_transaction_rejection() { + nbgl_useCaseChoice(&C_warning64px, + "Reject message?", + NULL, + "Yes, Reject", + "Go back to message", + confirm_transaction_rejection_choice); +} + +void ui_191_start(void) { + state = UI_191_NBGL_START_REVIEW_DISPLAYED; + eip191MessageIdx = 0; + stringsTmpTmpIdx = 0; + + nbgl_useCaseReviewStart(&C_Message_64px, + "Review message", + NULL, + "Reject", + continue_review, + confirm_transaction_rejection); +} + +void ui_191_switch_to_message(void) { + // No question mechanism on Stax: + // Message is already displayed + state = UI_191_NBGL_GO_TO_NEXT_CONTENT; + continue_review(); +} + +void ui_191_switch_to_sign(void) { + // Next nav_callback callback must display + // the hold to approve screen + state = UI_191_NBGL_GO_TO_SIGN; +} + +void ui_191_switch_to_question(void) { + // No question mechanism on Stax: + // Always display the next message chunk. + continue_displaying_message(); +} \ No newline at end of file diff --git a/src_nbgl/ui_stark_limit_order.c b/src_nbgl/ui_stark_limit_order.c new file mode 100644 index 0000000..20277be --- /dev/null +++ b/src_nbgl/ui_stark_limit_order.c @@ -0,0 +1,67 @@ +#include "common_ui.h" +#include "ui_nbgl.h" +#include "ui_callbacks.h" +#include "nbgl_use_case.h" +#include "network.h" + +#ifdef HAVE_STARKWARE + +static nbgl_layoutTagValue_t tlv[3]; + +static void reviewReject(void) { + io_seproxyhal_touch_tx_cancel(NULL); +} + +static void confirmTransation(void) { + io_seproxyhal_touch_stark_ok(NULL); +} + +static void reviewChoice(bool confirm) { + if (confirm) { + confirmTransation(); + } else { + reviewReject(); + } +} + +static bool displayTransactionPage(uint8_t page, nbgl_pageContent_t *content) { + if (page == 0) { + tlv[0].item = "Sell"; + tlv[0].value = strings.common.fullAmount; + tlv[1].item = "Buy"; + tlv[1].value = strings.common.maxFee; + tlv[2].item = "Token amount"; + tlv[2].value = strings.common.fullAddress; + + content->type = TAG_VALUE_LIST; + content->tagValueList.nbPairs = 3; + content->tagValueList.pairs = (nbgl_layoutTagValue_t *) tlv; + } else if (page == 1) { + content->type = INFO_LONG_PRESS, content->infoLongPress.icon = get_app_chain_icon(); + content->infoLongPress.text = "Review stark limit order"; + content->infoLongPress.longPressText = "Hold to sign"; + } else { + return false; + } + // valid page so return true + return true; +} + +static void reviewContinue(void) { + nbgl_useCaseRegularReview(0, 2, "Reject", NULL, displayTransactionPage, reviewChoice); +} + +static void buildFirstPage(void) { + nbgl_useCaseReviewStart(get_app_chain_icon(), + "Review stark limit order", + NULL, + "Reject", + reviewContinue, + reviewReject); +} + +void ui_stark_limit_order(void) { + buildFirstPage(); +} + +#endif \ No newline at end of file diff --git a/src_nbgl/ui_stark_transfer.c b/src_nbgl/ui_stark_transfer.c new file mode 100644 index 0000000..3532c17 --- /dev/null +++ b/src_nbgl/ui_stark_transfer.c @@ -0,0 +1,127 @@ +#include +#include "shared_context.h" +#include "ui_callbacks.h" +#include "ui_nbgl.h" +#include "starkDisplayUtils.h" +#include "ethUtils.h" +#include "network.h" + +#ifdef HAVE_STARKWARE + +static nbgl_layoutTagValue_t tlv[3]; +static char condAddressBuffer[43]; +struct stark_transfer_context { + bool selfTransfer; + bool conditional; +}; + +static struct stark_transfer_context context; + +static void reviewReject(void) { + io_seproxyhal_touch_tx_cancel(NULL); +} + +static void confirmTransation(void) { + io_seproxyhal_touch_stark_ok(NULL); +} + +static void reviewChoice(bool confirm) { + if (confirm) { + confirmTransation(); + } else { + reviewReject(); + } +} + +static bool displayTransactionPage(uint8_t page, nbgl_pageContent_t *content) { + uint8_t count = 0; + if (page == 0) { + tlv[count].item = "Amount"; + tlv[count].value = tmpContent.tmp; + count++; + + if (context.selfTransfer == false && context.conditional == false) { + tlv[count].item = "Master Account"; + tlv[count].value = strings.tmp.tmp; + count++; + } + if (context.conditional) { + stark_sign_display_master_account(); + tlv[count].item = "Master Account"; + tlv[count].value = strings.tmp.tmp; + count++; + } + tlv[count].item = "Token Account"; + tlv[count].value = strings.tmp.tmp2; + content->type = TAG_VALUE_LIST; + content->tagValueList.nbPairs = count; + content->tagValueList.pairs = (nbgl_layoutTagValue_t *) tlv; + + return true; + } + if (page == 1) { + if (context.conditional) { + getEthDisplayableAddress(dataContext.starkContext.conditionAddress, + condAddressBuffer, + sizeof(condAddressBuffer), + &global_sha3, + chainConfig->chainId), + tlv[0].item = "Cond. Address"; + tlv[0].value = condAddressBuffer; + + stark_sign_display_condition_fact(); + tlv[1].item = "Cond. Address"; + tlv[1].value = strings.tmp.tmp; + + content->type = TAG_VALUE_LIST; + content->tagValueList.nbPairs = 2; + content->tagValueList.pairs = (nbgl_layoutTagValue_t *) tlv; + + } else { + page++; + } + } + if (page == 2) { + content->type = INFO_LONG_PRESS, content->infoLongPress.icon = get_app_chain_icon(); + content->infoLongPress.text = "Review transaction"; + content->infoLongPress.longPressText = "Hold to sign"; + } + + return false; +} + +static void reviewContinue(void) { + nbgl_useCaseRegularReview(0, + context.conditional ? 3 : 2, + "Reject", + NULL, + displayTransactionPage, + reviewChoice); +} + +void ui_stark_transfer(bool selfTransfer, bool conditional) { + context.selfTransfer = selfTransfer; + context.conditional = conditional; + char *subTitle; + if (conditional) { + if (selfTransfer) { + subTitle = "Conditionnal self transfer"; + } else { + subTitle = "Conditionnal transfer"; + } + } else { + if (selfTransfer) { + subTitle = "self transfer"; + } else { + subTitle = "Transfer"; + } + } + nbgl_useCaseReviewStart(get_app_chain_icon(), + "Review stark transaction", + subTitle, + "Reject", + reviewContinue, + reviewReject); +} + +#endif // #ifdef HAVE_STARKWARE diff --git a/src_nbgl/ui_stark_unsafe_sign.c b/src_nbgl/ui_stark_unsafe_sign.c new file mode 100644 index 0000000..59a6eac --- /dev/null +++ b/src_nbgl/ui_stark_unsafe_sign.c @@ -0,0 +1,70 @@ + +#include "common_ui.h" +#include "ui_nbgl.h" +#include "ui_callbacks.h" +#include "nbgl_use_case.h" +#include "network.h" + +#ifdef HAVE_STARKWARE + +static nbgl_layoutTagValue_t tlv[2]; +static char from_account[64]; +static char message_hash[64]; + +static void reviewReject(void) { + io_seproxyhal_touch_tx_cancel(NULL); +} + +static void confirmTransation(void) { + io_seproxyhal_touch_stark_unsafe_sign_ok(NULL); +} + +static void reviewChoice(bool confirm) { + if (confirm) { + confirmTransation(); + } else { + reviewReject(); + } +} + +static bool displayTransactionPage(uint8_t page, nbgl_pageContent_t *content) { + snprintf(from_account, sizeof(from_account), "0x%.*H", 32, dataContext.starkContext.w1); + snprintf(message_hash, sizeof(message_hash), "0x%.*H", 32, dataContext.starkContext.w2); + + if (page == 0) { + tlv[0].item = "From Account"; + tlv[0].value = from_account; + tlv[1].item = "Hash"; + tlv[1].value = message_hash; + content->type = TAG_VALUE_LIST; + content->tagValueList.nbPairs = 2; + content->tagValueList.pairs = (nbgl_layoutTagValue_t *) tlv; + } else if (page == 1) { + content->type = INFO_LONG_PRESS, content->infoLongPress.icon = get_app_chain_icon(); + content->infoLongPress.text = "Unsafe Stark Sign"; + content->infoLongPress.longPressText = "Hold to sign"; + } else { + return false; + } + // valid page so return true + return true; +} + +static void reviewContinue(void) { + nbgl_useCaseRegularReview(0, 2, "Reject", NULL, displayTransactionPage, reviewChoice); +} + +static void buildFirstPage(void) { + nbgl_useCaseReviewStart(get_app_chain_icon(), + "Unsafe Stark Sign", + NULL, + "Reject", + reviewContinue, + reviewReject); +} + +void ui_stark_unsafe_sign(void) { + buildFirstPage(); +} + +#endif // HAVE_STARKWARE \ No newline at end of file diff --git a/src_nbgl/ui_warning_contract_data.c b/src_nbgl/ui_warning_contract_data.c new file mode 100644 index 0000000..2619dce --- /dev/null +++ b/src_nbgl/ui_warning_contract_data.c @@ -0,0 +1,21 @@ +#include +#include "shared_context.h" +#include "ui_callbacks.h" +#include "ui_nbgl.h" + +static void ui_warning_contract_data_choice(bool confirm) { + if (confirm) { + ui_idle(); + } else { + ui_menu_settings(); + } +} + +void ui_warning_contract_data(void) { + nbgl_useCaseChoice(&C_warning64px, + "This message cannot\nbe clear-signed", + "Enable blind-signing in\nthe settings to sign\nthis transaction.", + "Exit", + "Go to settings", + ui_warning_contract_data_choice); +}