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
JavaScript
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}>`;
}
}