Skip to content

feat(keepkey): TIP-191 / TIP-712 / TON / Solana-offchain message-signing methods#38

Merged
BitHighlander merged 5 commits into
masterfrom
feat/message-signing-parity
Apr 29, 2026
Merged

feat(keepkey): TIP-191 / TIP-712 / TON / Solana-offchain message-signing methods#38
BitHighlander merged 5 commits into
masterfrom
feat/message-signing-parity

Conversation

@BitHighlander
Copy link
Copy Markdown
Collaborator

Summary

Adds KeepKey wallet support for the firmware 7.14.1 message-signing features. Hand-rolled jspb.Message subclasses (wire-compatible with firmware nanopb), wrapper functions, KeepKey class methods, and `hdwallet-core` type extensions.

Pairs with these firmware PRs (currently against BitHighlander/keepkey-firmware develop, headed to upstream after fork-side validation):

New API

TRON

  • `tronSignMessage(msg)` — TIP-191 `personal_sign`. Returns `{ address, signature }` (65-byte recoverable secp256k1)
  • `tronVerifyMessage(msg)` — verifies a TIP-191 signature against a claimed Base58Check address. Returns `boolean`
  • `tronSignTypedHash(msg)` — TIP-712 hash mode. Host pre-computes `domain_separator_hash` and `message_hash`; device assembles `keccak256("\x19\x01" || ds_hash || msg_hash)` and signs

TON

  • `tonSignMessage(msg)` — bare Ed25519 over message bytes. Returns `{ publicKey, signature }` (32-byte pubkey + 64-byte sig)
  • ⚠ Firmware fences this behind the `AdvancedMode` policy — bare Ed25519 has no domain separation. With the policy disabled (default) this call returns `Failure`. The proper domain-separated path is TON Connect's `ton_proof` envelope (separate proto + handler, not yet implemented)

Solana

  • `solanaSignOffchainMessage(msg)` — domain-separated Ed25519 message signing. Firmware constructs the spec envelope:
    ```
    '\xff' || 'solana offchain' || version:u8 || format:u8 || length:u16 LE || message
    ```
    and Ed25519-signs it. Format 2 (extended UTF-8) is rejected device-side; only formats 0 (ASCII) and 1 (UTF-8 limited, max 1212 bytes) are supported. No AdvancedMode gate — the envelope's leading `0xFF` byte is invalid as a Solana transaction prefix, providing the domain separation that bare `solanaSignMessage` lacks. This is the proper fix for the SignMessage AdvancedMode band-aid

Files

  • `packages/hdwallet-keepkey/src/tron.ts` (+686 LOC): 5 jspb classes + 3 wrapper functions, `registerTronMessages()` updated
  • `packages/hdwallet-keepkey/src/ton.ts` (+270): 2 jspb classes + 1 wrapper function, `registerTonMessages()` updated
  • `packages/hdwallet-keepkey/src/solana.ts` (+323): 2 jspb classes + 1 wrapper function, `registerSolanaMessages()` updated
  • `packages/hdwallet-keepkey/src/keepkey.ts` (+22): 5 new public methods on the KeepKey class
  • `packages/hdwallet-core/src/{tron,ton,solana}.ts` (+69): 9 new TypeScript interfaces + extended Wallet interfaces

Test plan

  • `yarn build` green (verified locally — 2.35s)
  • CI lint + test suite pass
  • Connect a real KeepKey running firmware 7.14.1 (or the integration emulator) and exercise each method end-to-end:
    • TRON: round-trip `sign + verify` succeeds; verify with off-device tronweb
    • TIP-712: sign known hash pair, recover signer matches `tronGetAddress`
    • TON: with AdvancedMode enabled, verify the Ed25519 signature with libsodium; with disabled, expect `Failure`
    • Solana off-chain: off-device verify against the envelope (NOT bare message); verify formats 0/1 succeed, format 2 rejected, length>1212 rejected, version!=0 rejected

Adds KeepKey wallet support for the firmware 7.14.1 message-signing
features:

TRON (TIP-191 + TIP-712):
- TronSignMessage / TronMessageSignature jspb shims (1404/1405)
- TronVerifyMessage jspb shim (1406)
- TronSignTypedHash / TronTypedDataSignature jspb shims (1407/1408)
- tronSignMessage / tronVerifyMessage / tronSignTypedHash wrapper functions
- Matching KeepKey class methods + core type extensions

TON:
- TonSignMessage / TonMessageSignature jspb shims (1504/1505)
- tonSignMessage wrapper + KeepKey method + core types
- Note: firmware fences this behind AdvancedMode policy until TON
  Connect ton_proof envelope is added — caller will get a Failure
  response if the policy is disabled

Solana off-chain message:
- SolanaSignOffchainMessage / SolanaOffchainMessageSignature jspb shims (756/757)
- solanaSignOffchainMessage wrapper + KeepKey method + core types
- This is the domain-separated path; firmware constructs the
  '\xff' || 'solana offchain' || version || format || length || msg
  envelope and Ed25519-signs it. Format 2 (extended UTF-8) is
  rejected device-side; only formats 0 (ASCII) and 1 (UTF-8 limited,
  max 1212 bytes) are supported.

All wrappers follow the existing transport.lockDuring + transport.call
pattern used by tronSignTx / tonSignTx / solanaSignTx. Hand-rolled
jspb.Message subclasses are wire-compatible with the firmware's
generated nanopb encoding.
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hdwallet-sandbox Ready Ready Preview, Comment Apr 29, 2026 11:33pm

Request Review

The previous implementation relied on 'transport.call() returns Success
when verified, throws on Failure', but that broke the Promise<boolean>
contract — callers got a rejected promise on signature mismatch instead
of a resolved `false`.

Mirror btcVerifyMessage / ethVerifyMessage: catch
MESSAGETYPE_FAILURE and return false. ActionCancelled is thrown by
transport.call() as a distinct core.ActionCancelled instance (no
message_enum), so it bubbles past the catch and continues to throw —
keeping cancel flow distinguishable from sig mismatch.

Reported in #38 review.
The migration in 58272dd ('ci: add GitHub Actions CI, replace CircleCI
with placeholder') moved CI from CircleCI to GitHub Actions but only
copied over yarn build + yarn lint — yarn test (49 tests across 4 files)
and yarn test:integration silently disappeared. The .circleci/config.yml
was left as a no-op placeholder, so the green 'ci/circleci: noop' check
on every PR has been giving the false impression that tests were running.

Add 'yarn test --runInBand --coverage=false' back to the workflow.
Integration tests (kkemu sidecar) are a separate follow-up since they
need a kkemu image source in CI — currently kktech/kkemu:latest comes
from dockerhub and isn't easily reproducible from a feature branch.
34 new tests across 3 files covering all 9 new jspb classes + 5
wrapper functions:

  tron-message.test.ts (20 tests)
    - registry: TronSignMessage/MessageSignature/VerifyMessage/
      SignTypedHash/TypedDataSignature (1404-1408) all registered
    - jspb round-trip: serializeBinary → deserializeBinaryFromReader
      preserves all fields for each class (incl. empty-message + domain-only
      typed-hash edge cases)
    - tronSignMessage wrapper: success path + UTF-8 string encoding
    - tronVerifyMessage wrapper:
        * Success → true
        * MESSAGETYPE_FAILURE thrown by transport.call → returns false
          (regression test for the Promise<boolean> contract bug
          caught in PR review)
        * core.ActionCancelled → bubbles past the catch (cancel is
          distinguishable from sig mismatch)
    - tronSignTypedHash wrapper: 32-byte hash length validation +
      success path

  ton-message.test.ts (7 tests)
    - registry: TonSignMessage/MessageSignature (1504/1505)
    - jspb round-trip
    - tonSignMessage wrapper: success + ActionCancelled bubbling
      (firmware AdvancedMode gate path)

  solana-offchain.test.ts (7 tests)
    - registry: SolanaSignOffchainMessage/OffchainMessageSignature (756/757)
    - jspb round-trip incl. 1212-byte boundary (spec ceiling for fmt 0/1)
    - solanaSignOffchainMessage wrapper: UTF-8 encoding,
      version + message_format forwarding, success path

Total jest run: 49 → 83 (all pass locally with yarn test).
@BitHighlander BitHighlander merged commit bf76b0a into master Apr 29, 2026
4 checks passed
@BitHighlander BitHighlander deleted the feat/message-signing-parity branch April 29, 2026 23:37
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