Introduction

Imagine you know a secret password. You want to convince a guard that you know it — but without actually saying it out loud. Is that even possible?

Sigma protocols (named for the Greek letter Σ, whose shape echoes the three-move flow) answer with a resounding yes. They are a family of interactive proofs of knowledge: cryptographic conversations in which a prover convinces a verifier that they know some secret, while the protocol itself leaks nothing about what the secret actually is.

The canonical example is the Schnorr protocol (Claus-Peter Schnorr, 1991), which proves knowledge of a discrete logarithm without revealing it. The idea is disarmingly simple: instead of showing the secret directly, the prover commits to a random blinding value, receives a random challenge, and sends back a response that entangles both. The verifier checks one equation — and learns nothing else.

That combination — completeness (honest provers always pass), soundness (cheaters fail with overwhelming probability), and zero-knowledge (the transcript could have been faked without the secret) — is the magic at the heart of modern privacy-preserving cryptography.

Try It

The demo below runs a Schnorr discrete-log proof over a small group so every number stays readable. The prover knows a secret xx such that gx≡y(modp)g^x \equiv y \pmod{p}. The goal: convince you of that fact without saying what xx is.

<!-- 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();
Result
Console

Notice the structure. The commit step blinds the secret with fresh randomness rr: the prover sends A=gr mod pA = g^r \bmod p. The challenge ee arrives from the verifier (or, in practice, from a hash). The response z=r+e⋅xz = r + e \cdot x folds the challenge in. Finally the verifier checks gz≡A⋅ye(modp)g^z \equiv A \cdot y^e \pmod{p} — one modular equation, no secret needed. A cheater without xx would have to predict ee in advance, which happens with probability 1/q1/q: negligible for a large prime qq.

The Real Complexity

What makes a sigma protocol work — and what are its limits?

  • Completeness: if the prover truly knows xx, the verification equation gz≡A⋅ye(modp)g^z \equiv A \cdot y^e \pmod{p} always holds. This follows from the algebra: gr+ex=gr⋅(gx)e=A⋅yeg^{r+ex} = g^r \cdot (g^x)^e = A \cdot y^e.
  • Special soundness: given two accepting transcripts (A,e1,z1)(A, e_1, z_1) and (A,e2,z2)(A, e_2, z_2) with the same commitment but different challenges, one can extract the secret as x=(z1−z2)/(e1−e2) mod qx = (z_1 - z_2)/(e_1 - e_2) \bmod q. A cheater who cannot predict the challenge cannot fake two such transcripts — so cheating succeeds only with probability 1/q1/q.
  • Honest-verifier zero-knowledge (HVZK): a simulator can produce transcripts (A,e,z)(A, e, z) that are statistically indistinguishable from real ones — without knowing xx. It picks zz and ee at random, then sets A=gz⋅y−e mod pA = g^z \cdot y^{-e} \bmod p. Because the challenge is chosen after the commitment in a real run, the distributions match perfectly.
  • The Fiat–Shamir transform (Amos Fiat and Adi Shamir, 1986) replaces the verifier's random challenge with a cryptographic hash e=H(A∥message)e = H(A \| \text{message}). This converts the interactive protocol into a non-interactive zero-knowledge (NIZK) proof — and, when a message is hashed in, into a digital signature. The Schnorr signature scheme is exactly this transform applied to the Schnorr protocol.
  • Security assumption: the protocol's soundness rests on the hardness of the discrete logarithm problem. Break discrete log and you break the proof.

Where It Matters

The commit-challenge-respond pattern is one of cryptography's most reused building blocks:

  • Digital signatures: the Schnorr signature (now standardized in EdDSA and used in Signal, SSH, and TLS 1.3) is literally the Fiat–Shamir transform of the Schnorr sigma protocol.
  • Anonymous credentials: systems like Microsoft U-Prove and IRMA let you prove properties about yourself (age, membership) without revealing your identity — via sigma protocols for committed values.
  • Ring signatures and group signatures: proving "I am one of these nn people" without saying which one uses sigma protocols composed with OR-proofs.
  • Zero-knowledge range proofs: proving a committed number lies in [0,2k−1][0, 2^k - 1] without revealing it — essential for confidential transactions in Monero, Zcash, and similar systems.
  • zk-SNARKs and zk-STARKs: modern succinct zero-knowledge proof systems build on the same completeness/soundness/zero-knowledge triad that sigma protocols made concrete, scaling it to arbitrary computations.

Understand sigma protocols and you hold the key to every privacy layer in modern cryptography — from your chat app to the blockchain.

Conclusion

Sigma protocols distill a profound idea into three lines of algebra: commit to hide yourself in randomness, challenge to prevent cheating, respond to prove you know without showing. The result is a protocol that is simultaneously convincing and revealing nothing.

The Fiat–Shamir transform then collapses the interaction entirely, turning a conversation into a signature. That single insight — that a hash can replace a verifier — connects sigma protocols to nearly every privacy-preserving construction in modern cryptography.

So the next time your phone signs into a server, or a blockchain transaction hides its amount, or an anonymous credential proves your age — somewhere in the machinery, three numbers are dancing the sigma protocol's elegant waltz. See also: discrete logarithm and the factoring problem that underlies so many cryptographic hardness assumptions.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/sigma-protocols/Content licensed under CC BY-NC 4.0.