From 242fc415ac851fb1fd02d125545ce2bc7135e01c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 10 Jul 2022 16:47:53 +0200 Subject: [PATCH 001/105] fix curve equation in the glossary --- docs/source/glossary.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst index 54866730..4aebb532 100644 --- a/docs/source/glossary.rst +++ b/docs/source/glossary.rst @@ -88,5 +88,5 @@ Glossary ``tuple``, ``set``, ``frozenset``, etc. short Weierstrass curve - A curve with the curve equation: :math:`x^2=y^3+ax+b`. Most popular + A curve with the curve equation: :math:`y^2=x^3+ax+b`. Most popular curves use equation of this format (e.g. NIST256p). From 87a1596b93f45f5b2ee484ca12a075365e67815a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 24 Oct 2022 20:27:50 +0200 Subject: [PATCH 002/105] tighter bounds for hypothesis parameters --- src/ecdsa/test_ecdsa.py | 4 ++-- src/ecdsa/test_ellipticcurve.py | 2 +- src/ecdsa/test_jacobi.py | 20 ++++++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/ecdsa/test_ecdsa.py b/src/ecdsa/test_ecdsa.py index dbc4a6eb..2af527b6 100644 --- a/src/ecdsa/test_ecdsa.py +++ b/src/ecdsa/test_ecdsa.py @@ -622,12 +622,12 @@ 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 = int(generator.order()) + order = int(generator.order()) - 1 key = draw(st.integers(min_value=1, max_value=order)) msg = draw(st.integers(min_value=1, max_value=order)) nonce = draw( - st.integers(min_value=1, max_value=order + 1) + st.integers(min_value=1, max_value=order) | st.integers(min_value=order >> 1, max_value=order) ) return generator, key, msg, nonce diff --git a/src/ecdsa/test_ellipticcurve.py b/src/ecdsa/test_ellipticcurve.py index 85faef4d..f46fd9ea 100644 --- a/src/ecdsa/test_ellipticcurve.py +++ b/src/ecdsa/test_ellipticcurve.py @@ -44,7 +44,7 @@ @settings(**HYP_SLOW_SETTINGS) -@given(st.integers(min_value=1, max_value=r + 1)) +@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 index 1f528048..71fb33e5 100644 --- a/src/ecdsa/test_jacobi.py +++ b/src/ecdsa/test_jacobi.py @@ -202,7 +202,7 @@ def test_compare_double_with_multiply(self): @settings(max_examples=10) @given( st.integers( - min_value=0, max_value=int(generator_brainpoolp160r1.order()) + min_value=0, max_value=int(generator_brainpoolp160r1.order() - 1) ) ) def test_multiplications(self, mul): @@ -217,7 +217,7 @@ def test_multiplications(self, mul): @settings(max_examples=10) @given( st.integers( - min_value=0, max_value=int(generator_brainpoolp160r1.order()) + min_value=0, max_value=int(generator_brainpoolp160r1.order() - 1) ) ) @example(0) @@ -235,10 +235,10 @@ def test_precompute(self, mul): @settings(max_examples=10) @given( st.integers( - min_value=1, max_value=int(generator_brainpoolp160r1.order()) + min_value=1, max_value=int(generator_brainpoolp160r1.order() - 1) ), st.integers( - min_value=1, max_value=int(generator_brainpoolp160r1.order()) + min_value=1, max_value=int(generator_brainpoolp160r1.order() - 1) ), ) @example(3, 3) @@ -254,10 +254,10 @@ def test_add_scaled_points(self, a_mul, b_mul): @settings(max_examples=10) @given( st.integers( - min_value=1, max_value=int(generator_brainpoolp160r1.order()) + min_value=1, max_value=int(generator_brainpoolp160r1.order() - 1) ), st.integers( - min_value=1, max_value=int(generator_brainpoolp160r1.order()) + min_value=1, max_value=int(generator_brainpoolp160r1.order() - 1) ), st.integers(min_value=1, max_value=int(curve_brainpoolp160r1.p() - 1)), ) @@ -286,10 +286,10 @@ def test_add_one_scaled_point(self, a_mul, b_mul, new_z): @settings(max_examples=10) @given( st.integers( - min_value=1, max_value=int(generator_brainpoolp160r1.order()) + min_value=1, max_value=int(generator_brainpoolp160r1.order() - 1) ), st.integers( - min_value=1, max_value=int(generator_brainpoolp160r1.order()) + min_value=1, max_value=int(generator_brainpoolp160r1.order() - 1) ), st.integers(min_value=1, max_value=int(curve_brainpoolp160r1.p() - 1)), ) @@ -351,10 +351,10 @@ def test_add_same_scale_points_static(self): @settings(max_examples=14) @given( st.integers( - min_value=1, max_value=int(generator_brainpoolp160r1.order()) + min_value=1, max_value=int(generator_brainpoolp160r1.order() - 1) ), st.integers( - min_value=1, max_value=int(generator_brainpoolp160r1.order()) + min_value=1, max_value=int(generator_brainpoolp160r1.order() - 1) ), st.lists( st.integers( From 0cdde88b6ffc208ecf20d5a5f39e3577d925445c Mon Sep 17 00:00:00 2001 From: LGTM Migrator Date: Wed, 9 Nov 2022 07:45:48 +0000 Subject: [PATCH 003/105] Add CodeQL workflow for GitHub code scanning --- .github/workflows/codeql.yml | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..c671375c --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,41 @@ +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: "59 21 * * 0" + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ python ] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + queries: +security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{ matrix.language }}" From 0edcc812994df273b86f815db66476c22a67969f Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Feb 2023 22:37:29 +0100 Subject: [PATCH 004/105] Drop support for python 3.3 and python 3.4 As Github actions is dropping support for Ubuntu-18.04 images and those are the only ones that support easy execution of python 3.3 and python 3.4 environments, support for py3.3 and 3.4 would be hard to ensure. For old systems python 2.6 and 2.7 support will remain. --- .github/workflows/ci.yml | 8 -------- README.md | 2 +- build-requirements-3.3.txt | 9 --------- build-requirements-3.4.txt | 7 ------- setup.py | 4 +--- src/ecdsa/_compat.py | 25 +++++-------------------- tox.ini | 12 +++--------- 7 files changed, 10 insertions(+), 57 deletions(-) delete mode 100644 build-requirements-3.3.txt delete mode 100644 build-requirements-3.4.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 112719c0..44c169ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,14 +47,6 @@ jobs: os: ubuntu-18.04 python-version: 2.7 tox-env: gmpy2py27 - - name: py3.3 - os: ubuntu-18.04 - python-version: 3.3 - tox-env: py33 - - name: py3.4 - os: ubuntu-18.04 - python-version: 3.4 - tox-env: py34 - name: py3.5 os: ubuntu-18.04 python-version: 3.5 diff --git a/README.md b/README.md index 11a00160..7cffeb17 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ curves over prime fields. ## Dependencies 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 alternative +Python 2.6, 2.7, and 3.5+. It also supports execution on alternative implementations like pypy and pypy3. If `gmpy2` or `gmpy` is installed, they will be used for faster arithmetic. diff --git a/build-requirements-3.3.txt b/build-requirements-3.3.txt deleted file mode 100644 index d817d2b3..00000000 --- a/build-requirements-3.3.txt +++ /dev/null @@ -1,9 +0,0 @@ -git+https://github.com/tomato42/coveralls-python.git@add-py26#egg=coveralls -pluggy<0.6 -tox<3 -wheel<0.30 -virtualenv==15.2.0 -enum34 -hypothesis<3.44 -coverage<5.0 -urllib3<=1.25.8 diff --git a/build-requirements-3.4.txt b/build-requirements-3.4.txt deleted file mode 100644 index ee734d11..00000000 --- a/build-requirements-3.4.txt +++ /dev/null @@ -1,7 +0,0 @@ -tox -git+https://github.com/tomato42/coveralls-python.git@add-py26#egg=coveralls -hypothesis -pytest>=4.6.0 -PyYAML<5.3 -coverage -attrs<21 diff --git a/setup.py b/setup.py index f6a1dd1e..6294117a 100755 --- a/setup.py +++ b/setup.py @@ -27,15 +27,13 @@ package_dir={"": "src"}, license="MIT", cmdclass=commands, - python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*", + python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", classifiers=[ "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", diff --git a/src/ecdsa/_compat.py b/src/ecdsa/_compat.py index 83d41a5f..f409a1a2 100644 --- a/src/ecdsa/_compat.py +++ b/src/ecdsa/_compat.py @@ -91,28 +91,13 @@ def int_to_bytes(val, length=None, byteorder="big"): raise ValueError("Only 'big' or 'little' endian supported") else: - if sys.version_info < (3, 4): # pragma: no branch - # on python 3.3 hmac.hmac.update() accepts only bytes, on newer - # versions it does accept memoryview() also - def hmac_compat(data): - if not isinstance(data, bytes): # pragma: no branch - return bytes(data) - return data - - def normalise_bytes(buffer_object): - """Cast the input into array of bytes.""" - if not buffer_object: - return b"" - return memoryview(buffer_object).cast("B") - else: - - def hmac_compat(data): - return data + def hmac_compat(data): + return data - def normalise_bytes(buffer_object): - """Cast the input into array of bytes.""" - return memoryview(buffer_object).cast("B") + def normalise_bytes(buffer_object): + """Cast the input into array of bytes.""" + return memoryview(buffer_object).cast("B") def compat26_str(val): return val diff --git a/tox.ini b/tox.ini index 46864866..390b7373 100644 --- a/tox.ini +++ b/tox.ini @@ -1,18 +1,13 @@ [tox] -envlist = py26, py27, py33, py34, py35, py36, py37, py38, py39, py310, py311, py, pypy, pypy3, gmpy2py27, gmpy2py39, gmpy2py310, gmpypy27, gmpypy39, gmpypy310, codechecks +envlist = py26, py27, py35, py36, py37, py38, py39, py310, py311, py, pypy, pypy3, gmpy2py27, gmpy2py39, gmpy2py310, gmpypy27, gmpypy39, gmpypy310, codechecks [testenv] deps = - py{33}: py<1.5 - py{33}: pytest<3.3 - py{33}: enum34 - py{33}: hypothesis<3.44 py{26}: unittest2 py{26}: hypothesis<3 - py{34}: attrs<21 - py{26,27,34,35,36,37,38,39,310,311,py,py3}: pytest - py{27,34,35,36,37,38,39,310,311,py,py3}: hypothesis + py{26,27,35,36,37,38,39,310,311,py,py3}: pytest + py{27,35,36,37,38,39,310,311,py,py3}: hypothesis gmpy2py{27,39,310,311}: gmpy2 gmpypy{27,39,310,311}: gmpy gmpy{2py27,2py39,2py310,2py311,py27,py39,py310,py311}: pytest @@ -31,7 +26,6 @@ deps = py27_old_gmpy2: hypothesis py: pytest py: hypothesis - py{33}: wheel<0.30 coverage commands = coverage run --branch -m pytest {posargs:src/ecdsa} From 7a0101901a3704a997fcfabdb34b34b887aa0bf2 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Feb 2023 22:39:59 +0100 Subject: [PATCH 005/105] upgrade containers to ubuntu-20.04 as ubuntu-18.04 is no longer supported, we need to start using newer version --- .github/workflows/ci.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44c169ea..f16d881c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,35 +24,35 @@ jobs: python-version: "3.10" tox-env: py310 - name: py2.7 - os: ubuntu-18.04 + os: ubuntu-20.04 python-version: 2.7 tox-env: py27 - name: py2.7 with old gmpy - os: ubuntu-18.04 + os: ubuntu-20.04 python-version: 2.7 tox-env: py27_old_gmpy - name: py2.7 with old gmpy2 - os: ubuntu-18.04 + os: ubuntu-20.04 python-version: 2.7 tox-env: py27_old_gmpy2 - name: py2.7 with old six - os: ubuntu-18.04 + os: ubuntu-20.04 python-version: 2.7 tox-env: py27_old_six - name: py2.7 with gmpy - os: ubuntu-18.04 + os: ubuntu-20.04 python-version: 2.7 tox-env: gmpypy27 - name: py2.7 with gmpy2 - os: ubuntu-18.04 + os: ubuntu-20.04 python-version: 2.7 tox-env: gmpy2py27 - name: py3.5 - os: ubuntu-18.04 + os: ubuntu-20.04 python-version: 3.5 tox-env: py35 - name: py3.6 - os: ubuntu-18.04 + os: ubuntu-20.04 python-version: 3.6 tox-env: py36 - name: py3.7 @@ -93,7 +93,7 @@ jobs: tox-env: pypy3 # special configurations - name: py2.7 with instrumental - os: ubuntu-18.04 + os: ubuntu-20.04 python-version: 2.7 opt-deps: ['instrumental'] - name: code checks From c2d9fe1d8cc1daaa2036751fc3a41b9ec7b7e140 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Feb 2023 22:41:24 +0100 Subject: [PATCH 006/105] use release version of python 3.11 for testing --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f16d881c..976359a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,7 +81,7 @@ jobs: tox-env: gmpy2py310 - name: py3.11 os: ubuntu-latest - python-version: '3.11.0-beta.3' + python-version: '3.11' tox-env: py311 - name: pypy os: ubuntu-latest From 5a114fddcc4c336d53180270d81d6aa42c5ee95b Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 24 Feb 2023 15:41:11 +0100 Subject: [PATCH 007/105] add testing on python 3.12 # Conflicts: # tox.ini --- .github/workflows/ci.yml | 4 ++++ setup.py | 1 + tox.ini | 20 +++++++++++++------- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 976359a8..8e2920c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,6 +83,10 @@ jobs: os: ubuntu-latest python-version: '3.11' tox-env: py311 + - name: py3.12 + os: ubuntu-latest + python-version: '3.12.0-alpha.5' + tox-env: py312 - name: pypy os: ubuntu-latest python-version: pypy-2.7 diff --git a/setup.py b/setup.py index 6294117a..a9ae2439 100755 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ], install_requires=["six>=1.9.0"], extras_require={"gmpy2": "gmpy2", "gmpy": "gmpy"}, diff --git a/tox.ini b/tox.ini index 390b7373..c9b07c9a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,17 +1,17 @@ [tox] -envlist = py26, py27, py35, py36, py37, py38, py39, py310, py311, py, pypy, pypy3, gmpy2py27, gmpy2py39, gmpy2py310, gmpypy27, gmpypy39, gmpypy310, codechecks +envlist = py26, py27, py35, py36, py37, py38, py39, py310, py311, py312, py, pypy, pypy3, gmpy2py27, gmpy2py39, gmpy2py310, gmpypy27, gmpypy39, gmpypy310, codechecks [testenv] deps = py{26}: unittest2 py{26}: hypothesis<3 - py{26,27,35,36,37,38,39,310,311,py,py3}: pytest - py{27,35,36,37,38,39,310,311,py,py3}: hypothesis - gmpy2py{27,39,310,311}: gmpy2 - gmpypy{27,39,310,311}: gmpy - gmpy{2py27,2py39,2py310,2py311,py27,py39,py310,py311}: pytest - gmpy{2py27,2py39,2py310,2py311,py27,py39,py310,py311}: hypothesis + py{26,27,35,36,37,38,39,310,311,312,py,py3}: pytest + py{27,35,36,37,38,39,310,311,312,py,py3}: hypothesis + gmpy2py{27,39,310,311,312}: gmpy2 + gmpypy{27,39,310,311,312}: gmpy + gmpy{2py27,2py39,2py310,2py311,2py312,py27,py39,py310,py311,py312}: pytest + gmpy{2py27,2py39,2py310,2py311,2py312,py27,py39,py310,py311,py312}: hypothesis # six==1.9.0 comes from setup.py install_requires py27_old_six: six==1.9.0 py27_old_six: pytest @@ -50,6 +50,9 @@ basepython=python3.10 [testenv:gmpypy311] basepython=python3.11 +[testenv:gmpypy312] +basepython=python3.12 + [testenv:gmpy2py27] basepython=python2.7 @@ -62,6 +65,9 @@ basepython=python3.10 [testenv:gmpy2py311] basepython=python3.11 +[testenv:gmpy2py312] +basepython=python3.12 + [testenv:instrumental] basepython = python2.7 deps = From f10e6d9be1251f9a109c0c2002ed12fee818b883 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Feb 2023 22:57:35 +0100 Subject: [PATCH 008/105] skip tests breaking coverage on pypy --- src/ecdsa/test_jacobi.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/ecdsa/test_jacobi.py b/src/ecdsa/test_jacobi.py index 71fb33e5..b3e69c00 100644 --- a/src/ecdsa/test_jacobi.py +++ b/src/ecdsa/test_jacobi.py @@ -558,8 +558,12 @@ def test_pickle(self): self.assertEqual(pickle.loads(pickle.dumps(pj)), pj) @settings(**NO_OLD_SETTINGS) + @pytest.mark.skipif( + platform.python_implementation() == "PyPy", + reason="threading on PyPy breaks coverage", + ) @given(st.integers(min_value=1, max_value=10)) - def test_multithreading(self, thread_num): + def test_multithreading(self, thread_num): # pragma: no cover # ensure that generator's precomputation table is filled generator_112r2 * 2 @@ -592,10 +596,12 @@ def runner(generator): ) @pytest.mark.skipif( - platform.system() == "Windows", - reason="there are no signals on Windows", + platform.system() == "Windows" + or platform.python_implementation() == "PyPy", + reason="there are no signals on Windows, and threading breaks coverage" + " on PyPy", ) - def test_multithreading_with_interrupts(self): + def test_multithreading_with_interrupts(self): # pragma: no cover thread_num = 10 # ensure that generator's precomputation table is filled generator_112r2 * 2 From 50c1fa44f35b9d383ffbc3e045a94419f6c347d1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Feb 2023 23:03:24 +0100 Subject: [PATCH 009/105] use recommended convention for testing that no warnings were raised --- src/ecdsa/test_der.py | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/ecdsa/test_der.py b/src/ecdsa/test_der.py index 0ca5bd7f..833d12d8 100644 --- a/src/ecdsa/test_der.py +++ b/src/ecdsa/test_der.py @@ -144,26 +144,22 @@ def test_old_call_convention(self): def test_new_call_convention(self): """This is how it should be called now.""" - warnings.simplefilter("always") - with pytest.warns(None) as warns: + # make sure no warnings are raised + with warnings.catch_warnings(): + warnings.simplefilter("error") der = encode_bitstring(b"\xff", 0) - # verify that new call convention doesn't raise Warnings - self.assertEqual(len(warns), 0) - self.assertEqual(der, b"\x03\x02\x00\xff") def test_implicit_unused_bits(self): """ Writing bit string with already included the number of unused bits. """ - warnings.simplefilter("always") - with pytest.warns(None) as warns: + # make sure no warnings are raised + with warnings.catch_warnings(): + warnings.simplefilter("error") der = encode_bitstring(b"\x00\xff", None) - # verify that new call convention doesn't raise Warnings - self.assertEqual(len(warns), 0) - self.assertEqual(der, b"\x03\x02\x00\xff") def test_explicit_unused_bits(self): @@ -203,22 +199,20 @@ def test_old_call_convention(self): self.assertEqual(rest, b"") def test_new_call_convention(self): - warnings.simplefilter("always") - with pytest.warns(None) as warns: + # make sure no warnings are raised + with warnings.catch_warnings(): + warnings.simplefilter("error") bits, rest = remove_bitstring(b"\x03\x02\x00\xff", 0) - self.assertEqual(len(warns), 0) - self.assertEqual(bits, b"\xff") self.assertEqual(rest, b"") def test_implicit_unexpected_unused(self): - warnings.simplefilter("always") - with pytest.warns(None) as warns: + # make sure no warnings are raised + with warnings.catch_warnings(): + warnings.simplefilter("error") bits, rest = remove_bitstring(b"\x03\x02\x00\xff", None) - self.assertEqual(len(warns), 0) - self.assertEqual(bits, (b"\xff", 0)) self.assertEqual(rest, b"") From 62f8e7c2b647eb526bd55e945ca0c01dae09ea96 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 24 Feb 2023 15:10:26 +0100 Subject: [PATCH 010/105] test_jacobi: make coverage realistic Since it's a test case, only one course of action is actually expected so there is no branch at this point, even if the with: never returns --- src/ecdsa/test_jacobi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ecdsa/test_jacobi.py b/src/ecdsa/test_jacobi.py index b3e69c00..322b568c 100644 --- a/src/ecdsa/test_jacobi.py +++ b/src/ecdsa/test_jacobi.py @@ -50,7 +50,7 @@ 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): + with self.assertRaises(ValueError): # pragma: no branch p_a + p_b def test_compare_different_curves(self): From 27db17c27427b0d852b5edfc5ddc98bfc56274f9 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 24 Feb 2023 15:57:43 +0100 Subject: [PATCH 011/105] test_numbertheory: make test coverage realistic --- src/ecdsa/test_numbertheory.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ecdsa/test_numbertheory.py b/src/ecdsa/test_numbertheory.py index 8bc787f1..239eaa8c 100644 --- a/src/ecdsa/test_numbertheory.py +++ b/src/ecdsa/test_numbertheory.py @@ -182,7 +182,7 @@ def st_comp_with_com_fac(draw): # select at most 20 lists (returned numbers), # each having at most 30 primes (factors) including none (then the number # will be 1) - comp_primes = draw( + comp_primes = draw( # pragma: no branch st.integers(min_value=1, max_value=20).flatmap( lambda n: st.lists( st.lists(st.sampled_from(primes), max_size=30), @@ -225,7 +225,7 @@ def st_comp_no_com_fac(draw): # select at most 20 lists, each having at most 30 primes # selected from the leftover_primes list - number_primes = draw( + number_primes = draw( # pragma: no branch st.integers(min_value=1, max_value=20).flatmap( lambda n: st.lists( st.lists(st.sampled_from(leftover_primes), max_size=30), From cc26d74b5a5cdc5c0134a4bfc56e604a0e9e00a9 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 24 Feb 2023 16:01:51 +0100 Subject: [PATCH 012/105] test_pyecdsa: make coverage realistic we want to test both with and without openssl command present --- src/ecdsa/test_pyecdsa.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index d61f5083..59c876cc 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -1359,12 +1359,12 @@ def do_test_to_openssl(self, curve, hash_name="SHA1"): OPENSSL_SUPPORTED_TYPES = set() try: if "-rawin" in run_openssl("pkeyutl -help"): - OPENSSL_SUPPORTED_TYPES = set( + OPENSSL_SUPPORTED_TYPES = set( # pragma: no branch c.lower() for c in ("ED25519", "ED448") if c in run_openssl("list -public-key-methods") ) - except SubprocessError: + except SubprocessError: # pragma: no cover pass def do_eddsa_test_to_openssl(self, curve): From 5c490fe7f86823cf2c918780dc8c49a766529a68 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 24 Feb 2023 22:59:06 +0100 Subject: [PATCH 013/105] test_malformed_sigs: make sure variable is initialised --- src/ecdsa/test_malformed_sigs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ecdsa/test_malformed_sigs.py b/src/ecdsa/test_malformed_sigs.py index 8e1b611d..d5e5001a 100644 --- a/src/ecdsa/test_malformed_sigs.py +++ b/src/ecdsa/test_malformed_sigs.py @@ -111,6 +111,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 + xors = None if sig: # pragma: no branch xors = draw( st.dictionaries( From 3a4704c143a0aa48ec840c7b90c918a74235dacc Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 24 Feb 2023 23:05:04 +0100 Subject: [PATCH 014/105] remove duplicate `intersphinx_mapping` in docs gen config --- docs/source/conf.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 0eaea411..0581c1c9 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -68,5 +68,3 @@ "undoc-members": True, "inherited-members": True, } - -intersphinx_mapping = {"https://docs.python.org/": None} From afcd9d777bdfbb3fa0defe5300183b5bf1a06724 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 24 Feb 2023 23:11:48 +0100 Subject: [PATCH 015/105] don't use lambdas as simple wrappers --- src/ecdsa/test_keys.py | 4 ++-- src/ecdsa/test_malformed_sigs.py | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py index 25386b17..f9dbcad1 100644 --- a/src/ecdsa/test_keys.py +++ b/src/ecdsa/test_keys.py @@ -769,8 +769,8 @@ def test_deterministic_sign_that_rises_S_zero_error(self): verifiers = [] for modifier, fun in [ ("bytes", lambda x: x), - ("bytes memoryview", lambda x: buffer(x)), - ("bytearray", lambda x: bytearray(x)), + ("bytes memoryview", buffer), + ("bytearray", bytearray), ("bytearray memoryview", lambda x: buffer(bytearray(x))), ("array.array of bytes", lambda x: array.array("B", x)), ("array.array of bytes memoryview", lambda x: buffer(array.array("B", x))), diff --git a/src/ecdsa/test_malformed_sigs.py b/src/ecdsa/test_malformed_sigs.py index d5e5001a..6db0176a 100644 --- a/src/ecdsa/test_malformed_sigs.py +++ b/src/ecdsa/test_malformed_sigs.py @@ -289,15 +289,13 @@ def st_der(): | st_der_octet_string(max_size=1024**2) | st_der_null() | st_der_oid(), - lambda children: st.builds( - lambda x: encode_octet_string(x), st.one_of(children) - ) + lambda children: st.builds(encode_octet_string, st.one_of(children)) | st.builds(lambda x: encode_bitstring(x, 0), st.one_of(children)) | st.builds( lambda x: encode_sequence(*x), st.lists(children, max_size=200) ) | st.builds( - lambda tag, x: encode_constructed(tag, x), + encode_constructed, st.integers(min_value=0, max_value=0x3F), st.one_of(children), ), From fd8b23950c0523c784333870dd25178c98154af0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 24 Feb 2023 23:25:02 +0100 Subject: [PATCH 016/105] don't mix import styles for imports from module --- src/ecdsa/test_pyecdsa.py | 102 +++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 46 deletions(-) diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index 59c876cc..edefb823 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -10,7 +10,6 @@ import subprocess import pytest from binascii import hexlify, unhexlify -from hashlib import sha1, sha256, sha384, sha512 import hashlib from functools import partial @@ -97,11 +96,16 @@ def test_deterministic(self): data = b("blahblah") secexp = int("9d0219792467d7d37b4d43298a7d0c05", 16) - priv = SigningKey.from_secret_exponent(secexp, SECP256k1, sha256) + priv = SigningKey.from_secret_exponent( + secexp, SECP256k1, hashlib.sha256 + ) pub = priv.get_verifying_key() k = rfc6979.generate_k( - SECP256k1.generator.order(), secexp, sha256, sha256(data).digest() + SECP256k1.generator.order(), + secexp, + hashlib.sha256, + hashlib.sha256(data).digest(), ) sig1 = priv.sign(data, k=k) @@ -110,7 +114,7 @@ def test_deterministic(self): sig2 = priv.sign(data, k=k) self.assertTrue(pub.verify(sig2, data)) - sig3 = priv.sign_deterministic(data, sha256) + sig3 = priv.sign_deterministic(data, hashlib.sha256) self.assertTrue(pub.verify(sig3, data)) self.assertEqual(sig1, sig2) @@ -601,27 +605,29 @@ def test_sign_with_too_long_hash(self): sk.sign_digest(b("\xff") * 64) def test_hashfunc(self): - sk = SigningKey.generate(curve=NIST256p, hashfunc=sha256) + sk = SigningKey.generate(curve=NIST256p, hashfunc=hashlib.sha256) data = b("security level is 128 bits") sig = sk.sign(data) vk = VerifyingKey.from_string( - sk.get_verifying_key().to_string(), curve=NIST256p, hashfunc=sha256 + sk.get_verifying_key().to_string(), + curve=NIST256p, + hashfunc=hashlib.sha256, ) self.assertTrue(vk.verify(sig, data)) sk2 = SigningKey.generate(curve=NIST256p) - sig2 = sk2.sign(data, hashfunc=sha256) + sig2 = sk2.sign(data, hashfunc=hashlib.sha256) vk2 = VerifyingKey.from_string( sk2.get_verifying_key().to_string(), curve=NIST256p, - hashfunc=sha256, + hashfunc=hashlib.sha256, ) self.assertTrue(vk2.verify(sig2, data)) vk3 = VerifyingKey.from_string( sk.get_verifying_key().to_string(), curve=NIST256p ) - self.assertTrue(vk3.verify(sig, data, hashfunc=sha256)) + self.assertTrue(vk3.verify(sig, data, hashfunc=hashlib.sha256)) def test_public_key_recovery(self): # Create keys @@ -660,7 +666,7 @@ def test_public_key_recovery_with_custom_hash(self): # Create keys curve = BRAINPOOLP160r1 - sk = SigningKey.generate(curve=curve, hashfunc=sha256) + sk = SigningKey.generate(curve=curve, hashfunc=hashlib.sha256) vk = sk.get_verifying_key() # Sign a message @@ -669,7 +675,11 @@ def test_public_key_recovery_with_custom_hash(self): # Recover verifying keys recovered_vks = VerifyingKey.from_public_key_recovery( - signature, data, curve, hashfunc=sha256, allow_truncate=True + signature, + data, + curve, + hashfunc=hashlib.sha256, + allow_truncate=True, ) # Test if each pk is valid @@ -679,7 +689,7 @@ def test_public_key_recovery_with_custom_hash(self): # Test if properties are equal self.assertEqual(vk.curve, recovered_vk.curve) - self.assertEqual(sha256, recovered_vk.default_hashfunc) + self.assertEqual(hashlib.sha256, recovered_vk.default_hashfunc) # Test if original vk is the list of recovered keys self.assertIn( @@ -1656,8 +1666,8 @@ def test_SECP256k1(self): self._do( generator=SECP256k1.generator, secexp=int("9d0219792467d7d37b4d43298a7d0c05", 16), - hsh=sha256(b("sample")).digest(), - hash_func=sha256, + hsh=hashlib.sha256(b("sample")).digest(), + hash_func=hashlib.sha256, expected=int( "8fa1f95d514760e498f28957b824ee6ec39ed64826ff4fecc2b5739ec45b91cd", 16, @@ -1671,8 +1681,8 @@ def test_SECP256k1_2(self): "cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50", 16, ), - hsh=sha256(b("sample")).digest(), - hash_func=sha256, + hsh=hashlib.sha256(b("sample")).digest(), + hash_func=hashlib.sha256, expected=int( "2df40ca70e639d89528a6b670d9d48d9165fdc0febc0974056bdce192b8e16a3", 16, @@ -1683,8 +1693,8 @@ def test_SECP256k1_3(self): self._do( generator=SECP256k1.generator, secexp=0x1, - hsh=sha256(b("Satoshi Nakamoto")).digest(), - hash_func=sha256, + hsh=hashlib.sha256(b("Satoshi Nakamoto")).digest(), + hash_func=hashlib.sha256, expected=0x8F8A276C19F4149656B280621E358CCE24F5F52542772691EE69063B74F15D15, ) @@ -1692,12 +1702,12 @@ def test_SECP256k1_4(self): self._do( generator=SECP256k1.generator, secexp=0x1, - hsh=sha256( + hsh=hashlib.sha256( b( "All those moments will be lost in time, like tears in rain. Time to die..." ) ).digest(), - hash_func=sha256, + hash_func=hashlib.sha256, expected=0x38AA22D72376B4DBC472E06C3BA403EE0A394DA63FC58D88686C611ABA98D6B3, ) @@ -1705,8 +1715,8 @@ def test_SECP256k1_5(self): self._do( generator=SECP256k1.generator, secexp=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140, - hsh=sha256(b("Satoshi Nakamoto")).digest(), - hash_func=sha256, + hsh=hashlib.sha256(b("Satoshi Nakamoto")).digest(), + hash_func=hashlib.sha256, expected=0x33A19B60E25FB6F4435AF53A3D42D493644827367E6453928554F43E49AA6F90, ) @@ -1714,8 +1724,8 @@ def test_SECP256k1_6(self): self._do( generator=SECP256k1.generator, secexp=0xF8B8AF8CE3C7CCA5E300D33939540C10D45CE001B8F252BFBC57BA0342904181, - hsh=sha256(b("Alan Turing")).digest(), - hash_func=sha256, + hsh=hashlib.sha256(b("Alan Turing")).digest(), + hash_func=hashlib.sha256, expected=0x525A82B70E67874398067543FD84C83D30C175FDC45FDEEE082FE13B1D7CFDF1, ) @@ -1734,7 +1744,7 @@ def test_1(self): "AF2BDBE1AA9B6EC1E2ADE1D694F41FC71A831D0268E9891562113D8A62ADD1BF" ) ), - hash_func=sha256, + hash_func=hashlib.sha256, expected=int("23AF4074C90A02B3FE61D286D5C87F425E6BDD81B", 16), ) @@ -1742,8 +1752,8 @@ def test_2(self): self._do( generator=NIST192p.generator, secexp=int("6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4", 16), - hsh=sha1(b("sample")).digest(), - hash_func=sha1, + hsh=hashlib.sha1(b("sample")).digest(), + hash_func=hashlib.sha1, expected=int( "37D7CA00D2C7B0E5E412AC03BD44BA837FDD5B28CD3B0021", 16 ), @@ -1753,8 +1763,8 @@ def test_3(self): self._do( generator=NIST192p.generator, secexp=int("6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4", 16), - hsh=sha256(b("sample")).digest(), - hash_func=sha256, + hsh=hashlib.sha256(b("sample")).digest(), + hash_func=hashlib.sha256, expected=int( "32B1B6D7D42A05CB449065727A84804FB1A3E34D8F261496", 16 ), @@ -1764,8 +1774,8 @@ def test_4(self): self._do( generator=NIST192p.generator, secexp=int("6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4", 16), - hsh=sha512(b("sample")).digest(), - hash_func=sha512, + hsh=hashlib.sha512(b("sample")).digest(), + hash_func=hashlib.sha512, expected=int( "A2AC7AB055E4F20692D49209544C203A7D1F2C0BFBC75DB1", 16 ), @@ -1775,8 +1785,8 @@ def test_5(self): self._do( generator=NIST192p.generator, secexp=int("6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4", 16), - hsh=sha1(b("test")).digest(), - hash_func=sha1, + hsh=hashlib.sha1(b("test")).digest(), + hash_func=hashlib.sha1, expected=int( "D9CF9C3D3297D3260773A1DA7418DB5537AB8DD93DE7FA25", 16 ), @@ -1786,8 +1796,8 @@ def test_6(self): self._do( generator=NIST192p.generator, secexp=int("6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4", 16), - hsh=sha256(b("test")).digest(), - hash_func=sha256, + hsh=hashlib.sha256(b("test")).digest(), + hash_func=hashlib.sha256, expected=int( "5C4CE89CF56D9E7C77C8585339B006B97B5F0680B4306C6C", 16 ), @@ -1797,8 +1807,8 @@ def test_7(self): self._do( generator=NIST192p.generator, secexp=int("6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4", 16), - hsh=sha512(b("test")).digest(), - hash_func=sha512, + hsh=hashlib.sha512(b("test")).digest(), + hash_func=hashlib.sha512, expected=int( "0758753A5254759C7CFBAD2E2D9B0792EEE44136C9480527", 16 ), @@ -1811,8 +1821,8 @@ def test_8(self): "0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538", 16, ), - hsh=sha1(b("sample")).digest(), - hash_func=sha1, + hsh=hashlib.sha1(b("sample")).digest(), + hash_func=hashlib.sha1, expected=int( "089C071B419E1C2820962321787258469511958E80582E95D8378E0C2CCDB3CB42BEDE42F50E3FA3C71F5A76724281D31D9C89F0F91FC1BE4918DB1C03A5838D0F9", 16, @@ -1826,8 +1836,8 @@ def test_9(self): "0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538", 16, ), - hsh=sha256(b("sample")).digest(), - hash_func=sha256, + hsh=hashlib.sha256(b("sample")).digest(), + hash_func=hashlib.sha256, expected=int( "0EDF38AFCAAECAB4383358B34D67C9F2216C8382AAEA44A3DAD5FDC9C32575761793FEF24EB0FC276DFC4F6E3EC476752F043CF01415387470BCBD8678ED2C7E1A0", 16, @@ -1841,8 +1851,8 @@ def test_10(self): "0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538", 16, ), - hsh=sha512(b("test")).digest(), - hash_func=sha512, + hsh=hashlib.sha512(b("test")).digest(), + hash_func=hashlib.sha512, expected=int( "16200813020EC986863BEDFC1B121F605C1215645018AEA1A7B215A564DE9EB1B38A67AA1128B80CE391C4FB71187654AAA3431027BFC7F395766CA988C964DC56D", 16, @@ -2206,7 +2216,7 @@ def test_brainpoolP512r1(self): "6FC98BD7E50211A4A27102FA3549DF79EBCB4BF246B80945CDDFE7D509BBFD7D", "9E56F509196784D963D1C0A401510EE7ADA3DCC5DEE04B154BF61AF1D5A6DECE", b"abc", - sha256, + hashlib.sha256, "CB28E0999B9C7715FD0A80D8E47A77079716CBBF917DD72E97566EA1C066957C", "86FA3BB4E26CAD5BF90B7F81899256CE7594BB1EA0C89212748BFF3B3D5B0315", NIST256p, @@ -2222,7 +2232,7 @@ def test_brainpoolP512r1(self): "B4B74E44D71A13D568003D7489908D564C7761E229C58CBFA18950096EB7463B" "854D7FA992F934D927376285E63414FA", b"abc", - sha384, + hashlib.sha384, "FB017B914E29149432D8BAC29A514640B46F53DDAB2C69948084E2930F1C8F7E" "08E07C9C63F2D21A07DCB56A6AF56EB3", "B263A1305E057F984D38726A1B46874109F417BCA112674C528262A40A629AF1" @@ -2244,7 +2254,7 @@ def test_brainpoolP512r1(self): "373778F9DE6B6497B1EF825FF24F42F9B4A4BD7382CFC3378A540B1B7F0C1B95" "6C2F", b"abc", - sha512, + hashlib.sha512, "0154FD3836AF92D0DCA57DD5341D3053988534FDE8318FC6AAAAB68E2E6F4339" "B19F2F281A7E0B22C269D93CF8794A9278880ED7DBB8D9362CAEACEE54432055" "2251", From 4b51ae695a050cc9b3d8bca63c20cf12abbc50a6 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 24 Feb 2023 23:29:22 +0100 Subject: [PATCH 017/105] explain why literal `!=` is used in asserts --- src/ecdsa/test_eddsa.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ecdsa/test_eddsa.py b/src/ecdsa/test_eddsa.py index 7a09ad7f..a125b94e 100644 --- a/src/ecdsa/test_eddsa.py +++ b/src/ecdsa/test_eddsa.py @@ -500,6 +500,7 @@ def test_equal_public_points(self): key2 = PublicKey(generator_ed25519, b"\x01" * 32) self.assertEqual(key1, key2) + # verify that `__ne__` works as expected self.assertFalse(key1 != key2) def test_unequal_public_points(self): @@ -519,6 +520,7 @@ def test_unequal_publickey_curves(self): key2 = PublicKey(generator_ed448, b"\x03" * 56 + b"\x00") self.assertNotEqual(key1, key2) + # verify that `__ne__` works as expected self.assertTrue(key1 != key2) def test_equal_private_keys(self): @@ -526,6 +528,7 @@ def test_equal_private_keys(self): key2 = PrivateKey(generator_ed25519, b"\x01" * 32) self.assertEqual(key1, key2) + # verify that `__ne__` works as expected self.assertFalse(key1 != key2) def test_unequal_private_keys(self): @@ -533,6 +536,7 @@ def test_unequal_private_keys(self): key2 = PrivateKey(generator_ed25519, b"\x02" * 32) self.assertNotEqual(key1, key2) + # verify that `__ne__` works as expected self.assertTrue(key1 != key2) def test_unequal_privatekey_to_string(self): From 7d18bf46b662f1c6f806c1f8d3ea6b9c5b88bbdb Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 24 Feb 2023 23:40:37 +0100 Subject: [PATCH 018/105] _rwlock: remove dead code --- src/ecdsa/_rwlock.py | 86 ------------------ src/ecdsa/test_rw_lock.py | 180 -------------------------------------- 2 files changed, 266 deletions(-) delete mode 100644 src/ecdsa/_rwlock.py delete mode 100644 src/ecdsa/test_rw_lock.py diff --git a/src/ecdsa/_rwlock.py b/src/ecdsa/_rwlock.py deleted file mode 100644 index 010e4981..00000000 --- a/src/ecdsa/_rwlock.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright Mateusz Kobos, (c) 2011 -# https://code.activestate.com/recipes/577803-reader-writer-lock-with-priority-for-writers/ -# released under the MIT licence - -import threading - - -__author__ = "Mateusz Kobos" - - -class RWLock: - """ - Read-Write locking primitive - - Synchronization object used in a solution of so-called second - readers-writers problem. In this problem, many readers can simultaneously - access a share, and a writer has an exclusive access to this share. - Additionally, the following constraints should be met: - 1) no reader should be kept waiting if the share is currently opened for - reading unless a writer is also waiting for the share, - 2) no writer should be kept waiting for the share longer than absolutely - necessary. - - The implementation is based on [1, secs. 4.2.2, 4.2.6, 4.2.7] - with a modification -- adding an additional lock (C{self.__readers_queue}) - -- in accordance with [2]. - - Sources: - [1] A.B. Downey: "The little book of semaphores", Version 2.1.5, 2008 - [2] P.J. Courtois, F. Heymans, D.L. Parnas: - "Concurrent Control with 'Readers' and 'Writers'", - Communications of the ACM, 1971 (via [3]) - [3] http://en.wikipedia.org/wiki/Readers-writers_problem - """ - - def __init__(self): - """ - A lock giving an even higher priority to the writer in certain - cases (see [2] for a discussion). - """ - self.__read_switch = _LightSwitch() - self.__write_switch = _LightSwitch() - self.__no_readers = threading.Lock() - self.__no_writers = threading.Lock() - self.__readers_queue = threading.Lock() - - def reader_acquire(self): - self.__readers_queue.acquire() - self.__no_readers.acquire() - self.__read_switch.acquire(self.__no_writers) - self.__no_readers.release() - self.__readers_queue.release() - - def reader_release(self): - self.__read_switch.release(self.__no_writers) - - def writer_acquire(self): - self.__write_switch.acquire(self.__no_readers) - self.__no_writers.acquire() - - def writer_release(self): - self.__no_writers.release() - self.__write_switch.release(self.__no_readers) - - -class _LightSwitch: - """An auxiliary "light switch"-like object. The first thread turns on the - "switch", the last one turns it off (see [1, sec. 4.2.2] for details).""" - - def __init__(self): - self.__counter = 0 - self.__mutex = threading.Lock() - - def acquire(self, lock): - self.__mutex.acquire() - self.__counter += 1 - if self.__counter == 1: - lock.acquire() - self.__mutex.release() - - def release(self, lock): - self.__mutex.acquire() - self.__counter -= 1 - if self.__counter == 0: - lock.release() - self.__mutex.release() diff --git a/src/ecdsa/test_rw_lock.py b/src/ecdsa/test_rw_lock.py deleted file mode 100644 index 0a84b9c7..00000000 --- a/src/ecdsa/test_rw_lock.py +++ /dev/null @@ -1,180 +0,0 @@ -# Copyright Mateusz Kobos, (c) 2011 -# https://code.activestate.com/recipes/577803-reader-writer-lock-with-priority-for-writers/ -# released under the MIT licence - -try: - import unittest2 as unittest -except ImportError: - import unittest -import threading -import time -import copy -from ._rwlock import RWLock - - -class Writer(threading.Thread): - def __init__( - self, buffer_, rw_lock, init_sleep_time, sleep_time, to_write - ): - """ - @param buffer_: common buffer_ shared by the readers and writers - @type buffer_: list - @type rw_lock: L{RWLock} - @param init_sleep_time: sleep time before doing any action - @type init_sleep_time: C{float} - @param sleep_time: sleep time while in critical section - @type sleep_time: C{float} - @param to_write: data that will be appended to the buffer - """ - threading.Thread.__init__(self) - self.__buffer = buffer_ - self.__rw_lock = rw_lock - self.__init_sleep_time = init_sleep_time - self.__sleep_time = sleep_time - self.__to_write = to_write - self.entry_time = None - """Time of entry to the critical section""" - self.exit_time = None - """Time of exit from the critical section""" - - def run(self): - time.sleep(self.__init_sleep_time) - self.__rw_lock.writer_acquire() - self.entry_time = time.time() - time.sleep(self.__sleep_time) - self.__buffer.append(self.__to_write) - self.exit_time = time.time() - self.__rw_lock.writer_release() - - -class Reader(threading.Thread): - def __init__(self, buffer_, rw_lock, init_sleep_time, sleep_time): - """ - @param buffer_: common buffer shared by the readers and writers - @type buffer_: list - @type rw_lock: L{RWLock} - @param init_sleep_time: sleep time before doing any action - @type init_sleep_time: C{float} - @param sleep_time: sleep time while in critical section - @type sleep_time: C{float} - """ - threading.Thread.__init__(self) - self.__buffer = buffer_ - self.__rw_lock = rw_lock - self.__init_sleep_time = init_sleep_time - self.__sleep_time = sleep_time - self.buffer_read = None - """a copy of a the buffer read while in critical section""" - self.entry_time = None - """Time of entry to the critical section""" - self.exit_time = None - """Time of exit from the critical section""" - - def run(self): - time.sleep(self.__init_sleep_time) - self.__rw_lock.reader_acquire() - self.entry_time = time.time() - time.sleep(self.__sleep_time) - self.buffer_read = copy.deepcopy(self.__buffer) - self.exit_time = time.time() - self.__rw_lock.reader_release() - - -class RWLockTestCase(unittest.TestCase): - def test_readers_nonexclusive_access(self): - (buffer_, rw_lock, threads) = self.__init_variables() - - threads.append(Reader(buffer_, rw_lock, 0, 0)) - threads.append(Writer(buffer_, rw_lock, 0.2, 0.4, 1)) - threads.append(Reader(buffer_, rw_lock, 0.3, 0.3)) - threads.append(Reader(buffer_, rw_lock, 0.5, 0)) - - self.__start_and_join_threads(threads) - - ## The third reader should enter after the second one but it should - ## exit before the second one exits - ## (i.e. the readers should be in the critical section - ## at the same time) - - self.assertEqual([], threads[0].buffer_read) - self.assertEqual([1], threads[2].buffer_read) - self.assertEqual([1], threads[3].buffer_read) - self.assertTrue(threads[1].exit_time <= threads[2].entry_time) - self.assertTrue(threads[2].entry_time <= threads[3].entry_time) - self.assertTrue(threads[3].exit_time < threads[2].exit_time) - - def test_writers_exclusive_access(self): - (buffer_, rw_lock, threads) = self.__init_variables() - - threads.append(Writer(buffer_, rw_lock, 0, 0.4, 1)) - threads.append(Writer(buffer_, rw_lock, 0.1, 0, 2)) - threads.append(Reader(buffer_, rw_lock, 0.2, 0)) - - self.__start_and_join_threads(threads) - - ## The second writer should wait for the first one to exit - - self.assertEqual([1, 2], threads[2].buffer_read) - self.assertTrue(threads[0].exit_time <= threads[1].entry_time) - self.assertTrue(threads[1].exit_time <= threads[2].exit_time) - - def test_writer_priority(self): - (buffer_, rw_lock, threads) = self.__init_variables() - - threads.append(Writer(buffer_, rw_lock, 0, 0, 1)) - threads.append(Reader(buffer_, rw_lock, 0.1, 0.4)) - threads.append(Writer(buffer_, rw_lock, 0.2, 0, 2)) - threads.append(Reader(buffer_, rw_lock, 0.3, 0)) - threads.append(Reader(buffer_, rw_lock, 0.3, 0)) - - self.__start_and_join_threads(threads) - - ## The second writer should go before the second and the third reader - - self.assertEqual([1], threads[1].buffer_read) - self.assertEqual([1, 2], threads[3].buffer_read) - self.assertEqual([1, 2], threads[4].buffer_read) - self.assertTrue(threads[0].exit_time < threads[1].entry_time) - self.assertTrue(threads[1].exit_time <= threads[2].entry_time) - self.assertTrue(threads[2].exit_time <= threads[3].entry_time) - self.assertTrue(threads[2].exit_time <= threads[4].entry_time) - - def test_many_writers_priority(self): - (buffer_, rw_lock, threads) = self.__init_variables() - - threads.append(Writer(buffer_, rw_lock, 0, 0, 1)) - threads.append(Reader(buffer_, rw_lock, 0.1, 0.6)) - threads.append(Writer(buffer_, rw_lock, 0.2, 0.1, 2)) - threads.append(Reader(buffer_, rw_lock, 0.3, 0)) - threads.append(Reader(buffer_, rw_lock, 0.4, 0)) - threads.append(Writer(buffer_, rw_lock, 0.5, 0.1, 3)) - - self.__start_and_join_threads(threads) - - ## The two last writers should go first -- after the first reader and - ## before the second and the third reader - - self.assertEqual([1], threads[1].buffer_read) - self.assertEqual([1, 2, 3], threads[3].buffer_read) - self.assertEqual([1, 2, 3], threads[4].buffer_read) - self.assertTrue(threads[0].exit_time < threads[1].entry_time) - self.assertTrue(threads[1].exit_time <= threads[2].entry_time) - self.assertTrue(threads[1].exit_time <= threads[5].entry_time) - self.assertTrue(threads[2].exit_time <= threads[3].entry_time) - self.assertTrue(threads[2].exit_time <= threads[4].entry_time) - self.assertTrue(threads[5].exit_time <= threads[3].entry_time) - self.assertTrue(threads[5].exit_time <= threads[4].entry_time) - - @staticmethod - def __init_variables(): - buffer_ = [] - rw_lock = RWLock() - threads = [] - return (buffer_, rw_lock, threads) - - @staticmethod - def __start_and_join_threads(threads): - for t in threads: - t.start() - for t in threads: - t.join() From d904c868e7ba2fa97f34a0c0bd108c727d0a9b34 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 24 Feb 2023 23:42:27 +0100 Subject: [PATCH 019/105] test_pyecdsa: remove unused variable --- src/ecdsa/test_pyecdsa.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index edefb823..b250677f 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -1378,8 +1378,6 @@ def do_test_to_openssl(self, curve, hash_name="SHA1"): pass def do_eddsa_test_to_openssl(self, curve): - curvename = curve.name.upper() - if os.path.isdir("t"): shutil.rmtree("t") os.mkdir("t") From 439e45271795b8c2ad4cec94c325258777ba0b41 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 25 Feb 2023 13:20:16 +0100 Subject: [PATCH 020/105] update badges for CodeQL --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 7cffeb17..fbb41a63 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,7 @@ [![Documentation Status](https://readthedocs.org/projects/ecdsa/badge/?version=latest)](https://ecdsa.readthedocs.io/en/latest/?badge=latest) [![Coverage Status](https://coveralls.io/repos/github/tlsfuzzer/python-ecdsa/badge.svg?branch=master)](https://coveralls.io/github/tlsfuzzer/python-ecdsa?branch=master) ![condition coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/tomato42/9b6ca1f3410207fbeca785a178781651/raw/python-ecdsa-condition-coverage.json) -[![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/tlsfuzzer/python-ecdsa.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/tlsfuzzer/python-ecdsa/context:python) -[![Total alerts](https://img.shields.io/lgtm/alerts/g/tlsfuzzer/python-ecdsa.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/tlsfuzzer/python-ecdsa/alerts/) +[![CodeQL](https://github.com/tlsfuzzer/python-ecdsa/actions/workflows/codeql.yml/badge.svg)](https://github.com/tlsfuzzer/python-ecdsa/actions/workflows/codeql.yml) [![Latest Version](https://img.shields.io/pypi/v/ecdsa.svg?style=flat)](https://pypi.python.org/pypi/ecdsa/) ![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat) From da44d14722fb317d0d26765bff66ca75ebeedd4c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 2 Mar 2023 01:57:40 +0100 Subject: [PATCH 021/105] test_malformed_sigs: no branches in strategy generators --- src/ecdsa/test_malformed_sigs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ecdsa/test_malformed_sigs.py b/src/ecdsa/test_malformed_sigs.py index 6db0176a..20912376 100644 --- a/src/ecdsa/test_malformed_sigs.py +++ b/src/ecdsa/test_malformed_sigs.py @@ -282,7 +282,7 @@ def st_der(): of a valid DER structure, sequence of valid DER objects or a constructed encoding of any of the above. """ - return st.recursive( + return st.recursive( # pragma: no branch st.just(b"") | st_der_integer(max_value=2**4096) | st_der_bit_string(max_size=1024**2) From 43a42b30669e90e0476482c41dad610e8c17591e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 2 Mar 2023 03:05:56 +0100 Subject: [PATCH 022/105] add test coverage for canonicalizing signature encoders --- src/ecdsa/test_pyecdsa.py | 82 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index b250677f..fc7e64fe 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -20,8 +20,14 @@ from .keys import SigningKey, VerifyingKey from .keys import BadSignatureError, MalformedPointError, BadDigestError from . import util -from .util import sigencode_der, sigencode_strings -from .util import sigdecode_der, sigdecode_strings +from .util import ( + sigencode_der, + sigencode_strings, + sigencode_strings_canonize, + sigencode_string_canonize, + sigencode_der_canonize, +) +from .util import sigdecode_der, sigdecode_strings, sigdecode_string from .util import number_to_string, encoded_oid_ecPublicKey, MalformedSignature from .curves import Curve, UnknownCurveError from .curves import ( @@ -451,6 +457,78 @@ def test_signature_strings(self): self.assertEqual(type(sig_der), binary_type) self.assertTrue(pub1.verify(sig_der, data, sigdecode=sigdecode_der)) + def test_sigencode_string_canonize_no_change(self): + r = 12 + s = 400 + order = SECP112r1.order + + new_r, new_s = sigdecode_string( + sigencode_string_canonize(r, s, order), order + ) + + self.assertEqual(r, new_r) + self.assertEqual(s, new_s) + + def test_sigencode_string_canonize(self): + r = 12 + order = SECP112r1.order + s = order - 10 + + new_r, new_s = sigdecode_string( + sigencode_string_canonize(r, s, order), order + ) + + self.assertEqual(r, new_r) + self.assertEqual(order - s, new_s) + + def test_sigencode_strings_canonize_no_change(self): + r = 12 + s = 400 + order = SECP112r1.order + + new_r, new_s = sigdecode_strings( + sigencode_strings_canonize(r, s, order), order + ) + + self.assertEqual(r, new_r) + self.assertEqual(s, new_s) + + def test_sigencode_strings_canonize(self): + r = 12 + order = SECP112r1.order + s = order - 10 + + new_r, new_s = sigdecode_strings( + sigencode_strings_canonize(r, s, order), order + ) + + self.assertEqual(r, new_r) + self.assertEqual(order - s, new_s) + + def test_sigencode_der_canonize_no_change(self): + r = 13 + s = 200 + order = SECP112r1.order + + new_r, new_s = sigdecode_der( + sigencode_der_canonize(r, s, order), order + ) + + self.assertEqual(r, new_r) + self.assertEqual(s, new_s) + + def test_sigencode_der_canonize(self): + r = 13 + order = SECP112r1.order + s = order - 14 + + new_r, new_s = sigdecode_der( + sigencode_der_canonize(r, s, order), order + ) + + self.assertEqual(r, new_r) + self.assertEqual(order - s, new_s) + def test_sig_decode_strings_with_invalid_count(self): with self.assertRaises(MalformedSignature): sigdecode_strings([b("one"), b("two"), b("three")], 0xFF) From 98312f650ed8f5186230a66355f733b043e11b93 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 2 Mar 2023 03:06:53 +0100 Subject: [PATCH 023/105] document sigencoding and sigdecoding functions --- src/ecdsa/util.py | 122 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 104 insertions(+), 18 deletions(-) diff --git a/src/ecdsa/util.py b/src/ecdsa/util.py index 9a561105..2477f7e2 100644 --- a/src/ecdsa/util.py +++ b/src/ecdsa/util.py @@ -1,3 +1,16 @@ +""" +This module includes some utility functions. + +The methods most typically used are the sigencode and sigdecode functions +to be used with :func:`~ecdsa.keys.SigningKey.sign` and +:func:`~ecdsa.keys.VerifyingKey.verify` +respectively. See the :func:`sigencode_strings`, :func:`sigdecode_string`, +:func:`sigencode_der`, :func:`sigencode_strings_canonize`, +:func:`sigencode_string_canonize`, :func:`sigencode_der_canonize`, +:func:`sigdecode_strings`, :func:`sigdecode_string`, and +:func:`sigdecode_der` functions. +""" + from __future__ import division import os @@ -9,6 +22,7 @@ from . import der from ._compat import normalise_bytes + # RFC5480: # The "unrestricted" algorithm identifier is: # id-ecPublicKey OBJECT IDENTIFIER ::= { @@ -221,12 +235,24 @@ def string_to_number_fixedlen(string, order): return int(binascii.hexlify(string), 16) -# these methods are useful for the sigencode= argument to SK.sign() and the -# sigdecode= argument to VK.verify(), and control how the signature is packed -# or unpacked. +def sigencode_strings(r, s, order): + """ + Encode the signature to a pair of strings in a tuple + + Encodes signature into raw encoding (:term:`raw encoding`) with the + ``r`` and ``s`` parts of the signature encoded separately. + It's expected that this function will be used as a ``sigencode=`` parameter + in :func:`ecdsa.keys.SigningKey.sign` method. -def sigencode_strings(r, s, order): + :param int r: first parameter of the signature + :param int s: second parameter of the signature + :param int order: the order of the curve over which the signature was + computed + + :return: raw encoding of ECDSA signature + :rtype: tuple(bytes, bytes) + """ r_str = number_to_string(r, order) s_str = number_to_string(s, order) return (r_str, s_str) @@ -236,7 +262,7 @@ def sigencode_string(r, s, order): """ Encode the signature to raw format (:term:`raw encoding`) - It's expected that this function will be used as a `sigencode=` parameter + It's expected that this function will be used as a ``sigencode=`` parameter in :func:`ecdsa.keys.SigningKey.sign` method. :param int r: first parameter of the signature @@ -264,7 +290,7 @@ def sigencode_der(r, s, order): s INTEGER } - It's expected that this function will be used as a `sigencode=` parameter + It's expected that this function will be used as a ``sigencode=`` parameter in :func:`ecdsa.keys.SigningKey.sign` method. :param int r: first parameter of the signature @@ -278,23 +304,83 @@ def sigencode_der(r, s, order): return der.encode_sequence(der.encode_integer(r), der.encode_integer(s)) -# canonical versions of sigencode methods -# these enforce low S values, by negating the value (modulo the order) if -# above order/2 see CECKey::Sign() -# https://github.com/bitcoin/bitcoin/blob/master/src/key.cpp#L214 def sigencode_strings_canonize(r, s, order): + """ + Encode the signature to a pair of strings in a tuple + + Encodes signature into raw encoding (:term:`raw encoding`) with the + ``r`` and ``s`` parts of the signature encoded separately. + + Makes sure that the signature is encoded in the canonical format, where + the ``s`` parameter is always smaller than ``order / 2``. + Most commonly used in bitcoin. + + It's expected that this function will be used as a ``sigencode=`` parameter + in :func:`ecdsa.keys.SigningKey.sign` method. + + :param int r: first parameter of the signature + :param int s: second parameter of the signature + :param int order: the order of the curve over which the signature was + computed + + :return: raw encoding of ECDSA signature + :rtype: tuple(bytes, bytes) + """ if s > order / 2: s = order - s return sigencode_strings(r, s, order) def sigencode_string_canonize(r, s, order): + """ + Encode the signature to raw format (:term:`raw encoding`) + + Makes sure that the signature is encoded in the canonical format, where + the ``s`` parameter is always smaller than ``order / 2``. + Most commonly used in bitcoin. + + It's expected that this function will be used as a ``sigencode=`` parameter + in :func:`ecdsa.keys.SigningKey.sign` method. + + :param int r: first parameter of the signature + :param int s: second parameter of the signature + :param int order: the order of the curve over which the signature was + computed + + :return: raw encoding of ECDSA signature + :rtype: bytes + """ if s > order / 2: s = order - s return sigencode_string(r, s, order) def sigencode_der_canonize(r, s, order): + """ + Encode the signature into the ECDSA-Sig-Value structure using :term:`DER`. + + Makes sure that the signature is encoded in the canonical format, where + the ``s`` parameter is always smaller than ``order / 2``. + Most commonly used in bitcoin. + + Encodes the signature to the following :term:`ASN.1` structure:: + + Ecdsa-Sig-Value ::= SEQUENCE { + r INTEGER, + s INTEGER + } + + It's expected that this function will be used as a ``sigencode=`` parameter + in :func:`ecdsa.keys.SigningKey.sign` method. + + :param int r: first parameter of the signature + :param int s: second parameter of the signature + :param int order: the order of the curve over which the signature was + computed + + :return: DER encoding of ECDSA signature + :rtype: bytes + """ if s > order / 2: s = order - s return sigencode_der(r, s, order) @@ -321,7 +407,7 @@ def sigdecode_string(signature, order): the signature, with each encoded using the same amount of bytes depending on curve size/order. - It's expected that this function will be used as the `sigdecode=` + It's expected that this function will be used as the ``sigdecode=`` parameter to the :func:`ecdsa.keys.VerifyingKey.verify` method. :param signature: encoded signature @@ -331,7 +417,7 @@ def sigdecode_string(signature, order): :raises MalformedSignature: when the encoding of the signature is invalid - :return: tuple with decoded 'r' and 's' values of signature + :return: tuple with decoded ``r`` and ``s`` values of signature :rtype: tuple of ints """ signature = normalise_bytes(signature) @@ -350,10 +436,10 @@ def sigdecode_strings(rs_strings, order): """ Decode the signature from two strings. - First string needs to be a big endian encoding of 'r', second needs to - be a big endian encoding of the 's' parameter of an ECDSA signature. + First string needs to be a big endian encoding of ``r``, second needs to + be a big endian encoding of the ``s`` parameter of an ECDSA signature. - It's expected that this function will be used as the `sigdecode=` + It's expected that this function will be used as the ``sigdecode=`` parameter to the :func:`ecdsa.keys.VerifyingKey.verify` method. :param list rs_strings: list of two bytes-like objects, each encoding one @@ -362,7 +448,7 @@ def sigdecode_strings(rs_strings, order): :raises MalformedSignature: when the encoding of the signature is invalid - :return: tuple with decoded 'r' and 's' values of signature + :return: tuple with decoded ``r`` and ``s`` values of signature :rtype: tuple of ints """ if not len(rs_strings) == 2: @@ -404,7 +490,7 @@ def sigdecode_der(sig_der, order): s INTEGER } - It's expected that this function will be used as as the `sigdecode=` + It's expected that this function will be used as as the ``sigdecode=`` parameter to the :func:`ecdsa.keys.VerifyingKey.verify` method. :param sig_der: encoded signature @@ -414,7 +500,7 @@ def sigdecode_der(sig_der, order): :raises UnexpectedDER: when the encoding of signature is invalid - :return: tuple with decoded 'r' and 's' values of signature + :return: tuple with decoded ``r`` and ``s`` values of signature :rtype: tuple of ints """ sig_der = normalise_bytes(sig_der) From efbab45cc52078a1963de95ab0f17ca26765f697 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 26 May 2023 19:07:10 +0200 Subject: [PATCH 024/105] add support for twisted Brainpool curves --- src/ecdsa/__init__.py | 14 +++ src/ecdsa/curves.py | 77 ++++++++++++++ src/ecdsa/ecdsa.py | 215 ++++++++++++++++++++++++++++++++++++++ src/ecdsa/test_pyecdsa.py | 112 ++++++++++++++++++++ 4 files changed, 418 insertions(+) diff --git a/src/ecdsa/__init__.py b/src/ecdsa/__init__.py index ce8749aa..342538e0 100644 --- a/src/ecdsa/__init__.py +++ b/src/ecdsa/__init__.py @@ -28,6 +28,13 @@ SECP160r1, Ed25519, Ed448, + BRAINPOOLP160t1, + BRAINPOOLP192t1, + BRAINPOOLP224t1, + BRAINPOOLP256t1, + BRAINPOOLP320t1, + BRAINPOOLP384t1, + BRAINPOOLP512t1, ) from .ecdh import ( ECDH, @@ -84,6 +91,13 @@ Ed25519, Ed448, six.b(""), + BRAINPOOLP160t1, + BRAINPOOLP192t1, + BRAINPOOLP224t1, + BRAINPOOLP256t1, + BRAINPOOLP320t1, + BRAINPOOLP384t1, + BRAINPOOLP512t1, ] del _hush_pyflakes diff --git a/src/ecdsa/curves.py b/src/ecdsa/curves.py index 1119ee5a..38e3a758 100644 --- a/src/ecdsa/curves.py +++ b/src/ecdsa/curves.py @@ -26,12 +26,19 @@ "curve_by_name", "SECP256k1", "BRAINPOOLP160r1", + "BRAINPOOLP160t1", "BRAINPOOLP192r1", + "BRAINPOOLP192t1", "BRAINPOOLP224r1", + "BRAINPOOLP224t1", "BRAINPOOLP256r1", + "BRAINPOOLP256t1", "BRAINPOOLP320r1", + "BRAINPOOLP320t1", "BRAINPOOLP384r1", + "BRAINPOOLP384t1", "BRAINPOOLP512r1", + "BRAINPOOLP512t1", "PRIME_FIELD_OID", "CHARACTERISTIC_TWO_FIELD_OID", "Ed25519", @@ -375,6 +382,15 @@ def from_pem(cls, string, valid_encodings=None): ) +BRAINPOOLP160t1 = Curve( + "BRAINPOOLP160t1", + ecdsa.curve_brainpoolp160t1, + ecdsa.generator_brainpoolp160t1, + (1, 3, 36, 3, 3, 2, 8, 1, 1, 2), + "brainpoolP160t1", +) + + BRAINPOOLP192r1 = Curve( "BRAINPOOLP192r1", ecdsa.curve_brainpoolp192r1, @@ -384,6 +400,15 @@ def from_pem(cls, string, valid_encodings=None): ) +BRAINPOOLP192t1 = Curve( + "BRAINPOOLP192t1", + ecdsa.curve_brainpoolp192t1, + ecdsa.generator_brainpoolp192t1, + (1, 3, 36, 3, 3, 2, 8, 1, 1, 4), + "brainpoolP192t1", +) + + BRAINPOOLP224r1 = Curve( "BRAINPOOLP224r1", ecdsa.curve_brainpoolp224r1, @@ -393,6 +418,15 @@ def from_pem(cls, string, valid_encodings=None): ) +BRAINPOOLP224t1 = Curve( + "BRAINPOOLP224t1", + ecdsa.curve_brainpoolp224t1, + ecdsa.generator_brainpoolp224t1, + (1, 3, 36, 3, 3, 2, 8, 1, 1, 6), + "brainpoolP224t1", +) + + BRAINPOOLP256r1 = Curve( "BRAINPOOLP256r1", ecdsa.curve_brainpoolp256r1, @@ -402,6 +436,15 @@ def from_pem(cls, string, valid_encodings=None): ) +BRAINPOOLP256t1 = Curve( + "BRAINPOOLP256t1", + ecdsa.curve_brainpoolp256t1, + ecdsa.generator_brainpoolp256t1, + (1, 3, 36, 3, 3, 2, 8, 1, 1, 8), + "brainpoolP256t1", +) + + BRAINPOOLP320r1 = Curve( "BRAINPOOLP320r1", ecdsa.curve_brainpoolp320r1, @@ -411,6 +454,15 @@ def from_pem(cls, string, valid_encodings=None): ) +BRAINPOOLP320t1 = Curve( + "BRAINPOOLP320t1", + ecdsa.curve_brainpoolp320t1, + ecdsa.generator_brainpoolp320t1, + (1, 3, 36, 3, 3, 2, 8, 1, 1, 10), + "brainpoolP320t1", +) + + BRAINPOOLP384r1 = Curve( "BRAINPOOLP384r1", ecdsa.curve_brainpoolp384r1, @@ -420,6 +472,15 @@ def from_pem(cls, string, valid_encodings=None): ) +BRAINPOOLP384t1 = Curve( + "BRAINPOOLP384t1", + ecdsa.curve_brainpoolp384t1, + ecdsa.generator_brainpoolp384t1, + (1, 3, 36, 3, 3, 2, 8, 1, 1, 12), + "brainpoolP384t1", +) + + BRAINPOOLP512r1 = Curve( "BRAINPOOLP512r1", ecdsa.curve_brainpoolp512r1, @@ -429,6 +490,15 @@ def from_pem(cls, string, valid_encodings=None): ) +BRAINPOOLP512t1 = Curve( + "BRAINPOOLP512t1", + ecdsa.curve_brainpoolp512t1, + ecdsa.generator_brainpoolp512t1, + (1, 3, 36, 3, 3, 2, 8, 1, 1, 14), + "brainpoolP512t1", +) + + Ed25519 = Curve( "Ed25519", eddsa.curve_ed25519, @@ -466,6 +536,13 @@ def from_pem(cls, string, valid_encodings=None): SECP160r1, Ed25519, Ed448, + BRAINPOOLP160t1, + BRAINPOOLP192t1, + BRAINPOOLP224t1, + BRAINPOOLP256t1, + BRAINPOOLP320t1, + BRAINPOOLP384t1, + BRAINPOOLP512t1, ] diff --git a/src/ecdsa/ecdsa.py b/src/ecdsa/ecdsa.py index 33282816..5a891ec9 100644 --- a/src/ecdsa/ecdsa.py +++ b/src/ecdsa/ecdsa.py @@ -654,6 +654,18 @@ def point_is_valid(generator, x, y): curve_brainpoolp160r1, _Gx, _Gy, 1, _q, generator=True ) +# Brainpool P-160-t1 +_a = 0xE95E4A5F737059DC60DFC7AD95B3D8139515620C +_b = 0x7A556B6DAE535B7B51ED2C4D7DAA7A0B5C55F380 +# _z = 0x24DBFF5DEC9B986BBFE5295A29BFBAE45E0F5D0B +_Gx = 0xB199B13B9B34EFC1397E64BAEB05ACC265FF2378 +_Gy = 0xADD6718B7C7C1961F0991B842443772152C9E0AD +_q = 0xE95E4A5F737059DC60DF5991D45029409E60FC09 +curve_brainpoolp160t1 = ellipticcurve.CurveFp(_p, _a, _b, 1) +generator_brainpoolp160t1 = ellipticcurve.PointJacobi( + curve_brainpoolp160t1, _Gx, _Gy, 1, _q, generator=True +) + # Brainpool P-192-r1 _a = 0x6A91174076B1E0E19C39C031FE8685C1CAE040E5C69A28EF _b = 0x469A28EF7C28CCA3DC721D044F4496BCCA7EF4146FBF25C9 @@ -667,6 +679,19 @@ def point_is_valid(generator, x, y): curve_brainpoolp192r1, _Gx, _Gy, 1, _q, generator=True ) +# Brainpool P-192-t1 +_a = 0xC302F41D932A36CDA7A3463093D18DB78FCE476DE1A86294 +_b = 0x13D56FFAEC78681E68F9DEB43B35BEC2FB68542E27897B79 +# _z = 0x1B6F5CC8DB4DC7AF19458A9CB80DC2295E5EB9C3732104CB +_Gx = 0x3AE9E58C82F63C30282E1FE7BBF43FA72C446AF6F4618129 +_Gy = 0x097E2C5667C2223A902AB5CA449D0084B7E5B3DE7CCC01C9 +_q = 0xC302F41D932A36CDA7A3462F9E9E916B5BE8F1029AC4ACC1 + +curve_brainpoolp192t1 = ellipticcurve.CurveFp(_p, _a, _b, 1) +generator_brainpoolp192t1 = ellipticcurve.PointJacobi( + curve_brainpoolp192t1, _Gx, _Gy, 1, _q, generator=True +) + # Brainpool P-224-r1 _a = 0x68A5E62CA9CE6C1C299803A6C1530B514E182AD8B0042A59CAD29F43 _b = 0x2580F63CCFE44138870713B1A92369E33E2135D266DBB372386C400B @@ -680,6 +705,19 @@ def point_is_valid(generator, x, y): curve_brainpoolp224r1, _Gx, _Gy, 1, _q, generator=True ) +# Brainpool P-224-t1 +_a = 0xD7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FC +_b = 0x4B337D934104CD7BEF271BF60CED1ED20DA14C08B3BB64F18A60888D +# _z = 0x2DF271E14427A346910CF7A2E6CFA7B3F484E5C2CCE1C8B730E28B3F +_Gx = 0x6AB1E344CE25FF3896424E7FFE14762ECB49F8928AC0C76029B4D580 +_Gy = 0x0374E9F5143E568CD23F3F4D7C0D4B1E41C8CC0D1C6ABD5F1A46DB4C +_q = 0xD7C134AA264366862A18302575D0FB98D116BC4B6DDEBCA3A5A7939F + +curve_brainpoolp224t1 = ellipticcurve.CurveFp(_p, _a, _b, 1) +generator_brainpoolp224t1 = ellipticcurve.PointJacobi( + curve_brainpoolp224t1, _Gx, _Gy, 1, _q, generator=True +) + # Brainpool P-256-r1 _a = 0x7D5A0975FC2C3057EEF67530417AFFE7FB8055C126DC5C6CE94A4B44F330B5D9 _b = 0x26DC5C6CE94A4B44F330B5D9BBD77CBF958416295CF7E1CE6BCCDC18FF8C07B6 @@ -693,6 +731,19 @@ def point_is_valid(generator, x, y): curve_brainpoolp256r1, _Gx, _Gy, 1, _q, generator=True ) +# Brainpool P-256-t1 +_a = 0xA9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5374 +_b = 0x662C61C430D84EA4FE66A7733D0B76B7BF93EBC4AF2F49256AE58101FEE92B04 +# _z = 0x3E2D4BD9597B58639AE7AA669CAB9837CF5CF20A2C852D10F655668DFC150EF0 +_Gx = 0xA3E8EB3CC1CFE7B7732213B23A656149AFA142C47AAFBC2B79A191562E1305F4 +_Gy = 0x2D996C823439C56D7F7B22E14644417E69BCB6DE39D027001DABE8F35B25C9BE +_q = 0xA9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7 + +curve_brainpoolp256t1 = ellipticcurve.CurveFp(_p, _a, _b, 1) +generator_brainpoolp256t1 = ellipticcurve.PointJacobi( + curve_brainpoolp256t1, _Gx, _Gy, 1, _q, generator=True +) + # Brainpool P-320-r1 _a = int( remove_whitespace( @@ -748,6 +799,61 @@ def point_is_valid(generator, x, y): curve_brainpoolp320r1, _Gx, _Gy, 1, _q, generator=True ) +# Brainpool P-320-t1 +_a = int( + remove_whitespace( + """ + D35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F92B9EC7893EC + 28FCD412B1F1B32E24""" + ), + 16, +) +_b = int( + remove_whitespace( + """ + A7F561E038EB1ED560B3D147DB782013064C19F27ED27C6780AAF77FB8A547 + CEB5B4FEF422340353""" + ), + 16, +) +# _z = int( +# remove_whitespace( +# """ +# 15F75CAF668077F7E85B42EB01F0A81FF56ECD6191D55CB82B7D861458A18F +# EFC3E5AB7496F3C7B1""" +# ), +# 16, +# ) +_Gx = int( + remove_whitespace( + """ + 925BE9FB01AFC6FB4D3E7D4990010F813408AB106C4F09CB7EE07868CC136F + FF3357F624A21BED52""" + ), + 16, +) +_Gy = int( + remove_whitespace( + """ + 63BA3A7A27483EBF6671DBEF7ABB30EBEE084E58A0B077AD42A5A0989D1EE7 + 1B1B9BC0455FB0D2C3""" + ), + 16, +) +_q = int( + remove_whitespace( + """ + D35E472036BC4FB7E13C785ED201E065F98FCFA5B68F12A32D482EC7EE8658 + E98691555B44C59311""" + ), + 16, +) + +curve_brainpoolp320t1 = ellipticcurve.CurveFp(_p, _a, _b, 1) +generator_brainpoolp320t1 = ellipticcurve.PointJacobi( + curve_brainpoolp320t1, _Gx, _Gy, 1, _q, generator=True +) + # Brainpool P-384-r1 _a = int( remove_whitespace( @@ -803,6 +909,60 @@ def point_is_valid(generator, x, y): curve_brainpoolp384r1, _Gx, _Gy, 1, _q, generator=True ) +_a = int( + remove_whitespace( + """ + 8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB711 + 23ACD3A729901D1A71874700133107EC50""" + ), + 16, +) +_b = int( + remove_whitespace( + """ + 7F519EADA7BDA81BD826DBA647910F8C4B9346ED8CCDC64E4B1ABD11756DCE + 1D2074AA263B88805CED70355A33B471EE""" + ), + 16, +) +# _z = int( +# remove_whitespace( +# """ +# 41DFE8DD399331F7166A66076734A89CD0D2BCDB7D068E44E1F378F41ECBAE +# 97D2D63DBC87BCCDDCCC5DA39E8589291C""" +# ), +# 16, +# ) +_Gx = int( + remove_whitespace( + """ + 18DE98B02DB9A306F2AFCD7235F72A819B80AB12EBD653172476FECD462AAB + FFC4FF191B946A5F54D8D0AA2F418808CC""" + ), + 16, +) +_Gy = int( + remove_whitespace( + """ + 25AB056962D30651A114AFD2755AD336747F93475B7A1FCA3B88F2B6A208CC + FE469408584DC2B2912675BF5B9E582928""" + ), + 16, +) +_q = int( + remove_whitespace( + """ + 8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425 + A7CF3AB6AF6B7FC3103B883202E9046565""" + ), + 16, +) + +curve_brainpoolp384t1 = ellipticcurve.CurveFp(_p, _a, _b, 1) +generator_brainpoolp384t1 = ellipticcurve.PointJacobi( + curve_brainpoolp384t1, _Gx, _Gy, 1, _q, generator=True +) + # Brainpool P-512-r1 _a = int( remove_whitespace( @@ -857,3 +1017,58 @@ def point_is_valid(generator, x, y): generator_brainpoolp512r1 = ellipticcurve.PointJacobi( curve_brainpoolp512r1, _Gx, _Gy, 1, _q, generator=True ) + +# Brainpool P-512-t1 +_a = int( + remove_whitespace( + """ + AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308 + 717D4D9B009BC66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F0""" + ), + 16, +) +_b = int( + remove_whitespace( + """ + 7CBBBCF9441CFAB76E1890E46884EAE321F70C0BCB4981527897504BEC3E36 + A62BCDFA2304976540F6450085F2DAE145C22553B465763689180EA2571867423E""" + ), + 16, +) +# _z = int( +# remove_whitespace( +# """ +# 12EE58E6764838B69782136F0F2D3BA06E27695716054092E60A80BEDB212B +# 64E585D90BCE13761F85C3F1D2A64E3BE8FEA2220F01EBA5EEB0F35DBD29D922AB""" +# ), +# 16, +# ) +_Gx = int( + remove_whitespace( + """ + 640ECE5C12788717B9C1BA06CBC2A6FEBA85842458C56DDE9DB1758D39C031 + 3D82BA51735CDB3EA499AA77A7D6943A64F7A3F25FE26F06B51BAA2696FA9035DA""" + ), + 16, +) +_Gy = int( + remove_whitespace( + """ + 5B534BD595F5AF0FA2C892376C84ACE1BB4E3019B71634C01131159CAE03CE + E9D9932184BEEF216BD71DF2DADF86A627306ECFF96DBB8BACE198B61E00F8B332""" + ), + 16, +) +_q = int( + remove_whitespace( + """ + AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308 + 70553E5C414CA92619418661197FAC10471DB1D381085DDADDB58796829CA90069""" + ), + 16, +) + +curve_brainpoolp512t1 = ellipticcurve.CurveFp(_p, _a, _b, 1) +generator_brainpoolp512t1 = ellipticcurve.PointJacobi( + curve_brainpoolp512t1, _Gx, _Gy, 1, _q, generator=True +) diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index fc7e64fe..3ce3e04f 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -48,6 +48,13 @@ BRAINPOOLP320r1, BRAINPOOLP384r1, BRAINPOOLP512r1, + BRAINPOOLP160t1, + BRAINPOOLP192t1, + BRAINPOOLP224t1, + BRAINPOOLP256t1, + BRAINPOOLP320t1, + BRAINPOOLP384t1, + BRAINPOOLP512t1, Ed25519, Ed448, curves, @@ -151,6 +158,13 @@ def test_lengths(self): BRAINPOOLP320r1, BRAINPOOLP384r1, BRAINPOOLP512r1, + BRAINPOOLP160t1, + BRAINPOOLP192t1, + BRAINPOOLP224t1, + BRAINPOOLP256t1, + BRAINPOOLP320t1, + BRAINPOOLP384t1, + BRAINPOOLP512t1, ): priv = SigningKey.generate(curve=curve) pub1 = priv.get_verifying_key() @@ -1167,6 +1181,55 @@ def test_from_openssl_brainpoolp384r1(self): def test_from_openssl_brainpoolp512r1(self): return self.do_test_from_openssl(BRAINPOOLP512r1) + @pytest.mark.skipif( + "brainpoolP160t1" not in OPENSSL_SUPPORTED_CURVES, + reason="system openssl does not support brainpoolP160t1", + ) + def test_from_openssl_brainpoolp160t1(self): + return self.do_test_from_openssl(BRAINPOOLP160t1) + + @pytest.mark.skipif( + "brainpoolP192t1" not in OPENSSL_SUPPORTED_CURVES, + reason="system openssl does not support brainpoolP192t1", + ) + def test_from_openssl_brainpoolp192t1(self): + return self.do_test_from_openssl(BRAINPOOLP192t1) + + @pytest.mark.skipif( + "brainpoolP224t1" not in OPENSSL_SUPPORTED_CURVES, + reason="system openssl does not support brainpoolP224t1", + ) + def test_from_openssl_brainpoolp224t1(self): + return self.do_test_from_openssl(BRAINPOOLP224t1) + + @pytest.mark.skipif( + "brainpoolP256t1" not in OPENSSL_SUPPORTED_CURVES, + reason="system openssl does not support brainpoolP256t1", + ) + def test_from_openssl_brainpoolp256t1(self): + return self.do_test_from_openssl(BRAINPOOLP256t1) + + @pytest.mark.skipif( + "brainpoolP320t1" not in OPENSSL_SUPPORTED_CURVES, + reason="system openssl does not support brainpoolP320t1", + ) + def test_from_openssl_brainpoolp320t1(self): + return self.do_test_from_openssl(BRAINPOOLP320t1) + + @pytest.mark.skipif( + "brainpoolP384t1" not in OPENSSL_SUPPORTED_CURVES, + reason="system openssl does not support brainpoolP384t1", + ) + def test_from_openssl_brainpoolp384t1(self): + return self.do_test_from_openssl(BRAINPOOLP384t1) + + @pytest.mark.skipif( + "brainpoolP512t1" not in OPENSSL_SUPPORTED_CURVES, + reason="system openssl does not support brainpoolP512t1", + ) + def test_from_openssl_brainpoolp512t1(self): + return self.do_test_from_openssl(BRAINPOOLP512t1) + def do_test_from_openssl(self, curve, hash_name="SHA1"): curvename = curve.openssl_name assert curvename @@ -1359,6 +1422,55 @@ def test_to_openssl_brainpoolp384r1(self): def test_to_openssl_brainpoolp512r1(self): self.do_test_to_openssl(BRAINPOOLP512r1) + @pytest.mark.skipif( + "brainpoolP160t1" not in OPENSSL_SUPPORTED_CURVES, + reason="system openssl does not support brainpoolP160t1", + ) + def test_to_openssl_brainpoolp160t1(self): + self.do_test_to_openssl(BRAINPOOLP160t1) + + @pytest.mark.skipif( + "brainpoolP192t1" not in OPENSSL_SUPPORTED_CURVES, + reason="system openssl does not support brainpoolP192t1", + ) + def test_to_openssl_brainpoolp192t1(self): + self.do_test_to_openssl(BRAINPOOLP192t1) + + @pytest.mark.skipif( + "brainpoolP224t1" not in OPENSSL_SUPPORTED_CURVES, + reason="system openssl does not support brainpoolP224t1", + ) + def test_to_openssl_brainpoolp224t1(self): + self.do_test_to_openssl(BRAINPOOLP224t1) + + @pytest.mark.skipif( + "brainpoolP256t1" not in OPENSSL_SUPPORTED_CURVES, + reason="system openssl does not support brainpoolP256t1", + ) + def test_to_openssl_brainpoolp256t1(self): + self.do_test_to_openssl(BRAINPOOLP256t1) + + @pytest.mark.skipif( + "brainpoolP320t1" not in OPENSSL_SUPPORTED_CURVES, + reason="system openssl does not support brainpoolP320t1", + ) + def test_to_openssl_brainpoolp320t1(self): + self.do_test_to_openssl(BRAINPOOLP320t1) + + @pytest.mark.skipif( + "brainpoolP384t1" not in OPENSSL_SUPPORTED_CURVES, + reason="system openssl does not support brainpoolP384t1", + ) + def test_to_openssl_brainpoolp384t1(self): + self.do_test_to_openssl(BRAINPOOLP384t1) + + @pytest.mark.skipif( + "brainpoolP512t1" not in OPENSSL_SUPPORTED_CURVES, + reason="system openssl does not support brainpoolP512t1", + ) + def test_to_openssl_brainpoolp512t1(self): + self.do_test_to_openssl(BRAINPOOLP512t1) + def do_test_to_openssl(self, curve, hash_name="SHA1"): curvename = curve.openssl_name assert curvename From 33a4114ccd1f08bf331df7dcf161aa0cd16785a7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 26 May 2023 19:18:57 +0200 Subject: [PATCH 025/105] use newer python 3.12 beta version --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e2920c0..8cf0cee2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,7 +85,7 @@ jobs: tox-env: py311 - name: py3.12 os: ubuntu-latest - python-version: '3.12.0-alpha.5' + python-version: '3.12.0-beta.1' tox-env: py312 - name: pypy os: ubuntu-latest From c586ada54ba7b19557421a49ff059bb204c7b51b Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 28 Jun 2023 19:24:43 +0200 Subject: [PATCH 026/105] fix CI for python2.7 --- .github/workflows/ci.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8cf0cee2..a3b218cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -160,10 +160,20 @@ jobs: run: git fetch origin master:refs/remotes/origin/master - name: Set up Python ${{ matrix.python-version }} # we use containers to use the native python version from them - if: ${{ !matrix.container }} + if: ${{ !matrix.container && matrix.python-version != '2.7' }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} + - name: Ensure python 2.7 + if: matrix.python-version == '2.7' + run: | + sudo apt-get update + sudo apt-get install -y \ + python2.7 python2.7-dev python-pip-whl + sudo ln -sf python2.7 /usr/bin/python + export PYTHONPATH=`echo /usr/share/python-wheels/pip-*py2*.whl` + sudo --preserve-env=PYTHONPATH python -m pip install --upgrade pip setuptools wheel + sudo chown -R $USER /usr/local/lib/python2.7 - name: Display Python version run: python -c "import sys; print(sys.version)" - name: Display installed python package versions From c9b0a01c1fad3e97be0447c1d4421f11d25ca866 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 30 Sep 2023 22:31:31 +0200 Subject: [PATCH 027/105] _compat.py: fix "do not compare types" --- src/ecdsa/_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ecdsa/_compat.py b/src/ecdsa/_compat.py index f409a1a2..4558e33a 100644 --- a/src/ecdsa/_compat.py +++ b/src/ecdsa/_compat.py @@ -127,7 +127,7 @@ def int_to_bytes(val, length=None, byteorder="big"): if length is None: length = byte_length(val) # for gmpy we need to convert back to native int - if type(val) != int: + if not isinstance(val, int): val = int(val) return bytearray(val.to_bytes(length=length, byteorder=byteorder)) From 5379cf3170da0601382df82522b184d6cf3f4b80 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 30 Sep 2023 22:33:20 +0200 Subject: [PATCH 028/105] pin version of flake8 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index c9b07c9a..ea01c4b4 100644 --- a/tox.ini +++ b/tox.ini @@ -104,7 +104,7 @@ commands = {envpython} speed.py basepython = python3 deps = black==22.3.0 - flake8 + flake8==6.1.0 commands = flake8 setup.py speed.py src black --check --line-length 79 . From c3966c8f2f6fb6152657d3a290cf9e490a0472bb Mon Sep 17 00:00:00 2001 From: vojtechsu Date: Sat, 30 Sep 2023 18:37:01 +0200 Subject: [PATCH 029/105] Remove unnecessary adds from mul_add --- src/ecdsa/ellipticcurve.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ecdsa/ellipticcurve.py b/src/ecdsa/ellipticcurve.py index d6f71463..af2aaeab 100644 --- a/src/ecdsa/ellipticcurve.py +++ b/src/ecdsa/ellipticcurve.py @@ -1008,8 +1008,8 @@ def mul_add(self, self_mul, other, other_mul): # so we need 4 combined points: mAmB_X, mAmB_Y, mAmB_Z = _add(X1, -Y1, Z1, X2, -Y2, Z2, p) pAmB_X, pAmB_Y, pAmB_Z = _add(X1, Y1, Z1, X2, -Y2, Z2, p) - mApB_X, mApB_Y, mApB_Z = _add(X1, -Y1, Z1, X2, Y2, Z2, p) - pApB_X, pApB_Y, pApB_Z = _add(X1, Y1, Z1, X2, Y2, Z2, p) + mApB_X, mApB_Y, mApB_Z = pAmB_X, -pAmB_Y, pAmB_Z + pApB_X, pApB_Y, pApB_Z = mAmB_X, -mAmB_Y, mAmB_Z # when the self and other sum to infinity, we need to add them # one by one to get correct result but as that's very unlikely to # happen in regular operation, we don't need to optimise this case From 52e2f06470fd045bd5c16badc03ab081aa17b7c7 Mon Sep 17 00:00:00 2001 From: Pablo Mazzini Date: Sun, 24 Sep 2023 21:40:48 +0100 Subject: [PATCH 030/105] export to ssh --- src/ecdsa/der.py | 6 +-- src/ecdsa/keys.py | 27 +++++++++++++- src/ecdsa/ssh.py | 83 ++++++++++++++++++++++++++++++++++++++++++ src/ecdsa/test_keys.py | 39 ++++++++++++++++++-- 4 files changed, 147 insertions(+), 8 deletions(-) create mode 100644 src/ecdsa/ssh.py diff --git a/src/ecdsa/der.py b/src/ecdsa/der.py index 8b27941c..fa6979d1 100644 --- a/src/ecdsa/der.py +++ b/src/ecdsa/der.py @@ -5,7 +5,7 @@ import warnings from itertools import chain from six import int2byte, b, text_type -from ._compat import str_idx_as_int +from ._compat import compat26_str, str_idx_as_int class UnexpectedDER(Exception): @@ -400,10 +400,10 @@ def unpem(pem): def topem(der, name): - b64 = base64.b64encode(der) + b64 = base64.b64encode(compat26_str(der)) lines = [("-----BEGIN %s-----\n" % name).encode()] lines.extend( - [b64[start : start + 64] + b("\n") for start in range(0, len(b64), 64)] + [b64[start : start + 76] + b("\n") for start in range(0, len(b64), 76)] ) lines.append(("-----END %s-----\n" % name).encode()) return b("").join(lines) diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index 2b7d3168..d77f4fc4 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -7,7 +7,7 @@ import os from six import PY2, b from . import ecdsa, eddsa -from . import der +from . import der, ssh from . import rfc6979 from . import ellipticcurve from .curves import NIST192p, Curve, Ed25519, Ed448 @@ -614,6 +614,18 @@ def to_der( der.encode_bitstring(point_str, 0), ) + def to_ssh(self): + """ + Convert the public key to the SSH format. + + :return: SSH encoding of the public key + :rtype: bytes + """ + return ssh.serialize_public( + self.curve.name, + self.to_string(), + ) + def verify( self, signature, @@ -1281,6 +1293,19 @@ def to_der( der.encode_octet_string(ec_private_key), ) + def to_ssh(self): + """ + Convert the private key to the SSH format. + + :return: SSH encoded private key + :rtype: bytes + """ + return ssh.serialize_private( + self.curve.name, + self.verifying_key.to_string(), + self.to_string(), + ) + def get_verifying_key(self): """ Return the VerifyingKey associated with this private key. diff --git a/src/ecdsa/ssh.py b/src/ecdsa/ssh.py new file mode 100644 index 00000000..64e94030 --- /dev/null +++ b/src/ecdsa/ssh.py @@ -0,0 +1,83 @@ +import binascii +from . import der +from ._compat import compat26_str, int_to_bytes + +_SSH_ED25519 = b"ssh-ed25519" +_SK_MAGIC = b"openssh-key-v1\0" +_NONE = b"none" + + +def _get_key_type(name): + if name == "Ed25519": + return _SSH_ED25519 + else: + raise ValueError("Unsupported key type") + + +class _Serializer: + def __init__(self): + self.bytes = b"" + + def put_raw(self, val): + self.bytes += val + + def put_u32(self, val): + self.bytes += int_to_bytes(val, length=4, byteorder="big") + + def put_str(self, val): + self.put_u32(len(val)) + self.bytes += val + + def put_pad(self, blklen=8): + padlen = blklen - (len(self.bytes) % blklen) + self.put_raw(bytearray(range(1, 1 + padlen))) + + def encode(self): + return binascii.b2a_base64(compat26_str(self.bytes)) + + def tobytes(self): + return self.bytes + + def topem(self): + return der.topem(self.bytes, "OPENSSH PRIVATE KEY") + + +def serialize_public(name, pub): + serial = _Serializer() + ktype = _get_key_type(name) + serial.put_str(ktype) + serial.put_str(pub) + return b" ".join([ktype, serial.encode()]) + + +def serialize_private(name, pub, priv): + # encode public part + spub = _Serializer() + ktype = _get_key_type(name) + spub.put_str(ktype) + spub.put_str(pub) + + # encode private part + spriv = _Serializer() + checksum = 0 + spriv.put_u32(checksum) + spriv.put_u32(checksum) + spriv.put_raw(spub.tobytes()) + spriv.put_str(priv + pub) + comment = b"" + spriv.put_str(comment) + spriv.put_pad() + + # top-level structure + main = _Serializer() + main.put_raw(_SK_MAGIC) + ciphername = kdfname = _NONE + main.put_str(ciphername) + main.put_str(kdfname) + nokdf = 0 + main.put_u32(nokdf) + nkeys = 1 + main.put_u32(nkeys) + main.put_str(spub.tobytes()) + main.put_str(spriv.tobytes()) + return main.topem() diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py index f9dbcad1..4d9e976e 100644 --- a/src/ecdsa/test_keys.py +++ b/src/ecdsa/test_keys.py @@ -364,6 +364,18 @@ def test_export_ed255_to_pem(self): self.assertEqual(vk_pem, vk.to_pem()) + def test_export_ed255_to_ssh(self): + vk_str = ( + b"\x23\x00\x50\xd0\xd6\x64\x22\x28\x8e\xe3\x55\x89\x7e\x6e\x41\x57" + b"\x8d\xae\xde\x44\x26\xee\x56\x27\xbc\x85\xe6\x0b\x2f\x2a\xcb\x65" + ) + + vk = VerifyingKey.from_string(vk_str, Ed25519) + + vk_ssh = b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICMAUNDWZCIojuNViX5uQVeNrt5EJu5WJ7yF5gsvKstl\n" + + self.assertEqual(vk_ssh, vk.to_ssh()) + def test_ed25519_export_import(self): sk = SigningKey.generate(Ed25519) vk = sk.verifying_key @@ -428,8 +440,8 @@ def test_ed448_to_pem(self): vk_pem = ( b"-----BEGIN PUBLIC KEY-----\n" - b"MEMwBQYDK2VxAzoAeQtetSu7CMEzE+XWB10Bg47LCA0giNikOxHzdp+tZ/eK/En0\n" - b"dTdYD2ll94g58MhSnBiBQB9A1MMA\n" + b"MEMwBQYDK2VxAzoAeQtetSu7CMEzE+XWB10Bg47LCA0giNikOxHzdp+tZ/eK/En0dTdYD2ll94g5\n" + b"8MhSnBiBQB9A1MMA\n" b"-----END PUBLIC KEY-----\n" ) @@ -629,6 +641,25 @@ def test_ed25519_to_pem(self): self.assertEqual(sk.to_pem(format="pkcs8"), pem_str) + def test_ed25519_to_ssh(self): + sk = 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, + ) + + ssh_str = ( + b"-----BEGIN OPENSSH PRIVATE KEY-----\n" + b"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZWQyNTUx\n" + b"OQAAACAjAFDQ1mQiKI7jVYl+bkFXja7eRCbuVie8heYLLyrLZQAAAIgAAAAAAAAAAAAAAAtzc2gt\n" + b"ZWQyNTUxOQAAACAjAFDQ1mQiKI7jVYl+bkFXja7eRCbuVie8heYLLyrLZQAAAEA0usfRTtTxvE+M\n" + b"SD4PGXdM/Li+rFRmRRGa19e4Bwv11CMAUNDWZCIojuNViX5uQVeNrt5EJu5WJ7yF5gsvKstlAAAA\n" + b"AAECAwQF\n" + b"-----END OPENSSH PRIVATE KEY-----\n" + ) + + self.assertEqual(sk.to_ssh(), ssh_str) + def test_ed25519_to_and_from_pem(self): sk = SigningKey.generate(Ed25519) @@ -665,8 +696,8 @@ def test_ed448_to_pem(self): ) pem_str = ( b"-----BEGIN PRIVATE KEY-----\n" - b"MEcCAQAwBQYDK2VxBDsEOTyFuXqFLXgJlV8uDqcOw9nG4IqzLiZ/i5NfBDoHPzmP\n" - b"OP0JMYaLGlTzwovmvCDJ2zLaezu9NLz9aQ==\n" + b"MEcCAQAwBQYDK2VxBDsEOTyFuXqFLXgJlV8uDqcOw9nG4IqzLiZ/i5NfBDoHPzmPOP0JMYaLGlTz\n" + b"wovmvCDJ2zLaezu9NLz9aQ==\n" b"-----END PRIVATE KEY-----\n" ) From d32cf787650c9b877a08f76bd7d35cfdaa15c31f Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 20 Dec 2023 13:26:31 +0100 Subject: [PATCH 031/105] be explicit in requiring rtd theme for docs --- .readthedocs.yaml | 11 ++++------- docs/requirements.txt | 2 +- requirements.txt | 1 + 3 files changed, 6 insertions(+), 8 deletions(-) create mode 100644 requirements.txt diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 3fcbd5ca..14dd391c 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -7,17 +7,13 @@ version: 2 # Set the version of Python and other tools you might need build: - os: ubuntu-20.04 + os: ubuntu-22.04 tools: - python: "3.9" - # You can also specify other tool versions: - # nodejs: "16" - # rust: "1.55" - # golang: "1.17" + python: "3.11" # Build documentation in the docs/ directory with Sphinx sphinx: - configuration: docs/source/conf.py + configuration: docs/source/conf.py # If using Sphinx, optionally build your docs in additional formats such as PDF # formats: @@ -26,4 +22,5 @@ sphinx: # Optionally declare the Python requirements required to build your docs python: install: + - requirements: requirements.txt - requirements: docs/requirements.txt diff --git a/docs/requirements.txt b/docs/requirements.txt index ffe2fce4..6c5d5d44 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1 +1 @@ -six +sphinx-rtd-theme diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..ffe2fce4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +six From e8e272dc2ed36bd3d0cf8c58c2d3d8cf15c6dc2b Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 Dec 2023 00:14:05 +0100 Subject: [PATCH 032/105] fix typo in test_sha3.py --- src/ecdsa/test_sha3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ecdsa/test_sha3.py b/src/ecdsa/test_sha3.py index 2c6bd15c..3e5107cc 100644 --- a/src/ecdsa/test_sha3.py +++ b/src/ecdsa/test_sha3.py @@ -43,7 +43,7 @@ def test_int_to_bytes_wrong_endian(self): int_to_bytes(0, byteorder="middle") -@pytest.mark.skipif(GMPY == False, reason="requites gmpy or gmpy2") +@pytest.mark.skipif(GMPY == False, reason="requires gmpy or gmpy2") def test_int_to_bytes_with_gmpy(): assert int_to_bytes(mpz(1)) == b"\x01" From 60d3fd50f163ba220ce19b951032310a0647fd44 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 1 Nov 2019 23:56:59 +0100 Subject: [PATCH 033/105] configuration for cosmic-ray --- cosmic-ray.sh | 9 +++++++++ cosmic-ray.toml | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 cosmic-ray.sh create mode 100644 cosmic-ray.toml diff --git a/cosmic-ray.sh b/cosmic-ray.sh new file mode 100644 index 00000000..43684650 --- /dev/null +++ b/cosmic-ray.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +cosmic-ray init cosmic-ray.toml session.sqlite +cosmic-ray baseline --report session.sqlite +cr-report --show-output session.baseline.sqlite +cosmic-ray exec session.sqlite +cr-report session.sqlite +cr-html session.sqlite > session.html diff --git a/cosmic-ray.toml b/cosmic-ray.toml new file mode 100644 index 00000000..b3ee9e1f --- /dev/null +++ b/cosmic-ray.toml @@ -0,0 +1,18 @@ +[cosmic-ray] +module-path = "src" +python-version = "" +timeout = 120.0 +exclude-modules = ['src/ecdsa/_version.py', 'src/ecdsa/test*'] +test-command = "pytest -x src/" + +[cosmic-ray.execution-engine] +name = "local" + +[cosmic-ray.cloning] +method = "copy" +commands = [] + +[cosmic-ray.interceptors] +enabled = [ "spor", "pragma_no_mutate", "operators-filter",] + +[cosmic-ray.operators-filter] From aa7e7b36b9eedecbbc0698e80ea9d59882e95bed Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 2 Nov 2019 19:24:22 +0100 Subject: [PATCH 034/105] add quick mutation testing to travis --- .travis.yml | 37 +++++++++++++++++++++++++++++++------ cosmic-ray.sh | 1 + cosmic-ray.toml | 2 +- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index f31a7197..009656c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,9 +12,13 @@ addons: - libmpc-dev before_cache: - rm -f $HOME/.cache/pip/log/debug.log -# place the slowest (instrumental and py2.6) first +# place the slowest (instrumental, mutation and py2.6) first matrix: include: + - python: 3.9 + dist: bionic + sudo: true + env: MUTATION=yes - python: 2.7 env: INSTRUMENTAL=yes dist: bionic @@ -125,15 +129,16 @@ install: else travis_retry pip install -r build-requirements.txt; fi - - if [[ $TOX_ENV =~ gmpy2 ]] || [[ $INSTRUMENTAL ]]; then travis_retry pip install gmpy2; fi + - if [[ $TOX_ENV =~ gmpy2 ]] || [[ $INSTRUMENTAL ]] || [[ $MUTATION ]]; 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 + - if [[ $MUTATION ]]; then travis_retry pip install cosmic-ray; fi - pip list script: - if [[ $TOX_ENV ]]; then tox -e $TOX_ENV; fi - - 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 [[ $TOX_ENV =~ gmpy2 ]] && [[ -z $MUTATION ]]; then tox -e speedgmpy2; fi + - if [[ $TOX_ENV =~ gmpyp ]] && [[ -z $MUTATION ]]; then tox -e speedgmpy; fi + - if ! [[ $TOX_ENV =~ gmpy ]] && [[ -z $MUTATION ]]; then tox -e speed; fi - | if [[ $INSTRUMENTAL && $TRAVIS_PULL_REQUEST != "false" ]]; then git checkout $PR_FIRST^ @@ -155,6 +160,26 @@ script: if [[ $INSTRUMENTAL && $TRAVIS_PULL_REQUEST != "false" ]]; then instrumental -f .instrumental.cov -s | python diff-instrumental.py --read .diff-instrumental --fail-under 70 --max-difference -0.1 fi + # cosmic-ray (mutation testing) runs + - if [[ $MUTATION ]]; then cosmic-ray init cosmic-ray.toml session.sqlite; fi + - if [[ $MUTATION ]]; then cosmic-ray baseline --report session.sqlite; fi + - if [[ $MUTATION ]]; then cr-report --show-output session.baseline.sqlite; fi + - | + if [[ $MUTATION ]]; then + cosmic-ray exec session.sqlite & + COSMIC_PID=$! + # make sure that we output something every 5 minutes (otherwise travis will kill us) + while kill -s 0 $COSMIC_PID; do + sleep 300 + cr-report session.sqlite | tail -n 3; + done & + REPORT_PID=$! + # kill exec after 25 minutes + (sleep $((60*25)); kill $COSMIC_PID) & + fi + - if [[ $MUTATION ]]; then wait $COSMIC_PID ; kill $REPORT_PID ; true; fi + - if [[ $MUTATION ]]; then cr-report --show-output session.sqlite | tail -n 40; fi + - if [[ $MUTATION ]]; then cr-rate --estimate --fail-over 25 --confidence 99.9 session.sqlite; fi after_success: - - if [[ -z $INSTRUMENTAL ]]; then coveralls; fi + - if [[ -z $INSTRUMENTAL && -z $MUTATION ]]; then coveralls; fi diff --git a/cosmic-ray.sh b/cosmic-ray.sh index 43684650..ba512d50 100644 --- a/cosmic-ray.sh +++ b/cosmic-ray.sh @@ -7,3 +7,4 @@ cr-report --show-output session.baseline.sqlite cosmic-ray exec session.sqlite cr-report session.sqlite cr-html session.sqlite > session.html +cr-rate --estimate --fail-over 25 --confidence 99.9 session.sqlite diff --git a/cosmic-ray.toml b/cosmic-ray.toml index b3ee9e1f..c9a234e0 100644 --- a/cosmic-ray.toml +++ b/cosmic-ray.toml @@ -1,7 +1,7 @@ [cosmic-ray] module-path = "src" python-version = "" -timeout = 120.0 +timeout = 30.0 exclude-modules = ['src/ecdsa/_version.py', 'src/ecdsa/test*'] test-command = "pytest -x src/" From 880d3ccfc4afa1b47841b10f8cbc54e01968d2c6 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 12 Nov 2020 01:33:07 +0100 Subject: [PATCH 035/105] add --fast option to run hypothesis tests faster --- conftest.py | 4 ++++ cosmic-ray.toml | 2 +- src/ecdsa/test_der.py | 11 ++++++++++- src/ecdsa/test_malformed_sigs.py | 13 +++++++++---- src/ecdsa/test_numbertheory.py | 14 ++++++++++++-- src/ecdsa/test_pyecdsa.py | 11 ++++++++++- 6 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 conftest.py diff --git a/conftest.py b/conftest.py new file mode 100644 index 00000000..13387580 --- /dev/null +++ b/conftest.py @@ -0,0 +1,4 @@ +def pytest_addoption(parser): + parser.addoption( + "--fast", action="store_true", default=False, help="run tests fast" + ) diff --git a/cosmic-ray.toml b/cosmic-ray.toml index c9a234e0..f5de84bf 100644 --- a/cosmic-ray.toml +++ b/cosmic-ray.toml @@ -3,7 +3,7 @@ module-path = "src" python-version = "" timeout = 30.0 exclude-modules = ['src/ecdsa/_version.py', 'src/ecdsa/test*'] -test-command = "pytest -x src/" +test-command = "pytest -x --fast src/" [cosmic-ray.execution-engine] name = "local" diff --git a/src/ecdsa/test_der.py b/src/ecdsa/test_der.py index 833d12d8..f450baf8 100644 --- a/src/ecdsa/test_der.py +++ b/src/ecdsa/test_der.py @@ -7,9 +7,10 @@ import unittest2 as unittest except ImportError: import unittest +import sys from six import b import hypothesis.strategies as st -from hypothesis import given +from hypothesis import given, settings import pytest from ._compat import str_idx_as_int from .curves import NIST256p, NIST224p @@ -462,6 +463,14 @@ def st_oid(draw, max_value=2**512, max_size=50): return (first, second) + tuple(rest) +HYP_SETTINGS = {} + + +if "--fast" in sys.argv: + HYP_SETTINGS["max_examples"] = 2 + + +@settings(**HYP_SETTINGS) @given(st_oid()) def test_oids(ids): encoded_oid = encode_oid(*ids) diff --git a/src/ecdsa/test_malformed_sigs.py b/src/ecdsa/test_malformed_sigs.py index 20912376..2a318a44 100644 --- a/src/ecdsa/test_malformed_sigs.py +++ b/src/ecdsa/test_malformed_sigs.py @@ -154,12 +154,17 @@ def st_fuzzed_sig(draw, keys_and_sigs): HealthCheck.filter_too_much, HealthCheck.too_slow, ] +if "--fast" in sys.argv: + params["max_examples"] = 20 slow_params = dict(params) -slow_params["max_examples"] = 10 +if "--fast" in sys.argv: + slow_params["max_examples"] = 2 +else: + slow_params["max_examples"] = 10 -@settings(**params) +@settings(**slow_params) @given(st_fuzzed_sig(keys_and_sigs)) def test_fuzzed_der_signatures(args): verifying_key, sig = args @@ -303,7 +308,7 @@ def st_der(): ) -@settings(**params) +@settings(**slow_params) @given(st.sampled_from(keys_and_sigs), st_der()) def test_random_der_as_signature(params, der): """Check if random DER structures are rejected as signature""" @@ -360,7 +365,7 @@ def test_random_bytes_as_signature(params, der): ] -@settings(**params) +@settings(**slow_params) @given(st_fuzzed_sig(keys_and_string_sigs)) def test_fuzzed_string_signatures(params): verifying_key, sig = params diff --git a/src/ecdsa/test_numbertheory.py b/src/ecdsa/test_numbertheory.py index 239eaa8c..dcaa6d92 100644 --- a/src/ecdsa/test_numbertheory.py +++ b/src/ecdsa/test_numbertheory.py @@ -1,5 +1,6 @@ import operator from functools import reduce +import sys try: import unittest2 as unittest @@ -251,9 +252,15 @@ def st_comp_no_com_fac(draw): # the factorization() sometimes takes a long time to finish HYP_SETTINGS["deadline"] = 5000 +if "--fast" in sys.argv: + HYP_SETTINGS["max_examples"] = 20 + HYP_SLOW_SETTINGS = dict(HYP_SETTINGS) -HYP_SLOW_SETTINGS["max_examples"] = 10 +if "--fast" in sys.argv: + HYP_SLOW_SETTINGS["max_examples"] = 2 +else: + HYP_SLOW_SETTINGS["max_examples"] = 20 class TestIsPrime(unittest.TestCase): @@ -320,6 +327,7 @@ def test_gcd_with_uncom_factor(self, numbers): n = gcd(numbers) assert n == 1 + @settings(**HYP_SLOW_SETTINGS) @given( st.lists( st.integers(min_value=1, max_value=2**8192), @@ -338,6 +346,7 @@ def test_lcm(self): assert lcm([3, 5 * 3, 7 * 3]) == 3 * 5 * 7 assert lcm(3) == 3 + @settings(**HYP_SLOW_SETTINGS) @given( st.lists( st.integers(min_value=1, max_value=2**8192), @@ -356,7 +365,7 @@ def test_lcm_with_random_numbers(self, numbers): "meet requirements (like `is_prime()`), the test " "case times-out on it", ) - @settings(**HYP_SETTINGS) + @settings(**HYP_SLOW_SETTINGS) @given(st_num_square_prime()) def test_square_root_mod_prime(self, vals): square, prime = vals @@ -420,6 +429,7 @@ def test_jacobi(self, mod): c *= jacobi(a, i[0]) ** i[1] assert c == jacobi(a, mod) + @settings(**HYP_SETTINGS) @given(st_two_nums_rel_prime()) def test_inverse_mod(self, nums): num, mod = nums diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index 3ce3e04f..1a428478 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -9,11 +9,12 @@ import shutil import subprocess import pytest +import sys from binascii import hexlify, unhexlify import hashlib from functools import partial -from hypothesis import given +from hypothesis import given, settings import hypothesis.strategies as st from six import b, print_, binary_type @@ -75,6 +76,13 @@ class SubprocessError(Exception): pass +HYP_SETTINGS = {} + + +if "--fast" in sys.argv: + HYP_SETTINGS["max_examples"] = 20 + + def run_openssl(cmd): OPENSSL = "openssl" p = subprocess.Popen( @@ -1811,6 +1819,7 @@ def test_trytryagain_single(self): else: self.assertEqual(n, 18) + @settings(**HYP_SETTINGS) @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 From f433d49df7ecc5f2ab2365eff3ab390cbe36d7dc Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 12 Nov 2020 01:43:50 +0100 Subject: [PATCH 036/105] add mark @slow to allow skipping slowest tests as calling openssl is very slow, allow skipping tests that do that --- conftest.py | 7 +++++++ cosmic-ray.toml | 2 +- src/ecdsa/test_ecdh.py | 1 + src/ecdsa/test_jacobi.py | 2 +- src/ecdsa/test_pyecdsa.py | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 42 insertions(+), 2 deletions(-) diff --git a/conftest.py b/conftest.py index 13387580..6c163acf 100644 --- a/conftest.py +++ b/conftest.py @@ -2,3 +2,10 @@ def pytest_addoption(parser): parser.addoption( "--fast", action="store_true", default=False, help="run tests fast" ) + + +def pytest_configure(config): + config.addinivalue_line( + "markers", + "slow: mark test as slow to run (deselect with '-m \"not slow\"')", + ) diff --git a/cosmic-ray.toml b/cosmic-ray.toml index f5de84bf..3d27d1b4 100644 --- a/cosmic-ray.toml +++ b/cosmic-ray.toml @@ -3,7 +3,7 @@ module-path = "src" python-version = "" timeout = 30.0 exclude-modules = ['src/ecdsa/_version.py', 'src/ecdsa/test*'] -test-command = "pytest -x --fast src/" +test-command = "pytest -x --fast -m 'not slow' src/" [cosmic-ray.execution-engine] name = "local" diff --git a/src/ecdsa/test_ecdh.py b/src/ecdsa/test_ecdh.py index 872d4d14..3395a212 100644 --- a/src/ecdsa/test_ecdh.py +++ b/src/ecdsa/test_ecdh.py @@ -366,6 +366,7 @@ def run_openssl(cmd): ) +@pytest.mark.slow @pytest.mark.parametrize( "vcurve", curves, diff --git a/src/ecdsa/test_jacobi.py b/src/ecdsa/test_jacobi.py index 322b568c..7afe19af 100644 --- a/src/ecdsa/test_jacobi.py +++ b/src/ecdsa/test_jacobi.py @@ -348,7 +348,7 @@ def test_add_same_scale_points_static(self): self.assertEqual(c, x + y) - @settings(max_examples=14) + @settings(max_examples=10) @given( st.integers( min_value=1, max_value=int(generator_brainpoolp160r1.order() - 1) diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index 1a428478..cdaa03e2 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -1049,6 +1049,7 @@ def get_openssl_messagedigest_arg(self, hash_name): # vk: 3:OpenSSL->python 4:python->OpenSSL # sig: 5:OpenSSL->python 6:python->OpenSSL + @pytest.mark.slow @pytest.mark.skipif( "secp112r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support secp112r1", @@ -1084,6 +1085,7 @@ def test_from_openssl_secp160r1(self): def test_from_openssl_nist192p(self): return self.do_test_from_openssl(NIST192p) + @pytest.mark.slow @pytest.mark.skipif( "prime192v1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support prime192v1", @@ -1091,6 +1093,7 @@ def test_from_openssl_nist192p(self): def test_from_openssl_nist192p_sha256(self): return self.do_test_from_openssl(NIST192p, "SHA256") + @pytest.mark.slow @pytest.mark.skipif( "secp224r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support secp224r1", @@ -1098,6 +1101,7 @@ def test_from_openssl_nist192p_sha256(self): def test_from_openssl_nist224p(self): return self.do_test_from_openssl(NIST224p) + @pytest.mark.slow @pytest.mark.skipif( "prime256v1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support prime256v1", @@ -1105,6 +1109,7 @@ def test_from_openssl_nist224p(self): def test_from_openssl_nist256p(self): return self.do_test_from_openssl(NIST256p) + @pytest.mark.slow @pytest.mark.skipif( "prime256v1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support prime256v1", @@ -1112,6 +1117,7 @@ def test_from_openssl_nist256p(self): def test_from_openssl_nist256p_sha384(self): return self.do_test_from_openssl(NIST256p, "SHA384") + @pytest.mark.slow @pytest.mark.skipif( "prime256v1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support prime256v1", @@ -1119,6 +1125,7 @@ def test_from_openssl_nist256p_sha384(self): def test_from_openssl_nist256p_sha512(self): return self.do_test_from_openssl(NIST256p, "SHA512") + @pytest.mark.slow @pytest.mark.skipif( "secp384r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support secp384r1", @@ -1126,6 +1133,7 @@ def test_from_openssl_nist256p_sha512(self): def test_from_openssl_nist384p(self): return self.do_test_from_openssl(NIST384p) + @pytest.mark.slow @pytest.mark.skipif( "secp521r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support secp521r1", @@ -1133,6 +1141,7 @@ def test_from_openssl_nist384p(self): def test_from_openssl_nist521p(self): return self.do_test_from_openssl(NIST521p) + @pytest.mark.slow @pytest.mark.skipif( "secp256k1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support secp256k1", @@ -1140,6 +1149,7 @@ def test_from_openssl_nist521p(self): def test_from_openssl_secp256k1(self): return self.do_test_from_openssl(SECP256k1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP160r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP160r1", @@ -1147,6 +1157,7 @@ def test_from_openssl_secp256k1(self): def test_from_openssl_brainpoolp160r1(self): return self.do_test_from_openssl(BRAINPOOLP160r1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP192r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP192r1", @@ -1154,6 +1165,7 @@ def test_from_openssl_brainpoolp160r1(self): def test_from_openssl_brainpoolp192r1(self): return self.do_test_from_openssl(BRAINPOOLP192r1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP224r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP224r1", @@ -1161,6 +1173,7 @@ def test_from_openssl_brainpoolp192r1(self): def test_from_openssl_brainpoolp224r1(self): return self.do_test_from_openssl(BRAINPOOLP224r1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP256r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP256r1", @@ -1168,6 +1181,7 @@ def test_from_openssl_brainpoolp224r1(self): def test_from_openssl_brainpoolp256r1(self): return self.do_test_from_openssl(BRAINPOOLP256r1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP320r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP320r1", @@ -1175,6 +1189,7 @@ def test_from_openssl_brainpoolp256r1(self): def test_from_openssl_brainpoolp320r1(self): return self.do_test_from_openssl(BRAINPOOLP320r1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP384r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP384r1", @@ -1182,6 +1197,7 @@ def test_from_openssl_brainpoolp320r1(self): def test_from_openssl_brainpoolp384r1(self): return self.do_test_from_openssl(BRAINPOOLP384r1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP512r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP512r1", @@ -1290,6 +1306,7 @@ def do_test_from_openssl(self, curve, hash_name="SHA1"): sk_from_p8 = SigningKey.from_pem(privkey_p8_pem) self.assertEqual(sk, sk_from_p8) + @pytest.mark.slow @pytest.mark.skipif( "secp112r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support secp112r1", @@ -1325,6 +1342,7 @@ def test_to_openssl_secp160r1(self): def test_to_openssl_nist192p(self): self.do_test_to_openssl(NIST192p) + @pytest.mark.slow @pytest.mark.skipif( "prime192v1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support prime192v1", @@ -1332,6 +1350,7 @@ def test_to_openssl_nist192p(self): def test_to_openssl_nist192p_sha256(self): self.do_test_to_openssl(NIST192p, "SHA256") + @pytest.mark.slow @pytest.mark.skipif( "secp224r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support secp224r1", @@ -1339,6 +1358,7 @@ def test_to_openssl_nist192p_sha256(self): def test_to_openssl_nist224p(self): self.do_test_to_openssl(NIST224p) + @pytest.mark.slow @pytest.mark.skipif( "prime256v1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support prime256v1", @@ -1346,6 +1366,7 @@ def test_to_openssl_nist224p(self): def test_to_openssl_nist256p(self): self.do_test_to_openssl(NIST256p) + @pytest.mark.slow @pytest.mark.skipif( "prime256v1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support prime256v1", @@ -1353,6 +1374,7 @@ def test_to_openssl_nist256p(self): def test_to_openssl_nist256p_sha384(self): self.do_test_to_openssl(NIST256p, "SHA384") + @pytest.mark.slow @pytest.mark.skipif( "prime256v1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support prime256v1", @@ -1360,6 +1382,7 @@ def test_to_openssl_nist256p_sha384(self): def test_to_openssl_nist256p_sha512(self): self.do_test_to_openssl(NIST256p, "SHA512") + @pytest.mark.slow @pytest.mark.skipif( "secp384r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support secp384r1", @@ -1367,6 +1390,7 @@ def test_to_openssl_nist256p_sha512(self): def test_to_openssl_nist384p(self): self.do_test_to_openssl(NIST384p) + @pytest.mark.slow @pytest.mark.skipif( "secp521r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support secp521r1", @@ -1374,6 +1398,7 @@ def test_to_openssl_nist384p(self): def test_to_openssl_nist521p(self): self.do_test_to_openssl(NIST521p) + @pytest.mark.slow @pytest.mark.skipif( "secp256k1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support secp256k1", @@ -1381,6 +1406,7 @@ def test_to_openssl_nist521p(self): def test_to_openssl_secp256k1(self): self.do_test_to_openssl(SECP256k1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP160r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP160r1", @@ -1388,6 +1414,7 @@ def test_to_openssl_secp256k1(self): def test_to_openssl_brainpoolp160r1(self): self.do_test_to_openssl(BRAINPOOLP160r1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP192r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP192r1", @@ -1395,6 +1422,7 @@ def test_to_openssl_brainpoolp160r1(self): def test_to_openssl_brainpoolp192r1(self): self.do_test_to_openssl(BRAINPOOLP192r1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP224r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP224r1", @@ -1402,6 +1430,7 @@ def test_to_openssl_brainpoolp192r1(self): def test_to_openssl_brainpoolp224r1(self): self.do_test_to_openssl(BRAINPOOLP224r1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP256r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP256r1", @@ -1409,6 +1438,7 @@ def test_to_openssl_brainpoolp224r1(self): def test_to_openssl_brainpoolp256r1(self): self.do_test_to_openssl(BRAINPOOLP256r1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP320r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP320r1", @@ -1416,6 +1446,7 @@ def test_to_openssl_brainpoolp256r1(self): def test_to_openssl_brainpoolp320r1(self): self.do_test_to_openssl(BRAINPOOLP320r1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP384r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP384r1", @@ -1423,6 +1454,7 @@ def test_to_openssl_brainpoolp320r1(self): def test_to_openssl_brainpoolp384r1(self): self.do_test_to_openssl(BRAINPOOLP384r1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP512r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP512r1", From 260cc296738cf7f81e70cf94b83f257fb941aef2 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 6 Dec 2020 16:13:38 +0100 Subject: [PATCH 037/105] more test speed-ups --- src/ecdsa/test_ecdsa.py | 5 ++- src/ecdsa/test_ellipticcurve.py | 2 +- src/ecdsa/test_jacobi.py | 46 +++++++++++++++++++++---- src/ecdsa/test_malformed_sigs.py | 4 +-- src/ecdsa/test_numbertheory.py | 10 +++--- src/ecdsa/test_pyecdsa.py | 58 ++++++++++++++------------------ 6 files changed, 79 insertions(+), 46 deletions(-) diff --git a/src/ecdsa/test_ecdsa.py b/src/ecdsa/test_ecdsa.py index 2af527b6..a23373fd 100644 --- a/src/ecdsa/test_ecdsa.py +++ b/src/ecdsa/test_ecdsa.py @@ -634,7 +634,10 @@ def st_random_gen_key_msg_nonce(draw): SIG_VER_SETTINGS = dict(HYP_SETTINGS) -SIG_VER_SETTINGS["max_examples"] = 10 +if "--fast" in sys.argv: + SIG_VER_SETTINGS["max_examples"] = 1 +else: + SIG_VER_SETTINGS["max_examples"] = 10 @settings(**SIG_VER_SETTINGS) diff --git a/src/ecdsa/test_ellipticcurve.py b/src/ecdsa/test_ellipticcurve.py index f46fd9ea..33248f6e 100644 --- a/src/ecdsa/test_ellipticcurve.py +++ b/src/ecdsa/test_ellipticcurve.py @@ -40,7 +40,7 @@ HYP_SLOW_SETTINGS = dict(HYP_SETTINGS) -HYP_SLOW_SETTINGS["max_examples"] = 10 +HYP_SLOW_SETTINGS["max_examples"] = 2 @settings(**HYP_SLOW_SETTINGS) diff --git a/src/ecdsa/test_jacobi.py b/src/ecdsa/test_jacobi.py index 7afe19af..a4750455 100644 --- a/src/ecdsa/test_jacobi.py +++ b/src/ecdsa/test_jacobi.py @@ -1,4 +1,5 @@ import pickle +import sys try: import unittest2 as unittest @@ -32,6 +33,13 @@ NO_OLD_SETTINGS["deadline"] = 5000 +SLOW_SETTINGS = {} +if "--fast" in sys.argv: + SLOW_SETTINGS["max_examples"] = 2 +else: + SLOW_SETTINGS["max_examples"] = 10 + + class TestJacobi(unittest.TestCase): def test___init__(self): curve = object() @@ -199,7 +207,7 @@ def test_compare_double_with_multiply(self): self.assertEqual(dbl, mlpl) - @settings(max_examples=10) + @settings(**SLOW_SETTINGS) @given( st.integers( min_value=0, max_value=int(generator_brainpoolp160r1.order() - 1) @@ -214,7 +222,7 @@ def test_multiplications(self, mul): self.assertEqual((pj.x(), pj.y()), (pw.x(), pw.y())) self.assertEqual(pj, pw) - @settings(max_examples=10) + @settings(**SLOW_SETTINGS) @given( st.integers( min_value=0, max_value=int(generator_brainpoolp160r1.order() - 1) @@ -232,7 +240,7 @@ def test_precompute(self, mul): self.assertEqual(a, b) - @settings(max_examples=10) + @settings(**SLOW_SETTINGS) @given( st.integers( min_value=1, max_value=int(generator_brainpoolp160r1.order() - 1) @@ -251,7 +259,7 @@ def test_add_scaled_points(self, a_mul, b_mul): self.assertEqual(c, j_g * (a_mul + b_mul)) - @settings(max_examples=10) + @settings(**SLOW_SETTINGS) @given( st.integers( min_value=1, max_value=int(generator_brainpoolp160r1.order() - 1) @@ -283,7 +291,8 @@ def test_add_one_scaled_point(self, a_mul, b_mul, new_z): self.assertEqual(c, j_g * (a_mul + b_mul)) - @settings(max_examples=10) + @pytest.mark.slow + @settings(**SLOW_SETTINGS) @given( st.integers( min_value=1, max_value=int(generator_brainpoolp160r1.order() - 1) @@ -348,7 +357,8 @@ def test_add_same_scale_points_static(self): self.assertEqual(c, x + y) - @settings(max_examples=10) + @pytest.mark.slow + @settings(**SLOW_SETTINGS) @given( st.integers( min_value=1, max_value=int(generator_brainpoolp160r1.order() - 1) @@ -423,6 +433,30 @@ def test_add_different_scale_points_static(self): self.assertEqual(c, x + y) + def test_add_different_scale_points_static(self): + j_g = generator_brainpoolp160r1 + p = curve_brainpoolp160r1.p() + a = j_g * 11 + a.scale() + z1 = 13 + x = PointJacobi( + curve_brainpoolp160r1, + a.x() * z1**2 % p, + a.y() * z1**3 % p, + z1, + ) + z2 = 29 + y = PointJacobi( + curve_brainpoolp160r1, + a.x() * z2**2 % p, + a.y() * z2**3 % p, + z2, + ) + + c = a + a + + self.assertEqual(c, x + y) + def test_add_point_3_times(self): j_g = PointJacobi.from_affine(generator_256) diff --git a/src/ecdsa/test_malformed_sigs.py b/src/ecdsa/test_malformed_sigs.py index 2a318a44..e822a0fd 100644 --- a/src/ecdsa/test_malformed_sigs.py +++ b/src/ecdsa/test_malformed_sigs.py @@ -159,7 +159,7 @@ def st_fuzzed_sig(draw, keys_and_sigs): slow_params = dict(params) if "--fast" in sys.argv: - slow_params["max_examples"] = 2 + slow_params["max_examples"] = 1 else: slow_params["max_examples"] = 10 @@ -318,7 +318,7 @@ def test_random_der_as_signature(params, der): verifying_key.verify(der, example_data, sigdecode=sigdecode_der) -@settings(**params) +@settings(**slow_params) @given(st.sampled_from(keys_and_sigs), st.binary(max_size=1024**2)) @example( keys_and_sigs[0], encode_sequence(encode_integer(0), encode_integer(0)) diff --git a/src/ecdsa/test_numbertheory.py b/src/ecdsa/test_numbertheory.py index dcaa6d92..643a0148 100644 --- a/src/ecdsa/test_numbertheory.py +++ b/src/ecdsa/test_numbertheory.py @@ -67,6 +67,7 @@ def test_next_prime_with_nums_less_2(val): assert next_prime(val) == 2 +@pytest.mark.slow @pytest.mark.parametrize("prime", smallprimes) def test_square_root_mod_prime_for_small_primes(prime): squares = set() @@ -258,7 +259,7 @@ def st_comp_no_com_fac(draw): HYP_SLOW_SETTINGS = dict(HYP_SETTINGS) if "--fast" in sys.argv: - HYP_SLOW_SETTINGS["max_examples"] = 2 + HYP_SLOW_SETTINGS["max_examples"] = 1 else: HYP_SLOW_SETTINGS["max_examples"] = 20 @@ -373,7 +374,8 @@ def test_square_root_mod_prime(self, vals): calc = square_root_mod_prime(square, prime) assert calc * calc % prime == square - @settings(**HYP_SETTINGS) + @pytest.mark.slow + @settings(**HYP_SLOW_SETTINGS) @given(st.integers(min_value=1, max_value=10**12)) @example(265399 * 1526929) @example(373297**2 * 553991) @@ -410,7 +412,7 @@ def test_jacobi_with_zero(self): def test_jacobi_with_one(self): assert jacobi(1, 3) == 1 - @settings(**HYP_SETTINGS) + @settings(**HYP_SLOW_SETTINGS) @given(st.integers(min_value=3, max_value=1000).filter(lambda x: x % 2)) def test_jacobi(self, mod): if is_prime(mod): @@ -429,7 +431,7 @@ def test_jacobi(self, mod): c *= jacobi(a, i[0]) ** i[1] assert c == jacobi(a, mod) - @settings(**HYP_SETTINGS) + @settings(**HYP_SLOW_SETTINGS) @given(st_two_nums_rel_prime()) def test_inverse_mod(self, nums): num, mod = nums diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index cdaa03e2..9335631b 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -80,7 +80,7 @@ class SubprocessError(Exception): if "--fast" in sys.argv: - HYP_SETTINGS["max_examples"] = 20 + HYP_SETTINGS["max_examples"] = 2 def run_openssl(cmd): @@ -146,41 +146,13 @@ def test_bad_usage(self): self.assertRaises(TypeError, SigningKey) self.assertRaises(TypeError, VerifyingKey) - def test_lengths(self): + def test_lengths_default(self): default = NIST192p priv = SigningKey.generate() pub = priv.get_verifying_key() self.assertEqual(len(pub.to_string()), default.verifying_key_length) - sig = priv.sign(b("data")) + sig = priv.sign(b"data") self.assertEqual(len(sig), default.signature_length) - for curve in ( - NIST192p, - NIST224p, - NIST256p, - NIST384p, - NIST521p, - BRAINPOOLP160r1, - BRAINPOOLP192r1, - BRAINPOOLP224r1, - BRAINPOOLP256r1, - BRAINPOOLP320r1, - BRAINPOOLP384r1, - BRAINPOOLP512r1, - BRAINPOOLP160t1, - BRAINPOOLP192t1, - BRAINPOOLP224t1, - BRAINPOOLP256t1, - BRAINPOOLP320t1, - BRAINPOOLP384t1, - BRAINPOOLP512t1, - ): - priv = SigningKey.generate(curve=curve) - pub1 = priv.get_verifying_key() - pub2 = VerifyingKey.from_string(pub1.to_string(), curve) - self.assertEqual(pub1.to_string(), pub2.to_string()) - self.assertEqual(len(pub1.to_string()), curve.verifying_key_length) - sig = priv.sign(b("data")) - self.assertEqual(len(sig), curve.signature_length) def test_serialize(self): seed = b("secret") @@ -1013,6 +985,23 @@ def test_VerifyingKey_encode_decode(curve, encoding): assert vk.pubkey.point == from_enc.pubkey.point +if "--fast" in sys.argv: + params = [NIST192p, BRAINPOOLP160r1] +else: + params = curves + + +@pytest.mark.parametrize("curve", params) +def test_lengths(curve): + priv = SigningKey.generate(curve=curve) + pub1 = priv.get_verifying_key() + pub2 = VerifyingKey.from_string(pub1.to_string(), curve) + assert pub1.to_string() == pub2.to_string() + assert len(pub1.to_string()) == curve.verifying_key_length + sig = priv.sign(b"data") + assert len(sig) == curve.signature_length + + class OpenSSL(unittest.TestCase): # test interoperability with OpenSSL tools. Note that openssl's ECDSA # sign/verify arguments changed between 0.9.8 and 1.0.0: the early @@ -1819,6 +1808,7 @@ def test_constructed(self): class Util(unittest.TestCase): + @pytest.mark.slow def test_trytryagain(self): tta = util.randrange_from_seed__trytryagain for i in range(1000): @@ -1846,7 +1836,7 @@ def test_trytryagain_single(self): seed = b"text" n = tta(seed, order) # known issue: https://github.com/warner/python-ecdsa/issues/221 - if sys.version_info < (3, 0): # pragma: no branch + if sys.version_info < (3, 0): self.assertEqual(n, 228) else: self.assertEqual(n, 18) @@ -2184,6 +2174,7 @@ def test_brainpoolP256r1(self): ), ) + @pytest.mark.slow def test_brainpoolP384r1(self): self._do( curve=curve_brainpoolp384r1, @@ -2230,6 +2221,7 @@ def test_brainpoolP384r1(self): ), ) + @pytest.mark.slow def test_brainpoolP512r1(self): self._do( curve=curve_brainpoolp512r1, @@ -2334,6 +2326,7 @@ def test_brainpoolP256r1(self): ), ) + @pytest.mark.slow def test_brainpoolP384r1(self): self._do( curve=curve_brainpoolp384r1, @@ -2380,6 +2373,7 @@ def test_brainpoolP384r1(self): ), ) + @pytest.mark.slow def test_brainpoolP512r1(self): self._do( curve=curve_brainpoolp512r1, From 724535a14588b7e04e3308ff8761b00f5dc86560 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 6 Dec 2020 16:46:41 +0100 Subject: [PATCH 038/105] extend the cosmic-ray testing --- .travis.yml | 4 ++-- cosmic-ray.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 009656c5..931b1ad7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -174,8 +174,8 @@ script: cr-report session.sqlite | tail -n 3; done & REPORT_PID=$! - # kill exec after 25 minutes - (sleep $((60*25)); kill $COSMIC_PID) & + # kill exec after 40 minutes + (sleep $((60*40)); kill $COSMIC_PID) & fi - if [[ $MUTATION ]]; then wait $COSMIC_PID ; kill $REPORT_PID ; true; fi - if [[ $MUTATION ]]; then cr-report --show-output session.sqlite | tail -n 40; fi diff --git a/cosmic-ray.toml b/cosmic-ray.toml index 3d27d1b4..09f364aa 100644 --- a/cosmic-ray.toml +++ b/cosmic-ray.toml @@ -1,7 +1,7 @@ [cosmic-ray] module-path = "src" python-version = "" -timeout = 30.0 +timeout = 20.0 exclude-modules = ['src/ecdsa/_version.py', 'src/ecdsa/test*'] test-command = "pytest -x --fast -m 'not slow' src/" From cbf086e602f04c873b64b4b4f0a3174db8bb3857 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 6 Dec 2020 17:46:00 +0100 Subject: [PATCH 039/105] how to run cosmic-ray comfortably --- cosmic-ray.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cosmic-ray.sh b/cosmic-ray.sh index ba512d50..d9aea619 100644 --- a/cosmic-ray.sh +++ b/cosmic-ray.sh @@ -4,6 +4,8 @@ set -e cosmic-ray init cosmic-ray.toml session.sqlite cosmic-ray baseline --report session.sqlite cr-report --show-output session.baseline.sqlite +# some mutations cause huge memory use, so put it in a cgroup +# systemd-run --user --scope -p MemoryMax=2G -p MemoryHigh=2G cosmic-ray exec session.sqlite cosmic-ray exec session.sqlite cr-report session.sqlite cr-html session.sqlite > session.html From 6ee5257f7a34e202d55fad17931aee0008ab608a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 6 Dec 2020 20:40:43 +0100 Subject: [PATCH 040/105] increase the survival rate limit currently we have 28.88% survival rate, so set that as the baseline --- .travis.yml | 2 +- cosmic-ray.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 931b1ad7..8729ca80 100644 --- a/.travis.yml +++ b/.travis.yml @@ -179,7 +179,7 @@ script: fi - if [[ $MUTATION ]]; then wait $COSMIC_PID ; kill $REPORT_PID ; true; fi - if [[ $MUTATION ]]; then cr-report --show-output session.sqlite | tail -n 40; fi - - if [[ $MUTATION ]]; then cr-rate --estimate --fail-over 25 --confidence 99.9 session.sqlite; fi + - if [[ $MUTATION ]]; then cr-rate --estimate --fail-over 29 --confidence 99.9 session.sqlite; fi after_success: - if [[ -z $INSTRUMENTAL && -z $MUTATION ]]; then coveralls; fi diff --git a/cosmic-ray.sh b/cosmic-ray.sh index d9aea619..384750a3 100644 --- a/cosmic-ray.sh +++ b/cosmic-ray.sh @@ -9,4 +9,4 @@ cr-report --show-output session.baseline.sqlite cosmic-ray exec session.sqlite cr-report session.sqlite cr-html session.sqlite > session.html -cr-rate --estimate --fail-over 25 --confidence 99.9 session.sqlite +cr-rate --estimate --fail-over 29 --confidence 99.9 session.sqlite From 082a403b108b3cca81bfc976bd34f8f820d42572 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 7 Dec 2020 00:31:37 +0100 Subject: [PATCH 041/105] waive test coverage for mutation speedups the branches in test coverage that are there to speed up mutation testing don't have to be covered by regular testing so waive them --- src/ecdsa/test_der.py | 2 +- src/ecdsa/test_ecdsa.py | 2 +- src/ecdsa/test_jacobi.py | 2 +- src/ecdsa/test_malformed_sigs.py | 4 ++-- src/ecdsa/test_numbertheory.py | 4 ++-- src/ecdsa/test_pyecdsa.py | 8 ++++---- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ecdsa/test_der.py b/src/ecdsa/test_der.py index f450baf8..f08ea573 100644 --- a/src/ecdsa/test_der.py +++ b/src/ecdsa/test_der.py @@ -466,7 +466,7 @@ def st_oid(draw, max_value=2**512, max_size=50): HYP_SETTINGS = {} -if "--fast" in sys.argv: +if "--fast" in sys.argv: # pragma: no cover HYP_SETTINGS["max_examples"] = 2 diff --git a/src/ecdsa/test_ecdsa.py b/src/ecdsa/test_ecdsa.py index a23373fd..29f05b8f 100644 --- a/src/ecdsa/test_ecdsa.py +++ b/src/ecdsa/test_ecdsa.py @@ -634,7 +634,7 @@ def st_random_gen_key_msg_nonce(draw): SIG_VER_SETTINGS = dict(HYP_SETTINGS) -if "--fast" in sys.argv: +if "--fast" in sys.argv: # pragma: no cover SIG_VER_SETTINGS["max_examples"] = 1 else: SIG_VER_SETTINGS["max_examples"] = 10 diff --git a/src/ecdsa/test_jacobi.py b/src/ecdsa/test_jacobi.py index a4750455..ed598bb3 100644 --- a/src/ecdsa/test_jacobi.py +++ b/src/ecdsa/test_jacobi.py @@ -34,7 +34,7 @@ SLOW_SETTINGS = {} -if "--fast" in sys.argv: +if "--fast" in sys.argv: # pragma: no cover SLOW_SETTINGS["max_examples"] = 2 else: SLOW_SETTINGS["max_examples"] = 10 diff --git a/src/ecdsa/test_malformed_sigs.py b/src/ecdsa/test_malformed_sigs.py index e822a0fd..d7050d2f 100644 --- a/src/ecdsa/test_malformed_sigs.py +++ b/src/ecdsa/test_malformed_sigs.py @@ -154,11 +154,11 @@ def st_fuzzed_sig(draw, keys_and_sigs): HealthCheck.filter_too_much, HealthCheck.too_slow, ] -if "--fast" in sys.argv: +if "--fast" in sys.argv: # pragma: no cover params["max_examples"] = 20 slow_params = dict(params) -if "--fast" in sys.argv: +if "--fast" in sys.argv: # pragma: no cover slow_params["max_examples"] = 1 else: slow_params["max_examples"] = 10 diff --git a/src/ecdsa/test_numbertheory.py b/src/ecdsa/test_numbertheory.py index 643a0148..983039e5 100644 --- a/src/ecdsa/test_numbertheory.py +++ b/src/ecdsa/test_numbertheory.py @@ -253,12 +253,12 @@ def st_comp_no_com_fac(draw): # the factorization() sometimes takes a long time to finish HYP_SETTINGS["deadline"] = 5000 -if "--fast" in sys.argv: +if "--fast" in sys.argv: # pragma: no cover HYP_SETTINGS["max_examples"] = 20 HYP_SLOW_SETTINGS = dict(HYP_SETTINGS) -if "--fast" in sys.argv: +if "--fast" in sys.argv: # pragma: no cover HYP_SLOW_SETTINGS["max_examples"] = 1 else: HYP_SLOW_SETTINGS["max_examples"] = 20 diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index 9335631b..579a5738 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -79,7 +79,7 @@ class SubprocessError(Exception): HYP_SETTINGS = {} -if "--fast" in sys.argv: +if "--fast" in sys.argv: # pragma: no cover HYP_SETTINGS["max_examples"] = 2 @@ -985,7 +985,7 @@ def test_VerifyingKey_encode_decode(curve, encoding): assert vk.pubkey.point == from_enc.pubkey.point -if "--fast" in sys.argv: +if "--fast" in sys.argv: # pragma: no cover params = [NIST192p, BRAINPOOLP160r1] else: params = curves @@ -1836,9 +1836,9 @@ def test_trytryagain_single(self): seed = b"text" n = tta(seed, order) # known issue: https://github.com/warner/python-ecdsa/issues/221 - if sys.version_info < (3, 0): + if sys.version_info < (3, 0): # pragma: no branch self.assertEqual(n, 228) - else: + else: # pragma: no branch self.assertEqual(n, 18) @settings(**HYP_SETTINGS) From c2a1c1631f70f3b9273f061be49bb94e33a103c0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 7 Dec 2020 03:28:58 +0100 Subject: [PATCH 042/105] ensure that the equality tests are collected by instrumental --- src/ecdsa/test_keys.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py index 4d9e976e..a41a3dbc 100644 --- a/src/ecdsa/test_keys.py +++ b/src/ecdsa/test_keys.py @@ -249,13 +249,13 @@ def test_array_array_of_bytes_memoryview(self): self.assertEqual(self.vk.to_string(), vk.to_string()) def test_equality_on_verifying_keys(self): - self.assertEqual(self.vk, self.sk.get_verifying_key()) + self.assertTrue(self.vk == self.sk.get_verifying_key()) def test_inequality_on_verifying_keys(self): - self.assertNotEqual(self.vk, self.vk2) + self.assertFalse(self.vk == self.vk2) def test_inequality_on_verifying_keys_not_implemented(self): - self.assertNotEqual(self.vk, None) + self.assertFalse(self.vk == None) def test_VerifyingKey_inequality_on_same_curve(self): self.assertNotEqual(self.vk, self.sk2.verifying_key) @@ -264,7 +264,7 @@ def test_SigningKey_inequality_on_same_curve(self): self.assertNotEqual(self.sk, self.sk2) def test_inequality_on_wrong_types(self): - self.assertNotEqual(self.vk, self.sk) + self.assertFalse(self.vk == self.sk) def test_from_public_point_old(self): pj = self.vk.pubkey.point @@ -272,7 +272,7 @@ def test_from_public_point_old(self): vk = VerifyingKey.from_public_point(point, self.vk.curve) - self.assertEqual(vk, self.vk) + self.assertTrue(vk == self.vk) def test_ed25519_VerifyingKey_repr__(self): sk = SigningKey.from_string(Ed25519.generator.to_bytes(), Ed25519) @@ -970,14 +970,14 @@ def test_VerifyingKey_inequality_with_different_curves(): sk1 = SigningKey.from_secret_exponent(2, BRAINPOOLP160r1) sk2 = SigningKey.from_secret_exponent(2, NIST256p) - assert sk1.verifying_key != sk2.verifying_key + assert not (sk1.verifying_key == sk2.verifying_key) def test_VerifyingKey_inequality_with_different_secret_points(): sk1 = SigningKey.from_secret_exponent(2, BRAINPOOLP160r1) sk2 = SigningKey.from_secret_exponent(3, BRAINPOOLP160r1) - assert sk1.verifying_key != sk2.verifying_key + assert not (sk1.verifying_key == sk2.verifying_key) def test_SigningKey_from_pem_pkcs8v2_EdDSA(): From c17d7e7a002cba75d807f20aebe1874a285ff8de Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 Dec 2023 18:04:51 +0100 Subject: [PATCH 043/105] test verification with plain Point PointJacobi has a mul_add() method, but Point doesn't, verify that verifies() works with both point encodings --- src/ecdsa/test_ecdsa.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/ecdsa/test_ecdsa.py b/src/ecdsa/test_ecdsa.py index 29f05b8f..6798a7e6 100644 --- a/src/ecdsa/test_ecdsa.py +++ b/src/ecdsa/test_ecdsa.py @@ -27,6 +27,7 @@ generator_112r2, int_to_string, ) +from .ellipticcurve import Point HYP_SETTINGS = {} @@ -76,6 +77,19 @@ def test_verification(self): def test_rejection(self): assert not self.pubk.verifies(self.msg - 1, self.sig) + def test_verification_with_regular_point(self): + pubk = Public_key( + Point( + generator_192.curve(), + generator_192.x(), + generator_192.y(), + generator_192.order(), + ), + self.pubk.point, + ) + + assert pubk.verifies(self.msg, self.sig) + class TestPublicKey(unittest.TestCase): def test_equality_public_keys(self): From c98364927e406af037d8f9652ff18094f207dcec Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 Dec 2023 18:45:59 +0100 Subject: [PATCH 044/105] deprecate digest_integer() and related methods --- src/ecdsa/ecdsa.py | 26 +++++++++++++++++++++++--- src/ecdsa/test_ecdsa.py | 22 +++++++++++++++++++--- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/ecdsa/ecdsa.py b/src/ecdsa/ecdsa.py index 5a891ec9..7ac76c2f 100644 --- a/src/ecdsa/ecdsa.py +++ b/src/ecdsa/ecdsa.py @@ -64,6 +64,7 @@ modified as part of the python-ecdsa package. """ +import warnings from six import int2byte, b from . import ellipticcurve from . import numbertheory @@ -269,8 +270,14 @@ def sign(self, hash, random_k): return Signature(r, s) -def int_to_string(x): +def int_to_string(x): # pragma: no cover """Convert integer x into a string of bytes, as per X9.62.""" + # deprecated in 0.19 + warnings.warn( + "Function is unused in library code. If you use this code, " + "change to util.string_to_number.", + DeprecationWarning, + ) assert x >= 0 if x == 0: return b("\0") @@ -284,8 +291,14 @@ def int_to_string(x): return b("").join(result) -def string_to_int(s): +def string_to_int(s): # pragma: no cover """Convert a string of bytes into an integer, as per X9.62.""" + # deprecated in 0.19 + warnings.warn( + "Function is unused in library code. If you use this code, " + "change to util.number_to_string.", + DeprecationWarning, + ) result = 0 for c in s: if not isinstance(c, int): @@ -294,9 +307,16 @@ def string_to_int(s): return result -def digest_integer(m): +def digest_integer(m): # pragma: no cover """Convert an integer into a string of bytes, compute its SHA-1 hash, and convert the result to an integer.""" + # deprecated in 0.19 + warnings.warn( + "Function is unused in library code. If you use this code, " + "change to a one-liner with util.number_to_string and " + "util.string_to_number methods.", + DeprecationWarning, + ) # # I don't expect this function to be used much. I wrote # it in order to be able to duplicate the examples diff --git a/src/ecdsa/test_ecdsa.py b/src/ecdsa/test_ecdsa.py index 6798a7e6..c1e25829 100644 --- a/src/ecdsa/test_ecdsa.py +++ b/src/ecdsa/test_ecdsa.py @@ -598,7 +598,13 @@ def test_signature_validity(gen, msg, qx, qy, r, s, expected): elliptic curve of `gen`, `r` and `s` are the signature, and `expected` is True iff the signature is expected to be valid.""" pubk = Public_key(gen, ellipticcurve.Point(gen.curve(), qx, qy)) - assert expected == pubk.verifies(digest_integer(msg), Signature(r, s)) + with pytest.warns(DeprecationWarning) as warns: + msg_dgst = digest_integer(msg) + assert len(warns) == 3 + assert "unused" in warns[0].message.args[0] + assert "unused" in warns[1].message.args[0] + assert "unused" in warns[2].message.args[0] + assert expected == pubk.verifies(msg_dgst, Signature(r, s)) @pytest.mark.parametrize( @@ -607,7 +613,13 @@ def test_signature_validity(gen, msg, qx, qy, r, s, expected): def test_pk_recovery(gen, msg, r, s, qx, qy, expected): del expected sign = Signature(r, s) - pks = sign.recover_public_keys(digest_integer(msg), gen) + with pytest.warns(DeprecationWarning) as warns: + msg_dgst = digest_integer(msg) + assert len(warns) == 3 + assert "unused" in warns[0].message.args[0] + assert "unused" in warns[1].message.args[0] + assert "unused" in warns[2].message.args[0] + pks = sign.recover_public_keys(msg_dgst, gen) assert pks @@ -675,4 +687,8 @@ def test_sig_verify(args): def test_int_to_string_with_zero(): - assert int_to_string(0) == b"\x00" + with pytest.warns(DeprecationWarning) as warns: + assert int_to_string(0) == b"\x00" + + assert len(warns) == 1 + assert "unused" in warns[0].message.args[0] From f369620060ed254954d43f56e5b1967ea8a0231c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 Dec 2023 18:58:59 +0100 Subject: [PATCH 045/105] test coverage for Edwards key public_point() method --- src/ecdsa/test_eddsa.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/ecdsa/test_eddsa.py b/src/ecdsa/test_eddsa.py index a125b94e..d398d033 100644 --- a/src/ecdsa/test_eddsa.py +++ b/src/ecdsa/test_eddsa.py @@ -259,6 +259,22 @@ def test_add_wrong_point_type(self): self.assertIn("different curve", str(e.exception)) +def test_generate_with_point(): + x1 = int( + "427838232691226969392843410947554224151809796397784248136826" + "78720006717057747" + ) + y1 = int( + "463168356949264781694283940034751631413079938662562256157830" + "33603165251855960" + ) + p = PointEdwards(curve_ed25519, x1, y1, 1, x1 * y1) + + pk = PublicKey(generator_ed25519, b"0" * 32, public_point=p) + + assert pk.public_point() == p + + def test_ed25519_mul_to_order_min_1(): x1 = int( "427838232691226969392843410947554224151809796397784248136826" From c5b7c7acc9bce4b5cb08e357ea9cf6aa547ee407 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 Dec 2023 20:39:56 +0100 Subject: [PATCH 046/105] test coverage for CurveFp.__str__ --- src/ecdsa/ellipticcurve.py | 10 ++++++++-- src/ecdsa/test_ellipticcurve.py | 7 +++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/ecdsa/ellipticcurve.py b/src/ecdsa/ellipticcurve.py index af2aaeab..942094a0 100644 --- a/src/ecdsa/ellipticcurve.py +++ b/src/ecdsa/ellipticcurve.py @@ -136,11 +136,17 @@ def contains_point(self, x, y): return (y * y - ((x * x + self.__a) * x + self.__b)) % self.__p == 0 def __str__(self): - return "CurveFp(p=%d, a=%d, b=%d, h=%d)" % ( + if self.__h is not None: + return "CurveFp(p={0}, a={1}, b={2}, h={3})".format( + self.__p, + self.__a, + self.__b, + self.__h, + ) + return "CurveFp(p={0}, a={1}, b={2})".format( self.__p, self.__a, self.__b, - self.__h, ) diff --git a/src/ecdsa/test_ellipticcurve.py b/src/ecdsa/test_ellipticcurve.py index 33248f6e..0e07c25e 100644 --- a/src/ecdsa/test_ellipticcurve.py +++ b/src/ecdsa/test_ellipticcurve.py @@ -97,6 +97,13 @@ def test_conflation_curves(self): self.assertDictEqual({c_23: None}, {eq1: None}) self.assertIn(eq2, {eq3: None}) + def test___str__(self): + self.assertEqual(str(self.c_23), "CurveFp(p=23, a=1, b=1)") + + def test___str___with_cofactor(self): + c = CurveFp(23, 1, 1, 4) + self.assertEqual(str(c), "CurveFp(p=23, a=1, b=1, h=4)") + class TestPoint(unittest.TestCase): @classmethod From 8125ca2b8e1eb24d159f80f7c734b63b40621c09 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 Dec 2023 20:47:43 +0100 Subject: [PATCH 047/105] test coverage for CurveEdTw() __str__ --- src/ecdsa/ellipticcurve.py | 10 ++++++++-- src/ecdsa/test_ellipticcurve.py | 15 ++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/ecdsa/ellipticcurve.py b/src/ecdsa/ellipticcurve.py index 942094a0..067a149c 100644 --- a/src/ecdsa/ellipticcurve.py +++ b/src/ecdsa/ellipticcurve.py @@ -225,11 +225,17 @@ def cofactor(self): return self.__h def __str__(self): - return "CurveEdTw(p={0}, a={1}, d={2}, h={3})".format( + if self.__h is not None: + return "CurveEdTw(p={0}, a={1}, d={2}, h={3})".format( + self.__p, + self.__a, + self.__d, + self.__h, + ) + return "CurveEdTw(p={0}, a={1}, d={2})".format( self.__p, self.__a, self.__d, - self.__h, ) diff --git a/src/ecdsa/test_ellipticcurve.py b/src/ecdsa/test_ellipticcurve.py index 0e07c25e..6e121546 100644 --- a/src/ecdsa/test_ellipticcurve.py +++ b/src/ecdsa/test_ellipticcurve.py @@ -14,7 +14,7 @@ except ImportError: # pragma: no cover HC_PRESENT = False from .numbertheory import inverse_mod -from .ellipticcurve import CurveFp, INFINITY, Point +from .ellipticcurve import CurveFp, INFINITY, Point, CurveEdTw HYP_SETTINGS = {} @@ -105,6 +105,19 @@ def test___str___with_cofactor(self): self.assertEqual(str(c), "CurveFp(p=23, a=1, b=1, h=4)") +class TestCurveEdTw(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.c_23 = CurveEdTw(23, 1, 1) + + def test___str__(self): + self.assertEqual(str(self.c_23), "CurveEdTw(p=23, a=1, d=1)") + + def test___str___with_cofactor(self): + c = CurveEdTw(23, 1, 1, 4) + self.assertEqual(str(c), "CurveEdTw(p=23, a=1, d=1, h=4)") + + class TestPoint(unittest.TestCase): @classmethod def setUpClass(cls): From 6b56848ba366465013dec7fdc7f52089481a3260 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 Dec 2023 21:45:45 +0100 Subject: [PATCH 048/105] ensure CurveEdTw hashability --- src/ecdsa/test_ellipticcurve.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ecdsa/test_ellipticcurve.py b/src/ecdsa/test_ellipticcurve.py index 6e121546..9fac7eed 100644 --- a/src/ecdsa/test_ellipticcurve.py +++ b/src/ecdsa/test_ellipticcurve.py @@ -117,6 +117,12 @@ def test___str___with_cofactor(self): c = CurveEdTw(23, 1, 1, 4) self.assertEqual(str(c), "CurveEdTw(p=23, a=1, d=1, h=4)") + def test_usability_in_a_hashed_collection_curves(self): + {self.c_23: None} + + def test_hashability_curves(self): + hash(self.c_23) + class TestPoint(unittest.TestCase): @classmethod From 27b13109404449bd05a674c86cfea391099b77c0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 Dec 2023 21:54:12 +0100 Subject: [PATCH 049/105] more complete test coverage for Jacobi points without hypothesis --- src/ecdsa/test_jacobi.py | 67 ++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/src/ecdsa/test_jacobi.py b/src/ecdsa/test_jacobi.py index ed598bb3..efe298e8 100644 --- a/src/ecdsa/test_jacobi.py +++ b/src/ecdsa/test_jacobi.py @@ -23,6 +23,7 @@ generator_brainpoolp160r1, curve_brainpoolp160r1, generator_112r2, + curve_112r2, ) from .numbertheory import inverse_mod from .util import randrange @@ -433,30 +434,78 @@ def test_add_different_scale_points_static(self): self.assertEqual(c, x + y) - def test_add_different_scale_points_static(self): + def test_add_different_points_same_scale_static(self): j_g = generator_brainpoolp160r1 p = curve_brainpoolp160r1.p() a = j_g * 11 a.scale() - z1 = 13 + b = j_g * 12 + z = 13 x = PointJacobi( curve_brainpoolp160r1, - a.x() * z1**2 % p, - a.y() * z1**3 % p, - z1, + a.x() * z**2 % p, + a.y() * z**3 % p, + z, ) - z2 = 29 y = PointJacobi( curve_brainpoolp160r1, - a.x() * z2**2 % p, - a.y() * z2**3 % p, - z2, + b.x() * z**2 % p, + b.y() * z**3 % p, + z, + ) + + c = a + b + + self.assertEqual(c, x + y) + + def test_add_same_point_different_scale_second_z_1_static(self): + j_g = generator_112r2 + p = curve_112r2.p() + z = 11 + a = j_g * z + a.scale() + + x = PointJacobi( + curve_112r2, + a.x() * z**2 % p, + a.y() * z**3 % p, + z, + ) + y = PointJacobi( + curve_112r2, + a.x(), + a.y(), + 1, ) c = a + a self.assertEqual(c, x + y) + def test_add_to_infinity_static(self): + j_g = generator_112r2 + + z = 11 + a = j_g * z + a.scale() + + b = -a + + x = PointJacobi( + curve_112r2, + a.x(), + a.y(), + 1, + ) + y = PointJacobi( + curve_112r2, + b.x(), + b.y(), + 1, + ) + + self.assertEqual(INFINITY, x + y) + def test_add_point_3_times(self): j_g = PointJacobi.from_affine(generator_256) From 55d73214d372ff41e6c7901170ecb3b5785eb0ad Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 Dec 2023 23:28:13 +0100 Subject: [PATCH 050/105] mark multithreaded tests as slow --- src/ecdsa/test_jacobi.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ecdsa/test_jacobi.py b/src/ecdsa/test_jacobi.py index efe298e8..2100cc16 100644 --- a/src/ecdsa/test_jacobi.py +++ b/src/ecdsa/test_jacobi.py @@ -640,6 +640,7 @@ 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) + @pytest.mark.slow @settings(**NO_OLD_SETTINGS) @pytest.mark.skipif( platform.python_implementation() == "PyPy", @@ -678,6 +679,7 @@ def runner(generator): generator_112r2._PointJacobi__precompute, ) + @pytest.mark.slow @pytest.mark.skipif( platform.system() == "Windows" or platform.python_implementation() == "PyPy", From 0cfc121ca29de56cb6159ab09e5a285d1a05524a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Dec 2023 01:07:13 +0100 Subject: [PATCH 051/105] mark all openssl calling tests as slow --- src/ecdsa/test_pyecdsa.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index 579a5738..069f3e0c 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -1002,6 +1002,7 @@ def test_lengths(curve): assert len(sig) == curve.signature_length +@pytest.mark.slow class OpenSSL(unittest.TestCase): # test interoperability with OpenSSL tools. Note that openssl's ECDSA # sign/verify arguments changed between 0.9.8 and 1.0.0: the early @@ -1046,6 +1047,7 @@ def get_openssl_messagedigest_arg(self, hash_name): def test_from_openssl_secp112r1(self): return self.do_test_from_openssl(SECP112r1) + @pytest.mark.slow @pytest.mark.skipif( "secp112r2" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support secp112r2", @@ -1053,6 +1055,7 @@ def test_from_openssl_secp112r1(self): def test_from_openssl_secp112r2(self): return self.do_test_from_openssl(SECP112r2) + @pytest.mark.slow @pytest.mark.skipif( "secp128r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support secp128r1", @@ -1060,6 +1063,7 @@ def test_from_openssl_secp112r2(self): def test_from_openssl_secp128r1(self): return self.do_test_from_openssl(SECP128r1) + @pytest.mark.slow @pytest.mark.skipif( "secp160r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support secp160r1", @@ -1067,6 +1071,7 @@ def test_from_openssl_secp128r1(self): def test_from_openssl_secp160r1(self): return self.do_test_from_openssl(SECP160r1) + @pytest.mark.slow @pytest.mark.skipif( "prime192v1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support prime192v1", @@ -1194,6 +1199,7 @@ def test_from_openssl_brainpoolp384r1(self): def test_from_openssl_brainpoolp512r1(self): return self.do_test_from_openssl(BRAINPOOLP512r1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP160t1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP160t1", @@ -1201,6 +1207,7 @@ def test_from_openssl_brainpoolp512r1(self): def test_from_openssl_brainpoolp160t1(self): return self.do_test_from_openssl(BRAINPOOLP160t1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP192t1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP192t1", @@ -1208,6 +1215,7 @@ def test_from_openssl_brainpoolp160t1(self): def test_from_openssl_brainpoolp192t1(self): return self.do_test_from_openssl(BRAINPOOLP192t1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP224t1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP224t1", @@ -1215,6 +1223,7 @@ def test_from_openssl_brainpoolp192t1(self): def test_from_openssl_brainpoolp224t1(self): return self.do_test_from_openssl(BRAINPOOLP224t1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP256t1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP256t1", @@ -1222,6 +1231,7 @@ def test_from_openssl_brainpoolp224t1(self): def test_from_openssl_brainpoolp256t1(self): return self.do_test_from_openssl(BRAINPOOLP256t1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP320t1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP320t1", @@ -1229,6 +1239,7 @@ def test_from_openssl_brainpoolp256t1(self): def test_from_openssl_brainpoolp320t1(self): return self.do_test_from_openssl(BRAINPOOLP320t1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP384t1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP384t1", @@ -1236,6 +1247,7 @@ def test_from_openssl_brainpoolp320t1(self): def test_from_openssl_brainpoolp384t1(self): return self.do_test_from_openssl(BRAINPOOLP384t1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP512t1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP512t1", @@ -1303,6 +1315,7 @@ def do_test_from_openssl(self, curve, hash_name="SHA1"): def test_to_openssl_secp112r1(self): self.do_test_to_openssl(SECP112r1) + @pytest.mark.slow @pytest.mark.skipif( "secp112r2" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support secp112r2", @@ -1310,6 +1323,7 @@ def test_to_openssl_secp112r1(self): def test_to_openssl_secp112r2(self): self.do_test_to_openssl(SECP112r2) + @pytest.mark.slow @pytest.mark.skipif( "secp128r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support secp128r1", @@ -1317,6 +1331,7 @@ def test_to_openssl_secp112r2(self): def test_to_openssl_secp128r1(self): self.do_test_to_openssl(SECP128r1) + @pytest.mark.slow @pytest.mark.skipif( "secp160r1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support secp160r1", @@ -1324,6 +1339,7 @@ def test_to_openssl_secp128r1(self): def test_to_openssl_secp160r1(self): self.do_test_to_openssl(SECP160r1) + @pytest.mark.slow @pytest.mark.skipif( "prime192v1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support prime192v1", @@ -1451,6 +1467,7 @@ def test_to_openssl_brainpoolp384r1(self): def test_to_openssl_brainpoolp512r1(self): self.do_test_to_openssl(BRAINPOOLP512r1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP160t1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP160t1", @@ -1458,6 +1475,7 @@ def test_to_openssl_brainpoolp512r1(self): def test_to_openssl_brainpoolp160t1(self): self.do_test_to_openssl(BRAINPOOLP160t1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP192t1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP192t1", @@ -1465,6 +1483,7 @@ def test_to_openssl_brainpoolp160t1(self): def test_to_openssl_brainpoolp192t1(self): self.do_test_to_openssl(BRAINPOOLP192t1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP224t1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP224t1", @@ -1472,6 +1491,7 @@ def test_to_openssl_brainpoolp192t1(self): def test_to_openssl_brainpoolp224t1(self): self.do_test_to_openssl(BRAINPOOLP224t1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP256t1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP256t1", @@ -1479,6 +1499,7 @@ def test_to_openssl_brainpoolp224t1(self): def test_to_openssl_brainpoolp256t1(self): self.do_test_to_openssl(BRAINPOOLP256t1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP320t1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP320t1", @@ -1486,6 +1507,7 @@ def test_to_openssl_brainpoolp256t1(self): def test_to_openssl_brainpoolp320t1(self): self.do_test_to_openssl(BRAINPOOLP320t1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP384t1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP384t1", @@ -1493,6 +1515,7 @@ def test_to_openssl_brainpoolp320t1(self): def test_to_openssl_brainpoolp384t1(self): self.do_test_to_openssl(BRAINPOOLP384t1) + @pytest.mark.slow @pytest.mark.skipif( "brainpoolP512t1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support brainpoolP512t1", @@ -1633,6 +1656,7 @@ def do_eddsa_test_to_openssl(self, curve): # in practice at least OpenSSL 3.0.0 is needed to make EdDSA signatures # earlier versions support EdDSA only in X.509 certificates + @pytest.mark.slow @pytest.mark.skipif( "ed25519" not in OPENSSL_SUPPORTED_TYPES, reason="system openssl does not support signing with Ed25519", @@ -1640,6 +1664,7 @@ def do_eddsa_test_to_openssl(self, curve): def test_to_openssl_ed25519(self): return self.do_eddsa_test_to_openssl(Ed25519) + @pytest.mark.slow @pytest.mark.skipif( "ed448" not in OPENSSL_SUPPORTED_TYPES, reason="system openssl does not support signing with Ed448", @@ -1683,6 +1708,7 @@ def do_eddsa_test_from_openssl(self, curve): shutil.rmtree("t") + @pytest.mark.slow @pytest.mark.skipif( "ed25519" not in OPENSSL_SUPPORTED_TYPES, reason="system openssl does not support signing with Ed25519", @@ -1690,6 +1716,7 @@ def do_eddsa_test_from_openssl(self, curve): def test_from_openssl_ed25519(self): return self.do_eddsa_test_from_openssl(Ed25519) + @pytest.mark.slow @pytest.mark.skipif( "ed448" not in OPENSSL_SUPPORTED_TYPES, reason="system openssl does not support signing with Ed448", From 00563070d1726052eed1c9945a7d9be78a506ec7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Dec 2023 01:46:50 +0100 Subject: [PATCH 052/105] quicker encode-decode tests --- src/ecdsa/test_eddsa.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ecdsa/test_eddsa.py b/src/ecdsa/test_eddsa.py index d398d033..1a35fb32 100644 --- a/src/ecdsa/test_eddsa.py +++ b/src/ecdsa/test_eddsa.py @@ -1,3 +1,4 @@ +import sys import pickle import hashlib import pytest @@ -663,7 +664,10 @@ def test_invalid_r_value(self): HYP_SETTINGS = dict() -HYP_SETTINGS["max_examples"] = 10 +if "--fast" in sys.argv: + HYP_SETTINGS["max_examples"] = 2 +else: + HYP_SETTINGS["max_examples"] = 10 @settings(**HYP_SETTINGS) From 4203e7e7b88763540e27024291f247a9760b5070 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Dec 2023 17:37:09 +0100 Subject: [PATCH 053/105] Create SECURITY.md Add formal security policy to the project. --- SECURITY.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..f5dbcb48 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,48 @@ +# Security Policy + +## Supported Versions + +Only the latest released version is supported. +Alpha and beta releases are always unsupported with security fixes. + +The project uses semantic versioning, as such, minor version changes are API compatible. + +| Version | Supported | +| -------- | ------------------ | +| 0.18.x | :white_check_mark: | +| < 0.18 | :x: | + +## Support Scope + +This library was not designed with security in mind. If you are processing data that needs +to be protected we suggest you use a quality wrapper around OpenSSL. +[`pyca/cryptography`](https://cryptography.io/) is one example of such a wrapper. +The primary use-case of this library is as a portable library for interoperability testing +and as a teaching tool. + +**This library does not protect against side-channel attacks.** + +Do not allow attackers to measure how long it takes you to generate a key pair or sign a message. +Do not allow attackers to run code on the same physical machine when key pair generation or +signing is taking place (this includes virtual machines). +Do not allow attackers to measure how much power your computer uses while generating the key pair +or signing a message. Do not allow attackers to measure RF interference coming from your computer +while generating a key pair or signing a message. Note: just loading the private key will cause +key pair generation. Other operations or attack vectors may also be vulnerable to attacks. +For a sophisticated attacker observing just one operation with a private key will be sufficient +to completely reconstruct the private key. + +Fixes for side-channel vulerabilities will not be developed. + +Please also note that any Pure-python cryptographic library will be vulnerable to the same +side-channel attacks. This is because Python does not provide side-channel secure primitives +(with the exception of [`hmac.compare_digest()`](https://docs.python.org/3/library/hmac.html#hmac.compare_digest)), +making side-channel secure programming impossible. + +This library depends upon a strong source of random numbers. Do not use it on a system +where `os.urandom()` does not provide cryptographically secure random numbers. + +## Reporting a Vulnerability + +If you find a security vulnerability in this library, you can report it using the "Report a vulnerability" button on the Security tab in github UI. +Alternatively, you can contact the project maintainer at hkario at redhat dot com. From 159be34d0d5858d6218a298ff9e97c2e601577f4 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Dec 2023 19:40:03 +0100 Subject: [PATCH 054/105] update examples for the new cosmic-ray --- cosmic-ray-12way.sh | 13 +++++++++++++ cosmic-ray-12way.toml | 28 ++++++++++++++++++++++++++++ cosmic-ray.sh | 6 +++--- cosmic-ray.toml | 15 ++++----------- 4 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 cosmic-ray-12way.sh create mode 100644 cosmic-ray-12way.toml diff --git a/cosmic-ray-12way.sh b/cosmic-ray-12way.sh new file mode 100644 index 00000000..7b16fb2d --- /dev/null +++ b/cosmic-ray-12way.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -e + +cosmic-ray init cosmic-ray-12way.toml session.sqlite +cosmic-ray baseline --session-file session.baseline.sqlite cosmic-ray-12way.toml +cr-report --show-output session.baseline.sqlite +# some mutations cause huge memory use, so put it in a cgroup +# systemd-run --user --scope -p MemoryMax=8G -p MemoryHigh=8G cr-http-workers cosmic-ray-12way.toml . +cr-http-workers cosmic-ray-12way.toml . +cosmic-ray exec cosmic-ray-12way.toml session.sqlite +cr-report session.sqlite +cr-html session.sqlite > session.html +cr-rate --estimate --fail-over 29 --confidence 99.9 session.sqlite diff --git a/cosmic-ray-12way.toml b/cosmic-ray-12way.toml new file mode 100644 index 00000000..78ee99c3 --- /dev/null +++ b/cosmic-ray-12way.toml @@ -0,0 +1,28 @@ +[cosmic-ray] +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/" + +[cosmic-ray.distributor] +name = "http" + +[cosmic-ray.distributor.http] +worker-urls = [ + "http://localhost:9870", + "http://localhost:9871", + "http://localhost:9872", + "http://localhost:9873", + "http://localhost:9874", + "http://localhost:9875", + "http://localhost:9876", + "http://localhost:9877", + "http://localhost:9878", + "http://localhost:9879", + "http://localhost:9880", + "http://localhost:9881", + "http://localhost:9882" +] + +[cosmic-ray.filters.git-filter] +branch = "master" diff --git a/cosmic-ray.sh b/cosmic-ray.sh index 384750a3..51181a3a 100644 --- a/cosmic-ray.sh +++ b/cosmic-ray.sh @@ -2,11 +2,11 @@ set -e cosmic-ray init cosmic-ray.toml session.sqlite -cosmic-ray baseline --report session.sqlite +cosmic-ray baseline --session-file session.baseline.sqlite cosmic-ray.toml cr-report --show-output session.baseline.sqlite # some mutations cause huge memory use, so put it in a cgroup -# systemd-run --user --scope -p MemoryMax=2G -p MemoryHigh=2G cosmic-ray exec session.sqlite -cosmic-ray exec session.sqlite +# systemd-run --user --scope -p MemoryMax=2G -p MemoryHigh=2G cosmic-ray exec cosmic-ray.toml session.sqlite +cosmic-ray exec cosmic-ray.toml session.sqlite cr-report session.sqlite cr-html session.sqlite > session.html cr-rate --estimate --fail-over 29 --confidence 99.9 session.sqlite diff --git a/cosmic-ray.toml b/cosmic-ray.toml index 09f364aa..31c95f9f 100644 --- a/cosmic-ray.toml +++ b/cosmic-ray.toml @@ -1,18 +1,11 @@ [cosmic-ray] module-path = "src" -python-version = "" timeout = 20.0 -exclude-modules = ['src/ecdsa/_version.py', 'src/ecdsa/test*'] +exclude-modules = ['src/ecdsa/_sha3.py', 'src/ecdsa/_version.py', 'src/ecdsa/test*'] test-command = "pytest -x --fast -m 'not slow' src/" -[cosmic-ray.execution-engine] +[cosmic-ray.distributor] name = "local" -[cosmic-ray.cloning] -method = "copy" -commands = [] - -[cosmic-ray.interceptors] -enabled = [ "spor", "pragma_no_mutate", "operators-filter",] - -[cosmic-ray.operators-filter] +[cosmic-ray.filters.git-filter] +branch = "master" From ad564c04ad1a3309079be11a86c4a656c7eebdb6 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Dec 2023 21:33:30 +0100 Subject: [PATCH 055/105] don't run speed tests on mutation and codechecks tests --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3b218cc..d9d2eac1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -260,7 +260,7 @@ jobs: run: pip list - name: Test native speed # tox uses pip to install dependenceis, so it breaks on py2.6 - if: ${{ !contains(matrix.tox-env, 'gmpy') && matrix.python-version != '2.6'}} + if: ${{ !contains(matrix.tox-env, 'gmpy') && matrix.python-version != '2.6' && ! matrix.mutation && !contains(matrix.tox-env, 'codechecks') }} run: tox -e speed - name: Test speed with gmpy if: ${{ contains(matrix.tox-env, 'gmpyp') }} From da9d8bb93ecdcccec21416f23895a3a131bd5c58 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Dec 2023 20:03:37 +0100 Subject: [PATCH 056/105] add mutation testing to PR --- .github/workflows/ci.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9d2eac1..5e7b8fea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,6 +104,10 @@ jobs: os: ubuntu-latest python-version: 3.9 tox-env: codechecks + - name: mutation testing + os: ubuntu-latest + python-version: '3.11' + mutation: 'true' steps: - uses: actions/checkout@v2 if: ${{ !matrix.container }} @@ -149,6 +153,11 @@ jobs: run: | apt-get update apt-get install -y git make python-is-python3 python3 curl wget python3-distutils python3-pip + + - name: Dependencies for mutation testing + if: ${{ matrix.mutation == 'true' }} + run: | + sudo apt-get install -y sqlite3 - name: workaround git failures with py3.10 run: | git config --global --add safe.directory /__w/python-ecdsa/python-ecdsa @@ -256,6 +265,10 @@ jobs: else pip install -r build-requirements.txt; fi + - name: Install mutation testing dependencies + if: ${{ matrix.mutation == 'true' }} + run: | + pip install cosmic-ray - name: Display installed python package versions run: pip list - name: Test native speed @@ -277,6 +290,26 @@ jobs: - name: Run unit tests if: ${{ matrix.tox-env }} run: tox -e ${{ matrix.tox-env }} + - name: Init for mutation testing in PR + if: ${{ matrix.mutation == 'true' && github.event.pull_request }} + run: | + cosmic-ray init cosmic-ray.toml session-vs-master.sqlite + git branch master origin/master + git rev-parse HEAD + git rev-parse master + cr-filter-git --config cosmic-ray.toml session-vs-master.sqlite + - 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 + - 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 = 'NO_TEST' OR 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 - name: instrumental test coverage on PR if: ${{ contains(matrix.opt-deps, 'instrumental') && github.event.pull_request }} env: From 6f09910c89996313fff1223ccff34e362986a7f1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Dec 2023 22:53:44 +0100 Subject: [PATCH 057/105] correct exclude configuration --- cosmic-ray.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cosmic-ray.toml b/cosmic-ray.toml index 31c95f9f..6f1c54fa 100644 --- a/cosmic-ray.toml +++ b/cosmic-ray.toml @@ -1,7 +1,7 @@ [cosmic-ray] module-path = "src" timeout = 20.0 -exclude-modules = ['src/ecdsa/_sha3.py', 'src/ecdsa/_version.py', 'src/ecdsa/test*'] +excluded-modules = ['src/ecdsa/_sha3.py', 'src/ecdsa/_version.py', 'src/ecdsa/test*'] test-command = "pytest -x --fast -m 'not slow' src/" [cosmic-ray.distributor] From 09ffa0f326bf66f273d4c7152cdcff88b3beb663 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Dec 2023 23:01:49 +0100 Subject: [PATCH 058/105] log number of tests to run --- .github/workflows/ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e7b8fea..4b15bc54 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -295,9 +295,8 @@ jobs: run: | cosmic-ray init cosmic-ray.toml session-vs-master.sqlite git branch master origin/master - git rev-parse HEAD - git rev-parse master cr-filter-git --config cosmic-ray.toml session-vs-master.sqlite + cr-report session-vs-master.sqlite | tail -n 5 - name: Exec mutation testing for PR if: ${{ matrix.mutation == 'true' && github.event.pull_request }} run: | @@ -306,7 +305,7 @@ jobs: 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 = 'NO_TEST' OR work_results.worker_outcome = 'SKIPPED'" + 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 From cae3082265ef725584f5c0a27a965bd53b8a6669 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 31 Dec 2023 02:10:30 +0100 Subject: [PATCH 059/105] Run mutation testing on multiple workers in parallel --- .github/workflows/ci.yml | 255 +++++++++++++++++++++++++++++++++++++++ sql/combine.sql | 7 ++ sql/create_to_del.sql | 2 + sql/shard-db.sql | 3 + 4 files changed, 267 insertions(+) create mode 100644 sql/combine.sql create mode 100644 sql/create_to_del.sql create mode 100644 sql/shard-db.sql diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b15bc54..562ca7ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -369,3 +369,258 @@ jobs: COVERALLS_SERVICE_NAME: github run: | coveralls --finish + + mutation-prepare: + name: Prepare job files for the mutation runners + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + if: ${{ !matrix.container }} + with: + fetch-depth: 50 + - name: save session objects + uses: actions/cache@v3 + with: + path: | + sessions/ + key: sessions-${{ github.sha }} + - name: Install cosmic-ray + run: | + pip3 install cosmic-ray + - name: Install dependencies + run: | + sudo apt-get install -y sqlite3 + - name: Display Python version + run: python -c "import sys; print(sys.version)" + - name: Create list of mutations + run: | + cosmic-ray init cosmic-ray.toml session.sqlite + - name: Log number of jobs created + run: | + cr-report session.sqlite | tail -n 3 + - name: Split up mutations to workers + run: | + cp session.sqlite session-to_del.sqlite + sqlite3 session-to_del.sqlite "$(cat sql/create_to_del.sql)" + mkdir sessions + for i in $(seq 0 19); do + sed "s/%SHARD%/$i/" < sql/shard-db.sql > shard.sql + cp session-to_del.sqlite session-$i.sqlite + sqlite3 session-$i.sqlite "$(cat shard.sql)" + mv session-$i.sqlite sessions/ + done + mutation-execute: + name: Execute mutation testing + needs: mutation-prepare + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + include: + - name: 0 + - name: 1 + - name: 2 + - name: 3 + - name: 4 + - name: 5 + - name: 6 + - name: 7 + - name: 8 + - name: 9 + - name: 10 + - name: 11 + - name: 12 + - name: 13 + - name: 14 + - name: 15 + - name: 16 + - name: 17 + - name: 18 + - name: 19 + steps: + - uses: actions/checkout@v2 + if: ${{ !matrix.container }} + with: + fetch-depth: 1 + - name: Session objects + uses: actions/cache@v3 + with: + path: | + sessions/ + key: sessions-${{ github.sha }} + - name: Session done objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-${{ matrix.name }}-done.sqlite + key: sessions-${{ github.sha }}-${{ matrix.name }}-done + - name: Install gmpy2 dependencies + run: sudo apt-get install -y libmpfr-dev libmpc-dev + - name: Install gmpy2 + run: pip install gmpy2 + - name: Install build dependencies + run: | + pip install -r build-requirements.txt + pip install cosmic-ray + - name: Run mutation testing + run: | + cp sessions/session-${{ matrix.name }}.sqlite session.sqlite + systemd-run --user --scope -p MemoryMax=2G -p MemoryHigh=2G cosmic-ray exec cosmic-ray.toml session.sqlite & + cosmic_pid=$! + for i in $(seq 1 10); do + echo $i + sleep 60 + done + kill $cosmic_pid + mkdir sessions-done/ + cp session.sqlite sessions-done/session-${{ matrix.name }}-done.sqlite + - name: Report executed + run: | + cr-report session.sqlite | tail -n 3 + mutation-combine: + name: Combine mutation testing results + needs: mutation-execute + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + if: ${{ !matrix.container }} + with: + fetch-depth: 1 + - name: Session done 0 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-0-done.sqlite + key: sessions-${{ github.sha }}-0-done + - name: Session done 1 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-1-done.sqlite + key: sessions-${{ github.sha }}-1-done + - name: Session done 2 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-2-done.sqlite + key: sessions-${{ github.sha }}-2-done + - name: Session done 3 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-3-done.sqlite + key: sessions-${{ github.sha }}-3-done + - name: Session done 4 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-4-done.sqlite + key: sessions-${{ github.sha }}-4-done + - name: Session done 5 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-5-done.sqlite + key: sessions-${{ github.sha }}-5-done + - name: Session done 6 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-6-done.sqlite + key: sessions-${{ github.sha }}-6-done + - name: Session done 7 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-7-done.sqlite + key: sessions-${{ github.sha }}-7-done + - name: Session done 8 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-8-done.sqlite + key: sessions-${{ github.sha }}-8-done + - name: Session done 9 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-9-done.sqlite + key: sessions-${{ github.sha }}-9-done + - name: Session done 10 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-10-done.sqlite + key: sessions-${{ github.sha }}-10-done + - name: Session done 11 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-11-done.sqlite + key: sessions-${{ github.sha }}-11-done + - name: Session done 12 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-12-done.sqlite + key: sessions-${{ github.sha }}-12-done + - name: Session done 13 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-13-done.sqlite + key: sessions-${{ github.sha }}-13-done + - name: Session done 14 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-14-done.sqlite + key: sessions-${{ github.sha }}-14-done + - name: Session done 15 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-15-done.sqlite + key: sessions-${{ github.sha }}-15-done + - name: Session done 16 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-16-done.sqlite + key: sessions-${{ github.sha }}-16-done + - name: Session done 17 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-17-done.sqlite + key: sessions-${{ github.sha }}-17-done + - name: Session done 18 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-18-done.sqlite + key: sessions-${{ github.sha }}-18-done + - name: Session done 19 objects + uses: actions/cache@v3 + with: + path: | + sessions-done/session-19-done.sqlite + key: sessions-${{ github.sha }}-19-done + - name: Install cosmic-ray + run: | + pip3 install cosmic-ray + - name: Install dependencies + run: | + sudo apt-get install -y sqlite3 + - name: Combine worker results + run: | + cp sessions-done/session-0-done.sqlite session.sqlite + for i in $(seq 1 19); do + cp sessions-done/session-$i-done.sqlite session-to_merge.sqlite + sqlite3 session.sqlite "$(cat sql/combine.sql)" + done + - name: Report executed + run: | + cr-report session.sqlite | tail -n 3 + - name: Check mutation level + run: cr-rate --estimate --fail-over 32 --confidence 99.9 session.sqlite diff --git a/sql/combine.sql b/sql/combine.sql new file mode 100644 index 00000000..d2a4490d --- /dev/null +++ b/sql/combine.sql @@ -0,0 +1,7 @@ +attach 'session-to_merge.sqlite' as toMerge; +BEGIN; + insert into work_items select * from toMerge.work_items; + insert into mutation_specs select * from toMerge.mutation_specs; + insert into work_results select * from toMerge.work_results; +COMMIT; +detach toMerge; diff --git a/sql/create_to_del.sql b/sql/create_to_del.sql new file mode 100644 index 00000000..956d1a28 --- /dev/null +++ b/sql/create_to_del.sql @@ -0,0 +1,2 @@ +create table to_del (job_id VARCHAR NOT NULL, id INTEGER PRIMARY KEY); +insert into to_del select *, ROWID from work_items; diff --git a/sql/shard-db.sql b/sql/shard-db.sql new file mode 100644 index 00000000..7460bd31 --- /dev/null +++ b/sql/shard-db.sql @@ -0,0 +1,3 @@ +delete from mutation_specs where job_id in (select job_id from to_del where to_del.ID % 20 != %SHARD%); +delete from work_items where job_id in (select job_id from to_del where to_del.ID % 20 != %SHARD%); +drop table to_del; From 8df3bb87ef21c85f6946e4a2222dd14b1a47c3b4 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 31 Dec 2023 12:18:07 +0100 Subject: [PATCH 060/105] create mutation score badge --- .github/workflows/ci.yml | 15 ++++++++++++++- README.md | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 562ca7ea..ce34b5cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -622,5 +622,18 @@ jobs: - name: Report executed run: | cr-report session.sqlite | tail -n 3 - - name: Check mutation level + - name: Check survival estimate run: cr-rate --estimate --fail-over 32 --confidence 99.9 session.sqlite + - name: Get mutation score + run: echo "MUT_SCORE=$(python -c 'print(100-$(cr-rate session.sqlite))')" >> $GITHUB_ENV + - name: Create mutation score badge + uses: schneegans/dynamic-badges-action@v1.4.0 + with: + auth: ${{ secrets.GIST_SECRET }} + gistID: 9b6ca1f3410207fbeca785a178781651 + filename: python-ecdsa-mutation-score.json + label: mutation score + message: ${{ env.MUT_SCORE }}% + valColorRange: ${{ env.MUT_SCORE }} + maxColorRange: 100 + minColorRange: 0 diff --git a/README.md b/README.md index fbb41a63..6c43f85d 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![Documentation Status](https://readthedocs.org/projects/ecdsa/badge/?version=latest)](https://ecdsa.readthedocs.io/en/latest/?badge=latest) [![Coverage Status](https://coveralls.io/repos/github/tlsfuzzer/python-ecdsa/badge.svg?branch=master)](https://coveralls.io/github/tlsfuzzer/python-ecdsa?branch=master) ![condition coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/tomato42/9b6ca1f3410207fbeca785a178781651/raw/python-ecdsa-condition-coverage.json) +![mutation score](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/tomato42/9b6ca1f3410207fbeca785a178781651/raw/python-ecdsa-mutation-score.json) [![CodeQL](https://github.com/tlsfuzzer/python-ecdsa/actions/workflows/codeql.yml/badge.svg)](https://github.com/tlsfuzzer/python-ecdsa/actions/workflows/codeql.yml) [![Latest Version](https://img.shields.io/pypi/v/ecdsa.svg?style=flat)](https://pypi.python.org/pypi/ecdsa/) ![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat) From 1364e899d62b5962aeea3fcd91d31f798337e0b7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 31 Dec 2023 12:42:52 +0100 Subject: [PATCH 061/105] error tolerance --- .github/workflows/ci.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ce34b5cf..6594746a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -414,7 +414,7 @@ jobs: needs: mutation-prepare runs-on: ubuntu-latest strategy: - fail-fast: true + fail-fast: false matrix: include: - name: 0 @@ -467,7 +467,7 @@ jobs: cp sessions/session-${{ matrix.name }}.sqlite session.sqlite systemd-run --user --scope -p MemoryMax=2G -p MemoryHigh=2G cosmic-ray exec cosmic-ray.toml session.sqlite & cosmic_pid=$! - for i in $(seq 1 10); do + for i in $(seq 1 2); do echo $i sleep 60 done @@ -616,16 +616,17 @@ jobs: run: | cp sessions-done/session-0-done.sqlite session.sqlite for i in $(seq 1 19); do - cp sessions-done/session-$i-done.sqlite session-to_merge.sqlite - sqlite3 session.sqlite "$(cat sql/combine.sql)" + cp sessions-done/session-$i-done.sqlite session-to_merge.sqlite && sqlite3 session.sqlite "$(cat sql/combine.sql)" || true done - name: Report executed run: | cr-report session.sqlite | tail -n 3 - - name: Check survival estimate - run: cr-rate --estimate --fail-over 32 --confidence 99.9 session.sqlite + - name: Log survival estimate + run: cr-rate --estimate --fail-over 32 --confidence 99.9 session.sqlite || true - name: Get mutation score - run: echo "MUT_SCORE=$(python -c 'print(100-$(cr-rate session.sqlite))')" >> $GITHUB_ENV + run: | + echo "print(100-$(cr-rate session.sqlite))" > print-score.py + echo "MUT_SCORE=$(python print-score.py)" >> $GITHUB_ENV - name: Create mutation score badge uses: schneegans/dynamic-badges-action@v1.4.0 with: From c4c6ff6c13ce71726dcf9140f6fbc87aa4f6d8e0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 31 Dec 2023 15:44:58 +0100 Subject: [PATCH 062/105] long execution for mutation testing --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6594746a..f25221df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -467,7 +467,7 @@ jobs: cp sessions/session-${{ matrix.name }}.sqlite session.sqlite systemd-run --user --scope -p MemoryMax=2G -p MemoryHigh=2G cosmic-ray exec cosmic-ray.toml session.sqlite & cosmic_pid=$! - for i in $(seq 1 2); do + for i in $(seq 1 10); do echo $i sleep 60 done From 503155fbac901aef885900e1be5dd578b1ac3874 Mon Sep 17 00:00:00 2001 From: Alexandre Detiste Date: Sat, 6 Jan 2024 01:41:09 +0100 Subject: [PATCH 063/105] remove usage of six.b("literal") as a side effect the program would maybe run faster --- speed.py | 7 +- src/ecdsa/der.py | 28 ++--- src/ecdsa/ecdsa.py | 6 +- src/ecdsa/keys.py | 8 +- src/ecdsa/test_der.py | 39 ++++--- src/ecdsa/test_jacobi.py | 1 - src/ecdsa/test_pyecdsa.py | 219 +++++++++++++++++++------------------- src/ecdsa/util.py | 4 +- 8 files changed, 153 insertions(+), 159 deletions(-) diff --git a/speed.py b/speed.py index 3ed72c49..a3f9e59c 100644 --- a/speed.py +++ b/speed.py @@ -1,4 +1,3 @@ -import six import timeit from ecdsa.curves import curves @@ -43,9 +42,9 @@ def do(setup_statements, statement): ) for curve in [i.name for i in curves]: - S1 = "import six; from ecdsa import SigningKey, %s" % curve + S1 = "from ecdsa import SigningKey, %s" % curve S2 = "sk = SigningKey.generate(%s)" % curve - S3 = "msg = six.b('msg')" + S3 = "msg = b'msg'" S4 = "sig = sk.sign(msg)" S5 = "vk = sk.get_verifying_key()" S6 = "vk.precompute()" @@ -61,7 +60,7 @@ def do(setup_statements, statement): import ecdsa c = getattr(ecdsa, curve) - sig = ecdsa.SigningKey.generate(c).sign(six.b("msg")) + sig = ecdsa.SigningKey.generate(c).sign(b"msg") print( prnt_form.format( name=curve, diff --git a/src/ecdsa/der.py b/src/ecdsa/der.py index fa6979d1..b2914859 100644 --- a/src/ecdsa/der.py +++ b/src/ecdsa/der.py @@ -4,7 +4,7 @@ import base64 import warnings from itertools import chain -from six import int2byte, b, text_type +from six import int2byte, text_type from ._compat import compat26_str, str_idx_as_int @@ -20,16 +20,16 @@ def encode_integer(r): assert r >= 0 # can't support negative numbers yet h = ("%x" % r).encode() if len(h) % 2: - h = b("0") + h + h = b"0" + h s = binascii.unhexlify(h) num = str_idx_as_int(s, 0) if num <= 0x7F: - return b("\x02") + encode_length(len(s)) + s + return b"\x02" + encode_length(len(s)) + s else: # DER integers are two's complement, so if the first byte is # 0x80-0xff then we need an extra 0x00 byte to prevent it from # looking negative. - return b("\x02") + encode_length(len(s) + 1) + b("\x00") + s + return b"\x02" + encode_length(len(s) + 1) + b"\x00" + s # sentry object to check if an argument was specified (used to detect @@ -91,11 +91,11 @@ def encode_bitstring(s, unused=_sentry): raise ValueError("unused bits must be zeros in DER") encoded_unused = int2byte(unused) len_extra = 1 - return b("\x03") + encode_length(len(s) + len_extra) + encoded_unused + s + return b"\x03" + encode_length(len(s) + len_extra) + encoded_unused + s def encode_octet_string(s): - return b("\x04") + encode_length(len(s)) + s + return b"\x04" + encode_length(len(s)) + s def encode_oid(first, second, *pieces): @@ -111,7 +111,7 @@ def encode_oid(first, second, *pieces): def encode_sequence(*encoded_pieces): total_len = sum([len(p) for p in encoded_pieces]) - return b("\x30") + encode_length(total_len) + b("").join(encoded_pieces) + return b"\x30" + encode_length(total_len) + b"".join(encoded_pieces) def encode_number(n): @@ -122,7 +122,7 @@ def encode_number(n): if not b128_digits: b128_digits.append(0) b128_digits[-1] &= 0x7F - return b("").join([int2byte(d) for d in b128_digits]) + return b"".join([int2byte(d) for d in b128_digits]) def is_sequence(string): @@ -254,7 +254,7 @@ def encode_length(l): return int2byte(l) s = ("%x" % l).encode() if len(s) % 2: - s = b("0") + s + s = b"0" + s s = binascii.unhexlify(s) llen = len(s) return int2byte(0x80 | llen) + s @@ -389,11 +389,11 @@ def unpem(pem): if isinstance(pem, text_type): # pragma: no branch pem = pem.encode() - d = b("").join( + d = b"".join( [ l.strip() - for l in pem.split(b("\n")) - if l and not l.startswith(b("-----")) + for l in pem.split(b"\n") + if l and not l.startswith(b"-----") ] ) return base64.b64decode(d) @@ -403,7 +403,7 @@ def topem(der, name): b64 = base64.b64encode(compat26_str(der)) lines = [("-----BEGIN %s-----\n" % name).encode()] lines.extend( - [b64[start : start + 76] + b("\n") for start in range(0, len(b64), 76)] + [b64[start : start + 76] + b"\n" for start in range(0, len(b64), 76)] ) lines.append(("-----END %s-----\n" % name).encode()) - return b("").join(lines) + return b"".join(lines) diff --git a/src/ecdsa/ecdsa.py b/src/ecdsa/ecdsa.py index 7ac76c2f..9284ace4 100644 --- a/src/ecdsa/ecdsa.py +++ b/src/ecdsa/ecdsa.py @@ -65,7 +65,7 @@ """ import warnings -from six import int2byte, b +from six import int2byte from . import ellipticcurve from . import numbertheory from .util import bit_length @@ -280,7 +280,7 @@ def int_to_string(x): # pragma: no cover ) assert x >= 0 if x == 0: - return b("\0") + return b"\0" result = [] while x: ordinal = x & 0xFF @@ -288,7 +288,7 @@ def int_to_string(x): # pragma: no cover x >>= 8 result.reverse() - return b("").join(result) + return b"".join(result) def string_to_int(s): # pragma: no cover diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index d77f4fc4..f493995d 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -5,7 +5,7 @@ import binascii from hashlib import sha1 import os -from six import PY2, b +from six import PY2 from . import ecdsa, eddsa from . import der, ssh from . import rfc6979 @@ -1044,7 +1044,7 @@ def from_der(cls, string, hashfunc=sha1, valid_curve_encodings=None): curve = None s, empty = der.remove_sequence(s) - if empty != b(""): + if empty != b"": raise der.UnexpectedDER( "trailing junk after DER privkey: %s" % binascii.hexlify(empty) ) @@ -1115,7 +1115,7 @@ def from_der(cls, string, hashfunc=sha1, valid_curve_encodings=None): # Unpack the ECPrivateKey to get to the key data octet string, # and rejoin the ssleay parsing path. s, empty = der.remove_sequence(s) - if empty != b(""): + if empty != b"": raise der.UnexpectedDER( "trailing junk after DER privkey: %s" % binascii.hexlify(empty) @@ -1155,7 +1155,7 @@ def from_der(cls, string, hashfunc=sha1, valid_curve_encodings=None): # our from_string method likes fixed-length privkey strings if len(privkey_str) < curve.baselen: privkey_str = ( - b("\x00") * (curve.baselen - len(privkey_str)) + privkey_str + b"\x00" * (curve.baselen - len(privkey_str)) + privkey_str ) return cls.from_string(privkey_str, curve, hashfunc) diff --git a/src/ecdsa/test_der.py b/src/ecdsa/test_der.py index f08ea573..0c2dc4d1 100644 --- a/src/ecdsa/test_der.py +++ b/src/ecdsa/test_der.py @@ -8,7 +8,6 @@ except ImportError: import unittest import sys -from six import b import hypothesis.strategies as st from hypothesis import given, settings import pytest @@ -33,44 +32,44 @@ class TestRemoveInteger(unittest.TestCase): # interpreted as negative, check if those errors are detected def test_non_minimal_encoding(self): with self.assertRaises(UnexpectedDER): - remove_integer(b("\x02\x02\x00\x01")) + remove_integer(b"\x02\x02\x00\x01") def test_negative_with_high_bit_set(self): with self.assertRaises(UnexpectedDER): - remove_integer(b("\x02\x01\x80")) + remove_integer(b"\x02\x01\x80") def test_minimal_with_high_bit_set(self): - val, rem = remove_integer(b("\x02\x02\x00\x80")) + val, rem = remove_integer(b"\x02\x02\x00\x80") self.assertEqual(val, 0x80) self.assertEqual(rem, b"") def test_two_zero_bytes_with_high_bit_set(self): with self.assertRaises(UnexpectedDER): - remove_integer(b("\x02\x03\x00\x00\xff")) + remove_integer(b"\x02\x03\x00\x00\xff") def test_zero_length_integer(self): with self.assertRaises(UnexpectedDER): - remove_integer(b("\x02\x00")) + remove_integer(b"\x02\x00") def test_empty_string(self): with self.assertRaises(UnexpectedDER): - remove_integer(b("")) + remove_integer(b"") def test_encoding_of_zero(self): - val, rem = remove_integer(b("\x02\x01\x00")) + val, rem = remove_integer(b"\x02\x01\x00") self.assertEqual(val, 0) self.assertEqual(rem, b"") def test_encoding_of_127(self): - val, rem = remove_integer(b("\x02\x01\x7f")) + val, rem = remove_integer(b"\x02\x01\x7f") self.assertEqual(val, 127) self.assertEqual(rem, b"") def test_encoding_of_128(self): - val, rem = remove_integer(b("\x02\x02\x00\x80")) + val, rem = remove_integer(b"\x02\x02\x00\x80") self.assertEqual(val, 128) self.assertEqual(rem, b"") @@ -93,37 +92,37 @@ class TestReadLength(unittest.TestCase): # form and lengths above that encoded with minimal number of bytes # necessary def test_zero_length(self): - self.assertEqual((0, 1), read_length(b("\x00"))) + self.assertEqual((0, 1), read_length(b"\x00")) def test_two_byte_zero_length(self): with self.assertRaises(UnexpectedDER): - read_length(b("\x81\x00")) + read_length(b"\x81\x00") def test_two_byte_small_length(self): with self.assertRaises(UnexpectedDER): - read_length(b("\x81\x7f")) + read_length(b"\x81\x7f") def test_long_form_with_zero_length(self): with self.assertRaises(UnexpectedDER): - read_length(b("\x80")) + read_length(b"\x80") def test_smallest_two_byte_length(self): - self.assertEqual((128, 2), read_length(b("\x81\x80"))) + self.assertEqual((128, 2), read_length(b"\x81\x80")) def test_zero_padded_length(self): with self.assertRaises(UnexpectedDER): - read_length(b("\x82\x00\x80")) + read_length(b"\x82\x00\x80") def test_two_three_byte_length(self): self.assertEqual((256, 3), read_length(b"\x82\x01\x00")) def test_empty_string(self): with self.assertRaises(UnexpectedDER): - read_length(b("")) + read_length(b"") def test_length_overflow(self): with self.assertRaises(UnexpectedDER): - read_length(b("\x83\x01\x00")) + read_length(b"\x83\x01\x00") class TestEncodeBitstring(unittest.TestCase): @@ -270,10 +269,10 @@ def test_bytearray(self): class TestEncodeOid(unittest.TestCase): def test_pub_key_oid(self): oid_ecPublicKey = encode_oid(1, 2, 840, 10045, 2, 1) - self.assertEqual(hexlify(oid_ecPublicKey), b("06072a8648ce3d0201")) + self.assertEqual(hexlify(oid_ecPublicKey), b"06072a8648ce3d0201") def test_nist224p_oid(self): - self.assertEqual(hexlify(NIST224p.encoded_oid), b("06052b81040021")) + self.assertEqual(hexlify(NIST224p.encoded_oid), b"06052b81040021") def test_nist256p_oid(self): self.assertEqual( diff --git a/src/ecdsa/test_jacobi.py b/src/ecdsa/test_jacobi.py index 2100cc16..e52f93bd 100644 --- a/src/ecdsa/test_jacobi.py +++ b/src/ecdsa/test_jacobi.py @@ -7,7 +7,6 @@ import unittest import os -import sys import signal import pytest import threading diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index 069f3e0c..ffa4ab7a 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -5,7 +5,6 @@ except ImportError: import unittest import os -import sys import shutil import subprocess import pytest @@ -104,17 +103,17 @@ def test_basic(self): priv = SigningKey.generate() pub = priv.get_verifying_key() - data = b("blahblah") + data = b"blahblah" sig = priv.sign(data) self.assertTrue(pub.verify(sig, data)) - self.assertRaises(BadSignatureError, pub.verify, sig, data + b("bad")) + self.assertRaises(BadSignatureError, pub.verify, sig, data + b"bad") pub2 = VerifyingKey.from_string(pub.to_string()) self.assertTrue(pub2.verify(sig, data)) def test_deterministic(self): - data = b("blahblah") + data = b"blahblah" secexp = int("9d0219792467d7d37b4d43298a7d0c05", 16) priv = SigningKey.from_secret_exponent( @@ -155,7 +154,7 @@ def test_lengths_default(self): self.assertEqual(len(sig), default.signature_length) def test_serialize(self): - seed = b("secret") + seed = b"secret" curve = NIST192p secexp1 = util.randrange_from_seed__trytryagain(seed, curve.order) secexp2 = util.randrange_from_seed__trytryagain(seed, curve.order) @@ -168,7 +167,7 @@ def test_serialize(self): self.assertEqual(priv1.to_pem(), priv2.to_pem()) pub1 = priv1.get_verifying_key() pub2 = priv2.get_verifying_key() - data = b("data") + data = b"data" sig1 = priv1.sign(data) sig2 = priv2.sign(data) self.assertTrue(pub1.verify(sig1, data)) @@ -178,7 +177,7 @@ def test_serialize(self): self.assertEqual(hexlify(pub1.to_string()), hexlify(pub2.to_string())) def test_nonrandom(self): - s = b("all the entropy in the entire world, compressed into one line") + s = b"all the entropy in the entire world, compressed into one line" def not_much_entropy(numbytes): return s[:numbytes] @@ -194,8 +193,8 @@ def not_much_entropy(numbytes): # want to do this with keys you care about, because the secrecy of # the private key depends upon using different random numbers for # each signature - sig1 = priv1.sign(b("data"), entropy=not_much_entropy) - sig2 = priv2.sign(b("data"), entropy=not_much_entropy) + sig1 = priv1.sign(b"data", entropy=not_much_entropy) + sig2 = priv2.sign(b"data", entropy=not_much_entropy) self.assertEqual(hexlify(sig1), hexlify(sig2)) def assertTruePrivkeysEqual(self, priv1, priv2): @@ -208,7 +207,7 @@ def assertTruePrivkeysEqual(self, priv1, priv2): ) def test_privkey_creation(self): - s = b("all the entropy in the entire world, compressed into one line") + s = b"all the entropy in the entire world, compressed into one line" def not_much_entropy(numbytes): return s[:numbytes] @@ -243,8 +242,8 @@ def test_privkey_strings(self): s1 = priv1.to_pem() self.assertEqual(type(s1), binary_type) - self.assertTrue(s1.startswith(b("-----BEGIN EC PRIVATE KEY-----"))) - self.assertTrue(s1.strip().endswith(b("-----END EC PRIVATE KEY-----"))) + self.assertTrue(s1.startswith(b"-----BEGIN EC PRIVATE KEY-----")) + self.assertTrue(s1.strip().endswith(b"-----END EC PRIVATE KEY-----")) priv2 = SigningKey.from_pem(s1) self.assertTruePrivkeysEqual(priv1, priv2) @@ -256,8 +255,8 @@ def test_privkey_strings(self): priv1 = SigningKey.generate(curve=NIST256p) s1 = priv1.to_pem() self.assertEqual(type(s1), binary_type) - self.assertTrue(s1.startswith(b("-----BEGIN EC PRIVATE KEY-----"))) - self.assertTrue(s1.strip().endswith(b("-----END EC PRIVATE KEY-----"))) + self.assertTrue(s1.startswith(b"-----BEGIN EC PRIVATE KEY-----")) + self.assertTrue(s1.strip().endswith(b"-----END EC PRIVATE KEY-----")) priv2 = SigningKey.from_pem(s1) self.assertTruePrivkeysEqual(priv1, priv2) @@ -270,8 +269,8 @@ def test_privkey_strings_brainpool(self): priv1 = SigningKey.generate(curve=BRAINPOOLP512r1) s1 = priv1.to_pem() self.assertEqual(type(s1), binary_type) - self.assertTrue(s1.startswith(b("-----BEGIN EC PRIVATE KEY-----"))) - self.assertTrue(s1.strip().endswith(b("-----END EC PRIVATE KEY-----"))) + self.assertTrue(s1.startswith(b"-----BEGIN EC PRIVATE KEY-----")) + self.assertTrue(s1.strip().endswith(b"-----END EC PRIVATE KEY-----")) priv2 = SigningKey.from_pem(s1) self.assertTruePrivkeysEqual(priv1, priv2) @@ -308,7 +307,7 @@ def test_pubkey_strings(self): self.assertTruePubkeysEqual(pub1, pub2) self.assertRaises( - der.UnexpectedDER, VerifyingKey.from_der, pub1_der + b("junk") + der.UnexpectedDER, VerifyingKey.from_der, pub1_der + b"junk" ) badpub = VerifyingKey.from_der(pub1_der) @@ -332,9 +331,9 @@ def p(self): pem = pub1.to_pem() self.assertEqual(type(pem), binary_type) - self.assertTrue(pem.startswith(b("-----BEGIN PUBLIC KEY-----")), pem) + self.assertTrue(pem.startswith(b"-----BEGIN PUBLIC KEY-----"), pem) self.assertTrue( - pem.strip().endswith(b("-----END PUBLIC KEY-----")), pem + pem.strip().endswith(b"-----END PUBLIC KEY-----"), pem ) pub2 = VerifyingKey.from_pem(pem) self.assertTruePubkeysEqual(pub1, pub2) @@ -392,7 +391,7 @@ def test_vk_from_der_garbage_after_point_string(self): type_oid_der = encoded_oid_ecPublicKey curve_oid_der = der.encode_oid(*(1, 2, 840, 10045, 3, 1, 1)) enc_type_der = der.encode_sequence(type_oid_der, curve_oid_der) - point_der = der.encode_bitstring(b"\x00\xff", None) + b("garbage") + point_der = der.encode_bitstring(b"\x00\xff", None) + b"garbage" to_decode = der.encode_sequence(enc_type_der, point_der) with self.assertRaises(der.UnexpectedDER): @@ -431,7 +430,7 @@ def test_vk_from_der_with_raw_encoding(self): def test_signature_strings(self): priv1 = SigningKey.generate() pub1 = priv1.get_verifying_key() - data = b("data") + data = b"data" sig = priv1.sign(data) self.assertEqual(type(sig), binary_type) @@ -525,22 +524,22 @@ def test_sigencode_der_canonize(self): def test_sig_decode_strings_with_invalid_count(self): with self.assertRaises(MalformedSignature): - sigdecode_strings([b("one"), b("two"), b("three")], 0xFF) + sigdecode_strings([b"one", b"two", b"three"], 0xFF) def test_sig_decode_strings_with_wrong_r_len(self): with self.assertRaises(MalformedSignature): - sigdecode_strings([b("one"), b("two")], 0xFF) + sigdecode_strings([b"one", b"two"], 0xFF) def test_sig_decode_strings_with_wrong_s_len(self): with self.assertRaises(MalformedSignature): - sigdecode_strings([b("\xa0"), b("\xb0\xff")], 0xFF) + sigdecode_strings([b"\xa0", b"\xb0\xff"], 0xFF) def test_verify_with_too_long_input(self): sk = SigningKey.generate() vk = sk.verifying_key with self.assertRaises(BadDigestError): - vk.verify_digest(None, b("\x00") * 128) + vk.verify_digest(None, b"\x00" * 128) def test_sk_from_secret_exponent_with_wrong_sec_exponent(self): with self.assertRaises(MalformedPointError): @@ -548,11 +547,11 @@ def test_sk_from_secret_exponent_with_wrong_sec_exponent(self): def test_sk_from_string_with_wrong_len_string(self): with self.assertRaises(MalformedPointError): - SigningKey.from_string(b("\x01")) + SigningKey.from_string(b"\x01") def test_sk_from_der_with_junk_after_sequence(self): ver_der = der.encode_integer(1) - to_decode = der.encode_sequence(ver_der) + b("garbage") + to_decode = der.encode_sequence(ver_der) + b"garbage" with self.assertRaises(der.UnexpectedDER): SigningKey.from_der(to_decode) @@ -566,7 +565,7 @@ def test_sk_from_der_with_wrong_version(self): def test_sk_from_der_invalid_const_tag(self): ver_der = der.encode_integer(1) - privkey_der = der.encode_octet_string(b("\x00\xff")) + privkey_der = der.encode_octet_string(b"\x00\xff") curve_oid_der = der.encode_oid(*(1, 2, 3)) const_der = der.encode_constructed(1, curve_oid_der) to_decode = der.encode_sequence( @@ -578,8 +577,8 @@ def test_sk_from_der_invalid_const_tag(self): def test_sk_from_der_garbage_after_privkey_oid(self): ver_der = der.encode_integer(1) - privkey_der = der.encode_octet_string(b("\x00\xff")) - curve_oid_der = der.encode_oid(*(1, 2, 3)) + b("garbage") + privkey_der = der.encode_octet_string(b"\x00\xff") + curve_oid_der = der.encode_oid(*(1, 2, 3)) + b"garbage" const_der = der.encode_constructed(0, curve_oid_der) to_decode = der.encode_sequence( ver_der, privkey_der, const_der, curve_oid_der @@ -590,7 +589,7 @@ def test_sk_from_der_garbage_after_privkey_oid(self): def test_sk_from_der_with_short_privkey(self): ver_der = der.encode_integer(1) - privkey_der = der.encode_octet_string(b("\x00\xff")) + privkey_der = der.encode_octet_string(b"\x00\xff") curve_oid_der = der.encode_oid(*(1, 2, 840, 10045, 3, 1, 1)) const_der = der.encode_constructed(0, curve_oid_der) to_decode = der.encode_sequence( @@ -674,11 +673,11 @@ def test_sign_with_too_long_hash(self): sk = SigningKey.from_secret_exponent(12) with self.assertRaises(BadDigestError): - sk.sign_digest(b("\xff") * 64) + sk.sign_digest(b"\xff" * 64) def test_hashfunc(self): sk = SigningKey.generate(curve=NIST256p, hashfunc=hashlib.sha256) - data = b("security level is 128 bits") + data = b"security level is 128 bits" sig = sk.sign(data) vk = VerifyingKey.from_string( sk.get_verifying_key().to_string(), @@ -709,7 +708,7 @@ def test_public_key_recovery(self): vk = sk.get_verifying_key() # Sign a message - data = b("blahblah") + data = b"blahblah" signature = sk.sign(data) # Recover verifying keys @@ -742,7 +741,7 @@ def test_public_key_recovery_with_custom_hash(self): vk = sk.get_verifying_key() # Sign a message - data = b("blahblah") + data = b"blahblah" signature = sk.sign(data) # Recover verifying keys @@ -780,9 +779,9 @@ def test_encoding(self): ) self.assertEqual(vk.to_string(), exp) self.assertEqual(vk.to_string("raw"), exp) - self.assertEqual(vk.to_string("uncompressed"), b("\x04") + exp) - self.assertEqual(vk.to_string("compressed"), b("\x02") + exp[:24]) - self.assertEqual(vk.to_string("hybrid"), b("\x06") + exp) + self.assertEqual(vk.to_string("uncompressed"), b"\x04" + exp) + self.assertEqual(vk.to_string("compressed"), b"\x02" + exp[:24]) + self.assertEqual(vk.to_string("hybrid"), b"\x06" + exp) def test_decoding(self): sk = SigningKey.from_secret_exponent(123456789) @@ -797,13 +796,13 @@ def test_decoding(self): from_raw = VerifyingKey.from_string(enc) self.assertEqual(from_raw.pubkey.point, vk.pubkey.point) - from_uncompressed = VerifyingKey.from_string(b("\x04") + enc) + from_uncompressed = VerifyingKey.from_string(b"\x04" + enc) self.assertEqual(from_uncompressed.pubkey.point, vk.pubkey.point) - from_compressed = VerifyingKey.from_string(b("\x02") + enc[:24]) + from_compressed = VerifyingKey.from_string(b"\x02" + enc[:24]) self.assertEqual(from_compressed.pubkey.point, vk.pubkey.point) - from_uncompressed = VerifyingKey.from_string(b("\x06") + enc) + from_uncompressed = VerifyingKey.from_string(b"\x06" + enc) self.assertEqual(from_uncompressed.pubkey.point, vk.pubkey.point) def test_uncompressed_decoding_as_only_alowed(self): @@ -879,7 +878,7 @@ def test_decoding_with_malformed_uncompressed(self): ) with self.assertRaises(MalformedPointError): - VerifyingKey.from_string(b("\x02") + enc) + VerifyingKey.from_string(b"\x02" + enc) def test_decoding_with_malformed_compressed(self): enc = b( @@ -889,7 +888,7 @@ def test_decoding_with_malformed_compressed(self): ) with self.assertRaises(MalformedPointError): - VerifyingKey.from_string(b("\x01") + enc[:24]) + VerifyingKey.from_string(b"\x01" + enc[:24]) def test_decoding_with_inconsistent_hybrid(self): enc = b( @@ -899,7 +898,7 @@ def test_decoding_with_inconsistent_hybrid(self): ) with self.assertRaises(MalformedPointError): - VerifyingKey.from_string(b("\x07") + enc) + VerifyingKey.from_string(b"\x07" + enc) def test_decoding_with_point_not_on_curve(self): enc = b( @@ -909,18 +908,18 @@ def test_decoding_with_point_not_on_curve(self): ) with self.assertRaises(MalformedPointError): - VerifyingKey.from_string(enc[:47] + b("\x00")) + 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")) + VerifyingKey.from_string(b"\x00") def test_not_lying_on_curve(self): enc = number_to_string(NIST192p.curve.p(), NIST192p.curve.p() + 1) with self.assertRaises(MalformedPointError): - VerifyingKey.from_string(b("\x02") + enc) + VerifyingKey.from_string(b"\x02" + enc) def test_from_string_with_invalid_curve_too_short_ver_key_len(self): # both verifying_key_length and baselen are calculated internally @@ -931,7 +930,7 @@ def test_from_string_with_invalid_curve_too_short_ver_key_len(self): curve.baselen = 32 with self.assertRaises(MalformedPointError): - VerifyingKey.from_string(b("\x00") * 16, curve) + VerifyingKey.from_string(b"\x00" * 16, curve) def test_from_string_with_invalid_curve_too_long_ver_key_len(self): # both verifying_key_length and baselen are calculated internally @@ -942,7 +941,7 @@ def test_from_string_with_invalid_curve_too_long_ver_key_len(self): curve.baselen = 16 with self.assertRaises(MalformedPointError): - VerifyingKey.from_string(b("\x00") * 16, curve) + VerifyingKey.from_string(b"\x00" * 16, curve) @pytest.mark.parametrize( @@ -952,9 +951,9 @@ def test_VerifyingKey_decode_with_small_values(val, even): enc = number_to_string(val, NIST192p.order) if even: - enc = b("\x02") + enc + enc = b"\x02" + enc else: - enc = b("\x03") + enc + enc = b"\x03" + enc # small values can both be actual valid public keys and not, verify that # only expected exceptions are raised if they are not @@ -1266,7 +1265,7 @@ def do_test_from_openssl(self, curve, hash_name="SHA1"): os.mkdir("t") run_openssl("ecparam -name %s -genkey -out t/privkey.pem" % curvename) run_openssl("ec -in t/privkey.pem -pubout -out t/pubkey.pem") - data = b("data") + data = b"data" with open("t/data.txt", "wb") as e: e.write(data) run_openssl( @@ -1534,7 +1533,7 @@ def do_test_to_openssl(self, curve, hash_name="SHA1"): os.mkdir("t") sk = SigningKey.generate(curve=curve) vk = sk.get_verifying_key() - data = b("data") + data = b"data" with open("t/pubkey.der", "wb") as e: e.write(vk.to_der()) # 4 with open("t/pubkey.pem", "wb") as e: @@ -1550,7 +1549,7 @@ def do_test_to_openssl(self, curve, hash_name="SHA1"): with open("t/data.txt", "wb") as e: e.write(data) with open("t/baddata.txt", "wb") as e: - e.write(data + b("corrupt")) + e.write(data + b"corrupt") self.assertRaises( SubprocessError, @@ -1737,7 +1736,7 @@ class TooSmallCurve(unittest.TestCase): ) def test_sign_too_small_curve_dont_allow_truncate_raises(self): sk = SigningKey.generate(curve=NIST192p) - data = b("data") + data = b"data" with self.assertRaises(BadDigestError): sk.sign( data, @@ -1753,7 +1752,7 @@ def test_sign_too_small_curve_dont_allow_truncate_raises(self): def test_verify_too_small_curve_dont_allow_truncate_raises(self): sk = SigningKey.generate(curve=NIST192p) vk = sk.get_verifying_key() - data = b("data") + data = b"data" sig_der = sk.sign( data, hashfunc=partial(hashlib.new, "SHA256"), @@ -1772,66 +1771,66 @@ def test_verify_too_small_curve_dont_allow_truncate_raises(self): class DER(unittest.TestCase): def test_integer(self): - self.assertEqual(der.encode_integer(0), b("\x02\x01\x00")) - self.assertEqual(der.encode_integer(1), b("\x02\x01\x01")) - self.assertEqual(der.encode_integer(127), b("\x02\x01\x7f")) - self.assertEqual(der.encode_integer(128), b("\x02\x02\x00\x80")) - self.assertEqual(der.encode_integer(256), b("\x02\x02\x01\x00")) - # self.assertEqual(der.encode_integer(-1), b("\x02\x01\xff")) + self.assertEqual(der.encode_integer(0), b"\x02\x01\x00") + self.assertEqual(der.encode_integer(1), b"\x02\x01\x01") + self.assertEqual(der.encode_integer(127), b"\x02\x01\x7f") + self.assertEqual(der.encode_integer(128), b"\x02\x02\x00\x80") + self.assertEqual(der.encode_integer(256), b"\x02\x02\x01\x00") + # self.assertEqual(der.encode_integer(-1), b"\x02\x01\xff") def s(n): - return der.remove_integer(der.encode_integer(n) + b("junk")) + return der.remove_integer(der.encode_integer(n) + b"junk") - self.assertEqual(s(0), (0, b("junk"))) - self.assertEqual(s(1), (1, b("junk"))) - self.assertEqual(s(127), (127, b("junk"))) - self.assertEqual(s(128), (128, b("junk"))) - self.assertEqual(s(256), (256, b("junk"))) + self.assertEqual(s(0), (0, b"junk")) + self.assertEqual(s(1), (1, b"junk")) + self.assertEqual(s(127), (127, b"junk")) + self.assertEqual(s(128), (128, b"junk")) + self.assertEqual(s(256), (256, b"junk")) self.assertEqual( s(1234567890123456789012345678901234567890), - (1234567890123456789012345678901234567890, b("junk")), + (1234567890123456789012345678901234567890, b"junk"), ) def test_number(self): - self.assertEqual(der.encode_number(0), b("\x00")) - self.assertEqual(der.encode_number(127), b("\x7f")) - self.assertEqual(der.encode_number(128), b("\x81\x00")) - self.assertEqual(der.encode_number(3 * 128 + 7), b("\x83\x07")) + self.assertEqual(der.encode_number(0), b"\x00") + self.assertEqual(der.encode_number(127), b"\x7f") + self.assertEqual(der.encode_number(128), b"\x81\x00") + self.assertEqual(der.encode_number(3 * 128 + 7), b"\x83\x07") # self.assertEqual(der.read_number("\x81\x9b" + "more"), (155, 2)) - # self.assertEqual(der.encode_number(155), b("\x81\x9b")) + # self.assertEqual(der.encode_number(155), b"\x81\x9b") for n in (0, 1, 2, 127, 128, 3 * 128 + 7, 840, 10045): # , 155): - x = der.encode_number(n) + b("more") + x = der.encode_number(n) + b"more" n1, llen = der.read_number(x) self.assertEqual(n1, n) - self.assertEqual(x[llen:], b("more")) + self.assertEqual(x[llen:], b"more") def test_length(self): - self.assertEqual(der.encode_length(0), b("\x00")) - self.assertEqual(der.encode_length(127), b("\x7f")) - self.assertEqual(der.encode_length(128), b("\x81\x80")) - self.assertEqual(der.encode_length(255), b("\x81\xff")) - self.assertEqual(der.encode_length(256), b("\x82\x01\x00")) - self.assertEqual(der.encode_length(3 * 256 + 7), b("\x82\x03\x07")) - self.assertEqual(der.read_length(b("\x81\x9b") + b("more")), (155, 2)) - self.assertEqual(der.encode_length(155), b("\x81\x9b")) + self.assertEqual(der.encode_length(0), b"\x00") + self.assertEqual(der.encode_length(127), b"\x7f") + self.assertEqual(der.encode_length(128), b"\x81\x80") + self.assertEqual(der.encode_length(255), b"\x81\xff") + self.assertEqual(der.encode_length(256), b"\x82\x01\x00") + self.assertEqual(der.encode_length(3 * 256 + 7), b"\x82\x03\x07") + self.assertEqual(der.read_length(b"\x81\x9b" + b"more"), (155, 2)) + self.assertEqual(der.encode_length(155), b"\x81\x9b") for n in (0, 1, 2, 127, 128, 255, 256, 3 * 256 + 7, 155): - x = der.encode_length(n) + b("more") + x = der.encode_length(n) + b"more" n1, llen = der.read_length(x) self.assertEqual(n1, n) - self.assertEqual(x[llen:], b("more")) + self.assertEqual(x[llen:], b"more") def test_sequence(self): - x = der.encode_sequence(b("ABC"), b("DEF")) + b("GHI") - self.assertEqual(x, b("\x30\x06ABCDEFGHI")) + x = der.encode_sequence(b"ABC", b"DEF") + b"GHI" + self.assertEqual(x, b"\x30\x06ABCDEFGHI") x1, rest = der.remove_sequence(x) - self.assertEqual(x1, b("ABCDEF")) - self.assertEqual(rest, b("GHI")) + self.assertEqual(x1, b"ABCDEF") + self.assertEqual(rest, b"GHI") def test_constructed(self): x = der.encode_constructed(0, NIST224p.encoded_oid) - self.assertEqual(hexlify(x), b("a007") + b("06052b81040021")) - x = der.encode_constructed(1, unhexlify(b("0102030a0b0c"))) - self.assertEqual(hexlify(x), b("a106") + b("0102030a0b0c")) + self.assertEqual(hexlify(x), b"a007" + b"06052b81040021") + x = der.encode_constructed(1, unhexlify(b"0102030a0b0c")) + self.assertEqual(hexlify(x), b"a106" + b"0102030a0b0c") class Util(unittest.TestCase): @@ -1854,7 +1853,7 @@ def test_trytryagain(self): # this trytryagain *does* provide long-term stability self.assertEqual( ("%x" % (tta("seed", NIST224p.order))).encode(), - b("6fa59d73bf0446ae8743cf748fc5ac11d5585a90356417e97155c3bc"), + b"6fa59d73bf0446ae8743cf748fc5ac11d5585a90356417e97155c3bc", ) def test_trytryagain_single(self): @@ -1912,7 +1911,7 @@ def test_SECP256k1(self): self._do( generator=SECP256k1.generator, secexp=int("9d0219792467d7d37b4d43298a7d0c05", 16), - hsh=hashlib.sha256(b("sample")).digest(), + hsh=hashlib.sha256(b"sample").digest(), hash_func=hashlib.sha256, expected=int( "8fa1f95d514760e498f28957b824ee6ec39ed64826ff4fecc2b5739ec45b91cd", @@ -1927,7 +1926,7 @@ def test_SECP256k1_2(self): "cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50", 16, ), - hsh=hashlib.sha256(b("sample")).digest(), + hsh=hashlib.sha256(b"sample").digest(), hash_func=hashlib.sha256, expected=int( "2df40ca70e639d89528a6b670d9d48d9165fdc0febc0974056bdce192b8e16a3", @@ -1939,7 +1938,7 @@ def test_SECP256k1_3(self): self._do( generator=SECP256k1.generator, secexp=0x1, - hsh=hashlib.sha256(b("Satoshi Nakamoto")).digest(), + hsh=hashlib.sha256(b"Satoshi Nakamoto").digest(), hash_func=hashlib.sha256, expected=0x8F8A276C19F4149656B280621E358CCE24F5F52542772691EE69063B74F15D15, ) @@ -1949,9 +1948,7 @@ def test_SECP256k1_4(self): generator=SECP256k1.generator, secexp=0x1, hsh=hashlib.sha256( - b( - "All those moments will be lost in time, like tears in rain. Time to die..." - ) + b"All those moments will be lost in time, like tears in rain. Time to die..." ).digest(), hash_func=hashlib.sha256, expected=0x38AA22D72376B4DBC472E06C3BA403EE0A394DA63FC58D88686C611ABA98D6B3, @@ -1961,7 +1958,7 @@ def test_SECP256k1_5(self): self._do( generator=SECP256k1.generator, secexp=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140, - hsh=hashlib.sha256(b("Satoshi Nakamoto")).digest(), + hsh=hashlib.sha256(b"Satoshi Nakamoto").digest(), hash_func=hashlib.sha256, expected=0x33A19B60E25FB6F4435AF53A3D42D493644827367E6453928554F43E49AA6F90, ) @@ -1970,7 +1967,7 @@ def test_SECP256k1_6(self): self._do( generator=SECP256k1.generator, secexp=0xF8B8AF8CE3C7CCA5E300D33939540C10D45CE001B8F252BFBC57BA0342904181, - hsh=hashlib.sha256(b("Alan Turing")).digest(), + hsh=hashlib.sha256(b"Alan Turing").digest(), hash_func=hashlib.sha256, expected=0x525A82B70E67874398067543FD84C83D30C175FDC45FDEEE082FE13B1D7CFDF1, ) @@ -1998,7 +1995,7 @@ def test_2(self): self._do( generator=NIST192p.generator, secexp=int("6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4", 16), - hsh=hashlib.sha1(b("sample")).digest(), + hsh=hashlib.sha1(b"sample").digest(), hash_func=hashlib.sha1, expected=int( "37D7CA00D2C7B0E5E412AC03BD44BA837FDD5B28CD3B0021", 16 @@ -2009,7 +2006,7 @@ def test_3(self): self._do( generator=NIST192p.generator, secexp=int("6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4", 16), - hsh=hashlib.sha256(b("sample")).digest(), + hsh=hashlib.sha256(b"sample").digest(), hash_func=hashlib.sha256, expected=int( "32B1B6D7D42A05CB449065727A84804FB1A3E34D8F261496", 16 @@ -2020,7 +2017,7 @@ def test_4(self): self._do( generator=NIST192p.generator, secexp=int("6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4", 16), - hsh=hashlib.sha512(b("sample")).digest(), + hsh=hashlib.sha512(b"sample").digest(), hash_func=hashlib.sha512, expected=int( "A2AC7AB055E4F20692D49209544C203A7D1F2C0BFBC75DB1", 16 @@ -2031,7 +2028,7 @@ def test_5(self): self._do( generator=NIST192p.generator, secexp=int("6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4", 16), - hsh=hashlib.sha1(b("test")).digest(), + hsh=hashlib.sha1(b"test").digest(), hash_func=hashlib.sha1, expected=int( "D9CF9C3D3297D3260773A1DA7418DB5537AB8DD93DE7FA25", 16 @@ -2042,7 +2039,7 @@ def test_6(self): self._do( generator=NIST192p.generator, secexp=int("6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4", 16), - hsh=hashlib.sha256(b("test")).digest(), + hsh=hashlib.sha256(b"test").digest(), hash_func=hashlib.sha256, expected=int( "5C4CE89CF56D9E7C77C8585339B006B97B5F0680B4306C6C", 16 @@ -2053,7 +2050,7 @@ def test_7(self): self._do( generator=NIST192p.generator, secexp=int("6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4", 16), - hsh=hashlib.sha512(b("test")).digest(), + hsh=hashlib.sha512(b"test").digest(), hash_func=hashlib.sha512, expected=int( "0758753A5254759C7CFBAD2E2D9B0792EEE44136C9480527", 16 @@ -2067,7 +2064,7 @@ def test_8(self): "0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538", 16, ), - hsh=hashlib.sha1(b("sample")).digest(), + hsh=hashlib.sha1(b"sample").digest(), hash_func=hashlib.sha1, expected=int( "089C071B419E1C2820962321787258469511958E80582E95D8378E0C2CCDB3CB42BEDE42F50E3FA3C71F5A76724281D31D9C89F0F91FC1BE4918DB1C03A5838D0F9", @@ -2082,7 +2079,7 @@ def test_9(self): "0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538", 16, ), - hsh=hashlib.sha256(b("sample")).digest(), + hsh=hashlib.sha256(b"sample").digest(), hash_func=hashlib.sha256, expected=int( "0EDF38AFCAAECAB4383358B34D67C9F2216C8382AAEA44A3DAD5FDC9C32575761793FEF24EB0FC276DFC4F6E3EC476752F043CF01415387470BCBD8678ED2C7E1A0", @@ -2097,7 +2094,7 @@ def test_10(self): "0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538", 16, ), - hsh=hashlib.sha512(b("test")).digest(), + hsh=hashlib.sha512(b"test").digest(), hash_func=hashlib.sha512, expected=int( "16200813020EC986863BEDFC1B121F605C1215645018AEA1A7B215A564DE9EB1B38A67AA1128B80CE391C4FB71187654AAA3431027BFC7F395766CA988C964DC56D", diff --git a/src/ecdsa/util.py b/src/ecdsa/util.py index 2477f7e2..639bc0c2 100644 --- a/src/ecdsa/util.py +++ b/src/ecdsa/util.py @@ -18,7 +18,7 @@ import binascii import sys from hashlib import sha256 -from six import PY2, int2byte, b, next +from six import PY2, int2byte, next from . import der from ._compat import normalise_bytes @@ -202,7 +202,7 @@ def randrange_from_seed__trytryagain(seed, order): bits, bytes, extrabits = bits_and_bytes(order) generate = PRNG(seed) while True: - extrabyte = b("") + extrabyte = b"" if extrabits: extrabyte = int2byte(ord(generate(1)) & lsb_of_ones(extrabits)) guess = string_to_number(extrabyte + generate(bytes)) + 1 From e55f6e689158b9b1956e0a1f9c99ebe8ed848979 Mon Sep 17 00:00:00 2001 From: Alexandre Detiste Date: Mon, 8 Jan 2024 20:14:26 +0100 Subject: [PATCH 064/105] reformat with 'tox -e codeformat' --- src/ecdsa/test_pyecdsa.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index ffa4ab7a..20201ba2 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -332,9 +332,7 @@ def p(self): pem = pub1.to_pem() self.assertEqual(type(pem), binary_type) self.assertTrue(pem.startswith(b"-----BEGIN PUBLIC KEY-----"), pem) - self.assertTrue( - pem.strip().endswith(b"-----END PUBLIC KEY-----"), pem - ) + self.assertTrue(pem.strip().endswith(b"-----END PUBLIC KEY-----"), pem) pub2 = VerifyingKey.from_pem(pem) self.assertTruePubkeysEqual(pub1, pub2) From 8c2531fd0cee30cd88eee9bfa383d99c9129375e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 2 Jan 2024 19:40:39 +0100 Subject: [PATCH 065/105] more realistic mutation rate and update badge only on merge --- .github/workflows/ci.yml | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f25221df..043d1789 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -268,7 +268,7 @@ jobs: - name: Install mutation testing dependencies if: ${{ matrix.mutation == 'true' }} run: | - pip install cosmic-ray + pip install https://github.com/tomato42/cosmic-ray/archive/random-order.zip - name: Display installed python package versions run: pip list - name: Test native speed @@ -372,6 +372,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 +389,7 @@ jobs: key: sessions-${{ github.sha }} - name: Install cosmic-ray run: | - pip3 install cosmic-ray + pip3 install https://github.com/tomato42/cosmic-ray/archive/random-order.zip - name: Install dependencies run: | sudo apt-get install -y sqlite3 @@ -461,7 +464,7 @@ jobs: - name: Install build dependencies run: | pip install -r build-requirements.txt - pip install cosmic-ray + pip install https://github.com/tomato42/cosmic-ray/archive/random-order.zip - name: Run mutation testing run: | cp sessions/session-${{ matrix.name }}.sqlite session.sqlite @@ -608,7 +611,7 @@ jobs: key: sessions-${{ github.sha }}-19-done - name: Install cosmic-ray run: | - pip3 install cosmic-ray + pip3 install https://github.com/tomato42/cosmic-ray/archive/random-order.zip - name: Install dependencies run: | sudo apt-get install -y sqlite3 @@ -621,13 +624,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 "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 +648,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 From 87ef2b2ce0ad416d113a40ea34a203eb0be8e26f Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 3 Jan 2024 21:54:53 +0100 Subject: [PATCH 066/105] faster ecdh and malformed_sigs tests in mutation testing --- src/ecdsa/test_ecdh.py | 7 +++++++ src/ecdsa/test_malformed_sigs.py | 22 +++++++++++++--------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/ecdsa/test_ecdh.py b/src/ecdsa/test_ecdh.py index 3395a212..7df0243f 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: + curves = [SECP112r2, SECP128r1] + + @pytest.mark.parametrize( "vcurve", curves, 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. From 307fa3a0949bba4dbb400ec9092489fd7f3ef38e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 3 Jan 2024 21:55:17 +0100 Subject: [PATCH 067/105] fast examples for numbertheory tests --- src/ecdsa/numbertheory.py | 16 +++++++++++----- src/ecdsa/test_numbertheory.py | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/ecdsa/numbertheory.py b/src/ecdsa/numbertheory.py index d3500c70..9223a24d 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,14 @@ except ImportError: GMPY = False + +if GMPY2 or GMPY: # pragma: no branch + integer_types = tuple(integer_types + (type(mpz(1)),)) + + import math import warnings +from .util import bit_length class Error(Exception): @@ -555,8 +561,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 +570,7 @@ 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) for k, tt in ( (100, 27), (150, 18), diff --git a/src/ecdsa/test_numbertheory.py b/src/ecdsa/test_numbertheory.py index 983039e5..1d98a004 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,7 @@ 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) class TestNumbertheory(unittest.TestCase): @@ -309,6 +319,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 +334,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 +427,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) From d56453917a70f4b64070ba9522b820e7f1006d80 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 3 Jan 2024 21:57:20 +0100 Subject: [PATCH 068/105] test coverage for keys module --- src/ecdsa/test_keys.py | 115 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py index a41a3dbc..f339d0fc 100644 --- a/src/ecdsa/test_keys.py +++ b/src/ecdsa/test_keys.py @@ -13,7 +13,12 @@ import pytest import hashlib -from .keys import VerifyingKey, SigningKey, MalformedPointError +from .keys import ( + VerifyingKey, + SigningKey, + MalformedPointError, + BadSignatureError, +) from .der import ( unpem, UnexpectedDER, @@ -120,6 +125,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 +414,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 +548,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 +668,71 @@ def test_ed25519_from_pem(self): self.assertEqual(sk, sk_str) + 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 +774,12 @@ def test_ed25519_to_and_from_pem(self): self.assertEqual(sk, decoded) + 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" From 9fc7ed8f5aef9eb6724565a0196a4f61378e1f44 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 3 Jan 2024 22:14:47 +0100 Subject: [PATCH 069/105] mark env-related branches in tests as no cover --- src/ecdsa/test_eddsa.py | 2 +- src/ecdsa/test_sha3.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ecdsa/test_eddsa.py b/src/ecdsa/test_eddsa.py index 1a35fb32..195942d8 100644 --- a/src/ecdsa/test_eddsa.py +++ b/src/ecdsa/test_eddsa.py @@ -664,7 +664,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_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 From ce2dd53554b80e593223d5c59eb228080da3adf7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 3 Jan 2024 22:35:12 +0100 Subject: [PATCH 070/105] more test coverage for keys module, remove dead code --- src/ecdsa/keys.py | 6 ------ src/ecdsa/test_keys.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) 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/test_keys.py b/src/ecdsa/test_keys.py index f339d0fc..348475e2 100644 --- a/src/ecdsa/test_keys.py +++ b/src/ecdsa/test_keys.py @@ -25,6 +25,8 @@ encode_sequence, encode_oid, encode_bitstring, + encode_integer, + encode_octet_string, ) from .util import ( sigencode_string, @@ -668,6 +670,34 @@ 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" @@ -774,6 +804,11 @@ 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) From a394b68a64716069a9cae097f4421da13c12ef6c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 3 Jan 2024 22:42:36 +0100 Subject: [PATCH 071/105] numbertheory: don't require branch coverage for loops that always execute at least once --- src/ecdsa/numbertheory.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ecdsa/numbertheory.py b/src/ecdsa/numbertheory.py index 9223a24d..2ce6ebdc 100644 --- a/src/ecdsa/numbertheory.py +++ b/src/ecdsa/numbertheory.py @@ -222,14 +222,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 @@ -352,7 +353,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: @@ -376,7 +377,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 From 5025ce35b32ea0e089549ec32fa7f1d011689183 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 3 Jan 2024 23:44:44 +0100 Subject: [PATCH 072/105] limit runtime for PR-specific mutation testing --- .github/workflows/ci.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 043d1789..21b80e2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -300,15 +300,25 @@ jobs: - 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 exec cosmic-ray.toml session-vs-master.sqlite & + cosmic_pid=$! + for i in $(seq 1 10); do + echo $i + for j in $(seq 1 60); do + echo -n . + sleep 1 + done + done + kill $cosmic_pid + 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 + # check if executed have at most 15% survival rate + cr-rate --estimate --confidence 99.9 --fail-over 15 session-vs-master.sqlite - name: instrumental test coverage on PR if: ${{ contains(matrix.opt-deps, 'instrumental') && github.event.pull_request }} env: From 304263b7352b1bf994bf274b5523178531a07954 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 4 Jan 2024 01:30:50 +0100 Subject: [PATCH 073/105] provide debugging information in mutation run, upload mutation report --- .github/workflows/ci.yml | 43 +++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21b80e2d..f3390f4a 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,7 @@ jobs: - name: Install mutation testing dependencies if: ${{ matrix.mutation == 'true' }} run: | - pip install https://github.com/tomato42/cosmic-ray/archive/random-order.zip + pip install https://github.com/sixty-north/cosmic-ray/archive/master.zip - name: Display installed python package versions run: pip list - name: Test native speed @@ -296,27 +296,38 @@ 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: | - systemd-run --user --scope -p MemoryMax=2G -p MemoryHigh=2G 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 10); do - echo $i - for j in $(seq 1 60); do - echo -n . - sleep 1 - done + 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 + 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 + 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 15% survival rate cr-rate --estimate --confidence 99.9 --fail-over 15 session-vs-master.sqlite - name: instrumental test coverage on PR @@ -399,7 +410,7 @@ jobs: key: sessions-${{ github.sha }} - name: Install cosmic-ray run: | - pip3 install https://github.com/tomato42/cosmic-ray/archive/random-order.zip + pip3 install https://github.com/sixty-north/cosmic-ray/archive/master.zip - name: Install dependencies run: | sudo apt-get install -y sqlite3 @@ -474,7 +485,7 @@ jobs: - name: Install build dependencies run: | pip install -r build-requirements.txt - pip install https://github.com/tomato42/cosmic-ray/archive/random-order.zip + pip install https://github.com/sixty-north/cosmic-ray/archive/master.zip - name: Run mutation testing run: | cp sessions/session-${{ matrix.name }}.sqlite session.sqlite @@ -621,7 +632,7 @@ jobs: key: sessions-${{ github.sha }}-19-done - name: Install cosmic-ray run: | - pip3 install https://github.com/tomato42/cosmic-ray/archive/random-order.zip + pip3 install https://github.com/sixty-north/cosmic-ray/archive/master.zip - name: Install dependencies run: | sudo apt-get install -y sqlite3 From 79bc0c5f4692a495f20158c57b2c2d7fda02833e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 15 Jan 2024 00:20:48 +0100 Subject: [PATCH 074/105] more test coverage for ellipticurve module --- src/ecdsa/ellipticcurve.py | 4 +++- src/ecdsa/test_ecdh.py | 2 +- src/ecdsa/test_eddsa.py | 21 +++++++++++++++++++++ src/ecdsa/test_ellipticcurve.py | 31 +++++++++++++++++++++++++++++++ src/ecdsa/test_jacobi.py | 6 ++++++ 5 files changed, 62 insertions(+), 2 deletions(-) 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/test_ecdh.py b/src/ecdsa/test_ecdh.py index 7df0243f..cb225803 100644 --- a/src/ecdsa/test_ecdh.py +++ b/src/ecdsa/test_ecdh.py @@ -32,7 +32,7 @@ from .ellipticcurve import CurveEdTw -if "--fast" in sys.argv: +if "--fast" in sys.argv: # pragma: no cover curves = [SECP112r2, SECP128r1] diff --git a/src/ecdsa/test_eddsa.py b/src/ecdsa/test_eddsa.py index 195942d8..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() 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) From e16173b25dfc9ea4b1cc323874369c1d38fb3664 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 15 Jan 2024 00:29:03 +0100 Subject: [PATCH 075/105] two digit precision for the mutation score badge --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f3390f4a..57faef05 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -655,7 +655,7 @@ jobs: 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 }} From 09f0d106d13d05089078c95a947ad4f3a2602a5f Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 15 Jan 2024 00:50:14 +0100 Subject: [PATCH 076/105] add hard timeout for test mutation test suite --- .github/workflows/ci.yml | 4 ++++ cosmic-ray-12way.toml | 2 +- cosmic-ray.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 57faef05..405d4f01 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -269,6 +269,7 @@ jobs: if: ${{ matrix.mutation == 'true' }} run: | 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 @@ -411,6 +412,7 @@ jobs: - name: Install cosmic-ray run: | 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 @@ -486,6 +488,7 @@ jobs: run: | pip install -r build-requirements.txt 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 @@ -633,6 +636,7 @@ jobs: - name: Install cosmic-ray run: | 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 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" From 4845e8fa64c1b1463e400ac1a0d5c4e87615bcde Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 15 Jan 2024 01:42:25 +0100 Subject: [PATCH 077/105] better is_prime() --- src/ecdsa/numbertheory.py | 4 +++- src/ecdsa/test_numbertheory.py | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/ecdsa/numbertheory.py b/src/ecdsa/numbertheory.py index 2ce6ebdc..fe974f8e 100644 --- a/src/ecdsa/numbertheory.py +++ b/src/ecdsa/numbertheory.py @@ -40,6 +40,7 @@ import math import warnings +import random from .util import bit_length @@ -573,6 +574,7 @@ def is_prime(n): t = 40 n_bits = 1 + bit_length(n) + assert 11 <= n_bits <= 16384 for k, tt in ( (100, 27), (150, 18), @@ -599,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_numbertheory.py b/src/ecdsa/test_numbertheory.py index 1d98a004..966eca29 100644 --- a/src/ecdsa/test_numbertheory.py +++ b/src/ecdsa/test_numbertheory.py @@ -305,6 +305,29 @@ def test_large_prime(self): # nextPrime[2^2048] 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): def test_gcd(self): From 02c83503ffcd06a0ac15535a3afac88ad5725710 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 15 Jan 2024 01:43:56 +0100 Subject: [PATCH 078/105] be more permissive for the PR mutation test coverage --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 405d4f01..8568f31f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -329,8 +329,8 @@ jobs: - name: Check test coverage for PR if: ${{ matrix.mutation == 'true' && github.event.pull_request }} run: | - # check if executed have at most 15% survival rate - cr-rate --estimate --confidence 99.9 --fail-over 15 session-vs-master.sqlite + # 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: From 66d0d74a331af339715e1e70274a45f775817f7f Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 8 Apr 2024 17:38:30 +0200 Subject: [PATCH 079/105] add release notes for 0.19.0 release --- NEWS | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/NEWS b/NEWS index cba5f930..5f27c168 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,34 @@ +* Release 0.19.0 (08 Apr 2024) + +New API: +* `to_ssh` in `VerifyingKey` and `SigningKey`, supports Ed25519 keys only + (Pablo Mazzini) + +New features: +* Support for twisted Brainpool curves + +Doc fix: +* Fix curve equation in glossary +* Documentation for signature encoding and signature decoding functions + +Maintenance: +* Dropped official support for 3.3 and 3.4 (because of problems running them + in CI, not because it's actually incompatible; support for 2.6 and 2.7 is + unaffected) +* Fixes aroung hypothesis parameters +* Officially support Python 3.11 and 3.12 +* Small updates to test suite to make it work with 3.11 and 3.12 and new + releases of test dependencies +* Dropped the internal `_rwlock` module as it's unused +* Added mutation testing to CI, lots of speed-ups to the test suite + to make it happen +* Removal of unnecessary `six.b` literals (Alexandre Detiste) + +Deprecations: +* `int_to_string`, `string_to_int`, and `digest_integer` from `ecdsa.ecdsa` + module are now considered deprecated, they will be removed in a future + release + * Release 0.18.0 (09 Jul 2022) New API: From c56030efe0044c6deb8a5f815eabb590e574fc41 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 8 Apr 2024 19:29:38 +0200 Subject: [PATCH 080/105] make coveralls submission work with py2.6 again --- .github/workflows/ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8568f31f..b8636078 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -374,7 +374,13 @@ jobs: COVERALLS_FLAG_NAME: ${{ matrix.name }} COVERALLS_PARALLEL: true COVERALLS_SERVICE_NAME: github - run: coveralls + PY_VERSION: ${{ matrix.python-version }} + run: | + if [[ $PY_VERSION == "2.6" ]]; then + COVERALLS_SKIP_SSL_VERIFY=1 coveralls + else + coveralls + fi coveralls: name: Indicate completion to coveralls.io From 6e7adff153ad877747f56771c842a94bca65ede9 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 8 Apr 2024 19:42:52 +0200 Subject: [PATCH 081/105] don't check rate if no tests executed --- .github/workflows/ci.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8636078..ba4238c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -268,7 +268,8 @@ jobs: - name: Install mutation testing dependencies if: ${{ matrix.mutation == 'true' }} run: | - pip install https://github.com/sixty-north/cosmic-ray/archive/master.zip + #pip install https://github.com/sixty-north/cosmic-ray/archive/master.zip + pip install https://github.com/tomato42/cosmic-ray/archive/no-executed.zip pip install pytest-timeout - name: Display installed python package versions run: pip list @@ -417,7 +418,8 @@ jobs: key: sessions-${{ github.sha }} - name: Install cosmic-ray run: | - pip3 install https://github.com/sixty-north/cosmic-ray/archive/master.zip + #pip3 install https://github.com/sixty-north/cosmic-ray/archive/master.zip + pip3 install https://github.com/tomato42/cosmic-ray/archive/no-executed.zip pip install pytest-timeout - name: Install dependencies run: | @@ -493,7 +495,8 @@ jobs: - name: Install build dependencies run: | pip install -r build-requirements.txt - pip install https://github.com/sixty-north/cosmic-ray/archive/master.zip + #pip install https://github.com/sixty-north/cosmic-ray/archive/master.zip + pip install https://github.com/tomato42/cosmic-ray/archive/no-executed.zip pip install pytest-timeout - name: Run mutation testing run: | @@ -641,7 +644,8 @@ jobs: key: sessions-${{ github.sha }}-19-done - name: Install cosmic-ray run: | - pip3 install https://github.com/sixty-north/cosmic-ray/archive/master.zip + #pip3 install https://github.com/sixty-north/cosmic-ray/archive/master.zip + pip3 install https://github.com/tomato42/cosmic-ray/archive/no-executed.zip pip install pytest-timeout - name: Install dependencies run: | From 217735bb28dd30c12619564da8b0ec7022ec0a95 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 8 Apr 2024 20:37:59 +0200 Subject: [PATCH 082/105] allow early exit from worker processes when running mutation testing --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba4238c0..36d9445d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -503,7 +503,11 @@ jobs: cp sessions/session-${{ matrix.name }}.sqlite session.sqlite systemd-run --user --scope -p MemoryMax=2G -p MemoryHigh=2G cosmic-ray exec cosmic-ray.toml session.sqlite & cosmic_pid=$! + sleep 1 for i in $(seq 1 10); do + if ! kill -s 0 $cosmic_pid; then + break + fi echo $i sleep 60 done From 0bdcf5ecb13d1f370c39470e0cdbf0e770edcd12 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 11 Apr 2024 02:27:57 +0200 Subject: [PATCH 083/105] fix mutation testing in CI --- .github/workflows/ci.yml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36d9445d..c73bc005 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -268,8 +268,7 @@ jobs: - name: Install mutation testing dependencies if: ${{ matrix.mutation == 'true' }} run: | - #pip install https://github.com/sixty-north/cosmic-ray/archive/master.zip - pip install https://github.com/tomato42/cosmic-ray/archive/no-executed.zip + 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 @@ -306,7 +305,9 @@ jobs: cosmic_pid=$! for i in $(seq 1 600); do # wait for test execution at most 10 minutes - kill -s 0 $cosmic_pid || break + if ! kill -s 0 $cosmic_pid; then + break + fi sleep 1 done kill $cosmic_pid || true @@ -418,8 +419,7 @@ jobs: key: sessions-${{ github.sha }} - name: Install cosmic-ray run: | - #pip3 install https://github.com/sixty-north/cosmic-ray/archive/master.zip - pip3 install https://github.com/tomato42/cosmic-ray/archive/no-executed.zip + pip3 install https://github.com/sixty-north/cosmic-ray/archive/master.zip pip install pytest-timeout - name: Install dependencies run: | @@ -495,8 +495,7 @@ jobs: - name: Install build dependencies run: | pip install -r build-requirements.txt - #pip install https://github.com/sixty-north/cosmic-ray/archive/master.zip - pip install https://github.com/tomato42/cosmic-ray/archive/no-executed.zip + pip install https://github.com/sixty-north/cosmic-ray/archive/master.zip pip install pytest-timeout - name: Run mutation testing run: | @@ -511,7 +510,7 @@ jobs: echo $i sleep 60 done - kill $cosmic_pid + kill -s 0 $cosmic_pid && kill $cosmic_pid mkdir sessions-done/ cp session.sqlite sessions-done/session-${{ matrix.name }}-done.sqlite - name: Report executed @@ -648,8 +647,7 @@ jobs: key: sessions-${{ github.sha }}-19-done - name: Install cosmic-ray run: | - #pip3 install https://github.com/sixty-north/cosmic-ray/archive/master.zip - pip3 install https://github.com/tomato42/cosmic-ray/archive/no-executed.zip + pip3 install https://github.com/sixty-north/cosmic-ray/archive/master.zip pip install pytest-timeout - name: Install dependencies run: | From 469d127ad50d4b39d2cd88c331bb9e3bacef3bff Mon Sep 17 00:00:00 2001 From: Alexandre Detiste Date: Wed, 24 Apr 2024 22:18:41 +0200 Subject: [PATCH 084/105] trim more usage of six.b() --- src/ecdsa/ecdsa.py | 8 +-- src/ecdsa/ellipticcurve.py | 4 +- src/ecdsa/test_pyecdsa.py | 110 ++++++++++++++++++------------------- 3 files changed, 59 insertions(+), 63 deletions(-) diff --git a/src/ecdsa/ecdsa.py b/src/ecdsa/ecdsa.py index 9284ace4..1c2e25c5 100644 --- a/src/ecdsa/ecdsa.py +++ b/src/ecdsa/ecdsa.py @@ -40,16 +40,16 @@ # Verifying a signature for a hash value: if pubkey.verifies( hash, signature ): - print_("Demo verification succeeded.") + print("Demo verification succeeded.") else: - print_("*** Demo verification failed.") + print("*** Demo verification failed.") # Verification fails if the hash value is modified: if pubkey.verifies( hash-1, signature ): - print_("**** Demo verification failed to reject tampered hash.") + print("**** Demo verification failed to reject tampered hash.") else: - print_("Demo verification correctly rejected tampered hash.") + print("Demo verification correctly rejected tampered hash.") Revision history: 2005.12.31 - Initial version. diff --git a/src/ecdsa/ellipticcurve.py b/src/ecdsa/ellipticcurve.py index 18816a66..1ea04a47 100644 --- a/src/ecdsa/ellipticcurve.py +++ b/src/ecdsa/ellipticcurve.py @@ -1223,14 +1223,14 @@ def leftmost_bit(x): negative_self = Point(self.__curve, self.__x, -self.__y, self.__order) i = leftmost_bit(e3) // 2 result = self - # print_("Multiplying %s by %d (e3 = %d):" % (self, other, e3)) + # print("Multiplying %s by %d (e3 = %d):" % (self, other, e3)) while i > 1: result = result.double() if (e3 & i) != 0 and (e & i) == 0: result = result + self if (e3 & i) == 0 and (e & i) != 0: result = result + negative_self - # print_(". . . i = %d, result = %s" % ( i, result )) + # print(". . . i = %d, result = %s" % ( i, result )) i = i // 2 return result diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index 20201ba2..69f85c61 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -1,4 +1,4 @@ -from __future__ import with_statement, division +from __future__ import with_statement, division, print_function try: import unittest2 as unittest @@ -16,7 +16,7 @@ from hypothesis import given, settings import hypothesis.strategies as st -from six import b, print_, binary_type +from six import binary_type from .keys import SigningKey, VerifyingKey from .keys import BadSignatureError, MalformedPointError, BadDigestError from . import util @@ -365,9 +365,7 @@ def test_sk_to_der_with_invalid_point_encoding(self): def test_vk_from_der_garbage_after_curve_oid(self): type_oid_der = encoded_oid_ecPublicKey - curve_oid_der = der.encode_oid(*(1, 2, 840, 10045, 3, 1, 1)) + b( - "garbage" - ) + curve_oid_der = der.encode_oid(*(1, 2, 840, 10045, 3, 1, 1)) + b"garbage" enc_type_der = der.encode_sequence(type_oid_der, curve_oid_der) point_der = der.encode_bitstring(b"\x00\xff", None) to_decode = der.encode_sequence(enc_type_der, point_der) @@ -770,10 +768,10 @@ def test_encoding(self): sk = SigningKey.from_secret_exponent(123456789) vk = sk.verifying_key - exp = 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*" + exp = ( + b"\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3" + b"\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4" + b"z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*" ) self.assertEqual(vk.to_string(), exp) self.assertEqual(vk.to_string("raw"), exp) @@ -785,10 +783,10 @@ def test_decoding(self): sk = SigningKey.from_secret_exponent(123456789) vk = sk.verifying_key - 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 = ( + b"\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3" + b"\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4" + b"z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*" ) from_raw = VerifyingKey.from_string(enc) @@ -804,11 +802,11 @@ def test_decoding(self): self.assertEqual(from_uncompressed.pubkey.point, vk.pubkey.point) def test_uncompressed_decoding_as_only_alowed(self): - enc = b( - "\x04" - "\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 = ( + b"\x04" + b"\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3" + b"\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4" + b"z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*" ) vk = VerifyingKey.from_string(enc, valid_encodings=("uncompressed",)) sk = SigningKey.from_secret_exponent(123456789) @@ -816,10 +814,10 @@ def test_uncompressed_decoding_as_only_alowed(self): self.assertEqual(vk, sk.verifying_key) def test_raw_decoding_with_blocked_format(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 = ( + b"\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3" + b"\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4" + b"z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*" ) with self.assertRaises(MalformedPointError) as exp: VerifyingKey.from_string(enc, valid_encodings=("hybrid",)) @@ -833,11 +831,11 @@ def test_decoding_with_unknown_format(self): self.assertIn("Only uncompressed, compressed", str(e.exception)) def test_uncompressed_decoding_with_blocked_format(self): - enc = b( - "\x04" - "\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 = ( + b"\x04" + b"\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3" + b"\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4" + b"z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*" ) with self.assertRaises(MalformedPointError) as exp: VerifyingKey.from_string(enc, valid_encodings=("hybrid",)) @@ -845,11 +843,11 @@ def test_uncompressed_decoding_with_blocked_format(self): self.assertIn("Invalid X9.62 encoding", str(exp.exception)) def test_hybrid_decoding_with_blocked_format(self): - enc = b( - "\x06" - "\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 = ( + b"\x06" + b"\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3" + b"\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4" + b"z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*" ) with self.assertRaises(MalformedPointError) as exp: VerifyingKey.from_string(enc, valid_encodings=("uncompressed",)) @@ -857,11 +855,11 @@ def test_hybrid_decoding_with_blocked_format(self): self.assertIn("Invalid X9.62 encoding", str(exp.exception)) def test_compressed_decoding_with_blocked_format(self): - enc = b( - "\x02" - "\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 = ( + b"\x02" + b"\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3" + b"\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4" + b"z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*" )[:25] with self.assertRaises(MalformedPointError) as exp: VerifyingKey.from_string(enc, valid_encodings=("hybrid", "raw")) @@ -869,40 +867,40 @@ def test_compressed_decoding_with_blocked_format(self): self.assertIn("(hybrid, raw)", str(exp.exception)) 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 = ( + b"\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3" + b"\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4" + b"z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*" ) with self.assertRaises(MalformedPointError): VerifyingKey.from_string(b"\x02" + enc) def test_decoding_with_malformed_compressed(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 = ( + b"\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3" + b"\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4" + b"z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*" ) with self.assertRaises(MalformedPointError): VerifyingKey.from_string(b"\x01" + enc[:24]) def test_decoding_with_inconsistent_hybrid(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 = ( + b"\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3" + b"\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4" + b"z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*" ) with self.assertRaises(MalformedPointError): VerifyingKey.from_string(b"\x07" + 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*" + enc = ( + b"\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3" + b"\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4" + b"z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*" ) with self.assertRaises(MalformedPointError): @@ -1894,7 +1892,7 @@ def OFF_test_prove_uniformity(self): # pragma: no cover # this technique should use the full range self.assertTrue(counts[order - 1]) for i in range(1, order): - print_("%3d: %s" % (i, "*" * (counts[i] // 100))) + print("%3d: %s" % (i, "*" * (counts[i] // 100))) class RFC6979(unittest.TestCase): @@ -1981,9 +1979,7 @@ def test_1(self): ), secexp=int("09A4D6792295A7F730FC3F2B49CBC0F62E862272F", 16), hsh=unhexlify( - b( - "AF2BDBE1AA9B6EC1E2ADE1D694F41FC71A831D0268E9891562113D8A62ADD1BF" - ) + b"AF2BDBE1AA9B6EC1E2ADE1D694F41FC71A831D0268E9891562113D8A62ADD1BF" ), hash_func=hashlib.sha256, expected=int("23AF4074C90A02B3FE61D286D5C87F425E6BDD81B", 16), From bfce13b5edb3b8d7e2f601e641beadaa7d34a684 Mon Sep 17 00:00:00 2001 From: Alexandre Detiste Date: Wed, 24 Apr 2024 22:23:43 +0200 Subject: [PATCH 085/105] tox -e codeformat --- src/ecdsa/test_pyecdsa.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index 69f85c61..0a4ed8af 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -365,7 +365,9 @@ def test_sk_to_der_with_invalid_point_encoding(self): def test_vk_from_der_garbage_after_curve_oid(self): type_oid_der = encoded_oid_ecPublicKey - curve_oid_der = der.encode_oid(*(1, 2, 840, 10045, 3, 1, 1)) + b"garbage" + curve_oid_der = ( + der.encode_oid(*(1, 2, 840, 10045, 3, 1, 1)) + b"garbage" + ) enc_type_der = der.encode_sequence(type_oid_der, curve_oid_der) point_der = der.encode_bitstring(b"\x00\xff", None) to_decode = der.encode_sequence(enc_type_der, point_der) From 96382a01bf9e530d84d3b2ec0ac83d8f1a07e98b Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 6 Aug 2024 23:43:32 +0200 Subject: [PATCH 086/105] drop python 3.5 --- .github/workflows/ci.yml | 4 ---- README.md | 2 +- setup.py | 4 ++-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c73bc005..b7f785cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,10 +47,6 @@ jobs: os: ubuntu-20.04 python-version: 2.7 tox-env: gmpy2py27 - - name: py3.5 - os: ubuntu-20.04 - python-version: 3.5 - tox-env: py35 - name: py3.6 os: ubuntu-20.04 python-version: 3.6 diff --git a/README.md b/README.md index 6c43f85d..980abc1e 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ curves over prime fields. ## Dependencies This library uses only Python and the 'six' package. It is compatible with -Python 2.6, 2.7, and 3.5+. It also supports execution on alternative +Python 2.6, 2.7, and 3.6+. It also supports execution on alternative implementations like pypy and pypy3. If `gmpy2` or `gmpy` is installed, they will be used for faster arithmetic. diff --git a/setup.py b/setup.py index a9ae2439..341fe734 100755 --- a/setup.py +++ b/setup.py @@ -27,14 +27,14 @@ package_dir={"": "src"}, license="MIT", cmdclass=commands, - python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", + python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, " + "!=3.5.*", classifiers=[ "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", From 1ca8c56d33cd73f9a63657b9fca51198bc9aa2b5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 9 Aug 2024 17:32:27 +0200 Subject: [PATCH 087/105] use release version of cosmic-ray --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b7f785cf..1f946e5f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -264,7 +264,7 @@ jobs: - name: Install mutation testing dependencies if: ${{ matrix.mutation == 'true' }} run: | - pip install https://github.com/sixty-north/cosmic-ray/archive/master.zip + pip install cosmic-ray pip install pytest-timeout - name: Display installed python package versions run: pip list @@ -415,7 +415,7 @@ jobs: key: sessions-${{ github.sha }} - name: Install cosmic-ray run: | - pip3 install https://github.com/sixty-north/cosmic-ray/archive/master.zip + pip3 install cosmic-ray pip install pytest-timeout - name: Install dependencies run: | @@ -491,7 +491,7 @@ jobs: - name: Install build dependencies run: | pip install -r build-requirements.txt - pip install https://github.com/sixty-north/cosmic-ray/archive/master.zip + pip install cosmic-ray pip install pytest-timeout - name: Run mutation testing run: | @@ -643,7 +643,7 @@ jobs: key: sessions-${{ github.sha }}-19-done - name: Install cosmic-ray run: | - pip3 install https://github.com/sixty-north/cosmic-ray/archive/master.zip + pip3 install cosmic-ray pip install pytest-timeout - name: Install dependencies run: | From 82540382009f19c3cc93f98827bf99d47bc6cc81 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 6 Aug 2024 23:32:41 +0200 Subject: [PATCH 088/105] handle non-prime order curves more gracefully when the order of the curve is not a prime, then point doubling can return INFINITY, this will cause some negative values not to be reduced modulo curve p; fix this --- src/ecdsa/ellipticcurve.py | 30 +++++++++++++++++++----------- src/ecdsa/test_ellipticcurve.py | 27 +++++++++++++++++++++++++++ src/ecdsa/test_jacobi.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/src/ecdsa/ellipticcurve.py b/src/ecdsa/ellipticcurve.py index 1ea04a47..6dedff82 100644 --- a/src/ecdsa/ellipticcurve.py +++ b/src/ecdsa/ellipticcurve.py @@ -633,7 +633,7 @@ def __eq__(self, other): """ x1, y1, z1 = self.__coords if other is INFINITY: - return not y1 or not z1 + return (not x1 and not y1) or not z1 if isinstance(other, Point): x2, y2, z2 = other.x(), other.y(), 1 elif isinstance(other, PointJacobi): @@ -728,6 +728,7 @@ def to_affine(self): return INFINITY self.scale() x, y, z = self.__coords + assert z == 1 return Point(self.__curve, x, y, self.__order) @staticmethod @@ -802,7 +803,7 @@ def double(self): X3, Y3, Z3 = self._double(X1, Y1, Z1, p, a) - if not Y3 or not Z3: + if not Y3 and not X3: return INFINITY return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) @@ -886,10 +887,10 @@ def __radd__(self, other): def _add(self, X1, Y1, Z1, X2, Y2, Z2, p): """add two points, select fastest method.""" - if not Y1 or not Z1: - return X2, Y2, Z2 - if not Y2 or not Z2: - return X1, Y1, Z1 + if (not X1 and not Y1) or not Z1: + return X2 % p, Y2 % p, Z2 % p + if (not X2 and not Y2) or not Z2: + return X1 % p, Y1 % p, Z1 % p if Z1 == Z2: if Z1 == 1: return self._add_with_z_1(X1, Y1, X2, Y2, p) @@ -917,7 +918,7 @@ def __add__(self, other): X3, Y3, Z3 = self._add(X1, Y1, Z1, X2, Y2, Z2, p) - if not Y3 or not Z3: + if (not X3 and not Y3) or not Z3: return INFINITY return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) @@ -972,7 +973,7 @@ def __mul__(self, other): elif i > 0: X3, Y3, Z3 = _add(X3, Y3, Z3, X2, Y2, 1, p) - if not Y3 or not Z3: + if (not X3 and not Y3) or not Z3: return INFINITY return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) @@ -1070,7 +1071,7 @@ def mul_add(self, self_mul, other, other_mul): assert B > 0 X3, Y3, Z3 = _add(X3, Y3, Z3, pApB_X, pApB_Y, pApB_Z, p) - if not Y3 or not Z3: + if (not X3 and not Y3) or not Z3: return INFINITY return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) @@ -1220,7 +1221,12 @@ def leftmost_bit(x): # From X9.62 D.3.2: e3 = 3 * e - negative_self = Point(self.__curve, self.__x, -self.__y, self.__order) + negative_self = Point( + self.__curve, + self.__x, + (-self.__y) % self.__curve.p(), + self.__order, + ) i = leftmost_bit(e3) // 2 result = self # print("Multiplying %s by %d (e3 = %d):" % (self, other, e3)) @@ -1247,7 +1253,6 @@ def __str__(self): def double(self): """Return a new point that is twice the old.""" - if self == INFINITY: return INFINITY @@ -1261,6 +1266,9 @@ def double(self): * numbertheory.inverse_mod(2 * self.__y, p) ) % p + if not l: + return INFINITY + x3 = (l * l - 2 * self.__x) % p y3 = (l * (self.__x - x3) - self.__y) % p diff --git a/src/ecdsa/test_ellipticcurve.py b/src/ecdsa/test_ellipticcurve.py index 9bf09513..db5ac7c4 100644 --- a/src/ecdsa/test_ellipticcurve.py +++ b/src/ecdsa/test_ellipticcurve.py @@ -184,6 +184,33 @@ def test_double(self): self.assertEqual(p3.x(), x3) self.assertEqual(p3.y(), y3) + def test_double_to_infinity(self): + p1 = Point(self.c_23, 11, 20) + p2 = p1.double() + self.assertEqual((p2.x(), p2.y()), (4, 0)) + self.assertNotEqual(p2, INFINITY) + p3 = p2.double() + self.assertEqual(p3, INFINITY) + self.assertIs(p3, INFINITY) + + def test_add_self_to_infinity(self): + p1 = Point(self.c_23, 11, 20) + p2 = p1 + p1 + self.assertEqual((p2.x(), p2.y()), (4, 0)) + self.assertNotEqual(p2, INFINITY) + p3 = p2 + p2 + self.assertEqual(p3, INFINITY) + self.assertIs(p3, INFINITY) + + def test_mul_to_infinity(self): + p1 = Point(self.c_23, 11, 20) + p2 = p1 * 2 + self.assertEqual((p2.x(), p2.y()), (4, 0)) + self.assertNotEqual(p2, INFINITY) + p3 = p2 * 2 + self.assertEqual(p3, INFINITY) + self.assertIs(p3, INFINITY) + def test_multiply(self): x1, y1, m, x3, y3 = (3, 10, 2, 7, 12) p1 = Point(self.c_23, x1, y1) diff --git a/src/ecdsa/test_jacobi.py b/src/ecdsa/test_jacobi.py index 9a46afea..bb06cce6 100644 --- a/src/ecdsa/test_jacobi.py +++ b/src/ecdsa/test_jacobi.py @@ -641,6 +641,36 @@ def test_add_with_point_at_infinity(self): self.assertEqual((x, y, z), (2, 3, 1)) + def test_double_to_infinity(self): + c_23 = CurveFp(23, 1, 1) + p = PointJacobi(c_23, 11, 20, 1) + p2 = p.double() + self.assertEqual((p2.x(), p2.y()), (4, 0)) + self.assertNotEqual(p2, INFINITY) + p3 = p2.double() + self.assertEqual(p3, INFINITY) + self.assertIs(p3, INFINITY) + + def test_mul_to_infinity(self): + c_23 = CurveFp(23, 1, 1) + p = PointJacobi(c_23, 11, 20, 1) + p2 = p * 2 + self.assertEqual((p2.x(), p2.y()), (4, 0)) + self.assertNotEqual(p2, INFINITY) + p3 = p2 * 2 + self.assertEqual(p3, INFINITY) + self.assertIs(p3, INFINITY) + + def test_add_to_infinity(self): + c_23 = CurveFp(23, 1, 1) + p = PointJacobi(c_23, 11, 20, 1) + p2 = p + p + self.assertEqual((p2.x(), p2.y()), (4, 0)) + self.assertNotEqual(p2, INFINITY) + p3 = p2 + p2 + self.assertEqual(p3, INFINITY) + self.assertIs(p3, INFINITY) + 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) From 3d00b320fa675d79a5c1e00dc4bac41abf968d91 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 9 Aug 2024 01:14:38 +0200 Subject: [PATCH 089/105] work with curves that have x=0, y=0 as point on the curve --- src/ecdsa/ellipticcurve.py | 41 ++++---- src/ecdsa/test_ellipticcurve.py | 6 ++ src/ecdsa/test_jacobi.py | 170 ++++++++++++++++++++++++++++++-- 3 files changed, 191 insertions(+), 26 deletions(-) diff --git a/src/ecdsa/ellipticcurve.py b/src/ecdsa/ellipticcurve.py index 6dedff82..a982c1e4 100644 --- a/src/ecdsa/ellipticcurve.py +++ b/src/ecdsa/ellipticcurve.py @@ -633,7 +633,7 @@ def __eq__(self, other): """ x1, y1, z1 = self.__coords if other is INFINITY: - return (not x1 and not y1) or not z1 + return not z1 if isinstance(other, Point): x2, y2, z2 = other.x(), other.y(), 1 elif isinstance(other, PointJacobi): @@ -723,8 +723,9 @@ def scale(self): def to_affine(self): """Return point in affine form.""" - _, y, z = self.__coords - if not y or not z: + _, _, z = self.__coords + p = self.__curve.p() + if not (z % p): return INFINITY self.scale() x, y, z = self.__coords @@ -760,7 +761,7 @@ def _double_with_z_1(self, X1, Y1, p, a): # http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-mdbl-2007-bl XX, YY = X1 * X1 % p, Y1 * Y1 % p if not YY: - return 0, 0, 1 + return 0, 0, 0 YYYY = YY * YY % p S = 2 * ((X1 + YY) ** 2 - XX - YYYY) % p M = 3 * XX + a @@ -774,13 +775,13 @@ 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) - if not Y1 or not Z1: - return 0, 0, 1 + if not Z1: + return 0, 0, 0 # after: # http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-2007-bl XX, YY = X1 * X1 % p, Y1 * Y1 % p if not YY: - return 0, 0, 1 + return 0, 0, 0 YYYY = YY * YY % p ZZ = Z1 * Z1 % p S = 2 * ((X1 + YY) ** 2 - XX - YYYY) % p @@ -796,14 +797,14 @@ def double(self): """Add a point to itself.""" X1, Y1, Z1 = self.__coords - if not Y1: + if not Z1: return INFINITY p, a = self.__curve.p(), self.__curve.a() X3, Y3, Z3 = self._double(X1, Y1, Z1, p, a) - if not Y3 and not X3: + if not Z3: return INFINITY return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) @@ -887,9 +888,9 @@ def __radd__(self, other): def _add(self, X1, Y1, Z1, X2, Y2, Z2, p): """add two points, select fastest method.""" - if (not X1 and not Y1) or not Z1: + if not Z1: return X2 % p, Y2 % p, Z2 % p - if (not X2 and not Y2) or not Z2: + if not Z2: return X1 % p, Y1 % p, Z1 % p if Z1 == Z2: if Z1 == 1: @@ -918,7 +919,7 @@ def __add__(self, other): X3, Y3, Z3 = self._add(X1, Y1, Z1, X2, Y2, Z2, p) - if (not X3 and not Y3) or not Z3: + if not Z3: return INFINITY return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) @@ -928,7 +929,7 @@ def __rmul__(self, other): def _mul_precompute(self, other): """Multiply point by integer with precomputation table.""" - X3, Y3, Z3, p = 0, 0, 1, self.__curve.p() + X3, Y3, Z3, p = 0, 0, 0, self.__curve.p() _add = self._add for X2, Y2 in self.__precompute: if other % 2: @@ -941,7 +942,7 @@ def _mul_precompute(self, other): else: other //= 2 - if not Y3 or not Z3: + if not Z3: return INFINITY return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) @@ -960,7 +961,7 @@ def __mul__(self, other): self = self.scale() X2, Y2, _ = self.__coords - X3, Y3, Z3 = 0, 0, 1 + X3, Y3, Z3 = 0, 0, 0 p, a = self.__curve.p(), self.__curve.a() _double = self._double _add = self._add @@ -973,7 +974,7 @@ def __mul__(self, other): elif i > 0: X3, Y3, Z3 = _add(X3, Y3, Z3, X2, Y2, 1, p) - if (not X3 and not Y3) or not Z3: + if not Z3: return INFINITY return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) @@ -1002,7 +1003,7 @@ def mul_add(self, self_mul, other, other_mul): other_mul = other_mul % self.__order # (X3, Y3, Z3) is the accumulator - X3, Y3, Z3 = 0, 0, 1 + X3, Y3, Z3 = 0, 0, 0 p, a = self.__curve.p(), self.__curve.a() # as we have 6 unique points to work with, we can't scale all of them, @@ -1026,7 +1027,7 @@ def mul_add(self, self_mul, other, other_mul): # when the self and other sum to infinity, we need to add them # one by one to get correct result but as that's very unlikely to # happen in regular operation, we don't need to optimise this case - if not pApB_Y or not pApB_Z: + if not pApB_Z: return self * self_mul + other * other_mul # gmp object creation has cumulatively higher overhead than the @@ -1071,7 +1072,7 @@ def mul_add(self, self_mul, other, other_mul): assert B > 0 X3, Y3, Z3 = _add(X3, Y3, Z3, pApB_X, pApB_Y, pApB_Z, p) - if (not X3 and not Y3) or not Z3: + if not Z3: return INFINITY return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) @@ -1155,6 +1156,8 @@ def __eq__(self, other): Note: only points that lay on the same curve can be equal. """ + if other is INFINITY: + return self.__x is None or self.__y is None if isinstance(other, Point): return ( self.__curve == other.__curve diff --git a/src/ecdsa/test_ellipticcurve.py b/src/ecdsa/test_ellipticcurve.py index db5ac7c4..31f9e0d8 100644 --- a/src/ecdsa/test_ellipticcurve.py +++ b/src/ecdsa/test_ellipticcurve.py @@ -251,6 +251,12 @@ def test_inequality_points_diff_types(self): c = CurveFp(100, -3, 100) self.assertNotEqual(self.g_23, c) + def test_inequality_diff_y(self): + p1 = Point(self.c_23, 6, 4) + p2 = Point(self.c_23, 6, 19) + + self.assertNotEqual(p1, p2) + def test_to_bytes_from_bytes(self): p = Point(self.c_23, 3, 10) diff --git a/src/ecdsa/test_jacobi.py b/src/ecdsa/test_jacobi.py index bb06cce6..50650280 100644 --- a/src/ecdsa/test_jacobi.py +++ b/src/ecdsa/test_jacobi.py @@ -14,7 +14,7 @@ import hypothesis.strategies as st from hypothesis import given, assume, settings, example -from .ellipticcurve import CurveFp, PointJacobi, INFINITY +from .ellipticcurve import CurveFp, PointJacobi, INFINITY, Point from .ecdsa import ( generator_256, curve_256, @@ -92,14 +92,21 @@ def test_double_with_zero_point(self): self.assertIs(pj, INFINITY) def test_double_with_zero_equivalent_point(self): - pj = PointJacobi(curve_256, 0, curve_256.p(), 1) + pj = PointJacobi(curve_256, 0, 0, 0) 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) + def test_double_with_zero_equivalent_point_non_zero_z_non_zero_y(self): + pj = PointJacobi(curve_256, 0, 1, curve_256.p()) + + pj = pj.double() + + self.assertIs(pj, INFINITY) + + def test_double_with_zero_equivalent_point_non_zero_z(self): + pj = PointJacobi(curve_256, 0, 0, curve_256.p()) pj = pj.double() @@ -113,7 +120,7 @@ def test_compare_with_affine_point(self): self.assertEqual(pa, pj) def test_to_affine_with_zero_point(self): - pj = PointJacobi(curve_256, 0, 0, 1) + pj = PointJacobi(curve_256, 0, 0, 0) pa = pj.to_affine() @@ -144,7 +151,7 @@ def test_add_with_infinity(self): def test_add_zero_point_to_affine(self): pa = PointJacobi.from_affine(generator_256).to_affine() - pj = PointJacobi(curve_256, 0, 0, 1) + pj = PointJacobi(curve_256, 0, 0, 0) s = pj + pa @@ -195,8 +202,35 @@ def test_compare_non_zero_with_infinity(self): self.assertNotEqual(pj, INFINITY) + def test_compare_non_zero_bad_scale_with_infinity(self): + pj = PointJacobi(curve_256, 1, 1, 0) + self.assertEqual(pj, INFINITY) + + def test_eq_x_0_on_curve_with_infinity(self): + c_23 = CurveFp(23, 1, 1) + pj = PointJacobi(c_23, 0, 1, 1) + + self.assertTrue(c_23.contains_point(0, 1)) + + self.assertNotEqual(pj, INFINITY) + + def test_eq_y_0_on_curve_with_infinity(self): + c_23 = CurveFp(23, 1, 1) + pj = PointJacobi(c_23, 4, 0, 1) + + self.assertTrue(c_23.contains_point(4, 0)) + + self.assertNotEqual(pj, INFINITY) + + def test_eq_with_same_x_different_y(self): + c_23 = CurveFp(23, 1, 1) + p_a = PointJacobi(c_23, 0, 22, 1) + p_b = PointJacobi(c_23, 0, 1, 1) + + self.assertNotEqual(p_a, p_b) + def test_compare_zero_point_with_infinity(self): - pj = PointJacobi(curve_256, 0, 0, 1) + pj = PointJacobi(curve_256, 0, 0, 0) self.assertEqual(pj, INFINITY) @@ -579,6 +613,18 @@ def test_mul_add(self): self.assertEqual(ret.to_affine(), w_a + w_b) + def test_mul_add_zero(self): + j_g = PointJacobi.from_affine(generator_256) + + w_a = generator_256 * 255 + w_b = generator_256 * (0 * 0xA8) + + j_b = j_g * 0xA8 + + ret = j_g.mul_add(255, j_b, 0) + + 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) @@ -619,6 +665,26 @@ def test_mul_add_with_doubled_negation_of_itself(self): self.assertEqual(j_g.mul_add(4, dbl_neg, 2), INFINITY) + @given( + st.integers( + min_value=0, max_value=int(generator_112r2.order() - 1) + ), + st.integers( + min_value=0, max_value=int(generator_112r2.order() - 1) + ), + st.integers( + min_value=0, max_value=int(generator_112r2.order() - 1) + ) + ) + @example(693, 2, 3293) # values that will hit all the conditions for NAF + def test_mul_add_random(self, mul1, mul2, mul3): + p_a = PointJacobi.from_affine(generator_112r2) + p_b = generator_112r2 * mul2 + + res = p_a.mul_add(mul1, p_b, mul3) + + self.assertEqual(res, p_a * mul1 + p_b * mul3) + def test_equality(self): pj1 = PointJacobi(curve=CurveFp(23, 1, 1, 1), x=2, y=3, z=1, order=1) pj2 = PointJacobi(curve=CurveFp(23, 1, 1, 1), x=2, y=3, z=1, order=1) @@ -651,6 +717,13 @@ def test_double_to_infinity(self): self.assertEqual(p3, INFINITY) self.assertIs(p3, INFINITY) + def test_double_to_x_0(self): + c_23_2 = CurveFp(23, 1, 2) + p = PointJacobi(c_23_2, 9, 2, 1) + p2 = p.double() + + self.assertEqual((p2.x(), p2.y()), (0, 18)) + def test_mul_to_infinity(self): c_23 = CurveFp(23, 1, 1) p = PointJacobi(c_23, 11, 20, 1) @@ -671,6 +744,41 @@ def test_add_to_infinity(self): self.assertEqual(p3, INFINITY) self.assertIs(p3, INFINITY) + def test_mul_to_x_0(self): + c_23 = CurveFp(23, 1, 1) + p = PointJacobi(c_23, 9, 7, 1) + + p2 = p * 13 + self.assertEqual((p2.x(), p2.y()), (0, 22)) + + def test_mul_to_y_0(self): + c_23 = CurveFp(23, 1, 1) + p = PointJacobi(c_23, 9, 7, 1) + + p2 = p * 14 + self.assertEqual((p2.x(), p2.y()), (4, 0)) + + def test_add_to_x_0(self): + c_23 = CurveFp(23, 1, 1) + p = PointJacobi(c_23, 9, 7, 1) + + p2 = p * 12 + p + self.assertEqual((p2.x(), p2.y()), (0, 22)) + + def test_add_to_y_0(self): + c_23 = CurveFp(23, 1, 1) + p = PointJacobi(c_23, 9, 7, 1) + + p2 = p * 13 + p + self.assertEqual((p2.x(), p2.y()), (4, 0)) + + def test_add_diff_z_to_infinity(self): + c_23 = CurveFp(23, 1, 1) + p = PointJacobi(c_23, 9, 7, 1) + + c = p * 20 + p * 8 + self.assertIs(c, INFINITY) + 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) @@ -781,3 +889,51 @@ def interrupter(barrier_start, barrier_end, lock_exit): gen._PointJacobi__precompute, generator_112r2._PointJacobi__precompute, ) + + +class TestZeroCurve(unittest.TestCase): + """Tests with curve that has (0, 0) on the curve.""" + def setUp(self): + self.curve = CurveFp(23, 1, 0) + + def test_zero_point_on_curve(self): + self.assertTrue(self.curve.contains_point(0, 0)) + + def test_double_to_0_0_point(self): + p = PointJacobi(self.curve, 1, 18, 1) + + d = p.double() + + self.assertNotEqual(d, INFINITY) + self.assertEqual((0, 0), (d.x(), d.y())) + + def test_double_to_0_0_point_with_non_one_z(self): + z = 2 + p = PointJacobi(self.curve, 1 * z**2, 18 * z**3, z) + + d = p.double() + + self.assertNotEqual(d, INFINITY) + self.assertEqual((0, 0), (d.x(), d.y())) + + def test_mul_to_0_0_point(self): + p = PointJacobi(self.curve, 11, 13, 1) + + d = p * 12 + + self.assertNotEqual(d, INFINITY) + self.assertEqual((0, 0), (d.x(), d.y())) + + def test_double_of_0_0_point(self): + p = PointJacobi(self.curve, 0, 0, 1) + + d = p.double() + + self.assertIs(d, INFINITY) + + def test_compare_to_old_implementation(self): + p = PointJacobi(self.curve, 11, 13, 1) + p_c = Point(self.curve, 11, 13) + + for i in range(24): + self.assertEqual(p * i, p_c * i) From 7426df8ba1a64c34f0e9fae25c72560da0e81861 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 17 Aug 2024 00:24:17 +0200 Subject: [PATCH 090/105] better test coverage for ellipticcurve --- src/ecdsa/test_ellipticcurve.py | 5 +++++ src/ecdsa/test_pyecdsa.py | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/ecdsa/test_ellipticcurve.py b/src/ecdsa/test_ellipticcurve.py index 31f9e0d8..864cf108 100644 --- a/src/ecdsa/test_ellipticcurve.py +++ b/src/ecdsa/test_ellipticcurve.py @@ -83,6 +83,11 @@ def test_inequality_curves(self): c192 = CurveFp(p, -3, b) self.assertNotEqual(self.c_23, c192) + def test_inequality_curves_by_b_only(self): + a = CurveFp(23, 1, 0) + b = CurveFp(23, 1, 1) + self.assertNotEqual(a, b) + def test_usability_in_a_hashed_collection_curves(self): {self.c_23: None} diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index 0a4ed8af..6c0266e8 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -856,6 +856,22 @@ def test_hybrid_decoding_with_blocked_format(self): self.assertIn("Invalid X9.62 encoding", str(exp.exception)) + def test_hybrid_decoding_with_inconsistent_encoding_and_no_validation(self): + sk = SigningKey.from_secret_exponent(123456789) + vk = sk.verifying_key + + enc = vk.to_string("hybrid") + self.assertEqual(enc[:1], b'\x06') + enc = b'\x07' + enc[1:] + + b = VerifyingKey.from_string( + enc, + valid_encodings=("hybrid",), + validate_point=False + ) + + self.assertEqual(vk, b) + def test_compressed_decoding_with_blocked_format(self): enc = ( b"\x02" @@ -898,6 +914,17 @@ def test_decoding_with_inconsistent_hybrid(self): with self.assertRaises(MalformedPointError): VerifyingKey.from_string(b"\x07" + enc) + def test_decoding_with_inconsistent_hybrid_odd_point(self): + sk = SigningKey.from_secret_exponent(123456791) + vk = sk.verifying_key + + enc = vk.to_string("hybrid") + self.assertEqual(enc[:1], b'\x07') + enc = b'\x06' + enc[1:] + + with self.assertRaises(MalformedPointError): + b = VerifyingKey.from_string(enc, valid_encodings=("hybrid",)) + def test_decoding_with_point_not_on_curve(self): enc = ( b"\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3" From de9141cded117fe4b4a8a3c241d5da89cc6d749a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 17 Aug 2024 00:39:06 +0200 Subject: [PATCH 091/105] formatting fixups --- src/ecdsa/test_jacobi.py | 13 ++++--------- src/ecdsa/test_pyecdsa.py | 16 ++++++++-------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/ecdsa/test_jacobi.py b/src/ecdsa/test_jacobi.py index 50650280..f811b922 100644 --- a/src/ecdsa/test_jacobi.py +++ b/src/ecdsa/test_jacobi.py @@ -666,15 +666,9 @@ def test_mul_add_with_doubled_negation_of_itself(self): self.assertEqual(j_g.mul_add(4, dbl_neg, 2), INFINITY) @given( - st.integers( - min_value=0, max_value=int(generator_112r2.order() - 1) - ), - st.integers( - min_value=0, max_value=int(generator_112r2.order() - 1) - ), - st.integers( - min_value=0, max_value=int(generator_112r2.order() - 1) - ) + st.integers(min_value=0, max_value=int(generator_112r2.order() - 1)), + st.integers(min_value=0, max_value=int(generator_112r2.order() - 1)), + st.integers(min_value=0, max_value=int(generator_112r2.order() - 1)), ) @example(693, 2, 3293) # values that will hit all the conditions for NAF def test_mul_add_random(self, mul1, mul2, mul3): @@ -893,6 +887,7 @@ def interrupter(barrier_start, barrier_end, lock_exit): class TestZeroCurve(unittest.TestCase): """Tests with curve that has (0, 0) on the curve.""" + def setUp(self): self.curve = CurveFp(23, 1, 0) diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index 6c0266e8..41878959 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -856,18 +856,18 @@ def test_hybrid_decoding_with_blocked_format(self): self.assertIn("Invalid X9.62 encoding", str(exp.exception)) - def test_hybrid_decoding_with_inconsistent_encoding_and_no_validation(self): + def test_hybrid_decoding_with_inconsistent_encoding_and_no_validation( + self, + ): sk = SigningKey.from_secret_exponent(123456789) vk = sk.verifying_key enc = vk.to_string("hybrid") - self.assertEqual(enc[:1], b'\x06') - enc = b'\x07' + enc[1:] + self.assertEqual(enc[:1], b"\x06") + enc = b"\x07" + enc[1:] b = VerifyingKey.from_string( - enc, - valid_encodings=("hybrid",), - validate_point=False + enc, valid_encodings=("hybrid",), validate_point=False ) self.assertEqual(vk, b) @@ -919,8 +919,8 @@ def test_decoding_with_inconsistent_hybrid_odd_point(self): vk = sk.verifying_key enc = vk.to_string("hybrid") - self.assertEqual(enc[:1], b'\x07') - enc = b'\x06' + enc[1:] + self.assertEqual(enc[:1], b"\x07") + enc = b"\x06" + enc[1:] with self.assertRaises(MalformedPointError): b = VerifyingKey.from_string(enc, valid_encodings=("hybrid",)) From 3cb9ed8fe2ab094624a78d932249912097fba26e Mon Sep 17 00:00:00 2001 From: Alicja Kario Date: Fri, 13 Sep 2024 19:36:11 +0200 Subject: [PATCH 092/105] fix warning of int_to_string() and string_to_int() --- src/ecdsa/ecdsa.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ecdsa/ecdsa.py b/src/ecdsa/ecdsa.py index 1c2e25c5..f7109659 100644 --- a/src/ecdsa/ecdsa.py +++ b/src/ecdsa/ecdsa.py @@ -275,7 +275,7 @@ def int_to_string(x): # pragma: no cover # deprecated in 0.19 warnings.warn( "Function is unused in library code. If you use this code, " - "change to util.string_to_number.", + "change to util.number_to_string.", DeprecationWarning, ) assert x >= 0 @@ -296,7 +296,7 @@ def string_to_int(s): # pragma: no cover # deprecated in 0.19 warnings.warn( "Function is unused in library code. If you use this code, " - "change to util.number_to_string.", + "change to util.string_to_number.", DeprecationWarning, ) result = 0 From 47677dc4626b34147cda58e886af1c1973fbac28 Mon Sep 17 00:00:00 2001 From: Alicja Kario Date: Thu, 16 Jan 2025 13:25:23 +0100 Subject: [PATCH 093/105] update CI to make 3.7 continue to work --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f946e5f..c212199b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: python-version: 3.6 tox-env: py36 - name: py3.7 - os: ubuntu-latest + os: ubuntu-22.04 python-version: 3.7 tox-env: py37 - name: py3.8 @@ -88,7 +88,7 @@ jobs: python-version: pypy-2.7 tox-env: pypy - name: pypy3 - os: ubuntu-latest + os: ubuntu-22.04 python-version: pypy-3.7 tox-env: pypy3 # special configurations From 27233b2de8e295c04a1fb3766cc0302dae1b273c Mon Sep 17 00:00:00 2001 From: Alicja Kario Date: Thu, 16 Jan 2025 13:29:10 +0100 Subject: [PATCH 094/105] don't use beta for py3.12, it released... --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c212199b..ae773e83 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,7 +81,7 @@ jobs: tox-env: py311 - name: py3.12 os: ubuntu-latest - python-version: '3.12.0-beta.1' + python-version: '3.12' tox-env: py312 - name: pypy os: ubuntu-latest From ef96a1366ced61e97898ac892e253503ecfb6ed4 Mon Sep 17 00:00:00 2001 From: Alicja Kario Date: Thu, 16 Jan 2025 13:32:03 +0100 Subject: [PATCH 095/105] add py3.13 to testing --- .github/workflows/ci.yml | 4 ++++ setup.py | 1 + tox.ini | 20 +++++++++++++------- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae773e83..def4e430 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,6 +83,10 @@ jobs: os: ubuntu-latest python-version: '3.12' tox-env: py312 + - name: py3.13 + os: ubuntu-latest + python-version: '3.13' + tox-env: py313 - name: pypy os: ubuntu-latest python-version: pypy-2.7 diff --git a/setup.py b/setup.py index 341fe734..618f5e6d 100755 --- a/setup.py +++ b/setup.py @@ -42,6 +42,7 @@ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ], install_requires=["six>=1.9.0"], extras_require={"gmpy2": "gmpy2", "gmpy": "gmpy"}, diff --git a/tox.ini b/tox.ini index ea01c4b4..19c03175 100644 --- a/tox.ini +++ b/tox.ini @@ -1,17 +1,17 @@ [tox] -envlist = py26, py27, py35, py36, py37, py38, py39, py310, py311, py312, py, pypy, pypy3, gmpy2py27, gmpy2py39, gmpy2py310, gmpypy27, gmpypy39, gmpypy310, codechecks +envlist = py26, py27, py35, py36, py37, py38, py39, py310, py311, py312, py313, py, pypy, pypy3, gmpy2py27, gmpy2py39, gmpy2py310, gmpypy27, gmpypy39, gmpypy310, codechecks [testenv] deps = py{26}: unittest2 py{26}: hypothesis<3 - py{26,27,35,36,37,38,39,310,311,312,py,py3}: pytest - py{27,35,36,37,38,39,310,311,312,py,py3}: hypothesis - gmpy2py{27,39,310,311,312}: gmpy2 - gmpypy{27,39,310,311,312}: gmpy - gmpy{2py27,2py39,2py310,2py311,2py312,py27,py39,py310,py311,py312}: pytest - gmpy{2py27,2py39,2py310,2py311,2py312,py27,py39,py310,py311,py312}: hypothesis + py{26,27,35,36,37,38,39,310,311,312,313,py,py3}: pytest + py{27,35,36,37,38,39,310,311,312,313,py,py3}: hypothesis + gmpy2py{27,39,310,311,312,313}: gmpy2 + gmpypy{27,39,310,311,312,313}: gmpy + gmpy{2py27,2py39,2py310,2py311,2py312,2py313,py27,py39,py310,py311,py312,py313}: pytest + gmpy{2py27,2py39,2py310,2py311,2py312,2py313,py27,py39,py310,py311,py312,py313}: hypothesis # six==1.9.0 comes from setup.py install_requires py27_old_six: six==1.9.0 py27_old_six: pytest @@ -53,6 +53,9 @@ basepython=python3.11 [testenv:gmpypy312] basepython=python3.12 +[testenv:gmpypy313] +basepython=python3.13 + [testenv:gmpy2py27] basepython=python2.7 @@ -68,6 +71,9 @@ basepython=python3.11 [testenv:gmpy2py312] basepython=python3.12 +[testenv:gmpy2py313] +basepython=python3.13 + [testenv:instrumental] basepython = python2.7 deps = From 098198aabf235c26195ab79b49e79b50dfaeb5c6 Mon Sep 17 00:00:00 2001 From: Alicja Kario Date: Thu, 20 Feb 2025 17:36:35 +0100 Subject: [PATCH 096/105] update to new upload-artifact version --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index def4e430..b1d4a1f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -324,7 +324,7 @@ jobs: 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 + uses: actions/upload-artifact@v4 with: name: mutation-PR-coverage-report path: cosmic-ray.html @@ -665,7 +665,7 @@ jobs: run: | cr-html session.sqlite > cosmic-ray.html - name: Archive mutation testing results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: mutation-coverage-report path: cosmic-ray.html From e26a80d90273e95c7e76e0ffa5efd2b40b717fa5 Mon Sep 17 00:00:00 2001 From: Alicja Kario Date: Tue, 25 Feb 2025 13:25:31 +0100 Subject: [PATCH 097/105] fix doc building with new sphinx --- docs/source/conf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 0581c1c9..d86ea492 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -62,7 +62,9 @@ html_static_path = ["_static"] # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {"https://docs.python.org/": None} +intersphinx_mapping = { + "python": ("https://docs.python.org/", None), +} autodoc_default_options = { "undoc-members": True, From 8e3f653e66150b9c787d6ca4fb015d05dd164e51 Mon Sep 17 00:00:00 2001 From: Alicja Kario Date: Thu, 6 Mar 2025 18:27:37 +0100 Subject: [PATCH 098/105] add support for parsing implicit DER tags --- src/ecdsa/der.py | 43 +++++++++++++++++++++++++++ src/ecdsa/test_der.py | 68 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/src/ecdsa/der.py b/src/ecdsa/der.py index b2914859..be61b867 100644 --- a/src/ecdsa/der.py +++ b/src/ecdsa/der.py @@ -142,6 +142,49 @@ def remove_constructed(string): return tag, body, rest +def remove_implicit(string, exp_class="context-specific"): + """ + Removes an IMPLICIT tagged value from ``string`` following :term:`DER`. + + :param bytes string: a byte string that can have one or more + DER elements. + :param str exp_class: the expected tag class of the implicitly + encoded value. Possible values are: "context-specific", "application", + and "private". + :return: a tuple with first value being the tag without indicator bits, + second being the raw bytes of the value and the third one being + remaining bytes (or an empty string if there are none) + :rtype: tuple(int,bytes,bytes) + """ + if exp_class not in ("context-specific", "application", "private"): + raise ValueError("invalid `exp_class` value") + if exp_class == "application": + tag_class = 0b01000000 + elif exp_class == "context-specific": + tag_class = 0b10000000 + else: + assert exp_class == "private" + tag_class = 0b11000000 + tag_mask = 0b11000000 + + s0 = str_idx_as_int(string, 0) + + if (s0 & tag_mask) != tag_class: + raise UnexpectedDER( + "wanted class {0}, got 0x{1:02x} tag".format(exp_class, s0) + ) + if s0 & 0b00100000 != 0: + raise UnexpectedDER( + "wanted type primitive, got 0x{0:02x} tag".format(s0) + ) + + tag = s0 & 0x1F + length, llen = read_length(string[1:]) + body = string[1 + llen : 1 + llen + length] + rest = string[1 + llen + length :] + return tag, body, rest + + def remove_sequence(string): if not string: raise UnexpectedDER("Empty string does not encode a sequence") diff --git a/src/ecdsa/test_der.py b/src/ecdsa/test_der.py index 0c2dc4d1..87eae1e8 100644 --- a/src/ecdsa/test_der.py +++ b/src/ecdsa/test_der.py @@ -22,6 +22,7 @@ remove_object, encode_oid, remove_constructed, + remove_implicit, remove_octet_string, remove_sequence, ) @@ -396,6 +397,73 @@ def test_with_malformed_tag(self): self.assertIn("constructed tag", str(e.exception)) +class TestRemoveImplicit(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.exp_tag = 6 + cls.exp_data = b"\x0a\x0b" + # data with application tag class + cls.data_application = b"\x46\x02\x0a\x0b" + # data with context-specific tag class + cls.data_context_specific = b"\x86\x02\x0a\x0b" + # data with private tag class + cls.data_private = b"\xc6\x02\x0a\x0b" + + def test_simple(self): + tag, body, rest = remove_implicit(self.data_context_specific) + + self.assertEqual(tag, self.exp_tag) + self.assertEqual(body, self.exp_data) + self.assertEqual(rest, b"") + + def test_wrong_expected_class(self): + with self.assertRaises(ValueError) as e: + remove_implicit(self.data_context_specific, "foobar") + + self.assertIn("invalid `exp_class` value", str(e.exception)) + + def test_with_wrong_class(self): + with self.assertRaises(UnexpectedDER) as e: + remove_implicit(self.data_application) + + self.assertIn( + "wanted class context-specific, got 0x46 tag", str(e.exception) + ) + + def test_with_application_class(self): + tag, body, rest = remove_implicit(self.data_application, "application") + + self.assertEqual(tag, self.exp_tag) + self.assertEqual(body, self.exp_data) + self.assertEqual(rest, b"") + + def test_with_private_class(self): + tag, body, rest = remove_implicit(self.data_private, "private") + + self.assertEqual(tag, self.exp_tag) + self.assertEqual(body, self.exp_data) + self.assertEqual(rest, b"") + + def test_with_data_following(self): + extra_data = b"\x00\x01" + + tag, body, rest = remove_implicit( + self.data_context_specific + extra_data + ) + + self.assertEqual(tag, self.exp_tag) + self.assertEqual(body, self.exp_data) + self.assertEqual(rest, extra_data) + + def test_with_constructed(self): + data = b"\xa6\x02\x0a\x0b" + + with self.assertRaises(UnexpectedDER) as e: + remove_implicit(data) + + self.assertIn("wanted type primitive, got 0xa6 tag", str(e.exception)) + + class TestRemoveOctetString(unittest.TestCase): def test_simple(self): data = b"\x04\x03\xaa\xbb\xcc" From dba9f8096268f7e97ff1f7a21c910fff4d764e6d Mon Sep 17 00:00:00 2001 From: Alicja Kario Date: Thu, 6 Mar 2025 18:55:31 +0100 Subject: [PATCH 099/105] add support for encoding --- src/ecdsa/der.py | 26 ++++++++++++++++++++ src/ecdsa/test_der.py | 56 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/src/ecdsa/der.py b/src/ecdsa/der.py index be61b867..7a06b681 100644 --- a/src/ecdsa/der.py +++ b/src/ecdsa/der.py @@ -16,6 +16,32 @@ def encode_constructed(tag, value): return int2byte(0xA0 + tag) + encode_length(len(value)) + value +def encode_implicit(tag, value, cls="context-specific"): + """ + Encode and IMPLICIT value using :term:`DER`. + + :param int tag: the tag value to encode, must be between 0 an 31 inclusive + :param bytes value: the data to encode + :param str cls: the class of the tag to encode: "application", + "context-specific", or "private" + :rtype: bytes + """ + if cls not in ("application", "context-specific", "private"): + raise ValueError("invalid tag class") + if tag > 31: + raise ValueError("Long tags not supported") + + if cls == "application": + tag_class = 0b01000000 + elif cls == "context-specific": + tag_class = 0b10000000 + else: + assert cls == "private" + tag_class = 0b11000000 + + return int2byte(tag_class + tag) + encode_length(len(value)) + value + + def encode_integer(r): assert r >= 0 # can't support negative numbers yet h = ("%x" % r).encode() diff --git a/src/ecdsa/test_der.py b/src/ecdsa/test_der.py index 87eae1e8..b0955431 100644 --- a/src/ecdsa/test_der.py +++ b/src/ecdsa/test_der.py @@ -25,6 +25,7 @@ remove_implicit, remove_octet_string, remove_sequence, + encode_implicit, ) @@ -463,6 +464,61 @@ def test_with_constructed(self): self.assertIn("wanted type primitive, got 0xa6 tag", str(e.exception)) + def test_encode_decode(self): + data = b"some longish string" + + tag, body, rest = remove_implicit( + encode_implicit(6, data, "application"), "application" + ) + + self.assertEqual(tag, 6) + self.assertEqual(body, data) + self.assertEqual(rest, b"") + + +class TestEncodeImplicit(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.data = b"\x0a\x0b" + # data with application tag class + cls.data_application = b"\x46\x02\x0a\x0b" + # data with context-specific tag class + cls.data_context_specific = b"\x86\x02\x0a\x0b" + # data with private tag class + cls.data_private = b"\xc6\x02\x0a\x0b" + + def test_encode_with_default_class(self): + ret = encode_implicit(6, self.data) + + self.assertEqual(ret, self.data_context_specific) + + def test_encode_with_application_class(self): + ret = encode_implicit(6, self.data, "application") + + self.assertEqual(ret, self.data_application) + + def test_encode_with_context_specific_class(self): + ret = encode_implicit(6, self.data, "context-specific") + + self.assertEqual(ret, self.data_context_specific) + + def test_encode_with_private_class(self): + ret = encode_implicit(6, self.data, "private") + + self.assertEqual(ret, self.data_private) + + def test_encode_with_invalid_class(self): + with self.assertRaises(ValueError) as e: + encode_implicit(6, self.data, "foobar") + + self.assertIn("invalid tag class", str(e.exception)) + + def test_encode_with_too_large_tag(self): + with self.assertRaises(ValueError) as e: + encode_implicit(32, self.data) + + self.assertIn("Long tags not supported", str(e.exception)) + class TestRemoveOctetString(unittest.TestCase): def test_simple(self): From ef75fea937fee20561ba0d81a4d0a9bb371d20e7 Mon Sep 17 00:00:00 2001 From: Alicja Kario Date: Thu, 6 Mar 2025 19:09:54 +0100 Subject: [PATCH 100/105] use the new badge URL for the build status --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 980abc1e..0bf02bde 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Pure-Python ECDSA and ECDH -[![Build Status](https://github.com/tlsfuzzer/python-ecdsa/workflows/GitHub%20CI/badge.svg?branch=master)](https://github.com/tlsfuzzer/python-ecdsa/actions?query=workflow%3A%22GitHub+CI%22+branch%3Amaster) +[![GitHub CI](https://github.com/tlsfuzzer/python-ecdsa/actions/workflows/ci.yml/badge.svg)](https://github.com/tlsfuzzer/python-ecdsa/actions/workflows/ci.yml) [![Documentation Status](https://readthedocs.org/projects/ecdsa/badge/?version=latest)](https://ecdsa.readthedocs.io/en/latest/?badge=latest) [![Coverage Status](https://coveralls.io/repos/github/tlsfuzzer/python-ecdsa/badge.svg?branch=master)](https://coveralls.io/github/tlsfuzzer/python-ecdsa?branch=master) ![condition coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/tomato42/9b6ca1f3410207fbeca785a178781651/raw/python-ecdsa-condition-coverage.json) From b6d43c60e309bccd681eb1baab502f817ff226b6 Mon Sep 17 00:00:00 2001 From: Alicja Kario Date: Thu, 13 Mar 2025 11:09:08 +0100 Subject: [PATCH 101/105] use integer division for canonicalization of signatures fixes #353 --- src/ecdsa/test_pyecdsa.py | 17 +++++++++++++++++ src/ecdsa/util.py | 26 ++++++++++++++++++++------ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index 41878959..799e9b74 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -520,6 +520,23 @@ def test_sigencode_der_canonize(self): self.assertEqual(r, new_r) self.assertEqual(order - s, new_s) + def test_sigencode_der_canonize_with_close_to_half_order(self): + r = 13 + order = SECP112r1.order + s = order // 2 + 1 + + regular_encode = sigencode_der(r, s, order) + canonical_encode = sigencode_der_canonize(r, s, order) + + self.assertNotEqual(regular_encode, canonical_encode) + + new_r, new_s = sigdecode_der( + sigencode_der_canonize(r, s, order), order + ) + + self.assertEqual(r, new_r) + self.assertEqual(order - s, new_s) + def test_sig_decode_strings_with_invalid_count(self): with self.assertRaises(MalformedSignature): sigdecode_strings([b"one", b"two", b"three"], 0xFF) diff --git a/src/ecdsa/util.py b/src/ecdsa/util.py index 639bc0c2..1aff5bf5 100644 --- a/src/ecdsa/util.py +++ b/src/ecdsa/util.py @@ -304,6 +304,23 @@ def sigencode_der(r, s, order): return der.encode_sequence(der.encode_integer(r), der.encode_integer(s)) +def _canonize(s, order): + """ + Internal function for ensuring that the ``s`` value of a signature is in + the "canonical" format. + + :param int s: the second parameter of ECDSA signature + :param int order: the order of the curve over which the signatures was + computed + + :return: canonical value of s + :rtype: int + """ + if s > order // 2: + s = order - s + return s + + def sigencode_strings_canonize(r, s, order): """ Encode the signature to a pair of strings in a tuple @@ -326,8 +343,7 @@ def sigencode_strings_canonize(r, s, order): :return: raw encoding of ECDSA signature :rtype: tuple(bytes, bytes) """ - if s > order / 2: - s = order - s + s = _canonize(s, order) return sigencode_strings(r, s, order) @@ -350,8 +366,7 @@ def sigencode_string_canonize(r, s, order): :return: raw encoding of ECDSA signature :rtype: bytes """ - if s > order / 2: - s = order - s + s = _canonize(s, order) return sigencode_string(r, s, order) @@ -381,8 +396,7 @@ def sigencode_der_canonize(r, s, order): :return: DER encoding of ECDSA signature :rtype: bytes """ - if s > order / 2: - s = order - s + s = _canonize(s, order) return sigencode_der(r, s, order) From 658ddc81bbd20e0197a59c9c0fa7dcc5b17d7d06 Mon Sep 17 00:00:00 2001 From: Alicja Kario Date: Thu, 13 Mar 2025 11:45:55 +0100 Subject: [PATCH 102/105] add release notes for 0.19.1 release --- NEWS | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/NEWS b/NEWS index 5f27c168..0d066f4b 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,25 @@ +* Release 0.19.1 (13 Mar 2025) + +New API: +* ``der.remove_implitic`` and ``der.encode_implicit`` for decoding and + encoding DER IMPLICIT values with custom tag values and arbitrary + classes + +Bug fixes: +* Minor fixes around arithmetic with curves that have non-prime order + (useful for experimentation, not practical deployments) +* Fix arithmetic to work with curves that have (0, 0) on the curve +* Fix canonicalization of signatures when ``s`` is just slightly + above half of curve order + +Maintenance: +* Dropped official support for Python 3.5 (again, issues with CI, support + for Python 2.6 and Python 2.7 is unchanged) +* Officialy support Python 3.12 and 3.13 (add them to CI) +* Removal of few more unnecessary `six.b` literals (Alexandre Detiste) +* Fix typos in warning messages + + * Release 0.19.0 (08 Apr 2024) New API: From c4f0df174ad3ae75e59f4e9f47d4a912f5fe21cf Mon Sep 17 00:00:00 2001 From: Ganna Starovoytova Date: Tue, 2 Sep 2025 13:12:53 +0200 Subject: [PATCH 103/105] chore: change to ubuntu-22 since u-20 is deprecated -ensure working pip on python2.7 -adjust python 3.6 to run on centos:8, since ubuntu22 image does not support python3.6 -git version on centos:6 was too old and was not working properly, changing it to manually download and build newer git version --- .github/workflows/ci.yml | 80 ++++++++++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b1d4a1f6..61c8d941 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,31 +24,32 @@ jobs: python-version: "3.10" tox-env: py310 - name: py2.7 - os: ubuntu-20.04 + os: ubuntu-22.04 python-version: 2.7 tox-env: py27 - name: py2.7 with old gmpy - os: ubuntu-20.04 + os: ubuntu-22.04 python-version: 2.7 tox-env: py27_old_gmpy - name: py2.7 with old gmpy2 - os: ubuntu-20.04 + os: ubuntu-22.04 python-version: 2.7 tox-env: py27_old_gmpy2 - name: py2.7 with old six - os: ubuntu-20.04 + os: ubuntu-22.04 python-version: 2.7 tox-env: py27_old_six - name: py2.7 with gmpy - os: ubuntu-20.04 + os: ubuntu-22.04 python-version: 2.7 tox-env: gmpypy27 - name: py2.7 with gmpy2 - os: ubuntu-20.04 + os: ubuntu-22.04 python-version: 2.7 tox-env: gmpy2py27 - name: py3.6 - os: ubuntu-20.04 + os: ubuntu-latest + container: centos:8 python-version: 3.6 tox-env: py36 - name: py3.7 @@ -97,7 +98,7 @@ jobs: tox-env: pypy3 # special configurations - name: py2.7 with instrumental - os: ubuntu-20.04 + os: ubuntu-22.04 python-version: 2.7 opt-deps: ['instrumental'] - name: code checks @@ -118,7 +119,7 @@ jobs: if: ${{ matrix.container }} with: fetch-depth: 50 - - name: Ensure dependencies on CentOS + - name: Ensure dependencies on CentOS:6 if: ${{ matrix.container == 'centos:6' }} run: | ls /etc/yum.repos.d/ @@ -147,13 +148,55 @@ jobs: echo installing yum clean all yum repolist all - yum install -y git make python curl gcc libffi-devel python-devel glibc-devel openssl-devel wget + yum install -y make python curl gcc libffi-devel python-devel glibc-devel openssl-devel wget curl-config curl-devel expat-devel gettext-devel zlib-devel perl-ExtUtils-MakeMaker + cd /usr/src + wget https://www.kernel.org/pub/software/scm/git/git-2.31.0.tar.gz + tar xzf git-2.31.0.tar.gz + cd git-2.31.0 + make prefix=/usr/local/git all + make prefix=/usr/local/git install + ln -fs /usr/local/git/bin/git /usr/bin/git + - name: Ensere dependenceis on CentOS:8 + if: ${{ matrix.container == 'centos:8' }} + run: | + ls /etc/yum.repos.d/ + cat /etc/yum.repos.d/CentOS-Linux-BaseOS.repo + cat /etc/yum.repos.d/CentOS-Linux-AppStream.repo + cat /etc/yum.repos.d/CentOS-Linux-Extras.repo + rm /etc/yum.repos.d/CentOS-Linux-BaseOS.repo + rm /etc/yum.repos.d/CentOS-Linux-AppStream.repo + rm /etc/yum.repos.d/CentOS-Linux-Extras.repo + cat > /etc/yum.repos.d/CentOS-Linux-BaseOS.repo < /etc/yum.repos.d/CentOS-Linux-AppStream.repo < /etc/yum.repos.d/CentOS-Linux-Extras.repo < Date: Sun, 1 Mar 2026 09:18:21 +0200 Subject: [PATCH 104/105] der: reject truncated lengths in octet/implicit/constructed --- src/ecdsa/der.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ecdsa/der.py b/src/ecdsa/der.py index 7a06b681..5d35d698 100644 --- a/src/ecdsa/der.py +++ b/src/ecdsa/der.py @@ -163,6 +163,8 @@ def remove_constructed(string): ) tag = s0 & 0x1F length, llen = read_length(string[1:]) + if length > len(string) - 1 - llen: + raise UnexpectedDER("Length longer than the provided buffer") body = string[1 + llen : 1 + llen + length] rest = string[1 + llen + length :] return tag, body, rest @@ -206,6 +208,8 @@ def remove_implicit(string, exp_class="context-specific"): tag = s0 & 0x1F length, llen = read_length(string[1:]) + if length > len(string) - 1 - llen: + raise UnexpectedDER("Length longer than the provided buffer") body = string[1 + llen : 1 + llen + length] rest = string[1 + llen + length :] return tag, body, rest @@ -229,6 +233,8 @@ def remove_octet_string(string): n = str_idx_as_int(string, 0) raise UnexpectedDER("wanted type 'octetstring' (0x04), got 0x%02x" % n) length, llen = read_length(string[1:]) + if length > len(string) - 1 - llen: + raise UnexpectedDER("Length longer than the provided buffer") body = string[1 + llen : 1 + llen + length] rest = string[1 + llen + length :] return body, rest From 9c046ee7f61649a8a43d3f6f9c64f13e76e148db Mon Sep 17 00:00:00 2001 From: 0xmrma Date: Thu, 5 Mar 2026 18:44:50 +0200 Subject: [PATCH 105/105] tests: reject truncated DER lengths --- src/ecdsa/test_der.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/ecdsa/test_der.py b/src/ecdsa/test_der.py index b0955431..33a2dca6 100644 --- a/src/ecdsa/test_der.py +++ b/src/ecdsa/test_der.py @@ -600,3 +600,23 @@ def test_oids(ids): decoded_oid, rest = remove_object(encoded_oid) assert rest == b"" assert decoded_oid == ids + +def test_remove_octet_string_rejects_truncated_length(): + # OCTET STRING: declared length 4096, but only 3 bytes present + bad = b"\x04\x82\x10\x00" + b"ABC" + with pytest.raises(UnexpectedDER, match="Length longer than the provided buffer"): + remove_octet_string(bad) + +def test_remove_constructed_rejects_truncated_length(): + # Constructed tag: 0xA0 (context-specific constructed, tag=0) + # declared length 4096, but only 3 bytes present + bad = b"\xA0\x82\x10\x00" + b"ABC" + with pytest.raises(UnexpectedDER, match="Length longer than the provided buffer"): + remove_constructed(bad) + +def test_remove_implicit_rejects_truncated_length(): + # IMPLICIT primitive context-specific tag 0: 0x80 + # declared length 4096, but only 3 bytes present + bad = b"\x80\x82\x10\x00" + b"ABC" + with pytest.raises(UnexpectedDER, match="Length longer than the provided buffer"): + remove_implicit(bad)