One of the most important tenets of Web3 is empowerment: putting people in control of their applications and their data. In order for developers to honor that principle while pushing the boundaries of DeFi, DAOs and other decentralized applications, developers need a way for their users to authorize both on- and off-chain activity with their wallet beyond transaction signing and broadcasting for asset transfers, contract executions and contract deployments.
We recently released a new feature for the Hiro Wallet called arbitrary message signing, which refers to using digital signatures to enable users to do two things: 1) prove they have control over a wallet address and 2) sign the content of any type of message that they authorize for usage elsewhere in the same application or in later downstream on-chain effects.
Use Cases For Arbitrary Message Signing
This new functionality enables a range of new use cases for developers building applications on Stacks. For example, with this feature an application can prompt a user to submit a signature to prove she controls an address or to authorize an action.
Through arbitrary message signing, you can create token-gated features that grant access or special privileges to holders of a specific NFT collection. Learn how Sigle built token-gated features in their app. This feature is also particularly useful for participating in off-chain mechanisms that require later settlement on-chain, such as a decentralized exchange settling off-chain order matching. Multi-signature wallets and governance tools for DAOs can also benefit from this feature as well as Oracle messages and cross-chain bridges.
Arbitrary message signing makes all of these use cases feasible by introducing a way for people to authorize specific actions once specific conditions are met.
How You Can Implement Arbitrary Message Signing For Your Users
There are two main implementations of arbitrary message signing: unstructured data and structured data. Here’s an overview of these two implementations:
Unstructured Data
To sign a message with unstructured data, you can use a utf-8 string that is passed to the Hiro Wallet to be signed. The message is hashed using sha256 before being signed with secp256k1.
This type of implementation is good for proving ownership or signaling a decision (for example, proving that a user agreed to a contract or Terms of Services). Of course, there may be other use cases depending on the design of a specific application.
Structured Data
To sign a message with structured data, you can use ClarityValue, a special data type from the Stacks blockchain. This implementation follows SIP-018. SIP-018 describes the Signed Structured Data specification, a standard way to represent information in human-readable format in applications and smart contracts in order to produce signatures that are straightforward and inexpensive to verify. For developers familiar with Ethereum, this design is similar to EIP-712.
The structured data implementation makes the message more readable and allows developers to more easily interact with ClarityValue in their applications.
The message here is a ClarityValue that is serialized in hexadecimal format. This is then hashed and signed just like above, using sha256 and secp256k1.
How It Works
In order to prompt users, you first need to authenticate them (to learn more about that process, go here). Once a user is authenticated, you can prompt them to sign a message by calling the openSignatureRequestPopup function provided by the connect package.
As you can see above, this function allows you to customize the popup message by specifying certain parameters, including:
- message: a string that contains a display message for the user.
- name: a string that contains the name of your app.
- icon: a string that contains an image, perhaps your app’s logo.
Once the user successfully signs the message, you can specify an onFinish callback that will return the SignatureData to you, including both the Signature and the PublicKey. Once you have the data, you can easily verify it using the Stacks.js package:
Learn more about the details of implementation in our documentation.
Users Can Sign With Peace of Mind
One of the primary considerations when introducing a new feature around signing is security. It is important to empower end-users, so that they are fully aware of what exactly they are signing.
This is an advantage of a human-readable language like Clarity. With Clarity, code is easier to verify. In order to prevent malicious applications, all of the arbitrary message signing implementations have the following security features built in:
- The message is prefixed first to prevent the dapp from asking to sign a transaction that could result in a loss of funds.
- Hashes of the message are verifiable from the wallet using standard methods (sha256).
- Signing methods are separate from transaction methods to prevent the possibility of tricking a user to trigger a transaction without their consent.
Sign, Sealed, Delivered
That’s how arbitrary message signing works. If you have additional questions or are ready to integrate arbitrary message signing into your application, you can learn more in our documentation.