diff --git a/doc/ethapp.asc b/doc/ethapp.asc index 5b21fa8..8931fe6 100644 --- a/doc/ethapp.asc +++ b/doc/ethapp.asc @@ -1,7 +1,7 @@ Ethereum application : Common Technical Specifications ======================================================= Ledger Firmware Team -Application version 1.5.0 - 25th of September 2020 +Application version 1.9.16 - 10th of January 2022 ## 1.0 - Initial release @@ -26,6 +26,9 @@ Application version 1.5.0 - 25th of September 2020 ## 1.9.13 - Add SET PLUGIN +## 1.9.16 + - Add PERFORM PRIVACY OPERATION + ## About This application describes the APDU messages interface to communicate with the Ethereum application. @@ -379,6 +382,52 @@ type || version || len(pluginName) || pluginName || address || selector || chain None +### PERFORM PRIVACY OPERATION + +#### Description + +This command performs privacy operations as defined in EIP 1024 (https://ethereum-magicians.org/t/eip-1024-cross-client-encrypt-decrypt/505) + +It can return the public encryption key on Curve25519 for a given Ethereum account or the shared secret (generated by the scalar multiplication of the remote public key by the account private key on Curve25519) used to decrypt private data encrypted for a given Ethereum account + +All data can be optionally checked on the device before being returned. + +#### Coding + +'Command' + +[width="80%"] +|============================================================================================================================== +| *CLA* | *INS* | *P1* | *P2* | *Lc* | *Le* +| E0 | 18 | 00 : return data + + 01 : display data and confirm before returning + | 00 : return the public encryption key + + 01 : return the shared secret | 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 +| Third party public key on Curve25519, if returning the shared secret | 32 +|============================================================================================================================== + +'Output data' + +[width="80%"] +|============================================================================================================================== +| *Description* | *Length* +| Public encryption key or shared secret | 32 +|============================================================================================================================== + + ### GET APP CONFIGURATION #### Description diff --git a/src/apdu_constants.h b/src/apdu_constants.h index 474b29d..590f561 100644 --- a/src/apdu_constants.h +++ b/src/apdu_constants.h @@ -20,6 +20,7 @@ #define INS_SET_EXTERNAL_PLUGIN 0x12 #define INS_PROVIDE_NFT_INFORMATION 0x14 #define INS_SET_PLUGIN 0x16 +#define INS_PERFORM_PRIVACY_OPERATION 0x18 #define P1_CONFIRM 0x01 #define P1_NON_CONFIRM 0x00 #define P2_NO_CHAINCODE 0x00 @@ -114,6 +115,13 @@ void handleSetPlugin(uint8_t p1, unsigned int *flags, unsigned int *tx); +void handlePerformPrivacyOperation(uint8_t p1, + uint8_t p2, + uint8_t *workBuffer, + uint16_t dataLength, + unsigned int *flags, + unsigned int *tx); + #ifdef HAVE_ETH2 void handleGetEth2PublicKey(uint8_t p1, diff --git a/src/main.c b/src/main.c index 2b5c878..fc7217c 100644 --- a/src/main.c +++ b/src/main.c @@ -540,6 +540,15 @@ void handleApdu(unsigned int *flags, unsigned int *tx) { 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], diff --git a/src/ui_callbacks.h b/src/ui_callbacks.h index a1a74cd..2d7ff98 100644 --- a/src/ui_callbacks.h +++ b/src/ui_callbacks.h @@ -17,6 +17,8 @@ unsigned int io_seproxyhal_touch_data_cancel(const bagl_element_t *e); unsigned int io_seproxyhal_touch_signMessage712_v0_ok(const bagl_element_t *e); unsigned int io_seproxyhal_touch_signMessage712_v0_cancel(const bagl_element_t *e); unsigned int io_seproxyhal_touch_eth2_address_ok(const bagl_element_t *e); +unsigned int io_seproxyhal_touch_privacy_ok(const bagl_element_t *e); +unsigned int io_seproxyhal_touch_privacy_cancel(const bagl_element_t *e); void ui_idle(void); void ui_warning_contract_data(void); diff --git a/src/ui_flow.h b/src/ui_flow.h index aadc40d..796e4da 100644 --- a/src/ui_flow.h +++ b/src/ui_flow.h @@ -26,6 +26,10 @@ extern const ux_flow_step_t* const ux_sign_712_v0_flow[]; extern const ux_flow_step_t* const ux_display_public_eth2_flow[]; +extern const ux_flow_step_t* const ux_display_privacy_public_key_flow[]; + +extern const ux_flow_step_t* const ux_display_privacy_shared_secret_flow[]; + #ifdef HAVE_STARKWARE extern const ux_flow_step_t* const ux_display_stark_public_flow[]; diff --git a/src_features/performPrivacyOperation/cmd_performPrivacyOperation.c b/src_features/performPrivacyOperation/cmd_performPrivacyOperation.c new file mode 100644 index 0000000..1706703 --- /dev/null +++ b/src_features/performPrivacyOperation/cmd_performPrivacyOperation.c @@ -0,0 +1,122 @@ +#include "shared_context.h" +#include "apdu_constants.h" + +#include "ui_flow.h" +#include "feature_performPrivacyOperation.h" + +#define P2_PUBLIC_ENCRYPTION_KEY 0x00 +#define P2_SHARED_SECRET 0x01 + +void decodeScalar(const uint8_t *scalarIn, uint8_t *scalarOut) { + uint8_t i; + for (i=0; i<32; i++) { + switch(i) { + case 0: + scalarOut[0] = (scalarIn[31] & 0x7f) | 0x40; + break; + case 31: + scalarOut[31] = scalarIn[0] & 0xf8; + break; + default: + scalarOut[i] = scalarIn[31 - i]; + } + } +} + +void handlePerformPrivacyOperation(uint8_t p1, + uint8_t p2, + uint8_t *dataBuffer, + uint16_t dataLength, + unsigned int *flags, + unsigned int *tx) { + UNUSED(dataLength); + uint8_t privateKeyData[INT256_LENGTH]; + uint8_t privateKeyDataSwapped[INT256_LENGTH]; + uint32_t bip32Path[MAX_BIP32_PATH]; + uint32_t i; + uint8_t bip32PathLength = *(dataBuffer++); + cx_err_t status = CX_OK; + if (p2 == P2_PUBLIC_ENCRYPTION_KEY) { + if (dataLength < 1 + 4 * bip32PathLength) { + THROW(0x6700); + } + } + else + if (p2 == P2_SHARED_SECRET) { + if (dataLength < 1 + 4 * bip32PathLength + 32) { + THROW(0x6700); + } + } + else { + THROW(0x6B00); + } + cx_ecfp_private_key_t privateKey; + if ((bip32PathLength < 0x01) || (bip32PathLength > MAX_BIP32_PATH)) { + PRINTF("Invalid path\n"); + THROW(0x6a80); + } + if ((p1 != P1_CONFIRM) && (p1 != P1_NON_CONFIRM)) { + THROW(0x6B00); + } + for (i = 0; i < bip32PathLength; i++) { + bip32Path[i] = U4BE(dataBuffer, 0); + dataBuffer += 4; + } + os_perso_derive_node_bip32( + CX_CURVE_256K1, + bip32Path, + bip32PathLength, + privateKeyData, + (tmpCtx.publicKeyContext.getChaincode ? tmpCtx.publicKeyContext.chainCode : NULL)); + cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &privateKey); + cx_ecfp_generate_pair(CX_CURVE_256K1, &tmpCtx.publicKeyContext.publicKey, &privateKey, 1); + getEthAddressStringFromKey(&tmpCtx.publicKeyContext.publicKey, + tmpCtx.publicKeyContext.address, + &global_sha3, + chainConfig->chainId); + if (p2 == P2_PUBLIC_ENCRYPTION_KEY) { + decodeScalar(privateKeyData, privateKeyDataSwapped); + cx_ecfp_init_private_key(CX_CURVE_Curve25519, privateKeyDataSwapped, 32, &privateKey); + cx_ecfp_generate_pair(CX_CURVE_Curve25519, &tmpCtx.publicKeyContext.publicKey, &privateKey, 1); + explicit_bzero(privateKeyDataSwapped, sizeof(privateKeyDataSwapped)); + } + else { + memmove(tmpCtx.publicKeyContext.publicKey.W + 1, dataBuffer, 32); + status = cx_x25519(tmpCtx.publicKeyContext.publicKey.W + 1, privateKeyData, 32); + } + explicit_bzero(&privateKey, sizeof(privateKey)); + explicit_bzero(privateKeyData, sizeof(privateKeyData)); + + if (status != CX_OK) { + THROW(0x6A80); + } + +#ifndef NO_CONSENT + if (p1 == P1_NON_CONFIRM) +#endif // NO_CONSENT + { + *tx = set_result_perform_privacy_operation(); + THROW(0x9000); + } +#ifndef NO_CONSENT + else { + snprintf(strings.common.fullAddress, + sizeof(strings.common.fullAddress), + "0x%.*s", + 40, + tmpCtx.publicKeyContext.address); + for (i=0; i<32; i++) { + privateKeyData[i] = tmpCtx.publicKeyContext.publicKey.W[32 - i]; + } + snprintf(strings.common.fullAmount, sizeof(strings.common.fullAmount) - 1, "%.*H", 32, privateKeyData); + if (p2 == P2_PUBLIC_ENCRYPTION_KEY) { + ux_flow_init(0, ux_display_privacy_public_key_flow, NULL); + } + else { + ux_flow_init(0, ux_display_privacy_shared_secret_flow, NULL); + } + + *flags |= IO_ASYNCH_REPLY; + } +#endif // NO_CONSENT +} diff --git a/src_features/performPrivacyOperation/feature_performPrivacyOperation.h b/src_features/performPrivacyOperation/feature_performPrivacyOperation.h new file mode 100644 index 0000000..df80ffd --- /dev/null +++ b/src_features/performPrivacyOperation/feature_performPrivacyOperation.h @@ -0,0 +1,3 @@ +#include "shared_context.h" + +uint32_t set_result_perform_privacy_operation(void); diff --git a/src_features/performPrivacyOperation/logic_performPrivacyOperation.c b/src_features/performPrivacyOperation/logic_performPrivacyOperation.c new file mode 100644 index 0000000..6be9957 --- /dev/null +++ b/src_features/performPrivacyOperation/logic_performPrivacyOperation.c @@ -0,0 +1,10 @@ +#include "shared_context.h" + +uint32_t set_result_perform_privacy_operation() { + uint32_t tx = 0, i; + for (i=0; i<32; i++) { + G_io_apdu_buffer[i] = tmpCtx.publicKeyContext.publicKey.W[32 - i]; + } + tx = 32; + return tx; +} diff --git a/src_features/performPrivacyOperation/ui_common_performPrivacyOperation.c b/src_features/performPrivacyOperation/ui_common_performPrivacyOperation.c new file mode 100644 index 0000000..0dcd822 --- /dev/null +++ b/src_features/performPrivacyOperation/ui_common_performPrivacyOperation.c @@ -0,0 +1,27 @@ +#include "shared_context.h" +#include "feature_getPublicKey.h" +#include "ui_callbacks.h" + +unsigned int io_seproxyhal_touch_privacy_ok(__attribute__((unused)) const bagl_element_t *e) { + uint32_t tx = set_result_perform_privacy_operation(); + 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 +} + +unsigned int io_seproxyhal_touch_privacy_cancel(__attribute__((unused)) 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 +} + diff --git a/src_features/performPrivacyOperation/ui_flow_performPrivacyOperation.c b/src_features/performPrivacyOperation/ui_flow_performPrivacyOperation.c new file mode 100644 index 0000000..5c181ef --- /dev/null +++ b/src_features/performPrivacyOperation/ui_flow_performPrivacyOperation.c @@ -0,0 +1,67 @@ +#include "shared_context.h" +#include "ui_callbacks.h" + +// clang-format off +UX_STEP_NOCB( + ux_display_privacy_public_key_flow_1_step, + pnn, + { + &C_icon_eye, + "Provide public", + "privacy key", + }); +UX_STEP_NOCB( + ux_display_privacy_public_key_flow_2_step, + bnnn_paging, + { + .title = "Address", + .text = strings.common.fullAddress, + }); +UX_STEP_NOCB( + ux_display_privacy_public_key_flow_3_step, + bnnn_paging, + { + .title = "Key", + .text = strings.common.fullAmount, + }); +UX_STEP_CB( + ux_display_privacy_public_key_flow_4_step, + pb, + io_seproxyhal_touch_privacy_ok(NULL), + { + &C_icon_validate_14, + "Approve", + }); +UX_STEP_CB( + ux_display_privacy_public_key_flow_5_step, + pb, + io_seproxyhal_touch_privacy_cancel(NULL), + { + &C_icon_crossmark, + "Reject", + }); + +UX_STEP_NOCB( + ux_display_privacy_shared_secret_flow_1_step, + pnn, + { + &C_icon_eye, + "Provide privacy", + "secret key", + }); + +// clang-format on + +UX_FLOW(ux_display_privacy_public_key_flow, + &ux_display_privacy_public_key_flow_1_step, + &ux_display_privacy_public_key_flow_2_step, + &ux_display_privacy_public_key_flow_3_step, + &ux_display_privacy_public_key_flow_4_step, + &ux_display_privacy_public_key_flow_5_step); + +UX_FLOW(ux_display_privacy_shared_secret_flow, + &ux_display_privacy_shared_secret_flow_1_step, + &ux_display_privacy_public_key_flow_2_step, + &ux_display_privacy_public_key_flow_3_step, + &ux_display_privacy_public_key_flow_4_step, + &ux_display_privacy_public_key_flow_5_step);