<!-- Sigma protocol (Schnorr) interactive demo -->
<div class="sigma-app">
<div class="params-row">
<span class="param-label">p = <strong id="val-p"></strong></span>
<span class="param-label">g = <strong id="val-g"></strong></span>
<span class="param-label">q = <strong id="val-q"></strong></span>
<span class="param-label">y = g<sup>x</sup> = <strong id="val-y"></strong></span>
<span class="param-label secret-label">x = <strong id="val-x" class="secret-val"></strong> <em class="secret-tag">(secret — only prover knows)</em></span>
</div>
<div class="steps-grid">
<!-- Step 1: Prover commits with a random blinding factor -->
<div class="step-card" id="card-commit">
<div class="step-num">1</div>
<div class="step-body">
<div class="step-title">Commit</div>
<div class="step-desc">Prover picks random r, sends A = g^r mod p</div>
<div class="step-math" id="commit-math"></div>
</div>
</div>
<!-- Step 2: Verifier issues a random challenge -->
<div class="step-card dimmed" id="card-challenge">
<div class="step-num">2</div>
<div class="step-body">
<div class="step-title">Challenge</div>
<div class="step-desc">Verifier picks random e and sends it</div>
<div class="step-math" id="challenge-math"></div>
</div>
</div>
<!-- Step 3: Prover responds folding in challenge and secret -->
<div class="step-card dimmed" id="card-respond">
<div class="step-num">3</div>
<div class="step-body">
<div class="step-title">Respond</div>
<div class="step-desc">Prover sends z = (r + e·x) mod q</div>
<div class="step-math" id="respond-math"></div>
</div>
</div>
<!-- Verification: one modular equation, no secret needed -->
<div class="step-card dimmed" id="card-verify">
<div class="step-num">✓</div>
<div class="step-body">
<div class="step-title">Verify</div>
<div class="step-desc">Check g^z ≡ A · y^e (mod p)</div>
<div class="step-math" id="verify-math"></div>
</div>
</div>
</div>
<div class="status-bar" id="status-bar"></div>
<div class="btns">
<button id="btn-next" type="button">Next Step</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div class="cheat-panel" id="cheat-panel" style="display:none;">
<div class="cheat-title">Why a cheater fails</div>
<div class="cheat-body" id="cheat-body"></div>
</div>
</div>
/* Sigma protocol demo styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.sigma-app { padding: .5rem .2rem; }
.params-row { display: flex; flex-wrap: wrap; gap: .5rem .9rem; font-size: .82rem;
background: #eef2f6; border-radius: 8px; padding: .5rem .8rem; margin-bottom: .8rem; }
.param-label { color: #444; }
.secret-label { color: #c0392b; }
.secret-val { color: #c0392b; }
.secret-tag { font-style: italic; font-size: .75rem; color: #888; }
.steps-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .5rem; margin-bottom: .6rem; }
.step-card { border: 1.5px solid #cdd9e3; border-radius: 10px; padding: .55rem .75rem;
display: flex; gap: .5rem; background: #f5f8fb; transition: opacity .25s, border-color .25s; }
.step-card.dimmed { opacity: .38; }
.step-card.active { border-color: #1d3557; background: #eaf0f8; opacity: 1; }
.step-card.done { border-color: #0a7d33; background: #edfaf2; opacity: 1; }
.step-card.fail { border-color: #c0392b; background: #fdecea; opacity: 1; }
.step-num { font-weight: 700; font-size: 1.05rem; color: #1d3557; min-width: 1.4rem; }
.step-title { font-weight: 700; font-size: .88rem; color: #1d3557; margin-bottom: .15rem; }
.step-desc { font-size: .78rem; color: #555; line-height: 1.35; }
.step-math { font-family: ui-monospace, monospace; font-size: .82rem; color: #222;
margin-top: .3rem; line-height: 1.5; min-height: 1.2em; }
.status-bar { font-size: .95rem; font-weight: 600; min-height: 1.4em; margin: .4rem 0 .5rem; }
.status-bar.ok { color: #0a7d33; }
.status-bar.bad { color: #c0392b; }
.status-bar.info { color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .4rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.cheat-panel { margin-top: .8rem; border: 1.5px solid #e2a800; border-radius: 10px;
padding: .55rem .75rem; background: #fffbea; }
.cheat-title { font-weight: 700; color: #b87800; font-size: .85rem; margin-bottom: .3rem; }
.cheat-body { font-size: .82rem; color: #555; line-height: 1.5; }
@media (max-width: 420px) {
.steps-grid { grid-template-columns: 1fr; }
.params-row { font-size: .78rem; }
}
// Schnorr sigma protocol demo over a small safe-prime group.
// Prover knows x such that g^x = y (mod p).
// Protocol: (1) commit A=g^r, (2) challenge e, (3) respond z=r+ex, (4) verify g^z == A*y^e.
// Group parameters: p prime, q = (p-1)/2 prime (safe prime), g generator of order q.
// p is prime, q = (p-1)/2 is prime (safe prime), g is a generator of order q.
const p = 23, g = 2, q = 11;
// Protocol state: secret x, public y, blinding r, commitment A, challenge e, response z.
let x, y, r, A, e, z;
function modpow(base, exp, mod) {
// Fast modular exponentiation using BigInt.
let result = 1n;
base = base % mod;
while (exp > 0n) {
if (exp % 2n === 1n) result = result * base % mod;
exp = exp / 2n;
base = base * base % mod;
}
return result;
}
function modinv(a, m) {
// Modular inverse via Fermat's little theorem (p prime).
return modpow(a, m - 2n, m);
}
function randInt(min, max) {
// Uniform random integer in [min, max].
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function setup() {
// Initialize a fresh run: pick new secret x, compute public y.
x = BigInt(randInt(1, Number(q) - 1));
y = modpow(BigInt(g), x, BigInt(p));
document.getElementById('val-p').textContent = p;
document.getElementById('val-g').textContent = g;
document.getElementById('val-q').textContent = q;
document.getElementById('val-y').textContent = y;
document.getElementById('val-x').textContent = x;
r = null; A = null; e = null; z = null;
phase = 0;
document.getElementById('cheat-panel').style.display = 'none';
renderStep();
setStatus('', '');
}
let phase = 0;
function renderStep() {
// Update the UI to reflect the current phase.
const cards = ['card-commit', 'card-challenge', 'card-respond', 'card-verify'];
cards.forEach((id, i) => {
const el = document.getElementById(id);
el.classList.remove('active', 'done', 'fail', 'dimmed');
if (i < phase) el.classList.add('done');
else if (i === phase) el.classList.add('active');
else el.classList.add('dimmed');
});
if (phase >= 1 && A !== null) {
document.getElementById('commit-math').textContent = `r = ${r} → A = ${g}^${r} mod ${p} = ${A}`;
}
if (phase >= 2 && e !== null) {
document.getElementById('challenge-math').textContent = `e = ${e}`;
}
if (phase >= 3 && z !== null) {
document.getElementById('respond-math').textContent = `z = r + e·x = ${r} + ${e}·${x} = ${z} (mod ${q})`;
}
if (phase >= 4) {
const lhsVal = modpow(BigInt(g), z, BigInt(p));
const rhsVal = A * modpow(y, e, BigInt(p)) % BigInt(p);
const ok = lhsVal === rhsVal;
document.getElementById('verify-math').textContent =
`g^z = ${g}^${z} = ${lhsVal} mod ${p}\nA·y^e = ${A}·${y}^${e} = ${rhsVal} mod ${p}`;
const vCard = document.getElementById('card-verify');
vCard.classList.remove('done', 'fail');
vCard.classList.add(ok ? 'done' : 'fail');
}
const nextBtn = document.getElementById('btn-next');
nextBtn.textContent = phase === 0 ? t('btn_commit') :
phase === 1 ? t('btn_challenge') :
phase === 2 ? t('btn_respond') :
phase === 3 ? t('btn_verify') : t('btn_reset_run');
nextBtn.disabled = phase >= 4;
}
function advance() {
// Advance to the next protocol step.
if (phase === 0) {
r = BigInt(randInt(1, Number(q) - 1));
A = modpow(BigInt(g), r, BigInt(p));
phase = 1;
setStatus(t('status_committed'), 'info');
} else if (phase === 1) {
e = BigInt(randInt(1, Number(q) - 1));
phase = 2;
setStatus(t('status_challenged'), 'info');
} else if (phase === 2) {
z = ((r + e * x) % q + q) % q;
phase = 3;
setStatus(t('status_responded'), 'info');
} else if (phase === 3) {
phase = 4;
const lhsVal = modpow(BigInt(g), z, BigInt(p));
const rhsVal = A * modpow(y, e, BigInt(p)) % BigInt(p);
const ok = lhsVal === rhsVal;
if (ok) {
setStatus(t('status_verified_ok'), 'ok');
} else {
setStatus(t('status_verified_fail'), 'bad');
}
}
renderStep();
}
function setStatus(msg, cls) {
const el = document.getElementById('status-bar');
el.textContent = msg;
el.className = 'status-bar ' + cls;
}
// Pick translation string by browser language.
function t(key) {
const lang = (navigator.language || 'en').startsWith('es') ? 'es' : 'en';
return (TRANSLATIONS[lang] && TRANSLATIONS[lang][key]) || TRANSLATIONS['en'][key] || key;
}
const TRANSLATIONS = {
en: {
btn_commit: '1. Prover Commits',
btn_challenge: '2. Verifier Challenges',
btn_respond: '3. Prover Responds',
btn_verify: '4. Verifier Checks',
btn_reset_run: 'Run Again',
status_committed: 'Commitment A sent. Verifier has not seen x or r.',
status_challenged: 'Challenge e issued randomly.',
status_responded: 'Response z computed and sent.',
status_verified_ok: '✓ Verification passed — the prover knows x!',
status_verified_fail: '✗ Verification failed.',
},
es: {
btn_commit: '1. Demostrador se compromete',
btn_challenge: '2. Verificador desafía',
btn_respond: '3. Demostrador responde',
btn_verify: '4. Verificador comprueba',
btn_reset_run: 'Ejecutar de nuevo',
status_committed: 'Compromiso A enviado. El verificador no ha visto x ni r.',
status_challenged: 'Desafío e emitido aleatoriamente.',
status_responded: 'Respuesta z calculada y enviada.',
status_verified_ok: '✓ Verificación exitosa — ¡el demostrador conoce x!',
status_verified_fail: '✗ La verificación falló.',
}
};
document.getElementById('btn-next').addEventListener('click', () => {
if (phase >= 4) setup();
else advance();
});
document.getElementById('btn-reset').addEventListener('click', setup);
setup();
<!-- Sigma protocol (Schnorr) interactive demo -->
<div class="sigma-app">
<div class="params-row">
<span class="param-label">p = <strong id="val-p"></strong></span>
<span class="param-label">g = <strong id="val-g"></strong></span>
<span class="param-label">q = <strong id="val-q"></strong></span>
<span class="param-label">y = g<sup>x</sup> = <strong id="val-y"></strong></span>
<span class="param-label secret-label">x = <strong id="val-x" class="secret-val"></strong> <em class="secret-tag">(secret — only prover knows)</em></span>
</div>
<div class="steps-grid">
<!-- Step 1: Prover commits with a random blinding factor -->
<div class="step-card" id="card-commit">
<div class="step-num">1</div>
<div class="step-body">
<div class="step-title">Commit</div>
<div class="step-desc">Prover picks random r, sends A = g^r mod p</div>
<div class="step-math" id="commit-math"></div>
</div>
</div>
<!-- Step 2: Verifier issues a random challenge -->
<div class="step-card dimmed" id="card-challenge">
<div class="step-num">2</div>
<div class="step-body">
<div class="step-title">Challenge</div>
<div class="step-desc">Verifier picks random e and sends it</div>
<div class="step-math" id="challenge-math"></div>
</div>
</div>
<!-- Step 3: Prover responds folding in challenge and secret -->
<div class="step-card dimmed" id="card-respond">
<div class="step-num">3</div>
<div class="step-body">
<div class="step-title">Respond</div>
<div class="step-desc">Prover sends z = (r + e·x) mod q</div>
<div class="step-math" id="respond-math"></div>
</div>
</div>
<!-- Verification: one modular equation, no secret needed -->
<div class="step-card dimmed" id="card-verify">
<div class="step-num">✓</div>
<div class="step-body">
<div class="step-title">Verify</div>
<div class="step-desc">Check g^z ≡ A · y^e (mod p)</div>
<div class="step-math" id="verify-math"></div>
</div>
</div>
</div>
<div class="status-bar" id="status-bar"></div>
<div class="btns">
<button id="btn-next" type="button">Next Step</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div class="cheat-panel" id="cheat-panel" style="display:none;">
<div class="cheat-title">Why a cheater fails</div>
<div class="cheat-body" id="cheat-body"></div>
</div>
</div>
/* Sigma protocol demo styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.sigma-app { padding: .5rem .2rem; }
.params-row { display: flex; flex-wrap: wrap; gap: .5rem .9rem; font-size: .82rem;
background: #eef2f6; border-radius: 8px; padding: .5rem .8rem; margin-bottom: .8rem; }
.param-label { color: #444; }
.secret-label { color: #c0392b; }
.secret-val { color: #c0392b; }
.secret-tag { font-style: italic; font-size: .75rem; color: #888; }
.steps-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .5rem; margin-bottom: .6rem; }
.step-card { border: 1.5px solid #cdd9e3; border-radius: 10px; padding: .55rem .75rem;
display: flex; gap: .5rem; background: #f5f8fb; transition: opacity .25s, border-color .25s; }
.step-card.dimmed { opacity: .38; }
.step-card.active { border-color: #1d3557; background: #eaf0f8; opacity: 1; }
.step-card.done { border-color: #0a7d33; background: #edfaf2; opacity: 1; }
.step-card.fail { border-color: #c0392b; background: #fdecea; opacity: 1; }
.step-num { font-weight: 700; font-size: 1.05rem; color: #1d3557; min-width: 1.4rem; }
.step-title { font-weight: 700; font-size: .88rem; color: #1d3557; margin-bottom: .15rem; }
.step-desc { font-size: .78rem; color: #555; line-height: 1.35; }
.step-math { font-family: ui-monospace, monospace; font-size: .82rem; color: #222;
margin-top: .3rem; line-height: 1.5; min-height: 1.2em; }
.status-bar { font-size: .95rem; font-weight: 600; min-height: 1.4em; margin: .4rem 0 .5rem; }
.status-bar.ok { color: #0a7d33; }
.status-bar.bad { color: #c0392b; }
.status-bar.info { color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .4rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.cheat-panel { margin-top: .8rem; border: 1.5px solid #e2a800; border-radius: 10px;
padding: .55rem .75rem; background: #fffbea; }
.cheat-title { font-weight: 700; color: #b87800; font-size: .85rem; margin-bottom: .3rem; }
.cheat-body { font-size: .82rem; color: #555; line-height: 1.5; }
@media (max-width: 420px) {
.steps-grid { grid-template-columns: 1fr; }
.params-row { font-size: .78rem; }
}
// Schnorr sigma protocol demo over a small safe-prime group.
// Prover knows x such that g^x = y (mod p).
// Protocol: (1) commit A=g^r, (2) challenge e, (3) respond z=r+ex, (4) verify g^z == A*y^e.
// Group parameters: p prime, q = (p-1)/2 prime (safe prime), g generator of order q.
// p is prime, q = (p-1)/2 is prime (safe prime), g is a generator of order q.
const p = 23, g = 2, q = 11;
// Protocol state: secret x, public y, blinding r, commitment A, challenge e, response z.
let x, y, r, A, e, z;
function modpow(base, exp, mod) {
// Fast modular exponentiation using BigInt.
let result = 1n;
base = base % mod;
while (exp > 0n) {
if (exp % 2n === 1n) result = result * base % mod;
exp = exp / 2n;
base = base * base % mod;
}
return result;
}
function modinv(a, m) {
// Modular inverse via Fermat's little theorem (p prime).
return modpow(a, m - 2n, m);
}
function randInt(min, max) {
// Uniform random integer in [min, max].
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function setup() {
// Initialize a fresh run: pick new secret x, compute public y.
x = BigInt(randInt(1, Number(q) - 1));
y = modpow(BigInt(g), x, BigInt(p));
document.getElementById('val-p').textContent = p;
document.getElementById('val-g').textContent = g;
document.getElementById('val-q').textContent = q;
document.getElementById('val-y').textContent = y;
document.getElementById('val-x').textContent = x;
r = null; A = null; e = null; z = null;
phase = 0;
document.getElementById('cheat-panel').style.display = 'none';
renderStep();
setStatus('', '');
}
let phase = 0;
function renderStep() {
// Update the UI to reflect the current phase.
const cards = ['card-commit', 'card-challenge', 'card-respond', 'card-verify'];
cards.forEach((id, i) => {
const el = document.getElementById(id);
el.classList.remove('active', 'done', 'fail', 'dimmed');
if (i < phase) el.classList.add('done');
else if (i === phase) el.classList.add('active');
else el.classList.add('dimmed');
});
if (phase >= 1 && A !== null) {
document.getElementById('commit-math').textContent = `r = ${r} → A = ${g}^${r} mod ${p} = ${A}`;
}
if (phase >= 2 && e !== null) {
document.getElementById('challenge-math').textContent = `e = ${e}`;
}
if (phase >= 3 && z !== null) {
document.getElementById('respond-math').textContent = `z = r + e·x = ${r} + ${e}·${x} = ${z} (mod ${q})`;
}
if (phase >= 4) {
const lhsVal = modpow(BigInt(g), z, BigInt(p));
const rhsVal = A * modpow(y, e, BigInt(p)) % BigInt(p);
const ok = lhsVal === rhsVal;
document.getElementById('verify-math').textContent =
`g^z = ${g}^${z} = ${lhsVal} mod ${p}\nA·y^e = ${A}·${y}^${e} = ${rhsVal} mod ${p}`;
const vCard = document.getElementById('card-verify');
vCard.classList.remove('done', 'fail');
vCard.classList.add(ok ? 'done' : 'fail');
}
const nextBtn = document.getElementById('btn-next');
nextBtn.textContent = phase === 0 ? t('btn_commit') :
phase === 1 ? t('btn_challenge') :
phase === 2 ? t('btn_respond') :
phase === 3 ? t('btn_verify') : t('btn_reset_run');
nextBtn.disabled = phase >= 4;
}
function advance() {
// Advance to the next protocol step.
if (phase === 0) {
r = BigInt(randInt(1, Number(q) - 1));
A = modpow(BigInt(g), r, BigInt(p));
phase = 1;
setStatus(t('status_committed'), 'info');
} else if (phase === 1) {
e = BigInt(randInt(1, Number(q) - 1));
phase = 2;
setStatus(t('status_challenged'), 'info');
} else if (phase === 2) {
z = ((r + e * x) % q + q) % q;
phase = 3;
setStatus(t('status_responded'), 'info');
} else if (phase === 3) {
phase = 4;
const lhsVal = modpow(BigInt(g), z, BigInt(p));
const rhsVal = A * modpow(y, e, BigInt(p)) % BigInt(p);
const ok = lhsVal === rhsVal;
if (ok) {
setStatus(t('status_verified_ok'), 'ok');
} else {
setStatus(t('status_verified_fail'), 'bad');
}
}
renderStep();
}
function setStatus(msg, cls) {
const el = document.getElementById('status-bar');
el.textContent = msg;
el.className = 'status-bar ' + cls;
}
// Pick translation string by browser language.
function t(key) {
const lang = (navigator.language || 'en').startsWith('es') ? 'es' : 'en';
return (TRANSLATIONS[lang] && TRANSLATIONS[lang][key]) || TRANSLATIONS['en'][key] || key;
}
const TRANSLATIONS = {
en: {
btn_commit: '1. Prover Commits',
btn_challenge: '2. Verifier Challenges',
btn_respond: '3. Prover Responds',
btn_verify: '4. Verifier Checks',
btn_reset_run: 'Run Again',
status_committed: 'Commitment A sent. Verifier has not seen x or r.',
status_challenged: 'Challenge e issued randomly.',
status_responded: 'Response z computed and sent.',
status_verified_ok: '✓ Verification passed — the prover knows x!',
status_verified_fail: '✗ Verification failed.',
},
es: {
btn_commit: '1. Demostrador se compromete',
btn_challenge: '2. Verificador desafía',
btn_respond: '3. Demostrador responde',
btn_verify: '4. Verificador comprueba',
btn_reset_run: 'Ejecutar de nuevo',
status_committed: 'Compromiso A enviado. El verificador no ha visto x ni r.',
status_challenged: 'Desafío e emitido aleatoriamente.',
status_responded: 'Respuesta z calculada y enviada.',
status_verified_ok: '✓ Verificación exitosa — ¡el demostrador conoce x!',
status_verified_fail: '✗ La verificación falló.',
}
};
document.getElementById('btn-next').addEventListener('click', () => {
if (phase >= 4) setup();
else advance();
});
document.getElementById('btn-reset').addEventListener('click', setup);
setup();
Notice the structure. The commit step blinds the secret with fresh randomness r: the prover sends A=grmodp. The challenge e arrives from the verifier (or, in practice, from a hash). The response z=r+e⋅x folds the challenge in. Finally the verifier checks gz≡A⋅ye(modp) — one modular equation, no secret needed. A cheater without x would have to predict e in advance, which happens with probability 1/q: negligible for a large prime q.
Comments
Loading comments...