diff --git a/.gitignore b/.gitignore index 2904277..3ff0d93 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ stax.hex stax.json flex.hex flex.json +ledger/ +app/build diff --git a/Makefile b/Makefile index 1801047..17b718f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -version := 3.34.0 +version := latest ledger_app_builder = ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:$(version) ledger_app_dev_tools = ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:$(version) diff --git a/app/Cargo.lock b/app/Cargo.lock index 899ff5d..7c01fe1 100644 --- a/app/Cargo.lock +++ b/app/Cargo.lock @@ -351,9 +351,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "ledger_device_sdk" -version = "1.15.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff9990e68e73870cac570e9c45f1de618419d5b4c2b3923b51d23428cd7b9c8" +checksum = "e5994587541e97058c8acfac99dbd3130b65d7daba2221d4917f0be72de0ce5c" dependencies = [ "const-zero", "include_gif", @@ -366,9 +366,9 @@ dependencies = [ [[package]] name = "ledger_secure_sdk_sys" -version = "1.4.4" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bc18740897f4e3b28025e97c31210dd36a69fe1114d66fe9372938d6f3673b6" +checksum = "bc289cbd2e3d451002a1cba0c71aeee97a5c7745e0570f05a665a8d9f8971688" dependencies = [ "bindgen", "cc", diff --git a/app/Cargo.toml b/app/Cargo.toml index 6856d20..06148e6 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -6,8 +6,8 @@ edition = "2021" license = "MIT" [dependencies] -ledger_device_sdk = "=1.15.0" -ledger_secure_sdk_sys = "=1.4.4" +ledger_device_sdk = "1.17.1" +ledger_secure_sdk_sys = "1.5.0" utils= { path = "../utils" } numtoa = "=0.2.4" shlex = { version = "1.3.0", default-features = false } # patch for ledger_device_sdk dependency diff --git a/app/src/handler.rs b/app/src/handler.rs index dbfbf6a..8ee643f 100644 --- a/app/src/handler.rs +++ b/app/src/handler.rs @@ -43,7 +43,7 @@ pub fn handle_apdu( ins: Ins, sign_tx_context: &mut SignTxContext, tx_reviewer: &mut TxReviewer, -) -> Result<(), io::Reply> { +) -> Result { if comm.rx == 0 { return Err(ErrorCode::BadLen.into()); } @@ -118,7 +118,7 @@ pub fn handle_apdu( }; match handle_sign_tx(apdu_header, data, sign_tx_context, tx_reviewer) { Ok(()) if !sign_tx_context.is_complete() => { - return Ok(()); + return Ok(false); } Ok(()) => { // The transaction is signed when all the data is processed @@ -129,7 +129,7 @@ pub fn handle_apdu( let result = match sign_result { Ok((signature_buf, length, _)) => { comm.append(&signature_buf[..length as usize]); - Ok(()) + Ok(true) } Err(code) => Err(code.into()), }; @@ -143,7 +143,7 @@ pub fn handle_apdu( } } } - Ok(()) + Ok(true) } // The transaction is split into multiple APDU commands, consisting of token metadata APDU and tx APDU commands diff --git a/app/src/ledger_sdk_stub/mod.rs b/app/src/ledger_sdk_stub/mod.rs deleted file mode 100644 index a192374..0000000 --- a/app/src/ledger_sdk_stub/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -#[cfg(not(any(target_os = "stax", target_os = "flex")))] -pub mod multi_field_review; - -#[cfg(any(target_os = "stax", target_os = "flex"))] -pub mod nbgl_display; - -#[cfg(any(target_os = "stax", target_os = "flex"))] -pub mod nbgl_review; - -pub mod nvm; -pub mod swapping_buffer; diff --git a/app/src/ledger_sdk_stub/multi_field_review.rs b/app/src/ledger_sdk_stub/multi_field_review.rs deleted file mode 100644 index 830030a..0000000 --- a/app/src/ledger_sdk_stub/multi_field_review.rs +++ /dev/null @@ -1,405 +0,0 @@ -use ledger_device_sdk::ui::{ - bagls::{self, Icon, Label}, - bitmaps::Glyph, - fonts::OPEN_SANS, - gadgets::{clear_screen, get_event}, - layout::{self, Draw, Layout, Location, StringPlace}, - screen_util::screen_update, -}; -use ledger_secure_sdk_sys::buttons::{ButtonEvent, ButtonsState}; -use numtoa::NumToA; - -const MAX_CHAR_PER_LINE: usize = 17; - -// The code is from ledger-rust-sdk: https://github.com/LedgerHQ/ledger-device-rust-sdk/blob/3c22c1c1b5e2d909e34409fc92cfeed775541a63/ledger_device_sdk/src/ui/gadgets.rs#L803. -// We've made modifications here to ensure the `Approve` page comes before the `Reject` page. -pub struct MultiFieldReview<'a> { - fields: &'a [Field<'a>], - review_message: &'a [&'a str], - review_glyph: Option<&'a Glyph<'a>>, - validation_message: [&'a str; 2], - validation_glyph: Option<&'a Glyph<'a>>, - cancel_message: &'a str, - cancel_glyph: Option<&'a Glyph<'a>>, -} - -impl<'a> MultiFieldReview<'a> { - pub fn new( - fields: &'a [Field<'a>], - review_message: &'a [&'a str], - review_glyph: Option<&'a Glyph<'a>>, - validation_message: [&'a str; 2], - validation_glyph: Option<&'a Glyph<'a>>, - cancel_message: &'a str, - cancel_glyph: Option<&'a Glyph<'a>>, - ) -> Self { - MultiFieldReview { - fields, - review_message, - review_glyph, - validation_message, - validation_glyph, - cancel_message, - cancel_glyph, - } - } - - pub fn simple( - fields: &'a [Field<'a>], - review_message: &'a [&'a str], - review_glyph: Option<&'a Glyph<'a>>, - validation_message: &'a str, - validation_glyph: Option<&'a Glyph<'a>>, - cancel_message: &'a str, - cancel_glyph: Option<&'a Glyph<'a>>, - ) -> Self { - MultiFieldReview::new( - fields, - review_message, - review_glyph, - [validation_message, ""], - validation_glyph, - cancel_message, - cancel_glyph, - ) - } - - pub fn show(&self) -> bool { - let first_page_opt = match self.review_message.len() { - 0 => None, - 1 => Some(Page::new( - PageStyle::PictureBold, - [self.review_message[0], ""], - self.review_glyph, - )), - _ => Some(Page::new( - PageStyle::PictureNormal, - [self.review_message[0], self.review_message[1]], - self.review_glyph, - )), - }; - - display_first_page(&first_page_opt); - - let validation_page = Page::new( - PageStyle::PictureBold, - self.validation_message, - self.validation_glyph, - ); - let cancel_page = Page::new( - PageStyle::PictureBold, - [self.cancel_message, ""], - self.cancel_glyph, - ); - - let mut cur_page = 0usize; - let mut direction = ButtonEvent::RightButtonRelease; - - loop { - match cur_page { - cancel if cancel == self.fields.len() + 1 => { - let mut buttons = ButtonsState::new(); - clear_screen(); - bagls::LEFT_ARROW.display(); - cancel_page.place(); - screen_update(); - loop { - match get_event(&mut buttons) { - Some(ButtonEvent::LeftButtonRelease) => { - cur_page = cur_page.saturating_sub(1); - break; - } - Some(ButtonEvent::BothButtonsRelease) => return false, - _ => (), - } - } - } - validation if validation == self.fields.len() => { - let mut buttons = ButtonsState::new(); - clear_screen(); - bagls::LEFT_ARROW.display(); - bagls::RIGHT_ARROW.display(); - validation_page.place(); - screen_update(); - loop { - match get_event(&mut buttons) { - Some(ButtonEvent::LeftButtonRelease) => { - cur_page = cur_page.saturating_sub(1); - if cur_page == 0 && self.fields.is_empty() { - display_first_page(&first_page_opt); - } else { - direction = ButtonEvent::LeftButtonRelease; - } - break; - } - Some(ButtonEvent::RightButtonRelease) => { - cur_page += 1; - break; - } - Some(ButtonEvent::BothButtonsRelease) => return true, - _ => (), - } - } - } - _ => { - direction = self.fields[cur_page] - .event_loop(direction, cur_page == 0 && first_page_opt.is_none()); - match direction { - ButtonEvent::LeftButtonRelease => { - if cur_page == 0 { - display_first_page(&first_page_opt); - direction = ButtonEvent::RightButtonRelease; - } else { - cur_page -= 1; - } - } - ButtonEvent::RightButtonRelease => { - cur_page += 1; - } - _ => (), - } - } - } - } - } -} - -pub fn display_first_page(page_opt: &Option) { - match page_opt { - Some(page) => { - clear_screen(); - bagls::RIGHT_ARROW.display(); - page.place(); - screen_update(); - - let mut buttons = ButtonsState::new(); - loop { - if let Some(ButtonEvent::RightButtonRelease) = get_event(&mut buttons) { - return; - } - } - } - None => (), - } -} - -pub struct Field<'a> { - pub name: &'a str, - pub value: &'a str, -} - -impl<'a> Field<'a> { - pub fn event_loop(&self, incoming_direction: ButtonEvent, is_first_field: bool) -> ButtonEvent { - let mut buttons = ButtonsState::new(); - let chunk_max_lines = layout::MAX_LINES - 1; - let max_size_per_page = chunk_max_lines * MAX_CHAR_PER_LINE; - let page_count = (self.value.len() + max_size_per_page - 1) / max_size_per_page; - - let mut cur_page = match incoming_direction { - ButtonEvent::LeftButtonRelease => page_count - 1, - ButtonEvent::RightButtonRelease => 0, - _ => 0, - }; - - // A closure to draw common elements of the screen - // cur_page passed as parameter to prevent borrowing - let draw = |page: usize| { - clear_screen(); - let mut chunks = [Label::default(); layout::MAX_LINES]; - for (i, chunk) in self - .value - .as_bytes() - .chunks(MAX_CHAR_PER_LINE) - .skip(page * chunk_max_lines) - .take(chunk_max_lines) - .enumerate() - { - chunks[1 + i] = Label::from(core::str::from_utf8(chunk).unwrap_or("")); - } - - let mut header_buf = [b' '; MAX_CHAR_PER_LINE + 4]; - - if page == 0 && MAX_CHAR_PER_LINE * chunk_max_lines > self.value.len() { - // There is a single page. Do not display counter `( x / n )` - header_buf[..self.name.len()].copy_from_slice(self.name.as_bytes()); - } else { - let mut buf_page = [0u8; 3]; - let mut buf_count = [0u8; 3]; - let page_str = (page + 1).numtoa_str(10, &mut buf_page); - let count_str = page_count.numtoa_str(10, &mut buf_count); - - concatenate( - &[&self.name, " (", &page_str, "/", &count_str, ")"], - &mut header_buf, - ); - } - let header = core::str::from_utf8(&header_buf) - .unwrap_or("") - .trim_end_matches(' '); - chunks[0] = Label::from(header).bold(); - - if !is_first_field { - bagls::LEFT_ARROW.display(); - } - bagls::RIGHT_ARROW.display(); - - chunks.place(Location::Middle, Layout::Centered, false); - - screen_update(); - }; - - draw(cur_page); - - loop { - match get_event(&mut buttons) { - Some(ButtonEvent::LeftButtonRelease) => { - if cur_page == 0 { - return ButtonEvent::LeftButtonRelease; - } - cur_page = cur_page.saturating_sub(1); - draw(cur_page); - } - Some(ButtonEvent::RightButtonRelease) => { - if cur_page + 1 == page_count { - return ButtonEvent::RightButtonRelease; - } - if cur_page + 1 < page_count { - cur_page += 1; - } - draw(cur_page); - } - Some(_) | None => (), - } - } - } -} - -// Function to concatenate multiple strings into a fixed-size array -fn concatenate(strings: &[&str], output: &mut [u8]) { - let mut offset = 0; - - for s in strings { - let s_len = s.len(); - let copy_len = core::cmp::min(s_len, output.len() - offset); - - if copy_len > 0 { - output[offset..offset + copy_len].copy_from_slice(&s.as_bytes()[..copy_len]); - offset += copy_len; - } else { - // If the output buffer is full, stop concatenating. - break; - } - } -} - -// This is a modified version of the Ledger Rust SDK code. -// The modifications were made to address the issue of the Ledger Rust SDK -// not supporting two lines of bold text. -#[derive(Copy, Clone, PartialEq, Default)] -pub enum PageStyle { - #[default] - PictureNormal, // Picture (should be 16x16) with two lines of text (page layout depends on device). - PictureBold, // Icon on top with one line of text on the bottom. - BoldNormal, // One line of bold text and one line of normal text. - Normal, // 2 lines of centered text. -} - -#[derive(Copy, Clone, Default)] -pub struct Page<'a> { - style: PageStyle, - label: [&'a str; 2], - glyph: Option<&'a Glyph<'a>>, -} - -// new_picture_normal -impl<'a> From<([&'a str; 2], &'a Glyph<'a>)> for Page<'a> { - fn from((label, glyph): ([&'a str; 2], &'a Glyph<'a>)) -> Page<'a> { - Page::new(PageStyle::PictureNormal, [label[0], label[1]], Some(glyph)) - } -} - -// new bold normal or new normal -impl<'a> From<([&'a str; 2], bool)> for Page<'a> { - fn from((label, bold): ([&'a str; 2], bool)) -> Page<'a> { - if bold { - Page::new(PageStyle::BoldNormal, [label[0], label[1]], None) - } else { - Page::new(PageStyle::Normal, [label[0], label[1]], None) - } - } -} - -// new picture bold -impl<'a> From<(&'a str, &'a Glyph<'a>)> for Page<'a> { - fn from((label, glyph): (&'a str, &'a Glyph<'a>)) -> Page<'a> { - let label = [label, ""]; - Page::new(PageStyle::PictureBold, label, Some(glyph)) - } -} - -impl<'a> Page<'a> { - pub const fn new(style: PageStyle, label: [&'a str; 2], glyph: Option<&'a Glyph<'a>>) -> Self { - Page { - style, - label, - glyph, - } - } - - pub fn place(&self) { - match self.style { - PageStyle::Normal => { - self.label.place(Location::Middle, Layout::Centered, false); - } - PageStyle::PictureNormal => { - let icon_x = 57; - let icon_y = 10; - self.label - .place(Location::Custom(28), Layout::Centered, false); - if let Some(glyph) = self.glyph { - let icon = Icon::from(glyph); - icon.set_x(icon_x).set_y(icon_y).display(); - } - } - PageStyle::PictureBold => { - let icon_x = 57; - let icon_y = 10; - self.label[0].place(Location::Custom(28), Layout::Centered, true); - self.label[1].place(Location::Custom(42), Layout::Centered, true); - if let Some(glyph) = self.glyph { - let icon = Icon::from(glyph); - icon.set_x(icon_x).set_y(icon_y).display(); - } - } - PageStyle::BoldNormal => { - let padding = 1; - let max_text_lines = 3; - let total_height = (OPEN_SANS[0].height * max_text_lines) as usize - + OPEN_SANS[1].height as usize - + 2 * padding as usize; - let mut cur_y = Location::Middle.get_y(total_height); - - self.label[0].place(Location::Custom(cur_y), Layout::Centered, true); - cur_y += OPEN_SANS[0].height as usize + 2 * padding as usize; - - // Display the second label as up to 3 lines of text - let mut indices = [(0, 0); 3]; - let len = self.label[1].len(); - for (i, indice) in indices.iter_mut().enumerate() { - let start = (i * MAX_CHAR_PER_LINE).min(len); - if start >= len { - break; // Break if we reach the end of the string - } - let end = (start + MAX_CHAR_PER_LINE).min(len); - *indice = (start, end); - (&self.label[1][start..end]).place( - Location::Custom(cur_y), - Layout::Centered, - false, - ); - cur_y += OPEN_SANS[0].height as usize + 2 * padding as usize; - } - } - } - } -} diff --git a/app/src/ledger_sdk_stub/nbgl_display.rs b/app/src/ledger_sdk_stub/nbgl_display.rs deleted file mode 100644 index 2d87f91..0000000 --- a/app/src/ledger_sdk_stub/nbgl_display.rs +++ /dev/null @@ -1,150 +0,0 @@ -// This file is a modified version of the Ledger Rust SDK code. -// The modifications were made to address the issue of the Ledger Rust SDK -// not supporting navigation to the settings page. -// To maintain consistency with the original Ledger Rust SDK code, -// we have disabled Clippy warnings for this file. -#![allow(clippy::all)] - -use crate::settings::{SETTINGS_DATA, SETTINGS_SIZE}; -use const_zero::const_zero; - -extern crate alloc; -use alloc::ffi::CString; -use alloc::vec::Vec; -use core::ffi::*; -use core::mem::transmute; -use include_gif::include_gif; -use ledger_device_sdk::io::{ApduHeader, Comm, Event, Reply}; -use ledger_device_sdk::nbgl::{NbglGlyph, TuneIndex}; -use ledger_device_sdk::nvm::{AtomicStorage, SingleStorage}; -use ledger_secure_sdk_sys::*; - -static mut NVM_REF: Option<&mut AtomicStorage<[u8; SETTINGS_SIZE]>> = None; -static mut SWITCH_ARRAY: [nbgl_contentSwitch_t; SETTINGS_SIZE] = - [unsafe { const_zero!(nbgl_contentSwitch_t) }; SETTINGS_SIZE]; -static mut SETTINGS_UPDATED: bool = false; - -/// Information fields name to display in the dedicated -/// page of the home screen. -const INFO_FIELDS: [*const c_char; 2] = [ - "Version\0".as_ptr() as *const c_char, - "Developer\0".as_ptr() as *const c_char, -]; - -pub fn nbgl_display<'a, T: TryFrom>( - comm: &mut Comm, - settings_strings: &[[&'a str; 2]], - page: u8, -) -> Event -where - Reply: From<>::Error>, -{ - let mut info_contents: Vec = Vec::new(); - info_contents.push(CString::new("Alephium").unwrap()); - info_contents.push(CString::new(env!("CARGO_PKG_VERSION")).unwrap()); - info_contents.push(CString::new(env!("CARGO_PKG_AUTHORS")).unwrap()); - - unsafe { - NVM_REF = Some(transmute(SETTINGS_DATA.get_mut())); - } - - let nb_settings = settings_strings.len() as u8; - let setting_contents: Vec<[CString; 2]> = settings_strings - .iter() - .map(|s| [CString::new(s[0]).unwrap(), CString::new(s[1]).unwrap()]) - .collect(); - - const APP_ICON: NbglGlyph = NbglGlyph::from_include(include_gif!("alph_64x64.gif", NBGL)); - unsafe { - let mut page_index = page; - 'outer: loop { - let info_contents: Vec<*const c_char> = - info_contents.iter().map(|s| s.as_ptr()).collect::>(); - - let info_list: nbgl_contentInfoList_t = nbgl_contentInfoList_t { - infoTypes: INFO_FIELDS.as_ptr() as *const *const c_char, - infoContents: info_contents[1..].as_ptr() as *const *const c_char, - nbInfos: INFO_FIELDS.len() as u8, - }; - - let icon: nbgl_icon_details_t = (&APP_ICON).into(); - - for (i, setting) in setting_contents.iter().enumerate() { - SWITCH_ARRAY[i].text = setting[0].as_ptr(); - SWITCH_ARRAY[i].subText = setting[1].as_ptr(); - SWITCH_ARRAY[i].initState = NVM_REF.as_mut().unwrap().get_ref()[i] as nbgl_state_t; - SWITCH_ARRAY[i].token = (FIRST_USER_TOKEN + i as u32) as u8; - SWITCH_ARRAY[i].tuneId = TuneIndex::TapCasual as u8; - } - - let content: nbgl_content_t = nbgl_content_t { - content: nbgl_content_u { - switchesList: nbgl_pageSwitchesList_s { - switches: &SWITCH_ARRAY as *const nbgl_contentSwitch_t, - nbSwitches: nb_settings, - }, - }, - contentActionCallback: Some(settings_callback), - type_: SWITCHES_LIST, - }; - - let generic_contents: nbgl_genericContents_t = nbgl_genericContents_t { - callbackCallNeeded: false, - __bindgen_anon_1: nbgl_genericContents_t__bindgen_ty_1 { - contentsList: &content as *const nbgl_content_t, - }, - nbContents: if nb_settings > 0 { 1 } else { 0 }, - }; - - nbgl_useCaseHomeAndSettings( - info_contents[0], - &icon as *const nbgl_icon_details_t, - core::ptr::null(), - page_index, - &generic_contents as *const nbgl_genericContents_t, - &info_list as *const nbgl_contentInfoList_t, - core::ptr::null(), - Some(app_exit), - ); - loop { - match comm.next_event() { - Event::Command(t) => return Event::Command(t), - _ => { - // The Ledger Rust SDK does not refresh the UI after updating settings. - // Here we refresh the UI manually upon detecting settings updates. - // We have reported this issue to Ledger dev, and once the Rust SDK is fixed, - // we can remove this workaround. - if SETTINGS_UPDATED { - SETTINGS_UPDATED = false; - page_index = 0; // display the settings page - continue 'outer; - } - } - } - } - } - } -} - -/// Callback triggered by the NBGL API when a setting switch is toggled. -unsafe extern "C" fn settings_callback(token: c_int, _index: u8, _page: c_int) { - let idx = token - FIRST_USER_TOKEN as i32; - if idx < 0 || idx >= SETTINGS_SIZE as i32 { - panic!("Invalid token."); - } - - if let Some(data) = NVM_REF.as_mut() { - let setting_idx: usize = idx as usize; - let mut switch_values: [u8; SETTINGS_SIZE] = data.get_ref().clone(); - switch_values[setting_idx] = !switch_values[setting_idx]; - data.update(&switch_values); - SWITCH_ARRAY[setting_idx].initState = switch_values[setting_idx] as nbgl_state_t; - SETTINGS_UPDATED = true; - } -} - -unsafe extern "C" fn app_exit() { - // The Ledger C app uses `-1`: https://github.com/LedgerHQ/ledger-secure-sdk/blob/master/lib_standard_app/main.c#L40, - // but the type is `uchar`, so we need to use `u8::MAX` in Rust - os_sched_exit(u8::MAX); -} diff --git a/app/src/ledger_sdk_stub/nbgl_review.rs b/app/src/ledger_sdk_stub/nbgl_review.rs deleted file mode 100644 index 70e2755..0000000 --- a/app/src/ledger_sdk_stub/nbgl_review.rs +++ /dev/null @@ -1,163 +0,0 @@ -// In the ledger rust sdk, `NbglStreamingReview` uses unnecessary `&mut self`, -// which makes our code much more complicated due to Rust's borrow checker. -// Therefore, we copied it from the ledger rust sdk and changed all `&mut self` to `&self`. -// We have provided feedback to the ledger developers, and we will remove this once the SDK is updated. -// And to maintain consistency with the code in the ledger rust sdk, we ignored all clippy warnings. -#![allow(clippy::all)] - -extern crate alloc; -use alloc::ffi::CString; -use alloc::vec::Vec; -use core::ffi::*; -use include_gif::include_gif; -use ledger_device_sdk::nbgl::{Field, NbglChoice, NbglGlyph, TransactionType}; -use ledger_secure_sdk_sys::*; - -struct CField { - pub name: CString, - pub value: CString, -} - -/// A wrapper around the synchronous NBGL ux_sync_reviewStreaming (start, continue and finish) -/// C API binding. Used to display streamed transaction review screens. -pub struct NbglStreamingReview { - icon: nbgl_icon_details_t, - tx_type: TransactionType, - blind: bool, -} - -impl NbglStreamingReview { - pub fn new() -> NbglStreamingReview { - NbglStreamingReview { - icon: nbgl_icon_details_t::default(), - tx_type: TransactionType::Transaction, - blind: false, - } - } - - pub fn tx_type(self, tx_type: TransactionType) -> NbglStreamingReview { - NbglStreamingReview { tx_type, ..self } - } - - pub fn blind(self) -> NbglStreamingReview { - NbglStreamingReview { - blind: true, - ..self - } - } - - pub fn glyph(self, glyph: &NbglGlyph) -> NbglStreamingReview { - NbglStreamingReview { - icon: glyph.into(), - ..self - } - } - - pub fn start(&self, title: &str, subtitle: &str) -> bool { - unsafe { - let title = CString::new(title).unwrap(); - let subtitle = CString::new(subtitle).unwrap(); - - if self.blind { - if !accept_blind_warning() { - return false; - } - } - - let sync_ret = ux_sync_reviewStreamingStart( - self.tx_type.to_c_type(self.blind, false), - &self.icon as *const nbgl_icon_details_t, - title.as_ptr() as *const c_char, - subtitle.as_ptr() as *const c_char, - ); - - // Return true if the user approved the transaction, false otherwise. - match sync_ret { - UX_SYNC_RET_APPROVED => { - return true; - } - _ => { - return false; - } - } - } - } - - pub fn continue_review(&self, fields: &[Field]) -> bool { - unsafe { - let v: Vec = fields - .iter() - .map(|f| CField { - name: CString::new(f.name).unwrap(), - value: CString::new(f.value).unwrap(), - }) - .collect(); - - // Fill the tag_value_array with the fields converted to nbgl_contentTagValue_t - let mut tag_value_array: Vec = Vec::new(); - for field in v.iter() { - let val = nbgl_contentTagValue_t { - item: field.name.as_ptr() as *const i8, - value: field.value.as_ptr() as *const i8, - ..Default::default() - }; - tag_value_array.push(val); - } - - // Create the tag_value_list with the tag_value_array. - let tag_value_list = nbgl_contentTagValueList_t { - pairs: tag_value_array.as_ptr() as *const nbgl_contentTagValue_t, - nbPairs: fields.len() as u8, - ..Default::default() - }; - - let sync_ret = ux_sync_reviewStreamingContinue( - &tag_value_list as *const nbgl_contentTagValueList_t, - ); - - // Return true if the user approved the transaction, false otherwise. - match sync_ret { - UX_SYNC_RET_APPROVED => { - return true; - } - _ => { - return false; - } - } - } - } - - pub fn finish(&self, finish_title: &str) -> bool { - unsafe { - let finish_title = CString::new(finish_title).unwrap(); - let sync_ret = ux_sync_reviewStreamingFinish(finish_title.as_ptr() as *const c_char); - - // Return true if the user approved the transaction, false otherwise. - match sync_ret { - UX_SYNC_RET_APPROVED => { - return true; - } - _ => { - return false; - } - } - } - } -} - -/// Private helper function to display a warning screen when a transaction -/// is reviewed in "blind" mode. The user can choose to go back to safety -/// or review the risk. If the user chooses to review the risk, a second screen -/// is displayed with the option to accept the risk or reject the transaction. -/// Used in NbglReview and NbglStreamingReview. -fn accept_blind_warning() -> bool { - const WARNING: NbglGlyph = NbglGlyph::from_include(include_gif!("Warning_64px.gif", NBGL)); - - !NbglChoice::new().glyph(&WARNING) - .show( - "Blind signing ahead", - "This transaction's details are not fully verifiable. If you sign it, you could lose all your assets.", - "Back to safety", - "Continue anyway" - ) -} diff --git a/app/src/ledger_sdk_stub/nvm.rs b/app/src/ledger_sdk_stub/nvm.rs deleted file mode 100644 index 937d26e..0000000 --- a/app/src/ledger_sdk_stub/nvm.rs +++ /dev/null @@ -1,102 +0,0 @@ -use ledger_secure_sdk_sys::nvm_write; - -use crate::error_code::ErrorCode; - -pub const NVM_DATA_SIZE: usize = 2048; - -// The following code is from ledger-rust-sdk -// We've made modifications here to add the `get_mut` functions to `NVMData` - -#[allow(clippy::upper_case_acronyms)] -#[repr(align(64))] -pub struct NVM(pub [u8; N]); - -impl NVM { - pub const fn zeroed() -> Self { - Self([0; N]) - } - - pub fn write(&mut self, from: usize, slice: &[u8]) -> bool { - let len = slice.len(); - if from + len > N { - return false; - } - - unsafe { - let dst = self.0[from..].as_mut_ptr() as *mut _; - let src = slice.as_ptr() as *mut u8 as *mut _; - nvm_write(dst, src, len as u32); - - assert_eq!(&self.0[from..(from + len)], slice); - }; - true - } -} - -// NOTE: `NVMData` is from ledger sdk, we need the `get_ref` -// Needed for `NVMData` to function properly -extern "C" { - // This is a linker script symbol defining the beginning of - // the .nvm_data section. Declaring it as a static u32 - // (as is usually done) will result in a r9-indirect memory - // access, as if it were a RAM access. - // To force the compiler out of this assumption, we define - // it as a function instead, but it is _not_ a function at all - fn _nvm_data_start(); -} - -/// The following is a means to correctly access data stored in NVM -/// through the `#[link_section = ".nvm_data"]` attribute -pub struct NVMData { - data: T, -} - -impl NVMData { - pub const fn new(data: T) -> NVMData { - NVMData { data } - } - - /// This will return a mutable access by casting the pointer - /// to the correct offset in `.nvm_data` manually. - /// This is necessary when using the `rwpi` relocation model, - /// because a static mutable will be assumed to be located in - /// RAM, and be accessed through the static base (r9) - fn get_addr(&self) -> *mut T { - use core::arch::asm; - unsafe { - // Compute offset in .nvm_data by taking the reference to - // self.data and subtracting r9 - let addr = &self.data as *const T as u32; - let static_base: u32; - asm!( "mov {}, r9", out(reg) static_base); - let offset = (addr - static_base) as isize; - let data_addr = (_nvm_data_start as *const u8).offset(offset); - ledger_secure_sdk_sys::pic(data_addr as *mut core::ffi::c_void) as *mut T - } - } - - pub fn get_mut(&mut self) -> &mut T { - unsafe { - let pic_addr = self.get_addr(); - &mut *pic_addr.cast() - } - } - - pub fn get_ref(&self) -> &T { - unsafe { - let pic_addr = self.get_addr(); - &*pic_addr.cast() - } - } -} - -impl NVMData> { - pub fn write_from(&mut self, from_index: usize, bytes: &[u8]) -> Result<(), ErrorCode> { - let data = self.get_mut(); - if data.write(from_index, bytes) { - Ok(()) - } else { - Err(ErrorCode::Overflow) - } - } -} diff --git a/app/src/main.rs b/app/src/main.rs index c718bf6..aa410d2 100644 --- a/app/src/main.rs +++ b/app/src/main.rs @@ -2,7 +2,7 @@ #![no_main] use crate::ui::tx_reviewer::TxReviewer; -use handler::{handle_apdu, Ins}; +use handler::handle_apdu; use ledger_device_sdk::io; use sign_tx_context::SignTxContext; @@ -10,7 +10,7 @@ mod blake2b_hasher; mod debug; mod error_code; mod handler; -mod ledger_sdk_stub; +mod nvm; mod public_key; mod settings; mod sign_tx_context; @@ -31,6 +31,7 @@ extern "C" fn sample_main() { #[cfg(not(any(target_os = "stax", target_os = "flex")))] { use crate::ui::bagl::home::MainPages; + use handler::Ins; let mut main_pages = MainPages::new(); loop { @@ -38,7 +39,7 @@ extern "C" fn sample_main() { // or an APDU command if let io::Event::Command(ins) = main_pages.show::(&mut comm) { match handle_apdu(&mut comm, ins, &mut sign_tx_context, &mut tx_reviewer) { - Ok(()) => comm.reply_ok(), + Ok(_) => comm.reply_ok(), Err(sw) => comm.reply(sw), } main_pages.show_ui(); @@ -48,26 +49,45 @@ extern "C" fn sample_main() { #[cfg(any(target_os = "stax", target_os = "flex"))] { - use crate::ledger_sdk_stub::nbgl_display::nbgl_display; + use crate::settings::SETTINGS_DATA; + use include_gif::include_gif; use ledger_device_sdk::nbgl::init_comm; - use ledger_secure_sdk_sys::INIT_HOME_PAGE; + use ledger_device_sdk::nbgl::{NbglGlyph, NbglHomeAndSettings, PageIndex}; + + const APP_ICON: NbglGlyph = NbglGlyph::from_include(include_gif!("alph_64x64.gif", NBGL)); + let settings_strings: &[[&str; 2]] = &[["Blind signing", "Enable blind signing"]]; + let mut home_and_settings = NbglHomeAndSettings::new() + .glyph(&APP_ICON) + .settings(unsafe { SETTINGS_DATA.get_mut() }, settings_strings) + .infos( + "Alephium", + env!("CARGO_PKG_VERSION"), + env!("CARGO_PKG_AUTHORS"), + ); init_comm(&mut comm); - let settings_strings = &[["Blind signing", "Enable blind signing"]]; + home_and_settings.show_and_return(); loop { - let event = if tx_reviewer.display_settings() { - tx_reviewer.reset_display_settings(); - nbgl_display::(&mut comm, settings_strings, 0) - } else if !tx_reviewer.review_started() { - nbgl_display::(&mut comm, settings_strings, INIT_HOME_PAGE as u8) - } else { - comm.next_event() - }; - if let io::Event::Command(ins) = event { - match handle_apdu(&mut comm, ins, &mut sign_tx_context, &mut tx_reviewer) { - Ok(_) => comm.reply_ok(), - Err(sw) => comm.reply(sw), + if let io::Event::Command(ins) = comm.next_event() { + let display_home = + match handle_apdu(&mut comm, ins, &mut sign_tx_context, &mut tx_reviewer) { + Ok(result) => { + comm.reply_ok(); + result + } + Err(sw) => { + comm.reply(sw); + true + } + }; + if tx_reviewer.display_settings() { + tx_reviewer.reset_display_settings(); + home_and_settings = home_and_settings.set_start_page(PageIndex::Settings(0)); + home_and_settings.show_and_return(); + } else if display_home { + home_and_settings = home_and_settings.set_start_page(PageIndex::Home); + home_and_settings.show_and_return(); } } } diff --git a/app/src/nvm/mod.rs b/app/src/nvm/mod.rs new file mode 100644 index 0000000..eed61da --- /dev/null +++ b/app/src/nvm/mod.rs @@ -0,0 +1,47 @@ +use ledger_device_sdk::NVMData; +use ledger_secure_sdk_sys::nvm_write; + +use crate::error_code::ErrorCode; + +pub mod swapping_buffer; + +pub const NVM_DATA_SIZE: usize = 2048; + +#[allow(clippy::upper_case_acronyms)] +#[repr(align(64))] +pub struct NVM(pub [u8; N]); + +impl NVM { + pub const fn zeroed() -> Self { + Self([0; N]) + } + + pub fn write(&mut self, from: usize, slice: &[u8]) -> bool { + let len = slice.len(); + if from + len > N { + return false; + } + + unsafe { + let dst = self.0[from..].as_mut_ptr() as *mut _; + let src = slice.as_ptr() as *mut u8 as *mut _; + nvm_write(dst, src, len as u32); + + assert_eq!(&self.0[from..(from + len)], slice); + }; + true + } +} + +pub fn write_from( + nvm_data: &mut NVMData>, + from_index: usize, + bytes: &[u8], +) -> Result<(), ErrorCode> { + let data = nvm_data.get_mut(); + if data.write(from_index, bytes) { + Ok(()) + } else { + Err(ErrorCode::Overflow) + } +} diff --git a/app/src/ledger_sdk_stub/swapping_buffer.rs b/app/src/nvm/swapping_buffer.rs similarity index 93% rename from app/src/ledger_sdk_stub/swapping_buffer.rs rename to app/src/nvm/swapping_buffer.rs index a308349..e67fb38 100644 --- a/app/src/ledger_sdk_stub/swapping_buffer.rs +++ b/app/src/nvm/swapping_buffer.rs @@ -3,8 +3,9 @@ use utils::buffer::Writable; use crate::{ error_code::ErrorCode, - ledger_sdk_stub::nvm::{NVMData, NVM}, + nvm::{write_from, NVM}, }; +use ledger_device_sdk::NVMData; pub const RAM_SIZE: usize = 512; @@ -70,15 +71,15 @@ impl<'a, const RAM: usize, const FLASH: usize> SwappingBuffer<'a, RAM, FLASH> { #[inline] fn write_to_nvm(&mut self, data: &[u8], from: usize) -> Result<(), ErrorCode> { - self.flash.write_from(from, data)?; + write_from(self.flash, from, data)?; self.state = BufferState::WritingToFlash(from + data.len()); Ok(()) } #[inline] fn switch_to_nvm(&mut self, ram_length: usize, data: &[u8]) -> Result<(), ErrorCode> { - self.flash.write_from(0, &self.ram[..ram_length])?; - self.flash.write_from(ram_length, data)?; + write_from(self.flash, 0, &self.ram[..ram_length])?; + write_from(self.flash, ram_length, data)?; self.state = BufferState::WritingToFlash(ram_length + data.len()); Ok(()) } @@ -126,7 +127,7 @@ impl<'a, const RAM: usize, const FLASH: usize> SwappingBuffer<'a, RAM, FLASH> { } BufferState::WritingToFlash(_) => { assert!(from_index + size <= FLASH); - self.flash.write_from(from_index, data).unwrap(); + write_from(self.flash, from_index, data).unwrap(); } } } diff --git a/app/src/sign_tx_context.rs b/app/src/sign_tx_context.rs index 5de8f84..d820972 100644 --- a/app/src/sign_tx_context.rs +++ b/app/src/sign_tx_context.rs @@ -1,10 +1,11 @@ use ledger_device_sdk::io::ApduHeader; +use ledger_device_sdk::NVMData; use utils::{ buffer::Buffer, decode::StreamingDecoder, deserialize_path, types::UnsignedTx, PATH_LENGTH, }; -use crate::ledger_sdk_stub::nvm::{NVMData, NVM, NVM_DATA_SIZE}; -use crate::ledger_sdk_stub::swapping_buffer::{SwappingBuffer, RAM_SIZE}; +use crate::nvm::swapping_buffer::{SwappingBuffer, RAM_SIZE}; +use crate::nvm::{NVM, NVM_DATA_SIZE}; use crate::public_key::sign_hash; use crate::public_key::Address; use crate::ui::tx_reviewer::TxReviewer; diff --git a/app/src/ui/bagl/home.rs b/app/src/ui/bagl/home.rs index 3b9a29a..daf092f 100644 --- a/app/src/ui/bagl/home.rs +++ b/app/src/ui/bagl/home.rs @@ -13,49 +13,34 @@ use crate::settings::{is_blind_signing_enabled, toggle_blind_signing_setting}; const UI_PAGE_NUM: u8 = 4; -fn show_ui_common(draw: fn() -> ()) { - gadgets::clear_screen(); - - bagls::LEFT_ARROW.display(); - bagls::RIGHT_ARROW.display(); - - draw(); - - screen_util::screen_update(); -} - fn show_ui_welcome() { - show_ui_common(|| { - const APP_ICON: Glyph = Glyph::from_include(include_gif!("alph_14x14.gif")); - gadgets::Page::from((["Alephium", "is ready"], &APP_ICON)).place(); - }); + const APP_ICON: Glyph = Glyph::from_include(include_gif!("alph_14x14.gif")); + gadgets::Page::from((["Alephium", "is ready"], &APP_ICON)).place(); } fn show_ui_blind_signing() { - show_ui_common(|| { - let label = if is_blind_signing_enabled() { - "enabled" - } else { - "disabled" - }; - gadgets::Page::from((["Blind Signing", label], false)).place(); - }); + let label = if is_blind_signing_enabled() { + "enabled" + } else { + "disabled" + }; + gadgets::Page::from((["Blind Signing", label], false)).place(); } fn show_ui_version() { - show_ui_common(|| { - const VERSION: &str = env!("CARGO_PKG_VERSION"); - gadgets::Page::from((["Version", VERSION], false)).place(); - }); + const VERSION: &str = env!("CARGO_PKG_VERSION"); + gadgets::Page::from((["Version", VERSION], false)).place(); } fn show_ui_quit() { - show_ui_common(|| { - gadgets::Page::from(("Quit", &DASHBOARD_X)).place(); - }); + gadgets::Page::from(("Quit", &DASHBOARD_X)).place(); } fn show_ui(index: u8) { + gadgets::clear_screen(); + bagls::LEFT_ARROW.display(); + bagls::RIGHT_ARROW.display(); + match index { 0 => show_ui_welcome(), 1 => show_ui_version(), @@ -63,6 +48,8 @@ fn show_ui(index: u8) { 3 => show_ui_quit(), _ => panic!("Invalid ui index"), } + + screen_util::screen_update(); } pub struct MainPages { diff --git a/app/src/ui/bagl/mod.rs b/app/src/ui/bagl/mod.rs index 699784b..25e8f12 100644 --- a/app/src/ui/bagl/mod.rs +++ b/app/src/ui/bagl/mod.rs @@ -1,13 +1,10 @@ pub mod home; pub mod tx_reviewer_inner; -use crate::{ - error_code::ErrorCode, - ledger_sdk_stub::multi_field_review::{Field, MultiFieldReview}, - public_key::sign_hash, -}; +use crate::{error_code::ErrorCode, public_key::sign_hash}; use core::str::from_utf8; use ledger_device_sdk::ui::bitmaps::{CHECKMARK, CROSS, EYE}; +use ledger_device_sdk::ui::gadgets::{Field, MultiFieldReview}; pub fn sign_hash_ui(path: &[u32], message: &[u8]) -> Result<([u8; 72], u32, u32), ErrorCode> { let hex: [u8; 64] = utils::to_hex(message).ok_or(ErrorCode::BadLen)?; @@ -18,7 +15,7 @@ pub fn sign_hash_ui(path: &[u32], message: &[u8]) -> Result<([u8; 72], u32, u32) name: "Hash", value: hex_str, }]; - let review = MultiFieldReview::simple( + let review = MultiFieldReview::new( &fields, &review_messages, Some(&EYE), @@ -40,7 +37,7 @@ pub fn review_address(address: &str) -> Result<(), ErrorCode> { name: "Address", value: address, }]; - let review = MultiFieldReview::simple( + let review = MultiFieldReview::new( &fields, &review_messages, Some(&EYE), diff --git a/app/src/ui/bagl/tx_reviewer_inner.rs b/app/src/ui/bagl/tx_reviewer_inner.rs index 6036cb5..138009e 100644 --- a/app/src/ui/bagl/tx_reviewer_inner.rs +++ b/app/src/ui/bagl/tx_reviewer_inner.rs @@ -1,10 +1,9 @@ use crate::error_code::ErrorCode; -use crate::ledger_sdk_stub::multi_field_review::{Field, MultiFieldReview}; use crate::settings::is_blind_signing_enabled; use ledger_device_sdk::{ buttons::{ButtonEvent, ButtonsState}, ui::bitmaps::{Glyph, CHECKMARK, CROSS, CROSSMARK, EYE, WARNING}, - ui::gadgets::{clear_screen, get_event, Page, PageStyle}, + ui::gadgets::{clear_screen, get_event, Field, MultiFieldReview, Page, PageStyle}, ui::screen_util::screen_update, }; @@ -34,7 +33,7 @@ impl TxReviewerInner { review_message: &str, ) -> Result<(), ErrorCode> { let review_messages = ["Review", review_message]; - let review = MultiFieldReview::simple( + let review = MultiFieldReview::new( fields, &review_messages, Some(&EYE), @@ -63,7 +62,7 @@ impl TxReviewerInner { // Review the warning for external inputs, i.e. inputs that are not from the device address pub fn warning_external_inputs(&self) -> Result<(), ErrorCode> { let review_messages = ["There are", "external inputs"]; - let review = MultiFieldReview::simple( + let review = MultiFieldReview::new( &[], &review_messages, Some(&WARNING), @@ -89,17 +88,17 @@ impl TxReviewerInner { review_message: &'a [&'a str], review_glyph: Option<&'a Glyph<'a>>, ) -> Result<(), ErrorCode> { - let validation_message = if !self.is_tx_execute_script { + let validation_messages = if !self.is_tx_execute_script { ["Accept", "and sign"] } else { ["Accept risk", "and sign"] }; - let review = MultiFieldReview::new( + let review = MultiFieldReview::new_with_validation_messages( fields, review_message, review_glyph, - validation_message, + validation_messages, Some(&CHECKMARK), "Reject", Some(&CROSS), diff --git a/app/src/ui/nbgl/mod.rs b/app/src/ui/nbgl/mod.rs index c56962b..b75acc9 100644 --- a/app/src/ui/nbgl/mod.rs +++ b/app/src/ui/nbgl/mod.rs @@ -1,12 +1,11 @@ pub mod tx_reviewer_inner; -use crate::{ - error_code::ErrorCode, ledger_sdk_stub::nbgl_review::NbglStreamingReview, public_key::sign_hash, -}; +use crate::{error_code::ErrorCode, public_key::sign_hash}; use core::str::from_utf8; use include_gif::include_gif; use ledger_device_sdk::nbgl::{ - Field, NbglAddressReview, NbglChoice, NbglGlyph, NbglReviewStatus, TransactionType, + Field, NbglAddressReview, NbglChoice, NbglGlyph, NbglReviewStatus, NbglStreamingReview, + TransactionType, }; pub static APP_ICON: NbglGlyph = NbglGlyph::from_include(include_gif!("alph_64x64.gif", NBGL)); diff --git a/app/src/ui/nbgl/tx_reviewer_inner.rs b/app/src/ui/nbgl/tx_reviewer_inner.rs index 91a6a1e..f3a8c48 100644 --- a/app/src/ui/nbgl/tx_reviewer_inner.rs +++ b/app/src/ui/nbgl/tx_reviewer_inner.rs @@ -1,16 +1,14 @@ use crate::{ error_code::ErrorCode, - ledger_sdk_stub::nbgl_review::NbglStreamingReview, settings::is_blind_signing_enabled, ui::nbgl::{nbgl_review_warning, new_nbgl_review}, }; -use ledger_device_sdk::nbgl::{Field, NbglReviewStatus, TransactionType}; +use ledger_device_sdk::nbgl::{Field, NbglReviewStatus, NbglStreamingReview, TransactionType}; // Different Ledger devices use different UI libraries, so we've introduced the // `TxReviewInner` to facilitate the display of tx details across different devices. // The `TxReviewInner` here is for Ledger Stax/Flex. pub struct TxReviewerInner { - pub review_started: bool, pub display_settings: bool, is_tx_execute_script: bool, reviewer: Option, @@ -19,7 +17,6 @@ pub struct TxReviewerInner { impl TxReviewerInner { pub fn new() -> TxReviewerInner { TxReviewerInner { - review_started: false, display_settings: false, is_tx_execute_script: false, reviewer: None, @@ -49,7 +46,6 @@ impl TxReviewerInner { "Review transaction to send assets" }; if self.get_reviewer().start(message, "") { - self.review_started = true; Ok(()) } else { NbglReviewStatus::new().show(false); @@ -124,7 +120,6 @@ impl TxReviewerInner { // Since `reset` is called when blind signing checks fails, // we cannot reset the `display_settings` within the reset function. // Instead, we will reset the `display_settings` in the `finish_review` function. - self.review_started = false; self.reviewer = None; self.is_tx_execute_script = false; } diff --git a/app/src/ui/tx_reviewer.rs b/app/src/ui/tx_reviewer.rs index 4e8c87f..c947c88 100644 --- a/app/src/ui/tx_reviewer.rs +++ b/app/src/ui/tx_reviewer.rs @@ -1,20 +1,19 @@ use super::TxReviewerInner; -#[cfg(not(any(target_os = "stax", target_os = "flex")))] -use crate::ledger_sdk_stub::multi_field_review::Field; use crate::{ blake2b_hasher::Blake2bHasher, error_code::ErrorCode, handler::TOKEN_METADATA_SIZE, - ledger_sdk_stub::{ - nvm::{NVMData, NVM, NVM_DATA_SIZE}, - swapping_buffer::{SwappingBuffer, RAM_SIZE}, - }, + nvm::swapping_buffer::{SwappingBuffer, RAM_SIZE}, + nvm::{NVM, NVM_DATA_SIZE}, public_key::{to_base58_address, Address}, token_verifier::TokenVerifier, ui::bytes_to_string, }; #[cfg(any(target_os = "stax", target_os = "flex"))] use ledger_device_sdk::nbgl::Field; +#[cfg(not(any(target_os = "stax", target_os = "flex")))] +use ledger_device_sdk::ui::gadgets::Field; +use ledger_device_sdk::NVMData; use utils::{ base58::ALPHABET, types::{ @@ -574,12 +573,6 @@ impl TxReviewer { self.inner.display_settings } - #[cfg(any(target_os = "stax", target_os = "flex"))] - #[inline] - pub fn review_started(&self) -> bool { - self.inner.review_started - } - #[cfg(any(target_os = "stax", target_os = "flex"))] #[inline] pub fn reset_display_settings(&mut self) {