├── .clang-format ├── .clusterfuzzlite ├── Dockerfile ├── build.sh └── project.yaml ├── .doxygen └── Doxyfile ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── cflite_cron.yml │ ├── cflite_pr.yml │ ├── ci-workflow.yml │ ├── codeql-workflow.yml │ ├── guidelines_enforcer.yml │ └── lint-workflow.yml ├── .gitignore ├── .vscode ├── c_cpp_properties.json ├── extensions.json ├── launch.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── doc ├── APDU.md ├── INS-01-APP-VERSION.md ├── INS-02-APP-NAME.md ├── INS-10-EXT-PUB-KEY.md ├── INS-11-DERIVE-ADDR.md ├── INS-20-ATTEST-BOX.md ├── INS-21-SIGN-TRANSACTION.md └── README.md ├── fuzzing ├── CMakeLists.txt ├── README.md ├── include │ └── glyphs.h └── src │ ├── ainpt_harness.c │ ├── da_harness.c │ ├── epk_harness.c │ ├── stx_harness.c │ └── utils │ ├── crc32.c │ ├── glyphs.c │ └── os_mocks.c ├── glyphs ├── app_logo_16px.gif └── app_logo_64px.gif ├── icons ├── app_14px.gif ├── app_16px.gif ├── app_32px.gif └── app_40px.gif ├── ledger_app.toml ├── src ├── apdu_dispatcher.c ├── apdu_dispatcher.h ├── app_main.c ├── commands │ ├── app_name.c │ ├── app_name.h │ ├── app_version.c │ ├── app_version.h │ ├── attestinput │ │ ├── ainpt_context.h │ │ ├── ainpt_handler.c │ │ ├── ainpt_handler.h │ │ ├── ainpt_response.c │ │ ├── ainpt_response.h │ │ ├── ainpt_ui.h │ │ ├── ainpt_ui_bagl.c │ │ └── ainpt_ui_nbgl.c │ ├── deriveaddress │ │ ├── da_context.h │ │ ├── da_handler.c │ │ ├── da_handler.h │ │ ├── da_response.c │ │ ├── da_response.h │ │ ├── da_ui.h │ │ ├── da_ui_bagl.c │ │ └── da_ui_nbgl.c │ ├── extpubkey │ │ ├── epk_context.h │ │ ├── epk_handler.c │ │ ├── epk_handler.h │ │ ├── epk_response.c │ │ ├── epk_response.h │ │ ├── epk_ui.h │ │ ├── epk_ui_bagl.c │ │ └── epk_ui_nbgl.c │ └── signtx │ │ ├── operations │ │ ├── stx_op_p2pk.c │ │ └── stx_op_p2pk.h │ │ ├── stx_amounts.c │ │ ├── stx_amounts.h │ │ ├── stx_context.h │ │ ├── stx_handler.c │ │ ├── stx_handler.h │ │ ├── stx_output.c │ │ ├── stx_output.h │ │ ├── stx_response.c │ │ ├── stx_response.h │ │ ├── stx_types.h │ │ ├── stx_ui.h │ │ ├── stx_ui_bagl.c │ │ ├── stx_ui_common.c │ │ ├── stx_ui_common.h │ │ └── stx_ui_nbgl.c ├── common │ ├── bip32_ext.c │ ├── bip32_ext.h │ ├── buffer_ext.c │ ├── buffer_ext.h │ ├── gve.c │ ├── gve.h │ ├── macros_ext.h │ ├── rwbuffer.c │ ├── rwbuffer.h │ ├── safeint.h │ └── zigzag.h ├── constants.h ├── context.c ├── context.h ├── ergo │ ├── address.c │ ├── address.h │ ├── ergo_tree.c │ ├── ergo_tree.h │ ├── network_id.h │ ├── schnorr.c │ ├── schnorr.h │ ├── tx_ser_box.c │ ├── tx_ser_box.h │ ├── tx_ser_full.c │ ├── tx_ser_full.h │ ├── tx_ser_input.c │ ├── tx_ser_input.h │ ├── tx_ser_table.c │ └── tx_ser_table.h ├── helpers │ ├── blake2b.c │ ├── blake2b.h │ ├── cmd_macros.h │ ├── crypto.c │ ├── crypto.h │ ├── input_frame.c │ ├── input_frame.h │ ├── response.h │ ├── session_id.h │ └── sw_result.h ├── sw.h └── ui │ ├── display.c │ ├── display.h │ ├── ui_application_id.h │ ├── ui_application_id_bagl.c │ ├── ui_application_id_nbgl.c │ ├── ui_approve_reject.c │ ├── ui_approve_reject.h │ ├── ui_bip32_path.h │ ├── ui_bip32_path_bagl.c │ ├── ui_bip32_path_nbgl.c │ ├── ui_dynamic_flow.c │ ├── ui_dynamic_flow.h │ ├── ui_main.h │ ├── ui_main_bagl.c │ ├── ui_menu.h │ ├── ui_menu_bagl.c │ ├── ui_menu_nbgl.c │ ├── ui_sign_reject.c │ └── ui_sign_reject.h ├── tests ├── .gitignore ├── README.md ├── address-tests.js ├── basic-tests.js ├── helpers │ ├── automation.js │ ├── common.js │ ├── data.js │ ├── flow.js │ ├── hooks.js │ ├── makefile.js │ ├── screen.js │ └── transaction.js ├── package.json ├── public-key-tests.js ├── seed.txt └── transaction-tests.js └── unit-tests ├── .vscode ├── c_cpp_properties.json └── tasks.json ├── CMakeLists.txt ├── README.md ├── gen_coverage.sh ├── test_address.c ├── test_bip32.c ├── test_buffer.c ├── test_ergo_tree.c ├── test_full_tx.c ├── test_gve.c ├── test_input_frame.c ├── test_safeint.c ├── test_tx_ser_box.c ├── test_tx_ser_input.c ├── test_tx_ser_table.c ├── test_zigzag.c └── utils ├── base58.c ├── base58.h ├── bip32.c ├── bip32.h ├── blake2-impl.h ├── blake2b-ref.c ├── blake2b-ref.h ├── buffer.c ├── buffer.h ├── cx.c ├── cx.h ├── ledger_assert.h ├── macro_helpers.h ├── macros.h ├── os.h ├── read.c ├── read.h ├── varint.c ├── varint.h ├── write.c └── write.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | IndentWidth: 4 4 | --- 5 | Language: Cpp 6 | ColumnLimit: 100 7 | PointerAlignment: Right 8 | AlignAfterOpenBracket: Align 9 | AlignConsecutiveMacros: true 10 | AllowAllParametersOfDeclarationOnNextLine: false 11 | SortIncludes: false 12 | SpaceAfterCStyleCast: true 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowAllArgumentsOnNextLine: false 15 | AllowShortBlocksOnASingleLine: Never 16 | AllowShortFunctionsOnASingleLine: None 17 | BinPackArguments: false 18 | BinPackParameters: false 19 | --- 20 | -------------------------------------------------------------------------------- /.clusterfuzzlite/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest AS LITE_BUILDER 2 | 3 | # Base image with clang toolchain 4 | FROM gcr.io/oss-fuzz-base/base-builder:v1 5 | 6 | # Copy the project's source code. 7 | COPY . $SRC/ledger-app-ergo 8 | COPY --from=LITE_BUILDER /opt/ledger-secure-sdk $SRC/ledger-app-ergo/BOLOS_SDK 9 | 10 | # Working directory for build.sh 11 | WORKDIR $SRC/ledger-app-ergo 12 | 13 | # Copy build.sh into $SRC dir. 14 | COPY ./.clusterfuzzlite/build.sh $SRC/ -------------------------------------------------------------------------------- /.clusterfuzzlite/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | # build fuzzers 4 | 5 | pushd fuzzing 6 | cmake -DBOLOS_SDK=../BOLOS_SDK -Bbuild -H. 7 | make -C build 8 | mv ./build/*_harness "${OUT}" 9 | popd -------------------------------------------------------------------------------- /.clusterfuzzlite/project.yaml: -------------------------------------------------------------------------------- 1 | language: c -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Checklist 2 | 3 | - [ ] App update process has been followed 4 | - [ ] Target branch is `develop` 5 | - [ ] Application version has been bumped 6 | 7 | 9 | -------------------------------------------------------------------------------- /.github/workflows/cflite_cron.yml: -------------------------------------------------------------------------------- 1 | name: ClusterFuzzLite cron tasks 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main # Use your actual default branch here. 7 | schedule: 8 | - cron: '0 13 * * 6' # At 01:00 PM, only on Saturday 9 | permissions: read-all 10 | jobs: 11 | Fuzzing: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | include: 17 | - mode: batch 18 | sanitizer: address 19 | - mode: batch 20 | sanitizer: memory 21 | - mode: prune 22 | sanitizer: address 23 | - mode: coverage 24 | sanitizer: coverage 25 | steps: 26 | - name: Build Fuzzers (${{ matrix.mode }} - ${{ matrix.sanitizer }}) 27 | id: build 28 | uses: google/clusterfuzzlite/actions/build_fuzzers@v1 29 | with: 30 | github-token: ${{ secrets.GITHUB_TOKEN }} 31 | language: c # Change this to the language you are fuzzing. 32 | sanitizer: ${{ matrix.sanitizer }} 33 | - name: Run Fuzzers (${{ matrix.mode }} - ${{ matrix.sanitizer }}) 34 | id: run 35 | uses: google/clusterfuzzlite/actions/run_fuzzers@v1 36 | with: 37 | github-token: ${{ secrets.GITHUB_TOKEN }} 38 | fuzz-seconds: 300 # 5 minutes 39 | mode: ${{ matrix.mode }} 40 | sanitizer: ${{ matrix.sanitizer }} -------------------------------------------------------------------------------- /.github/workflows/cflite_pr.yml: -------------------------------------------------------------------------------- 1 | name: ClusterFuzzLite PR fuzzing 2 | on: 3 | pull_request: 4 | paths: 5 | - '**' 6 | permissions: read-all 7 | jobs: 8 | PR: 9 | runs-on: ubuntu-latest 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ matrix.sanitizer }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | sanitizer: [address, undefined, memory] # Override this with the sanitizers you want. 17 | steps: 18 | - name: Build Fuzzers (${{ matrix.sanitizer }}) 19 | id: build 20 | uses: google/clusterfuzzlite/actions/build_fuzzers@v1 21 | with: 22 | language: c # Change this to the language you are fuzzing. 23 | github-token: ${{ secrets.GITHUB_TOKEN }} 24 | sanitizer: ${{ matrix.sanitizer }} 25 | # Optional but recommended: used to only run fuzzers that are affected 26 | # by the PR. 27 | # storage-repo: https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/OWNER/STORAGE-REPO-NAME.git 28 | # storage-repo-branch: main # Optional. Defaults to "main" 29 | # storage-repo-branch-coverage: gh-pages # Optional. Defaults to "gh-pages". 30 | - name: Run Fuzzers (${{ matrix.sanitizer }}) 31 | id: run 32 | uses: google/clusterfuzzlite/actions/run_fuzzers@v1 33 | with: 34 | github-token: ${{ secrets.GITHUB_TOKEN }} 35 | fuzz-seconds: 300 # 5 minutes 36 | mode: 'code-change' 37 | sanitizer: ${{ matrix.sanitizer }} 38 | output-sarif: true 39 | # Optional but recommended: used to download the corpus produced by 40 | # batch fuzzing. 41 | # storage-repo: https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/OWNER/STORAGE-REPO-NAME.git 42 | # storage-repo-branch: main # Optional. Defaults to "main" 43 | # storage-repo-branch-coverage: gh-pages # Optional. Defaults to "gh-pages". -------------------------------------------------------------------------------- /.github/workflows/codeql-workflow.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | on: 3 | # temporary disable this workflow as it seems to be failing 4 | # on LedgerHQ/app-ergo repo due to specific repo settings. 5 | # See https://github.com/LedgerHQ/app-ergo/actions/runs/14265601766/job/39986602333 6 | workflow_dispatch: 7 | 8 | # push: 9 | # branches: ["main", "master", "develop"] 10 | # pull_request: 11 | # branches: ["main", "master", "develop"] 12 | # # Excluded path: add the paths you want to ignore instead of deleting the workflow 13 | # paths-ignore: 14 | # - '.github/workflows/*.yml' 15 | # - 'tests/*' 16 | jobs: 17 | analyse: 18 | name: Analyse 19 | strategy: 20 | matrix: 21 | sdk: [ "$NANOX_SDK", "$NANOSP_SDK", "$STAX_SDK", "$FLEX_SDK" ] 22 | #'cpp' covers C and C++ 23 | language: [ 'cpp' ] 24 | runs-on: ubuntu-latest 25 | container: 26 | image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-legacy:latest 27 | steps: 28 | - name: Clone 29 | uses: actions/checkout@v3 30 | with: 31 | submodules: recursive 32 | - name: Initialize CodeQL 33 | uses: github/codeql-action/init@v3 34 | with: 35 | languages: ${{ matrix.language }} 36 | queries: security-and-quality 37 | # CodeQL will create the database during the compilation 38 | - name: Build 39 | run: | 40 | make BOLOS_SDK=${{ matrix.sdk }} 41 | 42 | - name: Perform CodeQL Analysis 43 | uses: github/codeql-action/analyze@v3 -------------------------------------------------------------------------------- /.github/workflows/guidelines_enforcer.yml: -------------------------------------------------------------------------------- 1 | name: Ensure compliance with Ledger guidelines 2 | 3 | # This workflow is mandatory in all applications 4 | # It calls a reusable workflow guidelines_enforcer developed by Ledger's internal developer team. 5 | # The successful completion of the reusable workflow is a mandatory step for an app to be available on the Ledger 6 | # application store. 7 | # 8 | # More information on the guidelines can be found in the repository: 9 | # LedgerHQ/ledger-app-workflows/ 10 | 11 | on: 12 | workflow_dispatch: 13 | push: 14 | branches: 15 | - master 16 | - main 17 | - develop 18 | pull_request: 19 | 20 | jobs: 21 | guidelines_enforcer: 22 | name: Call Ledger guidelines_enforcer 23 | uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_guidelines_enforcer.yml@v1 24 | -------------------------------------------------------------------------------- /.github/workflows/lint-workflow.yml: -------------------------------------------------------------------------------- 1 | name: Code style check 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | job_lint: 7 | name: Lint 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Clone 12 | uses: actions/checkout@v4 13 | 14 | - name: Lint 15 | uses: DoozyX/clang-format-lint-action@v0.18.2 16 | with: 17 | source: './src' 18 | extensions: 'h,c' 19 | clangFormatVersion: 12 20 | 21 | job_misspell: 22 | name: Check misspellings 23 | runs-on: ubuntu-latest 24 | 25 | steps: 26 | - name: Clone 27 | uses: actions/checkout@v4 28 | with: 29 | fetch-depth: 0 30 | 31 | - name: Check misspellings 32 | uses: codespell-project/actions-codespell@bcf481f4d5cce7b92b65f05aebe8f552d4f1442c 33 | with: 34 | builtin: clear,rare 35 | check_filenames: true 36 | ignore_words_list: afterall 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compilation of Ledger's app 2 | bin/ 3 | build/ 4 | debug/ 5 | 6 | # Speculos related 7 | *_nvram.bin* 8 | 9 | # Unit tests and code coverage 10 | unit-tests/build/ 11 | unit-tests/coverage/ 12 | unit-tests/coverage.info 13 | 14 | # Fuzzing 15 | fuzzing/build/ 16 | 17 | 18 | # Editors 19 | .idea/ 20 | 21 | # Python 22 | *.pyc[cod] 23 | *.egg 24 | __pycache__/ 25 | *.egg-info/ 26 | .eggs/ 27 | .python-version 28 | 29 | # Doxygen 30 | doc/html 31 | doc/latex 32 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "BOLOS_SDK": "~/.ledger/nanosplus-sdk", 4 | "ARM_GCC": "~/.ledger/gcc-arm-none-eabi-13.2-2023.10" 5 | }, 6 | "configurations": [ 7 | { 8 | "name": "Nano S+", 9 | "includePath": [ 10 | "${workspaceFolder}/src", 11 | "${workspaceFolder}/build/stax/gen_src", 12 | "${env:ARM_GCC}/arm-none-eabi/include/*", 13 | "${env:ARM_GCC}/lib/gcc/arm-none-eabi/13.2.1/include/*", 14 | "${env:BOLOS_SDK}/include/*", 15 | "${env:BOLOS_SDK}/lib_ux/include/*", 16 | "${env:BOLOS_SDK}/lib_nbgl/include/*", 17 | "${env:BOLOS_SDK}/lib_cxng/include/*", 18 | "${env:BOLOS_SDK}/lib_standard_app/*" 19 | ], 20 | "defines": [ 21 | "TARGET_NANOS2", 22 | "OS_IO_SEPROXYHAL", 23 | "HAVE_NBGL", 24 | "HAVE_SE_TOUCH", 25 | "HAVE_ECC", 26 | "HAVE_ECC_WEIERSTRASS", 27 | "HAVE_SECP_CURVES", 28 | "HAVE_SPRINTF", 29 | "HAVE_HASH", 30 | "HAVE_BLAKE2", 31 | "HAVE_RNG", 32 | "HAVE_HMAC", 33 | "HAVE_SHA256", 34 | "HAVE_MATH", 35 | "HAVE_IO_USB", 36 | "HAVE_L4_USBLIB", 37 | "IO_USB_MAX_ENDPOINTS=6", 38 | "IO_HID_EP_LENGTH=64", 39 | "HAVE_USB_APDU", 40 | "USB_SEGMENT_SIZE=64", 41 | "UNUSED(x)=(void)x", 42 | "APPVERSION=\"1.0.0\"", 43 | "APPNAME=\"Ergo\"", 44 | "MAJOR_VERSION=1", 45 | "MINOR_VERSION=0", 46 | "PATCH_VERSION=0", 47 | "IO_SEPROXYHAL_BUFFER_SIZE_B=128", 48 | "HAVE_UX_FLOW", 49 | "DEBUG=1", 50 | "DEBUG_BUILD=1", 51 | "HAVE_PRINTF", 52 | "PRINTF=screen_printf", 53 | "_DEFAULT_SOURCE" 54 | ], 55 | "compilerPath": "/usr/bin/gcc", 56 | "cStandard": "c11", 57 | "cppStandard": "c++17", 58 | "intelliSenseMode": "gcc-arm", 59 | "browse": { 60 | "limitSymbolsToIncludedHeaders": true, 61 | "databaseFilename": "" 62 | } 63 | } 64 | ], 65 | "version": 4 66 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-vscode.cpptools", 4 | "LedgerHQ.ledger-dev-tools", 5 | ] 6 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "gdb", 9 | "request": "attach", 10 | "name": "Attach to gdbserver", 11 | "executable": "${workspaceFolder}/bin/app.elf", 12 | "target": ":1234", 13 | "remote": true, 14 | "cwd": "${workspaceFolder}", 15 | "valuesFormatting": "parseText", 16 | "gdbpath": "gdb-multiarch", 17 | "autorun": [ 18 | "set substitute-path /project ${workspaceFolder}", 19 | "set architecture arm", 20 | "set backtrace limit 15", 21 | "handle SIGILL nostop pass noprint", 22 | "add-symbol-file ${workspaceFolder}/bin/app.elf 0x40000000", 23 | "b *0x40000000" 24 | ] 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.h": "c", 4 | "format": "c" 5 | }, 6 | "editor.formatOnSave": false, 7 | "python.terminal.activateEnvironment": false, 8 | "git.ignoreLimitWarning": true, 9 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.0.6] - 2024-06-10 9 | 10 | - Added Stax/Flex 11 | - Removed Nano S 12 | 13 | ## [0.0.5] - 2024-06-10 14 | 15 | - Updated to the latest Ledger SDK 16 | - Send addresses is in full form on all devices: #10 17 | - Better signing UX: #11 18 | - Fixed broken burning token TX signing 19 | 20 | ## [0.0.4] - 2023-03-12 21 | 22 | - Full base58 address of output 23 | - Updated copyrights 24 | 25 | ## [0.0.3] - 2022-12-14 26 | 27 | - Initial version of the application 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ledger Ergo Application 2 | 3 | This is a Ergo application for the Ledger Nano S+/X/Stax/Flex. 4 | 5 | ## Prerequisite 6 | 7 | Be sure to have your environment correctly set up (see [Getting Started](https://developers.ledger.com/docs/nano-app/introduction/)) and [ledgerblue](https://pypi.org/project/ledgerblue/) and installed. 8 | 9 | If you want to benefit from [vscode](https://code.visualstudio.com/) integration, it's recommended to move the toolchain in `/opt` and set `BOLOS_ENV` environment variable as follows 10 | 11 | ``` 12 | BOLOS_ENV=/opt/bolos-devenv 13 | ``` 14 | 15 | and do the same with `BOLOS_SDK` environment variable 16 | 17 | ``` 18 | BOLOS_SDK=/opt/bolos-sdk/nanosplus-secure-sdk 19 | ``` 20 | 21 | ## Compilation 22 | 23 | ``` 24 | make DEBUG=1 # compile optionally with PRINTF 25 | make load # load the app on the Nano using ledgerblue 26 | ``` 27 | 28 | ## Documentation 29 | 30 | API documentation can be found in the [doc](doc/README.md) folder. 31 | 32 | Ledger app developer documentation which can be generated with [doxygen](https://www.doxygen.nl) 33 | 34 | ``` 35 | doxygen .doxygen/Doxyfile 36 | ``` 37 | 38 | the process outputs HTML and LaTeX documentations in `doc/html` and `doc/latex` folders. 39 | 40 | ## Tests & Continuous Integration 41 | 42 | The flow processed in [GitHub Actions](https://github.com/features/actions) is the following: 43 | 44 | - Code formatting with [clang-format](http://clang.llvm.org/docs/ClangFormat.html) 45 | - Compilation of the application for Ledger Nano S+ in [ledger-app-builder](https://github.com/LedgerHQ/ledger-app-builder) 46 | - Unit tests of C functions with [cmocka](https://cmocka.org/) (see [unit-tests/](unit-tests/)) 47 | - End-to-end tests with [Speculos](https://github.com/LedgerHQ/speculos) emulator (see [tests/](tests/)) 48 | - Code coverage with [gcov](https://gcc.gnu.org/onlinedocs/gcc/Gcov.html)/[lcov](http://ltp.sourceforge.net/coverage/lcov.php) and upload to [codecov.io](https://about.codecov.io) 49 | - Documentation generation with [doxygen](https://www.doxygen.nl) 50 | 51 | It outputs 4 artifacts: 52 | 53 | - `ergo-app-debug` within output files of the compilation process in debug mode 54 | - `speculos-log` within APDU command/response when executing end-to-end tests 55 | - `code-coverage` within HTML details of code coverage 56 | - `documentation` within HTML auto-generated documentation 57 | -------------------------------------------------------------------------------- /doc/APDU.md: -------------------------------------------------------------------------------- 1 | # Messaging protocol (Application Protocol Data Unit) 2 | 3 | The communication protocol used by [BOLOS](https://ledger.readthedocs.io/en/latest/bolos/overview.html) to exchange [APDU](https://en.wikipedia.org/wiki/Smart_card_application_protocol_data_unit) is very close to [ISO 7816-4](https://www.iso.org/standard/77180.html) with a few differences: 4 | 5 | - `Lc` length is always exactly 1 byte 6 | - No `Le` field in APDU command 7 | - Maximum size of APDU command is 260 bytes: 5 bytes of header + 255 bytes of data 8 | - Maximum size of APDU response is 260 bytes: 258 bytes of response data + 2 bytes of status word 9 | 10 | Status words tend to be similar to common [APDU responses](https://www.eftlab.com/knowledge-base/complete-list-of-apdu-responses/) in the industry. 11 | 12 | ## Command APDU 13 | 14 | | Field | CLA | INS | P1 | P2 | Lc | Data | Le | 15 | | --- | --- | --- | --- | --- | --- | --- | --- | 16 | | Size (B) | 1 | 1 | 1 | 1 | 1 | variable | 0 | 17 | 18 | Where: 19 | * **CLA=0xE0** is the APDU class number. As we don’t adhere to the strict APDU protocol, we can arbitrarily choose a value belonging to the "proprietary structure and coding of command/response" CLA range (0xD0-0xFE range) 20 | * **INS** is the instruction number 21 | * **P1** and **P2** are instruction parameters 22 | * **Lc** is the length of the data body encoded as uint8. Note: unlike standard APDU, ledger.js produces **Lc** of exactly 1 byte (even for empty data). Data of length >= 256 are not supported by ledger.js 23 | * **Data** is binary data 24 | * **Le** is the max length of response. This APDU field is not present in ledger.js implementation 25 | 26 | Upon receiving the APDU message, the Ledger Application checks: 27 | * RX size >= 5 (i.e., the request has all required APDU fields) 28 | * **CLA** is a valid CLA of the Ledger Ergo Application (**0xE0**) 29 | * **INS** is a valid and enabled instruction 30 | * **Lc** is consistent with RX, i.e. **Lc** + 5 == RX 31 | * **INS** is not changed in the middle of the multi-APDU exchange. 32 | 33 | All unused parameters (**P1** and **P2**, **Lc**) should be set to 0. Meaningful values are never 0 for any of the fields (i.e. enum values must start from 1). 34 | 35 | ## Response APDU 36 | 37 | | Field | Data | SW1 | SW2 | 38 | | --- | --- | --- | --- | 39 | | Size (B) | variable | 1 | 1 | 40 | 41 | where **SW1** and **SW2** represent the return code. 42 | 43 | Known codes are: 44 | * **0x9000** = OK (SW1=0x90, SW2=0x00) 45 | * for error codes check [sw.h](../src/sw.h) file. 46 | 47 | -------------------------------------------------------------------------------- /doc/INS-01-APP-VERSION.md: -------------------------------------------------------------------------------- 1 | # 0x01 - Get Ledger Application Version 2 | 3 | Returns the Ledger Application version. 4 | 5 | ## Command 6 | | INS | P1 | P2 | Lc | 7 | | --- | --- | --- | --- | 8 | | 0x01 | 0x00 | 0x00 | 0x00 | 9 | 10 | Ledger Application checks that **P1**, **P2**, and **Lc** are all set to **0x00**. If not - the error code is returned. 11 | 12 | ## Response 13 | 14 | | Byte 0 | Byte 1 | Byte 2 | Byte 3 | 15 | | --- | --- | --- | --- | 16 | | Major version | Minor version | Patch | Debug | 17 | 18 | **Debug** flag is set to 0x01 for all debug builds of the Ledger Application. Apps and Wallets should show an error message when this flag is set (debug version of Ledger Application should never be used in any real environment). 19 | -------------------------------------------------------------------------------- /doc/INS-02-APP-NAME.md: -------------------------------------------------------------------------------- 1 | # 0x02 - Get Ledger Application Name 2 | Returns the Ledger Application name. 3 | 4 | ## Command 5 | | INS | P1 | P2 | Lc | 6 | | --- | --- | --- | --- | 7 | | 0x02 | 0x00 | 0x00 | 0x00 | 8 | 9 | Ledger Application checks that **P1**, **P2**, and **Lc** are all set to **0x00**. If not - the error code is returned. 10 | 11 | ## Response 12 | 13 | Response of this call is an array of ANSI encoded characters with application name ("Ergo"). 14 | -------------------------------------------------------------------------------- /doc/INS-10-EXT-PUB-KEY.md: -------------------------------------------------------------------------------- 1 | # 0x10 - Get the extended public key 2 | 3 | Returns a combination of chain code and public key bytes for provided BIP44 path. An optional token can be added to create an authenticated session within the Ledger Application. 4 | 5 | ## Command 6 | 7 | | INS | P1 | P2 | Lc | Data | 8 | | --- | --- | --- | --- | --- | 9 | | 0x10 | 0x01 - without token
0x02 - with token |0x00 | variable | see below | 10 | 11 | ### Data 12 | | Field | Size (B) | Description | 13 | | --- | --- | --- | 14 | | BIP32 path length | 1 | Value: 0x02-0x0A (2-10). Count of path components | 15 | | First derivation index | 4 | Big-endian. Value: 44' | 16 | | Second derivation index | 4 | Big-endian. Value: 429’ (Ergo coin id) | 17 | | [Optional] Third index | 4 | Big-endian. Any valid bip44 hardened value. | 18 | | ... | 4 | ... | 19 | | [Optional] Last index | 4 | Big-endian. Any valid bip44 value. | 20 | | [Optional] Auth Token | 4 | Big-endian. Randomly generated value. Becomes an ID for the session for future use, i.e. once transaction signing is requested. If present, **P1** should be set to 0x02 | 21 | 22 | 23 | Ledger Application checks all input parameters to be valid. Data length is also checked based on the **P1** parameter and **bip32 path length**. Ledger Application asks user permission to send extended public key information back. If Authorization Token is present, it’s presented to the user in the HEX format. Last Authorization Token is saved in RAM of the Ledger as current Application Session Token. 24 | 25 | ## Response 26 | 27 | Response is 65 bytes of data which consists of 33 bytes of compressed public key and 32 bytes of chain code. 28 | 29 | | Bytes [0-32] | Bytes [33-64] | 30 | | --- | --- | 31 | | Compressed Public Key | Chain Code | 32 | -------------------------------------------------------------------------------- /doc/INS-11-DERIVE-ADDR.md: -------------------------------------------------------------------------------- 1 | # 0x11 - Derive address 2 | 3 | Derive the address for a given BIP44 path and return or show it to the user. This call is intended for address verification purposes (i.e., matching the address on Ledger with the one on the dApp/Wallet screen). 4 | 5 | The address can’t be that of an account, or of external/internal address chain root, i.e. it needs to have: 6 | * path_len >= 5, 7 | * path[2] is hardened (account), and 8 | * path[3] in [0,1] (internal/external chain) 9 | 10 | ## Command 11 | 12 | | INS | P1 | P2 | Lc | Data | 13 | | --- | --- | --- | --- | --- | 14 | | 0x11 | 0x01 - return
0x02 - display | 0x01 - without token
0x02 - with token | variable | see below | 15 | 16 | ### Data 17 | | Field | Size (B) | Description | 18 | | --- | --- | --- | 19 | | Network Type | 1 | Value: 0x00-0xFC (0-252). Network Type | 20 | | BIP32 path length | 1 | Value: 0x05-0x0A (5-10). Count of path components | 21 | | First derivation index | 4 | Big-endian. Value: 44’ | 22 | | Second derivation index | 4 | Big-endian. Value: 429’ (Ergo coin id) | 23 | | Third index | 4 | Big-endian. Any valid bip44 hardened value. | 24 | | ... | 4 | ... | 25 | | [Optional] Last index | 4 | Big-endian. Any valid bip44 value. | 26 | | [Optional] Auth Token | 4 | Big-endian. Randomly generated value (session). If present **P2** should be set to 0x02 | 27 | 28 | ## Response 29 | 30 | Empty if "display" was sent. 38 bytes of the address data otherwise. 31 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # Ergo Ledger App Communication Protocol 2 | 3 | This document describes the communication protocol between Wallets/dApps and Ergo Ledger Application. It contains detailed information about messaging layer protocol, and available methods, which can be used on Ledger Ergo Application. 4 | 5 | ## Helper Libraries 6 | 7 | This documentation is a pretty low-level one and created for library developers. 8 | 9 | If you are Wallet or dApp developer it's simpler to use one of the helper libraries: 10 | 11 | * [ledger-ergo-js](https://www.npmjs.com/package/ledger-ergo-js) - JavaScript helper library 12 | 13 | ## Message data format 14 | 15 | Ergo Communication Protocol uses an envelope message defined by APDU protocol and a set of wrapped messages for actions that can be performed by the application. 16 | 17 | For more information about request and response formats read [APDU](APDU.md) document. 18 | 19 | ## Application Instruction Codes 20 | 21 | There is the list of the instructions which can be called in the Ergo Ledger Application: 22 | 23 | * **0x01-0x0F** - General application status 24 | * [0x01 - Get Ledger Application version](INS-01-APP-VERSION.md) 25 | * [0x02 - Get Ledger Application name](INS-02-APP-NAME.md) 26 | * **0x10-0x1F** - Public key / Addresses 27 | * [0x10 - Get the extended public key](INS-10-EXT-PUB-KEY.md) 28 | * [0x11 - Derive address](INS-11-DERIVE-ADDR.md) 29 | * **0x20-0x2F** - Signing 30 | * [0x20 - Attest Input Box](INS-20-ATTEST-BOX.md) 31 | * [0x21 - Sign transaction](INS-21-SIGN-TRANSACTION.md) 32 | -------------------------------------------------------------------------------- /fuzzing/README.md: -------------------------------------------------------------------------------- 1 | # Fuzzing tests 2 | 3 | ## Fuzzing 4 | 5 | Fuzzing allows us to test how a program behaves when provided with invalid, unexpected, or random data as input. 6 | 7 | In the case of `ledger-app-ergo` we want to test the code that is responsible for handling attest input, derive address, ext pubkey and sign tx APDU handlers. 8 | 9 | If the application crashes, or a [sanitizer](https://github.com/google/sanitizers) detects any kind of 10 | access violation, the fuzzing process is stopped, a report regarding the vulnerability is shown, 11 | and the input that triggered the bug is written to disk under the name `crash-*`. 12 | The vulnerable input file created can be passed as an argument to the fuzzer to triage the issue. 13 | 14 | ## Manual usage based on Ledger container 15 | 16 | ### Preparation 17 | 18 | The fuzzer can run from the docker `ledger-app-builder-legacy`. You can download it from the `ghcr.io` docker repository: 19 | 20 | ```console 21 | sudo docker pull ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-legacy:latest 22 | ``` 23 | 24 | You can then enter this development environment by executing the following command from the repository root directory: 25 | 26 | ```console 27 | sudo docker run --rm -ti --user "$(id -u):$(id -g)" -v "$(realpath .):/app" ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-legacy:latest 28 | ``` 29 | 30 | ### Compilation 31 | 32 | Once in the container, go into the `fuzzing` folder to compile the fuzzer: 33 | 34 | ```console 35 | cd fuzzing 36 | 37 | # cmake initialization 38 | cmake -DBOLOS_SDK=/opt/ledger-secure-sdk -DCMAKE_C_COMPILER=/usr/bin/clang -Bbuild -H. 39 | 40 | # Fuzzer compilation 41 | make -C build 42 | ``` 43 | 44 | ### Run 45 | 46 | ```console 47 | ./build/ainpt_harness 48 | ./build/da_harness 49 | ./build/epk_harness 50 | ./build/stx_harness 51 | ``` 52 | 53 | ## Notes 54 | 55 | For more context regarding fuzzing check out the app-boilerplate fuzzing [README.md](https://github.com/LedgerHQ/app-boilerplate/blob/master/fuzzing/README.md) -------------------------------------------------------------------------------- /fuzzing/include/glyphs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | // Code taken from: https://github.com/LedgerHQ/app-cardano/blob/develop/fuzzing/src/os_mocks.c 6 | 7 | extern const bagl_icon_details_t C_icon_crossmark; 8 | extern const bagl_icon_details_t C_icon_processing; 9 | extern const bagl_icon_details_t C_icon_eye; 10 | extern const bagl_icon_details_t C_icon_validate_14; 11 | extern const bagl_icon_details_t C_icon_left; 12 | extern const bagl_icon_details_t C_icon_right; 13 | extern const bagl_icon_details_t C_icon_app; 14 | extern const bagl_icon_details_t C_icon_dashboard_x; 15 | extern const bagl_icon_details_t C_icon_back; 16 | extern const bagl_icon_details_t C_icon_certificate; 17 | extern const bagl_icon_details_t C_icon_coggle; 18 | extern const bagl_icon_details_t C_app_logo_16px; 19 | extern const bagl_icon_details_t C_icon_warning; -------------------------------------------------------------------------------- /fuzzing/src/ainpt_harness.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "commands/attestinput/ainpt_handler.h" 10 | 11 | 12 | uint8_t G_io_apdu_buffer[IO_APDU_BUFFER_SIZE]; 13 | 14 | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 15 | uint8_t *input = NULL; 16 | 17 | while (size > 5) { 18 | uint8_t ins = data[0]; 19 | uint8_t p1 = data[1]; 20 | uint8_t p2 = data[2]; 21 | uint8_t lc = data[3]; 22 | 23 | data += sizeof(uint8_t) * 4; 24 | size -= sizeof(uint8_t) * 4; 25 | 26 | if (size < lc) { 27 | return 0; 28 | } 29 | 30 | uint8_t *input = malloc(lc); 31 | if (input == NULL) { 32 | return 0; 33 | } 34 | 35 | memcpy(input, data, lc); 36 | 37 | data += lc; 38 | size -= lc; 39 | 40 | buffer_t buf; 41 | buffer_init(&buf, input, lc); 42 | 43 | BEGIN_TRY { 44 | TRY { 45 | handler_attest_input(&buf, p1, p2); 46 | } 47 | CATCH_ALL { 48 | } 49 | FINALLY { 50 | } 51 | } 52 | END_TRY; 53 | 54 | free(input); 55 | } 56 | return 0; 57 | } -------------------------------------------------------------------------------- /fuzzing/src/da_harness.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "commands/deriveaddress/da_handler.h" 10 | 11 | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 12 | uint8_t *input = NULL; 13 | 14 | while (size > 5) { 15 | uint8_t ins = data[0]; 16 | uint8_t p1 = data[1]; 17 | uint8_t p2 = data[2]; 18 | uint8_t lc = data[3]; 19 | 20 | data += sizeof(uint8_t) * 4; 21 | size -= sizeof(uint8_t) * 4; 22 | 23 | if (size < lc) { 24 | return 0; 25 | } 26 | 27 | uint8_t *input = malloc(lc); 28 | if (input == NULL) { 29 | return 0; 30 | } 31 | 32 | memcpy(input, data, lc); 33 | 34 | data += lc; 35 | size -= lc; 36 | 37 | buffer_t buf; 38 | buffer_init(&buf, input, lc); 39 | 40 | BEGIN_TRY { 41 | TRY { 42 | handler_derive_address(&buf, p1 == 2, p2 == 2); 43 | } 44 | CATCH_ALL { 45 | } 46 | FINALLY { 47 | } 48 | } 49 | END_TRY; 50 | 51 | free(input); 52 | } 53 | return 0; 54 | } -------------------------------------------------------------------------------- /fuzzing/src/epk_harness.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "commands/extpubkey/epk_handler.h" 10 | 11 | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 12 | uint8_t *input = NULL; 13 | 14 | while (size > 5) { 15 | uint8_t ins = data[0]; 16 | uint8_t p1 = data[1]; 17 | uint8_t p2 = data[2]; 18 | uint8_t lc = data[3]; 19 | 20 | data += sizeof(uint8_t) * 4; 21 | size -= sizeof(uint8_t) * 4; 22 | 23 | if (size < lc) { 24 | return 0; 25 | } 26 | 27 | uint8_t *input = malloc(lc); 28 | if (input == NULL) { 29 | return 0; 30 | } 31 | 32 | memcpy(input, data, lc); 33 | 34 | data += lc; 35 | size -= lc; 36 | 37 | buffer_t buf; 38 | buffer_init(&buf, input, lc); 39 | 40 | BEGIN_TRY { 41 | TRY { 42 | handler_get_extended_public_key(&buf, p1 == 2); 43 | } 44 | CATCH_ALL { 45 | } 46 | FINALLY { 47 | } 48 | } 49 | END_TRY; 50 | 51 | free(input); 52 | } 53 | return 0; 54 | } -------------------------------------------------------------------------------- /fuzzing/src/stx_harness.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "commands/signtx/stx_handler.h" 10 | 11 | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 12 | uint8_t *input = NULL; 13 | 14 | while (size > 5) { 15 | uint8_t ins = data[0]; 16 | uint8_t p1 = data[1]; 17 | uint8_t p2 = data[2]; 18 | uint8_t lc = data[3]; 19 | 20 | data += sizeof(uint8_t) * 4; 21 | size -= sizeof(uint8_t) * 4; 22 | 23 | if (size < lc) { 24 | return 0; 25 | } 26 | 27 | uint8_t *input = malloc(lc); 28 | if (input == NULL) { 29 | return 0; 30 | } 31 | 32 | memcpy(input, data, lc); 33 | 34 | data += lc; 35 | size -= lc; 36 | 37 | buffer_t buf; 38 | buffer_init(&buf, input, lc); 39 | 40 | BEGIN_TRY { 41 | TRY { 42 | handler_sign_transaction(&buf, p1, p2); 43 | } 44 | CATCH_ALL { 45 | } 46 | FINALLY { 47 | } 48 | } 49 | END_TRY; 50 | 51 | free(input); 52 | } 53 | return 0; 54 | } -------------------------------------------------------------------------------- /fuzzing/src/utils/crc32.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #ifdef FUZZING 7 | #define explicit_bzero(addr, size) memset((addr), 0, (size)) 8 | #endif 9 | #define BUFFER_SIZE_PARANOIA 1024 10 | 11 | // Code taken from: https://www.hackersdelight.org/hdcodetxt/crc.c.txt option crc32b 12 | 13 | uint32_t cx_crc32(const uint8_t* inBuffer, size_t inSize) { 14 | ASSERT(inSize < BUFFER_SIZE_PARANOIA); 15 | 16 | uint32_t byte, crc, mask; 17 | 18 | crc = 0xFFFFFFFF; 19 | for (size_t i = 0; i < inSize; i++) { 20 | byte = inBuffer[i]; 21 | crc = crc ^ byte; 22 | 23 | for (uint32_t j = 0; j < 8; j++) { 24 | mask = -(crc & 1); 25 | crc = (crc >> 1) ^ (0xEDB88320 & mask); 26 | } 27 | } 28 | 29 | return ~crc; 30 | } -------------------------------------------------------------------------------- /fuzzing/src/utils/glyphs.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // Code taken from: https://github.com/LedgerHQ/app-cardano/blob/develop/fuzzing/src/os_mocks.c 5 | 6 | const bagl_icon_details_t C_icon_crossmark = {0}; 7 | const bagl_icon_details_t C_icon_processing = {0}; 8 | const bagl_icon_details_t C_icon_eye = {0}; 9 | const bagl_icon_details_t C_icon_validate_14 = {0}; 10 | const bagl_icon_details_t C_icon_left = {0}; 11 | const bagl_icon_details_t C_icon_right = {0}; 12 | const bagl_icon_details_t C_icon_app = {0}; 13 | const bagl_icon_details_t C_icon_dashboard_x = {0}; 14 | const bagl_icon_details_t C_icon_back = {0}; 15 | const bagl_icon_details_t C_icon_certificate = {0}; 16 | const bagl_icon_details_t C_icon_coggle = {0}; 17 | const bagl_icon_details_t C_app_logo_16px = {0}; 18 | const bagl_icon_details_t C_icon_warning = {0}; -------------------------------------------------------------------------------- /glyphs/app_logo_16px.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ergoplatform/ledger-app-ergo/5cdf7614c799d250c0c20a592ac9a7585dab8c1e/glyphs/app_logo_16px.gif -------------------------------------------------------------------------------- /glyphs/app_logo_64px.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ergoplatform/ledger-app-ergo/5cdf7614c799d250c0c20a592ac9a7585dab8c1e/glyphs/app_logo_64px.gif -------------------------------------------------------------------------------- /icons/app_14px.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ergoplatform/ledger-app-ergo/5cdf7614c799d250c0c20a592ac9a7585dab8c1e/icons/app_14px.gif -------------------------------------------------------------------------------- /icons/app_16px.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ergoplatform/ledger-app-ergo/5cdf7614c799d250c0c20a592ac9a7585dab8c1e/icons/app_16px.gif -------------------------------------------------------------------------------- /icons/app_32px.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ergoplatform/ledger-app-ergo/5cdf7614c799d250c0c20a592ac9a7585dab8c1e/icons/app_32px.gif -------------------------------------------------------------------------------- /icons/app_40px.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ergoplatform/ledger-app-ergo/5cdf7614c799d250c0c20a592ac9a7585dab8c1e/icons/app_40px.gif -------------------------------------------------------------------------------- /ledger_app.toml: -------------------------------------------------------------------------------- 1 | [app] 2 | build_directory = "./" 3 | sdk = "C" 4 | devices = ["nanox", "nanos+", "stax", "flex"] 5 | 6 | [tests] 7 | unit_directory = "./unit-tests/" 8 | -------------------------------------------------------------------------------- /src/apdu_dispatcher.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "apdu_dispatcher.h" 7 | #include "constants.h" 8 | #include "sw.h" 9 | #include "commands/app_name.h" 10 | #include "commands/app_version.h" 11 | #include "commands/extpubkey/epk_handler.h" 12 | #include "commands/deriveaddress/da_handler.h" 13 | #include "commands/attestinput/ainpt_handler.h" 14 | #include "commands/signtx/stx_handler.h" 15 | 16 | int apdu_dispatcher(const command_t *cmd) { 17 | if (cmd->cla != CLA) { 18 | return io_send_sw(SW_CLA_NOT_SUPPORTED); 19 | } 20 | 21 | buffer_t buf; 22 | buffer_init(&buf, cmd->data, cmd->lc); 23 | 24 | switch (cmd->ins) { 25 | case CMD_GET_APP_VERSION: 26 | if (cmd->p1 != 0 || cmd->p2 != 0) { 27 | return io_send_sw(SW_WRONG_P1P2); 28 | } 29 | return handler_get_version(); 30 | case CMD_GET_APP_NAME: 31 | if (cmd->p1 != 0 || cmd->p2 != 0) { 32 | return io_send_sw(SW_WRONG_P1P2); 33 | } 34 | return handler_get_app_name(); 35 | case CMD_GET_EXTENDED_PUBLIC_KEY: 36 | if (cmd->p1 == 0 || cmd->p1 > 2 || cmd->p2 > 0) { 37 | return io_send_sw(SW_WRONG_P1P2); 38 | } 39 | return handler_get_extended_public_key(&buf, cmd->p1 == 2); 40 | case CMD_DERIVE_ADDRESS: 41 | if (cmd->p1 == 0 || cmd->p1 > 2 || cmd->p2 == 0 || cmd->p2 > 2) { 42 | return io_send_sw(SW_WRONG_P1P2); 43 | } 44 | return handler_derive_address(&buf, cmd->p1 == 2, cmd->p2 == 2); 45 | case CMD_ATTEST_INPUT_BOX: 46 | if (cmd->p1 == 0 || cmd->p2 == 0) { 47 | return io_send_sw(SW_WRONG_P1P2); 48 | } 49 | return handler_attest_input(&buf, cmd->p1, cmd->p2); 50 | case CMD_SIGN_TRANSACTION: 51 | if (cmd->p1 == 0 || cmd->p2 == 0) { 52 | return io_send_sw(SW_WRONG_P1P2); 53 | } 54 | return handler_sign_transaction(&buf, cmd->p1, cmd->p2); 55 | default: 56 | return io_send_sw(SW_INS_NOT_SUPPORTED); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/apdu_dispatcher.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | * Enumeration with expected INS of APDU commands. 7 | */ 8 | typedef enum { 9 | CMD_NONE = 0x00, /// empty command 10 | CMD_GET_APP_VERSION = 0x01, /// version of the application 11 | CMD_GET_APP_NAME = 0x02, /// application name 12 | CMD_GET_EXTENDED_PUBLIC_KEY = 0x10, /// extended public key of corresponding BIP32 path 13 | CMD_DERIVE_ADDRESS = 0x11, /// derive address for corresponding BIP32 path 14 | CMD_ATTEST_INPUT_BOX = 0x20, /// attest input box command 15 | CMD_SIGN_TRANSACTION = 0x21 /// sign transaction with BIP32 path 16 | } command_e; 17 | 18 | /** 19 | * Dispatch APDU command received to the right handler. 20 | * 21 | * @param[in] cmd 22 | * Structured APDU command (CLA, INS, P1, P2, Lc, Command data). 23 | * 24 | * @return zero or positive integer if success, negative integer otherwise. 25 | * 26 | */ 27 | int apdu_dispatcher(const command_t *cmd); 28 | -------------------------------------------------------------------------------- /src/app_main.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Ledger App Boilerplate. 3 | * (c) 2020 Ledger SAS. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | *****************************************************************************/ 17 | 18 | #include // uint*_t 19 | #include // memset, explicit_bzero 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "context.h" 28 | #include "sw.h" 29 | #include "apdu_dispatcher.h" 30 | #include "ui/ui_menu.h" 31 | #include "common/macros_ext.h" 32 | 33 | /** 34 | * Handle APDU command received and send back APDU response using handlers. 35 | */ 36 | void app_main() { 37 | // Length of APDU command received in G_io_apdu_buffer 38 | int input_len = 0; 39 | // Structured APDU command 40 | command_t cmd; 41 | 42 | // Initialize the NVM data if required 43 | if (N_storage.initialized != 0x01) { 44 | internal_storage_t storage; 45 | storage.blind_signing_enabled = 0x00; 46 | storage.initialized = 0x01; 47 | nvm_write((void *) &N_storage, &storage, sizeof(internal_storage_t)); 48 | } 49 | 50 | // Initialize I/O 51 | io_init(); 52 | 53 | // Initialize Global App Context 54 | app_init(); 55 | 56 | // Show main menu 57 | ui_menu_main(); 58 | 59 | for (;;) { 60 | // Receive command bytes in G_io_apdu_buffer 61 | if ((input_len = io_recv_command()) < 0) { 62 | PRINTF("=> io_recv_command failure\n"); 63 | return; 64 | } 65 | 66 | // Parse APDU command from G_io_apdu_buffer 67 | if (!apdu_parser(&cmd, G_io_apdu_buffer, input_len)) { 68 | PRINTF("=> /!\\ BAD LENGTH: %.*H\n", input_len, G_io_apdu_buffer); 69 | io_send_sw(SW_WRONG_APDU_DATA_LENGTH); 70 | continue; 71 | } 72 | 73 | PRINTF("=> CLA=%02X | INS=%02X | P1=%02X | P2=%02X | Lc=%02X | CData=%.*H\n", 74 | cmd.cla, 75 | cmd.ins, 76 | cmd.p1, 77 | cmd.p2, 78 | cmd.lc, 79 | cmd.lc, 80 | cmd.data); 81 | 82 | // Dispatch structured APDU command to handler 83 | if (apdu_dispatcher(&cmd) < 0) { 84 | PRINTF("=> apdu_dispatcher failure\n"); 85 | return; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/commands/app_name.c: -------------------------------------------------------------------------------- 1 | #include // uint*_t 2 | 3 | #include 4 | 5 | #include "app_name.h" 6 | #include "../constants.h" 7 | #include "../helpers/response.h" 8 | #include "../common/rwbuffer.h" 9 | 10 | int handler_get_app_name() { 11 | _Static_assert(APPNAME_LEN < MAX_APPNAME_LEN, "APPNAME must be at most 64 characters!"); 12 | 13 | RW_BUFFER_FROM_ARRAY_FULL(buf, (uint8_t *) PIC(APPNAME), APPNAME_LEN); 14 | return res_ok_data(&buf); 15 | } 16 | -------------------------------------------------------------------------------- /src/commands/app_name.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * Handler for CMD_GET_APP_NAME command. Send APDU response with ASCII 5 | * encoded name of the application. 6 | * 7 | * @see variable APPNAME in Makefile. 8 | * 9 | * @return zero or positive integer if success, negative integer otherwise. 10 | * 11 | */ 12 | int handler_get_app_name(void); -------------------------------------------------------------------------------- /src/commands/app_version.c: -------------------------------------------------------------------------------- 1 | #include // uint*_t 2 | #include // UINT8_MAX 3 | #include // _Static_assert 4 | #include "app_version.h" 5 | #include "../constants.h" 6 | #include "../helpers/response.h" 7 | #include "../common/rwbuffer.h" 8 | 9 | int handler_get_version() { 10 | _Static_assert(APPVERSION_LEN == 4, "Length of (MAJOR || MINOR || PATCH || DEBUG) must be 4!"); 11 | _Static_assert(MAJOR_VERSION >= 0 && MAJOR_VERSION <= UINT8_MAX, 12 | "MAJOR version must be between 0 and 255!"); 13 | _Static_assert(MINOR_VERSION >= 0 && MINOR_VERSION <= UINT8_MAX, 14 | "MINOR version must be between 0 and 255!"); 15 | _Static_assert(PATCH_VERSION >= 0 && PATCH_VERSION <= UINT8_MAX, 16 | "PATCH version must be between 0 and 255!"); 17 | uint8_t version[APPVERSION_LEN] = {MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION, 0}; 18 | #ifdef DEBUG_BUILD 19 | version[APPVERSION_LEN - 1] = 1; 20 | #endif 21 | 22 | RW_BUFFER_FROM_ARRAY_FULL(buf, version, APPVERSION_LEN); 23 | return res_ok_data(&buf); 24 | } 25 | -------------------------------------------------------------------------------- /src/commands/app_version.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * Handler gor CMD_GET_VERSION command. Send APDU response with version 5 | * of the application. 6 | * 7 | * @see MAJOR_VERSION, MINOR_VERSION and PATCH_VERSION in Makefile. 8 | * 9 | * @return zero or positive integer if success, negative integer otherwise. 10 | * 11 | */ 12 | int handler_get_version(void); -------------------------------------------------------------------------------- /src/commands/attestinput/ainpt_context.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "../../constants.h" 7 | #include "../../ergo/tx_ser_box.h" 8 | #include "../../helpers/input_frame.h" 9 | #include "../../ui/ui_application_id.h" 10 | 11 | typedef struct { 12 | char app_token[APPLICATION_ID_STR_LEN]; // hexified app token 13 | uint32_t app_token_value; 14 | } _attest_input_ui_ctx_t; 15 | 16 | typedef enum { 17 | ATTEST_INPUT_STATE_INITIALIZED, 18 | ATTEST_INPUT_STATE_FINISHED, 19 | ATTEST_INPUT_STATE_APPROVED, 20 | ATTEST_INPUT_STATE_ERROR 21 | } attest_input_state_e; 22 | 23 | typedef struct { 24 | uint8_t box_id[ERGO_ID_LEN]; // TXID && BOXID 25 | token_table_t tokens_table; 26 | union { 27 | uint64_t token_amounts[TOKEN_MAX_COUNT]; 28 | _attest_input_ui_ctx_t ui; 29 | }; 30 | ergo_tx_serializer_box_context_t box; 31 | cx_blake2b_t hash; 32 | uint16_t box_index; 33 | uint8_t session; 34 | attest_input_state_e state; 35 | } attest_input_ctx_t; 36 | -------------------------------------------------------------------------------- /src/commands/attestinput/ainpt_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // size_t 4 | #include // bool 5 | #include // uint*_t 6 | 7 | #include 8 | 9 | #include "ainpt_context.h" 10 | 11 | typedef enum { 12 | ATTEST_INPUT_SUBCOMMAND_INIT = 0x01, 13 | ATTEST_INPUT_SUBCOMMAND_TREE_CHUNK = 0x02, 14 | ATTEST_INPUT_SUBCOMMAND_TOKENS = 0x03, 15 | ATTEST_INPUT_SUBCOMMAND_REGISTERS = 0x04, 16 | ATTEST_INPUT_SUBCOMMAND_GET_RESPONSE_FRAME = 0x05 17 | } attest_input_subcommand_e; 18 | 19 | /** 20 | * Handler for CMD_ATTEST_INPUT command. 21 | * 22 | * @param[in,out] cdata 23 | * Command data. Check APDU documentation. 24 | * @param[in] subcommand 25 | * Subcommand identifier. 26 | * @param[in] session_or_token 27 | * Whether data has access token or not, or session id (depends on subcommand) 28 | * @param[in] app_context 29 | * Whether data has access token or not, or session id (depends on subcommand) 30 | * 31 | * @return zero or positive integer if success, negative integer otherwise. 32 | * 33 | */ 34 | int handler_attest_input(buffer_t *cdata, 35 | attest_input_subcommand_e subcommand, 36 | uint8_t session_or_token); 37 | -------------------------------------------------------------------------------- /src/commands/attestinput/ainpt_response.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "ainpt_response.h" 4 | #include "ainpt_context.h" 5 | #include "../../helpers/response.h" 6 | #include "../../helpers/input_frame.h" 7 | 8 | #define WRITE_ERROR_HANDLER send_error 9 | #include "../../helpers/cmd_macros.h" 10 | 11 | static inline uint8_t get_frames_count(uint8_t tokens_count) { 12 | uint8_t frames_count = (tokens_count + (FRAME_MAX_TOKENS_COUNT - 1)) / FRAME_MAX_TOKENS_COUNT; 13 | return frames_count == 0 ? 1 : frames_count; 14 | } 15 | 16 | static inline int send_error(uint16_t error) { 17 | app_set_current_command(CMD_NONE); 18 | return res_error(error); 19 | } 20 | 21 | int send_response_attested_input_frame(attest_input_ctx_t *ctx, 22 | const uint8_t session_key[static SESSION_KEY_LEN], 23 | uint8_t index) { 24 | uint8_t frames_count = get_frames_count(ctx->tokens_table.count); 25 | if (index >= frames_count) { 26 | return send_error(SW_BAD_FRAME_INDEX); 27 | } 28 | 29 | // Hack for the stack overflow. Writing directly to the IO buffer. 30 | // Not elegant but works. 31 | RW_BUFFER_FROM_ARRAY_EMPTY(output, G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 2); 32 | 33 | CHECK_WRITE_PARAM(rw_buffer_write_bytes(&output, ctx->box_id, ERGO_ID_LEN)); 34 | CHECK_WRITE_PARAM(rw_buffer_write_u8(&output, frames_count)); 35 | CHECK_WRITE_PARAM(rw_buffer_write_u8(&output, index)); 36 | CHECK_WRITE_PARAM(rw_buffer_write_u64(&output, ctx->box.value, BE)); 37 | 38 | uint8_t tokens_count = (ctx->tokens_table.count - index * FRAME_MAX_TOKENS_COUNT); 39 | tokens_count = MIN(tokens_count, FRAME_MAX_TOKENS_COUNT); 40 | CHECK_WRITE_PARAM(rw_buffer_write_u8(&output, tokens_count)); 41 | 42 | uint8_t offset = index * FRAME_MAX_TOKENS_COUNT; 43 | if (offset + tokens_count > 255) { 44 | return send_error(SW_TOO_MUCH_DATA); 45 | } 46 | for (uint8_t i = offset; i < (uint8_t) (offset + tokens_count); i++) { 47 | CHECK_WRITE_PARAM(rw_buffer_write_bytes(&output, ctx->tokens_table.tokens[i], ERGO_ID_LEN)); 48 | CHECK_WRITE_PARAM(rw_buffer_write_u64(&output, ctx->token_amounts[i], BE)); 49 | } 50 | 51 | CHECK_WRITE_PARAM(rw_buffer_can_write(&output, CX_SHA256_SIZE)); 52 | cx_hmac_sha256(session_key, 53 | SESSION_KEY_LEN, 54 | rw_buffer_read_ptr(&output), 55 | rw_buffer_data_len(&output), 56 | rw_buffer_write_ptr(&output), 57 | CX_SHA256_SIZE); 58 | CHECK_WRITE_PARAM(rw_buffer_seek_write_cur(&output, INPUT_FRAME_SIGNATURE_LEN)); 59 | 60 | return res_ok_data(&output); 61 | } 62 | 63 | int send_response_attested_input_frame_count(uint8_t tokens_count) { 64 | uint8_t frames_count = get_frames_count(tokens_count); 65 | RW_BUFFER_FROM_VAR_FULL(buf, frames_count); 66 | return res_ok_data(&buf); 67 | } 68 | 69 | int send_response_attested_input_session_id(uint8_t session_id) { 70 | RW_BUFFER_FROM_VAR_FULL(buf, session_id); 71 | return res_ok_data(&buf); 72 | } 73 | -------------------------------------------------------------------------------- /src/commands/attestinput/ainpt_response.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // uint*_t 4 | #include "../../constants.h" 5 | #include "ainpt_context.h" 6 | 7 | /** 8 | * Send APDU response with Attested Input frame 9 | * 10 | * response = 11 | * 12 | * @return zero or positive integer if success, -1 otherwise. 13 | * 14 | */ 15 | int send_response_attested_input_frame(attest_input_ctx_t *ctx, 16 | const uint8_t session_key[static SESSION_KEY_LEN], 17 | uint8_t index); 18 | 19 | /** 20 | * Send APDU response with Attested Input frame count 21 | * 22 | * response = (uint8_t)frame_count 23 | * 24 | * @return zero or positive integer if success, -1 otherwise. 25 | * 26 | */ 27 | int send_response_attested_input_frame_count(uint8_t tokens_count); 28 | 29 | /** 30 | * Send APDU response with session_id 31 | * 32 | * response = (uint8_t)session_id 33 | * 34 | * @return zero or positive integer if success, -1 otherwise. 35 | * 36 | */ 37 | int send_response_attested_input_session_id(uint8_t session_id); 38 | -------------------------------------------------------------------------------- /src/commands/attestinput/ainpt_ui.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "ainpt_context.h" 7 | 8 | /** 9 | * Display application access_token on the device and ask confirmation to proceed. 10 | * 11 | * @return 0 if success, negative integer otherwise. 12 | * 13 | */ 14 | int ui_display_access_token(uint32_t app_access_token, attest_input_ctx_t* context); -------------------------------------------------------------------------------- /src/commands/attestinput/ainpt_ui_bagl.c: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_BAGL 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "ainpt_ui.h" 8 | #include "ainpt_response.h" 9 | #include "../../context.h" 10 | #include "../../common/macros_ext.h" 11 | #include "../../helpers/response.h" 12 | #include "../../ui/ui_application_id.h" 13 | #include "../../ui/ui_approve_reject.h" 14 | #include "../../ui/ui_menu.h" 15 | #include "../../ui/ui_main.h" 16 | 17 | // Step with icon and text 18 | UX_STEP_NOCB(ux_ainpt_display_confirm_step, pn, {&C_icon_processing, "Confirm Attest Input"}); 19 | 20 | static NOINLINE void ui_action_attest_input(bool approved, void* context) { 21 | attest_input_ctx_t* ctx = (attest_input_ctx_t*) context; 22 | app_set_ui_busy(false); 23 | 24 | if (approved) { 25 | app_set_connected_app_id(ctx->ui.app_token_value); 26 | ctx->state = ATTEST_INPUT_STATE_APPROVED; 27 | send_response_attested_input_session_id(ctx->session); 28 | } else { 29 | app_set_current_command(CMD_NONE); 30 | res_deny(); 31 | } 32 | 33 | ui_menu_main(); 34 | } 35 | 36 | int ui_display_access_token(uint32_t app_access_token, attest_input_ctx_t* context) { 37 | context->ui.app_token_value = app_access_token; 38 | 39 | uint8_t screen = 0; 40 | ui_add_screen(&ux_ainpt_display_confirm_step, &screen); 41 | 42 | if (app_access_token != 0) { 43 | ui_add_screen(ui_application_id_screen(app_access_token, context->ui.app_token), &screen); 44 | } 45 | 46 | ui_approve_reject_screens(ui_action_attest_input, 47 | context, 48 | ui_next_sreen_ptr(&screen), 49 | ui_next_sreen_ptr(&screen)); 50 | ui_display_screens(&screen); 51 | 52 | return 0; 53 | } 54 | 55 | #endif -------------------------------------------------------------------------------- /src/commands/attestinput/ainpt_ui_nbgl.c: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_NBGL 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "ainpt_ui.h" 9 | #include "ainpt_response.h" 10 | #include "../../context.h" 11 | #include "../../common/macros_ext.h" 12 | #include "../../helpers/response.h" 13 | #include "../../ui/ui_application_id.h" 14 | #include "../../ui/ui_menu.h" 15 | #include "../../ui/ui_main.h" 16 | #include "../../ui/display.h" 17 | 18 | #define APPLICATION_ID_SUBLEN APPLICATION_ID_STR_LEN + 13 19 | static char sub_message[APPLICATION_ID_SUBLEN]; 20 | 21 | void ui_action_attest_input(bool approved) { 22 | set_flow_response(approved); 23 | } 24 | 25 | int ui_display_access_token(uint32_t app_access_token, attest_input_ctx_t* context) { 26 | context->ui.app_token_value = app_access_token; 27 | 28 | if (app_access_token != 0) { 29 | ui_application_id_screen(app_access_token, context->ui.app_token); 30 | memset(sub_message, 0, APPLICATION_ID_SUBLEN); 31 | snprintf(sub_message, APPLICATION_ID_SUBLEN, "Application: 0x%08x", app_access_token); 32 | } 33 | 34 | nbgl_useCaseChoice(&VALIDATE_ICON, 35 | "Confirm Attest Input", 36 | sub_message, 37 | "Confirm", 38 | "Reject", 39 | ui_action_attest_input); 40 | bool approved = io_ui_process(); 41 | 42 | if (approved) { 43 | app_set_connected_app_id(context->ui.app_token_value); 44 | context->state = ATTEST_INPUT_STATE_APPROVED; 45 | send_response_attested_input_session_id(context->session); 46 | 47 | } else { 48 | app_set_current_command(CMD_NONE); 49 | res_deny(); 50 | 51 | ui_menu_main(); 52 | } 53 | 54 | return 0; 55 | } 56 | 57 | #endif -------------------------------------------------------------------------------- /src/commands/deriveaddress/da_context.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "../../constants.h" 5 | #include "../../ui/ui_application_id.h" 6 | #include "../../ergo/address.h" 7 | 8 | typedef struct { 9 | uint32_t app_token_value; 10 | uint8_t raw_address[P2PK_ADDRESS_LEN]; // response data address 11 | char bip32_path[MAX_BIP32_STRING_LEN]; // Bip32 path string 12 | char address[P2PK_ADDRESS_STRING_MAX_LEN]; // Address string 13 | char app_id[APPLICATION_ID_STR_LEN]; // hexified app token 14 | bool send; 15 | } derive_address_ctx_t; -------------------------------------------------------------------------------- /src/commands/deriveaddress/da_handler.c: -------------------------------------------------------------------------------- 1 | #include // uint*_t 2 | #include // bool 3 | #include // size_t 4 | #include // memset, explicit_bzero 5 | 6 | #include 7 | #include 8 | 9 | #include "da_handler.h" 10 | #include "da_ui.h" 11 | #include "da_response.h" 12 | #include "../../sw.h" 13 | #include "../../context.h" 14 | #include "../../helpers/crypto.h" 15 | #include "../../common/rwbuffer.h" 16 | #include "../../common/macros_ext.h" 17 | #include "../../helpers/response.h" 18 | #include "../../helpers/session_id.h" 19 | #include "../../ergo/address.h" 20 | 21 | #define COMMAND_ERROR_HANDLER handler_err 22 | #include "../../helpers/cmd_macros.h" 23 | 24 | static inline int handler_err(derive_address_ctx_t *_ctx, uint16_t err) { 25 | UNUSED(_ctx); 26 | app_set_current_command(CMD_NONE); 27 | return res_error(err); 28 | } 29 | 30 | int handler_derive_address(buffer_t *cdata, bool display, bool has_access_token) { 31 | if (app_is_ui_busy()) { 32 | return res_ui_busy(); 33 | } 34 | app_set_current_command(CMD_DERIVE_ADDRESS); 35 | 36 | derive_address_ctx_t *ctx = app_derive_address_context(); 37 | 38 | uint8_t bip32_path_len; 39 | uint32_t bip32_path[MAX_BIP32_PATH]; 40 | uint8_t public_key[PUBLIC_KEY_LEN]; 41 | 42 | uint32_t access_token = 0; 43 | uint8_t network_type = 0; 44 | 45 | CHECK_READ_PARAM(ctx, buffer_read_u8(cdata, &network_type)); 46 | CHECK_READ_PARAM(ctx, buffer_read_u8(cdata, &bip32_path_len)); 47 | CHECK_READ_PARAM(ctx, buffer_read_bip32_path(cdata, bip32_path, (size_t) bip32_path_len)); 48 | if (has_access_token) { 49 | CHECK_READ_PARAM(ctx, buffer_read_u32(cdata, &access_token, BE)); 50 | } 51 | CHECK_PARAMS_FINISHED(ctx, cdata); 52 | 53 | if (!bip32_path_validate(bip32_path, 54 | bip32_path_len, 55 | BIP32_HARDENED(44), 56 | BIP32_HARDENED(BIP32_ERGO_COIN), 57 | BIP32_PATH_VALIDATE_ADDRESS_GE5)) { 58 | return handler_err(ctx, SW_BIP32_BAD_PATH); 59 | } 60 | 61 | if (crypto_generate_public_key(bip32_path, bip32_path_len, public_key, NULL) != 0) { 62 | return handler_err(ctx, SW_INTERNAL_CRYPTO_ERROR); 63 | } 64 | 65 | if (!ergo_address_from_pubkey(network_type, public_key, ctx->raw_address)) { 66 | return res_error(SW_ADDRESS_GENERATION_FAILED); 67 | } 68 | 69 | if (!display && is_known_application(access_token, app_connected_app_id())) { 70 | return send_response_address(ctx->raw_address); 71 | } 72 | 73 | return ui_display_address(ctx, 74 | !display, 75 | access_token, 76 | bip32_path, 77 | bip32_path_len, 78 | ctx->raw_address); 79 | } -------------------------------------------------------------------------------- /src/commands/deriveaddress/da_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // size_t 4 | #include // bool 5 | #include // uint*_t 6 | 7 | #include 8 | 9 | /** 10 | * Handler for CMD_DERIVE_ADDRESS command. If successfully parse BIP32 path, 11 | * derive public key/chain code and send APDU response or show on screen. 12 | * 13 | * @param[in,out] cdata 14 | * Command data with BIP32 path and optional access token 15 | * @param[in] display 16 | * Show address on screen or return through APDU 17 | * @param[in] has_access_token 18 | * Whether data has access token or not 19 | * 20 | * @return zero or positive integer if success, negative integer otherwise. 21 | * 22 | */ 23 | int handler_derive_address(buffer_t *cdata, bool display, bool has_access_token); -------------------------------------------------------------------------------- /src/commands/deriveaddress/da_response.c: -------------------------------------------------------------------------------- 1 | #include // size_t 2 | #include // uint*_t 3 | #include // memmove 4 | 5 | #include "da_response.h" 6 | #include "../../sw.h" 7 | #include "../../constants.h" 8 | #include "../../context.h" 9 | #include "../../common/rwbuffer.h" 10 | #include "../../helpers/response.h" 11 | #include "../../ergo/address.h" 12 | 13 | #define WRITE_ERROR_HANDLER send_error 14 | #include "../../helpers/cmd_macros.h" 15 | 16 | static inline int send_error(uint16_t error) { 17 | app_set_current_command(CMD_NONE); 18 | return res_error(error); 19 | } 20 | 21 | int send_response_address(uint8_t address[static P2PK_ADDRESS_LEN]) { 22 | RW_BUFFER_NEW_LOCAL_EMPTY(response, P2PK_ADDRESS_LEN); 23 | 24 | CHECK_WRITE_PARAM(rw_buffer_write_bytes(&response, address, P2PK_ADDRESS_LEN)); 25 | 26 | app_set_current_command(CMD_NONE); 27 | 28 | return res_ok_data(&response); 29 | } -------------------------------------------------------------------------------- /src/commands/deriveaddress/da_response.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../constants.h" 4 | #include "../../ergo/address.h" 5 | 6 | /** 7 | * Send APDU response with address public key. 8 | * 9 | * response = G_context.derive_ctx.raw_address (ADDRESS_LEN) 10 | * 11 | * @return zero or positive integer if success, -1 otherwise. 12 | * 13 | */ 14 | int send_response_address(uint8_t address[static P2PK_ADDRESS_LEN]); -------------------------------------------------------------------------------- /src/commands/deriveaddress/da_ui.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // uint* 4 | #include // bool 5 | #include "da_context.h" 6 | #include "../../constants.h" 7 | #include "../../context.h" 8 | #include "../../helpers/response.h" 9 | 10 | /** 11 | * Display account on the device and ask confirmation to export. 12 | * 13 | * @return 0 if success, negative integer otherwise. 14 | * 15 | */ 16 | int ui_display_address(derive_address_ctx_t* ctx, 17 | bool send, 18 | uint32_t app_access_token, 19 | uint32_t* bip32_path, 20 | uint8_t bip32_path_len, 21 | uint8_t raw_address[static P2PK_ADDRESS_LEN]); 22 | 23 | static inline int send_error(uint16_t err) { 24 | app_set_current_command(CMD_NONE); 25 | return res_error(err); 26 | } -------------------------------------------------------------------------------- /src/commands/extpubkey/epk_context.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "../../constants.h" 5 | #include "../../ui/ui_application_id.h" 6 | 7 | typedef struct { 8 | uint32_t app_token_value; 9 | uint8_t chain_code[CHAIN_CODE_LEN]; 10 | uint8_t raw_public_key[PUBLIC_KEY_LEN]; 11 | char bip32_path[MAX_BIP32_STRING_LEN]; // Bip32 path string 12 | char app_token[APPLICATION_ID_STR_LEN]; // hexified app token 13 | } extended_public_key_ctx_t; -------------------------------------------------------------------------------- /src/commands/extpubkey/epk_handler.c: -------------------------------------------------------------------------------- 1 | #include // uint*_t 2 | #include // bool 3 | #include // size_t 4 | #include // memset, explicit_bzero 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "epk_handler.h" 11 | #include "epk_ui.h" 12 | #include "epk_response.h" 13 | #include "../../sw.h" 14 | #include "../../context.h" 15 | #include "../../helpers/crypto.h" 16 | #include "../../helpers/response.h" 17 | #include "../../common/macros_ext.h" 18 | #include "../../helpers/session_id.h" 19 | 20 | #define COMMAND_ERROR_HANDLER handler_err 21 | #include "../../helpers/cmd_macros.h" 22 | 23 | static inline int handler_err(extended_public_key_ctx_t *_ctx, uint16_t err) { 24 | UNUSED(_ctx); 25 | app_set_current_command(CMD_NONE); 26 | return res_error(err); 27 | } 28 | 29 | int handler_get_extended_public_key(buffer_t *cdata, bool has_access_token) { 30 | if (app_is_ui_busy()) { 31 | return res_ui_busy(); 32 | } 33 | app_set_current_command(CMD_GET_EXTENDED_PUBLIC_KEY); 34 | 35 | extended_public_key_ctx_t *ctx = app_extended_public_key_context(); 36 | 37 | uint8_t bip32_path_len; 38 | uint32_t bip32_path[MAX_BIP32_PATH]; 39 | uint32_t access_token = 0; 40 | 41 | CHECK_READ_PARAM(ctx, buffer_read_u8(cdata, &bip32_path_len)); 42 | CHECK_READ_PARAM(ctx, buffer_read_bip32_path(cdata, bip32_path, (size_t) bip32_path_len)); 43 | if (has_access_token) { 44 | CHECK_READ_PARAM(ctx, buffer_read_u32(cdata, &access_token, BE)); 45 | } 46 | CHECK_PARAMS_FINISHED(ctx, cdata); 47 | 48 | if (!bip32_path_validate(bip32_path, 49 | bip32_path_len, 50 | BIP32_HARDENED(44), 51 | BIP32_HARDENED(BIP32_ERGO_COIN), 52 | BIP32_PATH_VALIDATE_ACCOUNT_GE3)) { 53 | return handler_err(ctx, SW_BIP32_BAD_PATH); 54 | } 55 | 56 | if (crypto_generate_public_key(bip32_path, 57 | bip32_path_len, 58 | ctx->raw_public_key, 59 | ctx->chain_code) != 0) { 60 | return handler_err(ctx, SW_INTERNAL_CRYPTO_ERROR); 61 | } 62 | 63 | if (is_known_application(access_token, app_connected_app_id())) { 64 | return send_response_extended_pubkey(ctx->raw_public_key, ctx->chain_code); 65 | } 66 | 67 | return ui_display_account(ctx, 68 | access_token, 69 | bip32_path, 70 | bip32_path_len, 71 | ctx->raw_public_key, 72 | ctx->chain_code); 73 | } -------------------------------------------------------------------------------- /src/commands/extpubkey/epk_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // size_t 4 | #include // bool 5 | #include // uint*_t 6 | 7 | #include 8 | 9 | /** 10 | * Handler for CMD_GET_EXTENDED_PUBLIC_KEY command. If successfully parse BIP32 path, 11 | * derive public key/chain code and send APDU response. 12 | * 13 | * @param[in,out] cdata 14 | * Command data with BIP32 path and optional access token 15 | * @param[in] has_access_token 16 | * Whether data has access token or not 17 | * 18 | * @return zero or positive integer if success, negative integer otherwise. 19 | * 20 | */ 21 | int handler_get_extended_public_key(buffer_t *cdata, bool has_access_token); -------------------------------------------------------------------------------- /src/commands/extpubkey/epk_response.c: -------------------------------------------------------------------------------- 1 | #include // size_t 2 | #include // uint*_t 3 | #include // memmove 4 | 5 | #include "epk_response.h" 6 | #include "../../sw.h" 7 | #include "../../constants.h" 8 | #include "../../context.h" 9 | #include "../../common/rwbuffer.h" 10 | #include "../../helpers/response.h" 11 | 12 | #define WRITE_ERROR_HANDLER send_error 13 | #include "../../helpers/cmd_macros.h" 14 | 15 | static inline int send_error(uint16_t error) { 16 | app_set_current_command(CMD_NONE); 17 | return res_error(error); 18 | } 19 | 20 | int send_response_extended_pubkey(uint8_t raw_public_key[static PUBLIC_KEY_LEN], 21 | uint8_t chain_code[static CHAIN_CODE_LEN]) { 22 | RW_BUFFER_NEW_LOCAL_EMPTY(response, EXTENDED_PUBLIC_KEY_LEN); 23 | 24 | // Compressed pubkey 25 | CHECK_WRITE_PARAM(rw_buffer_write_u8(&response, ((raw_public_key[64] & 1) ? 0x03 : 0x02))); 26 | CHECK_WRITE_PARAM(rw_buffer_write_bytes(&response, raw_public_key + 1, 32)); 27 | CHECK_WRITE_PARAM(rw_buffer_write_bytes(&response, chain_code, CHAIN_CODE_LEN)); 28 | 29 | app_set_current_command(CMD_NONE); 30 | 31 | return res_ok_data(&response); 32 | } -------------------------------------------------------------------------------- /src/commands/extpubkey/epk_response.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "../../constants.h" 5 | 6 | /** 7 | * Send APDU response with public key and chain code. 8 | * 9 | * response = G_context.ext_pub_ctx.raw_public_key (PUBLIC_KEY_LEN) || 10 | * G_context.ext_pub_ctx.chain_code (CHAIN_CODE_LEN) 11 | * 12 | * @return zero or positive integer if success, -1 otherwise. 13 | * 14 | */ 15 | int send_response_extended_pubkey(uint8_t raw_public_key[static PUBLIC_KEY_LEN], 16 | uint8_t chain_code[static CHAIN_CODE_LEN]); -------------------------------------------------------------------------------- /src/commands/extpubkey/epk_ui.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // uint* 4 | #include // bool 5 | #include "epk_context.h" 6 | #include "../../constants.h" 7 | 8 | /** 9 | * Display account on the device and ask confirmation to export. 10 | * 11 | * @return 0 if success, negative integer otherwise. 12 | * 13 | */ 14 | int ui_display_account(extended_public_key_ctx_t* ctx, 15 | uint32_t app_access_token, 16 | uint32_t* bip32_path, 17 | uint8_t bip32_path_len, 18 | uint8_t raw_pub_key[static PUBLIC_KEY_LEN], 19 | uint8_t chain_code[static CHAIN_CODE_LEN]); 20 | -------------------------------------------------------------------------------- /src/commands/extpubkey/epk_ui_bagl.c: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_BAGL 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "epk_ui.h" 9 | #include "epk_response.h" 10 | #include "../../context.h" 11 | #include "../../sw.h" 12 | #include "../../common/bip32_ext.h" 13 | #include "../../common/macros_ext.h" 14 | #include "../../helpers/response.h" 15 | #include "../../ui/ui_bip32_path.h" 16 | #include "../../ui/ui_application_id.h" 17 | #include "../../ui/ui_approve_reject.h" 18 | #include "../../ui/ui_menu.h" 19 | #include "../../ui/ui_main.h" 20 | 21 | // Step with icon and text 22 | UX_STEP_NOCB(ux_epk_display_confirm_ext_pubkey_step, pn, {&C_icon_warning, "Ext PubKey Export"}); 23 | 24 | static NOINLINE void ui_action_get_extended_pubkey(bool approved, void* context) { 25 | extended_public_key_ctx_t* ctx = (extended_public_key_ctx_t*) context; 26 | app_set_ui_busy(false); 27 | 28 | if (approved) { 29 | app_set_connected_app_id(ctx->app_token_value); 30 | send_response_extended_pubkey(ctx->raw_public_key, ctx->chain_code); 31 | explicit_bzero(ctx, sizeof(extended_public_key_ctx_t)); 32 | } else { 33 | explicit_bzero(ctx, sizeof(extended_public_key_ctx_t)); 34 | res_deny(); 35 | } 36 | 37 | app_set_current_command(CMD_NONE); 38 | 39 | ui_menu_main(); 40 | } 41 | 42 | int ui_display_account(extended_public_key_ctx_t* ctx, 43 | uint32_t app_access_token, 44 | uint32_t* bip32_path, 45 | uint8_t bip32_path_len, 46 | uint8_t raw_pub_key[static PUBLIC_KEY_LEN], 47 | uint8_t chain_code[static CHAIN_CODE_LEN]) { 48 | if (!bip32_path_validate(bip32_path, 49 | bip32_path_len, 50 | BIP32_HARDENED(44), 51 | BIP32_HARDENED(BIP32_ERGO_COIN), 52 | BIP32_PATH_VALIDATE_ACCOUNT_GE3)) { 53 | return res_error(SW_BIP32_BAD_PATH); 54 | } 55 | 56 | uint8_t screen = 0; 57 | ui_add_screen(&ux_epk_display_confirm_ext_pubkey_step, &screen); 58 | 59 | const ux_flow_step_t* b32_step = 60 | ui_bip32_path_screen(bip32_path, 61 | bip32_path_len, 62 | "Path", 63 | ctx->bip32_path, 64 | MEMBER_SIZE(extended_public_key_ctx_t, bip32_path), 65 | NULL, 66 | NULL); 67 | if (b32_step == NULL) { 68 | app_set_current_command(CMD_NONE); 69 | return res_error(SW_BIP32_FORMATTING_FAILED); 70 | } 71 | ui_add_screen(b32_step, &screen); 72 | 73 | if (app_access_token != 0) { 74 | ui_add_screen(ui_application_id_screen(app_access_token, ctx->app_token), &screen); 75 | } 76 | 77 | ui_approve_reject_screens(ui_action_get_extended_pubkey, 78 | ctx, 79 | ui_next_sreen_ptr(&screen), 80 | ui_next_sreen_ptr(&screen)); 81 | 82 | ctx->app_token_value = app_access_token; 83 | memmove(ctx->raw_public_key, raw_pub_key, PUBLIC_KEY_LEN); 84 | memmove(ctx->chain_code, chain_code, CHAIN_CODE_LEN); 85 | 86 | ui_display_screens(&screen); 87 | 88 | return 0; 89 | } 90 | 91 | #endif -------------------------------------------------------------------------------- /src/commands/extpubkey/epk_ui_nbgl.c: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_NBGL 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "epk_ui.h" 9 | #include "epk_response.h" 10 | #include "../../context.h" 11 | #include "../../sw.h" 12 | #include "../../common/bip32_ext.h" 13 | #include "../../common/macros_ext.h" 14 | #include "../../helpers/response.h" 15 | #include "../../ui/ui_bip32_path.h" 16 | #include "../../ui/ui_application_id.h" 17 | #include "../../ui/ui_approve_reject.h" 18 | #include "../../ui/ui_menu.h" 19 | #include "../../ui/ui_main.h" 20 | #include "../../ui/display.h" 21 | 22 | void ui_display_account_confirm(bool approved) { 23 | set_flow_response(approved); 24 | } 25 | 26 | #define PK_APPID_SIZE MAX_BIP32_STRING_LEN + APPLICATION_ID_STR_LEN + 13 + 1 27 | char pk_appid[PK_APPID_SIZE]; 28 | 29 | int ui_display_account(extended_public_key_ctx_t* ctx, 30 | uint32_t app_access_token, 31 | uint32_t* bip32_path, 32 | uint8_t bip32_path_len, 33 | uint8_t raw_pub_key[static PUBLIC_KEY_LEN], 34 | uint8_t chain_code[static CHAIN_CODE_LEN]) { 35 | if (!bip32_path_validate(bip32_path, 36 | bip32_path_len, 37 | BIP32_HARDENED(44), 38 | BIP32_HARDENED(BIP32_ERGO_COIN), 39 | BIP32_PATH_VALIDATE_ACCOUNT_GE3)) { 40 | return res_error(SW_BIP32_BAD_PATH); 41 | } 42 | 43 | if (!ui_bip32_path_screen(bip32_path, 44 | bip32_path_len, 45 | ctx->bip32_path, 46 | MEMBER_SIZE(derive_address_ctx_t, bip32_path))) { 47 | return res_error(SW_BIP32_BAD_PATH); 48 | } 49 | 50 | int bip32_str_len = strlen(ctx->bip32_path); 51 | 52 | memset(pk_appid, 0, PK_APPID_SIZE); 53 | strncpy(pk_appid, ctx->bip32_path, bip32_str_len); 54 | if (app_access_token != 0) { 55 | pk_appid[bip32_str_len] = '\n'; 56 | snprintf(*(&pk_appid) + bip32_str_len + 1, 57 | APPLICATION_ID_STR_LEN + 13, 58 | "Application: 0x%08x", 59 | app_access_token); 60 | } 61 | 62 | ctx->app_token_value = app_access_token; 63 | memmove(ctx->raw_public_key, raw_pub_key, PUBLIC_KEY_LEN); 64 | memmove(ctx->chain_code, chain_code, CHAIN_CODE_LEN); 65 | 66 | nbgl_useCaseChoice(&C_app_logo_64px, 67 | "Export Extended Public Key", 68 | pk_appid, 69 | "Confirm", 70 | "Cancel", 71 | ui_display_account_confirm); 72 | bool approved = io_ui_process(); 73 | 74 | if (approved) { 75 | app_set_connected_app_id(ctx->app_token_value); 76 | send_response_extended_pubkey(ctx->raw_public_key, ctx->chain_code); 77 | explicit_bzero(ctx, sizeof(extended_public_key_ctx_t)); 78 | } else { 79 | explicit_bzero(ctx, sizeof(extended_public_key_ctx_t)); 80 | res_deny(); 81 | } 82 | 83 | app_set_current_command(CMD_NONE); 84 | 85 | ui_menu_main(); 86 | 87 | return 0; 88 | } 89 | 90 | #endif -------------------------------------------------------------------------------- /src/commands/signtx/stx_amounts.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "../../constants.h" 7 | #include "../../ergo/tx_ser_full.h" 8 | #include "../../common/safeint.h" 9 | #include "../../sw.h" 10 | 11 | typedef struct { 12 | uint64_t fee; 13 | uint64_t value; 14 | int64_t tokens[TOKEN_MAX_COUNT]; 15 | token_table_t tokens_table; 16 | } sign_transaction_amounts_ctx_t; 17 | 18 | static inline void stx_amounts_init(sign_transaction_amounts_ctx_t *ctx) { 19 | memset(ctx, 0, sizeof(sign_transaction_amounts_ctx_t)); 20 | } 21 | 22 | static inline uint16_t stx_amounts_add_input(sign_transaction_amounts_ctx_t *ctx, uint64_t value) { 23 | // Add input amount to the stored value 24 | return checked_add_u64(ctx->value, value, &ctx->value) ? SW_OK : SW_U64_OVERFLOW; 25 | } 26 | 27 | ergo_tx_serializer_input_result_e stx_amounts_add_input_token( 28 | sign_transaction_amounts_ctx_t *ctx, 29 | const uint8_t box_id[static ERGO_ID_LEN], 30 | const uint8_t tn_id[static ERGO_ID_LEN], 31 | uint64_t value); 32 | 33 | ergo_tx_serializer_box_result_e stx_amounts_add_output(sign_transaction_amounts_ctx_t *ctx, 34 | ergo_tx_serializer_box_type_e type, 35 | uint64_t value); 36 | 37 | ergo_tx_serializer_box_result_e stx_amounts_add_output_token(sign_transaction_amounts_ctx_t *ctx, 38 | ergo_tx_serializer_box_type_e type, 39 | const uint8_t id[static ERGO_ID_LEN], 40 | uint64_t value); 41 | 42 | uint8_t stx_amounts_non_zero_tokens_count(const sign_transaction_amounts_ctx_t *ctx); 43 | 44 | uint8_t stx_amounts_non_zero_token_index(const sign_transaction_amounts_ctx_t *ctx, 45 | uint8_t zero_index); 46 | -------------------------------------------------------------------------------- /src/commands/signtx/stx_context.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "stx_types.h" 7 | #include "./operations/stx_op_p2pk.h" 8 | 9 | typedef struct { 10 | sign_transaction_state_e state; 11 | uint8_t session; 12 | sign_transaction_operation_type_e operation; 13 | union { 14 | sign_transaction_operation_p2pk_ctx_t p2pk; 15 | }; 16 | } sign_transaction_ctx_t; 17 | -------------------------------------------------------------------------------- /src/commands/signtx/stx_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // size_t 4 | #include // bool 5 | #include // uint*_t 6 | 7 | #include 8 | 9 | #include "stx_context.h" 10 | 11 | typedef enum { 12 | SIGN_TRANSACTION_SUBCOMMAND_SIGN_PK = 0x01, 13 | SIGN_TRANSACTION_SUBCOMMAND_START_TX = 0x10, 14 | SIGN_TRANSACTION_SUBCOMMAND_TOKEN_IDS = 0x11, 15 | SIGN_TRANSACTION_SUBCOMMAND_INPUT_FRAME = 0x12, 16 | SIGN_TRANSACTION_SUBCOMMAND_INPUT_CONTEXT_EXTENSION = 0x13, 17 | SIGN_TRANSACTION_SUBCOMMAND_DATA_INPUT = 0x14, 18 | SIGN_TRANSACTION_SUBCOMMAND_OUTPUT = 0x15, 19 | SIGN_TRANSACTION_SUBCOMMAND_OUTPUT_TREE_CHUNK = 0x16, 20 | SIGN_TRANSACTION_SUBCOMMAND_OUTPUT_MINERS_FEE_TREE = 0x17, 21 | SIGN_TRANSACTION_SUBCOMMAND_OUTPUT_CHANGE_TREE = 0x18, 22 | SIGN_TRANSACTION_SUBCOMMAND_OUTPUT_TOKENS = 0x19, 23 | SIGN_TRANSACTION_SUBCOMMAND_OUTPUT_REGISTERS = 0x1A, 24 | SIGN_TRANSACTION_SUBCOMMAND_CONFIRM = 0x20 25 | } sign_transaction_subcommand_e; 26 | 27 | /** 28 | * Handler for CMD_ATTEST_SIGN_TX command. 29 | * 30 | * @param[in,out] cdata 31 | * Command data. Check APDU documentation. 32 | * @param[in] subcommand 33 | * Subcommand identifier. 34 | * @param[in] session_or_token 35 | * Whether data has access token or not, or session id (depends on subcommand) 36 | * 37 | * @return zero or positive integer if success, negative integer otherwise. 38 | * 39 | */ 40 | int handler_sign_transaction(buffer_t *cdata, 41 | sign_transaction_subcommand_e subcommand, 42 | uint8_t session_or_token); 43 | -------------------------------------------------------------------------------- /src/commands/signtx/stx_response.c: -------------------------------------------------------------------------------- 1 | #include "stx_response.h" 2 | #include "../../helpers/response.h" 3 | 4 | int send_response_sign_transaction_session_id(uint8_t session_id) { 5 | RW_BUFFER_FROM_VAR_FULL(buf, session_id); 6 | return res_ok_data(&buf); 7 | } 8 | -------------------------------------------------------------------------------- /src/commands/signtx/stx_response.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // uint*_t 4 | #include "../../constants.h" 5 | #include "stx_context.h" 6 | 7 | /** 8 | * Send APDU response with session_id 9 | * 10 | * response = (uint8_t)session_id 11 | * 12 | * @return zero or positive integer if success, -1 otherwise. 13 | * 14 | */ 15 | int send_response_sign_transaction_session_id(uint8_t session_id); 16 | -------------------------------------------------------------------------------- /src/commands/signtx/stx_types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "../../constants.h" 6 | #include "../../ergo/tx_ser_full.h" 7 | #include "../../ui/ui_application_id.h" 8 | #include "stx_amounts.h" 9 | #include "stx_output.h" 10 | 11 | typedef enum { 12 | SIGN_TRANSACTION_STATE_INITIALIZED, 13 | SIGN_TRANSACTION_STATE_APPROVED, 14 | SIGN_TRANSACTION_STATE_ERROR 15 | } sign_transaction_state_e; 16 | 17 | typedef enum { SIGN_TRANSACTION_OPERATION_P2PK } sign_transaction_operation_type_e; 18 | 19 | typedef enum { 20 | SIGN_TRANSACTION_UI_TRANSACTION_STATE_NONE, 21 | SIGN_TRANSACTION_UI_TRANSACTION_STATE_OPERATION_SCREEN, 22 | SIGN_TRANSACTION_UI_TRANSACTION_STATE_TX_VALUE, 23 | SIGN_TRANSACTION_UI_TRANSACTION_STATE_TX_FEE, 24 | SIGN_TRANSACTION_UI_TRANSACTION_STATE_TOKEN_ID, 25 | SIGN_TRANSACTION_UI_TRANSACTION_STATE_TOKEN_VALUE 26 | } sign_transaction_ui_transaction_state_e; 27 | 28 | typedef enum { 29 | SIGN_TRANSACTION_UI_OUTPUT_STATE_NONE, 30 | SIGN_TRANSACTION_UI_OUTPUT_STATE_ADDRESS, 31 | SIGN_TRANSACTION_UI_OUTPUT_STATE_VALUE, 32 | SIGN_TRANSACTION_UI_OUTPUT_STATE_TOKEN_ID, 33 | SIGN_TRANSACTION_UI_OUTPUT_STATE_TOKEN_VALUE 34 | } sign_transaction_ui_output_state_e; 35 | 36 | typedef struct { 37 | uint32_t app_token_value; // App token value 38 | char app_token[APPLICATION_ID_STR_LEN]; // App token string 39 | bool is_known_application; 40 | void *sign_tx_context; 41 | } sign_transaction_ui_aprove_ctx_t; 42 | 43 | typedef struct { 44 | char title[20]; // dynamic screen title 45 | char text[70]; // dynamic screen text 46 | uint8_t network_id; 47 | const sign_transaction_output_info_ctx_t *output; 48 | sign_transaction_bip32_path_t *last_approved_change; 49 | } sign_transaction_ui_output_confirm_ctx_t; 50 | 51 | // Show screen callback: (index, title, title_len, text, text_len, cb_context) 52 | typedef uint16_t ( 53 | *ui_sign_transaction_operation_show_screen_cb)(uint8_t, char *, size_t, char *, size_t, void *); 54 | // Send response callback (cb_context) 55 | typedef void (*ui_sign_transaction_operation_send_response_cb)(void *); 56 | 57 | typedef struct { 58 | char title[20]; // dynamic screen title 59 | char text[70]; // dynamic screen text 60 | uint8_t op_screen_count; 61 | ui_sign_transaction_operation_show_screen_cb op_screen_cb; 62 | ui_sign_transaction_operation_send_response_cb op_response_cb; 63 | void *op_cb_context; 64 | const sign_transaction_amounts_ctx_t *amounts; 65 | } sign_transaction_ui_sign_confirm_ctx_t; 66 | -------------------------------------------------------------------------------- /src/commands/signtx/stx_ui.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "../../ui/ui_approve_reject.h" 7 | #include "stx_amounts.h" 8 | #include "stx_types.h" 9 | #include "stx_context.h" 10 | 11 | /** 12 | * Add application access_token and accept/reject screens to the UI. 13 | * 14 | * @return true if success, false if screens flow is full. 15 | * 16 | */ 17 | bool ui_stx_add_operation_approve_screens(sign_transaction_ui_aprove_ctx_t* ctx, 18 | uint8_t* screen, 19 | uint32_t app_access_token, 20 | bool is_known_application, 21 | sign_transaction_ctx_t* sign_tx); 22 | 23 | /** 24 | * Add output info and accept/reject screens to the UI. 25 | * 26 | * @return true if success, false if screens flow is full. 27 | * 28 | */ 29 | bool ui_stx_add_output_screens(sign_transaction_ui_output_confirm_ctx_t* ctx, 30 | uint8_t* screen, 31 | uint8_t* output_screen, 32 | const sign_transaction_output_info_ctx_t* output, 33 | sign_transaction_bip32_path_t* last_approved_change, 34 | uint8_t network_id); 35 | 36 | /** 37 | * Add transaction info and accept/reject screens to the UI. 38 | * 39 | * @return true if success, false if screens flow is full. 40 | * 41 | */ 42 | bool ui_stx_add_transaction_screens(sign_transaction_ui_sign_confirm_ctx_t* ctx, 43 | uint8_t* screen, 44 | uint8_t* output_screen, 45 | const sign_transaction_amounts_ctx_t* amounts, 46 | uint8_t blind_signing_required, 47 | uint8_t op_screen_count, 48 | ui_sign_transaction_operation_show_screen_cb screen_cb, 49 | ui_sign_transaction_operation_send_response_cb response_cb, 50 | void* cb_context); 51 | 52 | #ifdef HAVE_BAGL 53 | /** 54 | * Finalizes screen flow and pushes it to the screen. 55 | * 56 | * @return true if success, false if screens buffer is full. 57 | * 58 | */ 59 | bool ui_stx_display_screens(uint8_t screen_count); 60 | #endif 61 | 62 | /** 63 | * Approve or reject operation programmatically. 64 | * 65 | */ 66 | void ui_stx_operation_approve_reject(bool approved, sign_transaction_ui_aprove_ctx_t* ctx); -------------------------------------------------------------------------------- /src/common/bip32_ext.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * (c) 2020 Ledger SAS. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | *****************************************************************************/ 16 | 17 | #include // snprintf 18 | #include // memset, strlen 19 | #include // size_t 20 | #include // uint*_t 21 | #include // bool 22 | 23 | #include 24 | 25 | #include "bip32_ext.h" 26 | 27 | bool bip32_path_validate(const uint32_t *bip32_path, 28 | uint8_t bip32_path_len, 29 | uint32_t type, 30 | uint32_t coin, 31 | bip32_path_validation_type_e vtype) { 32 | if (bip32_path_len <= 2 || bip32_path[0] != type || bip32_path[1] != coin) { 33 | return false; 34 | } 35 | switch (vtype) { 36 | case BIP32_PATH_VALIDATE_COIN: 37 | return true; 38 | case BIP32_PATH_VALIDATE_COIN_GE2_HARD: 39 | for (uint8_t i = 2; i < bip32_path_len; i++) { 40 | if (bip32_path[i] < BIP32_HARDENED_CONSTANT) { 41 | return false; 42 | } 43 | } 44 | return true; 45 | case BIP32_PATH_VALIDATE_ACCOUNT_E3: 46 | return bip32_path_len == 3 && bip32_path[2] >= BIP32_HARDENED_CONSTANT; 47 | case BIP32_PATH_VALIDATE_ACCOUNT_GE3: 48 | for (uint8_t i = 2; i < bip32_path_len; i++) { 49 | if (bip32_path[i] < BIP32_HARDENED_CONSTANT) { 50 | return false; 51 | } 52 | } 53 | return true; 54 | case BIP32_PATH_VALIDATE_ADDRESS_E5: 55 | return bip32_path_len == 5 && bip32_path[2] >= BIP32_HARDENED_CONSTANT && 56 | (bip32_path[3] == 0 || bip32_path[3] == 1) && 57 | bip32_path[4] < BIP32_HARDENED_CONSTANT; 58 | case BIP32_PATH_VALIDATE_ADDRESS_GE5: 59 | if (bip32_path_len < 5) { 60 | return false; 61 | } 62 | if (bip32_path[2] < BIP32_HARDENED_CONSTANT) { 63 | return false; 64 | } 65 | if (bip32_path[3] != 0 && bip32_path[3] != 1) { 66 | return false; 67 | } 68 | for (uint8_t i = 4; i < bip32_path_len; i++) { 69 | if (bip32_path[i] >= BIP32_HARDENED_CONSTANT) { 70 | return false; 71 | } 72 | } 73 | return true; 74 | } 75 | LEDGER_ASSERT(false, "Bip32 validation type isn't handled properly"); 76 | } -------------------------------------------------------------------------------- /src/common/bip32_ext.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define BIP32_HARDENED_CONSTANT 0x80000000u 6 | 7 | #define BIP32_HARDENED(x) (BIP32_HARDENED_CONSTANT + x) 8 | 9 | typedef enum { 10 | BIP32_PATH_VALIDATE_COIN, 11 | BIP32_PATH_VALIDATE_COIN_GE2_HARD, 12 | BIP32_PATH_VALIDATE_ACCOUNT_E3, 13 | BIP32_PATH_VALIDATE_ACCOUNT_GE3, 14 | BIP32_PATH_VALIDATE_ADDRESS_E5, 15 | BIP32_PATH_VALIDATE_ADDRESS_GE5 16 | } bip32_path_validation_type_e; 17 | 18 | bool bip32_path_validate(const uint32_t *bip32_path, 19 | uint8_t bip32_path_len, 20 | uint32_t type, 21 | uint32_t coin, 22 | bip32_path_validation_type_e vtype); 23 | 24 | static inline bool bip32_path_is_equal(const uint32_t *bip32_path_1, 25 | uint8_t bip32_path_1_len, 26 | const uint32_t *bip32_path_2, 27 | uint8_t bip32_path_2_len) { 28 | return bip32_path_1_len == bip32_path_2_len && 29 | memcmp(bip32_path_1, bip32_path_2, bip32_path_1_len) == 0; 30 | } 31 | 32 | static inline bool bip32_path_same_account(const uint32_t *bip32_path_1, 33 | uint8_t bip32_path_1_len, 34 | const uint32_t *bip32_path_2, 35 | uint8_t bip32_path_2_len) { 36 | return bip32_path_1_len >= 3 && bip32_path_2_len >= 3 && 37 | memcmp(bip32_path_1, bip32_path_2, 3) == 0; 38 | } -------------------------------------------------------------------------------- /src/common/buffer_ext.c: -------------------------------------------------------------------------------- 1 | #include // uint*_t 2 | #include // size_t 3 | #include // bool 4 | #include // memmove 5 | 6 | #include "buffer_ext.h" 7 | 8 | bool buffer_copy_bytes(const buffer_t *buffer, uint8_t *out, size_t count) { 9 | if (!buffer_can_read(buffer, count)) return false; 10 | memmove(out, buffer_read_ptr(buffer), count); 11 | return true; 12 | } 13 | 14 | bool buffer_read_bytes(buffer_t *buffer, uint8_t *out, size_t count) { 15 | if (!buffer_copy_bytes(buffer, out, count)) { 16 | return false; 17 | } 18 | return buffer_seek_cur(buffer, count); 19 | } -------------------------------------------------------------------------------- /src/common/buffer_ext.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | * Initialize buffer. 7 | * 8 | * @param[out] buffer 9 | * Pointer to input buffer struct. 10 | * @param[in] ptr 11 | * Pointer to byte buffert. 12 | * @param[in] data_size 13 | * Data size of the buffer. 14 | * 15 | */ 16 | static inline void buffer_init(buffer_t *buffer, const uint8_t *ptr, size_t data_size) { 17 | buffer->ptr = ptr; 18 | buffer->offset = 0; 19 | buffer->size = data_size; 20 | } 21 | 22 | /** 23 | * Return read pointer to the start of the data. 24 | * 25 | * @param[in] buffer 26 | * Pointer to input buffer struct. 27 | * 28 | */ 29 | static inline const uint8_t *buffer_read_ptr(const buffer_t *buffer) { 30 | return buffer->ptr + buffer->offset; 31 | } 32 | 33 | /** 34 | * Tell whether buffer has bytes to read or not. 35 | * 36 | * @param[in] buffer 37 | * Pointer to input buffer struct. 38 | * 39 | * @return length of the data in buffer. 40 | * 41 | */ 42 | static inline size_t buffer_data_len(const buffer_t *buffer) { 43 | return buffer->size - buffer->offset; 44 | } 45 | 46 | /** 47 | * Copy bytes from buffer without its modification. 48 | * 49 | * @param[in,out] buffer 50 | * Pointer to input buffer struct. 51 | * @param[out] out 52 | * Pointer to output byte buffer. 53 | * @param[in] count 54 | * Amount of bytes to copy. 55 | * 56 | * @return true if success, false otherwise. 57 | * 58 | */ 59 | bool buffer_copy_bytes(const buffer_t *buffer, uint8_t *out, size_t count); 60 | 61 | /** 62 | * Read bytes from buffer. 63 | * 64 | * @param[in,out] buffer 65 | * Pointer to input buffer struct. 66 | * @param[out] out 67 | * Pointer to output byte buffer. 68 | * @param[in] count 69 | * Amount of bytes to copy. 70 | * 71 | * @return true if success, false otherwise. 72 | * 73 | */ 74 | bool buffer_read_bytes(buffer_t *buffer, uint8_t *out, size_t count); -------------------------------------------------------------------------------- /src/common/gve.c: -------------------------------------------------------------------------------- 1 | // 2 | // gve.c 3 | // ErgoTxParser 4 | // 5 | // Created by Yehor Popovych on 11.08.2021. 6 | // 7 | 8 | #include "gve.h" 9 | 10 | gve_result_e gve_get_i16(buffer_t *buffer, int16_t *val) { 11 | int32_t i32; 12 | gve_result_e res; 13 | *val = 0; 14 | if ((res = gve_get_i32(buffer, &i32)) != GVE_OK) return res; 15 | if (i32 > INT16_MAX || i32 < INT16_MIN) return GVE_ERR_INT_TO_BIG; 16 | *val = (int16_t) i32; 17 | return GVE_OK; 18 | } 19 | 20 | gve_result_e gve_get_u16(buffer_t *buffer, uint16_t *val) { 21 | uint64_t u64; 22 | gve_result_e res; 23 | *val = 0; 24 | if ((res = gve_get_u64(buffer, &u64)) != GVE_OK) return res; 25 | if (u64 > UINT16_MAX) return GVE_ERR_INT_TO_BIG; 26 | *val = (uint16_t) u64; 27 | return GVE_OK; 28 | } 29 | 30 | gve_result_e gve_get_i32(buffer_t *buffer, int32_t *val) { 31 | uint64_t u64; 32 | gve_result_e res; 33 | *val = 0; 34 | if ((res = gve_get_u64(buffer, &u64)) != GVE_OK) return res; 35 | if (u64 > 0xFFFFFFFFULL) return GVE_ERR_INT_TO_BIG; 36 | *val = zigzag_decode_i32(u64); 37 | return GVE_OK; 38 | } 39 | 40 | gve_result_e gve_get_u32(buffer_t *buffer, uint32_t *val) { 41 | uint64_t u64; 42 | gve_result_e res; 43 | *val = 0; 44 | if ((res = gve_get_u64(buffer, &u64)) != GVE_OK) return res; 45 | if (u64 > UINT32_MAX) return GVE_ERR_INT_TO_BIG; 46 | *val = (uint32_t) u64; 47 | return GVE_OK; 48 | } 49 | 50 | gve_result_e gve_get_i64(buffer_t *buffer, int64_t *val) { 51 | uint64_t u64; 52 | gve_result_e res; 53 | *val = 0; 54 | if ((res = gve_get_u64(buffer, &u64)) != GVE_OK) return res; 55 | *val = zigzag_decode_i64(u64); 56 | return GVE_OK; 57 | } 58 | 59 | gve_result_e gve_get_u64(buffer_t *buffer, uint64_t *val) { 60 | *val = 0; 61 | uint8_t byte = 0; 62 | size_t shift = 0; 63 | gve_result_e res = GVE_ERR_DATA_SIZE; 64 | while (shift < 64) { 65 | if ((res = gve_get_u8(buffer, &byte)) != GVE_OK) break; 66 | *val |= (uint64_t) (byte & 0x7F) << shift; 67 | if ((byte & 0x80) == 0) { 68 | res = GVE_OK; 69 | break; 70 | } 71 | shift += 7; 72 | } 73 | return res; 74 | } 75 | 76 | gve_result_e gve_put_u64(rw_buffer_t *buffer, uint64_t val) { 77 | uint8_t out[10]; 78 | size_t i = 0; 79 | do { 80 | uint8_t byte = val & 0x7FU; 81 | val >>= 7; 82 | if (val) byte |= 0x80U; 83 | out[i++] = byte; 84 | } while (val); 85 | return rw_buffer_write_bytes(buffer, out, i) ? GVE_OK : GVE_ERR_DATA_SIZE; 86 | } 87 | -------------------------------------------------------------------------------- /src/common/gve.h: -------------------------------------------------------------------------------- 1 | // 2 | // gve.h 3 | // ErgoTxParser 4 | // 5 | // Created by Yehor Popovych on 11.08.2021. 6 | // 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include "rwbuffer.h" 13 | #include "zigzag.h" 14 | 15 | typedef enum { GVE_OK = 0, GVE_ERR_INT_TO_BIG, GVE_ERR_DATA_SIZE } gve_result_e; 16 | 17 | static inline gve_result_e gve_get_u8(buffer_t *buffer, uint8_t *val) { 18 | return buffer_read_u8(buffer, val) ? GVE_OK : GVE_ERR_DATA_SIZE; 19 | } 20 | 21 | static inline gve_result_e gve_get_i8(buffer_t *buffer, int8_t *val) { 22 | return gve_get_u8(buffer, (uint8_t *) val); 23 | } 24 | 25 | gve_result_e gve_get_i16(buffer_t *buffer, int16_t *val); 26 | gve_result_e gve_get_u16(buffer_t *buffer, uint16_t *val); 27 | gve_result_e gve_get_i32(buffer_t *buffer, int32_t *val); 28 | gve_result_e gve_get_u32(buffer_t *buffer, uint32_t *val); 29 | gve_result_e gve_get_i64(buffer_t *buffer, int64_t *val); 30 | gve_result_e gve_get_u64(buffer_t *buffer, uint64_t *val); 31 | 32 | gve_result_e gve_put_u64(rw_buffer_t *buffer, uint64_t val); 33 | 34 | static inline gve_result_e gve_put_i64(rw_buffer_t *buffer, int64_t val) { 35 | return gve_put_u64(buffer, zigzag_encode_i64(val)); 36 | } 37 | 38 | static inline gve_result_e gve_put_u32(rw_buffer_t *buffer, uint32_t val) { 39 | return gve_put_u64(buffer, val); 40 | } 41 | 42 | static inline gve_result_e gve_put_i32(rw_buffer_t *buffer, int32_t val) { 43 | return gve_put_u64(buffer, zigzag_encode_i32(val)); 44 | } 45 | 46 | static inline gve_result_e gve_put_u8(rw_buffer_t *buffer, uint8_t val) { 47 | return rw_buffer_write_u8(buffer, val) ? GVE_OK : GVE_ERR_DATA_SIZE; 48 | } 49 | 50 | static inline gve_result_e gve_put_i8(rw_buffer_t *buffer, int8_t val) { 51 | return gve_put_u8(buffer, (uint8_t) val); 52 | } 53 | 54 | static inline gve_result_e gve_put_i16(rw_buffer_t *buffer, int16_t val) { 55 | return gve_put_u32(buffer, (uint32_t) zigzag_encode_i32(val)); 56 | } 57 | 58 | static inline gve_result_e gve_put_u16(rw_buffer_t *buffer, uint16_t val) { 59 | return gve_put_u64(buffer, val); 60 | } 61 | -------------------------------------------------------------------------------- /src/common/macros_ext.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | * Macro for disabling inlining for function (GCC & Clang) 7 | */ 8 | #define NOINLINE __attribute__((noinline)) 9 | 10 | /** 11 | * Macros for checking indexes 12 | */ 13 | #define INDEX_NOT_EXIST ((uint8_t) 0xFF) 14 | #define IS_ELEMENT_FOUND(index) ((uint8_t) (index) != INDEX_NOT_EXIST) -------------------------------------------------------------------------------- /src/common/rwbuffer.c: -------------------------------------------------------------------------------- 1 | #include // uint*_t 2 | #include // size_t 3 | #include // bool 4 | #include // memmove 5 | 6 | #include 7 | #include "rwbuffer.h" 8 | 9 | bool rw_buffer_seek_write_set(rw_buffer_t *buffer, size_t offset) { 10 | if (offset > buffer->size) { 11 | return false; 12 | } 13 | buffer->read.size = offset; 14 | return true; 15 | } 16 | 17 | bool rw_buffer_seek_write_cur(rw_buffer_t *buffer, size_t offset) { 18 | if (buffer->read.size + offset < buffer->read.size || // overflow 19 | buffer->read.size + offset > buffer->size) { // exceed buffer size 20 | return false; 21 | } 22 | buffer->read.size += offset; 23 | return true; 24 | } 25 | 26 | bool rw_buffer_seek_write_end(rw_buffer_t *buffer, size_t offset) { 27 | if (offset > buffer->size) { 28 | return false; 29 | } 30 | buffer->read.size = buffer->size - offset; 31 | return true; 32 | } 33 | 34 | bool rw_buffer_write_u8(rw_buffer_t *buffer, uint8_t value) { 35 | if (!rw_buffer_can_write(buffer, 1)) { 36 | return false; 37 | } 38 | 39 | ((uint8_t *) buffer->read.ptr)[buffer->read.size] = value; 40 | 41 | rw_buffer_seek_write_cur(buffer, 1); 42 | return true; 43 | } 44 | 45 | bool rw_buffer_write_u16(rw_buffer_t *buffer, uint16_t value, endianness_t endianness) { 46 | if (!rw_buffer_can_write(buffer, 2)) { 47 | return false; 48 | } 49 | 50 | if (endianness == BE) { 51 | write_u16_be((uint8_t *) buffer->read.ptr, buffer->read.size, value); 52 | } else { 53 | write_u16_le((uint8_t *) buffer->read.ptr, buffer->read.size, value); 54 | } 55 | 56 | rw_buffer_seek_write_cur(buffer, 2); 57 | 58 | return true; 59 | } 60 | 61 | bool rw_buffer_write_u32(rw_buffer_t *buffer, uint32_t value, endianness_t endianness) { 62 | if (!rw_buffer_can_write(buffer, 4)) { 63 | return false; 64 | } 65 | 66 | if (endianness == BE) { 67 | write_u32_be((uint8_t *) buffer->read.ptr, buffer->read.size, value); 68 | } else { 69 | write_u32_le((uint8_t *) buffer->read.ptr, buffer->read.size, value); 70 | } 71 | 72 | rw_buffer_seek_write_cur(buffer, 4); 73 | 74 | return true; 75 | } 76 | 77 | bool rw_buffer_write_u64(rw_buffer_t *buffer, uint64_t value, endianness_t endianness) { 78 | if (!rw_buffer_can_write(buffer, 8)) { 79 | return false; 80 | } 81 | 82 | if (endianness == BE) { 83 | write_u64_be((uint8_t *) buffer->read.ptr, buffer->read.size, value); 84 | } else { 85 | write_u64_le((uint8_t *) buffer->read.ptr, buffer->read.size, value); 86 | } 87 | 88 | rw_buffer_seek_write_cur(buffer, 8); 89 | 90 | return true; 91 | } 92 | 93 | bool rw_buffer_write_bytes(rw_buffer_t *buffer, const uint8_t *from, size_t from_len) { 94 | if (!rw_buffer_can_write(buffer, from_len)) { 95 | return false; 96 | } 97 | 98 | memmove(rw_buffer_write_ptr(buffer), from, from_len); 99 | 100 | rw_buffer_seek_write_cur(buffer, from_len); 101 | 102 | return true; 103 | } 104 | 105 | void rw_buffer_shift_data(rw_buffer_t *buffer) { 106 | if (rw_buffer_read_position(buffer) == 0) return; 107 | size_t data_len = rw_buffer_data_len(buffer); 108 | memmove((uint8_t *) buffer->read.ptr, rw_buffer_read_ptr(buffer), data_len); 109 | rw_buffer_seek_read_set(buffer, 0); 110 | rw_buffer_seek_write_set(buffer, data_len); 111 | } 112 | -------------------------------------------------------------------------------- /src/common/safeint.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | static inline bool checked_add_u64(uint64_t l, uint64_t r, uint64_t* out) { 7 | if (UINT64_MAX - l < r) return false; 8 | *out = l + r; 9 | return true; 10 | } 11 | 12 | static inline bool checked_sub_u64(uint64_t l, uint64_t r, uint64_t* out) { 13 | if (r > l) return false; 14 | *out = l - r; 15 | return true; 16 | } 17 | 18 | static inline bool checked_add_i64(int64_t l, uint64_t r, int64_t* out) { 19 | if (l < 0) { 20 | // If r is so big that even without "l" it will overflow int64_t. 21 | if (r >= (uint64_t) -l && r + l > INT64_MAX) return false; 22 | } else { 23 | // if r is bigger than int64_t or r + l is bigger than int64_t. 24 | if (r > INT64_MAX || INT64_MAX - r < (uint64_t) l) return false; 25 | } 26 | *out = l + r; 27 | return true; 28 | } 29 | 30 | static inline bool checked_sub_i64(int64_t l, uint64_t r, int64_t* out) { 31 | if (l < 0) { 32 | // r is bigger than INT64_MAX or r + (-l) is greater than -INT64_MIN 33 | if (r > INT64_MAX || r - l > (INT64_MAX + (uint64_t) 1)) return false; 34 | } else { 35 | // r - l is greater than -INT64_MIN 36 | if ((uint64_t) l < r && r - l > (INT64_MAX + (uint64_t) 1)) return false; 37 | } 38 | *out = l - r; 39 | return true; 40 | } -------------------------------------------------------------------------------- /src/common/zigzag.h: -------------------------------------------------------------------------------- 1 | // 2 | // zigzag.h 3 | // ErgoTxParser 4 | // 5 | // Created by Yehor Popovych on 11.08.2021. 6 | // 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | static inline uint64_t zigzag_encode_i32(int32_t v) { 13 | return (uint64_t) ((v << 1) ^ (v >> 31)); 14 | } 15 | 16 | static inline int32_t zigzag_decode_i32(uint64_t v) { 17 | return (int32_t) ((uint32_t) v >> 1) ^ -((int32_t) v & 1); 18 | } 19 | 20 | static inline uint64_t zigzag_encode_i64(int64_t v) { 21 | return (uint64_t) ((v << 1) ^ (v >> 63)); 22 | } 23 | 24 | static inline int64_t zigzag_decode_i64(uint64_t v) { 25 | return (int64_t) ((v >> 1) ^ ((uint64_t) - ((int64_t) (v & 1)))); 26 | } 27 | -------------------------------------------------------------------------------- /src/constants.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * Instruction class of the Ergo application. 5 | */ 6 | #define CLA 0xE0 7 | 8 | /** 9 | * Length of APPNAME variable in the Makefile. 10 | */ 11 | #define APPNAME_LEN (sizeof(APPNAME) - 1) 12 | 13 | /** 14 | * Maximum length of MAJOR_VERSION || MINOR_VERSION || PATCH_VERSION || DEBUG. 15 | */ 16 | #define APPVERSION_LEN 4 17 | 18 | /** 19 | * Maximum length of application name. 20 | */ 21 | #define MAX_APPNAME_LEN 64 22 | 23 | /** 24 | * Bip32 coin type of Ergo token 25 | */ 26 | #define BIP32_ERGO_COIN 429 27 | 28 | /** 29 | * Number of fraction digits in ERG token 30 | */ 31 | #define ERGO_ERG_FRACTION_DIGIT_COUNT 9 32 | 33 | /** 34 | * Length of hashed ids in Ergo. 35 | */ 36 | #define ERGO_ID_LEN 32 37 | 38 | /** 39 | * Maximum number of tokens in TX. 40 | */ 41 | #define TOKEN_MAX_COUNT 100 42 | 43 | /** 44 | * Length of Session Key. 45 | */ 46 | #define SESSION_KEY_LEN 16 47 | 48 | /** 49 | * Length of Chain Code. 50 | */ 51 | #define CHAIN_CODE_LEN 32 52 | 53 | /** 54 | * Length of Public Key. 55 | */ 56 | #define PUBLIC_KEY_LEN 65 57 | 58 | /** 59 | * Length of Compressed Public Key. 60 | */ 61 | #define COMPRESSED_PUBLIC_KEY_LEN 33 62 | 63 | /** 64 | * Length of Private Key. 65 | */ 66 | #define PRIVATE_KEY_LEN 32 67 | 68 | /** 69 | * Length of Extended Public Key. 70 | */ 71 | #define EXTENDED_PUBLIC_KEY_LEN (COMPRESSED_PUBLIC_KEY_LEN + CHAIN_CODE_LEN) 72 | 73 | /** 74 | * Length of Input Frame Signature. 75 | */ 76 | #define INPUT_FRAME_SIGNATURE_LEN 16 77 | 78 | /** 79 | * Length of the secp265k1 schnorr signature 80 | */ 81 | #define ERGO_SIGNATURE_LEN 56 82 | 83 | /** 84 | * Max number of screens 85 | */ 86 | #define MAX_NUMBER_OF_SCREENS 16 87 | 88 | /** 89 | * Max length of TX data part 90 | */ 91 | #define MAX_TX_DATA_PART_LEN 32768 92 | 93 | /** 94 | * Max length of TX data chunk 95 | */ 96 | #define MAX_DATA_CHUNK_LEN 255 97 | 98 | /** 99 | * Max length of BIP32 path string 100 | */ 101 | #define MAX_BIP32_STRING_LEN 60 -------------------------------------------------------------------------------- /src/context.c: -------------------------------------------------------------------------------- 1 | #include // uint*_t 2 | #include // memset, explicit_bzero 3 | #include 4 | 5 | #include "context.h" 6 | #include "./common/macros_ext.h" 7 | 8 | // Saved here to store it outside of the stack 9 | app_ctx_t G_app_context; 10 | 11 | // Internal storage 12 | const internal_storage_t N_storage_real; 13 | 14 | void app_init(void) { 15 | // Clear context 16 | explicit_bzero(&G_app_context, sizeof(app_ctx_t)); 17 | 18 | // Generate random key for session 19 | cx_rng(G_app_context.session_key, MEMBER_SIZE(app_ctx_t, session_key)); 20 | 21 | // Reset context to default values 22 | app_set_current_command(CMD_NONE); 23 | } 24 | 25 | void app_set_current_command(command_e current_command) { 26 | explicit_bzero(&G_app_context.commands_ctx, MEMBER_SIZE(app_ctx_t, commands_ctx)); 27 | app_set_ui_busy(false); 28 | G_app_context.current_command = current_command; 29 | } -------------------------------------------------------------------------------- /src/context.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "apdu_dispatcher.h" 4 | #include "commands/extpubkey/epk_context.h" 5 | #include "commands/deriveaddress/da_context.h" 6 | #include "commands/attestinput/ainpt_context.h" 7 | #include "commands/signtx/stx_context.h" 8 | 9 | /** 10 | * Structure for application context. 11 | */ 12 | typedef struct { 13 | uint32_t connected_app_id; 14 | uint8_t session_key[SESSION_KEY_LEN]; 15 | command_e current_command; /// current command 16 | bool is_ui_busy; 17 | union { 18 | attest_input_ctx_t attest_input; 19 | sign_transaction_ctx_t sign_tx; 20 | derive_address_ctx_t derive_address; 21 | extended_public_key_ctx_t ext_pub_key; 22 | } commands_ctx; 23 | } app_ctx_t; 24 | 25 | /** 26 | * Global application context 27 | */ 28 | extern app_ctx_t G_app_context; 29 | 30 | /** 31 | * Global structure for NVM data storage. 32 | */ 33 | typedef struct internal_storage_t { 34 | uint8_t blind_signing_enabled; 35 | uint8_t initialized; 36 | } internal_storage_t; 37 | 38 | extern const internal_storage_t N_storage_real; 39 | #define N_storage (*(volatile internal_storage_t*) PIC(&N_storage_real)) 40 | 41 | /** 42 | * Check is ui busy 43 | */ 44 | static inline bool app_is_ui_busy() { 45 | return G_app_context.is_ui_busy; 46 | } 47 | 48 | /** 49 | * Set UI busy 50 | */ 51 | static inline void app_set_ui_busy(bool is_busy) { 52 | G_app_context.is_ui_busy = is_busy; 53 | } 54 | 55 | /** 56 | * Get connected application id. 57 | */ 58 | static inline uint32_t app_connected_app_id(void) { 59 | return G_app_context.connected_app_id; 60 | } 61 | 62 | /** 63 | * Set connected application id. 64 | */ 65 | static inline void app_set_connected_app_id(uint32_t id) { 66 | G_app_context.connected_app_id = id; 67 | } 68 | 69 | /** 70 | * Get session key. 71 | */ 72 | static inline const uint8_t* app_session_key(void) { 73 | return G_app_context.session_key; 74 | } 75 | 76 | /** 77 | * Get current command key. 78 | */ 79 | static inline command_e app_current_command(void) { 80 | return G_app_context.current_command; 81 | } 82 | 83 | /** 84 | * Attest Input command context 85 | */ 86 | static inline attest_input_ctx_t* app_attest_input_context(void) { 87 | return &G_app_context.commands_ctx.attest_input; 88 | } 89 | 90 | /** 91 | * Sign Transaction command context 92 | */ 93 | static inline sign_transaction_ctx_t* app_sign_transaction_context(void) { 94 | return &G_app_context.commands_ctx.sign_tx; 95 | } 96 | 97 | /** 98 | * Derive Address command context 99 | */ 100 | static inline derive_address_ctx_t* app_derive_address_context(void) { 101 | return &G_app_context.commands_ctx.derive_address; 102 | } 103 | 104 | /** 105 | * Extended Public Key command context 106 | */ 107 | static inline extended_public_key_ctx_t* app_extended_public_key_context(void) { 108 | return &G_app_context.commands_ctx.ext_pub_key; 109 | } 110 | 111 | /** 112 | * Init application context 113 | */ 114 | void app_init(void); 115 | 116 | /** 117 | * Switch context state to the command 118 | */ 119 | void app_set_current_command(command_e current_command); -------------------------------------------------------------------------------- /src/ergo/address.c: -------------------------------------------------------------------------------- 1 | #include // uint*_t 2 | #include // size_t 3 | #include // bool 4 | #include // memmove 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "address.h" 12 | #include "network_id.h" 13 | #include "../common/rwbuffer.h" 14 | #include "../helpers/blake2b.h" 15 | 16 | static inline bool _ergo_address_from_pubkey(uint8_t network, 17 | const uint8_t* public_key, 18 | uint8_t address[static P2PK_ADDRESS_LEN], 19 | bool is_compressed) { 20 | RW_BUFFER_FROM_ARRAY_EMPTY(buffer, address, P2PK_ADDRESS_LEN); 21 | 22 | if (!network_id_is_valid(network)) { 23 | return false; 24 | } 25 | // P2PK + network id 26 | if (!rw_buffer_write_u8(&buffer, ERGO_ADDRESS_TYPE_P2PK + network)) { 27 | return false; 28 | } 29 | 30 | if (is_compressed) { 31 | if (!rw_buffer_write_bytes(&buffer, public_key, COMPRESSED_PUBLIC_KEY_LEN)) { 32 | return false; 33 | } 34 | } else { 35 | // Compress pubkey 36 | if (!rw_buffer_write_u8(&buffer, ((public_key[64] & 1) ? 0x03 : 0x02))) { 37 | return false; 38 | } 39 | if (!rw_buffer_write_bytes(&buffer, public_key + 1, COMPRESSED_PUBLIC_KEY_LEN - 1)) { 40 | return false; 41 | } 42 | } 43 | 44 | uint8_t hash[CX_BLAKE2B_256_SIZE] = {0}; 45 | 46 | if (!blake2b_256(rw_buffer_read_ptr(&buffer), rw_buffer_data_len(&buffer), hash)) { 47 | return false; 48 | } 49 | // Checksum 50 | if (!rw_buffer_write_bytes(&buffer, hash, ADDRESS_CHECKSUM_LEN)) { 51 | return false; 52 | } 53 | 54 | return true; 55 | } 56 | 57 | bool ergo_address_from_pubkey(uint8_t network, 58 | const uint8_t public_key[static PUBLIC_KEY_LEN], 59 | uint8_t address[static P2PK_ADDRESS_LEN]) { 60 | return _ergo_address_from_pubkey(network, public_key, address, false); 61 | } 62 | 63 | bool ergo_address_from_compressed_pubkey(uint8_t network, 64 | const uint8_t public_key[static COMPRESSED_PUBLIC_KEY_LEN], 65 | uint8_t address[static P2PK_ADDRESS_LEN]) { 66 | return _ergo_address_from_pubkey(network, public_key, address, true); 67 | } 68 | 69 | bool ergo_address_from_script_hash(uint8_t network, 70 | const uint8_t hash[static P2SH_HASH_LEN], 71 | uint8_t address[static P2SH_ADDRESS_LEN]) { 72 | RW_BUFFER_FROM_ARRAY_EMPTY(buffer, address, P2SH_ADDRESS_LEN); 73 | if (!network_id_is_valid(network)) { 74 | return false; 75 | } 76 | // P2SH + network id 77 | if (!rw_buffer_write_u8(&buffer, ERGO_ADDRESS_TYPE_P2SH + network)) { 78 | return false; 79 | } 80 | if (!rw_buffer_write_bytes(&buffer, hash, P2SH_HASH_LEN)) { 81 | return false; 82 | } 83 | uint8_t checksum[CX_BLAKE2B_256_SIZE] = {0}; 84 | if (!blake2b_256(rw_buffer_read_ptr(&buffer), rw_buffer_data_len(&buffer), checksum)) { 85 | return false; 86 | } 87 | // Checksum 88 | if (!rw_buffer_write_bytes(&buffer, checksum, ADDRESS_CHECKSUM_LEN)) { 89 | return false; 90 | } 91 | return true; 92 | } -------------------------------------------------------------------------------- /src/ergo/address.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // uint*_t 4 | #include // size_t 5 | #include // bool 6 | 7 | #include "../constants.h" 8 | 9 | /** 10 | * Length of Address checksum in bytes. 11 | */ 12 | #define ADDRESS_CHECKSUM_LEN 4 13 | 14 | /** 15 | * Length of Address prefix (type + network_id) in bytes. 16 | */ 17 | #define ADDRESS_PREFIX_LEN 1 18 | 19 | /** 20 | * Length of P2PK Address in bytes. 21 | */ 22 | #define P2PK_ADDRESS_LEN (COMPRESSED_PUBLIC_KEY_LEN + ADDRESS_PREFIX_LEN + ADDRESS_CHECKSUM_LEN) 23 | 24 | /** 25 | * Length of P2SH Address hash in bytes. 26 | */ 27 | #define P2SH_HASH_LEN 24 // 192bits 28 | 29 | /** 30 | * Length of P2SH Address in bytes. 31 | */ 32 | #define P2SH_ADDRESS_LEN (P2SH_HASH_LEN + ADDRESS_PREFIX_LEN + ADDRESS_CHECKSUM_LEN) 33 | 34 | /** 35 | * Length of P2PK Address string in chars. (base58 of 38 bytes) 36 | */ 37 | #define P2PK_ADDRESS_STRING_MAX_LEN 55 38 | 39 | typedef enum { 40 | ERGO_ADDRESS_TYPE_P2PK = 0x01, 41 | ERGO_ADDRESS_TYPE_P2SH = 0x02, 42 | ERGO_ADDRESS_TYPE_P2S = 0x03 43 | } ergo_address_type_e; 44 | 45 | /** 46 | * Convert public key to address. 47 | * 48 | * https://ergoplatform.org/en/blog/2019_07_24_ergo_address/ 49 | * 50 | * @param[in] public_key 51 | * Pointer to byte buffer with public key. 52 | * The public key is represented as 65 bytes with 32 bytes for 53 | * each coordinate. 54 | * @param[out] address 55 | * Pointer to output byte buffer for address. 56 | * 57 | * @return true if success, false otherwise. 58 | * 59 | */ 60 | bool ergo_address_from_pubkey(uint8_t network, 61 | const uint8_t public_key[static PUBLIC_KEY_LEN], 62 | uint8_t address[static P2PK_ADDRESS_LEN]); 63 | 64 | /** 65 | * Convert compressed public key to address. 66 | * 67 | * https://ergoplatform.org/en/blog/2019_07_24_ergo_address/ 68 | * 69 | * @param[in] public_key 70 | * Pointer to byte buffer with public key. 71 | * The public key is represented as 33 bytes. 72 | * @param[out] address 73 | * Pointer to output byte buffer for address. 74 | * 75 | * @return true if success, false otherwise. 76 | * 77 | */ 78 | bool ergo_address_from_compressed_pubkey(uint8_t network, 79 | const uint8_t public_key[static COMPRESSED_PUBLIC_KEY_LEN], 80 | uint8_t address[static P2PK_ADDRESS_LEN]); 81 | 82 | /** 83 | * Convert script hash to P2SH address. 84 | * 85 | * https://ergoplatform.org/en/blog/2019_07_24_ergo_address/ 86 | * 87 | * @param[in] hash 88 | * Pointer to byte buffer with 24 bytes of blake2b256 script hash.. 89 | * @param[out] address 90 | * Pointer to output byte buffer for address. 91 | * 92 | * @return true if success, false otherwise. 93 | * 94 | */ 95 | bool ergo_address_from_script_hash(uint8_t network, 96 | const uint8_t hash[static P2SH_HASH_LEN], 97 | uint8_t address[static P2SH_ADDRESS_LEN]); 98 | -------------------------------------------------------------------------------- /src/ergo/ergo_tree.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "../constants.h" 7 | #include "address.h" 8 | 9 | #define ERGO_TREE_P2PK_PREFIX_LEN 3 10 | #define ERGO_TREE_P2PK_LEN (COMPRESSED_PUBLIC_KEY_LEN + ERGO_TREE_P2PK_PREFIX_LEN) 11 | 12 | #define ERGO_TREE_P2SH_PREFIX_LEN 17 13 | #define ERGO_TREE_P2SH_SUFFIX_LEN 3 14 | #define ERGO_TREE_P2SH_LEN (P2SH_HASH_LEN + ERGO_TREE_P2SH_PREFIX_LEN + ERGO_TREE_P2SH_SUFFIX_LEN) 15 | 16 | void ergo_tree_generate_p2pk(const uint8_t raw_public_key[static PUBLIC_KEY_LEN], 17 | uint8_t tree[ERGO_TREE_P2PK_LEN]); 18 | 19 | bool ergo_tree_parse_p2pk(const uint8_t tree[ERGO_TREE_P2PK_LEN], 20 | uint8_t public_key[static COMPRESSED_PUBLIC_KEY_LEN]); 21 | 22 | bool ergo_tree_parse_p2sh(const uint8_t tree[ERGO_TREE_P2SH_LEN], 23 | uint8_t hash[static P2SH_HASH_LEN]); 24 | 25 | void ergo_tree_miners_fee_tree(bool is_mainnet, const uint8_t** tree, size_t* size); -------------------------------------------------------------------------------- /src/ergo/network_id.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | static inline bool network_id_is_mainnet(uint8_t network_id) { 7 | return network_id == 0x00; 8 | } 9 | 10 | static inline bool network_id_is_testnet(uint8_t network_id) { 11 | return network_id == 0x10; 12 | } 13 | 14 | static inline bool network_id_is_valid(uint8_t network_id) { 15 | return network_id <= 252; 16 | } 17 | 18 | static inline bool network_id_is_supported(uint8_t network_id) { 19 | return network_id_is_mainnet(network_id) || network_id_is_testnet(network_id); 20 | } -------------------------------------------------------------------------------- /src/ergo/schnorr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "../constants.h" 5 | #include "../helpers/blake2b.h" 6 | 7 | bool ergo_secp256k1_schnorr_p2pk_sign_init(cx_blake2b_t* hash, 8 | uint8_t key[static PRIVATE_KEY_LEN], 9 | const uint8_t secret[static PRIVATE_KEY_LEN]); 10 | 11 | bool ergo_secp256k1_schnorr_p2pk_sign_finish(uint8_t signature[static ERGO_SIGNATURE_LEN], 12 | cx_blake2b_t* hash, 13 | const uint8_t secret[static PRIVATE_KEY_LEN], 14 | const uint8_t key[static PRIVATE_KEY_LEN]); 15 | -------------------------------------------------------------------------------- /src/ergo/tx_ser_input.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "../constants.h" 9 | #include "../helpers/blake2b.h" 10 | #include "tx_ser_table.h" 11 | 12 | typedef enum { 13 | ERGO_TX_SERIALIZER_INPUT_RES_OK = 0x7F, 14 | ERGO_TX_SERIALIZER_INPUT_RES_MORE_DATA = 0x7E, 15 | ERGO_TX_SERIALIZER_INPUT_RES_ERR_BAD_CONTEXT_EXTENSION_SIZE = 0x01, 16 | ERGO_TX_SERIALIZER_INPUT_RES_ERR_BAD_INPUT_ID = 0x02, 17 | ERGO_TX_SERIALIZER_INPUT_RES_ERR_BAD_FRAME_INDEX = 0x03, 18 | ERGO_TX_SERIALIZER_INPUT_RES_ERR_BAD_TOKEN_ID = 0x04, 19 | ERGO_TX_SERIALIZER_INPUT_RES_ERR_BAD_TOKEN_VALUE = 0x05, 20 | ERGO_TX_SERIALIZER_INPUT_RES_ERR_TOO_MANY_INPUT_FRAMES = 0x06, 21 | ERGO_TX_SERIALIZER_INPUT_RES_ERR_TOO_MUCH_PROOF_DATA = 0x07, 22 | ERGO_TX_SERIALIZER_INPUT_RES_ERR_HASHER = 0x08, 23 | ERGO_TX_SERIALIZER_INPUT_RES_ERR_BUFFER = 0x09, 24 | ERGO_TX_SERIALIZER_INPUT_RES_ERR_BAD_STATE = 0x0A, 25 | ERGO_TX_SERIALIZER_INPUT_RES_ERR_U64_OVERFLOW = 0x0B, 26 | ERGO_TX_SERIALIZER_INPUT_RES_ERR_TOO_MANY_TOKENS = 0x0C 27 | } ergo_tx_serializer_input_result_e; 28 | 29 | typedef enum { 30 | ERGO_TX_SERIALIZER_INPUT_STATE_FRAMES_STARTED, 31 | ERGO_TX_SERIALIZER_INPUT_STATE_EXTENSION_STARTED, 32 | ERGO_TX_SERIALIZER_INPUT_STATE_FINISHED, 33 | ERGO_TX_SERIALIZER_INPUT_STATE_ERROR 34 | } ergo_tx_serializer_input_state_e; 35 | 36 | typedef ergo_tx_serializer_input_result_e (*ergo_tx_serializer_input_token_cb)( 37 | const uint8_t[static ERGO_ID_LEN], 38 | const uint8_t[static ERGO_ID_LEN], 39 | uint64_t, 40 | void*); 41 | 42 | typedef struct { 43 | ergo_tx_serializer_input_state_e state; 44 | uint8_t box_id[ERGO_ID_LEN]; 45 | uint8_t frames_count; 46 | uint8_t frames_processed; 47 | uint32_t context_extension_data_size; 48 | ergo_tx_serializer_input_token_cb on_token_cb; 49 | void* callback_context; 50 | token_table_t* tokens_table; 51 | cx_blake2b_t* hash; 52 | } ergo_tx_serializer_input_context_t; 53 | 54 | ergo_tx_serializer_input_result_e ergo_tx_serializer_input_init( 55 | ergo_tx_serializer_input_context_t* context, 56 | const uint8_t box_id[ERGO_ID_LEN], 57 | uint8_t token_frames_count, 58 | uint32_t proof_data_size, 59 | token_table_t* tokens_table, 60 | cx_blake2b_t* hash); 61 | 62 | ergo_tx_serializer_input_result_e ergo_tx_serializer_input_add_tokens( 63 | ergo_tx_serializer_input_context_t* context, 64 | const uint8_t box_id[ERGO_ID_LEN], 65 | uint8_t token_frame_index, 66 | buffer_t* tokens); 67 | 68 | ergo_tx_serializer_input_result_e ergo_tx_serializer_input_add_context_extension( 69 | ergo_tx_serializer_input_context_t* context, 70 | buffer_t* chunk); 71 | 72 | static inline void ergo_tx_serializer_input_set_callback( 73 | ergo_tx_serializer_input_context_t* context, 74 | ergo_tx_serializer_input_token_cb callback, 75 | void* cb_context) { 76 | context->on_token_cb = callback; 77 | context->callback_context = cb_context; 78 | } 79 | 80 | static inline bool ergo_tx_serializer_input_is_finished( 81 | ergo_tx_serializer_input_context_t* context) { 82 | return context->state == ERGO_TX_SERIALIZER_INPUT_STATE_FINISHED; 83 | } -------------------------------------------------------------------------------- /src/ergo/tx_ser_table.c: -------------------------------------------------------------------------------- 1 | #include "tx_ser_table.h" 2 | #include "../common/gve.h" 3 | 4 | static inline ergo_tx_serializer_table_result_e parse_token(buffer_t* tokens, 5 | token_table_t* table, 6 | uint8_t tokens_max) { 7 | if (table->count >= tokens_max) { 8 | return ERGO_TX_SERIALIZER_TABLE_RES_ERR_TOO_MANY_TOKENS; 9 | } 10 | if (!buffer_read_bytes(tokens, table->tokens[table->count++], ERGO_ID_LEN)) { 11 | return ERGO_TX_SERIALIZER_TABLE_RES_ERR_BAD_TOKEN_ID; 12 | } 13 | return ERGO_TX_SERIALIZER_TABLE_RES_OK; 14 | } 15 | 16 | ergo_tx_serializer_table_result_e ergo_tx_serializer_table_init( 17 | ergo_tx_serializer_table_context_t* context, 18 | uint8_t tokens_count, 19 | token_table_t* tokens_table) { 20 | // tokens table should be empty. 21 | // we add distinct tokens to the start of the table 22 | if (tokens_table->count != 0 || tokens_count > TOKEN_MAX_COUNT) { 23 | return ERGO_TX_SERIALIZER_TABLE_RES_ERR_TOO_MANY_TOKENS; 24 | } 25 | // clean tokens area (we will search) 26 | memset(&tokens_table->tokens, 0, MEMBER_SIZE(token_table_t, tokens)); 27 | // save in context 28 | context->distinct_tokens_count = tokens_count; 29 | context->tokens_table = tokens_table; 30 | return ERGO_TX_SERIALIZER_TABLE_RES_OK; 31 | } 32 | 33 | ergo_tx_serializer_table_result_e ergo_tx_serializer_table_add( 34 | ergo_tx_serializer_table_context_t* context, 35 | buffer_t* tokens) { 36 | while (buffer_data_len(tokens) > 0) { 37 | ergo_tx_serializer_table_result_e res = 38 | parse_token(tokens, context->tokens_table, context->distinct_tokens_count); 39 | if (res != ERGO_TX_SERIALIZER_TABLE_RES_OK) { 40 | return res; 41 | } 42 | } 43 | if (ergo_tx_serializer_table_is_finished(context)) { 44 | return ERGO_TX_SERIALIZER_TABLE_RES_OK; 45 | } 46 | return ERGO_TX_SERIALIZER_TABLE_RES_MORE_DATA; 47 | } 48 | 49 | ergo_tx_serializer_table_result_e ergo_tx_serializer_table_hash( 50 | const ergo_tx_serializer_table_context_t* context, 51 | cx_blake2b_t* hash) { 52 | RW_BUFFER_NEW_LOCAL_EMPTY(buffer, 10); 53 | if (gve_put_u32(&buffer, context->distinct_tokens_count) != GVE_OK) { 54 | return ERGO_TX_SERIALIZER_TABLE_RES_ERR_BUFFER; 55 | } 56 | if (!blake2b_update(hash, rw_buffer_read_ptr(&buffer), rw_buffer_data_len(&buffer))) { 57 | return ERGO_TX_SERIALIZER_TABLE_RES_ERR_HASHER; 58 | } 59 | for (uint8_t i = 0; i < context->distinct_tokens_count; i++) { 60 | if (!blake2b_update(hash, context->tokens_table->tokens[i], ERGO_ID_LEN)) { 61 | return ERGO_TX_SERIALIZER_TABLE_RES_ERR_HASHER; 62 | } 63 | } 64 | return ERGO_TX_SERIALIZER_TABLE_RES_OK; 65 | } 66 | -------------------------------------------------------------------------------- /src/ergo/tx_ser_table.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "../constants.h" 10 | #include "../helpers/blake2b.h" 11 | #include "../common/macros_ext.h" 12 | 13 | typedef struct { 14 | uint8_t count; 15 | uint8_t tokens[TOKEN_MAX_COUNT][ERGO_ID_LEN]; 16 | } token_table_t; 17 | 18 | typedef enum { 19 | ERGO_TX_SERIALIZER_TABLE_RES_OK = 0x7F, 20 | ERGO_TX_SERIALIZER_TABLE_RES_MORE_DATA = 0x7E, 21 | ERGO_TX_SERIALIZER_TABLE_RES_ERR_BAD_TOKEN_ID = 0x01, 22 | ERGO_TX_SERIALIZER_TABLE_RES_ERR_TOO_MANY_TOKENS = 0x02, 23 | ERGO_TX_SERIALIZER_TABLE_RES_ERR_HASHER = 0x03, 24 | ERGO_TX_SERIALIZER_TABLE_RES_ERR_BUFFER = 0x04 25 | } ergo_tx_serializer_table_result_e; 26 | 27 | typedef struct { 28 | token_table_t* tokens_table; 29 | uint8_t distinct_tokens_count; 30 | } ergo_tx_serializer_table_context_t; 31 | 32 | static inline uint8_t token_table_find_token_index(const token_table_t* table, 33 | const uint8_t id[static ERGO_ID_LEN]) { 34 | for (uint8_t i = 0; i < table->count; i++) { 35 | if (memcmp(table->tokens[i], id, ERGO_ID_LEN) == 0) return i; 36 | } 37 | return INDEX_NOT_EXIST; 38 | } 39 | 40 | static inline uint8_t token_table_add_token(token_table_t* table, 41 | const uint8_t id[static ERGO_ID_LEN]) { 42 | if (table->count >= TOKEN_MAX_COUNT) return INDEX_NOT_EXIST; 43 | uint8_t index = table->count++; 44 | memmove(table->tokens[index], id, ERGO_ID_LEN); 45 | return index; 46 | } 47 | 48 | ergo_tx_serializer_table_result_e ergo_tx_serializer_table_init( 49 | ergo_tx_serializer_table_context_t* context, 50 | uint8_t tokens_count, 51 | token_table_t* tokens_table); 52 | 53 | ergo_tx_serializer_table_result_e ergo_tx_serializer_table_add( 54 | ergo_tx_serializer_table_context_t* context, 55 | buffer_t* tokens); 56 | 57 | ergo_tx_serializer_table_result_e ergo_tx_serializer_table_hash( 58 | const ergo_tx_serializer_table_context_t* context, 59 | cx_blake2b_t* hash); 60 | 61 | static inline bool ergo_tx_serializer_table_is_finished( 62 | const ergo_tx_serializer_table_context_t* context) { 63 | return context->tokens_table->count >= context->distinct_tokens_count; 64 | } 65 | -------------------------------------------------------------------------------- /src/helpers/blake2b.c: -------------------------------------------------------------------------------- 1 | #include "blake2b.h" 2 | 3 | bool blake2b_256_init(cx_blake2b_t* ctx) { 4 | return cx_blake2b_init_no_throw(ctx, 256) == CX_OK; 5 | } 6 | 7 | bool blake2b_update(cx_blake2b_t* ctx, const uint8_t* data, size_t len) { 8 | return cx_hash_no_throw((cx_hash_t*) ctx, 0, data, len, NULL, 0) == CX_OK; 9 | } 10 | 11 | bool blake2b_256_finalize(cx_blake2b_t* ctx, uint8_t out[static CX_BLAKE2B_256_SIZE]) { 12 | return cx_hash_no_throw((cx_hash_t*) ctx, 13 | CX_LAST | CX_NO_REINIT, 14 | NULL, 15 | 0, 16 | out, 17 | CX_BLAKE2B_256_SIZE) == CX_OK; 18 | } 19 | 20 | bool blake2b_256(const uint8_t* data, size_t len, uint8_t out[static CX_BLAKE2B_256_SIZE]) { 21 | return cx_blake2b_256_hash(data, len, out) == CX_OK; 22 | } -------------------------------------------------------------------------------- /src/helpers/blake2b.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | bool blake2b_256_init(cx_blake2b_t* ctx); 9 | bool blake2b_update(cx_blake2b_t* ctx, const uint8_t* data, size_t len); 10 | bool blake2b_256_finalize(cx_blake2b_t* ctx, uint8_t out[static CX_BLAKE2B_256_SIZE]); 11 | bool blake2b_256(const uint8_t* data, size_t len, uint8_t out[static CX_BLAKE2B_256_SIZE]); -------------------------------------------------------------------------------- /src/helpers/cmd_macros.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "../context.h" 5 | #include "../sw.h" 6 | 7 | #define CHECK_COMMAND(_ctx, _cmd) \ 8 | if (_cmd != app_current_command()) return COMMAND_ERROR_HANDLER(_ctx, SW_BAD_STATE) 9 | 10 | #define CHECK_SESSION(_ctx, _session_id) \ 11 | if (_session_id != _ctx->session) return COMMAND_ERROR_HANDLER(_ctx, SW_BAD_SESSION_ID) 12 | 13 | #define CHECK_PROPER_STATE(_ctx, _state) \ 14 | if (_ctx->state != _state) return COMMAND_ERROR_HANDLER(_ctx, SW_BAD_STATE) 15 | 16 | #define CHECK_PROPER_STATES(_ctx, _state1, _state2) \ 17 | if (_ctx->state != _state1 && _ctx->state != _state2) \ 18 | return COMMAND_ERROR_HANDLER(_ctx, SW_BAD_STATE) 19 | 20 | #define CHECK_READ_PARAM(_ctx, _call) \ 21 | if (!_call) return COMMAND_ERROR_HANDLER(_ctx, SW_NOT_ENOUGH_DATA) 22 | 23 | #define CHECK_PARAMS_FINISHED(_ctx, _buffer) \ 24 | if (buffer_can_read(_buffer, 1)) return COMMAND_ERROR_HANDLER(_ctx, SW_TOO_MUCH_DATA) 25 | 26 | #define CHECK_CALL_RESULT_SW_OK(_ctx, _call) \ 27 | do { \ 28 | uint16_t _res = _call; \ 29 | if (_res != SW_OK) return COMMAND_ERROR_HANDLER(_ctx, _res); \ 30 | } while (0) 31 | 32 | #define CHECK_WRITE_PARAM(_call) \ 33 | if (!_call) return WRITE_ERROR_HANDLER(SW_BUFFER_ERROR) -------------------------------------------------------------------------------- /src/helpers/crypto.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include // uint*_t 5 | 6 | #include 7 | #include 8 | #include "constants.h" 9 | 10 | /** 11 | * Derive private key given BIP32 path. 12 | * 13 | * @param[out] private_key 14 | * Pointer to private key. 15 | * @param[out] chain_code 16 | * Pointer to 32 bytes array for chain code. 17 | * @param[in] bip32_path 18 | * Pointer to buffer with BIP32 path. 19 | * @param[in] bip32_path_len 20 | * Number of path in BIP32 path. 21 | * 22 | * @returns 0 if ok, error_code on error 23 | * 24 | */ 25 | uint16_t crypto_derive_private_key(cx_ecfp_256_private_key_t *private_key, 26 | uint8_t chain_code[static CHAIN_CODE_LEN], 27 | const uint32_t *bip32_path, 28 | uint8_t bip32_path_len); 29 | 30 | /** 31 | * Initialize public key given private key. 32 | * 33 | * @param[in] private_key 34 | * Pointer to private key. 35 | * @param[out] public_key 36 | * Pointer to public key. 37 | * @param[out] raw_public_key 38 | * Pointer to raw public key. 39 | * 40 | * @returns 0 if ok, error_code on error. 41 | * 42 | */ 43 | uint16_t crypto_init_public_key(const cx_ecfp_256_private_key_t *private_key, 44 | cx_ecfp_256_public_key_t *public_key, 45 | uint8_t raw_public_key[static PUBLIC_KEY_LEN]); 46 | 47 | uint16_t crypto_generate_private_key(const uint32_t *bip32_path, 48 | uint8_t bip32_path_len, 49 | uint8_t private_key[static PRIVATE_KEY_LEN]); 50 | 51 | uint16_t crypto_generate_public_key(const uint32_t *bip32_path, 52 | uint8_t bip32_path_len, 53 | uint8_t raw_public_key[static PUBLIC_KEY_LEN], 54 | uint8_t chain_code[CHAIN_CODE_LEN]); 55 | -------------------------------------------------------------------------------- /src/helpers/input_frame.c: -------------------------------------------------------------------------------- 1 | #include "input_frame.h" 2 | #include "../common/buffer_ext.h" 3 | 4 | uint8_t input_frame_data_length(const buffer_t* input) { 5 | if (!buffer_can_read(input, FRAME_MIN_SIZE)) { 6 | return 0; 7 | } 8 | uint8_t token_count = buffer_read_ptr(input)[FRAME_TOKEN_COUNT_POSITION]; 9 | uint8_t data_len = token_count * FRAME_TOKEN_VALUE_PAIR_SIZE + FRAME_TOKEN_PREFIX_LEN; 10 | if (!buffer_can_read(input, data_len + INPUT_FRAME_SIGNATURE_LEN)) { 11 | return 0; 12 | } 13 | return data_len; 14 | } 15 | 16 | const uint8_t* input_frame_signature_ptr(const buffer_t* input) { 17 | uint8_t data_len = input_frame_data_length(input); 18 | if (data_len == 0) return NULL; 19 | return buffer_read_ptr(input) + data_len; 20 | } 21 | -------------------------------------------------------------------------------- /src/helpers/input_frame.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "../constants.h" 9 | #include "../common/buffer_ext.h" 10 | #include "../ergo/tx_ser_table.h" 11 | 12 | #define FRAME_MAX_TOKENS_COUNT 4 13 | #define FRAME_TOKEN_VALUE_PAIR_SIZE (ERGO_ID_LEN + sizeof(uint64_t)) 14 | #define FRAME_TOKEN_COUNT_POSITION (ERGO_ID_LEN + 2 + sizeof(uint64_t)) 15 | #define FRAME_TOKEN_PREFIX_LEN (FRAME_TOKEN_COUNT_POSITION + 1) 16 | #define FRAME_MIN_SIZE (FRAME_TOKEN_PREFIX_LEN + INPUT_FRAME_SIGNATURE_LEN) 17 | #define FRAME_MAX_SIZE (FRAME_MIN_SIZE + FRAME_MAX_TOKENS_COUNT * FRAME_TOKEN_VALUE_PAIR_SIZE) 18 | 19 | uint8_t input_frame_data_length(const buffer_t* input); 20 | const uint8_t* input_frame_signature_ptr(const buffer_t* input); -------------------------------------------------------------------------------- /src/helpers/response.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include "../sw.h" 7 | #include "../common/rwbuffer.h" 8 | 9 | static inline int res_ok() { 10 | return io_send_sw(SW_OK); 11 | } 12 | 13 | static inline int res_ok_data(const rw_buffer_t* data) { 14 | return io_send_response_buffer(&data->read, SW_OK); 15 | } 16 | 17 | static inline int res_ui_busy() { 18 | return io_send_sw(SW_BUSY); 19 | } 20 | 21 | static inline int res_deny() { 22 | return io_send_sw(SW_DENY); 23 | } 24 | 25 | static inline int res_error(uint16_t code) { 26 | return io_send_sw(code); 27 | } -------------------------------------------------------------------------------- /src/helpers/session_id.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | static inline uint8_t session_id_new_random(uint8_t old_session_id) { 9 | uint8_t id = 0; 10 | do { 11 | id = cx_rng_u8(); 12 | } while (id == 0 || id == old_session_id); 13 | return id; 14 | } 15 | 16 | static inline bool is_known_application(uint32_t saved, uint32_t app_id) { 17 | return saved != 0 && app_id != 0 && app_id == saved; 18 | } -------------------------------------------------------------------------------- /src/sw.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * Status word for success. 5 | */ 6 | #define SW_OK 0x9000 7 | /** 8 | * Status word for denied by user. 9 | */ 10 | #define SW_DENY 0x6985 11 | /** 12 | * Status word for incorrect P1 or P2. 13 | */ 14 | #define SW_WRONG_P1P2 0x6A86 15 | /** 16 | * Status word for either wrong Lc or length of APDU command less than 5. 17 | */ 18 | #define SW_WRONG_APDU_DATA_LENGTH 0x6A87 19 | /** 20 | * Status word for unknown command with this INS. 21 | */ 22 | #define SW_INS_NOT_SUPPORTED 0x6D00 23 | /** 24 | * Status word for instruction class is different than CLA. 25 | */ 26 | #define SW_CLA_NOT_SUPPORTED 0x6E00 27 | /** 28 | * Status word for busy state. 29 | */ 30 | #define SW_BUSY 0xB000 31 | /** 32 | * Status word for wrong response length (buffer too small or too big). 33 | */ 34 | #define SW_WRONG_RESPONSE_LENGTH 0xB001 35 | 36 | #define SW_BAD_SESSION_ID 0xB002 37 | 38 | #define SW_WRONG_SUBCOMMAND 0xB003 39 | 40 | #define SW_SCREENS_BUFFER_OVERFLOW 0xB004 41 | 42 | #define SW_BAD_STATE 0xB0FF 43 | 44 | #define SW_BAD_TOKEN_ID 0xE001 45 | #define SW_BAD_TOKEN_VALUE 0xE002 46 | #define SW_BAD_CONTEXT_EXTENSION_SIZE 0xE003 47 | #define SW_BAD_DATA_INPUT 0xE004 48 | #define SW_BAD_BOX_ID 0xE005 49 | #define SW_BAD_TOKEN_INDEX 0xE006 50 | #define SW_BAD_FRAME_INDEX 0xE007 51 | #define SW_BAD_INPUT_COUNT 0xE008 52 | #define SW_BAD_OUTPUT_COUNT 0xE009 53 | #define SW_TOO_MANY_TOKENS 0xE00A 54 | #define SW_TOO_MANY_INPUTS 0xE00B 55 | #define SW_TOO_MANY_DATA_INPUTS 0xE00C 56 | #define SW_TOO_MANY_INPUT_FRAMES 0xE00D 57 | #define SW_TOO_MANY_OUTPUTS 0xE00E 58 | #define SW_HASHER_ERROR 0xE00F 59 | #define SW_BUFFER_ERROR 0xE010 60 | #define SW_U64_OVERFLOW 0xE011 61 | #define SW_BIP32_BAD_PATH 0xE012 62 | #define SW_INTERNAL_CRYPTO_ERROR 0xE013 63 | #define SW_NOT_ENOUGH_DATA 0xE014 64 | #define SW_TOO_MUCH_DATA 0xE015 65 | #define SW_ADDRESS_GENERATION_FAILED 0xE016 66 | #define SW_SCHNORR_SIGNING_FAILED 0xE017 67 | #define SW_BAD_FRAME_SIGNATURE 0xE018 68 | #define SW_BAD_NET_TYPE_VALUE 0xE019 69 | #define SW_SMALL_CHUNK 0xE01A 70 | 71 | #define SW_BIP32_FORMATTING_FAILED 0xE101 72 | #define SW_ADDRESS_FORMATTING_FAILED 0xE102 73 | -------------------------------------------------------------------------------- /src/ui/display.c: -------------------------------------------------------------------------------- 1 | #include "display.h" 2 | #include "../context.h" 3 | #ifdef REVAMPED_IO 4 | #include "os_io_legacy.h" 5 | #endif 6 | 7 | #ifdef HAVE_NBGL 8 | 9 | nbgl_layoutTagValue_t pairs_global[N_UX_PAIRS]; 10 | #endif 11 | 12 | bool flow_response = false; 13 | 14 | void set_flow_response(bool response) { 15 | flow_response = response; 16 | app_set_ui_busy(false); 17 | } 18 | 19 | void io_common_process() { 20 | #ifdef REVAMPED_IO 21 | do { 22 | io_seproxyhal_io_heartbeat(); 23 | } while (app_is_ui_busy()); 24 | #else 25 | io_seproxyhal_general_status(); 26 | do { 27 | io_seproxyhal_spi_recv(G_io_seproxyhal_spi_buffer, sizeof(G_io_seproxyhal_spi_buffer), 0); 28 | io_seproxyhal_handle_event(); 29 | io_seproxyhal_general_status(); 30 | } while (io_seproxyhal_spi_is_status_sent() && app_is_ui_busy()); 31 | #endif // !REVAMPED_IO 32 | } 33 | 34 | bool io_ui_process() { 35 | // We are not waiting for the client's input, nor we are doing computations on the device 36 | // io_clear_processing_timeout(); 37 | 38 | app_set_ui_busy(true); 39 | 40 | io_common_process(); 41 | 42 | // We're back at work, we want to show the "Processing..." screen when appropriate 43 | // io_start_processing_timeout(); 44 | 45 | return flow_response; 46 | } 47 | -------------------------------------------------------------------------------- /src/ui/display.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | #include "../context.h" 6 | #include "../constants.h" 7 | 8 | extern bool flow_response; 9 | 10 | void set_flow_response(bool response); 11 | void io_common_process(); 12 | bool io_ui_process(); 13 | 14 | #define N_UX_PAIRS TOKEN_MAX_COUNT * 2 + 10 15 | static char pair_mem_title[N_UX_PAIRS][20]; 16 | static char pair_mem_text[N_UX_PAIRS][70]; 17 | 18 | #ifdef HAVE_NBGL 19 | static nbgl_layoutTagValueList_t pair_list; 20 | extern nbgl_layoutTagValue_t pairs_global[N_UX_PAIRS]; 21 | #endif -------------------------------------------------------------------------------- /src/ui/ui_application_id.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define APPLICATION_ID_STR_LEN 11 7 | 8 | #ifdef HAVE_BAGL 9 | 10 | const ux_flow_step_t* ui_application_id_screen(uint32_t app_id, 11 | char buffer[static APPLICATION_ID_STR_LEN]); 12 | 13 | #endif 14 | 15 | #ifdef HAVE_NBGL 16 | #include 17 | 18 | nbgl_layoutTagValue_t ui_application_id_screen(uint32_t app_id, 19 | char buffer[static APPLICATION_ID_STR_LEN]); 20 | 21 | #endif -------------------------------------------------------------------------------- /src/ui/ui_application_id_bagl.c: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_BAGL 2 | #include "ui_application_id.h" 3 | #include 4 | #include 5 | 6 | static ux_layout_bn_params_t G_ui_application_id_params[1]; 7 | 8 | const ux_flow_step_t ux_app_token_step = { 9 | ux_layout_bn_init, 10 | G_ui_application_id_params, 11 | NULL, 12 | NULL, 13 | }; 14 | 15 | const ux_flow_step_t* ui_application_id_screen(uint32_t app_id, 16 | char buffer[static APPLICATION_ID_STR_LEN]) { 17 | G_ui_application_id_params[0].line1 = "Application"; 18 | G_ui_application_id_params[0].line2 = buffer; 19 | 20 | memset(buffer, 0, APPLICATION_ID_STR_LEN); 21 | snprintf(buffer, APPLICATION_ID_STR_LEN, "0x%08x", app_id); 22 | return &ux_app_token_step; 23 | } 24 | #endif -------------------------------------------------------------------------------- /src/ui/ui_application_id_nbgl.c: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_NBGL 2 | 3 | #include "ui_application_id.h" 4 | 5 | nbgl_layoutTagValue_t ui_application_id_screen(uint32_t app_id, 6 | char buffer[static APPLICATION_ID_STR_LEN]) { 7 | memset(buffer, 0, APPLICATION_ID_STR_LEN); 8 | snprintf(buffer, APPLICATION_ID_STR_LEN, "0x%08x", app_id); 9 | 10 | nbgl_layoutTagValue_t tag = (nbgl_layoutTagValue_t){.item = "Application", .value = buffer}; 11 | 12 | return tag; 13 | } 14 | 15 | #endif -------------------------------------------------------------------------------- /src/ui/ui_approve_reject.c: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_BAGL 2 | #include 3 | #include "ui_approve_reject.h" 4 | 5 | static ui_approve_reject_callback G_ui_approve_reject_callback; 6 | static void* G_ui_approve_reject_callback_context; 7 | 8 | // Step with approve button 9 | UX_STEP_CB(ux_approve_step, 10 | pb, 11 | G_ui_approve_reject_callback(true, G_ui_approve_reject_callback_context), 12 | { 13 | &C_icon_validate_14, 14 | "Approve", 15 | }); 16 | // Step with reject button 17 | UX_STEP_CB(ux_reject_step, 18 | pb, 19 | G_ui_approve_reject_callback(false, G_ui_approve_reject_callback_context), 20 | { 21 | &C_icon_crossmark, 22 | "Reject", 23 | }); 24 | 25 | void ui_approve_reject_screens(ui_approve_reject_callback cb, 26 | void* context, 27 | const ux_flow_step_t** approve, 28 | const ux_flow_step_t** reject) { 29 | G_ui_approve_reject_callback = cb; 30 | G_ui_approve_reject_callback_context = context; 31 | *approve = &ux_approve_step; 32 | *reject = &ux_reject_step; 33 | } 34 | #endif -------------------------------------------------------------------------------- /src/ui/ui_approve_reject.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifdef HAVE_BAGL 3 | #include 4 | #include 5 | 6 | typedef void (*ui_approve_reject_callback)(bool, void*); 7 | 8 | void ui_approve_reject_screens(ui_approve_reject_callback cb, 9 | void* context, 10 | const ux_flow_step_t** approve, 11 | const ux_flow_step_t** reject); 12 | 13 | #endif -------------------------------------------------------------------------------- /src/ui/ui_bip32_path.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #ifdef HAVE_BAGL 7 | 8 | typedef void (*ui_bip32_approve_callback)(void*); 9 | 10 | const ux_flow_step_t* ui_bip32_path_screen(uint32_t* path, 11 | uint8_t path_len, 12 | const char* title, 13 | char* buffer, 14 | uint8_t buffer_len, 15 | ui_bip32_approve_callback cb, 16 | void* cb_context); 17 | 18 | #endif 19 | 20 | #ifdef HAVE_NBGL 21 | bool ui_bip32_path_screen(uint32_t* path, uint8_t path_len, char* buffer, uint8_t buffer_len); 22 | #endif -------------------------------------------------------------------------------- /src/ui/ui_bip32_path_bagl.c: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_BAGL 2 | 3 | #include "ui_bip32_path.h" 4 | #include "../common/bip32_ext.h" 5 | 6 | static ux_layout_bnnn_paging_params_t G_ui_path_params[1]; 7 | static ui_bip32_approve_callback G_ui_bip32_approve_callback; 8 | static void* G_ui_bip32_approve_callback_context; 9 | 10 | void ux_bip32_path_validate_init(unsigned int stack_slot) { 11 | UNUSED(stack_slot); 12 | if (G_ui_bip32_approve_callback != NULL) { 13 | G_ui_bip32_approve_callback(G_ui_bip32_approve_callback_context); 14 | } else { 15 | ux_flow_next(); 16 | } 17 | } 18 | 19 | const ux_flow_step_t ux_bip32_path_validate_step = {ux_bip32_path_validate_init, NULL, NULL, NULL}; 20 | 21 | const ux_flow_step_t* const ux_bip32_path_validate[] = {&ux_bip32_path_validate_step, 22 | FLOW_END_STEP}; 23 | 24 | const ux_flow_step_t ux_bip32_path_step = { 25 | ux_layout_bnnn_paging_init, 26 | G_ui_path_params, 27 | ux_bip32_path_validate, 28 | NULL, 29 | }; 30 | 31 | const ux_flow_step_t* ui_bip32_path_screen(uint32_t* path, 32 | uint8_t path_len, 33 | const char* title, 34 | char* buffer, 35 | uint8_t buffer_len, 36 | ui_bip32_approve_callback cb, 37 | void* cb_context) { 38 | G_ui_path_params[0].title = title; 39 | G_ui_path_params[0].text = buffer; 40 | memset(buffer, 0, buffer_len); 41 | if (!bip32_path_format(path, path_len, buffer, buffer_len)) { 42 | return NULL; 43 | } 44 | G_ui_bip32_approve_callback = cb; 45 | G_ui_bip32_approve_callback_context = cb_context; 46 | return &ux_bip32_path_step; 47 | } 48 | 49 | #endif -------------------------------------------------------------------------------- /src/ui/ui_bip32_path_nbgl.c: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_NBGL 2 | 3 | #include "ui_bip32_path.h" 4 | #include "../common/bip32_ext.h" 5 | 6 | bool ui_bip32_path_screen(uint32_t* path, uint8_t path_len, char* buffer, uint8_t buffer_len) { 7 | memset(buffer, 0, buffer_len); 8 | if (!bip32_path_format(path, path_len, buffer, buffer_len)) { 9 | return false; 10 | } 11 | 12 | return true; 13 | } 14 | 15 | #endif -------------------------------------------------------------------------------- /src/ui/ui_dynamic_flow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifdef HAVE_BAGL 3 | #include 4 | #include 5 | #include 6 | 7 | typedef uint16_t (*ui_dynamic_flow_show_screen_cb)(uint8_t, char *, char *); 8 | 9 | // Global context pointer will be set to the dynamic flow context. Don't change it. 10 | bool ui_add_dynamic_flow_screens(uint8_t *screen, 11 | uint8_t dynamic_screen_count, 12 | char *title_storage, 13 | char *text_storage, 14 | ui_dynamic_flow_show_screen_cb show_cb); 15 | 16 | #endif -------------------------------------------------------------------------------- /src/ui/ui_main.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "../constants.h" 5 | #include "../context.h" 6 | 7 | #ifdef HAVE_BAGL 8 | /** 9 | * Global array for UI screen flow 10 | */ 11 | extern ux_flow_step_t const* G_ux_flow_steps[MAX_NUMBER_OF_SCREENS + 1]; 12 | 13 | static inline const ux_flow_step_t** ui_next_sreen_ptr(uint8_t* position) { 14 | if (*position >= MAX_NUMBER_OF_SCREENS) return NULL; 15 | return &G_ux_flow_steps[(*position)++]; 16 | } 17 | 18 | static inline bool ui_add_screen(const ux_flow_step_t* screen, uint8_t* position) { 19 | if (*position >= MAX_NUMBER_OF_SCREENS) return false; 20 | G_ux_flow_steps[(*position)++] = screen; 21 | return true; 22 | } 23 | 24 | static inline bool ui_display_screens(uint8_t* position) { 25 | if (MAX_NUMBER_OF_SCREENS - *position < 2) return false; 26 | 27 | G_ux_flow_steps[(*position)++] = FLOW_LOOP; 28 | G_ux_flow_steps[(*position)++] = FLOW_END_STEP; 29 | 30 | ux_flow_init(0, G_ux_flow_steps, NULL); 31 | 32 | app_set_ui_busy(true); 33 | 34 | return true; 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /src/ui/ui_main_bagl.c: -------------------------------------------------------------------------------- 1 | #include "ui_main.h" 2 | 3 | #ifdef HAVE_BAGL 4 | ux_flow_step_t const *G_ux_flow_steps[MAX_NUMBER_OF_SCREENS + 1]; 5 | #endif -------------------------------------------------------------------------------- /src/ui/ui_menu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * Show main menu (ready screen, version, about, quit). 5 | */ 6 | void ui_menu_main(void); 7 | 8 | #ifdef HAVE_BAGL 9 | /** 10 | * Show settings submenu (blind signing toggle). 11 | */ 12 | void ui_menu_settings(void); 13 | #endif 14 | 15 | /** 16 | * Show about submenu (copyright, date). 17 | */ 18 | void ui_menu_about(void); 19 | -------------------------------------------------------------------------------- /src/ui/ui_menu_bagl.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Ledger App Boilerplate. 3 | * (c) 2020 Ledger SAS. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | *****************************************************************************/ 17 | 18 | #ifdef HAVE_BAGL 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "ui_menu.h" 25 | #include "ui_main.h" 26 | 27 | UX_STEP_NOCB(ux_menu_ready_step, pnn, {&C_app_logo_16px, APPNAME, "is ready"}); 28 | UX_STEP_CB(ux_menu_settings_step, pb, ui_menu_settings(), {&C_icon_coggle, "Settings"}); 29 | UX_STEP_CB(ux_menu_about_step, pb, ui_menu_about(), {&C_icon_certificate, "About"}); 30 | UX_STEP_CB(ux_menu_exit_step, pb, os_sched_exit(-1), {&C_icon_dashboard_x, "Quit"}); 31 | 32 | void ui_menu_main() { 33 | if (G_ux.stack_count == 0) { 34 | ux_stack_push(); 35 | } 36 | 37 | uint8_t screen = 0; 38 | ui_add_screen(&ux_menu_ready_step, &screen); 39 | ui_add_screen(&ux_menu_settings_step, &screen); 40 | ui_add_screen(&ux_menu_about_step, &screen); 41 | ui_add_screen(&ux_menu_exit_step, &screen); 42 | ui_display_screens(&screen); 43 | 44 | app_set_ui_busy(false); 45 | } 46 | 47 | UX_STEP_NOCB(ux_menu_info_step, bn, {APPNAME " App", "(c) 2024 Ergo"}); 48 | UX_STEP_NOCB(ux_menu_version_step, bn, {"Version", APPVERSION}); 49 | UX_STEP_CB(ux_menu_back_step, pb, ui_menu_main(), {&C_icon_back, "Back"}); 50 | 51 | void ui_menu_about() { 52 | uint8_t screen = 0; 53 | ui_add_screen(&ux_menu_info_step, &screen); 54 | ui_add_screen(&ux_menu_version_step, &screen); 55 | ui_add_screen(&ux_menu_back_step, &screen); 56 | ui_display_screens(&screen); 57 | 58 | app_set_ui_busy(false); 59 | } 60 | 61 | static void toggle_blind_signing() { 62 | uint8_t switch_value = !N_storage.blind_signing_enabled; 63 | nvm_write((void *) &N_storage.blind_signing_enabled, &switch_value, 1); 64 | ui_menu_settings(); 65 | } 66 | 67 | UX_STEP_CB(ux_menu_bs_enabled_step, 68 | bnnn, 69 | toggle_blind_signing(), 70 | {"Blind signing", "Enable transaction", "blind signing.", "Enabled"}); 71 | UX_STEP_CB(ux_menu_bs_disabled_step, 72 | bnnn, 73 | toggle_blind_signing(), 74 | {"Blind signing", "Enable transaction", "blind signing.", "Disabled"}); 75 | 76 | void ui_menu_settings() { 77 | uint8_t screen = 0; 78 | if (N_storage.blind_signing_enabled) { 79 | ui_add_screen(&ux_menu_bs_enabled_step, &screen); 80 | } else { 81 | ui_add_screen(&ux_menu_bs_disabled_step, &screen); 82 | } 83 | ui_add_screen(&ux_menu_back_step, &screen); 84 | ui_display_screens(&screen); 85 | 86 | app_set_ui_busy(false); 87 | } 88 | 89 | #endif -------------------------------------------------------------------------------- /src/ui/ui_menu_nbgl.c: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_NBGL 2 | 3 | #include "ui_menu.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define APPTAGLINE "This app enables signing\ntransactions on the Ergo\nnetwork." 10 | #define APPCOPYRIGHT "Ergo App (c) 2024" 11 | 12 | // info definition 13 | #define SETTING_INFO_NB 2 14 | static const char* const INFO_TYPES[SETTING_INFO_NB] = {"Version", "Copyright"}; 15 | static const char* const INFO_CONTENTS[SETTING_INFO_NB] = {APPVERSION, APPCOPYRIGHT}; 16 | static const nbgl_contentInfoList_t infoList = { 17 | .nbInfos = SETTING_INFO_NB, 18 | .infoTypes = INFO_TYPES, 19 | .infoContents = INFO_CONTENTS, 20 | }; 21 | 22 | // settings switches definitions 23 | enum { BLIND_SIGNING_SWITCH_TOKEN = FIRST_USER_TOKEN }; 24 | enum { BLIND_SIGNING_SWITCH_ID = 0, SETTINGS_SWITCHES_NB }; 25 | 26 | static nbgl_contentSwitch_t switches[SETTINGS_SWITCHES_NB] = {0}; 27 | 28 | static void controls_callback(int token, uint8_t index, int page); 29 | 30 | // settings definition 31 | #define SETTING_CONTENTS_NB 1 32 | static const nbgl_content_t contents[SETTING_CONTENTS_NB] = { 33 | {.type = SWITCHES_LIST, 34 | .content.switchesList.nbSwitches = SETTINGS_SWITCHES_NB, 35 | .content.switchesList.switches = switches, 36 | .contentActionCallback = controls_callback}}; 37 | 38 | static const nbgl_genericContents_t settingContents = {.callbackCallNeeded = false, 39 | .contentsList = contents, 40 | .nbContents = SETTING_CONTENTS_NB}; 41 | 42 | void app_quit(void) { 43 | // exit app here 44 | os_sched_exit(-1); 45 | } 46 | 47 | static void controls_callback(int token, uint8_t index, __attribute__((unused)) int page) { 48 | UNUSED(index); 49 | 50 | uint8_t switch_value; 51 | if (token == BLIND_SIGNING_SWITCH_TOKEN) { 52 | // toggle the switch value 53 | switch_value = !N_storage.blind_signing_enabled; 54 | switches[BLIND_SIGNING_SWITCH_ID].initState = (nbgl_state_t) switch_value; 55 | // store the new setting value in NVM 56 | nvm_write((void*) &N_storage.blind_signing_enabled, &switch_value, 1); 57 | } 58 | } 59 | 60 | void ui_menu_main() { 61 | switches[BLIND_SIGNING_SWITCH_ID].initState = (nbgl_state_t) N_storage.blind_signing_enabled; 62 | switches[BLIND_SIGNING_SWITCH_ID].text = "Blind signing"; 63 | switches[BLIND_SIGNING_SWITCH_ID].subText = "Enable transaction blind\nsigning."; 64 | switches[BLIND_SIGNING_SWITCH_ID].token = BLIND_SIGNING_SWITCH_TOKEN; 65 | #ifdef HAVE_PIEZO_SOUND 66 | switches[BLIND_SIGNING_SWITCH_ID].tuneId = TUNE_TAP_CASUAL; 67 | #endif 68 | 69 | nbgl_useCaseHomeAndSettings(APPNAME, 70 | &C_app_logo_64px, 71 | APPTAGLINE, 72 | INIT_HOME_PAGE, 73 | &settingContents, 74 | &infoList, 75 | NULL, 76 | app_quit); 77 | } 78 | 79 | void ui_menu_about() { 80 | nbgl_useCaseHomeAndSettings(APPNAME, 81 | &C_app_logo_64px, 82 | APPTAGLINE, 83 | 0, 84 | &settingContents, 85 | &infoList, 86 | NULL, 87 | app_quit); 88 | } 89 | 90 | #endif -------------------------------------------------------------------------------- /src/ui/ui_sign_reject.c: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_BAGL 2 | #include 3 | #include "ui_sign_reject.h" 4 | 5 | static ui_sign_reject_callback G_ui_sign_reject_callback; 6 | static void* G_ui_sign_reject_callback_context; 7 | 8 | // Step with sign button 9 | UX_STEP_CB(ux_sign_step, 10 | pb, 11 | G_ui_sign_reject_callback(true, G_ui_sign_reject_callback_context), 12 | { 13 | &C_icon_validate_14, 14 | "Sign transaction", 15 | }); 16 | // Step with reject button 17 | UX_STEP_CB(ux_sign_reject_step, 18 | pb, 19 | G_ui_sign_reject_callback(false, G_ui_sign_reject_callback_context), 20 | { 21 | &C_icon_crossmark, 22 | "Reject", 23 | }); 24 | 25 | void ui_sign_reject_screens(ui_sign_reject_callback cb, 26 | void* context, 27 | const ux_flow_step_t** sign, 28 | const ux_flow_step_t** reject) { 29 | G_ui_sign_reject_callback = cb; 30 | G_ui_sign_reject_callback_context = context; 31 | *sign = &ux_sign_step; 32 | *reject = &ux_sign_reject_step; 33 | } 34 | #endif -------------------------------------------------------------------------------- /src/ui/ui_sign_reject.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifdef HAVE_BAGL 3 | #include 4 | #include 5 | 6 | typedef void (*ui_sign_reject_callback)(bool, void*); 7 | 8 | void ui_sign_reject_screens(ui_sign_reject_callback cb, 9 | void* context, 10 | const ux_flow_step_t** sign, 11 | const ux_flow_step_t** reject); 12 | 13 | #endif -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # End-to-end tests 2 | 3 | These tests are implemented in NodeJS and can be executed either using the [Speculos](https://github.com/LedgerHQ/speculos) emulator or a Ledger Nano S+/X. 4 | NodeJS dependencies are listed in [package.json](package.json), install them using `npm` or `yarn`. 5 | 6 | ``` 7 | npm install 8 | ``` 9 | 10 | ### Launch with Speculos 11 | 12 | First start your application with Speculos 13 | 14 | ``` 15 | export SEED=`cat '/path/to/tests/seed.txt'` 16 | ./path/to/speculos.py /path/to/bin/app.elf --apdu-port 9999 --seed "$SEED" 17 | ``` 18 | 19 | then run 20 | 21 | ``` 22 | npm run test --model= 23 | ``` 24 | 25 | ### Launch with your Nano S+/X 26 | 27 | Be sure to have you device connected through USB (without any other software interacting with it) and run 28 | 29 | ``` 30 | LEDGER_LIVE_HARDWARE=1 npm run test --model= 31 | ``` 32 | 33 | `` can be either `nanox` or `nanosp` -------------------------------------------------------------------------------- /tests/address-tests.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | .use(require('chai-bytes')); 3 | const { toHex, getApplication, removeMasterNode } = require('./helpers/common'); 4 | const { TEST_DATA } = require('./helpers/data'); 5 | const { mergePagedScreens } = require("./helpers/screen"); 6 | const { authTokenFlows } = require('./helpers/flow'); 7 | 8 | describe("Address Tests", function () { 9 | context("Address Commands", function () { 10 | authTokenFlows("can derive address") 11 | .init(async ({test, auth}) => { 12 | const address = TEST_DATA.address0; 13 | const flow = [{ header: null, body: 'Confirm Send Address' }, 14 | { header: 'Path', body: removeMasterNode(address.path.toString()) }]; 15 | if (auth) { 16 | flow.push({ header: 'Application', body: getApplication(test.device) }) 17 | } 18 | flow.push({ header: null, body: 'Approve' }, { header: null, body: 'Reject' }); 19 | return { address, flow, flowsCount: 1 }; 20 | }) 21 | .shouldSucceed(({flow, flows, address}, derived) => { 22 | expect(flows[0]).to.be.deep.equal(flow); 23 | expect(derived).to.be.deep.equal({ 24 | addressHex: toHex(address.toBytes()) 25 | }); 26 | }) 27 | .run(({test, address}) => test.device.deriveAddress(address.path.toString())); 28 | 29 | authTokenFlows("can show address") 30 | .init(async ({test, auth}) => { 31 | const address = TEST_DATA.address0; 32 | const flow = [{ header: null, body: 'Confirm Address' }, 33 | { header: 'Path', body: removeMasterNode(address.path.toString()) }, 34 | { header: 'Address', body: address.toBase58() }]; 35 | if (auth) { 36 | flow.push({ header: 'Application', body: getApplication(test.device) }) 37 | } 38 | flow.push({ header: null, body: 'Approve' }, { header: null, body: 'Reject' }); 39 | return { address, flow, flowsCount: 1 }; 40 | }) 41 | .shouldSucceed(({flow, flows}, show) => { 42 | expect(mergePagedScreens(flows[0])).to.be.deep.equal(flow); 43 | expect(show).to.be.true; 44 | }) 45 | .run(({test, address}) => test.device.showAddress(address.path.toString())); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /tests/basic-tests.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const { expect } = chai.use(require('chai-bytes')); 3 | const makefile = require('./helpers/makefile'); 4 | const screen = require('./helpers/screen'); 5 | 6 | describe("Basic Tests", function () { 7 | context("Basic Commands", function () { 8 | it("can fetch version of the app", async function () { 9 | const version = await this.device.getAppVersion(); 10 | expect(version).to.be.deep.equal({ 11 | major: makefile.versionMajor, 12 | minor: makefile.versionMinor, 13 | patch: makefile.versionPatch, 14 | flags: { isDebug: false } 15 | }); 16 | }); 17 | 18 | it("can fetch name of the app", async function () { 19 | const name = (await this.device.getAppName()).name; 20 | expect(name).to.be.equal(makefile.appName); 21 | }); 22 | }); 23 | 24 | context("Basic Flows", function () { 25 | it("main flow is working", async function () { 26 | if (this.screens) { 27 | this.timeout(5000); 28 | const main = await this.screens.ensureMainMenu(); 29 | expect(main).to.be.equal(true); 30 | } else { 31 | console.log("Check screens on the device, please!"); 32 | console.log("Screens", screen.MAIN_FLOW); 33 | } 34 | }); 35 | 36 | it("settings flow is working, blind signing enabled", async function () { 37 | if (this.screens) { 38 | this.timeout(10000); 39 | const main = await this.screens.ensureMainMenu(); 40 | expect(main).to.be.equal(true); 41 | await this.screens.click(1); 42 | let settingsMenu = await this.screens.readFlow(); 43 | if(!settingsMenu[0].body.endsWith("Enabled")) { 44 | await this.screens.click(0); 45 | settingsMenu = await this.screens.readFlow(); 46 | } 47 | await this.screens.click(1); 48 | expect(settingsMenu).to.be.deep.equal(screen.SETTINGS_FLOW); 49 | } else { 50 | console.log("Check screens on the device, please!"); 51 | console.log("Screens", screen.SETTINGS_FLOW); 52 | } 53 | }); 54 | 55 | it("about flow is working", async function () { 56 | if (this.screens) { 57 | this.timeout(10000); 58 | const main = await this.screens.ensureMainMenu(); 59 | expect(main).to.be.equal(true); 60 | await this.screens.click(2); 61 | const aboutMenu = await this.screens.readFlow(); 62 | await this.screens.click(2); 63 | expect(aboutMenu).to.be.deep.equal(screen.ABOUT_FLOW); 64 | } else { 65 | console.log("Check screens on the device, please!"); 66 | console.log("Screens", screen.ABOUT_FLOW); 67 | } 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /tests/helpers/automation.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events'); 2 | 3 | class SpeculosAutomation { 4 | constructor(transport) { 5 | this.transport = transport 6 | this.events = new EventEmitter();; 7 | this.transport.eventStream.on('data', (data) => this._data(data)); 8 | this.transport.eventStream.on('error', (err) => this.events.emit('error', err)); 9 | } 10 | 11 | _data(data) { 12 | const split = data.toString("ascii").split("\n"); 13 | split 14 | .filter(ascii => !!ascii) 15 | .map(str => str.slice(str.indexOf("{"))) 16 | .forEach((ascii) => { 17 | const json = JSON.parse(ascii); 18 | if (json.text) { 19 | this.events.emit('text', json); 20 | } 21 | this.events.emit('any', json); 22 | }); 23 | } 24 | 25 | pressButton(button) { 26 | return this.transport.button(button); 27 | } 28 | 29 | close() { 30 | this.events = null; 31 | this.transport = null; 32 | } 33 | } 34 | 35 | exports.SpeculosAutomation = SpeculosAutomation; -------------------------------------------------------------------------------- /tests/helpers/common.js: -------------------------------------------------------------------------------- 1 | const { NetworkPrefix, MinerAddress, Address } = require("ergo-lib-wasm-nodejs"); 2 | const { Network } = require("ledger-ergo-js"); 3 | 4 | async function sleep(ms) { 5 | await new Promise(r => setTimeout(r, ms != null ? ms : 500)); 6 | } 7 | 8 | function toHex(bytes) { 9 | return Buffer.from(bytes).toString('hex'); 10 | } 11 | 12 | function toBytes(hex) { 13 | return Uint8Array.from(Buffer.from(hex, 'hex')); 14 | } 15 | 16 | function toArray(object) { 17 | const array = []; 18 | for (let i = 0; i < object.len(); i++) { 19 | array.push(object.get(i)); 20 | } 21 | return array; 22 | } 23 | 24 | function toNetwork(prefix) { 25 | switch (prefix) { 26 | case NetworkPrefix.Mainnet: 27 | return Network.Mainnet; 28 | case NetworkPrefix.Testnet: 29 | return Network.Testnet; 30 | } 31 | } 32 | 33 | function getApplication(device) { 34 | return '0x' + device.authToken.toString(16).padStart(8, 0); 35 | } 36 | 37 | function ellipsize(model, string) { 38 | let amount = 26; 39 | if (model === 'nanos') { 40 | amount = 7; 41 | } 42 | return string.substring(0, amount) + '...' + string.substring(string.length - amount, string.length); 43 | } 44 | 45 | function removeMasterNode(path) { 46 | return path.replace(/^(m\/)/, ''); 47 | } 48 | 49 | function getMinerAddress(network) { 50 | switch (network) { 51 | case NetworkPrefix.Mainnet: 52 | return Address.from_base58(MinerAddress.mainnet_fee_address()); 53 | case NetworkPrefix.Testnet: 54 | return Address.from_base58(MinerAddress.testnet_fee_address()); 55 | } 56 | } 57 | 58 | exports.sleep = sleep; 59 | exports.toHex = toHex; 60 | exports.toBytes = toBytes; 61 | exports.toArray = toArray; 62 | exports.toNetwork = toNetwork; 63 | exports.getApplication = getApplication; 64 | exports.ellipsize = ellipsize; 65 | exports.removeMasterNode = removeMasterNode; 66 | exports.getMinerAddress = getMinerAddress; 67 | -------------------------------------------------------------------------------- /tests/helpers/data.js: -------------------------------------------------------------------------------- 1 | const { Address, NetworkPrefix, DerivationPath, ExtPubKey } = require("ergo-lib-wasm-nodejs"); 2 | const { toBytes } = require("./common"); 3 | 4 | class Account { 5 | constructor(publicKey, chainCode, path) { 6 | this.publicKey = ExtPubKey.new(toBytes(publicKey), toBytes(chainCode), path); 7 | this.path = path; 8 | } 9 | } 10 | 11 | class ExtendedAddress { 12 | constructor(network, address, path) { 13 | this.network = network; 14 | this.address = address; 15 | this.path = DerivationPath.new(path[0], [path[1]]); 16 | this.acc_index = path[0]; 17 | this.addr_index = path[1]; 18 | } 19 | 20 | toBase58() { 21 | return this.address.to_base58(this.network); 22 | } 23 | 24 | toBytes() { 25 | return this.address.to_bytes(this.network); 26 | } 27 | } 28 | 29 | class TestData { 30 | constructor(network) { 31 | this.network = network; 32 | this.account = new Account( 33 | '03c24e55008b523ccaf03b6c757f88c4881ef3331a255b76d2e078016c69c3dfd4', 34 | '8eb29c7897d57aee371bf254be6516e6963e2d9b379d0d626c17a39d1a3bf553', 35 | DerivationPath.from_string(`m/44'/429'/0'`) 36 | ); 37 | this.address0 = new ExtendedAddress( 38 | network, 39 | Address.from_base58("9gqBSpseifxnkjRLZUxs5wbJGsvYPG7MLRcBgnKEzFiJoMJaakg"), 40 | [0, 0] 41 | ); 42 | this.address1 = new ExtendedAddress( 43 | network, 44 | Address.from_base58("9iKPzGpzrEFFQ7kn2n6BHWU4fgTwSMF7atqPsvHAjgGvogSHz6Y"), 45 | [0, 1] 46 | ); 47 | this.changeAddress = new ExtendedAddress( 48 | network, 49 | Address.from_base58("9eo8hALVQTaAuu8m95JhR8rhXuAMsLacaxM8X4omp724Smt8ior"), 50 | [0, 2] 51 | ); 52 | this.changeAddress22 = new ExtendedAddress( 53 | network, 54 | Address.from_base58("9fRejXDJxxdJ1KVRH6HdxDj1S1duKmUNGG7CjztN2YjHnooxYAX"), 55 | [0, 22] 56 | ); 57 | this.addressScript = new ExtendedAddress( 58 | network, 59 | Address.from_base58("HTNVLC7W3rw7LbSXR1biLQqQgmdz9d9D1Wpwt73WmkEVcxMpQzzajV6njQQcET9ytvR2ZALwtr1jn3DLAKPs8FSiMUbU9XLvY8BrQYRnjLiiF58EYgDGa72tLugcLWbh84S1cuYkFebZaH8Ataoiu6ssF9eVPhJeaYqXxKEqKnR89HfGZBk48phyoYvWR4ZyKkm65DNAsxHz7Z2GELjAkAbuX3xobgzao5MZeLb7nDxemNBSoBhRz3n34wd4eVvKeoG5KEkax7g1vGWhMGajHh5QPVpc62H4cYopnEeoEg18FM82QiHjhSPJy84RvBhGa3912PRJgYDKxvnGVgTzG7R3uePoBeypGExCaG4YQHZ6caH7aDBBqZkVK9hg3tzXYd7XCj6jqTGkFRsdMuvkC2nJBJjzzi1txKpcPFLqdr5Tcm7BufPmJUz1zSmtVHzuT9DLBDYwuRkrVZG15mWZqP9Kyq5DqoEgA6GXUJJuRv7HrpBQG6U1oGHdAgiWqPo7qx679tVSjE63wzB1sphJ9GNxQCTTXJ79ZonGDfbmxGzL3Raoq7YsFJKZoNNgZDsdpYzcMJjvxj4FVTcmzC5StAHAvQkkWuX5mUkjF18DYt8ZQ52GThWPznHEGLvGku9ryiNPWbhEca5izzhH8mLjykYcqfTyZ7cVnQk46q31zyG3MsXXewb9bFDDk4deB6LCaQcA"), 60 | [0, 0] 61 | ); 62 | this.addressScriptHash = "72H58ETmT5dN7GfQnHbAfo2WLPuGxcpoP6QkQJn"; 63 | } 64 | } 65 | 66 | exports.TEST_DATA = new TestData(NetworkPrefix.Mainnet); 67 | -------------------------------------------------------------------------------- /tests/helpers/flow.js: -------------------------------------------------------------------------------- 1 | const { mergePagedScreens } = require('./screen'); 2 | 3 | function suppressPomiseError(promise) { 4 | return promise.catch((reason) => ({ __rejected: true, reason })); 5 | } 6 | 7 | function restorePromiseError(promise) { 8 | return promise.then((val) => { 9 | if (val.__rejected) { 10 | throw val.reason; 11 | } 12 | return val; 13 | }); 14 | } 15 | 16 | class AuthTokenFlows { 17 | constructor(name) { 18 | this._name = name; 19 | this._before = null; 20 | this.shouldSucceed(null); 21 | this.shouldFail(null); 22 | } 23 | 24 | init(before) { 25 | this._before = before; 26 | return this; 27 | } 28 | 29 | shouldSucceed(success) { 30 | this._success = success ?? (function (_, result) { 31 | throw new Error(`Success called: ${JSON.stringify(result)}`); 32 | }); 33 | return this; 34 | } 35 | 36 | shouldFail(failure) { 37 | this._failure = failure ?? (function (_, error) { 38 | throw new Error(`Failure called: ${error}`); 39 | }); 40 | return this; 41 | } 42 | 43 | run(action) { 44 | this.__run(false, action); 45 | this.__run(true, action); 46 | } 47 | 48 | __run(auth, action) { 49 | const before = this._before; 50 | const success = this._success; 51 | const failure = this._failure; 52 | 53 | it(`${this._name}${auth ? ' (with auth token)' : ''}`, async function() { 54 | this.timeout(30_000); 55 | this.device.useAuthToken(auth); 56 | const params = { test: this, auth }; 57 | if (before) { 58 | Object.assign(params, await before(params)); 59 | } 60 | const flowsCount = params.flowsCount; 61 | if (typeof flowsCount !== "number") { 62 | throw new Error("flowsCount should be defined and to be a number"); 63 | } 64 | this.screens.removeCurrentScreen(); // Wait for new screen in the readFlow 65 | // Call action 66 | const promise = suppressPomiseError(action(params)); 67 | // Read flows 68 | const flows = []; 69 | for (let i = 0; i < flowsCount; i++) { 70 | let flow = await this.screens.readFlow(); 71 | flows.push(mergePagedScreens(flow)); 72 | // try to click on "Sign transaction" button first 73 | await this.screens.clickOn('Sign transaction'); 74 | await this.screens.clickOn('Approve'); 75 | if (i != flowsCount - 1 && await this.screens.isReadyMainScreen()) { // we have more flows 76 | this.screens.removeCurrentScreen(); // Wait for new screen in the readFlow 77 | } 78 | } 79 | params.flows = flows; 80 | // Call success or error 81 | let result; 82 | try { 83 | result = await restorePromiseError(promise); 84 | } catch (error) { 85 | failure(params, error); 86 | return; 87 | } 88 | success(params, result); 89 | }); 90 | return null; 91 | } 92 | } 93 | 94 | exports.authTokenFlows = function(name) { 95 | return new AuthTokenFlows(name); 96 | }; 97 | 98 | exports.AuthTokenFlows = AuthTokenFlows; 99 | -------------------------------------------------------------------------------- /tests/helpers/hooks.js: -------------------------------------------------------------------------------- 1 | const SpeculosTransport = require('@ledgerhq/hw-transport-node-speculos-http').default; 2 | const HidTransport = require('@ledgerhq/hw-transport-node-hid').default; 3 | const { ErgoLedgerApp } = require('ledger-ergo-js'); 4 | const SpeculosAutomation = require('./automation').SpeculosAutomation; 5 | const ScreenReader = require('./screen').ScreenReader; 6 | 7 | // Use IPV4 by default (for speculos) 8 | const dns = require('node:dns'); 9 | dns.setDefaultResultOrder('ipv4first'); 10 | 11 | const API_PORT = 5000; 12 | 13 | exports.mochaHooks = { 14 | beforeAll: async function () { 15 | this.model = process.env.npm_config_model; 16 | const port = process.env.npm_config_port ?? API_PORT; 17 | if (!this.model) { 18 | throw new Error("No model. Provide model with --model= parameter"); 19 | } 20 | if (["nanox", "nanosp", "hid"].indexOf(this.model) < 0) { 21 | throw new Error("Unknown model: " + this.model + ", supports: nanox, nanosp, hid"); 22 | } 23 | if (this.model === "hid") { 24 | this.transport = await HidTransport.create(); 25 | } else { 26 | this.transport = await SpeculosTransport.open({ 27 | baseURL: "http://127.0.0.1:" + port 28 | }); 29 | this.automation = new SpeculosAutomation(this.transport); 30 | this.screens = new ScreenReader(this.automation, this.model); 31 | this.device = new ErgoLedgerApp(this.transport); 32 | } 33 | }, 34 | afterAll: async function () { 35 | this.device = undefined; 36 | this.screens = undefined; 37 | this.transport.close(); 38 | if (this.automation) { 39 | this.automation.close(); 40 | } 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /tests/helpers/makefile.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | const makefile = fs.readFileSync(path.join(__dirname, "..", "..", "Makefile"), { encoding: "utf-8" }); 5 | 6 | exports.versionMajor = parseInt(makefile.match(/^APPVERSION_M\s+=\s+([0-9]+)$/m)[1], 10); 7 | exports.versionMinor = parseInt(makefile.match(/^APPVERSION_N\s+=\s+([0-9]+)$/m)[1], 10); 8 | exports.versionPatch = parseInt(makefile.match(/^APPVERSION_P\s+=\s+([0-9]+)$/m)[1], 10); 9 | exports.version = `${exports.versionMajor}.${exports.versionMinor}.${exports.versionPatch}`; 10 | 11 | exports.appName = makefile.match(/^APPNAME\s+=\s+\"(.+?)\"$/m)[1] -------------------------------------------------------------------------------- /tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ergo-ledger-tests", 3 | "version": "0.1.0", 4 | "src": "./", 5 | "scripts": { 6 | "test": "mocha . --exit --require helpers/hooks" 7 | }, 8 | "bin": "run-tests.js", 9 | "devDependencies": { 10 | "chai": "^4.3.7", 11 | "chai-as-promised": "^7.1.1", 12 | "chai-bytes": "^0.1.2", 13 | "mocha": "^10.1.0" 14 | }, 15 | "dependencies": { 16 | "@ledgerhq/hw-transport-node-hid": "6.29.5", 17 | "@ledgerhq/hw-transport-node-speculos-http": "6.29.4", 18 | "ergo-lib-wasm-nodejs": "v0.28.0", 19 | "ledger-ergo-js": "^0.1.18" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/public-key-tests.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | .use(require('chai-bytes')); 3 | const { toHex, getApplication, removeMasterNode } = require('./helpers/common'); 4 | const { TEST_DATA } = require('./helpers/data'); 5 | const { authTokenFlows } = require('./helpers/flow'); 6 | 7 | describe("Public Key Tests", function () { 8 | context("Public Key Commands", function () { 9 | authTokenFlows("can get extended public key") 10 | .init(async ({test, auth}) => { 11 | const account = TEST_DATA.account; 12 | const flow = [{ header: null, body: 'Ext PubKey Export' }, 13 | { header: 'Path', body: removeMasterNode(account.path.toString()) }]; 14 | if (auth) { 15 | flow.push({ header: 'Application', body: getApplication(test.device) }); 16 | } 17 | flow.push({ header: null, body: 'Approve' }, { header: null, body: 'Reject' }); 18 | return { account, flow, flowsCount: 1 }; 19 | }) 20 | .shouldSucceed(({flow, flows, account}, extPubKey) => { 21 | expect(flows[0]).to.be.deep.equal(flow); 22 | expect(extPubKey).to.be.deep.equal({ 23 | publicKey: toHex(account.publicKey.pub_key_bytes()), 24 | chainCode: toHex(account.publicKey.chain_code()), 25 | }); 26 | }) 27 | .run(({test, account}) => test.device.getExtendedPublicKey(account.path.toString())); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /tests/seed.txt: -------------------------------------------------------------------------------- 1 | jaguar swallow dinner course lend surround warm robot grape pear skate relief -------------------------------------------------------------------------------- /unit-tests/.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux-Clang", 5 | "includePath": [ 6 | "/usr/include", 7 | "${workspaceFolder}/utils", 8 | "${workspaceFolder}/../src" 9 | ], 10 | "defines": [ 11 | "TARGET_NANOS", 12 | "OS_IO_SEPROXYHAL", 13 | "HAVE_BAGL", 14 | "HAVE_ECC", 15 | "HAVE_ECC_WEIERSTRASS", 16 | "HAVE_SECP_CURVES", 17 | "HAVE_SPRINTF", 18 | "HAVE_HASH", 19 | "HAVE_BLAKE2", 20 | "HAVE_RNG", 21 | "HAVE_HMAC", 22 | "HAVE_SHA256", 23 | "HAVE_MATH", 24 | "HAVE_IO_USB", 25 | "HAVE_L4_USBLIB", 26 | "IO_USB_MAX_ENDPOINTS=6", 27 | "IO_HID_EP_LENGTH=64", 28 | "HAVE_USB_APDU", 29 | "USB_SEGMENT_SIZE=64", 30 | "UNUSED(x)=(void)x", 31 | "APPVERSION=\"1.0.0\"", 32 | "APPNAME=\"Ergo\"", 33 | "MAJOR_VERSION=1", 34 | "MINOR_VERSION=0", 35 | "PATCH_VERSION=0", 36 | "IO_SEPROXYHAL_BUFFER_SIZE_B=128", 37 | "HAVE_UX_FLOW", 38 | "DEBUG_BUILD=1", 39 | "HAVE_PRINTF", 40 | "PRINTF=screen_printf" 41 | ], 42 | "compilerPath": "/usr/bin/clang", 43 | "cStandard": "c99", 44 | "cppStandard": "c++17", 45 | "browse": { 46 | "limitSymbolsToIncludedHeaders": true, 47 | "databaseFilename": "" 48 | }, 49 | "configurationProvider": "ms-vscode.cmake-tools" 50 | } 51 | ], 52 | "version": 4 53 | } -------------------------------------------------------------------------------- /unit-tests/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "run unit tests", 6 | "type": "shell", 7 | "command": "rm -rf build && cmake -Bbuild -H. && make -C build && CTEST_OUTPUT_ON_FAILURE=1 make -C build test", 8 | "group": { 9 | "kind": "build", 10 | "isDefault": true 11 | }, 12 | "problemMatcher": [ 13 | "$gcc" 14 | ] 15 | }, 16 | ] 17 | } -------------------------------------------------------------------------------- /unit-tests/README.md: -------------------------------------------------------------------------------- 1 | # Unit tests 2 | 3 | ## Prerequisite 4 | 5 | Be sure to have installed: 6 | 7 | - CMake >= 3.10 8 | - CMocka >= 1.1.5 9 | 10 | and for code coverage generation: 11 | 12 | - lcov >= 1.14 13 | 14 | ## Overview 15 | 16 | In `unit-tests` folder, compile with 17 | 18 | ``` 19 | cmake -Bbuild -H. && make -C build 20 | ``` 21 | 22 | and run tests with 23 | 24 | ``` 25 | CTEST_OUTPUT_ON_FAILURE=1 make -C build test 26 | ``` 27 | 28 | ## Generate code coverage 29 | 30 | Just execute in `unit-tests` folder 31 | 32 | ``` 33 | ./gen_coverage.sh 34 | ``` 35 | 36 | it will output `coverage.total` and `coverage/` folder with HTML details (in `coverage/index.html`). 37 | -------------------------------------------------------------------------------- /unit-tests/gen_coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | set -e 5 | 6 | BUILD_DIRECTORY=$(realpath build/) 7 | 8 | lcov --directory . -b "${BUILD_DIRECTORY}" --capture --initial -o coverage.base && 9 | lcov --rc lcov_branch_coverage=1 --directory . -b "${BUILD_DIRECTORY}" --capture -o coverage.capture && 10 | lcov --directory . -b "${BUILD_DIRECTORY}" --add-tracefile coverage.base --add-tracefile coverage.capture -o coverage.info && 11 | lcov --directory . -b "${BUILD_DIRECTORY}" --remove coverage.info '*/unit-tests/*' -o coverage.info && 12 | echo "Generated 'coverage.info'." && 13 | genhtml coverage.info -o coverage 14 | 15 | rm -f coverage.base coverage.capture 16 | -------------------------------------------------------------------------------- /unit-tests/test_address.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "ergo/address.h" 10 | 11 | static void test_ergo_address_from_pubkey(void **state) { 12 | (void) state; 13 | 14 | uint8_t network = 0; 15 | const uint8_t public_key[65] = { 16 | 0x04, 0x8b, 0x9f, 0xf8, 0x5d, 0xdd, 0x9f, 0x1e, 0x22, 0x88, 0xfc, 0x53, 0x9d, 17 | 0x39, 0xc7, 0xc4, 0xee, 0xb7, 0xa5, 0x56, 0xf4, 0xd8, 0x11, 0xcb, 0x73, 0x99, 18 | 0x64, 0x18, 0xde, 0x5a, 0xbd, 0xcb, 0x2a, 0xfa, 0x2d, 0x53, 0x17, 0x16, 0x0a, 19 | 0x59, 0x50, 0x0f, 0x5d, 0x31, 0xfa, 0xe8, 0x6b, 0xce, 0xe9, 0xab, 0x1a, 0x60, 20 | 0x53, 0xa1, 0x1d, 0x53, 0x5d, 0x2d, 0x04, 0x3c, 0xe5, 0xcf, 0xf1, 0x0a, 0xe7}; 21 | uint8_t address[38]; 22 | uint8_t expected[38] = {0x01, 0x03, 0x8b, 0x9f, 0xf8, 0x5d, 0xdd, 0x9f, 0x1e, 0x22, 23 | 0x88, 0xfc, 0x53, 0x9d, 0x39, 0xc7, 0xc4, 0xee, 0xb7, 0xa5, 24 | 0x56, 0xf4, 0xd8, 0x11, 0xcb, 0x73, 0x99, 0x64, 0x18, 0xde, 25 | 0x5a, 0xbd, 0xcb, 0x2a, 0xb5, 0x2d, 0xca, 0xce}; 26 | assert_true(ergo_address_from_pubkey(network, public_key, address)); 27 | assert_memory_equal(address, expected, sizeof(expected)); 28 | } 29 | 30 | int main() { 31 | const struct CMUnitTest tests[] = {cmocka_unit_test(test_ergo_address_from_pubkey)}; 32 | 33 | return cmocka_run_group_tests(tests, NULL, NULL); 34 | } 35 | -------------------------------------------------------------------------------- /unit-tests/test_safeint.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "common/safeint.h" 10 | 11 | static void test_checked_add_u64_min(void **state) { 12 | (void) state; 13 | 14 | uint64_t l = 0; 15 | uint64_t r = 0; 16 | uint64_t out; 17 | assert_true(checked_add_u64(l, r, &out)); 18 | assert_int_equal(out, 0); 19 | } 20 | 21 | static void test_checked_add_u64_max(void **state) { 22 | (void) state; 23 | 24 | uint64_t l = 0; 25 | uint64_t r = UINT64_MAX; 26 | uint64_t out; 27 | assert_true(checked_add_u64(l, r, &out)); 28 | assert_int_equal(out, UINT64_MAX); 29 | } 30 | 31 | static void test_checked_add_u64_more_than_max(void **state) { 32 | (void) state; 33 | 34 | uint64_t l = 1; 35 | uint64_t r = UINT64_MAX; 36 | uint64_t out; 37 | assert_false(checked_add_u64(l, r, &out)); 38 | } 39 | 40 | static void test_checked_add_u64(void **state) { 41 | (void) state; 42 | 43 | uint64_t l = 1; 44 | uint64_t r = 2; 45 | uint64_t out; 46 | assert_true(checked_add_u64(l, r, &out)); 47 | assert_int_equal(out, 3); 48 | } 49 | 50 | static void test_checked_sub_u64_min(void **state) { 51 | (void) state; 52 | 53 | uint64_t l = 0; 54 | uint64_t r = 0; 55 | uint64_t out; 56 | assert_true(checked_sub_u64(l, r, &out)); 57 | assert_int_equal(out, 0); 58 | } 59 | 60 | static void test_checked_sub_u64_max(void **state) { 61 | (void) state; 62 | 63 | uint64_t l = UINT64_MAX; 64 | uint64_t r = UINT64_MAX; 65 | uint64_t out; 66 | assert_true(checked_sub_u64(l, r, &out)); 67 | assert_int_equal(out, 0); 68 | } 69 | 70 | static void test_checked_sub_u64_r_more_than_l(void **state) { 71 | (void) state; 72 | 73 | uint64_t l = 0; 74 | uint64_t r = 1; 75 | uint64_t out; 76 | assert_false(checked_sub_u64(l, r, &out)); 77 | } 78 | 79 | int main() { 80 | const struct CMUnitTest tests[] = {cmocka_unit_test(test_checked_add_u64_min), 81 | cmocka_unit_test(test_checked_add_u64_max), 82 | cmocka_unit_test(test_checked_add_u64_more_than_max), 83 | cmocka_unit_test(test_checked_add_u64), 84 | cmocka_unit_test(test_checked_sub_u64_min), 85 | cmocka_unit_test(test_checked_sub_u64_max), 86 | cmocka_unit_test(test_checked_sub_u64_r_more_than_l)}; 87 | 88 | return cmocka_run_group_tests(tests, NULL, NULL); 89 | } 90 | -------------------------------------------------------------------------------- /unit-tests/utils/base58.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // size_t 4 | #include // uint*_t 5 | #include // bool 6 | 7 | /** 8 | * Maximum length of input when decoding in base 58. 9 | */ 10 | #define MAX_DEC_INPUT_SIZE 164 11 | /** 12 | * Maximum length of input when encoding in base 58. 13 | */ 14 | #define MAX_ENC_INPUT_SIZE 120 15 | 16 | /** 17 | * Decode input string in base 58. 18 | * 19 | * @see https://tools.ietf.org/html/draft-msporny-base58-02 20 | * 21 | * @param[in] in 22 | * Pointer to input string buffer. 23 | * @param[in] in_len 24 | * Length of the input string buffer. 25 | * @param[out] out 26 | * Pointer to output byte buffer. 27 | * @param[in] out_len 28 | * Maximum length to write in output byte buffer. 29 | * 30 | * @return number of bytes decoded, -1 otherwise. 31 | * 32 | */ 33 | int base58_decode(const char *in, size_t in_len, uint8_t *out, size_t out_len); 34 | 35 | /** 36 | * Encode input bytes in base 58. 37 | * 38 | * @see https://tools.ietf.org/html/draft-msporny-base58-02 39 | * 40 | * @param[in] in 41 | * Pointer to input byte buffer. 42 | * @param[in] in_len 43 | * Length of the input byte buffer. 44 | * @param[out] out 45 | * Pointer to output string buffer. 46 | * @param[in] out_len 47 | * Maximum length to write in output byte buffer. 48 | * 49 | * @return number of bytes encoded, -1 otherwise. 50 | * 51 | */ 52 | int base58_encode(const uint8_t *in, size_t in_len, char *out, size_t out_len); -------------------------------------------------------------------------------- /unit-tests/utils/bip32.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * (c) 2020 Ledger SAS. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | *****************************************************************************/ 16 | 17 | #include // snprintf 18 | #include // memset, strlen 19 | #include // size_t 20 | #include // uint*_t 21 | #include // bool 22 | 23 | #include "bip32.h" 24 | #include "read.h" 25 | 26 | bool bip32_path_read(const uint8_t *in, size_t in_len, uint32_t *out, size_t out_len) 27 | { 28 | if (out_len == 0 || out_len > MAX_BIP32_PATH) { 29 | return false; 30 | } 31 | 32 | size_t offset = 0; 33 | 34 | for (size_t i = 0; i < out_len; i++) { 35 | if (offset + 4 > in_len) { 36 | return false; 37 | } 38 | out[i] = read_u32_be(in, offset); 39 | offset += 4; 40 | } 41 | 42 | return true; 43 | } 44 | 45 | bool bip32_path_format(const uint32_t *bip32_path, size_t bip32_path_len, char *out, size_t out_len) 46 | { 47 | if (bip32_path_len == 0 || bip32_path_len > MAX_BIP32_PATH) { 48 | return false; 49 | } 50 | 51 | size_t offset = 0; 52 | 53 | for (uint16_t i = 0; i < bip32_path_len; i++) { 54 | size_t written; 55 | 56 | snprintf(out + offset, out_len - offset, "%u", bip32_path[i] & 0x7FFFFFFFu); 57 | written = strlen(out + offset); 58 | if (written == 0 || written >= out_len - offset) { 59 | memset(out, 0, out_len); 60 | return false; 61 | } 62 | offset += written; 63 | 64 | if ((bip32_path[i] & 0x80000000u) != 0) { 65 | snprintf(out + offset, out_len - offset, "'"); 66 | written = strlen(out + offset); 67 | if (written == 0 || written >= out_len - offset) { 68 | memset(out, 0, out_len); 69 | return false; 70 | } 71 | offset += written; 72 | } 73 | 74 | if (i != bip32_path_len - 1) { 75 | snprintf(out + offset, out_len - offset, "/"); 76 | written = strlen(out + offset); 77 | if (written == 0 || written >= out_len - offset) { 78 | memset(out, 0, out_len); 79 | return false; 80 | } 81 | offset += written; 82 | } 83 | } 84 | 85 | return true; 86 | } -------------------------------------------------------------------------------- /unit-tests/utils/bip32.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // size_t 4 | #include // uint*_t 5 | #include // bool 6 | 7 | /** 8 | * Maximum length of BIP32 path allowed. 9 | */ 10 | #define MAX_BIP32_PATH 10 11 | 12 | /** 13 | * Read BIP32 path from byte buffer. 14 | * 15 | * @param[in] in 16 | * Pointer to input byte buffer. 17 | * @param[in] in_len 18 | * Length of input byte buffer. 19 | * @param[out] out 20 | * Pointer to output 32-bit integer buffer. 21 | * @param[in] out_len 22 | * Number of BIP32 paths read in the output buffer. 23 | * 24 | * @return true if success, false otherwise. 25 | * 26 | */ 27 | bool bip32_path_read(const uint8_t *in, size_t in_len, uint32_t *out, size_t out_len); 28 | 29 | /** 30 | * Format BIP32 path as string. 31 | * 32 | * @param[in] bip32_path 33 | * Pointer to 32-bit integer input buffer. 34 | * @param[in] bip32_path_len 35 | * Maximum number of BIP32 paths in the input buffer. 36 | * @param[out] out string 37 | * Pointer to output string. 38 | * @param[in] out_len 39 | * Length of the output string. 40 | * 41 | * @return true if success, false otherwise. 42 | * 43 | */ 44 | bool bip32_path_format(const uint32_t *bip32_path, 45 | size_t bip32_path_len, 46 | char *out, 47 | size_t out_len); -------------------------------------------------------------------------------- /unit-tests/utils/blake2b-ref.h: -------------------------------------------------------------------------------- 1 | /* 2 | BLAKE2 reference source code package - reference C implementations 3 | 4 | Copyright 2012, Samuel Neves . You may use this under the 5 | terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at 6 | your option. The terms of these licenses can be found at: 7 | 8 | - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 9 | - OpenSSL license : https://www.openssl.org/source/license.html 10 | - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | More information about the BLAKE2 hash function can be found at 13 | https://blake2.net. 14 | */ 15 | #ifndef BLAKE2_H 16 | #define BLAKE2_H 17 | 18 | #include 19 | #include 20 | 21 | #if defined(_MSC_VER) 22 | #define BLAKE2_PACKED(x) __pragma(pack(push, 1)) x __pragma(pack(pop)) 23 | #else 24 | #define BLAKE2_PACKED(x) x __attribute__((packed)) 25 | #endif 26 | 27 | #if defined(__cplusplus) 28 | extern "C" { 29 | #endif 30 | 31 | enum blake2b_constant 32 | { 33 | BLAKE2B_BLOCKBYTES = 128, 34 | BLAKE2B_OUTBYTES = 64, 35 | BLAKE2B_KEYBYTES = 64, 36 | BLAKE2B_SALTBYTES = 16, 37 | BLAKE2B_PERSONALBYTES = 16 38 | }; 39 | 40 | typedef struct blake2b_state__ 41 | { 42 | uint64_t h[8]; 43 | uint64_t t[2]; 44 | uint64_t f[2]; 45 | uint8_t buf[BLAKE2B_BLOCKBYTES]; 46 | size_t buflen; 47 | size_t outlen; 48 | uint8_t last_node; 49 | } blake2b_state; 50 | 51 | 52 | BLAKE2_PACKED(struct blake2b_param__ 53 | { 54 | uint8_t digest_length; /* 1 */ 55 | uint8_t key_length; /* 2 */ 56 | uint8_t fanout; /* 3 */ 57 | uint8_t depth; /* 4 */ 58 | uint32_t leaf_length; /* 8 */ 59 | uint32_t node_offset; /* 12 */ 60 | uint32_t xof_length; /* 16 */ 61 | uint8_t node_depth; /* 17 */ 62 | uint8_t inner_length; /* 18 */ 63 | uint8_t reserved[14]; /* 32 */ 64 | uint8_t salt[BLAKE2B_SALTBYTES]; /* 48 */ 65 | uint8_t personal[BLAKE2B_PERSONALBYTES]; /* 64 */ 66 | }); 67 | 68 | typedef struct blake2b_param__ blake2b_param; 69 | 70 | /* Padded structs result in a compile-time error */ 71 | enum { 72 | BLAKE2_DUMMY_2 = 1/(int)(sizeof(blake2b_param) == BLAKE2B_OUTBYTES) 73 | }; 74 | 75 | /* Streaming API */ 76 | int blake2b_ref_init( blake2b_state *S, size_t outlen ); 77 | int blake2b_ref_init_key( blake2b_state *S, size_t outlen, const void *key, size_t keylen ); 78 | int blake2b_ref_init_param( blake2b_state *S, const blake2b_param *P ); 79 | int blake2b_ref_update( blake2b_state *S, const void *in, size_t inlen ); 80 | int blake2b_ref_final( blake2b_state *S, void *out, size_t outlen ); 81 | 82 | /* Simple API */ 83 | int blake2b_ref( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ); 84 | 85 | /* This is simply an alias for blake2b */ 86 | int blake2_ref( void *out, size_t outlen, const void *in, size_t inlen, const void *key, size_t keylen ); 87 | 88 | #if defined(__cplusplus) 89 | } 90 | #endif 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /unit-tests/utils/cx.c: -------------------------------------------------------------------------------- 1 | #include "cx.h" 2 | #include "blake2b-ref.h" 3 | #include 4 | #include 5 | 6 | #define BUFFER_SIZE 65535 7 | 8 | typedef struct { 9 | uint8_t hashed_data[BUFFER_SIZE]; 10 | size_t hashed_data_offset; 11 | blake2b_state ctx; 12 | } b2b_context; 13 | 14 | static cx_err_t blake2_hash(cx_blake2b_t *hash, 15 | uint32_t mode, 16 | const uint8_t *in, 17 | size_t len, 18 | uint8_t *out, 19 | size_t out_len) { 20 | b2b_context *_ctx = (b2b_context *) hash->ctx; 21 | if ((BUFFER_SIZE - _ctx->hashed_data_offset) < len) return -1; 22 | memmove(_ctx->hashed_data + _ctx->hashed_data_offset, in, len); 23 | _ctx->hashed_data_offset += len; 24 | if (blake2b_ref_update(&_ctx->ctx, in, len) != 0) return -1; 25 | if (mode & CX_LAST) { 26 | if (blake2b_ref_final(&_ctx->ctx, out, out_len) != 0) return -1; 27 | if (mode & CX_NO_REINIT) return 0; 28 | memset(_ctx->hashed_data, 0, BUFFER_SIZE); 29 | _ctx->hashed_data_offset = 0; 30 | return blake2b_ref_init(&_ctx->ctx, hash->info.output_size / 8); 31 | } 32 | return 0; 33 | } 34 | 35 | cx_err_t cx_blake2b_init_no_throw(cx_blake2b_t *hash, size_t out_len) { 36 | b2b_context *ctx = (b2b_context *) malloc(sizeof(b2b_context)); 37 | if (blake2b_ref_init(&ctx->ctx, out_len / 8) == 0) { 38 | hash->ctx = (void *) ctx; 39 | hash->info.md_type = CX_BLAKE2B; 40 | hash->info.output_size = out_len; 41 | memset(ctx->hashed_data, 0, BUFFER_SIZE); 42 | ctx->hashed_data_offset = 0; 43 | return 0; 44 | } else { 45 | free(ctx); 46 | return -1; 47 | } 48 | } 49 | 50 | cx_err_t cx_hash_no_throw(cx_hash_t *hash, 51 | uint32_t mode, 52 | const uint8_t *in, 53 | size_t len, 54 | uint8_t *out, 55 | size_t out_len) { 56 | switch (hash->md_type) { 57 | case CX_BLAKE2B: 58 | return blake2_hash((cx_blake2b_t *) hash, mode, in, len, out, out_len); 59 | default: 60 | return -1; 61 | } 62 | } 63 | 64 | cx_err_t cx_blake2b_256_hash(const uint8_t* data, 65 | size_t len, 66 | uint8_t out[static CX_BLAKE2B_256_SIZE]) 67 | { 68 | return blake2b_ref(out, CX_BLAKE2B_256_SIZE, data, len, NULL, 0); 69 | } 70 | 71 | void _cx_blake2b_get_data(cx_blake2b_t *ctx, uint8_t **data, size_t *len) { 72 | b2b_context *_ctx = (b2b_context *) ctx->ctx; 73 | *data = _ctx->hashed_data; 74 | *len = _ctx->hashed_data_offset; 75 | } 76 | 77 | void _cx_blake2b_free_data(cx_blake2b_t *ctx) { 78 | free(ctx->ctx); 79 | } -------------------------------------------------------------------------------- /unit-tests/utils/cx.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | typedef int cx_err_t; 7 | 8 | typedef enum cx_md_e { CX_BLAKE2B = 9 } cx_md_t; 9 | 10 | /** Convenience type. See #cx_hash_info_t. */ 11 | typedef struct cx_hash_header_s cx_hash_t; 12 | 13 | /* Generic API */ 14 | struct cx_hash_header_s { 15 | cx_md_t md_type; 16 | size_t output_size; 17 | }; 18 | 19 | struct cx_blake2b_s { 20 | struct cx_hash_header_s info; 21 | void *ctx; 22 | }; 23 | /** Convenience type. See #cx_blake2b_s. */ 24 | typedef struct cx_blake2b_s cx_blake2b_t; 25 | 26 | #define CX_OK 0 27 | 28 | #define CX_BLAKE2B_256_SIZE 32 29 | 30 | #define CX_FLAG 31 | /* 32 | * Bit 0 33 | */ 34 | #define CX_LAST (1 << 0) 35 | /* 36 | * Bit 15 37 | */ 38 | #define CX_NO_REINIT (1 << 15) 39 | 40 | cx_err_t cx_blake2b_init_no_throw(cx_blake2b_t *hash, size_t out_len); 41 | cx_err_t cx_hash_no_throw(cx_hash_t *hash, 42 | uint32_t mode, 43 | const uint8_t *in, 44 | size_t len, 45 | uint8_t *out, 46 | size_t out_len); 47 | 48 | cx_err_t cx_blake2b_256_hash(const uint8_t* data, 49 | size_t len, 50 | uint8_t out[static CX_BLAKE2B_256_SIZE]); 51 | 52 | void _cx_blake2b_get_data(cx_blake2b_t *ctx, uint8_t **data, size_t *len); 53 | void _cx_blake2b_free_data(cx_blake2b_t *ctx); -------------------------------------------------------------------------------- /unit-tests/utils/ledger_assert.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define LEDGER_ASSERT(_test, ...) assert(_test) -------------------------------------------------------------------------------- /unit-tests/utils/macro_helpers.h: -------------------------------------------------------------------------------- 1 | // macros that can't be passed through CMake 2 | #pragma once 3 | 4 | // UNUSED macro. Defined in the Makefile 5 | #define UNUSED(x) (void)(x) 6 | 7 | 8 | #define BUFFER_FROM_ARRAY(_name, _array, _size) \ 9 | RW_BUFFER_FROM_ARRAY_FULL(__rw_##_name, _array, _size); \ 10 | buffer_t _name = __rw_##_name.read 11 | 12 | 13 | #define BUFFER_NEW_LOCAL_EMPTY(_name, _size) \ 14 | RW_BUFFER_NEW_LOCAL_EMPTY(__rw_##_name, _size); \ 15 | buffer_t _name = __rw_##_name.read -------------------------------------------------------------------------------- /unit-tests/utils/macros.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * Macro for the size of a specific structure field. 5 | */ 6 | #define MEMBER_SIZE(type, member) (sizeof(((type *) 0)->member)) 7 | 8 | #define WEAK __attribute((weak)) 9 | 10 | #define ARRAY_LENGTH(array) (sizeof((array)) / sizeof((array)[0])) -------------------------------------------------------------------------------- /unit-tests/utils/os.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define PIC(x) (x) -------------------------------------------------------------------------------- /unit-tests/utils/read.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * (c) 2020 Ledger SAS. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | *****************************************************************************/ 16 | 17 | #include // uint*_t 18 | #include // size_t 19 | 20 | #include "read.h" 21 | 22 | uint16_t read_u16_be(const uint8_t *ptr, size_t offset) 23 | { 24 | return (uint16_t) ptr[offset + 0] << 8 | // 25 | (uint16_t) ptr[offset + 1] << 0; 26 | } 27 | 28 | uint32_t read_u32_be(const uint8_t *ptr, size_t offset) 29 | { 30 | return (uint32_t) ptr[offset + 0] << 24 | // 31 | (uint32_t) ptr[offset + 1] << 16 | // 32 | (uint32_t) ptr[offset + 2] << 8 | // 33 | (uint32_t) ptr[offset + 3] << 0; 34 | } 35 | 36 | uint64_t read_u64_be(const uint8_t *ptr, size_t offset) 37 | { 38 | return (uint64_t) ptr[offset + 0] << 56 | // 39 | (uint64_t) ptr[offset + 1] << 48 | // 40 | (uint64_t) ptr[offset + 2] << 40 | // 41 | (uint64_t) ptr[offset + 3] << 32 | // 42 | (uint64_t) ptr[offset + 4] << 24 | // 43 | (uint64_t) ptr[offset + 5] << 16 | // 44 | (uint64_t) ptr[offset + 6] << 8 | // 45 | (uint64_t) ptr[offset + 7] << 0; 46 | } 47 | 48 | uint16_t read_u16_le(const uint8_t *ptr, size_t offset) 49 | { 50 | return (uint16_t) ptr[offset + 0] << 0 | // 51 | (uint16_t) ptr[offset + 1] << 8; 52 | } 53 | 54 | uint32_t read_u32_le(const uint8_t *ptr, size_t offset) 55 | { 56 | return (uint32_t) ptr[offset + 0] << 0 | // 57 | (uint32_t) ptr[offset + 1] << 8 | // 58 | (uint32_t) ptr[offset + 2] << 16 | // 59 | (uint32_t) ptr[offset + 3] << 24; 60 | } 61 | 62 | uint64_t read_u64_le(const uint8_t *ptr, size_t offset) 63 | { 64 | return (uint64_t) ptr[offset + 0] << 0 | // 65 | (uint64_t) ptr[offset + 1] << 8 | // 66 | (uint64_t) ptr[offset + 2] << 16 | // 67 | (uint64_t) ptr[offset + 3] << 24 | // 68 | (uint64_t) ptr[offset + 4] << 32 | // 69 | (uint64_t) ptr[offset + 5] << 40 | // 70 | (uint64_t) ptr[offset + 6] << 48 | // 71 | (uint64_t) ptr[offset + 7] << 56; 72 | } 73 | -------------------------------------------------------------------------------- /unit-tests/utils/read.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // uint*_t 4 | #include // size_t 5 | 6 | /** 7 | * Read 2 bytes as Big Endian from byte buffer. 8 | * 9 | * @param[in] ptr 10 | * Pointer to byte buffer. 11 | * @param[in] offset 12 | * Offset in the byte buffer. 13 | * 14 | * @return 2 bytes value read from buffer. 15 | * 16 | */ 17 | uint16_t read_u16_be(const uint8_t *ptr, size_t offset); 18 | 19 | /** 20 | * Read 4 bytes as Big Endian from byte buffer. 21 | * 22 | * @param[in] ptr 23 | * Pointer to byte buffer. 24 | * @param[in] offset 25 | * Offset in the byte buffer. 26 | * 27 | * @return 4 bytes value read from buffer. 28 | * 29 | */ 30 | uint32_t read_u32_be(const uint8_t *ptr, size_t offset); 31 | 32 | /** 33 | * Read 8 bytes as Big Endian from byte buffer. 34 | * 35 | * @param[in] ptr 36 | * Pointer to byte buffer. 37 | * @param[in] offset 38 | * Offset in the byte buffer. 39 | * 40 | * @return 8 bytes value read from buffer. 41 | * 42 | */ 43 | uint64_t read_u64_be(const uint8_t *ptr, size_t offset); 44 | 45 | /** 46 | * Read 2 bytes as Little Endian from byte buffer. 47 | * 48 | * @param[in] ptr 49 | * Pointer to byte buffer. 50 | * @param[in] offset 51 | * Offset in the byte buffer. 52 | * 53 | * @return 2 bytes value read from buffer. 54 | * 55 | */ 56 | uint16_t read_u16_le(const uint8_t *ptr, size_t offset); 57 | 58 | /** 59 | * Read 4 bytes as Little Endian from byte buffer. 60 | * 61 | * @param[in] ptr 62 | * Pointer to byte buffer. 63 | * @param[in] offset 64 | * Offset in the byte buffer. 65 | * 66 | * @return 4 bytes value read from buffer. 67 | * 68 | */ 69 | uint32_t read_u32_le(const uint8_t *ptr, size_t offset); 70 | 71 | /** 72 | * Read 8 bytes as Little Endian from byte buffer. 73 | * 74 | * @param[in] ptr 75 | * Pointer to byte buffer. 76 | * @param[in] offset 77 | * Offset in the byte buffer. 78 | * 79 | * @return 8 bytes value read from buffer. 80 | * 81 | */ 82 | uint64_t read_u64_le(const uint8_t *ptr, size_t offset); 83 | -------------------------------------------------------------------------------- /unit-tests/utils/varint.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * (c) 2020 Ledger SAS. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | *****************************************************************************/ 16 | 17 | #include // uint*_t 18 | #include // size_t 19 | #include // bool 20 | 21 | #include "varint.h" 22 | #include "write.h" 23 | #include "read.h" 24 | 25 | uint8_t varint_size(uint64_t value) 26 | { 27 | if (value <= 0xFC) { 28 | return 1; 29 | } 30 | 31 | if (value <= UINT16_MAX) { 32 | return 3; 33 | } 34 | 35 | if (value <= UINT32_MAX) { 36 | return 5; 37 | } 38 | 39 | return 9; // <= UINT64_MAX 40 | } 41 | 42 | int varint_read(const uint8_t *in, size_t in_len, uint64_t *value) 43 | { 44 | if (in_len < 1) { 45 | return -1; 46 | } 47 | 48 | uint8_t prefix = in[0]; 49 | 50 | if (prefix == 0xFD) { 51 | if (in_len < 3) { 52 | return -1; 53 | } 54 | *value = (uint64_t) read_u16_le(in, 1); 55 | return 3; 56 | } 57 | 58 | if (prefix == 0xFE) { 59 | if (in_len < 5) { 60 | return -1; 61 | } 62 | *value = (uint64_t) read_u32_le(in, 1); 63 | return 5; 64 | } 65 | 66 | if (prefix == 0xFF) { 67 | if (in_len < 9) { 68 | return -1; 69 | } 70 | *value = (uint64_t) read_u64_le(in, 1); 71 | return 9; 72 | } 73 | 74 | *value = (uint64_t) prefix; // prefix <= 0xFC 75 | 76 | return 1; 77 | } 78 | 79 | int varint_write(uint8_t *out, size_t offset, uint64_t value) 80 | { 81 | uint8_t varint_len = varint_size(value); 82 | 83 | switch (varint_len) { 84 | case 1: 85 | out[offset] = (uint8_t) value; 86 | break; 87 | case 3: 88 | out[offset++] = 0xFD; 89 | write_u16_le(out, offset, (uint16_t) value); 90 | break; 91 | case 5: 92 | out[offset++] = 0xFE; 93 | write_u32_le(out, offset, (uint32_t) value); 94 | break; 95 | case 9: 96 | out[offset++] = 0xFF; 97 | write_u64_le(out, offset, (uint64_t) value); 98 | break; 99 | default: 100 | return -1; 101 | } 102 | 103 | return varint_len; 104 | } 105 | -------------------------------------------------------------------------------- /unit-tests/utils/varint.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // uint*_t 4 | #include // size_t 5 | #include // bool 6 | 7 | /** 8 | * Size of value represented as Bitcoin-like varint. 9 | * 10 | * @see https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer 11 | * 12 | * @param[in] value 13 | * 64-bit unsigned integer to compute varint size. 14 | * 15 | * @return number of bytes to write value as varint (1, 3, 5 or 9 bytes). 16 | * 17 | */ 18 | uint8_t varint_size(uint64_t value); 19 | 20 | /** 21 | * Read Bitcoin-like varint from byte buffer. 22 | * 23 | * @see https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer 24 | * 25 | * @param[in] in 26 | * Pointer to input byte buffer. 27 | * @param[in] in_len 28 | * Length of the input byte buffer. 29 | * @param[out] value 30 | * Pointer to 64-bit unsigned integer to output varint. 31 | * 32 | * @return number of bytes read (1, 3, 5 or 9 bytes), -1 otherwise. 33 | * 34 | */ 35 | int varint_read(const uint8_t *in, size_t in_len, uint64_t *value); 36 | 37 | /** 38 | * Write Bitcoin-like varint to byte buffer. 39 | * 40 | * @see https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer 41 | * 42 | * @param[out] out 43 | * Pointer to output byte buffer. 44 | * @param[in] offset 45 | * Offset in the output byte buffer. 46 | * @param[in] value 47 | * 64-bit unsigned integer to write as varint. 48 | * 49 | * @return number of bytes written (1, 3, 5 or 9 bytes), -1 otherwise. 50 | * 51 | */ 52 | int varint_write(uint8_t *out, size_t offset, uint64_t value); 53 | -------------------------------------------------------------------------------- /unit-tests/utils/write.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * (c) 2020 Ledger SAS. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | *****************************************************************************/ 16 | 17 | #include // uint*_t 18 | #include // size_t 19 | 20 | #include "write.h" 21 | 22 | void write_u16_be(uint8_t *ptr, size_t offset, uint16_t value) 23 | { 24 | ptr[offset + 0] = (uint8_t) (value >> 8); 25 | ptr[offset + 1] = (uint8_t) (value >> 0); 26 | } 27 | 28 | void write_u32_be(uint8_t *ptr, size_t offset, uint32_t value) 29 | { 30 | ptr[offset + 0] = (uint8_t) (value >> 24); 31 | ptr[offset + 1] = (uint8_t) (value >> 16); 32 | ptr[offset + 2] = (uint8_t) (value >> 8); 33 | ptr[offset + 3] = (uint8_t) (value >> 0); 34 | } 35 | 36 | void write_u64_be(uint8_t *ptr, size_t offset, uint64_t value) 37 | { 38 | ptr[offset + 0] = (uint8_t) (value >> 56); 39 | ptr[offset + 1] = (uint8_t) (value >> 48); 40 | ptr[offset + 2] = (uint8_t) (value >> 40); 41 | ptr[offset + 3] = (uint8_t) (value >> 32); 42 | ptr[offset + 4] = (uint8_t) (value >> 24); 43 | ptr[offset + 5] = (uint8_t) (value >> 16); 44 | ptr[offset + 6] = (uint8_t) (value >> 8); 45 | ptr[offset + 7] = (uint8_t) (value >> 0); 46 | } 47 | 48 | void write_u16_le(uint8_t *ptr, size_t offset, uint16_t value) 49 | { 50 | ptr[offset + 0] = (uint8_t) (value >> 0); 51 | ptr[offset + 1] = (uint8_t) (value >> 8); 52 | } 53 | 54 | void write_u32_le(uint8_t *ptr, size_t offset, uint32_t value) 55 | { 56 | ptr[offset + 0] = (uint8_t) (value >> 0); 57 | ptr[offset + 1] = (uint8_t) (value >> 8); 58 | ptr[offset + 2] = (uint8_t) (value >> 16); 59 | ptr[offset + 3] = (uint8_t) (value >> 24); 60 | } 61 | 62 | void write_u64_le(uint8_t *ptr, size_t offset, uint64_t value) 63 | { 64 | ptr[offset + 0] = (uint8_t) (value >> 0); 65 | ptr[offset + 1] = (uint8_t) (value >> 8); 66 | ptr[offset + 2] = (uint8_t) (value >> 16); 67 | ptr[offset + 3] = (uint8_t) (value >> 24); 68 | ptr[offset + 4] = (uint8_t) (value >> 32); 69 | ptr[offset + 5] = (uint8_t) (value >> 40); 70 | ptr[offset + 6] = (uint8_t) (value >> 48); 71 | ptr[offset + 7] = (uint8_t) (value >> 56); 72 | } -------------------------------------------------------------------------------- /unit-tests/utils/write.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // uint*_t 4 | #include // size_t 5 | 6 | /** 7 | * Write 16-bit unsigned integer value as Big Endian. 8 | * 9 | * @param[out] ptr 10 | * Pointer to output byte buffer. 11 | * @param[in] offset 12 | * Offset in the output byte buffer. 13 | * @param[in] value 14 | * 16-bit unsigned integer to write in output byte buffer as Big Endian. 15 | * 16 | */ 17 | void write_u16_be(uint8_t *ptr, size_t offset, uint16_t value); 18 | 19 | /** 20 | * Write 32-bit unsigned integer value as Big Endian. 21 | * 22 | * @param[out] ptr 23 | * Pointer to output byte buffer. 24 | * @param[in] offset 25 | * Offset in the output byte buffer. 26 | * @param[in] value 27 | * 32-bit unsigned integer to write in output byte buffer as Big Endian. 28 | * 29 | */ 30 | void write_u32_be(uint8_t *ptr, size_t offset, uint32_t value); 31 | 32 | /** 33 | * Write 64-bit unsigned integer value as Big Endian. 34 | * 35 | * @param[out] ptr 36 | * Pointer to output byte buffer. 37 | * @param[in] offset 38 | * Offset in the output byte buffer. 39 | * @param[in] value 40 | * 64-bit unsigned integer to write in output byte buffer as Big Endian. 41 | * 42 | */ 43 | void write_u64_be(uint8_t *ptr, size_t offset, uint64_t value); 44 | 45 | /** 46 | * Write 16-bit unsigned integer value as Little Endian. 47 | * 48 | * @param[out] ptr 49 | * Pointer to output byte buffer. 50 | * @param[in] offset 51 | * Offset in the output byte buffer. 52 | * @param[in] value 53 | * 16-bit unsigned integer to write in output byte buffer as Little Endian. 54 | * 55 | */ 56 | void write_u16_le(uint8_t *ptr, size_t offset, uint16_t value); 57 | 58 | /** 59 | * Write 32-bit unsigned integer value as Little Endian. 60 | * 61 | * @param[out] ptr 62 | * Pointer to output byte buffer. 63 | * @param[in] offset 64 | * Offset in the output byte buffer. 65 | * @param[in] value 66 | * 32-bit unsigned integer to write in output byte buffer as Little Endian. 67 | * 68 | */ 69 | void write_u32_le(uint8_t *ptr, size_t offset, uint32_t value); 70 | 71 | /** 72 | * Write 64-bit unsigned integer value as Little Endian. 73 | * 74 | * @param[out] ptr 75 | * Pointer to output byte buffer. 76 | * @param[in] offset 77 | * Offset in the output byte buffer. 78 | * @param[in] value 79 | * 64-bit unsigned integer to write in output byte buffer as Little Endian. 80 | * 81 | */ 82 | void write_u64_le(uint8_t *ptr, size_t offset, uint64_t value); 83 | --------------------------------------------------------------------------------