Below is a simplified chip executing a PIN check. In Normal mode the check runs correctly. Press Inject Fault to fire a voltage glitch at the comparison instruction — with the right timing the chip skips it entirely and unlocks.
<!-- Fault-injection simulator — pipeline visualization with optional dual-redundancy check -->
<div class="panel">
<div class="mode-bar">
<label class="toggle-label">
<input type="checkbox" id="redundancy-toggle">
<span>Dual Redundancy</span>
</label>
</div>
<div class="chip-view" id="chip-view">
<div class="chip-title">Secure Chip — PIN Check Pipeline</div>
<div class="pipeline" id="pipeline">
<div class="stage" id="stage-fetch">FETCH</div>
<div class="arrow">→</div>
<div class="stage" id="stage-decode">DECODE</div>
<div class="arrow">→</div>
<div class="stage" id="stage-cmp" title="Compare instruction — the guard">CMP</div>
<div class="arrow">→</div>
<div class="stage" id="stage-branch">BRANCH</div>
<div class="arrow">→</div>
<div class="stage" id="stage-out">OUT</div>
</div>
<div class="path-row" id="path-row">
<div class="path-box" id="path-a">
<span class="path-label">Path A</span>
<span class="path-result" id="path-a-result">—</span>
</div>
<div class="path-box" id="path-b">
<span class="path-label">Path B</span>
<span class="path-result" id="path-b-result">—</span>
</div>
</div>
<div class="glitch-indicator" id="glitch-indicator">⚡ GLITCH ACTIVE</div>
</div>
<div class="log" id="log"></div>
<div class="controls">
<button id="btn-normal" type="button">Normal Run</button>
<button id="btn-glitch" type="button" class="danger">Inject Fault</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div class="result-banner" id="result-banner"></div>
</div>
/* Styles for the fault-injection demo */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; font-size: 14px; color: #222; }
.panel { display: flex; flex-direction: column; gap: .6rem; padding: .6rem; }
.mode-bar { display: flex; align-items: center; gap: .5rem; }
.toggle-label { display: flex; align-items: center; gap: .5rem; cursor: pointer; font-weight: 600; font-size: .85rem; }
.toggle-label input { width: 36px; height: 20px; cursor: pointer; accent-color: #1d6fa8; }
.chip-view { background: #f0f4f8; border: 1.5px solid #c5d4e0; border-radius: 10px; padding: .7rem .8rem; display: flex; flex-direction: column; gap: .5rem; }
.chip-title { font-size: .75rem; font-weight: 700; color: #555; text-transform: uppercase; letter-spacing: .05em; }
.pipeline { display: flex; align-items: center; gap: .25rem; flex-wrap: wrap; }
.stage { background: #dce8f0; border: 1px solid #b0c8d8; border-radius: 6px; padding: .3rem .5rem; font-size: .78rem; font-weight: 600; color: #1d3557; min-width: 52px; text-align: center; transition: background .25s, border-color .25s; }
.stage.active { background: #1d6fa8; border-color: #155a8a; color: #fff; }
.stage.glitched { background: #e63946; border-color: #b52a33; color: #fff; animation: shake .25s; }
.stage.ok { background: #2a9d5c; border-color: #1e7a47; color: #fff; }
.arrow { color: #8fa8ba; font-size: 1rem; }
.path-row { display: flex; gap: .5rem; }
.path-box { flex: 1; background: #e8eef4; border: 1px solid #c0d0dc; border-radius: 6px; padding: .35rem .5rem; display: flex; justify-content: space-between; align-items: center; font-size: .8rem; }
.path-box.hidden { visibility: hidden; }
.path-label { font-weight: 600; color: #444; }
.path-result { font-weight: 700; color: #888; }
.path-result.pass { color: #2a9d5c; }
.path-result.fail { color: #e63946; }
.path-result.skip { color: #e09b00; }
.glitch-indicator { display: none; background: #fff3cd; border: 1px solid #e0a800; border-radius: 5px; padding: .2rem .5rem; font-size: .78rem; font-weight: 700; color: #856404; }
.glitch-indicator.visible { display: block; }
.log { background: #1a1a2e; color: #a0d8a0; border-radius: 7px; padding: .5rem .7rem; min-height: 4.5rem; max-height: 120px; overflow-y: auto; font: 12px/1.5 ui-monospace, monospace; }
.log .err { color: #ff7070; }
.log .warn { color: #ffd080; }
.log .info { color: #a0d8a0; }
.controls { display: flex; gap: .4rem; flex-wrap: wrap; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .8rem; border-radius: 7px; cursor: pointer; border: 1px solid #1d3557; background: #1d3557; color: #fff; }
button.danger { background: #c0392b; border-color: #922b21; }
button.ghost { background: #fff; color: #1d3557; }
.result-banner { min-height: 1.6em; font-size: .95rem; font-weight: 700; text-align: center; border-radius: 6px; padding: .3rem; }
.result-banner.unlocked { background: #fdecea; color: #c0392b; }
.result-banner.locked { background: #e8f5e9; color: #1e7a47; }
.result-banner.detected { background: #fff3cd; color: #856404; }
@keyframes shake { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-4px)} 75%{transform:translateX(4px)} }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Fault-injection simulator: a simplified pipeline shows what happens when a
// voltage glitch corrupts the compare instruction that guards a PIN check.
const $ = id => document.getElementById(id);
const stages = ['stage-fetch','stage-decode','stage-cmp','stage-branch','stage-out'];
const stageEls = stages.map($);
let running = false;
function clearStages() {
stageEls.forEach(el => el.className = 'stage');
$('glitch-indicator').className = 'glitch-indicator';
$('path-a-result').textContent = '—'; $('path-a-result').className = 'path-result';
$('path-b-result').textContent = '—'; $('path-b-result').className = 'path-result';
}
function log(msg, cls) {
const el = document.createElement('div');
el.className = cls || 'info'; el.textContent = msg;
const logEl = $('log'); logEl.appendChild(el); logEl.scrollTop = logEl.scrollHeight;
}
function setBanner(msg, cls) {
const b = $('result-banner'); b.textContent = msg; b.className = 'result-banner ' + cls;
}
// Utility to pause execution for a given number of milliseconds
function delay(ms) { return new Promise(r => setTimeout(r, ms)); }
// Main simulation: runs the 5-stage pipeline, optionally injecting a fault at the CMP stage
async function runPipeline(injectFault) {
if (running) return; running = true;
const redundancy = $('redundancy-toggle').checked;
clearStages(); setBanner('', '');
const pathRow = $('path-row');
pathRow.style.display = redundancy ? 'flex' : 'none';
// Advance through pipeline stages one by one
const steps = 4; // total stages to animate (fetch, decode, cmp, branch, out)
const cmpIndex = 2; // the compare stage is at index 2
let glitched = false;
for (let i = 0; i < steps; i++) {
stageEls[i].className = 'stage active';
if (i > 0) stageEls[i-1].className = 'stage ok';
await delay(350);
if (i === cmpIndex && injectFault) {
// Glitch fires! The compare stage is corrupted.
stageEls[i].className = 'stage glitched';
$('glitch-indicator').className = 'glitch-indicator visible';
log('⚡ ' + 'Voltage glitch detected at CMP — comparison corrupted!', 'warn');
await delay(450);
glitched = true;
if (redundancy) {
// Redundancy path B catches the skipped comparison
$('path-a-result').textContent = 'SKIPPED';
$('path-a-result').className = 'path-result skip';
await delay(200);
$('path-b-result').textContent = 'FAIL';
$('path-b-result').className = 'path-result fail';
await delay(300);
log('Path A/B mismatch detected — device locked!', 'err');
stageEls[i].className = 'stage glitched';
// Lock instead of granting access
stageEls[steps].className = 'stage glitched';
setBanner('GLITCH DETECTED — dual redundancy saved the device!', 'detected');
running = false; return;
}
// No redundancy — the skip is invisible to the CPU
log('CMP skipped — branch treats check as passed.', 'warn');
stageEls[i].className = 'stage ok';
}
}
// Mark the final output stage
stageEls[steps-1].className = 'stage ok';
stageEls[steps].className = 'stage ' + (glitched ? 'glitched' : 'ok');
if (glitched) {
log('UNLOCKED — fault injection bypassed PIN check.', 'err');
setBanner('UNLOCKED — glitch bypassed the PIN check!', 'unlocked');
} else {
if (redundancy) {
$('path-a-result').textContent = 'FAIL'; $('path-a-result').className = 'path-result fail';
await delay(150);
$('path-b-result').textContent = 'FAIL'; $('path-b-result').className = 'path-result fail';
await delay(150);
}
log('Locked — PIN check failed correctly.', 'info');
setBanner('Locked — PIN check blocked access.', 'locked');
}
running = false;
}
$('btn-normal').addEventListener('click', () => { clearStages(); setBanner('',''); log('Running pipeline — no fault injected.','info'); runPipeline(false); });
$('btn-glitch').addEventListener('click', () => { clearStages(); setBanner('',''); log('Injecting voltage glitch at CMP stage...','warn'); runPipeline(true); });
$('btn-reset').addEventListener('click', () => {
running = false; clearStages(); setBanner('','');
$('log').innerHTML = ''; $('path-row').style.display = 'none';
});
$('redundancy-toggle').addEventListener('change', () => {
clearStages(); setBanner('','');
const on = $('redundancy-toggle').checked;
log(on ? 'Dual redundancy ENABLED — glitches will be caught.' : 'Dual redundancy DISABLED — single path only.', 'info');
});
// Initial state — hide path row until first run
$('path-row').style.display = 'none';
log('Ready. Press ’Normal Run’ or ’Inject Fault’.', 'info');
<!-- Fault-injection simulator — pipeline visualization with optional dual-redundancy check -->
<div class="panel">
<div class="mode-bar">
<label class="toggle-label">
<input type="checkbox" id="redundancy-toggle">
<span>Dual Redundancy</span>
</label>
</div>
<div class="chip-view" id="chip-view">
<div class="chip-title">Secure Chip — PIN Check Pipeline</div>
<div class="pipeline" id="pipeline">
<div class="stage" id="stage-fetch">FETCH</div>
<div class="arrow">→</div>
<div class="stage" id="stage-decode">DECODE</div>
<div class="arrow">→</div>
<div class="stage" id="stage-cmp" title="Compare instruction — the guard">CMP</div>
<div class="arrow">→</div>
<div class="stage" id="stage-branch">BRANCH</div>
<div class="arrow">→</div>
<div class="stage" id="stage-out">OUT</div>
</div>
<div class="path-row" id="path-row">
<div class="path-box" id="path-a">
<span class="path-label">Path A</span>
<span class="path-result" id="path-a-result">—</span>
</div>
<div class="path-box" id="path-b">
<span class="path-label">Path B</span>
<span class="path-result" id="path-b-result">—</span>
</div>
</div>
<div class="glitch-indicator" id="glitch-indicator">⚡ GLITCH ACTIVE</div>
</div>
<div class="log" id="log"></div>
<div class="controls">
<button id="btn-normal" type="button">Normal Run</button>
<button id="btn-glitch" type="button" class="danger">Inject Fault</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div class="result-banner" id="result-banner"></div>
</div>
/* Styles for the fault-injection demo */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; font-size: 14px; color: #222; }
.panel { display: flex; flex-direction: column; gap: .6rem; padding: .6rem; }
.mode-bar { display: flex; align-items: center; gap: .5rem; }
.toggle-label { display: flex; align-items: center; gap: .5rem; cursor: pointer; font-weight: 600; font-size: .85rem; }
.toggle-label input { width: 36px; height: 20px; cursor: pointer; accent-color: #1d6fa8; }
.chip-view { background: #f0f4f8; border: 1.5px solid #c5d4e0; border-radius: 10px; padding: .7rem .8rem; display: flex; flex-direction: column; gap: .5rem; }
.chip-title { font-size: .75rem; font-weight: 700; color: #555; text-transform: uppercase; letter-spacing: .05em; }
.pipeline { display: flex; align-items: center; gap: .25rem; flex-wrap: wrap; }
.stage { background: #dce8f0; border: 1px solid #b0c8d8; border-radius: 6px; padding: .3rem .5rem; font-size: .78rem; font-weight: 600; color: #1d3557; min-width: 52px; text-align: center; transition: background .25s, border-color .25s; }
.stage.active { background: #1d6fa8; border-color: #155a8a; color: #fff; }
.stage.glitched { background: #e63946; border-color: #b52a33; color: #fff; animation: shake .25s; }
.stage.ok { background: #2a9d5c; border-color: #1e7a47; color: #fff; }
.arrow { color: #8fa8ba; font-size: 1rem; }
.path-row { display: flex; gap: .5rem; }
.path-box { flex: 1; background: #e8eef4; border: 1px solid #c0d0dc; border-radius: 6px; padding: .35rem .5rem; display: flex; justify-content: space-between; align-items: center; font-size: .8rem; }
.path-box.hidden { visibility: hidden; }
.path-label { font-weight: 600; color: #444; }
.path-result { font-weight: 700; color: #888; }
.path-result.pass { color: #2a9d5c; }
.path-result.fail { color: #e63946; }
.path-result.skip { color: #e09b00; }
.glitch-indicator { display: none; background: #fff3cd; border: 1px solid #e0a800; border-radius: 5px; padding: .2rem .5rem; font-size: .78rem; font-weight: 700; color: #856404; }
.glitch-indicator.visible { display: block; }
.log { background: #1a1a2e; color: #a0d8a0; border-radius: 7px; padding: .5rem .7rem; min-height: 4.5rem; max-height: 120px; overflow-y: auto; font: 12px/1.5 ui-monospace, monospace; }
.log .err { color: #ff7070; }
.log .warn { color: #ffd080; }
.log .info { color: #a0d8a0; }
.controls { display: flex; gap: .4rem; flex-wrap: wrap; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .8rem; border-radius: 7px; cursor: pointer; border: 1px solid #1d3557; background: #1d3557; color: #fff; }
button.danger { background: #c0392b; border-color: #922b21; }
button.ghost { background: #fff; color: #1d3557; }
.result-banner { min-height: 1.6em; font-size: .95rem; font-weight: 700; text-align: center; border-radius: 6px; padding: .3rem; }
.result-banner.unlocked { background: #fdecea; color: #c0392b; }
.result-banner.locked { background: #e8f5e9; color: #1e7a47; }
.result-banner.detected { background: #fff3cd; color: #856404; }
@keyframes shake { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-4px)} 75%{transform:translateX(4px)} }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Fault-injection simulator: a simplified pipeline shows what happens when a
// voltage glitch corrupts the compare instruction that guards a PIN check.
const $ = id => document.getElementById(id);
const stages = ['stage-fetch','stage-decode','stage-cmp','stage-branch','stage-out'];
const stageEls = stages.map($);
let running = false;
function clearStages() {
stageEls.forEach(el => el.className = 'stage');
$('glitch-indicator').className = 'glitch-indicator';
$('path-a-result').textContent = '—'; $('path-a-result').className = 'path-result';
$('path-b-result').textContent = '—'; $('path-b-result').className = 'path-result';
}
function log(msg, cls) {
const el = document.createElement('div');
el.className = cls || 'info'; el.textContent = msg;
const logEl = $('log'); logEl.appendChild(el); logEl.scrollTop = logEl.scrollHeight;
}
function setBanner(msg, cls) {
const b = $('result-banner'); b.textContent = msg; b.className = 'result-banner ' + cls;
}
// Utility to pause execution for a given number of milliseconds
function delay(ms) { return new Promise(r => setTimeout(r, ms)); }
// Main simulation: runs the 5-stage pipeline, optionally injecting a fault at the CMP stage
async function runPipeline(injectFault) {
if (running) return; running = true;
const redundancy = $('redundancy-toggle').checked;
clearStages(); setBanner('', '');
const pathRow = $('path-row');
pathRow.style.display = redundancy ? 'flex' : 'none';
// Advance through pipeline stages one by one
const steps = 4; // total stages to animate (fetch, decode, cmp, branch, out)
const cmpIndex = 2; // the compare stage is at index 2
let glitched = false;
for (let i = 0; i < steps; i++) {
stageEls[i].className = 'stage active';
if (i > 0) stageEls[i-1].className = 'stage ok';
await delay(350);
if (i === cmpIndex && injectFault) {
// Glitch fires! The compare stage is corrupted.
stageEls[i].className = 'stage glitched';
$('glitch-indicator').className = 'glitch-indicator visible';
log('⚡ ' + 'Voltage glitch detected at CMP — comparison corrupted!', 'warn');
await delay(450);
glitched = true;
if (redundancy) {
// Redundancy path B catches the skipped comparison
$('path-a-result').textContent = 'SKIPPED';
$('path-a-result').className = 'path-result skip';
await delay(200);
$('path-b-result').textContent = 'FAIL';
$('path-b-result').className = 'path-result fail';
await delay(300);
log('Path A/B mismatch detected — device locked!', 'err');
stageEls[i].className = 'stage glitched';
// Lock instead of granting access
stageEls[steps].className = 'stage glitched';
setBanner('GLITCH DETECTED — dual redundancy saved the device!', 'detected');
running = false; return;
}
// No redundancy — the skip is invisible to the CPU
log('CMP skipped — branch treats check as passed.', 'warn');
stageEls[i].className = 'stage ok';
}
}
// Mark the final output stage
stageEls[steps-1].className = 'stage ok';
stageEls[steps].className = 'stage ' + (glitched ? 'glitched' : 'ok');
if (glitched) {
log('UNLOCKED — fault injection bypassed PIN check.', 'err');
setBanner('UNLOCKED — glitch bypassed the PIN check!', 'unlocked');
} else {
if (redundancy) {
$('path-a-result').textContent = 'FAIL'; $('path-a-result').className = 'path-result fail';
await delay(150);
$('path-b-result').textContent = 'FAIL'; $('path-b-result').className = 'path-result fail';
await delay(150);
}
log('Locked — PIN check failed correctly.', 'info');
setBanner('Locked — PIN check blocked access.', 'locked');
}
running = false;
}
$('btn-normal').addEventListener('click', () => { clearStages(); setBanner('',''); log('Running pipeline — no fault injected.','info'); runPipeline(false); });
$('btn-glitch').addEventListener('click', () => { clearStages(); setBanner('',''); log('Injecting voltage glitch at CMP stage...','warn'); runPipeline(true); });
$('btn-reset').addEventListener('click', () => {
running = false; clearStages(); setBanner('','');
$('log').innerHTML = ''; $('path-row').style.display = 'none';
});
$('redundancy-toggle').addEventListener('change', () => {
clearStages(); setBanner('','');
const on = $('redundancy-toggle').checked;
log(on ? 'Dual redundancy ENABLED — glitches will be caught.' : 'Dual redundancy DISABLED — single path only.', 'info');
});
// Initial state — hide path row until first run
$('path-row').style.display = 'none';
log('Ready. Press ’Normal Run’ or ’Inject Fault’.', 'info');
Now enable Dual Redundancy: the check runs on two independent paths. A glitch that corrupts one path leaves the other intact — the mismatch is detected and the device locks instead of granting access. Notice how the same glitch that worked before is now caught every time.
Comments
Loading comments...