Files
app-ethereum/src/main.c
Alexandre Paillier c2011f5d42 Renamed Item to Asset & changed the set marker type to boolean
Also renamed the reset assets function
2024-05-07 17:58:17 +02:00

728 lines
24 KiB
C

/*******************************************************************************
* Ledger Ethereum App
* (c) 2016-2019 Ledger
*
* 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.
********************************************************************************/
#include "shared_context.h"
#include "apdu_constants.h"
#include "common_ui.h"
#include "os_io_seproxyhal.h"
#include "glyphs.h"
#include "common_utils.h"
#include "swap_lib_calls.h"
#include "handle_swap_sign_transaction.h"
#include "handle_get_printable_amount.h"
#include "handle_check_address.h"
#include "commands_712.h"
#include "challenge.h"
#include "domain_name.h"
#include "crypto_helpers.h"
#include "manage_asset_info.h"
unsigned char G_io_seproxyhal_spi_buffer[IO_SEPROXYHAL_BUFFER_SIZE_B];
void ui_idle(void);
uint32_t set_result_get_publicKey(void);
void finalizeParsing(bool);
tmpCtx_t tmpCtx;
txContext_t txContext;
tmpContent_t tmpContent;
dataContext_t dataContext;
strings_t strings;
cx_sha3_t global_sha3;
uint8_t appState;
uint16_t apdu_response_code;
bool G_called_from_swap;
bool G_swap_response_ready;
pluginType_t pluginType;
#ifdef HAVE_ETH2
uint32_t eth2WithdrawalIndex;
#include "withdrawal_index.h"
#endif
#include "ux.h"
ux_state_t G_ux;
bolos_ux_params_t G_ux_params;
const internalStorage_t N_storage_real;
#ifdef HAVE_NBGL
caller_app_t *caller_app = NULL;
#endif
const chain_config_t *chainConfig;
void reset_app_context() {
// PRINTF("!!RESET_APP_CONTEXT\n");
appState = APP_STATE_IDLE;
G_called_from_swap = false;
G_swap_response_ready = false;
pluginType = OLD_INTERNAL;
#ifdef HAVE_ETH2
eth2WithdrawalIndex = 0;
#endif
memset((uint8_t *) &tmpCtx, 0, sizeof(tmpCtx));
forget_known_assets();
memset((uint8_t *) &txContext, 0, sizeof(txContext));
memset((uint8_t *) &tmpContent, 0, sizeof(tmpContent));
}
void io_seproxyhal_send_status(uint32_t sw) {
G_io_apdu_buffer[0] = ((sw >> 8) & 0xff);
G_io_apdu_buffer[1] = (sw & 0xff);
io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2);
}
unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) {
switch (channel & ~(IO_FLAGS)) {
case CHANNEL_KEYBOARD:
break;
// multiplexed io exchange over a SPI channel and TLV encapsulated protocol
case CHANNEL_SPI:
if (tx_len) {
io_seproxyhal_spi_send(G_io_apdu_buffer, tx_len);
if (channel & IO_RESET_AFTER_REPLIED) {
reset();
}
return 0; // nothing received from the master so far (it's a tx
// transaction)
} else {
return io_seproxyhal_spi_recv(G_io_apdu_buffer, sizeof(G_io_apdu_buffer), 0);
}
default:
THROW(INVALID_PARAMETER);
}
return 0;
}
const uint8_t *parseBip32(const uint8_t *dataBuffer, uint8_t *dataLength, bip32_path_t *bip32) {
if (*dataLength < 1) {
PRINTF("Invalid data\n");
return NULL;
}
bip32->length = *dataBuffer;
if (bip32->length < 0x1 || bip32->length > MAX_BIP32_PATH) {
PRINTF("Invalid bip32\n");
return NULL;
}
dataBuffer++;
(*dataLength)--;
if (*dataLength < sizeof(uint32_t) * (bip32->length)) {
PRINTF("Invalid data\n");
return NULL;
}
for (uint8_t i = 0; i < bip32->length; i++) {
bip32->path[i] = U4BE(dataBuffer, 0);
dataBuffer += sizeof(uint32_t);
*dataLength -= sizeof(uint32_t);
}
return dataBuffer;
}
void handleApdu(unsigned int *flags, unsigned int *tx) {
unsigned short sw = 0;
BEGIN_TRY {
TRY {
if (G_io_apdu_buffer[OFFSET_CLA] != CLA) {
THROW(0x6E00);
}
switch (G_io_apdu_buffer[OFFSET_INS]) {
case INS_GET_PUBLIC_KEY:
forget_known_assets();
handleGetPublicKey(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
G_io_apdu_buffer + OFFSET_CDATA,
G_io_apdu_buffer[OFFSET_LC],
flags,
tx);
break;
case INS_PROVIDE_ERC20_TOKEN_INFORMATION:
handleProvideErc20TokenInformation(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
G_io_apdu_buffer + OFFSET_CDATA,
G_io_apdu_buffer[OFFSET_LC],
flags,
tx);
break;
#ifdef HAVE_NFT_SUPPORT
case INS_PROVIDE_NFT_INFORMATION:
handleProvideNFTInformation(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
G_io_apdu_buffer + OFFSET_CDATA,
G_io_apdu_buffer[OFFSET_LC],
flags,
tx);
break;
#endif // HAVE_NFT_SUPPORT
case INS_SET_EXTERNAL_PLUGIN:
handleSetExternalPlugin(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
G_io_apdu_buffer + OFFSET_CDATA,
G_io_apdu_buffer[OFFSET_LC],
flags,
tx);
break;
case INS_SET_PLUGIN:
handleSetPlugin(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
G_io_apdu_buffer + OFFSET_CDATA,
G_io_apdu_buffer[OFFSET_LC],
flags,
tx);
break;
case INS_PERFORM_PRIVACY_OPERATION:
handlePerformPrivacyOperation(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
G_io_apdu_buffer + OFFSET_CDATA,
G_io_apdu_buffer[OFFSET_LC],
flags,
tx);
break;
case INS_SIGN:
handleSign(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
G_io_apdu_buffer + OFFSET_CDATA,
G_io_apdu_buffer[OFFSET_LC],
flags,
tx);
break;
case INS_GET_APP_CONFIGURATION:
handleGetAppConfiguration(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
G_io_apdu_buffer + OFFSET_CDATA,
G_io_apdu_buffer[OFFSET_LC],
flags,
tx);
break;
case INS_SIGN_PERSONAL_MESSAGE:
forget_known_assets();
*flags |= IO_ASYNCH_REPLY;
if (!handleSignPersonalMessage(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
G_io_apdu_buffer + OFFSET_CDATA,
G_io_apdu_buffer[OFFSET_LC])) {
reset_app_context();
}
break;
case INS_SIGN_EIP_712_MESSAGE:
switch (G_io_apdu_buffer[OFFSET_P2]) {
case P2_EIP712_LEGACY_IMPLEM:
forget_known_assets();
handleSignEIP712Message_v0(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
G_io_apdu_buffer + OFFSET_CDATA,
G_io_apdu_buffer[OFFSET_LC],
flags,
tx);
break;
#ifdef HAVE_EIP712_FULL_SUPPORT
case P2_EIP712_FULL_IMPLEM:
*flags |= IO_ASYNCH_REPLY;
handle_eip712_sign(G_io_apdu_buffer);
break;
#endif // HAVE_EIP712_FULL_SUPPORT
default:
THROW(APDU_RESPONSE_INVALID_P1_P2);
}
break;
#ifdef HAVE_ETH2
case INS_GET_ETH2_PUBLIC_KEY:
forget_known_assets();
handleGetEth2PublicKey(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
G_io_apdu_buffer + OFFSET_CDATA,
G_io_apdu_buffer[OFFSET_LC],
flags,
tx);
break;
case INS_SET_ETH2_WITHDRAWAL_INDEX:
handleSetEth2WithdrawalIndex(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
G_io_apdu_buffer + OFFSET_CDATA,
G_io_apdu_buffer[OFFSET_LC],
flags,
tx);
break;
#endif
#ifdef HAVE_EIP712_FULL_SUPPORT
case INS_EIP712_STRUCT_DEF:
*flags |= IO_ASYNCH_REPLY;
handle_eip712_struct_def(G_io_apdu_buffer);
break;
case INS_EIP712_STRUCT_IMPL:
*flags |= IO_ASYNCH_REPLY;
handle_eip712_struct_impl(G_io_apdu_buffer);
break;
case INS_EIP712_FILTERING:
*flags |= IO_ASYNCH_REPLY;
handle_eip712_filtering(G_io_apdu_buffer);
break;
#endif // HAVE_EIP712_FULL_SUPPORT
#ifdef HAVE_DOMAIN_NAME
case INS_ENS_GET_CHALLENGE:
handle_get_challenge();
break;
case INS_ENS_PROVIDE_INFO:
handle_provide_domain_name(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
G_io_apdu_buffer + OFFSET_CDATA,
G_io_apdu_buffer[OFFSET_LC]);
break;
#endif // HAVE_DOMAIN_NAME
#if 0
case 0xFF: // return to dashboard
goto return_to_dashboard;
#endif
default:
THROW(0x6D00);
break;
}
}
CATCH(EXCEPTION_IO_RESET) {
THROW(EXCEPTION_IO_RESET);
}
CATCH_OTHER(e) {
bool quit_now = G_called_from_swap && G_swap_response_ready;
switch (e & 0xF000) {
case 0x6000:
// Wipe the transaction context and report the exception
sw = e;
reset_app_context();
break;
case 0x9000:
// All is well
sw = e;
break;
default:
// Internal error
sw = 0x6800 | (e & 0x7FF);
reset_app_context();
break;
}
// Unexpected exception => report
G_io_apdu_buffer[*tx] = sw >> 8;
G_io_apdu_buffer[*tx + 1] = sw;
*tx += 2;
// If we are in swap mode and have validated a TX, we send it and immediately quit
if (quit_now) {
if (io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, *tx) == 0) {
// In case of success, the apdu is sent immediately and eth exits
// Reaching this code means we encountered an error
finalize_exchange_sign_transaction(false);
} else {
PRINTF("Unrecoverable\n");
os_sched_exit(-1);
}
}
}
FINALLY {
}
}
END_TRY;
}
void app_main(void) {
unsigned int rx = 0;
unsigned int tx = 0;
unsigned int flags = 0;
// DESIGN NOTE: the bootloader ignores the way APDU are fetched. The only
// goal is to retrieve APDU.
// When APDU are to be fetched from multiple IOs, like NFC+USB+BLE, make
// sure the io_event is called with a
// switch event, before the apdu is replied to the bootloader. This avoid
// APDU injection faults.
for (;;) {
unsigned short sw = 0;
BEGIN_TRY {
TRY {
rx = tx;
tx = 0; // ensure no race in catch_other if io_exchange throws
// an error
rx = io_exchange(CHANNEL_APDU | flags, rx);
flags = 0;
// no apdu received, well, reset the session, and reset the
// bootloader configuration
if (rx == 0) {
THROW(ERR_APDU_EMPTY);
}
if (rx > OFFSET_LC && rx != (G_io_apdu_buffer[OFFSET_LC] + 5)) {
THROW(ERR_APDU_SIZE_MISMATCH);
}
handleApdu(&flags, &tx);
}
CATCH(EXCEPTION_IO_RESET) {
THROW(EXCEPTION_IO_RESET);
}
CATCH_OTHER(e) {
switch (e & 0xF000) {
case 0x6000:
// Wipe the transaction context and report the exception
sw = e;
reset_app_context();
break;
case 0x9000:
// All is well
sw = e;
break;
default:
// Internal error
sw = 0x6800 | (e & 0x7FF);
reset_app_context();
break;
}
if (e != 0x9000) {
flags &= ~IO_ASYNCH_REPLY;
}
// Unexpected exception => report
G_io_apdu_buffer[tx] = sw >> 8;
G_io_apdu_buffer[tx + 1] = sw;
tx += 2;
}
FINALLY {
}
}
END_TRY;
}
}
// override point, but nothing more to do
#ifdef HAVE_BAGL
void io_seproxyhal_display(const bagl_element_t *element) {
io_seproxyhal_display_default(element);
}
#endif
unsigned char io_event(__attribute__((unused)) unsigned char channel) {
// nothing done with the event, throw an error on the transport layer if
// needed
// can't have more than one tag in the reply, not supported yet.
switch (G_io_seproxyhal_spi_buffer[0]) {
case SEPROXYHAL_TAG_FINGER_EVENT:
UX_FINGER_EVENT(G_io_seproxyhal_spi_buffer);
break;
#ifdef HAVE_BAGL
case SEPROXYHAL_TAG_BUTTON_PUSH_EVENT:
UX_BUTTON_PUSH_EVENT(G_io_seproxyhal_spi_buffer);
break;
#endif // HAVE_BAGL
case SEPROXYHAL_TAG_STATUS_EVENT:
if (G_io_apdu_media == IO_APDU_MEDIA_USB_HID &&
!(U4BE(G_io_seproxyhal_spi_buffer, 3) &
SEPROXYHAL_TAG_STATUS_EVENT_FLAG_USB_POWERED)) {
THROW(EXCEPTION_IO_RESET);
}
__attribute__((fallthrough));
default:
UX_DEFAULT_EVENT();
break;
case SEPROXYHAL_TAG_DISPLAY_PROCESSED_EVENT:
#ifdef HAVE_BAGL
UX_DISPLAYED_EVENT({});
#endif // HAVE_BAGL
#ifdef HAVE_NBGL
UX_DEFAULT_EVENT();
#endif // HAVE_NBGL
break;
case SEPROXYHAL_TAG_TICKER_EVENT:
UX_TICKER_EVENT(G_io_seproxyhal_spi_buffer, {});
break;
}
// close the event if not done previously (by a display or whatever)
if (!io_seproxyhal_spi_is_status_sent()) {
io_seproxyhal_general_status();
}
// command has been processed, DO NOT reset the current APDU transport
return 1;
}
void app_exit() {
BEGIN_TRY_L(exit) {
TRY_L(exit) {
os_sched_exit(-1);
}
FINALLY_L(exit) {
}
}
END_TRY_L(exit);
}
void init_coin_config(chain_config_t *coin_config) {
memset(coin_config, 0, sizeof(chain_config_t));
strcpy(coin_config->coinName, CHAINID_COINNAME);
coin_config->chainId = CHAIN_ID;
}
__attribute__((noreturn)) void coin_main(libargs_t *args) {
chain_config_t config;
if (args) {
if (args->chain_config != NULL) {
chainConfig = args->chain_config;
}
#ifdef HAVE_NBGL
if ((caller_app = args->caller_app) != NULL) {
if (chainConfig != NULL) {
caller_app->type = CALLER_TYPE_CLONE;
} else {
caller_app->type = CALLER_TYPE_PLUGIN;
}
}
#endif
}
if (chainConfig == NULL) {
init_coin_config(&config);
chainConfig = &config;
}
reset_app_context();
for (;;) {
UX_INIT();
BEGIN_TRY {
TRY {
io_seproxyhal_init();
#ifdef HAVE_BLE
// grab the current plane mode setting
G_io_app.plane_mode = os_setting_get(OS_SETTING_PLANEMODE, NULL, 0);
#endif // HAVE_BLE
if (!N_storage.initialized) {
internalStorage_t storage;
#ifdef HAVE_ALLOW_DATA
storage.dataAllowed = true;
#else
storage.dataAllowed = false;
#endif
storage.contractDetails = false;
storage.displayNonce = false;
#ifdef HAVE_EIP712_FULL_SUPPORT
storage.verbose_eip712 = false;
#endif
#ifdef HAVE_DOMAIN_NAME
storage.verbose_domain_name = false;
#endif
storage.initialized = true;
nvm_write((void *) &N_storage, (void *) &storage, sizeof(internalStorage_t));
}
USB_power(0);
USB_power(1);
ui_idle();
#ifdef HAVE_BLE
BLE_power(0, NULL);
BLE_power(1, NULL);
#endif // HAVE_BLE
#ifdef HAVE_DOMAIN_NAME
// to prevent it from having a fixed value at boot
roll_challenge();
#endif // HAVE_DOMAIN_NAME
app_main();
}
CATCH(EXCEPTION_IO_RESET) {
// reset IO and UX before continuing
CLOSE_TRY;
continue;
}
CATCH_ALL {
CLOSE_TRY;
break;
}
FINALLY {
}
}
END_TRY;
}
os_sched_exit(-1);
}
__attribute__((noreturn)) void library_main(libargs_t *args) {
chain_config_t coin_config;
if (args->chain_config == NULL) {
// We have been started directly by Exchange, not by a Clone. Init default chain
init_coin_config(&coin_config);
args->chain_config = &coin_config;
}
PRINTF("Inside a library \n");
switch (args->command) {
case CHECK_ADDRESS:
handle_check_address(args->check_address, args->chain_config);
break;
case SIGN_TRANSACTION:
if (copy_transaction_parameters(args->create_transaction, args->chain_config)) {
// never returns
handle_swap_sign_transaction(args->chain_config);
} else {
// Failed to copy, non recoverable
os_sched_exit(-1);
}
break;
case GET_PRINTABLE_AMOUNT:
handle_get_printable_amount(args->get_printable_amount, args->chain_config);
break;
default:
break;
}
os_lib_end();
}
/* Eth clones do not actually contain any logic, they delegate everything to the ETH application.
* Start Eth in lib mode with the correct chain config
*/
__attribute__((noreturn)) void clone_main(libargs_t *args) {
PRINTF("Starting in clone_main\n");
BEGIN_TRY {
TRY {
unsigned int libcall_params[5];
chain_config_t local_chainConfig;
init_coin_config(&local_chainConfig);
libcall_params[0] = (unsigned int) "Ethereum";
libcall_params[1] = 0x100;
libcall_params[3] = (unsigned int) &local_chainConfig;
// Clone called by Exchange, forward the request to Ethereum
if (args != NULL) {
if (args->id != 0x100) {
os_sched_exit(0);
}
libcall_params[2] = args->command;
libcall_params[4] = (unsigned int) args->get_printable_amount;
os_lib_call((unsigned int *) &libcall_params);
// Ethereum fulfilled the request and returned to us. We return to Exchange.
os_lib_end();
} else {
// Clone called from Dashboard, start Ethereum
libcall_params[2] = RUN_APPLICATION;
// On Stax, forward our icon to Ethereum
#ifdef HAVE_NBGL
const char app_name[] = APPNAME;
caller_app_t capp;
nbgl_icon_details_t icon_details;
uint8_t bitmap[sizeof(ICONBITMAP)];
memcpy(&icon_details, &ICONGLYPH, sizeof(ICONGLYPH));
memcpy(&bitmap, &ICONBITMAP, sizeof(bitmap));
icon_details.bitmap = (const uint8_t *) bitmap;
capp.name = app_name;
capp.icon = &icon_details;
libcall_params[4] = (unsigned int) &capp;
#else
libcall_params[4] = 0;
#endif // HAVE_NBGL
os_lib_call((unsigned int *) &libcall_params);
// Ethereum should not return to us
os_sched_exit(-1);
}
}
FINALLY {
}
}
END_TRY;
// os_lib_call will raise if Ethereum application is not installed. Do not try to recover.
os_sched_exit(-1);
}
int ethereum_main(libargs_t *args) {
// exit critical section
__asm volatile("cpsie i");
// ensure exception will work as planned
os_boot();
if (args == NULL) {
// called from dashboard as standalone eth app
coin_main(NULL);
return 0;
}
if (args->id != 0x100) {
app_exit();
return 0;
}
switch (args->command) {
case RUN_APPLICATION:
// called as ethereum from altcoin or plugin
coin_main(args);
break;
default:
// called as ethereum or altcoin library
library_main(args);
}
return 0;
}
__attribute__((section(".boot"))) int main(int arg0) {
#ifdef USE_LIB_ETHEREUM
clone_main((libargs_t *) arg0);
#else
return ethereum_main((libargs_t *) arg0);
#endif
}