The demo below shows a small memory bank of eight slots. Each slot holds a secret value (hidden from view). Click Read a slot to fetch one — in Naive mode the accessed slot lights up, revealing exactly which address was touched. Switch to ORAM mode and every read reshuffles all slots through dummy accesses first, so an observer watching the address bus sees the same uniform pattern regardless of which slot you actually wanted.
<!-- ORAM demo: naive vs oblivious memory access -->
<div class="controls">
<label class="toggle-wrap">
<input type="checkbox" id="oramToggle">
<span class="toggle-label">ORAM mode</span>
</label>
<button id="readBtn" type="button">Read a slot</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
<div class="mode-badge" id="modeBadge">NAIVE — leaks address</div>
<div class="memory" id="memory"></div>
<div class="log-wrap">
<div class="log-title">Access log (observer's view)</div>
<div id="log" class="log"></div>
</div>
<div class="status" id="status"></div>
/* Styles for the ORAM demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.controls { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-bottom: .5rem; }
.toggle-wrap { display: flex; align-items: center; gap: .4rem; cursor: pointer; font-size: .9rem; }
.toggle-label { font-weight: 600; }
button { font: 600 14px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.mode-badge { display: inline-block; padding: .2rem .65rem; border-radius: 20px;
font-size: .8rem; font-weight: 700; margin-bottom: .5rem;
background: #fde8e8; color: #c0392b; }
.mode-badge.oram { background: #e2f4e8; color: #1a7a3c; }
.memory { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: .6rem; }
.slot { width: 54px; height: 54px; display: flex; flex-direction: column;
align-items: center; justify-content: center; border-radius: 8px;
border: 2px solid #cdd9e3; background: #e8eef3;
font: 700 12px ui-monospace, monospace; transition: all .25s; user-select: none; }
.slot .addr { font-size: 10px; color: #6c7a8a; font-weight: 400; margin-bottom: 2px; }
.slot .val { font-size: 15px; color: #1d3557; }
.slot.accessed { background: #e63946; border-color: #c92f3c; }
.slot.accessed .addr, .slot.accessed .val { color: #fff; }
.slot.dummy { background: #f5c542; border-color: #d4a017; }
.slot.dummy .addr, .slot.dummy .val { color: #5a4000; }
.slot.target-oram { background: #2a9d5c; border-color: #1e7344; }
.slot.target-oram .addr, .slot.target-oram .val { color: #fff; }
.log-wrap { margin-top: .4rem; }
.log-title { font-size: .78rem; font-weight: 700; color: #6c7a8a; margin-bottom: 3px; }
.log { font: 12px ui-monospace, monospace; background: #f4f6f8; border-radius: 6px;
padding: .4rem .6rem; min-height: 60px; max-height: 100px; overflow-y: auto;
border: 1px solid #cdd9e3; }
.log .entry { margin: 1px 0; }
.log .entry.naive { color: #c0392b; }
.log .entry.oram-access { color: #1a7a3c; }
.log .entry.oram-dummy { color: #b08000; }
.status { font-size: .9rem; font-weight: 600; min-height: 1.2em; margin-top: .3rem; }
.status.ok { color: #0a7d33; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// ORAM demo: compare naive vs oblivious memory reads
const N = 8; // number of memory slots
// secret logical values (hidden from observer)
const SECRET = ['A1','B7','C3','D9','E2','F5','G8','H4'];
let oramMode = false;
let accessCount = 0;
// Fisher-Yates shuffle
function shuffle(arr) {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
// permutation[phys] = logical index stored at physical slot phys
let permutation = Array.from({ length: N }, (_, i) => i);
function renderSlots(highlighted) {
// render all physical slots; only show addresses, never values
const mem = document.getElementById('memory');
mem.innerHTML = '';
for (let phys = 0; phys < N; phys++) {
const logical = permutation[phys];
const d = document.createElement('div');
d.className = 'slot';
if (highlighted) {
if (highlighted.type === 'naive' && phys === highlighted.phys) d.className += ' accessed';
if (highlighted.type === 'oram' && phys === highlighted.real) d.className += ' target-oram';
if (highlighted.type === 'oram' && highlighted.dummies && highlighted.dummies.includes(phys)) d.className += ' dummy';
}
d.innerHTML = `<span class="addr">${'S%i%'.replace('%i%', phys)}</span><span class="val">?</span>`;
mem.appendChild(d);
}
}
function appendLog(text, cls) {
const log = document.getElementById('log');
const e = document.createElement('div');
e.className = 'entry ' + cls;
e.textContent = text;
log.appendChild(e);
log.scrollTop = log.scrollHeight;
}
function setStatus(text, cls) {
const s = document.getElementById('status');
s.textContent = text;
s.className = 'status ' + (cls || '');
}
function naiveRead() {
// Naive read: directly access the target physical slot — address is visible
const target = Math.floor(Math.random() * N);
accessCount++;
renderSlots({ type: 'naive', phys: target });
const val = SECRET[permutation[target]];
appendLog(i18nFmt('READ phys[%addr%] → value *** (address EXPOSED)', { addr: target, val: val }), 'naive');
setStatus(i18nFmt('Read physical slot %addr% — observer sees exactly which slot was touched.', { addr: target }), '');
}
function oramRead() {
// ORAM read: reshuffle all slots, touch all physical addresses
const logicalTarget = Math.floor(Math.random() * N);
// assign a new random physical location to every logical slot
const newPerm = shuffle(Array.from({ length: N }, (_, i) => i));
const dummyPhys = [];
for (let phys = 0; phys < N; phys++) {
if (newPerm[phys] !== logicalTarget) dummyPhys.push(phys);
}
const realPhys = newPerm.indexOf(logicalTarget);
// touch every physical slot (dummy + real) so observer cannot distinguish
for (let phys = 0; phys < N; phys++) {
if (phys !== realPhys) {
appendLog(i18nFmt('TOUCH phys[%addr%] (dummy)', { addr: phys }), 'oram-dummy');
}
}
permutation = newPerm;
accessCount++;
renderSlots({ type: 'oram', real: realPhys, dummies: dummyPhys });
const val = SECRET[logicalTarget];
appendLog(i18nFmt('READ phys[%addr%] ← logical[%logical%] → value *** (hidden in uniform noise)', { addr: realPhys, logical: logicalTarget, val: val }), 'oram-access');
setStatus(i18nFmt('Read logical slot %logical% now at phys[%addr%] — observer sees uniform noise.', { logical: logicalTarget, addr: realPhys }), 'ok');
}
document.getElementById('oramToggle').addEventListener('change', function() {
oramMode = this.checked;
const badge = document.getElementById('modeBadge');
if (oramMode) { badge.textContent = 'ORAM — address hidden'; badge.className = 'mode-badge oram'; }
else { badge.textContent = 'NAIVE — leaks address'; badge.className = 'mode-badge'; }
renderSlots(null);
setStatus('');
});
document.getElementById('readBtn').addEventListener('click', function() {
if (oramMode) oramRead(); else naiveRead();
});
document.getElementById('resetBtn').addEventListener('click', function() {
permutation = Array.from({ length: N }, (_, i) => i);
accessCount = 0;
document.getElementById('log').innerHTML = '';
renderSlots(null);
setStatus('');
});
renderSlots(null);
<!-- ORAM demo: naive vs oblivious memory access -->
<div class="controls">
<label class="toggle-wrap">
<input type="checkbox" id="oramToggle">
<span class="toggle-label">ORAM mode</span>
</label>
<button id="readBtn" type="button">Read a slot</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
<div class="mode-badge" id="modeBadge">NAIVE — leaks address</div>
<div class="memory" id="memory"></div>
<div class="log-wrap">
<div class="log-title">Access log (observer's view)</div>
<div id="log" class="log"></div>
</div>
<div class="status" id="status"></div>
/* Styles for the ORAM demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.controls { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-bottom: .5rem; }
.toggle-wrap { display: flex; align-items: center; gap: .4rem; cursor: pointer; font-size: .9rem; }
.toggle-label { font-weight: 600; }
button { font: 600 14px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.mode-badge { display: inline-block; padding: .2rem .65rem; border-radius: 20px;
font-size: .8rem; font-weight: 700; margin-bottom: .5rem;
background: #fde8e8; color: #c0392b; }
.mode-badge.oram { background: #e2f4e8; color: #1a7a3c; }
.memory { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: .6rem; }
.slot { width: 54px; height: 54px; display: flex; flex-direction: column;
align-items: center; justify-content: center; border-radius: 8px;
border: 2px solid #cdd9e3; background: #e8eef3;
font: 700 12px ui-monospace, monospace; transition: all .25s; user-select: none; }
.slot .addr { font-size: 10px; color: #6c7a8a; font-weight: 400; margin-bottom: 2px; }
.slot .val { font-size: 15px; color: #1d3557; }
.slot.accessed { background: #e63946; border-color: #c92f3c; }
.slot.accessed .addr, .slot.accessed .val { color: #fff; }
.slot.dummy { background: #f5c542; border-color: #d4a017; }
.slot.dummy .addr, .slot.dummy .val { color: #5a4000; }
.slot.target-oram { background: #2a9d5c; border-color: #1e7344; }
.slot.target-oram .addr, .slot.target-oram .val { color: #fff; }
.log-wrap { margin-top: .4rem; }
.log-title { font-size: .78rem; font-weight: 700; color: #6c7a8a; margin-bottom: 3px; }
.log { font: 12px ui-monospace, monospace; background: #f4f6f8; border-radius: 6px;
padding: .4rem .6rem; min-height: 60px; max-height: 100px; overflow-y: auto;
border: 1px solid #cdd9e3; }
.log .entry { margin: 1px 0; }
.log .entry.naive { color: #c0392b; }
.log .entry.oram-access { color: #1a7a3c; }
.log .entry.oram-dummy { color: #b08000; }
.status { font-size: .9rem; font-weight: 600; min-height: 1.2em; margin-top: .3rem; }
.status.ok { color: #0a7d33; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// ORAM demo: compare naive vs oblivious memory reads
const N = 8; // number of memory slots
// secret logical values (hidden from observer)
const SECRET = ['A1','B7','C3','D9','E2','F5','G8','H4'];
let oramMode = false;
let accessCount = 0;
// Fisher-Yates shuffle
function shuffle(arr) {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
// permutation[phys] = logical index stored at physical slot phys
let permutation = Array.from({ length: N }, (_, i) => i);
function renderSlots(highlighted) {
// render all physical slots; only show addresses, never values
const mem = document.getElementById('memory');
mem.innerHTML = '';
for (let phys = 0; phys < N; phys++) {
const logical = permutation[phys];
const d = document.createElement('div');
d.className = 'slot';
if (highlighted) {
if (highlighted.type === 'naive' && phys === highlighted.phys) d.className += ' accessed';
if (highlighted.type === 'oram' && phys === highlighted.real) d.className += ' target-oram';
if (highlighted.type === 'oram' && highlighted.dummies && highlighted.dummies.includes(phys)) d.className += ' dummy';
}
d.innerHTML = `<span class="addr">${'S%i%'.replace('%i%', phys)}</span><span class="val">?</span>`;
mem.appendChild(d);
}
}
function appendLog(text, cls) {
const log = document.getElementById('log');
const e = document.createElement('div');
e.className = 'entry ' + cls;
e.textContent = text;
log.appendChild(e);
log.scrollTop = log.scrollHeight;
}
function setStatus(text, cls) {
const s = document.getElementById('status');
s.textContent = text;
s.className = 'status ' + (cls || '');
}
function naiveRead() {
// Naive read: directly access the target physical slot — address is visible
const target = Math.floor(Math.random() * N);
accessCount++;
renderSlots({ type: 'naive', phys: target });
const val = SECRET[permutation[target]];
appendLog(i18nFmt('READ phys[%addr%] → value *** (address EXPOSED)', { addr: target, val: val }), 'naive');
setStatus(i18nFmt('Read physical slot %addr% — observer sees exactly which slot was touched.', { addr: target }), '');
}
function oramRead() {
// ORAM read: reshuffle all slots, touch all physical addresses
const logicalTarget = Math.floor(Math.random() * N);
// assign a new random physical location to every logical slot
const newPerm = shuffle(Array.from({ length: N }, (_, i) => i));
const dummyPhys = [];
for (let phys = 0; phys < N; phys++) {
if (newPerm[phys] !== logicalTarget) dummyPhys.push(phys);
}
const realPhys = newPerm.indexOf(logicalTarget);
// touch every physical slot (dummy + real) so observer cannot distinguish
for (let phys = 0; phys < N; phys++) {
if (phys !== realPhys) {
appendLog(i18nFmt('TOUCH phys[%addr%] (dummy)', { addr: phys }), 'oram-dummy');
}
}
permutation = newPerm;
accessCount++;
renderSlots({ type: 'oram', real: realPhys, dummies: dummyPhys });
const val = SECRET[logicalTarget];
appendLog(i18nFmt('READ phys[%addr%] ← logical[%logical%] → value *** (hidden in uniform noise)', { addr: realPhys, logical: logicalTarget, val: val }), 'oram-access');
setStatus(i18nFmt('Read logical slot %logical% now at phys[%addr%] — observer sees uniform noise.', { logical: logicalTarget, addr: realPhys }), 'ok');
}
document.getElementById('oramToggle').addEventListener('change', function() {
oramMode = this.checked;
const badge = document.getElementById('modeBadge');
if (oramMode) { badge.textContent = 'ORAM — address hidden'; badge.className = 'mode-badge oram'; }
else { badge.textContent = 'NAIVE — leaks address'; badge.className = 'mode-badge'; }
renderSlots(null);
setStatus('');
});
document.getElementById('readBtn').addEventListener('click', function() {
if (oramMode) oramRead(); else naiveRead();
});
document.getElementById('resetBtn').addEventListener('click', function() {
permutation = Array.from({ length: N }, (_, i) => i);
accessCount = 0;
document.getElementById('log').innerHTML = '';
renderSlots(null);
setStatus('');
});
renderSlots(null);
Notice that in ORAM mode the number of physical memory touches per logical read grows — that overhead is the cost of obliviousness. Reducing that overhead while keeping the access pattern truly random is the central research challenge.
Comments
Loading comments...