Skip to content

7.15 audit 4/6: Zcash Orchard - #362

Closed
BitHighlander wants to merge 10 commits into
release/715-audit-03-recovery-entropyfrom
release/715-audit-04-zcash
Closed

7.15 audit 4/6: Zcash Orchard#362
BitHighlander wants to merge 10 commits into
release/715-audit-03-recovery-entropyfrom
release/715-audit-04-zcash

Conversation

@BitHighlander

Copy link
Copy Markdown
Owner

What changed

  • adds the Orchard shielded-signing engine
  • adds Zcash message handlers and session wiring
  • adds PCZT policy, unified-address, RedPallas, Pallas, Sinsemilla, and digest coverage
  • adds constant-time API and disassembly gates

Why

The engine and its tests were previously separated across two PRs. This bundle makes Zcash self-contained: reviewers see the cryptographic implementation, firmware wiring, policy, and evidence in one diff.

Validation

  • 65 Zcash firmware tests passed
  • 7 Pallas constant-time tests passed
  • Pallas API-boundary static gate passed
  • aggregate six-bundle container build and unit suite passed
  • git diff --check

Stack: 4 of 6. Review only the diff from release/715-audit-03-recovery-entropy.

Adds the signed-metadata engine: a host may supply a description of what an
EVM transaction does (contract alias, method, argument labels), and the device
displays it only if the blob carries a signature from a trust anchor it already
holds. Unsigned or wrongly signed metadata is ignored — it can never turn a
blind-signing prompt into a friendly one.

- Anchor verification over secp256k1 with the schema bound to the request, so a
  signature captured from one transaction cannot dress up another.
- Runtime signer table only; nothing is persisted to public flash. A rogue
  entry surviving a reboot would suppress the raw-data screen, which is exactly
  the failure this design refuses.
- tiny-json gains the bounds needed to parse metadata safely on device.
- unittests/firmware/signed_metadata.cpp covers the accept/reject matrix,
  including malformed blobs and signature/schema mismatches.

Message plumbing and the Ethereum call sites arrive in the clear-sign EVM
branch; this commit is the engine and its tests only.
…hemas

Solana signing moves from "confirm this blob" to describing the actual
instructions, using schemas that are signed once and reused:

- v0 (versioned) transactions and address-table lookups are parsed, and the
  instruction set is rendered per program: transfers, token transfers, memo
  bodies and offchain messages.
- A schema describes a program's instruction layout (discriminator, argument
  types and labels) and carries a signature with no transaction hash bound to
  it, so one blob covers every call of that shape instead of needing a fresh
  signature per transaction.
- x402 payment requests are clear-signed through the same path: the payment
  amount, asset and recipient are shown from schema-described fields rather
  than trusting host-supplied text.
- Anything without a verified schema still reaches the raw-data screen.
- unittests/firmware/solana.cpp covers v0 parsing, schema decode, argument
  rendering and the reject paths for malformed or unsigned schemas.
Uses the signed-metadata engine on the Ethereum path so a user sees what a
contract call actually does instead of a hex blob:

- EthereumTxMetadata carries the signed description and EthereumMetadataAck
  acknowledges it; LoadClearsignSigner installs a trust anchor for the current
  session only. Metadata that fails verification is dropped and the transaction
  falls back to raw display — it never degrades to a friendlier screen.
- EIP-712 typed data: domain/primary-type handling is corrected, the policy
  gate decides when a typed hash may be signed at all, and the recursion and
  length limits are enforced rather than assumed.
- EIP-7702 authorization lists are recognised and shown.
- ERC-20 and the bundled contract decoders (THORChain router, liquidity and
  swap proxies) check the selector against the argument shape before claiming
  to know what a call means.
- A factory reset clears loaded signers.
- unittests/firmware/eip712.cpp and the extended ethereum.cpp cover the typed
  data limits, selector mismatches and the metadata accept/reject paths.
Adds messages 1700-1703 so a KeepKey can issue the schema signatures the
clear-sign path verifies, instead of every schema having to come from an
offline signing ceremony.

- ClearsignAttestorGetPublicKey exports the attestor key; ClearsignAttestorSign
  signs a schema after showing the user what they are attesting to — program
  identity, discriminator, and each argument label with its type and width.
- Self-service attestation is gated behind advanced mode, so a default device
  cannot be talked into minting trust for an attacker's schema.
- The signed variant ships the built-in anchor table, so schemas issued this way
  verify on a stock device with nothing loaded.
- scripts/emulator/capture-clearsign-attestor.py reproduces the OLED sequence,
  and docs/security/evidence/7.15.0-rc21-clearsign-attestor holds the captured
  frames including the 44-character program id and 16-character label bounds.
Storage:
- The PIN key-derivation function is versioned so its cost can be raised
  without breaking existing wallets, with the migration path documented in
  docs/security/pin-kdf-v19-migration.md.
- A wallet created by bitcoin-only firmware is stamped, and multi-chain
  firmware refuses to touch it (storage_isBitcoinOnlyLocked) rather than
  silently operating on a wallet whose owner chose a reduced attack surface.
- Storage layout reserves the clear-sign identity block and zeroes it on read
  and on wipe, so nothing there can outlive a factory reset.
- Orchard key derivation and seed fingerprinting are reachable through storage
  with progress reporting, guarded by the privacy build flag.
- docs/security/anti-rollback-security-epoch-rfc.md records the proposed
  security-epoch scheme; it is a design note, nothing here implements it.

BIP-85:
- GetBip85Mnemonic derives a child mnemonic and displays it under constant
  power, with PIN and initialization checks. Always available — it is a seed
  derivation, not a coin engine.

Recovery and authenticator:
- The cipher-recovery wordlist permutation now borrows the shared frame arena
  instead of another multi-kilobyte stack buffer.
- wipeAuthData reports failure instead of always claiming success, a cancelled
  authenticator action is distinguishable from an error, and a cancelled
  passphrase prompt aborts the request instead of continuing without one.
…isplay

RNG:
- The RNG source selection is explicit and reportable, and a host can pull bulk
  samples for a health audit: a per-boot 64 KB budget replaces the
  press-per-kilobyte prompt that made auditing real hardware impossible.
  Scope is stated honestly in the code and docs — bulk output supports HEALTH
  testing (stuck/biased output, repeated buffers, transport caching, gross
  correlation), not a bound on the entropy of the generator's internal state.
- On an uninitialized device, one press unlocks the bulk audit; on an
  initialized device the confirm still gates entropy after the budget is spent.
- The emulator gets a real CSPRNG (lib/emulator/random.c, BCryptGenRandom on
  Windows) instead of libc random(), which CI now enforces.

Dice:
- ResetDevice can request on-device dice entropy: short press advances 1-6,
  long press confirms, undo is available, and the roll digest is confirmed
  before the seed is mixed. Pre-initialization only.
- The abort path disarms EntropyAck, so an aborted reset can no longer leave the
  device accepting host-supplied entropy for the seed.
- Evidence frames under docs/security/evidence/dice-entropy-reset, reproducible
  with scripts/emulator/capture-dice-flow.py.

Internal entropy is no longer displayed or returned: it is seed pre-image
material. display_random stays in the wire schema for host compatibility and is
ignored.
The device-side Orchard engine behind Zcash shielded sends: Orchard key
derivation from the wallet seed, note/action handling, RedPallas signing over
the Pallas curve, and unified-address encoding (ZIP-316).

- Signing is constant-time on the secret scalar path; the Pallas arithmetic
  comes from the pinned trezor-firmware fork and is compiled in only when the
  privacy engine is enabled, so the bitcoin-only image carries none of it.
- Progress is reported to the host during action signing — multi-action
  transactions take long enough that a silent device looks hung.
- messages-zcash proto/options are registered with the transport generator.

Handlers, host-visible messages, unit tests, the constant-time CI gates and
the coin-integration docs land in the Zcash wiring branch; this commit adds the
engine and its build wiring only.
Wires the Orchard engine to the host and puts it under test:

- ZcashSignPCZT / ZcashPCZTAction / ZcashGetOrchardFVK / transparent input and
  output / ZcashDisplayAddress handlers, registered privacy-guarded so the
  bitcoin-only image exposes none of them.
- unittests/firmware/zcash.cpp covers PCZT parsing, action signing and unified
  address encoding; a second binary links the engine against the host crypto
  so the Orchard paths run under the normal unit-test job.
- unittests/crypto/pallas_ct.cpp plus tools/check_pallas_api_boundary.py and
  tools/check_pallas_ct_disassembly.py enforce the constant-time contract at
  three levels: operation counters in the test build, an API boundary check on
  the sources, and a disassembly scan of the shipped ELF. CI runs the pinned
  fork's Pallas suite under Valgrind and ASan/UBSan as well.
- docs/coin-integration documents the PCZT flow, on-device unified addresses
  and the clear-sign handoff.
@BitHighlander

Copy link
Copy Markdown
Owner Author

CI queue note: this head was explicitly dispatched at https://github.com/BitHighlander/keepkey-firmware/actions/runs/31128231385 after the superseded 17-PR stack saturated the Actions queue. GitHub currently shows the jobs queued before runner assignment. The complete aggregate independently passed the CI-equivalent container suite locally: 405 firmware, 2 board, 18 crypto, and 7 Pallas constant-time tests, plus actionlint, gitleaks, the Pallas API-boundary gate, and git diff --check.

@BitHighlander
BitHighlander force-pushed the release/715-audit-03-recovery-entropy branch from e3457e0 to 39555bd Compare August 8, 2026 22:10
@BitHighlander

Copy link
Copy Markdown
Owner Author

Superseded by #364. Slices 04 (Zcash), 05 (chains) and 06 (Bitcoin/release security) are mutually dependent and do not compile apart: audit-04 failed every target on thortx.c:265: implicit declaration of 'thorchain_confirm_full_memo' (callee lands in 05), and audit-05 still failed the bitcoin-only and emulator builds on ethereum_address_checksum / hdnode_get_ethereum_pubkeyhash (resolved only by 06's variant gating). Splitting them by topic rather than by build dependency meant 4 of 6 merge points left develop non-compiling. They are now combined into #364, which is green. No content dropped.

@BitHighlander
BitHighlander deleted the release/715-audit-04-zcash branch August 9, 2026 04:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant