Skip to main content

Sign Messages

Connect 🌐

Stacks.js starters offer working templates with Stacks Connect pre-installed for a quick and easy way to get started with building Stacks enabled web apps.


This guide explains how to prompt users to sign a message. Messaging signing can be used to have a user prove they control a particular address or to have a user authorize a particular action in your app.

When signing a message, a user will be prompted via a popup from their wallet of choice, showing the message you want them to sign.

The user can then click on the 'Sign' button, which will return the signature data and the user's publicKey to your app. You can then verify the signature by passing the signature data and the public key to the stacks.js verifySignature method.

The message can be any utf-8 string.

Internally the string will be hashed using sha256 and signed with secp256k1 using the user's privateKey

Install dependency

@stacks/connect should be installed as a dependency.

npm install @stacks/connect

Initiate session

Users must be authenticated before they can sign a message from your app. Users can install an authenticator (e.g. a wallet) like Leather for that purpose.

See the authentication guide before integrating the following message signing capabilities.

Prompt to sign a message

Call the openSignatureRequestPopup function provided by the connect  package to trigger the display of the message signing prompt.

import { openSignatureRequestPopup } from '@stacks/connect';
import { StacksTestnet } from '@stacks/network';

const message = 'Hello World';

openSignatureRequestPopup({
message,
network: new StacksTestnet(), // for mainnet, `new StacksMainnet()`
appDetails: {
name: 'My App',
icon: window.location.origin + '/my-app-logo.svg',
},
onFinish(data) {
console.log('Signature of the message', data.signature);
console.log('Use public key:', data.publicKey);
},
});

Several parameters are available for calling openSignatureRequestPopup. Here's the exact interface for them:

interface SignatureRequestOptions {
message: string;
onFinish?: (data: SignatureData) => void;
onCancel?: (data: SignatureData) => void;
appDetails: {
name: string;
icon: string;
};
authOrigin?: string;
stxAddress?: string;
userSession?: UserSession;
}

Getting the signed message back after completion

The openSignatureRequestPopup signing method from @stacks/connect allows you to specify an onFinish callback. This callback will be triggered after the user has successfully signed the message.

You can get the message's signature via the arguments passed to onFinish. Your callback will be fired with a single argument, which is an object with the following properties:

export interface SignatureData {
/* Hex encoded DER signature */
signature: string;
/* Hex encoded private string taken from privateKey */
publicKey: string;
}
const onFinish = (data: SignatureData) => {
console.log('Signature', data.signature);
console.log('PublicKey', data.publicKey);
};

How to verify a signature

You can easily verify the signature using the @stacks/stacks.js package as seen in the following example.

import { verifyMessageSignatureRsv } from '@stacks/encryption';

const message = 'Hello World';

openSignatureRequestPopup({
// ...
onFinish({ publicKey, signature }) {
const verified = verifyMessageSignatureRsv({ message, publicKey, signature });
if (verified) {
// Trigger a notification explaining signature is verified
}
},
});

Specifying the network for a transaction

All of the methods included on this page accept a network option. By default, Connect uses a testnet network option. You can import a network configuration from the @stacks/network package.

import { StacksTestnet, StacksMainnet } from '@stacks/network';

const testnet = new StacksTestnet();
const mainnet = new StacksMainnet();

// use this in your message signing method:

openSignatureRequestPopup({
network: mainnet,
// other relevant options
});

Usage in React Apps

Import the useConnect helper from connect-react package to sign messages more seamlessly with React apps. You must install a version >= 15.0.0

npm install @stacks/connect-react

Use the function with the same parameters as outlined above. However, you don't have to specify appDetails since they are detected automatically if useConnect has been used already for authentication.

import { useConnect } from '@stacks/connect-react';

const MyComponent = () => {
const { sign } = useConnect();

const onClick = async () => {
const options = {
/** See examples above */
};
await sign(options);
};

return <span onClick={onClick}>Sign message</span>;
};

Signature request / response payload

Under the hood, @stacks/connect will serialize and deserialize data between your app and the Hiro Wallet.

These payloads are tokens that conform to the JSON Web Token (JWT) standard with additional support for the secp256k1 curve used by Bitcoin and many other cryptocurrencies.

Signature Request Payload

When an application triggers a message signing from @stacks/connect, the options of that signature request are serialized into a signatureRequest payload. The signatureRequest is similar to the authRequest payload used for authentication.

The signature request payload has the following schema, in addition to the standard JWT required fields:

interface SignatureRequestPayload {
message: string;
publicKey: string;
/**
* Provide the Hiro Wallet with a suggested account to sign this transaction with.
* This is set by default if a `userSession` option is provided.
*/
stxAddress?: string;
appDetails?: AuthOptions['appDetails'];
network?: StacksNetwork;
}

Signature Response payload

After the user signs the message, a signatureResponse payload is sent back to your app.

interface SignatureData {
/* Hex encoded DER signature */
signature: string;
/* Hex encoded private string taken from privateKey */
publicKey: string;
}

StacksProvider injected variable

When users have the Hiro Wallet extension installed, the extension will inject a global StacksProvider variable into the JavaScript context of your web app. This allows your JavaScript code to hook into the extension, and make authentication, transaction and signature requests. @stacks/connect automatically detects and uses this global variable for you.

At the moment, only the Hiro Wallet extension and the Xverse built-in browswer includes a StacksProvider, however, ideally more wallets (and mobile wallets) will support this format, so that your app can be compatible with any Stacks wallet that has functionality to embed web applications.

In your web application, you can check to see if the user has a compatible wallet installed by checking for the presence of window.StacksProvider.

Here is the interface for the StacksProvider variable.

interface StacksProvider {
transactionRequest(payload: string): Promise<FinishedTxPayload | SponsoredFinishedTxPayload>;
authenticationRequest(payload: string): Promise<string>;
signatureRequest(payload: string): Promise<SignatureData>;
structuredDataSignatureRequest(payload: string): Promise<SignatureData>;
profileUpdateRequest(payload: string): Promise<PublicProfile>;
getProductInfo:
| undefined
| (() => {
version: string;
name: string;
meta?: {
tag?: string;
commit?: string;
[key: string]: any;
};
[key: string]: any;
});
}