Introduction

Imagine a bank transaction where the amount must be positive — you can't spend money you don't have — but the amount itself should stay private. How can the network verify the rule without seeing the number?

That tension — prove a fact about a secret without revealing the secret — is what zero-knowledge proofs (ZKPs) are built for. And one of the most practically important ZKPs is the range proof: a short cryptographic certificate that says "I know a number xx such that 0x<2320 \leq x < 2^{32}" without disclosing xx at all.

The key ingredient is a commitment: a value CC that binds you to xx (you can't change it later) but hides xx from everyone else — like sealing a number in an envelope. A range proof then convinces a verifier that the committed value really does fall inside the promised interval, using nothing but math.

This is not just a theoretical curiosity. Range proofs are the core cryptographic primitive behind confidential transactions in blockchains like Monero and the Mimblewimble protocol, and they appear in privacy-preserving credentials, anonymous voting, and secure multi-party computation.

Try It

Pick a secret value and a valid range, then commit it. The demo simulates a Prover and a Verifier: the prover generates a proof that the committed value lies in the range, and the verifier checks the proof — without ever learning what the value is.

<!-- {{c_demo_intro}} -->
<div class="panel">
  <div class="section-label">{{label_prover}}</div>
  <label class="field-label">{{label_secret}}
    <input id="secret" type="number" min="0" max="255" value="42" title="{{title_secret}}">
  </label>
  <label class="field-label">{{label_range}}
    <select id="rangeMax" title="{{title_range}}">
      <option value="7">0 – 7 (3 {{label_bits}})</option>
      <option value="15">0 – 15 (4 {{label_bits}})</option>
      <option value="31">0 – 31 (5 {{label_bits}})</option>
      <option value="63">0 – 63 (6 {{label_bits}})</option>
      <option value="127">0 – 127 (7 {{label_bits}})</option>
      <option value="255" selected>0 – 255 (8 {{label_bits}})</option>
    </select>
  </label>
  <button id="btnCommit" type="button">{{btn_commit}}</button>
</div>

<div class="panel commitment-panel" id="commitPanel" style="display:none">
  <div class="section-label">{{label_commitment_phase}}</div>
  <div class="commit-row">
    <span class="mono" id="commitVal"></span>
    <span class="tag">{{tag_commitment}}</span>
  </div>
  <div class="hint-small">{{hint_commitment}}</div>
  <button id="btnProve" type="button">{{btn_prove}}</button>
</div>

<div class="panel proof-panel" id="proofPanel" style="display:none">
  <div class="section-label">{{label_proof_phase}}</div>
  <div id="bitRows" class="bit-rows"></div>
  <div class="hint-small">{{hint_proof}}</div>
</div>

<div class="panel verifier-panel" id="verifierPanel" style="display:none">
  <div class="section-label">{{label_verifier}}</div>
  <div class="status" id="verifyStatus"></div>
  <div class="hint-small" id="verifyDetail"></div>
  <button id="btnReset" type="button" class="ghost">{{btn_reset}}</button>
</div>
/* {{c_layout_comment}} */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 15px; }
.panel { background: #f4f7fa; border: 1px solid #d0dae4; border-radius: 10px;
         padding: .8rem 1rem; margin-bottom: .6rem; }
.section-label { font-size: .7rem; font-weight: 700; text-transform: uppercase;
                 letter-spacing: .06em; color: #5a7088; margin-bottom: .45rem; }
.field-label { display: flex; align-items: center; gap: .5rem; font-size: .9rem;
               font-weight: 600; margin-bottom: .45rem; }
input[type=number] { width: 72px; padding: .3rem .4rem; border: 1px solid #b0bec5;
                     border-radius: 6px; font-size: .95rem; text-align: center; }
select { padding: .3rem .4rem; border: 1px solid #b0bec5; border-radius: 6px;
         font-size: .9rem; }
button { font: 600 13px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557;
         background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer;
         margin-top: .3rem; }
button.ghost { background: #fff; color: #1d3557; }
.commit-row { display: flex; align-items: center; gap: .6rem; margin-bottom: .35rem; }
.mono { font-family: ui-monospace, monospace; font-size: .82rem; color: #1d3557;
        background: #dce8f3; padding: .2rem .45rem; border-radius: 5px;
        word-break: break-all; }
.tag { font-size: .7rem; font-weight: 700; text-transform: uppercase; background: #e2ecf5;
       color: #3a6080; padding: .15rem .4rem; border-radius: 4px; }
.hint-small { font-size: .8rem; color: #555; line-height: 1.4; margin-top: .3rem; }
/* {{c_bit_rows_comment}} */
.bit-rows { display: flex; flex-wrap: wrap; gap: .35rem; margin-bottom: .4rem; }
.bit-cell { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.bit-box { width: 34px; height: 34px; border-radius: 6px; display: flex;
           align-items: center; justify-content: center; font: 700 14px ui-monospace;
           border: 1.5px solid #8da7be; }
.bit-box.b0 { background: #e8eef3; color: #444; }
.bit-box.b1 { background: #1d3557; color: #fff; border-color: #1d3557; }
.bit-idx { font-size: .6rem; color: #7a8fa0; }
.commit-mini { font-family: ui-monospace; font-size: .7rem; color: #3a6080;
               background: #dce8f3; border-radius: 4px; padding: .1rem .3rem;
               margin-top: 2px; }
.status { font-size: 1rem; font-weight: 700; padding: .3rem 0; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.verifier-panel { background: #f0f9f4; border-color: #b8d8c6; }
.verifier-panel .status.bad { background: #fdf0f0; border-radius: 6px;
                              padding: .3rem .5rem; }
// Code not found

Notice the key property: the verifier sees only the commitment and the proof, never the secret. Try entering a value outside the range — the proof fails, and the commitment gives nothing away about the actual number. The proof is zero-knowledge (the verifier learns nothing beyond "it's in range") and succinct (the certificate is tiny compared to checking all possibilities).

The Real Complexity

A naïve range proof for x[0,2n1]x \in [0, 2^{n}-1] works by decomposing xx in binary and proving each bit is 00 or 11 — one sub-proof per bit, so the proof size is O(n)O(n). For n=32n = 32 that is already 32 Schnorr-style sub-proofs, which is workable but large.

The breakthrough came with Bulletproofs (Bünz et al., 2017), which use an inner-product argument to collapse the nn bit-commitments into a proof of size O(logn)O(\log n) — about 700 bytes for a 64-bit range, without any trusted setup.

  • Soundness rests on the discrete-logarithm hardness assumption: an adversary who could forge a range proof would be able to compute discrete logs, which is believed to be computationally infeasible.
  • Zero-knowledge is guaranteed because the commitment scheme (Pedersen commitments, of the form C=xG+rHC = xG + rH for random blinding factor rr) reveals nothing about xx.
  • Verification costs O(logn)O(\log n) group operations — fast enough to validate thousands of confidential transactions per second.

The result sits at the intersection of discrete logarithm hardness and interactive proof systems, and it exemplifies how modern cryptography turns intractability into a feature rather than a barrier.

Where It Matters

Range proofs solve the "prove a bound without disclosing the value" problem that appears across privacy-sensitive systems:

  • Confidential transactions: Monero uses Bulletproofs so that every transaction proves its amounts are non-negative and sum correctly, without revealing the amounts to anyone on the network.
  • Anonymous age verification: prove you are over 18 without disclosing your birth date — a range proof on a committed birthdate timestamp.
  • Private auction bids: prove your bid is above a reserve price without revealing the bid amount until the auction closes.
  • Zero-knowledge machine learning: prove a model output falls in an acceptable range without exposing the model weights or the input data.
  • Regulatory compliance: a financial institution proves a balance or position stays within regulatory limits, while the regulator learns only "compliant" or "not compliant."

Learn how range proofs work and you've unlocked a key building block of modern zero-knowledge proof systems — the same kind of privacy-preserving arithmetic that powers Zcash, StarkNet, and the next generation of privacy protocols.

Conclusion

A range proof is a certificate that a secret number obeys a rule — without revealing the number. It combines the hiding power of a commitment scheme with the persuasive force of a zero-knowledge argument, and Bulletproofs make the whole thing fit in a few hundred bytes.

The deeper lesson is that computational hardness can be an asset: because the discrete logarithm problem is believed to be hard, we can build commitments that are perfectly hiding yet publicly verifiable. An adversary who broke discrete log would break range proofs too — but as long as the problem stays hard, your secret stays safe, provably in range, and completely undisclosed.

Share this article

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

Comments

Loading comments...

https://www.kipuhub.com/en/article/zero-knowledge-range-proofs/Content licensed under CC BY-NC 4.0.