diff --git a/.gitignore b/.gitignore index 7aedf487..cc95aaa7 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,9 @@ chains .cache .pytest_cache +# mypy +.mypy_cache/ + # Test output logs logs ### JetBrains template diff --git a/py_ecc/BaseCurve.py b/py_ecc/BaseCurve.py new file mode 100644 index 00000000..d5582714 --- /dev/null +++ b/py_ecc/BaseCurve.py @@ -0,0 +1,422 @@ +from abc import ( + abstractmethod, +) + +from typing import ( # noqa: F401 + cast, + List, +) + +from py_ecc.field_elements import ( # noqa: F401 + FQ, + FQP, + FQ12, +) + +from py_ecc.optimized_field_elements import ( # noqa: F401 + FQ as optimized_FQ, + FQP as optimized_FQP, + FQ12 as optimized_FQ12, +) + +from py_ecc.typing import ( + Field, + FQPoint2D, + FQ2Point2D, + FQ12Point2D, + GeneralPoint, + Optimized_Field, + Optimized_FQPoint3D, + Optimized_FQ2Point3D, + Optimized_Point2D, + Optimized_Point3D, + Point2D, +) + + +class BaseCurve: + # Name of the curve can be "bn128" or "bls12_381" + curve_name = None # type: str + curve_order = None # type: int + field_modulus = None # type: int + # Curve is y**2 = x**3 + b + b = None # type: FQ + # Twisted curve over FQ**2 + b2 = None # type: FQP + # Extension curve over FQ**12; same b value as over FQ + b12 = None # type: FQP + # Generator for curve over FQ + G1 = None # type: Point2D[FQ] + # Generator for twisted curve over FQ2 + G2 = None # type: Point2D[FQP] + # Generator for twisted curve over FQ12 + G12 = None # type: Point2D[FQP] + # Point at infinity over FQ + Z1 = None + # Point at infinity for twisted curve over FQ2 + Z2 = None + ate_loop_count = None # type: int + log_ate_loop_count = None # type: int + pseudo_binary_encoding = None # type: List[int] + + def __init__(self) -> None: + self.G12 = self.twist(self.G2) + + @staticmethod + def is_inf(pt: GeneralPoint[Field]) -> bool: + """ + Check if a point is the point at infinity + """ + return pt is None + + @classmethod + def is_on_curve(cls, pt: Point2D[Field], b: Field) -> bool: + """ + Check that a point is on the curve + """ + if cls.is_inf(pt): + return True + x, y = pt + return y**2 == x**3 + b + + @staticmethod + def double(pt: Point2D[Field]) -> Point2D[Field]: + """ + Elliptic Curve Doubling (P+P). + """ + x, y = pt + m = (3 * x**2) / (2 * y) + newx = m**2 - 2 * x + newy = -m * newx + m * x - y + return (newx, newy) + + @classmethod + def add(cls, + p1: Point2D[Field], + p2: Point2D[Field]) -> Point2D[Field]: + """ + Elliptic curve addition. + """ + if p1 is None or p2 is None: + return p1 if p2 is None else p2 + x1, y1 = p1 + x2, y2 = p2 + if x2 == x1 and y2 == y1: + return cls.double(p1) + elif x2 == x1: + return None + else: + m = (y2 - y1) / (x2 - x1) + newx = m**2 - x1 - x2 + newy = -m * newx + m * x1 - y1 + + return (newx, newy) + + @classmethod + def multiply(cls, pt: Point2D[Field], n: int) -> Point2D[Field]: + """ + Elliptic curve point multiplication. + """ + if n == 0: + return None + elif n == 1: + return pt + elif not n % 2: + return cls.multiply(cls.double(pt), n // 2) + else: + return cls.add(cls.multiply(cls.double(pt), n // 2), pt) + + @staticmethod + def eq(p1: GeneralPoint[Field], p2: GeneralPoint[Field]) -> bool: + """ + Check if 2 points are equal. + """ + return p1 == p2 + + @staticmethod + def neg(pt: Point2D[Field]) -> Point2D[Field]: + """ + Gives the reflection of point wrt x-axis (P => -P). + """ + if pt is None: + return None + x, y = pt + return (x, -y) + + @staticmethod + @abstractmethod + def twist(pt: Point2D[FQP]) -> Point2D[FQP]: + """ + 'Twist' a point in E(FQ2) into a point in E(FQ12) + """ + raise NotImplementedError("Must be implemented by subclasses") + + # Pairing Related Functionalities + @classmethod + def cast_point_to_fq12(cls, pt: FQPoint2D) -> FQ12Point2D: + if pt is None: + return None + x, y = pt + fq12_point = ( + FQ12([x.n] + [0] * 11, cls.curve_name), + FQ12([y.n] + [0] * 11, cls.curve_name), + ) + return cast(FQ12Point2D, fq12_point) + + @staticmethod + def linefunc(P1: Point2D[Field], + P2: Point2D[Field], + T: Point2D[Field]) -> Field: + """ + Create a function representing the line between P1 and P2, + and evaluate it at T + """ + if not P1 or not P2 or not T: + raise ValueError("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 + + @classmethod + @abstractmethod + def miller_loop(cls, Q: Point2D[Field], P: Point2D[Field]) -> FQP: + raise NotImplementedError("Must be implemented by subclasses") + + @classmethod + def pairing(cls, Q: FQ2Point2D, P: FQPoint2D) -> FQP: + if not cls.is_on_curve(Q, cls.b2): + raise ValueError("Point Q is not on the curve defined by b2") + if not cls.is_on_curve(P, cls.b): + raise ValueError("Point P is not on the curve defined by b") + + return cls.miller_loop(cls.twist(Q), cls.cast_point_to_fq12(P)) + + +class BaseOptimizedCurve: + # Name of the curve can be "bn128" or "bls12_381" + curve_name = None # type: str + curve_order = None # type: int + field_modulus = None # type: int + # Curve is y**2 = x**3 + b + b = None # type: optimized_FQ + # Twisted curve over FQ**2 + b2 = None # type: optimized_FQP + # Extension curve over FQ**12; same b value as over FQ + b12 = None # type: optimized_FQP + # Generator for curve over FQ + G1 = None # type: Optimized_Point3D[optimized_FQ] + # Generator for twisted curve over FQ2 + G2 = None # type: Optimized_Point3D[optimized_FQP] + # Generator for curve over FQ12 + G12 = None # type: Optimized_Point3D[optimized_FQP] + # Point at infinity over FQ + Z1 = None # type: Optimized_Point3D[optimized_FQ] + # Point at infinity for twisted curve over FQ2 + Z2 = None # type: Optimized_Point3D[optimized_FQP] + ate_loop_count = None # type: int + log_ate_loop_count = None # type: int + pseudo_binary_encoding = None # type: List[int] + + def __init__(self) -> None: + self.G12 = self.twist(self.G2) + + @classmethod + def is_inf(cls, pt: Optimized_Point3D[Optimized_Field]) -> bool: + """ + Check if a point is the point at infinity + """ + return pt[-1] == (type(pt[-1]).zero(cls.curve_name)) + + @classmethod + def is_on_curve(cls, pt: Optimized_Point3D[Optimized_Field], b: Optimized_Field) -> bool: + """ + Check that a point is on the curve defined by y**2 == x**3 + b + """ + if cls.is_inf(pt): + return True + x, y, z = pt + return y**2 * z == x**3 + (b * z**3) + + @staticmethod + def double(pt: Optimized_Point3D[Optimized_Field]) -> Optimized_Point3D[Optimized_Field]: + """ + Elliptic curve doubling + """ + x, y, z = pt + W = 3 * x * x + S = y * z + B = x * y * S + H = W * W - 8 * B + S_squared = S * S + newx = 2 * H * S + newy = W * (4 * B - H) - 8 * y * y * S_squared + newz = 8 * S * S_squared + return (newx, newy, newz) + + @classmethod + def add(cls, + p1: Optimized_Point3D[Optimized_Field], + p2: Optimized_Point3D[Optimized_Field]) -> Optimized_Point3D[Optimized_Field]: + """ + Elliptic curve addition + """ + one, zero = type(p1[0]).one(cls.curve_name), type(p1[0]).zero(cls.curve_name) + if p1[2] == zero or p2[2] == zero: + return p1 if p2[2] == zero else p2 + x1, y1, z1 = p1 + x2, y2, z2 = p2 + U1 = y2 * z1 + U2 = y1 * z2 + V1 = x2 * z1 + V2 = x1 * z2 + if V1 == V2 and U1 == U2: + return cls.double(p1) + elif V1 == V2: + return (one, one, zero) + U = U1 - U2 + V = V1 - V2 + V_squared = V * V + V_squared_times_V2 = V_squared * V2 + V_cubed = V * V_squared + W = z1 * z2 + A = U * U * W - V_cubed - 2 * V_squared_times_V2 + newx = V * A + newy = U * (V_squared_times_V2 - A) - V_cubed * U2 + newz = V_cubed * W + return (newx, newy, newz) + + @classmethod + def multiply(cls, + pt: Optimized_Point3D[Optimized_Field], + n: int) -> Optimized_Point3D[Optimized_Field]: + """ + Elliptic curve point multiplication + """ + if n == 0: + return ( + type(pt[0]).one(cls.curve_name), + type(pt[0]).one(cls.curve_name), + type(pt[0]).zero(cls.curve_name) + ) + elif n == 1: + return pt + elif not n % 2: + return cls.multiply(cls.double(pt), n // 2) + else: + return cls.add(cls.multiply(cls.double(pt), int(n // 2)), pt) + + @staticmethod + def eq(p1: Optimized_Point3D[Optimized_Field], + p2: Optimized_Point3D[Optimized_Field]) -> bool: + """ + Check if 2 points are equal. + """ + x1, y1, z1 = p1 + x2, y2, z2 = p2 + return x1 * z2 == x2 * z1 and y1 * z2 == y2 * z1 + + @staticmethod + def normalize(pt: Optimized_Point3D[Optimized_Field]) -> Optimized_Point2D[Optimized_Field]: + """ + Convert the Jacobian Point to a normal point + """ + x, y, z = pt + return (x / z, y / z) + + @staticmethod + def neg(pt: Optimized_Point3D[Optimized_Field]) -> Optimized_Point3D[Optimized_Field]: + """ + Gives the reflection of point wrt x-axis (P => -P). + """ + if pt is None: + return None + x, y, z = pt + return (x, -y, z) + + @staticmethod + @abstractmethod + def twist(pt: Optimized_Point3D[optimized_FQP]) -> Optimized_Point3D[optimized_FQP]: + """ + 'Twist' a point in E(FQ2) into a point in E(FQ12) + """ + raise NotImplementedError("Must be implemented by subclasses") + + # Pairing Related Functionalities + @classmethod + def cast_point_to_fq12( + cls, + pt: Optimized_Point3D[optimized_FQ]) -> Optimized_Point3D[optimized_FQ12]: + if pt is None: + return None + x, y, z = pt + return ( + optimized_FQ12([x.n] + [0] * 11, cls.curve_name), + optimized_FQ12([y.n] + [0] * 11, cls.curve_name), + optimized_FQ12([z.n] + [0] * 11, cls.curve_name), + ) + + @classmethod + def linefunc(cls, + P1: Optimized_Point3D[Optimized_Field], + P2: Optimized_Point3D[Optimized_Field], + T: Optimized_Point3D[Optimized_Field]) -> Optimized_Point2D[Optimized_Field]: + """ + Create a function representing the line between P1 and P2, + and evaluate it at T. + Returns a numerator and a denominator to avoid unneeded divisions + """ + zero = type(P1[0]).zero(cls.curve_name) + x1, y1, z1 = P1 + x2, y2, z2 = P2 + xt, yt, zt = T + # points in projective coords: (x / z, y / z) + # hence, m = (y2/z2 - y1/z1) / (x2/z2 - x1/z1) + # multiply numerator and denominator by z1z2 to get values below + m_numerator = y2 * z1 - y1 * z2 + m_denominator = x2 * z1 - x1 * z2 + if m_denominator != zero: + # m * ((xt/zt) - (x1/z1)) - ((yt/zt) - (y1/z1)) + return m_numerator * (xt * z1 - x1 * zt) - m_denominator * (yt * z1 - y1 * zt), \ + m_denominator * zt * z1 + elif m_numerator == zero: + # m = 3(x/z)^2 / 2(y/z), multiply num and den by z**2 + m_numerator = 3 * x1 * x1 + m_denominator = 2 * y1 * z1 + return m_numerator * (xt * z1 - x1 * zt) - m_denominator * (yt * z1 - y1 * zt), \ + m_denominator * zt * z1 + else: + return xt * z1 - x1 * zt, z1 * zt + + @classmethod + @abstractmethod + def miller_loop(cls, + Q: Optimized_Point3D[optimized_FQP], + P: Optimized_Point3D[optimized_FQP], + final_exponentiate: bool=True) -> optimized_FQP: + raise NotImplementedError("Must be implemented by subclasses") + + @classmethod + def pairing(cls, + Q: Optimized_FQ2Point3D, + P: Optimized_FQPoint3D, + final_exponentiate: bool=True) -> optimized_FQP: + if not cls.is_on_curve(Q, cls.b2): + raise ValueError("Point Q is not on the curve defined by b2") + if not cls.is_on_curve(P, cls.b): + raise ValueError("Point P is not on the curve defined by b") + + if P[-1] == (type(P[-1]).zero(cls.curve_name)) or Q[-1] == (type(Q[-1]).zero(cls.curve_name)): # noqa: E501 + return optimized_FQ12.one(cls.curve_name) + return cls.miller_loop( + cls.twist(Q), + cls.cast_point_to_fq12(P), + final_exponentiate=final_exponentiate, + ) diff --git a/py_ecc/__init__.py b/py_ecc/__init__.py index 544b95ea..df9e3897 100644 --- a/py_ecc/__init__.py +++ b/py_ecc/__init__.py @@ -1,11 +1,39 @@ import sys +from .bls12_381_curve import ( # noqa: F401 + bls12_381, + BLS12_381_Curve, + optimized_bls12_381, + Optimized_BLS12_381_Curve, +) -sys.setrecursionlimit(max(100000, sys.getrecursionlimit())) +from .bn128_curve import ( # noqa: F401 + bn128, + BN128_Curve, + optimized_bn128, + Optimized_BN128_Curve, +) + +from .field_elements import ( # noqa: F401 + FQ, + FQP, + FQ2, + FQ12, +) + +from .optimized_field_elements import ( # noqa: F401 + FQ as optimized_FQ, + FQP as optimized_FQP, + FQ2 as optimized_FQ2, + FQ12 as optimized_FQ12, +) + +from .secp256k1 import secp256k1 # noqa: F401 +from .validate_constants import validate_constants + + +sys.setrecursionlimit(max(100000, sys.getrecursionlimit())) -from py_ecc import secp256k1 # noqa: F401 -from py_ecc import bn128 # noqa: F401 -from py_ecc import optimized_bn128 # noqa: F401 -from py_ecc import bls12_381 # noqa: F401 -from py_ecc import optimized_bls12_381 # noqa: F401 +# Check all the constants are valid, before using them +validate_constants() diff --git a/py_ecc/bls12_381/__init__.py b/py_ecc/bls12_381/__init__.py deleted file mode 100644 index 3c062bd4..00000000 --- a/py_ecc/bls12_381/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -from __future__ import absolute_import - -from .bls12_381_field_elements import ( # noqa: F401 - field_modulus, - FQ, - FQP, - FQ2, - FQ12, -) -from .bls12_381_curve import ( # noqa: F401 - add, - double, - multiply, - is_inf, - is_on_curve, - eq, - neg, - twist, - b, - b2, - b12, - curve_order, - G1, - G2, - Z1, - Z2, - G12, -) -from .bls12_381_pairing import ( # noqa: F401 - pairing, - final_exponentiate, -) diff --git a/py_ecc/bls12_381/bls12_381_curve.py b/py_ecc/bls12_381/bls12_381_curve.py deleted file mode 100644 index 6b64c786..00000000 --- a/py_ecc/bls12_381/bls12_381_curve.py +++ /dev/null @@ -1,136 +0,0 @@ -from __future__ import absolute_import - -from .bls12_381_field_elements import ( - field_modulus, - FQ, - FQ2, - FQ12, -) - - -curve_order = 52435875175126190479447740508185965837690552500527637822603658699938581184513 - -# Curve order should be prime -assert pow(2, curve_order, curve_order) == 2 -# Curve order should be a factor of field_modulus**12 - 1 -assert (field_modulus ** 12 - 1) % curve_order == 0 - -# Curve is y**2 = x**3 + 4 -b = FQ(4) -# Twisted curve over FQ**2 -b2 = FQ2((4, 4)) -# Extension curve over FQ**12; same b value as over FQ -b12 = FQ12((4,) + (0,) * 11) - -# Generator for curve over FQ -G1 = ( - FQ(3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507), # noqa: E501 - FQ(1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569), # noqa: E501 -) -# Generator for twisted curve over FQ2 -G2 = ( - FQ2([ - 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160, # noqa: E501 - 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758, # noqa: E501 - ]), - FQ2([ - 1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905, # noqa: E501 - 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582, # noqa: E501 - ]), -) -# Point at infinity over FQ -Z1 = None -# Point at infinity for twisted curve over FQ2 -Z2 = None - - -# Check if a point is the point at infinity -def is_inf(pt): - return pt is None - - -# Check that a point is on the curve defined by y**2 == x**3 + b -def is_on_curve(pt, b): - if is_inf(pt): - return True - x, y = pt - return y**2 - x**3 == b - - -assert is_on_curve(G1, b) -assert is_on_curve(G2, b2) - - -# Elliptic curve doubling -def double(pt): - x, y = pt - m = 3 * x**2 / (2 * y) - newx = m**2 - 2 * x - newy = -m * newx + m * x - y - return newx, newy - - -# Elliptic curve addition -def add(p1, p2): - if p1 is None or p2 is None: - return p1 if p2 is None else p2 - x1, y1 = p1 - x2, y2 = p2 - if x2 == x1 and y2 == y1: - return double(p1) - elif x2 == x1: - return None - else: - m = (y2 - y1) / (x2 - x1) - newx = m**2 - x1 - x2 - newy = -m * newx + m * x1 - y1 - assert newy == (-m * newx + m * x2 - y2) - return (newx, newy) - - -# Elliptic curve point multiplication -def multiply(pt, n): - if n == 0: - return None - elif n == 1: - return pt - elif not n % 2: - return multiply(double(pt), n // 2) - else: - return add(multiply(double(pt), int(n // 2)), pt) - - -def eq(p1, p2): - return p1 == p2 - - -# "Twist" a point in E(FQ2) into a point in E(FQ12) -w = FQ12([0, 1] + [0] * 10) - - -# Convert P => -P -def neg(pt): - if pt is None: - return None - x, y = pt - return (x, -y) - - -def twist(pt): - if pt is None: - return None - _x, _y = pt - # Field isomorphism from Z[p] / x**2 to Z[p] / x**2 - 2*x + 2 - xcoeffs = [_x.coeffs[0] - _x.coeffs[1], _x.coeffs[1]] - ycoeffs = [_y.coeffs[0] - _y.coeffs[1], _y.coeffs[1]] - # Isomorphism into subfield of Z[p] / w**12 - 2 * w**6 + 2, - # where w**6 = x - nx = FQ12([xcoeffs[0]] + [0] * 5 + [xcoeffs[1]] + [0] * 5) - ny = FQ12([ycoeffs[0]] + [0] * 5 + [ycoeffs[1]] + [0] * 5) - # Divide x coord by w**2 and y coord by w**3 - return (nx / w ** 2, ny / w**3) - - -G12 = twist(G2) -# Check that the twist creates a point that is on the curve -assert is_on_curve(G12, b12) diff --git a/py_ecc/bls12_381/bls12_381_field_elements.py b/py_ecc/bls12_381/bls12_381_field_elements.py deleted file mode 100644 index c98d8fa4..00000000 --- a/py_ecc/bls12_381/bls12_381_field_elements.py +++ /dev/null @@ -1,247 +0,0 @@ -from __future__ import absolute_import - - -# The prime modulus of the field -field_modulus = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 # noqa: E501 -# See, it's prime! -assert pow(2, field_modulus, field_modulus) == 2 - -# The modulus of the polynomial in this representation of FQ12 -FQ12_modulus_coeffs = (2, 0, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0) # Implied + [1] - - -# Extended euclidean algorithm to find modular inverses for -# integers -def inv(a, n): - if a == 0: - return 0 - lm, hm = 1, 0 - low, high = a % n, n - while low > 1: - r = high // low - nm, new = hm - lm * r, high - low * r - lm, low, hm, high = nm, new, lm, low - return lm % n - - -# A class for field elements in FQ. Wrap a number in this class, -# and it becomes a field element. -class FQ(object): - def __init__(self, n): - if isinstance(n, self.__class__): - self.n = n.n - else: - self.n = n % field_modulus - assert isinstance(self.n, int) - - def __add__(self, other): - on = other.n if isinstance(other, FQ) else other - return FQ((self.n + on) % field_modulus) - - def __mul__(self, other): - on = other.n if isinstance(other, FQ) else other - return FQ((self.n * on) % field_modulus) - - def __rmul__(self, other): - return self * other - - def __radd__(self, other): - return self + other - - def __rsub__(self, other): - on = other.n if isinstance(other, FQ) else other - return FQ((on - self.n) % field_modulus) - - def __sub__(self, other): - on = other.n if isinstance(other, FQ) else other - return FQ((self.n - on) % field_modulus) - - def __div__(self, other): - on = other.n if isinstance(other, FQ) else other - assert isinstance(on, int) - return FQ(self.n * inv(on, field_modulus) % field_modulus) - - def __truediv__(self, other): - return self.__div__(other) - - def __rdiv__(self, other): - on = other.n if isinstance(other, FQ) else other - assert isinstance(on, int), on - return FQ(inv(self.n, field_modulus) * on % field_modulus) - - def __rtruediv__(self, other): - return self.__rdiv__(other) - - def __pow__(self, other): - if other == 0: - return FQ(1) - elif other == 1: - return FQ(self.n) - elif other % 2 == 0: - return (self * self) ** (other // 2) - else: - return ((self * self) ** int(other // 2)) * self - - def __eq__(self, other): - if isinstance(other, FQ): - return self.n == other.n - else: - return self.n == other - - def __ne__(self, other): - return not self == other - - def __neg__(self): - return FQ(-self.n) - - def __repr__(self): - return repr(self.n) - - @classmethod - def one(cls): - return cls(1) - - @classmethod - def zero(cls): - return cls(0) - - -# Utility methods for polynomial math -def deg(p): - d = len(p) - 1 - while p[d] == 0 and d: - d -= 1 - return d - - -def poly_rounded_div(a, b): - dega = deg(a) - degb = deg(b) - temp = [x for x in a] - o = [0 for x in a] - for i in range(dega - degb, -1, -1): - o[i] += temp[degb + i] / b[degb] - for c in range(degb + 1): - temp[c + i] -= o[c] - return tuple(o[:deg(o) + 1]) - - -int_types_or_FQ = (int, FQ) - - -# A class for elements in polynomial extension fields -class FQP(object): - def __init__(self, coeffs, modulus_coeffs): - assert len(coeffs) == len(modulus_coeffs) - self.coeffs = tuple(FQ(c) for c in coeffs) - # The coefficients of the modulus, without the leading [1] - self.modulus_coeffs = modulus_coeffs - # The degree of the extension field - self.degree = len(self.modulus_coeffs) - - def __add__(self, other): - assert isinstance(other, self.__class__) - return self.__class__(tuple(x + y for x, y in zip(self.coeffs, other.coeffs))) - - def __sub__(self, other): - assert isinstance(other, self.__class__) - return self.__class__(tuple(x - y for x, y in zip(self.coeffs, other.coeffs))) - - def __mul__(self, other): - if isinstance(other, int_types_or_FQ): - return self.__class__(tuple(c * other for c in self.coeffs)) - else: - assert isinstance(other, self.__class__) - b = [FQ(0) for i in range(self.degree * 2 - 1)] - for i in range(self.degree): - for j in range(self.degree): - b[i + j] += self.coeffs[i] * other.coeffs[j] - while len(b) > self.degree: - exp, top = len(b) - self.degree - 1, b.pop() - for i in range(self.degree): - b[exp + i] -= top * FQ(self.modulus_coeffs[i]) - return self.__class__(tuple(b)) - - def __rmul__(self, other): - return self * other - - def __div__(self, other): - if isinstance(other, int_types_or_FQ): - return self.__class__(tuple(c / other for c in self.coeffs)) - else: - assert isinstance(other, self.__class__) - return self * other.inv() - - def __truediv__(self, other): - return self.__div__(other) - - def __pow__(self, other): - if other == 0: - return self.__class__((1,) + (0,) * (self.degree - 1)) - elif other == 1: - return self.__class__(self.coeffs) - elif other % 2 == 0: - return (self * self) ** (other // 2) - else: - return ((self * self) ** int(other // 2)) * self - - # Extended euclidean algorithm used to find the modular inverse - def inv(self): - lm, hm = [1] + [0] * self.degree, [0] * (self.degree + 1) - low, high = self.coeffs + (0,), self.modulus_coeffs + (1,) - while deg(low): - r = list(poly_rounded_div(high, low)) - r += [0] * (self.degree + 1 - len(r)) - nm = [x for x in hm] - new = [x for x in high] - assert len(set( - [len(lm), len(hm), len(low), len(high), len(nm), len(new), self.degree + 1] - )) == 1 - for i in range(self.degree + 1): - for j in range(self.degree + 1 - i): - nm[i + j] -= lm[i] * r[j] - new[i + j] -= low[i] * r[j] - lm, low, hm, high = nm, new, lm, low - return self.__class__(tuple(lm[:self.degree])) / low[0] - - def __repr__(self): - return repr(self.coeffs) - - def __eq__(self, other): - assert isinstance(other, self.__class__) - for c1, c2 in zip(self.coeffs, other.coeffs): - if c1 != c2: - return False - return True - - def __ne__(self, other): - return not self == other - - def __neg__(self): - return self.__class__([-c for c in self.coeffs]) - - @classmethod - def one(cls): - return cls((1,) + (0,) * (cls.degree - 1)) - - @classmethod - def zero(cls): - return cls((0,) * cls.degree) - - -# The quadratic extension field -class FQ2(FQP): - def __init__(self, coeffs): - self.coeffs = tuple(FQ(c) for c in coeffs) - self.modulus_coeffs = (1, 0) - self.degree = 2 - self.__class__.degree = 2 - - -# The 12th-degree extension field -class FQ12(FQP): - def __init__(self, coeffs): - self.coeffs = tuple(FQ(c) for c in coeffs) - self.modulus_coeffs = FQ12_modulus_coeffs - self.degree = 12 - self.__class__.degree = 12 diff --git a/py_ecc/bls12_381/bls12_381_pairing.py b/py_ecc/bls12_381/bls12_381_pairing.py deleted file mode 100644 index 21ec4f22..00000000 --- a/py_ecc/bls12_381/bls12_381_pairing.py +++ /dev/null @@ -1,102 +0,0 @@ -from __future__ import absolute_import - -from .bls12_381_curve import ( - double, - add, - multiply, - is_on_curve, - twist, - b, - b2, - curve_order, - G1, -) -from .bls12_381_field_elements import ( - field_modulus, - FQ, - FQ12, -) - - -ate_loop_count = 15132376222941642752 -log_ate_loop_count = 62 - - -# Create a function representing the line between P1 and P2, -# and evaluate it at T -def linefunc(P1, P2, T): - assert P1 and P2 and T # No points-at-infinity allowed, sorry - 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): - 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, two, three = G1, double(G1), multiply(G1, 3) -negone, negtwo, negthree = ( - multiply(G1, curve_order - 1), - multiply(G1, curve_order - 2), - multiply(G1, curve_order - 3), -) - - -assert linefunc(one, two, one) == FQ(0) -assert linefunc(one, two, two) == FQ(0) -assert linefunc(one, two, three) != FQ(0) -assert linefunc(one, two, negthree) == FQ(0) -assert linefunc(one, negone, one) == FQ(0) -assert linefunc(one, negone, negone) == FQ(0) -assert linefunc(one, negone, two) != FQ(0) -assert linefunc(one, one, one) == FQ(0) -assert linefunc(one, one, two) != FQ(0) -assert linefunc(one, one, negtwo) == FQ(0) - - -# Main miller loop -def miller_loop(Q, P): - 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) - # assert R == multiply(Q, ate_loop_count) - # Q1 = (Q[0] ** field_modulus, Q[1] ** field_modulus) - # assert is_on_curve(Q1, b12) - # nQ2 = (Q1[0] ** field_modulus, -Q1[1] ** field_modulus) - # assert is_on_curve(nQ2, b12) - # f = f * linefunc(R, Q1, P) - # R = add(R, Q1) - # f = f * linefunc(R, nQ2, P) - # R = add(R, nQ2) This line is in many specifications but it technically does nothing - return f ** ((field_modulus ** 12 - 1) // curve_order) - - -# Pairing computation -def pairing(Q, P): - assert is_on_curve(Q, b2) - assert is_on_curve(P, b) - return miller_loop(twist(Q), cast_point_to_fq12(P)) - - -def final_exponentiate(p): - return p ** ((field_modulus ** 12 - 1) // curve_order) diff --git a/py_ecc/bls12_381_curve.py b/py_ecc/bls12_381_curve.py new file mode 100644 index 00000000..7a7a3296 --- /dev/null +++ b/py_ecc/bls12_381_curve.py @@ -0,0 +1,158 @@ +from py_ecc.BaseCurve import ( + BaseCurve, + BaseOptimizedCurve, +) + +from py_ecc.curve_properties import ( + bls12_381_props, + optimized_bls12_381_props, +) + +from py_ecc.field_elements import ( + FQP, + FQ12, +) + +from py_ecc.optimized_field_elements import ( + FQP as optimized_FQP, + FQ12 as optimized_FQ12, +) + +from py_ecc.typing import ( + Field, + Optimized_Point3D, + Point2D, +) + + +class BLS12_381_Curve(BaseCurve): + curve_name = "bls12_381" + curve_order = bls12_381_props.curve_order + field_modulus = bls12_381_props.field_modulus + b = bls12_381_props.b + b2 = bls12_381_props.b2 + b12 = bls12_381_props.b12 + G1 = bls12_381_props.G1 + G2 = bls12_381_props.G2 + Z1 = bls12_381_props.Z1 + Z2 = bls12_381_props.Z2 + ate_loop_count = bls12_381_props.ate_loop_count + log_ate_loop_count = bls12_381_props.log_ate_loop_count + pseudo_binary_encoding = bls12_381_props.pseudo_binary_encoding + + @staticmethod + def twist(pt: Point2D[FQP]) -> Point2D[FQP]: + if pt is None: + return None + w = FQ12([0, 1] + [0] * 10, "bls12_381") + _x, _y = pt + # Field isomorphism from Z[p] / x**2 + 1 to Z[p] / x**2 - 2*x + 2 + xcoeffs = [_x.coeffs[0] - _x.coeffs[1], _x.coeffs[1]] + ycoeffs = [_y.coeffs[0] - _y.coeffs[1], _y.coeffs[1]] + # Isomorphism into subfield of Z[p] / w**12 - 2 * w**6 + 2, + # where w**6 = x + nx = FQ12([int(xcoeffs[0])] + [0] * 5 + [int(xcoeffs[1])] + [0] * 5, "bls12_381") + ny = FQ12([int(ycoeffs[0])] + [0] * 5 + [int(ycoeffs[1])] + [0] * 5, "bls12_381") + # Divide x coord by w**2 and y coord by w**3 + return (nx / w**2, ny / w**3) + + @classmethod + def miller_loop(cls, Q: Point2D[Field], P: Point2D[Field]) -> FQP: + if Q is None or P is None: + return FQ12.one(cls.curve_name) + R = Q + f = FQ12.one(cls.curve_name) + for i in range(cls.log_ate_loop_count, -1, -1): + f = f * f * cls.linefunc(R, R, P) + R = cls.double(R) + if cls.ate_loop_count & (2**i): + f = f * cls.linefunc(R, Q, P) + R = cls.add(R, Q) + # assert R == multiply(Q, ate_loop_count) + # Q1 = (Q[0] ** field_modulus, Q[1] ** field_modulus) + # assert is_on_curve(Q1, b12) + # nQ2 = (Q1[0] ** field_modulus, -Q1[1] ** field_modulus) + # assert is_on_curve(nQ2, b12) + # f = f * linefunc(R, Q1, P) + # R = add(R, Q1) + # f = f * linefunc(R, nQ2, P) + # R = add(R, nQ2) This line is in many specifications but it technically does nothing + return f ** ((cls.field_modulus ** 12 - 1) // cls.curve_order) + + +class Optimized_BLS12_381_Curve(BaseOptimizedCurve): + curve_name = "bls12_381" + curve_order = optimized_bls12_381_props.curve_order + field_modulus = optimized_bls12_381_props.field_modulus + b = optimized_bls12_381_props.b + b2 = optimized_bls12_381_props.b2 + b12 = optimized_bls12_381_props.b12 + G1 = optimized_bls12_381_props.G1 + G2 = optimized_bls12_381_props.G2 + Z1 = optimized_bls12_381_props.Z1 + Z2 = optimized_bls12_381_props.Z2 + ate_loop_count = optimized_bls12_381_props.ate_loop_count + log_ate_loop_count = optimized_bls12_381_props.log_ate_loop_count + pseudo_binary_encoding = optimized_bls12_381_props.pseudo_binary_encoding + + @staticmethod + def twist(pt: Optimized_Point3D[optimized_FQP]) -> Optimized_Point3D[optimized_FQP]: + if pt is None: + return None + w = optimized_FQ12([0, 1] + [0] * 10, "bls12_381") + _x, _y, _z = pt + # Field isomorphism from Z[p] / x**2 + 1 to Z[p] / x**2 - 2*x + 2 + xcoeffs = [_x.coeffs[0] - _x.coeffs[1], _x.coeffs[1]] + ycoeffs = [_y.coeffs[0] - _y.coeffs[1], _y.coeffs[1]] + zcoeffs = [_z.coeffs[0] - _z.coeffs[1], _z.coeffs[1]] + nx = optimized_FQ12([xcoeffs[0]] + [0] * 5 + [xcoeffs[1]] + [0] * 5, "bls12_381") + ny = optimized_FQ12([ycoeffs[0]] + [0] * 5 + [ycoeffs[1]] + [0] * 5, "bls12_381") + nz = optimized_FQ12([zcoeffs[0]] + [0] * 5 + [zcoeffs[1]] + [0] * 5, "bls12_381") + # Divide x coord by w**2 and y coord by w**3 + return (nx / w**2, ny / w**3, nz) + + @classmethod + def miller_loop(cls, + Q: Optimized_Point3D[optimized_FQP], + P: Optimized_Point3D[optimized_FQP], + final_exponentiate: bool=True) -> optimized_FQP: + if Q is None or P is None: + return optimized_FQ12.one(cls.curve_name) + R = Q + f_num, f_den = optimized_FQ12.one(cls.curve_name), optimized_FQ12.one(cls.curve_name) + # for i in range(log_ate_loop_count, -1, -1): + for v in cls.pseudo_binary_encoding[62::-1]: + _n, _d = cls.linefunc(R, R, P) + f_num = f_num * f_num * _n + f_den = f_den * f_den * _d + R = cls.double(R) + if v == 1: + _n, _d = cls.linefunc(R, Q, P) + f_num = f_num * _n + f_den = f_den * _d + R = cls.add(R, Q) + elif v == -1: + nQ = cls.neg(Q) + _n, _d = cls.linefunc(R, nQ, P) + f_num = f_num * _n + f_den = f_den * _d + R = cls.add(R, nQ) + # assert R == multiply(Q, ate_loop_count) + # Q1 = (Q[0] ** field_modulus, Q[1] ** field_modulus, Q[2] ** field_modulus) + # assert is_on_curve(Q1, b12) + # nQ2 = (Q1[0] ** field_modulus, -Q1[1] ** field_modulus, Q1[2] ** field_modulus) + # assert is_on_curve(nQ2, b12) + # _n1, _d1 = linefunc(R, Q1, P) + # R = add(R, Q1) + # _n2, _d2 = linefunc(R, nQ2, P) + # f = f_num * _n1 * _n2 / (f_den * _d1 * _d2) + f = f_num / f_den + # R = add(R, nQ2) This line is in many specifications but it technically does nothing + if final_exponentiate: + return f ** ((cls.field_modulus ** 12 - 1) // cls.curve_order) + else: + return f + + +bls12_381 = BLS12_381_Curve() +optimized_bls12_381 = Optimized_BLS12_381_Curve() diff --git a/py_ecc/bn128/__init__.py b/py_ecc/bn128/__init__.py deleted file mode 100644 index d26899a0..00000000 --- a/py_ecc/bn128/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -from __future__ import absolute_import - -from .bn128_field_elements import ( # noqa: F401 - field_modulus, - FQ, - FQP, - FQ2, - FQ12, -) -from .bn128_curve import ( # noqa: F401 - add, - double, - multiply, - is_inf, - is_on_curve, - eq, - neg, - twist, - b, - b2, - b12, - curve_order, - G1, - G2, - Z1, - Z2, - G12, -) -from .bn128_pairing import ( # noqa: F401 - pairing, - final_exponentiate, -) diff --git a/py_ecc/bn128/bn128_curve.py b/py_ecc/bn128/bn128_curve.py deleted file mode 100644 index 8ffbd52b..00000000 --- a/py_ecc/bn128/bn128_curve.py +++ /dev/null @@ -1,145 +0,0 @@ -from __future__ import absolute_import - -from typing import ( - cast, -) - -from py_ecc.typing import ( - Field, - GeneralPoint, - Point2D, -) - -from .bn128_field_elements import ( - field_modulus, - FQ, - FQ2, - FQ12, - FQP, -) - - -curve_order = 21888242871839275222246405745257275088548364400416034343698204186575808495617 - -# Curve order should be prime -assert pow(2, curve_order, curve_order) == 2 -# Curve order should be a factor of field_modulus**12 - 1 -assert (field_modulus ** 12 - 1) % curve_order == 0 - -# Curve is y**2 = x**3 + 3 -b = FQ(3) -# Twisted curve over FQ**2 -b2 = FQ2([3, 0]) / FQ2([9, 1]) -# Extension curve over FQ**12; same b value as over FQ -b12 = FQ12([3] + [0] * 11) - -# Generator for curve over FQ -G1 = cast(Point2D[FQ], (FQ(1), FQ(2))) -# Generator for twisted curve over FQ2 -G2 = ( - FQ2([ - 10857046999023057135944570762232829481370756359578518086990519993285655852781, - 11559732032986387107991004021392285783925812861821192530917403151452391805634, - ]), - FQ2([ - 8495653923123431417604973247489272438418190587263600148770280649306958101930, - 4082367875863433681332203403145435568316851327593401208105741076214120093531, - ]), -) -# Point at infinity over FQ -Z1 = None -# Point at infinity for twisted curve over FQ2 -Z2 = None - - -# Check if a point is the point at infinity -def is_inf(pt: GeneralPoint[Field]) -> bool: - return pt is None - - -# Check that a point is on the curve defined by y**2 == x**3 + b -def is_on_curve(pt: Point2D[Field], b: Field) -> bool: - if is_inf(pt): - return True - x, y = pt - return y**2 - x**3 == b - - -assert is_on_curve(G1, b) -assert is_on_curve(cast(Point2D[FQ2], G2), b2) - - -# Elliptic curve doubling -def double(pt: Point2D[Field]) -> Point2D[Field]: - x, y = pt - m = 3 * x**2 / (2 * y) - newx = m**2 - 2 * x - newy = -m * newx + m * x - y - return (newx, newy) - - -# Elliptic curve addition -def add(p1: Point2D[Field], - p2: Point2D[Field]) -> Point2D[Field]: - if p1 is None or p2 is None: - return p1 if p2 is None else p2 - x1, y1 = p1 - x2, y2 = p2 - if x2 == x1 and y2 == y1: - return double(p1) - elif x2 == x1: - return None - else: - m = (y2 - y1) / (x2 - x1) - newx = m**2 - x1 - x2 - newy = -m * newx + m * x1 - y1 - assert newy == (-m * newx + m * x2 - y2) - return (newx, newy) - - -# Elliptic curve point multiplication -def multiply(pt: Point2D[Field], n: int) -> Point2D[Field]: - if n == 0: - return None - elif n == 1: - return pt - elif not n % 2: - return multiply(double(pt), n // 2) - else: - return add(multiply(double(pt), int(n // 2)), pt) - - -def eq(p1: GeneralPoint[Field], p2: GeneralPoint[Field]) -> bool: - return p1 == p2 - - -# "Twist" a point in E(FQ2) into a point in E(FQ12) -w = FQ12([0, 1] + [0] * 10) - - -# Convert P => -P -def neg(pt: Point2D[Field]) -> Point2D[Field]: - if pt is None: - return None - x, y = pt - return (x, -y) - - -def twist(pt: Point2D[FQP]) -> Point2D[FQP]: - if pt is None: - return None - _x, _y = pt - # Field isomorphism from Z[p] / x**2 to Z[p] / x**2 - 18*x + 82 - xcoeffs = [_x.coeffs[0] - _x.coeffs[1] * 9, _x.coeffs[1]] - ycoeffs = [_y.coeffs[0] - _y.coeffs[1] * 9, _y.coeffs[1]] - # Isomorphism into subfield of Z[p] / w**12 - 18 * w**6 + 82, - # where w**6 = x - nx = FQ12([int(xcoeffs[0])] + [0] * 5 + [int(xcoeffs[1])] + [0] * 5) - ny = FQ12([int(ycoeffs[0])] + [0] * 5 + [int(ycoeffs[1])] + [0] * 5) - # Divide x coord by w**2 and y coord by w**3 - return (nx * w ** 2, ny * w**3) - - -G12 = twist(cast(Point2D[FQP], G2)) -# Check that the twist creates a point that is on the curve -assert is_on_curve(G12, b12) diff --git a/py_ecc/bn128/bn128_field_elements.py b/py_ecc/bn128/bn128_field_elements.py deleted file mode 100644 index 4c182156..00000000 --- a/py_ecc/bn128/bn128_field_elements.py +++ /dev/null @@ -1,276 +0,0 @@ -from __future__ import absolute_import - -from typing import ( - cast, - List, - Tuple, - Sequence, - Union, -) - - -# The prime modulus of the field -field_modulus = 21888242871839275222246405745257275088696311157297823662689037894645226208583 -# See, it's prime! -assert pow(2, field_modulus, field_modulus) == 2 - -# The modulus of the polynomial in this representation of FQ12 -FQ12_MODULUS_COEFFS = (82, 0, 0, 0, 0, 0, -18, 0, 0, 0, 0, 0) # Implied + [1] -FQ2_MODULUS_COEFFS = (1, 0) - - -# Extended euclidean algorithm to find modular inverses for -# integers -def inv(a: int, n: int) -> int: - if a == 0: - return 0 - lm, hm = 1, 0 - low, high = a % n, n - while low > 1: - r = high // low - nm, new = hm - lm * r, high - low * r - lm, low, hm, high = nm, new, lm, low - return lm % n - - -IntOrFQ = Union[int, "FQ"] - - -# A class for field elements in FQ. Wrap a number in this class, -# and it becomes a field element. -class FQ(object): - n = None # type: int - - def __init__(self, val: IntOrFQ) -> None: - if isinstance(val, FQ): - self.n = val.n - else: - self.n = val % field_modulus - assert isinstance(self.n, int) - - def __add__(self, other: IntOrFQ) -> "FQ": - on = other.n if isinstance(other, FQ) else other - return FQ((self.n + on) % field_modulus) - - def __mul__(self, other: IntOrFQ) -> "FQ": - on = other.n if isinstance(other, FQ) else other - return FQ((self.n * on) % field_modulus) - - def __rmul__(self, other: IntOrFQ) -> "FQ": - return self * other - - def __radd__(self, other: IntOrFQ) -> "FQ": - return self + other - - def __rsub__(self, other: IntOrFQ) -> "FQ": - on = other.n if isinstance(other, FQ) else other - return FQ((on - self.n) % field_modulus) - - def __sub__(self, other: IntOrFQ) -> "FQ": - on = other.n if isinstance(other, FQ) else other - return FQ((self.n - on) % field_modulus) - - def __div__(self, other: IntOrFQ) -> "FQ": - on = other.n if isinstance(other, FQ) else other - assert isinstance(on, int) - return FQ(self.n * inv(on, field_modulus) % field_modulus) - - def __truediv__(self, other: IntOrFQ) -> "FQ": - return self.__div__(other) - - def __rdiv__(self, other: IntOrFQ) -> "FQ": - on = other.n if isinstance(other, FQ) else other - assert isinstance(on, int), on - return FQ(inv(self.n, field_modulus) * on % field_modulus) - - def __rtruediv__(self, other: IntOrFQ) -> "FQ": - return self.__rdiv__(other) - - def __pow__(self, other: int) -> "FQ": - if other == 0: - return FQ(1) - elif other == 1: - return FQ(self.n) - elif other % 2 == 0: - return (self * self) ** (other // 2) - else: - return ((self * self) ** int(other // 2)) * self - - def __eq__(self, other: IntOrFQ) -> bool: # type:ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 - if isinstance(other, FQ): - return self.n == other.n - else: - return self.n == other - - def __ne__(self, other: IntOrFQ) -> bool: # type:ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 - return not self == other - - def __neg__(self) -> "FQ": - return FQ(-self.n) - - def __repr__(self) -> str: - return repr(self.n) - - def __int__(self) -> int: - return self.n - - @classmethod - def one(cls) -> "FQ": - return cls(1) - - @classmethod - def zero(cls) -> "FQ": - return cls(0) - - -# Utility methods for polynomial math -def deg(p: Sequence[IntOrFQ]) -> int: - d = len(p) - 1 - while p[d] == 0 and d: - d -= 1 - return d - - -def poly_rounded_div(a: Sequence[IntOrFQ], - b: Sequence[IntOrFQ]) -> Tuple[IntOrFQ]: - dega = deg(a) - degb = deg(b) - temp = [x for x in a] - o = [0 for x in a] - for i in range(dega - degb, -1, -1): - o[i] += int(temp[degb + i] / b[degb]) - for c in range(degb + 1): - temp[c + i] -= o[c] - return cast(Tuple[IntOrFQ], tuple(o[:deg(o) + 1])) - - -int_types_or_FQ = (int, FQ) - - -# A class for elements in polynomial extension fields -class FQP(object): - degree = 0 - - def __init__(self, - coeffs: Sequence[IntOrFQ], - modulus_coeffs: Sequence[IntOrFQ]=None) -> None: - assert len(coeffs) == len(modulus_coeffs) - self.coeffs = tuple(FQ(c) for c in coeffs) - # The coefficients of the modulus, without the leading [1] - self.modulus_coeffs = modulus_coeffs - # The degree of the extension field - self.degree = len(self.modulus_coeffs) - - def __add__(self, other: "FQP") -> "FQP": - assert isinstance(other, type(self)) - return type(self)([x + y for x, y in zip(self.coeffs, other.coeffs)]) - - def __sub__(self, other: "FQP") -> "FQP": - assert isinstance(other, type(self)) - return type(self)([x - y for x, y in zip(self.coeffs, other.coeffs)]) - - def __mul__(self, other: Union[int, "FQ", "FQP"]) -> "FQP": - if isinstance(other, int) or isinstance(other, FQ): - return type(self)([c * other for c in self.coeffs]) - else: - assert isinstance(other, FQP) - b = [FQ(0) for i in range(self.degree * 2 - 1)] - for i in range(self.degree): - for j in range(self.degree): - b[i + j] += self.coeffs[i] * other.coeffs[j] - while len(b) > self.degree: - exp, top = len(b) - self.degree - 1, b.pop() - for i in range(self.degree): - b[exp + i] -= top * FQ(self.modulus_coeffs[i]) - return type(self)(b) - - def __rmul__(self, other: Union[int, "FQ", "FQP"]) -> "FQP": - return self * other - - def __div__(self, other: Union[int, "FQ", "FQP"]) -> "FQP": - if isinstance(other, int_types_or_FQ): - return type(self)([c / other for c in self.coeffs]) - else: - assert isinstance(other, FQP) - return self * other.inv() - - def __truediv__(self, other: Union[int, "FQ", "FQP"]) -> "FQP": - return self.__div__(other) - - def __pow__(self, other: int) -> "FQP": - if other == 0: - return type(self)([1] + [0] * (self.degree - 1)) - elif other == 1: - return type(self)(self.coeffs) - elif other % 2 == 0: - return (self * self) ** (other // 2) - else: - return ((self * self) ** int(other // 2)) * self - - # Extended euclidean algorithm used to find the modular inverse - def inv(self) -> "FQP": - lm, hm = ( - [1] + [0] * self.degree, - [0] * (self.degree + 1), - ) - low, high = ( - # Ignore mypy yelling about the inner types for the tuples being incompatible - cast(List[IntOrFQ], list(self.coeffs + (0,))), # type: ignore - cast(List[IntOrFQ], list(self.modulus_coeffs + (1,))), # type: ignore - ) - while deg(low): - r = cast(List[IntOrFQ], list(poly_rounded_div(high, low))) - r += [0] * (self.degree + 1 - len(r)) - nm = [x for x in hm] - new = [x for x in high] - assert len(set( - [len(lm), len(hm), len(low), len(high), len(nm), len(new), self.degree + 1] - )) == 1 - for i in range(self.degree + 1): - for j in range(self.degree + 1 - i): - nm[i + j] -= lm[i] * int(r[j]) - new[i + j] -= low[i] * int(r[j]) - lm, low, hm, high = nm, new, lm, low - return type(self)(lm[:self.degree]) / low[0] - - def __repr__(self) -> str: - return repr(self.coeffs) - - def __eq__(self, other: "FQP") -> bool: # type: ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 - assert isinstance(other, type(self)) - for c1, c2 in zip(self.coeffs, other.coeffs): - if c1 != c2: - return False - return True - - def __ne__(self, other: "FQP") -> bool: # type: ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 - return not self == other - - def __neg__(self) -> "FQP": - return type(self)([-c for c in self.coeffs]) - - @classmethod - def one(cls) -> "FQP": - return cls([1] + [0] * (cls.degree - 1)) - - @classmethod - def zero(cls) -> "FQP": - return cls([0] * cls.degree) - - -# The quadratic extension field -class FQ2(FQP): - degree = 2 - - def __init__(self, coeffs: Sequence[IntOrFQ]) -> None: - super().__init__(coeffs, FQ2_MODULUS_COEFFS) - assert self.degree == 2 - - -# The 12th-degree extension field -class FQ12(FQP): - degree = 12 - - def __init__(self, coeffs: Sequence[IntOrFQ]) -> None: - super().__init__(coeffs, FQ12_MODULUS_COEFFS) - assert self.degree == 12 diff --git a/py_ecc/bn128/bn128_pairing.py b/py_ecc/bn128/bn128_pairing.py deleted file mode 100644 index bc070266..00000000 --- a/py_ecc/bn128/bn128_pairing.py +++ /dev/null @@ -1,120 +0,0 @@ -from __future__ import absolute_import - -from typing import ( - cast, -) - -from py_ecc.typing import ( - Field, - FQPoint2D, - FQ2Point2D, - FQ12Point2D, - Point2D, -) - -from .bn128_curve import ( - double, - add, - multiply, - is_on_curve, - twist, - b, - b2, - curve_order, - G1, -) -from .bn128_field_elements import ( - field_modulus, - FQ, - FQ12, - FQP, -) - - -ate_loop_count = 29793968203157093288 -log_ate_loop_count = 63 - - -# Create a function representing the line between P1 and P2, -# and evaluate it at T -def linefunc(P1: Point2D[Field], - P2: Point2D[Field], - T: Point2D[Field]) -> Field: - assert P1 and P2 and T # No points-at-infinity allowed, sorry - 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: FQPoint2D) -> FQ12Point2D: - if pt is None: - return None - x, y = pt - fq12_point = (FQ12([x.n] + [0] * 11), FQ12([y.n] + [0] * 11)) - return cast(FQ12Point2D, fq12_point) - - -# Check consistency of the "line function" -one, two, three = G1, double(G1), multiply(G1, 3) -negone, negtwo, negthree = ( - multiply(G1, curve_order - 1), - multiply(G1, curve_order - 2), - multiply(G1, curve_order - 3), -) - - -assert linefunc(one, two, one) == FQ(0) -assert linefunc(one, two, two) == FQ(0) -assert linefunc(one, two, three) != FQ(0) -assert linefunc(one, two, negthree) == FQ(0) -assert linefunc(one, negone, one) == FQ(0) -assert linefunc(one, negone, negone) == FQ(0) -assert linefunc(one, negone, two) != FQ(0) -assert linefunc(one, one, one) == FQ(0) -assert linefunc(one, one, two) != FQ(0) -assert linefunc(one, one, negtwo) == FQ(0) - - -# Main miller loop -def miller_loop(Q: Point2D[Field], - P: Point2D[Field]) -> FQP: - if Q is None or P is None: - return FQ12.one() - R = Q # type: Point2D[Field] - 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) - # assert R == multiply(Q, ate_loop_count) - Q1 = cast(Point2D[Field], (Q[0] ** field_modulus, Q[1] ** field_modulus)) - # assert is_on_curve(Q1, b12) - nQ2 = cast(Point2D[Field], - (Q1[0] ** field_modulus, -Q1[1] ** field_modulus)) - # assert is_on_curve(nQ2, b12) - f = f * linefunc(R, Q1, P) - R = add(R, Q1) - f = f * linefunc(R, nQ2, P) - # R = add(R, nQ2) This line is in many specifications but it technically does nothing - return f ** ((field_modulus ** 12 - 1) // curve_order) - - -# Pairing computation -def pairing(Q: FQ2Point2D, P: FQPoint2D) -> FQP: - assert is_on_curve(Q, b2) - assert is_on_curve(P, b) - return miller_loop(twist(Q), cast_point_to_fq12(P)) - - -def final_exponentiate(p: Field) -> Field: - return p ** ((field_modulus ** 12 - 1) // curve_order) diff --git a/py_ecc/bn128_curve.py b/py_ecc/bn128_curve.py new file mode 100644 index 00000000..e593965e --- /dev/null +++ b/py_ecc/bn128_curve.py @@ -0,0 +1,170 @@ +from typing import ( + cast, +) + +from py_ecc.BaseCurve import ( + BaseCurve, + BaseOptimizedCurve, +) + +from py_ecc.curve_properties import ( + bn128_props, + optimized_bn128_props, +) + +from py_ecc.field_elements import ( + FQP, + FQ12, +) + +from py_ecc.optimized_field_elements import ( + FQP as optimized_FQP, + FQ12 as optimized_FQ12, +) + +from py_ecc.typing import ( + Field, + Optimized_Point3D, + Point2D, +) + + +class BN128_Curve(BaseCurve): + curve_name = "bn128" + curve_order = bn128_props.curve_order + field_modulus = bn128_props.field_modulus + b = bn128_props.b + b2 = bn128_props.b2 + b12 = bn128_props.b12 + G1 = bn128_props.G1 + G2 = bn128_props.G2 + Z1 = bn128_props.Z1 + Z2 = bn128_props.Z2 + ate_loop_count = bn128_props.ate_loop_count + log_ate_loop_count = bn128_props.log_ate_loop_count + pseudo_binary_encoding = bn128_props.pseudo_binary_encoding + + @staticmethod + def twist(pt: Point2D[FQP]) -> Point2D[FQP]: + if pt is None: + return None + w = FQ12([0, 1] + [0] * 10, "bn128") + _x, _y = pt + # Field isomorphism from Z[p] / x**2 + 1 to Z[p] / x**2 - 18*x + 82 + xcoeffs = [_x.coeffs[0] - _x.coeffs[1] * 9, _x.coeffs[1]] + ycoeffs = [_y.coeffs[0] - _y.coeffs[1] * 9, _y.coeffs[1]] + # Isomorphism into subfield of Z[p] / w**12 - 18 * w**6 + 82, + # where w**6 = x + nx = FQ12([int(xcoeffs[0])] + [0] * 5 + [int(xcoeffs[1])] + [0] * 5, "bn128") + ny = FQ12([int(ycoeffs[0])] + [0] * 5 + [int(ycoeffs[1])] + [0] * 5, "bn128") + # Divide x coord by w**2 and y coord by w**3 + return (nx * w**2, ny * w**3) + + @classmethod + def miller_loop(cls, Q: Point2D[Field], P: Point2D[Field]) -> FQP: + if Q is None or P is None: + return FQ12.one(cls.curve_name) + R = Q # type: Point2D[Field] + f = FQ12.one(cls.curve_name) + for i in range(cls.log_ate_loop_count, -1, -1): + f = f * f * cls.linefunc(R, R, P) + R = cls.double(R) + if cls.ate_loop_count & (2**i): + f = f * cls.linefunc(R, Q, P) + R = cls.add(R, Q) + # assert R == multiply(Q, ate_loop_count) + Q1 = cast(Point2D[Field], (Q[0] ** cls.field_modulus, Q[1] ** cls.field_modulus)) + # assert is_on_curve(Q1, b12) + nQ2 = cast(Point2D[Field], + (Q1[0] ** cls.field_modulus, -Q1[1] ** cls.field_modulus)) + # assert is_on_curve(nQ2, b12) + f = f * cls.linefunc(R, Q1, P) + R = cls.add(R, Q1) + f = f * cls.linefunc(R, nQ2, P) + # R = add(R, nQ2) This line is in many specifications but it technically does nothing + return f ** ((cls.field_modulus ** 12 - 1) // cls.curve_order) + + +class Optimized_BN128_Curve(BaseOptimizedCurve): + curve_name = "bn128" + curve_order = optimized_bn128_props.curve_order + field_modulus = optimized_bn128_props.field_modulus + b = optimized_bn128_props.b + b2 = optimized_bn128_props.b2 + b12 = optimized_bn128_props.b12 + G1 = optimized_bn128_props.G1 + G2 = optimized_bn128_props.G2 + Z1 = optimized_bn128_props.Z1 + Z2 = optimized_bn128_props.Z2 + ate_loop_count = optimized_bn128_props.ate_loop_count + log_ate_loop_count = optimized_bn128_props.log_ate_loop_count + pseudo_binary_encoding = optimized_bn128_props.pseudo_binary_encoding + + @staticmethod + def twist(pt: Optimized_Point3D[optimized_FQP]) -> Optimized_Point3D[optimized_FQP]: + if pt is None: + return None + w = optimized_FQ12([0, 1] + [0] * 10, "bn128") + _x, _y, _z = pt + # Field isomorphism from Z[p] / x**2 + 1 to Z[p] / x**2 - 18*x + 82 + xcoeffs = [_x.coeffs[0] - _x.coeffs[1] * 9, _x.coeffs[1]] + ycoeffs = [_y.coeffs[0] - _y.coeffs[1] * 9, _y.coeffs[1]] + zcoeffs = [_z.coeffs[0] - _z.coeffs[1] * 9, _z.coeffs[1]] + nx = optimized_FQ12([xcoeffs[0]] + [0] * 5 + [xcoeffs[1]] + [0] * 5, "bn128") + ny = optimized_FQ12([ycoeffs[0]] + [0] * 5 + [ycoeffs[1]] + [0] * 5, "bn128") + nz = optimized_FQ12([zcoeffs[0]] + [0] * 5 + [zcoeffs[1]] + [0] * 5, "bn128") + return (nx * w**2, ny * w**3, nz) + + @classmethod + def miller_loop(cls, + Q: Optimized_Point3D[optimized_FQP], + P: Optimized_Point3D[optimized_FQP], + final_exponentiate: bool=True) -> optimized_FQP: + if Q is None or P is None: + return optimized_FQ12.one(cls.curve_name) + R = Q # type: Optimized_Point3D[optimized_FQP] + f_num, f_den = optimized_FQ12.one(cls.curve_name), optimized_FQ12.one(cls.curve_name) + # for i in range(log_ate_loop_count, -1, -1): + for v in cls.pseudo_binary_encoding[63::-1]: + _n, _d = cls.linefunc(R, R, P) + f_num = f_num * f_num * _n + f_den = f_den * f_den * _d + R = cls.double(R) + # if ate_loop_count & (2**i): + if v == 1: + _n, _d = cls.linefunc(R, Q, P) + f_num = f_num * _n + f_den = f_den * _d + R = cls.add(R, Q) + elif v == -1: + nQ = cls.neg(Q) + _n, _d = cls.linefunc(R, nQ, P) + f_num = f_num * _n + f_den = f_den * _d + R = cls.add(R, nQ) + # assert R == multiply(Q, ate_loop_count) + Q1 = ( + Q[0] ** cls.field_modulus, + Q[1] ** cls.field_modulus, + Q[2] ** cls.field_modulus, + ) + # assert is_on_curve(Q1, b12) + nQ2 = ( + Q1[0] ** cls.field_modulus, + -Q1[1] ** cls.field_modulus, + Q1[2] ** cls.field_modulus, + ) + # assert is_on_curve(nQ2, b12) + _n1, _d1 = cls.linefunc(R, Q1, P) + R = cls.add(R, Q1) + _n2, _d2 = cls.linefunc(R, nQ2, P) + f = f_num * _n1 * _n2 / (f_den * _d1 * _d2) + # R = add(R, nQ2) This line is in many specifications but it technically does nothing + if final_exponentiate: + return f ** ((cls.field_modulus ** 12 - 1) // cls.curve_order) + else: + return f + + +bn128 = BN128_Curve() +optimized_bn128 = Optimized_BN128_Curve() diff --git a/py_ecc/curve_properties.py b/py_ecc/curve_properties.py new file mode 100644 index 00000000..326b7e65 --- /dev/null +++ b/py_ecc/curve_properties.py @@ -0,0 +1,248 @@ +from typing import ( + Any, + cast, + List, + NamedTuple, + Optional, +) + +from py_ecc.field_elements import ( + FQ, + FQP, + FQ2, + FQ12, +) + +from py_ecc.optimized_field_elements import ( + FQ as optimized_FQ, + FQP as optimized_FQP, + FQ2 as optimized_FQ2, + FQ12 as optimized_FQ12, +) + +from py_ecc.typing import ( + Point2D, + Optimized_Point3D, +) + + +curve_props = NamedTuple( + 'curve_props', + ( + ('name', str), + ('field_modulus', int), + ('curve_order', int), + ('b', FQ), + ('b2', FQP), + ('b12', FQP), + ('G1', Point2D[FQ]), + ('G2', Point2D[FQP]), + ('Z1', Optional[Any]), + ('Z2', Optional[Any]), + ('ate_loop_count', int), + ('log_ate_loop_count', int), + ('pseudo_binary_encoding', List[int]), + ) +) + + +optimized_curve_props = NamedTuple( + 'optimized_curve_props', + ( + ('name', str), + ('field_modulus', int), + ('curve_order', int), + ('b', optimized_FQ), + ('b2', optimized_FQP), + ('b12', optimized_FQP), + ('G1', Optimized_Point3D[optimized_FQ]), + ('G2', Optimized_Point3D[optimized_FQP]), + ('Z1', Optimized_Point3D[optimized_FQ]), + ('Z2', Optimized_Point3D[optimized_FQP]), + ('ate_loop_count', int), + ('log_ate_loop_count', int), + ('pseudo_binary_encoding', List[int]), + ) +) + + +bn128_props = curve_props( + name="bn128", + field_modulus=21888242871839275222246405745257275088696311157297823662689037894645226208583, # noqa: E501 + # Order of the curve + curve_order=21888242871839275222246405745257275088548364400416034343698204186575808495617, # noqa: E501 + # Curve is y**2 = x**3 + 3 + b=FQ(3, "bn128"), + # Twisted curve over FQ**2 + b2=FQ2([3, 0], "bn128") / FQ2([9, 1], "bn128"), + # Extension curve over FQ**12; same b value as over FQ + b12=FQ12([3] + [0] * 11, "bn128"), + # Generator for curve over FQ + G1=cast(Point2D[FQ], (FQ(1, "bn128"), FQ(2, "bn128"))), + # Generator for twisted curve over FQ2 + G2=( + FQ2([ + 10857046999023057135944570762232829481370756359578518086990519993285655852781, + 11559732032986387107991004021392285783925812861821192530917403151452391805634, + ], "bn128"), + FQ2([ + 8495653923123431417604973247489272438418190587263600148770280649306958101930, + 4082367875863433681332203403145435568316851327593401208105741076214120093531, + ], "bn128"), + ), + # Point at infinity over FQ + Z1=None, + # Point at infinity for twisted curve over FQ2 + Z2=None, + ate_loop_count=29793968203157093288, + log_ate_loop_count=63, + pseudo_binary_encoding=[ + 0, 0, 0, 1, 0, 1, 0, -1, 0, 0, 1, -1, 0, 0, 1, 0, + 0, 1, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 0, 0, 1, 1, + 1, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, 0, 1, + 1, 0, 0, -1, 0, 0, 0, 1, 1, 0, -1, 0, 0, 1, 0, 1, 1, + ], +) + +bls12_381_props = curve_props( + name="bls12_381", + field_modulus=4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787, # noqa: E501 + # Order of the curve + curve_order=52435875175126190479447740508185965837690552500527637822603658699938581184513, # noqa: E501 + # Curve is y**2 = x**3 + 4 + b=FQ(4, "bls12_381"), + # Twisted curve over FQ**2 + b2=FQ2((4, 4), "bls12_381"), + # Extension curve over FQ**12; same b value as over FQ + b12=FQ12((4,) + (0,) * 11, "bls12_381"), + # Generator for curve over FQ + G1=( + FQ(3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507, "bls12_381"), # noqa: E501 + FQ(1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569, "bls12_381"), # noqa: E501 + ), + # Generator for twisted curve over FQ2 + G2=( + FQ2([ + 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160, # noqa: E501 + 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758, # noqa: E501 + ], "bls12_381"), + FQ2([ + 1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905, # noqa: E501 + 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582, # noqa: E501 + ], "bls12_381"), + ), + # Point at infinity over FQ + Z1=None, + # Point at infinity for twisted curve over FQ2 + Z2=None, + ate_loop_count=15132376222941642752, + log_ate_loop_count=62, + pseudo_binary_encoding=[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1 + ], +) + +optimized_bn128_props = optimized_curve_props( + name="bn128", + field_modulus=21888242871839275222246405745257275088696311157297823662689037894645226208583, # noqa: E501 + # Order of the curve + curve_order=21888242871839275222246405745257275088548364400416034343698204186575808495617, # noqa: E501 + # Curve is y**2 = x**3 + 3 + b=optimized_FQ(3, "bn128"), + # Twisted curve over FQ**2 + b2=optimized_FQ2([3, 0], "bn128") / optimized_FQ2([9, 1], "bn128"), + # Extension curve over FQ**12; same b value as over FQ + b12=optimized_FQ12([3] + [0] * 11, "bn128"), + # Generator for curve over FQ (optimized version) + G1=( + optimized_FQ(1, "bn128"), + optimized_FQ(2, "bn128"), + optimized_FQ(1, "bn128") + ), + # Generator for twisted curve over FQ2 (optimized version) + G2=( + optimized_FQ2([ + 10857046999023057135944570762232829481370756359578518086990519993285655852781, + 11559732032986387107991004021392285783925812861821192530917403151452391805634 + ], "bn128"), + optimized_FQ2([ + 8495653923123431417604973247489272438418190587263600148770280649306958101930, + 4082367875863433681332203403145435568316851327593401208105741076214120093531, + ], "bn128"), + optimized_FQ2.one("bn128"), + ), + # Point at infinity over FQ (optimized version) + Z1=( + optimized_FQ.one("bn128"), + optimized_FQ.one("bn128"), + optimized_FQ.zero("bn128") + ), + # Point at infinity for twisted curve over FQ2 (optimized version) + Z2=( + optimized_FQ2.one("bn128"), + optimized_FQ2.one("bn128"), + optimized_FQ2.zero("bn128") + ), + ate_loop_count=29793968203157093288, + log_ate_loop_count=63, + pseudo_binary_encoding=[ + 0, 0, 0, 1, 0, 1, 0, -1, 0, 0, 1, -1, 0, 0, 1, 0, + 0, 1, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 0, 0, 1, 1, + 1, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, 0, 1, + 1, 0, 0, -1, 0, 0, 0, 1, 1, 0, -1, 0, 0, 1, 0, 1, 1, + ], +) + +optimized_bls12_381_props = optimized_curve_props( + name="bls12_381", + field_modulus=4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787, # noqa: E501 + # Order of the curve + curve_order=52435875175126190479447740508185965837690552500527637822603658699938581184513, # noqa: E501 + # Curve is y**2 = x**3 + 4 + b=optimized_FQ(4, "bls12_381"), + # Twisted curve over FQ**2 + b2=optimized_FQ2((4, 4), "bls12_381"), + # Extension curve over FQ**12; same b value as over FQ + b12=optimized_FQ12((4,) + (0,) * 11, "bls12_381"), + # Generator for curve over FQ (optimized version) + G1=( + optimized_FQ(3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507, "bls12_381"), # noqa: E501 + optimized_FQ(1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569, "bls12_381"), # noqa: E501 + optimized_FQ(1, "bls12_381"), + ), + # Generator for twisted curve over FQ2 (optimized version) + G2=( + optimized_FQ2(( + 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160, # noqa: E501 + 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758, # noqa: E501 + ), "bls12_381"), + optimized_FQ2(( + 1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905, # noqa: E501 + 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582, # noqa: E501 + ), "bls12_381"), + optimized_FQ2.one("bls12_381"), + ), + # Point at infinity over FQ (optimized version) + Z1=( + optimized_FQ.one("bls12_381"), + optimized_FQ.one("bls12_381"), + optimized_FQ.zero("bls12_381") + ), + # Point at infinity for twisted curve over FQ2 (optimized version) + Z2=( + optimized_FQ2.one("bls12_381"), + optimized_FQ2.one("bls12_381"), + optimized_FQ2.zero("bls12_381") + ), + ate_loop_count=15132376222941642752, + log_ate_loop_count=62, + pseudo_binary_encoding=[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1 + ], +) diff --git a/py_ecc/field_elements.py b/py_ecc/field_elements.py new file mode 100644 index 00000000..94716f36 --- /dev/null +++ b/py_ecc/field_elements.py @@ -0,0 +1,346 @@ +from typing import ( + cast, + List, + Sequence, + Union, +) + +from py_ecc.field_properties import ( + field_properties, +) + +from py_ecc.utils import ( + deg, + poly_rounded_div, + prime_field_inv, +) + + +IntOrFQ = Union[int, "FQ"] + + +# A class for field elements in FQ. Wrap a number in this class, +# and it becomes a field element. +class FQ(object): + n = None # type: int + field_modulus = None + curve_name = None + + def __init__(self, val: IntOrFQ, curve_name: str) -> None: + """ + curve_name can be either 'bn128' or 'bls12_381' + This is needed to obtain field_modulus, FQ2_MODULUS_COEFFS + and FQ12_MODULUS_COEFFS from the curve properties + """ + self.curve_name = curve_name + self.field_modulus = field_properties[curve_name]["field_modulus"] + + if isinstance(val, FQ): + self.n = val.n + elif isinstance(val, int): + self.n = val % self.field_modulus + else: + raise ValueError( + "Expected an int or FQ object, but got object of type {}" + .format(type(val)) + ) + + def __add__(self, other: IntOrFQ) -> "FQ": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise ValueError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return FQ((self.n + on) % self.field_modulus, self.curve_name) + + def __mul__(self, other: IntOrFQ) -> "FQ": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise ValueError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return FQ((self.n * on) % self.field_modulus, self.curve_name) + + def __rmul__(self, other: IntOrFQ) -> "FQ": + return self * other + + def __radd__(self, other: IntOrFQ) -> "FQ": + return self + other + + def __rsub__(self, other: IntOrFQ) -> "FQ": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise ValueError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return FQ((on - self.n) % self.field_modulus, self.curve_name) + + def __sub__(self, other: IntOrFQ) -> "FQ": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise ValueError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return FQ((self.n - on) % self.field_modulus, self.curve_name) + + def __div__(self, other: IntOrFQ) -> "FQ": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise ValueError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return FQ( + self.n * prime_field_inv(on, self.field_modulus) % self.field_modulus, + self.curve_name + ) + + def __truediv__(self, other: IntOrFQ) -> "FQ": + return self.__div__(other) + + def __rdiv__(self, other: IntOrFQ) -> "FQ": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise ValueError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return FQ( + prime_field_inv(self.n, self.field_modulus) * on % self.field_modulus, + self.curve_name + ) + + def __rtruediv__(self, other: IntOrFQ) -> "FQ": + return self.__rdiv__(other) + + def __pow__(self, other: int) -> "FQ": + if other == 0: + return FQ(1, self.curve_name) + elif other == 1: + return FQ(self.n, self.curve_name) + elif other % 2 == 0: + return (self * self) ** (other // 2) + else: + return ((self * self) ** int(other // 2)) * self + + def __eq__(self, other: IntOrFQ) -> bool: # type:ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 + if isinstance(other, FQ): + return self.n == other.n + elif isinstance(other, int): + return self.n == other + else: + raise ValueError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + def __ne__(self, other: IntOrFQ) -> bool: # type:ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 + return not self == other + + def __neg__(self) -> "FQ": + return FQ(-self.n, self.curve_name) + + def __repr__(self) -> str: + return repr(self.n) + + def __int__(self) -> int: + return self.n + + @classmethod + def one(cls, curve_name: str) -> "FQ": + return cls(1, curve_name) + + @classmethod + def zero(cls, curve_name: str) -> "FQ": + return cls(0, curve_name) + + +int_types_or_FQ = (int, FQ) + + +# A class for elements in polynomial extension fields +class FQP(object): + degree = 0 + curve_name = None + + def __init__(self, + coeffs: Sequence[IntOrFQ], + curve_name: str, + modulus_coeffs: Sequence[IntOrFQ]=None) -> None: + if len(coeffs) != len(modulus_coeffs): + raise Exception( + "coeffs and modulus_coeffs aren't of the same length" + ) + self.coeffs = tuple(FQ(c, curve_name) for c in coeffs) + self.curve_name = curve_name + # The coefficients of the modulus, without the leading [1] + self.modulus_coeffs = modulus_coeffs + # The degree of the extension field + self.degree = len(self.modulus_coeffs) + + def __add__(self, other: "FQP") -> "FQP": + if not isinstance(other, type(self)): + raise ValueError( + "Expected an FQP object, but got object of type {}" + .format(type(other)) + ) + + return type(self)([x + y for x, y in zip(self.coeffs, other.coeffs)], self.curve_name) + + def __sub__(self, other: "FQP") -> "FQP": + if not isinstance(other, type(self)): + raise ValueError( + "Expected an FQP object, but got object of type {}" + .format(type(other)) + ) + + return type(self)([x - y for x, y in zip(self.coeffs, other.coeffs)], self.curve_name) + + def __mul__(self, other: Union[int, "FQ", "FQP"]) -> "FQP": + if isinstance(other, int) or isinstance(other, FQ): + return type(self)([c * other for c in self.coeffs], self.curve_name) + elif isinstance(other, FQP): + b = [FQ(0, self.curve_name) for i in range(self.degree * 2 - 1)] + for i in range(self.degree): + for j in range(self.degree): + b[i + j] += self.coeffs[i] * other.coeffs[j] + while len(b) > self.degree: + exp, top = len(b) - self.degree - 1, b.pop() + for i in range(self.degree): + b[exp + i] -= top * FQ(self.modulus_coeffs[i], self.curve_name) + return type(self)(b, self.curve_name) + else: + raise ValueError( + "Expected an int or FQ object or FQP object, but got object of type {}" + .format(type(other)) + ) + + def __rmul__(self, other: Union[int, "FQ", "FQP"]) -> "FQP": + return self * other + + def __div__(self, other: Union[int, "FQ", "FQP"]) -> "FQP": + if isinstance(other, int_types_or_FQ): + return type(self)([c / other for c in self.coeffs], self.curve_name) + elif isinstance(other, FQP): + return self * other.inv() + else: + raise ValueError( + "Expected an int or FQ object or FQP object, but got object of type {}" + .format(type(other)) + ) + + def __truediv__(self, other: Union[int, "FQ", "FQP"]) -> "FQP": + return self.__div__(other) + + def __pow__(self, other: int) -> "FQP": + if other == 0: + return type(self)([1] + [0] * (self.degree - 1), self.curve_name) + elif other == 1: + return type(self)(self.coeffs, self.curve_name) + elif other % 2 == 0: + return (self * self) ** (other // 2) + else: + return ((self * self) ** int(other // 2)) * self + + # Extended euclidean algorithm used to find the modular inverse + def inv(self) -> "FQP": + lm, hm = ( + [1] + [0] * self.degree, + [0] * (self.degree + 1), + ) + low, high = ( + # Ignore mypy yelling about the inner types for the tuples being incompatible + cast(List[IntOrFQ], list(self.coeffs + (0,))), # type: ignore + cast(List[IntOrFQ], list(self.modulus_coeffs + (1,))), # type: ignore + ) + while deg(low): + r = cast(List[IntOrFQ], list(poly_rounded_div(high, low))) + r += [0] * (self.degree + 1 - len(r)) + nm = [x for x in hm] + new = [x for x in high] + if len(set( + [len(lm), len(hm), len(low), len(high), len(nm), len(new), self.degree + 1] + )) != 1: + raise Exception("Mismatch between the lengths of lm, hm, low, high, nm, new") + + for i in range(self.degree + 1): + for j in range(self.degree + 1 - i): + nm[i + j] -= lm[i] * int(r[j]) + new[i + j] -= low[i] * int(r[j]) + lm, low, hm, high = nm, new, lm, low + return type(self)(lm[:self.degree], self.curve_name) / low[0] + + def __repr__(self) -> str: + return repr(self.coeffs) + + def __eq__(self, other: "FQP") -> bool: # type: ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 + if not isinstance(other, type(self)): + raise ValueError( + "Expected an FQP object, but got object of type {}" + .format(type(other)) + ) + + for c1, c2 in zip(self.coeffs, other.coeffs): + if c1 != c2: + return False + return True + + def __ne__(self, other: "FQP") -> bool: # type: ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 + return not self == other + + def __neg__(self) -> "FQP": + return type(self)([-c for c in self.coeffs], self.curve_name) + + @classmethod + def one(cls, curve_name: str) -> "FQP": + return cls([1] + [0] * (cls.degree - 1), curve_name) + + @classmethod + def zero(cls, curve_name: str) -> "FQP": + return cls([0] * cls.degree, curve_name) + + +# The quadratic extension field +class FQ2(FQP): + degree = 2 + + def __init__(self, coeffs: Sequence[IntOrFQ], curve_name: str) -> None: + FQ2_MODULUS_COEFFS = field_properties[curve_name]["fq2_modulus_coeffs"] + super().__init__(coeffs, curve_name, FQ2_MODULUS_COEFFS) + + +# The 12th-degree extension field +class FQ12(FQP): + degree = 12 + + def __init__(self, coeffs: Sequence[IntOrFQ], curve_name: str) -> None: + FQ12_MODULUS_COEFFS = field_properties[curve_name]["fq12_modulus_coeffs"] + super().__init__(coeffs, curve_name, FQ12_MODULUS_COEFFS) diff --git a/py_ecc/field_properties.py b/py_ecc/field_properties.py new file mode 100644 index 00000000..3ca362e8 --- /dev/null +++ b/py_ecc/field_properties.py @@ -0,0 +1,28 @@ +from typing import ( + Dict, + Tuple, +) + +from mypy_extensions import TypedDict + + +Curve_Field_Properties = TypedDict('Curve_Field_Properties', + { + 'field_modulus': int, + 'fq2_modulus_coeffs': Tuple[int, ...], + 'fq12_modulus_coeffs': Tuple[int, ...], + }) +Field_Properties = Dict[str, Curve_Field_Properties] + +field_properties = { + "bn128": { + "field_modulus": 21888242871839275222246405745257275088696311157297823662689037894645226208583, # noqa: E501 + "fq2_modulus_coeffs": (1, 0), + "fq12_modulus_coeffs": (82, 0, 0, 0, 0, 0, -18, 0, 0, 0, 0, 0), # Implied + [1] + }, + "bls12_381": { + "field_modulus": 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787, # noqa: E501 + "fq2_modulus_coeffs": (1, 0), + "fq12_modulus_coeffs": (2, 0, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0), # Implied + [1] + }, +} # type: Field_Properties diff --git a/py_ecc/optimized_bls12_381/__init__.py b/py_ecc/optimized_bls12_381/__init__.py deleted file mode 100644 index 2f00e9a2..00000000 --- a/py_ecc/optimized_bls12_381/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -from __future__ import absolute_import - -from .optimized_field_elements import ( # noqa: F401 - field_modulus, - FQ, - FQP, - FQ2, - FQ12, -) -from .optimized_curve import ( # noqa: F401 - add, - double, - multiply, - is_inf, - is_on_curve, - eq, - neg, - twist, - b, - b2, - b12, - curve_order, - G1, - G2, - Z1, - Z2, - G12, - normalize, -) -from .optimized_pairing import ( # noqa: F401 - pairing, - final_exponentiate, -) diff --git a/py_ecc/optimized_bls12_381/optimized_curve.py b/py_ecc/optimized_bls12_381/optimized_curve.py deleted file mode 100644 index d6eda8bb..00000000 --- a/py_ecc/optimized_bls12_381/optimized_curve.py +++ /dev/null @@ -1,159 +0,0 @@ -from __future__ import absolute_import - -from .optimized_field_elements import ( - FQ2, - FQ12, - field_modulus, - FQ, -) - - -curve_order = 52435875175126190479447740508185965837690552500527637822603658699938581184513 - -# Curve order should be prime -assert pow(2, curve_order, curve_order) == 2 -# Curve order should be a factor of field_modulus**12 - 1 -assert (field_modulus ** 12 - 1) % curve_order == 0 - -# Curve is y**2 = x**3 + 4 -b = FQ(4) -# Twisted curve over FQ**2 -b2 = FQ2((4, 4)) -# Extension curve over FQ**12; same b value as over FQ -b12 = FQ12((4,) + (0,) * 11) - -# Generator for curve over FQ -G1 = ( - FQ(3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507), # noqa: E501 - FQ(1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569), # noqa: E501 - FQ(1), -) -# Generator for twisted curve over FQ2 -G2 = ( - FQ2(( - 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160, # noqa: E501 - 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758, # noqa: E501 - )), - FQ2(( - 1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905, # noqa: E501 - 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582, # noqa: E501 - )), - FQ2.one(), -) -# Point at infinity over FQ -Z1 = (FQ.one(), FQ.one(), FQ.zero()) -# Point at infinity for twisted curve over FQ2 -Z2 = (FQ2.one(), FQ2.one(), FQ2.zero()) - - -# Check if a point is the point at infinity -def is_inf(pt): - return pt[-1] == pt[-1].__class__.zero() - - -# Check that a point is on the curve defined by y**2 == x**3 + b -def is_on_curve(pt, b): - if is_inf(pt): - return True - x, y, z = pt - return y**2 * z - x**3 == b * z**3 - - -assert is_on_curve(G1, b) -assert is_on_curve(G2, b2) - - -# Elliptic curve doubling -def double(pt): - x, y, z = pt - W = 3 * x * x - S = y * z - B = x * y * S - H = W * W - 8 * B - S_squared = S * S - newx = 2 * H * S - newy = W * (4 * B - H) - 8 * y * y * S_squared - newz = 8 * S * S_squared - return newx, newy, newz - - -# Elliptic curve addition -def add(p1, p2): - one, zero = p1[0].__class__.one(), p1[0].__class__.zero() - if p1[2] == zero or p2[2] == zero: - return p1 if p2[2] == zero else p2 - x1, y1, z1 = p1 - x2, y2, z2 = p2 - U1 = y2 * z1 - U2 = y1 * z2 - V1 = x2 * z1 - V2 = x1 * z2 - if V1 == V2 and U1 == U2: - return double(p1) - elif V1 == V2: - return (one, one, zero) - U = U1 - U2 - V = V1 - V2 - V_squared = V * V - V_squared_times_V2 = V_squared * V2 - V_cubed = V * V_squared - W = z1 * z2 - A = U * U * W - V_cubed - 2 * V_squared_times_V2 - newx = V * A - newy = U * (V_squared_times_V2 - A) - V_cubed * U2 - newz = V_cubed * W - return (newx, newy, newz) - - -# Elliptic curve point multiplication -def multiply(pt, n): - if n == 0: - return (pt[0].__class__.one(), pt[0].__class__.one(), pt[0].__class__.zero()) - elif n == 1: - return pt - elif not n % 2: - return multiply(double(pt), n // 2) - else: - return add(multiply(double(pt), int(n // 2)), pt) - - -def eq(p1, p2): - x1, y1, z1 = p1 - x2, y2, z2 = p2 - return x1 * z2 == x2 * z1 and y1 * z2 == y2 * z1 - - -def normalize(pt): - x, y, z = pt - return (x / z, y / z) - - -# "Twist" a point in E(FQ2) into a point in E(FQ12) -w = FQ12([0, 1] + [0] * 10) - - -# Convert P => -P -def neg(pt): - if pt is None: - return None - x, y, z = pt - return (x, -y, z) - - -def twist(pt): - if pt is None: - return None - _x, _y, _z = pt - # Field isomorphism from Z[p] / x**2 to Z[p] / x**2 - 2*x + 2 - xcoeffs = [_x.coeffs[0] - _x.coeffs[1], _x.coeffs[1]] - ycoeffs = [_y.coeffs[0] - _y.coeffs[1], _y.coeffs[1]] - zcoeffs = [_z.coeffs[0] - _z.coeffs[1], _z.coeffs[1]] - nx = FQ12([xcoeffs[0]] + [0] * 5 + [xcoeffs[1]] + [0] * 5) - ny = FQ12([ycoeffs[0]] + [0] * 5 + [ycoeffs[1]] + [0] * 5) - nz = FQ12([zcoeffs[0]] + [0] * 5 + [zcoeffs[1]] + [0] * 5) - return (nx / w ** 2, ny / w**3, nz) - - -# Check that the twist creates a point that is on the curve -G12 = twist(G2) -assert is_on_curve(G12, b12) diff --git a/py_ecc/optimized_bls12_381/optimized_field_elements.py b/py_ecc/optimized_bls12_381/optimized_field_elements.py deleted file mode 100644 index 3f537bba..00000000 --- a/py_ecc/optimized_bls12_381/optimized_field_elements.py +++ /dev/null @@ -1,256 +0,0 @@ -from __future__ import absolute_import - - -field_modulus = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 # noqa: E501 -FQ12_modulus_coeffs = (2, 0, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0) # Implied + [1] -FQ12_mc_tuples = tuple((i, c) for i, c in enumerate(FQ12_modulus_coeffs) if c) - - -# Extended euclidean algorithm to find modular inverses for -# integers -def prime_field_inv(a, n): - if a == 0: - return 0 - lm, hm = 1, 0 - low, high = a % n, n - while low > 1: - r = high // low - nm, new = hm - lm * r, high - low * r - lm, low, hm, high = nm, new, lm, low - return lm % n - - -# A class for field elements in FQ. Wrap a number in this class, -# and it becomes a field element. -class FQ(object): - def __init__(self, n): - if isinstance(n, self.__class__): - self.n = n.n - else: - self.n = n % field_modulus - assert isinstance(self.n, int) - - def __add__(self, other): - on = other.n if isinstance(other, FQ) else other - return FQ((self.n + on) % field_modulus) - - def __mul__(self, other): - on = other.n if isinstance(other, FQ) else other - return FQ((self.n * on) % field_modulus) - - def __rmul__(self, other): - return self * other - - def __radd__(self, other): - return self + other - - def __rsub__(self, other): - on = other.n if isinstance(other, FQ) else other - return FQ((on - self.n) % field_modulus) - - def __sub__(self, other): - on = other.n if isinstance(other, FQ) else other - return FQ((self.n - on) % field_modulus) - - def __div__(self, other): - on = other.n if isinstance(other, FQ) else other - assert isinstance(on, int) - return FQ(self.n * prime_field_inv(on, field_modulus) % field_modulus) - - def __truediv__(self, other): - return self.__div__(other) - - def __rdiv__(self, other): - on = other.n if isinstance(other, FQ) else other - assert isinstance(on, int), on - return FQ(prime_field_inv(self.n, field_modulus) * on % field_modulus) - - def __rtruediv__(self, other): - return self.__rdiv__(other) - - def __pow__(self, other): - if other == 0: - return FQ(1) - elif other == 1: - return FQ(self.n) - elif other % 2 == 0: - return (self * self) ** (other // 2) - else: - return ((self * self) ** int(other // 2)) * self - - def __eq__(self, other): - if isinstance(other, FQ): - return self.n == other.n - else: - return self.n == other - - def __ne__(self, other): - return not self == other - - def __neg__(self): - return FQ(-self.n) - - def __repr__(self): - return repr(self.n) - - @classmethod - def one(cls): - return cls(1) - - @classmethod - def zero(cls): - return cls(0) - - -# Utility methods for polynomial math -def deg(p): - d = len(p) - 1 - while p[d] == 0 and d: - d -= 1 - return d - - -def poly_rounded_div(a, b): - dega = deg(a) - degb = deg(b) - temp = [x for x in a] - o = [0 for x in a] - for i in range(dega - degb, -1, -1): - o[i] = (o[i] + temp[degb + i] * prime_field_inv(b[degb], field_modulus)) - for c in range(degb + 1): - temp[c + i] = (temp[c + i] - o[c]) - return tuple(x % field_modulus for x in o[:deg(o) + 1]) - - -# A class for elements in polynomial extension fields -class FQP(object): - def __init__(self, coeffs, modulus_coeffs): - assert len(coeffs) == len(modulus_coeffs) - self.coeffs = tuple(coeffs) - # The coefficients of the modulus, without the leading [1] - self.modulus_coeffs = tuple(modulus_coeffs) - # The degree of the extension field - self.degree = len(self.modulus_coeffs) - - def __add__(self, other): - assert isinstance(other, self.__class__) - return self.__class__(tuple( - (x + y) % field_modulus - for x, y - in zip(self.coeffs, other.coeffs) - )) - - def __sub__(self, other): - assert isinstance(other, self.__class__) - return self.__class__(tuple( - (x - y) % field_modulus - for x, y - in zip(self.coeffs, other.coeffs) - )) - - def __mul__(self, other): - if isinstance(other, int): - return self.__class__(tuple(c * other % field_modulus for c in self.coeffs)) - else: - # assert isinstance(other, self.__class__) - b = [0] * (self.degree * 2 - 1) - inner_enumerate = list(enumerate(other.coeffs)) - for i, eli in enumerate(self.coeffs): - for j, elj in inner_enumerate: - b[i + j] += eli * elj - # MID = len(self.coeffs) // 2 - for exp in range(self.degree - 2, -1, -1): - top = b.pop() - for i, c in self.mc_tuples: - b[exp + i] -= top * c - return self.__class__(tuple(x % field_modulus for x in b)) - - def __rmul__(self, other): - return self * other - - def __div__(self, other): - if isinstance(other, int): - return self.__class__([ - c * prime_field_inv(other, field_modulus) % field_modulus - for c - in self.coeffs - ]) - else: - assert isinstance(other, self.__class__) - return self * other.inv() - - def __truediv__(self, other): - return self.__div__(other) - - def __pow__(self, other): - o = self.__class__([1] + [0] * (self.degree - 1)) - t = self - while other > 0: - if other & 1: - o = o * t - other >>= 1 - t = t * t - return o - - # Extended euclidean algorithm used to find the modular inverse - def inv(self): - lm, hm = [1] + [0] * self.degree, [0] * (self.degree + 1) - low, high = self.coeffs + (0,), self.modulus_coeffs + (1,) - while deg(low): - r = list(poly_rounded_div(high, low)) - r += [0] * (self.degree + 1 - len(r)) - nm = [x for x in hm] - new = [x for x in high] - # assert len(lm) == len(hm) == len(low) == len(high) == len(nm) == len(new) == self.degree + 1 # noqa: E501 - for i in range(self.degree + 1): - for j in range(self.degree + 1 - i): - nm[i + j] -= lm[i] * r[j] - new[i + j] -= low[i] * r[j] - nm = [x % field_modulus for x in nm] - new = [x % field_modulus for x in new] - lm, low, hm, high = nm, new, lm, low - return self.__class__(lm[:self.degree]) / low[0] - - def __repr__(self): - return repr(self.coeffs) - - def __eq__(self, other): - assert isinstance(other, self.__class__) - for c1, c2 in zip(self.coeffs, other.coeffs): - if c1 != c2: - return False - return True - - def __ne__(self, other): - return not self == other - - def __neg__(self): - return self.__class__([-c for c in self.coeffs]) - - @classmethod - def one(cls): - return cls([1] + [0] * (cls.degree - 1)) - - @classmethod - def zero(cls): - return cls([0] * cls.degree) - - -# The quadratic extension field -class FQ2(FQP): - def __init__(self, coeffs): - self.coeffs = tuple(coeffs) - self.modulus_coeffs = (1, 0) - self.mc_tuples = [(0, 1)] - self.degree = 2 - self.__class__.degree = 2 - - -# The 12th-degree extension field -class FQ12(FQP): - def __init__(self, coeffs): - self.coeffs = tuple(coeffs) - self.modulus_coeffs = FQ12_modulus_coeffs - self.mc_tuples = FQ12_mc_tuples - self.degree = 12 - self.__class__.degree = 12 diff --git a/py_ecc/optimized_bls12_381/optimized_pairing.py b/py_ecc/optimized_bls12_381/optimized_pairing.py deleted file mode 100644 index 430e3a4a..00000000 --- a/py_ecc/optimized_bls12_381/optimized_pairing.py +++ /dev/null @@ -1,147 +0,0 @@ -from __future__ import absolute_import - -from .optimized_curve import ( - double, - add, - multiply, - is_on_curve, - neg, - twist, - b, - b2, - curve_order, - G1, - normalize, -) -from .optimized_field_elements import ( - FQ12, - field_modulus, - FQ, -) - - -ate_loop_count = 15132376222941642752 -log_ate_loop_count = 62 -pseudo_binary_encoding = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1 -] - - -assert sum([e * 2**i for i, e in enumerate(pseudo_binary_encoding)]) == ate_loop_count - - -def normalize1(p): - x, y = normalize(p) - - return x, y, x.__class__.one() - - -# Create a function representing the line between P1 and P2, -# and evaluate it at T. Returns a numerator and a denominator -# to avoid unneeded divisions -def linefunc(P1, P2, T): - zero = P1[0].__class__.zero() - x1, y1, z1 = P1 - x2, y2, z2 = P2 - xt, yt, zt = T - # points in projective coords: (x / z, y / z) - # hence, m = (y2/z2 - y1/z1) / (x2/z2 - x1/z1) - # multiply numerator and denominator by z1z2 to get values below - m_numerator = y2 * z1 - y1 * z2 - m_denominator = x2 * z1 - x1 * z2 - if m_denominator != zero: - # m * ((xt/zt) - (x1/z1)) - ((yt/zt) - (y1/z1)) - return m_numerator * (xt * z1 - x1 * zt) - m_denominator * (yt * z1 - y1 * zt), \ - m_denominator * zt * z1 - elif m_numerator == zero: - # m = 3(x/z)^2 / 2(y/z), multiply num and den by z**2 - m_numerator = 3 * x1 * x1 - m_denominator = 2 * y1 * z1 - return m_numerator * (xt * z1 - x1 * zt) - m_denominator * (yt * z1 - y1 * zt), \ - m_denominator * zt * z1 - else: - return xt * z1 - x1 * zt, z1 * zt - - -def cast_point_to_fq12(pt): - if pt is None: - return None - x, y, z = pt - return (FQ12([x.n] + [0] * 11), FQ12([y.n] + [0] * 11), FQ12([z.n] + [0] * 11)) - - -# Check consistency of the "line function" -one, two, three = G1, double(G1), multiply(G1, 3) -negone, negtwo, negthree = ( - multiply(G1, curve_order - 1), - multiply(G1, curve_order - 2), - multiply(G1, curve_order - 3), -) - - -assert linefunc(one, two, one)[0] == FQ(0) -assert linefunc(one, two, two)[0] == FQ(0) -assert linefunc(one, two, three)[0] != FQ(0) -assert linefunc(one, two, negthree)[0] == FQ(0) -assert linefunc(one, negone, one)[0] == FQ(0) -assert linefunc(one, negone, negone)[0] == FQ(0) -assert linefunc(one, negone, two)[0] != FQ(0) -assert linefunc(one, one, one)[0] == FQ(0) -assert linefunc(one, one, two)[0] != FQ(0) -assert linefunc(one, one, negtwo)[0] == FQ(0) - - -# Main miller loop -def miller_loop(Q, P, final_exponentiate=True): - if Q is None or P is None: - return FQ12.one() - R = Q - f_num, f_den = FQ12.one(), FQ12.one() - # for i in range(log_ate_loop_count, -1, -1): - for v in pseudo_binary_encoding[62::-1]: - _n, _d = linefunc(R, R, P) - f_num = f_num * f_num * _n - f_den = f_den * f_den * _d - R = double(R) - if v == 1: - _n, _d = linefunc(R, Q, P) - f_num = f_num * _n - f_den = f_den * _d - R = add(R, Q) - elif v == -1: - nQ = neg(Q) - _n, _d = linefunc(R, nQ, P) - f_num = f_num * _n - f_den = f_den * _d - R = add(R, nQ) - # assert R == multiply(Q, ate_loop_count) - # Q1 = (Q[0] ** field_modulus, Q[1] ** field_modulus, Q[2] ** field_modulus) - # assert is_on_curve(Q1, b12) - # nQ2 = (Q1[0] ** field_modulus, -Q1[1] ** field_modulus, Q1[2] ** field_modulus) - # assert is_on_curve(nQ2, b12) - # _n1, _d1 = linefunc(R, Q1, P) - # R = add(R, Q1) - # _n2, _d2 = linefunc(R, nQ2, P) - # f = f_num * _n1 * _n2 / (f_den * _d1 * _d2) - f = f_num / f_den - # R = add(R, nQ2) This line is in many specifications but it technically does nothing - if final_exponentiate: - return f ** ((field_modulus ** 12 - 1) // curve_order) - else: - return f - - -# Pairing computation -def pairing(Q, P, final_exponentiate=True): - assert is_on_curve(Q, b2) - assert is_on_curve(P, b) - if P[-1] == P[-1].__class__.zero() or Q[-1] == Q[-1].__class__.zero(): - return FQ12.one() - return miller_loop(twist(Q), cast_point_to_fq12(P), final_exponentiate=final_exponentiate) - - -def final_exponentiate(p): - return p ** ((field_modulus ** 12 - 1) // curve_order) diff --git a/py_ecc/optimized_bn128/__init__.py b/py_ecc/optimized_bn128/__init__.py deleted file mode 100644 index 2f00e9a2..00000000 --- a/py_ecc/optimized_bn128/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -from __future__ import absolute_import - -from .optimized_field_elements import ( # noqa: F401 - field_modulus, - FQ, - FQP, - FQ2, - FQ12, -) -from .optimized_curve import ( # noqa: F401 - add, - double, - multiply, - is_inf, - is_on_curve, - eq, - neg, - twist, - b, - b2, - b12, - curve_order, - G1, - G2, - Z1, - Z2, - G12, - normalize, -) -from .optimized_pairing import ( # noqa: F401 - pairing, - final_exponentiate, -) diff --git a/py_ecc/optimized_bn128/optimized_curve.py b/py_ecc/optimized_bn128/optimized_curve.py deleted file mode 100644 index 0a3caa6f..00000000 --- a/py_ecc/optimized_bn128/optimized_curve.py +++ /dev/null @@ -1,167 +0,0 @@ -from __future__ import absolute_import - -from typing import ( - cast, -) - -from .optimized_field_elements import ( - field_modulus, - FQ, - FQ2, - FQ12, - FQP, -) - -from py_ecc.typing import ( - Optimized_Field, - Optimized_Point2D, - Optimized_Point3D, -) - - -curve_order = 21888242871839275222246405745257275088548364400416034343698204186575808495617 - -# Curve order should be prime -assert pow(2, curve_order, curve_order) == 2 -# Curve order should be a factor of field_modulus**12 - 1 -assert (field_modulus ** 12 - 1) % curve_order == 0 - -# Curve is y**2 = x**3 + 3 -b = FQ(3) -# Twisted curve over FQ**2 -b2 = FQ2([3, 0]) / FQ2([9, 1]) -# Extension curve over FQ**12; same b value as over FQ -b12 = FQ12([3] + [0] * 11) - -# Generator for curve over FQ -G1 = (FQ(1), FQ(2), FQ(1)) -# Generator for twisted curve over FQ2 -G2 = ( - FQ2([ - 10857046999023057135944570762232829481370756359578518086990519993285655852781, - 11559732032986387107991004021392285783925812861821192530917403151452391805634 - ]), - FQ2([ - 8495653923123431417604973247489272438418190587263600148770280649306958101930, - 4082367875863433681332203403145435568316851327593401208105741076214120093531, - ]), - FQ2.one(), -) -# Point at infinity over FQ -Z1 = (FQ.one(), FQ.one(), FQ.zero()) -# Point at infinity for twisted curve over FQ2 -Z2 = (FQ2.one(), FQ2.one(), FQ2.zero()) - - -# Check if a point is the point at infinity -def is_inf(pt: Optimized_Point3D[Optimized_Field]) -> bool: - return pt[-1] == (type(pt[-1]).zero()) - - -# Check that a point is on the curve defined by y**2 == x**3 + b -def is_on_curve(pt: Optimized_Point3D[Optimized_Field], b: Optimized_Field) -> bool: - if is_inf(pt): - return True - x, y, z = pt - return y**2 * z - x**3 == b * z**3 - - -assert is_on_curve(G1, b) -assert is_on_curve(G2, b2) - - -# Elliptic curve doubling -def double(pt: Optimized_Point3D[Optimized_Field]) -> Optimized_Point3D[Optimized_Field]: - x, y, z = pt - W = 3 * x * x - S = y * z - B = x * y * S - H = W * W - 8 * B - S_squared = S * S - newx = 2 * H * S - newy = W * (4 * B - H) - 8 * y * y * S_squared - newz = 8 * S * S_squared - return (newx, newy, newz) - - -# Elliptic curve addition -def add(p1: Optimized_Point3D[Optimized_Field], - p2: Optimized_Point3D[Optimized_Field]) -> Optimized_Point3D[Optimized_Field]: - one, zero = type(p1[0]).one(), type(p1[0]).zero() - if p1[2] == zero or p2[2] == zero: - return p1 if p2[2] == zero else p2 - x1, y1, z1 = p1 - x2, y2, z2 = p2 - U1 = y2 * z1 - U2 = y1 * z2 - V1 = x2 * z1 - V2 = x1 * z2 - if V1 == V2 and U1 == U2: - return double(p1) - elif V1 == V2: - return (one, one, zero) - U = U1 - U2 - V = V1 - V2 - V_squared = V * V - V_squared_times_V2 = V_squared * V2 - V_cubed = V * V_squared - W = z1 * z2 - A = U * U * W - V_cubed - 2 * V_squared_times_V2 - newx = V * A - newy = U * (V_squared_times_V2 - A) - V_cubed * U2 - newz = V_cubed * W - return (newx, newy, newz) - - -# Elliptic curve point multiplication -def multiply(pt: Optimized_Point3D[Optimized_Field], n: int) -> Optimized_Point3D[Optimized_Field]: - if n == 0: - return (type(pt[0]).one(), type(pt[0]).one(), type(pt[0]).zero()) - elif n == 1: - return pt - elif not n % 2: - return multiply(double(pt), n // 2) - else: - return add(multiply(double(pt), int(n // 2)), pt) - - -def eq(p1: Optimized_Point3D[Optimized_Field], p2: Optimized_Point3D[Optimized_Field]) -> bool: - x1, y1, z1 = p1 - x2, y2, z2 = p2 - return x1 * z2 == x2 * z1 and y1 * z2 == y2 * z1 - - -def normalize(pt: Optimized_Point3D[Optimized_Field]) -> Optimized_Point2D[Optimized_Field]: - x, y, z = pt - return (x / z, y / z) - - -# "Twist" a point in E(FQ2) into a point in E(FQ12) -w = FQ12([0, 1] + [0] * 10) - - -# Convert P => -P -def neg(pt: Optimized_Point3D[Optimized_Field]) -> Optimized_Point3D[Optimized_Field]: - if pt is None: - return None - x, y, z = pt - return (x, -y, z) - - -def twist(pt: Optimized_Point3D[FQP]) -> Optimized_Point3D[FQP]: - if pt is None: - return None - _x, _y, _z = pt - # Field isomorphism from Z[p] / x**2 to Z[p] / x**2 - 18*x + 82 - xcoeffs = [_x.coeffs[0] - _x.coeffs[1] * 9, _x.coeffs[1]] - ycoeffs = [_y.coeffs[0] - _y.coeffs[1] * 9, _y.coeffs[1]] - zcoeffs = [_z.coeffs[0] - _z.coeffs[1] * 9, _z.coeffs[1]] - nx = FQ12([xcoeffs[0]] + [0] * 5 + [xcoeffs[1]] + [0] * 5) - ny = FQ12([ycoeffs[0]] + [0] * 5 + [ycoeffs[1]] + [0] * 5) - nz = FQ12([zcoeffs[0]] + [0] * 5 + [zcoeffs[1]] + [0] * 5) - return (nx * w ** 2, ny * w**3, nz) - - -# Check that the twist creates a point that is on the curve -G12 = twist(cast(Optimized_Point3D[FQ2], G2)) -assert is_on_curve(G12, b12) diff --git a/py_ecc/optimized_bn128/optimized_field_elements.py b/py_ecc/optimized_bn128/optimized_field_elements.py deleted file mode 100644 index b414c02e..00000000 --- a/py_ecc/optimized_bn128/optimized_field_elements.py +++ /dev/null @@ -1,292 +0,0 @@ -from __future__ import absolute_import - -from typing import ( # noqa: F401 - cast, - List, - Sequence, - Tuple, - Union, -) - -field_modulus = 21888242871839275222246405745257275088696311157297823662689037894645226208583 -FQ2_MODULUS_COEFFS = [1, 0] -FQ12_MODULUS_COEFFS = [82, 0, 0, 0, 0, 0, -18, 0, 0, 0, 0, 0] # Implied + [1] -FQ2_MC_TUPLES = [(0, 1)] -FQ12_MC_TUPLES = [(i, c) for i, c in enumerate(FQ12_MODULUS_COEFFS) if c] - - -# Extended euclidean algorithm to find modular inverses for -# integers -def prime_field_inv(a: int, n: int) -> int: - if a == 0: - return 0 - lm, hm = 1, 0 - low, high = a % n, n - while low > 1: - r = high // low - nm, new = hm - lm * r, high - low * r - lm, low, hm, high = nm, new, lm, low - return lm % n - - -IntOrFQ = Union[int, "FQ"] - - -# A class for field elements in FQ. Wrap a number in this class, -# and it becomes a field element. -class FQ(object): - n = None # type: int - - def __init__(self, val: IntOrFQ) -> None: - if isinstance(val, FQ): - self.n = val.n - else: - self.n = val % field_modulus - assert isinstance(self.n, int) - - def __add__(self, other: IntOrFQ) -> "FQ": - on = other.n if isinstance(other, FQ) else other - return FQ((self.n + on) % field_modulus) - - def __mul__(self, other: IntOrFQ) -> "FQ": - on = other.n if isinstance(other, FQ) else other - return FQ((self.n * on) % field_modulus) - - def __rmul__(self, other: IntOrFQ) -> "FQ": - return self * other - - def __radd__(self, other: IntOrFQ) -> "FQ": - return self + other - - def __rsub__(self, other: IntOrFQ) -> "FQ": - on = other.n if isinstance(other, FQ) else other - return FQ((on - self.n) % field_modulus) - - def __sub__(self, other: IntOrFQ) -> "FQ": - on = other.n if isinstance(other, FQ) else other - return FQ((self.n - on) % field_modulus) - - def __mod__(self, other: Union[int, "FQ"]) -> "FQ": - return self.__mod__(other) - - def __div__(self, other: IntOrFQ) -> "FQ": - on = other.n if isinstance(other, FQ) else other - assert isinstance(on, int) - return FQ(self.n * prime_field_inv(on, field_modulus) % field_modulus) - - def __truediv__(self, other: IntOrFQ) -> "FQ": - return self.__div__(other) - - def __rdiv__(self, other: IntOrFQ) -> "FQ": - on = other.n if isinstance(other, FQ) else other - assert isinstance(on, int), on - return FQ(prime_field_inv(self.n, field_modulus) * on % field_modulus) - - def __rtruediv__(self, other: IntOrFQ) -> "FQ": - return self.__rdiv__(other) - - def __pow__(self, other: int) -> "FQ": - if other == 0: - return FQ(1) - elif other == 1: - return FQ(self.n) - elif other % 2 == 0: - return (self * self) ** (other // 2) - else: - return ((self * self) ** int(other // 2)) * self - - def __eq__(self, other: IntOrFQ) -> bool: # type:ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 - if isinstance(other, FQ): - return self.n == other.n - else: - return self.n == other - - def __ne__(self, other: IntOrFQ) -> bool: # type:ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 - return not self == other - - def __neg__(self) -> "FQ": - return FQ(-self.n) - - def __repr__(self) -> str: - return repr(self.n) - - def __int__(self) -> int: - return self.n - - @classmethod - def one(cls) -> "FQ": - return cls(1) - - @classmethod - def zero(cls) -> "FQ": - return cls(0) - - -# Utility methods for polynomial math -def deg(p: Sequence[IntOrFQ]) -> int: - d = len(p) - 1 - while p[d] == 0 and d: - d -= 1 - return d - - -def poly_rounded_div(a: Sequence[IntOrFQ], - b: Sequence[IntOrFQ]) -> Sequence[IntOrFQ]: - dega = deg(a) - degb = deg(b) - temp = [x for x in a] - o = [0 for x in a] - for i in range(dega - degb, -1, -1): - o[i] = int(o[i] + temp[degb + i] * prime_field_inv(int(b[degb]), field_modulus)) - for c in range(degb + 1): - temp[c + i] = (temp[c + i] - o[c]) - return [x % field_modulus for x in o[:deg(o) + 1]] - - -# A class for elements in polynomial extension fields -class FQP(object): - degree = 0 # type: int - mc_tuples = None # type: List[Tuple[int, int]] - - def __init__(self, - coeffs: Sequence[IntOrFQ], - modulus_coeffs: Sequence[IntOrFQ]=None) -> None: - assert len(coeffs) == len(modulus_coeffs) - self.coeffs = coeffs - # The coefficients of the modulus, without the leading [1] - self.modulus_coeffs = modulus_coeffs - # The degree of the extension field - self.degree = len(self.modulus_coeffs) - - def __add__(self, other: "FQP") -> "FQP": - assert isinstance(other, type(self)) - return type(self)([ - int(x + y) % field_modulus - for x, y - in zip(self.coeffs, other.coeffs) - ]) - - def __sub__(self, other: "FQP") -> "FQP": - assert isinstance(other, type(self)) - return type(self)([ - int(x - y) % field_modulus - for x, y - in zip(self.coeffs, other.coeffs) - ]) - - def __mod__(self, other: Union[int, "FQP"]) -> "FQP": - return self.__mod__(other) - - def __mul__(self, other: Union[int, "FQP"]) -> "FQP": - if isinstance(other, int): - return type(self)([int(c) * other % field_modulus for c in self.coeffs]) - else: - # assert isinstance(other, self.__class__) - b = [0] * (self.degree * 2 - 1) - inner_enumerate = list(enumerate(other.coeffs)) - for i, eli in enumerate(self.coeffs): - for j, elj in inner_enumerate: - b[i + j] += int(eli * elj) - # MID = len(self.coeffs) // 2 - for exp in range(self.degree - 2, -1, -1): - top = b.pop() - for i, c in self.mc_tuples: - b[exp + i] -= top * c - return type(self)([x % field_modulus for x in b]) - - def __rmul__(self, other: Union[int, "FQP"]) -> "FQP": - return self * other - - def __div__(self, other: Union[int, "FQ", "FQP"]) -> "FQP": - if isinstance(other, int): - return type(self)([ - int(c) * prime_field_inv(other, field_modulus) % field_modulus - for c - in self.coeffs - ]) - else: - assert isinstance(other, type(self)) - return self * other.inv() - - def __truediv__(self, other: Union[int, "FQ", "FQP"]) -> "FQP": - return self.__div__(other) - - def __pow__(self, other: int) -> "FQP": - o = type(self)([1] + [0] * (self.degree - 1)) - t = self - while other > 0: - if other & 1: - o = o * t - other >>= 1 - t = t * t - return o - - # Extended euclidean algorithm used to find the modular inverse - def inv(self) -> "FQP": - lm, hm = [1] + [0] * self.degree, [0] * (self.degree + 1) - low, high = ( - # Ignore mypy yelling about the inner types for the lists being incompatible - cast(List[IntOrFQ], list(self.coeffs + [0])), # type: ignore - cast(List[IntOrFQ], list(self.modulus_coeffs + [1])), # type: ignore - ) - low, high = list(self.coeffs + [0]), self.modulus_coeffs + [1] # type: ignore - while deg(low): - r = cast(List[IntOrFQ], poly_rounded_div(high, low)) - r += [0] * (self.degree + 1 - len(r)) - nm = [x for x in hm] - new = [x for x in high] - # assert len(lm) == len(hm) == len(low) == len(high) == len(nm) == len(new) == self.degree + 1 # noqa: E501 - for i in range(self.degree + 1): - for j in range(self.degree + 1 - i): - nm[i + j] -= lm[i] * int(r[j]) - new[i + j] -= low[i] * r[j] - nm = [x % field_modulus for x in nm] - new = [int(x) % field_modulus for x in new] - lm, low, hm, high = nm, new, lm, low - return type(self)(lm[:self.degree]) / low[0] - - def __repr__(self) -> str: - return repr(self.coeffs) - - def __eq__(self, other: "FQP") -> bool: # type: ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 - assert isinstance(other, type(self)) - for c1, c2 in zip(self.coeffs, other.coeffs): - if c1 != c2: - return False - return True - - def __ne__(self, other: "FQP") -> bool: # type: ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 - return not self == other - - def __neg__(self) -> "FQP": - return type(self)([-c for c in self.coeffs]) - - @classmethod - def one(cls) -> "FQP": - return cls([1] + [0] * (cls.degree - 1)) - - @classmethod - def zero(cls) -> "FQP": - return cls([0] * cls.degree) - - -# The quadratic extension field -class FQ2(FQP): - degree = 2 - mc_tuples = FQ2_MC_TUPLES - - def __init__(self, coeffs: Sequence[IntOrFQ]) -> None: - super().__init__(coeffs, FQ2_MODULUS_COEFFS) - assert self.degree == 2 - assert self.mc_tuples == FQ2_MC_TUPLES - - -# The 12th-degree extension field -class FQ12(FQP): - degree = 12 - mc_tuples = FQ12_MC_TUPLES - - def __init__(self, coeffs: Sequence[IntOrFQ]) -> None: - super().__init__(coeffs, FQ12_MODULUS_COEFFS) - assert self.degree == 12 - assert self.mc_tuples == FQ12_MC_TUPLES diff --git a/py_ecc/optimized_bn128/optimized_pairing.py b/py_ecc/optimized_bn128/optimized_pairing.py deleted file mode 100644 index 8289d548..00000000 --- a/py_ecc/optimized_bn128/optimized_pairing.py +++ /dev/null @@ -1,160 +0,0 @@ -from __future__ import absolute_import - -from py_ecc.typing import ( - Optimized_Field, - Optimized_FQPoint3D, - Optimized_FQ2Point3D, - Optimized_Point2D, - Optimized_Point3D, -) - -from .optimized_curve import ( - double, - add, - multiply, - is_on_curve, - neg, - twist, - b, - b2, - curve_order, - G1, - normalize, -) -from .optimized_field_elements import ( - field_modulus, - FQ, - FQ12, - FQP, -) - - -ate_loop_count = 29793968203157093288 -log_ate_loop_count = 63 -pseudo_binary_encoding = [ - 0, 0, 0, 1, 0, 1, 0, -1, 0, 0, 1, -1, 0, 0, 1, 0, - 0, 1, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 0, 0, 1, 1, - 1, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, 0, 1, - 1, 0, 0, -1, 0, 0, 0, 1, 1, 0, -1, 0, 0, 1, 0, 1, 1, -] - - -assert sum([e * 2**i for i, e in enumerate(pseudo_binary_encoding)]) == ate_loop_count - - -def normalize1(p: Optimized_Point3D[Optimized_Field]) -> Optimized_Point3D[Optimized_Field]: - x, y = normalize(p) - - return x, y, type(x).one() - - -# Create a function representing the line between P1 and P2, -# and evaluate it at T. Returns a numerator and a denominator -# to avoid unneeded divisions -def linefunc(P1: Optimized_Point3D[Optimized_Field], - P2: Optimized_Point3D[Optimized_Field], - T: Optimized_Point3D[Optimized_Field]) -> Optimized_Point2D[Optimized_Field]: - zero = type(P1[0]).zero() - x1, y1, z1 = P1 - x2, y2, z2 = P2 - xt, yt, zt = T - # points in projective coords: (x / z, y / z) - # hence, m = (y2/z2 - y1/z1) / (x2/z2 - x1/z1) - # multiply numerator and denominator by z1z2 to get values below - m_numerator = y2 * z1 - y1 * z2 - m_denominator = x2 * z1 - x1 * z2 - if m_denominator != zero: - # m * ((xt/zt) - (x1/z1)) - ((yt/zt) - (y1/z1)) - return m_numerator * (xt * z1 - x1 * zt) - m_denominator * (yt * z1 - y1 * zt), \ - m_denominator * zt * z1 - elif m_numerator == zero: - # m = 3(x/z)^2 / 2(y/z), multiply num and den by z**2 - m_numerator = 3 * x1 * x1 - m_denominator = 2 * y1 * z1 - return m_numerator * (xt * z1 - x1 * zt) - m_denominator * (yt * z1 - y1 * zt), \ - m_denominator * zt * z1 - else: - return xt * z1 - x1 * zt, z1 * zt - - -def cast_point_to_fq12(pt: Optimized_Point3D[FQ]) -> Optimized_Point3D[FQ12]: - if pt is None: - return None - x, y, z = pt - return (FQ12([x.n] + [0] * 11), FQ12([y.n] + [0] * 11), FQ12([z.n] + [0] * 11)) - - -# Check consistency of the "line function" -one, two, three = G1, double(G1), multiply(G1, 3) -negone, negtwo, negthree = ( - multiply(G1, curve_order - 1), - multiply(G1, curve_order - 2), - multiply(G1, curve_order - 3), -) - - -assert linefunc(one, two, one)[0] == FQ(0) -assert linefunc(one, two, two)[0] == FQ(0) -assert linefunc(one, two, three)[0] != FQ(0) -assert linefunc(one, two, negthree)[0] == FQ(0) -assert linefunc(one, negone, one)[0] == FQ(0) -assert linefunc(one, negone, negone)[0] == FQ(0) -assert linefunc(one, negone, two)[0] != FQ(0) -assert linefunc(one, one, one)[0] == FQ(0) -assert linefunc(one, one, two)[0] != FQ(0) -assert linefunc(one, one, negtwo)[0] == FQ(0) - - -# Main miller loop -def miller_loop(Q: Optimized_Point3D[FQP], - P: Optimized_Point3D[FQP], - final_exponentiate: bool=True) -> FQP: - if Q is None or P is None: - return FQ12.one() - R = Q # type: Optimized_Point3D[FQP] - f_num, f_den = FQ12.one(), FQ12.one() - # for i in range(log_ate_loop_count, -1, -1): - for v in pseudo_binary_encoding[63::-1]: - _n, _d = linefunc(R, R, P) - f_num = f_num * f_num * _n - f_den = f_den * f_den * _d - R = double(R) - # if ate_loop_count & (2**i): - if v == 1: - _n, _d = linefunc(R, Q, P) - f_num = f_num * _n - f_den = f_den * _d - R = add(R, Q) - elif v == -1: - nQ = neg(Q) - _n, _d = linefunc(R, nQ, P) - f_num = f_num * _n - f_den = f_den * _d - R = add(R, nQ) - # assert R == multiply(Q, ate_loop_count) - Q1 = (Q[0] ** field_modulus, Q[1] ** field_modulus, Q[2] ** field_modulus) - # assert is_on_curve(Q1, b12) - nQ2 = (Q1[0] ** field_modulus, -Q1[1] ** field_modulus, Q1[2] ** field_modulus) - # assert is_on_curve(nQ2, b12) - _n1, _d1 = linefunc(R, Q1, P) - R = add(R, Q1) - _n2, _d2 = linefunc(R, nQ2, P) - f = f_num * _n1 * _n2 / (f_den * _d1 * _d2) - # R = add(R, nQ2) This line is in many specifications but it technically does nothing - if final_exponentiate: - return f ** ((field_modulus ** 12 - 1) // curve_order) - else: - return f - - -# Pairing computation -def pairing(Q: Optimized_FQ2Point3D, P: Optimized_FQPoint3D, final_exponentiate: bool=True) -> FQP: - assert is_on_curve(Q, b2) - assert is_on_curve(P, b) - if P[-1] == (type(P[-1]).zero()) or Q[-1] == (type(Q[-1]).zero()): - return FQ12.one() - return miller_loop(twist(Q), cast_point_to_fq12(P), final_exponentiate=final_exponentiate) - - -def final_exponentiate(p: Optimized_Field) -> Optimized_Field: - return p ** ((field_modulus ** 12 - 1) // curve_order) diff --git a/py_ecc/optimized_field_elements.py b/py_ecc/optimized_field_elements.py new file mode 100644 index 00000000..c90d600d --- /dev/null +++ b/py_ecc/optimized_field_elements.py @@ -0,0 +1,386 @@ +from typing import ( # noqa: F401 + cast, + List, + Sequence, + Tuple, + Union, +) + +from py_ecc.field_properties import ( + field_properties, +) + +from py_ecc.utils import ( + deg, + prime_field_inv, +) + + +IntOrFQ = Union[int, "FQ"] + + +# A class for field elements in FQ. Wrap a number in this class, +# and it becomes a field element. +class FQ(object): + n = None # type: int + field_modulus = None + curve_name = None + + def __init__(self, val: IntOrFQ, curve_name: str) -> None: + """ + curve_name can be either 'bn128' or 'bls12_381' + This is needed to obtain field_modulus, FQ2_MODULUS_COEFFS + and FQ12_MODULUS_COEFFS from the curve properties + """ + self.curve_name = curve_name + self.field_modulus = field_properties[curve_name]["field_modulus"] + + if isinstance(val, FQ): + self.n = val.n + elif isinstance(val, int): + self.n = val % self.field_modulus + else: + raise ValueError( + "Expected an int or FQ object, but got object of type {}" + .format(type(val)) + ) + + def __add__(self, other: IntOrFQ) -> "FQ": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise ValueError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return FQ((self.n + on) % self.field_modulus, self.curve_name) + + def __mul__(self, other: IntOrFQ) -> "FQ": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise ValueError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return FQ((self.n * on) % self.field_modulus, self.curve_name) + + def __rmul__(self, other: IntOrFQ) -> "FQ": + return self * other + + def __radd__(self, other: IntOrFQ) -> "FQ": + return self + other + + def __rsub__(self, other: IntOrFQ) -> "FQ": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise ValueError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return FQ((on - self.n) % self.field_modulus, self.curve_name) + + def __sub__(self, other: IntOrFQ) -> "FQ": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise ValueError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return FQ((self.n - on) % self.field_modulus, self.curve_name) + + def __mod__(self, other: Union[int, "FQ"]) -> "FQ": + return self.__mod__(other) + + def __div__(self, other: IntOrFQ) -> "FQ": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise ValueError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return FQ( + self.n * prime_field_inv(on, self.field_modulus) % self.field_modulus, + self.curve_name + ) + + def __truediv__(self, other: IntOrFQ) -> "FQ": + return self.__div__(other) + + def __rdiv__(self, other: IntOrFQ) -> "FQ": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise ValueError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return FQ( + prime_field_inv(self.n, self.field_modulus) * on % self.field_modulus, + self.curve_name + ) + + def __rtruediv__(self, other: IntOrFQ) -> "FQ": + return self.__rdiv__(other) + + def __pow__(self, other: int) -> "FQ": + if other == 0: + return FQ(1, self.curve_name) + elif other == 1: + return FQ(self.n, self.curve_name) + elif other % 2 == 0: + return (self * self) ** (other // 2) + else: + return ((self * self) ** int(other // 2)) * self + + def __eq__(self, other: IntOrFQ) -> bool: # type:ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 + if isinstance(other, FQ): + return self.n == other.n + elif isinstance(other, int): + return self.n == other + else: + raise ValueError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + def __ne__(self, other: IntOrFQ) -> bool: # type:ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 + return not self == other + + def __neg__(self) -> "FQ": + return FQ(-self.n, self.curve_name) + + def __repr__(self) -> str: + return repr(self.n) + + def __int__(self) -> int: + return self.n + + @classmethod + def one(cls, curve_name: str) -> "FQ": + return cls(1, curve_name) + + @classmethod + def zero(cls, curve_name: str) -> "FQ": + return cls(0, curve_name) + + +# A class for elements in polynomial extension fields +class FQP(object): + degree = 0 # type: int + mc_tuples = None # type: List[Tuple[int, int]] + curve_name = None + field_modulus = None + + def __init__(self, + coeffs: Sequence[IntOrFQ], + curve_name: str, + modulus_coeffs: Sequence[IntOrFQ]=None) -> None: + """ + curve_name can be either 'bn128' or 'bls12_381' + This is needed to obtain field_modulus, FQ2_MODULUS_COEFFS + and FQ12_MODULUS_COEFFS from the curve properties + """ + self.curve_name = curve_name + self.field_modulus = field_properties[curve_name]["field_modulus"] + + if len(coeffs) != len(modulus_coeffs): + raise Exception( + "coeffs and modulus_coeffs aren't of the same length" + ) + self.coeffs = tuple(coeffs) + # The coefficients of the modulus, without the leading [1] + self.modulus_coeffs = tuple(modulus_coeffs) + # The degree of the extension field + self.degree = len(self.modulus_coeffs) + + def __add__(self, other: "FQP") -> "FQP": + if not isinstance(other, type(self)): + raise ValueError( + "Expected an FQP object, but got object of type {}" + .format(type(other)) + ) + + return type(self)([ + int(x + y) % self.field_modulus + for x, y + in zip(self.coeffs, other.coeffs) + ], self.curve_name) + + def __sub__(self, other: "FQP") -> "FQP": + if not isinstance(other, type(self)): + raise ValueError( + "Expected an FQP object, but got object of type {}" + .format(type(other)) + ) + + return type(self)([ + int(x - y) % self.field_modulus + for x, y + in zip(self.coeffs, other.coeffs) + ], self.curve_name) + + def __mod__(self, other: Union[int, "FQP"]) -> "FQP": + return self.__mod__(other) + + def __mul__(self, other: Union[int, "FQP"]) -> "FQP": + if isinstance(other, int): + return type(self)([ + int(c) * other % self.field_modulus + for c + in self.coeffs + ], self.curve_name) + elif isinstance(other, FQP): + b = [0] * (self.degree * 2 - 1) + inner_enumerate = list(enumerate(other.coeffs)) + for i, eli in enumerate(self.coeffs): + for j, elj in inner_enumerate: + b[i + j] += int(eli * elj) + # MID = len(self.coeffs) // 2 + for exp in range(self.degree - 2, -1, -1): + top = b.pop() + for i, c in self.mc_tuples: + b[exp + i] -= top * c + return type(self)([x % self.field_modulus for x in b], self.curve_name) + else: + raise ValueError( + "Expected an int or FQP object, but got object of type {}" + .format(type(other)) + ) + + def __rmul__(self, other: Union[int, "FQP"]) -> "FQP": + return self * other + + def __div__(self, other: Union[int, "FQ", "FQP"]) -> "FQP": + if isinstance(other, int): + return type(self)([ + int(c) * prime_field_inv(other, self.field_modulus) % self.field_modulus + for c + in self.coeffs + ], self.curve_name) + elif isinstance(other, type(self)): + return self * other.inv() + else: + raise ValueError( + "Expected an int or FQP object, but got object of type {}" + .format(type(other)) + ) + + def __truediv__(self, other: Union[int, "FQ", "FQP"]) -> "FQP": + return self.__div__(other) + + def __pow__(self, other: int) -> "FQP": + o = type(self)([1] + [0] * (self.degree - 1), self.curve_name) + t = self + while other > 0: + if other & 1: + o = o * t + other >>= 1 + t = t * t + return o + + def optimized_poly_rounded_div(self, + a: Sequence[IntOrFQ], + b: Sequence[IntOrFQ]) -> Sequence[IntOrFQ]: + dega = deg(a) + degb = deg(b) + temp = [x for x in a] + o = [0 for x in a] + for i in range(dega - degb, -1, -1): + o[i] = int(o[i] + temp[degb + i] * prime_field_inv(int(b[degb]), self.field_modulus)) + for c in range(degb + 1): + temp[c + i] = (temp[c + i] - o[c]) + return [x % self.field_modulus for x in o[:deg(o) + 1]] + + # Extended euclidean algorithm used to find the modular inverse + def inv(self) -> "FQP": + lm, hm = [1] + [0] * self.degree, [0] * (self.degree + 1) + low, high = ( + cast(List[IntOrFQ], list(self.coeffs + (0,))), + cast(List[IntOrFQ], list(self.modulus_coeffs + (1,))), + ) + low, high = list(self.coeffs + (0,)), self.modulus_coeffs + (1,) # type: ignore + while deg(low): + r = cast(List[IntOrFQ], list(self.optimized_poly_rounded_div(high, low))) + r += [0] * (self.degree + 1 - len(r)) + nm = [x for x in hm] + new = [x for x in high] + # assert len(lm) == len(hm) == len(low) == len(high) == len(nm) == len(new) == self.degree + 1 # noqa: E501 + for i in range(self.degree + 1): + for j in range(self.degree + 1 - i): + nm[i + j] -= lm[i] * int(r[j]) + new[i + j] -= low[i] * r[j] + nm = [x % self.field_modulus for x in nm] + new = [int(x) % self.field_modulus for x in new] + lm, low, hm, high = nm, new, lm, low + return type(self)(lm[:self.degree], self.curve_name) / low[0] + + def __repr__(self) -> str: + return repr(self.coeffs) + + def __eq__(self, other: "FQP") -> bool: # type: ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 + if not isinstance(other, type(self)): + raise ValueError( + "Expected an FQP object, but got object of type {}" + .format(type(other)) + ) + + for c1, c2 in zip(self.coeffs, other.coeffs): + if c1 != c2: + return False + return True + + def __ne__(self, other: "FQP") -> bool: # type: ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 + return not self == other + + def __neg__(self) -> "FQP": + return type(self)([-c for c in self.coeffs], self.curve_name) + + @classmethod + def one(cls, curve_name: str) -> "FQP": + return cls([1] + [0] * (cls.degree - 1), curve_name) + + @classmethod + def zero(cls, curve_name: str) -> "FQP": + return cls([0] * cls.degree, curve_name) + + +# The quadratic extension field +class FQ2(FQP): + degree = 2 + + def __init__(self, coeffs: Sequence[IntOrFQ], curve_name: str) -> None: + FQ2_MODULUS_COEFFS = field_properties[curve_name]["fq2_modulus_coeffs"] + self.mc_tuples = [(i, c) for i, c in enumerate(FQ2_MODULUS_COEFFS) if c] + super().__init__(coeffs, curve_name, FQ2_MODULUS_COEFFS) + + +# The 12th-degree extension field +class FQ12(FQP): + degree = 12 + + def __init__(self, coeffs: Sequence[IntOrFQ], curve_name: str) -> None: + FQ12_MODULUS_COEFFS = field_properties[curve_name]["fq12_modulus_coeffs"] + self.mc_tuples = [(i, c) for i, c in enumerate(FQ12_MODULUS_COEFFS) if c] + super().__init__(coeffs, curve_name, FQ12_MODULUS_COEFFS) diff --git a/py_ecc/typing.py b/py_ecc/typing.py index f08a3670..4aa6f5af 100644 --- a/py_ecc/typing.py +++ b/py_ecc/typing.py @@ -5,7 +5,7 @@ Union, ) -from py_ecc.bn128.bn128_field_elements import ( +from py_ecc.field_elements import ( FQ, FQP, FQ2, @@ -13,7 +13,7 @@ ) if TYPE_CHECKING: - from py_ecc.optimized_bn128.optimized_field_elements import ( # noqa: F401 + from py_ecc.optimized_field_elements import ( # noqa: F401 FQ as Optimized_FQ, FQP as Optimized_FQP, FQ2 as Optimized_FQ2, diff --git a/py_ecc/utils.py b/py_ecc/utils.py new file mode 100644 index 00000000..b1eb6eff --- /dev/null +++ b/py_ecc/utils.py @@ -0,0 +1,54 @@ +from typing import ( + cast, + Tuple, + Sequence, + Union, + TYPE_CHECKING, +) + +if TYPE_CHECKING: + from py_ecc.field_elements import ( # noqa: F401 + FQ, + ) + from py_ecc.optimized_field_elements import ( # noqa: F401 + FQ as optimized_FQ, + ) + + +IntOrFQ = Union[int, "FQ"] + + +def prime_field_inv(a: int, n: int) -> int: + """ + Extended euclidean algorithm to find modular inverses for integers + """ + if a == 0: + return 0 + lm, hm = 1, 0 + low, high = a % n, n + while low > 1: + r = high // low + nm, new = hm - lm * r, high - low * r + lm, low, hm, high = nm, new, lm, low + return lm % n + + +# Utility methods for polynomial math +def deg(p: Sequence[Union[int, "FQ", "optimized_FQ"]]) -> int: + d = len(p) - 1 + while p[d] == 0 and d: + d -= 1 + return d + + +def poly_rounded_div(a: Sequence[IntOrFQ], + b: Sequence[IntOrFQ]) -> Tuple[IntOrFQ]: + dega = deg(a) + degb = deg(b) + temp = [x for x in a] + o = [0 for x in a] + for i in range(dega - degb, -1, -1): + o[i] += int(temp[degb + i] / b[degb]) + for c in range(degb + 1): + temp[c + i] -= o[c] + return cast(Tuple[IntOrFQ], tuple(o[:deg(o) + 1])) diff --git a/py_ecc/validate_constants.py b/py_ecc/validate_constants.py new file mode 100644 index 00000000..32898881 --- /dev/null +++ b/py_ecc/validate_constants.py @@ -0,0 +1,166 @@ +from functools import lru_cache + +from py_ecc.bls12_381_curve import ( + bls12_381, + optimized_bls12_381, +) + +from py_ecc.bn128_curve import ( + bn128, + optimized_bn128, +) + +from py_ecc.curve_properties import ( + bls12_381_props, + bn128_props, + optimized_bls12_381_props, + optimized_bn128_props, +) + +from py_ecc.field_properties import ( + field_properties, +) + + +def validate_field_properties() -> None: + for curve_props in ( + bls12_381_props, + bn128_props, + optimized_bls12_381_props, + optimized_bn128_props, + ): + # Do a smoke test to be sure that field_modulus is prime + + # Type ignore because bn128_props, optimized_bn128_props are of different types + # Mypy as of now doesn't support overwrite of variables + curve_name = curve_props.name # type: ignore + field_modulus = field_properties[curve_name]["field_modulus"] + if pow(2, field_modulus, field_modulus) != 2: + raise ValueError( + "Field Modulus of the curve {} is not a prime".format(curve_name) + ) + + +def validate_curve_properties() -> None: + for curve_props in ( + bls12_381_props, + bn128_props, + ): + # Do a smoke test to be sure that field_modulus is prime + field_modulus = curve_props.field_modulus + if pow(2, field_modulus, field_modulus) != 2: + raise ValueError( + "Curve Order of the curve {} is not a prime".format(curve_props.name) + ) + + # Do a smoke test to be sure that curve_order is prime + curve_order = curve_props.curve_order + if pow(2, curve_order, curve_order) != 2: + raise ValueError( + "Curve Order of the curve {} is not a prime".format(curve_props.name) + ) + + # Check consistency b/w field_modulus and curve_order + field_modulus = field_properties[curve_props.name]["field_modulus"] + if (field_modulus ** 12 - 1) % curve_order != 0: + raise ValueError( + "Inconsistent values among field_modulus and curve_order in the curve {}" + .format(curve_props.name) + ) + + # Check validity of pseudo_binary_encoding + pseudo_binary_encoding = curve_props.pseudo_binary_encoding + ate_loop_count = curve_props.ate_loop_count + if sum([e * 2**i for i, e in enumerate(pseudo_binary_encoding)]) != ate_loop_count: + raise ValueError( + "Inconsistent values among pseudo_binary_encoding and ate_loop_count" + ) + + +def validate_optimized_curve_properties() -> None: + for curve_props in ( + optimized_bls12_381_props, + optimized_bn128_props, + ): + # Do a smoke test to be sure that field_modulus is prime + field_modulus = curve_props.field_modulus + if pow(2, field_modulus, field_modulus) != 2: + raise ValueError( + "Curve Order of the curve {} is not a prime".format(curve_props.name) + ) + + # Do a smoke test to be sure that curve_order is prime + curve_order = curve_props.curve_order + if pow(2, curve_order, curve_order) != 2: + raise ValueError( + "Curve Order of the optimized curve {} is not a prime".format(curve_props.name) + ) + + # Check consistency b/w field_modulus and curve_order + field_modulus = field_properties[curve_props.name]["field_modulus"] + if (field_modulus ** 12 - 1) % curve_order != 0: + raise ValueError( + "Inconsistent values among field_modulus and curve_order in the optimized curve {}" + .format(curve_props.name) + ) + + # Check validity of pseudo_binary_encoding + pseudo_binary_encoding = curve_props.pseudo_binary_encoding + ate_loop_count = curve_props.ate_loop_count + if sum([e * 2**i for i, e in enumerate(pseudo_binary_encoding)]) != ate_loop_count: + raise ValueError( + "Inconsistent values among pseudo_binary_encoding and ate_loop_count" + "in the optimized curve {}" + .format(curve_props.name) + ) + + +def validate_generators() -> None: + # Validate generators of normal curves + for curve_obj in (bn128, bls12_381): + if not curve_obj.is_on_curve(curve_obj.G1, curve_obj.b): + raise ValueError( + "G1 doesn't lie on the curve {} defined by b".format(curve_obj.curve_name) + ) + + if not curve_obj.is_on_curve(curve_obj.G2, curve_obj.b2): + raise ValueError( + "G2 doesn't lie on the curve {} defined by b2".format(curve_obj.curve_name) + ) + + if not curve_obj.is_on_curve(curve_obj.G12, curve_obj.b12): + raise ValueError( + "G12 doesn't lie on the curve {} defined by b12".format(curve_obj.curve_name) + ) + + # Validate generators of optimized curves + for optimized_curve_obj in (optimized_bn128, optimized_bls12_381): + if not optimized_curve_obj.is_on_curve(optimized_curve_obj.G1, optimized_curve_obj.b): + raise ValueError( + "G1 doesn't lie on the optimized curve {} defined by b" + .format(optimized_curve_obj.curve_name) + ) + + if not optimized_curve_obj.is_on_curve(optimized_curve_obj.G2, optimized_curve_obj.b2): + raise ValueError( + "G2 doesn't lie on the optimized curve {} defined by b2" + .format(optimized_curve_obj.curve_name) + ) + + if not optimized_curve_obj.is_on_curve(optimized_curve_obj.G12, optimized_curve_obj.b12): + raise ValueError( + "G12 doesn't lie on the optimized curve {} defined by b12" + .format(optimized_curve_obj.curve_name) + ) + + +@lru_cache(maxsize=1) +def validate_constants() -> None: + """ + This function validates the constants that are being used throughout + the whole codebase. It specifically verifies the curve and field properties + """ + validate_field_properties() + validate_curve_properties() + validate_optimized_curve_properties() + validate_generators() diff --git a/setup.py b/setup.py index 5335fd11..087e2693 100644 --- a/setup.py +++ b/setup.py @@ -4,8 +4,8 @@ extras_require = { 'test': [ - "pytest==3.2.2", - "pytest-xdist" + "pytest>=3.6.0", + "pytest-xdist", ], 'lint': [ "flake8==3.4.1", @@ -44,6 +44,7 @@ packages=find_packages(exclude=('tests', 'docs')), package_data={'py_ecc': ['py.typed']}, install_requires=[ + "mypy-extensions>=0.4.1", ], python_requires='>=3.5, <4', extras_require=extras_require, diff --git a/tests/test_bn128_and_bls12_381.py b/tests/test_bn128_and_bls12_381.py deleted file mode 100644 index 14909f41..00000000 --- a/tests/test_bn128_and_bls12_381.py +++ /dev/null @@ -1,297 +0,0 @@ -import time - -import pytest - -from py_ecc import bn128, optimized_bn128, bls12_381, optimized_bls12_381 - - -@pytest.fixture(params=[bn128, optimized_bn128, bls12_381, optimized_bls12_381]) -def lib(request): - return request.param - - -@pytest.fixture -def FQ(lib): - return lib.FQ - - -@pytest.fixture -def FQ2(lib): - return lib.FQ2 - - -@pytest.fixture -def FQ12(lib): - return lib.FQ12 - - -@pytest.fixture -def field_modulus(lib): - return lib.field_modulus - -@pytest.fixture -def G1(lib): - return lib.G1 - - -@pytest.fixture -def G2(lib): - return lib.G2 - - -@pytest.fixture -def G12(lib): - return lib.G12 - - -@pytest.fixture -def b(lib): - return lib.b - - -@pytest.fixture -def b2(lib): - return lib.b2 - - -@pytest.fixture -def b12(lib): - return lib.b12 - - -@pytest.fixture -def is_inf(lib): - return lib.is_inf - - -@pytest.fixture -def is_on_curve(lib): - return lib.is_on_curve - - -@pytest.fixture -def eq(lib): - return lib.eq - - -@pytest.fixture -def add(lib): - return lib.add - - -@pytest.fixture -def double(lib): - return lib.double - - -@pytest.fixture -def curve_order(lib): - return lib.curve_order - - -@pytest.fixture -def multiply(lib): - return lib.multiply - - -@pytest.fixture -def pairing(lib): - return lib.pairing - - -@pytest.fixture -def neg(lib): - return lib.neg - - -def test_FQ_object(FQ, field_modulus): - assert FQ(2) * FQ(2) == FQ(4) - assert FQ(2) / FQ(7) + FQ(9) / FQ(7) == FQ(11) / FQ(7) - assert FQ(2) * FQ(7) + FQ(9) * FQ(7) == FQ(11) * FQ(7) - assert FQ(9) ** field_modulus == FQ(9) - - -def test_FQ2_object(FQ2, field_modulus): - x = FQ2([1, 0]) - f = FQ2([1, 2]) - fpx = FQ2([2, 2]) - one = FQ2.one() - assert x + f == fpx - assert f / f == one - assert one / f + x / f == (one + x) / f - assert one * f + x * f == (one + x) * f - assert x ** (field_modulus ** 2 - 1) == one - - -def test_FQ12_object(FQ12, field_modulus): - x = FQ12([1] + [0] * 11) - f = FQ12([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) - fpx = FQ12([2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) - one = FQ12.one() - assert x + f == fpx - assert f / f == one - assert one / f + x / f == (one + x) / f - assert one * f + x * f == (one + x) * f - # This check takes too long - # assert x ** (field_modulus ** 12 - 1) == one - - -def test_G1_object(G1, eq, double, add, multiply, curve_order, is_inf): - assert eq(add(add(double(G1), G1), G1), double(double(G1))) - assert not eq(double(G1), G1) - assert eq(add(multiply(G1, 9), multiply(G1, 5)), add(multiply(G1, 12), multiply(G1, 2))) - assert is_inf(multiply(G1, curve_order)) - - -def test_G2_object(G2, b2, eq, add, double, multiply, is_inf, curve_order, field_modulus, is_on_curve): - assert eq(add(add(double(G2), G2), G2), double(double(G2))) - assert not eq(double(G2), G2) - assert eq(add(multiply(G2, 9), multiply(G2, 5)), add(multiply(G2, 12), multiply(G2, 2))) - assert is_inf(multiply(G2, curve_order)) - assert not is_inf(multiply(G2, 2 * field_modulus - curve_order)) - assert is_on_curve(multiply(G2, 9), b2) - - -def test_G12_object(G12, b12, eq, add, double, multiply, is_on_curve, is_inf, curve_order): - assert eq(add(add(double(G12), G12), G12), double(double(G12))) - assert not eq(double(G12), G12) - assert eq(add(multiply(G12, 9), multiply(G12, 5)), add(multiply(G12, 12), multiply(G12, 2))) - assert is_on_curve(multiply(G12, 9), b12) - assert is_inf(multiply(G12, curve_order)) - - -def test_pairing_negative_G1(pairing, G1, G2, FQ12, curve_order, multiply, neg): - p1 = pairing(G2, G1) - pn1 = pairing(G2, neg(G1)) - - assert p1 * pn1 == FQ12.one() - - -def test_pairing_negative_G2(pairing, G1, G2, FQ12, curve_order, multiply, neg): - p1 = pairing(G2, G1) - pn1 = pairing(G2, neg(G1)) - np1 = pairing(neg(G2), G1) - - assert p1 * np1 == FQ12.one() - assert pn1 == np1 - - -def test_pairing_output_order(G1, G2, FQ12, pairing, curve_order): - p1 = pairing(G2, G1) - - assert p1 ** curve_order == FQ12.one() - - -def test_pairing_bilinearity_on_G1(G1, G2, neg, multiply, pairing): - p1 = pairing(G2, G1) - p2 = pairing(G2, multiply(G1, 2)) - np1 = pairing(neg(G2), G1) - - assert p1 * p1 == p2 - - -def test_pairing_is_non_degenerate(G1, G2, neg, pairing, multiply): - p1 = pairing(G2, G1) - p2 = pairing(G2, multiply(G1, 2)) - np1 = pairing(neg(G2), G1) - - assert p1 != p2 and p1 != np1 and p2 != np1 - - -def test_pairing_bilinearity_on_G2(G1, G2, pairing, multiply): - p1 = pairing(G2, G1) - po2 = pairing(multiply(G2, 2), G1) - - assert p1 * p1 == po2 - - -def test_pairing_composit_check(G1, G2, multiply, pairing): - p3 = pairing(multiply(G2, 27), multiply(G1, 37)) - po3 = pairing(G2, multiply(G1, 999)) - assert p3 == po3 - - -""" -for lib in (bn128, optimized_bn128): - FQ, FQ2, FQ12, field_modulus = lib.FQ, lib.FQ2, lib.FQ12, lib.field_modulus - assert FQ(2) * FQ(2) == FQ(4) - assert FQ(2) / FQ(7) + FQ(9) / FQ(7) == FQ(11) / FQ(7) - assert FQ(2) * FQ(7) + FQ(9) * FQ(7) == FQ(11) * FQ(7) - assert FQ(9) ** field_modulus == FQ(9) - print('FQ works fine') - - x = FQ2([1, 0]) - f = FQ2([1, 2]) - fpx = FQ2([2, 2]) - one = FQ2.one() - assert x + f == fpx - assert f / f == one - assert one / f + x / f == (one + x) / f - assert one * f + x * f == (one + x) * f - assert x ** (field_modulus ** 2 - 1) == one - print('FQ2 works fine') - - x = FQ12([1] + [0] * 11) - f = FQ12([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) - fpx = FQ12([2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) - one = FQ12.one() - assert x + f == fpx - assert f / f == one - assert one / f + x / f == (one + x) / f - assert one * f + x * f == (one + x) * f - # This check takes too long - # assert x ** (field_modulus ** 12 - 1) == one - print('FQ12 works fine') - - G1, G2, G12, b, b2, b12, is_inf, is_on_curve, eq, add, double, curve_order, multiply = \ - lib.G1, lib.G2, lib.G12, lib.b, lib.b2, lib.b12, lib.is_inf, lib.is_on_curve, lib.eq, lib.add, lib.double, lib.curve_order, lib.multiply - - assert eq(add(add(double(G1), G1), G1), double(double(G1))) - assert not eq(double(G1), G1) - assert eq(add(multiply(G1, 9), multiply(G1, 5)), add(multiply(G1, 12), multiply(G1, 2))) - assert is_inf(multiply(G1, curve_order)) - print('G1 works fine') - - assert eq(add(add(double(G2), G2), G2), double(double(G2))) - assert not eq(double(G2), G2) - assert eq(add(multiply(G2, 9), multiply(G2, 5)), add(multiply(G2, 12), multiply(G2, 2))) - assert is_inf(multiply(G2, curve_order)) - assert not is_inf(multiply(G2, 2 * field_modulus - curve_order)) - assert is_on_curve(multiply(G2, 9), b2) - print('G2 works fine') - - assert eq(add(add(double(G12), G12), G12), double(double(G12))) - assert not eq(double(G12), G12) - assert eq(add(multiply(G12, 9), multiply(G12, 5)), add(multiply(G12, 12), multiply(G12, 2))) - assert is_on_curve(multiply(G12, 9), b12) - assert is_inf(multiply(G12, curve_order)) - print('G12 works fine') - - pairing, neg = lib.pairing, lib.neg - - print('Starting pairing tests') - a = time.time() - p1 = pairing(G2, G1) - pn1 = pairing(G2, neg(G1)) - assert p1 * pn1 == FQ12.one() - print('Pairing check against negative in G1 passed') - np1 = pairing(neg(G2), G1) - assert p1 * np1 == FQ12.one() - assert pn1 == np1 - print('Pairing check against negative in G2 passed') - assert p1 ** curve_order == FQ12.one() - print('Pairing output has correct order') - p2 = pairing(G2, multiply(G1, 2)) - assert p1 * p1 == p2 - print('Pairing bilinearity in G1 passed') - assert p1 != p2 and p1 != np1 and p2 != np1 - print('Pairing is non-degenerate') - po2 = pairing(multiply(G2, 2), G1) - assert p1 * p1 == po2 - print('Pairing bilinearity in G2 passed') - p3 = pairing(multiply(G2, 27), multiply(G1, 37)) - po3 = pairing(G2, multiply(G1, 999)) - assert p3 == po3 - print('Composite check passed') - print('Total time for pairings: %.3f' % (time.time() - a)) -""" diff --git a/tests/test_curve_generators.py b/tests/test_curve_generators.py new file mode 100644 index 00000000..b1c75396 --- /dev/null +++ b/tests/test_curve_generators.py @@ -0,0 +1,65 @@ +import pytest + +from py_ecc.bls12_381_curve import ( + BLS12_381_Curve, + Optimized_BLS12_381_Curve, +) +from py_ecc.bn128_curve import ( + BN128_Curve, + Optimized_BN128_Curve, +) + + +@pytest.mark.parametrize( + 'curve_obj', + ( + BN128_Curve(), + Optimized_BN128_Curve(), + BLS12_381_Curve(), + Optimized_BLS12_381_Curve(), + ), +) +def test_G1_object(curve_obj): + G1 = curve_obj.G1 + assert curve_obj.eq(curve_obj.add(curve_obj.add(curve_obj.double(G1), G1), G1), curve_obj.double(curve_obj.double(G1))) + assert not curve_obj.eq(curve_obj.double(G1), G1) + assert curve_obj.eq(curve_obj.add(curve_obj.multiply(G1, 9), curve_obj.multiply(G1, 5)), curve_obj.add(curve_obj.multiply(G1, 12), curve_obj.multiply(G1, 2))) + assert curve_obj.is_inf(curve_obj.multiply(G1, curve_obj.curve_order)) + + +@pytest.mark.parametrize( + 'curve_obj', + ( + BN128_Curve(), + Optimized_BN128_Curve(), + BLS12_381_Curve(), + Optimized_BLS12_381_Curve(), + ), +) +def test_G2_object(curve_obj): + G2 = curve_obj.G2 + field_modulus = curve_obj.field_modulus + assert curve_obj.eq(curve_obj.add(curve_obj.add(curve_obj.double(G2), G2), G2), curve_obj.double(curve_obj.double(G2))) + assert not curve_obj.eq(curve_obj.double(G2), G2) + assert curve_obj.eq(curve_obj.add(curve_obj.multiply(G2, 9), curve_obj.multiply(G2, 5)), curve_obj.add(curve_obj.multiply(G2, 12), curve_obj.multiply(G2, 2))) + assert curve_obj.is_inf(curve_obj.multiply(G2, curve_obj.curve_order)) + assert not curve_obj.is_inf(curve_obj.multiply(G2, 2 * field_modulus - curve_obj.curve_order)) + assert curve_obj.is_on_curve(curve_obj.multiply(G2, 9), curve_obj.b2) + + +@pytest.mark.parametrize( + 'curve_obj', + ( + BN128_Curve(), + Optimized_BN128_Curve(), + BLS12_381_Curve(), + Optimized_BLS12_381_Curve(), + ), +) +def test_G12_object(curve_obj): + G12 = curve_obj.G12 + assert curve_obj.eq(curve_obj.add(curve_obj.add(curve_obj.double(G12), G12), G12), curve_obj.double(curve_obj.double(G12))) + assert not curve_obj.eq(curve_obj.double(G12), G12) + assert curve_obj.eq(curve_obj.add(curve_obj.multiply(G12, 9), curve_obj.multiply(G12, 5)), curve_obj.add(curve_obj.multiply(G12, 12), curve_obj.multiply(G12, 2))) + assert curve_obj.is_on_curve(curve_obj.multiply(G12, 9), curve_obj.b12) + assert curve_obj.is_inf(curve_obj.multiply(G12, curve_obj.curve_order)) diff --git a/tests/test_field_elements.py b/tests/test_field_elements.py new file mode 100644 index 00000000..aca2832c --- /dev/null +++ b/tests/test_field_elements.py @@ -0,0 +1,68 @@ +import pytest + +from py_ecc import ( + field_elements, + optimized_field_elements, +) +from py_ecc.field_properties import ( + field_properties, +) + + +# Tests both field_elements and optimized_field_elements +@pytest.fixture(params=[field_elements, optimized_field_elements]) +def lib(request): + return request.param + + +@pytest.fixture +def FQ(lib): + return lib.FQ + + +@pytest.fixture +def FQ2(lib): + return lib.FQ2 + + +@pytest.fixture +def FQ12(lib): + return lib.FQ12 + + +def test_FQ_object(FQ): + for curve_name in ("bn128", "bls12_381"): + field_modulus = field_properties[curve_name]["field_modulus"] + assert FQ(2, curve_name) * FQ(2, curve_name) == FQ(4, curve_name) + assert FQ(2, curve_name) / FQ(7, curve_name) + FQ(9, curve_name) / FQ(7, curve_name) == FQ(11, curve_name) / FQ(7, curve_name) + assert FQ(2, curve_name) * FQ(7, curve_name) + FQ(9, curve_name) * FQ(7, curve_name) == FQ(11, curve_name) * FQ(7, curve_name) + assert FQ(9, curve_name) ** field_modulus == FQ(9, curve_name) + + +def test_FQ2_object(FQ2): + for curve_name in ("bn128", "bls12_381"): + field_modulus = field_properties[curve_name]["field_modulus"] + x = FQ2([1, 0], curve_name) + f = FQ2([1, 2], curve_name) + fpx = FQ2([2, 2], curve_name) + one = FQ2.one(curve_name) + assert x + f == fpx + assert f / f == one + assert one / f + x / f == (one + x) / f + assert one * f + x * f == (one + x) * f + assert x ** (field_modulus ** 2 - 1) == one + + +def test_FQ12_object(FQ12): + for curve_name in ("bn128", "bls12_381"): + field_modulus = field_properties[curve_name]["field_modulus"] + x = FQ12([1] + [0] * 11, curve_name) + f = FQ12([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], curve_name) + fpx = FQ12([2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], curve_name) + one = FQ12.one(curve_name) + assert x + f == fpx + assert f / f == one + assert one / f + x / f == (one + x) / f + assert one * f + x * f == (one + x) * f + # This check takes too long + # assert x ** (field_modulus ** 12 - 1) == one diff --git a/tests/test_pairing.py b/tests/test_pairing.py new file mode 100644 index 00000000..f82fb89d --- /dev/null +++ b/tests/test_pairing.py @@ -0,0 +1,190 @@ +import pytest + +from py_ecc.bls12_381_curve import ( + BLS12_381_Curve, + Optimized_BLS12_381_Curve, +) +from py_ecc.bn128_curve import ( + BN128_Curve, + Optimized_BN128_Curve, +) + +from py_ecc.field_elements import ( + FQ, + FQ2, + FQ12, +) +from py_ecc.optimized_field_elements import ( + FQ as optimized_FQ, + FQ2 as optimized_FQ2, + FQ12 as optimized_FQ12, +) + + +@pytest.mark.parametrize( + 'curve_obj', + ( + BN128_Curve(), + BLS12_381_Curve(), + ), +) +def test_linefunc_consistency_object_plain_curves(curve_obj): + one, two, three = curve_obj.G1, curve_obj.double(curve_obj.G1), curve_obj.multiply(curve_obj.G1, 3) + negone, negtwo, negthree = ( + curve_obj.multiply(curve_obj.G1, curve_obj.curve_order - 1), + curve_obj.multiply(curve_obj.G1, curve_obj.curve_order - 2), + curve_obj.multiply(curve_obj.G1, curve_obj.curve_order - 3), + ) + + assert curve_obj.linefunc(one, two, one) == FQ(0, curve_obj.curve_name) + assert curve_obj.linefunc(one, two, two) == FQ(0, curve_obj.curve_name) + assert curve_obj.linefunc(one, two, three) != FQ(0, curve_obj.curve_name) + assert curve_obj.linefunc(one, two, negthree) == FQ(0, curve_obj.curve_name) + assert curve_obj.linefunc(one, negone, one) == FQ(0, curve_obj.curve_name) + assert curve_obj.linefunc(one, negone, negone) == FQ(0, curve_obj.curve_name) + assert curve_obj.linefunc(one, negone, two) != FQ(0, curve_obj.curve_name) + assert curve_obj.linefunc(one, one, one) == FQ(0, curve_obj.curve_name) + assert curve_obj.linefunc(one, one, two) != FQ(0, curve_obj.curve_name) + assert curve_obj.linefunc(one, one, negtwo) == FQ(0, curve_obj.curve_name) + + +@pytest.mark.parametrize( + 'curve_obj', + ( + Optimized_BN128_Curve(), + Optimized_BLS12_381_Curve(), + ), +) +def test_linefunc_consistency_object_optimized_curves(curve_obj): + one, two, three = curve_obj.G1, curve_obj.double(curve_obj.G1), curve_obj.multiply(curve_obj.G1, 3) + negone, negtwo, negthree = ( + curve_obj.multiply(curve_obj.G1, curve_obj.curve_order - 1), + curve_obj.multiply(curve_obj.G1, curve_obj.curve_order - 2), + curve_obj.multiply(curve_obj.G1, curve_obj.curve_order - 3), + ) + + assert curve_obj.linefunc(one, two, one)[0] == optimized_FQ(0, curve_obj.curve_name) + assert curve_obj.linefunc(one, two, two)[0] == optimized_FQ(0, curve_obj.curve_name) + assert curve_obj.linefunc(one, two, three)[0] != optimized_FQ(0, curve_obj.curve_name) + assert curve_obj.linefunc(one, two, negthree)[0] == optimized_FQ(0, curve_obj.curve_name) + assert curve_obj.linefunc(one, negone, one)[0] == optimized_FQ(0, curve_obj.curve_name) + assert curve_obj.linefunc(one, negone, negone)[0] == optimized_FQ(0, curve_obj.curve_name) + assert curve_obj.linefunc(one, negone, two)[0] != optimized_FQ(0, curve_obj.curve_name) + assert curve_obj.linefunc(one, one, one)[0] == optimized_FQ(0, curve_obj.curve_name) + assert curve_obj.linefunc(one, one, two)[0] != optimized_FQ(0, curve_obj.curve_name) + assert curve_obj.linefunc(one, one, negtwo)[0] == optimized_FQ(0, curve_obj.curve_name) + + +@pytest.mark.parametrize( + 'curve_obj,FQ12', + ( + (BN128_Curve(), FQ12), + (Optimized_BN128_Curve(), optimized_FQ12), + (BLS12_381_Curve(), FQ12), + (Optimized_BLS12_381_Curve(), optimized_FQ12), + ), +) +def test_pairing_negative_G1(curve_obj, FQ12): + p1 = curve_obj.pairing(curve_obj.G2, curve_obj.G1) + pn1 = curve_obj.pairing(curve_obj.G2, curve_obj.neg(curve_obj.G1)) + + assert p1 * pn1 == FQ12.one(curve_obj.curve_name) + + +@pytest.mark.parametrize( + 'curve_obj,FQ12', + ( + (BN128_Curve(), FQ12), + (Optimized_BN128_Curve(), optimized_FQ12), + (BLS12_381_Curve(), FQ12), + (Optimized_BLS12_381_Curve(), optimized_FQ12), + ), +) +def test_pairing_negative_G2(curve_obj, FQ12): + p1 = curve_obj.pairing(curve_obj.G2, curve_obj.G1) + pn1 = curve_obj.pairing(curve_obj.G2, curve_obj.neg(curve_obj.G1)) + np1 = curve_obj.pairing(curve_obj.neg(curve_obj.G2), curve_obj.G1) + + assert p1 * np1 == FQ12.one(curve_obj.curve_name) + assert pn1 == np1 + + +@pytest.mark.parametrize( + 'curve_obj,FQ12', + ( + (BN128_Curve(), FQ12), + (Optimized_BN128_Curve(), optimized_FQ12), + (BLS12_381_Curve(), FQ12), + (Optimized_BLS12_381_Curve(), optimized_FQ12), + ), +) +def test_pairing_output_order(curve_obj, FQ12): + p1 = curve_obj.pairing(curve_obj.G2, curve_obj.G1) + + assert p1 ** curve_obj.curve_order == FQ12.one(curve_obj.curve_name) + + +@pytest.mark.parametrize( + 'curve_obj', + ( + BN128_Curve(), + Optimized_BN128_Curve(), + BLS12_381_Curve(), + Optimized_BLS12_381_Curve(), + ), +) +def test_pairing_bilinearity_on_G1(curve_obj): + p1 = curve_obj.pairing(curve_obj.G2, curve_obj.G1) + p2 = curve_obj.pairing(curve_obj.G2, curve_obj.multiply(curve_obj.G1, 2)) + np1 = curve_obj.pairing(curve_obj.neg(curve_obj.G2), curve_obj.G1) + + assert p1 * p1 == p2 + + +@pytest.mark.parametrize( + 'curve_obj', + ( + BN128_Curve(), + Optimized_BN128_Curve(), + BLS12_381_Curve(), + Optimized_BLS12_381_Curve(), + ), +) +def test_pairing_is_non_degenerate(curve_obj): + p1 = curve_obj.pairing(curve_obj.G2, curve_obj.G1) + p2 = curve_obj.pairing(curve_obj.G2, curve_obj.multiply(curve_obj.G1, 2)) + np1 = curve_obj.pairing(curve_obj.neg(curve_obj.G2), curve_obj.G1) + + assert p1 != p2 and p1 != np1 and p2 != np1 + + +@pytest.mark.parametrize( + 'curve_obj', + ( + BN128_Curve(), + Optimized_BN128_Curve(), + BLS12_381_Curve(), + Optimized_BLS12_381_Curve(), + ), +) +def test_pairing_bilinearity_on_G2(curve_obj): + p1 = curve_obj.pairing(curve_obj.G2, curve_obj.G1) + po2 = curve_obj.pairing(curve_obj.multiply(curve_obj.G2, 2), curve_obj.G1) + + assert p1 * p1 == po2 + + +@pytest.mark.parametrize( + 'curve_obj', + ( + BN128_Curve(), + Optimized_BN128_Curve(), + BLS12_381_Curve(), + Optimized_BLS12_381_Curve(), + ), +) +def test_pairing_composit_check(curve_obj): + p3 = curve_obj.pairing(curve_obj.multiply(curve_obj.G2, 27), curve_obj.multiply(curve_obj.G1, 37)) + po3 = curve_obj.pairing(curve_obj.G2, curve_obj.multiply(curve_obj.G1, 999)) + + assert p3 == po3 diff --git a/tests/test_secp256k1.py b/tests/test_secp256k1.py index a56bba1b..b24e0856 100644 --- a/tests/test_secp256k1.py +++ b/tests/test_secp256k1.py @@ -1,6 +1,12 @@ -from py_ecc.secp256k1 import privtopub, ecdsa_raw_sign, ecdsa_raw_recover import binascii +from py_ecc.secp256k1 import ( + ecdsa_raw_recover, + ecdsa_raw_sign, + privtopub, +) + + priv = binascii.unhexlify('792eca682b890b31356247f2b04662bff448b6bb19ea1c8ab48da222c894ef9b') pub = (20033694065814990006010338153307081985267967222430278129327181081381512401190, 72089573118161052907088366229362685603474623289048716349537937839432544970413) diff --git a/tox.ini b/tox.ini index b02e57df..08fbd8f0 100644 --- a/tox.ini +++ b/tox.ini @@ -24,7 +24,5 @@ basepython = basepython=python extras=lint commands= - flake8 {toxinidir}/py_ecc - mypy --strict --follow-imports=silent --ignore-missing-imports --no-strict-optional -p py_ecc.bn128 - mypy --strict --follow-imports=silent --ignore-missing-imports --no-strict-optional -p py_ecc.optimized_bn128 - mypy --strict --follow-imports=silent --ignore-missing-imports --no-strict-optional -p py_ecc.secp256k1 + flake8 {toxinidir}/py_ecc {toxinidir}/tests + mypy --strict --follow-imports=silent --ignore-missing-imports --no-strict-optional -p py_ecc