npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dcr-ts

v0.2.1

Published

Decred (DCR) primitives for TypeScript: BLAKE-256, addresses, BIP32 HD keys, WIF, the transaction wire format, the Decred signature hash, and low-S ECDSA signing. Byte-exact with dcrd.

Downloads

288

Readme

dcr-ts

CI npm

Decred (DCR) primitives for TypeScript: BLAKE-256, addresses, WIF, BIP32 HD keys with Decred serialization, BIP39 mnemonics, the transaction wire format, the Decred signature hash, and low-S ECDSA P2PKH signing.

Every consensus-critical byte format is verified byte-for-byte against dcrd — the vectors in test/fixtures/dcrd-vectors.json are generated by a Go program that imports dcrd directly, so this library's output is pinned to the reference implementation rather than to a second hand-rolled one.

Built from scratch using dcrd as the specification. ISC licensed.

Not audited. dcr-ts has had no independent security audit. Byte formats are pinned to dcrd; that says nothing about memory handling, timing or logic bugs. It also cannot erase secrets from memory, offers no constant-time guarantees, and does no transaction-policy validation. Read SECURITY.md before trusting it with funds.

Design

The hard rule mirrors the Rust sibling dcr-rs: hand-roll nothing that touches elliptic-curve math or standard KDFs. Those come from the audited @noble / @scure packages. This library owns only the Decred-specific glue:

  • BLAKE-256 — the 14-round SHA-3 finalist Decred uses for everything (txids, sighashes, address hashes, base58 checksums). This is not BLAKE2 or BLAKE3; it is implemented here from the specification and pinned by dcrd vectors.
  • base58check with the double-BLAKE-256 checksum, and the single-BLAKE-256 checksum quirk used by WIF.
  • Addresses — P2PKH (secp256k1 ECDSA, Ed25519, Schnorr), P2SH, and the full-pubkey address, for mainnet, testnet3, simnet and regnet, with decode/classify and the correct OP_CHECKSIGALT payment scripts for the alternative signature suites.
  • HD keys — BIP32 with Decred's dprv/dpub (and tprv/sprv/rprv…) version bytes and base58check checksum; private (signer) and public (watch-only) derivation. Hardened derivation follows Decred's variation on BIP32, not BIP32 itself — see below.
  • Transactions — the dcrd MsgTx wire format (prefix ‖ witness), byte-exact serialize/parse, and all three txid variants.
  • Signing — the Decred signature hash (not Bitcoin's BIP143) and RFC 6979 / low-S ECDSA signature scripts for P2PKH inputs.

Out of scope: networking/RPC, staking/tickets, mixing, and transaction-building policy (coin selection, fees).

Install

npm install dcr-ts

Ships ESM and CommonJS with type declarations. Node ≥ 18.

Usage

Hashing and addresses

import { blake256, addressFromPubKey, decodeAddress, mainnet } from "dcr-ts";

blake256(new Uint8Array()); // 716f6e86…  (BLAKE-256, not BLAKE2/3)

const addr = addressFromPubKey(compressedPubKey, mainnet); // "Ds…"
const { kind, hash, network } = decodeAddress(addr);       // "pubkeyhash-ecdsa"

HD keys from a mnemonic

import { mnemonicToMasterKey, mainnet } from "dcr-ts";

const master = mnemonicToMasterKey(mnemonic, mainnet);
const key = master.derivePath("m/44'/42'/0'/0/0");
key.toString();   // "dprv…"
key.address();    // "Ds…"

// Watch-only: neuter an account key and derive receive addresses publicly.
const xpub = master.derivePath("m/44'/42'/0'").neuter();
xpub.derive(0).derive(0).address();

Build and sign a transaction

import {
  Transaction,
  outPointFromTxid,
  addressToScript,
  signP2PKHInput,
  decodeWif,
  mainnet,
} from "dcr-ts";

const { privateKey } = decodeWif(wif);
// The network is required: a payment script commits only to the 20-byte hash, so
// without it a pasted testnet address would silently pay whoever controls that
// hash on mainnet.
const prevScript = addressToScript(myAddress, mainnet);

const tx = new Transaction();
tx.addInput(outPointFromTxid(prevTxid, vout), { valueIn: 200_000_000n });
tx.addOutput(199_990_000n, addressToScript(destinationAddress, mainnet));

signP2PKHInput(tx, 0, prevScript, privateKey); // SigHashAll, low-S, RFC 6979
tx.serialize();  // Uint8Array ready for the wire
tx.txid();       // reversed-hex display id

Transaction is a mutable builder and does no policy validation: it will not check fees, dust, amount bounds or that your inputs cover your outputs. It copies the buffers you hand it, so mutating your own scripts afterwards cannot rewrite a transaction you have already signed.

Amounts

import { dcrToAtoms, atomsToDcr } from "dcr-ts";

dcrToAtoms("1.5");        // 150000000n
atomsToDcr(150_000_000n); // "1.50000000"

Signing several inputs

calcSignatureHash re-serializes and re-hashes the whole transaction prefix per call, so signing N inputs one at a time is O(N²). Under SigHashAll the prefix half does not depend on which input is being signed, so signP2PKHInputs computes it once — the same thing dcrd's cachedPrefix argument is for:

import { signP2PKHInputs } from "dcr-ts";

signP2PKHInputs(tx, [
  { idx: 0, subScript: prevScript0, privateKey: key0 },
  { idx: 1, subScript: prevScript1, privateKey: key1 },
]);

Byte-identical to calling signP2PKHInput per input. On the hashing alone this is 12–26x for 50–1000 inputs; end to end the win is smaller (1.7x at 250 inputs) because ECDSA dominates. calcSignatureHash also takes an optional cachedPrefix from sigHashPrefixAll(tx) if you are building signature scripts yourself.

Hardened derivation is not plain BIP32

Decred deviates from BIP32 in the hardened child function, and the difference is load-bearing. dcrd's hdkeychain strips leading zero bytes from a derived private key and carries the shortened string into the next hardened HMAC:

Note that per [BIP32] this should be the fully zero-padded 32-bytes, however, the Decred variation strips leading zeros for legacy reasons and changing it now would break derivation for a lot of Decred wallets that rely on this behavior.

So for a parent scalar with a leading zero byte the hardened HMAC input is 0x00 ‖ key31 ‖ 0x00 ‖ ser32(i) rather than BIP32's 0x00 ‖ 0x00 ‖ key31 ‖ ser32(i) — the same length, different bytes, and every descendant diverges. Roughly 1 seed in 128 is affected on a BIP44 path — two hardened levels below the master, each with a 1/256 chance of a leading zero byte. Measured 0.8–0.9% over 20,000 seeds.

dcrd exposes both variants and dcrwallet uses the legacy one for the entire wallet path, so this library mirrors that:

key.derive(0);            // Decred variant — dcrd Child, what wallets use
key.derivePath("m/44'/42'/0'/0/0");

key.deriveBip32Std(0);    // strict BIP32 — dcrd ChildBIP32Std
key.derivePathBip32Std("m/44'/42'/0'/0/0");

Use the strict form only when strict BIP32 is genuinely what you want. Anything that has to agree with a Decrediton or dcrwallet seed must not: getting it wrong is silent, showing the user a different, empty wallet with coins sent to its addresses invisible to every other Decred wallet holding the same phrase.

Two consequences worth knowing:

  • Public (non-hardened) derivation is unaffected. There is no private key to strip, and a stripped scalar has the same value and therefore the same public key, so an account dpub and every address below it agree between variants.
  • The stripped state does not survive serialization. dcrd pads the scalar back out in the extended-key string, so a key round-tripped through dprv derives strictly from then on — in dcrd too, which this mirrors. Only hardened steps are affected, and in BIP44 the deepest hardened level is the account key, so it rarely shows up in practice.

Both variants are pinned against dcrd-generated vectors from a seed chosen to make them disagree (hd.leadingZero in the fixture).

Where this deliberately does not match dcrd

Byte-exactness is the goal everywhere it is achievable, and every serialization this library produces is pinned against dcrd-generated vectors. But byte formats are not the whole contract: two implementations can agree on every byte they emit and still disagree on what they accept. Four accept/reject divergences are deliberate. Each one fails closed — this library rejects something dcrd takes — so "accepted by dcr-ts" implies "accepted by dcrd", never the reverse.

  • An extended key whose version and key type disagree is refused. NewKeyFromString decides private-vs-public from the key-data byte and treats the version bytes only as a network tag, so a string beginning dpub that wraps 0x00 ‖ privkey32 parses there as a private key — and re-serializes as dprv, since dcrd re-attaches the version matching the key it ended up with. ExtendedKey.fromString takes the type from the version and throws invalid-public-key (or invalid-private-key the other way). No honest encoder emits such a string, dcrd's own String() included, so nothing round-trips differently. The point is that a dpub prefix and isPrivate === false can never disagree, which matters because "it starts with dpub, so it is safe to paste here" is a real pattern.
  • A WIF with an unknown signature-suite byte is refused. dcrd's DecodeWIF switches on that byte with no default arm, so an unrecognised suite yields a WIF holding a nil private key, with the scheme silently defaulted to ECDSA and no error. Its own String() on that struct is not a WIF. There is nothing there to be compatible with.
  • The signing entry points reject an unusable private key. dcrd's secp256k1.PrivKeyFromBytes cannot fail: it reduces mod n and left-pads a short slice, so a zero key signs under an all-zero-X public key and a 31-byte key is silently padded and signed. signHash and friends throw invalid-private-key or bad-length instead.
  • Transaction.fromBytes applies no input/output count caps. This one is the exception to the pattern above: it is more permissive than dcrd, which rejects counts over maxTxInPerMessage (780336) or maxTxOutPerMessage (3728271). Those bounds exist because dcrd decodes from an io.Reader of unknown length and sizes make([]TxIn, count) from the count before reading anything; here the argument is a Uint8Array whose length is already the bound, and nothing is allocated from a declared count. The only blobs that parse here and not there are ~43 MiB or larger, which is over MaxMessagePayload and 115x mainnet's MaxTxSize — neither relayable nor valid. See fromBytes for the caller-side sizing advice that does matter.

One more divergence is worth naming because it is not a rejection at all: ExtendedKey.fromString takes no network and recognises all four, reporting which it found on .network. dcrd's NewKeyFromString takes NetworkParams and returns ErrWrongNetwork for any other network's version. A caller that wants dcrd's answer compares .network itself.

Development

npm install
npm test          # vitest, all vectors checked against dcrd
npm run typecheck # tsc --strict
npm run build     # tsup → dist (esm + cjs + d.ts)

To regenerate the dcrd vectors (requires Go), see vectorgen.

License

ISC