UNPKG

lotus-sdk

Version:

Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem

382 lines (381 loc) 13.7 kB
import { BN } from './crypto/bn.js'; import { PrivateKey } from './privatekey.js'; import { PublicKey } from './publickey.js'; import { Point } from './crypto/point.js'; import { get as getNetwork, defaultNetwork, } from './networks.js'; import { Hash } from './crypto/hash.js'; import { Random } from './crypto/random.js'; import { Base58Check } from './encoding/base58check.js'; import { JSUtil } from './util/js.js'; import { Preconditions } from './util/preconditions.js'; import { HDPublicKey } from './hdpublickey.js'; export class HDPrivateKey { privateKey; network; depth; parentFingerPrint; childIndex; chainCode; fingerPrint; publicKey; xprivkey; _hdPublicKey; _buffers; static Hardened = 0x80000000; static MaxIndex = 2 * HDPrivateKey.Hardened; static RootElementAlias = ['m', 'M', "m'", "M'"]; constructor(data) { if (data instanceof HDPrivateKey) { return data; } if (data === undefined) { data = HDPrivateKey._getRandomData(); } const info = this._classifyArguments(data); this._buildFromObject(info); } get hdPublicKey() { return this._hdPublicKey; } get xpubkey() { return this._hdPublicKey.xpubkey; } _classifyArguments(data) { if (typeof data === 'string') { return HDPrivateKey._transformString(data); } else if (Buffer.isBuffer(data)) { const str = data.toString(); if (HDPrivateKey.isValidSerialized(str)) { return HDPrivateKey._transformSerialized(str); } else { return HDPrivateKey._transformBuffer(data); } } else if (typeof data === 'object' && data !== null) { if ('xprivkey' in data) { return HDPrivateKey._transformObject(data); } else { return data; } } else { throw new Error('Invalid HDPrivateKey data'); } } static _transformString(str) { if (!JSUtil.isHexa(str)) { return HDPrivateKey._transformSerialized(str); } return HDPrivateKey._transformBuffer(Buffer.from(str, 'hex')); } static _transformSerialized(str) { const buf = Base58Check.decode(str); return HDPrivateKey._transformBuffer(buf); } static _transformBuffer(buf) { if (buf.length !== 78) { throw new Error('Invalid HDPrivateKey buffer length'); } const version = buf.readUInt32BE(0); const network = getNetwork(version, 'xprivkey'); if (!network) { throw new Error('Invalid HDPrivateKey network'); } const depth = buf.readUInt8(4); const parentFingerPrint = buf.subarray(5, 9); const childIndex = buf.readUInt32BE(9); const chainCode = buf.subarray(13, 45); const privateKeyBuffer = buf.subarray(46, 78); const compressed = buf[45] === 0x01; return { network, depth, parentFingerPrint, childIndex, chainCode, privateKey: new PrivateKey({ buf: privateKeyBuffer, compressed }, network), }; } static _transformObject(obj) { const network = getNetwork(obj.network); if (!network) { throw new Error('Invalid network'); } return { network, depth: obj.depth, parentFingerPrint: Buffer.from(obj.parentFingerPrint, 'hex'), childIndex: obj.childIndex, chainCode: Buffer.from(obj.chainCode, 'hex'), privateKey: new PrivateKey(obj.privateKey, network), }; } static _getRandomData() { const seed = Random.getRandomBuffer(64); return HDPrivateKey._fromSeed(seed); } static _fromSeed(seed) { const hash = Hash.sha512hmac(seed, Buffer.from('Bitcoin seed')); const privateKeyBuffer = hash.subarray(0, 32); const chainCode = hash.subarray(32, 64); return { network: defaultNetwork, depth: 0, parentFingerPrint: Buffer.alloc(4), childIndex: 0, chainCode, privateKey: new PrivateKey(privateKeyBuffer, defaultNetwork), }; } _buildFromObject(info) { Preconditions.checkArgument(!!info.network, 'network', 'Network is required'); Preconditions.checkArgument(!!info.privateKey, 'privateKey', 'Private key is required'); Preconditions.checkArgument(!!info.chainCode, 'chainCode', 'Chain code is required'); const buffers = { version: Buffer.alloc(4), depth: Buffer.from([info.depth || 0]), parentFingerPrint: info.parentFingerPrint || Buffer.alloc(4), childIndex: Buffer.alloc(4), chainCode: info.chainCode, privateKey: info.privateKey.toBuffer(), checksum: undefined, }; buffers.version.writeUInt32BE(info.network.xprivkey, 0); buffers.childIndex.writeUInt32BE(info.childIndex || 0, 0); const version = info.network.xprivkey; const depth = info.depth || 0; const parentFingerPrint = info.parentFingerPrint || Buffer.alloc(4); const childIndex = info.childIndex || 0; const chainCode = info.chainCode; const privateKeyBuffer = info.privateKey.toBuffer(); const buf = Buffer.alloc(78); buf.writeUInt32BE(version, 0); buf.writeUInt8(depth, 4); parentFingerPrint.copy(buf, 5); buf.writeUInt32BE(childIndex, 9); chainCode.copy(buf, 13); privateKeyBuffer.copy(buf, 46); const xprivkey = Base58Check.encode(buf); JSUtil.defineImmutable(this, { network: info.network, depth: info.depth || 0, parentFingerPrint: info.parentFingerPrint || Buffer.alloc(4), childIndex: info.childIndex || 0, chainCode: info.chainCode, privateKey: info.privateKey, publicKey: PublicKey.fromPoint(info.privateKey.toPublicKey().point, true), fingerPrint: Hash.sha256ripemd160(PublicKey.fromPoint(info.privateKey.toPublicKey().point, true).toBuffer()).subarray(0, 4), xprivkey: xprivkey, _buffers: buffers, }); this._hdPublicKey = new HDPublicKey({ network: this.network, depth: this.depth, parentFingerPrint: this.parentFingerPrint, childIndex: this.childIndex, chainCode: this.chainCode, publicKey: this.publicKey, }); } isValidPath(arg, hardened) { if (typeof arg === 'string') { const indexes = HDPrivateKey._getDerivationIndexes(arg); return indexes !== null && indexes.every(index => this.isValidPath(index)); } if (typeof arg === 'number') { if (arg < HDPrivateKey.Hardened && hardened === true) { arg += HDPrivateKey.Hardened; } return arg >= 0 && arg < HDPrivateKey.MaxIndex; } return false; } static _getDerivationIndexes(path) { const steps = path.split('/'); if (HDPrivateKey.RootElementAlias.includes(path)) { return []; } if (!HDPrivateKey.RootElementAlias.includes(steps[0])) { return null; } const indexes = steps.slice(1).map(step => { const isHardened = step.slice(-1) === "'"; if (isHardened) { step = step.slice(0, -1); } if (!step || step[0] === '-') { return NaN; } let index = +step; if (isHardened) { index += HDPrivateKey.Hardened; } return index; }); return indexes.some(isNaN) ? null : indexes; } static isValidSerialized(data, network) { try { HDPrivateKey._transformString(typeof data === 'string' ? data : data.toString('hex')); return true; } catch (e) { return false; } } getSerializedError(data, network) { try { HDPrivateKey._transformString(typeof data === 'string' ? data : data.toString('hex')); return null; } catch (e) { return e; } } derive(arg, hardened) { return this.deriveNonCompliantChild(arg, hardened); } _deriveFromString(path, nonCompliant) { if (!this.isValidPath(path)) { throw new Error('Invalid derivation path'); } const indexes = HDPrivateKey._getDerivationIndexes(path); if (indexes === null) { throw new Error('Invalid derivation path'); } return indexes.reduce((prev, index) => { return prev._deriveWithNumber(index, undefined, nonCompliant); }, this); } _deriveWithNumber(index, hardened, nonCompliant) { if (!this.isValidPath(index, hardened)) { throw new Error('Invalid derivation path'); } hardened = index >= HDPrivateKey.Hardened ? true : hardened; if (index < HDPrivateKey.Hardened && hardened === true) { index += HDPrivateKey.Hardened; } const indexBuffer = Buffer.from([ index >> 24, index >> 16, index >> 8, index, ]); let data; if (hardened && nonCompliant) { const nonZeroPadded = this.privateKey.bn.toBuffer(); data = Buffer.concat([Buffer.from([0]), nonZeroPadded, indexBuffer]); } else if (hardened) { const privateKeyBuffer = this.privateKey.bn.toBuffer({ size: 32 }); data = Buffer.concat([Buffer.from([0]), privateKeyBuffer, indexBuffer]); } else { data = Buffer.concat([this.publicKey.toBuffer(), indexBuffer]); } const hash = Hash.sha512hmac(data, this.chainCode); const leftPart = BN.fromBuffer(hash.subarray(0, 32), { size: 32 }); const childChainCode = hash.subarray(32, 64); const childPrivateKey = leftPart .add(this.privateKey.toBigNumber()) .umod(Point.getN()) .toBuffer({ size: 32 }); if (!PrivateKey.isValid(childPrivateKey)) { return this._deriveWithNumber(index + 1, undefined, nonCompliant); } return new HDPrivateKey({ network: this.network, depth: this.depth + 1, parentFingerPrint: Hash.sha256ripemd160(this.privateKey.toPublicKey().toBuffer()).subarray(0, 4), childIndex: index, chainCode: childChainCode, privateKey: new PrivateKey({ bn: childPrivateKey.toString('hex'), network: this.network.name, compressed: this.privateKey.compressed, }), }); } deriveChild(arg, hardened) { if (typeof arg === 'string') { return this._deriveFromString(arg, false); } else if (typeof arg === 'number') { return this._deriveWithNumber(arg, hardened, false); } else { throw new Error('Invalid derivation argument'); } } deriveNonCompliantChild(arg, hardened) { if (typeof arg === 'string') { return this._deriveFromString(arg, true); } else if (typeof arg === 'number') { return this._deriveWithNumber(arg, hardened, true); } else { throw new Error('Invalid derivation argument'); } } toString() { return this.xprivkey; } toObject() { return { xprivkey: this.xprivkey, network: this.network.toString(), depth: this.depth, parentFingerPrint: this.parentFingerPrint.toString('hex'), childIndex: this.childIndex, chainCode: this.chainCode.toString('hex'), privateKey: this.privateKey.toString(), }; } toJSON() { return this.toObject(); } toBuffer() { if (this._buffers.xprivkey) { return this._buffers.xprivkey; } const version = this.network.xprivkey; const depth = this.depth; const parentFingerPrint = this.parentFingerPrint; const childIndex = this.childIndex; const chainCode = this.chainCode; const privateKeyBuffer = this.privateKey.toBuffer(); const buf = Buffer.alloc(78); buf.writeUInt32BE(version, 0); buf.writeUInt8(depth, 4); parentFingerPrint.copy(buf, 5); buf.writeUInt32BE(childIndex, 9); chainCode.copy(buf, 13); privateKeyBuffer.copy(buf, 46); this._buffers.xprivkey = buf; return buf; } static fromBuffer(arg) { return new HDPrivateKey(arg.toString('hex')); } static fromString(arg) { return new HDPrivateKey(arg); } static fromObject(obj) { return new HDPrivateKey(obj); } static fromSeed(hexa, network) { const seed = typeof hexa === 'string' ? Buffer.from(hexa, 'hex') : hexa; const data = HDPrivateKey._fromSeed(seed); if (network) { data.network = getNetwork(network) || defaultNetwork; } return new HDPrivateKey(data); } inspect() { return `<HDPrivateKey: ${this.xprivkey}, network: ${this.network}>`; } }