Download app/static/js/attacks.js from Jethro85/DPSGDTool: direct link, hf CLI and curl.
- Browser
- Download file 59.2 kB
-
https://huggingface.co/spaces/Jethro85/DPSGDTool/resolve/main/app/static/js/attacks.js
- Command line
-
hf download hf://spaces/Jethro85/DPSGDTool/app/static/js/attacks.js
-
curl -L -o attacks.js https://huggingface.co/spaces/Jethro85/DPSGDTool/resolve/main/app/static/js/attacks.js
59.2 kB
| // Privacy Attacks Interactive Demonstrations | |
| class AttackSimulator { | |
| constructor() { | |
| this.charts = {}; | |
| this.initializeEventListeners(); | |
| this.initializeCharts(); | |
| } | |
| initializeEventListeners() { | |
| // Tab switching | |
| // document.querySelectorAll('.attack-tab').forEach(tab => { | |
| // tab.addEventListener('click', (e) => { | |
| // this.switchTab(e.target.dataset.attack); | |
| // }); | |
| // }); | |
| document.querySelectorAll('.attack-tab').forEach(tab => { | |
| tab.addEventListener('click', (e) => { | |
| const attackType = e.currentTarget.dataset.attack; // use currentTarget (safer than target) | |
| // 1) UI event (generic) | |
| track('ui_click', { | |
| component: 'attack_tab', | |
| attack: attackType, | |
| page: 'privacy_attacks' | |
| }); | |
| // 2) Semantic event (recommended) | |
| track('attack_tab_open', { | |
| attack: attackType, | |
| page: 'privacy_attacks' | |
| }); | |
| this.switchTab(attackType); | |
| }); | |
| }); | |
| // Slider updates | |
| // this.setupSliderUpdates(); | |
| // ✅ Add this (bind events for whatever tab is active on load) | |
| this.bindAttacksPageEvents(); | |
| } | |
| setupSliderUpdates() { | |
| // New membership privacy level slider | |
| const privacyLevelSlider = document.getElementById('privacy-level-slider'); | |
| if (privacyLevelSlider) { | |
| privacyLevelSlider.addEventListener('input', (e) => { | |
| const levels = ['Very High', 'High', 'Medium', 'Low', 'Very Low']; | |
| document.getElementById('privacy-level-text').textContent = levels[e.target.value - 1]; | |
| this.updateMembershipDemo(); | |
| }); | |
| } | |
| // Reconstruction sliders | |
| const reconClipping = document.getElementById('recon-clipping-slider'); | |
| const reconNoise = document.getElementById('recon-noise-slider'); | |
| if (reconClipping) { | |
| reconClipping.addEventListener('input', (e) => { | |
| document.getElementById('recon-clipping').textContent = e.target.value; | |
| this.updateReconstructionAttack(); | |
| }); | |
| } | |
| if (reconNoise) { | |
| reconNoise.addEventListener('input', (e) => { | |
| document.getElementById('recon-noise-level').textContent = e.target.value; | |
| this.updateReconstructionAttack(); | |
| }); | |
| } | |
| // Linkage sliders | |
| const linkageQuality = document.getElementById('linkage-quality-slider'); | |
| const linkagePrivacy = document.getElementById('linkage-privacy-slider'); | |
| if (linkageQuality) { | |
| linkageQuality.addEventListener('input', (e) => { | |
| const qualities = ['Very Low', 'Low', 'Medium', 'High', 'Very High']; | |
| document.getElementById('linkage-quality').textContent = qualities[e.target.value - 1]; | |
| this.updateLinkageAttack(); | |
| }); | |
| } | |
| if (linkagePrivacy) { | |
| linkagePrivacy.addEventListener('input', (e) => { | |
| const epsilon = (11 - e.target.value).toFixed(1); | |
| document.getElementById('linkage-model-privacy').textContent = `ε=${epsilon}`; | |
| this.updateLinkageAttack(); | |
| }); | |
| } | |
| } | |
| bindAttacksPageEvents(attackType = null) { | |
| // Bind only within the currently active attack content (prevents binding to hidden/nonexistent nodes) | |
| const container = attackType | |
| ? document.getElementById(`${attackType}-content`) | |
| : document.querySelector('.attack-content.active'); | |
| if (!container) return; | |
| // Helper: bind once per element per handler-key | |
| const bindOnce = (el, key, eventName, fn) => { | |
| if (!el) return; | |
| const flag = `bound_${key}`; | |
| if (el.dataset[flag] === '1') return; | |
| el.addEventListener(eventName, fn); | |
| el.dataset[flag] = '1'; | |
| }; | |
| // ---- Membership tab ---- | |
| bindOnce( | |
| container.querySelector('#privacy-level-slider'), | |
| 'privacy_level_slider', | |
| 'input', | |
| () => this.updateMembershipDemo() | |
| ); | |
| bindOnce( | |
| container.querySelector('#update-privacy-btn'), | |
| 'update_privacy_btn', | |
| 'click', | |
| (e) => { | |
| // If you have a button on the Attacks page, prefer binding here over inline onclick | |
| e.preventDefault(); | |
| this.updateMembershipDemo(); | |
| } | |
| ); | |
| // ---- Reconstruction tab ---- | |
| bindOnce( | |
| container.querySelector('#recon-clipping-slider'), | |
| 'recon_clipping_slider', | |
| 'input', | |
| () => this.updateReconstructionAttack() | |
| ); | |
| bindOnce( | |
| container.querySelector('#recon-noise-slider'), | |
| 'recon_noise_slider', | |
| 'input', | |
| () => this.updateReconstructionAttack() | |
| ); | |
| bindOnce( | |
| container.querySelector('#run-reconstruction-btn'), | |
| 'run_reconstruction_btn', | |
| 'click', | |
| (e) => { | |
| e.preventDefault(); | |
| this.updateReconstructionAttack(); | |
| updateCurrentReconstructionExamples(); | |
| } | |
| ); | |
| // ---- Inversion tab ---- | |
| bindOnce( | |
| container.querySelector('#inversion-privacy-slider'), | |
| 'inversion_privacy_slider', | |
| 'input', | |
| () => { | |
| const privacySlider = container.querySelector('#inversion-privacy-slider'); | |
| const classSelect = container.querySelector('#inversion-class-select'); | |
| if (!privacySlider || !classSelect) return; | |
| const sliderValue = parseInt(privacySlider.value); | |
| const privacyLevel = 11 - sliderValue; | |
| drawInvertedFeatures(classSelect.value, privacyLevel); | |
| } | |
| ); | |
| bindOnce( | |
| container.querySelector('#run-inversion-btn'), | |
| 'run_inversion_btn', | |
| 'click', | |
| (e) => { | |
| e.preventDefault(); | |
| // If you want: call your existing function or inline the logic | |
| runInversionAttack(); | |
| } | |
| ); | |
| // ---- Property tab ---- | |
| bindOnce( | |
| container.querySelector('#run-property-btn'), | |
| 'run_property_btn', | |
| 'click', | |
| (e) => { | |
| e.preventDefault(); | |
| runPropertyAttack(); | |
| } | |
| ); | |
| // ---- Linkage tab ---- | |
| bindOnce( | |
| container.querySelector('#linkage-quality-slider'), | |
| 'linkage_quality_slider', | |
| 'input', | |
| () => this.updateLinkageAttack() | |
| ); | |
| bindOnce( | |
| container.querySelector('#linkage-privacy-slider'), | |
| 'linkage_privacy_slider', | |
| 'input', | |
| () => this.updateLinkageAttack() | |
| ); | |
| bindOnce( | |
| container.querySelector('#run-linkage-btn'), | |
| 'run_linkage_btn', | |
| 'click', | |
| (e) => { | |
| e.preventDefault(); | |
| this.updateLinkageAttack(); | |
| } | |
| ); | |
| } | |
| switchTab(attackType) { | |
| const prev = document.querySelector('.attack-tab.active')?.dataset?.attack; | |
| // Update tab buttons | |
| document.querySelectorAll('.attack-tab').forEach(tab => { | |
| tab.classList.remove('active'); | |
| }); | |
| document.querySelector(`[data-attack="${attackType}"]`).classList.add('active'); | |
| // Update content | |
| document.querySelectorAll('.attack-content').forEach(content => { | |
| content.classList.remove('active'); | |
| }); | |
| document.getElementById(`${attackType}-content`).classList.add('active'); | |
| // Initialize chart for this tab if needed | |
| this.initializeTabChart(attackType); | |
| // ✅ Add this (bind events for the newly activated tab) | |
| this.bindAttacksPageEvents(attackType); | |
| if (prev && prev !== attackType) { | |
| track('attack_tab_switch', { | |
| from: prev, | |
| to: attackType, | |
| page: 'privacy_attacks' | |
| }); | |
| } | |
| } | |
| initializeCharts() { | |
| this.initializeMembershipChart(); | |
| this.initializeComparisonChart(); | |
| } | |
| initializeMembershipChart() { | |
| const ctx = document.getElementById('membership-chart'); | |
| if (!ctx) return; | |
| this.charts.membership = new Chart(ctx, { | |
| type: 'line', | |
| data: { | |
| labels: ['ε=0.5', 'ε=1.0', 'ε=2.0', 'ε=3.0', 'ε=5.0', 'ε=8.0', 'ε=∞'], | |
| datasets: [{ | |
| label: 'Attack Success Rate', | |
| data: [52, 58, 65, 72, 78, 83, 87], | |
| borderColor: '#ff6b6b', | |
| backgroundColor: 'rgba(255, 107, 107, 0.1)', | |
| tension: 0.4, | |
| fill: true | |
| }, { | |
| label: 'Random Guessing', | |
| data: [50, 50, 50, 50, 50, 50, 50], | |
| borderColor: '#666', | |
| borderDash: [5, 5], | |
| fill: false | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| title: { | |
| display: true, | |
| text: 'Membership Inference Attack Success vs Privacy Budget' | |
| }, | |
| legend: { | |
| display: true | |
| } | |
| }, | |
| scales: { | |
| y: { | |
| beginAtZero: true, | |
| max: 100, | |
| title: { | |
| display: true, | |
| text: 'Attack Success Rate (%)' | |
| } | |
| }, | |
| x: { | |
| title: { | |
| display: true, | |
| text: 'Privacy Budget (ε)' | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| initializeComparisonChart() { | |
| const ctx = document.getElementById('comparison-chart'); | |
| if (!ctx) return; | |
| this.charts.comparison = new Chart(ctx, { | |
| type: 'radar', | |
| data: { | |
| labels: ['Membership Inference', 'Data Reconstruction', 'Model Inversion', 'Property Inference', 'Linkage Attack'], | |
| datasets: [{ | |
| label: 'No Privacy (ε=∞)', | |
| data: [87, 92, 78, 83, 89], | |
| borderColor: '#d32f2f', | |
| backgroundColor: 'rgba(211, 47, 47, 0.2)', | |
| pointBackgroundColor: '#d32f2f' | |
| }, { | |
| label: 'Low Privacy (ε=8.0)', | |
| data: [72, 76, 65, 70, 74], | |
| borderColor: '#f57c00', | |
| backgroundColor: 'rgba(245, 124, 0, 0.2)', | |
| pointBackgroundColor: '#f57c00' | |
| }, { | |
| label: 'Medium Privacy (ε=3.0)', | |
| data: [58, 61, 52, 56, 60], | |
| borderColor: '#fbc02d', | |
| backgroundColor: 'rgba(251, 192, 45, 0.2)', | |
| pointBackgroundColor: '#fbc02d' | |
| }, { | |
| label: 'High Privacy (ε=1.0)', | |
| data: [42, 45, 38, 41, 44], | |
| borderColor: '#2e7d32', | |
| backgroundColor: 'rgba(46, 125, 50, 0.2)', | |
| pointBackgroundColor: '#2e7d32' | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| title: { | |
| display: true, | |
| text: 'Attack Success Rates Across Different Privacy Levels' | |
| } | |
| }, | |
| scales: { | |
| r: { | |
| beginAtZero: true, | |
| max: 100, | |
| ticks: { | |
| stepSize: 20 | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| initializeTabChart(attackType) { | |
| if (attackType === 'reconstruction') { | |
| this.initializeReconstructionChart(); | |
| } else if (attackType === 'property') { | |
| this.initializePropertyChart(); | |
| } else if (attackType === 'linkage') { | |
| this.initializeLinkageChart(); | |
| } | |
| } | |
| initializeReconstructionChart() { | |
| const ctx = document.getElementById('reconstruction-chart'); | |
| if (!ctx || this.charts.reconstruction) return; | |
| this.charts.reconstruction = new Chart(ctx, { | |
| type: 'bar', | |
| data: { | |
| labels: ['No Noise', 'Low Noise (σ=0.5)', 'Medium Noise (σ=1.0)', 'High Noise (σ=2.0)', 'Very High Noise (σ=3.0)'], | |
| datasets: [{ | |
| label: 'Reconstruction Quality (SSIM)', | |
| data: [0.95, 0.78, 0.52, 0.31, 0.18], | |
| backgroundColor: ['#d32f2f', '#f57c00', '#fbc02d', '#689f38', '#2e7d32'], | |
| borderWidth: 1 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| title: { | |
| display: true, | |
| text: 'Data Reconstruction Quality vs Noise Level' | |
| } | |
| }, | |
| scales: { | |
| y: { | |
| beginAtZero: true, | |
| max: 1, | |
| title: { | |
| display: true, | |
| text: 'Reconstruction Quality (SSIM Score)' | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| initializePropertyChart() { | |
| const ctx = document.getElementById('property-chart'); | |
| if (!ctx || this.charts.property) return; | |
| this.charts.property = new Chart(ctx, { | |
| type: 'doughnut', | |
| data: { | |
| labels: ['Correctly Inferred', 'Incorrectly Inferred', 'Uncertain'], | |
| datasets: [{ | |
| data: [52, 18, 30], | |
| backgroundColor: ['#d32f2f', '#f57c00', '#2e7d32'], | |
| borderWidth: 2 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| title: { | |
| display: true, | |
| text: 'Property Inference Attack Results' | |
| }, | |
| legend: { | |
| position: 'bottom' | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| initializeLinkageChart() { | |
| const ctx = document.getElementById('linkage-chart'); | |
| if (!ctx || this.charts.linkage) return; | |
| this.charts.linkage = new Chart(ctx, { | |
| type: 'scatter', | |
| data: { | |
| datasets: [{ | |
| label: 'Successful Links', | |
| data: [ | |
| {x: 1, y: 45}, {x: 2, y: 52}, {x: 3, y: 61}, {x: 4, y: 68}, {x: 5, y: 74}, | |
| {x: 6, y: 79}, {x: 7, y: 83}, {x: 8, y: 86}, {x: 9, y: 89}, {x: 10, y: 91} | |
| ], | |
| backgroundColor: '#d32f2f', | |
| borderColor: '#d32f2f' | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| title: { | |
| display: true, | |
| text: 'Linkage Attack Success vs Privacy Budget' | |
| } | |
| }, | |
| scales: { | |
| x: { | |
| title: { | |
| display: true, | |
| text: 'Privacy Budget (ε)' | |
| }, | |
| min: 0, | |
| max: 11 | |
| }, | |
| y: { | |
| title: { | |
| display: true, | |
| text: 'Successful Links (%)' | |
| }, | |
| min: 0, | |
| max: 100 | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| // Attack simulation functions | |
| updateMembershipDemo() { | |
| const privacyLevel = parseInt(document.getElementById('privacy-level-slider').value); | |
| // Calculate attack success based on privacy level (1=highest privacy, 5=lowest) | |
| const successRates = [45, 52, 65, 78, 87]; // Success rates for each privacy level | |
| const successRate = successRates[privacyLevel - 1]; | |
| // Update confidence differences based on privacy | |
| const confidenceDiffs = [8, 12, 18, 24, 28]; // Confidence differences | |
| const trainingConf = [76, 80, 86, 92, 96]; // Training confidence | |
| const testConf = trainingConf.map((tc, i) => tc - confidenceDiffs[i]); // Test confidence | |
| const currentTrainingConf = trainingConf[privacyLevel - 1]; | |
| const currentTestConf = testConf[privacyLevel - 1]; | |
| const currentDiff = confidenceDiffs[privacyLevel - 1]; | |
| // Update visual elements | |
| document.getElementById('training-confidence').style.width = `${currentTrainingConf}%`; | |
| document.getElementById('training-confidence').textContent = `${currentTrainingConf}%`; | |
| document.getElementById('test-confidence').style.width = `${currentTestConf}%`; | |
| document.getElementById('test-confidence').textContent = `${currentTestConf}%`; | |
| document.getElementById('confidence-diff').textContent = `${currentDiff}%`; | |
| // Update success rate circle | |
| document.getElementById('membership-success').textContent = `${successRate}%`; | |
| // Update circle color based on success rate | |
| const circle = document.getElementById('success-rate-circle'); | |
| if (successRate < 55) { | |
| circle.style.background = 'linear-gradient(135deg, #28a745, #20c997)'; // Green - good privacy | |
| } else if (successRate < 70) { | |
| circle.style.background = 'linear-gradient(135deg, #ffc107, #fd7e14)'; // Yellow - medium privacy | |
| } else { | |
| circle.style.background = 'linear-gradient(135deg, #dc3545, #fd7e14)'; // Red - poor privacy | |
| } | |
| // Update explanation text | |
| const explanations = [ | |
| "Excellent! With very high privacy protection, the attacker can barely do better than random guessing (50%). Your data is well protected!", | |
| "Great! High privacy protection makes the attack much less effective. The confidence differences are small and hard to exploit.", | |
| "With medium privacy protection, the attacker can still succeed 65% of the time. Consider increasing privacy for sensitive data.", | |
| "Low privacy protection allows attackers to succeed most of the time. The model shows clear differences between training and test data.", | |
| "Very low privacy means the attack is highly successful. The model 'remembers' training data too well, making membership easy to detect." | |
| ]; | |
| document.getElementById('privacy-explanation').textContent = explanations[privacyLevel - 1]; | |
| } | |
| updateReconstructionAttack() { | |
| const clipping = parseFloat(document.getElementById('recon-clipping-slider').value); | |
| const noise = parseFloat(document.getElementById('recon-noise-slider').value); | |
| // Calculate reconstruction quality (SSIM score) | |
| const baseQuality = 0.95; | |
| const clippingReduction = (5 - clipping) * 0.08; | |
| const noiseReduction = noise * 0.22; | |
| const quality = Math.max(0.05, baseQuality - clippingReduction - noiseReduction); | |
| const ssimScore = quality.toFixed(2); | |
| // Update SSIM score display | |
| const ssimElement = document.getElementById('ssim-score'); | |
| if (ssimElement) { | |
| ssimElement.textContent = ssimScore; | |
| ssimElement.style.color = quality > 0.7 ? '#dc3545' : quality > 0.4 ? '#f57c00' : '#28a745'; | |
| } | |
| // Update quality badge | |
| const qualityElement = document.getElementById('recon-quality'); | |
| if (qualityElement) { | |
| if (quality > 0.7) { | |
| qualityElement.textContent = '❌ High Quality (Vulnerable!)'; | |
| qualityElement.className = 'reconstruction-quality quality-high'; | |
| } else if (quality > 0.4) { | |
| qualityElement.textContent = '⚠️ Medium Quality'; | |
| qualityElement.className = 'reconstruction-quality quality-medium'; | |
| } else { | |
| qualityElement.textContent = '✅ Low Quality (Protected!)'; | |
| qualityElement.className = 'reconstruction-quality quality-low'; | |
| } | |
| } | |
| // Calculate privacy level | |
| const epsilon = Math.max(0.5, 10 - (clipping * 0.5 + noise * 2)); | |
| const privacyStatus = document.getElementById('privacy-status'); | |
| if (privacyStatus) { | |
| if (epsilon > 6) { | |
| privacyStatus.textContent = `❌ Low (ε ≈ ${epsilon.toFixed(1)})`; | |
| privacyStatus.style.color = '#dc3545'; | |
| } else if (epsilon > 3) { | |
| privacyStatus.textContent = `⚠️ Medium (ε ≈ ${epsilon.toFixed(1)})`; | |
| privacyStatus.style.color = '#f57c00'; | |
| } else { | |
| privacyStatus.textContent = `✅ High (ε ≈ ${epsilon.toFixed(1)})`; | |
| privacyStatus.style.color = '#28a745'; | |
| } | |
| } | |
| // Update attack success rate | |
| const attackSuccess = Math.round(quality * 100); | |
| const attackSuccessElement = document.getElementById('attack-success'); | |
| if (attackSuccessElement) { | |
| attackSuccessElement.textContent = `${attackSuccess}%`; | |
| attackSuccessElement.style.color = attackSuccess > 70 ? '#dc3545' : attackSuccess > 40 ? '#f57c00' : '#28a745'; | |
| } | |
| // Update reconstructed image visualization | |
| this.updateReconstructedImage(quality, noise); | |
| // Update explanation | |
| const explanationElement = document.getElementById('recon-explanation'); | |
| if (explanationElement) { | |
| if (quality > 0.7) { | |
| explanationElement.textContent = '⚠️ Without sufficient privacy protection, attackers can reconstruct training images with high fidelity from gradient information alone!'; | |
| explanationElement.style.background = '#fff3cd'; | |
| explanationElement.style.borderLeft = '4px solid #ffc107'; | |
| } else if (quality > 0.4) { | |
| explanationElement.textContent = '🛡️ Medium privacy protection degrades reconstruction quality, but some features may still be visible. Consider increasing noise level.'; | |
| explanationElement.style.background = '#fff3e0'; | |
| explanationElement.style.borderLeft = '4px solid #f57c00'; | |
| } else { | |
| explanationElement.textContent = '✅ Excellent! With strong differential privacy, reconstructed images are too noisy to reveal sensitive information. Training data is well protected!'; | |
| explanationElement.style.background = '#e8f5e9'; | |
| explanationElement.style.borderLeft = '4px solid #28a745'; | |
| } | |
| } | |
| } | |
| updateReconstructedImage(quality, noise) { | |
| const reconstructedImg = document.getElementById('reconstructed-img'); | |
| const privacyOverlay = document.getElementById('privacy-overlay'); | |
| const leakStatus = document.getElementById('leak-status'); | |
| const modelPrivacyLevel = document.getElementById('model-privacy-level'); | |
| if (!reconstructedImg) return; | |
| // Calculate epsilon for display | |
| const epsilon = Math.max(0.5, 10 - (parseFloat(document.getElementById('recon-clipping-slider').value) * 0.5 + noise * 2)); | |
| if (modelPrivacyLevel) { | |
| modelPrivacyLevel.textContent = `ε = ${epsilon.toFixed(1)}`; | |
| } | |
| if (quality < 0.4) { | |
| // High privacy - show heavily obscured/protected image with overlay | |
| const svg = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 500'%3E%3Cdefs%3E%3Cfilter id='heavynoise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='${1.5 + noise * 0.5}' numOctaves='8' /%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3ClinearGradient id='protectgrad' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' style='stop-color:%23e8f5e9;stop-opacity:1' /%3E%3Cstop offset='100%25' style='stop-color:%23c8e6c9;stop-opacity:1' /%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='400' height='500' fill='url(%23protectgrad)'/%3E%3Crect width='400' height='500' fill='gray' opacity='0.95' filter='url(%23heavynoise)'/%3E%3C/svg%3E`; | |
| reconstructedImg.src = svg; | |
| if (privacyOverlay) { | |
| privacyOverlay.style.display = 'flex'; | |
| privacyOverlay.innerHTML = '<div style="font-size: 3rem;">🔒</div><div>PROTECTED</div>'; | |
| } | |
| if (leakStatus) { | |
| leakStatus.innerHTML = '✅ ATTACK<br/>FAILED!'; | |
| leakStatus.style.color = '#28a745'; | |
| } | |
| } else if (quality < 0.7) { | |
| // Medium privacy - show partially obscured image | |
| const svg = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 500'%3E%3Cdefs%3E%3Cfilter id='mednoise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='${0.8 + noise * 0.3}' numOctaves='5' /%3E%3CfeColorMatrix type='saturate' values='0.2'/%3E%3C/filter%3E%3ClinearGradient id='medgrad' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' style='stop-color:%23fff3e0;stop-opacity:1' /%3E%3Cstop offset='100%25' style='stop-color:%23ffe0b2;stop-opacity:1' /%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='400' height='500' fill='url(%23medgrad)'/%3E%3Crect width='400' height='500' fill='gray' opacity='${0.5 + noise * 0.15}' filter='url(%23mednoise)'/%3E%3Cellipse cx='200' cy='180' rx='80' ry='90' fill='%238b6f47' opacity='0.25'/%3E%3Crect x='140' y='260' width='120' height='160' fill='%236b8e6b' opacity='0.2' rx='8'/%3E%3C/svg%3E`; | |
| reconstructedImg.src = svg; | |
| if (privacyOverlay) { | |
| privacyOverlay.style.display = 'none'; | |
| } | |
| if (leakStatus) { | |
| leakStatus.innerHTML = '⚠️ PARTIAL<br/>LEAK'; | |
| leakStatus.style.color = '#f57c00'; | |
| } | |
| } else { | |
| // Low privacy - show clear reconstruction (vulnerable) | |
| const svg = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 500'%3E%3Cdefs%3E%3Cfilter id='lightnoise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='${0.3 + noise * 0.1}' numOctaves='2' /%3E%3CfeColorMatrix type='saturate' values='0.4'/%3E%3C/filter%3E%3ClinearGradient id='vulngrad' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' style='stop-color:%23e8b4b8;stop-opacity:1' /%3E%3Cstop offset='100%25' style='stop-color:%23d4a5a8;stop-opacity:1' /%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='400' height='500' fill='url(%23vulngrad)'/%3E%3Crect width='400' height='500' fill='gray' opacity='${0.15 + noise * 0.05}' filter='url(%23lightnoise)'/%3E%3Cellipse cx='200' cy='180' rx='80' ry='90' fill='%238b6f47' opacity='0.5'/%3E%3Crect x='140' y='260' width='120' height='160' fill='%236b8e6b' opacity='0.4' rx='8'/%3E%3Ccircle cx='280' cy='140' r='35' fill='%23fff' opacity='0.2'/%3E%3Crect x='80' y='350' width='240' height='100' fill='%23a0826d' opacity='0.3' rx='6'/%3E%3C/svg%3E`; | |
| reconstructedImg.src = svg; | |
| if (privacyOverlay) { | |
| privacyOverlay.style.display = 'none'; | |
| } | |
| if (leakStatus) { | |
| leakStatus.innerHTML = '🚨 LEAKED<br/>PRIVATE DATA!'; | |
| leakStatus.style.color = '#c1272d'; | |
| } | |
| } | |
| } | |
| updateLinkageAttack() { | |
| const quality = parseInt(document.getElementById('linkage-quality-slider').value); | |
| const privacySlider = parseInt(document.getElementById('linkage-privacy-slider').value); | |
| // Calculate epsilon: epsilon = 11 - privacySlider | |
| // privacySlider = 1 → ε = 10.0 (LOW privacy, HIGH success) | |
| // privacySlider = 10 → ε = 1.0 (HIGH privacy, LOW success) | |
| const epsilon = 11 - privacySlider; | |
| // Calculate linkage success | |
| // Higher quality = easier to link (increases success) | |
| // Higher epsilon (lower privacy) = easier to link (increases success) | |
| const qualityBonus = quality * 10; // Quality factor (0-60) | |
| const epsilonBonus = epsilon * 4; // Epsilon: 1.0 (high privacy) = 4, 10.0 (low privacy) = 40 | |
| // Base success is 20%, add quality scaled by epsilon influence | |
| const successRate = Math.max(15, Math.min(95, 20 + qualityBonus * (epsilon / 10))); | |
| document.getElementById('linkage-success').textContent = `${Math.round(successRate)}%`; | |
| // Update confidence | |
| const confidence = document.getElementById('linkage-confidence'); | |
| if (successRate > 75) { | |
| confidence.textContent = 'High'; | |
| } else if (successRate > 50) { | |
| confidence.textContent = 'Medium'; | |
| } else { | |
| confidence.textContent = 'Low'; | |
| } | |
| } | |
| } | |
| // Attack simulation functions (called by buttons) | |
| function updatePrivacyDemo() { | |
| const simulator = window.attackSimulator; | |
| simulator.updateMembershipDemo(); | |
| // Add visual feedback | |
| const button = event.target; | |
| const originalText = button.textContent; | |
| button.textContent = 'Updating...'; | |
| button.disabled = true; | |
| setTimeout(() => { | |
| button.textContent = originalText; | |
| button.disabled = false; | |
| }, 800); | |
| } | |
| function runReconstructionAttack() { | |
| const simulator = window.attackSimulator; | |
| simulator.updateReconstructionAttack(); | |
| // Update the canvas examples | |
| updateCurrentReconstructionExamples(); | |
| // Add visual feedback | |
| const button = event.target; | |
| const originalText = button.textContent; | |
| button.textContent = 'Reconstructing...'; | |
| button.disabled = true; | |
| setTimeout(() => { | |
| button.textContent = originalText; | |
| button.disabled = false; | |
| }, 2000); | |
| } | |
| function runInversionAttack() { | |
| const classSelect = document.getElementById('inversion-class-select'); | |
| const privacySlider = document.getElementById('inversion-privacy-slider'); | |
| const selectedClass = classSelect.value; | |
| const sliderValue = parseInt(privacySlider.value); | |
| // Invert the slider: slider 1 = high privacy (ε=1), slider 10 = low privacy (ε=10) | |
| const privacyLevel = 11 - sliderValue; // For drawing function | |
| const epsilon = sliderValue; // epsilon matches slider position now | |
| const confidence = Math.max(30, 35 + (sliderValue * 6)); // Higher slider = higher confidence | |
| // Update displays | |
| document.getElementById('inversion-confidence').textContent = `${confidence}%`; | |
| document.getElementById('inversion-class').textContent = classSelect.options[classSelect.selectedIndex].text; | |
| // Update privacy bar | |
| const privacyLevels = ['Very High', 'Very High', 'High', 'High', 'Medium', 'Medium', 'Low', 'Low', 'Very Low', 'Very Low']; | |
| updateInversionPrivacyBar(sliderValue, privacyLevels[sliderValue - 1]); | |
| // Update explanation based on slider value (1=high privacy, 10=low privacy) | |
| const explanation = document.getElementById('inversion-explanation'); | |
| if (explanation) { | |
| if (sliderValue <= 3) { | |
| explanation.textContent = '✅ High privacy! The inverted features are very noisy and don\'t reveal clear class characteristics. Training data is well protected!'; | |
| explanation.style.background = '#e8f5e9'; | |
| explanation.style.borderLeft = '4px solid #4caf50'; | |
| } else if (sliderValue <= 6) { | |
| explanation.textContent = '⚠️ Medium privacy. Some class features are visible but degraded. Consider increasing privacy for sensitive data.'; | |
| explanation.style.background = '#fff3e0'; | |
| explanation.style.borderLeft = '4px solid #ff9800'; | |
| } else { | |
| explanation.textContent = '❌ Low privacy! The model reveals clear, detailed class features. An attacker can learn what the model associates with this class.'; | |
| explanation.style.background = '#ffebee'; | |
| explanation.style.borderLeft = '4px solid #f44336'; | |
| } | |
| } | |
| // Draw the inverted features on canvas | |
| drawInvertedFeatures(selectedClass, privacyLevel); | |
| // Add visual feedback | |
| const button = event.target; | |
| const originalText = button.textContent; | |
| button.textContent = 'Generating...'; | |
| button.disabled = true; | |
| setTimeout(() => { | |
| button.textContent = originalText; | |
| button.disabled = false; | |
| }, 1800); | |
| } | |
| function getPrivacyLevelText(level) { | |
| if (level <= 2) return 'Very High'; | |
| if (level <= 4) return 'High'; | |
| if (level <= 6) return 'Medium'; | |
| if (level <= 8) return 'Low'; | |
| return 'Very Low'; | |
| } | |
| function updateInversionPrivacyBar(sliderValue, privacyText) { | |
| // Privacy bar has been removed - function kept for compatibility | |
| } | |
| function drawInvertedFeatures(classDigit, privacyLevel) { | |
| const canvas = document.getElementById('inversion-canvas'); | |
| if (!canvas) return; | |
| const ctx = canvas.getContext('2d'); | |
| const width = canvas.width; | |
| const height = canvas.height; | |
| ctx.clearRect(0, 0, width, height); | |
| // Background | |
| ctx.fillStyle = '#e3f2fd'; | |
| ctx.fillRect(0, 0, width, height); | |
| // Calculate noise level based on privacy (1=high privacy/more noise, 10=low privacy/less noise) | |
| const noiseLevel = (privacyLevel - 1) / 9; // 0 to 1, where 0 = most noise | |
| const clarity = 1 - noiseLevel; // Inverted: high privacy = low clarity | |
| // Draw the digit with varying clarity | |
| ctx.save(); | |
| ctx.globalAlpha = Math.max(0.3, clarity); | |
| // Draw digit based on selected class - scale font size based on canvas size | |
| ctx.fillStyle = '#1976d2'; | |
| ctx.font = `bold ${Math.floor(width * 0.6)}px Arial`; | |
| ctx.textAlign = 'center'; | |
| ctx.textBaseline = 'middle'; | |
| ctx.fillText(classDigit, width / 2, height / 2); | |
| ctx.restore(); | |
| // Add noise based on privacy level | |
| if (privacyLevel <= 7) { | |
| const noiseIntensity = (7 - privacyLevel) / 7; // More noise for higher privacy | |
| addCanvasNoise(ctx, noiseIntensity, width, height); | |
| } | |
| // For very high privacy, add blur effect | |
| if (privacyLevel <= 3) { | |
| ctx.filter = `blur(${(4 - privacyLevel) * 4}px)`; | |
| ctx.drawImage(canvas, 0, 0); | |
| ctx.filter = 'none'; | |
| } | |
| } | |
| function addCanvasNoise(ctx, intensity, width, height) { | |
| if (intensity < 0.1) return; | |
| const imageData = ctx.getImageData(0, 0, width, height); | |
| const data = imageData.data; | |
| for (let i = 0; i < data.length; i += 4) { | |
| const noise = (Math.random() - 0.5) * intensity * 200; | |
| data[i] += noise; // R | |
| data[i + 1] += noise; // G | |
| data[i + 2] += noise; // B | |
| } | |
| ctx.putImageData(imageData, 0, 0); | |
| } | |
| function runPropertyAttack() { | |
| const propertyType = document.getElementById('property-type').value; | |
| const accessLevel = parseInt(document.getElementById('property-access-slider').value); | |
| const privacySlider = document.getElementById('property-privacy-slider'); | |
| const sliderValue = privacySlider ? parseInt(privacySlider.value) : 5; | |
| // Calculate property inference accuracy based on privacy and access | |
| // Lower privacy (higher slider value) = more accurate inference | |
| const privacyPenalty = (11 - sliderValue) * 5; // High privacy reduces accuracy | |
| const accessBonus = accessLevel * 8; | |
| const baseAccuracy = 50; | |
| const accuracy = Math.min(95, baseAccuracy + accessBonus + (sliderValue * 3)); | |
| // Update uncertainty based on privacy (high privacy = high uncertainty) | |
| const uncertainty = Math.max(2, 20 - sliderValue * 1.5 - accessLevel * 2); | |
| document.getElementById('property-male').textContent = `${52}% ± ${Math.round(uncertainty)}%`; | |
| document.getElementById('property-female').textContent = `${48}% ± ${Math.round(uncertainty)}%`; | |
| // Update privacy bar | |
| const privacyLevels = ['Very High', 'Very High', 'High', 'High', 'Medium', 'Medium', 'Low', 'Low', 'Very Low', 'Very Low']; | |
| updatePropertyPrivacyBar(sliderValue, privacyLevels[sliderValue - 1]); | |
| // Update explanation | |
| const explanation = document.getElementById('property-explanation'); | |
| if (explanation) { | |
| if (sliderValue <= 3) { | |
| explanation.textContent = '✅ High privacy! The attacker cannot accurately infer dataset properties. Large confidence intervals show high uncertainty.'; | |
| explanation.style.background = '#e8f5e9'; | |
| explanation.style.borderLeft = '4px solid #4caf50'; | |
| } else if (sliderValue <= 6) { | |
| explanation.textContent = '⚠️ Medium privacy. The attacker can infer properties with moderate accuracy. Consider increasing privacy for sensitive datasets.'; | |
| explanation.style.background = '#fff3e0'; | |
| explanation.style.borderLeft = '4px solid #ff9800'; | |
| } else { | |
| explanation.textContent = '❌ Low privacy! The attacker can accurately infer sensitive dataset properties like demographic distributions. Privacy breach risk!'; | |
| explanation.style.background = '#ffebee'; | |
| explanation.style.borderLeft = '4px solid #f44336'; | |
| } | |
| } | |
| // Add visual feedback | |
| const button = event.target; | |
| const originalText = button.textContent; | |
| button.textContent = 'Analyzing...'; | |
| button.disabled = true; | |
| setTimeout(() => { | |
| button.textContent = originalText; | |
| button.disabled = false; | |
| }, 2200); | |
| } | |
| function updatePropertyPrivacyBar(sliderValue, privacyText) { | |
| // Privacy bar has been removed - function kept for compatibility | |
| } | |
| function runLinkageAttack() { | |
| const simulator = window.attackSimulator; | |
| simulator.updateLinkageAttack(); | |
| // Add visual feedback | |
| const button = event.target; | |
| const originalText = button.textContent; | |
| button.textContent = 'Linking Data...'; | |
| button.disabled = true; | |
| setTimeout(() => { | |
| button.textContent = originalText; | |
| button.disabled = false; | |
| }, 2500); | |
| } | |
| // Draw reconstruction examples on canvases | |
| function drawReconstructionExamples() { | |
| // Example data patterns | |
| const examples = [ | |
| { // Face-like | |
| draw: (ctx, noise) => { | |
| // Head | |
| ctx.fillStyle = `rgba(139, 111, 71, ${1 - noise * 0.8})`; | |
| ctx.beginPath(); | |
| ctx.ellipse(60, 45, 30, 35, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Eyes | |
| ctx.fillStyle = `rgba(50, 50, 50, ${1 - noise * 0.9})`; | |
| ctx.fillRect(45 + Math.random() * noise * 5, 35, 8, 8); | |
| ctx.fillRect(67 + Math.random() * noise * 5, 35, 8, 8); | |
| // Nose | |
| ctx.fillStyle = `rgba(100, 80, 60, ${1 - noise * 0.85})`; | |
| ctx.fillRect(57, 50, 6, 12); | |
| // Mouth | |
| ctx.fillStyle = `rgba(80, 60, 50, ${1 - noise * 0.9})`; | |
| ctx.fillRect(48, 70, 24, 5); | |
| // Body | |
| ctx.fillStyle = `rgba(107, 142, 107, ${1 - noise * 0.8})`; | |
| ctx.fillRect(35, 85, 50, 30); | |
| } | |
| }, | |
| { // Eye-like medical scan | |
| draw: (ctx, noise) => { | |
| // Dark background | |
| ctx.fillStyle = '#1a1a1a'; | |
| ctx.fillRect(0, 0, 120, 120); | |
| // Eye socket (bright) | |
| ctx.fillStyle = `rgba(255, 200, 100, ${0.9 - noise * 0.7})`; | |
| ctx.beginPath(); | |
| ctx.ellipse(60, 60, 35, 30, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Pupil (dark) | |
| ctx.fillStyle = `rgba(20, 20, 20, ${1 - noise * 0.8})`; | |
| ctx.beginPath(); | |
| ctx.ellipse(60, 60, 15, 15, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Highlight | |
| ctx.fillStyle = `rgba(255, 255, 255, ${0.8 - noise * 0.7})`; | |
| ctx.beginPath(); | |
| ctx.arc(55, 52, 5, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| }, | |
| { // Medical scan with bright center | |
| draw: (ctx, noise) => { | |
| // Dark background | |
| ctx.fillStyle = '#0a0a0a'; | |
| ctx.fillRect(0, 0, 120, 120); | |
| // Bright center (hot spot) | |
| const gradient = ctx.createRadialGradient(60, 60, 5, 60, 60, 40); | |
| gradient.addColorStop(0, `rgba(255, 100, 50, ${1 - noise * 0.6})`); | |
| gradient.addColorStop(0.5, `rgba(200, 80, 40, ${0.8 - noise * 0.6})`); | |
| gradient.addColorStop(1, `rgba(100, 40, 20, ${0.3 - noise * 0.3})`); | |
| ctx.fillStyle = gradient; | |
| ctx.fillRect(0, 0, 120, 120); | |
| // Green markers | |
| ctx.fillStyle = `rgba(100, 255, 100, ${0.7 - noise * 0.6})`; | |
| ctx.fillRect(20, 20, 8, 8); | |
| ctx.fillRect(92, 20, 8, 8); | |
| ctx.fillRect(20, 92, 8, 8); | |
| ctx.fillRect(92, 92, 8, 8); | |
| } | |
| }, | |
| { // Computer/object | |
| draw: (ctx, noise) => { | |
| // Screen | |
| ctx.fillStyle = `rgba(100, 150, 255, ${0.9 - noise * 0.7})`; | |
| ctx.fillRect(30, 20, 60, 45); | |
| // Screen content | |
| ctx.fillStyle = `rgba(200, 220, 255, ${0.8 - noise * 0.7})`; | |
| ctx.fillRect(35, 25, 50, 35); | |
| // Base | |
| ctx.fillStyle = `rgba(80, 80, 80, ${1 - noise * 0.8})`; | |
| ctx.fillRect(40, 70, 40, 8); | |
| // Stand | |
| ctx.fillStyle = `rgba(100, 100, 100, ${1 - noise * 0.8})`; | |
| ctx.fillRect(55, 65, 10, 15); | |
| // Keyboard | |
| ctx.fillStyle = `rgba(60, 60, 60, ${0.9 - noise * 0.7})`; | |
| ctx.fillRect(20, 85, 80, 25); | |
| } | |
| } | |
| ]; | |
| // Draw each example at different privacy levels | |
| examples.forEach((example, idx) => { | |
| const exampleNum = idx + 1; | |
| // Ground truth (original) | |
| const originalCanvas = document.getElementById(`original-${exampleNum}`); | |
| if (originalCanvas) { | |
| const ctx = originalCanvas.getContext('2d'); | |
| ctx.clearRect(0, 0, 120, 120); | |
| example.draw(ctx, 0); | |
| } | |
| // No privacy (nearly perfect reconstruction - privacy breach!) | |
| const noPrivacyCanvas = document.getElementById(`no-privacy-${exampleNum}`); | |
| if (noPrivacyCanvas) { | |
| const ctx = noPrivacyCanvas.getContext('2d'); | |
| ctx.clearRect(0, 0, 120, 120); | |
| example.draw(ctx, 0.02); // Almost no degradation | |
| addNoise(ctx, 0.01, 120, 120); // Minimal noise | |
| } | |
| // High privacy (ε = 0.1) - complete noise/static | |
| const highPrivacyCanvas = document.getElementById(`high-privacy-${exampleNum}`); | |
| if (highPrivacyCanvas) { | |
| const ctx = highPrivacyCanvas.getContext('2d'); | |
| ctx.clearRect(0, 0, 120, 120); | |
| // Draw pure random noise - no original features visible | |
| drawCompleteNoise(ctx, 120, 120); | |
| } | |
| }); | |
| // Update current setting examples | |
| updateCurrentReconstructionExamples(); | |
| } | |
| function updateCurrentReconstructionExamples() { | |
| const clipping = parseFloat(document.getElementById('recon-clipping-slider')?.value || 1.0); | |
| const noise = parseFloat(document.getElementById('recon-noise-slider')?.value || 1.0); | |
| // Calculate quality/noise level | |
| const quality = Math.max(0.05, 0.95 - (5 - clipping) * 0.08 - noise * 0.22); | |
| const noiseLevel = 1 - quality; | |
| // Calculate epsilon | |
| const epsilon = Math.max(0.5, 10 - (clipping * 0.5 + noise * 2)); | |
| // Update epsilon display | |
| const epsilonDisplay = document.getElementById('current-epsilon'); | |
| if (epsilonDisplay) { | |
| epsilonDisplay.textContent = `(ε = ${epsilon.toFixed(1)})`; | |
| } | |
| // Example patterns (same as above) | |
| const examples = [ | |
| { // Face-like | |
| draw: (ctx, noise) => { | |
| ctx.fillStyle = `rgba(139, 111, 71, ${1 - noise * 0.8})`; | |
| ctx.beginPath(); | |
| ctx.ellipse(60, 45, 30, 35, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.fillStyle = `rgba(50, 50, 50, ${1 - noise * 0.9})`; | |
| ctx.fillRect(45 + Math.random() * noise * 5, 35, 8, 8); | |
| ctx.fillRect(67 + Math.random() * noise * 5, 35, 8, 8); | |
| ctx.fillStyle = `rgba(100, 80, 60, ${1 - noise * 0.85})`; | |
| ctx.fillRect(57, 50, 6, 12); | |
| ctx.fillStyle = `rgba(80, 60, 50, ${1 - noise * 0.9})`; | |
| ctx.fillRect(48, 70, 24, 5); | |
| ctx.fillStyle = `rgba(107, 142, 107, ${1 - noise * 0.8})`; | |
| ctx.fillRect(35, 85, 50, 30); | |
| } | |
| }, | |
| { // Eye-like | |
| draw: (ctx, noise) => { | |
| ctx.fillStyle = '#1a1a1a'; | |
| ctx.fillRect(0, 0, 120, 120); | |
| ctx.fillStyle = `rgba(255, 200, 100, ${0.9 - noise * 0.7})`; | |
| ctx.beginPath(); | |
| ctx.ellipse(60, 60, 35, 30, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.fillStyle = `rgba(20, 20, 20, ${1 - noise * 0.8})`; | |
| ctx.beginPath(); | |
| ctx.ellipse(60, 60, 15, 15, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.fillStyle = `rgba(255, 255, 255, ${0.8 - noise * 0.7})`; | |
| ctx.beginPath(); | |
| ctx.arc(55, 52, 5, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| }, | |
| { // Medical scan | |
| draw: (ctx, noise) => { | |
| ctx.fillStyle = '#0a0a0a'; | |
| ctx.fillRect(0, 0, 120, 120); | |
| const gradient = ctx.createRadialGradient(60, 60, 5, 60, 60, 40); | |
| gradient.addColorStop(0, `rgba(255, 100, 50, ${1 - noise * 0.6})`); | |
| gradient.addColorStop(0.5, `rgba(200, 80, 40, ${0.8 - noise * 0.6})`); | |
| gradient.addColorStop(1, `rgba(100, 40, 20, ${0.3 - noise * 0.3})`); | |
| ctx.fillStyle = gradient; | |
| ctx.fillRect(0, 0, 120, 120); | |
| ctx.fillStyle = `rgba(100, 255, 100, ${0.7 - noise * 0.6})`; | |
| ctx.fillRect(20, 20, 8, 8); | |
| ctx.fillRect(92, 20, 8, 8); | |
| ctx.fillRect(20, 92, 8, 8); | |
| ctx.fillRect(92, 92, 8, 8); | |
| } | |
| }, | |
| { // Computer | |
| draw: (ctx, noise) => { | |
| ctx.fillStyle = `rgba(100, 150, 255, ${0.9 - noise * 0.7})`; | |
| ctx.fillRect(30, 20, 60, 45); | |
| ctx.fillStyle = `rgba(200, 220, 255, ${0.8 - noise * 0.7})`; | |
| ctx.fillRect(35, 25, 50, 35); | |
| ctx.fillStyle = `rgba(80, 80, 80, ${1 - noise * 0.8})`; | |
| ctx.fillRect(40, 70, 40, 8); | |
| ctx.fillStyle = `rgba(100, 100, 100, ${1 - noise * 0.8})`; | |
| ctx.fillRect(55, 65, 10, 15); | |
| ctx.fillStyle = `rgba(60, 60, 60, ${0.9 - noise * 0.7})`; | |
| ctx.fillRect(20, 85, 80, 25); | |
| } | |
| } | |
| ]; | |
| // Draw current setting for each example | |
| examples.forEach((example, idx) => { | |
| const currentCanvas = document.getElementById(`current-${idx + 1}`); | |
| if (currentCanvas) { | |
| const ctx = currentCanvas.getContext('2d'); | |
| ctx.clearRect(0, 0, 120, 120); | |
| // If very high privacy (epsilon < 1.0), show mostly noise | |
| if (epsilon < 1.0) { | |
| // Draw mostly random noise with tiny hint of original | |
| example.draw(ctx, 0.98); | |
| drawCompleteNoise(ctx, 120, 120); | |
| } else if (epsilon < 3.0) { | |
| // High privacy - heavy noise | |
| example.draw(ctx, noiseLevel); | |
| addNoise(ctx, Math.min(0.85, noiseLevel * 1.2), 120, 120); | |
| } else if (epsilon < 6.0) { | |
| // Medium privacy - moderate noise | |
| example.draw(ctx, noiseLevel * 0.7); | |
| addNoise(ctx, noiseLevel * 0.8, 120, 120); | |
| } else { | |
| // Low privacy - more visible noise (increased from 0.4 to 0.6) | |
| example.draw(ctx, noiseLevel * 0.4); | |
| addNoise(ctx, Math.max(0.3, noiseLevel * 0.6), 120, 120); | |
| } | |
| } | |
| }); | |
| } | |
| function addNoise(ctx, intensity, width, height) { | |
| if (intensity < 0.05) return; | |
| const imageData = ctx.getImageData(0, 0, width, height); | |
| const data = imageData.data; | |
| // For high intensity, make it more aggressive | |
| if (intensity > 0.7) { | |
| // Very high noise - almost complete randomization | |
| for (let i = 0; i < data.length; i += 4) { | |
| const randomness = intensity * 1.5; | |
| data[i] = Math.random() * 255 * randomness + data[i] * (1 - randomness); // R | |
| data[i + 1] = Math.random() * 255 * randomness + data[i + 1] * (1 - randomness); // G | |
| data[i + 2] = Math.random() * 255 * randomness + data[i + 2] * (1 - randomness); // B | |
| } | |
| } else { | |
| // Normal noise addition | |
| for (let i = 0; i < data.length; i += 4) { | |
| const noise = (Math.random() - 0.5) * intensity * 300; | |
| data[i] += noise; // R | |
| data[i + 1] += noise; // G | |
| data[i + 2] += noise; // B | |
| } | |
| } | |
| ctx.putImageData(imageData, 0, 0); | |
| } | |
| function drawCompleteNoise(ctx, width, height) { | |
| // Draw pure random static - complete privacy protection | |
| const imageData = ctx.createImageData(width, height); | |
| const data = imageData.data; | |
| for (let i = 0; i < data.length; i += 4) { | |
| // Random RGB values - pure noise | |
| data[i] = Math.random() * 255; // R | |
| data[i + 1] = Math.random() * 255; // G | |
| data[i + 2] = Math.random() * 255; // B | |
| data[i + 3] = 255; // A (fully opaque) | |
| } | |
| ctx.putImageData(imageData, 0, 0); | |
| } | |
| // Initialize when page loads | |
| document.addEventListener('DOMContentLoaded', function() { | |
| window.attackSimulator = new AttackSimulator(); | |
| // Track initial active tab | |
| const active = document.querySelector('.attack-tab.active'); | |
| if (active) { | |
| track('attack_tab_open', { | |
| attack: active.dataset.attack, | |
| page: 'privacy_attacks', | |
| reason: 'page_load' | |
| }); | |
| } | |
| // Run initial updates | |
| window.attackSimulator.updateMembershipDemo(); | |
| window.attackSimulator.updateReconstructionAttack(); | |
| window.attackSimulator.updateLinkageAttack(); | |
| // Draw reconstruction examples | |
| setTimeout(() => { | |
| drawReconstructionExamples(); | |
| }, 100); | |
| // Initialize inversion canvas and privacy bar with default values | |
| setTimeout(() => { | |
| drawInvertedFeatures('7', 6); // Default: digit 7, privacy level 6 (slider=5, inverted to 6) | |
| updateInversionPrivacyBar(5, 'Medium'); // Initialize privacy bar at slider position 5 | |
| updatePropertyPrivacyBar(5, 'Medium'); // Initialize property privacy bar | |
| }, 100); | |
| // Add event listeners for inversion attack controls | |
| const inversionPrivacySlider = document.getElementById('inversion-privacy-slider'); | |
| if (inversionPrivacySlider) { | |
| inversionPrivacySlider.addEventListener('input', function() { | |
| const sliderValue = parseInt(this.value); | |
| // Invert the slider: slider 1 = high privacy, slider 10 = low privacy | |
| const privacyLevel = 11 - sliderValue; | |
| const privacyLevels = ['Very High', 'Very High', 'High', 'High', 'Medium', 'Medium', 'Low', 'Low', 'Very Low', 'Very Low']; | |
| document.getElementById('inversion-privacy').textContent = privacyLevels[sliderValue - 1]; | |
| // Update visualization in real-time | |
| const classSelect = document.getElementById('inversion-class-select'); | |
| const selectedClass = classSelect ? classSelect.value : '7'; | |
| drawInvertedFeatures(selectedClass, privacyLevel); | |
| // Update privacy bar indicator | |
| updateInversionPrivacyBar(sliderValue, privacyLevels[sliderValue - 1]); | |
| // Update explanation based on slider value (1=high privacy, 10=low privacy) | |
| const explanation = document.getElementById('inversion-explanation'); | |
| if (explanation) { | |
| if (sliderValue <= 3) { | |
| explanation.textContent = '✅ High privacy! The inverted features are very noisy and don\'t reveal clear class characteristics. Training data is well protected!'; | |
| explanation.style.background = '#e8f5e9'; | |
| explanation.style.borderLeft = '4px solid #4caf50'; | |
| } else if (sliderValue <= 6) { | |
| explanation.textContent = '⚠️ Medium privacy. Some class features are visible but degraded. Consider increasing privacy for sensitive data.'; | |
| explanation.style.background = '#fff3e0'; | |
| explanation.style.borderLeft = '4px solid #ff9800'; | |
| } else { | |
| explanation.textContent = '❌ Low privacy! The model reveals clear, detailed class features. An attacker can learn what the model associates with this class.'; | |
| explanation.style.background = '#ffebee'; | |
| explanation.style.borderLeft = '4px solid #f44336'; | |
| } | |
| } | |
| }); | |
| } | |
| // Add event listener for class selection | |
| const inversionClassSelect = document.getElementById('inversion-class-select'); | |
| if (inversionClassSelect) { | |
| inversionClassSelect.addEventListener('change', function() { | |
| const privacySlider = document.getElementById('inversion-privacy-slider'); | |
| const sliderValue = privacySlider ? parseInt(privacySlider.value) : 5; | |
| const privacyLevel = 11 - sliderValue; // Invert for drawing | |
| drawInvertedFeatures(this.value, privacyLevel); | |
| document.getElementById('inversion-class').textContent = this.options[this.selectedIndex].text; | |
| }); | |
| } | |
| // Add event listeners for property inference attack controls | |
| const propertyPrivacySlider = document.getElementById('property-privacy-slider'); | |
| if (propertyPrivacySlider) { | |
| propertyPrivacySlider.addEventListener('input', function() { | |
| const sliderValue = parseInt(this.value); | |
| const privacyLevels = ['Very High', 'Very High', 'High', 'High', 'Medium', 'Medium', 'Low', 'Low', 'Very Low', 'Very Low']; | |
| document.getElementById('property-privacy').textContent = privacyLevels[sliderValue - 1]; | |
| // Update privacy bar in real-time | |
| updatePropertyPrivacyBar(sliderValue, privacyLevels[sliderValue - 1]); | |
| // Update uncertainty display in real-time | |
| const accessLevel = parseInt(document.getElementById('property-access-slider').value); | |
| const uncertainty = Math.max(2, 20 - sliderValue * 1.5 - accessLevel * 2); | |
| document.getElementById('property-male').textContent = `${52}% ± ${Math.round(uncertainty)}%`; | |
| document.getElementById('property-female').textContent = `${48}% ± ${Math.round(uncertainty)}%`; | |
| // Update explanation | |
| const explanation = document.getElementById('property-explanation'); | |
| if (explanation) { | |
| if (sliderValue <= 3) { | |
| explanation.textContent = '✅ High privacy! The attacker cannot accurately infer dataset properties. Large confidence intervals show high uncertainty.'; | |
| explanation.style.background = '#e8f5e9'; | |
| explanation.style.borderLeft = '4px solid #4caf50'; | |
| } else if (sliderValue <= 6) { | |
| explanation.textContent = '⚠️ Medium privacy. The attacker can infer properties with moderate accuracy. Consider increasing privacy for sensitive datasets.'; | |
| explanation.style.background = '#fff3e0'; | |
| explanation.style.borderLeft = '4px solid #ff9800'; | |
| } else { | |
| explanation.textContent = '❌ Low privacy! The attacker can accurately infer sensitive dataset properties like demographic distributions. Privacy breach risk!'; | |
| explanation.style.background = '#ffebee'; | |
| explanation.style.borderLeft = '4px solid #f44336'; | |
| } | |
| } | |
| }); | |
| } | |
| // Add event listener for model access slider | |
| const propertyAccessSlider = document.getElementById('property-access-slider'); | |
| if (propertyAccessSlider) { | |
| propertyAccessSlider.addEventListener('input', function() { | |
| const accessLevel = parseInt(this.value); | |
| const accessLevels = ['Black-box', 'Gray-box', 'White-box']; | |
| document.getElementById('property-access').textContent = accessLevels[accessLevel - 1]; | |
| // Update uncertainty display when access changes | |
| const privacySlider = document.getElementById('property-privacy-slider'); | |
| const sliderValue = privacySlider ? parseInt(privacySlider.value) : 5; | |
| const uncertainty = Math.max(2, 20 - sliderValue * 1.5 - accessLevel * 2); | |
| document.getElementById('property-male').textContent = `${52}% ± ${Math.round(uncertainty)}%`; | |
| document.getElementById('property-female').textContent = `${48}% ± ${Math.round(uncertainty)}%`; | |
| }); | |
| } | |
| }); | |