WebAssembly Bindings for libsecp256k1
secp256k1-wasm provides high-performance WebAssembly bindings for `libsecp256k1`, the highly optimized C library for secp256k1 elliptic curve cryptography. This package enables cryptographic operations such as key generation, signature signing, and verification directly within JavaScript environments like web browsers and Node.js, offering near-native performance without requiring platform-specific native add-ons. The current stable version is 2.0.0, released in November 2023. While an explicit release cadence is not defined, releases appear to be feature-driven and occur periodically. Its primary differentiator is its reliance on Emscripten to compile the robust `libsecp256k1` to WebAssembly, making it ideal for applications where cryptographic throughput is critical, such as blockchain wallets or transaction processing, by avoiding the overhead of pure JavaScript implementations.
Common errors
-
TypeError: secp256k1.sign is not a function
cause The WASM module was not fully loaded and initialized before attempting to call a cryptographic function. The initial `initSecp256k1()` call returns a Promise, not the API object itself.fixEnsure you `await` the result of `initSecp256k1()` to get the resolved API object before using its methods. Example: `const secp256k1 = await initSecp256k1();` -
Error: Failed to load WebAssembly module: NetworkError when attempting to fetch resource.
cause The browser or Node.js environment could not find or fetch the `.wasm` binary file, typically because of an incorrect path, server configuration, or bundling issue.fixVerify that the `.wasm` file is located at the expected path relative to your JavaScript entry point or that your build tools (e.g., Webpack, Rollup) are correctly bundling and serving the `.wasm` asset. Check network requests in browser developer tools for the exact path being attempted. -
WebAssembly.Compile is disallowed on the main thread, if the buffer size is larger than 4KB.
cause This browser-specific error occurs when attempting to synchronously compile a large WebAssembly module (over 4KB) on the main thread, which can block the UI.fixEnsure that WebAssembly compilation is handled asynchronously, which is typically the default behavior when using `WebAssembly.instantiateStreaming` or `WebAssembly.compileStreaming` (used by modern Emscripten glue code). If you are manually loading, use streaming compilation or `WebAssembly.instantiate(source, importObject)`. The `secp256k1-wasm` library handles this internally via its promise-based initialization. -
Argument must be a Uint8Array
cause A function expected a `Uint8Array` but received another type, such as a plain JavaScript `Array`, `Buffer` (in older Node.js versions or specific contexts), or a string.fixConvert your input data to a `Uint8Array` before passing it to the `secp256k1-wasm` function. For example, `const messageHash = new Uint8Array(myArray);` or `const messageHash = Buffer.from('hexstring', 'hex');` if starting from a hex string in Node.js, ensuring `Buffer` is compatible.
Warnings
- gotcha The `secp256k1-wasm` module, like most WebAssembly modules, loads asynchronously. Developers must `await` the result of the default export function (`initSecp256k1()`) before attempting to access any cryptographic functions (e.g., `sign`, `verify`). Failure to do so will result in `TypeError: secp256k1.sign is not a function` or similar errors because the API object will not yet be available.
- gotcha All cryptographic inputs and outputs for `secp256k1-wasm` functions (e.g., private keys, public keys, message hashes, signatures) are expected to be `Uint8Array` instances. Using other types like plain JavaScript `Array`s or `Buffer`s (without converting them to `Uint8Array` where necessary, though Node.js `Buffer`s are `Uint8Array` subclasses) can lead to unexpected behavior or errors.
- gotcha When integrating `secp256k1-wasm` into bundlers like Webpack, Rollup, or specific environments like Electron or React Native, you might encounter issues with the WASM file not being correctly located or served. This is often due to the bundler's default asset handling not being configured for `.wasm` files, leading to 'Module not found' or 'Failed to load WebAssembly module' errors.
- gotcha Proper public key validation is a critical security consideration in secp256k1 cryptography. While `libsecp256k1` itself is robust, misusing high-level APIs or introducing invalid curve points can lead to vulnerabilities like small subgroup attacks or invalid public key exploits. Always ensure that public keys are derived from valid private keys or are properly validated when received from external sources.
Install
-
npm install secp256k1-wasm -
yarn add secp256k1-wasm -
pnpm add secp256k1-wasm
Imports
- initSecp256k1
import { sign } from 'secp256k1-wasm'; // Incorrect: default export is a Promise-returning factoryimport initSecp256k1 from 'secp256k1-wasm';
- High-level API (resolved)
const secp256k1 = initSecp256k1(); // Missing await: 'secp256k1' will be a Promise, not the API object
const secp256k1 = await initSecp256k1(); // 'secp256k1' now holds functions like .sign, .verify
- CommonJS Import
const { sign } = require('secp256k1-wasm'); // Incorrect: cannot destructure directly from the initial promise-returning exportconst initSecp256k1 = require('secp256k1-wasm');
Quickstart
import initSecp256k1 from 'secp256k1-wasm';
import { randomBytes } from 'crypto'; // Node.js built-in for secure randomness
async function runSecp256k1Example() {
console.log("Loading secp256k1 WASM module...");
// Initialize the WASM module. This returns a Promise that resolves to the API object.
const secp256k1 = await initSecp256k1();
console.log("secp256k1 WASM module loaded successfully.");
// 1. Generate a random 32-byte private key
const privateKey = randomBytes(32);
console.log(`\nPrivate Key: ${privateKey.toString('hex')}`);
// 2. Create a public key from the private key
// 'false' means uncompressed public key (65 bytes)
const publicKey = secp256k1.pubkeyCreate(privateKey, false);
console.log(`Public Key (uncompressed): ${publicKey.toString('hex')}`);
// 3. Prepare a 32-byte message hash to sign
const messageHash = randomBytes(32);
console.log(`Message Hash: ${messageHash.toString('hex')}`);
// 4. Sign the message hash with the private key
const { signature, recid } = secp256k1.sign(messageHash, privateKey);
console.log(`Signature: ${signature.toString('hex')}`);
console.log(`Recovery ID (RecID): ${recid}`);
// 5. Verify the signature using the original message hash, signature, and public key
const isValid = secp256k1.verify(messageHash, signature, publicKey);
console.log(`Signature valid: ${isValid}`);
// 6. Recover the public key from the message hash, signature, and recovery ID
const recoveredPublicKey = secp256k1.recover(messageHash, signature, recid, false);
console.log(`Recovered Public Key: ${recoveredPublicKey.toString('hex')}`);
console.log(`Recovery matches original public key: ${publicKey.equals(recoveredPublicKey)}`);
}
runSecp256k1Example().catch(console.error);