diff --git a/lib_nbgl/include/nbgl_content.h b/lib_nbgl/include/nbgl_content.h index 1fa0fefe..c7c41def 100644 --- a/lib_nbgl/include/nbgl_content.h +++ b/lib_nbgl/include/nbgl_content.h @@ -140,7 +140,8 @@ typedef enum { NO_ALIAS_TYPE = 0, ENS_ALIAS, ///< alias comes from ENS ADDRESS_BOOK_ALIAS, ///< alias comes from Address Book - QR_CODE_ALIAS ///< alias is an address to be displayed as a QR Code + QR_CODE_ALIAS, ///< alias is an address to be displayed as a QR Code + INFO_LIST_ALIAS ///< alias is list of infos } nbgl_contentValueAliasType_t; /** @@ -153,7 +154,8 @@ typedef struct { ///< only used if aliasType is @ref NO_ALIAS_TYPE const char *title; ///< if not NULL and aliasType is @ref QR_CODE_ALIAS, is used as title of ///< the QR Code - nbgl_contentValueAliasType_t aliasType; ///< type of alias + const struct nbgl_contentInfoList_s *infolist; ///< if aliasType is INFO_LIST_ALIAS + nbgl_contentValueAliasType_t aliasType; ///< type of alias } nbgl_contentValueExt_t; /** @@ -172,8 +174,8 @@ typedef struct { int8_t forcePageStart : 1; ///< if set to 1, the tag will be displayed at the top of a new ///< review page int8_t centeredInfo : 1; ///< if set to 1, the tag will be displayed as a centered info - int8_t aliasValue : 1; ///< if set to 1, the value represents an alias and an icon enables to - ///< view the full value + int8_t aliasValue : 1; ///< if set to 1, the value represents an alias and an > icon enables to + ///< view the full value (extension field in union) #endif } nbgl_contentTagValue_t; @@ -279,7 +281,7 @@ typedef struct nbgl_pageSwitchesList_s { /** * @brief This structure contains data to build a @ref INFOS_LIST content */ -typedef struct { +typedef struct nbgl_contentInfoList_s { const char *const *infoTypes; ///< array of types of infos (in black/bold) const char *const *infoContents; ///< array of contents of infos (in black) const nbgl_contentValueExt_t diff --git a/lib_nbgl/include/nbgl_use_case.h b/lib_nbgl/include/nbgl_use_case.h index 4975579a..7c943134 100644 --- a/lib_nbgl/include/nbgl_use_case.h +++ b/lib_nbgl/include/nbgl_use_case.h @@ -184,7 +184,8 @@ typedef struct { const char *modalTitle; ///< title given to modal window displayed when tip-box is touched nbgl_contentType_t type; ///< type of page content in the following union union { - nbgl_contentInfoList_t infos; ///< infos pairs displayed in modal. + nbgl_contentInfoList_t + infos; ///< infos pairs displayed in modal, if type is @ref INFOS_LIST. }; } nbgl_tipBox_t; @@ -235,10 +236,11 @@ typedef struct nbgl_warningDetails_s { * */ typedef enum { - BLIND_SIGNING_WARN = 0, ///< Blind signing - W3C_ISSUE_WARN, ///< Web3 Checks issue - W3C_LOSING_SWAP_WARN, ///< Web3 Checks: Losing Swap risk - W3C_THREAT_DETECTED_WARN, ///< Web3 Checks: Thread detexted, malicious (know drainer) + W3C_ISSUE_WARN = 0, ///< Web3 Checks issue (not available) + W3C_RISK_DETECTED_WARN, ///< Web3 Checks: Risk detected (see reportRisk field) + W3C_THREAT_DETECTED_WARN, ///< Web3 Checks: Threat detected (see reportRisk field) + W3C_NO_THREAT_WARN, ///< Web3 Checks: No Threat detected + BLIND_SIGNING_WARN, ///< Blind signing NB_WARNING_TYPES } nbgl_warningType_t; @@ -252,10 +254,11 @@ typedef enum { * */ typedef struct { - uint32_t predefinedSet; ///< bitfield of pre-defined warnings, to be taken in @ref - ///< nbgl_warningType_t set it to 0 if not using pre-defined warnings + uint32_t predefinedSet; ///< bitfield of pre-defined warnings, to be taken in @ref + ///< nbgl_warningType_t set it to 0 if not using pre-defined warnings + const char *dAppProvider; ///< name of the dApp provider, used in some strings + const char *reportUrl; ///< URL of the report, used in some strings const char *reportProvider; ///< name of the security report provider, used in some strings - const char *reportUrl; ///< URL of the report, used in some strings const char *providerMessage; ///< Dedicated provider message. Default one will be used if NULL const nbgl_warningDetails_t *introDetails; ///< details displayed when top-right button is touched in intro page diff --git a/lib_nbgl/src/nbgl_layout.c b/lib_nbgl/src/nbgl_layout.c index edcfdf23..31500c08 100644 --- a/lib_nbgl/src/nbgl_layout.c +++ b/lib_nbgl/src/nbgl_layout.c @@ -299,7 +299,7 @@ static void touchCallback(nbgl_obj_t *obj, nbgl_touchType_t eventType) return; } LOG_DEBUG(LAYOUT_LOGGER, "touchCallback(): layout->callback = %p\n", layout->callback); - if (layout->callback != NULL) { + if ((layout->callback != NULL) && (layoutObj->token != NBGL_INVALID_TOKEN)) { #ifdef HAVE_PIEZO_SOUND if (layoutObj->tuneId < NBGL_NO_TUNE) { io_seproxyhal_play_tune(layoutObj->tuneId); diff --git a/lib_nbgl/src/nbgl_page.c b/lib_nbgl/src/nbgl_page.c index f3799e5e..32115438 100644 --- a/lib_nbgl/src/nbgl_page.c +++ b/lib_nbgl/src/nbgl_page.c @@ -93,7 +93,7 @@ static void addContent(nbgl_pageContent_t *content, break; case EXTENDED_CENTER: - if ((!headerAdded) && (content->extendedCenter.tipBox.text == NULL)) { + if (!headerAdded) { addEmptyHeader(layout, SMALL_CENTERING_HEADER); } nbgl_layoutAddContentCenter(layout, &content->extendedCenter.contentCenter); diff --git a/lib_nbgl/src/nbgl_use_case.c b/lib_nbgl/src/nbgl_use_case.c index 413ede95..5aaad5d8 100644 --- a/lib_nbgl/src/nbgl_use_case.c +++ b/lib_nbgl/src/nbgl_use_case.c @@ -61,6 +61,13 @@ */ #define RISKY_OPERATION (1 << 6) +/** + * @brief This is to use in @ref nbgl_operationType_t when the operation is concerned by an internal + * information. This is used to indicate an info with a top-right button in review first & last page + * + */ +#define NO_THREAT_OPERATION (1 << 7) + /********************** * TYPEDEFS **********************/ @@ -79,9 +86,11 @@ enum { REJECT_TOKEN, VALUE_ALIAS_TOKEN, INFO_ALIAS_TOKEN, + INFOS_TIP_BOX_TOKEN, BLIND_WARNING_TOKEN, WARNING_BUTTON_TOKEN, TIP_BOX_TOKEN, + QUIT_TIPBOX_MODAL_TOKEN, WARNING_CHOICE_TOKEN, DISMISS_QR_TOKEN, DISMISS_WARNING_TOKEN, @@ -121,7 +130,7 @@ typedef struct KeypadContext_s { } KeypadContext_t; #endif -typedef struct BlindSigningContext_s { +typedef struct ReviewWithWarningContext_s { bool isStreaming; nbgl_operationType_t operationType; const nbgl_contentTagValueList_t *tagValueList; @@ -129,7 +138,6 @@ typedef struct BlindSigningContext_s { const char *reviewTitle; const char *reviewSubTitle; const char *finishTitle; - const nbgl_tipBox_t *tipBox; const nbgl_warning_t *warning; nbgl_choiceCallback_t choiceCallback; nbgl_layout_t *layoutCtx; @@ -164,12 +172,14 @@ typedef struct { const char *detailsItem; const char *detailsvalue; bool detailsWrapping; + bool validWarningCtx; // set to true if the WarningContext is valid const nbgl_contentTagValue_t *currentPairs; // to be used to retrieve the pairs with value alias nbgl_contentTagValueCallback_t currentCallback; // to be used to retrieve the pairs with value alias nbgl_layout_t modalLayout; nbgl_layout_t backgroundLayout; + const nbgl_contentInfoList_t *currentInfos; } GenericContext_t; typedef struct { @@ -235,8 +245,7 @@ static nbgl_page_t *modalPageContext; static const char *pageTitle; // context for tip-box -static const char *tipBoxModalTitle; -static nbgl_contentInfoList_t tipBoxInfoList; +static nbgl_tipBox_t activeTipBox; // context for navigation use case static nbgl_pageNavigationInfo_t navInfo; @@ -276,19 +285,23 @@ static const uint8_t nbMaxElementsPerContentType[] = { }; static const SecurityReportItem_t securityReportItems[NB_WARNING_TYPES] = { - [BLIND_SIGNING_WARN] = {.icon = &WARNING_ICON, - .text = "Blind signing", - .subText = "This transaction cannot be fully decoded." }, - [W3C_ISSUE_WARN] = {.icon = &ROUND_WARN_ICON, - .text = "Web3 Checks issue", - .subText = "Web3 Checks could not verify this transaction." }, - [W3C_LOSING_SWAP_WARN] = {.icon = &ROUND_WARN_ICON, - .text = "Risk detected", - .subText = "This transaction was scanned as risky by Web3 Checks." }, + [BLIND_SIGNING_WARN] = {.icon = &WARNING_ICON, + .text = "Blind signing required", + .subText = "This transaction's details are not fully verifiable. If " + "you sign, you could lose all your assets." }, + [W3C_ISSUE_WARN] + = {.icon = &WARNING_ICON, .text = "Transaction Check not available", .subText = NULL}, + [W3C_RISK_DETECTED_WARN] = {.icon = &ROUND_WARN_ICON, + .text = "Risk detected", + .subText = "This transaction was scanned as risky by Web3 Checks." }, [W3C_THREAT_DETECTED_WARN] - = {.icon = &ROUND_WARN_ICON, - .text = "Threat detected", - .subText = "This transaction was scanned as malicious by Web3 Checks."} + = {.icon = &WARNING_ICON, + .text = "Critical threat", + .subText = "This transaction was scanned as malicious by Web3 Checks." }, + [W3C_NO_THREAT_WARN] = {.icon = NULL, + .text = "No threat detected", + .subText = "Transaction Check didn't find any threat, but always " + "review transaction details carefully." } }; // configuration of warning when using @ref nbgl_useCaseReviewBlindSigning() @@ -307,7 +320,9 @@ static void displayDetailsPage(uint8_t page, bool forceFullRefresh); static void displayFullValuePage(const char *backText, const char *aliasText, const nbgl_contentValueExt_t *extension); -static void displayTipBoxModal(void); +static void displayInfosListModal(const char *modalTitle, + const nbgl_contentInfoList_t *infos, + bool fromReview); static void displaySettingsPage(uint8_t page, bool forceFullRefresh); static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh); static void pageCallback(int token, uint8_t index); @@ -334,7 +349,8 @@ static void useCaseReview(nbgl_operationType_t operationType, const nbgl_tipBox_t *tipBox, nbgl_choiceCallback_t choiceCallback, bool isLight, - bool playNotifSound); + bool playNotifSound, + bool validWarningCtx); static void useCaseReviewStreamingStart(nbgl_operationType_t operationType, const nbgl_icon_details_t *icon, const char *reviewTitle, @@ -501,11 +517,18 @@ static void pageModalCallback(int token, uint8_t index) { LOG_DEBUG(USE_CASE_LOGGER, "pageModalCallback, token = %d, index = %d\n", token, index); - if (token == INFO_ALIAS_TOKEN) { + if (token == INFOS_TIP_BOX_TOKEN) { // the icon next to info type alias has been touched - displayFullValuePage(tipBoxInfoList.infoTypes[index], - tipBoxInfoList.infoContents[index], - &tipBoxInfoList.infoExtensions[index]); + displayFullValuePage(activeTipBox.infos.infoTypes[index], + activeTipBox.infos.infoContents[index], + &activeTipBox.infos.infoExtensions[index]); + return; + } + else if (token == INFO_ALIAS_TOKEN) { + // the icon next to info type alias has been touched, but coming from review + displayFullValuePage(genericContext.currentInfos->infoTypes[index], + genericContext.currentInfos->infoContents[index], + &genericContext.currentInfos->infoExtensions[index]); return; } nbgl_pageRelease(modalPageContext); @@ -520,11 +543,23 @@ static void pageModalCallback(int token, uint8_t index) displayDetailsPage(index, false); } } - if (token == QUIT_TOKEN) { + else if (token == QUIT_TOKEN) { // redraw the background layer nbgl_screenRedraw(); nbgl_refresh(); } + else if (token == QUIT_TIPBOX_MODAL_TOKEN) { + // if in "warning context", go back to security report + if (reviewWithWarnCtx.securityReportLevel == 2) { + reviewWithWarnCtx.securityReportLevel = 1; + displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet); + } + else { + // redraw the background layer + nbgl_screenRedraw(); + nbgl_refresh(); + } + } else if (token == SKIP_TOKEN) { if (index == 0) { // display the last forward only review page, whatever it is @@ -669,7 +704,14 @@ static void pageCallback(int token, uint8_t index) } } else if (token == TIP_BOX_TOKEN) { - displayTipBoxModal(); + // if warning context is valid and if W3C directly display same as top-right + if (genericContext.validWarningCtx && (reviewWithWarnCtx.warning->predefinedSet != 0)) { + reviewWithWarnCtx.securityReportLevel = 1; + displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet); + } + else { + displayInfosListModal(activeTipBox.modalTitle, &activeTipBox.infos, false); + } } else { // probably a control provided by caller if (onContentAction != NULL) { @@ -1049,11 +1091,12 @@ static bool genericContextPreparePageContent(const nbgl_content_t *p_content, : ((navType == GENERIC_NAV) ? bundleNavContext.review.operationType : 0); // if first or last page of review and blind/risky operation, add the warning top-right button - if (isFirstOrLastPage && (operationType & (BLIND_OPERATION | RISKY_OPERATION))) { - // if issue is only Web3Checks issue, use '!' icon - if ((operationType & RISKY_OPERATION) - && (reviewWithWarnCtx.warning->predefinedSet == (1 << W3C_ISSUE_WARN))) { - pageContent->topRightIcon = &EXCLAMATION_ICON; + if (isFirstOrLastPage + && (operationType & (BLIND_OPERATION | RISKY_OPERATION | NO_THREAT_OPERATION))) { + // if issue is only Web3Checks "no threat", use privacy icon + if ((operationType & NO_THREAT_OPERATION) + && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) { + pageContent->topRightIcon = &PRIVACY_ICON; } else { pageContent->topRightIcon = &WARNING_ICON; @@ -1230,75 +1273,84 @@ static void displayFullValuePage(const char *backText, const char *aliasText, const nbgl_contentValueExt_t *extension) { - nbgl_layoutDescription_t layoutDescription = {.modal = true, - .withLeftBorder = true, - .onActionCallback = &modalLayoutTouchCallback, - .tapActionText = NULL}; - nbgl_layoutHeader_t headerDesc = {.type = HEADER_BACK_AND_TEXT, - .separationLine = false, - .backAndText.token = 0, - .backAndText.tuneId = TUNE_TAP_CASUAL, - .backAndText.text = PIC(backText)}; - genericContext.modalLayout = nbgl_layoutGet(&layoutDescription); - // add header with the tag part of the pair, to go back - nbgl_layoutAddHeader(genericContext.modalLayout, &headerDesc); - // add either QR Code or full value text - if (extension->aliasType == QR_CODE_ALIAS) { -#ifdef NBGL_QRCODE - nbgl_layoutQRCode_t qrCode - = {.url = extension->fullValue, - .text1 = (extension->title != NULL) ? extension->title : extension->fullValue, - .text2 = extension->explanation, - .centered = true, - .offsetY = 0}; - - nbgl_layoutAddQRCode(genericContext.modalLayout, &qrCode); -#endif // NBGL_QRCODE + if (extension->aliasType == INFO_LIST_ALIAS) { + genericContext.currentInfos = extension->infolist; + displayInfosListModal(PIC(backText), extension->infolist, true); } else { - const char *info; - // add full value text - if (extension->aliasType == ENS_ALIAS) { - info = "ENS names are resolved by Ledger backend."; - } - else if (extension->aliasType == ADDRESS_BOOK_ALIAS) { - info = "This account label comes from your Address Book in Ledger Live."; + nbgl_layoutDescription_t layoutDescription = {.modal = true, + .withLeftBorder = true, + .onActionCallback = &modalLayoutTouchCallback, + .tapActionText = NULL}; + nbgl_layoutHeader_t headerDesc = {.type = HEADER_BACK_AND_TEXT, + .separationLine = false, + .backAndText.token = 0, + .backAndText.tuneId = TUNE_TAP_CASUAL, + .backAndText.text = PIC(backText)}; + genericContext.modalLayout = nbgl_layoutGet(&layoutDescription); + // add header with the tag part of the pair, to go back + nbgl_layoutAddHeader(genericContext.modalLayout, &headerDesc); + // add either QR Code or full value text + if (extension->aliasType == QR_CODE_ALIAS) { +#ifdef NBGL_QRCODE + nbgl_layoutQRCode_t qrCode + = {.url = extension->fullValue, + .text1 = (extension->title != NULL) ? extension->title : extension->fullValue, + .text2 = extension->explanation, + .centered = true, + .offsetY = 0}; + + nbgl_layoutAddQRCode(genericContext.modalLayout, &qrCode); +#endif // NBGL_QRCODE } else { - info = extension->explanation; + const char *info; + // add full value text + if (extension->aliasType == ENS_ALIAS) { + info = "ENS names are resolved by Ledger backend."; + } + else if (extension->aliasType == ADDRESS_BOOK_ALIAS) { + info = "This account label comes from your Address Book in Ledger Live."; + } + else { + info = extension->explanation; + } + nbgl_layoutAddTextContent( + genericContext.modalLayout, aliasText, extension->fullValue, info); } - nbgl_layoutAddTextContent( - genericContext.modalLayout, aliasText, extension->fullValue, info); + // draw & refresh + nbgl_layoutDraw(genericContext.modalLayout); + nbgl_refresh(); } - // draw & refresh - nbgl_layoutDraw(genericContext.modalLayout); - nbgl_refresh(); } // function used to display the modal containing tip-box infos -static void displayTipBoxModal(void) -{ - nbgl_pageNavigationInfo_t info = {.activePage = 0, - .nbPages = 1, - .navType = NAV_WITH_BUTTONS, - .quitToken = QUIT_TOKEN, - .navWithButtons.navToken = NAV_TOKEN, - .navWithButtons.quitButton = false, - .navWithButtons.backButton = true, - .navWithButtons.quitText = NULL, - .progressIndicator = false, - .tuneId = TUNE_TAP_CASUAL}; - nbgl_pageContent_t content = {.type = INFOS_LIST, - .topRightIcon = NULL, - .infosList.nbInfos = tipBoxInfoList.nbInfos, - .infosList.withExtensions = tipBoxInfoList.withExtensions, - .infosList.infoTypes = tipBoxInfoList.infoTypes, - .infosList.infoContents = tipBoxInfoList.infoContents, - .infosList.infoExtensions = tipBoxInfoList.infoExtensions, - .infosList.token = INFO_ALIAS_TOKEN, - .title = tipBoxModalTitle, - .titleToken = QUIT_TOKEN, - .tuneId = TUNE_TAP_CASUAL}; +static void displayInfosListModal(const char *modalTitle, + const nbgl_contentInfoList_t *infos, + bool fromReview) +{ + nbgl_pageNavigationInfo_t info = {.activePage = 0, + .nbPages = 1, + .navType = NAV_WITH_BUTTONS, + .quitToken = QUIT_TIPBOX_MODAL_TOKEN, + .navWithButtons.navToken = NAV_TOKEN, + .navWithButtons.quitButton = false, + .navWithButtons.backButton = true, + .navWithButtons.quitText = NULL, + .progressIndicator = false, + .tuneId = TUNE_TAP_CASUAL}; + nbgl_pageContent_t content + = {.type = INFOS_LIST, + .topRightIcon = NULL, + .infosList.nbInfos = infos->nbInfos, + .infosList.withExtensions = infos->withExtensions, + .infosList.infoTypes = infos->infoTypes, + .infosList.infoContents = infos->infoContents, + .infosList.infoExtensions = infos->infoExtensions, + .infosList.token = fromReview ? INFO_ALIAS_TOKEN : INFOS_TIP_BOX_TOKEN, + .title = modalTitle, + .titleToken = QUIT_TIPBOX_MODAL_TOKEN, + .tuneId = TUNE_TAP_CASUAL}; if (modalPageContext != NULL) { nbgl_pageRelease(modalPageContext); @@ -1443,16 +1495,17 @@ static void layoutTouchCallback(int token, uint8_t index) reviewWithWarnCtx.reviewTitle, reviewWithWarnCtx.reviewSubTitle, reviewWithWarnCtx.finishTitle, - reviewWithWarnCtx.tipBox, + &activeTipBox, reviewWithWarnCtx.choiceCallback, false, - false); + false, + true); } } } // top-right button in initial warning page else if (token == WARNING_BUTTON_TOKEN) { - // start at first level of security report + // start directly at first level of security report reviewWithWarnCtx.securityReportLevel = 1; // if preset is used if (reviewWithWarnCtx.warning->predefinedSet) { @@ -1807,7 +1860,8 @@ static void bundleNavReviewAskRejectionConfirmation(nbgl_operationType_t operati const char *title; const char *confirmText; // clear skip and blind bits - operationType &= ~(SKIPPABLE_OPERATION | BLIND_OPERATION | RISKY_OPERATION); + operationType + &= ~(SKIPPABLE_OPERATION | BLIND_OPERATION | RISKY_OPERATION | NO_THREAT_OPERATION); if (operationType == TYPE_TRANSACTION) { title = "Reject transaction?"; confirmText = "Go back to transaction"; @@ -1880,34 +1934,36 @@ static void displaySecurityReport(uint32_t set) // count the number of warnings for (i = 0; i < NB_WARNING_TYPES; i++) { - if (set & (1 << i)) { + if ((set & (1 << i)) && (i != W3C_NO_THREAT_WARN)) { nbWarnings++; } } - if ((nbWarnings > 1) && (reviewWithWarnCtx.securityReportLevel == 1)) { - // if more than one warning warning, so use a list of touchable bars + // display a list of touchable bars in some conditions: + // - we must be in the first level of security report + // - and be in the review itself (not from the intro/warning screen) + // + if ((reviewWithWarnCtx.securityReportLevel == 1) && (!reviewWithWarnCtx.isIntro)) { for (i = 0; i < NB_WARNING_TYPES; i++) { if (reviewWithWarnCtx.warning->predefinedSet & (1 << i)) { - nbgl_layoutBar_t bar; - if ((i == BLIND_SIGNING_WARN) + nbgl_layoutBar_t bar = {0}; + if ((i == BLIND_SIGNING_WARN) || (i == W3C_NO_THREAT_WARN) || (i == W3C_ISSUE_WARN) || (reviewWithWarnCtx.warning->providerMessage == NULL)) { bar.subText = securityReportItems[i].subText; } else { - snprintf(tmpString, - W3C_DESCRIPTION_MAX_LEN, - "Web3 Checks found a risk:\n%s.", - reviewWithWarnCtx.warning->providerMessage); - bar.subText = tmpString; + bar.subText = reviewWithWarnCtx.warning->providerMessage; + } + bar.text = securityReportItems[i].text; + if (i != W3C_NO_THREAT_WARN) { + bar.iconRight = &PUSH_ICON; + bar.token = FIRST_WARN_BAR_TOKEN + i; + bar.tuneId = TUNE_TAP_CASUAL; + } + else { + bar.token = NBGL_INVALID_TOKEN; } - bar.text = securityReportItems[i].text; - bar.iconRight = &PUSH_ICON; - bar.iconLeft = securityReportItems[i].icon; - bar.token = FIRST_WARN_BAR_TOKEN + i; - bar.tuneId = TUNE_TAP_CASUAL; - bar.large = false; - bar.inactive = false; + bar.iconLeft = securityReportItems[i].icon; nbgl_layoutAddTouchableBar(reviewWithWarnCtx.modalLayout, &bar); nbgl_layoutAddSeparationLine(reviewWithWarnCtx.modalLayout); } @@ -1924,126 +1980,56 @@ static void displaySecurityReport(uint32_t set) else { provider = "[unknown]"; } - if (set & (1 << BLIND_SIGNING_WARN)) { - if (reviewWithWarnCtx.isIntro) { -#ifdef NBGL_QRCODE - // display a QR Code if in intro - nbgl_layoutQRCode_t qrCode - = {.url = "ledger.com/e8", - .text1 = "ledger.com/e8", - .text2 = "Scan to learn about the risks of blind signing.", - .centered = true, - .offsetY = 0}; - nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &qrCode); - footerDesc.emptySpace.height = 24; -#endif // NBGL_QRCODE - headerDesc.backAndText.text = "Blind signing report"; - } - else { - // display a centered if in review - nbgl_contentCenter_t info = {0}; - info.icon = &C_Warning_64px; - info.title = "Blind Signing"; - info.description - = "This transaction's details are not fully verifiable. If you sign it, you could " - "lose all your assets.\n\n" - "Learn about blind signing:\nledger.com/e8"; - nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info); - footerDesc.emptySpace.height = MEDIUM_CENTERING_HEADER; - headerDesc.separationLine = false; - } - } - else if (set & (1 << W3C_ISSUE_WARN)) { + if (set & (1 << W3C_ISSUE_WARN)) { // if W3 Checks issue, display a centered info nbgl_contentCenter_t info = {0}; - info.icon = &C_Important_Circle_64px; - info.title = "Web3 Checks could not verify this message"; - info.description = "An issue prevented Web3 Checks from running.\nGet help: ledger.com/e11"; + info.icon = &C_Warning_64px; + info.title = "Transaction Check not available"; + info.description + = "If you're not using the\nLedger Live app, Transaction Check might not work. If your " + "are using Ledger Live, reject the transaction and try again.\n\nGet help at " + "ledger.com/e11"; nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info); footerDesc.emptySpace.height = MEDIUM_CENTERING_HEADER; headerDesc.separationLine = false; } - else if (set & (1 << W3C_THREAT_DETECTED_WARN)) { - if (reviewWithWarnCtx.isIntro) { -#ifdef NBGL_QRCODE - // display a QR Code if in intro - nbgl_layoutQRCode_t qrCode = {.url = reviewWithWarnCtx.warning->reportUrl, - .text1 = reviewWithWarnCtx.warning->reportUrl, - .text2 = tmpString, - .centered = true, - .offsetY = 0}; - snprintf(tmpString, - W3C_DESCRIPTION_MAX_LEN, - "Scan to view the threat report from %s.", - provider); - nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &qrCode); - footerDesc.emptySpace.height = 24; -#endif // NBGL_QRCODE - headerDesc.backAndText.text = "Web3 Checks threat report"; - } - else { - // display a centered if in review - nbgl_contentCenter_t info = {0}; - info.icon = &C_Warning_64px; - info.title = "Threat detected"; - info.description = tmpString; - if (reviewWithWarnCtx.warning->providerMessage != NULL) { - info.smallTitle = reviewWithWarnCtx.warning->providerMessage; - } - else { - info.smallTitle = "Known drainer contract"; - } - snprintf(tmpString, - W3C_DESCRIPTION_MAX_LEN, - "This transaction was scanned as malicious by Web3 Checks.\n\n" - "View full %s report:\n%s", - provider, - reviewWithWarnCtx.warning->reportUrl); - nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info); - footerDesc.emptySpace.height = MEDIUM_CENTERING_HEADER; - headerDesc.separationLine = false; - } - } - else if (set & (1 << W3C_LOSING_SWAP_WARN)) { - if (reviewWithWarnCtx.isIntro) { + else if ((set & (1 << W3C_THREAT_DETECTED_WARN)) || (set & (1 << W3C_RISK_DETECTED_WARN))) { + size_t urlLen = 0; + char *destStr + = tmpString + + W3C_DESCRIPTION_MAX_LEN / 2; // use the second half of tmpString for strings #ifdef NBGL_QRCODE - // display a QR Code if in intro - nbgl_layoutQRCode_t qrCode = {.url = reviewWithWarnCtx.warning->reportUrl, - .text1 = reviewWithWarnCtx.warning->reportUrl, - .text2 = tmpString, - .centered = true, - .offsetY = 0}; - snprintf(tmpString, - W3C_DESCRIPTION_MAX_LEN, - "Scan to view the risk report from %s.", - provider); - nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &qrCode); - footerDesc.emptySpace.height = 24; + // display a QR Code + nbgl_layoutQRCode_t qrCode = {.url = tmpString, + .text1 = reviewWithWarnCtx.warning->reportUrl, + .text2 = "Scan to view full report", + .centered = true, + .offsetY = 0}; + // add "https://"" as prefix of the given URL + snprintf(destStr, + W3C_DESCRIPTION_MAX_LEN / 2, + "https://%s", + reviewWithWarnCtx.warning->reportUrl); + urlLen = strlen(destStr) + 1; + nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &qrCode); + footerDesc.emptySpace.height = 24; #endif // NBGL_QRCODE - headerDesc.backAndText.text = "Web3 Checks risk report"; - } - else { - // display a centered if in review - nbgl_contentCenter_t info = {0}; - info.icon = &C_Warning_64px; - info.title = "Risk detected"; - info.description = tmpString; - if (reviewWithWarnCtx.warning->providerMessage != NULL) { - info.smallTitle = reviewWithWarnCtx.warning->providerMessage; - } - else { - info.smallTitle = "Losing swap"; - } - snprintf(tmpString, - W3C_DESCRIPTION_MAX_LEN, - "This transaction was scanned as risky by Web3 Checks.\n\n" - "View full %s report:\n%s", - provider, - reviewWithWarnCtx.warning->reportUrl); - nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info); - footerDesc.emptySpace.height = MEDIUM_CENTERING_HEADER; - headerDesc.separationLine = false; - } + // use the next part of destStr for back bar text + snprintf(destStr + urlLen, W3C_DESCRIPTION_MAX_LEN / 2 - urlLen, "%s report", provider); + headerDesc.backAndText.text = destStr + urlLen; + } + else if (set & (1 << BLIND_SIGNING_WARN)) { + // display a centered if in review + nbgl_contentCenter_t info = {0}; + info.icon = &C_Warning_64px; + info.title = "This transaction cannot be Clear Signed"; + info.description + = "This transaction or message cannot be decoded fully. If you choose to sign, you " + "could be authorizing malicious actions that can drain your wallet.\n\nLearn more: " + "ledger.com/e8"; + nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info); + footerDesc.emptySpace.height = MEDIUM_CENTERING_HEADER; + headerDesc.separationLine = false; } nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc); if (footerDesc.emptySpace.height > 0) { @@ -2120,6 +2106,8 @@ static void displayInitialWarning(void) nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY, .separationLine = false, .emptySpace.height = MEDIUM_CENTERING_HEADER}; + uint32_t set = reviewWithWarnCtx.warning->predefinedSet + & ~((1 << W3C_NO_THREAT_WARN) | (1 << W3C_ISSUE_WARN)); reviewWithWarnCtx.isIntro = true; @@ -2133,72 +2121,68 @@ static void displayInitialWarning(void) reviewWithWarnCtx.layoutCtx = nbgl_layoutGet(&layoutDescription); nbgl_layoutAddHeader(reviewWithWarnCtx.layoutCtx, &headerDesc); - // do not display top-right icon if only Web3 Checks issue - if (reviewWithWarnCtx.warning->predefinedSet != (1 << W3C_ISSUE_WARN)) { - if (reviewWithWarnCtx.warning->predefinedSet != 0) { - nbgl_layoutAddTopRightButton( - reviewWithWarnCtx.layoutCtx, &PRIVACY_ICON, WARNING_BUTTON_TOKEN, TUNE_TAP_CASUAL); - } - else if (reviewWithWarnCtx.warning->introTopRightIcon != NULL) { - nbgl_layoutAddTopRightButton(reviewWithWarnCtx.layoutCtx, - reviewWithWarnCtx.warning->introTopRightIcon, - WARNING_BUTTON_TOKEN, - TUNE_TAP_CASUAL); - } + if (reviewWithWarnCtx.warning->predefinedSet != 0) { + nbgl_layoutAddTopRightButton( + reviewWithWarnCtx.layoutCtx, + (set == (1 << BLIND_SIGNING_WARN)) ? &INFO_I_ICON : &QRCODE_ICON, + WARNING_BUTTON_TOKEN, + TUNE_TAP_CASUAL); + } + else if (reviewWithWarnCtx.warning->introTopRightIcon != NULL) { + nbgl_layoutAddTopRightButton(reviewWithWarnCtx.layoutCtx, + reviewWithWarnCtx.warning->introTopRightIcon, + WARNING_BUTTON_TOKEN, + TUNE_TAP_CASUAL); } - // add button and footer on bottom - nbgl_layoutAddChoiceButtons(reviewWithWarnCtx.layoutCtx, &buttonsInfo); // add main content // if predefined content is configured, use it preferably if (reviewWithWarnCtx.warning->predefinedSet != 0) { nbgl_contentCenter_t info = {0}; - info.icon = &C_Warning_64px; - if (reviewWithWarnCtx.warning->predefinedSet == (1 << BLIND_SIGNING_WARN)) { - info.title = "Blind signing ahead"; - info.description - = "The details of this transaction or message are not fully verifiable. If " - "you sign it, you could lose all " - "your assets."; - } - else if (reviewWithWarnCtx.warning->predefinedSet == (1 << W3C_ISSUE_WARN)) { - info.icon = &C_Important_Circle_64px; - info.title = "Web3 Checks could not verify this message"; - info.description - = "An issue prevented Web3 Checks from running.\nGet help: ledger.com/e11"; - } - else if (reviewWithWarnCtx.warning->predefinedSet == (1 << W3C_LOSING_SWAP_WARN)) { - info.title = "Risk detected"; - info.description = "This transaction was scanned as ricky by Web3 Checks."; - if (reviewWithWarnCtx.warning->providerMessage != NULL) { - info.smallTitle = reviewWithWarnCtx.warning->providerMessage; + + const char *provider; + + // default icon + info.icon = &C_Warning_64px; + + // use small title only if not Blind signing only + if (set != (1 << BLIND_SIGNING_WARN)) { + if (reviewWithWarnCtx.warning->reportProvider) { + provider = reviewWithWarnCtx.warning->reportProvider; } else { - info.smallTitle = "Losing swap"; + provider = "[unknown]"; } + info.smallTitle = tmpString; + snprintf(tmpString, W3C_DESCRIPTION_MAX_LEN, "Detected by %s", provider); + } + + if (set == (1 << BLIND_SIGNING_WARN)) { + info.title = "Blind signing ahead"; + info.description + = "This transaction's details are not fully verifiable. If you sign, you could " + "lose all your assets."; + buttonsInfo.bottomText = "Continue anyway"; } - else if (reviewWithWarnCtx.warning->predefinedSet == (1 << W3C_THREAT_DETECTED_WARN)) { - info.title = "Threat detected"; - info.description = "This transaction was scanned as malicious by Web3 Checks."; - if (reviewWithWarnCtx.warning->providerMessage != NULL) { - info.smallTitle = reviewWithWarnCtx.warning->providerMessage; + else if (set & (1 << W3C_RISK_DETECTED_WARN)) { + info.icon = &C_Important_Circle_64px; + info.title = "Potential risk"; + if (reviewWithWarnCtx.warning->providerMessage == NULL) { + info.description = "Unidentified risk"; } else { - info.smallTitle = "Known drainer contract"; + info.description = reviewWithWarnCtx.warning->providerMessage; } + buttonsInfo.bottomText = "Accept risk and continue"; } - else { - // Case with Several warnings (e.g. Blind + W3C) - info.title = "Dangerous transaction"; - if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_ISSUE_WARN)) { - info.description - = "This transaction cannot be fully decoded, and was not verified by Web3 " - "Checks."; + else if (set & (1 << W3C_THREAT_DETECTED_WARN)) { + info.title = "Critical threat"; + if (reviewWithWarnCtx.warning->providerMessage == NULL) { + info.description = "Unidentified threat"; } else { - info.description - = "This transaction cannot be fully decoded, and was marked as risky by Web3 " - "Checks."; + info.description = reviewWithWarnCtx.warning->providerMessage; } + buttonsInfo.bottomText = "Accept threat and continue"; } nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, &info); } @@ -2206,6 +2190,8 @@ static void displayInitialWarning(void) // if no predefined content, use custom one nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, reviewWithWarnCtx.warning->info); } + // add button and footer on bottom + nbgl_layoutAddChoiceButtons(reviewWithWarnCtx.layoutCtx, &buttonsInfo); nbgl_layoutDraw(reviewWithWarnCtx.layoutCtx); nbgl_refresh(); @@ -2221,8 +2207,10 @@ static void useCaseReview(nbgl_operationType_t operationType, const nbgl_tipBox_t *tipBox, nbgl_choiceCallback_t choiceCallback, bool isLight, - bool playNotifSound) + bool playNotifSound, + bool validWarningCtx) { + const char *predefinedTipBoxText = NULL; reset_callbacks(); memset(&genericContext, 0, sizeof(genericContext)); @@ -2234,6 +2222,7 @@ static void useCaseReview(nbgl_operationType_t operationType, navType = GENERIC_NAV; pageTitle = NULL; + genericContext.validWarningCtx = validWarningCtx; genericContext.genericContents.contentsList = localContentsList; genericContext.genericContents.nbContents = 3; memset(localContentsList, 0, 3 * sizeof(nbgl_content_t)); @@ -2242,23 +2231,59 @@ static void useCaseReview(nbgl_operationType_t operationType, STARTING_CONTENT.type = EXTENDED_CENTER; prepareReviewFirstPage( &STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle); - if (tipBox != NULL) { + + // if warning is valid and a warning requiring a tip box + if (validWarningCtx) { + if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_ISSUE_WARN)) { + if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) { + predefinedTipBoxText = "Transaction Check unavailable.\nBlind signing required."; + } + else { + predefinedTipBoxText = "Transaction Check unavailable"; + } + } + else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN)) { + if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) { + predefinedTipBoxText = "Critical threat detected.\nBlind signing required."; + } + else { + predefinedTipBoxText = "Critical threat detected."; + } + } + else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN)) { + if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) { + predefinedTipBoxText = "Potential risk detected.\nBlind signing required."; + } + else { + predefinedTipBoxText = "Potential risk detected."; + } + } + else if (reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_NO_THREAT_WARN)) { + if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) { + predefinedTipBoxText + = "No threat detected by Transaction Check but blind signing required."; + } + else { + predefinedTipBoxText = "No threat detected by Transaction Check."; + } + } + else if (reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN)) { + predefinedTipBoxText = "Blind signing required."; + } + } + if ((tipBox != NULL) || (predefinedTipBoxText != NULL)) { // do not display "Swipe to review" if a tip-box is displayed STARTING_CONTENT.content.extendedCenter.contentCenter.subText = NULL; - - STARTING_CONTENT.content.extendedCenter.tipBox.icon = tipBox->icon; - STARTING_CONTENT.content.extendedCenter.tipBox.text = tipBox->text; + if (predefinedTipBoxText != NULL) { + STARTING_CONTENT.content.extendedCenter.tipBox.icon = NULL; + STARTING_CONTENT.content.extendedCenter.tipBox.text = predefinedTipBoxText; + } + else { + STARTING_CONTENT.content.extendedCenter.tipBox.icon = tipBox->icon; + STARTING_CONTENT.content.extendedCenter.tipBox.text = tipBox->text; + } STARTING_CONTENT.content.extendedCenter.tipBox.token = TIP_BOX_TOKEN; STARTING_CONTENT.content.extendedCenter.tipBox.tuneId = TUNE_TAP_CASUAL; - tipBoxModalTitle = tipBox->modalTitle; - // the only supported type yet is @ref INFOS_LIST - if (tipBox->type == INFOS_LIST) { - tipBoxInfoList.nbInfos = tipBox->infos.nbInfos; - tipBoxInfoList.withExtensions = tipBox->infos.withExtensions; - tipBoxInfoList.infoTypes = PIC(tipBox->infos.infoTypes); - tipBoxInfoList.infoContents = PIC(tipBox->infos.infoContents); - tipBoxInfoList.infoExtensions = PIC(tipBox->infos.infoExtensions); - } } // Then the tag/value pairs @@ -3401,7 +3426,8 @@ void nbgl_useCaseReview(nbgl_operationType_t operationType, NULL, choiceCallback, false, - true); + true, + false); } /** @@ -3444,7 +3470,7 @@ void nbgl_useCaseReviewBlindSigning(nbgl_operationType_t operationT } /** - * @brief Draws a flow of pages of a review requiring a warning page before the review. + * @brief Draws a flow of pages of a review requiring if necessary a warning page before the review. * Moreover, the first and last pages of review display a top-right button, that displays more * information about the warnings * @@ -3461,7 +3487,7 @@ void nbgl_useCaseReviewBlindSigning(nbgl_operationType_t operationT * @param reviewSubTitle string to set under reviewTitle (can be NULL) * @param finishTitle string used in the last review page * @param tipBox parameter to build a tip-box and necessary modal (can be NULL) - * @param warning structure to build the initial warning page (cannot be NULL) + * @param warning structure to build the initial warning page (can be NULL) * @param choiceCallback callback called when operation is accepted (param is true) or rejected * (param is false) */ @@ -3475,18 +3501,66 @@ void nbgl_useCaseAdvancedReview(nbgl_operationType_t operationType, const nbgl_warning_t *warning, nbgl_choiceCallback_t choiceCallback) { + // memorize tipBox because it can be in the call stack of the caller + if (tipBox != NULL) { + memcpy(&activeTipBox, tipBox, sizeof(activeTipBox)); + } + else { + memset(&activeTipBox, 0, sizeof(activeTipBox)); + } + // if no warning at all, it's a simple review + if ((warning == NULL) + || ((warning->predefinedSet == 0) && (warning->introDetails == NULL) + && (warning->reviewDetails == NULL))) { + useCaseReview(operationType, + tagValueList, + icon, + reviewTitle, + reviewSubTitle, + finishTitle, + tipBox, + choiceCallback, + false, + true, + false); + return; + } + if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) { + operationType |= NO_THREAT_OPERATION; + } + else { + operationType |= RISKY_OPERATION; + } + memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx)); reviewWithWarnCtx.isStreaming = false; - reviewWithWarnCtx.operationType = operationType | RISKY_OPERATION; + reviewWithWarnCtx.operationType = operationType; reviewWithWarnCtx.tagValueList = tagValueList; reviewWithWarnCtx.icon = icon; reviewWithWarnCtx.reviewTitle = reviewTitle; reviewWithWarnCtx.reviewSubTitle = reviewSubTitle; reviewWithWarnCtx.finishTitle = finishTitle; - reviewWithWarnCtx.tipBox = tipBox; reviewWithWarnCtx.warning = warning; reviewWithWarnCtx.choiceCallback = choiceCallback; + // display the initial warning only of a risk/threat or blind signing + if (!(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN)) + && !(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN)) + && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) { + useCaseReview(operationType, + tagValueList, + icon, + reviewTitle, + reviewSubTitle, + finishTitle, + tipBox, + choiceCallback, + false, + true, + true); + return; + } + displayInitialWarning(); } @@ -3523,7 +3597,8 @@ void nbgl_useCaseReviewLight(nbgl_operationType_t operationType, NULL, choiceCallback, true, - true); + true, + false); } /** @@ -3609,7 +3684,7 @@ void nbgl_useCaseReviewStreamingBlindSigningStart(nbgl_operationType_t ope /** * @brief Start drawing the flow of pages of a blind-signing review. The review is preceded by a - * warning page + * warning page if needed * @note This should be followed by calls to @ref nbgl_useCaseReviewStreamingContinue and finally * to * @ref nbgl_useCaseReviewStreamingFinish. @@ -3629,16 +3704,38 @@ void nbgl_useCaseAdvancedReviewStreamingStart(nbgl_operationType_t operati const nbgl_warning_t *warning, nbgl_choiceCallback_t choiceCallback) { + // if no warning at all, it's a simple review + if ((warning == NULL) + || ((warning->predefinedSet == 0) && (warning->introDetails == NULL) + && (warning->reviewDetails == NULL))) { + useCaseReviewStreamingStart( + operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true); + return; + } + if (warning->predefinedSet == (1 << W3C_NO_THREAT_WARN)) { + operationType |= NO_THREAT_OPERATION; + } + else { + operationType |= RISKY_OPERATION; + } memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx)); reviewWithWarnCtx.isStreaming = true; - reviewWithWarnCtx.operationType = operationType | RISKY_OPERATION; + reviewWithWarnCtx.operationType = operationType; reviewWithWarnCtx.icon = icon; reviewWithWarnCtx.reviewTitle = reviewTitle; reviewWithWarnCtx.reviewSubTitle = reviewSubTitle; reviewWithWarnCtx.choiceCallback = choiceCallback; reviewWithWarnCtx.warning = warning; + // display the initial warning only of a risk/threat or blind signing + if (!(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_THREAT_DETECTED_WARN)) + && !(reviewWithWarnCtx.warning->predefinedSet & (1 << W3C_RISK_DETECTED_WARN)) + && !(reviewWithWarnCtx.warning->predefinedSet & (1 << BLIND_SIGNING_WARN))) { + useCaseReviewStreamingStart( + operationType, icon, reviewTitle, reviewSubTitle, choiceCallback, true); + return; + } displayInitialWarning(); }