From 5f40d6276dd6ab41acd1ce336c875b2c1a5e4be5 Mon Sep 17 00:00:00 2001 From: Natanael Mojica Date: Tue, 21 Feb 2023 07:32:51 -0500 Subject: [PATCH] App size (#146) * avoid indexing to reduce binary size use try call to avoid panic machinery to be added by the compiler optimize for binary size and remove debug symbols * update tests * bump app version and update snapshots * Derive Debug only under test * Remove panic-halt to reduce app size * Reduce calls to procedures that can cause panics * Rename LedgerPanic to ApduPanic * fix format * use unreachable_unchecked * some improvements in size * fix return value * Update zxlib * use copy_from_slice and better error report --- app/Makefile | 1 + app/Makefile.version | 2 +- app/rust/Cargo.lock | 7 --- app/rust/Cargo.toml | 7 +-- app/rust/src/lib.rs | 2 - app/rust/src/parser/c32.rs | 38 +++++++++---- app/rust/src/parser/message.rs | 5 +- app/rust/src/parser/parsed_obj.rs | 3 +- app/rust/src/parser/parser_common.rs | 24 +++++--- app/rust/src/parser/post_condition.rs | 12 ++-- app/rust/src/parser/principal.rs | 9 ++- app/rust/src/parser/spending_condition.rs | 34 +++++++----- app/rust/src/parser/structured_msg.rs | 12 +++- app/rust/src/parser/transaction.rs | 15 +++-- app/rust/src/parser/transaction_auth.rs | 3 +- app/rust/src/parser/transaction_payload.rs | 44 ++++++++++----- app/rust/src/parser/utils.rs | 52 ++++++++++++++++++ app/rust/src/parser/value/int.rs | 9 ++- app/rust/src/parser/value/string.rs | 9 ++- app/rust/src/parser/value/tuple.rs | 6 +- app/rust/src/zxformat.rs | 2 + deps/ledger-zxlib | 2 +- tests_zemu/package.json | 4 +- tests_zemu/snapshots/s-mainmenu/00004.png | Bin 439 -> 442 bytes tests_zemu/snapshots/s-mainmenu/00010.png | Bin 439 -> 442 bytes tests_zemu/snapshots/sp-mainmenu/00004.png | Bin 377 -> 366 bytes tests_zemu/snapshots/sp-mainmenu/00010.png | Bin 377 -> 366 bytes tests_zemu/snapshots/x-mainmenu/00004.png | Bin 377 -> 366 bytes tests_zemu/snapshots/x-mainmenu/00010.png | Bin 377 -> 366 bytes tests_zemu/tests/pullImageKillOld.ts | 4 ++ tests_zemu/tests/sign_structured_data.test.ts | 4 +- tests_zemu/tests/standard.test.ts | 2 + 32 files changed, 222 insertions(+), 90 deletions(-) create mode 100644 tests_zemu/tests/pullImageKillOld.ts diff --git a/app/Makefile b/app/Makefile index 40d8e45c..8af2c49c 100755 --- a/app/Makefile +++ b/app/Makefile @@ -206,6 +206,7 @@ APP_SOURCE_PATH += $(MY_DIR)/src APP_SOURCE_PATH += $(MY_DIR)/glyphs APP_SOURCE_PATH += $(MY_DIR)/rust/include APP_SOURCE_PATH += $(MY_DIR)/../deps/ledger-zxlib/include +APP_SOURCE_PATH += $(MY_DIR)/../deps/ledger-zxlib/app/ui APP_SOURCE_PATH += $(MY_DIR)/../deps/ledger-zxlib/src APP_SOURCE_PATH += $(MY_DIR)/../deps/ledger-zxlib/app/common APP_SOURCE_PATH += $(MY_DIR)/../deps/sha512 diff --git a/app/Makefile.version b/app/Makefile.version index e2f67215..d791c852 100644 --- a/app/Makefile.version +++ b/app/Makefile.version @@ -1,3 +1,3 @@ APPVERSION_M=0 APPVERSION_N=23 -APPVERSION_P=7 +APPVERSION_P=8 diff --git a/app/rust/Cargo.lock b/app/rust/Cargo.lock index 13ca5e4d..7fa5af10 100644 --- a/app/rust/Cargo.lock +++ b/app/rust/Cargo.lock @@ -259,12 +259,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "panic-halt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812" - [[package]] name = "proc-macro2" version = "1.0.43" @@ -332,7 +326,6 @@ dependencies = [ "no-std-compat", "nom", "numtoa", - "panic-halt", "serde", "serde-json-core", "serde_json", diff --git a/app/rust/Cargo.toml b/app/rust/Cargo.toml index c85fc917..57a1b8b1 100644 --- a/app/rust/Cargo.toml +++ b/app/rust/Cargo.toml @@ -28,9 +28,6 @@ default-features = false version = "0.5.1" default-features = false -[target.thumbv6m-none-eabi.dev-dependencies] -panic-halt = "0.2.0" - [dev-dependencies] no-std-compat = { version = "0.4.1", features = ["std"] } serde_json = "1.0.56" @@ -48,8 +45,8 @@ no-std-compat = { version = "0.4.1", features = ["std"] } [profile.release] lto=false codegen-units = 1 -debug=true -opt-level = "s" +debug=false +opt-level = "z" [profile.dev] panic = "abort" diff --git a/app/rust/src/lib.rs b/app/rust/src/lib.rs index bd8f2264..cae2b01f 100644 --- a/app/rust/src/lib.rs +++ b/app/rust/src/lib.rs @@ -10,8 +10,6 @@ mod bolos; pub mod parser; mod zxformat; -fn debug(_msg: &str) {} - #[cfg(not(any(test, fuzzing)))] use core::panic::PanicInfo; diff --git a/app/rust/src/parser/c32.rs b/app/rust/src/parser/c32.rs index cd1a22d9..1a4892fe 100644 --- a/app/rust/src/parser/c32.rs +++ b/app/rust/src/parser/c32.rs @@ -1,3 +1,5 @@ +#[cfg(not(any(test, fuzzing)))] +use crate::parser::utils::ApduPanic; use crate::parser::{ error::ParserError, parser_common::{C32_ENCODED_ADDRS_LENGTH, HASH160_LEN}, @@ -64,15 +66,20 @@ fn double_sha256_checksum(data: &mut [u8; SHA256_LEN]) { fn double_sha256_checksum(data: &mut [u8; SHA256_LEN]) { let mut output = [0u8; SHA256_LEN]; // safe to unwrap as we are passing the right len - sha256(&data[..21], &mut output[..]).unwrap(); + sha256(&data[..21], &mut output[..]).apdu_unwrap(); data.copy_from_slice(output.as_ref()); // safe to unwrap as we are passing the right len - sha256(&data[..], &mut output).unwrap(); - data[20..24].copy_from_slice(&output[..4]) + sha256(&data[..], &mut output).apdu_unwrap(); + let d = data.get_mut(20..24).apdu_unwrap(); + let o = output.get(..4).apdu_unwrap(); + d.copy_from_slice(o); } #[inline(never)] -fn c32_encode(input_bytes: &[u8], result: &mut ArrayVec<[u8; C32_ENCODED_ADDRS_LENGTH]>) { +fn c32_encode( + input_bytes: &[u8], + result: &mut ArrayVec<[u8; C32_ENCODED_ADDRS_LENGTH]>, +) -> Result<(), ParserError> { let mut carry = 0; let mut carry_bits = 0; @@ -80,26 +87,34 @@ fn c32_encode(input_bytes: &[u8], result: &mut ArrayVec<[u8; C32_ENCODED_ADDRS_L let low_bits_to_take = 5 - carry_bits; let low_bits = current_value & ((1 << low_bits_to_take) - 1); let c32_value = (low_bits << carry_bits) + carry; - result.push(C32_CHARACTERS[c32_value as usize]); + result + .try_push(C32_CHARACTERS[c32_value as usize]) + .map_err(|_| ParserError::parser_unexpected_buffer_end)?; carry_bits = (8 + carry_bits) - 5; carry = current_value >> (8 - carry_bits); if carry_bits >= 5 { let c32_value = carry & ((1 << 5) - 1); - result.push(C32_CHARACTERS[c32_value as usize]); + result + .try_push(C32_CHARACTERS[c32_value as usize]) + .map_err(|_| ParserError::parser_unexpected_buffer_end)?; carry_bits -= 5; carry >>= 5; } } if carry_bits > 0 { - result.push(C32_CHARACTERS[carry as usize]); + result + .try_push(C32_CHARACTERS[carry as usize]) + .map_err(|_| ParserError::parser_unexpected_buffer_end)?; } // remove leading zeros from c32 encoding while let Some(v) = result.pop() { if v != C32_CHARACTERS[0] { - result.push(v); + result + .try_push(v) + .map_err(|_| ParserError::parser_unexpected_buffer_end)?; break; } } @@ -107,12 +122,15 @@ fn c32_encode(input_bytes: &[u8], result: &mut ArrayVec<[u8; C32_ENCODED_ADDRS_L // add leading zeros from input. for current_value in input_bytes.iter() { if *current_value == 0 { - result.push(C32_CHARACTERS[0]); + result + .try_push(C32_CHARACTERS[0]) + .map_err(|_| ParserError::parser_unexpected_buffer_end)?; } else { break; } } result.reverse(); + Ok(()) } #[inline(never)] @@ -137,7 +155,7 @@ fn c32_check_encode( check_data[..20].copy_from_slice(data); // here we use only the 24-bytes - c32_encode(&check_data[..24], c32_string); + c32_encode(&check_data[..24], c32_string)?; let version_char = C32_CHARACTERS[version as usize]; c32_string.insert(0, version_char); Ok(()) diff --git a/app/rust/src/parser/message.rs b/app/rust/src/parser/message.rs index d12b8dfe..bac1a07e 100644 --- a/app/rust/src/parser/message.rs +++ b/app/rust/src/parser/message.rs @@ -118,7 +118,10 @@ impl<'a> ByteString<'a> { }); let mut copy_len = if self.0.len() > MAX_ASCII_LEN { - msg[MAX_ASCII_LEN..MAX_ASCII_LEN + suffix.len()].copy_from_slice(&suffix[..]); + let m = msg + .get_mut(MAX_ASCII_LEN..MAX_ASCII_LEN + suffix.len()) + .ok_or(ParserError::parser_unexpected_buffer_end)?; + m.copy_from_slice(&suffix[..]); MAX_ASCII_LEN } else { self.0.len() diff --git a/app/rust/src/parser/parsed_obj.rs b/app/rust/src/parser/parsed_obj.rs index 9c94a0ff..55ef45dd 100644 --- a/app/rust/src/parser/parsed_obj.rs +++ b/app/rust/src/parser/parsed_obj.rs @@ -6,7 +6,8 @@ use super::{Jwt, StructuredMsg}; use core::mem::ManuallyDrop; #[repr(u8)] -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(test, derive(Debug))] pub enum Tag { Transaction, Message, diff --git a/app/rust/src/parser/parser_common.rs b/app/rust/src/parser/parser_common.rs index 1da475f6..76aa6213 100644 --- a/app/rust/src/parser/parser_common.rs +++ b/app/rust/src/parser/parser_common.rs @@ -32,7 +32,8 @@ pub const MAX_DEPTH: u8 = 20; /// Stacks transaction versions #[repr(u8)] -#[derive(Debug, Clone, PartialEq, Copy)] +#[derive(Clone, PartialEq, Copy)] +#[cfg_attr(test, derive(Debug))] pub enum TransactionVersion { Mainnet = 0x00, Testnet = 0x80, @@ -60,7 +61,8 @@ impl TransactionVersion { } #[repr(u8)] -#[derive(Clone, PartialEq, Copy, Debug)] +#[derive(Clone, PartialEq, Copy)] +#[cfg_attr(test, derive(Debug))] pub enum AssetInfoId { STX = 0, FungibleAsset = 1, @@ -82,7 +84,8 @@ impl TryFrom for AssetInfoId { } #[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub struct AssetInfo<'a> { pub address: StacksAddress<'a>, pub contract_name: ContractName<'a>, @@ -121,7 +124,8 @@ impl<'a> AssetInfo<'a> { } #[repr(u8)] -#[derive(Debug, Clone, PartialEq, Copy)] +#[derive(Clone, PartialEq, Copy)] +#[cfg_attr(test, derive(Debug))] // Flag used to know if the signer is valid and // who is pub enum SignerId { @@ -133,7 +137,8 @@ pub enum SignerId { // tag address hash modes as "singlesig" or "multisig" so we can't accidentally construct an // invalid spending condition #[repr(u8)] -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub enum HashMode { P2PKH = 0x00, P2SH = 0x01, @@ -175,7 +180,8 @@ impl HashMode { // contract name with valid charactes being // ^[a-zA-Z]([a-zA-Z0-9]|[-_])*$ #[repr(C)] -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Eq, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub struct ContractName<'a>(ClarityName<'a>); impl<'a> ContractName<'a> { @@ -204,7 +210,8 @@ impl<'a> ContractName<'a> { // A clarity value used in tuples #[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(test, derive(Debug))] pub struct ClarityName<'a>(pub &'a [u8]); impl<'a> ClarityName<'a> { @@ -239,7 +246,8 @@ impl<'a> ClarityName<'a> { } #[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(test, derive(Debug))] // we take HASH160_LEN + 1-byte hash mode pub struct StacksAddress<'a>(pub &'a [u8; STACKS_ADDR_LEN]); diff --git a/app/rust/src/parser/post_condition.rs b/app/rust/src/parser/post_condition.rs index a9134522..98c1365f 100644 --- a/app/rust/src/parser/post_condition.rs +++ b/app/rust/src/parser/post_condition.rs @@ -37,7 +37,8 @@ impl TryFrom for PostConditionPrincipalId { } #[repr(C)] -#[derive(Clone, Copy, PartialEq, Debug)] +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub enum PostConditionPrincipal<'a> { Origin, Standard(StacksAddress<'a>), @@ -127,7 +128,8 @@ impl<'a> PostConditionPrincipal<'a> { } #[repr(u8)] -#[derive(Clone, PartialEq, Copy, Debug)] +#[derive(Clone, PartialEq, Copy)] +#[cfg_attr(test, derive(Debug))] pub enum FungibleConditionCode { SentEq = 0x01, SentGt = 0x02, @@ -160,7 +162,8 @@ impl FungibleConditionCode { } #[repr(u8)] -#[derive(Clone, PartialEq, Copy, Debug)] +#[derive(Clone, PartialEq, Copy)] +#[cfg_attr(test, derive(Debug))] pub enum NonfungibleConditionCode { Sent = 0x10, NotSent = 0x11, @@ -208,7 +211,8 @@ impl TryFrom for PostConditionType { /// Post-condition on a transaction #[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub enum TransactionPostCondition<'a> { STX(&'a [u8]), Fungible(&'a [u8]), diff --git a/app/rust/src/parser/principal.rs b/app/rust/src/parser/principal.rs index b10caabc..f589ff77 100644 --- a/app/rust/src/parser/principal.rs +++ b/app/rust/src/parser/principal.rs @@ -3,7 +3,8 @@ use nom::bytes::complete::take; use super::{c32, ContractName, ParserError, C32_ENCODED_ADDRS_LENGTH, HASH160_LEN}; #[repr(C)] -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Eq, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub struct StandardPrincipal<'a>(pub &'a [u8]); impl<'a> StandardPrincipal<'a> { @@ -18,7 +19,8 @@ impl<'a> StandardPrincipal<'a> { } #[repr(C)] -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Eq, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub struct ContractPrincipal<'a>(StandardPrincipal<'a>, ContractName<'a>); impl<'a> ContractPrincipal<'a> { #[inline(never)] @@ -37,7 +39,8 @@ impl<'a> ContractPrincipal<'a> { } #[repr(C)] -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Clone, Eq, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub struct PrincipalData<'a> { pub data: (StandardPrincipal<'a>, Option>), } diff --git a/app/rust/src/parser/spending_condition.rs b/app/rust/src/parser/spending_condition.rs index 8a0866f3..b5b4f6fc 100644 --- a/app/rust/src/parser/spending_condition.rs +++ b/app/rust/src/parser/spending_condition.rs @@ -37,7 +37,8 @@ const SPENDING_CONDITION_SIGNER_LEN: usize = 37; const SINGLE_SPENDING_CONDITION_LEN: usize = 66; #[repr(u8)] -#[derive(Debug, Clone, PartialEq, Copy)] +#[derive(Clone, PartialEq, Copy)] +#[cfg_attr(test, derive(Debug))] pub enum TransactionPublicKeyEncoding { // ways we can encode a public key Compressed = 0x00, @@ -62,7 +63,8 @@ impl TransactionPublicKeyEncoding { /// An auth field can be a public key or a signature. In both cases, the public key (either given /// in-the-raw or embedded in a signature) may be encoded as compressed or uncompressed. #[repr(u8)] -#[derive(Debug, Clone, PartialEq, Copy)] +#[derive(Clone, PartialEq, Copy)] +#[cfg_attr(test, derive(Debug))] pub enum TransactionAuthFieldID { // types of auth fields PublicKeyCompressed = 0x00, @@ -73,7 +75,8 @@ pub enum TransactionAuthFieldID { // {FT} Replacer 37 with SPENDING_CONDITION_SIGNER_LEN #[repr(C)] -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Clone)] +#[cfg_attr(test, derive(Debug))] pub struct SpendingConditionSigner<'a> { pub data: &'a [u8; SPENDING_CONDITION_SIGNER_LEN], } @@ -156,7 +159,8 @@ impl<'a> SpendingConditionSigner<'a> { } #[repr(C)] -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Clone)] +#[cfg_attr(test, derive(Debug))] pub struct SinglesigSpendingCondition<'a>(&'a [u8; SINGLE_SPENDING_CONDITION_LEN]); /// A structure that encodes enough state to authenticate @@ -164,11 +168,13 @@ pub struct SinglesigSpendingCondition<'a>(&'a [u8; SINGLE_SPENDING_CONDITION_LEN /// public_keys + signatures_required determines the Principal. /// nonce is the "check number" for the Principal. #[repr(C)] -#[derive(Debug, PartialEq, Clone)] +#[derive(PartialEq, Clone)] +#[cfg_attr(test, derive(Debug))] pub struct MultisigSpendingCondition<'a>(&'a [u8]); #[repr(C)] -#[derive(Debug, PartialEq, Clone)] +#[derive(PartialEq, Clone)] +#[cfg_attr(test, derive(Debug))] pub enum SpendingConditionSignature<'a> { Singlesig(SinglesigSpendingCondition<'a>), Multisig(MultisigSpendingCondition<'a>), @@ -191,7 +197,8 @@ impl<'a> SpendingConditionSignature<'a> { } #[repr(C)] -#[derive(Debug, PartialEq, Clone)] +#[derive(PartialEq, Clone)] +#[cfg_attr(test, derive(Debug))] pub struct TransactionSpendingCondition<'a> { pub signer: SpendingConditionSigner<'a>, signature: SpendingConditionSignature<'a>, @@ -383,14 +390,15 @@ impl<'a> TransactionSpendingCondition<'a> { } else if self.is_multisig() && buf_len >= STANDARD_MULTISIG_AUTH_LEN { // fills with zeroes // 16-byte fee and nonce - // 4-byte signature fields count + // 4-byte num auth fields buf.iter_mut().take(20).for_each(|v| *v = 0); - // append the signatures count at the end - if let Some(count) = self.required_signatures() { - buf[20..STANDARD_MULTISIG_AUTH_LEN].copy_from_slice(&count.to_be_bytes()); - return Ok(STANDARD_MULTISIG_AUTH_LEN); - } + // append the signatures count at the end 2-bytes + let count = self + .required_signatures() + .ok_or(ParserError::parser_no_data)?; + buf[20..STANDARD_MULTISIG_AUTH_LEN].copy_from_slice(&count.to_be_bytes()); + return Ok(STANDARD_MULTISIG_AUTH_LEN); } Err(ParserError::parser_no_data) } diff --git a/app/rust/src/parser/structured_msg.rs b/app/rust/src/parser/structured_msg.rs index b77bd4ba..fc2355d2 100644 --- a/app/rust/src/parser/structured_msg.rs +++ b/app/rust/src/parser/structured_msg.rs @@ -13,7 +13,8 @@ use crate::bolos::{sha256, SHA256_LEN}; use hex::encode_to_slice; #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub struct Domain<'a>(Value<'a>); impl<'a> Domain<'a> { @@ -74,7 +75,11 @@ impl<'a> Domain<'a> { let mut buff = [0; 39]; if let Some((key, value)) = self.tuple().iter().nth(display_idx as usize) { - out_key[0..key.name().len()].copy_from_slice(key.name()); + let name = key.name(); + let m = out_key + .get_mut(0..name.len()) + .ok_or(ParserError::parser_unexpected_buffer_end)?; + m.copy_from_slice(name); let id = value.value_id(); @@ -96,7 +101,8 @@ impl<'a> Domain<'a> { } #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub struct StructuredMsg<'a>(&'a [u8]); impl<'a> StructuredMsg<'a> { diff --git a/app/rust/src/parser/transaction.rs b/app/rust/src/parser/transaction.rs index cd843adf..7e7d3a50 100644 --- a/app/rust/src/parser/transaction.rs +++ b/app/rust/src/parser/transaction.rs @@ -27,14 +27,16 @@ use crate::{check_canary, zxformat}; const MULTISIG_PREVIOUS_SIGNER_DATA_LEN: usize = 98; #[repr(u8)] -#[derive(Debug, Clone, PartialEq, Copy)] +#[derive(Clone, PartialEq, Copy)] +#[cfg_attr(test, derive(Debug))] pub enum TransactionAuthFlags { Standard = 0x04, Sponsored = 0x05, } #[repr(u8)] -#[derive(Debug, Clone, PartialEq, Copy)] +#[derive(Clone, PartialEq, Copy)] +#[cfg_attr(test, derive(Debug))] pub enum TransactionPostConditionMode { Allow = 0x01, // allow any other changes not specified Deny = 0x02, // deny any other changes not specified @@ -60,7 +62,8 @@ impl TransactionPostConditionMode { } #[repr(u8)] -#[derive(Debug, Clone, PartialEq, Copy)] +#[derive(Clone, PartialEq, Copy)] +#[cfg_attr(test, derive(Debug))] pub enum TransactionAnchorMode { OnChainOnly = 1, // must be included in a StacksBlock OffChainOnly = 2, // must be included in a StacksMicroBlock @@ -88,7 +91,8 @@ impl TransactionAnchorMode { } #[repr(C)] -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub struct PostConditions<'a> { pub(crate) conditions: ArrayVec<[&'a [u8]; NUM_SUPPORTED_POST_CONDITIONS]>, num_items: u8, @@ -229,7 +233,8 @@ impl<'a> From<(&'a [u8], TxTuple<'a>)> for Transaction<'a> { } #[repr(C)] -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub struct Transaction<'a> { pub version: TransactionVersion, pub chain_id: u32, diff --git a/app/rust/src/parser/transaction_auth.rs b/app/rust/src/parser/transaction_auth.rs index 9e587e00..f85f6cc6 100644 --- a/app/rust/src/parser/transaction_auth.rs +++ b/app/rust/src/parser/transaction_auth.rs @@ -18,7 +18,8 @@ const SPONSOR_SENTINEL_LEN: usize = 21 + 16 + 66; /// this structure contains the address of the origin account, /// signature(s) and signature threshold for the origin account #[repr(C)] -#[derive(Debug, PartialEq, Clone)] +#[derive(PartialEq, Clone)] +#[cfg_attr(test, derive(Debug))] pub enum TransactionAuth<'a> { // 0x04 Standard(TransactionSpendingCondition<'a>), diff --git a/app/rust/src/parser/transaction_payload.rs b/app/rust/src/parser/transaction_payload.rs index e9c4196e..24e4484a 100644 --- a/app/rust/src/parser/transaction_payload.rs +++ b/app/rust/src/parser/transaction_payload.rs @@ -9,8 +9,8 @@ use arrayvec::ArrayVec; use numtoa::NumToA; use super::{ - ClarityName, ContractName, PrincipalData, StacksAddress, C32_ENCODED_ADDRS_LENGTH, HASH160_LEN, - TX_DEPTH_LIMIT, + utils::ApduPanic, ClarityName, ContractName, PrincipalData, StacksAddress, + C32_ENCODED_ADDRS_LENGTH, HASH160_LEN, TX_DEPTH_LIMIT, }; use crate::parser::error::ParserError; @@ -28,7 +28,8 @@ pub const CONTRACT_CALL_BASE_ITEMS: u8 = 3; pub const MAX_STRING_ASCII_TO_SHOW: usize = 60; #[repr(u8)] -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub enum TokenTranferPrincipal { Standard = 0x05, Contract = 0x06, @@ -45,7 +46,8 @@ impl TokenTranferPrincipal { } #[repr(C)] -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub struct StxTokenTransfer<'a>(&'a [u8]); impl<'a> StxTokenTransfer<'a> { @@ -64,27 +66,36 @@ impl<'a> StxTokenTransfer<'a> { pub fn memo(&self) -> &[u8] { let at = self.0.len() - 34; - &self.0[at..] + // safe to unwrap as parser checked for proper len + self.0.get(at..).apdu_unwrap() } pub fn amount(&self) -> Result { let at = self.0.len() - 34 - 8; - be_u64::<'a, ParserError>(&self.0[at..]) + let amount = self.0.get(at..).ok_or(ParserError::parser_no_data)?; + be_u64::<'a, ParserError>(amount) .map(|res| res.1) .map_err(|_| ParserError::parser_unexpected_buffer_end) } pub fn raw_address(&self) -> &[u8] { // Skips the principal-id and hash_mode - &self.0[2..22] + // is valid as this was check by the parser + // safe to unwrap as this was checked at parsing + self.0.get(2..22).apdu_unwrap() } pub fn encoded_address( &self, ) -> Result, ParserError> { // Skips the principal-id at [0] and uses hash_mode and the follow 20-bytes - let version = self.0[1]; - c32::c32_address(version, &self.0[2..22]) + let version = self.0.get(1).ok_or(ParserError::parser_no_data)?; + c32::c32_address( + *version, + self.0 + .get(2..22) + .ok_or(ParserError::parser_invalid_address)?, + ) } pub fn amount_stx(&self) -> Result, ParserError> { @@ -139,7 +150,8 @@ impl<'a> StxTokenTransfer<'a> { } #[repr(C)] -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub struct Arguments<'a>(&'a [u8]); impl<'a> Arguments<'a> { @@ -192,7 +204,8 @@ impl<'a> Arguments<'a> { /// A transaction that calls into a smart contract #[repr(C)] -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub struct TransactionContractCall<'a>(&'a [u8]); impl<'a> TransactionContractCall<'a> { @@ -443,7 +456,8 @@ impl<'a> TransactionContractCall<'a> { /// A transaction that instantiates a smart contract #[repr(C)] -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub struct TransactionSmartContract<'a>(&'a [u8]); impl<'a> TransactionSmartContract<'a> { @@ -486,7 +500,8 @@ impl<'a> TransactionSmartContract<'a> { } #[repr(u8)] -#[derive(Debug, Clone, PartialEq, Copy)] +#[derive(Clone, PartialEq, Copy)] +#[cfg_attr(test, derive(Debug))] pub enum TransactionPayloadId { TokenTransfer = 0, SmartContract = 1, @@ -505,7 +520,8 @@ impl TransactionPayloadId { } #[repr(C)] -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub enum TransactionPayload<'a> { TokenTransfer(StxTokenTransfer<'a>), SmartContract(TransactionSmartContract<'a>), diff --git a/app/rust/src/parser/utils.rs b/app/rust/src/parser/utils.rs index 10e857a8..0b30f2d1 100644 --- a/app/rust/src/parser/utils.rs +++ b/app/rust/src/parser/utils.rs @@ -30,3 +30,55 @@ pub fn read_varint(input: &[u8]) -> Result<(&[u8], u64), nom::Err> _ => Ok((rem, prefix as _)), } } + +pub trait ApduPanic: Sized { + type Item; + + fn apdu_unwrap(self) -> Self::Item; + + fn apdu_expect(self, s: &str) -> Self::Item; +} + +impl ApduPanic for Result { + type Item = T; + + #[inline] + fn apdu_unwrap(self) -> Self::Item { + match self { + Ok(t) => t, + // be sure this point is unreachable when calling this function + Err(_) => unsafe { std::hint::unreachable_unchecked() }, + } + } + + #[inline] + fn apdu_expect(self, _: &str) -> Self::Item { + match self { + Ok(t) => t, + // be sure this point is unreachable when calling this function + Err(_) => unsafe { std::hint::unreachable_unchecked() }, + } + } +} + +impl ApduPanic for Option { + type Item = T; + + #[inline] + fn apdu_unwrap(self) -> Self::Item { + match self { + Some(t) => t, + // be sure this point is unreachable when calling this function + _ => unsafe { std::hint::unreachable_unchecked() }, + } + } + + #[inline] + fn apdu_expect(self, _: &str) -> Self::Item { + match self { + Some(t) => t, + // be sure this point is unreachable when calling this function + _ => unsafe { std::hint::unreachable_unchecked() }, + } + } +} diff --git a/app/rust/src/parser/value/int.rs b/app/rust/src/parser/value/int.rs index e616384f..aa142f62 100644 --- a/app/rust/src/parser/value/int.rs +++ b/app/rust/src/parser/value/int.rs @@ -11,17 +11,20 @@ const INT_WIDTH: usize = 16; // Represents the inner bytes which conform either a Int128 as defined by SIP005 regarding // clarity values types. #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] struct IntBytes<'a>(&'a [u8; INT_WIDTH]); // Represents a clarity signed integer of 128 bits #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub struct Int128(i128); // Represents a clarity unsigned integer of 128 bits #[repr(C)] -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub struct UInt128(u128); impl Int128 { diff --git a/app/rust/src/parser/value/string.rs b/app/rust/src/parser/value/string.rs index 05f6ce4d..cca98977 100644 --- a/app/rust/src/parser/value/string.rs +++ b/app/rust/src/parser/value/string.rs @@ -3,17 +3,20 @@ use nom::{bytes::complete::take, number::complete::be_u32}; // Represent a clarity value string #[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(test, derive(Debug))] struct String<'a>(&'a [u8]); // Represent a clarity value string #[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub struct StringAscii<'a>(String<'a>); // Represent a clarity value string #[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(test, derive(Debug))] pub struct StringUtf8<'a>(String<'a>); impl<'a> String<'a> { diff --git a/app/rust/src/parser/value/tuple.rs b/app/rust/src/parser/value/tuple.rs index 4dd5c54a..e383e825 100644 --- a/app/rust/src/parser/value/tuple.rs +++ b/app/rust/src/parser/value/tuple.rs @@ -3,7 +3,8 @@ use nom::number::complete::be_u32; // This type is meant to get from the Value type #[repr(C)] -#[derive(Debug, Clone, PartialEq, Copy)] +#[derive(Clone, PartialEq, Copy)] +#[cfg_attr(test, derive(Debug))] pub struct Tuple<'a>(pub &'a [u8]); impl<'a> Tuple<'a> { @@ -42,7 +43,8 @@ impl<'a> Tuple<'a> { } #[repr(C)] -#[derive(Debug, Clone, PartialEq, Copy)] +#[derive(Clone, PartialEq, Copy)] +#[cfg_attr(test, derive(Debug))] pub struct TupleIter<'a> { data: &'a [u8], read: usize, diff --git a/app/rust/src/zxformat.rs b/app/rust/src/zxformat.rs index f882aad7..06c1edfc 100644 --- a/app/rust/src/zxformat.rs +++ b/app/rust/src/zxformat.rs @@ -65,6 +65,7 @@ macro_rules! num_to_str { } num_to_str!(u64_to_str, u64); + num_to_str!(i64_to_str, i64); /// Fixed point u64 number @@ -118,6 +119,7 @@ pub fn fpu64_to_str_check_test( /// * `decimals`: the number of decimals after the decimal point /// # Returns /// The number of bytes written if success or Error otherwise +#[cfg(any(test, fuzzing))] pub fn fpi64_to_str(out: &mut [u8], value: i64, decimals: u8) -> Result { let mut temp = [0u8; MAX_STR_BUFF_LEN]; let len = i64_to_str(temp.as_mut(), value)?; diff --git a/deps/ledger-zxlib b/deps/ledger-zxlib index 82f1655c..6fe9c31b 160000 --- a/deps/ledger-zxlib +++ b/deps/ledger-zxlib @@ -1 +1 @@ -Subproject commit 82f1655cd3c69f7184f21bb9174485336df5b7ba +Subproject commit 6fe9c31bf8a4e1fc34ed5d31ef6e77624b133ac9 diff --git a/tests_zemu/package.json b/tests_zemu/package.json index 084bb7c4..56307cac 100644 --- a/tests_zemu/package.json +++ b/tests_zemu/package.json @@ -6,7 +6,8 @@ "description": "", "main": "index.js", "scripts": { - "test": "jest", + "clean": "ts-node tests/pullImageKillOld.ts", + "test": "yarn clean && jest --detectOpenHandles --testPathIgnorePatterns='dev.*|testvectors.*'", "try": "node try.mjs" }, "repository": { @@ -44,6 +45,7 @@ "prettier": "^2.3.2", "secp256k1": "^4.0.2", "ts-jest": "^27.0.5", + "ts-node": "^10.9.1", "typescript": "^4.4.2", "varuint-bitcoin": "^1.1.2", "@ledgerhq/hw-transport-node-hid": "^6.27.2" diff --git a/tests_zemu/snapshots/s-mainmenu/00004.png b/tests_zemu/snapshots/s-mainmenu/00004.png index 568273097ca2e04419cf88cdb5a0878c30f1175c..727fe0ce6cb8a7cd21c968d80ce8d83685ce4933 100644 GIT binary patch delta 416 zcmV;R0bl;N1G)o{B!7WPL_t(|ob8#}5`!QNMYU7^|AT#KA8;5-780}(+Iyx?$YO8E zA}k940DymGja%xq7t3y^Cg~nN9?zh4sBk|gWgk%nsL0-s%^>-Q-kw@6@vWPP3PD+; zERf^@R9VR$O1F7<1g%r)PI>LeuWT{SSO#wF=Jhqs^81K9ub?R`pZlUHn>>kVHQ_%CFF_tfwT zo+S1@z?c5T1GfW^B!7NML_t(|ob8#(uEQVkZV$I|EPJYSb z3|3*!w|?B$(fqJVRBe#PQWmm1d*E>?ER7x@e(-Q-kw@6@vWPP3PD+; zERf^@R9VR$O1F7<1g%r)PI>LeuWT{SSO#wF=Jhqs^81K9ub?R`pZlUHn>>kVHQ_%CFF_tfwT zo+S1@z?c5T1GfW^B!7NML_t(|ob8#(uEQVkZV$I|EPJYSb z3|3*!w|?B$(fqJVRBe#PQWmm1d*E>?ER7x@e(tEGc6HZ;2C13S>b?T`* zv(8-?nquU|xHYZ8XIm)$QzQO&*O-3XBall-b)hHLkzXdHwv6JsZ+aEZOg) znsq6@M=Byobkh~inNoASj=g@ZvB=B5m!ntw+TXtp!E=tky#6NhquJ5F|AfCyyAnU) z{LAge4wl)U%XoaAUvywx^;XBR`^LN&--oZeBkKCD?ft>@lD&QX!n@}WX_i;+imWw! hTNlHO@IAxyFD#EGe#rh0&awiDdb;|#taD0e0suoYoOl2L delta 350 zcmaFI^pk0VO1*=pi(^Q|oVPb)g_;$3+yZaE^tg?EZ4zdODD%g1*IdU)Qs1%B%cZSa!g-N|$7$A*4?cRtDJ z_HI+wr#(duw>T!8%=&6D%k{&zcL#i??w(S2O1WP9edN8wc=LXd)eOy%&3`z)+a5h8 zP$;s3p?0Cf(G0)87nd!l;`Pw9EZex#ZyL9AanW?~Q?9oo4y@lIkUyvKXF}GACG|7i zt}LlH%zDrndE&Cui7O96jP6B53;KS`JF;NUhOFM%=?hnVd;G@i*hJIiF-QIi^-a4H zKjHlEYk~>yw}!LoWGxW9u>9AK`DN8iY-@LxGn&1P`I%RoneBrvp&feVH>#= rPEMM#GyMJBv*MT8ML3ZHqT~nD39UzbETLEDfkZuB{an^LB{Ts522!H^ diff --git a/tests_zemu/snapshots/sp-mainmenu/00010.png b/tests_zemu/snapshots/sp-mainmenu/00010.png index 68f00a8a22fab99ff0acefe95597223b41fbe0f8..d6c15f01f0c7d25156797b696f8f5b730db9cfef 100644 GIT binary patch delta 339 zcmey#^p0tQO1-J4i(^Q|oVPav`3@-vxCWZMTk<tEGc6HZ;2C13S>b?T`* zv(8-?nquU|xHYZ8XIm)$QzQO&*O-3XBall-b)hHLkzXdHwv6JsZ+aEZOg) znsq6@M=Byobkh~inNoASj=g@ZvB=B5m!ntw+TXtp!E=tky#6NhquJ5F|AfCyyAnU) z{LAge4wl)U%XoaAUvywx^;XBR`^LN&--oZeBkKCD?ft>@lD&QX!n@}WX_i;+imWw! hTNlHO@IAxyFD#EGe#rh0&awiDdb;|#taD0e0suoYoOl2L delta 350 zcmaFI^pk0VO1*=pi(^Q|oVPb)g_;$3+yZaE^tg?EZ4zdODD%g1*IdU)Qs1%B%cZSa!g-N|$7$A*4?cRtDJ z_HI+wr#(duw>T!8%=&6D%k{&zcL#i??w(S2O1WP9edN8wc=LXd)eOy%&3`z)+a5h8 zP$;s3p?0Cf(G0)87nd!l;`Pw9EZex#ZyL9AanW?~Q?9oo4y@lIkUyvKXF}GACG|7i zt}LlH%zDrndE&Cui7O96jP6B53;KS`JF;NUhOFM%=?hnVd;G@i*hJIiF-QIi^-a4H zKjHlEYk~>yw}!LoWGxW9u>9AK`DN8iY-@LxGn&1P`I%RoneBrvp&feVH>#= rPEMM#GyMJBv*MT8ML3ZHqT~nD39UzbETLEDfkZuB{an^LB{Ts522!H^ diff --git a/tests_zemu/snapshots/x-mainmenu/00004.png b/tests_zemu/snapshots/x-mainmenu/00004.png index 68f00a8a22fab99ff0acefe95597223b41fbe0f8..d6c15f01f0c7d25156797b696f8f5b730db9cfef 100644 GIT binary patch delta 339 zcmey#^p0tQO1-J4i(^Q|oVPav`3@-vxCWZMTk<tEGc6HZ;2C13S>b?T`* zv(8-?nquU|xHYZ8XIm)$QzQO&*O-3XBall-b)hHLkzXdHwv6JsZ+aEZOg) znsq6@M=Byobkh~inNoASj=g@ZvB=B5m!ntw+TXtp!E=tky#6NhquJ5F|AfCyyAnU) z{LAge4wl)U%XoaAUvywx^;XBR`^LN&--oZeBkKCD?ft>@lD&QX!n@}WX_i;+imWw! hTNlHO@IAxyFD#EGe#rh0&awiDdb;|#taD0e0suoYoOl2L delta 350 zcmaFI^pk0VO1*=pi(^Q|oVPb)g_;$3+yZaE^tg?EZ4zdODD%g1*IdU)Qs1%B%cZSa!g-N|$7$A*4?cRtDJ z_HI+wr#(duw>T!8%=&6D%k{&zcL#i??w(S2O1WP9edN8wc=LXd)eOy%&3`z)+a5h8 zP$;s3p?0Cf(G0)87nd!l;`Pw9EZex#ZyL9AanW?~Q?9oo4y@lIkUyvKXF}GACG|7i zt}LlH%zDrndE&Cui7O96jP6B53;KS`JF;NUhOFM%=?hnVd;G@i*hJIiF-QIi^-a4H zKjHlEYk~>yw}!LoWGxW9u>9AK`DN8iY-@LxGn&1P`I%RoneBrvp&feVH>#= rPEMM#GyMJBv*MT8ML3ZHqT~nD39UzbETLEDfkZuB{an^LB{Ts522!H^ diff --git a/tests_zemu/snapshots/x-mainmenu/00010.png b/tests_zemu/snapshots/x-mainmenu/00010.png index 68f00a8a22fab99ff0acefe95597223b41fbe0f8..d6c15f01f0c7d25156797b696f8f5b730db9cfef 100644 GIT binary patch delta 339 zcmey#^p0tQO1-J4i(^Q|oVPav`3@-vxCWZMTk<tEGc6HZ;2C13S>b?T`* zv(8-?nquU|xHYZ8XIm)$QzQO&*O-3XBall-b)hHLkzXdHwv6JsZ+aEZOg) znsq6@M=Byobkh~inNoASj=g@ZvB=B5m!ntw+TXtp!E=tky#6NhquJ5F|AfCyyAnU) z{LAge4wl)U%XoaAUvywx^;XBR`^LN&--oZeBkKCD?ft>@lD&QX!n@}WX_i;+imWw! hTNlHO@IAxyFD#EGe#rh0&awiDdb;|#taD0e0suoYoOl2L delta 350 zcmaFI^pk0VO1*=pi(^Q|oVPb)g_;$3+yZaE^tg?EZ4zdODD%g1*IdU)Qs1%B%cZSa!g-N|$7$A*4?cRtDJ z_HI+wr#(duw>T!8%=&6D%k{&zcL#i??w(S2O1WP9edN8wc=LXd)eOy%&3`z)+a5h8 zP$;s3p?0Cf(G0)87nd!l;`Pw9EZex#ZyL9AanW?~Q?9oo4y@lIkUyvKXF}GACG|7i zt}LlH%zDrndE&Cui7O96jP6B53;KS`JF;NUhOFM%=?hnVd;G@i*hJIiF-QIi^-a4H zKjHlEYk~>yw}!LoWGxW9u>9AK`DN8iY-@LxGn&1P`I%RoneBrvp&feVH>#= rPEMM#GyMJBv*MT8ML3ZHqT~nD39UzbETLEDfkZuB{an^LB{Ts522!H^ diff --git a/tests_zemu/tests/pullImageKillOld.ts b/tests_zemu/tests/pullImageKillOld.ts new file mode 100644 index 00000000..ddd30b71 --- /dev/null +++ b/tests_zemu/tests/pullImageKillOld.ts @@ -0,0 +1,4 @@ +import Zemu from '@zondax/zemu' + +Zemu.checkAndPullImage() +Zemu.stopAllEmuContainers() diff --git a/tests_zemu/tests/sign_structured_data.test.ts b/tests_zemu/tests/sign_structured_data.test.ts index 80e8a24e..47ed2b3e 100644 --- a/tests_zemu/tests/sign_structured_data.test.ts +++ b/tests_zemu/tests/sign_structured_data.test.ts @@ -49,6 +49,8 @@ const defaultOptions = { X11: false, } +jest.setTimeout(180000) + const DOMAIN = tupleCV({ 'name': stringAsciiCV("Stacks"), 'version': stringAsciiCV("1.0.0"), @@ -126,8 +128,6 @@ const SIGN_TEST_DATA = [ }, ] - - describe.each(models)('StructuredData', function (m) { test.each(SIGN_TEST_DATA)(`sign_structured_data_tuple`, async function ({ name, op } ) { diff --git a/tests_zemu/tests/standard.test.ts b/tests_zemu/tests/standard.test.ts index bbe662a8..36935aa0 100644 --- a/tests_zemu/tests/standard.test.ts +++ b/tests_zemu/tests/standard.test.ts @@ -52,6 +52,8 @@ const defaultOptions = { X11: false, } +jest.setTimeout(180000) + describe('Standard', function () { test.each(models)('can start and stop container', async function (m) { const sim = new Zemu(m.path)