Bulletproofs: Zero-Knowledge Range Proofs
What bulletproofs prove: Every transfer carries a zero-knowledge proof that the amount is valid without revealing what it is. DERO uses a 128-bit combined range proof that binds both the transfer amount and the sender's remaining balance to non-negative 64-bit ranges in a single proof.
What Problem Do Bulletproofs Solve?
The Challenge:
DERO needs to verify:
✓ Amount is positive (not negative)
✓ Amount is reasonable (not 999 trillion)
✓ Sender has enough balance
But WITHOUT revealing:
✗ The actual amount
✗ The sender's balance
✗ Any private informationThe Solution: Bulletproofs
- ✅ Prove combined 128-bit value is valid (transfer + remaining balance, each 64-bit)
- ✅ Never reveal the amount value
- ✅ Logarithmic-size proof (7 rounds for 128-bit range)
- ✅ No trusted setup needed (the bulletproof generators
GandHare derived deterministically from public protocol constants via hash-to-curve —cryptography/crypto/algebra_pedersen.go:36-37— so there is no setup ceremony to trust)
How It Works (Simple Analogy)
The Bouncer Problem:
Show ID (Reveals Everything)
You: "I'm 25 years old"
Bouncer: "Show me your ID"
Bouncer learns:
✓ You're over 21
✓ Your exact age (25)
✓ Your address
✓ Your full name
✓ Your photo
✓ Everything else on the ID
Disclosure: everything on the ID is exposedFor DERO:
- Bulletproof proves: "Combined 128-bit value is valid (transfer amount in lower 64 bits, remaining balance in upper 64 bits)"
- Network learns: ✅ Both values are valid (in [0, 2^64))
- Network learns: ❌ What either value actually is
The Proof Flow
What Network Sees:
- ✅ Commitment (encrypted amount)
- ✅ Bulletproof (logarithmic-size proof)
- ✅ Proof is valid
- ❌ Actual amount (hidden)
Protection Against Negative Transfers
The guarantee is cryptographic, not a string-parsing quirk: by the soundness of the Bulletproof range proof, no prover can construct a proof that verifies for a committed value outside the proven range. Combined with DERO's homomorphic value conservation, value cannot be created from nothing.
DERO packs two quantities into a single 128-bit value and range-proves them together — the transfer amount in the low 64 bits and the sender's remaining balance in the high 64 bits — so one Bulletproof binds both to non-negative 64-bit ranges (number = transfer + (balance << 64), cryptography/crypto/proof_generate.go:471).
A "negative" transfer is the uint64 wraparound of a value near 2^64. It fails the range proof from both directions:
| Attack framing | Why the range proof rejects it |
|---|---|
| Treat the wraparound as the transfer amount | A value near 2^64 lies outside [0, 2^64) — the low-64 range bound fails. |
| Treat it as receiving (so your own balance jumps) | The conservation proof forces a matching decrease; the sender's remaining balance goes negative → wraps → the high-64 range bound fails. |
The protection lives in verification (cryptography/crypto/proof_verify.go), not generation. The wallet builds the proof in proof_generate.go; an attacker controls their own wallet and could patch out any client-side check. The cryptographic guarantee comes from the verifier check that every node runs independently before accepting a block.
Common misconception: A claim circulates that Go's BigInt.Text(2) emitting a '-' for negative values "breaks the bit loop" in proof generation, and that this is what stops negative transfers. It does not — the loop is if b == '1' { … } else { … }, which silently treats a stray '-' as a 0 bit. And for any real transaction the packed 128-bit number is positive, so no '-' appears at all. The actual safeguard is verifier-side Bulletproof soundness. See Negative Transfer Protection for the full derivation.
The Six Sigma Proofs
DERO uses six interconnected proofs that all must pass:
| Proof | What It Validates | Security Level |
|---|---|---|
| A_y | Sender has private key | 🔒 Cryptographically secure |
| A_D | Encrypted balance update correct | 🔒 Homomorphic validation |
| A_b | Balance commitment valid | 🔒 Binding & hiding |
| A_X | Additional protocol constraints | 🔒 Protocol-specific |
| A_t | Range-proof commitment for the packed 128-bit value (transfer + balance << 64) — closed by the inner product proof below | 🔒 Bulletproof — soundness under DL on bn256 (random oracle model) |
| A_u | Key image (linking tag) is correctly derived from the sender's secret key — ties tx to the sender within the anonymity-set without revealing which slot is the sender | 🔒 Anti-replay, account-model linking (see Transaction Proofs § A_u) |
All Bound Together:
Challenge hash (c) = hash(A_y || A_D || A_b || A_X || A_t || A_u || ...)
If ANY proof fails:
→ Challenge hash is different
→ Verification fails
→ Transaction rejectedSource: cryptography/crypto/proof_verify.go:98 — Verify() recomputes the challenge hash from all six recovered sigma components (lines 410-425), then closes A_t via the inner product proof at line 457.
Inner Product Proof — Closing A_t
The inner product proof is not a seventh independent check. It is the recursive closure of the A_t bulletproof above: A_t commits to polynomial coefficients of the range-proof relation; the inner product argument shows those coefficients fold consistently with the committed value, proving (with the rest of the bulletproof scaffolding) that the packed transfer + balance << 64 lies in [0, 2^128).
A failed inner product check is a failed A_t — and therefore a failed transaction. There is one bulletproof gate, with two named pieces.
Recursive Halving — How Logarithmic Scaling Works
The Algorithm:
Iterations:
- Start: 128 elements
- Iteration 1: 64 elements
- Iteration 2: 32 elements
- Iteration 3: 16 elements
- Iteration 4: 8 elements
- Iteration 5: 4 elements
- Iteration 6: 2 elements
- Iteration 7: 1 element
Total: log₂(128) = 7 iterations
Source: cryptography/crypto/proof_innerproduct.go:71 hardcodes length := 7 for the 128-bit range (note: this lives in Deserialize, so the 128-bit range size is fixed at the wire level — an attacker cannot submit a proof of a different range); the verifier closure call is proof.ip.Verify(...) at cryptography/crypto/proof_verify.go:457. The bulletproof input vector itself is packed at proof_innerproduct.go:41 (the "7 entries" structure).
Zero-Knowledge Property Explained
What "Zero-Knowledge" Means
After seeing a valid bulletproof, verifier learns:
| Information | Learned? | Reason |
|---|---|---|
| Combined 128-bit value is valid | ✅ Yes | This is what's proven |
| Transfer and balance each in [0, 2^64) | ✅ Yes | Implicit from 128-bit structure |
| Exact transfer amount | ❌ No | Zero-knowledge property |
| Exact remaining balance | ❌ No | Zero-knowledge property |
| Any bits of either value | ❌ No | Bits are hidden |
| Any private information | ❌ No | The proof reveals nothing beyond the validity claim |
Formal Properties:
- ✅ Completeness: Valid proofs always verify
- ✅ Soundness: Invalid proofs never verify
- ✅ Zero-knowledge: No information leaked
Where Bulletproofs Are Used
Three Core Applications:
| Application | What It Validates | Protection |
|---|---|---|
| Transaction Amounts | Combined 128-bit value (transfer + remaining balance) | Prevents negative transfers |
| Balance Sufficiency | Both transfer and remaining balance in [0, 2^64) | Prevents overdrafts |
Asset (token) transfers via tx.Payloads[t] | Range-proves the same packed value for each non-DERO asset moved in the transaction | Same negative/overflow protection applied per asset payload (note: smart-contract STORE/LOAD state is stored plaintext in the graviton tree — it is not bulletproof-protected) |
All three use the same bulletproof mechanism:
- ✅ Prove value is in valid range
- ✅ Never reveal the actual value
- ✅ Cryptographic guarantee
Key Takeaways
What You Get
| Feature | Benefit | Impact |
|---|---|---|
| 🔒 Zero-Knowledge | Amount hidden from network | Transactions don't leak amounts to observers |
| 📦 Logarithmic Proofs | 7 rounds for 128-bit range | Efficient transactions |
| 🔐 No Trusted Setup | Pure cryptography | Decentralized security |
| 🛡️ Soundness + Conservation | No verifying proof for out-of-range values | Negative transfers cryptographically impossible |
| ✅ Proven Security | bn256 elliptic curve discrete log | Standard discrete log security assumption |
What You're Protected From
The Bottom Line:
Verifier-side guarantee: Every node independently checks the Bulletproof. By soundness, no prover can construct a verifying proof for an out-of-range value. Combined with homomorphic value conservation, negative transfers are cryptographically impossible.
Performance + Privacy: DERO's bulletproof implementation keeps proofs logarithmic in size while preserving strong cryptographic guarantees.
Related Pages
Privacy Suite:
- Ring Signatures - Sender anonymity
- Homomorphic Encryption - Amount encryption
- Transaction Privacy - Complete privacy model
Technical Details:
- DERO Tokens - How bulletproofs secure token balances
- Transaction Structure - Bulletproof integration
Learn More:
- Privacy Features Overview - Full privacy suite
- Private Smart Contracts - Contract privacy