diff --git a/Makefile b/Makefile index 31568e0..4319e07 100755 --- a/Makefile +++ b/Makefile @@ -46,6 +46,10 @@ DEFINES += CHAINID_UPCASE=\"ETHEREUM\" CHAINID_COINNAME=\"ETH\" CHAIN_KIND=CHAIN APP_LOAD_PARAMS += --path "2645'/579218131'" DEFINES += HAVE_STARKWARE DEFINES += STARK_BIP32_PATH_0=0x80000A55 STARK_BIP32_PATH_1=0xA2862AD3 +# Allow to derive ETH 2 public keys +# TODO : add curve reference on latest firmware +APP_LOAD_PARAMS += --path "12381/3600" +DEFINES += HAVE_ETH2 APPNAME = "Ethereum" DEFINES_LIB= APP_LOAD_FLAGS=--appFlags 0xa40 @@ -58,6 +62,10 @@ DEFINES += HAVE_STARKWARE # Keep for Starkware Ropsten tests DEFINES += HAVE_TOKENS_EXTRA_LIST DEFINES += STARK_BIP32_PATH_0=0x80000A55 STARK_BIP32_PATH_1=0xA2862AD3 +# Allow to derive ETH 2 public keys +# TODO : add curve reference on latest firmware +APP_LOAD_PARAMS += --path "12381/3600" +DEFINES += HAVE_ETH2 APPNAME = "Eth Ropsten" DEFINES_LIB= APP_LOAD_FLAGS=--appFlags 0xa40 diff --git a/doc/ethapp.asc b/doc/ethapp.asc index 6dda27a..716cd9a 100644 --- a/doc/ethapp.asc +++ b/doc/ethapp.asc @@ -18,6 +18,7 @@ Application version 1.5.0 - 25th of September 2020 ## 1.5.0 - Add SIGN ETH EIP 712 + - Add GET ETH2 PUBLIC KEY ## About @@ -315,6 +316,79 @@ This command has been supported since firmware version 1.5.0 | s | 32 |============================================================================================================================== +### GET ETH2 PUBLIC KEY + +#### Description + +This command returns an Ethereum 2 BLS12-381 public key derived following EIP 2333 specification (https://eips.ethereum.org/EIPS/eip-2333) + +This command has been supported since firmware version 1.5.0 + +#### Coding + +'Command' + +[width="80%"] +|============================================================================================================================== +| *CLA* | *INS* | *P1* | *P2* | *Lc* | *Le* +| E0 | 0E | 00 : return public key + + 01 : display public key and confirm before returning + | 00 | variable | variable +|============================================================================================================================== + +'Input data' + +[width="80%"] +|============================================================================================================================== +| *Description* | *Length* +| Number of BIP 32 derivations to perform (max 10) | 1 +| First derivation index (big endian) | 4 +| ... | 4 +| Last derivation index (big endian) | 4 +|============================================================================================================================== + +'Output data' + +[width="80%"] +|============================================================================================================================== +| *Description* | *Length* +| Public key | 48 +|============================================================================================================================== + +### SET ETH2 WITHDRAWAL INDEX + +#### Description + +This command sets the index of the Withdrawal key used as withdrawal credentials in an ETH2 deposit contract call signature. The path of the Withdrawal key starts with 12381/3600/0 according to EIP 2333 (https://eips.ethereum.org/EIPS/eip-2333) + +The default index used is 0 if this method isn't called before the deposit contract transaction is sent to the device to be signed + +This command has been supported since firmware version 1.5.0 + +#### Coding + +'Command' + +[width="80%"] +|============================================================================================================================== +| *CLA* | *INS* | *P1* | *P2* | *Lc* | *Le* +| E0 | 10 | 00 + | 00 | variable | variable +|============================================================================================================================== + +'Input data' + +[width="80%"] +|============================================================================================================================== +| *Description* | *Length* +| Withdrawal key index (big endian) | 4 +|============================================================================================================================== + +'Output data' + +None + ## Transport protocol ### General transport description diff --git a/src/apdu_constants.h b/src/apdu_constants.h index 2a24ff7..ee8b784 100644 --- a/src/apdu_constants.h +++ b/src/apdu_constants.h @@ -11,6 +11,8 @@ #define INS_SIGN_PERSONAL_MESSAGE 0x08 #define INS_PROVIDE_ERC20_TOKEN_INFORMATION 0x0A #define INS_SIGN_EIP_712_MESSAGE 0x0C +#define INS_GET_ETH2_PUBLIC_KEY 0x0E +#define INS_SET_ETH2_WITHDRAWAL_INDEX 0x10 #define P1_CONFIRM 0x01 #define P1_NON_CONFIRM 0x00 #define P2_NO_CHAINCODE 0x00 @@ -48,6 +50,14 @@ void handleProvideErc20TokenInformation(uint8_t p1, uint8_t p2, uint8_t *dataBuf void handleSign(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, uint16_t dataLength, unsigned int *flags, unsigned int *tx); void handleGetAppConfiguration(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, uint16_t dataLength, unsigned int *flags, unsigned int *tx); void handleSignPersonalMessage(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, uint16_t dataLength, unsigned int *flags, unsigned int *tx); +void handleSignEIP712Message(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, uint16_t dataLength, unsigned int *flags, unsigned int *tx); + +#ifdef HAVE_ETH2 + +void handleGetEth2PublicKey(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, uint16_t dataLength, unsigned int *flags, unsigned int *tx); +void handleSetEth2WinthdrawalIndex(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, uint16_t dataLength, unsigned int *flags, unsigned int *tx); + +#endif #ifdef HAVE_STARKWARE diff --git a/src/eth_plugin_handler.c b/src/eth_plugin_handler.c index fd4d206..1d40487 100644 --- a/src/eth_plugin_handler.c +++ b/src/eth_plugin_handler.c @@ -45,12 +45,16 @@ void eth_plugin_prepare_query_contract_UI(ethQueryContractUI_t *queryContractUI, int eth_plugin_perform_init(uint8_t *contractAddress, ethPluginInitContract_t *init) { uint8_t i; + const uint8_t **selectors; dataContext.tokenContext.pluginAvailable = 0; // Handle hardcoded plugin list PRINTF("Selector %.*H\n", 4, init->selector); - for (i=0; iselector, PIC(selectors[j]), SELECTOR_SIZE) == 0) { strcpy(dataContext.tokenContext.pluginName, INTERNAL_ETH_PLUGINS[i].alias); @@ -96,6 +100,7 @@ int eth_plugin_call(uint8_t *contractAddress, int method, void *parameter) { char tmp[PLUGIN_ID_LENGTH]; char *alias; uint8_t i; + uint8_t internalPlugin = 0; pluginRW.sha3 = &global_sha3; pluginRO.txContent = &tmpContent.txContent; @@ -154,14 +159,18 @@ int eth_plugin_call(uint8_t *contractAddress, int method, void *parameter) { // Perform the call - for (i=0; i 0) { + yFlag = 0x20; + } + publicKey.W[1] &= 0x1f; + publicKey.W[1] |= 0x80 | yFlag; + memmove(out, publicKey.W + 1, 48); +#endif +} + +void handleGetEth2PublicKey(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, uint16_t dataLength, unsigned int *flags, unsigned int *tx) { + UNUSED(dataLength); + uint32_t bip32Path[MAX_BIP32_PATH]; + uint32_t i; + uint8_t bip32PathLength = *(dataBuffer++); + + if(!called_from_swap){ + reset_app_context(); + } + if ((bip32PathLength < 0x01) || + (bip32PathLength > MAX_BIP32_PATH)) { + PRINTF("Invalid path\n"); + THROW(0x6a80); + } + if ((p1 != P1_CONFIRM) && (p1 != P1_NON_CONFIRM)) { + THROW(0x6B00); + } + if (p2 != 0) { + THROW(0x6B00); + } + for (i = 0; i < bip32PathLength; i++) { + bip32Path[i] = U4BE(dataBuffer, 0); + dataBuffer += 4; + } + getEth2PublicKey(bip32Path, bip32PathLength, tmpCtx.publicKeyContext.publicKey.W); + +#ifndef NO_CONSENT + if (p1 == P1_NON_CONFIRM) +#endif // NO_CONSENT + { + *tx = set_result_get_eth2_publicKey(); + THROW(0x9000); + } +#ifndef NO_CONSENT + else + { + ux_flow_init(0, ux_display_public_eth2_flow, NULL); + + *flags |= IO_ASYNCH_REPLY; + } +#endif // NO_CONSENT +} + +#endif diff --git a/src_features/getEth2PublicKey/feature_getEth2PublicKey.h b/src_features/getEth2PublicKey/feature_getEth2PublicKey.h new file mode 100644 index 0000000..f6a9f69 --- /dev/null +++ b/src_features/getEth2PublicKey/feature_getEth2PublicKey.h @@ -0,0 +1,4 @@ +#include "shared_context.h" + +uint32_t set_result_get_eth2_publicKey(void); + diff --git a/src_features/getEth2PublicKey/logic_getEth2PublicKey.c b/src_features/getEth2PublicKey/logic_getEth2PublicKey.c new file mode 100644 index 0000000..91ae7ab --- /dev/null +++ b/src_features/getEth2PublicKey/logic_getEth2PublicKey.c @@ -0,0 +1,13 @@ +#ifdef HAVE_ETH2 + +#include "shared_context.h" + +uint32_t set_result_get_eth2_publicKey() { + uint32_t tx = 0; + os_memmove(G_io_apdu_buffer + tx, tmpCtx.publicKeyContext.publicKey.W, 48); + tx += 48; + return tx; +} + +#endif + diff --git a/src_features/getEth2PublicKey/ui_common_getEth2PublicKey.c b/src_features/getEth2PublicKey/ui_common_getEth2PublicKey.c new file mode 100644 index 0000000..752fdcd --- /dev/null +++ b/src_features/getEth2PublicKey/ui_common_getEth2PublicKey.c @@ -0,0 +1,32 @@ +#ifdef HAVE_ETH2 + +#include "shared_context.h" +#include "feature_getEth2PublicKey.h" +#include "ui_callbacks.h" + +unsigned int io_seproxyhal_touch_eth2_address_ok(const bagl_element_t *e) { + uint32_t tx = set_result_get_eth2_publicKey(); + G_io_apdu_buffer[tx++] = 0x90; + G_io_apdu_buffer[tx++] = 0x00; + reset_app_context(); + // Send back the response, do not restart the event loop + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, tx); + // Display back the original UX + ui_idle(); + return 0; // do not redraw the widget +} + +#if 0 +unsigned int io_seproxyhal_touch_eth2_address_cancel(const bagl_element_t *e) { + G_io_apdu_buffer[0] = 0x69; + G_io_apdu_buffer[1] = 0x85; + reset_app_context(); + // Send back the response, do not restart the event loop + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); + // Display back the original UX + ui_idle(); + return 0; // do not redraw the widget +} +#endif + +#endif diff --git a/src_features/getEth2PublicKey/ui_flow_getEth2PublicKey.c b/src_features/getEth2PublicKey/ui_flow_getEth2PublicKey.c new file mode 100644 index 0000000..8a06547 --- /dev/null +++ b/src_features/getEth2PublicKey/ui_flow_getEth2PublicKey.c @@ -0,0 +1,50 @@ +#ifdef HAVE_ETH2 + +#include "shared_context.h" +#include "ui_callbacks.h" + +void prepare_eth2_public_key() { + snprintf(strings.tmp.tmp, 100, "0x%.*H", 48, tmpCtx.publicKeyContext.publicKey.W); +} + +UX_STEP_NOCB( + ux_display_public_eth2_flow_1_step, + pnn, + { + &C_icon_eye, + "Verify ETH2", + "public key", + }); +UX_STEP_NOCB_INIT( + ux_display_public_eth2_flow_2_step, + bnnn_paging, + prepare_eth2_public_key(), + { + .title = "Public Key", + .text = strings.tmp.tmp, + }); +UX_STEP_CB( + ux_display_public_eth2_flow_3_step, + pb, + io_seproxyhal_touch_eth2_address_ok(NULL), + { + &C_icon_validate_14, + "Approve", + }); +UX_STEP_CB( + ux_display_public_eth2_flow_4_step, + pb, + io_seproxyhal_touch_address_cancel(NULL), + { + &C_icon_crossmark, + "Reject", + }); + +UX_FLOW(ux_display_public_eth2_flow, + &ux_display_public_eth2_flow_1_step, + &ux_display_public_eth2_flow_2_step, + &ux_display_public_eth2_flow_3_step, + &ux_display_public_eth2_flow_4_step +); + +#endif diff --git a/src_features/setEth2WithdrawalIndex/cmd_setEth2WithdrawalIndex.c b/src_features/setEth2WithdrawalIndex/cmd_setEth2WithdrawalIndex.c new file mode 100644 index 0000000..1dfa19f --- /dev/null +++ b/src_features/setEth2WithdrawalIndex/cmd_setEth2WithdrawalIndex.c @@ -0,0 +1,23 @@ +#ifdef HAVE_ETH2 + +#include "shared_context.h" +#include "apdu_constants.h" + + +void handleSetEth2WithdrawalIndex(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, uint16_t dataLength, unsigned int *flags, unsigned int *tx) { + UNUSED(dataLength); + + if (dataLength != 4) { + THROW(0x6700); + } + + if ((p1 != 0) || (p2 != 0)) { + THROW(0x6B00); + } + + eth2WithdrawalIndex = U4BE(dataBuffer, 0); + + THROW(0x9000); +} + +#endif diff --git a/src_plugins/eth2/eth2_plugin.c b/src_plugins/eth2/eth2_plugin.c new file mode 100644 index 0000000..bd9b7f7 --- /dev/null +++ b/src_plugins/eth2/eth2_plugin.c @@ -0,0 +1,167 @@ +#ifdef HAVE_ETH2 + +#include +#include "eth_plugin_internal.h" +#include "eth_plugin_handler.h" +#include "shared_context.h" +#include "ethUtils.h" +#include "utils.h" + +void getEth2PublicKey(uint32_t *bip32Path, uint8_t bip32PathLength, uint8_t *out); + +#define WITHDRAWAL_KEY_PATH_1 12381 +#define WITHDRAWAL_KEY_PATH_2 3600 +#define WITHDRAWAL_KEY_PATH_3 0 + +#define ETH2_DEPOSIT_PUBKEY_OFFSET 0x80 +#define ETH2_WITHDRAWAL_CREDENTIALS_OFFSET 0xE0 +#define ETH2_SIGNATURE_OFFSET 0x120 +#define ETH2_DEPOSIT_PUBKEY_LENGTH 0x30 +#define ETH2_WITHDRAWAL_CREDENTIALS_LENGTH 0x20 +#define ETH2_SIGNATURE_LENGTH 0x60 + +typedef struct eth2_deposit_parameters_t { + uint8_t valid; +} eth2_deposit_parameters_t; + +void eth2_plugin_call(int message, void *parameters) { + + switch(message) { + case ETH_PLUGIN_INIT_CONTRACT: { + ethPluginInitContract_t *msg = (ethPluginInitContract_t*)parameters; + eth2_deposit_parameters_t *context = (eth2_deposit_parameters_t*)msg->pluginContext; + context->valid = 1; + msg->result = ETH_PLUGIN_RESULT_OK; + } + break; + + case ETH_PLUGIN_PROVIDE_PARAMETER : { + ethPluginProvideParameter_t *msg = (ethPluginProvideParameter_t*)parameters; + eth2_deposit_parameters_t *context = (eth2_deposit_parameters_t*)msg->pluginContext; + uint32_t index; + PRINTF("eth2 plugin provide parameter %d %.*H\n", msg->parameterOffset, 32, msg->parameter); + switch(msg->parameterOffset) { + case 4 + (32 * 0): // pubkey offset + case 4 + (32 * 1): // withdrawal credentials offset + case 4 + (32 * 2): // signature offset + case 4 + (32 * 4): // deposit pubkey length + case 4 + (32 * 7): // withdrawal credentials length + case 4 + (32 * 9): // signature length + { + uint32_t check = 0; + switch(msg->parameterOffset) { + case 4 + (32 * 0): + check = ETH2_DEPOSIT_PUBKEY_OFFSET; + break; + case 4 + (32 * 1): + check = ETH2_WITHDRAWAL_CREDENTIALS_OFFSET; + break; + case 4 + (32 * 2): + check = ETH2_SIGNATURE_OFFSET; + break; + case 4 + (32 * 4): + check = ETH2_DEPOSIT_PUBKEY_LENGTH; + break; + case 4 + (32 * 7): + check = ETH2_WITHDRAWAL_CREDENTIALS_LENGTH; + break; + case 4 + (32 * 9): + check = ETH2_SIGNATURE_LENGTH; + break; + default: + break; + } + index = U4BE(msg->parameter, 32 - 4); + if (index != check) { + PRINTF("eth2 plugin parameter check %d failed, expected %d got %d\n", msg->parameterOffset, check, index); + context->valid = 0; + } + msg->result = ETH_PLUGIN_RESULT_OK; + } + break; + + case 4 + (32 * 3): // deposit data root + case 4 + (32 * 5): // deposit pubkey + case 4 + (32 * 6): + case 4 + (32 * 10): // signature + case 4 + (32 * 11): + case 4 + (32 * 12): + msg->result = ETH_PLUGIN_RESULT_OK; + break; + + case 4 + (32 * 8): // withdrawal credentials + { + uint8_t tmp[48]; + uint32_t withdrawalKeyPath[4]; + withdrawalKeyPath[0] = WITHDRAWAL_KEY_PATH_1; + withdrawalKeyPath[1] = WITHDRAWAL_KEY_PATH_2; + withdrawalKeyPath[2] = WITHDRAWAL_KEY_PATH_3; + withdrawalKeyPath[3] = eth2WithdrawalIndex; + getEth2PublicKey(withdrawalKeyPath, 4, tmp); + PRINTF("eth2 plugin computed withdrawal public key %.*H\n", 48, tmp); + cx_hash_sha256(tmp, 48, tmp, 32); + tmp[0] = 0; + if (memcmp(tmp, msg->parameter, 32) != 0) { + PRINTF("eth2 plugin invalid withdrawal credentials\n"); + PRINTF("Got %.*H\n", 32, msg->parameter); + PRINTF("Expected %.*H\n", 32, tmp); + context->valid = 0; + } + msg->result = ETH_PLUGIN_RESULT_OK; + } + break; + + default: + PRINTF("Unhandled parameter offset\n"); + break; + } + } + break; + + case ETH_PLUGIN_FINALIZE: { + ethPluginFinalize_t *msg = (ethPluginFinalize_t*)parameters; + eth2_deposit_parameters_t *context = (eth2_deposit_parameters_t*)msg->pluginContext; + PRINTF("eth2 plugin finalize\n"); + if (context->valid) { + msg->numScreens = 1; + msg->uiType = ETH_UI_TYPE_GENERIC; + msg->result = ETH_PLUGIN_RESULT_OK; + } + else { + msg->result = ETH_PLUGIN_RESULT_FALLBACK; + } + } + break; + + case ETH_PLUGIN_QUERY_CONTRACT_ID: { + ethQueryContractID_t *msg = (ethQueryContractID_t*)parameters; + strcpy(msg->name, "ETH2"); + strcpy(msg->version, "Deposit"); + msg->result = ETH_PLUGIN_RESULT_OK; + } + break; + + case ETH_PLUGIN_QUERY_CONTRACT_UI: { + ethQueryContractUI_t *msg = (ethQueryContractUI_t*)parameters; + //eth2_deposit_parameters_t *context = (eth2_deposit_parameters_t*)msg->pluginContext; + switch(msg->screenIndex) { + case 0: { + uint8_t decimals = WEI_TO_ETHER; + uint8_t *ticker = (uint8_t *)PIC(chainConfig->coinName); + strcpy(msg->title, "Amount"); + amountToString(tmpContent.txContent.value.value, tmpContent.txContent.value.length, decimals, (char*)ticker, msg->msg, 100); + msg->result = ETH_PLUGIN_RESULT_OK; + } + break; + default: + break; + } + } + break; + + default: + PRINTF("Unhandled message %d\n", message); + } +} + +#endif