UNPKG

mcps-sdk-js

Version:

MCPS JavaScript SDK

307 lines (283 loc) 8.19 kB
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; } }