diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f25221df..8568f31f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -215,10 +215,10 @@ jobs: if: ${{ contains(matrix.tox-env, 'gmpyp') }} run: pip install gmpy - name: Install gmpy2 dependencies - if: ${{ contains(matrix.tox-env, 'gmpy2') || contains(matrix.tox-env, 'instrumental') }} + if: ${{ contains(matrix.tox-env, 'gmpy2') || contains(matrix.tox-env, 'instrumental') || matrix.mutation == 'true' }} run: sudo apt-get install -y libmpfr-dev libmpc-dev - name: Install gmpy2 - if: ${{ contains(matrix.tox-env, 'gmpy2') || contains(matrix.tox-env, 'instrumental') }} + if: ${{ contains(matrix.tox-env, 'gmpy2') || contains(matrix.tox-env, 'instrumental') || matrix.mutation == 'true' }} run: pip install gmpy2 - name: Install build dependencies (2.6) if: ${{ matrix.python-version == '2.6' }} @@ -268,7 +268,8 @@ jobs: - name: Install mutation testing dependencies if: ${{ matrix.mutation == 'true' }} run: | - pip install cosmic-ray + pip install https://github.com/sixty-north/cosmic-ray/archive/master.zip + pip install pytest-timeout - name: Display installed python package versions run: pip list - name: Test native speed @@ -296,19 +297,40 @@ jobs: cosmic-ray init cosmic-ray.toml session-vs-master.sqlite git branch master origin/master cr-filter-git --config cosmic-ray.toml session-vs-master.sqlite - cr-report session-vs-master.sqlite | tail -n 5 + cr-report session-vs-master.sqlite | tail -n 3 - name: Exec mutation testing for PR if: ${{ matrix.mutation == 'true' && github.event.pull_request }} run: | - cosmic-ray exec cosmic-ray.toml session-vs-master.sqlite + systemd-run --user --scope -p MemoryMax=2G -p MemoryHigh=2G cosmic-ray --verbosity INFO exec cosmic-ray.toml session-vs-master.sqlite & + cosmic_pid=$! + for i in $(seq 1 600); do + # wait for test execution at most 10 minutes + kill -s 0 $cosmic_pid || break + sleep 1 + done + kill $cosmic_pid || true + wait $cosmic_pid || true - name: Check test coverage for PR if: ${{ matrix.mutation == 'true' && github.event.pull_request }} run: | # remove not-executed results sqlite3 session-vs-master.sqlite "DELETE from work_results WHERE work_results.worker_outcome = 'SKIPPED'" - cr-report session-vs-master.sqlite | tail -n 5 - # check if executed have at most 5% survival rate - cr-rate --fail-over 5 session-vs-master.sqlite + cr-report session-vs-master.sqlite | tail -n 3 + - name: Generate html report + if: ${{ matrix.mutation == 'true' && github.event.pull_request }} + run: | + cr-html session-vs-master.sqlite > cosmic-ray.html + - name: Archive mutation testing results + if: ${{ matrix.mutation == 'true' && github.event.pull_request }} + uses: actions/upload-artifact@v3 + with: + name: mutation-PR-coverage-report + path: cosmic-ray.html + - name: Check test coverage for PR + if: ${{ matrix.mutation == 'true' && github.event.pull_request }} + run: | + # check if executed have at most 50% survival rate + cr-rate --estimate --confidence 99.9 --fail-over 50 session-vs-master.sqlite - name: instrumental test coverage on PR if: ${{ contains(matrix.opt-deps, 'instrumental') && github.event.pull_request }} env: @@ -372,6 +394,9 @@ jobs: mutation-prepare: name: Prepare job files for the mutation runners + # use runner minutes on mutation testing only after the PR passed basic + # testing + needs: coveralls runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -386,7 +411,8 @@ jobs: key: sessions-${{ github.sha }} - name: Install cosmic-ray run: | - pip3 install cosmic-ray + pip3 install https://github.com/sixty-north/cosmic-ray/archive/master.zip + pip install pytest-timeout - name: Install dependencies run: | sudo apt-get install -y sqlite3 @@ -461,7 +487,8 @@ jobs: - name: Install build dependencies run: | pip install -r build-requirements.txt - pip install cosmic-ray + pip install https://github.com/sixty-north/cosmic-ray/archive/master.zip + pip install pytest-timeout - name: Run mutation testing run: | cp sessions/session-${{ matrix.name }}.sqlite session.sqlite @@ -608,7 +635,8 @@ jobs: key: sessions-${{ github.sha }}-19-done - name: Install cosmic-ray run: | - pip3 install cosmic-ray + pip3 install https://github.com/sixty-north/cosmic-ray/archive/master.zip + pip install pytest-timeout - name: Install dependencies run: | sudo apt-get install -y sqlite3 @@ -621,13 +649,20 @@ jobs: - name: Report executed run: | cr-report session.sqlite | tail -n 3 - - name: Log survival estimate - run: cr-rate --estimate --fail-over 32 --confidence 99.9 session.sqlite || true + - name: Generate html report + run: | + cr-html session.sqlite > cosmic-ray.html + - name: Archive mutation testing results + uses: actions/upload-artifact@v3 + with: + name: mutation-coverage-report + path: cosmic-ray.html - name: Get mutation score run: | - echo "print(100-$(cr-rate session.sqlite))" > print-score.py + echo "print('{0:.2f}'.format(100-$(cr-rate session.sqlite)))" > print-score.py echo "MUT_SCORE=$(python print-score.py)" >> $GITHUB_ENV - name: Create mutation score badge + if: ${{ !github.event.pull_request }} uses: schneegans/dynamic-badges-action@v1.4.0 with: auth: ${{ secrets.GIST_SECRET }} @@ -638,3 +673,5 @@ jobs: valColorRange: ${{ env.MUT_SCORE }} maxColorRange: 100 minColorRange: 0 + - name: Check survival estimate + run: cr-rate --estimate --fail-over 32 --confidence 99.9 session.sqlite diff --git a/cosmic-ray-12way.toml b/cosmic-ray-12way.toml index 78ee99c3..c1e8e55c 100644 --- a/cosmic-ray-12way.toml +++ b/cosmic-ray-12way.toml @@ -2,7 +2,7 @@ module-path = "src" timeout = 20.0 excluded-modules = ['src/ecdsa/_sha3.py', 'src/ecdsa/_version.py', 'src/ecdsa/test*'] -test-command = "pytest -x --fast -m 'not slow' src/" +test-command = "pytest --timeout=30 -x --fast -m 'not slow' src/" [cosmic-ray.distributor] name = "http" diff --git a/cosmic-ray.toml b/cosmic-ray.toml index 6f1c54fa..af40c3cb 100644 --- a/cosmic-ray.toml +++ b/cosmic-ray.toml @@ -2,7 +2,7 @@ module-path = "src" timeout = 20.0 excluded-modules = ['src/ecdsa/_sha3.py', 'src/ecdsa/_version.py', 'src/ecdsa/test*'] -test-command = "pytest -x --fast -m 'not slow' src/" +test-command = "pytest --timeout 30 -x --fast -m 'not slow' src/" [cosmic-ray.distributor] name = "local" diff --git a/src/ecdsa/ellipticcurve.py b/src/ecdsa/ellipticcurve.py index 067a149c..18816a66 100644 --- a/src/ecdsa/ellipticcurve.py +++ b/src/ecdsa/ellipticcurve.py @@ -1532,7 +1532,9 @@ def double(self): X3, Y3, Z3, T3 = self._double(X1, Y1, Z1, T1, p, a) - if not X3 or not T3: + # both Ed25519 and Ed448 have prime order, so no point added to + # itself will equal zero + if not X3 or not T3: # pragma: no branch return INFINITY return PointEdwards(self.__curve, X3, Y3, Z3, T3, self.__order) diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index f493995d..f74252c7 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -1102,12 +1102,6 @@ def from_der(cls, string, hashfunc=sha1, valid_curve_encodings=None): curve = Curve.from_der(algorithm_identifier, valid_curve_encodings) - if empty != b"": - raise der.UnexpectedDER( - "unexpected data after algorithm identifier: %s" - % binascii.hexlify(empty) - ) - # Up next is an octet string containing an ECPrivateKey. Ignore # the optional "attributes" and "publicKey" fields that come after. s, _ = der.remove_octet_string(s) diff --git a/src/ecdsa/numbertheory.py b/src/ecdsa/numbertheory.py index d3500c70..fe974f8e 100644 --- a/src/ecdsa/numbertheory.py +++ b/src/ecdsa/numbertheory.py @@ -20,11 +20,11 @@ except NameError: xrange = range try: - from gmpy2 import powmod + from gmpy2 import powmod, mpz GMPY2 = True GMPY = False -except ImportError: +except ImportError: # pragma: no branch GMPY2 = False try: from gmpy import mpz @@ -33,8 +33,15 @@ except ImportError: GMPY = False + +if GMPY2 or GMPY: # pragma: no branch + integer_types = tuple(integer_types + (type(mpz(1)),)) + + import math import warnings +import random +from .util import bit_length class Error(Exception): @@ -216,14 +223,15 @@ def square_root_mod_prime(a, p): range_top = min(0x7FFFFFFF, p) else: range_top = p - for b in xrange(2, range_top): + for b in xrange(2, range_top): # pragma: no branch if jacobi(b * b - 4 * a, p) == -1: f = (a, -b, 1) ff = polynomial_exp_mod((0, 1), (p + 1) // 2, f, p) if ff[1]: raise SquareRootError("p is not prime") return ff[0] - raise RuntimeError("No b found.") + # just an assertion + raise RuntimeError("No b found.") # pragma: no cover # because all the inverse_mod code is arch/environment specific, and coveralls @@ -346,7 +354,7 @@ def factorization(n): q, r = divmod(n, d) if r == 0: count = 1 - while d <= n: + while d <= n: # pragma: no branch n = q q, r = divmod(n, d) if r != 0: @@ -370,7 +378,8 @@ def factorization(n): if r == 0: # d divides n. How many times? count = 1 n = q - while d <= n: # As long as d might still divide n, + # As long as d might still divide n, + while d <= n: # pragma: no branch q, r = divmod(n, d) # see if it does. if r != 0: break @@ -555,8 +564,8 @@ def is_prime(n): return True else: return False - - if gcd(n, 2 * 3 * 5 * 7 * 11) != 1: + # 2310 = 2 * 3 * 5 * 7 * 11 + if gcd(n, 2310) != 1: return False # Choose a number of iterations sufficient to reduce the @@ -564,7 +573,8 @@ def is_prime(n): # (from Menezes et al. Table 4.4): t = 40 - n_bits = 1 + int(math.log(n, 2)) + n_bits = 1 + bit_length(n) + assert 11 <= n_bits <= 16384 for k, tt in ( (100, 27), (150, 18), @@ -591,7 +601,7 @@ def is_prime(n): s = s + 1 r = r // 2 for i in xrange(t): - a = smallprimes[i] + a = random.choice(smallprimes) y = pow(a, r, n) if y != 1 and y != n - 1: j = 1 diff --git a/src/ecdsa/test_ecdh.py b/src/ecdsa/test_ecdh.py index 3395a212..cb225803 100644 --- a/src/ecdsa/test_ecdh.py +++ b/src/ecdsa/test_ecdh.py @@ -1,4 +1,5 @@ import os +import sys import shutil import subprocess import pytest @@ -16,6 +17,8 @@ NIST384p, NIST521p, BRAINPOOLP160r1, + SECP112r2, + SECP128r1, ) from .curves import curves from .ecdh import ( @@ -29,6 +32,10 @@ from .ellipticcurve import CurveEdTw +if "--fast" in sys.argv: # pragma: no cover + curves = [SECP112r2, SECP128r1] + + @pytest.mark.parametrize( "vcurve", curves, diff --git a/src/ecdsa/test_eddsa.py b/src/ecdsa/test_eddsa.py index 1a35fb32..6821b3bc 100644 --- a/src/ecdsa/test_eddsa.py +++ b/src/ecdsa/test_eddsa.py @@ -163,6 +163,27 @@ def test_ed25519_eq_x_different_y(): assert a != b +def test_ed25519_mul_by_order(): + g = PointEdwards( + curve_ed25519, + generator_ed25519.x(), + generator_ed25519.y(), + 1, + generator_ed25519.x() * generator_ed25519.y(), + ) + + assert g * generator_ed25519.order() == INFINITY + + +def test_radd(): + + a = PointEdwards(curve_ed25519, 1, 1, 1, 1) + + p = INFINITY + a + + assert p == a + + def test_ed25519_test_normalisation_and_scaling(): x = generator_ed25519.x() y = generator_ed25519.y() @@ -664,7 +685,7 @@ def test_invalid_r_value(self): HYP_SETTINGS = dict() -if "--fast" in sys.argv: +if "--fast" in sys.argv: # pragma: no cover HYP_SETTINGS["max_examples"] = 2 else: HYP_SETTINGS["max_examples"] = 10 diff --git a/src/ecdsa/test_ellipticcurve.py b/src/ecdsa/test_ellipticcurve.py index 9fac7eed..9bf09513 100644 --- a/src/ecdsa/test_ellipticcurve.py +++ b/src/ecdsa/test_ellipticcurve.py @@ -223,3 +223,34 @@ def test_inequality_points(self): def test_inequality_points_diff_types(self): c = CurveFp(100, -3, 100) self.assertNotEqual(self.g_23, c) + + def test_to_bytes_from_bytes(self): + p = Point(self.c_23, 3, 10) + + self.assertEqual(p, Point.from_bytes(self.c_23, p.to_bytes())) + + def test_add_to_neg_self(self): + p = Point(self.c_23, 3, 10) + + self.assertEqual(INFINITY, p + (-p)) + + def test_add_to_infinity(self): + p = Point(self.c_23, 3, 10) + + self.assertIs(p, p + INFINITY) + + def test_mul_infinity_by_scalar(self): + self.assertIs(INFINITY, INFINITY * 10) + + def test_mul_by_negative(self): + p = Point(self.c_23, 3, 10) + + self.assertEqual(p * -5, (-p) * 5) + + def test_str_infinity(self): + self.assertEqual(str(INFINITY), "infinity") + + def test_str_point(self): + p = Point(self.c_23, 3, 10) + + self.assertEqual(str(p), "(3,10)") diff --git a/src/ecdsa/test_jacobi.py b/src/ecdsa/test_jacobi.py index e52f93bd..9a46afea 100644 --- a/src/ecdsa/test_jacobi.py +++ b/src/ecdsa/test_jacobi.py @@ -635,6 +635,12 @@ def test_equality_with_wrong_curves(self): self.assertNotEqual(p_a, p_b) + def test_add_with_point_at_infinity(self): + pj1 = PointJacobi(curve=CurveFp(23, 1, 1, 1), x=2, y=3, z=1, order=1) + x, y, z = pj1._add(2, 3, 1, 5, 5, 0, 23) + + self.assertEqual((x, y, z), (2, 3, 1)) + def test_pickle(self): pj = PointJacobi(curve=CurveFp(23, 1, 1, 1), x=2, y=3, z=1, order=1) self.assertEqual(pickle.loads(pickle.dumps(pj)), pj) diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py index a41a3dbc..348475e2 100644 --- a/src/ecdsa/test_keys.py +++ b/src/ecdsa/test_keys.py @@ -13,13 +13,20 @@ import pytest import hashlib -from .keys import VerifyingKey, SigningKey, MalformedPointError +from .keys import ( + VerifyingKey, + SigningKey, + MalformedPointError, + BadSignatureError, +) from .der import ( unpem, UnexpectedDER, encode_sequence, encode_oid, encode_bitstring, + encode_integer, + encode_octet_string, ) from .util import ( sigencode_string, @@ -120,6 +127,10 @@ def test_bytearray_compressed(self): self.assertEqual(self.vk.to_string(), vk.to_string()) + def test_ed25519_VerifyingKey_from_string_imported(self): + with self.assertRaises(MalformedPointError): + VerifyingKey.from_string(b"AAA", Ed25519) + class TestVerifyingKeyFromDer(unittest.TestCase): """ @@ -405,6 +416,28 @@ def test_ed25519_sig_verify(self): self.assertTrue(vk.verify(sig, data)) + def test_ed25519_sig_verify_malformed(self): + vk_pem = ( + "-----BEGIN PUBLIC KEY-----\n" + "MCowBQYDK2VwAyEAIwBQ0NZkIiiO41WJfm5BV42u3kQm7lYnvIXmCy8qy2U=\n" + "-----END PUBLIC KEY-----\n" + ) + + vk = VerifyingKey.from_pem(vk_pem) + + data = b"data\n" + + # modified signature from test_ed25519_sig_verify + sig = ( + b"\xAA\x47\xab\x6a\x33\xcd\x79\x45\xad\x98\x11\x6c\xb9\xf2\x20\xeb" + b"\x90\xd6\x50\xe3\xc7\x8f\x9f\x60\x10\xec\x75\xe0\x2f\x27\xd3\x96" + b"\xda\xe8\x58\x7f\xe0\xfe\x46\x5c\x81\xef\x50\xec\x29\x9f\xae\xd5" + b"\xad\x46\x3c\x91\x68\x83\x4d\xea\x8d\xa8\x19\x04\x04\x79\x03\x0b" + ) + + with self.assertRaises(BadSignatureError): + vk.verify(sig, data) + def test_ed448_from_pem(self): pem_str = ( "-----BEGIN PUBLIC KEY-----\n" @@ -517,6 +550,17 @@ def setUpClass(cls): ) cls.sk2 = SigningKey.from_pem(prv_key_str) + def test_to_der_pkcs8(self): + self.assertEqual( + self.sk1.to_der(format="pkcs8"), + b"0o\x02\x01\x010\x13\x06\x07*\x86H\xce=\x02\x01\x06\x08*\x86H" + b"\xce=\x03\x01\x01\x04U0S\x02\x01\x01\x04\x18^\xc8B\x0b\xd6\xef" + b"\x92R\xa9B\xe9\x89\x04<\xa2\x9fV\x1f\xa5%w\x0e\xb1\xc5\xa14\x03" + b"2\x00\x04\xb8\x81w\xd0\x84\xef\x17\xf5\xe4V9@\x80(6\x0f\x9fY" + b"\xb4\xa4\xd7&Nb\xda\x06Q\xdc\xe4z5\xa4\xc5\xb4\\\xf5\x15\x93B:" + b"\x8bU{\x9c \x99\xf3l", + ) + def test_decoding_explicit_curve_parameters(self): prv_key_str = ( "-----BEGIN PRIVATE KEY-----\n" @@ -626,6 +670,99 @@ def test_ed25519_from_pem(self): self.assertEqual(sk, sk_str) + def test_ed25519_from_der_bad_alg_id_params(self): + der_str = encode_sequence( + encode_integer(1), + encode_sequence(encode_oid(*Ed25519.oid), encode_integer(1)), + encode_octet_string(encode_octet_string(b"A" * 32)), + ) + + with self.assertRaises(UnexpectedDER) as e: + SigningKey.from_der(der_str) + + self.assertIn("Non NULL parameters", str(e.exception)) + + def test_ed25519_from_der_junk_after_priv_key(self): + der_str = encode_sequence( + encode_integer(1), + encode_sequence( + encode_oid(*Ed25519.oid), + ), + encode_octet_string(encode_octet_string(b"A" * 32) + b"B"), + ) + + with self.assertRaises(UnexpectedDER) as e: + SigningKey.from_der(der_str) + + self.assertIn( + "trailing junk after the encoded private key", str(e.exception) + ) + + def test_ed25519_sign(self): + sk_str = SigningKey.from_string( + b"\x34\xBA\xC7\xD1\x4E\xD4\xF1\xBC\x4F\x8C\x48\x3E\x0F\x19\x77\x4C" + b"\xFC\xB8\xBE\xAC\x54\x66\x45\x11\x9A\xD7\xD7\xB8\x07\x0B\xF5\xD4", + Ed25519, + ) + + msg = b"message" + + sig = sk_str.sign(msg, sigencode=sigencode_der) + + self.assertEqual( + sig, + b"\xe1,v\xc9>%\xda\xd2~>\xc3&\na\xf4@|\x9e`X\x11\x13@<\x987\xd4" + b"\r\xb1\xf5\xb3\x15\x7f%i{\xdf}\xdd\xb1\xf3\x02\x7f\x80\x02\xc2" + b'|\xe5\xd6\x06\xc4\n\xa3\xb0\xf6}\xc0\xed)"+E\xaf\x00', + ) + + def test_ed25519_sign_digest_deterministic(self): + sk_str = SigningKey.from_string( + b"\x34\xBA\xC7\xD1\x4E\xD4\xF1\xBC\x4F\x8C\x48\x3E\x0F\x19\x77\x4C" + b"\xFC\xB8\xBE\xAC\x54\x66\x45\x11\x9A\xD7\xD7\xB8\x07\x0B\xF5\xD4", + Ed25519, + ) + with self.assertRaises(ValueError) as e: + sk_str.sign_digest_deterministic(b"a" * 20) + + self.assertIn("Method unsupported for Edwards", str(e.exception)) + + def test_ed25519_sign_digest(self): + sk_str = SigningKey.from_string( + b"\x34\xBA\xC7\xD1\x4E\xD4\xF1\xBC\x4F\x8C\x48\x3E\x0F\x19\x77\x4C" + b"\xFC\xB8\xBE\xAC\x54\x66\x45\x11\x9A\xD7\xD7\xB8\x07\x0B\xF5\xD4", + Ed25519, + ) + with self.assertRaises(ValueError) as e: + sk_str.sign_digest(b"a" * 20) + + self.assertIn("Method unsupported for Edwards", str(e.exception)) + + def test_ed25519_sign_number(self): + sk_str = SigningKey.from_string( + b"\x34\xBA\xC7\xD1\x4E\xD4\xF1\xBC\x4F\x8C\x48\x3E\x0F\x19\x77\x4C" + b"\xFC\xB8\xBE\xAC\x54\x66\x45\x11\x9A\xD7\xD7\xB8\x07\x0B\xF5\xD4", + Ed25519, + ) + with self.assertRaises(ValueError) as e: + sk_str.sign_number(20) + + self.assertIn("Method unsupported for Edwards", str(e.exception)) + + def test_ed25519_to_der_ssleay(self): + pem_str = ( + "-----BEGIN PRIVATE KEY-----\n" + "MC4CAQAwBQYDK2VwBCIEIDS6x9FO1PG8T4xIPg8Zd0z8uL6sVGZFEZrX17gHC/XU\n" + "-----END PRIVATE KEY-----\n" + ) + + sk = SigningKey.from_pem(pem_str) + + with self.assertRaises(ValueError) as e: + sk.to_der(format="ssleay") + + self.assertIn("Only PKCS#8 format", str(e.exception)) + def test_ed25519_to_pem(self): sk = SigningKey.from_string( b"\x34\xBA\xC7\xD1\x4E\xD4\xF1\xBC\x4F\x8C\x48\x3E\x0F\x19\x77\x4C" @@ -667,6 +804,17 @@ def test_ed25519_to_and_from_pem(self): self.assertEqual(sk, decoded) + def test_ed25519_custom_entropy(self): + sk = SigningKey.generate(Ed25519, entropy=os.urandom) + + self.assertIsNotNone(sk) + + def test_ed25519_from_secret_exponent(self): + with self.assertRaises(ValueError) as e: + SigningKey.from_secret_exponent(1234567890, curve=Ed25519) + + self.assertIn("don't support setting the secret", str(e.exception)) + def test_ed448_from_pem(self): pem_str = ( "-----BEGIN PRIVATE KEY-----\n" diff --git a/src/ecdsa/test_malformed_sigs.py b/src/ecdsa/test_malformed_sigs.py index d7050d2f..e5a87c28 100644 --- a/src/ecdsa/test_malformed_sigs.py +++ b/src/ecdsa/test_malformed_sigs.py @@ -31,7 +31,7 @@ from .keys import BadSignatureError from .util import sigencode_der, sigencode_string from .util import sigdecode_der, sigdecode_string -from .curves import curves +from .curves import curves, SECP112r2, SECP128r1 from .der import ( encode_integer, encode_bitstring, @@ -55,6 +55,10 @@ bigger than order sizes of curves.""" +if "--fast" in sys.argv: # pragma: no cover + curves = [SECP112r2, SECP128r1] + + keys_and_sigs = [] """Name of the curve+hash combination, VerifyingKey and DER signature.""" @@ -91,7 +95,7 @@ def test_signatures(verifying_key, signature): @st.composite -def st_fuzzed_sig(draw, keys_and_sigs): +def st_fuzzed_sig(draw, keys_and_sigs): # pragma: no cover """ Hypothesis strategy that generates pairs of VerifyingKey and malformed signatures created by fuzzing of a valid signature. @@ -174,7 +178,7 @@ def test_fuzzed_der_signatures(args): @st.composite -def st_random_der_ecdsa_sig_value(draw): +def st_random_der_ecdsa_sig_value(draw): # pragma: no cover """ Hypothesis strategy for selecting random values and encoding them to ECDSA-Sig-Value object:: @@ -220,7 +224,7 @@ def test_random_der_ecdsa_sig_value(params): verifying_key.verify(sig, example_data, sigdecode=sigdecode_der) -def st_der_integer(*args, **kwargs): +def st_der_integer(*args, **kwargs): # pragma: no cover """ Hypothesis strategy that returns a random positive integer as DER INTEGER. @@ -232,7 +236,7 @@ def st_der_integer(*args, **kwargs): @st.composite -def st_der_bit_string(draw, *args, **kwargs): +def st_der_bit_string(draw, *args, **kwargs): # pragma: no cover """ Hypothesis strategy that returns a random DER BIT STRING. Parameters are passed to hypothesis.strategy.binary. @@ -248,7 +252,7 @@ def st_der_bit_string(draw, *args, **kwargs): return encode_bitstring(data, unused) -def st_der_octet_string(*args, **kwargs): +def st_der_octet_string(*args, **kwargs): # pragma: no cover """ Hypothesis strategy that returns a random DER OCTET STRING object. Parameters are passed to hypothesis.strategy.binary @@ -256,7 +260,7 @@ def st_der_octet_string(*args, **kwargs): return st.builds(encode_octet_string, st.binary(*args, **kwargs)) -def st_der_null(): +def st_der_null(): # pragma: no cover """ Hypothesis strategy that returns DER NULL object. """ @@ -264,7 +268,7 @@ def st_der_null(): @st.composite -def st_der_oid(draw): +def st_der_oid(draw): # pragma: no cover """ Hypothesis strategy that returns DER OBJECT IDENTIFIER objects. """ @@ -279,7 +283,7 @@ def st_der_oid(draw): return encode_oid(first, second, *rest) -def st_der(): +def st_der(): # pragma: no cover """ Hypothesis strategy that returns random DER structures. diff --git a/src/ecdsa/test_numbertheory.py b/src/ecdsa/test_numbertheory.py index 983039e5..966eca29 100644 --- a/src/ecdsa/test_numbertheory.py +++ b/src/ecdsa/test_numbertheory.py @@ -30,6 +30,16 @@ square_root_mod_prime, ) +try: + from gmpy2 import mpz +except ImportError: + try: + from gmpy import mpz + except ImportError: + + def mpz(x): + return x + BIGPRIMES = ( 999671, @@ -293,7 +303,30 @@ def test_medium_non_trivial_composite(self): def test_large_prime(self): # nextPrime[2^2048] - assert is_prime(2**2048 + 0x3D5) + assert is_prime(mpz(2) ** 2048 + 0x3D5) + + def test_pseudoprime_base_19(self): + assert not is_prime(1543267864443420616877677640751301) + + def test_pseudoprime_base_300(self): + # F. Arnault "Constructing Carmichael Numbers Which Are Strong + # Pseudoprimes to Several Bases". Journal of Symbolic + # Computation. 20 (2): 151-161. doi:10.1006/jsco.1995.1042. + # Section 4.4 Large Example (a pseudoprime to all bases up to + # 300) + p = int( + "29 674 495 668 685 510 550 154 174 642 905 332 730 " + "771 991 799 853 043 350 995 075 531 276 838 753 171 " + "770 199 594 238 596 428 121 188 033 664 754 218 345 " + "562 493 168 782 883".replace(" ", "") + ) + + assert is_prime(p) + for _ in range(10): + if not is_prime(p * (313 * (p - 1) + 1) * (353 * (p - 1) + 1)): + break + else: + assert False, "composite not detected" class TestNumbertheory(unittest.TestCase): @@ -309,6 +342,7 @@ def test_gcd(self): "case times-out on it", ) @settings(**HYP_SLOW_SETTINGS) + @example([877 * 1151, 877 * 1009]) @given(st_comp_with_com_fac()) def test_gcd_with_com_factor(self, numbers): n = gcd(numbers) @@ -323,6 +357,7 @@ def test_gcd_with_com_factor(self, numbers): "case times-out on it", ) @settings(**HYP_SLOW_SETTINGS) + @example([1151, 1069, 1009]) @given(st_comp_no_com_fac()) def test_gcd_with_uncom_factor(self, numbers): n = gcd(numbers) @@ -415,13 +450,16 @@ def test_jacobi_with_one(self): @settings(**HYP_SLOW_SETTINGS) @given(st.integers(min_value=3, max_value=1000).filter(lambda x: x % 2)) def test_jacobi(self, mod): + mod = mpz(mod) if is_prime(mod): squares = set() for root in range(1, mod): + root = mpz(root) assert jacobi(root * root, mod) == 1 squares.add(root * root % mod) for i in range(1, mod): if i not in squares: + i = mpz(i) assert jacobi(i, mod) == -1 else: factors = factorization(mod) diff --git a/src/ecdsa/test_sha3.py b/src/ecdsa/test_sha3.py index 3e5107cc..d30381d7 100644 --- a/src/ecdsa/test_sha3.py +++ b/src/ecdsa/test_sha3.py @@ -8,7 +8,7 @@ from gmpy2 import mpz GMPY = True -except ImportError: +except ImportError: # pragma: no cover try: from gmpy import mpz