UNPKG

ecash-lib

Version:

Library for eCash transaction building

242 lines 9.13 kB
"use strict"; // Copyright (c) 2025 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. Object.defineProperty(exports, "__esModule", { value: true }); exports.HdNode = void 0; const ecc_js_1 = require("./ecc.js"); const hmac_js_1 = require("./hmac.js"); const hash_js_1 = require("./hash.js"); const bytes_js_1 = require("./io/bytes.js"); const str_js_1 = require("./io/str.js"); const writerbytes_js_1 = require("./io/writerbytes.js"); const b58_ts_1 = require("b58-ts"); const legacyaddr_js_1 = require("./address/legacyaddr.js"); // BIP32 extended public key version bytes // These match the values defined in Electrum ABC's networks.py const XPUB_VERSION_MAINNET = 0x0488b21e; const XPUB_VERSION_TESTNET = 0x043587cf; const HIGHEST_BIT = 0x80000000; class HdNode { constructor(params) { this._ecc = new ecc_js_1.Ecc(); this._seckey = params.seckey; this._pubkey = params.pubkey; this._chainCode = params.chainCode; this._depth = params.depth; this._index = params.index; this._parentFingerprint = params.parentFingerprint; } seckey() { return this._seckey; } pubkey() { return this._pubkey; } pkh() { return (0, hash_js_1.shaRmd160)(this._pubkey); } fingerprint() { return this.pkh().slice(0, 4); } index() { return this._index; } depth() { return this._depth; } parentFingerprint() { return this._parentFingerprint; } chainCode() { return this._chainCode; } /** * Encode this HdNode as an xpub (extended public key) string * * An xpub is a base58check-encoded string containing: * - 4 bytes: version (0x0488B21E for mainnet xpub, 0x043587CF for testnet xpub) * - 1 byte: depth * - 4 bytes: parent fingerprint * - 4 bytes: child index * - 32 bytes: chain code (needed to derive child keys) * - 33 bytes: public key (compressed) * * @param version - Version bytes (defaults to XPUB_VERSION_MAINNET) * @returns Base58check-encoded xpub string */ xpub(version = XPUB_VERSION_MAINNET) { // Validate public key is compressed if (this._pubkey.length !== 33 || (this._pubkey[0] !== 0x02 && this._pubkey[0] !== 0x03)) { throw new Error('Public key must be compressed (33 bytes, starts with 0x02 or 0x03)'); } // Write xpub data (78 bytes total) const writer = new writerbytes_js_1.WriterBytes(78); writer.putU32(version, 'BE'); writer.putU8(this._depth); writer.putU32(this._parentFingerprint, 'BE'); writer.putU32(this._index, 'BE'); writer.putBytes(this._chainCode); writer.putBytes(this._pubkey); // Encode with base58check const checksum = (0, hash_js_1.sha256d)(writer.data); const dataWithChecksum = new Uint8Array(78 + 4); dataWithChecksum.set(writer.data, 0); dataWithChecksum.set(checksum.subarray(0, 4), 78); return (0, b58_ts_1.encodeBase58)(dataWithChecksum); } derive(index) { const isHardened = index >= HIGHEST_BIT; const data = new writerbytes_js_1.WriterBytes(1 + 32 + 4); if (isHardened) { if (this._seckey === undefined) { throw new Error('Missing private key for hardened child key'); } data.putU8(0); data.putBytes(this._seckey); } else { data.putBytes(this._pubkey); } data.putU32(index, 'BE'); const hashed = (0, hmac_js_1.hmacSha512)(this._chainCode, data.data); const hashedLeft = hashed.slice(0, 32); const hashedRight = hashed.slice(32); // In case the secret key doesn't lie on the curve, we proceed with the // next index. This is astronomically unlikely but part of the specification. if (!this._ecc.isValidSeckey(hashedLeft)) { return this.derive(index + 1); } let seckey; let pubkey; if (this._seckey !== undefined) { try { seckey = this._ecc.seckeyAdd(this._seckey, hashedLeft); } catch (ex) { console.log('Skipping index', index, ':', ex); return this.derive(index + 1); } pubkey = this._ecc.derivePubkey(seckey); } else { try { pubkey = this._ecc.pubkeyAdd(this._pubkey, hashedLeft); } catch (ex) { console.log('Skipping index', index, ':', ex); return this.derive(index + 1); } seckey = undefined; } return new HdNode({ seckey: seckey, pubkey: pubkey, chainCode: hashedRight, depth: this._depth + 1, index, parentFingerprint: new bytes_js_1.Bytes(this.fingerprint()).readU32('BE'), }); } deriveHardened(index) { if (index < 0 || index >= HIGHEST_BIT) { throw new TypeError(`index must be between 0 and ${HIGHEST_BIT}, got ${index}`); } return this.derive(index + HIGHEST_BIT); } derivePath(path) { let splitPath = path.split('/'); if (splitPath[0] === 'm') { if (this._parentFingerprint) { throw new TypeError('Expected master, got child'); } splitPath = splitPath.slice(1); } // eslint-disable-next-line @typescript-eslint/no-this-alias let hd = this; for (const step of splitPath) { if (step.slice(-1) === `'`) { hd = hd.deriveHardened(parseInt(step.slice(0, -1), 10)); } else { hd = hd.derive(parseInt(step, 10)); } } return hd; } static fromPrivateKey(seckey, chainCode) { return new HdNode({ seckey: seckey, pubkey: new ecc_js_1.Ecc().derivePubkey(seckey), chainCode, depth: 0, index: 0, parentFingerprint: 0, }); } static fromSeed(seed) { if (seed.length < 16 || seed.length > 64) { throw new TypeError('Seed must be between 16 and 64 bytes long'); } const hashed = (0, hmac_js_1.hmacSha512)((0, str_js_1.strToBytes)('Bitcoin seed'), seed); const hashedLeft = hashed.slice(0, 32); const hashedRight = hashed.slice(32); return HdNode.fromPrivateKey(hashedLeft, hashedRight); } /** * Create an HdNode from an xpub (extended public key) string * * An xpub is a base58check-encoded string containing: * - 4 bytes: version (0x0488B21E for mainnet xpub, 0x043587CF for testnet xpub) * - 1 byte: depth * - 4 bytes: parent fingerprint * - 4 bytes: child index * - 32 bytes: chain code * - 33 bytes: public key (compressed) * * The resulting HdNode will not have a private key (watch-only). * * @param xpub - The extended public key string * @returns HdNode created from the xpub (without private key) */ static fromXpub(xpub) { const payload = (0, legacyaddr_js_1.decodeBase58Check)(xpub); if (payload.length !== 78) { throw new Error(`Invalid xpub: expected 78 bytes, got ${payload.length}`); } const bytes = new bytes_js_1.Bytes(payload); // Read version (4 bytes, big-endian) const version = bytes.readU32('BE'); // Validate version (mainnet or testnet xpub) if (version !== XPUB_VERSION_MAINNET && version !== XPUB_VERSION_TESTNET) { throw new Error(`Invalid xpub version: expected 0x${XPUB_VERSION_MAINNET.toString(16).toUpperCase()} (mainnet) or 0x${XPUB_VERSION_TESTNET.toString(16).toUpperCase()} (testnet), got 0x${version.toString(16)}`); } // Read depth (1 byte) const depth = bytes.readU8(); // Read parent fingerprint (4 bytes, big-endian) const parentFingerprint = bytes.readU32('BE'); // Read child index (4 bytes, big-endian) const index = bytes.readU32('BE'); // Read chain code (32 bytes) const chainCode = bytes.readBytes(32); // Read public key (33 bytes, compressed) const pubkey = bytes.readBytes(33); // Validate public key format (should start with 0x02 or 0x03 for compressed) if (pubkey[0] !== 0x02 && pubkey[0] !== 0x03) { throw new Error(`Invalid xpub: public key must be compressed (start with 0x02 or 0x03), got 0x${pubkey[0].toString(16)}`); } // Create HdNode without private key (watch-only) return new HdNode({ seckey: undefined, pubkey, chainCode, depth, index, parentFingerprint, }); } } exports.HdNode = HdNode; //# sourceMappingURL=hdwallet.js.map