1const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";23function base58Decode(str: string): Uint8Array {4 let num = 0n;5 for (const char of str) {6 const idx = BASE58_ALPHABET.indexOf(char);7 if (idx === -1) throw new Error("Invalid base58 character");8 num = num * 58n + BigInt(idx);9 }10 const hex = num.toString(16).padStart(2, "0");11 const paddedHex = hex.length % 2 ? "0" + hex : hex;12 const result = new Uint8Array(paddedHex.length / 2);13 for (let i = 0; i < paddedHex.length; i += 2) {14 result[i / 2] = parseInt(paddedHex.slice(i, i + 2), 16);15 }16 return result;17}1819function fromBase58Groups(groups: string[]): string {20 return groups.join("");21}2223export const PUBLIC_KEY_HEX =24 "c7dc52d2c484696cf8566dbe6cfe69025d114cb47f6cf53c25088ecfd539bdfa";2526export interface LicenseInfo {27 valid: boolean;28 plan: "free" | "pro";29 issued?: Date;30}3132const LICENSE_PREFIX = "PPK-";3334async function importPublicKey(): Promise<CryptoKey> {35 const keyData = hexToBytes(PUBLIC_KEY_HEX);36 return (crypto.subtle as any).importKey(37 "raw",38 keyData,39 { name: "Ed25519" },40 true,41 ["verify"]42 );43}4445function hexToBytes(hex: string): Uint8Array {46 const bytes = new Uint8Array(hex.length / 2);47 for (let i = 0; i < hex.length; i += 2) {48 bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);49 }50 return bytes;51}5253export async function validateLicense(keyString: string): Promise<LicenseInfo> {54 if (!keyString || typeof keyString !== "string") {55 return { valid: false, plan: "free" };56 }5758 const trimmed = keyString.trim();59 if (!trimmed.startsWith(LICENSE_PREFIX)) {60 return { valid: false, plan: "free" };61 }6263 try {64 const withoutPrefix = trimmed.slice(LICENSE_PREFIX.length);65 const groups = withoutPrefix.split("-");66 if (groups.length !== 5) {67 return { valid: false, plan: "free" };68 }6970 const base58String = fromBase58Groups(groups);71 const decoded = base58Decode(base58String);7273 if (decoded.length !== 76) {74 return { valid: false, plan: "free" };75 }7677 const payload = decoded.slice(0, 12);78 const signature = decoded.slice(12, 76);7980 if (payload[0] !== 0x01) {81 return { valid: false, plan: "free" };82 }83 if (payload[1] !== 0x01) {84 return { valid: false, plan: "free" };85 }86 if (payload[2] !== 0x01) {87 return { valid: false, plan: "free" };88 }8990 const issuedTimestamp = new DataView(payload.buffer, payload.byteOffset + 3, 4).getUint32(0, true);91 const issuedDate = new Date(issuedTimestamp * 86400 * 1000);9293 const publicKey = await importPublicKey();94 const isValid = await (crypto.subtle as any).verify(95 "Ed25519",96 publicKey,97 signature,98 payload99 );100101 if (!isValid) {102 return { valid: false, plan: "free" };103 }104105 return { valid: true, plan: "pro", issued: issuedDate };106 } catch {107 return { valid: false, plan: "free" };108 }109}110111export async function getLicenseInfo(): Promise<LicenseInfo> {112 try {113 const result = await chrome.storage.local.get("peach_license") as Record<string, string | undefined>;114 const stored = result["peach_license"];115 if (!stored) {116 return { valid: false, plan: "free" };117 }118 return validateLicense(stored);119 } catch {120 return { valid: false, plan: "free" };121 }122}123124const CELEBRATION_KEY = "peach_pro_celebrated";125126export async function activateLicense(keyString: string): Promise<LicenseInfo> {127 const validated = await validateLicense(keyString);128 if (validated.valid && validated.plan === "pro") {129 130 131 132 await chrome.storage.local.set({133 peach_license: keyString.trim(),134 [CELEBRATION_KEY]: false,135 });136 return validated;137 }138 return { valid: false, plan: "free" };139}140141export async function isProUser(): Promise<boolean> {142 const info = await getLicenseInfo();143 return info.valid && info.plan === "pro";144}145146147export async function getLicenseKey(): Promise<string | null> {148 try {149 const result = await chrome.storage.local.get("peach_license") as Record<string, string | undefined>;150 return result["peach_license"] ?? null;151 } catch {152 return null;153 }154}155156export async function deactivateLicense(): Promise<void> {157 await chrome.storage.local.remove("peach_license");158}159160export async function shouldPlayCelebration(): Promise<boolean> {161 const info = await getLicenseInfo();162 if (!info.valid || info.plan !== "pro") return false;163 const result = await chrome.storage.local.get(CELEBRATION_KEY) as Record<string, boolean | undefined>;164 return !result[CELEBRATION_KEY];165}166167export async function markCelebrationPlayed(): Promise<void> {168 await chrome.storage.local.set({ [CELEBRATION_KEY]: true });169}