# 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`; `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.