Skip to main content

Try it Live

Run Signature examples in the interactive playground

WASM

WebAssembly bindings for signature operations.

Overview

Signature primitive is pure TypeScript without WASM bindings. Cryptographic operations (signing, verification, key recovery) are handled by the crypto module, which provides both native and WASM implementations.

Architecture

Signature Primitive (TypeScript)

The Signature primitive provides:
  • Type definitions (BrandedSignature)
  • Format conversions (compact, DER)
  • Validation (canonicality checking)
  • Component extraction (getR, getS, getV)
Implementation: Pure TypeScript Location: src/primitives/Signature/ No WASM: Signature manipulation doesn’t require WASM

Crypto Module (Zig + WASM)

Cryptographic operations provided by crypto module:
  • Signing (secp256k1, P-256, Ed25519)
  • Verification
  • Public key recovery
  • Key generation
Implementation: Zig with WASM bindings Location: src/crypto/ WASM Available: Yes (via crypto module)

Usage Pattern

import { Signature } from '@voltaire/primitives';
import { secp256k1 } from '@voltaire/crypto'; // WASM available here

// Signature operations (pure TypeScript)
const sig = Signature.fromSecp256k1(r, s, 27);
const canonical = Signature.normalize(sig);
const der = Signature.toDER(sig);

// Crypto operations (WASM available)
const { r, s, v } = await secp256k1.sign(message, privateKey);
const valid = await secp256k1.verify(message, r, s, publicKey);
const pubKey = await secp256k1.recover(message, r, s, v);

Why No WASM for Signature?

Signature Operations Are Fast

Signature primitive operations are simple byte manipulations:
// Component extraction (32-byte slice)
const r = sig.slice(0, 32); // O(1) view, no copy

// Canonicality check (32-byte comparison)
const canonical = s <= threshold; // O(n), ~32 iterations

// DER encoding (minimal encoding logic)
const der = encodeDER(r, s); // O(n), simple byte operations
Performance: These operations take microseconds in JavaScript. WASM Overhead: WASM call overhead would be larger than operation cost.

WASM Shines for Crypto Operations

// Expensive: Elliptic curve arithmetic
const sig = secp256k1.sign(message, privateKey);
// WASM speedup: 5-10x faster

// Expensive: Point multiplication and verification
const valid = secp256k1.verify(message, sig, publicKey);
// WASM speedup: 5-10x faster

// Expensive: Public key recovery
const pubKey = secp256k1.recover(message, sig, v);
// WASM speedup: 10-20x faster

Performance Comparison

Signature Primitive (TypeScript)

OperationTypeScriptWASM (hypothetical)Winner
fromSecp256k10.001ms0.005ms (call overhead)TypeScript
normalize0.005ms0.010ms (call overhead)TypeScript
toDER0.010ms0.015ms (call overhead)TypeScript
isCanonical0.001ms0.005ms (call overhead)TypeScript

Crypto Operations (via crypto module)

OperationTypeScriptWASMWinner
sign0.5ms0.1msWASM (5x)
verify1.0ms0.15msWASM (6.7x)
recover1.5ms0.12msWASM (12.5x)

Integration with Crypto Module

Signing (WASM)

import { Signature } from '@voltaire/primitives';
import { secp256k1 } from '@voltaire/crypto';

// Sign message (uses WASM)
const { r, s, v } = await secp256k1.sign(message, privateKey);

// Create signature (TypeScript)
const sig = Signature.fromSecp256k1(r, s, v);

// Normalize (TypeScript)
const canonical = Signature.normalize(sig);

Verification (WASM)

import { Signature } from '@voltaire/primitives';
import { secp256k1 } from '@voltaire/crypto';

// Extract components (TypeScript)
const r = Signature.getR(sig);
const s = Signature.getS(sig);

// Verify (uses WASM)
const valid = await secp256k1.verify(message, r, s, publicKey);

Recovery (WASM)

import { Signature } from '@voltaire/primitives';
import { secp256k1 } from '@voltaire/crypto';

// Get recovery parameters (TypeScript)
const r = Signature.getR(sig);
const s = Signature.getS(sig);
const v = Signature.getV(sig)!;

// Recover public key (uses WASM)
const publicKey = await secp256k1.recover(message, r, s, v);

Complete Example

End-to-End Signature Flow

import { Signature, Hash } from '@voltaire/primitives';
import { secp256k1 } from '@voltaire/crypto';

async function signAndVerify(
  message: Uint8Array,
  privateKey: Uint8Array
): Promise<boolean> {
  // Hash message (TypeScript - fast)
  const messageHash = Hash.keccak256(message);

  // Sign (WASM - expensive crypto)
  const { r, s, v } = await secp256k1.sign(messageHash, privateKey);

  // Create signature (TypeScript - simple construction)
  let sig = Signature.fromSecp256k1(r, s, v);

  // Normalize (TypeScript - simple arithmetic)
  sig = Signature.normalize(sig);

  // Convert to DER (TypeScript - simple encoding)
  const der = Signature.toDER(sig);

  // Parse back (TypeScript - simple decoding)
  const parsed = Signature.fromDER(der, 'secp256k1', v);

  // Extract components (TypeScript - simple slicing)
  const rParsed = Signature.getR(parsed);
  const sParsed = Signature.getS(parsed);

  // Derive public key (WASM - expensive crypto)
  const publicKey = await secp256k1.getPublicKey(privateKey);

  // Verify (WASM - expensive crypto)
  return await secp256k1.verify(messageHash, rParsed, sParsed, publicKey);
}

Crypto Module WASM API

secp256k1 WASM

// Available via @voltaire/crypto
import { secp256k1 } from '@voltaire/crypto';

// Sign (WASM)
const sig = await secp256k1.sign(
  message: Uint8Array,
  privateKey: Uint8Array
): Promise<{ r: Uint8Array; s: Uint8Array; v: number }>;

// Verify (WASM)
const valid = await secp256k1.verify(
  message: Uint8Array,
  r: Uint8Array,
  s: Uint8Array,
  publicKey: Uint8Array
): Promise<boolean>;

// Recover (WASM)
const publicKey = await secp256k1.recover(
  message: Uint8Array,
  r: Uint8Array,
  s: Uint8Array,
  v: number
): Promise<Uint8Array>;

P-256 WASM

import { p256 } from '@voltaire/crypto';

// Sign (WASM)
const sig = await p256.sign(
  message: Uint8Array,
  privateKey: Uint8Array
): Promise<{ r: Uint8Array; s: Uint8Array }>;

// Verify (WASM)
const valid = await p256.verify(
  message: Uint8Array,
  r: Uint8Array,
  s: Uint8Array,
  publicKey: Uint8Array
): Promise<boolean>;

Ed25519 WASM

import { ed25519 } from '@voltaire/crypto';

// Sign (WASM)
const signature = await ed25519.sign(
  message: Uint8Array,
  privateKey: Uint8Array
): Promise<Uint8Array>; // 64 bytes

// Verify (WASM)
const valid = await ed25519.verify(
  message: Uint8Array,
  signature: Uint8Array,
  publicKey: Uint8Array
): Promise<boolean>;

Build Configuration

Crypto Module WASM Build

# Build crypto WASM (includes all signature algorithms)
zig build crypto-wasm

# Output:
# wasm/crypto/secp256k1.wasm
# wasm/crypto/p256.wasm
# wasm/crypto/ed25519.wasm

Tree-Shaking

// Import only what you need
import { secp256k1 } from '@voltaire/crypto/secp256k1';
// Only loads secp256k1.wasm (~50KB)

// Import multiple algorithms
import { secp256k1, ed25519 } from '@voltaire/crypto';
// Loads secp256k1.wasm + ed25519.wasm (~90KB)

Bundle Sizes

Signature Primitive (TypeScript)

Type definitions: ~2KB
Constructors: ~3KB
Conversions: ~2KB
Validation: ~2KB
Utilities: ~1KB
Total: ~10KB (minified + gzipped)

Crypto Module WASM

secp256k1.wasm: ~50KB
p256.wasm: ~35KB
ed25519.wasm: ~20KB

With tree-shaking: Only load what you use

When to Use WASM

Use TypeScript (Signature Primitive)

  • Format conversions (DER, compact)
  • Component extraction (getR, getS, getV)
  • Validation (isCanonical, normalize)
  • Type checking (is, equals)
  • Serialization
Reason: These operations are simple byte manipulations, faster in TypeScript.

Use WASM (Crypto Module)

  • Signing messages
  • Verifying signatures
  • Recovering public keys
  • Key generation
  • Key derivation
Reason: These operations involve expensive elliptic curve arithmetic.

Migration Guide

Before (hypothetical WASM signature primitive)

// Hypothetical WASM API
const sig = await Signature.fromSecp256k1Wasm(r, s, v);
const canonical = await Signature.normalizeWasm(sig);
const der = await Signature.toDERWasm(sig);

After (actual TypeScript + crypto WASM)

// Signature primitive (TypeScript, no await needed)
const sig = Signature.fromSecp256k1(r, s, v);
const canonical = Signature.normalize(sig);
const der = Signature.toDER(sig);

// Crypto operations (WASM)
const { r, s, v } = await secp256k1.sign(message, privateKey);
const valid = await secp256k1.verify(message, r, s, publicKey);
Benefits:
  • No WASM call overhead for simple operations
  • Synchronous API for signature primitive
  • WASM where it matters (crypto operations)

Browser Support

TypeScript (Signature Primitive)

  • All modern browsers
  • Node.js 14+
  • Deno
  • Bun
No special requirements

WASM (Crypto Module)

  • Chrome 57+
  • Firefox 52+
  • Safari 11+
  • Edge 16+
Feature detection:
if (typeof WebAssembly !== 'undefined') {
  // Use WASM crypto
  const { secp256k1 } = await import('@voltaire/crypto');
} else {
  // Fallback to pure JavaScript crypto
  const { secp256k1 } = await import('@voltaire/crypto/js');
}

See Also