export const base64ToBytes = (b64: string): Uint8Array => { const binaryString = atob(b64); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } return bytes; } export const bytesToBase64 = (buf: Uint8Array): string => { const bytes: string[] = new Array(buf.length); for (let i = 0; i < buf.length; i++) { bytes[i] = String.fromCharCode(buf[i]); } return btoa(bytes.join('')); } /** * Do semi constant time comparison of two Uint8Arrays to prevent timing attacks. * Returns true if the arrays are equal, false otherwise. * * Note: This is not truly constant time, but it is designed to take the same amount of time regardless of where the first difference is. * It will still take longer for longer arrays, but it will not short-circuit on the first difference. */ export const constantTimeEqual = (a: Uint8Array, b: Uint8Array): boolean => { if (a.length !== b.length) return false; let result = 0; for (let i = 0; i < a.length; i++) { result |= a[i] ^ b[i]; } return result === 0; } /* BufferReader is a utility class for reading and writing binary data from a Uint8Array buffer. * It maintains an internal offset and length to track the current position in the buffer. It * provides methods for reading unsigned integers in little-endian format, as well as arbitrary * byte arrays. It also allows writing unsigned integers back into the buffer. This class is * useful for parsing and constructing binary protocols. */ export class BufferReader { protected buffer: Uint8Array; protected offset: number = 0; public length: number = 0; constructor(buffer: Uint8Array) { this.buffer = new Uint8Array(buffer.length); for (let i = 0; i < buffer.length; i++) { this.buffer[i] = buffer[i]; } this.length = buffer.length; } public reset(buffer: Uint8Array) { this.buffer = new Uint8Array(buffer.length); for (let i = 0; i < buffer.length; i++) { this.buffer[i] = buffer[i]; } this.offset = 0; this.length = buffer.length; } public readUint8(): number { const value = this.buffer[this.offset]; this.offset++; this.length--; return value; } public readUint16LE(): number { const value = this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8); this.offset += 2; this.length -= 2; return value; } public readUint32LE(): number { const value = this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8) | (this.buffer[this.offset + 2] << 16) | (this.buffer[this.offset + 3] << 24); this.offset += 4; this.length -= 4; return value; } public readBytes(size?: number): Uint8Array { size ||= (this.buffer.length - this.offset); const value = this.buffer.slice(this.offset, this.offset + size); this.offset += size; this.length -= size; return value; } public writeUint16LE(value: number): void { this.buffer[this.offset] = value & 0xFF; this.buffer[this.offset + 1] = (value >> 8) & 0xFF; this.offset += 2; this.length += 2; } public writeUint32LE(value: number): void { this.buffer[this.offset] = value & 0xFF; this.buffer[this.offset + 1] = (value >> 8) & 0xFF; this.buffer[this.offset + 2] = (value >> 16) & 0xFF; this.buffer[this.offset + 3] = (value >> 24) & 0xFF; this.offset += 4; this.length += 4; } }