Documentation Index
Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
Use this file to discover all available pages before exploring further.
Overview
This guide shows you how to verify message signatures to authenticate users and ensure data integrity. Message verification is essential for secure dApp interactions and user authentication.
Prerequisites
Step 1: Basic Message Verification
Verify a simple message signature:
import { authenticatedEvmClient } from './client';
const evmClient = await authenticatedEvmClient();
const isValid = await evmClient.verifyMessageSignature({
accountAddress: '0xYourWalletAddress',
message: 'Hello, World!',
signature: '0xYourSignature',
});
console.log('Signature valid:', isValid);
Step 2: Authentication with Nonce
Verify a message with a nonce to prevent replay attacks:
export const verifyAuthentication = async ({
walletAddress,
message,
signature,
expectedNonce,
}: {
walletAddress: string;
message: string;
signature: string;
expectedNonce: string;
}) => {
const evmClient = await authenticatedEvmClient();
// Extract nonce from message
const nonceMatch = message.match(/nonce: (\d+)/);
if (!nonceMatch || nonceMatch[1] !== expectedNonce) {
throw new Error('Invalid or expired nonce');
}
// Verify signature
const isValid = await evmClient.verifyMessageSignature({
accountAddress: walletAddress,
message,
signature,
});
return isValid;
};
// Usage
const isValid = await verifyAuthentication({
walletAddress: '0xYourWalletAddress',
message: 'Sign this message to authenticate: nonce: 1234567890',
signature: '0xYourSignature',
expectedNonce: '1234567890',
});
if (isValid) {
console.log('User authenticated successfully');
} else {
console.log('Authentication failed');
}
Step 3: Data Integrity Verification
Verify that data hasn’t been tampered with:
export const verifyDataIntegrity = async ({
walletAddress,
originalData,
signature,
}: {
walletAddress: string;
originalData: any;
signature: string;
}) => {
const evmClient = await authenticatedEvmClient();
// Convert data to string (must match what was signed)
const message = JSON.stringify(originalData);
const isValid = await evmClient.verifyMessageSignature({
accountAddress: walletAddress,
message,
signature,
});
return isValid;
};
// Usage
const data = { userId: 123, action: 'transfer', amount: '100' };
const isValid = await verifyDataIntegrity({
walletAddress: '0xYourWalletAddress',
originalData: data,
signature: '0xYourSignature',
});
if (isValid) {
console.log('Data integrity verified');
} else {
console.log('Data has been tampered with');
}
Step 5: Complete Authentication Flow
Here’s a complete example of a secure authentication flow:
export class MessageVerifier {
private evmClient: any;
private nonceStore: Map<string, number> = new Map();
constructor() {
this.evmClient = await authenticatedEvmClient();
}
// Generate a nonce for a user
generateNonce(userId: string): string {
const nonce = Date.now();
this.nonceStore.set(userId, nonce);
return nonce.toString();
}
// Verify user authentication
async verifyUser({
walletAddress,
userId,
signature,
}: {
walletAddress: string;
userId: string;
signature: string;
}): Promise<boolean> {
const expectedNonce = this.nonceStore.get(userId);
if (!expectedNonce) {
throw new Error('No nonce found for user');
}
const message = `Sign this message to authenticate user ${userId}: nonce: ${expectedNonce}`;
const isValid = await this.evmClient.verifyMessageSignature({
accountAddress: walletAddress,
message,
signature,
});
if (isValid) {
// Clear the used nonce
this.nonceStore.delete(userId);
}
return isValid;
}
// Verify transaction approval
async verifyTransactionApproval({
walletAddress,
transactionHash,
signature,
}: {
walletAddress: string;
transactionHash: string;
signature: string;
}): Promise<boolean> {
const message = `I approve transaction: ${transactionHash}`;
return await this.evmClient.verifyMessageSignature({
accountAddress: walletAddress,
message,
signature,
});
}
}
// Usage
const verifier = new MessageVerifier();
// Step 1: Generate nonce for user
const nonce = verifier.generateNonce('user123');
console.log('Nonce generated:', nonce);
// Step 2: User signs message with nonce (on frontend)
// const message = `Sign this message to authenticate user user123: nonce: ${nonce}`;
// const signature = await wallet.signMessage(message);
// Step 3: Verify signature
const isValid = await verifier.verifyUser({
walletAddress: '0xUserWalletAddress',
userId: 'user123',
signature: '0xUserSignature',
});
if (isValid) {
console.log('User authenticated successfully');
} else {
console.log('Authentication failed');
}
Best Practices
- Nonce Usage: Always use nonces to prevent replay attacks
- Message Format: Use consistent message formats across your application
- Error Handling: Implement proper error handling for verification failures
- Security: Never trust client-side verification - always verify on the server
- Nonce Management: Clear used nonces to prevent reuse
Common Use Cases
- User Authentication: Verify wallet ownership for login
- Transaction Approval: Verify user approval for transactions
- Data Integrity: Ensure data hasn’t been modified
- Access Control: Verify permissions for specific actions
Next Steps