Try it Live Run SIWE examples in the interactive playground
Signing
EIP-191 message hash generation for SIWE messages.
getMessageHash
Get EIP-191 personal sign message hash for signing.
Signature
function getMessageHash ( message : BrandedMessage ) : Uint8Array
Parameters
message - BrandedMessage to hash
Returns
32-byte Keccak-256 hash with EIP-191 prefix
hash = keccak256(
"\x19Ethereum Signed Message:\n" +
length(messageText) +
messageText
)
Prefix: \x19Ethereum Signed Message:\n{length}
Message: EIP-4361 formatted message
Hash: Keccak-256 of concatenated bytes
Example
const message = Siwe . create ({
domain: "example.com" ,
address: Address ( "0x742d35Cc6634C0532925a3b844Bc9e7595f251e3" ),
uri: "https://example.com" ,
chainId: 1 ,
});
const hash = Siwe . getMessageHash ( message );
// Uint8Array(32) [...]
// Returns 32-byte hash ready for signing
Process
Format Message: Convert message to EIP-4361 string
const messageText = Siwe . format ( message );
Encode to Bytes: UTF-8 encode message text
const messageBytes = new TextEncoder (). encode ( messageText );
Build Prefix: Create EIP-191 prefix
const prefix = " \x19 Ethereum Signed Message: \n " + messageBytes . length ;
Concatenate: Combine prefix + message
const fullMessage = concat ( prefix , messageBytes );
Hash: Keccak-256 hash
const hash = Keccak256 . hash ( fullMessage );
Usage Patterns
Client-Side Signing
// Browser wallet signing
const message = Siwe . create ({ ... });
const text = Siwe . format ( message );
// Wallet handles EIP-191 internally
const signature = await ethereum . request ({
method: 'personal_sign' ,
params: [ text , address ],
});
Wallets apply EIP-191 prefix automatically. Do not hash before sending to wallet.
Server-Side Verification
// Get hash for signature verification
const hash = Siwe . getMessageHash ( message );
// Recover public key from signature
const publicKey = Secp256k1 . recoverPublicKey ( signature , hash );
// Derive address and compare
const recoveredAddress = Address . fromPublicKey ( publicKey . x , publicKey . y );
const valid = Address . equals ( recoveredAddress , message . address );
Manual Signing (Testing)
import { Secp256k1 } from 'tevm' ;
const message = Siwe . create ({ ... });
const hash = Siwe . getMessageHash ( message );
// Sign with private key
const privateKey = Bytes32 (); // Your private key
const signature = Secp256k1 . sign ( hash , privateKey );
Instance Method
const message = Siwe . create ({ ... });
const hash = message . getMessageHash ();
// Same as Siwe.getMessageHash(message)
EIP-191 Specification
EIP-191 defines three types of signed data. SIWE uses personal sign format:
0x19 <version byte>
"Ethereum Signed Message:\n" <message length>
<message>
Version byte: 0x19 (25 decimal)
Identifier: "Ethereum Signed Message:\n"
Length: ASCII decimal length of message
Message: Raw message bytes (UTF-8 encoded)
Common Mistakes
Don’t Double-Hash
// Wrong: Don't hash before wallet signing
const hash = Siwe . getMessageHash ( message );
await wallet . signMessage ( hash ); // Wallet will hash again
// Correct: Send formatted text to wallet
const text = Siwe . format ( message );
await wallet . signMessage ( text ); // Wallet applies EIP-191
Use Correct Encoding
// Correct: UTF-8 encoding
const messageBytes = new TextEncoder (). encode ( messageText );
// Wrong: Don't use hex or base64
const wrongBytes = hexToBytes ( messageText ); // No!
Length as String
// Correct: ASCII decimal length
const length = messageBytes . length . toString (); // "145"
// Wrong: Don't use hex length
const wrongLength = messageBytes . length . toString ( 16 ); // "91" - No!
Security Considerations
Prefix prevents signature reuse: EIP-191 prefix ensures message can’t be interpreted as transaction
Length prevents manipulation: Including length prevents message truncation attacks
UTF-8 encoding: Ensures consistent byte representation across platforms
Keccak-256: Same hash function as Ethereum transactions
Deterministic: Same message always produces same hash
Formatting: O(n) where n = message size
Encoding: O(n) UTF-8 encoding
Hashing: O(n) Keccak-256 hash
Total: Linear in message size, typically less than 1ms
See Also