Bulletproofs zero-knowledge range proof library over the BN254 curve in Go, built on gnark-crypto.
Implements the Bulletproofs protocol from Bünz et al. 2018 with security hardening for production use in blockchain contexts (e.g., Cosmos SDK confidential transfers).
- Range proofs -- prove a committed value is in [0, 2^n) without revealing it
- Aggregate range proofs -- prove multiple values in a single compact proof
- Threshold proofs -- prove v < threshold or v > threshold
- Inner product arguments -- standalone Protocol 1 from the paper
- Pedersen commitments -- with configurable base points
- Serialization -- compact binary encoding with compressed G1 points
- Fiat-Shamir transcripts -- with optional pre-initialized context for domain separation
go get github.com/nixprotocol/bulletproofs-bn254
Requires Go 1.23+.
package main
import (
"fmt"
bp "github.com/nixprotocol/bulletproofs-bn254"
"github.com/consensys/gnark-crypto/ecc/bn254/fr"
)
func main() {
// Secret value and random blinding factor.
v := uint64(42)
var r fr.Element
r.SetRandom()
// Prove v is in [0, 2^8).
proof, err := bp.RangeProve(v, &r, &bp.H, 8, nil)
if err != nil {
panic(err)
}
// Compute the public commitment V = v*G + r*H.
var vFr fr.Element
vFr.SetUint64(v)
V := bp.PedersenCommit(&vFr, &r)
// Verify.
ok := bp.RangeVerify(&V, proof, &bp.H, 8, nil)
fmt.Println("verified:", ok) // true
}// Prove v < 10000.
proof, err := bp.ProveLessThan(v, &r, &bp.H, 10000, 40, nil)
// Verify against the public commitment.
ok := bp.VerifyLessThan(&V, proof, &bp.H, 10000, 40, nil)// Prove multiple values are each in [0, 2^40).
values := []uint64{1000, 500, 250}
blindings := []*fr.Element{&r1, &r2, &r3}
proof, err := bp.AggregateRangeProve(values, blindings, &bp.H, 40, nil)
// Verify with the corresponding commitments.
ok := bp.AggregateRangeVerify(Vs, proof, &bp.H, 40, nil)Bind proofs to application context (chain ID, sender, etc.) to prevent cross-context replay:
import elgamal "github.com/nixprotocol/elgamal-bn254"
// Prover and verifier must use identical transcript context.
t := elgamal.NewTranscript("x/confidential/v1")
t.AppendBytes("chain_id", []byte("nix-1"))
t.AppendBytes("sender", []byte("cosmos1abc..."))
proof, _ := bp.RangeProve(v, &r, &bp.H, 40, t)data, _ := proof.Marshal() // compact binary
var p2 bp.RangeProof
p2.Unmarshal(data) // deserialize| Function | Description |
|---|---|
RangeProve(v, r, Hbase, n, transcript) |
Prove v in [0, 2^n) |
RangeVerify(V, proof, Hbase, n, transcript) |
Verify range proof |
AggregateRangeProve(values, blindings, Hbase, n, transcript) |
Prove multiple values |
AggregateRangeVerify(Vs, proof, Hbase, n, transcript) |
Verify aggregate proof |
ProveLessThan(v, r, Hbase, threshold, n, transcript) |
Prove v < threshold |
VerifyLessThan(V, proof, Hbase, threshold, n, transcript) |
Verify less-than |
ProveGreaterThan(v, r, Hbase, threshold, n, transcript) |
Prove v > threshold |
VerifyGreaterThan(V, proof, Hbase, threshold, n, transcript) |
Verify greater-than |
PedersenCommit(v, r) |
Commit with standard generators G, H |
PedersenCommitWithBase(v, G, r, H) |
Commit with custom generators |
InnerProductProve(G, H, U, a, b, transcript) |
Standalone IPA prover |
InnerProductVerify(G, H, U, P, proof, transcript) |
Standalone IPA verifier |
All proof types support Marshal()/Unmarshal() for binary serialization.
Benchmarks on Apple M1 Pro (single-threaded measurements, MSM parallelized internally):
| Operation | n | Time | Proof Size |
|---|---|---|---|
| RangeProve | 8 | 3.9 ms | 420 B |
| RangeVerify | 8 | 1.4 ms | -- |
| RangeProve | 40 | 20.8 ms | 612 B |
| RangeVerify | 40 | 5.0 ms | -- |
| AggregateProve (2 values) | 40 | 39.2 ms | 868 B |
| AggregateVerify (2 values) | 40 | 8.9 ms | -- |
| InnerProductProve | 64 | 15.7 ms | -- |
| InnerProductVerify | 64 | 0.8 ms | -- |
Proof sizes are logarithmic in the bit width: O(log n) group elements.
Run benchmarks: go test -bench=. -benchmem
- Zero-knowledge: proofs reveal nothing about the committed value beyond the stated range
- Soundness: a prover cannot convince a verifier of a false statement (under the discrete log assumption on BN254)
- Fiat-Shamir: all interactive challenges are replaced with transcript hashing; commitment V is bound into the transcript to prevent proof transplant attacks
- Inner product transcript continuation: the IP argument continues the main range proof transcript, binding IP challenges to all prior commitments (V, A, S, T1, T2)
- Hash-to-curve: uses gnark-crypto's RFC 9380 (Simplified SWU) implementation, constant-time
- Input validation: all public API functions reject nil pointers, identity points, off-curve points, and zero blinding factors
- Proof point validation: verifiers check that all proof points (A, S, T1, T2, L[i], R[i]) are on-curve before proceeding
- Challenge zero checks: Fiat-Shamir challenges y, z, x are explicitly checked for zero in both provers and verifiers
- Serialization bounds: deserialization rejects proofs with more than 64 inner product rounds, preventing allocation attacks
- Bounded generator cache: generator vectors are cached with a maximum of 32 entries and vector length capped at 2^20
- Subgroup checks: gnark-crypto's
SetBytesperforms on-curve and subgroup validation during deserialization
The hash-to-curve implementation was changed from try-and-increment to RFC 9380 (Simplified SWU). This changes all derived generators. Proofs created with v1 generators will not verify with v2. The DST is "bulletproofs-bn254-v2".
This library is designed for use in blockchain consensus, where:
- Proofs are publicly visible (no timing side-channels on verification)
- Provers may be adversarial (all inputs are validated)
- Proof data may be malformed (deserialization is hardened against DoS)
The library does NOT protect against:
- Side-channel attacks on the prover (the prover knows the secret value)
- Quantum adversaries (BN254 is not post-quantum secure)
go test ./... # unit tests (70 tests)
go test -fuzz=FuzzRangeProofUnmarshal -fuzztime=30s # fuzz deserialization
go test -bench=. -benchmem # benchmarksThe test suite includes:
- Correctness tests for all proof types and edge cases
- Adversarial tests: corrupted proof points, scalar fields, swapped fields, transplanted proofs
- Input validation tests: nil pointers, identity points, off-curve points, zero blindings
- Serialization tests: truncation at every offset, random garbage, excessive rounds
- Fuzz tests for all
Unmarshalfunctions - Concurrent generator cache access test
See SECURITY.md for vulnerability reporting.