Below you can experiment with the core idea of the NW generator. Choose a seed (a short truly-random string), pick the hard function used to stretch it, and observe how the output bits behave under a simple frequency test.
<p class="hint">
Choose a <b>seed length</b> and a <b>hard function</b>, then click <b>Generate</b>
to stretch the seed into pseudorandom bits. The frequency test checks how close the
fraction of 1-bits is to 0.5 — a basic randomness sanity check.
</p>
<div class="controls">
<label>Seed length (bits):
<select id="seedLen">
<option value="8">8</option>
<option value="12" selected>12</option>
<option value="16">16</option>
</select>
</label>
<label>Hard function:
<select id="hardFn">
<option value="inner">Inner product mod 2</option>
<option value="majority">Majority</option>
<option value="parity">Parity</option>
</select>
</label>
<label>Output length (bits):
<select id="outLen">
<option value="32">32</option>
<option value="64" selected>64</option>
<option value="128">128</option>
</select>
</label>
</div>
<div class="btn-row">
<button id="btnGen" type="button">Generate</button>
<button id="btnReSeed" type="button" class="ghost">New seed</button>
</div>
<div id="seedDisplay" class="mono-row seed-label"></div>
<div id="bitGrid" class="bit-grid"></div>
<div id="stats" class="stats"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .75rem; line-height: 1.45; }
.controls { display: flex; flex-wrap: wrap; gap: .5rem 1.2rem; margin-bottom: .6rem; }
label { display: flex; align-items: center; gap: .4rem; font-size: .88rem; }
select { font: inherit; padding: .2rem .4rem; border: 1px solid #bbb; border-radius: 5px; background: #f8f9fa; }
.btn-row { display: flex; gap: .5rem; margin-bottom: .5rem; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.seed-label { font: 12px ui-monospace, monospace; color: #555; margin-bottom: .4rem;
word-break: break-all; min-height: 1.4em; }
.bit-grid { display: flex; flex-wrap: wrap; gap: 3px; margin-bottom: .6rem; min-height: 40px; }
.bit { width: 14px; height: 14px; border-radius: 3px; display: inline-block; }
.bit.one { background: #1d3557; }
.bit.zero { background: #c9ccd1; }
.stats { font-size: .9rem; line-height: 1.7; min-height: 3em; }
.stats .ok { color: #0a7d33; font-weight: 600; }
.stats .bad { color: #c92f3c; font-weight: 600; }
.stats .label { color: #555; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Nisan–Wigderson generator demo (simplified, illustrative).
//
// Real NW construction:
// seed s in {0,1}^n, combinatorial design {S_1,...,S_ell} with |S_i|=n,
// |S_i ∩ S_j| ≤ log(ell), output bit i = f(s restricted to S_i).
//
// Here we use a simplified design: S_i selects bits at positions
// { (i*k + j) mod n : j = 0..k-1 } where k = floor(n/2).
// The "hard function" f is applied to those k bits to produce one output bit.
// This captures the seed-stretching spirit without full combinatorics.
let seed = [];
function randomSeed(n) {
return Array.from({length: n}, () => Math.random() < 0.5 ? 0 : 1);
}
// Hard functions f: {0,1}^k -> {0,1}
function innerProduct(bits) {
// sum of products of consecutive pairs, mod 2
let s = 0;
for (let i = 0; i + 1 < bits.length; i += 2) s ^= (bits[i] & bits[i+1]);
return s;
}
function majority(bits) {
const ones = bits.reduce((a, b) => a + b, 0);
return ones * 2 >= bits.length ? 1 : 0;
}
function parity(bits) {
return bits.reduce((a, b) => a ^ b, 0);
}
function getHardFn(name) {
if (name === 'inner') return innerProduct;
if (name === 'majority') return majority;
return parity;
}
// Simplified NW design: S_i = { (i*step + j) mod n : j=0..k-1 }
function nwDesign(i, n, k) {
const step = Math.max(1, Math.floor(n / k));
const indices = [];
for (let j = 0; j < k; j++) {
indices.push((i * step + j) % n);
}
return indices;
}
function generate() {
const n = parseInt(document.getElementById('seedLen').value);
const ell = parseInt(document.getElementById('outLen').value);
const fnName = document.getElementById('hardFn').value;
const f = getHardFn(fnName);
const k = Math.max(2, Math.floor(n / 2));
if (seed.length !== n) seed = randomSeed(n);
const output = [];
for (let i = 0; i < ell; i++) {
const indices = nwDesign(i, n, k);
const slice = indices.map(idx => seed[idx]);
output.push(f(slice));
}
// Display seed
document.getElementById('seedDisplay').textContent =
fmt('Seed (%n% bits): %seed%', { n: n, seed: seed.join('') });
// Display bit grid
const grid = document.getElementById('bitGrid');
grid.innerHTML = '';
output.forEach(b => {
const el = document.createElement('span');
el.className = 'bit ' + (b ? 'one' : 'zero');
el.title = b;
grid.appendChild(el);
});
// Frequency test
const ones = output.reduce((a, b) => a + b, 0);
const freq = ones / ell;
const deviation = Math.abs(freq - 0.5);
const passThreshold = 0.15; // generous for small samples
const pass = deviation < passThreshold;
const fnLabel = {inner:'Inner product mod 2', majority:'Majority', parity:'Parity'}[fnName];
document.getElementById('stats').innerHTML =
fmt('<span class="label">Output bits:</span> %ell% | <span class="label">Ones:</span> %ones% (%v%%) | <span class="label">Zeros:</span> %v2% (%v3%%)<br><span class="label">Function:</span> %fnLabel%<br><span class="label">Frequency test:</span> %v4%', { ell: ell, ones: ones, v: (freq*100).toFixed(1), v2: ell-ones, v3: ((1-freq)*100).toFixed(1), fnLabel: fnLabel, v4: pass
? '<span class="ok">' + fmt('✓ Pass — fraction of 1s near 0.5 (deviation %deviation%%)', { deviation: (deviation*100).toFixed(1) }) + '</span>'
: '<span class="bad">' + fmt('✗ Weak — fraction of 1s skewed (deviation %deviation%%). Try a different function or seed.', { deviation: (deviation*100).toFixed(1) }) + '</span>' });
console.log('NW demo | seed:', seed.join(''), '| fn:', fnName, '| ones:', ones, '/', ell);
}
document.getElementById('btnGen').addEventListener('click', generate);
document.getElementById('btnReSeed').addEventListener('click', () => {
const n = parseInt(document.getElementById('seedLen').value);
seed = randomSeed(n);
generate();
});
// Initial run
seed = randomSeed(parseInt(document.getElementById('seedLen').value));
generate();
<p class="hint">
Choose a <b>seed length</b> and a <b>hard function</b>, then click <b>Generate</b>
to stretch the seed into pseudorandom bits. The frequency test checks how close the
fraction of 1-bits is to 0.5 — a basic randomness sanity check.
</p>
<div class="controls">
<label>Seed length (bits):
<select id="seedLen">
<option value="8">8</option>
<option value="12" selected>12</option>
<option value="16">16</option>
</select>
</label>
<label>Hard function:
<select id="hardFn">
<option value="inner">Inner product mod 2</option>
<option value="majority">Majority</option>
<option value="parity">Parity</option>
</select>
</label>
<label>Output length (bits):
<select id="outLen">
<option value="32">32</option>
<option value="64" selected>64</option>
<option value="128">128</option>
</select>
</label>
</div>
<div class="btn-row">
<button id="btnGen" type="button">Generate</button>
<button id="btnReSeed" type="button" class="ghost">New seed</button>
</div>
<div id="seedDisplay" class="mono-row seed-label"></div>
<div id="bitGrid" class="bit-grid"></div>
<div id="stats" class="stats"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .75rem; line-height: 1.45; }
.controls { display: flex; flex-wrap: wrap; gap: .5rem 1.2rem; margin-bottom: .6rem; }
label { display: flex; align-items: center; gap: .4rem; font-size: .88rem; }
select { font: inherit; padding: .2rem .4rem; border: 1px solid #bbb; border-radius: 5px; background: #f8f9fa; }
.btn-row { display: flex; gap: .5rem; margin-bottom: .5rem; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.seed-label { font: 12px ui-monospace, monospace; color: #555; margin-bottom: .4rem;
word-break: break-all; min-height: 1.4em; }
.bit-grid { display: flex; flex-wrap: wrap; gap: 3px; margin-bottom: .6rem; min-height: 40px; }
.bit { width: 14px; height: 14px; border-radius: 3px; display: inline-block; }
.bit.one { background: #1d3557; }
.bit.zero { background: #c9ccd1; }
.stats { font-size: .9rem; line-height: 1.7; min-height: 3em; }
.stats .ok { color: #0a7d33; font-weight: 600; }
.stats .bad { color: #c92f3c; font-weight: 600; }
.stats .label { color: #555; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Nisan–Wigderson generator demo (simplified, illustrative).
//
// Real NW construction:
// seed s in {0,1}^n, combinatorial design {S_1,...,S_ell} with |S_i|=n,
// |S_i ∩ S_j| ≤ log(ell), output bit i = f(s restricted to S_i).
//
// Here we use a simplified design: S_i selects bits at positions
// { (i*k + j) mod n : j = 0..k-1 } where k = floor(n/2).
// The "hard function" f is applied to those k bits to produce one output bit.
// This captures the seed-stretching spirit without full combinatorics.
let seed = [];
function randomSeed(n) {
return Array.from({length: n}, () => Math.random() < 0.5 ? 0 : 1);
}
// Hard functions f: {0,1}^k -> {0,1}
function innerProduct(bits) {
// sum of products of consecutive pairs, mod 2
let s = 0;
for (let i = 0; i + 1 < bits.length; i += 2) s ^= (bits[i] & bits[i+1]);
return s;
}
function majority(bits) {
const ones = bits.reduce((a, b) => a + b, 0);
return ones * 2 >= bits.length ? 1 : 0;
}
function parity(bits) {
return bits.reduce((a, b) => a ^ b, 0);
}
function getHardFn(name) {
if (name === 'inner') return innerProduct;
if (name === 'majority') return majority;
return parity;
}
// Simplified NW design: S_i = { (i*step + j) mod n : j=0..k-1 }
function nwDesign(i, n, k) {
const step = Math.max(1, Math.floor(n / k));
const indices = [];
for (let j = 0; j < k; j++) {
indices.push((i * step + j) % n);
}
return indices;
}
function generate() {
const n = parseInt(document.getElementById('seedLen').value);
const ell = parseInt(document.getElementById('outLen').value);
const fnName = document.getElementById('hardFn').value;
const f = getHardFn(fnName);
const k = Math.max(2, Math.floor(n / 2));
if (seed.length !== n) seed = randomSeed(n);
const output = [];
for (let i = 0; i < ell; i++) {
const indices = nwDesign(i, n, k);
const slice = indices.map(idx => seed[idx]);
output.push(f(slice));
}
// Display seed
document.getElementById('seedDisplay').textContent =
fmt('Seed (%n% bits): %seed%', { n: n, seed: seed.join('') });
// Display bit grid
const grid = document.getElementById('bitGrid');
grid.innerHTML = '';
output.forEach(b => {
const el = document.createElement('span');
el.className = 'bit ' + (b ? 'one' : 'zero');
el.title = b;
grid.appendChild(el);
});
// Frequency test
const ones = output.reduce((a, b) => a + b, 0);
const freq = ones / ell;
const deviation = Math.abs(freq - 0.5);
const passThreshold = 0.15; // generous for small samples
const pass = deviation < passThreshold;
const fnLabel = {inner:'Inner product mod 2', majority:'Majority', parity:'Parity'}[fnName];
document.getElementById('stats').innerHTML =
fmt('<span class="label">Output bits:</span> %ell% | <span class="label">Ones:</span> %ones% (%v%%) | <span class="label">Zeros:</span> %v2% (%v3%%)<br><span class="label">Function:</span> %fnLabel%<br><span class="label">Frequency test:</span> %v4%', { ell: ell, ones: ones, v: (freq*100).toFixed(1), v2: ell-ones, v3: ((1-freq)*100).toFixed(1), fnLabel: fnLabel, v4: pass
? '<span class="ok">' + fmt('✓ Pass — fraction of 1s near 0.5 (deviation %deviation%%)', { deviation: (deviation*100).toFixed(1) }) + '</span>'
: '<span class="bad">' + fmt('✗ Weak — fraction of 1s skewed (deviation %deviation%%). Try a different function or seed.', { deviation: (deviation*100).toFixed(1) }) + '</span>' });
console.log('NW demo | seed:', seed.join(''), '| fn:', fnName, '| ones:', ones, '/', ell);
}
document.getElementById('btnGen').addEventListener('click', generate);
document.getElementById('btnReSeed').addEventListener('click', () => {
const n = parseInt(document.getElementById('seedLen').value);
seed = randomSeed(n);
generate();
});
// Initial run
seed = randomSeed(parseInt(document.getElementById('seedLen').value));
generate();
Notice: the seed is short, but the output is long. The frequency test checks whether the fraction of 1-bits stays near 0.5 — a necessary (though not sufficient) condition for randomness. A truly pseudorandom output passes even much harder tests; the demo gives you an intuition for the stretching step.
Comments
Loading comments...