diff --git a/.coveragerc b/.coveragerc index 64fdd3e7..57098f9a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -5,4 +5,3 @@ include = src/ecdsa/* omit = src/ecdsa/_version.py - src/ecdsa/test_* diff --git a/.travis.yml b/.travis.yml index 8d1f6a89..81fe86da 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,12 @@ dist: trusty sudo: false language: python cache: pip +addons: + apt_packages: + # needed for gmpy and gmpy2 + - libgmp-dev + - libmpfr-dev + - libmpc-dev before_cache: - rm -f $HOME/.cache/pip/log/debug.log # place the slowest (instrumental and py2.6) first @@ -15,6 +21,10 @@ matrix: env: TOX_ENV=py26 - python: 2.7 env: TOX_ENV=py27 + - python: 2.7 + env: TOX_ENV=gmpypy27 + - python: 2.7 + env: TOX_ENV=gmpy2py27 - python: 3.3 env: TOX_ENV=py33 - python: 3.4 @@ -31,6 +41,14 @@ matrix: env: TOX_ENV=py38 dist: xenial sudo: true + - python: 3.8 + env: TOX_ENV=gmpypy38 + dist: xenial + sudo: true + - python: 3.8 + env: TOX_ENV=gmpy2py38 + dist: xenial + sudo: true - python: nightly env: TOX_ENV=py dist: xenial @@ -73,11 +91,15 @@ install: else travis_retry pip install -r build-requirements.txt; fi + - if [[ $TOX_ENV =~ gmpy2 ]]; then travis_retry pip install gmpy2; fi + - if [[ $TOX_ENV =~ gmpyp ]]; then travis_retry pip install gmpy; fi - if [[ $INSTRUMENTAL ]]; then travis_retry pip install instrumental; fi - pip list script: - if [[ $TOX_ENV ]]; then tox -e $TOX_ENV; fi - - tox -e speed + - if [[ $TOX_ENV =~ gmpy2 ]]; then tox -e speedgmpy2; fi + - if [[ $TOX_ENV =~ gmpyp ]]; then tox -e speedgmpy; fi + - if ! [[ $TOX_ENV =~ gmpy ]]; then tox -e speed; fi - | if [[ $INSTRUMENTAL && $TRAVIS_PULL_REQUEST != "false" ]]; then git checkout $PR_FIRST^ diff --git a/README.md b/README.md index 551e02c7..1c1b9c16 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,38 @@ curves over prime fields. This library uses only Python and the 'six' package. It is compatible with Python 2.6, 2.7 and 3.3+. It also supports execution on the alternative -implementations like pypy and pypy3 +implementations like pypy and pypy3. + +If `gmpy2` or `gmpy` is installed, they will be used for faster arithmetic. +Either of them can be installed after this library is installed, +`python-ecdsa` will detect their presence on start-up and use them +automatically. To run the OpenSSL compatibility tests, the 'openssl' tool must be in your `PATH`. This release has been tested successfully against OpenSSL 0.9.8o, 1.0.0a, 1.0.2f and 1.1.1d (among others). + +## Installation + +This library is available on PyPI, it's recommended to install it using `pip`: + +``` +pip install ecdsa +``` + +In case higher performance is wanted and using native code is not a problem, +it's possible to specify installation together with `gmpy2`: + +``` +pip install ecdsa[gmpy2] +``` + +or (slower, legacy option): +``` +pip install ecdsa[gmpy] +``` + ## Speed The following table shows how long this library takes to generate keypairs @@ -53,24 +79,45 @@ On an Intel Core i7 4790K @ 4.0GHz I'm getting the following performance: ``` siglen keygen keygen/s sign sign/s verify verify/s - NIST192p: 48 0.01534s 65.18 0.00833s 120.05 0.01601s 62.48 - NIST224p: 56 0.02107s 47.46 0.01153s 86.74 0.02220s 45.05 - NIST256p: 64 0.02824s 35.40 0.01523s 65.66 0.02965s 33.73 - NIST384p: 96 0.06640s 15.06 0.03572s 27.99 0.06973s 14.34 - NIST521p: 132 0.13150s 7.60 0.07094s 14.10 0.13869s 7.21 - SECP256k1: 64 0.02807s 35.63 0.01525s 65.58 0.02964s 33.74 - BRAINPOOLP160r1: 40 0.01100s 90.88 0.00564s 177.45 0.01053s 94.92 - BRAINPOOLP192r1: 48 0.01633s 61.25 0.00833s 120.05 0.01591s 62.84 - BRAINPOOLP224r1: 56 0.02261s 44.23 0.01153s 86.76 0.02234s 44.77 - BRAINPOOLP256r1: 64 0.02997s 33.36 0.01532s 65.29 0.03084s 32.42 - BRAINPOOLP320r1: 80 0.04774s 20.95 0.02447s 40.86 0.04717s 21.20 - BRAINPOOLP384r1: 96 0.07007s 14.27 0.03566s 28.04 0.07056s 14.17 - BRAINPOOLP512r1: 128 0.13390s 7.47 0.06766s 14.78 0.13425s 7.45 + NIST192p: 48 0.00033s 3062.43 0.00036s 2748.08 0.00062s 1605.66 + NIST224p: 56 0.00041s 2424.07 0.00046s 2196.71 0.00083s 1205.81 + NIST256p: 64 0.00053s 1892.05 0.00058s 1735.23 0.00106s 944.82 + NIST384p: 96 0.00110s 904.98 0.00118s 847.82 0.00217s 460.26 + NIST521p: 132 0.00234s 428.24 0.00245s 408.54 0.00443s 225.95 + SECP256k1: 64 0.00053s 1891.93 0.00058s 1734.46 0.00109s 913.35 + BRAINPOOLP160r1: 40 0.00025s 3982.49 0.00029s 3490.15 0.00053s 1878.51 + BRAINPOOLP192r1: 48 0.00032s 3086.07 0.00036s 2761.68 0.00063s 1578.22 + BRAINPOOLP224r1: 56 0.00041s 2412.41 0.00046s 2185.52 0.00076s 1311.65 + BRAINPOOLP256r1: 64 0.00054s 1866.84 0.00058s 1719.45 0.00110s 906.85 + BRAINPOOLP320r1: 80 0.00077s 1306.97 0.00083s 1201.59 0.00158s 632.82 + BRAINPOOLP384r1: 96 0.00112s 892.44 0.00119s 841.48 0.00229s 436.71 + BRAINPOOLP512r1: 128 0.00214s 467.05 0.00226s 441.64 0.00422s 237.13 ``` +To test performance with `gmpy2` loaded, use `tox -e speedgmpy2`. +On the same machine I'm getting the following performance with `gmpy2`: +``` + siglen keygen keygen/s sign sign/s verify verify/s + NIST192p: 48 0.00016s 6180.12 0.00017s 5846.30 0.00033s 3029.51 + NIST224p: 56 0.00021s 4861.86 0.00021s 4662.63 0.00042s 2366.47 + NIST256p: 64 0.00023s 4343.93 0.00024s 4152.79 0.00047s 2148.83 + NIST384p: 96 0.00040s 2507.97 0.00041s 2435.99 0.00079s 1260.01 + NIST521p: 132 0.00088s 1135.13 0.00089s 1121.94 0.00139s 721.07 + SECP256k1: 64 0.00023s 4360.83 0.00024s 4147.61 0.00044s 2254.53 + BRAINPOOLP160r1: 40 0.00014s 7261.37 0.00015s 6824.47 0.00031s 3248.21 + BRAINPOOLP192r1: 48 0.00016s 6219.18 0.00017s 5862.93 0.00034s 2933.74 + BRAINPOOLP224r1: 56 0.00021s 4876.48 0.00022s 4640.40 0.00041s 2413.48 + BRAINPOOLP256r1: 64 0.00023s 4397.89 0.00024s 4178.48 0.00044s 2272.76 + BRAINPOOLP320r1: 80 0.00031s 3246.64 0.00032s 3138.38 0.00063s 1593.14 + BRAINPOOLP384r1: 96 0.00040s 2491.04 0.00041s 2421.67 0.00079s 1262.64 + BRAINPOOLP512r1: 128 0.00062s 1618.30 0.00063s 1577.42 0.00125s 799.29 +``` + +(there's also `gmpy` version, execute it using `tox -e speedgmpy`) For comparison, a highly optimised implementation (including curve-specific -assemply) like OpenSSL provides following performance numbers on the same -machine. Run `openssl speed` to reproduce it: +assemply for some curve), like the one in OpenSSL, provides following +performance numbers on the same machine. +Run `openssl speed ecdsa` to reproduce it: ``` sign verify sign/s verify/s 192 bits ecdsa (nistp192) 0.0002s 0.0002s 4785.6 5380.7 @@ -78,6 +125,9 @@ machine. Run `openssl speed` to reproduce it: 256 bits ecdsa (nistp256) 0.0000s 0.0001s 45069.6 14166.6 384 bits ecdsa (nistp384) 0.0008s 0.0006s 1265.6 1648.1 521 bits ecdsa (nistp521) 0.0003s 0.0005s 3753.1 1819.5 + 256 bits ecdsa (brainpoolP256r1) 0.0003s 0.0003s 2983.5 3333.2 + 384 bits ecdsa (brainpoolP384r1) 0.0008s 0.0007s 1258.8 1528.1 + 512 bits ecdsa (brainpoolP512r1) 0.0015s 0.0012s 675.1 860.1 ``` Keys and signature can be serialized in different ways (see Usage, below). @@ -94,7 +144,9 @@ following lengths (in bytes): In 2006, Peter Pearson announced his pure-python implementation of ECDSA in a [message to sci.crypt][1], available from his [download site][2]. In 2010, Brian Warner wrote a wrapper around this code, to make it a bit easier and -safer to use. You are looking at the README for this wrapper. +safer to use. Hubert Kario then included an implementation of elliptic curve +cryptography that uses Jacobian coordinates internally, improving performance +about 20-fold. You are looking at the README for this wrapper. [1]: http://www.derkeiler.com/Newsgroups/sci.crypt/2006-01/msg00651.html [2]: http://webpages.charter.net/curryfans/peter/downloads.html @@ -105,7 +157,7 @@ To run the full test suite, do this: tox -e coverage -On an Intel Core i7 4790K @ 4.0GHz, the tests take about 150 seconds to execute. +On an Intel Core i7 4790K @ 4.0GHz, the tests take about 16 seconds to execute. The test suite uses [`hypothesis`](https://github.com/HypothesisWorks/hypothesis) so there is some inherent variability in the test suite execution time. diff --git a/setup.py b/setup.py index c0959d44..1e35ac81 100755 --- a/setup.py +++ b/setup.py @@ -41,4 +41,8 @@ "Programming Language :: Python :: 3.8", ], install_requires=['six'], + extras_require={ + 'gmpy2': 'gmpy2', + 'gmpy': 'gmpy', + }, ) diff --git a/speed.py b/speed.py index d57f0dd4..41000425 100644 --- a/speed.py +++ b/speed.py @@ -30,14 +30,15 @@ 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")) diff --git a/src/ecdsa/curves.py b/src/ecdsa/curves.py index b10022f5..098a2911 100644 --- a/src/ecdsa/curves.py +++ b/src/ecdsa/curves.py @@ -63,36 +63,50 @@ def __repr__(self): SECP256k1 = Curve("SECP256k1", ecdsa.curve_secp256k1, ecdsa.generator_secp256k1, (1, 3, 132, 0, 10), "secp256k1") + + BRAINPOOLP160r1 = Curve("BRAINPOOLP160r1", ecdsa.curve_brainpoolp160r1, ecdsa.generator_brainpoolp160r1, (1, 3, 36, 3, 3, 2, 8, 1, 1, 1), "brainpoolP160r1") + + BRAINPOOLP192r1 = Curve("BRAINPOOLP192r1", ecdsa.curve_brainpoolp192r1, ecdsa.generator_brainpoolp192r1, (1, 3, 36, 3, 3, 2, 8, 1, 1, 3), "brainpoolP192r1") + + BRAINPOOLP224r1 = Curve("BRAINPOOLP224r1", ecdsa.curve_brainpoolp224r1, ecdsa.generator_brainpoolp224r1, (1, 3, 36, 3, 3, 2, 8, 1, 1, 5), "brainpoolP224r1") + + BRAINPOOLP256r1 = Curve("BRAINPOOLP256r1", ecdsa.curve_brainpoolp256r1, ecdsa.generator_brainpoolp256r1, (1, 3, 36, 3, 3, 2, 8, 1, 1, 7), "brainpoolP256r1") + + BRAINPOOLP320r1 = Curve("BRAINPOOLP320r1", ecdsa.curve_brainpoolp320r1, ecdsa.generator_brainpoolp320r1, (1, 3, 36, 3, 3, 2, 8, 1, 1, 9), "brainpoolP320r1") + + BRAINPOOLP384r1 = Curve("BRAINPOOLP384r1", ecdsa.curve_brainpoolp384r1, ecdsa.generator_brainpoolp384r1, (1, 3, 36, 3, 3, 2, 8, 1, 1, 11), "brainpoolP384r1") + + BRAINPOOLP512r1 = Curve("BRAINPOOLP512r1", ecdsa.curve_brainpoolp512r1, ecdsa.generator_brainpoolp512r1, diff --git a/src/ecdsa/ecdsa.py b/src/ecdsa/ecdsa.py index fae9609a..42c49b37 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,51 @@ 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 (the base point) + :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() + p = self.curve.p() + if not (0 <= point.x() < p) or not (0 <= point.y() < p): + raise InvalidPointError("The public 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 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 must have order.") + # for curve parameters with base point with cofactor 1, all points + # that are on the curve are scalar multiples of the base point, so + # verifying that is not necessary. See Section 3.2.2.1 of SEC 1 v2 + if verify and self.curve.cofactor() != 1 and \ + not n * point == ellipticcurve.INFINITY: + raise InvalidPointError("Generator point order is bad.") def __eq__(self, other): if isinstance(other, Public_key): @@ -144,7 +163,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 @@ -249,11 +271,14 @@ def point_is_valid(generator, x, y): n = generator.order() curve = generator.curve() - if x < 0 or n <= x or y < 0 or n <= y: + p = curve.p() + if not (0 <= x < p) or not (0 <= y < p): return False if not curve.contains_point(x, y): return False - if not n * ellipticcurve.Point(curve, x, y) == ellipticcurve.INFINITY: + if curve.cofactor() != 1 and \ + not n * ellipticcurve.PointJacobi(curve, x, y, 1)\ + == ellipticcurve.INFINITY: return False return True @@ -267,8 +292,9 @@ def point_is_valid(generator, x, y): _Gx = 0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012 _Gy = 0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811 -curve_192 = ellipticcurve.CurveFp(_p, -3, _b) -generator_192 = ellipticcurve.Point(curve_192, _Gx, _Gy, _r) +curve_192 = ellipticcurve.CurveFp(_p, -3, _b, 1) +generator_192 = ellipticcurve.PointJacobi( + curve_192, _Gx, _Gy, 1, _r, generator=True) # NIST Curve P-224: @@ -280,8 +306,9 @@ def point_is_valid(generator, x, y): _Gx = 0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21 _Gy = 0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34 -curve_224 = ellipticcurve.CurveFp(_p, -3, _b) -generator_224 = ellipticcurve.Point(curve_224, _Gx, _Gy, _r) +curve_224 = ellipticcurve.CurveFp(_p, -3, _b, 1) +generator_224 = ellipticcurve.PointJacobi( + curve_224, _Gx, _Gy, 1, _r, generator=True) # NIST Curve P-256: _p = 115792089210356248762697446949407573530086143415290314195533631308867097853951 @@ -292,8 +319,9 @@ def point_is_valid(generator, x, y): _Gx = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296 _Gy = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5 -curve_256 = ellipticcurve.CurveFp(_p, -3, _b) -generator_256 = ellipticcurve.Point(curve_256, _Gx, _Gy, _r) +curve_256 = ellipticcurve.CurveFp(_p, -3, _b, 1) +generator_256 = ellipticcurve.PointJacobi( + curve_256, _Gx, _Gy, 1, _r, generator=True) # NIST Curve P-384: _p = 39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319 @@ -304,8 +332,9 @@ def point_is_valid(generator, x, y): _Gx = 0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7 _Gy = 0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f -curve_384 = ellipticcurve.CurveFp(_p, -3, _b) -generator_384 = ellipticcurve.Point(curve_384, _Gx, _Gy, _r) +curve_384 = ellipticcurve.CurveFp(_p, -3, _b, 1) +generator_384 = ellipticcurve.PointJacobi( + curve_384, _Gx, _Gy, 1, _r, generator=True) # NIST Curve P-521: _p = 6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151 @@ -316,8 +345,9 @@ def point_is_valid(generator, x, y): _Gx = 0xc6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66 _Gy = 0x11839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650 -curve_521 = ellipticcurve.CurveFp(_p, -3, _b) -generator_521 = ellipticcurve.Point(curve_521, _Gx, _Gy, _r) +curve_521 = ellipticcurve.CurveFp(_p, -3, _b, 1) +generator_521 = ellipticcurve.PointJacobi( + curve_521, _Gx, _Gy, 1, _r, generator=True) # Certicom secp256-k1 _a = 0x0000000000000000000000000000000000000000000000000000000000000000 @@ -327,8 +357,9 @@ def point_is_valid(generator, x, y): _Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 _r = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 -curve_secp256k1 = ellipticcurve.CurveFp(_p, _a, _b) -generator_secp256k1 = ellipticcurve.Point(curve_secp256k1, _Gx, _Gy, _r) +curve_secp256k1 = ellipticcurve.CurveFp(_p, _a, _b, 1) +generator_secp256k1 = ellipticcurve.PointJacobi( + curve_secp256k1, _Gx, _Gy, 1, _r, generator=True) # Brainpool P-160-r1 _a = 0x340E7BE2A280EB74E2BE61BADA745D97E8F7C300 @@ -338,9 +369,9 @@ def point_is_valid(generator, x, y): _Gy = 0x1667CB477A1A8EC338F94741669C976316DA6321 _q = 0xE95E4A5F737059DC60DF5991D45029409E60FC09 -curve_brainpoolp160r1 = ellipticcurve.CurveFp(_p, _a, _b) -generator_brainpoolp160r1 = ellipticcurve.Point( - curve_brainpoolp160r1, _Gx, _Gy, _q) +curve_brainpoolp160r1 = ellipticcurve.CurveFp(_p, _a, _b, 1) +generator_brainpoolp160r1 = ellipticcurve.PointJacobi( + curve_brainpoolp160r1, _Gx, _Gy, 1, _q, generator=True) # Brainpool P-192-r1 _a = 0x6A91174076B1E0E19C39C031FE8685C1CAE040E5C69A28EF @@ -350,9 +381,9 @@ def point_is_valid(generator, x, y): _Gy = 0x14B690866ABD5BB88B5F4828C1490002E6773FA2FA299B8F _q = 0xC302F41D932A36CDA7A3462F9E9E916B5BE8F1029AC4ACC1 -curve_brainpoolp192r1 = ellipticcurve.CurveFp(_p, _a, _b) -generator_brainpoolp192r1 = ellipticcurve.Point( - curve_brainpoolp192r1, _Gx, _Gy, _q) +curve_brainpoolp192r1 = ellipticcurve.CurveFp(_p, _a, _b, 1) +generator_brainpoolp192r1 = ellipticcurve.PointJacobi( + curve_brainpoolp192r1, _Gx, _Gy, 1, _q, generator=True) # Brainpool P-224-r1 _a = 0x68A5E62CA9CE6C1C299803A6C1530B514E182AD8B0042A59CAD29F43 @@ -362,9 +393,9 @@ def point_is_valid(generator, x, y): _Gy = 0x58AA56F772C0726F24C6B89E4ECDAC24354B9E99CAA3F6D3761402CD _q = 0xD7C134AA264366862A18302575D0FB98D116BC4B6DDEBCA3A5A7939F -curve_brainpoolp224r1 = ellipticcurve.CurveFp(_p, _a, _b) -generator_brainpoolp224r1 = ellipticcurve.Point( - curve_brainpoolp224r1, _Gx, _Gy, _q) +curve_brainpoolp224r1 = ellipticcurve.CurveFp(_p, _a, _b, 1) +generator_brainpoolp224r1 = ellipticcurve.PointJacobi( + curve_brainpoolp224r1, _Gx, _Gy, 1, _q, generator=True) # Brainpool P-256-r1 _a = 0x7D5A0975FC2C3057EEF67530417AFFE7FB8055C126DC5C6CE94A4B44F330B5D9 @@ -374,9 +405,9 @@ def point_is_valid(generator, x, y): _Gy = 0x547EF835C3DAC4FD97F8461A14611DC9C27745132DED8E545C1D54C72F046997 _q = 0xA9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7 -curve_brainpoolp256r1 = ellipticcurve.CurveFp(_p, _a, _b) -generator_brainpoolp256r1 = ellipticcurve.Point( - curve_brainpoolp256r1, _Gx, _Gy, _q) +curve_brainpoolp256r1 = ellipticcurve.CurveFp(_p, _a, _b, 1) +generator_brainpoolp256r1 = ellipticcurve.PointJacobi( + curve_brainpoolp256r1, _Gx, _Gy, 1, _q, generator=True) # Brainpool P-320-r1 _a = 0x3EE30B568FBAB0F883CCEBD46D3F3BB8A2A73513F5EB79DA66190EB085FFA9F492F375A97D860EB4 @@ -386,9 +417,9 @@ def point_is_valid(generator, x, y): _Gy = 0x14FDD05545EC1CC8AB4093247F77275E0743FFED117182EAA9C77877AAAC6AC7D35245D1692E8EE1 _q = 0xD35E472036BC4FB7E13C785ED201E065F98FCFA5B68F12A32D482EC7EE8658E98691555B44C59311 -curve_brainpoolp320r1 = ellipticcurve.CurveFp(_p, _a, _b) -generator_brainpoolp320r1 = ellipticcurve.Point( - curve_brainpoolp320r1, _Gx, _Gy, _q) +curve_brainpoolp320r1 = ellipticcurve.CurveFp(_p, _a, _b, 1) +generator_brainpoolp320r1 = ellipticcurve.PointJacobi( + curve_brainpoolp320r1, _Gx, _Gy, 1, _q, generator=True) # Brainpool P-384-r1 _a = 0x7BC382C63D8C150C3C72080ACE05AFA0C2BEA28E4FB22787139165EFBA91F90F8AA5814A503AD4EB04A8C7DD22CE2826 @@ -398,9 +429,9 @@ def point_is_valid(generator, x, y): _Gy = 0x8ABE1D7520F9C2A45CB1EB8E95CFD55262B70B29FEEC5864E19C054FF99129280E4646217791811142820341263C5315 _q = 0x8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC3103B883202E9046565 -curve_brainpoolp384r1 = ellipticcurve.CurveFp(_p, _a, _b) -generator_brainpoolp384r1 = ellipticcurve.Point( - curve_brainpoolp384r1, _Gx, _Gy, _q) +curve_brainpoolp384r1 = ellipticcurve.CurveFp(_p, _a, _b, 1) +generator_brainpoolp384r1 = ellipticcurve.PointJacobi( + curve_brainpoolp384r1, _Gx, _Gy, 1, _q, generator=True) # Brainpool P-512-r1 _a = 0x7830A3318B603B89E2327145AC234CC594CBDD8D3DF91610A83441CAEA9863BC2DED5D5AA8253AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C1AC4D77FC94CA @@ -410,6 +441,6 @@ def point_is_valid(generator, x, y): _Gy = 0x7DDE385D566332ECC0EABFA9CF7822FDF209F70024A57B1AA000C55B881F8111B2DCDE494A5F485E5BCA4BD88A2763AED1CA2B2FA8F0540678CD1E0F3AD80892 _q = 0xAADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA92619418661197FAC10471DB1D381085DDADDB58796829CA90069 -curve_brainpoolp512r1 = ellipticcurve.CurveFp(_p, _a, _b) -generator_brainpoolp512r1 = ellipticcurve.Point( - curve_brainpoolp512r1, _Gx, _Gy, _q) +curve_brainpoolp512r1 = ellipticcurve.CurveFp(_p, _a, _b, 1) +generator_brainpoolp512r1 = ellipticcurve.PointJacobi( + curve_brainpoolp512r1, _Gx, _Gy, 1, _q, generator=True) diff --git a/src/ecdsa/ellipticcurve.py b/src/ecdsa/ellipticcurve.py index 9d5e6530..cf810a22 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. # @@ -34,17 +35,55 @@ from __future__ import division +try: + from gmpy2 import mpz + GMPY=True +except ImportError: + try: + from gmpy import mpz + GMPY=True + except ImportError: + GMPY=False + + from six import python_2_unicode_compatible from . import numbertheory + @python_2_unicode_compatible class CurveFp(object): """Elliptic Curve over the field of integers modulo a prime.""" - def __init__(self, p, a, b): - """The curve of points satisfying y^2 = x^3 + a*x + b (mod p).""" - self.__p = p - self.__a = a - self.__b = b + + if GMPY: + def __init__(self, p, a, b, h=None): + """ + The curve of points satisfying y^2 = x^3 + a*x + b (mod p). + + h is an integer that is the cofactor of the elliptic curve domain + parameters; it is the number of points satisfying the elliptic curve + equation divided by the order of the base point. It is used for selection + of efficient algorithm for public point verification. + """ + self.__p = mpz(p) + self.__a = mpz(a) + self.__b = mpz(b) + # h is not used in calculations and it can be None, so don't use + # gmpy with it + self.__h = h + else: + def __init__(self, p, a, b, h=None): + """ + The curve of points satisfying y^2 = x^3 + a*x + b (mod p). + + h is an integer that is the cofactor of the elliptic curve domain + parameters; it is the number of points satisfying the elliptic curve + equation divided by the order of the base point. It is used for selection + of efficient algorithm for public point verification. + """ + self.__p = p + self.__a = a + self.__b = b + self.__h = h def __eq__(self, other): if isinstance(other, CurveFp): @@ -63,12 +102,460 @@ def a(self): def b(self): return self.__b + def cofactor(self): + return self.__h + 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 * y - ((x * x + 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) + return "CurveFp(p=%d, a=%d, b=%d, h=%d)" % ( + self.__p, self.__a, self.__b, self.__h) + + +class PointJacobi(object): + """ + Point on an elliptic curve. Uses Jacobi coordinates. + + In Jacobian coordinates, there are three parameters, X, Y and Z. + They correspond to affine parameters 'x' and 'y' like so: + + x = X / Z² + y = Y / Z³ + """ + def __init__(self, curve, x, y, z, order=None, generator=False): + """ + Initialise a point that uses Jacobi representation internally. + + :param CurveFp curve: curve on which the point resides + :param int x: the X parameter of Jacobi representation (equal to x when + converting from affine coordinates + :param int y: the Y parameter of Jacobi representation (equal to y when + converting from affine coordinates + :param int z: the Z parameter of Jacobi representation (equal to 1 when + converting from affine coordinates + :param int order: the point order, must be non zero when using + generator=True + :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 + if GMPY: + self.__x = mpz(x) + self.__y = mpz(y) + self.__z = mpz(z) + self.__order = order and mpz(order) + else: + self.__x = x + self.__y = y + self.__z = z + self.__order = order + self.__precompute=[] + if generator: + assert order + i = 1 + order *= 2 + doubler = PointJacobi(curve, x, y, z, order) + order *= 2 + self.__precompute.append((doubler.x(), doubler.y())) + + while i < order: + i *= 2 + doubler = doubler.double().scale() + self.__precompute.append((doubler.x(), doubler.y())) + + 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 + if self.__curve != other.curve(): + return False + x1, y1, z1 = self.__x, self.__y, self.__z + p = self.__curve.p() + + zz1 = z1 * z1 % p + zz2 = z2 * z2 % p + + # compare the fractions by bringing them to the 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 the order of the point. + + None if it is undefined. + """ + return self.__order + + def curve(self): + """Return curve over which the point is defined.""" + return self.__curve + + def x(self): + """ + Return affine x coordinate. + + This method should be used only when the 'y' coordinate is not needed. + It's computationally more efficient to use `to_affine()` and then + call x() and y() on the returned instance. Or call `scale()` + and then 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 affine y coordinate. + + This method should be used only when the 'x' coordinate is not needed. + It's computationally more efficient to use `to_affine()` and then + call x() and y() on the returned instance. Or call `scale()` + and then 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. + """ + p = self.__curve.p() + z_inv = numbertheory.inverse_mod(self.__z, p) + zz_inv = z_inv * z_inv % p + self.__x = self.__x * zz_inv % p + self.__y = self.__y * zz_inv * z_inv % p + self.__z = 1 + return self + + def to_affine(self): + """Return point in affine 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_affine(point, generator=False): + """Create from an affine point. + + :param bool generator: set to True to make the point to precalculate + multiplication table - useful for public point when verifying many + signatures (around 100 or so) or for generator points of a curve. + """ + return PointJacobi(point.curve(), point.x(), point.y(), 1, + point.order(), generator) + + def _double_with_z_1(self, X1, Y1, p, a): + """Add a point to itself with z == 1.""" + # after: + # http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-mdbl-2007-bl + XX = X1 * X1 % p + YY = Y1 * Y1 % p + if not YY: + return 0, 0, 1 + YYYY = YY * YY % p + S = 2*((X1+YY)**2-XX-YYYY) % p + M = 3*XX+a + T = (M*M-2*S) % p + #X3 = T + Y3 = (M*(S-T)-8*YYYY) % p + Z3 = 2*Y1 % p + return T, Y3, Z3 + + def _double(self, X1, Y1, Z1, p, a): + """Add a point to itself, arbitrary z.""" + if Z1 == 1: + return self._double_with_z_1(X1, Y1, p, a) + # after: + # http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-2007-bl + XX = X1*X1%p + YY = Y1*Y1%p + if not YY: + return 0, 0, 1 + YYYY = YY*YY%p + ZZ = Z1*Z1%p + S = 2*((X1+YY)**2-XX-YYYY)%p + M = (3*XX+a*ZZ*ZZ)%p + T = (M*M-2*S)%p + #X3 = T + Y3 = (M*(S-T)-8*YYYY)%p + Z3 = ((Y1+Z1)**2-YY-ZZ)%p + + return T, Y3, Z3 + + 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 + + X3, Y3, Z3 = self._double(X1, Y1, Z1, p, a) + + if not Y3: + return INFINITY + return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) + + def _add_with_z_1(self, X1, Y1, X2, Y2, p): + """add points when both Z1 and Z2 equal 1""" + # after: + # http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-mmadd-2007-bl + H = X2-X1 + HH = H*H + I = 4*HH % p + J = H*I + r = 2*(Y2-Y1) + if not H and not r: + return self._double_with_z_1(X1, Y1, p, self.__curve.a()) + V = X1*I + X3 = (r**2-J-2*V)%p + Y3 = (r*(V-X3)-2*Y1*J)%p + Z3 = 2*H%p + return X3, Y3, Z3 + + def _add_with_z_eq(self, X1, Y1, Z1, X2, Y2, p): + """add points when Z1 == Z2""" + # after: + # http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-zadd-2007-m + 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(X1, Y1, Z1, p, self.__curve.a()) + X3 = (D-B-C)%p + Y3 = ((Y2-Y1)*(B-X3)-Y1*(C-B)) %p + Z3 = Z1*(X2-X1) %p + return X3, Y3, Z3 + + def _add_with_z2_1(self, X1, Y1, Z1, X2, Y2, p): + """add points when Z2 == 1""" + # after: + # http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-madd-2007-bl + Z1Z1 = Z1*Z1 % p + U2, S2 = X2*Z1Z1%p, Y2*Z1*Z1Z1%p + H = U2-X1%p + HH = H*H%p + I = 4*HH%p + J = H*I + r = 2*(S2-Y1)%p + if not r and not H: + return self._double_with_z_1(X2, Y2, p, self.__curve.a()) + V = X1*I + X3 = (r*r-J-2*V)%p + Y3 = (r*(V-X3)-2*Y1*J)%p + Z3 = ((Z1+H)**2-Z1Z1-HH)%p + return X3, Y3, Z3 + + def _add_with_z_ne(self, X1, Y1, Z1, X2, Y2, Z2, p): + """add points with arbitrary z""" + # after: + # http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-add-2007-bl + Z1Z1 = Z1*Z1 % p + Z2Z2 = Z2*Z2 % p + U1 = X1*Z2Z2 % p + U2 = X2*Z1Z1 % p + S1 = Y1*Z2*Z2Z2 %p + S2 = Y2*Z1*Z1Z1 % p + H = U2-U1 + I = 4*H*H % p + J = H*I % p + r = 2*(S2-S1) % p + if not H and not r: + return self._double(X1, Y1, Z1, p, self.__curve.a()) + V = U1*I + X3 = (r*r-J-2*V) % p + Y3 = (r*(V-X3)-2*S1*J) % p + Z3 = ((Z1+Z2)**2-Z1Z1-Z2Z2)*H % p + + return X3, Y3, Z3 + + def __radd__(self, other): + """Add other to self.""" + return self + other + + def _add(self, X1, Y1, Z1, X2, Y2, Z2, p): + """add two points, select fastest method.""" + if not Y1: + return X2, Y2, Z2 + if not Y2: + return X1, Y1, Z1 + if Z1 == Z2: + if Z1 == 1: + return self._add_with_z_1(X1, Y1, X2, Y2, p) + return self._add_with_z_eq(X1, Y1, Z1, X2, Y2, p) + if Z1 == 1: + return self._add_with_z2_1(X2, Y2, Z2, X1, Y1, p) + if Z2 == 1: + return self._add_with_z2_1(X1, Y1, Z1, X2, Y2, p) + return self._add_with_z_ne(X1, Y1, Z1, X2, Y2, Z2, p) + + 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_affine(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 + X3, Y3, Z3 = self._add(X1, Y1, Z1, X2, Y2, Z2, p) + + if not Y3 or not Z3: + return INFINITY + return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) + + def __rmul__(self, other): + """Multiply point by an integer.""" + return self * other + + def _mul_precompute(self, other): + """Multiply point by integer with precomputation table.""" + X3, Y3, Z3, p = 0, 0, 1, self.__curve.p() + _add = self._add + for X2, Y2 in self.__precompute: + if other % 2: + if other % 4 >= 2: + other = (other + 1)//2 + X3, Y3, Z3 = _add(X3, Y3, Z3, X2, -Y2, 1, p) + else: + other = (other - 1)//2 + X3, Y3, Z3 = _add(X3, Y3, Z3, X2, Y2, 1, p) + else: + other //= 2 + + if not Y3 or not Z3: + return INFINITY + return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) + + @staticmethod + def _naf(mult): + """Calculate non-adjacent form of number.""" + 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 not self.__y or not other: + return INFINITY + if other == 1: + return self + if self.__order: + # order*2 as a protection for Minerva + other = other % (self.__order*2) + if self.__precompute: + return self._mul_precompute(other) + + self = self.scale() + X2, Y2 = self.__x, self.__y + X3, Y3, Z3 = 0, 0, 1 + p, a = self.__curve.p(), self.__curve.a() + _double = self._double + _add = self._add + # since adding points when at least one of them is scaled + # is quicker, reverse the NAF order + for i in reversed(self._naf(other)): + X3, Y3, Z3 = _double(X3, Y3, Z3, p, a) + if i < 0: + X3, Y3, Z3 = _add(X3, Y3, Z3, X2, -Y2, 1, p) + elif i > 0: + X3, Y3, Z3 = _add(X3, Y3, Z3, X2, Y2, 1, p) + + if not Y3 or not Z3: + return INFINITY + + return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) + + @staticmethod + def _leftmost_bit(x): + """Return integer with the same magnitude as x but hamming weight of 1""" + 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 + X3, Y3, Z3 = 0, 0, 1 + p, a = self.__curve.p(), self.__curve.a() + self = self.scale() + X1, Y1 = self.__x, self.__y + other = other.scale() + X2, Y2 = other.__x, other.__y + both = (self + other).scale() + X4, Y4 = both.__x, both.__y + _double = self._double + _add = self._add + while i > 1: + X3, Y3, Z3 = _double(X3, Y3, Z3, p, a) + i = i // 2 + if self_mul & i and other_mul & i: + X3, Y3, Z3 = _add(X3, Y3, Z3, X4, Y4, 1, p) + elif self_mul & i: + X3, Y3, Z3 = _add(X3, Y3, Z3, X1, Y1, 1, p) + elif other_mul & i: + X3, Y3, Z3 = _add(X3, Y3, Z3, X2, Y2, 1, p) + + if not Y3 or not Z3: + return INFINITY + + return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) + + def __neg__(self): + """Return negated point.""" + 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, @@ -76,13 +563,21 @@ class Point(object): def __init__(self, curve, x, y, order=None): """curve, x, y, order; order (optional) is the order of this point.""" self.__curve = curve - self.__x = x - self.__y = y - self.__order = order + if GMPY: + self.__x = x and mpz(x) + self.__y = y and mpz(y) + self.__order = order and mpz(order) + else: + self.__x = x + self.__y = y + self.__order = order # self.curve is allowed to be None only for INFINITY: if self.__curve: assert self.__curve.contains_point(x, y) - if order: + # for curves with cofactor 1, all points that are on the curve are scalar + # multiples of the base point, so performing multiplication is not + # necessary to verify that. See Section 3.2.2.1 of SEC 1 v2 + if curve and curve.cofactor() != 1 and order: assert self * order == INFINITY def __eq__(self, other): @@ -101,6 +596,8 @@ def __add__(self, other): # X9.62 B.3: + if not isinstance(other, Point): + return NotImplemented if other == INFINITY: return self if self == INFINITY: diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index 7dd2c111..bc8cf161 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -148,7 +148,8 @@ def __eq__(self, other): return NotImplemented @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. @@ -163,19 +164,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_affine(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_affine( + 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`. @@ -193,13 +210,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") @@ -218,9 +233,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): @@ -229,7 +242,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 \ @@ -264,30 +277,33 @@ 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 """ string = normalise_bytes(string) 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): @@ -723,9 +739,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 - self.verifying_key = VerifyingKey.from_public_point(pubkey_point, - curve, - hashfunc) + if hasattr(pubkey_point, "scale"): + pubkey_point = pubkey_point.scale() + self.verifying_key = VerifyingKey.from_public_point(pubkey_point, curve, + hashfunc, False) pubkey = self.verifying_key.pubkey self.privkey = ecdsa.Private_key(pubkey, secexp) self.privkey.order = n diff --git a/src/ecdsa/numbertheory.py b/src/ecdsa/numbertheory.py index a84da53b..88f7cbbc 100644 --- a/src/ecdsa/numbertheory.py +++ b/src/ecdsa/numbertheory.py @@ -17,6 +17,17 @@ xrange except NameError: xrange = range +try: + from gmpy2 import powmod + GMPY2=True + GMPY=False +except ImportError: + GMPY2=False + try: + from gmpy import mpz + GMPY=True + except ImportError: + GMPY=False import math import warnings @@ -201,19 +212,45 @@ def square_root_mod_prime(a, p): raise RuntimeError("No b found.") -def inverse_mod(a, 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 +if GMPY2: + def inverse_mod(a, m): + """Inverse of a mod m.""" + if a == 0: + return 0 + return powmod(a, -1, m) +elif GMPY: + def inverse_mod(a, m): + """Inverse of a mod m.""" + # while libgmp likely does support inverses modulo, it is accessible + # only using the native `pow()` function, and `pow()` sanity checks + # the parameters before passing them on to underlying implementation + # on Python2 + if a == 0: + return 0 + a = mpz(a) + m = mpz(m) + + lm, hm = mpz(1), mpz(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 +else: + def inverse_mod(a, 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 try: diff --git a/src/ecdsa/test_ecdsa.py b/src/ecdsa/test_ecdsa.py index 200cacef..6fa587a9 100644 --- a/src/ecdsa/test_ecdsa.py +++ b/src/ecdsa/test_ecdsa.py @@ -15,7 +15,7 @@ HYP_SETTINGS = {} # old hypothesis doesn't have the "deadline" setting -if sys.version_info > (2, 7): +if sys.version_info > (2, 7): # pragma: no branch # SEC521p is slow, allow long execution for it HYP_SETTINGS["deadline"] = 5000 @@ -417,7 +417,7 @@ def st_random_gen_key_msg_nonce(draw): name = draw(st.sampled_from(sorted(name_gen.keys()))) note("Generator used: {0}".format(name)) generator = name_gen[name] - order = generator.order() + order = int(generator.order()) key = draw(st.integers(min_value=1, max_value=order)) msg = draw(st.integers(min_value=1, max_value=order)) diff --git a/src/ecdsa/test_ellipticcurve.py b/src/ecdsa/test_ellipticcurve.py index f0b35a86..6929b74d 100644 --- a/src/ecdsa/test_ellipticcurve.py +++ b/src/ecdsa/test_ellipticcurve.py @@ -9,14 +9,14 @@ try: from hypothesis import HealthCheck HC_PRESENT=True -except ImportError: +except ImportError: # pragma: no cover HC_PRESENT=False from .numbertheory import inverse_mod from .ellipticcurve import CurveFp, INFINITY, Point HYP_SETTINGS={} -if HC_PRESENT: +if HC_PRESENT: # pragma: no branch HYP_SETTINGS['suppress_health_check']=[HealthCheck.too_slow] HYP_SETTINGS['deadline'] = 5000 @@ -37,7 +37,11 @@ g_23 = Point(c_23, 13, 7, 7) -@settings(**HYP_SETTINGS) +HYP_SLOW_SETTINGS=dict(HYP_SETTINGS) +HYP_SLOW_SETTINGS["max_examples"]=10 + + +@settings(**HYP_SLOW_SETTINGS) @given(st.integers(min_value=1, max_value=r+1)) def test_p192_mult_tests(multiple): inv_m = inverse_mod(multiple, r) diff --git a/src/ecdsa/test_jacobi.py b/src/ecdsa/test_jacobi.py new file mode 100644 index 00000000..67dc9e1c --- /dev/null +++ b/src/ecdsa/test_jacobi.py @@ -0,0 +1,365 @@ + +try: + import unittest2 as unittest +except ImportError: + import unittest + +import hypothesis.strategies as st +from hypothesis import given, assume, settings, example + +from .ellipticcurve import Point, PointJacobi, INFINITY +from .ecdsa import generator_256, curve_256, generator_224 +from .numbertheory import inverse_mod + +class TestJacobi(unittest.TestCase): + def test___init__(self): + curve = object() + x = 2 + y = 3 + z = 1 + order = 4 + pj = PointJacobi(curve, x, y, z, order) + + self.assertEqual(pj.order(), order) + self.assertIs(pj.curve(), curve) + self.assertEqual(pj.x(), x) + self.assertEqual(pj.y(), y) + + def test_add_with_different_curves(self): + p_a = PointJacobi.from_affine(generator_256) + p_b = PointJacobi.from_affine(generator_224) + + with self.assertRaises(ValueError): + p_a + p_b + + def test_compare_different_curves(self): + self.assertNotEqual(generator_256, generator_224) + + def test_equality_with_non_point(self): + pj = PointJacobi.from_affine(generator_256) + + self.assertNotEqual(pj, "value") + + def test_conversion(self): + pj = PointJacobi.from_affine(generator_256) + pw = pj.to_affine() + + self.assertEqual(generator_256, pw) + + def test_single_double(self): + pj = PointJacobi.from_affine(generator_256) + pw = generator_256.double() + + pj = pj.double() + + self.assertEqual(pj.x(), pw.x()) + self.assertEqual(pj.y(), pj.y()) + + def test_double_with_zero_point(self): + pj = PointJacobi(curve_256, 0, 0, 1) + + pj = pj.double() + + self.assertIs(pj, INFINITY) + + def test_double_with_zero_equivalent_point(self): + pj = PointJacobi(curve_256, 0, curve_256.p(), 1) + + pj = pj.double() + + self.assertIs(pj, INFINITY) + + def test_double_with_zero_equivalent_point_non_1_z(self): + pj = PointJacobi(curve_256, 0, curve_256.p(), 2) + + pj = pj.double() + + self.assertIs(pj, INFINITY) + + def test_compare_with_affine_point(self): + pj = PointJacobi.from_affine(generator_256) + pa = pj.to_affine() + + self.assertEqual(pj, pa) + self.assertEqual(pa, pj) + + def test_to_affine_with_zero_point(self): + pj = PointJacobi(curve_256, 0, 0, 1) + + pa = pj.to_affine() + + self.assertIs(pa, INFINITY) + + def test_add_with_affine_point(self): + pj = PointJacobi.from_affine(generator_256) + pa = pj.to_affine() + + s = pj + pa + + self.assertEqual(s, pj.double()) + + def test_radd_with_affine_point(self): + pj = PointJacobi.from_affine(generator_256) + pa = pj.to_affine() + + s = pa + pj + + self.assertEqual(s, pj.double()) + + def test_add_with_infinity(self): + pj = PointJacobi.from_affine(generator_256) + + s = pj + INFINITY + + self.assertEqual(s, pj) + + def test_add_zero_point_to_affine(self): + pa = PointJacobi.from_affine(generator_256).to_affine() + pj = PointJacobi(curve_256, 0, 0, 1) + + s = pj + pa + + self.assertIs(s, pa) + + def test_multiply_by_zero(self): + pj = PointJacobi.from_affine(generator_256) + + pj = pj * 0 + + self.assertIs(pj, INFINITY) + + def test_zero_point_multiply_by_one(self): + pj = PointJacobi(curve_256, 0, 0, 1) + + pj = pj * 1 + + self.assertIs(pj, INFINITY) + + def test_multiply_by_one(self): + pj = PointJacobi.from_affine(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_affine(generator_256) + pw = generator_256 * 2 + + pj = pj * 2 + + self.assertEqual(pj.x(), pw.x()) + self.assertEqual(pj.y(), pj.y()) + + def test_rmul_by_two(self): + pj = PointJacobi.from_affine(generator_256) + pw = generator_256 * 2 + + pj = 2 * pj + + self.assertEqual(pj, pw) + + def test_compare_non_zero_with_infinity(self): + pj = PointJacobi.from_affine(generator_256) + + self.assertNotEqual(pj, INFINITY) + + def test_compare_zero_point_with_infinity(self): + pj = PointJacobi(curve_256, 0, 0, 1) + + self.assertEqual(pj, INFINITY) + + def test_compare_double_with_multiply(self): + pj = PointJacobi.from_affine(generator_256) + dbl = pj.double() + mlpl = pj * 2 + + self.assertEqual(dbl, mlpl) + + @settings(max_examples=10) + @given(st.integers(min_value=0, max_value=int(generator_256.order()))) + def test_multiplications(self, mul): + pj = PointJacobi.from_affine(generator_256) + pw = pj.to_affine() * mul + + pj = pj * mul + + self.assertEqual((pj.x(), pj.y()), (pw.x(), pw.y())) + self.assertEqual(pj, pw) + + @settings(max_examples=10) + @given(st.integers(min_value=0, max_value=int(generator_256.order()))) + @example(0) + @example(int(generator_256.order())) + def test_precompute(self, mul): + precomp = PointJacobi.from_affine(generator_256, True) + pj = PointJacobi.from_affine(generator_256) + + a = precomp * mul + b = pj * mul + + self.assertEqual(a, b) + + @settings(max_examples=10) + @given(st.integers(min_value=1, max_value=int(generator_256.order())), + st.integers(min_value=1, max_value=int(generator_256.order()))) + @example(3, 3) + def test_add_scaled_points(self, a_mul, b_mul): + j_g = PointJacobi.from_affine(generator_256) + a = PointJacobi.from_affine(j_g * a_mul) + b = PointJacobi.from_affine(j_g * b_mul) + + c = a + b + + self.assertEqual(c, j_g * (a_mul + b_mul)) + + @settings(max_examples=10) + @given(st.integers(min_value=1, max_value=int(generator_256.order())), + st.integers(min_value=1, max_value=int(generator_256.order())), + st.integers(min_value=1, max_value=int(curve_256.p()-1))) + def test_add_one_scaled_point(self, a_mul, b_mul, new_z): + j_g = PointJacobi.from_affine(generator_256) + a = PointJacobi.from_affine(j_g * a_mul) + b = PointJacobi.from_affine(j_g * b_mul) + + p = curve_256.p() + + assume(inverse_mod(new_z, p)) + + new_zz = new_z * new_z % p + + b = PointJacobi( + curve_256, b.x() * new_zz % p, b.y() * new_zz * new_z % p, new_z) + + c = a + b + + self.assertEqual(c, j_g * (a_mul + b_mul)) + + @settings(max_examples=10) + @given(st.integers(min_value=1, max_value=int(generator_256.order())), + st.integers(min_value=1, max_value=int(generator_256.order())), + st.integers(min_value=1, max_value=int(curve_256.p()-1))) + @example(1, 1, 1) + @example(3, 3, 3) + @example(2, int(generator_256.order()-2), 1) + @example(2, int(generator_256.order()-2), 3) + def test_add_same_scale_points(self, a_mul, b_mul, new_z): + j_g = PointJacobi.from_affine(generator_256) + a = PointJacobi.from_affine(j_g * a_mul) + b = PointJacobi.from_affine(j_g * b_mul) + + p = curve_256.p() + + assume(inverse_mod(new_z, p)) + + new_zz = new_z * new_z % p + + a = PointJacobi( + curve_256, a.x() * new_zz % p, a.y() * new_zz * new_z % p, new_z) + b = PointJacobi( + curve_256, b.x() * new_zz % p, b.y() * new_zz * new_z % p, new_z) + + c = a + b + + self.assertEqual(c, j_g * (a_mul + b_mul)) + + @settings(max_examples=14) + @given(st.integers(min_value=1, max_value=int(generator_256.order())), + st.integers(min_value=1, max_value=int(generator_256.order())), + st.lists(st.integers(min_value=1, max_value=int(curve_256.p()-1)), + min_size=2, max_size=2, unique=True)) + @example(2, 2, [2, 1]) + @example(2, 2, [2, 3]) + @example(2, int(generator_256.order()-2), [2, 3]) + @example(2, int(generator_256.order()-2), [2, 1]) + def test_add_different_scale_points(self, a_mul, b_mul, new_z): + j_g = PointJacobi.from_affine(generator_256) + a = PointJacobi.from_affine(j_g * a_mul) + b = PointJacobi.from_affine(j_g * b_mul) + + p = curve_256.p() + + assume(inverse_mod(new_z[0], p)) + assume(inverse_mod(new_z[1], p)) + + new_zz0 = new_z[0] * new_z[0] % p + new_zz1 = new_z[1] * new_z[1] % p + + a = PointJacobi( + curve_256, + a.x() * new_zz0 % p, + a.y() * new_zz0 * new_z[0] % p, + new_z[0]) + b = PointJacobi( + curve_256, + b.x() * new_zz1 % p, + b.y() * new_zz1 * new_z[1] % p, + new_z[1]) + + c = a + b + + self.assertEqual(c, j_g * (a_mul + b_mul)) + + def test_add_point_3_times(self): + j_g = PointJacobi.from_affine(generator_256) + + self.assertEqual(j_g * 3, j_g + j_g + j_g) + + def test_mul_add_inf(self): + j_g = PointJacobi.from_affine(generator_256) + + self.assertEqual(j_g, j_g.mul_add(1, INFINITY, 1)) + + def test_mul_add_same(self): + j_g = PointJacobi.from_affine(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_affine(generator_256, True) + b = PointJacobi.from_affine(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_affine(generator_256, True) + b = PointJacobi.from_affine(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_affine(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_affine(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_affine(), w_a + w_b) + + def test_mul_add_large(self): + j_g = PointJacobi.from_affine(generator_256) + b = PointJacobi.from_affine(j_g * 255) + + 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)) diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py index a6f23389..cde8d780 100644 --- a/src/ecdsa/test_keys.py +++ b/src/ecdsa/test_keys.py @@ -97,7 +97,7 @@ def test_bytes_compressed(self): self.assertEqual(self.vk.to_string(), vk.to_string()) - def test_bytearray_uncompressed(self): + def test_bytearray_compressed(self): vk = VerifyingKey.from_string(bytearray(b'\x02' + self.key_bytes[:24])) self.assertEqual(self.vk.to_string(), vk.to_string()) diff --git a/src/ecdsa/test_malformed_sigs.py b/src/ecdsa/test_malformed_sigs.py index 22ae4550..c1dca44a 100644 --- a/src/ecdsa/test_malformed_sigs.py +++ b/src/ecdsa/test_malformed_sigs.py @@ -3,7 +3,7 @@ import hashlib try: from hashlib import algorithms_available -except ImportError: +except ImportError: # pragma: no cover algorithms_available = [ "md5", "sha1", "sha224", "sha256", "sha384", "sha512"] from functools import partial @@ -82,7 +82,7 @@ def st_fuzzed_sig(draw, keys_and_sigs): note("Remove bytes: {0}".format(to_remove)) # decide which bytes of the original signature should be changed - if sig: + if sig: # pragma: no branch xors = draw(st.dictionaries( st.integers(min_value=0, max_value=len(sig)-1), st.integers(min_value=1, max_value=255))) @@ -110,7 +110,7 @@ def st_fuzzed_sig(draw, keys_and_sigs): params = {} # not supported in hypothesis 2.0.0 -if sys.version_info >= (2, 7): +if sys.version_info >= (2, 7): # pragma: no branch from hypothesis import HealthCheck # deadline=5s because NIST521p are slow to verify params["deadline"] = 5000 @@ -118,6 +118,9 @@ def st_fuzzed_sig(draw, keys_and_sigs): HealthCheck.filter_too_much, HealthCheck.too_slow] +slow_params = dict(params) +slow_params["max_examples"] = 10 + @settings(**params) @given(st_fuzzed_sig(keys_and_sigs)) @@ -141,7 +144,7 @@ def st_random_der_ecdsa_sig_value(draw): """ name, verifying_key, _ = draw(st.sampled_from(keys_and_sigs)) note("Configuration: {0}".format(name)) - order = verifying_key.curve.order + order = int(verifying_key.curve.order) # the encode_integer doesn't suport negative numbers, would be nice # to generate them too, but we have coverage for remove_integer() @@ -158,7 +161,7 @@ def st_random_der_ecdsa_sig_value(draw): return verifying_key, sig -@settings(**params) +@settings(**slow_params) @given(st_random_der_ecdsa_sig_value()) def test_random_der_ecdsa_sig_value(params): """ @@ -177,7 +180,7 @@ def st_der_integer(*args, **kwargs): INTEGER. Parameters are passed to hypothesis.strategy.integer. """ - if "min_value" not in kwargs: + if "min_value" not in kwargs: # pragma: no branch kwargs["min_value"] = 0 return st.builds(encode_integer, st.integers(*args, **kwargs)) diff --git a/src/ecdsa/test_numbertheory.py b/src/ecdsa/test_numbertheory.py index bf98dc15..4cec4fd6 100644 --- a/src/ecdsa/test_numbertheory.py +++ b/src/ecdsa/test_numbertheory.py @@ -12,7 +12,7 @@ try: from hypothesis import HealthCheck HC_PRESENT=True -except ImportError: +except ImportError: # pragma: no cover HC_PRESENT=False from .numbertheory import (SquareRootError, factorization, gcd, lcm, jacobi, inverse_mod, @@ -85,7 +85,7 @@ def st_two_nums_rel_prime(draw): @st.composite def st_primes(draw, *args, **kwargs): - if "min_value" not in kwargs: + if "min_value" not in kwargs: # pragma: no branch kwargs["min_value"] = 1 prime = draw(st.sampled_from(smallprimes) | st.integers(*args, **kwargs) @@ -161,13 +161,17 @@ def st_comp_no_com_fac(draw): HYP_SETTINGS = {} -if HC_PRESENT: +if HC_PRESENT: # pragma: no branch HYP_SETTINGS['suppress_health_check']=[HealthCheck.filter_too_much, HealthCheck.too_slow] # the factorization() sometimes takes a long time to finish HYP_SETTINGS['deadline'] = 5000 +HYP_SLOW_SETTINGS=dict(HYP_SETTINGS) +HYP_SLOW_SETTINGS["max_examples"] = 10 + + class TestNumbertheory(unittest.TestCase): def test_gcd(self): assert gcd(3 * 5 * 7, 3 * 5 * 11, 3 * 5 * 13) == 3 * 5 @@ -178,7 +182,7 @@ def test_gcd(self): "Hypothesis 2.0.0 can't be made tolerant of hard to " "meet requirements (like `is_prime()`), the test " "case times-out on it") - @settings(**HYP_SETTINGS) + @settings(**HYP_SLOW_SETTINGS) @given(st_comp_with_com_fac()) def test_gcd_with_com_factor(self, numbers): n = gcd(numbers) @@ -190,7 +194,7 @@ def test_gcd_with_com_factor(self, numbers): "Hypothesis 2.0.0 can't be made tolerant of hard to " "meet requirements (like `is_prime()`), the test " "case times-out on it") - @settings(**HYP_SETTINGS) + @settings(**HYP_SLOW_SETTINGS) @given(st_comp_no_com_fac()) def test_gcd_with_uncom_factor(self, numbers): n = gcd(numbers) diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index 3d1247ab..05f22e60 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -12,6 +12,9 @@ from binascii import hexlify, unhexlify from hashlib import sha1, sha256, sha512 +from hypothesis import given +import hypothesis.strategies as st + from six import b, print_, binary_type from .keys import SigningKey, VerifyingKey from .keys import BadSignatureError, MalformedPointError, BadDigestError @@ -49,9 +52,6 @@ def run_openssl(cmd): return stdout.decode() -BENCH = False - - class ECDSA(unittest.TestCase): def test_basic(self): priv = SigningKey.generate() @@ -100,8 +100,6 @@ def test_lengths(self): self.assertEqual(len(pub.to_string()), default.verifying_key_length) sig = priv.sign(b("data")) self.assertEqual(len(sig), default.signature_length) - if BENCH: - print_() for curve in (NIST192p, NIST224p, NIST256p, NIST384p, NIST521p, BRAINPOOLP160r1, BRAINPOOLP192r1, BRAINPOOLP224r1, BRAINPOOLP256r1, BRAINPOOLP320r1, BRAINPOOLP384r1, @@ -118,13 +116,6 @@ def test_lengths(self): sig = priv.sign(b("data")) sign_time = time.time() - start self.assertEqual(len(sig), curve.signature_length) - if BENCH: - start = time.time() - pub1.verify(sig, b("data")) - verify_time = time.time() - start - print_("%s: siglen=%d, keygen=%0.3fs, sign=%0.3f, verify=%0.3f" \ - % (curve.name, curve.signature_length, - keygen_time, sign_time, verify_time)) def test_serialize(self): seed = b("secret") @@ -174,10 +165,6 @@ def assertTruePrivkeysEqual(self, priv1, priv2): self.assertEqual(priv1.privkey.public_key.generator, priv2.privkey.public_key.generator) - def failIfPrivkeysEqual(self, priv1, priv2): - self.failIfEqual(priv1.privkey.secret_multiplier, - priv2.privkey.secret_multiplier) - def test_privkey_creation(self): s = b("all the entropy in the entire world, compressed into one line") @@ -634,32 +621,11 @@ def test_decoding_with_point_at_infinity(self): VerifyingKey.from_string(b('\x00')) def test_not_lying_on_curve(self): - enc = number_to_string(NIST192p.order, NIST192p.order+1) - - 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*') + enc = number_to_string(NIST192p.curve.p(), NIST192p.curve.p()+1) 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 @@ -747,9 +713,9 @@ def get_openssl_messagedigest_arg(self): # e.g. "OpenSSL 1.0.0 29 Mar 2010", or "OpenSSL 1.0.0a 1 Jun 2010", # or "OpenSSL 0.9.8o 01 Jun 2010" vs = v.split()[1].split(".") - if vs >= ["1", "0", "0"]: + if vs >= ["1", "0", "0"]: # pragma: no cover return "-SHA1" - else: + else: # pragma: no cover return "-ecdsa-with-SHA1" # sk: 1:OpenSSL->python 2:python->OpenSSL @@ -827,7 +793,7 @@ def do_test_from_openssl(self, curve): # OpenSSL: create sk, vk, sign. # Python: read vk(3), checksig(5), read sk(1), sign, check mdarg = self.get_openssl_messagedigest_arg() - if os.path.isdir("t"): + if os.path.isdir("t"): # pragma: no cover shutil.rmtree("t") os.mkdir("t") run_openssl("ecparam -name %s -genkey -out t/privkey.pem" % curvename) @@ -922,7 +888,7 @@ def do_test_to_openssl(self, curve): # Python: create sk, vk, sign. # OpenSSL: read vk(4), checksig(6), read sk(2), sign, check mdarg = self.get_openssl_messagedigest_arg() - if os.path.isdir("t"): + if os.path.isdir("t"): # pragma: no cover shutil.rmtree("t") os.mkdir("t") sk = SigningKey.generate(curve=curve) @@ -1025,19 +991,19 @@ def test_trytryagain(self): self.assertEqual(("%x" % (tta("seed", NIST224p.order))).encode(), b("6fa59d73bf0446ae8743cf748fc5ac11d5585a90356417e97155c3bc")) - def test_randrange(self): + @given(st.integers(min_value=0, max_value=10**200)) + def test_randrange(self, i): # util.randrange does not provide long-term stability: we might # change the algorithm in the future. - for i in range(1000): - entropy = util.PRNG("seed-%d" % i) - for order in (2**8 - 2, 2**8 - 1, 2**8, - 2**16 - 1, 2**16 + 1, - ): - # that oddball 2**16+1 takes half our runtime - n = util.randrange(order, entropy=entropy) - self.assertTrue(1 <= n < order, (1, n, order)) - - def OFF_test_prove_uniformity(self): + entropy = util.PRNG("seed-%d" % i) + for order in (2**8 - 2, 2**8 - 1, 2**8, + 2**16 - 1, 2**16 + 1, + ): + # that oddball 2**16+1 takes half our runtime + n = util.randrange(order, entropy=entropy) + self.assertTrue(1 <= n < order, (1, n, order)) + + def OFF_test_prove_uniformity(self): # pragma: no cover order = 2**8 - 2 counts = dict([(i, 0) for i in range(1, order)]) assert 0 not in counts @@ -1189,6 +1155,7 @@ def test_10(self): hash_func=sha512, expected=int("16200813020EC986863BEDFC1B121F605C1215645018AEA1A7B215A564DE9EB1B38A67AA1128B80CE391C4FB71187654AAA3431027BFC7F395766CA988C964DC56D", 16)) + class ECDH(unittest.TestCase): def _do(self, curve, generator, dA, x_qA, y_qA, dB, x_qB, y_qB, x_Z, y_Z): qA = dA * generator @@ -1372,121 +1339,3 @@ def test_brainpoolP512r1(self): y_Z=int("7DB71C3DEF63212841C463E881BDCF055523BD368240E6C3143BD8DEF8" "B3B3223B95E0F53082FF5E412F4222537A43DF1C6D25729DDB51620A83" "2BE6A26680A2", 16)) - - -class ECDH(unittest.TestCase): - def _do(self, curve, generator, dA, x_qA, y_qA, dB, x_qB, y_qB, x_Z, y_Z): - qA = dA * generator - qB = dB * generator - Z = dA * qB - self.assertEqual(Point(curve, x_qA, y_qA), qA) - self.assertEqual(Point(curve, x_qB, y_qB), qB) - self.assertTrue((dA * qB) == (dA * dB * generator) == (dB * dA * generator) == (dB * qA)) - self.assertEqual(Point(curve, x_Z, y_Z), Z) - - -class RFC6932(ECDH): - # https://tools.ietf.org/html/rfc6932#appendix-A.1 - - def test_brainpoolP224r1(self): - self._do( - curve=curve_brainpoolp224r1, - generator=BRAINPOOLP224r1.generator, - dA=int("7C4B7A2C8A4BAD1FBB7D79CC0955DB7C6A4660CA64CC4778159B495E", 16), - x_qA=int("B104A67A6F6E85E14EC1825E1539E8ECDBBF584922367DD88C6BDCF2", 16), - y_qA=int("46D782E7FDB5F60CD8404301AC5949C58EDB26BC68BA07695B750A94", 16), - dB=int("63976D4AAE6CD0F6DD18DEFEF55D96569D0507C03E74D6486FFA28FB", 16), - x_qB=int("2A97089A9296147B71B21A4B574E1278245B536F14D8C2B9D07A874E", 16), - y_qB=int("9B900D7C77A709A797276B8CA1BA61BB95B546FC29F862E44D59D25B", 16), - x_Z=int("312DFD98783F9FB77B9704945A73BEB6DCCBE3B65D0F967DCAB574EB", 16), - y_Z=int("6F800811D64114B1C48C621AB3357CF93F496E4238696A2A012B3C98", 16)) - - def test_brainpoolP256r1(self): - self._do( - curve=curve_brainpoolp256r1, - generator=BRAINPOOLP256r1.generator, - dA=int("041EB8B1E2BC681BCE8E39963B2E9FC415B05283313DD1A8BCC055F11AE49699", 16), - x_qA=int("78028496B5ECAAB3C8B6C12E45DB1E02C9E4D26B4113BC4F015F60C5CCC0D206", 16), - y_qA=int("A2AE1762A3831C1D20F03F8D1E3C0C39AFE6F09B4D44BBE80CD100987B05F92B", 16), - dB=int("06F5240EACDB9837BC96D48274C8AA834B6C87BA9CC3EEDD81F99A16B8D804D3", 16), - x_qB=int("8E07E219BA588916C5B06AA30A2F464C2F2ACFC1610A3BE2FB240B635341F0DB", 16), - y_qB=int("148EA1D7D1E7E54B9555B6C9AC90629C18B63BEE5D7AA6949EBBF47B24FDE40D", 16), - x_Z=int("05E940915549E9F6A4A75693716E37466ABA79B4BF2919877A16DD2CC2E23708", 16), - y_Z=int("6BC23B6702BC5A019438CEEA107DAAD8B94232FFBBC350F3B137628FE6FD134C", 16)) - - def test_brainpoolP384r1(self): - self._do( - curve=curve_brainpoolp384r1, - generator=BRAINPOOLP384r1.generator, - dA=int("014EC0755B78594BA47FB0A56F6173045B4331E74BA1A6F47322E70D79D828D97E095884CA72B73FDABD5910DF0FA76A", 16), - x_qA=int("45CB26E4384DAF6FB776885307B9A38B7AD1B5C692E0C32F0125332778F3B8D3F50CA358099B30DEB5EE69A95C058B4E", 16), - y_qA=int("8173A1C54AFFA7E781D0E1E1D12C0DC2B74F4DF58E4A4E3AF7026C5D32DC530A2CD89C859BB4B4B768497F49AB8CC859", 16), - dB=int("6B461CB79BD0EA519A87D6828815D8CE7CD9B3CAA0B5A8262CBCD550A015C90095B976F3529957506E1224A861711D54", 16), - x_qB=int("01BF92A92EE4BE8DED1A911125C209B03F99E3161CFCC986DC7711383FC30AF9CE28CA3386D59E2C8D72CE1E7B4666E8", 16), - y_qB=int("3289C4A3A4FEE035E39BDB885D509D224A142FF9FBCC5CFE5CCBB30268EE47487ED8044858D31D848F7A95C635A347AC", 16), - x_Z=int("04CC4FF3DCCCB07AF24E0ACC529955B36D7C807772B92FCBE48F3AFE9A2F370A1F98D3FA73FD0C0747C632E12F1423EC", 16), - y_Z=int("7F465F90BD69AFB8F828A214EB9716D66ABC59F17AF7C75EE7F1DE22AB5D05085F5A01A9382D05BF72D96698FE3FF64E", 16)) - - def test_brainpoolP512r1(self): - self._do( - curve=curve_brainpoolp512r1, - generator=BRAINPOOLP512r1.generator, - dA=int("636B6BE0482A6C1C41AA7AE7B245E983392DB94CECEA2660A379CFE159559E357581825391175FC195D28BAC0CF03A7841A383B95C262B983782874CCE6FE333", 16), - x_qA=int("0562E68B9AF7CBFD5565C6B16883B777FF11C199161ECC427A39D17EC2166499389571D6A994977C56AD8252658BA8A1B72AE42F4FB7532151AFC3EF0971CCDA", 16), - y_qA=int("A7CA2D8191E21776A89860AFBC1F582FAA308D551C1DC6133AF9F9C3CAD59998D70079548140B90B1F311AFB378AA81F51B275B2BE6B7DEE978EFC7343EA642E", 16), - dB=int("0AF4E7F6D52EDD52907BB8DBAB3992A0BB696EC10DF11892FF205B66D381ECE72314E6A6EA079CEA06961DBA5AE6422EF2E9EE803A1F236FB96A1799B86E5C8B", 16), - x_qB=int("5A7954E32663DFF11AE24712D87419F26B708AC2B92877D6BFEE2BFC43714D89BBDB6D24D807BBD3AEB7F0C325F862E8BADE4F74636B97EAACE739E11720D323", 16), - y_qB=int("96D14621A9283A1BED84DE8DD64836B2C0758B11441179DC0C54C0D49A47C03807D171DD544B72CAAEF7B7CE01C7753E2CAD1A861ECA55A71954EE1BA35E04BE", 16), - x_Z=int("1EE8321A4BBF93B9CF8921AB209850EC9B7066D1984EF08C2BB723236208AC8F1A483E79461A00E0D5F6921CE9D360502F85C812BEDEE23AC5B210E5811B191E", 16), - y_Z=int("2632095B7B936174B41FD2FAF369B1D18DCADEED7E410A7E251F0831097C50D02CFED02607B6A2D5ADB4C0006008562208631875B58B54ECDA5A4F9FE9EAABA6", 16)) - - -class RFC7027(ECDH): - # https://tools.ietf.org/html/rfc7027#appendix-A - - def test_brainpoolP256r1(self): - self._do( - curve=curve_brainpoolp256r1, - generator=BRAINPOOLP256r1.generator, - dA=int("81DB1EE100150FF2EA338D708271BE38300CB54241D79950F77B063039804F1D", 16), - x_qA=int("44106E913F92BC02A1705D9953A8414DB95E1AAA49E81D9E85F929A8E3100BE5", 16), - y_qA=int("8AB4846F11CACCB73CE49CBDD120F5A900A69FD32C272223F789EF10EB089BDC", 16), - dB=int("55E40BC41E37E3E2AD25C3C6654511FFA8474A91A0032087593852D3E7D76BD3", 16), - x_qB=int("8D2D688C6CF93E1160AD04CC4429117DC2C41825E1E9FCA0ADDD34E6F1B39F7B", 16), - y_qB=int("990C57520812BE512641E47034832106BC7D3E8DD0E4C7F1136D7006547CEC6A", 16), - x_Z=int("89AFC39D41D3B327814B80940B042590F96556EC91E6AE7939BCE31F3A18BF2B", 16), - y_Z=int("49C27868F4ECA2179BFD7D59B1E3BF34C1DBDE61AE12931648F43E59632504DE", 16)) - - def test_brainpoolP384r1(self): - self._do( - curve=curve_brainpoolp384r1, - generator=BRAINPOOLP384r1.generator, - dA=int("1E20F5E048A5886F1F157C74E91BDE2B98C8B52D58E5003D57053FC4B0BD65D6F15EB5D1EE1610DF870795143627D042", 16), - x_qA=int("68B665DD91C195800650CDD363C625F4E742E8134667B767B1B476793588F885AB698C852D4A6E77A252D6380FCAF068", 16), - y_qA=int("55BC91A39C9EC01DEE36017B7D673A931236D2F1F5C83942D049E3FA20607493E0D038FF2FD30C2AB67D15C85F7FAA59", 16), - dB=int("032640BC6003C59260F7250C3DB58CE647F98E1260ACCE4ACDA3DD869F74E01F8BA5E0324309DB6A9831497ABAC96670", 16), - x_qB=int("4D44326F269A597A5B58BBA565DA5556ED7FD9A8A9EB76C25F46DB69D19DC8CE6AD18E404B15738B2086DF37E71D1EB4", 16), - y_qB=int("62D692136DE56CBE93BF5FA3188EF58BC8A3A0EC6C1E151A21038A42E9185329B5B275903D192F8D4E1F32FE9CC78C48", 16), - x_Z=int("0BD9D3A7EA0B3D519D09D8E48D0785FB744A6B355E6304BC51C229FBBCE239BBADF6403715C35D4FB2A5444F575D4F42", 16), - y_Z=int("0DF213417EBE4D8E40A5F76F66C56470C489A3478D146DECF6DF0D94BAE9E598157290F8756066975F1DB34B2324B7BD", 16)) - - def test_brainpoolP512r1(self): - self._do( - curve=curve_brainpoolp512r1, - generator=BRAINPOOLP512r1.generator, - dA=int("16302FF0DBBB5A8D733DAB7141C1B45ACBC8715939677F6A56850A38BD87BD59B09E80279609FF333EB9D4C061231FB26F92EEB04982A5F1D1764CAD57665422", 16), - x_qA=int("0A420517E406AAC0ACDCE90FCD71487718D3B953EFD7FBEC5F7F27E28C6149999397E91E029E06457DB2D3E640668B392C2A7E737A7F0BF04436D11640FD09FD", 16), - y_qA=int("72E6882E8DB28AAD36237CD25D580DB23783961C8DC52DFA2EC138AD472A0FCEF3887CF62B623B2A87DE5C588301EA3E5FC269B373B60724F5E82A6AD147FDE7", 16), - dB=int("230E18E1BCC88A362FA54E4EA3902009292F7F8033624FD471B5D8ACE49D12CFABBC19963DAB8E2F1EBA00BFFB29E4D72D13F2224562F405CB80503666B25429", 16), - x_qB=int("9D45F66DE5D67E2E6DB6E93A59CE0BB48106097FF78A081DE781CDB31FCE8CCBAAEA8DD4320C4119F1E9CD437A2EAB3731FA9668AB268D871DEDA55A5473199F", 16), - y_qB=int("2FDC313095BCDD5FB3A91636F07A959C8E86B5636A1E930E8396049CB481961D365CC11453A06C719835475B12CB52FC3C383BCE35E27EF194512B71876285FA", 16), - x_Z=int("A7927098655F1F9976FA50A9D566865DC530331846381C87256BAF3226244B76D36403C024D7BBF0AA0803EAFF405D3D24F11A9B5C0BEF679FE1454B21C4CD1F", 16), - y_Z=int("7DB71C3DEF63212841C463E881BDCF055523BD368240E6C3143BD8DEF8B3B3223B95E0F53082FF5E412F4222537A43DF1C6D25729DDB51620A832BE6A26680A2", 16)) - - -def __main__(): - unittest.main() - - -if __name__ == "__main__": - __main__() diff --git a/tox.ini b/tox.ini index fbaf823e..1960fe17 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] -envlist = py26, py27, py33, py34, py35, py36, py37, py38, py, pypy, pypy3 +envlist = py26, py27, py33, py34, py35, py36, py37, py38, py, pypy, pypy3, gmpy2py27, gmpy2py38, gmpypy27, gmpypy38 [testenv] deps = @@ -12,12 +12,28 @@ deps = py{26}: hypothesis<3 py{26,27,34,35,36,37,38,py,py3}: pytest py{27,34,35,36,37,38,py,py3}: hypothesis + gmpy2py{27,38}: gmpy2 + gmpypy{27,38}: gmpy + gmpy{2py27,2py38,py27,py38}: pytest + gmpy{2py27,2py38,py27,py38}: hypothesis py: pytest py: hypothesis py{33}: wheel<0.30 coverage commands = coverage run --branch -m pytest {posargs:src/ecdsa} +[testenv:gmpypy27] +basepython=python2.7 + +[testenv:gmpypy38] +basepython=python3.8 + +[testenv:gmpy2py27] +basepython=python2.7 + +[testenv:gmpy2py38] +basepython=python3.8 + [testenv:coverage] sitepackages=True commands = coverage run --branch -m pytest --hypothesis-show-statistics {posargs:src/ecdsa} @@ -25,6 +41,14 @@ commands = coverage run --branch -m pytest --hypothesis-show-statistics {posargs [testenv:speed] commands = {envpython} speed.py +[testenv:speedgmpy] +deps = gmpy +commands = {envpython} speed.py + +[testenv:speedgmpy2] +deps = gmpy2 +commands = {envpython} speed.py + [testenv:codechecks] basepython = python3 deps =