A comprehensive Bitcoin utility library for JavaScript/TypeScript, powered by Go’s btcd/btcutil compiled to WebAssembly. Works in both Node.js and browsers.
Provides base58, bech32, address encoding/decoding, amount conversions, Hash160, WIF, BIP-32 HD key derivation, BIP-69 transaction sorting, BIP-174 PSBT inspection, BIP-322 message verification, BIP-327 MuSig2 signing, BIP-352 Silent Payments scanning, BIP-158 GCS filters, Bloom filter hashing, Neutrino scanning, raw transaction and full block utilities.
Install npm dependencies, then build the WASM module and JS wrapper:
npm install
npm run build
This runs two steps under the hood:
npm run build:wasm — Compiles the Go source to btcutil.wasm using
GOOS=js GOARCH=wasm.npm run build:js — Inlines the Go WASM runtime, bundles the TypeScript
source with tsup, and copies
btcutil.wasm into dist/.npm install btcutil-js
import { hdkeychain, hash, address } from 'btcutil-js';
// Generate a BIP-84 native SegWit address from a random seed.
const seed = await hdkeychain.generateSeed();
const master = await hdkeychain.newMaster(seed);
const child = await hdkeychain.derivePath(master, "m/84'/0'/0'/0/0");
const pubKey = await hdkeychain.publicKey(child);
const pkHash = await hash.hash160(pubKey);
const addr = await address.fromWitnessPubKeyHash(pkHash);
console.log(addr); // bc1q…
Every namespace method is async by default because it lazily initializes
the WASM module on first call. If you prefer synchronous calls, call init()
once and use the returned object — all methods on it are sync:
import { init } from 'btcutil-js';
const btcutil = await init();
// Everything below is synchronous — no await needed.
const seed = btcutil.hdkeychain.generateSeed();
const master = btcutil.hdkeychain.newMaster(seed);
const child = btcutil.hdkeychain.derivePath(master, "m/84'/0'/0'/0/0");
const pubKey = btcutil.hdkeychain.publicKey(child);
const pkHash = btcutil.hash.hash160(pubKey);
const addr = btcutil.address.fromWitnessPubKeyHash(pkHash);
console.log(addr); // bc1q…
// Signing is sync too.
const kp = btcutil.btcec.newPrivateKey();
const msgHash = btcutil.chainhash.doubleHash('68656c6c6f');
const sig = btcutil.btcec.schnorrSign(kp.privateKey, msgHash);
const valid = btcutil.btcec.schnorrVerify(
btcutil.btcec.schnorrSerializePubKey(kp.publicKey), msgHash, sig,
);
console.log(valid); // true
// Errors throw synchronously.
try {
btcutil.address.decode('not-an-address');
} catch (e) {
console.error(e.message);
}
The sync API has the same namespaces and method signatures as the async one,
just without Promise<> wrappers. TypeScript provides full autocompletion and
type checking via the BtcutilSync type.
You can pre-initialize the WASM module with a custom source by calling init
before any other function:
import { init } from 'btcutil-js';
// From a URL
await init('/assets/btcutil.wasm');
// From an ArrayBuffer
const buf = await fetch('/assets/btcutil.wasm').then(r => r.arrayBuffer());
await init(buf);
// From a fetch Response (uses streaming compilation)
await init(fetch('/assets/btcutil.wasm'));
The library loads the Go runtime as a static ES-module import (no eval,
no Function() constructor), so the only CSP source it requires is
'wasm-unsafe-eval' for WebAssembly.instantiate*:
Content-Security-Policy: script-src 'self' 'wasm-unsafe-eval'
'unsafe-eval' is not required.
The library also avoids polluting globalThis with its bridge namespace —
the Go-side btcutil namespace lives in module scope, accessed only via
the init() return value or the per-namespace exports. The only globals
touched are the Go runtime shims (fs / process / crypto) which are
installed conditionally by Go’s standard wasm_exec.js only when missing.
Live pages built on this library, all part of the cryptography-toolkit:
| Example | Built on | Source |
|---|---|---|
| BIP-322 message signing & verification | bip322 |
pages/bip322 |
| PSBT editor | psbt |
pages/psbt-editor |
| BIP-157: Compact Filters (watch-only wallet) | WatchOnlyWallet, neutrino, descriptors |
pages/bip157 |
| BIP-352: Silent Payments (scan for received payments) | SilentPaymentScanner, silentpayments |
pages/silentpayments |
See full API docs here, generated with typedoc.
The corresponding Go library this project wraps with WASM is documented here:
And two packages from currently in-flight PRs:
All functions are async (they ensure the WASM module is loaded on first call) — see Synchronous API above for a sync alternative.
Byte parameters accept either hex strings or Uint8Array, and byte
returns are always Uint8Array (type alias: Bytes = string | Uint8Array).
Network parameters accept "mainnet" (default), "testnet" / "testnet3",
"testnet4", "signet", "regtest", or "simnet".
init(wasmSource?)Explicitly initialize the WASM module. Called automatically on the first API
call. Accepts an optional ArrayBuffer, Response, or URL string.
base58Base58 encoding and decoding (with and without check).
| Method | Go function | Description |
|---|---|---|
encode(data) |
base58.Encode() |
Encode bytes to a base58 string. |
decode(str) |
base58.Decode() |
Decode a base58 string to bytes (Uint8Array). |
checkEncode(data, version) |
base58.CheckEncode() |
Encode with a version byte and checksum. |
checkDecode(str) |
base58.CheckDecode() |
Decode and verify checksum. Returns { data, version }. |
bech32Bech32 and bech32m encoding/decoding.
| Method | Go function | Description |
|---|---|---|
encode(hrp, data5bit) |
bech32.Encode() |
Bech32-encode 5-bit data with the given HRP. |
encodeM(hrp, data5bit) |
bech32.EncodeM() |
Bech32m-encode 5-bit data with the given HRP. |
decode(str) |
bech32.Decode() |
Decode a bech32 string (90-char limit). Returns { hrp, data }. |
decodeNoLimit(str) |
bech32.DecodeNoLimit() |
Decode without length limit. Returns { hrp, data }. |
encodeFromBase256(hrp, data) |
bech32.EncodeFromBase256() |
Encode base-256 data (handles 8→5 bit conversion). |
decodeToBase256(str) |
bech32.DecodeToBase256() |
Decode to base-256 data (handles 5→8 bit conversion). Returns { hrp, data }. |
convertBits(data, fromBits, toBits, pad) |
bech32.ConvertBits() |
Convert between bit groups (e.g. 8→5 or 5→8). |
addressBitcoin address encoding, decoding, and creation.
| Method | Go function | Description |
|---|---|---|
decode(addr, network?) |
btcutil.DecodeAddress() |
Decode an address string. Returns AddressInfo with type, scriptAddress, witnessVersion, etc. |
fromPubKeyHash(hash, network?) |
btcutil.NewAddressPubKeyHash() |
Create a P2PKH address from a 20-byte pubkey hash. |
fromScriptHash(hash, network?) |
btcutil.NewAddressScriptHashFromHash() |
Create a P2SH address from a 20-byte script hash. |
fromScript(script, network?) |
btcutil.NewAddressScriptHash() |
Create a P2SH address by hashing a serialized script. |
fromWitnessPubKeyHash(program, network?) |
btcutil.NewAddressWitnessPubKeyHash() |
Create a P2WPKH address from a 20-byte witness program. |
fromWitnessScriptHash(program, network?) |
btcutil.NewAddressWitnessScriptHash() |
Create a P2WSH address from a 32-byte witness program. |
fromTaproot(program, network?) |
btcutil.NewAddressTaproot() |
Create a P2TR (Taproot) address from a 32-byte witness program. |
fromPubKey(pubKey, network?) |
btcutil.NewAddressPubKey() |
Create a P2PK address from a serialized public key. |
amountBitcoin amount conversions between BTC and satoshis.
| Method | Go function | Description |
|---|---|---|
fromBTC(btc) |
btcutil.NewAmount() |
Convert a BTC float to satoshis. |
toBTC(satoshis) |
Amount.ToBTC() |
Convert satoshis to BTC. |
format(satoshis, unit?) |
Amount.Format() |
Format with a unit label. Units: "BTC", "mBTC", "uBTC", "satoshi" / "sat". |
hashCryptographic hash functions.
| Method | Go function | Description |
|---|---|---|
hash160(data) |
btcutil.Hash160() |
Compute RIPEMD160(SHA256(data)). |
wifWallet Import Format encoding and decoding.
| Method | Go function | Description |
|---|---|---|
decode(wifStr) |
btcutil.DecodeWIF() |
Decode a WIF string. Returns { privateKey, compressPubKey, publicKey, network }. |
encode(privateKey, network?, compress?) |
btcutil.NewWIF() |
Encode a private key as a WIF string. compress defaults to true. |
hdkeychainBIP-32 hierarchical deterministic key derivation.
| Method | Go function | Description |
|---|---|---|
newMaster(seed, network?) |
hdkeychain.NewMaster() |
Create a master extended key from a seed. |
fromString(key) |
hdkeychain.NewKeyFromString() |
Parse an xprv/xpub/tprv/tpub string. Returns ExtendedKeyInfo. |
derive(key, index) |
ExtendedKey.Derive() |
Derive a non-hardened child. index must be in [0, 2^31); pass hardened indices via deriveHardened. |
deriveHardened(key, index) |
ExtendedKey.Derive() |
Derive a hardened child (adds 0x80000000 automatically). index must be in [0, 2^31). |
derivePath(key, path) |
ExtendedKey.Derive() |
Derive along a BIP-32 path like "m/44'/0'/0'/0/0". |
neuter(key, targetPubVersion?) |
ExtendedKey.Neuter() / CloneWithVersion() |
Convert a private key to its public counterpart. For non-registered version bytes (yprv/zprv/…), pass the target public version explicitly (4 bytes, e.g. 04b24746 for zpub). |
generateSeed(length?) |
hdkeychain.GenerateSeed() |
Generate a random seed (default 32 bytes). |
publicKey(key) |
ExtendedKey.ECPubKey() |
Get the compressed public key (Uint8Array). |
address(key, network?) |
ExtendedKey.Address() |
Get the P2PKH address. |
bip322BIP-322 generic signed message construction and verification.
| Method | Go function | Description |
|---|---|---|
verifyMessage(message, address, signature, network?) |
bip322.VerifyMessage() |
Verify a BIP-322 signed message. Returns { valid, error? }. |
buildToSignPacketSimple(message, pkScript) |
bip322.BuildToSignPacketSimple() |
Build the to-sign PSBT for the simple variant (native segwit pkScripts). Returns base64. |
buildToSignPacketFull(message, pkScript, txVersion, lockTime, sequence) |
bip322.BuildToSignPacketFull() |
Build the to-sign PSBT for the full variant (legacy / nested-segwit pkScripts). Returns base64. |
serializeTxWitness(witness) |
bip322.SerializeTxWitness() |
Serialise a witness stack to the wire-encoded blob used as the simple-variant signature payload. |
parseTxWitness(rawWitness) |
bip322.ParseTxWitness() |
Inverse of serializeTxWitness. Returns the decoded witness stack. |
Supports P2WPKH, P2SH-P2WPKH, P2TR (Taproot), and multisig address types.
txsortBIP-69 deterministic transaction sorting.
| Method | Go function | Description |
|---|---|---|
sort(rawTx) |
txsort.Sort() |
Sort inputs and outputs per BIP-69. Returns sorted tx Uint8Array. |
isSorted(rawTx) |
txsort.IsSorted() |
Check if a transaction is BIP-69 sorted. |
txTransaction utilities.
| Method | Go function | Description |
|---|---|---|
hash(rawTx) |
Tx.Hash() |
Compute the txid (double-SHA256, reversed). |
witnessHash(rawTx) |
Tx.WitnessHash() |
Compute the witness txid (wtxid). |
hasWitness(rawTx) |
Tx.HasWitness() |
Check if the transaction contains witness data. |
decode(rawTx) |
btcutil.NewTx() |
Decode into a TxDecodeResult: { txid, wtxid, version, locktime, inputs[], outputs[] }. |
encode(data) |
MsgTx.Serialize() |
Serialise a TxData ({ version, locktime, inputs[], outputs[] }) back to raw bytes. Round-trips with decode(). |
blockFull-block utilities.
| Method | Go function | Description |
|---|---|---|
decode(rawBlock) |
wire.MsgBlock.Deserialize() |
Decode into { hash, version, prevBlock, merkleRoot, timestamp, bits, nonce, size, legacySize, weight, transactions[] }; each transaction uses the tx.decode shape (with derived txid/wtxid). weight is the BIP-141 block weight. |
merkleTree(rawBlock) |
blockchain.BuildMerkleTreeStore() semantics |
The full merkle tree bottom-up: levels[0] = txids (display byte order), last level = [merkleRoot]. Odd levels are hashed with the standard duplicate-last rule. |
psbtPartially Signed Bitcoin Transaction (BIP-174) utilities. Mutating functions take a base64 PSBT, apply the change, and return a new base64 PSBT:
let p = await psbt.create(inputs, outputs);
p = await psbt.addInWitnessUtxo(p, 0, 50000, pkScript);
const { psbt: signed } = await psbt.sign(p, 0, sig, pubKey);
const finalized = await psbt.maybeFinalizeAll(signed);
const rawTx = await psbt.extract(finalized);
Read-only:
| Method | Go function | Description |
|---|---|---|
decode(base64Psbt) |
psbt.NewFromRawBytes() |
Decode a PSBT into a PsbtDecodeResult: { unsignedTx, xpubs, unknowns, inputs[], outputs[], fee, isComplete }. The embedded unsignedTx is itself a TxDecodeResult. Per-input/output PSBT fields (partial sigs, BIP-32 derivation, taproot, witness UTXO, …) are populated when present. Master fingerprints come back as 8-char lowercase hex in wire byte order (LE per BIP-174 — same form every wallet/HWW shows, e.g. "73c5da0a"), BIP-32 paths come back as both path: number[] and pathStr: "m/84'/0'/0'/0/0", and global xpub extendedKey fields come back as base58 xpub/xprv strings. |
encode(data) |
Packet.Serialize() |
Re-encode a PsbtData (or full PsbtDecodeResult) back to base64. Round-trips with decode(). Accepts empty PSBTs (zero inputs / zero outputs). |
allUnknowns(base64Psbt) |
walks Packet.Unknowns |
Flatten unknown TLV entries from all three levels into one stream. Returns [{ level: 'global'\|'input'\|'output', index, key, value }] (index is -1 for global). |
isComplete(base64Psbt) |
Packet.IsComplete() |
Check if all inputs are finalized. |
extract(base64Psbt) |
psbt.Extract() |
Extract the final signed transaction. |
getFee(base64Psbt) |
Packet.GetTxFee() |
Get the fee in satoshis (requires UTXO info). |
fromBase64(base64Psbt) |
Packet.Serialize() |
Convert base64 PSBT to raw bytes. |
toBase64(psbtData) |
Packet.B64Encode() |
Convert raw PSBT bytes to base64. |
sumUtxoInputValues(base64Psbt) |
psbt.SumUtxoInputValues() |
Sum all input UTXO values in satoshis. |
inputsReadyToSign(base64Psbt) |
psbt.InputsReadyToSign() |
Verify all inputs have UTXO info. Throws on error. |
sanityCheck(base64Psbt) |
Packet.SanityCheck() |
Validate PSBT format per BIP-174. Throws on error. |
Creation:
| Method | Go function | Description |
|---|---|---|
create(inputs[], outputs[], version?, lockTime?) |
psbt.New() |
Create a new PSBT. Inputs: {txid, vout, sequence?}. Outputs: {value, script}. |
fromUnsignedTx(rawTx) |
psbt.NewFromUnsignedTx() |
Create a PSBT from an unsigned raw transaction. |
Updater — inputs:
| Method | Go function | Description |
|---|---|---|
addInNonWitnessUtxo(psbt, inIndex, rawTx) |
Updater.AddInNonWitnessUtxo() |
Add full previous transaction for non-segwit. |
addInWitnessUtxo(psbt, inIndex, value, pkScript) |
Updater.AddInWitnessUtxo() |
Add witness UTXO for segwit. |
addInSighashType(psbt, inIndex, sighashType) |
Updater.AddInSighashType() |
Set sighash type for an input. |
addInRedeemScript(psbt, inIndex, script) |
Updater.AddInRedeemScript() |
Add P2SH redeem script. |
addInWitnessScript(psbt, inIndex, script) |
Updater.AddInWitnessScript() |
Add witness script. |
addInBip32Derivation(psbt, inIndex, fp, path, pubKey) |
Updater.AddInBip32Derivation() |
Add BIP-32 derivation info. |
Updater — outputs:
| Method | Go function | Description |
|---|---|---|
addOutBip32Derivation(psbt, outIndex, fp, path, pubKey) |
Updater.AddOutBip32Derivation() |
Add BIP-32 derivation info. |
addOutRedeemScript(psbt, outIndex, script) |
Updater.AddOutRedeemScript() |
Add P2SH redeem script. |
addOutWitnessScript(psbt, outIndex, script) |
Updater.AddOutWitnessScript() |
Add witness script. |
Signing:
| Method | Go function | Description |
|---|---|---|
sign(psbt, inIndex, sig, pubKey, redeemScript?, witnessScript?) |
Updater.Sign() |
Attach a signature. Returns {psbt, outcome} (0=success, 1=finalized, -1=invalid). |
Finalization:
| Method | Go function | Description |
|---|---|---|
finalize(psbt, inIndex) |
psbt.Finalize() |
Finalize a specific input. |
maybeFinalize(psbt, inIndex) |
psbt.MaybeFinalize() |
Try to finalize. Returns {psbt, finalized}. |
maybeFinalizeAll(psbt) |
psbt.MaybeFinalizeAll() |
Try to finalize all inputs. |
Sorting:
| Method | Go function | Description |
|---|---|---|
inPlaceSort(psbt) |
psbt.InPlaceSort() |
Sort inputs/outputs per BIP-69. |
Helpers:
| Method | Go function | Description |
|---|---|---|
encodeExtendedKey(xpubStr) |
psbt.EncodeExtendedKey() |
Convert a base58 xpub/xprv string to the 78-byte PSBT-wire form (the checksum-less base58 decoding) used in PSBT_GLOBAL_XPUB keys. |
decodeExtendedKey(bytes) |
psbt.DecodeExtendedKey() |
Inverse of encodeExtendedKey — convert the 78-byte PSBT-wire form back to a base58 xpub/xprv string. |
gcsGolomb-Coded Set filter utilities (BIP-158 compact block filters).
| Method | Go function | Description |
|---|---|---|
buildFilter(p, m, key, dataItems[]) |
gcs.BuildGCSFilter() |
Build a GCS filter. Returns { filter, n }. |
match(filter, n, p, m, key, target) |
Filter.Match() |
Test if a single element matches. |
matchAny(filter, n, p, m, key, targets[]) |
Filter.MatchAny() |
Test if any element matches. |
Parameters: p = false-positive rate (1/2^P), m = filter parameter M,
key = 16-byte SipHash key.
bloomBloom filter utilities.
| Method | Go function | Description |
|---|---|---|
murmurHash3(seed, data) |
bloom.MurmurHash3() |
Compute MurmurHash3 of data with the given seed. |
txscriptBitcoin transaction script analysis, creation, taproot, and signing.
Script type checks:
| Method | Go function | Description |
|---|---|---|
isPayToPubKey(script) |
txscript.IsPayToPubKey() |
Check if script is P2PK. |
isPayToPubKeyHash(script) |
txscript.IsPayToPubKeyHash() |
Check if script is P2PKH. |
isPayToScriptHash(script) |
txscript.IsPayToScriptHash() |
Check if script is P2SH. |
isPayToWitnessPubKeyHash(script) |
txscript.IsPayToWitnessPubKeyHash() |
Check if script is P2WPKH. |
isPayToWitnessScriptHash(script) |
txscript.IsPayToWitnessScriptHash() |
Check if script is P2WSH. |
isPayToTaproot(script) |
txscript.IsPayToTaproot() |
Check if script is P2TR. |
isWitnessProgram(script) |
txscript.IsWitnessProgram() |
Check if script is any witness program. |
isNullData(script) |
txscript.IsNullData() |
Check if script is OP_RETURN null data. |
isMultisigScript(script) |
txscript.IsMultisigScript() |
Check if script is multisig. |
isUnspendable(script) |
txscript.IsUnspendable() |
Check if script is provably unspendable. |
isPushOnlyScript(script) |
txscript.IsPushOnlyScript() |
Check if script contains only push ops. |
scriptHasOpSuccess(script) |
txscript.ScriptHasOpSuccess() |
Check if script contains OP_SUCCESS. |
Script analysis:
| Method | Go function | Description |
|---|---|---|
disasmString(script) |
txscript.DisasmString() |
Disassemble script to human-readable opcodes. |
getScriptClass(script) |
txscript.GetScriptClass() |
Get the standard script class name. |
extractWitnessProgramInfo(script) |
txscript.ExtractWitnessProgramInfo() |
Extract witness version and program. Returns { version, program }. |
extractPkScriptAddrs(script, network?) |
txscript.ExtractPkScriptAddrs() |
Extract addresses and required sigs. Returns { scriptClass, addresses[], reqSigs }. |
pushedData(script) |
txscript.PushedData() |
Extract all data pushes from a script. |
getSigOpCount(script) |
txscript.GetSigOpCount() |
Count signature operations in a script. |
calcMultiSigStats(script) |
txscript.CalcMultiSigStats() |
Get multisig stats. Returns { numPubKeys, numSigs }. |
parsePkScript(script, network?) |
txscript.ParsePkScript() |
Parse into { class, script, address? }. |
computePkScript(sigScript, witness[], network?) |
txscript.ComputePkScript() |
Recover pkScript from spent input’s sigScript/witness. |
Script creation:
| Method | Go function | Description |
|---|---|---|
payToAddrScript(address, network?) |
txscript.PayToAddrScript() |
Create a pkScript paying to an address. |
nullDataScript(data) |
txscript.NullDataScript() |
Create an OP_RETURN null data script. |
payToTaprootScript(pubKey) |
txscript.PayToTaprootScript() |
Create a P2TR script from a 32-byte x-only key. |
multiSigScript(pubKeys[], nRequired, network?) |
txscript.MultiSigScript() |
Create a multisig script. |
Taproot:
| Method | Go function | Description |
|---|---|---|
computeTaprootOutputKey(internalKey, scriptRoot?) |
txscript.ComputeTaprootOutputKey() |
Compute tweaked output key. |
computeTaprootKeyNoScript(internalKey) |
txscript.ComputeTaprootKeyNoScript() |
Compute output key for key-only spend (BIP-86). |
tweakTaprootPrivKey(privKey, scriptRoot?) |
txscript.TweakTaprootPrivKey() |
Tweak a private key for taproot key-path spending. |
parseControlBlock(controlBlock) |
txscript.ParseControlBlock() |
Parse a serialized control block. |
assembleTaprootScriptTree(internalKey, leaves[]) |
txscript.AssembleTaprootScriptTree() |
Build a script tree with control blocks. Returns { outputKey, merkleRoot, leaves[] }. |
Signature hashing:
| Method | Go function | Description |
|---|---|---|
calcSignatureHash(script, hashType, rawTx, idx) |
txscript.CalcSignatureHash() |
Legacy (pre-segwit) sighash. |
calcWitnessSigHash(script, hashType, rawTx, idx, amount) |
txscript.CalcWitnessSigHash() |
BIP-143 witness v0 sighash. |
calcTaprootSignatureHash(hashType, rawTx, idx, prevOuts[]) |
txscript.CalcTaprootSignatureHash() |
BIP-341 taproot sighash. |
Signing:
| Method | Go function | Description |
|---|---|---|
rawTxInSignature(rawTx, idx, subScript, hashType, privKey) |
txscript.RawTxInSignature() |
Legacy input signature (DER + hashType). |
rawTxInWitnessSignature(rawTx, idx, amount, subScript, hashType, privKey) |
txscript.RawTxInWitnessSignature() |
Witness v0 input signature. |
witnessSignature(rawTx, idx, amount, subScript, hashType, privKey, compress) |
txscript.WitnessSignature() |
Complete P2WPKH witness stack (sig + pubkey). |
rawTxInTaprootSignature(rawTx, idx, merkleRoot, hashType, privKey, prevOuts[]) |
txscript.RawTxInTaprootSignature() |
Taproot key-path signature. |
Hash type constants: SigHashAll = 1, SigHashNone = 2, SigHashSingle = 3, SigHashAnyOneCanPay = 0x80, SigHashDefault = 0 (taproot).
btcecsecp256k1 elliptic curve cryptography: key management, ECDSA, Schnorr, ECDH.
Key management:
| Method | Go function | Description |
|---|---|---|
newPrivateKey() |
btcec.NewPrivateKey() |
Generate a random private key. Returns { privateKey, publicKey }. |
pointMultiply(scalar, point?) |
btcec.ScalarBaseMultNonConst() / ScalarMultNonConst() |
Multiply a point (omitted = generator G) by a scalar (big-endian, mod curve order). Returns { x, y, compressed }. |
privKeyFromBytes(privKey) |
btcec.PrivKeyFromBytes() |
Derive key pair from private key bytes. Returns { privateKey, publicKey }. |
pubKeyFromBytes(pubKey) |
btcec.ParsePubKey() |
Parse and normalize a public key to compressed form. |
isCompressedPubKey(pubKey) |
btcec.IsCompressedPubKey() |
Check if public key bytes are compressed (33 bytes). |
serializeUncompressed(pubKey) |
PublicKey.SerializeUncompressed() |
Serialize to uncompressed 65-byte form. |
serializeCompressed(pubKey) |
PublicKey.SerializeCompressed() |
Serialize to compressed 33-byte form. |
ECDH:
| Method | Go function | Description |
|---|---|---|
generateSharedSecret(privKey, pubKey) |
btcec.GenerateSharedSecret() |
Compute ECDH shared secret (32 bytes). |
ECDSA:
| Method | Go function | Description |
|---|---|---|
ecdsaSign(privKey, hash) |
ecdsa.Sign() |
Sign a 32-byte hash (RFC 6979). Returns DER-encoded signature. |
ecdsaVerify(pubKey, hash, sig) |
Signature.Verify() |
Verify a DER-encoded ECDSA signature. |
ecdsaSignCompact(privKey, hash, isCompressed) |
ecdsa.SignCompact() |
Sign and return a 65-byte recoverable compact signature. |
ecdsaRecoverCompact(sig, hash) |
ecdsa.RecoverCompact() |
Recover public key from compact signature. Returns { publicKey, compressed }. |
ecdsaParseSignature(sig) |
ecdsa.ParseSignature() |
Parse and normalize a BER-encoded signature. |
ecdsaParseDERSignature(sig) |
ecdsa.ParseDERSignature() |
Parse a strict DER-encoded signature. |
Schnorr (BIP-340):
| Method | Go function | Description |
|---|---|---|
schnorrSign(privKey, hash) |
schnorr.Sign() |
Sign a 32-byte hash. Returns 64-byte signature. |
schnorrVerify(pubKey, hash, sig) |
Signature.Verify() |
Verify a Schnorr signature. |
schnorrParsePubKey(xOnlyPubKey) |
schnorr.ParsePubKey() |
Parse a 32-byte x-only key. Returns 33-byte compressed. |
schnorrSerializePubKey(pubKey) |
schnorr.SerializePubKey() |
Serialize to 32-byte x-only format. |
schnorrParseSignature(sig) |
schnorr.ParseSignature() |
Parse a 64-byte Schnorr signature. |
musig2BIP-327 MuSig2 multi-signatures (two-round flow), wrapping the low-level step-by-step functions so every intermediate value is inspectable. All functions use BIP-327’s sorted-keys convention — the public key list may be passed in any order, as long as it is the same list everywhere.
const agg = await musig2.aggregateKeys([pub1, pub2]);
const n1 = await musig2.genNonces(pub1); // round 1, per signer
const n2 = await musig2.genNonces(pub2);
const combined = await musig2.aggregateNonces([n1.pubNonce, n2.pubNonce]);
const p1 = await musig2.partialSign( // round 2, per signer
n1.secNonce, priv1, combined, [pub1, pub2], msgHash);
const p2 = await musig2.partialSign(
n2.secNonce, priv2, combined, [pub1, pub2], msgHash);
const sig = await musig2.combineSigs(p1.r, [p1.s, p2.s]);
// await btcec.schnorrVerify(agg.xOnlyKey, msgHash, sig) === true
| Method | Go function | Description |
|---|---|---|
aggregateKeys(pubKeys[]) |
musig2.AggregateKeys() |
Aggregate the signers’ keys. Returns { combinedKey (33B), xOnlyKey (32B), parityOdd }. |
genNonces(pubKey, privKey?, combinedKey?, msg?) |
musig2.GenNonces() |
One signer’s nonce pair { pubNonce (66B), secNonce (97B) }. The optional arguments mix extra commitment entropy into the derivation. The secret nonce is strictly single-use. |
aggregateNonces(pubNonces[]) |
musig2.AggregateNonces() |
Combine all public nonces into the 66-byte combined nonce. |
partialSign(secNonce, privKey, combinedNonce, pubKeys[], msg) |
musig2.Sign() |
One signer’s partial signature { s (32B), r (33B) }; r is the final nonce, identical for every signer. |
combineSigs(finalNonce, partialSigs[]) |
musig2.CombineSigs() |
Combine the s values with the final nonce r into the final 64-byte BIP-340 signature. |
chaincfgBitcoin network configuration parameters.
| Method | Go function | Description |
|---|---|---|
getParams(network) |
chaincfg.*NetParams |
Get network parameters. Returns { name, bech32HRPSegwit, pubKeyHashAddrID, ... }. |
isPubKeyHashAddrID(id) |
chaincfg.IsPubKeyHashAddrID() |
Check if byte is a known P2PKH prefix. |
isScriptHashAddrID(id) |
chaincfg.IsScriptHashAddrID() |
Check if byte is a known P2SH prefix. |
isBech32SegwitPrefix(prefix) |
chaincfg.IsBech32SegwitPrefix() |
Check if string is a known bech32 HRP. |
hdPrivateKeyToPublicKeyID(privateKeyID) |
chaincfg.HDPrivateKeyToPublicKeyID() |
Convert HD private key version to public. |
chainhashSHA-256 and tagged hash utilities.
| Method | Go function | Description |
|---|---|---|
hash(data) |
chainhash.HashB() |
Compute SHA-256. |
doubleHash(data) |
chainhash.DoubleHashB() |
Compute SHA-256d (double SHA-256). |
taggedHash(tag, msgs[]) |
chainhash.TaggedHash() |
Compute BIP-340 tagged hash. |
newHashFromStr(hashStr) |
chainhash.NewHashFromStr() |
Parse a byte-reversed hash string (like a txid) to raw bytes. |
hashToString(hash) |
Hash.String() |
Convert raw bytes to byte-reversed display string. |
descriptorsBIP380 output descriptor parsing, address/script derivation, semantic-policy lifting, weight estimation and spending-plan construction.
Unlike the other namespaces, a parsed descriptor is a long-lived object: the
expensive parse (and the miniscript AST it caches) happens once, and every
derivation reuses it. descriptors.create() returns a Descriptor; call
free() when done, or let the garbage collector release the underlying
WASM-side handle automatically.
import { descriptors } from 'btcutil-js';
const desc = await descriptors.create('wpkh(xpub6Bzik.../*)');
desc.addressAt('mainnet', 0, 0); // 'bc1q...'
desc.free();
With the synchronous API, create() needs no await (the
returned Descriptor’s methods are synchronous either way):
const btcutil = await init();
const desc = btcutil.descriptors.create('tr(xpub.../*)');
const weight = desc.maxWeightToSatisfy();
Descriptor| Method | Go method | Description |
|---|---|---|
toString() |
Descriptor.String() |
Canonical descriptor string, including checksum. |
descType() |
Descriptor.DescType() |
Output type: 'Bare' \| 'Sh' \| 'Pkh' \| 'Wpkh' \| 'Wsh' \| 'ShWsh' \| 'ShWpkh' \| 'Tr'. |
keys() |
Descriptor.Keys() |
All keys, in the order they appear. |
multipathLen() |
Descriptor.MultipathLen() |
Number of multipath elements (1 if none). |
addressAt(network, multipathIndex, derivationIndex) |
Descriptor.AddressAt() |
Derive the address at an index. |
scriptCodeAt(multipathIndex, derivationIndex) |
Descriptor.ScriptCodeAt() |
Script code (for signature hashing) at an index. |
lift() |
Descriptor.Lift() |
Abstract SemanticPolicy tree for analysis. |
maxWeightToSatisfy() |
Descriptor.MaxWeightToSatisfy() |
Upper bound on satisfaction weight (weight units). |
planAt(multipathIndex, derivationIndex, assets) |
Descriptor.PlanAt() |
Build a spending Plan from the available assets. |
free() |
— | Release the WASM-side descriptor (idempotent). |
PlanReturned by Descriptor.planAt(). Exposes the satisfaction sizes and completes
the spend from concrete signatures.
| Member | Go | Description |
|---|---|---|
satisfactionWeight |
Plan.SatisfactionWeight() |
Weight, in weight units, to satisfy the plan. |
scriptSigSize |
Plan.ScriptSigSize() |
scriptSig size in bytes (with var-int prefix). |
witnessSize |
Plan.WitnessSize() |
Witness size in bytes. |
satisfy(satisfier) |
Plan.Satisfy() |
Produce { witness, scriptSig } from the satisfier. |
free() |
— | Release the WASM-side plan (idempotent). |
The assets (for planAt) and satisfier (for satisfy) are objects of
optional callbacks mirroring btcd’s Assets / Satisfier; each is invoked with
the concrete derived key(s) the descriptor needs at that index:
const plan = desc.planAt(0, 0, {
lookupTapKeySpendSig: (pubKey) => 64, // signature size, or false
});
const { witness, scriptSig } = plan.satisfy({
lookupTapKeySpendSig: () => mySchnorrSig, // Uint8Array | hex, or false
});
neutrinoBIP157/158 light-client primitives (“neutrino over HTTP”): the validation
and matching building blocks of a browser-based watch-only wallet. The
library also ships the complete wallet engine built on them —
WatchOnlyWallet (header sync, batched parallel scanning with worker-pool
matching, tip following, UTXO tracking), BlockDnClient and the
OpfsStorage/NodeStorage backends — with
block-dn as the data source. See the
BIP-157 toolkit page
for the browser frontend and
tools/neutrino-demo.mjs for a headless CLI.
Everything consensus- and CPU-critical runs in WASM: header validation
follows btcd’s blockchain rules (proof of work, difficulty retargets,
median-time-past), filters are verified against the BIP157 commitment chain
and matched with btcd’s gcs package. Like descriptors, the stateful
pieces are long-lived objects backed by WASM-side handles: call free()
when done, or let the garbage collector release them.
import { neutrino } from 'btcutil-js';
// Validate raw headers (e.g. straight from a block-dn header file).
const chain = await neutrino.headerChain('mainnet');
chain.append(headerFileBytes); // throws on any invalid header
const state = chain.exportState(); // ~80 KiB; resume without re-validating
// Verify + match one filter file against watched scripts, in one pass.
const watch = await neutrino.watchList([script1, script2]);
const matches = await neutrino.matchFilters(
watch, 0, filterFile, headersSlice, filterHeadersSlice, '',
);
// Fully scan only the blocks whose filter matched.
for (const m of matches) {
const { outputs, spends } = await neutrino.scanBlock(watch, blockBytes);
}
| Method | Description |
|---|---|
headerChain(network, state?) |
Create a HeaderChain, optionally resumed from an exported state. |
watchList(scripts?) |
Create a WatchList, optionally seeded with output scripts. |
matchFilters(watch, startHeight, filterFile, headers, filterHeaders, prevFilterHeader) |
One pass over a var-int prefixed filter file: verify every filter against the committed BIP157 filter-header chain (throws on corruption) and match against the watch list. Returns { height, blockHash }[]. |
scanBlock(watch, blockBytes) |
Extract watched-script outputs and watched-outpoint spends from a full block. Returns { outputs, spends }. |
HeaderChainA validating accumulator over raw 80-byte block headers: previous-hash linkage, proof of work, difficulty-retarget correctness and median-time-past, with accumulated chain work. Keeps a sliding window (~2000 headers) so shallow tail reorgs can be rolled back.
| Method | Description |
|---|---|
append(rawHeaders) |
Validate and append a batch; throws on the first invalid header (valid ones before it remain). Returns the new tip state. |
tip() |
Current { tipHeight, tipHash, tipTime, chainWork }. |
rollback(height) |
Drop all headers above height (within the in-memory window). |
exportState() |
Compact (~80 KiB) resume state; pass to headerChain() to resume instantly. |
free() |
Release the WASM-side chain (idempotent). |
WatchListThe set of watched output scripts (receive detection) and outpoints (spend detection), parked WASM-side so large watch lists aren’t re-marshalled per filter file.
| Method | Description |
|---|---|
addScripts(scripts) |
Add raw output scripts; returns the deduplicated total. |
addOutpoint(txid, vout) / removeOutpoint(txid, vout) |
Watch/unwatch an outpoint for spend detection in scanBlock. |
free() |
Release the WASM-side watch list (idempotent). |
Measured (real mainnet data): 100k headers validate in ~240 ms; a 2,000-filter file verifies and matches in ~12 ms.
silentpaymentsBIP-352
Silent Payments scanning (receiver side), driven by the binary tweak index
of a block-dn server
(/sp/tweaks/<dustLimit>/<startHeight>): per eligible transaction the
server publishes input_hash * A_sum (a 33-byte point), from which the
scanner derives candidate taproot output keys — one ECDH multiplication per
transaction — and matches them against the p2tr custom compact filter.
The scan private key and spend public key never leave the browser; the
server only sees which block ranges are downloaded.
The tweak data is materialized at four dust filter levels (0, 600, 1000 and 3750 sats): a transaction is included if its largest taproot output value is strictly greater than the level, so the higher levels skip transactions whose taproot outputs are all uneconomical dust (inscription postage etc.) and shrink both the download and the ECDH work in spam-heavy ranges. Scan quickly at a high level, re-scan lower if an expected payment doesn’t show up; level 0 is complete.
Like descriptors and neutrino, the scanner is a long-lived object
backed by a WASM-side handle (call free() or let GC release it). The
scanner always tracks the base address and the change label (m=0).
import { silentpayments } from 'btcutil-js';
const scanner = await silentpayments.scanner(scanPriv, spendPub, 'signet');
console.log(scanner.address); // tsp1q... (display form)
console.log(scanner.changeAddress); // the m=0 labeled address
// Verify + ECDH + match one block-dn range in a single WASM pass. The
// binary tweak file's self-describing header (network, format version,
// start height, dust limit) is validated against the request:
const result = await silentpayments.scanBatch(
scanner, 312000, tweakData, p2trFilterFile, headersSlice,
p2trFilterHeadersSlice, prevFilterHeaderHex, 1000,
);
// For a matched (downloaded) block, identify the actual outputs; each
// match carries its block's 33-byte tweak keys:
const found = await silentpayments.scanBlock(
scanner, blockBytes, result.matches[0].tweaks,
);
// found[i] = { txid, vout, value, xOnlyPubKey, label: 'base'|'change',
// k, privKeyTweak }
| Method | Go function | Description |
|---|---|---|
scanner(scanPrivKey, spendPubKey, network?) |
silentpayments.NewAddressForNet() / NewScanAddress() / LabelTweak() |
Create a scanner; exposes the bech32m address and changeAddress. |
scanBatch(scanner, startHeight, tweakData, filterFile, headers, filterHeaders, prevFilterHeader, dustLimit?) |
silentpayments.TransactionOutputKeysForFilter() + MatchBlock() |
One pass over a p2tr filter file range: derive k=0 candidates per served tweak, verify every filter against its committed header chain, match. Returns { matches: { height, blockHash, tweaks }[], skippedTweaks, timings }. |
scanBlock(scanner, blockBytes, tweakBytes) |
silentpayments.CreateOutputKey() |
Identify the scanner’s outputs in a downloaded block across output indexes k = 0, 1, 2, … (BIP-352 continuation), with per-output privKeyTweak (add to the spend private key to derive the signing key). The binary format carries no transaction indexes, so tweaks are paired with outputs by key equality across the whole block. |
scanOutputs(scanner, tweak, xOnlyKeys[]) |
silentpayments.CreateOutputKey() |
Pure identification against a list of x-only keys — the shape of the official BIP-352 receiving test vectors. |
The full fetch/verify/scan pipeline — header sync, p2tr filter-header
caching (shared with the watch-only wallet’s store), batched parallel
scanning on a worker pool with per-phase timing logs, spent-ness lookup —
is the SilentPaymentScanner engine (scan({ ..., dustLimit, onLog }));
the browser frontend is the cryptography-toolkit
“BIP-352: Silent Payments” page, and
tools/sp-demo.mjs is a headless CLI driver
(--dust, --from, --to).
Measured on live signet: ECDH candidate derivation is ~98 % of scan time (~174 µs per eligible transaction single-threaded, scaling with the worker pool); downloads and filter verification are noise. The dust filter levels directly shrink that dominant term.
npm run build
npm test
Tests cover all namespaces including BIP-322 test vectors, ECDSA/Schnorr signing round-trips, taproot script tree assembly, and sighash computation.
Each published release includes dist/SHA256SUMS (with the Go version and
SHA-256 hashes of btcutil.wasm and wasm_exec.js) and a detached PGP
signature dist/SHA256SUMS.asc.
Import the signing key and verify:
curl https://keybase.io/guggero/pgp_keys.asc | gpg --import
gpg --verify node_modules/btcutil-js/dist/SHA256SUMS.asc \
node_modules/btcutil-js/dist/SHA256SUMS
Confirm the key fingerprint:
gpg --list-keys --with-subkey-fingerprints F4FC70F07310028424EFC20A8E4256593F177720
To verify the WASM blob is reproducible, rebuild from source with the same Go
version listed in SHA256SUMS and compare the hash:
npm run build:wasm
sha256sum dist/btcutil.wasm