The demo below shows the classic 2-server XOR-based PIR scheme from Chor et al. (1995). A database of 8 bits is split across two servers. You pick which bit you want; the protocol constructs two random-looking queries so that each server individually sees a uniformly random subset of positions — learning nothing about your target.
<p class="hint">A public database of 8 bits (D[0]…D[7]). Pick which bit you want to retrieve <b>privately</b>. The XOR-PIR scheme sends two random-looking queries — neither server learns your target index. Only combining their XOR responses reveals your bit.</p>
<div class="db-row" id="db-display"></div>
<div class="picker">
<label>Target index <b id="idx-label">0</b>:</label>
<input type="range" id="idx-slider" min="0" max="7" value="0">
</div>
<div class="protocol-grid" id="protocol-grid">
<div class="proto-col">
<div class="proto-title">Server A query</div>
<div id="qa-bits" class="bit-row"></div>
<div class="proto-sub">random subset S</div>
</div>
<div class="proto-col">
<div class="proto-title">Server B query</div>
<div id="qb-bits" class="bit-row"></div>
<div class="proto-sub">S ⊕ {target}</div>
</div>
</div>
<div class="responses" id="responses">
<div class="resp-box">Server A replies: <b id="ra">–</b></div>
<div class="resp-box">Server B replies: <b id="rb">–</b></div>
</div>
<div class="result-row">
<button id="btn-query" type="button">Send Private Query</button>
<div id="result" class="result-msg"></div>
</div>
<div class="privacy-note" id="privacy-note"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: 14px; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.db-row { display: flex; gap: 5px; margin-bottom: .7rem; }
.db-bit { width: 36px; height: 36px; display: flex; align-items: center; justify-content: center;
font: 700 15px ui-monospace, monospace; border-radius: 6px;
background: #dce8f4; border: 1px solid #aac4de; color: #1d3557; }
.db-bit.target { background: #1d3557; color: #fff; border-color: #1d3557; }
.db-label { font-size: .7rem; color: #888; text-align: center; margin-top: 2px; }
.picker { display: flex; align-items: center; gap: .7rem; margin-bottom: .8rem; }
.picker label { font-size: .9rem; }
#idx-slider { width: 160px; }
.protocol-grid { display: flex; gap: 16px; margin-bottom: .7rem; }
.proto-col { flex: 1; background: #f4f7fa; border: 1px solid #cdd9e3; border-radius: 8px; padding: .5rem .6rem; }
.proto-title { font-size: .8rem; font-weight: 700; color: #1d3557; margin-bottom: .4rem; }
.proto-sub { font-size: .72rem; color: #777; margin-top: .3rem; }
.bit-row { display: flex; flex-wrap: wrap; gap: 4px; }
.q-bit { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
font: 700 12px ui-monospace, monospace; border-radius: 5px;
background: #c9d8e8; border: 1px solid #aab8c8; color: #1d3557; }
.q-bit.hi { background: #e63946; border-color: #c92f3c; color: #fff; }
.q-bit.target-hi { background: #2a9d8f; border-color: #1f7a6e; color: #fff; }
.responses { display: flex; gap: 10px; margin-bottom: .7rem; }
.resp-box { flex: 1; background: #f4f7fa; border: 1px solid #cdd9e3; border-radius: 8px;
padding: .45rem .6rem; font-size: .88rem; text-align: center; }
.resp-box b { color: #1d3557; }
.result-row { display: flex; align-items: center; gap: 1rem; margin-bottom: .6rem; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
.result-msg { font-size: 1rem; font-weight: 700; }
.result-msg.ok { color: #0a7d33; }
.privacy-note { font-size: .8rem; color: #555; background: #f9fafb;
border: 1px solid #dde3ea; border-radius: 6px; padding: .4rem .6rem; line-height: 1.5; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// 2-server XOR-based Private Information Retrieval (Chor et al. 1995).
// Database: n=8 bits. Client wants D[i].
// Protocol:
// 1. Client picks a random subset S ⊆ {0..n-1}.
// 2. Query to server A = S.
// 3. Query to server B = S △ {i} (symmetric difference, i.e. XOR the i-th bit).
// 4. Server A replies XOR of D[j] for j in S.
// 5. Server B replies XOR of D[j] for j in (S △ {i}).
// 6. Client computes rA ⊕ rB = D[i]. (The XORs cancel everywhere except position i.)
// Privacy: each server sees a uniformly random subset (S and S△{i} are both uniform if S is uniform), so neither server learns i.
const N = 8;
// Fixed public database — all values visible to the user.
const DB = [1, 0, 1, 1, 0, 0, 1, 0];
function renderDB(target) {
const row = document.getElementById('db-display');
row.innerHTML = '';
DB.forEach((bit, idx) => {
const wrap = document.createElement('div');
wrap.style.textAlign = 'center';
const cell = document.createElement('div');
cell.className = 'db-bit' + (idx === target ? ' target' : '');
cell.textContent = bit;
const lbl = document.createElement('div');
lbl.className = 'db-label';
lbl.textContent = '[' + idx + ']';
wrap.appendChild(cell);
wrap.appendChild(lbl);
row.appendChild(wrap);
});
}
function renderQueryBits(containerId, setS, targetIdx, isB) {
const row = document.getElementById(containerId);
row.innerHTML = '';
for (let j = 0; j < N; j++) {
const inS = setS.has(j);
const cell = document.createElement('div');
// For server B, highlight the flipped target bit distinctly
if (isB && j === targetIdx) {
cell.className = 'q-bit target-hi';
} else if (inS) {
cell.className = 'q-bit hi';
} else {
cell.className = 'q-bit';
}
cell.textContent = inS ? '1' : '0';
row.appendChild(cell);
}
}
function xorSubset(subset) {
let acc = 0;
subset.forEach(j => { acc ^= DB[j]; });
return acc;
}
function runQuery() {
const i = parseInt(document.getElementById('idx-slider').value, 10);
// Step 1: random subset S
const S = new Set();
for (let j = 0; j < N; j++) {
if (Math.random() < 0.5) S.add(j);
}
// Step 2+3: queries
const SB = new Set(S);
if (SB.has(i)) SB.delete(i); else SB.add(i);
renderQueryBits('qa-bits', S, i, false);
renderQueryBits('qb-bits', SB, i, true);
// Step 4+5: server responses
const rA = xorSubset(S);
const rB = xorSubset(SB);
document.getElementById('ra').textContent = rA;
document.getElementById('rb').textContent = rB;
// Step 6: recover
const recovered = rA ^ rB;
const correct = DB[i];
const match = recovered === correct;
const resultEl = document.getElementById('result');
resultEl.textContent = 'rA ⊕ rB = ' + rA + ' ⊕ ' + rB + ' = ' + recovered + (match ? ' ✓' : ' ✗');
resultEl.className = 'result-msg ' + (match ? 'ok' : 'bad');
// Explain privacy
const sSize = S.size;
const sbSize = SB.size;
document.getElementById('privacy-note').textContent =
fmt('Server A received a subset of size %sSize%; server B received a subset of size %sbSize%. Without colluding, neither can distinguish which index you wanted from any other. The expected query size per server is n/2 = 4 indices.', { sSize: sSize, sbSize: sbSize });
console.log(fmt('PIR run: target=%i% DB[i]=%correct% S=%v% SB=%v2% rA=%rA% rB=%rB% recovered=%recovered% match=%match%', { i: i, correct: correct, v: [...S].join(','), v2: [...SB].join(','), rA: rA, rB: rB, recovered: recovered, match: match }));
}
const slider = document.getElementById('idx-slider');
slider.addEventListener('input', () => {
document.getElementById('idx-label').textContent = slider.value;
renderDB(parseInt(slider.value, 10));
document.getElementById('qa-bits').innerHTML = '';
document.getElementById('qb-bits').innerHTML = '';
document.getElementById('ra').textContent = '–';
document.getElementById('rb').textContent = '–';
document.getElementById('result').textContent = '';
document.getElementById('privacy-note').textContent = '';
});
document.getElementById('btn-query').addEventListener('click', runQuery);
// Initial render
renderDB(0);
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// 2-server XOR-based Private Information Retrieval (Chor et al. 1995).
// Database: n=8 bits. Client wants D[i].
// Protocol:
// 1. Client picks a random subset S ⊆ {0..n-1}.
// 2. Query to server A = S.
// 3. Query to server B = S △ {i} (symmetric difference, i.e. XOR the i-th bit).
// 4. Server A replies XOR of D[j] for j in S.
// 5. Server B replies XOR of D[j] for j in (S △ {i}).
// 6. Client computes rA ⊕ rB = D[i]. (The XORs cancel everywhere except position i.)
// Privacy: each server sees a uniformly random subset (S and S△{i} are both uniform if S is uniform), so neither server learns i.
const N = 8;
// Fixed public database — all values visible to the user.
const DB = [1, 0, 1, 1, 0, 0, 1, 0];
function renderDB(target) {
const row = document.getElementById('db-display');
row.innerHTML = '';
DB.forEach((bit, idx) => {
const wrap = document.createElement('div');
wrap.style.textAlign = 'center';
const cell = document.createElement('div');
cell.className = 'db-bit' + (idx === target ? ' target' : '');
cell.textContent = bit;
const lbl = document.createElement('div');
lbl.className = 'db-label';
lbl.textContent = '[' + idx + ']';
wrap.appendChild(cell);
wrap.appendChild(lbl);
row.appendChild(wrap);
});
}
function renderQueryBits(containerId, setS, targetIdx, isB) {
const row = document.getElementById(containerId);
row.innerHTML = '';
for (let j = 0; j < N; j++) {
const inS = setS.has(j);
const cell = document.createElement('div');
// For server B, highlight the flipped target bit distinctly
if (isB && j === targetIdx) {
cell.className = 'q-bit target-hi';
} else if (inS) {
cell.className = 'q-bit hi';
} else {
cell.className = 'q-bit';
}
cell.textContent = inS ? '1' : '0';
row.appendChild(cell);
}
}
function xorSubset(subset) {
let acc = 0;
subset.forEach(j => { acc ^= DB[j]; });
return acc;
}
function runQuery() {
const i = parseInt(document.getElementById('idx-slider').value, 10);
// Step 1: random subset S
const S = new Set();
for (let j = 0; j < N; j++) {
if (Math.random() < 0.5) S.add(j);
}
// Step 2+3: queries
const SB = new Set(S);
if (SB.has(i)) SB.delete(i); else SB.add(i);
renderQueryBits('qa-bits', S, i, false);
renderQueryBits('qb-bits', SB, i, true);
// Step 4+5: server responses
const rA = xorSubset(S);
const rB = xorSubset(SB);
document.getElementById('ra').textContent = rA;
document.getElementById('rb').textContent = rB;
// Step 6: recover
const recovered = rA ^ rB;
const correct = DB[i];
const match = recovered === correct;
const resultEl = document.getElementById('result');
resultEl.textContent = 'rA ⊕ rB = ' + rA + ' ⊕ ' + rB + ' = ' + recovered + (match ? ' ✓' : ' ✗');
resultEl.className = 'result-msg ' + (match ? 'ok' : 'bad');
// Explain privacy
const sSize = S.size;
const sbSize = SB.size;
document.getElementById('privacy-note').textContent =
fmt('Server A received a subset of size %sSize%; server B received a subset of size %sbSize%. Without colluding, neither can distinguish which index you wanted from any other. The expected query size per server is n/2 = 4 indices.', { sSize: sSize, sbSize: sbSize });
console.log(fmt('PIR run: target=%i% DB[i]=%correct% S=%v% SB=%v2% rA=%rA% rB=%rB% recovered=%recovered% match=%match%', { i: i, correct: correct, v: [...S].join(','), v2: [...SB].join(','), rA: rA, rB: rB, recovered: recovered, match: match }));
}
const slider = document.getElementById('idx-slider');
slider.addEventListener('input', () => {
document.getElementById('idx-label').textContent = slider.value;
renderDB(parseInt(slider.value, 10));
document.getElementById('qa-bits').innerHTML = '';
document.getElementById('qb-bits').innerHTML = '';
document.getElementById('ra').textContent = '–';
document.getElementById('rb').textContent = '–';
document.getElementById('result').textContent = '';
document.getElementById('privacy-note').textContent = '';
});
document.getElementById('btn-query').addEventListener('click', runQuery);
// Initial render
renderDB(0);
Notice what each server receives: a random subset of indices. Neither server can tell which bit you care about. Only by combining both responses with XOR do you recover your bit. This is information-theoretic privacy — it holds even against a computationally unbounded server — and the query cost is just n/2 bits per server instead of the naive n bits.
The catch is the two-server assumption: if the two servers collude and compare notes, the privacy evaporates. Avoiding that limitation requires heavier cryptography (lattice-based or homomorphic schemes) but then you can achieve privacy with a single server.
Comments
Loading comments...