class EccPoint(object): """A class to abstract a point over an Elliptic Curve. :ivar x: The X-coordinate of the ECC point :vartype x: integer :ivar y: The Y-coordinate of the ECC point :vartype y: integer """ def __init__(self, x, y): self._x = Integer(x) self._y = Integer(y) # Buffers self._common = Integer(0) self._tmp1 = Integer(0) self._x3 = Integer(0) self._y3 = Integer(0) def set(self, point): self._x = Integer(point._x) self._y = Integer(point._y) return self def __eq__(self, point): return self._x == point._x and self._y == point._y def __neg__(self): if self.is_point_at_infinity(): return self.point_at_infinity() return EccPoint(self._x, _curve.p - self._y) def copy(self): return EccPoint(self._x, self._y) def is_point_at_infinity(self): return not (self._x or self._y) @staticmethod def point_at_infinity(): return EccPoint(0, 0) @property def x(self): if self.is_point_at_infinity(): raise ValueError("Point at infinity") return self._x @property def y(self): if self.is_point_at_infinity(): raise ValueError("Point at infinity") return self._y def double(self): """Double this point (in-place operation). :Return: :class:`EccPoint` : this same object (to enable chaining) """ if not self._y: return self.point_at_infinity() common = self._common tmp1 = self._tmp1 x3 = self._x3 y3 = self._y3 # common = (pow(self._x, 2, _curve.p) * 3 - 3) * (self._y << 1).inverse(_curve.p) % _curve.p common.set(self._x) common.inplace_pow(2, _curve.p) common *= 3 common -= 3 tmp1.set(self._y) tmp1 <<= 1 tmp1.inplace_inverse(_curve.p) common *= tmp1 common %= _curve.p # x3 = (pow(common, 2, _curve.p) - 2 * self._x) % _curve.p x3.set(common) x3.inplace_pow(2, _curve.p) x3 -= self._x x3 -= self._x while x3.is_negative(): x3 += _curve.p # y3 = ((self._x - x3) * common - self._y) % _curve.p y3.set(self._x) y3 -= x3 y3 *= common y3 -= self._y y3 %= _curve.p self._x.set(x3) self._y.set(y3) return self def __iadd__(self, point): """Add a second point to this one""" if self.is_point_at_infinity(): return self.set(point) if point.is_point_at_infinity(): return self if self == point: return self.double() if self._x == point._x: return self.set(self.point_at_infinity()) common = self._common tmp1 = self._tmp1 x3 = self._x3 y3 = self._y3 # common = (point._y - self._y) * (point._x - self._x).inverse(_curve.p) % _curve.p common.set(point._y) common -= self._y tmp1.set(point._x) tmp1 -= self._x tmp1.inplace_inverse(_curve.p) common *= tmp1 common %= _curve.p # x3 = (pow(common, 2, _curve.p) - self._x - point._x) % _curve.p x3.set(common) x3.inplace_pow(2, _curve.p) x3 -= self._x x3 -= point._x while x3.is_negative(): x3 += _curve.p # y3 = ((self._x - x3) * common - self._y) % _curve.p y3.set(self._x) y3 -= x3 y3 *= common y3 -= self._y y3 %= _curve.p self._x.set(x3) self._y.set(y3) return self def __add__(self, point): """Return a new point, the addition of this one and another""" result = self.copy() result += point return result def __mul__(self, scalar): """Return a new point, the scalar product of this one""" if scalar < 0: raise ValueError("Scalar multiplication only defined for non-negative integers") # Trivial results if scalar == 0 or self.is_point_at_infinity(): return self.point_at_infinity() elif scalar == 1: return self.copy() # Scalar randomization scalar_blind = Integer.random(exact_bits=64) * _curve.order + scalar # Montgomery key ladder r = [self.point_at_infinity().copy(), self.copy()] bit_size = int(scalar_blind.size_in_bits()) scalar_int = int(scalar_blind) for i in range(bit_size, -1, -1): di = scalar_int >> i & 1 r[di ^ 1] += r[di] r[di].double() return r[0]
def lucas_test(candidate): """Perform a Lucas primality test on an integer. The test is specified in Section C.3.3 of `FIPS PUB 186-4`__. :Parameters: candidate : integer The number to test for primality. :Returns: ``Primality.COMPOSITE`` or ``Primality.PROBABLY_PRIME``. .. __: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf """ if not isinstance(candidate, Integer): candidate = Integer(candidate) # Step 1 if candidate in (1, 2, 3, 5): return PROBABLY_PRIME if candidate.is_even() or candidate.is_perfect_square(): return COMPOSITE # Step 2 def alternate(): value = 5 while True: yield value if value > 0: value += 2 else: value -= 2 value = -value for D in alternate(): if candidate in (D, -D): continue js = Integer.jacobi_symbol(D, candidate) if js == 0: return COMPOSITE if js == -1: break # Found D. P=1 and Q=(1-D)/4 (note that Q is guaranteed to be an integer) # Step 3 # This is \delta(n) = n - jacobi(D/n) K = candidate + 1 # Step 4 r = K.size_in_bits() - 1 # Step 5 # U_1=1 and V_1=P U_i = Integer(1) V_i = Integer(1) U_temp = Integer(0) V_temp = Integer(0) # Step 6 for i in iter_range(r - 1, -1, -1): # Square # U_temp = U_i * V_i % candidate U_temp.set(U_i) U_temp *= V_i U_temp %= candidate # V_temp = (((V_i ** 2 + (U_i ** 2 * D)) * K) >> 1) % candidate V_temp.set(U_i) V_temp *= U_i V_temp *= D V_temp.multiply_accumulate(V_i, V_i) if V_temp.is_odd(): V_temp += candidate V_temp >>= 1 V_temp %= candidate # Multiply if K.get_bit(i): # U_i = (((U_temp + V_temp) * K) >> 1) % candidate U_i.set(U_temp) U_i += V_temp if U_i.is_odd(): U_i += candidate U_i >>= 1 U_i %= candidate # V_i = (((V_temp + U_temp * D) * K) >> 1) % candidate V_i.set(V_temp) V_i.multiply_accumulate(U_temp, D) if V_i.is_odd(): V_i += candidate V_i >>= 1 V_i %= candidate else: U_i.set(U_temp) V_i.set(V_temp) # Step 7 if U_i == 0: return PROBABLY_PRIME return COMPOSITE