Picture a ring-shaped cave with a single entrance that splits into two tunnels, left and right, joined at the back by a magic door that only opens with a secret word. Peggy claims she knows the word. Victor waits at the entrance.
Each round, Peggy walks in and picks a tunnel out of Victor's sight. Then Victor shouts which side he wants her to come out of. If she truly knows the word she can always obey — she just opens the door when needed. If she is bluffing, she can only obey when she happened to pick the right tunnel: a coin-flip, 50% chance.
<p class="hint">Peggy enters the ring-shaped cave and secretly picks a tunnel. Victor then demands a side to come out of. A real knower opens the magic door and <b>always</b> obeys; a cheater obeys only when they guessed right — a 50% coin flip. Run rounds and watch a cheater's luck collapse as 1/2ⁿ.</p>
<div class="who">
<label><input type="radio" name="role" value="knower" checked> Peggy knows the word</label>
<label><input type="radio" name="role" value="cheater"> Peggy is a cheater</label>
</div>
<div class="cave" id="cave">
<div class="ring"></div>
<div class="door" id="door">door</div>
<div class="peg" id="peg">P</div>
<div class="vic">V</div>
<div class="lbl lblL">LEFT</div>
<div class="lbl lblR">RIGHT</div>
</div>
<div class="status" id="status">Press "Run a round" to begin.</div>
<div class="tally" id="tally"></div>
<div class="btns">
<button id="one" type="button">Run a round</button>
<button id="ten" type="button">Run 10 rounds</button>
<button id="reset" type="button" class="ghost">Reset</button>
</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; }
.who { display: flex; gap: 1rem; flex-wrap: wrap; font-size: .9rem; margin: 0 0 .6rem; }
.who label { cursor: pointer; }
.cave { position: relative; width: 230px; height: 150px; margin: .4rem auto .6rem; }
.ring { position: absolute; inset: 18px 18px 0 18px; border: 14px solid #cdd9e3;
border-bottom: none; border-radius: 90px 90px 0 0; background: #f3f6f9; }
.door { position: absolute; top: 0; left: 50%; transform: translateX(-50%);
background: #1d3557; color: #fff; font: 700 10px system-ui; padding: 2px 7px;
border-radius: 5px; transition: background .2s; }
.door.open { background: #0a7d33; }
.peg { position: absolute; bottom: 4px; left: 50%; transform: translate(-50%,0);
width: 26px; height: 26px; border-radius: 50%; background: #e63946; color: #fff;
font: 700 13px system-ui; display: flex; align-items: center; justify-content: center;
transition: left .45s ease, transform .45s ease; z-index: 2; }
.vic { position: absolute; bottom: -22px; left: 50%; transform: translateX(-50%);
width: 24px; height: 24px; border-radius: 50%; background: #1d3557; color: #fff;
font: 700 12px system-ui; display: flex; align-items: center; justify-content: center; }
.lbl { position: absolute; bottom: 56px; font: 700 11px system-ui; color: #1d3557; }
.lblL { left: 6px; } .lblR { right: 6px; }
.status { font-size: .98rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; text-align: center; }
.status.ok { color: #0a7d33; } .status.bad { color: #c92f3c; }
.tally { font-size: .9rem; text-align: center; margin: .3rem 0 .6rem; color: #1d3557; min-height: 1.3em; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; justify-content: center; }
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; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Ali Baba cave — a zero-knowledge proof of soundness.
// Each round: Peggy secretly picks a side (L/R). Victor demands a side.
// * A real knower opens the magic door and ALWAYS comes out the demanded side.
// * A cheater can only obey if the demanded side == the side she picked (p = 1/2).
// Victor accepts only if Peggy emerges on the demanded side. After n successful rounds, a cheater's probability of fooling Victor is 1/2^n. A true knower never fails.
let passed = 0, failed = 0, rounds = 0;
const $ = id => document.getElementById(id);
const role = () => document.querySelector('input[name=role]:checked').value;
function place(side, open) {
const peg = $('peg'), door = $('door');
door.className = 'door' + (open ? ' open' : '');
peg.style.left = side === 'L' ? '18%' : side === 'R' ? '82%' : '50%';
}
function round() {
rounds++;
const knows = role() === 'knower';
const picked = Math.random() < 0.5 ? 'L' : 'R'; // Peggy's secret choice
const demanded = Math.random() < 0.5 ? 'L' : 'R'; // Victor's challenge
// success: knower always succeeds (door opens if needed); cheater only if guessed right.
const usedDoor = knows && picked !== demanded;
const success = knows ? true : picked === demanded;
// animate: show her secret pick, then move to the side she emerges on.
place(picked, false);
setTimeout(() => {
place(success ? demanded : picked, usedDoor);
if (success) { passed++;
$('status').textContent = fmt('✓ Victor asked %word% — Peggy emerged %word%%v%.', { word: word(demanded), v: usedDoor ? ' (opened the door)' : '' });
$('status').className = 'status ok';
} else { failed++;
$('status').textContent = fmt('✗ Victor asked %word% but Peggy was stuck on the %word2% side. Caught!', { word: word(demanded), word2: word(picked) });
$('status').className = 'status bad';
}
updateTally();
}, 60);
}
function word(s) { return s === 'L' ? 'LEFT' : 'RIGHT'; }
function updateTally() {
const odds = passed > 0 ? '1 / ' + Math.pow(2, passed).toLocaleString() : '—';
$('tally').innerHTML = fmt('Rounds: <b>%rounds%</b> Passed: <b>%passed%</b> Caught: <b>%failed%</b><br>%v%', { rounds: rounds, passed: passed, failed: failed, v: failed === 0 && passed > 0
? fmt('Chance a cheater fluked all %passed% passes: <b>%odds%</b>', { passed: passed, odds: odds })
: (failed > 0 ? 'A cheater got caught — proof rejected.' : '') });
}
function reset() { passed = failed = rounds = 0; place('M', false);
$('status').textContent = 'Press "Run a round" to begin.'; $('status').className = 'status';
$('tally').textContent = ''; }
$('one').addEventListener('click', round);
$('ten').addEventListener('click', () => { let i = 0;
const t = setInterval(() => { round(); if (++i >= 10) clearInterval(t); }, 120); });
$('reset').addEventListener('click', reset);
document.querySelectorAll('input[name=role]').forEach(r => r.addEventListener('change', reset));
reset();
<p class="hint">Peggy enters the ring-shaped cave and secretly picks a tunnel. Victor then demands a side to come out of. A real knower opens the magic door and <b>always</b> obeys; a cheater obeys only when they guessed right — a 50% coin flip. Run rounds and watch a cheater's luck collapse as 1/2ⁿ.</p>
<div class="who">
<label><input type="radio" name="role" value="knower" checked> Peggy knows the word</label>
<label><input type="radio" name="role" value="cheater"> Peggy is a cheater</label>
</div>
<div class="cave" id="cave">
<div class="ring"></div>
<div class="door" id="door">door</div>
<div class="peg" id="peg">P</div>
<div class="vic">V</div>
<div class="lbl lblL">LEFT</div>
<div class="lbl lblR">RIGHT</div>
</div>
<div class="status" id="status">Press "Run a round" to begin.</div>
<div class="tally" id="tally"></div>
<div class="btns">
<button id="one" type="button">Run a round</button>
<button id="ten" type="button">Run 10 rounds</button>
<button id="reset" type="button" class="ghost">Reset</button>
</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; }
.who { display: flex; gap: 1rem; flex-wrap: wrap; font-size: .9rem; margin: 0 0 .6rem; }
.who label { cursor: pointer; }
.cave { position: relative; width: 230px; height: 150px; margin: .4rem auto .6rem; }
.ring { position: absolute; inset: 18px 18px 0 18px; border: 14px solid #cdd9e3;
border-bottom: none; border-radius: 90px 90px 0 0; background: #f3f6f9; }
.door { position: absolute; top: 0; left: 50%; transform: translateX(-50%);
background: #1d3557; color: #fff; font: 700 10px system-ui; padding: 2px 7px;
border-radius: 5px; transition: background .2s; }
.door.open { background: #0a7d33; }
.peg { position: absolute; bottom: 4px; left: 50%; transform: translate(-50%,0);
width: 26px; height: 26px; border-radius: 50%; background: #e63946; color: #fff;
font: 700 13px system-ui; display: flex; align-items: center; justify-content: center;
transition: left .45s ease, transform .45s ease; z-index: 2; }
.vic { position: absolute; bottom: -22px; left: 50%; transform: translateX(-50%);
width: 24px; height: 24px; border-radius: 50%; background: #1d3557; color: #fff;
font: 700 12px system-ui; display: flex; align-items: center; justify-content: center; }
.lbl { position: absolute; bottom: 56px; font: 700 11px system-ui; color: #1d3557; }
.lblL { left: 6px; } .lblR { right: 6px; }
.status { font-size: .98rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; text-align: center; }
.status.ok { color: #0a7d33; } .status.bad { color: #c92f3c; }
.tally { font-size: .9rem; text-align: center; margin: .3rem 0 .6rem; color: #1d3557; min-height: 1.3em; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; justify-content: center; }
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; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Ali Baba cave — a zero-knowledge proof of soundness.
// Each round: Peggy secretly picks a side (L/R). Victor demands a side.
// * A real knower opens the magic door and ALWAYS comes out the demanded side.
// * A cheater can only obey if the demanded side == the side she picked (p = 1/2).
// Victor accepts only if Peggy emerges on the demanded side. After n successful rounds, a cheater's probability of fooling Victor is 1/2^n. A true knower never fails.
let passed = 0, failed = 0, rounds = 0;
const $ = id => document.getElementById(id);
const role = () => document.querySelector('input[name=role]:checked').value;
function place(side, open) {
const peg = $('peg'), door = $('door');
door.className = 'door' + (open ? ' open' : '');
peg.style.left = side === 'L' ? '18%' : side === 'R' ? '82%' : '50%';
}
function round() {
rounds++;
const knows = role() === 'knower';
const picked = Math.random() < 0.5 ? 'L' : 'R'; // Peggy's secret choice
const demanded = Math.random() < 0.5 ? 'L' : 'R'; // Victor's challenge
// success: knower always succeeds (door opens if needed); cheater only if guessed right.
const usedDoor = knows && picked !== demanded;
const success = knows ? true : picked === demanded;
// animate: show her secret pick, then move to the side she emerges on.
place(picked, false);
setTimeout(() => {
place(success ? demanded : picked, usedDoor);
if (success) { passed++;
$('status').textContent = fmt('✓ Victor asked %word% — Peggy emerged %word%%v%.', { word: word(demanded), v: usedDoor ? ' (opened the door)' : '' });
$('status').className = 'status ok';
} else { failed++;
$('status').textContent = fmt('✗ Victor asked %word% but Peggy was stuck on the %word2% side. Caught!', { word: word(demanded), word2: word(picked) });
$('status').className = 'status bad';
}
updateTally();
}, 60);
}
function word(s) { return s === 'L' ? 'LEFT' : 'RIGHT'; }
function updateTally() {
const odds = passed > 0 ? '1 / ' + Math.pow(2, passed).toLocaleString() : '—';
$('tally').innerHTML = fmt('Rounds: <b>%rounds%</b> Passed: <b>%passed%</b> Caught: <b>%failed%</b><br>%v%', { rounds: rounds, passed: passed, failed: failed, v: failed === 0 && passed > 0
? fmt('Chance a cheater fluked all %passed% passes: <b>%odds%</b>', { passed: passed, odds: odds })
: (failed > 0 ? 'A cheater got caught — proof rejected.' : '') });
}
function reset() { passed = failed = rounds = 0; place('M', false);
$('status').textContent = 'Press "Run a round" to begin.'; $('status').className = 'status';
$('tally').textContent = ''; }
$('one').addEventListener('click', round);
$('ten').addEventListener('click', () => { let i = 0;
const t = setInterval(() => { round(); if (++i >= 10) clearInterval(t); }, 120); });
$('reset').addEventListener('click', reset);
document.querySelectorAll('input[name=role]').forEach(r => r.addEventListener('change', reset));
reset();
Run a few rounds as a real knower, then toggle to Cheater and watch. One round proves nothing — a bluffer gets lucky half the time. But the chance of fluking n rounds in a row is 1/2n: ten rounds and it's under one in a thousand. Victor ends up certain Peggy knows the word, yet he never heard it. That is zero knowledge.
Comments
Loading comments...