// 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);
});
});
// Slider updates
this.setupSliderUpdates();
}
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();
});
}
}
switchTab(attackType) {
// 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);
}
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 = '
🔒
PROTECTED
';
}
if (leakStatus) {
leakStatus.innerHTML = '✅ ATTACK
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
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
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();
// 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)}%`;
});
}
});