Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-ecdsa for openSUSE:Factory checked in at 2025-04-03 16:51:03 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-ecdsa (Old) and /work/SRC/openSUSE:Factory/.python-ecdsa.new.1907 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-ecdsa" Thu Apr 3 16:51:03 2025 rev:18 rq:1266860 version:0.19.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-ecdsa/python-ecdsa.changes 2024-04-14 12:24:36.333531153 +0200 +++ /work/SRC/openSUSE:Factory/.python-ecdsa.new.1907/python-ecdsa.changes 2025-04-03 16:52:22.605191428 +0200 @@ -1,0 +2,18 @@ +Thu Apr 3 06:28:02 UTC 2025 - John Paul Adrian Glaubitz <adrian.glaub...@suse.com> + +- Update to 0.19.1 + * ``der.remove_implitic`` and ``der.encode_implicit`` for decoding and + encoding DER IMPLICIT values with custom tag values and arbitrary + classes + * 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 + * 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 + +------------------------------------------------------------------- Old: ---- ecdsa-0.19.0.tar.gz New: ---- ecdsa-0.19.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-ecdsa.spec ++++++ --- /var/tmp/diff_new_pack.m8D7Ne/_old 2025-04-03 16:52:23.061210641 +0200 +++ /var/tmp/diff_new_pack.m8D7Ne/_new 2025-04-03 16:52:23.061210641 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-ecdsa # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2025 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-ecdsa -Version: 0.19.0 +Version: 0.19.1 Release: 0 Summary: ECDSA cryptographic signature library (pure python) License: MIT ++++++ ecdsa-0.19.0.tar.gz -> ecdsa-0.19.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ecdsa-0.19.0/.github/workflows/ci.yml new/ecdsa-0.19.1/.github/workflows/ci.yml --- old/ecdsa-0.19.0/.github/workflows/ci.yml 2024-04-08 21:00:14.000000000 +0200 +++ new/ecdsa-0.19.1/.github/workflows/ci.yml 2025-02-25 13:23:23.000000000 +0100 @@ -47,16 +47,12 @@ 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 tox-env: py36 - name: py3.7 - os: ubuntu-latest + os: ubuntu-22.04 python-version: 3.7 tox-env: py37 - name: py3.8 @@ -85,14 +81,18 @@ tox-env: py311 - name: py3.12 os: ubuntu-latest - python-version: '3.12.0-beta.1' + 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 tox-env: pypy - name: pypy3 - os: ubuntu-latest + os: ubuntu-22.04 python-version: pypy-3.7 tox-env: pypy3 # special configurations @@ -268,8 +268,7 @@ - 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 cosmic-ray pip install pytest-timeout - name: Display installed python package versions run: pip list @@ -306,7 +305,9 @@ 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 @@ -323,7 +324,7 @@ 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 @@ -418,8 +419,7 @@ 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 cosmic-ray pip install pytest-timeout - name: Install dependencies run: | @@ -495,8 +495,7 @@ - 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 cosmic-ray pip install pytest-timeout - name: Run mutation testing run: | @@ -511,7 +510,7 @@ 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 @@ 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 cosmic-ray pip install pytest-timeout - name: Install dependencies run: | @@ -667,7 +665,7 @@ 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ecdsa-0.19.0/NEWS new/ecdsa-0.19.1/NEWS --- old/ecdsa-0.19.0/NEWS 2024-04-08 21:00:14.000000000 +0200 +++ new/ecdsa-0.19.1/NEWS 2025-03-13 12:48:36.000000000 +0100 @@ -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: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ecdsa-0.19.0/PKG-INFO new/ecdsa-0.19.1/PKG-INFO --- old/ecdsa-0.19.0/PKG-INFO 2024-04-08 21:00:45.803864700 +0200 +++ new/ecdsa-0.19.1/PKG-INFO 2025-03-13 12:49:22.201222000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: ecdsa -Version: 0.19.0 +Version: 0.19.1 Summary: ECDSA cryptographic signature library (pure python) Home-page: http://github.com/tlsfuzzer/python-ecdsa Author: Brian Warner @@ -11,7 +11,6 @@ Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 @@ -19,15 +18,19 @@ Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 -Requires-Python: >=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* +Classifier: Programming Language :: Python :: 3.13 +Requires-Python: >=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.* Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: six>=1.9.0 Provides-Extra: gmpy2 +Requires-Dist: gmpy2; extra == "gmpy2" Provides-Extra: gmpy -License-File: LICENSE +Requires-Dist: gmpy; extra == "gmpy" # Pure-Python ECDSA and ECDH -[](https://github.com/tlsfuzzer/python-ecdsa/actions?query=workflow%3A%22GitHub+CI%22+branch%3Amaster) +[](https://github.com/tlsfuzzer/python-ecdsa/actions/workflows/ci.yml) [](https://ecdsa.readthedocs.io/en/latest/?badge=latest) [](https://coveralls.io/github/tlsfuzzer/python-ecdsa?branch=master)  @@ -72,7 +75,7 @@ ## 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ecdsa-0.19.0/README.md new/ecdsa-0.19.1/README.md --- old/ecdsa-0.19.0/README.md 2024-01-02 19:13:16.000000000 +0100 +++ new/ecdsa-0.19.1/README.md 2025-03-13 11:02:27.000000000 +0100 @@ -1,6 +1,6 @@ # Pure-Python ECDSA and ECDH -[](https://github.com/tlsfuzzer/python-ecdsa/actions?query=workflow%3A%22GitHub+CI%22+branch%3Amaster) +[](https://github.com/tlsfuzzer/python-ecdsa/actions/workflows/ci.yml) [](https://ecdsa.readthedocs.io/en/latest/?badge=latest) [](https://coveralls.io/github/tlsfuzzer/python-ecdsa?branch=master)  @@ -45,7 +45,7 @@ ## 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ecdsa-0.19.0/docs/source/conf.py new/ecdsa-0.19.1/docs/source/conf.py --- old/ecdsa-0.19.0/docs/source/conf.py 2023-12-28 16:44:03.000000000 +0100 +++ new/ecdsa-0.19.1/docs/source/conf.py 2025-03-06 18:27:02.000000000 +0100 @@ -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, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ecdsa-0.19.0/setup.py new/ecdsa-0.19.1/setup.py --- old/ecdsa-0.19.0/setup.py 2023-12-28 16:44:03.000000000 +0100 +++ new/ecdsa-0.19.1/setup.py 2025-02-25 13:23:23.000000000 +0100 @@ -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", @@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ecdsa-0.19.0/src/ecdsa/_version.py new/ecdsa-0.19.1/src/ecdsa/_version.py --- old/ecdsa-0.19.0/src/ecdsa/_version.py 2024-04-08 21:00:45.804864600 +0200 +++ new/ecdsa-0.19.1/src/ecdsa/_version.py 2025-03-13 12:49:22.202222000 +0100 @@ -8,11 +8,11 @@ version_json = ''' { - "date": "2024-04-08T20:59:55+0200", + "date": "2025-03-13T12:48:15+0100", "dirty": false, "error": null, - "full-revisionid": "be70016f8911f79e891a65dcfcb602e5ba866ed3", - "version": "0.19.0" + "full-revisionid": "2a6593d840ad153a16ebdd4f9b772b290494f3e3", + "version": "0.19.1" } ''' # END VERSION_JSON diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ecdsa-0.19.0/src/ecdsa/der.py new/ecdsa-0.19.1/src/ecdsa/der.py --- old/ecdsa-0.19.0/src/ecdsa/der.py 2024-04-08 17:07:29.000000000 +0200 +++ new/ecdsa-0.19.1/src/ecdsa/der.py 2025-03-12 17:53:37.000000000 +0100 @@ -16,6 +16,32 @@ 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() @@ -138,6 +164,49 @@ 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_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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ecdsa-0.19.0/src/ecdsa/ecdsa.py new/ecdsa-0.19.1/src/ecdsa/ecdsa.py --- old/ecdsa-0.19.0/src/ecdsa/ecdsa.py 2024-04-08 17:07:29.000000000 +0200 +++ new/ecdsa-0.19.1/src/ecdsa/ecdsa.py 2024-10-18 19:28:38.000000000 +0200 @@ -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. @@ -275,7 +275,7 @@ # 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 @@ # 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ecdsa-0.19.0/src/ecdsa/ellipticcurve.py new/ecdsa-0.19.1/src/ecdsa/ellipticcurve.py --- old/ecdsa-0.19.0/src/ecdsa/ellipticcurve.py 2024-04-08 17:07:29.000000000 +0200 +++ new/ecdsa-0.19.1/src/ecdsa/ellipticcurve.py 2024-09-13 19:36:14.000000000 +0200 @@ -633,7 +633,7 @@ """ x1, y1, z1 = self.__coords if other is INFINITY: - return 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,11 +723,13 @@ 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 + assert z == 1 return Point(self.__curve, x, y, self.__order) @staticmethod @@ -759,7 +761,7 @@ # 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 @@ -773,13 +775,13 @@ """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 @@ -795,14 +797,14 @@ """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 or not Z3: + if not Z3: return INFINITY return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) @@ -886,10 +888,10 @@ 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 Z1: + return X2 % p, Y2 % p, Z2 % p + if 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 +919,7 @@ X3, Y3, Z3 = self._add(X1, Y1, Z1, X2, Y2, Z2, p) - if not Y3 or not Z3: + if not Z3: return INFINITY return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) @@ -927,7 +929,7 @@ 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: @@ -940,7 +942,7 @@ else: other //= 2 - if not Y3 or not Z3: + if not Z3: return INFINITY return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) @@ -959,7 +961,7 @@ 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 @@ -972,7 +974,7 @@ elif i > 0: X3, Y3, Z3 = _add(X3, Y3, Z3, X2, Y2, 1, p) - if not Y3 or not Z3: + if not Z3: return INFINITY return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) @@ -1001,7 +1003,7 @@ 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, @@ -1025,7 +1027,7 @@ # 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 @@ -1070,7 +1072,7 @@ 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 Z3: return INFINITY return PointJacobi(self.__curve, X3, Y3, Z3, self.__order) @@ -1154,6 +1156,8 @@ 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 @@ -1220,17 +1224,22 @@ # 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)) + # 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 @@ -1247,7 +1256,6 @@ def double(self): """Return a new point that is twice the old.""" - if self == INFINITY: return INFINITY @@ -1261,6 +1269,9 @@ * 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ecdsa-0.19.0/src/ecdsa/test_der.py new/ecdsa-0.19.1/src/ecdsa/test_der.py --- old/ecdsa-0.19.0/src/ecdsa/test_der.py 2024-04-08 17:07:29.000000000 +0200 +++ new/ecdsa-0.19.1/src/ecdsa/test_der.py 2025-03-12 17:53:37.000000000 +0100 @@ -22,8 +22,10 @@ remove_object, encode_oid, remove_constructed, + remove_implicit, remove_octet_string, remove_sequence, + encode_implicit, ) @@ -396,6 +398,128 @@ 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)) + + 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): data = b"\x04\x03\xaa\xbb\xcc" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ecdsa-0.19.0/src/ecdsa/test_ellipticcurve.py new/ecdsa-0.19.1/src/ecdsa/test_ellipticcurve.py --- old/ecdsa-0.19.0/src/ecdsa/test_ellipticcurve.py 2024-04-08 17:07:29.000000000 +0200 +++ new/ecdsa-0.19.1/src/ecdsa/test_ellipticcurve.py 2024-09-13 19:36:14.000000000 +0200 @@ -83,6 +83,11 @@ 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} @@ -184,6 +189,33 @@ 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) @@ -224,6 +256,12 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ecdsa-0.19.0/src/ecdsa/test_jacobi.py new/ecdsa-0.19.1/src/ecdsa/test_jacobi.py --- old/ecdsa-0.19.0/src/ecdsa/test_jacobi.py 2024-04-08 17:07:29.000000000 +0200 +++ new/ecdsa-0.19.1/src/ecdsa/test_jacobi.py 2024-09-13 19:36:14.000000000 +0200 @@ -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 @@ 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 @@ 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_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 @@ 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 @@ 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,20 @@ 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) @@ -641,6 +701,78 @@ 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_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) + 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_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) @@ -751,3 +883,52 @@ 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) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ecdsa-0.19.0/src/ecdsa/test_pyecdsa.py new/ecdsa-0.19.1/src/ecdsa/test_pyecdsa.py --- old/ecdsa-0.19.0/src/ecdsa/test_pyecdsa.py 2024-04-08 17:07:29.000000000 +0200 +++ new/ecdsa-0.19.1/src/ecdsa/test_pyecdsa.py 2025-03-13 11:53:15.000000000 +0100 @@ -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,8 +365,8 @@ 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) @@ -520,6 +520,23 @@ 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) @@ -770,10 +787,10 @@ 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 +802,10 @@ 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 +821,11 @@ 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 +833,10 @@ 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 +850,11 @@ 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,23 +862,39 @@ 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",)) 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" - "\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 +902,51 @@ 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_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" - "\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 +1938,7 @@ # 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 +2025,7 @@ ), secexp=int("09A4D6792295A7F730FC3F2B49CBC0F62E862272F", 16), hsh=unhexlify( - b( - "AF2BDBE1AA9B6EC1E2ADE1D694F41FC71A831D0268E9891562113D8A62ADD1BF" - ) + b"AF2BDBE1AA9B6EC1E2ADE1D694F41FC71A831D0268E9891562113D8A62ADD1BF" ), hash_func=hashlib.sha256, expected=int("23AF4074C90A02B3FE61D286D5C87F425E6BDD81B", 16), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ecdsa-0.19.0/src/ecdsa/util.py new/ecdsa-0.19.1/src/ecdsa/util.py --- old/ecdsa-0.19.0/src/ecdsa/util.py 2024-04-08 17:07:29.000000000 +0200 +++ new/ecdsa-0.19.1/src/ecdsa/util.py 2025-03-13 11:53:15.000000000 +0100 @@ -304,6 +304,23 @@ 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 @@ :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 @@ :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 @@ :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) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ecdsa-0.19.0/src/ecdsa.egg-info/PKG-INFO new/ecdsa-0.19.1/src/ecdsa.egg-info/PKG-INFO --- old/ecdsa-0.19.0/src/ecdsa.egg-info/PKG-INFO 2024-04-08 21:00:45.000000000 +0200 +++ new/ecdsa-0.19.1/src/ecdsa.egg-info/PKG-INFO 2025-03-13 12:49:21.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: ecdsa -Version: 0.19.0 +Version: 0.19.1 Summary: ECDSA cryptographic signature library (pure python) Home-page: http://github.com/tlsfuzzer/python-ecdsa Author: Brian Warner @@ -11,7 +11,6 @@ Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 @@ -19,15 +18,19 @@ Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 -Requires-Python: >=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* +Classifier: Programming Language :: Python :: 3.13 +Requires-Python: >=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.* Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: six>=1.9.0 Provides-Extra: gmpy2 +Requires-Dist: gmpy2; extra == "gmpy2" Provides-Extra: gmpy -License-File: LICENSE +Requires-Dist: gmpy; extra == "gmpy" # Pure-Python ECDSA and ECDH -[](https://github.com/tlsfuzzer/python-ecdsa/actions?query=workflow%3A%22GitHub+CI%22+branch%3Amaster) +[](https://github.com/tlsfuzzer/python-ecdsa/actions/workflows/ci.yml) [](https://ecdsa.readthedocs.io/en/latest/?badge=latest) [](https://coveralls.io/github/tlsfuzzer/python-ecdsa?branch=master)  @@ -72,7 +75,7 @@ ## 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ecdsa-0.19.0/tox.ini new/ecdsa-0.19.1/tox.ini --- old/ecdsa-0.19.0/tox.ini 2023-12-28 16:44:03.000000000 +0100 +++ new/ecdsa-0.19.1/tox.ini 2025-02-25 13:23:23.000000000 +0100 @@ -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 @@ [testenv:gmpypy312] basepython=python3.12 +[testenv:gmpypy313] +basepython=python3.13 + [testenv:gmpy2py27] basepython=python2.7 @@ -68,6 +71,9 @@ [testenv:gmpy2py312] basepython=python3.12 +[testenv:gmpy2py313] +basepython=python3.13 + [testenv:instrumental] basepython = python2.7 deps =