ox
Version:
Ethereum Standard Library
112 lines • 4.23 kB
JavaScript
import * as Errors from '../../Errors.js';
/** @internal */
const hexes = /*#__PURE__*/ Array.from({ length: 256 }, (_v, i) => i.toString(16).padStart(2, '0'));
// Char-code → 4-bit nibble lookup table. Sentinel `0xff` = invalid.
const nibbleTable = /*#__PURE__*/ (() => {
const table = new Uint8Array(256).fill(0xff);
for (let i = 0; i < 10; i++)
table[48 + i] = i; // '0'-'9'
for (let i = 0; i < 6; i++) {
table[65 + i] = 10 + i; // 'A'-'F'
table[97 + i] = 10 + i; // 'a'-'f'
}
return table;
})();
// Phase 2 native fast-path detection: `Uint8Array.prototype.toHex` lands in
// Node 22+, Safari 18+, Firefox 133+. Falls back to `Buffer` on Node, then to
// the JS loop.
const _Buffer = globalThis.Buffer;
const nativeToHex = Uint8Array.prototype.toHex;
const nativeFromHex = Uint8Array.fromHex;
/**
* Encodes a `Uint8Array` into a `0x`-prefixed lowercase hex string. Uses the
* native `Uint8Array.prototype.toHex` (Node 22+, Safari 18+, Firefox 133+) or
* Node's `Buffer` when available; otherwise a tight JS loop.
*
* @internal
*/
export function bytesToHex(value) {
if (nativeToHex)
return `0x${nativeToHex.call(value)}`;
if (_Buffer)
return `0x${_Buffer.from(value.buffer, value.byteOffset, value.byteLength).toString('hex')}`;
const length = value.length;
const parts = Array.from({ length });
for (let i = 0; i < length; i++)
parts[i] = hexes[value[i]];
return `0x${parts.join('')}`;
}
/**
* Strictly decodes a `0x`-prefixed even-length hex string into a `Uint8Array`.
* Uses the native `Uint8Array.fromHex` (Node 22+, Safari 18+, Firefox 133+) or
* Node's `Buffer` when available; otherwise a tight JS loop.
*
* @internal
*/
export function hexToBytes(value) {
if (typeof value !== 'string' ||
value.length < 2 ||
value.charCodeAt(0) !== 48 /* '0' */ ||
value.charCodeAt(1) !== 120 /* 'x' */)
throw new InvalidHexValueError(value);
const body = value.length === 2 ? '' : value.slice(2);
if ((body.length & 1) !== 0)
throw new InvalidLengthError(value);
if (nativeFromHex) {
try {
return nativeFromHex(body);
}
catch {
throw new InvalidHexValueError(value);
}
}
if (_Buffer && body.length > 0) {
// Buffer.from with 'hex' silently truncates on invalid chars; verify
// byteLength matches expectations to detect malformed input.
const expected = body.length >> 1;
const buf = _Buffer.from(body, 'hex');
if (buf.length !== expected)
throw new InvalidHexValueError(value);
// Copy out of Buffer pool: callers may rely on `.buffer` being a
// standalone ArrayBuffer (e.g. WebAuthn attestationObject round-trips).
const out = new Uint8Array(buf.byteLength);
out.set(buf);
return out;
}
const length = body.length >> 1;
const out = new Uint8Array(length);
for (let i = 0, j = 0; i < length; i++) {
const hi = nibbleTable[body.charCodeAt(j++)];
const lo = nibbleTable[body.charCodeAt(j++)];
if (hi === 0xff || lo === 0xff)
throw new InvalidHexValueError(value);
out[i] = (hi << 4) | lo;
}
return out;
}
/** @internal */
export function charCodeToBase16(char) {
const v = nibbleTable[char];
return v === 0xff ? undefined : v;
}
/** Thrown when a value is not a valid `0x`-prefixed hex string. */
export class InvalidHexValueError extends Errors.BaseError {
name = 'Hex.InvalidHexValueError';
constructor(value) {
super(`Value \`${value}\` is an invalid hex value.`, {
metaMessages: [
'Hex values must start with `"0x"` and contain only hexadecimal characters (0-9, a-f, A-F).',
],
});
}
}
/** Thrown when a hex string has an odd nibble count. */
export class InvalidLengthError extends Errors.BaseError {
name = 'Hex.InvalidLengthError';
constructor(value) {
super(`Hex value \`"${value}"\` is an odd length (${value.length - 2} nibbles).`, {
metaMessages: ['It must be an even length.'],
});
}
}
//# sourceMappingURL=hex.js.map