From 49dcca9951d6a7ed74a2025347a7cadb300c405c Mon Sep 17 00:00:00 2001 From: teyrebaz33 Date: Sat, 4 Apr 2026 13:04:39 +0300 Subject: [PATCH 1/2] docs: clarify sign_message contract across chains --- docs/02-signing-interface.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/02-signing-interface.md b/docs/02-signing-interface.md index 3ee4237b..0a23737f 100644 --- a/docs/02-signing-interface.md +++ b/docs/02-signing-interface.md @@ -57,7 +57,7 @@ An implementation that exposes `signAndSend`: ### `signMessage(request: SignMessageRequest): Promise` -Signs an arbitrary message (for authentication, attestation, or off-chain signatures like EIP-712). +Signs a message using the chain's message-signing convention. The exact bytes signed are chain-specific and may differ across chains. ```typescript interface SignMessageRequest { @@ -65,7 +65,6 @@ interface SignMessageRequest { chainId: ChainId; message: string | Uint8Array; encoding?: "utf8" | "hex"; - typedData?: TypedData; // EIP-712 typed data (EVM only) } interface SignMessageResult { @@ -74,12 +73,18 @@ interface SignMessageResult { } ``` -Message signing follows chain-specific conventions: -- **EVM**: `personal_sign` (EIP-191) or `eth_signTypedData_v4` (EIP-712) -- **Solana**: Ed25519 signature over the raw message bytes -- **Sui**: Intent-prefixed (scope=3) BLAKE2b-256 digest, Ed25519 signature -- **Cosmos**: ADR-036 off-chain signing +Current reference implementation behavior varies by chain: +- **EVM**: `personal_sign` (EIP-191) +- **Bitcoin**: Bitcoin Signed Message +- **Tron**: TRON-specific personal-message prefix +- **Sui**: personal-message intent hashing before signing +- **Cosmos**: hash-then-sign behavior - **Filecoin**: Blake2b-256 hash then secp256k1 signing +- **Solana**: Ed25519 signature over the raw message bytes +- **TON**: Ed25519 signature over the raw message bytes +- **XRPL**: unsupported in the current reference implementation + +Use `signTypedData` for EIP-712 typed structured data on EVM chains. ### `signTypedData(request: SignTypedDataRequest): Promise` From d474d50bb2526f38028e4745b3e690ff9b7b389e Mon Sep 17 00:00:00 2001 From: teyrebaz33 Date: Sun, 5 Apr 2026 02:35:17 +0300 Subject: [PATCH 2/2] docs: clarify sign_message contract across chains MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses the design questions raised in #183: 1. sign_message IS a user-facing cross-chain off-chain signing primitive, not a raw-byte escape hatch (use sign() for that). 2. It only exists where there is a recognized ecosystem convention. Chains without one SHOULD return CHAIN_NOT_SUPPORTED. 3. The interface is not a low-level utility — raw-byte signing under sign_message falsely implies interoperability that does not exist. 4. Transaction signing (sign/signAndSend) and off-chain message signing (signMessage) remain strictly separate. EIP-712 typed data is a third distinct operation (signTypedData). 5. Filecoin, Solana, and TON currently sign raw/hashed bytes without a recognized off-chain envelope — documented as a compatibility behavior, not the intended design. Added a chain behavior table, design intent section, and guidance for contributors adding new chains (e.g. Nano): a canonical specification is required before sign_message support can be added. Removed typedData from SignMessageRequest (use signTypedData instead). --- docs/02-signing-interface.md | 59 +++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/docs/02-signing-interface.md b/docs/02-signing-interface.md index 0a23737f..a3fc4a2a 100644 --- a/docs/02-signing-interface.md +++ b/docs/02-signing-interface.md @@ -7,7 +7,6 @@ ### `sign(request: SignRequest): Promise` Signs a transaction without broadcasting it. Returns the signed transaction bytes. - ```typescript interface SignRequest { walletId: WalletId; @@ -35,7 +34,6 @@ interface SignResult { ### `signAndSend(request: SignAndSendRequest): Promise` Signs, encodes, and broadcasts a transaction. - ```typescript interface SignAndSendRequest extends SignRequest { rpcUrl?: string; @@ -57,8 +55,7 @@ An implementation that exposes `signAndSend`: ### `signMessage(request: SignMessageRequest): Promise` -Signs a message using the chain's message-signing convention. The exact bytes signed are chain-specific and may differ across chains. - +Signs a message using the chain's recognized off-chain message-signing convention, where one exists. ```typescript interface SignMessageRequest { walletId: WalletId; @@ -69,27 +66,52 @@ interface SignMessageRequest { interface SignMessageResult { signature: string; - recoveryId?: number; // for secp256k1 recovery + recoveryId?: number; // for secp256k1 chains (EVM, Bitcoin, Tron) } ``` -Current reference implementation behavior varies by chain: -- **EVM**: `personal_sign` (EIP-191) -- **Bitcoin**: Bitcoin Signed Message -- **Tron**: TRON-specific personal-message prefix -- **Sui**: personal-message intent hashing before signing -- **Cosmos**: hash-then-sign behavior -- **Filecoin**: Blake2b-256 hash then secp256k1 signing -- **Solana**: Ed25519 signature over the raw message bytes -- **TON**: Ed25519 signature over the raw message bytes -- **XRPL**: unsupported in the current reference implementation +#### Design intent + +`signMessage` is a **user-facing, cross-chain off-chain signing primitive**. It is not a raw-byte signing escape hatch — use `sign` for that. + +The interface is intentionally restricted to chains that have a recognized, ecosystem-wide off-chain message signing convention. This means: + +- Signatures produced by `signMessage` on one chain are **not interoperable** with other chains, even if both use the same underlying curve. The chain-specific envelope (prefix, intent hash, domain tag, etc.) is what makes a signature meaningful and verifiable on that chain. +- Adding `signMessage` support for a new chain requires a **clearly defined, widely-adopted convention** for that chain — not just the ability to sign bytes. +- Chains without a recognized convention SHOULD return `CHAIN_NOT_SUPPORTED` rather than silently signing raw bytes, which would imply interoperability that does not exist. + +#### Behavior by chain + +| Chain | Convention | Bytes signed | +|-------|-----------|--------------| +| EVM | EIP-191 `personal_sign` | `\x19Ethereum Signed Message:\n` + length + message | +| Bitcoin | Bitcoin Signed Message | `\x18Bitcoin Signed Message:\n` + varint(len) + message | +| Tron | TRON personal-message prefix | `\x19TRON Signed Message:\n` + length + message | +| Sui | Personal message intent | Intent prefix (scope=3) + BCS-encoded message, BLAKE2b-256 hashed | +| Cosmos | ADR-036 | Amino-encoded `MsgSignData` with `chain_id: ""` | +| Filecoin | — | Blake2b-256 hash of message bytes, secp256k1 signed | +| Solana | — | Raw message bytes, Ed25519 signed | +| TON | — | Raw message bytes, Ed25519 signed | +| XRPL | unsupported | No recognized canonical convention yet | -Use `signTypedData` for EIP-712 typed structured data on EVM chains. +> **Note on Filecoin, Solana, and TON:** These chains currently sign raw or hashed bytes without a recognized off-chain message envelope. This behavior is preserved for compatibility but callers should be aware that the resulting signatures are not verifiable through any standard wallet verification flow on those chains. Future versions may introduce chain-specific conventions as they emerge. + +#### Adding support for new chains + +When adding `signMessage` for a new chain (e.g. Nano, Stellar, Aptos): + +1. Identify the chain's **canonical off-chain message signing specification** — a finalized EIP, BIP, or equivalent standards document. +2. If no specification exists, return `CHAIN_NOT_SUPPORTED` and document why in the chain plugin. +3. Do not sign raw bytes under `signMessage` — use `sign` for arbitrary byte signing. +4. Document the exact bytes signed in the chain plugin and in the table above. + +#### EIP-712 typed data + +For EIP-712 structured data on EVM chains, use `signTypedData` instead. The `signMessage` interface does not accept typed data — this separation keeps the API surface clean and avoids overloading a single method with two distinct signing semantics. ### `signTypedData(request: SignTypedDataRequest): Promise` Signs EIP-712 typed structured data. This is a dedicated operation separate from `signMessage` to provide a clean SDK interface for typed data signing without overloading the message signing API. - ```typescript interface SignTypedDataRequest { walletId: WalletId; @@ -99,7 +121,6 @@ interface SignTypedDataRequest { ``` The `typedDataJson` field must be a JSON string containing the standard EIP-712 fields: `types`, `primaryType`, `domain`, and `message`. - ```json { "types": { @@ -145,3 +166,5 @@ Current implementations do not provide a per-wallet nonce manager or explicit sa - [EIP-191: Signed Data Standard](https://eips.ethereum.org/EIPS/eip-191) - [EIP-712: Typed Structured Data](https://eips.ethereum.org/EIPS/eip-712) +- [ADR-036: Arbitrary Message Signing](https://docs.cosmos.network/main/build/architecture/adr-036-arbitrary-signature) +- [Bitcoin Signed Message](https://en.bitcoin.it/wiki/Message_signing)