UNPKG

@kazeblockchain/kazejs

Version:

Javascript libraries for Kaze wallet

476 lines (430 loc) 12.2 kB
import hexEncoding from 'crypto-js/enc-hex' import SHA256 from 'crypto-js/sha256' import RIPEMD160 from 'crypto-js/ripemd160' import BN from 'bignumber.js' import util from 'util' /** * @param {arrayBuffer} buf * @returns {string} ASCII string */ export const ab2str = buf => String.fromCharCode.apply(null, new Uint8Array(buf)) /** * @param {string} str - ASCII string * @returns {arrayBuffer} */ export const str2ab = str => { if (typeof str !== 'string') { throw new Error('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 } /** * @param {string} str - HEX string * @returns {number[]} */ export const hexstring2ab = str => { ensureHex(str) if (!str.length) return new Uint8Array() const iters = str.length / 2 const result = new Uint8Array(iters) for (let i = 0; i < iters; i++) { result[i] = parseInt(str.substring(0, 2), 16) str = str.substring(2) } return result } /** * @param {arrayBuffer} arr * @returns {string} HEX string */ export const ab2hexstring = arr => { if (typeof arr !== 'object') { throw new Error('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 } /** * @param {string} str - ASCII string * @returns {string} HEX string */ export const str2hexstring = str => ab2hexstring(str2ab(str)) /** * @param {string} hexstring - HEX string * @returns {string} ASCII string */ export const hexstring2str = hexstring => ab2str(hexstring2ab(hexstring)) /** * convert an integer to big endian hex and add leading zeros * @param {number} num * @returns {string} */ export const int2hex = num => { if (typeof num !== 'number') { throw new Error('int2hex expects a number') } let 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 {number} num * @param {number} size - The required size in bytes, eg 1 for Uint8, 2 for Uint16. Defaults to 1. * @param {boolean} littleEndian - Encode the hex in little endian form * @return {string} */ export const num2hexstring = (num, size = 1, littleEndian = false) => { if (typeof num !== 'number') throw new Error('num must be numeric') if (num < 0) throw new RangeError('num is unsigned (>= 0)') if (size % 1 !== 0) throw new Error('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 = reverseHex(hexstring) return hexstring } /** * Converts a number to a Fixed8 format hex string * @param {number} num * @param {number} size output size in bytes * @return {string} number in Fixed8 representation. */ export const num2fixed8 = (num, size = 8) => { if (typeof num !== 'number') throw new Error('num must be numeric') if (size % 1 !== 0) throw new Error('size must be a whole integer') return new Fixed8(num.toFixed(8)).toReverseHex().slice(0, size * 2) } /** * Converts a Fixed8 hex string to its original number * @param {string} fixed8hex - number in Fixed8 representation * @return {number} */ export const fixed82num = (fixed8hex) => { ensureHex(fixed8hex) if (fixed8hex === '') return 0 return Fixed8.fromReverseHex(fixed8hex).toNumber() } /** * Converts a number to a variable length Int. Used for array length header * @param {number} num - The number * @returns {string} hexstring of the variable Int. */ export const num2VarInt = (num) => { if (num < 0xfd) { return num2hexstring(num) } else if (num <= 0xffff) { // uint16 return 'fd' + num2hexstring(num, 2, true) } else if (num <= 0xffffffff) { // uint32 return 'fe' + num2hexstring(num, 4, true) } else { // uint64 return 'ff' + num2hexstring(num, 8, true) } } /** * XORs two hexstrings * @param {string} str1 - HEX string * @param {string} str2 - HEX string * @returns {string} XOR output as a HEX string */ export const hexXor = (str1, str2) => { ensureHex(str1) ensureHex(str2) if (str1.length !== str2.length) throw new Error('strings are disparate lengths') const result = [] for (let i = 0; i < str1.length; i += 2) { result.push(parseInt(str1.substr(i, 2), 16) ^ parseInt(str2.substr(i, 2), 16)) } return ab2hexstring(result) } /** * Reverses an array. Accepts arrayBuffer. * @param {Array} arr * @returns {Uint8Array} */ export const reverseArray = arr => { if (typeof arr !== 'object' || !arr.length) throw new Error('reverseArray expects an array') let 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 {string} hex - HEX string * @return {string} HEX string reversed in 2s. */ export const reverseHex = hex => { ensureHex(hex) let out = '' for (let i = hex.length - 2; i >= 0; i -= 2) { out += hex.substr(i, 2) } return out } const hexRegex = /^([0-9A-Fa-f]{2})*$/ /** * Checks if input is a hexstring. Empty string is considered a hexstring. * @example * isHex('0101') = true * isHex('') = true * isHex('0x01') = false * @param {string} str * @return {boolean} */ export const isHex = str => { try { return hexRegex.test(str) } catch (err) { return false } } /** * Throws an error if input is not hexstring. * @param {string} str */ export const ensureHex = str => { if (!isHex(str)) throw new Error(`Expected a hexstring but got ${str}`) } /** * @class StringStream * @classdesc A simple string stream that allows user to read a string byte by byte using read(). * @param {string} str - The string to read as a stream. */ export class StringStream { constructor (str = '') { this.str = str this.pter = 0 } /** * Checks if reached the end of the stream. Does not mean stream is actually empty (this.str is not empty) * @returns {boolean} */ isEmpty () { return this.pter >= this.str.length } /** * Peek at the next bytes on the string. May return less than intended bytes if reaching end of stream. * @param {number} bytes * @return {string} */ peek (bytes = 1) { if (this.isEmpty()) return '' return this.substr(this.ptr, bytes * 2) } /** * Reads some bytes off the stream. * @param {number} bytes - Number of bytes to read * @returns {string} */ read (bytes = 1) { if (this.isEmpty()) throw new Error() const out = this.str.substr(this.pter, bytes * 2) this.pter += bytes * 2 return out } /** * Reads some bytes off the stream using the first byte as the length indicator. * @return {string} */ readVarBytes () { return this.read(this.readVarInt()) } /** * Reads a variable Int. * @returns {number} */ readVarInt () { let len = parseInt(this.read(1), 16) if (len === 0xfd) len = parseInt(reverseHex(this.read(2)), 16) else if (len === 0xfe) len = parseInt(reverseHex(this.read(4)), 16) else if (len === 0xff) len = parseInt(reverseHex(this.read(8)), 16) return len } /** * Resets the pointer to start of string. */ reset () { this.pter = 0 } } /** * Performs a SHA256 followed by a RIPEMD160. * @param {string} hex - String to hash * @returns {string} hash output */ export const hash160 = (hex) => { if (typeof hex !== 'string') throw new Error('reverseHex expects a string') if (hex.length % 2 !== 0) throw new Error(`Incorrect Length: ${hex}`) let hexEncoded = hexEncoding.parse(hex) let ProgramSha256 = SHA256(hexEncoded) return RIPEMD160(ProgramSha256).toString() } /** * Performs 2 SHA256. * @param {string} hex - String to hash * @returns {string} hash output */ export const hash256 = (hex) => { if (typeof hex !== 'string') throw new Error('reverseHex expects a string') if (hex.length % 2 !== 0) throw new Error(`Incorrect Length: ${hex}`) let hexEncoded = hexEncoding.parse(hex) let ProgramSha256 = SHA256(hexEncoded) return SHA256(ProgramSha256).toString() } /** * Performs a single SHA256. * @param {string} hex - String to hash * @returns {string} hash output */ export const sha256 = (hex) => { if (typeof hex !== 'string') throw new Error('reverseHex expects a string') if (hex.length % 2 !== 0) throw new Error(`Incorrect Length: ${hex}`) let hexEncoded = hexEncoding.parse(hex) return SHA256(hexEncoded).toString() } /** * @class Fixed8 * @classdesc A wrapper around bignumber.js that adds on helper methods commonly used in Kazejs * @param {string|int} value * @param {number} [base] */ export class Fixed8 extends BN { constructor (input, base = undefined) { if (typeof input === 'number') input = input.toFixed(8) super(input, base) } toHex () { const hexstring = this.times(100000000).round(0).toString(16) return '0'.repeat(16 - hexstring.length) + hexstring } toReverseHex () { return reverseHex(this.toHex()) } [util.inspect.custom] (depth, opts) { return this.toFixed(8) } static fromHex (hex) { return new Fixed8(hex, 16).div(100000000) } static fromReverseHex (hex) { return this.fromHex(reverseHex(hex)) } /** * Returns a Fixed8 whose value is rounded upwards to the next whole number. * @return {Fixed8} */ ceil () { return new Fixed8(super.ceil()) } /** * Returns a Fixed8 whose value is rounded downwards to the previous whole number. * @return {Fixed8} */ floor () { return new Fixed8(super.floor()) } /** * Returns a Fixed8 rounded to the nearest dp decimal places according to rounding mode rm. * If dp is null, round to whole number. * If rm is null, round according to default rounding mode. * @param {number} [dp] * @param {number} [rm] * @return {Fixed8} */ round (dp = null, rm = null) { return new Fixed8(super.round(dp, rm)) } /** * See [[dividedBy]] * @param {string|number|Fixed8} * @param {number} [base] * @return {Fixed8} */ div (n, base = null) { return this.dividedBy(n, base) } /** * Returns a Fixed8 whose value is the value of this Fixed8 divided by `n` * @param {string|number|Fixed8} * @param {number} [base] * @return {Fixed8} * @alias [[div]] */ dividedBy (n, base = null) { return new Fixed8(super.dividedBy(n, base)) } /** * See [[times]] * @param {string|number|Fixed8} * @param {number} [base] * @return {Fixed8} */ mul (n, base = null) { return this.times(n, base) } /** * Returns a Fixed8 whose value is the value of this Fixed8 multipled by `n` * @param {string|number|Fixed8} * @param {number} [base] * @return {Fixed8} * @alias [[mul]] */ times (n, base = null) { return new Fixed8(super.times(n, base)) } /** * See [[plus]] * @param {string|number|Fixed8} * @param {number} [base] * @return {Fixed8} */ add (n, base = null) { return this.plus(n, base) } /** * Returns a Fixed8 whose value is the value of this Fixed8 plus `n` * @param {string|number|Fixed8} * @param {number} [base] * @return {Fixed8} * @alias [[add]] */ plus (n, base = null) { return new Fixed8(super.plus(n, base)) } /** * See [[minus]] * @param {string|number|Fixed8} * @param {number} [base] * @return {Fixed8} */ sub (n, base = null) { return this.minus(n, base) } /** * Returns a Fixed8 whose value is the value of this Fixed8 minus `n` * @param {string|number|Fixed8} * @param {number} [base] * @return {Fixed8} * @alias [[sub]] */ minus (n, base = null) { return new Fixed8(super.minus(n, base)) } }