Skip to content

Commit

Permalink
Stack usage optim : use slices, not arrays in Tx struct.
Browse files Browse the repository at this point in the history
Code cleanup.
  • Loading branch information
agrojean-ledger committed Nov 15, 2023
1 parent ee1e3c8 commit aa56193
Show file tree
Hide file tree
Showing 68 changed files with 152 additions and 177 deletions.
2 changes: 1 addition & 1 deletion src/app_ui/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub fn ui_display_tx(tx: &Tx) -> Result<bool, Reply> {
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))?;
let memo_str = from_utf8(&tx.memo[..tx.memo_len as usize]).map_err(|_| Reply(SW_TX_DISPLAY_FAIL))?;

// Define transaction review fields
let my_fields = [
Expand Down
44 changes: 9 additions & 35 deletions src/handlers/sign_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,18 @@ use nanos_sdk::testing;

const MAX_TRANSACTION_LEN: usize = 510;

pub struct Tx {
pub struct Tx<'a> {
nonce: u64,
pub value: u64,
pub to: [u8; 20],
pub memo: [u8; 255],
pub to: &'a [u8],
pub memo: &'a [u8],
pub memo_len: usize,
}

// Implement deserialize for Tx from a u8 array
impl TryFrom<&[u8]> for Tx {
impl<'a> TryFrom<&'a [u8]> for Tx<'a> {
type Error = ();
fn try_from(raw_tx: &[u8]) -> Result<Self, Self::Error> {
fn try_from(raw_tx: &'a [u8]) -> Result<Self, Self::Error> {
if raw_tx.len() > MAX_TRANSACTION_LEN {
return Err(());
}
Expand All @@ -46,23 +46,14 @@ impl TryFrom<&[u8]> for Tx {
// 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)?.try_into().map_err(|_| ())?;
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
// Memo will be trimmed to 255 bytes if it is longer
let (memo_len_u64, memo_len_size) = varint_read(&raw_tx[36..])?;
let memo_len = if memo_len_u64 < 255 {
memo_len_u64 as usize
} else {
255 as usize
};

let memo_len = memo_len_u64 as usize;
// Memo
let memo_slice = slice_or_err(raw_tx, 36 + memo_len_size, memo_len)?;
let mut memo = [0u8; 255];

memo[..memo_len].copy_from_slice(memo_slice);
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()) {
Expand Down Expand Up @@ -137,12 +128,10 @@ pub fn handler_sign_tx(
return Ok(());
// Otherwise, try to parse the transaction
} else {
testing::debug_print("Last chunk : parse transaction\n");
let tx = match Tx::try_from(&ctx.raw_tx[..ctx.raw_tx_len]) {
Ok(tx) => tx,
Err(_) => return Err(Reply(SW_TX_PARSING_FAIL)),
};
testing::debug_print("Transaction parsed\n");
// Display transaction. If user approves
// the transaction, sign it. Otherwise,
// return an error.
Expand All @@ -160,20 +149,10 @@ fn compute_signature_and_append(comm: &mut Comm, ctx: &mut TxContext) -> Result<
let mut keccak256: cx_sha3_t = Default::default();
let mut message_hash: [u8; 32] = [0u8; 32];

testing::debug_print("Signature is appended 1\n");
unsafe {
let res = cx_keccak_init_no_throw(&mut keccak256, 256);
if res != CX_OK {
// Print error
let err_buf = to_hex_all_caps(&res.to_be_bytes()).unwrap();
let err_str = core::str::from_utf8(&err_buf).unwrap();
testing::debug_print("Hashing err : ");
testing::debug_print(err_str);
testing::debug_print("\n");

if cx_keccak_init_no_throw(&mut keccak256, 256) != CX_OK {
return Err(Reply(SW_TX_HASH_FAIL));
}
testing::debug_print("Signature is appended 1.1\n");
if cx_hash_no_throw(
&mut keccak256.header as *mut cx_hash_t,
CX_LAST,
Expand All @@ -183,10 +162,8 @@ fn compute_signature_and_append(comm: &mut Comm, ctx: &mut TxContext) -> Result<
message_hash.len() as u32,
) != CX_OK
{
testing::debug_print("Hashing failed 2\n");
return Err(Reply(SW_TX_HASH_FAIL));
}
testing::debug_print("Signature is appended 1.2\n");
}

let (sig, siglen, parity) = Secp256k1::derive_from_path(&ctx.path[..ctx.path_len])
Expand All @@ -195,8 +172,5 @@ fn compute_signature_and_append(comm: &mut Comm, ctx: &mut TxContext) -> Result<
comm.append(&[siglen as u8]);
comm.append(&sig[..siglen as usize]);
comm.append(&[parity as u8]);

testing::debug_print("Signature is appended 2\n");

Ok(())
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00000.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00001.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00002.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00003.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00004.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00005.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00006.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00007.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00008.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00009.png
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00010.png
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00011.png
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00012.png
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00013.png
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00014.png
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00015.png
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00016.png
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00017.png
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00018.png
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00019.png
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00020.png
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00021.png
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00022.png
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00023.png
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00024.png
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00025.png
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00026.png
Binary file added tests/snapshots/nanos/test_sign_tx_long_tx/00027.png
Binary file added tests/snapshots/nanos/test_sign_tx_refused/00000.png
Binary file added tests/snapshots/nanos/test_sign_tx_refused/00001.png
Binary file added tests/snapshots/nanos/test_sign_tx_refused/00002.png
Binary file added tests/snapshots/nanos/test_sign_tx_refused/00003.png
Binary file added tests/snapshots/nanos/test_sign_tx_refused/00004.png
Binary file added tests/snapshots/nanos/test_sign_tx_refused/00005.png
Binary file added tests/snapshots/nanos/test_sign_tx_refused/00006.png
Binary file added tests/snapshots/nanos/test_sign_tx_refused/00007.png
283 changes: 142 additions & 141 deletions tests/test_sign_cmd.py
Original file line number Diff line number Diff line change
@@ -1,141 +1,142 @@
# import pytest

# from application_client.boilerplate_transaction import Transaction
# from application_client.boilerplate_command_sender import BoilerplateCommandSender, Errors
# from application_client.boilerplate_response_unpacker import unpack_get_public_key_response, unpack_sign_tx_response
# from ragger.error import ExceptionRAPDU
# from ragger.navigator import NavInsID
# from utils import ROOT_SCREENSHOT_PATH, check_signature_validity

# # In this tests we check the behavior of the device when asked to sign a transaction


# # In this test se send to the device a transaction to sign and validate it on screen
# # The transaction is short and will be sent in one chunk
# # We will ensure that the displayed information is correct by using screenshots comparison
# def test_sign_tx_short_tx(firmware, backend, navigator, test_name):
# # Use the app interface instead of raw interface
# client = BoilerplateCommandSender(backend)
# # The path used for this entire test
# path: str = "m/44'/1'/0'/0/0"

# # First we need to get the public key of the device in order to build the transaction
# rapdu = client.get_public_key(path=path)
# _, public_key, _, _ = unpack_get_public_key_response(rapdu.data)

# # Create the transaction that will be sent to the device for signing
# transaction = Transaction(
# nonce=1,
# to="0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
# value=666,
# memo="For u EthDev"
# ).serialize()

# # Send the sign device instruction.
# # As it requires on-screen validation, the function is asynchronous.
# # It will yield the result when the navigation is done
# with client.sign_tx(path=path, transaction=transaction):
# # Validate the on-screen request by performing the navigation appropriate for this device
# if firmware.device.startswith("nano"):
# navigator.navigate_until_text_and_compare(NavInsID.RIGHT_CLICK,
# [NavInsID.BOTH_CLICK],
# "Approve",
# ROOT_SCREENSHOT_PATH,
# test_name)
# else:
# navigator.navigate_until_text_and_compare(NavInsID.USE_CASE_REVIEW_TAP,
# [NavInsID.USE_CASE_REVIEW_CONFIRM,
# NavInsID.USE_CASE_STATUS_DISMISS],
# "Hold to sign",
# ROOT_SCREENSHOT_PATH,
# test_name)

# # The device as yielded the result, parse it and ensure that the signature is correct
# response = client.get_async_response().data
# _, der_sig, _ = unpack_sign_tx_response(response)
# assert check_signature_validity(public_key, der_sig, transaction)


# # In this test se send to the device a transaction to sign and validate it on screen
# # This test is mostly the same as the previous one but with different values.
# # In particular the long memo will force the transaction to be sent in multiple chunks
# def test_sign_tx_long_tx(firmware, backend, navigator, test_name):
# # Use the app interface instead of raw interface
# client = BoilerplateCommandSender(backend)
# path: str = "m/44'/1'/0'/0/0"

# rapdu = client.get_public_key(path=path)
# _, public_key, _, _ = unpack_get_public_key_response(rapdu.data)

# transaction = Transaction(
# nonce=1,
# to="0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
# value=666,
# memo=("This is a very long memo. "
# "It will force the app client to send the serialized transaction to be sent in chunk. "
# "As the maximum chunk size is 255 bytes we will make this memo greater than 255 characters. "
# "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam.")
# ).serialize()

# with client.sign_tx(path=path, transaction=transaction):
# if firmware.device.startswith("nano"):
# navigator.navigate_until_text_and_compare(NavInsID.RIGHT_CLICK,
# [NavInsID.BOTH_CLICK],
# "Approve",
# ROOT_SCREENSHOT_PATH,
# test_name)
# else:
# navigator.navigate_until_text_and_compare(NavInsID.USE_CASE_REVIEW_TAP,
# [NavInsID.USE_CASE_REVIEW_CONFIRM,
# NavInsID.USE_CASE_STATUS_DISMISS],
# "Hold to sign",
# ROOT_SCREENSHOT_PATH,
# test_name)
# response = client.get_async_response().data
# _, der_sig, _ = unpack_sign_tx_response(response)
# assert check_signature_validity(public_key, der_sig, transaction)


# # Transaction signature refused test
# # The test will ask for a transaction signature that will be refused on screen
# def test_sign_tx_refused(firmware, backend, navigator, test_name):
# # Use the app interface instead of raw interface
# client = BoilerplateCommandSender(backend)
# path: str = "m/44'/1'/0'/0/0"

# rapdu = client.get_public_key(path=path)
# _, pub_key, _, _ = unpack_get_public_key_response(rapdu.data)

# transaction = Transaction(
# nonce=1,
# to="0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
# value=666,
# memo="This transaction will be refused by the user"
# ).serialize()

# if firmware.device.startswith("nano"):
# with pytest.raises(ExceptionRAPDU) as e:
# with client.sign_tx(path=path, transaction=transaction):
# navigator.navigate_until_text_and_compare(NavInsID.RIGHT_CLICK,
# [NavInsID.BOTH_CLICK],
# "Reject",
# ROOT_SCREENSHOT_PATH,
# test_name)

# # Assert that we have received a refusal
# assert e.value.status == Errors.SW_DENY
# assert len(e.value.data) == 0
# else:
# for i in range(3):
# instructions = [NavInsID.USE_CASE_REVIEW_TAP] * i
# instructions += [NavInsID.USE_CASE_REVIEW_REJECT,
# NavInsID.USE_CASE_CHOICE_CONFIRM,
# NavInsID.USE_CASE_STATUS_DISMISS]
# with pytest.raises(ExceptionRAPDU) as e:
# with client.sign_tx(path=path, transaction=transaction):
# navigator.navigate_and_compare(ROOT_SCREENSHOT_PATH,
# test_name + f"/part{i}",
# instructions)
# # Assert that we have received a refusal
# assert e.value.status == Errors.SW_DENY
# assert len(e.value.data) == 0
import pytest

from application_client.boilerplate_transaction import Transaction
from application_client.boilerplate_command_sender import BoilerplateCommandSender, Errors
from application_client.boilerplate_response_unpacker import unpack_get_public_key_response, unpack_sign_tx_response
from ragger.error import ExceptionRAPDU
from ragger.navigator import NavInsID
from utils import ROOT_SCREENSHOT_PATH, check_signature_validity

# In this tests we check the behavior of the device when asked to sign a transaction


# In this test se send to the device a transaction to sign and validate it on screen
# The transaction is short and will be sent in one chunk
# We will ensure that the displayed information is correct by using screenshots comparison
def test_sign_tx_short_tx(firmware, backend, navigator, test_name):
# Use the app interface instead of raw interface
client = BoilerplateCommandSender(backend)
# The path used for this entire test
path: str = "m/44'/1'/0'/0/0"

# First we need to get the public key of the device in order to build the transaction
rapdu = client.get_public_key(path=path)
_, public_key, _, _ = unpack_get_public_key_response(rapdu.data)

# Create the transaction that will be sent to the device for signing
transaction = Transaction(
nonce=1,
to="0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
value=666,
memo="For u EthDev"
).serialize()

# Send the sign device instruction.
# As it requires on-screen validation, the function is asynchronous.
# It will yield the result when the navigation is done
with client.sign_tx(path=path, transaction=transaction):
# Validate the on-screen request by performing the navigation appropriate for this device
if firmware.device.startswith("nano"):
navigator.navigate_until_text_and_compare(NavInsID.RIGHT_CLICK,
[NavInsID.BOTH_CLICK],
"Approve",
ROOT_SCREENSHOT_PATH,
test_name)
else:
navigator.navigate_until_text_and_compare(NavInsID.USE_CASE_REVIEW_TAP,
[NavInsID.USE_CASE_REVIEW_CONFIRM,
NavInsID.USE_CASE_STATUS_DISMISS],
"Hold to sign",
ROOT_SCREENSHOT_PATH,
test_name)

# The device as yielded the result, parse it and ensure that the signature is correct
response = client.get_async_response().data
_, der_sig, _ = unpack_sign_tx_response(response)

assert check_signature_validity(public_key, der_sig, transaction)


# In this test se send to the device a transaction to sign and validate it on screen
# This test is mostly the same as the previous one but with different values.
# In particular the long memo will force the transaction to be sent in multiple chunks
def test_sign_tx_long_tx(firmware, backend, navigator, test_name):
# Use the app interface instead of raw interface
client = BoilerplateCommandSender(backend)
path: str = "m/44'/1'/0'/0/0"

rapdu = client.get_public_key(path=path)
_, public_key, _, _ = unpack_get_public_key_response(rapdu.data)

transaction = Transaction(
nonce=1,
to="0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
value=666,
memo=("This is a very long memo. "
"It will force the app client to send the serialized transaction to be sent in chunk. "
"As the maximum chunk size is 255 bytes we will make this memo greater than 255 characters. "
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam.")
).serialize()

with client.sign_tx(path=path, transaction=transaction):
if firmware.device.startswith("nano"):
navigator.navigate_until_text_and_compare(NavInsID.RIGHT_CLICK,
[NavInsID.BOTH_CLICK],
"Approve",
ROOT_SCREENSHOT_PATH,
test_name)
else:
navigator.navigate_until_text_and_compare(NavInsID.USE_CASE_REVIEW_TAP,
[NavInsID.USE_CASE_REVIEW_CONFIRM,
NavInsID.USE_CASE_STATUS_DISMISS],
"Hold to sign",
ROOT_SCREENSHOT_PATH,
test_name)
response = client.get_async_response().data
_, der_sig, _ = unpack_sign_tx_response(response)
assert check_signature_validity(public_key, der_sig, transaction)


# Transaction signature refused test
# The test will ask for a transaction signature that will be refused on screen
def test_sign_tx_refused(firmware, backend, navigator, test_name):
# Use the app interface instead of raw interface
client = BoilerplateCommandSender(backend)
path: str = "m/44'/1'/0'/0/0"

rapdu = client.get_public_key(path=path)
_, pub_key, _, _ = unpack_get_public_key_response(rapdu.data)

transaction = Transaction(
nonce=1,
to="0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
value=666,
memo="This transaction will be refused by the user"
).serialize()

if firmware.device.startswith("nano"):
with pytest.raises(ExceptionRAPDU) as e:
with client.sign_tx(path=path, transaction=transaction):
navigator.navigate_until_text_and_compare(NavInsID.RIGHT_CLICK,
[NavInsID.BOTH_CLICK],
"Reject",
ROOT_SCREENSHOT_PATH,
test_name)

# Assert that we have received a refusal
assert e.value.status == Errors.SW_DENY
assert len(e.value.data) == 0
else:
for i in range(3):
instructions = [NavInsID.USE_CASE_REVIEW_TAP] * i
instructions += [NavInsID.USE_CASE_REVIEW_REJECT,
NavInsID.USE_CASE_CHOICE_CONFIRM,
NavInsID.USE_CASE_STATUS_DISMISS]
with pytest.raises(ExceptionRAPDU) as e:
with client.sign_tx(path=path, transaction=transaction):
navigator.navigate_and_compare(ROOT_SCREENSHOT_PATH,
test_name + f"/part{i}",
instructions)
# Assert that we have received a refusal
assert e.value.status == Errors.SW_DENY
assert len(e.value.data) == 0

0 comments on commit aa56193

Please sign in to comment.