Skip to content

feat(rpc,sdk,rest): expose 5 new message-signing methods#140

Merged
BitHighlander merged 3 commits into
developfrom
feat/message-signing-rpc
Apr 30, 2026
Merged

feat(rpc,sdk,rest): expose 5 new message-signing methods#140
BitHighlander merged 3 commits into
developfrom
feat/message-signing-rpc

Conversation

@BitHighlander
Copy link
Copy Markdown
Collaborator

Summary

Wires the new firmware 7.14.1 message-signing methods through every vault layer — RPC schema, Bun handlers, REST endpoints, SDK types, SDK methods.

Pairs with keepkey/hdwallet#38 (merged), which added the underlying `tronSignMessage` / `tronVerifyMessage` / `tronSignTypedHash` / `tonSignMessage` / `solanaSignOffchainMessage` methods, and firmware integration branch `BitHighlander/keepkey-firmware:release/7.14.1` (PRs #221-#224, #217).

New API surface

REST endpoints

Method Path Spec
POST `/tron/sign-message` TIP-191 `personal_sign` (`keccak256("\x19TRON Signed Message:\n" + len + msg)`)
POST `/tron/verify-message` TIP-191 verify, returns `{verified: boolean}`
POST `/tron/sign-typed-hash` TIP-712 hash mode (32-byte domain + message hashes)
POST `/ton/sign-message` Bare Ed25519 over message bytes (firmware fences behind `AdvancedMode` policy)
POST `/solana/sign-offchain-message` Domain-separated envelope: `'\xff' + 'solana offchain' + version + format + length + msg`

SDK methods

  • `sdk.tron.tronSignMessage(params)` → `{ address, signature }`
  • `sdk.tron.tronVerifyMessage(params)` → `{ verified }`
  • `sdk.tron.tronSignTypedHash(params)` → `{ address, signature }`
  • `sdk.ton.tonSignMessage(params)` → `{ publicKey, signature }`
  • `sdk.solana.solanaSignOffchainMessage(params)` → `{ publicKey, signature }`

Files changed

  • `shared/rpc-schema.ts` — 5 new request entries (`any`/`any` matching existing pattern)
  • `bun/index.ts` — 5 RPC handlers (engine.wallet pass-through, hex-encodes signatures, wraps in `emuSigningOp` for emulator runs)
  • `bun/schemas.ts` — 5 zod request schemas (hex/text encoding selector, 32-byte hash regex for TIP-712, version + format range constraints for Solana off-chain)
  • `bun/rest-api.ts` — 5 REST endpoints
  • `keepkey-sdk/src/types.ts` — 9 new TypeScript interfaces
  • `keepkey-sdk/src/index.ts` — 5 new SDK methods on `solana` / `tron` / `ton` namespaces
  • `modules/hdwallet` bumped to `bf76b0a4` (PR fix: Zig 0.15.2 DrawTextW compat for wrapper launcher #38 merge commit on `keepkey/hdwallet:master`)

Test plan

  • CI green (lint + tsc on both keepkey-sdk and keepkey-vault)
  • Connect a real KeepKey running firmware 7.14.1 (or the integration emulator) and exercise each REST endpoint:
    • `POST /tron/sign-message` with a UTF-8 message, then `POST /tron/verify-message` with the returned address+signature → `{verified: true}`
    • `POST /tron/verify-message` with a corrupted signature → `{verified: false}` (NOT a 5xx)
    • `POST /tron/sign-typed-hash` with a 31-byte domain hash → 4xx (zod schema regex)
    • `POST /ton/sign-message` with AdvancedMode policy disabled → `Failure_ActionCancelled` (firmware enforces the gate)
    • `POST /solana/sign-offchain-message` with format=2 → 4xx (zod constraint)
    • Off-device verify the Solana signature against the spec envelope (NOT the bare message) — the load-bearing assertion that catches firmware envelope-construction regressions

Known caveats

  • With stock 7.14.0 firmware, all 5 endpoints return `Failure_UnknownMessage` since the underlying MessageType IDs (1404-1408, 1504-1505, 756-757) don't exist on-device. Live signing requires firmware 7.14.1+.
  • TON `/ton/sign-message` is intentionally fenced behind the `AdvancedMode` policy on the firmware side — no domain separation. The proper TON Connect `ton_proof` envelope is a follow-up (separate proto + handler).
  • Solana `/solana/sign-offchain-message` is the domain-separated path; it does NOT need an AdvancedMode gate. Off-device verifiers MUST reconstruct the envelope (`\xff` + "solana offchain" + version + format + length + msg) and verify against it, not the bare message.

Pairs with keepkey/hdwallet#38 (merged) which added the underlying
hdwallet methods and firmware PRs #221-#224 / #217 on the integration
release/7.14.1 branch.

New surface exposed at every layer:

shared/rpc-schema.ts: 5 new request entries
  - tronSignMessage, tronVerifyMessage, tronSignTypedHash
  - tonSignMessage
  - solanaSignOffchainMessage

bun/index.ts: 5 RPC handlers (engine.wallet.X pass-through with
  emuSigningOp wrapper for emulator runs, hex-encoding for transport)

bun/schemas.ts: 5 zod request schemas with hex/text encoding selectors,
  domain/message hash length regex (TIP-712 must be exactly 32 bytes
  each), version + format range constraints (Solana off-chain spec)

bun/rest-api.ts: 5 REST endpoints
  - POST /tron/sign-message       (TIP-191 personal_sign)
  - POST /tron/verify-message     (TIP-191 verify, returns {verified})
  - POST /tron/sign-typed-hash    (TIP-712 hash mode)
  - POST /ton/sign-message        (bare Ed25519, AdvancedMode-gated firmware)
  - POST /solana/sign-offchain-message (domain-separated envelope)

keepkey-sdk/src/types.ts: 9 new TypeScript interfaces
keepkey-sdk/src/index.ts: 5 new SDK methods on solana / tron / ton namespaces

modules/hdwallet bumped to bf76b0a4 (PR #38 merge commit on master)
which provides the underlying tronSignMessage / tronVerifyMessage /
tronSignTypedHash / tonSignMessage / solanaSignOffchainMessage methods.

Verified locally: hdwallet builds clean, vault tsc --noEmit shows no
new errors (1 pre-existing minimatch type config issue unchanged).

Live signing requires firmware 7.14.1+; with stock 7.14.0 the device
will return Failure_UnknownMessage.
1. Schema doc comments inverted is_text semantics (the implementation
   was correct, the comments said the opposite). Fixed all 5 schemas to
   match the SDK type docs:
     'Default: encoded as UTF-8 bytes. If is_text=false, expect hex'
   This matters because zod schema doc comments are what surface in any
   auto-generated OpenAPI / type-derived docs.

2. Empty-message handling was inconsistent: TRON sign/verify used min(0)
   while TON / Solana off-chain used min(1). Aligned to min(1) across all
   five — empty signing payloads are almost always a caller bug, and
   firmware-level zero-length support remains exercisable via the SDK
   directly if needed.

3. Strict hex parsing: Buffer.from(str, 'hex') silently truncates on
   non-hex chars or odd length, which surfaces downstream as confusing
   wrong-length errors. Added a parseHex(input, label, expectedBytes?)
   helper that throws a clear 400 up front. Replaced inline hex parsing
   in all 5 endpoints. Also added a HEX_SIG_65 regex on
   TronVerifyMessageRequest.signature so zod rejects malformed sigs
   before the handler runs.

4. Added decodeMessageBody() helper to centralize the
   is_text-defaults-true / hex-when-false decoding pattern that was
   duplicated in 4 of 5 handlers.

Pre-existing review nit (RPC layer accepts only camelCase addressNList,
not snake_case address_n) deliberately left as-is for parity with the
existing solanaSignTx / tonSignTx handlers — would be a fork of an
existing convention rather than a fix.
5. Solana off-chain 1212-byte ceiling now enforced at two layers:
   - Schema: SolanaSignOffchainMessageRequest.message capped at 2424
     chars (worst case = hex bytes). Defense-in-depth + DoS protection.
   - Handler: post-decodeMessageBody check against 1212 bytes (the
     actual spec ceiling for formats 0/1). Surfaces with a clear 400
     pre-USB-roundtrip and points at the off-chain spec, instead of the
     opaque firmware Failure that was the only signal before.

6. toHex helper consolidates the
       v instanceof Uint8Array ? Buffer.from(v).toString('hex') : v
   pattern in rest-api.ts (4 sites in the new handlers) and a parallel
   bytesToHex helper in bun/index.ts (4 sites). Pre-existing inline
   patterns elsewhere in both files left untouched per the
   surgical-changes rule — this PR only consolidates its own duplicates.

Both flagged as 'Style only' / optional cleanups in the review;
applying both since the user asked for the touch-up before merge.

Item 4 (signature/message hex truncation) is already covered by the
parseHex helper added in 4a8937c — the runtime check rejects garbage
hex with a clear 400 across all 5 endpoints. The hex-mode 'message'
field can't easily get a zod regex since its semantics are conditional
on is_text, but the runtime check covers the same ground.
@BitHighlander BitHighlander merged commit 42f529e into develop Apr 30, 2026
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