feat(signing): Tron/TON/Solana message signing via vault REST (firmware 7.14.1+)#56
Merged
BitHighlander merged 2 commits intodevelopfrom Apr 30, 2026
Merged
feat(signing): Tron/TON/Solana message signing via vault REST (firmware 7.14.1+)#56BitHighlander merged 2 commits intodevelopfrom
BitHighlander merged 2 commits intodevelopfrom
Conversation
…re 7.14.1+)
Wire the 5 message-signing endpoints landed in vault commit 7e6dfc9:
POST /tron/sign-message (TIP-191 personal_sign)
POST /tron/verify-message (TIP-191 verify)
POST /tron/sign-typed-hash (TIP-712 hash mode)
POST /ton/sign-message (bare Ed25519, AdvancedMode-gated)
POST /solana/sign-offchain-message (domain-separated envelope)
Replaces the "not yet supported" throw at tronHandler.ts:702 and adds
parallel surfaces on TON and Solana.
New: chrome-extension/src/background/firmware.ts — shared 7.14.1+ gate
called by every new handler before going to the device. 7.14.0 was an
internal stop-gap that never shipped publicly; treat the minimum as
7.14.1 so users on 7.14.0 see a clean upgrade prompt instead of a
Failure_UnknownMessage round-trip. Earlier firmware (7.13.x and below)
gets the same upgrade prompt.
Tron handler:
- tron_signMessage / signMessage (V1, hex) and signMessageV2 (V2,
UTF-8): both go through /tron/sign-message; V1 vs V2 only differ in
wire encoding of the message bytes. Returns 0x-prefixed 65-byte
recoverable signature to match TronWeb's expected return shape.
- tron_verifyMessage / verifyMessage / verifyMessageV2: pure utility,
doesn't touch the device — vault recovers the signer off-device.
- tron_signTypedHash / _signTypedData: TIP-712 hash mode only. Caller
pre-computes the 32-byte domainSeparator + message hashes; we don't
ship a struct → hash implementation. dApps using TronWeb's
_signTypedData should hash client-side then call this method.
Injected tron-provider.ts: signMessage / signMessageV2 /
verifyMessage / verifyMessageV2 now route to the backend instead of
throwing.
TON handler: ton_signMessage / signMessage. Firmware fences this behind
the AdvancedMode policy — when disabled, the vault returns a 4xx with
"AdvancedMode" in the body. Detect that and rewrite the error into an
actionable prompt ("Open Vault → Settings → Security and enable
Advanced Mode") instead of surfacing the raw HTTP failure.
Solana handler: solana_signOffchainMessage as a NEW method (does not
replace solana_signMessage — Wallet Standard expects bare-message
verification). The signature returned here is over the off-chain
envelope:
"\xff" || "solana offchain" || version (1B) || format (1B)
|| length (2B LE) || message
Verifiers MUST reconstruct the envelope and Ed25519-verify against
THAT, not the bare message bytes. The handler attaches a
`verifyAgainst: 'envelope'` hint to the approval event so any future
approval-card UI can warn the user about the verification model.
Defaults: version 0, messageFormat 1 (UTF-8). Caps at the firmware's
1212-byte limit.
Type-check: zero new errors introduced (verified vs. baseline on
develop).
…ility
1. Tron verifyMessageV2 was claiming TronWeb V2 compatibility but the
shapes don't match: TronWeb V2 is verifyMessageV2(message, sig)
returning the recovered base58 address; our endpoint takes an
`address` arg and returns boolean. Standard dApps would fail on the
2-arg call. Fix: drop verifyMessage / verifyMessageV2 from the
tronWeb.trx shim entirely (verification is client-side anyway —
TronWeb's static utilities cover it). Drop the verifyMessageV2 case
alias from the background handler. Keep tron_verifyMessage as a
non-standard utility reachable only via tronLink.request, with the
shape changed to a single-object param so the contract is
self-documenting.
2. The handler aliased _signTypedData to the hash-only API, but
tronWeb.trx has no _signTypedData method exposed and TronWeb's real
_signTypedData(domain, types, value) takes the full struct, not
pre-computed hashes. Fix: drop the _signTypedData alias from the
handler and from the docs — keep only tron_signTypedHash. Doc
comment now explicitly notes we don't ship a struct → hash impl.
3. solana_signOffchainMessage was unreachable from page context — the
Wallet Standard registration only advertised the three solana:*
features and there's no window.keepkey.solana provider. Fix: add a
vendor-namespaced 'keepkey:signOffchainMessage' feature to the
wallet-standard registration. dApps can feature-detect via
wallet.features['keepkey:signOffchainMessage'] and call
.signOffchainMessage({ message, version?, messageFormat? }). Solana
Wallet Standard explicitly reserves non-`solana:` namespaces for
vendor extensions, so this is the canonical way to expose
non-standard primitives.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Wires the 5 message-signing endpoints landed in vault commit
7e6dfc9:POST /tron/sign-messagePOST /tron/verify-messagePOST /tron/sign-typed-hashPOST /ton/sign-messagePOST /solana/sign-offchain-messageReplaces the
not yet supportedthrow attronHandler.ts:702and adds parallel surfaces on TON and Solana.Firmware gate
New
chrome-extension/src/background/firmware.tsenforces a 7.14.1 minimum on every new method. 7.14.0 was an internal stop-gap that never shipped publicly — treat it as nonexistent so anyone on it sees a clean upgrade prompt instead of aFailure_UnknownMessageround-trip. Same prompt for 7.13.x and earlier.Per-chain notes
Tron
tronWeb.trx.signMessage(V1, hex) andtronWeb.trx.signMessageV2(V2, UTF-8) now route to the backend instead of throwing — only the wire encoding of the message bytes differs between V1 and V2.tron_verifyMessageis reachable only viatronLink.request({ method: 'tron_verifyMessage', params: [{ address, signature, message, isText? }] }). We deliberately do NOT exposeverifyMessage/verifyMessageV2ontronWeb.trx: TronWeb V2's contract isverifyMessageV2(message, sig)returning the recovered base58 address, and our endpoint shape (address required, boolean returned) doesn't match. Verification is client-side anyway — TronWeb's static utilities cover it.tron_signTypedHashis hash-only. Caller pre-computes the two 32-byte hashes per the TIP-712 spec and passes them in. We do NOT ship a struct → hash implementation, so we do NOT expose_signTypedData/signTypedDataontronWeb.trxwhere the contract takes(domain, types, value).TON — Firmware fences
ton_signMessagebehind the AdvancedMode policy. When disabled, the vault returns a 4xx withAdvancedModein the body; the handler rewrites that into an actionable prompt (Open Vault → Settings → Security and enable Advanced Mode).Solana —
solana_signOffchainMessageis a NEW method exposed to dApps via a vendor-namespaced Wallet Standard feature,keepkey:signOffchainMessage. dApps feature-detect:solana_signMessage(Wallet Standard bare-message) is unchanged. The signature returned by the off-chain method is over the envelope:Verifiers MUST reconstruct the envelope and Ed25519-verify against THAT, not the bare bytes. The approval event carries a
verifyAgainst: 'envelope'hint for any future approval-card UI. Defaults: version 0, messageFormat 1 (UTF-8); 1212-byte cap matches firmware.Test plan
tronWeb.trx.signMessageV2('hello')from a dApp prompts approval and returns a 0x-prefixed 65-byte signaturetronWeb.trx.signMessage('0xdeadbeef')(V1, hex) prompts approval and returns a 0x-prefixed 65-byte signaturetronLink.request({ method: 'tron_verifyMessage', params: [{ address, signature, message, isText: true }] })returnstruefor a valid round-trip andfalsefor a tampered messagetronLink.request({ method: 'tron_signTypedHash', params: [{ domainSeparatorHash, messageHash }] })with two 32-byte hex hashes signs and returns a recoverable signaturetronWeb.trx._signTypedDataandtronWeb.trx.verifyMessageV2are intentionally absent (no compat claim)ton_signMessagewith AdvancedMode disabled surfaces the "enable Advanced Mode" prompt (no opaque 4xx)ton_signMessagewith AdvancedMode enabled signs and returns{ publicKey, signature }wallet.features['keepkey:signOffchainMessage'].signOffchainMessage({ message: 'hello' })returns{ publicKey, signature }; verifying against the bare message FAILS, verifying against the reconstructed envelope succeeds🤖 Generated with Claude Code