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

# Signature Recovery

<Card title="Try it Live" icon="play" href="https://playground.tevm.sh?example=primitives/signature.ts">
  Run Signature examples in the interactive playground
</Card>

# Signature Recovery

Recover public keys and addresses from ECDSA signatures using the recovery ID (v parameter).

## Overview

ECDSA signatures allow recovering the signer's public key from the signature and message hash. This is a key feature of Ethereum transactions, enabling address derivation without storing public keys.

**Supported Algorithms:**

* ✅ secp256k1 (Ethereum, Bitcoin)
* ❌ P-256 (no recovery support)
* ❌ Ed25519 (no recovery support)

## Recovery Process

### Public Key Recovery

Secp256k1 signatures can recover the public key that created the signature:

```typescript theme={null}
import { Signature } from 'tevm';
import { Secp256k1 } from 'tevm/crypto';

// Transaction signature
const sig = Signature.fromSecp256k1(r, s, 27);

// Message hash that was signed
const messageHash = keccak256(message);

// Recover public key
const publicKey = Secp256k1.recoverPublicKey(
  sig,
  messageHash
);

console.log(publicKey.length); // 65 bytes (uncompressed) or 33 bytes (compressed)
```

**See:** [Secp256k1.recoverPublicKey](/crypto/secp256k1#recoverpublickey) in crypto module

### Address Recovery

Ethereum addresses are derived from public keys via keccak256 hash:

```typescript theme={null}
import { Signature, Address } from 'tevm';
import { Secp256k1, keccak256 } from 'tevm/crypto';

const sig = Signature.fromSecp256k1(r, s, v);
const messageHash = keccak256(message);

// Recover address directly
const address = Secp256k1.recoverAddress(sig, messageHash);

console.log(Address.isValid(address)); // true
```

**See:** [Secp256k1.recoverAddress](/crypto/secp256k1#recoveraddress) in crypto module

## Recovery ID (v)

The recovery ID determines which of four possible public keys is correct.

### Standard Values

**Ethereum (pre-EIP-155):**

```typescript theme={null}
v = 27  // First recovery attempt (y-coordinate is even)
v = 28  // Second recovery attempt (y-coordinate is odd)
```

**EIP-155 (chain-specific):**

```typescript theme={null}
v = chainId * 2 + 35 + yParity

// Examples:
// Mainnet (chainId 1): v = 37 or 38
// Görli (chainId 5):   v = 45 or 46
// Polygon (chainId 137): v = 309 or 310
```

### Converting v Values

```typescript theme={null}
// EIP-155 to standard recovery ID
function toStandardV(eip155V: number, chainId: number): number {
  if (eip155V < 35) return eip155V; // Already standard
  return ((eip155V - 35 - chainId * 2) % 2) + 27;
}

// Standard to EIP-155
function toEIP155V(standardV: number, chainId: number): number {
  const yParity = standardV === 27 ? 0 : 1;
  return chainId * 2 + 35 + yParity;
}

// Usage
const eip155V = 37; // Mainnet signature
const standardV = toStandardV(eip155V, 1); // 27
const sig = Signature.fromSecp256k1(r, s, standardV);
```

## Recovery Examples

### Ethereum Transaction Recovery

```typescript theme={null}
import { Signature, Hash } from 'tevm';
import { Secp256k1, keccak256 } from 'tevm/crypto';

// Transaction data
const tx = {
  nonce: 0,
  gasPrice: 20000000000n,
  gasLimit: 21000n,
  to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
  value: 1000000000000000000n, // 1 ETH
  data: '0x',
  r: '0x...',
  s: '0x...',
  v: 27
};

// 1. Hash the transaction (RLP encoded)
const txHash = Hash(keccak256(rlpEncode(tx)));

// 2. Create signature
const sig = Signature.fromSecp256k1(
  Hex.toBytes(tx.r),
  Hex.toBytes(tx.s),
  tx.v
);

// 3. Recover sender address
const sender = Secp256k1.recoverAddress(sig, txHash);
console.log(Address.toString(sender)); // "0x..."
```

### EIP-712 Signed Message Recovery

```typescript theme={null}
import { Signature } from 'tevm';
import { EIP712, Secp256k1 } from 'tevm/crypto';

const domain = {
  name: 'My Dapp',
  version: '1',
  chainId: 1,
  verifyingContract: '0x...'
};

const types = {
  Person: [
    { name: 'name', type: 'string' },
    { name: 'wallet', type: 'address' }
  ]
};

const message = {
  name: 'Alice',
  wallet: '0x...'
};

// Hash the structured data
const messageHash = EIP712.hash({ domain, types, message });

// Parse signature from user
const sig = Signature(signatureBytes);

// Recover signer address
const signer = Secp256k1.recoverAddress(sig, messageHash);
```

### Personal Sign Recovery

```typescript theme={null}
import { Signature, Hash } from 'tevm';
import { keccak256, Secp256k1 } from 'tevm/crypto';

// Ethereum personal_sign prefixes message
const message = 'Sign this message';
const prefix = `\x19Ethereum Signed Message:\n${message.length}`;
const prefixedMessage = prefix + message;

// Hash the prefixed message
const messageHash = Hash(keccak256(prefixedMessage));

// Recover signer
const sig = Signature(signatureBytes);
const signer = Secp256k1.recoverAddress(sig, messageHash);
```

## Recovery Validation

### Verify Recovery Success

```typescript theme={null}
// Recover and verify against known address
const expectedAddress = Address('0x...');
const sig = Signature(signatureBytes);
const messageHash = Hash(keccak256(message));

const recoveredAddress = Secp256k1.recoverAddress(sig, messageHash);

if (Address.equals(recoveredAddress, expectedAddress)) {
  console.log('Signature verified');
} else {
  console.log('Invalid signature or wrong signer');
}
```

### Handle Recovery Errors

```typescript theme={null}
try {
  const publicKey = Secp256k1.recoverPublicKey(sig, messageHash);
} catch (err) {
  if (err instanceof InvalidSignatureError) {
    console.error('Invalid signature format');
  } else if (err instanceof RecoveryError) {
    console.error('Could not recover public key');
  }
}
```

## Multiple Recovery Attempts

When v is unknown, try all possibilities:

```typescript theme={null}
function tryRecoverAddress(
  r: Uint8Array,
  s: Uint8Array,
  messageHash: Hash
): Address | null {
  // Try both standard recovery IDs
  for (const v of [27, 28]) {
    try {
      const sig = Signature.fromSecp256k1(r, s, v);
      const address = Secp256k1.recoverAddress(sig, messageHash);
      // Validate against expected address if known
      return address;
    } catch {
      continue;
    }
  }
  return null;
}
```

## Security Considerations

### Canonical Signatures

Always normalize signatures before recovery to prevent malleability:

```typescript theme={null}
// Wrong: Non-canonical signature may recover different address
const sig = Signature.fromSecp256k1(r, sHigh, 27);
const address1 = Secp256k1.recoverAddress(sig, messageHash);

// Right: Normalize first
const canonical = Signature.normalize(sig);
const address2 = Secp256k1.recoverAddress(canonical, messageHash);

// address1 !== address2 if signature was non-canonical!
```

**Always normalize before recovery:**

```typescript theme={null}
function safeRecover(sig: BrandedSignature, messageHash: Hash): Address {
  const canonical = Signature.normalize(sig);
  return Secp256k1.recoverAddress(canonical, messageHash);
}
```

### Message Hash Validation

```typescript theme={null}
// Ensure message hash is 32 bytes (keccak256 output)
if (messageHash.length !== 32) {
  throw new Error('Message hash must be 32 bytes');
}

// Verify it's a proper keccak256 hash
const recomputed = keccak256(originalMessage);
if (!Hash.equals(messageHash, recomputed)) {
  throw new Error('Message hash mismatch');
}
```

### Recovery ID Bounds

```typescript theme={null}
// Validate v is in expected range
const v = Signature.getV(sig);

if (v === undefined) {
  throw new Error('Recovery ID required for recovery');
}

if (v !== 27 && v !== 28) {
  // Handle EIP-155 encoded v
  const chainId = Math.floor((v - 35) / 2);
  const yParity = (v - 35) % 2;
  const standardV = 27 + yParity;

  // Create new signature with standard v
  sig = Signature.fromSecp256k1(
    Signature.getR(sig),
    Signature.getS(sig),
    standardV
  );
}
```

## Performance

### Recovery Cost

Public key recovery is computationally expensive:

```typescript theme={null}
// Benchmark (approximate)
const sig = Signature.fromSecp256k1(r, s, 27);
const messageHash = Hash(keccak256(message));

console.time('recover');
const publicKey = Secp256k1.recoverPublicKey(sig, messageHash);
console.timeEnd('recover'); // ~0.5-1ms (native), ~2-5ms (WASM)

// Address recovery includes keccak256 hash
console.time('recoverAddress');
const address = Secp256k1.recoverAddress(sig, messageHash);
console.timeEnd('recoverAddress'); // ~0.6-1.2ms (native), ~2.5-6ms (WASM)
```

### Optimization Strategies

**Cache recovered addresses:**

```typescript theme={null}
const recoveryCache = new Map<string, Address>();

function cachedRecover(sig: BrandedSignature, messageHash: Hash): Address {
  const key = Hex(Signature.toBytes(sig)) + Hex(messageHash);

  if (recoveryCache.has(key)) {
    return recoveryCache.get(key)!;
  }

  const address = Secp256k1.recoverAddress(sig, messageHash);
  recoveryCache.set(key, address);
  return address;
}
```

**Batch recovery:**

```typescript theme={null}
// Recover multiple signatures in parallel
async function recoverBatch(
  signatures: BrandedSignature[],
  messageHashes: Hash[]
): Promise<Address[]> {
  return Promise.all(
    signatures.map((sig, i) =>
      Secp256k1.recoverAddress(sig, messageHashes[i])
    )
  );
}
```

## Algorithm Comparison

| Algorithm     | Recovery Support | Use Case           |
| ------------- | ---------------- | ------------------ |
| **secp256k1** | ✅ Yes (with v)   | Ethereum, Bitcoin  |
| **P-256**     | ❌ No             | TLS, WebAuthn, JWT |
| **Ed25519**   | ❌ No             | Modern protocols   |

**Note:** Only secp256k1 supports public key recovery. Other algorithms require storing the public key separately.

## See Also

* [Signature Validation](./validation.mdx) - Canonicalization and malleability
* [Signature Constructors](./constructors.mdx) - Creating signatures
* [Secp256k1 Crypto](/crypto/secp256k1) - Signing and verification
* [EIP-712](/crypto/eip712) - Structured data signing
* [EIP-155](https://eips.ethereum.org/EIPS/eip-155) - Chain-specific v values
