diff --git a/Makefile b/Makefile index 7020d17c..3b24de66 100644 --- a/Makefile +++ b/Makefile @@ -54,6 +54,7 @@ include ./makefile_conf/chain/$(CHAIN).mk else $(error Unsupported CHAIN - use $(SUPPORTED_CHAINS)) endif +DEFINES += APPNAME=\"$(APPNAME)\" ######### # Other # @@ -79,7 +80,7 @@ all: default ############ DEFINES += OS_IO_SEPROXYHAL -DEFINES += HAVE_BAGL HAVE_SPRINTF HAVE_SNPRINTF_FORMAT_U +DEFINES += HAVE_SPRINTF HAVE_SNPRINTF_FORMAT_U DEFINES += HAVE_IO_USB HAVE_L4_USBLIB IO_USB_MAX_ENDPOINTS=4 IO_HID_EP_LENGTH=64 HAVE_USB_APDU DEFINES += LEDGER_MAJOR_VERSION=$(APPVERSION_M) LEDGER_MINOR_VERSION=$(APPVERSION_N) LEDGER_PATCH_VERSION=$(APPVERSION_P) @@ -97,23 +98,33 @@ DEFINES += HAVE_UX_FLOW DEFINES += HAVE_WEBUSB WEBUSB_URL_SIZE_B=0 WEBUSB_URL="" -ifeq ($(TARGET_NAME),TARGET_NANOX) +ifneq (,$(filter $(TARGET_NAME),TARGET_NANOX TARGET_FATSTACKS)) +DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=300 DEFINES += HAVE_BLE BLE_COMMAND_TIMEOUT_MS=2000 DEFINES += HAVE_BLE_APDU # basic ledger apdu transport over BLE +SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl endif -ifeq ($(TARGET_NAME),TARGET_NANOS) +ifneq (,$(filter $(TARGET_NAME),TARGET_NANOS TARGET_NANOS2)) DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=72 -DEFINES += HAVE_WALLET_ID_SDK +endif + +ifeq ($(TARGET_NAME),TARGET_FATSTACKS) +DEFINES += HAVE_NBGL +else +DEFINES += HAVE_BAGL +ifeq ($(TARGET_NAME),TARGET_NANOS) +DEFINES += HAVE_WALLET_ID_SDK +DEFINES += BAGL_WIDTH=128 BAGL_HEIGHT=32 else -DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=300 DEFINES += HAVE_GLO096 -DEFINES += HAVE_BAGL BAGL_WIDTH=128 BAGL_HEIGHT=64 +DEFINES += BAGL_WIDTH=128 BAGL_HEIGHT=64 DEFINES += HAVE_BAGL_ELLIPSIS # long label truncation feature DEFINES += HAVE_BAGL_FONT_OPEN_SANS_REGULAR_11PX DEFINES += HAVE_BAGL_FONT_OPEN_SANS_EXTRABOLD_11PX DEFINES += HAVE_BAGL_FONT_OPEN_SANS_LIGHT_16PX endif +endif # Enables direct data signing without having to specify it in the settings. Useful when testing with speculos. ALLOW_DATA:=0 @@ -210,11 +221,13 @@ include $(BOLOS_SDK)/Makefile.glyphs ### variables processed by the common makefile.rules of the SDK to grab source files and include dirs APP_SOURCE_PATH += src_common src src_features src_plugins SDK_SOURCE_PATH += lib_stusb lib_stusb_impl lib_u2f +ifeq ($(TARGET_NAME),TARGET_FATSTACKS) +SDK_SOURCE_PATH += lib_ux_fatstacks +APP_SOURCE_PATH += src_nbgl +else SDK_SOURCE_PATH += lib_ux -ifeq ($(TARGET_NAME),TARGET_NANOX) -SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl -endif APP_SOURCE_PATH += src_bagl +endif ### initialize plugin SDK submodule if needed, rebuild it, and warn if a difference is noticed ifeq ($(CHAIN),ethereum) diff --git a/glyphs/badge_transaction_56.gif b/glyphs/badge_transaction_56.gif new file mode 100644 index 00000000..feb443f7 Binary files /dev/null and b/glyphs/badge_transaction_56.gif differ diff --git a/src/common_ui.h b/src/common_ui.h index 9b2b0dc8..e14f57ba 100644 --- a/src/common_ui.h +++ b/src/common_ui.h @@ -9,7 +9,6 @@ void ui_display_public_eth2(void); void ui_display_privacy_public_key(void); void ui_display_privacy_shared_secret(void); void ui_display_public_key(void); -void ui_display_sign(void); void ui_sign_712_v0(void); void ui_display_stark_public(void); void ui_confirm_selector(void); diff --git a/src/handle_swap_sign_transaction.c b/src/handle_swap_sign_transaction.c index 75e4d3a8..23b4a39a 100644 --- a/src/handle_swap_sign_transaction.c +++ b/src/handle_swap_sign_transaction.c @@ -65,7 +65,12 @@ void handle_swap_sign_transaction(chain_config_t* config) { nvm_write((void*) &N_storage, (void*) &storage, sizeof(internalStorage_t)); } - UX_INIT(); +#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 be755e55..3a35c516 100644 --- a/src/main.c +++ b/src/main.c @@ -61,6 +61,8 @@ uint32_t eth2WithdrawalIndex; #include "withdrawal_index.h" #endif +uint16_t G_ticks; + #include "ux.h" ux_state_t G_ux; bolos_ux_params_t G_ux_params; @@ -854,9 +856,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 @@ -867,33 +871,35 @@ 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 && - !(U4BE(G_io_seproxyhal_spi_buffer, 3) & - SEPROXYHAL_TAG_STATUS_EVENT_FLAG_USB_POWERED)) { - THROW(EXCEPTION_IO_RESET); - } + // if (G_io_apdu_media == IO_APDU_MEDIA_USB_HID && + // !(U4BE(G_io_seproxyhal_spi_buffer, 3) & + // SEPROXYHAL_TAG_STATUS_EVENT_FLAG_USB_POWERED)) { + // THROW(EXCEPTION_IO_RESET); + // } // no break is intentional default: UX_DEFAULT_EVENT(); break; case SEPROXYHAL_TAG_DISPLAY_PROCESSED_EVENT: +#ifdef HAVE_BAGL UX_DISPLAYED_EVENT({}); +#endif // HAVE_BAGL break; -#if 0 case SEPROXYHAL_TAG_TICKER_EVENT: + G_ticks++; UX_TICKER_EVENT(G_io_seproxyhal_spi_buffer, { }); break; -#endif } // close the event if not done previously (by a display or whatever) @@ -935,7 +941,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 2035c27a..041f7173 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 00000000..6a4a418e --- /dev/null +++ b/src_nbgl/ui_approve_tx.c @@ -0,0 +1,188 @@ + +#include +#include "shared_context.h" +#include "ui_callbacks.h" +#include "ui_nbgl.h" +#include "network.h" +#include "plugins.h" + +#define MAX_PLUGIN_ITEMS_PER_SCREEN 3 + +static nbgl_layoutTagValue_t tlv[MAX_PLUGIN_ITEMS_PER_SCREEN]; +static char title_buffer[MAX_PLUGIN_ITEMS_PER_SCREEN][43]; +static char msg_buffer[MAX_PLUGIN_ITEMS_PER_SCREEN][79]; + +static uint8_t pageCount = 0; +struct tx_approval_context_t { + bool fromPlugin; + bool blindSigning; + bool displayNetwork; +}; + +static struct tx_approval_context_t tx_approval_context; + + +static void reviewReject(void) { + io_seproxyhal_touch_tx_cancel(NULL); + ui_idle(); +} + +static void confirmTransation(void) { + io_seproxyhal_touch_tx_ok(NULL); + ui_idle(); +} + +static void reviewChoice(bool confirm) { + if (confirm) { + confirmTransation(); + } else { + reviewReject(); + } +} + +static void buildBlindSignWarningPage(nbgl_pageContent_t *content) { + content->type = CENTERED_INFO; + content->centeredInfo.icon = &C_icon_warning; + content->centeredInfo.text1 = "Blind Signing"; + content->centeredInfo.style = LARGE_CASE_INFO; +} + +static void buildRegularTransactionInfoAmountPage(nbgl_pageContent_t *content) { + tlv[0].item = "Amount"; + tlv[1].item = "Address"; + tlv[2].item = "Nonce"; + + tlv[0].value = strings.common.fullAmount; + tlv[1].value = strings.common.fullAddress; + tlv[2].value = strings.common.nonce; + + content->type = TAG_VALUE_LIST; + content->tagValueList.nbPairs = N_storage.displayNonce ? 3 : 2; + content->tagValueList.pairs = (nbgl_layoutTagValue_t *)tlv; +} + +static void buildRegularTransactionInfoAddressPage(nbgl_pageContent_t *content) { + tlv[0].item = "Max fees"; + tlv[1].item = "Network"; + + tlv[0].value = strings.common.maxFee; + tlv[1].value = strings.common.network_name; + + content->type = TAG_VALUE_LIST; + content->tagValueList.nbPairs = tx_approval_context.displayNetwork ? 2 : 1; + content->tagValueList.pairs = (nbgl_layoutTagValue_t *)tlv; +} + +static void buildRegularTransactionInfoConfirm(nbgl_pageContent_t *content) { + content->type = INFO_LONG_PRESS, + content->infoLongPress.icon = &C_badge_transaction_56; + content->infoLongPress.text = "Review transaction"; + content->infoLongPress.longPressText = "Hold to confirm"; +} + +static bool displayTransactionPage(uint8_t page, nbgl_pageContent_t *content) { + if (tx_approval_context.blindSigning) { + if (page == 0) { + buildBlindSignWarningPage(content); + return true; + } + } else { + page++; + } + + if (tx_approval_context.fromPlugin) { + if (page == 1) { + plugin_ui_get_id(); + tlv[0].item = strings.common.fullAddress; + tlv[0].value = strings.common.fullAmount; + + content->type = TAG_VALUE_LIST; + content->tagValueList.nbPairs = 1; + content->tagValueList.pairs = (nbgl_layoutTagValue_t *)tlv; + + return true; + } + else if (page < pageCount - 1) { + uint8_t count = 0; + for ( + dataContext.tokenContext.pluginUiCurrentItem = (page - 2) * MAX_PLUGIN_ITEMS_PER_SCREEN; + dataContext.tokenContext.pluginUiCurrentItem < (page - 1) * MAX_PLUGIN_ITEMS_PER_SCREEN && dataContext.tokenContext.pluginUiCurrentItem < dataContext.tokenContext.pluginUiMaxItems; + dataContext.tokenContext.pluginUiCurrentItem++, count++ + ) { + plugin_ui_get_item_internal( + title_buffer[count], + sizeof(title_buffer[0]), + msg_buffer[count], + sizeof(msg_buffer[0]) + ); + tlv[count].item = title_buffer[count]; + tlv[count].value = msg_buffer[count]; + } + content->type = TAG_VALUE_LIST; + content->tagValueList.pairs = (nbgl_layoutTagValue_t *)tlv; + content->tagValueList.nbPairs = count; + + return true; + } + else if (page == pageCount - 1) { + buildRegularTransactionInfoAddressPage(content); + return true; + } + else + { + buildRegularTransactionInfoConfirm(content); + return true; + } + } else { + if (page == 1) { + buildRegularTransactionInfoAmountPage(content); + return true; + } + if (page == 2) { + buildRegularTransactionInfoAddressPage(content); + return true; + } + if (page == 3) { + buildRegularTransactionInfoConfirm(content); + return true; + } + } + return false; +} + +static void reviewContinue(void) { + pageCount = 0; + + if (tx_approval_context.blindSigning) { + pageCount++; + } + + if (tx_approval_context.fromPlugin) { + // plugin id + max items + fees + confirm + pageCount += 1 + (dataContext.tokenContext.pluginUiMaxItems/MAX_PLUGIN_ITEMS_PER_SCREEN) + 1 + 1; + if (dataContext.tokenContext.pluginUiMaxItems%MAX_PLUGIN_ITEMS_PER_SCREEN) { + pageCount++; + } + } else { + pageCount += 3; + } + nbgl_useCaseRegularReview(0, pageCount, "Reject", NULL, displayTransactionPage, reviewChoice); +} + + +static void buildFirstPage(void) { + nbgl_useCaseReviewStart(&C_badge_transaction_56, "Review transaction", NULL, "Reject", reviewContinue, reviewReject); +} + +void ux_approve_tx(bool fromPlugin) { + tx_approval_context.blindSigning = !fromPlugin && tmpContent.txContent.dataPresent && !N_storage.dataAllowed; + tx_approval_context.fromPlugin = fromPlugin; + tx_approval_context.displayNetwork = false; + + uint64_t chain_id = get_chain_id(); + if (chainConfig->chainId == ETHEREUM_MAINNET_CHAINID && chain_id != chainConfig->chainId) { + tx_approval_context.displayNetwork = true; + } + + buildFirstPage(); +} \ No newline at end of file diff --git a/src_nbgl/ui_confirm_parameter.c b/src_nbgl/ui_confirm_parameter.c new file mode 100644 index 00000000..c23e0b6a --- /dev/null +++ b/src_nbgl/ui_confirm_parameter.c @@ -0,0 +1,55 @@ +#include "common_ui.h" +#include "ui_nbgl.h" + +static nbgl_layoutTagValue_t tlv; + +static void reviewReject(void) { + io_seproxyhal_touch_data_cancel(NULL); + ui_idle(); +} + +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 = &C_badge_transaction_56; + 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(&C_badge_transaction_56, "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 00000000..4a3907c7 --- /dev/null +++ b/src_nbgl/ui_confirm_selector.c @@ -0,0 +1,55 @@ +#include "common_ui.h" +#include "ui_nbgl.h" + +static nbgl_layoutTagValue_t tlv; + +static void reviewReject(void) { + io_seproxyhal_touch_data_cancel(NULL); + ui_idle(); +} + +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 = &C_badge_transaction_56; + 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(&C_badge_transaction_56, "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 00000000..ad7adfac --- /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" + +static nbgl_layoutTagValue_t tlv[2]; +static char* review_string; + +static void reviewReject(void) { + io_seproxyhal_touch_privacy_cancel(NULL); + ui_idle(); +} + +static void confirmTransation(void) { + io_seproxyhal_touch_privacy_ok(NULL); + ui_idle(); +} + +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 = &C_badge_transaction_56; + 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(&C_badge_transaction_56, 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 00000000..e83e29dc --- /dev/null +++ b/src_nbgl/ui_get_eth2_public_key.c @@ -0,0 +1,37 @@ +#include +#include "shared_context.h" +#include "ui_callbacks.h" +#include "ui_nbgl.h" + +static void reviewReject(void) { + io_seproxyhal_touch_address_cancel(NULL); + ui_idle(); +} + +static void confirmTransation(void) { + io_seproxyhal_touch_address_ok(NULL); + ui_idle(); +} + +static void reviewChoice(bool confirm) { + if (confirm) { + confirmTransation(); + } else { + reviewReject(); + } +} + +static void buildScreen(void) { + snprintf(strings.tmp.tmp, 100, "0x%.*H", 48, tmpCtx.publicKeyContext.publicKey.W); + nbgl_useCaseChoice( + "Export ETH2 public key?", + "Allow the Ethereum app\nto export your ETH2\npublic key.", + "Allow", + "Don't allow", + 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 00000000..b15bfed2 --- /dev/null +++ b/src_nbgl/ui_get_public_key.c @@ -0,0 +1,37 @@ +#include +#include "shared_context.h" +#include "ui_callbacks.h" +#include "ui_nbgl.h" + +static void reviewReject(void) { + io_seproxyhal_touch_address_cancel(NULL); + ui_idle(); +} + +static void confirmTransation(void) { + io_seproxyhal_touch_address_ok(NULL); + ui_idle(); +} + +static void reviewChoice(bool confirm) { + if (confirm) { + confirmTransation(); + } else { + reviewReject(); + } +} + +static void buildScreen(void) { + snprintf(strings.tmp.tmp, 100, "0x%.*H", 48, tmpCtx.publicKeyContext.publicKey.W); + nbgl_useCaseChoice( + "Export ETH public key?", + "Allow the Ethereum app\nto export your\npublic key.", + "Allow", + "Don't allow", + 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 00000000..c0e683a4 --- /dev/null +++ b/src_nbgl/ui_get_stark_public_key.c @@ -0,0 +1,36 @@ +#include +#include "shared_context.h" +#include "ui_callbacks.h" +#include "ui_nbgl.h" + +static void reviewReject(void) { + io_seproxyhal_touch_address_cancel(NULL); + ui_idle(); +} + +static void confirmTransation(void) { + io_seproxyhal_touch_stark_pubkey_ok(NULL); + ui_idle(); +} + +static void reviewChoice(bool confirm) { + if (confirm) { + confirmTransation(); + } else { + reviewReject(); + } +} + +static void buildScreen(void) { + nbgl_useCaseChoice( + "Verify stark key", + strings.tmp.tmp, + "Approuve", + "Reject", + 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 00000000..2f6fcffd --- /dev/null +++ b/src_nbgl/ui_idle.c @@ -0,0 +1,27 @@ +#include "common_ui.h" +#include "ui_nbgl.h" +#include "nbgl_use_case.h" + +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) { + nbgl_useCaseHome(APPNAME, + NULL, + APPNAME, + true, + ui_menu_settings, + app_quit); +} \ No newline at end of file diff --git a/src_nbgl/ui_nbgl.h b/src_nbgl/ui_nbgl.h new file mode 100644 index 00000000..3a9a3eac --- /dev/null +++ b/src_nbgl/ui_nbgl.h @@ -0,0 +1,15 @@ +#ifndef _UI_NBGL_H_ +#define _UI_NBGL_H_ + +#include +#include + +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 00000000..a6ef4941 --- /dev/null +++ b/src_nbgl/ui_settings.c @@ -0,0 +1,83 @@ +#include "common_ui.h" +#include "ui_nbgl.h" +#include "nbgl_use_case.h" + +static const char* const infoTypes[] = {"Version", "Ethereum App"}; +static const char* const infoContents[] = {APPVERSION, "(c) 2022 Ledger"}; + +enum { + BLIND_SIGNING_TOKEN = FIRST_USER_TOKEN, + DEBUG_TOKEN, + NONCE_TOKEN +}; + +static nbgl_layoutSwitch_t switches[3]; + +static bool navCallback(uint8_t page, nbgl_pageContent_t* content) { + switches[0] = (nbgl_layoutSwitch_t) { + .initState = N_storage.dataAllowed ? ON_STATE : OFF_STATE, + .text = "Blind signing", + .subText = "Enable transaction blind\nsigning", + .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 + }; + + switch (page) + { + case 0: + content->type = SWITCHES_LIST; + content->switchesList.nbSwitches = 3; + content->switchesList.switches = (nbgl_layoutSwitch_t*)switches; + break; + + case 1: + 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; + } +} + +void ui_menu_settings(void) { + nbgl_useCaseSettings("Ethereum settings", 0, 2, 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 00000000..8cce4270 --- /dev/null +++ b/src_nbgl/ui_sign_712.c @@ -0,0 +1,6 @@ +#include "common_ui.h" +#include "ui_nbgl.h" + +void ui_712_start(void) {} +void ui_712_switch_to_message(void) {} +void ui_712_switch_to_sign(void) {} diff --git a/src_nbgl/ui_sign_712_v0.c b/src_nbgl/ui_sign_712_v0.c new file mode 100644 index 00000000..25a5049f --- /dev/null +++ b/src_nbgl/ui_sign_712_v0.c @@ -0,0 +1,65 @@ +#include "common_ui.h" +#include "ui_nbgl.h" +#include "common_712.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); + ui_idle(); +} + +static void confirmTransation(void) { + ui_712_reject_cb(NULL); + ui_idle(); +} + +static void reviewChoice(bool confirm) { + if (confirm) { + confirmTransation(); + } else { + 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 = &C_badge_transaction_56; + content->infoLongPress.text = "Sign typed message"; + content->infoLongPress.longPressText = "Hold to approuve"; + } + 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(&C_badge_transaction_56, "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 00000000..731820af --- /dev/null +++ b/src_nbgl/ui_sign_message.c @@ -0,0 +1,136 @@ +#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; +#define MAX_CHARS_PER_PAGE 128+1 +static char eip191Message[MAX_CHARS_PER_PAGE] = {0}; +static uint32_t eip191MessageIdx = 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(eip191Message, 0, sizeof(eip191Message)); + memcpy(eip191Message, strings.tmp.tmp + eip191MessageIdx, MAX_CHARS_PER_PAGE - 1); + eip191MessageIdx += MAX_CHARS_PER_PAGE - 1; + } + + pair.value = eip191Message; + 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 = true; + + 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 (eip191MessageIdx >= strlen(strings.tmp.tmp)) { + // Fetch the next content to display into strings.tmp.tmp buffer. + question_switcher(); + eip191MessageIdx = 0; + } + } else { + eip191MessageIdx = 0; + // the last page must contain a long press button + content->type = INFO_LONG_PRESS, content->infoLongPress.icon = NULL; + 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("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; + 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; +} + +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 00000000..ce2f79ac --- /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" + +static nbgl_layoutTagValue_t tlv[3]; + +#ifdef HAVE_STARKWARE + +static void reviewReject(void) { + io_seproxyhal_touch_tx_cancel(NULL); + ui_idle(); +} + +static void confirmTransation(void) { + io_seproxyhal_touch_stark_ok(NULL); + ui_idle(); +} + +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 = &C_badge_transaction_56; + content->infoLongPress.text = "Review stark limit order"; + 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", NULL, displayTransactionPage, reviewChoice); +} + + +static void buildFirstPage(void) { + nbgl_useCaseReviewStart(&C_badge_transaction_56, "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 00000000..1ba9ab95 --- /dev/null +++ b/src_nbgl/ui_stark_transfer.c @@ -0,0 +1,120 @@ +#include +#include "shared_context.h" +#include "ui_callbacks.h" +#include "ui_nbgl.h" +#include "starkDisplayUtils.h" +#include "ethUtils.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); + ui_idle(); +} + +static void confirmTransation(void) { + io_seproxyhal_touch_stark_ok(NULL); + ui_idle(); +} + +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 = &C_badge_transaction_56; + content->infoLongPress.text = "Review transaction"; + content->infoLongPress.longPressText = "Hold to confirm"; + } + + 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(&C_badge_transaction_56, "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 00000000..9ac67799 --- /dev/null +++ b/src_nbgl/ui_stark_unsafe_sign.c @@ -0,0 +1,69 @@ + +#include "common_ui.h" +#include "ui_nbgl.h" +#include "ui_callbacks.h" +#include "nbgl_use_case.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); + ui_idle(); +} + +static void confirmTransation(void) { + io_seproxyhal_touch_stark_unsafe_sign_ok(NULL); + ui_idle(); +} + +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 = &C_badge_transaction_56; + content->infoLongPress.text = "Unsafe Stark Sign"; + 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", NULL, displayTransactionPage, reviewChoice); +} + +static void buildFirstPage(void) { + nbgl_useCaseReviewStart(&C_badge_transaction_56,"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 00000000..dd550981 --- /dev/null +++ b/src_nbgl/ui_warning_contract_data.c @@ -0,0 +1,38 @@ +#include +#include "shared_context.h" +#include "ui_callbacks.h" +#include "ui_nbgl.h" + +enum { OK_TOKEN }; + +static void pageTouchCallback(int token, uint8_t index) { + (void) index; + releaseContext(); + + if (token == OK_TOKEN) { + ui_idle(); + } +} + +void ui_warning_contract_data(void) { + releaseContext(); + nbgl_pageInfoDescription_t info = { + .centeredInfo = { + .text1 = "Error", + .text2 = "Blind signing must be\nenabled in Settings", + .text3 = NULL, + .style = LARGE_CASE_INFO, + .offsetY = 32 + }, + .footerText = NULL, + .bottomButtonStyle = QUIT_ICON, + .bottomButtonToken = OK_TOKEN, + .tapActionText = NULL, + .topRightStyle = NO_BUTTON_STYLE, + .topRightToken = 0, + .tuneId = TUNE_TAP_CASUAL + }; + + pageContext = nbgl_pageDrawInfo(pageTouchCallback, NULL, &info); + nbgl_refresh(); +}