Universal Bitcoin signer library with branded types and pluggable CryptoBackend
npm install @btc-vision/ecpair!Bitcoin
!TypeScript
!NodeJS
!NPM

Universal Bitcoin signer library with branded types and a pluggable CryptoBackend. Written in TypeScript with zero CommonJS. Ships with a pure-JS backend (@noble/curves) and a legacy adapter for tiny-secp256k1.
> What is ecpair?
>
> @btc-vision/ecpair provides secp256k1 key management, ECDSA signing, BIP-340 Schnorr signing, Taproot-style key tweaking, and WIF import/export. It is designed for use with @btc-vision/bitcoin and the broader OPNet ecosystem, but works with any Bitcoin library that consumes standard key types.
> Why branded types?
>
> PrivateKey, PublicKey, XOnlyPublicKey, Signature, and other key types are nominal (branded) Uint8Array subtypes. This prevents accidentally passing a raw hash where a private key is expected, or mixing up compressed and x-only public keys. Mistakes are caught at compile time, not at runtime in production.
> Why pluggable backends?
>
> CryptoBackend is an interface. The library ships two implementations:
> - NobleBackend: pure JavaScript via @noble/curves/secp256k1, zero native dependencies
> - LegacyBackend: adapter for existing tiny-secp256k1 installations (WASM or ASM.js)
>
> Swap backends without changing application code.
> No hardcoded networks
>
> The library does not ship Bitcoin, testnet, or regtest constants. Consumers must provide a Network object to every factory method. This keeps the library network-agnostic and avoids accidental mainnet usage in test environments.
``bash`
npm install @btc-vision/ecpair
Requires Node.js >= 24.0.0. The package is ESM-only ("type": "module").
`typescript
import {
ECPairSigner,
createNobleBackend,
createPrivateKey,
createMessageHash,
verifyCryptoBackend,
} from '@btc-vision/ecpair';
import type { Network } from '@btc-vision/ecpair';
// Define your network
const bitcoin: Network = {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'bc',
bech32Opnet: 'op',
bip32: { public: 0x0488b21e, private: 0x0488ade4 },
pubKeyHash: 0x00,
scriptHash: 0x05,
wif: 0x80,
};
// Create backend and verify integrity
const backend = createNobleBackend();
verifyCryptoBackend(backend);
// Generate a random signer (FIPS 186-5 B.4.2 key generation)
const signer = ECPairSigner.makeRandom(backend, bitcoin);
console.log(signer.toWIF());
// Sign and verify
const hash = createMessageHash(new Uint8Array(32));
const sig = signer.sign(hash);
console.log(signer.verify(hash, sig)); // true
// Schnorr (BIP-340)
const schnorrSig = signer.signSchnorr(hash);
console.log(signer.verifySchnorr(hash, schnorrSig)); // true
`
| Old API (v3) | New API (v4) |
|-------------------------------------|-----------------------------------------------------------------|
| ECPairFactory(tinysecp) | createNobleBackend() or createLegacyBackend(tinysecp) |ECPair.makeRandom()
| | ECPairSigner.makeRandom(backend, network) |ECPair.fromPrivateKey(buf, opts)
| | ECPairSigner.fromPrivateKey(backend, privateKey, network) |ECPair.fromPublicKey(buf, opts)
| | ECPairSigner.fromPublicKey(backend, publicKey, network) |ECPair.fromWIF(str, network)
| | ECPairSigner.fromWIF(backend, str, network) |keyPair.network
| (optional) | signer.network (always set, required parameter) |{ network }
| in options | Separate network parameter on every factory method |Set
| | number bitmask of SignerCapability flags |
`typescript`
const signer = ECPairSigner.fromWIF(backend, 'KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn', bitcoin);
console.log(signer.compressed); // true
console.log(signer.toWIF());
`typescript`
const signer = ECPairSigner.fromPrivateKey(
backend,
createPrivateKey(new Uint8Array(32).fill(1)),
bitcoin,
);
`typescript
import { createPublicKey } from '@btc-vision/ecpair';
const pubOnly = ECPairSigner.fromPublicKey(backend, createPublicKey(pubKeyBytes), bitcoin);
console.log(pubOnly.privateKey); // undefined
console.log(pubOnly.verify(hash, sig)); // true
`
`typescript
import type { Bytes32 } from '@btc-vision/ecpair';
const tweakScalar = new Uint8Array(32).fill(2) as Bytes32;
const tweaked = signer.tweak(tweakScalar);
console.log(tweaked.toWIF());
`
`typescript
import { createLegacyBackend } from '@btc-vision/ecpair';
import type { TinySecp256k1Interface } from '@btc-vision/ecpair';
import * as tinysecp from 'tiny-secp256k1';
const legacy = createLegacyBackend(tinysecp as unknown as TinySecp256k1Interface);
const kp = ECPairSigner.makeRandom(legacy, bitcoin);
`
`typescript
import { randomBytes } from 'node:crypto';
const kp = ECPairSigner.makeRandom(backend, bitcoin, {
rng: (size: number) => new Uint8Array(randomBytes(size).buffer),
});
`
The rng function receives 48 bytes (FIPS 186-5 seed length) and must return exactly size bytes.
`typescript
const testnet: Network = {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'tb',
bech32Opnet: 'opt',
bip32: { public: 0x043587cf, private: 0x04358394 },
pubKeyHash: 0x6f,
scriptHash: 0xc4,
wif: 0xef,
};
// fromWIF accepts an array of candidate networks
const kp = ECPairSigner.fromWIF(backend, wifString, [bitcoin, testnet]);
console.log(kp.network === bitcoin); // true if mainnet WIF
`
`typescript
import { SignerCapability } from '@btc-vision/ecpair';
const kp = ECPairSigner.makeRandom(backend, bitcoin);
if (kp.capabilities & SignerCapability.SchnorrSign) {
console.log('Schnorr signing available');
}
// Or use the convenience method
kp.hasCapability(SignerCapability.EcdsaSign); // true
kp.hasCapability(SignerCapability.PrivateKeyExport); // true
`
`typescript
import { encodeWIF, decodeWIF, createPrivateKey } from '@btc-vision/ecpair';
const wif = encodeWIF(createPrivateKey(keyBytes), true, bitcoin);
const decoded = decodeWIF(wif, bitcoin);
// decoded.privateKey, decoded.compressed, decoded.network
`
Visit our API documentation generated by TypeDoc.
`bash`
npm test
npm run lint
npm run lint:tests
npm run format:ci
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Run tests: npm test`
5. Submit a pull request
See CONTRIBUTING.md for details.
- Bugs: Open an issue
- Security: See SECURITY.md - do not open public issues for vulnerabilities