diff --git a/lib_nbgl/glyphs/32px/Privacy_32px.png b/lib_nbgl/glyphs/32px/Privacy_32px.png new file mode 100755 index 000000000..700f60a21 Binary files /dev/null and b/lib_nbgl/glyphs/32px/Privacy_32px.png differ diff --git a/lib_nbgl/glyphs/40px/Important_Circle_40px.png b/lib_nbgl/glyphs/40px/Important_Circle_40px.png new file mode 100755 index 000000000..5037b98d5 Binary files /dev/null and b/lib_nbgl/glyphs/40px/Important_Circle_40px.png differ diff --git a/lib_nbgl/glyphs/40px/Privacy_40px.png b/lib_nbgl/glyphs/40px/Privacy_40px.png new file mode 100755 index 000000000..1664a2088 Binary files /dev/null and b/lib_nbgl/glyphs/40px/Privacy_40px.png differ diff --git a/lib_nbgl/include/nbgl_obj.h b/lib_nbgl/include/nbgl_obj.h index c5b4d83bd..2c75d0ffd 100644 --- a/lib_nbgl/include/nbgl_obj.h +++ b/lib_nbgl/include/nbgl_obj.h @@ -127,6 +127,8 @@ extern "C" { #define QRCODE_ICON C_QRCode_32px #define MINI_PUSH_ICON C_Mini_Push_32px #define WARNING_ICON C_Warning_32px +#define ROUND_WARN_ICON C_Important_Circle_32px +#define PRIVACY_ICON C_Privacy_32px #else // TARGET_STAX #define SPACE_ICON C_Space_40px #define BACKSPACE_ICON C_Erase_40px @@ -146,6 +148,8 @@ extern "C" { #define QRCODE_ICON C_QRCode_40px #define MINI_PUSH_ICON C_Mini_Push_40px #define WARNING_ICON C_Warning_40px +#define ROUND_WARN_ICON C_Important_Circle_40px +#define PRIVACY_ICON C_Privacy_40px #endif // TARGET_STAX // For backward compatibility, to be remove later diff --git a/lib_nbgl/include/nbgl_use_case.h b/lib_nbgl/include/nbgl_use_case.h index 5d9f8dd24..bc9787724 100644 --- a/lib_nbgl/include/nbgl_use_case.h +++ b/lib_nbgl/include/nbgl_use_case.h @@ -188,6 +188,86 @@ typedef struct { }; } nbgl_tipBox_t; +/** + * @brief The different types of warning page contents + * + */ +typedef enum { + CENTERED_INFO_WARNING = 0, ///< Centered info + QRCODE_WARNING, ///< QR Code + BAR_LIST_WARNING ///< list of touchable bars, to display sub-pages +} nbgl_warningDetailsType_t; + +/** + * @brief The necessary parameters to build a list of touchable bars, to display sub-pages + * + */ +typedef struct { + uint8_t nbBars; ///< number of touchable bars + const char *const *texts; ///< array of texts for each bar (nbBars items, in black/bold) + const char *const *subTexts; ///< array of texts for each bar (nbBars items, in black) + const nbgl_icon_details_t **icons; ///< array of icons for each bar (nbBars items) + const struct nbgl_warningDetails_s + *details; ///< array of nbBars structure giving what to deplay when each bar is touched. +} nbgl_warningBarList_t; + +/** + * @brief The necessary parameters to build the page(s) displayed when the top-right button is + * touched in intro page (before review) + * + */ +typedef struct nbgl_warningDetails_s { + const char *title; ///< text of the page (used to go back) + nbgl_warningDetailsType_t + type; ///< type of content in the page, determining what to use in the following union + union { + nbgl_contentCenter_t + centeredInfo; ///< centered info, if type == @ref CENTERED_INFO_WARNING +#ifdef NBGL_QRCODE + nbgl_layoutQRCode_t qrCode; ///< QR code, if type == @ref QRCODE_WARNING +#endif // NBGL_QRCODE + nbgl_warningBarList_t barList; ///< touchable bars list, if type == @ref BAR_LIST_WARNING + }; +} nbgl_warningDetails_t; + +/** + * @brief The different types of pre-defined warnings + * + */ +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) + NB_WARNING_TYPES +} nbgl_warningType_t; + +/** + * @brief The necessary parameters to build a warning page preceding a review. + * One can either use `predefinedSet` when the warnings are already supported in @ref + * nbgl_warningType_t list, or use `introDetails` or `reviewDetails` to configure manually the + * warning pages + * @note it's also used to build the modal pages displayed when touching the top-right button in the + * review's first page + * + */ +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 + const nbgl_warningDetails_t + *introDetails; ///< details displayed when top-right button is touched in intro page + ///< (before review) if using pre-defined configuration, set to NULL, + const nbgl_warningDetails_t + *reviewDetails; ///< details displayed when top-right button is touched in first/last page + ///< of review if using pre-defined configuration, set to NULL + const nbgl_contentCenter_t + *info; ///< parameters to build the intro warning page, if not using pre-defined + const nbgl_icon_details_t + *introTopRightIcon; ///< icon to use in the intro warning page, if not using pre-defined + const nbgl_icon_details_t *reviewTopRightIcon; ///< icon to use in the first/last page of + ///< review, if not using pre-defined +} nbgl_warning_t; + /** * @brief The different types of operation to review * @@ -265,7 +345,15 @@ void nbgl_useCaseReviewBlindSigning(nbgl_operationType_t operationT const char *finishTitle, const nbgl_tipBox_t *tipBox, nbgl_choiceCallback_t choiceCallback); - +void nbgl_useCaseReviewWithWarning(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle, + const nbgl_tipBox_t *tipBox, + const nbgl_warning_t *warning, + nbgl_choiceCallback_t choiceCallback); void nbgl_useCaseAdvancedReview(nbgl_operationType_t operationType, const nbgl_contentTagValueList_t *tagValueList, const nbgl_icon_details_t *icon, diff --git a/lib_nbgl/src/nbgl_use_case.c b/lib_nbgl/src/nbgl_use_case.c index 79eb6a636..e7207cdc8 100644 --- a/lib_nbgl/src/nbgl_use_case.c +++ b/lib_nbgl/src/nbgl_use_case.c @@ -48,8 +48,8 @@ #define QRCODE_REDUCED_ADDR_LEN 128 // macros to ease access to shared contexts -#define keypadContext sharedContext.keypad -#define blindSigningContext sharedContext.blindSigning +#define keypadContext sharedContext.keypad +#define reviewWithWarnCtx sharedContext.reviewWithWarning /********************** * TYPEDEFS @@ -69,8 +69,13 @@ enum { REJECT_TOKEN, VALUE_ALIAS_TOKEN, INFO_ALIAS_TOKEN, - BLIND_WARNING_TOKEN, - TIP_BOX_TOKEN + WARNING_BUTTON_TOKEN, + TIP_BOX_TOKEN, + WARNING_CHOICE_TOKEN, + DISMISS_QR_TOKEN, + DISMISS_WARNING_TOKEN, + FIRST_WARN_BAR_TOKEN, + LAST_WARN_BAR_TOKEN = (FIRST_WARN_BAR_TOKEN + NB_WARNING_TYPES - 1), }; typedef enum { @@ -91,7 +96,7 @@ typedef struct DetailsContext_s { typedef struct AddressConfirmationContext_s { nbgl_layoutTagValue_t tagValuePair; - nbgl_layout_t modalLayout; + nbgl_layout_t *modalLayout; } AddressConfirmationContext_t; #ifdef NBGL_KEYPAD @@ -114,9 +119,13 @@ typedef struct BlindSigningContext_s { const char *reviewSubTitle; const char *finishTitle; const nbgl_tipBox_t *tipBox; + const nbgl_warning_t *warning; nbgl_choiceCallback_t choiceCallback; nbgl_layout_t *layoutCtx; -} BlindSigningContext_t; + nbgl_layout_t *modalLayout; + uint8_t securityReportLevel; // level 1 is the first level of menus + bool isIntro; // set to true during intro (before actual review) +} ReviewWithWarningContext_t; // this union is intended to save RAM for context storage // indeed, these 2 contexts cannot happen simultaneously @@ -124,7 +133,7 @@ typedef union { #ifdef NBGL_KEYPAD KeypadContext_t keypad; #endif - BlindSigningContext_t blindSigning; + ReviewWithWarningContext_t reviewWithWarning; } SharedContext_t; typedef struct { @@ -142,7 +151,7 @@ typedef struct { nbgl_contentTagValueCallback_t currentCallback; // to be used to retrieve the pairs with value alias - nbgl_layout_t modalLayout; + nbgl_layout_t *modalLayout; } GenericContext_t; typedef struct { @@ -174,6 +183,12 @@ typedef union { nbgl_reviewStreamingContext_t reviewStreaming; } nbgl_BundleNavContext_t; +typedef struct { + const nbgl_icon_details_t *icon; + const char *text; + const char *subText; +} SecurityReportItem_t; + /********************** * STATIC VARIABLES **********************/ @@ -242,6 +257,25 @@ static const uint8_t nbMaxElementsPerContentType[] = { 5, // BARS_LIST (computed dynamically) }; +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 = "Web3 Checks found a risk:\nLosing swap" }, + [W3C_THREAT_DETECTED_WARN] + = {.icon = &ROUND_WARN_ICON, + .text = "Threat detected", + .subText = "This transaction was scanned as malicious by Web3 Checks."} +}; + +// configuration of warning when using @ref nbgl_useCaseReviewBlindSigning() +static const nbgl_warning_t blindSigningWarning = {.predefinedSet = (1 << BLIND_SIGNING_WARN)}; + #ifdef NBGL_QRCODE /* buffer to store reduced address under QR Code */ static char reducedAddress[QRCODE_REDUCED_ADDR_LEN]; @@ -255,14 +289,12 @@ static void displayDetailsPage(uint8_t page, bool forceFullRefresh); static void displayFullValuePage(const char *backText, const char *aliasText, const nbgl_contentValueExt_t *extension); -static void displayBlindWarning(void); static void displayTipBoxModal(void); static void displaySettingsPage(uint8_t page, bool forceFullRefresh); static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh); static void pageCallback(int token, uint8_t index); #ifdef NBGL_QRCODE static void displayAddressQRCode(void); -static void addressLayoutTouchCallbackQR(int token, uint8_t index); #endif // NBGL_QRCODE static void modalLayoutTouchCallback(int token, uint8_t index); static void displaySkipWarning(void); @@ -272,7 +304,9 @@ static void bundleNavStartSettingsAtPage(uint8_t initSettingPage); static void bundleNavStartSettings(void); static void bundleNavReviewStreamingChoice(bool confirm); -static void blindSigningWarning(void); +static void displaySecurityReport(uint32_t set); +static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details); +static void displayInitialWarning(void); static void useCaseReview(nbgl_operationType_t operationType, const nbgl_contentTagValueList_t *tagValueList, const nbgl_icon_details_t *icon, @@ -594,8 +628,22 @@ static void pageCallback(int token, uint8_t index) } displayFullValuePage(pair->item, pair->value, pair->extension); } - else if (token == BLIND_WARNING_TOKEN) { - displayBlindWarning(); + else if (token == WARNING_BUTTON_TOKEN) { + // top-right button, display the security modal + reviewWithWarnCtx.securityReportLevel = 1; + // if preset is used + if (reviewWithWarnCtx.warning->predefinedSet) { + displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet); + } + else { + // use customized warning + if (reviewWithWarnCtx.isIntro) { + displayCustomizedSecurityReport(reviewWithWarnCtx.warning->introDetails); + } + else { + displayCustomizedSecurityReport(reviewWithWarnCtx.warning->reviewDetails); + } + } } else if (token == TIP_BOX_TOKEN) { displayTipBoxModal(); @@ -975,13 +1023,13 @@ static bool genericContextPreparePageContent(const nbgl_content_t *p_content, bool isStreamingNavAndBlindOperation = (navType == STREAMING_NAV) && (bundleNavContext.reviewStreaming.operationType & BLIND_OPERATION); - bool isGenericNavAndBlindOperation + bool isGenericNavAndRiskyOperation = (navType == GENERIC_NAV) && (bundleNavContext.review.operationType & BLIND_OPERATION); // if first or last page of review and blind operation, add the warning top-right button - if (isFirstOrLastPage && (isStreamingNavAndBlindOperation || isGenericNavAndBlindOperation)) { + if (isFirstOrLastPage && (isStreamingNavAndBlindOperation || isGenericNavAndRiskyOperation)) { pageContent->topRightIcon = &WARNING_ICON; - pageContent->topRightToken = BLIND_WARNING_TOKEN; + pageContent->topRightToken = WARNING_BUTTON_TOKEN; } return true; @@ -1196,37 +1244,6 @@ static void displayFullValuePage(const char *backText, nbgl_refresh(); } -// function used to display the modal warning when touching the alert symbol of a blind review -static void displayBlindWarning(void) -{ - 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 = NULL}; - nbgl_layoutCenteredInfo_t centeredInfo - = {.icon = NULL, .text3 = NULL, .style = LARGE_CASE_INFO, .offsetY = 0, .onTop = false}; - centeredInfo.text1 = "Security risk detected"; - centeredInfo.text2 - = "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\n" - "Learn more: ledger.com/e8"; - - genericContext.modalLayout = nbgl_layoutGet(&layoutDescription); - // add header with the tag part of the pair, to go back - nbgl_layoutAddHeader(genericContext.modalLayout, &headerDesc); - // add full value text - nbgl_layoutAddCenteredInfo(genericContext.modalLayout, ¢eredInfo); - - // draw & refresh - nbgl_layoutDraw(genericContext.modalLayout); - nbgl_refresh(); -} - // function used to display the modal containing tip-box infos static void displayTipBoxModal(void) { @@ -1266,7 +1283,7 @@ static void displayAddressQRCode(void) // display the address as QR Code nbgl_layoutDescription_t layoutDescription = {.modal = true, .withLeftBorder = true, - .onActionCallback = &addressLayoutTouchCallbackQR, + .onActionCallback = &modalLayoutTouchCallback, .tapActionText = NULL}; nbgl_layoutHeader_t headerDesc = { .type = HEADER_EMPTY, .separationLine = false, .emptySpace.height = SMALL_CENTERING_HEADER}; @@ -1298,38 +1315,128 @@ static void displayAddressQRCode(void) nbgl_layoutAddQRCode(addressConfirmationContext.modalLayout, &qrCode); - nbgl_layoutAddFooter(addressConfirmationContext.modalLayout, "Close", 0, TUNE_TAP_CASUAL); + nbgl_layoutAddFooter( + addressConfirmationContext.modalLayout, "Close", DISMISS_QR_TOKEN, TUNE_TAP_CASUAL); nbgl_layoutDraw(addressConfirmationContext.modalLayout); nbgl_refresh(); } -// called when quit button is touched on Address verification page -static void addressLayoutTouchCallbackQR(int token, uint8_t index) -{ - UNUSED(token); - UNUSED(index); - - // dismiss modal - nbgl_layoutRelease(addressConfirmationContext.modalLayout); - addressConfirmationContext.modalLayout = NULL; - nbgl_screenRedraw(); - nbgl_refresh(); -} #endif // NBGL_QRCODE // called when header is touched on modal page, to dismiss it static void modalLayoutTouchCallback(int token, uint8_t index) { - UNUSED(token); UNUSED(index); - - // dismiss modal - nbgl_layoutRelease(genericContext.modalLayout); - genericContext.modalLayout = NULL; - nbgl_screenRedraw(); + if (token == DISMISS_QR_TOKEN) { + // dismiss modal + nbgl_layoutRelease(addressConfirmationContext.modalLayout); + addressConfirmationContext.modalLayout = NULL; + nbgl_screenRedraw(); + } + else if (token == DISMISS_WARNING_TOKEN) { + // dismiss modal + nbgl_layoutRelease(reviewWithWarnCtx.modalLayout); + // if already at first level, simply redraw the background + if (reviewWithWarnCtx.securityReportLevel <= 1) { + reviewWithWarnCtx.modalLayout = NULL; + nbgl_screenRedraw(); + } + else { + // We are at level 2 of warning modal, so re-display the level 1 + reviewWithWarnCtx.securityReportLevel = 1; + // if preset is used + if (reviewWithWarnCtx.warning->predefinedSet) { + displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet); + } + else { + // use customized warning + const nbgl_warningDetails_t *details + = (reviewWithWarnCtx.isIntro) ? reviewWithWarnCtx.warning->introDetails + : reviewWithWarnCtx.warning->reviewDetails; + displayCustomizedSecurityReport(details); + } + return; + } + } + else if ((token >= FIRST_WARN_BAR_TOKEN) && (token <= LAST_WARN_BAR_TOKEN)) { + // dismiss modal before creating a new one + nbgl_layoutRelease(reviewWithWarnCtx.modalLayout); + reviewWithWarnCtx.securityReportLevel = 2; + // if preset is used + if (reviewWithWarnCtx.warning->predefinedSet) { + displaySecurityReport(1 << (token - FIRST_WARN_BAR_TOKEN)); + } + else { + // use customized warning + const nbgl_warningDetails_t *details = (reviewWithWarnCtx.isIntro) + ? reviewWithWarnCtx.warning->introDetails + : reviewWithWarnCtx.warning->reviewDetails; + if (details->type == BAR_LIST_WARNING) { + displayCustomizedSecurityReport( + &details->barList.details[token - FIRST_WARN_BAR_TOKEN]); + } + } + return; + } + else { + // dismiss modal + nbgl_layoutRelease(genericContext.modalLayout); + genericContext.modalLayout = NULL; + nbgl_screenRedraw(); + } nbgl_refresh(); } +// called when item are touched in layout +static void layoutTouchCallback(int token, uint8_t index) +{ + // choice buttons in initial warning page + if (token == WARNING_CHOICE_TOKEN) { + if (index == 0) { // top button to exit + reviewWithWarnCtx.choiceCallback(false); + } + else { // bottom button to continue to review + reviewWithWarnCtx.isIntro = false; + if (reviewWithWarnCtx.isStreaming) { + useCaseReviewStreamingStart(reviewWithWarnCtx.operationType, + reviewWithWarnCtx.icon, + reviewWithWarnCtx.reviewTitle, + reviewWithWarnCtx.reviewSubTitle, + reviewWithWarnCtx.choiceCallback, + false); + } + else { + useCaseReview(reviewWithWarnCtx.operationType, + reviewWithWarnCtx.tagValueList, + reviewWithWarnCtx.icon, + reviewWithWarnCtx.reviewTitle, + reviewWithWarnCtx.reviewSubTitle, + reviewWithWarnCtx.finishTitle, + reviewWithWarnCtx.tipBox, + reviewWithWarnCtx.choiceCallback, + false, + false); + } + } + } + // top-right button in initial warning page + else if (token == WARNING_BUTTON_TOKEN) { + // start at first level of security report + reviewWithWarnCtx.securityReportLevel = 1; + // if preset is used + if (reviewWithWarnCtx.warning->predefinedSet) { + displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet); + } + else { + // use customized warning + const nbgl_warningDetails_t *details = (reviewWithWarnCtx.isIntro) + ? reviewWithWarnCtx.warning->introDetails + : reviewWithWarnCtx.warning->reviewDetails; + displayCustomizedSecurityReport(details); + } + } +} + // called when skip button is touched in footer, during forward only review static void displaySkipWarning(void) { @@ -1718,51 +1825,283 @@ static void bundleNavReviewStreamingChoice(bool confirm) } } -// function called when the warning page of Blind Signing review buttons are pressed -static void blindSigningWarningCallback(bool confirm) +// function used to display the security level page in modal +// it can be either a single page with text or QR code, or a list of touchable bars leading +// to single pages +static void displaySecurityReport(uint32_t set) { - if (confirm) { // top button to exit - blindSigningContext.choiceCallback(false); + nbgl_layoutDescription_t layoutDescription = {.modal = true, + .withLeftBorder = true, + .onActionCallback = modalLayoutTouchCallback, + .tapActionText = NULL}; + nbgl_layoutHeader_t headerDesc = {.type = HEADER_BACK_AND_TEXT, + .separationLine = true, + .backAndText.icon = NULL, + .backAndText.tuneId = TUNE_TAP_CASUAL, + .backAndText.token = DISMISS_WARNING_TOKEN}; + uint8_t i; + uint8_t nbWarnings = 0; + + reviewWithWarnCtx.modalLayout = nbgl_layoutGet(&layoutDescription); + + for (i = 0; i < NB_WARNING_TYPES; i++) { + if (reviewWithWarnCtx.warning->predefinedSet & (1 << i)) { + nbWarnings++; + } + } + + if ((reviewWithWarnCtx.securityReportLevel == 1) && (nbWarnings > 1)) { + // if more than one warning warning, so use a list of touchable bars + for (i = 0; i < NB_WARNING_TYPES; i++) { + if (reviewWithWarnCtx.warning->predefinedSet & (1 << i)) { + nbgl_layoutBar_t bar; + bar.text = securityReportItems[i].text; + bar.subText = securityReportItems[i].subText; + 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; + nbgl_layoutAddTouchableBar(reviewWithWarnCtx.modalLayout, &bar); + nbgl_layoutAddSeparationLine(reviewWithWarnCtx.modalLayout); + } + } + headerDesc.backAndText.text = "Security report"; + nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc); + nbgl_layoutDraw(reviewWithWarnCtx.modalLayout); + nbgl_refresh(); + return; } - else { // bottom button to continue to review - if (blindSigningContext.isStreaming) { - useCaseReviewStreamingStart(blindSigningContext.operationType, - blindSigningContext.icon, - blindSigningContext.reviewTitle, - blindSigningContext.reviewSubTitle, - blindSigningContext.choiceCallback, - false); + 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); +#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); + headerDesc.separationLine = false; + } + } + 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"; + nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info); + 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 = "url.com/od24xz", + .text1 = "url.com/od24xz", + .text2 = "Scan to view the threat report from Blockaid.", + .centered = true, + .offsetY = 0}; + nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &qrCode); +#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.smallTitle = "Known drainer contract"; + info.description + = "This transaction was scanned as malicious by Web3 Checks.\n\n" + "View full Blockaid report:\nurl.com/od24xz"; + nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info); + headerDesc.separationLine = false; + } + } + else if (set & (1 << W3C_LOSING_SWAP_WARN)) { + if (reviewWithWarnCtx.isIntro) { +#ifdef NBGL_QRCODE + // display a QR Code if in intro + nbgl_layoutQRCode_t qrCode = {.url = "url.com/od24xz", + .text1 = "url.com/od24xz", + .text2 = "Scan to view the risk report from Blockaid.", + .centered = true, + .offsetY = 0}; + nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &qrCode); +#endif // NBGL_QRCODE + headerDesc.backAndText.text = "Web3 Checks risk report"; } else { - useCaseReview(blindSigningContext.operationType, - blindSigningContext.tagValueList, - blindSigningContext.icon, - blindSigningContext.reviewTitle, - blindSigningContext.reviewSubTitle, - blindSigningContext.finishTitle, - blindSigningContext.tipBox, - blindSigningContext.choiceCallback, - false, - false); + // display a centered if in review + nbgl_contentCenter_t info = {0}; + info.icon = &C_Warning_64px; + info.title = "Risk detected"; + info.smallTitle = "Losing swap"; + info.description + = "This transaction was scanned as risky by Web3 Checks.\n\n" + "View full Blockaid report:\\nurl.com/od24xz"; + nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info); + headerDesc.separationLine = false; } } + else { + } + nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc); + nbgl_layoutDraw(reviewWithWarnCtx.modalLayout); + nbgl_refresh(); +} + +// function used to display the security level page in modal +// it can be either a single page with text or QR code, or a list of touchable bars leading +// to single pages +static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details) +{ + nbgl_layoutDescription_t layoutDescription = {.modal = true, + .withLeftBorder = true, + .onActionCallback = modalLayoutTouchCallback, + .tapActionText = NULL}; + nbgl_layoutHeader_t headerDesc = {.type = HEADER_BACK_AND_TEXT, + .separationLine = true, + .backAndText.icon = NULL, + .backAndText.tuneId = TUNE_TAP_CASUAL, + .backAndText.token = DISMISS_WARNING_TOKEN}; + uint8_t i; + + reviewWithWarnCtx.modalLayout = nbgl_layoutGet(&layoutDescription); + headerDesc.backAndText.text = details->title; + nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc); + if (details->type == BAR_LIST_WARNING) { + // if more than one warning warning, so use a list of touchable bars + for (i = 0; i < details->barList.nbBars; i++) { + nbgl_layoutBar_t bar; + bar.text = details->barList.texts[i]; + bar.subText = details->barList.subTexts[i]; + bar.iconRight = &PUSH_ICON; + bar.iconLeft = details->barList.icons[i]; + bar.token = FIRST_WARN_BAR_TOKEN + i; + bar.tuneId = TUNE_TAP_CASUAL; + bar.large = false; + bar.inactive = false; + nbgl_layoutAddTouchableBar(reviewWithWarnCtx.modalLayout, &bar); + nbgl_layoutAddSeparationLine(reviewWithWarnCtx.modalLayout); + } + } + else if (details->type == QRCODE_WARNING) { +#ifdef NBGL_QRCODE + // display a QR Code + nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &details->qrCode); +#endif // NBGL_QRCODE + headerDesc.backAndText.text = details->title; + } + else if (details->type == CENTERED_INFO_WARNING) { + // display a centered info + nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &details->centeredInfo); + headerDesc.separationLine = false; + } + nbgl_layoutDraw(reviewWithWarnCtx.modalLayout); + nbgl_refresh(); } -// function used to display the warning page when starting a Bling Signing review -static void blindSigningWarning(void) +// function used to display the initial warning page when starting a "review with warning" +static void displayInitialWarning(void) { // Play notification sound #ifdef HAVE_PIEZO_SOUND io_seproxyhal_play_tune(TUNE_LOOK_AT_ME); #endif // HAVE_PIEZO_SOUND - nbgl_useCaseChoice(&C_Warning_64px, - "Blind signing ahead", - "The details of this transaction or message are not fully verifiable. If " - "you sign it, you could lose all " - "your assets.", - "Back to safety", - "Continue anyway", - blindSigningWarningCallback); + nbgl_layoutDescription_t layoutDescription; + nbgl_layoutChoiceButtons_t buttonsInfo = {.bottomText = "Continue anyway", + .token = WARNING_CHOICE_TOKEN, + .topText = "Back to safety", + .style = ROUNDED_AND_FOOTER_STYLE, + .tuneId = TUNE_TAP_CASUAL}; + nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY, + .separationLine = false, + .emptySpace.height = MEDIUM_CENTERING_HEADER}; + + reviewWithWarnCtx.isIntro = true; + + layoutDescription.modal = false; + layoutDescription.withLeftBorder = true; + + layoutDescription.onActionCallback = layoutTouchCallback; + layoutDescription.tapActionText = NULL; + + layoutDescription.ticker.tickerCallback = NULL; + 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)) + && ((reviewWithWarnCtx.warning->predefinedSet != 0) + || (reviewWithWarnCtx.warning->introTopRightIcon != NULL))) { + if (reviewWithWarnCtx.warning->predefinedSet != 0) { + nbgl_layoutAddTopRightButton( + reviewWithWarnCtx.layoutCtx, &PRIVACY_ICON, WARNING_BUTTON_TOKEN, TUNE_TAP_CASUAL); + } + else { + 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_THREAT_DETECTED_WARN)) { + info.title = "Threat detected"; + info.smallTitle = "Known drainer contract"; + info.description = "This transaction was scanned as malicious by Web3 Checks."; + } + else { + info.title = "Dangerous transaction"; + info.description + = "This transaction cannot be fully decoded, and was not verified by Web3 Checks."; + } + nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, &info); + } + else if (reviewWithWarnCtx.warning->info != NULL) { + // if no predefined content, use custom one + nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, reviewWithWarnCtx.warning->info); + } + + nbgl_layoutDraw(reviewWithWarnCtx.layoutCtx); + nbgl_refresh(); } // function to factorize code for all simple reviews @@ -3042,20 +3381,64 @@ void nbgl_useCaseReviewBlindSigning(nbgl_operationType_t operationT const nbgl_tipBox_t *tipBox, nbgl_choiceCallback_t choiceCallback) { - memset(&blindSigningContext, 0, sizeof(blindSigningContext)); - - blindSigningContext.isStreaming = false; - blindSigningContext.operationType = operationType | BLIND_OPERATION; - blindSigningContext.tagValueList = tagValueList; - blindSigningContext.icon = icon; - blindSigningContext.reviewTitle = reviewTitle; - blindSigningContext.reviewSubTitle = reviewSubTitle; - blindSigningContext.finishTitle = finishTitle; - blindSigningContext.tipBox = tipBox; - blindSigningContext.choiceCallback = choiceCallback; + nbgl_useCaseReviewWithWarning(operationType, + tagValueList, + icon, + reviewTitle, + reviewSubTitle, + finishTitle, + tipBox, + &blindSigningWarning, + choiceCallback); +} - blindSigningWarning(); +/** + * @brief Draws a flow of pages of a review requiring 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 + * + * Navigation operates with either swipe or navigation + * keys at bottom right. The last page contains a long-press button with the given finishTitle and + * the given icon. + * @note All tag/value pairs are provided in the API and the number of pages is automatically + * computed, the last page being a long press one + * + * @param operationType type of operation (Operation, Transaction, Message) + * @param tagValueList list of tag/value pairs + * @param icon icon used on first and last review page + * @param reviewTitle string used in the first review page + * @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 choiceCallback callback called when operation is accepted (param is true) or rejected + * (param is false) + */ +void nbgl_useCaseReviewWithWarning(nbgl_operationType_t operationType, + const nbgl_contentTagValueList_t *tagValueList, + const nbgl_icon_details_t *icon, + const char *reviewTitle, + const char *reviewSubTitle, + const char *finishTitle, + const nbgl_tipBox_t *tipBox, + const nbgl_warning_t *warning, + nbgl_choiceCallback_t choiceCallback) +{ + memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx)); + reviewWithWarnCtx.isStreaming = false; + reviewWithWarnCtx.operationType = operationType | BLIND_OPERATION; + reviewWithWarnCtx.tagValueList = tagValueList; + reviewWithWarnCtx.icon = icon; + reviewWithWarnCtx.reviewTitle = reviewTitle; + reviewWithWarnCtx.reviewSubTitle = reviewSubTitle; + reviewWithWarnCtx.finishTitle = finishTitle; + reviewWithWarnCtx.tipBox = tipBox; + reviewWithWarnCtx.warning = warning; + reviewWithWarnCtx.choiceCallback = choiceCallback; + + displayInitialWarning(); } + /** * @brief Draws a flow of pages of a light review. Navigation operates with either swipe or * navigation keys at bottom right. The last page contains a button/footer with the given @@ -3169,16 +3552,17 @@ void nbgl_useCaseReviewStreamingBlindSigningStart(nbgl_operationType_t ope const char *reviewSubTitle, nbgl_choiceCallback_t choiceCallback) { - memset(&blindSigningContext, 0, sizeof(blindSigningContext)); + memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx)); - blindSigningContext.isStreaming = true; - blindSigningContext.operationType = operationType | BLIND_OPERATION; - blindSigningContext.icon = icon; - blindSigningContext.reviewTitle = reviewTitle; - blindSigningContext.reviewSubTitle = reviewSubTitle; - blindSigningContext.choiceCallback = choiceCallback; + reviewWithWarnCtx.isStreaming = true; + reviewWithWarnCtx.operationType = operationType | BLIND_OPERATION; + reviewWithWarnCtx.icon = icon; + reviewWithWarnCtx.reviewTitle = reviewTitle; + reviewWithWarnCtx.reviewSubTitle = reviewSubTitle; + reviewWithWarnCtx.choiceCallback = choiceCallback; + reviewWithWarnCtx.warning = &blindSigningWarning; - blindSigningWarning(); + displayInitialWarning(); } /** diff --git a/unit-tests/lib_nbgl/CMakeLists.txt b/unit-tests/lib_nbgl/CMakeLists.txt index 815bc2a08..5ee877e25 100644 --- a/unit-tests/lib_nbgl/CMakeLists.txt +++ b/unit-tests/lib_nbgl/CMakeLists.txt @@ -78,7 +78,7 @@ add_library(nbgl_obj SHARED ../../lib_nbgl/src/nbgl_obj.c) target_link_libraries(test_nbgl_fonts PUBLIC cmocka gcov nbgl_fonts nbgl_stubs) target_link_libraries(test_nbgl_obj_pool PUBLIC cmocka gcov nbgl_obj_pool) -target_link_libraries(test_nbgl_screen PUBLIC cmocka gcov nbgl_screen nbgl_obj_pool) +target_link_libraries(test_nbgl_screen PUBLIC cmocka gcov nbgl_screen nbgl_obj_pool nbgl_stubs) target_link_libraries(test_nbgl_obj PUBLIC cmocka gcov nbgl_obj nbgl_screen nbgl_obj_pool nbgl_fonts nbgl_stubs) add_test(test_nbgl_fonts test_nbgl_fonts) diff --git a/unit-tests/lib_nbgl/nbgl_stubs.c b/unit-tests/lib_nbgl/nbgl_stubs.c index bdd133690..9ac409689 100644 --- a/unit-tests/lib_nbgl/nbgl_stubs.c +++ b/unit-tests/lib_nbgl/nbgl_stubs.c @@ -8,7 +8,9 @@ #include #include #include "nbgl_fonts.h" +#include "nbgl_touch.h" #include "ux_loc.h" +#include "os_helpers.h" #include "os_task.h" void fetch_language_packs(void); @@ -59,3 +61,12 @@ void fetch_language_packs(void) nbgl_refreshUnicodeFont(language_pack); } } + +void nbgl_touchHandler(bool fromUx, + nbgl_touchStatePosition_t *touchStatePosition, + uint32_t currentTime) +{ + UNUSED(fromUx); + UNUSED(touchStatePosition); + UNUSED(currentTime); +} diff --git a/unit-tests/lib_nbgl/test_nbgl_screen.c b/unit-tests/lib_nbgl/test_nbgl_screen.c index 3ef09bff4..8f542b207 100644 --- a/unit-tests/lib_nbgl/test_nbgl_screen.c +++ b/unit-tests/lib_nbgl/test_nbgl_screen.c @@ -9,6 +9,7 @@ #include #include "nbgl_screen.h" #include "nbgl_debug.h" +#include "nbgl_touch.h" #include "ux_loc.h" #include "os_task.h" @@ -46,6 +47,15 @@ void nbgl_objDraw(nbgl_obj_t *obj) UNUSED(obj); } +void nbgl_touchHandler(bool fromUx, + nbgl_touchStatePosition_t *touchStatePosition, + uint32_t currentTime) +{ + UNUSED(fromUx); + UNUSED(touchStatePosition); + UNUSED(currentTime); +} + static void test_push_pop(void **state __attribute__((unused))) { nbgl_obj_t **elements0, **elements1, **elements2, **elements3;