UNPKG

libnexa-ts

Version:

A pure and powerful Nexa SDK library.

289 lines (260 loc) 9.89 kB
import ValidationUtils from "../utils/validation.utils"; import Point from "../crypto/point"; import type { Network } from "../core/network/network"; import { networks } from "../core/network/network-manager"; import BufferUtils from "../utils/buffer.utils"; import BN from "../crypto/bn.extension"; import CommonUtils from "../utils/common.utils"; import type { IPrivateKey, IPublicKey, PublicKeyDto } from "../common/interfaces"; export type PublicKeyVariants = PublicKey | Point | Partial<IPrivateKey> | Partial<IPublicKey> | PublicKeyDto | Uint8Array | string; /** * Instantiate new PublicKey. * * There are two internal properties, `network` and `compressed`, that deal with importing * a PublicKey from a PrivateKey in WIF format. * * @remarks Better to use {@linkcode PublicKey.from} method to init public key from various formats if the formart unknown. * * @example * ```ts * // export to as a DER hex encoded string * let exported = key.toString(); * * // import the public key * let imported = PublicKey.fromString(exported); * //or * let imported = PublicKey.from(exported); * ``` */ export default class PublicKey implements IPublicKey { public point!: Point; public compressed!: boolean; public network!: Network; /** * @param data - The pubkey data */ constructor(data: Partial<IPublicKey>) { ValidationUtils.validateArgument(data != null, 'First argument is required, please include public key data.'); if (data instanceof PublicKey) { // Return copy, but as it's an immutable object, return same argument return data; } if (PublicKey._isPublicKeyData(data)) { data.point.validate(); this.point = data.point; this.compressed = data.compressed == null || data.compressed; this.network = data.network || networks.defaultNetwork; } else { throw new TypeError('First argument is an unrecognized data format.'); } } public toObject = this.toJSON; public toDER = this.toBuffer; /** * @returns A plain object of the PublicKey */ public toJSON(): PublicKeyDto { return { x: this.point.getX().toString('hex', 2), y: this.point.getY().toString('hex', 2), compressed: this.compressed, network: this.network.toString() }; } /** * Will output the PublicKey to a DER Uint8Array * * @returns A DER encoded buffer */ public toBuffer(): Uint8Array { let x = this.point.getX(); let y = this.point.getY(); let xbuf = x.toByteArray({ size: 32 }); let ybuf = y.toByteArray({ size: 32 }); let prefix: Uint8Array; if (!this.compressed) { prefix = Uint8Array.from([0x04]); return BufferUtils.concat([prefix, xbuf, ybuf]); } else { let odd = ybuf[ybuf.length - 1] % 2; if (odd) { prefix = Uint8Array.from([0x03]); } else { prefix = Uint8Array.from([0x02]); } return BufferUtils.concat([prefix, xbuf]); } } /** * Will output the PublicKey to a DER encoded hex string * * @returns A DER hex encoded string */ public toString(): string { return BufferUtils.bufferToHex(this.toBuffer()); } /** * Will return a string formatted for the console * * @returns Public key string inspection */ public inspect(): string { return '<PublicKey: ' + this.toString() + (this.compressed ? '' : ', uncompressed') + '>'; } /** * Instantiate a PublicKey from various formats * * @param data The encoded data in various formats * @param compressed If the public key is compressed * @param network The key network * @returns New PublicKey instance */ public static from(data: PublicKeyVariants, compressed?: boolean, network?: Network): PublicKey { // detect type of data if (data instanceof PublicKey) { return data; } else if (data instanceof Point) { return this.fromPoint(data, compressed, network); } else if (this._isPublicKeyDto(data)) { return this.fromObject(data); } else if (this._isPublicKeyData(data)) { return new PublicKey(data); } else if (this._isPrivateKeyData(data)) { return this.fromPrivateKey(data); } else if (BufferUtils.isBuffer(data)) { return this.fromBuffer(data, true, network); } else if (CommonUtils.isHexa(data)) { return this.fromString(data as string, network); } else { throw new TypeError('First argument is an unrecognized data format.'); } } public static fromDER = this.fromBuffer; public static fromObject = this.fromJSON; /** * Instantiate a PublicKey from a Uint8Array * * @param buf - A DER hex buffer * @param strict - if set to false, will loosen some conditions * @param network - the network of the key * @returns A new valid instance of PublicKey */ public static fromBuffer(buf: Uint8Array, strict?: boolean, network?: Network): PublicKey { ValidationUtils.validateArgument(BufferUtils.isBuffer(buf), 'Must be a hex buffer of DER encoded public key'); let info = PublicKey._transformDER(buf, strict); return new PublicKey({ point: info.point, compressed: info.compressed, network: network }); } /** * Instantiate a PublicKey from a Point * * @param point - A Point instance * @param compressed - whether to store this public key as compressed format * @param network - the network of the key * @returns A new valid instance of PublicKey */ public static fromPoint(point: Point, compressed?: boolean, network?: Network): PublicKey { ValidationUtils.validateArgument(point instanceof Point, 'First argument must be an instance of Point.'); return new PublicKey({ point: point, compressed: compressed, network: network }); } /** * Instantiate a PublicKey from a DER hex encoded string * * @param str - A DER hex string * @param network - the network of the key * @returns A new valid instance of PublicKey */ public static fromString(str: string, network?: Network): PublicKey { let buf = BufferUtils.hexToBuffer(str); let info = PublicKey._transformDER(buf); return new PublicKey({ point: info.point, compressed: info.compressed, network: network }); } /** * Instantiate a PublicKey from PrivateKey data * * @param data - Object contains data of PrivateKey * @returns A new valid instance of PublicKey */ public static fromPrivateKey(data: IPrivateKey): PublicKey { ValidationUtils.validateArgument(this._isPrivateKeyData(data), 'data', 'Must be data of PrivateKey'); let point = Point.getG().mul(data.bn); return new PublicKey({ point: point, compressed: data.compressed, network: data.network }); } public static fromJSON(data: PublicKeyDto): PublicKey { let info = PublicKey._transformObject(data); return new PublicKey(info); } /** * Check if there would be any errors when initializing a PublicKey * * @param data - The encoded data in various formats * @returns An error if exists */ public static getValidationError(data: PublicKeyVariants): Error | undefined { try { this.from(data); } catch (e) { return e as Error; } return undefined; } /** * Check if the parameters are valid * * @param data - The encoded data in various formats * @returns true If the public key would be valid */ public static isValid(data: PublicKeyVariants): boolean { return !PublicKey.getValidationError(data); } private static _isPublicKeyData(data: unknown): data is IPublicKey { return typeof data === 'object' && data !== null && 'point' in data && data.point instanceof Point; } private static _isPublicKeyDto(data: unknown): data is PublicKeyDto { return typeof data === 'object' && data !== null && 'x' in data && 'y' in data; } private static _isPrivateKeyData(data: unknown): data is IPrivateKey { return typeof data === 'object' && data !== null && 'bn' in data && 'network' in data; } /** * Internal function to transform DER into a public key point * * @param buf - An hex encoded buffer * @param strict - if set to false, will loosen some conditions * @returns An object with keys: point and compressed */ private static _transformDER(buf: Uint8Array, strict?: boolean): Partial<IPublicKey> { ValidationUtils.validateArgument(BufferUtils.isBuffer(buf), 'Must be a hex buffer of DER encoded public key'); strict = strict == null ? true : strict; if (buf[0] === 0x04 || (!strict && (buf[0] === 0x06 || buf[0] === 0x07))) { let xbuf = buf.subarray(1, 33); let 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'); } let x = new BN(xbuf); let y = new BN(ybuf); return { point: Point.ecPoint(x, y), compressed: false }; } else if (buf[0] === 0x03) { let xbuf = buf.subarray(1); let x = new BN(xbuf); return { point: Point.ecPointFromX(true, x), compressed: true }; } else if (buf[0] === 0x02) { let xbuf = buf.subarray(1); let x = new BN(xbuf); return { point: Point.ecPointFromX(false, x), compressed: true }; } else { throw new TypeError('Invalid DER format public key'); } } /** * Internal function to transform a JSON into a public key point */ private static _transformObject(json: PublicKeyDto): Partial<IPublicKey> { ValidationUtils.validateArgument(typeof json.x === 'string', 'x', 'must be a hex string'); ValidationUtils.validateArgument(typeof json.y === 'string', 'y', 'must be a hex string'); let x = new BN(json.x, 'hex'); let y = new BN(json.y, 'hex'); let point = Point.ecPoint(x, y); return { point: point, compressed: json.compressed, network: networks.get(json.network) }; } }