Added docstrings and functions for testing Uint8Array equality
This commit is contained in:
@@ -785,6 +785,7 @@ export {
|
|||||||
I32,
|
I32,
|
||||||
F32,
|
F32,
|
||||||
F64,
|
F64,
|
||||||
|
type TypedArray,
|
||||||
type Encoding,
|
type Encoding,
|
||||||
type HexEncoding,
|
type HexEncoding,
|
||||||
type Base64Encoding,
|
type Base64Encoding,
|
||||||
|
|||||||
195
src/utils.ts
195
src/utils.ts
@@ -1,49 +1,121 @@
|
|||||||
|
// TypedArray is a union type of all the standard typed array types in JavaScript. This allows us to write
|
||||||
|
// functions that can accept any typed array or an ArrayBuffer and convert it to the appropriate view.
|
||||||
export type TypedArray = Int8Array | Uint8ClampedArray | Uint8Array | Uint16Array | Int16Array | Uint32Array | Int32Array;
|
export type TypedArray = Int8Array | Uint8ClampedArray | Uint8Array | Uint16Array | Int16Array | Uint32Array | Int32Array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given ArrayBuffer or TypedArray to a Uint8Array.
|
||||||
|
*
|
||||||
|
* @param arr The ArrayBuffer or TypedArray to convert.
|
||||||
|
* @returns A Uint8Array representing the same data.
|
||||||
|
*/
|
||||||
export const U8 = (arr: ArrayBuffer | TypedArray): Uint8Array => {
|
export const U8 = (arr: ArrayBuffer | TypedArray): Uint8Array => {
|
||||||
if (arr instanceof ArrayBuffer) return new Uint8Array(arr);
|
if (arr instanceof ArrayBuffer) return new Uint8Array(arr);
|
||||||
return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
|
return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given ArrayBuffer or TypedArray to an Int8Array.
|
||||||
|
*
|
||||||
|
* @param arr The ArrayBuffer or TypedArray to convert.
|
||||||
|
* @returns An Int8Array representing the same data.
|
||||||
|
*/
|
||||||
export const I8 = (arr: ArrayBuffer | TypedArray): Int8Array => {
|
export const I8 = (arr: ArrayBuffer | TypedArray): Int8Array => {
|
||||||
if (arr instanceof ArrayBuffer) return new Int8Array(arr);
|
if (arr instanceof ArrayBuffer) return new Int8Array(arr);
|
||||||
return new Int8Array(arr.buffer, arr.byteOffset, arr.byteLength);
|
return new Int8Array(arr.buffer, arr.byteOffset, arr.byteLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given ArrayBuffer or TypedArray to a Uint16Array. If a TypedArray is provided,
|
||||||
|
* the byte length is adjusted to account for the 2 bytes per value in a Uint16Array.
|
||||||
|
*
|
||||||
|
* @param arr The ArrayBuffer or TypedArray to convert.
|
||||||
|
* @returns A Uint16Array representing the same data.
|
||||||
|
*/
|
||||||
export const U16 = (arr: ArrayBuffer | TypedArray): Uint16Array => {
|
export const U16 = (arr: ArrayBuffer | TypedArray): Uint16Array => {
|
||||||
if (arr instanceof ArrayBuffer) return new Uint16Array(arr);
|
if (arr instanceof ArrayBuffer) return new Uint16Array(arr);
|
||||||
return new Uint16Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 2));
|
return new Uint16Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given ArrayBuffer or TypedArray to an Int16Array. If a TypedArray is provided,
|
||||||
|
* the byte length is adjusted to account for the 2 bytes per value in an Int16Array.
|
||||||
|
*
|
||||||
|
* @param arr The ArrayBuffer or TypedArray to convert.
|
||||||
|
* @returns An Int16Array representing the same data.
|
||||||
|
*/
|
||||||
export const I16 = (arr: ArrayBuffer | TypedArray): Int16Array => {
|
export const I16 = (arr: ArrayBuffer | TypedArray): Int16Array => {
|
||||||
if (arr instanceof ArrayBuffer) return new Int16Array(arr);
|
if (arr instanceof ArrayBuffer) return new Int16Array(arr);
|
||||||
return new Int16Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 2));
|
return new Int16Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given ArrayBuffer or TypedArray to a Uint32Array. If a TypedArray is provided,
|
||||||
|
* the byte length is adjusted to account for the 4 bytes per value in a Uint32Array.
|
||||||
|
*
|
||||||
|
* @param arr The ArrayBuffer or TypedArray to convert.
|
||||||
|
* @returns A Uint32Array representing the same data.
|
||||||
|
*/
|
||||||
export const U32 = (arr: ArrayBuffer | TypedArray): Uint32Array => {
|
export const U32 = (arr: ArrayBuffer | TypedArray): Uint32Array => {
|
||||||
if (arr instanceof ArrayBuffer) return new Uint32Array(arr);
|
if (arr instanceof ArrayBuffer) return new Uint32Array(arr);
|
||||||
return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given ArrayBuffer or TypedArray to an Int32Array. If a TypedArray is provided,
|
||||||
|
* the byte length is adjusted to account for the 4 bytes per value in an Int32Array.
|
||||||
|
*
|
||||||
|
* @param arr The ArrayBuffer or TypedArray to convert.
|
||||||
|
* @returns An Int32Array representing the same data.
|
||||||
|
*/
|
||||||
export const I32 = (arr: ArrayBuffer | TypedArray): Int32Array => {
|
export const I32 = (arr: ArrayBuffer | TypedArray): Int32Array => {
|
||||||
if (arr instanceof ArrayBuffer) return new Int32Array(arr);
|
if (arr instanceof ArrayBuffer) return new Int32Array(arr);
|
||||||
return new Int32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
return new Int32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given ArrayBuffer or TypedArray to a Float32Array. If a TypedArray is provided,
|
||||||
|
* the byte length is adjusted to account for the 4 bytes per value in a Float32Array.
|
||||||
|
*
|
||||||
|
* @param arr The ArrayBuffer or TypedArray to convert.
|
||||||
|
* @returns A Float32Array representing the same data.
|
||||||
|
*/
|
||||||
export const F32 = (arr: ArrayBuffer | TypedArray): Float32Array => {
|
export const F32 = (arr: ArrayBuffer | TypedArray): Float32Array => {
|
||||||
if (arr instanceof ArrayBuffer) return new Float32Array(arr);
|
if (arr instanceof ArrayBuffer) return new Float32Array(arr);
|
||||||
return new Float32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
return new Float32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given ArrayBuffer or TypedArray to a Float64Array. If a TypedArray is provided,
|
||||||
|
* the byte length is adjusted to account for the 8 bytes per value in a Float64Array.
|
||||||
|
*
|
||||||
|
* @param arr The ArrayBuffer or TypedArray to convert.
|
||||||
|
* @returns A Float64Array representing the same data.
|
||||||
|
*/
|
||||||
export const F64 = (arr: ArrayBuffer | TypedArray): Float64Array => {
|
export const F64 = (arr: ArrayBuffer | TypedArray): Float64Array => {
|
||||||
if (arr instanceof ArrayBuffer) return new Float64Array(arr);
|
if (arr instanceof ArrayBuffer) return new Float64Array(arr);
|
||||||
return new Float64Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 8));
|
return new Float64Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether the provided value is a Uint8Array or a view of an ArrayBuffer that is a
|
||||||
|
* Uint8Array. This is used to validate that a given value can be treated as raw bytes for
|
||||||
|
* encoding/decoding operations.
|
||||||
|
*
|
||||||
|
* @param bytes The value to test.
|
||||||
|
* @returns True if the value is a Uint8Array or a view of an ArrayBuffer that is a Uint8Array, false otherwise.
|
||||||
|
*/
|
||||||
export const isBytes = (bytes: unknown): boolean => {
|
export const isBytes = (bytes: unknown): boolean => {
|
||||||
return bytes instanceof Uint8Array || (ArrayBuffer.isView(bytes) && bytes.constructor.name === "Uint8Array");
|
return bytes instanceof Uint8Array || (ArrayBuffer.isView(bytes) && bytes.constructor.name === "Uint8Array");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the provided value is a Uint8Array of the specified length (if provided). Throws a
|
||||||
|
* TypeError if the assertion fails.
|
||||||
|
*
|
||||||
|
* @param bytes The value to check.
|
||||||
|
* @param length The expected length of the Uint8Array.
|
||||||
|
* @param name The name of the variable being checked (for error messages).
|
||||||
|
*/
|
||||||
export const assertBytes = (bytes: Uint8Array, length?: number, name: string = ""): void => {
|
export const assertBytes = (bytes: Uint8Array, length?: number, name: string = ""): void => {
|
||||||
const valid = isBytes(bytes);
|
const valid = isBytes(bytes);
|
||||||
const sized = (typeof length !== 'undefined') ? (bytes.byteLength === length) : true;
|
const sized = (typeof length !== 'undefined') ? (bytes.byteLength === length) : true;
|
||||||
@@ -54,6 +126,48 @@ export const assertBytes = (bytes: Uint8Array, length?: number, name: string = "
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for equality of two Uint8Arrays. Returns true if the arrays are equal, false otherwise.
|
||||||
|
*
|
||||||
|
* Note: This function is not designed to be resistant to timing attacks. For security-sensitive
|
||||||
|
* comparisons (e.g., cryptographic keys, hashes), use `constantTimeEqualBytes` instead.
|
||||||
|
*
|
||||||
|
* @param a The first Uint8Array to compare.
|
||||||
|
* @param b The second Uint8Array to compare.
|
||||||
|
* @returns True if the arrays are equal, false otherwise.
|
||||||
|
*/
|
||||||
|
export const equalBytes = (a: Uint8Array, b: Uint8Array): boolean => {
|
||||||
|
if (a.byteLength !== b.byteLength) return false;
|
||||||
|
for (let i = 0; i < a.byteLength; i++) {
|
||||||
|
if (a[i] !== b[i]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a constant-time comparison of two Uint8Arrays to prevent timing attacks. Returns true
|
||||||
|
* if the arrays are equal, false otherwise.
|
||||||
|
*
|
||||||
|
* This function should be used for security-sensitive comparisons (e.g., cryptographic keys,
|
||||||
|
* hashes) to mitigate timing attacks. For general use where performance is a concern and security
|
||||||
|
* is not an issue, `equalBytes` may be more efficient.
|
||||||
|
*
|
||||||
|
* NB: This function is not truly constant-time in JavaScript due to the nature of the language and
|
||||||
|
* runtime, but it is designed to minimize timing differences based on the content of the arrays.
|
||||||
|
*
|
||||||
|
* @param a The first Uint8Array to compare.
|
||||||
|
* @param b The second Uint8Array to compare.
|
||||||
|
* @returns True if the arrays are equal, false otherwise.
|
||||||
|
*/
|
||||||
|
export const constantTimeEqualBytes = (a: Uint8Array, b: Uint8Array): boolean => {
|
||||||
|
if (a.byteLength !== b.byteLength) return false;
|
||||||
|
let result = 0;
|
||||||
|
for (let i = 0; i < a.byteLength; i++) {
|
||||||
|
result |= a[i] ^ b[i];
|
||||||
|
}
|
||||||
|
return result === 0;
|
||||||
|
}
|
||||||
|
|
||||||
const hasHexMethods = (() =>
|
const hasHexMethods = (() =>
|
||||||
// @ts-ignore testing for builtins
|
// @ts-ignore testing for builtins
|
||||||
'toHex' in Uint8Array.from([]) && typeof Uint8Array.from([])['toHex'] === 'function' &&
|
'toHex' in Uint8Array.from([]) && typeof Uint8Array.from([])['toHex'] === 'function' &&
|
||||||
@@ -71,8 +185,25 @@ export type Base64URLEncoding = 'base64url';
|
|||||||
export type Base64RawURLEncoding = 'base64urlraw';
|
export type Base64RawURLEncoding = 'base64urlraw';
|
||||||
export type Encoding = HexEncoding | Base64Encoding | Base64RawEncoding | Base64URLEncoding | Base64RawURLEncoding;
|
export type Encoding = HexEncoding | Base64Encoding | Base64RawEncoding | Base64URLEncoding | Base64RawURLEncoding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a Uint8Array of bytes to a hexadecimal string. If the environment supports built-in
|
||||||
|
* hex methods on Uint8Array, those will be used for better performance. Otherwise, a manual
|
||||||
|
* conversion is performed.
|
||||||
|
*
|
||||||
|
* If a length is provided, the function will assert that the input bytes have the expected
|
||||||
|
* length (in bytes). The length parameter is optional and can be used to enforce that the
|
||||||
|
* input bytes match an expected size for encoding/decoding operations.
|
||||||
|
*
|
||||||
|
* @param bytes The Uint8Array of bytes to convert to a hexadecimal string.
|
||||||
|
* @param length Optional expected length of the input bytes (in bytes).
|
||||||
|
* @returns The hexadecimal string representation of the input bytes.
|
||||||
|
*/
|
||||||
export const bytesToHex = (bytes: Uint8Array, length?: number): string => {
|
export const bytesToHex = (bytes: Uint8Array, length?: number): string => {
|
||||||
assertBytes(bytes, (typeof length !== 'undefined') ? length * 2 : undefined, "bytes");
|
assertBytes(bytes, (typeof length !== 'undefined') ? length * 2 : undefined, "bytes");
|
||||||
|
if (hasHexMethods) {
|
||||||
|
// @ts-ignore using built-in hex methods if available
|
||||||
|
return bytes.toHex();
|
||||||
|
}
|
||||||
let hex = '';
|
let hex = '';
|
||||||
for (const byte of bytes) {
|
for (const byte of bytes) {
|
||||||
hex += hexes[byte];
|
hex += hexes[byte];
|
||||||
@@ -80,6 +211,14 @@ export const bytesToHex = (bytes: Uint8Array, length?: number): string => {
|
|||||||
return hex;
|
return hex;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a hexadecimal string to a Uint8Array of bytes. If the environment supports built-in
|
||||||
|
* hex methods on Uint8Array, those will be used for better performance. Otherwise, a manual
|
||||||
|
* conversion is performed.
|
||||||
|
*
|
||||||
|
* @param hex The hexadecimal string to convert to a Uint8Array of bytes.
|
||||||
|
* @returns The Uint8Array of bytes represented by the hexadecimal string.
|
||||||
|
*/
|
||||||
export const hexToBytes = (hex: string): Uint8Array => {
|
export const hexToBytes = (hex: string): Uint8Array => {
|
||||||
if (typeof hex !== 'string' || hex.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(hex)) {
|
if (typeof hex !== 'string' || hex.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(hex)) {
|
||||||
throw new TypeError(`Expected hex string of even length, got ${hex}`);
|
throw new TypeError(`Expected hex string of even length, got ${hex}`);
|
||||||
@@ -96,6 +235,20 @@ export const hexToBytes = (hex: string): Uint8Array => {
|
|||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a Uint8Array of bytes to a base64 string using the specified encoding variant. The function
|
||||||
|
* asserts that the input is a valid Uint8Array and optionally checks for an expected length (in bytes).
|
||||||
|
*
|
||||||
|
* The `encoding` parameter specifies the base64 variant to use:
|
||||||
|
* - 'base64': Standard base64 encoding with padding.
|
||||||
|
* - 'base64raw': Standard base64 encoding without padding.
|
||||||
|
* - 'base64url': URL-safe base64 encoding with padding (replaces '+' with '-' and '/' with '_').
|
||||||
|
* - 'base64urlraw': URL-safe base64 encoding without padding.
|
||||||
|
*
|
||||||
|
* @param bytes The Uint8Array of bytes to convert to a base64 string.
|
||||||
|
* @param encoding The base64 encoding variant to use.
|
||||||
|
* @returns The base64 string representation of the input bytes.
|
||||||
|
*/
|
||||||
export const bytesToBase64 = (bytes: Uint8Array, encoding: Base64Encoding | Base64RawEncoding | Base64URLEncoding | Base64RawURLEncoding): string => {
|
export const bytesToBase64 = (bytes: Uint8Array, encoding: Base64Encoding | Base64RawEncoding | Base64URLEncoding | Base64RawURLEncoding): string => {
|
||||||
assertBytes(bytes, (typeof length !== 'undefined') ? length * 2 : undefined, "bytes");
|
assertBytes(bytes, (typeof length !== 'undefined') ? length * 2 : undefined, "bytes");
|
||||||
const binary = String.fromCharCode(...bytes);
|
const binary = String.fromCharCode(...bytes);
|
||||||
@@ -113,6 +266,16 @@ export const bytesToBase64 = (bytes: Uint8Array, encoding: Base64Encoding | Base
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a base64 string to a Uint8Array of bytes. The function accepts both standard and URL-safe
|
||||||
|
* base64 variants, and normalizes the input by replacing URL-safe characters and adding padding if necessary.
|
||||||
|
*
|
||||||
|
* The function asserts that the input is a valid base64 string after normalization. It then decodes the
|
||||||
|
* base64 string to binary and converts it to a Uint8Array of bytes.
|
||||||
|
*
|
||||||
|
* @param b64 The base64 string to convert to a Uint8Array of bytes.
|
||||||
|
* @returns The Uint8Array of bytes represented by the base64 string.
|
||||||
|
*/
|
||||||
export const base64ToBytes = (b64: string): Uint8Array => {
|
export const base64ToBytes = (b64: string): Uint8Array => {
|
||||||
if (typeof b64 !== "string") {
|
if (typeof b64 !== "string") {
|
||||||
throw new TypeError(`Expected base64 string, got ${b64}`);
|
throw new TypeError(`Expected base64 string, got ${b64}`);
|
||||||
@@ -134,6 +297,22 @@ export const base64ToBytes = (b64: string): Uint8Array => {
|
|||||||
return bytes;
|
return bytes;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes a string encoded in the specified encoding variant to a Uint8Array of bytes. The function
|
||||||
|
* supports the following encoding variants:
|
||||||
|
* - 'hex': Hexadecimal encoding (e.g., "deadbeef").
|
||||||
|
* - 'base64': Standard base64 encoding with padding.
|
||||||
|
* - 'base64raw': Standard base64 encoding without padding.
|
||||||
|
* - 'base64url': URL-safe base64 encoding with padding (replaces '+' with '-' and '/' with '_').
|
||||||
|
* - 'base64urlraw': URL-safe base64 encoding without padding.
|
||||||
|
*
|
||||||
|
* The function asserts that the input string is valid for the specified encoding and then decodes
|
||||||
|
* it to a Uint8Array of bytes. If the encoding is not supported, a TypeError is thrown.
|
||||||
|
*
|
||||||
|
* @param encoded The encoded string to decode.
|
||||||
|
* @param encoding The encoding variant of the input string.
|
||||||
|
* @returns The Uint8Array of bytes represented by the encoded string.
|
||||||
|
*/
|
||||||
export const decodeBytes = (encoded: string, encoding: Encoding): Uint8Array => {
|
export const decodeBytes = (encoded: string, encoding: Encoding): Uint8Array => {
|
||||||
switch (encoding) {
|
switch (encoding) {
|
||||||
case 'hex':
|
case 'hex':
|
||||||
@@ -148,6 +327,22 @@ export const decodeBytes = (encoded: string, encoding: Encoding): Uint8Array =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a Uint8Array of bytes to a string using the specified encoding variant. The function supports
|
||||||
|
* the following encoding variants:
|
||||||
|
* - 'hex': Hexadecimal encoding (e.g., "deadbeef").
|
||||||
|
* - 'base64': Standard base64 encoding with padding.
|
||||||
|
* - 'base64raw': Standard base64 encoding without padding.
|
||||||
|
* - 'base64url': URL-safe base64 encoding with padding (replaces '+' with '-' and '/' with '_').
|
||||||
|
* - 'base64urlraw': URL-safe base64 encoding without padding.
|
||||||
|
*
|
||||||
|
* The function asserts that the input is a valid Uint8Array of bytes and then encodes it to a string
|
||||||
|
* using the specified encoding variant. If the encoding is not supported, a TypeError is thrown.
|
||||||
|
*
|
||||||
|
* @param bytes The Uint8Array of bytes to encode.
|
||||||
|
* @param encoding The encoding variant to use.
|
||||||
|
* @returns The encoded string.
|
||||||
|
*/
|
||||||
export const encodeBytes = (bytes: Uint8Array, encoding: Encoding): string => {
|
export const encodeBytes = (bytes: Uint8Array, encoding: Encoding): string => {
|
||||||
switch (encoding) {
|
switch (encoding) {
|
||||||
case 'hex':
|
case 'hex':
|
||||||
|
|||||||
Reference in New Issue
Block a user