From 1f9a433c4e5e70d93067ae4948a0c139e8e87d31 Mon Sep 17 00:00:00 2001 From: Bhargavasomu Date: Sun, 13 Jan 2019 20:24:15 +0530 Subject: [PATCH] Remove Fields Redundancy --- py_ecc/fields/__init__.py | 13 + py_ecc/fields/field_elements.py | 355 +++++++++++++++++++ py_ecc/fields/field_properties.py | 28 ++ py_ecc/fields/optimized_field_elements.py | 396 ++++++++++++++++++++++ py_ecc/utils.py | 54 +++ setup.py | 2 + tests/test_field_elements.py | 67 ++++ 7 files changed, 915 insertions(+) create mode 100644 py_ecc/fields/__init__.py create mode 100644 py_ecc/fields/field_elements.py create mode 100644 py_ecc/fields/field_properties.py create mode 100644 py_ecc/fields/optimized_field_elements.py create mode 100644 py_ecc/utils.py create mode 100644 tests/test_field_elements.py diff --git a/py_ecc/fields/__init__.py b/py_ecc/fields/__init__.py new file mode 100644 index 00000000..3c3813f0 --- /dev/null +++ b/py_ecc/fields/__init__.py @@ -0,0 +1,13 @@ +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, +) diff --git a/py_ecc/fields/field_elements.py b/py_ecc/fields/field_elements.py new file mode 100644 index 00000000..e0a7160f --- /dev/null +++ b/py_ecc/fields/field_elements.py @@ -0,0 +1,355 @@ +from typing import ( + cast, + List, + Sequence, + Union, +) + + +from py_ecc.fields.field_properties import ( + field_properties, +) + +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 = 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) + + +class FQP(object): + """ + A class for elements in polynomial extension fields + """ + 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) + + +class FQ2(FQP): + """ + The quadratic extension field + """ + 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) + + +class FQ12(FQP): + """ + The 12th-degree extension field + """ + 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/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..9e9ebc59 --- /dev/null +++ b/py_ecc/fields/optimized_field_elements.py @@ -0,0 +1,396 @@ +from typing import ( # noqa: F401 + cast, + List, + Sequence, + Tuple, + Union, +) + +from cytoolz import curry + +from py_ecc.fields.field_properties import ( + field_properties, +) + +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 = 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) + + +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], + 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) + + +class FQ2(FQP): + """ + The quadratic extension field + """ + 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) + + +class FQ12(FQP): + """ + The 12th-degree extension field + """ + 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/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 a90314b0..1978f4b4 100644 --- a/setup.py +++ b/setup.py @@ -10,6 +10,7 @@ 'lint': [ "flake8==3.4.1", "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_field_elements.py b/tests/test_field_elements.py new file mode 100644 index 00000000..be77506d --- /dev/null +++ b/tests/test_field_elements.py @@ -0,0 +1,67 @@ +import pytest + +from py_ecc.fields import ( + field_elements, + optimized_field_elements, +) +from py_ecc.fields.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"): + 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