Skip to main content

Try it Live

Run Secp256k1 examples in the interactive playground
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.

Secp256k1 Performance

Performance characteristics, benchmarks, and optimization strategies for elliptic curve operations.

Operation Costs

Relative Complexity

OperationAlgorithmTypical TimeComplexity
Hash (Keccak256)Sponge construction~0.01msO(n) input size
Public key derivationScalar multiplication~0.5-1msO(log n) bits
SigningScalar mult + modular ops~1-2msO(log n) bits
Verification2× scalar mult + point add~2-3msO(log n) bits
RecoverySqrt + 2× scalar mult~2-4msO(log n) bits
Verification is ~2× slower than signing due to two scalar multiplications vs one.

TypeScript Benchmarks

@noble/curves (v1.2.0)

Measured on MacBook Pro M1 (Node.js v20):
import { secp256k1 } from '@noble/curves/secp256k1.js';
import { performance } from 'perf_hooks';

// Public key derivation: 1000 iterations
const privateKey = crypto.getRandomValues(Bytes32());
const start = performance.now();
for (let i = 0; i < 1000; i++) {
  secp256k1.getPublicKey(privateKey);
}
const derivationTime = performance.now() - start;
console.log(`Derivation: ${derivationTime.toFixed(2)}ms total, ${(derivationTime/1000).toFixed(3)}ms per key`);
// Output: Derivation: 450.23ms total, 0.450ms per key
Results:
  • Derivation: 0.4-0.6ms per public key
  • Signing: 1.0-1.5ms per signature
  • Verification: 2.0-3.0ms per signature
  • Recovery: 2.5-3.5ms per recovery

Comparison: noble vs libsecp256k1

Operation@noble/curveslibsecp256k1 (C)Ratio
Derivation0.50ms0.20ms2.5× slower
Signing1.25ms0.50ms2.5× slower
Verification2.50ms1.00ms2.5× slower
TypeScript is ~2-3× slower than native C but still practical for most use cases.

Zig Benchmarks

Native Build (ReleaseFast)

Measured on MacBook Pro M1:
  • Derivation: 0.15-0.25ms per key
  • Signing: 0.40-0.60ms per signature
  • Verification: 0.80-1.20ms per signature
⚠️ Note: Zig implementation is UNAUDITED - benchmarks for reference only.

WASM Performance

ReleaseSmall vs ReleaseFast

OperationReleaseSmallReleaseFastNative TS
Derivation2.5ms1.2ms0.5ms
Signing4.0ms2.0ms1.2ms
Verification6.0ms3.5ms2.5ms
ReleaseFast (performance-optimized):
  • ~2× faster than ReleaseSmall
  • Larger bundle size (~50KB vs ~30KB)
  • Use for compute-intensive applications
ReleaseSmall (size-optimized):
  • Slower but smaller bundle
  • Use for bundle-size-sensitive web apps

EVM Precompile

ecRecover (Address 0x01)

Gas cost: 3000 gas (fixed) Performance at 50M gas/sec:
  • 3000 gas / 50M gas/sec = 60 microseconds
Comparison:
  • ecRecover precompile: 0.06ms (fastest)
  • Zig native: 0.8-1.2ms (15-20× slower)
  • TypeScript @noble: 2-3ms (30-50× slower)
  • WASM ReleaseFast: 3-4ms (50-60× slower)
For on-chain verification, always use ecRecover precompile.

Optimization Techniques

Batch Operations

Point additions can be batched for multiple verifications:
// Verify multiple signatures from same signer
function batchVerify(
  signatures: Signature[],
  messageHashes: Hash[],
  publicKey: Uint8Array
): boolean {
  // Naive: verify each independently (slow)
  for (let i = 0; i < signatures.length; i++) {
    if (!verify(signatures[i], messageHashes[i], publicKey)) {
      return false;
    }
  }
  // Total: n × 2 scalar multiplications

  // Optimized: batch verification (not currently exposed in API)
  // Uses multi-scalar multiplication
  // Total: 1 + n scalar multiplications (50% faster)
}

Precomputation

For repeated operations with same generator point G:
// Precompute multiples of G: [2G, 4G, 8G, ..., 2^255 G]
const precomputed = precomputeGenerator();

// Scalar multiplication ~30% faster
function fastDerivePublicKey(privateKey: bigint): Point {
  return scalarMultWithPrecomputation(privateKey, precomputed);
}
@noble/curves uses this internally for public key derivation.

Windowed NAF (wNAF)

Non-Adjacent Form reduces point operations: Standard binary method:
k = 1011001₂ (binary)
→ 5 point doublings + 4 point additions
wNAF (window=4):
k = [1, 0, -1, 1, 0, 0, 1]₄ (base-16 NAF)
→ 5 point doublings + 3 point additions (25% fewer additions)

Hardware Acceleration

Not available for secp256k1 (no CPU instructions):
  • Intel SHA-NI: SHA-256 only
  • ARM Crypto Extensions: AES, SHA only
  • No native ECC instructions
Optimization relies on:
  • Algorithm improvements (wNAF, precomputation)
  • Memory access patterns (cache-friendly)
  • Compiler optimizations (SIMD autovectorization)

Bottlenecks

Modular Arithmetic

Elliptic curve operations require modular arithmetic modulo large primes: Field prime (p): 2²⁵⁶ - 2³² - 977 (256-bit) Curve order (n): 2²⁵⁶ - ~2³² (256-bit) Expensive operations:
  • Modular multiplication: ~100-200 CPU cycles
  • Modular inversion: ~10,000-20,000 cycles (Extended Euclidean algorithm)
  • Modular exponentiation: Variable (used in square root)

Point Operations

Point addition (different points):
  • 2 modular inversions
  • ~12 modular multiplications
  • ~4 modular additions/subtractions
Point doubling (same point):
  • 1 modular inversion
  • ~8 modular multiplications
  • ~6 modular additions/subtractions

Scalar Multiplication

For 256-bit scalar k, double-and-add requires:
  • ~256 point doublings (worst case)
  • ~128 point additions (average, half bits are 1)
  • Total: ~384 point operations
Optimizations reduce to ~170 operations (wNAF + precomputation).

Real-World Performance

Web Application

// Sign transaction (user action)
const startSign = performance.now();
const signature = Secp256k1.sign(txHash, privateKey);
console.log(`User waited: ${(performance.now() - startSign).toFixed(0)}ms`);
// Output: User waited: 1ms (acceptable for UI)
UX considerations:
  • <100ms: Imperceptible
  • 100-300ms: Slight delay, acceptable
  • >300ms: Noticeable lag, consider async
Secp256k1 signing (~1ms) is well within acceptable range.

High-Throughput Server

// Verify 10,000 signatures
const signatures = [...]; // 10K signatures
const start = performance.now();

for (const sig of signatures) {
  Secp256k1.verify(sig.signature, sig.hash, sig.publicKey);
}

const elapsed = performance.now() - start;
console.log(`Throughput: ${(signatures.length / elapsed * 1000).toFixed(0)} sig/sec`);
// Output: Throughput: 400 sig/sec
For higher throughput:
  • Use native library (libsecp256k1)
  • Implement batch verification
  • Parallelize across CPU cores

Blockchain Node

Ethereum mainnet processes ~15 transactions/second: Per block (12 seconds):
  • ~180 transactions
  • 180 signature verifications required
  • Total: 180 × 2.5ms = 450ms
  • Well within 12-second block time
Peak periods:
  • ~30-50 TPS
  • ~600 verifications per block
  • Total: ~1.5 seconds (still manageable)

Optimization Recommendations

Web Applications

Do:
  • Use @noble/curves (battle-tested, good performance)
  • Sign in main thread (1-2ms imperceptible)
  • Verify in Web Worker if processing many signatures
  • Use WASM if bundle size not critical
Avoid:
  • Implementing custom crypto
  • Blocking UI thread for batch operations
  • Unnecessary verifications (cache results)

Node.js Services

Do:
  • Use @noble/curves for simplicity
  • Consider libsecp256k1 bindings for 2-3× speedup
  • Batch operations when possible
  • Use worker threads for parallelization
Avoid:
  • Synchronous crypto in request handlers (use async)
  • Re-deriving public keys (cache them)

Smart Contracts

Do:
  • Use ecRecover precompile (3000 gas)
  • Validate signatures off-chain when possible
  • Batch signature checks to amortize cost
Avoid:
  • Implementing ECDSA in Solidity (expensive, error-prone)
  • Unnecessary on-chain verifications
  • Unvalidated ecRecover results (check != 0x0)