const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; function base58Encode(data: Uint8Array): string { let num = BigInt("0x" + Array.from(data).map(b => b.toString(16).padStart(2, "0")).join("")); let result = ""; while (num > 0n) { const remainder = num % 58n; num = num / 58n; result = BASE58_ALPHABET[Number(remainder)] + result; } for (let i = 0; i < data.length && data[i] === 0; i++) { result = "1" + result; } return result; } 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 toBase58Groups(encoded: string): string[] { const groups: string[] = []; for (let i = 0; i < encoded.length; i += 12) { groups.push(encoded.slice(i, i + 12)); } return groups; } function fromBase58Groups(groups: string[]): string { return groups.join(""); } export const PUBLIC_KEY_HEX = "29d0f82eb3527b99635336f5e9bef4ae07f80b32de5206b312e74eb3632d3599"; 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; } function bytesToHex(bytes: Uint8Array): string { return Array.from(bytes) .map(b => b.toString(16).padStart(2, "0")) .join(""); } 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" }; } } export async function activateLicense(keyString: string): Promise { const validated = await validateLicense(keyString); if (validated.valid && validated.plan === "pro") { await chrome.storage.local.set({ peach_license: keyString.trim() }); 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"); } const CELEBRATION_KEY = "peach_pro_celebrated"; 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 }); }