Below, a secret value is encoded into 6 shards so that any 4 can rebuild it. Click shards to "destroy" them, then press Rebuild and watch the original reappear — or fail when too few survive.
<p class="hint">A secret value is encoded into <b>6 shards</b> using a degree-3 polynomial. <b>Any 4</b> shards can rebuild it. Click shards to destroy them, then press <b>Rebuild</b>. With 4 or more survivors it always works — with 3 or fewer it is mathematically impossible.</p>
<div class="secret">Secret value: <span id="secret">?</span></div>
<div id="shards" class="shards"></div>
<div class="status" id="status">6 of 6 shards alive — any 4 suffice.</div>
<div class="btns">
<button id="rebuild" type="button">Rebuild from survivors</button>
<button id="reset" type="button" class="ghost">New secret</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; }
.secret { font-size: 1rem; font-weight: 600; margin: .3rem 0 .6rem; }
.secret span { font-family: ui-monospace, monospace; color: #1d3557; }
.shards { display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px; margin: .5rem 0; }
.shard { padding: .6rem .3rem; border-radius: 10px; text-align: center; cursor: pointer;
user-select: none; border: 2px solid #cdd9e3; background: #e8eef3; transition: all .12s; }
.shard:hover { border-color: #1d3557; }
.shard .lbl { font-size: .72rem; color: #57708a; }
.shard .val { font: 700 16px ui-monospace, monospace; color: #1d3557; margin-top: 2px; }
.shard.dead { background: #f3d6d9; border-color: #e0a4ab; opacity: .55; }
.shard.dead .val, .shard.dead .lbl { color: #b03a46; text-decoration: line-through; }
.status { font-size: 1rem; font-weight: 600; margin: .6rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
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] ?? ''));
// Reed-Solomon-style erasure coding (over rationals for a clear visual):
// secret S is the value at x=0 of a degree k-1 = 3 polynomial f.
// The 6 shards are f(1)..f(6). Any k=4 of them uniquely fix f (interpolation),
// so f(0)=S is recovered. With <4 survivors, infinitely many curves fit -> impossible.
const N = 6, K = 4; // n shards, any k recover
const XS = [1,2,3,4,5,6]; // evaluation points for the shards
let coeffs = []; // [a0(=secret), a1, a2, a3], f(x)=a0+a1 x+a2 x^2+a3 x^3
let shardVals = []; // f(x) for x in XS
const dead = new Set(); // indices (0..5) of destroyed shards
function feval(c, x){ let r=0; for(let i=c.length-1;i>=0;i--) r=r*x+c[i]; return r; }
// Lagrange interpolation: recover f(0) from the surviving (x, y) points.
function recoverSecret(points){
let s=0;
for(let i=0;i<points.length;i++){
let [xi,yi]=points[i], term=yi;
for(let j=0;j<points.length;j++){ if(j===i) continue;
const xj=points[j][0]; term *= (0 - xj)/(xi - xj); }
s += term;
}
return Math.round(s);
}
function newSecret(){
coeffs = [Math.floor(Math.random()*90)+10]; // secret 10..99
for(let i=1;i<K;i++) coeffs.push(Math.floor(Math.random()*9)-4); // small coeffs
shardVals = XS.map(x => feval(coeffs, x));
dead.clear();
document.getElementById('secret').textContent='?';
render(); setStatus(fmt('%N% of %N% shards alive — any %K% suffice.', { N: N, K: K }),'');
}
function render(){
const wrap=document.getElementById('shards'); wrap.innerHTML='';
XS.forEach((x,i)=>{
const d=document.createElement('div');
d.className='shard'+(dead.has(i)?' dead':'');
d.innerHTML=fmt('<div class="lbl">shard %i%%v% (x=%x%)</div><div class="val">%v2%</div>', { i: i, v: 1, x: x, v2: shardVals[i] });
d.addEventListener('click',()=>{ dead.has(i)?dead.delete(i):dead.add(i);
document.getElementById('secret').textContent='?'; render(); statusForAlive(); });
wrap.appendChild(d);
});
}
function alive(){ return XS.length - dead.size; }
function statusForAlive(){ const a=alive();
setStatus(fmt('%a% of %N% shards alive — %v%', { a: a, N: N, v: a>=K?'enough to rebuild.':'NOT enough (need '+K+').' }), a>=K?'':'bad'); }
function setStatus(t,cls){ const s=document.getElementById('status'); s.textContent=t; s.className='status '+cls; }
function rebuild(){
const pts=[]; XS.forEach((x,i)=>{ if(!dead.has(i)) pts.push([x, shardVals[i]]); });
if(pts.length < K){
document.getElementById('secret').textContent='✗ lost';
setStatus(fmt('Only %length% shards left — need %K%. Data is unrecoverable.', { length: pts.length, K: K }),'bad'); return;
}
const s=recoverSecret(pts.slice(0,K)); // any K survivors give the same answer
document.getElementById('secret').textContent=s;
const ok = s===coeffs[0];
setStatus(ok ? fmt('✓ Rebuilt the secret from %length% surviving shards!', { length: pts.length }) :
'unexpected mismatch', ok?'ok':'bad');
console.log('secret', coeffs[0], 'recovered', s, 'from', pts.length, 'shards');
}
document.getElementById('rebuild').addEventListener('click',rebuild);
document.getElementById('reset').addEventListener('click',newSecret);
newSecret();
<p class="hint">A secret value is encoded into <b>6 shards</b> using a degree-3 polynomial. <b>Any 4</b> shards can rebuild it. Click shards to destroy them, then press <b>Rebuild</b>. With 4 or more survivors it always works — with 3 or fewer it is mathematically impossible.</p>
<div class="secret">Secret value: <span id="secret">?</span></div>
<div id="shards" class="shards"></div>
<div class="status" id="status">6 of 6 shards alive — any 4 suffice.</div>
<div class="btns">
<button id="rebuild" type="button">Rebuild from survivors</button>
<button id="reset" type="button" class="ghost">New secret</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; }
.secret { font-size: 1rem; font-weight: 600; margin: .3rem 0 .6rem; }
.secret span { font-family: ui-monospace, monospace; color: #1d3557; }
.shards { display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px; margin: .5rem 0; }
.shard { padding: .6rem .3rem; border-radius: 10px; text-align: center; cursor: pointer;
user-select: none; border: 2px solid #cdd9e3; background: #e8eef3; transition: all .12s; }
.shard:hover { border-color: #1d3557; }
.shard .lbl { font-size: .72rem; color: #57708a; }
.shard .val { font: 700 16px ui-monospace, monospace; color: #1d3557; margin-top: 2px; }
.shard.dead { background: #f3d6d9; border-color: #e0a4ab; opacity: .55; }
.shard.dead .val, .shard.dead .lbl { color: #b03a46; text-decoration: line-through; }
.status { font-size: 1rem; font-weight: 600; margin: .6rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
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] ?? ''));
// Reed-Solomon-style erasure coding (over rationals for a clear visual):
// secret S is the value at x=0 of a degree k-1 = 3 polynomial f.
// The 6 shards are f(1)..f(6). Any k=4 of them uniquely fix f (interpolation),
// so f(0)=S is recovered. With <4 survivors, infinitely many curves fit -> impossible.
const N = 6, K = 4; // n shards, any k recover
const XS = [1,2,3,4,5,6]; // evaluation points for the shards
let coeffs = []; // [a0(=secret), a1, a2, a3], f(x)=a0+a1 x+a2 x^2+a3 x^3
let shardVals = []; // f(x) for x in XS
const dead = new Set(); // indices (0..5) of destroyed shards
function feval(c, x){ let r=0; for(let i=c.length-1;i>=0;i--) r=r*x+c[i]; return r; }
// Lagrange interpolation: recover f(0) from the surviving (x, y) points.
function recoverSecret(points){
let s=0;
for(let i=0;i<points.length;i++){
let [xi,yi]=points[i], term=yi;
for(let j=0;j<points.length;j++){ if(j===i) continue;
const xj=points[j][0]; term *= (0 - xj)/(xi - xj); }
s += term;
}
return Math.round(s);
}
function newSecret(){
coeffs = [Math.floor(Math.random()*90)+10]; // secret 10..99
for(let i=1;i<K;i++) coeffs.push(Math.floor(Math.random()*9)-4); // small coeffs
shardVals = XS.map(x => feval(coeffs, x));
dead.clear();
document.getElementById('secret').textContent='?';
render(); setStatus(fmt('%N% of %N% shards alive — any %K% suffice.', { N: N, K: K }),'');
}
function render(){
const wrap=document.getElementById('shards'); wrap.innerHTML='';
XS.forEach((x,i)=>{
const d=document.createElement('div');
d.className='shard'+(dead.has(i)?' dead':'');
d.innerHTML=fmt('<div class="lbl">shard %i%%v% (x=%x%)</div><div class="val">%v2%</div>', { i: i, v: 1, x: x, v2: shardVals[i] });
d.addEventListener('click',()=>{ dead.has(i)?dead.delete(i):dead.add(i);
document.getElementById('secret').textContent='?'; render(); statusForAlive(); });
wrap.appendChild(d);
});
}
function alive(){ return XS.length - dead.size; }
function statusForAlive(){ const a=alive();
setStatus(fmt('%a% of %N% shards alive — %v%', { a: a, N: N, v: a>=K?'enough to rebuild.':'NOT enough (need '+K+').' }), a>=K?'':'bad'); }
function setStatus(t,cls){ const s=document.getElementById('status'); s.textContent=t; s.className='status '+cls; }
function rebuild(){
const pts=[]; XS.forEach((x,i)=>{ if(!dead.has(i)) pts.push([x, shardVals[i]]); });
if(pts.length < K){
document.getElementById('secret').textContent='✗ lost';
setStatus(fmt('Only %length% shards left — need %K%. Data is unrecoverable.', { length: pts.length, K: K }),'bad'); return;
}
const s=recoverSecret(pts.slice(0,K)); // any K survivors give the same answer
document.getElementById('secret').textContent=s;
const ok = s===coeffs[0];
setStatus(ok ? fmt('✓ Rebuilt the secret from %length% surviving shards!', { length: pts.length }) :
'unexpected mismatch', ok?'ok':'bad');
console.log('secret', coeffs[0], 'recovered', s, 'from', pts.length, 'shards');
}
document.getElementById('rebuild').addEventListener('click',rebuild);
document.getElementById('reset').addEventListener('click',newSecret);
newSecret();
The trick is that the shards are points on a polynomial. Four points uniquely fix a degree-3 curve, so any four of the six points recover the same curve — and reading its value at x = 0 gives back the original data. Knock out three or more shards and only ≤3 points remain: infinitely many curves pass through them, so reconstruction becomes impossible. That sharp k-point threshold is exactly what makes Reed-Solomon codes so reliable.
Comments
Loading comments...