spark_signMessage
The spark_signMessage method lets your app request a secure, hashed message signature from the user’s Spark wallet.
This method provides a safe way to verify ownership of a Spark address, authenticate a user, or prove intent without exposing the wallet to raw arbitrary message signing attacks.
🔐 How It Works
To ensure signing security and prevent vulnerabilities with arbitrary data, Xverse implements a hash-before-sign scheme. When your app sends a message to be signed:
The raw message string is UTF-8 decoded into bytes.
The wallet computes the SHA-256 hash of the message bytes
The wallet signs the resulting hash with the user’s Spark identity key
The wallet returns the message signature to your app in base64 format
This prevents signature malleability and enforces minimum payload size, while aligning with the cryptographic standards used by Spark and Flashnet.
Parameters
message
a string representing the UTF-8 message to sign
Example
import { request } from "sats-connect";
const message = `Xverse / Spark — Sign-in Request
App: flashdex.example
Action: auth
Nonce: 6b6a7b2a-4e1b-4c2c-9f46-1f2b3d0a9e23
Issued At: 2025-10-22T09:31:12Z
Expires At: 2025-10-22T09:36:12Z
Chain: spark
Note: This does not move funds.`;
const res = await request("spark_signMessage", { message });What spark_signMessage does
spark_signMessage doesWhen called, this method:
Prompts the user to review the message to sign with their Xverse wallet:
Title:
Sign Message with Spark WalletMessage Preview: The human-readable message (UTF-8 text)
The user can confirm or cancel the signing request
Signs the message upon user approval, following the signing procedure
Returns the message signature to your app in base64 format
Response
✅ Success
{
signature: "3045022100..." // Spark signature of the message in base64 format
}❌ Error
{
errorCode: "SIGN-MESSAGE-001",
message: "User rejected message signing" | "Invalid message format",
requestId: "01HJZKFABCDEFGHJKLMNPQRSTVW"
}🔍 Verifying the Signature on Your Backend
To verify the signature:
import { secp256k1 } from '@noble/curves/secp256k1.js';
import { base64, hex } from '@scure/base';
// convert UTF-8 string to bytes
const decodedMessage = new TextEncoder().encode(message);
// decode base64 encoded signature
const decodedSignature = base64.decode(response.result.signature);
// decode hex encoded public key (provided by wallet)
const decodedPublicKey = hex.decode(sparkPublicKey);
const isValid = secp256k1.verify(decodedSignature, decodedMessage, decodedPublicKey);This ensures end-to-end consistency: the signature always corresponds to the deterministic SHA-256 hash of the message.
⚠️ Security Notes
The wallet never signs raw messages directly — only hashed digests.
The app must display or record the human-readable message alongside the signature for auditability.
Avoid embedding sensitive data directly in the message body; instead, use authentication challenges or session tokens.
SHA-256 is fixed as the canonical hash digest for this method to stay consistent with Spark’s on-chain cryptography.
Last updated