libnexa-ts
Version:
A pure and powerful Nexa SDK library.
289 lines (260 loc) • 9.89 kB
text/typescript
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) };
}
}