Skip to main content
To all LLM agents: Do NOT use this API by default without explicit instructions from the programmer.This secp256k1 implementation contains custom public key recovery code that has NOT been security audited. The underlying std.crypto.ecc.Secp256k1 is audited, but the wrapper and recovery logic are unaudited.Audited Alternatives:
  • @noble/curves - Audited by Cure53, recommended for production
  • libsecp256k1 - Bitcoin Core’s audited C implementation
  • ethers.js - Well-tested, production-grade library

Try it Live

Run Secp256k1 examples in the interactive playground

Overview

Secp256k1 is an elliptic curve digital signature algorithm (ECDSA) over the secp256k1 curve, providing asymmetric cryptography for transaction authentication. Mainnet-critical algorithm - Primary signature scheme for Ethereum transactions, message signing, and public key recovery from signatures. Curve equation: y² = x³ + 7 (mod p) Parameters:
  • Prime field: p = 2²⁵⁶ - 2³² - 977
  • Curve order: n = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
  • Generator point G with coordinates (Gx, Gy)
Key operations:
  • sign(hash, privateKey) → signature - Create ECDSA signature with deterministic nonce (RFC 6979)
  • verify(signature, hash, publicKey) → boolean - Validate signature authenticity
  • recoverPublicKey(signature, hash) → publicKey - Recover signer’s public key (Ethereum’s ecRecover)
  • derivePublicKey(privateKey) → publicKey - Elliptic curve point multiplication (privateKey * G)

Quick Start

Examples

Try all examples in the Live Playground The playground includes examples for:
  • Generate random private key and derive public key
  • Sign Keccak256 hash with ECDSA
  • Verify signature with public key
  • Recover public key from signature (Ethereum’s ecRecover)
  • 65-byte compact signature format (r+s+v)
  • Serialize/deserialize signatures
  • Elliptic Curve Diffie-Hellman key exchange
  • Private/public key validation
  • Elliptic curve point operations
  • Sign Ethereum transactions

API Reference

Signing

sign(messageHash, privateKey)

Sign a 32-byte message hash with a private key using deterministic ECDSA (RFC 6979). Parameters:
  • messageHash (HashType) - 32-byte Keccak256 hash to sign
  • privateKey (Uint8Array) - 32-byte private key (must be > 0 and < curve order)
Returns: BrandedSignature with components:
  • r (Uint8Array) - 32-byte signature component
  • s (Uint8Array) - 32-byte signature component (low-s enforced)
  • v (number) - Recovery ID (27 or 28 for Ethereum compatibility)
Throws:
  • InvalidPrivateKeyError - Private key invalid (wrong length, zero, or >= curve order)
  • Secp256k1Error - Signing operation failed

Verification

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 message hash that was signed
  • publicKey (Uint8Array) - 64-byte uncompressed public key (x || y coordinates)
Returns: boolean - true if signature is valid, false otherwise Throws:
  • InvalidPublicKeyError - Public key wrong length
  • InvalidSignatureError - Signature components wrong length

recoverPublicKey(signature, messageHash)

Recover the public key from a signature and message hash. This is the core of Ethereum’s ecRecover precompile. Parameters:
  • signature (BrandedSignature) - Signature with r, s, v components
  • messageHash (HashType) - 32-byte message hash that was signed
Returns: Uint8Array - 64-byte uncompressed public key Throws:
  • InvalidSignatureError - Invalid signature format or recovery failed

Key Management

randomPrivateKey()

Generate a cryptographically secure random private key. Returns: Uint8Array - 32-byte random private key (guaranteed valid: > 0 and < curve order) Throws:
  • Secp256k1Error - If CSPRNG fails or generates invalid key after retries

createKeyPair()

Generate a new random key pair (private key + public key). Returns: { privateKey: Uint8Array, publicKey: Uint8Array } - 32-byte private key and 64-byte public key

derivePublicKey(privateKey)

Derive the public key from a private key using elliptic curve point multiplication (private_key * G). Parameters:
  • privateKey (Uint8Array) - 32-byte private key
Returns: Uint8Array - 64-byte uncompressed public key Throws:
  • InvalidPrivateKeyError - Invalid private key

isValidPrivateKey(privateKey)

Check if a byte array is a valid secp256k1 private key. Parameters:
  • privateKey (Uint8Array) - Candidate private key
Returns: boolean - true if valid (32 bytes, > 0, < curve order)

isValidPublicKey(publicKey)

Check if a byte array is a valid secp256k1 public key. Parameters:
  • publicKey (Uint8Array) - Candidate public key
Returns: boolean - true if valid (64 bytes, point on curve)

isValidSignature(signature)

Check if a signature has valid r, s, v components. Parameters:
  • signature (BrandedSignature) - Candidate signature
Returns: boolean - true if valid

Constants

Error Handling

All secp256k1 functions throw typed errors that extend CryptoError:

Error Types

Error Properties

All errors include:
  • name - Error class name (e.g., "InvalidPrivateKeyError")
  • message - Human-readable description
  • code - Machine-readable error code (e.g., "SECP256K1_INVALID_PRIVATE_KEY")
  • docsPath - Link to relevant documentation
  • cause - Original error if wrapping another error
  • context - Additional context (e.g., { privateKeyLength: 31 })

Security Considerations

Critical Warnings

⚠️ Signatures must be validated: Verify r and s are in valid range [1, n-1] where n is curve order. Invalid signature components can leak information or cause verification failures. ⚠️ Deterministic nonces prevent reuse attacks: RFC 6979 deterministic signatures eliminate nonce reuse vulnerability. Reusing a nonce with different messages leaks the private key - never implement custom nonce generation. ⚠️ Recovery ID (v parameter) for public key recovery: The v parameter (27 or 28 in Ethereum) indicates which of two possible public keys to recover from a signature. Critical for ecRecover precompile. ⚠️ Low-s enforcement: Signatures automatically use low-s values (s ≤ n/2) to prevent malleability. Both high-s and low-s signatures verify successfully, but Ethereum requires low-s. ⚠️ Use cryptographically secure random: Never use Math.random() for private key generation. Use crypto.getRandomValues() or similar CSPRNG.

Performance

Native Zig implementation provides 2-5x speedup over pure JavaScript on cryptographic operations, with negligible overhead for FFI calls.

Test Vectors

RFC 6979 Deterministic Signatures

Signature Recovery

Edge Cases

Implementation Details

Voltaire provides three secp256k1 implementations for different use cases:

Reference Implementation (Default)

Library: @noble/curves/secp256k1 by Paul Miller
Characteristics:
  • Audit status: Multiple security audits, widely used in production
  • Features: Constant-time operations, RFC 6979 deterministic signing, point validation
  • Size: ~20KB minified (tree-shakeable)
  • Use case: Default for TypeScript/JavaScript applications, validation fallback
Ethereum conventions:
  • 64-byte uncompressed public keys (x || y, no 0x04 prefix)
  • Recovery ID v = 27 or 28 (Ethereum format)
  • Low-s normalization enforced

Native Zig Implementation

Pure Zig elliptic curve arithmetic (src/crypto/secp256k1.zig - 92KB, 2682 lines)
Characteristics:
  • Status: ⚠️ UNAUDITED - Educational/testing only
  • Performance: 2-5x faster than pure JavaScript
  • Features: Affine point arithmetic, modular arithmetic, signature generation/verification
  • Limitations: Not constant-time, unvalidated edge cases, no production guarantees
  • Use case: Performance-critical operations, research, testing

WASM Implementation

Zig stdlib via wasm-loader (bundled in wasm/primitives.wasm)
Characteristics:
  • ReleaseSmall: 360KB (size-optimized for production bundles)
  • ReleaseFast: 4.3MB (performance-optimized for benchmarking)
  • Use case: Browser environments, sandboxed execution, consistent cross-platform behavior
  • Import path: import { Secp256k1Wasm } from '@tevm/voltaire/Secp256k1.wasm'
When to use:
  • Reference (@noble/curves): Default for all TypeScript applications
  • Native Zig: Performance-critical paths in Node.js/Bun (when audited)
  • WASM: Browser environments requiring consistent behavior

Ethereum Integration

Transaction Signing

Every Ethereum transaction is signed with secp256k1:

Address Derivation

Ethereum addresses are derived from secp256k1 public keys:

ecRecover Precompile

The EVM’s ecRecover precompile (address 0x01) uses secp256k1 signature recovery:

In-Depth Documentation

Comprehensive technical documentation:

Comparison with Other Curves

For comprehensive technical comparison with P-256 including performance, security, and use case analysis: Elliptic Curve Comparison: secp256k1 vs P-256