Skip to content

Commit

Permalink
Merge pull request #34 from LedgerHQ/use-serde
Browse files Browse the repository at this point in the history
Use serde
  • Loading branch information
agrojean-ledger authored Nov 27, 2023
2 parents 683338a + 0c2c24e commit 3182781
Show file tree
Hide file tree
Showing 14 changed files with 196 additions and 259 deletions.
87 changes: 83 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ ledger_device_sdk = "1.0.1"
ledger_device_ui_sdk = "1.1.1"
ledger_secure_sdk_sys = "1.0.1"
include_gif = "1.0.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"] }
numtoa = "0.2.4"

[profile.release]
Expand Down
23 changes: 11 additions & 12 deletions src/app_ui/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,28 @@
* limitations under the License.
*****************************************************************************/

use crate::utils::{concatenate, to_hex_all_caps};
use crate::AppSW;
use core::str::from_utf8;
use core::str::from_utf8_mut;
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<bool, AppSW> {
let addr_hex_str_buf = to_hex_all_caps(&addr[addr.len() - DISPLAY_ADDR_BYTES_LEN..])
.map_err(|_| AppSW::AddrDisplayFail)?;
let addr_hex_str = from_utf8(&addr_hex_str_buf[..DISPLAY_ADDR_BYTES_LEN * 2])
.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(|_| AppSW::AddrDisplayFail)?;
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 my_field = [Field {
name: "Address",
value: addr_hex_str_with_prefix,
value: addr_hex,
}];

let my_review = MultiFieldReview::new(
Expand Down
15 changes: 5 additions & 10 deletions src/app_ui/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,8 @@ fn ui_about_menu(comm: &mut Comm) -> Event<ApduHeader> {
loop {
match MultiPageMenu::new(comm, &pages).show() {
EventOrPageIndex::Event(e) => return e,
i => {
if let EventOrPageIndex::Index(1) = i {
return ui_menu_main(comm);
}
}
EventOrPageIndex::Index(1) => return ui_menu_main(comm),
EventOrPageIndex::Index(_) => (),
}
}
}
Expand All @@ -50,11 +47,9 @@ pub fn ui_menu_main(comm: &mut Comm) -> Event<ApduHeader> {
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) => ledger_device_sdk::exit_app(0),
_ => (),
},
EventOrPageIndex::Index(2) => return ui_about_menu(comm),
EventOrPageIndex::Index(3) => ledger_device_sdk::exit_app(0),
EventOrPageIndex::Index(_) => (),
}
}
}
51 changes: 26 additions & 25 deletions src/app_ui/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,51 +14,52 @@
* 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::utils::concatenate;
use crate::AppSW;
use core::str::from_utf8;
use ledger_device_ui_sdk::bitmaps::{CROSSMARK, EYE, VALIDATE_14};
use ledger_device_ui_sdk::ui::{Field, MultiFieldReview};
use numtoa::NumToA;

const MAX_COIN_LENGTH: usize = 10;

/// 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.
///
/// # Arguments
///
/// * `tx` - Transaction to be displayed for validation
pub fn ui_display_tx(tx: &Tx) -> Result<bool, AppSW> {
// 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(|_| AppSW::TxDisplayFail)?
.trim_matches(char::from(0));
// Generate string for amount
let mut numtoa_buf = [0u8; 20];
let mut value_buf = [0u8; 20 + MAX_COIN_LENGTH + 1];

// Format destination address
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(|_| AppSW::TxDisplayFail)?;
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

// Format memo
let memo_str = from_utf8(&tx.memo[..tx.memo_len]).map_err(|_| AppSW::TxDisplayFail)?;
// 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();

// Define transaction review fields
let my_fields = [
Field {
name: "Amount",
value: amount_str_with_denom,
value: value_str,
},
Field {
name: "Destination",
value: hex_addr_str_with_prefix,
value: core::str::from_utf8(&to_str).unwrap(),
},
Field {
name: "Memo",
value: memo_str,
value: tx.memo,
},
];

Expand Down
57 changes: 12 additions & 45 deletions src/handlers/sign_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,62 +15,30 @@
* limitations under the License.
*****************************************************************************/
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::utils::{read_bip32_path, MAX_ALLOWED_PATH_LEN};
use crate::AppSW;
use ledger_device_sdk::ecc::{Secp256k1, SeedDerive};
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,
};

use serde::Deserialize;
use serde_json_core::from_slice;

const MAX_TRANSACTION_LEN: usize = 510;

#[derive(Deserialize)]
pub struct Tx<'a> {
#[allow(dead_code)]
nonce: u64,
pub coin: &'a str,
pub value: u64,
pub to: &'a [u8],
pub memo: &'a [u8],
pub memo_len: usize,
}

// Implement deserialize for Tx from a u8 array
impl<'a> TryFrom<&'a [u8]> for Tx<'a> {
type Error = ();
fn try_from(raw_tx: &'a [u8]) -> Result<Self, Self::Error> {
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)?;
// Amount value
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;
// 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,
to,
memo,
memo_len,
})
}
#[serde(with = "hex::serde")] // Allows JSON deserialization from hex string
pub to: [u8; 20],
pub memo: &'a str,
}

// #[derive(Copy, Clone)]
pub struct TxContext {
raw_tx: [u8; MAX_TRANSACTION_LEN], // raw transaction serialized
raw_tx_len: usize, // length of raw transaction
Expand Down Expand Up @@ -130,10 +98,9 @@ pub fn handler_sign_tx(
return Ok(());
// Otherwise, try to parse the transaction
} else {
let tx = match Tx::try_from(&ctx.raw_tx[..ctx.raw_tx_len]) {
Ok(tx) => tx,
Err(_) => return Err(AppSW::TxParsingFail),
};
// Try to deserialize the transaction
let (tx, _): (Tx, usize) =
from_slice(&ctx.raw_tx[..ctx.raw_tx_len]).map_err(|_| AppSW::TxParsingFail)?;
// Display transaction. If user approves
// the transaction, sign it. Otherwise,
// return a "deny" status word.
Expand Down
Loading

0 comments on commit 3182781

Please sign in to comment.