libnexa-ts
Version:
A pure and powerful Nexa SDK library.
362 lines (320 loc) • 10.7 kB
text/typescript
import type { Networkish } from "../common/types";
import type { IPrivateKey, PrivateKeyDto } from "../common/interfaces";
import type { Network } from "../core/network/network";
import { networks } from "../core/network/network-manager";
import BN from "../crypto/bn.extension";
import CommonUtils from "../utils/common.utils";
import BufferUtils from "../utils/buffer.utils";
import Point from "../crypto/point";
import Base58Check from "../encoding/base58check";
import ValidationUtils from "../utils/validation.utils";
import PublicKey from "./publickey";
import Address from "../core/address/address";
import { AddressType } from "../core/address/address-formatter";
export type PrivateKeyVariants = BN | string | Uint8Array | PrivateKey | IPrivateKey | PrivateKeyDto | null;
export default class PrivateKey implements IPrivateKey {
private _pubkey?: PublicKey;
public bn!: BN;
public compressed!: boolean;
public network!: Network;
/**
* Instantiate a PrivateKey.
*
* @param data The private key data
*
* @remarks Better to use {@linkcode PrivateKey.from} method to init private key from various formats if the formart unknown.
*
* @example
* ```ts
* // generate a new random key
* let key = new PrivateKey();
*
* // encode into wallet import format
* let exported = key.toWIF();
*
* // instantiate from the exported (and saved) private key
* let imported = PrivateKey.fromWIF(exported);
* ```
*/
constructor(data?: Partial<IPrivateKey>) {
if (data instanceof PrivateKey) {
return data;
}
if (data == null) {
data = {
bn: PrivateKey._getRandomBN(),
compressed: true,
network: networks.defaultNetwork
};
}
if (!PrivateKey._isPrivateKeyParams(data)) {
throw new TypeError('First argument is an unrecognized data type.');
}
// validation
if (!data.bn || data.bn.cmp(new BN(0)) === 0){
throw new TypeError('Number can not be equal to zero, undefined, null or false');
}
if (!data.bn.lt(Point.getN())) {
throw new TypeError('Number must be less than N');
}
this.bn = data.bn;
this.compressed = data.compressed == null ? true : data.compressed;
this.network = data.network || networks.defaultNetwork;
}
public get publicKey(): PublicKey {
return this.toPublicKey();
}
/**
* Will return an address for the private key with its defined network
*
* @param type - optional parameter specifying the desired type of the address.
* default {@link AddressType.PayToScriptTemplate}
*
* @returns An address generated from the private key
*/
public toAddress(type = AddressType.PayToScriptTemplate): Address {
return Address.fromPublicKey(this.publicKey, this.network, type);
}
/**
* Will output the PrivateKey encoded as hex string
*/
public toString(): string {
return BufferUtils.bufferToHex(this.toBuffer());
}
/**
* Will encode the PrivateKey to a WIF string
*
* @returns A WIF representation of the private key
*/
public toWIF(): string {
let buf: Uint8Array;
if (this.compressed) {
buf = BufferUtils.concat([Uint8Array.from([this.network.privatekey]),
this.bn.toByteArray({size: 32}),
Uint8Array.from([0x01])]);
} else {
buf = BufferUtils.concat([Uint8Array.from([this.network.privatekey]),
this.bn.toByteArray({size: 32})]);
}
return Base58Check.encode(buf);
}
/**
* Will return the private key as a BN instance
*
* @returns A BN instance of the private key
*/
public toBigNumber(): BN {
return this.bn;
}
/**
* Will return the private key as a BN buffer
*
* @returns A buffer of the private key
*/
public toBuffer(): Uint8Array {
return this.bn.toByteArray({size: 32});
}
/**
* Will return the private key as a BN buffer without leading zero padding
*
* @returns A buffer of the private key
*/
public toBufferNoPadding(): Uint8Array {
return this.bn.toByteArray();
}
/**
* Will return the corresponding public key
*
* @returns A public key generated from the private key
*/
public toPublicKey(): PublicKey {
if (!this._pubkey) {
this._pubkey = PublicKey.fromPrivateKey(this);
}
return this._pubkey;
}
public toObject = this.toJSON;
/**
* @returns A plain object representation
*/
public toJSON(): PrivateKeyDto {
return {
bn: this.bn.toString('hex'),
compressed: this.compressed,
network: this.network.toString()
};
}
/**
* Will return a string formatted for the console
*
* @returns Private key details
*/
public inspect(): string {
let uncompressed = !this.compressed ? ', uncompressed' : '';
return `<PrivateKey: ${this.toString()}, network: ${this.network}${uncompressed}>`;
}
/**
* Instantiate a PrivateKey from a Uint8Array with the DER or WIF representation
*/
public static fromBuffer(buf: Uint8Array, network?: Networkish): PrivateKey {
ValidationUtils.validateArgument(BufferUtils.isBuffer(buf), 'First argument is expected to be a buffer.');
let data = this._transformBuffer(buf, network);
return new PrivateKey(data);
}
public static fromString = this.fromWIF;
/**
* Instantiate a PrivateKey from a WIF string
*
* @param str - The WIF encoded private key string
* @returns A new valid instance of PrivateKey
*/
public static fromWIF(str: string, network?: Networkish): PrivateKey {
ValidationUtils.validateArgument(typeof str === 'string', 'First argument is expected to be a string.');
let data: Partial<IPrivateKey>;
if (CommonUtils.isHexa(str)) {
data = {
bn: new BN(BufferUtils.hexToBuffer(str)),
compressed: true,
network: networks.get(network) || networks.defaultNetwork
};
} else {
data = PrivateKey._transformWIF(str, network);
}
return new PrivateKey(data);
}
public static fromObject = this.fromJSON;
/**
* Instantiate a PrivateKey from a plain JavaScript object
*
* @param obj - The output from privateKey.toObject()
*/
public static fromJSON(obj: PrivateKeyDto): PrivateKey {
ValidationUtils.validateArgument(typeof obj === 'object' && obj !== null, 'First argument is expected to be an object.');
let data = this._transformObject(obj);
return new PrivateKey(data);
}
/**
* Instantiate a PrivateKey from random bytes
*
* @param network - Either "mainnet" or "testnet"
* @returns A new valid instance of PrivateKey
*/
public static fromRandom(network?: Networkish): PrivateKey {
let data = {
bn: PrivateKey._getRandomBN(),
compressed: true,
network: networks.get(network) || networks.defaultNetwork
}
return new PrivateKey(data);
}
/**
* Check if there would be any errors when initializing a PrivateKey
*
* @param data - The encoded data in various formats
* @param network - Either "mainnet" or "testnet"
* @returns An error if exists
*/
public static getValidationError(data: PrivateKeyVariants, network?: Networkish): Error | undefined {
try {
this.from(data, network);
} catch (e) {
return e as Error;
}
return undefined;
}
/**
* Check if the parameters are valid
*
* @param data - The encoded data in various formats
* @param network - Either "mainnet" or "testnet"
* @returns true If the private key would be valid
*/
public static isValid(data?: PrivateKeyVariants, network?: Networkish): boolean {
if (!data) {
return false;
}
return !this.getValidationError(data, network);
}
/**
* Helper to instantiate PrivateKey from different kinds of arguments.
*/
public static from(data?: PrivateKeyVariants, network?: Networkish): PrivateKey {
// detect type of data
if (data == null){
return this.fromRandom(network);
} else if (data instanceof PrivateKey) {
return data;
} else if (data instanceof BN) {
let info: Partial<IPrivateKey> = {
bn: data,
compressed: true,
network: networks.get(network) || networks.defaultNetwork
}
return new PrivateKey(info);
} else if (BufferUtils.isBuffer(data)) {
return this.fromBuffer(data, network);
} else if (this._isPrivateKeyParams(data) && data.bn && data.network){
return this.fromObject(data);
} else if (typeof data === 'string') {
return this.fromString(data, network);
} else {
throw new TypeError('First argument is an unrecognized data type.');
}
}
private static _isPrivateKeyParams(data: unknown): data is PrivateKeyDto {
return typeof data === 'object' && data !== null && 'bn' in data && 'network' in data;
}
private static _getRandomBN(): BN {
let bn: BN;
do {
let privbuf = BufferUtils.getRandomBuffer(32);
bn = BN.fromBuffer(privbuf);
} while (!bn.lt(Point.getN()));
return bn;
}
/**
* Internal function to transform a WIF Uint8Array into a private key
*/
private static _transformBuffer(buf: Uint8Array, network?: Networkish): IPrivateKey {
if (buf.length === 32) {
return {
network: networks.get(network) || networks.defaultNetwork,
bn: BN.fromBuffer(buf),
compressed: false,
} as IPrivateKey;
}
let info: Partial<IPrivateKey> = {};
info.network = networks.get(buf[0], 'privatekey');
if (!info.network) {
throw new Error(`Invalid network`);
}
if (network && info.network !== networks.get(network)) {
throw new TypeError('Private key network mismatch');
}
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 = BN.fromBuffer(buf.subarray(1, 32 + 1));
return info as IPrivateKey;
}
/**
* Internal function to transform a JSON object into a private key
*/
private static _transformObject(data: PrivateKeyDto): IPrivateKey {
return {
bn: new BN(data.bn, 'hex'),
network: networks.get(data.network) || networks.defaultNetwork,
compressed: data.compressed
} as IPrivateKey;
}
/**
* Internal function to transform a WIF string into a private key
*/
private static _transformWIF(str: string, network?: Networkish): IPrivateKey {
return this._transformBuffer(Base58Check.decode(str), network);
}
}