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

Not on PyPI yet. For now, install from a checkout:

pip install -e .

Python 3.9 or newer. No compiled extensions, no C library to find at build time.

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.

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.

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). 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. 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.1.tar.gz (17.0 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.1-py3-none-any.whl (12.2 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for sigma_rangeproof-0.1.1.tar.gz
Algorithm Hash digest
SHA256 72743de52c04a5a6d1f8ca94ca60665f42090c5d2349a0bf2b89dc0beff79746
MD5 fb96ee12113762a9e87c2e04e79d210c
BLAKE2b-256 02f6fc799cca2c4011e5d8012d7d93bf4fc443bf579d49a425e73e4091da52c1

See more details on using hashes here.

Provenance

The following attestation bundles were made for sigma_rangeproof-0.1.1.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.1-py3-none-any.whl.

File metadata

File hashes

Hashes for sigma_rangeproof-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 dbac8453c5f31687b143478cb3249c3247327ad2cd780329c8e43ab3a6a76da5
MD5 cbb81e719e7810f1c6e9be4a983ff518
BLAKE2b-256 40dbdd57465b5d2389781fb0898396a7f4faec4720505554f97674a5681ce30e

See more details on using hashes here.

Provenance

The following attestation bundles were made for sigma_rangeproof-0.1.1-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