Sign messages
Learn how to sign and verify messages to prove address ownership and authorize actions
Learn how to implement message signing in your Stacks application. Message signing allows users to cryptographically prove they control an address without making an on-chain transaction, enabling authentication, authorization, and verifiable statements.
What you'll learn
Prerequisites
- Node.js installed on your machine
- A code editor like VS Code
Installation
Install the required packages for message signing and verification.
$npm install @stacks/connect @stacks/encryption
Connect to wallet
Before signing messages, establish a connection to the user's wallet. The connection persists across page reloads.
import { connect, isConnected } from '@stacks/connect';async function connectWallet() {if (!isConnected()) {const response = await connect();console.log('Connected addresses:', response.addresses);}}
Call this function when your app loads or when the user clicks a connect button.
Sign text messages
Request a signature for a simple text message using the request
method.
import { request } from '@stacks/connect';async function signMessage() {const message = 'Hello World';const response = await request('stx_signMessage', {message,});console.log('Signature:', response.signature);console.log('Public key:', response.publicKey);return response;}
The wallet will display the message to the user for approval before signing.
Sign structured data
For more complex data, use structured message signing with Clarity values.
import { request } from '@stacks/connect';import { Cl } from '@stacks/transactions';async function signStructuredMessage() {const message = Cl.tuple({action: Cl.stringAscii('transfer'),amount: Cl.uint(1000),recipient: Cl.stringAscii('alice.btc')});const domain = Cl.tuple({name: Cl.stringAscii('My App'),version: Cl.stringAscii('1.0.0'),'chain-id': Cl.uint(1) // 1 for mainnet});const response = await request('stx_signStructuredMessage', {message,domain});return response;}
Structured messages provide better type safety and are easier to parse on-chain.
Verify signatures
Validate signatures to ensure they match the expected message and public key.
import { verifyMessageSignatureRsv } from '@stacks/encryption';async function verifySignature(message: string,signature: string,publicKey: string): Promise<boolean> {const isValid = verifyMessageSignatureRsv({message,signature,publicKey});if (isValid) {console.log('✓ Signature verified successfully');} else {console.log('✗ Invalid signature');}return isValid;}
Always verify signatures before trusting the signed data.
Complete verification flow
async function signAndVerify() {// Request signatureconst message = 'Authorize login at ' + new Date().toISOString();const signResponse = await request('stx_signMessage', { message });// Verify immediatelyconst isValid = await verifySignature(message,signResponse.signature,signResponse.publicKey);if (isValid) {// Proceed with authenticated actionconsole.log('Authentication successful');}}
Try it out
Create a simple authentication system using message signatures.
// Generate a unique challengefunction generateChallenge(): string {const nonce = Math.random().toString(36).substring(7);const timestamp = Date.now();return `Sign this message to authenticate:\nNonce: ${nonce}\nTime: ${timestamp}`;}// Complete auth flowasync function authenticate() {const challenge = generateChallenge();try {const response = await request('stx_signMessage', {message: challenge});const isValid = verifyMessageSignatureRsv({message: challenge,signature: response.signature,publicKey: response.publicKey});if (isValid) {// Store auth token or sessionlocalStorage.setItem('auth', JSON.stringify({publicKey: response.publicKey,timestamp: Date.now()}));return { success: true };}} catch (error) {console.error('Authentication failed:', error);}return { success: false };}