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:
parent
a69b87817c
commit
39efcaa174
8 changed files with 192 additions and 8 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -4,5 +4,11 @@ prototype_client.py
|
|||
.idea
|
||||
.venv
|
||||
__pycache__
|
||||
__pycache__/
|
||||
dist
|
||||
.mypy_cache
|
||||
*.c
|
||||
*.so
|
||||
*.o
|
||||
*.egg-info/
|
||||
core/services/crypto/cython/build
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
<br/>
|
||||
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
22
core/services/crypto/cython/README.md
Normal file
22
core/services/crypto/cython/README.md
Normal 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
|
||||
```
|
||||
125
core/services/crypto/cython/bls12_381_pairing.pyx
Normal file
125
core/services/crypto/cython/bls12_381_pairing.pyx
Normal 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)
|
||||
19
core/services/crypto/cython/setup.py
Normal file
19
core/services/crypto/cython/setup.py
Normal 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)
|
||||
)
|
||||
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
|
|
|
|||
Loading…
Reference in a new issue