98 lines · 3.3 KB
import { timingSafeEqualStrings } from "./crypto";

const BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
const SECRET_BYTES = 20;
const STEP_SECONDS = 30;
const DIGITS = 6;
const DRIFT_STEPS = 1;
const BASE32_BITS = 5;
const BYTE_BITS = 8;

export function base32Encode(bytes: Uint8Array): string {
  let bits = 0;
  let value = 0;
  let out = "";
  for (const byte of bytes) {
    value = (value << BYTE_BITS) | byte;
    bits += BYTE_BITS;
    while (bits >= BASE32_BITS) {
      out += BASE32_ALPHABET[(value >>> (bits - BASE32_BITS)) & 31];
      bits -= BASE32_BITS;
    }
  }
  if (bits > 0) out += BASE32_ALPHABET[(value << (BASE32_BITS - bits)) & 31];
  return out;
}

export function base32Decode(input: string): Uint8Array<ArrayBuffer> {
  const clean = input.toUpperCase().replace(/[\s=]/g, "");
  const out: number[] = [];
  let bits = 0;
  let value = 0;
  for (const char of clean) {
    const index = BASE32_ALPHABET.indexOf(char);
    if (index === -1) throw new Error("Invalid base32");
    value = (value << BASE32_BITS) | index;
    bits += BASE32_BITS;
    if (bits >= BYTE_BITS) {
      out.push((value >>> (bits - BYTE_BITS)) & 255);
      bits -= BYTE_BITS;
    }