|
|
|
|
|
class AttackSimulator { |
|
|
constructor() { |
|
|
this.charts = {}; |
|
|
this.initializeEventListeners(); |
|
|
this.initializeCharts(); |
|
|
} |
|
|
|
|
|
initializeEventListeners() { |
|
|
|
|
|
document.querySelectorAll('.attack-tab').forEach(tab => { |
|
|
tab.addEventListener('click', (e) => { |
|
|
this.switchTab(e.target.dataset.attack); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
this.setupSliderUpdates(); |
|
|
} |
|
|
|
|
|
setupSliderUpdates() { |
|
|
|
|
|
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(); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
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(); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
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(); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
switchTab(attackType) { |
|
|
|
|
|
document.querySelectorAll('.attack-tab').forEach(tab => { |
|
|
tab.classList.remove('active'); |
|
|
}); |
|
|
document.querySelector(`[data-attack="${attackType}"]`).classList.add('active'); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.attack-content').forEach(content => { |
|
|
content.classList.remove('active'); |
|
|
}); |
|
|
document.getElementById(`${attackType}-content`).classList.add('active'); |
|
|
|
|
|
|
|
|
this.initializeTabChart(attackType); |
|
|
} |
|
|
|
|
|
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 |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
updateMembershipDemo() { |
|
|
const privacyLevel = parseInt(document.getElementById('privacy-level-slider').value); |
|
|
|
|
|
|
|
|
const successRates = [45, 52, 65, 78, 87]; |
|
|
const successRate = successRates[privacyLevel - 1]; |
|
|
|
|
|
|
|
|
const confidenceDiffs = [8, 12, 18, 24, 28]; |
|
|
const trainingConf = [76, 80, 86, 92, 96]; |
|
|
const testConf = trainingConf.map((tc, i) => tc - confidenceDiffs[i]); |
|
|
|
|
|
const currentTrainingConf = trainingConf[privacyLevel - 1]; |
|
|
const currentTestConf = testConf[privacyLevel - 1]; |
|
|
const currentDiff = confidenceDiffs[privacyLevel - 1]; |
|
|
|
|
|
|
|
|
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}%`; |
|
|
|
|
|
|
|
|
document.getElementById('membership-success').textContent = `${successRate}%`; |
|
|
|
|
|
|
|
|
const circle = document.getElementById('success-rate-circle'); |
|
|
if (successRate < 55) { |
|
|
circle.style.background = 'linear-gradient(135deg, #28a745, #20c997)'; |
|
|
} else if (successRate < 70) { |
|
|
circle.style.background = 'linear-gradient(135deg, #ffc107, #fd7e14)'; |
|
|
} else { |
|
|
circle.style.background = 'linear-gradient(135deg, #dc3545, #fd7e14)'; |
|
|
} |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
const ssimElement = document.getElementById('ssim-score'); |
|
|
if (ssimElement) { |
|
|
ssimElement.textContent = ssimScore; |
|
|
ssimElement.style.color = quality > 0.7 ? '#dc3545' : quality > 0.4 ? '#f57c00' : '#28a745'; |
|
|
} |
|
|
|
|
|
|
|
|
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'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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'; |
|
|
} |
|
|
|
|
|
|
|
|
this.updateReconstructedImage(quality, noise); |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
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) { |
|
|
|
|
|
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 { |
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const epsilon = 11 - privacySlider; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const qualityBonus = quality * 10; |
|
|
const epsilonBonus = epsilon * 4; |
|
|
|
|
|
|
|
|
const successRate = Math.max(15, Math.min(95, 20 + qualityBonus * (epsilon / 10))); |
|
|
|
|
|
document.getElementById('linkage-success').textContent = `${Math.round(successRate)}%`; |
|
|
|
|
|
|
|
|
const confidence = document.getElementById('linkage-confidence'); |
|
|
if (successRate > 75) { |
|
|
confidence.textContent = 'High'; |
|
|
} else if (successRate > 50) { |
|
|
confidence.textContent = 'Medium'; |
|
|
} else { |
|
|
confidence.textContent = 'Low'; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function updatePrivacyDemo() { |
|
|
const simulator = window.attackSimulator; |
|
|
simulator.updateMembershipDemo(); |
|
|
|
|
|
|
|
|
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(); |
|
|
|
|
|
|
|
|
updateCurrentReconstructionExamples(); |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
const privacyLevel = 11 - sliderValue; |
|
|
const epsilon = sliderValue; |
|
|
const confidence = Math.max(30, 35 + (sliderValue * 6)); |
|
|
|
|
|
|
|
|
document.getElementById('inversion-confidence').textContent = `${confidence}%`; |
|
|
document.getElementById('inversion-class').textContent = classSelect.options[classSelect.selectedIndex].text; |
|
|
|
|
|
|
|
|
const privacyLevels = ['Very High', 'Very High', 'High', 'High', 'Medium', 'Medium', 'Low', 'Low', 'Very Low', 'Very Low']; |
|
|
updateInversionPrivacyBar(sliderValue, privacyLevels[sliderValue - 1]); |
|
|
|
|
|
|
|
|
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'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
drawInvertedFeatures(selectedClass, privacyLevel); |
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
} |
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
ctx.fillStyle = '#e3f2fd'; |
|
|
ctx.fillRect(0, 0, width, height); |
|
|
|
|
|
|
|
|
const noiseLevel = (privacyLevel - 1) / 9; |
|
|
const clarity = 1 - noiseLevel; |
|
|
|
|
|
|
|
|
ctx.save(); |
|
|
ctx.globalAlpha = Math.max(0.3, clarity); |
|
|
|
|
|
|
|
|
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(); |
|
|
|
|
|
|
|
|
if (privacyLevel <= 7) { |
|
|
const noiseIntensity = (7 - privacyLevel) / 7; |
|
|
addCanvasNoise(ctx, noiseIntensity, width, height); |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
data[i + 1] += noise; |
|
|
data[i + 2] += noise; |
|
|
} |
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
|
|
|
const privacyPenalty = (11 - sliderValue) * 5; |
|
|
const accessBonus = accessLevel * 8; |
|
|
const baseAccuracy = 50; |
|
|
const accuracy = Math.min(95, baseAccuracy + accessBonus + (sliderValue * 3)); |
|
|
|
|
|
|
|
|
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)}%`; |
|
|
|
|
|
|
|
|
const privacyLevels = ['Very High', 'Very High', 'High', 'High', 'Medium', 'Medium', 'Low', 'Low', 'Very Low', 'Very Low']; |
|
|
updatePropertyPrivacyBar(sliderValue, privacyLevels[sliderValue - 1]); |
|
|
|
|
|
|
|
|
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'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
} |
|
|
|
|
|
function runLinkageAttack() { |
|
|
const simulator = window.attackSimulator; |
|
|
simulator.updateLinkageAttack(); |
|
|
|
|
|
|
|
|
const button = event.target; |
|
|
const originalText = button.textContent; |
|
|
button.textContent = 'Linking Data...'; |
|
|
button.disabled = true; |
|
|
|
|
|
setTimeout(() => { |
|
|
button.textContent = originalText; |
|
|
button.disabled = false; |
|
|
}, 2500); |
|
|
} |
|
|
|
|
|
|
|
|
function drawReconstructionExamples() { |
|
|
|
|
|
const examples = [ |
|
|
{ |
|
|
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); |
|
|
} |
|
|
}, |
|
|
{ |
|
|
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(); |
|
|
} |
|
|
}, |
|
|
{ |
|
|
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); |
|
|
} |
|
|
}, |
|
|
{ |
|
|
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); |
|
|
} |
|
|
} |
|
|
]; |
|
|
|
|
|
|
|
|
examples.forEach((example, idx) => { |
|
|
const exampleNum = idx + 1; |
|
|
|
|
|
|
|
|
const originalCanvas = document.getElementById(`original-${exampleNum}`); |
|
|
if (originalCanvas) { |
|
|
const ctx = originalCanvas.getContext('2d'); |
|
|
ctx.clearRect(0, 0, 120, 120); |
|
|
example.draw(ctx, 0); |
|
|
} |
|
|
|
|
|
|
|
|
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); |
|
|
addNoise(ctx, 0.01, 120, 120); |
|
|
} |
|
|
|
|
|
|
|
|
const highPrivacyCanvas = document.getElementById(`high-privacy-${exampleNum}`); |
|
|
if (highPrivacyCanvas) { |
|
|
const ctx = highPrivacyCanvas.getContext('2d'); |
|
|
ctx.clearRect(0, 0, 120, 120); |
|
|
|
|
|
drawCompleteNoise(ctx, 120, 120); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
const quality = Math.max(0.05, 0.95 - (5 - clipping) * 0.08 - noise * 0.22); |
|
|
const noiseLevel = 1 - quality; |
|
|
|
|
|
|
|
|
const epsilon = Math.max(0.5, 10 - (clipping * 0.5 + noise * 2)); |
|
|
|
|
|
|
|
|
const epsilonDisplay = document.getElementById('current-epsilon'); |
|
|
if (epsilonDisplay) { |
|
|
epsilonDisplay.textContent = `(ε = ${epsilon.toFixed(1)})`; |
|
|
} |
|
|
|
|
|
|
|
|
const examples = [ |
|
|
{ |
|
|
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); |
|
|
} |
|
|
}, |
|
|
{ |
|
|
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(); |
|
|
} |
|
|
}, |
|
|
{ |
|
|
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); |
|
|
} |
|
|
}, |
|
|
{ |
|
|
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); |
|
|
} |
|
|
} |
|
|
]; |
|
|
|
|
|
|
|
|
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 (epsilon < 1.0) { |
|
|
|
|
|
example.draw(ctx, 0.98); |
|
|
drawCompleteNoise(ctx, 120, 120); |
|
|
} else if (epsilon < 3.0) { |
|
|
|
|
|
example.draw(ctx, noiseLevel); |
|
|
addNoise(ctx, Math.min(0.85, noiseLevel * 1.2), 120, 120); |
|
|
} else if (epsilon < 6.0) { |
|
|
|
|
|
example.draw(ctx, noiseLevel * 0.7); |
|
|
addNoise(ctx, noiseLevel * 0.8, 120, 120); |
|
|
} else { |
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
if (intensity > 0.7) { |
|
|
|
|
|
for (let i = 0; i < data.length; i += 4) { |
|
|
const randomness = intensity * 1.5; |
|
|
data[i] = Math.random() * 255 * randomness + data[i] * (1 - randomness); |
|
|
data[i + 1] = Math.random() * 255 * randomness + data[i + 1] * (1 - randomness); |
|
|
data[i + 2] = Math.random() * 255 * randomness + data[i + 2] * (1 - randomness); |
|
|
} |
|
|
} else { |
|
|
|
|
|
for (let i = 0; i < data.length; i += 4) { |
|
|
const noise = (Math.random() - 0.5) * intensity * 300; |
|
|
data[i] += noise; |
|
|
data[i + 1] += noise; |
|
|
data[i + 2] += noise; |
|
|
} |
|
|
} |
|
|
|
|
|
ctx.putImageData(imageData, 0, 0); |
|
|
} |
|
|
|
|
|
function drawCompleteNoise(ctx, width, height) { |
|
|
|
|
|
const imageData = ctx.createImageData(width, height); |
|
|
const data = imageData.data; |
|
|
|
|
|
for (let i = 0; i < data.length; i += 4) { |
|
|
|
|
|
data[i] = Math.random() * 255; |
|
|
data[i + 1] = Math.random() * 255; |
|
|
data[i + 2] = Math.random() * 255; |
|
|
data[i + 3] = 255; |
|
|
} |
|
|
|
|
|
ctx.putImageData(imageData, 0, 0); |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
window.attackSimulator = new AttackSimulator(); |
|
|
|
|
|
|
|
|
window.attackSimulator.updateMembershipDemo(); |
|
|
window.attackSimulator.updateReconstructionAttack(); |
|
|
window.attackSimulator.updateLinkageAttack(); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
drawReconstructionExamples(); |
|
|
}, 100); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
drawInvertedFeatures('7', 6); |
|
|
updateInversionPrivacyBar(5, 'Medium'); |
|
|
updatePropertyPrivacyBar(5, 'Medium'); |
|
|
}, 100); |
|
|
|
|
|
|
|
|
const inversionPrivacySlider = document.getElementById('inversion-privacy-slider'); |
|
|
if (inversionPrivacySlider) { |
|
|
inversionPrivacySlider.addEventListener('input', function() { |
|
|
const sliderValue = parseInt(this.value); |
|
|
|
|
|
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]; |
|
|
|
|
|
|
|
|
const classSelect = document.getElementById('inversion-class-select'); |
|
|
const selectedClass = classSelect ? classSelect.value : '7'; |
|
|
drawInvertedFeatures(selectedClass, privacyLevel); |
|
|
|
|
|
|
|
|
updateInversionPrivacyBar(sliderValue, privacyLevels[sliderValue - 1]); |
|
|
|
|
|
|
|
|
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'; |
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
drawInvertedFeatures(this.value, privacyLevel); |
|
|
document.getElementById('inversion-class').textContent = this.options[this.selectedIndex].text; |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
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]; |
|
|
|
|
|
|
|
|
updatePropertyPrivacyBar(sliderValue, privacyLevels[sliderValue - 1]); |
|
|
|
|
|
|
|
|
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)}%`; |
|
|
|
|
|
|
|
|
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'; |
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
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]; |
|
|
|
|
|
|
|
|
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)}%`; |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|