lotus-sdk
Version:
Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem
264 lines (263 loc) • 8.38 kB
JavaScript
import { BN } from './crypto/bn.js';
import { Point } from './crypto/point.js';
import { Hash } from './crypto/hash.js';
import { JSUtil } from './util/js.js';
import { get as getNetwork, defaultNetwork } from './networks.js';
import { PrivateKey } from './privatekey.js';
import { Address } from './address.js';
export class PublicKey {
point;
compressed;
network;
constructor(data, extra) {
if (data instanceof PublicKey) {
return data;
}
if (!data) {
throw new Error('First argument is required, please include public key data.');
}
extra = extra || {};
const info = this._classifyArgs(data, extra);
info.point?.validate();
JSUtil.defineImmutable(this, {
point: info.point,
compressed: info.compressed,
network: info.network || defaultNetwork,
});
}
_classifyArgs(data, extra) {
const info = {
compressed: extra.compressed === undefined ? true : extra.compressed,
};
if (data instanceof Point) {
info.point = data;
}
else if (typeof data === 'object' &&
data !== null &&
'x' in data &&
'y' in data) {
const objectInfo = PublicKey._transformObject(data);
Object.assign(info, objectInfo);
}
else if (typeof data === 'string') {
const derInfo = PublicKey._transformDER(Buffer.from(data, 'hex'));
Object.assign(info, derInfo);
}
else if (PublicKey._isBuffer(data)) {
const derInfo = PublicKey._transformDER(data);
Object.assign(info, derInfo);
}
else if (PublicKey._isPrivateKey(data)) {
const privkeyInfo = PublicKey._transformPrivateKey(data);
Object.assign(info, privkeyInfo);
}
else {
throw new TypeError('First argument is an unrecognized data format.');
}
if (!info.network) {
info.network = extra.network ? getNetwork(extra.network) : undefined;
}
if (!info.point) {
throw new Error('Failed to derive a valid point from the input data');
}
return info;
}
static _isPrivateKey(param) {
return param instanceof PrivateKey;
}
static _isBuffer(param) {
return Buffer.isBuffer(param) || param instanceof Uint8Array;
}
static _transformPrivateKey(privkey) {
if (!PublicKey._isPrivateKey(privkey)) {
throw new Error('Must be an instance of PrivateKey');
}
return {
point: Point.getG().mul(privkey.bn),
compressed: privkey.compressed,
network: privkey.network,
};
}
static _transformDER(buf, strict = true) {
if (!PublicKey._isBuffer(buf)) {
throw new Error('Must be a hex buffer of DER encoded public key');
}
let point;
let compressed;
let x;
let y;
let xbuf;
let ybuf;
if (buf[0] === 0x04 || (!strict && (buf[0] === 0x06 || buf[0] === 0x07))) {
xbuf = buf.subarray(1, 33);
ybuf = buf.subarray(33, 65);
if (xbuf.length !== 32 || ybuf.length !== 32 || buf.length !== 65) {
throw new TypeError('Length of x and y must be 32 bytes');
}
x = new BN(xbuf, 'be');
y = new BN(ybuf, 'be');
point = new Point(x, y);
compressed = false;
}
else if (buf[0] === 0x03) {
xbuf = buf.subarray(1);
x = new BN(xbuf, 'be');
const xInfo = PublicKey._transformX(true, x);
point = xInfo.point;
compressed = true;
}
else if (buf[0] === 0x02) {
xbuf = buf.subarray(1);
x = new BN(xbuf, 'be');
const xInfo = PublicKey._transformX(false, x);
point = xInfo.point;
compressed = true;
}
else {
throw new TypeError('Invalid DER format public key');
}
return {
point,
compressed,
};
}
static _transformX(odd, x) {
if (typeof odd !== 'boolean') {
throw new Error('Must specify whether y is odd or not (true or false)');
}
return {
point: Point.fromX(odd, x),
compressed: true,
};
}
static _transformObject(json) {
const x = new BN(json.x, 16);
const y = new BN(json.y, 16);
const point = new Point(x, y);
return {
point: point,
compressed: json.compressed,
};
}
static fromPrivateKey(privkey) {
if (!PublicKey._isPrivateKey(privkey)) {
throw new Error('Must be an instance of PrivateKey');
}
const info = PublicKey._transformPrivateKey(privkey);
return new PublicKey(info.point, {
compressed: info.compressed,
network: info.network,
});
}
static fromDER(buf, strict) {
if (!PublicKey._isBuffer(buf)) {
throw new Error('Must be a hex buffer of DER encoded public key');
}
const info = PublicKey._transformDER(buf, strict);
return new PublicKey(info.point, {
compressed: info.compressed,
});
}
static fromBuffer(buf, strict) {
return PublicKey.fromDER(buf, strict);
}
static fromPoint(point, compressed) {
if (!(point instanceof Point)) {
throw new Error('First argument must be an instance of Point.');
}
return new PublicKey(point, {
compressed: compressed,
});
}
static fromString(str, encoding) {
const buf = Buffer.from(str, encoding || 'hex');
const info = PublicKey._transformDER(buf);
return new PublicKey(info.point, {
compressed: info.compressed,
});
}
static fromX(odd, x) {
const info = PublicKey._transformX(odd, x);
return new PublicKey(info.point, {
compressed: info.compressed,
});
}
static getValidationError(data) {
try {
new PublicKey(data);
return null;
}
catch (e) {
return e;
}
}
static isValid(data) {
if (data instanceof PublicKey) {
return true;
}
return !PublicKey.getValidationError(data);
}
toObject() {
return {
x: this.point.getX().toString(16).padStart(64, '0'),
y: this.point.getY().toString(16).padStart(64, '0'),
compressed: this.compressed,
};
}
toJSON() {
return this.toObject();
}
toBigNumber() {
return this.point.getX();
}
toBuffer() {
return this.toDER();
}
toDER() {
const x = this.point.getX();
const y = this.point.getY();
const xbuf = x.toArrayLike(Buffer, 'be', 32);
const ybuf = y.toArrayLike(Buffer, 'be', 32);
let prefix;
if (!this.compressed) {
prefix = Buffer.from([0x04]);
return Buffer.concat([prefix, xbuf, ybuf]);
}
else {
const odd = y.mod(new BN(2)).eq(new BN(1));
if (odd) {
prefix = Buffer.from([0x03]);
}
else {
prefix = Buffer.from([0x02]);
}
return Buffer.concat([prefix, xbuf]);
}
}
_getID() {
return Hash.sha256ripemd160(this.toBuffer());
}
toAddress(network) {
return Address.fromPublicKey(this, network);
}
toString() {
return this.toDER().toString('hex');
}
inspect() {
return `<PublicKey: ${this.toString()}${this.compressed ? '' : ', uncompressed'}>`;
}
addScalar(scalar) {
const scalarBN = Buffer.isBuffer(scalar) ? new BN(scalar) : scalar;
const G = Point.getG();
const tweakPoint = G.mul(scalarBN);
const tweakedPoint = this.point.add(tweakPoint);
tweakedPoint.validate();
return new PublicKey(tweakedPoint, {
compressed: this.compressed,
network: this.network,
});
}
static getN() {
return Point.getN();
}
}