<p class="hint">
We work in a small group (prime p = 9007199254740997, g = 2, h = 3) so numbers stay readable. Enter a secret value (0–9999), then <b>Commit</b> — a random blinding factor r is chosen and C = g<sup>m</sup> · h<sup>r</sup> mod p is computed. Then <b>Reveal</b> to verify that the same (m, r) reopens the commitment honestly.
</p>
<div class="params">
<div class="row">
<span class="label">Prime p</span>
<span class="val" id="pVal"></span>
</div>
<div class="row">
<span class="label">Generator g</span>
<span class="val" id="gVal"></span>
</div>
<div class="row">
<span class="label">Generator h</span>
<span class="val" id="hVal"></span>
</div>
</div>
<div class="input-row">
<label for="secret">Secret value m (0–9999):</label>
<input id="secret" type="number" min="0" max="9999" value="42" />
</div>
<div class="btns">
<button id="commitBtn" type="button">Commit</button>
<button id="revealBtn" type="button" disabled>Reveal & Verify</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
<div id="output"></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; }
.params { background: #f0f4f8; border: 1px solid #d0dae4; border-radius: 8px; padding: .6rem .9rem; margin-bottom: .9rem; font-size: .85rem; }
.params .row { display: flex; gap: .5rem; align-items: baseline; margin-bottom: .2rem; }
.params .row:last-child { margin-bottom: 0; }
.label { color: #5a7088; min-width: 90px; font-weight: 600; }
.val { font-family: ui-monospace, monospace; color: #1d3557; word-break: break-all; }
.input-row { display: flex; align-items: center; gap: .6rem; margin-bottom: .7rem; flex-wrap: wrap; }
label { font-size: .9rem; font-weight: 600; color: #333; }
input { font: 400 .95rem system-ui; padding: .3rem .5rem; border: 1px solid #a0b0bf; border-radius: 6px; width: 120px; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .8rem; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557; background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .45; cursor: default; }
#output { font-size: .9rem; line-height: 1.65; }
.block { background: #f0f4f8; border: 1px solid #d0dae4; border-radius: 8px; padding: .65rem .9rem; margin-bottom: .6rem; }
.block h3 { margin: 0 0 .4rem; font-size: .9rem; color: #1d3557; letter-spacing: .03em; }
.mono { font-family: ui-monospace, monospace; font-size: .85rem; word-break: break-all; }
.ok { color: #0a7d33; font-weight: 700; }
.bad { color: #c92f3c; font-weight: 700; }
.step { margin: .2rem 0; }
.kv { display: flex; gap: .5rem; }
.kv .k { color: #5a7088; min-width: 120px; font-weight: 600; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Pedersen commitment in a small prime group.
// C = g^m * h^r (mod p)
// Perfectly hiding: for any C and m* there is exactly one r* with C = g^m* * h^r*.
// Computationally binding: opening (m,r) vs (m’,r’) requires dlog.
//
// We use a safe prime p = 2q+1 (q also prime) so the subgroup of order q is simple.
// For demo clarity we use small numbers and BigInt arithmetic.
//
// p = 23 (a safe prime: (23-1)/2 = 11, also prime)
// g = 2 (generator of Z*_23)
// h = 3 (another generator; in real life nobody knows log_g(h))
// Commitments live in Z*_23 (group order = 22, but we work mod 22 for exponents).
const P = 23n; // prime modulus
const G = 2n; // generator g
const H = 3n; // generator h (secret: log_2(3) mod 23)
const ORDER = 22n; // group order = P - 1
document.getElementById('pVal').textContent = P.toString();
document.getElementById('gVal').textContent = G.toString();
document.getElementById('hVal').textContent = H.toString();
// Modular exponentiation: base^exp mod mod
function modpow(base, exp, mod) {
base = ((base % mod) + mod) % mod;
let result = 1n;
while (exp > 0n) {
if (exp & 1n) result = result * base % mod;
base = base * base % mod;
exp >>= 1n;
}
return result;
}
// Random BigInt in [0, max)
function randBig(max) {
// Use crypto.getRandomValues for uniform random
const bytes = Math.ceil(max.toString(2).length / 8) + 2;
const arr = new Uint8Array(bytes);
crypto.getRandomValues(arr);
let n = 0n;
for (const b of arr) n = (n << 8n) | BigInt(b);
return n % max;
}
let state = null; // { m, r, C }
function commit() {
const raw = parseInt(document.getElementById('secret').value, 10);
if (isNaN(raw) || raw < 0 || raw > 9999) {
showOutput('<span class="bad">Please enter a value between 0 and 9999.</span>'); return;
}
const m = BigInt(raw) % ORDER; // reduce mod group order
const r = randBig(ORDER); // random blinding factor
// C = g^m * h^r mod p
const gm = modpow(G, m, P);
const hr = modpow(H, r, P);
const C = gm * hr % P;
state = { m, r, C };
document.getElementById('revealBtn').disabled = false;
const html = "\n " + fmt(`<div class="block">
<h3>COMMIT PHASE</h3>
<div class="step kv"><span class="k">Secret m</span><span class="mono">%m% (your input reduced mod %ORDER%)</span></div>
<div class="step kv"><span class="k">Blinding r</span><span class="mono">%r% (random)</span></div>
<div class="step kv"><span class="k">g^m mod p</span><span class="mono">%gm%</span></div>
<div class="step kv"><span class="k">h^r mod p</span><span class="mono">%hr%</span></div>
<div class="step kv"><span class="k">Commitment C</span><span class="mono">%C%</span></div>
</div>
<p style="font-size:.88rem;color:#444;margin:.2rem 0 0">
C = %G%<sup>%m%</sup> · %H%<sup>%r%</sup> mod %P% = <b>%C%</b>.
The value <b>%C%</b> is now public. It hides m completely — click <b>Reveal & Verify</b>.
</p>`, { m: m, ORDER: ORDER, r: r, gm: gm, hr: hr, C: C, G: G, H: H, P: P });
showOutput(html);
console.log(fmt(`Committed: m=%m% r=%r% C=%C%`, { m: m, r: r, C: C }));
}
function reveal() {
if (!state) return;
const { m, r, C } = state;
// Recompute to verify
const gm = modpow(G, m, P);
const hr = modpow(H, r, P);
const Ccheck = gm * hr % P;
const ok = Ccheck === C;
const html = "\n " + fmt(`<div class="block">
<h3>REVEAL PHASE</h3>
<div class="step kv"><span class="k">Revealed m</span><span class="mono">%m%</span></div>
<div class="step kv"><span class="k">Revealed r</span><span class="mono">%r%</span></div>
<div class="step kv"><span class="k">Recomputed C</span><span class="mono">%G%<sup>%m%</sup> · %H%<sup>%r%</sup> mod %P% = %Ccheck%</span></div>
<div class="step kv"><span class="k">Committed C</span><span class="mono">%C%</span></div>
<div class="step" style="margin-top:.5rem;font-size:1rem">
%v%
</div>
</div>
<p style="font-size:.88rem;color:#444;margin:.2rem 0 0">
To cheat and open as m'≠m the committer would need r' with
%G%<sup>m'</sup>·%H%<sup>r'</sup> ≡ %C% (mod %P%),
i.e. compute log<sub>${G}</sub>(${H}) — the discrete log problem.
</p>`, { m: m, r: r, G: G, H: H, P: P, Ccheck: Ccheck, C: C, v: ok
? '<span class="ok">✓ Commitment verified! The revealed value matches.</span>'
: '<span class="bad">✗ Mismatch! The commitment does not open to this value.</span>' });
showOutput(document.getElementById('output').innerHTML + html);
console.log(fmt(`Verified: ok=%ok% Ccheck=%Ccheck% C=%C%`, { ok: ok, Ccheck: Ccheck, C: C }));
}
function reset() {
state = null;
document.getElementById('revealBtn').disabled = true;
document.getElementById('secret').value = '42';
showOutput('');
}
function showOutput(html) {
document.getElementById('output').innerHTML = html;
}
document.getElementById('commitBtn').addEventListener('click', commit);
document.getElementById('revealBtn').addEventListener('click', reveal);
document.getElementById('resetBtn').addEventListener('click', reset);
<p class="hint">
We work in a small group (prime p = 9007199254740997, g = 2, h = 3) so numbers stay readable. Enter a secret value (0–9999), then <b>Commit</b> — a random blinding factor r is chosen and C = g<sup>m</sup> · h<sup>r</sup> mod p is computed. Then <b>Reveal</b> to verify that the same (m, r) reopens the commitment honestly.
</p>
<div class="params">
<div class="row">
<span class="label">Prime p</span>
<span class="val" id="pVal"></span>
</div>
<div class="row">
<span class="label">Generator g</span>
<span class="val" id="gVal"></span>
</div>
<div class="row">
<span class="label">Generator h</span>
<span class="val" id="hVal"></span>
</div>
</div>
<div class="input-row">
<label for="secret">Secret value m (0–9999):</label>
<input id="secret" type="number" min="0" max="9999" value="42" />
</div>
<div class="btns">
<button id="commitBtn" type="button">Commit</button>
<button id="revealBtn" type="button" disabled>Reveal & Verify</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
<div id="output"></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; }
.params { background: #f0f4f8; border: 1px solid #d0dae4; border-radius: 8px; padding: .6rem .9rem; margin-bottom: .9rem; font-size: .85rem; }
.params .row { display: flex; gap: .5rem; align-items: baseline; margin-bottom: .2rem; }
.params .row:last-child { margin-bottom: 0; }
.label { color: #5a7088; min-width: 90px; font-weight: 600; }
.val { font-family: ui-monospace, monospace; color: #1d3557; word-break: break-all; }
.input-row { display: flex; align-items: center; gap: .6rem; margin-bottom: .7rem; flex-wrap: wrap; }
label { font-size: .9rem; font-weight: 600; color: #333; }
input { font: 400 .95rem system-ui; padding: .3rem .5rem; border: 1px solid #a0b0bf; border-radius: 6px; width: 120px; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .8rem; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557; background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .45; cursor: default; }
#output { font-size: .9rem; line-height: 1.65; }
.block { background: #f0f4f8; border: 1px solid #d0dae4; border-radius: 8px; padding: .65rem .9rem; margin-bottom: .6rem; }
.block h3 { margin: 0 0 .4rem; font-size: .9rem; color: #1d3557; letter-spacing: .03em; }
.mono { font-family: ui-monospace, monospace; font-size: .85rem; word-break: break-all; }
.ok { color: #0a7d33; font-weight: 700; }
.bad { color: #c92f3c; font-weight: 700; }
.step { margin: .2rem 0; }
.kv { display: flex; gap: .5rem; }
.kv .k { color: #5a7088; min-width: 120px; font-weight: 600; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Pedersen commitment in a small prime group.
// C = g^m * h^r (mod p)
// Perfectly hiding: for any C and m* there is exactly one r* with C = g^m* * h^r*.
// Computationally binding: opening (m,r) vs (m’,r’) requires dlog.
//
// We use a safe prime p = 2q+1 (q also prime) so the subgroup of order q is simple.
// For demo clarity we use small numbers and BigInt arithmetic.
//
// p = 23 (a safe prime: (23-1)/2 = 11, also prime)
// g = 2 (generator of Z*_23)
// h = 3 (another generator; in real life nobody knows log_g(h))
// Commitments live in Z*_23 (group order = 22, but we work mod 22 for exponents).
const P = 23n; // prime modulus
const G = 2n; // generator g
const H = 3n; // generator h (secret: log_2(3) mod 23)
const ORDER = 22n; // group order = P - 1
document.getElementById('pVal').textContent = P.toString();
document.getElementById('gVal').textContent = G.toString();
document.getElementById('hVal').textContent = H.toString();
// Modular exponentiation: base^exp mod mod
function modpow(base, exp, mod) {
base = ((base % mod) + mod) % mod;
let result = 1n;
while (exp > 0n) {
if (exp & 1n) result = result * base % mod;
base = base * base % mod;
exp >>= 1n;
}
return result;
}
// Random BigInt in [0, max)
function randBig(max) {
// Use crypto.getRandomValues for uniform random
const bytes = Math.ceil(max.toString(2).length / 8) + 2;
const arr = new Uint8Array(bytes);
crypto.getRandomValues(arr);
let n = 0n;
for (const b of arr) n = (n << 8n) | BigInt(b);
return n % max;
}
let state = null; // { m, r, C }
function commit() {
const raw = parseInt(document.getElementById('secret').value, 10);
if (isNaN(raw) || raw < 0 || raw > 9999) {
showOutput('<span class="bad">Please enter a value between 0 and 9999.</span>'); return;
}
const m = BigInt(raw) % ORDER; // reduce mod group order
const r = randBig(ORDER); // random blinding factor
// C = g^m * h^r mod p
const gm = modpow(G, m, P);
const hr = modpow(H, r, P);
const C = gm * hr % P;
state = { m, r, C };
document.getElementById('revealBtn').disabled = false;
const html = "\n " + fmt(`<div class="block">
<h3>COMMIT PHASE</h3>
<div class="step kv"><span class="k">Secret m</span><span class="mono">%m% (your input reduced mod %ORDER%)</span></div>
<div class="step kv"><span class="k">Blinding r</span><span class="mono">%r% (random)</span></div>
<div class="step kv"><span class="k">g^m mod p</span><span class="mono">%gm%</span></div>
<div class="step kv"><span class="k">h^r mod p</span><span class="mono">%hr%</span></div>
<div class="step kv"><span class="k">Commitment C</span><span class="mono">%C%</span></div>
</div>
<p style="font-size:.88rem;color:#444;margin:.2rem 0 0">
C = %G%<sup>%m%</sup> · %H%<sup>%r%</sup> mod %P% = <b>%C%</b>.
The value <b>%C%</b> is now public. It hides m completely — click <b>Reveal & Verify</b>.
</p>`, { m: m, ORDER: ORDER, r: r, gm: gm, hr: hr, C: C, G: G, H: H, P: P });
showOutput(html);
console.log(fmt(`Committed: m=%m% r=%r% C=%C%`, { m: m, r: r, C: C }));
}
function reveal() {
if (!state) return;
const { m, r, C } = state;
// Recompute to verify
const gm = modpow(G, m, P);
const hr = modpow(H, r, P);
const Ccheck = gm * hr % P;
const ok = Ccheck === C;
const html = "\n " + fmt(`<div class="block">
<h3>REVEAL PHASE</h3>
<div class="step kv"><span class="k">Revealed m</span><span class="mono">%m%</span></div>
<div class="step kv"><span class="k">Revealed r</span><span class="mono">%r%</span></div>
<div class="step kv"><span class="k">Recomputed C</span><span class="mono">%G%<sup>%m%</sup> · %H%<sup>%r%</sup> mod %P% = %Ccheck%</span></div>
<div class="step kv"><span class="k">Committed C</span><span class="mono">%C%</span></div>
<div class="step" style="margin-top:.5rem;font-size:1rem">
%v%
</div>
</div>
<p style="font-size:.88rem;color:#444;margin:.2rem 0 0">
To cheat and open as m'≠m the committer would need r' with
%G%<sup>m'</sup>·%H%<sup>r'</sup> ≡ %C% (mod %P%),
i.e. compute log<sub>${G}</sub>(${H}) — the discrete log problem.
</p>`, { m: m, r: r, G: G, H: H, P: P, Ccheck: Ccheck, C: C, v: ok
? '<span class="ok">✓ Commitment verified! The revealed value matches.</span>'
: '<span class="bad">✗ Mismatch! The commitment does not open to this value.</span>' });
showOutput(document.getElementById('output').innerHTML + html);
console.log(fmt(`Verified: ok=%ok% Ccheck=%Ccheck% C=%C%`, { ok: ok, Ccheck: Ccheck, C: C }));
}
function reset() {
state = null;
document.getElementById('revealBtn').disabled = true;
document.getElementById('secret').value = '42';
showOutput('');
}
function showOutput(html) {
document.getElementById('output').innerHTML = html;
}
document.getElementById('commitBtn').addEventListener('click', commit);
document.getElementById('revealBtn').addEventListener('click', reveal);
document.getElementById('resetBtn').addEventListener('click', reset);
Notice the asymmetry. The commitment is a single number, yet it completely hides the secret. The reveal provides both the secret and the blinding factor, and verification is instant: recompute the commitment and compare. Changing the secret after committing would require computing a discrete logarithm — a problem believed to be computationally infeasible.
Comments
Loading comments...