Below, three parties each have a private salary. Type each value, then press Split into shares. Every salary is broken into three random shares that sum back to it. Each party keeps one share from everybody and adds up the shares it holds.
<p class="hint">Three parties each have a private salary. Each salary is split into three <b>random shares</b> that add back to it. Every party keeps one share from everyone and adds them up — no single share reveals anything, yet the totals sum to the true group total.</p>
<div class="inputs">
<label>Party A salary <input id="sA" type="number" value="50000"></label>
<label>Party B salary <input id="sB" type="number" value="62000"></label>
<label>Party C salary <input id="sC" type="number" value="44000"></label>
</div>
<div class="btns">
<button id="split" type="button">Split into shares</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div id="out"></div>
<div class="status" id="status">Enter salaries, then split.</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; }
.inputs { display: flex; flex-direction: column; gap: .4rem; margin-bottom: .6rem; }
label { font-size: .85rem; color: #1d3557; display: flex; justify-content: space-between;
align-items: center; gap: .6rem; font-weight: 600; }
input { font: 600 14px ui-monospace, monospace; padding: .35rem .5rem; width: 130px;
border: 1px solid #cdd9e3; border-radius: 6px; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .6rem; }
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; }
table { border-collapse: collapse; width: 100%; font: 13px ui-monospace, monospace; margin: .3rem 0; }
th, td { border: 1px solid #cdd9e3; padding: .35rem .4rem; text-align: center; }
th { background: #e8eef3; color: #1d3557; }
td.secret { background: #fff4f4; color: #c92f3c; font-weight: 700; }
td.share { color: #555; }
tr.totals td { background: #eef7f0; color: #0a7d33; font-weight: 700; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Additive secret sharing over a large modulus. Each secret s is split into 3
// shares: two uniform random values r1, r2 in [0,M), and r3 = (s - r1 - r2) mod M.
// Any single share is uniformly random and leaks nothing about s. The three
// shares sum (mod M) back to s. Each party j sums the shares it holds across all
// secrets; the parties' running totals sum (mod M) to the grand total — never
// exposing any individual salary. This is the core of secure multiparty computation.
const M = 1000003; // a prime modulus, comfortably above the inputs
const NP = 3;
const names = ['A', 'B', 'C'];
const rnd = () => Math.floor(Math.random() * M);
const mod = (x) => ((x % M) + M) % M;
function shares(secret) {
const out = [];
let acc = 0;
for (let i = 0; i < NP - 1; i++) { const r = rnd(); out.push(r); acc = mod(acc + r); }
out.push(mod(secret - acc)); // last share makes the sum equal the secret
return out; // out[j] = share given to party j
}
function run() {
const secrets = [ +document.getElementById('sA').value || 0,
+document.getElementById('sB').value || 0,
+document.getElementById('sC').value || 0 ];
// shareTable[i][j] = share of party i's secret held by party j
const shareTable = secrets.map(shares);
// each party j's running total = sum over secrets i of shareTable[i][j], mod M
const partyTotals = names.map((_, j) => mod(shareTable.reduce((a, row) => a + row[j], 0)));
const grand = mod(partyTotals.reduce((a, t) => a + t, 0));
const trueSum = mod(secrets.reduce((a, s) => a + s, 0));
let h = '<table><tr><th>secret of \ held by</th>';
names.forEach(n => h += fmt('<th>party %n%</th>', { n: n }));
h += '<th class="secret">true value</th></tr>';
shareTable.forEach((row, i) => {
h += fmt('<tr><td>party %v%</td>', { v: names[i] });
row.forEach(v => h += '<td class="share">' + v + '</td>');
h += fmt('<td class="secret">%v% (private)</td></tr>', { v: secrets[i] });
});
h += '<tr class="totals"><td>each party adds up</td>';
partyTotals.forEach(t => h += '<td>' + t + '</td>');
h += '<td>—</td></tr></table>';
document.getElementById('out').innerHTML = h;
const avg = (trueSum / NP).toFixed(1);
const ok = grand === trueSum;
const s = document.getElementById('status');
s.className = 'status' + (ok ? ' ok' : '');
s.textContent = ok
? fmt('✓ Sum of party totals (mod M) = %grand% = true total %trueSum%. Average = %avg%. No salary was revealed.', { grand: grand, trueSum: trueSum, avg: avg })
: '✗ Mismatch — reconstruction failed.';
console.log('party totals', partyTotals, 'reconstructed', grand, 'true', trueSum);
}
document.getElementById('split').addEventListener('click', run);
document.getElementById('reset').addEventListener('click', () => {
document.getElementById('sA').value = 50000;
document.getElementById('sB').value = 62000;
document.getElementById('sC').value = 44000;
document.getElementById('out').innerHTML = '';
const s = document.getElementById('status'); s.className = 'status'; s.textContent = 'Enter salaries, then split.';
});
run();
<p class="hint">Three parties each have a private salary. Each salary is split into three <b>random shares</b> that add back to it. Every party keeps one share from everyone and adds them up — no single share reveals anything, yet the totals sum to the true group total.</p>
<div class="inputs">
<label>Party A salary <input id="sA" type="number" value="50000"></label>
<label>Party B salary <input id="sB" type="number" value="62000"></label>
<label>Party C salary <input id="sC" type="number" value="44000"></label>
</div>
<div class="btns">
<button id="split" type="button">Split into shares</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div id="out"></div>
<div class="status" id="status">Enter salaries, then split.</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; }
.inputs { display: flex; flex-direction: column; gap: .4rem; margin-bottom: .6rem; }
label { font-size: .85rem; color: #1d3557; display: flex; justify-content: space-between;
align-items: center; gap: .6rem; font-weight: 600; }
input { font: 600 14px ui-monospace, monospace; padding: .35rem .5rem; width: 130px;
border: 1px solid #cdd9e3; border-radius: 6px; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .6rem; }
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; }
table { border-collapse: collapse; width: 100%; font: 13px ui-monospace, monospace; margin: .3rem 0; }
th, td { border: 1px solid #cdd9e3; padding: .35rem .4rem; text-align: center; }
th { background: #e8eef3; color: #1d3557; }
td.secret { background: #fff4f4; color: #c92f3c; font-weight: 700; }
td.share { color: #555; }
tr.totals td { background: #eef7f0; color: #0a7d33; font-weight: 700; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Additive secret sharing over a large modulus. Each secret s is split into 3
// shares: two uniform random values r1, r2 in [0,M), and r3 = (s - r1 - r2) mod M.
// Any single share is uniformly random and leaks nothing about s. The three
// shares sum (mod M) back to s. Each party j sums the shares it holds across all
// secrets; the parties' running totals sum (mod M) to the grand total — never
// exposing any individual salary. This is the core of secure multiparty computation.
const M = 1000003; // a prime modulus, comfortably above the inputs
const NP = 3;
const names = ['A', 'B', 'C'];
const rnd = () => Math.floor(Math.random() * M);
const mod = (x) => ((x % M) + M) % M;
function shares(secret) {
const out = [];
let acc = 0;
for (let i = 0; i < NP - 1; i++) { const r = rnd(); out.push(r); acc = mod(acc + r); }
out.push(mod(secret - acc)); // last share makes the sum equal the secret
return out; // out[j] = share given to party j
}
function run() {
const secrets = [ +document.getElementById('sA').value || 0,
+document.getElementById('sB').value || 0,
+document.getElementById('sC').value || 0 ];
// shareTable[i][j] = share of party i's secret held by party j
const shareTable = secrets.map(shares);
// each party j's running total = sum over secrets i of shareTable[i][j], mod M
const partyTotals = names.map((_, j) => mod(shareTable.reduce((a, row) => a + row[j], 0)));
const grand = mod(partyTotals.reduce((a, t) => a + t, 0));
const trueSum = mod(secrets.reduce((a, s) => a + s, 0));
let h = '<table><tr><th>secret of \ held by</th>';
names.forEach(n => h += fmt('<th>party %n%</th>', { n: n }));
h += '<th class="secret">true value</th></tr>';
shareTable.forEach((row, i) => {
h += fmt('<tr><td>party %v%</td>', { v: names[i] });
row.forEach(v => h += '<td class="share">' + v + '</td>');
h += fmt('<td class="secret">%v% (private)</td></tr>', { v: secrets[i] });
});
h += '<tr class="totals"><td>each party adds up</td>';
partyTotals.forEach(t => h += '<td>' + t + '</td>');
h += '<td>—</td></tr></table>';
document.getElementById('out').innerHTML = h;
const avg = (trueSum / NP).toFixed(1);
const ok = grand === trueSum;
const s = document.getElementById('status');
s.className = 'status' + (ok ? ' ok' : '');
s.textContent = ok
? fmt('✓ Sum of party totals (mod M) = %grand% = true total %trueSum%. Average = %avg%. No salary was revealed.', { grand: grand, trueSum: trueSum, avg: avg })
: '✗ Mismatch — reconstruction failed.';
console.log('party totals', partyTotals, 'reconstructed', grand, 'true', trueSum);
}
document.getElementById('split').addEventListener('click', run);
document.getElementById('reset').addEventListener('click', () => {
document.getElementById('sA').value = 50000;
document.getElementById('sB').value = 62000;
document.getElementById('sC').value = 44000;
document.getElementById('out').innerHTML = '';
const s = document.getElementById('status'); s.className = 'status'; s.textContent = 'Enter salaries, then split.';
});
run();
Look at the shares: each one is a meaningless random number — knowing a single share tells you nothing about the salary it came from. Yet when the three running totals are added, the random parts cancel exactly and the true sum (and average) appears. No party ever saw another's salary, and no trusted middleman was needed.
Comments
Loading comments...