diff --git a/bindings/nodejs/lib/utils/converter.ts b/bindings/nodejs/lib/utils/converter.ts new file mode 100644 index 0000000000..9a67d180c3 --- /dev/null +++ b/bindings/nodejs/lib/utils/converter.ts @@ -0,0 +1,104 @@ +/** + * Convert arrays to and from different formats. + */ +export class Converter { + /** + * Lookup table for encoding. + * @internal + */ + private static ENCODE_LOOKUP: string[] | undefined; + + /** + * Lookup table for decoding. + * @internal + */ + private static DECODE_LOOKUP: number[] | undefined; + + /** + * Encode a raw array to hex string. + * @param array The bytes to encode. + * @param includePrefix Include the 0x prefix on the returned hex. + * @param startIndex The index to start in the bytes. + * @param length The length of bytes to read. + * @param reverse Reverse the combine direction. + * @returns The array formated as hex. + */ + public static bytesToHex( + array: ArrayLike, + includePrefix: boolean = false, + startIndex?: number, + length?: number | undefined, + reverse?: boolean, + ): string { + let hex = ''; + this.buildHexLookups(); + if (Converter.ENCODE_LOOKUP) { + const len = length ?? array.length; + const start = startIndex ?? 0; + if (reverse) { + for (let i = 0; i < len; i++) { + hex = Converter.ENCODE_LOOKUP[array[start + i]] + hex; + } + } else { + for (let i = 0; i < len; i++) { + hex += Converter.ENCODE_LOOKUP[array[start + i]]; + } + } + } + return includePrefix ? hex.replace(/^0x/, '') : hex; + } + + /** + * Decode a hex string to raw array. + * @param hex The hex to decode. + * @param reverse Store the characters in reverse. + * @returns The array. + */ + public static hexToBytes(hex: string, reverse?: boolean): Uint8Array { + const strippedHex = hex.replace(/^0x/, ''); + const sizeof = strippedHex.length >> 1; + const length = sizeof << 1; + const array = new Uint8Array(sizeof); + + this.buildHexLookups(); + if (Converter.DECODE_LOOKUP) { + let i = 0; + let n = 0; + while (i < length) { + array[n++] = + (Converter.DECODE_LOOKUP[strippedHex.charCodeAt(i++)] << + 4) | + Converter.DECODE_LOOKUP[strippedHex.charCodeAt(i++)]; + } + + if (reverse) { + array.reverse(); + } + } + return array; + } + + /** + * Build the static lookup tables. + * @internal + */ + private static buildHexLookups(): void { + if (!Converter.ENCODE_LOOKUP) { + const alphabet = '0123456789abcdef'; + Converter.ENCODE_LOOKUP = []; + Converter.DECODE_LOOKUP = []; + + for (let i = 0; i < 256; i++) { + Converter.ENCODE_LOOKUP[i] = + alphabet[(i >> 4) & 0xf] + alphabet[i & 0xf]; + if (i < 16) { + if (i < 10) { + Converter.DECODE_LOOKUP[0x30 + i] = i; + } else { + Converter.DECODE_LOOKUP[0x61 - 10 + i] = i; + } + } + } + } + } +} diff --git a/bindings/nodejs/lib/utils/index.ts b/bindings/nodejs/lib/utils/index.ts index c15676adf4..296753ff95 100644 --- a/bindings/nodejs/lib/utils/index.ts +++ b/bindings/nodejs/lib/utils/index.ts @@ -4,3 +4,34 @@ export * from './utf8'; export * from './utils'; export * from '../types/utils'; + +/** + * Converts a byte array to a hexadecimal string. + * + * @param {Uint8Array} byteArray - The bytes to encode. + * @param {boolean} [prefix=false] - Whether to include the '0x' prefix in the resulting hexadecimal string. + * @returns {string} The hexadecimal representation of the input byte array. + */ +export const bytesToHex = (bytes: ArrayLike, prefix = false) => { + const hexArray = Array.from(bytes, (byte) => + byte.toString(16).padStart(2, '0'), + ); + const hexString = hexArray.join(''); + return prefix ? '0x' + hexString : hexString; +}; + +/** + * Converts a hexadecimal string to a Uint8Array byte array. + * + * @param {string} hexString - The hexadecimal string to be converted. + * @returns {Uint8Array} The Uint8Array byte array representation of the input hexadecimal string. + * @throws {Error} Will throw an error if the input string is not a valid hexadecimal string. + */ +export const hexToBytes = (hexString: string) => { + const hex = hexString.replace(/^0x/, ''); + const bytes = []; + for (let i = 0; i < hex.length; i += 2) { + bytes.push(parseInt(hex.substr(i, 2), 16)); + } + return new Uint8Array(bytes); +}; diff --git a/bindings/nodejs/lib/utils/utf8.ts b/bindings/nodejs/lib/utils/utf8.ts index 905659d104..b01e6151c2 100644 --- a/bindings/nodejs/lib/utils/utf8.ts +++ b/bindings/nodejs/lib/utils/utf8.ts @@ -11,7 +11,7 @@ export const utf8ToBytes = (utf8: string) => { /** Convert hex encoded string to UTF8 string */ export const hexToUtf8 = (hex: HexEncodedString) => - decodeURIComponent(hex.replace('0x', '').replace(/[0-9a-f]{2}/g, '%$&')); + decodeURIComponent(hex.replace(/^0x/, '').replace(/[0-9a-f]{2}/g, '%$&')); /** Convert UTF8 string to hex encoded string */ export const utf8ToHex = (utf8: string) =>