Documentation Index Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
Use this file to discover all available pages before exploring further.
Try it Live Run SIWE examples in the interactive playground
Verification
Signature verification for SIWE messages.
verify
Verify SIWE message signature matches claimed address.
Signature function verify (
message : BrandedMessage ,
signature : Signature
) : boolean
Parameters
message - BrandedMessage that was signed
signature - 65-byte signature (r + s + v)
Returns true if signature valid and matches message.address
false if signature invalid or address mismatchBytes 0-31: r (32 bytes) - ECDSA signature component
Bytes 32-63: s (32 bytes) - ECDSA signature component
Byte 64: v (1 byte) - Recovery ID (0, 1, 27, or 28)
Total: 65 bytesV normalization:
If v >= 27: recoveryId = v - 27
If v < 27: recoveryId = v
Valid recovery IDs: 0 or 1
Process
Validate Message: Check structure with validate(message)
Get Hash: Compute EIP-191 hash with getMessageHash(message)
Extract Components: Parse r, s, v from signature
Normalize V: Convert v to recovery ID (0 or 1)
Recover Public Key: Use secp256k1 recovery
Derive Address: Keccak-256(publicKey)[12:32]
Compare: Check recovered address === message.address
Example const message = Siwe . create ({
domain: "example.com" ,
address: userAddress ,
uri: "https://example.com" ,
chainId: 1 ,
});
const text = Siwe . format ( message );
const signature = await wallet . signMessage ( text );
const valid = Siwe . verify ( message , signature );
if ( valid ) {
console . log ( "Signature verified, user authenticated" );
} else {
console . log ( "Invalid signature" );
}
Common Patterns
Backend Verification
app . post ( '/auth/verify' , async ( req , res ) => {
try {
const message = Siwe . parse ( req . body . message );
const signature = hexToBytes ( req . body . signature );
const valid = Siwe . verify ( message , signature );
if ( valid ) {
createSession ( message . address );
res . json ({ success: true });
} else {
res . status ( 401 ). json ({ error: "Invalid signature" });
}
} catch ( err ) {
res . status ( 400 ). json ({ error: "Invalid request" });
}
});
Instance Method
const message = Siwe . create ({ ... });
const valid = message . verify ( signature );
// Same as Siwe.verify(message, signature)
With Validation
// Validate before expensive verification
const validationResult = Siwe . validate ( message );
if ( ! validationResult . valid ) {
return error ( "Invalid message structure" );
}
const signatureValid = Siwe . verify ( message , signature );
if ( ! signatureValid ) {
return error ( "Signature mismatch" );
}
Error Cases
Returns false for:
Invalid message structure
Signature length !== 65 bytes
Invalid v value (not 0, 1, 27, or 28)
Failed public key recovery
Address mismatch
Any exception during verification
Security Considerations
Constant-Time Comparison:
Address comparison loops through all 20 bytes
Prevents timing attacks
V Normalization:
Accepts both raw (0, 1) and EIP-155 (27, 28) formats
Rejects invalid v values
Public Key Recovery:
Uses secp256k1 curve (same as Ethereum)
Validates recovered point on curve
Rejects invalid signatures
No Malleability:
Signature uniqueness enforced by secp256k1
Low-s values recommended but not required
verifyMessage
Combined validation and signature verification.
Signature
function verifyMessage (
message : BrandedMessage ,
signature : Signature ,
options ?: { now ?: Date }
) : ValidationResult
Parameters
message - BrandedMessage to verify
signature - 65-byte signature
options.now - Current time for timestamp checks
Returns
ValidationResult:
{ valid: true } - Structure valid AND signature verified
{ valid: false, error: ValidationError } - Structure invalid OR signature mismatch
Process
Validate Structure: Call validate(message, options)
Return if Invalid: Early return with validation error
Verify Signature: Call verify(message, signature)
Return Result: { valid: true } or signature mismatch error
Example
const message = Siwe . parse ( req . body . message );
const signature = hexToBytes ( req . body . signature );
const result = Siwe . verifyMessage ( message , signature );
if ( result . valid ) {
// Message structure valid AND signature verified
createSession ( message . address );
} else {
// Either structure invalid or signature mismatch
console . error ( result . error . type );
console . error ( result . error . message );
}
Error Types
All validation errors plus:
signature_mismatch - Signature does not match address or verification failed
Common Patterns
Complete Verification
const result = Siwe . verifyMessage ( message , signature );
if ( ! result . valid ) {
switch ( result . error . type ) {
case "signature_mismatch" :
logSecurityEvent ( "Invalid signature" , { address: message . address });
return unauthorized ();
case "expired" :
return reauth ( "Session expired" );
case "not_yet_valid" :
return error ( "Authentication not yet valid" );
case "invalid_nonce" :
return error ( "Invalid nonce" );
default :
return error ( result . error . message );
}
}
// Authenticated
createSession ( message . address );
With Timestamp Check
const result = Siwe . verifyMessage ( message , signature , {
now: new Date (),
});
if ( result . valid ) {
// Message not expired, not before satisfied, signature valid
authenticateUser ( message . address );
}
Complete Auth Flow
async function authenticateWithSiwe (
messageText : string ,
signatureHex : string
) : Promise <{ success : boolean ; error ?: string }> {
try {
// Parse message
const message = Siwe . parse ( messageText );
// Verify nonce
if ( ! await verifyNonce ( message . nonce )) {
return { success: false , error: "Invalid or reused nonce" };
}
// Verify domain
if ( message . domain !== expectedDomain ) {
return { success: false , error: "Domain mismatch" };
}
// Convert signature
const signature = hexToBytes ( signatureHex );
// Verify message and signature
const result = Siwe . verifyMessage ( message , signature );
if ( ! result . valid ) {
return { success: false , error: result . error . message };
}
// Create session
await createSession ( message . address , message . chainId );
return { success: true };
} catch ( err ) {
return { success: false , error: "Authentication failed" };
}
}
Advantages Over Separate Calls
Convenience:
Single function call for complete verification
Structured error handling
Consistent return type
Efficiency:
Early return on validation failure
Skips expensive signature verification if structure invalid
Correctness:
Ensures validation always happens first
Prevents verification of malformed messages
Validation: O(1) - Fast structure checks
Signature Verification: O(1) - secp256k1 recovery (expensive)
Optimization: Always validates first to avoid wasting cycles on invalid messages
Factory API
Tree-shakeable factory pattern with explicit crypto dependencies.
Verify Factory
function Verify ({
keccak256 ,
secp256k1RecoverPublicKey ,
addressFromPublicKey
}) : ( message : BrandedMessage , signature : Signature ) => boolean
Dependencies:
keccak256: (data: Uint8Array) => Uint8Array - Keccak256 hash function
secp256k1RecoverPublicKey: (sig: {r, s, v}, hash: Uint8Array) => Uint8Array - secp256k1 public key recovery
addressFromPublicKey: (x: bigint, y: bigint) => Uint8Array - Address derivation from public key
Example:
import { Verify } from 'tevm/Siwe'
import { hash as keccak256 } from 'tevm/crypto/Keccak256'
import { recoverPublicKey as secp256k1RecoverPublicKey } from 'tevm/crypto/Secp256k1'
import { FromPublicKey } from 'tevm/Address'
const addressFromPublicKey = FromPublicKey ({ keccak256 })
const verify = Verify ({ keccak256 , secp256k1RecoverPublicKey , addressFromPublicKey })
const valid = verify ( message , signature )
Bundle size: Crypto only included if you import it.
VerifyMessage Factory
function VerifyMessage ({
keccak256 ,
secp256k1RecoverPublicKey ,
addressFromPublicKey
}) : ( message : BrandedMessage , signature : Signature , options ?: { now ?: Date }) => ValidationResult
Same dependencies as Verify.
Example:
import { VerifyMessage } from 'tevm/Siwe'
import { hash as keccak256 } from 'tevm/crypto/Keccak256'
import { recoverPublicKey as secp256k1RecoverPublicKey } from 'tevm/crypto/Secp256k1'
import { FromPublicKey } from 'tevm/Address'
const addressFromPublicKey = FromPublicKey ({ keccak256 })
const verifyMessage = VerifyMessage ({ keccak256 , secp256k1RecoverPublicKey , addressFromPublicKey })
const result = verifyMessage ( message , signature , { now: new Date () })
GetMessageHash Factory
function GetMessageHash ({
keccak256
}) : ( message : BrandedMessage ) => Uint8Array
Dependencies:
keccak256: (data: Uint8Array) => Uint8Array - Keccak256 hash function
Example:
import { GetMessageHash } from 'tevm/Siwe'
import { hash as keccak256 } from 'tevm/crypto/Keccak256'
const getMessageHash = GetMessageHash ({ keccak256 })
const hash = getMessageHash ( message )
See Also