UNPKG

ox

Version:

Ethereum Standard Library

666 lines 18.4 kB
import { equalBytes } from '@noble/curves/abstract/utils'; import * as Errors from './Errors.js'; import * as Hex from './Hex.js'; import * as internal from './internal/bytes.js'; import * as internal_hex from './internal/hex.js'; import * as Json from './Json.js'; const decoder = /*#__PURE__*/ new TextDecoder(); const encoder = /*#__PURE__*/ new TextEncoder(); /** * Asserts if the given value is {@link ox#Bytes.Bytes}. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * Bytes.assert('abc') * // @error: Bytes.InvalidBytesTypeError: * // @error: Value `"abc"` of type `string` is an invalid Bytes value. * // @error: Bytes values must be of type `Uint8Array`. * ``` * * @param value - Value to assert. */ export function assert(value) { if (value instanceof Uint8Array) return; if (!value) throw new InvalidBytesTypeError(value); if (typeof value !== 'object') throw new InvalidBytesTypeError(value); if (!('BYTES_PER_ELEMENT' in value)) throw new InvalidBytesTypeError(value); if (value.BYTES_PER_ELEMENT !== 1 || value.constructor.name !== 'Uint8Array') throw new InvalidBytesTypeError(value); } /** * Concatenates two or more {@link ox#Bytes.Bytes}. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * const bytes = Bytes.concat( * Bytes.from([1]), * Bytes.from([69]), * Bytes.from([420, 69]), * ) * // @log: Uint8Array [ 1, 69, 420, 69 ] * ``` * * @param values - Values to concatenate. * @returns Concatenated {@link ox#Bytes.Bytes}. */ export function concat(...values) { let length = 0; for (const arr of values) { length += arr.length; } const result = new Uint8Array(length); for (let i = 0, index = 0; i < values.length; i++) { const arr = values[i]; result.set(arr, index); index += arr.length; } return result; } /** * Instantiates a {@link ox#Bytes.Bytes} value from a `Uint8Array`, a hex string, or an array of unsigned 8-bit integers. * * :::tip * * To instantiate from a **Boolean**, **String**, or **Number**, use one of the following: * * - `Bytes.fromBoolean` * * - `Bytes.fromString` * * - `Bytes.fromNumber` * * ::: * * @example * ```ts twoslash * // @noErrors * import { Bytes } from 'ox' * * const data = Bytes.from([255, 124, 5, 4]) * // @log: Uint8Array([255, 124, 5, 4]) * * const data = Bytes.from('0xdeadbeef') * // @log: Uint8Array([222, 173, 190, 239]) * ``` * * @param value - Value to convert. * @returns A {@link ox#Bytes.Bytes} instance. */ export function from(value) { if (value instanceof Uint8Array) return value; if (typeof value === 'string') return fromHex(value); return fromArray(value); } /** * Converts an array of unsigned 8-bit integers into {@link ox#Bytes.Bytes}. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * const data = Bytes.fromArray([255, 124, 5, 4]) * // @log: Uint8Array([255, 124, 5, 4]) * ``` * * @param value - Value to convert. * @returns A {@link ox#Bytes.Bytes} instance. */ export function fromArray(value) { return value instanceof Uint8Array ? value : new Uint8Array(value); } /** * Encodes a boolean value into {@link ox#Bytes.Bytes}. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * const data = Bytes.fromBoolean(true) * // @log: Uint8Array([1]) * ``` * * @example * ```ts twoslash * import { Bytes } from 'ox' * * const data = Bytes.fromBoolean(true, { size: 32 }) * // @log: Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]) * ``` * * @param value - Boolean value to encode. * @param options - Encoding options. * @returns Encoded {@link ox#Bytes.Bytes}. */ export function fromBoolean(value, options = {}) { const { size } = options; const bytes = new Uint8Array(1); bytes[0] = Number(value); if (typeof size === 'number') { internal.assertSize(bytes, size); return padLeft(bytes, size); } return bytes; } /** * Encodes a {@link ox#Hex.Hex} value into {@link ox#Bytes.Bytes}. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * const data = Bytes.fromHex('0x48656c6c6f20776f726c6421') * // @log: Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]) * ``` * * @example * ```ts twoslash * import { Bytes } from 'ox' * * const data = Bytes.fromHex('0x48656c6c6f20776f726c6421', { size: 32 }) * // @log: Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) * ``` * * @param value - {@link ox#Hex.Hex} value to encode. * @param options - Encoding options. * @returns Encoded {@link ox#Bytes.Bytes}. */ export function fromHex(value, options = {}) { const { size } = options; let hex = value; if (size) { internal_hex.assertSize(value, size); hex = Hex.padRight(value, size); } let hexString = hex.slice(2); if (hexString.length % 2) hexString = `0${hexString}`; const length = hexString.length / 2; const bytes = new Uint8Array(length); for (let index = 0, j = 0; index < length; index++) { const nibbleLeft = internal.charCodeToBase16(hexString.charCodeAt(j++)); const nibbleRight = internal.charCodeToBase16(hexString.charCodeAt(j++)); if (nibbleLeft === undefined || nibbleRight === undefined) { throw new Errors.BaseError(`Invalid byte sequence ("${hexString[j - 2]}${hexString[j - 1]}" in "${hexString}").`); } bytes[index] = (nibbleLeft << 4) | nibbleRight; } return bytes; } /** * Encodes a number value into {@link ox#Bytes.Bytes}. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * const data = Bytes.fromNumber(420) * // @log: Uint8Array([1, 164]) * ``` * * @example * ```ts twoslash * import { Bytes } from 'ox' * * const data = Bytes.fromNumber(420, { size: 4 }) * // @log: Uint8Array([0, 0, 1, 164]) * ``` * * @param value - Number value to encode. * @param options - Encoding options. * @returns Encoded {@link ox#Bytes.Bytes}. */ export function fromNumber(value, options) { const hex = Hex.fromNumber(value, options); return fromHex(hex); } /** * Encodes a string into {@link ox#Bytes.Bytes}. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * const data = Bytes.fromString('Hello world!') * // @log: Uint8Array([72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33]) * ``` * * @example * ```ts twoslash * import { Bytes } from 'ox' * * const data = Bytes.fromString('Hello world!', { size: 32 }) * // @log: Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) * ``` * * @param value - String to encode. * @param options - Encoding options. * @returns Encoded {@link ox#Bytes.Bytes}. */ export function fromString(value, options = {}) { const { size } = options; const bytes = encoder.encode(value); if (typeof size === 'number') { internal.assertSize(bytes, size); return padRight(bytes, size); } return bytes; } /** * Checks if two {@link ox#Bytes.Bytes} values are equal. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * Bytes.isEqual(Bytes.from([1]), Bytes.from([1])) * // @log: true * * Bytes.isEqual(Bytes.from([1]), Bytes.from([2])) * // @log: false * ``` * * @param bytesA - First {@link ox#Bytes.Bytes} value. * @param bytesB - Second {@link ox#Bytes.Bytes} value. * @returns `true` if the two values are equal, otherwise `false`. */ export function isEqual(bytesA, bytesB) { return equalBytes(bytesA, bytesB); } /** * Pads a {@link ox#Bytes.Bytes} value to the left with zero bytes until it reaches the given `size` (default: 32 bytes). * * @example * ```ts twoslash * import { Bytes } from 'ox' * * Bytes.padLeft(Bytes.from([1]), 4) * // @log: Uint8Array([0, 0, 0, 1]) * ``` * * @param value - {@link ox#Bytes.Bytes} value to pad. * @param size - Size to pad the {@link ox#Bytes.Bytes} value to. * @returns Padded {@link ox#Bytes.Bytes} value. */ export function padLeft(value, size) { return internal.pad(value, { dir: 'left', size }); } /** * Pads a {@link ox#Bytes.Bytes} value to the right with zero bytes until it reaches the given `size` (default: 32 bytes). * * @example * ```ts twoslash * import { Bytes } from 'ox' * * Bytes.padRight(Bytes.from([1]), 4) * // @log: Uint8Array([1, 0, 0, 0]) * ``` * * @param value - {@link ox#Bytes.Bytes} value to pad. * @param size - Size to pad the {@link ox#Bytes.Bytes} value to. * @returns Padded {@link ox#Bytes.Bytes} value. */ export function padRight(value, size) { return internal.pad(value, { dir: 'right', size }); } /** * Generates random {@link ox#Bytes.Bytes} of the specified length. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * const bytes = Bytes.random(32) * // @log: Uint8Array([... x32]) * ``` * * @param length - Length of the random {@link ox#Bytes.Bytes} to generate. * @returns Random {@link ox#Bytes.Bytes} of the specified length. */ export function random(length) { return crypto.getRandomValues(new Uint8Array(length)); } /** * Retrieves the size of a {@link ox#Bytes.Bytes} value. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * Bytes.size(Bytes.from([1, 2, 3, 4])) * // @log: 4 * ``` * * @param value - {@link ox#Bytes.Bytes} value. * @returns Size of the {@link ox#Bytes.Bytes} value. */ export function size(value) { return value.length; } /** * Returns a section of a {@link ox#Bytes.Bytes} value given a start/end bytes offset. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * Bytes.slice( * Bytes.from([1, 2, 3, 4, 5, 6, 7, 8, 9]), * 1, * 4, * ) * // @log: Uint8Array([2, 3, 4]) * ``` * * @param value - The {@link ox#Bytes.Bytes} value. * @param start - Start offset. * @param end - End offset. * @param options - Slice options. * @returns Sliced {@link ox#Bytes.Bytes} value. */ export function slice(value, start, end, options = {}) { const { strict } = options; internal.assertStartOffset(value, start); const value_ = value.slice(start, end); if (strict) internal.assertEndOffset(value_, start, end); return value_; } /** * Decodes a {@link ox#Bytes.Bytes} into a bigint. * * @example * ```ts * import { Bytes } from 'ox' * * Bytes.toBigInt(Bytes.from([1, 164])) * // @log: 420n * ``` * * @param bytes - The {@link ox#Bytes.Bytes} to decode. * @param options - Decoding options. * @returns Decoded bigint. */ export function toBigInt(bytes, options = {}) { const { size } = options; if (typeof size !== 'undefined') internal.assertSize(bytes, size); const hex = Hex.fromBytes(bytes, options); return Hex.toBigInt(hex, options); } /** * Decodes a {@link ox#Bytes.Bytes} into a boolean. * * @example * ```ts * import { Bytes } from 'ox' * * Bytes.toBoolean(Bytes.from([1])) * // @log: true * ``` * * @param bytes - The {@link ox#Bytes.Bytes} to decode. * @param options - Decoding options. * @returns Decoded boolean. */ export function toBoolean(bytes, options = {}) { const { size } = options; let bytes_ = bytes; if (typeof size !== 'undefined') { internal.assertSize(bytes_, size); bytes_ = trimLeft(bytes_); } if (bytes_.length > 1 || bytes_[0] > 1) throw new InvalidBytesBooleanError(bytes_); return Boolean(bytes_[0]); } /** * Encodes a {@link ox#Bytes.Bytes} value into a {@link ox#Hex.Hex} value. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * Bytes.toHex(Bytes.from([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33])) * // '0x48656c6c6f20576f726c6421' * ``` * * @param value - The {@link ox#Bytes.Bytes} to decode. * @param options - Options. * @returns Decoded {@link ox#Hex.Hex} value. */ export function toHex(value, options = {}) { return Hex.fromBytes(value, options); } /** * Decodes a {@link ox#Bytes.Bytes} into a number. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * Bytes.toNumber(Bytes.from([1, 164])) * // @log: 420 * ``` */ export function toNumber(bytes, options = {}) { const { size } = options; if (typeof size !== 'undefined') internal.assertSize(bytes, size); const hex = Hex.fromBytes(bytes, options); return Hex.toNumber(hex, options); } /** * Decodes a {@link ox#Bytes.Bytes} into a string. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * const data = Bytes.toString(Bytes.from([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33])) * // @log: 'Hello world' * ``` * * @param bytes - The {@link ox#Bytes.Bytes} to decode. * @param options - Options. * @returns Decoded string. */ export function toString(bytes, options = {}) { const { size } = options; let bytes_ = bytes; if (typeof size !== 'undefined') { internal.assertSize(bytes_, size); bytes_ = trimRight(bytes_); } return decoder.decode(bytes_); } /** * Trims leading zeros from a {@link ox#Bytes.Bytes} value. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * Bytes.trimLeft(Bytes.from([0, 0, 0, 0, 1, 2, 3])) * // @log: Uint8Array([1, 2, 3]) * ``` * * @param value - {@link ox#Bytes.Bytes} value. * @returns Trimmed {@link ox#Bytes.Bytes} value. */ export function trimLeft(value) { return internal.trim(value, { dir: 'left' }); } /** * Trims trailing zeros from a {@link ox#Bytes.Bytes} value. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * Bytes.trimRight(Bytes.from([1, 2, 3, 0, 0, 0, 0])) * // @log: Uint8Array([1, 2, 3]) * ``` * * @param value - {@link ox#Bytes.Bytes} value. * @returns Trimmed {@link ox#Bytes.Bytes} value. */ export function trimRight(value) { return internal.trim(value, { dir: 'right' }); } /** * Checks if the given value is {@link ox#Bytes.Bytes}. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * Bytes.validate('0x') * // @log: false * * Bytes.validate(Bytes.from([1, 2, 3])) * // @log: true * ``` * * @param value - Value to check. * @returns `true` if the value is {@link ox#Bytes.Bytes}, otherwise `false`. */ export function validate(value) { try { assert(value); return true; } catch { return false; } } /** * Thrown when the bytes value cannot be represented as a boolean. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * Bytes.toBoolean(Bytes.from([5])) * // @error: Bytes.InvalidBytesBooleanError: Bytes value `[5]` is not a valid boolean. * // @error: The bytes array must contain a single byte of either a `0` or `1` value. * ``` */ export class InvalidBytesBooleanError extends Errors.BaseError { constructor(bytes) { super(`Bytes value \`${bytes}\` is not a valid boolean.`, { metaMessages: [ 'The bytes array must contain a single byte of either a `0` or `1` value.', ], }); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'Bytes.InvalidBytesBooleanError' }); } } /** * Thrown when a value cannot be converted to bytes. * * @example * ```ts twoslash * // @noErrors * import { Bytes } from 'ox' * * Bytes.from('foo') * // @error: Bytes.InvalidBytesTypeError: Value `foo` of type `string` is an invalid Bytes value. * ``` */ export class InvalidBytesTypeError extends Errors.BaseError { constructor(value) { super(`Value \`${typeof value === 'object' ? Json.stringify(value) : value}\` of type \`${typeof value}\` is an invalid Bytes value.`, { metaMessages: ['Bytes values must be of type `Bytes`.'], }); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'Bytes.InvalidBytesTypeError' }); } } /** * Thrown when a size exceeds the maximum allowed size. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * Bytes.fromString('Hello World!', { size: 8 }) * // @error: Bytes.SizeOverflowError: Size cannot exceed `8` bytes. Given size: `12` bytes. * ``` */ export class SizeOverflowError extends Errors.BaseError { constructor({ givenSize, maxSize }) { super(`Size cannot exceed \`${maxSize}\` bytes. Given size: \`${givenSize}\` bytes.`); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'Bytes.SizeOverflowError' }); } } /** * Thrown when a slice offset is out-of-bounds. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * Bytes.slice(Bytes.from([1, 2, 3]), 4) * // @error: Bytes.SliceOffsetOutOfBoundsError: Slice starting at offset `4` is out-of-bounds (size: `3`). * ``` */ export class SliceOffsetOutOfBoundsError extends Errors.BaseError { constructor({ offset, position, size, }) { super(`Slice ${position === 'start' ? 'starting' : 'ending'} at offset \`${offset}\` is out-of-bounds (size: \`${size}\`).`); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'Bytes.SliceOffsetOutOfBoundsError' }); } } /** * Thrown when a the padding size exceeds the maximum allowed size. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * Bytes.padLeft(Bytes.fromString('Hello World!'), 8) * // @error: [Bytes.SizeExceedsPaddingSizeError: Bytes size (`12`) exceeds padding size (`8`). * ``` */ export class SizeExceedsPaddingSizeError extends Errors.BaseError { constructor({ size, targetSize, type, }) { super(`${type.charAt(0).toUpperCase()}${type .slice(1) .toLowerCase()} size (\`${size}\`) exceeds padding size (\`${targetSize}\`).`); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'Bytes.SizeExceedsPaddingSizeError' }); } } //# sourceMappingURL=Bytes.js.map