> ## 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.

# Sign & Verify Messages

> Sign messages and verify signatures for authentication

<Info>
  **Skill** — Copyable reference implementation. Use as-is or customize. See [Skills Philosophy](/concepts/skills).
</Info>

Sign arbitrary messages with private keys and verify signatures to authenticate users. This guide covers EIP-191 personal messages, the standard used by wallets like MetaMask for off-chain authentication.

## EIP-191 Personal Sign

EIP-191 defines a standard format for signed data that prevents signed messages from being valid transactions. The format prepends `\x19Ethereum Signed Message:\n{length}` to your message before hashing.

```typescript theme={null}
import { Keccak256Hash } from '@voltaire/primitives/crypto/Keccak256';
import { Secp256k1 } from '@voltaire/primitives/crypto/Secp256k1';
import { Hash as SignedDataHash } from '@voltaire/primitives/SignedData';

// Create the hash function with keccak256 dependency
const hashPersonalMessage = SignedDataHash({ keccak256: Keccak256Hash.hash });

// Sign a personal message
const message = 'Sign this message to authenticate';
const messageHash = hashPersonalMessage(message);

// Sign with your private key (32 bytes)
const privateKey = new Uint8Array(32);
crypto.getRandomValues(privateKey);

const signature = Secp256k1.sign(messageHash, privateKey);
// Returns: { r: Uint8Array(32), s: Uint8Array(32), v: 27 | 28 }
```

## Hash Prefixing Convention

The EIP-191 personal message format:

```
"\x19Ethereum Signed Message:\n" + message.length + message
```

For example, signing "Hello" produces:

```
keccak256("\x19Ethereum Signed Message:\n5Hello")
```

This prefix ensures:

1. Signed messages cannot be valid transactions
2. Users know they are signing a message, not a transaction
3. Cross-application signature reuse is prevented

```typescript theme={null}
import { PERSONAL_MESSAGE_PREFIX } from '@voltaire/primitives/SignedData';

// The prefix is: "\x19Ethereum Signed Message:\n"
console.log(PERSONAL_MESSAGE_PREFIX);
// "\x19Ethereum Signed Message:\n"

// Manual hash construction (for understanding - use SignedData.Hash instead)
const message = 'Hello';
const prefix = new TextEncoder().encode(`\x19Ethereum Signed Message:\n${message.length}`);
const messageBytes = new TextEncoder().encode(message);
const data = new Uint8Array(prefix.length + messageBytes.length);
data.set(prefix, 0);
data.set(messageBytes, prefix.length);
const hash = Keccak256Hash.hash(data);
```

## Verifying Signatures

Verify that a signature was created by a specific address:

```typescript theme={null}
import { Keccak256Hash } from '@voltaire/primitives/crypto/Keccak256';
import { Secp256k1 } from '@voltaire/primitives/crypto/Secp256k1';
import { Verify as SignedDataVerify } from '@voltaire/primitives/SignedData';
import { FromPublicKey } from '@voltaire/primitives/Address';

// Create verification function with dependencies
const fromPublicKey = FromPublicKey({ keccak256: Keccak256Hash.hash });
const verify = SignedDataVerify({
  keccak256: Keccak256Hash.hash,
  recoverPublicKey: Secp256k1.recoverPublicKey,
  addressFromPublicKey: fromPublicKey,
});

// Verify a signature
const message = 'Sign this message to authenticate';
const signature = {
  r: signatureR, // 32 bytes
  s: signatureS, // 32 bytes
  v: 27,         // or 28
};
const expectedAddress = signerAddress; // 20-byte address

const isValid = verify(message, signature, expectedAddress);
// Returns: true if signature is valid and from expectedAddress
```

## Recovering the Signer Address

Recover the signer's address from a signature without knowing it beforehand:

```typescript theme={null}
import { Keccak256Hash } from '@voltaire/primitives/crypto/Keccak256';
import { Secp256k1 } from '@voltaire/primitives/crypto/Secp256k1';
import { Hash as SignedDataHash } from '@voltaire/primitives/SignedData';
import { FromPublicKey } from '@voltaire/primitives/Address';

// Set up dependencies
const hashPersonalMessage = SignedDataHash({ keccak256: Keccak256Hash.hash });
const fromPublicKey = FromPublicKey({ keccak256: Keccak256Hash.hash });

// Hash the message with EIP-191 format
const message = 'Sign this message to authenticate';
const messageHash = hashPersonalMessage(message);

// Recover public key from signature
const signature = {
  r: signatureR,
  s: signatureS,
  v: 27,
};
const publicKey = Secp256k1.recoverPublicKey(signature, messageHash);
// Returns: 64-byte public key (x || y coordinates)

// Derive address from public key
const recoveredAddress = fromPublicKey(publicKey);
// Returns: 20-byte Ethereum address
```

## Complete Example

Full sign-and-verify flow:

```typescript theme={null}
import { Keccak256Hash } from '@voltaire/primitives/crypto/Keccak256';
import { Secp256k1 } from '@voltaire/primitives/crypto/Secp256k1';
import { Hash as SignedDataHash, Verify as SignedDataVerify } from '@voltaire/primitives/SignedData';
import { FromPublicKey } from '@voltaire/primitives/Address';
import { Hex } from '@voltaire/primitives/Hex';

// Initialize dependencies
const hashPersonalMessage = SignedDataHash({ keccak256: Keccak256Hash.hash });
const fromPublicKey = FromPublicKey({ keccak256: Keccak256Hash.hash });
const verify = SignedDataVerify({
  keccak256: Keccak256Hash.hash,
  recoverPublicKey: Secp256k1.recoverPublicKey,
  addressFromPublicKey: fromPublicKey,
});

// 1. Generate a key pair
const privateKey = Secp256k1.randomPrivateKey();
const publicKey = Secp256k1.derivePublicKey(privateKey);
const address = fromPublicKey(publicKey);

// 2. Sign a message
const message = 'I agree to the terms of service';
const messageHash = hashPersonalMessage(message);
const signature = Secp256k1.sign(messageHash, privateKey);

console.log('Signature:', {
  r: Hex.fromBytes(signature.r),
  s: Hex.fromBytes(signature.s),
  v: signature.v,
});

// 3. Verify the signature
const isValid = verify(message, signature, address);
console.log('Signature valid:', isValid); // true

// 4. Recover signer from signature
const recoveredPublicKey = Secp256k1.recoverPublicKey(signature, messageHash);
const recoveredAddress = fromPublicKey(recoveredPublicKey);

// Compare addresses (both are Uint8Arrays)
const addressesMatch = address.every((byte, i) => byte === recoveredAddress[i]);
console.log('Addresses match:', addressesMatch); // true
```

## EIP-191 Version Bytes

EIP-191 defines three version bytes for different use cases:

| Version             | Byte         | Use Case                                                             |
| ------------------- | ------------ | -------------------------------------------------------------------- |
| Personal Message    | `0x45` ("E") | `\x19Ethereum Signed Message:\n` - Most common for wallet signatures |
| Structured Data     | `0x01`       | EIP-712 typed data signing                                           |
| Data with Validator | `0x00`       | Application-specific with validator address                          |

```typescript theme={null}
import {
  VERSION_PERSONAL_MESSAGE,    // 0x45
  VERSION_STRUCTURED_DATA,     // 0x01
  VERSION_DATA_WITH_VALIDATOR, // 0x00
  EIP191_PREFIX,               // 0x19
} from '@voltaire/primitives/SignedData';
```

## Security Considerations

<Warning>
  Always validate signatures server-side. Client-side validation can be bypassed.
</Warning>

* **Use EIP-191 prefix**: Raw message signing allows transaction replay attacks
* **Include context**: Add domain, timestamp, or nonce to prevent cross-site signature reuse
* **Verify v value**: Must be 27 or 28 (or 0/1 in some libraries)
* **Check address format**: Recovered addresses are checksummed differently per library
* **Time-bound signatures**: Include expiration timestamps in signed messages

## Related

* [Secp256k1 Signing](/crypto/secp256k1/signing) - Low-level ECDSA signing
* [Secp256k1 Recovery](/crypto/secp256k1/recovery) - Public key recovery
* [EIP-712](/crypto/eip712/index) - Typed structured data signing
* [Keccak256](/crypto/keccak256/index) - Hash function used for Ethereum
