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

# Public Key Recovery

> Recover secp256k1 public keys from signatures - Ethereum's ecRecover precompile

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

<Warning>
  **This page is a placeholder.** All examples on this page are currently AI-generated and are not correct. This documentation will be completed in the future with accurate, tested examples.
</Warning>

## Examples

* [Recover Public Key](https://github.com/evmts/voltaire/blob/main/playground/src/examples/crypto/secp256k1/recover-public-key.ts) - Recover public key from signature (ecRecover)
* [Sign Transaction](https://github.com/evmts/voltaire/blob/main/playground/src/examples/crypto/secp256k1/sign-transaction.ts) - Sign and recover sender address

# Secp256k1 Public Key Recovery

Recover the signer's public key from an ECDSA signature and message hash. This is the core mechanism of Ethereum's `ecRecover` precompile and enables address-based authentication without storing public keys on-chain.

## Overview

ECDSA signatures contain enough information to recover the signer's public key:

* **Signature (r, s, v)** - 65 bytes
* **Message hash** - 32 bytes

From these, we can compute the public key without knowing the private key. This enables:

* **ecRecover precompile** - On-chain signature verification (address 0x01)
* **Transaction authentication** - Derive sender address from transaction signature
* **Message signing** - Verify signed messages (EIP-191, EIP-712)
* **Compact storage** - Store signatures instead of public keys

## API

### `recoverPublicKey(signature, messageHash)`

Recover the 64-byte public key from a signature and message hash.

**Parameters:**

* `signature` (`BrandedSignature`) - Signature with r, s, v components
* `messageHash` (`HashType`) - 32-byte hash that was signed

**Returns:** `Uint8Array` - 64-byte uncompressed public key (x || y)

**Throws:**

* `InvalidSignatureError` - Invalid signature format or recovery failed
* `InvalidHashError` - Hash wrong length

**Example:**

```typescript theme={null}
import * as Secp256k1 from '@tevm/voltaire/Secp256k1';
import { Keccak256 } from '@tevm/voltaire/Keccak256';

// Sign message
const privateKey = Bytes32();
crypto.getRandomValues(privateKey);
const messageHash = Keccak256.hashString('Recover my key!');
const signature = Secp256k1.sign(messageHash, privateKey);

// Recover public key (without knowing private key)
const recoveredKey = Secp256k1.recoverPublicKey(signature, messageHash);

// Verify recovery succeeded
const actualKey = Secp256k1.derivePublicKey(privateKey);
console.log(recoveredKey.every((byte, i) => byte === actualKey[i])); // true
```

## Algorithm Details

### ECDSA Public Key Recovery

Given signature (r, s, v) and message hash e:

1. **Reconstruct R point from r**:
   * `r` is the x-coordinate of ephemeral point R = k \* G
   * Solve for y: `y² = x³ + 7 mod p` (curve equation)
   * Two possible y values (positive and negative)
   * Recovery ID v selects which y to use

2. **Calculate helper values**:
   * `r_inv = r^-1 mod n` (modular inverse of r)
   * `e_neg = -e mod n` (negation of message hash)

3. **Recover public key**:
   ```
   public_key = r_inv * (s * R + e_neg * G)
   ```
   Where:
   * `R` is the reconstructed point
   * `G` is the generator point
   * `*` denotes scalar multiplication

4. **Verify recovery**:
   * Check recovered key is valid curve point
   * Optionally verify signature with recovered key

### Why Recovery Works

The signature was created as:

```
s = k^-1 * (e + r * private_key) mod n
```

Rearranging:

```
k = s^-1 * (e + r * private_key) mod n
s * k = e + r * private_key mod n
s * k - e = r * private_key mod n
private_key = r^-1 * (s * k - e) mod n
```

Since `public_key = private_key * G` and `R = k * G`:

```
public_key = r^-1 * (s * k - e) * G
           = r^-1 * (s * (k * G) - e * G)
           = r^-1 * (s * R - e * G)
```

This matches step 3 above.

### Recovery ID (v)

The recovery ID v resolves ambiguities in recovery:

**Two y-coordinates:** For each x-coordinate r, there are two possible y-values satisfying the curve equation (y and p - y). The recovery ID selects which one.

**Ethereum format:**

* **v = 27**: Use y with even parity (y & 1 == 0)
* **v = 28**: Use y with odd parity (y & 1 == 1)

**Standard format:**

* **v = 0**: Even parity
* **v = 1**: Odd parity

**EIP-155 (replay protection):**

* **v = chainId \* 2 + 35**: Even parity
* **v = chainId \* 2 + 36**: Odd parity

Our API accepts all formats and normalizes internally.

## Ethereum Integration

### ecRecover Precompile

Ethereum provides a precompiled contract at address `0x0000000000000000000000000000000000000001` for on-chain recovery:

**Solidity:**

```solidity theme={null}
function ecrecover(
    bytes32 hash,
    uint8 v,
    bytes32 r,
    bytes32 s
) public pure returns (address) {
    // Returns signer address or 0x0 if invalid
}
```

**Gas cost:** 3000 gas

**Example:**

```solidity theme={null}
function verifySigner(
    bytes32 messageHash,
    uint8 v,
    bytes32 r,
    bytes32 s,
    address expectedSigner
) public pure returns (bool) {
    address signer = ecrecover(messageHash, v, r, s);
    return signer == expectedSigner;
}
```

### Transaction Sender Recovery

Every Ethereum transaction signature enables sender recovery:

```typescript theme={null}
import * as Secp256k1 from '@tevm/voltaire/Secp256k1';
import * as Address from '@tevm/voltaire/Address';
import * as Transaction from '@tevm/voltaire/Transaction';

// Parse transaction
const tx = {
  nonce: 0n,
  gasPrice: 20000000000n,
  gasLimit: 21000n,
  to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
  value: 1000000000000000000n,
  data: new Uint8Array(),
  v: 27,
  r: Bytes32(), // from transaction
  s: Bytes32(), // from transaction
};

// Recover sender public key
const txHash = Transaction.hash(tx);
const signature = { r: tx.r, s: tx.s, v: tx.v };
const publicKey = Secp256k1.recoverPublicKey(signature, txHash);

// Derive sender address
const senderAddress = Address.fromPublicKey(publicKey);
console.log(senderAddress.toHex());
```

### EIP-191 Personal Sign

Recover signer from personal\_sign messages:

```typescript theme={null}
import { Keccak256 } from '@tevm/voltaire/Keccak256';

function recoverPersonalSignSigner(
  message: string,
  signature: { r: Uint8Array; s: Uint8Array; v: number }
): Uint8Array {
  // EIP-191: "\x19Ethereum Signed Message:\n" + len(message) + message
  const prefix = `\x19Ethereum Signed Message:\n${message.length}`;
  const prefixedMessage = new TextEncoder().encode(prefix + message);

  // Hash prefixed message
  const messageHash = Keccak256.hash(prefixedMessage);

  // Recover public key
  return Secp256k1.recoverPublicKey(signature, messageHash);
}

// Usage
const message = "Sign this message";
const signature = { r, s, v }; // From wallet
const publicKey = recoverPersonalSignSigner(message, signature);
const address = Address.fromPublicKey(publicKey);
```

### EIP-712 Typed Data

Recover signer from typed structured data:

```typescript theme={null}
import * as EIP712 from '@tevm/voltaire/EIP712';

function recoverTypedDataSigner(
  domain: EIP712.Domain,
  types: EIP712.Types,
  message: any,
  signature: { r: Uint8Array; s: Uint8Array; v: number }
): Uint8Array {
  // Hash typed data
  const messageHash = EIP712.hashTypedData(domain, types, message);

  // Recover public key
  return Secp256k1.recoverPublicKey(signature, messageHash);
}
```

## Security Considerations

### Recovery Uniqueness

**Critical:** Recovery is only unique with the correct v value. Wrong v recovers a different (invalid) public key.

```typescript theme={null}
const signature1 = { r, s, v: 27 };
const signature2 = { r, s, v: 28 };

const key1 = Secp256k1.recoverPublicKey(signature1, messageHash);
const key2 = Secp256k1.recoverPublicKey(signature2, messageHash);

// Different v = different recovered keys (only one is correct)
console.log(key1.every((byte, i) => byte === key2[i])); // false
```

Always use the v value from the original signature.

### Malleability Protection

Signature malleability affects recovery:

**Original signature:**

```typescript theme={null}
const sig1 = { r, s, v: 27 };
const recovered1 = Secp256k1.recoverPublicKey(sig1, hash);
```

**Malleated signature:**

```typescript theme={null}
const sig2 = { r, s: CURVE_ORDER - s, v: 28 }; // High-s
const recovered2 = Secp256k1.recoverPublicKey(sig2, hash);
```

Both recover **different** public keys. Ethereum enforces low-s to prevent this.

### Invalid Signature Handling

Invalid signatures can:

* Return incorrect public keys
* Throw errors during recovery
* Recover keys not on the curve

Always verify recovered keys:

```typescript theme={null}
try {
  const publicKey = Secp256k1.recoverPublicKey(signature, messageHash);

  // Verify key is valid
  if (!Secp256k1.isValidPublicKey(publicKey)) {
    throw new Error('Recovered invalid public key');
  }

  // Optionally verify signature with recovered key
  if (!Secp256k1.verify(signature, messageHash, publicKey)) {
    throw new Error('Signature verification failed');
  }

  // Use recovered key
  const address = Address.fromPublicKey(publicKey);
} catch (error) {
  console.error('Recovery failed:', error);
}
```

## Test Vectors

### Basic Recovery

```typescript theme={null}
const privateKey = Bytes32();
privateKey[31] = 42;

const messageHash = Keccak256.hashString("test recovery");
const signature = Secp256k1.sign(messageHash, privateKey);

// Recover public key
const recovered = Secp256k1.recoverPublicKey(signature, messageHash);

// Verify matches original
const actual = Secp256k1.derivePublicKey(privateKey);
assert(recovered.every((byte, i) => byte === actual[i]));
```

### Recovery ID Selection

```typescript theme={null}
const signature = Secp256k1.sign(messageHash, privateKey);

// v = 27 (correct)
const correctV = { ...signature, v: 27 };
const key1 = Secp256k1.recoverPublicKey(correctV, messageHash);

// v = 28 (incorrect for this signature)
const incorrectV = { ...signature, v: 28 };
const key2 = Secp256k1.recoverPublicKey(incorrectV, messageHash);

// Different keys recovered
assert(!key1.every((byte, i) => byte === key2[i]));

// Only correct v matches actual public key
const actualKey = Secp256k1.derivePublicKey(privateKey);
const match1 = key1.every((byte, i) => byte === actualKey[i]);
const match2 = key2.every((byte, i) => byte === actualKey[i]);

assert(match1 !== match2); // Exactly one matches
```

### EIP-191 Personal Sign

```typescript theme={null}
// Sign message
const message = "Hello, Ethereum!";
const prefix = `\x19Ethereum Signed Message:\n${message.length}`;
const prefixedMessage = new TextEncoder().encode(prefix + message);
const messageHash = Keccak256.hash(prefixedMessage);
const signature = Secp256k1.sign(messageHash, privateKey);

// Recover signer
const recovered = Secp256k1.recoverPublicKey(signature, messageHash);
const recoveredAddress = Address.fromPublicKey(recovered);

// Verify matches expected
const expectedAddress = Address.fromPublicKey(
  Secp256k1.derivePublicKey(privateKey)
);

assert(recoveredAddress.equals(expectedAddress));
```

### Invalid Signature Recovery

```typescript theme={null}
// Invalid r (all zeros)
const invalidR = {
  r: Bytes32(),
  s: signature.s,
  v: 27,
};
expect(() => Secp256k1.recoverPublicKey(invalidR, messageHash)).toThrow();

// Invalid s (too large)
const invalidS = {
  r: signature.r,
  s: Bytes32().fill(0xff),
  v: 27,
};
expect(() => Secp256k1.recoverPublicKey(invalidS, messageHash)).toThrow();
```

## Performance

Public key recovery is more expensive than verification:

* **Verification:** Requires 2 scalar multiplications
* **Recovery:** Requires 2 scalar multiplications + modular square root

Typical recovery time:

* **TypeScript (@noble/curves):** \~1-2ms per signature
* **Zig (native):** \~0.5-1ms per signature
* **WASM (portable):** \~2-4ms per signature
* **EVM (ecRecover precompile):** 3000 gas (\~60µs at 50M gas/sec)

For verification-only use cases, prefer `verify()` with known public key over recovery.

## Implementation Notes

### TypeScript

Uses `@noble/curves/secp256k1`:

* Implements recovery via point reconstruction
* Handles both standard (0/1) and Ethereum (27/28) v values
* Validates recovered keys before returning
* Constant-time operations

### Zig

Custom implementation:

* ⚠️ **UNAUDITED** - Not security reviewed
* Implements modular square root for y recovery
* Basic validation only
* Educational purposes only

## Related

* [Signing](/crypto/secp256k1/signing) - Create signatures with private keys
* [Verification](/crypto/secp256k1/verification) - Verify signatures with known public keys
* [Key Derivation](/crypto/secp256k1/key-derivation) - Derive public keys from private keys
* [Usage Patterns](/crypto/secp256k1/usage-patterns) - Transaction and message signing examples
* [EIP-191](https://eips.ethereum.org/EIPS/eip-191) - Signed data standard
* [EIP-712](/crypto/eip712) - Typed structured data hashing and signing
