├── .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