import initWasm, { decrypt_serialized_u64_radix_flat_wasm } from './concrete-ml-extensions-wasm/concrete_ml_extensions_wasm.js'; const SERVER = 'https://backend-server.com'; let clientKey, serverKey; let encTokens; let encServerResult; let keygenWorker; let encryptWorker; let sessionUid; // Memory-efficient base64 encoding for large Uint8Array function uint8ToBase64(uint8) { return new Promise((resolve, reject) => { const blob = new Blob([uint8]); const reader = new FileReader(); reader.onload = function () { const base64 = reader.result.split(',')[1]; resolve(base64); }; reader.onerror = reject; reader.readAsDataURL(blob); }); } const $ = id => document.getElementById(id); const enable = (id, ok=true) => $(id).disabled = !ok; const show = (id, visible=true) => $(id).hidden = !visible; // Hide all spinners immediately show('keygenSpin', false); show('spin', false); show('encIcon', false); show('tokenizerSpin', false); // Initialize WASM (async () => { try { console.log('[Main] Initializing WASM module...'); await initWasm(); console.log('[Main] WASM module initialized successfully'); // Initialize the keygen worker keygenWorker = new Worker(new URL('./keygen-worker.js', import.meta.url), { type: 'module' }); keygenWorker.onmessage = async function(e) { if (e.data.type === 'success') { const res = e.data.result; console.log('[Main] Key generation successful'); console.log(`[Main] Client key size: ${res.clientKey.length} bytes`); console.log(`[Main] Server key size: ${res.serverKey.length} bytes`); clientKey = res.clientKey; serverKey = res.serverKey; try { // Initialize encryption worker encryptWorker = new Worker(new URL('./encrypt-worker.js', import.meta.url), { type: 'module' }); encryptWorker.onmessage = function(e) { if (e.data.type === 'ready') { console.log('[Main] Encryption worker ready'); } else if (e.data.type === 'success') { encTokens = e.data.result; console.log(`[Main] Encryption completed: ${encTokens.length} bytes`); show('encryptSpin', false); show('encIcon', true); enable('btnEncrypt', true); enable('btnSend'); $('encStatus').textContent = 'Your text is encrypted 🔒'; } else if (e.data.type === 'error') { console.error('[Main] Encryption error:', e.data.error); show('encryptSpin', false); enable('btnEncrypt', true); $('encStatus').textContent = `Encryption failed: ${e.data.error}`; alert(`Encryption failed: ${e.data.error}`); } }; // Initialize the worker with the client key encryptWorker.postMessage({ type: 'init', clientKey }); console.log('[Main] Encoding server key to base64...'); const serverKeyB64 = await uint8ToBase64(serverKey); console.log(`[Main] Server key base64 size: ${serverKeyB64.length} bytes`); const handshakeResponse = await fetch(`${SERVER}/handshake`, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ server_key_b64: serverKeyB64 }) }); if (!handshakeResponse.ok) { const errorText = await handshakeResponse.text(); throw new Error(`Server handshake failed: ${handshakeResponse.status} ${errorText}`); } const { uid } = await handshakeResponse.json(); sessionUid = uid; console.log('[Main] Server handshake successful'); $('keygenStatus').textContent = 'keys generated ✓'; enable('btnEncrypt'); } catch (error) { console.error('[Main] Server handshake error:', error); $('keygenStatus').textContent = `Server handshake failed: ${error.message}`; enable('btnEncrypt', false); } } else { console.error('[Main] Key generation error:', e.data.error); $('keygenStatus').textContent = `Error generating keys: ${e.data.error}`; } show('keygenSpin', false); }; } catch (e) { console.error('[Main] Failed to initialize WASM module:', e); $('keygenStatus').textContent = `Initialization Error: ${e.message}`; throw e; } })(); $('btnKeygen').onclick = async () => { if ($('keygenSpin').hidden === false) { console.log('[Main] Keygen already in progress, ignoring click'); return; } show('keygenSpin', true); $('keygenStatus').textContent = 'generating…'; try { keygenWorker.postMessage({}); } catch (e) { console.error('[Main] Key generation error:', e); $('keygenStatus').textContent = `Error generating keys: ${e.message}`; show('keygenSpin', false); } }; $('btnEncrypt').onclick = async () => { const text = $('tokenInput').value.trim(); if (!text) { console.error('[Main] No text provided for tokenization/encryption'); alert('Please enter text to encrypt.'); return; } if (!encryptWorker) { console.error('[Main] Encryption worker not initialized'); alert('Encryption worker is not ready. Please generate keys first.'); return; } show('encryptSpin', true); show('encIcon', false); enable('btnEncrypt', false); try { console.log('[Main] Tokenizing text:', text); const tokenIds = llama3Tokenizer.encode(text); console.log('[Main] Token IDs:', tokenIds); encryptWorker.postMessage({ type: 'encrypt', tokenIds }); } catch (error) { console.error('[Main] Tokenization or encryption initiation error:', error); show('encryptSpin', false); enable('btnEncrypt', true); alert(`Error during tokenization/encryption: ${error.message}`); } }; $('btnSend').onclick = async () => { if ($('spin').hidden === false) { console.log('[Main] Send already in progress, ignoring click'); return; } show('encIcon', false); show('spin', true); $('srvStatus').textContent = 'Preparing request…'; $('srvComputing').hidden = true; const startTime = performance.now(); try { const ciphertext_b64 = await uint8ToBase64(encTokens); $('srvStatus').textContent = 'Sending…'; console.log('[Main] Sending request to server...'); const response = await fetch(`${SERVER}/detect`, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ uid: sessionUid, ciphertext_b64 }) }).catch(error => { throw new Error(`Server unreachable: ${error.message}`); }); console.log(`[Main] Server response status: ${response.status}`); if (!response.ok) { const errorText = await response.text(); throw new Error(`Server error: ${response.status} ${errorText}`); } $('srvStatus').textContent = '✓ sent'; $('srvComputing').hidden = false; const {result_b64} = await response.json(); const endTime = performance.now(); const duration = ((endTime - startTime) / 1000).toFixed(2); console.log(`[Main] Server request completed in ${duration}s`); console.log('[Main] Processing server response...'); encServerResult = Uint8Array.from(atob(result_b64), c=>c.charCodeAt(0)); console.log(`[Main] Received encrypted result: ${encServerResult.length} bytes`); $('encResult').value = `(${encServerResult.length} B)`; $('srvComputing').hidden = true; $('srvStatus').textContent = `✓ received (${duration}s)`; enable('btnDecrypt'); } catch (e) { const endTime = performance.now(); const duration = ((endTime - startTime) / 1000).toFixed(2); console.error(`[Main] Server request failed after ${duration}s:`, e); $('srvComputing').hidden = true; $('srvStatus').textContent = `Server error: ${e.message} (${duration}s)`; show('spin', false); } }; $('btnDecrypt').onclick = () => { try { console.log('[Main] Starting decryption...'); const dec = decrypt_serialized_u64_radix_flat_wasm(encServerResult, clientKey); const [flag, score_scaled, total_g] = Array.from(dec); const score = (Number(score_scaled) / 1e6).toFixed(6); console.log('[Main] Decryption successful'); console.log(`[Main] Result - flag: ${flag}, score: ${score}, total_g: ${total_g}`); $('decResult').textContent = `flag=${flag}, score=${score}, total_g=${total_g}`; } catch (e) { console.error('[Main] Decryption error:', e); $('decResult').textContent = `Error decrypting result: ${e.message}`; } };