From 3444069fabf7926c6317b2439139cb28b5a413dd Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Wed, 25 Oct 2023 16:35:15 +0200 Subject: [PATCH 01/15] Add new main menu based on MultiPageMenu gadget. --- .cargo/config.toml | 5 ++++ Cargo.toml | 4 ++++ src/app_ui/menu.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 13 +++++----- 4 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 src/app_ui/menu.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index c233973..edf4752 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -7,3 +7,8 @@ target = "nanosplus" [unstable] build-std = ["core"] build-std-features = ["compiler-builtins-mem"] +host-config = true +target-applies-to-host = true + +[host] +rustflags = ["-Ctarget-feature=-crt-static"] \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 468411d..a171768 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,10 @@ edition = "2021" [dependencies] nanos_sdk = { git = "https://github.com/LedgerHQ/ledger-nanos-sdk.git" } nanos_ui = { git = "https://github.com/LedgerHQ/ledger-nanos-ui.git" } +include_gif = { git = "https://github.com/LedgerHQ/sdk_include_gif" } + +[patch."https://github.com/LedgerHQ/ledger-nanos-ui"] +nanos_ui = { path = "ledger-nanos-ui" } [profile.release] opt-level = 'z' diff --git a/src/app_ui/menu.rs b/src/app_ui/menu.rs new file mode 100644 index 0000000..83a5364 --- /dev/null +++ b/src/app_ui/menu.rs @@ -0,0 +1,59 @@ +/***************************************************************************** + * Ledger App Boilerplate Rust. + * (c) 2023 Ledger SAS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ + +use include_gif::include_gif; +use nanos_sdk::io; +use nanos_ui::bitmaps::{Glyph, BACK, CERTIFICATE, DASHBOARD_X}; +use nanos_ui::ui::{EventOrPageIndex, MultiPageMenu, Page}; + +fn ui_about_menu(comm: &mut io::Comm) -> io::Event { + let pages = [ + &Page::from((["Rust Boilerplate", "(c) 2023 Ledger"], true)), + &Page::from(("Back", &BACK)), + ]; + loop { + match MultiPageMenu::new(comm, &pages).show() { + EventOrPageIndex::Event(e) => return e, + i => match i { + EventOrPageIndex::Index(1) => return ui_menu_main(comm), + _ => (), + }, + } + } +} + +pub fn ui_menu_main(comm: &mut io::Comm) -> io::Event { + const APP_ICON: Glyph = Glyph::from_include(include_gif!("crab.gif")); + let pages = [ + // The from trait allows to create different styles of pages + // without having to use the new() function. + &Page::from((["Boilerplate", "is ready"], &APP_ICON)), + &Page::from((["Version", "2.0.0"], true)), + &Page::from(("About", &CERTIFICATE)), + &Page::from(("Quit", &DASHBOARD_X)), + ]; + loop { + match MultiPageMenu::new(comm, &pages).show() { + EventOrPageIndex::Event(e) => return e, + i => match i { + EventOrPageIndex::Index(2) => return ui_about_menu(comm), + EventOrPageIndex::Index(3) => nanos_sdk::exit_app(0), + _ => (), + }, + } + } +} diff --git a/src/main.rs b/src/main.rs index e70ce1b..81ad549 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,9 @@ #![no_main] mod utils; +mod app_ui { + pub mod menu; +} use core::str::from_utf8; use nanos_sdk::buttons::ButtonEvent; @@ -10,6 +13,8 @@ use nanos_sdk::io; use nanos_sdk::io::SyscallError; use nanos_ui::ui; +use app_ui::menu::ui_menu_main; + nanos_sdk::set_panic!(nanos_sdk::exiting_panic); pub const BIP32_PATH: [u32; 5] = nanos_sdk::ecc::make_bip32_path(b"m/44'/535348'/0'/0/0"); @@ -106,14 +111,10 @@ extern "C" fn sample_main() { let mut comm = io::Comm::new(); loop { - // Draw some 'welcome' screen - ui::SingleMessage::new("W e l c o m e").show(); - // Wait for either a specific button push to exit the app // or an APDU command - match comm.next_event() { - io::Event::Button(ButtonEvent::RightButtonRelease) => nanos_sdk::exit_app(0), - io::Event::Command(ins) => match handle_apdu(&mut comm, ins) { + match ui_menu_main(&mut comm) { + io::Event::Command(ins) => match handle_apdu(&mut comm, ins.into()) { Ok(()) => comm.reply_ok(), Err(sw) => comm.reply(sw), }, From 14aa875b85fb2c187296d9c57196554c43342eb4 Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Fri, 27 Oct 2023 17:13:50 +0200 Subject: [PATCH 02/15] Use MultiFieldReview gadget for tx review. --- src/main.rs | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index 81ad549..d000e99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,8 @@ use nanos_sdk::io; use nanos_sdk::io::SyscallError; use nanos_ui::ui; +use nanos_ui::bitmaps::{EYE, VALIDATE_14, CROSSMARK}; + use app_ui::menu::ui_menu_main; nanos_sdk::set_panic!(nanos_sdk::exiting_panic); @@ -65,16 +67,24 @@ fn menu_example() { /// to read the incoming message, a panel that requests user /// validation, and an exit message. fn sign_ui(message: &[u8]) -> Result, SyscallError> { - ui::popup("Message review"); - - { - let hex = utils::to_hex(message).map_err(|_| SyscallError::Overflow)?; - let m = from_utf8(&hex).map_err(|_| SyscallError::InvalidParameter)?; - - ui::MessageScroller::new(m).event_loop(); - } - - if ui::Validator::new("Sign ?").ask() { + let hex = utils::to_hex(message).map_err(|_| SyscallError::Overflow)?; + let m = from_utf8(&hex).map_err(|_| SyscallError::InvalidParameter)?; + let my_field = [ui::Field { + name: "Data", + value: m, + }]; + + let my_review = ui::MultiFieldReview::new( + &my_field, + &["Review ","Transaction"], + Some(&EYE), + "Approve", + Some(&VALIDATE_14), + "Reject", + Some(&CROSSMARK), + ); + + if my_review.show() { let signature = Secp256k1::derive_from_path(&BIP32_PATH) .deterministic_sign(message) .map_err(|_| SyscallError::Unspecified)?; From 6da298adf53b01e2f0a5e202eae131498f7e3865 Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Tue, 7 Nov 2023 17:38:07 +0100 Subject: [PATCH 03/15] Update authors, version and crate name in Cargo.toml --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a171768..4338d38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "rust-app" -version = "0.2.1" -authors = ["yhql"] +name = "app-boilerplate-rust" +version = "1.0.0" +authors = ["yhql", "agrojean-ledger"] edition = "2021" [dependencies] From 9cfd8a23ded93156100cf9854660c8dcf7589fc8 Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Tue, 7 Nov 2023 17:38:39 +0100 Subject: [PATCH 04/15] Update .gitignore --- .gitignore | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.gitignore b/.gitignore index 9833ef3..3735321 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,22 @@ target app.json +app_nanos.json +app_nanosplus.json +app_nanox.json +# Temporary directory with snapshots taken during test runs +tests/snapshots-tmp/ +# Python +*.pyc[cod] +*.egg +__pycache__/ +*.egg-info/ +.eggs/ +.python-version + +# Related to the Ledger VSCode extension +# Virtual env for sideload (macOS and Windows) +ledger/ +# Build directory +build/ \ No newline at end of file From 5d2ca170216ecfa1691fab67abe2bcfcdfca3a7f Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Tue, 7 Nov 2023 17:40:20 +0100 Subject: [PATCH 05/15] Update app instruction handling and UI. --- src/app_ui/address.rs | 47 ++++++++++++++ src/app_ui/menu.rs | 2 +- src/handlers/get_public_key.rs | 81 +++++++++++++++++++++++ src/handlers/get_version.rs | 39 ++++++++++++ src/main.rs | 113 ++++++++++++++++----------------- src/utils.rs | 8 ++- 6 files changed, 228 insertions(+), 62 deletions(-) create mode 100644 src/app_ui/address.rs create mode 100644 src/handlers/get_public_key.rs create mode 100644 src/handlers/get_version.rs diff --git a/src/app_ui/address.rs b/src/app_ui/address.rs new file mode 100644 index 0000000..4447776 --- /dev/null +++ b/src/app_ui/address.rs @@ -0,0 +1,47 @@ +/***************************************************************************** + * Ledger App Boilerplate Rust. + * (c) 2023 Ledger SAS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ + +use crate::utils; +use core::str::from_utf8; +use nanos_sdk::io; +use nanos_ui::bitmaps::{CROSSMARK, EYE, VALIDATE_14}; +use nanos_ui::ui::{Field, MultiFieldReview}; + +pub fn ui_display_pk(pk: &[u8]) -> Result { + // Todo add error handling + // ====================== + let hex = utils::to_hex(pk).unwrap(); + let m = from_utf8(&hex).unwrap(); + // ====================== + + let my_field = [Field { + name: "Public Key", + value: m[..pk.len() * 2].as_ref(), + }]; + + let my_review = MultiFieldReview::new( + &my_field, + &["Confirm Address"], + Some(&EYE), + "Approve", + Some(&VALIDATE_14), + "Reject", + Some(&CROSSMARK), + ); + + Ok(my_review.show()) +} diff --git a/src/app_ui/menu.rs b/src/app_ui/menu.rs index 83a5364..52a2695 100644 --- a/src/app_ui/menu.rs +++ b/src/app_ui/menu.rs @@ -42,7 +42,7 @@ pub fn ui_menu_main(comm: &mut io::Comm) -> io::Event { // The from trait allows to create different styles of pages // without having to use the new() function. &Page::from((["Boilerplate", "is ready"], &APP_ICON)), - &Page::from((["Version", "2.0.0"], true)), + &Page::from((["Version", env!("CARGO_PKG_VERSION")], true)), &Page::from(("About", &CERTIFICATE)), &Page::from(("Quit", &DASHBOARD_X)), ]; diff --git a/src/handlers/get_public_key.rs b/src/handlers/get_public_key.rs new file mode 100644 index 0000000..9216305 --- /dev/null +++ b/src/handlers/get_public_key.rs @@ -0,0 +1,81 @@ +/***************************************************************************** + * Ledger App Boilerplate Rust. + * (c) 2023 Ledger SAS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ + +use crate::app_ui::address::ui_display_pk; +use crate::SW_DENY; +use nanos_sdk::ecc::{Secp256k1, SeedDerive}; +use nanos_sdk::{io, testing}; + +const MAX_ALLOWED_PATH_LEN: usize = 10; + +// const SW_DENY: u16 = 0x6985; + +pub fn handler_get_public_key(comm: &mut io::Comm, display: bool) -> Result<(), io::Reply> { + let mut path = [0u32; MAX_ALLOWED_PATH_LEN]; + let data = comm.get_data()?; + + let path_len = read_bip32_path(data, &mut path)?; + + let pk = Secp256k1::derive_from_path(&path[..path_len]) + .public_key() + .map_err(|x| io::Reply(0x6eu16 | (x as u16 & 0xff)))?; + + // Display public key on device if requested + if display { + testing::debug_print("showing public key\n"); + if !ui_display_pk(&pk.pubkey)? { + testing::debug_print("denied\n"); + return Err(io::Reply(SW_DENY)); + } + } + + comm.append(&[pk.pubkey.len() as u8]); + comm.append(&pk.pubkey); + // Rust SDK key derivation API does not return chaincode yet + // so we just append a dummy chaincode. + const CHAINCODE_LEN: usize = 32; + comm.append(&[CHAINCODE_LEN as u8]); // Dummy chaincode length + comm.append(&[0u8; CHAINCODE_LEN]); // Dummy chaincode + + Ok(()) +} + +fn read_bip32_path(data: &[u8], path: &mut [u32]) -> Result { + // Check input length and path buffer capacity + if data.len() < 1 || path.len() < data.len() / 4 { + return Err(io::StatusWords::BadLen.into()); + } + + let path_len = data[0] as usize; // First byte is the length of the path + let path_data = &data[1..]; + + // Check path data length and alignment + if path_data.len() != path_len * 4 + || path_data.len() > MAX_ALLOWED_PATH_LEN * 4 + || path_data.len() % 4 != 0 + { + return Err(io::StatusWords::BadLen.into()); + } + + let mut idx = 0; + for (i, chunk) in path_data.chunks(4).enumerate() { + path[idx] = u32::from_be_bytes(chunk.try_into().unwrap()); + idx = i + 1; + } + + Ok(idx) +} diff --git a/src/handlers/get_version.rs b/src/handlers/get_version.rs new file mode 100644 index 0000000..fe46938 --- /dev/null +++ b/src/handlers/get_version.rs @@ -0,0 +1,39 @@ +/***************************************************************************** + * Ledger App Boilerplate Rust. + * (c) 2023 Ledger SAS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ + +use core::str::FromStr; +use nanos_sdk::io; + +pub fn handler_get_version(comm: &mut io::Comm) -> Result<(), io::Reply> { + if let Some((major, minor, patch)) = parse_version_string(env!("CARGO_PKG_VERSION")) { + comm.append(&[major, minor, patch]); + Ok(()) + } else { + Err(io::StatusWords::Unknown.into()) + } +} + +fn parse_version_string(input: &str) -> Option<(u8, u8, u8)> { + // Split the input string by '.'. + // Input should be of the form "major.minor.patch", + // where "major", "minor", and "patch" are integers. + let mut parts = input.split('.'); + let major = u8::from_str(parts.next()?).ok()?; + let minor = u8::from_str(parts.next()?).ok()?; + let patch = u8::from_str(parts.next()?).ok()?; + Some((major, minor, patch)) +} diff --git a/src/main.rs b/src/main.rs index d000e99..7060d53 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,8 +3,13 @@ mod utils; mod app_ui { + pub mod address; pub mod menu; } +mod handlers { + pub mod get_public_key; + pub mod get_version; +} use core::str::from_utf8; use nanos_sdk::buttons::ButtonEvent; @@ -13,55 +18,19 @@ use nanos_sdk::io; use nanos_sdk::io::SyscallError; use nanos_ui::ui; -use nanos_ui::bitmaps::{EYE, VALIDATE_14, CROSSMARK}; +use nanos_ui::bitmaps::{CROSSMARK, EYE, VALIDATE_14}; use app_ui::menu::ui_menu_main; +use handlers::{get_public_key::handler_get_public_key, get_version::handler_get_version}; nanos_sdk::set_panic!(nanos_sdk::exiting_panic); pub const BIP32_PATH: [u32; 5] = nanos_sdk::ecc::make_bip32_path(b"m/44'/535348'/0'/0/0"); -/// Display public key in two separate -/// message scrollers -fn show_pubkey() { - let pubkey = Secp256k1::derive_from_path(&BIP32_PATH).public_key(); - match pubkey { - Ok(pk) => { - { - let hex0 = utils::to_hex(&pk.as_ref()[1..33]).unwrap(); - let m = from_utf8(&hex0).unwrap(); - ui::MessageScroller::new(m).event_loop(); - } - { - let hex1 = utils::to_hex(&pk.as_ref()[33..65]).unwrap(); - let m = from_utf8(&hex1).unwrap(); - ui::MessageScroller::new(m).event_loop(); - } - } - Err(_) => ui::popup("Error"), - } -} - -/// Basic nested menu. Will be subject -/// to simplifications in the future. -#[allow(clippy::needless_borrow)] -fn menu_example() { - loop { - match ui::Menu::new(&[&"PubKey", &"Infos", &"Back", &"Exit App"]).show() { - 0 => show_pubkey(), - 1 => loop { - match ui::Menu::new(&[&"Copyright", &"Authors", &"Back"]).show() { - 0 => ui::popup("2020 Ledger"), - 1 => ui::popup("???"), - _ => break, - } - }, - 2 => return, - 3 => nanos_sdk::exit_app(0), - _ => (), - } - } -} +pub const SW_INS_NOT_SUPPORTED: u16 = 0x6D00; +pub const SW_DENY: u16 = 0x6985; +pub const SW_WRONG_P1P2: u16 = 0x6A86; +pub const SW_WRONG_DATA_LENGTH: u16 = 0x6A87; /// This is the UI flow for signing, composed of a scroller /// to read the incoming message, a panel that requests user @@ -76,7 +45,7 @@ fn sign_ui(message: &[u8]) -> Result, SyscallError> let my_review = ui::MultiFieldReview::new( &my_field, - &["Review ","Transaction"], + &["Review ", "Transaction"], Some(&EYE), "Approve", Some(&VALIDATE_14), @@ -134,21 +103,25 @@ extern "C" fn sample_main() { } #[repr(u8)] + enum Ins { + GetVersion, + GetAppName, GetPubkey, - Sign, - Menu, - Exit, + SignTx, + UnknownIns, } +const CLA: u8 = 0xe0; + impl From for Ins { fn from(header: io::ApduHeader) -> Ins { match header.ins { - 2 => Ins::GetPubkey, - 3 => Ins::Sign, - 4 => Ins::Menu, - 0xff => Ins::Exit, - _ => panic!(), + 3 => Ins::GetVersion, + 4 => Ins::GetAppName, + 5 => Ins::GetPubkey, + 6 => Ins::SignTx, + _ => Ins::UnknownIns, } } } @@ -160,21 +133,45 @@ fn handle_apdu(comm: &mut io::Comm, ins: Ins) -> Result<(), Reply> { return Err(io::StatusWords::NothingReceived.into()); } + let apdu_metadata = comm.get_apdu_metadata(); + + if apdu_metadata.cla != CLA { + return Err(io::StatusWords::BadCla.into()); + } + match ins { + Ins::GetAppName => { + if apdu_metadata.p1 != 0 || apdu_metadata.p2 != 0 { + return Err(io::Reply(SW_WRONG_P1P2)); + } + comm.append(env!("CARGO_PKG_NAME").as_bytes()); + } + Ins::GetVersion => { + if apdu_metadata.p1 != 0 || apdu_metadata.p2 != 0 { + return Err(io::Reply(SW_WRONG_P1P2)); + } + return handler_get_version(comm); + } Ins::GetPubkey => { - let pk = Secp256k1::derive_from_path(&BIP32_PATH) - .public_key() - .map_err(|x| Reply(0x6eu16 | (x as u16 & 0xff)))?; - comm.append(pk.as_ref()); + if apdu_metadata.p1 > 1 || apdu_metadata.p2 != 0 { + return Err(io::Reply(SW_WRONG_P1P2)); + } + + if (comm.get_data()?.len()) == 0 { + return Err(io::Reply(SW_WRONG_DATA_LENGTH)); + } + + return handler_get_public_key(comm, apdu_metadata.p1 == 1); } - Ins::Sign => { + Ins::SignTx => { let out = sign_ui(comm.get_data()?)?; if let Some((signature_buf, length, _)) = out { comm.append(&signature_buf[..length as usize]) } } - Ins::Menu => menu_example(), - Ins::Exit => nanos_sdk::exit_app(0), + Ins::UnknownIns => { + return Err(io::Reply(SW_INS_NOT_SUPPORTED)); + } } Ok(()) } diff --git a/src/utils.rs b/src/utils.rs index 352bbab..9c0acfc 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,12 +1,14 @@ use core::char; +use nanos_sdk::testing; /// Convert to hex. Returns a static buffer of 64 bytes #[inline] -pub fn to_hex(m: &[u8]) -> Result<[u8; 64], ()> { - if 2 * m.len() > 64 { +pub fn to_hex(m: &[u8]) -> Result<[u8; 255], ()> { + if 2 * m.len() > 255 { + testing::debug_print("to_hex: buffer too small\n"); return Err(()); } - let mut hex = [0u8; 64]; + let mut hex = [0u8; 255]; let mut i = 0; for c in m { let c0 = char::from_digit((c >> 4).into(), 16).unwrap(); From e423064fdb82b35dab5a514f26f8f04692bfbb5a Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Tue, 7 Nov 2023 17:41:15 +0100 Subject: [PATCH 06/15] Add Ragger tests and remove obsolete functional tests. --- test/menu.apdu | 1 - test/overflow.apdu | 1 - test/quit.apdu | 1 - test/sign.apdu | 1 - test/test_cmds.py | 31 ---- tests/application_client/__init__.py | 0 .../boilerplate_command_sender.py | 127 ++++++++++++++++ .../boilerplate_response_unpacker.py | 69 +++++++++ .../boilerplate_transaction.py | 52 +++++++ tests/application_client/boilerplate_utils.py | 61 ++++++++ tests/application_client/py.typed | 0 tests/boil-freeze.txt | 60 ++++++++ tests/conftest.py | 15 ++ tests/requirements.txt | 4 + tests/setup.cfg | 21 +++ .../nanos/test_app_mainmenu/00000.png | Bin 0 -> 455 bytes .../nanos/test_app_mainmenu/00001.png | Bin 0 -> 309 bytes .../nanos/test_app_mainmenu/00002.png | Bin 0 -> 327 bytes .../nanos/test_app_mainmenu/00003.png | Bin 0 -> 274 bytes .../00000.png | Bin 0 -> 375 bytes .../00001.png | Bin 0 -> 517 bytes .../00002.png | Bin 0 -> 555 bytes .../00003.png | Bin 0 -> 530 bytes .../00004.png | Bin 0 -> 548 bytes .../00005.png | Bin 0 -> 535 bytes .../00006.png | Bin 0 -> 535 bytes .../00007.png | Bin 0 -> 500 bytes .../00008.png | Bin 0 -> 413 bytes .../00009.png | Bin 0 -> 341 bytes .../00010.png | Bin 0 -> 455 bytes .../00000.png | Bin 0 -> 375 bytes .../00001.png | Bin 0 -> 517 bytes .../00002.png | Bin 0 -> 555 bytes .../00003.png | Bin 0 -> 530 bytes .../00004.png | Bin 0 -> 548 bytes .../00005.png | Bin 0 -> 535 bytes .../00006.png | Bin 0 -> 535 bytes .../00007.png | Bin 0 -> 500 bytes .../00008.png | Bin 0 -> 413 bytes .../00009.png | Bin 0 -> 341 bytes .../00010.png | Bin 0 -> 340 bytes .../00011.png | Bin 0 -> 455 bytes tests/test_app_mainmenu.py | 21 +++ tests/test_appname_cmd.py | 12 ++ tests/test_error_cmd.py | 56 +++++++ tests/test_name_version.py | 15 ++ tests/test_pubkey_cmd.py | 87 +++++++++++ tests/test_sign_cmd.py | 141 ++++++++++++++++++ tests/test_version_cmd.py | 16 ++ tests/usage.md | 74 +++++++++ tests/utils.py | 23 +++ 51 files changed, 854 insertions(+), 35 deletions(-) delete mode 100644 test/menu.apdu delete mode 100644 test/overflow.apdu delete mode 100644 test/quit.apdu delete mode 100644 test/sign.apdu delete mode 100644 test/test_cmds.py create mode 100644 tests/application_client/__init__.py create mode 100644 tests/application_client/boilerplate_command_sender.py create mode 100644 tests/application_client/boilerplate_response_unpacker.py create mode 100644 tests/application_client/boilerplate_transaction.py create mode 100644 tests/application_client/boilerplate_utils.py create mode 100644 tests/application_client/py.typed create mode 100644 tests/boil-freeze.txt create mode 100644 tests/conftest.py create mode 100644 tests/requirements.txt create mode 100644 tests/setup.cfg create mode 100644 tests/snapshots/nanos/test_app_mainmenu/00000.png create mode 100644 tests/snapshots/nanos/test_app_mainmenu/00001.png create mode 100644 tests/snapshots/nanos/test_app_mainmenu/00002.png create mode 100644 tests/snapshots/nanos/test_app_mainmenu/00003.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_accepted/00000.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_accepted/00001.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_accepted/00002.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_accepted/00003.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_accepted/00004.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_accepted/00005.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_accepted/00006.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_accepted/00007.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_accepted/00008.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_accepted/00009.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_accepted/00010.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_refused/00000.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_refused/00001.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_refused/00002.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_refused/00003.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_refused/00004.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_refused/00005.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_refused/00006.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_refused/00007.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_refused/00008.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_refused/00009.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_refused/00010.png create mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_refused/00011.png create mode 100644 tests/test_app_mainmenu.py create mode 100644 tests/test_appname_cmd.py create mode 100644 tests/test_error_cmd.py create mode 100644 tests/test_name_version.py create mode 100644 tests/test_pubkey_cmd.py create mode 100644 tests/test_sign_cmd.py create mode 100644 tests/test_version_cmd.py create mode 100644 tests/usage.md create mode 100644 tests/utils.py diff --git a/test/menu.apdu b/test/menu.apdu deleted file mode 100644 index 9135215..0000000 --- a/test/menu.apdu +++ /dev/null @@ -1 +0,0 @@ -8004 \ No newline at end of file diff --git a/test/overflow.apdu b/test/overflow.apdu deleted file mode 100644 index eacb2f7..0000000 --- a/test/overflow.apdu +++ /dev/null @@ -1 +0,0 @@ -80050008 \ No newline at end of file diff --git a/test/quit.apdu b/test/quit.apdu deleted file mode 100644 index d8d26d5..0000000 --- a/test/quit.apdu +++ /dev/null @@ -1 +0,0 @@ -80FF \ No newline at end of file diff --git a/test/sign.apdu b/test/sign.apdu deleted file mode 100644 index 5cbddb7..0000000 --- a/test/sign.apdu +++ /dev/null @@ -1 +0,0 @@ -800300002000112233445566778899aabbccddeeff0123456789abcdeffedcba9876543210 \ No newline at end of file diff --git a/test/test_cmds.py b/test/test_cmds.py deleted file mode 100644 index 80d9260..0000000 --- a/test/test_cmds.py +++ /dev/null @@ -1,31 +0,0 @@ -from ledgerblue.commTCP import getDongle as getDongleTCP -from ledgerblue.comm import getDongle - -from random import getrandbits as rnd -from binascii import hexlify, unhexlify - -rand_msg = hexlify(rnd(256).to_bytes(32, 'big')).decode() - -CMDS = [ - "8002", - "8003000020" + "00112233445566778899aabbccddeeff0123456789abcdeffedcba9876543210", - "8003000020" + rand_msg, - "8004", - "80050008", - "80FE", - "80FF", -] - -d = getDongleTCP(port=9999) # Speculos -# d = getDongle() # Nano - -from time import sleep -for cmd in map(unhexlify,CMDS): - r = None - try: - r = d.exchange(cmd, 20) - sleep(1) - except Exception as e: - print(e) - if r is not None: - print("Response : ", hexlify(r)) diff --git a/tests/application_client/__init__.py b/tests/application_client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/application_client/boilerplate_command_sender.py b/tests/application_client/boilerplate_command_sender.py new file mode 100644 index 0000000..cb83ce7 --- /dev/null +++ b/tests/application_client/boilerplate_command_sender.py @@ -0,0 +1,127 @@ +from enum import IntEnum +from typing import Generator, List, Optional +from contextlib import contextmanager + +from ragger.backend.interface import BackendInterface, RAPDU +from ragger.bip import pack_derivation_path + + +MAX_APDU_LEN: int = 255 + +CLA: int = 0xE0 + +class P1(IntEnum): + # Parameter 1 for first APDU number. + P1_START = 0x00 + # Parameter 1 for maximum APDU number. + P1_MAX = 0x03 + # Parameter 1 for screen confirmation for GET_PUBLIC_KEY. + P1_CONFIRM = 0x01 + +class P2(IntEnum): + # Parameter 2 for last APDU to receive. + P2_LAST = 0x00 + # Parameter 2 for more APDU to receive. + P2_MORE = 0x80 + +class InsType(IntEnum): + GET_VERSION = 0x03 + GET_APP_NAME = 0x04 + GET_PUBLIC_KEY = 0x05 + SIGN_TX = 0x06 + +class Errors(IntEnum): + SW_DENY = 0x6985 + SW_WRONG_P1P2 = 0x6A86 + SW_WRONG_DATA_LENGTH = 0x6A87 + SW_INS_NOT_SUPPORTED = 0x6D00 + SW_CLA_NOT_SUPPORTED = 0x6E00 + SW_WRONG_RESPONSE_LENGTH = 0xB000 + SW_DISPLAY_BIP32_PATH_FAIL = 0xB001 + SW_DISPLAY_ADDRESS_FAIL = 0xB002 + SW_DISPLAY_AMOUNT_FAIL = 0xB003 + SW_WRONG_TX_LENGTH = 0xB004 + SW_TX_PARSING_FAIL = 0xB005 + SW_TX_HASH_FAIL = 0xB006 + SW_BAD_STATE = 0xB007 + SW_SIGNATURE_FAIL = 0xB008 + + +def split_message(message: bytes, max_size: int) -> List[bytes]: + return [message[x:x + max_size] for x in range(0, len(message), max_size)] + + +class BoilerplateCommandSender: + def __init__(self, backend: BackendInterface) -> None: + self.backend = backend + + + def get_app_and_version(self) -> RAPDU: + return self.backend.exchange(cla=0xB0, # specific CLA for BOLOS + ins=0x01, # specific INS for get_app_and_version + p1=P1.P1_START, + p2=P2.P2_LAST, + data=b"") + + + def get_version(self) -> RAPDU: + return self.backend.exchange(cla=CLA, + ins=InsType.GET_VERSION, + p1=P1.P1_START, + p2=P2.P2_LAST, + data=b"") + + + def get_app_name(self) -> RAPDU: + return self.backend.exchange(cla=CLA, + ins=InsType.GET_APP_NAME, + p1=P1.P1_START, + p2=P2.P2_LAST, + data=b"") + + + def get_public_key(self, path: str) -> RAPDU: + return self.backend.exchange(cla=CLA, + ins=InsType.GET_PUBLIC_KEY, + p1=P1.P1_START, + p2=P2.P2_LAST, + data=pack_derivation_path(path)) + + + @contextmanager + def get_public_key_with_confirmation(self, path: str) -> Generator[None, None, None]: + with self.backend.exchange_async(cla=CLA, + ins=InsType.GET_PUBLIC_KEY, + p1=P1.P1_CONFIRM, + p2=P2.P2_LAST, + data=pack_derivation_path(path)) as response: + yield response + + + @contextmanager + def sign_tx(self, path: str, transaction: bytes) -> Generator[None, None, None]: + self.backend.exchange(cla=CLA, + ins=InsType.SIGN_TX, + p1=P1.P1_START, + p2=P2.P2_MORE, + data=pack_derivation_path(path)) + messages = split_message(transaction, MAX_APDU_LEN) + idx: int = P1.P1_START + 1 + + for msg in messages[:-1]: + self.backend.exchange(cla=CLA, + ins=InsType.SIGN_TX, + p1=idx, + p2=P2.P2_MORE, + data=msg) + idx += 1 + + with self.backend.exchange_async(cla=CLA, + ins=InsType.SIGN_TX, + p1=idx, + p2=P2.P2_LAST, + data=messages[-1]) as response: + yield response + + def get_async_response(self) -> Optional[RAPDU]: + return self.backend.last_async_response diff --git a/tests/application_client/boilerplate_response_unpacker.py b/tests/application_client/boilerplate_response_unpacker.py new file mode 100644 index 0000000..4e6fc9f --- /dev/null +++ b/tests/application_client/boilerplate_response_unpacker.py @@ -0,0 +1,69 @@ +from typing import Tuple +from struct import unpack + +# remainder, data_len, data +def pop_sized_buf_from_buffer(buffer:bytes, size:int) -> Tuple[bytes, bytes]: + return buffer[size:], buffer[0:size] + +# remainder, data_len, data +def pop_size_prefixed_buf_from_buf(buffer:bytes) -> Tuple[bytes, int, bytes]: + data_len = buffer[0] + return buffer[1+data_len:], data_len, buffer[1:data_len+1] + +# Unpack from response: +# response = app_name (var) +def unpack_get_app_name_response(response: bytes) -> str: + return response.decode("ascii") + +# Unpack from response: +# response = MAJOR (1) +# MINOR (1) +# PATCH (1) +def unpack_get_version_response(response: bytes) -> Tuple[int, int, int]: + assert len(response) == 3 + major, minor, patch = unpack("BBB", response) + return (major, minor, patch) + +# Unpack from response: +# response = format_id (1) +# app_name_raw_len (1) +# app_name_raw (var) +# version_raw_len (1) +# version_raw (var) +# unused_len (1) +# unused (var) +def unpack_get_app_and_version_response(response: bytes) -> Tuple[str, str]: + response, _ = pop_sized_buf_from_buffer(response, 1) + response, _, app_name_raw = pop_size_prefixed_buf_from_buf(response) + response, _, version_raw = pop_size_prefixed_buf_from_buf(response) + response, _, _ = pop_size_prefixed_buf_from_buf(response) + + assert len(response) == 0 + + return app_name_raw.decode("ascii"), version_raw.decode("ascii") + +# Unpack from response: +# response = pub_key_len (1) +# pub_key (var) +# chain_code_len (1) +# chain_code (var) +def unpack_get_public_key_response(response: bytes) -> Tuple[int, bytes, int, bytes]: + response, pub_key_len, pub_key = pop_size_prefixed_buf_from_buf(response) + response, chain_code_len, chain_code = pop_size_prefixed_buf_from_buf(response) + + assert pub_key_len == 65 + assert chain_code_len == 32 + assert len(response) == 0 + return pub_key_len, pub_key, chain_code_len, chain_code + +# Unpack from response: +# response = der_sig_len (1) +# der_sig (var) +# v (1) +def unpack_sign_tx_response(response: bytes) -> Tuple[int, bytes, int]: + response, der_sig_len, der_sig = pop_size_prefixed_buf_from_buf(response) + response, v = pop_sized_buf_from_buffer(response, 1) + + assert len(response) == 0 + + return der_sig_len, der_sig, int.from_bytes(v, byteorder='big') diff --git a/tests/application_client/boilerplate_transaction.py b/tests/application_client/boilerplate_transaction.py new file mode 100644 index 0000000..02bd01f --- /dev/null +++ b/tests/application_client/boilerplate_transaction.py @@ -0,0 +1,52 @@ +from io import BytesIO +from typing import Union + +from .boilerplate_utils import read, read_uint, read_varint, write_varint, UINT64_MAX + + +class TransactionError(Exception): + pass + + +class Transaction: + def __init__(self, + nonce: int, + to: Union[str, bytes], + value: int, + memo: str, + do_check: bool = True) -> None: + self.nonce: int = nonce + self.to: bytes = bytes.fromhex(to[2:]) if isinstance(to, str) else to + self.value: int = value + self.memo: bytes = memo.encode("ascii") + + if do_check: + if not 0 <= self.nonce <= UINT64_MAX: + raise TransactionError(f"Bad nonce: '{self.nonce}'!") + + if not 0 <= self.value <= UINT64_MAX: + raise TransactionError(f"Bad value: '{self.value}'!") + + if len(self.to) != 20: + raise TransactionError(f"Bad address: '{self.to.hex()}'!") + + def serialize(self) -> bytes: + return b"".join([ + self.nonce.to_bytes(8, byteorder="big"), + self.to, + self.value.to_bytes(8, byteorder="big"), + write_varint(len(self.memo)), + self.memo + ]) + + @classmethod + def from_bytes(cls, hexa: Union[bytes, BytesIO]): + buf: BytesIO = BytesIO(hexa) if isinstance(hexa, bytes) else hexa + + nonce: int = read_uint(buf, 64, byteorder="big") + to: bytes = read(buf, 20) + value: int = read_uint(buf, 64, byteorder="big") + memo_len: int = read_varint(buf) + memo: str = read(buf, memo_len).decode("ascii") + + return cls(nonce=nonce, to=to, value=value, memo=memo) diff --git a/tests/application_client/boilerplate_utils.py b/tests/application_client/boilerplate_utils.py new file mode 100644 index 0000000..fd96e62 --- /dev/null +++ b/tests/application_client/boilerplate_utils.py @@ -0,0 +1,61 @@ +from io import BytesIO +from typing import Optional, Literal + + +UINT64_MAX: int = 2**64-1 +UINT32_MAX: int = 2**32-1 +UINT16_MAX: int = 2**16-1 + + +def write_varint(n: int) -> bytes: + if n < 0xFC: + return n.to_bytes(1, byteorder="little") + + if n <= UINT16_MAX: + return b"\xFD" + n.to_bytes(2, byteorder="little") + + if n <= UINT32_MAX: + return b"\xFE" + n.to_bytes(4, byteorder="little") + + if n <= UINT64_MAX: + return b"\xFF" + n.to_bytes(8, byteorder="little") + + raise ValueError(f"Can't write to varint: '{n}'!") + + +def read_varint(buf: BytesIO, + prefix: Optional[bytes] = None) -> int: + b: bytes = prefix if prefix else buf.read(1) + + if not b: + raise ValueError(f"Can't read prefix: '{b.hex()}'!") + + n: int = {b"\xfd": 2, b"\xfe": 4, b"\xff": 8}.get(b, 1) # default to 1 + + b = buf.read(n) if n > 1 else b + + if len(b) != n: + raise ValueError("Can't read varint!") + + return int.from_bytes(b, byteorder="little") + + +def read(buf: BytesIO, size: int) -> bytes: + b: bytes = buf.read(size) + + if len(b) < size: + raise ValueError(f"Can't read {size} bytes in buffer!") + + return b + + +def read_uint(buf: BytesIO, + bit_len: int, + byteorder: Literal['big', 'little'] = 'little') -> int: + size: int = bit_len // 8 + b: bytes = buf.read(size) + + if len(b) < size: + raise ValueError(f"Can't read u{bit_len} in buffer!") + + return int.from_bytes(b, byteorder) diff --git a/tests/application_client/py.typed b/tests/application_client/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/tests/boil-freeze.txt b/tests/boil-freeze.txt new file mode 100644 index 0000000..a74fc92 --- /dev/null +++ b/tests/boil-freeze.txt @@ -0,0 +1,60 @@ +aniso8601==9.0.1 +asn1crypto==1.5.1 +attrs==22.2.0 +bip-utils==2.7.0 +cbor2==5.4.6 +certifi==2022.12.7 +cffi==1.15.1 +charset-normalizer==3.0.1 +click==8.1.3 +coincurve==17.0.0 +construct==2.10.68 +crcmod==1.7 +cryptography==39.0.1 +ecdsa==0.16.1 +ed25519-blake2b==1.4 +exceptiongroup==1.1.0 +Flask==2.1.2 +Flask-RESTful==0.3.9 +hidapi==0.13.1 +idna==3.4 +importlib-metadata==6.0.0 +importlib-resources==5.12.0 +iniconfig==2.0.0 +intelhex==2.3.0 +itsdangerous==2.1.2 +Jinja2==3.1.2 +jsonschema==4.17.3 +ledgerwallet==0.2.3 +MarkupSafe==2.1.2 +mnemonic==0.20 +packaging==23.0 +Pillow==9.4.0 +pkg_resources==0.0.0 +pkgutil_resolve_name==1.3.10 +pluggy==1.0.0 +protobuf==3.20.3 +py-sr25519-bindings==0.1.4 +pycparser==2.21 +pycryptodome==3.17 +pyelftools==0.29 +PyNaCl==1.5.0 +PyQt5==5.15.9 +PyQt5-Qt5==5.15.2 +PyQt5-sip==12.11.1 +pyrsistent==0.19.3 +pysha3==1.0.2 +pytesseract==0.3.10 +pytest==7.2.1 +pytz==2022.7.1 +ragger==1.6.0 +requests==2.28.2 +semver==2.13.0 +six==1.16.0 +speculos==0.1.224 +tabulate==0.9.0 +toml==0.10.2 +tomli==2.0.1 +urllib3==1.26.14 +Werkzeug==2.2.3 +zipp==3.15.0 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..909ec8b --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,15 @@ +from ragger.conftest import configuration + +########################### +### CONFIGURATION START ### +########################### + +# You can configure optional parameters by overriding the value of ragger.configuration.OPTIONAL_CONFIGURATION +# Please refer to ragger/conftest/configuration.py for their descriptions and accepted values + +######################### +### CONFIGURATION END ### +######################### + +# Pull all features from the base ragger conftest using the overridden configuration +pytest_plugins = ("ragger.conftest.base_conftest", ) diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..0913153 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,4 @@ +pytest +ragger[speculos,ledgerwallet]>=1.11.4 +ecdsa>=0.16.1,<0.17.0 +pysha3>=1.0.0,<2.0.0 diff --git a/tests/setup.cfg b/tests/setup.cfg new file mode 100644 index 0000000..7d0d7e3 --- /dev/null +++ b/tests/setup.cfg @@ -0,0 +1,21 @@ +[tool:pytest] +addopts = --strict-markers + +[pylint] +disable = C0114, # missing-module-docstring + C0115, # missing-class-docstring + C0116, # missing-function-docstring + C0103, # invalid-name + R0801, # duplicate-code + R0913 # too-many-arguments +max-line-length=100 +extension-pkg-whitelist=hid + +[pycodestyle] +max-line-length = 100 + +[mypy-hid.*] +ignore_missing_imports = True + +[mypy-pytest.*] +ignore_missing_imports = True diff --git a/tests/snapshots/nanos/test_app_mainmenu/00000.png b/tests/snapshots/nanos/test_app_mainmenu/00000.png new file mode 100644 index 0000000000000000000000000000000000000000..68ffb394818b8a1144c7bdffd0e413ebb828eca8 GIT binary patch literal 455 zcmV;&0XY7NP)ktdMRJ7$jtQV?E0Jw2 zEX<>cv$fC(3cFo%Dj&aPIUE4ZO{b?Km?d}fT6HH2&km6xtYY2t?0?Vdg78W$~UYw9Gu>A)BWT- z@}KCar_^|OW15hRAhQF2ZeR=QhlZ+`4$E`Eas@aBM%J9qx?7F%p>;S934?&BM2?}J zlQ_IdDoMLegCO&ry@(xg1W(hjcYU6aul_=@0gb}dlu*bos`+p@Zo;(uu+MEe@Hx8( z6#{B>R8eT>+?LGx?SfhmU2dMPhSs@oxE!RZhALO+^5VzU*c}iW!q%g(soRz@=ckE) x>DmKY`<^6J1d`P&@3BZxxL8|~DvF{=%pcj5^`~Z2UDE&n002ovPDHLkV1l(T)B*qi literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_app_mainmenu/00001.png b/tests/snapshots/nanos/test_app_mainmenu/00001.png new file mode 100644 index 0000000000000000000000000000000000000000..23b5eb1e71187fe3135a142873701080f43548f6 GIT binary patch literal 309 zcmV-50m}Y~P)sTVe;Ey_ss6>#15a3mJmGiPlP%>P?nGC+$O1{krj(r z5|Afh4bB(BsaXn->`fXq<*($eu{7a@cYLdXjbwWf(xm1<&z00000NkvXX Hu0mjftNnp} literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_app_mainmenu/00002.png b/tests/snapshots/nanos/test_app_mainmenu/00002.png new file mode 100644 index 0000000000000000000000000000000000000000..3476b972aad8b16723e63d03d83db7de63b698cd GIT binary patch literal 327 zcmV-N0l5B&P)q%Lk}$JN=*+Ya3V52_$ri_Tw`0y zW~o~dSBJ_)jqx$FVQ-p{pr~clG9JlqkTUKDen}9lbG=+9r&_RRCT`@ta9}o@5 zoGR6f6v&VngBn2&rF;h11n5AoQ(n^?5wLi<0+<0A9oYrVM?&+_T6|lSEB|J;2qA=^ ZvjZ`uW$88COrih)002ovPDHLkV1i+HiM9X$ literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_app_mainmenu/00003.png b/tests/snapshots/nanos/test_app_mainmenu/00003.png new file mode 100644 index 0000000000000000000000000000000000000000..e2279803e79ec6d6443deff45a1f9326b562e303 GIT binary patch literal 274 zcmV+t0qy>YP)D zPq`+h_bpEEKgJ+x(tj8nSi&r1$%l|}@K=b}8w!8IP1np9%K2s%)5~a1UA{VmQ8=+t z-ij#s)$}gEr*9l(42Z6SzZpj z4zQ3%^HmGlPh~1v2GAk(f;HI^<5u-M*+NCj@{yh^gq{Uirn7i#*V}GdEKhr=Zw7}* zt5j31N!mTC(DIvvE9c@ezzQfgzOhWK!PxiBAiv5j&VV}*elA@@y9Ib(^y^EIU|^3Q$%tW{d*b$=;&(vHQyOux1*O|6wz}NGtxk0|_8)0e-jJm90ssJT3tq77 VvGI(pU6=p>002ovPDHLkV1n$ksMG)e literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00001.png b/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00001.png new file mode 100644 index 0000000000000000000000000000000000000000..dce1ab738131945fb8fab9d2430efceb1d35d823 GIT binary patch literal 517 zcmV+g0{Z=lP)J59)GMhFOU7v7^kr*))=~vlD&iBeOp>r7v*4K=i= zF=2JPIr36PHE3l^4dPy8K`KrF4!B$Cb_+$H?ohf_{n|2s^Ea(Y#j%yDiUcHkCqMF}do1EMRcAz+?ZVt3+qOVGs08JJGIcLMvXv?qI9mH@Rs1NS2> zxJwn*DS}nVuO#xhbq7SVd;1evjgya(3nO9VUH(uBx&u`eCTiy|+)PCjHbv#!*0Fy< zNB<-p{ga63uco0+o0S*@BGaMe*gzd9MMp`Xo!shdmHcfl-}ULg((RXTwnSLU;Et)2 z&h+Xl2U4SmpR8KrTz|71Wa&?>pK6o@@dQ|}xa{3hK8X&f9j%0`(w{1^%++F~MAnE7M1s_OTOuI{+eG)>bq%`JEVuTyh5`k2or00000NkvXX Hu0mjf8Y%Bd literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00002.png b/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00002.png new file mode 100644 index 0000000000000000000000000000000000000000..81a18385deaf8fa9fbdfba064270163d8185aea2 GIT binary patch literal 555 zcmV+`0@VG9P)n%W~3qekL|cma|N|=rU4Fs=)kAYnpUN<)U`h zEE87Onjc~f(-yU4o+BX<00x;3Hqk>!O zlJq09X8ld$q@V;VQqYn!Aljoke&&fKrgD6?QF(6)>Nqh2!-?OWLfJ-!@L)s$RxJee zat0cw*>?}qt$6g8bj`>jZ?`ZSBkJoU7%uAtNkrEb^4IP12{*Bdy7oP13)eAV^&`< z>@M{^OWQ3wNak^VSqaJ#qcSqhs-~FF)<_9J0NC(`wLs*(Vxn5`PMkUfQpNJe{>mr- zewPT*Q_=B+8rigpf3ugoZ~3?y+{lz0fc)$)gk>l89wOU61Yig3>;qFD8Y$LWAZ?+i tC2l7V_&gxp^M}xx8);D#MNt&z@C2MRi1JxtPci@i002ovPDHLkV1lN>0tNs8 literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00003.png b/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00003.png new file mode 100644 index 0000000000000000000000000000000000000000..30a64c8a3823888e7505ded812807c6bd6f76cc9 GIT binary patch literal 530 zcmV+t0`2{YP)RF|3td=~LLL_qVt2~stSkkBKuDTyttg73D2h|~Nohas@5cTdNhUzQ zR0Xh|zPhUgtT9s&Cz)6`GR19LQ*k9}WX?_|`fPO>Wx7PqgcOr1VScAIO}eA9s6ov# zVNjB)yi`#Qj%F(n*&r)+K%C(Jr?oZ|eOjKhPJulA1}CW-WMbC7iLoL96KeyNxV0`x zKTOxG>@C(F%0-;9q6IBE14dU=|3CA@65EMmTTln&ESS#t-D$4-4>^-SBw$s^sF$;# zaoTNN6?RPwb|KkqSaSz=q0I5___qCq9R}nhb6{lbn8M!|{pLkyRRR{MTrm3Qa+MtH z$+f!BZd~ph-U<=k@HEt&)mW3lW?a$OJrz$2+|dy8%5uORpsE59Bl*!RLO%4%pMbC` zqfQeomB_w~coH|rbX9p^Kg=mIhvZh#^Y1ZD?GBw()mn-(B7 zzAnd4VL!mJJAm!kuZUohSOU~$x=?H4c1VwLtK(ZQCV`#E$q9@A_llw@ilW$yCk)VU UM#UnFQ2+n{07*qoM6N<$g06z>5&!@I literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00004.png b/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00004.png new file mode 100644 index 0000000000000000000000000000000000000000..e96693e1f1edba4dd42aef810806edf3fd91843d GIT binary patch literal 548 zcmV+<0^9wGP)7kNEc@E&+L|x_-IWS;@vCSUGU@#aA27{O2qptdV{AKzMB$)sm zR0VvVWj)k#*c_L9qf|`tWbj$moP(SyRXIQf8BrvrJe` zG)G>lsDy=RHC7fxdk5eE=T8$YwfZzSX_7kfbR=|daWZweAu?zOpkUZh!P%B1{joP; zt!>m>afWsY+HwYDEvl1eo>*cMbfX;ILp6&FOVENdJ#gmu-3gIl<&FxU9|;kFEjvq1 zB9NJu+E9hv5y2{i)DZcvbq1tP_`tpM5B*Ew#AD-uk)<+)|Jmvv(b8-Tzy_7Ot^Uh# zix|8uZd8Td5cfKV-wk4U?QW=S-KfnR=MqLQqgEf0j$|90Aiv{n_~uHPkOKXdE$3w; z>ovdLR&uIf?+iB~$B{R7`>vJj1%a=B zFfic`RkuIRFWIF3YB{oasKdW(>erhiQFEdIFdMAj+l1N@u*z;9&qvGu&?`&8HRqO~ zf}?C>f|={cu&)A5eguw*8k_5ty#tzQQeend_FXG}o(VwREkVs|q7U%1<;E(M{ZPsf mN;=ncgnuv?3EXnDVW{*rwMmdt=Q zss=vK>OJ&w*ccUKQ@f)!i=m3ojx&#@NmAqKsE@?J>O&?&@?`MUan`Y7l|}8;EHk!B z(z2H>Dq$*HJ!L`cm;pGz@zZiksZT4Ema1dVXf4qf8*`KsB8eRdD0J_*;B3p1KGd|3 znYJcSTXBXROVE}*AlsuV0gJ>kyA%F*30iP+2Bs4~I~7*{R3tr!1nepq^?U}B(*-wl zVJAg!6=Ew1^HL8;hqeb~vm3;A`w5$dlaKO-(IheL-%tG`5@<^T_IN!|{cG`*9NZJn zT7~Y0$L+(n0xGY6o8MgUW>#Y=0QIVF<28oMmPvs6aeKgjCzy^e_h$&D zl8sVyYL5$d>LRbRiROe;HW+XtG$|dqGv3HmNn>zy}3utNm5maq_v#?RJ$wr?a0TODNd@vXc27|!` Z_yx7>tR;yF1~32s002ovPDHLkV1mOn?kE5N literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00006.png b/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00006.png new file mode 100644 index 0000000000000000000000000000000000000000..b7d4f0db0fe698d60220a0a2e3a21ace0f185dfc GIT binary patch literal 535 zcmV+y0_gpTP)2G7B9p$wT1}c7Q zU6Q`Y6s$a3M%yU2;*9hWwB!zmdQ^Wri)19W881A7Iv{7xbozTIXMdH9CxJ-7lB!WV zGpA*G)S4=+M|7S-Y9q@IcWGyI2ZVBLg!dQrYygmtbbS_|xTNLT&b+)|(tWiYa5b?$(PFdkX`Jiz2Uyw`@uR37!nHhryYRb~^AU%ot{ z0NBYMiXYsoX4)(-FW&)IA#Z{`c;+RnUU%|&Ar@=D&p?1yW;H!VJpG0G6t*aeq9}@e Z`2rO`lsc<-N}~V(002ovPDHLkV1i8R@h<=X literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00007.png b/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00007.png new file mode 100644 index 0000000000000000000000000000000000000000..cfde67b4d8d5a664e05496ff1b161e39d1de0d91 GIT binary patch literal 500 zcmVICO{ii z0h?#^zSJ_<7?mVcd!#ahB!sW9rZSWyNsK46`mFaE6>|}33}!zMzQdYRjDbq0Hhh)| ztG;H)OBI#SYpuk}f~eR5IKcT+UvsTKbx-Q2hP-$1?-$oqgBXko0|fduRB*N-Ngpyk zYi}AO1SL=rf=bQ+Z;$Hy*+^z$JK=Xj(12kx(42Vg6pQ|@!6YCIP~|deat4yqXuDKl zX(AYfW|@qd1;|lJN!Rff{rm@E*duvhgn6dI|8Mn=)#_9kpg`q@tA8!-VuL+#uPC$| zZg&oE1+l#TYA9Izv(r$#<^v9CM(Zlj##$upxqlDf7O}f%Bvsd;5thSUqsIP`Fup5f*1Mq#v z%Ed`G#%Z*ilw71~>n>!%MOtZ$GY+xy4#w;60~!fnX(&Ba>|^Jc2L#4L$<@nxLs5N2 q(nrY|NIL0&!HG8-jYgx<*bW~+h<0}@GM`xh00004%P)9b)2)g92paL(i9{cLcOOt;HBu#1jH;My^e^WYIY`Tm zx==#ywGU^5gg0;-dK#9SqTqSI7{XpTs51Mo`ZhEP>V0Y&_Dq2K8pHOhlAF6q9}@@C@$p-4n^@LW*~M$00000NkvXX Hu0mjfmP@>b literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00009.png b/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00009.png new file mode 100644 index 0000000000000000000000000000000000000000..66c411c2ebc833c701039f213ad4ff68cc881146 GIT binary patch literal 341 zcmV-b0jmCqP)_wxG+Yry2qARt#sf<#$}=)VJ~ zBlvESR-Jq3=$jydfC?r!$iuAw9oGJ!U-M%YMJ+y&Ch%TCM^JUq5BGkf8O_{y`#mQ} nq4{V^xv44i3V1v$0C_ea3_go|-rj`700000NkvXXu0mjfd&QW1 literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00010.png b/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00010.png new file mode 100644 index 0000000000000000000000000000000000000000..68ffb394818b8a1144c7bdffd0e413ebb828eca8 GIT binary patch literal 455 zcmV;&0XY7NP)ktdMRJ7$jtQV?E0Jw2 zEX<>cv$fC(3cFo%Dj&aPIUE4ZO{b?Km?d}fT6HH2&km6xtYY2t?0?Vdg78W$~UYw9Gu>A)BWT- z@}KCar_^|OW15hRAhQF2ZeR=QhlZ+`4$E`Eas@aBM%J9qx?7F%p>;S934?&BM2?}J zlQ_IdDoMLegCO&ry@(xg1W(hjcYU6aul_=@0gb}dlu*bos`+p@Zo;(uu+MEe@Hx8( z6#{B>R8eT>+?LGx?SfhmU2dMPhSs@oxE!RZhALO+^5VzU*c}iW!q%g(soRz@=ckE) x>DmKY`<^6J1d`P&@3BZxxL8|~DvF{=%pcj5^`~Z2UDE&n002ovPDHLkV1l(T)B*qi literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_refused/00000.png b/tests/snapshots/nanos/test_get_public_key_confirm_refused/00000.png new file mode 100644 index 0000000000000000000000000000000000000000..4eea762449cd8605b155b45a55ede17581272d0f GIT binary patch literal 375 zcmV--0f_#IP)$}gEr*9l(42Z6SzZpj z4zQ3%^HmGlPh~1v2GAk(f;HI^<5u-M*+NCj@{yh^gq{Uirn7i#*V}GdEKhr=Zw7}* zt5j31N!mTC(DIvvE9c@ezzQfgzOhWK!PxiBAiv5j&VV}*elA@@y9Ib(^y^EIU|^3Q$%tW{d*b$=;&(vHQyOux1*O|6wz}NGtxk0|_8)0e-jJm90ssJT3tq77 VvGI(pU6=p>002ovPDHLkV1n$ksMG)e literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_refused/00001.png b/tests/snapshots/nanos/test_get_public_key_confirm_refused/00001.png new file mode 100644 index 0000000000000000000000000000000000000000..dce1ab738131945fb8fab9d2430efceb1d35d823 GIT binary patch literal 517 zcmV+g0{Z=lP)J59)GMhFOU7v7^kr*))=~vlD&iBeOp>r7v*4K=i= zF=2JPIr36PHE3l^4dPy8K`KrF4!B$Cb_+$H?ohf_{n|2s^Ea(Y#j%yDiUcHkCqMF}do1EMRcAz+?ZVt3+qOVGs08JJGIcLMvXv?qI9mH@Rs1NS2> zxJwn*DS}nVuO#xhbq7SVd;1evjgya(3nO9VUH(uBx&u`eCTiy|+)PCjHbv#!*0Fy< zNB<-p{ga63uco0+o0S*@BGaMe*gzd9MMp`Xo!shdmHcfl-}ULg((RXTwnSLU;Et)2 z&h+Xl2U4SmpR8KrTz|71Wa&?>pK6o@@dQ|}xa{3hK8X&f9j%0`(w{1^%++F~MAnE7M1s_OTOuI{+eG)>bq%`JEVuTyh5`k2or00000NkvXX Hu0mjf8Y%Bd literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_refused/00002.png b/tests/snapshots/nanos/test_get_public_key_confirm_refused/00002.png new file mode 100644 index 0000000000000000000000000000000000000000..81a18385deaf8fa9fbdfba064270163d8185aea2 GIT binary patch literal 555 zcmV+`0@VG9P)n%W~3qekL|cma|N|=rU4Fs=)kAYnpUN<)U`h zEE87Onjc~f(-yU4o+BX<00x;3Hqk>!O zlJq09X8ld$q@V;VQqYn!Aljoke&&fKrgD6?QF(6)>Nqh2!-?OWLfJ-!@L)s$RxJee zat0cw*>?}qt$6g8bj`>jZ?`ZSBkJoU7%uAtNkrEb^4IP12{*Bdy7oP13)eAV^&`< z>@M{^OWQ3wNak^VSqaJ#qcSqhs-~FF)<_9J0NC(`wLs*(Vxn5`PMkUfQpNJe{>mr- zewPT*Q_=B+8rigpf3ugoZ~3?y+{lz0fc)$)gk>l89wOU61Yig3>;qFD8Y$LWAZ?+i tC2l7V_&gxp^M}xx8);D#MNt&z@C2MRi1JxtPci@i002ovPDHLkV1lN>0tNs8 literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_refused/00003.png b/tests/snapshots/nanos/test_get_public_key_confirm_refused/00003.png new file mode 100644 index 0000000000000000000000000000000000000000..30a64c8a3823888e7505ded812807c6bd6f76cc9 GIT binary patch literal 530 zcmV+t0`2{YP)RF|3td=~LLL_qVt2~stSkkBKuDTyttg73D2h|~Nohas@5cTdNhUzQ zR0Xh|zPhUgtT9s&Cz)6`GR19LQ*k9}WX?_|`fPO>Wx7PqgcOr1VScAIO}eA9s6ov# zVNjB)yi`#Qj%F(n*&r)+K%C(Jr?oZ|eOjKhPJulA1}CW-WMbC7iLoL96KeyNxV0`x zKTOxG>@C(F%0-;9q6IBE14dU=|3CA@65EMmTTln&ESS#t-D$4-4>^-SBw$s^sF$;# zaoTNN6?RPwb|KkqSaSz=q0I5___qCq9R}nhb6{lbn8M!|{pLkyRRR{MTrm3Qa+MtH z$+f!BZd~ph-U<=k@HEt&)mW3lW?a$OJrz$2+|dy8%5uORpsE59Bl*!RLO%4%pMbC` zqfQeomB_w~coH|rbX9p^Kg=mIhvZh#^Y1ZD?GBw()mn-(B7 zzAnd4VL!mJJAm!kuZUohSOU~$x=?H4c1VwLtK(ZQCV`#E$q9@A_llw@ilW$yCk)VU UM#UnFQ2+n{07*qoM6N<$g06z>5&!@I literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_refused/00004.png b/tests/snapshots/nanos/test_get_public_key_confirm_refused/00004.png new file mode 100644 index 0000000000000000000000000000000000000000..e96693e1f1edba4dd42aef810806edf3fd91843d GIT binary patch literal 548 zcmV+<0^9wGP)7kNEc@E&+L|x_-IWS;@vCSUGU@#aA27{O2qptdV{AKzMB$)sm zR0VvVWj)k#*c_L9qf|`tWbj$moP(SyRXIQf8BrvrJe` zG)G>lsDy=RHC7fxdk5eE=T8$YwfZzSX_7kfbR=|daWZweAu?zOpkUZh!P%B1{joP; zt!>m>afWsY+HwYDEvl1eo>*cMbfX;ILp6&FOVENdJ#gmu-3gIl<&FxU9|;kFEjvq1 zB9NJu+E9hv5y2{i)DZcvbq1tP_`tpM5B*Ew#AD-uk)<+)|Jmvv(b8-Tzy_7Ot^Uh# zix|8uZd8Td5cfKV-wk4U?QW=S-KfnR=MqLQqgEf0j$|90Aiv{n_~uHPkOKXdE$3w; z>ovdLR&uIf?+iB~$B{R7`>vJj1%a=B zFfic`RkuIRFWIF3YB{oasKdW(>erhiQFEdIFdMAj+l1N@u*z;9&qvGu&?`&8HRqO~ zf}?C>f|={cu&)A5eguw*8k_5ty#tzQQeend_FXG}o(VwREkVs|q7U%1<;E(M{ZPsf mN;=ncgnuv?3EXnDVW{*rwMmdt=Q zss=vK>OJ&w*ccUKQ@f)!i=m3ojx&#@NmAqKsE@?J>O&?&@?`MUan`Y7l|}8;EHk!B z(z2H>Dq$*HJ!L`cm;pGz@zZiksZT4Ema1dVXf4qf8*`KsB8eRdD0J_*;B3p1KGd|3 znYJcSTXBXROVE}*AlsuV0gJ>kyA%F*30iP+2Bs4~I~7*{R3tr!1nepq^?U}B(*-wl zVJAg!6=Ew1^HL8;hqeb~vm3;A`w5$dlaKO-(IheL-%tG`5@<^T_IN!|{cG`*9NZJn zT7~Y0$L+(n0xGY6o8MgUW>#Y=0QIVF<28oMmPvs6aeKgjCzy^e_h$&D zl8sVyYL5$d>LRbRiROe;HW+XtG$|dqGv3HmNn>zy}3utNm5maq_v#?RJ$wr?a0TODNd@vXc27|!` Z_yx7>tR;yF1~32s002ovPDHLkV1mOn?kE5N literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_refused/00006.png b/tests/snapshots/nanos/test_get_public_key_confirm_refused/00006.png new file mode 100644 index 0000000000000000000000000000000000000000..b7d4f0db0fe698d60220a0a2e3a21ace0f185dfc GIT binary patch literal 535 zcmV+y0_gpTP)2G7B9p$wT1}c7Q zU6Q`Y6s$a3M%yU2;*9hWwB!zmdQ^Wri)19W881A7Iv{7xbozTIXMdH9CxJ-7lB!WV zGpA*G)S4=+M|7S-Y9q@IcWGyI2ZVBLg!dQrYygmtbbS_|xTNLT&b+)|(tWiYa5b?$(PFdkX`Jiz2Uyw`@uR37!nHhryYRb~^AU%ot{ z0NBYMiXYsoX4)(-FW&)IA#Z{`c;+RnUU%|&Ar@=D&p?1yW;H!VJpG0G6t*aeq9}@e Z`2rO`lsc<-N}~V(002ovPDHLkV1i8R@h<=X literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_refused/00007.png b/tests/snapshots/nanos/test_get_public_key_confirm_refused/00007.png new file mode 100644 index 0000000000000000000000000000000000000000..cfde67b4d8d5a664e05496ff1b161e39d1de0d91 GIT binary patch literal 500 zcmVICO{ii z0h?#^zSJ_<7?mVcd!#ahB!sW9rZSWyNsK46`mFaE6>|}33}!zMzQdYRjDbq0Hhh)| ztG;H)OBI#SYpuk}f~eR5IKcT+UvsTKbx-Q2hP-$1?-$oqgBXko0|fduRB*N-Ngpyk zYi}AO1SL=rf=bQ+Z;$Hy*+^z$JK=Xj(12kx(42Vg6pQ|@!6YCIP~|deat4yqXuDKl zX(AYfW|@qd1;|lJN!Rff{rm@E*duvhgn6dI|8Mn=)#_9kpg`q@tA8!-VuL+#uPC$| zZg&oE1+l#TYA9Izv(r$#<^v9CM(Zlj##$upxqlDf7O}f%Bvsd;5thSUqsIP`Fup5f*1Mq#v z%Ed`G#%Z*ilw71~>n>!%MOtZ$GY+xy4#w;60~!fnX(&Ba>|^Jc2L#4L$<@nxLs5N2 q(nrY|NIL0&!HG8-jYgx<*bW~+h<0}@GM`xh00004%P)9b)2)g92paL(i9{cLcOOt;HBu#1jH;My^e^WYIY`Tm zx==#ywGU^5gg0;-dK#9SqTqSI7{XpTs51Mo`ZhEP>V0Y&_Dq2K8pHOhlAF6q9}@@C@$p-4n^@LW*~M$00000NkvXX Hu0mjfmP@>b literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_refused/00009.png b/tests/snapshots/nanos/test_get_public_key_confirm_refused/00009.png new file mode 100644 index 0000000000000000000000000000000000000000..66c411c2ebc833c701039f213ad4ff68cc881146 GIT binary patch literal 341 zcmV-b0jmCqP)_wxG+Yry2qARt#sf<#$}=)VJ~ zBlvESR-Jq3=$jydfC?r!$iuAw9oGJ!U-M%YMJ+y&Ch%TCM^JUq5BGkf8O_{y`#mQ} nq4{V^xv44i3V1v$0C_ea3_go|-rj`700000NkvXXu0mjfd&QW1 literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_refused/00010.png b/tests/snapshots/nanos/test_get_public_key_confirm_refused/00010.png new file mode 100644 index 0000000000000000000000000000000000000000..9c7e7049cb3e9bcfb1601ec510ee465d38229d4d GIT binary patch literal 340 zcmV-a0jvIrP)b=%MgRq*37h4eRxPbkJCLY|1VIo49_}P}TKpH4$L<8?X{t;p zy+UQc_4@p%0?~E_&igM#?#L~IOHR(-<@sYotiy&C*Y&GII0yeh-p3zW9cv$Q0k>6Y_)5~SfP=m zSMUtz)%Ex|-o}7!H9hbQ(8{%C?kQVa?C`*Uj-J(h>P7(Y#?ZWvi?6}@n{fGLp>YTp myqR(V_$?>^<%seR_VWh!ktdMRJ7$jtQV?E0Jw2 zEX<>cv$fC(3cFo%Dj&aPIUE4ZO{b?Km?d}fT6HH2&km6xtYY2t?0?Vdg78W$~UYw9Gu>A)BWT- z@}KCar_^|OW15hRAhQF2ZeR=QhlZ+`4$E`Eas@aBM%J9qx?7F%p>;S934?&BM2?}J zlQ_IdDoMLegCO&ry@(xg1W(hjcYU6aul_=@0gb}dlu*bos`+p@Zo;(uu+MEe@Hx8( z6#{B>R8eT>+?LGx?SfhmU2dMPhSs@oxE!RZhALO+^5VzU*c}iW!q%g(soRz@=ckE) x>DmKY`<^6J1d`P&@3BZxxL8|~DvF{=%pcj5^`~Z2UDE&n002ovPDHLkV1l(T)B*qi literal 0 HcmV?d00001 diff --git a/tests/test_app_mainmenu.py b/tests/test_app_mainmenu.py new file mode 100644 index 0000000..de7f3ce --- /dev/null +++ b/tests/test_app_mainmenu.py @@ -0,0 +1,21 @@ +from ragger.navigator import NavInsID + +from utils import ROOT_SCREENSHOT_PATH + + +# In this test we check the behavior of the device main menu +def test_app_mainmenu(firmware, navigator, test_name): + # Navigate in the main menu + if firmware.device.startswith("nano"): + instructions = [ + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK + ] + else: + instructions = [ + NavInsID.USE_CASE_HOME_INFO, + NavInsID.USE_CASE_SETTINGS_SINGLE_PAGE_EXIT + ] + navigator.navigate_and_compare(ROOT_SCREENSHOT_PATH, test_name, instructions, + screen_change_before_first_instruction=False) diff --git a/tests/test_appname_cmd.py b/tests/test_appname_cmd.py new file mode 100644 index 0000000..dd6446b --- /dev/null +++ b/tests/test_appname_cmd.py @@ -0,0 +1,12 @@ +from application_client.boilerplate_command_sender import BoilerplateCommandSender +from application_client.boilerplate_response_unpacker import unpack_get_app_name_response + + +# In this test we check that the GET_APP_NAME replies the application name +def test_app_name(backend): + # Use the app interface instead of raw interface + client = BoilerplateCommandSender(backend) + # Send the GET_APP_NAME instruction to the app + response = client.get_app_name() + # Assert that we have received the correct appname + assert unpack_get_app_name_response(response.data) == "app-boilerplate-rust" diff --git a/tests/test_error_cmd.py b/tests/test_error_cmd.py new file mode 100644 index 0000000..277f2f8 --- /dev/null +++ b/tests/test_error_cmd.py @@ -0,0 +1,56 @@ +import pytest + +from ragger.error import ExceptionRAPDU +from application_client.boilerplate_command_sender import CLA, InsType, P1, P2, Errors + + +# Ensure the app returns an error when a bad CLA is used +def test_bad_cla(backend): + with pytest.raises(ExceptionRAPDU) as e: + backend.exchange(cla=CLA + 1, ins=InsType.GET_VERSION) + assert e.value.status == Errors.SW_CLA_NOT_SUPPORTED + + +# Ensure the app returns an error when a bad INS is used +def test_bad_ins(backend): + with pytest.raises(ExceptionRAPDU) as e: + backend.exchange(cla=CLA, ins=0xff) + assert e.value.status == Errors.SW_INS_NOT_SUPPORTED + + +# Ensure the app returns an error when a bad P1 or P2 is used +def test_wrong_p1p2(backend): + with pytest.raises(ExceptionRAPDU) as e: + backend.exchange(cla=CLA, ins=InsType.GET_VERSION, p1=P1.P1_START + 1, p2=P2.P2_LAST) + assert e.value.status == Errors.SW_WRONG_P1P2 + with pytest.raises(ExceptionRAPDU) as e: + backend.exchange(cla=CLA, ins=InsType.GET_VERSION, p1=P1.P1_START, p2=P2.P2_MORE) + assert e.value.status == Errors.SW_WRONG_P1P2 + with pytest.raises(ExceptionRAPDU) as e: + backend.exchange(cla=CLA, ins=InsType.GET_APP_NAME, p1=P1.P1_START + 1, p2=P2.P2_LAST) + assert e.value.status == Errors.SW_WRONG_P1P2 + with pytest.raises(ExceptionRAPDU) as e: + backend.exchange(cla=CLA, ins=InsType.GET_APP_NAME, p1=P1.P1_START, p2=P2.P2_MORE) + assert e.value.status == Errors.SW_WRONG_P1P2 + +# Ensure the app returns an error when a bad data length is used +# def test_wrong_data_length(backend): +# # APDUs must be at least 5 bytes: CLA, INS, P1, P2, Lc. +# with pytest.raises(ExceptionRAPDU) as e: +# backend.exchange_raw(b"E0030000") +# assert e.value.status == Errors.SW_WRONG_DATA_LENGTH +# # APDUs advertises a too long length +# with pytest.raises(ExceptionRAPDU) as e: +# backend.exchange_raw(b"E003000005") +# assert e.value.status == Errors.SW_WRONG_DATA_LENGTH + + +# Ensure there is no state confusion when trying wrong APDU sequences +# def test_invalid_state(backend): +# with pytest.raises(ExceptionRAPDU) as e: +# backend.exchange(cla=CLA, +# ins=InsType.SIGN_TX, +# p1=P1.P1_START + 1, # Try to continue a flow instead of start a new one +# p2=P2.P2_MORE, +# data=b"abcde") # data is not parsed in this case +# assert e.value.status == Errors.SW_BAD_STATE diff --git a/tests/test_name_version.py b/tests/test_name_version.py new file mode 100644 index 0000000..5248663 --- /dev/null +++ b/tests/test_name_version.py @@ -0,0 +1,15 @@ +# from application_client.boilerplate_command_sender import BoilerplateCommandSender +# from application_client.boilerplate_response_unpacker import unpack_get_app_and_version_response + + +# # Test a specific APDU asking BOLOS (and not the app) the name and version of the current app +# def test_get_app_and_version(backend, backend_name): +# # Use the app interface instead of raw interface +# client = BoilerplateCommandSender(backend) +# # Send the special instruction to BOLOS +# response = client.get_app_and_version() +# # Use an helper to parse the response, assert the values +# app_name, version = unpack_get_app_and_version_response(response.data) + +# assert app_name == "app-boilerplate-rust" +# assert version == "1.0.0" diff --git a/tests/test_pubkey_cmd.py b/tests/test_pubkey_cmd.py new file mode 100644 index 0000000..f295411 --- /dev/null +++ b/tests/test_pubkey_cmd.py @@ -0,0 +1,87 @@ +import pytest + +from application_client.boilerplate_command_sender import BoilerplateCommandSender, Errors +from application_client.boilerplate_response_unpacker import unpack_get_public_key_response +from ragger.bip import calculate_public_key_and_chaincode, CurveChoice +from ragger.error import ExceptionRAPDU +from ragger.navigator import NavInsID, NavIns +from utils import ROOT_SCREENSHOT_PATH + + +# In this test we check that the GET_PUBLIC_KEY works in non-confirmation mode +def test_get_public_key_no_confirm(backend): + for path in ["m/44'/1'/0'/0/0", "m/44'/1'/0/0/0", "m/44'/1'/911'/0/0", "m/44'/1'/255/255/255", "m/44'/1'/2147483647/0/0/0/0/0/0/0"]: + client = BoilerplateCommandSender(backend) + response = client.get_public_key(path=path).data + _, public_key, _, _ = unpack_get_public_key_response(response) + + ref_public_key, _ = calculate_public_key_and_chaincode(CurveChoice.Secp256k1, path=path) + assert public_key.hex() == ref_public_key + + +# In this test we check that the GET_PUBLIC_KEY works in confirmation mode +def test_get_public_key_confirm_accepted(firmware, backend, navigator, test_name): + client = BoilerplateCommandSender(backend) + path = "m/44'/1'/0'/0/0" + with client.get_public_key_with_confirmation(path=path): + if firmware.device.startswith("nano"): + navigator.navigate_until_text_and_compare(NavInsID.RIGHT_CLICK, + [NavInsID.BOTH_CLICK], + "Approve", + ROOT_SCREENSHOT_PATH, + test_name) + else: + instructions = [ + NavInsID.USE_CASE_REVIEW_TAP, + NavIns(NavInsID.TOUCH, (200, 335)), + NavInsID.USE_CASE_ADDRESS_CONFIRMATION_EXIT_QR, + NavInsID.USE_CASE_ADDRESS_CONFIRMATION_CONFIRM, + NavInsID.USE_CASE_STATUS_DISMISS + ] + navigator.navigate_and_compare(ROOT_SCREENSHOT_PATH, + test_name, + instructions) + response = client.get_async_response().data + _, public_key, _, _ = unpack_get_public_key_response(response) + + ref_public_key, _ = calculate_public_key_and_chaincode(CurveChoice.Secp256k1, path=path) + assert public_key.hex() == ref_public_key + + +# In this test we check that the GET_PUBLIC_KEY in confirmation mode replies an error if the user refuses +def test_get_public_key_confirm_refused(firmware, backend, navigator, test_name): + client = BoilerplateCommandSender(backend) + path = "m/44'/1'/0'/0/0" + + if firmware.device.startswith("nano"): + with pytest.raises(ExceptionRAPDU) as e: + with client.get_public_key_with_confirmation(path=path): + navigator.navigate_until_text_and_compare(NavInsID.RIGHT_CLICK, + [NavInsID.BOTH_CLICK], + "Reject", + ROOT_SCREENSHOT_PATH, + test_name) + # Assert that we have received a refusal + assert e.value.status == Errors.SW_DENY + assert len(e.value.data) == 0 + else: + instructions_set = [ + [ + NavInsID.USE_CASE_REVIEW_REJECT, + NavInsID.USE_CASE_STATUS_DISMISS + ], + [ + NavInsID.USE_CASE_REVIEW_TAP, + NavInsID.USE_CASE_ADDRESS_CONFIRMATION_CANCEL, + NavInsID.USE_CASE_STATUS_DISMISS + ] + ] + for i, instructions in enumerate(instructions_set): + with pytest.raises(ExceptionRAPDU) as e: + with client.get_public_key_with_confirmation(path=path): + navigator.navigate_and_compare(ROOT_SCREENSHOT_PATH, + test_name + f"/part{i}", + instructions) + # Assert that we have received a refusal + assert e.value.status == Errors.SW_DENY + assert len(e.value.data) == 0 diff --git a/tests/test_sign_cmd.py b/tests/test_sign_cmd.py new file mode 100644 index 0000000..fb9affb --- /dev/null +++ b/tests/test_sign_cmd.py @@ -0,0 +1,141 @@ +# import pytest + +# from application_client.boilerplate_transaction import Transaction +# from application_client.boilerplate_command_sender import BoilerplateCommandSender, Errors +# from application_client.boilerplate_response_unpacker import unpack_get_public_key_response, unpack_sign_tx_response +# from ragger.error import ExceptionRAPDU +# from ragger.navigator import NavInsID +# from utils import ROOT_SCREENSHOT_PATH, check_signature_validity + +# # In this tests we check the behavior of the device when asked to sign a transaction + + +# # In this test se send to the device a transaction to sign and validate it on screen +# # The transaction is short and will be sent in one chunk +# # We will ensure that the displayed information is correct by using screenshots comparison +# def test_sign_tx_short_tx(firmware, backend, navigator, test_name): +# # Use the app interface instead of raw interface +# client = BoilerplateCommandSender(backend) +# # The path used for this entire test +# path: str = "m/44'/1'/0'/0/0" + +# # First we need to get the public key of the device in order to build the transaction +# rapdu = client.get_public_key(path=path) +# _, public_key, _, _ = unpack_get_public_key_response(rapdu.data) + +# # Create the transaction that will be sent to the device for signing +# transaction = Transaction( +# nonce=1, +# to="0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", +# value=666, +# memo="For u EthDev" +# ).serialize() + +# # Send the sign device instruction. +# # As it requires on-screen validation, the function is asynchronous. +# # It will yield the result when the navigation is done +# with client.sign_tx(path=path, transaction=transaction): +# # Validate the on-screen request by performing the navigation appropriate for this device +# if firmware.device.startswith("nano"): +# navigator.navigate_until_text_and_compare(NavInsID.RIGHT_CLICK, +# [NavInsID.BOTH_CLICK], +# "Approve", +# ROOT_SCREENSHOT_PATH, +# test_name) +# else: +# navigator.navigate_until_text_and_compare(NavInsID.USE_CASE_REVIEW_TAP, +# [NavInsID.USE_CASE_REVIEW_CONFIRM, +# NavInsID.USE_CASE_STATUS_DISMISS], +# "Hold to sign", +# ROOT_SCREENSHOT_PATH, +# test_name) + +# # The device as yielded the result, parse it and ensure that the signature is correct +# response = client.get_async_response().data +# _, der_sig, _ = unpack_sign_tx_response(response) +# assert check_signature_validity(public_key, der_sig, transaction) + + +# # In this test se send to the device a transaction to sign and validate it on screen +# # This test is mostly the same as the previous one but with different values. +# # In particular the long memo will force the transaction to be sent in multiple chunks +# def test_sign_tx_long_tx(firmware, backend, navigator, test_name): +# # Use the app interface instead of raw interface +# client = BoilerplateCommandSender(backend) +# path: str = "m/44'/1'/0'/0/0" + +# rapdu = client.get_public_key(path=path) +# _, public_key, _, _ = unpack_get_public_key_response(rapdu.data) + +# transaction = Transaction( +# nonce=1, +# to="0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", +# value=666, +# memo=("This is a very long memo. " +# "It will force the app client to send the serialized transaction to be sent in chunk. " +# "As the maximum chunk size is 255 bytes we will make this memo greater than 255 characters. " +# "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam.") +# ).serialize() + +# with client.sign_tx(path=path, transaction=transaction): +# if firmware.device.startswith("nano"): +# navigator.navigate_until_text_and_compare(NavInsID.RIGHT_CLICK, +# [NavInsID.BOTH_CLICK], +# "Approve", +# ROOT_SCREENSHOT_PATH, +# test_name) +# else: +# navigator.navigate_until_text_and_compare(NavInsID.USE_CASE_REVIEW_TAP, +# [NavInsID.USE_CASE_REVIEW_CONFIRM, +# NavInsID.USE_CASE_STATUS_DISMISS], +# "Hold to sign", +# ROOT_SCREENSHOT_PATH, +# test_name) +# response = client.get_async_response().data +# _, der_sig, _ = unpack_sign_tx_response(response) +# assert check_signature_validity(public_key, der_sig, transaction) + + +# # Transaction signature refused test +# # The test will ask for a transaction signature that will be refused on screen +# def test_sign_tx_refused(firmware, backend, navigator, test_name): +# # Use the app interface instead of raw interface +# client = BoilerplateCommandSender(backend) +# path: str = "m/44'/1'/0'/0/0" + +# rapdu = client.get_public_key(path=path) +# _, pub_key, _, _ = unpack_get_public_key_response(rapdu.data) + +# transaction = Transaction( +# nonce=1, +# to="0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", +# value=666, +# memo="This transaction will be refused by the user" +# ).serialize() + +# if firmware.device.startswith("nano"): +# with pytest.raises(ExceptionRAPDU) as e: +# with client.sign_tx(path=path, transaction=transaction): +# navigator.navigate_until_text_and_compare(NavInsID.RIGHT_CLICK, +# [NavInsID.BOTH_CLICK], +# "Reject", +# ROOT_SCREENSHOT_PATH, +# test_name) + +# # Assert that we have received a refusal +# assert e.value.status == Errors.SW_DENY +# assert len(e.value.data) == 0 +# else: +# for i in range(3): +# instructions = [NavInsID.USE_CASE_REVIEW_TAP] * i +# instructions += [NavInsID.USE_CASE_REVIEW_REJECT, +# NavInsID.USE_CASE_CHOICE_CONFIRM, +# NavInsID.USE_CASE_STATUS_DISMISS] +# with pytest.raises(ExceptionRAPDU) as e: +# with client.sign_tx(path=path, transaction=transaction): +# navigator.navigate_and_compare(ROOT_SCREENSHOT_PATH, +# test_name + f"/part{i}", +# instructions) +# # Assert that we have received a refusal +# assert e.value.status == Errors.SW_DENY +# assert len(e.value.data) == 0 diff --git a/tests/test_version_cmd.py b/tests/test_version_cmd.py new file mode 100644 index 0000000..cc9e4a0 --- /dev/null +++ b/tests/test_version_cmd.py @@ -0,0 +1,16 @@ +from application_client.boilerplate_command_sender import BoilerplateCommandSender +from application_client.boilerplate_response_unpacker import unpack_get_version_response + +# Taken from the Cargo.toml, to update every time the version is bumped +MAJOR = 1 +MINOR = 0 +PATCH = 0 + +# In this test we check the behavior of the device when asked to provide the app version +def test_version(backend): + # Use the app interface instead of raw interface + client = BoilerplateCommandSender(backend) + # Send the GET_VERSION instruction + rapdu = client.get_version() + # Use an helper to parse the response, assert the values + assert unpack_get_version_response(rapdu.data) == (MAJOR, MINOR, PATCH) diff --git a/tests/usage.md b/tests/usage.md new file mode 100644 index 0000000..be8890f --- /dev/null +++ b/tests/usage.md @@ -0,0 +1,74 @@ +# How to use the Ragger test framework + +This framework allows testing the application on the Speculos emulator or on a real device using LedgerComm or LedgerWallet + + +## Quickly get started with Ragger and Speculos + +### Install ragger and dependencies + +``` +pip install --extra-index-url https://test.pypi.org/simple/ -r requirements.txt +sudo apt-get update && sudo apt-get install qemu-user-static +``` + +### Compile the application + +The application to test must be compiled for all required devices. +You can use for this the container `ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite`: +``` +docker pull ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest +cd # replace with the name of your app, (eg boilerplate) +docker run --user "$(id -u)":"$(id -g)" --rm -ti -v "$(realpath .):/app" --privileged -v "/dev/bus/usb:/dev/bus/usb" ledger-app-builder-lite:latest +make clean && make BOLOS_SDK=$_SDK # replace with one of [NANOS, NANOX, NANOSP, STAX] +exit +``` + +### Run a simple test using the Speculos emulator + +You can use the following command to get your first experience with Ragger and Speculos +``` +pytest -v --tb=short --device nanox --display +``` +Or you can refer to the section `Available pytest options` to configure the options you want to use + + +### Run a simple test using a real device + +The application to test must be loaded and started on a Ledger device plugged in USB. +You can use for this the container `ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite`: +``` +docker pull ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest +cd app-/ # replace with the name of your app, (eg boilerplate) +docker run --user "$(id -u)":"$(id -g)" --rm -ti -v "$(realpath .):/app" --privileged -v "/dev/bus/usb:/dev/bus/usb" ledger-app-builder-lite:latest +make clean && make BOLOS_SDK=$_SDK load # replace with one of [NANOS, NANOX, NANOSP, STAX] +exit +``` + +You can use the following command to get your first experience with Ragger and Ledgerwallet on a NANOX. +Make sure that the device is plugged, unlocked, and that the tested application is open. +``` +pytest -v --tb=short --device nanox --backend ledgerwallet +``` +Or you can refer to the section `Available pytest options` to configure the options you want to use + + +## Available pytest options + +Standard useful pytest options +``` + -v formats the test summary in a readable way + -s enable logs for successful tests, on Speculos it will enable app logs if compiled with DEBUG=1 + -k only run the tests that contain in their names + --tb=short in case of errors, formats the test traceback in a readable way +``` + +Custom pytest options +``` + --device run the test on the specified device [nanos,nanox,nanosp,stax,all]. This parameter is mandatory + --backend run the tests against the backend [speculos, ledgercomm, ledgerwallet]. Speculos is the default + --display on Speculos, enables the display of the app screen using QT + --golden_run on Speculos, screen comparison functions will save the current screen instead of comparing + --log_apdu_file log all apdu exchanges to the file in parameter. The previous file content is erased +``` + diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..cb52233 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,23 @@ +from pathlib import Path +from hashlib import sha256 +from sha3 import keccak_256 + +from ecdsa.curves import SECP256k1 +from ecdsa.keys import VerifyingKey +from ecdsa.util import sigdecode_der + + +ROOT_SCREENSHOT_PATH = Path(__file__).parent.resolve() + + +# Check if a signature of a given message is valid +def check_signature_validity(public_key: bytes, signature: bytes, message: bytes) -> bool: + pk: VerifyingKey = VerifyingKey.from_string( + public_key, + curve=SECP256k1, + hashfunc=sha256 + ) + return pk.verify(signature=signature, + data=message, + hashfunc=keccak_256, + sigdecode=sigdecode_der) From 00d537f43b91e61d5971ada0fe980bc0f339b4d6 Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Fri, 10 Nov 2023 17:03:05 +0100 Subject: [PATCH 07/15] Add transaction signing APDU handling and display. --- Cargo.toml | 1 + src/app_ui/address.rs | 32 ++++-- src/app_ui/sign.rs | 79 +++++++++++++ src/handlers/get_public_key.rs | 72 ++++++------ src/handlers/sign_tx.rs | 202 +++++++++++++++++++++++++++++++++ src/main.rs | 89 +++++++-------- src/utils.rs | 117 ++++++++++++++++++- 7 files changed, 495 insertions(+), 97 deletions(-) create mode 100644 src/app_ui/sign.rs create mode 100644 src/handlers/sign_tx.rs diff --git a/Cargo.toml b/Cargo.toml index 4338d38..dbdf0d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" nanos_sdk = { git = "https://github.com/LedgerHQ/ledger-nanos-sdk.git" } nanos_ui = { git = "https://github.com/LedgerHQ/ledger-nanos-ui.git" } include_gif = { git = "https://github.com/LedgerHQ/sdk_include_gif" } +numtoa = "0.2.4" [patch."https://github.com/LedgerHQ/ledger-nanos-ui"] nanos_ui = { path = "ledger-nanos-ui" } diff --git a/src/app_ui/address.rs b/src/app_ui/address.rs index 4447776..1534985 100644 --- a/src/app_ui/address.rs +++ b/src/app_ui/address.rs @@ -15,22 +15,34 @@ * limitations under the License. *****************************************************************************/ -use crate::utils; +use crate::utils::{concatenate, to_hex_all_caps}; +use crate::SW_DISPLAY_ADDRESS_FAIL; use core::str::from_utf8; -use nanos_sdk::io; +use nanos_sdk::io::Reply; use nanos_ui::bitmaps::{CROSSMARK, EYE, VALIDATE_14}; use nanos_ui::ui::{Field, MultiFieldReview}; -pub fn ui_display_pk(pk: &[u8]) -> Result { - // Todo add error handling - // ====================== - let hex = utils::to_hex(pk).unwrap(); - let m = from_utf8(&hex).unwrap(); - // ====================== +// Display only the last 20 bytes of the address +const DISPLAY_ADDR_BYTES_LEN: usize = 20; + +pub fn ui_display_pk(addr: &[u8]) -> Result { + let addr_hex_str_buf = + to_hex_all_caps(&addr[addr.len() - DISPLAY_ADDR_BYTES_LEN as usize..]) + .map_err(|_| Reply(SW_DISPLAY_ADDRESS_FAIL))?; + let addr_hex_str = from_utf8(&addr_hex_str_buf[..DISPLAY_ADDR_BYTES_LEN * 2]) + .map_err(|_| Reply(SW_DISPLAY_ADDRESS_FAIL))?; + + let mut addr_hex_str_with_prefix_buf = [0u8; DISPLAY_ADDR_BYTES_LEN * 2 + 2]; + concatenate( + &["0x", &addr_hex_str], + &mut addr_hex_str_with_prefix_buf, + ); + let addr_hex_str_with_prefix = + from_utf8(&addr_hex_str_with_prefix_buf).map_err(|_| Reply(SW_DISPLAY_ADDRESS_FAIL))?; let my_field = [Field { - name: "Public Key", - value: m[..pk.len() * 2].as_ref(), + name: "Address", + value: addr_hex_str_with_prefix, }]; let my_review = MultiFieldReview::new( diff --git a/src/app_ui/sign.rs b/src/app_ui/sign.rs new file mode 100644 index 0000000..3a703e3 --- /dev/null +++ b/src/app_ui/sign.rs @@ -0,0 +1,79 @@ +/***************************************************************************** + * Ledger App Boilerplate Rust. + * (c) 2023 Ledger SAS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ + +use crate::handlers::sign_tx::Tx; +use crate::utils::{concatenate, to_hex_all_caps}; +use crate::SW_TX_DISPLAY_FAIL; +use core::str::from_utf8; +use nanos_sdk::io::Reply; +use nanos_ui::bitmaps::{CROSSMARK, EYE, VALIDATE_14}; +use nanos_ui::ui::{Field, MultiFieldReview}; +use numtoa::NumToA; +// use nanos_sdk::testing; + +pub fn ui_display_tx(tx: &Tx) -> Result { + // Format amount value + let mut amount_buf = [0u8; 20]; + let mut amount_with_denom_buf = [0u8; 25]; + concatenate( + &["CRAB", " ", tx.value.numtoa_str(10, &mut amount_buf)], + &mut amount_with_denom_buf, + ); + let amount_str_with_denom = from_utf8(&amount_with_denom_buf) + .map_err(|_| Reply(SW_TX_DISPLAY_FAIL))? + .trim_matches(char::from(0)); + + // Format destination address + let hex_addr_buf = to_hex_all_caps(&tx.to).map_err(|_| Reply(SW_TX_DISPLAY_FAIL))?; + let hex_addr_str = from_utf8(&hex_addr_buf).map_err(|_| Reply(SW_TX_DISPLAY_FAIL))?; + let mut addr_with_prefix_buf = [0u8; 42]; + concatenate(&["0x", hex_addr_str], &mut addr_with_prefix_buf); + let hex_addr_str_with_prefix = + from_utf8(&addr_with_prefix_buf).map_err(|_| Reply(SW_TX_DISPLAY_FAIL))?; + + // Format memo + let memo_str = from_utf8(&tx.memo[..tx.memo_len]).map_err(|_| Reply(SW_TX_DISPLAY_FAIL))?; + + // Define transaction review fields + let my_fields = [ + Field { + name: "Amount", + value: amount_str_with_denom, + }, + Field { + name: "Destination", + value: hex_addr_str_with_prefix, + }, + Field { + name: "Memo", + value: memo_str, + }, + ]; + + // Create transaction review + let my_review = MultiFieldReview::new( + &my_fields, + &["Review ", "Transaction"], + Some(&EYE), + "Approve", + Some(&VALIDATE_14), + "Reject", + Some(&CROSSMARK), + ); + + Ok(my_review.show()) +} diff --git a/src/handlers/get_public_key.rs b/src/handlers/get_public_key.rs index 9216305..322b15a 100644 --- a/src/handlers/get_public_key.rs +++ b/src/handlers/get_public_key.rs @@ -16,15 +16,16 @@ *****************************************************************************/ use crate::app_ui::address::ui_display_pk; -use crate::SW_DENY; +use crate::utils::{read_bip32_path, MAX_ALLOWED_PATH_LEN}; +use crate::{SW_DENY, SW_DISPLAY_ADDRESS_FAIL}; +use nanos_sdk::bindings::{ + cx_hash_no_throw, cx_hash_t, cx_keccak_init_no_throw, cx_sha3_t, CX_LAST, CX_OK, +}; use nanos_sdk::ecc::{Secp256k1, SeedDerive}; -use nanos_sdk::{io, testing}; +use nanos_sdk::io::{Comm, Reply}; +use nanos_sdk::testing; -const MAX_ALLOWED_PATH_LEN: usize = 10; - -// const SW_DENY: u16 = 0x6985; - -pub fn handler_get_public_key(comm: &mut io::Comm, display: bool) -> Result<(), io::Reply> { +pub fn handler_get_public_key(comm: &mut Comm, display: bool) -> Result<(), Reply> { let mut path = [0u32; MAX_ALLOWED_PATH_LEN]; let data = comm.get_data()?; @@ -32,14 +33,37 @@ pub fn handler_get_public_key(comm: &mut io::Comm, display: bool) -> Result<(), let pk = Secp256k1::derive_from_path(&path[..path_len]) .public_key() - .map_err(|x| io::Reply(0x6eu16 | (x as u16 & 0xff)))?; + .map_err(|x| Reply(0x6eu16 | (x as u16 & 0xff)))?; - // Display public key on device if requested + // Display address on device if requested if display { + let mut keccak256: cx_sha3_t = Default::default(); + let mut address: [u8; 32] = [0u8; 32]; + + unsafe { + if cx_keccak_init_no_throw(&mut keccak256, 256) != CX_OK { + return Err(Reply(SW_DISPLAY_ADDRESS_FAIL)); + } + + let mut pk_mut = pk.pubkey; + let pk_ptr = pk_mut.as_mut_ptr().offset(1); + if cx_hash_no_throw( + &mut keccak256.header as *mut cx_hash_t, + CX_LAST, + pk_ptr, + 64 as u32, + address.as_mut_ptr(), + address.len() as u32, + ) != CX_OK + { + return Err(Reply(SW_DISPLAY_ADDRESS_FAIL)); + } + } + testing::debug_print("showing public key\n"); - if !ui_display_pk(&pk.pubkey)? { + if !ui_display_pk(&address)? { testing::debug_print("denied\n"); - return Err(io::Reply(SW_DENY)); + return Err(Reply(SW_DENY)); } } @@ -53,29 +77,3 @@ pub fn handler_get_public_key(comm: &mut io::Comm, display: bool) -> Result<(), Ok(()) } - -fn read_bip32_path(data: &[u8], path: &mut [u32]) -> Result { - // Check input length and path buffer capacity - if data.len() < 1 || path.len() < data.len() / 4 { - return Err(io::StatusWords::BadLen.into()); - } - - let path_len = data[0] as usize; // First byte is the length of the path - let path_data = &data[1..]; - - // Check path data length and alignment - if path_data.len() != path_len * 4 - || path_data.len() > MAX_ALLOWED_PATH_LEN * 4 - || path_data.len() % 4 != 0 - { - return Err(io::StatusWords::BadLen.into()); - } - - let mut idx = 0; - for (i, chunk) in path_data.chunks(4).enumerate() { - path[idx] = u32::from_be_bytes(chunk.try_into().unwrap()); - idx = i + 1; - } - - Ok(idx) -} diff --git a/src/handlers/sign_tx.rs b/src/handlers/sign_tx.rs new file mode 100644 index 0000000..146eff5 --- /dev/null +++ b/src/handlers/sign_tx.rs @@ -0,0 +1,202 @@ +/***************************************************************************** + * Ledger App Boilerplate Rust. + * (c) 2023 Ledger SAS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ +use crate::app_ui::sign::ui_display_tx; +use crate::utils::{read_bip32_path, varint_read, slice_or_err, MAX_ALLOWED_PATH_LEN, to_hex_all_caps}; +use crate::{SW_DENY, SW_TX_HASH_FAIL, SW_TX_PARSING_FAIL, SW_TX_SIGN_FAIL, SW_WRONG_TX_LENGTH}; +use nanos_sdk::bindings::{ + cx_hash_no_throw, cx_hash_t, cx_keccak_init_no_throw, cx_sha3_t, CX_LAST, CX_OK, +}; +use nanos_sdk::ecc::{Secp256k1, SeedDerive}; +use nanos_sdk::io::{Comm, Reply}; +use nanos_sdk::testing; + +const MAX_TRANSACTION_LEN: usize = 510; + +pub struct Tx { + nonce: u64, + pub value: u64, + pub to: [u8; 20], + pub memo: [u8; 255], + pub memo_len: usize, +} + +// Implement deserialize for Tx from a u8 array +impl TryFrom<&[u8]> for Tx { + type Error = (); + fn try_from(raw_tx: &[u8]) -> Result { + if raw_tx.len() > MAX_TRANSACTION_LEN { + return Err(()); + } + + // Try to parse the transaction fields : + // Nonce + let nonce = u64::from_be_bytes(slice_or_err(raw_tx, 0, 8)?.try_into().map_err(|_| ())?); + // Destination address + let to = slice_or_err(raw_tx, 8, 20)?.try_into().map_err(|_| ())?; + // Amount value + let value = u64::from_be_bytes(slice_or_err(raw_tx, 28, 8)?.try_into().map_err(|_| ())?); + // Memo length + // Memo will be trimmed to 255 bytes if it is longer + let (memo_len_u64, memo_len_size) = varint_read(&raw_tx[36..])?; + let memo_len = if memo_len_u64 < 255 { + memo_len_u64 as usize + } else { + 255 as usize + }; + + // Memo + let memo_slice = slice_or_err(raw_tx, 36 + memo_len_size, memo_len)?; + let mut memo = [0u8; 255]; + + memo[..memo_len].copy_from_slice(memo_slice); + + // Check memo ASCII encoding + if !memo[..memo_len].iter().all(|&byte| byte.is_ascii()) { + return Err(()); + } + + Ok(Tx { + nonce, + value, + to, + memo, + memo_len, + }) + } +} + +// #[derive(Copy, Clone)] +pub struct TxContext { + raw_tx: [u8; MAX_TRANSACTION_LEN], // raw transaction serialized + raw_tx_len: usize, // length of raw transaction + path: [u32; MAX_ALLOWED_PATH_LEN], // BIP32 path for key derivation + path_len: usize, // length of BIP32 path +} + +// Implement constructor for TxInfo with default values +impl TxContext { + pub fn new() -> TxContext { + TxContext { + raw_tx: [0u8; MAX_TRANSACTION_LEN], + raw_tx_len: 0, + path: [0u32; MAX_ALLOWED_PATH_LEN], + path_len: 0, + } + } + // Implement reset for TxInfo + fn reset(&mut self) { + self.raw_tx = [0u8; MAX_TRANSACTION_LEN]; + self.raw_tx_len = 0; + self.path = [0u32; MAX_ALLOWED_PATH_LEN]; + self.path_len = 0; + } +} + +pub fn handler_sign_tx( + comm: &mut Comm, + chunk: u8, + more: bool, + ctx: &mut TxContext, +) -> Result<(), Reply> { + // Try to get data from comm. If there is no data, + // the '?' operator will propagate the error. + let data = comm.get_data()?; + // First chunk, try to parse the path + if chunk == 0 { + // Reset transaction context + ctx.reset(); + // This will propagate the error if the path is invalid + ctx.path_len = read_bip32_path(data, &mut ctx.path)?; + // Next chunks, append data to raw_tx and return or parse + // the transaction if it is the last chunk. + } else { + if ctx.raw_tx_len + data.len() > MAX_TRANSACTION_LEN { + return Err(Reply(SW_WRONG_TX_LENGTH)); + } + + // Append data to raw_tx + ctx.raw_tx[ctx.raw_tx_len..ctx.raw_tx_len + data.len()].copy_from_slice(data); + ctx.raw_tx_len += data.len(); + + // If we expect more chunks, return + if more { + return Ok(()); + // Otherwise, try to parse the transaction + } else { + testing::debug_print("Last chunk : parse transaction\n"); + let tx = match Tx::try_from(&ctx.raw_tx[..ctx.raw_tx_len]) { + Ok(tx) => tx, + Err(_) => return Err(Reply(SW_TX_PARSING_FAIL)), + }; + testing::debug_print("Transaction parsed\n"); + // Display transaction. If user approves + // the transaction, sign it. Otherwise, + // return an error. + if ui_display_tx(&tx)? { + return compute_signature_and_append(comm, ctx); + } else { + return Err(Reply(SW_DENY)); + } + } + } + Ok(()) +} + +fn compute_signature_and_append(comm: &mut Comm, ctx: &mut TxContext) -> Result<(), Reply> { + let mut keccak256: cx_sha3_t = Default::default(); + let mut message_hash: [u8; 32] = [0u8; 32]; + + testing::debug_print("Signature is appended 1\n"); + unsafe { + let res = cx_keccak_init_no_throw(&mut keccak256, 256); + if res != CX_OK { + // Print error + let err_buf = to_hex_all_caps(&res.to_be_bytes()).unwrap(); + let err_str = core::str::from_utf8(&err_buf).unwrap(); + testing::debug_print("Hashing err : "); + testing::debug_print(err_str); + testing::debug_print("\n"); + + return Err(Reply(SW_TX_HASH_FAIL)); + } + testing::debug_print("Signature is appended 1.1\n"); + if cx_hash_no_throw( + &mut keccak256.header as *mut cx_hash_t, + CX_LAST, + ctx.raw_tx.as_ptr(), + ctx.raw_tx_len as u32, + message_hash.as_mut_ptr(), + message_hash.len() as u32, + ) != CX_OK + { + testing::debug_print("Hashing failed 2\n"); + return Err(Reply(SW_TX_HASH_FAIL)); + } + testing::debug_print("Signature is appended 1.2\n"); + } + + let (sig, siglen, parity) = Secp256k1::derive_from_path(&ctx.path[..ctx.path_len]) + .deterministic_sign(&message_hash) + .map_err(|_| Reply(SW_TX_SIGN_FAIL))?; + comm.append(&[siglen as u8]); + comm.append(&sig[..siglen as usize]); + comm.append(&[parity as u8]); + + testing::debug_print("Signature is appended 2\n"); + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 7060d53..f61a22b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,65 +5,37 @@ mod utils; mod app_ui { pub mod address; pub mod menu; + pub mod sign; } mod handlers { pub mod get_public_key; pub mod get_version; + pub mod sign_tx; } -use core::str::from_utf8; use nanos_sdk::buttons::ButtonEvent; -use nanos_sdk::ecc::{Secp256k1, SeedDerive}; use nanos_sdk::io; -use nanos_sdk::io::SyscallError; use nanos_ui::ui; -use nanos_ui::bitmaps::{CROSSMARK, EYE, VALIDATE_14}; - use app_ui::menu::ui_menu_main; -use handlers::{get_public_key::handler_get_public_key, get_version::handler_get_version}; +use handlers::{ + get_public_key::handler_get_public_key, + get_version::handler_get_version, + sign_tx::{handler_sign_tx, TxContext}, +}; nanos_sdk::set_panic!(nanos_sdk::exiting_panic); -pub const BIP32_PATH: [u32; 5] = nanos_sdk::ecc::make_bip32_path(b"m/44'/535348'/0'/0/0"); - pub const SW_INS_NOT_SUPPORTED: u16 = 0x6D00; pub const SW_DENY: u16 = 0x6985; pub const SW_WRONG_P1P2: u16 = 0x6A86; pub const SW_WRONG_DATA_LENGTH: u16 = 0x6A87; - -/// This is the UI flow for signing, composed of a scroller -/// to read the incoming message, a panel that requests user -/// validation, and an exit message. -fn sign_ui(message: &[u8]) -> Result, SyscallError> { - let hex = utils::to_hex(message).map_err(|_| SyscallError::Overflow)?; - let m = from_utf8(&hex).map_err(|_| SyscallError::InvalidParameter)?; - let my_field = [ui::Field { - name: "Data", - value: m, - }]; - - let my_review = ui::MultiFieldReview::new( - &my_field, - &["Review ", "Transaction"], - Some(&EYE), - "Approve", - Some(&VALIDATE_14), - "Reject", - Some(&CROSSMARK), - ); - - if my_review.show() { - let signature = Secp256k1::derive_from_path(&BIP32_PATH) - .deterministic_sign(message) - .map_err(|_| SyscallError::Unspecified)?; - ui::popup("Done !"); - Ok(Some(signature)) - } else { - ui::popup("Cancelled"); - Ok(None) - } -} +pub const SW_TX_DISPLAY_FAIL: u16 = 0xB001; +pub const SW_DISPLAY_ADDRESS_FAIL: u16 = 0xB002; +pub const SW_WRONG_TX_LENGTH: u16 = 0xB004; +pub const SW_TX_PARSING_FAIL: u16 = 0xB005; +pub const SW_TX_HASH_FAIL: u16 = 0xB006; +pub const SW_TX_SIGN_FAIL: u16 = 0xB008; #[no_mangle] extern "C" fn sample_pending() { @@ -88,12 +60,13 @@ extern "C" fn sample_pending() { #[no_mangle] extern "C" fn sample_main() { let mut comm = io::Comm::new(); + let mut tx_ctx = TxContext::new(); loop { // Wait for either a specific button push to exit the app // or an APDU command match ui_menu_main(&mut comm) { - io::Event::Command(ins) => match handle_apdu(&mut comm, ins.into()) { + io::Event::Command(ins) => match handle_apdu(&mut comm, ins.into(), &mut tx_ctx) { Ok(()) => comm.reply_ok(), Err(sw) => comm.reply(sw), }, @@ -104,6 +77,7 @@ extern "C" fn sample_main() { #[repr(u8)] +// Instruction set for the app. enum Ins { GetVersion, GetAppName, @@ -111,8 +85,16 @@ enum Ins { SignTx, UnknownIns, } - +// CLA (APDU class byte) for all APDUs. const CLA: u8 = 0xe0; +// P2 for last APDU to receive. +const P2_SIGN_TX_LAST: u8 = 0x00; +// P2 for more APDU to receive. +const P2_SIGN_TX_MORE: u8 = 0x80; +// P1 for first APDU number. +const P1_SIGN_TX_START: u8 = 0x00; +// P1 for maximum APDU number. +const P1_SIGN_TX_MAX: u8 = 0x03; impl From for Ins { fn from(header: io::ApduHeader) -> Ins { @@ -128,7 +110,7 @@ impl From for Ins { use nanos_sdk::io::Reply; -fn handle_apdu(comm: &mut io::Comm, ins: Ins) -> Result<(), Reply> { +fn handle_apdu(comm: &mut io::Comm, ins: Ins, ctx: &mut TxContext) -> Result<(), Reply> { if comm.rx == 0 { return Err(io::StatusWords::NothingReceived.into()); } @@ -164,10 +146,23 @@ fn handle_apdu(comm: &mut io::Comm, ins: Ins) -> Result<(), Reply> { return handler_get_public_key(comm, apdu_metadata.p1 == 1); } Ins::SignTx => { - let out = sign_ui(comm.get_data()?)?; - if let Some((signature_buf, length, _)) = out { - comm.append(&signature_buf[..length as usize]) + if (apdu_metadata.p1 == P1_SIGN_TX_START && apdu_metadata.p2 != P2_SIGN_TX_MORE) + || apdu_metadata.p1 > P1_SIGN_TX_MAX + || (apdu_metadata.p2 != P2_SIGN_TX_LAST && apdu_metadata.p2 != P2_SIGN_TX_MORE) + { + return Err(io::Reply(SW_WRONG_P1P2)); + } + + if (comm.get_data()?.len()) == 0 { + return Err(io::Reply(SW_WRONG_DATA_LENGTH)); } + + return handler_sign_tx( + comm, + apdu_metadata.p1, + apdu_metadata.p2 == P2_SIGN_TX_MORE, + ctx, + ); } Ins::UnknownIns => { return Err(io::Reply(SW_INS_NOT_SUPPORTED)); diff --git a/src/utils.rs b/src/utils.rs index 9c0acfc..ee44916 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,21 +1,132 @@ +use crate::SW_WRONG_DATA_LENGTH; use core::char; +use nanos_sdk::io::Reply; use nanos_sdk::testing; +pub const MAX_ALLOWED_PATH_LEN: usize = 10; +const MAX_HEX_LEN: usize = 64; + /// Convert to hex. Returns a static buffer of 64 bytes #[inline] -pub fn to_hex(m: &[u8]) -> Result<[u8; 255], ()> { - if 2 * m.len() > 255 { +pub fn to_hex(m: &[u8]) -> Result<[u8; MAX_HEX_LEN], ()> { + if 2 * m.len() > MAX_HEX_LEN { testing::debug_print("to_hex: buffer too small\n"); return Err(()); } - let mut hex = [0u8; 255]; + let mut hex = [0u8; MAX_HEX_LEN]; let mut i = 0; for c in m { let c0 = char::from_digit((c >> 4).into(), 16).unwrap(); let c1 = char::from_digit((c & 0xf).into(), 16).unwrap(); + hex[i] = c0 as u8; hex[i + 1] = c1 as u8; i += 2; } Ok(hex) } + +/// Convert to an all capitalized string. Returns a static buffer of 255 bytes +#[inline] +pub fn to_hex_all_caps(m: &[u8]) -> Result<[u8; MAX_HEX_LEN], ()> { + match to_hex(m) { + Ok(hex) => { + let mut hex_all_caps = hex; + hex_all_caps + .iter_mut() + .for_each(|x| *x = x.to_ascii_uppercase()); + Ok(hex_all_caps) + } + Err(_) => Err(()), + } +} + +/// Convert serialized derivation path to u32 array elements +pub fn read_bip32_path(data: &[u8], path: &mut [u32]) -> Result { + // Check input length and path buffer capacity + if data.len() < 1 || path.len() < data.len() / 4 { + return Err(Reply(SW_WRONG_DATA_LENGTH)); + } + + let path_len = data[0] as usize; // First byte is the length of the path + let path_data = &data[1..]; + + // Check path data length and alignment + if path_data.len() != path_len * 4 + || path_data.len() > MAX_ALLOWED_PATH_LEN * 4 + || path_data.len() % 4 != 0 + { + return Err(Reply(SW_WRONG_DATA_LENGTH)); + } + + let mut idx = 0; + for (i, chunk) in path_data.chunks(4).enumerate() { + path[idx] = u32::from_be_bytes(chunk.try_into().unwrap()); + idx = i + 1; + } + + Ok(idx) +} + +/// Concatenate multiple strings into a fixed-size array +pub 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; + } + } +} + +/// Get a subslice of a slice or return an error +#[inline] +pub fn slice_or_err(slice: &[u8], start: usize, len: usize) -> Result<&[u8], ()> { + match slice.get(start..start + len) { + Some(s) => Ok(s), + None => Err(()), + } +} + +/// Read a varint from a slice +pub fn varint_read(input: &[u8]) -> Result<(u64, usize), ()> { + let mut bytes = [0u8; 8]; + let int_length: usize; + + if input.is_empty() { + return Err(()); + } + + let prefix = input[0]; + + if prefix == 0xFD { + if input.len() < 3 { + return Err(()); + } + int_length = 2; + } else if prefix == 0xFE { + if input.len() < 5 { + return Err(()); + } + int_length = 4; + } else if prefix == 0xFF { + if input.len() < 9 { + return Err(()); + } + int_length = 8; + } else { + return Ok((u64::from(prefix), 1)); + } + + let buf = slice_or_err(input, 1, int_length)?; + bytes[..int_length].copy_from_slice(buf); + let result = u64::from_le_bytes(bytes); + Ok((result, int_length + 1)) +} \ No newline at end of file From 0cab934473863f1758191222306582ee9429c2cc Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Wed, 15 Nov 2023 13:28:12 +0100 Subject: [PATCH 08/15] Stack usage optim : use slices, not arrays in Tx struct. Code cleanup. --- Cargo.lock | 380 +++++++++++++++++- src/app_ui/sign.rs | 2 +- src/handlers/sign_tx.rs | 52 +-- .../00001.png | Bin 517 -> 496 bytes .../00002.png | Bin 555 -> 507 bytes .../00003.png | Bin 530 -> 447 bytes .../00004.png | Bin 548 -> 341 bytes .../00005.png | Bin 535 -> 455 bytes .../00006.png | Bin 535 -> 0 bytes .../00007.png | Bin 500 -> 0 bytes .../00008.png | Bin 413 -> 0 bytes .../00001.png | Bin 517 -> 496 bytes .../00002.png | Bin 555 -> 507 bytes .../00003.png | Bin 530 -> 447 bytes .../00004.png | Bin 548 -> 341 bytes .../00005.png | Bin 535 -> 340 bytes .../00006.png | Bin 535 -> 455 bytes .../00007.png | Bin 500 -> 0 bytes .../00008.png | Bin 413 -> 0 bytes .../nanos/test_sign_tx_long_tx/00000.png | Bin 0 -> 358 bytes .../nanos/test_sign_tx_long_tx/00001.png | Bin 0 -> 355 bytes .../nanos/test_sign_tx_long_tx/00002.png | Bin 0 -> 525 bytes .../nanos/test_sign_tx_long_tx/00003.png | Bin 0 -> 536 bytes .../nanos/test_sign_tx_long_tx/00004.png | Bin 0 -> 456 bytes .../nanos/test_sign_tx_long_tx/00005.png | Bin 0 -> 436 bytes .../nanos/test_sign_tx_long_tx/00006.png | Bin 0 -> 435 bytes .../nanos/test_sign_tx_long_tx/00007.png | Bin 0 -> 436 bytes .../nanos/test_sign_tx_long_tx/00008.png | Bin 0 -> 447 bytes .../nanos/test_sign_tx_long_tx/00009.png | Bin 0 -> 456 bytes .../nanos/test_sign_tx_long_tx/00010.png | Bin 0 -> 436 bytes .../nanos/test_sign_tx_long_tx/00011.png | Bin 0 -> 454 bytes .../nanos/test_sign_tx_long_tx/00012.png | Bin 0 -> 448 bytes .../nanos/test_sign_tx_long_tx/00013.png | Bin 0 -> 476 bytes .../nanos/test_sign_tx_long_tx/00014.png | Bin 0 -> 455 bytes .../nanos/test_sign_tx_long_tx/00015.png | Bin 0 -> 465 bytes .../nanos/test_sign_tx_long_tx/00016.png | Bin 0 -> 462 bytes .../nanos/test_sign_tx_long_tx/00017.png | Bin 0 -> 456 bytes .../nanos/test_sign_tx_long_tx/00018.png | Bin 0 -> 455 bytes .../nanos/test_sign_tx_long_tx/00019.png | Bin 0 -> 478 bytes .../nanos/test_sign_tx_long_tx/00020.png | Bin 0 -> 464 bytes .../nanos/test_sign_tx_long_tx/00021.png | Bin 0 -> 479 bytes .../nanos/test_sign_tx_long_tx/00022.png | Bin 0 -> 464 bytes .../nanos/test_sign_tx_long_tx/00023.png | Bin 0 -> 462 bytes .../nanos/test_sign_tx_long_tx/00024.png | Bin 0 -> 468 bytes .../nanos/test_sign_tx_long_tx/00025.png | Bin 0 -> 447 bytes .../nanos/test_sign_tx_long_tx/00026.png | Bin 0 -> 452 bytes .../nanos/test_sign_tx_long_tx/00027.png | Bin 0 -> 429 bytes .../00028.png} | Bin .../00029.png} | Bin .../nanos/test_sign_tx_refused/00000.png | Bin 0 -> 358 bytes .../nanos/test_sign_tx_refused/00001.png | Bin 0 -> 355 bytes .../nanos/test_sign_tx_refused/00002.png | Bin 0 -> 525 bytes .../nanos/test_sign_tx_refused/00003.png | Bin 0 -> 536 bytes .../nanos/test_sign_tx_refused/00004.png | Bin 0 -> 456 bytes .../nanos/test_sign_tx_refused/00005.png | Bin 0 -> 437 bytes .../nanos/test_sign_tx_refused/00006.png | Bin 0 -> 445 bytes .../nanos/test_sign_tx_refused/00007.png | Bin 0 -> 419 bytes .../00008.png} | Bin .../00009.png} | Bin .../00010.png} | Bin .../nanos/test_sign_tx_short_tx/00000.png | Bin 0 -> 358 bytes .../nanos/test_sign_tx_short_tx/00001.png | Bin 0 -> 355 bytes .../nanos/test_sign_tx_short_tx/00002.png | Bin 0 -> 525 bytes .../nanos/test_sign_tx_short_tx/00003.png | Bin 0 -> 536 bytes .../nanos/test_sign_tx_short_tx/00004.png | Bin 0 -> 456 bytes .../nanos/test_sign_tx_short_tx/00005.png | Bin 0 -> 353 bytes .../nanos/test_sign_tx_short_tx/00006.png | Bin 0 -> 341 bytes .../nanos/test_sign_tx_short_tx/00007.png | Bin 0 -> 455 bytes tests/test_sign_cmd.py | 283 ++++++------- 69 files changed, 529 insertions(+), 188 deletions(-) delete mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_accepted/00006.png delete mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_accepted/00007.png delete mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_accepted/00008.png delete mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_refused/00007.png delete mode 100644 tests/snapshots/nanos/test_get_public_key_confirm_refused/00008.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00000.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00001.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00002.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00003.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00004.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00005.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00006.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00007.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00008.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00009.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00010.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00011.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00012.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00013.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00014.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00015.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00016.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00017.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00018.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00019.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00020.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00021.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00022.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00023.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00024.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00025.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00026.png create mode 100644 tests/snapshots/nanos/test_sign_tx_long_tx/00027.png rename tests/snapshots/nanos/{test_get_public_key_confirm_accepted/00009.png => test_sign_tx_long_tx/00028.png} (100%) rename tests/snapshots/nanos/{test_get_public_key_confirm_accepted/00010.png => test_sign_tx_long_tx/00029.png} (100%) create mode 100644 tests/snapshots/nanos/test_sign_tx_refused/00000.png create mode 100644 tests/snapshots/nanos/test_sign_tx_refused/00001.png create mode 100644 tests/snapshots/nanos/test_sign_tx_refused/00002.png create mode 100644 tests/snapshots/nanos/test_sign_tx_refused/00003.png create mode 100644 tests/snapshots/nanos/test_sign_tx_refused/00004.png create mode 100644 tests/snapshots/nanos/test_sign_tx_refused/00005.png create mode 100644 tests/snapshots/nanos/test_sign_tx_refused/00006.png create mode 100644 tests/snapshots/nanos/test_sign_tx_refused/00007.png rename tests/snapshots/nanos/{test_get_public_key_confirm_refused/00009.png => test_sign_tx_refused/00008.png} (100%) rename tests/snapshots/nanos/{test_get_public_key_confirm_refused/00010.png => test_sign_tx_refused/00009.png} (100%) rename tests/snapshots/nanos/{test_get_public_key_confirm_refused/00011.png => test_sign_tx_refused/00010.png} (100%) create mode 100644 tests/snapshots/nanos/test_sign_tx_short_tx/00000.png create mode 100644 tests/snapshots/nanos/test_sign_tx_short_tx/00001.png create mode 100644 tests/snapshots/nanos/test_sign_tx_short_tx/00002.png create mode 100644 tests/snapshots/nanos/test_sign_tx_short_tx/00003.png create mode 100644 tests/snapshots/nanos/test_sign_tx_short_tx/00004.png create mode 100644 tests/snapshots/nanos/test_sign_tx_short_tx/00005.png create mode 100644 tests/snapshots/nanos/test_sign_tx_short_tx/00006.png create mode 100644 tests/snapshots/nanos/test_sign_tx_short_tx/00007.png diff --git a/Cargo.lock b/Cargo.lock index 1205c81..76d750e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,24 +2,120 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "app-boilerplate-rust" +version = "1.0.0" +dependencies = [ + "include_gif", + "nanos_sdk", + "nanos_ui", + "numtoa", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bindgen" +version = "0.65.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.20", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "errno" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "gif" version = "0.11.4" @@ -30,15 +126,91 @@ dependencies = [ "weezl", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + [[package]] name = "include_gif" version = "0.1.0" source = "git+https://github.com/LedgerHQ/sdk_include_gif#699d28c6157518c4493899e2eeaa8af08346e5e7" dependencies = [ "gif", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "ledger-sdk-sys" +version = "0.2.0" +source = "git+https://github.com/LedgerHQ/secure-sdk-rust#d9a9868320a0d27194d93c7b4c55f48f2300abdc" +dependencies = [ + "bindgen", + "cc", ] +[[package]] +name = "libc" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "nanos_sdk" version = "0.2.1" @@ -52,10 +224,21 @@ dependencies = [ [[package]] name = "nanos_ui" version = "0.2.0" -source = "git+https://github.com/LedgerHQ/ledger-nanos-ui.git#6977cef7770c8c52738defe898fed6e274047498" dependencies = [ "include_gif", + "ledger-sdk-sys", "nanos_sdk", + "numtoa", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", ] [[package]] @@ -67,6 +250,34 @@ dependencies = [ "autocfg", ] +[[package]] +name = "numtoa" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f" + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "prettyplease" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9825a04601d60621feed79c4e6b56d65db77cdca55cef43b46b0de1096d1c282" +dependencies = [ + "proc-macro2", + "syn 2.0.20", +] + [[package]] name = "proc-macro2" version = "1.0.60" @@ -92,13 +303,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] -name = "rust-app" -version = "0.2.1" +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ - "nanos_sdk", - "nanos_ui", + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", ] +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "shlex" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" + [[package]] name = "syn" version = "1.0.109" @@ -110,6 +367,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb8d4cebc40aa517dfb69618fa647a346562e67228e2236ae0042ee6ac14775" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "unicode-ident" version = "1.0.9" @@ -121,3 +389,103 @@ name = "weezl" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/src/app_ui/sign.rs b/src/app_ui/sign.rs index 3a703e3..758f623 100644 --- a/src/app_ui/sign.rs +++ b/src/app_ui/sign.rs @@ -46,7 +46,7 @@ pub fn ui_display_tx(tx: &Tx) -> Result { from_utf8(&addr_with_prefix_buf).map_err(|_| Reply(SW_TX_DISPLAY_FAIL))?; // Format memo - let memo_str = from_utf8(&tx.memo[..tx.memo_len]).map_err(|_| Reply(SW_TX_DISPLAY_FAIL))?; + let memo_str = from_utf8(&tx.memo[..tx.memo_len as usize]).map_err(|_| Reply(SW_TX_DISPLAY_FAIL))?; // Define transaction review fields let my_fields = [ diff --git a/src/handlers/sign_tx.rs b/src/handlers/sign_tx.rs index 146eff5..d9ff288 100644 --- a/src/handlers/sign_tx.rs +++ b/src/handlers/sign_tx.rs @@ -15,29 +15,28 @@ * limitations under the License. *****************************************************************************/ use crate::app_ui::sign::ui_display_tx; -use crate::utils::{read_bip32_path, varint_read, slice_or_err, MAX_ALLOWED_PATH_LEN, to_hex_all_caps}; +use crate::utils::{read_bip32_path, varint_read, slice_or_err, MAX_ALLOWED_PATH_LEN}; use crate::{SW_DENY, SW_TX_HASH_FAIL, SW_TX_PARSING_FAIL, SW_TX_SIGN_FAIL, SW_WRONG_TX_LENGTH}; use nanos_sdk::bindings::{ cx_hash_no_throw, cx_hash_t, cx_keccak_init_no_throw, cx_sha3_t, CX_LAST, CX_OK, }; use nanos_sdk::ecc::{Secp256k1, SeedDerive}; use nanos_sdk::io::{Comm, Reply}; -use nanos_sdk::testing; const MAX_TRANSACTION_LEN: usize = 510; -pub struct Tx { +pub struct Tx<'a> { nonce: u64, pub value: u64, - pub to: [u8; 20], - pub memo: [u8; 255], + pub to: &'a [u8], + pub memo: &'a [u8], pub memo_len: usize, } // Implement deserialize for Tx from a u8 array -impl TryFrom<&[u8]> for Tx { +impl<'a> TryFrom<&'a [u8]> for Tx<'a> { type Error = (); - fn try_from(raw_tx: &[u8]) -> Result { + fn try_from(raw_tx: &'a [u8]) -> Result { if raw_tx.len() > MAX_TRANSACTION_LEN { return Err(()); } @@ -46,23 +45,14 @@ impl TryFrom<&[u8]> for Tx { // Nonce let nonce = u64::from_be_bytes(slice_or_err(raw_tx, 0, 8)?.try_into().map_err(|_| ())?); // Destination address - let to = slice_or_err(raw_tx, 8, 20)?.try_into().map_err(|_| ())?; + let to = slice_or_err(raw_tx, 8, 20)?; // Amount value let value = u64::from_be_bytes(slice_or_err(raw_tx, 28, 8)?.try_into().map_err(|_| ())?); // Memo length - // Memo will be trimmed to 255 bytes if it is longer let (memo_len_u64, memo_len_size) = varint_read(&raw_tx[36..])?; - let memo_len = if memo_len_u64 < 255 { - memo_len_u64 as usize - } else { - 255 as usize - }; - + let memo_len = memo_len_u64 as usize; // Memo - let memo_slice = slice_or_err(raw_tx, 36 + memo_len_size, memo_len)?; - let mut memo = [0u8; 255]; - - memo[..memo_len].copy_from_slice(memo_slice); + let memo = slice_or_err(raw_tx, 36 + memo_len_size, memo_len)?; // Check memo ASCII encoding if !memo[..memo_len].iter().all(|&byte| byte.is_ascii()) { @@ -112,8 +102,7 @@ pub fn handler_sign_tx( more: bool, ctx: &mut TxContext, ) -> Result<(), Reply> { - // Try to get data from comm. If there is no data, - // the '?' operator will propagate the error. + // Try to get data from comm let data = comm.get_data()?; // First chunk, try to parse the path if chunk == 0 { @@ -137,15 +126,13 @@ pub fn handler_sign_tx( return Ok(()); // Otherwise, try to parse the transaction } else { - testing::debug_print("Last chunk : parse transaction\n"); let tx = match Tx::try_from(&ctx.raw_tx[..ctx.raw_tx_len]) { Ok(tx) => tx, Err(_) => return Err(Reply(SW_TX_PARSING_FAIL)), }; - testing::debug_print("Transaction parsed\n"); // Display transaction. If user approves // the transaction, sign it. Otherwise, - // return an error. + // return a "deny" status word. if ui_display_tx(&tx)? { return compute_signature_and_append(comm, ctx); } else { @@ -160,20 +147,10 @@ fn compute_signature_and_append(comm: &mut Comm, ctx: &mut TxContext) -> Result< let mut keccak256: cx_sha3_t = Default::default(); let mut message_hash: [u8; 32] = [0u8; 32]; - testing::debug_print("Signature is appended 1\n"); unsafe { - let res = cx_keccak_init_no_throw(&mut keccak256, 256); - if res != CX_OK { - // Print error - let err_buf = to_hex_all_caps(&res.to_be_bytes()).unwrap(); - let err_str = core::str::from_utf8(&err_buf).unwrap(); - testing::debug_print("Hashing err : "); - testing::debug_print(err_str); - testing::debug_print("\n"); - + if cx_keccak_init_no_throw(&mut keccak256, 256) != CX_OK { return Err(Reply(SW_TX_HASH_FAIL)); } - testing::debug_print("Signature is appended 1.1\n"); if cx_hash_no_throw( &mut keccak256.header as *mut cx_hash_t, CX_LAST, @@ -183,10 +160,8 @@ fn compute_signature_and_append(comm: &mut Comm, ctx: &mut TxContext) -> Result< message_hash.len() as u32, ) != CX_OK { - testing::debug_print("Hashing failed 2\n"); return Err(Reply(SW_TX_HASH_FAIL)); } - testing::debug_print("Signature is appended 1.2\n"); } let (sig, siglen, parity) = Secp256k1::derive_from_path(&ctx.path[..ctx.path_len]) @@ -195,8 +170,5 @@ fn compute_signature_and_append(comm: &mut Comm, ctx: &mut TxContext) -> Result< comm.append(&[siglen as u8]); comm.append(&sig[..siglen as usize]); comm.append(&[parity as u8]); - - testing::debug_print("Signature is appended 2\n"); - Ok(()) } diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00001.png b/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00001.png index dce1ab738131945fb8fab9d2430efceb1d35d823..2d939069ef8d6b7418263a31339cc281d6c93fa8 100644 GIT binary patch delta 470 zcmV;{0V)241n>ipB!9O_L_t(|ob8#>u7fZLhU>}p{tvthci}<^Q2dKU-5Fn&#V|@K zEvT%uq9}@@DE=MmtF5nrpB(*J{`k|8WV;XwIlld67;TNYn;KIb9cz>2`T5>HLNj44 zkLeb7FxOya!}9#dbs%ub5g$$=Cj)xNjR#cxs^g`Po_Q)s=zmDW6k5Stf*#f8?^9+5 z2#*8j4!6Jq+S^eFJ%py-axsmxdq7LLsZwdCO3mjoCATMG18C(u(|hL#Ib4noQhz`) zkGVry28hzhuf^F!YAY`YydsH8ttH45y$51pCs5gtxqgM?^>9{2{pmfpaCnlbB1rAQ zl~nYvq@sT%Du4RR*hsv=LY*8g5t_)Glpdwk%=a&6;E=?$Gc<4lNNdx4ng$hyF0=)_ z!RMG78Wl5T5q%N6TetaZIlUcy1TedMJy*X{X&umkV>Ktb0isziTtU3SS1SziR6TCwADueF|_& z^c%VXX2Aiy;YPHa*N|ofv?tNUVP*ef?z)Po?LLWut`TB!A3FL_t(|ob8#*ZUZq0MV+eo{}1k>SyZwJFJCZIPgTyUnF1zY zV}^{;G)>bq%@H13+TUOA#-0mFCO{vm0=}KO`cliWMs=kR>T*kCqMF}do1EMRcAz+?ZVt3+q zOVGs08JJGIcLMvXv?qI9mH@Rs1NS2>xJwn*DS}nVuO#xhbq7SVd;1evjgya(3nO9V zUH(uBx&u`eCVy(@FWgK;6gEZW+}5#wK}Y{29sQGt=&z=sPMeh&1R~R+<=8+SC`Cs} zpqd)ztZiOZ?;5O%HWQvlg{+&D+f}eh@Y%l<6M8U9AxQFt)FU?1n~q| zuej{pQa*_es2#0@tJ0q;tnY<6T=#px^7zvb;Nx6XOJsh`d6=^^?9|3h1rxxkqzonH zy2^umRVm{f+}hQhC<%fI*xZ99RIPha^hYd8b!{ke2``&G(!F~{ zemq`3v9Bw??ivwd0KKrxH7!$iiHP>srnwTZ0-8vg)nKeV;(weK6W#RfC2Pg)e6}66 zMRSd?70|eAocn7Ucyf*DZkqMIs`Z%CUy0cRh|M4`YF(~nn%fd3z_;7rJplZGF+4w5 zW)`mI!}?>_968OA7p>spio-QQJKi-yJfjyjVQ}(kc6-g7=+y#)^xkQ0dg4+Ky^5-; z*jrzlP#t9~q97b_v<;S)xUP&zZOv zur(EnsOJ*m2@JpaXnM3ZSey7HY1cN@3qOBOMQ11{=9}=Qi{eM^?uaqwF;4vA(Od$x zuOdUlyX(*gW3QneQ9|$0&DaVU0IkT*w$Z4ep3+AyVpO7AhHa;P{|DZS>_w&y%gG-xn{2~(l`()Q ziVn6`6h%=K#VLHHw718rvEP9t6QGT%fN!VwKGbrobzRGv+9MUCM&f)=YbvfJjm+7} z%s%^;vrN0_GEz*c!2C>Wnsi6yqIT9S6IR!nBQI4{gI2Vx_J738f~?p9IN)lfYb`YU zba~Qs>c~f(-yU4o+BX<00x;3Hqk>!OlJq09X8ld$q@V;VQqYn!Aljoke&&fKrgD6? zQF(6)>Nqh2!-?OWLfJ-!@L)s$RxJeeat0cw*>Mi2|%9v08;CHuNNFY7%*0mcNDq$lgMPt)taxM;b%!sf|;~BUg&BN?o90 zRIB|hH+A}u!UH%*h-{n9tTo z2|xhY@P@TOo~@q`-Lw2FVTm%MNJxEkEZlpBEj z>@S36C-xp9+dl+g2kh(vQy&^B)>|NLp{FHoClB~MAl>ta(3u-)Q4~c{6zA{+oHU5? USz=Ez01E&B07*qoM6N<$f*Rrg_y7O^ diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00003.png b/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00003.png index 30a64c8a3823888e7505ded812807c6bd6f76cc9..700668eb7e0b4e10ed5361e791c5fa344dec7e61 100644 GIT binary patch delta 421 zcmV;W0b2f&1iu52B!7lUL_t(|ob8#-5`-WKfN5v<{U5kPdvL~aJ+>m@m- z<1n4CxkU+b0$7;VTA34gNopkH@8*v~22?YtvO!sS@VXvAsBFbAWS{m`+o}(F__6qtI#4eoE$N51-aMlG*pAKm8|+duHCFUylSf4M@jkN@-Dce z1ya&HlI&KK_5E=>iqJ6lCHx4kJ&x9(sR_1q!~0**qR1)FooSN~l6O;y%hxF*7qVUk z^g+5${>`ukQv+c5mzC_1y4Bu7RU%3W*5sbjG)}Ub`5v!}AP9mW2yVb1wuUCnYHZY{ P00000NkvXXu0mjf7)Z!4 delta 504 zcmV@C(F%0-;9q6IBE14dU=|3CA@65EMm zTTln&ESS#t-D$4-4>^-SBw$s^sF$;#aoTNN6?RPwb|KkqSaSz=q0I5___qCq9R}nh zb6{lbn8M!|{eR{~XH^0gs9Z4m=W>-C?8&vd&~9Ar9Nr2M-taWkoz+;A!e(62*gX|b z3*6BV^U89-9iXZL5hMB0EJ8l?%b$R-Dx*#lEtSZ=jCc|^$aGbCU_Z(}_ zE>_PTFfHf;G8}GzA`t{;2T3GguKb%8AT_=&$4_BDz_B}k?b)x0V3Jq@)MmO+YvOiD uk8!KxTQ4Sooyf@vi~#qFq9}@@*o!9&&~HY?B8yP~00004uc>JMe8#E|AT#S4;a$4l#R@IsowLnAtUTG zToFYGA%qY@{$}4~-_Vm7Rt#sf<#$}=)VJ~BlvESR-Jq3=$jydfC?r!$iuAw9oGJ!U-M%Y zMJ+y&Ch%TCM>9}$(GT~2qZ!TIc>6skNTK;?O1Y^i^a^-9EC6{n9}GT=eBR!K!~g&Q M07*qoM6N<$g7>wSY5)KL delta 523 zcmV+m0`&dW0;B|xB!BBkL_t(|ob6f5ZUZq0+*a-Xe{c@zp^`;;4&dEHUFH-yFkpkR z%^t^KFc=I5gO}i=uKIlZW%>>znE)MB1$>@mJ=Aj89G85fR7~<@@LAU6el$sHJe}1K zN0^lr2^kDWH^Nt0Q^)dBW|Nb%Oju1cM_#I^goS7|Ru)8i2Y=uI=T8$YwfZzSX_7kf zbR=|daWZweAu?zOpkUZh!P%B1{joP;t!>m>afWsY+HwYDEvl1eo>*cMbfX;ILp6&F zOVENdJ#gmu-3gIl<&FxU9|;kFEjvq1B9NJu+E9hv5y2{i)DZcvbq1tP_`tpM5B*Ew z#AD-uk)<+)|9{!)AJNim48R7JyRH7qaf=wdEpAkW-VpaXhu;lidF^hfY~85M9On{7 zFQZl;k&a{=oFKpBZTRL&nUDhgmM!OHBkT>yWUO(E*0EUTS9_L-57I=~KrPLv_Y$@O zyX!T--d1v|VDAh!A;*z7cKfcC>;-|Ze=soN4pp~5&VMi2r2lF;vUsS&zijH)n&URL0!@Adj)@wZ>y^C&nrTvC z$X51UD}J5{K;11t&1<3$@U!K{DwO?D$`DFA*K~w`Fc=I5gTbZv0a!hh#C7AZU;qFB N07*qoLiRjLRg zN^3w3%%h33wa^I)yIpfCAAi4PIUE4ZO{b?Km?d}fT6HH2&km6 zxtYY2t?0?Vdg78W$~UYw9Gu>A)BWT-@}KCar_^|OW15hRAhQF2ZeR=QhlZ+`4$E`E zas@aBM%J9qx_?`Z@}YG&4+(>Ss6>vTo|8DdNh(RZPJp<3)s#@kFRJ-)IBvqU{IJh$I`BEW2o(ZqbW~Aj=iHXe`t5>R5M6Gbu7=jRaJU?# zsfH?7=n8p772u&LXYG3Td=fa%%;TKk?PR3-$H)hq9@NKv?0Taqe@qDagi X*_8FCW>a0$00000NkvXXu0mjf3c1oq delta 509 zcmVkyA%F*30iP+ z2Bs4~I~7*{R3tr!1nepq^?U}B(*-wlVJAg!6=Ew1^HL8;hqeb~vm3;A`w5$dlaKO- z(IheL-%tG`5`Sn*0`_=4Q2lH1lpNd>&sv4FnxEOS zN2JB#2}cBqmVk5%kIb}DKBW2r9r51rz{0Gk$~do#j*?dq=jWBIpcC0oN9m|iIc8R4 zDggDWZsRqE%a%!i`f+=}e&6;QpxHx^xo|IVYg1 zQ!cSiZI*pm-E10>odBjjovre14IPHswQx8~I>S`wWyEGAK$^-W@-4A2G7B9p$wT1}c7Q zU6Q`Y6s$a3M%yU2;*9hWwB!zmdQ^Wri)19W881A7Iv{7xbozTIXMdH9CxJ-7lB!WV zGpA*G)S4=+M|7S-Y9q@IcWGyI2ZVBLg!dQrYygmtbbS_|xTNLT&b+)|(tWiYa5b?$(PFdkX`Jiz2Uyw`@uR37!nHhryYRb~^AU%ot{ z0NBYMiXYsoX4)(-FW&)IA#Z{`c;+RnUU%|&Ar@=D&p?1yW;H!VJpG0G6t*aeq9}@e Z`2rO`lsc<-N}~V(002ovPDHLkV1i8R@h<=X diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00007.png b/tests/snapshots/nanos/test_get_public_key_confirm_accepted/00007.png deleted file mode 100644 index cfde67b4d8d5a664e05496ff1b161e39d1de0d91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 500 zcmVICO{ii z0h?#^zSJ_<7?mVcd!#ahB!sW9rZSWyNsK46`mFaE6>|}33}!zMzQdYRjDbq0Hhh)| ztG;H)OBI#SYpuk}f~eR5IKcT+UvsTKbx-Q2hP-$1?-$oqgBXko0|fduRB*N-Ngpyk zYi}AO1SL=rf=bQ+Z;$Hy*+^z$JK=Xj(12kx(42Vg6pQ|@!6YCIP~|deat4yqXuDKl zX(AYfW|@qd1;|lJN!Rff{rm@E*duvhgn6dI|8Mn=)#_9kpg`q@tA8!-VuL+#uPC$| zZg&oE1+l#TYA9Izv(r$#<^v9CM(Zlj##$upxqlDf7O}f%Bvsd;5thSUqsIP`Fup5f*1Mq#v z%Ed`G#%Z*ilw71~>n>!%MOtZ$GY+xy4#w;60~!fnX(&Ba>|^Jc2L#4L$<@nxLs5N2 q(nrY|NIL0&!HG8-jYgx<*bW~+h<0}@GM`xh00004%P)9b)2)g92paL(i9{cLcOOt;HBu#1jH;My^e^WYIY`Tm zx==#ywGU^5gg0;-dK#9SqTqSI7{XpTs51Mo`ZhEP>V0Y&_Dq2K8pHOhlAF6q9}@@C@$p-4n^@LW*~M$00000NkvXX Hu0mjfmP@>b diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_refused/00001.png b/tests/snapshots/nanos/test_get_public_key_confirm_refused/00001.png index dce1ab738131945fb8fab9d2430efceb1d35d823..2d939069ef8d6b7418263a31339cc281d6c93fa8 100644 GIT binary patch delta 470 zcmV;{0V)241n>ipB!9O_L_t(|ob8#>u7fZLhU>}p{tvthci}<^Q2dKU-5Fn&#V|@K zEvT%uq9}@@DE=MmtF5nrpB(*J{`k|8WV;XwIlld67;TNYn;KIb9cz>2`T5>HLNj44 zkLeb7FxOya!}9#dbs%ub5g$$=Cj)xNjR#cxs^g`Po_Q)s=zmDW6k5Stf*#f8?^9+5 z2#*8j4!6Jq+S^eFJ%py-axsmxdq7LLsZwdCO3mjoCATMG18C(u(|hL#Ib4noQhz`) zkGVry28hzhuf^F!YAY`YydsH8ttH45y$51pCs5gtxqgM?^>9{2{pmfpaCnlbB1rAQ zl~nYvq@sT%Du4RR*hsv=LY*8g5t_)Glpdwk%=a&6;E=?$Gc<4lNNdx4ng$hyF0=)_ z!RMG78Wl5T5q%N6TetaZIlUcy1TedMJy*X{X&umkV>Ktb0isziTtU3SS1SziR6TCwADueF|_& z^c%VXX2Aiy;YPHa*N|ofv?tNUVP*ef?z)Po?LLWut`TB!A3FL_t(|ob8#*ZUZq0MV+eo{}1k>SyZwJFJCZIPgTyUnF1zY zV}^{;G)>bq%@H13+TUOA#-0mFCO{vm0=}KO`cliWMs=kR>T*kCqMF}do1EMRcAz+?ZVt3+q zOVGs08JJGIcLMvXv?qI9mH@Rs1NS2>xJwn*DS}nVuO#xhbq7SVd;1evjgya(3nO9V zUH(uBx&u`eCVy(@FWgK;6gEZW+}5#wK}Y{29sQGt=&z=sPMeh&1R~R+<=8+SC`Cs} zpqd)ztZiOZ?;5O%HWQvlg{+&D+f}eh@Y%l<6M8U9AxQFt)FU?1n~q| zuej{pQa*_es2#0@tJ0q;tnY<6T=#px^7zvb;Nx6XOJsh`d6=^^?9|3h1rxxkqzonH zy2^umRVm{f+}hQhC<%fI*xZ99RIPha^hYd8b!{ke2``&G(!F~{ zemq`3v9Bw??ivwd0KKrxH7!$iiHP>srnwTZ0-8vg)nKeV;(weK6W#RfC2Pg)e6}66 zMRSd?70|eAocn7Ucyf*DZkqMIs`Z%CUy0cRh|M4`YF(~nn%fd3z_;7rJplZGF+4w5 zW)`mI!}?>_968OA7p>spio-QQJKi-yJfjyjVQ}(kc6-g7=+y#)^xkQ0dg4+Ky^5-; z*jrzlP#t9~q97b_v<;S)xUP&zZOv zur(EnsOJ*m2@JpaXnM3ZSey7HY1cN@3qOBOMQ11{=9}=Qi{eM^?uaqwF;4vA(Od$x zuOdUlyX(*gW3QneQ9|$0&DaVU0IkT*w$Z4ep3+AyVpO7AhHa;P{|DZS>_w&y%gG-xn{2~(l`()Q ziVn6`6h%=K#VLHHw718rvEP9t6QGT%fN!VwKGbrobzRGv+9MUCM&f)=YbvfJjm+7} z%s%^;vrN0_GEz*c!2C>Wnsi6yqIT9S6IR!nBQI4{gI2Vx_J738f~?p9IN)lfYb`YU zba~Qs>c~f(-yU4o+BX<00x;3Hqk>!OlJq09X8ld$q@V;VQqYn!Aljoke&&fKrgD6? zQF(6)>Nqh2!-?OWLfJ-!@L)s$RxJeeat0cw*>Mi2|%9v08;CHuNNFY7%*0mcNDq$lgMPt)taxM;b%!sf|;~BUg&BN?o90 zRIB|hH+A}u!UH%*h-{n9tTo z2|xhY@P@TOo~@q`-Lw2FVTm%MNJxEkEZlpBEj z>@S36C-xp9+dl+g2kh(vQy&^B)>|NLp{FHoClB~MAl>ta(3u-)Q4~c{6zA{+oHU5? USz=Ez01E&B07*qoM6N<$f*Rrg_y7O^ diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_refused/00003.png b/tests/snapshots/nanos/test_get_public_key_confirm_refused/00003.png index 30a64c8a3823888e7505ded812807c6bd6f76cc9..700668eb7e0b4e10ed5361e791c5fa344dec7e61 100644 GIT binary patch delta 421 zcmV;W0b2f&1iu52B!7lUL_t(|ob8#-5`-WKfN5v<{U5kPdvL~aJ+>m@m- z<1n4CxkU+b0$7;VTA34gNopkH@8*v~22?YtvO!sS@VXvAsBFbAWS{m`+o}(F__6qtI#4eoE$N51-aMlG*pAKm8|+duHCFUylSf4M@jkN@-Dce z1ya&HlI&KK_5E=>iqJ6lCHx4kJ&x9(sR_1q!~0**qR1)FooSN~l6O;y%hxF*7qVUk z^g+5${>`ukQv+c5mzC_1y4Bu7RU%3W*5sbjG)}Ub`5v!}AP9mW2yVb1wuUCnYHZY{ P00000NkvXXu0mjf7)Z!4 delta 504 zcmV@C(F%0-;9q6IBE14dU=|3CA@65EMm zTTln&ESS#t-D$4-4>^-SBw$s^sF$;#aoTNN6?RPwb|KkqSaSz=q0I5___qCq9R}nh zb6{lbn8M!|{eR{~XH^0gs9Z4m=W>-C?8&vd&~9Ar9Nr2M-taWkoz+;A!e(62*gX|b z3*6BV^U89-9iXZL5hMB0EJ8l?%b$R-Dx*#lEtSZ=jCc|^$aGbCU_Z(}_ zE>_PTFfHf;G8}GzA`t{;2T3GguKb%8AT_=&$4_BDz_B}k?b)x0V3Jq@)MmO+YvOiD uk8!KxTQ4Sooyf@vi~#qFq9}@@*o!9&&~HY?B8yP~00004uc>JMe8#E|AT#S4;a$4l#R@IsowLnAtUTG zToFYGA%qY@{$}4~-_Vm7Rt#sf<#$}=)VJ~BlvESR-Jq3=$jydfC?r!$iuAw9oGJ!U-M%Y zMJ+y&Ch%TCM>9}$(GT~2qZ!TIc>6skNTK;?O1Y^i^a^-9EC6{n9}GT=eBR!K!~g&Q M07*qoM6N<$g7>wSY5)KL delta 523 zcmV+m0`&dW0;B|xB!BBkL_t(|ob6f5ZUZq0+*a-Xe{c@zp^`;;4&dEHUFH-yFkpkR z%^t^KFc=I5gO}i=uKIlZW%>>znE)MB1$>@mJ=Aj89G85fR7~<@@LAU6el$sHJe}1K zN0^lr2^kDWH^Nt0Q^)dBW|Nb%Oju1cM_#I^goS7|Ru)8i2Y=uI=T8$YwfZzSX_7kf zbR=|daWZweAu?zOpkUZh!P%B1{joP;t!>m>afWsY+HwYDEvl1eo>*cMbfX;ILp6&F zOVENdJ#gmu-3gIl<&FxU9|;kFEjvq1B9NJu+E9hv5y2{i)DZcvbq1tP_`tpM5B*Ew z#AD-uk)<+)|9{!)AJNim48R7JyRH7qaf=wdEpAkW-VpaXhu;lidF^hfY~85M9On{7 zFQZl;k&a{=oFKpBZTRL&nUDhgmM!OHBkT>yWUO(E*0EUTS9_L-57I=~KrPLv_Y$@O zyX!T--d1v|VDAh!A;*z7cKfcC>;-|Ze=soN4pp~5&VMi2r2lF;vUsS&zijH)n&URL0!@Adj)@wZ>y^C&nrTvC z$X51UD}J5{K;11t&1<3$@U!K{DwO?D$`DFA*K~w`Fc=I5gTbZv0a!hh#C7AZU;qFB N07*qoL0?~E_&igM#?#L~IOHR(-<@sYotiy&C*Y&G zII0yeh-p3zW9cv$Q0k>6Y_)5~SfP=mSMUtz)%Ex|-o}7!H9hbQ(8{%C?kQVa?C`*U zj-J(h>P7(Y#xl^nkBhIt`kQe1WT9~g7`&Ns0r)K_0Og4C5BBp0_v3}4{b+)F00000 LNkvXXu0mjf8uONm delta 509 zcmVkyA%F*30iP+ z2Bs4~I~7*{R3tr!1nepq^?U}B(*-wlVJAg!6=Ew1^HL8;hqeb~vm3;A`w5$dlaKO- z(IheL-%tG`5`Sn*0`_=4Q2lH1lpNd>&sv4FnxEOS zN2JB#2}cBqmVk5%kIb}DKBW2r9r51rz{0Gk$~do#j*?dq=jWBIpcC0oN9m|iIc8R4 zDggDWZsRqE%a%!i`f+=}e&6;QpxHx^xo|IVYg1 zQ!cSiZI*pm-E10>odBjjovre14IPHswQx8~I>S`wWyEGAK$^-W@-4AiRjLRg zN^3w3%%h33wa^I)yIpfCAAi4PIUE4ZO{b?Km?d}fT6HH2&km6 zxtYY2t?0?Vdg78W$~UYw9Gu>A)BWT-@}KCar_^|OW15hRAhQF2ZeR=QhlZ+`4$E`E zas@aBM%J9qx_?`Z@}YG&4+(>Ss6>vTo|8DdNh(RZPJp<3)s#@kFRJ-)IBvqU{IJh$I`BEW2o(ZqbW~Aj=iHXe`t5>R5M6Gbu7=jRaJU?# zsfH?7=n8p772u&LXYG3Td=fa%%;TKk?PR3-$H)hq9@NKv?0Taqe@qDagi X*_8FCW>a0$00000NkvXXu0mjf3c1oq delta 509 zcmVXeExJ3-#3HF+FKBXf2# zt1sV$wT1}c7QU6Q`Y6s$a3M%yU2;*9hWwB!zmdQ^Wri)19W z881A7Iv{7xbozTIXMdH9CxJ-7lB!WVGpA*G)S4=+M|7S-Y9q@IcWGyI2ZVBLg!dQr zYygmtmJ?>vAota)NCl(fBzmk zpl<_qCc6HSiVWr(pg8TFSq_6Ygq#QXB~=yBHJXmp0jDa42bpw&G~s<@aDZbJ;hemy zV$K5%Cj;+^My3vI7ONJrIim1X>zeBJ0E%Vwm0Anzb#_Qs{oLGApb}*;vS)SffQc|3 zS^Ye~D2k#eihcP46+e_Zt9MGH00000NkvXXu0mjfH+t{I diff --git a/tests/snapshots/nanos/test_get_public_key_confirm_refused/00007.png b/tests/snapshots/nanos/test_get_public_key_confirm_refused/00007.png deleted file mode 100644 index cfde67b4d8d5a664e05496ff1b161e39d1de0d91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 500 zcmVICO{ii z0h?#^zSJ_<7?mVcd!#ahB!sW9rZSWyNsK46`mFaE6>|}33}!zMzQdYRjDbq0Hhh)| ztG;H)OBI#SYpuk}f~eR5IKcT+UvsTKbx-Q2hP-$1?-$oqgBXko0|fduRB*N-Ngpyk zYi}AO1SL=rf=bQ+Z;$Hy*+^z$JK=Xj(12kx(42Vg6pQ|@!6YCIP~|deat4yqXuDKl zX(AYfW|@qd1;|lJN!Rff{rm@E*duvhgn6dI|8Mn=)#_9kpg`q@tA8!-VuL+#uPC$| zZg&oE1+l#TYA9Izv(r$#<^v9CM(Zlj##$upxqlDf7O}f%Bvsd;5thSUqsIP`Fup5f*1Mq#v z%Ed`G#%Z*ilw71~>n>!%MOtZ$GY+xy4#w;60~!fnX(&Ba>|^Jc2L#4L$<@nxLs5N2 q(nrY|NIL0&!HG8-jYgx<*bW~+h<0}@GM`xh00004%P)9b)2)g92paL(i9{cLcOOt;HBu#1jH;My^e^WYIY`Tm zx==#ywGU^5gg0;-dK#9SqTqSI7{XpTs51Mo`ZhEP>V0Y&_Dq2K8pHOhlAF6q9}@@C@$p-4n^@LW*~M$00000NkvXX Hu0mjfmP@>b diff --git a/tests/snapshots/nanos/test_sign_tx_long_tx/00000.png b/tests/snapshots/nanos/test_sign_tx_long_tx/00000.png new file mode 100644 index 0000000000000000000000000000000000000000..e082b3c515cc7cb1940f5ebff3d06670575d4308 GIT binary patch literal 358 zcmV-s0h#`ZP)S|Nn#g&^@?xOb|tFYwJB}8fTDBP*9u$000000N`}|#oyo7U)wLXfhKt% zLmfcd)9||b4WKgVpM*D zL+BNPrl;*5AfC{Hg@#f@%G}n9LiN~Q+`FpLcoEx`oXjwr_3C0=AX=3GXMYp literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_sign_tx_long_tx/00001.png b/tests/snapshots/nanos/test_sign_tx_long_tx/00001.png new file mode 100644 index 0000000000000000000000000000000000000000..5472cd148c6d7cdc8b7d41cc6c68f849f6a61e75 GIT binary patch literal 355 zcmV-p0i6DcP)c?x8#cdvV{x=L%X$E`tt0LW}aLDaBLN9~hPCoilIL zM2`exfQK=_!x$i;_mR;1NSya4$Tl|ghJ0V4USFCY<4e!>_EB;A|5Gf(O)tpU^{ad9 zw2yQL{USEvbN5D=UeGn_0zwHopr`;zmy9|U?M4YYWppQG1{Aye0rdJCpwEDcTlHU8 z@@kjwBoc{4B5?#?BS!c2$niVnLSZ{j za$gVTny^av^s9m}-;4YDcY^;OFkj-sJOo?$EBN(rlW`y-qMrd91idNKC0UJv8Mq>>Gp`a&BIHGrb7B&aQNXPAfn&l%ThU0K*6jeX zD$S>3RkCYVW}#Sqz|?S0ldmp8q@pa!POrP(a()-AD?uQ#kxjkNjU_{G^Is((zRExy z)Yk*9$<>NO5M9a5J<(4hq65s8?Neo1Wl3A(~dTD)>#usOWOp?Qf0 zI$ioexr?y=Ha&?C7co&7dK2y$Ac3)=7bMWt0UqlVQD+HyB@87%_usK{T<2DwrF3C6 zt%MLubm^!)5-+OU5I3`0a8#H9-+kJnJ5o}gn}2SW*{p_y;BH-~r*R%N#z zfdm8#j<-@o2{zfxX$!+hao6O{ua$bA-Qkay}kw_#GL`3ulzQ}~S>D!9u P00000NkvXXu0mjfV|V92 literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_sign_tx_long_tx/00003.png b/tests/snapshots/nanos/test_sign_tx_long_tx/00003.png new file mode 100644 index 0000000000000000000000000000000000000000..9f97b45a02f77f5069b1c4af65fbc7f18cb8e0c6 GIT binary patch literal 536 zcmV+z0_XjSP)iy;dRP; z1IVrvpT<(;(2&F|5%4R%Qdp-=_gw1FPC>wCnWUY-MuXtyinGj7W(v}?>Z0p`6h_!J z-$@t17QnywN)qIE18j-SOnSzOhB#^fWp{6>A%@fO+3I zy-I^pflyv1@zU+o*F7p>ghM3-B~wk0=S*rb?g`|6ug>gWUtdQp$p1;{DfSY0C3y&E zd6~OVQftZ|EbhLJ1;CYh(+pSzSTx|jJrVb+4rVXnLO(D9Oz2!t4aJxTT0m;eJ&ja> z^svQ$E`5D3UI0ScmEd|8Bwr0_j`Xz_K)E=QcIN>=%=83p>WgZo3y)Nf$wR9silQir ar|LQPLd%0000Fhd#tru3OSvZ}2`x^oMUTE$zhU2i-P^jxH4e4vP!2k@6PC|!EB~6*?NFk{k zp$c7iqI_*?etdcwYhGX-SA}inO@c-`dDWygbtB!1Dwa{dNrpKMb+AVRehZ-Q#3LD_ zwly|qyPgGO2-ky+vw6HzP=ToF($4JOm%{lf`0W72vr@J>1MJAF>vN_63t*2jB?;o) zfND~;A(FCUggq@RT}T6#GAeuW>pc#pe8oL(;+Z#NQRlYauedD0y5`Nj|9o-{BFQWi zNuI-zfP0s!p)TlEqXO4El%T@URDnN(O6~K?NaYIrR@96?;kWc_1g^!nDQ*;Q8Yk3( zj=+|SjWuK`w+vVMNR9ekVK=^>g0NK38PU^@MHs=0?Enkx*=ww`M!Oq{I|D*|LEm;> ygZH_HD!0<2UM|b;n*_eFOV8^`k|arzT%QMT3r=~HHqwv)0000j}_{Dj70f0-FvEj=5D0-i?V{)IpB(;!nD_D8Y#_r)$SM-szP&<@CyKSw{mVMHsloC57y%MNu;;ut$XV?T=QYKV;n)&L6BjLp*)g21onepbsAd0 z6ZYQGrbeLB9QU)2jUHupc@{wtG63t+<{#G`#Y(WksugU>@-tYH0WzESO{~DE7ot`| z?*R19{WStZhsIIsMvN=T-hG)~0EP7zxufz^@y>wFolOuJ4T)^XT0f&nv0000I5x0-H0KVO5W7@0NjY_rR1~7d=9VAebqa>a`Dl29vfvt8|hUa_?k8& z6;aBeQ|L0X_SKYUZ~N+0`8`YdjeHp}2{CW3IBa8kQ9@&_GD#&d&cCZ89tQtDV1PTF zR7e91r%+aR`I#UMa3MX?SbuKZ$9q8Nw1jxf_O{nGI5E4qDB}AfW^T7FDUHo d?2h9c;R9;H5a6G+8ruK>002ovPDHLkV1klS$i)Bv literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_sign_tx_long_tx/00007.png b/tests/snapshots/nanos/test_sign_tx_long_tx/00007.png new file mode 100644 index 0000000000000000000000000000000000000000..9d783deccf1d91edca026328fc3120ae7e63ff8f GIT binary patch literal 436 zcmV;l0ZaagP)|Np@*bis&B9=9nHLOVI*LV<)pNLs_TB}tMbNfN@%OY!^lo#CmrcuZQ9 z*SYv~rHc&XMv_K1o9|9~10Z`>KBT?&Zn`H#L9S#F4=24p%JC5wz>ixyxRn|eMj$eR zh>L)_%jCGVX~dE}cvo%VjYh+WpcY9Z2y#HX-NuFSVOUUf?FJtSnxH4nDTt&IEgeTO zWs5OWB4EhAbCDrR`t*#0IbhnMs9|o;Lj3JT>>_Hld?KbnE~w4e$IeO@2+4lzj*Qi; zsyg-V7yKOHuJ8i8e=gDokz6?otc-Cp_B

51}V5`eS;o)$uXC}&4qy*z7lGJR`e7^w$5j<^U| zt<28a8tgcqFptVqGmRpu$we)rHzNQS-_`QxCZ$*=etk+QP-9X* zz$cbjh1pFhcr<+cCpo|&3}utRC-!SvYE6rcoUJ8UYpz8xyq*~mp!N<`a6bggVUIeM pHVuT!oIuNgF5x6ek|arX@BvK(5hSYN)tCSP002ovPDHLkV1g`t$I$=) literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_sign_tx_long_tx/00009.png b/tests/snapshots/nanos/test_sign_tx_long_tx/00009.png new file mode 100644 index 0000000000000000000000000000000000000000..fdd5105287cb43da1190e2c2448f1d93283a380c GIT binary patch literal 456 zcmV;(0XP1MP)pxt1K(yJLr8Hi8cjlqne#~2kHHzo*;G)^u$47ILjrCq_h~v z&O~(&8gU2o16&D2(_Lgvtn8 zrbls!i*>Hb02jYY*XbDzeJk;U1mMa;(K6j$h3F3$_ERC1O(|bc5-tW^`z~?X+2#Z( zd5VrJ_jSC1ZAMWBnA z3^*xMNF&;1&vqrydT>E=4S+EpJDR;-nQk#npCX?59-t8U25Uun!t=&21K8(77JXyz y7A@QJ2-3Xk9jSkLs7Ojx$iCAgNs=T<=H&+@StP;2nDUMQ0000qJ7EWj`&9`T!l5fR|avLGRN@6fI~I+3&>5!srh1y4DXJs<%XmJy}K>vtjg(}f*E#_4E*-3j56(4lfYPA6~b zU5}S&J8#Du7wz2I`x(;!*jih_`}ZPZ;K|Cn(EO}?4$sdZ_l}vyF;`|xQ}bVGW;43Z z(y1SSt{CAxTIGeiG^AXAN_O-%0J{?UPfd~(MfXb-410B6FVyYSKgrI*N^}*)jwEct zz8^3i#XC~V%#RfVAZ-q`%`m|>vQSI15~)4sMAr&nI~#vB5s{r!bvml3AbI`TdR>JE eK@bE%FcAmcc@p%Z8Wu7D0000^0fM#Y*PksVnl(MC1}nPHrQP#U32^34NF zOf0ix2TZX$?J`A4pPqRT2P_MUvd#5bhN_) z7E$GrEp5_gS?#~2dA53*8|90ES|gp|f_n}gxm?axE79}6jmqkqrjB2eq0^9;CWz@c zCj~WQzX+f#)SYeY{#pa9 wBtM`76J4sHFCz9l7v|=9x&}cI1VONX4=2$U{T{TX1poj507*qoM6N<$fsNi zu?!WVVJ8#%n0@z98UuCQ?qcTU9V*92hzn843?3dze<-;lT93I#C(~6EkCAaQB8WQy zT9WRE9Z$WOusB5LFnPOen!?!x1%SG{@vU&0vCxR_W?l&eqjt^4xdgE?qSfL+{9SCb z|^>hmKKAC`$TNF=4f5ZhSOMH5oer1=_(s zFPQt)AtvvtV_n_QRjt~klr+*>enxoM%s-jY4K2oLe|wz>;}vkIB_s`Woq&ms?p9^r q&*S=016CV4)M!tVBuSDaN8tlYV>-bC`A@O{0000jh7(Wo2K*sfeZ*?SN zQ``=BY*TH}(31`3sQt(gItFUEZenKT4jo5Nhzn6j1kVhi|7d4N)E>_oeVDEa3?m~l zMldb{v_sm19iv`6us9W>8xyES(rE@cfSTL+t?)5pVYipZPdI4gu4s}|5KALkE>7CI zVH+{u#5R{iK#1LOiARWdx;?_L0vvGJiJ)1+xhcN7h*J&B<3FL?DsH^;3{!1ad&$b zh2vk(+M{GP_r%_HV>3j@qV=U<38`5jD`Lyq8LN!H9%1mxx4`rT25%I S*h3%y0000f7uat_U$74 z&`1rSHo%#JVtQ!4^?&FFVc-yHQLQOi^AU(j;>G^CCe4~P%gA#y9q;gDC@o*?nT?2 xVoS`O0h%Wn;ZCURT-M!7Wz|6t1VIqY&JTPDDB+DI;+X&d002ovPDHLkV1j3J)$RZQ literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_sign_tx_long_tx/00015.png b/tests/snapshots/nanos/test_sign_tx_long_tx/00015.png new file mode 100644 index 0000000000000000000000000000000000000000..cc729677d10a85bb91bb2f83db3f95d028021c4d GIT binary patch literal 465 zcmV;?0WSWDP)G53;x zGwNFeol&Fr67Ig@Vt~e?KiQT7v_ADj`Jmx7QN28T#r;v@h1HnG7wim2wQeJ&_1OTA zAYupXRz~r)Z*aQP*&wBtpx7PSqSuoOrwZ6O-2u?t%9ZF-N|O$Pj|*%H@@7~HLTQAq zA#~$oMw(b=@D6aXI~`(o9xEXtCWr%0h=qM$>$4DjTwyP15E2^H2dal21T20!&@*l) zn$SD)3KLZ&OltkYX$}}AXKL%uNctd>HM7txta%@vg*5LM7eh2XNv}r7vd0xQ4blwf z0jQwZRKG+T)8VzB8!2qa{cH98TDiX_mR$v0PQbrtHlXO2jSCQEf6c%{X|lo{v&fPg z8F?OH;qZ(_?(Z_Gev~2n;~XH@RcZw|`E1KhM`g)2b({k(RgUh%BlVwk9zYSU%C~eR zp&Xku9aze@(`c&;AqqF1DES*$=|g zj8z3-H$iRGXuXQ3tvCmurAl9sl{2uNdLn($Kq}-MFI#bc(q>`RrJDcb z`phr_jrm5JSY~7dnAn{T>2n?$#R~B_42T0p?}Z%H`Yc5MtFS+@(4;5iG^i5PP|jUV zRF>=)iT&sud6Bt_5>~W+Vx|K|$ywL>ixNKYHp#rG1sj0=63LET@?RaAnrt5pFnDD~UM!q!CYh<;QCYRcw zKz3feOty3Nz?1d2I4>|1()n8^B1lIJPC z+)cqE0-9bBl}BGw7NRoYyPGf_9VPV92n>QC2!dc4zNRuH_Rq26TL1t607*qoM6N<$ Ef__-ffB*mh literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_sign_tx_long_tx/00017.png b/tests/snapshots/nanos/test_sign_tx_long_tx/00017.png new file mode 100644 index 0000000000000000000000000000000000000000..b511cebddb844b652e34a8747f8e443e474dd7b8 GIT binary patch literal 456 zcmV;(0XP1MP)oknDgWq1T+D_5HmE!mr|-%rP!)Z)lIG{%10fJG1dX4xrNKXZhO~HIy*Iw z1a6=KHYun|BV6*G2TK#Tv5Wu}yWImm$ANM|5Nb$K&_9SD1S_YplzHVLtSr_lX7Z~y1m+&jv5W4 zA=5MucsSt^P_4{PRt;^8@#gWwCaRY8xoqEKZ%l#9xIwG~sJWGUz-}g+SH2U?E65Zd z2e3UfJJ1`}k779Lc)4ddo)Lgsx?pY`GNgpLS#vUC0S9;=gc{NMTL?c?noW9yYmKgo zE|Pr?+<8=PG*5}%k&{f<9loOV3&9R>QL?V}7bSi0Bp-MSEyM@D!wcbJ-t^flIC)|c z2m36;q8@;WPt49!Ig`k_PfgB}Y0zi(^%6>NmdDU!T6*13DEuR<)hHL#p3bXLGLBn4 zpmJe}VRufml%^kRfxiQ`@r+GOP-FRFG|gwh`X$Mm^vZToSVw?R=ujd&%cThuqO6KL xTE8saa3D%(Id7szIFd;|N&hKn{Uk{eQ(gs?9Tschj|>0+002ovPDHLkV1hq+%dh|d literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_sign_tx_long_tx/00019.png b/tests/snapshots/nanos/test_sign_tx_long_tx/00019.png new file mode 100644 index 0000000000000000000000000000000000000000..2ca2342197e6ffe6e9d6ee22f615df5f4a3028eb GIT binary patch literal 478 zcmV<40U`d0P)WpuuZgHunJLJF!%!b#7~S`TmO0>$YB`WU_e(A+mpt5l*uw*;A0 zDr7^DHEtmYg%P@jsg95FG_j7e2bkDhx;%Se6=s|XvH=6mLh^lQW~xE}VG}@4{y0g&&1A z%_`OUS<#Ljb6RnoDJ9E^8KolfgkUa{=Fr)6rj&N4`bu(N83Lt4bP))fDWc^IA^qEI z4I_Hr`dZ~@lSpNoyLi3_)L`jfNrdzaSJQEL zh&S~B<=yJCSK-op%6*8c*r&}K%B?yl;}nba+uM^9L(o<%a0#yfihG|PXM$iDe;lhO U%{WZ1UH||907*qoM6N<$f=KArCIA2c literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_sign_tx_long_tx/00020.png b/tests/snapshots/nanos/test_sign_tx_long_tx/00020.png new file mode 100644 index 0000000000000000000000000000000000000000..2a41e2cb23f6e72e4e94e185091ca80576560a59 GIT binary patch literal 464 zcmV;>0WbcEP)l&MV0rP-bRkbeU2}kL!v7}VWzuob`#2KED5w@s(ZS15*B?1kNLz7cL18$m zwv0H}kfX>1AI|U!DDv^}!cp3|#>w==KP4*7v%tQ6{~D~tQ(!YX7#jfSZsi)dEu{)F z-bp+XxPjLAY(d2~f~}ud@+@vHmhr3r6TjmjR>2d_AKCzW9rZKvzR>Db{bdOtHb04_?__5Pwn3}$k}U1%Y0cn&XwqkHvV zwpH(-V-n(4X(unHcB!{=4S`)N0(LZOzgFa4JSSXCp={`r@~{;MC~f?>0@|1I`i7y0000%{TvV9e zb+(N3(P0;O1k)>^MY&sCt-7(yg_c4uQMFdCqJH$@9&-~w!>ybKid~q07su}JXpn6| z*7&rbu8rUXLaUy^GsH5U6<`u~yu~DWtdfXj0SWM43$3X2T?pS**h?eW{(}PLwJ}l; z<=%CQgDJ;V;&t?iT*TH;!;aCf%uIlno_(XgDVc*v-mnX8#2Y?`H)2wF+dEuHI_ayV zl`1#fHseQ0WbcEP)H9x$7o9~;J;War6>lBBRhs}Rh$fg29LI4S$63O|*xvi~G5i#-nDc(! zj9)Ec2e1!RGSl3~jA}x=H%`c&1*C(CZipRuPSK@M~!6#x+yezT(WH!c?m^ zjjCLe$&86i&j4vk#?;0fXQwCLqGlseE0NUq7Ms9sbTBsn)ZNN4pjg8+XX72RTf#+V z?J{D_5|k_>(#pFP!(5zaWCob{9aXG?$J&`bGavw?CjwoRm2KgJ3VW;ATt=Q<8)+w# z$ARWJ6^6Vh4ZF|>a+Bsn^%K2+F*yK3jGXKJL-81_Xm=iv6c=etjTYLXfTYNZv7RalORq~(ITjmA>L|lb1&ol2=EY2}QfzKT zqmucdZ=!}pj)e1k zyeZ!;q66pyy;JeL84R7sPoScmk-p@ytJ`8O8Aw3(LZCIlXbT@y*h>;v$Ca{1y`oMw zvfX+5Fy%?x(1ksb7cnP_uNeKE!3juFvTpPjrE)NnFRVff@rCE`LQDv6)-%HexUz5Y zB+b^NHo~Qd?*UD#693}s65H~Wv^C8PZ2hy=B`Z>ognhS?J_+bjqp@`4n{jr~F0S~X zTTy=K==vQ5HRJG`lxJz!#XCaK{mmeUhy*mHMr!N#X|vydN`HqxImC5Cv7~yOc8ae< z#17C8VW&2{Bpuoolw*0nBpGEj6v0$vXHwBGisJwA0;gjz(DnLSEC2ui07*qoM6N<$ Ef&;bEWdHyG literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_sign_tx_long_tx/00024.png b/tests/snapshots/nanos/test_sign_tx_long_tx/00024.png new file mode 100644 index 0000000000000000000000000000000000000000..2470abae4cb775d754f71e37a412a095b8af5e85 GIT binary patch literal 468 zcmV;_0W1EAP)yEn1QZ@^VAEl(OHZ~>^fl@q{bmx9VwVVhy05W9?U2tidC zX@y-6?wHBV#4>IkU}AR^(I`=N#5M~*3}69VH$rh|eH6m46t?PUggDwGiWFE`I?n2g zV0fJ_W`l4~%NPm4l(@oDGqUig|<);@guU4b=6 z^^d^Yh7qQz2%1wXU?yG8cU~%?X!8EO>DB6i0aXPg0S95X342pzrS2%vw^yd~hQ&LX z^dJ)%QsZySJ@`Dx$h1=bN_r!}im~F&yeG-llbJ?vZJT!C<^jS%S5>jkkYkz=RF2OT z!U%-{EWHVwVy=98o9GN@^S>f)GJFo%VftTC!Ea(~{K}9di4iaTF)YX%At_z}0000< KMNUMnLSTY2QO^4S literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_sign_tx_long_tx/00025.png b/tests/snapshots/nanos/test_sign_tx_long_tx/00025.png new file mode 100644 index 0000000000000000000000000000000000000000..c7c4212cb09b6385f0cef7ba7def84c1a617d383 GIT binary patch literal 447 zcmV;w0YLtVP)m)3PaiG`EKYM5%KAI{+#V;w-vt$YpMtlF{a71)3aVG6RQPYTM? z2!DaD8}hJT7W9k&6T9;+8YR^>u)&PYfH|P|T1Y{y&qDmH0v`okiW71g?L}vpodvG= zsq)J6BC#F4BNrK~s9{IzM@BlJm!5sCzbWp6ncOf7ZNv@t;f)w)Ui~R>%($nT4P1rY zKEz6g<*Ajg1u-o}WaS4~%M@z&aw$Ks4Z27s~rVTRHvuZ zym2)&gMV1+eQ=kZAy^_|+f40%Uf zZ-m~VIVtq5OA**fxtau5g(n5&PsOm+Vk)-`SdPUH0M%LMqp!o@pQ+Ujntx3CH+ATa z8@KlW63!T;6A623-S&lV7Gn*Nn9M`kKnU&DsJ~E_D6y`Fp7S;C?d%Wehyd)iS&#DR uL_TTZ&cZ3zdI0MC3r}xB5ClOG+>H;=KQO{KP4cY(0000eu6TU0#}3R2sU(|P1c zX?O{FAQv%5itp(CjB@}?lBw5DbXOe%TM_xsBs3U!<_3Y zO6IX!0H|KjXbg;-%yRbsOEM81?@h4J0NyoxbFsM(;q^$}mfesH1?)tI1wjx5L9jYs XO}HG>PNpS|Nn#g&^@?xOb|tFYwJB}8fTDBP*9u$000000N`}|#oyo7U)wLXfhKt% zLmfcd)9||b4WKgVpM*D zL+BNPrl;*5AfC{Hg@#f@%G}n9LiN~Q+`FpLcoEx`oXjwr_3C0=AX=3GXMYp literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_sign_tx_refused/00001.png b/tests/snapshots/nanos/test_sign_tx_refused/00001.png new file mode 100644 index 0000000000000000000000000000000000000000..5472cd148c6d7cdc8b7d41cc6c68f849f6a61e75 GIT binary patch literal 355 zcmV-p0i6DcP)c?x8#cdvV{x=L%X$E`tt0LW}aLDaBLN9~hPCoilIL zM2`exfQK=_!x$i;_mR;1NSya4$Tl|ghJ0V4USFCY<4e!>_EB;A|5Gf(O)tpU^{ad9 zw2yQL{USEvbN5D=UeGn_0zwHopr`;zmy9|U?M4YYWppQG1{Aye0rdJCpwEDcTlHU8 z@@kjwBoc{4B5?#?BS!c2$niVnLSZ{j za$gVTny^av^s9m}-;4YDcY^;OFkj-sJOo?$EBN(rlW`y-qMrd91idNKC0UJv8Mq>>Gp`a&BIHGrb7B&aQNXPAfn&l%ThU0K*6jeX zD$S>3RkCYVW}#Sqz|?S0ldmp8q@pa!POrP(a()-AD?uQ#kxjkNjU_{G^Is((zRExy z)Yk*9$<>NO5M9a5J<(4hq65s8?Neo1Wl3A(~dTD)>#usOWOp?Qf0 zI$ioexr?y=Ha&?C7co&7dK2y$Ac3)=7bMWt0UqlVQD+HyB@87%_usK{T<2DwrF3C6 zt%MLubm^!)5-+OU5I3`0a8#H9-+kJnJ5o}gn}2SW*{p_y;BH-~r*R%N#z zfdm8#j<-@o2{zfxX$!+hao6O{ua$bA-Qkay}kw_#GL`3ulzQ}~S>D!9u P00000NkvXXu0mjfV|V92 literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_sign_tx_refused/00003.png b/tests/snapshots/nanos/test_sign_tx_refused/00003.png new file mode 100644 index 0000000000000000000000000000000000000000..9f97b45a02f77f5069b1c4af65fbc7f18cb8e0c6 GIT binary patch literal 536 zcmV+z0_XjSP)iy;dRP; z1IVrvpT<(;(2&F|5%4R%Qdp-=_gw1FPC>wCnWUY-MuXtyinGj7W(v}?>Z0p`6h_!J z-$@t17QnywN)qIE18j-SOnSzOhB#^fWp{6>A%@fO+3I zy-I^pflyv1@zU+o*F7p>ghM3-B~wk0=S*rb?g`|6ug>gWUtdQp$p1;{DfSY0C3y&E zd6~OVQftZ|EbhLJ1;CYh(+pSzSTx|jJrVb+4rVXnLO(D9Oz2!t4aJxTT0m;eJ&ja> z^svQ$E`5D3UI0ScmEd|8Bwr0_j`Xz_K)E=QcIN>=%=83p>WgZo3y)Nf$wR9silQir ar|LQPLd%0000Fhd#tru3OSvZ}2`x^oMUTE$zhU2i-P^jxH4e4vP!2k@6PC|!EB~6*?NFk{k zp$c7iqI_*?etdcwYhGX-SA}inO@c-`dDWygbtB!1Dwa{dNrpKMb+AVRehZ-Q#3LD_ zwly|qyPgGO2-ky+vw6HzP=ToF($4JOm%{lf`0W72vr@J>1MJAF>vN_63t*2jB?;o) zfND~;A(FCUggq@RT}T6#GAeuW>pc#pe8oL(;+Z#NQRlYauedD0y5`Nj|9o-{BFQWi zNuI-zfP0s!p)TlEqXO4El%T@URDnN(O6~K?NaYIrR@96?;kWc_1g^!nDQ*;Q8Yk3( zj=+|SjWuK`w+vVMNR9ekVK=^>g0NK38PU^@MHs=0?Enkx*=ww`M!Oq{I|D*|LEm;> ygZH_HD!0<2UM|b;n*_eFOV8^`k|arzT%QMT3r=~HHqwv)0000%5#E^A7xvy3!~c*55`ix$LP?UD=|(0 zc)8k!kOyc&z$t~@?=T+J$`hbcRI>pA0)XAETreHhdZ{-(-FOxCZF(kPK1j$m#-0MpfS z(yy7_$boSxN0;(%mxDBAeX3R?<%(-Rpf?7-YHe^zK|(jNRfg2~RK7eHuC>WFXOzv( fBuSDaNiNI>{GSBuM&fnH00000NkvXXu0mjfJomaM literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_sign_tx_refused/00006.png b/tests/snapshots/nanos/test_sign_tx_refused/00006.png new file mode 100644 index 0000000000000000000000000000000000000000..c88c94e48ba2dad3a989cf9c0eea52d947b0c8f4 GIT binary patch literal 445 zcmV;u0Yd(XP)NS=aDY(;QI4QqT;=fdinqgDS0N}DtYx9H<%jglDj3}Z`u37K2VdtnRNIg{ zQo9qj+r}5?WQYTlV0!*D>-#5sFq5mOkWa3%5BtR0yzGC$Qk{W=4(wBT!pbLM`@@b& z4tXFyI{>BK(X-GTY+kdT#9zpPE2lS5o{mmWl31-TbA$Vg2Hd_(}<)7U#milkaa n#NnbTl?W#s1VIo4!O3_5nfMmnhF&;700000NkvXXu0mjfH)+B6 literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_sign_tx_refused/00007.png b/tests/snapshots/nanos/test_sign_tx_refused/00007.png new file mode 100644 index 0000000000000000000000000000000000000000..28d495204a126ce3f4fe21134f7e8b03c3618064 GIT binary patch literal 419 zcmV;U0bKrxP)v>nU=1vGz9d+Du=q2ovM)OK(oKU3^o(Pt12hO6(vyPXE>I|`+8}*r6^!lyx7)@K+}xdyKL!J6Po%aH)@*0T znhZ&R5{%_Pi@tv%2Q%44g?zHhKJ1hA!n@uT_u-%vLHYNfLrl&CD5$swf1S|Nn#g&^@?xOb|tFYwJB}8fTDBP*9u$000000N`}|#oyo7U)wLXfhKt% zLmfcd)9||b4WKgVpM*D zL+BNPrl;*5AfC{Hg@#f@%G}n9LiN~Q+`FpLcoEx`oXjwr_3C0=AX=3GXMYp literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_sign_tx_short_tx/00001.png b/tests/snapshots/nanos/test_sign_tx_short_tx/00001.png new file mode 100644 index 0000000000000000000000000000000000000000..5472cd148c6d7cdc8b7d41cc6c68f849f6a61e75 GIT binary patch literal 355 zcmV-p0i6DcP)c?x8#cdvV{x=L%X$E`tt0LW}aLDaBLN9~hPCoilIL zM2`exfQK=_!x$i;_mR;1NSya4$Tl|ghJ0V4USFCY<4e!>_EB;A|5Gf(O)tpU^{ad9 zw2yQL{USEvbN5D=UeGn_0zwHopr`;zmy9|U?M4YYWppQG1{Aye0rdJCpwEDcTlHU8 z@@kjwBoc{4B5?#?BS!c2$niVnLSZ{j za$gVTny^av^s9m}-;4YDcY^;OFkj-sJOo?$EBN(rlW`y-qMrd91idNKC0UJv8Mq>>Gp`a&BIHGrb7B&aQNXPAfn&l%ThU0K*6jeX zD$S>3RkCYVW}#Sqz|?S0ldmp8q@pa!POrP(a()-AD?uQ#kxjkNjU_{G^Is((zRExy z)Yk*9$<>NO5M9a5J<(4hq65s8?Neo1Wl3A(~dTD)>#usOWOp?Qf0 zI$ioexr?y=Ha&?C7co&7dK2y$Ac3)=7bMWt0UqlVQD+HyB@87%_usK{T<2DwrF3C6 zt%MLubm^!)5-+OU5I3`0a8#H9-+kJnJ5o}gn}2SW*{p_y;BH-~r*R%N#z zfdm8#j<-@o2{zfxX$!+hao6O{ua$bA-Qkay}kw_#GL`3ulzQ}~S>D!9u P00000NkvXXu0mjfV|V92 literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_sign_tx_short_tx/00003.png b/tests/snapshots/nanos/test_sign_tx_short_tx/00003.png new file mode 100644 index 0000000000000000000000000000000000000000..9f97b45a02f77f5069b1c4af65fbc7f18cb8e0c6 GIT binary patch literal 536 zcmV+z0_XjSP)iy;dRP; z1IVrvpT<(;(2&F|5%4R%Qdp-=_gw1FPC>wCnWUY-MuXtyinGj7W(v}?>Z0p`6h_!J z-$@t17QnywN)qIE18j-SOnSzOhB#^fWp{6>A%@fO+3I zy-I^pflyv1@zU+o*F7p>ghM3-B~wk0=S*rb?g`|6ug>gWUtdQp$p1;{DfSY0C3y&E zd6~OVQftZ|EbhLJ1;CYh(+pSzSTx|jJrVb+4rVXnLO(D9Oz2!t4aJxTT0m;eJ&ja> z^svQ$E`5D3UI0ScmEd|8Bwr0_j`Xz_K)E=QcIN>=%=83p>WgZo3y)Nf$wR9silQir ar|LQPLd%0000Fhd#tru3OSvZ}2`x^oMUTE$zhU2i-P^jxH4e4vP!2k@6PC|!EB~6*?NFk{k zp$c7iqI_*?etdcwYhGX-SA}inO@c-`dDWygbtB!1Dwa{dNrpKMb+AVRehZ-Q#3LD_ zwly|qyPgGO2-ky+vw6HzP=ToF($4JOm%{lf`0W72vr@J>1MJAF>vN_63t*2jB?;o) zfND~;A(FCUggq@RT}T6#GAeuW>pc#pe8oL(;+Z#NQRlYauedD0y5`Nj|9o-{BFQWi zNuI-zfP0s!p)TlEqXO4El%T@URDnN(O6~K?NaYIrR@96?;kWc_1g^!nDQ*;Q8Yk3( zj=+|SjWuK`w+vVMNR9ekVK=^>g0NK38PU^@MHs=0?Enkx*=ww`M!Oq{I|D*|LEm;> ygZH_HD!0<2UM|b;n*_eFOV8^`k|arzT%QMT3r=~HHqwv)0000?5 zO+iH((UJwrBtH{-1)QS0?h+|@Dgcp|r%g`f+q00&{#f-J4#!vj2DlPHqU7yg4J|%-;$NVy2o-P5#!k+}rQedO*BF{rJkMqQw&X>-Dw;}jBm_TE=?d*z`ILyB?rWPOBAJIX+Y1mF^T9mvV3_wxG+Yry2qARt#sf<#$}=)VJ~ zBlvESR-Jq3=$jydfC?r!$iuAw9oGJ!U-M%YMJ+y&Ch%TCM^JUq5BGkf8O_{y`#mQ} nq4{V^xv44i3V1v$0C_ea3_go|-rj`700000NkvXXu0mjfd&QW1 literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanos/test_sign_tx_short_tx/00007.png b/tests/snapshots/nanos/test_sign_tx_short_tx/00007.png new file mode 100644 index 0000000000000000000000000000000000000000..68ffb394818b8a1144c7bdffd0e413ebb828eca8 GIT binary patch literal 455 zcmV;&0XY7NP)ktdMRJ7$jtQV?E0Jw2 zEX<>cv$fC(3cFo%Dj&aPIUE4ZO{b?Km?d}fT6HH2&km6xtYY2t?0?Vdg78W$~UYw9Gu>A)BWT- z@}KCar_^|OW15hRAhQF2ZeR=QhlZ+`4$E`Eas@aBM%J9qx?7F%p>;S934?&BM2?}J zlQ_IdDoMLegCO&ry@(xg1W(hjcYU6aul_=@0gb}dlu*bos`+p@Zo;(uu+MEe@Hx8( z6#{B>R8eT>+?LGx?SfhmU2dMPhSs@oxE!RZhALO+^5VzU*c}iW!q%g(soRz@=ckE) x>DmKY`<^6J1d`P&@3BZxxL8|~DvF{=%pcj5^`~Z2UDE&n002ovPDHLkV1l(T)B*qi literal 0 HcmV?d00001 diff --git a/tests/test_sign_cmd.py b/tests/test_sign_cmd.py index fb9affb..8e64813 100644 --- a/tests/test_sign_cmd.py +++ b/tests/test_sign_cmd.py @@ -1,141 +1,142 @@ -# import pytest - -# from application_client.boilerplate_transaction import Transaction -# from application_client.boilerplate_command_sender import BoilerplateCommandSender, Errors -# from application_client.boilerplate_response_unpacker import unpack_get_public_key_response, unpack_sign_tx_response -# from ragger.error import ExceptionRAPDU -# from ragger.navigator import NavInsID -# from utils import ROOT_SCREENSHOT_PATH, check_signature_validity - -# # In this tests we check the behavior of the device when asked to sign a transaction - - -# # In this test se send to the device a transaction to sign and validate it on screen -# # The transaction is short and will be sent in one chunk -# # We will ensure that the displayed information is correct by using screenshots comparison -# def test_sign_tx_short_tx(firmware, backend, navigator, test_name): -# # Use the app interface instead of raw interface -# client = BoilerplateCommandSender(backend) -# # The path used for this entire test -# path: str = "m/44'/1'/0'/0/0" - -# # First we need to get the public key of the device in order to build the transaction -# rapdu = client.get_public_key(path=path) -# _, public_key, _, _ = unpack_get_public_key_response(rapdu.data) - -# # Create the transaction that will be sent to the device for signing -# transaction = Transaction( -# nonce=1, -# to="0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", -# value=666, -# memo="For u EthDev" -# ).serialize() - -# # Send the sign device instruction. -# # As it requires on-screen validation, the function is asynchronous. -# # It will yield the result when the navigation is done -# with client.sign_tx(path=path, transaction=transaction): -# # Validate the on-screen request by performing the navigation appropriate for this device -# if firmware.device.startswith("nano"): -# navigator.navigate_until_text_and_compare(NavInsID.RIGHT_CLICK, -# [NavInsID.BOTH_CLICK], -# "Approve", -# ROOT_SCREENSHOT_PATH, -# test_name) -# else: -# navigator.navigate_until_text_and_compare(NavInsID.USE_CASE_REVIEW_TAP, -# [NavInsID.USE_CASE_REVIEW_CONFIRM, -# NavInsID.USE_CASE_STATUS_DISMISS], -# "Hold to sign", -# ROOT_SCREENSHOT_PATH, -# test_name) - -# # The device as yielded the result, parse it and ensure that the signature is correct -# response = client.get_async_response().data -# _, der_sig, _ = unpack_sign_tx_response(response) -# assert check_signature_validity(public_key, der_sig, transaction) - - -# # In this test se send to the device a transaction to sign and validate it on screen -# # This test is mostly the same as the previous one but with different values. -# # In particular the long memo will force the transaction to be sent in multiple chunks -# def test_sign_tx_long_tx(firmware, backend, navigator, test_name): -# # Use the app interface instead of raw interface -# client = BoilerplateCommandSender(backend) -# path: str = "m/44'/1'/0'/0/0" - -# rapdu = client.get_public_key(path=path) -# _, public_key, _, _ = unpack_get_public_key_response(rapdu.data) - -# transaction = Transaction( -# nonce=1, -# to="0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", -# value=666, -# memo=("This is a very long memo. " -# "It will force the app client to send the serialized transaction to be sent in chunk. " -# "As the maximum chunk size is 255 bytes we will make this memo greater than 255 characters. " -# "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam.") -# ).serialize() - -# with client.sign_tx(path=path, transaction=transaction): -# if firmware.device.startswith("nano"): -# navigator.navigate_until_text_and_compare(NavInsID.RIGHT_CLICK, -# [NavInsID.BOTH_CLICK], -# "Approve", -# ROOT_SCREENSHOT_PATH, -# test_name) -# else: -# navigator.navigate_until_text_and_compare(NavInsID.USE_CASE_REVIEW_TAP, -# [NavInsID.USE_CASE_REVIEW_CONFIRM, -# NavInsID.USE_CASE_STATUS_DISMISS], -# "Hold to sign", -# ROOT_SCREENSHOT_PATH, -# test_name) -# response = client.get_async_response().data -# _, der_sig, _ = unpack_sign_tx_response(response) -# assert check_signature_validity(public_key, der_sig, transaction) - - -# # Transaction signature refused test -# # The test will ask for a transaction signature that will be refused on screen -# def test_sign_tx_refused(firmware, backend, navigator, test_name): -# # Use the app interface instead of raw interface -# client = BoilerplateCommandSender(backend) -# path: str = "m/44'/1'/0'/0/0" - -# rapdu = client.get_public_key(path=path) -# _, pub_key, _, _ = unpack_get_public_key_response(rapdu.data) - -# transaction = Transaction( -# nonce=1, -# to="0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", -# value=666, -# memo="This transaction will be refused by the user" -# ).serialize() - -# if firmware.device.startswith("nano"): -# with pytest.raises(ExceptionRAPDU) as e: -# with client.sign_tx(path=path, transaction=transaction): -# navigator.navigate_until_text_and_compare(NavInsID.RIGHT_CLICK, -# [NavInsID.BOTH_CLICK], -# "Reject", -# ROOT_SCREENSHOT_PATH, -# test_name) - -# # Assert that we have received a refusal -# assert e.value.status == Errors.SW_DENY -# assert len(e.value.data) == 0 -# else: -# for i in range(3): -# instructions = [NavInsID.USE_CASE_REVIEW_TAP] * i -# instructions += [NavInsID.USE_CASE_REVIEW_REJECT, -# NavInsID.USE_CASE_CHOICE_CONFIRM, -# NavInsID.USE_CASE_STATUS_DISMISS] -# with pytest.raises(ExceptionRAPDU) as e: -# with client.sign_tx(path=path, transaction=transaction): -# navigator.navigate_and_compare(ROOT_SCREENSHOT_PATH, -# test_name + f"/part{i}", -# instructions) -# # Assert that we have received a refusal -# assert e.value.status == Errors.SW_DENY -# assert len(e.value.data) == 0 +import pytest + +from application_client.boilerplate_transaction import Transaction +from application_client.boilerplate_command_sender import BoilerplateCommandSender, Errors +from application_client.boilerplate_response_unpacker import unpack_get_public_key_response, unpack_sign_tx_response +from ragger.error import ExceptionRAPDU +from ragger.navigator import NavInsID +from utils import ROOT_SCREENSHOT_PATH, check_signature_validity + +# In this tests we check the behavior of the device when asked to sign a transaction + + +# In this test se send to the device a transaction to sign and validate it on screen +# The transaction is short and will be sent in one chunk +# We will ensure that the displayed information is correct by using screenshots comparison +def test_sign_tx_short_tx(firmware, backend, navigator, test_name): + # Use the app interface instead of raw interface + client = BoilerplateCommandSender(backend) + # The path used for this entire test + path: str = "m/44'/1'/0'/0/0" + + # First we need to get the public key of the device in order to build the transaction + rapdu = client.get_public_key(path=path) + _, public_key, _, _ = unpack_get_public_key_response(rapdu.data) + + # Create the transaction that will be sent to the device for signing + transaction = Transaction( + nonce=1, + to="0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", + value=666, + memo="For u EthDev" + ).serialize() + + # Send the sign device instruction. + # As it requires on-screen validation, the function is asynchronous. + # It will yield the result when the navigation is done + with client.sign_tx(path=path, transaction=transaction): + # Validate the on-screen request by performing the navigation appropriate for this device + if firmware.device.startswith("nano"): + navigator.navigate_until_text_and_compare(NavInsID.RIGHT_CLICK, + [NavInsID.BOTH_CLICK], + "Approve", + ROOT_SCREENSHOT_PATH, + test_name) + else: + navigator.navigate_until_text_and_compare(NavInsID.USE_CASE_REVIEW_TAP, + [NavInsID.USE_CASE_REVIEW_CONFIRM, + NavInsID.USE_CASE_STATUS_DISMISS], + "Hold to sign", + ROOT_SCREENSHOT_PATH, + test_name) + + # The device as yielded the result, parse it and ensure that the signature is correct + response = client.get_async_response().data + _, der_sig, _ = unpack_sign_tx_response(response) + + assert check_signature_validity(public_key, der_sig, transaction) + + +# In this test se send to the device a transaction to sign and validate it on screen +# This test is mostly the same as the previous one but with different values. +# In particular the long memo will force the transaction to be sent in multiple chunks +def test_sign_tx_long_tx(firmware, backend, navigator, test_name): + # Use the app interface instead of raw interface + client = BoilerplateCommandSender(backend) + path: str = "m/44'/1'/0'/0/0" + + rapdu = client.get_public_key(path=path) + _, public_key, _, _ = unpack_get_public_key_response(rapdu.data) + + transaction = Transaction( + nonce=1, + to="0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", + value=666, + memo=("This is a very long memo. " + "It will force the app client to send the serialized transaction to be sent in chunk. " + "As the maximum chunk size is 255 bytes we will make this memo greater than 255 characters. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam.") + ).serialize() + + with client.sign_tx(path=path, transaction=transaction): + if firmware.device.startswith("nano"): + navigator.navigate_until_text_and_compare(NavInsID.RIGHT_CLICK, + [NavInsID.BOTH_CLICK], + "Approve", + ROOT_SCREENSHOT_PATH, + test_name) + else: + navigator.navigate_until_text_and_compare(NavInsID.USE_CASE_REVIEW_TAP, + [NavInsID.USE_CASE_REVIEW_CONFIRM, + NavInsID.USE_CASE_STATUS_DISMISS], + "Hold to sign", + ROOT_SCREENSHOT_PATH, + test_name) + response = client.get_async_response().data + _, der_sig, _ = unpack_sign_tx_response(response) + assert check_signature_validity(public_key, der_sig, transaction) + + +# Transaction signature refused test +# The test will ask for a transaction signature that will be refused on screen +def test_sign_tx_refused(firmware, backend, navigator, test_name): + # Use the app interface instead of raw interface + client = BoilerplateCommandSender(backend) + path: str = "m/44'/1'/0'/0/0" + + rapdu = client.get_public_key(path=path) + _, pub_key, _, _ = unpack_get_public_key_response(rapdu.data) + + transaction = Transaction( + nonce=1, + to="0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", + value=666, + memo="This transaction will be refused by the user" + ).serialize() + + if firmware.device.startswith("nano"): + with pytest.raises(ExceptionRAPDU) as e: + with client.sign_tx(path=path, transaction=transaction): + navigator.navigate_until_text_and_compare(NavInsID.RIGHT_CLICK, + [NavInsID.BOTH_CLICK], + "Reject", + ROOT_SCREENSHOT_PATH, + test_name) + + # Assert that we have received a refusal + assert e.value.status == Errors.SW_DENY + assert len(e.value.data) == 0 + else: + for i in range(3): + instructions = [NavInsID.USE_CASE_REVIEW_TAP] * i + instructions += [NavInsID.USE_CASE_REVIEW_REJECT, + NavInsID.USE_CASE_CHOICE_CONFIRM, + NavInsID.USE_CASE_STATUS_DISMISS] + with pytest.raises(ExceptionRAPDU) as e: + with client.sign_tx(path=path, transaction=transaction): + navigator.navigate_and_compare(ROOT_SCREENSHOT_PATH, + test_name + f"/part{i}", + instructions) + # Assert that we have received a refusal + assert e.value.status == Errors.SW_DENY + assert len(e.value.data) == 0 From 11b95908d7df714ad3ba9a188e99aba2945726b8 Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Wed, 15 Nov 2023 15:15:33 +0100 Subject: [PATCH 09/15] Add test snapshots for LNX / LNSP --- .../snapshots/nanosp/test_app_mainmenu/00000.png | Bin 0 -> 534 bytes .../snapshots/nanosp/test_app_mainmenu/00001.png | Bin 0 -> 332 bytes .../snapshots/nanosp/test_app_mainmenu/00002.png | Bin 0 -> 350 bytes .../snapshots/nanosp/test_app_mainmenu/00003.png | Bin 0 -> 295 bytes .../00000.png | Bin 0 -> 401 bytes .../00001.png | Bin 0 -> 734 bytes .../00002.png | Bin 0 -> 364 bytes .../00003.png | Bin 0 -> 534 bytes .../test_get_public_key_confirm_refused/00000.png | Bin 0 -> 401 bytes .../test_get_public_key_confirm_refused/00001.png | Bin 0 -> 734 bytes .../test_get_public_key_confirm_refused/00002.png | Bin 0 -> 364 bytes .../test_get_public_key_confirm_refused/00003.png | Bin 0 -> 365 bytes .../test_get_public_key_confirm_refused/00004.png | Bin 0 -> 534 bytes .../nanosp/test_sign_tx_long_tx/00000.png | Bin 0 -> 400 bytes .../nanosp/test_sign_tx_long_tx/00001.png | Bin 0 -> 376 bytes .../nanosp/test_sign_tx_long_tx/00002.png | Bin 0 -> 780 bytes .../nanosp/test_sign_tx_long_tx/00003.png | Bin 0 -> 708 bytes .../nanosp/test_sign_tx_long_tx/00004.png | Bin 0 -> 703 bytes .../nanosp/test_sign_tx_long_tx/00005.png | Bin 0 -> 767 bytes .../nanosp/test_sign_tx_long_tx/00006.png | Bin 0 -> 759 bytes .../nanosp/test_sign_tx_long_tx/00007.png | Bin 0 -> 738 bytes .../nanosp/test_sign_tx_long_tx/00008.png | Bin 0 -> 753 bytes .../nanosp/test_sign_tx_long_tx/00009.png | Bin 0 -> 727 bytes .../nanosp/test_sign_tx_long_tx/00010.png | Bin 0 -> 570 bytes .../nanosp/test_sign_tx_long_tx/00011.png | Bin 0 -> 364 bytes .../nanosp/test_sign_tx_long_tx/00012.png | Bin 0 -> 534 bytes .../nanosp/test_sign_tx_refused/00000.png | Bin 0 -> 400 bytes .../nanosp/test_sign_tx_refused/00001.png | Bin 0 -> 376 bytes .../nanosp/test_sign_tx_refused/00002.png | Bin 0 -> 780 bytes .../nanosp/test_sign_tx_refused/00003.png | Bin 0 -> 636 bytes .../nanosp/test_sign_tx_refused/00004.png | Bin 0 -> 364 bytes .../nanosp/test_sign_tx_refused/00005.png | Bin 0 -> 365 bytes .../nanosp/test_sign_tx_refused/00006.png | Bin 0 -> 534 bytes .../nanosp/test_sign_tx_short_tx/00000.png | Bin 0 -> 400 bytes .../nanosp/test_sign_tx_short_tx/00001.png | Bin 0 -> 376 bytes .../nanosp/test_sign_tx_short_tx/00002.png | Bin 0 -> 780 bytes .../nanosp/test_sign_tx_short_tx/00003.png | Bin 0 -> 388 bytes .../nanosp/test_sign_tx_short_tx/00004.png | Bin 0 -> 364 bytes .../nanosp/test_sign_tx_short_tx/00005.png | Bin 0 -> 534 bytes tests/snapshots/nanox/test_app_mainmenu/00000.png | Bin 0 -> 534 bytes tests/snapshots/nanox/test_app_mainmenu/00001.png | Bin 0 -> 332 bytes tests/snapshots/nanox/test_app_mainmenu/00002.png | Bin 0 -> 350 bytes tests/snapshots/nanox/test_app_mainmenu/00003.png | Bin 0 -> 295 bytes .../00000.png | Bin 0 -> 401 bytes .../00001.png | Bin 0 -> 734 bytes .../00002.png | Bin 0 -> 364 bytes .../00003.png | Bin 0 -> 534 bytes .../test_get_public_key_confirm_refused/00000.png | Bin 0 -> 401 bytes .../test_get_public_key_confirm_refused/00001.png | Bin 0 -> 734 bytes .../test_get_public_key_confirm_refused/00002.png | Bin 0 -> 364 bytes .../test_get_public_key_confirm_refused/00003.png | Bin 0 -> 365 bytes .../test_get_public_key_confirm_refused/00004.png | Bin 0 -> 534 bytes .../nanox/test_sign_tx_long_tx/00000.png | Bin 0 -> 400 bytes .../nanox/test_sign_tx_long_tx/00001.png | Bin 0 -> 376 bytes .../nanox/test_sign_tx_long_tx/00002.png | Bin 0 -> 780 bytes .../nanox/test_sign_tx_long_tx/00003.png | Bin 0 -> 708 bytes .../nanox/test_sign_tx_long_tx/00004.png | Bin 0 -> 703 bytes .../nanox/test_sign_tx_long_tx/00005.png | Bin 0 -> 767 bytes .../nanox/test_sign_tx_long_tx/00006.png | Bin 0 -> 759 bytes .../nanox/test_sign_tx_long_tx/00007.png | Bin 0 -> 738 bytes .../nanox/test_sign_tx_long_tx/00008.png | Bin 0 -> 753 bytes .../nanox/test_sign_tx_long_tx/00009.png | Bin 0 -> 727 bytes .../nanox/test_sign_tx_long_tx/00010.png | Bin 0 -> 570 bytes .../nanox/test_sign_tx_long_tx/00011.png | Bin 0 -> 364 bytes .../nanox/test_sign_tx_long_tx/00012.png | Bin 0 -> 534 bytes .../nanox/test_sign_tx_refused/00000.png | Bin 0 -> 400 bytes .../nanox/test_sign_tx_refused/00001.png | Bin 0 -> 376 bytes .../nanox/test_sign_tx_refused/00002.png | Bin 0 -> 780 bytes .../nanox/test_sign_tx_refused/00003.png | Bin 0 -> 636 bytes .../nanox/test_sign_tx_refused/00004.png | Bin 0 -> 364 bytes .../nanox/test_sign_tx_refused/00005.png | Bin 0 -> 365 bytes .../nanox/test_sign_tx_refused/00006.png | Bin 0 -> 534 bytes .../nanox/test_sign_tx_short_tx/00000.png | Bin 0 -> 400 bytes .../nanox/test_sign_tx_short_tx/00001.png | Bin 0 -> 376 bytes .../nanox/test_sign_tx_short_tx/00002.png | Bin 0 -> 780 bytes .../nanox/test_sign_tx_short_tx/00003.png | Bin 0 -> 388 bytes .../nanox/test_sign_tx_short_tx/00004.png | Bin 0 -> 364 bytes .../nanox/test_sign_tx_short_tx/00005.png | Bin 0 -> 534 bytes 78 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/snapshots/nanosp/test_app_mainmenu/00000.png create mode 100644 tests/snapshots/nanosp/test_app_mainmenu/00001.png create mode 100644 tests/snapshots/nanosp/test_app_mainmenu/00002.png create mode 100644 tests/snapshots/nanosp/test_app_mainmenu/00003.png create mode 100644 tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00000.png create mode 100644 tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00001.png create mode 100644 tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00002.png create mode 100644 tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00003.png create mode 100644 tests/snapshots/nanosp/test_get_public_key_confirm_refused/00000.png create mode 100644 tests/snapshots/nanosp/test_get_public_key_confirm_refused/00001.png create mode 100644 tests/snapshots/nanosp/test_get_public_key_confirm_refused/00002.png create mode 100644 tests/snapshots/nanosp/test_get_public_key_confirm_refused/00003.png create mode 100644 tests/snapshots/nanosp/test_get_public_key_confirm_refused/00004.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_long_tx/00000.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_long_tx/00001.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_long_tx/00002.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_long_tx/00003.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_long_tx/00004.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_long_tx/00005.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_long_tx/00006.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_long_tx/00007.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_long_tx/00008.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_long_tx/00009.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_long_tx/00010.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_long_tx/00011.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_long_tx/00012.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_refused/00000.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_refused/00001.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_refused/00002.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_refused/00003.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_refused/00004.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_refused/00005.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_refused/00006.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_short_tx/00000.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_short_tx/00001.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_short_tx/00002.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_short_tx/00003.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_short_tx/00004.png create mode 100644 tests/snapshots/nanosp/test_sign_tx_short_tx/00005.png create mode 100644 tests/snapshots/nanox/test_app_mainmenu/00000.png create mode 100644 tests/snapshots/nanox/test_app_mainmenu/00001.png create mode 100644 tests/snapshots/nanox/test_app_mainmenu/00002.png create mode 100644 tests/snapshots/nanox/test_app_mainmenu/00003.png create mode 100644 tests/snapshots/nanox/test_get_public_key_confirm_accepted/00000.png create mode 100644 tests/snapshots/nanox/test_get_public_key_confirm_accepted/00001.png create mode 100644 tests/snapshots/nanox/test_get_public_key_confirm_accepted/00002.png create mode 100644 tests/snapshots/nanox/test_get_public_key_confirm_accepted/00003.png create mode 100644 tests/snapshots/nanox/test_get_public_key_confirm_refused/00000.png create mode 100644 tests/snapshots/nanox/test_get_public_key_confirm_refused/00001.png create mode 100644 tests/snapshots/nanox/test_get_public_key_confirm_refused/00002.png create mode 100644 tests/snapshots/nanox/test_get_public_key_confirm_refused/00003.png create mode 100644 tests/snapshots/nanox/test_get_public_key_confirm_refused/00004.png create mode 100644 tests/snapshots/nanox/test_sign_tx_long_tx/00000.png create mode 100644 tests/snapshots/nanox/test_sign_tx_long_tx/00001.png create mode 100644 tests/snapshots/nanox/test_sign_tx_long_tx/00002.png create mode 100644 tests/snapshots/nanox/test_sign_tx_long_tx/00003.png create mode 100644 tests/snapshots/nanox/test_sign_tx_long_tx/00004.png create mode 100644 tests/snapshots/nanox/test_sign_tx_long_tx/00005.png create mode 100644 tests/snapshots/nanox/test_sign_tx_long_tx/00006.png create mode 100644 tests/snapshots/nanox/test_sign_tx_long_tx/00007.png create mode 100644 tests/snapshots/nanox/test_sign_tx_long_tx/00008.png create mode 100644 tests/snapshots/nanox/test_sign_tx_long_tx/00009.png create mode 100644 tests/snapshots/nanox/test_sign_tx_long_tx/00010.png create mode 100644 tests/snapshots/nanox/test_sign_tx_long_tx/00011.png create mode 100644 tests/snapshots/nanox/test_sign_tx_long_tx/00012.png create mode 100644 tests/snapshots/nanox/test_sign_tx_refused/00000.png create mode 100644 tests/snapshots/nanox/test_sign_tx_refused/00001.png create mode 100644 tests/snapshots/nanox/test_sign_tx_refused/00002.png create mode 100644 tests/snapshots/nanox/test_sign_tx_refused/00003.png create mode 100644 tests/snapshots/nanox/test_sign_tx_refused/00004.png create mode 100644 tests/snapshots/nanox/test_sign_tx_refused/00005.png create mode 100644 tests/snapshots/nanox/test_sign_tx_refused/00006.png create mode 100644 tests/snapshots/nanox/test_sign_tx_short_tx/00000.png create mode 100644 tests/snapshots/nanox/test_sign_tx_short_tx/00001.png create mode 100644 tests/snapshots/nanox/test_sign_tx_short_tx/00002.png create mode 100644 tests/snapshots/nanox/test_sign_tx_short_tx/00003.png create mode 100644 tests/snapshots/nanox/test_sign_tx_short_tx/00004.png create mode 100644 tests/snapshots/nanox/test_sign_tx_short_tx/00005.png diff --git a/tests/snapshots/nanosp/test_app_mainmenu/00000.png b/tests/snapshots/nanosp/test_app_mainmenu/00000.png new file mode 100644 index 0000000000000000000000000000000000000000..03d57ea36b8e8693f9589fcfc605d14d1fda5f32 GIT binary patch literal 534 zcmV+x0_pvUP) zcR?})lgB8tzX#6&My#>XE(mDt4L~};6KM@6y$9FwB$h$y&ccsRd9;+1g zr$>|4*qTO9O_%iCZeX*W$XkEIsl8#O46nkO)TF4I{F#3h6P3y^B5kVwaq@Tr;u|d+ z0qOj6d)#zTY$RVaWV);8W(1fgw@e`4$Md`vNY87W=&3KsxMe*Wkz?=A17gV90%pj=F6C008fnbIv(Gw*PpltN2{K zfd7TL literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanosp/test_app_mainmenu/00002.png b/tests/snapshots/nanosp/test_app_mainmenu/00002.png new file mode 100644 index 0000000000000000000000000000000000000000..7e1a28c65f77030ad7ed3d1b28cd5e3f15ba7106 GIT binary patch literal 350 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|>}Bba4!+nDh2VAm1Sc0oOp2 zcP)SQGrv7@7dB#Ax;1NN`1_y2CW`q9TE3h>O%SkS)9p8`GKpz-KhF7lQv2tf+HXwv zwoUt2p|$2XQ2a_>+0ieP^B6B>Kk7Q0t{B5`_k!(1rUE9l%e;4m^%&F>jDP}rs}CH# zqJH3vO4+m*`~R8U`(0tR;2@W(*#2FmF5jmt^1HI6-YCmJI5uNh?}U{H{RQ>E8E($n zyhZuCz(=*1rhiF~5?`x-(_7IJ@h&D?`RS?0@9k2b98t~ZC}+BL?y-H?&7ThJ(tG}B zJ(tn?E3$#1`|Ofkx~gfDCAn`ki7d8JyT$Tg}e1&P( zg<|UtodolD7x+GLS#VfwY~I79_sp!SLEQG|Hc1jBSX&jXhZOiO$If8La?C5$3lMS!}X;D@pOa^?=`i~U4QWG?Z>UV_f6Yx>HmFs?Avuec&|kE+z!7#sp;BE zrAaS#>Re(}{cj7#JQX&x_4vf5E--%xaLJr>mdKI;Vst0FolN%m4rY literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00001.png b/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00001.png new file mode 100644 index 0000000000000000000000000000000000000000..cd4a07c9cb001b24ceb76d9a110fb75570ad0c80 GIT binary patch literal 734 zcmV<40wMj0P)HmajV-ZtfV zwpaneS>W8CE1~wdHKI=vzXzlnHq~5_dQOksdm;y*^t=uQXH0L6k$LLa>=}PjN?(C> z`!PY?C#~(&n$i1j!h{e)2qEtTJ%)BKjp8~}^qD$ESR(f$=KxyL)iY@KO$J|I))@|;#(6k{Eb&jE7?o3B4Z)y&nm^za5Yo>dS}8IH*lXkD|2l6{wP?Muin-h_vY(m%+#V z#XmPvf3#yMorpR(TAD9Ic1s(|rPk%73{{~5-Grkq4o}t_)yMT}C?YgYD~RBoynE9I z0dKiNn;WWY@cl&;o&ZUeC;tNFRCrp~Fnpycqxd%)f5$6Vp7nsl*u&>q0k z>wWZBz!}thX%GOc zz#OXk+DW~qq!=~;3pHx+f#J$K#0MfmM4i%FTDg#elW(ThNC+W>5JK+e7u5$A5xk#w QrvLx|07*qoM6N<$f*I&lx&QzG literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00002.png b/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00002.png new file mode 100644 index 0000000000000000000000000000000000000000..53ae65195fbea5b281a1bc1a80351dbf1cd79ff8 GIT binary patch literal 364 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|=-%ba4!+nDh2#AYZeBfa}G~ z%X2sUSKTFSd4J{-YHcY(QO5;J{<~e9q=7F?O4)!WO}6UUdH! zS+Hz=(4})vCM2-VD4nt3W%LdvkyzeG%km#`1t_mu*zR}#jNB{EKZnW}g5-=Bym-_% z`=7%C>zxJL|9msNSMXnbQRayy^)nr>EU7olTHqoc^vG(B%bo(Id*|kR{84!`z2x6n zZ`;PkbrPvd6U}A>Y0chs_tyHmLi6kw6c{>JvYiQ=+MTFTx8d*9^V|R0+_75oZt9or zLetinU!`YMvFyLCb8XxElhO0{v2rTkNR$jMW7~4}*8a52+MC^J%KMbxPN-JRb2RNP zEsp0ebAPS3=e2G53#Z$XZAI6Yd=Y zcR?})lgB8tzX#6&My#>XE(mDt4L~};6KM@6y$9FwB$h$y&ccsRd9;+1g zr$>|4*qTO9O_%iCZeX*W$XkEIsl8#O46nkO)TF4I{F#3h6P3y^B5kVwaq@Tr;u|d+ z0qOj6d)#zTY$RVaWV);8W(1fgw@e`4$Md`vNY87W=&3KsxMe*WkzjBSX&jXhZOiO$If8La?C5$3lMS!}X;D@pOa^?=`i~U4QWG?Z>UV_f6Yx>HmFs?Avuec&|kE+z!7#sp;BE zrAaS#>Re(}{cj7#JQX&x_4vf5E--%xaLJr>mdKI;Vst0FolN%m4rY literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00001.png b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00001.png new file mode 100644 index 0000000000000000000000000000000000000000..cd4a07c9cb001b24ceb76d9a110fb75570ad0c80 GIT binary patch literal 734 zcmV<40wMj0P)HmajV-ZtfV zwpaneS>W8CE1~wdHKI=vzXzlnHq~5_dQOksdm;y*^t=uQXH0L6k$LLa>=}PjN?(C> z`!PY?C#~(&n$i1j!h{e)2qEtTJ%)BKjp8~}^qD$ESR(f$=KxyL)iY@KO$J|I))@|;#(6k{Eb&jE7?o3B4Z)y&nm^za5Yo>dS}8IH*lXkD|2l6{wP?Muin-h_vY(m%+#V z#XmPvf3#yMorpR(TAD9Ic1s(|rPk%73{{~5-Grkq4o}t_)yMT}C?YgYD~RBoynE9I z0dKiNn;WWY@cl&;o&ZUeC;tNFRCrp~Fnpycqxd%)f5$6Vp7nsl*u&>q0k z>wWZBz!}thX%GOc zz#OXk+DW~qq!=~;3pHx+f#J$K#0MfmM4i%FTDg#elW(ThNC+W>5JK+e7u5$A5xk#w QrvLx|07*qoM6N<$f*I&lx&QzG literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00002.png b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00002.png new file mode 100644 index 0000000000000000000000000000000000000000..53ae65195fbea5b281a1bc1a80351dbf1cd79ff8 GIT binary patch literal 364 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|=-%ba4!+nDh2#AYZeBfa}G~ z%X2sUSKTFSd4J{-YHcY(QO5;J{<~e9q=7F?O4)!WO}6UUdH! zS+Hz=(4})vCM2-VD4nt3W%LdvkyzeG%km#`1t_mu*zR}#jNB{EKZnW}g5-=Bym-_% z`=7%C>zxJL|9msNSMXnbQRayy^)nr>EU7olTHqoc^vG(B%bo(Id*|kR{84!`z2x6n zZ`;PkbrPvd6U}A>Y0chs_tyHmLi6kw6c{>JvYiQ=+MTFTx8d*9^V|R0+_75oZt9or zLetinU!`YMvFyLCb8XxElhO0{v2rTkNR$jMW7~4}*8a52+MC^J%KMbxPN-JRb2RNP zEsp0ebAPS3=e2G53#Z$XZAI6Yd=YMjqtrV%Nn6esuj!jIMi}Btw`sohxwi1&U@}K{{10Q-!s%eVxF#kF6*2U FngGM>p?d%T literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00004.png b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00004.png new file mode 100644 index 0000000000000000000000000000000000000000..03d57ea36b8e8693f9589fcfc605d14d1fda5f32 GIT binary patch literal 534 zcmV+x0_pvUP) zcR?})lgB8tzX#6&My#>XE(mDt4L~};6KM@6y$9FwB$h$y&ccsRd9;+1g zr$>|4*qTO9O_%iCZeX*W$XkEIsl8#O46nkO)TF4I{F#3h6P3y^B5kVwaq@Tr;u|d+ z0qOj6d)#zTY$RVaWV);8W(1fgw@e`4$Md`vNY87W=&3KsxMe*Wkzx`jaa0T*bBnvOlWS@c?vzLbsnPhXB6 z9v^PwZ5X3VHu}#{{=7TzX1{CPb%sO&4ZsrjCWyNhSXSfX>?x>exvTMTJB~niKu<*G z|L(A>F#rGneBD$HJ(FHt*oSX_JiR{^ikW=XJQJ}xZ|dI%4Brqmsg~WQ-d(<8>pWYh zdYYO46AnuYcD=!9@bM(}!5c&=Ma=)^;f0)6pcgVCGE?yB=d08+MxX(h*Nj1GQKq0W uH=o#xXBpK**G1T90{{R30002s`ThYU_ZM+!KV4D)0000nY zo6R5ob3YaPtvy&xSZVFME%8zJpNqRXs(UQCAqCXNkTGNC%$4t*9=O*suia|q^naV- z3gdhGUrFCcP22lj*JtsDsmD1pd-FwqamN0<9P-6j!KU-yb2009j^(|J`Sl;p?k{>D zEB!Ytk-@Az$*F1N@2#YeeWfG z50S0&mRLSq6K=QA?riwN&x=j-1G0J6bsAns=9$v5Pv~N}(l3zzk-&k|e;8X`g*n&i R=k5aud%F6$taD0e0szLDpK|~J literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanosp/test_sign_tx_long_tx/00002.png b/tests/snapshots/nanosp/test_sign_tx_long_tx/00002.png new file mode 100644 index 0000000000000000000000000000000000000000..c42e54904c5bc77f5bd4d828e6c81574573f3318 GIT binary patch literal 780 zcmV+n1M~ceP)m_d zz12>7*`DTeRnS{7&`m zQKDc5?rkhrUM$*)upj>Vs&W02OBI{+FHQm@UHCizMWgb#zln`O!$wS*B{bm}O|N8KXMIrzHb zxnZXoiV@KVap}VJbygvrvVqTSembQa;Bmq_7bN}kv5WWygNmt zVAC7enMo>#faI>7H}@d*bC!FyB!g37?Z4rROvMN_qrVuA!A{&za|{;H2r|cDDh%)K zKx!zM`2oB6VTd=eizuVblEdPww`{I4SL2Zm>$>B3>RXpE32Mdt;}(0x*GN};B~siU z&7MyaCvojk_RReouiMaq;-cGt=-4IdjkSmu-dICj5dG8~0CTfO6BCb46zO)D-gb+eVVeUWW0TBUJlpGT{6P%l0wI2R5uyUC`SCU?_ z;3<9Vo!!DVHS|HZ8{}SJ#+_NFdbuSo^F8REFdzKj)<>>rRyRF(#x30I35eiH-(8y) z_hH*RU`s5PeFGZjYo?)1ITL(rYJQQKKfeJ@k9r3XLI@#*?B)+Ap=)rkbP7lS0000< KMNUMnLSTYO1#u$) literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanosp/test_sign_tx_long_tx/00003.png b/tests/snapshots/nanosp/test_sign_tx_long_tx/00003.png new file mode 100644 index 0000000000000000000000000000000000000000..6f31bc8d21ea32052da86fbbfc7dbb0ea484a92f GIT binary patch literal 708 zcmV;#0z3VQP)N`B@`(rL?DMq+j!Dego`T*0}#0_r$9=Tx^>bPm?W zbt&sI8djH1{tDpcU`YZr8*m9^BiW|7QLF(LthiDBKL7_kgZ*ph6!=Zzs=XN&u#` z1ONa42;ulox!AQFx|-iUJBu{KhEk0D2m%@(Aw_hx%;t@W%56h~^0^WEyI&hW%Hnb_ z7DZSAG%pQbSuZ3jp_Z;&p)T2e3UT`Z9K*e*u7PzFmd%980pJvWje$u(UN&#EVqD5h z;daXO2Gm%;scW^r1MS7D`8jx|4DW1$fi)y@Mcy82KYb6&X2}48``)SU$)mqzbRRKz zlcM{xH9j*2!3+i&F>nRL3zE1z_4M+|HS$kI1Y;dO?-!#9Qbbo-Z2C!0CYS47t+c*k zwI4Q>ok$G@DKvFe>ld!=%z)G0L^MD(w+M|a3qLn)7XRB#MHv(ojfzT~Ik7=RBN z$2!*s=Y97Tuo)v^)g8IpvQYN&()<{{Xot;004X;0b5HeJWV+X z5_j=yjo*>9YfNv$gr^``4IOHn!WmFb2j$F$=SPRaZTEq#_J8pIKAws2=B1G>tZ$ssUSkzC^iF@cvMZ#lA*%%7=5aImrMTq~2v8Od5lL6cNqz|DI+Q!NF?VUw4iJ00000008hdKC=T{*Hu28 z=P8xU2-*!-%1hyGL;5+dhc|z-1LRX@Q_6CoEJU$r&sHYAyq{>;fZA!!LB z%ZRib&tRX`h9z|dgyg+#VsS!j5*}}itiK1;OR({3B~LF>)xj-s%CnHZQUNRWfz0vq zMxq8_+z9{x0PuSBJLTy( z^d`K?t%*h2P+NtgKdt?jR6iY3cE3=GE$waTen^!uXg5K+-l;@!--L@#zSUM2*Kfn5 z8re?A69RFx)&u!C8WJXybbNTTB*oE()@ zQYp*z!4u^um_?(t+iZbRo;2F)20o2b@vL;iZr~<<1`OI9bA(U3lZ*S`f_a1ufN?%L z4!}mubW3PScAYm8>yIW9qIC8VGG*788SqpS)oW{4t!KB!`Dl@pRw4U01poj5007x6 zKeY41p+%zl^hh%ZiMTdO%x^^y)sU4_lvAOSAGWz-EJf`0^MQlrxQ% zifm~YTy;NEp^v=ZibzA>fXhE%s`G;bU_a2g!!oj+8akWZ4wy#HfL4a|X@|F;Zu7R* lexP&kR4V`g0000O;UDUZ)AtpuZ9o73002ovPDHLkV1h18LURBB literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanosp/test_sign_tx_long_tx/00005.png b/tests/snapshots/nanosp/test_sign_tx_long_tx/00005.png new file mode 100644 index 0000000000000000000000000000000000000000..62e3af35daa5c0e48b07f8ed7fc41702289c5120 GIT binary patch literal 767 zcmV3`I#QH|hHy$#=s)CU%!CH-uelVDWorpt?IS8OL$e06HFuV^Sev(gCTOUE#1b8qeMR2w9VlNO% z(k9nOyJ~3nNh&$3t<64@2~#9hM-<`EsCUjuq|FpRwN+`Tdr?N_cR;3ohk3N^b5JxG zDT~V?b4?Gcpz`}u^uF6|&y@ zR>xx^1h$)9Ny8jyJc@O9KzaQRcb8QZBP^)Raj=EWa{$f1#u3UMU8GYI(l{vRfNB$< zfjy8bO}&f20EE~90002iQZ9eS=QOv4rUtcF8 zNk*9ojdG@MF4ddXYt_pi@?87|Dz1k27lRn=M07P`uz-e>5re6~z2FR@*3a+VH+H{| z<*pvvnFs&d(Ia9Z>#-t*N@@iCtCTN0BUvcRRF72Ivw*crj7@>FX-z+6R4AMJsIe8@ zaxaI9aqN90|hY@QvFY4zUr zR<{sv2Phi=Zhg^x)?sb$Kt5ECwrHQmNa$a3U%O)fDPqxEp%MXiK-XrZaq8>JLeh?A pQz$KK>AR<0$}0c>0000`_yIPu_0&hSf2{xj002ovPDHLkV1iS(sfGKg8meUan3t7Jglv&uA0je)B(yc@?n=}IG8o(L{ChQ&QZRER5 z>3}Jo00000Te4)K(<_a4Y2`iu-?CG;6j6zh(w|AtGA|>#B4630V!g4VBn)j`QZ-7j z0k1b0*zG0#AYJSze_O^(1@RN)KHJvC%p{VjX^)_8L-=+ zlU}0jhS5)2=C8Wlrw@-WM3xn=yCwvUd zAAGF?#P)tI5KFiTGWx(3g!grzOLxJkCpPI|KQ~5CN1x=xC#w`DvrsCX-;%aDx-QH5 zpcm8keNrvU(3L{Hn4&?pnza~L%-+^(7e`z1J(}3zVpz_pB!Q-SCpzhVHnjE7Ys{pk z5@u7q6P;XtG;tw2NUbwxz{#DbY-*vhEVDXdRlZxoqz}?7mm5>m-$$~1w0;^y9{>OV z2&7Mt7%UcSY8sz9n6;f0ksV@pOeZ3`I#QH|hHy$#==Z6^dxGm;|=}TP1FRKv-aZ=uQa$000000Pr@xrq+HO$N9(S z^Ubyo~zGpiKrVM2&qAZ58jDEH$Yaq4F;Aggjlk;v8`@`?P z(zq*` z3;Y8aS=GB3H~>TH0RR91X5wG3s-8Gwmcd6YwsvP~yZWfmD2$OJO8v6-{lySF4P)ZM zu7o#+NF&-C9N!xV*w1Kz=v;=Gs9$O|*wUL_q_ZBUO+TSh#Hl!vVc6=0)6&XH*wptx#h`x z9O!f;hJ{uQW+Q|e(GLb=uo5-Y7=r*Vf{Zbk3c~viswB_huE`($Ro#rW?H=eorXvwj zghy6%%AA*G+^JSW)k)K~Qfn|I&!kTgwoQZZ>2YM3`J2(n_zW-(P7yrH$B?cb>>C4i z_DpJq$gC;oQqpnCiv0;r9C5$RXWWOd{%B&%TE{i;O&)T3Gr%KxCUb^0mNlo$X-Csu zggq{7$#%O%xQ$Kj$9SIh9kJVi0RR91@IIP#g-svWnwwQs@OEP7C1ZXog1=-sPgQbt z({nb&W30U&i5|u8?`a3f2A;|qi6}`XN2XU5Z1&NkltArTY#r`#*oPwRhpFF*2SqX8%(Hg)@p3_yA!QYW<>^zaE4j4BA z0002&V{j=uc_lo#ZShRH+7YuMO$zuPK!iztDr)YUoGDfp^?Gj8<3&?M#1}5@JAqwm zHKHELPeq-*s;n6$JG>YNHn#l{V#GM27!Q6+@>lN&K~t4$e2^hijV9vvS@V}~vft63 zrdoce7}xEIPpKn~9;|*W{G#3@yW_UC7vVu`JWE@&Js8V!%F>*Q7CO+p2%a9uvIq!{ z5dFmwKp*Tx4K?~;0hJ)552k|f-WsP`Fk)mEXSV7=v}#9wA~$Af!l#z2DU`O6(xa`6 za^qTxcopP0S3GySN~kM$>Tv4!X zYht&mW&Ch)MP`kFs;HT>jL(y;mO0!w-=|$wK*ijnZ-Pm4uDyDP*j&&4-O(Ev{B#<^ zl)ohBUD$Cu5&!@I5VS{&*^e9{g6rbvqA5>7_MhpXBA8dbp4P=8Fn$Z65t)?G0_QyC&iqIs|yKWVI z%^m@o(#g}Ei?FS3mbLgu;>BzswpFGD|J-%BTmwWO006)>_yb{e<6`NUL@odT002ov JPDHLkV1loaR2l#P literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanosp/test_sign_tx_long_tx/00010.png b/tests/snapshots/nanosp/test_sign_tx_long_tx/00010.png new file mode 100644 index 0000000000000000000000000000000000000000..9f73d2b56ee9b98bf3ee5aeaa95d7fcab2e64bc9 GIT binary patch literal 570 zcmV-A0>%A_P)08K)mzulAo00000;Ay<3j{g0A^M}vp zqg|OXshm#En49v&rdc`8JpN`+5KQUH&BfBoneuwmOm=bSJ$r&o&Zg$`$m}bP%U+Jl zD8RFWEM5Ux_HD~w)&Z?_R86H#b-h9})7FhID+5rvd+<4>nbFaRZW8Oss8mylC_z;j z(Q5HCiCY0KewSU21@Af`U%UsD^>=XXS4S7EDJjL>I16c|>EN?Hlkq@C4)rV?1K=_a z002N6Pc_t#9?y@=aj?VGL)+{F$-)DVrxYQRR8t_D-hqZD7|D}nAT z>*-3(@}ib^(qQC+i~TMGmvs~LJ|OAAXeLot<2*UP5B5H|_ueXt)~h2SW^2 zqPm(G1kiCZF_;SOeLUS1@XZll+29ul_Q>`9K2eHj%8J{csrc?wiKbm4b6iZ7H)Dvh z@@95F^Q9QnDZ7>uFD{(LOB>(^b07*qo IM6N<$f^G%~j{pDw literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanosp/test_sign_tx_long_tx/00011.png b/tests/snapshots/nanosp/test_sign_tx_long_tx/00011.png new file mode 100644 index 0000000000000000000000000000000000000000..53ae65195fbea5b281a1bc1a80351dbf1cd79ff8 GIT binary patch literal 364 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|=-%ba4!+nDh2#AYZeBfa}G~ z%X2sUSKTFSd4J{-YHcY(QO5;J{<~e9q=7F?O4)!WO}6UUdH! zS+Hz=(4})vCM2-VD4nt3W%LdvkyzeG%km#`1t_mu*zR}#jNB{EKZnW}g5-=Bym-_% z`=7%C>zxJL|9msNSMXnbQRayy^)nr>EU7olTHqoc^vG(B%bo(Id*|kR{84!`z2x6n zZ`;PkbrPvd6U}A>Y0chs_tyHmLi6kw6c{>JvYiQ=+MTFTx8d*9^V|R0+_75oZt9or zLetinU!`YMvFyLCb8XxElhO0{v2rTkNR$jMW7~4}*8a52+MC^J%KMbxPN-JRb2RNP zEsp0ebAPS3=e2G53#Z$XZAI6Yd=Y zcR?})lgB8tzX#6&My#>XE(mDt4L~};6KM@6y$9FwB$h$y&ccsRd9;+1g zr$>|4*qTO9O_%iCZeX*W$XkEIsl8#O46nkO)TF4I{F#3h6P3y^B5kVwaq@Tr;u|d+ z0qOj6d)#zTY$RVaWV);8W(1fgw@e`4$Md`vNY87W=&3KsxMe*Wkzx`jaa0T*bBnvOlWS@c?vzLbsnPhXB6 z9v^PwZ5X3VHu}#{{=7TzX1{CPb%sO&4ZsrjCWyNhSXSfX>?x>exvTMTJB~niKu<*G z|L(A>F#rGneBD$HJ(FHt*oSX_JiR{^ikW=XJQJ}xZ|dI%4Brqmsg~WQ-d(<8>pWYh zdYYO46AnuYcD=!9@bM(}!5c&=Ma=)^;f0)6pcgVCGE?yB=d08+MxX(h*Nj1GQKq0W uH=o#xXBpK**G1T90{{R30002s`ThYU_ZM+!KV4D)0000nY zo6R5ob3YaPtvy&xSZVFME%8zJpNqRXs(UQCAqCXNkTGNC%$4t*9=O*suia|q^naV- z3gdhGUrFCcP22lj*JtsDsmD1pd-FwqamN0<9P-6j!KU-yb2009j^(|J`Sl;p?k{>D zEB!Ytk-@Az$*F1N@2#YeeWfG z50S0&mRLSq6K=QA?riwN&x=j-1G0J6bsAns=9$v5Pv~N}(l3zzk-&k|e;8X`g*n&i R=k5aud%F6$taD0e0szLDpK|~J literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanosp/test_sign_tx_refused/00002.png b/tests/snapshots/nanosp/test_sign_tx_refused/00002.png new file mode 100644 index 0000000000000000000000000000000000000000..c42e54904c5bc77f5bd4d828e6c81574573f3318 GIT binary patch literal 780 zcmV+n1M~ceP)m_d zz12>7*`DTeRnS{7&`m zQKDc5?rkhrUM$*)upj>Vs&W02OBI{+FHQm@UHCizMWgb#zln`O!$wS*B{bm}O|N8KXMIrzHb zxnZXoiV@KVap}VJbygvrvVqTSembQa;Bmq_7bN}kv5WWygNmt zVAC7enMo>#faI>7H}@d*bC!FyB!g37?Z4rROvMN_qrVuA!A{&za|{;H2r|cDDh%)K zKx!zM`2oB6VTd=eizuVblEdPww`{I4SL2Zm>$>B3>RXpE32Mdt;}(0x*GN};B~siU z&7MyaCvojk_RReouiMaq;-cGt=-4IdjkSmu-dICj5dG8~0CTfO6BCb46zO)D-gb+eVVeUWW0TBUJlpGT{6P%l0wI2R5uyUC`SCU?_ z;3<9Vo!!DVHS|HZ8{}SJ#+_NFdbuSo^F8REFdzKj)<>>rRyRF(#x30I35eiH-(8y) z_hH*RU`s5PeFGZjYo?)1ITL(rYJQQKKfeJ@k9r3XLI@#*?B)+Ap=)rkbP7lS0000< KMNUMnLSTYO1#u$) literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanosp/test_sign_tx_refused/00003.png b/tests/snapshots/nanosp/test_sign_tx_refused/00003.png new file mode 100644 index 0000000000000000000000000000000000000000..8a821ad1a7884c1d566ef04169d1fa0255d795a1 GIT binary patch literal 636 zcmV-?0)zdDP)q~hZ46#W%1}_Lt z3P9~{qbqr}XfcSGf=C+SE(@4RhKV}^rudyVi59%L0T3yv+@6K%>>thh000000AQSa zq!fxZ*P7NrWZO)T2N-*v#a@Wu^lAG6L1w7IyL)ODC()$ zRVgPxEXdnlwjSJ#;A(h(F^GXD zO;;lZ3pjByVsI;P@0+kF5YtD5eY4)!+iA%~Zj>U{FF}{BV_+|A^{0M8n%`94qL}KP7!9~W#G{GH{A|P9KCOL-^#l!o zadrdZeYAlEmvl(<(6<3;+NC004f9Ab+ag zO1S?KdxO(!9DXZ;OEIk@n5`3;idH%xf4*^H)vI@kO?tIbdp|4jmm&t}by+C+lkms@ zbmfX@@+ZOVaPGc&yUZ7HFSd4J{-YHcY(QO5;J{<~e9q=7F?O4)!WO}6UUdH! zS+Hz=(4})vCM2-VD4nt3W%LdvkyzeG%km#`1t_mu*zR}#jNB{EKZnW}g5-=Bym-_% z`=7%C>zxJL|9msNSMXnbQRayy^)nr>EU7olTHqoc^vG(B%bo(Id*|kR{84!`z2x6n zZ`;PkbrPvd6U}A>Y0chs_tyHmLi6kw6c{>JvYiQ=+MTFTx8d*9^V|R0+_75oZt9or zLetinU!`YMvFyLCb8XxElhO0{v2rTkNR$jMW7~4}*8a52+MC^J%KMbxPN-JRb2RNP zEsp0ebAPS3=e2G53#Z$XZAI6Yd=YMjqtrV%Nn6esuj!jIMi}Btw`sohxwi1&U@}K{{10Q-!s%eVxF#kF6*2U FngGM>p?d%T literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanosp/test_sign_tx_refused/00006.png b/tests/snapshots/nanosp/test_sign_tx_refused/00006.png new file mode 100644 index 0000000000000000000000000000000000000000..03d57ea36b8e8693f9589fcfc605d14d1fda5f32 GIT binary patch literal 534 zcmV+x0_pvUP) zcR?})lgB8tzX#6&My#>XE(mDt4L~};6KM@6y$9FwB$h$y&ccsRd9;+1g zr$>|4*qTO9O_%iCZeX*W$XkEIsl8#O46nkO)TF4I{F#3h6P3y^B5kVwaq@Tr;u|d+ z0qOj6d)#zTY$RVaWV);8W(1fgw@e`4$Md`vNY87W=&3KsxMe*Wkzx`jaa0T*bBnvOlWS@c?vzLbsnPhXB6 z9v^PwZ5X3VHu}#{{=7TzX1{CPb%sO&4ZsrjCWyNhSXSfX>?x>exvTMTJB~niKu<*G z|L(A>F#rGneBD$HJ(FHt*oSX_JiR{^ikW=XJQJ}xZ|dI%4Brqmsg~WQ-d(<8>pWYh zdYYO46AnuYcD=!9@bM(}!5c&=Ma=)^;f0)6pcgVCGE?yB=d08+MxX(h*Nj1GQKq0W uH=o#xXBpK**G1T90{{R30002s`ThYU_ZM+!KV4D)0000nY zo6R5ob3YaPtvy&xSZVFME%8zJpNqRXs(UQCAqCXNkTGNC%$4t*9=O*suia|q^naV- z3gdhGUrFCcP22lj*JtsDsmD1pd-FwqamN0<9P-6j!KU-yb2009j^(|J`Sl;p?k{>D zEB!Ytk-@Az$*F1N@2#YeeWfG z50S0&mRLSq6K=QA?riwN&x=j-1G0J6bsAns=9$v5Pv~N}(l3zzk-&k|e;8X`g*n&i R=k5aud%F6$taD0e0szLDpK|~J literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanosp/test_sign_tx_short_tx/00002.png b/tests/snapshots/nanosp/test_sign_tx_short_tx/00002.png new file mode 100644 index 0000000000000000000000000000000000000000..c42e54904c5bc77f5bd4d828e6c81574573f3318 GIT binary patch literal 780 zcmV+n1M~ceP)m_d zz12>7*`DTeRnS{7&`m zQKDc5?rkhrUM$*)upj>Vs&W02OBI{+FHQm@UHCizMWgb#zln`O!$wS*B{bm}O|N8KXMIrzHb zxnZXoiV@KVap}VJbygvrvVqTSembQa;Bmq_7bN}kv5WWygNmt zVAC7enMo>#faI>7H}@d*bC!FyB!g37?Z4rROvMN_qrVuA!A{&za|{;H2r|cDDh%)K zKx!zM`2oB6VTd=eizuVblEdPww`{I4SL2Zm>$>B3>RXpE32Mdt;}(0x*GN};B~siU z&7MyaCvojk_RReouiMaq;-cGt=-4IdjkSmu-dICj5dG8~0CTfO6BCb46zO)D-gb+eVVeUWW0TBUJlpGT{6P%l0wI2R5uyUC`SCU?_ z;3<9Vo!!DVHS|HZ8{}SJ#+_NFdbuSo^F8REFdzKj)<>>rRyRF(#x30I35eiH-(8y) z_hH*RU`s5PeFGZjYo?)1ITL(rYJQQKKfeJ@k9r3XLI@#*?B)+Ap=)rkbP7lS0000< KMNUMnLSTYO1#u$) literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanosp/test_sign_tx_short_tx/00003.png b/tests/snapshots/nanosp/test_sign_tx_short_tx/00003.png new file mode 100644 index 0000000000000000000000000000000000000000..2a94e16584f17935aa0c958b658c61a97ee96243 GIT binary patch literal 388 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|{t2ba4!+nDh2#G+&DXk3(Sk zBflH}C*3$*Gee+-X&+bWp691Ev@$nx>2ZT}Z~xiz?m*x7y4wZkrhfY~^Zg6HJ$J2k zF&n+#Tdc?P|4G1&dve`73Tv*3^j}QzcjzeMsJ$Vt?rf8KQ~i9$Od)~x-w7I@)2k) zE6)z^x8CI^zlWQBS)qUIYWG)July9o6@Wx21Oc&@2s>yJu>- z##Zb|e4FamivhpBM(kV@!S7%Ca@pIsPq7le?{X|u`!#oB^Te)&YV5KmO+I`ff^k3> cA_IrB{OzrRmvv5tF@uCXUHx3vIVCg!09`7lP5=M^ literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanosp/test_sign_tx_short_tx/00004.png b/tests/snapshots/nanosp/test_sign_tx_short_tx/00004.png new file mode 100644 index 0000000000000000000000000000000000000000..53ae65195fbea5b281a1bc1a80351dbf1cd79ff8 GIT binary patch literal 364 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|=-%ba4!+nDh2#AYZeBfa}G~ z%X2sUSKTFSd4J{-YHcY(QO5;J{<~e9q=7F?O4)!WO}6UUdH! zS+Hz=(4})vCM2-VD4nt3W%LdvkyzeG%km#`1t_mu*zR}#jNB{EKZnW}g5-=Bym-_% z`=7%C>zxJL|9msNSMXnbQRayy^)nr>EU7olTHqoc^vG(B%bo(Id*|kR{84!`z2x6n zZ`;PkbrPvd6U}A>Y0chs_tyHmLi6kw6c{>JvYiQ=+MTFTx8d*9^V|R0+_75oZt9or zLetinU!`YMvFyLCb8XxElhO0{v2rTkNR$jMW7~4}*8a52+MC^J%KMbxPN-JRb2RNP zEsp0ebAPS3=e2G53#Z$XZAI6Yd=Y zcR?})lgB8tzX#6&My#>XE(mDt4L~};6KM@6y$9FwB$h$y&ccsRd9;+1g zr$>|4*qTO9O_%iCZeX*W$XkEIsl8#O46nkO)TF4I{F#3h6P3y^B5kVwaq@Tr;u|d+ z0qOj6d)#zTY$RVaWV);8W(1fgw@e`4$Md`vNY87W=&3KsxMe*Wkz zcR?})lgB8tzX#6&My#>XE(mDt4L~};6KM@6y$9FwB$h$y&ccsRd9;+1g zr$>|4*qTO9O_%iCZeX*W$XkEIsl8#O46nkO)TF4I{F#3h6P3y^B5kVwaq@Tr;u|d+ z0qOj6d)#zTY$RVaWV);8W(1fgw@e`4$Md`vNY87W=&3KsxMe*Wkz?=A17gV90%pj=F6C008fnbIv(Gw*PpltN2{K zfd7TL literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanox/test_app_mainmenu/00002.png b/tests/snapshots/nanox/test_app_mainmenu/00002.png new file mode 100644 index 0000000000000000000000000000000000000000..7e1a28c65f77030ad7ed3d1b28cd5e3f15ba7106 GIT binary patch literal 350 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|>}Bba4!+nDh2VAm1Sc0oOp2 zcP)SQGrv7@7dB#Ax;1NN`1_y2CW`q9TE3h>O%SkS)9p8`GKpz-KhF7lQv2tf+HXwv zwoUt2p|$2XQ2a_>+0ieP^B6B>Kk7Q0t{B5`_k!(1rUE9l%e;4m^%&F>jDP}rs}CH# zqJH3vO4+m*`~R8U`(0tR;2@W(*#2FmF5jmt^1HI6-YCmJI5uNh?}U{H{RQ>E8E($n zyhZuCz(=*1rhiF~5?`x-(_7IJ@h&D?`RS?0@9k2b98t~ZC}+BL?y-H?&7ThJ(tG}B zJ(tn?E3$#1`|Ofkx~gfDCAn`ki7d8JyT$Tg}e1&P( zg<|UtodolD7x+GLS#VfwY~I79_sp!SLEQG|Hc1jBSX&jXhZOiO$If8La?C5$3lMS!}X;D@pOa^?=`i~U4QWG?Z>UV_f6Yx>HmFs?Avuec&|kE+z!7#sp;BE zrAaS#>Re(}{cj7#JQX&x_4vf5E--%xaLJr>mdKI;Vst0FolN%m4rY literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanox/test_get_public_key_confirm_accepted/00001.png b/tests/snapshots/nanox/test_get_public_key_confirm_accepted/00001.png new file mode 100644 index 0000000000000000000000000000000000000000..cd4a07c9cb001b24ceb76d9a110fb75570ad0c80 GIT binary patch literal 734 zcmV<40wMj0P)HmajV-ZtfV zwpaneS>W8CE1~wdHKI=vzXzlnHq~5_dQOksdm;y*^t=uQXH0L6k$LLa>=}PjN?(C> z`!PY?C#~(&n$i1j!h{e)2qEtTJ%)BKjp8~}^qD$ESR(f$=KxyL)iY@KO$J|I))@|;#(6k{Eb&jE7?o3B4Z)y&nm^za5Yo>dS}8IH*lXkD|2l6{wP?Muin-h_vY(m%+#V z#XmPvf3#yMorpR(TAD9Ic1s(|rPk%73{{~5-Grkq4o}t_)yMT}C?YgYD~RBoynE9I z0dKiNn;WWY@cl&;o&ZUeC;tNFRCrp~Fnpycqxd%)f5$6Vp7nsl*u&>q0k z>wWZBz!}thX%GOc zz#OXk+DW~qq!=~;3pHx+f#J$K#0MfmM4i%FTDg#elW(ThNC+W>5JK+e7u5$A5xk#w QrvLx|07*qoM6N<$f*I&lx&QzG literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanox/test_get_public_key_confirm_accepted/00002.png b/tests/snapshots/nanox/test_get_public_key_confirm_accepted/00002.png new file mode 100644 index 0000000000000000000000000000000000000000..53ae65195fbea5b281a1bc1a80351dbf1cd79ff8 GIT binary patch literal 364 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|=-%ba4!+nDh2#AYZeBfa}G~ z%X2sUSKTFSd4J{-YHcY(QO5;J{<~e9q=7F?O4)!WO}6UUdH! zS+Hz=(4})vCM2-VD4nt3W%LdvkyzeG%km#`1t_mu*zR}#jNB{EKZnW}g5-=Bym-_% z`=7%C>zxJL|9msNSMXnbQRayy^)nr>EU7olTHqoc^vG(B%bo(Id*|kR{84!`z2x6n zZ`;PkbrPvd6U}A>Y0chs_tyHmLi6kw6c{>JvYiQ=+MTFTx8d*9^V|R0+_75oZt9or zLetinU!`YMvFyLCb8XxElhO0{v2rTkNR$jMW7~4}*8a52+MC^J%KMbxPN-JRb2RNP zEsp0ebAPS3=e2G53#Z$XZAI6Yd=Y zcR?})lgB8tzX#6&My#>XE(mDt4L~};6KM@6y$9FwB$h$y&ccsRd9;+1g zr$>|4*qTO9O_%iCZeX*W$XkEIsl8#O46nkO)TF4I{F#3h6P3y^B5kVwaq@Tr;u|d+ z0qOj6d)#zTY$RVaWV);8W(1fgw@e`4$Md`vNY87W=&3KsxMe*WkzjBSX&jXhZOiO$If8La?C5$3lMS!}X;D@pOa^?=`i~U4QWG?Z>UV_f6Yx>HmFs?Avuec&|kE+z!7#sp;BE zrAaS#>Re(}{cj7#JQX&x_4vf5E--%xaLJr>mdKI;Vst0FolN%m4rY literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanox/test_get_public_key_confirm_refused/00001.png b/tests/snapshots/nanox/test_get_public_key_confirm_refused/00001.png new file mode 100644 index 0000000000000000000000000000000000000000..cd4a07c9cb001b24ceb76d9a110fb75570ad0c80 GIT binary patch literal 734 zcmV<40wMj0P)HmajV-ZtfV zwpaneS>W8CE1~wdHKI=vzXzlnHq~5_dQOksdm;y*^t=uQXH0L6k$LLa>=}PjN?(C> z`!PY?C#~(&n$i1j!h{e)2qEtTJ%)BKjp8~}^qD$ESR(f$=KxyL)iY@KO$J|I))@|;#(6k{Eb&jE7?o3B4Z)y&nm^za5Yo>dS}8IH*lXkD|2l6{wP?Muin-h_vY(m%+#V z#XmPvf3#yMorpR(TAD9Ic1s(|rPk%73{{~5-Grkq4o}t_)yMT}C?YgYD~RBoynE9I z0dKiNn;WWY@cl&;o&ZUeC;tNFRCrp~Fnpycqxd%)f5$6Vp7nsl*u&>q0k z>wWZBz!}thX%GOc zz#OXk+DW~qq!=~;3pHx+f#J$K#0MfmM4i%FTDg#elW(ThNC+W>5JK+e7u5$A5xk#w QrvLx|07*qoM6N<$f*I&lx&QzG literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanox/test_get_public_key_confirm_refused/00002.png b/tests/snapshots/nanox/test_get_public_key_confirm_refused/00002.png new file mode 100644 index 0000000000000000000000000000000000000000..53ae65195fbea5b281a1bc1a80351dbf1cd79ff8 GIT binary patch literal 364 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|=-%ba4!+nDh2#AYZeBfa}G~ z%X2sUSKTFSd4J{-YHcY(QO5;J{<~e9q=7F?O4)!WO}6UUdH! zS+Hz=(4})vCM2-VD4nt3W%LdvkyzeG%km#`1t_mu*zR}#jNB{EKZnW}g5-=Bym-_% z`=7%C>zxJL|9msNSMXnbQRayy^)nr>EU7olTHqoc^vG(B%bo(Id*|kR{84!`z2x6n zZ`;PkbrPvd6U}A>Y0chs_tyHmLi6kw6c{>JvYiQ=+MTFTx8d*9^V|R0+_75oZt9or zLetinU!`YMvFyLCb8XxElhO0{v2rTkNR$jMW7~4}*8a52+MC^J%KMbxPN-JRb2RNP zEsp0ebAPS3=e2G53#Z$XZAI6Yd=YMjqtrV%Nn6esuj!jIMi}Btw`sohxwi1&U@}K{{10Q-!s%eVxF#kF6*2U FngGM>p?d%T literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanox/test_get_public_key_confirm_refused/00004.png b/tests/snapshots/nanox/test_get_public_key_confirm_refused/00004.png new file mode 100644 index 0000000000000000000000000000000000000000..03d57ea36b8e8693f9589fcfc605d14d1fda5f32 GIT binary patch literal 534 zcmV+x0_pvUP) zcR?})lgB8tzX#6&My#>XE(mDt4L~};6KM@6y$9FwB$h$y&ccsRd9;+1g zr$>|4*qTO9O_%iCZeX*W$XkEIsl8#O46nkO)TF4I{F#3h6P3y^B5kVwaq@Tr;u|d+ z0qOj6d)#zTY$RVaWV);8W(1fgw@e`4$Md`vNY87W=&3KsxMe*Wkzx`jaa0T*bBnvOlWS@c?vzLbsnPhXB6 z9v^PwZ5X3VHu}#{{=7TzX1{CPb%sO&4ZsrjCWyNhSXSfX>?x>exvTMTJB~niKu<*G z|L(A>F#rGneBD$HJ(FHt*oSX_JiR{^ikW=XJQJ}xZ|dI%4Brqmsg~WQ-d(<8>pWYh zdYYO46AnuYcD=!9@bM(}!5c&=Ma=)^;f0)6pcgVCGE?yB=d08+MxX(h*Nj1GQKq0W uH=o#xXBpK**G1T90{{R30002s`ThYU_ZM+!KV4D)0000nY zo6R5ob3YaPtvy&xSZVFME%8zJpNqRXs(UQCAqCXNkTGNC%$4t*9=O*suia|q^naV- z3gdhGUrFCcP22lj*JtsDsmD1pd-FwqamN0<9P-6j!KU-yb2009j^(|J`Sl;p?k{>D zEB!Ytk-@Az$*F1N@2#YeeWfG z50S0&mRLSq6K=QA?riwN&x=j-1G0J6bsAns=9$v5Pv~N}(l3zzk-&k|e;8X`g*n&i R=k5aud%F6$taD0e0szLDpK|~J literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanox/test_sign_tx_long_tx/00002.png b/tests/snapshots/nanox/test_sign_tx_long_tx/00002.png new file mode 100644 index 0000000000000000000000000000000000000000..c42e54904c5bc77f5bd4d828e6c81574573f3318 GIT binary patch literal 780 zcmV+n1M~ceP)m_d zz12>7*`DTeRnS{7&`m zQKDc5?rkhrUM$*)upj>Vs&W02OBI{+FHQm@UHCizMWgb#zln`O!$wS*B{bm}O|N8KXMIrzHb zxnZXoiV@KVap}VJbygvrvVqTSembQa;Bmq_7bN}kv5WWygNmt zVAC7enMo>#faI>7H}@d*bC!FyB!g37?Z4rROvMN_qrVuA!A{&za|{;H2r|cDDh%)K zKx!zM`2oB6VTd=eizuVblEdPww`{I4SL2Zm>$>B3>RXpE32Mdt;}(0x*GN};B~siU z&7MyaCvojk_RReouiMaq;-cGt=-4IdjkSmu-dICj5dG8~0CTfO6BCb46zO)D-gb+eVVeUWW0TBUJlpGT{6P%l0wI2R5uyUC`SCU?_ z;3<9Vo!!DVHS|HZ8{}SJ#+_NFdbuSo^F8REFdzKj)<>>rRyRF(#x30I35eiH-(8y) z_hH*RU`s5PeFGZjYo?)1ITL(rYJQQKKfeJ@k9r3XLI@#*?B)+Ap=)rkbP7lS0000< KMNUMnLSTYO1#u$) literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanox/test_sign_tx_long_tx/00003.png b/tests/snapshots/nanox/test_sign_tx_long_tx/00003.png new file mode 100644 index 0000000000000000000000000000000000000000..6f31bc8d21ea32052da86fbbfc7dbb0ea484a92f GIT binary patch literal 708 zcmV;#0z3VQP)N`B@`(rL?DMq+j!Dego`T*0}#0_r$9=Tx^>bPm?W zbt&sI8djH1{tDpcU`YZr8*m9^BiW|7QLF(LthiDBKL7_kgZ*ph6!=Zzs=XN&u#` z1ONa42;ulox!AQFx|-iUJBu{KhEk0D2m%@(Aw_hx%;t@W%56h~^0^WEyI&hW%Hnb_ z7DZSAG%pQbSuZ3jp_Z;&p)T2e3UT`Z9K*e*u7PzFmd%980pJvWje$u(UN&#EVqD5h z;daXO2Gm%;scW^r1MS7D`8jx|4DW1$fi)y@Mcy82KYb6&X2}48``)SU$)mqzbRRKz zlcM{xH9j*2!3+i&F>nRL3zE1z_4M+|HS$kI1Y;dO?-!#9Qbbo-Z2C!0CYS47t+c*k zwI4Q>ok$G@DKvFe>ld!=%z)G0L^MD(w+M|a3qLn)7XRB#MHv(ojfzT~Ik7=RBN z$2!*s=Y97Tuo)v^)g8IpvQYN&()<{{Xot;004X;0b5HeJWV+X z5_j=yjo*>9YfNv$gr^``4IOHn!WmFb2j$F$=SPRaZTEq#_J8pIKAws2=B1G>tZ$ssUSkzC^iF@cvMZ#lA*%%7=5aImrMTq~2v8Od5lL6cNqz|DI+Q!NF?VUw4iJ00000008hdKC=T{*Hu28 z=P8xU2-*!-%1hyGL;5+dhc|z-1LRX@Q_6CoEJU$r&sHYAyq{>;fZA!!LB z%ZRib&tRX`h9z|dgyg+#VsS!j5*}}itiK1;OR({3B~LF>)xj-s%CnHZQUNRWfz0vq zMxq8_+z9{x0PuSBJLTy( z^d`K?t%*h2P+NtgKdt?jR6iY3cE3=GE$waTen^!uXg5K+-l;@!--L@#zSUM2*Kfn5 z8re?A69RFx)&u!C8WJXybbNTTB*oE()@ zQYp*z!4u^um_?(t+iZbRo;2F)20o2b@vL;iZr~<<1`OI9bA(U3lZ*S`f_a1ufN?%L z4!}mubW3PScAYm8>yIW9qIC8VGG*788SqpS)oW{4t!KB!`Dl@pRw4U01poj5007x6 zKeY41p+%zl^hh%ZiMTdO%x^^y)sU4_lvAOSAGWz-EJf`0^MQlrxQ% zifm~YTy;NEp^v=ZibzA>fXhE%s`G;bU_a2g!!oj+8akWZ4wy#HfL4a|X@|F;Zu7R* lexP&kR4V`g0000O;UDUZ)AtpuZ9o73002ovPDHLkV1h18LURBB literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanox/test_sign_tx_long_tx/00005.png b/tests/snapshots/nanox/test_sign_tx_long_tx/00005.png new file mode 100644 index 0000000000000000000000000000000000000000..62e3af35daa5c0e48b07f8ed7fc41702289c5120 GIT binary patch literal 767 zcmV3`I#QH|hHy$#=s)CU%!CH-uelVDWorpt?IS8OL$e06HFuV^Sev(gCTOUE#1b8qeMR2w9VlNO% z(k9nOyJ~3nNh&$3t<64@2~#9hM-<`EsCUjuq|FpRwN+`Tdr?N_cR;3ohk3N^b5JxG zDT~V?b4?Gcpz`}u^uF6|&y@ zR>xx^1h$)9Ny8jyJc@O9KzaQRcb8QZBP^)Raj=EWa{$f1#u3UMU8GYI(l{vRfNB$< zfjy8bO}&f20EE~90002iQZ9eS=QOv4rUtcF8 zNk*9ojdG@MF4ddXYt_pi@?87|Dz1k27lRn=M07P`uz-e>5re6~z2FR@*3a+VH+H{| z<*pvvnFs&d(Ia9Z>#-t*N@@iCtCTN0BUvcRRF72Ivw*crj7@>FX-z+6R4AMJsIe8@ zaxaI9aqN90|hY@QvFY4zUr zR<{sv2Phi=Zhg^x)?sb$Kt5ECwrHQmNa$a3U%O)fDPqxEp%MXiK-XrZaq8>JLeh?A pQz$KK>AR<0$}0c>0000`_yIPu_0&hSf2{xj002ovPDHLkV1iS(sfGKg8meUan3t7Jglv&uA0je)B(yc@?n=}IG8o(L{ChQ&QZRER5 z>3}Jo00000Te4)K(<_a4Y2`iu-?CG;6j6zh(w|AtGA|>#B4630V!g4VBn)j`QZ-7j z0k1b0*zG0#AYJSze_O^(1@RN)KHJvC%p{VjX^)_8L-=+ zlU}0jhS5)2=C8Wlrw@-WM3xn=yCwvUd zAAGF?#P)tI5KFiTGWx(3g!grzOLxJkCpPI|KQ~5CN1x=xC#w`DvrsCX-;%aDx-QH5 zpcm8keNrvU(3L{Hn4&?pnza~L%-+^(7e`z1J(}3zVpz_pB!Q-SCpzhVHnjE7Ys{pk z5@u7q6P;XtG;tw2NUbwxz{#DbY-*vhEVDXdRlZxoqz}?7mm5>m-$$~1w0;^y9{>OV z2&7Mt7%UcSY8sz9n6;f0ksV@pOeZ3`I#QH|hHy$#==Z6^dxGm;|=}TP1FRKv-aZ=uQa$000000Pr@xrq+HO$N9(S z^Ubyo~zGpiKrVM2&qAZ58jDEH$Yaq4F;Aggjlk;v8`@`?P z(zq*` z3;Y8aS=GB3H~>TH0RR91X5wG3s-8Gwmcd6YwsvP~yZWfmD2$OJO8v6-{lySF4P)ZM zu7o#+NF&-C9N!xV*w1Kz=v;=Gs9$O|*wUL_q_ZBUO+TSh#Hl!vVc6=0)6&XH*wptx#h`x z9O!f;hJ{uQW+Q|e(GLb=uo5-Y7=r*Vf{Zbk3c~viswB_huE`($Ro#rW?H=eorXvwj zghy6%%AA*G+^JSW)k)K~Qfn|I&!kTgwoQZZ>2YM3`J2(n_zW-(P7yrH$B?cb>>C4i z_DpJq$gC;oQqpnCiv0;r9C5$RXWWOd{%B&%TE{i;O&)T3Gr%KxCUb^0mNlo$X-Csu zggq{7$#%O%xQ$Kj$9SIh9kJVi0RR91@IIP#g-svWnwwQs@OEP7C1ZXog1=-sPgQbt z({nb&W30U&i5|u8?`a3f2A;|qi6}`XN2XU5Z1&NkltArTY#r`#*oPwRhpFF*2SqX8%(Hg)@p3_yA!QYW<>^zaE4j4BA z0002&V{j=uc_lo#ZShRH+7YuMO$zuPK!iztDr)YUoGDfp^?Gj8<3&?M#1}5@JAqwm zHKHELPeq-*s;n6$JG>YNHn#l{V#GM27!Q6+@>lN&K~t4$e2^hijV9vvS@V}~vft63 zrdoce7}xEIPpKn~9;|*W{G#3@yW_UC7vVu`JWE@&Js8V!%F>*Q7CO+p2%a9uvIq!{ z5dFmwKp*Tx4K?~;0hJ)552k|f-WsP`Fk)mEXSV7=v}#9wA~$Af!l#z2DU`O6(xa`6 za^qTxcopP0S3GySN~kM$>Tv4!X zYht&mW&Ch)MP`kFs;HT>jL(y;mO0!w-=|$wK*ijnZ-Pm4uDyDP*j&&4-O(Ev{B#<^ zl)ohBUD$Cu5&!@I5VS{&*^e9{g6rbvqA5>7_MhpXBA8dbp4P=8Fn$Z65t)?G0_QyC&iqIs|yKWVI z%^m@o(#g}Ei?FS3mbLgu;>BzswpFGD|J-%BTmwWO006)>_yb{e<6`NUL@odT002ov JPDHLkV1loaR2l#P literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanox/test_sign_tx_long_tx/00010.png b/tests/snapshots/nanox/test_sign_tx_long_tx/00010.png new file mode 100644 index 0000000000000000000000000000000000000000..9f73d2b56ee9b98bf3ee5aeaa95d7fcab2e64bc9 GIT binary patch literal 570 zcmV-A0>%A_P)08K)mzulAo00000;Ay<3j{g0A^M}vp zqg|OXshm#En49v&rdc`8JpN`+5KQUH&BfBoneuwmOm=bSJ$r&o&Zg$`$m}bP%U+Jl zD8RFWEM5Ux_HD~w)&Z?_R86H#b-h9})7FhID+5rvd+<4>nbFaRZW8Oss8mylC_z;j z(Q5HCiCY0KewSU21@Af`U%UsD^>=XXS4S7EDJjL>I16c|>EN?Hlkq@C4)rV?1K=_a z002N6Pc_t#9?y@=aj?VGL)+{F$-)DVrxYQRR8t_D-hqZD7|D}nAT z>*-3(@}ib^(qQC+i~TMGmvs~LJ|OAAXeLot<2*UP5B5H|_ueXt)~h2SW^2 zqPm(G1kiCZF_;SOeLUS1@XZll+29ul_Q>`9K2eHj%8J{csrc?wiKbm4b6iZ7H)Dvh z@@95F^Q9QnDZ7>uFD{(LOB>(^b07*qo IM6N<$f^G%~j{pDw literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanox/test_sign_tx_long_tx/00011.png b/tests/snapshots/nanox/test_sign_tx_long_tx/00011.png new file mode 100644 index 0000000000000000000000000000000000000000..53ae65195fbea5b281a1bc1a80351dbf1cd79ff8 GIT binary patch literal 364 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|=-%ba4!+nDh2#AYZeBfa}G~ z%X2sUSKTFSd4J{-YHcY(QO5;J{<~e9q=7F?O4)!WO}6UUdH! zS+Hz=(4})vCM2-VD4nt3W%LdvkyzeG%km#`1t_mu*zR}#jNB{EKZnW}g5-=Bym-_% z`=7%C>zxJL|9msNSMXnbQRayy^)nr>EU7olTHqoc^vG(B%bo(Id*|kR{84!`z2x6n zZ`;PkbrPvd6U}A>Y0chs_tyHmLi6kw6c{>JvYiQ=+MTFTx8d*9^V|R0+_75oZt9or zLetinU!`YMvFyLCb8XxElhO0{v2rTkNR$jMW7~4}*8a52+MC^J%KMbxPN-JRb2RNP zEsp0ebAPS3=e2G53#Z$XZAI6Yd=Y zcR?})lgB8tzX#6&My#>XE(mDt4L~};6KM@6y$9FwB$h$y&ccsRd9;+1g zr$>|4*qTO9O_%iCZeX*W$XkEIsl8#O46nkO)TF4I{F#3h6P3y^B5kVwaq@Tr;u|d+ z0qOj6d)#zTY$RVaWV);8W(1fgw@e`4$Md`vNY87W=&3KsxMe*Wkzx`jaa0T*bBnvOlWS@c?vzLbsnPhXB6 z9v^PwZ5X3VHu}#{{=7TzX1{CPb%sO&4ZsrjCWyNhSXSfX>?x>exvTMTJB~niKu<*G z|L(A>F#rGneBD$HJ(FHt*oSX_JiR{^ikW=XJQJ}xZ|dI%4Brqmsg~WQ-d(<8>pWYh zdYYO46AnuYcD=!9@bM(}!5c&=Ma=)^;f0)6pcgVCGE?yB=d08+MxX(h*Nj1GQKq0W uH=o#xXBpK**G1T90{{R30002s`ThYU_ZM+!KV4D)0000nY zo6R5ob3YaPtvy&xSZVFME%8zJpNqRXs(UQCAqCXNkTGNC%$4t*9=O*suia|q^naV- z3gdhGUrFCcP22lj*JtsDsmD1pd-FwqamN0<9P-6j!KU-yb2009j^(|J`Sl;p?k{>D zEB!Ytk-@Az$*F1N@2#YeeWfG z50S0&mRLSq6K=QA?riwN&x=j-1G0J6bsAns=9$v5Pv~N}(l3zzk-&k|e;8X`g*n&i R=k5aud%F6$taD0e0szLDpK|~J literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanox/test_sign_tx_refused/00002.png b/tests/snapshots/nanox/test_sign_tx_refused/00002.png new file mode 100644 index 0000000000000000000000000000000000000000..c42e54904c5bc77f5bd4d828e6c81574573f3318 GIT binary patch literal 780 zcmV+n1M~ceP)m_d zz12>7*`DTeRnS{7&`m zQKDc5?rkhrUM$*)upj>Vs&W02OBI{+FHQm@UHCizMWgb#zln`O!$wS*B{bm}O|N8KXMIrzHb zxnZXoiV@KVap}VJbygvrvVqTSembQa;Bmq_7bN}kv5WWygNmt zVAC7enMo>#faI>7H}@d*bC!FyB!g37?Z4rROvMN_qrVuA!A{&za|{;H2r|cDDh%)K zKx!zM`2oB6VTd=eizuVblEdPww`{I4SL2Zm>$>B3>RXpE32Mdt;}(0x*GN};B~siU z&7MyaCvojk_RReouiMaq;-cGt=-4IdjkSmu-dICj5dG8~0CTfO6BCb46zO)D-gb+eVVeUWW0TBUJlpGT{6P%l0wI2R5uyUC`SCU?_ z;3<9Vo!!DVHS|HZ8{}SJ#+_NFdbuSo^F8REFdzKj)<>>rRyRF(#x30I35eiH-(8y) z_hH*RU`s5PeFGZjYo?)1ITL(rYJQQKKfeJ@k9r3XLI@#*?B)+Ap=)rkbP7lS0000< KMNUMnLSTYO1#u$) literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanox/test_sign_tx_refused/00003.png b/tests/snapshots/nanox/test_sign_tx_refused/00003.png new file mode 100644 index 0000000000000000000000000000000000000000..8a821ad1a7884c1d566ef04169d1fa0255d795a1 GIT binary patch literal 636 zcmV-?0)zdDP)q~hZ46#W%1}_Lt z3P9~{qbqr}XfcSGf=C+SE(@4RhKV}^rudyVi59%L0T3yv+@6K%>>thh000000AQSa zq!fxZ*P7NrWZO)T2N-*v#a@Wu^lAG6L1w7IyL)ODC()$ zRVgPxEXdnlwjSJ#;A(h(F^GXD zO;;lZ3pjByVsI;P@0+kF5YtD5eY4)!+iA%~Zj>U{FF}{BV_+|A^{0M8n%`94qL}KP7!9~W#G{GH{A|P9KCOL-^#l!o zadrdZeYAlEmvl(<(6<3;+NC004f9Ab+ag zO1S?KdxO(!9DXZ;OEIk@n5`3;idH%xf4*^H)vI@kO?tIbdp|4jmm&t}by+C+lkms@ zbmfX@@+ZOVaPGc&yUZ7HFSd4J{-YHcY(QO5;J{<~e9q=7F?O4)!WO}6UUdH! zS+Hz=(4})vCM2-VD4nt3W%LdvkyzeG%km#`1t_mu*zR}#jNB{EKZnW}g5-=Bym-_% z`=7%C>zxJL|9msNSMXnbQRayy^)nr>EU7olTHqoc^vG(B%bo(Id*|kR{84!`z2x6n zZ`;PkbrPvd6U}A>Y0chs_tyHmLi6kw6c{>JvYiQ=+MTFTx8d*9^V|R0+_75oZt9or zLetinU!`YMvFyLCb8XxElhO0{v2rTkNR$jMW7~4}*8a52+MC^J%KMbxPN-JRb2RNP zEsp0ebAPS3=e2G53#Z$XZAI6Yd=YMjqtrV%Nn6esuj!jIMi}Btw`sohxwi1&U@}K{{10Q-!s%eVxF#kF6*2U FngGM>p?d%T literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanox/test_sign_tx_refused/00006.png b/tests/snapshots/nanox/test_sign_tx_refused/00006.png new file mode 100644 index 0000000000000000000000000000000000000000..03d57ea36b8e8693f9589fcfc605d14d1fda5f32 GIT binary patch literal 534 zcmV+x0_pvUP) zcR?})lgB8tzX#6&My#>XE(mDt4L~};6KM@6y$9FwB$h$y&ccsRd9;+1g zr$>|4*qTO9O_%iCZeX*W$XkEIsl8#O46nkO)TF4I{F#3h6P3y^B5kVwaq@Tr;u|d+ z0qOj6d)#zTY$RVaWV);8W(1fgw@e`4$Md`vNY87W=&3KsxMe*Wkzx`jaa0T*bBnvOlWS@c?vzLbsnPhXB6 z9v^PwZ5X3VHu}#{{=7TzX1{CPb%sO&4ZsrjCWyNhSXSfX>?x>exvTMTJB~niKu<*G z|L(A>F#rGneBD$HJ(FHt*oSX_JiR{^ikW=XJQJ}xZ|dI%4Brqmsg~WQ-d(<8>pWYh zdYYO46AnuYcD=!9@bM(}!5c&=Ma=)^;f0)6pcgVCGE?yB=d08+MxX(h*Nj1GQKq0W uH=o#xXBpK**G1T90{{R30002s`ThYU_ZM+!KV4D)0000nY zo6R5ob3YaPtvy&xSZVFME%8zJpNqRXs(UQCAqCXNkTGNC%$4t*9=O*suia|q^naV- z3gdhGUrFCcP22lj*JtsDsmD1pd-FwqamN0<9P-6j!KU-yb2009j^(|J`Sl;p?k{>D zEB!Ytk-@Az$*F1N@2#YeeWfG z50S0&mRLSq6K=QA?riwN&x=j-1G0J6bsAns=9$v5Pv~N}(l3zzk-&k|e;8X`g*n&i R=k5aud%F6$taD0e0szLDpK|~J literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanox/test_sign_tx_short_tx/00002.png b/tests/snapshots/nanox/test_sign_tx_short_tx/00002.png new file mode 100644 index 0000000000000000000000000000000000000000..c42e54904c5bc77f5bd4d828e6c81574573f3318 GIT binary patch literal 780 zcmV+n1M~ceP)m_d zz12>7*`DTeRnS{7&`m zQKDc5?rkhrUM$*)upj>Vs&W02OBI{+FHQm@UHCizMWgb#zln`O!$wS*B{bm}O|N8KXMIrzHb zxnZXoiV@KVap}VJbygvrvVqTSembQa;Bmq_7bN}kv5WWygNmt zVAC7enMo>#faI>7H}@d*bC!FyB!g37?Z4rROvMN_qrVuA!A{&za|{;H2r|cDDh%)K zKx!zM`2oB6VTd=eizuVblEdPww`{I4SL2Zm>$>B3>RXpE32Mdt;}(0x*GN};B~siU z&7MyaCvojk_RReouiMaq;-cGt=-4IdjkSmu-dICj5dG8~0CTfO6BCb46zO)D-gb+eVVeUWW0TBUJlpGT{6P%l0wI2R5uyUC`SCU?_ z;3<9Vo!!DVHS|HZ8{}SJ#+_NFdbuSo^F8REFdzKj)<>>rRyRF(#x30I35eiH-(8y) z_hH*RU`s5PeFGZjYo?)1ITL(rYJQQKKfeJ@k9r3XLI@#*?B)+Ap=)rkbP7lS0000< KMNUMnLSTYO1#u$) literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanox/test_sign_tx_short_tx/00003.png b/tests/snapshots/nanox/test_sign_tx_short_tx/00003.png new file mode 100644 index 0000000000000000000000000000000000000000..2a94e16584f17935aa0c958b658c61a97ee96243 GIT binary patch literal 388 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|{t2ba4!+nDh2#G+&DXk3(Sk zBflH}C*3$*Gee+-X&+bWp691Ev@$nx>2ZT}Z~xiz?m*x7y4wZkrhfY~^Zg6HJ$J2k zF&n+#Tdc?P|4G1&dve`73Tv*3^j}QzcjzeMsJ$Vt?rf8KQ~i9$Od)~x-w7I@)2k) zE6)z^x8CI^zlWQBS)qUIYWG)July9o6@Wx21Oc&@2s>yJu>- z##Zb|e4FamivhpBM(kV@!S7%Ca@pIsPq7le?{X|u`!#oB^Te)&YV5KmO+I`ff^k3> cA_IrB{OzrRmvv5tF@uCXUHx3vIVCg!09`7lP5=M^ literal 0 HcmV?d00001 diff --git a/tests/snapshots/nanox/test_sign_tx_short_tx/00004.png b/tests/snapshots/nanox/test_sign_tx_short_tx/00004.png new file mode 100644 index 0000000000000000000000000000000000000000..53ae65195fbea5b281a1bc1a80351dbf1cd79ff8 GIT binary patch literal 364 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h6`U|=-%ba4!+nDh2#AYZeBfa}G~ z%X2sUSKTFSd4J{-YHcY(QO5;J{<~e9q=7F?O4)!WO}6UUdH! zS+Hz=(4})vCM2-VD4nt3W%LdvkyzeG%km#`1t_mu*zR}#jNB{EKZnW}g5-=Bym-_% z`=7%C>zxJL|9msNSMXnbQRayy^)nr>EU7olTHqoc^vG(B%bo(Id*|kR{84!`z2x6n zZ`;PkbrPvd6U}A>Y0chs_tyHmLi6kw6c{>JvYiQ=+MTFTx8d*9^V|R0+_75oZt9or zLetinU!`YMvFyLCb8XxElhO0{v2rTkNR$jMW7~4}*8a52+MC^J%KMbxPN-JRb2RNP zEsp0ebAPS3=e2G53#Z$XZAI6Yd=Y zcR?})lgB8tzX#6&My#>XE(mDt4L~};6KM@6y$9FwB$h$y&ccsRd9;+1g zr$>|4*qTO9O_%iCZeX*W$XkEIsl8#O46nkO)TF4I{F#3h6P3y^B5kVwaq@Tr;u|d+ z0qOj6d)#zTY$RVaWV);8W(1fgw@e`4$Md`vNY87W=&3KsxMe*Wkz Date: Wed, 15 Nov 2023 15:20:27 +0100 Subject: [PATCH 10/15] Remove unnecessary fields from config.toml --- .cargo/config.toml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index edf4752..c233973 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -7,8 +7,3 @@ target = "nanosplus" [unstable] build-std = ["core"] build-std-features = ["compiler-builtins-mem"] -host-config = true -target-applies-to-host = true - -[host] -rustflags = ["-Ctarget-feature=-crt-static"] \ No newline at end of file From b1343726810385535028e1fa876c5ee051b0e8a7 Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Wed, 15 Nov 2023 15:23:07 +0100 Subject: [PATCH 11/15] Remove local version of nanos_ui in Cargo.toml --- Cargo.lock | 3 +-- Cargo.toml | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76d750e..c86f508 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,11 +224,10 @@ dependencies = [ [[package]] name = "nanos_ui" version = "0.2.0" +source = "git+https://github.com/LedgerHQ/ledger-nanos-ui.git#18dc6694bf63d8229ceb4494ccb239a380435ece" dependencies = [ "include_gif", "ledger-sdk-sys", - "nanos_sdk", - "numtoa", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index dbdf0d1..5db596a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,6 @@ nanos_ui = { git = "https://github.com/LedgerHQ/ledger-nanos-ui.git" } include_gif = { git = "https://github.com/LedgerHQ/sdk_include_gif" } numtoa = "0.2.4" -[patch."https://github.com/LedgerHQ/ledger-nanos-ui"] -nanos_ui = { path = "ledger-nanos-ui" } - [profile.release] opt-level = 'z' lto = true From b57d5bdbd8cffb6238b5cc440baec06f9ec4e328 Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Wed, 15 Nov 2023 15:25:15 +0100 Subject: [PATCH 12/15] Format --- src/app_ui/address.rs | 10 +++------- src/app_ui/sign.rs | 3 ++- src/handlers/sign_tx.rs | 10 +++++----- src/utils.rs | 8 ++++---- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/app_ui/address.rs b/src/app_ui/address.rs index 1534985..933ffd4 100644 --- a/src/app_ui/address.rs +++ b/src/app_ui/address.rs @@ -26,17 +26,13 @@ use nanos_ui::ui::{Field, MultiFieldReview}; const DISPLAY_ADDR_BYTES_LEN: usize = 20; pub fn ui_display_pk(addr: &[u8]) -> Result { - let addr_hex_str_buf = - to_hex_all_caps(&addr[addr.len() - DISPLAY_ADDR_BYTES_LEN as usize..]) - .map_err(|_| Reply(SW_DISPLAY_ADDRESS_FAIL))?; + let addr_hex_str_buf = to_hex_all_caps(&addr[addr.len() - DISPLAY_ADDR_BYTES_LEN as usize..]) + .map_err(|_| Reply(SW_DISPLAY_ADDRESS_FAIL))?; let addr_hex_str = from_utf8(&addr_hex_str_buf[..DISPLAY_ADDR_BYTES_LEN * 2]) .map_err(|_| Reply(SW_DISPLAY_ADDRESS_FAIL))?; let mut addr_hex_str_with_prefix_buf = [0u8; DISPLAY_ADDR_BYTES_LEN * 2 + 2]; - concatenate( - &["0x", &addr_hex_str], - &mut addr_hex_str_with_prefix_buf, - ); + concatenate(&["0x", &addr_hex_str], &mut addr_hex_str_with_prefix_buf); let addr_hex_str_with_prefix = from_utf8(&addr_hex_str_with_prefix_buf).map_err(|_| Reply(SW_DISPLAY_ADDRESS_FAIL))?; diff --git a/src/app_ui/sign.rs b/src/app_ui/sign.rs index 758f623..e3a8d4f 100644 --- a/src/app_ui/sign.rs +++ b/src/app_ui/sign.rs @@ -46,7 +46,8 @@ pub fn ui_display_tx(tx: &Tx) -> Result { from_utf8(&addr_with_prefix_buf).map_err(|_| Reply(SW_TX_DISPLAY_FAIL))?; // Format memo - let memo_str = from_utf8(&tx.memo[..tx.memo_len as usize]).map_err(|_| Reply(SW_TX_DISPLAY_FAIL))?; + let memo_str = + from_utf8(&tx.memo[..tx.memo_len as usize]).map_err(|_| Reply(SW_TX_DISPLAY_FAIL))?; // Define transaction review fields let my_fields = [ diff --git a/src/handlers/sign_tx.rs b/src/handlers/sign_tx.rs index d9ff288..2846121 100644 --- a/src/handlers/sign_tx.rs +++ b/src/handlers/sign_tx.rs @@ -15,7 +15,7 @@ * limitations under the License. *****************************************************************************/ use crate::app_ui::sign::ui_display_tx; -use crate::utils::{read_bip32_path, varint_read, slice_or_err, MAX_ALLOWED_PATH_LEN}; +use crate::utils::{read_bip32_path, slice_or_err, varint_read, MAX_ALLOWED_PATH_LEN}; use crate::{SW_DENY, SW_TX_HASH_FAIL, SW_TX_PARSING_FAIL, SW_TX_SIGN_FAIL, SW_WRONG_TX_LENGTH}; use nanos_sdk::bindings::{ cx_hash_no_throw, cx_hash_t, cx_keccak_init_no_throw, cx_sha3_t, CX_LAST, CX_OK, @@ -50,15 +50,15 @@ impl<'a> TryFrom<&'a [u8]> for Tx<'a> { let value = u64::from_be_bytes(slice_or_err(raw_tx, 28, 8)?.try_into().map_err(|_| ())?); // Memo length let (memo_len_u64, memo_len_size) = varint_read(&raw_tx[36..])?; - let memo_len = memo_len_u64 as usize; + let memo_len = memo_len_u64 as usize; // Memo let memo = slice_or_err(raw_tx, 36 + memo_len_size, memo_len)?; - + // Check memo ASCII encoding if !memo[..memo_len].iter().all(|&byte| byte.is_ascii()) { return Err(()); } - + Ok(Tx { nonce, value, @@ -148,7 +148,7 @@ fn compute_signature_and_append(comm: &mut Comm, ctx: &mut TxContext) -> Result< let mut message_hash: [u8; 32] = [0u8; 32]; unsafe { - if cx_keccak_init_no_throw(&mut keccak256, 256) != CX_OK { + if cx_keccak_init_no_throw(&mut keccak256, 256) != CX_OK { return Err(Reply(SW_TX_HASH_FAIL)); } if cx_hash_no_throw( diff --git a/src/utils.rs b/src/utils.rs index ee44916..366d650 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -96,14 +96,14 @@ pub fn slice_or_err(slice: &[u8], start: usize, len: usize) -> Result<&[u8], ()> } /// Read a varint from a slice -pub fn varint_read(input: &[u8]) -> Result<(u64, usize), ()> { +pub fn varint_read(input: &[u8]) -> Result<(u64, usize), ()> { let mut bytes = [0u8; 8]; let int_length: usize; if input.is_empty() { return Err(()); } - + let prefix = input[0]; if prefix == 0xFD { @@ -122,11 +122,11 @@ pub fn varint_read(input: &[u8]) -> Result<(u64, usize), ()> { } int_length = 8; } else { - return Ok((u64::from(prefix), 1)); + return Ok((u64::from(prefix), 1)); } let buf = slice_or_err(input, 1, int_length)?; bytes[..int_length].copy_from_slice(buf); let result = u64::from_le_bytes(bytes); Ok((result, int_length + 1)) -} \ No newline at end of file +} From a132cd4f292edf27065d96048b34994d80182051 Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Thu, 16 Nov 2023 09:57:49 +0100 Subject: [PATCH 13/15] Use dependencies from the new SDK workspace. Code cleanup. --- Cargo.lock | 117 +++++++++++++++++++-------------- Cargo.toml | 11 ++-- src/app_ui/address.rs | 6 +- src/app_ui/menu.rs | 12 ++-- src/app_ui/sign.rs | 7 +- src/handlers/get_public_key.rs | 12 ++-- src/handlers/get_version.rs | 2 +- src/handlers/sign_tx.rs | 10 +-- src/main.rs | 45 +++++++------ src/utils.rs | 5 +- 10 files changed, 120 insertions(+), 107 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c86f508..aaa22e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,8 +16,9 @@ name = "app-boilerplate-rust" version = "1.0.0" dependencies = [ "include_gif", - "nanos_sdk", - "nanos_ui", + "ledger_device_sdk", + "ledger_device_ui_sdk", + "ledger_secure_sdk_sys", "numtoa", ] @@ -46,7 +47,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.20", + "syn 2.0.39", "which", ] @@ -64,9 +65,12 @@ checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cexpr" @@ -108,9 +112,9 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "errno" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" dependencies = [ "libc", "windows-sys", @@ -143,8 +147,9 @@ dependencies = [ [[package]] name = "include_gif" -version = "0.1.0" -source = "git+https://github.com/LedgerHQ/sdk_include_gif#699d28c6157518c4493899e2eeaa8af08346e5e7" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b17d2390c93f5d739a6df5584cdb20f45ff9f1bd3c2d5644eadd8a7dfd576e1" dependencies = [ "gif", "syn 1.0.109", @@ -163,9 +168,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] -name = "ledger-sdk-sys" -version = "0.2.0" -source = "git+https://github.com/LedgerHQ/secure-sdk-rust#d9a9868320a0d27194d93c7b4c55f48f2300abdc" +name = "ledger_device_sdk" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc0bff405870d65947aff3d60f7c3ea908aaeb953c0577de7528424c51baa79" +dependencies = [ + "ledger_secure_sdk_sys", + "num-traits", + "rand_core", + "zeroize", +] + +[[package]] +name = "ledger_device_ui_sdk" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "541fedd3af199deb197aa7153aade1f2c2024ebcb6c99b5beff0a317dc725310" +dependencies = [ + "include_gif", + "ledger_device_sdk", + "ledger_secure_sdk_sys", + "numtoa", +] + +[[package]] +name = "ledger_secure_sdk_sys" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd205aff8bb68ea83fbc55e69af8b4867e473142a747d85731a4a497c4f39045" dependencies = [ "bindgen", "cc", @@ -173,9 +203,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.149" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libloading" @@ -189,9 +219,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "log" @@ -211,25 +241,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "nanos_sdk" -version = "0.2.1" -source = "git+https://github.com/LedgerHQ/ledger-nanos-sdk.git#4d9bfc6183d94cee6edb239c39286be3825cc179" -dependencies = [ - "cc", - "num-traits", - "rand_core", -] - -[[package]] -name = "nanos_ui" -version = "0.2.0" -source = "git+https://github.com/LedgerHQ/ledger-nanos-ui.git#18dc6694bf63d8229ceb4494ccb239a380435ece" -dependencies = [ - "include_gif", - "ledger-sdk-sys", -] - [[package]] name = "nom" version = "7.1.3" @@ -242,9 +253,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -269,28 +280,28 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "prettyplease" -version = "0.2.9" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9825a04601d60621feed79c4e6b56d65db77cdca55cef43b46b0de1096d1c282" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.20", + "syn 2.0.39", ] [[package]] name = "proc-macro2" -version = "1.0.60" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.28" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -338,9 +349,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.20" +version = "0.38.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" +checksum = "9ad981d6c340a49cdc40a1028d9c6084ec7e9fa33fcb839cab656a267071e234" dependencies = [ "bitflags 2.4.1", "errno", @@ -368,9 +379,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.20" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb8d4cebc40aa517dfb69618fa647a346562e67228e2236ae0042ee6ac14775" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -379,9 +390,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "weezl" @@ -488,3 +499,9 @@ name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "zeroize" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12a3946ecfc929b583800f4629b6c25b88ac6e92a40ea5670f77112a85d40a8b" diff --git a/Cargo.toml b/Cargo.toml index 5db596a..2916ed2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,9 +5,10 @@ authors = ["yhql", "agrojean-ledger"] edition = "2021" [dependencies] -nanos_sdk = { git = "https://github.com/LedgerHQ/ledger-nanos-sdk.git" } -nanos_ui = { git = "https://github.com/LedgerHQ/ledger-nanos-ui.git" } -include_gif = { git = "https://github.com/LedgerHQ/sdk_include_gif" } +ledger_device_sdk = "1.0.0" +ledger_device_ui_sdk = "1.1.0" +ledger_secure_sdk_sys = "1.0.0" +include_gif = "1.0.0" numtoa = "0.2.4" [profile.release] @@ -15,7 +16,7 @@ opt-level = 'z' lto = true [features] -pending_review_screen = ["nanos_sdk/pending_review_screen"] +pending_review_screen = ["ledger_device_sdk/pending_review_screen"] [package.metadata.ledger] curve = ["secp256k1"] @@ -29,4 +30,4 @@ icon = "crab.gif" icon = "crab_14x14.gif" [package.metadata.ledger.nanosplus] -icon = "crab_14x14.gif" \ No newline at end of file +icon = "crab_14x14.gif" diff --git a/src/app_ui/address.rs b/src/app_ui/address.rs index 933ffd4..1c653d7 100644 --- a/src/app_ui/address.rs +++ b/src/app_ui/address.rs @@ -18,9 +18,9 @@ use crate::utils::{concatenate, to_hex_all_caps}; use crate::SW_DISPLAY_ADDRESS_FAIL; use core::str::from_utf8; -use nanos_sdk::io::Reply; -use nanos_ui::bitmaps::{CROSSMARK, EYE, VALIDATE_14}; -use nanos_ui::ui::{Field, MultiFieldReview}; +use ledger_device_sdk::io::Reply; +use ledger_device_ui_sdk::bitmaps::{CROSSMARK, EYE, VALIDATE_14}; +use ledger_device_ui_sdk::ui::{Field, MultiFieldReview}; // Display only the last 20 bytes of the address const DISPLAY_ADDR_BYTES_LEN: usize = 20; diff --git a/src/app_ui/menu.rs b/src/app_ui/menu.rs index 52a2695..ac39c3e 100644 --- a/src/app_ui/menu.rs +++ b/src/app_ui/menu.rs @@ -16,11 +16,11 @@ *****************************************************************************/ use include_gif::include_gif; -use nanos_sdk::io; -use nanos_ui::bitmaps::{Glyph, BACK, CERTIFICATE, DASHBOARD_X}; -use nanos_ui::ui::{EventOrPageIndex, MultiPageMenu, Page}; +use ledger_device_sdk::io::{ApduHeader, Comm, Event}; +use ledger_device_ui_sdk::bitmaps::{Glyph, BACK, CERTIFICATE, DASHBOARD_X}; +use ledger_device_ui_sdk::ui::{EventOrPageIndex, MultiPageMenu, Page}; -fn ui_about_menu(comm: &mut io::Comm) -> io::Event { +fn ui_about_menu(comm: &mut Comm) -> Event { let pages = [ &Page::from((["Rust Boilerplate", "(c) 2023 Ledger"], true)), &Page::from(("Back", &BACK)), @@ -36,7 +36,7 @@ fn ui_about_menu(comm: &mut io::Comm) -> io::Event { } } -pub fn ui_menu_main(comm: &mut io::Comm) -> io::Event { +pub fn ui_menu_main(comm: &mut Comm) -> Event { const APP_ICON: Glyph = Glyph::from_include(include_gif!("crab.gif")); let pages = [ // The from trait allows to create different styles of pages @@ -51,7 +51,7 @@ pub fn ui_menu_main(comm: &mut io::Comm) -> io::Event { EventOrPageIndex::Event(e) => return e, i => match i { EventOrPageIndex::Index(2) => return ui_about_menu(comm), - EventOrPageIndex::Index(3) => nanos_sdk::exit_app(0), + EventOrPageIndex::Index(3) => ledger_device_sdk::exit_app(0), _ => (), }, } diff --git a/src/app_ui/sign.rs b/src/app_ui/sign.rs index e3a8d4f..7749568 100644 --- a/src/app_ui/sign.rs +++ b/src/app_ui/sign.rs @@ -19,11 +19,10 @@ use crate::handlers::sign_tx::Tx; use crate::utils::{concatenate, to_hex_all_caps}; use crate::SW_TX_DISPLAY_FAIL; use core::str::from_utf8; -use nanos_sdk::io::Reply; -use nanos_ui::bitmaps::{CROSSMARK, EYE, VALIDATE_14}; -use nanos_ui::ui::{Field, MultiFieldReview}; +use ledger_device_sdk::io::Reply; +use ledger_device_ui_sdk::bitmaps::{CROSSMARK, EYE, VALIDATE_14}; +use ledger_device_ui_sdk::ui::{Field, MultiFieldReview}; use numtoa::NumToA; -// use nanos_sdk::testing; pub fn ui_display_tx(tx: &Tx) -> Result { // Format amount value diff --git a/src/handlers/get_public_key.rs b/src/handlers/get_public_key.rs index 322b15a..ba234ea 100644 --- a/src/handlers/get_public_key.rs +++ b/src/handlers/get_public_key.rs @@ -18,12 +18,12 @@ use crate::app_ui::address::ui_display_pk; use crate::utils::{read_bip32_path, MAX_ALLOWED_PATH_LEN}; use crate::{SW_DENY, SW_DISPLAY_ADDRESS_FAIL}; -use nanos_sdk::bindings::{ +use ledger_device_sdk::ecc::{Secp256k1, SeedDerive}; +use ledger_device_sdk::io::{Comm, Reply}; +use ledger_device_sdk::testing; +use ledger_secure_sdk_sys::{ cx_hash_no_throw, cx_hash_t, cx_keccak_init_no_throw, cx_sha3_t, CX_LAST, CX_OK, }; -use nanos_sdk::ecc::{Secp256k1, SeedDerive}; -use nanos_sdk::io::{Comm, Reply}; -use nanos_sdk::testing; pub fn handler_get_public_key(comm: &mut Comm, display: bool) -> Result<(), Reply> { let mut path = [0u32; MAX_ALLOWED_PATH_LEN]; @@ -51,9 +51,9 @@ pub fn handler_get_public_key(comm: &mut Comm, display: bool) -> Result<(), Repl &mut keccak256.header as *mut cx_hash_t, CX_LAST, pk_ptr, - 64 as u32, + 64 as usize, address.as_mut_ptr(), - address.len() as u32, + address.len(), ) != CX_OK { return Err(Reply(SW_DISPLAY_ADDRESS_FAIL)); diff --git a/src/handlers/get_version.rs b/src/handlers/get_version.rs index fe46938..4200286 100644 --- a/src/handlers/get_version.rs +++ b/src/handlers/get_version.rs @@ -16,7 +16,7 @@ *****************************************************************************/ use core::str::FromStr; -use nanos_sdk::io; +use ledger_device_sdk::io; pub fn handler_get_version(comm: &mut io::Comm) -> Result<(), io::Reply> { if let Some((major, minor, patch)) = parse_version_string(env!("CARGO_PKG_VERSION")) { diff --git a/src/handlers/sign_tx.rs b/src/handlers/sign_tx.rs index 2846121..b82d764 100644 --- a/src/handlers/sign_tx.rs +++ b/src/handlers/sign_tx.rs @@ -17,11 +17,11 @@ use crate::app_ui::sign::ui_display_tx; use crate::utils::{read_bip32_path, slice_or_err, varint_read, MAX_ALLOWED_PATH_LEN}; use crate::{SW_DENY, SW_TX_HASH_FAIL, SW_TX_PARSING_FAIL, SW_TX_SIGN_FAIL, SW_WRONG_TX_LENGTH}; -use nanos_sdk::bindings::{ +use ledger_device_sdk::ecc::{Secp256k1, SeedDerive}; +use ledger_device_sdk::io::{Comm, Reply}; +use ledger_secure_sdk_sys::{ cx_hash_no_throw, cx_hash_t, cx_keccak_init_no_throw, cx_sha3_t, CX_LAST, CX_OK, }; -use nanos_sdk::ecc::{Secp256k1, SeedDerive}; -use nanos_sdk::io::{Comm, Reply}; const MAX_TRANSACTION_LEN: usize = 510; @@ -155,9 +155,9 @@ fn compute_signature_and_append(comm: &mut Comm, ctx: &mut TxContext) -> Result< &mut keccak256.header as *mut cx_hash_t, CX_LAST, ctx.raw_tx.as_ptr(), - ctx.raw_tx_len as u32, + ctx.raw_tx_len, message_hash.as_mut_ptr(), - message_hash.len() as u32, + message_hash.len(), ) != CX_OK { return Err(Reply(SW_TX_HASH_FAIL)); diff --git a/src/main.rs b/src/main.rs index f61a22b..13016bc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,9 +13,10 @@ mod handlers { pub mod sign_tx; } -use nanos_sdk::buttons::ButtonEvent; -use nanos_sdk::io; -use nanos_ui::ui; +use ledger_device_sdk::buttons::ButtonEvent; +use ledger_device_sdk::io::{ApduHeader, Comm, Event, Reply, StatusWords}; + +use ledger_device_ui_sdk::ui; use app_ui::menu::ui_menu_main; use handlers::{ @@ -24,7 +25,7 @@ use handlers::{ sign_tx::{handler_sign_tx, TxContext}, }; -nanos_sdk::set_panic!(nanos_sdk::exiting_panic); +ledger_device_sdk::set_panic!(ledger_device_sdk::exiting_panic); pub const SW_INS_NOT_SUPPORTED: u16 = 0x6D00; pub const SW_DENY: u16 = 0x6985; @@ -39,19 +40,19 @@ pub const SW_TX_SIGN_FAIL: u16 = 0xB008; #[no_mangle] extern "C" fn sample_pending() { - let mut comm = io::Comm::new(); + let mut comm = Comm::new(); loop { ui::SingleMessage::new("Pending").show(); match comm.next_event::() { - io::Event::Button(ButtonEvent::RightButtonRelease) => break, + Event::Button(ButtonEvent::RightButtonRelease) => break, _ => (), } } loop { ui::SingleMessage::new("Ledger review").show(); match comm.next_event::() { - io::Event::Button(ButtonEvent::BothButtonsRelease) => break, + Event::Button(ButtonEvent::BothButtonsRelease) => break, _ => (), } } @@ -59,14 +60,14 @@ extern "C" fn sample_pending() { #[no_mangle] extern "C" fn sample_main() { - let mut comm = io::Comm::new(); + let mut comm = Comm::new(); let mut tx_ctx = TxContext::new(); loop { // Wait for either a specific button push to exit the app // or an APDU command match ui_menu_main(&mut comm) { - io::Event::Command(ins) => match handle_apdu(&mut comm, ins.into(), &mut tx_ctx) { + Event::Command(ins) => match handle_apdu(&mut comm, ins.into(), &mut tx_ctx) { Ok(()) => comm.reply_ok(), Err(sw) => comm.reply(sw), }, @@ -96,8 +97,8 @@ const P1_SIGN_TX_START: u8 = 0x00; // P1 for maximum APDU number. const P1_SIGN_TX_MAX: u8 = 0x03; -impl From for Ins { - fn from(header: io::ApduHeader) -> Ins { +impl From for Ins { + fn from(header: ApduHeader) -> Ins { match header.ins { 3 => Ins::GetVersion, 4 => Ins::GetAppName, @@ -108,39 +109,37 @@ impl From for Ins { } } -use nanos_sdk::io::Reply; - -fn handle_apdu(comm: &mut io::Comm, ins: Ins, ctx: &mut TxContext) -> Result<(), Reply> { +fn handle_apdu(comm: &mut Comm, ins: Ins, ctx: &mut TxContext) -> Result<(), Reply> { if comm.rx == 0 { - return Err(io::StatusWords::NothingReceived.into()); + return Err(StatusWords::NothingReceived.into()); } let apdu_metadata = comm.get_apdu_metadata(); if apdu_metadata.cla != CLA { - return Err(io::StatusWords::BadCla.into()); + return Err(StatusWords::BadCla.into()); } match ins { Ins::GetAppName => { if apdu_metadata.p1 != 0 || apdu_metadata.p2 != 0 { - return Err(io::Reply(SW_WRONG_P1P2)); + return Err(Reply(SW_WRONG_P1P2)); } comm.append(env!("CARGO_PKG_NAME").as_bytes()); } Ins::GetVersion => { if apdu_metadata.p1 != 0 || apdu_metadata.p2 != 0 { - return Err(io::Reply(SW_WRONG_P1P2)); + return Err(Reply(SW_WRONG_P1P2)); } return handler_get_version(comm); } Ins::GetPubkey => { if apdu_metadata.p1 > 1 || apdu_metadata.p2 != 0 { - return Err(io::Reply(SW_WRONG_P1P2)); + return Err(Reply(SW_WRONG_P1P2)); } if (comm.get_data()?.len()) == 0 { - return Err(io::Reply(SW_WRONG_DATA_LENGTH)); + return Err(Reply(SW_WRONG_DATA_LENGTH)); } return handler_get_public_key(comm, apdu_metadata.p1 == 1); @@ -150,11 +149,11 @@ fn handle_apdu(comm: &mut io::Comm, ins: Ins, ctx: &mut TxContext) -> Result<(), || apdu_metadata.p1 > P1_SIGN_TX_MAX || (apdu_metadata.p2 != P2_SIGN_TX_LAST && apdu_metadata.p2 != P2_SIGN_TX_MORE) { - return Err(io::Reply(SW_WRONG_P1P2)); + return Err(Reply(SW_WRONG_P1P2)); } if (comm.get_data()?.len()) == 0 { - return Err(io::Reply(SW_WRONG_DATA_LENGTH)); + return Err(Reply(SW_WRONG_DATA_LENGTH)); } return handler_sign_tx( @@ -165,7 +164,7 @@ fn handle_apdu(comm: &mut io::Comm, ins: Ins, ctx: &mut TxContext) -> Result<(), ); } Ins::UnknownIns => { - return Err(io::Reply(SW_INS_NOT_SUPPORTED)); + return Err(Reply(SW_INS_NOT_SUPPORTED)); } } Ok(()) diff --git a/src/utils.rs b/src/utils.rs index 366d650..2c3e30f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,6 @@ use crate::SW_WRONG_DATA_LENGTH; use core::char; -use nanos_sdk::io::Reply; -use nanos_sdk::testing; +use ledger_device_sdk::io::Reply; pub const MAX_ALLOWED_PATH_LEN: usize = 10; const MAX_HEX_LEN: usize = 64; @@ -10,7 +9,6 @@ const MAX_HEX_LEN: usize = 64; #[inline] pub fn to_hex(m: &[u8]) -> Result<[u8; MAX_HEX_LEN], ()> { if 2 * m.len() > MAX_HEX_LEN { - testing::debug_print("to_hex: buffer too small\n"); return Err(()); } let mut hex = [0u8; MAX_HEX_LEN]; @@ -18,7 +16,6 @@ pub fn to_hex(m: &[u8]) -> Result<[u8; MAX_HEX_LEN], ()> { for c in m { let c0 = char::from_digit((c >> 4).into(), 16).unwrap(); let c1 = char::from_digit((c & 0xf).into(), 16).unwrap(); - hex[i] = c0 as u8; hex[i + 1] = c1 as u8; i += 2; From 337f1b95002c38c5aba2c430b8afe02a0b2ce0c5 Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Thu, 16 Nov 2023 16:40:06 +0100 Subject: [PATCH 14/15] Introduce new App Status Words enum and use it instead of io::Reply as return type --- src/app_ui/address.rs | 11 ++- src/app_ui/sign.rs | 16 ++-- src/handlers/get_public_key.rs | 19 ++-- src/handlers/get_version.rs | 6 +- src/handlers/sign_tx.rs | 25 +++--- src/main.rs | 154 +++++++++++++++++++++------------ src/utils.rs | 9 +- 7 files changed, 141 insertions(+), 99 deletions(-) diff --git a/src/app_ui/address.rs b/src/app_ui/address.rs index 1c653d7..55713f5 100644 --- a/src/app_ui/address.rs +++ b/src/app_ui/address.rs @@ -16,25 +16,24 @@ *****************************************************************************/ use crate::utils::{concatenate, to_hex_all_caps}; -use crate::SW_DISPLAY_ADDRESS_FAIL; +use crate::AppSW; use core::str::from_utf8; -use ledger_device_sdk::io::Reply; use ledger_device_ui_sdk::bitmaps::{CROSSMARK, EYE, VALIDATE_14}; use ledger_device_ui_sdk::ui::{Field, MultiFieldReview}; // Display only the last 20 bytes of the address const DISPLAY_ADDR_BYTES_LEN: usize = 20; -pub fn ui_display_pk(addr: &[u8]) -> Result { +pub fn ui_display_pk(addr: &[u8]) -> Result { let addr_hex_str_buf = to_hex_all_caps(&addr[addr.len() - DISPLAY_ADDR_BYTES_LEN as usize..]) - .map_err(|_| Reply(SW_DISPLAY_ADDRESS_FAIL))?; + .map_err(|_| AppSW::AddrDisplayFail)?; let addr_hex_str = from_utf8(&addr_hex_str_buf[..DISPLAY_ADDR_BYTES_LEN * 2]) - .map_err(|_| Reply(SW_DISPLAY_ADDRESS_FAIL))?; + .map_err(|_| AppSW::AddrDisplayFail)?; let mut addr_hex_str_with_prefix_buf = [0u8; DISPLAY_ADDR_BYTES_LEN * 2 + 2]; concatenate(&["0x", &addr_hex_str], &mut addr_hex_str_with_prefix_buf); let addr_hex_str_with_prefix = - from_utf8(&addr_hex_str_with_prefix_buf).map_err(|_| Reply(SW_DISPLAY_ADDRESS_FAIL))?; + from_utf8(&addr_hex_str_with_prefix_buf).map_err(|_| AppSW::AddrDisplayFail)?; let my_field = [Field { name: "Address", diff --git a/src/app_ui/sign.rs b/src/app_ui/sign.rs index 7749568..1531b81 100644 --- a/src/app_ui/sign.rs +++ b/src/app_ui/sign.rs @@ -17,14 +17,13 @@ use crate::handlers::sign_tx::Tx; use crate::utils::{concatenate, to_hex_all_caps}; -use crate::SW_TX_DISPLAY_FAIL; +use crate::AppSW; use core::str::from_utf8; -use ledger_device_sdk::io::Reply; use ledger_device_ui_sdk::bitmaps::{CROSSMARK, EYE, VALIDATE_14}; use ledger_device_ui_sdk::ui::{Field, MultiFieldReview}; use numtoa::NumToA; -pub fn ui_display_tx(tx: &Tx) -> Result { +pub fn ui_display_tx(tx: &Tx) -> Result { // Format amount value let mut amount_buf = [0u8; 20]; let mut amount_with_denom_buf = [0u8; 25]; @@ -33,20 +32,19 @@ pub fn ui_display_tx(tx: &Tx) -> Result { &mut amount_with_denom_buf, ); let amount_str_with_denom = from_utf8(&amount_with_denom_buf) - .map_err(|_| Reply(SW_TX_DISPLAY_FAIL))? + .map_err(|_| AppSW::TxDisplayFail)? .trim_matches(char::from(0)); // Format destination address - let hex_addr_buf = to_hex_all_caps(&tx.to).map_err(|_| Reply(SW_TX_DISPLAY_FAIL))?; - let hex_addr_str = from_utf8(&hex_addr_buf).map_err(|_| Reply(SW_TX_DISPLAY_FAIL))?; + let hex_addr_buf = to_hex_all_caps(&tx.to).map_err(|_| AppSW::TxDisplayFail)?; + let hex_addr_str = from_utf8(&hex_addr_buf).map_err(|_| AppSW::TxDisplayFail)?; let mut addr_with_prefix_buf = [0u8; 42]; concatenate(&["0x", hex_addr_str], &mut addr_with_prefix_buf); let hex_addr_str_with_prefix = - from_utf8(&addr_with_prefix_buf).map_err(|_| Reply(SW_TX_DISPLAY_FAIL))?; + from_utf8(&addr_with_prefix_buf).map_err(|_| AppSW::TxDisplayFail)?; // Format memo - let memo_str = - from_utf8(&tx.memo[..tx.memo_len as usize]).map_err(|_| Reply(SW_TX_DISPLAY_FAIL))?; + let memo_str = from_utf8(&tx.memo[..tx.memo_len as usize]).map_err(|_| AppSW::TxDisplayFail)?; // Define transaction review fields let my_fields = [ diff --git a/src/handlers/get_public_key.rs b/src/handlers/get_public_key.rs index ba234ea..7d93ff7 100644 --- a/src/handlers/get_public_key.rs +++ b/src/handlers/get_public_key.rs @@ -17,23 +17,26 @@ use crate::app_ui::address::ui_display_pk; use crate::utils::{read_bip32_path, MAX_ALLOWED_PATH_LEN}; -use crate::{SW_DENY, SW_DISPLAY_ADDRESS_FAIL}; +use crate::AppSW; use ledger_device_sdk::ecc::{Secp256k1, SeedDerive}; -use ledger_device_sdk::io::{Comm, Reply}; +use ledger_device_sdk::io::Comm; use ledger_device_sdk::testing; use ledger_secure_sdk_sys::{ cx_hash_no_throw, cx_hash_t, cx_keccak_init_no_throw, cx_sha3_t, CX_LAST, CX_OK, }; -pub fn handler_get_public_key(comm: &mut Comm, display: bool) -> Result<(), Reply> { +pub fn handler_get_public_key(comm: &mut Comm, display: bool) -> Result<(), AppSW> { let mut path = [0u32; MAX_ALLOWED_PATH_LEN]; - let data = comm.get_data()?; + let data = match comm.get_data() { + Ok(data) => data, + Err(_) => return Err(AppSW::WrongDataLength), + }; let path_len = read_bip32_path(data, &mut path)?; let pk = Secp256k1::derive_from_path(&path[..path_len]) .public_key() - .map_err(|x| Reply(0x6eu16 | (x as u16 & 0xff)))?; + .map_err(|_| AppSW::KeyDeriveFail)?; // Display address on device if requested if display { @@ -42,7 +45,7 @@ pub fn handler_get_public_key(comm: &mut Comm, display: bool) -> Result<(), Repl unsafe { if cx_keccak_init_no_throw(&mut keccak256, 256) != CX_OK { - return Err(Reply(SW_DISPLAY_ADDRESS_FAIL)); + return Err(AppSW::AddrDisplayFail); } let mut pk_mut = pk.pubkey; @@ -56,14 +59,14 @@ pub fn handler_get_public_key(comm: &mut Comm, display: bool) -> Result<(), Repl address.len(), ) != CX_OK { - return Err(Reply(SW_DISPLAY_ADDRESS_FAIL)); + return Err(AppSW::AddrDisplayFail); } } testing::debug_print("showing public key\n"); if !ui_display_pk(&address)? { testing::debug_print("denied\n"); - return Err(Reply(SW_DENY)); + return Err(AppSW::Deny); } } diff --git a/src/handlers/get_version.rs b/src/handlers/get_version.rs index 4200286..559a5af 100644 --- a/src/handlers/get_version.rs +++ b/src/handlers/get_version.rs @@ -14,16 +14,16 @@ * See the License for the specific language governing permissions and * limitations under the License. *****************************************************************************/ - +use crate::AppSW; use core::str::FromStr; use ledger_device_sdk::io; -pub fn handler_get_version(comm: &mut io::Comm) -> Result<(), io::Reply> { +pub fn handler_get_version(comm: &mut io::Comm) -> Result<(), AppSW> { if let Some((major, minor, patch)) = parse_version_string(env!("CARGO_PKG_VERSION")) { comm.append(&[major, minor, patch]); Ok(()) } else { - Err(io::StatusWords::Unknown.into()) + Err(AppSW::VersionParsingFail) } } diff --git a/src/handlers/sign_tx.rs b/src/handlers/sign_tx.rs index b82d764..cc38939 100644 --- a/src/handlers/sign_tx.rs +++ b/src/handlers/sign_tx.rs @@ -16,9 +16,9 @@ *****************************************************************************/ use crate::app_ui::sign::ui_display_tx; use crate::utils::{read_bip32_path, slice_or_err, varint_read, MAX_ALLOWED_PATH_LEN}; -use crate::{SW_DENY, SW_TX_HASH_FAIL, SW_TX_PARSING_FAIL, SW_TX_SIGN_FAIL, SW_WRONG_TX_LENGTH}; +use crate::AppSW; use ledger_device_sdk::ecc::{Secp256k1, SeedDerive}; -use ledger_device_sdk::io::{Comm, Reply}; +use ledger_device_sdk::io::Comm; use ledger_secure_sdk_sys::{ cx_hash_no_throw, cx_hash_t, cx_keccak_init_no_throw, cx_sha3_t, CX_LAST, CX_OK, }; @@ -101,9 +101,12 @@ pub fn handler_sign_tx( chunk: u8, more: bool, ctx: &mut TxContext, -) -> Result<(), Reply> { +) -> Result<(), AppSW> { // Try to get data from comm - let data = comm.get_data()?; + let data = match comm.get_data() { + Ok(data) => data, + Err(_) => return Err(AppSW::WrongDataLength), + }; // First chunk, try to parse the path if chunk == 0 { // Reset transaction context @@ -114,7 +117,7 @@ pub fn handler_sign_tx( // the transaction if it is the last chunk. } else { if ctx.raw_tx_len + data.len() > MAX_TRANSACTION_LEN { - return Err(Reply(SW_WRONG_TX_LENGTH)); + return Err(AppSW::TxWrongLength); } // Append data to raw_tx @@ -128,7 +131,7 @@ pub fn handler_sign_tx( } else { let tx = match Tx::try_from(&ctx.raw_tx[..ctx.raw_tx_len]) { Ok(tx) => tx, - Err(_) => return Err(Reply(SW_TX_PARSING_FAIL)), + Err(_) => return Err(AppSW::TxParsingFail), }; // Display transaction. If user approves // the transaction, sign it. Otherwise, @@ -136,20 +139,20 @@ pub fn handler_sign_tx( if ui_display_tx(&tx)? { return compute_signature_and_append(comm, ctx); } else { - return Err(Reply(SW_DENY)); + return Err(AppSW::Deny); } } } Ok(()) } -fn compute_signature_and_append(comm: &mut Comm, ctx: &mut TxContext) -> Result<(), Reply> { +fn compute_signature_and_append(comm: &mut Comm, ctx: &mut TxContext) -> Result<(), AppSW> { let mut keccak256: cx_sha3_t = Default::default(); let mut message_hash: [u8; 32] = [0u8; 32]; unsafe { if cx_keccak_init_no_throw(&mut keccak256, 256) != CX_OK { - return Err(Reply(SW_TX_HASH_FAIL)); + return Err(AppSW::TxHashFail); } if cx_hash_no_throw( &mut keccak256.header as *mut cx_hash_t, @@ -160,13 +163,13 @@ fn compute_signature_and_append(comm: &mut Comm, ctx: &mut TxContext) -> Result< message_hash.len(), ) != CX_OK { - return Err(Reply(SW_TX_HASH_FAIL)); + return Err(AppSW::TxHashFail); } } let (sig, siglen, parity) = Secp256k1::derive_from_path(&ctx.path[..ctx.path_len]) .deterministic_sign(&message_hash) - .map_err(|_| Reply(SW_TX_SIGN_FAIL))?; + .map_err(|_| AppSW::TxSignFail)?; comm.append(&[siglen as u8]); comm.append(&sig[..siglen as usize]); comm.append(&[parity as u8]); diff --git a/src/main.rs b/src/main.rs index 13016bc..2966b8d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,20 @@ +/***************************************************************************** + * Ledger App Boilerplate Rust. + * (c) 2023 Ledger SAS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ + #![no_std] #![no_main] @@ -14,7 +31,7 @@ mod handlers { } use ledger_device_sdk::buttons::ButtonEvent; -use ledger_device_sdk::io::{ApduHeader, Comm, Event, Reply, StatusWords}; +use ledger_device_sdk::io::{ApduHeader, Comm, Event, Reply}; use ledger_device_ui_sdk::ui; @@ -27,16 +44,62 @@ use handlers::{ ledger_device_sdk::set_panic!(ledger_device_sdk::exiting_panic); -pub const SW_INS_NOT_SUPPORTED: u16 = 0x6D00; -pub const SW_DENY: u16 = 0x6985; -pub const SW_WRONG_P1P2: u16 = 0x6A86; -pub const SW_WRONG_DATA_LENGTH: u16 = 0x6A87; -pub const SW_TX_DISPLAY_FAIL: u16 = 0xB001; -pub const SW_DISPLAY_ADDRESS_FAIL: u16 = 0xB002; -pub const SW_WRONG_TX_LENGTH: u16 = 0xB004; -pub const SW_TX_PARSING_FAIL: u16 = 0xB005; -pub const SW_TX_HASH_FAIL: u16 = 0xB006; -pub const SW_TX_SIGN_FAIL: u16 = 0xB008; +// CLA (APDU class byte) for all APDUs. +const CLA: u8 = 0xe0; +// P2 for last APDU to receive. +const P2_SIGN_TX_LAST: u8 = 0x00; +// P2 for more APDU to receive. +const P2_SIGN_TX_MORE: u8 = 0x80; +// P1 for first APDU number. +const P1_SIGN_TX_START: u8 = 0x00; +// P1 for maximum APDU number. +const P1_SIGN_TX_MAX: u8 = 0x03; + +// Application status words. +#[repr(u16)] +pub enum AppSW { + Deny = 0x6985, + WrongP1P2 = 0x6A86, + WrongDataLength = 0x6A87, + InsNotSupported = 0x6D00, + ClaNotSupported = 0x6E00, + TxDisplayFail = 0xB001, + AddrDisplayFail = 0xB002, + TxWrongLength = 0xB004, + TxParsingFail = 0xB005, + TxHashFail = 0xB006, + TxSignFail = 0xB008, + KeyDeriveFail = 0xB009, + VersionParsingFail = 0xB00A, +} + +impl From for Reply { + fn from(sw: AppSW) -> Reply { + Reply(sw as u16) + } +} + +#[repr(u8)] +// Instruction set for the app. +enum Ins { + GetVersion, + GetAppName, + GetPubkey, + SignTx, + UnknownIns, +} + +impl From for Ins { + fn from(header: ApduHeader) -> Ins { + match header.ins { + 3 => Ins::GetVersion, + 4 => Ins::GetAppName, + 5 => Ins::GetPubkey, + 6 => Ins::SignTx, + _ => Ins::UnknownIns, + } + } +} #[no_mangle] extern "C" fn sample_pending() { @@ -69,77 +132,49 @@ extern "C" fn sample_main() { match ui_menu_main(&mut comm) { Event::Command(ins) => match handle_apdu(&mut comm, ins.into(), &mut tx_ctx) { Ok(()) => comm.reply_ok(), - Err(sw) => comm.reply(sw), + Err(sw) => comm.reply(Reply::from(sw)), }, _ => (), } } } -#[repr(u8)] - -// Instruction set for the app. -enum Ins { - GetVersion, - GetAppName, - GetPubkey, - SignTx, - UnknownIns, -} -// CLA (APDU class byte) for all APDUs. -const CLA: u8 = 0xe0; -// P2 for last APDU to receive. -const P2_SIGN_TX_LAST: u8 = 0x00; -// P2 for more APDU to receive. -const P2_SIGN_TX_MORE: u8 = 0x80; -// P1 for first APDU number. -const P1_SIGN_TX_START: u8 = 0x00; -// P1 for maximum APDU number. -const P1_SIGN_TX_MAX: u8 = 0x03; - -impl From for Ins { - fn from(header: ApduHeader) -> Ins { - match header.ins { - 3 => Ins::GetVersion, - 4 => Ins::GetAppName, - 5 => Ins::GetPubkey, - 6 => Ins::SignTx, - _ => Ins::UnknownIns, - } - } -} - -fn handle_apdu(comm: &mut Comm, ins: Ins, ctx: &mut TxContext) -> Result<(), Reply> { +fn handle_apdu(comm: &mut Comm, ins: Ins, ctx: &mut TxContext) -> Result<(), AppSW> { if comm.rx == 0 { - return Err(StatusWords::NothingReceived.into()); + return Err(AppSW::WrongDataLength); } let apdu_metadata = comm.get_apdu_metadata(); if apdu_metadata.cla != CLA { - return Err(StatusWords::BadCla.into()); + return Err(AppSW::ClaNotSupported); } match ins { Ins::GetAppName => { if apdu_metadata.p1 != 0 || apdu_metadata.p2 != 0 { - return Err(Reply(SW_WRONG_P1P2)); + return Err(AppSW::WrongP1P2); } comm.append(env!("CARGO_PKG_NAME").as_bytes()); } Ins::GetVersion => { if apdu_metadata.p1 != 0 || apdu_metadata.p2 != 0 { - return Err(Reply(SW_WRONG_P1P2)); + return Err(AppSW::WrongP1P2); } return handler_get_version(comm); } Ins::GetPubkey => { if apdu_metadata.p1 > 1 || apdu_metadata.p2 != 0 { - return Err(Reply(SW_WRONG_P1P2)); + return Err(AppSW::WrongP1P2); } - if (comm.get_data()?.len()) == 0 { - return Err(Reply(SW_WRONG_DATA_LENGTH)); + match comm.get_data() { + Ok(data) => { + if data.len() == 0 { + return Err(AppSW::WrongDataLength); + } + } + Err(_) => return Err(AppSW::WrongDataLength), } return handler_get_public_key(comm, apdu_metadata.p1 == 1); @@ -149,11 +184,16 @@ fn handle_apdu(comm: &mut Comm, ins: Ins, ctx: &mut TxContext) -> Result<(), Rep || apdu_metadata.p1 > P1_SIGN_TX_MAX || (apdu_metadata.p2 != P2_SIGN_TX_LAST && apdu_metadata.p2 != P2_SIGN_TX_MORE) { - return Err(Reply(SW_WRONG_P1P2)); + return Err(AppSW::WrongP1P2); } - if (comm.get_data()?.len()) == 0 { - return Err(Reply(SW_WRONG_DATA_LENGTH)); + match comm.get_data() { + Ok(data) => { + if data.len() == 0 { + return Err(AppSW::WrongDataLength); + } + } + Err(_) => return Err(AppSW::WrongDataLength), } return handler_sign_tx( @@ -164,7 +204,7 @@ fn handle_apdu(comm: &mut Comm, ins: Ins, ctx: &mut TxContext) -> Result<(), Rep ); } Ins::UnknownIns => { - return Err(Reply(SW_INS_NOT_SUPPORTED)); + return Err(AppSW::InsNotSupported); } } Ok(()) diff --git a/src/utils.rs b/src/utils.rs index 2c3e30f..a5ce270 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,5 @@ -use crate::SW_WRONG_DATA_LENGTH; +use crate::AppSW; use core::char; -use ledger_device_sdk::io::Reply; pub const MAX_ALLOWED_PATH_LEN: usize = 10; const MAX_HEX_LEN: usize = 64; @@ -39,10 +38,10 @@ pub fn to_hex_all_caps(m: &[u8]) -> Result<[u8; MAX_HEX_LEN], ()> { } /// Convert serialized derivation path to u32 array elements -pub fn read_bip32_path(data: &[u8], path: &mut [u32]) -> Result { +pub fn read_bip32_path(data: &[u8], path: &mut [u32]) -> Result { // Check input length and path buffer capacity if data.len() < 1 || path.len() < data.len() / 4 { - return Err(Reply(SW_WRONG_DATA_LENGTH)); + return Err(AppSW::WrongDataLength); } let path_len = data[0] as usize; // First byte is the length of the path @@ -53,7 +52,7 @@ pub fn read_bip32_path(data: &[u8], path: &mut [u32]) -> Result { || path_data.len() > MAX_ALLOWED_PATH_LEN * 4 || path_data.len() % 4 != 0 { - return Err(Reply(SW_WRONG_DATA_LENGTH)); + return Err(AppSW::WrongDataLength); } let mut idx = 0; From 3c16549a24170e1469f2e645581e031f28cc1d7a Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Thu, 16 Nov 2023 16:48:02 +0100 Subject: [PATCH 15/15] Update CI --- .github/workflows/rust.yml | 39 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 67b1a51..5a98566 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -13,26 +13,28 @@ env: CARGO_TERM_COLOR: always jobs: - build_clippy_fmt: + build: + name : Build runs-on: ubuntu-latest + container: + image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest + strategy: + matrix: + target: ["nanos", "nanox", "nanosplus"] steps: - - name: arm-none-eabi-gcc - uses: fiam/arm-none-eabi-gcc@v1.0.3 - with: - release: '9-2019-q4' - - name: Checkout + - name: Clone + uses: actions/checkout@v3 + - name: Build app + run: cargo ledger build ${{ matrix.target }} + + clippy_fmt: + name: Run static analysis and formatting check + runs-on: ubuntu-latest + container: + image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest + steps: + - name: Clone uses: actions/checkout@v3 - - name: Install clang - run: sudo apt-get update && sudo apt install -y clang - - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - override: true - components: rust-src, rustfmt, clippy - - name: Install cargo-ledger - run: cargo install --git=https://github.com/LedgerHQ/cargo-ledger - - name: Setup cargo-ledger - run: cargo ledger setup - name: Cargo clippy uses: actions-rs/cargo@v1 with: @@ -42,6 +44,3 @@ jobs: with: command: fmt args: --all -- --check - - name: Build app - run: cargo ledger build nanosplus -