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
 
-[![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)
@@ -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
 
-[![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)
@@ -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
 
-[![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)
@@ -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 =

Reply via email to