-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add transaction signing APDU handling and display.
- Loading branch information
1 parent
3b35d0d
commit b16dc85
Showing
6 changed files
with
430 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/***************************************************************************** | ||
* 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 nanos_sdk::io; | ||
use nanos_ui::bitmaps::{EYE, VALIDATE_14, CROSSMARK}; | ||
use nanos_ui::ui::{MultiFieldReview, Field}; | ||
use crate::handlers::sign_tx::Tx; | ||
use crate::utils::{concatenate, to_hex}; | ||
use numtoa::NumToA; | ||
|
||
use nanos_sdk::testing; | ||
|
||
pub fn ui_display_tx(tx: &Tx) -> Result<bool, io::Reply> { | ||
// Todo add error handling | ||
|
||
testing::debug_print("Display Tx 1\n"); | ||
|
||
// 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); | ||
|
||
testing::debug_print("Display Tx 2\n"); | ||
|
||
let amount_str_with_denom = core::str::from_utf8(&amount_with_denom_buf).unwrap(); | ||
|
||
testing::debug_print("Display Tx 3\n"); | ||
|
||
// Format destination address | ||
let hex_address = to_hex(&tx.to).unwrap(); | ||
let hex_address_str = core::str::from_utf8(&hex_address).unwrap(); | ||
|
||
testing::debug_print("Display Tx 4\n"); | ||
|
||
// Format memo | ||
let memo_str = core::str::from_utf8(&tx.memo).unwrap(); | ||
|
||
testing::debug_print("Display Tx 5\n"); | ||
|
||
// Define transaction review fields | ||
let my_fields = [ | ||
Field { | ||
name: "Amount", | ||
value: amount_str_with_denom, | ||
}, | ||
Field { | ||
name: "Destination", | ||
value: hex_address_str, | ||
}, | ||
Field { | ||
name: "Memo", | ||
value: memo_str, | ||
}, | ||
]; | ||
|
||
testing::debug_print("Display Tx 6\n"); | ||
|
||
// Create transaction review | ||
let my_review = MultiFieldReview::new( | ||
&my_fields, | ||
&["Review ", "Transaction"], | ||
Some(&EYE), | ||
"Approve", | ||
Some(&VALIDATE_14), | ||
"Reject", | ||
Some(&CROSSMARK), | ||
); | ||
|
||
testing::debug_print("Display Tx 7\n"); | ||
|
||
Ok(my_review.show()) | ||
} | ||
|
||
// This is the UI flow for signing, composed of a scroller | ||
// to read the incoming message, a panel that requests user | ||
// validation, and an exit message. | ||
// fn sign_ui(message: &[u8]) -> Result<Option<([u8; 72], u32, u32)>, SyscallError> { | ||
// let hex = utils::to_hex(message).map_err(|_| SyscallError::Overflow)?; | ||
// let m = from_utf8(&hex).map_err(|_| SyscallError::InvalidParameter)?; | ||
// let my_field = [ui::Field { | ||
// name: "Data", | ||
// value: m, | ||
// }]; | ||
|
||
// let my_review = ui::MultiFieldReview::new( | ||
// &my_field, | ||
// &["Review ", "Transaction"], | ||
// Some(&EYE), | ||
// "Approve", | ||
// Some(&VALIDATE_14), | ||
// "Reject", | ||
// Some(&CROSSMARK), | ||
// ); | ||
|
||
// if my_review.show() { | ||
// let signature = Secp256k1::derive_from_path(&BIP32_PATH) | ||
// .deterministic_sign(message) | ||
// .map_err(|_| SyscallError::Unspecified)?; | ||
// ui::popup("Done !"); | ||
// Ok(Some(signature)) | ||
// } else { | ||
// ui::popup("Cancelled"); | ||
// Ok(None) | ||
// } | ||
// } | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
/***************************************************************************** | ||
* 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::app_ui::sign::ui_display_tx; | ||
use crate::utils::{read_bip32_path, MAX_ALLOWED_PATH_LEN}; | ||
use crate::{SW_DENY, SW_TX_PARSING_FAIL, SW_WRONG_TX_LENGTH}; | ||
// use nanos_sdk::ecc::{Secp256k1, SeedDerive}; | ||
use nanos_sdk::{io, testing}; | ||
|
||
const MAX_TRANSACTION_LEN: usize = 510; | ||
const MAX_DER_SIG_LEN: usize = 72; | ||
|
||
pub struct Tx { | ||
nonce: u64, | ||
pub value: u64, | ||
pub to: [u8; 20], | ||
pub memo: [u8; 255], | ||
pub memo_len: usize, | ||
} | ||
|
||
// Implement deserialize for Tx with TryFrom u8 array | ||
impl TryFrom<&[u8]> for Tx { | ||
type Error = (); | ||
fn try_from(raw_tx: &[u8]) -> Result<Self, Self::Error> { | ||
if raw_tx.len() > MAX_TRANSACTION_LEN { | ||
return Err(()); | ||
} | ||
|
||
testing::debug_print("Parsing 1\n"); | ||
|
||
// Try to parse the transaction fields : | ||
// Nonce | ||
let nonce_bytes = match raw_tx.get(..8) { | ||
Some(value) => value, | ||
None => return Err(()), | ||
}; | ||
let nonce = u64::from_be_bytes(nonce_bytes.try_into().unwrap()); | ||
|
||
testing::debug_print("Parsing 2\n"); | ||
|
||
// Amount value | ||
let value_bytes = match raw_tx.get(8..16) { | ||
Some(value) => value, | ||
None => return Err(()), | ||
}; | ||
let value = u64::from_be_bytes(value_bytes.try_into().unwrap()); | ||
|
||
testing::debug_print("Parsing 3\n"); | ||
|
||
// To address | ||
let to: [u8;20] = match raw_tx.get(16..36) { | ||
Some(value) => value.try_into().unwrap(), | ||
None => return Err(()), | ||
}; | ||
|
||
testing::debug_print("Parsing 4\n"); | ||
|
||
// Memo length | ||
let memo_len: usize = match raw_tx.get(36) { | ||
Some(value) => *value as usize, | ||
None => return Err(()), | ||
}; | ||
|
||
testing::debug_print("Parsing 5\n"); | ||
|
||
// Memo | ||
let mut memo: [u8; 255] = [0u8; 255]; | ||
match raw_tx.get(37..(37 + memo_len) as usize) { | ||
Some(value) => memo[0..memo_len].copy_from_slice(value), | ||
None => return Err(()), | ||
}; | ||
|
||
testing::debug_print("Parsing 6\n"); | ||
|
||
// Check memo ASCII encoding | ||
if !memo.iter().all(|&byte| byte.is_ascii()) { | ||
return Err(()); | ||
} | ||
|
||
testing::debug_print("Parsing 7\n"); | ||
|
||
Ok(Tx { | ||
nonce, | ||
value, | ||
to, | ||
memo, | ||
memo_len, | ||
}) | ||
} | ||
} | ||
|
||
// Implement constructor for Tx with default values | ||
impl Tx { | ||
fn new() -> Tx { | ||
Tx { | ||
nonce: 0, | ||
value: 0, | ||
to: [0u8; 20], | ||
memo: [0u8; 255], | ||
memo_len: 0, | ||
} | ||
} | ||
} | ||
|
||
// #[derive(Copy, Clone)] | ||
pub struct TxInfo { | ||
raw_tx: [u8; MAX_TRANSACTION_LEN], // raw transaction serialized | ||
raw_tx_len: usize, // length of raw transaction | ||
transaction: Tx, // structured transaction | ||
m_hash: [u8; 32], // message hash digest | ||
signature: [u8; MAX_DER_SIG_LEN], // transaction signature encoded in DER | ||
signature_len: usize, // length of transaction signature | ||
v: u8, // parity of y-coordinate of R in ECDSA signature | ||
} | ||
|
||
// Implement constructor for TxInfo with default values | ||
impl TxInfo { | ||
pub fn new() -> TxInfo { | ||
TxInfo { | ||
raw_tx: [0u8; MAX_TRANSACTION_LEN], | ||
raw_tx_len: 0, | ||
transaction: Tx::new(), | ||
m_hash: [0u8; 32], | ||
signature: [0u8; MAX_DER_SIG_LEN], | ||
signature_len: 0, | ||
v: 0, | ||
} | ||
} | ||
// Implement reset for TxInfo | ||
fn reset(&mut self) { | ||
self.raw_tx_len = 0; | ||
self.transaction = Tx::new(); | ||
self.m_hash = [0u8; 32]; | ||
self.signature = [0u8; MAX_DER_SIG_LEN]; | ||
self.signature_len = 0; | ||
self.v = 0; | ||
} | ||
} | ||
|
||
pub fn handler_sign_tx( | ||
comm: &mut io::Comm, | ||
chunk: u8, | ||
more: bool, | ||
txinfo: &mut TxInfo, | ||
) -> Result<(), io::Reply> { | ||
// Try to get data from comm. If there is no data, | ||
// the '?' operator will propagate the error. | ||
let data = comm.get_data()?; | ||
// First chunk, try to parse the path | ||
if chunk == 0 { | ||
testing::debug_print("First transaction chunk\n"); | ||
// Reset txinfo | ||
txinfo.reset(); | ||
let mut path = [0u32; MAX_ALLOWED_PATH_LEN]; | ||
// This will propagate the error if the path is invalid | ||
read_bip32_path(data, &mut path)?; | ||
// Next chunks, append data to raw_tx and return or parse | ||
// the transaction if it is the last chunk. | ||
} else { | ||
if txinfo.raw_tx_len + data.len() > MAX_TRANSACTION_LEN { | ||
return Err(io::Reply(SW_WRONG_TX_LENGTH)); | ||
} | ||
|
||
testing::debug_print("Next transaction chunks\n"); | ||
|
||
// Append data to raw_tx | ||
txinfo.raw_tx[txinfo.raw_tx_len..txinfo.raw_tx_len + data.len()].copy_from_slice(data); | ||
txinfo.raw_tx_len += data.len(); | ||
|
||
// If we expect more chunks, return | ||
if more { | ||
testing::debug_print("More chunks expected : return\n"); | ||
return Ok(()); | ||
// Otherwise, try to parse the transaction | ||
} else { | ||
testing::debug_print("Last chunk : parse transaction\n"); | ||
match Tx::try_from(&txinfo.raw_tx[..txinfo.raw_tx_len]) { | ||
Ok(tx) => { | ||
testing::debug_print("Transaction parsed\n"); | ||
txinfo.transaction = tx; | ||
} | ||
Err(_) => { | ||
testing::debug_print("Transaction parsing failed\n"); | ||
return Err(io::Reply(SW_TX_PARSING_FAIL)); | ||
} | ||
} | ||
|
||
// Display transaction. If user approves | ||
// the transaction, sign it. Otherwise, | ||
// return an error. | ||
if ui_display_tx(&txinfo.transaction)? | ||
{ | ||
return Ok(()) | ||
} | ||
else { | ||
return Err(io::Reply(SW_DENY)); | ||
} | ||
|
||
// let out = sign_ui(comm.get_data()?)?; | ||
// if let Some((signature_buf, length, _)) = out | ||
// { | ||
// comm.append(&signature_buf[..length as usize]) | ||
// } | ||
} | ||
} | ||
Ok(()) | ||
} |
Oops, something went wrong.