Exemple #1
0
class QuotientRing(CommutativeRing, metaclass=template("_ring", "_modulus")):
    """
    A ring of residue classes of ring elements modulo a fixed ring element; the
    residue classes (quotient ring elements) support infix notation for ring
    operations, as well as mixed argument types.

    This is a template class that must be instantiated with the source ring
    and modulus.  Use it, for example, as follows:
    @code
    # Instantiate the template; Z4 is a class (here: integer congruence classes)
    Z4 = QuotientRing( rings.integers.naive.Integers, 4 )
    x = Z4(1)      # Create a residue class: (1 mod 4)
    y = Z4(3)      # Another residue class: (2 mod 4)
    z = x+y        # z is (0 mod 4)
    z == Z4(2) * 2 # This true because 4 == 0 modulo 4
    type(z) is Z4  # This is also true
    @endcode

    Other examples of QuotientRing template instances are:
    @code
    Z17 = QuotientRing( rings.integers.naive.Integers, 17 )    # Z/17Z

    # The ring (Z/2Z[x]) / (x^3 + x + 1) is isomorphic to GF8,
    # the field with 8 elements
    GF2  = fields.finite.naive.FiniteField( 2 )
    GF2x = rings.polynomials.naive.Polynomials( GF2 )
    GF8 = QuotientRing( GF2x, GF2x(1, 1, 0, 1) ) 
    @endcode

    A quotient ring, factor ring, or residue class ring, is a ring
    @f$ R/(m) @f$, where @f$ R @f$ is the source ring, and @f$ (m) @f$
    denotes the ideal generated by the element @f$ m\in R @f$.  The ring
    operations carry over to quotient classes in a natural way: if
    @f$ x + y = z @f$ in @f$ R @f$, then @f$ [x] + [y] = [z] @f$
    in @f$ R/(m) @f$ (by the canonical homomorphism).

    With the quotient ring operations being defined in terms of source ring
    operations, elements of the source ring must implement:
    - __bool__(): Zero testing (@c True if not zero)
    - __eq__(): Equality testing with the @c == operator (@c True if equal)
    - __add__(): Addition with the @c + operator; @c self is the left summand
    - __neg__(): Negation with the @c - unary minus operator (the additive inverse)
    - __mul__(): Multiplication with the @c * operator; @c self is the
      left factor
    - __divmod__(): Division with remainder; @c self is the dividend (left element)

    @note  The implementation emphasizes simplicity over speed; it omits
           possible optimizations.

    @note  The class uses the operand_casting() decorator: @c other operands in
           binary operations will first be treated as QuotientRing elements.
           If that fails, the operation will be repeated after @p other was fed
           to the constructor __init__().  If that fails, too, then the
           operation returns @c NotImplemented (so that @p other.__rop__()
           might be invoked).

    @see   For example, Robinson, Derek J. S.,
           "An Introduction to Abstract Algebra", p. 106.
    """

    #- Instance Methods -----------------------------------------------------------

    def __init__(self, representative):
        """
        Construct a new residue class @p representative modulo modulus().

        If the @p representative already is an element of this QuotientRing
        class, then the new element is a copy of @p representative.
        """
        if isinstance(representative, self.__class__):
            self.__remainder = representative.__remainder
        elif isinstance(representative, self._modulus.__class__):
            self.__remainder = representative % self._modulus
        else:
            m = self._modulus
            self.__remainder = m.__class__(representative) % m

    def remainder(self):
        """
        Return the remainder of the residue class (QuotientRing element)
        @p self.  This is an element of the source ring(), not a residue class.
        """
        return self.__remainder

    def __bool__(self):
        """
        Test whether the residue class (QuotientRing element) is non-zero:
        return @c True if, and only if, the remainder modulo modulus() is
        non-zero. Return @c False if the remainder is zero.

        Implicit conversions to boolean (truth) values use this method, for
        example when @c x is an element of a QuotientRing:
        @code
        if x:
            do_something()
        @endcode
        """
        return bool(self.__remainder)

    def __eq__(self, other):
        """
        Test whether another residue class (QuotientRing element) @p other is
        equivalent to @p self; return @c True if that is the case.  The infix
        operator @c == calls this method.

        Two residue classes @f$ [x], [y] @f$ are equal if, and only if, the
        difference of two representatives is a multiple of the modulus():
        @f$ x-y = m\cdot z @f$. 
        """
        # The representative is always the remainder; an equality test suffices
        return self.__remainder == other.remainder()

    def __add__(self, other):
        """
        Return the sum of @p self and @p other. The infix operator @c + calls
        this method.

        The sum of two residue classes (QuotientRing elements) @f$ [x], [y] @f$
        is the residue class @f$ [x + y] @f$. 
        """
        return self.__class__(self.__remainder + other.remainder())

    def __neg__(self):
        """
        Return the additive inverse of @p self, which is @f$ [-x] @f$
        for a residue class (QuotientRing element) @f$ [x] @f$. The negation
        operator @c -x (unary minus) calls this method.
        """
        return self.__class__(-self.__remainder)

    def __mul__(self, other):
        """
        Return the product of @p self and @p other. The infix operator @c *
        calls this method.

        The product of two residue classes (QuotientRing elements)
        @f$ [x], [y] @f$ is the residue class @f$ [x \cdot y] @f$. 
        """
        return self.__class__(self.__remainder * other.remainder())

    def __truediv__(self, other):
        """
        Return the quotient of @p self and @p other: multiply @p self with
        @c other.multiplicative_inverse(), so that
        @code
        (self / other) * other == self
        @endcode
        The infix operator @c / calls this method.

        @exception ZeroDivisionError   if @p other is not a unit, that is, has
                                       no multiplicative inverse.
        """
        return self * other.multiplicative_inverse()

    def __rtruediv__(self, other):
        """
        Return the quotient of @p other and @p self: multiply @p other with
        @c self.multiplicative_inverse(), so that
        @code
        (other / self) * self == other
        @endcode
        The infix operator @c / calls this method if other.__truediv__()
        returned @c NotImplemented.

        @exception ZeroDivisionError   if @p other is not a unit, that is, has
                                       no multiplicative inverse.
        """
        return other * self.multiplicative_inverse()

    def multiplicative_inverse(self):
        """
        Return an residue class (QuotientRing element) @c n such that
        @c n * self is one().

        @exception ZeroDivisionError   if @p self is not a unit, that is, has
                                       no multiplicative inverse.
        """
        if not self.__remainder:
            raise ZeroDivisionError

        inverse, ignore, gcd = \
            extended_euclidean_algorithm(self.__remainder, self._modulus)

        if gcd == self._ring.one():
            return self.__class__(inverse)
        else:
            message = "element has no inverse: representative and modulus " \
                      "are not relatively prime"
            raise ZeroDivisionError(message)

    #- Class Methods-----------------------------------------------------------

    @classmethod
    def modulus(cls):
        """
        Return the quotient ring's modulus; this is an element of the
        source ring(), not a residue class (QuotientRing element).
        """
        return cls._modulus

    @classmethod
    def ring(cls):
        """
        Return the source ring.
        """
        return cls._ring

    @classmethod
    def zero(cls):
        """
        Return the quotient ring's neutral element of addition: the residue
        class (QuotientRing element) of ring().zero()
        """
        return cls(cls._ring.zero())

    @classmethod
    def one(cls):
        """
        Return the quotient ring's neutral element of multiplication: the
        residue class (QuotientRing element) of ring().one()
        """
        return cls(cls._ring.one())
Exemple #2
0
class LTorsionGroup(metaclass=template("_elliptic_curve")):
    """
    An l-torsion group of an elliptic curve for odd l; use the @c elements()
    method to have intuitive syntax when working with l-torsion points.

    This is a template class that must be instantiated with the elliptic curve.
    Use it, for example, as follows:
    @code
    # Create the underlying elliptic curve
    E = elliptic_curves.naive.EllipticCurve( FiniteField(7), 3, 4 )
    # Instantiate the template; El is a class (here: l-torsion groups of E)
    El = LTorsionGroup( E )

    # Construct the group of 3-torsion points and do something with its points
    for P in El[3].elements():
        do_something(P)
    @endcode

    The l-Torsion points of an elliptic curve are the points of order l.  Their
    coordinates come from the algebraic closure of the elliptic curve's field,
    which makes them badly suited for computer representation. (Extending the
    coordinate domain to the algebraic closure makes sure that all points of
    order l are available; there are always @f$ l^2 @f$ points of order l if
    l is prime to the field characteristic.) 

    @note  The l-torsion points are represented implicitly through polynomial
           arithmetic.  Therefore, the list of points in returned by
           @c elements() contains only a single entry: the point @f$ (x, y) \in
           E\bigl(\mathbb{F}_{p}(E) / \psi_l\bigr) @f$, where
           @f$ \mathbb{F}_{p}(E)/\psi_l @f$ denotes the field of rational
           functions over the elliptic curve @f$ E @f$ with numerator and
           denominator polynomials taken modulo the l-th division polynomial
           @f$ \psi_l @f$.

    @note  The class supports only odd torsions greater one because
           multivariate polynomial arithmetic is unavailable

    @see   Silverman, Joseph H., "The Arithmetic of Elliptic Curves",
           second edition, Springer, 2009, p. 373
    """

    #- Instance Methods -------------------------------------------------------

    def __init__(self, torsion):
        """
        Construct a new l-torsion group for the given @p torsion.

        @param torsion The @p torsion is the order of the points in the group;
                       the group represents all points of order @p torsion.  It
                       must be an odd integer greater than 1 and prime to the
                       field characteristic; the limitation comes from the
                       implicit representation, see above. 
        """
        torsion = int(torsion)
        if torsion < 3 or torsion % 2 == 0 or self.curve().field()(
                torsion) == 0:
            raise ValueError("only odd torsions greater than 1 are supported")

        self.__torsion = torsion
        self.__point = None

    def elements(self):
        """
        Return a list containing the one point that implicitly represents
        the whole group.

        Use it, for example, to elegantly perform an operation on all l-torsion
        points:
        @code
        for P in torsion_group.elements():
            do_something(P)
        @endcode

        The one point in the list is @f$ (x, y) \in
        E\bigl(\mathbb{F}_{p}(E) / \psi_l\bigr) @f$, where
        @f$ \mathbb{F}_{p}(E)/\psi_l @f$ denotes the field of rational functions
        over the elliptic curve @f$ E @f$ with numerator and denominator
        polynomials taken modulo the l-th division polynomial @f$ \psi_l @f$.
        """
        if not self.__point:
            self.__init_point()
        return [self.__point]

    def torsion(self):
        """
        Return the group's torsion, that is, l for the l-torsion group.
        """
        return self.__torsion

    def __init_point(self):
        """
        Create the point that implicitly represents the whole l-torsion group
        and store it in the cache.

        Calling the method has no effect if the point already exists.
        """
        if self.__point:
            return

        # R = F[x] / (y**2 - x**3 - A*x - B)
        R = self._division_polynomial_list().curve_polynomials()
        # The n-th division polynomial
        psi = self._division_polynomial_list()[self.__torsion]

        # T = ( F[x] / (y**2 - x**3 - A*x - B) ) / psi(l)
        S = QuotientRing(R, psi)
        T = FractionField(S)

        A, B = R.curve().parameters()

        # Polynomials x and y on the curve interpreted
        # as elements of the field of fractions
        x = T(R((0, 1), 0))
        y = T(R(0, 1))

        self.__point = EllipticCurve(T, A, B)(x, y)

    #- Class Methods-----------------------------------------------------------

    @classmethod
    def curve(cls):
        """
        Return the elliptic curve that was used to initialize the template.
        """
        return cls._elliptic_curve

    @classmethod
    def _division_polynomial_list(cls):
        """
        Return the list of division polynomials that supplies the modulus for
        the implicit polynomial representation of the l-torsion points.
        """
        try:
            return cls.__division_polynomial_list
        except AttributeError:
            # R = F[x] / (y**2 - x**3 - A*x - B)
            # where F, A, B are the elliptic curve's field and its parameters.
            R = CurvePolynomials(cls._elliptic_curve)
            cls.__division_polynomial_list = DivisionPolynomialsList(R)

            return cls.__division_polynomial_list
class Polynomials(CommutativeRing, metaclass=template("_coefficient_field")):
    """
    A ring of polynomials in one indeterminate with coefficients from the
    provided field; the polynomials support infix notation for ring operations,
    as well as mixed argument types.
    
    This is a template class that must be instantiated with the coefficient
    field.  Use it, for example, as follows:
    @code
    # Instantiate the template; GF7x is a class (here: polynomials over GF7)
    GF7 = fields.finite.naive.FiniteField( 7 )
    GF7x = Polynomials( GF7 )
    p = GF7x(1, 1)    # Create a polynomial: x + 1
    q = GF7x(-1, 1)   # Another polynomial: x - 1
    p * q == GF7x(-1, 0, 1)    # This is true: (x + 1)(x - 1) = x^2 - 1
    type(p) is GF7x   # This is also true

    # Use the polynomial 'x' for a more natural syntax
    x = GF7(0, 1)
    x**3 - 2*x + 1 == GF7x(1, -2, 0, 1)
    @endcode
    
    The polynomial ring operations are defined in terms of the coefficient
    field operations.  For everything to work as expected, the template
    argument must be a class whose instances implement the fields.Field
    interface.  Typically, this will be some specialization of
    fields.finite.naive.FiniteField.
    
    Rings, however, also work as a source of coefficients.  Only division
    requires that the leading coefficient is a unit. 

    @note  The implementation emphasizes simplicity over speed; it omits
           possible optimizations.
    
    @note  The class uses the operand_casting() decorator: @c other operands in
           binary operations will first be treated as Polynomial elements.
           If that fails, the operation will be repeated after @p other was fed
           to the constructor __init__().  If that fails, too, then the
           operation returns @c NotImplemented (so that @p other.__rop__()
           might be invoked).
    
    @see   For example, Robinson, Derek J. S.,
           "An Introduction to Abstract Algebra", p. 100.
    """

    #- Instance Methods -----------------------------------------------------------

    def __init__(self, element_description, *further_coefficients):
        """
        Create a new polynomial from the given @p element_description.
        
        @param element_description
            Valid input values are:
            - A Polynomial over the same field: then the polynomial is copied
              and @p further_coefficients is ignored.
            - An iterable object (with an @c __iter__() method): then the
              polynomial coefficients are read from the iterable and, again,
              @p further_coefficients is ignored.  The list must be in
              ascending order: first the constant, then the linear, quadratic,
              and cubic coefficients; and so on.
            - Any number of arguments that can be interpreted as coefficient
              field elements; they will be combined
              into the list of coefficients
        
        @param further_coefficients    The list of variable positional
                                       arguments.
        
        For example, there are several ways to construct the polynomial
        @f$ x^3 + 4x^2 - 3x + 2@f$:
        @code
        # Suppose R is a Polynomials template specialization
        # List of coefficients
        p2 = R( [2, -3, 4, 1] )
        # Variable argument count
        p3 = R( 2, -3, 4, 1 )
        # Copy
        p1 = R( p2 )
        @endcode
        
        @note  Leading zeros will be ignored.
        """
        # List of coefficients is in ascending order without leading zeros.
        # Example: x^2 + 2x + 5 = [5, 2, 1]
        if isinstance(element_description, self.__class__):
            # A reference suffices because objects are immutable
            self.__coefficients = element_description.__coefficients

        else:
            if type(element_description) in [list, tuple]:
                coefficients = element_description
            else:
                coefficients = [element_description
                                ] + list(further_coefficients)

            F = self._coefficient_field
            self.__coefficients = [F(c) for c in coefficients]

        self.__remove_leading_zeros()

    def coefficients(self):
        """
        Return a copy of the list of coefficients.  The list is in ascending
        order: it starts with the constant coefficient, then the linear,
        quadratic, and cubic coefficients; and so on.  It ends with the leading
        coefficient.
        """
        return self.__coefficients[:]

    def leading_coefficient(self):
        """
        Return the leading coefficient, or zero if @p self is
        the zero polynomial.
        """
        if self.__coefficients:
            return self.__coefficients[-1]
        else:
            return self._coefficient_field.zero()

    def degree(self):
        """
        Return the degree of the polynomial.
        
        @note  The zero polynomial has degree @f$ -\infty @f$; however, this
               implementation returns the number @f$ -2^{30} @f$ to avoid
               special constructions that side-step Python's @c int objects.
               For practical purposes, this should suffice.
        """
        if not self.__coefficients:
            # FIXME: The degree of the zero polynomial is minus infinity.
            #        This will, however, do for now.
            return -(2**30)
        return len(self.__coefficients) - 1

    def __bool__(self):
        """
        Test whether the polynomial is non-zero: return @c True if, and only
        if, at least one coefficient is non-zero. Return @c False if all
        coefficients are zero.
        
        Implicit conversions to boolean (truth) values use this method, for
        example when @c x is an element of Polynomials:
        @code
        if x:
            do_something()
        @endcode
        """
        return bool(self.__coefficients)

    def __eq__(self, other):
        """
        Test whether another polynomial @p other is equal to @p self; return
        @c True if that is the case.  The infix operator @c == calls
        this method.
        
        Two polynomials are equal if, and only if, all their coefficients are
        equal. 
        """
        if self.degree() != other.degree():
            return False

        # zip() is OK because we have identical length
        for x, y in zip(self.__coefficients, other.__coefficients):
            if x != y:
                return False

        return True

    def __add__(self, other):
        """
        Return the sum of @p self and @p other. The infix operator @c + calls
        this method.
        
        Polynomials add coefficient-wise without carrying: the sum of two
        polynomials @f$ \sum_{k} a_{k}x^{k} @f$ and
        @f$ \sum_{k} b_{k}x^{k} @f$ is the polynomial
        @f$ \sum_{k} (a_{k} + b_{k})x^{k} @f$.
        """
        zero = self._coefficient_field.zero()
        coefficient_pairs = self.__pad_and_zip(self.__coefficients,
                                               other.__coefficients, zero)
        coefficient_sums = [x + y for x, y in coefficient_pairs]
        return self.__class__(coefficient_sums)

    def __neg__(self):
        """
        Return the additive inverse (the negative) of @p self.
        
        A polynomial is negated by negating its coefficients: the additive
        inverse of @f$ \sum_{k} a_{k}x^{k} @f$ is @f$ \sum_{k} -a_{k}x^{k} @f$.
        """
        return self.__class__([-c for c in self.__coefficients])

    def __mul__(self, other):
        """
        Return the product of @p self and @p other. The infix operator @c *
        calls this method.
        
        Two polynomials are multiplied through convolution of their
        coefficients: for polynomials @f$ \sum_{k} a_{k}x^{k} @f$ and
        @f$ \sum_{k} b_{k}x^{k} @f$, their product is the polynomial
        @f$ \sum_{k} \sum_{j=0}^{k}(a_{j} + b_{k-j})x^{k} @f$.
        """
        # Initialize result as list of all zeros
        zero = self._coefficient_field.zero()
        # Add 2 because degrees count from 0.
        result = [zero] * (self.degree() + other.degree() + 2)

        for i, x in enumerate(self.__coefficients):
            for j, y in enumerate(other.__coefficients):
                result[i + j] += x * y

        return self.__class__(result)

    def __divmod__(self, other):
        """
        Return the quotient and remainder of @p self divided by @p other.
        
        The built-in method @c divmod() calls this method.  For the returned
        values @c quotient and @c remainder, we have
        @code
        self == quotient * other + remainder
        @endcode
        """
        # Lists will be modified, so copy them
        dividend = self.__coefficients[:]
        divisor = other.__coefficients[:]
        n = other.degree()

        zero = self._coefficient_field.zero()
        quotient = [zero] * (self.degree() - n + 1)

        for k in reversed(range(0, len(quotient))):
            quotient[k] = dividend[n + k] / divisor[n]
            for j in range(k, n + k):
                dividend[j] -= quotient[k] * divisor[j - k]

        remainder = dividend[0:n]

        return self.__class__( quotient ), \
                self.__class__( remainder )

    def __call__(self, point):
        """
        Return the polynomial function's value at @p point.
        
        This method evaluates the polynomial by replacing the indeterminate
        with @p point and summing the powers: for a polynomial
        @f$ p(x) = \sum_{k} a_{k}x^{k} @f$, the value at @f$ c @f$ is
        @f$ p(c) = \sum_{k} a_{k}c^{k} @f$.
        """
        return sum([c * point**i for i, c in enumerate(self.__coefficients)])

    #- Class Methods-----------------------------------------------------------

    @classmethod
    def coefficient_field(cls):
        """
        Return the coefficient field.
        """
        return cls._coefficient_field

    @classmethod
    def zero(cls):
        """
        Return the polynomial ring's neutral element of addition: the zero
        polynomial.
        """
        return cls(cls._coefficient_field.zero())

    @classmethod
    def one(cls):
        """
        Return the polynomial ring's neutral element of multiplication: the
        constant polynomial one.
        """
        return cls(cls._coefficient_field.one())

    #- Auxiliary Functions ----------------------------------------------------

    def __remove_leading_zeros(self):
        """
        Remove all leading zeros from the list of coefficients.  This might
        empty the list completely. 
        """
        while len( self.__coefficients ) > 0 \
                and not self.__coefficients[-1]:
            self.__coefficients.pop()

    @staticmethod
    def __pad_and_zip(list1, list2, padding_element):
        """
        Combine @p list1 and @p list1 into a single list of pairs.  If the
        lists have different lengths, pad the shorter list with
        @p padding_element: fill in elements until they have the same length.
        """
        max_length = max(len(list1), len(list2))
        padded_list1 = list1 + ([padding_element] * (max_length - len(list1)))
        padded_list2 = list2 + ([padding_element] * (max_length - len(list2)))
        return zip(padded_list1, padded_list2)
Exemple #4
0
class FractionField(Field, metaclass=template("_integral_domain")):
    """
    A field of formal quotients (fractions)
    @f$ \frac{\mathrm{numerator}}{\mathrm{denominator}} @f$ over an integral
    domain; the field elements support infix notation for field operations,
    and mixed argument types.

    This is a template class that must be instantiated with the underlying
    integral domain. For example:
    @code
    # Instantiate the template; Q is a class (here: the rational numbers)
    Q = FractionField( rings.integers.naive.Integers )
    x = Q(1, 2)    # Create a field element: one over two
    y = Q(2)       # Another field element: two, which is two over one
    z = x*y        # z is two over two; no canceling takes place
    z == Q(4, 4)   # This true because 4*2 == 2*4
    type(z) is Q   # This is also true
    @endcode

    A formal quotient is an equivalence class of pairs (numerator, denominator)
    whose entries come from an integral domain (a commutative ring with
    identity and no zero divisors). Two pairs @f$ \frac{u}{v} @f$ and
    @f$ \frac{s}{t} @f$ are considered equivalent if, and only if, @f$ u\cdot t
    = s\cdot v @f$. Observe that this comparison uses multiplication only;
    since the elements come from a ring and might have no multiplicative
    inverses, division does not work.

    @note  A consequence of omitted canceling is that the elements might grow
           large.

    @note  The class uses the operand_casting() decorator: @c other operands in
           binary operations will first be treated as FractionField elements.
           If that fails, the operation will be repeated after @p other was fed
           to the constructor __init__().  If that fails, too, then the
           operation returns @c NotImplemented (so that @p other.__rop__()
           might be invoked).

    @see   For example, Robinson, Derek J. S.,
           "An Introduction to Abstract Algebra", p. 113.
    """
    def __init__(self, numerator, denominator=None):
        """
        Construct a new formal quotient (@p numerator, @p denominator).

        If the @p numerator is an element of this FractionField class, then
        the new element is a copy of @p numerator; @p denominator is ignored.
        Otherwise, a @c None denominator (the default) is interpreted as the
        multiplicative neutral element (one) in the underlying integral domain.

        @exception     ZeroDivisonError    if the denominator is zero (and not
                                           @c None, see above).
        """
        if isinstance(numerator, self.__class__):
            # Copy an instance
            self.__numerator = numerator.__numerator
            self.__denominator = numerator.__denominator

        else:
            if denominator is None:
                denominator = self._integral_domain.one()
            if not denominator:
                raise ZeroDivisionError
            self.__numerator = self._integral_domain(numerator)
            self.__denominator = self._integral_domain(denominator)

    def __bool__(self):
        """
        Test whether the formal quotient is non-zero: return @c True if, and
        only if, the numerator is non-zero. Return @c False if the numerator
        is zero.

        Implicit conversions to boolean (truth) values use this method, for
        example when @c x is an element of a FractionField:
        @code
        if x:
            do_something()
        @endcode
        """
        return bool(self.__numerator)

    def __eq__(self, other):
        """
        Test whether another formal quotient @p other is equivalent to @p self;
        return @c True if that is the case.  The infix operator @c == calls
        this method.

        Two fractions @f$ \frac{u}{v} @f$ and @f$ \frac{s}{t} @f$ are equivalent
        if, and only if, @f$ u\cdot t = s\cdot v @f$. 

        @note  Comparison may be expensive: it requires two multiplications in
               the underlying integral domain.
        """
        # Use the basic definition of equivalence for comparison.
        return self.__numerator * other.__denominator \
            == other.__numerator * self.__denominator

    def __add__(self, other):
        """
        Return the sum of @p self and @p other. The infix operator @c + calls
        this method.

        As usual, the sum of two fractions @f$ \frac{u}{v} @f$ and
        @f$ \frac{s}{t} @f$ is @f$ \frac{ u\cdot t + s\cdot v }{ v\cdot t } @f$.

        @note  Canceling of common factors is impossible, for the underlying
               integral domain contains non-units. Therefore, repeated addition
               results in large elements.
        """
        numerator = self.__numerator * other.__denominator \
            + self.__denominator * other.__numerator
        denominator = self.__denominator * other.__denominator
        return self.__class__(numerator, denominator)

    def __neg__(self):
        """
        Return the additive inverse of @p self, which is @f$ \frac{-u}{v} @f$
        for a fraction @f$ \frac{u}{v} @f$. The negation operator @c -x
        (unary minus) calls this method.
        """
        return self.__class__(-self.__numerator, self.__denominator)

    def __mul__(self, other):
        """
        Return the product of @p self and @p other. The @c * infix operator
        calls this method.

        As usual, the product of two fractions @f$ \frac{u}{v} @f$ and
        @f$ \frac{s}{t} @f$ is @f$ \frac{ u\cdot s }{ v\cdot t } @f$.

        @note  Canceling of common factors is impossible, for the underlying
               integral domain contains non-units. Therefore, repeated
               multiplication results in large elements.
        """
        numerator = self.__numerator * other.__numerator
        denominator = self.__denominator * other.__denominator
        return self.__class__(numerator, denominator)

    def multiplicative_inverse(self):
        """
        Return the multiplicative inverse of @p self, which is
        @f$ \frac{v}{u} @f$ for a fraction @f$ \frac{u}{v} @f$.

        @exception ZeroDivisionError   if @p self is zero (has a zero numerator)

        @see   __bool__()
        """
        if not self:
            raise ZeroDivisionError

        return self.__class__(self.__denominator, self.__numerator)

    @classmethod
    def zero(cls):
        """
        Return the field's neutral element of addition (zero).

        Zero in a FractionField is a fraction @f$ \frac{0}{1} @f$, where
        @f$ 0 @f$ and @f$ 1 @f$ denote the zero and one of the underlying
        integral domain.
        """
        return cls(cls._integral_domain.zero(), cls._integral_domain.one())

    @classmethod
    def one(cls):
        """
        Return the field's neutral element of multiplication (one).

        One (or unity) in a FractionField is a fraction @f$ \frac{1}{1} @f$,
        where @f$ 1 @f$ denotes the one of the underlying integral domain.
        """
        return cls(cls._integral_domain.one(), cls._integral_domain.one())
Exemple #5
0
class CurvePolynomials(CommutativeRing, metaclass=template("_elliptic_curve")):
    """
    A ring of polynomials over an elliptic curve; the polynomials support infix
    notation for ring operations, as well as mixed argument types.

    This is a template class that must be instantiated with the elliptic curve.
    Use it, for example, as follows:
    @code
    # Create the underlying elliptic curve
    E = elliptic_curves.naive.EllipticCurve( FiniteField(7), 3, 4 )
    # Instantiate the template; GF7E is a class (here: polynomials over E)
    GF7E = CurvePolynomials( E )

    # Use two coefficient lists as arguments; the polynomial is (list1) + y*(list2)
    s = GF7E( (4, 3, 0, 1) )  # The curve's defining polynomial
    y = GF7E( 0, (0, 1) )     # Another polynomial: y
    y**2 == s                 # The fundamental relation holds
    type(s) is GF7E           # This is also true

    # Use the polynomials 'x' and 'y' for a more natural syntax
    x = GF7( (0, 1), () )     # The empty tuple is just for emphasis
    y = GF7( (), (0, 1) )     # The empty tuple is just for emphasis
    y**2 == x**3 + 3**x + 4
    p = x**2 + 2* x + y*( 3 * x**5 + 6 * x**2 )  # A (pseudo) random polynomial
    @endcode

    The ring of polynomials over an elliptic curve @f$ E @f$ is
    @f$ \mathbb{F}[x,y]/(y^2 - x^3 - Ax - B) @f$, where @f$ \mathbb{F} @f$ is
    the field over which the curve was defined, and @f$ A, B @f$ are the curve
    parameters.  The quotient polynomial @f$ y^2 - x^3 - Ax - B @f$ expresses
    that the polynomial's domain is the set of points on @f$ E @f$; the
    (Cartesian) coordinates of the points @f$ (x, y) \in E @f$ are connected by
    the fundamental relation @f$ y^2 = x^3 + Ax + B @f$.

    @note  As a consequence of the fundamental relation, powers of @f$ y @f$
           greater than one can be reduced to a polynomial in  @f$ x @f$.  This
           is what this implementation does.  Since it does not provide full
           multivariate arithmetic, division (with remainder; see __divmod__())
           only works in the following cases:
           - The divisor is a polynomial in @f$ x @f$ alone; this works for all
             dividends.
           - Dividend and divisor are polynomials in @f$ y @f$ alone.

           Besides restricting arithmetic, the implementation emphasizes
           simplicity over speed; it omits possible optimizations.

    @note  The class uses the operand_casting() decorator: @c other operands in
           binary operations will first be treated as CurvePolynomial elements.
           If that fails, the operation will be repeated after @p other was fed
           to the constructor __init__().  If that fails, too, then the
           operation returns @c NotImplemented (so that @p other.__rop__()
           might be invoked).

    @see   Charlap, Leonard S. and Robbins, David P., "CRD Expositroy Report 31:
           An Elementary Introduction to Elliptic Curves", 1988, chapter 3
    """

    #- Instance Methods -----------------------------------------------------------

    def __init__(self, x_factor, y_factor=None):
        """
        Create a new polynomial from the given coefficient lists @p x_factor
        and @p y_factor.

        Polynomials @f$ s @f$ over elliptic curves have a canonical form
        @f$ s(x, y) = a(x) + y\cdot b(x) @f$, where @f$ a, b @f$ are
        polynomials of @f$ x @f$ alone.  The parameter @p x_factor
        determines the coefficients of @f$ a @f$, @p y_factor determines the
        coefficients of @f$ b @f$.

        @param x_factor    An iterable that lists the coefficients of the
                           polynomial @f$ a @f$ in ascending order:
                           @p x_factor[0] is the constant,
                           @p x_factor[1] the linear, @p x_factor[2] the
                           quadratic coeffiecient; and so on.
        @param y_factor    Similar to @p x_factor, but for the polynomial
                           @f$ b @f$.

        For example, construct the polynomial @f$ x^3 + 4x - y\cdot(3x + 2) @f$
        as follows:
        @code
        # Suppose E is an is an EllipticCurve template specialization
        R = CurvePolynomials( E )
        p = R( (0, 4, 0, 1), (2, 3) )
        @endcode

        @note  Leading zeros in the coefficient lists will be ignored.
        """
        # x_factor: polynomial in x only
        # y_factor: polynomial in x [implicitly * y]
        if isinstance(x_factor, self.__class__):
            # Casting an object that already has this type means copying it.
            self.__x_factor = x_factor.__x_factor
            self.__y_factor = x_factor.__y_factor
        else:
            self.__x_factor = self.polynomial_ring()(x_factor)
            if y_factor is None:
                self.__y_factor = self.polynomial_ring().zero()
            else:
                self.__y_factor = self.polynomial_ring()(y_factor)

    def x_factor(self):
        """
        Return the polynomial @f$ a @f$ from the canonical form
        @f$ s(x, y) = a(x) + y\cdot b(x) @f$ of @p self.

        @note  The returned polynomial is univariate.
        """
        return self.__x_factor

    def y_factor(self):
        """
        Return the polynomial @f$ b @f$ from the canonical form
        @f$ s(x, y) = a(x) + y\cdot b(x) @f$ of @p self.

        @note  The returned polynomial is univariate.
        """
        return self.__y_factor

    def leading_coefficient(self):
        """
        Return the leading coefficient, or zero if @p self is
        the zero polynomial.

        @note  The fundamental relation @f$ y^2 = x^3 + Ax + B @f$ forces the
               linear polynomials @f$ x @f$ and @f$ y @f$ to have different
               degrees.  Thus @f$ \deg(x) = 2 @f$ and @f$ \deg(y) = 3 @f$.
               The leading coefficient is the coefficient of the power with
               the highest degree; for @f$ x^4 + 2y^3 @f$ it is 2. (Ignore
               the reduction of @f$ y^2 @f$ for a moment.)
        """
        # Equality is impossible: we compare an odd and an even number.
        if (2 * self.__x_factor.degree()) > (3 + 2 * self.__y_factor.degree()):
            return self.__x_factor.leading_coefficient()
        else:
            return self.__y_factor.leading_coefficient()

    def __bool__(self):
        """
        Test whether the polynomial is non-zero: return @c True if, and only
        if, at least one coefficient is non-zero. Return @c False if all
        coefficients are zero.

        Implicit conversions to boolean (truth) values use this method, for
        example when @c x is an element of Polynomials:
        @code
        if x:
            do_something()
        @endcode
        """
        return bool(self.__x_factor) or bool(self.__y_factor)

    def __eq__(self, other):
        """
        Test whether another polynomial @p other is equal to @p self; return
        @c True if that is the case.  The infix operator @c == calls
        this method.

        Two polynomials are equal if, and only if, all the coefficients of
        their canonical forms are equal. 
        """
        return self.__x_factor == other.x_factor() \
            and self.__y_factor == other.y_factor()

    def __add__(self, other):
        """
        Return the sum of @p self and @p other. The infix operator @c + calls
        this method.

        Polynomials add coefficient-wise without carrying: the sum of two
        polynomials @f$ \sum_{k,l} a_{k,l}x^{k}y^{l} @f$ and
        @f$ \sum_{k,l} b_{k,l}x^{k}y^{l} @f$ is the polynomial
        @f$ \sum_{k,l} (a_{k,l} + b_{k,l})x^{k}y^{l} @f$.
        """
        x = self.__x_factor + other.x_factor()
        y = self.__y_factor + other.y_factor()
        return self.__class__(x, y)

    def __neg__(self):
        """
        Return the additive inverse (the negative) of @p self.

        A polynomial is negated by negating its coefficients: the additive
        inverse of @f$ \sum_{k,l} a_{k,l}x^{k}y^{l} @f$ is
        @f$ \sum_{k,l} -a_{k,l}x^{k}y^{l} @f$.
        """
        return self.__class__(
            -self.__x_factor,
            -self.__y_factor
        )

    def __mul__(self, other):
        """
        Return the product of @p self and @p other. The infix operator @c *
        calls this method.

        Two polynomials are multiplied through convolution of their
        coefficients; however that is overly complicated to write with sums.
        Instead, simply imagine mutliplying the canonical forms
        @f$ a(x) + y \cdot b(x) @f$ and @f$ c(x) + y \cdot d(x) @f$.

        @see   rings.polynomials.naive.Polynomials.__mul__() for an explanation
               of univariate polynomial multiplication.
        """
        x = self.__x_factor * other.x_factor()
        xy = self.__x_factor * other.y_factor()
        yx = self.__y_factor * other.x_factor()
        y = xy + yx

        # Reduce y**2 to y2_reduction
        if self.__y_factor and other.y_factor():
            # y = self.__y_factor - self.__y_factor # Zero of unknown type
            x += self.__y_factor * other.y_factor() * self.y2_reduction()

        return self.__class__(x, y)

    def __divmod__(self, other):
        """
        Return the quotient and remainder of @p self divided by @p other.

        The built-in method @c divmod() calls this method.  For the returned
        values @c quotient and @c remainder, we have
        @code
        self == quotient * other + remainder
        @endcode

        @note  Division has limited functionality in this implementation.  It
               only works in the following cases:
               - The divisor (@p other) is a polynomial in @f$ x @f$ alone; this
                 works for all dividends.
               - Dividend (@p self) and divisor (@p other) are polynomials in
                 @f$ y @f$ alone.
        """
        if not other:
            raise ZeroDivisionError

        if not self:
            return self.polynomial_ring().zero(), self.polynomial_ring().zero()

        if other.y_factor() and (self.__x_factor or other.x_factor()):
            # Ok, this is cheating. Multivariate polynomial division is,
            # however, quite complicated and unnecessary for our purpose.
            raise ValueError("multivariate division is unsupported")

        if other.x_factor():
            qx, rx = divmod(self.__x_factor, other.x_factor())
            qy, ry = divmod(self.__y_factor, other.x_factor())
        else:
            # Case: self and other have only y factors
            qx, rx = divmod(self.__y_factor, other.y_factor())
            zero = self.polynomial_ring().zero().x_factor()
            qy, ry = zero, zero

        quotient = self.__class__(qx, qy)
        remainder = self.__class__(rx, ry)

        return quotient, remainder

    #- Class Methods-----------------------------------------------------------

    @classmethod
    def curve(cls):
        """
        Return the elliptic curve over which the polynomial ring was defined.
        """
        return cls._elliptic_curve

    @classmethod
    def y2_reduction(cls):
        """
        Return the polynomial that may be substituted for @f$ y^2 @f$.  For an
        elliptic curve with parameters @f$ A, B @f$, this is the polynomial
        @f$ x^3 + Ax + B @f$.
        """
        try:
            return cls.__y2_reduction
        except AttributeError:
            # Use y**2 = x**3 + A*x + B to eliminate any y-power greater than 1.
            A, B = cls._elliptic_curve.parameters()
            cls.__y2_reduction = cls.polynomial_ring()(B, A, 0, 1)
            return cls.__y2_reduction

    @classmethod
    def polynomial_ring(cls):
        """
        Return the ring of (univariate) polynomials that is source of the
        polynomials @f$ a @f$ and @f$ b @f$ in the canonical form
        @f$ s(x, y) = a(x) + y \cdot b(x) @f$.
        """
        try:
            return cls.__polynomial_ring
        except AttributeError:
            cls.__polynomial_ring = Polynomials(cls._elliptic_curve.field())
            return cls.__polynomial_ring

    @classmethod
    def zero(cls):
        """
        Return the polynomial ring's neutral element of addition: the zero
        polynomial.
        """
        R = cls.polynomial_ring()
        return cls(R.zero(), R.zero())

    @classmethod
    def one(cls):
        """
        Return the polynomial ring's neutral element of multiplication: the
        constant polynomial one.
        """
        R = cls.polynomial_ring()
        return cls(R.one(), R.zero())
Exemple #6
0
class EllipticCurve(metaclass=template("_field", "_A", "_B")):
    """
    An elliptic curve; the points support additive infix notation for group
    operations, as well as multiplication by integers.

    This is a template class that must be instantiated with the field and the
    parameters @c A and @c B.  Use it, for example, as follows:
    @code
    # Instantiate the elliptic curve template as y^2 = x^3 + 3x + 4 over GF(7)
    E = EllipticCurve( FiniteField(7), 3, 4 )

    # Create two points and add them
    P = E(1, 1)
    Q = E(2, 5)

    P + Q == E(6, 0)   # Point addition
    3*P == E(0, 5)     # Multiplication by integers

    O = PointAtInfinity()
    P == P + O         # The point at infinity is the neutral element
    type(P) is E       # This is also true
    @endcode

    An elliptic curve is the set of all points @f$ E = \bigl\{ (x, y) \mid y^2
    = x^3 + Ax + B \bigr\} \cup \{\mathcal{O}\} @f$, where @f$ \mathcal{O} @f$
    is a special element, the point at infinity.  The points form an abelian
    group with the point at infinity as neutral element.

    The relation @f$ y^2 = x^3 + Ax + B @f$ between the (Cartesian) coordinates
    of the points @f$ (x, y) \in E @f$ is called the fundamental relation.

    @see   Charlap, Leonard S. and Robbins, David P., "CRD Expositroy Report 31:
           An Elementary Introduction to Elliptic Curves", 1988, chapter 1
    """

    #- Instance Methods -----------------------------------------------------------

    def __init__(self, x, y):
        """
        Construct a new point on the elliptic curve with coordinates @p x
        and @p y.

        The coordinates must be connected by the fundamental relation
        @f$ y^2 = x^3 + Ax + B @f$.

        @param x   The first coordinate of the point.  It will be interpreted
                   as an element of field(), which might result in a ValueError
                   or TypeError.
        @param y   The second coordinate of the point.  It will be interpreted
                   as an element of field(), which might result in a ValueError
                   or TypeError.

        @exception AssertionError  if the fundamental relation does not hold.
        @exception ValueError      if an argument cannot be cast as element
                                   of field(). 
        @exception TypeError       same as @c ValueError. 
        """
        self.__x = self._field(x)
        self.__y = self._field(y)

        A, B = self.parameters()
        x, y = self.__x, self.__y
        assert y ** 2 == x ** 3 + A * x + B, \
            "point ({x}, {y}) is not on the curve".format(x=x, y=y)

    def x(self):
        """
        Return the first (Cartesian) coordinate of the point @f$ (x,y) @f$
        on the curve.

        @note  The fundamental relation links the coordinates:
               @f$ y^2 = x^3 + Ax + B @f$.
        """
        return self.__x

    def y(self):
        """
        Return the second (Cartesian) coordinate of the point @f$ (x,y) @f$
        on the curve.

        @note  The fundamental relation links the coordinates:
               @f$ y^2 = x^3 + Ax + B @f$.
        """
        return self.__y

    def is_infinite(self):
        """
        Test whether the point is infinite or not: always return @c False for
        the point is finite.  The point at infinity returns @c True.
        """
        return False

    def __bool__(self):
        """
        Test whether the point is infinite or not: always return @c True, for
        the point is finite.  The point at infinity returns @c False.

        Implicit conversions to boolean (truth) values use this method, for
        example when @c P is an element of EllipticCurve:
        @code
        if P:
            do_something()
        @endcode
        """
        return True

    def __eq__(self, other):
        """
        Test whether another point @p other is equal to @p self; return
        @c True if that is the case.  The infix operator @c == calls
        this method.

        Two points are equal if, and only if, both of their (Cartesian)
        coordinates are equal or if they both are infinite.
        """
        if other.is_infinite():
            return False
        else:
            return self.__x == other.x() and self.__y == other.y()

    def __neq__(self, other):
        """
        Test whether another point @p other is different from @p self; return
        @c True if that is the case.  The infix operator @c != calls
        this method.

        Two points are different if, and only if, they differ in at least one
        of their (Cartesian) coordinates. 
        """
        return not self == other

    def __add__(self, other):
        """
        Return the sum of @p self and @p other.  The infix operator @c + calls
        this method.

        The geometric interpretation of point addition is to draw a line between
        the points and mirror the point at the third intersection of line and
        curve on the y-axis.  However, the justification why this works is
        rather complicated.  Please see some introductory text for the formulas
        and the respective proofs.

        @note  Addition on elliptic curves is commutative.  The neutral element
               is the point at infinity (compare PointAtInfinity).

        @see   For example Washington, Lawrence C. "Elliptic Curves:
               Number Theory and Cryptography", second edition, CRC Press 2008,
               chapter 2.
        """
        if other.is_infinite():
            return self
        elif self == -other:
            return PointAtInfinity()
        else:
            if self.__x == other.x():
                return self.__double__()
            else:
                return self.__generic_add__(other)

    def __generic_add__(self, other):
        """
        Generic addition of points @p self and @p other: the points are neither
        identical nor inverses.

        @note  This method should not be called directly.  Instead use the
               infix operator @c +, which calls __add__(). 

        @note  Putting the cases in separate methods makes call profiles more
               expressive.

        @see __add__()
        """
        gamma = (other.y() - self.__y) / (other.x() - self.__x)
        u = -self.__x - other.x() + gamma**2
        v = -self.__y - gamma * (u - self.__x)

        return self.__class__(u, v)

    def __double__(self):
        """
        Point doubling: add a point to itself.

        @note  This method should not be called directly.  Instead use the
               infix operator @c +, which calls __add__().

        @note  Putting the cases in separate methods makes call profiles more
               expressive.

        @see __add__()
        """
        A, B = self.parameters()
        delta = (3 * self.__x**2 + A) / (2 * self.__y)
        u = -self.__x - self.__x + delta**2
        v = -self.__y - delta * (u - self.__x)

        return self.__class__(u, v)

    def __neg__(self):
        """
        Return the additive inverse (the negative) of @p self.

        The additive inverse of a point @f$ (x,y) @f$ on the curve is
        @f$ (x, -y) @f$.
        """
        return self.__class__(self.__x, -self.__y)

    def __sub__(self, other):
        """
        Return the difference between @p self and @p other.  The infix operator
        @c - calls this method.

        This is the same as @p self + (-other).

        @see   __add__()
        """
        return self + (-other)

    def __mul__(self, n):
        """
        Multiplication with integers: adding @p n copies of the point @p self;
        the infix operator @c * calls this method.

        This method is used when self was the left factor.

        @param n   A number object that can be interpreted as an integer.  It
                   determines how often the point will be added to itself.

        @exception ValueError  if @p n cannot be cast to @c int().
        @exception TypeError   same as @c ValueError.  
        """
        n = int(n)
        if n == 0:
            return PointAtInfinity()

        point = self
        for i in range(1, n):
            point += self

        return point

    def __rmul__(self, other):
        """
        Multiplication with integers: adding @p n copies of the point @p self;
        the infix operator @c * calls this method.

        This method is used when self was the right factor.

        @param n   A number object that can be interpreted as an integer.  It
                   determines how often the point will be added to itself.

        @exception ValueError  if @p n cannot be cast to @c int().
        @exception TypeError   same as @c ValueError.  
        """
        # Multiplication with integers is always commutative.
        return self * other

    #- Class Methods-----------------------------------------------------------

    @classmethod
    def field(cls):
        """
        Return the field over which the curve was defined.
        """
        return cls._field

    @classmethod
    def parameters(cls):
        """
        Return the parameter pair @f$ (A, B) @f$ of the curve.

        The parameters determine the fundamental relation
        @f$ y^2 = x^3 + Ax + B @f$.
        """
        return (cls._A, cls._B)

    @classmethod
    def is_singular(cls):
        """
        Test whether the curve is singular or not: return @c True if, and
        only if, the curve is singular.

        A curve is singular if the polynomial @f$ x^3 + Ax + B @f$ has
        repeated roots.
        """
        # A curve is singular if and only if its determinant is 0
        # (in fields of characteristic neither 2 nor 3).
        return not 4 * cls._A**3 + 27 * cls._B**2