diff --git a/py_ecc/bls12_381/bls12_381_curve.py b/py_ecc/bls12_381/bls12_381_curve.py index 7a8105d7..9bda1d41 100644 --- a/py_ecc/bls12_381/bls12_381_curve.py +++ b/py_ecc/bls12_381/bls12_381_curve.py @@ -1,10 +1,13 @@ from __future__ import absolute_import +from py_ecc.fields import ( + bls12_381_FQ as FQ, + bls12_381_FQ2 as FQ2, + bls12_381_FQ12 as FQ12, +) + from .bls12_381_field_elements import ( field_modulus, - FQ, - FQ2, - FQ12, ) diff --git a/py_ecc/bls12_381/bls12_381_pairing.py b/py_ecc/bls12_381/bls12_381_pairing.py index 21ec4f22..29da476c 100644 --- a/py_ecc/bls12_381/bls12_381_pairing.py +++ b/py_ecc/bls12_381/bls12_381_pairing.py @@ -1,5 +1,10 @@ from __future__ import absolute_import +from py_ecc.fields import ( + bls12_381_FQ as FQ, + bls12_381_FQ12 as FQ12, +) + from .bls12_381_curve import ( double, add, @@ -13,8 +18,6 @@ ) from .bls12_381_field_elements import ( field_modulus, - FQ, - FQ12, ) diff --git a/py_ecc/bn128/bn128_curve.py b/py_ecc/bn128/bn128_curve.py index 6cca05fe..bb598681 100644 --- a/py_ecc/bn128/bn128_curve.py +++ b/py_ecc/bn128/bn128_curve.py @@ -4,6 +4,13 @@ cast, ) +from py_ecc.fields import ( + bn128_FQ as FQ, + bn128_FQ2 as FQ2, + bn128_FQ12 as FQ12, + bn128_FQP as FQP, +) + from py_ecc.typing import ( Field, GeneralPoint, @@ -12,10 +19,6 @@ from .bn128_field_elements import ( field_modulus, - FQ, - FQ2, - FQ12, - FQP, ) diff --git a/py_ecc/bn128/bn128_pairing.py b/py_ecc/bn128/bn128_pairing.py index bc070266..04f480e1 100644 --- a/py_ecc/bn128/bn128_pairing.py +++ b/py_ecc/bn128/bn128_pairing.py @@ -4,6 +4,12 @@ cast, ) +from py_ecc.fields import ( + bn128_FQ as FQ, + bn128_FQ12 as FQ12, + bn128_FQP as FQP, +) + from py_ecc.typing import ( Field, FQPoint2D, @@ -25,9 +31,6 @@ ) from .bn128_field_elements import ( field_modulus, - FQ, - FQ12, - FQP, ) diff --git a/py_ecc/fields/__init__.py b/py_ecc/fields/__init__.py new file mode 100644 index 00000000..36258773 --- /dev/null +++ b/py_ecc/fields/__init__.py @@ -0,0 +1,160 @@ +from .field_elements import ( + FQ, + FQP, + FQ2, + FQ12, +) + +from .field_properties import ( + field_properties, +) + +from .optimized_field_elements import ( + FQ as optimized_FQ, + FQP as optimized_FQP, + FQ2 as optimized_FQ2, + FQ12 as optimized_FQ12, +) + + +# Create seperate classes for all Fields for each curve + +bn128_FQ = type( + "bn128_FQ", + (FQ,), + { + 'curve_name': "bn128", + 'field_modulus': field_properties["bn128"]["field_modulus"], + } +) +bn128_FQP = type( + "bn128_FQP", + (FQP,), + { + 'curve_name': "bn128", + 'field_modulus': field_properties["bn128"]["field_modulus"], + } +) +bn128_FQ2 = type( + "bn128_FQ2", + (FQ2,), + { + 'curve_name': "bn128", + 'field_modulus': field_properties["bn128"]["field_modulus"], + 'FQ2_MODULUS_COEFFS': field_properties["bn128"]["fq2_modulus_coeffs"], + } +) +bn128_FQ12 = type( + "bn128_FQ12", + (FQ12,), + { + 'curve_name': "bn128", + 'field_modulus': field_properties["bn128"]["field_modulus"], + 'FQ12_MODULUS_COEFFS': field_properties["bn128"]["fq12_modulus_coeffs"], + } +) + +bls12_381_FQ = type( + "bls12_381_FQ", + (FQ,), + { + 'curve_name': "bls12_381", + 'field_modulus': field_properties["bls12_381"]["field_modulus"], + } +) +bls12_381_FQP = type( + "bls12_381_FQP", + (FQP,), + { + 'curve_name': "bls12_381", + 'field_modulus': field_properties["bls12_381"]["field_modulus"], + } +) +bls12_381_FQ2 = type( + "bls12_381_FQ2", + (FQ2,), + { + 'curve_name': "bls12_381", + 'field_modulus': field_properties["bls12_381"]["field_modulus"], + 'FQ2_MODULUS_COEFFS': field_properties["bls12_381"]["fq2_modulus_coeffs"], + } +) +bls12_381_FQ12 = type( + "bls12_381_FQ12", + (FQ12,), + { + 'curve_name': "bls12_381", + 'field_modulus': field_properties["bls12_381"]["field_modulus"], + 'FQ12_MODULUS_COEFFS': field_properties["bls12_381"]["fq12_modulus_coeffs"], + } +) + +optimized_bn128_FQ = type( + "optimized_bn128_FQ", + (optimized_FQ,), + { + 'curve_name': "bn128", + 'field_modulus': field_properties["bn128"]["field_modulus"], + } +) +optimized_bn128_FQP = type( + "optimized_bn128_FQP", + (optimized_FQP,), + { + 'curve_name': "bn128", + 'field_modulus': field_properties["bn128"]["field_modulus"], + } +) +optimized_bn128_FQ2 = type( + "optimized_bn128_FQ2", + (optimized_FQ2,), + { + 'curve_name': "bn128", + 'field_modulus': field_properties["bn128"]["field_modulus"], + 'FQ2_MODULUS_COEFFS': field_properties["bn128"]["fq2_modulus_coeffs"], + } +) +optimized_bn128_FQ12 = type( + "optimized_bn128_FQ12", + (optimized_FQ12,), + { + 'curve_name': "bn128", + 'field_modulus': field_properties["bn128"]["field_modulus"], + 'FQ12_MODULUS_COEFFS': field_properties["bn128"]["fq12_modulus_coeffs"], + } +) + +optimized_bls12_381_FQ = type( + "optimized_bls12_381_FQ", + (optimized_FQ,), + { + 'curve_name': "bls12_381", + 'field_modulus': field_properties["bls12_381"]["field_modulus"], + } +) +optimized_bls12_381_FQP = type( + "optimized_bls12_381_FQP", + (optimized_FQP,), + { + 'curve_name': "bls12_381", + 'field_modulus': field_properties["bls12_381"]["field_modulus"], + } +) +optimized_bls12_381_FQ2 = type( + "optimized_bls12_381_FQ2", + (optimized_FQ2,), + { + 'curve_name': "bls12_381", + 'field_modulus': field_properties["bls12_381"]["field_modulus"], + 'FQ2_MODULUS_COEFFS': field_properties["bls12_381"]["fq2_modulus_coeffs"], + } +) +optimized_bls12_381_FQ12 = type( + "optimized_bls12_381_FQ12", + (optimized_FQ12,), + { + 'curve_name': "bls12_381", + 'field_modulus': field_properties["bls12_381"]["field_modulus"], + 'FQ12_MODULUS_COEFFS': field_properties["bls12_381"]["fq12_modulus_coeffs"], + } +) diff --git a/py_ecc/fields/field_elements.py b/py_ecc/fields/field_elements.py new file mode 100644 index 00000000..716be2c3 --- /dev/null +++ b/py_ecc/fields/field_elements.py @@ -0,0 +1,377 @@ +from typing import ( + cast, + List, + Sequence, + Union, +) + +from py_ecc.utils import ( + deg, + poly_rounded_div, + prime_field_inv, +) + + +IntOrFQ = Union[int, "FQ"] + + +class FQ(object): + """ + A class for field elements in FQ. Wrap a number in this class, + and it becomes a field element. + """ + n = None # type: int + field_modulus = 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 + curve_name = None + + def __init__(self, val: IntOrFQ) -> None: + if self.curve_name is None: + raise AttributeError("Curve Name hasn't been specified") + if self.field_modulus is None: + raise AttributeError("Field Modulus hasn't been specified") + + if isinstance(val, FQ): + self.n = val.n + elif isinstance(val, int): + self.n = val % self.field_modulus + else: + raise TypeError( + "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 TypeError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return type(self)((self.n + on) % self.field_modulus) + + def __mul__(self, other: IntOrFQ) -> "FQ": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise TypeError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return type(self)((self.n * on) % self.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": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise TypeError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return type(self)((on - self.n) % self.field_modulus) + + def __sub__(self, other: IntOrFQ) -> "FQ": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise TypeError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return type(self)((self.n - on) % self.field_modulus) + + def __div__(self, other: IntOrFQ) -> "FQ": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise TypeError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return type(self)( + self.n * prime_field_inv(on, self.field_modulus) % self.field_modulus + ) + + 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 TypeError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return type(self)( + prime_field_inv(self.n, self.field_modulus) * on % self.field_modulus + ) + + def __rtruediv__(self, other: IntOrFQ) -> "FQ": + return self.__rdiv__(other) + + def __pow__(self, other: int) -> "FQ": + if other == 0: + return type(self)(1) + elif other == 1: + return type(self)(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 + elif isinstance(other, int): + return self.n == other + else: + raise TypeError( + "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 type(self)(-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) + + +int_types_or_FQ = (int, FQ) + + +class FQP(object): + """ + A class for elements in polynomial extension fields + """ + degree = 0 + curve_name = None + field_modulus = None + + def __init__(self, + coeffs: Sequence[IntOrFQ], + modulus_coeffs: Sequence[IntOrFQ]=None) -> None: + if self.curve_name is None: + raise AttributeError("Curve Name hasn't been specified") + if self.field_modulus is None: + raise AttributeError("Field Modulus hasn't been specified") + + if len(coeffs) != len(modulus_coeffs): + raise Exception( + "coeffs and modulus_coeffs aren't of the same length" + ) + # Encoding all coefficients in type FQ (in regards to the curve name too) + self.FQP_corresponding_FQ_class = type( + "FQP_corresponding_FQ_class_" + self.curve_name, + (FQ,), + {'curve_name': self.curve_name, 'field_modulus': self.field_modulus} + ) + self.coeffs = tuple(self.FQP_corresponding_FQ_class(c) for c in 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 TypeError( + "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)]) + + def __sub__(self, other: "FQP") -> "FQP": + if not isinstance(other, type(self)): + raise TypeError( + "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)]) + + def __mul__(self, other: Union[int, "FQ", "FQP"]) -> "FQP": + if isinstance(other, int_types_or_FQ): + return type(self)([c * other for c in self.coeffs]) + elif isinstance(other, FQP): + b = [self.FQP_corresponding_FQ_class(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 * self.FQP_corresponding_FQ_class(self.modulus_coeffs[i]) + return type(self)(b) + else: + raise TypeError( + "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]) + elif isinstance(other, FQP): + return self * other.inv() + else: + raise TypeError( + "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)) + 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] + + if len(lm) != self.degree + 1: + raise Exception("Length of lm is not {}".format(self.degree + 1)) + elif len(hm) != self.degree + 1: + raise Exception("Length of hm is not {}".format(self.degree + 1)) + elif len(nm) != self.degree + 1: + raise Exception("Length of nm is not {}".format(self.degree + 1)) + elif len(low) != self.degree + 1: + raise Exception("Length of low is not {}".format(self.degree + 1)) + elif len(high) != self.degree + 1: + raise Exception("Length of high is not {}".format(self.degree + 1)) + elif len(new) != self.degree + 1: + raise Exception("Length of new is not {}".format(self.degree + 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 + if not isinstance(other, type(self)): + raise TypeError( + "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]) + + @classmethod + def one(cls) -> "FQP": + return cls([1] + [0] * (cls.degree - 1)) + + @classmethod + def zero(cls) -> "FQP": + return cls([0] * cls.degree) + + +class FQ2(FQP): + """ + The quadratic extension field + """ + degree = 2 + FQ2_MODULUS_COEFFS = None + + def __init__(self, coeffs: Sequence[IntOrFQ]) -> None: + if self.curve_name is None: + raise AttributeError("Curve Name hasn't been specified") + if self.FQ2_MODULUS_COEFFS is None: + raise AttributeError("FQ2 Modulus Coeffs haven't been specified") + + super().__init__(coeffs, self.FQ2_MODULUS_COEFFS) + + +class FQ12(FQP): + """ + The 12th-degree extension field + """ + degree = 12 + FQ12_MODULUS_COEFFS = None + + def __init__(self, coeffs: Sequence[IntOrFQ]) -> None: + if self.curve_name is None: + raise AttributeError("Curve Name hasn't been specified") + if self.FQ12_MODULUS_COEFFS is None: + raise AttributeError("FQ12 Modulus Coeffs haven't been specified") + + super().__init__(coeffs, self.FQ12_MODULUS_COEFFS) diff --git a/py_ecc/fields/field_properties.py b/py_ecc/fields/field_properties.py new file mode 100644 index 00000000..3ca362e8 --- /dev/null +++ b/py_ecc/fields/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/fields/optimized_field_elements.py b/py_ecc/fields/optimized_field_elements.py new file mode 100644 index 00000000..114eecf0 --- /dev/null +++ b/py_ecc/fields/optimized_field_elements.py @@ -0,0 +1,398 @@ +from typing import ( # noqa: F401 + cast, + List, + Sequence, + Tuple, + Union, +) + +from py_ecc.utils import ( + deg, + prime_field_inv, +) + + +IntOrFQ = Union[int, "FQ"] + + +class FQ(object): + """ + A class for field elements in FQ. Wrap a number in this class, + and it becomes a field element. + """ + n = None # type: int + field_modulus = 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 + curve_name = None + + def __init__(self, val: IntOrFQ) -> None: + if self.curve_name is None: + raise AttributeError("Curve Name hasn't been specified") + if self.field_modulus is None: + raise AttributeError("Field Modulus hasn't been specified") + + if isinstance(val, FQ): + self.n = val.n + elif isinstance(val, int): + self.n = val % self.field_modulus + else: + raise TypeError( + "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 TypeError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return type(self)((self.n + on) % self.field_modulus) + + def __mul__(self, other: IntOrFQ) -> "FQ": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise TypeError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return type(self)((self.n * on) % self.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": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise TypeError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return type(self)((on - self.n) % self.field_modulus) + + def __sub__(self, other: IntOrFQ) -> "FQ": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise TypeError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return type(self)((self.n - on) % self.field_modulus) + + def __mod__(self, other: IntOrFQ) -> "FQ": + raise NotImplementedError("Modulo Operation not yet supported by fields") + + def __div__(self, other: IntOrFQ) -> "FQ": + if isinstance(other, FQ): + on = other.n + elif isinstance(other, int): + on = other + else: + raise TypeError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return type(self)( + self.n * prime_field_inv(on, self.field_modulus) % self.field_modulus + ) + + 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 TypeError( + "Expected an int or FQ object, but got object of type {}" + .format(type(other)) + ) + + return type(self)( + prime_field_inv(self.n, self.field_modulus) * on % self.field_modulus + ) + + def __rtruediv__(self, other: IntOrFQ) -> "FQ": + return self.__rdiv__(other) + + def __pow__(self, other: int) -> "FQ": + if other == 0: + return type(self)(1) + elif other == 1: + return type(self)(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 + elif isinstance(other, int): + return self.n == other + else: + raise TypeError( + "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 type(self)(-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) + + +class FQP(object): + """ + A class for elements in polynomial extension fields + """ + degree = 0 # type: int + mc_tuples = None # type: List[Tuple[int, int]] + curve_name = None + field_modulus = None + + def __init__(self, + coeffs: Sequence[IntOrFQ], + modulus_coeffs: Sequence[IntOrFQ]=None) -> None: + if self.curve_name is None: + raise AttributeError("Curve Name hasn't been specified") + if self.field_modulus is None: + raise AttributeError("Field Modulus hasn't been specified") + + if len(coeffs) != len(modulus_coeffs): + raise Exception( + "coeffs and modulus_coeffs aren't of the same length" + ) + + # Not converting coeffs to FQ or explicitly making them integers for performance reasons + if isinstance(coeffs[0], int): + self.coeffs = tuple(coeff % self.field_modulus for coeff in coeffs) + else: + 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 TypeError( + "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) + ]) + + def __sub__(self, other: "FQP") -> "FQP": + if not isinstance(other, type(self)): + raise TypeError( + "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) + ]) + + def __mod__(self, other: Union[int, "FQP"]) -> "FQP": + raise NotImplementedError("Modulo Operation not yet supported by fields") + + 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 + ]) + 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]) + else: + raise TypeError( + "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 + ]) + elif isinstance(other, type(self)): + return self * other.inv() + else: + raise TypeError( + "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)) + 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,))), + ) + 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]) / 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 TypeError( + "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]) + + @classmethod + def one(cls) -> "FQP": + return cls([1] + [0] * (cls.degree - 1)) + + @classmethod + def zero(cls) -> "FQP": + return cls([0] * cls.degree) + + +class FQ2(FQP): + """ + The quadratic extension field + """ + degree = 2 + FQ2_MODULUS_COEFFS = None + + def __init__(self, coeffs: Sequence[IntOrFQ]) -> None: + if self.curve_name is None: + raise AttributeError("Curve Name hasn't been specified") + if self.FQ2_MODULUS_COEFFS is None: + raise AttributeError("FQ2 Modulus Coeffs haven't been specified") + + self.mc_tuples = [(i, c) for i, c in enumerate(self.FQ2_MODULUS_COEFFS) if c] + super().__init__(coeffs, self.FQ2_MODULUS_COEFFS) + + +class FQ12(FQP): + """ + The 12th-degree extension field + """ + degree = 12 + FQ12_MODULUS_COEFFS = None + + def __init__(self, coeffs: Sequence[IntOrFQ]) -> None: + if self.curve_name is None: + raise AttributeError("Curve Name hasn't been specified") + if self.FQ12_MODULUS_COEFFS is None: + raise AttributeError("FQ12 Modulus Coeffs haven't been specified") + + self.mc_tuples = [(i, c) for i, c in enumerate(self.FQ12_MODULUS_COEFFS) if c] + super().__init__(coeffs, self.FQ12_MODULUS_COEFFS) diff --git a/py_ecc/optimized_bls12_381/optimized_curve.py b/py_ecc/optimized_bls12_381/optimized_curve.py index 64b8d846..b86c99fc 100644 --- a/py_ecc/optimized_bls12_381/optimized_curve.py +++ b/py_ecc/optimized_bls12_381/optimized_curve.py @@ -1,10 +1,13 @@ from __future__ import absolute_import +from py_ecc.fields import ( + optimized_bls12_381_FQ as FQ, + optimized_bls12_381_FQ2 as FQ2, + optimized_bls12_381_FQ12 as FQ12, +) + from .optimized_field_elements import ( - FQ2, - FQ12, field_modulus, - FQ, ) diff --git a/py_ecc/optimized_bls12_381/optimized_pairing.py b/py_ecc/optimized_bls12_381/optimized_pairing.py index 430e3a4a..0e1bd737 100644 --- a/py_ecc/optimized_bls12_381/optimized_pairing.py +++ b/py_ecc/optimized_bls12_381/optimized_pairing.py @@ -1,5 +1,10 @@ from __future__ import absolute_import +from py_ecc.fields import ( + optimized_bls12_381_FQ as FQ, + optimized_bls12_381_FQ12 as FQ12, +) + from .optimized_curve import ( double, add, @@ -14,9 +19,7 @@ normalize, ) from .optimized_field_elements import ( - FQ12, field_modulus, - FQ, ) diff --git a/py_ecc/optimized_bn128/optimized_curve.py b/py_ecc/optimized_bn128/optimized_curve.py index 484456c7..ac787ebf 100644 --- a/py_ecc/optimized_bn128/optimized_curve.py +++ b/py_ecc/optimized_bn128/optimized_curve.py @@ -4,12 +4,15 @@ cast, ) +from py_ecc.fields import ( + optimized_bn128_FQ as FQ, + optimized_bn128_FQP as FQP, + optimized_bn128_FQ2 as FQ2, + optimized_bn128_FQ12 as FQ12, +) + from .optimized_field_elements import ( field_modulus, - FQ, - FQ2, - FQ12, - FQP, ) from py_ecc.typing import ( diff --git a/py_ecc/optimized_bn128/optimized_pairing.py b/py_ecc/optimized_bn128/optimized_pairing.py index 8289d548..fa87b315 100644 --- a/py_ecc/optimized_bn128/optimized_pairing.py +++ b/py_ecc/optimized_bn128/optimized_pairing.py @@ -1,5 +1,11 @@ from __future__ import absolute_import +from py_ecc.fields import ( + optimized_bn128_FQ as FQ, + optimized_bn128_FQP as FQP, + optimized_bn128_FQ12 as FQ12, +) + from py_ecc.typing import ( Optimized_Field, Optimized_FQPoint3D, @@ -23,9 +29,6 @@ ) from .optimized_field_elements import ( field_modulus, - FQ, - FQ12, - FQP, ) 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/setup.py b/setup.py index b5879966..0018399b 100644 --- a/setup.py +++ b/setup.py @@ -8,8 +8,9 @@ "pytest-xdist==1.26.0" ], 'lint': [ - "flake8==3.4.1", + "flake8==3.5.0", "mypy==0.641", + "mypy-extensions>=0.4.1", ], 'dev': [ "bumpversion>=0.5.3,<1", @@ -44,6 +45,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 index 93342b76..155d4b68 100644 --- a/tests/test_bn128_and_bls12_381.py +++ b/tests/test_bn128_and_bls12_381.py @@ -2,7 +2,27 @@ import pytest -from py_ecc import bn128, optimized_bn128, bls12_381, optimized_bls12_381 +from py_ecc import ( + bn128, + optimized_bn128, + bls12_381, + optimized_bls12_381, +) + +from py_ecc.fields import ( + bls12_381_FQ, + bls12_381_FQ2, + bls12_381_FQ12, + bn128_FQ, + bn128_FQ2, + bn128_FQ12, + optimized_bls12_381_FQ, + optimized_bls12_381_FQ2, + optimized_bls12_381_FQ12, + optimized_bn128_FQ, + optimized_bn128_FQ2, + optimized_bn128_FQ12, +) @pytest.fixture(params=[bn128, optimized_bn128, bls12_381, optimized_bls12_381]) @@ -12,17 +32,44 @@ def lib(request): @pytest.fixture def FQ(lib): - return lib.FQ + if lib == bn128: + return bn128_FQ + elif lib == optimized_bn128: + return optimized_bn128_FQ + elif lib == bls12_381: + return bls12_381_FQ + elif lib == optimized_bls12_381: + return optimized_bls12_381_FQ + else: + raise Exception("Library Not Found") @pytest.fixture def FQ2(lib): - return lib.FQ2 + if lib == bn128: + return bn128_FQ2 + elif lib == optimized_bn128: + return optimized_bn128_FQ2 + elif lib == bls12_381: + return bls12_381_FQ2 + elif lib == optimized_bls12_381: + return optimized_bls12_381_FQ2 + else: + raise Exception("Library Not Found") @pytest.fixture def FQ12(lib): - return lib.FQ12 + if lib == bn128: + return bn128_FQ12 + elif lib == optimized_bn128: + return optimized_bn128_FQ12 + elif lib == bls12_381: + return bls12_381_FQ12 + elif lib == optimized_bls12_381: + return optimized_bls12_381_FQ12 + else: + raise Exception("Library Not Found") @pytest.fixture diff --git a/tox.ini b/tox.ini index b02e57df..306b88ce 100644 --- a/tox.ini +++ b/tox.ini @@ -25,6 +25,4 @@ 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}/tests