UNPKG

ox

Version:

Ethereum Standard Library

141 lines 4.6 kB
import * as Errors from '../../Errors.js'; import { bytesToHex } from './hex.js'; /** * Decodes a big-endian unsigned `Uint8Array` into a `bigint`. When `signed` is * true, applies twos-complement on the high bit. * * @internal */ export function bytesToBigInt(value, signed) { const len = value.length; if (len === 0) return 0n; // Fast path: unsigned big-endian read using DataView for 8-byte chunks, // skipping the hex round-trip. if (!signed) { let val = 0n; let i = 0; if (len >= 8) { const view = new DataView(value.buffer, value.byteOffset, len); const tail = len & 7; const fullEnd = len - tail; while (i < fullEnd) { val = (val << 64n) | view.getBigUint64(i, false); i += 8; } } while (i < len) { val = (val << 8n) | BigInt(value[i]); i++; } return val; } // Signed: BigInt parses hex strings faster than the per-byte loop for // > 8-byte inputs in V8/JSC, so route through `bytesToHex`. const hex = bytesToHex(value); const n = hex === '0x' ? 0n : BigInt(hex); const max_unsigned = 1n << (BigInt(len) * 8n); const max_signed = max_unsigned >> 1n; if (n < max_signed) return n; return n - max_unsigned; } /** * Decodes a big-endian unsigned `Uint8Array` into a number, throwing if the * value cannot be represented as a safe integer. * * @internal */ export function bytesToSafeNumber(value, signed) { if (!signed && value.length <= 6) { // Small unsigned values fit in a number directly without BigInt round-trip. let n = 0; for (let i = 0; i < value.length; i++) n = n * 256 + value[i]; return n; } const big = bytesToBigInt(value, signed); const num = Number(big); if (!Number.isSafeInteger(num)) { throw new IntegerOutOfRangeError({ max: `${Number.MAX_SAFE_INTEGER}`, min: signed ? `${Number.MIN_SAFE_INTEGER}` : '0', signed, value: `${num}`, }); } return num; } /** * Encodes a number/bigint into a big-endian `Uint8Array` of the given byte * size (or minimal size when `size` is omitted). * * @internal */ export function bigIntToBytes(value, options = {}) { const { signed, size } = options; // Range check (mirrors `Hex.fromNumber` semantics). let value_; if (typeof value === 'bigint') value_ = value; else if (Number.isSafeInteger(value)) value_ = BigInt(value); else value_ = BigInt(value); let maxValue; if (size) { if (signed) maxValue = (1n << (BigInt(size) * 8n - 1n)) - 1n; else maxValue = (1n << (BigInt(size) * 8n)) - 1n; } else if (typeof value === 'number') { maxValue = BigInt(Number.MAX_SAFE_INTEGER); } const minValue = typeof maxValue === 'bigint' && signed ? -maxValue - 1n : 0n; if ((maxValue && value_ > maxValue) || value_ < minValue) { const suffix = typeof value === 'bigint' ? 'n' : ''; throw new IntegerOutOfRangeError({ max: maxValue ? `${maxValue}${suffix}` : undefined, min: `${minValue}${suffix}`, signed, size, value: `${value}${suffix}`, }); } // Normalize negatives to two's-complement before encoding. const n = signed && value_ < 0 ? BigInt.asUintN((size ?? bigIntByteLength(-value_) + 1) * 8, value_) : value_; const minBytes = bigIntByteLength(n); const length = size ?? Math.max(minBytes, 1); const out = new Uint8Array(length); // Right-aligned big-endian write. let i = length - 1; let rem = n; while (rem > 0n && i >= 0) { out[i--] = Number(rem & 0xffn); rem >>= 8n; } return out; } /** @internal */ export function bigIntByteLength(n) { if (n === 0n) return 0; let len = 0; let v = n < 0n ? -n : n; while (v > 0n) { len++; v >>= 8n; } return len; } /** Thrown when a value cannot be represented as a safe integer. */ export class IntegerOutOfRangeError extends Errors.BaseError { name = 'Hex.IntegerOutOfRangeError'; constructor({ max, min, signed, size, value, }) { super(`Number \`${value}\` is not in safe${size ? ` ${size * 8}-bit` : ''}${signed ? ' signed' : ' unsigned'} integer range ${max ? `(\`${min}\` to \`${max}\`)` : `(above \`${min}\`)`}`); } } //# sourceMappingURL=int.js.map