UNPKG

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
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(); } }