diff --git a/app/rust/include/rslib.h b/app/rust/include/rslib.h index e2c574cd6..9a205bdf0 100644 --- a/app/rust/include/rslib.h +++ b/app/rust/include/rslib.h @@ -93,9 +93,8 @@ parser_error_t _parse_sign_hash_tx(uint8_t *input, uint16_t len); void _clean_up_hash(); -void _computeV(parser_context_t *ctx, uint8_t parity, uint8_t *v); +uint8_t _computeV(parser_context_t *ctx, uint8_t parity); zxerr_t _process_nft_info(uint8_t *buffer, uint16_t buffer_len); uint64_t _eth_chain_id(parser_context_t *ctx); - diff --git a/app/rust/src/ffi.rs b/app/rust/src/ffi.rs index 061eedcfa..c6ddcf60b 100644 --- a/app/rust/src/ffi.rs +++ b/app/rust/src/ffi.rs @@ -21,9 +21,10 @@ use crate::handlers::avax::sign_hash::{Sign as SignHash, SignUI}; use crate::handlers::avax::signing::Sign; use crate::parser::{ parse_path_list, AvaxMessage, DisplayableItem, EthTransaction, ObjectList, PathWrapper, - U32_SIZE, + PersonalMsg, U32_SIZE, }; use crate::parser::{FromBytes, ParserError, Transaction}; +use crate::utils::ApduPanic; use crate::ZxError; pub mod context; @@ -47,6 +48,12 @@ macro_rules! avax_msg_from_state { }; } +macro_rules! eth_msg_from_state { + ($ptr:expr) => { + unsafe { &mut (*addr_of_mut!((*$ptr).tx_obj.state).cast::>()) } + }; +} + /// Cast a *mut u8 to a *mut Transaction macro_rules! eth_tx_from_state { ($ptr:expr) => { @@ -77,9 +84,7 @@ pub unsafe extern "C" fn _parser_init( Instruction::SignAvaxTx => core::mem::size_of::>() as u32, Instruction::SignEthTx => core::mem::size_of::>() as u32, Instruction::SignAvaxMsg => core::mem::size_of::>() as u32, - Instruction::SignEthMsg => { - return ParserError::UnexpectedError as u32; - } + Instruction::SignEthMsg => core::mem::size_of::>() as u32, Instruction::SignAvaxHash => SIGN_HASH_TX_SIZE as u32, }; @@ -177,6 +182,14 @@ pub unsafe extern "C" fn _parser_read(ctx: *const parser_context_t) -> u32 { } } + Instruction::SignEthMsg => { + let tx = eth_msg_from_state!(ctx as *mut parser_context_t); + match PersonalMsg::from_bytes_into(data, tx) { + Ok(_) => ParserError::ParserOk as u32, + Err(_) => ParserError::InvalidAvaxMessage as u32, + } + } + _ => ParserError::UnexpectedError as u32, } } @@ -239,7 +252,17 @@ pub unsafe extern "C" fn _getNumItems(ctx: *const parser_context_t, num_items: * } } - _ => todo!(), + Instruction::SignEthMsg => { + let tx = eth_msg_from_state!(ctx as *mut parser_context_t); + let obj = tx.assume_init_mut(); + match obj.num_items() { + Ok(n) => { + *num_items = n; + ParserError::ParserOk as u32 + } + Err(e) => e as u32, + } + } }; ParserError::ParserOk as u32 @@ -322,7 +345,17 @@ pub unsafe extern "C" fn _getItem( } } - _ => todo!(), + Instruction::SignEthMsg => { + let msg = eth_msg_from_state!(ctx as *mut parser_context_t); + let obj = msg.assume_init_mut(); + match obj.render_item(display_idx, key, value, page_idx) { + Ok(page) => { + *page_count = page; + ParserError::ParserOk as _ + } + Err(e) => e as _, + } + } } } @@ -362,7 +395,7 @@ pub unsafe extern "C" fn _tx_data_offset( // for eth transactions. this is required for the app_ethereum js application // to compute the real V from this bytes #[no_mangle] -pub unsafe extern "C" fn _computeV(ctx: *const parser_context_t, parity: u8, v: *mut u8) { +pub unsafe extern "C" fn _computeV(ctx: *const parser_context_t, parity: u8) -> u8 { let tx = eth_tx_from_state!(ctx as *mut parser_context_t); let data = core::slice::from_raw_parts((*ctx).buffer, (*ctx).buffer_len as _); @@ -377,13 +410,10 @@ pub unsafe extern "C" fn _computeV(ctx: *const parser_context_t, parity: u8, v: let len = core::cmp::min(U32_SIZE, chain_id.len()); // Check for typed transactions + // eip-1559 and eip-2930 if tx.is_typed_tx() { - // For EIP1559 and EIP2930 transactions chain_id is not empty - let id: u64 = crate::parser::bytes_to_u64(&chain_id[..len]).unwrap(); - // v = (35 + parity) + (chain_id * 2) - let x: u32 = (35 + parity as u32).saturating_add((id as u32) << 1); - - *v = x as u8; + // this is what the app=ethereum does + parity // below for legacy transactions } else if chain_id.is_empty() { @@ -394,7 +424,7 @@ pub unsafe extern "C" fn _computeV(ctx: *const parser_context_t, parity: u8, v: // see https://bitcoin.stackexchange.com/a/112489 // https://ethereum.stackexchange.com/a/113505 // https://eips.ethereum.org/EIPS/eip-155 - *v = 27 + parity; + 27 + parity } else { // app-ethereum reads the first 4 bytes then cast it to an u8 // this is not good but it relies on hw-eth-app lib from ledger @@ -403,9 +433,8 @@ pub unsafe extern "C" fn _computeV(ctx: *const parser_context_t, parity: u8, v: // Ensure that leading is not greater than U32_SIZE // unwrap here as chain_id is neither empty nor grater that u64_size let id: u64 = crate::parser::bytes_to_u64(&chain_id[..len]).unwrap(); - + // let x: u32 = (35 + parity as u32).saturating_add((id as u32) << 1); - - *v = x as u8; + x as u8 } } diff --git a/app/rust/src/parser/coreth/native.rs b/app/rust/src/parser/coreth/native.rs index 2454cd28b..c0f6ee99c 100644 --- a/app/rust/src/parser/coreth/native.rs +++ b/app/rust/src/parser/coreth/native.rs @@ -217,6 +217,7 @@ impl<'b> FromBytes<'b> for EthTransaction<'b> { match tx_type { EthTransaction__Type::Legacy => { + crate::zlog("**** Legacy Transaction ****"); let out = out.as_mut_ptr() as *mut Legacy__Variant; let legacy = unsafe { &mut *addr_of_mut!((*out).1).cast() }; @@ -233,6 +234,7 @@ impl<'b> FromBytes<'b> for EthTransaction<'b> { } } EthTransaction__Type::Eip1559 => { + crate::zlog("**** EIP1559 Transaction ****"); let out = out.as_mut_ptr() as *mut Eip1559__Variant; let eip = unsafe { &mut *addr_of_mut!((*out).1).cast() }; @@ -249,6 +251,7 @@ impl<'b> FromBytes<'b> for EthTransaction<'b> { } } EthTransaction__Type::Eip2930 => { + crate::zlog("**** EIP2930 Transaction ****"); let out = out.as_mut_ptr() as *mut Eip2930__Variant; let eip = unsafe { &mut *addr_of_mut!((*out).1).cast() }; diff --git a/app/src/apdu_handler.c b/app/src/apdu_handler.c index 9e216dd43..e3345b6c5 100644 --- a/app/src/apdu_handler.c +++ b/app/src/apdu_handler.c @@ -38,6 +38,8 @@ static bool tx_initialized = false; bool process_chunk_eth(__Z_UNUSED volatile uint32_t *tx, uint32_t rx); +bool +process_chunk_eth_msg(__Z_UNUSED volatile uint32_t *tx, uint32_t rx); void extract_eth_path(uint32_t rx, uint32_t offset); @@ -255,6 +257,94 @@ process_chunk_eth(__Z_UNUSED volatile uint32_t *tx, uint32_t rx) THROW(APDU_CODE_INVALIDP1P2); } +bool process_chunk_eth_msg(volatile uint32_t *tx, uint32_t rx) { + zemu_log("process_chunk_eth_msg\n"); + const uint8_t payloadType = G_io_apdu_buffer[OFFSET_PAYLOAD_TYPE]; + + if (G_io_apdu_buffer[OFFSET_P2] != 0) { + THROW(APDU_CODE_INVALIDP1P2); + } + + if (rx < OFFSET_DATA) { + THROW(APDU_CODE_WRONG_LENGTH); + } + + uint64_t read = 0; + uint64_t msg_len = 0; + + uint8_t *data = &(G_io_apdu_buffer[OFFSET_DATA]); + uint32_t len = rx - OFFSET_DATA; + uint64_t added; + bool tx_init = false; + + switch (payloadType) { + case P1_ETH_FIRST: + tx_initialize(); + tx_reset(); + extract_eth_path(rx, OFFSET_DATA); + uint32_t path_len = sizeof(uint32_t) * hdPath_len; + data += path_len + 1; + if (len < path_len) { + THROW(APDU_CODE_WRONG_LENGTH); + } + + if (be_bytes_to_u64(data, 4, &msg_len) != 0) { + THROW(APDU_CODE_WRONG_LENGTH); + } + + uint32_t remaining_len = len - path_len - 1 - 4; + + // Adjust the data buffer based on the read message length + max_len = MIN(msg_len, remaining_len); + added = tx_append(data, max_len); + + if (added != max_len) { + THROW(APDU_CODE_OUTPUT_BUFFER_TOO_SMALL); + } + + // Consider transaction initialized if we handle the full length + tx_init = true; + return (remaining_len - added == 0); + + case P1_ETH_MORE: + if (!tx_init) { + THROW(APDU_CODE_TX_NOT_INITIALIZED); + } + + uint32_t buff_len = tx_get_buffer_length(); + uint8_t *buff_data = tx_get_buffer(); + + // Read the expected message length from the start of the buffer + if (be_bytes_to_u64(buff_data, 4, &msg_len) != 0) { + THROW(APDU_CODE_DATA_INVALID); + } + + // Ensure that the total data we expect is less than or equal to what's + // already in the buffer plus what's incoming + if (msg_len > buff_len - 4 + len) { + THROW(APDU_CODE_WRONG_LENGTH); + } + + // Append new data to buffer + added = tx_append(data, len); + if (added != len) { + THROW(APDU_CODE_OUTPUT_BUFFER_TOO_SMALL); + } + + // Update the buffer length after appending + buff_len = tx_get_buffer_length(); + + // Check if we've received the entire message based on the initial + // message length indicator + if (msg_len + 4 == buff_len) { + return true; + } + + return false; + default: + THROW(APDU_CODE_INVALIDP1P2); + } +} __Z_INLINE void handleGetAddr(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { zemu_log("handleGetAddr\n"); @@ -402,6 +492,30 @@ __Z_INLINE void handleSignAvaxMsg(volatile uint32_t *flags, volatile uint32_t *t *flags |= IO_ASYNCH_REPLY; } +__Z_INLINE void handleSignEthMsg(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { + zemu_log("handleSignEthMsg\n"); + + if (!process_chunk_eth_msg(tx, rx)) { + } + + const char *error_msg = tx_eth_parse_msg(); + zemu_log("error\n"); + + CHECK_APP_CANARY() + + if (error_msg != NULL) { + zemu_log(error_msg); + const int error_msg_length = strnlen(error_msg, sizeof(G_io_apdu_buffer)); + memcpy(G_io_apdu_buffer, error_msg, error_msg_length); + *tx += (error_msg_length); + THROW(APDU_CODE_DATA_INVALID); + } + + view_review_init(tx_getItem, tx_getNumItems, app_sign_eth_msg); + view_review_show(REVIEW_TXN); + *flags |= IO_ASYNCH_REPLY; +} + __Z_INLINE void handleSignEthTx(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { zemu_log("handleSignEthTx\n"); @@ -453,6 +567,15 @@ __Z_INLINE void handleNftInfo(volatile uint32_t *flags, volatile uint32_t *tx, u *flags |= IO_ASYNCH_REPLY; } +__Z_INLINE void handleErc20(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { + zemu_log("handleErc20\n"); + + // nothing to do here + set_code(G_io_apdu_buffer, 0, APDU_CODE_OK); + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); + *flags |= IO_ASYNCH_REPLY; +} + __Z_INLINE void handleSetPlugin(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { zemu_log("handleSetPlugin\n"); @@ -584,12 +707,11 @@ __Z_INLINE void eth_dispatch(volatile uint32_t *flags, volatile uint32_t *tx, ui break; } - // case INS_ETH_PROVIDE_ERC20: { - // CHECK_PIN_VALIDATED() - // handleSignAvaxMsg(flags, tx, rx); - // - // break; - // } + case INS_ETH_PROVIDE_ERC20: { + CHECK_PIN_VALIDATED() + handleErc20(flags, tx, rx); + break; + } // // case INS_SIGN_ETH_MSG: { // CHECK_PIN_VALIDATED() diff --git a/app/src/common/actions.h b/app/src/common/actions.h index c73bf5885..ff5298147 100644 --- a/app/src/common/actions.h +++ b/app/src/common/actions.h @@ -145,6 +145,24 @@ __Z_INLINE void app_sign_msg() { app_sign(0); } +__Z_INLINE void app_sign_eth_msg() { + const uint8_t *message = tx_get_buffer(); + const uint16_t messageLength = tx_get_buffer_length(); + uint16_t replyLen = 0; + + // MEMZERO(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE); + // zxerr_t err = crypto_sign_eth(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 3, message, messageLength, &replyLen); + zxerr_t err = crypto_sign_eth_msg(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 2, message, messageLength, &replyLen); + + if (err != zxerr_ok || replyLen == 0) { + set_code(G_io_apdu_buffer, 0, APDU_CODE_SIGN_VERIFY_ERROR); + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); + } else { + set_code(G_io_apdu_buffer, replyLen, APDU_CODE_OK); + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, replyLen + 2); + } +} + __Z_INLINE void app_reject() { MEMZERO(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE); set_code(G_io_apdu_buffer, 0, APDU_CODE_COMMAND_NOT_ALLOWED); diff --git a/app/src/common/parser.h b/app/src/common/parser.h index 086090dd7..2c2b06f4e 100644 --- a/app/src/common/parser.h +++ b/app/src/common/parser.h @@ -38,7 +38,7 @@ parser_error_t parser_getNumItems(const parser_context_t *ctx, uint8_t *num_item // retrieves a readable output for each field / page parser_error_t parser_getItem(const parser_context_t *ctx, uint8_t displayIdx, char *outKey, uint16_t outKeyLen, char *outVal, uint16_t outValLen, uint8_t pageIdx, uint8_t *pageCount); -parser_compute_eth_v(parser_context_t *ctx, unsigned int info, uint8_t *v); +uint8_t parser_compute_eth_v(parser_context_t *ctx, unsigned int info); #ifdef __cplusplus } diff --git a/app/src/common/tx.c b/app/src/common/tx.c index 9286c2016..a88df2d9e 100644 --- a/app/src/common/tx.c +++ b/app/src/common/tx.c @@ -119,6 +119,13 @@ const char *tx_avax_parse_msg() { return tx_parse(); } +const char *tx_eth_parse_msg() { + MEMZERO(&ctx_parsed_tx.tx_obj, sizeof(parser_tx_t)); + ctx_parsed_tx.ins = SignEthMsg; + + return tx_parse(); +} + const char *tx_eth_parse() { MEMZERO(&ctx_parsed_tx.tx_obj, sizeof(parser_tx_t)); ctx_parsed_tx.ins = SignEthTx; @@ -172,6 +179,6 @@ zxerr_t tx_getItem(int8_t displayIdx, return zxerr_ok; } -void tx_compute_eth_v(unsigned int info, uint8_t *v) { - parser_compute_eth_v(&ctx_parsed_tx, info, v); +uint8_t tx_compute_eth_v(unsigned int info) { + return parser_compute_eth_v(&ctx_parsed_tx, info); } diff --git a/app/src/common/tx.h b/app/src/common/tx.h index fe4797528..e4f8e1617 100644 --- a/app/src/common/tx.h +++ b/app/src/common/tx.h @@ -57,10 +57,13 @@ const char *tx_avax_parse_hash(); /// plus the message data to be signed const char *tx_avax_parse_msg(); +/// Parse an ethereum message. +const char *tx_eth_parse_msg(); + /// Return the number of items in the transaction zxerr_t tx_getNumItems(uint8_t *num_items); /// Gets an specific item from the transaction (including paging) zxerr_t tx_getItem(int8_t displayIdx, char *outKey, uint16_t outKeyLen, char *outValue, uint16_t outValueLen, uint8_t pageIdx, uint8_t *pageCount); -void tx_compute_eth_v(unsigned int info, uint8_t *v); +uint8_t tx_compute_eth_v(unsigned int info); diff --git a/app/src/crypto.c b/app/src/crypto.c index 453e9276e..7acbaab65 100644 --- a/app/src/crypto.c +++ b/app/src/crypto.c @@ -225,8 +225,16 @@ zxerr_t crypto_sign_eth(uint8_t *buffer, uint16_t signatureMaxlen, const uint8_t return zxerr_invalid_crypto_settings; } - uint8_t message_digest[KECCAK_256_SIZE] = {0}; + uint8_t message_digest[KECCAK_256_SIZE] = {'\n'}; CHECK_ZXERR(keccak_digest(message, messageLen, message_digest, KECCAK_256_SIZE)) + char data[KECCAK_256_SIZE * 2 + 1] = {0}; // Each byte needs 2 characters, plus null terminator + + for (int i = 0; i < KECCAK_256_SIZE; i++) { + snprintf(data + i * 2, 3, "%02x", message_digest[i]); + } + zemu_log("***********digest: \n"); + zemu_log(data); + zemu_log("\n"); unsigned int info = 0; zxerr_t error = _sign(buffer, signatureMaxlen, message_digest, KECCAK_256_SIZE, sigSize, &info); @@ -234,10 +242,58 @@ zxerr_t crypto_sign_eth(uint8_t *buffer, uint16_t signatureMaxlen, const uint8_t return zxerr_invalid_crypto_settings; // we need to fix V - uint8_t v = 0; + uint8_t v = tx_compute_eth_v(info); + { + char data[10] = {0}; + snprintf(data, sizeof(data), "V: %d\n", v); + zemu_log(data); + } - // Check this or use our rust implementation? - tx_compute_eth_v(info, &v); + // need to reorder signature as hw-eth-app expects v at the beginning. + // so rsv -> vrs + uint8_t rs_size = sizeof_field(signature_t, r) + sizeof_field(signature_t, s); + memmove(buffer + 1, buffer, rs_size); + buffer[0] = v; + + return error; +} + +// Sign an ethereum personal message +zxerr_t crypto_sign_eth_msg(uint8_t *buffer, uint16_t signatureMaxlen, const uint8_t *message, uint16_t messageLen, uint16_t *sigSize) { + if (buffer == NULL || message == NULL || sigSize == NULL || signatureMaxlen < sizeof(signature_t)) { + return zxerr_invalid_crypto_settings; + } + + uint8_t message_digest[KECCAK_256_SIZE] = {'\n'}; + CHECK_ZXERR(keccak_digest(message, messageLen, message_digest, KECCAK_256_SIZE)) + + char data[KECCAK_256_SIZE * 2 + 1] = {0}; // Each byte needs 2 characters, plus null terminator + + for (int i = 0; i < KECCAK_256_SIZE; i++) { + snprintf(data + i * 2, 3, "%02x", message_digest[i]); + } + zemu_log("***********digest: \n"); + zemu_log(data); + zemu_log("\n"); + + unsigned int info = 0; + zxerr_t error = _sign(buffer, signatureMaxlen, message_digest, KECCAK_256_SIZE, sigSize, &info); + if (error != zxerr_ok) + return zxerr_invalid_crypto_settings; + + // we need to fix V + uint8_t v = 27; + if (info & CX_ECCINFO_PARITY_ODD) + v + 1; + + if (info & CX_ECCINFO_xGTn) + v += 2; + + { + char data[10] = {0}; + snprintf(data, sizeof(data), "V: %d\n", v); + zemu_log(data); + } // need to reorder signature as hw-eth-app expects v at the beginning. // so rsv -> vrs @@ -249,6 +305,7 @@ zxerr_t crypto_sign_eth(uint8_t *buffer, uint16_t signatureMaxlen, const uint8_t } + zxerr_t crypto_fillAddress(uint8_t *buffer, uint16_t bufferLen, uint16_t *addrResponseLen) { if (buffer == NULL || addrResponseLen == NULL) { return zxerr_unknown; diff --git a/app/src/crypto.h b/app/src/crypto.h index 4e2278ca5..d786d2744 100644 --- a/app/src/crypto.h +++ b/app/src/crypto.h @@ -36,6 +36,9 @@ zxerr_t keccak_digest(const unsigned char *in, unsigned int inLen, zxerr_t crypto_fillAddress(uint8_t *buffer, uint16_t bufferLen, uint16_t *addrResponseLen); zxerr_t crypto_sign_avax(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t *hash, uint16_t hash_len, const uint32_t *path, uint16_t path_len); +zxerr_t crypto_sign_eth(uint8_t *buffer, uint16_t signatureMaxlen, const uint8_t *message, uint16_t messageLen, uint16_t *sigSize); +zxerr_t crypto_sign_eth_msg(uint8_t *buffer, uint16_t signatureMaxlen, const uint8_t *message, uint16_t messageLen, uint16_t *sigSize); + #ifdef __cplusplus diff --git a/app/src/parser.c b/app/src/parser.c index 3f1845d10..99e8675ef 100644 --- a/app/src/parser.c +++ b/app/src/parser.c @@ -140,11 +140,10 @@ parser_error_t parser_getItem(const parser_context_t *ctx, uint8_t displayIdx, c return _getItem(ctx, displayIdx, outKey, outKeyLen, outVal, outValLen, pageIdx, pageCount); } -parser_compute_eth_v(parser_context_t *ctx, unsigned int info, - uint8_t *v) { +uint8_t parser_compute_eth_v(parser_context_t *ctx, unsigned int info) { unsigned int parity = (info & CX_ECCINFO_PARITY_ODD); - _computeV(ctx, parity, v); + return _computeV(ctx, parity); }