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}"