├── .cargo └── config.toml ├── .github └── workflows │ ├── build_with_current_nightly.yml │ ├── ci.yml │ ├── force-rebase.yml │ ├── get_rust_apps.py │ ├── publish.yml │ └── reusable_build_all_apps.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── include_gif ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── ledger_device_sdk ├── .cargo │ └── config.toml ├── Cargo.toml ├── README.md ├── examples │ ├── crab_64x64.gif │ ├── gadgets.rs │ ├── nbgl_address.rs │ ├── nbgl_choice.rs │ ├── nbgl_generic_review.rs │ ├── nbgl_home.rs │ ├── nbgl_review.rs │ ├── nbgl_spinner.rs │ ├── nbgl_streaming_review.rs │ └── review.rs ├── icons │ ├── Important_Circle_64px.png │ ├── Warning_64px.gif │ ├── badge_back.gif │ ├── badge_check.gif │ ├── icon_back.gif │ ├── icon_back_x.gif │ ├── icon_certificate.gif │ ├── icon_coggle.gif │ ├── icon_cross_badge.gif │ ├── icon_crossmark.gif │ ├── icon_dashboard.gif │ ├── icon_dashboard_x.gif │ ├── icon_down.gif │ ├── icon_eye.gif │ ├── icon_left.gif │ ├── icon_processing.gif │ ├── icon_right.gif │ ├── icon_up.gif │ ├── icon_validate_14.gif │ └── icon_warning.gif └── src │ ├── ble.rs │ ├── ecc.rs │ ├── ecc │ └── stark.rs │ ├── hash.rs │ ├── hash │ ├── blake2.rs │ ├── ripemd.rs │ ├── sha2.rs │ └── sha3.rs │ ├── hmac.rs │ ├── hmac │ ├── ripemd.rs │ └── sha2.rs │ ├── io.rs │ ├── lib.rs │ ├── libcall.rs │ ├── libcall │ ├── string.rs │ └── swap.rs │ ├── nbgl.rs │ ├── nbgl │ ├── nbgl_address_review.rs │ ├── nbgl_choice.rs │ ├── nbgl_generic_review.rs │ ├── nbgl_home_and_settings.rs │ ├── nbgl_review.rs │ ├── nbgl_review_status.rs │ ├── nbgl_spinner.rs │ ├── nbgl_status.rs │ └── nbgl_streaming_review.rs │ ├── nvm.rs │ ├── random.rs │ ├── screen.rs │ ├── seph.rs │ ├── testing.rs │ ├── ui.rs │ ├── ui │ ├── bagls.rs │ ├── bagls │ │ └── se.rs │ ├── bitmaps.rs │ ├── fonts.rs │ ├── fonts │ │ └── opensans.rs │ ├── gadgets.rs │ ├── layout.rs │ ├── screen_util.rs │ └── string_se.rs │ └── uxapp.rs ├── ledger_secure_sdk_sys ├── Cargo.toml ├── README.md ├── build.rs ├── c_sdk_build_flex.cflags ├── c_sdk_build_nanosplus.cflags ├── c_sdk_build_nanox.cflags ├── c_sdk_build_stax.cflags ├── csdk_flex.h ├── csdk_nanos2.h ├── csdk_nanox.h ├── csdk_stax.h ├── flex.json ├── flex_layout.ld ├── link.ld ├── link_wrap.sh ├── nanosplus.json ├── nanosplus_layout.ld ├── nanox.json ├── nanox_layout.ld ├── src │ ├── buttons.rs │ ├── c │ │ ├── sjlj.s │ │ └── src.c │ ├── infos.rs │ ├── lib.rs │ └── seph.rs ├── stax.json └── stax_layout.ld ├── rust-toolchain.toml └── testmacro ├── Cargo.toml ├── README.md └── src └── lib.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [unstable] 2 | build-std = ["core", "alloc"] 3 | build-std-features = ["compiler-builtins-mem"] 4 | 5 | [build] 6 | target = "flex" -------------------------------------------------------------------------------- /.github/workflows/build_with_current_nightly.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test with last nightly everyday @12PM 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | # * is a special character in YAML so you have to quote this string 7 | - cron: '0 12 * * *' 8 | 9 | jobs: 10 | build_with_last_nightly: 11 | runs-on: ubuntu-latest 12 | container: 13 | image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest 14 | strategy: 15 | matrix: 16 | device: ["nanox", "nanosplus", "stax", "flex"] 17 | steps: 18 | - name: update nightly toolchain 19 | run: | 20 | rustup update nightly 21 | rustup component add rust-src --toolchain nightly 22 | cargo +nightly ledger setup 23 | 24 | - name: Checkout Code 25 | uses: actions/checkout@v4 26 | with: 27 | repository: LedgerHQ/app-boilerplate-rust 28 | path: app-boilerplate 29 | 30 | - name: Build 31 | run: | 32 | BUILD_DEVICE_NAME="$(echo ${{ matrix.device }})" 33 | BIN_DIR_NAME="$(echo ${{ matrix.device }} | sed 's/nanosplus/nanos2/')" 34 | cd app-boilerplate 35 | cargo ledger build ${{ matrix.device }} 36 | 37 | #- name: Upload binary artifacts 38 | # uses: actions/upload-artifact@v3 39 | # with: 40 | # name: "app_elf_binaries" 41 | # path: app-boilerplate/target/* 42 | # if-no-files-found: error 43 | 44 | #ragger_tests: 45 | # name: Run ragger tests using the reusable workflow 46 | # needs: build_with_last_nightly 47 | # uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_ragger_tests.yml@v1 48 | # with: 49 | # app_repository: LedgerHQ/app-boilerplate-rust 50 | # app_branch_name: "main" 51 | # download_app_binaries_artifact: "app_elf_binaries" -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Run cargo clippy, cargo fmt, build and Unit+Integration tests 2 | 3 | on: 4 | push: 5 | branches: 6 | master 7 | pull_request: 8 | workflow_dispatch: 9 | inputs: 10 | name: 11 | description: 'Manually triggered' 12 | 13 | env: 14 | CARGO_TERM_COLOR: always 15 | 16 | jobs: 17 | clippy: 18 | name: Run static analysis 19 | runs-on: ubuntu-latest 20 | container: 21 | image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest 22 | strategy: 23 | matrix: 24 | target: ["nanox", "nanosplus", "stax", "flex"] 25 | package: [include_gif, testmacro, ledger_secure_sdk_sys, ledger_device_sdk] 26 | steps: 27 | - name: Clone 28 | uses: actions/checkout@v4 29 | - name: Cargo clippy 30 | working-directory: ${{ matrix.package }} 31 | run: | 32 | cargo clippy --target ${{ matrix.target }} 33 | 34 | format: 35 | name: Check code formatting 36 | runs-on: ubuntu-latest 37 | container: 38 | image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest 39 | steps: 40 | - name: Clone 41 | uses: actions/checkout@v4 42 | - name: Run cargo fmt 43 | run: | 44 | cargo fmt --all --check 45 | 46 | build: 47 | name: Build SDK 48 | runs-on: ubuntu-latest 49 | container: 50 | image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest 51 | strategy: 52 | matrix: 53 | target: ["nanox", "nanosplus", "stax", "flex"] 54 | steps: 55 | - name: Clone 56 | uses: actions/checkout@v4 57 | - name: Cargo build 58 | working-directory: ledger_device_sdk 59 | run: | 60 | cargo build --target ${{ matrix.target }} 61 | 62 | test: 63 | name: Run unit and integration tests 64 | runs-on: ubuntu-latest 65 | container: 66 | image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest 67 | strategy: 68 | matrix: 69 | target: ["nanox", "nanosplus", "stax", "flex"] 70 | steps: 71 | - name: Clone 72 | uses: actions/checkout@v4 73 | - name: Unit tests 74 | working-directory: ledger_device_sdk 75 | run: | 76 | cargo test --target ${{ matrix.target }} --features speculos --tests 77 | -------------------------------------------------------------------------------- /.github/workflows/force-rebase.yml: -------------------------------------------------------------------------------- 1 | name: Force rebased 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | force-rebase: 7 | runs-on: ubuntu-latest 8 | steps: 9 | 10 | - name: 'PR commits + 1' 11 | id: pr_commits 12 | run: echo "pr_fetch_depth=$(( ${{ github.event.pull_request.commits }} + 1 ))" >> "${GITHUB_OUTPUT}" 13 | 14 | - name: 'Checkout PR branch and all PR commits' 15 | uses: actions/checkout@v4 16 | with: 17 | ref: ${{ github.event.pull_request.head.sha }} 18 | fetch-depth: ${{ steps.pr_commits.outputs.pr_fetch_depth }} 19 | 20 | - name: Check if PR branch is rebased on target branch 21 | shell: bash 22 | run: | 23 | git merge-base --is-ancestor ${{ github.event.pull_request.base.sha }} HEAD 24 | 25 | - name: Check if PR branch contains merge commits 26 | shell: bash 27 | run: | 28 | merges=$(git log --oneline HEAD~${{ github.event.pull_request.commits }}...HEAD --merges ); \ 29 | echo "--- Merges ---"; \ 30 | echo ${merges}; \ 31 | [[ -z "${merges}" ]] 32 | -------------------------------------------------------------------------------- /.github/workflows/get_rust_apps.py: -------------------------------------------------------------------------------- 1 | from ledgered.github import GitHubLedgerHQ, NoManifestException, Condition 2 | from github.GithubException import GithubException 3 | 4 | import sys 5 | import json 6 | 7 | if len(sys.argv) != 2: 8 | print("Usage: get_rust_apps.py ") 9 | sys.exit(1) 10 | 11 | # Excluded Rust apps 12 | # app-kadena-legacy: has been replaced by app-kadena 13 | # app-pocket: does not build (Obsidians' Alamgu issue) 14 | # app-age: not maintained anymore 15 | excluded_apps = ["app-kadena-legacy", "app-pocket", "app-age"] 16 | 17 | # Retrieve all public apps on LedgerHQ GitHub organization 18 | token = sys.argv[1] 19 | gh = GitHubLedgerHQ(token) 20 | apps=gh.apps.filter(private=Condition.WITHOUT, archived=Condition.WITHOUT) 21 | 22 | rust_apps = [] 23 | exclude_apps = [] 24 | # loop all apps in gh.apps 25 | for app in apps: 26 | try: 27 | manifest = app.manifest 28 | except NoManifestException as e: 29 | pass 30 | except GithubException as e: 31 | pass 32 | else: 33 | # Filter out apps that are Rust based 34 | if manifest.app.sdk == "rust": 35 | if app.name not in excluded_apps: 36 | for d in manifest.app.devices: 37 | rust_apps.append({"app-name": app.name, "device": d}) 38 | 39 | # save the list of (apps, device) pairs to build in a json format: 40 | with open("rust_apps.json", "w") as f: 41 | f.write(json.dumps(rust_apps)) -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Crates.io 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | workflow_dispatch: # Allow manual workflow dispatch 8 | 9 | jobs: 10 | dry-run-publish: 11 | runs-on: ubuntu-latest 12 | if: github.event_name == 'workflow_dispatch' # Only run this job for manual triggers 13 | 14 | steps: 15 | - name: Checkout Code 16 | uses: actions/checkout@v4 17 | 18 | - name: Set Up Rust 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | profile: minimal 22 | toolchain: stable 23 | 24 | - name: Test Dry-Run Publish for Each Package 25 | run: | 26 | # Iterate through package names retrieved 27 | PACKAGE_NAMES="testmacro include_gif ledger_secure_sdk_sys ledger_device_sdk" 28 | for PACKAGE_NAME in $PACKAGE_NAMES; do 29 | # Test a dry-run publish for each package within the workspace if required 30 | last_published_version=$(cargo search -q --limit 1 $PACKAGE_NAME | grep -oP '\d+\.\d+\.\d+') 31 | echo "Published version of $PACKAGE_NAME is $version_published" 32 | manifest_version=$(cargo metadata --format-version=1 --no-deps | jq -r --arg PACKAGE_NAME "$PACKAGE_NAME" '.packages[] | select(.name == $PACKAGE_NAME) | .version') 33 | echo "Current version in manifest for $PACKAGE_NAME is $manifest_version" 34 | if [ "$last_published_version" == "$manifest_version" ]; then 35 | echo "Package $PACKAGE_NAME is already published with version $manifest_version." 36 | else 37 | echo "Package $PACKAGE_NAME is not published with version $manifest_version." 38 | echo "Dry-run publishing $PACKAGE_NAME..." 39 | cargo publish --dry-run --no-verify --token ${{ secrets.CARGO_REGISTRY_TOKEN }} --package "$PACKAGE_NAME" 40 | fi 41 | done 42 | env: 43 | CARGO_TERM_COLOR: always 44 | working-directory: ${{ github.workspace }} 45 | 46 | crates-io-publish: 47 | runs-on: ubuntu-latest 48 | if: github.event_name == 'push' # Only run this job for pushes 49 | 50 | steps: 51 | - name: Checkout Code 52 | uses: actions/checkout@v4 53 | 54 | - name: Set Up Rust 55 | uses: actions-rs/toolchain@v1 56 | with: 57 | profile: minimal 58 | toolchain: stable 59 | 60 | - name: Publish Package on crates.io if required 61 | run: | 62 | # Iterate through package names retrieved 63 | PACKAGE_NAMES="testmacro include_gif ledger_secure_sdk_sys ledger_device_sdk" 64 | for PACKAGE_NAME in $PACKAGE_NAMES; do 65 | # Publish each package within the workspace if required 66 | last_published_version=$(cargo search -q --limit 1 $PACKAGE_NAME | grep -oP '\d+\.\d+\.\d+') 67 | echo "Published version of $PACKAGE_NAME is $last_published_version" 68 | manifest_version=$(cargo metadata --format-version=1 --no-deps | jq -r --arg PACKAGE_NAME "$PACKAGE_NAME" '.packages[] | select(.name == $PACKAGE_NAME) | .version') 69 | echo "Current version in manifest for $PACKAGE_NAME is $manifest_version" 70 | if [ "$last_published_version" == "$manifest_version" ]; then 71 | echo "Package $PACKAGE_NAME is already published with version $manifest_version." 72 | else 73 | echo "Package $PACKAGE_NAME with version $manifest_version is not published." 74 | echo "Publishing $PACKAGE_NAME..." 75 | cargo publish --no-verify --token ${{ secrets.CARGO_REGISTRY_TOKEN }} --package "$PACKAGE_NAME" 76 | fi 77 | done 78 | env: 79 | CARGO_TERM_COLOR: always 80 | working-directory: ${{ github.workspace }} 81 | -------------------------------------------------------------------------------- /.github/workflows/reusable_build_all_apps.yml: -------------------------------------------------------------------------------- 1 | name: Build all Rust apps 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | c_sdk_branch: 7 | required: false 8 | default: '' 9 | type: string 10 | workflow_dispatch: 11 | pull_request: 12 | 13 | env: 14 | C_SDK_URL: 'https://github.com/LedgerHQ/ledger-secure-sdk.git' 15 | 16 | jobs: 17 | how-workflow-is-called: 18 | name: Determine how the workflow is called 19 | runs-on: ubuntu-latest 20 | outputs: 21 | repository: ${{ steps.get_repo_and_branch.outputs.repo }} 22 | branch: ${{ steps.get_repo_and_branch.outputs.branch }} 23 | steps: 24 | - name: Get repository and branch 25 | id: get_repo_and_branch 26 | run: | 27 | if [ -n "${{ inputs.c_sdk_branch }}" ]; then 28 | echo "repo=LedgerHQ/ledger-device-rust-sdk" >> $GITHUB_OUTPUT 29 | echo "branch=master" >> $GITHUB_OUTPUT 30 | else 31 | echo "repo=${{ github.repository}}" >> $GITHUB_OUTPUT 32 | echo "branch=${{ github.ref }}" >> $GITHUB_OUTPUT 33 | fi 34 | retrieve-rust-apps: 35 | name: Retrieve Rust Apps 36 | runs-on: ubuntu-latest 37 | needs: how-workflow-is-called 38 | outputs: 39 | rust_apps: ${{ steps.get_rust_apps.outputs.rust_apps }} 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v4 43 | with: 44 | repository: ${{ needs.how-workflow-is-called.outputs.repository }} 45 | ref: ${{ needs.how-workflow-is-called.outputs.branch }} 46 | - name: Set up Python 47 | uses: actions/setup-python@v4 48 | with: 49 | python-version: '3.x' 50 | - name: Install ledgered 51 | run: pip install ledgered 52 | - name: Get all rust apps 53 | id: get_rust_apps 54 | run: | 55 | python .github/workflows/get_rust_apps.py ${{ secrets.GITHUB_TOKEN }} 56 | echo "rust_apps=$(cat rust_apps.json)" >> $GITHUB_OUTPUT 57 | 58 | display-rust-apps: 59 | name: Display Rust Apps 60 | runs-on: ubuntu-latest 61 | needs: retrieve-rust-apps 62 | steps: 63 | - name: Display Rust Apps 64 | run: | 65 | echo "Rust apps: ${{ needs.retrieve-rust-apps.outputs.rust_apps }}" 66 | 67 | test-build: 68 | name: Build for all targets 69 | needs: [retrieve-rust-apps, how-workflow-is-called] 70 | strategy: 71 | fail-fast: false 72 | matrix: 73 | app-name: ["app-boilerplate-rust"] 74 | device: ["nanos+", "nanox", "stax", "flex"] 75 | include: ${{ fromJSON(needs.retrieve-rust-apps.outputs.rust_apps) }} 76 | runs-on: ubuntu-latest 77 | container: 78 | image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest 79 | steps: 80 | - name: Install ledgered 81 | run: pip install ledgered --break-system-packages 82 | - name: Clone SDK 83 | uses: actions/checkout@v4 84 | with: 85 | path: sdk 86 | repository: ${{ needs.how-workflow-is-called.outputs.repository }} 87 | ref: ${{ needs.how-workflow-is-called.outputs.branch }} 88 | - name: Clone App 89 | uses: actions/checkout@v4 90 | with: 91 | repository: LedgerHQ/${{ matrix.app-name }} 92 | submodules: true 93 | path: ${{ matrix.app-name }} 94 | - name: Patch Cargo.toml 95 | continue-on-error: false 96 | run: | 97 | cd ${{ matrix.app-name }} 98 | build_directory=$(ledger-manifest --output-build-directory ledger_app.toml) 99 | cd $build_directory 100 | workspace_root=$(cargo metadata --no-deps --format-version 1 | jq -r '.workspace_root') 101 | cargo_toml_path="$workspace_root/Cargo.toml" 102 | 103 | # Patch ledger_device_sdk 104 | echo "" >> $cargo_toml_path 105 | echo "[patch.crates-io.ledger_device_sdk]" >> $cargo_toml_path 106 | path=\"$GITHUB_WORKSPACE/sdk/ledger_device_sdk\" 107 | echo "path=$path" >> $cargo_toml_path 108 | echo "Patch added to Cargo.toml" 109 | 110 | # Patch ledger_secure_sdk_sys 111 | echo "" >> $cargo_toml_path 112 | echo "[patch.crates-io.ledger_secure_sdk_sys]" >> $cargo_toml_path 113 | path=\"$GITHUB_WORKSPACE/sdk/ledger_secure_sdk_sys\" 114 | echo "path=$path" >> $cargo_toml_path 115 | echo "Patch added to Cargo.toml" 116 | 117 | # Patch include_gif 118 | echo "" >> $cargo_toml_path 119 | echo "[patch.crates-io.include_gif]" >> $cargo_toml_path 120 | path=\"$GITHUB_WORKSPACE/sdk/include_gif\" 121 | echo "path=$path" >> $cargo_toml_path 122 | echo "Patch added to Cargo.toml" 123 | 124 | # Print Cargo.toml 125 | echo "Cargo.toml:" 126 | cat $cargo_toml_path 127 | 128 | - name: Build 129 | run: | 130 | # Clone C SDK if provided 131 | if [ -n "${{ inputs.c_sdk_branch }}" ]; then 132 | git clone $C_SDK_URL --branch ${{ inputs.c_sdk_branch }} --single-branch c_sdk 133 | echo "setting LEDGER_SDK_PATH to $(realpath c_sdk)" 134 | LEDGER_SDK_PATH=$(realpath c_sdk) 135 | else 136 | echo "using C SDK from ledger-app-builder" 137 | fi 138 | cd ${{ matrix.app-name }} 139 | build_directory=$(ledger-manifest --output-build-directory ledger_app.toml) 140 | cd $build_directory 141 | # Required as patch has a different version from what is locked in Cargo.lock 142 | cargo update include_gif 143 | cargo update ledger_secure_sdk_sys 144 | cargo update ledger_device_sdk 145 | device=$(echo ${{ matrix.device }} | sed 's/+/plus/') 146 | echo "Build for "$device 147 | cargo ledger build $device 148 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Rust 2 | /target 3 | 4 | # Editors 5 | .idea/ 6 | .vscode/ 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "ledger_device_sdk", 4 | "ledger_secure_sdk_sys", 5 | "include_gif", 6 | "testmacro" 7 | ] 8 | resolver = "2" 9 | 10 | [workspace.package] 11 | license = "Apache-2.0" 12 | description = "Ledger Device Rust SDK crates" 13 | repository = "https://github.com/LedgerHQ/ledger-device-rust-sdk" 14 | readme = "README.md" 15 | authors = ["Ledger"] 16 | 17 | [profile.release] 18 | opt-level = 's' 19 | lto = true -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ledger Device Rust SDK 2 | [![DEV SUPPORT](https://img.shields.io/badge/Dev_Support-red?logo=discord 3 | )](https://discord.com/channels/885256081289379850/912241185677000704) 4 | 5 | ## Crates 6 | 7 | | Crate | Description | Latest Release | 8 | | ------------------------------------------------ | --------------------------------------------------------------- | -------------- | 9 | | [cargo-ledger](./cargo-ledger) | Cargo extension required to build Ledger applications | ![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FLedgerHQ%2Fledger-device-rust-sdk%2Frefs%2Fheads%2Fmaster%2Fcargo-ledger%2FCargo.toml&query=%24.package.version&label=version) | 10 | | [ledger_device_sdk](./ledger_device_sdk) | Rust SDK | ![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FLedgerHQ%2Fledger-device-rust-sdk%2Frefs%2Fheads%2Fmaster%2Fledger_device_sdk%2FCargo.toml&query=%24.package.version&label=version) | 11 | | [ledger_secure_sdk_sys](./ledger_secure_sdk_sys) | [C SDK](https://github.com/LedgerHQ/ledger-secure-sdk) bindings | ![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FLedgerHQ%2Fledger-device-rust-sdk%2Frefs%2Fheads%2Fmaster%2Fledger_secure_sdk_sys%2FCargo.toml&query=%24.package.version&label=version) | 12 | | [include_gif](./include_gif) | Procedural macro to integrate logo in the UI/UX | ![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FLedgerHQ%2Fledger-device-rust-sdk%2Frefs%2Fheads%2Fmaster%2Finclude_gif%2FCargo.toml&query=%24.package.version&label=version) | 13 | | [testmacro](./testmacro) | Procedural macro used by unit and integrations tests | ![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FLedgerHQ%2Fledger-device-rust-sdk%2Frefs%2Fheads%2Fmaster%2Ftestmacro%2FCargo.toml&query=%24.package.version&label=version) | 14 | 15 | ## Docker builder 16 | 17 | Docker images are available and shall be used to build and test Rust applications for Ledger devices. 18 | 19 | ||`full` | `dev-tools` | 20 | | ---- | ----- | ----------- | 21 | | Rust nightly-2024-12-01 toolchain | :white_check_mark: | :white_check_mark: | 22 | | cargo-ledger | :white_check_mark: | :white_check_mark: | 23 | | Rust Ledger devices custom targets | :white_check_mark: | :white_check_mark: | 24 | | ARM & LLVM toolchains | :white_check_mark: | :white_check_mark: | 25 | | [Speculos](https://github.com/LedgerHQ/speculos) | :x: | :white_check_mark: | 26 | | [Ragger](https://github.com/LedgerHQ/ragger)  | :x: | :white_check_mark: | 27 | 28 | Please check [here](https://github.com/LedgerHQ/ledger-app-builder) for more details. 29 | 30 | ## Links 31 | 32 | To learn more about using the SDK and what is required to publish an app on the Ledger Live app store, please don't hesitate to check the following resources: 33 | 34 | - 📚 [Developer's documentation](https://developers.ledger.com/) 35 | - 🗣️ [Ledger's Discord server](https://discord.gg/Ledger) 36 | - 📦 [Fully featured boilerplate app](https://github.com/LedgerHQ/app-boilerplate-rust) -------------------------------------------------------------------------------- /include_gif/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "include_gif" 3 | version = "1.2.0" 4 | edition = "2021" 5 | license.workspace = true 6 | repository.workspace = true 7 | description = "procedural macro that packs a gif image into a byte representation" 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | syn = { version = "1.0", features = ["full"] } 14 | flate2 = "1.0.28" 15 | image = "=0.24.9" 16 | -------------------------------------------------------------------------------- /include_gif/README.md: -------------------------------------------------------------------------------- 1 | # include_gif 2 | ![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FLedgerHQ%2Fledger-device-rust-sdk%2Frefs%2Fheads%2Fmaster%2Finclude_gif%2FCargo.toml&query=%24.package.version&label=version) 3 | 4 | This crate provides a macro `include_gif!("path/to/image.gif")` that packs a gif image into a byte representation that can be understood by the [Rust SDK](https://github.com/LedgerHQ/ledger-device-rust-sdk/tree/master/ledger_device_sdk) and included at compile time to produce icons. 5 | -------------------------------------------------------------------------------- /include_gif/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | 3 | use image::*; 4 | use proc_macro::TokenStream; 5 | use std::collections::HashMap; 6 | use std::io::Write; 7 | use syn::{parse_macro_input, Ident, LitStr}; 8 | 9 | enum BppFormat { 10 | Bpp1 = 0, 11 | Bpp2 = 1, 12 | Bpp4 = 2, 13 | } 14 | 15 | enum GlyphType { 16 | Bagl, 17 | Nbgl, 18 | } 19 | 20 | enum Input { 21 | FileNameOnly(LitStr), 22 | FileNameAndType(LitStr, GlyphType), 23 | } 24 | 25 | impl syn::parse::Parse for Input { 26 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 27 | // Parse the filename 28 | let filename = input.parse::()?; 29 | if input.is_empty() { 30 | Ok(Input::FileNameOnly(filename)) 31 | } else { 32 | // Parse comma separator (won't be used, just to skip it) 33 | let _: syn::Token![,] = input.parse()?; 34 | // Parse the glyph type 35 | let glyph_type = input.parse::()?; 36 | match glyph_type.to_string().as_str() { 37 | "BAGL" => Ok(Input::FileNameAndType(filename, GlyphType::Bagl)), 38 | "NBGL" => Ok(Input::FileNameAndType(filename, GlyphType::Nbgl)), 39 | _ => Err(syn::Error::new_spanned(glyph_type, "Invalid glyph type")), 40 | } 41 | } 42 | } 43 | } 44 | 45 | #[proc_macro] 46 | pub fn include_gif(input: TokenStream) -> TokenStream { 47 | let input = parse_macro_input!(input as Input); 48 | match input { 49 | Input::FileNameOnly(filename) => { 50 | // Default to Bagl if no type is specified. 51 | let generate_type = GlyphType::Bagl; 52 | generate_glyph(filename, generate_type) 53 | } 54 | Input::FileNameAndType(filename, generate_type) => generate_glyph(filename, generate_type), 55 | } 56 | } 57 | 58 | fn generate_glyph(filename: LitStr, glyph_type: GlyphType) -> TokenStream { 59 | let path = format!( 60 | "{}/{}", 61 | std::env::var("CARGO_MANIFEST_DIR").unwrap(), 62 | filename.value() 63 | ); 64 | let grayscale_image: GrayImage = open(path).unwrap().to_luma8(); 65 | let mut vec_output = Vec::new(); 66 | 67 | match glyph_type { 68 | GlyphType::Bagl => { 69 | let packed = generate_bagl_glyph(&grayscale_image); 70 | write!( 71 | &mut vec_output, 72 | "(&{:?}, {}, {})", 73 | packed, 74 | grayscale_image.width(), 75 | grayscale_image.height() 76 | ) 77 | .unwrap(); 78 | } 79 | GlyphType::Nbgl => { 80 | let (compressed_buffer, bpp) = generate_nbgl_glyph(&grayscale_image); 81 | write!( 82 | &mut vec_output, 83 | "(&{:?}, {}, {}, {}, {})", 84 | compressed_buffer, 85 | grayscale_image.width(), 86 | grayscale_image.height(), 87 | bpp, 88 | true 89 | ) 90 | .unwrap(); 91 | } 92 | }; 93 | 94 | let stream_output = std::str::from_utf8(&vec_output).unwrap(); 95 | stream_output.parse().unwrap() 96 | } 97 | 98 | // Convert a frame into a bagl glyph : pack 8 pixels in a single byte. 99 | // Each pixel is 1 bit, 0 for black, 1 for white. 100 | fn generate_bagl_glyph(frame: &GrayImage) -> Vec { 101 | let width = frame.width() as usize; 102 | let height = frame.height() as usize; 103 | // Number of pixels to be packed into bytes 104 | let size = width * height; 105 | let mut packed = Vec::with_capacity(size / 8); 106 | // Main loop, run through all pixels in the frame, by groups of 8 107 | for i in 0..size / 8 { 108 | let mut byte = 0; 109 | for j in 0..8 { 110 | // Compute linear index 111 | let idx = 8 * i + j; 112 | // Get x and y coordinates from linear index 113 | // Remainder of the division by width tells us how far we are on the x axis. 114 | let x = idx % width; 115 | // Integer division by width tells us how far we are on the y axis. 116 | let y = idx / width; 117 | let pixel = frame.get_pixel(x as u32, y as u32); 118 | // If pixel is not black (0), set the corresponding bit in the byte. 119 | let color = (pixel[0] != 0) as u8; 120 | // Set the j-th bit of the byte to the color of the pixel. 121 | byte |= color << j; 122 | } 123 | packed.push(byte); 124 | } 125 | // Remainder handling 126 | let remainder = size % 8; 127 | if remainder != 0 { 128 | let mut byte = 0; 129 | for j in 0..remainder { 130 | let x = (8 * (size / 8) + j) % width; 131 | let y = (8 * (size / 8) + j) / width; 132 | let pixel = frame.get_pixel(x as u32, y as u32); 133 | let color = (pixel[0] != 0) as u8; 134 | byte |= color << j; 135 | } 136 | packed.push(byte); 137 | } 138 | packed 139 | } 140 | 141 | // Get the palette of colors of a grayscale image 142 | fn get_palette<'a>(img: &'a GrayImage) -> Vec { 143 | let mut palette = HashMap::new(); 144 | // Count the number of occurrences of each color 145 | for &pixel in img.pixels() { 146 | *palette.entry(pixel[0]).or_insert(0) += 1; 147 | } 148 | let palette: Vec<_> = palette.into_iter().collect(); 149 | // Collect all colors in a vector 150 | palette.into_iter().map(|(luma, _)| luma).collect() 151 | } 152 | 153 | fn image_to_packed_buffer(frame: &GrayImage) -> (Vec, u8) { 154 | let mut colors = get_palette(&frame).len() as u8; 155 | if colors > 16 { 156 | colors = 16; 157 | } 158 | // Round number of colors to a power of 2 159 | if !(colors != 0 && colors.count_ones() == 1) { 160 | colors = (2.0_f64.powf((colors as f64).log2().ceil())) as u8; 161 | } 162 | 163 | let mut bits_per_pixel: u8 = (colors as f32).log2().floor() as u8; 164 | match bits_per_pixel { 165 | 0 => bits_per_pixel = 1, 166 | 3 => bits_per_pixel = 4, 167 | _ => (), 168 | } 169 | 170 | let width = frame.width(); 171 | let height = frame.height(); 172 | let base_threshold = (256 / colors as u32) as u8; 173 | let half_threshold = base_threshold / 2; 174 | let mut current_byte = 0 as u16; 175 | let mut current_bit = 0 as u16; 176 | let mut packed: Vec = Vec::new(); 177 | 178 | for x in (0..width).rev() { 179 | for y in 0..height { 180 | let mut color: u16 = frame.get_pixel(x, y)[0] as u16; 181 | color = (color + half_threshold as u16) / base_threshold as u16; 182 | if color >= colors as u16 { 183 | color = colors as u16 - 1; 184 | } 185 | current_byte += color << ((8 - bits_per_pixel as u16) - current_bit); 186 | current_bit += bits_per_pixel as u16; 187 | if current_bit >= 8 { 188 | packed.push(current_byte as u8 & 0xFF); 189 | current_byte = 0; 190 | current_bit = 0; 191 | } 192 | } 193 | } 194 | if current_bit > 0 { 195 | packed.push(current_byte as u8 & 0xFF); 196 | } 197 | (packed, bits_per_pixel) 198 | } 199 | 200 | fn generate_nbgl_glyph(frame: &GrayImage) -> (Vec, u8) { 201 | let (packed, bpp) = image_to_packed_buffer(&frame); 202 | 203 | let mut compressed_image: Vec = Vec::new(); 204 | let mut full_uncompressed_size = packed.len(); 205 | let mut i = 0; 206 | 207 | while full_uncompressed_size > 0 { 208 | let chunk_size = std::cmp::min(2048, full_uncompressed_size); 209 | let tmp = &packed[i..i + chunk_size]; 210 | 211 | let mut encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default()); 212 | encoder.write_all(tmp).unwrap(); 213 | let compressed_buffer = encoder.finish().unwrap(); 214 | 215 | let compressed_len = compressed_buffer.len(); 216 | let len_bytes: [u8; 2] = [ 217 | (compressed_len & 0xFF) as u8, 218 | ((compressed_len >> 8) & 0xFF) as u8, 219 | ]; 220 | 221 | compressed_image.extend_from_slice(&len_bytes); 222 | compressed_image.extend_from_slice(&compressed_buffer); 223 | 224 | full_uncompressed_size -= chunk_size; 225 | i += chunk_size; 226 | } 227 | 228 | let bpp_format: u8 = match bpp { 229 | 1 => BppFormat::Bpp1 as u8, 230 | 2 => BppFormat::Bpp2 as u8, 231 | 4 => BppFormat::Bpp4 as u8, 232 | _ => panic!("Invalid bpp"), 233 | }; 234 | 235 | let len = compressed_image.len(); 236 | let metadata: [u8; 8] = [ 237 | frame.width() as u8, 238 | (frame.width() >> 8) as u8, 239 | frame.height() as u8, 240 | (frame.height() >> 8) as u8, 241 | bpp_format << 4 | 1, // 1 is gzip compression type. We only support gzip. 242 | len as u8, 243 | (len >> 8) as u8, 244 | (len >> 16) as u8, 245 | ]; 246 | 247 | let mut result: Vec = Vec::new(); 248 | result.extend_from_slice(&metadata); 249 | result.extend_from_slice(&compressed_image); 250 | 251 | (result, bpp) 252 | } 253 | -------------------------------------------------------------------------------- /ledger_device_sdk/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.nanox] 2 | runner = "speculos -m nanox --display=headless" 3 | 4 | [target.nanosplus] 5 | runner = "speculos -m nanosp --display=headless" 6 | 7 | [target.stax] 8 | runner = "speculos --model stax --display=headless" 9 | 10 | [target.flex] 11 | runner = "speculos --model flex --display=headless" -------------------------------------------------------------------------------- /ledger_device_sdk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ledger_device_sdk" 3 | version = "1.22.8" 4 | authors = ["yhql", "yogh333", "agrojean-ledger", "kingofpayne"] 5 | edition = "2021" 6 | license.workspace = true 7 | repository.workspace = true 8 | description = "Ledger device Rust SDK" 9 | 10 | [dev-dependencies] 11 | # enable the 'speculos' feature when testing 12 | # https://github.com/rust-lang/cargo/issues/2911#issuecomment-749580481 13 | ledger_device_sdk = { path = ".", features = ["speculos"] } 14 | 15 | testmacro = { path = "../testmacro", version = "0.1.0"} 16 | 17 | [dependencies] 18 | include_gif = {path = "../include_gif", version = "1.2.0"} 19 | num-traits = { version = "0.2.14", default-features = false } 20 | rand_core = { version = "0.6.3", default-features = false } 21 | zeroize = { version = "1.6.0", default-features = false } 22 | numtoa = "0.2.4" 23 | const-zero = "0.1.1" 24 | ledger_secure_sdk_sys = { path = "../ledger_secure_sdk_sys", version = "1.8.2" } 25 | 26 | [features] 27 | debug = [] 28 | speculos = [] 29 | heap = [ "ledger_secure_sdk_sys/heap" ] 30 | nano_nbgl = [ "ledger_secure_sdk_sys/nano_nbgl" ] 31 | 32 | default = [ "heap" ] 33 | 34 | [lints.rust.unexpected_cfgs] 35 | level = "warn" 36 | check-cfg = ['cfg(target_os, values("stax", "flex", "nanox", "nanosplus"))'] 37 | -------------------------------------------------------------------------------- /ledger_device_sdk/README.md: -------------------------------------------------------------------------------- 1 | # Ledger device SDK for Rust Applications 2 | ![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FLedgerHQ%2Fledger-device-rust-sdk%2Frefs%2Fheads%2Fmaster%2Fledger_device_sdk%2FCargo.toml&query=%24.package.version&label=version) 3 | 4 | Crate that allows developing Ledger device apps in Rust with a default configuration. 5 | 6 | Contains: 7 | 8 | - some safe wrappers over common syscalls 9 | - IO abstractions 10 | - signature abstractions 11 | - UI libraries (the `ui` module for Nano (S/SP/X) apps, `nbgl` module for Stax and Flex apps) 12 | 13 | ## Supported devices 14 | 15 | | Nano X | Nano S Plus | Stax | Flex | 16 | | ------------------ | ------------------ | ------------------ | ------------------ | 17 | | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | 18 | 19 | ## Usage 20 | 21 | Building requires adding `rust-src` to your Rust installation, and both Clang and arm-none-eabi-gcc. 22 | On Ubuntu, `gcc-multilib` might also be required. 23 | 24 | Using rustc nightly builds is mandatory as some unstable features are required. 25 | 26 | - `rustup default nightly-2024-12-01` 27 | - `rustup component add rust-src` 28 | - install [Clang](http://releases.llvm.org/download.html). 29 | - install an [ARM gcc toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads) 30 | 31 | If you wish to install the ARM gcc toolchain using your distribution's packages, these commands should work: 32 | 33 | ```bash 34 | # On Debian and Ubuntu 35 | sudo apt install clang gcc-arm-none-eabi gcc-multilib 36 | 37 | # On Fedora or Red Hat Entreprise Linux 38 | sudo dnf install clang arm-none-eabi-gcc arm-none-eabi-newlib 39 | 40 | # On ArchLinux 41 | sudo pacman -S clang arm-none-eabi-gcc arm-none-eabi-newlib 42 | ``` 43 | 44 | This SDK provides [custom target](https://doc.rust-lang.org/rustc/targets/custom.html) files. One for each supported device. 45 | 46 | ### Building for Nano X 47 | 48 | ``` 49 | cargo build --release --target=nanox 50 | ``` 51 | 52 | ### Building for Nano S+ 53 | 54 | ``` 55 | cargo build --release --target=nanosplus 56 | ``` 57 | 58 | ### Building for Stax 59 | 60 | ``` 61 | cargo build --release --target=stax 62 | ``` 63 | 64 | ### Building for Flex 65 | 66 | ``` 67 | cargo build --release --target=flex 68 | ``` 69 | 70 | ## Contributing 71 | 72 | You can submit an issue or even a pull request if you wish to contribute. 73 | 74 | Make sure you've followed the installation steps above. In order for your PR to be accepted, it will have to pass the CI, which performs the following checks: 75 | 76 | - Check if the code builds on nightly 77 | - Check that `clippy` does not emit any warnings 78 | - check that your code follows `rustfmt`'s format (using `cargo fmt`) 79 | -------------------------------------------------------------------------------- /ledger_device_sdk/examples/crab_64x64.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/examples/crab_64x64.gif -------------------------------------------------------------------------------- /ledger_device_sdk/examples/gadgets.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use core::panic::PanicInfo; 5 | #[panic_handler] 6 | fn panic(_: &PanicInfo) -> ! { 7 | ledger_device_sdk::exit_app(1); 8 | } 9 | 10 | use ledger_device_sdk::buttons::*; 11 | use ledger_device_sdk::ui::gadgets; 12 | use ledger_device_sdk::ui::layout::{Layout, Location, StringPlace}; 13 | 14 | fn wait_any() { 15 | let mut buttons = ButtonsState::new(); 16 | loop { 17 | match gadgets::get_event(&mut buttons) { 18 | Some(ButtonEvent::LeftButtonRelease) 19 | | Some(ButtonEvent::RightButtonRelease) 20 | | Some(ButtonEvent::BothButtonsRelease) => return, 21 | _ => (), 22 | } 23 | } 24 | } 25 | 26 | #[no_mangle] 27 | extern "C" fn sample_main() { 28 | gadgets::clear_screen(); 29 | gadgets::popup("Hello"); 30 | 31 | gadgets::clear_screen(); 32 | 33 | ["First", "Second"].place(Location::Middle, Layout::Centered, false); 34 | wait_any(); 35 | gadgets::clear_screen(); 36 | 37 | ["First Line", "Second Line", "Third Line"].place(Location::Middle, Layout::Centered, false); 38 | wait_any(); 39 | gadgets::clear_screen(); 40 | 41 | ["First Line", "Second Line", "Third Line", "Fourth"].place( 42 | Location::Middle, 43 | Layout::Centered, 44 | false, 45 | ); 46 | wait_any(); 47 | gadgets::clear_screen(); 48 | 49 | ["Monero &", "Ethereum &", "Zcash &", "NanoPass"].place( 50 | Location::Top, 51 | Layout::LeftAligned, 52 | false, 53 | ); 54 | wait_any(); 55 | gadgets::clear_screen(); 56 | 57 | ["Monero &", "Ethereum &", "Zcash &", "NanoPass"].place( 58 | Location::Top, 59 | Layout::RightAligned, 60 | false, 61 | ); 62 | wait_any(); 63 | 64 | let scrolled_message = "Arbitrary long text goes here, with numbers -1234567890"; 65 | gadgets::MessageScroller::new(scrolled_message).event_loop(); 66 | 67 | loop { 68 | match gadgets::Menu::new(&[&"Top0", &"Top1", &"Top2", &"Top3", &"Next"]).show() { 69 | 0 => loop { 70 | match gadgets::Menu::new(&[&"Top0_sub0", &"Back"]).show() { 71 | 0 => gadgets::popup("Top0_sub0_0"), 72 | _ => break, 73 | } 74 | }, 75 | 1 => loop { 76 | match gadgets::Menu::new(&[&"Top1_sub0", &"Top1_sub1", &"Back"]).show() { 77 | 0 => gadgets::popup("Top1_sub0_0"), 78 | 1 => gadgets::popup("Top1_sub1_0"), 79 | _ => break, 80 | } 81 | }, 82 | 2 => break, 83 | 3 => break, 84 | 4 => break, 85 | _ => (), 86 | } 87 | } 88 | 89 | let _ = gadgets::Validator::new("Confirm?").ask(); 90 | let _ = gadgets::MessageValidator::new( 91 | &[&"Message Review"], 92 | &[&"Confirm", &"message?"], 93 | &[&"Cancel"], 94 | ) 95 | .ask(); 96 | 97 | gadgets::clear_screen(); 98 | 99 | use ledger_device_sdk::ui::bagls::RectFull as Rect; 100 | use ledger_device_sdk::ui::layout::Draw; 101 | 102 | Rect::new() 103 | .width(10) 104 | .height(10) 105 | .pos(16, 16) 106 | .instant_display(); 107 | Rect::new() 108 | .width(10) 109 | .height(10) 110 | .pos(32, 16) 111 | .instant_display(); 112 | Rect::new() 113 | .width(10) 114 | .height(10) 115 | .pos(48, 16) 116 | .instant_display(); 117 | wait_any(); 118 | 119 | gadgets::clear_screen(); 120 | 121 | let checkmark = ledger_device_sdk::ui::bagls::CHECKMARK_ICON 122 | .set_x(0) 123 | .set_y(4); 124 | checkmark.instant_display(); 125 | ledger_device_sdk::ui::bagls::CROSS_ICON 126 | .set_x(20) 127 | .set_y(4) 128 | .instant_display(); 129 | ledger_device_sdk::ui::bagls::COGGLE_ICON 130 | .set_x(40) 131 | .set_y(4) 132 | .instant_display(); 133 | wait_any(); 134 | checkmark.instant_erase(); 135 | wait_any(); 136 | 137 | ledger_device_sdk::exit_app(0); 138 | } 139 | -------------------------------------------------------------------------------- /ledger_device_sdk/examples/nbgl_address.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | // Force boot section to be embedded in 5 | use ledger_device_sdk as _; 6 | 7 | use include_gif::include_gif; 8 | use ledger_device_sdk::io::*; 9 | use ledger_device_sdk::nbgl::{ 10 | init_comm, NbglAddressReview, NbglGlyph, NbglReviewStatus, StatusType, 11 | }; 12 | use ledger_secure_sdk_sys::*; 13 | 14 | #[panic_handler] 15 | fn panic(_: &core::panic::PanicInfo) -> ! { 16 | exit_app(1); 17 | } 18 | 19 | #[no_mangle] 20 | extern "C" fn sample_main() { 21 | unsafe { 22 | nbgl_refreshReset(); 23 | } 24 | 25 | let mut comm = Comm::new(); 26 | // Initialize reference to Comm instance for NBGL 27 | // API calls. 28 | init_comm(&mut comm); 29 | 30 | let addr_hex = "0x1234567890ABCDEF1234567890ABCDEF12345678"; 31 | 32 | // Load glyph from 64x64 4bpp gif file with include_gif macro. Creates an NBGL compatible glyph. 33 | const FERRIS: NbglGlyph = 34 | NbglGlyph::from_include(include_gif!("examples/crab_64x64.gif", NBGL)); 35 | // Display the address confirmation screen. 36 | let success = NbglAddressReview::new() 37 | .glyph(&FERRIS) 38 | .verify_str("Verify Address") 39 | .show(addr_hex); 40 | NbglReviewStatus::new() 41 | .status_type(StatusType::Address) 42 | .show(success); 43 | exit_app(0); 44 | } 45 | -------------------------------------------------------------------------------- /ledger_device_sdk/examples/nbgl_choice.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | // Force boot section to be embedded in 5 | use ledger_device_sdk as _; 6 | 7 | use include_gif::include_gif; 8 | use ledger_device_sdk::io::*; 9 | use ledger_device_sdk::nbgl::{init_comm, NbglChoice, NbglGlyph, NbglStatus}; 10 | use ledger_secure_sdk_sys::*; 11 | 12 | #[panic_handler] 13 | fn panic(_: &core::panic::PanicInfo) -> ! { 14 | exit_app(1); 15 | } 16 | 17 | #[no_mangle] 18 | extern "C" fn sample_main() { 19 | unsafe { 20 | nbgl_refreshReset(); 21 | } 22 | 23 | let mut comm = Comm::new(); 24 | // Initialize reference to Comm instance for NBGL 25 | // API calls. 26 | init_comm(&mut comm); 27 | 28 | // Load glyph from 64x64 4bpp gif file with include_gif macro. Creates an NBGL compatible glyph. 29 | const WARNING: NbglGlyph = 30 | NbglGlyph::from_include(include_gif!("icons/Warning_64px.gif", NBGL)); 31 | 32 | let back_to_safety = NbglChoice::new().glyph(&WARNING).show( 33 | "Security risk detected", 34 | "It may not be safe to sign this transaction. To continue, you'll need to review the risk.", 35 | "Back to safety", 36 | "Review risk", 37 | ); 38 | 39 | if back_to_safety { 40 | NbglStatus::new().text("Transaction rejected").show(false); 41 | } else { 42 | let confirmed = NbglChoice::new() 43 | .show( 44 | "The transaction cannot be trusted", 45 | "Your Ledger cannot decode this transaction. If you sign it, you could be authorizing malicious actions that can drain your wallet.\n\nLearn more: ledger.com/e8", 46 | "I accept the risk", 47 | "Reject transaction" 48 | ); 49 | 50 | NbglStatus::new() 51 | .text(if confirmed { 52 | "Transaction confirmed" 53 | } else { 54 | "Transaction rejected" 55 | }) 56 | .show(confirmed); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ledger_device_sdk/examples/nbgl_generic_review.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | // Force boot section to be embedded in 5 | use ledger_device_sdk as _; 6 | 7 | use include_gif::include_gif; 8 | use ledger_device_sdk::io::*; 9 | use ledger_device_sdk::nbgl::{ 10 | init_comm, CenteredInfo, CenteredInfoStyle, Field, InfoButton, InfoLongPress, InfosList, 11 | NbglChoice, NbglGenericReview, NbglGlyph, NbglPageContent, NbglStatus, TagValueConfirm, 12 | TagValueList, TuneIndex, 13 | }; 14 | use ledger_secure_sdk_sys::*; 15 | 16 | use core::ops::Not; 17 | 18 | #[panic_handler] 19 | fn panic(_: &core::panic::PanicInfo) -> ! { 20 | exit_app(1); 21 | } 22 | 23 | #[no_mangle] 24 | extern "C" fn sample_main() { 25 | unsafe { 26 | nbgl_refreshReset(); 27 | } 28 | 29 | let mut comm = Comm::new(); 30 | // Initialize reference to Comm instance for NBGL 31 | // API calls. 32 | init_comm(&mut comm); 33 | 34 | // Load glyph from 64x64 4bpp gif file with include_gif macro. Creates an NBGL compatible glyph. 35 | const FERRIS: NbglGlyph = 36 | NbglGlyph::from_include(include_gif!("examples/crab_64x64.gif", NBGL)); 37 | 38 | let centered_info = CenteredInfo::new( 39 | "Sample centered info", 40 | "Generic text", 41 | "More generic text", 42 | Some(&FERRIS), 43 | true, 44 | CenteredInfoStyle::LargeCaseBoldInfo, 45 | 0, 46 | ); 47 | 48 | let info_button = InfoButton::new( 49 | "Validate info : abc", 50 | Some(&FERRIS), 51 | "Approve", 52 | TuneIndex::Success, 53 | ); 54 | 55 | let info_long_press = InfoLongPress::new( 56 | "Validate to send token", 57 | Some(&FERRIS), 58 | "Hold to validate", 59 | TuneIndex::Success, 60 | ); 61 | 62 | let my_example_fields = [ 63 | Field { 64 | name: "Field 1", 65 | value: "0x1234567890abcdef", 66 | }, 67 | Field { 68 | name: "Field 2", 69 | value: "0xdeafbeefdeadbeef", 70 | }, 71 | ]; 72 | 73 | let tag_values_list = TagValueList::new(&my_example_fields, 2, false, false); 74 | 75 | let tag_value_confirm = TagValueConfirm::new( 76 | &tag_values_list, 77 | TuneIndex::Success, 78 | "Confirm hash", 79 | "Reject hash", 80 | ); 81 | 82 | let infos_list = InfosList::new(&my_example_fields); 83 | 84 | let mut review: NbglGenericReview = NbglGenericReview::new() 85 | .add_content(NbglPageContent::CenteredInfo(centered_info)) 86 | .add_content(NbglPageContent::InfoButton(info_button)) 87 | .add_content(NbglPageContent::InfoLongPress(info_long_press)) 88 | .add_content(NbglPageContent::TagValueList(tag_values_list)) 89 | .add_content(NbglPageContent::TagValueConfirm(tag_value_confirm)) 90 | .add_content(NbglPageContent::InfosList(infos_list)); 91 | 92 | const IMPORTANT: NbglGlyph = 93 | NbglGlyph::from_include(include_gif!("icons/Important_Circle_64px.png", NBGL)); 94 | 95 | let mut show_tx = true; 96 | let mut status_text = "Example rejected"; 97 | while show_tx { 98 | let confirm = review.show("Reject Example"); 99 | if confirm { 100 | status_text = "Example confirmed"; 101 | show_tx = false; 102 | } else { 103 | show_tx = NbglChoice::new() 104 | .glyph(&IMPORTANT) 105 | .show( 106 | "Reject transaction?", 107 | "", 108 | "Yes, reject", 109 | "Go back to transaction", 110 | ) 111 | // not() is used to invert the boolean value returned 112 | // by the choice (since we want to return to showing the 113 | // transaction if the user selects "Go back to transaction" 114 | // which returns false). 115 | .not(); 116 | } 117 | } 118 | NbglStatus::new() 119 | .text(status_text) 120 | .show(status_text == "Example confirmed"); 121 | } 122 | -------------------------------------------------------------------------------- /ledger_device_sdk/examples/nbgl_home.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | // Force boot section to be embedded in 5 | use ledger_device_sdk as _; 6 | 7 | use include_gif::include_gif; 8 | use ledger_device_sdk::io::*; 9 | use ledger_device_sdk::nbgl::{init_comm, NbglGlyph, NbglHomeAndSettings}; 10 | use ledger_device_sdk::nvm::*; 11 | use ledger_device_sdk::NVMData; 12 | use ledger_secure_sdk_sys::*; 13 | 14 | #[panic_handler] 15 | fn panic(_: &core::panic::PanicInfo) -> ! { 16 | exit_app(1); 17 | } 18 | 19 | pub enum Instruction { 20 | GetVersion, 21 | GetAppName, 22 | } 23 | 24 | impl TryFrom for Instruction { 25 | type Error = StatusWords; 26 | 27 | fn try_from(value: ApduHeader) -> Result { 28 | match value.ins { 29 | 3 => Ok(Instruction::GetVersion), 30 | 4 => Ok(Instruction::GetAppName), 31 | _ => Err(StatusWords::NothingReceived), 32 | } 33 | } 34 | } 35 | 36 | #[no_mangle] 37 | extern "C" fn sample_main() { 38 | unsafe { 39 | nbgl_refreshReset(); 40 | } 41 | 42 | const SETTINGS_SIZE: usize = 10; 43 | #[link_section = ".nvm_data"] 44 | static mut DATA: NVMData> = 45 | NVMData::new(AtomicStorage::new(&[0u8; 10])); 46 | 47 | let mut comm = Comm::new(); 48 | // Initialize reference to Comm instance for NBGL 49 | // API calls. 50 | init_comm(&mut comm); 51 | 52 | // Load glyph from 64x64 4bpp gif file with include_gif macro. Creates an NBGL compatible glyph. 53 | const FERRIS: NbglGlyph = 54 | NbglGlyph::from_include(include_gif!("examples/crab_64x64.gif", NBGL)); 55 | 56 | let settings_strings = [["Switch title", "Switch subtitle"]]; 57 | // Display the home screen. 58 | NbglHomeAndSettings::new() 59 | .glyph(&FERRIS) 60 | .settings(unsafe { DATA.get_mut() }, &settings_strings) 61 | .infos( 62 | "Example App", 63 | env!("CARGO_PKG_VERSION"), 64 | env!("CARGO_PKG_AUTHORS"), 65 | ) 66 | .show::(); 67 | } 68 | -------------------------------------------------------------------------------- /ledger_device_sdk/examples/nbgl_review.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | // Force boot section to be embedded in 5 | use ledger_device_sdk as _; 6 | 7 | use include_gif::include_gif; 8 | use ledger_device_sdk::io::*; 9 | use ledger_device_sdk::nbgl::{ 10 | init_comm, Field, NbglGlyph, NbglReview, NbglReviewStatus, StatusType, 11 | }; 12 | use ledger_secure_sdk_sys::*; 13 | 14 | #[panic_handler] 15 | fn panic(_: &core::panic::PanicInfo) -> ! { 16 | exit_app(1); 17 | } 18 | 19 | #[no_mangle] 20 | extern "C" fn sample_main() { 21 | unsafe { 22 | nbgl_refreshReset(); 23 | } 24 | 25 | let mut comm = Comm::new(); 26 | // Initialize reference to Comm instance for NBGL 27 | // API calls. 28 | init_comm(&mut comm); 29 | 30 | let my_fields = [ 31 | Field { 32 | name: "Amount", 33 | value: "111 CRAB", 34 | }, 35 | Field { 36 | name: "Destination", 37 | value: "0x1234567890ABCDEF1234567890ABCDEF12345678", 38 | }, 39 | Field { 40 | name: "Memo", 41 | value: "This is a test transaction.", 42 | }, 43 | ]; 44 | 45 | // Load glyph from 64x64 4bpp gif file with include_gif macro. Creates an NBGL compatible glyph. 46 | const FERRIS: NbglGlyph = 47 | NbglGlyph::from_include(include_gif!("examples/crab_64x64.gif", NBGL)); 48 | // Create NBGL review. Maximum number of fields and string buffer length can be customised 49 | // with constant generic parameters of NbglReview. Default values are 32 and 1024 respectively. 50 | let success = NbglReview::new() 51 | .titles( 52 | "Please review transaction", 53 | "To send CRAB", 54 | "Sign transaction\nto send CRAB", 55 | ) 56 | .glyph(&FERRIS) 57 | .blind() 58 | .show(&my_fields); 59 | NbglReviewStatus::new().show(success); 60 | } 61 | -------------------------------------------------------------------------------- /ledger_device_sdk/examples/nbgl_spinner.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | // Force boot section to be embedded in 5 | use ledger_device_sdk as _; 6 | 7 | use include_gif::include_gif; 8 | use ledger_device_sdk::io::*; 9 | use ledger_device_sdk::nbgl::{ 10 | init_comm, Field, NbglGlyph, NbglReview, NbglReviewStatus, NbglSpinner, StatusType, 11 | }; 12 | use ledger_device_sdk::testing::debug_print; 13 | use ledger_secure_sdk_sys::*; 14 | 15 | #[panic_handler] 16 | fn panic(_: &core::panic::PanicInfo) -> ! { 17 | exit_app(1); 18 | } 19 | 20 | // static spin_end: bool = false; 21 | 22 | #[no_mangle] 23 | extern "C" fn sample_main() { 24 | unsafe { 25 | nbgl_refreshReset(); 26 | } 27 | 28 | let mut comm = Comm::new(); 29 | // Initialize reference to Comm instance for NBGL 30 | // API calls. 31 | init_comm(&mut comm); 32 | 33 | let my_field = [Field { 34 | name: "Amount", 35 | value: "111 CRAB", 36 | }]; 37 | 38 | // Load glyph from 64x64 4bpp gif file with include_gif macro. Creates an NBGL compatible glyph. 39 | const FERRIS: NbglGlyph = 40 | NbglGlyph::from_include(include_gif!("examples/crab_64x64.gif", NBGL)); 41 | // Create NBGL review. Maximum number of fields and string buffer length can be customised 42 | // with constant generic parameters of NbglReview. Default values are 32 and 1024 respectively. 43 | let success = NbglReview::new() 44 | .titles( 45 | "Please review transaction", 46 | "To send CRAB", 47 | "Sign transaction\nto send CRAB", 48 | ) 49 | .glyph(&FERRIS) 50 | .show(&my_field); 51 | 52 | NbglSpinner::new().show("Please wait..."); 53 | 54 | // Simulate an idle state of the app where it just 55 | // waits for some event to happen (such as APDU reception), going through 56 | // the event loop to process TickerEvents so that the spinner can be animated 57 | // every 800ms. 58 | let mut loop_count = 50; 59 | while loop_count > 0 { 60 | comm.next_event::(); 61 | loop_count -= 1; 62 | } 63 | NbglReviewStatus::new().show(success); 64 | } 65 | -------------------------------------------------------------------------------- /ledger_device_sdk/examples/nbgl_streaming_review.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | // Force boot section to be embedded in 5 | use ledger_device_sdk as _; 6 | 7 | use include_gif::include_gif; 8 | use ledger_device_sdk::io::*; 9 | use ledger_device_sdk::nbgl::{ 10 | init_comm, Field, NbglGlyph, NbglReviewStatus, NbglStreamingReview, StatusType, TransactionType, 11 | }; 12 | use ledger_secure_sdk_sys::*; 13 | 14 | #[panic_handler] 15 | fn panic(_: &core::panic::PanicInfo) -> ! { 16 | exit_app(1); 17 | } 18 | 19 | #[no_mangle] 20 | extern "C" fn sample_main() { 21 | unsafe { 22 | nbgl_refreshReset(); 23 | } 24 | 25 | let mut comm = Comm::new(); 26 | // Initialize reference to Comm instance for NBGL 27 | // API calls. 28 | init_comm(&mut comm); 29 | 30 | // Load glyph from 64x64 4bpp gif file with include_gif macro. Creates an NBGL compatible glyph. 31 | const FERRIS: NbglGlyph = 32 | NbglGlyph::from_include(include_gif!("examples/crab_64x64.gif", NBGL)); 33 | 34 | let mut review: NbglStreamingReview = NbglStreamingReview::new() 35 | .glyph(&FERRIS) 36 | .tx_type(TransactionType::Message); 37 | 38 | if !review.start("Streaming example", "Example Subtitle") { 39 | NbglReviewStatus::new().show(false); 40 | return; 41 | } 42 | 43 | let fields = [ 44 | Field { 45 | name: "Name1", 46 | value: "Value1", 47 | }, 48 | Field { 49 | name: "Name2", 50 | value: "Value2", 51 | }, 52 | Field { 53 | name: "Name3", 54 | value: "Value3", 55 | }, 56 | Field { 57 | name: "Name4", 58 | value: "Value4", 59 | }, 60 | Field { 61 | name: "Name5", 62 | value: "Value5", 63 | }, 64 | ]; 65 | 66 | for i in 0..fields.len() { 67 | if !review.continue_review(&fields[i..i + 1]) { 68 | NbglReviewStatus::new().show(false); 69 | return; 70 | } 71 | } 72 | 73 | let success = review.finish("Sign to send token\n"); 74 | NbglReviewStatus::new().show(success); 75 | } 76 | -------------------------------------------------------------------------------- /ledger_device_sdk/examples/review.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use core::panic::PanicInfo; 5 | #[panic_handler] 6 | fn panic(_: &PanicInfo) -> ! { 7 | ledger_device_sdk::exit_app(1); 8 | } 9 | 10 | use ledger_device_sdk::ui::bitmaps::*; 11 | use ledger_device_sdk::ui::gadgets::{Field, MultiFieldReview}; 12 | 13 | #[no_mangle] 14 | extern "C" fn sample_main() { 15 | let fields = [ 16 | Field { 17 | name: "Amount", 18 | value: "CRAB 666", 19 | }, 20 | Field { 21 | name: "Destination", 22 | value: "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", 23 | }, 24 | Field { 25 | name: "Memo", 26 | value: "This is a very long memo. It will force the app client to send the serialized transaction to be sent in chunk. As the maximum chunk size is 255 bytes we will make this memo greater than 255 characters. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam.", 27 | }, 28 | ]; 29 | let review = MultiFieldReview::new( 30 | &fields, 31 | &["Review ", "Transaction"], 32 | Some(&EYE), 33 | "Approve", 34 | Some(&VALIDATE_14), 35 | "Reject", 36 | Some(&CROSSMARK), 37 | ); 38 | review.show(); 39 | ledger_device_sdk::exit_app(0); 40 | } 41 | -------------------------------------------------------------------------------- /ledger_device_sdk/icons/Important_Circle_64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/Important_Circle_64px.png -------------------------------------------------------------------------------- /ledger_device_sdk/icons/Warning_64px.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/Warning_64px.gif -------------------------------------------------------------------------------- /ledger_device_sdk/icons/badge_back.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/badge_back.gif -------------------------------------------------------------------------------- /ledger_device_sdk/icons/badge_check.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/badge_check.gif -------------------------------------------------------------------------------- /ledger_device_sdk/icons/icon_back.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/icon_back.gif -------------------------------------------------------------------------------- /ledger_device_sdk/icons/icon_back_x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/icon_back_x.gif -------------------------------------------------------------------------------- /ledger_device_sdk/icons/icon_certificate.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/icon_certificate.gif -------------------------------------------------------------------------------- /ledger_device_sdk/icons/icon_coggle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/icon_coggle.gif -------------------------------------------------------------------------------- /ledger_device_sdk/icons/icon_cross_badge.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/icon_cross_badge.gif -------------------------------------------------------------------------------- /ledger_device_sdk/icons/icon_crossmark.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/icon_crossmark.gif -------------------------------------------------------------------------------- /ledger_device_sdk/icons/icon_dashboard.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/icon_dashboard.gif -------------------------------------------------------------------------------- /ledger_device_sdk/icons/icon_dashboard_x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/icon_dashboard_x.gif -------------------------------------------------------------------------------- /ledger_device_sdk/icons/icon_down.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/icon_down.gif -------------------------------------------------------------------------------- /ledger_device_sdk/icons/icon_eye.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/icon_eye.gif -------------------------------------------------------------------------------- /ledger_device_sdk/icons/icon_left.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/icon_left.gif -------------------------------------------------------------------------------- /ledger_device_sdk/icons/icon_processing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/icon_processing.gif -------------------------------------------------------------------------------- /ledger_device_sdk/icons/icon_right.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/icon_right.gif -------------------------------------------------------------------------------- /ledger_device_sdk/icons/icon_up.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/icon_up.gif -------------------------------------------------------------------------------- /ledger_device_sdk/icons/icon_validate_14.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/icon_validate_14.gif -------------------------------------------------------------------------------- /ledger_device_sdk/icons/icon_warning.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/ledger-device-rust-sdk/2679ffaa2f2c69d0d215ed490c5df5c8a935b879/ledger_device_sdk/icons/icon_warning.gif -------------------------------------------------------------------------------- /ledger_device_sdk/src/ble.rs: -------------------------------------------------------------------------------- 1 | use ledger_secure_sdk_sys::{LEDGER_BLE_receive, LEDGER_BLE_send, LEDGER_BLE_set_recv_buffer}; 2 | 3 | pub fn receive(apdu_buffer: &mut [u8], spi_buffer: &[u8]) { 4 | unsafe { 5 | LEDGER_BLE_set_recv_buffer(apdu_buffer.as_mut_ptr(), apdu_buffer.len() as u16); 6 | LEDGER_BLE_receive(spi_buffer.as_ptr()); 7 | } 8 | } 9 | 10 | pub fn send(buffer: &[u8]) { 11 | unsafe { 12 | LEDGER_BLE_send(buffer.as_ptr(), buffer.len() as u16); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/ecc/stark.rs: -------------------------------------------------------------------------------- 1 | use crate::ecc::{CurvesId, Secret}; 2 | use ledger_secure_sdk_sys::*; 3 | 4 | // C_cx_secp256k1_n - (C_cx_secp256k1_n % C_cx_Stark256_n) 5 | const STARK_DERIVE_BIAS: [u8; 32] = [ 6 | 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 7 | 0x38, 0xa1, 0x3b, 0x4b, 0x92, 0x0e, 0x94, 0x11, 0xae, 0x6d, 0xa5, 0xf4, 0x0b, 0x03, 0x58, 0xb1, 8 | ]; 9 | 10 | // n: 0x0800000000000010ffffffffffffffffb781126dcae7b2321e66a241adc64d2f 11 | const C_CX_STARK256_N: [u8; 32] = [ 12 | 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 13 | 0xb7, 0x81, 0x12, 0x6d, 0xca, 0xe7, 0xb2, 0x32, 0x1e, 0x66, 0xa2, 0x41, 0xad, 0xc6, 0x4d, 0x2f, 14 | ]; 15 | 16 | /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2645.md 17 | pub fn eip2645_derive(path: &[u32], key: &mut [u8]) { 18 | let mut x_key = Secret::<64>::new(); 19 | // Ignoring 'Result' here because known to be valid 20 | let _ = super::bip32_derive(CurvesId::Secp256k1, path, x_key.as_mut(), None); 21 | 22 | let mut index = 0; 23 | let mut cmp = 0; 24 | 25 | loop { 26 | x_key.as_mut()[32] = index; 27 | unsafe { cx_hash_sha256(x_key.as_ref().as_ptr(), 33, key.as_mut_ptr(), 32) }; 28 | unsafe { cx_math_cmp_no_throw(key.as_ptr(), STARK_DERIVE_BIAS.as_ptr(), 32, &mut cmp) }; 29 | if cmp < 0 { 30 | unsafe { cx_math_modm_no_throw(key.as_mut_ptr(), 32, C_CX_STARK256_N.as_ptr(), 32) }; 31 | break; 32 | } 33 | index += 1; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/hash.rs: -------------------------------------------------------------------------------- 1 | use ledger_secure_sdk_sys::{ 2 | cx_hash_final, cx_hash_get_size, cx_hash_no_throw, cx_hash_t, cx_hash_update, 3 | CX_INVALID_PARAMETER, CX_LAST, CX_OK, 4 | }; 5 | 6 | pub mod blake2; 7 | pub mod ripemd; 8 | pub mod sha2; 9 | pub mod sha3; 10 | 11 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 12 | pub enum HashError { 13 | InvalidParameter, 14 | InvalidOutputLength, 15 | InternalError, 16 | } 17 | 18 | impl From for HashError { 19 | fn from(x: u32) -> HashError { 20 | match x { 21 | CX_INVALID_PARAMETER => HashError::InvalidParameter, 22 | _ => HashError::InternalError, 23 | } 24 | } 25 | } 26 | 27 | impl From for u32 { 28 | fn from(e: HashError) -> u32 { 29 | e as u32 30 | } 31 | } 32 | 33 | pub trait HashInit: Sized { 34 | fn as_ctx_mut(&mut self) -> &mut cx_hash_t; 35 | fn as_ctx(&self) -> &cx_hash_t; 36 | fn new() -> Self; 37 | fn reset(&mut self); 38 | fn get_size(&mut self) -> usize { 39 | unsafe { cx_hash_get_size(self.as_ctx()) } 40 | } 41 | fn hash(&mut self, input: &[u8], output: &mut [u8]) -> Result<(), HashError> { 42 | let output_size = self.get_size(); 43 | if output_size > output.len() { 44 | return Err(HashError::InvalidOutputLength); 45 | } 46 | 47 | let err = unsafe { 48 | cx_hash_no_throw( 49 | self.as_ctx_mut(), 50 | CX_LAST, 51 | input.as_ptr(), 52 | input.len(), 53 | output.as_mut_ptr(), 54 | output.len(), 55 | ) 56 | }; 57 | if err != CX_OK { 58 | Err(err.into()) 59 | } else { 60 | Ok(()) 61 | } 62 | } 63 | fn update(&mut self, input: &[u8]) -> Result<(), HashError> { 64 | let err = unsafe { cx_hash_update(self.as_ctx_mut(), input.as_ptr(), input.len()) }; 65 | if err != CX_OK { 66 | Err(err.into()) 67 | } else { 68 | Ok(()) 69 | } 70 | } 71 | fn finalize(&mut self, output: &mut [u8]) -> Result<(), HashError> { 72 | let output_size = self.get_size(); 73 | if output_size > output.len() { 74 | return Err(HashError::InvalidOutputLength); 75 | } 76 | 77 | let err = unsafe { cx_hash_final(self.as_ctx_mut(), output.as_mut_ptr()) }; 78 | if err != CX_OK { 79 | Err(err.into()) 80 | } else { 81 | Ok(()) 82 | } 83 | } 84 | } 85 | 86 | macro_rules! impl_hash { 87 | ($typename:ident, $ctxname:ident, $initfname:ident, $size:expr) => { 88 | #[derive(Default)] 89 | #[allow(non_camel_case_types)] 90 | pub struct $typename { 91 | ctx: $ctxname, 92 | } 93 | impl HashInit for $typename { 94 | fn as_ctx_mut(&mut self) -> &mut cx_hash_t { 95 | &mut self.ctx.header 96 | } 97 | 98 | fn as_ctx(&self) -> &cx_hash_t { 99 | &self.ctx.header 100 | } 101 | 102 | fn new() -> Self { 103 | let mut ctx: $typename = Default::default(); 104 | let _err = unsafe { $initfname(&mut ctx.ctx, $size) }; 105 | ctx 106 | } 107 | 108 | fn reset(&mut self) { 109 | let _err = unsafe { $initfname(&mut self.ctx, $size) }; 110 | } 111 | } 112 | }; 113 | 114 | ($typename:ident, $ctxname:ident, $initfname:ident) => { 115 | #[derive(Default)] 116 | #[allow(non_camel_case_types)] 117 | pub struct $typename { 118 | ctx: $ctxname, 119 | } 120 | impl HashInit for $typename { 121 | fn as_ctx_mut(&mut self) -> &mut cx_hash_t { 122 | &mut self.ctx.header 123 | } 124 | 125 | fn as_ctx(&self) -> &cx_hash_t { 126 | &self.ctx.header 127 | } 128 | 129 | fn new() -> Self { 130 | let mut ctx: $typename = Default::default(); 131 | let _err = unsafe { $initfname(&mut ctx.ctx) }; 132 | ctx 133 | } 134 | 135 | fn reset(&mut self) { 136 | let _err = unsafe { $initfname(&mut self.ctx) }; 137 | } 138 | } 139 | }; 140 | } 141 | pub(crate) use impl_hash; 142 | 143 | #[cfg(test)] 144 | mod tests { 145 | use crate::assert_eq_err as assert_eq; 146 | use crate::hash::sha2::Sha2_256; 147 | use crate::hash::sha3::*; 148 | use crate::hash::HashInit; 149 | use crate::testing::TestType; 150 | use testmacro::test_item as test; 151 | 152 | const TEST_HASH: &[u8; 29] = b"Not your keys, not your coins"; 153 | 154 | #[test] 155 | fn test_hash() { 156 | let mut keccak = Keccak256::new(); 157 | 158 | let mut output: [u8; 32] = [0u8; 32]; 159 | 160 | let ouput_size = keccak.get_size(); 161 | assert_eq!(ouput_size, 32); 162 | 163 | let _ = keccak.hash(TEST_HASH, &mut output); 164 | 165 | let expected = [ 166 | 0x1f, 0x20, 0x7c, 0xd9, 0xfd, 0x9f, 0x0b, 0x09, 0xb0, 0x04, 0x93, 0x6c, 0xa5, 0xe0, 167 | 0xd3, 0x1b, 0xa1, 0x6c, 0xd6, 0x14, 0x53, 0xaa, 0x28, 0x7e, 0x65, 0xaa, 0x88, 0x25, 168 | 0x3c, 0xdc, 0x1c, 0x94, 169 | ]; 170 | assert_eq!(&output, &expected); 171 | } 172 | 173 | #[test] 174 | fn test_sha2_update() { 175 | let mut hasher = Sha2_256::new(); 176 | 177 | let mut output: [u8; 32] = [0u8; 32]; 178 | 179 | let ouput_size = hasher.get_size(); 180 | assert_eq!(ouput_size, 32); 181 | 182 | let _ = hasher.update(TEST_HASH); 183 | 184 | let _ = hasher.finalize(&mut output); 185 | 186 | let expected = [ 187 | 0x52, 0x49, 0x2e, 0x81, 0x92, 0x16, 0xf3, 0x6b, 0x74, 0x7d, 0xd5, 0xda, 0x70, 0x3a, 188 | 0x26, 0x60, 0x14, 0x34, 0x60, 0x42, 0x42, 0xfa, 0xb2, 0x7e, 0x85, 0x51, 0xe7, 0x82, 189 | 0xa5, 0x11, 0x13, 0x40, 190 | ]; 191 | assert_eq!(&output, &expected); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/hash/blake2.rs: -------------------------------------------------------------------------------- 1 | use super::HashInit; 2 | use ledger_secure_sdk_sys::{cx_blake2b_init_no_throw, cx_blake2b_t, cx_hash_t}; 3 | 4 | use super::impl_hash; 5 | impl_hash!(Blake2b_256, cx_blake2b_t, cx_blake2b_init_no_throw, 256); 6 | impl_hash!(Blake2b_384, cx_blake2b_t, cx_blake2b_init_no_throw, 384); 7 | impl_hash!(Blake2b_512, cx_blake2b_t, cx_blake2b_init_no_throw, 512); 8 | 9 | #[cfg(test)] 10 | mod tests { 11 | use crate::assert_eq_err as assert_eq; 12 | use crate::hash::blake2::*; 13 | use crate::testing::TestType; 14 | use testmacro::test_item as test; 15 | 16 | const TEST_HASH: &[u8; 29] = b"Not your keys, not your coins"; 17 | 18 | #[test] 19 | fn test_hash_blake2b256() { 20 | let mut blake2 = Blake2b_256::new(); 21 | 22 | let mut output: [u8; 32] = [0u8; 32]; 23 | 24 | let ouput_size = blake2.get_size(); 25 | assert_eq!(ouput_size, 32); 26 | 27 | let _ = blake2.hash(TEST_HASH, &mut output); 28 | 29 | let expected = [ 30 | 0xcd, 0xa6, 0x49, 0x8e, 0x2f, 0x89, 0x71, 0xe8, 0x4e, 0xd5, 0x68, 0x2e, 0x3d, 0x47, 31 | 0x9c, 0xcc, 0x2c, 0xce, 0x7f, 0x37, 0xac, 0x92, 0x9c, 0xa0, 0xb0, 0x41, 0xb2, 0xdd, 32 | 0x06, 0xa9, 0xf3, 0xcb, 33 | ]; 34 | assert_eq!(&output, &expected); 35 | } 36 | 37 | #[test] 38 | fn test_hash_blake2b384() { 39 | let mut blake2 = Blake2b_384::new(); 40 | 41 | let mut output: [u8; 48] = [0u8; 48]; 42 | 43 | let ouput_size = blake2.get_size(); 44 | assert_eq!(ouput_size, 48); 45 | 46 | let _ = blake2.hash(TEST_HASH, &mut output); 47 | 48 | let expected = [ 49 | 0x5f, 0x03, 0x04, 0x77, 0x92, 0x5e, 0x91, 0x29, 0xf9, 0xb8, 0xef, 0xf9, 0x88, 0x29, 50 | 0x04, 0xf4, 0x4f, 0x65, 0x3b, 0xef, 0xf8, 0x21, 0xca, 0x48, 0x68, 0xa7, 0xbe, 0x46, 51 | 0x1c, 0x45, 0x82, 0xb3, 0x3d, 0xd7, 0x7b, 0x9e, 0x91, 0x9a, 0xfe, 0x1c, 0x3b, 0xed, 52 | 0x4b, 0x8f, 0x3c, 0x5d, 0xde, 0x53, 53 | ]; 54 | assert_eq!(&output, &expected); 55 | } 56 | 57 | #[test] 58 | fn test_hash_blake2b512() { 59 | let mut blake2 = Blake2b_512::new(); 60 | 61 | let mut output: [u8; 64] = [0u8; 64]; 62 | 63 | let ouput_size = blake2.get_size(); 64 | assert_eq!(ouput_size, 64); 65 | 66 | let _ = blake2.hash(TEST_HASH, &mut output); 67 | 68 | let expected = [ 69 | 0xc2, 0xe0, 0xfe, 0x8c, 0xb7, 0x83, 0x43, 0x7c, 0x8f, 0x36, 0x89, 0x48, 0xc4, 0x7a, 70 | 0x9c, 0x7c, 0x27, 0xa3, 0xb5, 0x98, 0x7a, 0x2d, 0x1b, 0x3b, 0xab, 0x48, 0x3d, 0xd6, 71 | 0xf6, 0x4c, 0xd1, 0x20, 0x7d, 0x72, 0x62, 0xb5, 0x35, 0xfe, 0x3f, 0x86, 0xad, 0x0c, 72 | 0x5f, 0x33, 0x4e, 0x55, 0x07, 0x64, 0x49, 0x7c, 0x11, 0xd5, 0xbd, 0x6a, 0x44, 0x2a, 73 | 0x9c, 0x2e, 0x6a, 0xab, 0xf9, 0x31, 0xc0, 0xab, 74 | ]; 75 | assert_eq!(&output, &expected); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/hash/ripemd.rs: -------------------------------------------------------------------------------- 1 | use super::HashInit; 2 | use ledger_secure_sdk_sys::{cx_hash_t, cx_ripemd160_init_no_throw, cx_ripemd160_t}; 3 | 4 | use super::impl_hash; 5 | impl_hash!(Ripemd160, cx_ripemd160_t, cx_ripemd160_init_no_throw); 6 | 7 | #[cfg(test)] 8 | mod tests { 9 | use crate::assert_eq_err as assert_eq; 10 | use crate::hash::ripemd::*; 11 | use crate::testing::TestType; 12 | use testmacro::test_item as test; 13 | 14 | const TEST_HASH: &[u8; 29] = b"Not your keys, not your coins"; 15 | 16 | #[test] 17 | fn test_hash_ripemd160() { 18 | let mut ripemd = Ripemd160::new(); 19 | 20 | let mut output: [u8; 20] = [0u8; 20]; 21 | 22 | let ouput_size = ripemd.get_size(); 23 | assert_eq!(ouput_size, 20); 24 | 25 | let _ = ripemd.hash(TEST_HASH, &mut output); 26 | 27 | let expected = [ 28 | 0x75, 0x0f, 0x75, 0x73, 0x6a, 0x34, 0xac, 0x02, 0xd0, 0x72, 0xec, 0x2a, 0xf5, 0xf7, 29 | 0x1d, 0x16, 0xc2, 0x6f, 0x63, 0x23, 30 | ]; 31 | assert_eq!(&output, &expected); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/hash/sha2.rs: -------------------------------------------------------------------------------- 1 | use super::HashInit; 2 | use ledger_secure_sdk_sys::{ 3 | cx_hash_t, cx_sha224_init_no_throw, cx_sha256_init_no_throw, cx_sha256_t, 4 | cx_sha384_init_no_throw, cx_sha512_init_no_throw, cx_sha512_t, 5 | }; 6 | 7 | use super::impl_hash; 8 | impl_hash!(Sha2_224, cx_sha256_t, cx_sha224_init_no_throw); 9 | impl_hash!(Sha2_256, cx_sha256_t, cx_sha256_init_no_throw); 10 | impl_hash!(Sha2_384, cx_sha512_t, cx_sha384_init_no_throw); 11 | impl_hash!(Sha2_512, cx_sha512_t, cx_sha512_init_no_throw); 12 | 13 | #[cfg(test)] 14 | mod tests { 15 | use crate::assert_eq_err as assert_eq; 16 | use crate::hash::sha2::*; 17 | use crate::testing::TestType; 18 | use testmacro::test_item as test; 19 | 20 | const TEST_HASH: &[u8; 29] = b"Not your keys, not your coins"; 21 | 22 | #[test] 23 | fn test_hash_sha2224() { 24 | let mut sha2 = Sha2_224::new(); 25 | 26 | let mut output: [u8; 28] = [0u8; 28]; 27 | 28 | let ouput_size = sha2.get_size(); 29 | assert_eq!(ouput_size, 28); 30 | 31 | let _ = sha2.hash(TEST_HASH, &mut output); 32 | 33 | let expected = [ 34 | 0x5a, 0x5b, 0xea, 0xa1, 0x3f, 0x5d, 0xf3, 0xd8, 0x5a, 0xc8, 0x62, 0x44, 0x95, 0x9b, 35 | 0xa2, 0x8e, 0xed, 0x08, 0x65, 0xa2, 0xcd, 0x10, 0xd1, 0x5c, 0xce, 0x47, 0x9a, 0x2a, 36 | ]; 37 | assert_eq!(&output, &expected); 38 | } 39 | 40 | #[test] 41 | fn test_hash_sha2256() { 42 | let mut sha2 = Sha2_256::new(); 43 | 44 | let mut output: [u8; 32] = [0u8; 32]; 45 | 46 | let ouput_size = sha2.get_size(); 47 | assert_eq!(ouput_size, 32); 48 | 49 | let _ = sha2.hash(TEST_HASH, &mut output); 50 | 51 | let expected = [ 52 | 0x52, 0x49, 0x2e, 0x81, 0x92, 0x16, 0xf3, 0x6b, 0x74, 0x7d, 0xd5, 0xda, 0x70, 0x3a, 53 | 0x26, 0x60, 0x14, 0x34, 0x60, 0x42, 0x42, 0xfa, 0xb2, 0x7e, 0x85, 0x51, 0xe7, 0x82, 54 | 0xa5, 0x11, 0x13, 0x40, 55 | ]; 56 | assert_eq!(&output, &expected); 57 | } 58 | 59 | #[test] 60 | fn test_hash_sha2384() { 61 | let mut sha2 = Sha2_384::new(); 62 | 63 | let mut output: [u8; 48] = [0u8; 48]; 64 | 65 | let ouput_size = sha2.get_size(); 66 | assert_eq!(ouput_size, 48); 67 | 68 | let _ = sha2.hash(TEST_HASH, &mut output); 69 | 70 | let expected = [ 71 | 0x11, 0xe3, 0xe7, 0xec, 0x0d, 0xc5, 0x81, 0x87, 0x8c, 0x35, 0xc6, 0xc8, 0x07, 0x15, 72 | 0x65, 0x53, 0x26, 0x1d, 0xb1, 0x7e, 0x32, 0x8c, 0xf8, 0x7d, 0x37, 0xbe, 0x05, 0x35, 73 | 0xf8, 0x45, 0x8d, 0x7c, 0xc9, 0x15, 0x74, 0xa2, 0x3f, 0x4f, 0x3e, 0x5f, 0x98, 0x23, 74 | 0xc7, 0xaa, 0x3a, 0xff, 0xf1, 0x59, 75 | ]; 76 | assert_eq!(&output, &expected); 77 | } 78 | 79 | #[test] 80 | fn test_hash_sha2512() { 81 | let mut sha2 = Sha2_512::new(); 82 | 83 | let mut output: [u8; 64] = [0u8; 64]; 84 | 85 | let ouput_size = sha2.get_size(); 86 | assert_eq!(ouput_size, 64); 87 | 88 | let _ = sha2.hash(TEST_HASH, &mut output); 89 | 90 | let expected = [ 91 | 0xf0, 0xe9, 0x96, 0x75, 0x81, 0xc0, 0xdb, 0x4c, 0x8e, 0xc0, 0xeb, 0xb2, 0x53, 0xa7, 92 | 0xff, 0x8d, 0x8a, 0x1a, 0x69, 0x06, 0xbc, 0x1b, 0x76, 0x0c, 0x23, 0x09, 0x9c, 0xc5, 93 | 0xe4, 0xf7, 0xea, 0x19, 0x07, 0x73, 0x57, 0x07, 0x8a, 0x66, 0x6b, 0x45, 0x1c, 0xa2, 94 | 0x32, 0xa4, 0xa7, 0x0c, 0xa1, 0x8d, 0xaa, 0x4e, 0xd0, 0x5a, 0xdd, 0x03, 0x02, 0x05, 95 | 0x04, 0xdf, 0xdd, 0x93, 0x1d, 0x54, 0x6f, 0xfd, 96 | ]; 97 | assert_eq!(&output, &expected); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/hash/sha3.rs: -------------------------------------------------------------------------------- 1 | use super::HashInit; 2 | use ledger_secure_sdk_sys::{ 3 | cx_hash_t, cx_keccak_init_no_throw, cx_sha3_init_no_throw, cx_sha3_t, 4 | cx_shake128_init_no_throw, cx_shake256_init_no_throw, 5 | }; 6 | 7 | use super::impl_hash; 8 | impl_hash!(Keccak256, cx_sha3_t, cx_keccak_init_no_throw, 256); 9 | impl_hash!(Sha3_224, cx_sha3_t, cx_sha3_init_no_throw, 224); 10 | impl_hash!(Sha3_256, cx_sha3_t, cx_sha3_init_no_throw, 256); 11 | impl_hash!(Sha3_384, cx_sha3_t, cx_sha3_init_no_throw, 384); 12 | impl_hash!(Sha3_512, cx_sha3_t, cx_sha3_init_no_throw, 512); 13 | impl_hash!(Shake128, cx_sha3_t, cx_shake128_init_no_throw, 128); 14 | impl_hash!(Shake256, cx_sha3_t, cx_shake256_init_no_throw, 256); 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use crate::assert_eq_err as assert_eq; 19 | use crate::hash::sha3::*; 20 | use crate::testing::TestType; 21 | use testmacro::test_item as test; 22 | 23 | const TEST_HASH: &[u8; 29] = b"Not your keys, not your coins"; 24 | 25 | #[test] 26 | fn test_hash_keccak() { 27 | let mut keccak = Keccak256::new(); 28 | 29 | let mut output: [u8; 32] = [0u8; 32]; 30 | 31 | let ouput_size = keccak.get_size(); 32 | assert_eq!(ouput_size, 32); 33 | 34 | let _ = keccak.hash(TEST_HASH, &mut output); 35 | 36 | let expected = [ 37 | 0x1f, 0x20, 0x7c, 0xd9, 0xfd, 0x9f, 0x0b, 0x09, 0xb0, 0x04, 0x93, 0x6c, 0xa5, 0xe0, 38 | 0xd3, 0x1b, 0xa1, 0x6c, 0xd6, 0x14, 0x53, 0xaa, 0x28, 0x7e, 0x65, 0xaa, 0x88, 0x25, 39 | 0x3c, 0xdc, 0x1c, 0x94, 40 | ]; 41 | assert_eq!(&output, &expected); 42 | } 43 | 44 | #[test] 45 | fn test_hash_sha3224() { 46 | let mut sha3224 = Sha3_224::new(); 47 | 48 | let mut output: [u8; 32] = [0u8; 32]; 49 | 50 | let ouput_size: usize = sha3224.get_size(); 51 | assert_eq!(ouput_size, 28); 52 | 53 | let _ = sha3224.hash(TEST_HASH, &mut output); 54 | 55 | let expected = [ 56 | 0x92, 0xd0, 0x94, 0x85, 0xb4, 0x74, 0xdc, 0x58, 0xcc, 0xbb, 0x03, 0xb5, 0x0e, 0x1c, 57 | 0x1c, 0xe2, 0xab, 0x33, 0xd5, 0xf2, 0xf9, 0xbd, 0xfd, 0xda, 0xcd, 0x88, 0xc6, 0xfc, 58 | ]; 59 | assert_eq!(&output[..28], &expected); 60 | } 61 | 62 | #[test] 63 | fn test_hash_sha3256() { 64 | let mut sha3256 = Sha3_256::new(); 65 | 66 | let mut output: [u8; 32] = [0u8; 32]; 67 | 68 | let ouput_size: usize = sha3256.get_size(); 69 | assert_eq!(ouput_size, 32); 70 | 71 | let _ = sha3256.hash(TEST_HASH, &mut output); 72 | 73 | let expected = [ 74 | 0x80, 0x8b, 0x0a, 0xd9, 0xdd, 0x0f, 0xe7, 0x6f, 0x8d, 0xb4, 0xbb, 0x99, 0xe6, 0x3e, 75 | 0x9d, 0x24, 0xce, 0xa6, 0x4c, 0xfc, 0xdf, 0x93, 0x7a, 0xdb, 0xca, 0x9a, 0xe1, 0x1f, 76 | 0x27, 0x3a, 0x00, 0xb7, 77 | ]; 78 | assert_eq!(&output[..32], &expected); 79 | } 80 | 81 | #[test] 82 | fn test_hash_sha3384() { 83 | let mut sha3384 = Sha3_384::new(); 84 | 85 | let mut output: [u8; 48] = [0u8; 48]; 86 | 87 | let ouput_size: usize = sha3384.get_size(); 88 | assert_eq!(ouput_size, 48); 89 | 90 | let _ = sha3384.hash(TEST_HASH, &mut output); 91 | 92 | let expected = [ 93 | 0x16, 0xdd, 0xbe, 0xd5, 0xf8, 0x95, 0xf9, 0x04, 0xe9, 0xb8, 0xc2, 0x71, 0xe9, 0xa0, 94 | 0x66, 0x7d, 0xa4, 0x35, 0x7e, 0xd9, 0x87, 0x4b, 0x27, 0x23, 0x00, 0xf9, 0xd5, 0x10, 95 | 0x55, 0xd5, 0x6c, 0x65, 0xe3, 0x3d, 0x83, 0xd7, 0xff, 0x8e, 0xab, 0x98, 0x80, 0x60, 96 | 0xe5, 0x9c, 0x1a, 0x34, 0xe7, 0xdc, 97 | ]; 98 | assert_eq!(&output[..48], &expected); 99 | } 100 | 101 | #[test] 102 | fn test_hash_sha3512() { 103 | let mut sha3512 = Sha3_512::new(); 104 | 105 | let mut output: [u8; 64] = [0u8; 64]; 106 | 107 | let ouput_size: usize = sha3512.get_size(); 108 | assert_eq!(ouput_size, 64); 109 | 110 | let _ = sha3512.hash(TEST_HASH, &mut output); 111 | 112 | let expected = [ 113 | 0x4e, 0x81, 0x24, 0xc2, 0xed, 0x1e, 0x9a, 0x1c, 0x60, 0x0f, 0xc0, 0x6b, 0x49, 0xd3, 114 | 0xa3, 0x54, 0x28, 0x81, 0x86, 0x62, 0xf0, 0xcd, 0x95, 0x1d, 0x67, 0x58, 0x3d, 0x8d, 115 | 0x28, 0xab, 0x97, 0x9a, 0x56, 0xab, 0x57, 0xa3, 0x78, 0x15, 0x01, 0x86, 0x6a, 0x00, 116 | 0xf3, 0x89, 0x11, 0x7d, 0x7e, 0x47, 0x84, 0xcd, 0x0c, 0x00, 0x25, 0xad, 0xac, 0xbe, 117 | 0x00, 0xb2, 0xf5, 0xf2, 0x6e, 0x0d, 0x61, 0x59, 118 | ]; 119 | assert_eq!(&output[..64], &expected); 120 | } 121 | 122 | #[test] 123 | fn test_hash_shake128() { 124 | let mut shake128 = Shake128::new(); 125 | 126 | let mut output: [u8; 16] = [0u8; 16]; 127 | 128 | let ouput_size: usize = shake128.get_size(); 129 | assert_eq!(ouput_size, 16); 130 | 131 | let _ = shake128.hash(TEST_HASH, &mut output); 132 | 133 | let expected = [ 134 | 0x45, 0xd9, 0xa1, 0x61, 0x7b, 0x0d, 0x7b, 0xb1, 0xf1, 0x09, 0x63, 0xe1, 0xb0, 0xa5, 135 | 0xaa, 0x2c, 136 | ]; 137 | assert_eq!(&output[..16], &expected); 138 | } 139 | 140 | #[test] 141 | fn test_hash_shake256() { 142 | let mut shake256 = Shake256::new(); 143 | 144 | let mut output: [u8; 32] = [0u8; 32]; 145 | 146 | let ouput_size: usize = shake256.get_size(); 147 | assert_eq!(ouput_size, 32); 148 | 149 | let _ = shake256.hash(TEST_HASH, &mut output); 150 | 151 | let expected = [ 152 | 0x3d, 0x51, 0xd1, 0xfc, 0x5e, 0x2a, 0x3e, 0x4b, 0x9c, 0xdf, 0x2b, 0x03, 0x18, 0xf5, 153 | 0xd1, 0x91, 0x87, 0x4d, 0x52, 0xc1, 0x8c, 0x7b, 0x33, 0x36, 0x52, 0x7b, 0x0b, 0x64, 154 | 0x28, 0xfa, 0xad, 0xf1, 155 | ]; 156 | assert_eq!(&output[..32], &expected); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/hmac.rs: -------------------------------------------------------------------------------- 1 | //! Hash-based message authentication code (HMAC) related functions 2 | use ledger_secure_sdk_sys::{ 3 | cx_hmac_final, cx_hmac_no_throw, cx_hmac_t, cx_hmac_update, CX_INVALID_PARAMETER, CX_LAST, 4 | CX_OK, 5 | }; 6 | 7 | pub mod ripemd; 8 | pub mod sha2; 9 | 10 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 11 | pub enum HMACError { 12 | InvalidParameter, 13 | InvalidOutputLength, 14 | InternalError, 15 | } 16 | 17 | impl From for HMACError { 18 | fn from(x: u32) -> HMACError { 19 | match x { 20 | CX_INVALID_PARAMETER => HMACError::InvalidParameter, 21 | _ => HMACError::InternalError, 22 | } 23 | } 24 | } 25 | 26 | impl From for u32 { 27 | fn from(e: HMACError) -> u32 { 28 | e as u32 29 | } 30 | } 31 | 32 | /// Defines the behavior of a rust HMAC object. 33 | /// The implementation for a given algorithm is done using a rust macro 34 | /// to avoid code duplication since only the C structures and functions 35 | /// imported from the C SDK change. 36 | pub trait HMACInit: Sized { 37 | /// Recovers a mutable version of the HMAC context that can be used 38 | /// to call HMAC related method in the C SDK. 39 | fn as_ctx_mut(&mut self) -> &mut cx_hmac_t; 40 | /// Recovers a constant version of the HMAC context that can be used 41 | /// to call HMAC related method in the C SDK. 42 | fn as_ctx(&self) -> &cx_hmac_t; 43 | /// Creates the HMAC object by initializing the associated context using 44 | /// the related C structure. 45 | fn new(key: &[u8]) -> Self; 46 | 47 | /// Computes a HMAC in one line by providing the complete input as well as the 48 | /// output buffer. 49 | /// An error can be returned if one of the parameter is invalid 50 | /// or if the output buffer size is not enough. 51 | fn hmac(&mut self, input: &[u8], output: &mut [u8]) -> Result<(), HMACError> { 52 | let err = unsafe { 53 | cx_hmac_no_throw( 54 | self.as_ctx_mut(), 55 | CX_LAST, 56 | input.as_ptr(), 57 | input.len(), 58 | output.as_mut_ptr(), 59 | output.len(), 60 | ) 61 | }; 62 | if err != CX_OK { 63 | Err(err.into()) 64 | } else { 65 | Ok(()) 66 | } 67 | } 68 | 69 | /// Updates the current HMAC object state with the given input data. 70 | /// This method may be called as many times needed (useful for large bufferized 71 | /// inputs). This method should not be called after `finalize`. 72 | /// An error can be returned if the input is invalid or the context in a wrong state. 73 | fn update(&mut self, input: &[u8]) -> Result<(), HMACError> { 74 | let err = unsafe { cx_hmac_update(self.as_ctx_mut(), input.as_ptr(), input.len()) }; 75 | if err != CX_OK { 76 | Err(err.into()) 77 | } else { 78 | Ok(()) 79 | } 80 | } 81 | 82 | /// Finalizes the computation of the MAC and stores the result in the output buffer 83 | /// as well as returning the MAC length. 84 | /// This method should be called after one or many calls to `update`. 85 | /// An error can be returned if one of the parameter is invalid 86 | /// or if the output buffer size is not enough. 87 | fn finalize(&mut self, output: &mut [u8]) -> Result { 88 | let mut out_len = output.len(); 89 | let err = unsafe { cx_hmac_final(self.as_ctx_mut(), output.as_mut_ptr(), &mut out_len) }; 90 | if err != CX_OK { 91 | Err(err.into()) 92 | } else { 93 | Ok(out_len) 94 | } 95 | } 96 | } 97 | 98 | /// This macro can be used to implement the HMACInit trait for a given hash 99 | /// algorithm by providing the structure name, the C context name, and the C 100 | /// context initialization function. 101 | macro_rules! impl_hmac { 102 | ($typename:ident, $ctxname:ident, $initfname:ident) => { 103 | #[derive(Default)] 104 | #[allow(non_camel_case_types)] 105 | pub struct $typename { 106 | ctx: $ctxname, 107 | } 108 | impl HMACInit for $typename { 109 | fn as_ctx_mut(&mut self) -> &mut cx_hmac_t { 110 | unsafe { mem::transmute::<&mut $ctxname, &mut cx_hmac_t>(&mut self.ctx) } 111 | } 112 | 113 | fn as_ctx(&self) -> &cx_hmac_t { 114 | unsafe { mem::transmute::<&$ctxname, &cx_hmac_t>(&self.ctx) } 115 | } 116 | 117 | fn new(key: &[u8]) -> Self { 118 | let mut ctx: $typename = Default::default(); 119 | let _err = unsafe { 120 | $initfname(&mut ctx.ctx, key.as_ptr(), key.len().try_into().unwrap()) 121 | }; 122 | ctx 123 | } 124 | } 125 | }; 126 | } 127 | pub(crate) use impl_hmac; 128 | 129 | #[cfg(test)] 130 | mod tests { 131 | use crate::assert_eq_err as assert_eq; 132 | use crate::hmac::ripemd::Ripemd160; 133 | use crate::hmac::HMACInit; 134 | use crate::testing::TestType; 135 | use testmacro::test_item as test; 136 | 137 | const TEST_MSG: &[u8; 29] = b"Not your keys, not your coins"; 138 | const TEST_KEY: &[u8; 16] = b"hmac test key!!!"; 139 | 140 | #[test] 141 | fn test_hmac_oneline() { 142 | let mut mac = Ripemd160::new(TEST_KEY); 143 | 144 | let mut output: [u8; 20] = [0u8; 20]; 145 | 146 | let _ = mac.hmac(TEST_MSG, &mut output); 147 | 148 | let expected = [ 149 | 0xfa, 0xde, 0x57, 0x70, 0xf8, 0xa5, 0x04, 0x1a, 0xac, 0xdb, 0xe1, 0xc5, 0x64, 0x21, 150 | 0x0d, 0xa6, 0x89, 0x9b, 0x2e, 0x6f, 151 | ]; 152 | assert_eq!(&output, &expected); 153 | } 154 | 155 | #[test] 156 | fn test_hmac_update() { 157 | let mut mac = Ripemd160::new(TEST_KEY); 158 | 159 | let mut output: [u8; 20] = [0u8; 20]; 160 | 161 | let _ = mac.update(TEST_MSG); 162 | 163 | let res = mac.finalize(&mut output); 164 | 165 | let expected = [ 166 | 0xfa, 0xde, 0x57, 0x70, 0xf8, 0xa5, 0x04, 0x1a, 0xac, 0xdb, 0xe1, 0xc5, 0x64, 0x21, 167 | 0x0d, 0xa6, 0x89, 0x9b, 0x2e, 0x6f, 168 | ]; 169 | assert_eq!(&output, &expected); 170 | assert_eq!(res.unwrap(), expected.len()); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/hmac/ripemd.rs: -------------------------------------------------------------------------------- 1 | use super::HMACInit; 2 | use core::mem; 3 | use ledger_secure_sdk_sys::{cx_hmac_ripemd160_init_no_throw, cx_hmac_ripemd160_t, cx_hmac_t}; 4 | 5 | use super::impl_hmac; 6 | impl_hmac!( 7 | Ripemd160, 8 | cx_hmac_ripemd160_t, 9 | cx_hmac_ripemd160_init_no_throw 10 | ); 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | use crate::assert_eq_err as assert_eq; 15 | use crate::hmac::ripemd::*; 16 | use crate::testing::TestType; 17 | use testmacro::test_item as test; 18 | 19 | const TEST_MSG: &[u8; 29] = b"Not your keys, not your coins"; 20 | const TEST_KEY: &[u8; 16] = b"hmac test key!!!"; 21 | 22 | #[test] 23 | fn test_hmac_ripemd160() { 24 | let mut mac = Ripemd160::new(TEST_KEY); 25 | 26 | let mut output: [u8; 20] = [0u8; 20]; 27 | 28 | let _ = mac.hmac(TEST_MSG, &mut output); 29 | 30 | let expected = [ 31 | 0xfa, 0xde, 0x57, 0x70, 0xf8, 0xa5, 0x04, 0x1a, 0xac, 0xdb, 0xe1, 0xc5, 0x64, 0x21, 32 | 0x0d, 0xa6, 0x89, 0x9b, 0x2e, 0x6f, 33 | ]; 34 | assert_eq!(&output, &expected); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/hmac/sha2.rs: -------------------------------------------------------------------------------- 1 | use super::HMACInit; 2 | use core::mem; 3 | use ledger_secure_sdk_sys::{ 4 | cx_hmac_sha224_init, cx_hmac_sha256_init_no_throw, cx_hmac_sha256_t, cx_hmac_sha384_init, 5 | cx_hmac_sha512_init_no_throw, cx_hmac_sha512_t, cx_hmac_t, 6 | }; 7 | 8 | use super::impl_hmac; 9 | impl_hmac!(Sha2_224, cx_hmac_sha256_t, cx_hmac_sha224_init); 10 | impl_hmac!(Sha2_256, cx_hmac_sha256_t, cx_hmac_sha256_init_no_throw); 11 | impl_hmac!(Sha2_384, cx_hmac_sha512_t, cx_hmac_sha384_init); 12 | impl_hmac!(Sha2_512, cx_hmac_sha512_t, cx_hmac_sha512_init_no_throw); 13 | 14 | #[cfg(test)] 15 | mod tests { 16 | use crate::assert_eq_err as assert_eq; 17 | use crate::hmac::sha2::*; 18 | use crate::testing::TestType; 19 | use testmacro::test_item as test; 20 | 21 | const TEST_MSG: &[u8; 29] = b"Not your keys, not your coins"; 22 | const TEST_KEY: &[u8; 16] = b"hmac test key!!!"; 23 | 24 | #[test] 25 | fn test_hmac_sha224() { 26 | let mut mac = Sha2_224::new(TEST_KEY); 27 | 28 | let mut output: [u8; 28] = [0u8; 28]; 29 | 30 | let _ = mac.hmac(TEST_MSG, &mut output); 31 | 32 | let expected = [ 33 | 0xc4, 0x64, 0x80, 0xfb, 0xea, 0xc7, 0x75, 0x6d, 0xee, 0xc1, 0x6a, 0xcb, 0x6d, 0xae, 34 | 0x6a, 0xfa, 0x5d, 0x03, 0x17, 0x73, 0xd6, 0x4d, 0x49, 0xea, 0xa8, 0x5e, 0x4c, 0x1d, 35 | ]; 36 | assert_eq!(&output, &expected); 37 | } 38 | 39 | #[test] 40 | fn test_hmac_sha256() { 41 | let mut mac = Sha2_256::new(TEST_KEY); 42 | 43 | let mut output: [u8; 32] = [0u8; 32]; 44 | 45 | let _ = mac.hmac(TEST_MSG, &mut output); 46 | 47 | let expected = [ 48 | 0x4d, 0x23, 0x82, 0xff, 0xc3, 0xb0, 0x60, 0x48, 0x59, 0xc0, 0xe5, 0x28, 0xf3, 0x66, 49 | 0xa0, 0xba, 0x5b, 0xcb, 0x2c, 0x24, 0x10, 0x9c, 0x9d, 0x0b, 0x3b, 0x0a, 0x75, 0x8d, 50 | 0x0f, 0x5a, 0x2a, 0x13, 51 | ]; 52 | assert_eq!(&output, &expected); 53 | } 54 | 55 | #[test] 56 | fn test_hmac_sha384() { 57 | let mut mac = Sha2_384::new(TEST_KEY); 58 | 59 | let mut output: [u8; 48] = [0u8; 48]; 60 | 61 | let _ = mac.hmac(TEST_MSG, &mut output); 62 | 63 | let expected = [ 64 | 0x20, 0x6d, 0x0d, 0xfd, 0xfd, 0x22, 0x43, 0xde, 0xb0, 0x23, 0xf8, 0x56, 0x63, 0xd1, 65 | 0xa2, 0x1e, 0xc1, 0x6a, 0x72, 0x6b, 0xa7, 0x8e, 0xc2, 0x25, 0xe2, 0x1e, 0x3e, 0x3b, 66 | 0xb2, 0xf0, 0x55, 0x1d, 0x4e, 0xde, 0x5f, 0x81, 0xf6, 0xa1, 0xff, 0x8e, 0x76, 0x86, 67 | 0xf1, 0x0f, 0x7a, 0xec, 0xbe, 0x35, 68 | ]; 69 | assert_eq!(&output, &expected); 70 | } 71 | 72 | #[test] 73 | fn test_hmac_sha512() { 74 | let mut mac = Sha2_512::new(TEST_KEY); 75 | 76 | let mut output: [u8; 64] = [0u8; 64]; 77 | 78 | let _ = mac.hmac(TEST_MSG, &mut output); 79 | 80 | let expected = [ 81 | 0x2d, 0x03, 0x14, 0x96, 0x68, 0x0e, 0xcc, 0x41, 0x2a, 0x42, 0xf2, 0x45, 0xf8, 0x0b, 82 | 0x10, 0x87, 0x43, 0x96, 0x4d, 0x80, 0x5d, 0x93, 0x5c, 0xd1, 0x6b, 0x95, 0xc1, 0x7a, 83 | 0xed, 0xbd, 0xd8, 0x8c, 0xf8, 0xa7, 0x60, 0xed, 0x04, 0xa2, 0x5b, 0x8d, 0xd8, 0x3d, 84 | 0xa3, 0x13, 0xa1, 0x6a, 0x07, 0x33, 0x49, 0x06, 0x15, 0x79, 0x70, 0xf3, 0xe9, 0x9a, 85 | 0xff, 0x25, 0xb6, 0x5e, 0x37, 0xd1, 0x7e, 0x2b, 86 | ]; 87 | assert_eq!(&output, &expected); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![cfg_attr(test, no_main)] 3 | #![feature(custom_test_frameworks)] 4 | #![reexport_test_harness_main = "test_main"] 5 | #![test_runner(testing::sdk_test_runner)] 6 | #![allow(incomplete_features)] 7 | #![feature(generic_const_exprs)] 8 | #![feature(cfg_version)] 9 | 10 | #[cfg(any(target_os = "nanox", target_os = "stax", target_os = "flex"))] 11 | pub mod ble; 12 | 13 | pub mod ecc; 14 | pub mod hash; 15 | pub mod hmac; 16 | pub mod io; 17 | pub mod libcall; 18 | pub mod nvm; 19 | pub mod random; 20 | pub mod screen; 21 | pub mod seph; 22 | 23 | pub mod testing; 24 | 25 | #[cfg(any(target_os = "stax", target_os = "flex", feature = "nano_nbgl"))] 26 | pub mod nbgl; 27 | #[cfg(not(any(target_os = "stax", target_os = "flex", feature = "nano_nbgl")))] 28 | pub mod ui; 29 | 30 | pub mod uxapp; 31 | 32 | use core::panic::PanicInfo; 33 | 34 | /// In case of runtime problems, return an internal error and exit the app 35 | #[inline] 36 | pub fn exiting_panic(_info: &PanicInfo) -> ! { 37 | let mut comm = io::Comm::new(); 38 | comm.reply(io::StatusWords::Panic); 39 | ledger_secure_sdk_sys::exit_app(0); 40 | } 41 | 42 | // re-export exit_app 43 | pub use ledger_secure_sdk_sys::buttons; 44 | pub use ledger_secure_sdk_sys::exit_app; 45 | 46 | /// Helper macro that sets an external panic handler 47 | /// as the project's current panic handler 48 | #[macro_export] 49 | macro_rules! set_panic { 50 | ($f:expr) => { 51 | use core::panic::PanicInfo; 52 | #[panic_handler] 53 | fn panic(info: &PanicInfo) -> ! { 54 | $f(info) 55 | } 56 | }; 57 | } 58 | 59 | extern "C" { 60 | fn c_main(); 61 | } 62 | 63 | #[link_section = ".boot"] 64 | #[no_mangle] 65 | pub extern "C" fn _start() -> ! { 66 | // Main is in C until the try_context can be set properly from Rust 67 | unsafe { c_main() }; 68 | ledger_secure_sdk_sys::exit_app(1); 69 | } 70 | 71 | /// Data wrapper to force access through address translation with [`pic_rs`] or 72 | /// [`pic_rs_mut`]. This can help preventing mistakes when accessing data which 73 | /// has been relocated. 74 | /// 75 | /// # Examples 76 | /// 77 | /// ``` 78 | /// // This constant data is stored in Code space, which is relocated. 79 | /// static DATA: Pic = Pic::new(42); 80 | /// ... 81 | /// // Access with address translation is enforced thanks to Pic wrapper 82 | /// let x: u32 = *DATA.get_ref(); 83 | /// ``` 84 | pub struct Pic { 85 | data: T, 86 | } 87 | 88 | impl Pic { 89 | pub const fn new(data: T) -> Pic { 90 | Pic { data } 91 | } 92 | 93 | /// Returns translated reference to the wrapped data. 94 | pub fn get_ref(&self) -> &T { 95 | ledger_secure_sdk_sys::pic_rs(&self.data) 96 | } 97 | 98 | /// Returns translated mutable reference to the wrapped data. 99 | pub fn get_mut(&mut self) -> &mut T { 100 | ledger_secure_sdk_sys::pic_rs_mut(&mut self.data) 101 | } 102 | } 103 | 104 | // Needed for `NVMData` to function properly 105 | extern "C" { 106 | // This is a linker script symbol defining the beginning of 107 | // the .nvm_data section. Declaring it as a static u32 108 | // (as is usually done) will result in a r9-indirect memory 109 | // access, as if it were a RAM access. 110 | // To force the compiler out of this assumption, we define 111 | // it as a function instead, but it is _not_ a function at all 112 | fn _nvm_data_start(); 113 | } 114 | 115 | /// The following is a means to correctly access data stored in NVM 116 | /// through the `#[link_section = ".nvm_data"]` attribute 117 | pub struct NVMData { 118 | data: T, 119 | } 120 | 121 | impl NVMData { 122 | pub const fn new(data: T) -> NVMData { 123 | NVMData { data } 124 | } 125 | 126 | /// This will return a mutable access by casting the pointer 127 | /// to the correct offset in `.nvm_data` manually. 128 | /// This is necessary when using the `rwpi` relocation model, 129 | /// because a static mutable will be assumed to be located in 130 | /// RAM, and be accessed through the static base (r9) 131 | fn get_addr(&self) -> *mut T { 132 | use core::arch::asm; 133 | unsafe { 134 | // Compute offset in .nvm_data by taking the reference to 135 | // self.data and subtracting r9 136 | let addr = &self.data as *const T as u32; 137 | let static_base: u32; 138 | asm!( "mov {}, r9", out(reg) static_base); 139 | let offset = (addr - static_base) as isize; 140 | let data_addr = (_nvm_data_start as *const u8).offset(offset); 141 | ledger_secure_sdk_sys::pic(data_addr as *mut core::ffi::c_void) as *mut T 142 | } 143 | } 144 | 145 | pub fn get_mut(&mut self) -> &mut T { 146 | unsafe { 147 | let pic_addr = self.get_addr(); 148 | &mut *pic_addr.cast() 149 | } 150 | } 151 | 152 | pub fn get_ref(&self) -> &T { 153 | unsafe { 154 | let pic_addr = self.get_addr(); 155 | &*pic_addr.cast() 156 | } 157 | } 158 | } 159 | 160 | #[cfg(test)] 161 | #[no_mangle] 162 | fn sample_main() { 163 | test_main(); 164 | } 165 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/libcall.rs: -------------------------------------------------------------------------------- 1 | use crate::testing::debug_print; 2 | 3 | use ledger_secure_sdk_sys::{libargs_t, CHECK_ADDRESS, GET_PRINTABLE_AMOUNT, SIGN_TRANSACTION}; 4 | 5 | pub mod string; 6 | pub mod swap; 7 | 8 | pub enum LibCallCommand { 9 | SwapSignTransaction, 10 | SwapGetPrintableAmount, 11 | SwapCheckAddress, 12 | } 13 | 14 | impl From for LibCallCommand { 15 | fn from(command: u32) -> Self { 16 | match command { 17 | SIGN_TRANSACTION => LibCallCommand::SwapSignTransaction, 18 | GET_PRINTABLE_AMOUNT => LibCallCommand::SwapGetPrintableAmount, 19 | CHECK_ADDRESS => LibCallCommand::SwapCheckAddress, 20 | _ => panic!("Unknown command"), 21 | } 22 | } 23 | } 24 | 25 | pub fn get_command(arg0: u32) -> LibCallCommand { 26 | debug_print("GET_CMD\n"); 27 | let mut libarg: libargs_t = libargs_t::default(); 28 | 29 | let arg = arg0 as *const u32; 30 | 31 | libarg.id = unsafe { *arg }; 32 | libarg.command = unsafe { *arg.add(1) }; 33 | libarg.unused = unsafe { *arg.add(2) }; 34 | libarg.command.into() 35 | } 36 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/libcall/string.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Copy, Clone)] 2 | pub struct CustomString { 3 | pub arr: [u8; N], 4 | pub capacity: usize, 5 | pub len: usize, 6 | } 7 | 8 | impl Default for CustomString { 9 | fn default() -> Self { 10 | Self { 11 | arr: [b'0'; N], 12 | capacity: N, 13 | len: 0, 14 | } 15 | } 16 | } 17 | 18 | impl CustomString { 19 | pub fn new() -> Self { 20 | Self { 21 | arr: [b'0'; N], 22 | capacity: N, 23 | len: 0, 24 | } 25 | } 26 | 27 | pub fn clear(&mut self) { 28 | self.arr.fill(0); 29 | self.len = 0; 30 | } 31 | 32 | pub fn as_str(&self) -> &str { 33 | core::str::from_utf8(&self.arr[..self.len]).unwrap() 34 | } 35 | 36 | pub fn copy_from(&mut self, s: &CustomString) { 37 | self.arr[..s.len].copy_from_slice(&s.arr[..s.len]); 38 | self.len = s.len; 39 | } 40 | } 41 | 42 | impl From for CustomString<2> { 43 | fn from(val: u8) -> Self { 44 | let mut s = CustomString::<2>::new(); 45 | let mut i: usize = 0; 46 | for c in val.to_be_bytes().into_iter() { 47 | let (c0, c1) = byte_to_hex(c); 48 | s.arr[i] = c0 as u8; 49 | s.arr[i + 1] = c1 as u8; 50 | s.len += 2; 51 | i += 2; 52 | } 53 | s 54 | } 55 | } 56 | 57 | impl From for CustomString<4> { 58 | fn from(val: u16) -> Self { 59 | let mut s = CustomString::<4>::new(); 60 | let mut i: usize = 0; 61 | for c in val.to_be_bytes().into_iter() { 62 | let (c0, c1) = byte_to_hex(c); 63 | s.arr[i] = c0 as u8; 64 | s.arr[i + 1] = c1 as u8; 65 | s.len += 2; 66 | i += 2; 67 | } 68 | s 69 | } 70 | } 71 | 72 | impl From for CustomString<8> { 73 | fn from(val: u32) -> Self { 74 | let mut s = CustomString::<8>::new(); 75 | let mut i: usize = 0; 76 | for c in val.to_be_bytes().into_iter() { 77 | let (c0, c1) = byte_to_hex(c); 78 | s.arr[i] = c0 as u8; 79 | s.arr[i + 1] = c1 as u8; 80 | s.len += 2; 81 | i += 2; 82 | } 83 | s 84 | } 85 | } 86 | 87 | impl From<[u8; 32]> for CustomString<64> { 88 | fn from(arr: [u8; 32]) -> Self { 89 | let mut s = CustomString::<64>::new(); 90 | let mut i: usize = 0; 91 | for c in arr.into_iter() { 92 | let (c0, c1) = byte_to_hex(c); 93 | s.arr[i] = c0 as u8; 94 | s.arr[i + 1] = c1 as u8; 95 | s.len += 2; 96 | i += 2; 97 | } 98 | s 99 | } 100 | } 101 | 102 | impl TryFrom<&str> for CustomString { 103 | type Error = &'static str; 104 | fn try_from(st: &str) -> Result { 105 | if N >= st.len() { 106 | let mut s = CustomString::::new(); 107 | s.arr[..st.len()].copy_from_slice(st.as_bytes()); 108 | s.len = st.len(); 109 | Ok(s) 110 | } else { 111 | Err("CustomString's capacity overflow!") 112 | } 113 | } 114 | } 115 | 116 | /// Output an uint256 as an decimal CustomString 117 | /// For instance: 118 | /// 119 | /// let val: [u8; 32] = token amount (32 bytes / 256 bits); 120 | /// let s: CustomString<79> = uint256_to_integer(&val); // max number of decimal digits for Uint256 = 78 (+ 1 spare for '.') 121 | /// testing::debug_print(s.print().unwrap()); 122 | pub fn uint256_to_integer(value: &[u8; 32]) -> CustomString<79> { 123 | let mut s: CustomString<79> = CustomString::new(); 124 | 125 | // Special case when value is 0 126 | if *value == [0u8; 32] { 127 | s.arr[0] = b'0'; 128 | s.len = 1; 129 | return s; 130 | } 131 | 132 | let mut n: [u16; 16] = [0u16; 16]; 133 | for idx in 0..16 { 134 | n[idx] = u16::from_be_bytes([value[2 * idx], value[2 * idx + 1]]); 135 | } 136 | 137 | let mut pos: usize = s.capacity; 138 | while n != [0u16; 16] { 139 | if pos == 0 { 140 | return s; 141 | } 142 | pos -= 1; 143 | let mut carry = 0u32; 144 | let mut rem: u32; 145 | for i in 0..16 { 146 | rem = ((carry << 16) | u32::from(n[i])) % 10; 147 | n[i] = (((carry << 16) | u32::from(n[i])) / 10) as u16; 148 | carry = rem; 149 | } 150 | s.arr[pos] = u8::try_from(char::from_digit(carry, 10).unwrap()).unwrap(); 151 | } 152 | s.arr.copy_within(pos.., 0); 153 | s.len = s.capacity - pos; 154 | s 155 | } 156 | 157 | /// Output an uint256 as a float string 158 | pub fn uint256_to_float(value: &[u8; 32], decimals: usize) -> CustomString<79> { 159 | let mut s: CustomString<79> = uint256_to_integer(value); 160 | 161 | if decimals == 0 || s.arr[0] == b'0' { 162 | return s; 163 | } 164 | 165 | if s.len <= decimals { 166 | s.arr.copy_within(0..s.len, 2 + decimals - s.len); 167 | s.arr[0..2 + decimals - s.len].fill(b'0'); 168 | s.arr[1] = b'.'; 169 | s.len += 2 + decimals - s.len; 170 | } else { 171 | s.arr 172 | .copy_within(s.len - decimals..s.len, s.len - decimals + 1); 173 | s.arr[s.len - decimals] = b'.'; 174 | s.len += 1; 175 | } 176 | s 177 | } 178 | 179 | fn byte_to_hex(b: u8) -> (char, char) { 180 | let c0 = char::from_digit((b >> 4).into(), 16).unwrap(); 181 | let c1 = char::from_digit((b & 0xf).into(), 16).unwrap(); 182 | (c0, c1) 183 | } 184 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/nbgl.rs: -------------------------------------------------------------------------------- 1 | use crate::io::{ApduHeader, Comm, Event, Reply}; 2 | use crate::nvm::*; 3 | use const_zero::const_zero; 4 | extern crate alloc; 5 | use alloc::ffi::CString; 6 | use alloc::{vec, vec::Vec}; 7 | use core::ffi::{c_char, c_int}; 8 | use core::mem::transmute; 9 | use ledger_secure_sdk_sys::*; 10 | 11 | pub mod nbgl_address_review; 12 | pub mod nbgl_choice; 13 | #[cfg(any(target_os = "stax", target_os = "flex"))] 14 | pub mod nbgl_generic_review; 15 | pub mod nbgl_home_and_settings; 16 | pub mod nbgl_review; 17 | pub mod nbgl_review_status; 18 | pub mod nbgl_spinner; 19 | pub mod nbgl_status; 20 | pub mod nbgl_streaming_review; 21 | 22 | pub use nbgl_address_review::*; 23 | pub use nbgl_choice::*; 24 | #[cfg(any(target_os = "stax", target_os = "flex"))] 25 | pub use nbgl_generic_review::*; 26 | pub use nbgl_home_and_settings::*; 27 | pub use nbgl_review::*; 28 | pub use nbgl_review_status::*; 29 | pub use nbgl_spinner::*; 30 | pub use nbgl_status::*; 31 | pub use nbgl_streaming_review::*; 32 | 33 | static mut COMM_REF: Option<&mut Comm> = None; 34 | 35 | #[derive(Copy, Clone)] 36 | enum SyncNbgl { 37 | UxSyncRetApproved = 0x00, 38 | UxSyncRetRejected = 0x01, 39 | UxSyncRetQuitted = 0x02, 40 | UxSyncRetApduReceived = 0x03, 41 | UxSyncRetError = 0xFF, 42 | } 43 | 44 | impl From for SyncNbgl { 45 | fn from(val: u8) -> SyncNbgl { 46 | match val { 47 | 0x00 => SyncNbgl::UxSyncRetApproved, 48 | 0x01 => SyncNbgl::UxSyncRetRejected, 49 | 0x02 => SyncNbgl::UxSyncRetQuitted, 50 | 0x03 => SyncNbgl::UxSyncRetApduReceived, 51 | _ => SyncNbgl::UxSyncRetError, 52 | } 53 | } 54 | } 55 | 56 | impl From for u8 { 57 | fn from(val: SyncNbgl) -> u8 { 58 | match val { 59 | SyncNbgl::UxSyncRetApproved => 0x00, 60 | SyncNbgl::UxSyncRetRejected => 0x01, 61 | SyncNbgl::UxSyncRetQuitted => 0x02, 62 | SyncNbgl::UxSyncRetApduReceived => 0x03, 63 | SyncNbgl::UxSyncRetError => 0xFF, 64 | } 65 | } 66 | } 67 | 68 | static mut G_RET: u8 = 0; 69 | static mut G_ENDED: bool = false; 70 | 71 | trait SyncNBGL: Sized { 72 | fn ux_sync_init(&self) { 73 | unsafe { 74 | G_RET = SyncNbgl::UxSyncRetError.into(); 75 | G_ENDED = false; 76 | } 77 | } 78 | 79 | fn ux_sync_wait(&self, exit_on_apdu: bool) -> SyncNbgl { 80 | unsafe { 81 | if let Some(comm) = (*(&raw mut COMM_REF)).as_mut() { 82 | while !G_ENDED { 83 | let apdu_received = comm.next_event_ahead::(); 84 | if exit_on_apdu && apdu_received { 85 | return SyncNbgl::UxSyncRetApduReceived; 86 | } 87 | } 88 | return G_RET.into(); 89 | } else { 90 | panic!("COMM_REF not initialized"); 91 | } 92 | } 93 | } 94 | } 95 | 96 | unsafe extern "C" fn choice_callback(confirm: bool) { 97 | G_RET = if confirm { 98 | SyncNbgl::UxSyncRetApproved.into() 99 | } else { 100 | SyncNbgl::UxSyncRetRejected.into() 101 | }; 102 | G_ENDED = true; 103 | } 104 | 105 | unsafe extern "C" fn quit_callback() { 106 | G_RET = SyncNbgl::UxSyncRetQuitted.into(); 107 | G_ENDED = true; 108 | } 109 | 110 | #[cfg(any(target_os = "stax", target_os = "flex"))] 111 | unsafe extern "C" fn rejected_callback() { 112 | G_RET = SyncNbgl::UxSyncRetRejected.into(); 113 | G_ENDED = true; 114 | } 115 | 116 | pub struct Field<'a> { 117 | pub name: &'a str, 118 | pub value: &'a str, 119 | } 120 | 121 | struct CField { 122 | pub name: CString, 123 | pub value: CString, 124 | } 125 | 126 | pub struct NbglGlyph<'a> { 127 | pub width: u16, 128 | pub height: u16, 129 | pub bpp: u8, 130 | pub is_file: bool, 131 | pub bitmap: &'a [u8], 132 | } 133 | 134 | impl<'a> NbglGlyph<'a> { 135 | pub const fn new( 136 | bitmap: &'a [u8], 137 | width: u16, 138 | height: u16, 139 | bpp: u8, 140 | is_file: bool, 141 | ) -> NbglGlyph<'a> { 142 | NbglGlyph { 143 | width, 144 | height, 145 | bpp, 146 | is_file, 147 | bitmap, 148 | } 149 | } 150 | pub const fn from_include(packed: (&'a [u8], u16, u16, u8, bool)) -> NbglGlyph<'a> { 151 | NbglGlyph { 152 | width: packed.1, 153 | height: packed.2, 154 | bpp: packed.3, 155 | is_file: packed.4, 156 | bitmap: packed.0, 157 | } 158 | } 159 | } 160 | 161 | impl<'a> Into for &NbglGlyph<'a> { 162 | fn into(self) -> nbgl_icon_details_t { 163 | let bpp = match self.bpp { 164 | 1 => NBGL_BPP_1, 165 | 2 => NBGL_BPP_2, 166 | 4 => NBGL_BPP_4, 167 | _ => panic!("Invalid bpp"), 168 | }; 169 | nbgl_icon_details_t { 170 | width: self.width, 171 | height: self.height, 172 | bpp, 173 | isFile: self.is_file, 174 | bitmap: self.bitmap.as_ptr() as *const u8, 175 | } 176 | } 177 | } 178 | 179 | pub enum TransactionType { 180 | Transaction, 181 | Message, 182 | Operation, 183 | } 184 | 185 | pub enum StatusType { 186 | Transaction, 187 | Message, 188 | Operation, 189 | Address, 190 | } 191 | 192 | impl StatusType { 193 | fn transaction_type(&self) -> Option { 194 | match self { 195 | StatusType::Transaction => Some(TransactionType::Transaction), 196 | StatusType::Message => Some(TransactionType::Message), 197 | StatusType::Operation => Some(TransactionType::Operation), 198 | StatusType::Address => None, 199 | } 200 | } 201 | } 202 | 203 | trait ToMessage { 204 | fn to_message(&self, success: bool) -> nbgl_reviewStatusType_t; 205 | } 206 | 207 | impl TransactionType { 208 | fn to_c_type(&self, skippable: bool) -> nbgl_operationType_t { 209 | let mut tx_type = match self { 210 | TransactionType::Transaction => TYPE_TRANSACTION.into(), 211 | TransactionType::Message => TYPE_MESSAGE.into(), 212 | TransactionType::Operation => TYPE_OPERATION.into(), 213 | }; 214 | if skippable { 215 | tx_type |= SKIPPABLE_OPERATION; 216 | } 217 | tx_type 218 | } 219 | } 220 | 221 | impl ToMessage for TransactionType { 222 | fn to_message(&self, success: bool) -> nbgl_reviewStatusType_t { 223 | match (self, success) { 224 | (TransactionType::Transaction, true) => STATUS_TYPE_TRANSACTION_SIGNED, 225 | (TransactionType::Transaction, false) => STATUS_TYPE_TRANSACTION_REJECTED, 226 | (TransactionType::Message, true) => STATUS_TYPE_MESSAGE_SIGNED, 227 | (TransactionType::Message, false) => STATUS_TYPE_MESSAGE_REJECTED, 228 | (TransactionType::Operation, true) => STATUS_TYPE_OPERATION_SIGNED, 229 | (TransactionType::Operation, false) => STATUS_TYPE_OPERATION_REJECTED, 230 | } 231 | } 232 | } 233 | 234 | impl ToMessage for StatusType { 235 | fn to_message(&self, success: bool) -> nbgl_reviewStatusType_t { 236 | match self { 237 | StatusType::Address => { 238 | if success { 239 | STATUS_TYPE_ADDRESS_VERIFIED 240 | } else { 241 | STATUS_TYPE_ADDRESS_REJECTED 242 | } 243 | } 244 | _ => self 245 | .transaction_type() 246 | .expect("Should be a transaction type") 247 | .to_message(success), 248 | } 249 | } 250 | } 251 | 252 | /// Initialize the global COMM_REF variable with the provided Comm instance. 253 | /// This function should be called from the main function of the application. 254 | /// The COMM_REF variable is used by the NBGL API to detect touch events and 255 | /// APDU reception. 256 | pub fn init_comm(comm: &mut Comm) { 257 | unsafe { 258 | COMM_REF = Some(transmute(comm)); 259 | } 260 | } 261 | 262 | #[derive(Copy, Clone)] 263 | pub enum TuneIndex { 264 | Reserved, 265 | Boot, 266 | Charging, 267 | LedgerMoment, 268 | Error, 269 | Neutral, 270 | Lock, 271 | Success, 272 | LookAtMe, 273 | TapCasual, 274 | TapNext, 275 | } 276 | 277 | // Direct translation of the C original. This was done to 278 | // avoid compiling `os_io_seproxyhal.c` which includes many other things 279 | #[no_mangle] 280 | #[cfg(any(target_os = "stax", target_os = "flex"))] 281 | extern "C" fn io_seproxyhal_play_tune(tune_index: u8) { 282 | let mut buffer = [0u8; 4]; 283 | let mut spi_buffer = [0u8; 128]; 284 | 285 | if tune_index >= NB_TUNES { 286 | return; 287 | } 288 | 289 | let sound_setting = 290 | unsafe { os_setting_get(OS_SETTING_PIEZO_SOUND.into(), core::ptr::null_mut(), 0) }; 291 | 292 | if ((sound_setting & 2) == 2) && (tune_index < TUNE_TAP_CASUAL) { 293 | return; 294 | } 295 | 296 | if ((sound_setting & 1) == 1) && (tune_index >= TUNE_TAP_CASUAL) { 297 | return; 298 | } 299 | 300 | if seph::is_status_sent() { 301 | seph::seph_recv(&mut spi_buffer, 0); 302 | } 303 | 304 | buffer[0] = SEPROXYHAL_TAG_PLAY_TUNE as u8; 305 | buffer[1] = 0; 306 | buffer[2] = 1; 307 | buffer[3] = tune_index; 308 | seph::seph_send(&buffer); 309 | } 310 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/nbgl/nbgl_address_review.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// A wrapper around the asynchronous NBGL nbgl_useCaseAddressReview C API binding. 4 | /// Used to display address confirmation screens. 5 | pub struct NbglAddressReview<'a> { 6 | glyph: Option<&'a NbglGlyph<'a>>, 7 | verify_str: CString, 8 | } 9 | 10 | impl SyncNBGL for NbglAddressReview<'_> {} 11 | 12 | impl<'a> NbglAddressReview<'a> { 13 | pub fn new() -> NbglAddressReview<'a> { 14 | NbglAddressReview { 15 | verify_str: CString::default(), 16 | glyph: None, 17 | } 18 | } 19 | 20 | pub fn glyph(self, glyph: &'a NbglGlyph) -> NbglAddressReview<'a> { 21 | NbglAddressReview { 22 | glyph: Some(glyph), 23 | ..self 24 | } 25 | } 26 | 27 | pub fn verify_str(self, verify_str: &str) -> NbglAddressReview<'a> { 28 | NbglAddressReview { 29 | verify_str: CString::new(verify_str).unwrap(), 30 | ..self 31 | } 32 | } 33 | 34 | pub fn show(&self, address: &str) -> bool { 35 | unsafe { 36 | let icon: nbgl_icon_details_t = match self.glyph { 37 | Some(g) => g.into(), 38 | None => nbgl_icon_details_t::default(), 39 | }; 40 | 41 | let address = CString::new(address).unwrap(); 42 | 43 | self.ux_sync_init(); 44 | nbgl_useCaseAddressReview( 45 | address.as_ptr(), 46 | core::ptr::null(), 47 | &icon as *const nbgl_icon_details_t, 48 | match self.verify_str.is_empty() { 49 | true => core::ptr::null(), 50 | false => self.verify_str.as_ptr(), 51 | }, 52 | core::ptr::null(), 53 | Some(choice_callback), 54 | ); 55 | let sync_ret = self.ux_sync_wait(false); 56 | 57 | // Return true if the user approved the address, false otherwise. 58 | match sync_ret { 59 | SyncNbgl::UxSyncRetApproved => { 60 | return true; 61 | } 62 | SyncNbgl::UxSyncRetRejected => { 63 | return false; 64 | } 65 | _ => { 66 | panic!("Unexpected return value from ux_sync_addressReview"); 67 | } 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/nbgl/nbgl_choice.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | const WARNING_64PX_BITMAP: [u8; 359] = [ 4 | 0x40, 0x00, 0x40, 0x00, 0x21, 0x5f, 0x01, 0x00, 0x5d, 0x01, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 5 | 0x00, 0x00, 0x02, 0xff, 0x85, 0xd5, 0x31, 0x4e, 0xc3, 0x40, 0x10, 0x05, 0xd0, 0x8d, 0x85, 0x08, 6 | 0x4a, 0x45, 0x49, 0x91, 0xc2, 0x25, 0xb2, 0x52, 0xe4, 0x08, 0x39, 0x40, 0xc4, 0x19, 0x72, 0x82, 7 | 0x5c, 0x81, 0x84, 0x03, 0x80, 0xb8, 0x41, 0xc4, 0x05, 0x72, 0x04, 0x0a, 0xd2, 0xe7, 0x08, 0x69, 8 | 0x02, 0xa2, 0x0b, 0xa5, 0x13, 0xa1, 0x1d, 0xec, 0x99, 0x5d, 0xb3, 0xf6, 0xee, 0x1f, 0x6f, 0xfb, 9 | 0xc6, 0x96, 0xbc, 0xfe, 0x33, 0x43, 0x84, 0x4f, 0xb9, 0x27, 0xed, 0x5c, 0xf2, 0xeb, 0x93, 0xc2, 10 | 0xbf, 0x53, 0x63, 0x26, 0x0a, 0xcf, 0x8c, 0x31, 0x23, 0x9d, 0xb1, 0x5b, 0x66, 0xe8, 0x76, 0x51, 11 | 0xe1, 0x60, 0x06, 0x7d, 0x5d, 0x3f, 0xfc, 0xb8, 0x41, 0xce, 0xbc, 0x24, 0xe4, 0xcc, 0x73, 0x42, 12 | 0xfe, 0xea, 0x18, 0xf8, 0x9b, 0xe7, 0xb4, 0xef, 0x6a, 0x96, 0x7b, 0x4b, 0x39, 0x73, 0x41, 0xc8, 13 | 0x99, 0xef, 0x08, 0xf9, 0x31, 0xe4, 0xd8, 0xbf, 0x5a, 0x1c, 0x79, 0x79, 0x5b, 0xf1, 0x90, 0x90, 14 | 0x33, 0x87, 0x91, 0x68, 0x3b, 0xf3, 0x55, 0x18, 0xa9, 0x96, 0x9f, 0x23, 0x6e, 0xf9, 0x25, 0x8f, 15 | 0x38, 0x74, 0x8e, 0x4b, 0xd6, 0xc9, 0xeb, 0xbf, 0x0b, 0x6f, 0x09, 0x38, 0xf3, 0xe0, 0x85, 0x80, 16 | 0xdb, 0x34, 0x7b, 0x97, 0xb0, 0xc5, 0xec, 0x5d, 0xc2, 0x46, 0xc8, 0x5d, 0xd8, 0x90, 0xaf, 0x9b, 17 | 0xb8, 0x94, 0xc6, 0x9f, 0x1b, 0xfa, 0x31, 0x46, 0xdc, 0x36, 0x8c, 0x7d, 0x4e, 0xba, 0x17, 0x3d, 18 | 0xae, 0xbd, 0x5f, 0xe2, 0xbc, 0xc4, 0x6e, 0x9f, 0xf4, 0xef, 0x93, 0xeb, 0x4b, 0x16, 0x84, 0xf7, 19 | 0x9b, 0x2a, 0x68, 0xfe, 0x4f, 0x5d, 0x90, 0x3d, 0x43, 0x77, 0x05, 0x5b, 0xe8, 0x7d, 0xf9, 0x70, 20 | 0xf1, 0xeb, 0x16, 0x44, 0xf9, 0xcc, 0xde, 0xa1, 0xbb, 0x00, 0x1f, 0xa0, 0xd3, 0x39, 0xef, 0xb4, 21 | 0x4f, 0xb7, 0xbf, 0xbe, 0xf5, 0xfe, 0x72, 0xed, 0x1b, 0x14, 0xa4, 0xfb, 0x7b, 0x78, 0x82, 0x2e, 22 | 0x05, 0xf7, 0xd8, 0xe9, 0x33, 0x18, 0x3f, 0xc9, 0xf9, 0xf4, 0xa1, 0xcf, 0x27, 0x97, 0x97, 0x02, 23 | 0xbb, 0x14, 0x4c, 0xb0, 0x4b, 0xc1, 0x03, 0x76, 0xf2, 0x89, 0x43, 0x6e, 0x57, 0xfa, 0x7c, 0xf7, 24 | 0x89, 0xdb, 0xe8, 0xfb, 0xc5, 0x4c, 0x95, 0xfd, 0xb4, 0x50, 0xf7, 0x93, 0x5f, 0x60, 0x23, 0x65, 25 | 0x3f, 0xe6, 0xba, 0x73, 0xa0, 0xc6, 0x3d, 0xfb, 0x99, 0xf4, 0x05, 0x5e, 0xcd, 0xcb, 0x3f, 0xe4, 26 | 0x2a, 0x7b, 0x24, 0x00, 0x08, 0x00, 0x00, 27 | ]; 28 | 29 | /// A wrapper around the asynchronous NBGL nbgl_useCaseChoice C API binding. 30 | /// Draws a generic choice page, described in a centered info (with configurable icon), 31 | /// thanks to a button and a footer at the bottom of the page. 32 | pub struct NbglChoice<'a> { 33 | glyph: Option<&'a NbglGlyph<'a>>, 34 | } 35 | 36 | impl SyncNBGL for NbglChoice<'_> {} 37 | 38 | impl<'a> NbglChoice<'a> { 39 | pub fn new() -> NbglChoice<'a> { 40 | NbglChoice { glyph: None } 41 | } 42 | 43 | pub fn glyph(self, glyph: &'a NbglGlyph) -> NbglChoice<'a> { 44 | NbglChoice { 45 | glyph: Some(glyph), 46 | ..self 47 | } 48 | } 49 | 50 | pub fn show( 51 | &self, 52 | message: &str, 53 | sub_message: &str, 54 | confirm_text: &str, 55 | cancel_text: &str, 56 | ) -> bool { 57 | unsafe { 58 | let icon: nbgl_icon_details_t = match self.glyph { 59 | Some(g) => g.into(), 60 | None => nbgl_icon_details_t { 61 | width: 64, 62 | height: 64, 63 | bpp: NBGL_BPP_4, 64 | isFile: true, 65 | bitmap: &WARNING_64PX_BITMAP as *const u8, 66 | }, 67 | }; 68 | let message = CString::new(message).unwrap(); 69 | let sub_message = CString::new(sub_message).unwrap(); 70 | let confirm_text = CString::new(confirm_text).unwrap(); 71 | let cancel_text = CString::new(cancel_text).unwrap(); 72 | 73 | self.ux_sync_init(); 74 | nbgl_useCaseChoice( 75 | &icon as *const nbgl_icon_details_t, 76 | message.as_ptr() as *const c_char, 77 | sub_message.as_ptr() as *const c_char, 78 | confirm_text.as_ptr() as *const c_char, 79 | cancel_text.as_ptr() as *const c_char, 80 | Some(choice_callback), 81 | ); 82 | let sync_ret = self.ux_sync_wait(false); 83 | 84 | // Return true if the user approved the transaction, false otherwise. 85 | match sync_ret { 86 | SyncNbgl::UxSyncRetApproved => { 87 | return true; 88 | } 89 | _ => { 90 | return false; 91 | } 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/nbgl/nbgl_review.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// A wrapper around the asynchronous NBGL nbgl_useCaseReview C API binding. 4 | /// Used to display transaction review screens. 5 | pub struct NbglReview<'a> { 6 | title: CString, 7 | subtitle: CString, 8 | finish_title: CString, 9 | glyph: Option<&'a NbglGlyph<'a>>, 10 | tx_type: TransactionType, 11 | blind: bool, 12 | light: bool, 13 | } 14 | 15 | impl SyncNBGL for NbglReview<'_> {} 16 | 17 | impl<'a> NbglReview<'a> { 18 | pub fn new() -> NbglReview<'a> { 19 | NbglReview { 20 | title: CString::default(), 21 | subtitle: CString::default(), 22 | finish_title: CString::default(), 23 | glyph: None, 24 | tx_type: TransactionType::Transaction, 25 | blind: false, 26 | light: false, 27 | } 28 | } 29 | 30 | pub fn tx_type(self, tx_type: TransactionType) -> NbglReview<'a> { 31 | NbglReview { tx_type, ..self } 32 | } 33 | 34 | pub fn blind(self) -> NbglReview<'a> { 35 | NbglReview { 36 | blind: true, 37 | ..self 38 | } 39 | } 40 | 41 | pub fn light(self) -> NbglReview<'a> { 42 | NbglReview { 43 | light: true, 44 | ..self 45 | } 46 | } 47 | 48 | pub fn titles( 49 | self, 50 | title: &'a str, 51 | subtitle: &'a str, 52 | finish_title: &'a str, 53 | ) -> NbglReview<'a> { 54 | NbglReview { 55 | title: CString::new(title).unwrap(), 56 | subtitle: CString::new(subtitle).unwrap(), 57 | finish_title: CString::new(finish_title).unwrap(), 58 | ..self 59 | } 60 | } 61 | 62 | pub fn glyph(self, glyph: &'a NbglGlyph) -> NbglReview<'a> { 63 | NbglReview { 64 | glyph: Some(glyph), 65 | ..self 66 | } 67 | } 68 | 69 | pub fn show(&self, fields: &[Field]) -> bool { 70 | unsafe { 71 | let v: Vec = fields 72 | .iter() 73 | .map(|f| CField { 74 | name: CString::new(f.name).unwrap(), 75 | value: CString::new(f.value).unwrap(), 76 | }) 77 | .collect(); 78 | 79 | // Fill the tag_value_array with the fields converted to nbgl_contentTagValue_t 80 | let mut tag_value_array: Vec = Vec::new(); 81 | for field in v.iter() { 82 | let val = nbgl_contentTagValue_t { 83 | item: field.name.as_ptr() as *const i8, 84 | value: field.value.as_ptr() as *const i8, 85 | ..Default::default() 86 | }; 87 | tag_value_array.push(val); 88 | } 89 | 90 | // Create the tag_value_list with the tag_value_array. 91 | let tag_value_list = nbgl_contentTagValueList_t { 92 | pairs: tag_value_array.as_ptr() as *const nbgl_contentTagValue_t, 93 | nbPairs: fields.len() as u8, 94 | ..Default::default() 95 | }; 96 | 97 | let icon: nbgl_icon_details_t = match self.glyph { 98 | Some(g) => g.into(), 99 | None => nbgl_icon_details_t::default(), 100 | }; 101 | 102 | // Show the review on the device. 103 | self.ux_sync_init(); 104 | match self.blind { 105 | true => { 106 | nbgl_useCaseReviewBlindSigning( 107 | self.tx_type.to_c_type(false), 108 | &tag_value_list as *const nbgl_contentTagValueList_t, 109 | &icon as *const nbgl_icon_details_t, 110 | self.title.as_ptr() as *const c_char, 111 | self.subtitle.as_ptr() as *const c_char, 112 | self.finish_title.as_ptr() as *const c_char, 113 | core::ptr::null(), 114 | Some(choice_callback), 115 | ); 116 | } 117 | false => { 118 | if self.light { 119 | nbgl_useCaseReviewLight( 120 | self.tx_type.to_c_type(false), 121 | &tag_value_list as *const nbgl_contentTagValueList_t, 122 | &icon as *const nbgl_icon_details_t, 123 | self.title.as_ptr() as *const c_char, 124 | self.subtitle.as_ptr() as *const c_char, 125 | self.finish_title.as_ptr() as *const c_char, 126 | Some(choice_callback), 127 | ); 128 | } else { 129 | nbgl_useCaseReview( 130 | self.tx_type.to_c_type(false), 131 | &tag_value_list as *const nbgl_contentTagValueList_t, 132 | &icon as *const nbgl_icon_details_t, 133 | match self.title.is_empty() { 134 | true => core::ptr::null(), 135 | false => self.title.as_ptr() as *const c_char, 136 | }, 137 | match self.subtitle.is_empty() { 138 | true => core::ptr::null(), 139 | false => self.subtitle.as_ptr() as *const c_char, 140 | }, 141 | match self.finish_title.is_empty() { 142 | true => core::ptr::null(), 143 | false => self.finish_title.as_ptr() as *const c_char, 144 | }, 145 | Some(choice_callback), 146 | ); 147 | } 148 | } 149 | } 150 | let sync_ret = self.ux_sync_wait(false); 151 | 152 | // Return true if the user approved the transaction, false otherwise. 153 | match sync_ret { 154 | SyncNbgl::UxSyncRetApproved => { 155 | return true; 156 | } 157 | _ => { 158 | return false; 159 | } 160 | } 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/nbgl/nbgl_review_status.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// A wrapper around the synchronous NBGL ux_sync_reviewStatus C API binding. 4 | /// Draws a transient (3s) status page of the chosen type. 5 | pub struct NbglReviewStatus { 6 | status_type: StatusType, 7 | } 8 | 9 | impl SyncNBGL for NbglReviewStatus {} 10 | 11 | impl<'a> NbglReviewStatus { 12 | pub fn new() -> NbglReviewStatus { 13 | NbglReviewStatus { 14 | status_type: StatusType::Transaction, 15 | } 16 | } 17 | 18 | pub fn status_type(self, status_type: StatusType) -> NbglReviewStatus { 19 | NbglReviewStatus { status_type } 20 | } 21 | 22 | pub fn show(&self, success: bool) { 23 | unsafe { 24 | self.ux_sync_init(); 25 | nbgl_useCaseReviewStatus(self.status_type.to_message(success), Some(quit_callback)); 26 | self.ux_sync_wait(false); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/nbgl/nbgl_spinner.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | extern crate alloc; 3 | use alloc::ffi::CString; 4 | 5 | /// A wrapper around the asynchronous NBGL nbgl_useCaseSpinner C API binding. 6 | /// Draws a spinner page with the given parameters. The spinner will "turn" automatically every 7 | /// 800 ms, provided the IO event loop is running to process TickerEvents. 8 | #[derive(Debug, Default)] 9 | pub struct NbglSpinner { 10 | text: [CString; 2], 11 | write_idx: usize, 12 | read_idx: usize, 13 | } 14 | 15 | impl NbglSpinner { 16 | pub fn new() -> NbglSpinner { 17 | NbglSpinner { 18 | text: [CString::default(), CString::default()], 19 | write_idx: 0, 20 | read_idx: 0, 21 | } 22 | } 23 | 24 | /// Shows the spinner with the current text. 25 | /// Every call make the spinner "turn" to the next text. 26 | pub fn show(&mut self, text: &str) { 27 | self.text[self.write_idx] = CString::new(text).unwrap(); 28 | self.read_idx = self.write_idx; 29 | self.write_idx = (self.write_idx + 1) % 2; 30 | unsafe { 31 | nbgl_useCaseSpinner(self.text[self.read_idx].as_ptr() as *const c_char); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/nbgl/nbgl_status.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// A wrapper around the asynchronous NBGL ux_sync_status C API binding. 4 | /// Draws a transient (3s) status page, either of success or failure, with the given message 5 | pub struct NbglStatus { 6 | text: CString, 7 | } 8 | 9 | impl SyncNBGL for NbglStatus {} 10 | 11 | impl NbglStatus { 12 | pub fn new() -> NbglStatus { 13 | NbglStatus { 14 | text: CString::new("").unwrap(), 15 | } 16 | } 17 | 18 | pub fn text(self, text: &str) -> NbglStatus { 19 | NbglStatus { 20 | text: CString::new(text).unwrap(), 21 | } 22 | } 23 | 24 | pub fn show(&self, success: bool) { 25 | unsafe { 26 | self.ux_sync_init(); 27 | nbgl_useCaseStatus( 28 | self.text.as_ptr() as *const c_char, 29 | success, 30 | Some(quit_callback), 31 | ); 32 | self.ux_sync_wait(false); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/nbgl/nbgl_streaming_review.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// A wrapper around the asynchronous NBGL nbgl_useCaseReviewStreamingStart/Continue/Finish) 4 | /// C API binding. Used to display streamed transaction review screens. 5 | pub struct NbglStreamingReview { 6 | icon: nbgl_icon_details_t, 7 | tx_type: TransactionType, 8 | blind: bool, 9 | } 10 | 11 | impl SyncNBGL for NbglStreamingReview {} 12 | 13 | impl NbglStreamingReview { 14 | pub fn new() -> NbglStreamingReview { 15 | NbglStreamingReview { 16 | icon: nbgl_icon_details_t::default(), 17 | tx_type: TransactionType::Transaction, 18 | blind: false, 19 | } 20 | } 21 | 22 | pub fn tx_type(self, tx_type: TransactionType) -> NbglStreamingReview { 23 | NbglStreamingReview { tx_type, ..self } 24 | } 25 | 26 | pub fn blind(self) -> NbglStreamingReview { 27 | NbglStreamingReview { 28 | blind: true, 29 | ..self 30 | } 31 | } 32 | 33 | pub fn glyph(self, glyph: &NbglGlyph) -> NbglStreamingReview { 34 | NbglStreamingReview { 35 | icon: glyph.into(), 36 | ..self 37 | } 38 | } 39 | 40 | pub fn start(&self, title: &str, subtitle: &str) -> bool { 41 | unsafe { 42 | let title = CString::new(title).unwrap(); 43 | let subtitle = CString::new(subtitle).unwrap(); 44 | 45 | self.ux_sync_init(); 46 | match self.blind { 47 | true => { 48 | nbgl_useCaseReviewStreamingBlindSigningStart( 49 | self.tx_type.to_c_type(false), 50 | &self.icon as *const nbgl_icon_details_t, 51 | title.as_ptr() as *const c_char, 52 | subtitle.as_ptr() as *const c_char, 53 | Some(choice_callback), 54 | ); 55 | } 56 | false => { 57 | nbgl_useCaseReviewStreamingStart( 58 | self.tx_type.to_c_type(false), 59 | &self.icon as *const nbgl_icon_details_t, 60 | title.as_ptr() as *const c_char, 61 | subtitle.as_ptr() as *const c_char, 62 | Some(choice_callback), 63 | ); 64 | } 65 | } 66 | let sync_ret = self.ux_sync_wait(false); 67 | 68 | // Return true if the user approved the transaction, false otherwise. 69 | match sync_ret { 70 | SyncNbgl::UxSyncRetApproved => { 71 | return true; 72 | } 73 | _ => { 74 | return false; 75 | } 76 | } 77 | } 78 | } 79 | 80 | pub fn continue_review(&self, fields: &[Field]) -> bool { 81 | unsafe { 82 | let v: Vec = fields 83 | .iter() 84 | .map(|f| CField { 85 | name: CString::new(f.name).unwrap(), 86 | value: CString::new(f.value).unwrap(), 87 | }) 88 | .collect(); 89 | 90 | // Fill the tag_value_array with the fields converted to nbgl_contentTagValue_t 91 | let mut tag_value_array: Vec = Vec::new(); 92 | for field in v.iter() { 93 | let val = nbgl_contentTagValue_t { 94 | item: field.name.as_ptr() as *const i8, 95 | value: field.value.as_ptr() as *const i8, 96 | ..Default::default() 97 | }; 98 | tag_value_array.push(val); 99 | } 100 | 101 | // Create the tag_value_list with the tag_value_array. 102 | let tag_value_list = nbgl_contentTagValueList_t { 103 | pairs: tag_value_array.as_ptr() as *const nbgl_contentTagValue_t, 104 | nbPairs: fields.len() as u8, 105 | ..Default::default() 106 | }; 107 | 108 | self.ux_sync_init(); 109 | nbgl_useCaseReviewStreamingContinue( 110 | &tag_value_list as *const nbgl_contentTagValueList_t, 111 | Some(choice_callback), 112 | ); 113 | let sync_ret = self.ux_sync_wait(false); 114 | 115 | // Return true if the user approved the transaction, false otherwise. 116 | match sync_ret { 117 | SyncNbgl::UxSyncRetApproved => { 118 | return true; 119 | } 120 | _ => { 121 | return false; 122 | } 123 | } 124 | } 125 | } 126 | 127 | pub fn finish(&self, finish_title: &str) -> bool { 128 | unsafe { 129 | let finish_title = CString::new(finish_title).unwrap(); 130 | 131 | self.ux_sync_init(); 132 | nbgl_useCaseReviewStreamingFinish( 133 | finish_title.as_ptr() as *const c_char, 134 | Some(choice_callback), 135 | ); 136 | let sync_ret = self.ux_sync_wait(false); 137 | 138 | // Return true if the user approved the transaction, false otherwise. 139 | match sync_ret { 140 | SyncNbgl::UxSyncRetApproved => { 141 | return true; 142 | } 143 | _ => { 144 | return false; 145 | } 146 | } 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/random.rs: -------------------------------------------------------------------------------- 1 | //! Random number generation functions 2 | 3 | use core::ops::Range; 4 | 5 | use num_traits::{Bounded, PrimInt, Unsigned}; 6 | use rand_core::{CryptoRng, RngCore}; 7 | 8 | /// Fills a byte array with random bytes. 9 | /// 10 | /// # Arguments 11 | /// 12 | /// * `out` - Destination array. 13 | #[inline] 14 | pub fn rand_bytes(out: &mut [u8]) { 15 | unsafe { 16 | ledger_secure_sdk_sys::cx_rng_no_throw(out.as_mut_ptr(), out.len()); 17 | } 18 | } 19 | 20 | /// In-house random trait for generating random numbers. 21 | pub trait Random 22 | where 23 | Self: PrimInt + Unsigned + Bounded, 24 | { 25 | /// Generates a random value. 26 | fn random() -> Self; 27 | 28 | /// Generates and returns a random number in the given range 29 | /// 30 | /// # Arguments 31 | /// 32 | /// * `range` - range bounded inclusively below and exclusively above. Empty 33 | /// ranges are not allowed and will cause panic. 34 | /// 35 | /// # Example 36 | /// 37 | /// ``` 38 | /// // Roll a dice 39 | /// let r = random_from_range::(1..7); 40 | /// ``` 41 | /// 42 | fn random_from_range(range: Range) -> Self { 43 | assert!(range.end > range.start, "Invalid range"); 44 | let width = range.end - range.start; 45 | 46 | if width & (width - Self::one()) == Self::zero() { 47 | // Special case: range is a power of 2 48 | // Result is very fast to calculate. 49 | range.start + Self::random() % width 50 | } else { 51 | let chunk_size = Self::max_value() / width; 52 | let last_chunk_value = chunk_size * width; 53 | let mut r = Self::random(); 54 | while r >= last_chunk_value { 55 | r = Self::random(); 56 | } 57 | range.start + r / chunk_size 58 | } 59 | } 60 | } 61 | 62 | impl Random for u8 { 63 | fn random() -> Self { 64 | let mut r = [0u8; 1]; 65 | rand_bytes(&mut r); 66 | r[0] 67 | } 68 | } 69 | 70 | impl Random for u32 { 71 | fn random() -> Self { 72 | let mut r = [0u8; 4]; 73 | rand_bytes(&mut r); 74 | u32::from_be_bytes(r) 75 | } 76 | } 77 | 78 | /// [`RngCore`] implementation via the [`rand_bytes`] syscall 79 | #[derive(Copy, Clone, Debug)] 80 | pub struct LedgerRng; 81 | 82 | /// Implement [`RngCore`] (for `rand_core@0.6.x`) using ledger syscalls 83 | /// 84 | /// For backwards compatibility with `rand_core@0.5.x` see [rand_compat](https://docs.rs/rand-compat/latest/rand_compat/) 85 | impl RngCore for LedgerRng { 86 | #[inline] 87 | fn next_u32(&mut self) -> u32 { 88 | let mut b = [0u8; 4]; 89 | rand_bytes(&mut b); 90 | u32::from_be_bytes(b) 91 | } 92 | 93 | #[inline] 94 | fn next_u64(&mut self) -> u64 { 95 | let mut b = [0u8; 8]; 96 | rand_bytes(&mut b); 97 | u64::from_be_bytes(b) 98 | } 99 | 100 | #[inline] 101 | fn fill_bytes(&mut self, dest: &mut [u8]) { 102 | rand_bytes(dest); 103 | } 104 | 105 | #[inline] 106 | fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { 107 | self.fill_bytes(dest); 108 | Ok(()) 109 | } 110 | } 111 | 112 | /// Mark LedgerRng as safe for cryptographic use 113 | impl CryptoRng for LedgerRng {} 114 | 115 | #[cfg(test)] 116 | mod tests { 117 | use super::*; 118 | use crate::assert_eq_err as assert_eq; 119 | use crate::testing::TestType; 120 | use testmacro::test_item as test; 121 | 122 | #[test] 123 | fn rng() { 124 | // Test that the bindings are not broken by checking a random u128 125 | // isn't 0 (has 1/2^128 of happening) 126 | let r: [u8; 16] = core::array::from_fn(|_| u8::random()); 127 | assert_eq!(u128::from_be_bytes(r) != 0, true); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/screen.rs: -------------------------------------------------------------------------------- 1 | extern "C" { 2 | fn screen_clear(); 3 | fn screen_update(); 4 | fn screen_set_keepout(x: u32, y: u32, width: u32, height: u32); 5 | fn bagl_hal_draw_bitmap_within_rect( 6 | x: i32, 7 | y: i32, 8 | width: u32, 9 | height: u32, 10 | color_count: u32, 11 | colors: *const u32, 12 | bit_per_pixel: u32, 13 | bitmap: *const u8, 14 | bitmap_length_bits: u32, 15 | ); 16 | fn bagl_hal_draw_rect(color: u32, x: i32, y: i32, width: u32, height: u32); 17 | } 18 | 19 | pub fn sdk_screen_clear() { 20 | unsafe { 21 | screen_clear(); 22 | } 23 | } 24 | 25 | pub fn sdk_set_keepout(x: u32, y: u32, width: u32, height: u32) { 26 | unsafe { 27 | screen_set_keepout(x, y, width, height); 28 | } 29 | } 30 | 31 | pub fn sdk_screen_update() { 32 | unsafe { 33 | screen_update(); 34 | } 35 | } 36 | 37 | pub fn sdk_bagl_hal_draw_bitmap_within_rect( 38 | x: i32, 39 | y: i32, 40 | width: u32, 41 | height: u32, 42 | inverted: bool, 43 | bitmap: &[u8], 44 | ) { 45 | let inverted = [inverted as u32, !inverted as u32]; 46 | unsafe { 47 | bagl_hal_draw_bitmap_within_rect( 48 | x, 49 | y, 50 | width, 51 | height, 52 | 2, 53 | inverted.as_ptr(), 54 | 1, 55 | bitmap.as_ptr(), 56 | width * height, 57 | ) 58 | } 59 | } 60 | 61 | pub fn sdk_bagl_hal_draw_rect(color: u32, x: i32, y: i32, width: u32, height: u32) { 62 | unsafe { 63 | bagl_hal_draw_rect(color, x, y, width, height); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/seph.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::upper_case_acronyms)] 2 | 3 | use ledger_secure_sdk_sys::*; 4 | 5 | #[cfg(any(target_os = "nanox", target_os = "stax", target_os = "flex"))] 6 | use crate::ble; 7 | 8 | #[repr(u8)] 9 | pub enum Events { 10 | USBXFEREvent = SEPROXYHAL_TAG_USB_EP_XFER_EVENT as u8, 11 | USBEvent = SEPROXYHAL_TAG_USB_EVENT as u8, 12 | USBEventReset = SEPROXYHAL_TAG_USB_EVENT_RESET as u8, 13 | USBEventSOF = SEPROXYHAL_TAG_USB_EVENT_SOF as u8, 14 | USBEventSuspend = SEPROXYHAL_TAG_USB_EVENT_SUSPENDED as u8, 15 | USBEventResume = SEPROXYHAL_TAG_USB_EVENT_RESUMED as u8, 16 | CAPDUEvent = SEPROXYHAL_TAG_CAPDU_EVENT as u8, 17 | TickerEvent = SEPROXYHAL_TAG_TICKER_EVENT as u8, 18 | ButtonPush = SEPROXYHAL_TAG_BUTTON_PUSH_EVENT as u8, 19 | DisplayProcessed = SEPROXYHAL_TAG_DISPLAY_PROCESSED_EVENT as u8, 20 | BleReceive = SEPROXYHAL_TAG_BLE_RECV_EVENT as u8, 21 | ScreenTouch = SEPROXYHAL_TAG_FINGER_EVENT as u8, 22 | Unknown = 0xff, 23 | } 24 | #[repr(u8)] 25 | pub enum UsbEp { 26 | USBEpXFERSetup = SEPROXYHAL_TAG_USB_EP_XFER_SETUP as u8, 27 | USBEpXFERIn = SEPROXYHAL_TAG_USB_EP_XFER_IN as u8, 28 | USBEpXFEROut = SEPROXYHAL_TAG_USB_EP_XFER_OUT as u8, 29 | USBEpPrepare = SEPROXYHAL_TAG_USB_EP_PREPARE as u8, 30 | USBEpPrepareDirIn = SEPROXYHAL_TAG_USB_EP_PREPARE_DIR_IN as u8, 31 | Unknown, 32 | } 33 | 34 | impl From for Events { 35 | fn from(v: u8) -> Events { 36 | match v as u32 { 37 | SEPROXYHAL_TAG_USB_EP_XFER_EVENT => Events::USBXFEREvent, 38 | SEPROXYHAL_TAG_USB_EVENT => Events::USBEvent, 39 | SEPROXYHAL_TAG_USB_EVENT_RESET => Events::USBEventReset, 40 | SEPROXYHAL_TAG_USB_EVENT_SOF => Events::USBEventSOF, 41 | SEPROXYHAL_TAG_USB_EVENT_SUSPENDED => Events::USBEventSuspend, 42 | SEPROXYHAL_TAG_USB_EVENT_RESUMED => Events::USBEventResume, 43 | SEPROXYHAL_TAG_CAPDU_EVENT => Events::CAPDUEvent, 44 | SEPROXYHAL_TAG_TICKER_EVENT => Events::TickerEvent, 45 | SEPROXYHAL_TAG_BUTTON_PUSH_EVENT => Events::ButtonPush, 46 | SEPROXYHAL_TAG_DISPLAY_PROCESSED_EVENT => Events::DisplayProcessed, 47 | SEPROXYHAL_TAG_BLE_RECV_EVENT => Events::BleReceive, 48 | SEPROXYHAL_TAG_FINGER_EVENT => Events::ScreenTouch, 49 | _ => Events::Unknown, 50 | } 51 | } 52 | } 53 | 54 | impl From for UsbEp { 55 | fn from(v: u8) -> UsbEp { 56 | match v as u32 { 57 | SEPROXYHAL_TAG_USB_EP_XFER_SETUP => UsbEp::USBEpXFERSetup, 58 | SEPROXYHAL_TAG_USB_EP_XFER_IN => UsbEp::USBEpXFERIn, 59 | SEPROXYHAL_TAG_USB_EP_XFER_OUT => UsbEp::USBEpXFEROut, 60 | SEPROXYHAL_TAG_USB_EP_PREPARE => UsbEp::USBEpPrepare, 61 | SEPROXYHAL_TAG_USB_EP_PREPARE_DIR_IN => UsbEp::USBEpPrepareDirIn, 62 | _ => UsbEp::Unknown, 63 | } 64 | } 65 | } 66 | 67 | /// FFI bindings to USBD functions inlined here for clarity 68 | /// and also because some of the generated ones are incorrectly 69 | /// assuming mutable pointers when they are not 70 | #[repr(C)] 71 | #[derive(Copy, Clone)] 72 | pub struct apdu_buffer_s { 73 | pub buf: *mut u8, 74 | pub len: u16, 75 | } 76 | impl Default for apdu_buffer_s { 77 | fn default() -> Self { 78 | unsafe { ::core::mem::zeroed() } 79 | } 80 | } 81 | pub type ApduBufferT = apdu_buffer_s; 82 | extern "C" { 83 | pub static mut USBD_Device: USBD_HandleTypeDef; 84 | pub fn USBD_LL_SetupStage( 85 | pdev: *mut USBD_HandleTypeDef, 86 | psetup: *const u8, 87 | ) -> USBD_StatusTypeDef; 88 | pub fn USBD_LL_DataOutStage( 89 | pdev: *mut USBD_HandleTypeDef, 90 | epnum: u8, 91 | pdata: *const u8, 92 | arg1: *mut ApduBufferT, 93 | ) -> USBD_StatusTypeDef; 94 | pub fn USBD_LL_DataInStage( 95 | pdev: *mut USBD_HandleTypeDef, 96 | epnum: u8, 97 | pdata: *const u8, 98 | ) -> USBD_StatusTypeDef; 99 | pub fn USBD_LL_Reset(pdev: *mut USBD_HandleTypeDef) -> USBD_StatusTypeDef; 100 | pub fn USBD_LL_SetSpeed( 101 | pdev: *mut USBD_HandleTypeDef, 102 | speed: USBD_SpeedTypeDef, 103 | ) -> USBD_StatusTypeDef; 104 | pub fn USBD_LL_Suspend(pdev: *mut USBD_HandleTypeDef) -> USBD_StatusTypeDef; 105 | pub fn USBD_LL_Resume(pdev: *mut USBD_HandleTypeDef) -> USBD_StatusTypeDef; 106 | pub fn USBD_LL_SOF(pdev: *mut USBD_HandleTypeDef) -> USBD_StatusTypeDef; 107 | } 108 | 109 | /// Below is a straightforward translation of the corresponding functions 110 | /// in the C SDK, they could be improved 111 | pub fn handle_usb_event(event: u8) { 112 | match Events::from(event) { 113 | Events::USBEventReset => { 114 | unsafe { 115 | USBD_LL_SetSpeed(&raw mut USBD_Device, 1 /*USBD_SPEED_FULL*/); 116 | USBD_LL_Reset(&raw mut USBD_Device); 117 | 118 | if G_io_app.apdu_media != IO_APDU_MEDIA_NONE { 119 | return; 120 | } 121 | 122 | G_io_app.usb_ep_xfer_len = core::mem::zeroed(); 123 | G_io_app.usb_ep_timeouts = core::mem::zeroed(); 124 | } 125 | } 126 | Events::USBEventSOF => unsafe { 127 | USBD_LL_SOF(&raw mut USBD_Device); 128 | }, 129 | Events::USBEventSuspend => unsafe { 130 | USBD_LL_Suspend(&raw mut USBD_Device); 131 | }, 132 | Events::USBEventResume => unsafe { 133 | USBD_LL_Resume(&raw mut USBD_Device); 134 | }, 135 | _ => (), 136 | } 137 | } 138 | 139 | pub fn handle_usb_ep_xfer_event(apdu_buffer: &mut [u8], buffer: &[u8]) { 140 | let endpoint = buffer[3] & 0x7f; 141 | match UsbEp::from(buffer[4]) { 142 | UsbEp::USBEpXFERSetup => unsafe { 143 | USBD_LL_SetupStage(&raw mut USBD_Device, &buffer[6]); 144 | }, 145 | UsbEp::USBEpXFERIn => { 146 | if (endpoint as u32) < IO_USB_MAX_ENDPOINTS { 147 | unsafe { 148 | G_io_app.usb_ep_timeouts[endpoint as usize].timeout = 0; 149 | USBD_LL_DataInStage(&raw mut USBD_Device, endpoint, &buffer[6]); 150 | } 151 | } 152 | } 153 | UsbEp::USBEpXFEROut => { 154 | if (endpoint as u32) < IO_USB_MAX_ENDPOINTS { 155 | unsafe { 156 | G_io_app.usb_ep_xfer_len[endpoint as usize] = buffer[5]; 157 | let mut apdu_buf = ApduBufferT { 158 | buf: apdu_buffer.as_mut_ptr(), 159 | len: 260, 160 | }; 161 | USBD_LL_DataOutStage(&raw mut USBD_Device, endpoint, &buffer[6], &mut apdu_buf); 162 | } 163 | } 164 | } 165 | _ => (), 166 | } 167 | } 168 | 169 | pub fn handle_capdu_event(apdu_buffer: &mut [u8], buffer: &[u8]) { 170 | let io_app = &raw mut G_io_app; 171 | unsafe { 172 | if (*io_app).apdu_state == APDU_IDLE { 173 | let max = (apdu_buffer.len() - 3).min(buffer.len() - 3); 174 | let size = u16::from_be_bytes([buffer[1], buffer[2]]) as usize; 175 | 176 | (*io_app).apdu_media = IO_APDU_MEDIA_RAW; 177 | (*io_app).apdu_state = APDU_RAW; 178 | 179 | let len = size.min(max); 180 | 181 | (*io_app).apdu_length = len as u16; 182 | 183 | apdu_buffer[..len].copy_from_slice(&buffer[3..len + 3]); 184 | } 185 | } 186 | } 187 | 188 | pub fn handle_event(apdu_buffer: &mut [u8], spi_buffer: &[u8]) { 189 | let len = u16::from_be_bytes([spi_buffer[1], spi_buffer[2]]); 190 | match Events::from(spi_buffer[0]) { 191 | Events::USBEvent => { 192 | if len == 1 { 193 | handle_usb_event(spi_buffer[3]); 194 | } 195 | } 196 | Events::USBXFEREvent => { 197 | if len >= 3 { 198 | handle_usb_ep_xfer_event(apdu_buffer, spi_buffer); 199 | } 200 | } 201 | #[cfg(any(target_os = "nanox", target_os = "stax", target_os = "flex"))] 202 | Events::BleReceive => ble::receive(apdu_buffer, spi_buffer), 203 | Events::CAPDUEvent => handle_capdu_event(apdu_buffer, spi_buffer), 204 | Events::TickerEvent => { 205 | #[cfg(any(target_os = "stax", target_os = "flex", feature = "nano_nbgl"))] 206 | unsafe { 207 | ux_process_ticker_event(); 208 | } 209 | } 210 | _ => (), 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/testing.rs: -------------------------------------------------------------------------------- 1 | use core::panic::PanicInfo; 2 | 3 | #[cfg(feature = "debug")] 4 | use core::arch::asm; 5 | 6 | /// Debug 'print' function that uses ARM semihosting 7 | /// Prints only strings with no formatting 8 | #[cfg(feature = "debug")] 9 | pub fn debug_print(s: &str) { 10 | let p = s.as_bytes().as_ptr(); 11 | for i in 0..s.len() { 12 | let m = unsafe { p.add(i) }; 13 | unsafe { 14 | asm!( 15 | "svc #0xab", 16 | in("r1") m, 17 | inout("r0") 3 => _, 18 | ); 19 | } 20 | } 21 | } 22 | #[cfg(not(feature = "debug"))] 23 | pub fn debug_print(_s: &str) {} 24 | 25 | pub fn to_hex(m: u32) -> [u8; 8] { 26 | let mut hex = [0u8; 8]; 27 | let mut i = 0; 28 | for c in m.to_be_bytes().iter() { 29 | let c0 = char::from_digit((c >> 4).into(), 16).unwrap(); 30 | let c1 = char::from_digit((c & 0xf).into(), 16).unwrap(); 31 | hex[i] = c0 as u8; 32 | hex[i + 1] = c1 as u8; 33 | i += 2; 34 | } 35 | hex 36 | } 37 | 38 | #[cfg_attr(test, panic_handler)] 39 | pub fn test_panic(info: &PanicInfo) -> ! { 40 | debug_print("Panic! "); 41 | let loc = info.location().unwrap(); 42 | debug_print(loc.file()); 43 | debug_print("\n"); 44 | debug_print(core::str::from_utf8(&to_hex(loc.line())).unwrap()); 45 | debug_print("\n"); 46 | ledger_secure_sdk_sys::exit_app(1); 47 | } 48 | 49 | /// Custom type used to implement tests 50 | #[cfg(feature = "speculos")] 51 | pub struct TestType { 52 | pub modname: &'static str, 53 | pub name: &'static str, 54 | pub f: fn() -> Result<(), ()>, 55 | } 56 | 57 | /// Custom test runner that uses non-formatting print functions 58 | /// using semihosting. Only reports 'Ok' or 'fail'. 59 | #[cfg(feature = "speculos")] 60 | pub fn sdk_test_runner(tests: &[&TestType]) { 61 | use core::ffi::c_void; 62 | use ledger_secure_sdk_sys::{pic, pic_rs}; 63 | let mut failures = 0; 64 | debug_print("--- Tests ---\n"); 65 | for test_ in tests { 66 | // (ノಠ益ಠ)ノ彡ꓛIꓒ 67 | let test = pic_rs(*test_); 68 | let modname; 69 | let name; 70 | unsafe { 71 | let t = pic(test.modname.as_ptr() as *mut c_void) as *const u8; 72 | let t = core::ptr::slice_from_raw_parts(t, test.modname.len()); 73 | let t: &[u8] = core::mem::transmute(t); 74 | modname = core::str::from_utf8_unchecked(t); 75 | 76 | let t = pic(test.name.as_ptr() as *mut c_void) as *const u8; 77 | let t = core::ptr::slice_from_raw_parts(t, test.name.len()); 78 | let t: &[u8] = core::mem::transmute(t); 79 | name = core::str::from_utf8_unchecked(t); 80 | } 81 | let fp = unsafe { pic(test.f as *mut c_void) }; 82 | let fp: fn() -> Result<(), ()> = unsafe { core::mem::transmute(fp) }; 83 | let res = fp(); 84 | match res { 85 | Ok(()) => debug_print("\x1b[1;32m ok \x1b[0m"), 86 | Err(()) => { 87 | failures += 1; 88 | debug_print("\x1b[1;31m fail \x1b[0m") 89 | } 90 | } 91 | debug_print(modname); 92 | debug_print("::"); 93 | debug_print(name); 94 | debug_print("\n"); 95 | } 96 | if failures > 0 { 97 | ledger_secure_sdk_sys::exit_app(1); 98 | } 99 | ledger_secure_sdk_sys::exit_app(0); 100 | } 101 | 102 | /// This variant of `assert_eq!()` returns an error 103 | /// `Err(())` instead of panicking, to prevent tests 104 | /// from exiting on first failure 105 | #[cfg(feature = "speculos")] 106 | #[macro_export] 107 | macro_rules! assert_eq_err { 108 | ($left:expr, $right:expr) => {{ 109 | match (&$left, &$right) { 110 | (left_val, right_val) => { 111 | if !(*left_val == *right_val) { 112 | $crate::testing::debug_print("assertion failed: `(left == right)`\n"); 113 | return Err(()); 114 | } 115 | } 116 | } 117 | }}; 118 | } 119 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/ui.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(version("1.63")), feature(array_from_fn))] 2 | 3 | pub mod bagls; 4 | 5 | pub mod string_se; 6 | 7 | pub mod bitmaps; 8 | pub mod fonts; 9 | pub mod layout; 10 | 11 | pub mod gadgets; 12 | pub mod screen_util; 13 | 14 | pub const PADDING: usize = 2; 15 | pub const Y_PADDING: usize = 3; 16 | pub const SCREEN_WIDTH: usize = 128; 17 | 18 | pub const SCREEN_HEIGHT: usize = 64; 19 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/ui/bagls.rs: -------------------------------------------------------------------------------- 1 | pub mod se; 2 | pub use self::se::*; 3 | 4 | use bitmaps::Glyph; 5 | 6 | pub struct RectFull { 7 | pos: (i32, i32), 8 | width: u32, 9 | height: u32, 10 | } 11 | 12 | impl RectFull { 13 | pub const fn new() -> RectFull { 14 | RectFull { 15 | pos: (0, 0), 16 | width: 1, 17 | height: 1, 18 | } 19 | } 20 | pub const fn pos(self, x: i32, y: i32) -> RectFull { 21 | RectFull { 22 | pos: (x, y), 23 | ..self 24 | } 25 | } 26 | pub const fn width(self, width: u32) -> RectFull { 27 | RectFull { width, ..self } 28 | } 29 | pub const fn height(self, height: u32) -> RectFull { 30 | RectFull { height, ..self } 31 | } 32 | } 33 | 34 | const fn middle_y(glyph: &Glyph) -> i16 { 35 | ((crate::ui::SCREEN_HEIGHT as u32 - glyph.height) / 2) as i16 36 | } 37 | 38 | pub struct Icon<'a> { 39 | pub icon: &'a Glyph<'a>, 40 | pub pos: (i16, i16), 41 | } 42 | 43 | impl<'a> From<&'a Glyph<'a>> for Icon<'a> { 44 | fn from(glyph: &'a Glyph) -> Icon<'a> { 45 | Icon { 46 | icon: glyph, 47 | pos: (0, middle_y(glyph)), 48 | } 49 | } 50 | } 51 | 52 | impl<'a> Icon<'a> { 53 | pub const fn from(glyph: &'a Glyph<'a>) -> Icon<'a> { 54 | Icon { 55 | icon: glyph, 56 | pos: (0, middle_y(glyph)), 57 | } 58 | } 59 | 60 | /// Set specific x-coordinate 61 | pub const fn set_x(self, x: i16) -> Icon<'a> { 62 | Icon { 63 | pos: (x, self.pos.1), 64 | ..self 65 | } 66 | } 67 | 68 | /// Set specific y-coordinate 69 | pub const fn set_y(self, y: i16) -> Icon<'a> { 70 | Icon { 71 | pos: (self.pos.0, y), 72 | ..self 73 | } 74 | } 75 | 76 | /// Shift horizontally 77 | pub const fn shift_h(self, n: i16) -> Icon<'a> { 78 | Icon { 79 | pos: (self.pos.0 + n, self.pos.1), 80 | ..self 81 | } 82 | } 83 | 84 | /// Shift vertically 85 | pub const fn shift_v(self, n: i16) -> Icon<'a> { 86 | Icon { 87 | pos: (self.pos.0, self.pos.1 + n), 88 | ..self 89 | } 90 | } 91 | } 92 | 93 | use crate::ui::bitmaps; 94 | 95 | pub const OUTER_PADDING: usize = 2; 96 | pub const SCREENW: i16 = (crate::ui::SCREEN_WIDTH - OUTER_PADDING) as i16; 97 | 98 | pub const DOWN_ARROW: Icon = 99 | Icon::from(&bitmaps::DOWN_ARROW).set_x(SCREENW - bitmaps::DOWN_ARROW.width as i16); 100 | pub const LEFT_ARROW: Icon = Icon::from(&bitmaps::LEFT_ARROW).set_x(OUTER_PADDING as i16); 101 | pub const RIGHT_ARROW: Icon = 102 | Icon::from(&bitmaps::RIGHT_ARROW).set_x(SCREENW - bitmaps::RIGHT_ARROW.width as i16); 103 | pub const UP_ARROW: Icon = Icon::from(&bitmaps::UP_ARROW).set_x(OUTER_PADDING as i16); 104 | pub const DOWN_S_ARROW: Icon = DOWN_ARROW.shift_v(4); 105 | pub const LEFT_S_ARROW: Icon = LEFT_ARROW.shift_h(4); 106 | pub const RIGHT_S_ARROW: Icon = RIGHT_ARROW.shift_h(-4); 107 | pub const UP_S_ARROW: Icon = UP_ARROW.shift_v(-4); 108 | 109 | pub const CHECKMARK_ICON: Icon = Icon::from(&bitmaps::CHECKMARK); 110 | pub const CROSS_ICON: Icon = Icon::from(&bitmaps::CROSS); 111 | pub const COGGLE_ICON: Icon = Icon::from(&bitmaps::COGGLE); 112 | pub const CERTIFICATE_ICON: Icon = Icon::from(&bitmaps::CERTIFICATE); 113 | pub const CROSSMARK_ICON: Icon = Icon::from(&bitmaps::CROSSMARK); 114 | pub const DASHBOARD_ICON: Icon = Icon::from(&bitmaps::DASHBOARD); 115 | pub const DASHBOARD_X_ICON: Icon = Icon::from(&bitmaps::DASHBOARD_X); 116 | pub const EYE_ICON: Icon = Icon::from(&bitmaps::EYE); 117 | pub const PROCESSING_ICON: Icon = Icon::from(&bitmaps::PROCESSING); 118 | pub const VALIDATE_14_ICON: Icon = Icon::from(&bitmaps::VALIDATE_14); 119 | pub const WARNING_ICON: Icon = Icon::from(&bitmaps::WARNING); 120 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/ui/bagls/se.rs: -------------------------------------------------------------------------------- 1 | use super::Icon; 2 | use crate::ui::fonts::OPEN_SANS; 3 | use crate::ui::layout::*; 4 | use ledger_secure_sdk_sys; 5 | 6 | #[derive(Clone, Copy)] 7 | pub struct Label<'a> { 8 | pub text: &'a str, 9 | pub bold: bool, 10 | pub loc: Location, 11 | layout: Layout, 12 | } 13 | 14 | impl Default for Label<'_> { 15 | fn default() -> Self { 16 | Label { 17 | text: "", 18 | bold: false, 19 | loc: Location::Middle, 20 | layout: Layout::Centered, 21 | } 22 | } 23 | } 24 | 25 | impl<'a> From<&'a str> for Label<'a> { 26 | fn from(s: &'a str) -> Label<'a> { 27 | Label { 28 | text: s, 29 | bold: false, 30 | loc: Location::Middle, 31 | layout: Layout::Centered, 32 | } 33 | } 34 | } 35 | 36 | impl<'a> Label<'a> { 37 | pub const fn from_const(s: &'a str) -> Label<'a> { 38 | Label { 39 | text: s, 40 | bold: false, 41 | loc: Location::Middle, 42 | layout: Layout::Centered, 43 | } 44 | } 45 | 46 | pub const fn location(self, loc: Location) -> Label<'a> { 47 | Label { loc, ..self } 48 | } 49 | 50 | pub const fn layout(self, layout: Layout) -> Label<'a> { 51 | Label { layout, ..self } 52 | } 53 | 54 | pub const fn bold(&self) -> Label<'a> { 55 | Label { 56 | bold: true, 57 | ..*self 58 | } 59 | } 60 | } 61 | 62 | impl Draw for Label<'_> { 63 | fn display(&self) { 64 | self.text.place(self.loc, self.layout, self.bold); 65 | } 66 | fn erase(&self) { 67 | let total_width = self.text.compute_width(self.bold); 68 | let c_height = OPEN_SANS[self.bold as usize].height as usize; 69 | if total_width != 0 { 70 | let x = self.layout.get_x(total_width); 71 | let y = self.loc.get_y(c_height); 72 | pic_draw( 73 | x as i32, 74 | y as i32, 75 | total_width as u32, 76 | c_height as u32, 77 | false, 78 | &crate::ui::bitmaps::BLANK, 79 | ) 80 | } 81 | } 82 | } 83 | 84 | use crate::ui::bagls::RectFull; 85 | 86 | impl Draw for RectFull { 87 | fn display(&self) { 88 | unsafe { 89 | ledger_secure_sdk_sys::bagl_hal_draw_rect( 90 | 1, 91 | self.pos.0, 92 | self.pos.1, 93 | self.width, 94 | self.height, 95 | ); 96 | } 97 | } 98 | 99 | fn erase(&self) { 100 | unsafe { 101 | ledger_secure_sdk_sys::bagl_hal_draw_rect( 102 | 0, 103 | self.pos.0, 104 | self.pos.1, 105 | self.width, 106 | self.height, 107 | ); 108 | } 109 | } 110 | } 111 | 112 | use core::ffi::c_void; 113 | 114 | #[inline(never)] 115 | fn pic_draw(x: i32, y: i32, width: u32, height: u32, inverted: bool, bitmap: &[u8]) { 116 | let inverted = [inverted as u32, !inverted as u32]; 117 | unsafe { 118 | let pic_bmp = ledger_secure_sdk_sys::pic(bitmap.as_ptr() as *mut c_void); 119 | let _ = ledger_secure_sdk_sys::bagl_hal_draw_bitmap_within_rect( 120 | x, 121 | y, 122 | width, 123 | height, 124 | 2, 125 | inverted.as_ptr(), 126 | 1, 127 | pic_bmp as *const u8, 128 | width * height, 129 | ); 130 | } 131 | } 132 | 133 | impl<'a> Draw for Icon<'a> { 134 | fn display(&self) { 135 | let icon = ledger_secure_sdk_sys::pic_rs(self.icon); 136 | pic_draw( 137 | self.pos.0 as i32, 138 | self.pos.1 as i32, 139 | icon.width, 140 | icon.height, 141 | icon.inverted, 142 | icon.bitmap, 143 | ); 144 | } 145 | 146 | fn erase(&self) { 147 | let icon = ledger_secure_sdk_sys::pic_rs(self.icon); 148 | pic_draw( 149 | self.pos.0 as i32, 150 | self.pos.1 as i32, 151 | icon.width, 152 | icon.height, 153 | icon.inverted, 154 | &crate::ui::bitmaps::BLANK, 155 | ); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/ui/bitmaps.rs: -------------------------------------------------------------------------------- 1 | use crate::ui::screen_util::draw; 2 | use ledger_secure_sdk_sys; 3 | 4 | pub struct Glyph<'a> { 5 | pub bitmap: &'a [u8], 6 | pub width: u32, 7 | pub height: u32, 8 | pub inverted: bool, 9 | } 10 | 11 | impl<'a> Glyph<'a> { 12 | pub const fn new(bitmap: &'a [u8], width: u32, height: u32) -> Glyph<'a> { 13 | Glyph { 14 | bitmap, 15 | width, 16 | height, 17 | inverted: false, 18 | } 19 | } 20 | pub const fn from_include(packed: (&'a [u8], u32, u32)) -> Glyph<'a> { 21 | Glyph { 22 | bitmap: packed.0, 23 | width: packed.1, 24 | height: packed.2, 25 | inverted: false, 26 | } 27 | } 28 | pub const fn invert(self) -> Glyph<'a> { 29 | Glyph { 30 | inverted: true, 31 | ..self 32 | } 33 | } 34 | pub fn draw(&self, x: i32, y: i32) { 35 | draw(x, y, self.width, self.height, self.inverted, self.bitmap); 36 | } 37 | } 38 | 39 | pub fn manual_screen_clear() { 40 | let inverted = [0u32, 1u32]; 41 | unsafe { 42 | ledger_secure_sdk_sys::bagl_hal_draw_bitmap_within_rect( 43 | 0, 44 | 0, 45 | 128, 46 | 64, 47 | 2, 48 | inverted.as_ptr(), 49 | 1, 50 | BLANK.as_ptr(), 51 | 128 * 64, 52 | ); 53 | } 54 | } 55 | 56 | use include_gif::include_gif; 57 | 58 | pub const BLANK: [u8; 1024] = [0u8; 1024]; 59 | 60 | pub const BACK: Glyph = Glyph::from_include(include_gif!("icons/badge_back.gif")); 61 | pub const CHECKMARK: Glyph = Glyph::from_include(include_gif!("icons/badge_check.gif")); 62 | pub const COGGLE: Glyph = Glyph::from_include(include_gif!("icons/icon_coggle.gif")); 63 | pub const CROSS: Glyph = Glyph::from_include(include_gif!("icons/icon_cross_badge.gif")); 64 | pub const DOWN_ARROW: Glyph = Glyph::from_include(include_gif!("icons/icon_down.gif")); 65 | pub const LEFT_ARROW: Glyph = Glyph::from_include(include_gif!("icons/icon_left.gif")); 66 | pub const RIGHT_ARROW: Glyph = Glyph::from_include(include_gif!("icons/icon_right.gif")); 67 | pub const UP_ARROW: Glyph = Glyph::from_include(include_gif!("icons/icon_up.gif")); 68 | pub const CERTIFICATE: Glyph = Glyph::from_include(include_gif!("icons/icon_certificate.gif")); 69 | pub const CROSSMARK: Glyph = Glyph::from_include(include_gif!("icons/icon_crossmark.gif")); 70 | pub const DASHBOARD: Glyph = Glyph::from_include(include_gif!("icons/icon_dashboard.gif")); 71 | pub const DASHBOARD_X: Glyph = Glyph::from_include(include_gif!("icons/icon_dashboard_x.gif")); 72 | pub const EYE: Glyph = Glyph::from_include(include_gif!("icons/icon_eye.gif")); 73 | pub const PROCESSING: Glyph = Glyph::from_include(include_gif!("icons/icon_processing.gif")); 74 | pub const VALIDATE_14: Glyph = Glyph::from_include(include_gif!("icons/icon_validate_14.gif")); 75 | pub const WARNING: Glyph = Glyph::from_include(include_gif!("icons/icon_warning.gif")); 76 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/ui/fonts.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | mod opensans; 4 | 5 | use opensans::CharArray; 6 | 7 | pub struct Font { 8 | pub chars: CharArray, 9 | pub dims: [u8; 96], 10 | pub height: u8, 11 | } 12 | 13 | impl Font { 14 | const fn new(chars: CharArray, dims: [u8; 96], height: u8) -> Font { 15 | Font { 16 | chars, 17 | dims, 18 | height, 19 | } 20 | } 21 | } 22 | 23 | const OPEN_SANS_REGULAR_11PX: Font = Font::new( 24 | opensans::OPEN_SANS_REGULAR_11PX_CHARS, 25 | opensans::OPEN_SANS_REGULAR_11PX_DIMS, 26 | 12, 27 | ); 28 | const OPEN_SANS_EXTRABOLD_11PX: Font = Font::new( 29 | opensans::OPEN_SANS_EXTRABOLD_11PX_CHARS, 30 | opensans::OPEN_SANS_EXTRABOLD_11PX_DIMS, 31 | 12, 32 | ); 33 | 34 | pub const OPEN_SANS: [Font; 2] = [OPEN_SANS_REGULAR_11PX, OPEN_SANS_EXTRABOLD_11PX]; 35 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/ui/layout.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone)] 2 | pub enum Layout { 3 | LeftAligned, 4 | RightAligned, 5 | Centered, 6 | Custom(usize), 7 | } 8 | 9 | impl Layout { 10 | pub fn get_x(&self, width: usize) -> usize { 11 | match self { 12 | Layout::LeftAligned => crate::ui::PADDING, 13 | Layout::Centered => (crate::ui::SCREEN_WIDTH - width) / 2, 14 | Layout::RightAligned => crate::ui::SCREEN_WIDTH - crate::ui::PADDING - width, 15 | Layout::Custom(x) => *x, 16 | } 17 | } 18 | } 19 | 20 | #[derive(Copy, Clone)] 21 | pub enum Location { 22 | Top, 23 | Middle, 24 | Bottom, 25 | Custom(usize), 26 | } 27 | 28 | impl Location { 29 | pub fn get_y(&self, height: usize) -> usize { 30 | match self { 31 | Location::Top => crate::ui::Y_PADDING, 32 | Location::Middle => (crate::ui::SCREEN_HEIGHT - height) / 2, 33 | Location::Bottom => crate::ui::SCREEN_HEIGHT - height - crate::ui::Y_PADDING, 34 | Location::Custom(y) => *y, 35 | } 36 | } 37 | } 38 | 39 | pub const MAX_LINES: usize = 4; 40 | 41 | pub trait Place { 42 | fn place_pad(&self, loc: Location, layout: Layout, padding: i32); 43 | fn place(&self, loc: Location, layout: Layout) { 44 | self.place_pad(loc, layout, 0) 45 | } 46 | } 47 | 48 | pub trait StringPlace { 49 | fn compute_width(&self, bold: bool) -> usize; 50 | fn place(&self, loc: Location, layout: Layout, bold: bool); 51 | } 52 | 53 | pub trait Draw { 54 | /// Display right away (updates screen) 55 | fn instant_display(&self) { 56 | self.display(); 57 | crate::ui::screen_util::screen_update(); 58 | } 59 | fn instant_erase(&self) { 60 | self.erase(); 61 | crate::ui::screen_util::screen_update(); 62 | } 63 | fn display(&self); 64 | fn erase(&self); 65 | } 66 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/ui/screen_util.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use ledger_secure_sdk_sys; 4 | 5 | pub fn draw(x_pos: i32, y_pos: i32, w: u32, h: u32, inv: bool, bmp: &[u8]) { 6 | let inverted = [inv as u32, !inv as u32]; 7 | unsafe { 8 | ledger_secure_sdk_sys::bagl_hal_draw_bitmap_within_rect( 9 | x_pos, 10 | y_pos, 11 | w, 12 | h, 13 | 2, 14 | inverted.as_ptr(), 15 | 1, 16 | bmp.as_ptr(), 17 | w * h, 18 | ); 19 | } 20 | } 21 | 22 | pub fn fulldraw(x_pos: i32, y_pos: i32, bmp: &[u8]) { 23 | draw(x_pos, y_pos, 128, 64, false, bmp); 24 | } 25 | 26 | pub fn screen_update() { 27 | unsafe { 28 | ledger_secure_sdk_sys::screen_update(); 29 | } 30 | } 31 | 32 | #[cfg(not(feature = "speculos"))] 33 | pub fn seph_setup_ticker(interval_ms: u16) { 34 | let ms = interval_ms.to_be_bytes(); 35 | ledger_secure_sdk_sys::seph::seph_send(&[0x4e, 0, 2, ms[0], ms[1]]); 36 | } 37 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/ui/string_se.rs: -------------------------------------------------------------------------------- 1 | use crate::ui::fonts::OPEN_SANS; 2 | use crate::ui::layout::*; 3 | use crate::ui::screen_util::{draw, screen_update}; 4 | use core::ffi::c_void; 5 | use ledger_secure_sdk_sys; 6 | 7 | extern "C" { 8 | fn pic(link_address: *mut c_void) -> *mut c_void; 9 | } 10 | 11 | impl StringPlace for &str { 12 | fn compute_width(&self, bold: bool) -> usize { 13 | let font_choice = bold as usize; 14 | self.as_bytes() 15 | .iter() 16 | .map(ledger_secure_sdk_sys::pic_rs) 17 | .fold(0, |acc, c| { 18 | acc + OPEN_SANS[font_choice].dims[*c as usize - 0x20] as usize 19 | }) 20 | } 21 | 22 | fn place(&self, loc: Location, layout: Layout, bold: bool) { 23 | let total_width = self.compute_width(bold); 24 | let mut cur_x = layout.get_x(total_width) as i32; 25 | 26 | let font_choice = bold as usize; 27 | for c in self.as_bytes().iter().map(ledger_secure_sdk_sys::pic_rs) { 28 | let offset_c = *c as usize - 0x20; 29 | let character = unsafe { 30 | let tmp = pic(OPEN_SANS[font_choice].chars.0[offset_c].as_ptr() as *mut c_void) 31 | as *const u8; 32 | core::slice::from_raw_parts(tmp, OPEN_SANS[font_choice].chars.0[offset_c].len()) 33 | }; 34 | let c_width = OPEN_SANS[font_choice].dims[offset_c]; 35 | let c_height = OPEN_SANS[font_choice].height as usize; 36 | let y = loc.get_y(c_height); 37 | draw( 38 | cur_x, 39 | y as i32, 40 | c_width as u32, 41 | c_height as u32, 42 | false, 43 | character, 44 | ); 45 | cur_x += c_width as i32; 46 | } 47 | screen_update(); 48 | } 49 | } 50 | 51 | impl StringPlace for [&str] { 52 | fn compute_width(&self, bold: bool) -> usize { 53 | self.iter().fold(0, |acc, s| acc.max(s.compute_width(bold))) 54 | } 55 | 56 | fn place(&self, loc: Location, layout: Layout, bold: bool) { 57 | let c_height = OPEN_SANS[bold as usize].height as usize; 58 | let padding = if self.len() > 4 { 0 } else { 1 }; 59 | let total_height = self.len() * (c_height + padding); 60 | let mut cur_y = loc.get_y(total_height); 61 | for string in self.iter() { 62 | string.place(Location::Custom(cur_y), layout, bold); 63 | cur_y += c_height + 2 * padding; 64 | } 65 | } 66 | } 67 | 68 | use crate::ui::bagls::se::Label; 69 | 70 | impl<'a> StringPlace for Label<'a> { 71 | fn compute_width(&self, _bold: bool) -> usize { 72 | self.text.compute_width(self.bold) 73 | } 74 | 75 | fn place(&self, loc: Location, layout: Layout, bold: bool) { 76 | self.text.place(loc, layout, bold); 77 | } 78 | } 79 | 80 | impl<'a> StringPlace for [Label<'a>] { 81 | fn compute_width(&self, bold: bool) -> usize { 82 | self.iter() 83 | .fold(0, |acc, lbl| acc.max(lbl.compute_width(bold))) 84 | } 85 | 86 | fn place(&self, _loc: Location, layout: Layout, bold: bool) { 87 | let c_height = OPEN_SANS[bold as usize].height as usize; 88 | let padding = ((crate::ui::SCREEN_HEIGHT / self.len()) - c_height) / 2; 89 | let mut cur_y = padding; 90 | for label in self.iter() { 91 | label.place(Location::Custom(cur_y), layout, label.bold); 92 | cur_y += c_height + 2 * padding; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /ledger_device_sdk/src/uxapp.rs: -------------------------------------------------------------------------------- 1 | use ledger_secure_sdk_sys::seph as sys_seph; 2 | use ledger_secure_sdk_sys::*; 3 | 4 | use crate::io::Reply; 5 | use crate::io::{ApduHeader, Comm, Event}; 6 | 7 | pub use ledger_secure_sdk_sys::BOLOS_UX_CANCEL; 8 | pub use ledger_secure_sdk_sys::BOLOS_UX_CONTINUE; 9 | pub use ledger_secure_sdk_sys::BOLOS_UX_ERROR; 10 | pub use ledger_secure_sdk_sys::BOLOS_UX_IGNORE; 11 | pub use ledger_secure_sdk_sys::BOLOS_UX_OK; 12 | pub use ledger_secure_sdk_sys::BOLOS_UX_REDRAW; 13 | 14 | unsafe extern "C" { 15 | pub unsafe static mut G_ux_params: bolos_ux_params_t; 16 | } 17 | 18 | #[repr(u8)] 19 | pub enum UxEvent { 20 | Event = BOLOS_UX_EVENT, 21 | Keyboard = BOLOS_UX_KEYBOARD, 22 | WakeUp = BOLOS_UX_WAKE_UP, 23 | ValidatePIN = BOLOS_UX_VALIDATE_PIN, 24 | DelayLock = BOLOS_UX_DELAY_LOCK, 25 | LastID = BOLOS_UX_DELAY_LOCK + 1, 26 | } 27 | 28 | impl UxEvent { 29 | #[allow(unused)] 30 | pub fn request(&self) -> u32 { 31 | unsafe { 32 | //let mut params = bolos_ux_params_t::default(); 33 | G_ux_params.ux_id = match self { 34 | Self::Event => Self::Event as u8, 35 | Self::Keyboard => Self::Keyboard as u8, 36 | Self::WakeUp => Self::WakeUp as u8, 37 | Self::ValidatePIN => { 38 | // Perform pre-wake up 39 | G_ux_params.ux_id = Self::WakeUp as u8; 40 | os_ux(&raw mut G_ux_params as *mut bolos_ux_params_t); 41 | 42 | Self::ValidatePIN as u8 43 | } 44 | Self::DelayLock => { 45 | #[cfg(any(target_os = "stax", target_os = "flex", feature = "nano_nbgl"))] 46 | { 47 | G_ux_params.u.lock_delay.delay_ms = 10000; 48 | } 49 | 50 | Self::DelayLock as u8 51 | } 52 | Self::LastID => panic!("Unknown UX Event"), 53 | }; 54 | 55 | os_ux(&raw mut G_ux_params as *mut bolos_ux_params_t); 56 | 57 | match self { 58 | Self::ValidatePIN => Self::block(), 59 | _ => os_sched_last_status(TASK_BOLOS_UX as u32) as u32, 60 | } 61 | } 62 | } 63 | 64 | pub fn block() -> u32 { 65 | let mut ret = unsafe { os_sched_last_status(TASK_BOLOS_UX as u32) } as u32; 66 | while ret == BOLOS_UX_IGNORE || ret == BOLOS_UX_CONTINUE { 67 | if unsafe { os_sched_is_running(TASK_SUBTASKS_START as u32) } 68 | != BOLOS_TRUE.try_into().unwrap() 69 | { 70 | let mut spi_buffer = [0u8; 256]; 71 | sys_seph::send_general_status(); 72 | sys_seph::seph_recv(&mut spi_buffer, 0); 73 | UxEvent::Event.request(); 74 | } else { 75 | unsafe { os_sched_yield(BOLOS_UX_OK as u8) }; 76 | } 77 | ret = unsafe { os_sched_last_status(TASK_BOLOS_UX as u32) } as u32; 78 | } 79 | ret 80 | } 81 | 82 | pub fn block_and_get_event(comm: &mut Comm) -> (u32, Option>) 83 | where 84 | T: TryFrom, 85 | Reply: From<>::Error>, 86 | { 87 | let mut ret = unsafe { os_sched_last_status(TASK_BOLOS_UX as u32) } as u32; 88 | let mut event = None; 89 | while ret == BOLOS_UX_IGNORE || ret == BOLOS_UX_CONTINUE { 90 | if unsafe { os_sched_is_running(TASK_SUBTASKS_START as u32) } 91 | != BOLOS_TRUE.try_into().unwrap() 92 | { 93 | let mut spi_buffer = [0u8; 256]; 94 | seph::send_general_status(); 95 | seph::seph_recv(&mut spi_buffer, 0); 96 | event = comm.decode_event(&mut spi_buffer); 97 | 98 | UxEvent::Event.request(); 99 | 100 | if let Option::Some(Event::Command(_)) = event { 101 | return (ret, event); 102 | } 103 | } else { 104 | unsafe { os_sched_yield(BOLOS_UX_OK as u8) }; 105 | } 106 | ret = unsafe { os_sched_last_status(TASK_BOLOS_UX as u32) } as u32; 107 | } 108 | (ret, event) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ledger_secure_sdk_sys" 3 | version = "1.8.2" 4 | authors = ["yhql", "agrojean-ledger", "yogh333"] 5 | edition = "2021" 6 | license.workspace = true 7 | repository.workspace = true 8 | description = "Bindings to Ledger C SDK" 9 | 10 | [build-dependencies] 11 | bindgen = "0.65.1" 12 | cc = "1.0.73" 13 | glob = "0.3.1" 14 | 15 | [dependencies] 16 | embedded-alloc = { version = "0.5.1", optional = true } 17 | critical-section = { version = "1.1.2", optional = true } 18 | 19 | [features] 20 | heap = ["dep:embedded-alloc", "dep:critical-section"] 21 | nano_nbgl = [] 22 | 23 | [lints.rust.unexpected_cfgs] 24 | level = "warn" 25 | check-cfg = ['cfg(target_os, values("stax", "flex", "nanox", "nanosplus"))'] 26 | -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/README.md: -------------------------------------------------------------------------------- 1 | # Rust Low-level bindings to the Ledger C SDK 2 | ![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FLedgerHQ%2Fledger-device-rust-sdk%2Frefs%2Fheads%2Fmaster%2Fledger_secure_sdk_sys%2FCargo.toml&query=%24.package.version&label=version) 3 | 4 | Provides access to low-level APIs to the operating system of Ledger devices. 5 | 6 | ## Build 7 | 8 | Depending on the target (`--target nanosplus`, `--target nanox`, ...), this crate will `git clone` the appropriate branch (`API_LEVEL_x`) of the [C SDK](https://github.com/LedgerHQ/ledger-secure-sdk/) and compile the subset of files necessary for the [Rust SDK](https://github.com/LedgerHQ/ledger-device-rust-sdk/) to work. 9 | 10 | To use an already-cloned C SDK, you can pass its path through the environment variable `LEDGER_SDK_PATH=/path/to/c_sdk` or through `cargo`'s `--config` flag: 11 | 12 | ```sh 13 | cargo build --target nanosplus --config env.LEDGER_SDK_PATH="../ledger-secure-sdk/" 14 | ``` 15 | -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/c_sdk_build_flex.cflags: -------------------------------------------------------------------------------- 1 | --sysroot="/usr/arm-none-eabi" 2 | -Oz 3 | -g0 4 | -fomit-frame-pointer 5 | -momit-leaf-frame-pointer 6 | -fno-common 7 | -mlittle-endian 8 | -std=gnu99 9 | -Wall 10 | -Wextra 11 | -Wno-main 12 | -Werror=int-to-pointer-cast 13 | -Wno-error=int-conversion 14 | -Wimplicit-fallthrough 15 | -Wvla 16 | -Wundef 17 | -Wshadow 18 | -Wformat=2 19 | -Wformat-security 20 | -Wwrite-strings 21 | -fdata-sections 22 | -ffunction-sections 23 | -funsigned-char 24 | -fshort-enums 25 | -mno-unaligned-access 26 | -fropi 27 | -fno-jump-tables 28 | -nostdlib 29 | -nodefaultlibs 30 | -frwpi 31 | -mthumb 32 | --target=armv8m-none-eabi 33 | -mcpu=cortex-m35p+nodsp 34 | -msoft-float -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/c_sdk_build_nanosplus.cflags: -------------------------------------------------------------------------------- 1 | --sysroot="/usr/arm-none-eabi" 2 | -Oz 3 | -g0 4 | -fomit-frame-pointer 5 | -momit-leaf-frame-pointer 6 | -fno-common 7 | -mlittle-endian 8 | -std=gnu99 9 | -Wall 10 | -Wextra 11 | -Wno-main 12 | -Werror=int-to-pointer-cast 13 | -Wno-error=int-conversion 14 | -Wimplicit-fallthrough 15 | -Wvla 16 | -Wundef 17 | -Wshadow 18 | -Wformat=2 19 | -Wformat-security 20 | -Wwrite-strings 21 | -fdata-sections 22 | -ffunction-sections 23 | -funsigned-char 24 | -fshort-enums 25 | -mno-unaligned-access 26 | -fropi 27 | -fno-jump-tables 28 | -nostdlib 29 | -nodefaultlibs 30 | -frwpi 31 | -mthumb 32 | --target=armv8m-none-eabi 33 | -mcpu=cortex-m35p+nodsp 34 | -msoft-float -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/c_sdk_build_nanox.cflags: -------------------------------------------------------------------------------- 1 | --sysroot="/usr/arm-none-eabi" 2 | -Oz 3 | -g0 4 | -fomit-frame-pointer 5 | -momit-leaf-frame-pointer 6 | -fno-common 7 | -mlittle-endian 8 | -std=gnu99 9 | -Wall 10 | -Wextra 11 | -Wno-main 12 | -Werror=int-to-pointer-cast 13 | -Wno-error=int-conversion 14 | -Wimplicit-fallthrough 15 | -Wvla 16 | -Wundef 17 | -Wshadow 18 | -Wformat=2 19 | -Wformat-security 20 | -Wwrite-strings 21 | -fdata-sections 22 | -ffunction-sections 23 | -funsigned-char 24 | -fshort-enums 25 | -mno-unaligned-access 26 | -fropi 27 | -fno-jump-tables 28 | -nostdlib 29 | -nodefaultlibs 30 | -frwpi 31 | -mthumb 32 | --target=armv6m-none-eabi 33 | -mcpu=cortex-m0plus 34 | 35 | -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/c_sdk_build_stax.cflags: -------------------------------------------------------------------------------- 1 | --sysroot="/usr/arm-none-eabi" 2 | -Oz 3 | -g0 4 | -fomit-frame-pointer 5 | -momit-leaf-frame-pointer 6 | -fno-common 7 | -mlittle-endian 8 | -std=gnu99 9 | -Wall 10 | -Wextra 11 | -Wno-main 12 | -Werror=int-to-pointer-cast 13 | -Wno-error=int-conversion 14 | -Wimplicit-fallthrough 15 | -Wvla 16 | -Wundef 17 | -Wshadow 18 | -Wformat=2 19 | -Wformat-security 20 | -Wwrite-strings 21 | -fdata-sections 22 | -ffunction-sections 23 | -funsigned-char 24 | -fshort-enums 25 | -mno-unaligned-access 26 | -fropi 27 | -fno-jump-tables 28 | -nostdlib 29 | -nodefaultlibs 30 | -frwpi 31 | -mthumb 32 | --target=armv8m-none-eabi 33 | -mcpu=cortex-m35p+nodsp 34 | -msoft-float -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/csdk_flex.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Makefile.standard_app 3 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // BLUETOOTH 5 | #define HAVE_BLE 6 | #define HAVE_BLE_APDU 7 | #define BLE_COMMAND_TIMEOUT_MS 2000 8 | #define BLE_SEGMENT_SIZE 32 9 | // NFC SUPPORT (feature dependent) 10 | //#define HAVE_NFC 11 | //#define HAVE_NFC_READER 12 | // APP STORAGE (feature dependent) 13 | //#define HAVE_APP_STORAGE 14 | // IO SEPROXY BUFFER SIZE 15 | #define IO_SEPROXYHAL_BUFFER_SIZE_B 300 16 | // NBGL QRCODE (feature dependent) 17 | #define NBGL_QRCODE 18 | // NBGL KEYBOARD (feature dependent) 19 | //#define NBGL_KEYBOARD 20 | // NBGL KEYPAD (feature dependent) 21 | //#define NBGL_KEYPAD 22 | // STANDARD DEFINES 23 | #define IO_HID_EP_LENGTH 64 24 | #define HAVE_SPRINTF 25 | #define HAVE_SNPRINTF_FORMAT_U 26 | #define HAVE_IO_USB 27 | #define HAVE_L4_USBLIB 28 | #define IO_USB_MAX_ENDPOINTS 4 29 | #define HAVE_USB_APDU 30 | #define USB_SEGMENT_SIZE 64 31 | //#define HAVE_WEBUSB 32 | //#define WEBUSB_URL_SIZE_B 33 | //#define WEBUSB_URL 34 | #define OS_IO_SEPROXYHAL 35 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 36 | // Makefile.defines 37 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 38 | #define gcc 39 | #define __IO volatile 40 | // Flex 41 | #define HAVE_BAGL_FONT_INTER_REGULAR_28PX 42 | #define HAVE_BAGL_FONT_INTER_SEMIBOLD_28PX 43 | #define HAVE_BAGL_FONT_INTER_MEDIUM_36PX 44 | #define HAVE_INAPP_BLE_PAIRING 45 | #define HAVE_NBGL 46 | #define HAVE_PIEZO_SOUND 47 | #define HAVE_SE_TOUCH 48 | #define HAVE_SE_EINK_DISPLAY 49 | //#define HAVE_HW_TOUCH_SWIPE 50 | #define NBGL_PAGE 51 | #define NBGL_USE_CASE 52 | #define SCREEN_SIZE_WALLET 53 | #define HAVE_FAST_HOLD_TO_APPROVE 54 | 55 | #define HAVE_LEDGER_PKI 56 | 57 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 58 | // Misc 59 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 60 | #define HAVE_LOCAL_APDU_BUFFER 61 | 62 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 63 | // DEBUG C SDK 64 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 65 | //#define HAVE_PRINTF 66 | //#define PRINTF mcu_usb_printf -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/csdk_nanos2.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Makefile.standard_app 3 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // APP STORAGE (feature dependent) 5 | //#define HAVE_APP_STORAGE 6 | // IO SEPROXY BUFFER SIZE 7 | #define IO_SEPROXYHAL_BUFFER_SIZE_B 300 8 | // NBGL KEYBOARD (feature dependent) 9 | //#define NBGL_KEYBOARD 10 | // NBGL KEYPAD (feature dependent) 11 | //#define NBGL_KEYPAD 12 | // STANDARD DEFINES 13 | #define IO_HID_EP_LENGTH 64 14 | #define HAVE_SPRINTF 15 | #define HAVE_SNPRINTF_FORMAT_U 16 | #define HAVE_IO_USB 17 | #define HAVE_L4_USBLIB 18 | #define IO_USB_MAX_ENDPOINTS 4 19 | #define HAVE_USB_APDU 20 | #define USB_SEGMENT_SIZE 64 21 | //#define HAVE_WEBUSB 22 | //#define WEBUSB_URL_SIZE_B 23 | //#define WEBUSB_URL 24 | #define OS_IO_SEPROXYHAL 25 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 26 | // Makefile.defines 27 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 28 | #define gcc 29 | #define __IO volatile 30 | 31 | #define BAGL_HEIGHT 64 32 | #define BAGL_WIDTH 128 33 | #define HAVE_BAGL_ELLIPSIS 34 | #define HAVE_BAGL_FONT_OPEN_SANS_REGULAR_11PX 35 | #define HAVE_BAGL_FONT_OPEN_SANS_EXTRABOLD_11PX 36 | #define HAVE_BAGL_FONT_OPEN_SANS_LIGHT_16PX 37 | #define SCREEN_SIZE_NANO 38 | 39 | #define HAVE_SE_BUTTON 40 | #define HAVE_SE_SCREEN 41 | #define HAVE_FONTS 42 | #define HAVE_INAPP_BLE_PAIRING 43 | #define HAVE_BATTERY 44 | 45 | #define HAVE_LEDGER_PKI 46 | 47 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 48 | // Misc 49 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 50 | #define HAVE_LOCAL_APDU_BUFFER 51 | 52 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 53 | // DEBUG C SDK 54 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 55 | //#define HAVE_PRINTF 56 | //#define PRINTF mcu_usb_printf 57 | -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/csdk_nanox.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Makefile.standard_app 3 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // BLUETOOTH 5 | #define HAVE_BLE 6 | #define HAVE_BLE_APDU 7 | #define BLE_COMMAND_TIMEOUT_MS 2000 8 | #define BLE_SEGMENT_SIZE 32 9 | // APP STORAGE (feature dependent) 10 | //#define HAVE_APP_STORAGE 11 | // IO SEPROXY BUFFER SIZE 12 | #define IO_SEPROXYHAL_BUFFER_SIZE_B 300 13 | // NBGL KEYBOARD (feature dependent) 14 | //#define NBGL_KEYBOARD 15 | // NBGL KEYPAD (feature dependent) 16 | //#define NBGL_KEYPAD 17 | // STANDARD DEFINES 18 | #define IO_HID_EP_LENGTH 64 19 | #define HAVE_SPRINTF 20 | #define HAVE_SNPRINTF_FORMAT_U 21 | #define HAVE_IO_USB 22 | #define HAVE_L4_USBLIB 23 | #define IO_USB_MAX_ENDPOINTS 4 24 | #define HAVE_USB_APDU 25 | #define USB_SEGMENT_SIZE 64 26 | //#define HAVE_WEBUSB 27 | //#define WEBUSB_URL_SIZE_B 28 | //#define WEBUSB_URL 29 | #define OS_IO_SEPROXYHAL 30 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 31 | // Makefile.defines 32 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 33 | #define gcc 34 | #define __IO volatile 35 | 36 | #define BAGL_HEIGHT 64 37 | #define BAGL_WIDTH 128 38 | #define HAVE_BAGL_ELLIPSIS 39 | #define HAVE_BAGL_FONT_OPEN_SANS_REGULAR_11PX 40 | #define HAVE_BAGL_FONT_OPEN_SANS_EXTRABOLD_11PX 41 | #define HAVE_BAGL_FONT_OPEN_SANS_LIGHT_16PX 42 | #define SCREEN_SIZE_NANO 43 | 44 | #define HAVE_SE_BUTTON 45 | #define HAVE_SE_SCREEN 46 | #define HAVE_FONTS 47 | #define HAVE_INAPP_BLE_PAIRING 48 | #define HAVE_BATTERY 49 | 50 | #define HAVE_LEDGER_PKI 51 | 52 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 53 | // Misc 54 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 55 | #define HAVE_LOCAL_APDU_BUFFER 56 | #define HAVE_SEPROXYHAL_MCU 57 | #define HAVE_MCU_PROTECT 58 | 59 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 60 | // DEBUG C SDK 61 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 62 | //#define HAVE_PRINTF 63 | //#define PRINTF mcu_usb_printf -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/csdk_stax.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Makefile.standard_app 3 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // BLUETOOTH 5 | #define HAVE_BLE 6 | #define HAVE_BLE_APDU 7 | #define BLE_COMMAND_TIMEOUT_MS 2000 8 | #define BLE_SEGMENT_SIZE 32 9 | // NFC SUPPORT (feature dependent) 10 | //#define HAVE_NFC 11 | //#define HAVE_NFC_READER 12 | // APP STORAGE (feature dependent) 13 | //#define HAVE_APP_STORAGE 14 | // IO SEPROXY BUFFER SIZE 15 | #define IO_SEPROXYHAL_BUFFER_SIZE_B 300 16 | // NBGL QRCODE (feature dependent) 17 | #define NBGL_QRCODE 18 | // NBGL KEYBOARD (feature dependent) 19 | //#define NBGL_KEYBOARD 20 | // NBGL KEYPAD (feature dependent) 21 | //#define NBGL_KEYPAD 22 | // STANDARD DEFINES 23 | #define IO_HID_EP_LENGTH 64 24 | #define HAVE_SPRINTF 25 | #define HAVE_SNPRINTF_FORMAT_U 26 | #define HAVE_IO_USB 27 | #define HAVE_L4_USBLIB 28 | #define IO_USB_MAX_ENDPOINTS 4 29 | #define HAVE_USB_APDU 30 | #define USB_SEGMENT_SIZE 64 31 | //#define HAVE_WEBUSB 32 | //#define WEBUSB_URL_SIZE_B 33 | //#define WEBUSB_URL 34 | #define OS_IO_SEPROXYHAL 35 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 36 | // Makefile.defines 37 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 38 | #define gcc 39 | #define __IO volatile 40 | // Stax 41 | #define HAVE_BAGL_FONT_INTER_REGULAR_24PX 42 | #define HAVE_BAGL_FONT_INTER_SEMIBOLD_24PX 43 | #define HAVE_BAGL_FONT_INTER_MEDIUM_32PX 44 | #define HAVE_INAPP_BLE_PAIRING 45 | #define HAVE_NBGL 46 | #define HAVE_PIEZO_SOUND 47 | #define HAVE_SE_TOUCH 48 | #define HAVE_SE_EINK_DISPLAY 49 | #define NBGL_PAGE 50 | #define NBGL_USE_CASE 51 | #define SCREEN_SIZE_WALLET 52 | 53 | #define HAVE_LEDGER_PKI 54 | 55 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 56 | // Misc 57 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 58 | #define HAVE_LOCAL_APDU_BUFFER 59 | 60 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 61 | // DEBUG C SDK 62 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 63 | //#define HAVE_PRINTF 64 | //#define PRINTF mcu_usb_printf -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/flex.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": "eabi", 3 | "arch": "arm", 4 | "c-enum-min-bits": 8, 5 | "data-layout": "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64", 6 | "emit-debug-gdb-scripts": false, 7 | "executables": true, 8 | "frame-pointer": "always", 9 | "linker": "link_wrap.sh", 10 | "linker-flavor": "ld.lld", 11 | "llvm-target": "thumbv8m.main-none-eabi", 12 | "max-atomic-width": 32, 13 | "panic-strategy": "abort", 14 | "pre-link-args": { 15 | "ld.lld": [ 16 | "-Tflex_layout.ld", 17 | "-Tlink.ld" 18 | ], 19 | "ld": [ 20 | "-Tflex_layout.ld", 21 | "-Tlink.ld" 22 | ] 23 | }, 24 | "relocation-model": "ropi-rwpi", 25 | "singlethread": true, 26 | "target-pointer-width": "32", 27 | "os": "flex", 28 | "target-family": [ "bolos" ] 29 | } 30 | -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/flex_layout.ld: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | FLASH (rx) : ORIGIN = 0xc0de0000, LENGTH = 400K 4 | DATA (r) : ORIGIN = 0xc0de0000, LENGTH = 400K 5 | SRAM (rwx) : ORIGIN = 0xda7a0000, LENGTH = 44K 6 | } 7 | 8 | PAGE_SIZE = 512; 9 | STACK_SIZE = 1500; 10 | END_STACK = ORIGIN(SRAM) + LENGTH(SRAM); -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/link.ld: -------------------------------------------------------------------------------- 1 | PHDRS 2 | { 3 | flash0 PT_LOAD ; 4 | data PT_LOAD ; 5 | flash2 PT_LOAD ; 6 | sram PT_LOAD ; 7 | 8 | headers PT_PHDR PHDRS ; 9 | } 10 | 11 | SECTIONS 12 | { 13 | /* Code, read only, no relocations needed. */ 14 | .text : 15 | { 16 | _text = .; 17 | /* Here begins flash. This symbol is used by the ideompotent `pic` 18 | function as the lower bound of addressed to relocate. */ 19 | _nvram_start = .; 20 | 21 | *(.boot*) 22 | *(.text*) 23 | /* .rodata is moved out so we can update it */ 24 | 25 | . = ALIGN(PAGE_SIZE); 26 | _etext = .; 27 | } > FLASH :flash0 28 | 29 | /* Relocations, read only, no relocations aginst the relocations themselves 30 | needed! */ 31 | _reloc_size = SIZEOF(.rel.rodata) + SIZEOF(.rel.data) + SIZEOF(.rel.nvm_data); 32 | .rel_flash : ALIGN(PAGE_SIZE) 33 | { 34 | _relocs = .; 35 | 36 | . += _reloc_size; 37 | . = ALIGN(PAGE_SIZE); 38 | 39 | _erelocs = .; 40 | 41 | . = ALIGN(PAGE_SIZE); 42 | 43 | /* After this section we have mutable flash. Must be a multiple of PAGE_SIZE from _nvram_start. */ 44 | _nvram_data = .; 45 | } > FLASH :flash0 46 | 47 | /* Immutable globals, read only during app running proper, but 48 | relocations are needed. (So not read-only completely.) */ 49 | .rodata : ALIGN(PAGE_SIZE) 50 | { 51 | /* Moved here from .text so we can permantly apply relocations to it with 52 | nvm_write() */ 53 | . = ALIGN(PAGE_SIZE); 54 | _rodata = .; 55 | _rodata_src = .; 56 | *(.rodata*) 57 | . = ALIGN(PAGE_SIZE); 58 | _erodata = .; 59 | } > FLASH :flash0 60 | _rodata_len = _erodata - _rodata; 61 | 62 | /* Mutable Globals, writable, relocations are needed. */ 63 | .data : ALIGN(4) 64 | { 65 | _data = .; 66 | *(vtable) 67 | *(.data*) 68 | . = ALIGN(PAGE_SIZE); 69 | _edata = .; 70 | } > SRAM AT> FLASH :data =0xa4a4 71 | _data_len = SIZEOF(.data); 72 | 73 | ASSERT( (_edata - _data) <= 0, ".data section must be empty" ) 74 | 75 | /* Persistent data, read and written during app running proper, 76 | relocations are also needed. */ 77 | .nvm_data : ALIGN(PAGE_SIZE) 78 | { 79 | _nvm_data_start = .; 80 | *(.nvm_data*) 81 | 82 | /* Store _nvram value during link_pass and use this to detect movement of 83 | _nvram as compared to the previous app execution, and redo the relocations 84 | if necessary */ 85 | . = ALIGN(4); 86 | _nvram_prev_run = .; 87 | LONG(ABSOLUTE(_nvram_start)) 88 | 89 | . = ALIGN(PAGE_SIZE); 90 | 91 | /* After this section we no longer have Flash memory at all. */ 92 | 93 | /* This symbol is used by the mutable portion of flash calculations. */ 94 | _envram_data = .; 95 | _install_parameters = .; 96 | /* This symbol is used by the ideompotent `pic` function as the upper 97 | bound of addressed to relocate. */ 98 | _nvram_end = .; 99 | } > FLASH :flash2 100 | 101 | _sidata_src = LOADADDR(.data); 102 | 103 | .bss : 104 | { 105 | _bss = .; 106 | *(.bss*) 107 | _ebss = .; 108 | _bss_len = ABSOLUTE(_ebss) - ABSOLUTE(_bss); 109 | 110 | . = ALIGN(4); 111 | app_stack_canary = .; 112 | . += 4; 113 | _stack_validation = .; 114 | . = _stack_validation + STACK_SIZE; 115 | _stack = ABSOLUTE(END_STACK) - STACK_SIZE; 116 | _estack = ABSOLUTE(END_STACK); 117 | } > SRAM :sram 118 | 119 | .stack_sizes (INFO): 120 | { 121 | KEEP(*(.stack_sizes)); 122 | } 123 | 124 | /DISCARD/ : 125 | { 126 | libc.a ( * ) 127 | libm.a ( * ) 128 | libgcc.a ( * ) 129 | *(.ARM.exidx* .gnu.linkonce.armexidx.*) 130 | *(.debug_info) 131 | } 132 | 133 | ledger.target (INFO): { KEEP(*(ledger.target)) } 134 | ledger.target_id (INFO): { KEEP(*(ledger.target_id)) } 135 | ledger.target_name (INFO): { KEEP(*(ledger.target_name)) } 136 | ledger.app_name (INFO): { KEEP(*(ledger.app_name)) } 137 | ledger.app_version (INFO): { KEEP(*(ledger.app_version)) } 138 | ledger.api_level (INFO): { KEEP(*(ledger.api_level)) } 139 | ledger.sdk_version (INFO): { KEEP(*(ledger.sdk_version)) } 140 | ledger.rust_sdk_version (INFO): { KEEP(*(ledger.rust_sdk_version)) } 141 | ledger.rust_sdk_name (INFO): { KEEP(*(ledger.rust_sdk_name)) } 142 | ledger.sdk_name (INFO): { KEEP(*(ledger.sdk_name)) } 143 | ledger.sdk_hash (INFO): { KEEP(*(ledger.sdk_hash)) } 144 | ledger.sdk_graphics (INFO): { KEEP(*(ledger.sdk_graphics)) } 145 | } 146 | 147 | PROVIDE(_nvram = ABSOLUTE(_nvram_start)); 148 | PROVIDE(_envram = ABSOLUTE(_nvram_end)); 149 | -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/link_wrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | set -x 6 | 7 | LD=${LD:-rust-lld} 8 | # Needed because LLD gets behavior from argv[0] 9 | LD=${LD/-ld/-lld} 10 | ${LD} "$@" --emit-relocs 11 | 12 | echo RUST_LLD DONE 13 | 14 | while [ $# -gt 0 -a "$1" != "-o" ]; 15 | do 16 | shift; 17 | done 18 | OUT="$2" 19 | 20 | echo OUT IS $OUT 21 | 22 | # the relocations for the constants section are required 23 | llvm-objcopy --dump-section .rel.rodata=$OUT-rodata-reloc $OUT /dev/null 24 | # there might not _be_ nonempty .data or .nvm_data sections, so there might be no relocations for it; fail gracefully. 25 | llvm-objcopy --dump-section .rel.data=$OUT-data-reloc $OUT /dev/null || true 26 | llvm-objcopy --dump-section .rel.nvm_data=$OUT-nvm-reloc $OUT /dev/null || true 27 | # Concatenate the relocation sections; this should still write $OUT-relocs even if $OUT-data-reloc doesn't exist. 28 | cat $OUT-rodata-reloc $OUT-nvm-reloc $OUT-data-reloc > $OUT-relocs || true 29 | 30 | reloc_allocated_size="$((0x$(llvm-nm $OUT | grep _reloc_size | cut -d' ' -f1)))" 31 | reloc_real_size="$(stat -c %s $OUT-relocs)" 32 | # Check that our relocations _actually_ fit. 33 | if [ "$reloc_real_size" -gt "$reloc_allocated_size" ] 34 | then 35 | echo "Insufficient size for relocs; This is likely some bug in nanos_sdk's link.ld." 36 | echo "Available size: " $reloc_allocated_size " Used size: " $reloc_real_size 37 | exit 1 38 | fi 39 | 40 | truncate -s $reloc_allocated_size $OUT-relocs 41 | # and write the relocs to their section in the flash image. 42 | llvm-objcopy --update-section .rel_flash=$OUT-relocs $OUT 43 | -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/nanosplus.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": "eabi", 3 | "arch": "arm", 4 | "c-enum-min-bits": 8, 5 | "data-layout": "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64", 6 | "emit-debug-gdb-scripts": false, 7 | "executables": true, 8 | "frame-pointer": "always", 9 | "linker": "link_wrap.sh", 10 | "linker-flavor": "ld.lld", 11 | "llvm-target": "thumbv8m.main-none-eabi", 12 | "max-atomic-width": 32, 13 | "panic-strategy": "abort", 14 | "pre-link-args": { 15 | "ld.lld": [ 16 | "-Tnanosplus_layout.ld", 17 | "-Tlink.ld" 18 | ], 19 | "ld": [ 20 | "-Tnanosplus_layout.ld", 21 | "-Tlink.ld" 22 | ] 23 | }, 24 | "relocation-model": "ropi-rwpi", 25 | "singlethread": true, 26 | "target-pointer-width": "32", 27 | "os": "nanosplus", 28 | "target-family": [ "bolos" ] 29 | } 30 | -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/nanosplus_layout.ld: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | FLASH (rx) : ORIGIN = 0xc0de0000, LENGTH = 400K 4 | DATA (r) : ORIGIN = 0xc0de0000, LENGTH = 400K 5 | SRAM (rwx) : ORIGIN = 0xda7a0000, LENGTH = 44K 6 | } 7 | 8 | PAGE_SIZE = 512; 9 | STACK_SIZE = 1500; 10 | END_STACK = ORIGIN(SRAM) + LENGTH(SRAM); -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/nanox.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": "eabi", 3 | "arch": "arm", 4 | "atomic-cas": false, 5 | "c-enum-min-bits": 8, 6 | "data-layout": "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64", 7 | "emit-debug-gdb-scripts": false, 8 | "executables": true, 9 | "frame-pointer": "always", 10 | "linker": "link_wrap.sh", 11 | "linker-flavor": "ld.lld", 12 | "llvm-target": "thumbv6m-none-eabi", 13 | "panic-strategy": "abort", 14 | "pre-link-args": { 15 | "ld.lld": [ 16 | "-Tnanox_layout.ld", 17 | "-Tlink.ld" 18 | ], 19 | "ld": [ 20 | "-Tnanox_layout.ld", 21 | "-Tlink.ld" 22 | ] 23 | }, 24 | "relocation-model": "ropi-rwpi", 25 | "singlethread": true, 26 | "target-pointer-width": "32", 27 | "os": "nanox", 28 | "target-family": [ "bolos" ] 29 | } 30 | -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/nanox_layout.ld: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | FLASH (rx) : ORIGIN = 0xc0de0000, LENGTH = 400K 4 | DATA (r) : ORIGIN = 0xc0de0000, LENGTH = 400K 5 | SRAM (rwx) : ORIGIN = 0xda7a0000, LENGTH = 28K 6 | } 7 | 8 | PAGE_SIZE = 256; 9 | STACK_SIZE = 8192; 10 | END_STACK = ORIGIN(SRAM) + LENGTH(SRAM); -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/src/buttons.rs: -------------------------------------------------------------------------------- 1 | /// Structure keeping track of button pushes 2 | /// 1 -> left button, 2 -> right button 3 | #[derive(Default)] 4 | pub struct ButtonsState { 5 | pub button_mask: u8, 6 | pub cmd_buffer: [u8; 4], 7 | } 8 | 9 | impl ButtonsState { 10 | pub const fn new() -> ButtonsState { 11 | ButtonsState { 12 | button_mask: 0, 13 | cmd_buffer: [0; 4], 14 | } 15 | } 16 | } 17 | 18 | /// Event types needed by 19 | /// an application 20 | #[derive(Eq, PartialEq)] 21 | pub enum ButtonEvent { 22 | LeftButtonPress, 23 | RightButtonPress, 24 | BothButtonsPress, 25 | LeftButtonRelease, 26 | RightButtonRelease, 27 | BothButtonsRelease, 28 | } 29 | 30 | /// Distinguish between button press and button release 31 | pub fn get_button_event(buttons: &mut ButtonsState, new: u8) -> Option { 32 | let old = buttons.button_mask; 33 | buttons.button_mask |= new; 34 | match (old, new) { 35 | (0, 1) => Some(ButtonEvent::LeftButtonPress), 36 | (0, 2) => Some(ButtonEvent::RightButtonPress), 37 | (_, 3) => Some(ButtonEvent::BothButtonsPress), 38 | (b, 0) => { 39 | buttons.button_mask = 0; // reset state on release 40 | match b { 41 | 1 => Some(ButtonEvent::LeftButtonRelease), 42 | 2 => Some(ButtonEvent::RightButtonRelease), 43 | 3 => Some(ButtonEvent::BothButtonsRelease), 44 | _ => None, 45 | } 46 | } 47 | _ => None, 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/src/c/sjlj.s: -------------------------------------------------------------------------------- 1 | .syntax unified 2 | 3 | .global setjmp 4 | .text 5 | .thumb_func 6 | setjmp: 7 | stmia r0!, {r4, r5, r6, r7} 8 | mov r1, r8 9 | mov r2, r9 10 | mov r3, r10 11 | mov r4, r11 12 | mov r5, sp 13 | mov r6, lr 14 | stmia r0!, {r1, r2, r3, r4, r5, r6} 15 | subs r0, #40 16 | ldmia r0!, {r4, r5, r6, r7} 17 | movs r0, #0 18 | bx lr 19 | 20 | .global longjmp 21 | .text 22 | .thumb_func 23 | 24 | longjmp: 25 | adds r0, #16 // fetch from r8, r9, r10, r11, sp 26 | ldmia r0!, {r2, r3, r4, r5, r6} 27 | mov r8, r2 28 | mov r9, r3 29 | mov r10, r4 30 | mov r11, r5 31 | mov sp, r6 32 | ldmia r0!, {r3} // lr into r3 33 | subs r0, #40 34 | ldmia r0!, {r4, r5, r6, r7} // fetch low registers 35 | adds r0, r1, #0 36 | bne longjmp_ret_ok 37 | movs r0, #1 38 | longjmp_ret_ok: 39 | bx r3 // go back to lr 40 | .end 41 | 42 | -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/src/infos.rs: -------------------------------------------------------------------------------- 1 | const fn make_c_string(s: &str) -> [u8; N] { 2 | let mut result = [0u8; N]; 3 | let mut i = 0; 4 | while i != s.len() { 5 | result[i] = s.as_bytes()[i]; 6 | i += 1; 7 | } 8 | result[i] = b'\n'; 9 | result 10 | } 11 | 12 | macro_rules! const_cstr { 13 | ($name: ident, $section: literal, $in_str: expr) => { 14 | #[used] 15 | #[link_section = $section] 16 | static $name: [u8; $in_str.len() + 1] = make_c_string($in_str); 17 | }; 18 | } 19 | 20 | const fn const_parse_api_level(x: &str) -> u8 { 21 | let a = x.as_bytes(); 22 | let mut res = a[0] - b'0'; 23 | let mut i = 1; 24 | while i < a.len() { 25 | res *= 10; 26 | res += a[i] - b'0'; 27 | i += 1; 28 | } 29 | res 30 | } 31 | 32 | /// Expose the API_LEVEL 33 | #[used] 34 | static API_LEVEL: u8 = const_parse_api_level(env!("API_LEVEL")); 35 | 36 | // Store metadata in the ELF file 37 | const_cstr!(ELF_API_LEVEL, "ledger.api_level", env!("API_LEVEL")); 38 | 39 | const_cstr!(ELF_TARGET, "ledger.target", env!("TARGET")); 40 | const_cstr!(ELF_TARGET_ID, "ledger.target_id", env!("TARGET_ID")); 41 | const_cstr!(ELF_TARGET_NAME, "ledger.target_name", env!("TARGET_NAME")); 42 | const_cstr!( 43 | ELF_RUST_SDK_NAME, 44 | "ledger.rust_sdk_name", 45 | env!("CARGO_PKG_NAME") 46 | ); 47 | const_cstr!( 48 | ELF_RUST_SDK_VERSION, 49 | "ledger.rust_sdk_version", 50 | env!("CARGO_PKG_VERSION") 51 | ); 52 | const_cstr!(ELF_C_SDK_NAME, "ledger.sdk_name", env!("C_SDK_NAME")); 53 | const_cstr!(ELF_C_SDK_HASH, "ledger.sdk_hash", env!("C_SDK_HASH")); 54 | const_cstr!( 55 | ELF_C_SDK_VERSION, 56 | "ledger.sdk_version", 57 | env!("C_SDK_VERSION") 58 | ); 59 | const_cstr!( 60 | ELF_C_SDK_GRAPHICS, 61 | "ledger.sdk_graphics", 62 | env!("C_SDK_GRAPHICS") 63 | ); 64 | -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![allow(non_upper_case_globals)] 3 | #![allow(non_camel_case_types)] 4 | #![allow(non_snake_case)] 5 | 6 | use core::ffi::c_void; 7 | #[cfg(all(feature = "heap"))] 8 | use core::mem::MaybeUninit; 9 | 10 | pub mod buttons; 11 | mod infos; 12 | pub mod seph; 13 | 14 | /// Wrapper for 'os_sched_exit' 15 | /// Exit application with status 16 | pub fn exit_app(status: u8) -> ! { 17 | unsafe { os_sched_exit(status) } 18 | } 19 | 20 | /// Performs code address translation for reading data located in the program 21 | /// and relocated during application installation. 22 | pub fn pic_rs(x: &T) -> &T { 23 | let ptr = unsafe { pic(x as *const T as *mut c_void) as *const T }; 24 | unsafe { &*ptr } 25 | } 26 | 27 | /// Performs code address translation for reading mutable data located in the 28 | /// program and relocated during application installation. 29 | /// 30 | /// Warning: this is for corner cases as it is not directly possible to write 31 | /// data stored in the code as it resides in Flash memory. This is needed in 32 | /// particular when using the `nvm` module. 33 | pub fn pic_rs_mut(x: &mut T) -> &mut T { 34 | let ptr = unsafe { pic(x as *mut T as *mut c_void) as *mut T }; 35 | unsafe { &mut *ptr } 36 | } 37 | 38 | #[cfg(all(feature = "heap"))] 39 | use critical_section::RawRestoreState; 40 | #[cfg(all(feature = "heap"))] 41 | use embedded_alloc::Heap; 42 | 43 | #[cfg(all(feature = "heap"))] 44 | #[global_allocator] 45 | static HEAP: Heap = Heap::empty(); 46 | 47 | #[cfg(all(feature = "heap"))] 48 | struct CriticalSection; 49 | #[cfg(all(feature = "heap"))] 50 | critical_section::set_impl!(CriticalSection); 51 | 52 | /// Default empty implementation as we don't have concurrency. 53 | #[cfg(all(feature = "heap"))] 54 | unsafe impl critical_section::Impl for CriticalSection { 55 | unsafe fn acquire() -> RawRestoreState {} 56 | unsafe fn release(_restore_state: RawRestoreState) {} 57 | } 58 | 59 | /// Initializes the heap memory for the global allocator. 60 | /// 61 | /// The heap is stored in the stack, and has a fixed size. 62 | /// This method is called just before [sample_main]. 63 | #[no_mangle] 64 | #[cfg(all(feature = "heap"))] 65 | extern "C" fn heap_init() { 66 | // HEAP_SIZE comes from heap_size.rs, which is defined via env var and build.rs 67 | static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; 68 | unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) } 69 | } 70 | 71 | #[no_mangle] 72 | #[cfg(any(not(feature = "heap")))] 73 | extern "C" fn heap_init() {} 74 | 75 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 76 | include!(concat!(env!("OUT_DIR"), "/heap_size.rs")); 77 | -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/src/seph.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | io_seph_is_status_sent, io_seph_recv, io_seph_send, SEPROXYHAL_TAG_GENERAL_STATUS, 3 | SEPROXYHAL_TAG_RAPDU, SEPROXYHAL_TAG_SCREEN_DISPLAY_STATUS, 4 | }; 5 | 6 | /// Directly send buffer over the SPI channel to the MCU 7 | pub fn seph_send(buffer: &[u8]) { 8 | unsafe { io_seph_send(buffer.as_ptr(), buffer.len() as u16) }; 9 | } 10 | 11 | /// Receive the next APDU into 'buffer' 12 | pub fn seph_recv(buffer: &mut [u8], flags: u32) -> u16 { 13 | unsafe { io_seph_recv(buffer.as_mut_ptr(), buffer.len() as u16, flags) } 14 | } 15 | 16 | /// Wrapper for 'io_seph_is_status_sent' 17 | pub fn is_status_sent() -> bool { 18 | let status = unsafe { io_seph_is_status_sent() }; 19 | status == 1 20 | } 21 | 22 | /// Inform the MCU that the previous event was processed 23 | pub fn send_general_status() { 24 | // XXX: Not sure we need this line to 'avoid troubles' like 25 | // in the original SDK 26 | // if io_seproxyhal_spi_is_status_sent() { 27 | // return; 28 | // } 29 | if !is_status_sent() { 30 | // The two last bytes are supposed to be 31 | // SEPROXYHAL_TAG_GENERAL_STATUS_LAST_COMMAND, which is 0u16 32 | let status = [SEPROXYHAL_TAG_GENERAL_STATUS as u8, 0, 2, 0, 0]; 33 | seph_send(&status); 34 | } 35 | } 36 | 37 | /// Function to ensure a I/O channel is not timeouting waiting 38 | /// for operations after a long time without SEPH packet exchanges 39 | pub fn heartbeat() { 40 | send_general_status(); 41 | let mut spi_buffer = [0u8; 128]; 42 | seph_recv(&mut spi_buffer, 0); 43 | while is_status_sent() { 44 | seph_recv(&mut spi_buffer, 0); 45 | } 46 | } 47 | 48 | #[repr(u8)] 49 | pub enum SephTags { 50 | ScreenDisplayStatus = SEPROXYHAL_TAG_SCREEN_DISPLAY_STATUS as u8, 51 | GeneralStatus = SEPROXYHAL_TAG_GENERAL_STATUS as u8, 52 | RawAPDU = SEPROXYHAL_TAG_RAPDU as u8, 53 | Unknown, 54 | } 55 | 56 | impl From for SephTags { 57 | fn from(v: u8) -> SephTags { 58 | match v as u32 { 59 | SEPROXYHAL_TAG_SCREEN_DISPLAY_STATUS => SephTags::ScreenDisplayStatus, 60 | SEPROXYHAL_TAG_GENERAL_STATUS => SephTags::GeneralStatus, 61 | _ => SephTags::Unknown, 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/stax.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": "eabi", 3 | "arch": "arm", 4 | "c-enum-min-bits": 8, 5 | "data-layout": "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64", 6 | "emit-debug-gdb-scripts": false, 7 | "executables": true, 8 | "frame-pointer": "always", 9 | "linker": "link_wrap.sh", 10 | "linker-flavor": "ld.lld", 11 | "llvm-target": "thumbv8m.main-none-eabi", 12 | "max-atomic-width": 32, 13 | "panic-strategy": "abort", 14 | "pre-link-args": { 15 | "ld.lld": [ 16 | "-Tstax_layout.ld", 17 | "-Tlink.ld" 18 | ], 19 | "ld": [ 20 | "-Tstax_layout.ld", 21 | "-Tlink.ld" 22 | ] 23 | }, 24 | "relocation-model": "ropi-rwpi", 25 | "singlethread": true, 26 | "target-pointer-width": "32", 27 | "os": "stax", 28 | "target-family": [ "bolos" ] 29 | } 30 | -------------------------------------------------------------------------------- /ledger_secure_sdk_sys/stax_layout.ld: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | FLASH (rx) : ORIGIN = 0xc0de0000, LENGTH = 400K 4 | DATA (r) : ORIGIN = 0xc0de0000, LENGTH = 400K 5 | SRAM (rwx) : ORIGIN = 0xda7a0000, LENGTH = 44K 6 | } 7 | 8 | PAGE_SIZE = 512; 9 | STACK_SIZE = 1500; 10 | END_STACK = ORIGIN(SRAM) + LENGTH(SRAM); -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2024-12-01" -------------------------------------------------------------------------------- /testmacro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "testmacro" 3 | version = "0.1.0" 4 | authors = ["yhql "] 5 | edition = "2018" 6 | license.workspace = true 7 | repository.workspace = true 8 | description = "procedural macro used to run unit and integration tests" 9 | 10 | [lib] 11 | proc-macro = true 12 | 13 | [dependencies] 14 | syn = { version = "1.0", features = ["full"] } 15 | quote = "1.0" -------------------------------------------------------------------------------- /testmacro/README.md: -------------------------------------------------------------------------------- 1 | # testmacro 2 | ![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FLedgerHQ%2Fledger-device-rust-sdk%2Frefs%2Fheads%2Fmaster%2Ftestmacro%2FCargo.toml&query=%24.package.version&label=version) 3 | 4 | A macro inspired from [Writing an OS in Rust](https://os.phil-opp.com/testing/) and [Rust Raspberry OS tutorials](https://github.com/rust-embedded/rust-raspberrypi-OS-tutorials/tree/master/13_integrated_testing) that helps building `#![no_std]` tests in some other projects. 5 | -------------------------------------------------------------------------------- /testmacro/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | use proc_macro::TokenStream; 3 | use quote::quote; 4 | 5 | #[proc_macro_attribute] 6 | pub fn test_item(_attr: TokenStream, item: TokenStream) -> TokenStream { 7 | let input = syn::parse_macro_input!(item as syn::ItemFn); 8 | let name = &input.sig.ident.to_string(); 9 | let func = &input.block; 10 | 11 | let r = quote! { 12 | #[test_case] 13 | const t: TestType = TestType { 14 | modname: module_path!(), 15 | name: #name, 16 | f: || -> Result<(),()> { 17 | #func 18 | Ok(()) 19 | } 20 | }; 21 | }; 22 | r.into() 23 | } 24 | --------------------------------------------------------------------------------