const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; function base58Decode(str: string): Uint8Array { let num = 0n; for (const char of str) { const idx = BASE58_ALPHABET.indexOf(char); if (idx === -1) throw new Error("Invalid base58 character"); num = num * 58n + BigInt(idx); } const hex = num.toString(16).padStart(2, "0"); const paddedHex = hex.length % 2 ? "0" + hex : hex; const result = new Uint8Array(paddedHex.length / 2); for (let i = 0; i < paddedHex.length; i += 2) { result[i / 2] = parseInt(paddedHex.slice(i, i + 2), 16); } return result; } function fromBase58Groups(groups: string[]): string { return groups.join(""); } export const PUBLIC_KEY_HEX = "c7dc52d2c484696cf8566dbe6cfe69025d114cb47f6cf53c25088ecfd539bdfa"; export interface LicenseInfo { valid: boolean; plan: "free" | "pro"; issued?: Date; } const LICENSE_PREFIX = "PPK-"; async function importPublicKey(): Promise { const keyData = hexToBytes(PUBLIC_KEY_HEX); return (crypto.subtle as any).importKey( "raw", keyData, { name: "Ed25519" }, true, ["verify"] ); } function hexToBytes(hex: string): Uint8Array { const bytes = new Uint8Array(hex.length / 2); for (let i = 0; i < hex.length; i += 2) { bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16); } return bytes; } export async function validateLicense(keyString: string): Promise { if (!keyString || typeof keyString !== "string") { return { valid: false, plan: "free" }; } const trimmed = keyString.trim(); if (!trimmed.startsWith(LICENSE_PREFIX)) { return { valid: false, plan: "free" }; } try { const withoutPrefix = trimmed.slice(LICENSE_PREFIX.length); const groups = withoutPrefix.split("-"); if (groups.length !== 5) { return { valid: false, plan: "free" }; } const base58String = fromBase58Groups(groups); const decoded = base58Decode(base58String); if (decoded.length !== 76) { return { valid: false, plan: "free" }; } const payload = decoded.slice(0, 12); const signature = decoded.slice(12, 76); if (payload[0] !== 0x01) { return { valid: false, plan: "free" }; } if (payload[1] !== 0x01) { return { valid: false, plan: "free" }; } if (payload[2] !== 0x01) { return { valid: false, plan: "free" }; } const issuedTimestamp = new DataView(payload.buffer, payload.byteOffset + 3, 4).getUint32(0, true); const issuedDate = new Date(issuedTimestamp * 86400 * 1000); const publicKey = await importPublicKey(); const isValid = await (crypto.subtle as any).verify( "Ed25519", publicKey, signature, payload ); if (!isValid) { return { valid: false, plan: "free" }; } return { valid: true, plan: "pro", issued: issuedDate }; } catch { return { valid: false, plan: "free" }; } } export async function getLicenseInfo(): Promise { try { const result = await chrome.storage.local.get("peach_license") as Record; const stored = result["peach_license"]; if (!stored) { return { valid: false, plan: "free" }; } return validateLicense(stored); } catch { return { valid: false, plan: "free" }; } } const CELEBRATION_KEY = "peach_pro_celebrated"; export async function activateLicense(keyString: string): Promise { const validated = await validateLicense(keyString); if (validated.valid && validated.plan === "pro") { // Reset the celebration flag so every new license activation triggers // the full-screen Pro celebration in the popup, even on devices that // have celebrated before (e.g., testers who activate repeatedly). await chrome.storage.local.set({ peach_license: keyString.trim(), [CELEBRATION_KEY]: false, }); return validated; } return { valid: false, plan: "free" }; } export async function isProUser(): Promise { const info = await getLicenseInfo(); return info.valid && info.plan === "pro"; } /** Returns the raw stored license key string, or null if none. */ export async function getLicenseKey(): Promise { try { const result = await chrome.storage.local.get("peach_license") as Record; return result["peach_license"] ?? null; } catch { return null; } } export async function deactivateLicense(): Promise { await chrome.storage.local.remove("peach_license"); } export async function shouldPlayCelebration(): Promise { const info = await getLicenseInfo(); if (!info.valid || info.plan !== "pro") return false; const result = await chrome.storage.local.get(CELEBRATION_KEY) as Record; return !result[CELEBRATION_KEY]; } export async function markCelebrationPlayed(): Promise { await chrome.storage.local.set({ [CELEBRATION_KEY]: true }); }