Skip to content

Commit

Permalink
Add transaction signing APDU handling and display.
Browse files Browse the repository at this point in the history
  • Loading branch information
agrojean-ledger committed Nov 13, 2023
1 parent 3b35d0d commit cb572c8
Show file tree
Hide file tree
Showing 7 changed files with 444 additions and 98 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
28 changes: 18 additions & 10 deletions src/app_ui/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,30 @@
* 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<bool, io::Reply> {
// Todo add error handling
// ======================
let hex = utils::to_hex(pk).unwrap();
let m = from_utf8(&hex).unwrap();
// ======================
pub fn ui_display_pk(addr: &[u8]) -> Result<bool, Reply> {
let address_hex_str_bytes =
to_hex_all_caps(addr).map_err(|_| Reply(SW_DISPLAY_ADDRESS_FAIL))?;
let address_hex_str =
from_utf8(&address_hex_str_bytes).map_err(|_| Reply(SW_DISPLAY_ADDRESS_FAIL))?;

let mut address_hex_str_with_prefix_buf = [0u8; 42];
concatenate(
&["0x", &address_hex_str[..addr.len() * 2]],
&mut address_hex_str_with_prefix_buf,
);
let address_hex_str_with_prefix =
from_utf8(&address_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: address_hex_str_with_prefix,
}];

let my_review = MultiFieldReview::new(
Expand Down
79 changes: 79 additions & 0 deletions src/app_ui/sign.rs
Original file line number Diff line number Diff line change
@@ -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<bool, Reply> {
// 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())
}
72 changes: 35 additions & 37 deletions src/handlers/get_public_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,54 @@
*****************************************************************************/

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()?;

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)))?;
.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[address.len() - 20 as usize..])? {
testing::debug_print("denied\n");
return Err(io::Reply(SW_DENY));
return Err(Reply(SW_DENY));
}
}

Expand All @@ -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<usize, io::Reply> {
// 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)
}
Loading

0 comments on commit cb572c8

Please sign in to comment.