Use this file to discover all available pages before exploring further.
Try it Live
Run Signature examples in the interactive playground
Conceptual Guide - For API reference and method documentation, see Signature API.
ECDSA signatures provide cryptographic proof that a message was authorized by the holder of a specific private key. This guide teaches signature fundamentals using Tevm.
ECDSA (Elliptic Curve Digital Signature Algorithm) is a cryptographic signature scheme that proves message authenticity without revealing the private key. Ethereum uses ECDSA for transaction authorization.Key properties:
Unforgeable - Only the private key holder can create valid signatures
Non-repudiable - Signer cannot deny creating a valid signature
Verifiable - Anyone can verify signature authenticity with the public key
Non-transferable - Signature cannot be reused for different messages
v - Recovery identifier (1 byte, typically 27 or 28)
Enables public key recovery without providing the full public key
Ethereum standard: 27 for even y-coordinate, 28 for odd y-coordinate
import { Signature } from 'tevm';// Signature componentsconst sig = Signature.fromSecp256k1( rBytes, // 32 bytes sBytes, // 32 bytes 27 // v value (recovery ID));// Extract componentsconst r = Signature.getR(sig); // 32-byte r valueconst s = Signature.getS(sig); // 32-byte s valueconst v = Signature.getV(sig); // 27 or 28
EIP-2098 defines a compact 64-byte signature format by embedding the recovery ID into the s value’s highest bit.Standard format: 65 bytes (r: 32, s: 32, v: 1)
EIP-2098 format: 64 bytes (r: 32, s with embedded v: 32)
import { Signature } from 'tevm';// Standard 65-byte signatureconst standardSig = Signature.fromSecp256k1(rBytes, sBytes, 27);// Convert to compact 64-byte format (EIP-2098)const compactBytes = Signature.toCompact(standardSig); // 64 bytesconsole.log(compactBytes.length); // 64// Parse compact signature backconst parsedSig = Signature.fromCompact(compactBytes, 'secp256k1');console.log(Signature.equals(standardSig, parsedSig)); // true// Gas savings: 64 bytes vs 65 bytes saves gas when passing signatures to contracts
ECDSA signatures are malleable: both (r, s) and (r, -s mod n) are mathematically valid signatures for the same message. This can enable replay attacks if not handled properly.
import { Signature } from 'tevm';// Non-canonical signature (high-s)const sig = Signature.fromSecp256k1(rBytes, highSBytes, 27);// Check if canonical (s ≤ n/2)console.log(Signature.isCanonical(sig)); // false// Normalize to canonical form (flip s if s > n/2)const canonicalSig = Signature.normalize(sig);console.log(Signature.isCanonical(canonicalSig)); // true// Both signatures are cryptographically valid but produce different hashesconsole.log(Signature.equals(sig, canonicalSig)); // false (different s values)
// Attacker can flip signature s valueconst tx1 = { ...txData, signature: sig };const tx2 = { ...txData, signature: Signature.normalize(sig) };// Both transactions are valid but have different hashes// This enables replay attacks and transaction confusion
With normalization:
// Always normalize before verification or storageconst canonical = Signature.normalize(sig);// Now there's only one valid signature representation// Replay attacks and hash confusion are prevented