Range proofs in 80 lines: Pedersen commitments and a tiny Bulletproof
How a Bulletproof actually compresses a range proof to logarithmic size. Derive the inner-product argument from scratch, run a toy prover/verifier in the browser, and pick the right range-proof primitive for 2026.
- FROM
- Dax the Dev <[email protected]>
- SOURCE
- https://blog.skill-issue.dev/blog/range_proofs_in_80_lines/
- FILED
- 2026-04-08 16:00 UTC
- REVISED
- 2026-04-08 16:00 UTC
- TIME
- 9 min read
- SERIES
- ZK SNARKs in production
- TAGS
A confidential transaction has to prove one annoying little thing: that the hidden amount is non-negative and bounded. Without that, an attacker can mint coins out of thin air by committing to a “negative balance” that wraps around the field. The cryptographic primitive that does the proving is the range proof, and the question of which range proof to ship in 2026 is — surprisingly — still live.
This post does three things:
- Derives the inner-product argument that makes Bulletproofs short.
- Walks an 80-line, runnable toy Bulletproof prover/verifier in the browser.
- Maps the trade-offs between Bulletproofs, classical range proofs, and SNARK-based range proofs onto the deployment surface I keep hitting in zera-sdk.
It’s a sibling piece to Pedersen commitments, in production. Read that one first if “Pedersen” still feels like a textbook word to you. This one assumes you know what C = a·G + b·H is and want to know what to do with it.
What a range proof has to do
The setup. A prover holds a value and a blinding factor , and publishes a Pedersen commitment
with independent generators of an elliptic-curve group of prime order . The hiding property of comes from being uniformly random; the binding property comes from and being independent (no known with ).
The prover then wants to convince a verifier that lies in some range, typically for or . Crucially, stays hidden. The verifier learns only the fact that the committed value is in range.
The naive proof of "" is to commit bit-by-bit: write with , commit to each , and prove each . That works. It takes commitments and proof size, which is what Confidential Transactions in Bitcoin shipped in 2015 and which is roughly 2.5 KB per transaction at .
Bünz, Bootle, Boneh, Poelstra, Wuille, and Maxwell (2018) — the Bulletproofs paper — got that down to about 672 bytes, with no trusted setup, by replacing the linear blob with a logarithmic-size inner-product argument. The compression ratio is roughly 4× over the naive bit-commitment scheme, and it gets better as the range grows.
The inner-product argument, derived
The whole game in Bulletproofs is the inner-product argument (IPA). Forget range proofs for a paragraph. The IPA proves the following:
Statement. Given commitments and , plus a scalar , the prover knows vectors such that
The naive proof is to send and — that’s scalars. The IPA gets it to group elements plus two scalars.
The trick is recursion. Split each vector in half: , same for . The prover sends two cross-terms:
The verifier responds with a random challenge . Both parties then compute folded vectors of half the length:
and the verifier folds the generators in the dual direction:
The new commitment is
and you can check by direct expansion that exactly when the original relation held. Recurse on . After rounds, the vectors are length 1 and the prover just sends the two remaining scalars.
That’s the entire IPA in seven lines of math. Total proof size: group elements (the and from each round) + 2 final scalars. At , that’s 12 group elements + 2 scalars ≈ 416 bytes.
flowchart LR
A[a, b length n] --> S1[split into halves]
S1 --> X1[prover sends L_1, R_1]
X1 --> C1[verifier sends challenge x_1]
C1 --> F1[fold to length n/2]
F1 --> R{length 1?}
R -->|no| S1
R -->|yes| F[send final a, b]
F --> V[verifier checks single point] From IPA to range proof in two reductions
The range proof reduces to the IPA in two steps.
Step 1: bit decomposition as a vector identity. Write where is the bit decomposition and . Define (so each ). The conjunction "" becomes the vector identities
The first identity (Hadamard product is zero) is exactly the bit constraint rewritten.
Step 2: collapse three vector identities to one inner product. The verifier samples challenges . The prover constructs polynomials
with random blinding vectors. The inner product is a quadratic in , and the constant term collapses to
The verifier knows (it’s all public scalars) and knows (the commitment to ), so it can check the equation against . The prover then runs the IPA on and for a fresh challenge , and that is what gets compressed to .
The whole construction is one Pedersen commitment, two challenges, two polynomial-coefficient commitments, and an IPA. It fits in a paragraph and runs in a browser.
The 80-line toy
This is a runnable Bulletproof-style range proof for (so ). It is intentionally small. It uses scalar arithmetic in a tiny prime field instead of an elliptic-curve group, which means it demonstrates the protocol shape but provides zero cryptographic security. Read it for the algebra, not the hardness.
The shape is the thing. A real Bulletproof replaces my BigInt scalars with elliptic-curve points (typically Ristretto or BN254 G1), runs the IPA recursively to length 1 instead of just one fold, and uses Fiat-Shamir over a transcript that includes every public group element. The protocol stays under 700 bytes for , and the verifier cost stays at multiplications (the prover dominates at ).
Choosing a range proof in 2026
The trade-off space has settled enough to write down honestly.
| Option | Cost | Latency | Blast radius | Notes |
|---|---|---|---|---|
| Naive bit-commitment range proof | ~2.5 KB at n=64 | Prover ~100ms, verifier ~10ms | Low risk; well understood since 2015 | Confidential Transactions shipped this. Big proofs, but trivial to audit. |
| Bulletproofs (Bunz et al. 2018) | ~672 bytes at n=64; logarithmic in n | Prover ~500ms, verifier ~20ms (linear) | Battle-tested in Monero, dalek-cryptography | No trusted setup. Best choice when you have one or a few range checks per tx. |
| Bulletproofs+ (Chung-Han-Hwang-Kim-Lee 2020) | ~576 bytes at n=64 | Prover ~10% faster than BP; verifier ~25% faster | Less deployment than original BP | Drop-in if you control both ends. Worth it for a fresh deployment. |
| SNARK-embedded range proof (Groth16 / PLONK) | ~200-300 bytes; constant | Verifier ~3-5ms (constant); prover dominates | Inherits the SNARK's trusted setup story | Right answer when you're already paying for a SNARK. zera-sdk does this. |
| STARK-embedded range proof | ~50-200 KB; logarithmic | Prover slow, verifier fast | Post-quantum, transparent setup | Big proofs are the cost. Worth it for batched provers (rollups, not transfers). |
The pattern: if you’re already running a SNARK for the privacy proof, embed the range check inside it and pay nothing extra. If you don’t have a SNARK and you want short proofs without a trusted setup, Bulletproofs are the right answer. The naive bit-commitment scheme is what you ship when you don’t trust the cryptanalysis of either and you’re willing to pay 2.5 KB per transaction. STARKs are aspirational for transfers and the right tool for rollups.
In zera-sdk, the range check on amount is a 64-bit decomposition inside the Groth16 transfer circuit. Cost: 64 R1CS constraints (one per bit), zero additional bytes on chain. The Bulletproof would have been 672 bytes per spend, which on Solana at 5,000 lamports per byte adds up faster than the constraint cost in the prover.
What I’d reach for, and when
The framing I keep coming back to: range proofs are a feature of a privacy system, not a product. The product is the privacy pool. The range proof exists because, without it, the pool is exploitable. Pick the one that disappears most quietly into the rest of your system.
For the unified shielded pool on Solana, the SNARK-embedded approach wins for compute units and bytes. For a chain that doesn’t already have a SNARK, Bulletproofs are the line where the cryptography costs roughly the same per-byte on chain as a multisig and you stop arguing about it. For anything post-quantum, STARKs are the only answer — the discrete-log assumption everything else here leans on collapses to a quantum adversary, and the bullet has to be biting.
Bulletproofs greatly improve on the linear (in the bitlength of the range) proof size of confidential transactions. They are also a drop-in replacement for the range proofs used in Monero and other confidential-transaction systems, requiring no trusted setup and relying only on the discrete-logarithm assumption.
The 80-line toy at the top of this post is the entire algebraic core of that paper, with the elliptic curve removed. Once you see that the inner-product argument is just fold the vector in half, prove a smaller statement, the rest of the construction is bookkeeping.
Further reading
- Bulletproofs: Short Proofs for Confidential Transactions and More — Bünz, Bootle, Boneh, Poelstra, Wuille, Maxwell (IEEE S&P 2018) — the original.
- Bulletproofs+: Shorter Proofs for a Privacy-Enhanced Distributed Ledger — Chung, Han, Hwang, Kim, Lee (2020) — the ~15% smaller refinement.
- dalek-cryptography/bulletproofs — the canonical Rust implementation; constant-time, audited.
- Pedersen commitments, in production — sister piece on what we’re committing to.
- Poseidon, by hand and by code — sister piece on the hash function inside our circuits.
- Privacy’s broadband moment — why these primitives shipped together in 2026.
Dax911/zera-sdk— production Rust implementation of the range-check-inside-Groth16 path.