UNPKG

@hdwallet/core

Version:

A complete Hierarchical Deterministic (HD) Wallet generator for 200+ cryptocurrencies, built with TypeScript.

630 lines 21.4 kB
"use strict"; // SPDX-License-Identifier: MIT Object.defineProperty(exports, "__esModule", { value: true }); exports.getBytes = getBytes; exports.toBuffer = toBuffer; exports.hexToBytes = hexToBytes; exports.bytesToHex = bytesToHex; exports.bytesToString = bytesToString; exports.randomBytes = randomBytes; exports.bytesToInteger = bytesToInteger; exports.ensureString = ensureString; exports.stringToInteger = stringToInteger; exports.equalBytes = equalBytes; exports.integerToBytes = integerToBytes; exports.concatBytes = concatBytes; exports.bytesToBinaryString = bytesToBinaryString; exports.binaryStringToInteger = binaryStringToInteger; exports.integerToBinaryString = integerToBinaryString; exports.binaryStringToBytes = binaryStringToBytes; exports.isAllEqual = isAllEqual; exports.generatePassphrase = generatePassphrase; exports.getHmac = getHmac; exports.excludeKeys = excludeKeys; exports.pathToIndexes = pathToIndexes; exports.indexesToPath = indexesToPath; exports.normalizeIndex = normalizeIndex; exports.normalizeDerivation = normalizeDerivation; exports.indexTupleToInteger = indexTupleToInteger; exports.indexTupleToString = indexTupleToString; exports.indexStringToTuple = indexStringToTuple; exports.xor = xor; exports.addNoCarry = addNoCarry; exports.multiplyScalarNoCarry = multiplyScalarNoCarry; exports.isBitsSet = isBitsSet; exports.areBitsSet = areBitsSet; exports.setBit = setBit; exports.setBits = setBits; exports.resetBit = resetBit; exports.resetBits = resetBits; exports.bytesReverse = bytesReverse; exports.convertBits = convertBits; exports.bytesChunkToWords = bytesChunkToWords; exports.wordsToBytesChunk = wordsToBytesChunk; exports.toCamelCase = toCamelCase; exports.ensureTypeMatch = ensureTypeMatch; const utils_1 = require("@noble/hashes/utils"); const exceptions_1 = require("./exceptions"); function getBytes(data, encoding = 'hex') { if (data == null) { return new Uint8Array(); } // Already a Uint8Array? if (data instanceof Uint8Array) { return data; } // Array of numbers? if (Array.isArray(data)) { return new Uint8Array(data); } // From here on: data is a string const str = data; switch (encoding) { case 'hex': { // Strip optional 0x/0X let s = str.startsWith('0x') || str.startsWith('0X') ? str.slice(2) : str; // Pad odd length if (s.length % 2 === 1) s = '0' + s; // Split into byte-pairs and parse return Uint8Array.from(s.match(/.{1,2}/g).map(b => parseInt(b, 16))); } case 'utf8': return new TextEncoder().encode(str); case 'base64': // atob → binary-string → map char codes return Uint8Array.from(atob(str), c => c.charCodeAt(0)); default: throw new Error(`Unsupported encoding: ${encoding}`); } } function toBuffer(input, encoding = 'utf8') { if (typeof input === 'string') { switch (encoding) { case 'utf8': // UTF-8 encode a string return new TextEncoder().encode(input); case 'base64': // atob gives a binary‐string; map char→byte return Uint8Array.from(atob(input), c => c.charCodeAt(0)); case 'hex': // split every two hex digits → parse → byte return Uint8Array.from(input.match(/.{1,2}/g).map(byte => parseInt(byte, 16))); default: throw new Error(`Unsupported encoding: ${encoding}`); } } // If it's already an ArrayBuffer or TypedArray if (input instanceof ArrayBuffer) { return new Uint8Array(input); } if (ArrayBuffer.isView(input)) { return new Uint8Array(input.buffer, input.byteOffset, input.byteLength); } // Fallback: try Array-like (e.g. number[]) return Uint8Array.from(input); } function hexToBytes(hex) { const normalized = hex.startsWith('0x') ? hex.slice(2) : hex; if (normalized.length % 2 !== 0) { throw new Error(`Invalid hex string length: ${normalized.length}`); } const bytes = new Uint8Array(normalized.length / 2); for (let i = 0; i < bytes.length; i++) { bytes[i] = parseInt(normalized.substr(i * 2, 2), 16); } return bytes; } function bytesToHex(bytes, prefix = false) { const hex = Array.from(bytes) .map(b => b.toString(16).padStart(2, '0')) .join(''); return prefix ? `0x${hex}` : hex; } function bytesToString(data) { if (data == null || (typeof data === 'string' && data.length === 0) || (data instanceof Uint8Array && data.length === 0)) { return ''; } if (typeof data === 'string') { // If it’s a valid even-length hex string (0–9, A–F, a–f), return it lowercased: if (data.length % 2 === 0 && /^[0-9A-Fa-f]+$/.test(data)) { return data.toLowerCase(); } // Otherwise treat `data` as UTF-8 text: encode to Uint8Array then to hex const encoder = new TextEncoder(); const bytes = encoder.encode(data); return bytesToHex(bytes); } // Uint8Array case: just convert those bytes to hex return bytesToHex(data); } function randomBytes(len) { if (!Number.isInteger(len) || len <= 0) { throw new Error('randomBytes: length must be a positive integer'); } return (0, utils_1.randomBytes)(len); } function bytesToInteger(bytes, littleEndian = false) { // if little-endian, reverse into a new array const data = littleEndian ? bytes.slice().reverse() : bytes; return data.reduce((acc, b) => (acc << BigInt(8)) + BigInt(b), BigInt(0)); } function ensureString(data) { if (data instanceof Uint8Array) { return new TextDecoder().decode(data); } if (typeof data === 'string') { return data; } throw new exceptions_1.TypeError('Invalid value for string'); } function stringToInteger(data) { let buf; if (typeof data === 'string') { // treat string as hex (even-length hex string) buf = hexToBytes(data); } else { buf = data; } let val = BigInt(0); for (let i = 0; i < buf.length; i++) { val = (val << BigInt(8)) + BigInt(buf[i]); } return val; } function equalBytes(a, b) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) return false; } return true; } function integerToBytes(value, length, endianness = 'big') { // coerce to BigInt without using 0n let val = typeof value === 'number' ? BigInt(value) : value; if (val < BigInt(0)) { throw new Error(`Cannot convert negative integers: ${val}`); } // build big-endian array const bytes = []; const ZERO = BigInt(0); const SHIFT = BigInt(8); const MASK = BigInt(0xff); while (val > ZERO) { // val & 0xffn → val & MASK bytes.unshift(Number(val & MASK)); // val >>= 8n → val = val >> SHIFT val = val >> SHIFT; } if (bytes.length === 0) { bytes.push(0); } // pad/truncate if (length !== undefined) { if (bytes.length > length) { throw new Error(`Integer too large to fit in ${length} bytes`); } while (bytes.length < length) { bytes.unshift(0); } } const result = new Uint8Array(bytes); return endianness === 'little' ? result.reverse() : result; } function concatBytes(...chunks) { const totalLength = chunks.reduce((sum, arr) => sum + arr.length, 0); const result = new Uint8Array(totalLength); let offset = 0; for (const chunk of chunks) { result.set(chunk, offset); offset += chunk.length; } return result; } function bytesToBinaryString(data, zeroPadBits = 0) { const bits = Array.from(data) .map((b) => b.toString(2).padStart(8, '0')) .join(''); return bits.length < zeroPadBits ? bits.padStart(zeroPadBits, '0') : bits; } function binaryStringToInteger(data) { const bin = typeof data === 'string' ? data : bytesToBinaryString(data); const clean = bin.trim(); return BigInt('0b' + clean); } function integerToBinaryString(data, zeroPadBits = 0) { const big = typeof data === 'bigint' ? data : BigInt(data); const bits = big.toString(2); return bits.length < zeroPadBits ? bits.padStart(zeroPadBits, '0') : bits; } function binaryStringToBytes(data, zeroPadByteLen = 0) { const bits = typeof data === 'string' ? data.trim() : bytesToBinaryString(data); const bitLen = bits.length; const val = BigInt('0b' + bits); let hex = val.toString(16); if (hex.length % 2 === 1) { hex = '0' + hex; } const byteLen = zeroPadByteLen > 0 ? zeroPadByteLen : Math.ceil(bitLen / 8); const expectedHexLen = byteLen * 2; if (hex.length < expectedHexLen) { hex = hex.padStart(expectedHexLen, '0'); } return hexToBytes(hex); } function isAllEqual(...inputs) { if (inputs.length < 2) return true; const getTag = (v) => { if (typeof v === 'string') return 'string'; if (typeof v === 'number') return 'number'; if (typeof v === 'boolean') return 'boolean'; if (Array.isArray(v)) { if (v.every(i => typeof i === 'number')) return 'array:number'; if (v.every(i => typeof i === 'string')) return 'array:string'; if (v.every(i => typeof i === 'boolean')) return 'array:boolean'; return 'array:unknown'; } if (v instanceof Uint8Array) return 'uint8array'; if (v instanceof ArrayBuffer) return 'arraybuffer'; if (ArrayBuffer.isView(v)) return 'view'; return 'unknown'; }; const firstTag = getTag(inputs[0]); if (firstTag === 'unknown' || firstTag === 'array:unknown') return false; for (const v of inputs.slice(1)) { if (getTag(v) !== firstTag) return false; } if (firstTag === 'string' || firstTag === 'number' || firstTag === 'boolean') { const first = inputs[0]; return inputs.every(v => v === first); } if (firstTag.startsWith('array:')) { const firstArr = inputs[0]; const len = firstArr.length; return inputs.slice(1).every(item => { const arr = item; if (arr.length !== len) return false; for (let i = 0; i < len; i++) { if (arr[i] !== firstArr[i]) return false; } return true; }); } const normalize = (v) => { if (v instanceof Uint8Array) return v; if (v instanceof ArrayBuffer) return new Uint8Array(v); return new Uint8Array(v.buffer, v.byteOffset, v.byteLength); }; const firstArr = normalize(inputs[0]); const len = firstArr.byteLength; return inputs.slice(1).every(item => { const arr = normalize(item); if (arr.byteLength !== len) return false; for (let i = 0; i < len; i++) { if (arr[i] !== firstArr[i]) return false; } return true; }); } function generatePassphrase(length = 32, chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') { const bytes = randomBytes(length); let result = ''; for (let i = 0; i < length; i++) { result += chars[bytes[i] % chars.length]; } return result; } function getHmac(eccName) { const encoder = new TextEncoder(); if ([ 'Kholaw-Ed25519', 'SLIP10-Ed25519', 'SLIP10-Ed25519-Blake2b', 'SLIP10-Ed25519-Monero', ].includes(eccName)) { return encoder.encode('ed25519 seed'); } else if (eccName === 'SLIP10-Nist256p1') { return encoder.encode('Nist256p1 seed'); } else if (eccName === 'SLIP10-Secp256k1') { return encoder.encode('Bitcoin seed'); } throw new exceptions_1.DerivationError('Unknown ECC name'); } function excludeKeys(nested, keys) { const out = {}; const keySet = new Set(keys); // optional optimization for (const [k, v] of Object.entries(nested)) { const normKey = k.replace(/-/g, '_'); if (keySet.has(normKey)) continue; if (v && typeof v === 'object' && !Array.isArray(v) && !(v instanceof Uint8Array) && !(v instanceof Uint8Array)) { out[k] = excludeKeys(v, keys); } else { out[k] = v; } } return out; } function pathToIndexes(path) { if (path === 'm' || path === 'm/') return []; if (!path.startsWith('m/')) { throw new exceptions_1.DerivationError(`Bad path format, expected 'm/0'/0', got '${path}'`); } return path .slice(2) .split('/') .map(i => i.endsWith("'") ? parseInt(i.slice(0, -1), 10) + 0x80000000 : parseInt(i, 10)); } function indexesToPath(indexes) { return ('m' + indexes .map(i => i & 0x80000000 ? `/${(i & ~0x80000000).toString()}'` : `/${i.toString()}`) .join('')); } function normalizeIndex(index, hardened = false) { if (typeof index === 'number') { if (index < 0) throw new exceptions_1.DerivationError(`Bad index: ${index}`); return [index, hardened]; } if (typeof index === 'string') { const m = index.match(/^(\d+)(?:-(\d+))?$/); if (!m) { throw new exceptions_1.DerivationError(`Bad index format, got '${index}'`); } const from = parseInt(m[1], 10); const to = m[2] ? parseInt(m[2], 10) : undefined; if (to === undefined) return [from, hardened]; if (from > to) { throw new exceptions_1.DerivationError(`Range start ${from} > end ${to}`); } return [from, to, hardened]; } if (Array.isArray(index)) { const [a, b] = index; if (index.length !== 2 || typeof a !== 'number' || typeof b !== 'number') { throw new exceptions_1.DerivationError(`Bad index tuple: ${JSON.stringify(index)}`); } if (a < 0 || b < 0) { throw new exceptions_1.DerivationError(`Negative in tuple: ${index}`); } if (a > b) { throw new exceptions_1.DerivationError(`Range start ${a} > end ${b}`); } return [a, b, hardened]; } throw new exceptions_1.DerivationError(`Invalid index instance, got ${typeof index}`); } function normalizeDerivation(path, indexes) { let _path = 'm'; const _indexes = []; const _deriv = []; if (indexes && path) { throw new exceptions_1.DerivationError('Provide either path or indexes, not both'); } if (indexes) { path = indexesToPath(indexes); } if (!path || path === 'm' || path === 'm/') { return [`${_path}/`, _indexes, _deriv]; } if (!path.startsWith('m/')) { throw new exceptions_1.DerivationError(`Bad path format, got '${path}'`); } for (const seg of path.slice(2).split('/')) { const hardened = seg.endsWith("'"); const core = hardened ? seg.slice(0, -1) : seg; const parts = core.split('-').map(x => parseInt(x, 10)); if (parts.length === 2) { const [from, to] = parts; if (from > to) { throw new exceptions_1.DerivationError(`Range start ${from} > end ${to}`); } _deriv.push([from, to, hardened]); _indexes.push(to + (hardened ? 0x80000000 : 0)); _path += hardened ? `/${to}'` : `/${to}`; } else { const idx = parts[0]; _deriv.push([idx, hardened]); _indexes.push(idx + (hardened ? 0x80000000 : 0)); _path += hardened ? `/${idx}'` : `/${idx}`; } } return [_path, _indexes, _deriv]; } function indexTupleToInteger(idx) { if (idx.length === 2) { const [i, h] = idx; return i + (h ? 0x80000000 : 0); } else { const [from, to, h] = idx; return to + (h ? 0x80000000 : 0); } } function indexTupleToString(idx) { if (idx.length === 2) { const [i, h] = idx; return `${i}${h ? "'" : ''}`; } else { const [from, to, h] = idx; return `${from}-${to}${h ? "'" : ''}`; } } function indexStringToTuple(i) { const hardened = i.endsWith("'"); const num = parseInt(hardened ? i.slice(0, -1) : i, 10); return [num, hardened]; } function xor(a, b) { if (a.length !== b.length) throw new exceptions_1.DerivationError('Uint8Arrays must match length for XOR'); return getBytes(a.map((x, i) => x ^ b[i])); } function addNoCarry(a, b) { if (a.length !== b.length) throw new exceptions_1.DerivationError('Uint8Arrays must match length for addNoCarry'); return getBytes(a.map((x, i) => (x + b[i]) & 0xff)); } function multiplyScalarNoCarry(data, scalar) { return getBytes(data.map(x => (x * scalar) & 0xff)); } function isBitsSet(value, bitNum) { return (value & (1 << bitNum)) !== 0; } function areBitsSet(value, mask) { return (value & mask) !== 0; } function setBit(value, bitNum) { return value | (1 << bitNum); } function setBits(value, mask) { return value | mask; } function resetBit(value, bitNum) { return value & ~(1 << bitNum); } function resetBits(value, mask) { return value & ~mask; } function bytesReverse(data) { return getBytes(data).reverse(); } function convertBits(data, fromBits, toBits) { const input = Array.isArray(data) ? data : Array.from(data); const maxVal = (1 << toBits) - 1; let acc = 0; let bits = 0; const out = []; for (const val of input) { if (val < 0 || val >> fromBits) { return null; } acc |= val << bits; bits += fromBits; while (bits >= toBits) { out.push(acc & maxVal); acc >>= toBits; bits -= toBits; } } if (bits > 0) { out.push(acc & maxVal); } return out; } function bytesChunkToWords(bytesChunk, wordsList, endianness) { const len = BigInt(wordsList.length); let chunkNum = bytesToInteger(new Uint8Array(bytesChunk), endianness !== 'big'); const i1 = Number(chunkNum % len); const i2 = Number(((chunkNum / len) + BigInt(i1)) % len); const i3 = Number(((chunkNum / len / len) + BigInt(i2)) % len); return [wordsList[i1], wordsList[i2], wordsList[i3]]; } function wordsToBytesChunk(w1, w2, w3, wordsList, endianness) { const len = BigInt(wordsList.length); const idxMap = new Map(wordsList.map((w, i) => [w, BigInt(i)])); const i1 = idxMap.get(w1); const i2 = idxMap.get(w2); const i3 = idxMap.get(w3); const chunk = i1 + len * ((i2 - i1 + len) % len) + len * len * ((i3 - i2 + len) % len); const u8 = integerToBytes(chunk, 4, endianness); return getBytes(u8); } function toCamelCase(input) { return input.toLowerCase().replace(/-([a-z])/g, (_, char) => char.toUpperCase()); } function ensureTypeMatch(instanceOrClass, expectedType, options = {}) { const tryMatch = (type) => { if (type === 'any') return true; if (type === 'null') return instanceOrClass === null; if (type === 'array') return Array.isArray(instanceOrClass); if (typeof type === 'string') return typeof instanceOrClass === type; if (typeof type === 'function') { if (typeof instanceOrClass === 'function') { let proto = instanceOrClass; while (proto && proto !== Function.prototype) { if (proto === type) return true; proto = Object.getPrototypeOf(proto); } return false; } return options.strict ? instanceOrClass?.constructor === type : instanceOrClass instanceof type; } return false; }; const allExpectedTypes = [expectedType, ...(options.otherTypes || [])]; const matched = allExpectedTypes.find(tryMatch); if (!matched && (options.errorClass || options.otherTypes)) { const expectedNames = allExpectedTypes.map((type) => typeof type === 'function' ? type.name : String(type)); const gotName = typeof instanceOrClass === 'function' ? instanceOrClass.name : instanceOrClass?.constructor?.name ?? typeof instanceOrClass; if (options.errorClass) { throw new options.errorClass(`Invalid type`, { expected: expectedNames, got: gotName }); } else { throw new exceptions_1.TypeError(`Invalid type`, { expected: expectedNames, got: gotName }); } } return matched && options.errorClass ? instanceOrClass : { value: instanceOrClass, isValid: tryMatch(expectedType) }; } //# sourceMappingURL=utils.js.map