From 39efcaa174c44e172edd865bc2fcd817a5cb740d Mon Sep 17 00:00:00 2001 From: SimplifiedPrivacy Date: Sat, 22 Aug 2026 20:48:22 -0400 Subject: [PATCH] Cython for Miller's Loop Cryptography first introduced, which dramatically reduces verification times. Includes a setup.py option for manual builds if desired. README for manual compile if desired. And a fallback import using the legacy python-only system. --- .gitignore | 6 + change_log.md | 5 + core/services/crypto/TicketCustomer.py | 14 +- core/services/crypto/cython/README.md | 22 +++ .../crypto/cython/bls12_381_pairing.pyx | 125 ++++++++++++++++++ core/services/crypto/cython/setup.py | 19 +++ core/services/crypto/make_commitments.py | 2 +- .../validate_blind_signatures.py | 7 +- 8 files changed, 192 insertions(+), 8 deletions(-) create mode 100644 core/services/crypto/cython/README.md create mode 100644 core/services/crypto/cython/bls12_381_pairing.pyx create mode 100644 core/services/crypto/cython/setup.py diff --git a/.gitignore b/.gitignore index 8a72b39..e94b46c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,11 @@ prototype_client.py .idea .venv __pycache__ +__pycache__/ dist .mypy_cache +*.c +*.so +*.o +*.egg-info/ +core/services/crypto/cython/build diff --git a/change_log.md b/change_log.md index ef0b8a4..f6f22d2 100644 --- a/change_log.md +++ b/change_log.md @@ -1,5 +1,10 @@ # Major Change Log: +# Cython Introduced +### Aug 22, 2026 +Cython for Miller's Loop Cryptography first introduced, which dramatically reduces verification times. Includes a setup.py option for manual builds if desired. README for manual compile if desired. And a fallback import using the legacy python-only system. +
+ # Vless Introduced ### Aug 20, 2026 Vless is now working. Config Parsing and setup is stable, and increased the wait time for connection testing. But this is a temporary solution. The real answer is not a static time check, but a dynamic result reading for when to test. Which then has it's own timeout time diff --git a/core/services/crypto/TicketCustomer.py b/core/services/crypto/TicketCustomer.py index 6e78ae2..e08d2e3 100644 --- a/core/services/crypto/TicketCustomer.py +++ b/core/services/crypto/TicketCustomer.py @@ -15,13 +15,19 @@ from py_ecc.optimized_bls12_381 import ( ) from py_ecc.optimized_bls12_381.optimized_curve import FQ, FQ2 -# for the (optional) validity tests: -from py_ecc.optimized_bls12_381 import pairing - # errors: from core.errors.logger import logger import traceback +# for the (optional) validity tests: +try: + from core.services.crypto.cython.bls12_381_pairing import pairing + logger.info("Imported Cython pairings") +except ImportError: + # Fallback to py_ecc if not compiled + from py_ecc.optimized_bls12_381 import pairing + logger.error("FAILED to import cython pairings, used python py_ecc") + class TicketCustomer: """ @@ -206,6 +212,7 @@ class TicketCustomer: blinded_signature = self._deserialize_point_g2(blind_signature) blinded_commitment_as_dict = get_data(which_ticket, "blinded_commitment_json") + blinded_commitment = self._deserialize_point_g2(blinded_commitment_as_dict) projective_public_key = self.load_key(string_public_key) @@ -214,6 +221,7 @@ class TicketCustomer: return {"valid": False, "message": "invalid_key"} # All of that was to prep the values for this pairing equation, + # Which now uses Cython try: if pairing(blinded_signature, G1) == pairing( blinded_commitment, projective_public_key diff --git a/core/services/crypto/cython/README.md b/core/services/crypto/cython/README.md new file mode 100644 index 0000000..ce75071 --- /dev/null +++ b/core/services/crypto/cython/README.md @@ -0,0 +1,22 @@ +# Overview +We use Cython to compile certain cryptographic operations to C, to make it faster. This is the situation with miller's loop from py_ecc. + +# Pre-reqs: +In the same venv as the project itself: +```bash +pip install cython +pip install setuptools +``` +
+ +# Setup Compile +```bash +python3 setup.py build_ext --inplace +``` +
+ +# If any issues +Try the exact path to your venv. +```bash +path/to/your/venv/python3 setup.py build_ext --inplace +``` diff --git a/core/services/crypto/cython/bls12_381_pairing.pyx b/core/services/crypto/cython/bls12_381_pairing.pyx new file mode 100644 index 0000000..5f18fd0 --- /dev/null +++ b/core/services/crypto/cython/bls12_381_pairing.pyx @@ -0,0 +1,125 @@ +from py_ecc.fields import ( + bls12_381_FQ as FQ, + bls12_381_FQ2 as FQ2, + bls12_381_FQ12 as FQ12 +) +from py_ecc.fields.field_properties import ( + field_properties, +) +from py_ecc.typing import ( + Field, + Point2D, +) + +from py_ecc.bls12_381 import ( + G1, + add, + b, + b2, + curve_order, + double, + is_on_curve, + multiply, + twist, +) + +field_modulus = field_properties["bls12_381"]["field_modulus"] + +ate_loop_count = 15132376222941642752 +log_ate_loop_count = 62 + +from typing import Optional, Tuple, Union + +from py_ecc.fields import bls12_381_FQ as FQ_py_ecc +from py_ecc.utils import prime_field_inv + +def linefunc( + P1: Tuple[Field, Field], + P2: Tuple[Field, Field], + T: Tuple[Field, Field] +) -> Field: + """ + Create a function representing the line between P1 and P2, + and evaluate it at T + """ + if P1 is None or P2 is None or T is None: + raise ValueError("Invalid input - no points-at-infinity allowed") + + x1, y1 = P1 + x2, y2 = P2 + xt, yt = T + + if x1 != x2: + m = (y2 - y1) / (x2 - x1) + return m * (xt - x1) - (yt - y1) + elif y1 == y2: + m = 3 * x1**2 / (2 * y1) + return m * (xt - x1) - (yt - y1) + else: + return xt - x1 + + +def cast_point_to_fq12( + pt: Optional[Tuple[FQ, FQ]] +) -> Optional[Tuple[FQ12, FQ12]]: + if pt is None: + return None + + x, y = pt + return (FQ12([x.n] + [0] * 11), FQ12([y.n] + [0] * 11)) + + +# Check consistency of the "line function" +one = G1 +two = double(G1) +three = multiply(G1, 3) +negone = multiply(G1, curve_order - 1) +negtwo = multiply(G1, curve_order - 2) +negthree = multiply(G1, curve_order - 3) + +conditions = [ + linefunc(one, two, one) == FQ(0), + linefunc(one, two, two) == FQ(0), + linefunc(one, two, three) != FQ(0), + linefunc(one, two, negthree) == FQ(0), + linefunc(one, negone, one) == FQ(0), + linefunc(one, negone, negone) == FQ(0), + linefunc(one, negone, two) != FQ(0), + linefunc(one, one, one) == FQ(0), + linefunc(one, one, two) != FQ(0), + linefunc(one, one, negtwo) == FQ(0), +] + +if not all(conditions): + raise ValueError("Line function is inconsistent") + + +def miller_loop(Q: Point2D[FQ12], P: Point2D[FQ12]) -> FQ12: + cdef int i + + if Q is None or P is None: + return FQ12.one() + + R = Q + f = FQ12.one() + + for i in range(log_ate_loop_count, -1, -1): + f = f * f * linefunc(R, R, P) + R = double(R) + if ate_loop_count & (2**i): + f = f * linefunc(R, Q, P) + R = add(R, Q) + + return f ** ((field_modulus**12 - 1) // curve_order) + + +def pairing(Q: Point2D[FQ2], P: Point2D[FQ]) -> FQ12: + if not is_on_curve(Q, b2): + raise ValueError("Invalid input - point Q is not on the correct curve") + if not is_on_curve(P, b): + raise ValueError("Invalid input - point P is not on the correct curves") + return miller_loop(twist(Q), cast_point_to_fq12(P)) + + +def final_exponentiate(p: FQ12) -> FQ12: + return p ** ((field_modulus**12 - 1) // curve_order) diff --git a/core/services/crypto/cython/setup.py b/core/services/crypto/cython/setup.py new file mode 100644 index 0000000..820e021 --- /dev/null +++ b/core/services/crypto/cython/setup.py @@ -0,0 +1,19 @@ + +""" +This is the Manual Setup for Cython extensions. + +Compile with: +pip install cython setuptools +python3 setup.py build_ext --inplace + +& Put the path to venv prior to the python3 if any issues. +path/to/venv/python3 setup.py build_ext --inplace +""" + +from Cython.Build import cythonize +from setuptools import setup + +setup( + ext_modules=cythonize("bls12_381_pairing.pyx", language_level=3) +) + diff --git a/core/services/crypto/make_commitments.py b/core/services/crypto/make_commitments.py index d92e2ca..e6e3ba8 100644 --- a/core/services/crypto/make_commitments.py +++ b/core/services/crypto/make_commitments.py @@ -25,7 +25,7 @@ def make_ONE_commitment_pair( # Then BLIND it, so it can be sent to the billing server: blind_commitment = profile_object.blind_commitment(which_ticket) - logger.info(f"blind_commitment is {blind_commitment}") + # logger.info(f"blind_commitment is {blind_commitment}") # we need to make sure we actually saved the data, # because it's the only way to unblind it later: diff --git a/core/services/prepare_tickets/validate_blind_signatures.py b/core/services/prepare_tickets/validate_blind_signatures.py index eb1494b..2fa00e7 100644 --- a/core/services/prepare_tickets/validate_blind_signatures.py +++ b/core/services/prepare_tickets/validate_blind_signatures.py @@ -134,14 +134,10 @@ def validate_blind_signatures( which_ticket, blind_signature, public_key ) - # bump loop for next round: - which_ticket = which_ticket + 1 - if validity_data["valid"] == True: # verification went fine: notification = f"Verified {which_ticket}'s blind signature" ticket_observer.notify("preparing", subject=notification) - continue else: # is it an invalid pub key? if validity_data["message"] == "invalid_key": @@ -154,6 +150,9 @@ def validate_blind_signatures( notification = f"Invalid {which_ticket} blind signature" ticket_observer.notify("preparing", subject=notification) + # bump loop for next round: + which_ticket = which_ticket + 1 + ### LOOP DONE logger.debug( f"Exiting the validation loop with the list_of_failed_verifications: {list_of_failed_verifications}"