feat: signMessage end-to-end and Sign-In-With-Canton over WalletConnect#1692
Closed
ganchoradkov wants to merge 13 commits into
Closed
feat: signMessage end-to-end and Sign-In-With-Canton over WalletConnect#1692ganchoradkov wants to merge 13 commits into
ganchoradkov wants to merge 13 commits into
Conversation
…alletConnect Signed-off-by: Gancho Radkov <ganchoradkov93@gmail.com>
Signed-off-by: Gancho Radkov <ganchoradkov93@gmail.com>
Signed-off-by: Marc Juchli <marc.juchli@digitalasset.com>
mjuchli-da
reviewed
Apr 30, 2026
Contributor
mjuchli-da
left a comment
There was a problem hiding this comment.
I've added the introduced method signMessage to the openrpc-signing specification (otherwise these types are not available to the signing drivers). Given that we only support this method for the WalletKernel (internal, non-prod) use case, I'm wondering if this is needed. In addition, we should likely make the controller method of the user api a bit more secure:
- require user approval
- restrict arbitrary message signing
I will make a second pass of this PR on Monday and address the mentioned points in more detail.
mjuchli-da
reviewed
Apr 30, 2026
Signed-off-by: Marc Juchli <marc.juchli@digitalasset.com>
Signed-off-by: Marc Juchli <marc.juchli@digitalasset.com>
Signed-off-by: Marc Juchli <marc.juchli@digitalasset.com>
pawelstepien-da
previously approved these changes
May 6, 2026
|
|
||
| /** | ||
| * This interface represents the SIWX message identifier. | ||
| * Here must contain the request id and the timestamps. |
Contributor
There was a problem hiding this comment.
If all fields are required, then maybe we should set them as non-optional in the interface?
Signed-off-by: Marc Juchli <marc.juchli@digitalasset.com>
Signed-off-by: Marc Juchli <marc.juchli@digitalasset.com>
Closed
Signed-off-by: Marc Juchli <marc.juchli@digitalasset.com>
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.
feat(user-api + sdk):
signMessageend-to-end and Sign-In-With-Canton over WalletConnectSummary
Adds first-class arbitrary message signing to the wallet stack and uses it to implement a Sign-In-With-X style auth flow ("Sign In With Canton") on the dApp SDK's WalletConnect adapter.
Two cohesive changes shipped together:
signMessageon the signing driver + user-api — wallets can now sign arbitrary UTF-8 messages with their Ed25519 key without going through the prepared-transaction pipeline.signInWithCanton, the adapter automatically issues acanton_signMessagerequest right after the WC session is approved, returning a SIWX-formatted message + signature to the dApp.End-to-end flow
sequenceDiagram autonumber participant dApp as dApp<br/>(examples/ping) participant Adapter as WalletConnectAdapter<br/>(sdk/dapp-sdk) participant WC as WalletConnect<br/>relay participant Wallet as Wallet<br/>(examples/walletconnect) participant Gateway as Gateway user-api<br/>(wallet-gateway/remote) participant Driver as WALLET_KERNEL driver<br/>(core/signing-internal) Note over dApp,Adapter: dApp configures signInWithCanton<br/>{ domain, uri, version, nonce, ... } dApp->>Adapter: connect() Adapter->>WC: signClient.connect() WC-->>Adapter: pairing URI Adapter-->>dApp: showUriInPopup(uri) (QR + URI) Wallet->>WC: pair(uri) WC->>Wallet: session_proposal Wallet-->>WC: approve(session) WC-->>Adapter: session approved Note over Adapter: signInWithCanton is set →<br/>compose SIWX message Adapter->>Adapter: composeSIWXMessage({domain, uri, nonce,<br/>account, chainId, ...}) Adapter->>WC: request canton_signMessage<br/>{ message } WC->>Wallet: session_request canton_signMessage Wallet->>Gateway: callUserApi("signMessage",<br/>{ message, partyId? }) Gateway->>Gateway: resolve wallet (primary or by partyId)<br/>assert signingProviderId === WALLET_KERNEL Gateway->>Driver: driver.signMessage({message, keyIdentifier:{publicKey}}) Driver->>Driver: lookup privateKey by publicKey<br/>nacl.sign.detached(utf8(message), sk) Driver-->>Gateway: { signature } Gateway-->>Wallet: { signature, publicKey } Wallet-->>WC: respond { signature, publicKey } WC-->>Adapter: response Adapter->>dApp: onSignInWithCanton({ requestId, nonce,<br/>account, chainId, message,<br/>signature, publicKey })Motivation
Today the gateway only knows how to sign prepared Canton transaction hashes via
sign/execute. dApps that want lightweight off-chain proofs (login, ownership challenges, EIP-4361-style auth) had no path. This PR closes that gap and demonstrates the canonical use case (SIWX) on the WalletConnect adapter so any wallet implementingcanton_signMessageworks out of the box.Changes
1. Signing driver —
signMessagebecomes part of the interfacecore/signing-libsignMessage(message, privateKey)(Ed25519 over UTF-8 bytes viatweetnacl).SignMessageParams,SignMessageResult).core/signing-internal(WALLET_KERNEL) — implementssignMessageend-to-end (looks up the private key bykeyIdentifier.publicKey, signs, returns base64 signature). Keeps the transaction history clean — message signatures are not persisted asTransactionrows.core/signing-fireblocks,core/signing-blockdaemon— returnnot_allowedstubs for now (custodial providers don't currently expose arbitrary message signing).2. User API — new
signMessagemethodapi-specs/openrpc-user-api.json— adds:signature— base64 Ed25519 over the UTF-8 bytes of the message.publicKey— the public key of the wallet that signed (so the dApp can verify without an extra round-trip).The spec docstring notes that the bytes are signed verbatim — domain separation is the caller's responsibility (see "Security notes" below).
wallet-gateway/remote/src/user-api/controller.ts:partyIdis omitted).WALLET_KERNELwallets with a clear error (custodial providers go through their own SDK).driver.signMessage(...), which signs the UTF-8 bytes of the message directly. Callers are expected to embed any domain separation (e.g. EIP-4361 / SIWX text — seecomposeSIWXMessage) in the message itself.core/wallet-user-rpc-client/*,wallet-gateway/remote/src/user-api/rpc-gen/*— regenerated client + server typings.3. WalletConnect adapter — Sign In With Canton
sdk/dapp-sdk/src/adapter/walletconnect-adapter.tscanton_signMessageto the supported methods.signInWithCantonis set, the adapter:composeSIWXMessage.canton_signMessageover WC.onSignInWithCantonwith{ requestId, nonce, account, chainId, message, signature, publicKey }(or anerrorpayload on failure).sdk/dapp-sdk/src/util.ts— addscomposeSIWXMessage(...)that produces the canonical SIWX text block (domain, address, statement, URI, version, chain id, nonce, optional issuedAt / expirationTime / notBefore / requestId / resources).4. Examples wired up
examples/walletconnect/src/walletkit/handler.ts— routescanton_signMessagerequests tocallUserApi('signMessage', params)instead of the dApp API.examples/ping/src/hooks/useConnect.ts— demonstrates configuringWalletConnectAdapterwithsignInWithCanton+onSignInWithCanton.Security notes
signMessagesigns the UTF-8 bytes verbatim. The SIWX flow added in this PR usescomposeSIWXMessage, which produces an EIP-4361-style text block starting with<domain> wants you to sign in with your Canton account:— that text cannot collide with the 32-byte hashes consumed bysign/execute. If you build a custom flow on top ofsignMessage, make sure your message format can't be confused with a prepared-transaction hash.signMessagefor non-WALLET_KERNELwallets, so custodial flows aren't silently bypassed.sign.Files changed
How to test
Manual — end-to-end SIWX flow
yarn start:localnet # in its own terminal yarn start:allhttp://localhost:3030) and create awallet-kernelwallet (the only provider that supports message signing today).onSignInWithCanton: { signature, publicKey, message, ... }— that's the SIWX result coming back from the gateway via WC.Backward compatibility
signInWithCantonis unchanged.wallet-kernelis the only provider that returns a signature today; other providers respond withnot_allowed, which dApps can detect.Follow-ups (not in this PR)
signMessagefor Fireblocks / Blockdaemon when their APIs support EdDSA arbitrary-message signing (or stipulate a different signing endpoint per provider).examples/pingUI (currently justconsole.log).signMessageon the dApp API as a convenience for non-WC adapters.