diff --git a/.coveragerc b/.coveragerc index 5549ac31..64fdd3e7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -4,9 +4,5 @@ include = src/ecdsa/* omit = - src/ecdsa/six.py src/ecdsa/_version.py - src/ecdsa/test_ecdsa.py - src/ecdsa/test_ellipticcurve.py - src/ecdsa/test_numbertheory.py - src/ecdsa/test_pyecdsa.py + src/ecdsa/test_* diff --git a/README.md b/README.md index 23e9682d..36324362 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,8 @@ is to call `s=sk.to_string()`, and then re-create it with `SigningKey.from_string(s, curve)` . This short form does not record the curve, so you must be sure to tell from_string() the same curve you used for the original key. The short form of a NIST192p-based signing key is just 24 -bytes long. +bytes long. If a point encoding is invalid or it does not lie on the specified +curve, `from_string()` will raise MalformedPointError. ```python from ecdsa import SigningKey, NIST384p @@ -139,7 +140,8 @@ formats that OpenSSL uses. The PEM file looks like the familiar ASCII-armored is a shorter binary form of the same data. `SigningKey.from_pem()/.from_der()` will undo this serialization. These formats include the curve name, so you do not need to pass in a curve -identifier to the deserializer. +identifier to the deserializer. In case the file is malformed `from_der()` +and `from_pem()` will raise UnexpectedDER or MalformedPointError. ```python from ecdsa import SigningKey, NIST384p diff --git a/speed.py b/speed.py index 3f05309b..686ebf52 100644 --- a/speed.py +++ b/speed.py @@ -31,5 +31,7 @@ def do(setup_statements, statement): 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, %5.1f/s, " + "sign=%.4fs, %5.1f/s, verify=%.4fs, %5.1f/s" \ + % (curve, len(sig), keygen, 1.0/keygen, sign, 1.0/sign, + verf, 1.0/verf)) diff --git a/src/ecdsa/__init__.py b/src/ecdsa/__init__.py index d896bbc1..1075810c 100644 --- a/src/ecdsa/__init__.py +++ b/src/ecdsa/__init__.py @@ -1,5 +1,7 @@ -from .keys import SigningKey, VerifyingKey, BadSignatureError, BadDigestError +from .keys import SigningKey, VerifyingKey, BadSignatureError, BadDigestError,\ + MalformedPointError from .curves import NIST192p, NIST224p, NIST256p, NIST384p, NIST521p, SECP256k1 +from .der import UnexpectedDER # This code comes from http://github.com/warner/python-ecdsa from ._version import get_versions @@ -10,5 +12,6 @@ "test_pyecdsa", "util", "six"] _hush_pyflakes = [SigningKey, VerifyingKey, BadSignatureError, BadDigestError, + MalformedPointError, UnexpectedDER, NIST192p, NIST224p, NIST256p, NIST384p, NIST521p, SECP256k1] del _hush_pyflakes diff --git a/src/ecdsa/curves.py b/src/ecdsa/curves.py index 5ff53ad9..3bfc8a68 100644 --- a/src/ecdsa/curves.py +++ b/src/ecdsa/curves.py @@ -1,6 +1,7 @@ from __future__ import division from . import der, ecdsa +from .ellipticcurve import PointJacobi class UnknownCurveError(Exception): @@ -26,22 +27,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 a7b93614..4cb7e67a 100644 --- a/src/ecdsa/ecdsa.py +++ b/src/ecdsa/ecdsa.py @@ -87,12 +87,12 @@ 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) diff --git a/src/ecdsa/ellipticcurve.py b/src/ecdsa/ellipticcurve.py index f1751418..dcbc05f3 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. # @@ -36,6 +37,20 @@ from six import python_2_unicode_compatible from . import numbertheory +import binascii + + +def orderlen(order): + return (1+len("%x" % order))//2 # bytes + + +def number_to_string(num, order): + l = orderlen(order) + fmt_str = "%0" + str(2 * l) + "x" + string = binascii.unhexlify((fmt_str % num).encode()) + assert len(string) == l, (len(string), l) + return string + @python_2_unicode_compatible class CurveFp(object): @@ -57,11 +72,271 @@ 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: + point = PointJacobi(curve, x, y, z, order) + for i in range(1, 256): + self.__precompute[i] = (point * i).normalise() + + def __eq__(self, other): + """Compare two points with each-other.""" + if self.__y == 0 and other == INFINITY: + return True + if isinstance(other, Point): + return self.to_weierstrass() == other + return self.x() == other.x() and self.y() == other.y() + + 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. + """ + 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. + """ + p = self.__curve.p() + z = numbertheory.inverse_mod(self.__z, p) + return self.__y * z**3 % p + + def normalise(self): + """Return point scaled so that z == 1.""" + if self.__z == 1: + return self + return self.from_weierstrass(self.to_weierstrass()) + + def to_weierstrass(self): + """Return point in Weierstrass form""" + p = self.__curve.p() + z = numbertheory.inverse_mod(self.__z, p) + return Point(self.__curve, self.__x * z**2 % p, + self.__y * z**3 % p, 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 PointJacobi(self.__curve, 0, 0, 0) + + 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 + + return PointJacobi(self.__curve, X3, 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) + V = X1*I + X3 = (r**2-J-2*V)%p + Y3 = (r*(V-X3)-2*Y1*J)%p + Z3 = 2*H%p + if Y3 == 0 or Z3 == 0: + 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 + X3 = (D-B-C)%p + Y3 = ((Y2-Y1)*(B-X3)-Y1*(C-B)) %p + Z3 = Z1*(X2-X1) %p + if Y3 == 0 or Z3 == 0: + 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) + 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 Y3 == 0 or Z3 == 0: + return INFINITY + return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) + + def __add__(self, other): + """Add two points on elliptic curve.""" + if self == INFINITY: + return other + if other == 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 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 + 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 Y3 == 0 or Z3 == 0: + 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 = None + bit_mask = number_to_string(other, other) + bit_mask = bytearray(bit_mask) + result = self.__precompute[bit_mask[0]] + for b in bit_mask[1:]: + for _ in range(8): + result = result.double() + if b: + result = result + self.__precompute[b] + + return result + + def __mul__(self, other): + """Multiply point by an integer.""" + if self.__y == 0 or other == 0: + return PointJacobi(self.__curve, 0, 0, 1) + if other == 1: + return self + if self.__precompute: + return self._mul_precompute(other) + # makes vulnerable to Minerva + #if self.__order: + # other = other % self.__order + + def leftmost_bit(x): + assert x > 0 + result = 1 + while result <= x: + result = 2 * result + return result // 2 + + e = other + i = leftmost_bit(e) + self = self.normalise() + result = self + while i > 1: + result = result.double() + i = i // 2 + if e & i != 0: + result = result + self + return result + + 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 +354,8 @@ def __init__(self, curve, x, y, order=None): def __eq__(self, other): """Return True if the points are identical, False otherwise.""" + if isinstance(other, PointJacobi): + return self == other.to_weierstrass() if self.__curve == other.__curve \ and self.__x == other.__x \ and self.__y == other.__y: @@ -95,6 +372,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 9e289935..eb552245 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -39,6 +39,8 @@ def __init__(self, _error__please_use_generate=None): @classmethod def from_public_point(klass, point, curve=NIST192p, hashfunc=sha1): self = klass(_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) @@ -48,12 +50,14 @@ def from_public_point(klass, point, curve=NIST192p, hashfunc=sha1): @staticmethod def _from_raw_encoding(string, curve, validate_point): order = curve.order - assert (len(string) == curve.verifying_key_length), \ - (len(string), curve.verifying_key_length) + # real assert, from_string() should not call us with different length + assert len(string) == curve.verifying_key_length xs = string[:curve.baselen] ys = string[curve.baselen:] - assert len(xs) == curve.baselen, (len(xs), curve.baselen) - assert len(ys) == curve.baselen, (len(ys), curve.baselen) + if len(xs) != curve.baselen: + raise MalformedPointError("Unexpected length of encoded x") + if len(ys) != curve.baselen: + 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): @@ -86,6 +90,7 @@ def _from_compressed(string, curve, validate_point): @classmethod def _from_hybrid(cls, string, curve, validate_point): + # real assert, from_string() should not call us with different types assert string[:1] in (b('\x06'), b('\x07')) # primarily use the uncompressed as it's easiest to handle @@ -223,9 +228,6 @@ def to_pem(self): return der.topem(self.to_der(), "PUBLIC KEY") def to_der(self, point_encoding="uncompressed"): - order = self.pubkey.order - x_str = number_to_string(self.pubkey.point.x(), order) - y_str = number_to_string(self.pubkey.point.y(), order) point_str = b("\x00") + self.to_string(point_encoding) return der.encode_sequence(der.encode_sequence(encoded_oid_ecPublicKey, self.curve.encoded_oid), @@ -274,8 +276,12 @@ def from_secret_exponent(klass, secexp, curve=NIST192p, hashfunc=sha1): self.default_hashfunc = hashfunc self.baselen = curve.baselen n = curve.order - assert 1 <= secexp < n + if not 1 <= secexp < n: + raise MalformedPointError( + "Invalid value for secexp, expected integer between 1 and {0}" + .format(n)) pubkey_point = curve.generator * secexp + pubkey_point = pubkey_point.normalise() pubkey = ecdsa.Public_key(curve.generator, pubkey_point) pubkey.order = n self.verifying_key = VerifyingKey.from_public_point(pubkey_point, curve, @@ -286,7 +292,10 @@ def from_secret_exponent(klass, secexp, curve=NIST192p, hashfunc=sha1): @classmethod def from_string(klass, string, curve=NIST192p, hashfunc=sha1): - assert len(string) == curve.baselen, (len(string), curve.baselen) + if len(string) != curve.baselen: + raise MalformedPointError( + "Invalid length of private key, received {0}, expected {1}" + .format(len(string), curve.baselen)) secexp = string_to_number(string) return klass.from_secret_exponent(secexp, curve, hashfunc) diff --git a/src/ecdsa/numbertheory.py b/src/ecdsa/numbertheory.py index d7b906e3..1a34c8b2 100644 --- a/src/ecdsa/numbertheory.py +++ b/src/ecdsa/numbertheory.py @@ -19,6 +19,7 @@ xrange = range import math +import warnings class Error(Exception): @@ -206,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): @@ -326,8 +315,13 @@ def factorization(n): return result -def phi(n): +def phi(n): # pragma: no cover """Return the Euler totient function of n.""" + # deprecated in 0.14 + warnings.warn("Function is unused by library code. If you use this code, " + "please open an issue in " + "https://github.com/warner/python-ecdsa", + DeprecationWarning) assert isinstance(n, integer_types) @@ -345,20 +339,30 @@ def phi(n): return result -def carmichael(n): +def carmichael(n): # pragma: no cover """Return Carmichael function of n. Carmichael(n) is the smallest integer x such that m**x = 1 mod n for all m relatively prime to n. """ + # deprecated in 0.14 + warnings.warn("Function is unused by library code. If you use this code, " + "please open an issue in " + "https://github.com/warner/python-ecdsa", + DeprecationWarning) return carmichael_of_factorized(factorization(n)) -def carmichael_of_factorized(f_list): +def carmichael_of_factorized(f_list): # pragma: no cover """Return the Carmichael function of a number that is represented as a list of (prime,exponent) pairs. """ + # deprecated in 0.14 + warnings.warn("Function is unused by library code. If you use this code, " + "please open an issue in " + "https://github.com/warner/python-ecdsa", + DeprecationWarning) if len(f_list) < 1: return 1 @@ -370,9 +374,14 @@ def carmichael_of_factorized(f_list): return result -def carmichael_of_ppower(pp): +def carmichael_of_ppower(pp): # pragma: no cover """Carmichael function of the given power of the given prime. """ + # deprecated in 0.14 + warnings.warn("Function is unused by library code. If you use this code, " + "please open an issue in " + "https://github.com/warner/python-ecdsa", + DeprecationWarning) p, a = pp if p == 2 and a > 2: @@ -381,9 +390,14 @@ def carmichael_of_ppower(pp): return (p - 1) * p**(a - 1) -def order_mod(x, m): +def order_mod(x, m): # pragma: no cover """Return the order of x in the multiplicative group mod m. """ + # deprecated in 0.14 + warnings.warn("Function is unused by library code. If you use this code, " + "please open an issue in " + "https://github.com/warner/python-ecdsa", + DeprecationWarning) # Warning: this implementation is not very clever, and will # take a long time if m is very large. @@ -401,9 +415,14 @@ def order_mod(x, m): return result -def largest_factor_relatively_prime(a, b): +def largest_factor_relatively_prime(a, b): # pragma: no cover """Return the largest factor of a relatively prime to b. """ + # deprecated in 0.14 + warnings.warn("Function is unused by library code. If you use this code, " + "please open an issue in " + "https://github.com/warner/python-ecdsa", + DeprecationWarning) while 1: d = gcd(a, b) @@ -418,10 +437,15 @@ def largest_factor_relatively_prime(a, b): return a -def kinda_order_mod(x, m): +def kinda_order_mod(x, m): # pragma: no cover """Return the order of x in the multiplicative group mod m', where m' is the largest factor of m relatively prime to x. """ + # deprecated in 0.14 + warnings.warn("Function is unused by library code. If you use this code, " + "please open an issue in " + "https://github.com/warner/python-ecdsa", + DeprecationWarning) return order_mod(x, largest_factor_relatively_prime(m, x)) diff --git a/src/ecdsa/test_jacobi.py b/src/ecdsa/test_jacobi.py new file mode 100644 index 00000000..94a483c6 --- /dev/null +++ b/src/ecdsa/test_jacobi.py @@ -0,0 +1,70 @@ + +try: + import unittest2 as unittest +except ImportError: + import unittest + +from .ellipticcurve import Point, PointJacobi +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)) diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index bcca3ce8..36b989df 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -14,17 +14,19 @@ from six import b, print_, binary_type from .keys import SigningKey, VerifyingKey -from .keys import BadSignatureError, MalformedPointError +from .keys import BadSignatureError, MalformedPointError, BadDigestError from . import util from .util import sigencode_der, sigencode_strings from .util import sigdecode_der, sigdecode_strings -from .util import number_to_string +from .util import number_to_string, encoded_oid_ecPublicKey, \ + MalformedSignature from .curves import Curve, UnknownCurveError from .curves import NIST192p, NIST224p, NIST256p, NIST384p, NIST521p, \ SECP256k1, curves from .ellipticcurve import Point from . import der from . import rfc6979 +from . import ecdsa class SubprocessError(Exception): @@ -275,6 +277,47 @@ def order(self): pub2 = VerifyingKey.from_pem(pem) self.assertTruePubkeysEqual(pub1, pub2) + def test_vk_from_der_garbage_after_curve_oid(self): + type_oid_der = encoded_oid_ecPublicKey + curve_oid_der = der.encode_oid(*(1, 2, 840, 10045, 3, 1, 1)) + \ + b('garbage') + enc_type_der = der.encode_sequence(type_oid_der, curve_oid_der) + point_der = der.encode_bitstring(b'\x00\xff') + to_decode = der.encode_sequence(enc_type_der, point_der) + + with self.assertRaises(der.UnexpectedDER): + VerifyingKey.from_der(to_decode) + + def test_vk_from_der_invalid_key_type(self): + type_oid_der = der.encode_oid(*(1, 2, 3)) + curve_oid_der = der.encode_oid(*(1, 2, 840, 10045, 3, 1, 1)) + enc_type_der = der.encode_sequence(type_oid_der, curve_oid_der) + point_der = der.encode_bitstring(b'\x00\xff') + to_decode = der.encode_sequence(enc_type_der, point_der) + + with self.assertRaises(der.UnexpectedDER): + VerifyingKey.from_der(to_decode) + + def test_vk_from_der_garbage_after_point_string(self): + type_oid_der = encoded_oid_ecPublicKey + curve_oid_der = der.encode_oid(*(1, 2, 840, 10045, 3, 1, 1)) + enc_type_der = der.encode_sequence(type_oid_der, curve_oid_der) + point_der = der.encode_bitstring(b'\x00\xff') + b('garbage') + to_decode = der.encode_sequence(enc_type_der, point_der) + + with self.assertRaises(der.UnexpectedDER): + VerifyingKey.from_der(to_decode) + + def test_vk_from_der_invalid_bitstring(self): + type_oid_der = encoded_oid_ecPublicKey + curve_oid_der = der.encode_oid(*(1, 2, 840, 10045, 3, 1, 1)) + enc_type_der = der.encode_sequence(type_oid_der, curve_oid_der) + point_der = der.encode_bitstring(b'\x08\xff') + to_decode = der.encode_sequence(enc_type_der, point_der) + + with self.assertRaises(der.UnexpectedDER): + VerifyingKey.from_der(to_decode) + def test_signature_strings(self): priv1 = SigningKey.generate() pub1 = priv1.get_verifying_key() @@ -298,6 +341,86 @@ def test_signature_strings(self): self.assertEqual(type(sig_der), binary_type) self.assertTrue(pub1.verify(sig_der, data, sigdecode=sigdecode_der)) + def test_sig_decode_strings_with_invalid_count(self): + with self.assertRaises(MalformedSignature): + sigdecode_strings([b('one'), b('two'), b('three')], 0xff) + + def test_sig_decode_strings_with_wrong_r_len(self): + with self.assertRaises(MalformedSignature): + sigdecode_strings([b('one'), b('two')], 0xff) + + def test_sig_decode_strings_with_wrong_s_len(self): + with self.assertRaises(MalformedSignature): + sigdecode_strings([b('\xa0'), b('\xb0\xff')], 0xff) + + def test_verify_with_too_long_input(self): + sk = SigningKey.generate() + vk = sk.verifying_key + + with self.assertRaises(BadDigestError): + vk.verify_digest(None, b('\x00') * 128) + + def test_sk_from_secret_exponent_with_wrong_sec_exponent(self): + with self.assertRaises(MalformedPointError): + SigningKey.from_secret_exponent(0) + + def test_sk_from_string_with_wrong_len_string(self): + with self.assertRaises(MalformedPointError): + SigningKey.from_string(b('\x01')) + + def test_sk_from_der_with_junk_after_sequence(self): + ver_der = der.encode_integer(1) + to_decode = der.encode_sequence(ver_der) + b('garbage') + + with self.assertRaises(der.UnexpectedDER): + SigningKey.from_der(to_decode) + + def test_sk_from_der_with_wrong_version(self): + ver_der = der.encode_integer(0) + to_decode = der.encode_sequence(ver_der) + + with self.assertRaises(der.UnexpectedDER): + SigningKey.from_der(to_decode) + + def test_sk_from_der_invalid_const_tag(self): + ver_der = der.encode_integer(1) + privkey_der = der.encode_octet_string(b('\x00\xff')) + curve_oid_der = der.encode_oid(*(1, 2, 3)) + const_der = der.encode_constructed(1, curve_oid_der) + to_decode = der.encode_sequence(ver_der, privkey_der, const_der, + curve_oid_der) + + with self.assertRaises(der.UnexpectedDER): + SigningKey.from_der(to_decode) + + def test_sk_from_der_garbage_after_privkey_oid(self): + ver_der = der.encode_integer(1) + privkey_der = der.encode_octet_string(b('\x00\xff')) + curve_oid_der = der.encode_oid(*(1, 2, 3)) + b('garbage') + const_der = der.encode_constructed(0, curve_oid_der) + to_decode = der.encode_sequence(ver_der, privkey_der, const_der, + curve_oid_der) + + with self.assertRaises(der.UnexpectedDER): + SigningKey.from_der(to_decode) + + def test_sk_from_der_with_short_privkey(self): + ver_der = der.encode_integer(1) + privkey_der = der.encode_octet_string(b('\x00\xff')) + curve_oid_der = der.encode_oid(*(1, 2, 840, 10045, 3, 1, 1)) + const_der = der.encode_constructed(0, curve_oid_der) + to_decode = der.encode_sequence(ver_der, privkey_der, const_der, + curve_oid_der) + + sk = SigningKey.from_der(to_decode) + self.assertEqual(sk.privkey.secret_multiplier, 255) + + def test_sign_with_too_long_hash(self): + sk = SigningKey.from_secret_exponent(12) + + with self.assertRaises(BadDigestError): + sk.sign_digest(b('\xff') * 64) + def test_hashfunc(self): sk = SigningKey.generate(curve=NIST256p, hashfunc=sha256) data = b("security level is 128 bits") @@ -448,6 +571,49 @@ def test_not_lying_on_curve(self): with self.assertRaises(MalformedPointError): VerifyingKey.from_string(b('\x02') + enc) + def test_decoding_with_malformed_uncompressed(self): + enc = b('\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3' + '\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4' + 'z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*') + + with self.assertRaises(MalformedPointError): + VerifyingKey.from_string(b('\x02') + enc) + + def test_decoding_with_point_not_on_curve(self): + enc = b('\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3' + '\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4' + 'z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*') + + with self.assertRaises(MalformedPointError): + VerifyingKey.from_string(enc[:47] + b('\x00')) + + def test_decoding_with_point_at_infinity(self): + # decoding it is unsupported, as it's not necessary to encode it + with self.assertRaises(MalformedPointError): + VerifyingKey.from_string(b('\x00')) + + def test_from_string_with_invalid_curve_too_short_ver_key_len(self): + # both verifying_key_length and baselen are calculated internally + # by the Curve constructor, but since we depend on them verify + # that inconsistent values are detected + curve = Curve("test", ecdsa.curve_192, ecdsa.generator_192, (1, 2)) + curve.verifying_key_length = 16 + curve.baselen = 32 + + with self.assertRaises(MalformedPointError): + VerifyingKey.from_string(b('\x00')*16, curve) + + def test_from_string_with_invalid_curve_too_long_ver_key_len(self): + # both verifying_key_length and baselen are calculated internally + # by the Curve constructor, but since we depend on them verify + # that inconsistent values are detected + curve = Curve("test", ecdsa.curve_192, ecdsa.generator_192, (1, 2)) + curve.verifying_key_length = 16 + curve.baselen = 16 + + with self.assertRaises(MalformedPointError): + VerifyingKey.from_string(b('\x00')*16, curve) + @pytest.mark.parametrize("val,even", [(i, j) for i in range(256) for j in [True, False]])