Generate, validate, and convert DASH WIFs and Pay Addresses.
(Base58Check encoding/decoding for Private Keys and Public Key Hashes)
WIF: XCGKuZcKDjNhx8DaNKK4xwMMNzspaoToT6CafJAbBfQTi57buhLK
Address: XrZJJfEKRNobcuwWKTD3bDu8ou7XSWPbc9
A fully-functional, production-ready reference implementation of Dash Keys - suitable for learning DASH specs and protocols, and porting to other languages.
- 🚀 Install
- Terminal (CLI)
- Node & Bundlers
- Browser
- 💪 Usage
- ⚙️ API
- 🧰 Developer Resources
- 👩🏫 Glossary of Terms
- Address, Check, Compressed, Private Key,
- PubKey Hash, Public Key, Version, WIF, etc ...
- 🦓 Fixtures (for testing)
- The Canonical Dash "Zoomonic"
- Anatomy of Addrs & WIFs
- Troubleshooting Uncompressed Keys
- Implementation Details
- 👩🏫 Glossary of Terms
- 📄 License
Works in Command Line, Node, Bun, Bundlers, and Browsers
npm install --location=global dashkeys-cli
dashkeys helpSee DashKey's CLI README at github.com/dashhive/dashkeys-cli.js.
Install
npm install --save dashkeys@1.x
npm install --save @dashincubator/secp256k1@1.xlet DashKeys = require("dashkeys");
let toHex = DashKeys.utils.bytesToHex;
let toBytes = DashKeys.utils.hexToBytes;Install
<script src="https://unpkg.com/@dashincubator/secp256k1@1.x/secp256k1.js"></script>
<script src="https://unpkg.com/dashkeys@1.x/dashkeys.js"></script>async function main() {
"use strict";
let DashKeys = window.DashKeys;
let toHex = DashKeys.utils.bytesToHex;
let toBytes = DashKeys.utils.hexToBytes;
// ...
}
main().catch(function (err) {
console.error("Fail:");
console.error(err.stack || err);
});let wif = await DashKeys.utils.generateWifNonHd();
// ex: "XCGKuZcKDjNhx8DaNKK4xwMMNzspaoToT6CafJAbBfQTi57buhLK"
let addr = await DashKeys.wifToAddr(wif);
// ex: "XrZJJfEKRNobcuwWKTD3bDu8ou7XSWPbc9"You can use DashKeys.privKeyToWif(privateKey) to encode Private Keys to WIFs:
let privBuf = toBytes(
"1d2a6b22fcb5a29a5357eaf27b1444c623e5e580b66ac5f1109e2778a0ffb950",
);
let wif = await DashKeys.privKeyToWif(privateKey);
let addr = await DashKeys.wifToAddr(wif);Dash Keys doesn't do anything that other Base58Check libraries don't do.
The purpose of this rewrite has been to provide, a simpler, lightweight, more streamlined production-ready reference implementation that takes advantage of modern, cross-platform JS-native APIs, such as WebCrypto.
However, it does (we think) do a better job at exposing a functions for how the Base58Check codec is used in practice (rather than everything it can theoretically be used for).
// Bi-Directional Conversions
await DashKeys.addrToPkh(address); // PubKey Hash Uint8Array (ShaRipeBytes)
await DashKeys.pkhToAddr(hashBytes, { version }); // Address (Base58Check-encoded)
await DashKeys.pubkeyToAddr(pubBytes); // Address (Base58Check-encoded)
await DashKeys.privKeyToWif(privBytes, { version }); // WIF (Base58Check-encoded)
await DashKeys.wifToPrivKey(wif); // Private Key Uint8Array
// One-Way Conversions
await DashKeys.wifToAddr(wif); // Address (Base58Check-encoded)
await DashKeys.pubkeyToPkh(pubBytes); // shaRipeBytes Uint8ArrayNote: these all output either Base58Check Strings, or Byte Arrays (Uint8Array).
{
// (default) throw if check(sum) fails
validate: true,
// which encoding to use
// (not all are valid all the time - it depends on the use)
version: "mainnet|testnet|private|pkh|xprv|xpub|cc|4c|ef|8c",
}// Base58Check Codec for all of Private Key and PubKey Hash (and X Keys)
await DashKeys.decode(b58cString, { validate }); // { version, type, check, etc }
await DashKeys.encodeKey(keyBytes, { version }); // Base58Check-encoded keyDecode Output:
{
check: "<hex>",
compressed: true,
type: "private|pkh|xprv|xpub",
version: "cc|4c|ef|8c",
valid: true, // check matches
// possible key types
privateKey: "<hex>",
pubKeyHash: "<hex>",
xprv: "<hex>",
xpub: "<hex>",
}note: compressed only applies to Private Keys and is always true.
Remains in for compatibility, but not used.
// Byte Utils (NOT async)
let toHex = DashKeys.utils.bytesToHex;
toHex(uint8Array); // hex String
let toBytes = DashKeys.utils.hexToBytes;
toBytes(hexString); // bytes Uint8Array
// Hash Utils
await DashKeys.utils.ripemd160sum(bytes); // hash bytes Uint8Array
await DashKeys.utils.sha256sum(bytes); // hash bytes Uint8ArrayWe felt it was important to not strictly depend on our chosen
secp256k1 implementation.
(that's why you have to manually install it as a dependency yourself)
Use these functions as-is, or overwrite them with your own implementation.
// Key Utils (over
await DashKeys.utils.generateWifNonHd({ version }); // WIF string (non-hd, dev tool)
await DashKeys.utils.toPublicKey(privBytes); // Public Key BytesExample Overwrite
You DO NOT need to do this, but you may if you wish:
let Secp256k1 = require("@noble/secp256k1");
DashKeys.utils.generateWifNonHd = async function (opts) {
let privBytes = Secp256k1.utils.randomPrivateKey();
let privateKey = toHex(privBytes);
let version = opts.version ?? "cc";
let wif = await DashKeys.encode(privBytes, { version });
return wif;
};
DashKeys.utils.toPublicKey = async function (privBytes) {
let isCompressed = true;
let pubBytes = Secp256k1.getPublicKey(privBytes, isCompressed);
return pubBytes;
};Here are bunches of terms by their canonical name, as well as a terse description.
- Address
- Check
- Base X
- Base58
- Base58Check
- Compressed Byte
- HD Key
- Private Key
- PubKey Hash
- Public Key
- RIPEMD160
- Version
- WIF
- Zoomonic
Also: Payment Address, Pay Addr, Addr
A Base58Check-encoded PubKey Hash.
(can NOT be reversed into the Public Key)
The encoding is like this:
- Coin Version Public byte(s), which is 4c for DASH
- Public Key Hash (20 bytes)
- Check(sum) is 4 bytes of SHA-256(concat(coin, comp, pkh))
- Base58Check is Base85(concat(coin, comp, pkh, check))
Version: cc
PubKey Hash: ae14c8728915b492d9d77813bd8fddd91ce70948
Check: ce08541e
Decoded: ccae14c8728915b492d9d77813bd8fddd91ce70948ce08541e
Encoded: XrZJJfEKRNobcuwWKTD3bDu8ou7XSWPbc9
A bespoke algorithm for arbitrary-width bit encoding.
Similar to (but not at all compatible with) Base64.
Bit-width is based on the given alphabet's number of characters.
A specific 58-character Base X alphabet.
The same is used for DASH as most cryptocurrencies.
123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
Chosen to eliminate confusion between similar characters.
(0 and O, 1 and l and I, etc)
A Base58 encoding schema with prefixes and suffixes.
verisonBytes is added as a prefix (before encoding).
Some metadata may come after the data.
checkBytes are added as a suffix (before encoding). \
Base58(`${versionBytes}${dataBytes}${metaBytes}${checkBytes}`);See also Address, Check and WIF
Also: Base58 Checksum, Base58 Hash, Base58 SHA-256
The last 4 bytes of a decoded WIF or Addr.
These are the first 4 bytes of the SHA-256 Hash of the same.
Also: Compression Flag, Recovery Bit, Y is Even / Odd Byte, Quadrant
A Base58Check private key has the suffix 0x01, the compression flag.
This indicates that Pub Key Hashes must not include the Y value.
See also: Public Key.
Also: HD Wallet WIF
An HD Key is a Private Key or WIF generated from an HD Wallet.
These are recoverable from a Passphrase "Mnemonic".
(HD means Hierarchical-Deterministic, as in "generated from seed")
HD keys are almost always preferrable over non-HD keys.
See Dash HD, Dash Wallet.
Also: PrivKey
Any 32 random bytes (256-bits) that can produce a valid Public Key.
The public key is produced by creating a point on a known curve.
In essence:
let privBuf = genRandom(32);
let pirvHex = toHex(privBuf);
let privNum = BigInt(`0x${privHex}`);
// magic secp256k1 curve values
let curveN =
0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
let Gx =
55066263022277343669578718895168534326250603453777594175500187360389116729240n;
let Gy =
32670510020758816978083085130507043184471273380659243275938904335757337482424n;
let windowSize = 8;
let isWithinCurveOrder = 0n < privNum && privNum < curveN;
if (!isWithinCurveOrder) {
throw new Error("not in curve order");
}
let BASE = new JacobianPoint(CURVE.Gx, CURVE.Gy, 1n);
let jPoint = BASE.multiply(privNum, { Gx, Gy, windowSize }); // fancy math
let pubPoint = jPoint.toAffine(); // fancy math
let isOdd = pubPoint.y % 2n;
let prefix = "02";
if (isOdd) {
prefix = "03";
}
let x = pubPoint.x.toString(16).padStart(32, "0");
let pubHex = `${prefix}${x}`;See https://github.com/dashhive/secp256k1.js.
Also: Public Key Hash, PKH, PubKeyHash
The public key is hashed with SHA-256.
That result is hashed with RIPEMD-160.
RIPEMD160(SHA256(PublicKey))
Also: PubKey
An 32-byte X value, prefixed with a byte describing the Y value.
The indicator is 0x02, if Y is ever, or 0x03 if Y is odd. \
In essence:
let expectOdd = 0x03 === pubkey[0];
let xBuf = pubkey.subarray(1);
let xHex = toHex(xBuf);
let x = BigInt(xHex);
// magic secp256k1 curve values
let a = 0n;
let b = 7n;
let P = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fn;
function mod(a, b = P) {
let result = a % b;
if (result >= 0n) {
return result;
}
return b + result;
}
let x2 = mod(x * x);
let x3 = mod(x2 * x);
let y2 = mod(x3 + a * x + b);
let y = curveSqrtMod(y2); // very complicated
let isYOdd = (y & 1n) === 1n;
if (expectOdd && !isYOdd) {
y = mod(-y);
}
let pubPoint = { x: x, y: y };See https://github.com/dashhive/secp256k1.js.
An old, deprecated hash 20-byte algorithm - similar to MD5.
We're stuck with it for the foreseeable future. Oh, well.
Also: Base58Check Version, Coin Version, Privacy Byte
0xcc is used for DASH mainnet WIFs (Private Key).
0x4c is the prefix for Payment Addresses (PubKey Hash) .
These bytes Base58Encode to X, (for mystery, i.e. "DarkCoin").
0xef (Priv) and 0x8c (PKH) are used for DASH testnet.
These Base58Encode to Y.
For use with HD tools, this Base58Check codec also supports:
0x0488ade4, which Base58-encodes to the xprv prefix.
0x0488b21e, which Base58-encodes to the xpub prefix.
0x04358394 and 0x043587cf, which encode to tprv and tpub.
See Dash HD for more info about Extended Private Keys (xprv,
xpriv) and Extended Public Keys (xpub).
Also: Wallet Import Format, Paper Wallet, Swipe Key, Private Key QR
A Base58Check-encoded Private Key.
(CAN be reversed into the Private Key)
The encoding is like this:
- Coin Version Private byte(s), which is cc for DASH
- Compression byte (always 0x01)
- Private Key (32 bytes)
- Checksum is 4 bytes of SHA-256(concat(coin, privkey, compression))
- Base58Check is Base85(concat(coin, privkey, compression, checksum))
Version: cc
Comp Byte: 01 (always)
Private Key: 1d2a6b22fcb5a29a5357eaf27b1444c623e5e580b66ac5f1109e2778a0ffb950
Checksum: ec533f80
Decoded: cc011d2a6b22fcb5a29a5357eaf27b1444c623e5e580b66ac5f1109e2778a0ffb950ec533f80
Encoded: XCGKuZcKDjNhx8DaNKK4xwMMNzspaoToT6CafJAbBfQTi57buhLK
The HD Wallet used for all testing fixtures in this ecosystem of code.
(chosen from the original Trezor / BIP-39 test fixtures)
See The Canonical Dash "Zoomonic".
For troubleshooting, debugging, etc.
All keys used in this example - and across this ecosystem of DASH tools - are HD keys derived from the "Zoomonic":
Passphrase (Mnemonic) : zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong
Secret (Salt Password) : TREZOR
HD Path : m/44'/5'/0'/0/0
WIF : XCGKuZcKDjNhx8DaNKK4xwMMNzspaoToT6CafJAbBfQTi57buhLK
Addr : XrZJJfEKRNobcuwWKTD3bDu8ou7XSWPbc9dashkeys inspect --unsafe ./examples/m44_5_0_0-0.wif Version: cc
Private Key: 1d2a6b22fcb5a29a5357eaf27b1444c623e5e580b66ac5f1109e2778a0ffb950
Compressed: 01
Pay Addr: XrZJJfEKRNobcuwWKTD3bDu8ou7XSWPbc9
Check: ec533f80
Valid: trueCorrect Private Key
PrivateKey: cc011d2a6b22fcb5a29a5357eaf27b1444c623e5e580b66ac5f1109e2778a0ffb950
--------
Version: cc
Comp Flag: 01 (Compressed)
Priv Key: 1d2a6b22fcb5a29a5357eaf27b1444c623e5e580b66ac5f1109e2778a0ffb950
--------
WIF: XCGKuZcKDjNhx8DaNKK4xwMMNzspaoToT6CafJAbBfQTi57buhLKCorrect Pub Key Hash
PubKey: 0245ddd5edaa25313bb88ee0abd359f6f58ac38ed597d33a981f1799903633d902
--------
Comp Flag: 02 (Quadrant 2)
X: 45ddd5edaa25313bb88ee0abd359f6f58ac38ed597d33a981f1799903633d902
SHA256: 8e5abfc42a6d7529b860ce2b4b8889380db893438dc96430f597ddb455e85fdd
*RMD160: 54408a877b83cb9706373918a430728f72f3d001 (*not used)
PubKeyHash: ae14c8728915b492d9d77813bd8fddd91ce70948
Check: ce08541e
Version: 4c
--------
Pay Address: XrZJJfEKRNobcuwWKTD3bDu8ou7XSWPbc9If you see these values, then you've mistakenly used uncompressed keys.
Incorrect Private Key (Uncompressed)
PrivateKey: cc1d2a6b22fcb5a29a5357eaf27b1444c623e5e580b66ac5f1109e2778a0ffb950
--------
Version: cc
Comp Flag: missing, or 00 (Uncompressed)
Priv Key: 1d2a6b22fcb5a29a5357eaf27b1444c623e5e580b66ac5f1109e2778a0ffb950
--------
WIF: XCGKuZcKDjNhx8DaNKK4xwMMNzspaoToT6CafJAbBfQTi4vf57Ka (00 comp flag)
7qmhzDpsoPHhYXBZ2f8igQeEgRSZXmWdoh9Wq6hgvAcDrD3Arhr (no comp flag)Incorrect Pub Key Hash (Uncompressed)
PubKey (X+Y): 04
45ddd5edaa25313bb88ee0abd359f6f58ac38ed597d33a981f1799903633d902
607c88b97231d7f1419c772a6c55d2ad6a7c478a66fdd28ac88c622383073d12
--------
Comp Flag: 04, or 'false' (uncompressed)
X: 45ddd5edaa25313bb88ee0abd359f6f58ac38ed597d33a981f1799903633d902
Y: 607c88b97231d7f1419c772a6c55d2ad6a7c478a66fdd28ac88c622383073d12
SHA256: 85c03bf3ba5042d2e7a84f0fcc969a7753a91e9c5c299062e1fdf7e0506b5f66
*RMD160: b9d17c4c4fb6307ba78c8d4853ed07bd7e4c9f5a (*not used)
PubKeyHash: 9eee08ab54036069e5aaa15dcb204baa0fae622d
Check: d38b9fd2
Version: 4c
--------
Pay Address: XqBBkSnvWMcLyyRRvH1S4mWH4f2zugr7CdIt also serves as a reference implementation for porting to other platforms such as modern mobile and desktop programming languages.
As a reference implementation, it's valuable to understand that tedium, here's a peek behind the curtain:
- Address
↔️ PubKey Hash - WIF
↔️ Private Key - Private Key ➡️ Public Key
- Public Key ➡️ PubKey Hash
These are simplified version of what's in the actual code:
(removed error checking, etc, for clarity)
let Base58Check = require("@dashincubator/base58check").Base58Check;
// "dash58check" because we're using the Dash magic version bytes.
let dash58check = Base58Check.create({
privateKeyVersion: "cc", // "ef" for dash testnet, "80" for bitcoin main
pubKeyHashVersion: "4c", // "8c" for dash testnet, "00" for bitcoin main
});
/**
* @param {String} addr
* @returns {Promise<Uint8Array>} - p2pkh (no magic byte or checksum)
*/
async function addrToPkh(addr) {
let b58cAddr = dash58check.decode(addr);
let pubKeyHash = toBytes(b58cAddr.pubKeyHash);
return pubKeyHash;
}
/**
* @param {Uint8Array} pubKeyHash - no magic byte or checksum
* @returns {Promise<String>} - Pay Addr
*/
async function pkhToAddr(pubKeyHash) {
let hex = toHex(pubKeyHash);
let addr = await dash58check.encode({ pubKeyHash: hex });
return addr;
}
/**
* @param {String} wif
* @returns {Promise<Uint8Array>} - private key (no magic byte or checksum)
*/
async function wifToPrivKey(wif) {
let b58cWif = dash58check.decode(wif);
let privateKey = toBytes(b58cWif.privateKey);
return privateKey;
}
/**
* @param {Uint8Array} privKey
* @returns {Promise<String>} - wif
*/
async function privKeyToWif(privKey) {
let privateKey = toHex(privKey);
let wif = await dash58check.encode({ privateKey: privateKey });
return wif;
}
/**
* @param {String} addrOrWif
*/
async function decode(addrOrWif) {
let parts = await dash58check.decode(addrOrWif);
let check = await dash58check.checksum(parts);
let valid = parts.check === check;
parts.valid = valid;
//parts.privBytes = toBytes(parts.privateKey);
//parts.shaRipeBytes = toBytes(parts.pubKeyHash);
return parts;
}
/**
* @param {Uint8Array} buf
* @returns {String} - Pay Addr or WIF
* @throws {Error}
*/
async function encode(buf) {
let hex = toHex(buf);
if (32 === buf.length) {
return await dash58check.encode({
privateKey: hex,
});
}
if (20 === buf.length) {
return await dash58check.encode({
pubKeyHash: hex,
});
}
throw new Error("buffer length must be (PubKeyHash) or 32 (PrivateKey)");
}To keep the dependency tree slim, this includes BaseX and Base58Check, which
are derivatives of base58.cpp, as well as RIPEMD160.
These have all been complete for several years. They do not need updates.
Copyright (c) 2022-2023 Dash Incubator
Copyright (c) 2021-2023 AJ ONeal
MIT License
Copyright (c) 2018 base-x contributors
Copyright (c) 2014-2018 The Bitcoin Core developers
MIT License
Copyright (c) 2016 crypto-browserify
MIT License