Signing Messages
Before signing messages, read the Installation guide and dApp Guide.
Import required functions
import { verifyMessageSignature, verifySignerSignature } from '@hashgraph/hedera-wallet-connect'
import { PublicKey } from '@hashgraph/sdk'
Signing and verifying messages using dAppConnector
First, get the address, params, and call the signMessage method. This will return a SignatureMap
object once the user has signed the message.
const address = localStorage.getItem('hederaAddress')
const params = {
signerAccountId: `hedera:mainnet:${address}`,
message: 'Example message to sign',
}
Next, let's sign and verify the signature.
On your frontend call the signMessage
method in your dAppConnector instance.
const result = await dAppConnector.signMessage(params)
On your backend, retrieve the user's public key from a mirror node and verify the signature.
export const getPublicKey = async (accountId, network) => {
try {
const URL = `https://mainnet.mirrornode.hedera.com/api/v1/accounts/${address}`;
const request = await fetch(URL);
const response = (await request.json())
return { key: response?.key?.key, type: response?.key?._type };
} catch (e) {
console.log('failed to request', e);
return { key: undefined, type: undefined };
}
};
const keyResponse = await getPublicKey(address, 'mainnet');
const publicKey = PublicKey.fromString(publicKey);
const verifiedResult = verifyMessageSignature(params.message, result.signatureMap, publicKey)
Signing and verifying messages using a Signer
To sign messages, you can use the signer.sign
method. This method takes a base64 encoded
string and returns an array of SignatureMap
objects. Each SignatureMap
object contains a
signature
and publicKey
property.
To verify the signature, you can use the verifySignerSignature
function. This function takes
the base64 encoded string, the SignatureMap
object, and a PublicKey
object.
const text = 'Example message to sign'
const base64String = btoa(text)
const sigMaps = await signer.sign([base64StringToUint8Array(base64String)])
// sigMaps[0].publicKey also contains the public key of the signer, but you should obtain a PublicKey you can trust from a mirror node.
const verifiedResult = verifySignerSignature(base64String, sigMaps[0], publicKey)
Putting it all together
Here's a complete example showing both signing approaches and their verification:
import { verifyMessageSignature, verifySignerSignature } from '@hashgraph/hedera-wallet-connect'
import { PublicKey } from '@hashgraph/sdk'
// Helper function to get public key from mirror node
async function getPublicKey(accountId, network = 'mainnet') {
try {
const URL = `https://${network}.mirrornode.hedera.com/api/v1/accounts/${accountId}`
const request = await fetch(URL)
const response = await request.json()
return {
key: response?.key?.key,
type: response?.key?._type
}
} catch (error) {
console.error('Failed to fetch public key:', error)
throw error
}
}
// Example using dAppConnector
async function signAndVerifyWithDAppConnector(dAppConnector, message) {
try {
// Get the connected account
const address = localStorage.getItem('hederaAddress')
if (!address) throw new Error('No connected account found')
// Prepare parameters
const params = {
signerAccountId: `hedera:mainnet:${address}`,
message: message,
}
// Sign the message
const result = await dAppConnector.signMessage(params)
console.log('Message signed successfully')
// Get the public key from mirror node
const { key: publicKeyString } = await getPublicKey(address)
if (!publicKeyString) throw new Error('Could not fetch public key')
// Verify the signature
const publicKey = PublicKey.fromString(publicKeyString)
const isValid = verifyMessageSignature(params.message, result.signatureMap, publicKey)
console.log('Signature verification result:', isValid)
return isValid
} catch (error) {
console.error('Error in signAndVerifyWithDAppConnector:', error)
throw error
}
}
// Example using Signer
async function signAndVerifyWithSigner(signer, message) {
try {
// Convert message to base64
const base64String = btoa(message)
// Helper function to convert base64 to Uint8Array
const base64StringToUint8Array = (base64String) => {
const binaryString = atob(base64String)
const bytes = new Uint8Array(binaryString.length)
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i)
}
return bytes
}
// Sign the message
const sigMaps = await signer.sign([base64StringToUint8Array(base64String)])
console.log('Message signed successfully')
// Get the public key from mirror node
const accountId = signer.getAccountId().toString()
const { key: publicKeyString } = await getPublicKey(accountId)
if (!publicKeyString) throw new Error('Could not fetch public key')
// Verify the signature
const publicKey = PublicKey.fromString(publicKeyString)
const isValid = verifySignerSignature(base64String, sigMaps[0], publicKey)
console.log('Signature verification result:', isValid)
return isValid
} catch (error) {
console.error('Error in signAndVerifyWithSigner:', error)
throw error
}
}
// Usage example
async function example() {
try {
// Using dAppConnector
const dAppConnectorResult = await signAndVerifyWithDAppConnector(
dAppConnector,
'Example message to sign with dAppConnector'
)
console.log('DAppConnector verification:', dAppConnectorResult)
// Using Signer
const signerResult = await signAndVerifyWithSigner(
signer,
'Example message to sign with Signer'
)
console.log('Signer verification:', signerResult)
} catch (error) {
console.error('Error in example:', error)
}
}
This complete example demonstrates:
- A reusable function to fetch public keys from the mirror node
- Two complete implementations for signing and verifying messages:
- Using dAppConnector
- Using Signer
- Proper error handling throughout the process
- Helper functions for base64 conversion
- Usage examples for both methods
You can adapt this code to your specific needs and integrate it into your application's architecture.