Skip to content

7.15 audit 2/6: authenticated clear-signing - #360

Closed
BitHighlander wants to merge 5 commits into
release/715-audit-01-foundationfrom
release/715-audit-02-clearsigning
Closed

7.15 audit 2/6: authenticated clear-signing#360
BitHighlander wants to merge 5 commits into
release/715-audit-01-foundationfrom
release/715-audit-02-clearsigning

Conversation

@BitHighlander

Copy link
Copy Markdown
Owner

What changed

  • verifies signed transaction metadata on-device
  • adds Solana v0 clear-signing and reusable KKSOLSC1 instruction schemas
  • adds EVM clear-sign descriptions and schema-attestor support
  • rejects overflowing EIP-712 integer/bytes widths
  • enforces fixed EIP-712 array cardinality
  • keeps parser, schema, attestation, and negative regression tests with the implementation

Why

These features share one trust boundary: host-provided descriptions are useful only when the device can authenticate their schema and bind every displayed value to the signed payload.

Validation

  • aggregate six-bundle container build and unit suite passed
  • new EIP-712 overflow and fixed-array regressions passed
  • existing malformed KKSOLSC1 parser and attestor issuer/verifier signature-vector tests passed
  • git diff --check

Stack: 2 of 6. Review only the diff from release/715-audit-01-foundation.

@BitHighlander

Copy link
Copy Markdown
Owner Author

CI queue note: this head was explicitly dispatched at https://github.com/BitHighlander/keepkey-firmware/actions/runs/31128230883 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.

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.
@BitHighlander
BitHighlander force-pushed the release/715-audit-02-clearsigning branch from 53b14bb to 6c27e11 Compare August 8, 2026 22:10
@BitHighlander

Copy link
Copy Markdown
Owner Author

Superseded by #364, which now carries the whole 7.15 stack against develop.

Once the CI gate stopped short-circuiting the build (the libc-random() invariant was asserted three slices before the fix that satisfies it, so check-submodules failed and every build/test job was SKIPPED), these branches compiled for the first time — and none of them compile. The topic-based split cuts across at least three signature/caller pairs:

  • audit-01: app_layout.h declares layout_cipher with 3 args; recovery_cipher.c:380 still calls it with 2. Caller is fixed in the recovery slice.
  • audit-02 / audit-03: thortx.c:265: implicit declaration of 'thorchain_confirm_full_memo' — callee lands in the chains slice.
  • audit-05: ethereum_address_checksum / hdnode_get_ethereum_pubkeyhash — resolved only by the bitcoin-only variant gating in 06.

The dependency graph is a tangle, not a chain, so re-slicing it by build order means re-authoring the release. Only the complete set compiles, and #364 is green. No content is dropped.

@BitHighlander
BitHighlander deleted the release/715-audit-02-clearsigning 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