diff --git a/.github/actions/commit-changes/action.yml b/.github/actions/commit-changes/action.yml index 0099ad6..f572b32 100644 --- a/.github/actions/commit-changes/action.yml +++ b/.github/actions/commit-changes/action.yml @@ -114,6 +114,6 @@ runs: uses: ad-m/github-push-action@master with: github_token: ${{ inputs.secret }} - branch: ${{ steps.commit.outputs.src_branch }}:${{ steps.commit.outputs.dst_branch }} + branch: ${{ steps.commit.outputs.dst_branch }} directory: ${{ inputs.directory }} repository: ${{ inputs.repository }} diff --git a/.github/workflows/auto-author-assign.yml b/.github/workflows/auto-author-assign.yml index acd55ab..a6255c9 100644 --- a/.github/workflows/auto-author-assign.yml +++ b/.github/workflows/auto-author-assign.yml @@ -11,4 +11,4 @@ jobs: assign-author: runs-on: ubuntu-latest steps: - - uses: toshimaru/auto-author-assign@v1.4.0 + - uses: toshimaru/auto-author-assign@v1.6.1 diff --git a/.github/workflows/build-workflow.yml b/.github/workflows/build-workflow.yml index 46f714b..1eb105d 100644 --- a/.github/workflows/build-workflow.yml +++ b/.github/workflows/build-workflow.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Clone - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Build Ethereum run: | diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index e5357df..3e15b79 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -18,14 +18,14 @@ jobs: image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Build with Clang Static Analyzer run: | make clean scan-build --use-cc=clang -analyze-headers -enable-checker security -enable-checker unix -enable-checker valist -o scan-build --status-bugs make default - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 if: failure() with: name: scan-build @@ -42,7 +42,7 @@ jobs: image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Build testing binaries run: | @@ -50,7 +50,7 @@ jobs: cd tests/zemu/ && ./build_local_test_elfs.sh - name: Upload app binaries - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: e2e_zemu_elfs path: ./tests/zemu/elfs/ @@ -68,14 +68,14 @@ jobs: echo $DISPLAY - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - run: sudo apt-get update -y && sudo apt-get install -y libusb-1.0.0 libudev-dev - - name: Install node - uses: actions/setup-node@v2 + - name: Install NodeJS + uses: actions/setup-node@v3 with: - node-version: "14.4.0" + node-version: "16" - name: Install yarn run: npm install -g yarn @@ -87,7 +87,7 @@ jobs: run: mkdir tests/zemu/elfs - name: Download app binaries - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: path: tmp/ @@ -109,7 +109,7 @@ jobs: image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Build testing binaries run: | @@ -119,7 +119,7 @@ jobs: make clean && make -j DEBUG=1 NFT_TESTING_KEY=1 BOLOS_SDK=$NANOSP_SDK && mv bin/app.elf tests/speculos/elfs/nanosp.elf - name: Upload app binaries - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: e2e_speculos_elfs path: ./tests/speculos/elfs @@ -136,13 +136,13 @@ jobs: steps: - name: Clone - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Create tmp folder for artifacts run: mkdir tests/speculos/elfs - name: Download app binaries - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: path: tmp/ @@ -166,113 +166,18 @@ jobs: # ===================================================== build_ragger_elfs: - name: Building binaries for Ragger tests - runs-on: ubuntu-latest - container: - image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest - - steps: - - name: Clone - uses: actions/checkout@v2 - - - name: Build test binaries - run: | - make -j BOLOS_SDK=$NANOS_SDK CAL_TESTING_KEY=1 - mv bin/app.elf app-nanos.elf - make clean - make -j BOLOS_SDK=$NANOX_SDK CAL_TESTING_KEY=1 - mv bin/app.elf app-nanox.elf - make clean - make -j BOLOS_SDK=$NANOSP_SDK CAL_TESTING_KEY=1 - mv bin/app.elf app-nanosp.elf - - - name: Upload app binaries - uses: actions/upload-artifact@v2 - with: - name: ragger_elfs - path: ./app-*.elf - - create_ragger_env: - name: Cache Ragger environment - runs-on: ubuntu-latest - - steps: - - name: Clone - uses: actions/checkout@v2 - - - name: APT update - run: | - sudo apt update - - - name: Create virtual env with dependencies - run: | - cd tests/ragger - python3 -m venv ./venv - . ./venv/bin/activate - pip3 install --extra-index-url https://test.pypi.org/simple/ -r requirements.txt - # Used for the cache key - echo "py_deps=$(pip freeze | md5sum | cut -d' ' -f1)" >> $GITHUB_ENV - - - name: Download QEMU - run: | - sudo apt install --download-only -y qemu-user-static - mkdir -p tests/ragger/packages - cp /var/cache/apt/archives/*.deb tests/ragger/packages/ - # Used for the cache key - echo "deb_deps=$(find /var/cache/apt/archives/ -maxdepth 0 -type f -name '*.deb' | md5sum | cut -d' ' -f 1)" >> $GITHUB_ENV - - - name: Set up cache - uses: actions/cache@v3 - with: - key: ${{ runner.os }}-raggenv-${{ env.py_deps }}-${{ env.deb_deps }} - path: | - tests/ragger/venv/ - tests/ragger/packages/ - outputs: - py_deps: ${{ env.py_deps }} - deb_deps: ${{ env.deb_deps }} - + name: Build app for Ragger tests + uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_build.yml@v1 + with: + upload_app_binaries_artifact: "ragger_elfs" + flags: "DEBUG=1 CAL_CI_KEY=1 DOMAIN_NAME_TEST_KEY=1" + run_for_devices: '["nanos", "nanox", "nanosp"]' jobs-ragger-tests: - name: Ragger tests - strategy: - matrix: - model: ["nanos", "nanox", "nanosp"] - needs: [build_ragger_elfs, create_ragger_env] - runs-on: ubuntu-latest - - steps: - - name: Clone - uses: actions/checkout@v2 - - - name: Download previously built artifacts - uses: actions/download-artifact@v2 - with: - name: ragger_elfs - path: tmp/ - - - name: Put them where they belong - run: | - mkdir -p tests/ragger/elfs - find tmp/ -type f -name '*.elf' -exec cp {} tests/ragger/elfs/ \; - - - name: Get cached environment - uses: actions/cache@v3 - with: - key: ${{ runner.os }}-raggenv-${{ needs.create_ragger_env.outputs.py_deps }}-${{ needs.create_ragger_env.outputs.deb_deps }} - path: | - tests/ragger/venv/ - tests/ragger/packages/ - - - name: Install QEMU - run: | - sudo mv tests/ragger/packages/*.deb /var/cache/apt/archives/ - sudo apt install -y qemu-user-static - - - name: Run tests - env: - CAL_SIGNATURE_TEST_KEY: ${{ secrets.CAL_SIGNATURE_TEST_KEY }} - run: | - cd tests/ragger - . ./venv/bin/activate - pytest --path ./elfs --model ${{ matrix.model }} -s -v + name: Run Ragger tests + needs: build_ragger_elfs + uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_ragger_tests.yml@v1 + with: + download_app_binaries_artifact: "ragger_elfs" + test_dir: tests/ragger + run_for_devices: '["nanos", "nanox", "nanosp"]' diff --git a/.github/workflows/lint-workflow.yml b/.github/workflows/lint-workflow.yml index f74416d..7b98075 100644 --- a/.github/workflows/lint-workflow.yml +++ b/.github/workflows/lint-workflow.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Clone - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Lint uses: DoozyX/clang-format-lint-action@v0.14 diff --git a/.github/workflows/sdk-generation.yml b/.github/workflows/sdk-generation.yml index 4593b41..9d26eb3 100644 --- a/.github/workflows/sdk-generation.yml +++ b/.github/workflows/sdk-generation.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Clone - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: # by default the action uses fetch-depth = 1, which creates # shallow repositories from which we can't push @@ -42,11 +42,10 @@ jobs: secret: ${{ secrets.CI_BOT_TOKEN }} repository: LedgerHQ/ethereum-plugin-sdk - - name: Update the SDK submodule in the Ethereum app - uses: ./.github/actions/commit-changes + - name: Create SDK update pull request + uses: peter-evans/create-pull-request@v4 with: - name: 'ldg-github-ci' - files: ethereum-plugin-sdk - message: "[update][SDK] Branch ${{ steps.extract_branch.outputs.branch }} | Commit ${GITHUB_SHA}" - secret: ${{ secrets.CI_BOT_TOKEN }} - repository: LedgerHQ/app-ethereum + branch: sdk/update-submodule + delete-branch: true + title: Update the SDK submodule + reviewers: apailler-ledger diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ebd76b..50c74f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [1.10.2](https://github.com/ledgerhq/app-ethereum/compare/1.10.1...1.10.2) - 2023-04-24 + +### Added + +- (clone) ID4Good +- (network) Cronos +- (network) Scroll +- (network) KCC +- (network) Rootstock +- (network) Evmos +- (network) Metis Andromeda +- (network) Kava EVM +- (network) Klaytn Cypress +- (network) Syscoin +- (network) Velas EVM +- (network) Boba Network +- (network) Energi +- Domain names support (LNX / LNS+) + +### Changed + +- Starknet blind signing wording + +### Fixed + +- Missing 44'/60' derivation path for XDC Network +- Small visual glitch with EIP-712 verbose mode with the "Review struct" page +- Possible overflow with very large transactions +- EnergyWebChain ticker +- Arbitrum ticker +- Error handling on EIP-191 APDUs +- Swap transactions handling + ## [1.10.1](https://github.com/ledgerhq/app-ethereum/compare/1.10.0...1.10.1) - 2022-11-09 ### Fixed diff --git a/Makefile b/Makefile index 0adefe5..b7704d7 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ APP_LOAD_PARAMS += --path "1517992542'/1101353413'" APPVERSION_M=1 APPVERSION_N=10 -APPVERSION_P=1 +APPVERSION_P=2 APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) APP_LOAD_FLAGS= --appFlags 0xa40 --dep Ethereum:$(APPVERSION) @@ -147,10 +147,23 @@ ifneq ($(TARGET_NAME),TARGET_NANOS) DEFINES += HAVE_EIP712_FULL_SUPPORT endif -# CryptoAssetsList testing key -CAL_TESTING_KEY:=0 -ifneq ($(CAL_TESTING_KEY),0) -DEFINES += HAVE_CAL_TESTING_KEY +# CryptoAssetsList key +CAL_TEST_KEY:=0 +CAL_CI_KEY:=0 +ifneq ($(CAL_TEST_KEY),0) +DEFINES += HAVE_CAL_TEST_KEY +endif +ifneq ($(CAL_CI_KEY),0) +DEFINES += HAVE_CAL_CI_KEY +endif + +# ENS +ifneq ($(TARGET_NAME),TARGET_NANOS) +DEFINES += HAVE_DOMAIN_NAME +DOMAIN_NAME_TEST_KEY:=0 +ifneq ($(DOMAIN_NAME_TEST_KEY),0) +DEFINES += HAVE_DOMAIN_NAME_TEST_KEY +endif endif # Enabling debug PRINTF diff --git a/doc/ethapp.adoc b/doc/ethapp.adoc index bc57b3b..6997b9b 100644 --- a/doc/ethapp.adoc +++ b/doc/ethapp.adoc @@ -38,6 +38,9 @@ Application version 1.9.19 - 2022-05-17 - Add EIP712 STRUCT DEFINITION & EIP712 STRUCT IMPLEMENTATION - Update to SIGN ETH EIP712 +### 1.10.2 + - Add domain names support + ## About This application describes the APDU messages interface to communicate with the Ethereum application. @@ -881,6 +884,82 @@ _Output data_ None +### GET CHALLENGE + +#### Description + +Sends a random 32-bit long value. Can prevent replay of signed payloads when the challenge +is included in said payload. + +#### Coding + +_Command_ + +[width="80%"] +|============================================================= +| *CLA* | *INS* | *P1* | *P2* | *LC* +| E0 | 20 | 00 | 00 | 00 +|============================================================= + +_Input data_ + +None + +_Output data_ + +[width="80%"] +|=========================================== +| *Description* | *Length* +| Challenge value (BE) | 4 +|=========================================== + + +### PROVIDE DOMAIN NAME + +#### Description + +This command provides a domain name (like ENS) to be displayed during transactions in place of the address it is associated to. +It shall be run just before a transaction involving the associated address that would be displayed on the device. + +The signature is computed on the TLV payload (minus the signature obviously). + +#### Coding + +_Command_ + +[width="80%"] +|============================================================== +| *CLA* | *INS* | *P1* | *P2* | *LC* +| E0 | 22 | 01 : first chunk + + 00 : following chunk + | 00 | 00 +|============================================================== + +_Input data_ + +##### If P1 == first chunk + +[width="80%"] +|========================================== +| *Description* | *Length (byte)* +| Payload length | 2 +| TLV payload | variable +|========================================== + +##### If P1 == following chunk + +[width="80%"] +|========================================== +| *Description* | *Length (byte)* +| TLV payload | variable +|========================================== + +_Output data_ + +None + + ## Transport protocol ### General transport description diff --git a/doc/ethapp_plugins.adoc b/doc/ethapp_plugins.adoc index af89087..a3bdbbe 100644 --- a/doc/ethapp_plugins.adoc +++ b/doc/ethapp_plugins.adoc @@ -134,6 +134,17 @@ The following return codes are expected, any other will abort the signing proces * ETH_PLUGIN_RESULT_OK : if the plugin can be successfully initialized * ETH_PLUGIN_RESULT_FALLBACK : if the signing logic should fallback to the generic one +There are already defined functions to extract data from a parameter: +[source,C] +---- +void copy_address(uint8_t* dst, const uint8_t* parameter, uint8_t dst_size); +void copy_parameter(uint8_t* dst, const uint8_t* parameter, uint8_t dst_size); + +// Get the value from the beginning of the parameter (right to left) and check if the rest of it is zero +bool U2BE_from_parameter(const uint8_t* parameter, uint16_t* value); +bool U4BE_from_parameter(const uint8_t* parameter, uint32_t* value); +---- + ### ETH_PLUGIN_FINALIZE [source,C] diff --git a/ethereum-plugin-sdk b/ethereum-plugin-sdk index 0de74c6..a4b971f 160000 --- a/ethereum-plugin-sdk +++ b/ethereum-plugin-sdk @@ -1 +1 @@ -Subproject commit 0de74c6382f876f89f82ef0eef90408fd94888a3 +Subproject commit a4b971f67980694d8c3862e793859209bc525545 diff --git a/examples/signTx.py b/examples/signTx.py index 527c0ce..af97925 100755 --- a/examples/signTx.py +++ b/examples/signTx.py @@ -96,6 +96,8 @@ tx = UnsignedTransaction( ) encodedTx = encode(tx, UnsignedTransaction) + +# To test an EIP-1559 transaction, uncomment this line # encodedTx = bytearray.fromhex( # "02ef0306843b9aca008504a817c80082520894b2bb2b958afa2e96dab3f3ce7162b87daea39017872386f26fc1000080c0") diff --git a/icons/nanos_app_id4good.gif b/icons/nanos_app_id4good.gif new file mode 100644 index 0000000..ad64f0f Binary files /dev/null and b/icons/nanos_app_id4good.gif differ diff --git a/icons/nanos_app_oasys.gif b/icons/nanos_app_oasys.gif new file mode 100644 index 0000000..e43f394 Binary files /dev/null and b/icons/nanos_app_oasys.gif differ diff --git a/icons/nanox_app_id4good.gif b/icons/nanox_app_id4good.gif new file mode 100644 index 0000000..d36d209 Binary files /dev/null and b/icons/nanox_app_id4good.gif differ diff --git a/icons/nanox_app_oasys.gif b/icons/nanox_app_oasys.gif new file mode 100644 index 0000000..cfb4ea4 Binary files /dev/null and b/icons/nanox_app_oasys.gif differ diff --git a/makefile_conf/chain/energywebchain.mk b/makefile_conf/chain/energywebchain.mk new file mode 100644 index 0000000..1024857 --- /dev/null +++ b/makefile_conf/chain/energywebchain.mk @@ -0,0 +1,3 @@ +APP_LOAD_PARAMS += --path "44'/246'" --path "44'/60'" +DEFINES += CHAINID_UPCASE=\"ENERGYWEBCHAIN\" CHAINID_COINNAME=\"EWT\" CHAIN_KIND=CHAIN_KIND_ENERGYWEBCHAIN CHAIN_ID=246 +APPNAME = "EnergyWebChain" diff --git a/makefile_conf/chain/ewc.mk b/makefile_conf/chain/ewc.mk deleted file mode 100644 index 816a5fb..0000000 --- a/makefile_conf/chain/ewc.mk +++ /dev/null @@ -1,3 +0,0 @@ -APP_LOAD_PARAMS += --path "44'/246'" --path "44'/60'" -DEFINES += CHAINID_UPCASE=\"EWC\" CHAINID_COINNAME=\"EWC\" CHAIN_KIND=CHAIN_KIND_EWC CHAIN_ID=246 -APPNAME = "EnergyWebChain" \ No newline at end of file diff --git a/makefile_conf/chain/id4good.mk b/makefile_conf/chain/id4good.mk new file mode 100644 index 0000000..57a50cb --- /dev/null +++ b/makefile_conf/chain/id4good.mk @@ -0,0 +1,3 @@ +APP_LOAD_PARAMS += --path "44'/161803'" +DEFINES += CHAINID_UPCASE=\"ID4GOOD\" CHAINID_COINNAME=\"A4G\" CHAIN_KIND=CHAIN_KIND_ID4GOOD CHAIN_ID=846000 +APPNAME = "ID4Good" diff --git a/makefile_conf/chain/oasys.mk b/makefile_conf/chain/oasys.mk new file mode 100644 index 0000000..0e2dc18 --- /dev/null +++ b/makefile_conf/chain/oasys.mk @@ -0,0 +1,3 @@ +APP_LOAD_PARAMS += --path "44'/685'" --path "44'/60'" +DEFINES += CHAINID_UPCASE=\"OASYS\" CHAINID_COINNAME=\"OAS\" CHAIN_KIND=CHAIN_KIND_OASYS CHAIN_ID=248 +APPNAME = "Oasys" diff --git a/makefile_conf/chain/xdcnetwork.mk b/makefile_conf/chain/xdcnetwork.mk index 0bea527..4be443a 100644 --- a/makefile_conf/chain/xdcnetwork.mk +++ b/makefile_conf/chain/xdcnetwork.mk @@ -1,3 +1,3 @@ -APP_LOAD_PARAMS += --path "44'/550'" +APP_LOAD_PARAMS += --path "44'/60'" --path "44'/550'" DEFINES += CHAINID_UPCASE=\"XDCNETWORK\" CHAINID_COINNAME=\"XDC\" CHAIN_KIND=CHAIN_KIND_XDCNETWORK CHAIN_ID=50 -APPNAME = "XDC Network" \ No newline at end of file +APPNAME = "XDC Network" diff --git a/src/apdu_constants.h b/src/apdu_constants.h index cab0688..7911d6b 100644 --- a/src/apdu_constants.h +++ b/src/apdu_constants.h @@ -24,6 +24,8 @@ #define INS_EIP712_STRUCT_DEF 0x1A #define INS_EIP712_STRUCT_IMPL 0x1C #define INS_EIP712_FILTERING 0x1E +#define INS_ENS_GET_CHALLENGE 0x20 +#define INS_ENS_PROVIDE_INFO 0x22 #define P1_CONFIRM 0x01 #define P1_NON_CONFIRM 0x00 #define P2_NO_CHAINCODE 0x00 diff --git a/src/chainConfig.h b/src/chainConfig.h index ab91aa6..070de49 100644 --- a/src/chainConfig.h +++ b/src/chainConfig.h @@ -49,7 +49,7 @@ typedef enum chain_kind_e { CHAIN_KIND_TOBALABA, CHAIN_KIND_DEXON, CHAIN_KIND_VOLTA, - CHAIN_KIND_EWC, + CHAIN_KIND_ENERGYWEBCHAIN, CHAIN_KIND_ARTIS_SIGMA1, CHAIN_KIND_ARTIS_TAU1, CHAIN_KIND_WEBCHAIN, @@ -72,7 +72,9 @@ typedef enum chain_kind_e { CHAIN_KIND_METER, CHAIN_KIND_MULTIVAC, CHAIN_KIND_TECRA, - CHAIN_KIND_APOTHEMNETWORK + CHAIN_KIND_APOTHEMNETWORK, + CHAIN_KIND_ID4GOOD, + CHAIN_KIND_OASYS } chain_kind_t; typedef struct chain_config_s { diff --git a/src/eth_plugin_handler.c b/src/eth_plugin_handler.c index 22ffbe8..3aa6883 100644 --- a/src/eth_plugin_handler.c +++ b/src/eth_plugin_handler.c @@ -63,12 +63,8 @@ void eth_plugin_prepare_query_contract_UI(ethQueryContractUI_t *queryContractUI, queryContractUI->item2 = &tmpCtx.transactionContext.extraInfo[0]; } - strlcpy(queryContractUI->network_ticker, get_network_ticker(), MAX_TICKER_LEN); - queryContractUI->screenIndex = screenIndex; - strlcpy(queryContractUI->network_ticker, - get_network_ticker(), - sizeof(queryContractUI->network_ticker)); + strlcpy(queryContractUI->network_ticker, get_network_ticker(), MAX_TICKER_LEN); queryContractUI->title = title; queryContractUI->titleLength = titleLength; queryContractUI->msg = msg; diff --git a/src/eth_plugin_handler.h b/src/eth_plugin_handler.h index 28b13fb..acd982d 100644 --- a/src/eth_plugin_handler.h +++ b/src/eth_plugin_handler.h @@ -31,6 +31,4 @@ eth_plugin_result_t eth_plugin_perform_init(uint8_t *contractAddress, // NULL for cached address, or base contract address eth_plugin_result_t eth_plugin_call(int method, void *parameter); -void plugin_ui_start(void); - #endif // _ETH_PLUGIN_HANDLER_H_ diff --git a/src/eth_plugin_interface.h b/src/eth_plugin_interface.h index 48c6e67..5bf5499 100644 --- a/src/eth_plugin_interface.h +++ b/src/eth_plugin_interface.h @@ -13,7 +13,8 @@ typedef enum { ETH_PLUGIN_INTERFACE_VERSION_2 = 2, ETH_PLUGIN_INTERFACE_VERSION_3 = 3, ETH_PLUGIN_INTERFACE_VERSION_4 = 4, - ETH_PLUGIN_INTERFACE_VERSION_LATEST = 5, + ETH_PLUGIN_INTERFACE_VERSION_5 = 5, + ETH_PLUGIN_INTERFACE_VERSION_LATEST = 6 } eth_plugin_interface_version_t; typedef enum { diff --git a/src/eth_plugin_internal.c b/src/eth_plugin_internal.c index b266969..075f2ec 100644 --- a/src/eth_plugin_internal.c +++ b/src/eth_plugin_internal.c @@ -1,5 +1,6 @@ #include #include "eth_plugin_internal.h" +#include "ethUtils.h" // allzeroes bool erc20_plugin_available_check(void); @@ -15,6 +16,24 @@ void copy_parameter(uint8_t* dst, const uint8_t* parameter, uint8_t dst_size) { memmove(dst, parameter, copy_size); } +bool U2BE_from_parameter(const uint8_t* parameter, uint16_t* value) { + if (allzeroes(parameter, PARAMETER_LENGTH - sizeof(uint16_t))) { + *value = U2BE(parameter, PARAMETER_LENGTH - sizeof(uint16_t)); + return true; + } + + return false; +} + +bool U4BE_from_parameter(const uint8_t* parameter, uint32_t* value) { + if (allzeroes(parameter, PARAMETER_LENGTH - sizeof(uint32_t))) { + *value = U4BE(parameter, PARAMETER_LENGTH - sizeof(uint32_t)); + return true; + } + + return false; +} + #ifdef HAVE_STARKWARE void starkware_plugin_call(int message, void* parameters); #endif diff --git a/src/eth_plugin_internal.h b/src/eth_plugin_internal.h index 1ad8382..692c9b6 100644 --- a/src/eth_plugin_internal.h +++ b/src/eth_plugin_internal.h @@ -16,6 +16,11 @@ void copy_parameter(uint8_t* dst, const uint8_t* parameter, uint8_t dst_size); void erc721_plugin_call(int message, void* parameters); void erc1155_plugin_call(int message, void* parameters); +// Get the value from the beginning of the parameter (right to left) and check if the rest of it is +// zero +bool U2BE_from_parameter(const uint8_t* parameter, uint16_t* value); +bool U4BE_from_parameter(const uint8_t* parameter, uint32_t* value); + typedef bool (*PluginAvailableCheck)(void); typedef struct internalEthPlugin_t { diff --git a/src/eth_plugin_ui.c b/src/eth_plugin_ui.c deleted file mode 100644 index d4bdc15..0000000 --- a/src/eth_plugin_ui.c +++ /dev/null @@ -1,11 +0,0 @@ -#include "shared_context.h" -#include "eth_plugin_handler.h" -#include "ux.h" -#include "feature_signTx.h" - -void plugin_ui_start() { - dataContext.tokenContext.pluginUiState = PLUGIN_UI_OUTSIDE; - dataContext.tokenContext.pluginUiCurrentItem = 0; - - ux_approve_tx(true); -} diff --git a/src/handle_get_printable_amount.c b/src/handle_get_printable_amount.c index 13acc28..715c85d 100644 --- a/src/handle_get_printable_amount.c +++ b/src/handle_get_printable_amount.c @@ -20,8 +20,7 @@ int handle_get_printable_amount(get_printable_amount_parameters_t* params, chain if (params->is_fee) { uint8_t ticker_len = strnlen(config->coinName, sizeof(config->coinName)); memcpy(ticker, config->coinName, ticker_len); - ticker[ticker_len] = ' '; - ticker[ticker_len + 1] = '\0'; + ticker[ticker_len] = '\0'; decimals = WEI_TO_ETHER; } else { // If the amount is *not* a fee, decimals and ticker are built from the given config diff --git a/src/main.c b/src/main.c index 1828416..a483bf4 100644 --- a/src/main.c +++ b/src/main.c @@ -29,6 +29,8 @@ #include "handle_get_printable_amount.h" #include "handle_check_address.h" #include "commands_712.h" +#include "challenge.h" +#include "domain_name.h" #ifdef HAVE_STARKWARE #include "stark_crypto.h" @@ -228,8 +230,8 @@ extraInfo_t *getKnownToken(uint8_t *contractAddress) { case CHAIN_KIND_VOLTA: numTokens = NUM_TOKENS_VOLTA; break; - case CHAIN_KIND_EWC: - numTokens = NUM_TOKENS_EWC; + case CHAIN_KIND_ENERGYWEBCHAIN: + numTokens = NUM_TOKENS_ENERGYWEBCHAIN; break; case CHAIN_KIND_WEBCHAIN: numTokens = NUM_TOKENS_WEBCHAIN; @@ -294,6 +296,12 @@ extraInfo_t *getKnownToken(uint8_t *contractAddress) { case CHAIN_KIND_APOTHEMNETWORK: numTokens = NUM_TOKENS_APOTHEMNETWORK; break; + case CHAIN_KIND_ID4GOOD: + numTokens = NUM_TOKENS_ID4GOOD; + break; + case CHAIN_KIND_OASYS: + numTokens = NUM_TOKENS_OASYS; + break; } for (i = 0; i < numTokens; i++) { switch (chainConfig->kind) { @@ -381,8 +389,8 @@ extraInfo_t *getKnownToken(uint8_t *contractAddress) { case CHAIN_KIND_VOLTA: currentToken = (tokenDefinition_t *) PIC(&TOKENS_VOLTA[i]); break; - case CHAIN_KIND_EWC: - currentToken = (tokenDefinition_t *) PIC(&TOKENS_EWC[i]); + case CHAIN_KIND_ENERGYWEBCHAIN: + currentToken = (tokenDefinition_t *) PIC(&TOKENS_ENERGYWEBCHAIN[i]); break; case CHAIN_KIND_WEBCHAIN: currentToken = (tokenDefinition_t *) PIC(&TOKENS_WEBCHAIN[i]); @@ -447,6 +455,12 @@ extraInfo_t *getKnownToken(uint8_t *contractAddress) { case CHAIN_KIND_APOTHEMNETWORK: currentToken = (tokenDefinition_t *) PIC(&TOKENS_APOTHEMNETWORK[i]); break; + case CHAIN_KIND_ID4GOOD: + currentToken = (tokenDefinition_t *) PIC(&TOKENS_ID4GOOD[i]); + break; + case CHAIN_KIND_OASYS: + currentToken = (tokenDefinition_t *) PIC(&TOKENS_OASYS[i]); + break; } if (memcmp(currentToken->address, tmpContent.txContent.destination, ADDRESS_LENGTH) == 0) { return currentToken; @@ -667,10 +681,12 @@ void handleApdu(unsigned int *flags, unsigned int *tx) { case INS_SIGN_PERSONAL_MESSAGE: memset(tmpCtx.transactionContext.tokenSet, 0, MAX_ITEMS); *flags |= IO_ASYNCH_REPLY; - 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]); + 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: @@ -735,6 +751,19 @@ void handleApdu(unsigned int *flags, unsigned int *tx) { 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; @@ -912,7 +941,7 @@ void app_exit() { void init_coin_config(chain_config_t *coin_config) { memset(coin_config, 0, sizeof(chain_config_t)); - strcpy(coin_config->coinName, CHAINID_COINNAME " "); + strcpy(coin_config->coinName, CHAINID_COINNAME); coin_config->chainId = CHAIN_ID; coin_config->kind = CHAIN_KIND; } @@ -940,16 +969,22 @@ void coin_main(chain_config_t *coin_config) { G_io_app.plane_mode = os_setting_get(OS_SETTING_PLANEMODE, NULL, 0); #endif // TARGET_NANOX - if (N_storage.initialized != 0x01) { + if (!N_storage.initialized) { internalStorage_t storage; #ifdef HAVE_ALLOW_DATA - storage.dataAllowed = 0x01; + storage.dataAllowed = true; #else - storage.dataAllowed = 0x00; + storage.dataAllowed = false; #endif - storage.contractDetails = 0x00; - storage.displayNonce = 0x00; - storage.initialized = 0x01; + 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)); } @@ -963,6 +998,11 @@ void coin_main(chain_config_t *coin_config) { BLE_power(1, "Nano X"); #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) { diff --git a/src/shared_context.h b/src/shared_context.h index 3b3b98b..ce907dd 100644 --- a/src/shared_context.h +++ b/src/shared_context.h @@ -24,13 +24,16 @@ typedef struct bip32_path_t { } bip32_path_t; typedef struct internalStorage_t { - unsigned char dataAllowed; - unsigned char contractDetails; - unsigned char displayNonce; + bool dataAllowed; + bool contractDetails; + bool displayNonce; #ifdef HAVE_EIP712_FULL_SUPPORT bool verbose_eip712; #endif // HAVE_EIP712_FULL_SUPPORT - uint8_t initialized; +#ifdef HAVE_DOMAIN_NAME + bool verbose_domain_name; +#endif // HAVE_DOMAIN_NAME + bool initialized; } internalStorage_t; #ifdef HAVE_STARKWARE @@ -50,10 +53,9 @@ typedef enum starkQuantumType_e { typedef struct tokenContext_t { char pluginName[PLUGIN_ID_LENGTH]; - uint8_t pluginStatus; uint8_t data[INT256_LENGTH]; - uint8_t fieldIndex; + uint16_t fieldIndex; uint8_t fieldOffset; uint8_t pluginUiMaxItems; @@ -65,9 +67,13 @@ typedef struct tokenContext_t { uint8_t contractAddress[ADDRESS_LENGTH]; uint8_t methodSelector[SELECTOR_LENGTH]; }; - uint8_t pluginContext[5 * INT256_LENGTH]; + // This needs to be strictly 4 bytes aligned since pointers to it will be casted as + // plugin context struct pointers (structs that contain up to 4 bytes wide elements) + uint8_t pluginContext[5 * INT256_LENGTH] __attribute__((aligned(4))); }; + uint8_t pluginStatus; + #ifdef HAVE_STARKWARE uint8_t quantum[32]; uint8_t mintingBlob[32]; @@ -77,6 +83,8 @@ typedef struct tokenContext_t { } tokenContext_t; +_Static_assert((offsetof(tokenContext_t, pluginContext) % 4) == 0, "Plugin context not aligned"); + typedef struct publicKeyContext_t { cx_ecfp_public_key_t publicKey; char address[41]; @@ -167,7 +175,7 @@ typedef enum { #define NETWORK_STRING_MAX_SIZE 16 -typedef struct txStringProperties_t { +typedef struct txStringProperties_s { char fullAddress[43]; char fullAmount[79]; // 2^256 is 78 digits long char maxFee[50]; @@ -182,7 +190,7 @@ typedef struct txStringProperties_t { #endif #define SHARED_CTX_FIELD_2_SIZE 40 -typedef struct strDataTmp_t { +typedef struct strDataTmp_s { char tmp[SHARED_CTX_FIELD_1_SIZE]; char tmp2[SHARED_CTX_FIELD_2_SIZE]; } strDataTmp_t; diff --git a/src/tokens.c b/src/tokens.c index 20ae6f8..e17e482 100644 --- a/src/tokens.c +++ b/src/tokens.c @@ -172,7 +172,7 @@ const tokenDefinition_t const TOKENS_DEXON[NUM_TOKENS_DEXON] = {}; const tokenDefinition_t const TOKENS_VOLTA[NUM_TOKENS_VOLTA] = {}; -const tokenDefinition_t const TOKENS_EWC[NUM_TOKENS_EWC] = {}; +const tokenDefinition_t const TOKENS_ENERGYWEBCHAIN[NUM_TOKENS_ENERGYWEBCHAIN] = {}; const tokenDefinition_t const TOKENS_ARTIS_SIGMA1[NUM_TOKENS_ARTIS_SIGMA1] = {}; @@ -222,4 +222,6 @@ const tokenDefinition_t const TOKENS_TECRA[NUM_TOKENS_TECRA] = {}; const tokenDefinition_t const TOKENS_APOTHEMNETWORK[NUM_TOKENS_APOTHEMNETWORK] = {}; +const tokenDefinition_t const TOKENS_OASYS[NUM_TOKENS_OASYS] = {}; + #endif diff --git a/src/tokens.h b/src/tokens.h index 7aa38f6..1a261c3 100644 --- a/src/tokens.h +++ b/src/tokens.h @@ -21,7 +21,7 @@ #include #include "ethUstream.h" -#define MAX_TICKER_LEN 12 // 10 characters + ' ' + '\0' +#define MAX_TICKER_LEN 11 // 10 characters + '\0' #define MAX_ITEMS 2 typedef struct tokenDefinition_t { @@ -43,21 +43,31 @@ extern tokenDefinition_t const TOKENS_EXTRA[NUM_TOKENS_EXTRA]; #ifndef HAVE_TOKENS_LIST +#if defined(HAVE_CAL_TEST_KEY) && defined(HAVE_CAL_CI_KEY) +#error "CAL key contradiction, two alternative keys selected at once" +#endif + static const uint8_t LEDGER_SIGNATURE_PUBLIC_KEY[] = { -#ifndef HAVE_CAL_TESTING_KEY - // production key 2019-01-11 03:07PM (erc20signer) - 0x04, 0x5e, 0x6c, 0x10, 0x20, 0xc1, 0x4d, 0xc4, 0x64, 0x42, 0xfe, 0x89, 0xf9, 0x7c, - 0x0b, 0x68, 0xcd, 0xb1, 0x59, 0x76, 0xdc, 0x24, 0xf2, 0x4c, 0x31, 0x6e, 0x7b, 0x30, - 0xfe, 0x4e, 0x8c, 0xc7, 0x6b, 0x14, 0x89, 0x15, 0x0c, 0x21, 0x51, 0x4e, 0xbf, 0x44, - 0x0f, 0xf5, 0xde, 0xa5, 0x39, 0x3d, 0x83, 0xde, 0x53, 0x58, 0xcd, 0x09, 0x8f, 0xce, - 0x8f, 0xd0, 0xf8, 0x1d, 0xaa, 0x94, 0x97, 0x91, 0x83 -#else +#if defined(HAVE_CAL_TEST_KEY) // test key 2019-01-11 03:07PM (erc20signer) 0x04, 0x20, 0xda, 0x62, 0x00, 0x3c, 0x0c, 0xe0, 0x97, 0xe3, 0x36, 0x44, 0xa1, 0x0f, 0xe4, 0xc3, 0x04, 0x54, 0x06, 0x9a, 0x44, 0x54, 0xf0, 0xfa, 0x9d, 0x4e, 0x84, 0xf4, 0x50, 0x91, 0x42, 0x9b, 0x52, 0x20, 0xaf, 0x9e, 0x35, 0xc0, 0xb2, 0xd9, 0x28, 0x93, 0x80, 0x13, 0x73, 0x07, 0xde, 0x4d, 0xd1, 0xd4, 0x18, 0x42, 0x8c, 0xf2, 0x1a, 0x93, 0xb3, 0x35, 0x61, 0xbb, 0x09, 0xd8, 0x8f, 0xe5, 0x79 +#elif defined(HAVE_CAL_CI_KEY) + 0x04, 0x4c, 0xca, 0x8f, 0xad, 0x49, 0x6a, 0xa5, 0x04, 0x0a, 0x00, 0xa7, 0xeb, 0x2f, + 0x5c, 0xc3, 0xb8, 0x53, 0x76, 0xd8, 0x8b, 0xa1, 0x47, 0xa7, 0xd7, 0x05, 0x4a, 0x99, + 0xc6, 0x40, 0x56, 0x18, 0x87, 0xfe, 0x17, 0xa0, 0x96, 0xe3, 0x6c, 0x3b, 0x52, 0x3b, + 0x24, 0x4f, 0x3e, 0x2f, 0xf7, 0xf8, 0x40, 0xae, 0x26, 0xc4, 0xe7, 0x7a, 0xd3, 0xbc, + 0x73, 0x9a, 0xf5, 0xde, 0x6f, 0x2d, 0x77, 0xa7, 0xb6 +#else + // production key 2019-01-11 03:07PM (erc20signer) + 0x04, 0x5e, 0x6c, 0x10, 0x20, 0xc1, 0x4d, 0xc4, 0x64, 0x42, 0xfe, 0x89, 0xf9, 0x7c, + 0x0b, 0x68, 0xcd, 0xb1, 0x59, 0x76, 0xdc, 0x24, 0xf2, 0x4c, 0x31, 0x6e, 0x7b, 0x30, + 0xfe, 0x4e, 0x8c, 0xc7, 0x6b, 0x14, 0x89, 0x15, 0x0c, 0x21, 0x51, 0x4e, 0xbf, 0x44, + 0x0f, 0xf5, 0xde, 0xa5, 0x39, 0x3d, 0x83, 0xde, 0x53, 0x58, 0xcd, 0x09, 0x8f, 0xce, + 0x8f, 0xd0, 0xf8, 0x1d, 0xaa, 0x94, 0x97, 0x91, 0x83 #endif }; @@ -90,7 +100,7 @@ static const uint8_t LEDGER_SIGNATURE_PUBLIC_KEY[] = { #define NUM_TOKENS_TOBALABA 0 #define NUM_TOKENS_DEXON 0 #define NUM_TOKENS_VOLTA 0 -#define NUM_TOKENS_EWC 0 +#define NUM_TOKENS_ENERGYWEBCHAIN 0 #define NUM_TOKENS_ARTIS_SIGMA1 0 #define NUM_TOKENS_ARTIS_TAU1 0 #define NUM_TOKENS_WEBCHAIN 0 @@ -115,6 +125,7 @@ static const uint8_t LEDGER_SIGNATURE_PUBLIC_KEY[] = { #define NUM_TOKENS_MULTIVAC 0 #define NUM_TOKENS_TECRA 0 #define NUM_TOKENS_APOTHEMNETWORK 0 +#define NUM_TOKENS_OASYS 0 extern tokenDefinition_t const TOKENS_AKROMA[NUM_TOKENS_AKROMA]; extern tokenDefinition_t const TOKENS_ELLAISM[NUM_TOKENS_ELLAISM]; @@ -141,7 +152,7 @@ extern tokenDefinition_t const TOKENS_TOMOCHAIN[NUM_TOKENS_TOMOCHAIN]; extern tokenDefinition_t const TOKENS_TOBALABA[NUM_TOKENS_TOBALABA]; extern tokenDefinition_t const TOKENS_DEXON[NUM_TOKENS_DEXON]; extern tokenDefinition_t const TOKENS_VOLTA[NUM_TOKENS_VOLTA]; -extern tokenDefinition_t const TOKENS_EWC[NUM_TOKENS_EWC]; +extern tokenDefinition_t const TOKENS_ENERGYWEBCHAIN[NUM_TOKENS_ENERGYWEBCHAIN]; extern tokenDefinition_t const TOKENS_ARTIS_SIGMA1[NUM_TOKENS_ARTIS_SIGMA1]; extern tokenDefinition_t const TOKENS_ARTIS_TAU1[NUM_TOKENS_ARTIS_TAU1]; extern tokenDefinition_t const TOKENS_WEBCHAIN[NUM_TOKENS_WEBCHAIN]; @@ -165,6 +176,7 @@ extern tokenDefinition_t const TOKENS_METER[NUM_TOKENS_METER]; extern tokenDefinition_t const TOKENS_MULTIVAC[NUM_TOKENS_MULTIVAC]; extern tokenDefinition_t const TOKENS_TECRA[NUM_TOKENS_TECRA]; extern tokenDefinition_t const TOKENS_APOTHEMNETWORK[NUM_TOKENS_APOTHEMNETWORK]; +extern tokenDefinition_t const TOKENS_OASYS[NUM_TOKENS_OASYS]; #endif /* HAVE_TOKENS_LIST */ diff --git a/src/utils.c b/src/utils.c index be03f10..2c553b4 100644 --- a/src/utils.c +++ b/src/utils.c @@ -132,6 +132,9 @@ void amountToString(const uint8_t *amount, uint8_t ticker_len = strnlen(ticker, MAX_TICKER_LEN); memcpy(out_buffer, ticker, MIN(out_buffer_size, ticker_len)); + if (ticker_len > 0) { + out_buffer[ticker_len++] = ' '; + } if (adjustDecimals(tmp_buffer, amount_len, @@ -155,8 +158,7 @@ bool parse_swap_config(const uint8_t *config, uint8_t config_len, char *ticker, } memcpy(ticker, config + offset, ticker_len); offset += ticker_len; - ticker[ticker_len] = ' '; - ticker[ticker_len + 1] = '\0'; + ticker[ticker_len] = '\0'; if (config_len - offset < 1) { return false; diff --git a/src_bagl/ui_domain_name.c b/src_bagl/ui_domain_name.c new file mode 100644 index 0000000..599133d --- /dev/null +++ b/src_bagl/ui_domain_name.c @@ -0,0 +1,17 @@ +#ifdef HAVE_DOMAIN_NAME + +#include "ux.h" +#include "domain_name.h" + +////////////////////////////////////////////////////////////////////// +// clang-format off +UX_STEP_NOCB( + ux_domain_name_step, + bnnn_paging, + { + .title = "Domain", + .text = g_domain_name + }); +// clang-format on + +#endif // HAVE_DOMAIN_NAME diff --git a/src_bagl/ui_domain_name.h b/src_bagl/ui_domain_name.h new file mode 100644 index 0000000..bcfa8b0 --- /dev/null +++ b/src_bagl/ui_domain_name.h @@ -0,0 +1,12 @@ +#ifdef HAVE_DOMAIN_NAME + +#ifndef UI_DOMAIN_NAME_H_ +#define UI_DOMAIN_NAME_H_ + +#include "ux.h" + +extern const ux_flow_step_t ux_domain_name_step; + +#endif // UI_DOMAIN_NAME_H_ + +#endif // HAVE_DOMAIN_NAME diff --git a/src_bagl/ui_flow.c b/src_bagl/ui_flow.c index 7cac427..a98ce6b 100644 --- a/src_bagl/ui_flow.c +++ b/src_bagl/ui_flow.c @@ -7,13 +7,26 @@ #define DISABLED_STR "Disabled" #define BUF_INCREMENT (MAX(strlen(ENABLED_STR), strlen(DISABLED_STR)) + 1) -void display_settings(const ux_flow_step_t* const start_step); -void switch_settings_blind_signing(void); -void switch_settings_display_data(void); -void switch_settings_display_nonce(void); +// Reuse the strings.common.fullAmount buffer for settings displaying. +// No risk of collision as this buffer is unused in the settings menu +#define SETTING_BLIND_SIGNING_STATE (strings.common.fullAmount) +#define SETTING_DISPLAY_DATA_STATE (strings.common.fullAmount + (BUF_INCREMENT * 1)) +#define SETTING_DISPLAY_NONCE_STATE (strings.common.fullAmount + (BUF_INCREMENT * 2)) +#define SETTING_VERBOSE_EIP712_STATE (strings.common.fullAmount + (BUF_INCREMENT * 3)) +#define SETTING_VERBOSE_DOMAIN_NAME_STATE (strings.common.fullAmount + (BUF_INCREMENT * 4)) + +#define BOOL_TO_STATE_STR(b) (b ? ENABLED_STR : DISABLED_STR) + +static void display_settings(const ux_flow_step_t* const start_step); +static void switch_settings_blind_signing(void); +static void switch_settings_display_data(void); +static void switch_settings_display_nonce(void); #ifdef HAVE_EIP712_FULL_SUPPORT -void switch_settings_verbose_eip712(void); +static void switch_settings_verbose_eip712(void); #endif // HAVE_EIP712_FULL_SUPPORT +#ifdef HAVE_DOMAIN_NAME +static void switch_settings_verbose_domain_name(void); +#endif // HAVE_DOMAIN_NAME ////////////////////////////////////////////////////////////////////// // clang-format off @@ -75,7 +88,7 @@ UX_STEP_CB( "Transaction", "blind signing", #endif - strings.common.fullAddress + SETTING_BLIND_SIGNING_STATE }); UX_STEP_CB( @@ -95,7 +108,7 @@ UX_STEP_CB( "Show contract data", "details", #endif - strings.common.fullAddress + BUF_INCREMENT + SETTING_DISPLAY_DATA_STATE }); UX_STEP_CB( @@ -115,7 +128,7 @@ UX_STEP_CB( "Show account nonce", "in transactions", #endif - strings.common.fullAddress + (BUF_INCREMENT * 2) + SETTING_DISPLAY_NONCE_STATE }); #ifdef HAVE_EIP712_FULL_SUPPORT @@ -127,10 +140,23 @@ UX_STEP_CB( "Verbose EIP-712", "Ignore filtering &", "display raw content", - strings.common.fullAddress + (BUF_INCREMENT * 3) + SETTING_VERBOSE_EIP712_STATE }); #endif // HAVE_EIP712_FULL_SUPPORT +#ifdef HAVE_DOMAIN_NAME +UX_STEP_CB( + ux_settings_flow_verbose_domain_name_step, + bnnn, + switch_settings_verbose_domain_name(), + { + "Verbose domains", + "Show", + "resolved address", + SETTING_VERBOSE_DOMAIN_NAME_STATE + }); +#endif // HAVE_DOMAIN_NAME + UX_STEP_CB( ux_settings_flow_back_step, @@ -149,54 +175,61 @@ UX_FLOW(ux_settings_flow, #ifdef HAVE_EIP712_FULL_SUPPORT &ux_settings_flow_verbose_eip712_step, #endif // HAVE_EIP712_FULL_SUPPORT +#ifdef HAVE_DOMAIN_NAME + &ux_settings_flow_verbose_domain_name_step, +#endif // HAVE_DOMAIN_NAME &ux_settings_flow_back_step); -void display_settings(const ux_flow_step_t* const start_step) { - bool settings[] = {N_storage.dataAllowed, - N_storage.contractDetails, - N_storage.displayNonce, +static void display_settings(const ux_flow_step_t* const start_step) { + strlcpy(SETTING_BLIND_SIGNING_STATE, BOOL_TO_STATE_STR(N_storage.dataAllowed), BUF_INCREMENT); + strlcpy(SETTING_DISPLAY_DATA_STATE, + BOOL_TO_STATE_STR(N_storage.contractDetails), + BUF_INCREMENT); + strlcpy(SETTING_DISPLAY_NONCE_STATE, BOOL_TO_STATE_STR(N_storage.displayNonce), BUF_INCREMENT); #ifdef HAVE_EIP712_FULL_SUPPORT - N_storage.verbose_eip712 + strlcpy(SETTING_VERBOSE_EIP712_STATE, + BOOL_TO_STATE_STR(N_storage.verbose_eip712), + BUF_INCREMENT); #endif // HAVE_EIP712_FULL_SUPPORT - }; - uint8_t offset = 0; - - for (unsigned int i = 0; i < ARRAY_SIZE(settings); ++i) { - strlcpy(strings.common.fullAddress + offset, - (settings[i] ? ENABLED_STR : DISABLED_STR), - sizeof(strings.common.fullAddress) - offset); - offset += BUF_INCREMENT; - } +#ifdef HAVE_DOMAIN_NAME + strlcpy(SETTING_VERBOSE_DOMAIN_NAME_STATE, + BOOL_TO_STATE_STR(N_storage.verbose_domain_name), + BUF_INCREMENT); +#endif // HAVE_DOMAIN_NAME ux_flow_init(0, ux_settings_flow, start_step); } -void switch_settings_blind_signing(void) { - uint8_t value = (N_storage.dataAllowed ? 0 : 1); - nvm_write((void*) &N_storage.dataAllowed, (void*) &value, sizeof(uint8_t)); - display_settings(&ux_settings_flow_blind_signing_step); +static void toggle_setting(volatile bool* setting, const ux_flow_step_t* ui_step) { + bool value = !*setting; + nvm_write((void*) setting, (void*) &value, sizeof(value)); + display_settings(ui_step); } -void switch_settings_display_data(void) { - uint8_t value = (N_storage.contractDetails ? 0 : 1); - nvm_write((void*) &N_storage.contractDetails, (void*) &value, sizeof(uint8_t)); - display_settings(&ux_settings_flow_display_data_step); +static void switch_settings_blind_signing(void) { + toggle_setting(&N_storage.dataAllowed, &ux_settings_flow_blind_signing_step); } -void switch_settings_display_nonce(void) { - uint8_t value = (N_storage.displayNonce ? 0 : 1); - nvm_write((void*) &N_storage.displayNonce, (void*) &value, sizeof(uint8_t)); - display_settings(&ux_settings_flow_display_nonce_step); +static void switch_settings_display_data(void) { + toggle_setting(&N_storage.contractDetails, &ux_settings_flow_display_data_step); +} + +static void switch_settings_display_nonce(void) { + toggle_setting(&N_storage.displayNonce, &ux_settings_flow_display_nonce_step); } #ifdef HAVE_EIP712_FULL_SUPPORT -void switch_settings_verbose_eip712(void) { - bool value = !N_storage.verbose_eip712; - nvm_write((void*) &N_storage.verbose_eip712, (void*) &value, sizeof(value)); - display_settings(&ux_settings_flow_verbose_eip712_step); +static void switch_settings_verbose_eip712(void) { + toggle_setting(&N_storage.verbose_eip712, &ux_settings_flow_verbose_eip712_step); } #endif // HAVE_EIP712_FULL_SUPPORT +#ifdef HAVE_DOMAIN_NAME +static void switch_settings_verbose_domain_name(void) { + toggle_setting(&N_storage.verbose_domain_name, &ux_settings_flow_verbose_domain_name_step); +} +#endif // HAVE_DOMAIN_NAME + ////////////////////////////////////////////////////////////////////// // clang-format off #ifdef TARGET_NANOS diff --git a/src_bagl/ui_flow_signMessage.c b/src_bagl/ui_flow_signMessage.c index 9b97d57..3c2a75a 100644 --- a/src_bagl/ui_flow_signMessage.c +++ b/src_bagl/ui_flow_signMessage.c @@ -18,6 +18,9 @@ static void dummy_pre_cb(void) { static void dummy_post_cb(void) { if (ui_pos == UI_191_POS_QUESTION) { + // temporarily disable button clicks, they will be re-enabled as soon as new data + // is received and the page is redrawn with ux_flow_init() + G_ux.stack[0].button_push_callback = NULL; continue_displaying_message(); } else // UI_191_END { @@ -55,6 +58,7 @@ UX_STEP_CB( #else nnn, #endif + G_ux.stack[0].button_push_callback = NULL; // disable button clicks skip_rest_of_message(), { #ifndef TARGET_NANOS diff --git a/src_bagl/ui_flow_signMessage712.c b/src_bagl/ui_flow_signMessage712.c index 9769bb2..d21d047 100644 --- a/src_bagl/ui_flow_signMessage712.c +++ b/src_bagl/ui_flow_signMessage712.c @@ -7,19 +7,25 @@ enum { UI_712_POS_REVIEW, UI_712_POS_END }; static uint8_t ui_pos; static void dummy_cb(void) { - if (!ui_712_next_field()) { - if (ui_pos == UI_712_POS_REVIEW) { - ux_flow_next(); - ui_pos = UI_712_POS_END; - } else // UI_712_POS_END - { - ux_flow_prev(); - ui_pos = UI_712_POS_REVIEW; - } - } else { - // temporarily disable button clicks, they will be re-enabled as soon as new data - // is received and the page is redrawn with ux_flow_init() - G_ux.stack[0].button_push_callback = NULL; + switch (ui_712_next_field()) { + case EIP712_NO_MORE_FIELD: + if (ui_pos == UI_712_POS_REVIEW) { + ux_flow_next(); + ui_pos = UI_712_POS_END; + } else // UI_712_POS_END + { + ux_flow_prev(); + ui_pos = UI_712_POS_REVIEW; + } + break; + case EIP712_FIELD_INCOMING: + // temporarily disable button clicks, they will be re-enabled as soon as new data + // is received and the page is redrawn with ux_flow_init() + G_ux.stack[0].button_push_callback = NULL; + break; + case EIP712_FIELD_LATER: + default: + break; } } diff --git a/src_bagl/ui_flow_signTx.c b/src_bagl/ui_flow_signTx.c index 574dd9c..2b55922 100644 --- a/src_bagl/ui_flow_signTx.c +++ b/src_bagl/ui_flow_signTx.c @@ -8,6 +8,8 @@ #include "ui_plugin.h" #include "common_ui.h" #include "plugins.h" +#include "domain_name.h" +#include "ui_domain_name.h" // clang-format off UX_STEP_NOCB( @@ -217,7 +219,19 @@ void ux_approve_tx(bool fromPlugin) { } else { // We're in a regular transaction, just show the amount and the address ux_approval_tx_flow[step++] = &ux_approval_amount_step; - ux_approval_tx_flow[step++] = &ux_approval_address_step; +#ifdef HAVE_DOMAIN_NAME + uint64_t chain_id = get_chain_id(); + if (has_domain_name(&chain_id, tmpContent.txContent.destination)) { + ux_approval_tx_flow[step++] = &ux_domain_name_step; + if (N_storage.verbose_domain_name) { + ux_approval_tx_flow[step++] = &ux_approval_address_step; + } + } else { +#endif // HAVE_DOMAIN_NAME + ux_approval_tx_flow[step++] = &ux_approval_address_step; +#ifdef HAVE_DOMAIN_NAME + } +#endif // HAVE_DOMAIN_NAME } if (N_storage.displayNonce) { @@ -235,4 +249,4 @@ void ux_approve_tx(bool fromPlugin) { ux_approval_tx_flow[step++] = FLOW_END_STEP; ux_flow_init(0, ux_approval_tx_flow, NULL); -} \ No newline at end of file +} diff --git a/src_bagl/ui_flow_stark_unsafe_sign.c b/src_bagl/ui_flow_stark_unsafe_sign.c index 6bb8764..1c08058 100644 --- a/src_bagl/ui_flow_stark_unsafe_sign.c +++ b/src_bagl/ui_flow_stark_unsafe_sign.c @@ -18,8 +18,8 @@ UX_STEP_NOCB(ux_stark_unsafe_sign_1_step, pnn, { &C_icon_warning, - "Unsafe", - "Stark Sign", + "StarkNet", + "Blind Sign", }); UX_STEP_NOCB_INIT( @@ -36,7 +36,7 @@ UX_STEP_NOCB_INIT( bnnn_paging, stark_unsafe_sign_display_hash(), { - .title = "Hash", + .title = "Tx Hash", .text = strings.tmp.tmp }); diff --git a/src_common/ethUtils.h b/src_common/ethUtils.h index 0755b13..9722e2c 100644 --- a/src_common/ethUtils.h +++ b/src_common/ethUtils.h @@ -66,7 +66,7 @@ bool adjustDecimals(const char *src, size_t targetLength, uint8_t decimals); -static __attribute__((no_instrument_function)) inline int allzeroes(void *buf, size_t n) { +static __attribute__((no_instrument_function)) inline int allzeroes(const void *buf, size_t n) { uint8_t *p = (uint8_t *) buf; for (size_t i = 0; i < n; ++i) { if (p[i]) { diff --git a/src_features/signMessageEIP712/hash_bytes.c b/src_common/hash_bytes.c similarity index 67% rename from src_features/signMessageEIP712/hash_bytes.c rename to src_common/hash_bytes.c index 6f702a4..e6b6437 100644 --- a/src_features/signMessageEIP712/hash_bytes.c +++ b/src_common/hash_bytes.c @@ -1,5 +1,3 @@ -#ifdef HAVE_EIP712_FULL_SUPPORT - #include "hash_bytes.h" /** @@ -9,7 +7,7 @@ * @param[in] n number of bytes to hash * @param[in] hash_ctx pointer to the hashing context */ -void hash_nbytes(const uint8_t *const bytes_ptr, uint8_t n, cx_hash_t *const hash_ctx) { +void hash_nbytes(const uint8_t *bytes_ptr, size_t n, cx_hash_t *hash_ctx) { cx_hash(hash_ctx, 0, bytes_ptr, n, NULL, 0); } @@ -19,8 +17,6 @@ void hash_nbytes(const uint8_t *const bytes_ptr, uint8_t n, cx_hash_t *const has * @param[in] byte byte to hash * @param[in] hash_ctx pointer to the hashing context */ -void hash_byte(uint8_t byte, cx_hash_t *const hash_ctx) { +void hash_byte(uint8_t byte, cx_hash_t *hash_ctx) { hash_nbytes(&byte, 1, hash_ctx); } - -#endif // HAVE_EIP712_FULL_SUPPORT diff --git a/src_common/hash_bytes.h b/src_common/hash_bytes.h new file mode 100644 index 0000000..2928fab --- /dev/null +++ b/src_common/hash_bytes.h @@ -0,0 +1,10 @@ +#ifndef HASH_BYTES_H_ +#define HASH_BYTES_H_ + +#include +#include "cx.h" + +void hash_nbytes(const uint8_t *const bytes_ptr, size_t n, cx_hash_t *hash_ctx); +void hash_byte(uint8_t byte, cx_hash_t *hash_ctx); + +#endif // HASH_BYTES_H_ diff --git a/src_common/network.c b/src_common/network.c index 5bd29f7..f0dc4d1 100644 --- a/src_common/network.c +++ b/src_common/network.c @@ -8,47 +8,61 @@ #include "utils.h" // Mappping of chain ids to networks. -const network_info_t NETWORK_MAPPING[] = { - {.chain_id = 1, .name = "Ethereum", .ticker = "ETH "}, - {.chain_id = 3, .name = "Ropsten", .ticker = "ETH "}, - {.chain_id = 4, .name = "Rinkeby", .ticker = "ETH "}, - {.chain_id = 5, .name = "Goerli", .ticker = "ETH "}, - {.chain_id = 10, .name = "Optimism", .ticker = "ETH "}, - {.chain_id = 42, .name = "Kovan", .ticker = "ETH "}, - {.chain_id = 56, .name = "BSC", .ticker = "BNB "}, - {.chain_id = 100, .name = "xDai", .ticker = "xDAI "}, - {.chain_id = 137, .name = "Polygon", .ticker = "MATIC "}, - {.chain_id = 250, .name = "Fantom", .ticker = "FTM "}, - {.chain_id = 42161, .name = "Arbitrum", .ticker = "AETH "}, - {.chain_id = 42220, .name = "Celo", .ticker = "CELO "}, - {.chain_id = 43114, .name = "Avalanche", .ticker = "AVAX "}, - {.chain_id = 44787, .name = "Celo Alfajores", .ticker = "aCELO "}, - {.chain_id = 62320, .name = "Celo Baklava", .ticker = "bCELO "}, - {.chain_id = 11297108109, .name = "Palm Network", .ticker = "PALM "}, - {.chain_id = 1818, .name = "Cube", .ticker = "CUBE "}, - {.chain_id = 336, .name = "Shiden", .ticker = "SDN "}, - {.chain_id = 592, .name = "Astar", .ticker = "ASTR "}, - {.chain_id = 50, .name = "XDC", .ticker = "XDC "}, - {.chain_id = 82, .name = "Meter", .ticker = "MTR "}, - {.chain_id = 62621, .name = "Multivac", .ticker = "MTV "}, - {.chain_id = 20531812, .name = "Tecra", .ticker = "TCR "}, - {.chain_id = 20531811, .name = "TecraTestnet", .ticker = "TCR "}, - {.chain_id = 51, .name = "Apothemnetwork", .ticker = "XDC "}, - {.chain_id = 199, .name = "BTTC", .ticker = "BTT "}, - {.chain_id = 1030, .name = "Conflux", .ticker = "CFX "}, - {.chain_id = 61, .name = "Ethereum Classic", .ticker = "ETC "}, - {.chain_id = 246, .name = "EnergyWebChain", .ticker = "EWC "}, - {.chain_id = 14, .name = "Flare", .ticker = "FLR "}, - {.chain_id = 16, .name = "Flare Coston", .ticker = "FLR "}, - {.chain_id = 24, .name = "KardiaChain", .ticker = "KAI "}, - {.chain_id = 1284, .name = "Moonbeam", .ticker = "GLMR "}, - {.chain_id = 1285, .name = "Moonriver", .ticker = "MOVR "}, - {.chain_id = 66, .name = "OKXChain", .ticker = "OKT "}, - {.chain_id = 99, .name = "POA", .ticker = "POA "}, - {.chain_id = 7341, .name = "Shyft", .ticker = "SHFT "}, - {.chain_id = 19, .name = "Songbird", .ticker = "SGB "}, - {.chain_id = 73799, .name = "Volta", .ticker = "VOLTA "}, - {.chain_id = 25, .name = "Cronos", .ticker = "CRO "}}; +static const network_info_t NETWORK_MAPPING[] = { + {.chain_id = 1, .name = "Ethereum", .ticker = "ETH"}, + {.chain_id = 3, .name = "Ropsten", .ticker = "ETH"}, + {.chain_id = 4, .name = "Rinkeby", .ticker = "ETH"}, + {.chain_id = 5, .name = "Goerli", .ticker = "ETH"}, + {.chain_id = 10, .name = "Optimism", .ticker = "ETH"}, + {.chain_id = 42, .name = "Kovan", .ticker = "ETH"}, + {.chain_id = 56, .name = "BSC", .ticker = "BNB"}, + {.chain_id = 100, .name = "xDai", .ticker = "xDAI"}, + {.chain_id = 137, .name = "Polygon", .ticker = "MATIC"}, + {.chain_id = 250, .name = "Fantom", .ticker = "FTM"}, + {.chain_id = 42161, .name = "Arbitrum", .ticker = "ETH"}, + {.chain_id = 42220, .name = "Celo", .ticker = "CELO"}, + {.chain_id = 43114, .name = "Avalanche", .ticker = "AVAX"}, + {.chain_id = 44787, .name = "Celo Alfajores", .ticker = "aCELO"}, + {.chain_id = 62320, .name = "Celo Baklava", .ticker = "bCELO"}, + {.chain_id = 11297108109, .name = "Palm Network", .ticker = "PALM"}, + {.chain_id = 1818, .name = "Cube", .ticker = "CUBE"}, + {.chain_id = 336, .name = "Shiden", .ticker = "SDN"}, + {.chain_id = 592, .name = "Astar", .ticker = "ASTR"}, + {.chain_id = 50, .name = "XDC", .ticker = "XDC"}, + {.chain_id = 82, .name = "Meter", .ticker = "MTR"}, + {.chain_id = 62621, .name = "Multivac", .ticker = "MTV"}, + {.chain_id = 20531812, .name = "Tecra", .ticker = "TCR"}, + {.chain_id = 20531811, .name = "TecraTestnet", .ticker = "TCR"}, + {.chain_id = 51, .name = "Apothemnetwork", .ticker = "XDC"}, + {.chain_id = 199, .name = "BTTC", .ticker = "BTT"}, + {.chain_id = 1030, .name = "Conflux", .ticker = "CFX"}, + {.chain_id = 61, .name = "Ethereum Classic", .ticker = "ETC"}, + {.chain_id = 246, .name = "EnergyWebChain", .ticker = "EWT"}, + {.chain_id = 14, .name = "Flare", .ticker = "FLR"}, + {.chain_id = 16, .name = "Flare Coston", .ticker = "FLR"}, + {.chain_id = 24, .name = "KardiaChain", .ticker = "KAI"}, + {.chain_id = 1284, .name = "Moonbeam", .ticker = "GLMR"}, + {.chain_id = 1285, .name = "Moonriver", .ticker = "MOVR"}, + {.chain_id = 66, .name = "OKXChain", .ticker = "OKT"}, + {.chain_id = 99, .name = "POA", .ticker = "POA"}, + {.chain_id = 7341, .name = "Shyft", .ticker = "SHFT"}, + {.chain_id = 19, .name = "Songbird", .ticker = "SGB"}, + {.chain_id = 73799, .name = "Volta", .ticker = "VOLTA"}, + {.chain_id = 25, .name = "Cronos", .ticker = "CRO"}, + {.chain_id = 534354, .name = "Scroll (Pre-Alpha)", .ticker = "SCR"}, + {.chain_id = 534353, .name = "Scroll (Goerli)", .ticker = "SCR"}, + {.chain_id = 534352, .name = "Scroll", .ticker = "SCR"}, + {.chain_id = 321, .name = "KCC", .ticker = "KCS"}, + {.chain_id = 30, .name = "Rootstock", .ticker = "RBTC"}, + {.chain_id = 9001, .name = "Evmos", .ticker = "EVMOS"}, + {.chain_id = 1088, .name = "Metis Andromeda", .ticker = "METIS"}, + {.chain_id = 2222, .name = "Kava EVM", .ticker = "KAVA"}, + {.chain_id = 8217, .name = "Klaytn Cypress", .ticker = "KLAY"}, + {.chain_id = 57, .name = "Syscoin", .ticker = "SYS"}, + {.chain_id = 106, .name = "Velas EVM", .ticker = "VLX"}, + {.chain_id = 288, .name = "Boba Network", .ticker = "ETH"}, + {.chain_id = 39797, .name = "Energi", .ticker = "NRG"}, + {.chain_id = 248, .name = "Oasys", .ticker = "OAS"}}; uint64_t get_chain_id(void) { uint64_t chain_id = 0; diff --git a/src_common/network.h b/src_common/network.h index ade3152..b9ce7f3 100644 --- a/src_common/network.h +++ b/src_common/network.h @@ -5,8 +5,6 @@ #include "tokens.h" #include "shared_context.h" -#define MAX_NETWORK_TICKER_LEN 8 - typedef struct network_info_s { const char *name; const char *ticker; diff --git a/src_common/uint256.c b/src_common/uint256.c index 9da483d..7851124 100644 --- a/src_common/uint256.c +++ b/src_common/uint256.c @@ -230,18 +230,20 @@ bool tostring256(const uint256_t *const number, UPPER(LOWER(base)) = 0; LOWER(LOWER(base)) = baseParam; uint32_t offset = 0; - if ((baseParam < 2) || (baseParam > 16)) { + if ((outLength == 0) || (baseParam < 2) || (baseParam > 16)) { return false; } do { - if (offset > (outLength - 1)) { - return false; - } divmod256(&rDiv, &base, &rDiv, &rMod); out[offset++] = HEXDIGITS[(uint8_t) LOWER(LOWER(rMod))]; - } while (!zero256(&rDiv)); + } while (!zero256(&rDiv) && (offset < outLength)); - if (offset > (outLength - 1)) { + if (offset == outLength) { // destination buffer too small + if (outLength > 3) { + strlcpy(out, "...", outLength); + } else { + out[0] = '\0'; + } return false; } diff --git a/src_features/getChallenge/challenge.h b/src_features/getChallenge/challenge.h new file mode 100644 index 0000000..9fdb01d --- /dev/null +++ b/src_features/getChallenge/challenge.h @@ -0,0 +1,14 @@ +#ifdef HAVE_DOMAIN_NAME + +#ifndef CHALLENGE_H_ +#define CHALLENGE_H_ + +#include + +void roll_challenge(void); +uint32_t get_challenge(void); +void handle_get_challenge(void); + +#endif // CHALLENGE_H_ + +#endif // HAVE_DOMAIN_NAME diff --git a/src_features/getChallenge/cmd_get_challenge.c b/src_features/getChallenge/cmd_get_challenge.c new file mode 100644 index 0000000..93e52d9 --- /dev/null +++ b/src_features/getChallenge/cmd_get_challenge.c @@ -0,0 +1,38 @@ +#ifdef HAVE_DOMAIN_NAME + +#include +#include +#include +#include "apdu_constants.h" +#include "challenge.h" + +static uint32_t challenge; + +/** + * Generate a new challenge from the Random Number Generator + */ +void roll_challenge(void) { + challenge = cx_rng_u32(); +} + +/** + * Get the current challenge + * + * @return challenge + */ +uint32_t get_challenge(void) { + return challenge; +} + +/** + * Send back the current challenge + */ +void handle_get_challenge(void) { + PRINTF("New challenge -> %u\n", get_challenge()); + U4BE_ENCODE(G_io_apdu_buffer, 0, get_challenge()); + U2BE_ENCODE(G_io_apdu_buffer, 4, APDU_RESPONSE_OK); + + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 6); +} + +#endif // HAVE_DOMAIN_NAME diff --git a/src_features/provideDomainName/cmd_provide_domain_name.c b/src_features/provideDomainName/cmd_provide_domain_name.c new file mode 100644 index 0000000..0d0849f --- /dev/null +++ b/src_features/provideDomainName/cmd_provide_domain_name.c @@ -0,0 +1,710 @@ +#ifdef HAVE_DOMAIN_NAME + +#include +#include +#include +#include +#include "utils.h" // ARRAY_SIZE +#include "apdu_constants.h" +#include "domain_name.h" +#include "challenge.h" +#include "mem.h" +#include "hash_bytes.h" + +static const uint8_t DOMAIN_NAME_PUB_KEY[] = { +#ifdef HAVE_DOMAIN_NAME_TEST_KEY + 0x04, 0xb9, 0x1f, 0xbe, 0xc1, 0x73, 0xe3, 0xba, 0x4a, 0x71, 0x4e, 0x01, 0x4e, 0xbc, + 0x82, 0x7b, 0x6f, 0x89, 0x9a, 0x9f, 0xa7, 0xf4, 0xac, 0x76, 0x9c, 0xde, 0x28, 0x43, + 0x17, 0xa0, 0x0f, 0x4f, 0x65, 0x0f, 0x09, 0xf0, 0x9a, 0xa4, 0xff, 0x5a, 0x31, 0x76, + 0x02, 0x55, 0xfe, 0x5d, 0xfc, 0x81, 0x13, 0x29, 0xb3, 0xb5, 0x0b, 0xe9, 0x91, 0x94, + 0xfc, 0xa1, 0x16, 0x19, 0xe6, 0x5f, 0x2e, 0xdf, 0xea +#else + 0x04, 0x6a, 0x94, 0xe7, 0xa4, 0x2c, 0xd0, 0xc3, 0x3f, 0xdf, 0x44, 0x0c, 0x8e, 0x2a, + 0xb2, 0x54, 0x2c, 0xef, 0xbe, 0x5d, 0xb7, 0xaa, 0x0b, 0x93, 0xa9, 0xfc, 0x81, 0x4b, + 0x9a, 0xcf, 0xa7, 0x5e, 0xb4, 0xe5, 0x3d, 0x6f, 0x00, 0x25, 0x94, 0xbd, 0xb6, 0x05, + 0xd9, 0xb5, 0xbd, 0xa9, 0xfa, 0x4b, 0x4b, 0xf3, 0xa5, 0x49, 0x6f, 0xd3, 0x16, 0x4b, + 0xae, 0xf5, 0xaf, 0xcf, 0x90, 0xe8, 0x40, 0x88, 0x71 +#endif +}; + +#define P1_FIRST_CHUNK 0x01 +#define P1_FOLLOWING_CHUNK 0x00 + +#define ALGO_SECP256K1 1 + +#define SLIP_44_ETHEREUM 60 + +#define DER_LONG_FORM_FLAG 0x80 // 8th bit set +#define DER_FIRST_BYTE_VALUE_MASK 0x7f + +typedef enum { TLV_TAG, TLV_LENGTH, TLV_VALUE } e_tlv_step; + +typedef enum { + STRUCTURE_TYPE = 0x01, + STRUCTURE_VERSION = 0x02, + CHALLENGE = 0x12, + SIGNER_KEY_ID = 0x13, + SIGNER_ALGO = 0x14, + SIGNATURE = 0x15, + DOMAIN_NAME = 0x20, + COIN_TYPE = 0x21, + ADDRESS = 0x22 +} e_tlv_tag; + +typedef enum { KEY_ID_TEST = 0x00, KEY_ID_PROD = 0x03 } e_key_id; + +typedef struct { + uint8_t *buf; + uint16_t size; + uint16_t expected_size; +} s_tlv_payload; + +typedef struct { + e_tlv_tag tag; + uint8_t length; + const uint8_t *value; +} s_tlv_data; + +typedef struct { + bool valid; + char *name; + uint8_t addr[ADDRESS_LENGTH]; +} s_domain_name_info; + +typedef struct { + e_key_id key_id; + uint8_t input_sig_size; + const uint8_t *input_sig; + cx_sha256_t hash_ctx; +} s_sig_ctx; + +typedef bool(t_tlv_handler)(const s_tlv_data *data, + s_domain_name_info *domain_name_info, + s_sig_ctx *sig_ctx); + +typedef struct { + uint8_t tag; + t_tlv_handler *func; + uint8_t found; +} s_tlv_handler; + +static s_tlv_payload g_tlv_payload = {0}; +static s_domain_name_info g_domain_name_info; +char g_domain_name[DOMAIN_NAME_MAX_LENGTH + 1]; + +/** + * Send a response APDU + * + * @param[in] success whether it should use \ref APDU_RESPONSE_OK + * @param[in] off payload offset (0 if no data other than status word) + */ +static void response_to_domain_name(bool success, uint8_t off) { + uint16_t sw; + + if (success) { + sw = APDU_RESPONSE_OK; + } else { + sw = apdu_response_code; + } + U2BE_ENCODE(G_io_apdu_buffer, off, sw); + + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, off + 2); +} + +/** + * Checks if a domain name for the given chain ID and address is known + * + * Always wipes the content of \ref g_domain_name_info + * + * @param[in] chain_id given chain ID + * @param[in] addr given address + * @return whether there is or not + */ +bool has_domain_name(const uint64_t *chain_id, const uint8_t *addr) { + bool ret = false; + + if (g_domain_name_info.valid) { + // TODO: Remove once other domain name providers are supported + if ((*chain_id == ETHEREUM_MAINNET_CHAINID) && + (memcmp(addr, g_domain_name_info.addr, ADDRESS_LENGTH) == 0)) { + ret = true; + } + } + memset(&g_domain_name_info, 0, sizeof(g_domain_name_info)); + return ret; +} + +/** + * Get uint from tlv data + * + * Get an unsigned integer from variable length tlv data (up to 4 bytes) + * + * @param[in] data tlv data + * @param[out] value the returned value + * @return whether it was successful + */ +static bool get_uint_from_data(const s_tlv_data *data, uint32_t *value) { + uint8_t size_diff; + uint8_t buffer[sizeof(uint32_t)]; + + if (data->length > sizeof(buffer)) { + PRINTF("Unexpectedly long value (%u bytes) for tag 0x%x\n", data->length, data->tag); + return false; + } + size_diff = sizeof(buffer) - data->length; + memset(buffer, 0, size_diff); + memcpy(buffer + size_diff, data->value, data->length); + *value = U4BE(buffer, 0); + return true; +} + +/** + * Handler for tag \ref STRUCTURE_TYPE + * + * @param[] data the tlv data + * @param[] domain_name_info the domain name information + * @param[] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_structure_type(const s_tlv_data *data, + s_domain_name_info *domain_name_info, + s_sig_ctx *sig_ctx) { + (void) data; + (void) domain_name_info; + (void) sig_ctx; + return true; // unhandled for now +} + +/** + * Handler for tag \ref STRUCTURE_VERSION + * + * @param[] data the tlv data + * @param[] domain_name_info the domain name information + * @param[] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_structure_version(const s_tlv_data *data, + s_domain_name_info *domain_name_info, + s_sig_ctx *sig_ctx) { + (void) data; + (void) domain_name_info; + (void) sig_ctx; + return true; // unhandled for now +} + +/** + * Handler for tag \ref CHALLENGE + * + * @param[in] data the tlv data + * @param[] domain_name_info the domain name information + * @param[] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_challenge(const s_tlv_data *data, + s_domain_name_info *domain_name_info, + s_sig_ctx *sig_ctx) { + uint32_t value; + + (void) domain_name_info; + (void) sig_ctx; + return get_uint_from_data(data, &value) && (value == get_challenge()); +} + +/** + * Handler for tag \ref SIGNER_KEY_ID + * + * @param[in] data the tlv data + * @param[] domain_name_info the domain name information + * @param[out] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_sign_key_id(const s_tlv_data *data, + s_domain_name_info *domain_name_info, + s_sig_ctx *sig_ctx) { + uint32_t value; + + (void) domain_name_info; + if (!get_uint_from_data(data, &value) || (value > UINT8_MAX)) { + return false; + } + sig_ctx->key_id = value; + return true; +} + +/** + * Handler for tag \ref SIGNER_ALGO + * + * @param[in] data the tlv data + * @param[] domain_name_info the domain name information + * @param[] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_sign_algo(const s_tlv_data *data, + s_domain_name_info *domain_name_info, + s_sig_ctx *sig_ctx) { + uint32_t value; + + (void) domain_name_info; + (void) sig_ctx; + return get_uint_from_data(data, &value) && (value == ALGO_SECP256K1); +} + +/** + * Handler for tag \ref SIGNATURE + * + * @param[in] data the tlv data + * @param[] domain_name_info the domain name information + * @param[out] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_signature(const s_tlv_data *data, + s_domain_name_info *domain_name_info, + s_sig_ctx *sig_ctx) { + (void) domain_name_info; + sig_ctx->input_sig_size = data->length; + sig_ctx->input_sig = data->value; + return true; +} + +/** + * Tests if the given domain name character is valid (in our subset of allowed characters) + * + * @param[in] c given character + * @return whether the character is valid + */ +static bool is_valid_domain_character(char c) { + if (isalpha((int) c)) { + if (!islower((int) c)) { + return false; + } + } else if (!isdigit((int) c)) { + switch (c) { + case '.': + case '-': + case '_': + break; + default: + return false; + } + } + return true; +} + +/** + * Handler for tag \ref DOMAIN_NAME + * + * @param[in] data the tlv data + * @param[out] domain_name_info the domain name information + * @param[] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_domain_name(const s_tlv_data *data, + s_domain_name_info *domain_name_info, + s_sig_ctx *sig_ctx) { + (void) sig_ctx; + if (data->length > DOMAIN_NAME_MAX_LENGTH) { + PRINTF("Domain name too long! (%u)\n", data->length); + return false; + } + // TODO: Remove once other domain name providers are supported + if ((data->length < 5) || (strncmp(".eth", (char *) &data->value[data->length - 4], 4) != 0)) { + PRINTF("Unexpected TLD!\n"); + return false; + } + for (int idx = 0; idx < data->length; ++idx) { + if (!is_valid_domain_character(data->value[idx])) { + PRINTF("Domain name contains non-allowed character! (0x%x)\n", data->value[idx]); + return false; + } + domain_name_info->name[idx] = data->value[idx]; + } + domain_name_info->name[data->length] = '\0'; + return true; +} + +/** + * Handler for tag \ref COIN_TYPE + * + * @param[in] data the tlv data + * @param[] domain_name_info the domain name information + * @param[] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_coin_type(const s_tlv_data *data, + s_domain_name_info *domain_name_info, + s_sig_ctx *sig_ctx) { + uint32_t value; + + (void) domain_name_info; + (void) sig_ctx; + return get_uint_from_data(data, &value) && (value == SLIP_44_ETHEREUM); +} + +/** + * Handler for tag \ref ADDRESS + * + * @param[in] data the tlv data + * @param[out] domain_name_info the domain name information + * @param[] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_address(const s_tlv_data *data, + s_domain_name_info *domain_name_info, + s_sig_ctx *sig_ctx) { + (void) sig_ctx; + if (data->length != ADDRESS_LENGTH) { + return false; + } + memcpy(domain_name_info->addr, data->value, ADDRESS_LENGTH); + return true; +} + +/** + * Verify the signature context + * + * Verify the SHA-256 hash of the payload against the public key + * + * @param[in] sig_ctx the signature context + * @return whether it was successful + */ +static bool verify_signature(const s_sig_ctx *sig_ctx) { + uint8_t hash[INT256_LENGTH]; + cx_ecfp_public_key_t verif_key; + + cx_hash((cx_hash_t *) &sig_ctx->hash_ctx, CX_LAST, NULL, 0, hash, INT256_LENGTH); + switch (sig_ctx->key_id) { +#ifdef HAVE_DOMAIN_NAME_TEST_KEY + case KEY_ID_TEST: +#else + case KEY_ID_PROD: +#endif + cx_ecfp_init_public_key(CX_CURVE_256K1, + DOMAIN_NAME_PUB_KEY, + sizeof(DOMAIN_NAME_PUB_KEY), + &verif_key); + break; + default: + PRINTF("Error: Unknown metadata key ID %u\n", sig_ctx->key_id); + return false; + } + if (!cx_ecdsa_verify(&verif_key, + CX_LAST, + CX_SHA256, + hash, + sizeof(hash), + sig_ctx->input_sig, + sig_ctx->input_sig_size)) { + PRINTF("Domain name signature verification failed!\n"); +#ifndef HAVE_BYPASS_SIGNATURES + return false; +#endif + } + return true; +} + +/** + * Calls the proper handler for the given TLV data + * + * Checks if there is a proper handler function for the given TLV tag and then calls it + * + * @param[in] handlers list of tag / handler function pairs + * @param[in] handler_count number of handlers + * @param[in] data the TLV data + * @param[out] domain_name_info the domain name information + * @param[out] sig_ctx the signature context + * @return whether it was successful + */ +static bool handle_tlv_data(s_tlv_handler *handlers, + int handler_count, + const s_tlv_data *data, + s_domain_name_info *domain_name_info, + s_sig_ctx *sig_ctx) { + t_tlv_handler *fptr; + + // check if a handler exists for this tag + for (int idx = 0; idx < handler_count; ++idx) { + if (handlers[idx].tag == data->tag) { + handlers[idx].found += 1; + fptr = PIC(handlers[idx].func); + if (!(*fptr)(data, domain_name_info, sig_ctx)) { + PRINTF("Error while handling tag 0x%x\n", handlers[idx].tag); + return false; + } + break; + } + } + return true; +} + +/** + * Checks if all the TLV tags were found during parsing + * + * @param[in,out] handlers list of tag / handler function pairs + * @param[in] handler_count number of handlers + * @return whether all tags were found + */ +static bool check_found_tlv_tags(s_tlv_handler *handlers, int handler_count) { + // prevent missing or duplicated tags + for (int idx = 0; idx < handler_count; ++idx) { + if (handlers[idx].found != 1) { + PRINTF("Found %u occurence(s) of tag 0x%x in TLV!\n", + handlers[idx].found, + handlers[idx].tag); + return false; + } + } + return true; +} + +/** Parse DER-encoded value + * + * Parses a DER-encoded value (up to 4 bytes long) + * https://en.wikipedia.org/wiki/X.690 + * + * @param[in] payload the TLV payload + * @param[in,out] offset the payload offset + * @param[out] value the parsed value + * @return whether it was successful + */ +static bool parse_der_value(const s_tlv_payload *payload, size_t *offset, uint32_t *value) { + bool ret = false; + uint8_t byte_length; + uint8_t buf[sizeof(*value)]; + + if (value != NULL) { + if (payload->buf[*offset] & DER_LONG_FORM_FLAG) { // long form + byte_length = payload->buf[*offset] & DER_FIRST_BYTE_VALUE_MASK; + *offset += 1; + if ((*offset + byte_length) > payload->size) { + PRINTF("TLV payload too small for DER encoded value\n"); + } else { + if (byte_length > sizeof(buf)) { + PRINTF("Unexpectedly long DER-encoded value (%u bytes)\n", byte_length); + } else { + memset(buf, 0, (sizeof(buf) - byte_length)); + memcpy(buf + (sizeof(buf) - byte_length), &payload->buf[*offset], byte_length); + *value = U4BE(buf, 0); + *offset += byte_length; + ret = true; + } + } + } else { // short form + *value = payload->buf[*offset]; + *offset += 1; + ret = true; + } + } + return ret; +} + +/** + * Get DER-encoded value as an uint8 + * + * Parses the value and checks if it fits in the given \ref uint8_t value + * + * @param[in] payload the TLV payload + * @param[in,out] offset + * @param[out] value the parsed value + * @return whether it was successful + */ +static bool get_der_value_as_uint8(const s_tlv_payload *payload, size_t *offset, uint8_t *value) { + bool ret = false; + uint32_t tmp_value; + + if (value != NULL) { + if (!parse_der_value(payload, offset, &tmp_value)) { + apdu_response_code = APDU_RESPONSE_INVALID_DATA; + } else { + if (tmp_value <= UINT8_MAX) { + *value = tmp_value; + ret = true; + } else { + apdu_response_code = APDU_RESPONSE_INVALID_DATA; + PRINTF("TLV DER-encoded value larger than 8 bits\n"); + } + } + } + return ret; +} + +/** + * Parse the TLV payload + * + * Does the TLV parsing but also the SHA-256 hash of the payload. + * + * @param[in] payload the raw TLV payload + * @param[out] domain_name_info the domain name information + * @param[out] sig_ctx the signature context + * @return whether it was successful + */ +static bool parse_tlv(const s_tlv_payload *payload, + s_domain_name_info *domain_name_info, + s_sig_ctx *sig_ctx) { + s_tlv_handler handlers[] = { + {.tag = STRUCTURE_TYPE, .func = &handle_structure_type, .found = 0}, + {.tag = STRUCTURE_VERSION, .func = &handle_structure_version, .found = 0}, + {.tag = CHALLENGE, .func = &handle_challenge, .found = 0}, + {.tag = SIGNER_KEY_ID, .func = &handle_sign_key_id, .found = 0}, + {.tag = SIGNER_ALGO, .func = &handle_sign_algo, .found = 0}, + {.tag = SIGNATURE, .func = &handle_signature, .found = 0}, + {.tag = DOMAIN_NAME, .func = &handle_domain_name, .found = 0}, + {.tag = COIN_TYPE, .func = &handle_coin_type, .found = 0}, + {.tag = ADDRESS, .func = &handle_address, .found = 0}}; + e_tlv_step step = TLV_TAG; + s_tlv_data data; + size_t offset = 0; + size_t tag_start_off; + + cx_sha256_init(&sig_ctx->hash_ctx); + // handle TLV payload + while (offset < payload->size) { + switch (step) { + case TLV_TAG: + tag_start_off = offset; + if (!get_der_value_as_uint8(payload, &offset, &data.tag)) { + return false; + } + step = TLV_LENGTH; + break; + + case TLV_LENGTH: + if (!get_der_value_as_uint8(payload, &offset, &data.length)) { + return false; + } + step = TLV_VALUE; + break; + + case TLV_VALUE: + if (offset >= payload->size) { + return false; + } + data.value = &payload->buf[offset]; + if (!handle_tlv_data(handlers, + ARRAY_SIZE(handlers), + &data, + domain_name_info, + sig_ctx)) { + return false; + } + offset += data.length; + if (data.tag != SIGNATURE) { // the signature wasn't computed on itself + hash_nbytes(&payload->buf[tag_start_off], + (offset - tag_start_off), + (cx_hash_t *) &sig_ctx->hash_ctx); + } + step = TLV_TAG; + break; + + default: + return false; + } + } + return check_found_tlv_tags(handlers, ARRAY_SIZE(handlers)); +} + +/** + * Allocate and assign TLV payload + * + * @param[in] payload payload structure + * @param[in] size size of the payload + * @return whether it was successful + */ +static bool alloc_payload(s_tlv_payload *payload, uint16_t size) { + if ((payload->buf = mem_alloc(size)) == NULL) { + apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY; + return false; + } + payload->expected_size = size; + return true; +} + +/** + * Deallocate and unassign TLV payload + * + * @param[in] payload payload structure + */ +static void free_payload(s_tlv_payload *payload) { + mem_dealloc(payload->expected_size); + memset(payload, 0, sizeof(*payload)); +} + +static bool handle_first_chunk(const uint8_t **data, uint8_t *length, s_tlv_payload *payload) { + // check if no payload is already in memory + if (payload->buf != NULL) { + free_payload(payload); + apdu_response_code = APDU_RESPONSE_INVALID_P1_P2; + return false; + } + + // check if we at least get the size + if (*length < sizeof(payload->expected_size)) { + apdu_response_code = APDU_RESPONSE_INVALID_DATA; + return false; + } + if (!alloc_payload(payload, U2BE(*data, 0))) { + apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY; + return false; + } + + // skip the size so we can process it like a following chunk + *data += sizeof(payload->expected_size); + *length -= sizeof(payload->expected_size); + return true; +} + +/** + * Handle domain name APDU + * + * @param[in] p1 first APDU instruction parameter + * @param[in] p2 second APDU instruction parameter + * @param[in] data APDU payload + * @param[in] length payload size + */ +void handle_provide_domain_name(uint8_t p1, uint8_t p2, const uint8_t *data, uint8_t length) { + s_sig_ctx sig_ctx; + + (void) p2; + if (p1 == P1_FIRST_CHUNK) { + if (!handle_first_chunk(&data, &length, &g_tlv_payload)) { + return response_to_domain_name(false, 0); + } + } else { + // check if a payload is already in memory + if (g_tlv_payload.buf == NULL) { + apdu_response_code = APDU_RESPONSE_INVALID_P1_P2; + return response_to_domain_name(false, 0); + } + } + + if ((g_tlv_payload.size + length) > g_tlv_payload.expected_size) { + apdu_response_code = APDU_RESPONSE_INVALID_DATA; + free_payload(&g_tlv_payload); + PRINTF("TLV payload size mismatch!\n"); + return response_to_domain_name(false, 0); + } + // feed into tlv payload + memcpy(g_tlv_payload.buf + g_tlv_payload.size, data, length); + g_tlv_payload.size += length; + + // everything has been received + if (g_tlv_payload.size == g_tlv_payload.expected_size) { + g_domain_name_info.name = g_domain_name; + if (!parse_tlv(&g_tlv_payload, &g_domain_name_info, &sig_ctx) || + !verify_signature(&sig_ctx)) { + free_payload(&g_tlv_payload); + roll_challenge(); // prevent brute-force guesses + apdu_response_code = APDU_RESPONSE_INVALID_DATA; + return response_to_domain_name(false, 0); + } + g_domain_name_info.valid = true; + PRINTF("Registered : %s => %.*h\n", + g_domain_name_info.name, + ADDRESS_LENGTH, + g_domain_name_info.addr); + free_payload(&g_tlv_payload); + roll_challenge(); // prevent replays + } + return response_to_domain_name(true, 0); +} + +#endif // HAVE_DOMAIN_NAME diff --git a/src_features/provideDomainName/domain_name.h b/src_features/provideDomainName/domain_name.h new file mode 100644 index 0000000..502254f --- /dev/null +++ b/src_features/provideDomainName/domain_name.h @@ -0,0 +1,18 @@ +#ifdef HAVE_DOMAIN_NAME + +#ifndef DOMAIN_NAME_H_ +#define DOMAIN_NAME_H_ + +#include +#include + +#define DOMAIN_NAME_MAX_LENGTH 30 + +bool has_domain_name(const uint64_t *chain_id, const uint8_t *addr); +void handle_provide_domain_name(uint8_t p1, uint8_t p2, const uint8_t *data, uint8_t length); + +extern char g_domain_name[DOMAIN_NAME_MAX_LENGTH + 1]; + +#endif // DOMAIN_NAME_H_ + +#endif // HAVE_DOMAIN_NAME diff --git a/src_features/provideErc20TokenInformation/cmd_provideTokenInfo.c b/src_features/provideErc20TokenInformation/cmd_provideTokenInfo.c index 1340966..496c54a 100644 --- a/src_features/provideErc20TokenInformation/cmd_provideTokenInfo.c +++ b/src_features/provideErc20TokenInformation/cmd_provideTokenInfo.c @@ -42,8 +42,7 @@ void handleProvideErc20TokenInformation(uint8_t p1, } cx_hash((cx_hash_t *) &sha256, 0, workBuffer + offset, tickerLength, NULL, 0); memmove(token->ticker, workBuffer + offset, tickerLength); - token->ticker[tickerLength] = ' '; - token->ticker[tickerLength + 1] = '\0'; + token->ticker[tickerLength] = '\0'; offset += tickerLength; dataLength -= tickerLength; @@ -136,8 +135,7 @@ void handleProvideErc20TokenInformation(uint8_t p1, } cx_hash_sha256(workBuffer + offset, tickerLength + 20 + 4 + 4, hash, 32); memmove(token->ticker, workBuffer + offset, tickerLength); - token->ticker[tickerLength] = ' '; - token->ticker[tickerLength + 1] = '\0'; + token->ticker[tickerLength] = '\0'; offset += tickerLength; dataLength -= tickerLength; memmove(token->address, workBuffer + offset, 20); diff --git a/src_features/signMessage/cmd_signMessage.c b/src_features/signMessage/cmd_signMessage.c index 1779142..6280ca0 100644 --- a/src_features/signMessage/cmd_signMessage.c +++ b/src_features/signMessage/cmd_signMessage.c @@ -21,6 +21,9 @@ static const char SIGN_MAGIC[] = * @param[in] sw status word */ static void apdu_reply(uint16_t sw) { + if ((sw != APDU_RESPONSE_OK) && states.ui_started) { + ui_idle(); + } G_io_apdu_buffer[0] = (sw >> 8) & 0xff; G_io_apdu_buffer[1] = sw & 0xff; io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); @@ -90,18 +93,18 @@ static void reset_ui_buffer(void) { */ static const uint8_t *first_apdu_data(const uint8_t *data, uint8_t *length) { if (appState != APP_STATE_IDLE) { - reset_app_context(); + apdu_reply(APDU_RESPONSE_CONDITION_NOT_SATISFIED); } appState = APP_STATE_SIGNING_MESSAGE; data = parseBip32(data, length, &tmpCtx.messageSigningContext.bip32); if (data == NULL) { - apdu_reply(0x6a80); + apdu_reply(APDU_RESPONSE_INVALID_DATA); return NULL; } if (*length < sizeof(uint32_t)) { PRINTF("Invalid data\n"); - apdu_reply(0x6a80); + apdu_reply(APDU_RESPONSE_INVALID_DATA); return NULL; } @@ -140,7 +143,7 @@ static bool feed_hash(const uint8_t *const data, const uint8_t length) { PRINTF("Error: Length mismatch ! (%u > %u)!\n", length, tmpCtx.messageSigningContext.remainingLength); - apdu_reply(0x6a80); + apdu_reply(APDU_RESPONSE_INVALID_DATA); return false; } cx_hash((cx_hash_t *) &global_sha3, 0, data, length, NULL, 0); @@ -194,7 +197,7 @@ static void feed_display(void) { } if ((unprocessed_length() == 0) && (tmpCtx.messageSigningContext.remainingLength > 0)) { - apdu_reply(0x9000); + apdu_reply(APDU_RESPONSE_OK); } } @@ -222,7 +225,11 @@ bool handleSignPersonalMessage(uint8_t p1, processed_size = data - payload; } else if (p1 != P1_MORE) { PRINTF("Error: Unexpected P1 (%u)!\n", p1); - apdu_reply(0x6B00); + apdu_reply(APDU_RESPONSE_INVALID_P1_P2); + return false; + } else if (appState != APP_STATE_SIGNING_MESSAGE) { + PRINTF("Error: App not already in signing state!\n"); + apdu_reply(APDU_RESPONSE_INVALID_DATA); return false; } @@ -241,7 +248,7 @@ bool handleSignPersonalMessage(uint8_t p1, ui_191_switch_to_sign(); #endif } else { - apdu_reply(0x9000); + apdu_reply(APDU_RESPONSE_OK); } } return true; @@ -266,7 +273,7 @@ void question_switcher(void) { void skip_rest_of_message(void) { states.sign_state = STATE_191_HASH_ONLY; if (tmpCtx.messageSigningContext.remainingLength > 0) { - apdu_reply(0x9000); + apdu_reply(APDU_RESPONSE_OK); } else { ui_191_switch_to_sign(); } diff --git a/src_features/signMessageEIP712/hash_bytes.h b/src_features/signMessageEIP712/hash_bytes.h deleted file mode 100644 index 6c1d329..0000000 --- a/src_features/signMessageEIP712/hash_bytes.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef HASH_BYTES_H_ -#define HASH_BYTES_H_ - -#ifdef HAVE_EIP712_FULL_SUPPORT - -#include "cx.h" - -void hash_nbytes(const uint8_t *const bytes_ptr, uint8_t n, cx_hash_t *hash_ctx); -void hash_byte(uint8_t byte, cx_hash_t *hash_ctx); - -#endif // HAVE_EIP712_FULL_SUPPORT - -#endif // HASH_BYTES_H_ diff --git a/src_features/signMessageEIP712/ui_logic.c b/src_features/signMessageEIP712/ui_logic.c index 698e5e1..e6f7b6a 100644 --- a/src_features/signMessageEIP712/ui_logic.c +++ b/src_features/signMessageEIP712/ui_logic.c @@ -115,10 +115,12 @@ void ui_712_redraw_generic_step(void) { /** * Called to fetch the next field if they have not all been processed yet * - * @return whether there will be a next field + * Also handles the special "Review struct" screen of the verbose mode + * + * @return the next field state */ -bool ui_712_next_field(void) { - bool next = false; +e_eip712_nfs ui_712_next_field(void) { + e_eip712_nfs state = EIP712_NO_MORE_FIELD; if (ui_ctx == NULL) { apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED; @@ -126,13 +128,13 @@ bool ui_712_next_field(void) { if (ui_ctx->structs_to_review > 0) { ui_712_review_struct(path_get_nth_field_to_last(ui_ctx->structs_to_review)); ui_ctx->structs_to_review -= 1; - } - if (!ui_ctx->end_reached) { + state = EIP712_FIELD_LATER; + } else if (!ui_ctx->end_reached) { handle_eip712_return_code(true); - next = true; + state = EIP712_FIELD_INCOMING; } } - return next; + return state; } /** diff --git a/src_features/signMessageEIP712/ui_logic.h b/src_features/signMessageEIP712/ui_logic.h index 7ebb4b6..0b651e6 100644 --- a/src_features/signMessageEIP712/ui_logic.h +++ b/src_features/signMessageEIP712/ui_logic.h @@ -10,6 +10,11 @@ #define UI_712_FIELD_NAME_PROVIDED (1 << 1) typedef enum { EIP712_FILTERING_BASIC, EIP712_FILTERING_FULL } e_eip712_filtering_mode; +typedef enum { + EIP712_FIELD_LATER, + EIP712_FIELD_INCOMING, + EIP712_NO_MORE_FIELD +} e_eip712_nfs; // next field state typedef struct { bool shown; @@ -22,7 +27,7 @@ typedef struct { bool ui_712_init(void); void ui_712_deinit(void); -bool ui_712_next_field(void); +e_eip712_nfs ui_712_next_field(void); void ui_712_review_struct(const void *const struct_ptr); bool ui_712_new_field(const void *const field_ptr, const uint8_t *const data, uint8_t length); void ui_712_end_sign(void); diff --git a/src_features/signMessageEIP712_v0/cmd_signMessage712.c b/src_features/signMessageEIP712_v0/cmd_signMessage712.c index 539e8f0..deaab84 100644 --- a/src_features/signMessageEIP712_v0/cmd_signMessage712.c +++ b/src_features/signMessageEIP712_v0/cmd_signMessage712.c @@ -1,7 +1,7 @@ #include "shared_context.h" #include "apdu_constants.h" #include "utils.h" -#include "ui_flow.h" +#include "common_ui.h" #include "common_712.h" #include "ethUtils.h" @@ -33,7 +33,7 @@ void handleSignEIP712Message_v0(uint8_t p1, #ifdef NO_CONSENT io_seproxyhal_touch_signMessage_ok(NULL); #else // NO_CONSENT - ux_flow_init(0, ux_sign_712_v0_flow, NULL); + ui_sign_712_v0(); #endif // NO_CONSENT *flags |= IO_ASYNCH_REPLY; diff --git a/src_features/signTx/logic_signTx.c b/src_features/signTx/logic_signTx.c index 1c41646..199ccbb 100644 --- a/src_features/signTx/logic_signTx.c +++ b/src_features/signTx/logic_signTx.c @@ -9,6 +9,7 @@ #include "ethUtils.h" #include "common_ui.h" #include "ui_callbacks.h" +#include #define ERR_SILENT_MODE_CHECK_FAILED 0x6001 @@ -170,26 +171,6 @@ customStatus_e customProcessor(txContext_t *context) { return CUSTOM_NOT_HANDLED; } -void to_uppercase(char *str, unsigned char size) { - for (unsigned char i = 0; i < size && str[i] != 0; i++) { - str[i] = str[i] >= 'a' ? str[i] - ('a' - 'A') : str[i]; - } -} - -void compareOrCopy(char *preapproved_string, size_t size, char *parsed_string, bool silent_mode) { - if (silent_mode) { - /* ETH address are not fundamentally case sensitive but might - have some for checksum purpose, so let's get rid of these diffs */ - to_uppercase(preapproved_string, strlen(preapproved_string)); - to_uppercase(parsed_string, strlen(parsed_string)); - if (memcmp(preapproved_string, parsed_string, strlen(preapproved_string))) { - THROW(ERR_SILENT_MODE_CHECK_FAILED); - } - } else { - strlcpy(preapproved_string, parsed_string, size); - } -} - void reportFinalizeError(bool direct) { reset_app_context(); if (direct) { @@ -200,20 +181,20 @@ void reportFinalizeError(bool direct) { } } -// Convert `BEgasPrice` and `BEgasLimit` to Uint256 and then stores the multiplication of both in -// `output`. -static void computeFees(txInt256_t *BEgasPrice, txInt256_t *BEgasLimit, uint256_t *output) { - uint256_t gasPrice = {0}; - uint256_t gasLimit = {0}; - - PRINTF("Gas price %.*H\n", BEgasPrice->length, BEgasPrice->value); - PRINTF("Gas limit %.*H\n", BEgasLimit->length, BEgasLimit->value); - convertUint256BE(BEgasPrice->value, BEgasPrice->length, &gasPrice); - convertUint256BE(BEgasLimit->value, BEgasLimit->length, &gasLimit); - mul256(&gasPrice, &gasLimit, output); +static void address_to_string(uint8_t *in, + size_t in_len, + char *out, + size_t out_len, + cx_sha3_t *sha3, + uint64_t chainId) { + if (in_len != 0) { + getEthDisplayableAddress(in, out, out_len, sha3, chainId); + } else { + strlcpy(out, "Contract", out_len); + } } -static void feesToString(uint256_t *rawFee, char *displayBuffer, uint32_t displayBufferSize) { +static void raw_fee_to_string(uint256_t *rawFee, char *displayBuffer, uint32_t displayBufferSize) { const char *feeTicker = get_network_ticker(); uint8_t tickerOffset = 0; uint32_t i; @@ -240,6 +221,7 @@ static void feesToString(uint256_t *rawFee, char *displayBuffer, uint32_t displa displayBuffer[tickerOffset] = feeTicker[tickerOffset]; tickerOffset++; } + if ((uint32_t) tickerOffset < displayBufferSize) displayBuffer[tickerOffset++] = ' '; while (G_io_apdu_buffer[i]) { if ((uint32_t) (tickerOffset) + i >= displayBufferSize) { break; @@ -254,32 +236,40 @@ static void feesToString(uint256_t *rawFee, char *displayBuffer, uint32_t displa } // Compute the fees, transform it to a string, prepend a ticker to it and copy everything to -// `displayBuffer`. -void prepareAndCopyFees(txInt256_t *BEGasPrice, - txInt256_t *BEGasLimit, - char *displayBuffer, - uint32_t displayBufferSize) { +// `displayBuffer` output +static void max_transaction_fee_to_string(const txInt256_t *BEGasPrice, + const txInt256_t *BEGasLimit, + char *displayBuffer, + uint32_t displayBufferSize) { + // Use temporary variables to convert values to uint256_t + uint256_t gasPrice = {0}; + uint256_t gasLimit = {0}; + // Use temporary variable to store the result of the operation in uint256_t uint256_t rawFee = {0}; - computeFees(BEGasPrice, BEGasLimit, &rawFee); - feesToString(&rawFee, displayBuffer, displayBufferSize); + + PRINTF("Gas price %.*H\n", BEGasPrice->length, BEGasPrice->value); + PRINTF("Gas limit %.*H\n", BEGasLimit->length, BEGasLimit->value); + convertUint256BE(BEGasPrice->value, BEGasPrice->length, &gasPrice); + convertUint256BE(BEGasLimit->value, BEGasLimit->length, &gasLimit); + mul256(&gasPrice, &gasLimit, &rawFee); + raw_fee_to_string(&rawFee, displayBuffer, displayBufferSize); } -void prepareFeeDisplay() { - prepareAndCopyFees(&tmpContent.txContent.gasprice, - &tmpContent.txContent.startgas, - strings.common.maxFee, - sizeof(strings.common.maxFee)); +static void nonce_to_string(const txInt256_t *nonce, char *out, size_t out_size) { + uint256_t nonce_uint256; + convertUint256BE(nonce->value, nonce->length, &nonce_uint256); + tostring256(&nonce_uint256, 10, out, out_size); } -void prepareNetworkDisplay() { +static void get_network_as_string(char *out, size_t out_size) { const char *name = get_network_name(); if (name == NULL) { // No network name found so simply copy the chain ID as the network name. uint64_t chain_id = get_chain_id(); - u64_to_string(chain_id, strings.common.network_name, sizeof(strings.common.network_name)); + u64_to_string(chain_id, out, out_size); } else { // Network name found, simply copy it. - strlcpy(strings.common.network_name, name, sizeof(strings.common.network_name)); + strlcpy(out, name, out_size); } } @@ -304,12 +294,27 @@ static void get_public_key(uint8_t *out, uint8_t outLength) { getEthAddressFromKey(&publicKey, out, &global_sha3); } +/* Local implmentation of strncasecmp, workaround of the segfaulting base implem + * Remove once strncasecmp is fixed + */ +static int strcasecmp_workaround(const char *str1, const char *str2) { + unsigned char c1, c2; + do { + c1 = *str1++; + c2 = *str2++; + if (toupper(c1) != toupper(c2)) { + return toupper(c1) - toupper(c2); + } + } while (c1 != '\0'); + return 0; +} + void finalizeParsing(bool direct) { char displayBuffer[50]; uint8_t decimals = WEI_TO_ETHER; const char *ticker = get_network_ticker(); ethPluginFinalize_t pluginFinalize; - bool genericUI = true; + bool use_standard_UI = true; // Verify the chain if (chainConfig->chainId != ETHEREUM_MAINNET_CHAINID) { @@ -334,7 +339,6 @@ void finalizeParsing(bool direct) { // Finalize the plugin handling if (dataContext.tokenContext.pluginStatus >= ETH_PLUGIN_RESULT_SUCCESSFUL) { - genericUI = false; eth_plugin_prepare_finalize(&pluginFinalize); uint8_t msg_sender[ADDRESS_LENGTH] = {0}; @@ -380,6 +384,8 @@ void finalizeParsing(bool direct) { // Handle the right interface switch (pluginFinalize.uiType) { case ETH_UI_TYPE_GENERIC: + // Use the dedicated ETH plugin UI + use_standard_UI = false; tmpContent.txContent.dataPresent = false; // Add the number of screens + the number of additional screens to get the total // number of screens needed. @@ -387,7 +393,8 @@ void finalizeParsing(bool direct) { pluginFinalize.numScreens + pluginProvideInfo.additionalScreens; break; case ETH_UI_TYPE_AMOUNT_ADDRESS: - genericUI = true; + // Use the standard ETH UI as this plugin uses the amount/address UI + use_standard_UI = true; tmpContent.txContent.dataPresent = false; if ((pluginFinalize.amount == NULL) || (pluginFinalize.address == NULL)) { PRINTF("Incorrect amount/address set by plugin\n"); @@ -412,11 +419,15 @@ void finalizeParsing(bool direct) { return; } } - } else { - genericUI = true; } } + // User has just validated a swap but ETH received apdus about a non standard plugin / contract + if (called_from_swap && !use_standard_UI) { + PRINTF("ERR_SILENT_MODE_CHECK_FAILED, called_from_swap\n"); + THROW(ERR_SILENT_MODE_CHECK_FAILED); + } + if (tmpContent.txContent.dataPresent && !N_storage.dataAllowed) { reportFinalizeError(direct); ui_warning_contract_data(); @@ -425,66 +436,94 @@ void finalizeParsing(bool direct) { } } - // Prepare destination address to display - if (genericUI) { - if (tmpContent.txContent.destinationLength != 0) { - getEthDisplayableAddress(tmpContent.txContent.destination, - displayBuffer, - sizeof(displayBuffer), - &global_sha3, - chainConfig->chainId); - compareOrCopy(strings.common.fullAddress, - sizeof(strings.common.fullAddress), + // Prepare destination address and amount to display + if (use_standard_UI) { + // Format the address in a temporary buffer, if in swap case compare it with validated + // address, else commit it + address_to_string(tmpContent.txContent.destination, + tmpContent.txContent.destinationLength, displayBuffer, - called_from_swap); + sizeof(displayBuffer), + &global_sha3, + chainConfig->chainId); + if (called_from_swap) { + // Ensure the values are the same that the ones that have been previously validated + if (strcasecmp_workaround(strings.common.fullAddress, displayBuffer) != 0) { + PRINTF("ERR_SILENT_MODE_CHECK_FAILED, address check failed\n"); + THROW(ERR_SILENT_MODE_CHECK_FAILED); + } } else { - strcpy(strings.common.fullAddress, "Contract"); + strlcpy(strings.common.fullAddress, displayBuffer, sizeof(strings.common.fullAddress)); } - } + PRINTF("Address displayed: %s\n", strings.common.fullAddress); - // Prepare amount to display - if (genericUI) { + // Format the amount in a temporary buffer, if in swap case compare it with validated + // amount, else commit it amountToString(tmpContent.txContent.value.value, tmpContent.txContent.value.length, decimals, ticker, displayBuffer, sizeof(displayBuffer)); - compareOrCopy(strings.common.fullAmount, - sizeof(strings.common.fullAmount), - displayBuffer, - called_from_swap); + if (called_from_swap) { + // Ensure the values are the same that the ones that have been previously validated + if (strcmp(strings.common.fullAmount, displayBuffer) != 0) { + PRINTF("ERR_SILENT_MODE_CHECK_FAILED, amount check failed\n"); + THROW(ERR_SILENT_MODE_CHECK_FAILED); + } + } else { + strlcpy(strings.common.fullAmount, displayBuffer, sizeof(strings.common.fullAmount)); + } + PRINTF("Amount displayed: %s\n", strings.common.fullAmount); } - // Prepare nonce to display - uint256_t nonce; - convertUint256BE(tmpContent.txContent.nonce.value, tmpContent.txContent.nonce.length, &nonce); - tostring256(&nonce, 10, displayBuffer, sizeof(displayBuffer)); - strlcpy(strings.common.nonce, displayBuffer, sizeof(strings.common.nonce)); + // Compute the max fee in a temporary buffer, if in swap case compare it with validated max fee, + // else commit it + max_transaction_fee_to_string(&tmpContent.txContent.gasprice, + &tmpContent.txContent.startgas, + displayBuffer, + sizeof(displayBuffer)); + if (called_from_swap) { + // Ensure the values are the same that the ones that have been previously validated + if (strcmp(strings.common.maxFee, displayBuffer) != 0) { + PRINTF("ERR_SILENT_MODE_CHECK_FAILED, fees check failed\n"); + THROW(ERR_SILENT_MODE_CHECK_FAILED); + } + } else { + strlcpy(strings.common.maxFee, displayBuffer, sizeof(strings.common.maxFee)); + } - // Compute maximum fee - prepareFeeDisplay(); PRINTF("Fees displayed: %s\n", strings.common.maxFee); + // Prepare nonce to display + nonce_to_string(&tmpContent.txContent.nonce, + strings.common.nonce, + sizeof(strings.common.nonce)); + PRINTF("Nonce: %s\n", strings.common.nonce); + // Prepare chainID field - prepareNetworkDisplay(); + get_network_as_string(strings.common.network_name, sizeof(strings.common.network_name)); PRINTF("Network: %s\n", strings.common.network_name); - bool no_consent; + bool no_consent_check; - no_consent = called_from_swap; + // If called from swap, the user as already validated a standard transaction + // We have already checked the fields of this transaction above + no_consent_check = called_from_swap && use_standard_UI; #ifdef NO_CONSENT - no_consent = true; + no_consent_check = true; #endif // NO_CONSENT - if (no_consent) { + if (no_consent_check) { io_seproxyhal_touch_tx_ok(NULL); } else { - if (genericUI) { + if (use_standard_UI) { ux_approve_tx(false); } else { - plugin_ui_start(); + dataContext.tokenContext.pluginUiState = PLUGIN_UI_OUTSIDE; + dataContext.tokenContext.pluginUiCurrentItem = 0; + ux_approve_tx(true); } } } diff --git a/src_plugins/starkware/starkware_plugin.c b/src_plugins/starkware/starkware_plugin.c index 3937ada..634ea4e 100644 --- a/src_plugins/starkware/starkware_plugin.c +++ b/src_plugins/starkware/starkware_plugin.c @@ -326,10 +326,11 @@ void starkware_print_amount(uint8_t *amountData, } tostring256(&amount, 10, (char *) (G_io_apdu_buffer + 100), 100); strlcpy(destination, ticker, destinationLength); + strlcat(destination, " ", destinationLength); adjustDecimals((char *) (G_io_apdu_buffer + 100), strlen((char *) (G_io_apdu_buffer + 100)), - destination + strlen(ticker), - destinationLength - strlen(ticker), + destination + strlen(ticker) + 1, + destinationLength - strlen(ticker) - 1, decimals); } diff --git a/tests/ragger/.gitignore b/tests/ragger/.gitignore index 93526df..a3fe09d 100644 --- a/tests/ragger/.gitignore +++ b/tests/ragger/.gitignore @@ -1,2 +1,4 @@ venv/ __pycache__/ +snapshots-tmp/ +elfs/ diff --git a/tests/ragger/ethereum_client/client.py b/tests/ragger/app/client.py similarity index 56% rename from tests/ragger/ethereum_client/client.py rename to tests/ragger/app/client.py index 9622a05..9732c81 100644 --- a/tests/ragger/ethereum_client/client.py +++ b/tests/ragger/app/client.py @@ -1,17 +1,48 @@ from enum import IntEnum, auto -from typing import Iterator, Dict, List +from typing import Optional from ragger.backend import BackendInterface from ragger.utils import RAPDU -from ethereum_client.command_builder import EthereumCmdBuilder -from ethereum_client.setting import SettingType, SettingImpl -from ethereum_client.eip712 import EIP712FieldType -from ethereum_client.response_parser import EthereumRespParser +from ragger.navigator import NavInsID, NavIns, NanoNavigator +from .command_builder import EthereumCmdBuilder +from .setting import SettingType, SettingImpl +from .eip712 import EIP712FieldType +from .response_parser import EthereumRespParser +from .tlv import format_tlv import signal import time +from pathlib import Path +import keychain +import rlp -class EthereumClient: - _settings: Dict[SettingType, SettingImpl] = { +ROOT_SCREENSHOT_PATH = Path(__file__).parent.parent +WEI_IN_ETH = 1e+18 + + +class StatusWord(IntEnum): + OK = 0x9000 + ERROR_NO_INFO = 0x6a00 + INVALID_DATA = 0x6a80 + INSUFFICIENT_MEMORY = 0x6a84 + INVALID_INS = 0x6d00 + INVALID_P1_P2 = 0x6b00 + CONDITION_NOT_SATISFIED = 0x6985 + REF_DATA_NOT_FOUND = 0x6a88 + +class DOMAIN_NAME_TAG(IntEnum): + STRUCTURE_TYPE = 0x01 + STRUCTURE_VERSION = 0x02 + CHALLENGE = 0x12 + SIGNER_KEY_ID = 0x13 + SIGNER_ALGO = 0x14 + SIGNATURE = 0x15 + DOMAIN_NAME = 0x20 + COIN_TYPE = 0x21 + ADDRESS = 0x22 + + +class EthereumClient: + _settings: dict[SettingType, SettingImpl] = { SettingType.BLIND_SIGNING: SettingImpl( [ "nanos", "nanox", "nanosp" ] ), @@ -23,15 +54,20 @@ class EthereumClient: ), SettingType.VERBOSE_EIP712: SettingImpl( [ "nanox", "nanosp" ] + ), + SettingType.VERBOSE_ENS: SettingImpl( + [ "nanox", "nanosp" ] ) } _click_delay = 1/4 _eip712_filtering = False - def __init__(self, client: BackendInterface): + def __init__(self, client: BackendInterface, golden_run: bool): self._client = client + self._chain_id = 1 self._cmd_builder = EthereumCmdBuilder() self._resp_parser = EthereumRespParser() + self._nav = NanoNavigator(client, client.firmware, golden_run) signal.signal(signal.SIGALRM, self._click_signal_timeout) for setting in self._settings.values(): setting.value = False @@ -65,11 +101,11 @@ class EthereumClient: array_levels: [], key_name: str): with self._send(self._cmd_builder.eip712_send_struct_def_struct_field( - field_type, - type_name, - type_size, - array_levels, - key_name)): + field_type, + type_name, + type_size, + array_levels, + key_name)): pass return self._recv() @@ -91,8 +127,8 @@ class EthereumClient: self._disable_click_until_response() assert self._recv().status == 0x9000 - def eip712_sign_new(self, bip32): - with self._send(self._cmd_builder.eip712_sign_new(bip32)): + def eip712_sign_new(self, bip32_path: str): + with self._send(self._cmd_builder.eip712_sign_new(bip32_path)): time.sleep(0.5) # tight on timing, needed by the CI otherwise might fail sometimes if not self._settings[SettingType.VERBOSE_EIP712].value and \ not self._eip712_filtering: # need to skip the message hash @@ -104,10 +140,10 @@ class EthereumClient: return self._resp_parser.sign(resp.data) def eip712_sign_legacy(self, - bip32, + bip32_path: str, domain_hash: bytes, message_hash: bytes): - with self._send(self._cmd_builder.eip712_sign_legacy(bip32, + with self._send(self._cmd_builder.eip712_sign_legacy(bip32_path, domain_hash, message_hash)): self._client.right_click() # sign typed message screen @@ -125,7 +161,7 @@ class EthereumClient: assert resp.status == 0x9000 return self._resp_parser.sign(resp.data) - def settings_set(self, new_values: Dict[SettingType, bool]): + def settings_set(self, new_values: dict[SettingType, bool]): # Go to settings for _ in range(2): self._client.right_click() @@ -156,3 +192,61 @@ class EthereumClient: with self._send(self._cmd_builder.eip712_filtering_show_field(name, sig)): pass assert self._recv().status == 0x9000 + + def send_fund(self, + bip32_path: str, + nonce: int, + gas_price: int, + gas_limit: int, + to: bytes, + amount: float, + chain_id: int, + screenshot_collection: str = None): + data = list() + data.append(nonce) + data.append(gas_price) + data.append(gas_limit) + data.append(to) + data.append(int(amount * WEI_IN_ETH)) + data.append(bytes()) + data.append(chain_id) + data.append(bytes()) + data.append(bytes()) + + for chunk in self._cmd_builder.sign(bip32_path, rlp.encode(data)): + with self._send(chunk): + nav_ins = NavIns(NavInsID.RIGHT_CLICK) + final_ins = [ NavIns(NavInsID.BOTH_CLICK) ] + target_text = "and send" + if screenshot_collection: + self._nav.navigate_until_text_and_compare(nav_ins, + final_ins, + target_text, + ROOT_SCREENSHOT_PATH, + screenshot_collection) + else: + self._nav.navigate_until_text(nav_ins, + final_ins, + target_text) + + def get_challenge(self) -> int: + with self._send(self._cmd_builder.get_challenge()): + pass + resp = self._recv() + return self._resp_parser.challenge(resp.data) + + def provide_domain_name(self, challenge: int, name: str, addr: bytes): + payload = format_tlv(DOMAIN_NAME_TAG.STRUCTURE_TYPE, 3) # TrustedDomainName + payload += format_tlv(DOMAIN_NAME_TAG.STRUCTURE_VERSION, 1) + payload += format_tlv(DOMAIN_NAME_TAG.SIGNER_KEY_ID, 0) # test key + payload += format_tlv(DOMAIN_NAME_TAG.SIGNER_ALGO, 1) # secp256k1 + payload += format_tlv(DOMAIN_NAME_TAG.CHALLENGE, challenge) + payload += format_tlv(DOMAIN_NAME_TAG.COIN_TYPE, 0x3c) # ETH in slip-44 + payload += format_tlv(DOMAIN_NAME_TAG.DOMAIN_NAME, name) + payload += format_tlv(DOMAIN_NAME_TAG.ADDRESS, addr) + payload += format_tlv(DOMAIN_NAME_TAG.SIGNATURE, + keychain.sign_data(keychain.Key.DOMAIN_NAME, payload)) + + for chunk in self._cmd_builder.provide_domain_name(payload): + with self._send(chunk): + pass diff --git a/tests/ragger/ethereum_client/command_builder.py b/tests/ragger/app/command_builder.py similarity index 78% rename from tests/ragger/ethereum_client/command_builder.py rename to tests/ragger/app/command_builder.py index 134405f..aac10d0 100644 --- a/tests/ragger/ethereum_client/command_builder.py +++ b/tests/ragger/app/command_builder.py @@ -1,18 +1,25 @@ from enum import IntEnum, auto -from typing import Iterator -from ethereum_client.eip712 import EIP712FieldType +from typing import Iterator, Optional +from .eip712 import EIP712FieldType +from ragger.bip import pack_derivation_path +import struct -class InsType(IntEnum): +class InsType(IntEnum): + SIGN = 0x04 EIP712_SEND_STRUCT_DEF = 0x1a EIP712_SEND_STRUCT_IMPL = 0x1c EIP712_SEND_FILTERING = 0x1e EIP712_SIGN = 0x0c + GET_CHALLENGE = 0x20 + PROVIDE_DOMAIN_NAME = 0x22 -class P1Type(IntEnum): +class P1Type(IntEnum): COMPLETE_SEND = 0x00 PARTIAL_SEND = 0x01 + SIGN_FIRST_CHUNK = 0x00 + SIGN_SUBSQT_CHUNK = 0x80 -class P2Type(IntEnum): +class P2Type(IntEnum): STRUCT_NAME = 0x00 STRUCT_FIELD = 0xff ARRAY = 0x0f @@ -22,14 +29,14 @@ class P2Type(IntEnum): FILTERING_CONTRACT_NAME = 0x0f FILTERING_FIELD_NAME = 0xff -class EthereumCmdBuilder: +class EthereumCmdBuilder: _CLA: int = 0xE0 def _serialize(self, ins: InsType, p1: int, p2: int, - cdata: bytearray = bytearray()) -> bytes: + cdata: bytearray = bytes()) -> bytes: header = bytearray() header.append(self._CLA) @@ -109,27 +116,18 @@ class EthereumCmdBuilder: data_w_length[:0xff]) data_w_length = data_w_length[0xff:] - def _format_bip32(self, bip32, data: bytearray) -> bytearray: - data.append(len(bip32)) - for idx in bip32: - data.append((idx & 0xff000000) >> 24) - data.append((idx & 0x00ff0000) >> 16) - data.append((idx & 0x0000ff00) >> 8) - data.append((idx & 0x000000ff)) - return data - - def eip712_sign_new(self, bip32) -> bytes: - data = self._format_bip32(bip32, bytearray()) + def eip712_sign_new(self, bip32_path: str) -> bytes: + data = pack_derivation_path(bip32_path) return self._serialize(InsType.EIP712_SIGN, P1Type.COMPLETE_SEND, P2Type.NEW_IMPLEM, data) def eip712_sign_legacy(self, - bip32, + bip32_path: str, domain_hash: bytes, message_hash: bytes) -> bytes: - data = self._format_bip32(bip32, bytearray()) + data = pack_derivation_path(bip32_path) data += domain_hash data += message_hash return self._serialize(InsType.EIP712_SIGN, @@ -168,3 +166,30 @@ class EthereumCmdBuilder: P1Type.COMPLETE_SEND, P2Type.FILTERING_FIELD_NAME, self._eip712_filtering_send_name(name, sig)) + + def sign(self, bip32_path: str, rlp_data: bytes) -> Iterator[bytes]: + payload = pack_derivation_path(bip32_path) + payload += rlp_data + p1 = P1Type.SIGN_FIRST_CHUNK + while len(payload) > 0: + yield self._serialize(InsType.SIGN, + p1, + 0x00, + payload[:0xff]) + payload = payload[0xff:] + p1 = P1Type.SIGN_SUBSQT_CHUNK + + def get_challenge(self) -> bytes: + return self._serialize(InsType.GET_CHALLENGE, 0x00, 0x00) + + def provide_domain_name(self, tlv_payload: bytes) -> bytes: + payload = struct.pack(">H", len(tlv_payload)) + payload += tlv_payload + p1 = 1 + while len(payload) > 0: + yield self._serialize(InsType.PROVIDE_DOMAIN_NAME, + p1, + 0x00, + payload[:0xff]) + payload = payload[0xff:] + p1 = 0 diff --git a/tests/ragger/ethereum_client/eip712.py b/tests/ragger/app/eip712.py similarity index 84% rename from tests/ragger/ethereum_client/eip712.py rename to tests/ragger/app/eip712.py index 3438a1c..f719c6e 100644 --- a/tests/ragger/ethereum_client/eip712.py +++ b/tests/ragger/app/eip712.py @@ -1,6 +1,6 @@ from enum import IntEnum, auto -class EIP712FieldType(IntEnum): +class EIP712FieldType(IntEnum): CUSTOM = 0, INT = auto() UINT = auto() diff --git a/tests/ragger/ethereum_client/response_parser.py b/tests/ragger/app/response_parser.py similarity index 62% rename from tests/ragger/ethereum_client/response_parser.py rename to tests/ragger/app/response_parser.py index 681c18d..242f4cf 100644 --- a/tests/ragger/ethereum_client/response_parser.py +++ b/tests/ragger/app/response_parser.py @@ -1,4 +1,4 @@ -class EthereumRespParser: +class EthereumRespParser: def sign(self, data: bytes): assert len(data) == (1 + 32 + 32) @@ -12,3 +12,7 @@ class EthereumRespParser: data = data[32:] return v, r, s + + def challenge(self, data: bytes) -> int: + assert len(data) == 4 + return int.from_bytes(data, "big") diff --git a/tests/ragger/ethereum_client/setting.py b/tests/ragger/app/setting.py similarity index 78% rename from tests/ragger/ethereum_client/setting.py rename to tests/ragger/app/setting.py index a965fe3..7e79da7 100644 --- a/tests/ragger/ethereum_client/setting.py +++ b/tests/ragger/app/setting.py @@ -1,13 +1,14 @@ from enum import IntEnum, auto from typing import List -class SettingType(IntEnum): +class SettingType(IntEnum): BLIND_SIGNING = 0, DEBUG_DATA = auto() NONCE = auto() VERBOSE_EIP712 = auto() + VERBOSE_ENS = auto() -class SettingImpl: +class SettingImpl: devices: List[str] value: bool diff --git a/tests/ragger/app/tlv.py b/tests/ragger/app/tlv.py new file mode 100644 index 0000000..2ff4cef --- /dev/null +++ b/tests/ragger/app/tlv.py @@ -0,0 +1,25 @@ +from typing import Any + +def der_encode(value: int) -> bytes: + # max() to have minimum length of 1 + value_bytes = value.to_bytes(max(1, (value.bit_length() + 7) // 8), 'big') + if value >= 0x80: + value_bytes = (0x80 | len(value_bytes)).to_bytes(1, 'big') + value_bytes + return value_bytes + +def format_tlv(tag: int, value: Any) -> bytes: + if isinstance(value, int): + # max() to have minimum length of 1 + value = value.to_bytes(max(1, (value.bit_length() + 7) // 8), 'big') + elif isinstance(value, str): + value = value.encode() + + if not isinstance(value, bytes): + print("Unhandled TLV formatting for type : %s" % (type(value))) + return None + + tlv = bytearray() + tlv += der_encode(tag) + tlv += der_encode(len(value)) + tlv += value + return tlv diff --git a/tests/ragger/conftest.py b/tests/ragger/conftest.py index de165db..68799b2 100644 --- a/tests/ragger/conftest.py +++ b/tests/ragger/conftest.py @@ -1,68 +1,12 @@ import pytest -from pathlib import Path -from ragger import Firmware -from ragger.backend import SpeculosBackend, LedgerCommBackend, LedgerWalletBackend, BackendInterface -from ethereum_client.client import EthereumClient - -FWS = [ - Firmware("nanos", "2.1"), - Firmware("nanox", "2.0.2"), - Firmware("nanosp", "1.0.3") -] - -def pytest_addoption(parser): - parser.addoption("--backend", action="store", default="speculos") - parser.addoption("--path", action="store", default="./elfs") - parser.addoption("--model", action="store", required=True) - -# accessing the value of the "--backend" option as a fixture -@pytest.fixture -def arg_backend(pytestconfig) -> str: - return pytestconfig.getoption("backend") - -@pytest.fixture -def arg_path(pytestconfig) -> str: - return pytestconfig.getoption("path") - -@pytest.fixture -def arg_model(pytestconfig) -> str: - return pytestconfig.getoption("model") - -# Providing the firmware as a fixture -@pytest.fixture -def firmware(arg_model: str) -> Firmware: - for fw in FWS: - if fw.device == arg_model: - return fw - raise ValueError("Unknown device model \"%s\"" % (arg_model)) - -def get_elf_path(arg_path: str, firmware: Firmware) -> Path: - elf_dir = Path(arg_path).resolve() - assert elf_dir.is_dir(), ("%s is not a directory" % (arg_path)) - app = elf_dir / ("app-%s.elf" % firmware.device) - assert app.is_file(), ("Firmware %s does not exist !" % (app)) - return app - -# Depending on the "--backend" option value, a different backend is -# instantiated, and the tests will either run on Speculos or on a physical -# device depending on the backend -def create_backend(backend: str, arg_path: str, firmware: Firmware) -> BackendInterface: - if backend.lower() == "ledgercomm": - return LedgerCommBackend(firmware, interface="hid") - elif backend.lower() == "ledgerwallet": - return LedgerWalletBackend(firmware) - elif backend.lower() == "speculos": - return SpeculosBackend(get_elf_path(arg_path, firmware), firmware) - else: - raise ValueError(f"Backend '{backend}' is unknown. Valid backends are: {BACKENDS}") - -# This fixture will create and return the backend client -@pytest.fixture -def backend_client(arg_backend: str, arg_path: str, firmware: Firmware) -> BackendInterface: - with create_backend(arg_backend, arg_path, firmware) as b: - yield b +from ragger.conftest import configuration +from ragger.backend import BackendInterface +from app.client import EthereumClient # This final fixture will return the properly configured app client, to be used in tests @pytest.fixture -def app_client(backend_client: BackendInterface) -> EthereumClient: - return EthereumClient(backend_client) +def app_client(backend: BackendInterface, golden_run: bool) -> EthereumClient: + return EthereumClient(backend, golden_run) + +# Pull all features from the base ragger conftest using the overridden configuration +pytest_plugins = ("ragger.conftest.base_conftest", ) diff --git a/tests/ragger/eip712/InputData.py b/tests/ragger/eip712/InputData.py index 89e877c..02a96eb 100644 --- a/tests/ragger/eip712/InputData.py +++ b/tests/ragger/eip712/InputData.py @@ -1,14 +1,11 @@ #!/usr/bin/env python3 -import os import json import sys import re import hashlib -from ecdsa import SigningKey -from ecdsa.util import sigencode_der -from ethereum_client.client import EthereumClient, EIP712FieldType -import base64 +from app.client import EthereumClient, EIP712FieldType +import keychain # global variables app_client: EthereumClient = None @@ -24,7 +21,7 @@ sig_ctx = {} # Output = ('uint8', [2, None, 4]) | ('bool', []) def get_array_levels(typename): array_lvls = list() - regex = re.compile("(.*)\[([0-9]*)\]$") + regex = re.compile(r"(.*)\[([0-9]*)\]$") while True: result = regex.search(typename) @@ -45,7 +42,7 @@ def get_array_levels(typename): # Input = "uint64" | "string" # Output = ('uint', 64) | ('string', None) def get_typesize(typename): - regex = re.compile("^(\w+?)(\d*)$") + regex = re.compile(r"^(\w+?)(\d*)$") result = regex.search(typename) typename = result.group(1) typesize = result.group(2) @@ -254,7 +251,7 @@ def send_filtering_message_info(display_name: str, filters_count: int): for char in display_name: to_sign.append(ord(char)) - sig = sig_ctx["key"].sign_deterministic(to_sign, sigencode=sigencode_der) + sig = keychain.sign_data(keychain.Key.CAL, to_sign) app_client.eip712_filtering_message_info(display_name, filters_count, sig) # ledgerjs doesn't actually sign anything, and instead uses already pre-computed signatures @@ -272,7 +269,7 @@ def send_filtering_show_field(display_name): to_sign.append(ord(char)) for char in display_name: to_sign.append(ord(char)) - sig = sig_ctx["key"].sign_deterministic(to_sign, sigencode=sigencode_der) + sig = keychain.sign_data(keychain.Key.CAL, to_sign) app_client.eip712_filtering_show_field(display_name, sig) def read_filtering_file(domain, message, filtering_file_path): @@ -299,9 +296,6 @@ def init_signature_context(types, domain): global sig_ctx handle_optional_domain_values(domain) - env_key = os.environ["CAL_SIGNATURE_TEST_KEY"] - key = base64.b64decode(env_key).decode() # base 64 string -> decode bytes -> string - sig_ctx["key"] = SigningKey.from_pem(key, hashlib.sha256) caddr = domain["verifyingContract"] if caddr.startswith("0x"): caddr = caddr[2:] diff --git a/tests/ragger/eip712/__init__.py b/tests/ragger/eip712/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/ragger/ethereum_client/__init__.py b/tests/ragger/ethereum_client/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/ragger/keychain.py b/tests/ragger/keychain.py new file mode 100644 index 0000000..31914a4 --- /dev/null +++ b/tests/ragger/keychain.py @@ -0,0 +1,27 @@ +import os +import hashlib +from ecdsa.util import sigencode_der +from ecdsa import SigningKey +from enum import Enum, auto + +# Private key PEM files have to be named the same (lowercase) as their corresponding enum entries +# Example: for an entry in the Enum named DEV, its PEM file must be at keychain/dev.pem +class Key(Enum): + CAL = auto() + DOMAIN_NAME = auto() + +_keys: dict[Key, SigningKey] = dict() + +# Open the corresponding PEM file and load its key in the global dict +def _init_key(key: Key): + global _keys + with open("%s/keychain/%s.pem" % (os.path.dirname(__file__), key.name.lower())) as pem_file: + _keys[key] = SigningKey.from_pem(pem_file.read(), hashlib.sha256) + assert (key in _keys) and (_keys[key] != None) + +# Generate a SECP256K1 signature of the given data with the given key +def sign_data(key: Key, data: bytes) -> bytes: + global _keys + if key not in _keys: + _init_key(key) + return _keys[key].sign_deterministic(data, sigencode=sigencode_der) diff --git a/tests/ragger/keychain/cal.pem b/tests/ragger/keychain/cal.pem new file mode 100644 index 0000000..338e49f --- /dev/null +++ b/tests/ragger/keychain/cal.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQACg== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHQCAQEEIHoMkoRaNq0neb1TxRBor4WouV8PQqJf02sg4eh768LpoAcGBSuBBAAK +oUQDQgAETMqPrUlqpQQKAKfrL1zDuFN22IuhR6fXBUqZxkBWGIf+F6CW42w7Ujsk +Tz4v9/hAribE53rTvHOa9d5vLXentg== +-----END EC PRIVATE KEY----- diff --git a/tests/ragger/keychain/domain_name.pem b/tests/ragger/keychain/domain_name.pem new file mode 100644 index 0000000..726204e --- /dev/null +++ b/tests/ragger/keychain/domain_name.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQACg== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHQCAQEEIHfwyko1dEHTTQ7es7EUy2ajZo1IRRcEC8/9b+MDOzUaoAcGBSuBBAAK +oUQDQgAEuR++wXPjukpxTgFOvIJ7b4man6f0rHac3ihDF6APT2UPCfCapP9aMXYC +Vf5d/IETKbO1C+mRlPyhFhnmXy7f6g== +-----END EC PRIVATE KEY----- diff --git a/tests/ragger/requirements.txt b/tests/ragger/requirements.txt index 8836582..e408ead 100644 --- a/tests/ragger/requirements.txt +++ b/tests/ragger/requirements.txt @@ -1,6 +1,4 @@ -requests>=2.28,<3.0 -click>=8.0,<9.0 # needed by the CI as it installs an older version and breaks dependencies -protobuf==3.20.1 # To fix the protobuf dependency bug -ragger[speculos] +ragger[speculos]>=1.6.0,<1.7.0 pytest ecdsa +simple-rlp diff --git a/tests/ragger/snapshots/nanosp b/tests/ragger/snapshots/nanosp new file mode 120000 index 0000000..da13a6a --- /dev/null +++ b/tests/ragger/snapshots/nanosp @@ -0,0 +1 @@ +nanox/ \ No newline at end of file diff --git a/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00000.png b/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00000.png new file mode 100644 index 0000000..487ea10 Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00000.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00001.png b/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00001.png new file mode 100644 index 0000000..f9840e3 Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00001.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00002.png b/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00002.png new file mode 100644 index 0000000..6a5a9d6 Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00002.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00003.png b/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00003.png new file mode 100644 index 0000000..93112b3 Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00003.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00004.png b/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00004.png new file mode 100644 index 0000000..70c1b9a Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00004.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00005.png b/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00005.png new file mode 100644 index 0000000..570ce28 Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00005.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00006.png b/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00006.png new file mode 100644 index 0000000..a58590b Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_non_mainnet/00006.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_verbose_False/00000.png b/tests/ragger/snapshots/nanox/domain_name_verbose_False/00000.png new file mode 100644 index 0000000..487ea10 Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_verbose_False/00000.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_verbose_False/00001.png b/tests/ragger/snapshots/nanox/domain_name_verbose_False/00001.png new file mode 100644 index 0000000..f9840e3 Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_verbose_False/00001.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_verbose_False/00002.png b/tests/ragger/snapshots/nanox/domain_name_verbose_False/00002.png new file mode 100644 index 0000000..6af0ec5 Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_verbose_False/00002.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_verbose_False/00003.png b/tests/ragger/snapshots/nanox/domain_name_verbose_False/00003.png new file mode 100644 index 0000000..70c1b9a Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_verbose_False/00003.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_verbose_False/00004.png b/tests/ragger/snapshots/nanox/domain_name_verbose_False/00004.png new file mode 100644 index 0000000..570ce28 Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_verbose_False/00004.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_verbose_False/00005.png b/tests/ragger/snapshots/nanox/domain_name_verbose_False/00005.png new file mode 100644 index 0000000..a58590b Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_verbose_False/00005.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_verbose_True/00000.png b/tests/ragger/snapshots/nanox/domain_name_verbose_True/00000.png new file mode 100644 index 0000000..487ea10 Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_verbose_True/00000.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_verbose_True/00001.png b/tests/ragger/snapshots/nanox/domain_name_verbose_True/00001.png new file mode 100644 index 0000000..f9840e3 Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_verbose_True/00001.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_verbose_True/00002.png b/tests/ragger/snapshots/nanox/domain_name_verbose_True/00002.png new file mode 100644 index 0000000..6af0ec5 Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_verbose_True/00002.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_verbose_True/00003.png b/tests/ragger/snapshots/nanox/domain_name_verbose_True/00003.png new file mode 100644 index 0000000..6a5a9d6 Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_verbose_True/00003.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_verbose_True/00004.png b/tests/ragger/snapshots/nanox/domain_name_verbose_True/00004.png new file mode 100644 index 0000000..70c1b9a Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_verbose_True/00004.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_verbose_True/00005.png b/tests/ragger/snapshots/nanox/domain_name_verbose_True/00005.png new file mode 100644 index 0000000..570ce28 Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_verbose_True/00005.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_verbose_True/00006.png b/tests/ragger/snapshots/nanox/domain_name_verbose_True/00006.png new file mode 100644 index 0000000..a58590b Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_verbose_True/00006.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_wrong_addr/00000.png b/tests/ragger/snapshots/nanox/domain_name_wrong_addr/00000.png new file mode 100644 index 0000000..487ea10 Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_wrong_addr/00000.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_wrong_addr/00001.png b/tests/ragger/snapshots/nanox/domain_name_wrong_addr/00001.png new file mode 100644 index 0000000..f9840e3 Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_wrong_addr/00001.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_wrong_addr/00002.png b/tests/ragger/snapshots/nanox/domain_name_wrong_addr/00002.png new file mode 100644 index 0000000..639e421 Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_wrong_addr/00002.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_wrong_addr/00003.png b/tests/ragger/snapshots/nanox/domain_name_wrong_addr/00003.png new file mode 100644 index 0000000..70c1b9a Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_wrong_addr/00003.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_wrong_addr/00004.png b/tests/ragger/snapshots/nanox/domain_name_wrong_addr/00004.png new file mode 100644 index 0000000..570ce28 Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_wrong_addr/00004.png differ diff --git a/tests/ragger/snapshots/nanox/domain_name_wrong_addr/00005.png b/tests/ragger/snapshots/nanox/domain_name_wrong_addr/00005.png new file mode 100644 index 0000000..a58590b Binary files /dev/null and b/tests/ragger/snapshots/nanox/domain_name_wrong_addr/00005.png differ diff --git a/tests/ragger/test_domain_name.py b/tests/ragger/test_domain_name.py new file mode 100644 index 0000000..317f72d --- /dev/null +++ b/tests/ragger/test_domain_name.py @@ -0,0 +1,128 @@ +import pytest +from ragger.error import ExceptionRAPDU +from app.client import EthereumClient, StatusWord +from app.setting import SettingType +import struct + +# Values used across all tests +CHAIN_ID = 1 +NAME = "ledger.eth" +ADDR = bytes.fromhex("0011223344556677889900112233445566778899") +KEY_ID = 1 +ALGO_ID = 1 +BIP32_PATH = "m/44'/60'/0'/0/0" +NONCE = 21 +GAS_PRICE = 13000000000 +GAS_LIMIT = 21000 +AMOUNT = 1.22 + +@pytest.fixture(params=[False, True]) +def verbose(request) -> bool: + return request.param + +def common(app_client: EthereumClient) -> int: + if app_client._client.firmware.device == "nanos": + pytest.skip("Not supported on LNS") + return app_client.get_challenge() + + +def test_send_fund(app_client: EthereumClient, verbose: bool): + challenge = common(app_client) + + if verbose: + app_client.settings_set({ + SettingType.VERBOSE_ENS: True + }) + + app_client.provide_domain_name(challenge, NAME, ADDR) + + app_client.send_fund(BIP32_PATH, + NONCE, + GAS_PRICE, + GAS_LIMIT, + ADDR, + AMOUNT, + CHAIN_ID, + "domain_name_verbose_" + str(verbose)) + +def test_send_fund_wrong_challenge(app_client: EthereumClient): + caught = False + challenge = common(app_client) + + try: + app_client.provide_domain_name(~challenge & 0xffffffff, NAME, ADDR) + except ExceptionRAPDU as e: + assert e.status == StatusWord.INVALID_DATA + else: + assert False # An exception should have been raised + +def test_send_fund_wrong_addr(app_client: EthereumClient): + challenge = common(app_client) + + app_client.provide_domain_name(challenge, NAME, ADDR) + + addr = bytearray(ADDR) + addr.reverse() + + app_client.send_fund(BIP32_PATH, + NONCE, + GAS_PRICE, + GAS_LIMIT, + addr, + AMOUNT, + CHAIN_ID, + "domain_name_wrong_addr") + +def test_send_fund_non_mainnet(app_client: EthereumClient): + challenge = common(app_client) + + app_client.provide_domain_name(challenge, NAME, ADDR) + + app_client.send_fund(BIP32_PATH, + NONCE, + GAS_PRICE, + GAS_LIMIT, + ADDR, + AMOUNT, + 5, + "domain_name_non_mainnet") + +def test_send_fund_domain_too_long(app_client: EthereumClient): + challenge = common(app_client) + + try: + app_client.provide_domain_name(challenge, "ledger" + "0"*25 + ".eth", ADDR) + except ExceptionRAPDU as e: + assert e.status == StatusWord.INVALID_DATA + else: + assert False # An exception should have been raised + +def test_send_fund_domain_invalid_character(app_client: EthereumClient): + challenge = common(app_client) + + try: + app_client.provide_domain_name(challenge, "l\xe8dger.eth", ADDR) + except ExceptionRAPDU as e: + assert e.status == StatusWord.INVALID_DATA + else: + assert False # An exception should have been raised + +def test_send_fund_uppercase(app_client: EthereumClient): + challenge = common(app_client) + + try: + app_client.provide_domain_name(challenge, NAME.upper(), ADDR) + except ExceptionRAPDU as e: + assert e.status == StatusWord.INVALID_DATA + else: + assert False # An exception should have been raised + +def test_send_fund_domain_non_ens(app_client: EthereumClient): + challenge = common(app_client) + + try: + app_client.provide_domain_name(challenge, "ledger.hte", ADDR) + except ExceptionRAPDU as e: + assert e.status == StatusWord.INVALID_DATA + else: + assert False # An exception should have been raised diff --git a/tests/ragger/test_eip712.py b/tests/ragger/test_eip712.py index bb53ba8..29cb843 100644 --- a/tests/ragger/test_eip712.py +++ b/tests/ragger/test_eip712.py @@ -2,23 +2,17 @@ import pytest import os import fnmatch from typing import List -from ethereum_client.client import EthereumClient, SettingType +from app.client import EthereumClient, SettingType from eip712 import InputData from pathlib import Path from configparser import ConfigParser -bip32 = [ - 0x8000002c, - 0x8000003c, - 0x80000000, - 0, - 0 -] +BIP32_PATH = "m/44'/60'/0'/0/0" def input_files() -> List[str]: files = [] - for file in os.scandir("./eip712/input_files"): + for file in os.scandir("%s/eip712/input_files" % (os.path.dirname(__file__))): if fnmatch.fnmatch(file, "*-data.json"): files.append(file.path) return sorted(files) @@ -38,7 +32,7 @@ def filtering(request) -> bool: def test_eip712_legacy(app_client: EthereumClient): v, r, s = app_client.eip712_sign_legacy( - bip32, + BIP32_PATH, bytes.fromhex('6137beb405d9ff777172aa879e33edb34a1460e701802746c5ef96e741710e59'), bytes.fromhex('eb4221181ff3f1a83ea7313993ca9218496e424604ba9492bb4052c03d5c3df8') ) @@ -47,10 +41,10 @@ def test_eip712_legacy(app_client: EthereumClient): assert r == bytes.fromhex("ea66f747173762715751c889fea8722acac3fc35db2c226d37a2e58815398f64") assert s == bytes.fromhex("52d8ba9153de9255da220ffd36762c0b027701a3b5110f0a765f94b16a9dfb55") - def test_eip712_new(app_client: EthereumClient, input_file: Path, verbose: bool, filtering: bool): - print("=====> %s" % (input_file)) - if app_client._client.firmware.device != "nanos": + if app_client._client.firmware.device == "nanos": + pytest.skip("Not supported on LNS") + else: test_path = "%s/%s" % (input_file.parent, "-".join(input_file.stem.split("-")[:-1])) conf_file = "%s.ini" % (test_path) filter_file = None @@ -74,7 +68,7 @@ def test_eip712_new(app_client: EthereumClient, input_file: Path, verbose: bool, }) assert InputData.process_file(app_client, input_file, filter_file) == True - v, r, s = app_client.eip712_sign_new(bip32) + v, r, s = app_client.eip712_sign_new(BIP32_PATH) #print("[signature]") #print("v = %s" % (v.hex())) #print("r = %s" % (r.hex())) @@ -84,6 +78,4 @@ def test_eip712_new(app_client: EthereumClient, input_file: Path, verbose: bool, assert r == bytes.fromhex(config["signature"]["r"]) assert s == bytes.fromhex(config["signature"]["s"]) else: - print("No filter file found, skipping...") - else: - print("Not supported by LNS, skipping...") + pytest.skip("No filter file found") diff --git a/tests/speculos/conftest.py b/tests/speculos/conftest.py index b10f7a6..4d68fc6 100644 --- a/tests/speculos/conftest.py +++ b/tests/speculos/conftest.py @@ -9,8 +9,6 @@ from ethereum_client.ethereum_cmd import EthereumCommand SCRIPT_DIR = Path(__file__).absolute().parent API_URL = "http://127.0.0.1:5000" -VERSION = {"nanos": "2.1", "nanox": "2.0.2", "nanosp": "1.0.3"} - def pytest_addoption(parser): # nanos, nanox, nanosp @@ -27,7 +25,7 @@ def client(pytestconfig): file_path = pytestconfig.getoption("path") model = pytestconfig.getoption("model") - args = ['--log-level', 'speculos:DEBUG','--model', model, '--display', pytestconfig.getoption("display"), '--sdk', VERSION[model]] + args = ['--log-level', 'speculos:DEBUG','--model', model, '--display', pytestconfig.getoption("display")] with SpeculosClient(app=str(file_path), args=args) as client: yield client diff --git a/tests/speculos/test_configuration_cmd.py b/tests/speculos/test_configuration_cmd.py index 55c99ac..1d84839 100644 --- a/tests/speculos/test_configuration_cmd.py +++ b/tests/speculos/test_configuration_cmd.py @@ -1,10 +1,10 @@ def test_configuration(cmd): if cmd.model == "nanos": - assert cmd.get_configuration() == (14, 1, 10, 1) + assert cmd.get_configuration() == (14, 1, 10, 2) if cmd.model == "nanox": - assert cmd.get_configuration() == (14, 1, 10, 1) + assert cmd.get_configuration() == (14, 1, 10, 2) if cmd.model == "nanosp": - assert cmd.get_configuration() == (14, 1, 10, 1) + assert cmd.get_configuration() == (14, 1, 10, 2) diff --git a/tests/speculos/test_sign_cmd.py b/tests/speculos/test_sign_cmd.py index 0e0207a..588e7aa 100644 --- a/tests/speculos/test_sign_cmd.py +++ b/tests/speculos/test_sign_cmd.py @@ -619,7 +619,7 @@ def test_sign_blind_and_nonce_display(cmd): transaction = Transaction( txType=0xEB, - nonce=2**64-1, + nonce=1844674, gasPrice=0x0306dc4200, gasLimit=0x5208, to="0x5a321744667052affa8386ed49e00ef223cbffc3", @@ -699,6 +699,6 @@ def test_sign_blind_and_nonce_display(cmd): v, r, s = result - assert v == 0x25 # 37 - assert r.hex() == "737c07042022d37286216312d62163c4238536d82c5b45937ce9fbf259d11b7d" - assert s.hex() == "5604485e0cf37e465a84290eb26a18e40a430f1b0fda184c56b2c3a51ada2e6c" + assert v == 0x26 # 38 + assert r.hex() == "c8d7cd5c1711ea1af7da048d15d1a95fc9347d4622afa11f32320d73384984d1" + assert s.hex() == "3165ca0a27f565e1a87560ed3d3a144c4ac9732370428da5e6952e93659f6ac2" diff --git a/tests/zemu/snapshots/nanos_approve_dai_tokens/00002.png b/tests/zemu/snapshots/nanos_approve_dai_tokens/00002.png index d514de0..8afcc01 100644 Binary files a/tests/zemu/snapshots/nanos_approve_dai_tokens/00002.png and b/tests/zemu/snapshots/nanos_approve_dai_tokens/00002.png differ diff --git a/tests/zemu/snapshots/nanox_approve_dai_tokens/00002.png b/tests/zemu/snapshots/nanox_approve_dai_tokens/00002.png index f42c8d1..1bf7f2e 100644 Binary files a/tests/zemu/snapshots/nanox_approve_dai_tokens/00002.png and b/tests/zemu/snapshots/nanox_approve_dai_tokens/00002.png differ diff --git a/tests/zemu/snapshots/nanox_enable_blind_signing/00007.png b/tests/zemu/snapshots/nanox_enable_blind_signing/00007.png index 5b3eed9..706ef7d 100644 Binary files a/tests/zemu/snapshots/nanox_enable_blind_signing/00007.png and b/tests/zemu/snapshots/nanox_enable_blind_signing/00007.png differ diff --git a/tests/zemu/snapshots/nanox_enable_blind_signing/00008.png b/tests/zemu/snapshots/nanox_enable_blind_signing/00008.png index 61861f2..5b3eed9 100644 Binary files a/tests/zemu/snapshots/nanox_enable_blind_signing/00008.png and b/tests/zemu/snapshots/nanox_enable_blind_signing/00008.png differ diff --git a/tests/zemu/snapshots/nanox_enable_blind_signing/00009.png b/tests/zemu/snapshots/nanox_enable_blind_signing/00009.png index a58590b..61861f2 100644 Binary files a/tests/zemu/snapshots/nanox_enable_blind_signing/00009.png and b/tests/zemu/snapshots/nanox_enable_blind_signing/00009.png differ diff --git a/tests/zemu/src/blind_compound_deposit.test.js b/tests/zemu/src/blind_compound_deposit.test.js index 2f999ac..670153a 100644 --- a/tests/zemu/src/blind_compound_deposit.test.js +++ b/tests/zemu/src/blind_compound_deposit.test.js @@ -5,9 +5,9 @@ import { waitForAppScreen, zemu, nano_models } from './test.fixture'; nano_models.forEach(function(model) { test('[Nano ' + model.letter + '] Deposit ETH on compound, blind sign', zemu(model, async (sim, eth) => { let clicks; - // LNS does not have an EIP712 setting + // LNS does not have EIP712 & ENS settings if (model.letter === 'S') clicks = 3; - else clicks = 4; + else clicks = 5; // Enable blind-signing await sim.navigateAndCompareSnapshots('.', model.name + '_enable_blind_signing', [-2, 0, 0, clicks, 0]); diff --git a/tests/zemu/src/erc1155.test.js b/tests/zemu/src/erc1155.notest.js similarity index 100% rename from tests/zemu/src/erc1155.test.js rename to tests/zemu/src/erc1155.notest.js diff --git a/tests/zemu/src/erc721.test.js b/tests/zemu/src/erc721.notest.js similarity index 100% rename from tests/zemu/src/erc721.test.js rename to tests/zemu/src/erc721.notest.js diff --git a/tests/zemu/src/test.fixture.js b/tests/zemu/src/test.fixture.js index c29dbad..1a2f898 100644 --- a/tests/zemu/src/test.fixture.js +++ b/tests/zemu/src/test.fixture.js @@ -26,8 +26,8 @@ const NANOS_CLONE_ELF_PATH = Resolve("elfs/ethereum_classic_nanos.elf"); const NANOX_CLONE_ELF_PATH = Resolve("elfs/ethereum_classic_nanox.elf"); const nano_models: DeviceModel[] = [ - { name: 'nanos', letter: 'S', path: NANOS_ELF_PATH, clone_path: NANOS_CLONE_ELF_PATH }, - { name: 'nanox', letter: 'X', path: NANOX_ELF_PATH, clone_path: NANOX_CLONE_ELF_PATH } + { name: 'nanos', letter: 'S', path: NANOS_ELF_PATH, clone_path: NANOS_CLONE_ELF_PATH }/*, + { name: 'nanox', letter: 'X', path: NANOX_ELF_PATH, clone_path: NANOX_CLONE_ELF_PATH }*/ ]; const TIMEOUT = 1000000; diff --git a/tools/build_sdk.py b/tools/build_sdk.py index aab3a6e..a808e4a 100755 --- a/tools/build_sdk.py +++ b/tools/build_sdk.py @@ -164,7 +164,7 @@ if __name__ == "__main__": "typedef union": ["extraInfo_t"], "__attribute__((no_instrument_function)) inline": ["int allzeroes"], "const": ["HEXDIGITS"], - "fn": ["void getEthAddressStringFromBinary", "void getEthAddressFromKey", "void getEthDisplayableAddress", "bool adjustDecimals", "bool uint256_to_decimal", "void amountToString", "void u64_to_string", "void copy_address", "void copy_parameter"] + "fn": ["void getEthAddressStringFromBinary", "void getEthAddressFromKey", "void getEthDisplayableAddress", "bool adjustDecimals", "bool uint256_to_decimal", "void amountToString", "void u64_to_string", "void copy_address", "void copy_parameter", "bool U2BE_from_parameter", "bool U4BE_from_parameter"] } merge_headers(headers_to_merge, nodes_to_extract)