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.

This commit is contained in:
SimplifiedPrivacy 2026-08-22 20:48:22 -04:00
parent a69b87817c
commit 39efcaa174
8 changed files with 192 additions and 8 deletions

6
.gitignore vendored
View file

@ -4,5 +4,11 @@ prototype_client.py
.idea .idea
.venv .venv
__pycache__ __pycache__
__pycache__/
dist dist
.mypy_cache .mypy_cache
*.c
*.so
*.o
*.egg-info/
core/services/crypto/cython/build

View file

@ -1,5 +1,10 @@
# Major Change Log: # 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.
<br/>
# Vless Introduced # Vless Introduced
### Aug 20, 2026 ### 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 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

View file

@ -15,13 +15,19 @@ from py_ecc.optimized_bls12_381 import (
) )
from py_ecc.optimized_bls12_381.optimized_curve import FQ, FQ2 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: # errors:
from core.errors.logger import logger from core.errors.logger import logger
import traceback 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: class TicketCustomer:
""" """
@ -206,6 +212,7 @@ class TicketCustomer:
blinded_signature = self._deserialize_point_g2(blind_signature) blinded_signature = self._deserialize_point_g2(blind_signature)
blinded_commitment_as_dict = get_data(which_ticket, "blinded_commitment_json") blinded_commitment_as_dict = get_data(which_ticket, "blinded_commitment_json")
blinded_commitment = self._deserialize_point_g2(blinded_commitment_as_dict) blinded_commitment = self._deserialize_point_g2(blinded_commitment_as_dict)
projective_public_key = self.load_key(string_public_key) projective_public_key = self.load_key(string_public_key)
@ -214,6 +221,7 @@ class TicketCustomer:
return {"valid": False, "message": "invalid_key"} return {"valid": False, "message": "invalid_key"}
# All of that was to prep the values for this pairing equation, # All of that was to prep the values for this pairing equation,
# Which now uses Cython
try: try:
if pairing(blinded_signature, G1) == pairing( if pairing(blinded_signature, G1) == pairing(
blinded_commitment, projective_public_key blinded_commitment, projective_public_key

View file

@ -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
```
<br/>
# Setup Compile
```bash
python3 setup.py build_ext --inplace
```
<br/>
# If any issues
Try the exact path to your venv.
```bash
path/to/your/venv/python3 setup.py build_ext --inplace
```

View file

@ -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)

View file

@ -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)
)

View file

@ -25,7 +25,7 @@ def make_ONE_commitment_pair(
# Then BLIND it, so it can be sent to the billing server: # Then BLIND it, so it can be sent to the billing server:
blind_commitment = profile_object.blind_commitment(which_ticket) 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, # we need to make sure we actually saved the data,
# because it's the only way to unblind it later: # because it's the only way to unblind it later:

View file

@ -134,14 +134,10 @@ def validate_blind_signatures(
which_ticket, blind_signature, public_key which_ticket, blind_signature, public_key
) )
# bump loop for next round:
which_ticket = which_ticket + 1
if validity_data["valid"] == True: if validity_data["valid"] == True:
# verification went fine: # verification went fine:
notification = f"Verified {which_ticket}'s blind signature" notification = f"Verified {which_ticket}'s blind signature"
ticket_observer.notify("preparing", subject=notification) ticket_observer.notify("preparing", subject=notification)
continue
else: else:
# is it an invalid pub key? # is it an invalid pub key?
if validity_data["message"] == "invalid_key": if validity_data["message"] == "invalid_key":
@ -154,6 +150,9 @@ def validate_blind_signatures(
notification = f"Invalid {which_ticket} blind signature" notification = f"Invalid {which_ticket} blind signature"
ticket_observer.notify("preparing", subject=notification) ticket_observer.notify("preparing", subject=notification)
# bump loop for next round:
which_ticket = which_ticket + 1
### LOOP DONE ### LOOP DONE
logger.debug( logger.debug(
f"Exiting the validation loop with the list_of_failed_verifications: {list_of_failed_verifications}" f"Exiting the validation loop with the list_of_failed_verifications: {list_of_failed_verifications}"