Skip to main content

Dependency-free zero-knowledge range proofs (prove value >= threshold over a Pedersen commitment) via a Sigma-protocol bit decomposition.

Project description

sigma-rangeproof

sigma-rangeproof

Prove that a number you committed to is at least some threshold, without saying what the number is. The whole thing is pure Python with nothing outside the standard library.

A typical use: you hold a score and you publish a commitment to it. Later you want to convince someone the score clears a bar (say 700 out of 1000) but you don't want to hand over the score itself, or anything that would let them work it out. This library produces a proof of exactly that statement and nothing more.

from sigma_rangeproof import commit, prove_ge, verify_ge

commitment, blinding = commit(740)            # publish commitment, keep blinding
proof = prove_ge(740, blinding, 700, bits=10) # "the committed value is >= 700"

verify_ge(commitment, 700, proof)   # True
verify_ge(commitment, 720, proof)   # False: a proof for >=700 is not one for >=720

The verifier learns one bit of information: whether the claim holds. The score, and how far above or below the bar it sits, stay hidden.

Install

pip install sigma-rangeproof

Python 3.9 or newer. No compiled extensions, no C library to find at build time, and no runtime dependencies. To work from a checkout instead:

pip install -e .

The three calls

commit(value, blinding=None) returns (commitment, blinding). If you don't pass a blinding factor one is drawn at random. Keep it next to the value; you need both to produce a proof later. The commitment is safe to publish.

prove_ge(value, blinding, threshold, *, bits=32) returns a RangeProof. It proves value >= threshold. The catch worth knowing up front: the proof only covers the window [threshold, threshold + 2**bits). If value - threshold falls outside [0, 2**bits) the call raises ValueError, because a value below the threshold has no valid proof and the library will not pretend otherwise.

verify_ge(commitment, threshold, proof) returns a bool. It recomputes everything it needs from the public commitment and the threshold you pass, so a proof made for one threshold will not check out against a different one.

A RangeProof serialises with to_dict() and rebuilds with RangeProof.from_dict(...). The dictionary holds hex strings, so it drops straight into JSON.

Choosing bits

bits sets the width of the range you can prove and, with it, the size and cost of the proof. Both grow linearly in bits. Pick the smallest width that covers your values:

  • a percentage or a 0-1000 score fits in bits=10 (covers 0 through 1023)
  • a 16-bit counter fits in bits=16
  • leave the default bits=32 if you have no reason to narrow it

The prover and verifier have to agree on bits; it travels inside the proof, so the verifier reads it from there. The width is capped at MAX_BITS (64); a proof claiming more is rejected before any arithmetic runs.

Speed, honestly

The work is bits modular exponentiations on each side, over a 2048-bit modulus. On a normally built CPython that lands in the low tens of milliseconds for bits=10. One trap to flag: an x86 Python running under Rosetta on Apple silicon does big-integer math something like a hundred times slower, so the same call can take seconds there. Build and run natively before you judge the numbers.

How it works, in one paragraph

The commitment is a Pedersen commitment, C = g^v * h^r, over the prime-order subgroup of a 2048-bit safe prime. To show v >= T, note the verifier can form C / g^T = g^(v-T) * h^r by itself. Write w = v - T in binary, commit to each bit, and arrange the bit blindings so the bit commitments multiply back to C / g^T. Then attach, for every bit, a short proof that it is a commitment to 0 or to 1 (a Schnorr OR-proof made non-interactive with Fiat-Shamir). If each of the bits bits is genuinely 0 or 1, then w lies in [0, 2**bits), which is the range claim. Security rests on the discrete logarithm assumption and the random-oracle heuristic; there is no trusted setup.

The longer version, with the math worked out and the code walked through line by line, is in docs/. There is also a runnable notebook at examples/demo.ipynb.

Why not Bulletproofs

Bulletproofs prove the same kind of statement with a proof that grows like log(bits) instead of bits, and several proofs can be folded into one. That matters when you are proving 64-bit amounts or batching thousands of proofs. None of the maintained Bulletproofs code targets Python, and the inner-product argument behind it is genuinely fiddly to get right. For a ten-bit score the size gap is a few dozen group elements, so the simpler construction here is the better trade. If your ranges or volumes grow, reach for a Bulletproofs library in Rust — the related-work page surveys the Python ZK options (zkbp, zksk, py_ecc, pysnark, ezkl, …) and when to pick each.

A word on trust

The construction is textbook and the test suite is adversarial: alongside the happy path it checks that out-of-range values cannot be proved, that proofs do not transfer across thresholds or commitments, that bit positions cannot be reordered, that out-of-subgroup elements are rejected, and that proofs are non-malleable (a scalar shifted by the group order no longer verifies). The verifier also treats every incoming proof as hostile until checked: it bounds its own work before touching the algebra, rejecting a proof that declares more than MAX_BITS (64) bit positions or a threshold outside [0, q), and deserialization refuses hex fields wider than the canonical encoding. On top of the fixed cases there is a property-based fuzz suite (Hypothesis) that asserts the same invariants over thousands of randomized inputs; run it hard with HYPOTHESIS_PROFILE=ci pytest tests/test_fuzz.py. What it has not had is an external audit by a third party — the review so far, including a static self-review, is recorded under audits/. Read it before you put real secrets behind it. It is short on purpose, partly so you can.

License

MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sigma_rangeproof-0.1.2.tar.gz (19.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sigma_rangeproof-0.1.2-py3-none-any.whl (14.2 kB view details)

Uploaded Python 3

File details

Details for the file sigma_rangeproof-0.1.2.tar.gz.

File metadata

  • Download URL: sigma_rangeproof-0.1.2.tar.gz
  • Upload date:
  • Size: 19.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for sigma_rangeproof-0.1.2.tar.gz
Algorithm Hash digest
SHA256 d1cd598fdb9c5f3a43837e63bed505e3a69613366c3d93f9d92451e2ba36d166
MD5 7b50241006dee8443830bf272fc20a1f
BLAKE2b-256 c9b64d6f76c2120f494f34ebd09699372ff8023b056f98b43d32d77c550b7456

See more details on using hashes here.

Provenance

The following attestation bundles were made for sigma_rangeproof-0.1.2.tar.gz:

Publisher: release.yml on boyroywax/sigma-rangeproof

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sigma_rangeproof-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for sigma_rangeproof-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 791eaffd5fe9d078d4d376b7b21273a7c14049c7aae8e4a90b04a634cb12bd95
MD5 e86d415b4f633d52be5ec570e56d65d5
BLAKE2b-256 bf7e11fd7d80a52916d54d0742911f73bd5cfe19c22850ab0addf50d655b4fdd

See more details on using hashes here.

Provenance

The following attestation bundles were made for sigma_rangeproof-0.1.2-py3-none-any.whl:

Publisher: release.yml on boyroywax/sigma-rangeproof

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page