BIP-322 JavaScript Library
bip322-js is a TypeScript/JavaScript library providing utility functions for the BIP-322 signature scheme, enabling generic message signing and verification across various Bitcoin address types. It supports P2PKH, P2SH-P2WPKH, P2WPKH, and single-key-spend P2TR addresses on mainnet, testnet, and regtest. The current stable version is 3.0.0, released in April 2025. A key differentiator is its 'Loose BIP-137 Verification' by default, which allows backward compatibility with legacy BIP-137 signatures, even if they have non-standard header flags, by assuming ownership of the private key for all derivable addresses. It also provides functionalities to generate raw `toSpend` and `toSign` BIP-322 transactions.
Common errors
-
TypeError: Signer.sign() missing 4th argument 'network'
cause Attempting to pass the `network` argument to `Signer.sign` in versions 2.0.0 or higher.fixRemove the `network` argument. The function now infers the network directly from the provided Bitcoin address. E.g., `Signer.sign(privateKey, address, message)`. -
TypeError: Cannot read properties of undefined (reading 'compressPublicKey')
cause Using `Address.compressPublicKey` or `Address.uncompressPublicKey` directly from the `Address` class in versions 2.0.0 or higher.fixThese functions have been moved to the `Key` class. Use `Key.compressPublicKey` or `Key.uncompressPublicKey` instead. -
Error: Invalid signature format: expected Base64 string
cause Your code is expecting `Signer.sign` to return a Buffer for P2PKH addresses, but it now returns a Base64 string.fixUpdate your code to expect a `string` (Base64-encoded) return from `Signer.sign` for all address types. If a Buffer is needed, decode the Base64 string, e.g., `Buffer.from(signature, 'base64')`. -
Signatures for Taproot addresses are inconsistent across runs/machines
cause Due to an update in the underlying secp256k1 library, BIP-322 Schnorr signatures for Taproot addresses are now non-deterministic.fixThis is expected behavior and does not indicate an invalid signature. Instead of byte-for-byte comparison, always use `Verifier.verifySignature()` to validate the signature.
Warnings
- breaking The `Signer.sign` function now consistently returns a Base64-encoded string for all address types. Previously, for P2PKH addresses, it incorrectly returned a raw Buffer. Code expecting a Buffer for P2PKH signatures must be updated.
- breaking The `network` argument in `Signer.sign` was removed. The network is now automatically inferred from the provided address.
- breaking The utility functions `Address.compressPublicKey` and `Address.uncompressPublicKey` have been moved.
- breaking Updating `@bitcoinerlab/secp256k1` to v1.2.0 for BIP-322 Schnorr signatures means `signSchnorr` now uses secure internal randomness (auxRand) by default. This makes BIP-322 signatures for Taproot addresses non-deterministic.
- gotcha By default, the library uses 'Loose BIP-137 Verification,' meaning it will attempt to verify BIP-137 signatures even if they have an incorrect header flag, assuming the signature is valid for any address derivable from the public key. This behavior can lead to unexpected valid verifications if strict adherence to BIP-137 header flags is required.
- gotcha The library's 'Loose BIP-137 Verification' also allows BIP-137 signatures to be used for taproot addresses, which is technically out-of-spec for both BIP-137 and BIP-322 specifications. This might lead to compatibility issues with other stricter implementations.
Install
-
npm install bip322-js -
yarn add bip322-js -
pnpm add bip322-js
Imports
- Signer
const Signer = require('bip322-js').Signerimport { Signer } from 'bip322-js' - Verifier
const Verifier = require('bip322-js').Verifierimport { Verifier } from 'bip322-js' - BIP322
const BIP322 = require('bip322-js').BIP322import { BIP322 } from 'bip322-js'
Quickstart
import { Signer, Verifier } from 'bip322-js';
const privateKey = 'L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k'; // Example private key (do not use in production)
const address = 'bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l'; // P2WPKH address
const message = 'Hello World';
async function runBip322Example() {
try {
// Sign the message
const signature = Signer.sign(privateKey, address, message);
console.log('Generated Signature:', signature);
// Verify the signature
const isValid = Verifier.verifySignature(address, message, signature);
console.log('Is signature valid (strict verification disabled by default)?', isValid);
// Verify with strict BIP-137 verification (if applicable for BIP-137 signatures)
const isStrictlyValid = Verifier.verifySignature(address, message, signature, true);
console.log('Is signature valid (strict verification enabled)?', isStrictlyValid);
} catch (error) {
console.error('An error occurred:', error);
}
}
runBip322Example();