import { equalBytes } from "@noble/ciphers/utils.js"; import { bytesToHex, hexToBytes as nobleHexToBytes } from "@noble/hashes/utils.js"; export { bytesToHex, equalBytes }; export const base64ToBytes = (base64: string, size?: number): Uint8Array => { // Normalize URL-safe base64 to standard base64 let normalized = base64.replace(/-/g, '+').replace(/_/g, '/'); // Add padding if missing while (normalized.length % 4 !== 0) { normalized += '='; } const binaryString = atob(normalized); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } if (size !== undefined && bytes.length !== size) { throw new Error(`Invalid base64 length: expected ${size} bytes, got ${bytes.length}`); } return bytes; } // Note: encodedStringToBytes removed — prefer explicit parsers. // Use `hexToBytes` for hex inputs and `base64ToBytes` for base64 inputs. // Wrapper around @noble/hashes hexToBytes that optionally validates size. export const hexToBytes = (hex: string, size?: number): Uint8Array => { const bytes = nobleHexToBytes(hex); if (size !== undefined && bytes.length !== size) { throw new Error(`Invalid hex length: expected ${size} bytes, got ${bytes.length}`); } return bytes; }; export class BufferReader { private buffer: Uint8Array; private offset: number; constructor(buffer: Uint8Array) { this.buffer = buffer; this.offset = 0; } public readByte(): number { if (!this.hasMore()) throw new Error('read past end'); return this.buffer[this.offset++]; } public readBytes(length?: number): Uint8Array { if (length === undefined) { length = this.buffer.length - this.offset; } if (this.remainingBytes() < length) throw new Error('read past end'); const bytes = this.buffer.slice(this.offset, this.offset + length); this.offset += length; return bytes; } public hasMore(): boolean { return this.offset < this.buffer.length; } public remainingBytes(): number { return this.buffer.length - this.offset; } public peekByte(): number { if (!this.hasMore()) throw new Error('read past end'); return this.buffer[this.offset]; } public readUint16LE(): number { if (this.remainingBytes() < 2) throw new Error('read past end'); const value = this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8); this.offset += 2; return value; } public readUint32LE(): number { if (this.remainingBytes() < 4) throw new Error('read past end'); const value = (this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8) | (this.buffer[this.offset + 2] << 16) | (this.buffer[this.offset + 3] << 24)) >>> 0; this.offset += 4; return value; } public readInt16LE(): number { if (this.remainingBytes() < 2) throw new Error('read past end'); const value = this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8); this.offset += 2; return value < 0x8000 ? value : value - 0x10000; } public readInt32LE(): number { if (this.remainingBytes() < 4) throw new Error('read past end'); const u = (this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8) | (this.buffer[this.offset + 2] << 16) | (this.buffer[this.offset + 3] << 24)) >>> 0; this.offset += 4; return u < 0x80000000 ? u : u - 0x100000000; } public readTimestamp(): Date { const timestamp = this.readUint32LE(); return new Date(timestamp * 1000); } } export class BufferWriter { private buffer: number[] = []; public writeByte(value: number): void { this.buffer.push(value & 0xFF); } public writeBytes(bytes: Uint8Array): void { this.buffer.push(...bytes); } public writeUint16LE(value: number): void { this.buffer.push(value & 0xFF, (value >> 8) & 0xFF); } public writeUint32LE(value: number): void { this.buffer.push( value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF ); } public writeInt16LE(value: number): void { this.writeUint16LE(value < 0 ? value + 0x10000 : value); } public writeInt32LE(value: number): void { this.writeUint32LE(value < 0 ? value + 0x100000000 : value); } public writeTimestamp(date: Date): void { const timestamp = Math.floor(date.getTime() / 1000); this.writeUint32LE(timestamp); } public toBytes(): Uint8Array { return new Uint8Array(this.buffer); } }