diff --git a/.github/workflows/check_version.yml b/.github/workflows/check_version.yml index 85e7bb4..b0c682f 100644 --- a/.github/workflows/check_version.yml +++ b/.github/workflows/check_version.yml @@ -1,9 +1,11 @@ -name: "Verify PRs to master" +name: Verify PRs to master on: + workflow_dispatch: pull_request: branches: - master + - dev jobs: configure: @@ -12,33 +14,24 @@ jobs: uid_gid: ${{ steps.get-user.outputs.uid_gid }} steps: - id: get-user - run: echo "::set-output name=uid_gid::$(id -u):$(id -g)" + run: echo "uid_gid=$(id -u):$(id -g)" >> $GITHUB_OUTPUT get_version: needs: configure runs-on: ubuntu-latest container: - image: zondax/builder-bolos:latest + image: zondax/ledger-app-builder:latest options: --user ${{ needs.configure.outputs.uid_gid }} - env: - BOLOS_ENV: /opt/bolos - HOME: /home/zondax_circle outputs: version: ${{ steps.store-version.outputs.version }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: true - - - name: Invoke `version` - shell: bash -l {0} - env: - BOLOS_SDK: ${{ github.workspace }}/deps/nanos-secure-sdk - run: make version - + - run: make version - id: store-version - run: echo ::set-output name=version::$(cat ./app/app.version) + run: echo "version=$(cat ./app/app.version)" >> $GITHUB_OUTPUT check_app_version: needs: get_version @@ -55,4 +48,4 @@ jobs: - name: Tag exists if: ${{ steps.checkTag.outputs.exists == 'true' }} - run: exit 1 \ No newline at end of file + run: exit 1 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..e524544 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,37 @@ +name: "CodeQL" + +on: + workflow_dispatch: + push: + pull_request: + branches: + - master + - dev + +jobs: + analyse: + name: Analyse + strategy: + matrix: + sdk: ["$NANOS_SDK", "$NANOX_SDK", "$NANOSP_SDK", "$STAX_SDK"] + runs-on: ubuntu-latest + container: + image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-legacy:latest + + steps: + - name: Clone + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: cpp + queries: security-and-quality + + - name: Build + run: | + make -j BOLOS_SDK=${{ matrix.sdk }} + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/guidelines_enforcer.yml b/.github/workflows/guidelines_enforcer.yml new file mode 100644 index 0000000..831a296 --- /dev/null +++ b/.github/workflows/guidelines_enforcer.yml @@ -0,0 +1,24 @@ +name: Ensure compliance with Ledger guidelines + +# This workflow is mandatory in all applications +# It calls a reusable workflow guidelines_enforcer developed by Ledger's internal developer team. +# The successful completion of the reusable workflow is a mandatory step for an app to be available on the Ledger +# application store. +# +# More information on the guidelines can be found in the repository: +# LedgerHQ/ledger-app-workflows/ + +on: + workflow_dispatch: + push: + branches: + - master + - dev + pull_request: + +jobs: + guidelines_enforcer: + name: Call Ledger guidelines_enforcer + uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_guidelines_enforcer.yml@v1 + with: + relative_app_directory: app diff --git a/.github/workflows/ledger.yml b/.github/workflows/ledger.yml deleted file mode 100644 index 22428da..0000000 --- a/.github/workflows/ledger.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: CI - -on: - # Triggers the workflow on push or pull request events but only for the main branch - push: - branches: [ master ] - pull_request: - branches: [ master ] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -jobs: - scan-build: - name: Clang Static Analyzer - runs-on: ubuntu-latest - - container: - image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest - - steps: - - uses: actions/checkout@v2 - - - name: Build with Clang Static Analyzer - run: | - 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 - if: failure() - with: - name: scan-build - path: scan-build diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7704fb9..0306555 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,34 +8,32 @@ jobs: uid_gid: ${{ steps.get-user.outputs.uid_gid }} steps: - id: get-user - run: echo "::set-output name=uid_gid::$(id -u):$(id -g)" + run: echo "uid_gid=$(id -u):$(id -g)" >> $GITHUB_OUTPUT build: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: true - name: Install deps run: | sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 10 make deps - pip install conan==1.60.2 - source ~/.profile - - run: cmake -DCMAKE_BUILD_TYPE=Debug . && make - - run: GTEST_COLOR=1 ASAN_OPTIONS=detect_leaks=0 ctest -VV + - run: make cpp_test build_only_rust: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: true - - name: Install deps - run: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + - name: Install rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable - name: rustfmt run: | cd ./app/rust @@ -55,27 +53,37 @@ jobs: needs: configure runs-on: ubuntu-latest container: - image: zondax/builder-bolos:latest + image: zondax/ledger-app-builder:latest options: --user ${{ needs.configure.outputs.uid_gid }} - env: - BOLOS_SDK: ${{ github.workspace }}/deps/nanos-secure-sdk - BOLOS_ENV: /opt/bolos - HOME: /home/zondax_circle + env: + BOLOS_SDK: /opt/nanos-secure-sdk + outputs: + size: ${{steps.build.outputs.size}} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: true - name: Build Standard app + id: build shell: bash -l {0} run: | - source $HOME/.cargo/env - make + SUBSTRATE_PARSER_FULL=1 make + echo "size=$(python3 deps/ledger-zxlib/scripts/getSize.py s)" >> $GITHUB_OUTPUT - name: Build SR25519 app shell: bash -l {0} run: | - source $HOME/.cargo/env - SUPPORT_SR25519=1 make + SUBSTRATE_PARSER_FULL=1 SUPPORT_SR25519=1 make + + size_nano_s: + needs: build_ledger + runs-on: ubuntu-latest + env: + NANOS_LIMIT_SIZE: 136 + steps: + - run: | + echo "LNS app size: ${{needs.build_ledger.outputs.size}} KiB" + [ ${{needs.build_ledger.outputs.size}} -le $NANOS_LIMIT_SIZE ] test_zemu: runs-on: ubuntu-latest @@ -86,94 +94,48 @@ jobs: echo $HOME echo $DISPLAY - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: true - run: sudo apt-get update -y && sudo apt-get install -y libusb-1.0.0 libudev-dev - name: Install rust - run: | - sudo apt-get update - sudo apt-get install -y cmake binutils-dev libcurl4-openssl-dev libiberty-dev libelf-dev libdw-dev - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --no-modify-path --default-toolchain none -y; - - name: Install node - uses: actions/setup-node@v2 + uses: actions-rs/toolchain@v1 with: - node-version: '14.17.0' - - name: Install yarn - run: | - npm install -g yarn - - name: Build Ledger app - run: | - make SUBSTRATE_PARSER_FULL=1 - - name: Build/Install build js deps - run: | - export PATH=~/.cargo/bin:$PATH - make zemu_install - - name: Run zemu tests - run: | - export PATH=~/.cargo/bin:$PATH - make zemu_test - - test_zemu_sr25519: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - submodules: true - - run: sudo apt-get update -y && sudo apt-get install -y libusb-1.0.0 libudev-dev - - name: Install rust - run: | - sudo apt-get update - sudo apt-get install -y cmake binutils-dev libcurl4-openssl-dev libiberty-dev libelf-dev libdw-dev - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --no-modify-path --default-toolchain none -y; + toolchain: stable - name: Install node - uses: actions/setup-node@v2 - with: - node-version: '14.17.0' + uses: actions/setup-node@v3 - name: Install yarn run: | npm install -g yarn - - name: Build Ledger app - run: | - make clean_build && SUBSTRATE_PARSER_FULL=1 SUPPORT_SR25519=1 make buildS - - name: Build/Install build js deps + - name: Build and run zemu tests run: | - export PATH=~/.cargo/bin:$PATH - make zemu_install - - name: Run zemu tests for sr25519 - run: | - export PATH=~/.cargo/bin:$PATH - cd tests_zemu && yarn testSR25519 + make test_all - build_package: - needs: [ configure, build, build_ledger, test_zemu, test_zemu_sr25519 ] + build_package_nanos: + needs: [configure, build, build_ledger, test_zemu] if: ${{ github.ref == 'refs/heads/master' }} runs-on: ubuntu-latest container: - image: zondax/builder-bolos:latest + image: zondax/ledger-app-builder:latest options: --user ${{ needs.configure.outputs.uid_gid }} - env: - BOLOS_SDK: ${{ github.workspace }}/deps/nanos-secure-sdk - BOLOS_ENV: /opt/bolos - HOME: /home/zondax_circle + env: + BOLOS_SDK: /opt/nanos-secure-sdk steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: true - name: Install deps run: pip install ledgerblue - - name: Build NanoS light + - name: Build NanoS shell: bash -l {0} run: | - source $HOME/.cargo/env make SUBSTRATE_PARSER_FULL=0 mv ./app/pkg/installer_s.sh ./app/pkg/installer_nanos.sh - name: Set tag - id: nanos - run: echo ::set-output name=tag_name::$(./app/pkg/installer_nanos.sh version) + id: nanos_light + run: echo "tag_name=$(./app/pkg/installer_nanos.sh version)" >> $GITHUB_OUTPUT - name: Create or Update Release (1) id: create_release_0 uses: softprops/action-gh-release@v1 @@ -181,21 +143,37 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token with: files: ./app/pkg/installer_nanos.sh - tag_name: ${{ steps.nanos.outputs.tag_name }} + tag_name: ${{ steps.nanos_light.outputs.tag_name }} draft: false prerelease: false + build_package_nanos_xl: + needs: [configure, build, build_ledger, test_zemu] + if: ${{ github.ref == 'refs/heads/master' }} + runs-on: ubuntu-latest + container: + image: zondax/ledger-app-builder:latest + options: --user ${{ needs.configure.outputs.uid_gid }} + env: + BOLOS_SDK: /opt/nanos-secure-sdk + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: true + - name: Install deps + run: pip install ledgerblue + - name: Build NanoS XL shell: bash -l {0} run: | - source $HOME/.cargo/env make SUBSTRATE_PARSER_FULL=1 mv ./app/pkg/installer_s.sh ./app/pkg/installer_nanos_xl.sh - name: Set tag id: nanos_xl - run: echo ::set-output name=tag_name::$(./app/pkg/installer_nanos_xl.sh version) - - name: Create or Update Release (1) - id: create_release_1 + run: echo "tag_name=$(./app/pkg/installer_nanos_xl.sh version)" >> $GITHUB_OUTPUT + - name: Update Release + id: update_release_1 uses: softprops/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token @@ -205,19 +183,33 @@ jobs: draft: false prerelease: false + build_package_nanosp: + needs: [configure, build, build_ledger, test_zemu] + if: ${{ github.ref == 'refs/heads/master' }} + runs-on: ubuntu-latest + container: + image: zondax/ledger-app-builder:latest + options: --user ${{ needs.configure.outputs.uid_gid }} + env: + BOLOS_SDK: /opt/nanosplus-secure-sdk + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: true + - name: Install deps + run: pip install ledgerblue + - name: Build NanoSP - env: - BOLOS_SDK: ${{ github.workspace }}/deps/nanosplus-secure-sdk shell: bash -l {0} run: | - source $HOME/.cargo/env make SUBSTRATE_PARSER_FULL=1 mv ./app/pkg/installer_s2.sh ./app/pkg/installer_nanos_plus.sh - name: Set tag id: nanosp - run: echo ::set-output name=tag_name::$(./app/pkg/installer_nanos_plus.sh version) - - name: Create or Update Release (1) - id: create_release_2 + run: echo "tag_name=$(./app/pkg/installer_nanos_plus.sh version)" >> $GITHUB_OUTPUT + - name: Update Release + id: update_release_2 uses: softprops/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token @@ -226,3 +218,37 @@ jobs: tag_name: ${{ steps.nanosp.outputs.tag_name }} draft: false prerelease: false + + build_package_stax: + needs: [configure, build, build_ledger, test_zemu] + if: ${{ github.ref == 'refs/heads/master' }} + runs-on: ubuntu-latest + container: + image: zondax/ledger-app-builder:latest + options: --user ${{ needs.configure.outputs.uid_gid }} + env: + BOLOS_SDK: /opt/stax-secure-sdk + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: true + - name: Install deps + run: pip install ledgerblue + + - name: Build Stax + shell: bash -l {0} + run: make SUBSTRATE_PARSER_FULL=1 + - name: Set tag + id: stax + run: echo "tag_name=$(./app/pkg/installer_stax.sh version)" >> $GITHUB_OUTPUT + - name: Update Release + id: update_release_2 + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + with: + files: ./app/pkg/installer_stax.sh + tag_name: ${{ steps.stax.outputs.tag_name }} + draft: false + prerelease: false diff --git a/.gitignore b/.gitignore index 69423b4..8954c83 100644 --- a/.gitignore +++ b/.gitignore @@ -68,12 +68,12 @@ fuzz/corpora !build/.gitkeep build/* +app/build cmake-build-debug tests_zemu/snapshots-tmp tests_zemu/yarn.lock tests_tools/target - fuzz-*.log /scan-build diff --git a/.gitmodules b/.gitmodules index b88a7f6..11db38e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,7 +9,13 @@ url = https://github.com/bilke/cmake-modules.git [submodule "deps/nanox-secure-sdk"] path = deps/nanox-secure-sdk - url = https://github.com/LedgerHQ/nanox-secure-sdk.git + url = https://github.com/LedgerHQ/ledger-secure-sdk.git [submodule "deps/nanosplus-secure-sdk"] path = deps/nanosplus-secure-sdk - url = https://github.com/LedgerHQ/nanosplus-secure-sdk + url = https://github.com/LedgerHQ/ledger-secure-sdk.git +[submodule "deps/ledger-zxlib"] + path = deps/ledger-zxlib + url = https://github.com/zondax/ledger-zxlib +[submodule "deps/stax-secure-sdk"] + path = deps/stax-secure-sdk + url = https://github.com/LedgerHQ/ledger-secure-sdk.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 78475a3..2ff11ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,9 +115,10 @@ file(GLOB_RECURSE LIB_SRC #### ${CMAKE_CURRENT_SOURCE_DIR}/app/src/crypto_helper.c ${CMAKE_CURRENT_SOURCE_DIR}/app/src/parser.c + ${CMAKE_CURRENT_SOURCE_DIR}/app/src/parser_impl_common.c ${CMAKE_CURRENT_SOURCE_DIR}/app/src/parser_impl.c ${CMAKE_CURRENT_SOURCE_DIR}/app/src/parser_txdef.c - ${CMAKE_CURRENT_SOURCE_DIR}/app/src/substrate*.c + ${CMAKE_CURRENT_SOURCE_DIR}/app/src/substrate/substrate*.c ) add_library(app_lib STATIC ${LIB_SRC}) @@ -127,6 +128,7 @@ target_include_directories(app_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/app/src ${CMAKE_CURRENT_SOURCE_DIR}/app/src/lib ${CMAKE_CURRENT_SOURCE_DIR}/app/src/common + ${CMAKE_CURRENT_SOURCE_DIR}/app/src/substrate ) ############################################################## diff --git a/Makefile b/Makefile index d8f8178..eb480bc 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ #******************************************************************************* -#* (c) 2019 Zondax GmbH +#* (c) 2019 - 2023 Zondax AG #* #* Licensed under the Apache License, Version 2.0 (the "License"); #* you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ ifeq ($(BOLOS_SDK),) # In this case, there is not predefined SDK and we run dockerized # When not using the SDK, we override and build the XL complete app +ZXLIB_COMPILE_STAX ?= 1 SUBSTRATE_PARSER_FULL ?= 1 include $(CURDIR)/deps/ledger-zxlib/dockerized_build.mk @@ -46,8 +47,6 @@ zemu_install: tests_tools_build test_all: make zemu_install - # test sr25519 - make clean_build && SUBSTRATE_PARSER_FULL=1 SUPPORT_SR25519=1 make buildS - cd tests_zemu && yarn testSR25519 - make clean_build && SUBSTRATE_PARSER_FULL=1 make + SUBSTRATE_PARSER_FULL=1 make + SUBSTRATE_PARSER_FULL=1 SUPPORT_SR25519=1 make buildS make zemu_test diff --git a/app/.gitignore b/app/.gitignore index 66d5371..338ec87 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -48,5 +48,4 @@ bin/app.sha256 bin/app.apdu \output/app.* -pkg/zxtool.sh pkg/installer_*.sh diff --git a/app/Makefile b/app/Makefile index e4eecbe..1d839f1 100755 --- a/app/Makefile +++ b/app/Makefile @@ -1,6 +1,6 @@ #******************************************************************************* # Ledger App -# (c) 2019 - 2022 Zondax GmbH +# (c) 2019 - 2023 Zondax AG # (c) 2017 Ledger # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,33 +22,18 @@ endif MY_DIR := $(dir $(lastword $(MAKEFILE_LIST))) -all: bin/app.elf - @echo "#!/usr/bin/env bash" > $(OUTPUT_INSTALLER) - @echo "APPNAME=\"${APPNAME}\"" >> $(OUTPUT_INSTALLER) - @echo "APPVERSION=\"${APPVERSION}\"" >> $(OUTPUT_INSTALLER) - @echo "APPPATH=\""${APPPATH}"\"" >> $(OUTPUT_INSTALLER) - @echo "LOAD_PARAMS=\"${COMMON_LOAD_PARAMS}\"" >> $(OUTPUT_INSTALLER) - @echo "DELETE_PARAMS=\"${COMMON_DELETE_PARAMS}\"" >> $(OUTPUT_INSTALLER) - @echo "APPHEX=\"" >> $(OUTPUT_INSTALLER) - @cat $(CURDIR)/bin/app.hex >> $(OUTPUT_INSTALLER) - @echo "\"" >> $(OUTPUT_INSTALLER) - @cat $(CURDIR)/../deps/ledger-zxlib/scripts/template.sh >> $(OUTPUT_INSTALLER) - @chmod +x $(OUTPUT_INSTALLER) - @cp $(CURDIR)/bin/* $(CURDIR)/output - @cp $(CURDIR)/output/app.elf ${OUTPUT_ELF} - @rm $(CURDIR)/output/app.elf +include $(CURDIR)/../deps/ledger-zxlib/makefiles/Makefile.installer_script include $(BOLOS_SDK)/Makefile.defines +DEFINES += HAVE_SWAP +HAVE_SWAP = 1 + DEFINES += APP_SECRET_MODE_ENABLED $(info ************ TARGET_NAME = [$(TARGET_NAME)]) -ifeq ($(APP_TESTING),1) -DEFINES += APP_TESTING -DEFINES += ZEMU_LOGGING -$(info ************ LOGGING ENABLED ************) -endif +include $(CURDIR)/../deps/ledger-zxlib/makefiles/Makefile.app_testing ifeq ($(SUPPORT_SR25519),1) DEFINES += SUPPORT_SR25519 @@ -76,50 +61,28 @@ ifneq ($(TARGET_NAME),TARGET_NANOS) DEFINES += SUBSTRATE_PARSER_FULL endif APPNAME = "Edgeware" -APPPATH = "44'/523'" +APPPATH = "44'/523'" --path "44'/434'" else ifeq ($(COIN),EDG_XL) # XL app configuration DEFINES += APP_STANDARD SUBSTRATE_PARSER_FULL APPNAME = "Edgeware XL" -APPPATH = "44'/523'" +APPPATH = "44'/523'" --path "44'/434'" else define error_message - COIN value not supported: [$(COIN)] - endef $(error "$(error_message)") endif -APP_LOAD_PARAMS = --curve ed25519 --delete $(COMMON_LOAD_PARAMS) --path $(APPPATH) +APP_LOAD_PARAMS = --curve ed25519 $(COMMON_LOAD_PARAMS) --path $(APPPATH) -ifeq ($(TARGET_NAME),TARGET_NANOS) -APP_LOAD_PARAMS += --appFlags 0x000 -APP_STACK_SIZE:=3216 -ICONNAME:=$(CURDIR)/nanos_icon.gif -OUTPUT_ELF ?= $(CURDIR)/output/app_s.elf -OUTPUT_INSTALLER := $(CURDIR)/pkg/installer_s.sh -endif +NANOS_STACK_SIZE := 3136 -ifeq ($(TARGET_NAME),TARGET_NANOX) -APP_LOAD_PARAMS += --appFlags 0x200 -SCRIPT_LD:=$(CURDIR)/script_x.ld -ICONNAME:=$(CURDIR)/nanox_icon.gif -OUTPUT_ELF ?= $(CURDIR)/output/app_x.elf -OUTPUT_INSTALLER:= $(CURDIR)/pkg/installer_x.sh -endif - -ifeq ($(TARGET_NAME),TARGET_NANOS2) -APP_LOAD_PARAMS += --appFlags 0x000 -SCRIPT_LD:=$(CURDIR)/script_s2.ld -ICONNAME:=$(CURDIR)/nanox_icon.gif -OUTPUT_ELF ?= $(CURDIR)/output/app_s2.elf -OUTPUT_INSTALLER:= $(CURDIR)/pkg/installer_s2.sh -endif +include $(CURDIR)/../deps/ledger-zxlib/makefiles/Makefile.devices $(info TARGET_NAME = [$(TARGET_NAME)]) $(info ICONNAME = [$(ICONNAME)]) @@ -128,122 +91,26 @@ ifndef ICONNAME $(error ICONNAME is not set) endif -############ -# Platform - -DEFINES += PRINTF\(...\)= - -APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) -DEFINES += APPVERSION=\"$(APPVERSION)\" - -DEFINES += OS_IO_SEPROXYHAL -DEFINES += HAVE_BAGL HAVE_SPRINTF -DEFINES += HAVE_IO_USB HAVE_L4_USBLIB IO_USB_MAX_ENDPOINTS=7 IO_HID_EP_LENGTH=64 HAVE_USB_APDU - -DEFINES += LEDGER_MAJOR_VERSION=$(APPVERSION_M) LEDGER_MINOR_VERSION=$(APPVERSION_N) LEDGER_PATCH_VERSION=$(APPVERSION_P) - -DEFINES += USB_SEGMENT_SIZE=64 -DEFINES += HAVE_BOLOS_APP_STACK_CANARY - -DEFINES += HAVE_WEBUSB WEBUSB_URL_SIZE_B=0 WEBUSB_URL="" - -ifeq ($(TARGET_NAME),TARGET_NANOS) -DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=128 -else -# Assume Nano X/S+ -DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=300 - -DEFINES += HAVE_GLO096 -DEFINES += HAVE_BAGL BAGL_WIDTH=128 BAGL_HEIGHT=64 -DEFINES += HAVE_BAGL_ELLIPSIS # long label truncation feature -DEFINES += HAVE_BAGL_FONT_OPEN_SANS_REGULAR_11PX -DEFINES += HAVE_BAGL_FONT_OPEN_SANS_EXTRABOLD_11PX -DEFINES += HAVE_BAGL_FONT_OPEN_SANS_LIGHT_16PX - -DEFINES += HAVE_UX_FLOW -endif - -ifeq ($(TARGET_NAME),TARGET_NANOX) -# X specific -DEFINES += HAVE_BLE -DEFINES += HAVE_BLE_APDU BLE_COMMAND_TIMEOUT_MS=2000 - -SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl -endif - -# App specific - -#Feature temporarily disabled -DEFINES += LEDGER_SPECIFIC - -# Compiler, assembler, and linker - -ifneq ($(BOLOS_ENV),) -$(info BOLOS_ENV is $(BOLOS_ENV)) -CLANGPATH := /usr/bin/ -GCCPATH := /usr/bin/ -else -$(info BOLOS_ENV is not set: falling back to CLANGPATH and GCCPATH) -endif - -ifeq ($(CLANGPATH),) -$(info CLANGPATH is not set: clang will be used from PATH) -endif +include $(CURDIR)/../deps/ledger-zxlib/makefiles/Makefile.platform +LDFLAGS += -z muldefs -ifeq ($(GCCPATH),) -$(info GCCPATH is not set: arm-none-eabi-* will be used from PATH) +ifeq ($(SUPPORT_SR25519),1) +APP_CUSTOM_LINK_DEPENDENCIES = rust +LDLIBS += -Lrust/target/thumbv6m-none-eabi/release -lrslib +APP_SOURCE_PATH += $(CURDIR)/rust/include endif -######################### - -CC := $(CLANGPATH)clang -CFLAGS += -O3 -Os -Wno-unknown-pragmas -Wno-unused-parameter -Wvla - -AS := $(GCCPATH)arm-none-eabi-gcc -AFLAGS += - -LD := $(GCCPATH)arm-none-eabi-gcc -LDFLAGS += -O3 -Os -LDFLAGS += -z muldefs -LDLIBS += -lm -lgcc -lc -LDLIBS += -Lrust/target/thumbv6m-none-eabi/release -lrslib - -########################## -GLYPH_SRC_DIR = glyphs -INCLUDES_PATH += $(MY_DIR)/glyphs -include $(BOLOS_SDK)/Makefile.glyphs - -APP_SOURCE_PATH += $(MY_DIR)/src -APP_SOURCE_PATH += $(MY_DIR)/glyphs -APP_SOURCE_PATH += $(MY_DIR)/rust/include -APP_SOURCE_PATH += $(MY_DIR)/../deps/ledger-zxlib/include -APP_SOURCE_PATH += $(MY_DIR)/../deps/ledger-zxlib/src -APP_SOURCE_PATH += $(MY_DIR)/../deps/ledger-zxlib/app/common - -SDK_SOURCE_PATH += lib_stusb lib_stusb_impl -SDK_SOURCE_PATH += lib_ux - .PHONY: rust rust: cd rust && CARGO_HOME="$(CURDIR)/rust/.cargo" cargo build --target thumbv6m-none-eabi --release -# Before linking, we need to be sure rust lib is there -bin/app.elf: rust - .PHONY: rust_clean rust_clean: cd rust && CARGO_HOME="$(CURDIR)/rust/.cargo" cargo clean clean: rust_clean -# load, delete and listvariants are provided to comply with Ledger requirements -.PHONY: load -load: - python3 -m ledgerblue.loadApp $(APP_LOAD_PARAMS) - -.PHONY: delete -delete: - python3 -m ledgerblue.deleteApp $(COMMON_DELETE_PARAMS) +include $(CURDIR)/../deps/ledger-zxlib/makefiles/Makefile.side_loading # Import generic rules from the SDK include $(BOLOS_SDK)/Makefile.rules diff --git a/app/glyphs/icon_back.gif b/app/glyphs/icon_back.gif deleted file mode 100644 index a2a7e6d..0000000 Binary files a/app/glyphs/icon_back.gif and /dev/null differ diff --git a/app/glyphs/icon_crossmark.gif b/app/glyphs/icon_crossmark.gif deleted file mode 100644 index 2dcf9d9..0000000 Binary files a/app/glyphs/icon_crossmark.gif and /dev/null differ diff --git a/app/glyphs/icon_dashboard.gif b/app/glyphs/icon_dashboard.gif deleted file mode 100644 index 33d9b0a..0000000 Binary files a/app/glyphs/icon_dashboard.gif and /dev/null differ diff --git a/app/glyphs/icon_eye.gif b/app/glyphs/icon_eye.gif deleted file mode 100644 index df4bb82..0000000 Binary files a/app/glyphs/icon_eye.gif and /dev/null differ diff --git a/app/glyphs/icon_key.gif b/app/glyphs/icon_key.gif deleted file mode 100644 index 1830d46..0000000 Binary files a/app/glyphs/icon_key.gif and /dev/null differ diff --git a/app/glyphs/icon_refresh.gif b/app/glyphs/icon_refresh.gif deleted file mode 100644 index a6606de..0000000 Binary files a/app/glyphs/icon_refresh.gif and /dev/null differ diff --git a/app/glyphs/icon_stax_32.gif b/app/glyphs/icon_stax_32.gif new file mode 100644 index 0000000..6a7df8f Binary files /dev/null and b/app/glyphs/icon_stax_32.gif differ diff --git a/app/glyphs/icon_stax_64.gif b/app/glyphs/icon_stax_64.gif new file mode 100644 index 0000000..7b97e37 Binary files /dev/null and b/app/glyphs/icon_stax_64.gif differ diff --git a/app/glyphs/icon_validate_14.gif b/app/glyphs/icon_validate_14.gif deleted file mode 100644 index ccb5cab..0000000 Binary files a/app/glyphs/icon_validate_14.gif and /dev/null differ diff --git a/app/glyphs/icon_warning.gif b/app/glyphs/icon_warning.gif deleted file mode 100644 index 08bd4a7..0000000 Binary files a/app/glyphs/icon_warning.gif and /dev/null differ diff --git a/app/rust/Cargo.lock b/app/rust/Cargo.lock index 1f3ac45..20e6a11 100644 --- a/app/rust/Cargo.lock +++ b/app/rust/Cargo.lock @@ -1,10 +1,12 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "aho-corasick" -version = "0.7.15" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] @@ -71,11 +73,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + [[package]] name = "curve25519-dalek" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434e1720189a637d44fe464f4df1e6eb900b4835255b14354497c78af37d9bb8" +checksum = "4a9b85542f99a2dfa2a1b8e192662741c9859a846b296bef1c92ef9b58b5a216" dependencies = [ "byteorder", "digest 0.8.1", @@ -86,9 +97,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.0.2" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f627126b946c25a4638eec0ea634fc52506dea98db118aae985118ce7c3d723f" +checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" dependencies = [ "byteorder", "digest 0.9.0", @@ -112,7 +123,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", ] [[package]] @@ -145,9 +156,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.4" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -166,9 +177,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] @@ -181,9 +192,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-literal" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "961de220ec9a91af2e1e5bd80d02109155695e516771762381ef8581317066e0" +checksum = "d70693199b3cf4552f3fa720b54163927a3ebed2aef240efaf556033ab336a11" dependencies = [ "hex-literal-impl", "proc-macro-hack", @@ -191,9 +202,9 @@ dependencies = [ [[package]] name = "hex-literal-impl" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "853f769599eb31de176303197b7ba4973299c38c7a7604a6bc88c3eef05b9b46" +checksum = "59448fc2f82a5fb6907f78c3d69d843e82ff5b051923313cc4438cb0c7b745a8" dependencies = [ "proc-macro-hack", ] @@ -209,30 +220,33 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" +checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768" +dependencies = [ + "cpufeatures", +] [[package]] name = "libc" -version = "0.2.89" +version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "538c092e5586f4cdd7dd8078c4a79220e3e168880218124dcbce860f0ea938c6" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" [[package]] name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] [[package]] name = "memchr" -version = "2.3.4" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "merlin" @@ -260,23 +274,23 @@ checksum = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812" [[package]] name = "ppv-lite86" -version = "0.2.10" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-hack" -version = "0.5.19" +version = "0.5.20+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.24" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -287,9 +301,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.9" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -332,9 +346,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.4.5" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "aho-corasick", "memchr", @@ -343,15 +357,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.23" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "rslib" version = "0.1.0" dependencies = [ - "curve25519-dalek 3.0.2", + "curve25519-dalek 3.2.1", "env_logger", "getrandom", "hex", @@ -372,7 +386,7 @@ checksum = "021b403afe70d81eea68f6ea12f6b3c9588e5d536a94c3bf80f15e7faa267862" dependencies = [ "arrayref", "arrayvec", - "curve25519-dalek 2.1.2", + "curve25519-dalek 2.1.3", "merlin", "rand_core", "sha2", @@ -394,26 +408,26 @@ dependencies = [ [[package]] name = "subtle" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.64" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] name = "synstructure" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", @@ -423,30 +437,36 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ "winapi-util", ] [[package]] name = "typenum" -version = "1.13.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-xid" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasi" @@ -487,18 +507,18 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "zeroize" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a974bcdd357f0dca4d41677db03436324d45a4c9ed2d0b873a5a360ce41c36" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.0.1" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f369ddb18862aba61aa49bf31e74d29f0f162dec753063200e1dc084345d16" +checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" dependencies = [ "proc-macro2", "quote", diff --git a/app/src/addr.c b/app/src/addr.c index 5ec1a4b..f8fa670 100644 --- a/app/src/addr.c +++ b/app/src/addr.c @@ -22,7 +22,6 @@ #include "app_mode.h" #include "crypto.h" - zxerr_t addr_getNumItems(uint8_t *num_items) { zemu_log_stack("addr_getNumItems"); *num_items = 1; @@ -36,9 +35,8 @@ zxerr_t addr_getItem(int8_t displayIdx, char *outKey, uint16_t outKeyLen, char *outVal, uint16_t outValLen, uint8_t pageIdx, uint8_t *pageCount) { - char buffer[30]; - snprintf(buffer, sizeof(buffer), "addr_getItem %d/%d", displayIdx, pageIdx); - zemu_log_stack(buffer); + ZEMU_LOGF(50, "addr_getItem %d/%d", displayIdx, pageIdx) + switch (displayIdx) { case 0: snprintf(outKey, outKeyLen, "Address"); diff --git a/app/src/apdu_handler.c b/app/src/apdu_handler.c index fd3e179..6872e69 100644 --- a/app/src/apdu_handler.c +++ b/app/src/apdu_handler.c @@ -31,13 +31,19 @@ #include "zxmacros.h" #include "secret.h" #include "app_mode.h" +#include "view.h" +#include "swap.h" + +static bool tx_initialized = false; void extractHDPath(uint32_t rx, uint32_t offset) { + tx_initialized = false; + if ((rx - offset) < sizeof(uint32_t) * HDPATH_LEN_DEFAULT) { THROW(APDU_CODE_WRONG_LENGTH); } - MEMCPY(hdPath, G_io_apdu_buffer + offset, sizeof(uint32_t) * HDPATH_LEN_DEFAULT); + memcpy(hdPath, G_io_apdu_buffer + offset, sizeof(uint32_t) * HDPATH_LEN_DEFAULT); const bool mainnet = hdPath[0] == HDPATH_0_DEFAULT && hdPath[1] == HDPATH_1_DEFAULT; @@ -53,8 +59,7 @@ void extractHDPath(uint32_t rx, uint32_t offset) { #endif } -__Z_INLINE bool process_chunk(volatile uint32_t *tx, uint32_t rx) { - zemu_log("process_chunk\n"); +__Z_INLINE bool process_chunk(__Z_UNUSED volatile uint32_t *tx, uint32_t rx) { const uint8_t payloadType = G_io_apdu_buffer[OFFSET_PAYLOAD_TYPE]; #ifndef SUPPORT_SR25519 if (G_io_apdu_buffer[OFFSET_P2] != 0) { @@ -67,22 +72,28 @@ __Z_INLINE bool process_chunk(volatile uint32_t *tx, uint32_t rx) { uint32_t added; switch (payloadType) { - case 0: - zemu_log("process_chunk - init\n"); + case P1_INIT: tx_initialize(); tx_reset(); extractHDPath(rx, OFFSET_DATA); + tx_initialized = true; return false; - case 1: - zemu_log("process_chunk - add \n"); + case P1_ADD: + if (!tx_initialized) { + THROW(APDU_CODE_TX_NOT_INITIALIZED); + } added = tx_append(&(G_io_apdu_buffer[OFFSET_DATA]), rx - OFFSET_DATA); if (added != rx - OFFSET_DATA) { + tx_initialized = false; THROW(APDU_CODE_OUTPUT_BUFFER_TOO_SMALL); } return false; - case 2: - zemu_log("process_chunk - end \n"); + case P1_LAST: + if (!tx_initialized) { + THROW(APDU_CODE_TX_NOT_INITIALIZED); + } added = tx_append(&(G_io_apdu_buffer[OFFSET_DATA]), rx - OFFSET_DATA); + tx_initialized = false; if (added != rx - OFFSET_DATA) { THROW(APDU_CODE_OUTPUT_BUFFER_TOO_SMALL); } @@ -92,7 +103,7 @@ __Z_INLINE bool process_chunk(volatile uint32_t *tx, uint32_t rx) { THROW(APDU_CODE_INVALIDP1P2); } -__Z_INLINE void handle_getversion(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { +__Z_INLINE void handle_getversion(__Z_UNUSED volatile uint32_t *flags, volatile uint32_t *tx) { G_io_apdu_buffer[0] = 0; #if defined(APP_TESTING) @@ -133,7 +144,7 @@ __Z_INLINE void handleGetAddr(volatile uint32_t *flags, volatile uint32_t *tx, u } if (requireConfirmation) { view_review_init(addr_getItem, addr_getNumItems, app_reply_address); - view_review_show(); + view_review_show(REVIEW_ADDRESS); *flags |= IO_ASYNCH_REPLY; return; } @@ -141,48 +152,63 @@ __Z_INLINE void handleGetAddr(volatile uint32_t *flags, volatile uint32_t *tx, u THROW(APDU_CODE_OK); } -#ifdef SUPPORT_SR25519 -__Z_INLINE void handleSignSr25519(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { - zxerr_t err = app_sign_sr25519(); - if(err != zxerr_ok){ - *tx = 0; - THROW(APDU_CODE_DATA_INVALID); +__Z_INLINE void handleSign(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { + zemu_log("handleSign\n"); + if (!process_chunk(tx, rx)) { + THROW(APDU_CODE_OK); } + if (app_mode_secret()) { + app_mode_set_secret(false); + } + const uint8_t addr_type = G_io_apdu_buffer[OFFSET_P2]; + const key_kind_e key_type = get_key_type(addr_type); - CHECK_APP_CANARY() - + *tx = 0; const char *error_msg = tx_parse(); CHECK_APP_CANARY() if (error_msg != NULL) { - int error_msg_length = strlen(error_msg); - MEMCPY(G_io_apdu_buffer, error_msg, error_msg_length); + const int error_msg_length = strnlen(error_msg, sizeof(G_io_apdu_buffer)); + memcpy(G_io_apdu_buffer, error_msg, error_msg_length); *tx += (error_msg_length); THROW(APDU_CODE_DATA_INVALID); } - - view_review_init(tx_getItem, tx_getNumItems, app_return_sr25519); - view_review_show(); - *flags |= IO_ASYNCH_REPLY; -} + switch (key_type) { + case key_ed25519: { + if (G_swap_state.called_from_swap) { + G_swap_state.should_exit = 1; + app_sign_ed25519(); + } else { + view_review_init(tx_getItem, tx_getNumItems, app_sign_ed25519); + view_review_show(REVIEW_TXN); + *flags |= IO_ASYNCH_REPLY; + } + break; + } +#ifdef SUPPORT_SR25519 + case key_sr25519: { + zxerr_t err = app_sign_sr25519(); + if (err != zxerr_ok) { + THROW(APDU_CODE_DATA_INVALID); + } + if (G_swap_state.called_from_swap) { + G_swap_state.should_exit = 1; + app_return_sr25519(); + } else { + view_review_init(tx_getItem, tx_getNumItems, app_return_sr25519); + view_review_show(REVIEW_TXN); + *flags |= IO_ASYNCH_REPLY; + } + break; + } #endif - -__Z_INLINE void handleSignEd25519(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { - const char *error_msg = tx_parse(); - CHECK_APP_CANARY() - if (error_msg != NULL) { - int error_msg_length = strlen(error_msg); - MEMCPY(G_io_apdu_buffer, error_msg, error_msg_length); - *tx += (error_msg_length); - THROW(APDU_CODE_DATA_INVALID); + default: { + THROW(APDU_CODE_DATA_INVALID); + } } - - view_review_init(tx_getItem, tx_getNumItems, app_sign_ed25519); - view_review_show(); - *flags |= IO_ASYNCH_REPLY; } -__Z_INLINE void handleSign(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { - zemu_log("handleSign\n"); +__Z_INLINE void handleSignRaw(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { + zemu_log("handleSignRaw\n"); if (!process_chunk(tx, rx)) { THROW(APDU_CODE_OK); } @@ -193,14 +219,33 @@ __Z_INLINE void handleSign(volatile uint32_t *flags, volatile uint32_t *tx, uint const key_kind_e key_type = get_key_type(addr_type); *tx = 0; + const char *error_msg = tx_raw_parse(); + CHECK_APP_CANARY() + if (error_msg != NULL) { + const int error_msg_length = strnlen(error_msg, sizeof(G_io_apdu_buffer)); + memcpy(G_io_apdu_buffer, error_msg, error_msg_length); + *tx += (error_msg_length); + THROW(APDU_CODE_DATA_INVALID); + } switch (key_type) { - case key_ed25519: - handleSignEd25519(flags, tx, rx); + case key_ed25519: { + view_review_init(tx_raw_getItem, tx_raw_getNumItems, app_sign_ed25519); + view_review_show(REVIEW_TXN); + *flags |= IO_ASYNCH_REPLY; break; + } #ifdef SUPPORT_SR25519 - case key_sr25519: - handleSignSr25519(flags, tx, rx); + case key_sr25519: { + zxerr_t err = app_sign_sr25519(); + if(err != zxerr_ok){ + *tx = 0; + THROW(APDU_CODE_DATA_INVALID); + } + view_review_init(tx_raw_getItem, tx_raw_getNumItems, app_return_sr25519); + view_review_show(REVIEW_TXN); + *flags |= IO_ASYNCH_REPLY; break; + } #endif default: { THROW(APDU_CODE_DATA_INVALID); @@ -215,7 +260,7 @@ void handleTest(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { #endif void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { - uint16_t sw = 0; + volatile uint16_t sw = 0; BEGIN_TRY { @@ -231,22 +276,23 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { switch (G_io_apdu_buffer[OFFSET_INS]) { case INS_GET_VERSION: { - handle_getversion(flags, tx, rx); + handle_getversion(flags, tx); break; } case INS_GET_ADDR: { - if( os_global_pin_is_validated() != BOLOS_UX_OK ) { - THROW(APDU_CODE_COMMAND_NOT_ALLOWED); - } + CHECK_PIN_VALIDATED() handleGetAddr(flags, tx, rx); break; } + case INS_SIGN_RAW: + CHECK_PIN_VALIDATED() + handleSignRaw(flags, tx, rx); + break; + case INS_SIGN: { - if( os_global_pin_is_validated() != BOLOS_UX_OK ) { - THROW(APDU_CODE_COMMAND_NOT_ALLOWED); - } + CHECK_PIN_VALIDATED() handleSign(flags, tx, rx); break; } @@ -278,7 +324,7 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { break; } G_io_apdu_buffer[*tx] = sw >> 8; - G_io_apdu_buffer[*tx + 1] = sw; + G_io_apdu_buffer[*tx + 1] = sw & 0xFF; *tx += 2; } FINALLY diff --git a/app/src/coin.h b/app/src/coin.h index 8404cd3..b081787 100644 --- a/app/src/coin.h +++ b/app/src/coin.h @@ -15,50 +15,4 @@ ********************************************************************************/ #pragma once -#ifdef __cplusplus -extern "C" { -#endif - -#define CLA 0x94 - -#define HDPATH_LEN_DEFAULT 5 -#define HDPATH_0_DEFAULT (0x80000000 | 0x2c) -#define HDPATH_1_DEFAULT (0x80000000 | 0x20b) -#define HDPATH_1_RECOVERY (0x80000000 | 0x162) - -#define SK_LEN_25519 64u -#define SCALAR_LEN_ED25519 32u -#define SIG_PLUS_TYPE_LEN 65u - -#define PK_LEN_25519 32u -#define MAX_SIGN_SIZE 256u -#define BLAKE2B_DIGEST_SIZE 32u - -typedef enum { - key_ed25519 = 0, - -#if defined(SUPPORT_SR25519) - key_sr25519 = 1 -#endif - -} key_kind_e; - -// Coin Specific -#define PK_ADDRESS_TYPE COIN_ADDR_TYPE_EDGEWARE -#define SUPPORTED_TX_VERSION_CURRENT LEDGER_MAJOR_VERSION -#define SUPPORTED_TX_VERSION_PREVIOUS (LEDGER_MAJOR_VERSION - 1) -#define SUPPORTED_MINIMUM_SPEC_VERSION 46 - -#define COIN_AMOUNT_DECIMAL_PLACES 18 - -#define COIN_GENESIS_HASH "742a2ca70c2fda6cee4f8df98d64c4c670a052d9568058982dad9d5a7a135c5b" -#define COIN_NAME "Edgeware" -#define COIN_TICKER "EDG " - -#define COIN_SECRET_REQUIRED_CLICKS 0 - -#include "coin_standard.h" - -#ifdef __cplusplus -} -#endif +#include "substrate_coin.h" diff --git a/app/src/coin_ss58.h b/app/src/coin_ss58.h deleted file mode 100644 index 365248a..0000000 --- a/app/src/coin_ss58.h +++ /dev/null @@ -1,30 +0,0 @@ -/******************************************************************************* -* (c) 2020 Zondax GmbH -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -********************************************************************************/ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#define COIN_ADDR_TYPE_POLKADOT 0 -#define COIN_ADDR_TYPE_KUSAMA 2 -#define COIN_ADDR_TYPE_EDGEWARE 7 -#define COIN_ADDR_TYPE_KULUPU 16 -#define COIN_ADDR_TYPE_DOTHEREUM 20 - -#ifdef __cplusplus -} -#endif diff --git a/app/src/coin_standard.h b/app/src/coin_standard.h deleted file mode 100644 index 3b301b3..0000000 --- a/app/src/coin_standard.h +++ /dev/null @@ -1,39 +0,0 @@ -/******************************************************************************* -* (c) 2019 Zondax GmbH -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -********************************************************************************/ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include "coin_ss58.h" - - -#define HDPATH_2_DEFAULT (0x80000000u | 0u) -#define HDPATH_3_DEFAULT (0u) -#define HDPATH_4_DEFAULT (0u) - -#define MENU_MAIN_APP_LINE1 "Edgeware" -#define MENU_MAIN_APP_LINE2 "Ready" -#define MENU_MAIN_APP_LINE2_SECRET "RECOVERY" -#define APPVERSION_LINE1 "Edgeware" -#define APPVERSION_LINE2 "v" APPVERSION - -#ifdef __cplusplus -} -#endif diff --git a/app/src/common/actions.h b/app/src/common/actions.h index 680725e..e1c206e 100644 --- a/app/src/common/actions.h +++ b/app/src/common/actions.h @@ -24,30 +24,21 @@ #include "zxerror.h" extern uint16_t action_addrResponseLen; -extern uint16_t action_signResponseLen; #ifdef SUPPORT_SR25519 __Z_INLINE zxerr_t app_sign_sr25519() { const uint8_t *message = tx_get_buffer(); const uint16_t messageLength = tx_get_buffer_length(); - uint16_t replyLen = 0; - zxerr_t zxerr; - zxerr = crypto_sign_sr25519_prephase(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 3, message, messageLength); - if (zxerr != zxerr_ok) { - MEMZERO(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE); - return zxerr; - } - zxerr = crypto_sign_sr25519(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 3, &replyLen); - return zxerr; + return crypto_sign_sr25519(message, messageLength); } #endif __Z_INLINE void app_sign_ed25519() { const uint8_t *message = tx_get_buffer(); const uint16_t messageLength = tx_get_buffer_length(); - uint16_t replyLen = 0; - zxerr_t err = crypto_sign_ed25519(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 3, - message, messageLength, &replyLen); + + zxerr_t err = crypto_sign_ed25519(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 3, message, messageLength); + if (err != zxerr_ok) { set_code(G_io_apdu_buffer, 0, APDU_CODE_SIGN_VERIFY_ERROR); io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); @@ -59,10 +50,10 @@ __Z_INLINE void app_sign_ed25519() { #ifdef SUPPORT_SR25519 __Z_INLINE void app_return_sr25519() { - MEMCPY(G_io_apdu_buffer, (void *) &N_sr25519_signdata.signature, SIG_PLUS_TYPE_LEN); - zxerr_t zxerr = zeroize_sr25519_signdata(); + const zxerr_t err = copy_sr25519_signdata(G_io_apdu_buffer, sizeof(G_io_apdu_buffer) - 2); + zeroize_sr25519_signdata(); - if (zxerr != zxerr_ok) { + if (err != zxerr_ok) { set_code(G_io_apdu_buffer, 0, APDU_CODE_SIGN_VERIFY_ERROR); io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); } else { @@ -73,35 +64,39 @@ __Z_INLINE void app_return_sr25519() { #endif __Z_INLINE void app_reject() { +#ifdef SUPPORT_SR25519 zeroize_sr25519_signdata(); +#endif set_code(G_io_apdu_buffer, 0, APDU_CODE_COMMAND_NOT_ALLOWED); io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); } __Z_INLINE zxerr_t app_fill_address(key_kind_e addressKind) { // Put data directly in the apdu buffer - MEMZERO(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE); - CHECK_ZXERR(crypto_fillAddress(addressKind, - G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 2, - &action_addrResponseLen)); - return zxerr_ok; + return crypto_fillAddress(addressKind, + G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 2, + &action_addrResponseLen); } __Z_INLINE key_kind_e get_key_type(uint8_t num) { +#ifdef SUPPORT_SR25519 switch (num) { case 0x00: return key_ed25519; -#ifdef SUPPORT_SR25519 case 0x01: return key_sr25519; -#endif - default: - return 0xff; } + return 0xff; +#else + UNUSED(num); + return key_ed25519; +#endif } __Z_INLINE void app_reply_error() { +#ifdef SUPPORT_SR25519 zeroize_sr25519_signdata(); +#endif set_code(G_io_apdu_buffer, 0, APDU_CODE_DATA_INVALID); io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); } diff --git a/app/src/common/app_main.c b/app/src/common/app_main.c deleted file mode 100644 index d7baac4..0000000 --- a/app/src/common/app_main.c +++ /dev/null @@ -1,204 +0,0 @@ -/******************************************************************************* -* (c) 2018, 2019 Zondax GmbH -* (c) 2016 Ledger -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -********************************************************************************/ - -#include "app_main.h" - -#include -#include -#include -#include - -#include "view.h" -#include "actions.h" -#include "tx.h" -#include "crypto.h" -#include "coin.h" -#include "zxmacros.h" -#include "app_mode.h" - -unsigned char G_io_seproxyhal_spi_buffer[IO_SEPROXYHAL_BUFFER_SIZE_B]; - -unsigned char io_event(unsigned char channel) { - UNUSED(channel); - - switch (G_io_seproxyhal_spi_buffer[0]) { - case SEPROXYHAL_TAG_FINGER_EVENT: // - UX_FINGER_EVENT(G_io_seproxyhal_spi_buffer); - break; - - case SEPROXYHAL_TAG_BUTTON_PUSH_EVENT: // for Nano S - UX_BUTTON_PUSH_EVENT(G_io_seproxyhal_spi_buffer); - break; - - case SEPROXYHAL_TAG_DISPLAY_PROCESSED_EVENT: - if (!UX_DISPLAYED()) { - UX_DISPLAYED_EVENT(); - } - break; - - case SEPROXYHAL_TAG_TICKER_EVENT: { // - UX_TICKER_EVENT(G_io_seproxyhal_spi_buffer, { - if (UX_ALLOWED) { - UX_REDISPLAY(); - } - }); - break; - } - - // unknown events are acknowledged - default: - UX_DEFAULT_EVENT(); - break; - } - if (!io_seproxyhal_spi_is_status_sent()) { - io_seproxyhal_general_status(); - } - return 1; // DO NOT reset the current APDU transport -} - -unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) { - switch (channel & ~(IO_FLAGS)) { - case CHANNEL_KEYBOARD: - break; - - // multiplexed io exchange over a SPI channel and TLV encapsulated protocol - case CHANNEL_SPI: - if (tx_len) { - io_seproxyhal_spi_send(G_io_apdu_buffer, tx_len); - - if (channel & IO_RESET_AFTER_REPLIED) { - reset(); - } - return 0; // nothing received from the master so far (it's a tx - // transaction) - } else { - return io_seproxyhal_spi_recv(G_io_apdu_buffer, sizeof(G_io_apdu_buffer), 0); - } - - default: - THROW(INVALID_PARAMETER); - } - return 0; -} - -void handle_generic_apdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { - UNUSED(flags); - - if (rx > 4 && MEMCMP(G_io_apdu_buffer, "\xE0\x01\x00\x00", 4) == 0) { - // Respond to get device info command - uint8_t *p = G_io_apdu_buffer; - // Target ID 4 bytes - p[0] = (TARGET_ID >> 24) & 0xFF; - p[1] = (TARGET_ID >> 16) & 0xFF; - p[2] = (TARGET_ID >> 8) & 0xFF; - p[3] = (TARGET_ID >> 0) & 0xFF; - p += 4; - // SE Version [length][non-terminated string] - *p = os_version(p + 1, 64); - p = p + 1 + *p; - // Flags [length][flags] - *p = 0; - p++; - // MCU Version [length][non-terminated string] - *p = os_seph_version(p + 1, 64); - p = p + 1 + *p; - - *tx = p - G_io_apdu_buffer; - THROW(APDU_CODE_OK); - } -} - -void app_init() { - io_seproxyhal_init(); - -#ifdef TARGET_NANOX - // grab the current plane mode setting - G_io_app.plane_mode = os_setting_get(OS_SETTING_PLANEMODE, NULL, 0); -#endif // TARGET_NANOX - - USB_power(0); - USB_power(1); - app_mode_reset(); - zeroize_sr25519_signdata(); - view_idle_show(0, NULL); - -#ifdef HAVE_BLE - // Enable Bluetooth - BLE_power(0, NULL); - BLE_power(1, "Nano X"); -#endif // HAVE_BLE - -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wmissing-noreturn" - -void app_main() { - volatile uint32_t rx = 0, tx = 0, flags = 0; - - for (;;) { - volatile uint16_t sw = 0; - - BEGIN_TRY; - { - TRY; - { - rx = tx; - tx = 0; - - rx = io_exchange(CHANNEL_APDU | flags, rx); - flags = 0; - CHECK_APP_CANARY() - - if (rx == 0) - THROW(APDU_CODE_EMPTY_BUFFER); - - handle_generic_apdu(&flags, &tx, rx); - CHECK_APP_CANARY() - - handleApdu(&flags, &tx, rx); - CHECK_APP_CANARY() - } - CATCH(EXCEPTION_IO_RESET) - { - // reset IO and UX before continuing - app_init(); - continue; - } - CATCH_OTHER(e); - { - switch (e & 0xF000) { - case 0x6000: - case 0x9000: - sw = e; - break; - default: - sw = 0x6800 | (e & 0x7FF); - break; - } - G_io_apdu_buffer[tx] = sw >> 8; - G_io_apdu_buffer[tx + 1] = sw; - tx += 2; - } - FINALLY; - {} - } - END_TRY; - } -} - -#pragma clang diagnostic pop diff --git a/app/src/common/app_main.h b/app/src/common/app_main.h deleted file mode 100644 index 4d21bb7..0000000 --- a/app/src/common/app_main.h +++ /dev/null @@ -1,50 +0,0 @@ -/******************************************************************************* -* (c) 2016 Ledger -* (c) 2018 Zondax GmbH -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -********************************************************************************/ -#pragma once - -#include -#include "apdu_codes.h" - -#define OFFSET_CLA 0 -#define OFFSET_INS 1 //< Instruction offset -#define OFFSET_P1 2 //< P1 -#define OFFSET_P2 3 //< P2 -#define OFFSET_DATA_LEN 4 //< Data Length -#define OFFSET_DATA 5 //< Data offset - -#define APDU_MIN_LENGTH 5 - -#define OFFSET_PAYLOAD_TYPE OFFSET_P1 - -#define INS_GET_VERSION 0x00 -#define INS_GET_ADDR 0x01 -#define INS_SIGN 0x02 - -#define INS_ALLOWLIST_GET_PUBKEY 0x90 -#define INS_ALLOWLIST_SET_PUBKEY 0x91 -#define INS_ALLOWLIST_GET_HASH 0x92 -#define INS_ALLOWLIST_UPLOAD 0x93 - -#if defined(APP_TESTING) -#define INS_TEST 0xFF -#endif - -void app_init(); - -void app_main(); - -void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx); diff --git a/app/src/common/main.c b/app/src/common/main.c index 87150f0..9e52617 100644 --- a/app/src/common/main.c +++ b/app/src/common/main.c @@ -16,17 +16,42 @@ ********************************************************************************/ #include "app_main.h" #include "view.h" +#include "swap.h" #include +static void app_exit(void) { + BEGIN_TRY_L(exit) { + TRY_L(exit) { + os_sched_exit(-1); + } + FINALLY_L(exit) { + } + } + END_TRY_L(exit); +} + __attribute__((section(".boot"))) int -main(void) { +main(int arg0) { // exit critical section __asm volatile("cpsie i"); view_init(); os_boot(); + if (arg0) { + libargs_s *args = (libargs_s *) arg0; + if (args->id != 0x100) { + app_exit(); + return 0; + } + + swap_handle_command(args); + if (!G_swap_state.called_from_swap) { + os_lib_end(); + } + } + BEGIN_TRY { TRY diff --git a/app/src/common/parser_common.h b/app/src/common/parser_common.h index 9e06875..3ad97ae 100644 --- a/app/src/common/parser_common.h +++ b/app/src/common/parser_common.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 Zondax GmbH +* (c) 2019 - 2023 Zondax AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,8 +50,14 @@ typedef enum { parser_unexpected_callIndex, parser_unexpected_unparsed_bytes, parser_print_not_supported, + parser_tx_nesting_not_supported, parser_tx_nesting_limit_reached, parser_tx_call_vec_too_large, + // Swap specific + parser_swap_tx_wrong_method, + parser_swap_tx_wrong_method_args_num, + parser_swap_tx_wrong_dest_addr, + parser_swap_tx_wrong_amount, } parser_error_t; typedef struct { diff --git a/app/src/common/tx.c b/app/src/common/tx.c index 7a979b9..b901fae 100644 --- a/app/src/common/tx.c +++ b/app/src/common/tx.c @@ -16,12 +16,15 @@ #include "tx.h" #include "apdu_codes.h" +#include "app_main.h" #include "buffering.h" #include "parser.h" #include +#include "zxformat.h" #include "zxmacros.h" +#include "swap.h" -#if defined(TARGET_NANOX) || defined(TARGET_NANOS2) +#if defined(TARGET_NANOX) || defined(TARGET_NANOS2) || defined(TARGET_STAX) #define RAM_BUFFER_SIZE 8192 #define FLASH_BUFFER_SIZE 16384 #elif defined(TARGET_NANOS) @@ -37,7 +40,7 @@ typedef struct { uint8_t buffer[FLASH_BUFFER_SIZE]; } storage_t; -#if defined(TARGET_NANOS) || defined(TARGET_NANOX) || defined(TARGET_NANOS2) +#if defined(TARGET_NANOS) || defined(TARGET_NANOX) || defined(TARGET_NANOS2) || defined(TARGET_STAX) storage_t NV_CONST N_appdata_impl __attribute__ ((aligned(64))); #define N_appdata (*(NV_VOLATILE storage_t *)PIC(&N_appdata_impl)) #endif @@ -70,6 +73,30 @@ uint8_t *tx_get_buffer() { return buffering_get_buffer()->data; } +const char *tx_raw_parse() { + const char prefix[] = ""; + const uint8_t prefixLen = strlen(prefix); + const char postfix[] = ""; + const uint8_t postfixLen = strlen(postfix); + + const uint8_t *data = tx_get_buffer(); + const size_t dataLen = tx_get_buffer_length(); + if (data == NULL) return parser_getErrorDescription(parser_no_data); + + // we need to have, at least, prefix and postfix + if (dataLen < prefixLen + postfixLen) { + return parser_getErrorDescription(parser_unexpected_value); + } + + // check if both prefix and postfix are correct + if (strncmp((const char*)data, prefix, prefixLen) != 0 || + strncmp((const char*)data + dataLen - postfixLen, postfix, postfixLen) != 0) { + return parser_getErrorDescription(parser_unexpected_value); + } + + return NULL; +} + const char *tx_parse() { uint8_t err = parser_parse( @@ -89,6 +116,15 @@ const char *tx_parse() { return parser_getErrorDescription(err); } + // If in swap mode, compare swap tx parameters with stored info. + if (G_swap_state.called_from_swap) { + err = check_swap_conditions(&ctx_parsed_tx); + CHECK_APP_CANARY() + if (err != parser_ok) { + return parser_getErrorDescription(err); + } + } + return NULL; } @@ -110,7 +146,7 @@ zxerr_t tx_getItem(int8_t displayIdx, CHECK_ZXERR(tx_getNumItems(&numItems)) - if (displayIdx < 0 || displayIdx > numItems) { + if (displayIdx < 0 || displayIdx >= numItems) { return zxerr_no_data; } @@ -131,3 +167,44 @@ zxerr_t tx_getItem(int8_t displayIdx, return zxerr_ok; } + +zxerr_t tx_raw_getNumItems(uint8_t *num_items) { + *num_items = 2; + return zxerr_ok; +} + +zxerr_t tx_raw_getItem(int8_t displayIdx, + char *outKey, uint16_t outKeyLen, + char *outVal, uint16_t outValLen, + uint8_t pageIdx, uint8_t *pageCount) { + MEMZERO(outKey, outKeyLen); + MEMZERO(outVal, outValLen); + + uint8_t numItems = 0; + CHECK_ZXERR(tx_raw_getNumItems(&numItems)) + if (displayIdx < 0 || displayIdx >= numItems) return zxerr_no_data; + + if (displayIdx == 0) { + *pageCount = 1; + snprintf(outKey, outKeyLen, "Sign and Verify"); + snprintf(outVal, outValLen, "Arbitrary text"); + return zxerr_ok; + } + const uint8_t *buf = tx_get_buffer(); + const uint16_t bufLen = tx_get_buffer_length(); + if (buf == NULL) return zxerr_no_data; + + bool allPrintable = true; + for (uint16_t i = 0; i < bufLen; i++) { + allPrintable &= IS_PRINTABLE(buf[i]); + } + if (allPrintable) { + snprintf(outKey, outKeyLen, "Payload"); + pageStringExt(outVal, outValLen, (const char*)buf, bufLen, pageIdx, pageCount); + } else { + snprintf(outKey, outKeyLen, "Payload (hex)"); + pageStringHex(outVal, outValLen, (const char*)buf, bufLen, pageIdx, pageCount); + } + + return zxerr_ok; +} diff --git a/app/src/common/tx.h b/app/src/common/tx.h index b84b915..a66b03e 100644 --- a/app/src/common/tx.h +++ b/app/src/common/tx.h @@ -44,6 +44,8 @@ uint8_t *tx_get_buffer(); /// \return It returns NULL if data is valid or error message otherwise. const char *tx_parse(); +const char *tx_raw_parse(); + /// Return the number of items in the transaction zxerr_t tx_getNumItems(uint8_t *num_items); @@ -52,3 +54,10 @@ zxerr_t tx_getItem(int8_t displayIdx, char *outKey, uint16_t outKeyLen, char *outValue, uint16_t outValueLen, uint8_t pageIdx, uint8_t *pageCount); + +zxerr_t tx_raw_getNumItems(uint8_t *num_items); + +zxerr_t tx_raw_getItem(int8_t displayIdx, + char *outKey, uint16_t outKeyLen, + char *outValue, uint16_t outValueLen, + uint8_t pageIdx, uint8_t *pageCount); diff --git a/app/src/crypto.c b/app/src/crypto.c index a9da358..123a055 100644 --- a/app/src/crypto.c +++ b/app/src/crypto.c @@ -18,223 +18,209 @@ #include "base58.h" #include "coin.h" #include "cx.h" -#include "rslib.h" #include "zxmacros.h" #include "ristretto.h" +#include "crypto_helper.h" + +#ifdef SUPPORT_SR25519 +#include "rslib.h" +#endif -uint16_t sr25519_signdataLen; uint32_t hdPath[HDPATH_LEN_DEFAULT]; -zxerr_t crypto_extractPublicKey(key_kind_e addressKind, const uint32_t path[HDPATH_LEN_DEFAULT], - uint8_t *pubKey, uint16_t pubKeyLen) { +static zxerr_t crypto_extractPublicKey(key_kind_e addressKind, uint8_t *pubKey, uint16_t pubKeyLen) { + if (pubKey == NULL || pubKeyLen < PK_LEN_25519) { + return zxerr_invalid_crypto_settings; + } + + zxerr_t error = zxerr_unknown; cx_ecfp_public_key_t cx_publicKey; cx_ecfp_private_key_t cx_privateKey; - uint8_t privateKeyData[SK_LEN_25519]; - MEMZERO(privateKeyData, SK_LEN_25519); + uint8_t privateKeyData[SK_LEN_25519] = {0}; + + // Generate keys + CATCH_CXERROR(os_derive_bip32_with_seed_no_throw(HDW_NORMAL, + CX_CURVE_Ed25519, + hdPath, + HDPATH_LEN_DEFAULT, + privateKeyData, + NULL, + NULL, + 0)) + + switch (addressKind) { + case key_ed25519: { + CATCH_CXERROR(cx_ecfp_init_private_key_no_throw(CX_CURVE_Ed25519, privateKeyData, 32, &cx_privateKey)) + CATCH_CXERROR(cx_ecfp_init_public_key_no_throw(CX_CURVE_Ed25519, NULL, 0, &cx_publicKey)) + CATCH_CXERROR(cx_ecfp_generate_pair_no_throw(CX_CURVE_Ed25519, &cx_publicKey, &cx_privateKey, 1)) + for (unsigned int i = 0; i < PK_LEN_25519; i++) { + pubKey[i] = cx_publicKey.W[64 - i]; + } - if (pubKeyLen < PK_LEN_25519) { - return zxerr_invalid_crypto_settings; - } - zxerr_t err = zxerr_ok; - - BEGIN_TRY - { - TRY - { - // Generate keys - os_perso_derive_node_bip32_seed_key( - HDW_NORMAL, - CX_CURVE_Ed25519, - path, - HDPATH_LEN_DEFAULT, - privateKeyData, - NULL, - NULL, - 0); - - switch (addressKind) { - case key_ed25519: { - cx_ecfp_init_private_key(CX_CURVE_Ed25519, privateKeyData, 32, &cx_privateKey); - cx_ecfp_init_public_key(CX_CURVE_Ed25519, NULL, 0, &cx_publicKey); - cx_ecfp_generate_pair(CX_CURVE_Ed25519, &cx_publicKey, &cx_privateKey, 1); - for (unsigned int i = 0; i < PK_LEN_25519; i++) { - pubKey[i] = cx_publicKey.W[64 - i]; - } - - if ((cx_publicKey.W[PK_LEN_25519] & 1) != 0) { - pubKey[31] |= 0x80; - } - break; - } -#ifdef SUPPORT_SR25519 - case key_sr25519: - get_sr25519_sk(privateKeyData); - crypto_scalarmult_ristretto255_base_sdk(pubKey, privateKeyData); - break; -#endif - default: - err = zxerr_invalid_crypto_settings; + if ((cx_publicKey.W[PK_LEN_25519] & 1) != 0) { + pubKey[31] |= 0x80; } + error = zxerr_ok; + break; } - CATCH_ALL - { - err = zxerr_unknown; - } - FINALLY - { - MEMZERO(&cx_privateKey, sizeof(cx_privateKey)); - MEMZERO(privateKeyData, SK_LEN_25519); - } +#ifdef SUPPORT_SR25519 + case key_sr25519: + get_sr25519_sk(privateKeyData); + CATCH_CXERROR(crypto_scalarmult_ristretto255_base_sdk(pubKey, privateKeyData)) + error = zxerr_ok; + break; +#endif + default: + error = zxerr_invalid_crypto_settings; + break; } - END_TRY; - return err; +catch_cx_error: + MEMZERO(&cx_privateKey, sizeof(cx_privateKey)); + MEMZERO(privateKeyData, SK_LEN_25519); + + if (error != zxerr_ok) { + MEMZERO(pubKey, pubKeyLen); + } + return error; } -zxerr_t crypto_sign_ed25519(uint8_t *signature, uint16_t signatureMaxlen, - const uint8_t *message, uint16_t messageLen, - uint16_t *signatureLen) { +zxerr_t crypto_sign_ed25519(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t *message, uint16_t messageLen) { + if (signature == NULL || message == NULL || signatureMaxlen < SIG_PLUS_TYPE_LEN) { + return zxerr_unknown; + } + cx_ecfp_private_key_t cx_privateKey; + uint8_t privateKeyData[SK_LEN_25519] = {0}; + const uint8_t *toSign = message; - uint8_t messageDigest[BLAKE2B_DIGEST_SIZE]; + uint8_t messageDigest[BLAKE2B_DIGEST_SIZE] = {0}; + + zxerr_t error = zxerr_unknown; if (messageLen > MAX_SIGN_SIZE) { // Hash it cx_blake2b_t ctx; - cx_blake2b_init(&ctx, 256); - cx_hash(&ctx.header, CX_LAST, message, messageLen, messageDigest, BLAKE2B_DIGEST_SIZE); + CATCH_CXERROR(cx_blake2b_init_no_throw(&ctx, 256)) + CATCH_CXERROR(cx_hash_no_throw(&ctx.header, CX_LAST, message, messageLen, messageDigest, BLAKE2B_DIGEST_SIZE)) toSign = messageDigest; messageLen = BLAKE2B_DIGEST_SIZE; } - cx_ecfp_private_key_t cx_privateKey; - uint8_t privateKeyData[SK_LEN_25519]; - int signatureLength = 0; - unsigned int info = 0; - - zxerr_t err = zxerr_ok; - - BEGIN_TRY - { - TRY - { - // Generate keys - os_perso_derive_node_bip32_seed_key( - HDW_NORMAL, - CX_CURVE_Ed25519, - hdPath, - HDPATH_LEN_DEFAULT, - privateKeyData, - NULL, - NULL, - 0); - - cx_ecfp_init_private_key(CX_CURVE_Ed25519, privateKeyData, SCALAR_LEN_ED25519, &cx_privateKey); - - // Sign - *signature = PREFIX_SIGNATURE_TYPE_ED25519; - signatureLength = cx_eddsa_sign(&cx_privateKey, - CX_LAST, - CX_SHA512, - toSign, - messageLen, - NULL, - 0, - signature + 1, - signatureMaxlen - 1, - &info); + // Generate keys + CATCH_CXERROR(os_derive_bip32_with_seed_no_throw(HDW_NORMAL, + CX_CURVE_Ed25519, + hdPath, + HDPATH_LEN_DEFAULT, + privateKeyData, + NULL, + NULL, + 0)) + + CATCH_CXERROR(cx_ecfp_init_private_key_no_throw(CX_CURVE_Ed25519, privateKeyData, SCALAR_LEN_ED25519, &cx_privateKey)) + + // Sign + *signature = PREFIX_SIGNATURE_TYPE_ED25519; + CATCH_CXERROR(cx_eddsa_sign_no_throw(&cx_privateKey, + CX_SHA512, + toSign, + messageLen, + signature + 1, + signatureMaxlen - 1)) + error = zxerr_ok; + +catch_cx_error: + MEMZERO(&cx_privateKey, sizeof(cx_privateKey)); + MEMZERO(privateKeyData, SK_LEN_25519); - } - CATCH_ALL - { - *signatureLen = 0; - err = zxerr_unknown; - } - FINALLY - { - MEMZERO(&cx_privateKey, sizeof(cx_privateKey)); - MEMZERO(privateKeyData, SK_LEN_25519); - MEMZERO(signature + signatureLength + 1, signatureMaxlen - signatureLength - 1); - } + if (error != zxerr_ok) { + MEMZERO(signature, signatureMaxlen); } - END_TRY; - return err; + + return error; } #ifdef SUPPORT_SR25519 -zxerr_t crypto_sign_sr25519_prephase(uint8_t *buffer, uint16_t bufferLen, - const uint8_t *message, uint16_t messageLen) { - if (messageLen > MAX_SIGN_SIZE) { - uint8_t messageDigest[BLAKE2B_DIGEST_SIZE]; - cx_blake2b_t *ctx = (cx_blake2b_t *) buffer; - cx_blake2b_init(ctx, 256); - cx_hash(&ctx->header, CX_LAST, message, messageLen, messageDigest, BLAKE2B_DIGEST_SIZE); - MEMCPY_NV((void *) &N_sr25519_signdata.signdata, messageDigest, BLAKE2B_DIGEST_SIZE); - sr25519_signdataLen = BLAKE2B_DIGEST_SIZE; - } else { - MEMCPY_NV((void *) &N_sr25519_signdata.signdata, (void *) message, messageLen); - sr25519_signdataLen = messageLen; +static uint8_t sr25519_signature[SIG_PLUS_TYPE_LEN] = {0}; + +void zeroize_sr25519_signdata(void) { + explicit_bzero(sr25519_signature, sizeof(sr25519_signature)); +} + +zxerr_t copy_sr25519_signdata(uint8_t *buffer, uint16_t bufferLen) { + if (SIG_PLUS_TYPE_LEN > bufferLen) { + return zxerr_buffer_too_small; } - MEMZERO(buffer, bufferLen); - uint8_t privateKeyData[SK_LEN_25519]; - MEMZERO(privateKeyData, SK_LEN_25519); - os_perso_derive_node_bip32_seed_key( - HDW_NORMAL, - CX_CURVE_Ed25519, - hdPath, - HDPATH_LEN_DEFAULT, - privateKeyData, - NULL, - NULL, - 0); - - uint8_t pubkey[PK_LEN_25519]; - MEMZERO(pubkey, PK_LEN_25519); - get_sr25519_sk(privateKeyData); - crypto_scalarmult_ristretto255_base_sdk(pubkey, privateKeyData); - MEMCPY_NV((void *) &N_sr25519_signdata.sk, privateKeyData, SK_LEN_25519); - MEMCPY_NV((void *) &N_sr25519_signdata.pk, pubkey, PK_LEN_25519); - MEMZERO(buffer, bufferLen); + memcpy(buffer, sr25519_signature, SIG_PLUS_TYPE_LEN); return zxerr_ok; } -zxerr_t crypto_sign_sr25519(uint8_t *signature, uint16_t signatureMaxlen, - uint16_t *signatureLen) { - - zxerr_t err = zxerr_ok; - - BEGIN_TRY - { - TRY - { - *signature = PREFIX_SIGNATURE_TYPE_SR25519; - sign_sr25519_phase1((uint8_t *) &N_sr25519_signdata.sk, (uint8_t *) &N_sr25519_signdata.pk, NULL, 0, - (uint8_t *) &N_sr25519_signdata.signdata, sr25519_signdataLen, signature + 1); - crypto_scalarmult_ristretto255_base_sdk(signature + 1, signature + 1 + PK_LEN_25519); - sign_sr25519_phase2((uint8_t *) &N_sr25519_signdata.sk, (uint8_t *) &N_sr25519_signdata.pk, NULL, 0, - (uint8_t *) &N_sr25519_signdata.signdata, sr25519_signdataLen, signature + 1); - MEMCPY_NV((void *) &N_sr25519_signdata.signature, signature, SIG_PLUS_TYPE_LEN); - } - CATCH_ALL - { - err = zxerr_unknown; - }; - FINALLY - { - MEMZERO(signature + SIG_PLUS_TYPE_LEN, signatureMaxlen - SIG_PLUS_TYPE_LEN); +static zxerr_t crypto_sign_sr25519_helper(const uint8_t *data, size_t len) { + uint8_t privateKeyData[SK_LEN_25519] = {0}; + uint8_t pubkey[PK_LEN_25519] = {0}; + + zxerr_t error = zxerr_unknown; + + // Generate keys + CATCH_CXERROR(os_derive_bip32_with_seed_no_throw(HDW_NORMAL, + CX_CURVE_Ed25519, + hdPath, + HDPATH_LEN_DEFAULT, + privateKeyData, + NULL, + NULL, + 0)) + + get_sr25519_sk(privateKeyData); + CATCH_CXERROR(crypto_scalarmult_ristretto255_base_sdk(pubkey, privateKeyData)) + *sr25519_signature = PREFIX_SIGNATURE_TYPE_SR25519; + sign_sr25519_phase1(privateKeyData, pubkey, NULL, 0, data, len, sr25519_signature + 1); + CATCH_CXERROR(crypto_scalarmult_ristretto255_base_sdk(sr25519_signature + 1, sr25519_signature + 1 + PK_LEN_25519)) + error = zxerr_ok; + +catch_cx_error: + if (error == zxerr_ok) { + sign_sr25519_phase2((const uint8_t *)privateKeyData, (const uint8_t *)pubkey, NULL, 0, + data, len, sr25519_signature + 1); + } else { + explicit_bzero(sr25519_signature, sizeof(sr25519_signature)); + } + + MEMZERO(pubkey, sizeof(pubkey)); + MEMZERO(privateKeyData, sizeof(privateKeyData)); + + return error; +} + +zxerr_t crypto_sign_sr25519(const uint8_t *message, size_t messageLen) { + if (message == NULL) { + return zxerr_unknown; + } + + uint8_t messageDigest[BLAKE2B_DIGEST_SIZE] = {0}; + const uint8_t *toSign = message; + + if (messageLen > MAX_SIGN_SIZE) { + // Hash it + cx_blake2b_t ctx; + if ((cx_blake2b_init_no_throw(&ctx, 256) != CX_OK) || + (cx_hash_no_throw(&ctx.header, CX_LAST, message, messageLen, messageDigest, BLAKE2B_DIGEST_SIZE) != CX_OK)) { + return zxerr_unknown; } + toSign = messageDigest; + messageLen = BLAKE2B_DIGEST_SIZE; } - END_TRY; - return err; + + return crypto_sign_sr25519_helper(toSign, messageLen); } #endif zxerr_t crypto_fillAddress(key_kind_e addressKind, uint8_t *buffer, uint16_t bufferLen, uint16_t *addrResponseLen) { if (bufferLen < PK_LEN_25519 + SS58_ADDRESS_MAX_LEN) { - return 0; + return zxerr_unknown; } MEMZERO(buffer, bufferLen); - CHECK_ZXERR(crypto_extractPublicKey(addressKind, hdPath, buffer, bufferLen)); + CHECK_ZXERR(crypto_extractPublicKey(addressKind, buffer, bufferLen)) size_t outLen = crypto_SS58EncodePubkey(buffer + PK_LEN_25519, bufferLen - PK_LEN_25519, diff --git a/app/src/crypto.h b/app/src/crypto.h index 20643e7..afe5955 100644 --- a/app/src/crypto.h +++ b/app/src/crypto.h @@ -26,52 +26,6 @@ extern "C" { #include #include "zxerror.h" -#ifdef SUPPORT_SR25519 -// Flash -typedef struct { - uint8_t sk[SK_LEN_25519]; - uint8_t pk[PK_LEN_25519]; - uint8_t signdata[MAX_SIGN_SIZE]; - uint8_t signature[SIG_PLUS_TYPE_LEN]; -} sr25519_signdata_t; - -extern uint16_t sr25519_signdataLen; - -#if defined(TARGET_NANOS) || defined(TARGET_NANOX) -sr25519_signdata_t NV_CONST N_srdata_impl __attribute__ ((aligned(64))); -#define N_sr25519_signdata (*(NV_VOLATILE sr25519_signdata_t *)PIC(&N_srdata_impl)) -#endif - -zxerr_t zeroize_sr25519_signdata() { - uint8_t dummysk[SK_LEN_25519]; - MEMZERO(dummysk, SK_LEN_25519); - uint8_t dummypk[PK_LEN_25519]; - MEMZERO(dummypk, PK_LEN_25519); - uint8_t dummysigndata[MAX_SIGN_SIZE]; - MEMZERO(dummysigndata, MAX_SIGN_SIZE); - uint8_t dummysignature[SIG_PLUS_TYPE_LEN]; - MEMZERO(dummysignature, SIG_PLUS_TYPE_LEN); - - MEMCPY_NV((void *) &N_sr25519_signdata.sk, dummysk, SK_LEN_25519); - MEMCPY_NV((void *) &N_sr25519_signdata.pk, dummypk, PK_LEN_25519); - MEMCPY_NV((void *) &N_sr25519_signdata.signdata, dummysigndata, MAX_SIGN_SIZE); - MEMCPY_NV((void *) &N_sr25519_signdata.signature, dummysignature, SIG_PLUS_TYPE_LEN); - sr25519_signdataLen = 0; - return zxerr_ok; -} - -#else - -zxerr_t zeroize_sr25519_signdata() { - return zxerr_ok; -} - -#endif - -//#define SS58_BLAKE_PREFIX (const unsigned char *) "SS58PRE" -//#define SS58_BLAKE_PREFIX_LEN 7 -#define SS58_ADDRESS_MAX_LEN 60u - #define PREFIX_SIGNATURE_TYPE_ED25519 0 #define PREFIX_SIGNATURE_TYPE_SR25519 1 #define PREFIX_SIGNATURE_TYPE_EDCSA 2 @@ -81,18 +35,19 @@ zxerr_t zeroize_sr25519_signdata() { extern uint32_t hdPath[HDPATH_LEN_DEFAULT]; uint8_t crypto_SS58EncodePubkey(uint8_t *buffer, uint16_t buffer_len, - uint8_t addressType, const uint8_t *pubkey); + uint16_t addressType, const uint8_t *pubkey); zxerr_t crypto_fillAddress(key_kind_e addressKind, uint8_t *buffer, uint16_t bufferLen, uint16_t *addrResponseLen); -zxerr_t crypto_sign_sr25519_prephase(uint8_t *buffer, uint16_t bufferLen, const uint8_t *message, uint16_t messageLen); +zxerr_t crypto_sign_ed25519(uint8_t *signature, uint16_t signatureMaxlen, const uint8_t *message, uint16_t messageLen); + +#ifdef SUPPORT_SR25519 +void zeroize_sr25519_signdata(void); -zxerr_t crypto_sign_sr25519(uint8_t *signature, uint16_t signatureMaxlen, - uint16_t *signatureLen); +zxerr_t copy_sr25519_signdata(uint8_t *buffer, uint16_t bufferLen); -zxerr_t crypto_sign_ed25519(uint8_t *signature, uint16_t signatureMaxlen, - const uint8_t *message, uint16_t messageLen, - uint16_t *signatureLen); +zxerr_t crypto_sign_sr25519(const uint8_t *message, size_t messageLen); +#endif #ifdef __cplusplus } diff --git a/app/src/crypto_helper.c b/app/src/crypto_helper.c index 761db6d..fdf1b95 100644 --- a/app/src/crypto_helper.c +++ b/app/src/crypto_helper.c @@ -17,18 +17,18 @@ #include "crypto_helper.h" #include "base58.h" -#if defined(TARGET_NANOS) || defined(TARGET_NANOX) || defined(TARGET_NANOS2) +#if defined(TARGET_NANOS) || defined(TARGET_NANOX) || defined(TARGET_NANOS2) || defined(TARGET_STAX) #include "cx.h" -int ss58hash(const unsigned char *in, unsigned int inLen, +cx_err_t ss58hash(const unsigned char *in, unsigned int inLen, unsigned char *out, unsigned int outLen) { cx_blake2b_t ctx; - cx_blake2b_init(&ctx, 512); - cx_hash(&ctx.header, 0, SS58_BLAKE_PREFIX, SS58_BLAKE_PREFIX_LEN, NULL, 0); - cx_hash(&ctx.header, CX_LAST, in, inLen, out, outLen); + CHECK_CXERROR(cx_blake2b_init_no_throw(&ctx, 512)) + CHECK_CXERROR(cx_hash_no_throw(&ctx.header, 0, SS58_BLAKE_PREFIX, SS58_BLAKE_PREFIX_LEN, NULL, 0)) + CHECK_CXERROR(cx_hash_no_throw(&ctx.header, CX_LAST, in, inLen, out, outLen)) - return 0; + return CX_OK; } #else @@ -47,8 +47,24 @@ int ss58hash(const unsigned char *in, unsigned int inLen, #endif +uint8_t crypto_SS58CalculatePrefix(uint16_t addressType, uint8_t *prefixBytes) { + if (addressType > 16383) { + return 0; + } + + if (addressType > 63) { + prefixBytes[0] = 0x40 | ((addressType >> 2) & 0x3F); + prefixBytes[1] = ((addressType & 0x3) << 6) + ((addressType >> 8) & 0x3F); + return 2; + } + + prefixBytes[0] = addressType & 0x3F; // address type + return 1; +} + uint8_t crypto_SS58EncodePubkey(uint8_t *buffer, uint16_t buffer_len, - uint8_t addressType, const uint8_t *pubkey) { + uint16_t addressType, const uint8_t *pubkey) { + // based on https://docs.substrate.io/v3/advanced/ss58/ if (buffer == NULL || buffer_len < SS58_ADDRESS_MAX_LEN) { return 0; } @@ -57,17 +73,27 @@ uint8_t crypto_SS58EncodePubkey(uint8_t *buffer, uint16_t buffer_len, } MEMZERO(buffer, buffer_len); - uint8_t unencoded[35]; uint8_t hash[64]; + uint8_t unencoded[36]; + + const uint8_t prefixSize = crypto_SS58CalculatePrefix(addressType, unencoded); + if (prefixSize == 0) { + return 0; + } - unencoded[0] = addressType; // address type - MEMCPY(unencoded + 1, pubkey, 32); // account id - ss58hash((uint8_t *) unencoded, 33, hash, 64); - unencoded[33] = hash[0]; - unencoded[34] = hash[1]; + memcpy(unencoded + prefixSize, pubkey, 32); // account id + if (ss58hash((uint8_t *) unencoded, 32 + prefixSize, hash, 64) != CX_OK) { + MEMZERO(buffer, buffer_len); + return 0; + } + unencoded[32 + prefixSize] = hash[0]; + unencoded[33 + prefixSize] = hash[1]; size_t outLen = buffer_len; - encode_base58(unencoded, 35, buffer, &outLen); + if (encode_base58(unencoded, 34 + prefixSize, buffer, &outLen) != 0) { + MEMZERO(buffer, buffer_len); + return 0; + } return outLen; } diff --git a/app/src/crypto_helper.h b/app/src/crypto_helper.h index 090875f..d7b0f68 100644 --- a/app/src/crypto_helper.h +++ b/app/src/crypto_helper.h @@ -30,8 +30,10 @@ extern "C" { #define SS58_BLAKE_PREFIX_LEN 7 #define SS58_ADDRESS_MAX_LEN 60u +uint8_t crypto_SS58CalculatePrefix(uint16_t addressType, uint8_t *prefixBytes); + uint8_t crypto_SS58EncodePubkey(uint8_t *buffer, uint16_t buffer_len, - uint8_t addressType, const uint8_t *pubkey); + uint16_t addressType, const uint8_t *pubkey); #ifdef __cplusplus } diff --git a/app/src/parser.c b/app/src/parser.c index ff31cb9..c489a03 100644 --- a/app/src/parser.c +++ b/app/src/parser.c @@ -20,16 +20,8 @@ #include "app_mode.h" #include "parser.h" #include "coin.h" -#include "coin_ss58.h" #include "substrate_dispatch.h" -#if defined(TARGET_NANOX) || defined(TARGET_NANOS2) -// For some reason NanoX requires this function -void __assert_fail(const char * assertion, const char * file, unsigned int line, const char * function){ - while(1) {}; -} -#endif - #define FIELD_FIXED_TOTAL_COUNT 7 #define FIELD_METHOD 0 @@ -132,19 +124,20 @@ parser_error_t parser_getItem(const parser_context_t *ctx, return parser_no_data; } - parser_error_t err = parser_ok; + parser_error_t err = parser_unexpected_error; if (displayIdx == FIELD_METHOD) { snprintf(outKey, outKeyLen, "%s", _getMethod_ModuleName(ctx->tx_obj->transactionVersion, ctx->tx_obj->callIndex.moduleIdx)); snprintf(outVal, outValLen, "%s", _getMethod_Name(ctx->tx_obj->transactionVersion, ctx->tx_obj->callIndex.moduleIdx, ctx->tx_obj->callIndex.idx)); - return err; + return parser_ok; } // VARIABLE ARGUMENTS uint8_t methodArgCount = _getMethod_NumItems(ctx->tx_obj->transactionVersion, ctx->tx_obj->callIndex.moduleIdx, ctx->tx_obj->callIndex.idx); + // Adjust offset when displayIdx > 0 uint8_t argIdx = displayIdx - 1; @@ -165,12 +158,11 @@ parser_error_t parser_getItem(const parser_context_t *ctx, ctx->tx_obj->callIndex.idx, argIdx)); - err = _getMethod_ItemValue(ctx->tx_obj->transactionVersion, + return _getMethod_ItemValue(ctx->tx_obj->transactionVersion, &ctx->tx_obj->method, ctx->tx_obj->callIndex.moduleIdx, ctx->tx_obj->callIndex.idx, argIdx, outVal, outValLen, pageIdx, pageCount); - return err; } else { // CONTINUE WITH FIXED ARGUMENTS displayIdx -= methodArgCount; @@ -179,14 +171,13 @@ parser_error_t parser_getItem(const parser_context_t *ctx, if (parser_show_expert_fields()) { snprintf(outKey, outKeyLen, "Chain"); snprintf(outVal, outValLen, COIN_NAME); - return err; + return parser_ok; } } else { snprintf(outKey, outKeyLen, "Genesis Hash"); - _toStringHash(&ctx->tx_obj->genesisHash, + return _toStringHash(&ctx->tx_obj->genesisHash, outVal, outValLen, pageIdx, pageCount); - return err; } } @@ -222,7 +213,7 @@ parser_error_t parser_getItem(const parser_context_t *ctx, if (displayIdx == FIELD_ERA_PHASE && parser_show_expert_fields()) { snprintf(outKey, outKeyLen, "Era Phase"); uint64_to_str(outVal, outValLen, ctx->tx_obj->era.phase); - return err; + return parser_ok; } if (!parser_show_expert_fields()) { @@ -232,7 +223,7 @@ parser_error_t parser_getItem(const parser_context_t *ctx, if (displayIdx == FIELD_ERA_PERIOD && parser_show_expert_fields()) { snprintf(outKey, outKeyLen, "Era Period"); uint64_to_str(outVal, outValLen, ctx->tx_obj->era.period); - return err; + return parser_ok; } if (!parser_show_expert_fields()) { @@ -241,10 +232,9 @@ parser_error_t parser_getItem(const parser_context_t *ctx, if (displayIdx == FIELD_BLOCK_HASH && parser_show_expert_fields()) { snprintf(outKey, outKeyLen, "Block"); - _toStringHash(&ctx->tx_obj->blockHash, + return _toStringHash(&ctx->tx_obj->blockHash, outVal, outValLen, pageIdx, pageCount); - return err; } return parser_no_data; diff --git a/app/src/parser_impl.c b/app/src/parser_impl.c index 515d0b4..bc5e2f1 100644 --- a/app/src/parser_impl.c +++ b/app/src/parser_impl.c @@ -21,377 +21,27 @@ #include "coin.h" #include "crypto_helper.h" #include "bignum.h" -#include "coin_ss58.h" #include "substrate_types.h" #include "substrate_dispatch.h" -parser_error_t parser_init_context(parser_context_t *ctx, - const uint8_t *buffer, - uint16_t bufferSize) { - ctx->offset = 0; - ctx->buffer = NULL; - ctx->bufferLen = 0; - - if (bufferSize == 0 || buffer == NULL) { - // Not available, use defaults - return parser_init_context_empty; - } - - ctx->buffer = buffer; - ctx->bufferLen = bufferSize; - return parser_ok; -} - -parser_error_t parser_init(parser_context_t *ctx, const uint8_t *buffer, uint16_t bufferSize) { - CHECK_PARSER_ERR(parser_init_context(ctx, buffer, bufferSize)) - return parser_ok; -} - -const char *parser_getErrorDescription(parser_error_t err) { - switch (err) { - // General errors - case parser_ok: - return "No error"; - case parser_no_data: - return "No more data"; - case parser_init_context_empty: - return "Initialized empty context"; - case parser_display_idx_out_of_range: - return "display_idx_out_of_range"; - case parser_display_page_out_of_range: - return "display_page_out_of_range"; - // Coin specific - case parser_spec_not_supported: - return "Spec version not supported"; - case parser_tx_version_not_supported: - return "Txn version not supported"; - case parser_not_allowed: - return "Not allowed"; - case parser_not_supported: - return "Not supported"; - case parser_unexpected_buffer_end: - return "Unexpected buffer end"; - case parser_unexpected_value: - return "Unexpected value"; - case parser_value_out_of_range: - return "Value out of range"; - case parser_value_too_many_bytes: - return "Value too many bytes"; - case parser_unexpected_module: - return "Unexpected module"; - case parser_unexpected_callIndex: - return "Unexpected call index"; - case parser_unexpected_unparsed_bytes: - return "Unexpected unparsed bytes"; - case parser_print_not_supported: - return "Value cannot be printed"; - case parser_tx_nesting_limit_reached: - return "Max nested calls reached"; - case parser_tx_call_vec_too_large: - return "Call vector exceeds limit"; - default: - return "Unrecognized error code"; - } -} - -GEN_DEF_READFIX_UNSIGNED(8) - -GEN_DEF_READFIX_UNSIGNED(16) - -GEN_DEF_READFIX_UNSIGNED(32) - -GEN_DEF_READFIX_UNSIGNED(64) - -parser_error_t _readBool(parser_context_t *c, pd_bool_t *v) { - CHECK_INPUT(); - - const uint8_t p = *(c->buffer + c->offset); - CTX_CHECK_AND_ADVANCE(c, 1) - - switch (p) { - case 0x00: - *v = bool_false; - break; - case 0x01: - *v = bool_true; - break; - default: - return parser_unexpected_value; - } - return parser_ok; -} - -parser_error_t _readCompactInt(parser_context_t *c, compactInt_t *v) { - CHECK_INPUT(); - - v->ptr = c->buffer + c->offset; - const uint8_t mode = *v->ptr & 0x03u; // get mode from two least significant bits - - uint64_t tmp; - switch (mode) { - case 0: // single byte - v->len = 1; - CTX_CHECK_AND_ADVANCE(c, v->len) - _getValue(v, &tmp); - break; - case 1: // 2-byte - v->len = 2; - CTX_CHECK_AND_ADVANCE(c, v->len) - _getValue(v, &tmp); - break; - case 2: // 4-byte - v->len = 4; - CTX_CHECK_AND_ADVANCE(c, v->len) - _getValue(v, &tmp); - break; - case 3: // bigint - v->len = (*v->ptr >> 2u) + 4 + 1; - CTX_CHECK_AND_ADVANCE(c, v->len) - break; - default: - // this is actually impossible - return parser_unexpected_value; - } - - return parser_ok; -} - -parser_error_t _getValue(const compactInt_t *c, uint64_t *v) { - *v = 0; - - switch (c->len) { - case 1: - *v = (*c->ptr) >> 2u; - break; - case 2: - *v = (*c->ptr) >> 2u; - *v += *(c->ptr + 1) << 6u; - if (*v < 64) { - return parser_value_out_of_range; - } - break; - case 4: - *v = (*c->ptr) >> 2u; - *v += *(c->ptr + 1) << 6u; - *v += *(c->ptr + 2) << (8u + 6u); - *v += *(c->ptr + 3) << (16u + 6u); - if (*v < 16383) { - return parser_value_out_of_range; - } - break; - default: - return parser_value_out_of_range; - } - - return parser_ok; -} - -parser_error_t _toStringCompactInt(const compactInt_t *c, - uint8_t decimalPlaces, - char postfix[], - char prefix[], - char *outValue, uint16_t outValueLen, - uint8_t pageIdx, uint8_t *pageCount) { - char bufferUI[200]; - MEMZERO(outValue, outValueLen); - MEMZERO(bufferUI, sizeof(bufferUI)); - *pageCount = 1; - - if (c->len <= 4) { - uint64_t v; - _getValue(c, &v); - if (uint64_to_str(bufferUI, sizeof(bufferUI), v) != NULL) { - return parser_unexpected_value; - } - } else { - // This is longer number - uint8_t bcdOut[100]; - const uint16_t bcdOutLen = sizeof(bcdOut); - - bignumLittleEndian_to_bcd(bcdOut, bcdOutLen, c->ptr + 1, c->len - 1); - if (!bignumLittleEndian_bcdprint(bufferUI, sizeof(bufferUI), bcdOut, bcdOutLen)) - return parser_unexpected_buffer_end; - } - - // Format number - if (intstr_to_fpstr_inplace(bufferUI, sizeof(bufferUI), decimalPlaces) == 0){ - return parser_unexpected_value; - } - - // Handle prefix and postfix - if (z_str3join(bufferUI, sizeof(bufferUI), prefix, postfix) != zxerr_ok) { - return parser_unexpected_buffer_end; - } - - pageString(outValue, outValueLen, bufferUI, pageIdx, pageCount); - - return parser_ok; -} - -////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////// - -parser_error_t _readCallIndex(parser_context_t *c, pd_CallIndex_t *v) { - CHECK_INPUT(); - - CHECK_ERROR(_readUInt8(c, &v->moduleIdx)); - CHECK_ERROR(_readUInt8(c, &v->idx)); - return parser_ok; -} - -parser_error_t _readEra(parser_context_t *c, pd_ExtrinsicEra_t *v) { - CHECK_INPUT(); - // https://github.com/paritytech/substrate/blob/fc3adc87dc806237eb7371c1d21055eea1702be0/core/sr-primitives/src/generic/era.rs#L117 - - v->type = eEraImmortal; - - uint8_t first; - CHECK_ERROR(_readUInt8(c, &first)); - if (first == 0) { return parser_ok; } - - v->type = eEraMortal; - uint64_t encoded = first; - CHECK_ERROR(_readUInt8(c, &first)); - encoded += (uint64_t) first << 8u; - - v->period = 2U << (encoded % (1u << 4u)); - uint64_t quantize_factor = (v->period >> 12u); - quantize_factor = (quantize_factor == 0 ? 1 : quantize_factor); - - v->phase = (encoded >> 4u) * quantize_factor; - - if (v->period >= 4 && v->phase < v->period) { - return parser_ok; - } - - return parser_unexpected_value; -} - -//////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////// - -parser_error_t _readCompactIndex(parser_context_t *c, pd_CompactIndex_t *v) { - CHECK_INPUT(); - CHECK_ERROR(_readCompactInt(c, &v->index)); - return parser_ok; -} - -parser_error_t _readCompactBalance(parser_context_t *c, pd_CompactBalance_t *v) { - CHECK_INPUT(); - CHECK_ERROR(_readCompactInt(c, &v->value)); - return parser_ok; -} - -parser_error_t _toStringCompactIndex(const pd_CompactIndex_t *v, - char *outValue, uint16_t outValueLen, - uint8_t pageIdx, uint8_t *pageCount) { - return _toStringCompactInt(&v->index, 0, 0, "", outValue, outValueLen, pageIdx, pageCount); -} - -parser_error_t _toStringCompactBalance(const pd_CompactBalance_t *v, - char *outValue, uint16_t outValueLen, - uint8_t pageIdx, uint8_t *pageCount) { - CHECK_ERROR(_toStringCompactInt( - &v->value, - COIN_AMOUNT_DECIMAL_PLACES, "", COIN_TICKER, - outValue, outValueLen, pageIdx, pageCount)) - number_inplace_trimming(outValue, 1); - return parser_ok; -} - -//////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////// - -parser_error_t _checkVersions(parser_context_t *c) { - // Methods are not length delimited so in order to retrieve the specVersion - // it is necessary to parse from the back. - // The transaction is expect to end in - // [4 bytes] specVersion - // [4 bytes] transactionVersion - // [32 bytes] genesisHash - // [32 bytes] blockHash - const uint16_t specOffsetFromBack = 4 + 4 + 32 + 32; - if (c->bufferLen < specOffsetFromBack) { - return parser_unexpected_buffer_end; - } - - uint8_t *p = (uint8_t *) (c->buffer + c->bufferLen - specOffsetFromBack); - uint32_t specVersion = 0; - specVersion += (uint32_t) p[0] << 0u; - specVersion += (uint32_t) p[1] << 8u; - specVersion += (uint32_t) p[2] << 16u; - specVersion += (uint32_t) p[3] << 24u; - - p += 4; - uint32_t transactionVersion = 0; - transactionVersion += (uint32_t) p[0] << 0u; - transactionVersion += (uint32_t) p[1] << 8u; - transactionVersion += (uint32_t) p[2] << 16u; - transactionVersion += (uint32_t) p[3] << 24u; - - if (transactionVersion != (SUPPORTED_TX_VERSION_CURRENT) && - transactionVersion != (SUPPORTED_TX_VERSION_PREVIOUS) ) { - return parser_tx_version_not_supported; - } - - if (specVersion < SUPPORTED_MINIMUM_SPEC_VERSION) { - return parser_spec_not_supported; - } - - c->tx_obj->specVersion = specVersion; - c->tx_obj->transactionVersion = transactionVersion; - - return parser_ok; -} - -uint8_t __address_type; - -uint8_t _getAddressType() { - return __address_type; -} - -uint8_t _detectAddressType(const parser_context_t *c) { - char hashstr[65]; - uint8_t pc; - - if (c->tx_obj->genesisHash._ptr != NULL) { - _toStringHash(&c->tx_obj->genesisHash, hashstr, 65, 0, &pc); - - // Compare with known genesis hashes - if (strcmp(hashstr, COIN_GENESIS_HASH) == 0) { - return PK_ADDRESS_TYPE; - } - } - - return 42; -} +extern uint16_t __address_type; parser_error_t _readTx(parser_context_t *c, parser_tx_t *v) { - CHECK_INPUT(); + CHECK_INPUT() // Reverse parse to retrieve spec before forward parsing - CHECK_ERROR(_checkVersions(c)); + CHECK_ERROR(_checkVersions(c)) // Now forward parse - CHECK_ERROR(_readCallIndex(c, &v->callIndex)); - CHECK_ERROR(_readMethod(c, v->callIndex.moduleIdx, v->callIndex.idx, &v->method)); - CHECK_ERROR(_readEra(c, &v->era)); - CHECK_ERROR(_readCompactIndex(c, &v->nonce)); - CHECK_ERROR(_readCompactBalance(c, &v->tip)); - CHECK_ERROR(_readUInt32(c, &v->specVersion)); - CHECK_ERROR(_readUInt32(c, &v->transactionVersion)); - CHECK_ERROR(_readHash(c, &v->genesisHash)); - CHECK_ERROR(_readHash(c, &v->blockHash)); + CHECK_ERROR(_readCallIndex(c, &v->callIndex)) + CHECK_ERROR(_readMethod(c, v->callIndex.moduleIdx, v->callIndex.idx, &v->method)) + CHECK_ERROR(_readEra(c, &v->era)) + CHECK_ERROR(_readCompactIndex(c, &v->nonce)) + CHECK_ERROR(_readCompactBalance(c, &v->tip)) + CHECK_ERROR(_readUInt32(c, &v->specVersion)) + CHECK_ERROR(_readUInt32(c, &v->transactionVersion)) + CHECK_ERROR(_readHash(c, &v->genesisHash)) + CHECK_ERROR(_readHash(c, &v->blockHash)) if (c->offset < c->bufferLen) { return parser_unexpected_unparsed_bytes; @@ -405,108 +55,3 @@ parser_error_t _readTx(parser_context_t *c, parser_tx_t *v) { return parser_ok; } - -//////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////// - -parser_error_t _readAddress(parser_context_t *c, pd_Address_t *v) { - CHECK_INPUT(); - // Based on - // https://github.com/paritytech/substrate/blob/fc3adc87dc806237eb7371c1d21055eea1702be0/srml/indices/src/address.rs#L66 - - uint8_t tmp; - CHECK_ERROR(_readUInt8(c, &tmp)); - - switch (tmp) { - case 0xFF: { - v->type = eAddressId; - v->idPtr = c->buffer + c->offset; - CTX_CHECK_AND_ADVANCE(c, 32); - break; - } - case 0xFE: { - compactInt_t ci; - CHECK_ERROR(_readCompactInt(c, &ci)); - - v->type = eAddressIndex; - CHECK_ERROR(_getValue(&ci, &v->idx)); - - if (v->idx <= 0xffffffffu) { - return parser_unexpected_value; - } - break; - } - case 0xFD: { - uint32_t tmpval; - CHECK_ERROR(_readUInt32(c, &tmpval)); - v->type = eAddressIndex; - v->idx = tmpval; - if (v->idx <= 0xFFFF) { - return parser_unexpected_value; - } - break; - } - case 0xFC: { - uint16_t tmpval; - CHECK_ERROR(_readUInt16(c, &tmpval)); - v->type = eAddressIndex; - v->idx = tmpval; - if (v->idx <= 0xEF) { - return parser_unexpected_value; - } - break; - } - default: - if (tmp <= 0xEF) { - v->type = eAddressIndex; - v->idx = tmp; - return parser_ok; - } - - return parser_unexpected_value; - } - - return parser_ok; -} - -parser_error_t _toStringPubkeyAsAddress(const uint8_t *pubkey, - char *outValue, uint16_t outValueLen, - uint8_t pageIdx, uint8_t *pageCount) { - char bufferUI[200]; - - if (crypto_SS58EncodePubkey((uint8_t *) bufferUI, sizeof(bufferUI), __address_type, pubkey) == 0) { - return parser_no_data; - } - - pageString(outValue, outValueLen, bufferUI, pageIdx, pageCount); - if (pageIdx >= *pageCount) { - return parser_no_data; - } - return parser_ok; -} - -parser_error_t _toStringAddress(const pd_Address_t *v, - char *outValue, uint16_t outValueLen, - uint8_t pageIdx, uint8_t *pageCount) { - MEMZERO(outValue, outValueLen); - if (v == NULL) { - return parser_ok; - } - - *pageCount = 1; - switch (v->type) { - case eAddressIndex: - return parser_not_supported; - case eAddressId: { - return _toStringPubkeyAsAddress(v->idPtr, - outValue, outValueLen, - pageIdx, pageCount); - } - } - - return parser_ok; -} diff --git a/app/src/parser_impl.h b/app/src/parser_impl.h index 4791de9..0263230 100644 --- a/app/src/parser_impl.h +++ b/app/src/parser_impl.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2019 Zondax GmbH +* (c) 2019 - 2023 Zondax AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ extern "C" { // Checks that there are at least SIZE bytes available in the buffer #define CTX_CHECK_AVAIL(CTX, SIZE) \ - if ( (CTX) == NULL || ((CTX)->offset + SIZE) > (CTX)->bufferLen) { return parser_unexpected_buffer_end; } + if ( (CTX) == NULL || ((CTX)->offset + (SIZE)) > (CTX)->bufferLen) { return parser_unexpected_buffer_end; } #define CTX_CHECK_AND_ADVANCE(CTX, SIZE) \ CTX_CHECK_AVAIL((CTX), (SIZE)) \ @@ -52,13 +52,18 @@ extern "C" { #define GEN_DEF_TOSTRING_ARRAY(SIZE) \ CLEAN_AND_CHECK(); \ + if ((SIZE) == 0) { \ + snprintf(outValue, outValueLen, "Empty"); \ + return parser_ok; \ + } \ if (v->_ptr == NULL || outValueLen == 0 ) return parser_unexpected_buffer_end; \ const uint16_t outLenNormalized = (outValueLen - 1) / 2; \ - *pageCount = SIZE / outLenNormalized; \ - if (SIZE % outLenNormalized != 0) *pageCount+=1; \ + if (outLenNormalized == 0 ) return parser_unexpected_buffer_end; \ + *pageCount = (SIZE) / outLenNormalized; \ + if ((SIZE) % outLenNormalized != 0) *pageCount+=1; \ const uint16_t pageOffset = pageIdx * outLenNormalized; \ uint16_t loopmax = outLenNormalized; \ - if (loopmax > SIZE - pageOffset) loopmax = SIZE - pageOffset; \ + if (loopmax > (SIZE) - pageOffset) loopmax = (SIZE) - pageOffset; \ for (uint16_t i = 0; i < loopmax; i++) { \ const uint16_t offset = i << 1u; \ const uint8_t *c = v->_ptr + pageOffset; \ @@ -114,11 +119,11 @@ GEN_DEC_READFIX_UNSIGNED(64); #define GEN_DEF_READVECTOR_ITEM(VEC, TYPE, INDEX, VALUE) \ parser_context_t ctx; \ - parser_init(&ctx, VEC._ptr, VEC._lenBuffer); \ + parser_init(&ctx, (VEC)._ptr, (VEC)._lenBuffer); \ compactInt_t clen; \ CHECK_PARSER_ERR(_readCompactInt(&ctx, &clen)); \ - if ((INDEX) >= VEC._len) return parser_no_data; \ - for (uint64_t i = 0; i < VEC._len; i++ ) CHECK_PARSER_ERR(_read_cro_##TYPE(&ctx, &VALUE)); \ + if ((INDEX) >= (VEC)._len) return parser_no_data; \ + for (uint64_t i = 0; i < (VEC)._len; i++ ) CHECK_PARSER_ERR(_read_cro_##TYPE(&ctx, &(VALUE)); \ return parser_ok; #define GEN_DEF_TOSTRING_VECTOR(TYPE) \ @@ -127,7 +132,7 @@ GEN_DEC_READFIX_UNSIGNED(64); *pageCount = 0; \ pd_##TYPE##_t tmp; \ parser_context_t ctx; \ - uint8_t chunkPageCount; \ + uint8_t chunkPageCount = 0; \ uint16_t currentPage, currentTotalPage = 0; \ if(v->_len == 0) { \ *pageCount = 1; \ @@ -137,14 +142,14 @@ GEN_DEC_READFIX_UNSIGNED(64); /* We need to do it twice because there is no memory to keep intermediate results*/ \ /* First count*/ \ parser_init(&ctx, v->_ptr, v->_lenBuffer);\ - for (uint16_t i = 0; i < v->_len; i++) {\ + for (uint64_t i = 0; i < v->_len; i++) {\ CHECK_ERROR(_read##TYPE(&ctx, &tmp));\ CHECK_ERROR(_toString##TYPE(&tmp, outValue, outValueLen, 0, &chunkPageCount));\ (*pageCount)+=chunkPageCount;\ }\ /* Then iterate until we can print the corresponding chunk*/ \ parser_init(&ctx, v->_ptr, v->_lenBuffer);\ - for (uint16_t i = 0; i < v->_len; i++) {\ + for (uint64_t i = 0; i < v->_len; i++) {\ CHECK_ERROR(_read##TYPE(&ctx, &tmp));\ chunkPageCount = 1;\ currentPage = 0;\ @@ -173,11 +178,18 @@ parser_error_t _readEra(parser_context_t *c, pd_ExtrinsicEra_t *v); parser_error_t _readTx(parser_context_t *c, parser_tx_t *v); -uint8_t _getAddressType(); +parser_error_t _checkVersions(parser_context_t *c); + +uint16_t _getAddressType(); + +parser_error_t _readCompactIndex(parser_context_t *c, pd_CompactIndex_t *v); + +uint16_t _detectAddressType(const parser_context_t *c); parser_error_t _toStringCompactInt(const compactInt_t *c, uint8_t decimalPlaces, - char postfix[], - char prefix[], + bool trimTrailingZeros, + const char postfix[], + const char prefix[], char *outValue, uint16_t outValueLen, uint8_t pageIdx, uint8_t *pageCount); diff --git a/app/src/parser_impl_common.c b/app/src/parser_impl_common.c new file mode 100644 index 0000000..ab15f36 --- /dev/null +++ b/app/src/parser_impl_common.c @@ -0,0 +1,494 @@ +/******************************************************************************* +* (c) 2019 - 2023 Zondax AG +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#include +#include +#include "parser_impl.h" +#include "parser_txdef.h" +#include "coin.h" +#include "crypto_helper.h" +#include "bignum.h" +#include "substrate_types.h" +#include "substrate_dispatch.h" + +parser_error_t parser_init_context(parser_context_t *ctx, + const uint8_t *buffer, + uint16_t bufferSize) { + if (ctx == NULL || bufferSize == 0 || buffer == NULL) { + // Not available, use defaults + return parser_init_context_empty; + } + + ctx->offset = 0; + ctx->buffer = buffer; + ctx->bufferLen = bufferSize; + return parser_ok; +} + +parser_error_t parser_init(parser_context_t *ctx, const uint8_t *buffer, uint16_t bufferSize) { + CHECK_PARSER_ERR(parser_init_context(ctx, buffer, bufferSize)) + return parser_ok; +} + +const char *parser_getErrorDescription(parser_error_t err) { + switch (err) { + // General errors + case parser_ok: + return "No error"; + case parser_no_data: + return "No more data"; + case parser_init_context_empty: + return "Initialized empty context"; + case parser_display_idx_out_of_range: + return "display_idx_out_of_range"; + case parser_display_page_out_of_range: + return "display_page_out_of_range"; + // Coin specific + case parser_spec_not_supported: + return "Spec version not supported"; + case parser_tx_version_not_supported: + return "Txn version not supported"; + case parser_not_allowed: + return "Not allowed"; + case parser_not_supported: + return "Not supported"; + case parser_unexpected_buffer_end: + return "Unexpected buffer end"; + case parser_unexpected_value: + return "Unexpected value"; + case parser_value_out_of_range: + return "Value out of range"; + case parser_value_too_many_bytes: + return "Value too many bytes"; + case parser_unexpected_module: + return "Unexpected module index"; + case parser_unexpected_callIndex: + return "Method not supported"; + case parser_unexpected_unparsed_bytes: + return "Unexpected unparsed bytes"; + case parser_print_not_supported: + return "Value cannot be printed"; + case parser_tx_nesting_not_supported: + return "Call nesting not supported"; + case parser_tx_nesting_limit_reached: + return "Max nested calls reached"; + case parser_tx_call_vec_too_large: + return "Call vector exceeds limit"; + // Swap specific. + case parser_swap_tx_wrong_method: + return "Swap txn wrong method"; + case parser_swap_tx_wrong_method_args_num: + return "Swap txn wrong method args count"; + case parser_swap_tx_wrong_dest_addr: + return "Swap txn wrong destination addr"; + case parser_swap_tx_wrong_amount: + return "Swap txn wrong amount"; + default: + return "Unrecognized error code"; + } +} + +GEN_DEF_READFIX_UNSIGNED(8) + +GEN_DEF_READFIX_UNSIGNED(16) + +GEN_DEF_READFIX_UNSIGNED(32) + +GEN_DEF_READFIX_UNSIGNED(64) + +parser_error_t _readBool(parser_context_t *c, pd_bool_t *v) { + CHECK_INPUT() + + const uint8_t p = *(c->buffer + c->offset); + CTX_CHECK_AND_ADVANCE(c, 1) + + switch (p) { + case 0x00: + *v = bool_false; + break; + case 0x01: + *v = bool_true; + break; + default: + return parser_unexpected_value; + } + return parser_ok; +} + +parser_error_t _readCompactInt(parser_context_t *c, compactInt_t *v) { + CHECK_INPUT() + + v->ptr = c->buffer + c->offset; + const uint8_t mode = *v->ptr & 0x03u; // get mode from two least significant bits + + uint64_t tmp; + switch (mode) { + case 0: // single byte + v->len = 1; + CTX_CHECK_AND_ADVANCE(c, v->len) + _getValue(v, &tmp); + break; + case 1: // 2-byte + v->len = 2; + CTX_CHECK_AND_ADVANCE(c, v->len) + _getValue(v, &tmp); + break; + case 2: // 4-byte + v->len = 4; + CTX_CHECK_AND_ADVANCE(c, v->len) + _getValue(v, &tmp); + break; + case 3: // bigint + v->len = (*v->ptr >> 2u) + 4 + 1; + CTX_CHECK_AND_ADVANCE(c, v->len) + break; + default: + // this is actually impossible + return parser_unexpected_value; + } + + return parser_ok; +} + +parser_error_t _getValue(const compactInt_t *c, uint64_t *v) { + *v = 0; + + switch (c->len) { + case 1: + *v = (*c->ptr) >> 2u; + break; + case 2: + *v = (*c->ptr) >> 2u; + *v += *(c->ptr + 1) << 6u; + if (*v < 64) { + return parser_value_out_of_range; + } + break; + case 4: + *v = (*c->ptr) >> 2u; + *v += *(c->ptr + 1) << 6u; + *v += *(c->ptr + 2) << (8u + 6u); + *v += *(c->ptr + 3) << (16u + 6u); + if (*v < 16383) { + return parser_value_out_of_range; + } + break; + default: + return parser_value_out_of_range; + } + + return parser_ok; +} + +parser_error_t _toStringCompactInt(const compactInt_t *c, + uint8_t decimalPlaces, + bool trimTrailingZeros, + const char postfix[], + const char prefix[], + char *outValue, uint16_t outValueLen, + uint8_t pageIdx, uint8_t *pageCount) { + char bufferUI[200]; + MEMZERO(outValue, outValueLen); + MEMZERO(bufferUI, sizeof(bufferUI)); + *pageCount = 1; + + if (c->len <= 4) { + uint64_t v; + _getValue(c, &v); + if (uint64_to_str(bufferUI, sizeof(bufferUI), v) != NULL) { + return parser_unexpected_value; + } + } else { + // This is longer number + uint8_t bcdOut[100]; + const uint16_t bcdOutLen = sizeof(bcdOut); + + bignumLittleEndian_to_bcd(bcdOut, bcdOutLen, c->ptr + 1, c->len - 1); + if (!bignumLittleEndian_bcdprint(bufferUI, sizeof(bufferUI), bcdOut, bcdOutLen)) + return parser_unexpected_buffer_end; + } + + // Format number + if (intstr_to_fpstr_inplace(bufferUI, sizeof(bufferUI), decimalPlaces) == 0) { + return parser_unexpected_value; + } + + if (z_str3join(bufferUI, sizeof(bufferUI), prefix, postfix) != zxerr_ok) { + return parser_unexpected_buffer_end; + } + + if(trimTrailingZeros) { + number_inplace_trimming(bufferUI, 1); + } + + pageString(outValue, outValueLen, bufferUI, pageIdx, pageCount); + + return parser_ok; +} + +////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////// + +parser_error_t _readCallIndex(parser_context_t *c, pd_CallIndex_t *v) { + CHECK_INPUT() + + CHECK_ERROR(_readUInt8(c, &v->moduleIdx)) + CHECK_ERROR(_readUInt8(c, &v->idx)) + return parser_ok; +} + +parser_error_t _readEra(parser_context_t *c, pd_ExtrinsicEra_t *v) { + CHECK_INPUT() + // https://github.com/paritytech/substrate/blob/fc3adc87dc806237eb7371c1d21055eea1702be0/core/sr-primitives/src/generic/era.rs#L117 + + v->type = eEraImmortal; + v->period = 0; + v->phase = 0; + + uint8_t first; + CHECK_ERROR(_readUInt8(c, &first)) + if (first == 0) { return parser_ok; } + + v->type = eEraMortal; + uint64_t encoded = first; + CHECK_ERROR(_readUInt8(c, &first)) + encoded += (uint64_t) first << 8u; + + v->period = 2U << (encoded % (1u << 4u)); + uint64_t quantize_factor = (v->period >> 12u); + quantize_factor = (quantize_factor == 0 ? 1 : quantize_factor); + + v->phase = (encoded >> 4u) * quantize_factor; + + if (v->period >= 4 && v->phase < v->period) { + return parser_ok; + } + + return parser_unexpected_value; +} + +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// + +parser_error_t _readCompactIndex(parser_context_t *c, pd_CompactIndex_t *v) { + CHECK_INPUT() + CHECK_ERROR(_readCompactInt(c, &v->index)) + return parser_ok; +} + +parser_error_t _readCompactBalance(parser_context_t *c, pd_CompactBalance_t *v) { + CHECK_INPUT() + CHECK_ERROR(_readCompactInt(c, &v->value)) + return parser_ok; +} + +parser_error_t _toStringCompactIndex(const pd_CompactIndex_t *v, + char *outValue, uint16_t outValueLen, + uint8_t pageIdx, uint8_t *pageCount) { + return _toStringCompactInt(&v->index, 0, false, "", "", outValue, outValueLen, pageIdx, pageCount); +} + +parser_error_t _toStringCompactBalance(const pd_CompactBalance_t *v, + char *outValue, uint16_t outValueLen, + uint8_t pageIdx, uint8_t *pageCount) { + CHECK_ERROR(_toStringCompactInt( + &v->value, + COIN_AMOUNT_DECIMAL_PLACES, true, "", COIN_TICKER, + outValue, outValueLen, pageIdx, pageCount)) + return parser_ok; +} + +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// + +parser_error_t _checkVersions(parser_context_t *c) { + // Methods are not length delimited so in order to retrieve the specVersion + // it is necessary to parse from the back. + // The transaction is expect to end in + // [4 bytes] specVersion + // [4 bytes] transactionVersion + // [32 bytes] genesisHash + // [32 bytes] blockHash + const uint16_t specOffsetFromBack = 4 + 4 + 32 + 32; + if (c->bufferLen < specOffsetFromBack) { + return parser_unexpected_buffer_end; + } + + uint8_t *p = (uint8_t *) (c->buffer + c->bufferLen - specOffsetFromBack); + uint32_t specVersion = 0; + specVersion += (uint32_t) p[0] << 0u; + specVersion += (uint32_t) p[1] << 8u; + specVersion += (uint32_t) p[2] << 16u; + specVersion += (uint32_t) p[3] << 24u; + + p += 4; + uint32_t transactionVersion = 0; + transactionVersion += (uint32_t) p[0] << 0u; + transactionVersion += (uint32_t) p[1] << 8u; + transactionVersion += (uint32_t) p[2] << 16u; + transactionVersion += (uint32_t) p[3] << 24u; + + if (transactionVersion != (SUPPORTED_TX_VERSION_CURRENT) && + transactionVersion != (SUPPORTED_TX_VERSION_PREVIOUS)) { + return parser_tx_version_not_supported; + } + + if (specVersion < SUPPORTED_MINIMUM_SPEC_VERSION) { + return parser_spec_not_supported; + } + + c->tx_obj->specVersion = specVersion; + c->tx_obj->transactionVersion = transactionVersion; + + return parser_ok; +} + +uint16_t __address_type; + +uint16_t _getAddressType() { + return __address_type; +} + +uint16_t _detectAddressType(const parser_context_t *c) { + char hashstr[65]; + uint8_t pc; + + if (c->tx_obj->genesisHash._ptr != NULL) { + _toStringHash(&c->tx_obj->genesisHash, hashstr, 65, 0, &pc); + + // Compare with known genesis hashes + if (strcmp(hashstr, COIN_GENESIS_HASH) == 0) { + return PK_ADDRESS_TYPE; + } + } + + return 42; +} + +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// + +parser_error_t _readAddress(parser_context_t *c, pd_Address_t *v) { + CHECK_INPUT() + // Based on + // https://github.com/paritytech/substrate/blob/fc3adc87dc806237eb7371c1d21055eea1702be0/srml/indices/src/address.rs#L66 + + uint8_t tmp; + CHECK_ERROR(_readUInt8(c, &tmp)) + + switch (tmp) { + case 0xFF: { + v->type = eAddressId; + v->idPtr = c->buffer + c->offset; + CTX_CHECK_AND_ADVANCE(c, 32) + break; + } + case 0xFE: { + compactInt_t ci; + CHECK_ERROR(_readCompactInt(c, &ci)) + + v->type = eAddressIndex; + CHECK_ERROR(_getValue(&ci, &v->idx)) + + if (v->idx <= 0xffffffffu) { + return parser_unexpected_value; + } + break; + } + case 0xFD: { + uint32_t tmpval; + CHECK_ERROR(_readUInt32(c, &tmpval)) + v->type = eAddressIndex; + v->idx = tmpval; + if (v->idx <= 0xFFFF) { + return parser_unexpected_value; + } + break; + } + case 0xFC: { + uint16_t tmpval; + CHECK_ERROR(_readUInt16(c, &tmpval)) + v->type = eAddressIndex; + v->idx = tmpval; + if (v->idx <= 0xEF) { + return parser_unexpected_value; + } + break; + } + default: + if (tmp <= 0xEF) { + v->type = eAddressIndex; + v->idx = tmp; + return parser_ok; + } + + return parser_unexpected_value; + } + + return parser_ok; +} + +parser_error_t _toStringPubkeyAsAddress(const uint8_t *pubkey, + char *outValue, uint16_t outValueLen, + uint8_t pageIdx, uint8_t *pageCount) { + char bufferUI[200]; + + if (crypto_SS58EncodePubkey((uint8_t *) bufferUI, sizeof(bufferUI), __address_type, pubkey) == 0) { + return parser_no_data; + } + + pageString(outValue, outValueLen, bufferUI, pageIdx, pageCount); + if (pageIdx >= *pageCount) { + return parser_no_data; + } + return parser_ok; +} + +parser_error_t _toStringAddress(const pd_Address_t *v, + char *outValue, uint16_t outValueLen, + uint8_t pageIdx, uint8_t *pageCount) { + MEMZERO(outValue, outValueLen); + if (v == NULL) { + return parser_no_data; + } + + *pageCount = 1; + switch (v->type) { + case eAddressIndex: + return parser_not_supported; + case eAddressId: { + return _toStringPubkeyAsAddress(v->idPtr, + outValue, outValueLen, + pageIdx, pageCount); + } + } + + return parser_ok; +} diff --git a/app/src/parser_txdef.h b/app/src/parser_txdef.h index 051e630..a66f54b 100644 --- a/app/src/parser_txdef.h +++ b/app/src/parser_txdef.h @@ -23,7 +23,7 @@ extern "C" { #include #include "substrate_methods.h" -#if defined(TARGET_NANOX) || defined(TARGET_NANOS2) +#if defined(TARGET_NANOX) || defined(TARGET_NANOS2) || defined(TARGET_STAX) #define MAX_CALL_NESTING_SIZE 6 #define MAX_CALL_VEC_SIZE 6 #else diff --git a/app/src/ristretto.c b/app/src/ristretto.c index 5953134..af4ca1d 100644 --- a/app/src/ristretto.c +++ b/app/src/ristretto.c @@ -16,7 +16,6 @@ #include #include "ristretto.h" #include "cx.h" -#include "rslib.h" unsigned char const ED25519_GEN[ED25519_SDKPOINT_BYTES] = { //uncompressed @@ -58,24 +57,24 @@ const fe25519_sdk ed25519_invsqrtamd_sdk = { 0x5a, 0x41, 0x72, 0xbe, 0x99, 0xc8, 0xfd, 0xaa, 0x80, 0x5d, 0x40, 0xea }; -void fe25519_add_sdk(fe25519_sdk h, const fe25519_sdk f, const fe25519_sdk g) +static cx_err_t fe25519_add_sdk(fe25519_sdk h, const fe25519_sdk f, const fe25519_sdk g) { - cx_math_addm(h, f, g, ED25519_FIELD_SIZE, ED25519_SCALAR_BYTES); + return cx_math_addm_no_throw(h, f, g, ED25519_FIELD_SIZE, ED25519_SCALAR_BYTES); } -void fe25519_sub_sdk(fe25519_sdk h, const fe25519_sdk f, const fe25519_sdk g) +static cx_err_t fe25519_sub_sdk(fe25519_sdk h, const fe25519_sdk f, const fe25519_sdk g) { - cx_math_subm(h, f, g, ED25519_FIELD_SIZE, ED25519_SCALAR_BYTES); + return cx_math_subm_no_throw(h, f, g, ED25519_FIELD_SIZE, ED25519_SCALAR_BYTES); } -void fe25519_mul_sdk(fe25519_sdk h, const fe25519_sdk f, const fe25519_sdk g) +static cx_err_t fe25519_mul_sdk(fe25519_sdk h, const fe25519_sdk f, const fe25519_sdk g) { - cx_math_multm(h, f, g, ED25519_FIELD_SIZE, ED25519_SCALAR_BYTES); + return cx_math_multm_no_throw(h, f, g, ED25519_FIELD_SIZE, ED25519_SCALAR_BYTES); } -void fe25519_sq_sdk(fe25519_sdk h, const fe25519_sdk f) +static cx_err_t fe25519_sq_sdk(fe25519_sdk h, const fe25519_sdk f) { - cx_math_multm(h, f, f, ED25519_FIELD_SIZE, ED25519_SCALAR_BYTES); + return cx_math_multm_no_throw(h, f, f, ED25519_FIELD_SIZE, ED25519_SCALAR_BYTES); } void fe25519_1_sdk(fe25519_sdk h) @@ -94,22 +93,22 @@ int fe25519_isnegative_sdk(const fe25519_sdk f) return f[ED25519_SCALAR_BYTES-1] & 1; } -void fe25519_neg_sdk(fe25519_sdk h, const fe25519_sdk f) +static cx_err_t fe25519_neg_sdk(fe25519_sdk h, const fe25519_sdk f) { - cx_math_subm(h, ED25519_FIELD_ZERO, f, ED25519_FIELD_SIZE, ED25519_SCALAR_BYTES); + return cx_math_subm_no_throw(h, ED25519_FIELD_ZERO, f, ED25519_FIELD_SIZE, ED25519_SCALAR_BYTES); } void fe25519_copy_sdk(const fe25519_sdk h, const fe25519_sdk f) { - MEMCPY((void *) h, f, sizeof(fe25519_sdk)); + memcpy((void *) h, f, sizeof(fe25519_sdk)); } //TODO: check this for constant-time void fe25519_cmov_sdk(fe25519_sdk f, const fe25519_sdk g, unsigned int b) { uint8_t mask = (uint8_t) (-(int8_t) b); - uint8_t h[ED25519_SCALAR_BYTES]; - uint8_t x[ED25519_SCALAR_BYTES]; + uint8_t h[ED25519_SCALAR_BYTES] = {0}; + uint8_t x[ED25519_SCALAR_BYTES] = {0}; for(int i = 0; i < ED25519_SCALAR_BYTES; i++){ h[i] = f[i]; x[i] = h[i] ^ g[i]; @@ -118,68 +117,70 @@ void fe25519_cmov_sdk(fe25519_sdk f, const fe25519_sdk g, unsigned int b) } } -void fe25519_cneg_sdk(fe25519_sdk h, const fe25519_sdk f, unsigned int b) +static cx_err_t fe25519_cneg_sdk(fe25519_sdk h, const fe25519_sdk f, unsigned int b) { fe25519_sdk negf; - fe25519_neg_sdk(negf, f); + CHECK_CXERROR(fe25519_neg_sdk(negf, f)) fe25519_copy_sdk(h, f); fe25519_cmov_sdk(h, negf, b); + + return CX_OK; } -void fe25519_abs_sdk(fe25519_sdk h, const fe25519_sdk f) +static cx_err_t fe25519_abs_sdk(fe25519_sdk h, const fe25519_sdk f) { - fe25519_cneg_sdk(h, f, fe25519_isnegative_sdk(f)); + return fe25519_cneg_sdk(h, f, fe25519_isnegative_sdk(f)); } void fe25519_tobytes_sdk(unsigned char *s, const fe25519_sdk f) { uint8_t tmp = 0; - MEMCPY(s, f, sizeof(fe25519_sdk)); + memcpy(s, f, sizeof(fe25519_sdk)); SWAP_ENDIAN(&s[0], tmp); } -void fe25519_pow22523_sdk(fe25519_sdk out, const fe25519_sdk z) +static cx_err_t fe25519_pow22523_sdk(fe25519_sdk out, const fe25519_sdk z) { - cx_math_powm(out, z, ED25519_POW225, ED25519_SCALAR_BYTES, ED25519_FIELD_SIZE, ED25519_SCALAR_BYTES); + return cx_math_powm_no_throw(out, z, ED25519_POW225, ED25519_SCALAR_BYTES, ED25519_FIELD_SIZE, ED25519_SCALAR_BYTES); } -int ristretto255_sqrt_ratio_m1_sdk(fe25519_sdk x, const fe25519_sdk u, const fe25519_sdk v) +static cx_err_t ristretto255_sqrt_ratio_m1_sdk(fe25519_sdk x, const fe25519_sdk u, const fe25519_sdk v) { fe25519_sdk v3; fe25519_sdk vxx; fe25519_sdk m_root_check, p_root_check, f_root_check; fe25519_sdk x_sqrtm1; - int has_m_root, has_p_root, has_f_root; - - fe25519_sq_sdk(v3, v); - fe25519_mul_sdk(v3, v3, v); /* v3 = v^3 */ - fe25519_sq_sdk(x, v3); - fe25519_mul_sdk(x, x, u); - fe25519_mul_sdk(x, x, v); /* x = uv^7 */ - - fe25519_pow22523_sdk(x, x); /* x = (uv^7)^((q-5)/8) */ - fe25519_mul_sdk(x, x, v3); - fe25519_mul_sdk(x, x, u); /* x = uv^3(uv^7)^((q-5)/8) */ - - fe25519_sq_sdk(vxx, x); - fe25519_mul_sdk(vxx, vxx, v); /* vx^2 */ - fe25519_sub_sdk(m_root_check, vxx, u); /* vx^2-u */ - fe25519_add_sdk(p_root_check, vxx, u); /* vx^2+u */ - fe25519_mul_sdk(f_root_check, u, fe25519_sqrtm1_sdk); /* u*sqrt(-1) */ - fe25519_add_sdk(f_root_check, vxx, f_root_check); /* vx^2+u*sqrt(-1) */ - has_m_root = fe25519_iszero_sdk(m_root_check); + int has_p_root, has_f_root; + + CHECK_CXERROR(fe25519_sq_sdk(v3, v)) + CHECK_CXERROR(fe25519_mul_sdk(v3, v3, v)) /* v3 = v^3 */ + CHECK_CXERROR(fe25519_sq_sdk(x, v3)) + CHECK_CXERROR(fe25519_mul_sdk(x, x, u)) + CHECK_CXERROR(fe25519_mul_sdk(x, x, v)) /* x = uv^7 */ + + CHECK_CXERROR(fe25519_pow22523_sdk(x, x)) /* x = (uv^7)^((q-5)/8) */ + CHECK_CXERROR(fe25519_mul_sdk(x, x, v3)) + CHECK_CXERROR(fe25519_mul_sdk(x, x, u)) /* x = uv^3(uv^7)^((q-5)/8) */ + + CHECK_CXERROR(fe25519_sq_sdk(vxx, x)) + CHECK_CXERROR(fe25519_mul_sdk(vxx, vxx, v)) /* vx^2 */ + CHECK_CXERROR(fe25519_sub_sdk(m_root_check, vxx, u)) /* vx^2-u */ + CHECK_CXERROR(fe25519_add_sdk(p_root_check, vxx, u)) /* vx^2+u */ + CHECK_CXERROR(fe25519_mul_sdk(f_root_check, u, fe25519_sqrtm1_sdk)) /* u*sqrt(-1) */ + CHECK_CXERROR(fe25519_add_sdk(f_root_check, vxx, f_root_check)) /* vx^2+u*sqrt(-1) */ + fe25519_iszero_sdk(m_root_check); has_p_root = fe25519_iszero_sdk(p_root_check); has_f_root = fe25519_iszero_sdk(f_root_check); - fe25519_mul_sdk(x_sqrtm1,x, fe25519_sqrtm1_sdk); + CHECK_CXERROR(fe25519_mul_sdk(x_sqrtm1,x, fe25519_sqrtm1_sdk)) fe25519_cmov_sdk(x, x_sqrtm1, has_p_root | has_f_root); - fe25519_abs_sdk(x, x); + CHECK_CXERROR(fe25519_abs_sdk(x, x)) - return has_m_root | has_p_root; + return CX_OK; } -void ristretto255_p3_tobytes_sdk(fe25519_sdk s, const ge25519_p3_sdk *h) +static cx_err_t ristretto255_p3_tobytes_sdk(fe25519_sdk s, const ge25519_p3_sdk *h) { fe25519_sdk den1, den2; fe25519_sdk den_inv; @@ -197,28 +198,28 @@ void ristretto255_p3_tobytes_sdk(fe25519_sdk s, const ge25519_p3_sdk *h) fe25519_sdk zmy; int rotate; - fe25519_add_sdk(u1, h->Z, h->Y); /* u1 = Z+Y */ - fe25519_sub_sdk(zmy, h->Z, h->Y); /* zmy = Z-Y */ - fe25519_mul_sdk(u1, u1, zmy); /* u1 = (Z+Y)*(Z-Y) */ - fe25519_mul_sdk(u2, h->X, h->Y); /* u2 = X*Y */ + CHECK_CXERROR(fe25519_add_sdk(u1, h->Z, h->Y)) /* u1 = Z+Y */ + CHECK_CXERROR(fe25519_sub_sdk(zmy, h->Z, h->Y)) /* zmy = Z-Y */ + CHECK_CXERROR(fe25519_mul_sdk(u1, u1, zmy)) /* u1 = (Z+Y)*(Z-Y) */ + CHECK_CXERROR(fe25519_mul_sdk(u2, h->X, h->Y)) /* u2 = X*Y */ - fe25519_sq_sdk(u1_u2u2, u2); /* u1_u2u2 = u2^2 */ - fe25519_mul_sdk(u1_u2u2, u1, u1_u2u2); /* u1_u2u2 = u1*u2^2 */ + CHECK_CXERROR(fe25519_sq_sdk(u1_u2u2, u2)) /* u1_u2u2 = u2^2 */ + CHECK_CXERROR(fe25519_mul_sdk(u1_u2u2, u1, u1_u2u2)) /* u1_u2u2 = u1*u2^2 */ fe25519_1_sdk(one); - ristretto255_sqrt_ratio_m1_sdk(inv_sqrt, one, u1_u2u2); + CHECK_CXERROR(ristretto255_sqrt_ratio_m1_sdk(inv_sqrt, one, u1_u2u2)) - fe25519_mul_sdk(den1, inv_sqrt, u1); - fe25519_mul_sdk(den2, inv_sqrt, u2); - fe25519_mul_sdk(z_inv, den1, den2); - fe25519_mul_sdk(z_inv, z_inv, h->T); + CHECK_CXERROR(fe25519_mul_sdk(den1, inv_sqrt, u1)) + CHECK_CXERROR(fe25519_mul_sdk(den2, inv_sqrt, u2)) + CHECK_CXERROR(fe25519_mul_sdk(z_inv, den1, den2)) + CHECK_CXERROR(fe25519_mul_sdk(z_inv, z_inv, h->T)) - fe25519_mul_sdk(ix, h->X, fe25519_sqrtm1_sdk); - fe25519_mul_sdk(iy, h->Y, fe25519_sqrtm1_sdk); + CHECK_CXERROR(fe25519_mul_sdk(ix, h->X, fe25519_sqrtm1_sdk)) + CHECK_CXERROR(fe25519_mul_sdk(iy, h->Y, fe25519_sqrtm1_sdk)) - fe25519_mul_sdk(eden, den1, ed25519_invsqrtamd_sdk); + CHECK_CXERROR(fe25519_mul_sdk(eden, den1, ed25519_invsqrtamd_sdk)) - fe25519_mul_sdk(t_z_inv, h->T, z_inv); + CHECK_CXERROR(fe25519_mul_sdk(t_z_inv, h->T, z_inv)) rotate = fe25519_isnegative_sdk(t_z_inv); fe25519_copy_sdk(x_, h->X); @@ -230,16 +231,18 @@ void ristretto255_p3_tobytes_sdk(fe25519_sdk s, const ge25519_p3_sdk *h) fe25519_cmov_sdk(y_, ix, rotate); fe25519_cmov_sdk(den_inv, eden, rotate); - fe25519_mul_sdk(x_z_inv, x_, z_inv); - fe25519_cneg_sdk(y_, y_, fe25519_isnegative_sdk(x_z_inv)); + CHECK_CXERROR(fe25519_mul_sdk(x_z_inv, x_, z_inv)) + CHECK_CXERROR(fe25519_cneg_sdk(y_, y_, fe25519_isnegative_sdk(x_z_inv))) + + CHECK_CXERROR(fe25519_sub_sdk(s_, h->Z, y_)) + CHECK_CXERROR(fe25519_mul_sdk(s_, den_inv, s_)) + CHECK_CXERROR(fe25519_abs_sdk(s, s_)) - fe25519_sub_sdk(s_, h->Z, y_); - fe25519_mul_sdk(s_, den_inv, s_); - fe25519_abs_sdk(s, s_); + return CX_OK; } -int crypto_scalarmult_ristretto255_base_sdk(unsigned char *q,const unsigned char *n) +cx_err_t crypto_scalarmult_ristretto255_base_sdk(unsigned char *q,const unsigned char *n) { unsigned char *t = q; unsigned int i; @@ -250,24 +253,24 @@ int crypto_scalarmult_ristretto255_base_sdk(unsigned char *q,const unsigned char uint8_t Pxy[ED25519_SDKPOINT_BYTES]; memcpy(Pxy, ED25519_GEN, sizeof(Pxy)); - cx_ecfp_scalar_mult(CX_CURVE_Ed25519, Pxy, sizeof(Pxy), t, ED25519_SCALAR_BYTES); + CHECK_CXERROR(cx_ecfp_scalar_mult_no_throw(CX_CURVE_Ed25519, Pxy, t, ED25519_SCALAR_BYTES)) ge25519_p3_sdk Q_sdk; MEMZERO(&Q_sdk, sizeof(ge25519_p3_sdk)); - MEMCPY(Q_sdk.X, &Pxy[1],ED25519_SCALAR_BYTES); - MEMCPY(Q_sdk.Y, &Pxy[1+ED25519_SCALAR_BYTES],ED25519_SCALAR_BYTES); + memcpy(Q_sdk.X, &Pxy[1],ED25519_SCALAR_BYTES); + memcpy(Q_sdk.Y, &Pxy[1+ED25519_SCALAR_BYTES],ED25519_SCALAR_BYTES); fe25519_1_sdk(Q_sdk.Z); - fe25519_mul_sdk(Q_sdk.T, Q_sdk.X,Q_sdk.Y); + CHECK_CXERROR(fe25519_mul_sdk(Q_sdk.T, Q_sdk.X,Q_sdk.Y)) fe25519_sdk s; - ristretto255_p3_tobytes_sdk(s, &Q_sdk); + CHECK_CXERROR(ristretto255_p3_tobytes_sdk(s, &Q_sdk)) if (fe25519_iszero_sdk(s)) { - return -1; + return CX_INTERNAL_ERROR; } fe25519_tobytes_sdk(q,s); - return 0; -} \ No newline at end of file + return CX_OK; +} diff --git a/app/src/ristretto.h b/app/src/ristretto.h index abfa9f2..821efc4 100644 --- a/app/src/ristretto.h +++ b/app/src/ristretto.h @@ -39,5 +39,5 @@ typedef struct { } \ } -void ristretto255_p3_tobytes_sdk(unsigned char *s, const ge25519_p3_sdk *h); -int crypto_scalarmult_ristretto255_base_sdk(unsigned char *q, const unsigned char *n); \ No newline at end of file + +cx_err_t crypto_scalarmult_ristretto255_base_sdk(unsigned char *q, const unsigned char *n); diff --git a/app/src/secret.c b/app/src/secret.c index 78f0c8e..a76bd0c 100644 --- a/app/src/secret.c +++ b/app/src/secret.c @@ -30,11 +30,20 @@ void secret_accept() { #endif } -static char *secret_message = +#if defined(TARGET_STAX) +static const char *secret_message = + "You are about to enable EDG\n" + "recovery mode. Activating this\n" + "mode will temporarily allow\n" + "you to sign transactions\n" + "using Kusama keys."; +#else +static const char *secret_message = "USE AT YOUR OWN RISK!! " - "You are about to enable the KSM recovery mode." + "You are about to enable the EDG recovery mode." "If you are not sure why you are here, reject or unplug your device immediately." - "Activating this mode will temporarily allow you to sign transactions using Polkadot keys"; + "Activating this mode will temporarily allow you to sign transactions using Kusama keys"; +#endif zxerr_t secret_getNumItems(uint8_t *num_items) { zemu_log_stack("secret_getNumItems"); @@ -57,9 +66,17 @@ zxerr_t secret_getItem(int8_t displayIdx, zxerr_t secret_enabled() { #ifdef APP_SECRET_MODE_ENABLED - zemu_log("RECOVERY TRIGGERED"); - view_review_init(secret_getItem, secret_getNumItems, secret_accept); - view_review_show(); + if (app_mode_secret()) { + zemu_log("DISABLE RECOVERY"); + app_mode_set_secret(false); +#if !defined(TARGET_STAX) + view_idle_show(0, NULL); +#endif + } else { + zemu_log("RECOVERY TRIGGERED"); + view_review_init(secret_getItem, secret_getNumItems, secret_accept); + view_review_show(0x00); + } #endif return zxerr_ok; } diff --git a/app/src/substrate/substrate_coin.h b/app/src/substrate/substrate_coin.h new file mode 100644 index 0000000..5fc6baa --- /dev/null +++ b/app/src/substrate/substrate_coin.h @@ -0,0 +1,76 @@ +/******************************************************************************* + * (c) 2019 - 2023 Zondax AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ********************************************************************************/ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#define COIN_ADDR_TYPE 7 +#define CLA 0x94 + +#define INS_SIGN_RAW 0x03 + +#define HDPATH_LEN_DEFAULT 5 +#define HDPATH_0_DEFAULT (0x80000000 | 0x2c) +#define HDPATH_1_DEFAULT (0x80000000 | 0x20b) +#define HDPATH_1_RECOVERY (0x80000000 | 0x1b2) + +#define HDPATH_2_DEFAULT (0x80000000u | 0u) +#define HDPATH_3_DEFAULT (0u) +#define HDPATH_4_DEFAULT (0u) + +#define SK_LEN_25519 64u +#define SCALAR_LEN_ED25519 32u +#define SIG_PLUS_TYPE_LEN 65u + +#define PK_LEN_25519 32u +#define MAX_SIGN_SIZE 256u +#define BLAKE2B_DIGEST_SIZE 32u + +typedef enum { + key_ed25519 = 0, + +#if defined(SUPPORT_SR25519) + key_sr25519 = 1 +#endif + +} key_kind_e; + +// Coin Specific +#define PK_ADDRESS_TYPE COIN_ADDR_TYPE +#define SUPPORTED_TX_VERSION_CURRENT LEDGER_MAJOR_VERSION +#define SUPPORTED_TX_VERSION_PREVIOUS (LEDGER_MAJOR_VERSION - 1) +#define SUPPORTED_SPEC_VERSION (LEDGER_MINOR_VERSION + 0) +#define SUPPORTED_MINIMUM_SPEC_VERSION 46 + +#define COIN_AMOUNT_DECIMAL_PLACES 18 + +#define COIN_GENESIS_HASH "742a2ca70c2fda6cee4f8df98d64c4c670a052d9568058982dad9d5a7a135c5b" +#define COIN_NAME "Edgeware" +#define COIN_TICKER "EDG " + +#define COIN_SECRET_REQUIRED_CLICKS 0 + +#define MENU_MAIN_APP_LINE1 "Edgeware" +#define MENU_MAIN_APP_LINE2 "Ready" +#define MENU_MAIN_APP_LINE2_SECRET "EDG RECOVERY" +#define APPVERSION_LINE1 "Edgeware" +#define APPVERSION_LINE2 "v" APPVERSION + +#ifdef __cplusplus +} +#endif diff --git a/app/src/substrate_dispatch.c b/app/src/substrate/substrate_dispatch.c similarity index 100% rename from app/src/substrate_dispatch.c rename to app/src/substrate/substrate_dispatch.c diff --git a/app/src/substrate_dispatch.h b/app/src/substrate/substrate_dispatch.h similarity index 100% rename from app/src/substrate_dispatch.h rename to app/src/substrate/substrate_dispatch.h diff --git a/app/src/substrate_dispatch_V1.c b/app/src/substrate/substrate_dispatch_V1.c similarity index 100% rename from app/src/substrate_dispatch_V1.c rename to app/src/substrate/substrate_dispatch_V1.c diff --git a/app/src/substrate_dispatch_V1.h b/app/src/substrate/substrate_dispatch_V1.h similarity index 100% rename from app/src/substrate_dispatch_V1.h rename to app/src/substrate/substrate_dispatch_V1.h diff --git a/app/src/substrate_dispatch_V2.c b/app/src/substrate/substrate_dispatch_V2.c similarity index 100% rename from app/src/substrate_dispatch_V2.c rename to app/src/substrate/substrate_dispatch_V2.c diff --git a/app/src/substrate_dispatch_V2.h b/app/src/substrate/substrate_dispatch_V2.h similarity index 100% rename from app/src/substrate_dispatch_V2.h rename to app/src/substrate/substrate_dispatch_V2.h diff --git a/app/src/substrate_functions.h b/app/src/substrate/substrate_functions.h similarity index 100% rename from app/src/substrate_functions.h rename to app/src/substrate/substrate_functions.h diff --git a/app/src/substrate_functions_V1.h b/app/src/substrate/substrate_functions_V1.h similarity index 100% rename from app/src/substrate_functions_V1.h rename to app/src/substrate/substrate_functions_V1.h diff --git a/app/src/substrate_functions_V2.h b/app/src/substrate/substrate_functions_V2.h similarity index 100% rename from app/src/substrate_functions_V2.h rename to app/src/substrate/substrate_functions_V2.h diff --git a/app/src/substrate_methods.h b/app/src/substrate/substrate_methods.h similarity index 100% rename from app/src/substrate_methods.h rename to app/src/substrate/substrate_methods.h diff --git a/app/src/substrate_methods_V1.h b/app/src/substrate/substrate_methods_V1.h similarity index 100% rename from app/src/substrate_methods_V1.h rename to app/src/substrate/substrate_methods_V1.h diff --git a/app/src/substrate_methods_V2.h b/app/src/substrate/substrate_methods_V2.h similarity index 100% rename from app/src/substrate_methods_V2.h rename to app/src/substrate/substrate_methods_V2.h diff --git a/app/src/substrate_strings.h b/app/src/substrate/substrate_strings.h similarity index 100% rename from app/src/substrate_strings.h rename to app/src/substrate/substrate_strings.h diff --git a/app/src/substrate_types.c b/app/src/substrate/substrate_types.c similarity index 100% rename from app/src/substrate_types.c rename to app/src/substrate/substrate_types.c diff --git a/app/src/substrate_types.h b/app/src/substrate/substrate_types.h similarity index 100% rename from app/src/substrate_types.h rename to app/src/substrate/substrate_types.h diff --git a/app/src/substrate_types_V1.c b/app/src/substrate/substrate_types_V1.c similarity index 100% rename from app/src/substrate_types_V1.c rename to app/src/substrate/substrate_types_V1.c diff --git a/app/src/substrate_types_V1.h b/app/src/substrate/substrate_types_V1.h similarity index 100% rename from app/src/substrate_types_V1.h rename to app/src/substrate/substrate_types_V1.h diff --git a/app/src/substrate_types_V2.c b/app/src/substrate/substrate_types_V2.c similarity index 100% rename from app/src/substrate_types_V2.c rename to app/src/substrate/substrate_types_V2.c diff --git a/app/src/substrate_types_V2.h b/app/src/substrate/substrate_types_V2.h similarity index 100% rename from app/src/substrate_types_V2.h rename to app/src/substrate/substrate_types_V2.h diff --git a/app/src/swap.c b/app/src/swap.c new file mode 100644 index 0000000..11832e6 --- /dev/null +++ b/app/src/swap.c @@ -0,0 +1,296 @@ +/******************************************************************************* +* (c) 2016 Ledger +* (c) 2018 - 2023 Zondax AG +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +#include "swap.h" +#include "crypto.h" +#include "bignum.h" +#include "zxformat.h" +#include "substrate_dispatch.h" + +static zxerr_t extractHDPath(uint8_t* params, uint8_t paramsSize); +static zxerr_t readU32BE(uint8_t* input, uint32_t *output); +static zxerr_t readU64BE(uint8_t* input, uint64_t *output); +static zxerr_t bytesAmountToStringBalance(uint8_t *amount, uint8_t amount_len, char *out, uint8_t out_len); + +static void handle_check_address(check_address_parameters_t* params); +static void handle_get_printable_amount(get_printable_amount_parameters_t* params); + +static bool copy_transaction_parameters(create_transaction_parameters_t* sign_transaction_params); + +swap_globals_t G_swap_state; + +void swap_handle_command(libargs_s *args) { + switch (args->command) { + case check_address: + handle_check_address(args->check_address); + G_swap_state.called_from_swap = 0; + break; + + case sign_transaction: + if (copy_transaction_parameters(args->create_transaction)) { + G_swap_state.called_from_swap = 1; + } else { + G_swap_state.called_from_swap = 0; + } + break; + + case get_printable_amount: + handle_get_printable_amount(args->get_printable_amount); + G_swap_state.called_from_swap = 0; + break; + + case run_application: + default: + break; + } +} + +void handle_check_address(check_address_parameters_t* params) { + if (params == NULL) { + return; + } + params->result = 0; + uint8_t buffer[100] = {0}; + uint16_t replyLen = 0; + + // address parameters have the following structure + // address kind (1 byte) | path length (1 byte) | bip44 path (4 * pathLength bytes) + const key_kind_e add_kind = (key_kind_e) *params->address_parameters; + zxerr_t err = extractHDPath((uint8_t*) params->address_parameters + 2, (uint8_t) params->address_parameters_length - 2); + if (params->address_to_check == 0 || err != zxerr_ok) { + return; + } + + err = crypto_fillAddress(add_kind, buffer, sizeof(buffer), &replyLen); + + if (err != zxerr_ok || replyLen <= PK_LEN_25519) { + MEMZERO(buffer, sizeof(buffer)); + return; + } + + const uint8_t *address = buffer + PK_LEN_25519; + const uint8_t addressLen = replyLen - PK_LEN_25519; + const uint8_t addressToCheckLen = strlen(params->address_to_check); + + if (addressLen == addressToCheckLen && + memcmp(address, params->address_to_check, addressLen) == 0) { + params->result = 1; + } +} + +void handle_get_printable_amount( get_printable_amount_parameters_t* params) { + if (params == NULL) { + return; + } + uint8_t amount[16]; + + MEMZERO(amount, sizeof(amount)); + MEMZERO(params->printable_amount, sizeof(params->printable_amount)); + + if (params->amount_length > 16) { + return; + } + + memcpy(amount + 16 - params->amount_length, params->amount, params->amount_length); + + char tmp_amount[100] = {0}; + const zxerr_t zxerr = bytesAmountToStringBalance(amount, sizeof(amount), tmp_amount, sizeof(tmp_amount)); + + if (zxerr != zxerr_ok || strnlen(tmp_amount, sizeof(tmp_amount)) > sizeof(params->printable_amount)) { + return; + } + strncpy(params->printable_amount, tmp_amount, sizeof(params->printable_amount) - 1); +} + +bool copy_transaction_parameters(create_transaction_parameters_t* sign_transaction_params) { + if (sign_transaction_params == NULL) { + return false; + } + + // First copy parameters to stack, and then to global data. + // We need this "trick" as the input data position can overlap with globals + char destination_address[65] = {0}; + uint8_t amount[16] = {0}; + uint8_t amount_length = {0}; + uint8_t fees[8] = {0}; + + strncpy(destination_address, + sign_transaction_params->destination_address, + sizeof(destination_address) - 1); + + if ((destination_address[sizeof(destination_address) - 1] != '\0') || + (sign_transaction_params->amount_length > 16) || + (sign_transaction_params->fee_amount_length > 8)) { + return false; + } + + // store amount as big endian in 16 bytes, so the passed data should be alligned to right + // input {0xEE, 0x00, 0xFF} should be stored like {0x00, 0x00, 0x00, 0x00, 0x00, 0xEE, 0x00, 0xFF} + memcpy(amount + 16 - sign_transaction_params->amount_length, + sign_transaction_params->amount, + sign_transaction_params->amount_length); + + memcpy(fees + 8 - sign_transaction_params->fee_amount_length, + sign_transaction_params->fee_amount, + sign_transaction_params->fee_amount_length); + + amount_length = sign_transaction_params->amount_length; + + MEMZERO(&G_swap_state, sizeof(G_swap_state)); + + G_swap_state.amount_length = amount_length; + memcpy(G_swap_state.amount,amount,sizeof(amount)); + memcpy(G_swap_state.destination_address, + destination_address, + sizeof(G_swap_state.destination_address)); + readU64BE(fees, &G_swap_state.fees); + + return true; +} + +parser_error_t check_swap_conditions(const parser_context_t *ctx) { + parser_error_t err = parser_unexpected_error; + if (ctx == NULL) { + return err; + } + // Check method. + const char * valid_tx_method = "Balances Transfer"; + char tmp_str[80] = {0}; + snprintf(tmp_str, sizeof(tmp_str), "%s %s", _getMethod_ModuleName(ctx->tx_obj->transactionVersion, + ctx->tx_obj->callIndex.moduleIdx), + _getMethod_Name(ctx->tx_obj->transactionVersion, + ctx->tx_obj->callIndex.moduleIdx, + ctx->tx_obj->callIndex.idx)); + + if (strncmp(tmp_str, &valid_tx_method[0], strlen(valid_tx_method)) != 0) { + PRINTF("Wrong swap tx method (%s, should be : %s).\n",tmp_str,valid_tx_method); + ZEMU_LOGF(100, "Wrong swap tx method (%s, should be : %s).\n",tmp_str,valid_tx_method) + return parser_swap_tx_wrong_method; + } + + // Check transaction method arguments number. Balance transfer Should be 2 (for tx v18). + if (_getMethod_NumItems(ctx->tx_obj->transactionVersion, + ctx->tx_obj->callIndex.moduleIdx, + ctx->tx_obj->callIndex.idx) != 2) { + ZEMU_LOGF(50, "Wrong swap tx method arguments count.\n") + return parser_swap_tx_wrong_method_args_num; + } + + // Check destination address. + MEMZERO(tmp_str,sizeof(tmp_str)); + uint8_t pageCount = 0; + err = _getMethod_ItemValue(ctx->tx_obj->transactionVersion, + &ctx->tx_obj->method, + ctx->tx_obj->callIndex.moduleIdx, ctx->tx_obj->callIndex.idx, 0, + tmp_str, sizeof(tmp_str), + 0, &pageCount); + + if (err != parser_ok) { + ZEMU_LOGF(50, "Could not parse swap tx destination address."); + return err; + } + + if (strncmp(tmp_str, &(G_swap_state.destination_address[0]), sizeof(G_swap_state.destination_address)) != 0) { + ZEMU_LOGF(100, "Wrong swap tx destination address (%s, should be : %s).\n", tmp_str, G_swap_state.destination_address) + return parser_swap_tx_wrong_dest_addr; + } + + // Check amount. + MEMZERO(tmp_str, sizeof(tmp_str)); + err = _getMethod_ItemValue(ctx->tx_obj->transactionVersion, + &ctx->tx_obj->method, + ctx->tx_obj->callIndex.moduleIdx, ctx->tx_obj->callIndex.idx, 1, + tmp_str, sizeof(tmp_str), + 0, &pageCount); + + if(err != parser_ok) + { + ZEMU_LOGF(50, "Could not parse swap tx amount."); + return err; + } + + char tmp_amount[100] = {0}; + const zxerr_t zxerr = bytesAmountToStringBalance(G_swap_state.amount, sizeof(G_swap_state.amount), tmp_amount, sizeof(tmp_amount)); + + const size_t strLen = strlen(tmp_str); + const size_t amountLen = strlen(tmp_amount); + if (zxerr != zxerr_ok || strLen != amountLen || strncmp(tmp_str, tmp_amount, strLen)) { + ZEMU_LOGF(100, "Wrong swap tx amount (%s, should be : %s).\n", tmp_str, tmp_amount) + return parser_swap_tx_wrong_amount; + } + + ZEMU_LOGF(50, "Swap parameters verified by current tx\n") + return err; +} + + +//////////////////////////////////////////////////////////////// +zxerr_t extractHDPath(uint8_t* params, uint8_t paramsSize) { + if (paramsSize != (sizeof(uint32_t) * HDPATH_LEN_DEFAULT)) { + return zxerr_invalid_crypto_settings; + } + + for (uint32_t i = 0; i < HDPATH_LEN_DEFAULT; i++) { + CHECK_ZXERR(readU32BE(params + (i * 4), &hdPath[i])) + } + + return zxerr_ok; +} + +zxerr_t readU32BE(uint8_t* input, uint32_t *output) { + if (input == NULL || output == NULL) { + return zxerr_no_data; + } + + *output = 0; + for(uint8_t i = 0; i < 4; i++) { + *output += (uint32_t) *(input + i) << (32 - (8*(i+1))); + } + return zxerr_ok; +} + +zxerr_t readU64BE(uint8_t* input, uint64_t *output) { + if (input == NULL || output == NULL) { + return zxerr_no_data; + } + + *output = 0; + for(uint8_t i = 0; i < 8; i++) { + *output += (uint64_t) *(input + i) << (64 - (8*(i+1))); + } + return zxerr_ok; +} + +zxerr_t bytesAmountToStringBalance(uint8_t *amount, uint8_t amount_len, char *out, uint8_t out_len) { + uint8_t tmpBuf[50] = {0}; + + //Convert byte array (up to 128bits/16bytes) to decimal string + bignumBigEndian_to_bcd(tmpBuf, sizeof(tmpBuf), amount, amount_len); + bignumBigEndian_bcdprint(out, out_len, tmpBuf, sizeof(tmpBuf)); + + // Format number. + if (!intstr_to_fpstr_inplace(out, out_len, COIN_AMOUNT_DECIMAL_PLACES)) { + return zxerr_encoding_failed; + } + + // Add ticker prefix. + CHECK_ZXERR(z_str3join(out, out_len, COIN_TICKER, "")) + + // Trim trailing zeros + number_inplace_trimming(out, 1); + + return zxerr_ok; +} diff --git a/app/src/swap.h b/app/src/swap.h new file mode 100644 index 0000000..eff8d5d --- /dev/null +++ b/app/src/swap.h @@ -0,0 +1,100 @@ +/******************************************************************************* +* (c) 2016 Ledger +* (c) 2018 - 2023 Zondax AG +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +#pragma once + +#include "stdbool.h" +#include "stdint.h" +#include "parser_common.h" + +#define RUN_APPLICATION 1 +#define SIGN_TRANSACTION 2 +#define CHECK_ADDRESS 3 +#define GET_PRINTABLE_AMOUNT 4 + +typedef enum { + run_application = 1, + sign_transaction, + check_address, + get_printable_amount, +} swap_options_e; + +// structure that should be send to specific coin application to get address +typedef struct { + // IN + unsigned char* coin_configuration; + unsigned char coin_configuration_length; + // serialized path, segwit, version prefix, hash used, dictionary etc. + // fields and serialization format depends on spesific coin app + unsigned char* address_parameters; + unsigned char address_parameters_length; + char *address_to_check; + char *extra_id_to_check; + // OUT + int result; +} check_address_parameters_t; + +// structure that should be send to specific coin application to get printable amount +typedef struct { + // IN + unsigned char* coin_configuration; + unsigned char coin_configuration_length; + unsigned char* amount; + unsigned char amount_length; + bool is_fee; + // OUT + char printable_amount[30]; + // int result; +} get_printable_amount_parameters_t; + +typedef struct { + unsigned char* coin_configuration; + unsigned char coin_configuration_length; + unsigned char* amount; + unsigned char amount_length; + unsigned char* fee_amount; + unsigned char fee_amount_length; + char *destination_address; + char *destination_address_extra_id; +} create_transaction_parameters_t; + + +typedef struct { + uint8_t amount[16]; + uint8_t amount_length; + uint64_t fees; + char destination_address[65]; + /*Is swap mode*/ + unsigned char called_from_swap; + unsigned char should_exit; +} swap_globals_t; + + +typedef struct { + unsigned int id; + unsigned int command; + void *coin_config_legacy; // This is unused but kept for compatibility + union { + check_address_parameters_t *check_address; + create_transaction_parameters_t *create_transaction; + get_printable_amount_parameters_t *get_printable_amount; + }; +} libargs_s; + + +extern swap_globals_t G_swap_state; +void swap_handle_command(libargs_s *args); +parser_error_t check_swap_conditions(const parser_context_t *ctx); diff --git a/app/stax_icon.gif b/app/stax_icon.gif new file mode 100644 index 0000000..6a7df8f Binary files /dev/null and b/app/stax_icon.gif differ diff --git a/docs/APDUSPEC.md b/docs/APDUSPEC.md index e0ee07b..c84da3b 100644 --- a/docs/APDUSPEC.md +++ b/docs/APDUSPEC.md @@ -113,7 +113,7 @@ The general structure of commands and responses is as follows: | Field | Type | Content | Note | | ------- | --------- | ----------- | ------------------------ | | PK | byte (32) | Public Key | | -| ADDR | byte (??) | DOT address | | +| ADDR | byte (??) | EDG address | | | SW1-SW2 | byte (2) | Return code | see list of return codes | --- diff --git a/tests_zemu/package.json b/tests_zemu/package.json index 6d4fa6d..ce112e4 100644 --- a/tests_zemu/package.json +++ b/tests_zemu/package.json @@ -14,29 +14,29 @@ "Ledger" ], "scripts": { - "test": "ts-node tests/pullImageKillOld.ts && jest -t 'Standard'", - "testSR25519": "ts-node tests/pullImageKillOld.ts && jest tests/sr25519.test.ts" + "clean": "ts-node tests/pullImageKillOld.ts", + "test": "yarn clean && jest --maxConcurrency 3" }, "dependencies": { - "@zondax/ledger-substrate": "^0.39.0", - "@zondax/zemu": "^0.34.0" + "@zondax/ledger-substrate": "^0.41.1", + "@zondax/zemu": "^0.44.0" }, "devDependencies": { - "@types/jest": "^29.2.1", + "@types/jest": "^29.5.0", "@types/ledgerhq__hw-transport": "^4.21.4", - "@typescript-eslint/eslint-plugin": "^5.7.0", - "@typescript-eslint/parser": "^5.7.0", + "@typescript-eslint/eslint-plugin": "^5.56.0", + "@typescript-eslint/parser": "^5.56.0", "blakejs": "^1.1.1", - "crypto-js": "4.1.1", + "crypto-js": "^4.1.1", "ed25519-supercop": "^2.0.1", - "eslint": "^8.25.0", - "eslint-config-prettier": "^8.3.0", + "eslint": "^8.36.0", + "eslint-config-prettier": "^8.8.0", "eslint-plugin-import": "^2.25.3", - "eslint-plugin-jest": "^27.1.2", + "eslint-plugin-jest": "^27.1.3", "eslint-plugin-prettier": "^4.0.0", - "jest": "29.2.0", + "jest": "^29.5.0", "jssha": "^3.2.0", - "prettier": "^2.5.1", + "prettier": "^2.8.6", "ts-jest": "^29.0.3", "ts-node": "^10.9.1", "typescript": "^4.5.3" diff --git a/tests_zemu/tests/common.ts b/tests_zemu/tests/common.ts index 5018e44..123a009 100644 --- a/tests_zemu/tests/common.ts +++ b/tests_zemu/tests/common.ts @@ -1,21 +1,17 @@ -import { DeviceModel } from '@zondax/zemu' +import { IDeviceModel } from '@zondax/zemu' -const Resolve = require('path').resolve +import { resolve } from 'path' export const APP_SEED = 'equip will roof matter pink blind book anxiety banner elbow sun young' -const APP_PATH_S = Resolve('../app/output/app_s.elf') -const APP_PATH_X = Resolve('../app/output/app_x.elf') -const APP_PATH_SP = Resolve('../app/output/app_s2.elf') +const APP_PATH_S = resolve('../app/output/app_s.elf') +const APP_PATH_X = resolve('../app/output/app_x.elf') +const APP_PATH_SP = resolve('../app/output/app_s2.elf') +const APP_PATH_ST = resolve('../app/output/app_stax.elf') -export const models: DeviceModel[] = [ +export const models: IDeviceModel[] = [ { name: 'nanos', prefix: 'S', path: APP_PATH_S }, { name: 'nanox', prefix: 'X', path: APP_PATH_X }, { name: 'nanosp', prefix: 'SP', path: APP_PATH_SP }, + { name: 'stax', prefix: 'ST', path: APP_PATH_ST }, ] - -export const txBasic = - '060000036fa3fc0b5aa41e86dc2ce5cb3a28cb322ad401b017c2232949f009697dce7e0b63ce64c10c05d503ae11030003d20296493500000002000000742a2ca70c2fda6cee4f8df98d64c4c670a052d9568058982dad9d5a7a135c5b742a2ca70c2fda6cee4f8df98d64c4c670a052d9568058982dad9d5a7a135c5b' - -export const txNomination = - '080508002229b353a1b15bf4743ff12fb2c660ff8edf888f6f89bb11fb9878a57435034e00240eb98ac9d5823076f4005dfade11fadd72fcf2c9b902401f882ba926d0170ad503006d0f3500000002000000742a2ca70c2fda6cee4f8df98d64c4c670a052d9568058982dad9d5a7a135c5b742a2ca70c2fda6cee4f8df98d64c4c670a052d9568058982dad9d5a7a135c5b' diff --git a/tests_zemu/tests/custom.test.ts b/tests_zemu/tests/custom.test.ts new file mode 100644 index 0000000..a6abc7e --- /dev/null +++ b/tests_zemu/tests/custom.test.ts @@ -0,0 +1,132 @@ +/** ****************************************************************************** + * (c) 2020 Zondax GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************* */ + +import Zemu, { DEFAULT_START_OPTIONS } from '@zondax/zemu' +import { newEdgewareApp } from '@zondax/ledger-substrate' +import { APP_SEED, models } from './common' + +// @ts-expect-error missing typings +import ed25519 from 'ed25519-supercop' +import { blake2bFinal, blake2bInit, blake2bUpdate } from 'blakejs' +import { txBalances_transfer, txSession_setKeys, txStaking_nominate, txProxy_proxy, txUtility_batch } from './zemu_blobs' + +const defaultOptions = { + ...DEFAULT_START_OPTIONS, + logging: true, + custom: `-s "${APP_SEED}"`, + X11: false, +} + +jest.setTimeout(180000) + +const TXNS = [ + { + name: 'balances_transfer', + blob: txBalances_transfer, + }, + { + name: 'staking_nominate', + blob: txStaking_nominate, + }, +] + +describe.each(TXNS)('Transactions', function (data) { + test.concurrent.each(models)(`Test: ${data.name}`, async function (m) { + const sim = new Zemu(m.path) + try { + await sim.start({ ...defaultOptions, model: m.name }) + const app = newEdgewareApp(sim.getTransport()) + const pathAccount = 0x80000000 + const pathChange = 0x80000000 + const pathIndex = 0x80000000 + + const txBlob = Buffer.from(data.blob, 'hex') + + const responseAddr = await app.getAddress(pathAccount, pathChange, pathIndex) + const pubKey = Buffer.from(responseAddr.pubKey, 'hex') + + // do not wait here.. we need to navigate + const signatureRequest = app.sign(pathAccount, pathChange, pathIndex, txBlob) + // Wait until we are not in the main menu + await sim.waitUntilScreenIsNot(sim.getMainMenuSnapshot()) + + await sim.compareSnapshotsAndApprove('.', `${m.prefix.toLowerCase()}-${data.name}`) + + const signatureResponse = await signatureRequest + console.log(signatureResponse) + + expect(signatureResponse.return_code).toEqual(0x9000) + expect(signatureResponse.error_message).toEqual('No errors') + + // Now verify the signature + let prehash = txBlob + if (txBlob.length > 256) { + const context = blake2bInit(32) + blake2bUpdate(context, txBlob) + prehash = Buffer.from(blake2bFinal(context)) + } + const valid = ed25519.verify(signatureResponse.signature.subarray(1), prehash, pubKey) + expect(valid).toEqual(true) + } finally { + await sim.close() + } + }) +}) + +test.concurrent.each(models)('balances transfer expert', async function (m) { + const sim = new Zemu(m.path) + try { + await sim.start({ ...defaultOptions, model: m.name }) + const app = newEdgewareApp(sim.getTransport()) + const pathAccount = 0x80000000 + const pathChange = 0x80000000 + const pathIndex = 0x80000000 + + // Change to expert mode + await sim.toggleExpertMode() + + const txBlob = Buffer.from(txBalances_transfer, 'hex') + + const responseAddr = await app.getAddress(pathAccount, pathChange, pathIndex) + const pubKey = Buffer.from(responseAddr.pubKey, 'hex') + + // do not wait here.. we need to navigate + const signatureRequest = app.sign(pathAccount, pathChange, pathIndex, txBlob) + + // Wait until we are not in the main menu + await sim.waitUntilScreenIsNot(sim.getMainMenuSnapshot()) + + await sim.compareSnapshotsAndApprove('.', `${m.prefix.toLowerCase()}-balances_transfer_expert`) + + const signatureResponse = await signatureRequest + console.log(signatureResponse) + + expect(signatureResponse.return_code).toEqual(0x9000) + expect(signatureResponse.error_message).toEqual('No errors') + + // Now verify the signature + let prehash = txBlob + if (txBlob.length > 256) { + const context = blake2bInit(32) + blake2bUpdate(context, txBlob) + prehash = Buffer.from(blake2bFinal(context)) + } + const valid = ed25519.verify(signatureResponse.signature.subarray(1), prehash, pubKey) + expect(valid).toEqual(true) + } finally { + await sim.close() + } +}) diff --git a/tests_zemu/tests/raw.test.ts b/tests_zemu/tests/raw.test.ts new file mode 100644 index 0000000..bae83c4 --- /dev/null +++ b/tests_zemu/tests/raw.test.ts @@ -0,0 +1,109 @@ +/** ****************************************************************************** + * (c) 2020 Zondax GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************* */ + +import Zemu, { DEFAULT_START_OPTIONS } from '@zondax/zemu' +import { newEdgewareApp } from '@zondax/ledger-substrate' +import { APP_SEED, models } from './common' + +// @ts-expect-error missing typings +import ed25519 from 'ed25519-supercop' +import { blake2bFinal, blake2bInit, blake2bUpdate } from 'blakejs' + +const defaultOptions = { + ...DEFAULT_START_OPTIONS, + logging: true, + custom: `-s "${APP_SEED}"`, + X11: false, +} + +jest.setTimeout(180000) + +const TESTS = [ + { + name: 'raw_sign', + text: 'This is our test payload!', + }, + { + name: 'raw_sign_hex', + text: 'This is our test payload with emoji! 😉', + }, +] + +describe.each(TESTS)('Raw signing', function (data) { + test.concurrent.each(models)(`${data.name}`, async function (m) { + const sim = new Zemu(m.path) + try { + await sim.start({ ...defaultOptions, model: m.name }) + const app = newEdgewareApp(sim.getTransport()) + const pathAccount = 0x80000000 + const pathChange = 0x80000000 + const pathIndex = 0x80000000 + + const txBlob = Buffer.from(data.text) + + const responseAddr = await app.getAddress(pathAccount, pathChange, pathIndex) + const pubKey = Buffer.from(responseAddr.pubKey, 'hex') + + // do not wait here.. we need to navigate + const signatureRequest = app.signRaw(pathAccount, pathChange, pathIndex, txBlob) + + // Wait until we are not in the main menu + await sim.waitUntilScreenIsNot(sim.getMainMenuSnapshot()) + + await sim.compareSnapshotsAndApprove('.', `${m.prefix.toLowerCase()}-${data.name}`) + + const signatureResponse = await signatureRequest + console.log(signatureResponse) + + expect(signatureResponse.return_code).toEqual(0x9000) + expect(signatureResponse.error_message).toEqual('No errors') + + // Now verify the signature + let prehash = txBlob + if (txBlob.length > 256) { + const context = blake2bInit(32) + blake2bUpdate(context, txBlob) + prehash = Buffer.from(blake2bFinal(context)) + } + const valid = ed25519.verify(signatureResponse.signature.subarray(1), prehash, pubKey) + expect(valid).toEqual(true) + } finally { + await sim.close() + } + }) +}) + +test.concurrent.each(models)('raw signing - incorrect', async function (m) { + const sim = new Zemu(m.path) + try { + await sim.start({ ...defaultOptions, model: m.name }) + const app = newEdgewareApp(sim.getTransport()) + const pathAccount = 0x80000000 + const pathChange = 0x80000000 + const pathIndex = 0x80000000 + + const txBlob = Buffer.from('Incorrect blob/Bytes>') + + const signatureResponse = await app.signRaw(pathAccount, pathChange, pathIndex, txBlob) + + console.log(signatureResponse) + + expect(signatureResponse.return_code).toEqual(0x6984) + expect(signatureResponse.error_message).toEqual('Unexpected value') + } finally { + await sim.close() + } +}) diff --git a/tests_zemu/tests/recovery.test.ts b/tests_zemu/tests/recovery.test.ts new file mode 100644 index 0000000..8c00d83 --- /dev/null +++ b/tests_zemu/tests/recovery.test.ts @@ -0,0 +1,67 @@ +/** ****************************************************************************** + * (c) 2020 Zondax GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************* */ + +import Zemu, { DEFAULT_START_OPTIONS } from '@zondax/zemu' +import { newEdgewareApp } from '@zondax/ledger-substrate' +import { APP_SEED, models } from './common' + +const defaultOptions = { + ...DEFAULT_START_OPTIONS, + logging: true, + custom: `-s "${APP_SEED}"`, + X11: false, +} + +jest.setTimeout(60000) + +describe('Recovery', function () { + test.concurrent.each(models)('main secret menu (%s)', async function (m) { + const sim = new Zemu(m.path) + try { + await sim.start({ ...defaultOptions, model: m.name }) + const app = newEdgewareApp(sim.getTransport()) + + const edgeware_expected_address = 'ndQcBy1NSwonPeWBFWcG159epFUTxn4afoAAAuhRTNutr9c' + const edgeware_expected_pk = 'e25acea4eab17e0ed9cabfa56b83a48229fad7545dc6a8e9e0643b786cb3df3f' + const kusama_expected_address = '16nK5XEGrPHjSwzHAdkKabmwu6L2t1RGW6drYyGgS84UZDRy' + const kusama_expected_pk = 'ffbc10f71d63e0da1b9e7ee2eb4037466551dc32b9d4641aafd73a65970fae42' + + let resp = await app.getAddress(0x80000000, 0x80000000, 0x80000000) + + console.log(resp) + + expect(resp.return_code).toEqual(0x9000) + expect(resp.error_message).toEqual('No errors') + + expect(resp.address).toEqual(edgeware_expected_address) + expect(resp.pubKey).toEqual(edgeware_expected_pk) + + await sim.enableSpecialMode('Zondax', true) + + resp = await app.getAddress(0x80000000, 0x80000000, 0x80000000) + + console.log(resp) + + expect(resp.return_code).toEqual(0x9000) + expect(resp.error_message).toEqual('No errors') + + expect(resp.address).toEqual(kusama_expected_address) + expect(resp.pubKey).toEqual(kusama_expected_pk) + } finally { + await sim.close() + } + }) +}) diff --git a/tests_zemu/tests/sr25519.test.ts b/tests_zemu/tests/sr25519.test.ts index c3ee39b..974011f 100644 --- a/tests_zemu/tests/sr25519.test.ts +++ b/tests_zemu/tests/sr25519.test.ts @@ -16,7 +16,8 @@ import Zemu, { DEFAULT_START_OPTIONS } from '@zondax/zemu' import { newEdgewareApp } from '@zondax/ledger-substrate' -import { APP_SEED, txBasic, txNomination } from './common' +import { APP_SEED } from './common' +import { txBalances_transfer } from './zemu_blobs' // @ts-ignore import { blake2bFinal, blake2bInit, blake2bUpdate } from 'blakejs' @@ -33,13 +34,13 @@ const defaultOptions = { X11: false, } -jest.setTimeout(60000) -beforeAll(async () => { - await Zemu.checkAndPullImage() -}) +const expected_address = 'niSdcp3RnvwZbcobU7YDijKe7CQXCWNXauJXztuQerxuwNT' +const expected_pk = 'e631d987c5bc9f47fa63f0cf9b938dd8ba8d83d9d240c4ee13e49f2eea74882a' + +jest.setTimeout(180000) describe('SR25519', function () { - test('get address sr25519', async function () { + test.concurrent('get address sr25519', async function () { const sim = new Zemu(APP_PATH) try { await sim.start({ ...defaultOptions }) @@ -52,9 +53,6 @@ describe('SR25519', function () { expect(resp.return_code).toEqual(0x9000) expect(resp.error_message).toEqual('No errors') - const expected_address = 'niSdcp3RnvwZbcobU7YDijKe7CQXCWNXauJXztuQerxuwNT' - const expected_pk = 'e631d987c5bc9f47fa63f0cf9b938dd8ba8d83d9d240c4ee13e49f2eea74882a' - expect(resp.address).toEqual(expected_address) expect(resp.pubKey).toEqual(expected_pk) } finally { @@ -62,7 +60,7 @@ describe('SR25519', function () { } }) - test('show address sr25519', async function () { + test.concurrent('show address sr25519', async function () { const sim = new Zemu(APP_PATH) try { await sim.start({ ...defaultOptions, model: 'nanos' }) @@ -71,7 +69,6 @@ describe('SR25519', function () { const respRequest = app.getAddress(0x80000000, 0x80000000, 0x80000000, true, 1) // Wait until we are not in the main menu await sim.waitUntilScreenIsNot(sim.getMainMenuSnapshot()) - await sim.compareSnapshotsAndApprove('.', 's-show_address_sr25519') const resp = await respRequest @@ -80,9 +77,6 @@ describe('SR25519', function () { expect(resp.return_code).toEqual(0x9000) expect(resp.error_message).toEqual('No errors') - const expected_address = 'niSdcp3RnvwZbcobU7YDijKe7CQXCWNXauJXztuQerxuwNT' - const expected_pk = 'e631d987c5bc9f47fa63f0cf9b938dd8ba8d83d9d240c4ee13e49f2eea74882a' - expect(resp.address).toEqual(expected_address) expect(resp.pubKey).toEqual(expected_pk) } finally { @@ -90,7 +84,7 @@ describe('SR25519', function () { } }) - test('show address - reject sr25519', async function () { + test.concurrent('show address - reject sr25519', async function () { const sim = new Zemu(APP_PATH) try { await sim.start({ ...defaultOptions }) @@ -111,7 +105,7 @@ describe('SR25519', function () { } }) - test('sign basic normal', async function () { + test.concurrent('sign basic normal', async function () { const sim = new Zemu(APP_PATH) try { await sim.start({ ...defaultOptions }) @@ -120,7 +114,7 @@ describe('SR25519', function () { const pathChange = 0x80000000 const pathIndex = 0x80000000 - const txBlob = Buffer.from(txBasic, 'hex') + const txBlob = Buffer.from(txBalances_transfer, 'hex') const responseAddr = await app.getAddress(pathAccount, pathChange, pathIndex, false, 1) const pubKey = Buffer.from(responseAddr.pubKey, 'hex') @@ -128,9 +122,8 @@ describe('SR25519', function () { // do not wait here.. we need to navigate const signatureRequest = app.sign(pathAccount, pathChange, pathIndex, txBlob, 1) // Wait until we are not in the main menu - console.log("Before the main menu") await sim.waitUntilScreenIsNot(sim.getMainMenuSnapshot()) - await sim.compareSnapshotsAndApprove('.', 's-sign_basic_normal') + await sim.compareSnapshotsAndApprove('.', 's-sign_basic_normal_sr25519') const signatureResponse = await signatureRequest console.log(signatureResponse) @@ -146,14 +139,14 @@ describe('SR25519', function () { prehash = Buffer.from(blake2bFinal(context)) } const signingcontext = Buffer.from([]) - const valid = addon.schnorrkel_verify(pubKey, signingcontext, prehash, signatureResponse.signature.slice(1)) + const valid = addon.schnorrkel_verify(pubKey, signingcontext, prehash, signatureResponse.signature.subarray(1)) expect(valid).toEqual(true) } finally { await sim.close() } }) - test('sign basic expert', async function () { + test.concurrent('sign basic expert', async function () { const sim = new Zemu(APP_PATH) try { await sim.start({ ...defaultOptions }) @@ -167,7 +160,7 @@ describe('SR25519', function () { await sim.clickBoth() await sim.clickLeft() - const txBlob = Buffer.from(txBasic, 'hex') + const txBlob = Buffer.from(txBalances_transfer, 'hex') const responseAddr = await app.getAddress(pathAccount, pathChange, pathIndex, false, 1) const pubKey = Buffer.from(responseAddr.pubKey, 'hex') @@ -177,51 +170,7 @@ describe('SR25519', function () { // Wait until we are not in the main menu await sim.waitUntilScreenIsNot(sim.getMainMenuSnapshot()) - console.log("We are in the main menu") - - await sim.compareSnapshotsAndApprove('.', 's-sign_basic_expert') - - const signatureResponse = await signatureRequest - console.log(signatureResponse) - - expect(signatureResponse.return_code).toEqual(0x9000) - expect(signatureResponse.error_message).toEqual('No errors') - - // Now verify the signature - let prehash = txBlob - if (txBlob.length > 256) { - const context = blake2bInit(32) - blake2bUpdate(context, txBlob) - prehash = Buffer.from(blake2bFinal(context)) - } - const signingcontext = Buffer.from([]) - const valid = addon.schnorrkel_verify(pubKey, signingcontext, prehash, signatureResponse.signature.slice(1)) - expect(valid).toEqual(true) - } finally { - await sim.close() - } - }) - - test('sign large nomination', async function () { - const sim = new Zemu(APP_PATH) - try { - await sim.start({ ...defaultOptions }) - const app = newEdgewareApp(sim.getTransport()) - const pathAccount = 0x80000000 - const pathChange = 0x80000000 - const pathIndex = 0x80000000 - - const txBlob = Buffer.from(txNomination, 'hex') - - const responseAddr = await app.getAddress(pathAccount, pathChange, pathIndex, false, 1) - const pubKey = Buffer.from(responseAddr.pubKey, 'hex') - - // do not wait here.. we need to navigate - const signatureRequest = app.sign(pathAccount, pathChange, pathIndex, txBlob, 1) - // Wait until we are not in the main menu - await sim.waitUntilScreenIsNot(sim.getMainMenuSnapshot()) - - await sim.compareSnapshotsAndApprove('.', 's-sign_large_nomination') + await sim.compareSnapshotsAndApprove('.', 's-sign_basic_expert_sr25519') const signatureResponse = await signatureRequest console.log(signatureResponse) @@ -237,7 +186,7 @@ describe('SR25519', function () { prehash = Buffer.from(blake2bFinal(context)) } const signingcontext = Buffer.from([]) - const valid = addon.schnorrkel_verify(pubKey, signingcontext, prehash, signatureResponse.signature.slice(1)) + const valid = addon.schnorrkel_verify(pubKey, signingcontext, prehash, signatureResponse.signature.subarray(1)) expect(valid).toEqual(true) } finally { await sim.close() diff --git a/tests_zemu/tests/standard.test.ts b/tests_zemu/tests/standard.test.ts index 7c92d5a..e7001d1 100644 --- a/tests_zemu/tests/standard.test.ts +++ b/tests_zemu/tests/standard.test.ts @@ -1,5 +1,5 @@ /** ****************************************************************************** - * (c) 2021 Zondax GmbH + * (c) 2020 Zondax GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,9 @@ * limitations under the License. ******************************************************************************* */ -import Zemu, { DEFAULT_START_OPTIONS } from '@zondax/zemu' +import Zemu, { ButtonKind, DEFAULT_START_OPTIONS, zondaxMainmenuNavigation } from '@zondax/zemu' import { newEdgewareApp } from '@zondax/ledger-substrate' -import {APP_SEED, models, txBasic, txNomination} from './common' - -// @ts-ignore -import ed25519 from 'ed25519-supercop' -// @ts-ignore -import { blake2bFinal, blake2bInit, blake2bUpdate } from 'blakejs' +import { APP_SEED, models } from './common' const defaultOptions = { ...DEFAULT_START_OPTIONS, @@ -30,14 +25,13 @@ const defaultOptions = { X11: false, } -jest.setTimeout(60000) +const expected_address = 'ndQcBy1NSwonPeWBFWcG159epFUTxn4afoAAAuhRTNutr9c' +const expected_pk = 'e25acea4eab17e0ed9cabfa56b83a48229fad7545dc6a8e9e0643b786cb3df3f' -beforeAll(async () => { - await Zemu.checkAndPullImage() -}) +jest.setTimeout(180000) describe('Standard', function () { - test.each(models)('can start and stop container', async function (m) { + test.concurrent.each(models)('can start and stop container', async function (m) { const sim = new Zemu(m.path) try { await sim.start({ ...defaultOptions, model: m.name }) @@ -46,17 +40,18 @@ describe('Standard', function () { } }) - test.each(models)('main menu', async function (m) { + test.concurrent.each(models)('main menu', async function (m) { const sim = new Zemu(m.path) try { + const mainmenuNavigation = zondaxMainmenuNavigation(m.name) await sim.start({ ...defaultOptions, model: m.name }) - await sim.navigateAndCompareSnapshots('.', `${m.prefix.toLowerCase()}-mainmenu`, [1, 0, 0, 4, -5]) + await sim.navigateAndCompareSnapshots('.', `${m.prefix.toLowerCase()}-mainmenu`, mainmenuNavigation.schedule) } finally { await sim.close() } }) - test.each(models)('get app version', async function (m) { + test.concurrent.each(models)('get app version', async function (m) { const sim = new Zemu(m.path) try { await sim.start({ ...defaultOptions, model: m.name }) @@ -76,7 +71,7 @@ describe('Standard', function () { } }) - test.each(models)('get address', async function (m) { + test.concurrent.each(models)('get address', async function (m) { const sim = new Zemu(m.path) try { await sim.start({ ...defaultOptions, model: m.name }) @@ -89,9 +84,6 @@ describe('Standard', function () { expect(resp.return_code).toEqual(0x9000) expect(resp.error_message).toEqual('No errors') - const expected_address = 'ndQcBy1NSwonPeWBFWcG159epFUTxn4afoAAAuhRTNutr9c' - const expected_pk = 'e25acea4eab17e0ed9cabfa56b83a48229fad7545dc6a8e9e0643b786cb3df3f' - expect(resp.address).toEqual(expected_address) expect(resp.pubKey).toEqual(expected_pk) } finally { @@ -99,16 +91,20 @@ describe('Standard', function () { } }) - test.each(models)('show address', async function (m) { + test.concurrent.each(models)('show address', async function (m) { const sim = new Zemu(m.path) try { - await sim.start({ ...defaultOptions, model: m.name }) + await sim.start({ + ...defaultOptions, + model: m.name, + approveKeyword: m.name === 'stax' ? 'QR' : '', + approveAction: ButtonKind.ApproveTapButton, + }) const app = newEdgewareApp(sim.getTransport()) const respRequest = app.getAddress(0x80000000, 0x80000000, 0x80000000, true) // Wait until we are not in the main menu await sim.waitUntilScreenIsNot(sim.getMainMenuSnapshot()) - await sim.compareSnapshotsAndApprove('.', `${m.prefix.toLowerCase()}-show_address`) const resp = await respRequest @@ -118,9 +114,6 @@ describe('Standard', function () { expect(resp.return_code).toEqual(0x9000) expect(resp.error_message).toEqual('No errors') - const expected_address = 'ndQcBy1NSwonPeWBFWcG159epFUTxn4afoAAAuhRTNutr9c' - const expected_pk = 'e25acea4eab17e0ed9cabfa56b83a48229fad7545dc6a8e9e0643b786cb3df3f' - expect(resp.address).toEqual(expected_address) expect(resp.pubKey).toEqual(expected_pk) } finally { @@ -128,17 +121,21 @@ describe('Standard', function () { } }) - test.each(models)('show address - reject', async function (m) { + test.concurrent.each(models)('show address - reject', async function (m) { const sim = new Zemu(m.path) try { - await sim.start({ ...defaultOptions, model: m.name }) + await sim.start({ + ...defaultOptions, + model: m.name, + rejectKeyword: m.name === 'stax' ? 'QR' : '', + }) const app = newEdgewareApp(sim.getTransport()) const respRequest = app.getAddress(0x80000000, 0x80000000, 0x80000000, true) // Wait until we are not in the main menu await sim.waitUntilScreenIsNot(sim.getMainMenuSnapshot()) - await sim.navigateAndCompareUntilText('.', `${m.prefix.toLowerCase()}-show_address_reject`, 'REJECT') + await sim.compareSnapshotsAndReject('.', `${m.prefix.toLowerCase()}-show_address_reject`) const resp = await respRequest console.log(resp) @@ -149,134 +146,4 @@ describe('Standard', function () { await sim.close() } }) - - test.each(models)('sign basic normal', async function (m) { - const sim = new Zemu(m.path) - try { - await sim.start({ ...defaultOptions, model: m.name }) - const app = newEdgewareApp(sim.getTransport()) - const pathAccount = 0x80000000 - const pathChange = 0x80000000 - const pathIndex = 0x80000000 - - const txBlob = Buffer.from(txBasic, 'hex') - - const responseAddr = await app.getAddress(pathAccount, pathChange, pathIndex) - const pubKey = Buffer.from(responseAddr.pubKey, 'hex') - - // do not wait here.. we need to navigate - const signatureRequest = app.sign(pathAccount, pathChange, pathIndex, txBlob) - // Wait until we are not in the main menu - await sim.waitUntilScreenIsNot(sim.getMainMenuSnapshot()) - - await sim.compareSnapshotsAndApprove('.', `${m.prefix.toLowerCase()}-sign_basic_normal`) - - const signatureResponse = await signatureRequest - console.log(signatureResponse) - - expect(signatureResponse.return_code).toEqual(0x9000) - expect(signatureResponse.error_message).toEqual('No errors') - - // Now verify the signature - let prehash = txBlob - if (txBlob.length > 256) { - const context = blake2bInit(32) - blake2bUpdate(context, txBlob) - prehash = Buffer.from(blake2bFinal(context)) - } - const valid = ed25519.verify(signatureResponse.signature.slice(1), prehash, pubKey) - expect(valid).toEqual(true) - } finally { - await sim.close() - } - }) - - test.each(models)('sign basic expert', async function (m) { - const sim = new Zemu(m.path) - try { - await sim.start({ ...defaultOptions, model: m.name }) - const app = newEdgewareApp(sim.getTransport()) - const pathAccount = 0x80000000 - const pathChange = 0x80000000 - const pathIndex = 0x80000000 - - // Change to expert mode so we can skip fields - await sim.clickRight() - await sim.clickBoth() - await sim.clickLeft() - - const txBlob = Buffer.from(txBasic, 'hex') - - const responseAddr = await app.getAddress(pathAccount, pathChange, pathIndex) - const pubKey = Buffer.from(responseAddr.pubKey, 'hex') - - // do not wait here.. we need to navigate - const signatureRequest = app.sign(pathAccount, pathChange, pathIndex, txBlob) - - // Wait until we are not in the main menu - await sim.waitUntilScreenIsNot(sim.getMainMenuSnapshot()) - - await sim.compareSnapshotsAndApprove('.', `${m.prefix.toLowerCase()}-sign_basic_expert`) - - - const signatureResponse = await signatureRequest - console.log(signatureResponse) - - expect(signatureResponse.return_code).toEqual(0x9000) - expect(signatureResponse.error_message).toEqual('No errors') - - // Now verify the signature - let prehash = txBlob - if (txBlob.length > 256) { - const context = blake2bInit(32) - blake2bUpdate(context, txBlob) - prehash = Buffer.from(blake2bFinal(context)) - } - const valid = ed25519.verify(signatureResponse.signature.slice(1), prehash, pubKey) - expect(valid).toEqual(true) - } finally { - await sim.close() - } - }) - - test.each(models)('sign large nomination', async function (m) { - const sim = new Zemu(m.path) - try { - await sim.start({ ...defaultOptions, model: m.name }) - const app = newEdgewareApp(sim.getTransport()) - const pathAccount = 0x80000000 - const pathChange = 0x80000000 - const pathIndex = 0x80000000 - - const txBlob = Buffer.from(txNomination, 'hex') - - const responseAddr = await app.getAddress(pathAccount, pathChange, pathIndex) - const pubKey = Buffer.from(responseAddr.pubKey, 'hex') - - // do not wait here.. we need to navigate - const signatureRequest = app.sign(pathAccount, pathChange, pathIndex, txBlob) - // Wait until we are not in the main menu - await sim.waitUntilScreenIsNot(sim.getMainMenuSnapshot()) - - await sim.compareSnapshotsAndApprove('.', `${m.prefix.toLowerCase()}-sign_large_nomination`) - - const signatureResponse = await signatureRequest - console.log(signatureResponse) - - expect(signatureResponse.return_code).toEqual(0x9000) - expect(signatureResponse.error_message).toEqual('No errors') - - // Now verify the signature - let prehash = txBlob - if (txBlob.length > 256) { - const context = blake2bInit(32) - blake2bUpdate(context, txBlob) - prehash = Buffer.from(blake2bFinal(context)) - } - const valid = ed25519.verify(signatureResponse.signature.slice(1), prehash, pubKey) - expect(valid).toEqual(true) - } finally { - await sim.close() - } - }) }) diff --git a/tests_zemu/tests/zemu_blobs.ts b/tests_zemu/tests/zemu_blobs.ts new file mode 100644 index 0000000..ca5357d --- /dev/null +++ b/tests_zemu/tests/zemu_blobs.ts @@ -0,0 +1,21 @@ +/** ****************************************************************************** + * (c) 2019 - 2023 Zondax AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************* */ + +export const txBalances_transfer = + '060000036fa3fc0b5aa41e86dc2ce5cb3a28cb322ad401b017c2232949f009697dce7e0b63ce64c10c05d503ae11030003d20296493500000002000000742a2ca70c2fda6cee4f8df98d64c4c670a052d9568058982dad9d5a7a135c5b742a2ca70c2fda6cee4f8df98d64c4c670a052d9568058982dad9d5a7a135c5b' + +export const txStaking_nominate = + '080508002229b353a1b15bf4743ff12fb2c660ff8edf888f6f89bb11fb9878a57435034e00240eb98ac9d5823076f4005dfade11fadd72fcf2c9b902401f882ba926d0170ad503006d0f3500000002000000742a2ca70c2fda6cee4f8df98d64c4c670a052d9568058982dad9d5a7a135c5b742a2ca70c2fda6cee4f8df98d64c4c670a052d9568058982dad9d5a7a135c5b'