diff --git a/speed.py b/speed.py index 3f05309b..bcc8a085 100644 --- a/speed.py +++ b/speed.py @@ -20,16 +20,19 @@ def do(setup_statements, statement): S3 = "msg = six.b('msg')" S4 = "sig = sk.sign(msg)" S5 = "vk = sk.get_verifying_key()" - S6 = "vk.verify(sig, msg)" + S6 = "vk.precompute()" + S7 = "vk.verify(sig, msg)" # We happen to know that .generate() also calculates the # verifying key, which is the time-consuming part. If the code # were changed to lazily calculate vk, we'd need to change this # benchmark to loop over S5 instead of S2 keygen = do([S1], S2) sign = do([S1,S2,S3], S4) - verf = do([S1,S2,S3,S4,S5], S6) + verf = do([S1,S2,S3,S4,S5,S6], S7) import ecdsa c = getattr(ecdsa, curve) sig = ecdsa.SigningKey.generate(c).sign(six.b("msg")) - print("%9s: siglen=%3d, keygen=%.3fs, sign=%.3fs, verify=%.3fs" \ - % (curve, len(sig), keygen, sign, verf)) + print("%9s: siglen=%3d, keygen=%.4fs, %6.1f/s, " + "sign=%.4fs, %6.1f/s, verify=%.4fs, %6.1f/s" \ + % (curve, len(sig), keygen, 1.0/keygen, sign, 1.0/sign, + verf, 1.0/verf)) diff --git a/src/ecdsa/curves.py b/src/ecdsa/curves.py index b61f4b45..2d6877f1 100644 --- a/src/ecdsa/curves.py +++ b/src/ecdsa/curves.py @@ -2,6 +2,7 @@ from . import der, ecdsa from .util import orderlen +from .ellipticcurve import PointJacobi # orderlen was defined in this module previously, so keep it in __all__, @@ -30,22 +31,22 @@ def __init__(self, name, curve, generator, oid, openssl_name=None): self.encoded_oid = der.encode_oid(*oid) NIST192p = Curve("NIST192p", ecdsa.curve_192, - ecdsa.generator_192, + PointJacobi.from_weierstrass(ecdsa.generator_192, True), (1, 2, 840, 10045, 3, 1, 1), "prime192v1") NIST224p = Curve("NIST224p", ecdsa.curve_224, - ecdsa.generator_224, + PointJacobi.from_weierstrass(ecdsa.generator_224, True), (1, 3, 132, 0, 33), "secp224r1") NIST256p = Curve("NIST256p", ecdsa.curve_256, - ecdsa.generator_256, + PointJacobi.from_weierstrass(ecdsa.generator_256, True), (1, 2, 840, 10045, 3, 1, 7), "prime256v1") NIST384p = Curve("NIST384p", ecdsa.curve_384, - ecdsa.generator_384, + PointJacobi.from_weierstrass(ecdsa.generator_384, True), (1, 3, 132, 0, 34), "secp384r1") NIST521p = Curve("NIST521p", ecdsa.curve_521, - ecdsa.generator_521, + PointJacobi.from_weierstrass(ecdsa.generator_521, True), (1, 3, 132, 0, 35), "secp521r1") SECP256k1 = Curve("SECP256k1", ecdsa.curve_secp256k1, - ecdsa.generator_secp256k1, + PointJacobi.from_weierstrass(ecdsa.generator_secp256k1, True), (1, 3, 132, 0, 10), "secp256k1") curves = [NIST192p, NIST224p, NIST256p, NIST384p, NIST521p, SECP256k1] diff --git a/src/ecdsa/ecdsa.py b/src/ecdsa/ecdsa.py index 213c67d8..2add4366 100644 --- a/src/ecdsa/ecdsa.py +++ b/src/ecdsa/ecdsa.py @@ -63,6 +63,10 @@ class RSZeroError(RuntimeError): pass +class InvalidPointError(RuntimeError): + pass + + class Signature(object): """ECDSA signature. """ @@ -88,36 +92,46 @@ def recover_public_keys(self, hash, generator): y = beta if beta % 2 == 0 else curve.p() - beta # Compute the public key - R1 = ellipticcurve.Point(curve, x, y, n) + R1 = ellipticcurve.PointJacobi(curve, x, y, 1, n) Q1 = numbertheory.inverse_mod(r, n) * (s * R1 + (-e % n) * generator) Pk1 = Public_key(generator, Q1) # And the second solution - R2 = ellipticcurve.Point(curve, x, -y, n) + R2 = ellipticcurve.PointJacobi(curve, x, -y, 1, n) Q2 = numbertheory.inverse_mod(r, n) * (s * R2 + (-e % n) * generator) Pk2 = Public_key(generator, Q2) return [Pk1, Pk2] + class Public_key(object): """Public key for ECDSA. """ - def __init__(self, generator, point): - """generator is the Point that generates the group, - point is the Point that defines the public key. + def __init__(self, generator, point, verify=True): + """ + Low level ECDSA public key object. + + :param generator: the Point that generates the group + :param point: the Point that defines the public key + :param bool verify: if True check if point is valid point on curve + + :raises InvalidPointError: if the point parameters are invalid or + point does not lie on the curve """ self.curve = generator.curve() self.generator = generator self.point = point n = generator.order() - if not n: - raise RuntimeError("Generator point must have order.") - if not n * point == ellipticcurve.INFINITY: - raise RuntimeError("Generator point order is bad.") if point.x() < 0 or n <= point.x() or point.y() < 0 or n <= point.y(): - raise RuntimeError("Generator point has x or y out of range.") + raise InvalidPointError("Generator point has x or y out of range.") + if verify and not self.curve.contains_point(point.x(), point.y()): + raise InvalidPointError("Point does not lie on the curve") + if not n: + raise InvalidPointError("Generator point must have order.") + if verify and not n * point == ellipticcurve.INFINITY: + raise InvalidPointError("Generator point order is bad.") def verifies(self, hash, signature): """Verify that signature is a valid signature of hash. @@ -137,7 +151,10 @@ def verifies(self, hash, signature): c = numbertheory.inverse_mod(s, n) u1 = (hash * c) % n u2 = (r * c) % n - xy = u1 * G + u2 * self.point + if hasattr(G, "mul_add"): + xy = G.mul_add(u1, self.point, u2) + else: + xy = u1 * G + u2 * self.point v = xy.x() % n return v == r diff --git a/src/ecdsa/ellipticcurve.py b/src/ecdsa/ellipticcurve.py index e78314cc..00e2457b 100644 --- a/src/ecdsa/ellipticcurve.py +++ b/src/ecdsa/ellipticcurve.py @@ -1,4 +1,5 @@ #! /usr/bin/env python +# -*- coding: utf-8 -*- # # Implementation of elliptic curves, for cryptographic applications. # @@ -57,11 +58,371 @@ def b(self): def contains_point(self, x, y): """Is the point (x,y) on this curve?""" - return (y * y - (x * x * x + self.__a * x + self.__b)) % self.__p == 0 + return ((y**2) - (x**3 + self.__a * x + self.__b)) % self.__p == 0 def __str__(self): return "CurveFp(p=%d, a=%d, b=%d)" % (self.__p, self.__a, self.__b) + +class PointJacobi(object): + """ + Point on an elliptic curve. Uses Jacobi representation + + In Jacobian coordinates, there are three parameters, X, Y and Z. + They correspond to Weierstrass representation points 'x' and 'y' like so: + + x = X / Z² + y = Y / Z³ + """ + def __init__(self, curve, x, y, z, order=None, generator=False): + """ + :param bool generator: the point provided is a curve generator, as + such, it will be commonly used with scalar multiplication. This will + cause to precompute multiplication table for it + """ + self.__curve = curve + self.__x = x + self.__y = y + self.__z = z + self.__order = order + self.__precompute={} + if generator: + assert order + i = 1 + doubler = PointJacobi(curve, x, y, z, order) + order *= 2 + self.__precompute[i] = doubler + + while i < order: + i *= 2 + doubler = doubler.double().scale() + self.__precompute[i] = doubler + + def __eq__(self, other): + """Compare two points with each-other.""" + if (not self.__y or not self.__z) and other is INFINITY: + return True + if self.__y and self.__z and other is INFINITY: + return False + if isinstance(other, Point): + x2, y2, z2 = other.x(), other.y(), 1 + elif isinstance(other, PointJacobi): + x2, y2, z2 = other.__x, other.__y, other.__z + else: + return NotImplemented + x1, y1, z1 = self.__x, self.__y, self.__z + p = self.__curve.p() + + zz1 = z1 ** 2 % p + zz2 = z2 ** 2 % p + + # compare the fractions by bringing them to same denominator + # depend on short-circuit to save 4 multiplications in case of inequality + return (x1 * zz2 - x2 * zz1) % p == 0 and \ + (y1 * zz2 * z2 - y2 * zz1 * z1) % p == 0 + + def order(self): + return self.__order + + def curve(self): + return self.__curve + + def x(self): + """ + Return x coordinate in Weierstrass form. + + This method should be used only when the 'y' coordinate is not needed. + It's computationally more efficient to use `to_weierstrass()` and then + call x() and y() on the returned instance. + """ + if self.__z == 1: + return self.__x + p = self.__curve.p() + z = numbertheory.inverse_mod(self.__z, p) + return self.__x * z**2 % p + + def y(self): + """ + Return y coordinate in Weierstrass form. + + This method should be used only when the 'x' coordinate is not needed. + It's computationally more efficient to use `to_weierstrass()` and then + call x() and y() on the returned instance. + """ + if self.__z == 1: + return self.__y + p = self.__curve.p() + z = numbertheory.inverse_mod(self.__z, p) + return self.__y * z**3 % p + + def scale(self): + """ + Return point scaled so that z == 1. + + Modifies point in place, returns self. + """ + if self.__z == 1: + return self + p = self.__curve.p() + z_inv = numbertheory.inverse_mod(self.__z, p) + zz_inv = z_inv ** 2 % p + self.__x = self.__x * zz_inv % p + self.__y = self.__y * zz_inv * z_inv % p + self.__z = 1 + return self + + def to_weierstrass(self): + """Return point in Weierstrass form""" + if not self.__y or not self.__z: + return INFINITY + self.scale() + return Point(self.__curve, self.__x, + self.__y, self.__order) + + @staticmethod + def from_weierstrass(point, generator=False): + """Create from a Weierstrass form point""" + return PointJacobi(point.curve(), point.x(), point.y(), 1, + point.order(), generator) + + def double(self): + """Add a point to itself.""" + if not self.__y: + return INFINITY + + p = self.__curve.p() + a = self.__curve.a() + + X1, Y1, Z1 = self.__x, self.__y, self.__z + + # after: + # http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-2007-bl + + XX = X1**2%p + YY = Y1**2%p + YYYY = YY**2%p + ZZ = Z1**2%p + S = 2*((X1+YY)**2-XX-YYYY)%p + M = (3*XX+a*ZZ**2)%p + T = (M**2-2*S)%p + #X3 = T + Y3 = (M*(S-T)-8*YYYY)%p + Z3 = ((Y1+Z1)**2-YY-ZZ)%p + + if not Y3: + return INFINITY + + return PointJacobi(self.__curve, T, Y3, Z3, self.__order) + + def _add_with_z_1(self, X1, Y1, X2, Y2): + """add points when both Z1 and Z2 equal 1""" + # after: + # http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-mmadd-2007-bl + p = self.__curve.p() + H = X2-X1 + HH = H**2 + I = 4*HH % p + J = H*I + r = 2*(Y2-Y1) + if not H and not r: + return self.double() + V = X1*I + X3 = (r**2-J-2*V)%p + Y3 = (r*(V-X3)-2*Y1*J)%p + Z3 = 2*H%p + if not Y3: + return INFINITY + return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) + + def _add_with_z_eq(self, X1, Y1, Z1, X2, Y2): + """add points when Z1 == Z2""" + # after: + # http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-zadd-2007-m + p = self.__curve.p() + A = (X2-X1)**2%p + B = X1*A%p + C = X2*A + D = (Y2-Y1)**2 %p + if not A and not D: + return self.double() + X3 = (D-B-C)%p + Y3 = ((Y2-Y1)*(B-X3)-Y1*(C-B)) %p + Z3 = Z1*(X2-X1) %p + if not Y3: + return INFINITY + return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) + + def _add_with_z2_1(self, X1, Y1, Z1, X2, Y2): + """add points when Z2 == 1""" + # after: + # http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-madd-2007-bl + p = self.__curve.p() + Z1Z1 = Z1**2 % p + U2 = X2*Z1Z1 + S2 = Y2*Z1*Z1Z1 + H = U2-X1 + HH = H**2%p + I = 4*HH%p + J = H*I + r = 2*(S2-Y1) + if not r and not H: + return self.double() + V = X1*I + X3 = (r**2-J-2*V)%p + Y3 = (r*(V-X3)-2*Y1*J)%p + Z3 = ((Z1+H)**2-Z1Z1-HH)%p + if not Y3: + return INFINITY + return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) + + def __add__(self, other): + """Add two points on elliptic curve.""" + if self.__y == 0: + return other + if other is INFINITY: + return self + if isinstance(other, Point): + other = PointJacobi.from_weierstrass(other) + if self.__curve != other.__curve: + raise ValueError("The other point is on different curve") + + p = self.__curve.p() + X1, Y1, Z1 = self.__x, self.__y, self.__z + X2, Y2, Z2 = other.__x, other.__y, other.__z + if (X1, Y1, Z1) == (X2, Y2, Z2): + return self.double() + if Z1 == Z2: + if Z1 == 1: + return self._add_with_z_1(X1, Y1, X2, Y2) + return self._add_with_z_eq(X1, Y1, Z1, X2, Y2) + if Z1 == 1 or Z2 == 1: + if Z1 == 1: + return self._add_with_z2_1(X2, Y2, Z2, X1, Y1) + return self._add_with_z2_1(X1, Y1, Z1, X2, Y2) + + # after: + # http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-add-2007-bl + Z1Z1 = Z1**2 % p + Z2Z2 = Z2**2 % p + U1 = X1*Z2Z2 % p + U2 = X2*Z1Z1 % p + S1 = Y1*Z2*Z2Z2 %p + S2 = Y2*Z1*Z1Z1 % p + H = U2-U1 + I = (2*H)**2 % p + J = H*I % p + r = 2*(S2-S1) % p + if not H and not r: + return self.double() + V = U1*I + X3 = (r**2-J-2*V) % p + Y3 = (r*(V-X3)-2*S1*J) % p + Z3 = ((Z1+Z2)**2-Z1Z1-Z2Z2)*H % p + + if not Y3: + return INFINITY + + return PointJacobi(self.__curve, X3, Y3, Z3) + + def __rmul__(self, other): + """Multiply point by an integer.""" + return self * other + + def _mul_precompute(self, other): + i = 1 + result = INFINITY + while other: + if other % 2: + if other % 4 >= 2: + result = result + (-self.__precompute[i]) + other -= -1 + else: + result = result + self.__precompute[i] + other -= 1 + other = other // 2 + i *= 2 + return result + + def _naf(self, mult): + ret = [] + while mult: + if mult % 2: + nd = mult % 4 + if nd >= 2: + nd = nd - 4 + ret += [nd] + mult -= nd + else: + ret += [0] + mult //= 2 + return ret + + def __mul__(self, other): + """Multiply point by an integer.""" + if self.__y == 0 or other == 0: + return INFINITY + if other == 1: + return self + if self.__precompute: + return self._mul_precompute(other) + # makes vulnerable to Minerva + #if self.__order: + # other = other % self.__order + + self = self.scale() + result = INFINITY + # since adding points when at least one of them is scaled + # is quicker, reverse the NAF order + for i in reversed(self._naf(other)): + result = result.double() + if i < 0: + result = result + (-self) + elif i > 0: + result = result + self + return result + + @staticmethod + def _leftmost_bit(x): + assert x > 0 + result = 1 + while result <= x: + result = 2 * result + return result // 2 + + def mul_add(self, self_mul, other, other_mul): + """ + Do two multiplications at the same time, add results. + + calculates self*self_mul + other*other_mul + """ + if other is INFINITY or other_mul == 0: + return self * self_mul + if self_mul == 0: + return other * other_mul + if self.__precompute or other.__precompute: + return self * self_mul + other * other_mul + + i = self._leftmost_bit(max(self_mul, other_mul))*2 + result = INFINITY + self = self.scale() + other = other.scale() + both = (self + other).scale() + while i > 1: + result = result.double() + i = i // 2 + if self_mul & i and other_mul & i: + result = result + both + elif self_mul & i: + result = result + self + elif other_mul & i: + result = result + other + return result + + def __neg__(self): + return PointJacobi(self.__curve, self.__x, -self.__y, self.__z, + self.__order) + + class Point(object): """A point on an elliptic curve. Altering x and y is forbidding, but they can be read by the x() and y() methods.""" @@ -79,6 +440,8 @@ def __init__(self, curve, x, y, order=None): def __eq__(self, other): """Return True if the points are identical, False otherwise.""" + if not isinstance(other, Point): + return NotImplemented if self.__curve == other.__curve \ and self.__x == other.__x \ and self.__y == other.__y: @@ -98,6 +461,8 @@ def __add__(self, other): return self if self == INFINITY: return other + if not isinstance(other, Point): + return NotImplemented assert self.__curve == other.__curve if self.__x == other.__x: if (self.__y + other.__y) % self.__curve.p() == 0: diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index 4f926429..132b6654 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -126,7 +126,8 @@ def __init__(self, _error__please_use_generate=None): self.pubkey = None @classmethod - def from_public_point(cls, point, curve=NIST192p, hashfunc=sha1): + def from_public_point(cls, point, curve=NIST192p, hashfunc=sha1, + validate_point=True): """ Initialise the object from a Point object. @@ -141,19 +142,35 @@ def from_public_point(cls, point, curve=NIST192p, hashfunc=sha1): verification, needs to implement the same interface as hashlib.sha1 :type hashfunc: callable + :type bool validate_point: whether to check if the point lies on curve + should always be used if the public point is not a result + of our own calculation + + :raises MalformedPointError: if the public point does not lie on the + curve :return: Initialised VerifyingKey object :rtype: VerifyingKey """ self = cls(_error__please_use_generate=True) + if not isinstance(point, ellipticcurve.PointJacobi): + point = ellipticcurve.PointJacobi.from_weierstrass(point) self.curve = curve self.default_hashfunc = hashfunc - self.pubkey = ecdsa.Public_key(curve.generator, point) + try: + self.pubkey = ecdsa.Public_key(curve.generator, point, + validate_point) + except ecdsa.InvalidPointError: + raise MalformedPointError("Point does not lie on the curve") self.pubkey.order = curve.order return self + def precompute(self): + self.pubkey.point = ellipticcurve.PointJacobi.from_weierstrass( + self.pubkey.point, True) + @staticmethod - def _from_raw_encoding(string, curve, validate_point): + def _from_raw_encoding(string, curve): """ Decode public point from :term:`raw encoding`. @@ -171,13 +188,11 @@ def _from_raw_encoding(string, curve, validate_point): raise MalformedPointError("Unexpected length of encoded y") x = string_to_number(xs) y = string_to_number(ys) - if validate_point and not ecdsa.point_is_valid(curve.generator, x, y): - raise MalformedPointError("Point does not lie on the curve") - return ellipticcurve.Point(curve.curve, x, y, order) + return ellipticcurve.PointJacobi(curve.curve, x, y, 1, order) @staticmethod - def _from_compressed(string, curve, validate_point): + def _from_compressed(string, curve): """Decode public point from compressed encoding.""" if string[:1] not in (b('\x02'), b('\x03')): raise MalformedPointError("Malformed compressed point encoding") @@ -196,9 +211,7 @@ def _from_compressed(string, curve, validate_point): y = p - beta else: y = beta - if validate_point and not ecdsa.point_is_valid(curve.generator, x, y): - raise MalformedPointError("Point does not lie on curve") - return ellipticcurve.Point(curve.curve, x, y, order) + return ellipticcurve.PointJacobi(curve.curve, x, y, 1, order) @classmethod def _from_hybrid(cls, string, curve, validate_point): @@ -207,7 +220,7 @@ def _from_hybrid(cls, string, curve, validate_point): assert string[:1] in (b('\x06'), b('\x07')) # primarily use the uncompressed as it's easiest to handle - point = cls._from_raw_encoding(string[1:], curve, validate_point) + point = cls._from_raw_encoding(string[1:], curve) # but validate if it's self-consistent if we're asked to do that if validate_point and \ @@ -242,29 +255,32 @@ def from_string(cls, string, curve=NIST192p, hashfunc=sha1, provided curve or not, defaults to True :type validate_point: bool + :raises MalformedPointError: if the public point does not lie on the + curve or the encoding is invalid + :return: Initialised VerifyingKey object :rtype: VerifyingKey """ sig_len = len(string) if sig_len == curve.verifying_key_length: - point = cls._from_raw_encoding(string, curve, validate_point) + point = cls._from_raw_encoding(string, curve) elif sig_len == curve.verifying_key_length + 1: if string[:1] in (b('\x06'), b('\x07')): point = cls._from_hybrid(string, curve, validate_point) elif string[:1] == b('\x04'): - point = cls._from_raw_encoding(string[1:], curve, - validate_point) + point = cls._from_raw_encoding(string[1:], curve) else: raise MalformedPointError( "Invalid X9.62 encoding of the public point") elif sig_len == curve.baselen + 1: - point = cls._from_compressed(string, curve, validate_point) + point = cls._from_compressed(string, curve) else: raise MalformedPointError( "Length of string does not match lengths of " "any of the supported encodings of {0} " "curve.".format(curve.name)) - return cls.from_public_point(point, curve, hashfunc) + return cls.from_public_point(point, curve, hashfunc, + validate_point) @classmethod def from_pem(cls, string): @@ -682,10 +698,10 @@ def from_secret_exponent(cls, secexp, curve=NIST192p, hashfunc=sha1): "Invalid value for secexp, expected integer between 1 and {0}" .format(n)) pubkey_point = curve.generator * secexp - pubkey = ecdsa.Public_key(curve.generator, pubkey_point) - pubkey.order = n + pubkey_point = pubkey_point.scale() self.verifying_key = VerifyingKey.from_public_point(pubkey_point, curve, - hashfunc) + hashfunc, False) + pubkey = self.verifying_key.pubkey self.privkey = ecdsa.Private_key(pubkey, secexp) self.privkey.order = n return self diff --git a/src/ecdsa/numbertheory.py b/src/ecdsa/numbertheory.py index 27a9b0f1..1a34c8b2 100644 --- a/src/ecdsa/numbertheory.py +++ b/src/ecdsa/numbertheory.py @@ -207,27 +207,15 @@ def square_root_mod_prime(a, p): def inverse_mod(a, m): - """Inverse of a mod m.""" - - if a < 0 or m <= a: - a = a % m - - # From Ferguson and Schneier, roughly: - - c, d = a, m - uc, vc, ud, vd = 1, 0, 0, 1 - while c != 0: - q, c, d = divmod(d, c) + (c,) - uc, vc, ud, vd = ud - q * uc, vd - q * vc, uc, vc - - # At this point, d is the GCD, and ud*a+vd*m = d. - # If d == 1, this means that ud is a inverse. - - assert d == 1 - if ud > 0: - return ud - else: - return ud + m + """Inverse of a mod m.""" + if a == 0: + return 0 + lm, hm = 1, 0 + low, high = a % m, m + while low > 1: + r = high // low + lm, low, hm, high = hm - lm * r, high-low *r, lm, low + return lm % m def gcd2(a, b): diff --git a/src/ecdsa/test_jacobi.py b/src/ecdsa/test_jacobi.py new file mode 100644 index 00000000..b2290967 --- /dev/null +++ b/src/ecdsa/test_jacobi.py @@ -0,0 +1,132 @@ + +try: + import unittest2 as unittest +except ImportError: + import unittest + +from .ellipticcurve import Point, PointJacobi, INFINITY +from .ecdsa import generator_256, curve_256 + +class TestJacobi(unittest.TestCase): + def test_conversion(self): + pj = PointJacobi.from_weierstrass(generator_256) + pw = pj.to_weierstrass() + + self.assertEqual(generator_256, pw) + + def test_single_double(self): + pj = PointJacobi.from_weierstrass(generator_256) + pw = generator_256.double() + + pj = pj.double() + + self.assertEqual(pj.x(), pw.x()) + self.assertEqual(pj.y(), pj.y()) + + def test_multiply_by_one(self): + pj = PointJacobi.from_weierstrass(generator_256) + pw = generator_256 * 1 + + pj = pj * 1 + + self.assertEqual(pj.x(), pw.x()) + self.assertEqual(pj.y(), pj.y()) + + def test_multiply_by_two(self): + pj = PointJacobi.from_weierstrass(generator_256) + pw = generator_256 * 2 + + pj = pj * 2 + + self.assertEqual(pj.x(), pw.x()) + self.assertEqual(pj.y(), pj.y()) + + def test_compare_double_with_multiply(self): + pj = PointJacobi.from_weierstrass(generator_256) + dbl = pj.double() + mlpl = pj * 2 + + self.assertEqual(dbl, mlpl) + + def test_simple_multiplications(self): + for i in range(1, 256): + pj = PointJacobi.from_weierstrass(generator_256) + pw = generator_256 * i + + pj = pj * i + + self.assertEqual((pj.x(), pj.y()), (pw.x(), pw.y())) + self.assertEqual(pj, PointJacobi.from_weierstrass(pw)) + + def test_precompute(self): + precomp = PointJacobi.from_weierstrass(generator_256, True) + pj = PointJacobi.from_weierstrass(generator_256) + + + for i in range(255): + print(i) + a = precomp * (2 << i) + b = pj * (2 << i) + self.assertEqual(precomp * (2 << i), pj * (2 << i)) + + def test_add_point(self): + j_g = PointJacobi.from_weierstrass(generator_256) + + self.assertEqual(j_g.double(), j_g + j_g) + + def test_add_point_3_times(self): + j_g = PointJacobi.from_weierstrass(generator_256) + + self.assertEqual(j_g * 3, j_g + j_g + j_g) + + def test_mul_add_inf(self): + j_g = PointJacobi.from_weierstrass(generator_256) + + self.assertEqual(j_g, j_g.mul_add(1, INFINITY, 1)) + + def test_mul_add_same(self): + j_g = PointJacobi.from_weierstrass(generator_256) + + self.assertEqual(j_g * 2, j_g.mul_add(1, j_g, 1)) + + def test_mul_add_precompute(self): + j_g = PointJacobi.from_weierstrass(generator_256, True) + + b = PointJacobi.from_weierstrass(j_g * 255, True) + + self.assertEqual(j_g * 256, j_g + b) + + self.assertEqual(j_g * (5 + 255 * 7), j_g * 5 + b * 7) + self.assertEqual(j_g * (5 + 255 * 7), j_g.mul_add(5, b, 7)) + + def test_mul_add_precompute_large(self): + j_g = PointJacobi.from_weierstrass(generator_256, True) + + b = PointJacobi.from_weierstrass(j_g * 255, True) + + self.assertEqual(j_g * 256, j_g + b) + + self.assertEqual(j_g * (0xff00 + 255 * 0xf0f0), + j_g * 0xff00 + b * 0xf0f0) + self.assertEqual(j_g * (0xff00 + 255 * 0xf0f0), + j_g.mul_add(0xff00, b, 0xf0f0)) + + + def test_mul_add_to_mul(self): + j_g = PointJacobi.from_weierstrass(generator_256) + + a = j_g * 3 + b = j_g.mul_add(2, j_g, 1) + self.assertEqual(a, b) + + def test_mul_add(self): + j_g = PointJacobi.from_weierstrass(generator_256) + w_a = generator_256 * 255 + w_b = generator_256 * (0xa8*0xf0) + + j_b = j_g * 0xa8 + + ret = j_g.mul_add(255, j_b, 0xf0) + + self.assertEqual(ret.to_weierstrass(), w_a + w_b) +