@lodestar/utils
Version:
Utilities required across multiple lodestar packages
217 lines (183 loc) • 6.14 kB
text/typescript
// "0".charCodeAt(0) = 48
const CHAR_CODE_0 = 48;
// "x".charCodeAt(0) = 120
const CHAR_CODE_X = 120;
export function toHex(bytes: Uint8Array): string {
const charCodes = new Array<number>(bytes.length * 2 + 2);
charCodes[0] = CHAR_CODE_0;
charCodes[1] = CHAR_CODE_X;
bytesIntoCharCodes(bytes, charCodes);
return String.fromCharCode(...charCodes);
}
const rootCharCodes = new Array<number>(32 * 2 + 2);
rootCharCodes[0] = CHAR_CODE_0;
rootCharCodes[1] = CHAR_CODE_X;
/**
* Convert a Uint8Array, length 32, to 0x-prefixed hex string
*/
export function toRootHex(root: Uint8Array): string {
if (root.length !== 32) {
throw Error(`Expect root to be 32 bytes, got ${root.length}`);
}
bytesIntoCharCodes(root, rootCharCodes);
return String.fromCharCode(...rootCharCodes);
}
const pubkeyCharCodes = new Array<number>(48 * 2 + 2);
pubkeyCharCodes[0] = CHAR_CODE_0;
pubkeyCharCodes[1] = CHAR_CODE_X;
/**
* Convert a Uint8Array, length 48, to 0x-prefixed hex string
*/
export function toPubkeyHex(pubkey: Uint8Array): string {
if (pubkey.length !== CHAR_CODE_0) {
throw Error(`Expect pubkey to be 48 bytes, got ${pubkey.length}`);
}
bytesIntoCharCodes(pubkey, pubkeyCharCodes);
return String.fromCharCode(...pubkeyCharCodes);
}
export function fromHex(hex: string): Uint8Array {
if (typeof hex !== "string") {
throw new Error(`hex argument type ${typeof hex} must be of type string`);
}
if (hex.startsWith("0x")) {
hex = hex.slice(2);
}
if (hex.length % 2 !== 0) {
throw new Error(`hex string length ${hex.length} must be multiple of 2`);
}
const byteLen = hex.length / 2;
const bytes = new Uint8Array(byteLen);
for (let i = 0; i < byteLen; i++) {
const byte2i = charCodeToByte(hex.charCodeAt(i * 2));
const byte2i1 = charCodeToByte(hex.charCodeAt(i * 2 + 1));
bytes[i] = (byte2i << 4) | byte2i1;
}
return bytes;
}
export function fromHexInto(hex: string, buffer: Uint8Array): void {
if (hex.startsWith("0x")) {
hex = hex.slice(2);
}
if (hex.length !== buffer.length * 2) {
throw new Error(`hex string length ${hex.length} must be exactly double the buffer length ${buffer.length}`);
}
for (let i = 0; i < buffer.length; i++) {
const byte2i = charCodeToByte(hex.charCodeAt(i * 2));
const byte2i1 = charCodeToByte(hex.charCodeAt(i * 2 + 1));
buffer[i] = (byte2i << 4) | byte2i1;
}
}
/**
* Populate charCodes from bytes. Note that charCodes index 0 and 1 ("0x") are not populated.
*/
function bytesIntoCharCodes(bytes: Uint8Array, charCodes: number[]): void {
if (bytes.length * 2 + 2 !== charCodes.length) {
throw Error(`Expect charCodes to be of length ${bytes.length * 2 + 2}, got ${charCodes.length}`);
}
for (let i = 0; i < bytes.length; i++) {
const byte = bytes[i];
const first = (byte & 0xf0) >> 4;
const second = byte & 0x0f;
// "0".charCodeAt(0) = 48
// "a".charCodeAt(0) = 97 => delta = 87
charCodes[2 + 2 * i] = first < 10 ? first + 48 : first + 87;
charCodes[2 + 2 * i + 1] = second < 10 ? second + 48 : second + 87;
}
}
function charCodeToByte(charCode: number): number {
// "a".charCodeAt(0) = 97, "f".charCodeAt(0) = 102 => delta = 87
if (charCode >= 97 && charCode <= 102) {
return charCode - 87;
}
// "A".charCodeAt(0) = 65, "F".charCodeAt(0) = 70 => delta = 55
if (charCode >= 65 && charCode <= 70) {
return charCode - 55;
}
// "0".charCodeAt(0) = 48, "9".charCodeAt(0) = 57 => delta = 48
if (charCode >= 48 && charCode <= 57) {
return charCode - 48;
}
throw new Error(`Invalid hex character code: ${charCode}`);
}
import {toBigIntBE, toBigIntLE, toBufferBE, toBufferLE} from "@vekexasia/bigint-buffer2";
type Endianness = "le" | "be";
const hexByByte: string[] = [];
/**
* @deprecated Use toHex() instead.
*/
export function toHexString(bytes: Uint8Array): string {
let hex = "0x";
for (const byte of bytes) {
if (!hexByByte[byte]) {
hexByByte[byte] = byte < 16 ? "0" + byte.toString(16) : byte.toString(16);
}
hex += hexByByte[byte];
}
return hex;
}
/**
* Return a byte array from a number or BigInt
*/
export function intToBytes(value: bigint | number, length: number, endianness: Endianness = "le"): Uint8Array {
return bigIntToBytes(BigInt(value), length, endianness);
}
/**
* Convert byte array in LE to integer.
*/
export function bytesToInt(value: Uint8Array, endianness: Endianness = "le"): number {
return Number(bytesToBigInt(value, endianness));
}
export function bigIntToBytes(value: bigint, length: number, endianness: Endianness = "le"): Uint8Array {
if (endianness === "le") {
return toBufferLE(value, length);
}
if (endianness === "be") {
return toBufferBE(value, length);
}
throw new Error("endianness must be either 'le' or 'be'");
}
export function bytesToBigInt(value: Uint8Array, endianness: Endianness = "le"): bigint {
if (!(value instanceof Uint8Array)) {
throw new TypeError("expected a Uint8Array");
}
if (endianness === "le") {
return toBigIntLE(value as Buffer);
}
if (endianness === "be") {
return toBigIntBE(value as Buffer);
}
throw new Error("endianness must be either 'le' or 'be'");
}
export function xor(a: Uint8Array, b: Uint8Array): Uint8Array {
const length = Math.min(a.length, b.length);
for (let i = 0; i < length; i++) {
a[i] = a[i] ^ b[i];
}
return a;
}
/** Count set bits in a Uint8Array without allocation (Brian Kernighan's algorithm) */
export function bitCount(arr: Uint8Array): number {
let count = 0;
for (let i = 0; i < arr.length; i++) {
let byte = arr[i];
while (byte) {
byte &= byte - 1;
count++;
}
}
return count;
}
/**
* Compare two byte arrays for equality.
* Note: In Node.js environment, the implementation in nodejs.ts uses Buffer.compare
* which is significantly faster due to native code.
*/
export function byteArrayEquals(a: Uint8Array, b: Uint8Array): boolean {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}