Comparing version 0.3.1 to 0.9.0
1411
dashkeys.js
@@ -1,184 +0,1321 @@ | ||
(function (exports) { | ||
/** @typedef {import('./base-x.types.js').BaseX} BaseX */ | ||
/** @typedef {import('./base-x.types.js').Create} BaseXCreate */ | ||
/** @typedef {import('./base-x.types.js').Decode} BaseXDecode */ | ||
/** @typedef {import('./base-x.types.js').DecodeUnsafe} BaseXDecodeUnsafe */ | ||
/** @typedef {import('./base-x.types.js').Encode} BaseXEncode */ | ||
/** @typedef {import('./base58check.types.js').Base58Check} Base58Check */ | ||
/** @typedef {import('./base58check.types.js').Checksum} Base58CheckChecksum */ | ||
/** @typedef {import('./base58check.types.js').Create} Base58CheckCreate */ | ||
/** @typedef {import('./base58check.types.js').Decode} Base58CheckDecode */ | ||
/** @typedef {import('./base58check.types.js').DecodeHex} Base58CheckDecodeHex */ | ||
/** @typedef {import('./base58check.types.js').Encode} Base58CheckEncode */ | ||
/** @typedef {import('./base58check.types.js').EncodeHex} Base58CheckEncodeHex */ | ||
/** @typedef {import('./base58check.types.js').EncodeParts} Base58CheckEncodeParts */ | ||
/** @typedef {import('./base58check.types.js').Parts} Base58CheckParts */ | ||
/** @typedef {import('./base58check.types.js').PrivateParts} Base58CheckPrivateParts */ | ||
/** @typedef {import('./base58check.types.js').PubKeyHashParts} Base58CheckPubKeyHashParts */ | ||
/** @typedef {import('./base58check.types.js').XPrvParts} Base58CheckXPrvParts */ | ||
/** @typedef {import('./base58check.types.js').XPubParts} Base58CheckXPubParts */ | ||
/** @typedef {import('./base58check.types.js').Verify} Base58CheckVerify */ | ||
/** @typedef {import('./base58check.types.js').VerifyHex} Base58CheckVerifyHex */ | ||
/** @typedef {import('./ripemd160.types.js').RIPEMD160} RIPEMD160 */ | ||
/** @typedef {import('./ripemd160.types.js').Create} RIPEMD160Create */ | ||
/** @typedef {import('./ripemd160.types.js').Digest} RIPEMD160Digest */ | ||
/** @typedef {import('./ripemd160.types.js').Hash} RIPEMD160Hash */ | ||
/** @typedef {import('./ripemd160.types.js').Update} RIPEMD160Update */ | ||
/** | ||
* @typedef {DashKeys} | ||
* @prop {Decode} decode | ||
* @prop {EncodeKeyUint8Array} encodeKey | ||
*/ | ||
/** | ||
* @typedef DashKeysUtils | ||
* @prop {Uint8ArrayToHex} bytesToHex | ||
* @prop {GenerateWif} generateWifNonHd | ||
* @prop {HexToUint8Array} hexToBytes | ||
* @prop {Hasher} ripemd160sum | ||
* @prop {Hasher} sha256sum | ||
* @prop {ToPublicKey} toPublicKey | ||
*/ | ||
/** @type {BaseX} */ | ||
//@ts-ignore | ||
var BaseX = {}; | ||
/** @type {Base58Check} */ | ||
//@ts-ignore | ||
var Base58Check = {}; | ||
/** @type {RIPEMD160} */ | ||
//@ts-ignore | ||
var RIPEMD160 = {}; | ||
/** @typedef {"4c"} DASH_PKH */ | ||
/** @typedef {"8c"} DASH_PKH_TESTNET */ | ||
/** @typedef {"cc"} DASH_PRIV_KEY */ | ||
/** @typedef {"ef"} DASH_PRIV_KEY_TESTNET */ | ||
/** @typedef {"0488ade4"} XPRV */ | ||
/** @typedef {"0488b21e"} XPUB */ | ||
/** @typedef {"04358394"} TPRV */ | ||
/** @typedef {"043587cf"} TPUB */ | ||
/** @typedef {"mainnet"|"testnet"|DASH_PKH|DASH_PRIV_KEY|DASH_PKH_TESTNET|DASH_PRIV_KEY_TESTNET|"xprv"|"tprv"|"xpub"|"tpub"|XPRV|XPUB|TPRV|TPUB} VERSION */ | ||
/** @typedef {"mainnet"|"cc"|"testnet"|"ef"} VERSION_PRIVATE */ | ||
/** @type {DashKeys} */ | ||
//@ts-ignore | ||
var DashKeys = ("object" === typeof module && exports) || {}; | ||
(function (Window, /** @type {DashKeys} */ _DashKeys) { | ||
"use strict"; | ||
let DashKeys = {}; | ||
//@ts-ignore | ||
exports.DashKeys = DashKeys; | ||
// generally the same across cryptocurrencies | ||
const BASE58 = `123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz`; | ||
// specific to DASH mainnet and testnet | ||
const DASH_PKH = "4c"; | ||
const DASH_PKH_TESTNET = "8c"; | ||
const DASH_PRIV_KEY = "cc"; | ||
const DASH_PRIV_KEY_TESTNET = "ef"; | ||
// generally the same across coins for mainnet | ||
const XPRV = "0488ade4"; | ||
const XPUB = "0488b21e"; | ||
// sometimes different for various coins on testnet | ||
const TPRV = "04358394"; | ||
const TPUB = "043587cf"; | ||
/** @type {import('node:crypto')} */ | ||
//@ts-ignore | ||
let Crypto = exports.crypto || require("node:crypto"); | ||
/** @type {typeof window.crypto} */ | ||
let Crypto = Window.crypto || require("node:crypto"); | ||
let Utils = {}; | ||
/** | ||
* @callback Sha256Sum | ||
* @param {Uint8Array|Buffer} u8 | ||
* @returns {Promise<Uint8Array|Buffer>} | ||
*/ | ||
/** @type {Uint8ArrayToHex} */ | ||
Utils.bytesToHex = function (bytes) { | ||
/** @type {Array<String>} */ | ||
let hex = []; | ||
/** @type {Sha256Sum} */ | ||
let sha256sum = async function (u8) { | ||
let arrayBuffer = await Crypto.subtle.digest("SHA-256", u8); | ||
let buf = new Uint8Array(arrayBuffer); | ||
return buf; | ||
bytes.forEach(function (b) { | ||
let h = b.toString(16); | ||
h = h.padStart(2, "0"); | ||
hex.push(h); | ||
}); | ||
return hex.join(""); | ||
}; | ||
/** @type {import('@dashincubator/base58check').Base58Check} */ | ||
let Base58Check = | ||
//@ts-ignore | ||
exports.Base58Check || require("@dashincubator/base58check").Base58Check; | ||
/** @type {GenerateWif} */ | ||
Utils.generateWifNonHd = async function (opts) { | ||
/** @type {import('@dashincubator/secp256k1')} */ | ||
let Secp256k1 = | ||
//@ts-ignore | ||
Window.nobleSecp256k1 || require("@dashincubator/secp256k1"); | ||
/** @type {import('@dashincubator/ripemd160')} */ | ||
//@ts-ignore | ||
let RIPEMD160 = exports.RIPEMD160 || require("@dashincubator/ripemd160"); | ||
let privBytes = Secp256k1.utils.randomPrivateKey(); | ||
let privateKey = Utils.bytesToHex(privBytes); | ||
let version = opts?.version ?? ""; | ||
switch (version) { | ||
case "mainnet": | ||
version = DASH_PRIV_KEY; | ||
break; | ||
case "testnet": | ||
version = DASH_PRIV_KEY_TESTNET; | ||
break; | ||
default: | ||
// no change | ||
} | ||
/** @type {import('@dashincubator/secp256k1')} */ | ||
let wif = await _DashKeys._dash58check.encode({ privateKey, version }); | ||
return wif; | ||
}; | ||
/** @type {HexToUint8Array} */ | ||
Utils.hexToBytes = function (hex) { | ||
let len = hex.length / 2; | ||
let bytes = new Uint8Array(len); | ||
let index = 0; | ||
for (let i = 0; i < hex.length; i += 2) { | ||
let c = hex.slice(i, i + 2); | ||
let b = parseInt(c, 16); | ||
bytes[index] = b; | ||
index += 1; | ||
} | ||
return bytes; | ||
}; | ||
/** @type {Hasher} */ | ||
Utils.ripemd160sum = async function (bytes) { | ||
let hashBytes = await RIPEMD160.hash(bytes); | ||
return hashBytes; | ||
}; | ||
/** @type {Hasher} */ | ||
Utils.sha256sum = async function (bytes) { | ||
let arrayBuffer = await Crypto.subtle.digest("SHA-256", bytes); | ||
let hashBytes = new Uint8Array(arrayBuffer); | ||
return hashBytes; | ||
}; | ||
/** @type {ToPublicKey} */ | ||
Utils.toPublicKey = async function (privBytes) { | ||
/** @type {import('@dashincubator/secp256k1')} */ | ||
let Secp256k1 = | ||
//@ts-ignore | ||
Window.nobleSecp256k1 || require("@dashincubator/secp256k1"); | ||
let isCompressed = true; | ||
return Secp256k1.getPublicKey(privBytes, isCompressed); | ||
}; | ||
(function () { | ||
// BaseX | ||
// base58 (base-x) encoding / decoding | ||
// Copyright (c) 2022 Dash Incubator (base58) | ||
// Copyright (c) 2021-2022 AJ ONeal (base62) | ||
// Copyright (c) 2018 base-x contributors | ||
// Copyright (c) 2014-2018 The Bitcoin Core developers (base58.cpp) | ||
// Distributed under the MIT software license, see the accompanying | ||
// file LICENSE or http://www.opensource.org/licenses/mit-license.php. | ||
// | ||
// Taken from https://github.com/therootcompany/base62.js | ||
// which is a fork of https://github.com/cryptocoinjs/base-x | ||
/** @type {BaseXCreate} */ | ||
BaseX.create = function (ALPHABET) { | ||
let baseX = {}; | ||
if (!ALPHABET) { | ||
ALPHABET = BASE58; | ||
} | ||
if (ALPHABET.length >= 255) { | ||
throw new TypeError("Alphabet too long"); | ||
} | ||
var BASE_MAP = new Uint8Array(256); | ||
for (var j = 0; j < BASE_MAP.length; j += 1) { | ||
BASE_MAP[j] = 255; | ||
} | ||
for (var i = 0; i < ALPHABET.length; i += 1) { | ||
var x = ALPHABET.charAt(i); | ||
var xc = x.charCodeAt(0); | ||
if (BASE_MAP[xc] !== 255) { | ||
throw new TypeError(x + " is ambiguous"); | ||
} | ||
BASE_MAP[xc] = i; | ||
} | ||
var BASE = ALPHABET.length; | ||
var LEADER = ALPHABET.charAt(0); | ||
var FACTOR = Math.log(BASE) / Math.log(256); // log(BASE) / log(256), rounded up | ||
var iFACTOR = Math.log(256) / Math.log(BASE); // log(256) / log(BASE), rounded up | ||
/** @type {BaseXDecode} */ | ||
baseX.decode = function (string) { | ||
var buffer = decodeUnsafe(string); | ||
if (buffer) { | ||
return buffer; | ||
} | ||
throw new Error("Non-base" + BASE + " character"); | ||
}; | ||
/** @type {BaseXDecodeUnsafe} */ | ||
function decodeUnsafe(source) { | ||
if (typeof source !== "string") { | ||
throw new TypeError("Expected String"); | ||
} | ||
if (source.length === 0) { | ||
return new Uint8Array(0); | ||
} | ||
var psz = 0; | ||
// Skip and count leading '1's. | ||
var zeroes = 0; | ||
var length = 0; | ||
while (source[psz] === LEADER) { | ||
zeroes += 1; | ||
psz += 1; | ||
} | ||
// Allocate enough space in big-endian base256 representation. | ||
var size = ((source.length - psz) * FACTOR + 1) >>> 0; // log(58) / log(256), rounded up. | ||
var b256 = new Uint8Array(size); | ||
// Process the characters. | ||
while (source[psz]) { | ||
// Decode character | ||
var carry = BASE_MAP[source.charCodeAt(psz)]; | ||
// Invalid character | ||
if (carry === 255) { | ||
return null; | ||
} | ||
var i = 0; | ||
for ( | ||
var it3 = size - 1; | ||
(carry !== 0 || i < length) && it3 !== -1; | ||
it3 -= 1, i += 1 | ||
) { | ||
carry += (BASE * b256[it3]) >>> 0; | ||
b256[it3] = carry % 256 >>> 0; | ||
carry = (carry / 256) >>> 0; | ||
} | ||
if (carry !== 0) { | ||
throw new Error("Non-zero carry"); | ||
} | ||
length = i; | ||
psz += 1; | ||
} | ||
// Skip leading zeroes in b256. | ||
var it4 = size - length; | ||
while (it4 !== size && b256[it4] === 0) { | ||
it4 += 1; | ||
} | ||
var vch = new Uint8Array(zeroes + (size - it4)); | ||
var j = zeroes; | ||
while (it4 !== size) { | ||
vch[j] = b256[it4]; | ||
j += 1; | ||
it4 += 1; | ||
} | ||
return vch; | ||
} | ||
/** @type {BaseXEncode} */ | ||
baseX.encode = function (source) { | ||
if (Array.isArray(source) || !(source instanceof Uint8Array)) { | ||
source = Uint8Array.from(source); | ||
} | ||
if (!(source instanceof Uint8Array)) { | ||
throw new TypeError("Expected Uint8Array"); | ||
} | ||
if (source.length === 0) { | ||
return ""; | ||
} | ||
// Skip & count leading zeroes. | ||
var zeroes = 0; | ||
var length = 0; | ||
var pbegin = 0; | ||
var pend = source.length; | ||
while (pbegin !== pend && source[pbegin] === 0) { | ||
pbegin += 1; | ||
zeroes += 1; | ||
} | ||
// Allocate enough space in big-endian base58 representation. | ||
var size = ((pend - pbegin) * iFACTOR + 1) >>> 0; | ||
var b58 = new Uint8Array(size); | ||
// Process the bytes. | ||
while (pbegin !== pend) { | ||
var carry = source[pbegin]; | ||
// Apply "b58 = b58 * 256 + ch". | ||
var i = 0; | ||
for ( | ||
var it1 = size - 1; | ||
(carry !== 0 || i < length) && it1 !== -1; | ||
it1 -= 1, i += 1 | ||
) { | ||
carry += (256 * b58[it1]) >>> 0; | ||
b58[it1] = carry % BASE >>> 0; | ||
carry = (carry / BASE) >>> 0; | ||
} | ||
if (carry !== 0) { | ||
throw new Error("Non-zero carry"); | ||
} | ||
length = i; | ||
pbegin += 1; | ||
} | ||
// Skip leading zeroes in base58 result. | ||
var it2 = size - length; | ||
while (it2 !== size && b58[it2] === 0) { | ||
it2 += 1; | ||
} | ||
// Translate the result into a string. | ||
var str = LEADER.repeat(zeroes); | ||
for (; it2 < size; it2 += 1) { | ||
str += ALPHABET.charAt(b58[it2]); | ||
} | ||
return str; | ||
}; | ||
return baseX; | ||
}; | ||
})(); | ||
(function () { | ||
// Base58Check | ||
// See also: | ||
// - https://en.bitcoin.it/wiki/Base58Check_encoding | ||
// - https://appdevtools.com/base58-encoder-decoder | ||
// - https://dashcore.readme.io/docs/core-ref-transactions-address-conversion | ||
// - https://docs.dash.org/en/stable/developers/testnet.html | ||
/** @type {Base58CheckCreate} */ | ||
Base58Check.create = function (opts) { | ||
let dictionary = opts?.dictionary || BASE58; | ||
// See https://github.com/dashhive/dashkeys.js/blob/1f0f4e0d0aabf9e68d94925d660f00666f502391/dashkeys.js#L38 | ||
let privateKeyVersion = opts?.privateKeyVersion || DASH_PRIV_KEY; | ||
let pubKeyHashVersion = opts?.pubKeyHashVersion || DASH_PKH; | ||
// From https://bitcoin.stackexchange.com/questions/38878/how-does-the-bip32-version-bytes-convert-to-base58 | ||
let xprvVersion = opts?.xprvVersion || XPRV; // base58-encoded "xprv..." | ||
let xpubVersion = opts?.xpubVersion || XPUB; // base58-encoded "xpub..." | ||
let bs58 = BaseX.create(dictionary); | ||
let b58c = {}; | ||
/** @type {Base58CheckChecksum} */ | ||
b58c.checksum = async function (parts) { | ||
b58c._setVersion(parts); | ||
//@ts-ignore | ||
parts.compressed = true; // parts.compressed ?? true; | ||
//@ts-ignore | ||
let key = parts.pubKeyHash || parts.privateKey; | ||
let compression = ""; | ||
//@ts-ignore | ||
if (parts.compressed && 64 === key.length) { | ||
compression = "01"; | ||
} | ||
let hex = `${parts.version}${key}${compression}`; | ||
let check = await b58c._checksumHexRaw(hex); | ||
return check; | ||
}; | ||
/** | ||
* @private | ||
* @param {String} hex | ||
*/ | ||
b58c._checksumHexRaw = async function (hex) { | ||
let buf = Utils.hexToBytes(hex); | ||
let hash1 = await Utils.sha256sum(buf); | ||
let hash2 = await Utils.sha256sum(hash1); | ||
let last4 = hash2.slice(0, 4); | ||
let check = Utils.bytesToHex(last4); | ||
return check; | ||
}; | ||
/** | ||
* @private | ||
* @param {Base58CheckEncodeParts} parts | ||
*/ | ||
b58c._setVersion = function (parts) { | ||
//@ts-ignore | ||
if (parts.pubKeyHash) { | ||
//@ts-ignore | ||
if (parts.privateKey) { | ||
throw new Error( | ||
`[@dashincubator/base58check] either 'privateKey' or 'pubKeyHash' must exist, but not both`, | ||
); | ||
} | ||
} | ||
if (!parts.version) { | ||
//@ts-ignore | ||
if (parts.privateKey) { | ||
parts.version = privateKeyVersion; | ||
} | ||
//@ts-ignore | ||
else if (parts.pubKeyHash) { | ||
parts.version = pubKeyHashVersion; | ||
} | ||
return; | ||
} | ||
//@ts-ignore | ||
if (parts.privateKey) { | ||
if (parts.version === pubKeyHashVersion) { | ||
throw new Error( | ||
`[@dashincubator/base58check] '${parts.version}' is a public version, but the given key is private`, | ||
); | ||
} | ||
return; | ||
} | ||
//@ts-ignore | ||
if (parts.pubKeyHash) { | ||
if (parts.version === privateKeyVersion) { | ||
throw new Error( | ||
`[@dashincubator/base58check] '${parts.version}' is a private version, but the given key is a pubKeyHash`, | ||
); | ||
} | ||
} | ||
}; | ||
/** @type {Base58CheckVerify} */ | ||
b58c.verify = async function (b58Addr, opts) { | ||
let bytes = bs58.decode(b58Addr); | ||
let hex = Utils.bytesToHex(bytes); | ||
return await b58c.verifyHex(hex, opts); | ||
}; | ||
/** @type {Base58CheckVerifyHex} */ | ||
b58c.verifyHex = async function (base58check, opts) { | ||
let parts = b58c.decodeHex(base58check); | ||
let check = await b58c.checksum(parts); | ||
let valid = parts.check === check; | ||
if (!valid) { | ||
if (false !== opts?.verify) { | ||
throw new Error(`expected '${parts.check}', but got '${check}'`); | ||
} | ||
parts.valid = valid; | ||
} | ||
return parts; | ||
}; | ||
/** @type {Base58CheckDecode} */ | ||
b58c.decode = function (b58Addr) { | ||
let bytes = bs58.decode(b58Addr); | ||
let hex = Utils.bytesToHex(bytes); | ||
return b58c.decodeHex(hex); | ||
}; | ||
/** @type {Base58CheckDecodeHex} */ | ||
b58c.decodeHex = function (addr) { | ||
let version = addr.slice(0, privateKeyVersion.length); | ||
let versions = [pubKeyHashVersion, privateKeyVersion]; | ||
let xversions = [xpubVersion, xprvVersion]; | ||
let isXKey = false; | ||
if (!versions.includes(version)) { | ||
let xversion = addr.slice(0, xprvVersion.length); | ||
isXKey = xversions.includes(xversion); | ||
if (!isXKey) { | ||
// TODO xkeys | ||
throw new Error( | ||
`[@dashincubator/base58check] expected pubKeyHash (or privateKey) to start with 0x${pubKeyHashVersion} (or 0x${privateKeyVersion}), not '0x${version}'`, | ||
); | ||
} | ||
version = xversion; | ||
} | ||
// Public Key Hash: 1 + 20 + 4 // 50 hex | ||
// Private Key: 1 + 32 + 1 + 4 // 74 or 76 hex | ||
if (![50, 74, 76].includes(addr.length)) { | ||
if (!isXKey) { | ||
throw new Error( | ||
`pubKeyHash (or privateKey) isn't as long as expected (should be 50, 74, or 76 hex chars, not ${addr.length})`, | ||
); | ||
} | ||
} | ||
let rawAddr = addr.slice(version.length, -8); | ||
if (50 === addr.length) { | ||
return { | ||
version: version, | ||
pubKeyHash: rawAddr, | ||
check: addr.slice(-8), | ||
}; | ||
} | ||
if (isXKey) { | ||
if (version === xprvVersion) { | ||
return { | ||
version: version, | ||
xprv: rawAddr, | ||
check: addr.slice(-8), | ||
}; | ||
} | ||
return { | ||
version: version, | ||
xpub: rawAddr, | ||
check: addr.slice(-8), | ||
}; | ||
} | ||
return { | ||
version: version, | ||
privateKey: rawAddr.slice(0, 64), | ||
compressed: true, // "01" === rawAddr.slice(64), | ||
check: addr.slice(-8), | ||
}; | ||
}; | ||
/** @type {Base58CheckEncode} */ | ||
b58c.encode = async function (parts) { | ||
//@ts-ignore | ||
if (parts.xprv) { | ||
let version = parts.version || xprvVersion; | ||
//@ts-ignore | ||
return await b58c._encodeXKey(`${version}${parts.xprv}`); | ||
} | ||
//@ts-ignore | ||
if (parts.xpub) { | ||
let version = parts.version || xpubVersion; | ||
//@ts-ignore | ||
return await b58c._encodeXKey(`${version}${parts.xpub}`); | ||
} | ||
let hex = await b58c.encodeHex(parts); | ||
let bytes = Utils.hexToBytes(hex); | ||
return bs58.encode(bytes); | ||
}; | ||
/** @type {Base58CheckEncodeHex} */ | ||
b58c.encodeHex = async function (parts) { | ||
//@ts-ignore | ||
if (parts.pubKeyHash) { | ||
return await b58c._encodePubKeyHashHex(parts); | ||
} | ||
//@ts-ignore | ||
if (parts.privateKey) { | ||
return await b58c._encodePrivateKeyHex(parts); | ||
} | ||
throw new Error( | ||
`[@dashincubator/base58check] either 'privateKey' or 'pubKeyHash' must exist to encode`, | ||
); | ||
}; | ||
/** | ||
* @private | ||
* @param {String} versionAndKeyHex | ||
*/ | ||
b58c._encodeXKey = async function (versionAndKeyHex) { | ||
let checkHex = await b58c._checksumHexRaw(versionAndKeyHex); | ||
let bytes = Utils.hexToBytes(`${versionAndKeyHex}${checkHex}`); | ||
return bs58.encode(bytes); | ||
}; | ||
/** | ||
* @private | ||
* @param {Base58CheckEncodeParts} parts | ||
*/ | ||
b58c._encodePrivateKeyHex = async function (parts) { | ||
parts.compressed = true; // parts.compressed ?? true; | ||
//@ts-ignore | ||
let key = parts.privateKey; | ||
if (66 === key.length && "01" === key.slice(-2)) { | ||
//key.slice(0, 64); | ||
parts.compressed = true; | ||
} else if (64 === key.length && parts.compressed) { | ||
key += "01"; | ||
} else { | ||
throw new Error( | ||
`[@dashincubator/dashkeys/base58check] ${key.length} is not a valid length for a private key - it should be 32 bytes (64 hex chars), or 33 bytes with compressed marker byte`, | ||
); | ||
} | ||
b58c._setVersion(parts); | ||
// after version and compression are set | ||
let check = await b58c.checksum(parts); | ||
return `${parts.version}${key}${check}`; | ||
}; | ||
/** | ||
* @private | ||
* @param {Base58CheckEncodeParts} parts | ||
*/ | ||
b58c._encodePubKeyHashHex = async function (parts) { | ||
//@ts-ignore | ||
let key = parts.pubKeyHash; | ||
if (40 !== key.length) { | ||
throw new Error( | ||
`[@dashincubator/base58check] ${key.length} is not a valid pub key hash length, should be 20 bytes (40 hex chars)`, | ||
); | ||
} | ||
b58c._setVersion(parts); | ||
// after version is set | ||
let check = await b58c.checksum(parts); | ||
return `${parts.version}${key}${check}`; | ||
}; | ||
return b58c; | ||
}; | ||
})(); | ||
(function () { | ||
// RIPEMD160 | ||
const ARRAY16 = new Array(16); | ||
const zl = [ | ||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 7, 4, 13, 1, 10, 6, | ||
15, 3, 12, 0, 9, 5, 2, 14, 11, 8, 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, | ||
13, 11, 5, 12, 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, 4, 0, | ||
5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13, | ||
]; | ||
const zr = [ | ||
5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, 6, 11, 3, 7, 0, 13, | ||
5, 10, 14, 15, 8, 12, 4, 9, 1, 2, 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, | ||
10, 0, 4, 13, 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, 12, | ||
15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11, | ||
]; | ||
const sl = [ | ||
11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, 7, 6, 8, 13, 11, | ||
9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, | ||
13, 6, 5, 12, 7, 5, 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, | ||
12, 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6, | ||
]; | ||
const sr = [ | ||
8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, 9, 13, 15, 7, 12, | ||
8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, | ||
5, 14, 13, 13, 7, 5, 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, | ||
8, 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11, | ||
]; | ||
const hl = [0x00000000, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e]; | ||
const hr = [0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0x00000000]; | ||
const blockSize = 64; | ||
class _RIPEMD160 { | ||
constructor() { | ||
// state | ||
/** @private */ | ||
this._a = 0x67452301; | ||
/** @private */ | ||
this._b = 0xefcdab89; | ||
/** @private */ | ||
this._c = 0x98badcfe; | ||
/** @private */ | ||
this._d = 0x10325476; | ||
/** @private */ | ||
this._e = 0xc3d2e1f0; | ||
/** @private */ | ||
this._block = new Uint8Array(blockSize); | ||
/** @private */ | ||
this._blockSize = blockSize; | ||
/** @private */ | ||
this._blockOffset = 0; | ||
/** @private */ | ||
this._length = [0, 0, 0, 0]; | ||
/** @private */ | ||
this._finalized = false; | ||
} | ||
/** @type {RIPEMD160Update} */ | ||
update(data) { | ||
if (this._finalized) { | ||
throw new Error("Digest already called"); | ||
} | ||
if (!(data instanceof Uint8Array)) { | ||
throw new Error("update() requires a Uint8Array"); | ||
} | ||
// consume data | ||
const block = this._block; | ||
let offset = 0; | ||
while (this._blockOffset + data.length - offset >= this._blockSize) { | ||
for (let i = this._blockOffset; i < this._blockSize; ) { | ||
block[i++] = data[offset++]; | ||
} | ||
this._update(); | ||
this._blockOffset = 0; | ||
} | ||
while (offset < data.length) { | ||
block[this._blockOffset++] = data[offset++]; | ||
} | ||
// update length | ||
for (let j = 0, carry = data.length * 8; carry > 0; ++j) { | ||
this._length[j] += carry; | ||
carry = (this._length[j] / 0x0100000000) | 0; | ||
if (carry > 0) { | ||
this._length[j] -= 0x0100000000 * carry; | ||
} | ||
} | ||
return this; | ||
} | ||
/** @private */ | ||
_update() { | ||
const words = ARRAY16; | ||
const dv = new DataView( | ||
this._block.buffer, | ||
this._block.byteOffset, | ||
blockSize, | ||
); | ||
for (let j = 0; j < 16; ++j) { | ||
words[j] = dv.getInt32(j * 4, true); | ||
} | ||
let al = this._a | 0; | ||
let bl = this._b | 0; | ||
let cl = this._c | 0; | ||
let dl = this._d | 0; | ||
let el = this._e | 0; | ||
let ar = this._a | 0; | ||
let br = this._b | 0; | ||
let cr = this._c | 0; | ||
let dr = this._d | 0; | ||
let er = this._e | 0; | ||
// computation | ||
for (let i = 0; i < 80; i += 1) { | ||
let tl; | ||
let tr; | ||
if (i < 16) { | ||
tl = fn1(al, bl, cl, dl, el, words[zl[i]], hl[0], sl[i]); | ||
tr = fn5(ar, br, cr, dr, er, words[zr[i]], hr[0], sr[i]); | ||
} else if (i < 32) { | ||
tl = fn2(al, bl, cl, dl, el, words[zl[i]], hl[1], sl[i]); | ||
tr = fn4(ar, br, cr, dr, er, words[zr[i]], hr[1], sr[i]); | ||
} else if (i < 48) { | ||
tl = fn3(al, bl, cl, dl, el, words[zl[i]], hl[2], sl[i]); | ||
tr = fn3(ar, br, cr, dr, er, words[zr[i]], hr[2], sr[i]); | ||
} else if (i < 64) { | ||
tl = fn4(al, bl, cl, dl, el, words[zl[i]], hl[3], sl[i]); | ||
tr = fn2(ar, br, cr, dr, er, words[zr[i]], hr[3], sr[i]); | ||
} else { | ||
// if (i<80) { | ||
tl = fn5(al, bl, cl, dl, el, words[zl[i]], hl[4], sl[i]); | ||
tr = fn1(ar, br, cr, dr, er, words[zr[i]], hr[4], sr[i]); | ||
} | ||
al = el; | ||
el = dl; | ||
dl = rotl(cl, 10); | ||
cl = bl; | ||
bl = tl; | ||
ar = er; | ||
er = dr; | ||
dr = rotl(cr, 10); | ||
cr = br; | ||
br = tr; | ||
} | ||
// update state | ||
const t = (this._b + cl + dr) | 0; | ||
this._b = (this._c + dl + er) | 0; | ||
this._c = (this._d + el + ar) | 0; | ||
this._d = (this._e + al + br) | 0; | ||
this._e = (this._a + bl + cr) | 0; | ||
this._a = t; | ||
} | ||
/** @type {RIPEMD160Digest} */ | ||
digest() { | ||
if (this._finalized) { | ||
throw new Error("Digest already called"); | ||
} | ||
this._finalized = true; | ||
const dig = this._digest(); | ||
return dig; | ||
} | ||
/** | ||
* @returns {Uint8Array} | ||
*/ | ||
_digest() { | ||
// create padding and handle blocks | ||
this._block[this._blockOffset++] = 0x80; | ||
if (this._blockOffset > 56) { | ||
for (let i = this._blockOffset; i < 64; i++) { | ||
this._block[i] = 0; | ||
} | ||
this._update(); | ||
this._blockOffset = 0; | ||
} | ||
for (let i = this._blockOffset; i < 56; i++) { | ||
this._block[i] = 0; | ||
} | ||
let dv = new DataView( | ||
this._block.buffer, | ||
this._block.byteOffset, | ||
blockSize, | ||
); | ||
dv.setUint32(56, this._length[0], true); | ||
dv.setUint32(60, this._length[1], true); | ||
this._update(); | ||
// produce result | ||
const buffer = new Uint8Array(20); | ||
dv = new DataView(buffer.buffer, buffer.byteOffset, 20); | ||
dv.setInt32(0, this._a, true); | ||
dv.setInt32(4, this._b, true); | ||
dv.setInt32(8, this._c, true); | ||
dv.setInt32(12, this._d, true); | ||
dv.setInt32(16, this._e, true); | ||
return buffer; | ||
} | ||
} | ||
/** @type {RIPEMD160Create} */ | ||
RIPEMD160.create = function () { | ||
return new _RIPEMD160(); | ||
}; | ||
/** @type {RIPEMD160Hash} */ | ||
RIPEMD160.hash = function (bytes) { | ||
let hasher = new _RIPEMD160(); | ||
hasher.update(bytes); | ||
return hasher.digest(); | ||
}; | ||
/** | ||
* @param {number} x | ||
* @param {number} n | ||
* @returns {number} | ||
*/ | ||
function rotl(x, n) { | ||
return (x << n) | (x >>> (32 - n)); | ||
} | ||
/** | ||
* @param {number} a | ||
* @param {number} b | ||
* @param {number} c | ||
* @param {number} d | ||
* @param {number} e | ||
* @param {number} m | ||
* @param {number} k | ||
* @param {number} s | ||
* @returns {number} | ||
*/ | ||
function fn1(a, b, c, d, e, m, k, s) { | ||
return (rotl((a + (b ^ c ^ d) + m + k) | 0, s) + e) | 0; | ||
} | ||
/** | ||
* @param {number} a | ||
* @param {number} b | ||
* @param {number} c | ||
* @param {number} d | ||
* @param {number} e | ||
* @param {number} m | ||
* @param {number} k | ||
* @param {number} s | ||
* @returns {number} | ||
*/ | ||
function fn2(a, b, c, d, e, m, k, s) { | ||
return (rotl((a + ((b & c) | (~b & d)) + m + k) | 0, s) + e) | 0; | ||
} | ||
/** | ||
* @param {number} a | ||
* @param {number} b | ||
* @param {number} c | ||
* @param {number} d | ||
* @param {number} e | ||
* @param {number} m | ||
* @param {number} k | ||
* @param {number} s | ||
* @returns | ||
*/ | ||
function fn3(a, b, c, d, e, m, k, s) { | ||
return (rotl((a + ((b | ~c) ^ d) + m + k) | 0, s) + e) | 0; | ||
} | ||
/** | ||
* @param {number} a | ||
* @param {number} b | ||
* @param {number} c | ||
* @param {number} d | ||
* @param {number} e | ||
* @param {number} m | ||
* @param {number} k | ||
* @param {number} s | ||
* @returns {number} | ||
*/ | ||
function fn4(a, b, c, d, e, m, k, s) { | ||
return (rotl((a + ((b & d) | (c & ~d)) + m + k) | 0, s) + e) | 0; | ||
} | ||
/** | ||
* @param {number} a | ||
* @param {number} b | ||
* @param {number} c | ||
* @param {number} d | ||
* @param {number} e | ||
* @param {number} m | ||
* @param {number} k | ||
* @param {number} s | ||
* @returns {number} | ||
*/ | ||
function fn5(a, b, c, d, e, m, k, s) { | ||
return (rotl((a + (b ^ (c | ~d)) + m + k) | 0, s) + e) | 0; | ||
} | ||
})(); | ||
let dash58check = Base58Check.create(); | ||
//@ts-ignore | ||
let Secp256k1 = exports.nobleSecp256k1 || require("@dashincubator/secp256k1"); | ||
_DashKeys._dash58check = dash58check; | ||
/** | ||
* Gets crypto-random bytes that may or may not be a valid private key | ||
* (and that's okay - expected even) | ||
* @param {Number} len | ||
* @returns {Uint8Array} | ||
*/ | ||
//@ts-ignore | ||
Secp256k1.utils.randomBytes = function (len) { | ||
let buf = new Uint8Array(len); | ||
Crypto.getRandomValues(buf); | ||
return buf; | ||
/** @type {AddressToPubKeyHash} */ | ||
_DashKeys.addrToPkh = async function (address) { | ||
let addrParts = await _DashKeys.decode(address); | ||
let shaRipeBytes = Utils.hexToBytes(addrParts.pubKeyHash); | ||
return shaRipeBytes; | ||
}; | ||
// https://dashcore.readme.io/docs/core-ref-transactions-address-conversion | ||
// https://docs.dash.org/en/stable/developers/testnet.html | ||
// "48" for mainnet, "8c" for testnet, '00' for bitcoin | ||
//DashKeys.pubKeyHashVersion = "4c"; | ||
// TODO type | ||
/** @type {Decode} */ | ||
_DashKeys.decode = async function (keyB58c, opts) { | ||
let parts = await dash58check.decode(keyB58c); | ||
let check = await dash58check.checksum(parts); | ||
let valid = parts.check === check; | ||
if (!valid) { | ||
if (false !== opts.validate) { | ||
// to throw the inner error | ||
await dash58check.verify(keyB58c); | ||
} | ||
} | ||
parts.valid = valid; | ||
// "cc" for mainnet, "ef" for testnet, '80' for bitcoin | ||
//DashKeys.privateKeyVersion = "cc"; | ||
switch (parts.version) { | ||
case DASH_PKH: | ||
/* fallsthrough */ | ||
case DASH_PKH_TESTNET: | ||
parts.type = "pkh"; | ||
break; | ||
case DASH_PRIV_KEY: | ||
/* fallsthrough */ | ||
case DASH_PRIV_KEY_TESTNET: | ||
parts.type = "private"; | ||
break; | ||
case XPRV: | ||
/* fallsthrough */ | ||
case TPRV: | ||
parts.type = "xprv"; | ||
break; | ||
case XPUB: | ||
/* fallsthrough */ | ||
case TPUB: | ||
parts.type = "xpub"; | ||
break; | ||
default: | ||
throw new Error(`unknown version ${parts.version}`); | ||
} | ||
// https://dashcore.readme.io/docs/core-ref-transactions-address-conversion | ||
// https://docs.dash.org/en/stable/developers/testnet.html | ||
let b58c = Base58Check.create(); | ||
return parts; | ||
}; | ||
/** | ||
* @param {Object} [opts] | ||
* @param {String} [opts.version] - '8c' for testnet addrs, 'ef' for testnet wifs, | ||
* @returns {Promise<String>} | ||
*/ | ||
DashKeys.generate = async function (opts) { | ||
let privKey = Secp256k1.utils.randomPrivateKey(); | ||
// TODO parity with decode | ||
/** @type {EncodeKeyUint8Array} */ | ||
_DashKeys.encodeKey = async function (keyBytes, opts) { | ||
if (20 === keyBytes.length) { | ||
return await _DashKeys._encodePkh(keyBytes, opts); | ||
} | ||
let wif = await DashKeys.privateKeyToWif(privKey, opts); | ||
return wif; | ||
if (32 === keyBytes.length) { | ||
return await _DashKeys._encodePrivKey(keyBytes, opts); | ||
} | ||
if (78 === keyBytes.length) { | ||
return await _DashKeys._encodeXKey(keyBytes, opts); | ||
} | ||
throw new Error( | ||
"key bytes length must be 20 (PubKeyHash), 32 (PrivateKey), or 78 (Extended Key)", | ||
); | ||
}; | ||
/** | ||
* @param {Uint8Array} privKey | ||
* @param {Object} [opts] | ||
* @param {String} [opts.version] - '8c' for testnet addrs, 'ef' for testnet wifs, | ||
* @returns {Promise<String>} | ||
*/ | ||
DashKeys.privateKeyToWif = async function (privKey, opts) { | ||
let privKeyHex = DashKeys._uint8ArrayToHex(privKey); | ||
let decoded = { | ||
version: opts?.version, | ||
privateKey: privKeyHex, | ||
}; | ||
/** @type {EncodeKeyUint8Array} */ | ||
_DashKeys._encodePkh = async function (shaRipeBytes, opts) { | ||
let pubKeyHash = Utils.bytesToHex(shaRipeBytes); | ||
let version = opts?.version; | ||
let wif = await b58c.encode(decoded); | ||
switch (version) { | ||
case "mainnet": | ||
version = DASH_PKH; | ||
break; | ||
case "testnet": | ||
version = DASH_PKH_TESTNET; | ||
break; | ||
case DASH_PKH: | ||
// keep as is | ||
break; | ||
case DASH_PKH_TESTNET: | ||
// keep as is | ||
break; | ||
default: | ||
throw new Error( | ||
`Address (PubKey Hash) version must be "mainnet" ("${DASH_PKH}") or "testnet" ("${DASH_PKH_TESTNET}"), not '${version}'`, | ||
); | ||
} | ||
let addr = await dash58check.encode({ | ||
pubKeyHash, | ||
version, | ||
}); | ||
return addr; | ||
}; | ||
/** @type {EncodeKeyUint8Array} */ | ||
_DashKeys._encodePrivKey = async function (privBytes, opts) { | ||
let privateKey = Utils.bytesToHex(privBytes); | ||
let version = opts?.version; | ||
switch (version) { | ||
case "mainnet": | ||
version = DASH_PRIV_KEY; | ||
break; | ||
case "testnet": | ||
version = DASH_PRIV_KEY_TESTNET; | ||
break; | ||
case DASH_PRIV_KEY: | ||
// keep as is | ||
break; | ||
case DASH_PRIV_KEY_TESTNET: | ||
// keep as is | ||
break; | ||
default: | ||
throw new Error( | ||
`WIF (Private Key) version must be "mainnet" ("${DASH_PRIV_KEY}") or "testnet" ("${DASH_PRIV_KEY_TESTNET}"), not '${version}'`, | ||
); | ||
} | ||
let wif = await dash58check.encode({ | ||
privateKey, | ||
version, | ||
}); | ||
return wif; | ||
}; | ||
// | ||
// Base58Check / Uint8Array Conversions | ||
// | ||
/** @type {EncodeKeyUint8Array} */ | ||
_DashKeys._encodeXKey = async function (xkeyBytes, opts) { | ||
let xkey = Utils.bytesToHex(xkeyBytes); | ||
let version = opts?.version; | ||
/** | ||
* @param {String} wif - private key | ||
* @param {Object} [opts] | ||
* @param {String} [opts.version] | ||
* @returns {Promise<String>} | ||
*/ | ||
DashKeys.wifToAddr = async function (wif, opts) { | ||
let privBuf = await DashKeys._wifToPrivateKey(wif); | ||
let xprv; | ||
let xpub; | ||
switch (version) { | ||
case "xprv": | ||
version = XPRV; | ||
xprv = xkey; | ||
break; | ||
case "tprv": | ||
version = TPRV; | ||
xprv = xkey; | ||
break; | ||
case "xpub": | ||
version = XPUB; | ||
xpub = xkey; | ||
break; | ||
case "tpub": | ||
version = TPUB; | ||
xpub = xkey; | ||
break; | ||
default: | ||
throw new Error( | ||
`cannot determine Extended Key (xkey) type from bytes length: please supply 'version' as 'xprv', 'xpub', 'tprv', or 'tpub'`, | ||
); | ||
} | ||
let isCompressed = true; | ||
let pubBuf = Secp256k1.getPublicKey(privBuf, isCompressed); | ||
let pubKeyHash = await DashKeys.publicKeyToPubKeyHash(pubBuf); | ||
let pubKeyHashHex = DashKeys._uint8ArrayToHex(pubKeyHash); | ||
let xkeyB58c = await dash58check.encode({ | ||
version, | ||
xprv, | ||
xpub, | ||
}); | ||
return xkeyB58c; | ||
}; | ||
let addr = await b58c.encode({ | ||
version: opts?.version, | ||
pubKeyHash: pubKeyHashHex, | ||
/** @type {PubKeyHashToAddress} */ | ||
_DashKeys.pkhToAddr = async function (shaRipeBytes, opts) { | ||
let pubKeyHash = Utils.bytesToHex(shaRipeBytes); | ||
let version = opts?.version; | ||
let addr = await dash58check.encode({ | ||
pubKeyHash, | ||
version, | ||
}); | ||
return addr; | ||
}; | ||
/** @type {PrivateKeyToWif} */ | ||
_DashKeys.privKeyToWif = async function (privBytes, opts) { | ||
let privateKey = Utils.bytesToHex(privBytes); | ||
let version = opts?.version; | ||
let wif = await dash58check.encode({ | ||
privateKey, | ||
version, | ||
}); | ||
return wif; | ||
}; | ||
/** @type {PublicKeyToAddress} */ | ||
_DashKeys.pubKeyToAddr = async function (pubBytes, opts) { | ||
let shaRipeBytes = await _DashKeys.pubKeyToPkh(pubBytes); | ||
let addr = await _DashKeys.pkhToAddr(shaRipeBytes, opts); | ||
return addr; | ||
}; | ||
/** | ||
* @param {String} wif - private key | ||
* @returns {Promise<Uint8Array>} | ||
*/ | ||
DashKeys._wifToPrivateKey = async function (wif) { | ||
let b58cWif = b58c.decode(wif); | ||
let privateKey = DashKeys._hexToUint8Array(b58cWif.privateKey); | ||
return privateKey; | ||
/** @type {PublicKeyToPubKeyHash} */ | ||
_DashKeys.pubKeyToPkh = async function (pubBytes) { | ||
let shaBytes = await Utils.sha256sum(pubBytes); | ||
let shaRipeBytes = await Utils.ripemd160sum(shaBytes); | ||
return shaRipeBytes; | ||
}; | ||
/** | ||
* @param {Uint8Array|Buffer} buf | ||
* @returns {Promise<Uint8Array>} - pubKeyHash buffer (no magic byte or checksum) | ||
*/ | ||
DashKeys.publicKeyToPubKeyHash = async function (buf) { | ||
let shaBuf = await sha256sum(buf); | ||
/** @type {WifToAddress} */ | ||
_DashKeys.wifToAddr = async function (wif) { | ||
let privBytes = await _DashKeys.wifToPrivKey(wif); | ||
let ripemd = RIPEMD160.create(); | ||
ripemd.update(shaBuf); | ||
let hash = ripemd.digest(); | ||
let pubBytes = await Utils.toPublicKey(privBytes); | ||
let pubKeyHash = await _DashKeys.pubKeyToPkh(pubBytes); | ||
let pubKeyHashHex = Utils.bytesToHex(pubKeyHash); | ||
return hash; | ||
let addr = await dash58check.encode({ | ||
pubKeyHash: pubKeyHashHex, | ||
}); | ||
return addr; | ||
}; | ||
/** | ||
* JS Buffer to Hex that works for Little-Endian CPUs (ARM, x64, x86, WASM) | ||
* @param {Buffer|Uint8Array} buf | ||
* @returns {String} - hex | ||
* @param {String} wif - Base58Check-encoded Private Key | ||
* @returns {Promise<Uint8Array>} - private key (no magic byte or checksum) | ||
*/ | ||
DashKeys._uint8ArrayToHex = function (buf) { | ||
/** @type {Array<String>} */ | ||
let hex = []; | ||
_DashKeys.wifToPrivKey = async function (wif) { | ||
let wifParts = await dash58check.decode(wif); | ||
let privBytes = Utils.hexToBytes(wifParts.privateKey); | ||
buf.forEach(function (b) { | ||
let h = b.toString(16); | ||
h = h.padStart(2, "0"); | ||
hex.push(h); | ||
}); | ||
return hex.join(""); | ||
return privBytes; | ||
}; | ||
/** | ||
* Hex to JS Buffer that works in browsers and Little-Endian CPUs | ||
* (which is most of the - ARM, x64, x86, WASM, etc) | ||
* @param {String} hex | ||
* @returns {Uint8Array} - JS Buffer (Node and Browsers) | ||
* @callback PrivateKeyToWif | ||
* @param {Uint8Array} privBytes | ||
* @returns {Promise<String>} - wif | ||
*/ | ||
DashKeys._hexToUint8Array = function (hex) { | ||
let len = hex.length / 2; | ||
let buf = new Uint8Array(len); | ||
let index = 0; | ||
for (let i = 0; i < hex.length; i += 2) { | ||
let c = hex.slice(i, i + 2); | ||
let b = parseInt(c, 16); | ||
buf[index] = b; | ||
index += 1; | ||
} | ||
_DashKeys.utils = Utils; | ||
return buf; | ||
}; | ||
//@ts-ignore | ||
Window.BaseX = _DashKeys.BaseX = BaseX; | ||
//@ts-ignore | ||
Window.Base58 = _DashKeys.Base58 = BaseX; | ||
//@ts-ignore | ||
Window.Base58Check = _DashKeys.Base58Check = Base58Check; | ||
//@ts-ignore | ||
Window.RIPEMD160 = _DashKeys.RIPEMD160 = RIPEMD160; | ||
})(globalThis.window || /** @type {window} */ {}, DashKeys); | ||
if ("object" === typeof module) { | ||
module.exports = DashKeys; | ||
} | ||
if ("undefined" !== typeof module) { | ||
module.exports = DashKeys; | ||
} | ||
})(("undefined" !== typeof module && module.exports) || window); | ||
// Type Aliases | ||
/** @typedef {String} HexString */ | ||
// Type Definitions | ||
/** | ||
* @callback AddressToPubKeyHash | ||
* @param {String} addr - Base58Check encoded version + pkh + check | ||
* @returns {Promise<Uint8Array>} - pkh bytes (no version or check, NOT Base58Check) | ||
*/ | ||
/** | ||
* @callback Decode | ||
* @param {String} keyB58c - addr, wif, or xkey (xprv, xpub) | ||
* @param {DecodeOpts} opts | ||
* @returns {Promise<Base58CheckParts>} | ||
*/ | ||
/** | ||
* @typedef DecodeOpts | ||
* @prop {Boolean} validate - throw if check fails, true by default | ||
*/ | ||
/** | ||
* @callback EncodeKeyUint8Array | ||
* @param {Uint8Array} keyBytes - privKey, pkh, or xkey (xprv or xpub) bytes | ||
* @param {EncodeKeyUint8ArrayOpts} opts | ||
* @returns {Promise<String>} - Address or WIF (or Extended Key - xPrv or xPub) | ||
* @throws {Error} | ||
*/ | ||
/** | ||
* @typedef EncodeKeyUint8ArrayOpts | ||
* @prop {VERSION} version - needed for xprv and xpub, or testnet | ||
*/ | ||
/** | ||
* Developer Convenience function for Generating Non-HD (NON-RECOVERABLE) WIFs | ||
* @callback GenerateWif | ||
* @param {GenerateWifOpts} opts | ||
* @returns {Promise<String>} - JS Bytes Buffer (Uint8Array, Node & Browsers) | ||
*/ | ||
/** | ||
* @typedef GenerateWifOpts | ||
* @prop {VERSION_PRIVATE} version - "mainnet" ("cc") by default | ||
*/ | ||
/** | ||
* @callback Hasher | ||
* @param {Uint8Array|Buffer} bytes | ||
* @returns {Promise<Uint8Array>} - hash Uint8Array | ||
*/ | ||
/** | ||
* Hex to JS Bytes Buffer (Uint8Array) | ||
* @callback HexToUint8Array | ||
* @param {String} hex - hex | ||
* @returns {Uint8Array} - JS Bytes Buffer (Uint8Array, Node & Browsers) | ||
*/ | ||
/** | ||
* @callback PrivateKeyToWif | ||
* @param {Uint8Array} privBytes | ||
* @param {PrivateKeyToWifOpts} [opts] | ||
* @returns {Promise<String>} | ||
*/ | ||
/** | ||
* @typedef PrivateKeyToWifOpts | ||
* @prop {VERSION_PRIVATE} version - "mainnet" ("cc") by default | ||
*/ | ||
/** | ||
* @callback PubKeyHashToAddress | ||
* @param {Uint8Array} shaRipeBytes - PubKey Hash (no magic byte or checksum) | ||
* @param {EncodeKeyUint8ArrayOpts} opts - for version | ||
* @returns {Promise<String>} - Address | ||
*/ | ||
/** | ||
* @callback PublicKeyToAddress | ||
* @param {Uint8Array} pubBytes - Public Key Uint8Array | ||
* @param {EncodeKeyUint8ArrayOpts} opts - for coin version | ||
* @returns {Promise<String>} - Address | ||
*/ | ||
/** | ||
* @callback PublicKeyToPubKeyHash | ||
* @param {Uint8Array|Buffer} pubBytes - Public Key Uint8Array | ||
* @returns {Promise<Uint8Array>} - pubKeyHash Uint8Array (no magic byte or checksum) | ||
*/ | ||
/** | ||
* @callback ToPublicKey | ||
* @param {Uint8Array} privBytes - Private Key Uint8Array | ||
* @returns {Promise<Uint8Array>} - Public Key Uint8Array | ||
*/ | ||
/** | ||
* JS Bytes Buffer (Uint8Array) to Hex | ||
* @callback Uint8ArrayToHex | ||
* @param {Uint8Array} bytes | ||
* @returns {String} - hex | ||
*/ | ||
/** | ||
* Converts a WIF-encoded PrivateKey to a PubKey Hash | ||
* (of the same coin type, of course) | ||
* @callback WifToAddress | ||
* @param {String} wif - private key | ||
* @returns {Promise<String>} - address | ||
*/ |
{ | ||
"name": "dashkeys", | ||
"version": "0.3.1", | ||
"version": "0.9.0", | ||
"description": "Generate, validate, create, and convert WIFs and PayAddress.", | ||
"main": "index.js", | ||
"bin": { | ||
"dashkeys": "bin/dashkeys.js" | ||
}, | ||
"main": "dashkeys.js", | ||
"bin": {}, | ||
"files": [ | ||
"dashkeys.js", | ||
"index.js", | ||
"*.js", | ||
"lib/" | ||
], | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1", | ||
"doc": "npx jsdoc@3.x --configure ./jsdoc.conf.json --destination ./docs --package ./package.json --readme ./README.md --access all --private --recurse ./lib/", | ||
"bump": "npm version -m \"chore(release): bump to v%s\"", | ||
"fmt": "npx -p prettier@2.x -- prettier -w '**/*.{html,js,md}'", | ||
"lint": "npx -p typescript@4.x -- tsc -p ./jsconfig.json", | ||
"prepublish": "npx -p jswt@1.x -- reexport", | ||
"version": "npm version -m \"chore(release): bump to v%s\"" | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
@@ -45,7 +40,5 @@ "repository": { | ||
"homepage": "https://github.com/dashhive/dashkeys.js#readme", | ||
"dependencies": { | ||
"@dashincubator/base58check": "^1.2.0", | ||
"@dashincubator/ripemd160": "^2.3.0", | ||
"@dashincubator/secp256k1": "^1.7.1-0" | ||
"devDependencies": { | ||
"@dashincubator/secp256k1": "^1.7.1-2" | ||
} | ||
} |
744
README.md
@@ -1,38 +0,58 @@ | ||
# dashkeys.js | ||
# [DashKeys.js][dashkeys-js] | ||
Generate, validate, create, and convert WIFs and PayAddress. | ||
Generate, validate, and convert DASH WIFs and Pay Addresses. \ | ||
(Base58Check encoding/decoding for Private Keys and Public Key Hashes) | ||
- CLI | ||
- Node & Bundlers | ||
- Browser | ||
- API | ||
- Fixtures (for developing and testing) | ||
```text | ||
WIF: XCGKuZcKDjNhx8DaNKK4xwMMNzspaoToT6CafJAbBfQTi57buhLK | ||
Address: XrZJJfEKRNobcuwWKTD3bDu8ou7XSWPbc9 | ||
``` | ||
## CLI | ||
> A fully-functional, production-ready **reference implementation** of Dash | ||
> Keys - suitable for learning DASH specs and protocols, and porting to other | ||
> languages. | ||
Saves a private key to a file with the name of the public key hash (pay addr) | ||
## Table of Contents | ||
**Install** | ||
- 🚀 [Install](#install) | ||
- Terminal (CLI) | ||
- Node & Bundlers | ||
- Browser | ||
- 💪 [Usage](#usage) | ||
- ⚙️ [API](#api) | ||
- 🧰 Developer Resources | ||
- 👩🏫 [Glossary of Terms](#glossary) | ||
- Address, Check, Compressed, Private Key, | ||
- PubKey Hash, Public Key, Version, WIF, etc ... | ||
- 🦓 [Fixtures](#fixtures) (for testing) | ||
- The Canonical Dash "Zoomonic" | ||
- Anatomy of Addrs & WIFs | ||
- Troubleshooting Uncompressed Keys | ||
- Implementation Details | ||
- 📄 [License](#license) | ||
```sh | ||
npm install --location=global dashkeys | ||
``` | ||
[base58check-js]: https://github.com/dashhive/base58check.js | ||
[dashkeys-js]: https://github.com/dashhive/dashkeys.js | ||
[dashhd-js]: https://github.com/dashhive/dashhd.js | ||
[dashwallet-js]: https://github.com/dashhive/dashwallet.js | ||
[dash-hd-cli]: https://github.com/dashhive/dashhd-cli.js | ||
[dash-keys-cli]: https://github.com/dashhive/dashkeys-cli.js | ||
[dash-wallet-cli]: https://github.com/dashhive/dashwallet-cli.js | ||
**Usage** | ||
## Install | ||
Works in Command Line, Node, Bun, Bundlers, and Browsers | ||
### Terminal | ||
```sh | ||
dashkeys generate | ||
npm install --location=global dashkeys-cli | ||
dashkeys help | ||
``` | ||
```txt | ||
Saved new private key to './Xjn9fksLacciynroVhMLKGXMqMJtzJNLvQ.wif' | ||
``` | ||
See DashKey's CLI README at | ||
[github.com/dashhive/dashkeys-cli.js][dash-keys-cli]. | ||
The name of the file is the Pay Addr (public key hash) and the contents are the | ||
WIF (private key). | ||
## Node & Bundlers | ||
The leading `X` for each is because they are base58check-encoded. | ||
## Node | ||
**Install** | ||
@@ -42,27 +62,11 @@ | ||
npm install --save dashkeys | ||
npm install --save @dashincubator/secp256k1@1 | ||
``` | ||
**Usage** | ||
```js | ||
let DashKeys = require("dashkeys"); | ||
let wif = await DashKeys.generate(); | ||
// ex: "XEez2HcUhEomZoxzgH7H3LxnRAkqF4kRCVE8mW9q4YSUV4yuADec" | ||
let addr = await DashKeys.wifToAddr(wif); | ||
// ex: "Xjn9fksLacciynroVhMLKGXMqMJtzJNLvQ" | ||
let toHex = DashKeys.utils.bytesToHex; | ||
let toBytes = DashKeys.utils.hexToBytes; | ||
``` | ||
You can use `DashKeys.privateKeyToWif(privateKey)` to generate non-random WIFs: | ||
```js | ||
let privateKey = Buffer.from( | ||
"647f06cbd6569feaa4b6a1e400284057a95d27f4206ce38300ae88d44418160d", | ||
"hex", | ||
); | ||
let wif = await DashKeys.privateKeyToWif(privateKey); | ||
let addr = await DashKeys.wifToAddr(wif); | ||
``` | ||
## Browser | ||
@@ -73,10 +77,6 @@ | ||
```html | ||
<script src="https://unpkg.com/@dashincubator/base58check/base58check.js"></script> | ||
<script src="https://unpkg.com/@dashincubator/secp256k1/secp256k1.js"></script> | ||
<script src="https://unpkg.com/@dashincubator/ripemd160/ripemd160.js"></script> | ||
<script src="https://unpkg.com/dashkeys/dashkeys.js"></script> | ||
``` | ||
**Usage** | ||
```js | ||
@@ -87,8 +87,6 @@ async function main() { | ||
let DashKeys = window.DashKeys; | ||
let toHex = DashKeys.utils.bytesToHex; | ||
let toBytes = DashKeys.utils.hexToBytes; | ||
let wif = await DashKeys.generate(); | ||
// ex: "XEez2HcUhEomZoxzgH7H3LxnRAkqF4kRCVE8mW9q4YSUV4yuADec" | ||
let addr = await DashKeys.wifToAddr(wif); | ||
// ex: "Xjn9fksLacciynroVhMLKGXMqMJtzJNLvQ" | ||
// ... | ||
} | ||
@@ -102,44 +100,524 @@ | ||
## API | ||
## Usage | ||
DashKeys has a small API wrapping Secp256k1 and Base58Check. | ||
```js | ||
let wif = await DashKeys.generateWifNonHd(); | ||
// ex: "XCGKuZcKDjNhx8DaNKK4xwMMNzspaoToT6CafJAbBfQTi57buhLK" | ||
Most of what you need to do can be done with those directly. | ||
let addr = await DashKeys.wifToAddr(wif); | ||
// ex: "XrZJJfEKRNobcuwWKTD3bDu8ou7XSWPbc9" | ||
``` | ||
You can use `DashKeys.privateKeyToWif(privateKey)` to encode Private Keys to | ||
WIFs: | ||
```js | ||
let wif = await DashKeys.generate(); | ||
let privBuf = toBytes( | ||
"1d2a6b22fcb5a29a5357eaf27b1444c623e5e580b66ac5f1109e2778a0ffb950", | ||
); | ||
let wif = await DashKeys.privateKeyToWif(privateKey); | ||
let addr = await DashKeys.wifToAddr(wif); | ||
``` | ||
let pkh = await DashKeys.publicKeyToPubKeyHash(publicKey); | ||
let wif = await DashKeys.privateKeyToWif(privateKey); | ||
## API | ||
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). | ||
### Common Conversions | ||
```js | ||
// 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 Uint8Array | ||
``` | ||
## Helpful Helper Functions | ||
**Note**: these all output either Base58Check Strings, or Byte Arrays | ||
(Uint8Array). | ||
These aren't included as part of the DashKeys API because they're things that | ||
[@dashincubator/base58check](https://github.com/dashhive/base58check.js) already | ||
does, 95% or so. | ||
### Common Encode / Decode Options | ||
```js | ||
await addrToPubKeyHash(addr); // buffer | ||
await pubKeyHashToAddr(pubKeyHash); // Pay Addr | ||
{ | ||
// (default) throw if check(sum) fails | ||
validate: true, | ||
await wifToPrivateKey(wif); // buffer | ||
await privateKeyToWif(privKey); // WIF | ||
// 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", | ||
} | ||
``` | ||
await decode(addrOrWif); // { version, pubKeyHash, privateKey, check, valid } | ||
await encode(pkhOrPkBuf); // Pay Addr or WIF | ||
### Debugging Encoder / Decoder | ||
uint8ArrayToHex(buf); // hex | ||
hexToUint8Array(hex); // buffer | ||
```js | ||
// 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 key | ||
``` | ||
However, you are welcome, of course, to copy and paste these to your heart's | ||
content. 😉 | ||
**Decode Output**: | ||
```json5 | ||
{ | ||
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. | ||
### Helpful Helper Utils | ||
```js | ||
// 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 Uint8Array | ||
``` | ||
### Swappable Secp256k1 Utils | ||
We 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. | ||
```js | ||
// Key Utils (over | ||
await DashKeys.utils.generateWifNonHd({ version }); // WIF string (non-hd, dev tool) | ||
await DashKeys.utils.toPublicKey(privBytes); // Public Key Bytes | ||
``` | ||
**Example Overwrite** | ||
You **DO NOT** need to do this, but you _may_ if you wish: | ||
```js | ||
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; | ||
}; | ||
``` | ||
## Glossary | ||
Here are bunches of terms by their canonical name, as well as a terse | ||
description. | ||
- [Address](#address) | ||
- [Check](#check) | ||
- [Base X](#base-x) | ||
- [Base58](#base58) | ||
- [Base58Check](#base58check) | ||
- [Compressed Byte](#compressed-byte) | ||
- [HD Key](#hd-key) | ||
- [Private Key](#private-key) | ||
- [PubKey Hash](#pubkey-hash) | ||
- [Public Key](#public-key) | ||
- [RIPEMD160](#ripemd160) | ||
- [Version](#version) | ||
- [WIF](#wif) | ||
- [Zoomonic](#zoomonic) | ||
### Address | ||
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)) | ||
```text | ||
Version: cc | ||
PubKey Hash: ae14c8728915b492d9d77813bd8fddd91ce70948 | ||
Check: ce08541e | ||
Decoded: ccae14c8728915b492d9d77813bd8fddd91ce70948ce08541e | ||
Encoded: XrZJJfEKRNobcuwWKTD3bDu8ou7XSWPbc9 | ||
``` | ||
### Base X | ||
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. | ||
### Base58 | ||
A specific 58-character _Base X_ alphabet. \ | ||
The same is used for DASH as most cryptocurrencies. | ||
```text | ||
123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz | ||
``` | ||
Chosen to eliminate confusion between similar characters. \ | ||
(`0` and `O`, `1` and `l` and `I`, etc) | ||
### Base58Check | ||
A Base58 encoding schema with prefixes and suffixes. \ | ||
`verisonBytes` is added as a prefix (before encoding). \ | ||
Some _metadata_ may come before the data. \ | ||
`checkBytes` are added as a suffix (before encoding). \ | ||
```js | ||
Base58(`${versionBytes}${metaBytes}${dataBytes}${checkBytes}`); | ||
``` | ||
See also [Address](#address), [Check](#check) and [WIF](#wif) | ||
### Check | ||
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. | ||
See [Address](#address) and [WIF](#wif). | ||
### Compressed Byte | ||
Also: Compression Flag, Recovery Bit, Y is Even / Odd Byte, Quadrant | ||
A private key always starts with `0x01`, the compression flag. \ | ||
This indicates that Pub Key Hashes must not include the Y value. | ||
See also: [Public Key](#public-key). | ||
### HD 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][dashhd-js], [Dash Wallet][dashwallet-js]. | ||
### Private Key | ||
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: | ||
```js | ||
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>. | ||
### PubKey Hash | ||
Also: Public Key Hash, PKH, PubKeyHash | ||
The public key is hashed with SHA-256. \ | ||
That result is hashed with RIPEMD-160. | ||
```text | ||
RIPEMD160(SHA256(PublicKey)) | ||
``` | ||
### Public Key | ||
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: | ||
```js | ||
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>. | ||
### RIPEMD160 | ||
An old, deprecated hash 20-byte algorithm - similar to MD5. \ | ||
We're stuck with it for the foreseeable future. Oh, well. | ||
### Version | ||
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][dashhd-js] for more info about Extended Private Keys (`xprv`, | ||
`xpriv`) and Extended Public Keys (`xpub`). | ||
### WIF | ||
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, compression, pubkey)) | ||
- Base58Check is Base85(concat(coin, compression, pubkey, checksum)) | ||
```text | ||
Version: cc | ||
Comp Byte: 01 (always) | ||
Private Key: 1d2a6b22fcb5a29a5357eaf27b1444c623e5e580b66ac5f1109e2778a0ffb950 | ||
Checksum: ec533f80 | ||
Decoded: cc011d2a6b22fcb5a29a5357eaf27b1444c623e5e580b66ac5f1109e2778a0ffb950ec533f80 | ||
Encoded: XCGKuZcKDjNhx8DaNKK4xwMMNzspaoToT6CafJAbBfQTi57buhLK | ||
``` | ||
### Zoomonic | ||
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"](#the-canonical-dash-zoomonic). | ||
## Fixtures | ||
For troubleshooting, debugging, etc. | ||
### The Canonical Dash "Zoomonic": | ||
All keys used in this example - and across this ecosystem of DASH tools - are HD | ||
keys derived from the "Zoomonic": | ||
```txt | ||
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 : XrZJJfEKRNobcuwWKTD3bDu8ou7XSWPbc9 | ||
``` | ||
### Anatomy of Addrs & WIFs | ||
```sh | ||
dashkeys inspect --unsafe ./examples/m44_5_0_0-0.wif | ||
``` | ||
```txt | ||
Version: cc | ||
Private Key: 1d2a6b22fcb5a29a5357eaf27b1444c623e5e580b66ac5f1109e2778a0ffb950 | ||
Compressed: 01 | ||
Pay Addr: XrZJJfEKRNobcuwWKTD3bDu8ou7XSWPbc9 | ||
Check: ec533f80 | ||
Valid: true | ||
``` | ||
**Correct Private Key** | ||
```txt | ||
PrivateKey: cc011d2a6b22fcb5a29a5357eaf27b1444c623e5e580b66ac5f1109e2778a0ffb950 | ||
-------- | ||
Version: cc | ||
Comp Flag: 01 (Compressed) | ||
Priv Key: 1d2a6b22fcb5a29a5357eaf27b1444c623e5e580b66ac5f1109e2778a0ffb950 | ||
-------- | ||
WIF: XCGKuZcKDjNhx8DaNKK4xwMMNzspaoToT6CafJAbBfQTi57buhLK | ||
``` | ||
**Correct Pub Key Hash** | ||
```txt | ||
PubKey: 0245ddd5edaa25313bb88ee0abd359f6f58ac38ed597d33a981f1799903633d902 | ||
-------- | ||
Comp Flag: 02 (Quadrant 2) | ||
X: 45ddd5edaa25313bb88ee0abd359f6f58ac38ed597d33a981f1799903633d902 | ||
SHA256: 8e5abfc42a6d7529b860ce2b4b8889380db893438dc96430f597ddb455e85fdd | ||
*RMD160: 54408a877b83cb9706373918a430728f72f3d001 (*not used) | ||
PubKeyHash: ae14c8728915b492d9d77813bd8fddd91ce70948 | ||
Check: ce08541e | ||
Version: 4c | ||
-------- | ||
Pay Address: XrZJJfEKRNobcuwWKTD3bDu8ou7XSWPbc9 | ||
``` | ||
### Troubleshooting Uncompressed Keys | ||
If you see these values, then you've mistakenly used uncompressed keys. | ||
**Incorrect Private Key** (Uncompressed) | ||
```txt | ||
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) | ||
```txt | ||
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: XqBBkSnvWMcLyyRRvH1S4mWH4f2zugr7Cd | ||
``` | ||
## Implementation Details | ||
It 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) | ||
```js | ||
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 | ||
privateKeyVersion: "cc", // "ef" for dash testnet, "80" for bitcoin main | ||
}); | ||
@@ -153,3 +631,3 @@ | ||
let b58cAddr = dash58check.decode(addr); | ||
let pubKeyHash = hexToUint8Array(b58cAddr.pubKeyHash); | ||
let pubKeyHash = toBytes(b58cAddr.pubKeyHash); | ||
return pubKeyHash; | ||
@@ -163,3 +641,3 @@ } | ||
async function pubKeyHashToAddr(pubKeyHash) { | ||
let hex = uint8ArrayToHex(pubKeyHash); | ||
let hex = toHex(pubKeyHash); | ||
let addr = await dash58check.encode({ pubkeyHash: hex }); | ||
@@ -175,3 +653,3 @@ return addr; | ||
let b58cWif = dash58check.decode(wif); | ||
let privateKey = hexToUint8Array(b58cWif.privateKey); | ||
let privateKey = toBytes(b58cWif.privateKey); | ||
return privateKey; | ||
@@ -185,3 +663,3 @@ } | ||
async function privateKeyToWif(privKey) { | ||
let privateKey = uint8ArrayToHex(privKey); | ||
let privateKey = toHex(privKey); | ||
@@ -201,4 +679,4 @@ let wif = await dash58check.encode({ privateKey: privateKey }); | ||
parts.valid = valid; | ||
//parts.privateKeyBuffer = hexToUint8Array(parts.privateKey); | ||
//parts.pubKeyHashBuffer = hexToUint8Array(parts.pubKeyHash); | ||
//parts.privBytes = toBytes(parts.privateKey); | ||
//parts.shaRipeBytes = toBytes(parts.pubKeyHash); | ||
@@ -214,3 +692,3 @@ return parts; | ||
async function encode(buf) { | ||
let hex = uint8ArrayToHex(buf); | ||
let hex = toHex(buf); | ||
@@ -231,105 +709,29 @@ if (32 === buf.length) { | ||
} | ||
/** | ||
* JS Buffer to Hex that works in browsers and Little-Endian | ||
* (which is most of the - ARM, x64, x86, WASM, etc) | ||
* @param {Uint8Array} buf | ||
* @returns {String} - hex | ||
*/ | ||
function uint8ArrayToHex(buf) { | ||
/** @type {Array<String>} */ | ||
let hex = []; | ||
buf.forEach(function (b) { | ||
let c = b.toString(16).padStart(2, "0"); | ||
hex.push(c); | ||
}); | ||
return hex.join(""); | ||
} | ||
/** | ||
* Hex to JS Buffer that works in browsers and Little-Endian CPUs | ||
* (which is most of the - ARM, x64, x86, WASM, etc) | ||
* @param {String} hex | ||
* @returns {Uint8Array} - JS Buffer (Node and Browsers) | ||
*/ | ||
function hexToUint8Array(hex) { | ||
let buf = new Uint8Array(hex.length / 2); | ||
for (let i = 0; i < hex.length; i += 2) { | ||
let c = hex.slice(i, i + 2); | ||
let b = parseInt(c, 16); | ||
let index = i / 2; | ||
buf[index] = b; | ||
} | ||
return buf; | ||
} | ||
``` | ||
## Fixtures | ||
# LICENSE | ||
For troubleshooting, debugging, etc: | ||
To keep the dependency tree slim, this includes `BaseX` and `Base58Check`, which | ||
are derivatives of `base58.cpp`, as well as RIPEMD160. | ||
**Correct Values** | ||
These have all been _complete_ for several years. They do not need updates. | ||
- WIF: XEez2HcUhEomZoxzgH7H3LxnRAkqF4kRCVE8mW9q4YSUV4yuADec | ||
- Pay Addr: Xjn9fksLacciynroVhMLKGXMqMJtzJNLvQ | ||
## DashKeys.js | ||
```txt | ||
WIF: XEez2HcUhEomZoxzgH7H3LxnRAkqF4kRCVE8mW9q4YSUV4yuADec (Base58Check) | ||
Copyright (c) 2022-2023 Dash Incubator \ | ||
Copyright (c) 2021-2023 AJ ONeal | ||
Private Parts: cc647f06cbd6569feaa4b6a1e400284057a95d27f4206ce38300ae88d44418160d012dc0e59d | ||
-------- | ||
Version: cc | ||
PrivKey: 647f06cbd6569feaa4b6a1e400284057a95d27f4206ce38300ae88d44418160d | ||
Compressed: 01 | ||
Checksum: 2dc0e59d | ||
MIT License | ||
## BaseX, Base58, Base58Check | ||
Compressed Pub: 0290940e7a082049ccfbf7999260ab9a6d88bcb34d57f6c6075e52dd7395ed7058 | ||
-------- | ||
Quadrant: 02 | ||
PubKey: 90940e7a082049ccfbf7999260ab9a6d88bcb34d57f6c6075e52dd7395ed7058 | ||
Sha256: 836eaaeef70089f38cbf878f6987a322260ad661f3c0fcaf9715834b5a5224c7 | ||
RipeMD: 63ba19d01e6cf812c01ca6a4041c3c04f2a4dfe9 (Pub Key Hash) | ||
Copyright (c) 2018 base-x contributors \ | ||
Copyright (c) 2014-2018 The Bitcoin Core developers | ||
MIT License | ||
Pub Key Hash: 4c63ba19d01e6cf812c01ca6a4041c3c04f2a4dfe99ec9cefd (Hex) | ||
-------- | ||
Version: 4c | ||
Hash: 63ba19d01e6cf812c01ca6a4041c3c04f2a4dfe9 | ||
Checksum: 9ec9cefd | ||
## RIPEMD160 | ||
Copyright (c) 2016 crypto-browserify | ||
Pay Addr: Xjn9fksLacciynroVhMLKGXMqMJtzJNLvQ (Base58Check) | ||
``` | ||
**Incorrect Values** | ||
These are the values you get for `{ compressed: false }`: | ||
```txt | ||
Private Parts: cc647f06cbd6569feaa4b6a1e400284057a95d27f4206ce38300ae88d44418160d012dc0e59d | ||
-------- | ||
Version: cc | ||
PrivKey: 647f06cbd6569feaa4b6a1e400284057a95d27f4206ce38300ae88d44418160d | ||
Compressed: (MISSING!) | ||
Checksum: (WRONG!) | ||
Uncompressed: 0490940e7a082049ccfbf7999260ab9a6d88bcb34d57f6c6075e52dd7395ed70581480b848f5a1e2fd61dd87a2815d90d2d118a46d666a7a559adab68b00e0dc1e | ||
-------- | ||
Quadrant: 04 (WRONG!) | ||
X: 90940e7a082049ccfbf7999260ab9a6d88bcb34d57f6c6075e52dd7395ed7058 | ||
Y: 1480b848f5a1e2fd61dd87a2815d90d2d118a46d666a7a559adab68b00e0dc1e | ||
Sha256: e1d11902550f530b33e4321f4f9044a67c3b9c38b6ed98accfaf0571067871d2 | ||
RipeMD: 30ad71f52c005b5444f94032dda84466ddaf87a0 (WRONG Pub Key Hash) | ||
Pay Addr: Xf8E3eA1Sh8vC29fxQVbET8cqfCRcmiQeA (WRONG) | ||
4c30ad71f52c005b5444f94032dda84466ddaf87a0dae0ce2f (WRONG) | ||
-------- | ||
Version: 4c | ||
Hash: 30ad71f52c005b5444f94032dda84466ddaf87a0 (WRONG) | ||
Checksum: dae0ce2f (WRONG) | ||
``` | ||
MIT License |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
67341
0
8
1426
727
0
1
1
- Removed@dashincubator/base58check@^1.2.0
- Removed@dashincubator/ripemd160@^2.3.0
- Removed@dashincubator/secp256k1@^1.7.1-0
- Removed@dashincubator/base58check@1.4.1(transitive)
- Removed@dashincubator/ripemd160@2.3.0(transitive)
- Removed@dashincubator/secp256k1@1.7.1-5(transitive)