Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use serde #34

Merged
merged 12 commits into from
Nov 27, 2023
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();
agrojean-ledger marked this conversation as resolved.
Show resolved Hide resolved
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(),
agrojean-ledger marked this conversation as resolved.
Show resolved Hide resolved
},
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
Loading