private-synthid / wasm-demo.js
jfrery-zama's picture
fix spinning
66919e5 unverified
raw
history blame
9.84 kB
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}`;
}
};