<div class="hint">
A <b>2-of-3 Shamir secret sharing</b> scheme over a small prime field. Any 2 shares reconstruct the secret; 1 share reveals nothing.
</div>
<div class="panel">
<div class="row">
<span class="label">Secret (0–96):</span>
<span id="secret-val" class="mono big">—</span>
</div>
<div class="row">
<span class="label">Polynomial: f(x) =</span>
<span id="poly-val" class="mono">—</span>
</div>
</div>
<div class="shares-grid" id="shares-grid">
<div class="share-card" id="card-1">
<div class="share-label">Share 1 <span class="share-coord" id="s1">—</span></div>
<label class="cb-row"><input type="checkbox" id="use1" disabled> Use in reconstruction</label>
</div>
<div class="share-card" id="card-2">
<div class="share-label">Share 2 <span class="share-coord" id="s2">—</span></div>
<label class="cb-row"><input type="checkbox" id="use2" disabled> Use in reconstruction</label>
</div>
<div class="share-card" id="card-3">
<div class="share-label">Share 3 <span class="share-coord" id="s3">—</span></div>
<label class="cb-row"><input type="checkbox" id="use3" disabled> Use in reconstruction</label>
</div>
</div>
<div class="btns">
<button id="gen" type="button">Generate new secret</button>
<button id="recon" type="button" disabled>Reconstruct</button>
</div>
<div class="result" id="result"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.panel { background: #eef2f7; border: 1px solid #cdd9e3; border-radius: 8px;
padding: .7rem 1rem; margin-bottom: .9rem; }
.row { display: flex; align-items: baseline; gap: .5rem; margin: .2rem 0; }
.label { font-size: .82rem; color: #555; white-space: nowrap; }
.mono { font-family: ui-monospace, monospace; }
.big { font-size: 1.5rem; font-weight: 700; color: #1d3557; }
.shares-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: .6rem; margin-bottom: .8rem; }
.share-card { border: 1.5px solid #cdd9e3; border-radius: 8px; padding: .6rem .8rem;
transition: border-color .2s, background .2s; }
.share-card.selected { border-color: #1d6fa5; background: #e6f1fa; }
.share-label { font-size: .82rem; font-weight: 600; color: #1d3557; margin-bottom: .4rem; }
.share-coord { font-family: ui-monospace, monospace; font-weight: 400; color: #444; }
.cb-row { display: flex; align-items: center; gap: .4rem; font-size: .8rem; cursor: pointer; }
input[type=checkbox] { accent-color: #1d6fa5; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .7rem; }
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:disabled { opacity: .45; cursor: default; }
.result { font-size: 1rem; font-weight: 600; min-height: 1.4em; padding: .3rem .2rem; }
.result.ok { color: #0a7d33; }
.result.bad { color: #c92f3c; }
.result.info { color: #1d3557; font-weight: 400; font-size: .9rem; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// 2-of-3 Shamir secret sharing over GF(p), p = 97 (prime).
// Secret s is hidden as f(0) = s in f(x) = s + a*x (degree k-1 = 1 polynomial).
// Shares: (1, f(1)), (2, f(2)), (3, f(3)) mod 97.
// Reconstruct from any 2 shares via Lagrange interpolation at x=0.
// Security: with only 1 share (one point on the line) every value of s in [0,96]
// is equally likely — no information is leaked.
const P = 97; // prime modulus — field GF(97)
function mod(a, m) { return ((a % m) + m) % m; }
// Modular inverse via Fermat's little theorem (p is prime)
function modInv(a, m) {
let result = 1n;
let base = BigInt(mod(a, m));
let exp = BigInt(m - 2);
let mod_ = BigInt(m);
while (exp > 0n) {
if (exp % 2n === 1n) result = result * base % mod_;
base = base * base % mod_;
exp = exp / 2n;
}
return Number(result);
}
// Evaluate polynomial f(x) = s + a*x mod P
function evalPoly(s, a, x) { return mod(s + a * x, P); }
// Lagrange interpolation at x=0 given points [(x1,y1),(x2,y2)]
function lagrange2(x1, y1, x2, y2) {
// L1(0) = (0-x2)/((x1-x2)) mod P
const num1 = mod(-x2, P);
const den1 = mod(x1 - x2, P);
const L1 = mod(num1 * modInv(den1, P), P);
// L2(0) = (0-x1)/((x2-x1)) mod P
const num2 = mod(-x1, P);
const den2 = mod(x2 - x1, P);
const L2 = mod(num2 * modInv(den2, P), P);
return mod(y1 * L1 + y2 * L2, P);
}
let shares = []; // [{x,y}, ...]
let secret = null;
let slope = null;
function randInt(max) { return Math.floor(Math.random() * max); }
function generate() {
secret = randInt(P);
slope = randInt(P); // random slope = random coefficient a1
shares = [1, 2, 3].map(x => ({ x, y: evalPoly(secret, slope, x) }));
document.getElementById('secret-val').textContent = secret;
document.getElementById('poly-val').textContent =
'f(x) = ' + secret + ' + ' + slope + '·x (mod ' + P + ')';
['s1','s2','s3'].forEach((id, i) => {
document.getElementById(id).textContent =
'f(' + shares[i].x + ') = ' + shares[i].y;
});
const cbs = [document.getElementById('use1'),
document.getElementById('use2'),
document.getElementById('use3')];
cbs.forEach(cb => { cb.checked = false; cb.disabled = false; });
updateCards();
document.getElementById('recon').disabled = false;
document.getElementById('result').textContent = '';
document.getElementById('result').className = 'result';
console.log('Secret:', secret, '| Slope:', slope,
'| Shares:', shares.map(s => '(' + s.x + ',' + s.y + ')').join(' '));
}
function updateCards() {
[1,2,3].forEach(i => {
const cb = document.getElementById('use' + i);
const card = document.getElementById('card-' + i);
card.classList.toggle('selected', cb.checked);
});
}
function reconstruct() {
if (shares.length === 0) return;
const selected = [1,2,3].filter(i => document.getElementById('use' + i).checked);
const res = document.getElementById('result');
if (selected.length < 1) {
res.textContent = 'Select at least one share.';
res.className = 'result info';
return;
}
if (selected.length === 1) {
const i = selected[0] - 1;
const s = shares[i];
res.textContent =
fmt('Share (%x%, %y%) alone: every secret 0–%v% is equally consistent — one point lies on infinitely many lines. No information revealed.', { x: s.x, y: s.y, v: P-1 });
res.className = 'result info';
console.log('Attempted reconstruction from 1 share — information-theoretically indeterminate.');
return;
}
// Use first two selected shares
const [i1, i2] = selected.slice(0, 2).map(i => i - 1);
const {x: x1, y: y1} = shares[i1];
const {x: x2, y: y2} = shares[i2];
const recovered = lagrange2(x1, y1, x2, y2);
const ok = recovered === secret;
res.textContent = ok
? fmt('✓ Reconstructed secret = %recovered% (matches!)', { recovered: recovered })
: fmt('✗ Got %recovered% but secret is %secret% — something went wrong.', { recovered: recovered, secret: secret });
res.className = 'result ' + (ok ? 'ok' : 'bad');
console.log('Reconstruction from shares', x1, x2, '→', recovered,
'| expected', secret, '| match:', ok);
}
document.getElementById('gen').addEventListener('click', generate);
document.getElementById('recon').addEventListener('click', reconstruct);
[1,2,3].forEach(i => {
document.getElementById('use' + i).addEventListener('change', updateCards);
});
// Auto-generate on load
generate();
<div class="hint">
A <b>2-of-3 Shamir secret sharing</b> scheme over a small prime field. Any 2 shares reconstruct the secret; 1 share reveals nothing.
</div>
<div class="panel">
<div class="row">
<span class="label">Secret (0–96):</span>
<span id="secret-val" class="mono big">—</span>
</div>
<div class="row">
<span class="label">Polynomial: f(x) =</span>
<span id="poly-val" class="mono">—</span>
</div>
</div>
<div class="shares-grid" id="shares-grid">
<div class="share-card" id="card-1">
<div class="share-label">Share 1 <span class="share-coord" id="s1">—</span></div>
<label class="cb-row"><input type="checkbox" id="use1" disabled> Use in reconstruction</label>
</div>
<div class="share-card" id="card-2">
<div class="share-label">Share 2 <span class="share-coord" id="s2">—</span></div>
<label class="cb-row"><input type="checkbox" id="use2" disabled> Use in reconstruction</label>
</div>
<div class="share-card" id="card-3">
<div class="share-label">Share 3 <span class="share-coord" id="s3">—</span></div>
<label class="cb-row"><input type="checkbox" id="use3" disabled> Use in reconstruction</label>
</div>
</div>
<div class="btns">
<button id="gen" type="button">Generate new secret</button>
<button id="recon" type="button" disabled>Reconstruct</button>
</div>
<div class="result" id="result"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.panel { background: #eef2f7; border: 1px solid #cdd9e3; border-radius: 8px;
padding: .7rem 1rem; margin-bottom: .9rem; }
.row { display: flex; align-items: baseline; gap: .5rem; margin: .2rem 0; }
.label { font-size: .82rem; color: #555; white-space: nowrap; }
.mono { font-family: ui-monospace, monospace; }
.big { font-size: 1.5rem; font-weight: 700; color: #1d3557; }
.shares-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: .6rem; margin-bottom: .8rem; }
.share-card { border: 1.5px solid #cdd9e3; border-radius: 8px; padding: .6rem .8rem;
transition: border-color .2s, background .2s; }
.share-card.selected { border-color: #1d6fa5; background: #e6f1fa; }
.share-label { font-size: .82rem; font-weight: 600; color: #1d3557; margin-bottom: .4rem; }
.share-coord { font-family: ui-monospace, monospace; font-weight: 400; color: #444; }
.cb-row { display: flex; align-items: center; gap: .4rem; font-size: .8rem; cursor: pointer; }
input[type=checkbox] { accent-color: #1d6fa5; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .7rem; }
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:disabled { opacity: .45; cursor: default; }
.result { font-size: 1rem; font-weight: 600; min-height: 1.4em; padding: .3rem .2rem; }
.result.ok { color: #0a7d33; }
.result.bad { color: #c92f3c; }
.result.info { color: #1d3557; font-weight: 400; font-size: .9rem; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// 2-of-3 Shamir secret sharing over GF(p), p = 97 (prime).
// Secret s is hidden as f(0) = s in f(x) = s + a*x (degree k-1 = 1 polynomial).
// Shares: (1, f(1)), (2, f(2)), (3, f(3)) mod 97.
// Reconstruct from any 2 shares via Lagrange interpolation at x=0.
// Security: with only 1 share (one point on the line) every value of s in [0,96]
// is equally likely — no information is leaked.
const P = 97; // prime modulus — field GF(97)
function mod(a, m) { return ((a % m) + m) % m; }
// Modular inverse via Fermat's little theorem (p is prime)
function modInv(a, m) {
let result = 1n;
let base = BigInt(mod(a, m));
let exp = BigInt(m - 2);
let mod_ = BigInt(m);
while (exp > 0n) {
if (exp % 2n === 1n) result = result * base % mod_;
base = base * base % mod_;
exp = exp / 2n;
}
return Number(result);
}
// Evaluate polynomial f(x) = s + a*x mod P
function evalPoly(s, a, x) { return mod(s + a * x, P); }
// Lagrange interpolation at x=0 given points [(x1,y1),(x2,y2)]
function lagrange2(x1, y1, x2, y2) {
// L1(0) = (0-x2)/((x1-x2)) mod P
const num1 = mod(-x2, P);
const den1 = mod(x1 - x2, P);
const L1 = mod(num1 * modInv(den1, P), P);
// L2(0) = (0-x1)/((x2-x1)) mod P
const num2 = mod(-x1, P);
const den2 = mod(x2 - x1, P);
const L2 = mod(num2 * modInv(den2, P), P);
return mod(y1 * L1 + y2 * L2, P);
}
let shares = []; // [{x,y}, ...]
let secret = null;
let slope = null;
function randInt(max) { return Math.floor(Math.random() * max); }
function generate() {
secret = randInt(P);
slope = randInt(P); // random slope = random coefficient a1
shares = [1, 2, 3].map(x => ({ x, y: evalPoly(secret, slope, x) }));
document.getElementById('secret-val').textContent = secret;
document.getElementById('poly-val').textContent =
'f(x) = ' + secret + ' + ' + slope + '·x (mod ' + P + ')';
['s1','s2','s3'].forEach((id, i) => {
document.getElementById(id).textContent =
'f(' + shares[i].x + ') = ' + shares[i].y;
});
const cbs = [document.getElementById('use1'),
document.getElementById('use2'),
document.getElementById('use3')];
cbs.forEach(cb => { cb.checked = false; cb.disabled = false; });
updateCards();
document.getElementById('recon').disabled = false;
document.getElementById('result').textContent = '';
document.getElementById('result').className = 'result';
console.log('Secret:', secret, '| Slope:', slope,
'| Shares:', shares.map(s => '(' + s.x + ',' + s.y + ')').join(' '));
}
function updateCards() {
[1,2,3].forEach(i => {
const cb = document.getElementById('use' + i);
const card = document.getElementById('card-' + i);
card.classList.toggle('selected', cb.checked);
});
}
function reconstruct() {
if (shares.length === 0) return;
const selected = [1,2,3].filter(i => document.getElementById('use' + i).checked);
const res = document.getElementById('result');
if (selected.length < 1) {
res.textContent = 'Select at least one share.';
res.className = 'result info';
return;
}
if (selected.length === 1) {
const i = selected[0] - 1;
const s = shares[i];
res.textContent =
fmt('Share (%x%, %y%) alone: every secret 0–%v% is equally consistent — one point lies on infinitely many lines. No information revealed.', { x: s.x, y: s.y, v: P-1 });
res.className = 'result info';
console.log('Attempted reconstruction from 1 share — information-theoretically indeterminate.');
return;
}
// Use first two selected shares
const [i1, i2] = selected.slice(0, 2).map(i => i - 1);
const {x: x1, y: y1} = shares[i1];
const {x: x2, y: y2} = shares[i2];
const recovered = lagrange2(x1, y1, x2, y2);
const ok = recovered === secret;
res.textContent = ok
? fmt('✓ Reconstructed secret = %recovered% (matches!)', { recovered: recovered })
: fmt('✗ Got %recovered% but secret is %secret% — something went wrong.', { recovered: recovered, secret: secret });
res.className = 'result ' + (ok ? 'ok' : 'bad');
console.log('Reconstruction from shares', x1, x2, '→', recovered,
'| expected', secret, '| match:', ok);
}
document.getElementById('gen').addEventListener('click', generate);
document.getElementById('recon').addEventListener('click', reconstruct);
[1,2,3].forEach(i => {
document.getElementById('use' + i).addEventListener('change', updateCards);
});
// Auto-generate on load
generate();
Comments
Loading comments...