Alice sends a stream of qubits, each polarized in one of two random bases (rectilinear + or diagonal ×). Bob measures each one in a basis he also picks at random. Whenever their bases happen to match, they keep the bit — that becomes the shared key.
<p class="hint">Alice sends qubits in random bases (+ or ×). Bob measures in his own random bases. They keep a bit only when the bases match. With <b>Eve</b> on, she must guess a basis before measuring — each wrong guess scrambles the qubit, so the kept bits start to disagree. Watch the error rate expose the spy.</p>
<div class="controls">
<label><input type="checkbox" id="eve"> Eve is intercepting</label>
<label>Qubits: <input type="range" id="n" min="8" max="40" value="20"></label>
</div>
<div class="btns">
<button id="send" type="button">Send qubits</button>
<button id="reset" type="button" class="ghost">Clear</button>
</div>
<div id="stream" class="stream"></div>
<div class="status" id="status">Press “Send qubits”.</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.controls { display: flex; gap: 1.2rem; flex-wrap: wrap; align-items: center;
font-size: .9rem; margin: .3rem 0 .6rem; }
.controls input[type=range] { vertical-align: middle; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .6rem; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.stream { display: flex; flex-wrap: wrap; gap: 4px; margin: .4rem 0; }
.q { width: 30px; text-align: center; font: 700 13px ui-monospace, monospace;
border-radius: 6px; padding: 4px 0; border: 1px solid #cdd9e3; background: #e8eef3; color: #1d3557; }
.q.drop { opacity: .35; }
.q.keep { background: #d4ead9; border-color: #9fcead; }
.q.err { background: #f6cdd2; border-color: #e29aa3; color: #9b1c2a; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// BB84 simulation. A qubit carries a bit value encoded in one of two bases:
// basis 0 = rectilinear (+): bit0 = | (0deg), bit1 = - (90deg)
// basis 1 = diagonal (×): bit0 = / (45deg), bit1 = \ (135deg)
// Measuring in the SAME basis Alice used returns her bit exactly.
// Measuring in the WRONG basis returns a uniformly random bit (quantum collapse).
// Eve must guess a basis before measuring; a wrong guess collapses the state
// and re-sends a qubit that no longer matches Alice's. The sifted key (bases
// matched) then shows ~25% errors — the eavesdropper's signature.
const rnd = () => Math.random() < 0.5 ? 0 : 1;
// Measure a qubit prepared as (aBit in aBasis) using mBasis. Returns the bit read.
function measure(aBit, aBasis, mBasis) {
return (mBasis === aBasis) ? aBit : rnd(); // wrong basis -> random outcome
}
function run() {
const eve = document.getElementById('eve').checked;
const N = +document.getElementById('n').value;
const stream = document.getElementById('stream'); stream.innerHTML = '';
let kept = 0, errors = 0;
const SYM = [['|','-'],['/','\\']]; // [basis][bit]
for (let i = 0; i < N; i++) {
const aBit = rnd(), aBasis = rnd(); // Alice prepares
let bit = aBit, basis = aBasis; // state travelling to Bob
if (eve) { // Eve intercepts and resends
const eBasis = rnd();
const eBit = measure(aBit, aBasis, eBasis);
bit = eBit; basis = eBasis; // Eve re-emits what she measured
}
const bBasis = rnd(); // Bob's random basis
const bBit = measure(bit, basis, bBasis);
const div = document.createElement('div');
div.className = 'q';
if (bBasis === aBasis) { // sifting: bases match -> keep
kept++;
const wrong = bBit !== aBit;
if (wrong) errors++;
div.className += wrong ? ' keep err' : ' keep';
div.textContent = SYM[aBasis][aBit];
} else {
div.className += ' drop'; // bases differ -> discard
div.textContent = SYM[aBasis][aBit];
}
stream.appendChild(div);
}
const rate = kept ? Math.round(100 * errors / kept) : 0;
const s = document.getElementById('status');
if (!eve) {
s.textContent = fmt('No spy. Kept %kept% bits, %errors% errors (%rate%%). Key is clean.', { kept: kept, errors: errors, rate: rate });
s.className = 'status ok';
} else if (rate >= 10) {
s.textContent = fmt('Eavesdropper detected! %errors%/%kept% kept bits flipped (%rate%%). Discard the key.', { errors: errors, kept: kept, rate: rate });
s.className = 'status bad';
} else {
s.textContent = fmt('Eve present but few errors this run (%rate%%) — send more qubits to expose her.', { rate: rate });
s.className = 'status bad';
}
console.log('eve=' + eve, 'kept=' + kept, 'errors=' + errors, 'rate=' + rate + '%');
}
function reset() {
document.getElementById('stream').innerHTML = '';
const s = document.getElementById('status');
s.textContent = 'Press “Send qubits”.'; s.className = 'status';
}
document.getElementById('send').addEventListener('click', run);
document.getElementById('reset').addEventListener('click', reset);
<p class="hint">Alice sends qubits in random bases (+ or ×). Bob measures in his own random bases. They keep a bit only when the bases match. With <b>Eve</b> on, she must guess a basis before measuring — each wrong guess scrambles the qubit, so the kept bits start to disagree. Watch the error rate expose the spy.</p>
<div class="controls">
<label><input type="checkbox" id="eve"> Eve is intercepting</label>
<label>Qubits: <input type="range" id="n" min="8" max="40" value="20"></label>
</div>
<div class="btns">
<button id="send" type="button">Send qubits</button>
<button id="reset" type="button" class="ghost">Clear</button>
</div>
<div id="stream" class="stream"></div>
<div class="status" id="status">Press “Send qubits”.</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.controls { display: flex; gap: 1.2rem; flex-wrap: wrap; align-items: center;
font-size: .9rem; margin: .3rem 0 .6rem; }
.controls input[type=range] { vertical-align: middle; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .6rem; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.stream { display: flex; flex-wrap: wrap; gap: 4px; margin: .4rem 0; }
.q { width: 30px; text-align: center; font: 700 13px ui-monospace, monospace;
border-radius: 6px; padding: 4px 0; border: 1px solid #cdd9e3; background: #e8eef3; color: #1d3557; }
.q.drop { opacity: .35; }
.q.keep { background: #d4ead9; border-color: #9fcead; }
.q.err { background: #f6cdd2; border-color: #e29aa3; color: #9b1c2a; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// BB84 simulation. A qubit carries a bit value encoded in one of two bases:
// basis 0 = rectilinear (+): bit0 = | (0deg), bit1 = - (90deg)
// basis 1 = diagonal (×): bit0 = / (45deg), bit1 = \ (135deg)
// Measuring in the SAME basis Alice used returns her bit exactly.
// Measuring in the WRONG basis returns a uniformly random bit (quantum collapse).
// Eve must guess a basis before measuring; a wrong guess collapses the state
// and re-sends a qubit that no longer matches Alice's. The sifted key (bases
// matched) then shows ~25% errors — the eavesdropper's signature.
const rnd = () => Math.random() < 0.5 ? 0 : 1;
// Measure a qubit prepared as (aBit in aBasis) using mBasis. Returns the bit read.
function measure(aBit, aBasis, mBasis) {
return (mBasis === aBasis) ? aBit : rnd(); // wrong basis -> random outcome
}
function run() {
const eve = document.getElementById('eve').checked;
const N = +document.getElementById('n').value;
const stream = document.getElementById('stream'); stream.innerHTML = '';
let kept = 0, errors = 0;
const SYM = [['|','-'],['/','\\']]; // [basis][bit]
for (let i = 0; i < N; i++) {
const aBit = rnd(), aBasis = rnd(); // Alice prepares
let bit = aBit, basis = aBasis; // state travelling to Bob
if (eve) { // Eve intercepts and resends
const eBasis = rnd();
const eBit = measure(aBit, aBasis, eBasis);
bit = eBit; basis = eBasis; // Eve re-emits what she measured
}
const bBasis = rnd(); // Bob's random basis
const bBit = measure(bit, basis, bBasis);
const div = document.createElement('div');
div.className = 'q';
if (bBasis === aBasis) { // sifting: bases match -> keep
kept++;
const wrong = bBit !== aBit;
if (wrong) errors++;
div.className += wrong ? ' keep err' : ' keep';
div.textContent = SYM[aBasis][aBit];
} else {
div.className += ' drop'; // bases differ -> discard
div.textContent = SYM[aBasis][aBit];
}
stream.appendChild(div);
}
const rate = kept ? Math.round(100 * errors / kept) : 0;
const s = document.getElementById('status');
if (!eve) {
s.textContent = fmt('No spy. Kept %kept% bits, %errors% errors (%rate%%). Key is clean.', { kept: kept, errors: errors, rate: rate });
s.className = 'status ok';
} else if (rate >= 10) {
s.textContent = fmt('Eavesdropper detected! %errors%/%kept% kept bits flipped (%rate%%). Discard the key.', { errors: errors, kept: kept, rate: rate });
s.className = 'status bad';
} else {
s.textContent = fmt('Eve present but few errors this run (%rate%%) — send more qubits to expose her.', { rate: rate });
s.className = 'status bad';
}
console.log('eve=' + eve, 'kept=' + kept, 'errors=' + errors, 'rate=' + rate + '%');
}
function reset() {
document.getElementById('stream').innerHTML = '';
const s = document.getElementById('status');
s.textContent = 'Press “Send qubits”.'; s.className = 'status';
}
document.getElementById('send').addEventListener('click', run);
document.getElementById('reset').addEventListener('click', reset);
Run it once with no spy: the bits Alice and Bob keep agree perfectly. Now flip on Eve. Because she has to guess each basis before measuring, she gets it wrong half the time, and every wrong guess randomizes the qubit. The result is unmistakable: about 25% of the shared bits flip. That error rate is the eavesdropper's fingerprint — there is no way to listen without leaving it.
Comments
Loading comments...