From 9ec3374eacde0e71d8292ecd0da7093246ea99e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20H=C3=A9riveaux?= Date: Tue, 21 May 2024 14:52:27 +0200 Subject: [PATCH] Use of heap feature for simpler code --- .cargo/config.toml | 2 +- Cargo.lock | 28 +++++++++++++++++-- Cargo.toml | 4 +-- src/app_ui/address.rs | 14 ++++------ src/app_ui/sign.rs | 24 ++++------------ src/handlers/sign_tx.rs | 20 ++++++-------- src/main.rs | 3 ++ src/utils.rs | 61 ++++++++--------------------------------- 8 files changed, 63 insertions(+), 93 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index c233973..aee4a63 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -5,5 +5,5 @@ runner = "speculos -a=1 --model=nanosp" target = "nanosplus" [unstable] -build-std = ["core"] +build-std = ["core", "alloc"] build-std-features = ["compiler-builtins-mem"] diff --git a/Cargo.lock b/Cargo.lock index 5589baf..9940965 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,6 +21,7 @@ dependencies = [ name = "app-boilerplate-rust" version = "1.3.0" dependencies = [ + "heapless", "hex", "include_gif", "ledger_device_sdk", @@ -115,8 +116,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] -name = "const-zero" -version = "0.1.1" +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + +[[package]] +name = "either" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3c6565524986fe3225da0beb9b4aa55ebc73cd57ff8cb4ccf016ca4c8d006af" @@ -135,6 +142,16 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +[[package]] +name = "embedded-alloc" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddae17915accbac2cfbc64ea0ae6e3b330e6ea124ba108dada63646fd3c6f815" +dependencies = [ + "critical-section", + "linked_list_allocator", +] + [[package]] name = "errno" version = "0.3.9" @@ -233,11 +250,18 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "ledger_device_sdk" +<<<<<<< HEAD version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c799dd808a566399aa22a31e3fa9563c8f9959a4960e3fbef65a7b492f9f83f7" dependencies = [ "const-zero", +======= +version = "1.9.0" +dependencies = [ + "critical-section", + "embedded-alloc", +>>>>>>> 675cae8 (Use of heap feature for simpler code) "include_gif", "ledger_secure_sdk_sys", "num-traits", diff --git a/Cargo.toml b/Cargo.toml index 629c73d..f4ed045 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,11 +5,11 @@ authors = ["yhql", "agrojean-ledger"] edition = "2021" [dependencies] -ledger_device_sdk = "1.9.2" +ledger_device_sdk = { version="1.10.0", features = ["heap"] } include_gif = "1.1.0" serde = {version="1.0.192", default_features = false, features = ["derive"]} serde-json-core = { git = "https://github.com/rust-embedded-community/serde-json-core"} -hex = { version = "0.4.3", default-features = false, features = ["serde"] } +hex = { version = "0.4.3", default-features = false, features = ["serde", "alloc"] } numtoa = "0.2.4" [profile.release] diff --git a/src/app_ui/address.rs b/src/app_ui/address.rs index 1c2ff63..7ff99e0 100644 --- a/src/app_ui/address.rs +++ b/src/app_ui/address.rs @@ -16,6 +16,7 @@ *****************************************************************************/ use crate::AppSW; +use alloc::format; use core::str::from_utf8_mut; #[cfg(not(any(target_os = "stax", target_os = "flex")))] @@ -34,15 +35,10 @@ use include_gif::include_gif; const DISPLAY_ADDR_BYTES_LEN: usize = 20; pub fn ui_display_pk(addr: &[u8]) -> Result { - let mut addr_hex = [0u8; DISPLAY_ADDR_BYTES_LEN * 2 + 2]; - addr_hex[..2].copy_from_slice("0x".as_bytes()); - hex::encode_to_slice( - &addr[addr.len() - DISPLAY_ADDR_BYTES_LEN..], - &mut addr_hex[2..], - ) - .unwrap(); - let addr_hex = from_utf8_mut(&mut addr_hex).unwrap(); - addr_hex[2..].make_ascii_uppercase(); + let addr_hex = format!( + "0x{}", + hex::encode(&addr[addr.len() - DISPLAY_ADDR_BYTES_LEN..]).to_uppercase() + ); #[cfg(not(any(target_os = "stax", target_os = "flex")))] { diff --git a/src/app_ui/sign.rs b/src/app_ui/sign.rs index abefde1..7fac365 100644 --- a/src/app_ui/sign.rs +++ b/src/app_ui/sign.rs @@ -15,7 +15,6 @@ * limitations under the License. *****************************************************************************/ use crate::handlers::sign_tx::Tx; -use crate::utils::concatenate; use crate::AppSW; #[cfg(not(any(target_os = "stax", target_os = "flex")))] @@ -35,6 +34,8 @@ use numtoa::NumToA; const MAX_COIN_LENGTH: usize = 10; +use alloc::format; + /// Displays a transaction and returns true if user approved it. /// /// This method can return [`AppSW::TxDisplayFail`] error if the coin name length is too long. @@ -43,31 +44,18 @@ const MAX_COIN_LENGTH: usize = 10; /// /// * `tx` - Transaction to be displayed for validation pub fn ui_display_tx(tx: &Tx) -> Result { - // Generate string for amount - let mut numtoa_buf = [0u8; 20]; - let mut value_buf = [0u8; 20 + MAX_COIN_LENGTH + 1]; - - let value_str = concatenate( - &[tx.coin, " ", tx.value.numtoa_str(10, &mut numtoa_buf)], - &mut value_buf, - ) - .map_err(|_| AppSW::TxDisplayFail)?; // Fails if value_buf is too small - - // Generate destination address string in hexadecimal format. - let mut to_str = [0u8; 42]; - to_str[..2].copy_from_slice("0x".as_bytes()); - hex::encode_to_slice(tx.to, &mut to_str[2..]).unwrap(); - to_str[2..].make_ascii_uppercase(); + let value_str = format!("{} {}", tx.coin, tx.value); + let to_str = format!("0x{}", hex::encode(tx.to).to_uppercase()); // Define transaction review fields let my_fields = [ Field { name: "Amount", - value: value_str, + value: value_str.as_str(), }, Field { name: "Destination", - value: core::str::from_utf8(&to_str).unwrap(), + value: to_str.as_str(), }, Field { name: "Memo", diff --git a/src/handlers/sign_tx.rs b/src/handlers/sign_tx.rs index 5a98a76..8b6ddee 100644 --- a/src/handlers/sign_tx.rs +++ b/src/handlers/sign_tx.rs @@ -17,6 +17,7 @@ use crate::app_ui::sign::ui_display_tx; use crate::utils::Bip32Path; use crate::AppSW; +use alloc::vec::Vec; use ledger_device_sdk::ecc::{Secp256k1, SeedDerive}; use ledger_device_sdk::hash::{sha3::Keccak256, HashInit}; use ledger_device_sdk::io::Comm; @@ -38,8 +39,7 @@ pub struct Tx<'a> { } pub struct TxContext { - raw_tx: [u8; MAX_TRANSACTION_LEN], // raw transaction serialized - raw_tx_len: usize, // length of raw transaction + raw_tx: Vec, path: Bip32Path, } @@ -47,15 +47,13 @@ pub struct TxContext { impl TxContext { pub fn new() -> TxContext { TxContext { - raw_tx: [0u8; MAX_TRANSACTION_LEN], - raw_tx_len: 0, + raw_tx: Vec::new(), path: Default::default(), } } // Implement reset for TxInfo fn reset(&mut self) { - self.raw_tx = [0u8; MAX_TRANSACTION_LEN]; - self.raw_tx_len = 0; + self.raw_tx.clear(); self.path = Default::default(); } } @@ -78,13 +76,12 @@ pub fn handler_sign_tx( // 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 { + if ctx.raw_tx.len() + data.len() > MAX_TRANSACTION_LEN { return Err(AppSW::TxWrongLength); } // 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(); + ctx.raw_tx.extend(data); // If we expect more chunks, return if more { @@ -92,8 +89,7 @@ pub fn handler_sign_tx( // Otherwise, try to parse the transaction } else { // Try to deserialize the transaction - let (tx, _): (Tx, usize) = - from_slice(&ctx.raw_tx[..ctx.raw_tx_len]).map_err(|_| AppSW::TxParsingFail)?; + let (tx, _): (Tx, usize) = from_slice(&ctx.raw_tx).map_err(|_| AppSW::TxParsingFail)?; // Display transaction. If user approves // the transaction, sign it. Otherwise, // return a "deny" status word. @@ -110,7 +106,7 @@ fn compute_signature_and_append(comm: &mut Comm, ctx: &mut TxContext) -> Result< let mut keccak256 = Keccak256::new(); let mut message_hash: [u8; 32] = [0u8; 32]; - let _ = keccak256.hash(&ctx.raw_tx[..ctx.raw_tx_len], &mut message_hash); + let _ = keccak256.hash(&ctx.raw_tx, &mut message_hash); let (sig, siglen, parity) = Secp256k1::derive_from_path(ctx.path.as_ref()) .deterministic_sign(&message_hash) diff --git a/src/main.rs b/src/main.rs index 5445208..b542ecf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,6 +45,9 @@ use ledger_device_sdk::ui::gadgets::display_pending_review; #[cfg(not(any(target_os = "stax", target_os = "flex")))] ledger_device_sdk::set_panic!(ledger_device_sdk::exiting_panic); +// Required for using String, Vec, format!... +extern crate alloc; + #[cfg(any(target_os = "stax", target_os = "flex"))] use ledger_device_sdk::nbgl::init_comm; diff --git a/src/utils.rs b/src/utils.rs index 77448e7..bac3ea0 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,32 +1,18 @@ +use alloc::vec::Vec; + use crate::AppSW; -use core::str::from_utf8; /// BIP32 path stored as an array of [`u32`]. -/// -/// # Generic arguments -/// -/// * `S` - Maximum possible path length, i.e. the capacity of the internal buffer. -pub struct Bip32Path { - buffer: [u32; S], - len: usize, -} +#[derive(Default)] +pub struct Bip32Path(Vec); impl AsRef<[u32]> for Bip32Path { fn as_ref(&self) -> &[u32] { - &self.buffer[..self.len] - } -} - -impl Default for Bip32Path { - fn default() -> Self { - Self { - buffer: [0u32; S], - len: 0, - } + &self.0 } } -impl TryFrom<&[u8]> for Bip32Path { +impl TryFrom<&[u8]> for Bip32Path { type Error = AppSW; /// Constructs a [`Bip32Path`] from a given byte array. @@ -34,46 +20,23 @@ impl TryFrom<&[u8]> for Bip32Path { /// This method will return an error in the following cases: /// - the input array is empty, /// - the number of bytes in the input array is not a multiple of 4, - /// - the input array exceeds the capacity of the [`Bip32Path`] internal buffer. /// /// # Arguments /// /// * `data` - Encoded BIP32 path. First byte is the length of the path, as encoded by ragger. fn try_from(data: &[u8]) -> Result { - let input_path_len = (data.len() - 1) / 4; // Check data length if data.is_empty() // At least the length byte is required - || (input_path_len > S) || (data[0] as usize * 4 != data.len() - 1) { return Err(AppSW::WrongApduLength); } - let mut path = [0; S]; - for (chunk, p) in data[1..].chunks(4).zip(path.iter_mut()) { - *p = u32::from_be_bytes(chunk.try_into().unwrap()); - } - - Ok(Self { - buffer: path, - len: input_path_len, - }) + Ok(Bip32Path( + data[1..] + .chunks(4) + .map(|chunk| u32::from_be_bytes(chunk.try_into().unwrap())) + .collect(), + )) } } - -/// Returns concatenated strings, or an error if the concatenation buffer is too small. -pub fn concatenate<'a>(strings: &[&str], output: &'a mut [u8]) -> Result<&'a str, ()> { - let mut offset = 0; - - for s in strings { - let s_len = s.len(); - if offset + s_len > output.len() { - return Err(()); - } - - output[offset..offset + s_len].copy_from_slice(s.as_bytes()); - offset += s_len; - } - - Ok(from_utf8(&output[..offset]).unwrap()) -}