<p class="hint">A quantum banknote stores qubits in a <b>secret basis</b> (rectilinear ⊕ or diagonal ×). The bank can verify it instantly. A counterfeiter who guesses the wrong basis collapses the state — and the copy fails verification.</p>
<div class="banknote" id="note">
<div class="note-header">
<span class="serial" id="serial">Serial: #—</span>
<span class="badge" id="badge">GENUINE</span>
</div>
<div class="qubits" id="qubits"></div>
</div>
<div class="status" id="status">Click <b>Issue new note</b> to begin.</div>
<div class="btns">
<button id="btnIssue" type="button">Issue new note</button>
<button id="btnVerify" type="button" disabled>Verify (bank)</button>
<button id="btnClone" type="button" disabled>Clone attempt</button>
</div>
<div class="log" id="log"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.banknote { background: linear-gradient(135deg,#e8f5e9,#f1f8e9); border: 2px solid #81c784;
border-radius: 12px; padding: .8rem 1rem; margin-bottom: .7rem; min-height: 90px; }
.banknote.invalid { background: linear-gradient(135deg,#ffebee,#fce4ec); border-color: #e57373; }
.banknote.clone { background: linear-gradient(135deg,#fff3e0,#fce4ec); border-color: #ffb74d; }
.note-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: .5rem; }
.serial { font: 700 .85rem ui-monospace, monospace; color: #2e7d32; }
.badge { font: 700 .75rem system-ui; padding: .2rem .5rem; border-radius: 20px;
background: #2e7d32; color: #fff; letter-spacing: .05em; }
.badge.fail { background: #c62828; }
.badge.warn { background: #e65100; }
.qubits { display: flex; gap: 6px; flex-wrap: wrap; }
.qubit { width: 38px; height: 38px; border-radius: 8px; display: flex; align-items: center;
justify-content: center; font: 700 18px ui-monospace, monospace;
border: 1.5px solid #bbb; background: #fff; transition: all .2s; }
.qubit.rec { border-color: #1565c0; color: #1565c0; background: #e3f2fd; }
.qubit.diag { border-color: #6a1b9a; color: #6a1b9a; background: #f3e5f5; }
.qubit.err { border-color: #c62828; color: #c62828; background: #ffebee; }
.status { font-size: .95rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; line-height: 1.4; }
.status.ok { color: #2e7d32; } .status.bad { color: #c62828; } .status.warn { color: #e65100; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .6rem; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1.5px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button:disabled { opacity: .4; cursor: not-allowed; }
button.ghost { background: #fff; color: #1d3557; }
.log { font: .8rem ui-monospace, monospace; color: #555; max-height: 130px; overflow-y: auto;
border: 1px solid #ddd; border-radius: 8px; padding: .4rem .6rem; line-height: 1.6;
background: #fafafa; }
.log:empty { display: none; }
.log .ok { color: #2e7d32; } .log .bad { color: #c62828; } .log .warn { color: #e65100; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Simplified quantum money simulation (classical model of qubit bases).
// Each qubit has a basis (rectilinear R or diagonal D) and a value (0 or 1).
// Bank verification: measure each qubit in the CORRECT basis → always matches.
// Clone attempt: attacker guesses a random basis for each qubit.
// If wrong basis → 50% chance of wrong value (state collapse).
// The cloned note then fails bank verification for each wrong qubit.
const N_QUBITS = 8;
let note = null; // { serial, bases[], values[] }
const SYMBOLS = {
// rectilinear: |0⟩ = ↑, |1⟩ = →
R: ['↑', '→'],
// diagonal: |0⟩ = ↗, |1⟩ = ↘
D: ['↗', '↘']
};
function rand(n) { return Math.floor(Math.random() * n); }
function coin() { return Math.random() < 0.5; }
function issueNote() {
const serial = 'QM-' + Math.floor(Math.random() * 90000 + 10000);
const bases = Array.from({length: N_QUBITS}, () => coin() ? 'R' : 'D');
const values = Array.from({length: N_QUBITS}, () => rand(2));
note = { serial, bases, values };
renderNote(note.bases, note.values, 'genuine');
setStatus(fmt('Note <b><b>%serial%</b></b> issued. Bank holds the secret basis for each qubit.', { serial: serial }), '');
document.getElementById('badge').textContent = 'GENUINE';
document.getElementById('badge').className = 'badge';
document.getElementById('serial').textContent = fmt('Serial: #%serial%', { serial: serial });
document.getElementById('btnVerify').disabled = false;
document.getElementById('btnClone').disabled = false;
log('');
addLog(fmt('Bank issued note %serial% with bases: %bases%', { serial: serial, bases: note.bases.join(' ') }), '');
}
function renderNote(bases, values, mode) {
const el = document.getElementById('note');
el.className = 'banknote' + (mode === 'invalid' ? ' invalid' : mode === 'clone' ? ' clone' : '');
const qEl = document.getElementById('qubits');
qEl.innerHTML = '';
bases.forEach((b, i) => {
const d = document.createElement('div');
const sym = SYMBOLS[b][values[i]];
d.className = 'qubit ' + (mode === 'invalid' ? 'err' : b === 'R' ? 'rec' : 'diag');
d.textContent = sym;
d.title = fmt('Qubit %i%%v%: basis=%b% value=%v2%', { i: i, v: 1, b: b, v2: values[i] });
qEl.appendChild(d);
});
}
function verify() {
if (!note) return;
// Bank measures each qubit in the correct basis → perfect match
const ok = note.values.every((v, i) => {
// correct measurement always returns the stored value
return true;
});
renderNote(note.bases, note.values, 'genuine');
document.getElementById('badge').textContent = 'GENUINE';
document.getElementById('badge').className = 'badge';
setStatus(fmt('✓ Bank verified note %serial% — all %N_QUBITS% qubits match perfectly.', { serial: note.serial, N_QUBITS: N_QUBITS }), 'ok');
addLog('Verification: correct basis used → all qubits match → ACCEPTED', 'ok');
}
function cloneAttempt() {
if (!note) return;
// Counterfeiter guesses a random basis for each qubit
const guessedBases = Array.from({length: N_QUBITS}, () => coin() ? 'R' : 'D');
const clonedValues = note.values.map((v, i) => {
if (guessedBases[i] === note.bases[i]) {
// Correct basis guess → correct value (but attacker doesn't know which are right)
return v;
} else {
// Wrong basis → measurement collapses state, 50% chance of wrong value
return coin() ? v : 1 - v;
}
});
// Count errors
const errors = clonedValues.filter((v, i) => v !== note.values[i]).length;
const wrongBases = guessedBases.filter((b, i) => b !== note.bases[i]).length;
// Bank verifies clone: uses original bases on the cloned (disturbed) values
const accepted = errors === 0;
renderNote(guessedBases, clonedValues, accepted ? 'genuine' : 'invalid');
document.getElementById('serial').textContent = fmt('Serial: #%serial% (COPY)', { serial: note.serial });
if (accepted) {
document.getElementById('badge').textContent = 'ACCEPTED';
document.getElementById('badge').className = 'badge';
setStatus(fmt('Lucky clone: by chance all %N_QUBITS% qubits landed correctly (%wrongBases% wrong bases, 0 errors). Probability ≈ (0.5)^%wrongBases% = %pow%', { N_QUBITS: N_QUBITS, wrongBases: wrongBases, pow: (Math.pow(0.5, wrongBases)).toFixed(4) }), 'warn');
addLog(fmt('Clone: %wrongBases% wrong bases, 0 errors → lucky ACCEPT (prob ≈ %pow%)', { wrongBases: wrongBases, pow: Math.pow(0.5,wrongBases).toFixed(4) }), 'warn');
} else {
document.getElementById('badge').textContent = 'REJECTED';
document.getElementById('badge').className = 'badge fail';
setStatus(fmt('✗ Clone REJECTED: %wrongBases% wrong basis guesses caused %errors% error(s). Bank detects the disturbance.', { wrongBases: wrongBases, errors: errors }), 'bad');
addLog(fmt('Clone: %wrongBases% wrong bases → %errors% error(s) → REJECTED', { wrongBases: wrongBases, errors: errors }), 'bad');
console.assert(errors > 0, 'Expected at least one error when bases mismatch');
}
}
function setStatus(t, cls) {
const s = document.getElementById('status');
s.innerHTML = t;
s.className = 'status ' + cls;
}
function addLog(msg, cls) {
const l = document.getElementById('log');
const line = document.createElement('div');
if (cls) line.className = cls;
line.textContent = msg;
l.appendChild(line);
l.scrollTop = l.scrollHeight;
}
function log(msg) {
document.getElementById('log').innerHTML = '';
if (msg) addLog(msg, '');
}
document.getElementById('btnIssue').addEventListener('click', issueNote);
document.getElementById('btnVerify').addEventListener('click', verify);
document.getElementById('btnClone').addEventListener('click', cloneAttempt);
// Verify the core claim: wrong basis → 50% error rate per affected qubit
(function selfTest() {
let trials = 10000, baseErrors = 0, valueErrors = 0;
for (let i = 0; i < trials; i++) {
const b = Math.random() < 0.5 ? 'R' : 'D';
const v = Math.random() < 0.5 ? 0 : 1;
const gb = Math.random() < 0.5 ? 'R' : 'D';
if (gb !== b) {
baseErrors++;
const gv = Math.random() < 0.5 ? v : 1 - v;
if (gv !== v) valueErrors++;
}
}
const rate = valueErrors / baseErrors;
console.log('Self-test: wrong-basis error rate =', rate.toFixed(3), '(expected ~0.5)');
console.assert(rate > 0.4 && rate < 0.6, fmt('Error rate should be ~0.5, got %rate%', { rate: rate }));
})();
<p class="hint">A quantum banknote stores qubits in a <b>secret basis</b> (rectilinear ⊕ or diagonal ×). The bank can verify it instantly. A counterfeiter who guesses the wrong basis collapses the state — and the copy fails verification.</p>
<div class="banknote" id="note">
<div class="note-header">
<span class="serial" id="serial">Serial: #—</span>
<span class="badge" id="badge">GENUINE</span>
</div>
<div class="qubits" id="qubits"></div>
</div>
<div class="status" id="status">Click <b>Issue new note</b> to begin.</div>
<div class="btns">
<button id="btnIssue" type="button">Issue new note</button>
<button id="btnVerify" type="button" disabled>Verify (bank)</button>
<button id="btnClone" type="button" disabled>Clone attempt</button>
</div>
<div class="log" id="log"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.banknote { background: linear-gradient(135deg,#e8f5e9,#f1f8e9); border: 2px solid #81c784;
border-radius: 12px; padding: .8rem 1rem; margin-bottom: .7rem; min-height: 90px; }
.banknote.invalid { background: linear-gradient(135deg,#ffebee,#fce4ec); border-color: #e57373; }
.banknote.clone { background: linear-gradient(135deg,#fff3e0,#fce4ec); border-color: #ffb74d; }
.note-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: .5rem; }
.serial { font: 700 .85rem ui-monospace, monospace; color: #2e7d32; }
.badge { font: 700 .75rem system-ui; padding: .2rem .5rem; border-radius: 20px;
background: #2e7d32; color: #fff; letter-spacing: .05em; }
.badge.fail { background: #c62828; }
.badge.warn { background: #e65100; }
.qubits { display: flex; gap: 6px; flex-wrap: wrap; }
.qubit { width: 38px; height: 38px; border-radius: 8px; display: flex; align-items: center;
justify-content: center; font: 700 18px ui-monospace, monospace;
border: 1.5px solid #bbb; background: #fff; transition: all .2s; }
.qubit.rec { border-color: #1565c0; color: #1565c0; background: #e3f2fd; }
.qubit.diag { border-color: #6a1b9a; color: #6a1b9a; background: #f3e5f5; }
.qubit.err { border-color: #c62828; color: #c62828; background: #ffebee; }
.status { font-size: .95rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; line-height: 1.4; }
.status.ok { color: #2e7d32; } .status.bad { color: #c62828; } .status.warn { color: #e65100; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .6rem; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1.5px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button:disabled { opacity: .4; cursor: not-allowed; }
button.ghost { background: #fff; color: #1d3557; }
.log { font: .8rem ui-monospace, monospace; color: #555; max-height: 130px; overflow-y: auto;
border: 1px solid #ddd; border-radius: 8px; padding: .4rem .6rem; line-height: 1.6;
background: #fafafa; }
.log:empty { display: none; }
.log .ok { color: #2e7d32; } .log .bad { color: #c62828; } .log .warn { color: #e65100; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Simplified quantum money simulation (classical model of qubit bases).
// Each qubit has a basis (rectilinear R or diagonal D) and a value (0 or 1).
// Bank verification: measure each qubit in the CORRECT basis → always matches.
// Clone attempt: attacker guesses a random basis for each qubit.
// If wrong basis → 50% chance of wrong value (state collapse).
// The cloned note then fails bank verification for each wrong qubit.
const N_QUBITS = 8;
let note = null; // { serial, bases[], values[] }
const SYMBOLS = {
// rectilinear: |0⟩ = ↑, |1⟩ = →
R: ['↑', '→'],
// diagonal: |0⟩ = ↗, |1⟩ = ↘
D: ['↗', '↘']
};
function rand(n) { return Math.floor(Math.random() * n); }
function coin() { return Math.random() < 0.5; }
function issueNote() {
const serial = 'QM-' + Math.floor(Math.random() * 90000 + 10000);
const bases = Array.from({length: N_QUBITS}, () => coin() ? 'R' : 'D');
const values = Array.from({length: N_QUBITS}, () => rand(2));
note = { serial, bases, values };
renderNote(note.bases, note.values, 'genuine');
setStatus(fmt('Note <b><b>%serial%</b></b> issued. Bank holds the secret basis for each qubit.', { serial: serial }), '');
document.getElementById('badge').textContent = 'GENUINE';
document.getElementById('badge').className = 'badge';
document.getElementById('serial').textContent = fmt('Serial: #%serial%', { serial: serial });
document.getElementById('btnVerify').disabled = false;
document.getElementById('btnClone').disabled = false;
log('');
addLog(fmt('Bank issued note %serial% with bases: %bases%', { serial: serial, bases: note.bases.join(' ') }), '');
}
function renderNote(bases, values, mode) {
const el = document.getElementById('note');
el.className = 'banknote' + (mode === 'invalid' ? ' invalid' : mode === 'clone' ? ' clone' : '');
const qEl = document.getElementById('qubits');
qEl.innerHTML = '';
bases.forEach((b, i) => {
const d = document.createElement('div');
const sym = SYMBOLS[b][values[i]];
d.className = 'qubit ' + (mode === 'invalid' ? 'err' : b === 'R' ? 'rec' : 'diag');
d.textContent = sym;
d.title = fmt('Qubit %i%%v%: basis=%b% value=%v2%', { i: i, v: 1, b: b, v2: values[i] });
qEl.appendChild(d);
});
}
function verify() {
if (!note) return;
// Bank measures each qubit in the correct basis → perfect match
const ok = note.values.every((v, i) => {
// correct measurement always returns the stored value
return true;
});
renderNote(note.bases, note.values, 'genuine');
document.getElementById('badge').textContent = 'GENUINE';
document.getElementById('badge').className = 'badge';
setStatus(fmt('✓ Bank verified note %serial% — all %N_QUBITS% qubits match perfectly.', { serial: note.serial, N_QUBITS: N_QUBITS }), 'ok');
addLog('Verification: correct basis used → all qubits match → ACCEPTED', 'ok');
}
function cloneAttempt() {
if (!note) return;
// Counterfeiter guesses a random basis for each qubit
const guessedBases = Array.from({length: N_QUBITS}, () => coin() ? 'R' : 'D');
const clonedValues = note.values.map((v, i) => {
if (guessedBases[i] === note.bases[i]) {
// Correct basis guess → correct value (but attacker doesn't know which are right)
return v;
} else {
// Wrong basis → measurement collapses state, 50% chance of wrong value
return coin() ? v : 1 - v;
}
});
// Count errors
const errors = clonedValues.filter((v, i) => v !== note.values[i]).length;
const wrongBases = guessedBases.filter((b, i) => b !== note.bases[i]).length;
// Bank verifies clone: uses original bases on the cloned (disturbed) values
const accepted = errors === 0;
renderNote(guessedBases, clonedValues, accepted ? 'genuine' : 'invalid');
document.getElementById('serial').textContent = fmt('Serial: #%serial% (COPY)', { serial: note.serial });
if (accepted) {
document.getElementById('badge').textContent = 'ACCEPTED';
document.getElementById('badge').className = 'badge';
setStatus(fmt('Lucky clone: by chance all %N_QUBITS% qubits landed correctly (%wrongBases% wrong bases, 0 errors). Probability ≈ (0.5)^%wrongBases% = %pow%', { N_QUBITS: N_QUBITS, wrongBases: wrongBases, pow: (Math.pow(0.5, wrongBases)).toFixed(4) }), 'warn');
addLog(fmt('Clone: %wrongBases% wrong bases, 0 errors → lucky ACCEPT (prob ≈ %pow%)', { wrongBases: wrongBases, pow: Math.pow(0.5,wrongBases).toFixed(4) }), 'warn');
} else {
document.getElementById('badge').textContent = 'REJECTED';
document.getElementById('badge').className = 'badge fail';
setStatus(fmt('✗ Clone REJECTED: %wrongBases% wrong basis guesses caused %errors% error(s). Bank detects the disturbance.', { wrongBases: wrongBases, errors: errors }), 'bad');
addLog(fmt('Clone: %wrongBases% wrong bases → %errors% error(s) → REJECTED', { wrongBases: wrongBases, errors: errors }), 'bad');
console.assert(errors > 0, 'Expected at least one error when bases mismatch');
}
}
function setStatus(t, cls) {
const s = document.getElementById('status');
s.innerHTML = t;
s.className = 'status ' + cls;
}
function addLog(msg, cls) {
const l = document.getElementById('log');
const line = document.createElement('div');
if (cls) line.className = cls;
line.textContent = msg;
l.appendChild(line);
l.scrollTop = l.scrollHeight;
}
function log(msg) {
document.getElementById('log').innerHTML = '';
if (msg) addLog(msg, '');
}
document.getElementById('btnIssue').addEventListener('click', issueNote);
document.getElementById('btnVerify').addEventListener('click', verify);
document.getElementById('btnClone').addEventListener('click', cloneAttempt);
// Verify the core claim: wrong basis → 50% error rate per affected qubit
(function selfTest() {
let trials = 10000, baseErrors = 0, valueErrors = 0;
for (let i = 0; i < trials; i++) {
const b = Math.random() < 0.5 ? 'R' : 'D';
const v = Math.random() < 0.5 ? 0 : 1;
const gb = Math.random() < 0.5 ? 'R' : 'D';
if (gb !== b) {
baseErrors++;
const gv = Math.random() < 0.5 ? v : 1 - v;
if (gv !== v) valueErrors++;
}
}
const rate = valueErrors / baseErrors;
console.log('Self-test: wrong-basis error rate =', rate.toFixed(3), '(expected ~0.5)');
console.assert(rate > 0.4 && rate < 0.6, fmt('Error rate should be ~0.5, got %rate%', { rate: rate }));
})();
Comments
Loading comments...