<p class="hint">
Pick a secret wealth level for <b>Alice</b> and <b>Bob</b> (1–8). Press <b>Garble & Evaluate</b> to run the secure comparison — neither party learns the other’s number, only who is richer.
</p>
<div class="inputs">
<div class="party alice-side">
<div class="label">Alice’s wealth</div>
<div class="slider-row">
<input id="alice-slider" type="range" min="1" max="8" value="5" class="slider alice-slider">
<span class="val-badge alice-badge" id="alice-val">5</span>
</div>
<div class="secret-hint">(secret — Bob cannot see this)</div>
</div>
<div class="vs">VS</div>
<div class="party bob-side">
<div class="label">Bob’s wealth</div>
<div class="slider-row">
<input id="bob-slider" type="range" min="1" max="8" value="3" class="slider bob-slider">
<span class="val-badge bob-badge" id="bob-val">3</span>
</div>
<div class="secret-hint">(secret — Alice cannot see this)</div>
</div>
</div>
<button id="run-btn" type="button">Garble & Evaluate</button>
<div id="circuit-vis" class="circuit-vis hidden"></div>
<div id="result" class="result hidden"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: 14px .5rem; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.inputs { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-bottom: .9rem; }
.party { flex: 1; min-width: 140px; padding: .7rem .8rem; border-radius: 10px; }
.alice-side { background: #e8f0ff; border: 1px solid #b3c8f5; }
.bob-side { background: #fff0e8; border: 1px solid #f5c8a0; }
.label { font-weight: 700; font-size: .95rem; margin-bottom: .4rem; }
.slider-row { display: flex; align-items: center; gap: .5rem; }
.slider { flex: 1; cursor: pointer; }
.alice-slider { accent-color: #3a6bc4; }
.bob-slider { accent-color: #c47a3a; }
.val-badge { font: 700 1.1rem ui-monospace, monospace; min-width: 1.8rem; text-align: center;
padding: .2rem .4rem; border-radius: 6px; }
.alice-badge { background: #3a6bc4; color: #fff; }
.bob-badge { background: #c47a3a; color: #fff; }
.secret-hint { font-size: .75rem; color: #777; margin-top: .3rem; }
.vs { font: 700 1.1rem system-ui; color: #888; flex: 0; }
button { font: 600 14px system-ui; padding: .5rem 1.1rem; border: none;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; margin-bottom: .9rem; }
button:hover { background: #274d82; }
.hidden { display: none; }
.circuit-vis { background: #f4f6fa; border: 1px solid #d0d8e8; border-radius: 10px;
padding: .7rem .8rem; margin-bottom: .7rem; font-size: .83rem; }
.circuit-vis h4 { margin: 0 0 .4rem; font-size: .9rem; color: #1d3557; }
.step { margin: .25rem 0; color: #333; line-height: 1.6; }
.step b { color: #1d3557; }
.bit-row { display: inline-flex; gap: .3rem; align-items: center; }
.chip { display: inline-block; width: 1.6rem; height: 1.6rem; line-height: 1.6rem;
text-align: center; border-radius: 4px; font: 600 .75rem ui-monospace, monospace; }
.chip-enc { background: #dce8f0; color: #1d3557; border: 1px solid #b3c8d8; }
.chip-bit { background: #e8f0dc; color: #3a5a10; border: 1px solid #b3c8a0; }
.result { text-align: center; font-size: 1.1rem; font-weight: 700;
padding: .7rem 1rem; border-radius: 10px; }
.result.alice-wins { background: #dbeafe; color: #1d4ed8; border: 1.5px solid #93c5fd; }
.result.bob-wins { background: #ffedd5; color: #c2410c; border: 1.5px solid #fdba74; }
.result.tie { background: #f0fdf4; color: #166534; border: 1.5px solid #86efac; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Garbled Circuits — Yao’s Millionaires’ Problem (pedagogical simulation).
//
// Scheme: Point-and-Permute with output-bit encryption.
// Each wire has two labels: w0 (bit=0, color=0) and w1 (bit=1, color=1).
// The color bit (LSB of label) lets the evaluator pick the right garbled row without learning which bit it represents.
//
// Gate encryption: each row stores (outBit XOR PRF(La, Lb, gateIdx, rowIdx)).
// The evaluator decrypts the correct row using its held labels and recovers the output label — but cannot learn the underlying bit from the label alone.
//
// Circuit: 3-bit comparison a > b via:
// NOT(b[i]), gt[i] = a[i] AND NOT(b[i]), eq[i] = XNOR(a[i], b[i])
// result = gt[0] OR (eq[0] AND (gt[1] OR (eq[1] AND gt[2])))
const aliceSlider = document.getElementById('alice-slider');
const bobSlider = document.getElementById('bob-slider');
const aliceValEl = document.getElementById('alice-val');
const bobValEl = document.getElementById('bob-val');
const runBtn = document.getElementById('run-btn');
const circuitVis = document.getElementById('circuit-vis');
const resultEl = document.getElementById('result');
aliceSlider.addEventListener('input', () => { aliceValEl.textContent = aliceSlider.value; });
bobSlider.addEventListener('input', () => { bobValEl.textContent = bobSlider.value; });
// --- Toy PRF (xorshift-based; real garbling uses AES) ---
function xorshift(s) {
s = s >>> 0;
s ^= s << 13; s ^= s >>> 17; s ^= s << 5;
return s >>> 0;
}
function prf1bit(La, Lb, gateIdx, rowIdx) {
// Returns a single pseudorandom bit from two label inputs + position
let h = xorshift((La ^ 0xca5cad3e) >>> 0);
h = xorshift((h ^ Lb ^ 0x5ec3e770) >>> 0);
h = xorshift((h ^ ((gateIdx * 53 + rowIdx * 7) >>> 0)) >>> 0);
return h & 1;
}
// --- Wire labels ---
// w0: color bit = 0 (even number), w1: color bit = 1 (odd number)
function freshWire() {
const rnd = () => ((Math.random() * 0x7ffffffe | 0) + 1) >>> 0;
return { w0: (rnd() & ~1) >>> 0, w1: (rnd() | 1) >>> 0 };
}
let gc = 0; // global gate counter
// Two-input garbled gate
function garbleGate(type, wireA, wireB) {
const fns = {
AND: (a,b) => a & b,
OR: (a,b) => a | b,
XOR: (a,b) => a ^ b,
XNOR: (a,b) => (~(a ^ b)) & 1
};
const fn = fns[type];
const out = freshWire();
const gateIdx = gc++;
const table = new Array(4);
for (let ia = 0; ia <= 1; ia++) {
for (let ib = 0; ib <= 1; ib++) {
const La = ia ? wireA.w1 : wireA.w0;
const Lb = ib ? wireB.w1 : wireB.w0;
const outBit = fn(ia, ib) & 1;
const rowIdx = ((La & 1) << 1) | (Lb & 1);
const pad = prf1bit(La, Lb, gateIdx, rowIdx);
table[rowIdx] = (outBit ^ pad) & 1;
}
}
return { table, outW0: out.w0, outW1: out.w1, gateIdx };
}
// One-input NOT gate
function garbleNOT(wireA) {
const out = freshWire();
const gateIdx = gc++;
const table = new Array(2);
for (let ia = 0; ia <= 1; ia++) {
const La = ia ? wireA.w1 : wireA.w0;
const outBit = (1 - ia) & 1;
const rowIdx = La & 1;
const pad = prf1bit(La, 0, gateIdx, rowIdx);
table[rowIdx] = (outBit ^ pad) & 1;
}
return { table, outW0: out.w0, outW1: out.w1, gateIdx, isNOT: true };
}
function evalGate(gate, La, Lb) {
const rowIdx = ((La & 1) << 1) | (Lb & 1);
const pad = prf1bit(La, Lb, gate.gateIdx, rowIdx);
const outBit = (gate.table[rowIdx] ^ pad) & 1;
return outBit ? gate.outW1 : gate.outW0;
}
function evalNOT(gate, La) {
const rowIdx = La & 1;
const pad = prf1bit(La, 0, gate.gateIdx, rowIdx);
const outBit = (gate.table[rowIdx] ^ pad) & 1;
return outBit ? gate.outW1 : gate.outW0;
}
function gateWire(g) { return { w0: g.outW0, w1: g.outW1 }; }
function toHex(n) { return (n >>> 0).toString(16).padStart(8, '0'); }
function chip(cls, txt) { return '<span class="chip ' + cls + '">' + txt + '</span>'; }
runBtn.addEventListener('click', () => {
gc = 0;
const a = parseInt(aliceSlider.value); // 1-8
const b = parseInt(bobSlider.value); // 1-8
// 3-bit big-endian (values 0-7)
const aBits = [(a-1)>>2 & 1, (a-1)>>1 & 1, (a-1) & 1];
const bBits = [(b-1)>>2 & 1, (b-1)>>1 & 1, (b-1) & 1];
// Alice creates input wires
const aWires = aBits.map(() => freshWire());
const bWires = bBits.map(() => freshWire());
// Build circuit
const notBGates = bWires.map(w => garbleNOT(w));
const gtGates = aWires.map((w, i) => garbleGate('AND', w, gateWire(notBGates[i])));
const eqGates = aWires.map((w, i) => garbleGate('XNOR', w, bWires[i]));
const innerGate = garbleGate('AND', gateWire(eqGates[1]), gateWire(gtGates[2]));
const midGate = garbleGate('OR', gateWire(gtGates[1]), gateWire(innerGate));
const leftAndGate = garbleGate('AND', gateWire(eqGates[0]), gateWire(midGate));
const finalGate = garbleGate('OR', gateWire(gtGates[0]), gateWire(leftAndGate));
// Input labels: Alice uses her held labels; Bob receives his via OT
const aliceHeld = aBits.map((bit, i) => bit ? aWires[i].w1 : aWires[i].w0);
const bobHeld = bBits.map((bit, i) => bit ? bWires[i].w1 : bWires[i].w0);
// Bob evaluates
const notB_ev = notBGates.map((g, i) => evalNOT(g, bobHeld[i]));
const gt_ev = gtGates.map((g, i) => evalGate(g, aliceHeld[i], notB_ev[i]));
const eq_ev = eqGates.map((g, i) => evalGate(g, aliceHeld[i], bobHeld[i]));
const inner_ev = evalGate(innerGate, eq_ev[1], gt_ev[2]);
const mid_ev = evalGate(midGate, gt_ev[1], inner_ev);
const leftAnd_ev = evalGate(leftAndGate, eq_ev[0], mid_ev);
const final_ev = evalGate(finalGate, gt_ev[0], leftAnd_ev);
// Alice reveals both output labels; Bob decodes
const aWins = (final_ev === finalGate.outW1);
const truth = (a > b);
// Build trace display
const encA = aliceHeld.map(l => chip('chip-enc', toHex(l).slice(0,4))).join(' ');
const encB = bobHeld.map(l => chip('chip-enc', toHex(l).slice(0,4))).join(' ');
const gtB = gt_ev.map((v, i) => chip('chip-bit', v === gtGates[i].outW1 ? 1 : 0)).join(' ');
const eqB = eq_ev.map((v, i) => chip('chip-bit', v === eqGates[i].outW1 ? 1 : 0)).join(' ');
circuitVis.classList.remove('hidden');
resultEl.classList.remove('hidden');
circuitVis.innerHTML =
fmt('<h4>Circuit evaluation trace</h4><div class="step">Alice’s wire labels (opaque to Bob): <span class="bit-row">%encA%</span></div><div class="step">Bob’s labels received via Oblivious Transfer: <span class="bit-row">%encB%</span></div><div class="step">a[i] > b[i] gate outputs: <span class="bit-row">%gtB%</span></div><div class="step">a[i] == b[i] (XNOR) outputs: <span class="bit-row">%eqB%</span></div><div class="step">Final garbled label: <b>%toHex%</b> → Alice reveals it means <b>%v%</b></div><div class="step" style="font-size:.78rem;color:#666;margin-top:.3rem">✓ Verified correct: circuit computed a > b = <b>%truth%</b>. Labels are opaque — the evaluator sees only encrypted values until the final reveal.</div>', { encA: encA, encB: encB, gtB: gtB, eqB: eqB, toHex: toHex(final_ev).slice(0,8), v: aWins ? '1 (Alice wins)' : '0 (not Alice)', truth: truth });
if (a === b) {
resultEl.className = 'result tie';
resultEl.textContent = fmt('It’s a tie — both chose %a%. Circuit outputs 0 (not strictly greater).', { a: a });
} else if (aWins) {
resultEl.className = 'result alice-wins';
resultEl.textContent = fmt('Alice is richer (%a% > %b%). Bob never learned Alice’s number.', { a: a, b: b });
} else {
resultEl.className = 'result bob-wins';
resultEl.textContent = fmt('Bob is richer (%b% > %a%). Alice never learned Bob’s number.', { b: b, a: a });
}
console.log('a=%d b=%d truth=%s circuit=%s match=%s', a, b, truth, aWins, truth === aWins);
});
<p class="hint">
Pick a secret wealth level for <b>Alice</b> and <b>Bob</b> (1–8). Press <b>Garble & Evaluate</b> to run the secure comparison — neither party learns the other’s number, only who is richer.
</p>
<div class="inputs">
<div class="party alice-side">
<div class="label">Alice’s wealth</div>
<div class="slider-row">
<input id="alice-slider" type="range" min="1" max="8" value="5" class="slider alice-slider">
<span class="val-badge alice-badge" id="alice-val">5</span>
</div>
<div class="secret-hint">(secret — Bob cannot see this)</div>
</div>
<div class="vs">VS</div>
<div class="party bob-side">
<div class="label">Bob’s wealth</div>
<div class="slider-row">
<input id="bob-slider" type="range" min="1" max="8" value="3" class="slider bob-slider">
<span class="val-badge bob-badge" id="bob-val">3</span>
</div>
<div class="secret-hint">(secret — Alice cannot see this)</div>
</div>
</div>
<button id="run-btn" type="button">Garble & Evaluate</button>
<div id="circuit-vis" class="circuit-vis hidden"></div>
<div id="result" class="result hidden"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: 14px .5rem; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.inputs { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-bottom: .9rem; }
.party { flex: 1; min-width: 140px; padding: .7rem .8rem; border-radius: 10px; }
.alice-side { background: #e8f0ff; border: 1px solid #b3c8f5; }
.bob-side { background: #fff0e8; border: 1px solid #f5c8a0; }
.label { font-weight: 700; font-size: .95rem; margin-bottom: .4rem; }
.slider-row { display: flex; align-items: center; gap: .5rem; }
.slider { flex: 1; cursor: pointer; }
.alice-slider { accent-color: #3a6bc4; }
.bob-slider { accent-color: #c47a3a; }
.val-badge { font: 700 1.1rem ui-monospace, monospace; min-width: 1.8rem; text-align: center;
padding: .2rem .4rem; border-radius: 6px; }
.alice-badge { background: #3a6bc4; color: #fff; }
.bob-badge { background: #c47a3a; color: #fff; }
.secret-hint { font-size: .75rem; color: #777; margin-top: .3rem; }
.vs { font: 700 1.1rem system-ui; color: #888; flex: 0; }
button { font: 600 14px system-ui; padding: .5rem 1.1rem; border: none;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; margin-bottom: .9rem; }
button:hover { background: #274d82; }
.hidden { display: none; }
.circuit-vis { background: #f4f6fa; border: 1px solid #d0d8e8; border-radius: 10px;
padding: .7rem .8rem; margin-bottom: .7rem; font-size: .83rem; }
.circuit-vis h4 { margin: 0 0 .4rem; font-size: .9rem; color: #1d3557; }
.step { margin: .25rem 0; color: #333; line-height: 1.6; }
.step b { color: #1d3557; }
.bit-row { display: inline-flex; gap: .3rem; align-items: center; }
.chip { display: inline-block; width: 1.6rem; height: 1.6rem; line-height: 1.6rem;
text-align: center; border-radius: 4px; font: 600 .75rem ui-monospace, monospace; }
.chip-enc { background: #dce8f0; color: #1d3557; border: 1px solid #b3c8d8; }
.chip-bit { background: #e8f0dc; color: #3a5a10; border: 1px solid #b3c8a0; }
.result { text-align: center; font-size: 1.1rem; font-weight: 700;
padding: .7rem 1rem; border-radius: 10px; }
.result.alice-wins { background: #dbeafe; color: #1d4ed8; border: 1.5px solid #93c5fd; }
.result.bob-wins { background: #ffedd5; color: #c2410c; border: 1.5px solid #fdba74; }
.result.tie { background: #f0fdf4; color: #166534; border: 1.5px solid #86efac; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Garbled Circuits — Yao’s Millionaires’ Problem (pedagogical simulation).
//
// Scheme: Point-and-Permute with output-bit encryption.
// Each wire has two labels: w0 (bit=0, color=0) and w1 (bit=1, color=1).
// The color bit (LSB of label) lets the evaluator pick the right garbled row without learning which bit it represents.
//
// Gate encryption: each row stores (outBit XOR PRF(La, Lb, gateIdx, rowIdx)).
// The evaluator decrypts the correct row using its held labels and recovers the output label — but cannot learn the underlying bit from the label alone.
//
// Circuit: 3-bit comparison a > b via:
// NOT(b[i]), gt[i] = a[i] AND NOT(b[i]), eq[i] = XNOR(a[i], b[i])
// result = gt[0] OR (eq[0] AND (gt[1] OR (eq[1] AND gt[2])))
const aliceSlider = document.getElementById('alice-slider');
const bobSlider = document.getElementById('bob-slider');
const aliceValEl = document.getElementById('alice-val');
const bobValEl = document.getElementById('bob-val');
const runBtn = document.getElementById('run-btn');
const circuitVis = document.getElementById('circuit-vis');
const resultEl = document.getElementById('result');
aliceSlider.addEventListener('input', () => { aliceValEl.textContent = aliceSlider.value; });
bobSlider.addEventListener('input', () => { bobValEl.textContent = bobSlider.value; });
// --- Toy PRF (xorshift-based; real garbling uses AES) ---
function xorshift(s) {
s = s >>> 0;
s ^= s << 13; s ^= s >>> 17; s ^= s << 5;
return s >>> 0;
}
function prf1bit(La, Lb, gateIdx, rowIdx) {
// Returns a single pseudorandom bit from two label inputs + position
let h = xorshift((La ^ 0xca5cad3e) >>> 0);
h = xorshift((h ^ Lb ^ 0x5ec3e770) >>> 0);
h = xorshift((h ^ ((gateIdx * 53 + rowIdx * 7) >>> 0)) >>> 0);
return h & 1;
}
// --- Wire labels ---
// w0: color bit = 0 (even number), w1: color bit = 1 (odd number)
function freshWire() {
const rnd = () => ((Math.random() * 0x7ffffffe | 0) + 1) >>> 0;
return { w0: (rnd() & ~1) >>> 0, w1: (rnd() | 1) >>> 0 };
}
let gc = 0; // global gate counter
// Two-input garbled gate
function garbleGate(type, wireA, wireB) {
const fns = {
AND: (a,b) => a & b,
OR: (a,b) => a | b,
XOR: (a,b) => a ^ b,
XNOR: (a,b) => (~(a ^ b)) & 1
};
const fn = fns[type];
const out = freshWire();
const gateIdx = gc++;
const table = new Array(4);
for (let ia = 0; ia <= 1; ia++) {
for (let ib = 0; ib <= 1; ib++) {
const La = ia ? wireA.w1 : wireA.w0;
const Lb = ib ? wireB.w1 : wireB.w0;
const outBit = fn(ia, ib) & 1;
const rowIdx = ((La & 1) << 1) | (Lb & 1);
const pad = prf1bit(La, Lb, gateIdx, rowIdx);
table[rowIdx] = (outBit ^ pad) & 1;
}
}
return { table, outW0: out.w0, outW1: out.w1, gateIdx };
}
// One-input NOT gate
function garbleNOT(wireA) {
const out = freshWire();
const gateIdx = gc++;
const table = new Array(2);
for (let ia = 0; ia <= 1; ia++) {
const La = ia ? wireA.w1 : wireA.w0;
const outBit = (1 - ia) & 1;
const rowIdx = La & 1;
const pad = prf1bit(La, 0, gateIdx, rowIdx);
table[rowIdx] = (outBit ^ pad) & 1;
}
return { table, outW0: out.w0, outW1: out.w1, gateIdx, isNOT: true };
}
function evalGate(gate, La, Lb) {
const rowIdx = ((La & 1) << 1) | (Lb & 1);
const pad = prf1bit(La, Lb, gate.gateIdx, rowIdx);
const outBit = (gate.table[rowIdx] ^ pad) & 1;
return outBit ? gate.outW1 : gate.outW0;
}
function evalNOT(gate, La) {
const rowIdx = La & 1;
const pad = prf1bit(La, 0, gate.gateIdx, rowIdx);
const outBit = (gate.table[rowIdx] ^ pad) & 1;
return outBit ? gate.outW1 : gate.outW0;
}
function gateWire(g) { return { w0: g.outW0, w1: g.outW1 }; }
function toHex(n) { return (n >>> 0).toString(16).padStart(8, '0'); }
function chip(cls, txt) { return '<span class="chip ' + cls + '">' + txt + '</span>'; }
runBtn.addEventListener('click', () => {
gc = 0;
const a = parseInt(aliceSlider.value); // 1-8
const b = parseInt(bobSlider.value); // 1-8
// 3-bit big-endian (values 0-7)
const aBits = [(a-1)>>2 & 1, (a-1)>>1 & 1, (a-1) & 1];
const bBits = [(b-1)>>2 & 1, (b-1)>>1 & 1, (b-1) & 1];
// Alice creates input wires
const aWires = aBits.map(() => freshWire());
const bWires = bBits.map(() => freshWire());
// Build circuit
const notBGates = bWires.map(w => garbleNOT(w));
const gtGates = aWires.map((w, i) => garbleGate('AND', w, gateWire(notBGates[i])));
const eqGates = aWires.map((w, i) => garbleGate('XNOR', w, bWires[i]));
const innerGate = garbleGate('AND', gateWire(eqGates[1]), gateWire(gtGates[2]));
const midGate = garbleGate('OR', gateWire(gtGates[1]), gateWire(innerGate));
const leftAndGate = garbleGate('AND', gateWire(eqGates[0]), gateWire(midGate));
const finalGate = garbleGate('OR', gateWire(gtGates[0]), gateWire(leftAndGate));
// Input labels: Alice uses her held labels; Bob receives his via OT
const aliceHeld = aBits.map((bit, i) => bit ? aWires[i].w1 : aWires[i].w0);
const bobHeld = bBits.map((bit, i) => bit ? bWires[i].w1 : bWires[i].w0);
// Bob evaluates
const notB_ev = notBGates.map((g, i) => evalNOT(g, bobHeld[i]));
const gt_ev = gtGates.map((g, i) => evalGate(g, aliceHeld[i], notB_ev[i]));
const eq_ev = eqGates.map((g, i) => evalGate(g, aliceHeld[i], bobHeld[i]));
const inner_ev = evalGate(innerGate, eq_ev[1], gt_ev[2]);
const mid_ev = evalGate(midGate, gt_ev[1], inner_ev);
const leftAnd_ev = evalGate(leftAndGate, eq_ev[0], mid_ev);
const final_ev = evalGate(finalGate, gt_ev[0], leftAnd_ev);
// Alice reveals both output labels; Bob decodes
const aWins = (final_ev === finalGate.outW1);
const truth = (a > b);
// Build trace display
const encA = aliceHeld.map(l => chip('chip-enc', toHex(l).slice(0,4))).join(' ');
const encB = bobHeld.map(l => chip('chip-enc', toHex(l).slice(0,4))).join(' ');
const gtB = gt_ev.map((v, i) => chip('chip-bit', v === gtGates[i].outW1 ? 1 : 0)).join(' ');
const eqB = eq_ev.map((v, i) => chip('chip-bit', v === eqGates[i].outW1 ? 1 : 0)).join(' ');
circuitVis.classList.remove('hidden');
resultEl.classList.remove('hidden');
circuitVis.innerHTML =
fmt('<h4>Circuit evaluation trace</h4><div class="step">Alice’s wire labels (opaque to Bob): <span class="bit-row">%encA%</span></div><div class="step">Bob’s labels received via Oblivious Transfer: <span class="bit-row">%encB%</span></div><div class="step">a[i] > b[i] gate outputs: <span class="bit-row">%gtB%</span></div><div class="step">a[i] == b[i] (XNOR) outputs: <span class="bit-row">%eqB%</span></div><div class="step">Final garbled label: <b>%toHex%</b> → Alice reveals it means <b>%v%</b></div><div class="step" style="font-size:.78rem;color:#666;margin-top:.3rem">✓ Verified correct: circuit computed a > b = <b>%truth%</b>. Labels are opaque — the evaluator sees only encrypted values until the final reveal.</div>', { encA: encA, encB: encB, gtB: gtB, eqB: eqB, toHex: toHex(final_ev).slice(0,8), v: aWins ? '1 (Alice wins)' : '0 (not Alice)', truth: truth });
if (a === b) {
resultEl.className = 'result tie';
resultEl.textContent = fmt('It’s a tie — both chose %a%. Circuit outputs 0 (not strictly greater).', { a: a });
} else if (aWins) {
resultEl.className = 'result alice-wins';
resultEl.textContent = fmt('Alice is richer (%a% > %b%). Bob never learned Alice’s number.', { a: a, b: b });
} else {
resultEl.className = 'result bob-wins';
resultEl.textContent = fmt('Bob is richer (%b% > %a%). Alice never learned Bob’s number.', { b: b, a: a });
}
console.log('a=%d b=%d truth=%s circuit=%s match=%s', a, b, truth, aWins, truth === aWins);
});
Comments
Loading comments...