Skip to main content

Signing Data

Signing arbitrary data enables you to verify the user's possession of an account. This guide will walk you through the signing process and verification.

Obtain a chain provider

As covered in Provider Activation, obtain a chain provider for the networks you want to interact with. In the following examples, we will use both Ethereum and Constellation providers.

TypeScript
const dagProvider = window.stargazer.getProvider("constellation");
const ethProvider = window.stargazer.getProvider("ethereum");

Constellation Message Signing

Build a signature request

Constellation signatures for messages are done through a signature request object. The signature request object is sent for the user to accept. Uppon approval, a signature of the whole object is returned.

TypeScript
// Build the signature request
const signatureRequest: SignatureRequest = {
content: "Sign this message to confirm your address",
metadata: {
user: "3feb69d6-d3f0-4812-9c93-384bee08afe8",
},
};

Encode the signature request

Requests need to be a Base64 < JSON encoded string to sign. The wallet will then generate the signature from the same characters that compose this encoded request.

TypeScript
// Encode the signature request - Base64 < JSON < Request
const signatureRequestEnconded = window.btoa(JSON.stringify(signatureRequest));

Send the signature request

Once built and encoded, you can send the encoded signature request using the dag_signMessage RPC method.

Important

When the signature request is sent, the wallet will verify compliance with the schema of the signature request object. If it does not comply, the wallet will throw an error.

TypeScript
// Send the request and wait for the signature
await dagProvider.request({
method: "dag_signMessage",
params: [
"DAG88C9WDSKH451sisyEP3hAkgCKn5DN72fuwjfX",
signatureRequestEnconded,
],
});
// "3045022100b35798008516373fcc6eef75fe8e322ce8fe0dccc4802b052f3ddc7c6b5dc2900220154cac1e4f3e7d9a64f4ed9d2a518221b273fe782f037a5842725054f1c62280"

The returned signature corresponds to the SHA512 hash of the encoded signature request and the private key of the user. ECDSA.sign(privateKey, sha512(signatureRequestEnconded)).

Read more about Constellation signature verification

Get the account public key

After you generate a signature from your encoded request, you need to retrieve the public key from the signer's account for future verification. This is due to the fact that Constellation signatures are not recoverable (i.e. do not contain the v parameter like in Ethereum).

TypeScript
// Send the request and wait for the signature
await dagProvider.request({
method: "dag_getPublicKey",
params: ["DAG88C9WDSKH451sisyEP3hAkgCKn5DN72fuwjfX"],
});
// "0482c4566a9c4cbb6f23b9a31c96876501c71f5c04b35f416e0b2243113cce8fb386a2db0b3881d1c908d33465748b948649165a6705904120238999eed6eed1f4"

Ethereum Message Signing

The Stargazer Ethereum RPC API implements both EIP-191 (personal_sign) and EIP-712 (eth_signTypedData) as arbitrary message signing methods.

personal_sign method

The RPC API provided reveals the personal_sign RPC method for message signing. In this case, the message signed is an arbitrary hex string prefixed by the "\x19Ethereum Signed Message:\n" string and the length of the message in bytes from EIP-191.

TypeScript
// Send the request and wait for the signature
await dagProvider.request({
method: "personal_sign",
params: [
"0x5369676e2074686973206d65737361676520746f20636f6e6669726d20796f7572206164647265737320616e64207573657249642033666562363964362d643366302d343831322d396339332d333834626565303861666538",
"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83",
],
});
// "0xa3f20717a250c2b0b729b7e5becbff67fdaef7e0699da4de7ca5895b02a170a12d887fd3b17bfdce3481f10bea41f45ba9f709d39ce8325427b57afcfc994cee1b"

// Can also be a valid UTF-8 string
await dagProvider.request({
method: "personal_sign",
params: [
"Sign this message to confirm your address and userId 3feb69d6-d3f0-4812-9c93-384bee08afe8",
"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83",
],
});
// "0xa3f20717a250c2b0b729b7e5becbff67fdaef7e0699da4de7ca5895b02a170a12d887fd3b17bfdce3481f10bea41f45ba9f709d39ce8325427b57afcfc994cee1b"

The returned signature corresponds to the keccak256 hash of the prefix + message string and the private key of the user. ECDSA.sign(privateKey, keccak256("\x19Ethereum Signed Message:\n" + len(message) + message)).

Read more about Ethereum signature verification

eth_signTypedData method

The RPC API provided reveals the eth_signTypedData RPC method for typed message signing. In this case, the message signed is the hash of the typed data according to EIP-712 prefixed by the "\x19\x01" string according to EIP-191.

TypeScript
await provider.request({
method: "eth_signTypedData",
params: [
"0x567d0382442c5178105fC03bd52b8Db6Afb4fE40",
{
types: {
DeviceControl: [
{
name: "principal",
type: "AuthorizedEntity",
},
{
name: "emergency",
type: "AuthorizedEntity",
},
],
AuthorizedEntity: [
{
name: "address",
type: "address",
},
{
name: "validUntil",
type: "uint256",
},
],
EIP712Domain: [
{
name: "name",
type: "string",
},
{
name: "version",
type: "string",
},
{
name: "chainId",
type: "uint256",
},
{
name: "verifyingContract",
type: "address",
},
],
},
domain: {
name: "Stargazer Demo",
version: "1.0.0",
chainId: "3",
verifyingContract: "0xeb14c9bb6c2dec2ecb9b278c9fa1ec763b04d545",
},
primaryType: "DeviceControl",
message: {
principal: {
address: "0xeb14c9bb6c2dec2ecb9b278c9fa1ec763b04d545",
validUntil: "1657823568",
},
emergency: {
address: "0xcac3da343670abb46bc6e8e6d375b66217519093",
validUntil: "1752517998",
},
},
},
],
});
// "0xa3f20717a250c2b0b729b7e5becbff67fdaef7e0699da4de7ca5895b02a170a12d887fd3b17bfdce3481f10bea41f45ba9f709d39ce8325427b57afcfc994cee1b"

The returned signature corresponds to the keccak256 hash of the domainSeparator + hashStruct(message) and the private key of the user. ECDSA.sign(privateKey, keccak256("\x19\x01" + domainSeparator + hashStruct(message)).

Read more about Ethereum signature verification

Constellation Signature Verification

For signature verification, we will be using the @stardust-collective/dag4 package. The following snippet illustrates how you can verify an encoded request signature.

TypeScript
import { dag4 } from "@stardust-collective/dag4";

const publicKey = "account-public-key";
const signatureRequestEnconded = "the-base64-encoded-signature-request";
const signatureHex = "some-hex-encoded-signature";

const valid: boolean = dag4.keyStore.verify(
publicKey,
signatureRequestEnconded,
signatureHex
);

const publicKeyAddress = dag4.keyStore.getDagAddressFromPublicKey(publicKey);

Ethereum Signature Verification

For signature verification, we will be using the ethers package. The following snippets illustrate how you can verify different message signatures.

eth_personalSign
import * as ethers from "ethers";

const accountWhichSigned = "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83";
const messageSigned = "some-message-the-user-signed";
const signatureHex = "some-hex-encoded-signature";

const messageHash = ethers.utils.hashMessage(messageSigned);
const recoveredAddress = ethers.utils.recoverAddress(messageHash, signatureHex);

if (recoveredAddress !== accountWhichSigned) {
throw new Error("Signature is not valid");
}
eth_signTypedData
import * as ethers from "ethers";

const accountWhichSigned = "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83";
const messageSigned = {
// The EIP-712 domain signed
domain: {
name: "Stargazer Demo",
version: "1.0.0",
chainId: 3,
verifyingContract: "0xabcdefABCDEF1234567890abcdefABCDEF123456",
},
// The EIP-712 types signed
types: {
DeviceControl: [
{ name: "principal", type: "AuthorizedEntity" },
{ name: "emergency", type: "AuthorizedEntity" },
],
AuthorizedEntity: [
{ name: "address", type: "address" },
{ name: "validUntil", type: "uint256" },
],
},
// The EIP-712 message signed
value: {
principal: {
address: "0xEb14c9bb6C2DEc2eCb9B278C9fa1EC763B04d545",
validUntil: 1657823568,
},
emergency: {
address: "0xcAc3DA343670aBB46BC6E8e6d375B66217519093",
validUntil: 1752517998,
},
},
};
const signatureHex = "some-hex-encoded-signature";

const messageHash = ethers.utils._TypedDataEncoder.hash(
messageSigned.domain,
messageSigned.types,
messageSigned.value
);
const recoveredAddress = ethers.utils.recoverAddress(messageHash, signatureHex);

if (recoveredAddress !== accountWhichSigned) {
throw new Error("Signature is not valid");
}