const textEncoder = new TextEncoder(); const textDecoder = new TextDecoder(); const base32Alphabet = "abcdefghijklmnopqrstuvwxyz234567"; export function utf8Bytes(value: string): Uint8Array { return textEncoder.encode(value); } export function utf8String(bytes: Uint8Array): string { return textDecoder.decode(bytes); } export function randomBytes(length: number): Uint8Array { const bytes = new Uint8Array(length); crypto.getRandomValues(bytes); return bytes; } export function hexFromBytes(bytes: Uint8Array): string { return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join(""); } export function randomHex(byteLength: number): string { return hexFromBytes(randomBytes(byteLength)); } export function base32FromBytes(bytes: Uint8Array): string { let output = ""; let buffer = 0; let bits = 0; for (const byte of bytes) { buffer = (buffer << 8) | byte; bits += 8; while (bits >= 5) { output += base32Alphabet[(buffer >>> (bits - 5)) & 31]; bits -= 5; } } if (bits > 0) output += base32Alphabet[(buffer << (5 - bits)) & 31]; return output; } export function base64UrlEncode(bytes: Uint8Array): string { let binary = ""; for (const byte of bytes) binary += String.fromCharCode(byte); return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, ""); } export function base64UrlDecode(value: string): Uint8Array { const normalized = value.replace(/-/g, "+").replace(/_/g, "/"); const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "="); const binary = atob(padded); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i); return bytes; } export function constantTimeEqual(a: string, b: string): boolean { const maxLength = Math.max(a.length, b.length); let diff = a.length ^ b.length; for (let i = 0; i < maxLength; i += 1) { diff |= (a.charCodeAt(i) || 0) ^ (b.charCodeAt(i) || 0); } return diff === 0; }