lotus-sdk
Version:
Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem
238 lines (237 loc) • 7.75 kB
JavaScript
import { BN } from './crypto/bn.js';
import { Point } from './crypto/point.js';
import { Random } from './crypto/random.js';
import { Base58Check } from './encoding/base58check.js';
import { JSUtil } from './util/js.js';
import { get as getNetwork, defaultNetwork, } from './networks.js';
import { PublicKey } from './publickey.js';
import { Address } from './address.js';
export class PrivateKey {
bn;
compressed;
network;
_pubkey;
constructor(data, network) {
if (data instanceof PrivateKey) {
return data;
}
const info = this._classifyArguments(data, network);
if (!info.bn || info.bn.isZero()) {
throw new TypeError('Number can not be equal to zero, undefined, null or false');
}
if (!info.bn.lt(Point.getN())) {
throw new TypeError('Number must be less than N');
}
if (!info.network) {
throw new TypeError('Must specify the network ("livenet" or "testnet")');
}
JSUtil.defineImmutable(this, {
bn: info.bn,
compressed: info.compressed,
network: info.network,
});
}
get publicKey() {
return this.toPublicKey();
}
_classifyArguments(data, network) {
const info = {
compressed: true,
network: network ? getNetwork(network) || defaultNetwork : defaultNetwork,
};
if (data === undefined || data === null) {
info.bn = PrivateKey._getRandomBN();
}
else if (data instanceof BN) {
info.bn = data;
}
else if (Buffer.isBuffer(data)) {
const bufferInfo = PrivateKey._transformBuffer(data, network);
Object.assign(info, bufferInfo);
}
else if (typeof data === 'object' &&
data !== null &&
'compressed' in data &&
'buf' in data) {
info.compressed = data.compressed;
info.bn = new BN(data.buf, 'be');
}
else if (typeof data === 'object' &&
data !== null &&
'bn' in data &&
'network' in data) {
const objectInfo = PrivateKey._transformObject(data);
Object.assign(info, objectInfo);
}
else if (!network && typeof data === 'string' && getNetwork(data)) {
info.bn = PrivateKey._getRandomBN();
info.network = getNetwork(data);
}
else if (typeof data === 'string') {
if (JSUtil.isHexa(data)) {
info.bn = new BN(data, 16);
}
else {
const wifInfo = PrivateKey._transformWIF(data, network);
Object.assign(info, wifInfo);
}
}
else {
throw new TypeError('First argument is an unrecognized data type.');
}
return info;
}
static _getRandomBN() {
let bn;
do {
const privbuf = Random.getPseudoRandomBuffer(32);
bn = new BN(privbuf, 'be');
} while (!bn.lt(Point.getN()));
return bn;
}
static _transformBuffer(buf, network) {
const info = {};
if (buf.length === 32) {
return PrivateKey._transformBNBuffer(buf, network);
}
const detectedNetwork = getNetwork(buf[0], 'privatekey');
if (!detectedNetwork) {
throw new Error('Invalid network');
}
info.network = detectedNetwork;
if (network) {
const specifiedNetwork = getNetwork(network);
if (specifiedNetwork && info.network !== specifiedNetwork) {
const isCompatible = (info.network.name === 'testnet' &&
specifiedNetwork.name === 'regtest') ||
(info.network.name === 'regtest' &&
specifiedNetwork.name === 'testnet');
if (!isCompatible) {
throw new TypeError('Private key network mismatch');
}
info.network = specifiedNetwork;
}
}
if (buf.length === 1 + 32 + 1 && buf[1 + 32 + 1 - 1] === 1) {
info.compressed = true;
}
else if (buf.length === 1 + 32) {
info.compressed = false;
}
else {
throw new Error('Length of buffer must be 33 (uncompressed) or 34 (compressed)');
}
info.bn = new BN(buf.subarray(1, 32 + 1), 'be');
return info;
}
static _transformBNBuffer(buf, network) {
network ||= defaultNetwork;
return {
network: getNetwork(network),
bn: new BN(buf, 'be'),
compressed: true,
};
}
static _transformWIF(str, network) {
return PrivateKey._transformBuffer(Base58Check.decode(str), network);
}
static _transformObject(json) {
const bn = new BN(json.bn, 16);
const network = getNetwork(json.network);
return {
bn: bn,
network: network || defaultNetwork,
compressed: json.compressed,
};
}
static fromBuffer(arg, network) {
return new PrivateKey(arg, network);
}
static fromString(str, network) {
if (typeof str !== 'string') {
throw new Error('First argument is expected to be a string.');
}
return new PrivateKey(str, network);
}
static fromWIF(str, network) {
return PrivateKey.fromString(str, network ?? defaultNetwork);
}
static fromObject(obj, network) {
if (typeof obj !== 'object') {
throw new Error('First argument is expected to be an object.');
}
return new PrivateKey(obj, network);
}
static fromRandom(network) {
const bn = PrivateKey._getRandomBN();
return new PrivateKey(bn, network);
}
static getValidationError(data, network) {
try {
new PrivateKey(data, network);
return null;
}
catch (e) {
return e;
}
}
static isValid(data, network) {
if (!data) {
return false;
}
return !PrivateKey.getValidationError(data, network);
}
toString() {
return this.toBuffer().toString('hex');
}
toWIF(compressed = true) {
let buf;
if (compressed) {
buf = Buffer.concat([
Buffer.from([this.network.privatekey]),
this.bn.toArrayLike(Buffer, 'be', 32),
Buffer.from([0x01]),
]);
}
else {
buf = Buffer.concat([
Buffer.from([this.network.privatekey]),
this.bn.toArrayLike(Buffer, 'be', 32),
]);
}
return Base58Check.encode(buf);
}
toBigNumber() {
return this.bn;
}
toBuffer() {
return this.bn.toArrayLike(Buffer, 'be', 32);
}
toBufferNoPadding() {
return this.bn.toArrayLike(Buffer, 'be');
}
toPublicKey() {
if (!this._pubkey) {
this._pubkey = PublicKey.fromPrivateKey(this);
}
return this._pubkey;
}
toAddress(network) {
const pubkey = this.toPublicKey();
return Address.fromPublicKey(pubkey, network ?? this.network.name);
}
toObject() {
return {
bn: this.bn.toString(16),
compressed: this.compressed,
network: this.network.toString(),
};
}
toJSON() {
return this.toObject();
}
inspect() {
const uncompressed = !this.compressed ? ', uncompressed' : '';
return `<PrivateKey: ${this.toString()}, network: ${this.network}${uncompressed}>`;
}
}