From b9289254009ec0d9293503210a41880a8c7d3668 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:37:26 +0000 Subject: [PATCH 01/11] Initial plan From 9a29a7f10eaded0f865bfbb25846bac3165b822a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:41:31 +0000 Subject: [PATCH 02/11] Remove Python 2 compatibility code and add mypy configuration Co-authored-by: richardkiss <5336+richardkiss@users.noreply.github.com> --- .github/workflows/mypy.yml | 37 ++++++++++++++++++++++++++++ .github/workflows/test-py27.yml | 43 --------------------------------- .github/workflows/test.yml | 2 +- mypy.ini | 12 +++++++++ pycoin/cmds/b58.py | 2 -- pycoin/cmds/dump.py | 1 - pycoin/cmds/keychain.py | 2 -- pycoin/cmds/ku.py | 2 -- pycoin/cmds/msg.py | 2 -- pycoin/cmds/tx.py | 2 -- pycoin/ecdsa/intstream.py | 34 ++------------------------ pycoin/ecdsa/rfc6979.py | 15 +++--------- pycoin/encoding/bytes32.py | 26 +++++--------------- pycoin/intbytes.py | 24 +++++------------- pycoin/py.typed | 0 setup.py | 9 ++++--- tox.ini | 5 ++-- 17 files changed, 75 insertions(+), 143 deletions(-) create mode 100644 .github/workflows/mypy.yml delete mode 100644 .github/workflows/test-py27.yml create mode 100644 mypy.ini create mode 100644 pycoin/py.typed diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml new file mode 100644 index 000000000..588b04a1d --- /dev/null +++ b/.github/workflows/mypy.yml @@ -0,0 +1,37 @@ +name: Type checking with mypy + +on: + push: + branches: + - '**' + pull_request: + branches: + - main + +jobs: + mypy: + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + matrix: + python-version: [3.8, "3.10", "3.11"] + + steps: + - name: Checkout Code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Setup Python environment + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + pip install --upgrade pip + pip install mypy>=1.0 + + - name: Run mypy + run: | + mypy pycoin diff --git a/.github/workflows/test-py27.yml b/.github/workflows/test-py27.yml deleted file mode 100644 index 5423549fb..000000000 --- a/.github/workflows/test-py27.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Tests and coverage - -on: - push: - branches: - - '**' - tags: - - '**' - -jobs: - build: - runs-on: ubuntu-latest - timeout-minutes: 30 - strategy: - fail-fast: false - max-parallel: 4 - matrix: - python-version: [2.7] - - steps: - - name: Checkout Code - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Set up Python 2.7 - run: | - sudo apt-get update - sudo apt-get install -y software-properties-common - sudo add-apt-repository universe - sudo apt-get install -y python2 - python2 --version - - - name: Install pip for Python 2.7 - run: | - curl https://bootstrap.pypa.io/pip/2.7/get-pip.py -o get-pip.py - sudo python2 get-pip.py - python2 -m pip --version - - - name: Test core code with pytest - run: | - pip install coverage pytest - coverage run -m pytest tests diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c5ff64295..e38043bd3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false max-parallel: 4 matrix: - python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12", "3.13"] + python-version: [3.8, 3.9, "3.10", "3.11"] steps: - name: Checkout Code diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 000000000..7cef3f5a1 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,12 @@ +[mypy] +python_version = 3.8 +warn_return_any = False +warn_unused_configs = True +disallow_untyped_defs = False +ignore_missing_imports = True +strict_optional = False +files = pycoin + +# Specific module ignores can be added as needed +# [mypy-pycoin.contrib.*] +# ignore_errors = True diff --git a/pycoin/cmds/b58.py b/pycoin/cmds/b58.py index 67ffe769a..e4824366b 100755 --- a/pycoin/cmds/b58.py +++ b/pycoin/cmds/b58.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -from __future__ import print_function - import argparse from pycoin.encoding.b58 import a2b_base58, b2a_base58, a2b_hashed_base58, b2a_hashed_base58 diff --git a/pycoin/cmds/dump.py b/pycoin/cmds/dump.py index 9f4d3db67..c519e86b7 100644 --- a/pycoin/cmds/dump.py +++ b/pycoin/cmds/dump.py @@ -1,4 +1,3 @@ -from __future__ import print_function import datetime diff --git a/pycoin/cmds/keychain.py b/pycoin/cmds/keychain.py index c5a059988..4f03f0855 100755 --- a/pycoin/cmds/keychain.py +++ b/pycoin/cmds/keychain.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -from __future__ import print_function - import argparse import sqlite3 import sys diff --git a/pycoin/cmds/ku.py b/pycoin/cmds/ku.py index 799f5afb8..f6ae763c4 100755 --- a/pycoin/cmds/ku.py +++ b/pycoin/cmds/ku.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -from __future__ import print_function - import argparse import json import re diff --git a/pycoin/cmds/msg.py b/pycoin/cmds/msg.py index 5796f9dba..f57287266 100755 --- a/pycoin/cmds/msg.py +++ b/pycoin/cmds/msg.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -from __future__ import print_function - import argparse import sys diff --git a/pycoin/cmds/tx.py b/pycoin/cmds/tx.py index 794166873..8026f242e 100755 --- a/pycoin/cmds/tx.py +++ b/pycoin/cmds/tx.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -from __future__ import print_function - import argparse import calendar import codecs diff --git a/pycoin/ecdsa/intstream.py b/pycoin/ecdsa/intstream.py index ecd8a055a..54d012534 100644 --- a/pycoin/ecdsa/intstream.py +++ b/pycoin/ecdsa/intstream.py @@ -1,40 +1,10 @@ -from pycoin.intbytes import iterbytes, byte2int - -def _to_bytes(v, length, byteorder="big"): +def to_bytes(v, length, byteorder="big"): """This is the same functionality as ``int.to_bytes`` in python 3""" return v.to_bytes(length, byteorder=byteorder) -def _from_bytes(bytes, byteorder="big", signed=False): +def from_bytes(bytes, byteorder="big", signed=False): """This is the same functionality as ``int.from_bytes`` in python 3""" return int.from_bytes(bytes, byteorder=byteorder, signed=signed) - - -if hasattr(int, "to_bytes"): - to_bytes = _to_bytes - from_bytes = _from_bytes -else: - def to_bytes(v, length, byteorder="big"): - """This is the same functionality as ``int.to_bytes`` in python 3""" - ba = bytearray() - for i in range(length): - mod = v & 0xff - v >>= 8 - ba.append(mod) - if byteorder == "big": - ba.reverse() - return bytes(ba) - - def from_bytes(bytes, byteorder="big", signed=False): - """This is the same functionality as ``int.from_bytes`` in python 3""" - if byteorder != "big": - bytes = reversed(bytes) - v = 0 - for c in iterbytes(bytes): - v <<= 8 - v += c - if signed and byte2int(bytes) & 0x80: - v = v - (1 << (8*len(bytes))) - return v diff --git a/pycoin/ecdsa/rfc6979.py b/pycoin/ecdsa/rfc6979.py index 1af9274bd..758ceae73 100644 --- a/pycoin/ecdsa/rfc6979.py +++ b/pycoin/ecdsa/rfc6979.py @@ -4,18 +4,9 @@ from . import intstream -if hasattr(1, "bit_length"): - def bit_length(v): - "the ``int.bit_length`` in `python 3 `_" - return v.bit_length() -else: - def bit_length(self): - "the ``int.bit_length`` in `python 3 `_" - # compared to "while n>0: bl +=1 ; n >>= 1", this is much faster in both python2 and pypy - # code taken from the link above - s = bin(self) # binary representation: bin(-37) --> '-0b100101' - s = s.lstrip('-0b') # remove leading zeros and minus sign - return len(s) # len('100101') --> 6 +def bit_length(v): + "the ``int.bit_length`` in `python 3 `_" + return v.bit_length() def deterministic_generate_k(generator_order, secret_exponent, val, hash_f=hashlib.sha256): diff --git a/pycoin/encoding/bytes32.py b/pycoin/encoding/bytes32.py index a757c558b..5be6919bb 100644 --- a/pycoin/encoding/bytes32.py +++ b/pycoin/encoding/bytes32.py @@ -1,24 +1,10 @@ -if hasattr(int, "to_bytes"): - def to_bytes_32(v): - return v.to_bytes(32, byteorder="big") - - def from_bytes_32(v): - return int.from_bytes(v, byteorder="big") -else: - from .base_conversion import from_long, to_long - from ..intbytes import byte2int - - def to_bytes_32(v): - v = from_long(v, 0, 256, lambda x: x) - if len(v) > 32: - raise ValueError("input to to_bytes_32 is too large") - return ((b'\0' * 32) + v)[-32:] - - def from_bytes_32(v): - if len(v) > 32: - raise OverflowError("int too big to convert") - return to_long(256, byte2int, v)[0] +def to_bytes_32(v): + return v.to_bytes(32, byteorder="big") + + +def from_bytes_32(v): + return int.from_bytes(v, byteorder="big") """ diff --git a/pycoin/intbytes.py b/pycoin/intbytes.py index e4b177fe6..3052a7cb4 100644 --- a/pycoin/intbytes.py +++ b/pycoin/intbytes.py @@ -1,6 +1,5 @@ """ -Provide the following functions, all cribbed from the library -`six `_. +Provide the following functions for byte operations in Python 3: iterbytes(buf): return an iterator of ints corresponding to the bytes of buf @@ -15,22 +14,11 @@ turn bs[0] into an int (0-255) """ -import functools -import itertools import operator import struct -if bytes == str: - iterbytes = functools.partial(itertools.imap, ord) - - def indexbytes(buf, i): - return ord(buf[i]) - int2byte = chr - - def byte2int(bs): - return ord(bs[0]) -else: - iterbytes = iter - indexbytes = operator.getitem - int2byte = struct.Struct(">B").pack - byte2int = operator.itemgetter(0) +# Python 3 implementations +iterbytes = iter +indexbytes = operator.getitem +int2byte = struct.Struct(">B").pack +byte2int = operator.itemgetter(0) diff --git a/pycoin/py.typed b/pycoin/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/setup.py b/setup.py index 2205b8f53..26e240906 100755 --- a/setup.py +++ b/setup.py @@ -52,13 +52,14 @@ description="Utilities for Bitcoin and altcoin addresses and transaction manipulation.", long_description=long_description, long_description_content_type='text/markdown', + python_requires='>=3.8', classifiers=[ 'Development Status :: 3 - Alpha', - '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', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', 'License :: OSI Approved :: MIT License', 'Topic :: Internet', 'Topic :: Security :: Cryptography', diff --git a/tox.ini b/tox.ini index 381dbc2f5..672fbbc38 100644 --- a/tox.ini +++ b/tox.ini @@ -4,11 +4,12 @@ # and then run "tox" from this directory. [tox] -envlist = py27, py34, py35, py36, pypy +envlist = py38, py39, py310, py311 [testenv] -commands = py.test --cov=. tests +commands = pytest tests deps = pytest groestlcoin_hash coverage + mypy>=1.0 From 4bd1a2733d9e2de406fb41992854a365a1c526b7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:44:03 +0000 Subject: [PATCH 03/11] Fix NotImplementedError and urllib compatibility, add initial type annotations Co-authored-by: richardkiss <5336+richardkiss@users.noreply.github.com> --- mypy.ini | 2 +- pycoin/coins/SolutionChecker.py | 2 +- pycoin/coins/Tx.py | 14 +++++++------- pycoin/coins/TxIn.py | 8 ++++---- pycoin/coins/TxOut.py | 8 ++++---- pycoin/ecdsa/intstream.py | 4 ++-- pycoin/ecdsa/rfc6979.py | 5 +++-- pycoin/encoding/bytes32.py | 4 ++-- pycoin/intbytes.py | 9 +++++---- pycoin/services/agent.py | 9 ++------- 10 files changed, 31 insertions(+), 34 deletions(-) diff --git a/mypy.ini b/mypy.ini index 7cef3f5a1..35cf999f6 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,5 +1,5 @@ [mypy] -python_version = 3.8 +python_version = 3.9 warn_return_any = False warn_unused_configs = True disallow_untyped_defs = False diff --git a/pycoin/coins/SolutionChecker.py b/pycoin/coins/SolutionChecker.py index 1932255de..a77e6554a 100644 --- a/pycoin/coins/SolutionChecker.py +++ b/pycoin/coins/SolutionChecker.py @@ -18,4 +18,4 @@ def check_solution(self, tx_context, traceback_f=None, *args, **kwargs): tx_context: information about the transaction that the VM may need traceback_f: a function invoked on occasion to check intermediate state """ - raise NotImplemented() + raise NotImplementedError() diff --git a/pycoin/coins/Tx.py b/pycoin/coins/Tx.py index d6336e92a..8800d64c1 100644 --- a/pycoin/coins/Tx.py +++ b/pycoin/coins/Tx.py @@ -17,7 +17,7 @@ class Tx(object): @classmethod def parse(class_, f): """Parse a transaction Tx from the file-like object f.""" - raise NotImplemented() + raise NotImplementedError() @classmethod def from_bin(class_, blob): @@ -53,11 +53,11 @@ def from_hex(class_, hex_string): return class_.from_bin(h2b(hex_string)) def __init__(self, *args, **kwargs): - raise NotImplemented() + raise NotImplementedError() def stream(self, f, *args, **kwargs): """Stream a transaction Tx to the file-like object f.""" - raise NotImplemented() + raise NotImplementedError() def as_bin(self, *args, **kwargs): """Returns a binary blob containing the streamed transaction. @@ -81,7 +81,7 @@ def as_hex(self, *args, **kwargs): def hash(self, hash_type=None): """Return the hash for this Tx object.""" - raise NotImplemented() + raise NotImplementedError() def id(self): """Return the human-readable hash for this Tx object.""" @@ -97,16 +97,16 @@ def tx_outs_as_spendable(self, block_index_available=0): for tx_out_index, tx_out in enumerate(self.txs_out)] def __str__(self): - raise NotImplemented() + raise NotImplementedError() def __repr__(self): - raise NotImplemented() + raise NotImplementedError() def check(self): """ Basic checks that don't depend on network or block context. """ - raise NotImplemented() + raise NotImplementedError() """ The functions below here deal with an optional additional parameter: "unspents". diff --git a/pycoin/coins/TxIn.py b/pycoin/coins/TxIn.py index d2415548d..16f6a2212 100644 --- a/pycoin/coins/TxIn.py +++ b/pycoin/coins/TxIn.py @@ -5,16 +5,16 @@ class TxIn(object): @classmethod def parse(class_, f): - raise NotImplemented() + raise NotImplementedError() def __init__(self, *args, **kwargs): - raise NotImplemented() + raise NotImplementedError() def stream(self, f): - raise NotImplemented() + raise NotImplementedError() def __str__(self): - raise NotImplemented() + raise NotImplementedError() """ diff --git a/pycoin/coins/TxOut.py b/pycoin/coins/TxOut.py index d35aefff0..5080b2fad 100644 --- a/pycoin/coins/TxOut.py +++ b/pycoin/coins/TxOut.py @@ -5,16 +5,16 @@ class TxOut(object): @classmethod def parse(class_, f): - raise NotImplemented() + raise NotImplementedError() def __init__(self, *args, **kwargs): - raise NotImplemented() + raise NotImplementedError() def stream(self, f): - raise NotImplemented() + raise NotImplementedError() def __str__(self): - raise NotImplemented() + raise NotImplementedError() """ diff --git a/pycoin/ecdsa/intstream.py b/pycoin/ecdsa/intstream.py index 54d012534..09ffd69cf 100644 --- a/pycoin/ecdsa/intstream.py +++ b/pycoin/ecdsa/intstream.py @@ -1,10 +1,10 @@ -def to_bytes(v, length, byteorder="big"): +def to_bytes(v: int, length: int, byteorder: str = "big") -> bytes: """This is the same functionality as ``int.to_bytes`` in python 3""" return v.to_bytes(length, byteorder=byteorder) -def from_bytes(bytes, byteorder="big", signed=False): +def from_bytes(bytes: bytes, byteorder: str = "big", signed: bool = False) -> int: """This is the same functionality as ``int.from_bytes`` in python 3""" return int.from_bytes(bytes, byteorder=byteorder, signed=signed) diff --git a/pycoin/ecdsa/rfc6979.py b/pycoin/ecdsa/rfc6979.py index 758ceae73..a532a9d56 100644 --- a/pycoin/ecdsa/rfc6979.py +++ b/pycoin/ecdsa/rfc6979.py @@ -1,15 +1,16 @@ import hashlib import hmac +from typing import Callable, Any from . import intstream -def bit_length(v): +def bit_length(v: int) -> int: "the ``int.bit_length`` in `python 3 `_" return v.bit_length() -def deterministic_generate_k(generator_order, secret_exponent, val, hash_f=hashlib.sha256): +def deterministic_generate_k(generator_order: int, secret_exponent: int, val: int, hash_f: Callable[[], Any] = hashlib.sha256) -> int: """ :param generator_order: result from `pycoin.ecdsa.Generator.Generator.order`, necessary to ensure the k value is within bound diff --git a/pycoin/encoding/bytes32.py b/pycoin/encoding/bytes32.py index 5be6919bb..7ca0aa165 100644 --- a/pycoin/encoding/bytes32.py +++ b/pycoin/encoding/bytes32.py @@ -1,9 +1,9 @@ -def to_bytes_32(v): +def to_bytes_32(v: int) -> bytes: return v.to_bytes(32, byteorder="big") -def from_bytes_32(v): +def from_bytes_32(v: bytes) -> int: return int.from_bytes(v, byteorder="big") diff --git a/pycoin/intbytes.py b/pycoin/intbytes.py index 3052a7cb4..f3f7e007f 100644 --- a/pycoin/intbytes.py +++ b/pycoin/intbytes.py @@ -14,11 +14,12 @@ turn bs[0] into an int (0-255) """ +from typing import Iterator, Callable import operator import struct # Python 3 implementations -iterbytes = iter -indexbytes = operator.getitem -int2byte = struct.Struct(">B").pack -byte2int = operator.itemgetter(0) +iterbytes: Callable[[bytes], Iterator[int]] = iter +indexbytes: Callable[[bytes, int], int] = operator.getitem +int2byte: Callable[[int], bytes] = struct.Struct(">B").pack +byte2int: Callable[[bytes], int] = operator.itemgetter(0) diff --git a/pycoin/services/agent.py b/pycoin/services/agent.py index 8c87901d7..19a73bdf8 100644 --- a/pycoin/services/agent.py +++ b/pycoin/services/agent.py @@ -1,11 +1,6 @@ from pycoin import version - -try: - import urllib2 as request - from urllib import urlencode # noqa -except ImportError: - from urllib import request - from urllib.parse import urlencode # noqa +from urllib import request +from urllib.parse import urlencode # noqa PYCOIN_AGENT = 'pycoin/%s' % version From e3742c325923b8f4f966481e9bfc48f34d255fc3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:45:48 +0000 Subject: [PATCH 04/11] Add type annotations to encoding utilities and create requirements-dev.txt Co-authored-by: richardkiss <5336+richardkiss@users.noreply.github.com> --- pycoin/ecdsa/intstream.py | 4 ++-- pycoin/encoding/hexbytes.py | 8 ++++---- requirements-dev.txt | 11 +++++++++++ 3 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 requirements-dev.txt diff --git a/pycoin/ecdsa/intstream.py b/pycoin/ecdsa/intstream.py index 09ffd69cf..79f86404a 100644 --- a/pycoin/ecdsa/intstream.py +++ b/pycoin/ecdsa/intstream.py @@ -2,9 +2,9 @@ def to_bytes(v: int, length: int, byteorder: str = "big") -> bytes: """This is the same functionality as ``int.to_bytes`` in python 3""" - return v.to_bytes(length, byteorder=byteorder) + return v.to_bytes(length, byteorder=byteorder) # type: ignore[arg-type] def from_bytes(bytes: bytes, byteorder: str = "big", signed: bool = False) -> int: """This is the same functionality as ``int.from_bytes`` in python 3""" - return int.from_bytes(bytes, byteorder=byteorder, signed=signed) + return int.from_bytes(bytes, byteorder=byteorder, signed=signed) # type: ignore[arg-type] diff --git a/pycoin/encoding/hexbytes.py b/pycoin/encoding/hexbytes.py index 326373735..973c67fe6 100644 --- a/pycoin/encoding/hexbytes.py +++ b/pycoin/encoding/hexbytes.py @@ -1,7 +1,7 @@ import binascii -def h2b(h): +def h2b(h: str) -> bytes: """ A version of binascii.unhexlify that accepts unicode. This is no longer necessary as of Python 3.3. But it doesn't hurt. @@ -15,15 +15,15 @@ def h2b(h): raise ValueError("h2b failed on %s" % h) -def h2b_rev(h): +def h2b_rev(h: str) -> bytes: return h2b(h)[::-1] -def b2h(the_bytes): +def b2h(the_bytes: bytes) -> str: return binascii.hexlify(the_bytes).decode("utf8") -def b2h_rev(the_bytes): +def b2h_rev(the_bytes: bytes) -> str: return b2h(bytearray(reversed(the_bytes))) diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 000000000..8e0bbedb9 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,11 @@ +# Development dependencies for pycoin + +# Testing +pytest>=7.0 +coverage>=5.0 + +# Type checking +mypy>=1.0 + +# Optional: for testing altcoin support +groestlcoin_hash From b6bd947cf1605ac98a79e20fdee414b063e0f289 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:46:35 +0000 Subject: [PATCH 05/11] Add type annotations to merkle and bloomfilter modules Co-authored-by: richardkiss <5336+richardkiss@users.noreply.github.com> --- pycoin/bloomfilter.py | 13 +++++++------ pycoin/merkle.py | 8 +++++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/pycoin/bloomfilter.py b/pycoin/bloomfilter.py index 335c56847..4017f2919 100644 --- a/pycoin/bloomfilter.py +++ b/pycoin/bloomfilter.py @@ -1,5 +1,6 @@ import math import struct +from typing import Tuple from pycoin.encoding.b58 import a2b_hashed_base58 from pycoin.intbytes import indexbytes @@ -7,7 +8,7 @@ LOG_2 = math.log(2) -def filter_size_required(element_count, false_positive_probability): +def filter_size_required(element_count: int, false_positive_probability: float) -> int: # The size S of the filter in bytes is given by # (-1 / pow(log(2), 2) * N * log(P)) / 8 # Of course you must ensure it does not go over the maximum size @@ -17,7 +18,7 @@ def filter_size_required(element_count, false_positive_probability): return min(36000, int(((-1 / pow(LOG_2, 2) * element_count * lfpp)+7) // 8)) -def hash_function_count_required(filter_size, element_count): +def hash_function_count_required(filter_size: int, element_count: int) -> int: # The number of hash functions required is given by S * 8 / N * log(2). return int(filter_size * 8.0 / element_count * LOG_2 + 0.5) @@ -25,7 +26,7 @@ def hash_function_count_required(filter_size, element_count): class BloomFilter(object): MASK_ARRAY = [1 << _ for _ in range(8)] - def __init__(self, size_in_bytes, hash_function_count, tweak): + def __init__(self, size_in_bytes: int, hash_function_count: int, tweak: int) -> None: if size_in_bytes > 36000: raise ValueError("too large") self.filter_bytes = bytearray(size_in_bytes) @@ -33,7 +34,7 @@ def __init__(self, size_in_bytes, hash_function_count, tweak): self.hash_function_count = hash_function_count self.tweak = tweak - def add_item(self, item_bytes): + def add_item(self, item_bytes: bytes) -> None: for hash_index in range(self.hash_function_count): seed = hash_index * 0xFBA4C795 + self.tweak self.set_bit(murmur3(item_bytes, seed=seed) % self.bit_count) @@ -63,13 +64,13 @@ def check_bit(self, v): byte_index, mask = self._index_for_bit(v) return (self.filter_bytes[byte_index] & mask) == mask - def filter_load_params(self): + def filter_load_params(self) -> Tuple[bytearray, int, int]: return self.filter_bytes, self.hash_function_count, self.tweak # http://stackoverflow.com/questions/13305290/is-there-a-pure-python-implementation-of-murmurhash -def murmur3(data, seed=0): +def murmur3(data: bytes, seed: int = 0) -> int: c1 = 0xcc9e2d51 c2 = 0x1b873593 diff --git a/pycoin/merkle.py b/pycoin/merkle.py index 3574b2bd1..eb541171d 100644 --- a/pycoin/merkle.py +++ b/pycoin/merkle.py @@ -1,15 +1,17 @@ +from typing import List, Callable + from .encoding.hash import double_sha256 from .encoding.hexbytes import h2b_rev -def merkle(hashes, hash_f=double_sha256): +def merkle(hashes: List[bytes], hash_f: Callable[[bytes], bytes] = double_sha256) -> bytes: """Take a list of hashes, and return the root merkle hash.""" while len(hashes) > 1: hashes = merkle_pair(hashes, hash_f) return hashes[0] -def merkle_pair(hashes, hash_f): +def merkle_pair(hashes: List[bytes], hash_f: Callable[[bytes], bytes]) -> List[bytes]: """Take a list of hashes, and return the parent row in the tree of merkle hashes.""" if len(hashes) % 2 == 1: hashes = list(hashes) @@ -20,7 +22,7 @@ def merkle_pair(hashes, hash_f): return items -def test_merkle(): +def test_merkle() -> None: s1 = h2b_rev("56dee62283a06e85e182e2d0b421aceb0eadec3d5f86cdadf9688fc095b72510") assert merkle([s1], double_sha256) == s1 # from block 71043 From 98cc1ca056c9c2c652ce0355f063cc30bf418e6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:47:39 +0000 Subject: [PATCH 06/11] Update README to reflect Python 3.8+ requirement and include py.typed in MANIFEST.in Co-authored-by: richardkiss <5336+richardkiss@users.noreply.github.com> --- MANIFEST.in | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index b3db1eb18..b8764253d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,2 @@ include CHANGES CREDITS LICENSE *.md *.txt +include pycoin/py.typed diff --git a/README.md b/README.md index 510c90a2b..46bc9a5d9 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ pycoin -- Python Cryptocoin Utilities ===================================== The pycoin library implements many utilities useful when dealing with bitcoin and some bitcoin-like -alt-coins. It has been tested with Python 2.7, 3.7-3.13. +alt-coins. It requires Python 3.8 or higher. See also [pycoinnet](http://github.com/richardkiss/pycoinnet/) for a library that speaks the bitcoin protocol. From 281df10f909fe0c33480e101c48eebf55600bebb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:49:29 +0000 Subject: [PATCH 07/11] Address code review: add Python 3.12-3.13 to test matrix, use Literal types, fix hash_f annotation Co-authored-by: richardkiss <5336+richardkiss@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- pycoin/ecdsa/intstream.py | 9 +++++---- pycoin/ecdsa/rfc6979.py | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e38043bd3..048090fd6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false max-parallel: 4 matrix: - python-version: [3.8, 3.9, "3.10", "3.11"] + python-version: [3.8, 3.9, "3.10", "3.11", "3.12", "3.13"] steps: - name: Checkout Code diff --git a/pycoin/ecdsa/intstream.py b/pycoin/ecdsa/intstream.py index 79f86404a..1601d8959 100644 --- a/pycoin/ecdsa/intstream.py +++ b/pycoin/ecdsa/intstream.py @@ -1,10 +1,11 @@ +from typing import Literal -def to_bytes(v: int, length: int, byteorder: str = "big") -> bytes: +def to_bytes(v: int, length: int, byteorder: Literal["big", "little"] = "big") -> bytes: """This is the same functionality as ``int.to_bytes`` in python 3""" - return v.to_bytes(length, byteorder=byteorder) # type: ignore[arg-type] + return v.to_bytes(length, byteorder=byteorder) -def from_bytes(bytes: bytes, byteorder: str = "big", signed: bool = False) -> int: +def from_bytes(bytes: bytes, byteorder: Literal["big", "little"] = "big", signed: bool = False) -> int: """This is the same functionality as ``int.from_bytes`` in python 3""" - return int.from_bytes(bytes, byteorder=byteorder, signed=signed) # type: ignore[arg-type] + return int.from_bytes(bytes, byteorder=byteorder, signed=signed) diff --git a/pycoin/ecdsa/rfc6979.py b/pycoin/ecdsa/rfc6979.py index a532a9d56..6672d115c 100644 --- a/pycoin/ecdsa/rfc6979.py +++ b/pycoin/ecdsa/rfc6979.py @@ -10,7 +10,7 @@ def bit_length(v: int) -> int: return v.bit_length() -def deterministic_generate_k(generator_order: int, secret_exponent: int, val: int, hash_f: Callable[[], Any] = hashlib.sha256) -> int: +def deterministic_generate_k(generator_order: int, secret_exponent: int, val: int, hash_f: Callable[..., Any] = hashlib.sha256) -> int: """ :param generator_order: result from `pycoin.ecdsa.Generator.Generator.order`, necessary to ensure the k value is within bound From b5ba7b10622ca34c36ac312d37a44870559a7560 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:50:53 +0000 Subject: [PATCH 08/11] Update tox.ini to include Python 3.12-3.13 and restore coverage reporting Co-authored-by: richardkiss <5336+richardkiss@users.noreply.github.com> --- .coverage | Bin 0 -> 69632 bytes requirements-dev.txt | 1 + tox.ini | 4 ++-- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 .coverage diff --git a/.coverage b/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..60862b5cd2d60a54a18d8cdd8bee29c645bbc5da GIT binary patch literal 69632 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.0 +pytest-cov>=3.0 coverage>=5.0 # Type checking diff --git a/tox.ini b/tox.ini index 672fbbc38..034ac41f0 100644 --- a/tox.ini +++ b/tox.ini @@ -4,10 +4,10 @@ # and then run "tox" from this directory. [tox] -envlist = py38, py39, py310, py311 +envlist = py38, py39, py310, py311, py312, py313 [testenv] -commands = pytest tests +commands = coverage run -m pytest tests deps = pytest groestlcoin_hash From 4b3c559bad8dcf8320016486af8ce08de3749df9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:51:23 +0000 Subject: [PATCH 09/11] Add coverage and cache files to .gitignore, remove .coverage from git Co-authored-by: richardkiss <5336+richardkiss@users.noreply.github.com> --- .coverage | Bin 69632 -> 0 bytes .gitignore | 5 +++++ 2 files changed, 5 insertions(+) delete mode 100644 .coverage diff --git a/.coverage b/.coverage deleted file mode 100644 index 60862b5cd2d60a54a18d8cdd8bee29c645bbc5da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69632 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 Date: Tue, 10 Feb 2026 20:52:26 +0000 Subject: [PATCH 10/11] Add Python 3.12-3.13 classifiers to setup.py and align mypy to Python 3.8 Co-authored-by: richardkiss <5336+richardkiss@users.noreply.github.com> --- mypy.ini | 2 +- setup.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy.ini b/mypy.ini index 35cf999f6..7cef3f5a1 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,5 +1,5 @@ [mypy] -python_version = 3.9 +python_version = 3.8 warn_return_any = False warn_unused_configs = True disallow_untyped_defs = False diff --git a/setup.py b/setup.py index 26e240906..34e9db8ad 100755 --- a/setup.py +++ b/setup.py @@ -60,6 +60,8 @@ 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'License :: OSI Approved :: MIT License', 'Topic :: Internet', 'Topic :: Security :: Cryptography', From 7adfee363a34a7c3764bd1b755600b0a398865e0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:53:34 +0000 Subject: [PATCH 11/11] Fix parameter name shadowing built-in 'bytes' in intstream.py Co-authored-by: richardkiss <5336+richardkiss@users.noreply.github.com> --- pycoin/ecdsa/intstream.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycoin/ecdsa/intstream.py b/pycoin/ecdsa/intstream.py index 1601d8959..44bd5d5f8 100644 --- a/pycoin/ecdsa/intstream.py +++ b/pycoin/ecdsa/intstream.py @@ -6,6 +6,6 @@ def to_bytes(v: int, length: int, byteorder: Literal["big", "little"] = "big") - return v.to_bytes(length, byteorder=byteorder) -def from_bytes(bytes: bytes, byteorder: Literal["big", "little"] = "big", signed: bool = False) -> int: +def from_bytes(data: bytes, byteorder: Literal["big", "little"] = "big", signed: bool = False) -> int: """This is the same functionality as ``int.from_bytes`` in python 3""" - return int.from_bytes(bytes, byteorder=byteorder, signed=signed) + return int.from_bytes(data, byteorder=byteorder, signed=signed)