Handshake Ledger app
Some checks failed
Checks on the Python tests / Call Ledger Python linters (push) Has been cancelled
Build and run functional tests using ragger through reusable workflow / Build application using the reusable workflow (push) Has been cancelled
Run coding style check / Check linting using the reusable workflow (push) Has been cancelled
Ensure compliance with Ledger guidelines / Call Ledger guidelines_enforcer (push) Has been cancelled
Misspellings checks / Check misspellings (push) Has been cancelled
Build and run functional tests using ragger through reusable workflow / Run standalone ragger tests using the reusable workflow (push) Has been cancelled
Some checks failed
Checks on the Python tests / Call Ledger Python linters (push) Has been cancelled
Build and run functional tests using ragger through reusable workflow / Build application using the reusable workflow (push) Has been cancelled
Run coding style check / Check linting using the reusable workflow (push) Has been cancelled
Ensure compliance with Ledger guidelines / Call Ledger guidelines_enforcer (push) Has been cancelled
Misspellings checks / Check misspellings (push) Has been cancelled
Build and run functional tests using ragger through reusable workflow / Run standalone ragger tests using the reusable workflow (push) Has been cancelled
Hardware-wallet app for Handshake (HNS). Derives Handshake addresses and signs
Handshake transactions, including name-auction covenants, on Ledger Stax, Flex,
Nano S+, Nano X and Apex+.
The design premise is that the host computer is untrusted: every APDU byte is
attacker-controlled, and the device's job is to be correct when the host lies.
Two failures outrank all others, and the code is shaped around them.
Signing something other than what the user approved. Any field the signature
commits to and the host can vary has to appear on the review screen, and
anything the screen cannot represent honestly is refused rather than shown under
a label that does not describe it. So the device signs SIGHASH_ALL only,
checked per input rather than once for the session; it displays a TRANSFER
covenant's destination, which decides who ends up owning the name and appears
nowhere else in the output; it shows a covenant's name hash when the plaintext
name cannot be verified against it; and it refuses covenant kinds it cannot
name, outputs whose values exceed their inputs, and declared input totals that
would overflow the fee arithmetic.
Misusing key material. Derivation paths are constrained to
m/44'/{5353..5356}'/account'[/change/index] in the app itself, not only by the
install manifest, because BOLOS terminates the app on an out-of-whitelist path
instead of returning an error. One coin type per signing session, since the
review renders every address under a single HRP.
Panics count too: set_panic!(exiting_panic) means a panic kills the app and
strands the session, so nothing unwraps on host-derived data and the session
ceilings are sized to the configured heap.
Consensus follows shd, the Swift Handshake node, which is the reference for the
sighash preimage, covenant item layouts and address encoding.
SIGN_TX is a PSBT-style state machine driven by P1 (BEGIN / ADD_INPUT /
ADD_OUTPUT / REVIEW / SIGN_INPUT): the host streams the transaction as
structured records, the device accumulates its own view of the values, shows one
review, then signs each input on demand.
Tests run under speculos in a container (./scripts/test), 66 of them, green on
all five devices. test_sign_tx_policy.py covers each refusal above and needs no
golden snapshots because every case is rejected before anything is drawn; the
snapshots that do exist are the record of what the user sees before approving.
tests/application_client/handshake_sighash.py is a deliberately independent
reimplementation of the sighash, used to verify real signatures.
Status: v0.1, pre-audit. Not for mainnet keys. Signing has not yet been
exercised on physical hardware.
Forked from LedgerHQ/app-boilerplate-rust (Apache-2.0).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Checklist
|
||||
<!-- Put an `x` in each box when you have completed the items. -->
|
||||
- [ ] App update process has been followed <!-- See comment below -->
|
||||
- [ ] Target branch is `develop` <!-- unless you have a very good reason -->
|
||||
- [ ] Application version has been bumped <!-- required if your changes are to be deployed -->
|
||||
|
||||
<!-- Make sure you followed the process described in https://developers.ledger.com/docs/device-app/deliver/maintenance before opening your Pull Request.
|
||||
Don't hesitate to contact us directly on Discord if you have any questions ! https://developers.ledger.com/discord -->
|
||||
96
.github/copilot-instructions.md
vendored
Normal file
96
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
# Handshake Ledger app development guide
|
||||
|
||||
Rust app for Ledger hardware wallets (Stax, Flex, Nano S+, Nano X, Apex+) using
|
||||
`ledger_device_sdk`. It signs [Handshake](https://handshake.org) transactions,
|
||||
including name-auction covenants.
|
||||
|
||||
## The threat model drives everything
|
||||
|
||||
The host computer is untrusted. Every APDU byte is attacker-controlled, and the
|
||||
device's job is to be correct when the host lies. Two failures outrank all
|
||||
others:
|
||||
|
||||
1. **Signing something other than what the user approved.** Any field the
|
||||
signature commits to and the host can vary must appear on the review screen.
|
||||
If it cannot be displayed honestly, refuse to sign rather than showing a
|
||||
label that does not describe the bytes.
|
||||
2. **Misusing key material.** Signing under an unapproved path, or returning
|
||||
anything derived from the seed without the user seeing what they authorised.
|
||||
|
||||
A panic is also a real defect: `set_panic!(exiting_panic)` means the app dies
|
||||
and the user's session is stranded. Never `unwrap()` on host-derived data.
|
||||
|
||||
## Architecture
|
||||
|
||||
**APDU flow**: `Comm` receives CLA=0xe0; `Instruction` parses INS/P1/P2
|
||||
(`src/main.rs`); handlers in `src/handlers/` return
|
||||
`Result<CommandResponse, AppSW>`; `AppSW` maps errors to status words.
|
||||
|
||||
**SIGN_TX is a state machine, not a chunked blob.** P1 selects the sub-op:
|
||||
`BEGIN` / `ADD_INPUT` / `ADD_OUTPUT` / `REVIEW` / `SIGN_INPUT`. The host streams
|
||||
the transaction as structured records, the device accumulates its own view of
|
||||
the values, shows one review, then signs each input on demand. P2 chunks a
|
||||
single large `ADD_OUTPUT` only. See the table at the top of
|
||||
`src/handlers/sign_tx.rs`.
|
||||
|
||||
**Session state** lives in `TxContext`, with a `Phase` enum enforcing ordering
|
||||
and a BLAKE2b fingerprint captured at approval so nothing can change between
|
||||
`REVIEW` and `SIGN_INPUT`.
|
||||
|
||||
**Protocol primitives** are in `src/handshake/`: addresses, bech32, BLAKE2b,
|
||||
covenants, sighash, transaction types. These mirror `shd` (the Swift Handshake
|
||||
node), which is the reference for every consensus question. Check against it
|
||||
rather than from memory; a divergence in the sighash is a chain-split risk.
|
||||
|
||||
**UI** is NBGL: `NbglHomeAndSettings` for home, `NbglReview` with `Field` arrays
|
||||
for transaction and message review, `NbglAddressReview` for address confirm.
|
||||
Glyphs load via `include_gif!()` under `#[cfg(target_os = "...")]`.
|
||||
|
||||
## Build and test
|
||||
|
||||
```bash
|
||||
./scripts/build stax # or flex / nanox / nanosplus / apex_p
|
||||
```
|
||||
|
||||
Use `scripts/build`, not `cargo ledger build`: on macOS the latter writes
|
||||
neither the Intel HEX nor the manifest, and cannot compile the Nano NBGL sources
|
||||
without a `uint` define. The manifest is generated from the ELF by
|
||||
`scripts/gen-manifest` and must never be hand-written, because `dataSize` has to
|
||||
match the binary.
|
||||
|
||||
```bash
|
||||
pip install -r tests/standalone/requirements.txt
|
||||
pytest tests/standalone --tb=short -v --device stax
|
||||
```
|
||||
|
||||
`tests/standalone/test_sign_tx_policy.py` is the regression suite for what the
|
||||
device refuses; it needs no golden snapshots because every case is rejected
|
||||
before anything is drawn. `tests/application_client/handshake_sighash.py` is an
|
||||
independent sighash implementation used to cross-check real signatures.
|
||||
|
||||
## Key patterns
|
||||
|
||||
**Error handling**: map SDK errors to `AppSW` variants
|
||||
(`.map_err(|_| AppSW::TxSignFail)`). Never `unwrap()` outside `build.rs`.
|
||||
|
||||
**BIP-32 paths**: length byte + big-endian 4-byte components. `Bip32Path` in
|
||||
`src/utils.rs`; `validate_path` enforces
|
||||
`m/44'/{5353..5356}'/account'[/change/index]` and every handler calls it before
|
||||
deriving. BOLOS terminates the app on an out-of-whitelist path rather than
|
||||
returning an error, which is why the check happens in-app first.
|
||||
|
||||
**Cryptography**: `Secp256k1::derive_from_path()` for derivation;
|
||||
**BLAKE2b-256** for the sighash and signed messages (not SHA256d, not Keccak);
|
||||
BLAKE2b-160 for witness programs; SHA3-256 for name hashes (`NameRules.hashName`
|
||||
is SHA3, and Keccak differs in padding). `SIGN_TX` returns a 64-byte compact
|
||||
`r||s` plus the sighash byte; `SIGN_MESSAGE` returns
|
||||
`recovery_id || sig || pubkey`.
|
||||
|
||||
**Memory**: `#![no_std]`, `alloc::vec::Vec` and `alloc::format!`. The heap is set
|
||||
in `.cargo/config.toml` and the review screen's strings are the binding
|
||||
constraint, so session ceilings in `sign_tx.rs` are sized against it. Do not
|
||||
reserve capacity for host-declared counts.
|
||||
|
||||
**Metadata**: `[package.metadata.ledger]` in `Cargo.toml` defines the app name,
|
||||
icons, derivation-path whitelist and flags per device. Keep the whitelist and
|
||||
`validate_path` in agreement.
|
||||
14
.github/dependabot.yml
vendored
Normal file
14
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "cargo" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
||||
allow:
|
||||
- dependency-name: "ledger_device_sdk"
|
||||
- dependency-name: "include_gif"
|
||||
50
.github/workflows/build_and_functional_tests.yml
vendored
Normal file
50
.github/workflows/build_and_functional_tests.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: Build and run functional tests using ragger through reusable workflow
|
||||
|
||||
# This workflow will build the app and then run functional tests using the Ragger framework upon Speculos emulation.
|
||||
# It calls a reusable workflow developed by Ledger's internal developer team to build the application and upload the
|
||||
# resulting binaries.
|
||||
# It then calls another reusable workflow to run the Ragger tests on the compiled application binary.
|
||||
#
|
||||
# The build part of this workflow is mandatory, this ensures that the app will be deployable in the Ledger App Store.
|
||||
# While the test part of this workflow is optional, having functional testing on your application is mandatory and this workflow and
|
||||
# tooling environment is meant to be easy to use and adapt after forking your application
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: write
|
||||
pull-requests: write
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
golden_run:
|
||||
type: choice
|
||||
required: true
|
||||
default: 'Raise an error (default)'
|
||||
description: CI behavior if the test snapshots are different than expected.
|
||||
options:
|
||||
- 'Raise an error (default)'
|
||||
- 'Open a PR'
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- develop
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build_application:
|
||||
name: Build application using the reusable workflow
|
||||
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_build.yml@v1
|
||||
with:
|
||||
upload_app_binaries_artifact: "app_handshake_binaries"
|
||||
builder: ledger-app-builder
|
||||
|
||||
tests_standalone:
|
||||
name: Run standalone ragger tests using the reusable workflow
|
||||
needs: build_application
|
||||
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_ragger_tests.yml@v1
|
||||
with:
|
||||
download_app_binaries_artifact: "app_handshake_binaries"
|
||||
regenerate_snapshots: ${{ github.event_name == 'workflow_dispatch' && inputs.golden_run == 'Open a PR' }}
|
||||
test_dir: "tests/standalone"
|
||||
24
.github/workflows/coding_style_checks.yml
vendored
Normal file
24
.github/workflows/coding_style_checks.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Run coding style check
|
||||
|
||||
# This workflow will run linting checks to ensure a level of code quality among all Ledger applications.
|
||||
#
|
||||
# The presence of this workflow is mandatory as a minimal level of linting is required.
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- develop
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
check_linting:
|
||||
name: Check linting using the reusable workflow
|
||||
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_lint.yml@v1
|
||||
with:
|
||||
source: './src'
|
||||
27
.github/workflows/guidelines_enforcer.yml
vendored
Normal file
27
.github/workflows/guidelines_enforcer.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Ensure compliance with Ledger guidelines
|
||||
|
||||
# This workflow is mandatory in all applications
|
||||
# It calls a reusable workflow guidelines_enforcer developed by Ledger's internal developer team.
|
||||
# The successful completion of the reusable workflow is a mandatory step for an app to be available on the Ledger
|
||||
# application store.
|
||||
#
|
||||
# More information on the guidelines can be found in the repository:
|
||||
# LedgerHQ/ledger-app-workflows/
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: write
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- develop
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
guidelines_enforcer:
|
||||
name: Call Ledger guidelines_enforcer
|
||||
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_guidelines_enforcer.yml@v1
|
||||
22
.github/workflows/misspellings_checks.yml
vendored
Normal file
22
.github/workflows/misspellings_checks.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Misspellings checks
|
||||
|
||||
# This workflow performs some misspelling checks on the repository
|
||||
# It is there to help us maintain a level of quality in our codebase and does not have to be kept on forked
|
||||
# applications.
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- develop
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
misspell:
|
||||
name: Check misspellings
|
||||
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_spell_check.yml@v1
|
||||
28
.github/workflows/python_tests_checks.yml
vendored
Normal file
28
.github/workflows/python_tests_checks.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Checks on the Python tests
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# This workflow performs some checks on the Python client used by the ragger tests
|
||||
# It is there to help us maintain a level of quality in our codebase and does not have to be kept on forked
|
||||
# applications.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- develop
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Call Ledger Python linters
|
||||
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_python_checks.yml@v1
|
||||
with:
|
||||
run_linter: pylint
|
||||
run_type_check: true
|
||||
src_directory: application_client
|
||||
setup_directory: tests
|
||||
req_directory: tests
|
||||
|
||||
Reference in New Issue
Block a user