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

# Verification

> ECDSA signature verification with secp256k1 public keys

<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

* [Verify Signature](https://github.com/evmts/voltaire/blob/main/playground/src/examples/crypto/secp256k1/verify-signature.ts) - Verify signature with public key
* [Validate Signature](https://github.com/evmts/voltaire/blob/main/playground/src/examples/crypto/secp256k1/validate-signature.ts) - Signature format validation

# Secp256k1 Verification

Verify ECDSA signatures against public keys to authenticate messages. Signature verification is the cornerstone of Ethereum's security model - every transaction must pass verification before execution.

## Overview

ECDSA verification confirms that a signature was created by the private key corresponding to a given public key. The verifier needs:

* **Signature** (r, s, v) - 65 bytes total
* **Message hash** - 32 bytes (what was signed)
* **Public key** - 64 bytes uncompressed (x || y coordinates)

Verification succeeds if the signature was created by the matching private key, fails otherwise. **No secret information is revealed during verification** - it's safe to perform publicly.

## API

### `verify(signature, messageHash, publicKey)`

Verify an ECDSA signature against a message hash and public key.

**Parameters:**

* `signature` (`BrandedSignature`) - Signature with r, s, v components
* `messageHash` (`HashType`) - 32-byte hash that was signed
* `publicKey` (`Uint8Array`) - 64-byte uncompressed public key (x || y)

**Returns:** `boolean`

* `true` - Signature is cryptographically valid
* `false` - Signature is invalid or forged

**Throws:**

* `InvalidPublicKeyError` - Public key wrong length or not on curve
* `InvalidSignatureError` - Signature components 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('Verify me!');
const signature = Secp256k1.sign(messageHash, privateKey);

// Verify with public key
const publicKey = Secp256k1.derivePublicKey(privateKey);
const isValid = Secp256k1.verify(signature, messageHash, publicKey);

console.log(isValid); // true

// Verify with wrong public key
const wrongKey = Bytes64();
const invalid = Secp256k1.verify(signature, messageHash, wrongKey);
console.log(invalid); // false
```

## Algorithm Details

### ECDSA Verification

1. **Validate inputs**:
   * Check `1 ≤ r < n` and `1 ≤ s < n` (signature component bounds)
   * Verify public key is a valid curve point (satisfies y² = x³ + 7)

2. **Compute message hash scalar**: `e = hash(message) mod n`

3. **Calculate inverse**: `s_inv = s^-1 mod n`

4. **Compute point**: `R = (e * s_inv) * G + (r * s_inv) * public_key`
   * `G` is the generator point
   * `public_key` is the signer's public key point

5. **Verify**: Check if `R.x mod n == r`
   * If equal, signature is valid
   * If not equal, signature is invalid or forged

### Why Verification 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`

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

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

This matches step 4 above. If the signature is valid, `R.x == r`.

## Validation Checks

### Signature Component Validation

```typescript theme={null}
function isValidSignatureComponents(r: bigint, s: bigint): boolean {
  const n = SECP256K1_N; // Curve order

  // r must be in [1, n-1]
  if (r < 1n || r >= n) return false;

  // s must be in [1, n-1]
  if (s < 1n || s >= n) return false;

  // Ethereum enforces low-s (s ≤ n/2) to prevent malleability
  if (s > n / 2n) return false;

  return true;
}
```

Invalid components always fail verification.

### Public Key Validation

```typescript theme={null}
function isValidPublicKey(pubkey: Uint8Array): boolean {
  if (pubkey.length !== 64) return false;

  // Parse x and y coordinates
  const x = bytesToBigInt(pubkey.slice(0, 32));
  const y = bytesToBigInt(pubkey.slice(32, 64));

  // Check point is on curve: y² = x³ + 7 (mod p)
  const p = SECP256K1_P; // Field prime
  const y2 = (y * y) % p;
  const x3_plus_7 = (x * x * x + 7n) % p;

  return y2 === x3_plus_7;
}
```

Invalid public keys (not on curve) always fail verification.

## Security Considerations

### Malleability and Low-s

ECDSA signatures have inherent malleability: both (r, s) and (r, n - s) are valid for the same message and key. This can cause issues:

**Problem:**

```typescript theme={null}
// Original signature
const sig1 = { r, s: s, v: 27 };
const valid1 = verify(sig1, hash, pubkey); // true

// Malleated signature (different bytes, same validity)
const sig2 = { r, s: CURVE_ORDER - s, v: 28 };
const valid2 = verify(sig2, hash, pubkey); // also true!
```

**Solution:** Ethereum enforces low-s (s ≤ n/2):

```typescript theme={null}
if (s > CURVE_ORDER / 2n) {
  // Reject high-s signatures
  return false;
}
```

All signatures created by `sign()` use low-s. Verification accepts only low-s.

### Recovery ID (v) Not Required

The `v` component is only needed for **public key recovery** (ecRecover). Standard verification ignores it because the public key is already provided.

```typescript theme={null}
// v is ignored during verification (only r and s matter)
const sig1 = { r, s, v: 27 };
const sig2 = { r, s, v: 28 };

// Both verify the same way if public key is provided
verify(sig1, hash, pubkey) === verify(sig2, hash, pubkey);
```

For recovery-based verification (like Ethereum's `ecRecover`), v is critical.

### Public Key Format

Secp256k1 public keys can be represented in multiple formats:

**Uncompressed (65 bytes):** `0x04 || x || y`

* Standard format with 0x04 prefix
* Contains both x and y coordinates

**Uncompressed without prefix (64 bytes):** `x || y`

* Voltaire's internal format (no prefix)
* Used by our verification API

**Compressed (33 bytes):** `0x02 || x` or `0x03 || x`

* Only x-coordinate + parity bit for y
* Not directly supported (must decompress first)

Our API expects 64-byte keys (no prefix). If you have prefixed keys:

```typescript theme={null}
// Remove 0x04 prefix
if (publicKey.length === 65 && publicKey[0] === 0x04) {
  publicKey = publicKey.slice(1);
}
```

## Test Vectors

### Basic Verification

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

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

// Correct public key: verification succeeds
assert(Secp256k1.verify(signature, messageHash, publicKey) === true);
```

### Wrong Public Key

```typescript theme={null}
// Different private key
const wrongPrivateKey = Bytes32();
wrongPrivateKey[31] = 99;
const wrongPublicKey = Secp256k1.derivePublicKey(wrongPrivateKey);

// Wrong public key: verification fails
assert(Secp256k1.verify(signature, messageHash, wrongPublicKey) === false);
```

### Wrong Message

```typescript theme={null}
const originalHash = Keccak256.hashString("original");
const signature = Secp256k1.sign(originalHash, privateKey);

const differentHash = Keccak256.hashString("different");

// Wrong message hash: verification fails
assert(Secp256k1.verify(signature, differentHash, publicKey) === false);
```

### Malleated Signature

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

// Create malleated signature (r, n - s)
const r = bytesToBigInt(signature.r);
const s = bytesToBigInt(signature.s);
const malleatedS = SECP256K1_N - s;

const malleatedSig = {
  r: signature.r,
  s: bigIntToBytes(malleatedS, 32),
  v: signature.v ^ 1, // Flip recovery ID
};

// Malleated signature (high-s): verification fails
assert(Secp256k1.verify(malleatedSig, messageHash, publicKey) === false);
```

### Invalid Signature Components

```typescript theme={null}
// r = 0 (invalid)
const invalidR = {
  r: Bytes32(), // All zeros
  s: signature.s,
  v: 27,
};
assert(Secp256k1.verify(invalidR, messageHash, publicKey) === false);

// s >= n (invalid)
const invalidS = {
  r: signature.r,
  s: Bytes32().fill(0xff), // All 0xff > n
  v: 27,
};
assert(Secp256k1.verify(invalidS, messageHash, publicKey) === false);
```

## Performance

### Verification Cost

ECDSA verification is computationally expensive:

1. **Modular inversion** - `s^-1 mod n` (expensive)
2. **Two scalar multiplications** - `u1 * G + u2 * public_key`
3. **Point operations** - Elliptic curve point addition

Typical verification time:

* **TypeScript (@noble/curves):** \~1-2ms per signature
* **Zig (native):** \~0.5-1ms per signature
* **WASM (portable):** \~2-4ms per signature

For batch verification of multiple signatures, use optimized batch algorithms (not currently exposed in API).

### EVM Precompile

Ethereum provides `ecRecover` precompile (address 0x01) for on-chain verification:

* **Gas cost:** 3000 gas
* **Input:** 128 bytes (hash, v, r, s)
* **Output:** 32 bytes (recovered address, zero-padded)

For smart contracts, use `ecrecover()` built-in instead of implementing verification in Solidity.

## Implementation Notes

### TypeScript

Uses `@noble/curves/secp256k1`:

* Constant-time operations (side-channel resistant)
* Validates all inputs (signature components, public keys)
* Enforces low-s malleability protection
* \~20KB minified, tree-shakeable

### Zig

Custom implementation:

* ⚠️ **UNAUDITED** - Not security reviewed
* ⚠️ **NOT constant-time** - Timing attack vulnerable
* Basic validation only
* Educational purposes only

## Related

* [Signing](/crypto/secp256k1/signing) - Create signatures with private keys
* [Recovery](/crypto/secp256k1/recovery) - Recover public key from signature
* [Key Derivation](/crypto/secp256k1/key-derivation) - Derive public keys
* [Security](/crypto/secp256k1/security) - Side-channel attacks and mitigations
* [Usage Patterns](/crypto/secp256k1/usage-patterns) - Transaction verification examples
