mcps-sdk-js
Version:
MCPS JavaScript SDK
307 lines (283 loc) • 8.19 kB
text/typescript
import * as hexEncoding from 'crypto-js/enc-hex';
import * as SHA3 from 'crypto-js/sha3';
import * as SHA256 from 'crypto-js/sha256';
import * as RIPEMD160 from 'crypto-js/ripemd160';
import * as is from 'is_js';
import { SdkError } from '../errors';
import * as types from '../types';
/**
* MCPS SDK JS Utils
* @hidden
*/
export class Utils {
/**
* String to ArrayBuffer
* @param str ASCII string
* @returns Uint8Array
*/
static str2ab(str: string): Uint8Array {
if (typeof str !== 'string') {
throw new SdkError('str2ab expects a string');
}
const result = new Uint8Array(str.length);
for (let i = 0, strLen = str.length; i < strLen; i++) {
result[i] = str.charCodeAt(i);
}
return result;
}
/**
* String to Byte Array
* @param str ASCII string
* @returns Uint8Array
*/
static str2ba(str: string): number[] {
if (typeof str !== 'string') {
throw new SdkError('str2ba expects a string');
}
const result = [];
for (let i = 0, strLen = str.length; i < strLen; i++) {
result[i] = str.charCodeAt(i);
}
return result;
}
/**
* ArrayBuffer to String
* @param arr Uint8Array
* @returns HEX string
*/
static ab2hexstring(arr: Uint8Array): string {
if (typeof arr !== 'object') {
throw new SdkError('ab2hexstring expects an array');
}
let result = '';
for (let i = 0; i < arr.length; i++) {
let str = arr[i].toString(16);
str = str.length === 0 ? '00' : str.length === 1 ? '0' + str : str;
result += str;
}
return result;
}
/**
* String to Hex String
* @param str ASCII string
* @returns HEX string
*/
static str2hexstring(str: string): string {
return Utils.ab2hexstring(Utils.str2ab(str));
}
/**
* Object to Hex String
* @param obj Json Object
* @returns HEX string
*/
static obj2hexstring(obj: object): string {
return Utils.str2hexstring(JSON.stringify(obj));
}
/**
* Convert an integer to big endian hex and add leading zeros
* @param num The number to be converted
* @returns HEX string
*/
static int2hex(num: number) {
if (typeof num !== 'number') {
throw new SdkError('int2hex expects a number');
}
const h = num.toString(16);
return h.length % 2 ? '0' + h : h;
}
/**
* Converts a number to a big endian hexstring of a suitable size, optionally little endian
* @param num Number to convert
* @param size The required size in bytes, eg 1 for Uint8, 2 for Uint16. Defaults to 1.
* @param littleEndian Encode the hex in little endian form
* @returns HEX string
*/
static num2hexstring(num: number, size = 1, littleEndian = false) {
if (typeof num !== 'number') throw new SdkError('num must be numeric');
if (num < 0) throw new RangeError('num is unsigned (>= 0)');
if (size % 1 !== 0) throw new SdkError('size must be a whole integer');
if (!Number.isSafeInteger(num)) {
throw new RangeError(`num (${num}) must be a safe integer`);
}
size = size * 2;
let hexstring = num.toString(16);
hexstring =
hexstring.length % size === 0
? hexstring
: ('0'.repeat(size) + hexstring).substring(hexstring.length);
if (littleEndian) hexstring = Utils.reverseHex(hexstring);
return hexstring;
}
/**
* Converts a number to a variable length Int. Used for array length header
* @param num Number to convert
* @returns HEX string of the variable Int.
*/
static num2VarInt(num: number) {
if (num < 0xfd) {
return Utils.num2hexstring(num);
} else if (num <= 0xffff) {
// uint16
return 'fd' + Utils.num2hexstring(num, 2, true);
} else if (num <= 0xffffffff) {
// uint32
return 'fe' + Utils.num2hexstring(num, 4, true);
} else {
// uint64
return 'ff' + Utils.num2hexstring(num, 8, true);
}
}
/**
* Reverses an array. Accepts arrayBuffer.
* @param arr Array to reverse
* @returns Reversed array
*/
static reverseArray(arr: Uint8Array): Uint8Array {
if (typeof arr !== 'object' || !arr.length) {
throw new SdkError('reverseArray expects an array');
}
const result = new Uint8Array(arr.length);
for (let i = 0; i < arr.length; i++) {
result[i] = arr[arr.length - 1 - i];
}
return result;
}
/**
* Reverses a HEX string, treating 2 chars as a byte.
* @example
* reverseHex('abcdef') = 'efcdab'
* @param hex HEX string
* @returns HEX string reversed in 2s.
*/
static reverseHex(hex: string): string {
Utils.ensureHex(hex);
let out = '';
for (let i = hex.length - 2; i >= 0; i -= 2) {
out += hex.substr(i, 2);
}
return out;
}
/**
* Checks if input is a hexstring. Empty string is considered a hexstring.
* @example
* isHex('0101') = true
* isHex('') = true
* isHex('0x01') = false
* @param str
* @returns {boolean}
*/
static isHex(str: string): boolean {
try {
const hexRegex = /^([0-9A-Fa-f]{2})*$/;
return hexRegex.test(str);
} catch (err) {
return false;
}
}
/**
* Throws an error if input is not hexstring.
* @param str
*/
static ensureHex(str: string) {
if (!Utils.isHex(str)) {
throw new SdkError(`Expected a hexstring but got ${str}`);
}
}
/**
* Computes a SHA256 followed by a RIPEMD160.
* @param hex Message to hash
* @returns Hash output
*/
static sha256ripemd160(hex: string): string {
if (typeof hex !== 'string') {
throw new SdkError('sha256ripemd160 expects a string');
}
if (hex.length % 2 !== 0) {
throw new SdkError(`invalid hex string length: ${hex}`);
}
const hexEncoded = hexEncoding.parse(hex);
const programSha256 = SHA256(hexEncoded);
return RIPEMD160(programSha256).toString();
}
/**
* Computes a single SHA256 digest.
* @param hex Message to hash
* @returns Hash output
*/
static sha256(hex: string): string {
if (typeof hex !== 'string') {
throw new SdkError('sha256 expects a hex string');
}
if (hex.length % 2 !== 0) {
throw new SdkError(`invalid hex string length: ${hex}`);
}
const hexEncoded = hexEncoding.parse(hex);
return SHA256(hexEncoded).toString();
}
/**
* Computes a single SHA3 (Keccak) digest.
* @param hex Message to hash
* @returns Hash output
*/
static sha3(hex: string): string {
if (typeof hex !== 'string') {
throw new SdkError('sha3 expects a hex string');
}
if (hex.length % 2 !== 0) {
throw new SdkError(`invalid hex string length: ${hex}`);
}
const hexEncoded = hexEncoding.parse(hex);
return SHA3(hexEncoded).toString();
}
static sortObject(obj: any): any {
if (obj === null) return null;
if (is.not.object(obj)) return obj;
if (is.array(obj)) return obj.map(Utils.sortObject);
const sortedKeys = Object.keys(obj).sort();
const result: { [k: string]: any } = {};
sortedKeys.forEach(key => {
result[key] = Utils.sortObject(obj[key]);
});
return result;
}
static base64ToString(b64: string): string {
return Buffer.from(b64, 'base64').toString();
}
static bytesToBase64(bytes:Uint8Array):string{
return Buffer.from(bytes).toString('base64');
}
/**
* Decode base64 encoded tags
* @param tags
*/
static decodeTags(tags: types.Tag[]): types.Tag[] {
const decodedTags: types.Tag[] = [];
if (!tags || tags.length === 0) {
return decodedTags;
}
tags.forEach((tag: types.Tag) => {
decodedTags.push({
key: Utils.base64ToString(tag.key),
value: Utils.base64ToString(tag.value),
});
});
return decodedTags;
}
/**
* get amino prefix from public key encode type.
* @param public key encode type
* @returns UintArray
*/
static getAminoPrefix(prefix:string):Uint8Array{
let b:any = Array.from(Buffer.from(SHA256(prefix).toString(),'hex'));
while (b[0] === 0) {
b = b.slice(1)
}
b = b.slice(3);
while (b[0] === 0) {
b = b.slice(1)
}
b = b.slice(0, 4);
return b;
}
}