diff --git a/app/Makefile.version b/app/Makefile.version index 230bef3c..163597f4 100644 --- a/app/Makefile.version +++ b/app/Makefile.version @@ -3,4 +3,4 @@ APPVERSION_M=2 # This is the `spec_version` field of `Runtime` APPVERSION_N=35 # This is the patch version of this release -APPVERSION_P=9 +APPVERSION_P=10 diff --git a/app/src/addr.c b/app/src/addr.c index 3debc5fa..6e43055e 100644 --- a/app/src/addr.c +++ b/app/src/addr.c @@ -25,7 +25,8 @@ zxerr_t addr_getNumItems(uint8_t *num_items) { zemu_log_stack("addr_getNumItems"); *num_items = 1; - if (app_mode_expert() || isEthPath) { + + if (app_mode_expert() || encoding != BECH32_COSMOS ) { zemu_log("num_items 2\n"); *num_items = 2; } else { @@ -47,7 +48,7 @@ zxerr_t addr_getItem(int8_t displayIdx, ZEMU_LOGF(200, "[addr_getItem] pageCount %d\n", *pageCount) return zxerr_ok; case 1: { - if (!app_mode_expert() && !isEthPath) { + if (!app_mode_expert() && encoding == BECH32_COSMOS) { return zxerr_no_data; } diff --git a/app/src/apdu_handler.c b/app/src/apdu_handler.c index 1671f0dd..150abcfb 100644 --- a/app/src/apdu_handler.c +++ b/app/src/apdu_handler.c @@ -35,7 +35,7 @@ #include "parser_impl.h" #include "view_internal.h" -bool isEthPath = false; +#include "chain_config.h" static const char *msg_error1 = "Expert Mode"; static const char *msg_error2 = "Required"; @@ -74,8 +74,11 @@ static void extractHDPath(uint32_t rx, uint32_t offset) { THROW(APDU_CODE_DATA_INVALID); } - // Set EthPath flag - isEthPath = (hdPath[1] == HDPATH_ETH_1_DEFAULT) ? true : false; + encoding = checkChainConfig(hdPath[1], bech32_hrp, bech32_hrp_len); + if (encoding == UNSUPPORTED) { + ZEMU_LOGF(50, "Chain config not supported for: %s\n", bech32_hrp) + THROW(APDU_CODE_COMMAND_NOT_ALLOWED); + } // Limit values unless the app is running in expert mode if (!app_mode_expert()) { @@ -124,7 +127,6 @@ __Z_INLINE void handleGetAddrSecp256K1(volatile uint32_t *flags, volatile uint32 extractHDPath(rx, OFFSET_DATA + 1 + len); uint8_t requireConfirmation = G_io_apdu_buffer[OFFSET_P1]; - zxerr_t zxerr = app_fill_address(); if (zxerr != zxerr_ok) { *tx = 0; @@ -158,7 +160,7 @@ __Z_INLINE void handleSign(volatile uint32_t *flags, volatile uint32_t *tx, uint } parser_tx_obj.tx_json.own_addr = (const char *) (G_io_apdu_buffer + VIEW_ADDRESS_OFFSET_SECP256K1); - if (isEthPath && !app_mode_expert()) { + if ((encoding != BECH32_COSMOS) && !app_mode_expert()) { *flags |= IO_ASYNCH_REPLY; view_custom_error_show(PIC(msg_error1),PIC(msg_error2)); THROW(APDU_CODE_DATA_INVALID); diff --git a/app/src/chain_config.c b/app/src/chain_config.c new file mode 100644 index 00000000..1fd848a7 --- /dev/null +++ b/app/src/chain_config.c @@ -0,0 +1,51 @@ +/******************************************************************************* +* (c) 2018 - 2023 Zondax AG +* +* 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 "chain_config.h" +#include + +typedef struct { + const uint32_t path; + const char *hrp; + const address_encoding_e encoding; +} chain_config_t; + +// To enable custom config for a new chain, just add a new entry in this array with path, hrp and encoding +static const chain_config_t chainConfig[] = { + // {118, cosmos, BECH32_COSMOS}, + {60, "inj", BECH32_ETH}, +}; + +static const uint32_t chainConfigLen = sizeof(chainConfig) / sizeof(chainConfig[0]); + +address_encoding_e checkChainConfig(uint32_t path, const char* hrp, uint8_t hrpLen) { + // Always allowed for 118 (default Cosmos) + if (path == HDPATH_1_DEFAULT) { + return BECH32_COSMOS; + } + + // Check special cases + for (uint32_t i = 0; i < chainConfigLen; i++) { + if (path == (0x80000000u | chainConfig[i].path)) { + const char* hrpPtr = (const char *) PIC(chainConfig[i].hrp); + const uint16_t hrpPtrLen = strlen(hrpPtr); + if (hrpPtrLen == hrpLen && memcmp(hrpPtr, hrp, hrpLen) == 0) { + return chainConfig[i].encoding; + } + } + } + + return UNSUPPORTED; +} diff --git a/app/src/chain_config.h b/app/src/chain_config.h new file mode 100644 index 00000000..3c0409b7 --- /dev/null +++ b/app/src/chain_config.h @@ -0,0 +1,30 @@ +/******************************************************************************* +* (c) 2018 - 2023 Zondax AG +* +* 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. +********************************************************************************/ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "coin.h" + +address_encoding_e checkChainConfig(uint32_t path, const char* hrp, uint8_t hrpLen); + +#ifdef __cplusplus +} +#endif diff --git a/app/src/coin.h b/app/src/coin.h index a58e1750..ece4d77f 100644 --- a/app/src/coin.h +++ b/app/src/coin.h @@ -32,7 +32,6 @@ extern "C" { #define PK_LEN_SECP256K1 33u #define PK_LEN_SECP256K1_UNCOMPRESSED 65u -extern bool isEthPath; typedef enum { addr_secp256k1 = 0, } address_kind_e; @@ -42,6 +41,12 @@ typedef enum { tx_textual } tx_type_e; +typedef enum { + BECH32_COSMOS = 0, + BECH32_ETH, + UNSUPPORTED = 0xFF, +} address_encoding_e; + #define VIEW_ADDRESS_OFFSET_SECP256K1 PK_LEN_SECP256K1 #define VIEW_ADDRESS_LAST_PAGE_DEFAULT 0 diff --git a/app/src/crypto.c b/app/src/crypto.c index 6544d8e1..d6de7652 100644 --- a/app/src/crypto.c +++ b/app/src/crypto.c @@ -21,11 +21,13 @@ #include "tx.h" #include +#include "chain_config.h" uint32_t hdPath[HDPATH_LEN_DEFAULT]; uint8_t bech32_hrp_len; char bech32_hrp[MAX_BECH32_HRP_LEN + 1]; +address_encoding_e encoding; #include "cx.h" @@ -94,11 +96,13 @@ zxerr_t crypto_sign(uint8_t *signature, const uint8_t *message = tx_get_buffer(); const uint16_t messageLen = tx_get_buffer_length(); - switch(hdPath[1]) { - case HDPATH_1_DEFAULT: + switch (encoding) { + case BECH32_COSMOS: { cx_hash_sha256(message, messageLen, messageDigest, CX_SHA256_SIZE); break; - case HDPATH_ETH_1_DEFAULT: { + } + + case BECH32_ETH: { cx_sha3_t sha3 = {0}; cx_err_t status = cx_keccak_init_no_throw(&sha3, 256); if (status != CX_OK) { @@ -108,8 +112,11 @@ zxerr_t crypto_sign(uint8_t *signature, if (status != CX_OK) { return zxerr_ledger_api_error; } + break; } - break; + + default: + return zxerr_unknown; } cx_ecfp_private_key_t cx_privateKey = {0}; @@ -198,20 +205,32 @@ zxerr_t crypto_fillAddress(uint8_t *buffer, uint16_t buffer_len, uint16_t *addrR char *addr = (char *) (buffer + PK_LEN_SECP256K1); uint8_t hashed1_pk[CX_SHA256_SIZE] = {0}; - if (isEthPath) { - cx_sha3_t ctx; - if (cx_keccak_init_no_throw(&ctx, 256) != CX_OK) { - return zxerr_unknown; + + switch (encoding) { + case BECH32_COSMOS: { + // Hash it + cx_hash_sha256(buffer, PK_LEN_SECP256K1, hashed1_pk, CX_SHA256_SIZE); + uint8_t hashed2_pk[CX_RIPEMD160_SIZE]; + ripemd160_32(hashed2_pk, hashed1_pk); + CHECK_ZXERR(bech32EncodeFromBytes(addr, buffer_len - PK_LEN_SECP256K1, bech32_hrp, hashed2_pk, CX_RIPEMD160_SIZE, 1, BECH32_ENCODING_BECH32)) + break; } - cx_hash((cx_hash_t *)&ctx, CX_LAST, uncompressedPubkey+1, sizeof(uncompressedPubkey)-1, hashed1_pk, sizeof(hashed1_pk)); - CHECK_ZXERR(bech32EncodeFromBytes(addr, buffer_len - PK_LEN_SECP256K1, bech32_hrp, hashed1_pk + 12, sizeof(hashed1_pk) - 12, 1, BECH32_ENCODING_BECH32)) - } else { - // Hash it - cx_hash_sha256(buffer, PK_LEN_SECP256K1, hashed1_pk, CX_SHA256_SIZE); - uint8_t hashed2_pk[CX_RIPEMD160_SIZE]; - ripemd160_32(hashed2_pk, hashed1_pk); - CHECK_ZXERR(bech32EncodeFromBytes(addr, buffer_len - PK_LEN_SECP256K1, bech32_hrp, hashed2_pk, CX_RIPEMD160_SIZE, 1, BECH32_ENCODING_BECH32)) + + case BECH32_ETH: { + cx_sha3_t ctx; + if (cx_keccak_init_no_throw(&ctx, 256) != CX_OK) { + return zxerr_unknown; + } + cx_hash((cx_hash_t *)&ctx, CX_LAST, uncompressedPubkey+1, sizeof(uncompressedPubkey)-1, hashed1_pk, sizeof(hashed1_pk)); + CHECK_ZXERR(bech32EncodeFromBytes(addr, buffer_len - PK_LEN_SECP256K1, bech32_hrp, hashed1_pk + 12, sizeof(hashed1_pk) - 12, 1, BECH32_ENCODING_BECH32)) + break; + } + + default: + *addrResponseLen = 0; + return zxerr_encoding_failed; } + *addrResponseLen = PK_LEN_SECP256K1 + strnlen(addr, (buffer_len - PK_LEN_SECP256K1)); return zxerr_ok; diff --git a/app/src/crypto.h b/app/src/crypto.h index 8cb3182c..6a7d319f 100644 --- a/app/src/crypto.h +++ b/app/src/crypto.h @@ -29,7 +29,9 @@ extern "C" { #define MAX_BECH32_HRP_LEN 83u extern uint32_t hdPath[HDPATH_LEN_DEFAULT]; -extern char *hrp; +extern char bech32_hrp[MAX_BECH32_HRP_LEN + 1]; +extern uint8_t bech32_hrp_len; +extern address_encoding_e encoding; uint8_t extractHRP(uint32_t rx, uint32_t offset); diff --git a/tests_zemu/package.json b/tests_zemu/package.json index f4d35d5d..88d928c9 100644 --- a/tests_zemu/package.json +++ b/tests_zemu/package.json @@ -14,7 +14,7 @@ "Ledger" ], "scripts": { - "test": "ts-node tests/pullImageKillOld.ts && jest" + "test": "ts-node tests/pullImageKillOld.ts && jest --maxConcurrency 2" }, "dependencies": { "@zondax/ledger-cosmos-js": "^3.0.2", diff --git a/tests_zemu/snapshots/s-mainmenu/00004.png b/tests_zemu/snapshots/s-mainmenu/00004.png index 1a35f133..98030e9a 100644 Binary files a/tests_zemu/snapshots/s-mainmenu/00004.png and b/tests_zemu/snapshots/s-mainmenu/00004.png differ diff --git a/tests_zemu/snapshots/s-mainmenu/00010.png b/tests_zemu/snapshots/s-mainmenu/00010.png index 1a35f133..98030e9a 100644 Binary files a/tests_zemu/snapshots/s-mainmenu/00010.png and b/tests_zemu/snapshots/s-mainmenu/00010.png differ diff --git a/tests_zemu/snapshots/s-show_eth_address/00000.png b/tests_zemu/snapshots/s-show_eth_address/00000.png index 83311eef..7e3a8f23 100644 Binary files a/tests_zemu/snapshots/s-show_eth_address/00000.png and b/tests_zemu/snapshots/s-show_eth_address/00000.png differ diff --git a/tests_zemu/snapshots/s-show_eth_address/00001.png b/tests_zemu/snapshots/s-show_eth_address/00001.png index 70192147..05d02753 100644 Binary files a/tests_zemu/snapshots/s-show_eth_address/00001.png and b/tests_zemu/snapshots/s-show_eth_address/00001.png differ diff --git a/tests_zemu/snapshots/sp-mainmenu/00004.png b/tests_zemu/snapshots/sp-mainmenu/00004.png index 6b0b34a7..d8d85a04 100644 Binary files a/tests_zemu/snapshots/sp-mainmenu/00004.png and b/tests_zemu/snapshots/sp-mainmenu/00004.png differ diff --git a/tests_zemu/snapshots/sp-mainmenu/00010.png b/tests_zemu/snapshots/sp-mainmenu/00010.png index 6b0b34a7..d8d85a04 100644 Binary files a/tests_zemu/snapshots/sp-mainmenu/00010.png and b/tests_zemu/snapshots/sp-mainmenu/00010.png differ diff --git a/tests_zemu/snapshots/sp-show_eth_address/00001.png b/tests_zemu/snapshots/sp-show_eth_address/00001.png index 8cecf2a8..84ed31e4 100644 Binary files a/tests_zemu/snapshots/sp-show_eth_address/00001.png and b/tests_zemu/snapshots/sp-show_eth_address/00001.png differ diff --git a/tests_zemu/snapshots/x-mainmenu/00004.png b/tests_zemu/snapshots/x-mainmenu/00004.png index 6b0b34a7..d8d85a04 100644 Binary files a/tests_zemu/snapshots/x-mainmenu/00004.png and b/tests_zemu/snapshots/x-mainmenu/00004.png differ diff --git a/tests_zemu/snapshots/x-mainmenu/00010.png b/tests_zemu/snapshots/x-mainmenu/00010.png index 6b0b34a7..d8d85a04 100644 Binary files a/tests_zemu/snapshots/x-mainmenu/00010.png and b/tests_zemu/snapshots/x-mainmenu/00010.png differ diff --git a/tests_zemu/snapshots/x-show_eth_address/00001.png b/tests_zemu/snapshots/x-show_eth_address/00001.png index 8cecf2a8..84ed31e4 100644 Binary files a/tests_zemu/snapshots/x-show_eth_address/00001.png and b/tests_zemu/snapshots/x-show_eth_address/00001.png differ diff --git a/tests_zemu/tests/json.test.ts b/tests_zemu/tests/json.test.ts index cfc138f3..5d2e9d45 100644 --- a/tests_zemu/tests/json.test.ts +++ b/tests_zemu/tests/json.test.ts @@ -323,9 +323,7 @@ describe('Json', function () { const app = new CosmosApp(sim.getTransport()) // Activate expert mode - await sim.clickRight() - await sim.clickBoth() - await sim.clickLeft() + await sim.toggleExpertMode(); const path = [44, 118, 0, 0, 0] const tx = Buffer.from(JSON.stringify(example_tx_str_msgMultiSend)) @@ -373,15 +371,13 @@ describe('Json', function () { const app = new CosmosApp(sim.getTransport()) // Change to expert mode so we can skip fields - await sim.clickRight() - await sim.clickBoth() - await sim.clickLeft() + await sim.toggleExpertMode(); const path = [44, 60, 0, 0, 0] const tx = Buffer.from(JSON.stringify(setWithdrawAddress)) // get address / publickey - const respPk = await app.getAddressAndPubKey(path, 'cosmos') + const respPk = await app.getAddressAndPubKey(path, 'inj') expect(respPk.return_code).toEqual(0x9000) expect(respPk.error_message).toEqual('No errors') console.log(respPk) diff --git a/tests_zemu/tests/standard.test.ts b/tests_zemu/tests/standard.test.ts index 304bfd6a..03e629a1 100644 --- a/tests_zemu/tests/standard.test.ts +++ b/tests_zemu/tests/standard.test.ts @@ -24,7 +24,7 @@ import secp256k1 from 'secp256k1/elliptic' // @ts-ignore import crypto from 'crypto' -jest.setTimeout(60000) +jest.setTimeout(90000) describe('Standard', function () { // eslint-disable-next-line jest/expect-expect @@ -129,7 +129,13 @@ describe('Standard', function () { // Derivation path. First 3 items are automatically hardened! const path = [44, 60, 0, 0, 1] - const hrp = 'cosmos' + const hrp = 'inj' + + // check with invalid HRP + const errorRespPk = await app.getAddressAndPubKey(path, 'cosmos') + expect(errorRespPk.return_code).toEqual(0x6986) + expect(errorRespPk.error_message).toEqual('Transaction rejected') + const respRequest = app.showAddressAndPubKey(path, hrp) // Wait until we are not in the main menu await sim.waitUntilScreenIsNot(sim.getMainMenuSnapshot()) @@ -157,8 +163,7 @@ describe('Standard', function () { const eth_address = bech32.encode(hrp, bech32.toWords(ethereumAddressBuffer)); // "cosmos15n2h0lzvfgc8x4fm6fdya89n78x6ee2fm7fxr3" expect(resp.bech32_address).toEqual(eth_address) - expect(resp.bech32_address).toEqual('cosmos15n2h0lzvfgc8x4fm6fdya89n78x6ee2fm7fxr3') - + expect(resp.bech32_address).toEqual('inj15n2h0lzvfgc8x4fm6fdya89n78x6ee2f3h7z3f') } finally { await sim.close() } @@ -189,9 +194,7 @@ describe('Standard', function () { const app = new CosmosApp(sim.getTransport()) // Activate expert mode - await sim.clickRight() - await sim.clickBoth() - await sim.clickLeft() + await sim.toggleExpertMode(); // Derivation path. First 3 items are automatically hardened! const path = [44, 118, 2147483647, 0, 4294967295] @@ -409,8 +412,13 @@ describe('Standard', function () { const path = [44, 60, 0, 0, 0] const tx = Buffer.from(JSON.stringify(example_tx_str_basic), "utf-8") + // check with invalid HRP + const errorRespPk = await app.getAddressAndPubKey(path, 'forbiddenHRP') + expect(errorRespPk.return_code).toEqual(0x6986) + expect(errorRespPk.error_message).toEqual('Transaction rejected') + // get address / publickey - const respPk = await app.getAddressAndPubKey(path, 'cosmos') + const respPk = await app.getAddressAndPubKey(path, 'inj') expect(respPk.return_code).toEqual(0x9000) expect(respPk.error_message).toEqual('No errors') console.log(respPk) @@ -454,7 +462,7 @@ describe('Standard', function () { const tx = Buffer.from(JSON.stringify(example_tx_str_basic), "utf-8") // get address / publickey - const respPk = await app.getAddressAndPubKey(path, 'cosmos') + const respPk = await app.getAddressAndPubKey(path, 'inj') expect(respPk.return_code).toEqual(0x9000) expect(respPk.error_message).toEqual('No errors') console.log(respPk) diff --git a/tests_zemu/tests/textual.test.ts b/tests_zemu/tests/textual.test.ts index 829b349e..2118e157 100644 --- a/tests_zemu/tests/textual.test.ts +++ b/tests_zemu/tests/textual.test.ts @@ -23,7 +23,7 @@ import secp256k1 from 'secp256k1/elliptic' // @ts-ignore import crypto from 'crypto' -jest.setTimeout(60000) +jest.setTimeout(90000) describe('Textual', function () { // eslint-disable-next-line jest/expect-expect @@ -88,9 +88,7 @@ describe('Textual', function () { const app = new CosmosApp(sim.getTransport()) // Change to expert mode so we can skip fields - await sim.clickRight() - await sim.clickBoth() - await sim.clickLeft() + await sim.toggleExpertMode() const path = [44, 118, 0, 0, 0] const tx = Buffer.from(tx_sign_textual, 'hex') @@ -144,7 +142,7 @@ describe('Textual', function () { const tx = Buffer.from(tx_sign_textual, 'hex') // get address / publickey - const respPk = await app.getAddressAndPubKey(path, 'cosmos') + const respPk = await app.getAddressAndPubKey(path, 'inj') expect(respPk.return_code).toEqual(0x9000) expect(respPk.error_message).toEqual('No errors') console.log(respPk) @@ -189,7 +187,7 @@ describe('Textual', function () { const tx = Buffer.from(tx_sign_textual, 'hex') // get address / publickey - const respPk = await app.getAddressAndPubKey(path, 'cosmos') + const respPk = await app.getAddressAndPubKey(path, 'inj') expect(respPk.return_code).toEqual(0x9000) expect(respPk.error_message).toEqual('No errors') console.log(respPk)