// Modal state variables let isModalOpen = false; let pendingAction = null; // Can be 'success' or 'error' let storedErrorMessage = ''; let storedRedirectUrl = ''; const REDIRECT_DELAY = 1488; function workerFunction() { self.onmessage = function (e) { const { type, data } = e.data; if (type === 'pow') { const { challenge, salt, startNonce, endNonce, target, batchId } = data; let count = 0; let solution = null; processNextNonce(startNonce); function processNextNonce(nonce) { const input = String(challenge) + String(salt) + nonce.toString(); const msgBuffer = new TextEncoder().encode(input); crypto.subtle .digest('SHA-256', msgBuffer) .then((hashBuffer) => { const hashArray = Array.from(new Uint8Array(hashBuffer)); const result = hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); count++; if (result.startsWith(target)) { solution = { nonce: nonce.toString(), found: true }; self.postMessage({ type: 'pow_result', solution: solution, count: count, batchId: batchId, }); return; } if (count % 1000 === 0) { self.postMessage({ type: 'progress', count: count, batchId: batchId, }); } if (nonce < endNonce && !solution) { setTimeout(() => processNextNonce(nonce + 1), 0); } else if (!solution) { self.postMessage({ type: 'pow_result', solution: null, count: count, batchId: batchId, }); } }) .catch((err) => { self.postMessage({ type: 'error', error: 'Crypto API error: ' + err.message, }); }); } } else { self.postMessage({ type: 'error', error: 'Unknown message type: ' + type }); } }; } const workerCode = '(' + workerFunction.toString() + ')()'; function posWorkerFunction() { self.onmessage = async function (e) { const { type, seedHex, isDecoy } = e.data; if (type === 'pos') { const minMB = 48, maxMB = 160; let seedInt = parseInt(seedHex.slice(0, 8), 16); if (isNaN(seedInt)) seedInt = Math.floor(Math.random() * (maxMB - minMB + 1)); const CHUNK_MB = isDecoy ? minMB + ((seedInt * 3 + 17) % (maxMB - minMB + 1)) : minMB + (seedInt % (maxMB - minMB + 1)); const CHUNK_SIZE = CHUNK_MB * 1024 * 1024; const chunkCount = 4 + (seedInt % 5); const chunkSize = Math.floor(CHUNK_SIZE / chunkCount); const FILL_STEP_4K = 4096, FILL_STEP_1K = 1024; const FILL_STEP_SWITCH = 35 * 1024 * 1024; const runs = 3; const mainBuf = new ArrayBuffer(CHUNK_SIZE); const view = new Uint8Array(mainBuf); const pressureBuf = new ArrayBuffer(16 * 1024 * 1024); const pressureView = new Uint8Array(pressureBuf); const hashes = []; const times = []; for (let r = 0; r < runs; r++) { const prng = seededPRNG(seedHex + r.toString(16)); const order = Array.from({ length: chunkCount }, (_, i) => i); for (let i = order.length - 1; i > 0; i--) { const j = prng() % (i + 1); [order[i], order[j]] = [order[j], order[i]]; } const t0 = performance.now(); for (let c = 0; c < chunkCount; c++) { const idx = order[c]; const start = idx * chunkSize; const end = idx === chunkCount - 1 ? CHUNK_SIZE : start + chunkSize; const step = start < FILL_STEP_SWITCH ? FILL_STEP_4K : FILL_STEP_1K; for (let i = start; i < end; i += step) view[i] = prng() & 0xff; } const hashBuf = await crypto.subtle.digest('SHA-256', view); const t2 = performance.now(); hashes.push( Array.from(new Uint8Array(hashBuf)) .map((b) => b.toString(16).padStart(2, '0')) .join(''), ); times.push(Math.round(t2 - t0)); for (let i = 0; i < pressureView.length; i += 4096) pressureView[i] = prng() & 0xff; } self.postMessage({ type: 'pos_result', hashes, times }); } }; function seededPRNG(seedHex) { const s = []; for (let i = 0; i < 4; i++) s[i] = parseInt(seedHex.substr(i * 8, 8), 16) >>> 0; function rotl(x, k) { return ((x << k) | (x >>> (32 - k))) >>> 0; } return function () { const t = s[1] << 9; let r = (s[0] * 5) >>> 0; r = (rotl(r, 7) * 9) >>> 0; const tmp = s[0] ^ s[2]; s[2] ^= s[1]; s[1] ^= s[3]; s[0] ^= s[1]; s[3] ^= tmp; s[2] ^= t; s[3] = rotl(s[3], 11); return r >>> 0; }; } } const posWorkerCode = '(' + posWorkerFunction.toString() + ')()'; document.addEventListener('DOMContentLoaded', function () { setTimeout(initVerification, 650); // Modal elements const infoButton = document.getElementById('infoBtn'); const infoModal = document.getElementById('infoModal'); const modalCloseButton = document.getElementById('modalCloseBtn'); if (infoButton) { infoButton.addEventListener('click', () => { if (isModalOpen) { closeModal(); } else { isModalOpen = true; if (infoModal) infoModal.classList.add('active'); } }); } if (modalCloseButton) { modalCloseButton.addEventListener('click', closeModal); } // Close modal if overlay is clicked if (infoModal) { infoModal.addEventListener('click', (event) => { if (event.target === infoModal) { closeModal(); } }); } function closeModal() { if (infoModal) infoModal.classList.remove('active'); isModalOpen = false; if (pendingAction === 'success') { triggerStoredSuccess(); } else if (pendingAction === 'error') { triggerStoredError(storedErrorMessage); } pendingAction = null; // Reset after handling } // Moved triggerStoredSuccess and triggerStoredError to this scope function triggerStoredSuccess() { document.querySelector('.container').classList.add('success'); const statusEl = document.getElementById('status'); if (statusEl) statusEl.textContent = 'Redirecting'; // Ensure status is updated if (storedRedirectUrl) { setTimeout(() => { window.location.href = storedRedirectUrl; }, REDIRECT_DELAY); // Ensure REDIRECT_DELAY is accessible or define it here } } function triggerStoredError(message) { const container = document.querySelector('.container'); const statusEl = document.getElementById('status'); const spinnerContainer = document.querySelector('.spinner-container'); container.classList.add('error'); container.classList.remove('success'); if (statusEl) { statusEl.style.display = 'inline-block'; statusEl.textContent = 'Error'; statusEl.classList.add('error'); statusEl.classList.remove('success'); } let errorDetails = document.getElementById('error-details'); if (!errorDetails && spinnerContainer) { errorDetails = document.createElement('div'); errorDetails.id = 'error-details'; errorDetails.className = 'error-details'; spinnerContainer.appendChild(errorDetails); } if (errorDetails) { // errorDetails.textContent = message; // Display the specific error message errorDetails.style.display = 'block'; } // Ensure any running workers are stopped on error } function initVerification() { const dataEl = document.getElementById('verification-data'); const targetPath = dataEl.getAttribute('data-target'); const requestID = dataEl.getAttribute('data-request-id'); startVerification(); async function startVerification() { try { const challengeResponse = await fetch( '/api/challenge?id=' + encodeURIComponent(requestID), { method: 'GET', headers: { Accept: 'application/json' }, credentials: 'include' }, ); if (!challengeResponse.ok) { throw new Error('Failed to get challenge parameters'); } const challengeData = await challengeResponse.json(); const verifier = new Verifier(challengeData, targetPath, requestID); verifier.start(); } catch (error) { showError('Verification setup failed: ' + error.message); } } function createWorker() { const blob = new Blob([workerCode], { type: 'text/javascript' }); return new Worker(URL.createObjectURL(blob)); } function createPosWorker() { const blob = new Blob([posWorkerCode], { type: 'text/javascript' }); return new Worker(URL.createObjectURL(blob)); } function showError(message) { const container = document.querySelector('.container'); const statusEl = document.getElementById('status'); const spinnerContainer = document.querySelector('.spinner-container'); storedErrorMessage = message; // Store for later if (isModalOpen) { pendingAction = 'error'; if (statusEl) { statusEl.textContent = 'Error'; // Generic message while modal is open statusEl.style.display = 'inline-block'; // Make sure status is visible statusEl.classList.remove('success'); // Ensure no success styling statusEl.classList.add('error'); // Add error styling for text color } // Do not show full error details or animations yet return; } // Proceed with normal error display if modal is not open container.classList.add('error'); container.classList.remove('success'); if (statusEl) { statusEl.style.display = 'inline-block'; statusEl.textContent = ''; statusEl.classList.add('error'); statusEl.classList.remove('success'); } let errorDetails = document.getElementById('error-details'); if (!errorDetails && spinnerContainer) { errorDetails = document.createElement('div'); errorDetails.id = 'error-details'; errorDetails.className = 'error-details'; spinnerContainer.appendChild(errorDetails); } if (errorDetails) { // errorDetails.textContent = message; // This was causing issues, let animations handle it or remove. errorDetails.style.display = 'none'; // Keep this hidden, rely on class-based animation } } function showSuccess() { const statusEl = document.getElementById('status'); if (statusEl) statusEl.textContent = 'Redirecting'; if (isModalOpen) { pendingAction = 'success'; // Do not add 'success' class or start animations yet return; } document.querySelector('.container').classList.add('success'); // The redirect will be handled by submitSolution after a delay } function Verifier(params, targetPath, requestID) { const workers = []; const activeBatches = {}; let powSolution = null; let isRunning = false; const cpuCount = navigator.hardwareConcurrency || 4; const workerCount = Math.max(1, Math.floor(cpuCount * 0.8)); // const REDIRECT_DELAY = 1488; // Defined globally now this.start = function () { setTimeout(findProofOfWork, 100); }; async function findProofOfWork() { try { isRunning = true; const challenge = params.a; const salt = params.b; const target = '0'.repeat(params.c); for (let i = 0; i < workerCount; i++) { const worker = createWorker(); worker.onmessage = (e) => handleWorkerMessage(e.data); worker.onerror = (error) => {}; workers.push(worker); } const totalRange = Number.MAX_SAFE_INTEGER; const rangePerWorker = Math.floor(totalRange / workerCount); for (let i = 0; i < workers.length; i++) { const startNonce = i * rangePerWorker; const endNonce = i === workers.length - 1 ? totalRange : (i + 1) * rangePerWorker - 1; const workerId = `pow-worker-${i}`; activeBatches[workerId] = { workerId: i, startNonce, endNonce, }; workers[i].postMessage({ type: 'pow', data: { challenge: challenge, salt: salt, startNonce, endNonce, target, batchId: workerId, }, }); } } catch (error) { terminateWorkers(); showError(error.message); } } function handleWorkerMessage(data) { if (!isRunning) return; if (data.type === 'pow_result') { if (activeBatches[data.batchId]) { delete activeBatches[data.batchId]; if (data.solution && data.solution.found) { if (!powSolution) { powSolution = data.solution; proofOfWorkFound(powSolution); } } } } else if (data.type === 'error') { showError('Compatibility error: ' + data.error); terminateWorkers(); } } async function proofOfWorkFound(solution) { isRunning = false; terminateWorkers(); try { const posResult = await new Promise((res) => { const w = createPosWorker(); w.onmessage = (e) => { if (e.data.type === 'pos_result') { res(e.data); w.terminate(); } }; w.postMessage({ type: 'pos', seedHex: params.d, isDecoy: false }); }); await submitSolution({ requestID, g: solution.nonce, h: posResult.hashes, i: posResult.times, }); } catch (error) { showError(error.message); } } function terminateWorkers() { workers.forEach((worker) => worker.terminate()); } async function submitSolution(solutionData) { try { const response = await fetch('/api/verify', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ request_id: solutionData.requestID, g: solutionData.g, h: solutionData.h, i: solutionData.i, }), }); if (!response.ok) { let errorMsg = `Verification failed: ${response.statusText}`; try { const errorData = await response.json(); if (errorData && errorData.error) { errorMsg += ` - ${errorData.error}`; } else { const text = await response.text(); errorMsg += ` - Response: ${text}`; } } catch (parseError) {} showError(errorMsg); return; } let result; try { result = await response.json(); } catch (e) { showError('Invalid verification response'); return; } const token = result.token; if (!token) { // Use existing showError for immediate display if modal not involved // or to set up pending error if modal is open. showError('Verification did not return a token'); return; } // Store for potential delayed redirect const sep = targetPath.includes('?') ? '&' : '?'; storedRedirectUrl = `${targetPath}${sep}token=${encodeURIComponent(token)}`; showSuccess(); // This will now respect isModalOpen if (!isModalOpen) { setTimeout(() => { window.location.href = storedRedirectUrl; }, REDIRECT_DELAY); } } catch (error) { // Use existing showError showError('Verification failed. Please refresh the page.'); } } } } });