/// Minimal RFC 4648 base32 encode/decode for TOTP secret parsing. /// /// Mirrors the encoder in crates/relicario-core/src/item.rs:base32_encode. /// Decode is case-insensitive, tolerates whitespace and `=` padding. const ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; export function base32Encode(bytes: Uint8Array): string { let out = ''; let buffer = 0; let bits = 0; for (const b of bytes) { buffer = (buffer << 8) | b; bits += 8; while (bits >= 5) { const idx = (buffer >> (bits - 5)) & 0x1f; out += ALPHA[idx]; bits -= 5; } } if (bits > 0) { const idx = (buffer << (5 - bits)) & 0x1f; out += ALPHA[idx]; } return out; } export function base32Decode(input: string): Uint8Array { const cleaned = input.replace(/\s+/g, '').replace(/=+$/g, '').toUpperCase(); const out: number[] = []; let buffer = 0; let bits = 0; for (const ch of cleaned) { const idx = ALPHA.indexOf(ch); if (idx === -1) throw new Error(`base32: invalid character "${ch}"`); buffer = (buffer << 5) | idx; bits += 5; if (bits >= 8) { out.push((buffer >> (bits - 8)) & 0xff); bits -= 8; } } return new Uint8Array(out); }