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 Signature examples in the interactive playground
Usage Patterns
Production patterns for signature handling.Ethereum Transaction Signatures
Creating Transaction Signature
import { Signature, Hex, Hash } from 'tevm';
import { secp256k1 } from 'tevm/crypto';
// Sign transaction
async function signTransaction(
tx: Transaction,
privateKey: Uint8Array
): Promise<BrandedSignature> {
// Encode transaction for signing
const encodedTx = RLP.encode([
tx.nonce,
tx.gasPrice,
tx.gasLimit,
tx.to,
tx.value,
tx.data,
]);
// Hash transaction
const txHash = Hash.keccak256(encodedTx);
// Sign with secp256k1
const { r, s, v } = await secp256k1.sign(txHash, privateKey);
// Create signature
const sig = Signature.fromSecp256k1(r, s, v);
// Ensure canonical (required by Ethereum)
return Signature.normalize(sig);
}
Verifying Transaction Signature
async function verifyTransactionSignature(
tx: Transaction,
sig: BrandedSignature
): Promise<Address> {
// Ensure canonical
if (!Signature.isCanonical(sig)) {
throw new Error('Non-canonical signature rejected');
}
// Reconstruct transaction hash
const encodedTx = RLP.encode([
tx.nonce,
tx.gasPrice,
tx.gasLimit,
tx.to,
tx.value,
tx.data,
]);
const txHash = Hash.keccak256(encodedTx);
// Recover public key
const publicKey = secp256k1.recover(
txHash,
Signature.getR(sig),
Signature.getS(sig),
Signature.getV(sig)!
);
// Derive address
return Address.fromPublicKey(publicKey);
}
EIP-155 Chain-Specific Signatures
function createEIP155Signature(
tx: Transaction,
privateKey: Uint8Array,
chainId: number
): BrandedSignature {
// EIP-155: Include chainId in signing
const encodedTx = RLP.encode([
tx.nonce,
tx.gasPrice,
tx.gasLimit,
tx.to,
tx.value,
tx.data,
chainId, // Chain ID
0, // r placeholder
0, // s placeholder
]);
const txHash = Hash.keccak256(encodedTx);
const { r, s, v } = secp256k1.sign(txHash, privateKey);
// Convert to EIP-155 v value
const eip155V = chainId * 2 + 35 + (v - 27);
// Store as standard v (27/28) in signature
const sig = Signature.fromSecp256k1(r, s, v);
return {
signature: Signature.normalize(sig),
eip155V, // Use this in transaction encoding
};
}
Message Signing (EIP-191)
Personal Sign
// Sign arbitrary message (EIP-191)
async function personalSign(
message: string,
privateKey: Uint8Array
): Promise<BrandedSignature> {
// EIP-191 prefix
const prefix = `\x19Ethereum Signed Message:\n${message.length}`;
const prefixedMessage = new TextEncoder().encode(prefix + message);
// Hash
const messageHash = Hash.keccak256(prefixedMessage);
// Sign
const { r, s, v } = await secp256k1.sign(messageHash, privateKey);
// Create canonical signature
const sig = Signature.fromSecp256k1(r, s, v);
return Signature.normalize(sig);
}
// Verify personal sign
async function personalVerify(
message: string,
sig: BrandedSignature
): Promise<Address> {
// Reconstruct hash
const prefix = `\x19Ethereum Signed Message:\n${message.length}`;
const prefixedMessage = new TextEncoder().encode(prefix + message);
const messageHash = Hash.keccak256(prefixedMessage);
// Recover address
const publicKey = secp256k1.recover(
messageHash,
Signature.getR(sig),
Signature.getS(sig),
Signature.getV(sig)!
);
return Address.fromPublicKey(publicKey);
}
Typed Data Signing (EIP-712)
// Sign typed data (EIP-712)
async function signTypedData(
domain: TypedDataDomain,
types: Record<string, TypedDataField[]>,
message: Record<string, any>,
privateKey: Uint8Array
): Promise<BrandedSignature> {
// Encode typed data
const domainHash = hashDomain(domain);
const messageHash = hashMessage(types, message);
// EIP-712 hash
const digest = Hash.keccak256(
concat([
Hex.toBytes('0x1901'),
domainHash,
messageHash,
])
);
// Sign
const { r, s, v } = await secp256k1.sign(digest, privateKey);
// Create canonical signature
const sig = Signature.fromSecp256k1(r, s, v);
return Signature.normalize(sig);
}
Multi-Algorithm Support
Algorithm Detection
function signMessage(
message: Uint8Array,
privateKey: Uint8Array,
algorithm: SignatureAlgorithm
): BrandedSignature {
switch (algorithm) {
case 'secp256k1': {
const { r, s, v } = secp256k1.sign(message, privateKey);
return Signature.fromSecp256k1(r, s, v);
}
case 'p256': {
const { r, s } = p256.sign(message, privateKey);
return Signature.fromP256(r, s);
}
case 'ed25519': {
const sig = ed25519.sign(message, privateKey);
return Signature.fromEd25519(sig);
}
}
}
function verifyMessage(
message: Uint8Array,
sig: BrandedSignature,
publicKey: Uint8Array
): boolean {
const algorithm = Signature.getAlgorithm(sig);
switch (algorithm) {
case 'secp256k1':
return secp256k1.verify(
message,
Signature.getR(sig),
Signature.getS(sig),
publicKey
);
case 'p256':
return p256.verify(
message,
Signature.getR(sig),
Signature.getS(sig),
publicKey
);
case 'ed25519':
return ed25519.verify(message, sig, publicKey);
}
}
Multi-Signature Wallet
interface MultiSigSignature {
signatures: BrandedSignature[];
threshold: number;
}
async function createMultiSig(
message: Uint8Array,
privateKeys: Uint8Array[],
threshold: number
): Promise<MultiSigSignature> {
const signatures = await Promise.all(
privateKeys.map(async (key) => {
const { r, s, v } = await secp256k1.sign(message, key);
const sig = Signature.fromSecp256k1(r, s, v);
return Signature.normalize(sig);
})
);
return { signatures, threshold };
}
async function verifyMultiSig(
message: Uint8Array,
multiSig: MultiSigSignature,
publicKeys: Uint8Array[]
): Promise<boolean> {
let validCount = 0;
for (const sig of multiSig.signatures) {
for (const pubKey of publicKeys) {
const valid = await secp256k1.verify(
message,
Signature.getR(sig),
Signature.getS(sig),
pubKey
);
if (valid) {
validCount++;
break;
}
}
}
return validCount >= multiSig.threshold;
}
Signature Storage
Database Schema
interface SignatureRecord {
id: string;
algorithm: SignatureAlgorithm;
r: Uint8Array; // ECDSA only
s: Uint8Array; // ECDSA only
v?: number; // secp256k1 only
signature?: Uint8Array; // Ed25519
createdAt: Date;
metadata?: Record<string, any>;
}
async function storeSignature(
db: Database,
sig: BrandedSignature,
metadata?: Record<string, any>
): Promise<string> {
const id = crypto.randomUUID();
const algorithm = Signature.getAlgorithm(sig);
const record: SignatureRecord = {
id,
algorithm,
createdAt: new Date(),
metadata,
...(algorithm === 'ed25519'
? { signature: Signature.toBytes(sig) }
: {
r: Signature.getR(sig),
s: Signature.getS(sig),
v: Signature.getV(sig),
}
),
};
await db.signatures.insert(record);
return id;
}
async function loadSignature(
db: Database,
id: string
): Promise<BrandedSignature> {
const record = await db.signatures.findById(id);
switch (record.algorithm) {
case 'secp256k1':
return Signature.fromSecp256k1(record.r!, record.s!, record.v);
case 'p256':
return Signature.fromP256(record.r!, record.s!);
case 'ed25519':
return Signature.fromEd25519(record.signature!);
}
}
Compact Storage
// Store signatures compactly
interface CompactSignature {
bytes: Uint8Array; // 64 bytes
algorithm: number; // 1 byte enum
v?: number; // 1 byte optional
}
function serializeCompact(sig: BrandedSignature): Uint8Array {
const algorithmByte = {
'secp256k1': 0x01,
'p256': 0x02,
'ed25519': 0x03,
}[Signature.getAlgorithm(sig)];
const compact = Signature.toCompact(sig);
const v = Signature.getV(sig);
const result = new Uint8Array(1 + compact.length + (v ? 1 : 0));
result[0] = algorithmByte;
result.set(compact, 1);
if (v !== undefined) {
result[1 + compact.length] = v;
}
return result;
}
function deserializeCompact(bytes: Uint8Array): BrandedSignature {
const algorithmByte = bytes[0]!;
const algorithm = {
0x01: 'secp256k1',
0x02: 'p256',
0x03: 'ed25519',
}[algorithmByte] as SignatureAlgorithm;
const compact = bytes.slice(1, 65);
const v = bytes.length > 65 ? bytes[65] : undefined;
if (algorithm === 'secp256k1' && v !== undefined) {
const r = compact.slice(0, 32);
const s = compact.slice(32, 64);
return Signature.fromSecp256k1(r, s, v);
}
return Signature.fromCompact(compact, algorithm);
}
Signature Batching
Batch Verification
interface SignatureBatch {
messages: Uint8Array[];
signatures: BrandedSignature[];
publicKeys: Uint8Array[];
}
async function verifyBatch(batch: SignatureBatch): Promise<boolean[]> {
return await Promise.all(
batch.signatures.map(async (sig, i) => {
const message = batch.messages[i]!;
const publicKey = batch.publicKeys[i]!;
return await verifyMessage(message, sig, publicKey);
})
);
}
// Optimized batch verification for same algorithm
async function verifyBatchOptimized(
batch: SignatureBatch
): Promise<boolean> {
const algorithm = Signature.getAlgorithm(batch.signatures[0]!);
// Ensure all signatures use same algorithm
if (!batch.signatures.every(sig =>
Signature.getAlgorithm(sig) === algorithm
)) {
throw new Error('Mixed algorithms not supported');
}
// Use algorithm-specific batch verification
switch (algorithm) {
case 'secp256k1':
return secp256k1.verifyBatch(
batch.messages,
batch.signatures,
batch.publicKeys
);
case 'ed25519':
return ed25519.verifyBatch(
batch.messages,
batch.signatures,
batch.publicKeys
);
default:
// Fallback to sequential verification
const results = await verifyBatch(batch);
return results.every(r => r);
}
}
Error Handling
Robust Signature Parsing
function parseSignatureSafe(
data: unknown,
expectedAlgorithm?: SignatureAlgorithm
): BrandedSignature | null {
try {
// Try parsing as BrandedSignature
if (Signature.is(data)) {
if (expectedAlgorithm &&
Signature.getAlgorithm(data) !== expectedAlgorithm) {
return null;
}
return data;
}
// Try parsing as bytes
if (data instanceof Uint8Array && data.length === 64) {
const algorithm = expectedAlgorithm || 'secp256k1';
return Signature.fromCompact(data, algorithm);
}
// Try parsing as object
if (typeof data === 'object' && data !== null) {
return Signature(data);
}
return null;
} catch (err) {
console.error('Signature parsing failed:', err);
return null;
}
}
Signature Validation
interface ValidationResult {
valid: boolean;
errors: string[];
}
function validateSignature(sig: BrandedSignature): ValidationResult {
const errors: string[] = [];
// Check length
if (sig.length !== 64) {
errors.push(`Invalid length: ${sig.length} (expected 64)`);
}
// Check algorithm
const algorithm = Signature.getAlgorithm(sig);
if (!['secp256k1', 'p256', 'ed25519'].includes(algorithm)) {
errors.push(`Invalid algorithm: ${algorithm}`);
}
// Check recovery ID
const v = Signature.getV(sig);
if (v !== undefined && v !== 27 && v !== 28) {
errors.push(`Invalid recovery ID: ${v} (expected 27 or 28)`);
}
// Check canonicality
if (algorithm !== 'ed25519' && !Signature.isCanonical(sig)) {
errors.push('Non-canonical signature (s > n/2)');
}
return {
valid: errors.length === 0,
errors,
};
}
Security Patterns
Signature Replay Prevention
class SignatureTracker {
private usedSignatures = new Set<string>();
private getSignatureHash(sig: BrandedSignature): string {
return Hex(Hash.keccak256(Signature.toBytes(sig)));
}
async verify(
message: Uint8Array,
sig: BrandedSignature,
publicKey: Uint8Array
): Promise<boolean> {
// Check if signature already used
const sigHash = this.getSignatureHash(sig);
if (this.usedSignatures.has(sigHash)) {
throw new Error('Signature already used');
}
// Verify signature
const valid = await verifyMessage(message, sig, publicKey);
if (!valid) {
return false;
}
// Mark as used
this.usedSignatures.add(sigHash);
return true;
}
}
Time-Limited Signatures
interface TimedSignature {
signature: BrandedSignature;
timestamp: number;
expiresAt: number;
}
function createTimedSignature(
sig: BrandedSignature,
ttl: number = 300000 // 5 minutes
): TimedSignature {
const timestamp = Date.now();
return {
signature: sig,
timestamp,
expiresAt: timestamp + ttl,
};
}
function verifyTimedSignature(
timed: TimedSignature,
message: Uint8Array,
publicKey: Uint8Array
): boolean {
// Check expiration
if (Date.now() > timed.expiresAt) {
throw new Error('Signature expired');
}
// Verify signature
return verifyMessage(message, timed.signature, publicKey);
}
Testing Helpers
Mock Signatures
function createMockSignature(
algorithm: SignatureAlgorithm = 'secp256k1'
): BrandedSignature {
const r = crypto.getRandomValues(Bytes32());
const s = crypto.getRandomValues(Bytes32());
switch (algorithm) {
case 'secp256k1':
return Signature.fromSecp256k1(r, s, 27);
case 'p256':
return Signature.fromP256(r, s);
case 'ed25519':
const sig = crypto.getRandomValues(Bytes64());
return Signature.fromEd25519(sig);
}
}
function createCanonicalMockSignature(): BrandedSignature {
const sig = createMockSignature('secp256k1');
return Signature.normalize(sig);
}
See Also
- Constructors - Creating signatures
- Validation - Canonicalization
- Utilities - Helper functions
- Conversions - Format conversions

