Skip to content

Commit f8afb3b

Browse files
Merge pull request #173 from LedgerHQ/nbgl-add-use-case-choice
Nbgl add use case choice
2 parents 1e7b585 + 83cd9d9 commit f8afb3b

File tree

4 files changed

+193
-21
lines changed

4 files changed

+193
-21
lines changed

ledger_device_sdk/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ledger_device_sdk"
3-
version = "1.12.0"
3+
version = "1.13.0"
44
authors = ["yhql", "yogh333", "agrojean-ledger", "kingofpayne"]
55
edition = "2021"
66
license.workspace = true
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#![no_std]
2+
#![no_main]
3+
4+
// Force boot section to be embedded in
5+
use ledger_device_sdk as _;
6+
7+
use include_gif::include_gif;
8+
use ledger_device_sdk::io::*;
9+
use ledger_device_sdk::nbgl::{init_comm, NbglChoice, NbglGlyph};
10+
use ledger_secure_sdk_sys::*;
11+
12+
#[panic_handler]
13+
fn panic(_: &core::panic::PanicInfo) -> ! {
14+
exit_app(1);
15+
}
16+
17+
#[no_mangle]
18+
extern "C" fn sample_main() {
19+
unsafe {
20+
nbgl_refreshReset();
21+
}
22+
23+
let mut comm = Comm::new();
24+
// Initialize reference to Comm instance for NBGL
25+
// API calls.
26+
init_comm(&mut comm);
27+
28+
// Load glyph from 64x64 4bpp gif file with include_gif macro. Creates an NBGL compatible glyph.
29+
const WARNING: NbglGlyph =
30+
NbglGlyph::from_include(include_gif!("icons/Warning_64px.gif", NBGL));
31+
32+
let back_to_safety = NbglChoice::new().glyph(&WARNING)
33+
.status_text(Some("Transaction rejected"),None)
34+
.show(
35+
"Security risk detected",
36+
"It may not be safe to sign this transaction. To continue, you'll need to review the risk.",
37+
"Back to safety",
38+
"Review risk",
39+
);
40+
41+
if !back_to_safety {
42+
NbglChoice::new()
43+
.status_text(Some("Transaction confirmed"), Some("Transaction rejected"))
44+
.show(
45+
"The transaction cannot be trusted",
46+
"Your Ledger cannot decode this transaction. If you sign it, you could be authorizing malicious actions that can drain your wallet.\n\nLearn more: ledger.com/e8",
47+
"I accept the risk",
48+
"Reject transaction"
49+
);
50+
}
51+
}
571 Bytes
Loading

ledger_device_sdk/src/nbgl.rs

Lines changed: 141 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use alloc::ffi::CString;
66
use alloc::vec::Vec;
77
use core::ffi::{c_char, c_int};
88
use core::mem::transmute;
9+
use include_gif::include_gif;
910
use ledger_secure_sdk_sys::*;
1011

1112
#[no_mangle]
@@ -307,7 +308,7 @@ impl<'a> NbglHomeAndSettings<'a> {
307308
nbContents: if self.nb_settings > 0 { 1 } else { 0 },
308309
};
309310

310-
match ledger_secure_sdk_sys::ux_sync_homeAndSettings(
311+
match ux_sync_homeAndSettings(
311312
info_contents[0],
312313
&icon as *const nbgl_icon_details_t,
313314
core::ptr::null(),
@@ -316,7 +317,7 @@ impl<'a> NbglHomeAndSettings<'a> {
316317
&info_list as *const nbgl_contentInfoList_t,
317318
core::ptr::null(),
318319
) {
319-
ledger_secure_sdk_sys::UX_SYNC_RET_APDU_RECEIVED => {
320+
UX_SYNC_RET_APDU_RECEIVED => {
320321
if let Some(comm) = COMM_REF.as_mut() {
321322
if let Some(value) = comm.check_event() {
322323
return value;
@@ -332,6 +333,35 @@ impl<'a> NbglHomeAndSettings<'a> {
332333
}
333334
}
334335

336+
/// Private helper function to display a warning screen when a transaction
337+
/// is reviewed in "blind" mode. The user can choose to go back to safety
338+
/// or review the risk. If the user chooses to review the risk, a second screen
339+
/// is displayed with the option to accept the risk or reject the transaction.
340+
/// Used in NbglReview and NbglStreamingReview.
341+
fn show_blind_warning() -> bool {
342+
const WARNING: NbglGlyph =
343+
NbglGlyph::from_include(include_gif!("icons/Warning_64px.gif", NBGL));
344+
345+
let back_to_safety = NbglChoice::new().glyph(&WARNING).show(
346+
"Security risk detected",
347+
"It may not be safe to sign this transaction. To continue, you'll need to review the risk.",
348+
"Back to safety",
349+
"Review risk",
350+
);
351+
352+
if !back_to_safety {
353+
NbglChoice::new()
354+
.show(
355+
"The transaction cannot be trusted",
356+
"Your Ledger cannot decode this transaction. If you sign it, you could be authorizing malicious actions that can drain your wallet.\n\nLearn more: ledger.com/e8",
357+
"I accept the risk",
358+
"Reject transaction"
359+
)
360+
} else {
361+
false
362+
}
363+
}
364+
335365
/// A wrapper around the synchronous NBGL ux_sync_review C API binding.
336366
/// Used to display transaction review screens.
337367
pub struct NbglReview<'a> {
@@ -420,8 +450,15 @@ impl<'a> NbglReview<'a> {
420450
None => nbgl_icon_details_t::default(),
421451
};
422452

453+
if self.blind {
454+
if !show_blind_warning() {
455+
ux_sync_reviewStatus(self.tx_type.to_message(false));
456+
return false;
457+
}
458+
}
459+
423460
// Show the review on the device.
424-
let sync_ret = ledger_secure_sdk_sys::ux_sync_review(
461+
let sync_ret = ux_sync_review(
425462
self.tx_type.to_c_type(self.blind, false),
426463
&tag_value_list as *const nbgl_contentTagValueList_t,
427464
&icon as *const nbgl_icon_details_t,
@@ -432,12 +469,12 @@ impl<'a> NbglReview<'a> {
432469

433470
// Return true if the user approved the transaction, false otherwise.
434471
match sync_ret {
435-
ledger_secure_sdk_sys::UX_SYNC_RET_APPROVED => {
436-
ledger_secure_sdk_sys::ux_sync_reviewStatus(self.tx_type.to_message(true));
472+
UX_SYNC_RET_APPROVED => {
473+
ux_sync_reviewStatus(self.tx_type.to_message(true));
437474
return true;
438475
}
439476
_ => {
440-
ledger_secure_sdk_sys::ux_sync_reviewStatus(self.tx_type.to_message(false));
477+
ux_sync_reviewStatus(self.tx_type.to_message(false));
441478
return false;
442479
}
443480
}
@@ -870,18 +907,12 @@ impl NbglGenericReview {
870907

871908
// Return true if the user approved the transaction, false otherwise.
872909
match sync_ret {
873-
ledger_secure_sdk_sys::UX_SYNC_RET_APPROVED => {
874-
ledger_secure_sdk_sys::ux_sync_status(
875-
succeed_cstring.as_ptr() as *const c_char,
876-
true,
877-
);
910+
UX_SYNC_RET_APPROVED => {
911+
ux_sync_status(succeed_cstring.as_ptr() as *const c_char, true);
878912
return true;
879913
}
880914
_ => {
881-
ledger_secure_sdk_sys::ux_sync_status(
882-
rejected_cstring.as_ptr() as *const c_char,
883-
false,
884-
);
915+
ux_sync_status(rejected_cstring.as_ptr() as *const c_char, false);
885916
return false;
886917
}
887918
}
@@ -929,6 +960,13 @@ impl NbglStreamingReview {
929960
let title = CString::new(title).unwrap();
930961
let subtitle = CString::new(subtitle).unwrap();
931962

963+
if self.blind {
964+
if !show_blind_warning() {
965+
ux_sync_reviewStatus(self.tx_type.to_message(false));
966+
return false;
967+
}
968+
}
969+
932970
let sync_ret = ux_sync_reviewStreamingStart(
933971
self.tx_type.to_c_type(self.blind, false),
934972
&self.icon as *const nbgl_icon_details_t,
@@ -1001,7 +1039,7 @@ impl NbglStreamingReview {
10011039

10021040
// Return true if the user approved the transaction, false otherwise.
10031041
match sync_ret {
1004-
ledger_secure_sdk_sys::UX_SYNC_RET_APPROVED => {
1042+
UX_SYNC_RET_APPROVED => {
10051043
ux_sync_reviewStatus(self.tx_type.to_message(true));
10061044
return true;
10071045
}
@@ -1063,12 +1101,12 @@ impl<'a> NbglAddressReview<'a> {
10631101

10641102
// Return true if the user approved the address, false otherwise.
10651103
match sync_ret {
1066-
ledger_secure_sdk_sys::UX_SYNC_RET_APPROVED => {
1067-
ledger_secure_sdk_sys::ux_sync_reviewStatus(STATUS_TYPE_ADDRESS_VERIFIED);
1104+
UX_SYNC_RET_APPROVED => {
1105+
ux_sync_reviewStatus(STATUS_TYPE_ADDRESS_VERIFIED);
10681106
return true;
10691107
}
1070-
ledger_secure_sdk_sys::UX_SYNC_RET_REJECTED => {
1071-
ledger_secure_sdk_sys::ux_sync_reviewStatus(STATUS_TYPE_ADDRESS_REJECTED);
1108+
UX_SYNC_RET_REJECTED => {
1109+
ux_sync_reviewStatus(STATUS_TYPE_ADDRESS_REJECTED);
10721110
return false;
10731111
}
10741112
_ => {
@@ -1079,6 +1117,89 @@ impl<'a> NbglAddressReview<'a> {
10791117
}
10801118
}
10811119

1120+
/// A wrapper around the synchronous NBGL ux_sync_status C API binding.
1121+
/// Draws a generic choice page, described in a centered info (with configurable icon),
1122+
/// thanks to a button and a footer at the bottom of the page.
1123+
pub struct NbglChoice<'a> {
1124+
glyph: Option<&'a NbglGlyph<'a>>,
1125+
confirmed_text: Option<CString>,
1126+
cancelled_text: Option<CString>,
1127+
}
1128+
1129+
impl<'a> NbglChoice<'a> {
1130+
pub fn new() -> NbglChoice<'a> {
1131+
NbglChoice {
1132+
glyph: None,
1133+
confirmed_text: None,
1134+
cancelled_text: None,
1135+
}
1136+
}
1137+
1138+
pub fn glyph(self, glyph: &'a NbglGlyph) -> NbglChoice<'a> {
1139+
NbglChoice {
1140+
glyph: Some(glyph),
1141+
..self
1142+
}
1143+
}
1144+
1145+
pub fn status_text(
1146+
self,
1147+
confirmed: Option<&'a str>,
1148+
cancelled: Option<&'a str>,
1149+
) -> NbglChoice<'a> {
1150+
let confirmed_text = confirmed.map(|s| CString::new(s).unwrap());
1151+
let cancelled_text = cancelled.map(|s| CString::new(s).unwrap());
1152+
NbglChoice {
1153+
confirmed_text,
1154+
cancelled_text,
1155+
..self
1156+
}
1157+
}
1158+
1159+
pub fn show(
1160+
self,
1161+
message: &str,
1162+
sub_message: &str,
1163+
confirm_text: &str,
1164+
cancel_text: &str,
1165+
) -> bool {
1166+
unsafe {
1167+
let icon: nbgl_icon_details_t = match self.glyph {
1168+
Some(g) => g.into(),
1169+
None => nbgl_icon_details_t::default(),
1170+
};
1171+
let message = CString::new(message).unwrap();
1172+
let sub_message = CString::new(sub_message).unwrap();
1173+
let confirm_text = CString::new(confirm_text).unwrap();
1174+
let cancel_text = CString::new(cancel_text).unwrap();
1175+
1176+
let sync_ret = ux_sync_choice(
1177+
&icon as *const nbgl_icon_details_t,
1178+
message.as_ptr() as *const c_char,
1179+
sub_message.as_ptr() as *const c_char,
1180+
confirm_text.as_ptr() as *const c_char,
1181+
cancel_text.as_ptr() as *const c_char,
1182+
);
1183+
1184+
// Return true if the user approved the transaction, false otherwise.
1185+
match sync_ret {
1186+
UX_SYNC_RET_APPROVED => {
1187+
if let Some(text) = self.confirmed_text {
1188+
ux_sync_status(text.as_ptr() as *const c_char, true);
1189+
}
1190+
return true;
1191+
}
1192+
_ => {
1193+
if let Some(text) = self.cancelled_text {
1194+
ux_sync_status(text.as_ptr() as *const c_char, false);
1195+
}
1196+
return false;
1197+
}
1198+
}
1199+
}
1200+
}
1201+
}
1202+
10821203
#[derive(Copy, Clone)]
10831204
pub enum TuneIndex {
10841205
Reserved,

0 commit comments

Comments
 (0)