├── tests ├── __init__.py ├── crypt │ ├── __init__.py │ ├── helper.py │ ├── test_error.py │ ├── test_eth.py │ ├── test_deprecated.py │ ├── test_random.py │ └── test_known.py ├── keys │ ├── __init__.py │ ├── conftest.py │ ├── test_error.py │ ├── test_public.py │ ├── test_private.py │ └── test_known.py ├── utils │ ├── __init__.py │ ├── test_hex.py │ ├── test_hash.py │ ├── test_eth.py │ ├── test_deprecated.py │ └── test_symmetric.py └── conftest.py ├── _config.yml ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── cd.yml │ └── ci.yml ├── ecies ├── py.typed ├── keys │ ├── __init__.py │ ├── public.py │ ├── private.py │ └── helper.py ├── consts.py ├── utils │ ├── hash.py │ ├── hex.py │ ├── __init__.py │ ├── eth.py │ ├── elliptic.py │ └── symmetric.py ├── config.py ├── __init__.py └── __main__.py ├── .ruff.toml ├── .cspell.jsonc ├── LICENSE ├── scripts └── ci.sh ├── CHANGELOG.md ├── .gitignore ├── pyproject.toml ├── README.md ├── DETAILS.md └── poetry.lock /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/crypt/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/keys/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: kigawas 2 | -------------------------------------------------------------------------------- /ecies/py.typed: -------------------------------------------------------------------------------- 1 | # Marker file for PEP 561. 2 | -------------------------------------------------------------------------------- /.ruff.toml: -------------------------------------------------------------------------------- 1 | line-length = 88 2 | 3 | [lint] 4 | ignore = ["E501", "W605", "E203"] 5 | 6 | [lint.mccabe] 7 | max-complexity = 10 8 | -------------------------------------------------------------------------------- /ecies/keys/__init__.py: -------------------------------------------------------------------------------- 1 | from .private import PrivateKey 2 | from .public import PublicKey 3 | 4 | __all__ = ["PrivateKey", "PublicKey"] 5 | -------------------------------------------------------------------------------- /tests/utils/test_hex.py: -------------------------------------------------------------------------------- 1 | from ecies.utils import decode_hex 2 | 3 | 4 | def test_decode_hex(): 5 | assert decode_hex("0x7468697320697320612074657374") == b"this is a test" 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | open-pull-requests-limit: 3 8 | -------------------------------------------------------------------------------- /tests/utils/test_hash.py: -------------------------------------------------------------------------------- 1 | from ecies.utils import derive_key 2 | 3 | 4 | def test_hkdf(): 5 | derived = derive_key(b"secret").hex() 6 | assert derived == "2f34e5ff91ec85d53ca9b543683174d0cf550b60d5f52b24c97b386cfcf6cbbf" 7 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(scope="session") 5 | def data(): 6 | return "helloworld🌍".encode() 7 | 8 | 9 | @pytest.fixture(scope="session") 10 | def big_data(): 11 | return b"1" * 1024 * 1024 * 100 # 100 MB 12 | -------------------------------------------------------------------------------- /ecies/consts.py: -------------------------------------------------------------------------------- 1 | # elliptic 2 | SECRET_KEY_SIZE = 32 3 | COMPRESSED_PUBLIC_KEY_SIZE = 33 4 | UNCOMPRESSED_PUBLIC_KEY_SIZE = 65 5 | CURVE25519_PUBLIC_KEY_SIZE = 32 6 | ETH_PUBLIC_KEY_LENGTH = 64 7 | 8 | # symmetric 9 | XCHACHA20_NONCE_LENGTH = 24 10 | AEAD_TAG_LENGTH = 16 11 | -------------------------------------------------------------------------------- /tests/utils/test_eth.py: -------------------------------------------------------------------------------- 1 | from ecies.keys import PublicKey 2 | from ecies.utils import to_eth_address 3 | 4 | 5 | def test_checksum_address(): 6 | pk = "02d9ed78008e7b6c4bdc2beea13230fb3ccb8072728c0986894a3d544485e9b727" 7 | address = "0x7aD23D6eD9a1D98E240988BED0d78e8C81Ec296C" 8 | assert to_eth_address(PublicKey.from_hex("secp256k1", pk).to_bytes()[1:]) == address 9 | -------------------------------------------------------------------------------- /tests/crypt/helper.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | 3 | 4 | from ecies import ECIES_CONFIG 5 | from ecies.config import EllipticCurve 6 | 7 | 8 | @contextmanager 9 | def config_manager(curve: EllipticCurve): 10 | _curve = ECIES_CONFIG.elliptic_curve 11 | ECIES_CONFIG.elliptic_curve = curve 12 | yield 13 | ECIES_CONFIG.elliptic_curve = _curve 14 | -------------------------------------------------------------------------------- /ecies/utils/hash.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | from Crypto.Hash import SHA256 4 | from Crypto.Protocol.KDF import HKDF 5 | from typing_extensions import deprecated 6 | 7 | 8 | @deprecated("Use `hashlib.sha256(data).digest()` instead") 9 | def sha256(data: bytes) -> bytes: 10 | return hashlib.sha256(data).digest() 11 | 12 | 13 | def derive_key(master: bytes, salt: bytes = b"") -> bytes: 14 | # 32 bytes for aes256 and xchacha20 15 | derived = HKDF(master, 32, salt, SHA256, num_keys=1) 16 | return derived # type: ignore 17 | -------------------------------------------------------------------------------- /ecies/utils/hex.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | 3 | 4 | def decode_hex(s: str) -> bytes: 5 | """ 6 | Decode hex string to bytes. `0x` prefix is optional. 7 | 8 | Parameters 9 | ---------- 10 | s: str 11 | hex string 12 | 13 | Returns 14 | ------- 15 | bytes 16 | decoded bytes 17 | """ 18 | return codecs.decode(remove_0x(s), "hex") 19 | 20 | 21 | # private below 22 | def remove_0x(s: str) -> str: 23 | if s.startswith("0x") or s.startswith("0X"): 24 | return s[2:] 25 | return s 26 | -------------------------------------------------------------------------------- /ecies/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .elliptic import decapsulate, encapsulate, generate_key, hex2pk, hex2sk 2 | from .eth import generate_eth_key, to_eth_address 3 | from .hash import derive_key, sha256 4 | from .hex import decode_hex 5 | from .symmetric import sym_decrypt, sym_encrypt 6 | 7 | __all__ = [ 8 | "sym_encrypt", 9 | "sym_decrypt", 10 | "generate_key", 11 | "hex2sk", 12 | "hex2pk", 13 | "encapsulate", 14 | "decapsulate", 15 | # eth 16 | "generate_eth_key", 17 | "to_eth_address", 18 | # hex 19 | "decode_hex", 20 | # hash 21 | "sha256", 22 | "derive_key", 23 | ] 24 | -------------------------------------------------------------------------------- /.cspell.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "words": [ 3 | "bitcointalk", 4 | "chacha", 5 | "Cipolla", 6 | "Codacy", 7 | "Codecov", 8 | "coincurve", 9 | "dataclass", 10 | "ecdh", 11 | "ecies", 12 | "eciespy", 13 | "eddsa", 14 | "elif", 15 | "fromhex", 16 | "hashlib", 17 | "helloworld", 18 | "hexdigest", 19 | "hkdf", 20 | "keccak", 21 | "pycryptodome", 22 | "pytest", 23 | "readablize", 24 | "secp", 25 | "urandom", 26 | "xcfl", 27 | "xchacha" 28 | ], 29 | "ignorePaths": [ 30 | ".cspell.jsonc", 31 | ".gitignore", 32 | "LICENSE", 33 | "pyproject.toml", 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /tests/utils/test_deprecated.py: -------------------------------------------------------------------------------- 1 | from coincurve import PrivateKey 2 | 3 | from ecies.utils import decapsulate, encapsulate, sha256 4 | 5 | 6 | def test_hash(): 7 | assert sha256(b"0" * 16).hex()[:8] == "fcdb4b42" 8 | 9 | 10 | def test_encapsulate_decapsulate(): 11 | k1 = PrivateKey(secret=bytes([2])) 12 | assert k1.to_int() == 2 13 | 14 | k2 = PrivateKey(secret=bytes([3])) 15 | assert k2.to_int() == 3 16 | 17 | assert encapsulate(k1, k2.public_key) == decapsulate(k1.public_key, k2) 18 | assert ( 19 | encapsulate(k1, k2.public_key).hex() 20 | == "6f982d63e8590c9d9b5b4c1959ff80315d772edd8f60287c9361d548d5200f82" 21 | ) 22 | -------------------------------------------------------------------------------- /tests/crypt/test_error.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ecies import decrypt, encrypt 4 | from ecies.keys import PrivateKey 5 | 6 | from .helper import config_manager 7 | 8 | 9 | @pytest.mark.parametrize("curve", ["secp256k1", "x25519", "ed25519"]) 10 | def test_encrypt_error(curve, data): 11 | with pytest.raises(TypeError): 12 | encrypt(1, data) # type: ignore 13 | 14 | 15 | @pytest.mark.parametrize("curve", ["secp256k1", "x25519", "ed25519"]) 16 | def test_decrypt_error(curve, data): 17 | pk_hex = PrivateKey(curve).public_key.to_bytes(True).hex() 18 | with config_manager(curve): 19 | encrypted = encrypt(bytes.fromhex(pk_hex), data) 20 | with pytest.raises(TypeError): 21 | decrypt(1, encrypted) # type: ignore 22 | -------------------------------------------------------------------------------- /tests/keys/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ecies.config import EllipticCurve 4 | from ecies.keys import PrivateKey, PublicKey 5 | 6 | 7 | @pytest.fixture 8 | def k1(curve: EllipticCurve): 9 | return PrivateKey(curve, b"\x00" * 31 + b"\x02") 10 | 11 | 12 | @pytest.fixture 13 | def k2(curve: EllipticCurve): 14 | return PrivateKey(curve, b"\x00" * 31 + b"\x03") 15 | 16 | 17 | @pytest.fixture 18 | def pk1(k1: PrivateKey): 19 | return k1.public_key 20 | 21 | 22 | @pytest.fixture 23 | def pk2(k2: PrivateKey): 24 | return k2.public_key 25 | 26 | 27 | @pytest.fixture 28 | def random_sk(curve: EllipticCurve) -> PrivateKey: 29 | return PrivateKey(curve) 30 | 31 | 32 | @pytest.fixture 33 | def random_pk(curve: EllipticCurve) -> PublicKey: 34 | return PrivateKey(curve).public_key 35 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: CD 2 | 3 | on: 4 | release: 5 | types: [published] 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | 12 | - name: Install poetry 13 | run: pipx install poetry 14 | 15 | - uses: actions/setup-python@v5 16 | with: 17 | python-version: "3.13" 18 | cache: poetry 19 | 20 | - name: Upload to pypi 21 | run: | 22 | poetry build 23 | poetry config repositories.testpypi https://test.pypi.org/legacy/ 24 | poetry config pypi-token.testpypi "$TEST_PYPI_TOKEN" 25 | poetry config pypi-token.pypi "$PYPI_TOKEN" 26 | poetry publish --dry-run 27 | poetry publish --repository testpypi 28 | poetry publish 29 | env: 30 | PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} 31 | TEST_PYPI_TOKEN: ${{ secrets.TEST_PYPI_TOKEN }} 32 | -------------------------------------------------------------------------------- /tests/keys/test_error.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from coincurve.utils import GROUP_ORDER_INT 3 | 4 | from ecies.config import EllipticCurve 5 | from ecies.keys import PrivateKey 6 | 7 | 8 | def test_group_order(): 9 | sk1 = PrivateKey("secp256k1", int(1).to_bytes(32, "big")) 10 | sk2 = PrivateKey("secp256k1", (GROUP_ORDER_INT - 1).to_bytes(32, "big")) 11 | assert sk1.multiply(sk2.public_key) == sk2.public_key.to_bytes() 12 | 13 | with pytest.raises(ValueError, match="Invalid secp256k1 secret key"): 14 | PrivateKey("secp256k1", b"\x00" * 32) 15 | 16 | with pytest.raises(ValueError, match="Invalid secp256k1 secret key"): 17 | PrivateKey("secp256k1", (GROUP_ORDER_INT + 1).to_bytes(32, "big")) 18 | 19 | 20 | @pytest.mark.parametrize("curve", ["secp256k1", "x25519", "ed25519"]) 21 | def test_invalid_length(curve: EllipticCurve): 22 | with pytest.raises(ValueError, match=f"Invalid {curve} secret key"): 23 | PrivateKey(curve, b"\x00") 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2025 Weiliang Li 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/keys/test_public.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ecies.keys import PublicKey 4 | 5 | 6 | @pytest.mark.parametrize("curve", ["secp256k1", "x25519", "ed25519"]) 7 | def test_repr(pk1, pk2): 8 | assert eval(repr(pk1)) == pk1 9 | assert eval(repr(pk2)) == pk2 10 | 11 | 12 | @pytest.mark.parametrize("curve", ["secp256k1", "x25519", "ed25519"]) 13 | def test_equal(pk1, pk2, random_sk, random_pk): 14 | assert pk1 != pk2 15 | assert pk1 == pk1 and pk2 == pk2 16 | assert pk1 != "" and "" != pk1 17 | 18 | assert random_pk != random_sk.public_key 19 | 20 | 21 | @pytest.mark.parametrize("curve", ["secp256k1", "x25519", "ed25519"]) 22 | def test_bytes(curve, pk1, pk2, random_pk): 23 | assert pk1 == PublicKey(curve, pk1.to_bytes()) 24 | assert pk2 == PublicKey(curve, pk2.to_bytes()) 25 | 26 | assert random_pk == PublicKey(curve, random_pk.to_bytes()) 27 | 28 | 29 | @pytest.mark.parametrize("curve", ["secp256k1", "x25519", "ed25519"]) 30 | def test_hex(curve, pk1, pk2, random_pk): 31 | assert pk1 == PublicKey.from_hex(curve, pk1.to_hex()) 32 | assert pk2 == PublicKey.from_hex(curve, pk2.to_hex()) 33 | 34 | assert random_pk == PublicKey.from_hex(curve, random_pk.to_hex()) 35 | -------------------------------------------------------------------------------- /scripts/ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # test cli 4 | poetry run eciespy -h 5 | 6 | # test encrypt/decrypt 7 | ## secp256k1 8 | poetry run eciespy -g 9 | echo '0x95d3c5e483e9b1d4f5fc8e79b2deaf51362980de62dbb082a9a4257eef653d7d' > sk 10 | echo '0x98afe4f150642cd05cc9d2fa36458ce0a58567daeaf5fde7333ba9b403011140a4e28911fcf83ab1f457a30b4959efc4b9306f514a4c3711a16a80e3b47eb58b' > pk 11 | echo 'hello world 🌍' | poetry run eciespy -e -k pk -O out 12 | poetry run eciespy -d -k sk -D out 13 | 14 | ## x25519 15 | poetry run eciespy -g -c x25519 16 | echo '0xe2bfe58d930bd4cb367498fdf5f3df33967d03a691b565360f6265604503748e' > sk 17 | echo '0x94cb092f6b68b4df6bbb0d5f3de01f95cb89f25e24d6cf89bba34de71d9da74e' > pk 18 | echo 'hello world (x25519) 🌍' | poetry run eciespy -e -k pk -O out -c x25519 19 | poetry run eciespy -d -k sk -D out -c x25519 20 | 21 | ## ed25519 22 | poetry run eciespy -g -c ed25519 23 | echo '0x94faa19c13ab8dddbd0b0a869473dc35c29790b2dd01923f5aefccfbe3657053' > sk 24 | echo '0x204c4e77a8c506e030f0209fbeca147068c33eef05affd11ea2b04afaaeec74b' > pk 25 | echo 'hello world (ed25519) 🌍' | poetry run eciespy -e -k pk -O out -c ed25519 26 | poetry run eciespy -d -k sk -D out -c ed25519 27 | 28 | # cleanup 29 | rm sk pk out 30 | -------------------------------------------------------------------------------- /ecies/config.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Literal 3 | 4 | from .consts import ( 5 | COMPRESSED_PUBLIC_KEY_SIZE, 6 | CURVE25519_PUBLIC_KEY_SIZE, 7 | UNCOMPRESSED_PUBLIC_KEY_SIZE, 8 | ) 9 | 10 | EllipticCurve = Literal["secp256k1", "x25519", "ed25519"] 11 | SymmetricAlgorithm = Literal["aes-256-gcm", "xchacha20"] 12 | NonceLength = Literal[12, 16] # only for aes-256-gcm, xchacha20 will always be 24 13 | 14 | 15 | @dataclass() 16 | class Config: 17 | elliptic_curve: EllipticCurve = "secp256k1" 18 | is_ephemeral_key_compressed: bool = False 19 | is_hkdf_key_compressed: bool = False 20 | symmetric_algorithm: SymmetricAlgorithm = "aes-256-gcm" 21 | symmetric_nonce_length: NonceLength = 16 22 | 23 | @property 24 | def ephemeral_key_size(self): 25 | if self.elliptic_curve == "secp256k1": 26 | return ( 27 | COMPRESSED_PUBLIC_KEY_SIZE 28 | if self.is_ephemeral_key_compressed 29 | else UNCOMPRESSED_PUBLIC_KEY_SIZE 30 | ) 31 | elif self.elliptic_curve in ("x25519", "ed25519"): 32 | return CURVE25519_PUBLIC_KEY_SIZE 33 | else: 34 | raise NotImplementedError 35 | 36 | 37 | ECIES_CONFIG = Config() 38 | -------------------------------------------------------------------------------- /tests/crypt/test_eth.py: -------------------------------------------------------------------------------- 1 | # TODO: delete 2 | import pytest 3 | 4 | from ecies import decrypt, encrypt 5 | from ecies.utils import ( 6 | decode_hex, 7 | generate_eth_key, 8 | hex2pk, 9 | hex2sk, 10 | sha256, 11 | ) 12 | 13 | eth_keys = pytest.importorskip("eth_keys") 14 | 15 | 16 | @pytest.fixture(scope="session") 17 | def sk(): 18 | return generate_eth_key() 19 | 20 | 21 | def test_elliptic_ok_eth(data, sk): 22 | sk_hex = sk.to_hex() 23 | pk_hex = sk.public_key.to_hex() 24 | assert data == decrypt(sk_hex, encrypt(pk_hex, data)) 25 | assert data == decrypt(decode_hex(sk_hex), encrypt(decode_hex(pk_hex), data)) 26 | 27 | 28 | def test_hex_to_pk(sk): 29 | data = b"0" * 32 30 | data_hash = sha256(data) 31 | cc_sk = hex2sk(sk.to_hex()) 32 | assert sk.sign_msg_hash(data_hash).to_bytes() == cc_sk.sign_recoverable(data) 33 | 34 | pk_hex = sk.public_key.to_hex() 35 | computed_pk = hex2pk(pk_hex) 36 | assert computed_pk == cc_sk.public_key 37 | 38 | 39 | def test_hex_to_sk(sk): 40 | sk_hex = sk.to_hex() 41 | pk_hex = sk.public_key.to_hex() 42 | computed_sk = hex2sk(sk_hex) 43 | assert computed_sk.to_int() == int(sk.to_hex(), 16) 44 | assert computed_sk.public_key.format(False)[1:] == decode_hex(pk_hex) 45 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | pull_request: 8 | branches: [master] 9 | 10 | jobs: 11 | build: 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest, macos-latest, windows-latest] 16 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Install poetry 21 | run: pipx install poetry 22 | 23 | - uses: actions/setup-python@v5 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | cache: poetry 27 | 28 | - run: brew install automake 29 | if: matrix.os == 'macos-latest' 30 | 31 | - run: poetry install --with test -E eth 32 | 33 | - name: Lint 34 | run: poetry run ruff check --fix && poetry run ruff format && poetry run ty check 35 | 36 | - name: Run test 37 | run: poetry run pytest -s --cov=ecies tests --cov-report xml 38 | 39 | - run: ./scripts/ci.sh 40 | 41 | - uses: codecov/codecov-action@v5 42 | if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13' 43 | with: 44 | token: ${{ secrets.CODECOV_TOKEN }} 45 | 46 | - run: poetry build 47 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | 3 | ## 0.4.6 4 | 5 | - Bump dependencies 6 | - Add ed25519 support 7 | - Add x25519/ed25519 for CLI 8 | - Update documentation 9 | 10 | ## 0.4.5 11 | 12 | - Add homemade key pair: `ecies.keys.PrivateKey` and `ecies.keys.PublicKey` 13 | - Deprecate some functions in `ecies.utils` 14 | - Bump dependencies 15 | - Add x25519 support 16 | 17 | ## 0.4.4 18 | 19 | - Make `eth-keys` optional 20 | - Drop Python 3.8 21 | - Refactor `utils` 22 | - Revamp documentation 23 | - Bump dependencies 24 | 25 | ## 0.4.1 ~ 0.4.3 26 | 27 | - Bump dependencies 28 | - Support Python 3.12, 3.13 29 | 30 | ## 0.4.0 31 | 32 | - Drop Python 3.7 33 | - Revamp documentation 34 | - Add configuration and XChaCha20 as an optional encryption backend 35 | 36 | ## 0.3.1 ~ 0.3.13 37 | 38 | - Support Python 3.8, 3.9, 3.10, 3.11 39 | - Drop Python 3.5, 3.6 40 | - Bump dependencies 41 | - Update documentation 42 | 43 | ## 0.3.0 44 | 45 | - API change: use `HKDF-sha256` to derive shared keys instead of `sha256` 46 | 47 | ## 0.2.0 48 | 49 | - API change: `ecies.encrypt` and `ecies.decrypt` now can take both hex `str` and raw `bytes` 50 | - Bump dependencies 51 | - Update documentation 52 | 53 | ## 0.1.1 ~ 0.1.9 54 | 55 | - Bump dependencies 56 | - Update documentation 57 | - Switch to Circle CI 58 | - Change license to MIT 59 | 60 | ## 0.1.0 61 | 62 | - First beta version release 63 | -------------------------------------------------------------------------------- /tests/keys/test_private.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ecies.keys import PrivateKey 4 | 5 | 6 | @pytest.mark.parametrize("curve", ["secp256k1", "x25519", "ed25519"]) 7 | def test_repr(k1, k2): 8 | assert eval(repr(k1)) == k1 9 | assert eval(repr(k2)) == k2 10 | 11 | 12 | @pytest.mark.parametrize("curve", ["secp256k1", "x25519", "ed25519"]) 13 | def test_equal(curve, k1, k2, random_sk): 14 | assert k1 == k1 and k2 == k2 and k1 != k2 15 | assert k1 != "" and "" != k1 16 | 17 | assert random_sk != PrivateKey(curve) 18 | 19 | 20 | @pytest.mark.parametrize("curve", ["secp256k1", "x25519", "ed25519"]) 21 | def test_bytes(curve, k1, k2, random_sk): 22 | assert k1 == PrivateKey(curve, k1.secret) 23 | assert k2 == PrivateKey(curve, k2.secret) 24 | 25 | assert random_sk == PrivateKey(curve, random_sk.secret) 26 | 27 | 28 | @pytest.mark.parametrize("curve", ["secp256k1", "x25519", "ed25519"]) 29 | def test_hex(curve, k1, k2, random_sk): 30 | assert ( 31 | k1.to_hex() 32 | == "0000000000000000000000000000000000000000000000000000000000000002" 33 | ) 34 | assert ( 35 | k2.to_hex() 36 | == "0000000000000000000000000000000000000000000000000000000000000003" 37 | ) 38 | assert k1 == PrivateKey.from_hex(curve, k1.to_hex()) 39 | assert k2 == PrivateKey.from_hex(curve, k2.to_hex()) 40 | 41 | assert random_sk == PrivateKey.from_hex(curve, random_sk.to_hex()) 42 | -------------------------------------------------------------------------------- /ecies/utils/eth.py: -------------------------------------------------------------------------------- 1 | from coincurve.utils import get_valid_secret 2 | from Crypto.Hash import keccak 3 | from typing_extensions import deprecated 4 | 5 | 6 | @deprecated( 7 | "Use `eth_keys.keys.PrivateKey(coincurve.utils.get_valid_secret())` instead" 8 | ) 9 | def generate_eth_key(): 10 | """ 11 | Note: `eth-keys` needs to be installed in advance. 12 | 13 | Generate a random `eth_keys.keys.PrivateKey` 14 | 15 | Returns 16 | ------- 17 | eth_keys.keys.PrivateKey 18 | An ethereum flavored secp256k1 key 19 | 20 | """ 21 | from eth_keys import keys # type:ignore 22 | 23 | return keys.PrivateKey(get_valid_secret()) 24 | 25 | 26 | # for cli only 27 | def to_eth_address(pk_bytes: bytes) -> str: 28 | if len(pk_bytes) != 64: 29 | raise NotImplementedError 30 | return encode_checksum(keccak256(pk_bytes)[-20:].hex()) 31 | 32 | 33 | # private below 34 | def encode_checksum(raw_address: str) -> str: 35 | # https://github.com/ethereum/ercs/blob/master/ERCS/erc-55.md 36 | address = raw_address.lower().replace("0x", "") 37 | address_hash = keccak256(address.encode()).hex() 38 | 39 | res = [] 40 | for a, h in zip(address, address_hash): 41 | if int(h, 16) >= 8: 42 | res.append(a.upper()) 43 | else: 44 | res.append(a) 45 | 46 | return "0x" + "".join(res) 47 | 48 | 49 | def keccak256(data: bytes) -> bytes: 50 | h = keccak.new(data=data, digest_bits=256) 51 | return h.digest() 52 | -------------------------------------------------------------------------------- /tests/utils/test_symmetric.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | from ecies.config import SymmetricAlgorithm 6 | from ecies.utils import decode_hex, sym_decrypt, sym_encrypt 7 | 8 | 9 | def __check_symmetric_random( 10 | data: bytes, algorithm: SymmetricAlgorithm = "aes-256-gcm" 11 | ): 12 | key = os.urandom(32) 13 | assert sym_decrypt(key, sym_encrypt(key, data, algorithm), algorithm) == data 14 | 15 | 16 | @pytest.mark.parametrize("algorithm", ["aes-256-gcm", "xchacha20"]) 17 | def test_symmetric_random(data, algorithm): 18 | __check_symmetric_random(data, algorithm) 19 | 20 | 21 | @pytest.mark.parametrize("algorithm", ["aes-256-gcm", "xchacha20"]) 22 | def test_symmetric_big(algorithm, big_data): 23 | __check_symmetric_random(big_data, algorithm) 24 | 25 | 26 | @pytest.mark.parametrize( 27 | "key,nonce,tag,encrypted,algorithm", 28 | [ 29 | ( 30 | "0000000000000000000000000000000000000000000000000000000000000000", 31 | "0xf3e1ba810d2c8900b11312b7c725565f", 32 | "0Xec3b71e17c11dbe31484da9450edcf6c", 33 | "02d2ffed93b856f148b9", 34 | "aes-256-gcm", 35 | ), 36 | ( 37 | "27bd6ec46292a3b421cdaf8a3f0ca759cbc67bcbe7c5855aa0d1e0700fd0e828", 38 | "0xfbd5dd10431af533c403d6f4fa629931e5f31872d2f7e7b6", 39 | "0X5b5ccc27324af03b7ca92dd067ad6eb5", 40 | "aa0664f3c00a09d098bf", 41 | "xchacha20", 42 | ), 43 | ], 44 | ) 45 | def test_known(key, nonce, tag, encrypted, algorithm): 46 | key = decode_hex(key) 47 | nonce = decode_hex(nonce) 48 | tag = decode_hex(tag) 49 | encrypted = decode_hex(encrypted) 50 | 51 | data = b"".join([nonce, tag, encrypted]) 52 | assert b"helloworld" == sym_decrypt(key, data, algorithm) 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | .vscode/ 107 | .DS_Store 108 | 109 | sk 110 | pk 111 | out 112 | -------------------------------------------------------------------------------- /tests/crypt/test_deprecated.py: -------------------------------------------------------------------------------- 1 | # TODO: delete 2 | import pytest 3 | from coincurve import PrivateKey 4 | 5 | from ecies import ECIES_CONFIG, decrypt, encrypt 6 | from ecies.utils import decode_hex, generate_key 7 | 8 | 9 | def __check(data: bytes, k: PrivateKey, compressed: bool = False): 10 | sk_hex = k.to_hex() 11 | pk_hex = k.public_key.format(compressed).hex() 12 | assert data == decrypt(sk_hex, encrypt(pk_hex, data)) 13 | assert data == decrypt(decode_hex(sk_hex), encrypt(decode_hex(pk_hex), data)) 14 | 15 | 16 | @pytest.mark.parametrize( 17 | "key,compressed", 18 | [ 19 | (generate_key(), False), 20 | (generate_key(), True), 21 | ], 22 | ) 23 | def test_elliptic_ok(data, key: PrivateKey, compressed: bool): 24 | __check(data, key, compressed) 25 | 26 | 27 | def test_elliptic_error(data): 28 | with pytest.raises(TypeError): 29 | encrypt(1, data) # type: ignore 30 | 31 | k = generate_key() 32 | pk_hex = k.public_key.format(True).hex() 33 | 34 | with pytest.raises(TypeError): 35 | decrypt(1, encrypt(bytes.fromhex(pk_hex), data)) # type: ignore 36 | 37 | 38 | def test_hkdf_config(data): 39 | ECIES_CONFIG.is_hkdf_key_compressed = True 40 | __check(data, generate_key()) 41 | ECIES_CONFIG.is_hkdf_key_compressed = False 42 | 43 | 44 | def test_ephemeral_key_config(data): 45 | ECIES_CONFIG.is_ephemeral_key_compressed = True 46 | __check(data, generate_key()) 47 | ECIES_CONFIG.is_ephemeral_key_compressed = False 48 | 49 | 50 | def test_aes_nonce_config(data): 51 | ECIES_CONFIG.symmetric_nonce_length = 12 52 | __check(data, generate_key()) 53 | ECIES_CONFIG.symmetric_nonce_length = 16 54 | 55 | 56 | def test_sym_config(data): 57 | ECIES_CONFIG.symmetric_algorithm = "xchacha20" 58 | __check(data, generate_key()) 59 | ECIES_CONFIG.symmetric_algorithm = "aes-256-gcm" 60 | -------------------------------------------------------------------------------- /tests/crypt/test_random.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ecies import ECIES_CONFIG, decrypt, encrypt 4 | from ecies.config import EllipticCurve 5 | from ecies.keys import PrivateKey 6 | from ecies.utils import decode_hex 7 | 8 | from .helper import config_manager 9 | 10 | 11 | def __check_random(data: bytes, k: PrivateKey, compressed: bool = False): 12 | sk_hex = k.to_hex() 13 | pk_hex = k.public_key.to_bytes(compressed).hex() 14 | assert data == decrypt(sk_hex, encrypt(pk_hex, data)) 15 | assert data == decrypt(decode_hex(sk_hex), encrypt(decode_hex(pk_hex), data)) 16 | 17 | 18 | @pytest.mark.parametrize( 19 | "curve,compressed", 20 | [ 21 | ("secp256k1", False), 22 | ("secp256k1", True), 23 | ("x25519", False), 24 | ("x25519", True), 25 | ("ed25519", False), 26 | ("ed25519", True), 27 | ], 28 | ) 29 | def test_elliptic_ok(data, curve: EllipticCurve, compressed: bool): 30 | with config_manager(curve): 31 | __check_random(data, PrivateKey(curve), compressed) 32 | 33 | 34 | @pytest.mark.parametrize("curve", ["secp256k1", "x25519", "ed25519"]) 35 | def test_hkdf_config(curve, data): 36 | ECIES_CONFIG.is_hkdf_key_compressed = True 37 | with config_manager(curve): 38 | __check_random(data, PrivateKey(curve)) 39 | ECIES_CONFIG.is_hkdf_key_compressed = False 40 | 41 | 42 | @pytest.mark.parametrize("curve", ["secp256k1", "x25519", "ed25519"]) 43 | def test_ephemeral_key_config(curve, data): 44 | ECIES_CONFIG.is_ephemeral_key_compressed = True 45 | with config_manager(curve): 46 | __check_random(data, PrivateKey(curve)) 47 | ECIES_CONFIG.is_ephemeral_key_compressed = False 48 | 49 | 50 | @pytest.mark.parametrize("curve", ["secp256k1", "x25519", "ed25519"]) 51 | def test_aes_nonce_config(curve, data): 52 | ECIES_CONFIG.symmetric_nonce_length = 12 53 | with config_manager(curve): 54 | __check_random(data, PrivateKey(curve)) 55 | ECIES_CONFIG.symmetric_nonce_length = 16 56 | -------------------------------------------------------------------------------- /ecies/keys/public.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from ..config import EllipticCurve 6 | from ..utils import decode_hex, derive_key 7 | from .helper import convert_public_key 8 | 9 | if TYPE_CHECKING: 10 | from .private import PrivateKey 11 | 12 | 13 | class PublicKey: 14 | def __init__(self, curve: EllipticCurve, data: bytes): 15 | self._curve = curve 16 | compressed = convert_public_key(curve, data, True) 17 | uncompressed = convert_public_key(curve, data, False) 18 | self._data = compressed 19 | self._data_uncompressed = ( 20 | uncompressed if len(compressed) != len(uncompressed) else b"" 21 | ) 22 | 23 | def __repr__(self): 24 | return f"PublicKey('{self._curve}', {self._data})" 25 | 26 | def __eq__(self, value): 27 | return self._data == value._data if isinstance(value, PublicKey) else False 28 | 29 | @classmethod 30 | def from_hex(cls, curve: EllipticCurve, pk_hex: str) -> PublicKey: 31 | """ 32 | For secp256k1, `pk_hex` can be 33(compressed)/65(uncompressed)/64(ethereum) bytes 33 | """ 34 | return cls(curve, decode_hex(pk_hex)) 35 | 36 | def to_hex(self, compressed: bool = False) -> str: 37 | """ 38 | For secp256k1, `pk_hex` can be 33(compressed)/65(uncompressed) bytes 39 | """ 40 | return self.to_bytes(compressed).hex() 41 | 42 | def to_bytes(self, compressed: bool = False) -> bytes: 43 | """ 44 | For secp256k1, return uncompressed public key (65 bytes) by default 45 | """ 46 | if not compressed and self._data_uncompressed: 47 | return self._data_uncompressed 48 | return self._data 49 | 50 | def decapsulate(self, sk: PrivateKey, compressed: bool = False) -> bytes: 51 | sender_point = self.to_bytes(compressed) 52 | shared_point = sk.multiply(self, compressed) 53 | return derive_key(sender_point + shared_point) 54 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "eciespy" 3 | packages = [ 4 | {include = "ecies"}, 5 | ] 6 | version = "0.4.7" 7 | # docs 8 | authors = ["Weiliang Li "] 9 | description = "Elliptic Curve Integrated Encryption Scheme for secp256k1/curve25519 in Python" 10 | license = "MIT" 11 | maintainers = ["Weiliang Li "] 12 | readme = "README.md" 13 | repository = "https://github.com/ecies/py" 14 | # tags 15 | classifiers = [ 16 | "Development Status :: 4 - Beta", 17 | "Intended Audience :: Developers", 18 | "Natural Language :: English", 19 | "Programming Language :: Python :: Implementation :: CPython", 20 | "Operating System :: OS Independent", 21 | "Topic :: Security :: Cryptography", 22 | ] 23 | keywords = [ 24 | "secp256k1", 25 | "crypto", 26 | "elliptic curves", 27 | "ecies", 28 | "bitcoin", 29 | "ethereum", 30 | "cryptocurrency", 31 | ] 32 | # package data 33 | include = ["ecies/py.typed"] 34 | 35 | [tool.poetry.dependencies] 36 | python = "^3.9" 37 | 38 | # for @deprecated 39 | typing-extensions = ">=4.9.0" 40 | 41 | # 3rd party 42 | coincurve = ">=13,<22" 43 | pycryptodome = ">=3.21.0" 44 | 45 | # optional 46 | eth-keys = {version = ">=0.4,<0.8", optional = true} 47 | 48 | [tool.poetry.extras] 49 | eth = ["eth-keys"] 50 | 51 | [tool.poetry.group.dev.dependencies] 52 | ipython = {version = "^9.7.0", python = "^3.11"} 53 | ruff = "^0.14.5" 54 | ty = "^0.0.1a27" 55 | 56 | eth-typing = "^5.2.1" 57 | 58 | [tool.poetry.group.test.dependencies] 59 | pytest = "^8.4.2" 60 | pytest-cov = "^6.3.0" 61 | 62 | [tool.poetry.scripts] 63 | eciespy = "ecies.__main__:main" 64 | 65 | [build-system] 66 | build-backend = "poetry.core.masonry.api" 67 | requires = ["poetry-core>=1.0.0"] 68 | 69 | [tool.pytest.ini_options] 70 | addopts = "--doctest-modules --cov=ecies --cov-report term:skip-covered" 71 | 72 | [tool.coverage.run] 73 | omit = ["ecies/__main__.py"] 74 | 75 | [tool.coverage.report] 76 | exclude_also = ["if TYPE_CHECKING:", "raise NotImplementedError"] 77 | 78 | [tool.ty.rules] 79 | deprecated = "ignore" 80 | -------------------------------------------------------------------------------- /ecies/keys/private.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional 4 | 5 | from ..config import EllipticCurve 6 | from ..utils import decode_hex, derive_key 7 | from .helper import ( 8 | bytes_to_int, 9 | get_public_key, 10 | get_shared_point, 11 | get_valid_secret, 12 | is_valid_secret, 13 | ) 14 | from .public import PublicKey 15 | 16 | 17 | class PrivateKey: 18 | def __init__(self, curve: EllipticCurve, secret: Optional[bytes] = None): 19 | self._curve: EllipticCurve = curve 20 | if not secret: 21 | self._secret = get_valid_secret(self._curve) 22 | elif is_valid_secret(self._curve, secret): 23 | self._secret = secret 24 | else: 25 | raise ValueError(f"Invalid {self._curve} secret key") 26 | self._public_key = PublicKey( 27 | self._curve, get_public_key(self._curve, self._secret) 28 | ) 29 | 30 | def __repr__(self): 31 | return f"PrivateKey('{self._curve}', {self._secret})" 32 | 33 | def __eq__(self, value): 34 | return self._secret == value._secret if isinstance(value, PrivateKey) else False 35 | 36 | @classmethod 37 | def from_hex(cls, curve: EllipticCurve, sk_hex: str) -> PrivateKey: 38 | """ 39 | For secp256k1, `sk_hex` can only be 32 bytes. `0x` prefix is optional. 40 | """ 41 | return cls(curve, decode_hex(sk_hex)) 42 | 43 | def to_hex(self) -> str: 44 | return self._secret.hex() 45 | 46 | @property 47 | def secret(self) -> bytes: 48 | return self._secret 49 | 50 | @property 51 | def public_key(self) -> PublicKey: 52 | return self._public_key 53 | 54 | def to_int(self) -> int: 55 | return bytes_to_int(self._secret) 56 | 57 | def multiply(self, pk: PublicKey, compressed: bool = False) -> bytes: 58 | return get_shared_point(self._curve, self._secret, pk.to_bytes(), compressed) 59 | 60 | def encapsulate(self, pk: PublicKey, compressed: bool = False) -> bytes: 61 | sender_point = self.public_key.to_bytes(compressed) 62 | shared_point = self.multiply(pk, compressed) 63 | return derive_key(sender_point + shared_point) 64 | -------------------------------------------------------------------------------- /tests/crypt/test_known.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ecies import ECIES_CONFIG, decrypt, encrypt 4 | from ecies.utils import decode_hex 5 | 6 | from .helper import config_manager 7 | 8 | 9 | def __check_known(sk: str, pk: str, data: bytes, encrypted: bytes): 10 | assert encrypt(pk, data) != encrypted 11 | assert decrypt(sk, encrypted) == data 12 | 13 | 14 | @pytest.mark.parametrize("curve", ["secp256k1", "x25519", "ed25519"]) 15 | def test_sym_config(curve, data): 16 | ECIES_CONFIG.symmetric_algorithm = "xchacha20" 17 | if curve == "secp256k1": 18 | sk = "0000000000000000000000000000000000000000000000000000000000000002" 19 | pk = "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" 20 | encrypted = decode_hex( 21 | "04e314abc14398e07974cd50221b682ed5f0629e977345fc03e2047208ee6e279f" 22 | "fb2a6942878d3798c968d89e59c999e082b0598d1b641968c48c8d47c570210d0a" 23 | "b1ade95eeca1080c45366562f9983faa423ee3fd3260757053d5843c5f453e1ee6" 24 | "bb955c8e5d4aee8572139357a091909357a8931b" 25 | ) 26 | elif curve == "x25519": 27 | sk = "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a" 28 | pk = "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a" 29 | encrypted = decode_hex( 30 | "cfff9c146116355d0e7ce81df984b4d64c5e5c9c055fbfda0ff8169e11d05e12ed" 31 | "f025069032adf3e16b763d886f3812bc8f1902fd29204ed3b6a2ea4e52a01dc440" 32 | "72ed1635aefbad1571bd5b972a7304ba25301f12" 33 | ) 34 | elif curve == "ed25519": 35 | sk = "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" 36 | pk = "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" 37 | encrypted = decode_hex( 38 | "329c94d4f7b282e885626302c1383a4f60a0d1ad34ca46b6c0d128404376afb5cf" 39 | "6d42a1f70997f4f2af4926e278259fb5b67ac9c30b5e50a311d4a890378926881d" 40 | "f1d3e0556c99ff7e0ed8b0d14f1e9536c83a282f" 41 | ) 42 | else: 43 | raise NotImplementedError 44 | 45 | with config_manager(curve): 46 | __check_known(sk, pk, data, encrypted) 47 | 48 | ECIES_CONFIG.symmetric_algorithm = "aes-256-gcm" 49 | -------------------------------------------------------------------------------- /ecies/utils/elliptic.py: -------------------------------------------------------------------------------- 1 | from coincurve import PrivateKey, PublicKey 2 | from coincurve.utils import get_valid_secret 3 | from typing_extensions import deprecated 4 | 5 | from ..consts import ETH_PUBLIC_KEY_LENGTH 6 | from .hash import derive_key 7 | from .hex import decode_hex 8 | 9 | 10 | @deprecated("Use `ecies.keys.PrivateKey` instead") 11 | def generate_key() -> PrivateKey: 12 | """ 13 | Generate a random coincurve.PrivateKey` 14 | 15 | Returns 16 | ------- 17 | coincurve.PrivateKey 18 | A secp256k1 key 19 | """ 20 | return PrivateKey(get_valid_secret()) 21 | 22 | 23 | @deprecated("Use `ecies.keys.PublicKey.from_hex` instead") 24 | def hex2pk(pk_hex: str) -> PublicKey: 25 | """ 26 | Convert public key hex to `coincurve.PublicKey` 27 | The hex should be 65 bytes (uncompressed) or 33 bytes (compressed), but ethereum public key has 64 bytes. 28 | `0x04` will be appended if it's an ethereum public key. 29 | 30 | Parameters 31 | ---------- 32 | pk_hex: str 33 | Public key hex string 34 | 35 | Returns 36 | ------- 37 | coincurve.PublicKey 38 | A secp256k1 public key 39 | 40 | """ 41 | return PublicKey(convert_eth_public_key(decode_hex(pk_hex))) 42 | 43 | 44 | @deprecated("Use `ecies.keys.PrivateKey.from_hex` instead") 45 | def hex2sk(sk_hex: str) -> PrivateKey: 46 | """ 47 | Convert ethereum hex to `coincurve.PrivateKey` 48 | 49 | Parameters 50 | ---------- 51 | sk_hex: str 52 | Private key hex string 53 | 54 | Returns 55 | ------- 56 | coincurve.PrivateKey 57 | A secp256k1 private key 58 | 59 | """ 60 | return PrivateKey(decode_hex(sk_hex)) 61 | 62 | 63 | # private below 64 | @deprecated("Use `ecies.keys.PrivateKey.encapsulate` instead") 65 | def encapsulate( 66 | private_key: PrivateKey, peer_public_key: PublicKey, is_compressed: bool = False 67 | ) -> bytes: 68 | shared_point = peer_public_key.multiply(private_key.secret) 69 | master = private_key.public_key.format(is_compressed) + shared_point.format( 70 | is_compressed 71 | ) 72 | return derive_key(master) 73 | 74 | 75 | @deprecated("Use `ecies.keys.PrivateKey.decapsulate` instead") 76 | def decapsulate( 77 | public_key: PublicKey, peer_private_key: PrivateKey, is_compressed: bool = False 78 | ) -> bytes: 79 | shared_point = public_key.multiply(peer_private_key.secret) 80 | master = public_key.format(is_compressed) + shared_point.format(is_compressed) 81 | return derive_key(master) 82 | 83 | 84 | def convert_eth_public_key(data: bytes): 85 | if len(data) == ETH_PUBLIC_KEY_LENGTH: 86 | data = b"\x04" + data 87 | return data 88 | -------------------------------------------------------------------------------- /ecies/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from .config import ECIES_CONFIG, Config 4 | from .keys import PrivateKey, PublicKey 5 | from .utils import sym_decrypt, sym_encrypt 6 | 7 | __all__ = ["encrypt", "decrypt", "ECIES_CONFIG"] 8 | 9 | 10 | def encrypt( 11 | receiver_pk: Union[str, bytes], data: bytes, config: Config = ECIES_CONFIG 12 | ) -> bytes: 13 | """ 14 | Encrypt with receiver's secp256k1 public key 15 | 16 | Parameters 17 | ---------- 18 | receiver_pk: Union[str, bytes] 19 | Receiver's public key (hex `str` or `bytes`) 20 | data: bytes 21 | Data to encrypt 22 | config: Config 23 | Optional configuration object 24 | 25 | Returns 26 | ------- 27 | bytes 28 | Encrypted data 29 | """ 30 | curve = config.elliptic_curve 31 | if isinstance(receiver_pk, str): 32 | _receiver_pk = PublicKey.from_hex(curve, receiver_pk) 33 | elif isinstance(receiver_pk, bytes): 34 | _receiver_pk = PublicKey(curve, receiver_pk) 35 | else: 36 | raise TypeError("Invalid public key type") 37 | 38 | ephemeral_sk = PrivateKey(curve) 39 | ephemeral_pk = ephemeral_sk.public_key.to_bytes(config.is_ephemeral_key_compressed) 40 | 41 | sym_key = ephemeral_sk.encapsulate(_receiver_pk, config.is_hkdf_key_compressed) 42 | encrypted = sym_encrypt( 43 | sym_key, data, config.symmetric_algorithm, config.symmetric_nonce_length 44 | ) 45 | return ephemeral_pk + encrypted 46 | 47 | 48 | def decrypt( 49 | receiver_sk: Union[str, bytes], data: bytes, config: Config = ECIES_CONFIG 50 | ) -> bytes: 51 | """ 52 | Decrypt with receiver's secp256k1 private key 53 | 54 | Parameters 55 | ---------- 56 | receiver_sk: Union[str, bytes] 57 | Receiver's private key (hex `str` or `bytes`) 58 | data: bytes 59 | Data to decrypt 60 | config: Config 61 | Optional configuration object 62 | 63 | Returns 64 | ------- 65 | bytes 66 | Plain text 67 | """ 68 | curve = config.elliptic_curve 69 | if isinstance(receiver_sk, str): 70 | _receiver_sk = PrivateKey.from_hex(curve, receiver_sk) 71 | elif isinstance(receiver_sk, bytes): 72 | _receiver_sk = PrivateKey(curve, receiver_sk) 73 | else: 74 | raise TypeError("Invalid secret key type") 75 | 76 | key_size = config.ephemeral_key_size 77 | ephemeral_pk, encrypted = PublicKey(curve, data[0:key_size]), data[key_size:] 78 | 79 | sym_key = ephemeral_pk.decapsulate(_receiver_sk, config.is_hkdf_key_compressed) 80 | return sym_decrypt( 81 | sym_key, encrypted, config.symmetric_algorithm, config.symmetric_nonce_length 82 | ) 83 | -------------------------------------------------------------------------------- /ecies/keys/helper.py: -------------------------------------------------------------------------------- 1 | from coincurve import PublicKey 2 | from coincurve.utils import GROUP_ORDER_INT 3 | from Crypto.Protocol.DH import ( 4 | import_x25519_private_key, 5 | import_x25519_public_key, 6 | key_agreement, 7 | ) 8 | from Crypto.PublicKey.ECC import EccKey 9 | from Crypto.Random import get_random_bytes 10 | from Crypto.Signature.eddsa import import_private_key as import_ed25519_private_key 11 | from Crypto.Signature.eddsa import import_public_key as import_ed25519_public_key 12 | 13 | from ..config import EllipticCurve 14 | from ..consts import ETH_PUBLIC_KEY_LENGTH, SECRET_KEY_SIZE 15 | 16 | 17 | def is_valid_secret(curve: EllipticCurve, secret: bytes) -> bool: 18 | if len(secret) != SECRET_KEY_SIZE: 19 | return False 20 | if curve == "secp256k1": 21 | return 0 < bytes_to_int(secret) < GROUP_ORDER_INT 22 | elif curve == "x25519": 23 | return True 24 | elif curve == "ed25519": 25 | return True 26 | else: 27 | raise NotImplementedError 28 | 29 | 30 | def get_valid_secret(curve: EllipticCurve) -> bytes: 31 | while True: 32 | key = get_random_bytes(SECRET_KEY_SIZE) 33 | if is_valid_secret(curve, key): 34 | return key 35 | 36 | 37 | def get_public_key( 38 | curve: EllipticCurve, secret: bytes, compressed: bool = False 39 | ) -> bytes: 40 | if curve == "secp256k1": 41 | return PublicKey.from_secret(secret).format(compressed) 42 | elif curve == "x25519": 43 | return import_x25519_private_key(secret).public_key().export_key(format="raw") 44 | elif curve == "ed25519": 45 | return import_ed25519_private_key(secret).public_key().export_key(format="raw") 46 | else: 47 | raise NotImplementedError 48 | 49 | 50 | def get_shared_point( 51 | curve: EllipticCurve, sk: bytes, pk: bytes, compressed: bool = False 52 | ) -> bytes: 53 | if curve == "secp256k1": 54 | return PublicKey(pk).multiply(sk).format(compressed) 55 | elif curve == "x25519": 56 | return key_agreement( 57 | kdf=lambda x: x, 58 | static_priv=import_x25519_private_key(sk), 59 | eph_pub=import_x25519_public_key(pk), 60 | ) 61 | elif curve == "ed25519": 62 | shared_point = ( 63 | import_ed25519_public_key(pk).pointQ * import_ed25519_private_key(sk).d 64 | ) 65 | return EccKey(curve=curve, point=shared_point).export_key(format="raw") 66 | else: 67 | raise NotImplementedError 68 | 69 | 70 | def convert_public_key( 71 | curve: EllipticCurve, data: bytes, compressed: bool = False 72 | ) -> bytes: 73 | if curve == "secp256k1": 74 | # handle 33/65/64 bytes 75 | return PublicKey(pad_eth_public_key(data)).format(compressed) 76 | elif curve == "x25519": 77 | return import_x25519_public_key(data).export_key(format="raw") 78 | elif curve == "ed25519": 79 | return import_ed25519_public_key(data).export_key(format="raw") 80 | else: 81 | raise NotImplementedError 82 | 83 | 84 | # private below 85 | def bytes_to_int(data: bytes) -> int: 86 | return int.from_bytes(data, "big") 87 | 88 | 89 | def pad_eth_public_key(data: bytes): 90 | if len(data) == ETH_PUBLIC_KEY_LENGTH: 91 | data = b"\x04" + data 92 | return data 93 | -------------------------------------------------------------------------------- /tests/keys/test_known.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ecies.config import EllipticCurve 4 | from ecies.keys import PrivateKey, PublicKey 5 | 6 | 7 | @pytest.mark.parametrize("curve", ["secp256k1", "x25519", "ed25519"]) 8 | def test_known(curve: EllipticCurve): 9 | if curve == "secp256k1": 10 | sk = "5b5b1a0ff51e4350badd6f58d9e6fa6f57fbdbde6079d12901770dda3b803081" 11 | pk = "048e41409f2e109f2d704f0afd15d1ab53935fd443729913a7e8536b4cef8cf5773d4db7bbd99e9ed64595e24a251c9836f35d4c9842132443c17f6d501b3410d2" 12 | elif curve == "x25519": 13 | sk = "a8abababababababababababababababababababababababababababababab6b" 14 | pk = "e3712d851a0e5d79b831c5e34ab22b41a198171de209b8b8faca23a11c624859" 15 | elif curve == "ed25519": 16 | sk = "0000000000000000000000000000000000000000000000000000000000000000" 17 | pk = "3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29" 18 | else: 19 | raise NotImplementedError 20 | 21 | assert PrivateKey.from_hex(curve, sk).public_key == PublicKey.from_hex(curve, pk) 22 | 23 | 24 | @pytest.mark.parametrize("curve", ["secp256k1", "x25519", "ed25519"]) 25 | def test_multiply(curve: EllipticCurve): 26 | if curve == "secp256k1": 27 | sk = "0000000000000000000000000000000000000000000000000000000000000003" 28 | peer_pk = "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" 29 | shared = "03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556" 30 | elif curve == "x25519": 31 | sk = "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a" 32 | peer_pk = "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f" 33 | shared = "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742" 34 | elif curve == "ed25519": 35 | sk = "0000000000000000000000000000000000000000000000000000000000000000" 36 | peer_pk = "4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29" 37 | shared = "79a82a4ed2cbf9cab6afbf353df0a225b58642c0c7b3760a99856bf01785817f" 38 | else: 39 | raise NotImplementedError 40 | 41 | assert PrivateKey.from_hex(curve, sk).multiply( 42 | PublicKey.from_hex(curve, peer_pk), compressed=True 43 | ) == bytes.fromhex(shared) 44 | 45 | 46 | @pytest.mark.parametrize("curve", ["secp256k1", "x25519", "ed25519"]) 47 | def test_encapsulate_decapsulate(curve, k1, k2): 48 | assert k1.to_int() == 2 49 | assert k2.to_int() == 3 50 | 51 | assert k1.encapsulate(k2.public_key) == k1.public_key.decapsulate(k2) 52 | assert k1.encapsulate(k2.public_key, True) == k1.public_key.decapsulate(k2, True) 53 | 54 | if curve == "secp256k1": 55 | known_shared_secret = ( 56 | "6f982d63e8590c9d9b5b4c1959ff80315d772edd8f60287c9361d548d5200f82" 57 | ) 58 | known_shared_secret_compressed = ( 59 | "b192b226edb3f02da11ef9c6ce4afe1c7e40be304e05ae3b988f4834b1cb6c69" 60 | ) 61 | elif curve == "x25519": 62 | known_shared_secret = ( 63 | "d8f3f4d3ed301a58dd1309c372cfd147ad881dc44f495948b3e47c4e07114d0c" 64 | ) 65 | known_shared_secret_compressed = known_shared_secret 66 | elif curve == "ed25519": 67 | known_shared_secret = ( 68 | "0c39bd5bbeaa991f10dfb399c1d326a1280812a53ba143a5edae0a8d737c45ca" 69 | ) 70 | known_shared_secret_compressed = known_shared_secret 71 | else: 72 | raise NotImplementedError 73 | 74 | assert k1.encapsulate(k2.public_key).hex() == known_shared_secret 75 | assert k1.encapsulate(k2.public_key, True).hex() == known_shared_secret_compressed 76 | -------------------------------------------------------------------------------- /ecies/utils/symmetric.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from Crypto.Cipher import AES, ChaCha20_Poly1305 4 | 5 | from ..config import NonceLength, SymmetricAlgorithm 6 | from ..consts import AEAD_TAG_LENGTH, XCHACHA20_NONCE_LENGTH 7 | 8 | 9 | def sym_encrypt( 10 | key: bytes, 11 | plain_text: bytes, 12 | algorithm: SymmetricAlgorithm = "aes-256-gcm", 13 | nonce_length: NonceLength = 16, 14 | ) -> bytes: 15 | """ 16 | Symmetric encryption. AES-256-GCM or XChaCha20-Poly1305. 17 | 18 | Nonce may be 12/16 bytes on AES, 24 bytes on XChaCha. Default is AES-256-GCM with 16 bytes nonce. 19 | 20 | Parameters 21 | ---------- 22 | key: bytes 23 | Symmetric encryption session key, which derived from two secp256k1 keys 24 | plain_text: bytes 25 | Plain text to encrypt 26 | 27 | Returns 28 | ------- 29 | bytes 30 | nonce + tag(16 bytes) + encrypted data 31 | """ 32 | if algorithm == "aes-256-gcm": 33 | nonce = os.urandom(nonce_length) 34 | aes_cipher = AES.new(key, AES.MODE_GCM, nonce) 35 | encrypted, tag = aes_cipher.encrypt_and_digest(plain_text) 36 | elif algorithm == "xchacha20": 37 | nonce = os.urandom(XCHACHA20_NONCE_LENGTH) 38 | chacha_cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce) 39 | encrypted, tag = chacha_cipher.encrypt_and_digest(plain_text) 40 | else: 41 | raise NotImplementedError 42 | 43 | cipher_text = bytearray() 44 | cipher_text.extend(nonce) 45 | cipher_text.extend(tag) 46 | cipher_text.extend(encrypted) 47 | return bytes(cipher_text) 48 | 49 | 50 | def sym_decrypt( 51 | key: bytes, 52 | cipher_text: bytes, 53 | algorithm: SymmetricAlgorithm = "aes-256-gcm", 54 | nonce_length: NonceLength = 16, 55 | ) -> bytes: 56 | """ 57 | AES-GCM decryption. AES-256-GCM or XChaCha20-Poly1305. 58 | 59 | Parameters 60 | ---------- 61 | key: bytes 62 | Symmetric encryption session key, which derived from two secp256k1 keys 63 | cipher_text: bytes 64 | Encrypted text: 65 | nonce + tag(16 bytes) + encrypted data 66 | 67 | Returns 68 | ------- 69 | bytes 70 | Plain text 71 | 72 | >>> from coincurve.utils import get_valid_secret 73 | >>> data = b'this is test data' 74 | >>> key = get_valid_secret() 75 | >>> sym_decrypt(key, sym_encrypt(key, data)) == data 76 | True 77 | >>> import os 78 | >>> key = os.urandom(32) 79 | >>> sym_decrypt(key, sym_encrypt(key, data)) == data 80 | True 81 | """ 82 | 83 | # NOTE 84 | # pycryptodome's aes gcm takes nonce as iv 85 | # but actually nonce (12 bytes) should be used to generate iv (16 bytes) and iv should be sequential 86 | # See https://crypto.stackexchange.com/a/71219 87 | # You can configure to use 12 bytes nonce by setting `ECIES_CONFIG.symmetric_nonce_length = 12` 88 | # If it's 12 bytes, the nonce can be incremented by 1 for each encryption 89 | # If it's 16 bytes, the nonce will be used to hash, so it's meaningless to increment 90 | 91 | if algorithm == "aes-256-gcm": 92 | nonce, tag, ciphered_data = __split_cipher_text(cipher_text, nonce_length) 93 | aes_cipher = AES.new(key, AES.MODE_GCM, nonce) 94 | return aes_cipher.decrypt_and_verify(ciphered_data, tag) 95 | elif algorithm == "xchacha20": 96 | nonce, tag, ciphered_data = __split_cipher_text( 97 | cipher_text, XCHACHA20_NONCE_LENGTH 98 | ) 99 | xchacha_cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce) 100 | return xchacha_cipher.decrypt_and_verify(ciphered_data, tag) 101 | else: 102 | raise NotImplementedError 103 | 104 | 105 | def __split_cipher_text(cipher_text: bytes, nonce_length: int): 106 | nonce_tag_length = nonce_length + AEAD_TAG_LENGTH 107 | nonce = cipher_text[:nonce_length] 108 | tag = cipher_text[nonce_length:nonce_tag_length] 109 | ciphered_data = cipher_text[nonce_tag_length:] 110 | return nonce, tag, ciphered_data 111 | -------------------------------------------------------------------------------- /ecies/__main__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | _______ _______ _________ _______ _______ _______ 3 | ( ____ \( ____ \\__ __/( ____ \( ____ \( ____ )|\ /| 4 | | ( \/| ( \/ ) ( | ( \/| ( \/| ( )|( \ / ) 5 | | (__ | | | | | (__ | (_____ | (____)| \ (_) / 6 | | __) | | | | | __) (_____ )| _____) \ / 7 | | ( | | | | | ( ) || ( ) ( 8 | | (____/\| (____/\___) (___| (____/\/\____) || ) | | 9 | (_______/(_______/\_______/(_______/\_______)|/ \_/ 10 | 11 | """ 12 | 13 | import argparse 14 | import sys 15 | 16 | from ecies import decrypt, encrypt 17 | from ecies.config import Config, EllipticCurve 18 | from ecies.keys import PrivateKey 19 | from ecies.utils import to_eth_address 20 | 21 | __description__ = ( 22 | "Elliptic Curve Integrated Encryption Scheme for secp256k1/curve25519 in Python" 23 | ) 24 | 25 | 26 | def readablize(b: bytes) -> str: 27 | try: 28 | return b.decode() 29 | except ValueError: 30 | return b.hex() 31 | 32 | 33 | def __generate(curve: EllipticCurve): 34 | k = PrivateKey(curve) 35 | pk_bytes = k.public_key.to_bytes() 36 | if curve == "secp256k1": 37 | eth_pk_bytes = pk_bytes[1:] 38 | sk, pk = f"0x{k.to_hex()}", f"0x{eth_pk_bytes.hex()}" 39 | address = to_eth_address(eth_pk_bytes) 40 | print("Private: {}\nPublic: {}\nAddress: {}".format(sk, pk, address)) 41 | elif curve in ("x25519", "ed25519"): 42 | sk, pk = f"0x{k.to_hex()}", f"0x{pk_bytes.hex()}" 43 | print("Private: {}\nPublic: {}".format(sk, pk)) 44 | else: 45 | raise NotImplementedError 46 | 47 | 48 | def main(): 49 | parser = argparse.ArgumentParser(description=__description__) 50 | 51 | parser.add_argument( 52 | "-e", 53 | "--encrypt", 54 | action="store_true", 55 | help="encrypt with public key, exclusive with -d", 56 | ) 57 | parser.add_argument( 58 | "-d", 59 | "--decrypt", 60 | action="store_true", 61 | help="decrypt with private key, exclusive with -e", 62 | ) 63 | parser.add_argument( 64 | "-g", 65 | "--generate", 66 | action="store_true", 67 | help="generate key pair, for secp256k1, ethereum public key and address will be printed", 68 | ) 69 | parser.add_argument( 70 | "-k", "--key", type=argparse.FileType("r"), help="public or private key file" 71 | ) 72 | parser.add_argument( 73 | "-c", 74 | "--curve", 75 | choices=["secp256k1", "x25519", "ed25519"], 76 | default="secp256k1", 77 | help="elliptic curve, default: secp256k1", 78 | ) 79 | 80 | parser.add_argument( 81 | "-D", 82 | "--data", 83 | nargs="?", 84 | type=argparse.FileType("rb"), 85 | default=sys.stdin, 86 | help="file to encrypt or decrypt, if not specified, it will read from stdin", 87 | ) 88 | 89 | parser.add_argument( 90 | "-O", 91 | "--out", 92 | nargs="?", 93 | type=argparse.FileType("wb"), 94 | default=sys.stdout, 95 | help="encrypted or decrypted file, if not specified, it will write to stdout", 96 | ) 97 | 98 | args = parser.parse_args() 99 | if args.generate: 100 | __generate(args.curve) 101 | return 102 | 103 | if args.encrypt == args.decrypt: 104 | parser.print_help() 105 | return 106 | 107 | if not args.key: 108 | parser.print_help() 109 | return 110 | 111 | config = Config(elliptic_curve=args.curve) 112 | key = args.key.read().strip() 113 | if args.encrypt: 114 | plain_text = args.data.read() 115 | if isinstance(plain_text, str): 116 | plain_text = plain_text.encode() 117 | data = encrypt(key, plain_text, config) 118 | if args.out == sys.stdout: 119 | data = data.hex() 120 | elif args.decrypt: 121 | cipher_text = args.data.read() 122 | if isinstance(cipher_text, str): 123 | # if not bytes, suppose hex string 124 | cipher_text = bytes.fromhex(cipher_text.strip()) 125 | data = decrypt(key, cipher_text, config) 126 | if args.out == sys.stdout: 127 | # if binary data, print hex; if not, print utf8 128 | data = readablize(data) 129 | else: 130 | raise NotImplementedError 131 | 132 | args.out.write(data) 133 | 134 | 135 | if __name__ == "__main__": 136 | main() 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eciespy 2 | 3 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/2a11aeb9939244019d2c64bce3ff3c4e)](https://app.codacy.com/gh/ecies/py/dashboard) 4 | [![License](https://img.shields.io/github/license/ecies/py.svg)](https://github.com/ecies/py) 5 | [![PyPI](https://img.shields.io/pypi/v/eciespy.svg)](https://pypi.org/project/eciespy/) 6 | [![PyPI - Downloads](https://img.shields.io/pypi/dm/eciespy)](https://pypistats.org/packages/eciespy) 7 | [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/eciespy.svg)](https://pypi.org/project/eciespy/) 8 | [![CI](https://img.shields.io/github/actions/workflow/status/ecies/py/ci.yml?branch=master)](https://github.com/ecies/py/actions) 9 | [![Codecov](https://img.shields.io/codecov/c/github/ecies/py.svg)](https://codecov.io/gh/ecies/py) 10 | 11 | Elliptic Curve Integrated Encryption Scheme for secp256k1/curve25519 in Python. 12 | 13 | Other language versions (curve25519 is not supported in some versions): 14 | 15 | - [TypeScript](https://github.com/ecies/js) 16 | - [Rust](https://github.com/ecies/rs) 17 | - [Golang](https://github.com/ecies/go) 18 | - [WASM](https://github.com/ecies/rs-wasm) 19 | - [Java](https://github.com/ecies/java) 20 | - [Dart](https://github.com/ecies/dart) 21 | - [Ruby](https://github.com/ecies/rb) 22 | 23 | You can also check a [web backend demo](https://github.com/ecies/py-demo). 24 | 25 | ## Install 26 | 27 | `pip install eciespy` 28 | 29 | Or `pip install 'eciespy[eth]'` to install `eth-keys` as well. 30 | 31 | ## Quick Start 32 | 33 | ### Secp256k1 34 | 35 | ```python 36 | >>> from ecies.keys import PrivateKey 37 | >>> from ecies import encrypt, decrypt 38 | >>> data = 'hello world🌍'.encode() 39 | >>> sk = PrivateKey('secp256k1') 40 | >>> sk_bytes = sk.secret # bytes 41 | >>> pk_bytes = sk.public_key.to_bytes(True) # bytes 42 | >>> decrypt(sk_bytes, encrypt(pk_bytes, data)).decode() 43 | 'hello world🌍' 44 | >>> sk_hex = sk.to_hex() # hex str 45 | >>> pk_hex = sk.public_key.to_hex(True) # hex str 46 | >>> decrypt(sk_hex, encrypt(pk_hex, data)).decode() 47 | 'hello world🌍' 48 | ``` 49 | 50 | ### X25519/Ed25519 51 | 52 | ```python 53 | >>> from ecies.keys import PrivateKey 54 | >>> from ecies import encrypt, decrypt 55 | >>> from ecies.config import ECIES_CONFIG 56 | >>> ECIES_CONFIG.elliptic_curve = 'x25519' # or 'ed25519' 57 | >>> data = 'hello world🌍'.encode() 58 | >>> sk = PrivateKey('x25519') # or 'ed25519' 59 | >>> decrypt(sk.secret, encrypt(sk.public_key.to_bytes(), data)).decode() 60 | 'hello world🌍' 61 | ``` 62 | 63 | Or just use a builtin command `eciespy` in your favorite [command line](#command-line-interface). 64 | 65 | ## API 66 | 67 | ### `ecies.encrypt(receiver_pk: Union[str, bytes], data: bytes, config: Config = ECIES_CONFIG) -> bytes` 68 | 69 | Parameters: 70 | 71 | - `receiver_pk` - Receiver's public key (hex `str` or `bytes`) 72 | - `data` - Data to encrypt 73 | - `config` - Optional configuration object 74 | 75 | Returns: `bytes` 76 | 77 | ### `ecies.decrypt(receiver_sk: Union[str, bytes], data: bytes, config: Config = ECIES_CONFIG) -> bytes` 78 | 79 | Parameters: 80 | 81 | - `receiver_sk` - Receiver's private key (hex `str` or `bytes`) 82 | - `data` - Data to decrypt 83 | - `config` - Optional configuration object 84 | 85 | Returns: `bytes` 86 | 87 | ## Command Line Interface 88 | 89 | ### Show help 90 | 91 | ```console 92 | $ eciespy -h 93 | usage: eciespy [-h] [-e] [-d] [-g] [-k KEY] [-c {secp256k1,x25519,ed25519}] [-D [DATA]] [-O [OUT]] 94 | 95 | Elliptic Curve Integrated Encryption Scheme for secp256k1/curve25519 in Python 96 | 97 | options: 98 | -h, --help show this help message and exit 99 | -e, --encrypt encrypt with public key, exclusive with -d 100 | -d, --decrypt decrypt with private key, exclusive with -e 101 | -g, --generate generate key pair, for secp256k1, ethereum public key and address will be printed 102 | -k, --key KEY public or private key file 103 | -c, --curve {secp256k1,x25519,ed25519} 104 | elliptic curve, default: secp256k1 105 | -D, --data [DATA] file to encrypt or decrypt, if not specified, it will read from stdin 106 | -O, --out [OUT] encrypted or decrypted file, if not specified, it will write to stdout 107 | ``` 108 | 109 | ### Generate eth key 110 | 111 | ```console 112 | $ eciespy -g 113 | Private: 0x95d3c5e483e9b1d4f5fc8e79b2deaf51362980de62dbb082a9a4257eef653d7d 114 | Public: 0x98afe4f150642cd05cc9d2fa36458ce0a58567daeaf5fde7333ba9b403011140a4e28911fcf83ab1f457a30b4959efc4b9306f514a4c3711a16a80e3b47eb58b 115 | Address: 0x47e801184B3a8ea8E6A4A7A4CFEfEcC76809Da72 116 | ``` 117 | 118 | ### Encrypt with public key and decrypt with private key 119 | 120 | ```console 121 | $ echo '0x95d3c5e483e9b1d4f5fc8e79b2deaf51362980de62dbb082a9a4257eef653d7d' > sk 122 | $ echo '0x98afe4f150642cd05cc9d2fa36458ce0a58567daeaf5fde7333ba9b403011140a4e28911fcf83ab1f457a30b4959efc4b9306f514a4c3711a16a80e3b47eb58b' > pk 123 | $ echo 'hello ecies' | eciespy -e -k pk | eciespy -d -k sk 124 | hello ecies 125 | $ echo 'data to encrypt' > data 126 | $ eciespy -e -k pk -D data -O enc_data 127 | $ eciespy -d -k sk -D enc_data 128 | data to encrypt 129 | $ rm sk pk data enc_data 130 | ``` 131 | 132 | ## Configuration 133 | 134 | Following configurations are available. 135 | 136 | - Elliptic curve: secp256k1 or curve25519 (x25519/ed25519) 137 | - Ephemeral key format in the payload: compressed or uncompressed (only for secp256k1) 138 | - Shared elliptic curve key format in the key derivation: compressed or uncompressed (only for secp256k1) 139 | - Symmetric cipher algorithm: AES-256-GCM or XChaCha20-Poly1305 140 | - Symmetric nonce length: 12 or 16 bytes (only for AES-256-GCM) 141 | 142 | For compatibility, make sure different applications share the same configuration. 143 | 144 | ```py 145 | EllipticCurve = Literal["secp256k1", "x25519", "ed25519"] 146 | SymmetricAlgorithm = Literal["aes-256-gcm", "xchacha20"] 147 | NonceLength = Literal[12, 16] # only for aes-256-gcm, xchacha20 will always be 24 148 | 149 | 150 | @dataclass() 151 | class Config: 152 | elliptic_curve: EllipticCurve = "secp256k1" 153 | is_ephemeral_key_compressed: bool = False 154 | is_hkdf_key_compressed: bool = False 155 | symmetric_algorithm: SymmetricAlgorithm = "aes-256-gcm" 156 | symmetric_nonce_length: NonceLength = 16 157 | 158 | @property 159 | def ephemeral_key_size(self): 160 | if self.elliptic_curve == "secp256k1": 161 | return ( 162 | COMPRESSED_PUBLIC_KEY_SIZE 163 | if self.is_ephemeral_key_compressed 164 | else UNCOMPRESSED_PUBLIC_KEY_SIZE 165 | ) 166 | elif self.elliptic_curve in ("x25519", "ed25519"): 167 | return CURVE25519_PUBLIC_KEY_SIZE 168 | else: 169 | raise NotImplementedError 170 | 171 | 172 | ECIES_CONFIG = Config() 173 | ``` 174 | 175 | On `ECIES_CONFIG.elliptic_curve = "x25519"` or `"ed25519"`, x25519 (key exchange function on curve25519) or ed25519 (signature algorithm on curve25519) will be used for key exchange instead of secp256k1. 176 | 177 | In this case, the payload would always be: `32 Bytes + Ciphered`. 178 | 179 | > If you don't know how to choose between x25519 and ed25519, just use the dedicated key exchange function x25519 for efficiency. 180 | > 181 | > Because any 32-byte data is a valid curve25519 public key, the payload would seem random. This property is excellent for circumventing censorship by adversaries. 182 | 183 | ### Secp256k1-specific configuration 184 | 185 | On `is_ephemeral_key_compressed = True`, the payload would be like: `33 Bytes + Ciphered` instead of `65 Bytes + Ciphered`. 186 | 187 | On `is_hkdf_key_compressed = True`, the hkdf key would be derived from `ephemeral public key (compressed) + shared public key (compressed)` instead of `ephemeral public key (uncompressed) + shared public key (uncompressed)`. 188 | 189 | ### Symmetric cipher configuration 190 | 191 | On `symmetric_algorithm = "xchacha20"`, plaintext data would be encrypted with XChaCha20-Poly1305. 192 | 193 | On `symmetric_nonce_length = 12`, then the nonce of AES-256-GCM would be 12 bytes. XChaCha20-Poly1305's nonce is always 24 bytes. 194 | 195 | ### Which configuration should I choose? 196 | 197 | For compatibility with other [ecies libraries](https://github.com/orgs/ecies/repositories), start with the default (secp256k1 with AES-256-GCM). 198 | 199 | For speed and security, pick x25519 with XChaCha20-Poly1305. 200 | 201 | ## Technical details 202 | 203 | See [DETAILS.md](./DETAILS.md). 204 | 205 | ## Changelog 206 | 207 | See [CHANGELOG.md](./CHANGELOG.md). 208 | -------------------------------------------------------------------------------- /DETAILS.md: -------------------------------------------------------------------------------- 1 | # Mechanism and implementation 2 | 3 | This library combines `secp256k1` and `AES-256-GCM` (powered by [`coincurve`](https://github.com/ofek/coincurve) and [`pycryptodome`](https://github.com/Legrandin/pycryptodome)) to provide an API of encrypting with `secp256k1` public key and decrypting with `secp256k1`'s private key. It has two parts generally: 4 | 5 | 1. Use [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie–Hellman) to exchange an AES session key; 6 | 7 | > Notice that the sender public key is generated every time when `ecies.encrypt` is invoked, thus, the AES session key varies. 8 | > 9 | > We are using HKDF-SHA256 instead of SHA256 to derive the AES keys. 10 | 11 | 2. Use this AES session key to encrypt/decrypt the data under `AES-256-GCM`. 12 | 13 | Basically the encrypted data will be like this: 14 | 15 | ```plaintext 16 | +-------------------------------+----------+----------+-----------------+ 17 | | 65 Bytes | 16 Bytes | 16 Bytes | == data size | 18 | +-------------------------------+----------+----------+-----------------+ 19 | | Sender Public Key (ephemeral) | Nonce/IV | Tag/MAC | Encrypted data | 20 | +-------------------------------+----------+----------+-----------------+ 21 | | sender_pk | nonce | tag | encrypted_data | 22 | +-------------------------------+----------+----------+-----------------+ 23 | | Secp256k1 | AES-256-GCM | 24 | +-------------------------------+---------------------------------------+ 25 | ``` 26 | 27 | ## Secp256k1 28 | 29 | ### Glance at ECDH 30 | 31 | So, **how** do we calculate the ECDH key under `secp256k1`? If you use a library like [`coincurve`](https://github.com/ofek/coincurve), you might just simply call `k1.ecdh(k2.public_key.format())`, then uh-huh, you got it! Let's see how to do it in simple Python snippets: 32 | 33 | ```python 34 | >>> from coincurve import PrivateKey 35 | >>> k1 = PrivateKey.from_int(3) 36 | >>> k2 = PrivateKey.from_int(2) 37 | >>> k1.public_key.format(False).hex() # 65 bytes, False means uncompressed key 38 | '04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672' 39 | >>> k2.public_key.format(False).hex() # 65 bytes 40 | '04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a' 41 | >>> k1.ecdh(k2.public_key.format()).hex() 42 | 'c7d9ba2fa1496c81be20038e5c608f2fd5d0246d8643783730df6c2bbb855cb2' 43 | >>> k2.ecdh(k1.public_key.format()).hex() 44 | 'c7d9ba2fa1496c81be20038e5c608f2fd5d0246d8643783730df6c2bbb855cb2' 45 | ``` 46 | 47 | ### Calculate your ecdh key manually 48 | 49 | However, as a hacker like you with strong desire to learn something, you must be curious about the magic under the ground. 50 | 51 | In one sentence, the `secp256k1`'s ECDH key of `k1` and `k2` is nothing but `sha256(k2.public_key.multiply(k1))`. 52 | 53 | ```python 54 | >>> k1.to_int() 55 | 3 56 | >>> shared = k2.public_key.multiply(k1.secret) 57 | >>> shared.point() 58 | (115780575977492633039504758427830329241728645270042306223540962614150928364886, 59 | 78735063515800386211891312544505775871260717697865196436804966483607426560663) 60 | >>> import hashlib 61 | >>> h = hashlib.sha256() 62 | >>> h.update(shared.format()) 63 | >>> h.hexdigest() # here you got the ecdh key same as above! 64 | 'c7d9ba2fa1496c81be20038e5c608f2fd5d0246d8643783730df6c2bbb855cb2' 65 | ``` 66 | 67 | > Warning: **NEVER** use small integers as private keys on any production systems or storing any valuable assets. 68 | > 69 | > Warning: **ALWAYS** use safe methods like [`os.urandom`](https://docs.python.org/3/library/os.html#os.urandom) to generate private keys. 70 | 71 | ### Math on ecdh 72 | 73 | Let's discuss in details. The word _multiply_ here means multiplying a **point** of a public key on elliptic curve (like `(x, y)`) with a **scalar** (like `k`). Here `k` is the integer format of a private key, for instance, it can be `3` for `k1` here, and `(x, y)` here is an extremely large number pair like `(115780575977492633039504758427830329241728645270042306223540962614150928364886, 78735063515800386211891312544505775871260717697865196436804966483607426560663)`. 74 | 75 | > Warning: 1 \* (x, y) == (x, y) is always true, since 1 is the **identity element** for multiplication. If you take integer 1 as a private key, the public key will be the [base point](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm#Signature_generation_algorithm). 76 | 77 | Mathematically, the elliptic curve cryptography is based on the fact that you can easily multiply point `A` (aka base point, or public key in ECDH) and scalar `k` (aka private key) to get another point `B` (aka public key), but it's almost impossible to calculate `A` from `B` reversely (which means it's a "one-way function"). 78 | 79 | ### Compressed and uncompressed keys 80 | 81 | A point multiplying a scalar can be regarded that this point adds itself multiple times, and the point `B` can be converted to a readable public key in a compressed or uncompressed format. 82 | 83 | - Compressed format (`x` coordinate only) 84 | 85 | ```python 86 | >>> point = (89565891926547004231252920425935692360644145829622209833684329913297188986597, 12158399299693830322967808612713398636155367887041628176798871954788371653930) 87 | >>> point == k2.public_key.point() 88 | True 89 | >>> prefix = '02' if point[1] % 2 == 0 else '03' 90 | >>> compressed_key_hex = prefix + hex(point[0])[2:] 91 | >>> compressed_key = bytes.fromhex(compressed_key_hex) 92 | >>> compressed_key.hex() 93 | '02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5' 94 | ``` 95 | 96 | - Uncompressed format (`(x, y)` coordinate) 97 | 98 | ```python 99 | >>> uncompressed_key_hex = '04' + hex(point[0])[2:] + hex(point[1])[2:] 100 | >>> uncompressed_key = bytes.fromhex(uncompressed_key_hex) 101 | >>> uncompressed_key.hex() 102 | '04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a' 103 | ``` 104 | 105 | The format is depicted by the image below from the [bitcoin book](https://github.com/bitcoinbook/bitcoinbook). 106 | 107 | ![EC public key format](https://raw.githubusercontent.com/bitcoinbook/bitcoinbook/develop/images/mbc3_0408.png) 108 | 109 | > If you want to convert the compressed format to uncompressed, basically, you need to calculate `y` from `x` by solving the equation using [Cipolla's Algorithm](https://en.wikipedia.org/wiki/Cipolla's_algorithm): 110 | > 111 | > ![y^2=(x^3 + 7) mod p, where p=2^{256}-2^{32}-2^{9}-2^{8}-2^{7}-2^{6}-2^{4}-1]() 112 | > 113 | > You can check the [bitcoin wiki](https://en.bitcoin.it/wiki/Secp256k1) and this thread on [bitcointalk.org](https://bitcointalk.org/index.php?topic=644919.msg7205689#msg7205689) for more details. 114 | 115 | Then, the shared key between `k1` and `k2` is the `sha256` hash of the **compressed** ECDH public key. It's better to use the compressed format, since you can always get `x` from `x` or `(x, y)` without any calculation. 116 | 117 | You may want to ask, what if we don't hash it? Briefly, hash can: 118 | 119 | 1. Make the shared key's length fixed; 120 | 2. Make it safer since hash functions can remove "weak bits" in the original computed key. Check the introduction section of this [paper](http://cacr.uwaterloo.ca/techreports/1998/corr98-05.pdf) for more details. 121 | 122 | > Warning: According to some recent research, although widely used, the `sha256` key derivation function is [not secure enough](https://github.com/ecies/py/issues/82). 123 | 124 | ## AES 125 | 126 | Now we have the shared key, and we can use the `nonce` and `tag` to decrypt. This is quite straight, and the example derives from `pycryptodome`'s [documentation](https://pycryptodome.readthedocs.io/en/latest/src/examples.html#encrypt-data-with-aes). 127 | 128 | ```python 129 | >>> from Crypto.Cipher import AES 130 | >>> key = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 131 | >>> nonce = b'\xf3\xe1\xba\x81\r,\x89\x00\xb1\x13\x12\xb7\xc7%V_' 132 | >>> tag = b'\xec;q\xe1|\x11\xdb\xe3\x14\x84\xda\x94P\xed\xcfl' 133 | >>> data = b'\x02\xd2\xff\xed\x93\xb8V\xf1H\xb9' 134 | >>> decipher = AES.new(key, AES.MODE_GCM, nonce=nonce) 135 | >>> decipher.decrypt_and_verify(data, tag) 136 | b'helloworld' 137 | ``` 138 | 139 | > Strictly speaking, `nonce` != `iv`, but this is a little bit off topic, if you are curious, you can check [the comment in `utils/symmetric.py`](./ecies/utils/symmetric.py#L83). 140 | > 141 | > Warning: it's dangerous to reuse nonce, if you don't know what you are doing, just follow the default setting. 142 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "annotated-types" 5 | version = "0.7.0" 6 | description = "Reusable constraint types to use with typing.Annotated" 7 | optional = true 8 | python-versions = ">=3.8" 9 | groups = ["main"] 10 | markers = "extra == \"eth\"" 11 | files = [ 12 | {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, 13 | {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, 14 | ] 15 | 16 | [[package]] 17 | name = "asttokens" 18 | version = "3.0.1" 19 | description = "Annotate AST trees with source code positions" 20 | optional = false 21 | python-versions = ">=3.8" 22 | groups = ["dev"] 23 | markers = "python_version >= \"3.11\"" 24 | files = [ 25 | {file = "asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a"}, 26 | {file = "asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7"}, 27 | ] 28 | 29 | [package.extras] 30 | astroid = ["astroid (>=2,<5)"] 31 | test = ["astroid (>=2,<5)", "pytest (<9.0)", "pytest-cov", "pytest-xdist"] 32 | 33 | [[package]] 34 | name = "coincurve" 35 | version = "21.0.0" 36 | description = "Safest and fastest Python library for secp256k1 elliptic curve operations" 37 | optional = false 38 | python-versions = ">=3.9" 39 | groups = ["main"] 40 | files = [ 41 | {file = "coincurve-21.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:986727bba6cf0c5670990358dc6af9a54f8d3e257979b992a9dbd50dd82fa0dc"}, 42 | {file = "coincurve-21.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1c584059de61ed16c658e7eae87ee488e81438897dae8fabeec55ef408af474"}, 43 | {file = "coincurve-21.0.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4210b35c922b2b36c987a48c0b110ab20e490a2d6a92464ca654cb09e739fcc"}, 44 | {file = "coincurve-21.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf67332cc647ef52ef371679c76000f096843ae266ae6df5e81906eb6463186b"}, 45 | {file = "coincurve-21.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997607a952913c6a4bebe86815f458e77a42467b7a75353ccdc16c3336726880"}, 46 | {file = "coincurve-21.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cfdd0938f284fb147aa1723a69f8794273ec673b10856b6e6f5f63fcc99d0c2e"}, 47 | {file = "coincurve-21.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:88c1e3f6df2f2fbe18152c789a18659ee0429dc604fc77530370c9442395f681"}, 48 | {file = "coincurve-21.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:530b58ed570895612ef510e28df5e8a33204b03baefb5c986e22811fa09622ef"}, 49 | {file = "coincurve-21.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:f920af756a98edd738c0cfa431e81e3109aeec6ffd6dffb5ed4f5b5a37aacba8"}, 50 | {file = "coincurve-21.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:070e060d0d57b496e68e48b39d5e3245681376d122827cb8e09f33669ff8cf1b"}, 51 | {file = "coincurve-21.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:65ec42cab9c60d587fb6275c71f0ebc580625c377a894c4818fb2a2b583a184b"}, 52 | {file = "coincurve-21.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5828cd08eab928db899238874d1aab12fa1236f30fe095a3b7e26a5fc81df0a3"}, 53 | {file = "coincurve-21.0.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54de1cac75182de9f71ce41415faafcaf788303e21cbd0188064e268d61625e5"}, 54 | {file = "coincurve-21.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07cda058d9394bea30d57a92fdc18ee3ca6b5bc8ef776a479a2ffec917105836"}, 55 | {file = "coincurve-21.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9070804d7c71badfe4f0bf19b728cfe7c70c12e733938ead6b1db37920b745c0"}, 56 | {file = "coincurve-21.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:669ab5db393637824b226de058bb7ea0cb9a0236e1842d7b22f74d4a8a1f1ff1"}, 57 | {file = "coincurve-21.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3bcd538af097b3914ec3cb654262e72e224f95f2e9c1eb7fbd75d843ae4e528e"}, 58 | {file = "coincurve-21.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45b6a5e6b5536e1f46f729829d99ce1f8f847308d339e8880fe7fa1646935c10"}, 59 | {file = "coincurve-21.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:87597cf30dfc05fa74218810776efacf8816813ab9fa6ea1490f94e9f8b15e77"}, 60 | {file = "coincurve-21.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:b992d1b1dac85d7f542d9acbcf245667438839484d7f2b032fd032256bcd778e"}, 61 | {file = "coincurve-21.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f60ad56113f08e8c540bb89f4f35f44d434311433195ffff22893ccfa335070c"}, 62 | {file = "coincurve-21.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1cb1cd19fb0be22e68ecb60ad950b41f18b9b02eebeffaac9391dc31f74f08f2"}, 63 | {file = "coincurve-21.0.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05d7e255a697b3475d7ae7640d3bdef3d5bc98ce9ce08dd387f780696606c33b"}, 64 | {file = "coincurve-21.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a366c314df7217e3357bb8c7d2cda540b0bce180705f7a0ce2d1d9e28f62ad4"}, 65 | {file = "coincurve-21.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b04778b75339c6e46deb9ae3bcfc2250fbe48d1324153e4310fc4996e135715"}, 66 | {file = "coincurve-21.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8efcbdcd50cc219989a2662e6c6552f455efc000a15dd6ab3ebf4f9b187f41a3"}, 67 | {file = "coincurve-21.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6df44b4e3b7acdc1453ade52a52e3f8a5b53ecdd5a06bd200f1ec4b4e250f7d9"}, 68 | {file = "coincurve-21.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bcc0831f07cb75b91c35c13b1362e7b9dc76c376b27d01ff577bec52005e22a8"}, 69 | {file = "coincurve-21.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:5dd7b66b83b143f3ad3861a68fc0279167a0bae44fe3931547400b7a200e90b1"}, 70 | {file = "coincurve-21.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:78dbe439e8cb22389956a4f2f2312813b4bd0531a0b691d4f8e868c7b366555d"}, 71 | {file = "coincurve-21.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9df5ceb5de603b9caf270629996710cf5ed1d43346887bc3895a11258644b65b"}, 72 | {file = "coincurve-21.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:154467858d23c48f9e5ab380433bc2625027b50617400e2984cc16f5799ab601"}, 73 | {file = "coincurve-21.0.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f57f07c44d14d939bed289cdeaba4acb986bba9f729a796b6a341eab1661eedc"}, 74 | {file = "coincurve-21.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fb03e3a388a93d31ed56a442bdec7983ea404490e21e12af76fb1dbf097082a"}, 75 | {file = "coincurve-21.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d09ba4fd9d26b00b06645fcd768c5ad44832a1fa847ebe8fb44970d3204c3cb7"}, 76 | {file = "coincurve-21.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1a1e7ee73bc1b3bcf14c7b0d1f44e6485785d3b53ef7b16173c36d3cefa57f93"}, 77 | {file = "coincurve-21.0.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ad05952b6edc593a874df61f1bc79db99d716ec48ba4302d699e14a419fe6f51"}, 78 | {file = "coincurve-21.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d2bf350ced38b73db9efa1ff8fd16a67a1cb35abb2dda50d89661b531f03fd3"}, 79 | {file = "coincurve-21.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:54d9500c56d5499375e579c3917472ffcf804c3584dd79052a79974280985c74"}, 80 | {file = "coincurve-21.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:773917f075ec4b94a7a742637d303a3a082616a115c36568eb6c873a8d950d18"}, 81 | {file = "coincurve-21.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb82ba677fc7600a3bf200edc98f4f9604c317b18c7b3f0a10784b42686e3a53"}, 82 | {file = "coincurve-21.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5001de8324c35eee95f34e011a5c3b4e7d9ae9ca4a862a93b2c89b3f467f511b"}, 83 | {file = "coincurve-21.0.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4d0bb5340bcac695731bef51c3e0126f252453e2d1ae7fa1486d90eff978bf6"}, 84 | {file = "coincurve-21.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a9b49789ff86f3cf86cfc8ff8c6c43bac2607720ec638e8ba471fa7e8765bd2"}, 85 | {file = "coincurve-21.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b85b49e192d2ca1a906a7b978bacb55d4dcb297cc2900fbbd9b9180d50878779"}, 86 | {file = "coincurve-21.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ad6445f0bb61b3a4404d87a857ddb2a74a642cd4d00810237641aab4d6b1a42f"}, 87 | {file = "coincurve-21.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d3f017f1491491f3f2c49e5d2d3a471a872d75117bfcb804d1167061c94bd347"}, 88 | {file = "coincurve-21.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:500e5e38cd4cbc4ea8a5c631ce843b1d52ef19ac41128568214d150f75f1f387"}, 89 | {file = "coincurve-21.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:ef81ca24511a808ad0ebdb8fdaf9c5c87f12f935b3d117acccc6520ad671bcce"}, 90 | {file = "coincurve-21.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:6ec8e859464116a3c90168cd2bd7439527d4b4b5e328b42e3c8e0475f9b0bf71"}, 91 | {file = "coincurve-21.0.0.tar.gz", hash = "sha256:8b37ce4265a82bebf0e796e21a769e56fdbf8420411ccbe3fafee4ed75b6a6e5"}, 92 | ] 93 | 94 | [[package]] 95 | name = "colorama" 96 | version = "0.4.6" 97 | description = "Cross-platform colored terminal text." 98 | optional = false 99 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 100 | groups = ["dev", "test"] 101 | files = [ 102 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 103 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 104 | ] 105 | markers = {dev = "python_version >= \"3.11\" and sys_platform == \"win32\"", test = "sys_platform == \"win32\""} 106 | 107 | [[package]] 108 | name = "coverage" 109 | version = "7.10.7" 110 | description = "Code coverage measurement for Python" 111 | optional = false 112 | python-versions = ">=3.9" 113 | groups = ["test"] 114 | files = [ 115 | {file = "coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a"}, 116 | {file = "coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5"}, 117 | {file = "coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17"}, 118 | {file = "coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b"}, 119 | {file = "coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87"}, 120 | {file = "coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e"}, 121 | {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e"}, 122 | {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df"}, 123 | {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0"}, 124 | {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13"}, 125 | {file = "coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b"}, 126 | {file = "coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807"}, 127 | {file = "coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59"}, 128 | {file = "coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a"}, 129 | {file = "coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699"}, 130 | {file = "coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d"}, 131 | {file = "coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e"}, 132 | {file = "coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23"}, 133 | {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab"}, 134 | {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82"}, 135 | {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2"}, 136 | {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61"}, 137 | {file = "coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14"}, 138 | {file = "coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2"}, 139 | {file = "coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a"}, 140 | {file = "coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417"}, 141 | {file = "coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973"}, 142 | {file = "coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c"}, 143 | {file = "coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7"}, 144 | {file = "coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6"}, 145 | {file = "coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59"}, 146 | {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b"}, 147 | {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a"}, 148 | {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb"}, 149 | {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1"}, 150 | {file = "coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256"}, 151 | {file = "coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba"}, 152 | {file = "coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf"}, 153 | {file = "coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d"}, 154 | {file = "coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b"}, 155 | {file = "coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e"}, 156 | {file = "coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b"}, 157 | {file = "coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49"}, 158 | {file = "coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911"}, 159 | {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0"}, 160 | {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f"}, 161 | {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c"}, 162 | {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f"}, 163 | {file = "coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698"}, 164 | {file = "coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843"}, 165 | {file = "coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546"}, 166 | {file = "coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c"}, 167 | {file = "coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15"}, 168 | {file = "coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4"}, 169 | {file = "coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0"}, 170 | {file = "coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0"}, 171 | {file = "coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65"}, 172 | {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541"}, 173 | {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6"}, 174 | {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999"}, 175 | {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2"}, 176 | {file = "coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a"}, 177 | {file = "coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb"}, 178 | {file = "coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb"}, 179 | {file = "coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520"}, 180 | {file = "coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32"}, 181 | {file = "coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f"}, 182 | {file = "coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a"}, 183 | {file = "coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360"}, 184 | {file = "coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69"}, 185 | {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14"}, 186 | {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe"}, 187 | {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e"}, 188 | {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd"}, 189 | {file = "coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2"}, 190 | {file = "coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681"}, 191 | {file = "coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880"}, 192 | {file = "coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63"}, 193 | {file = "coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2"}, 194 | {file = "coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d"}, 195 | {file = "coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0"}, 196 | {file = "coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699"}, 197 | {file = "coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9"}, 198 | {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f"}, 199 | {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1"}, 200 | {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0"}, 201 | {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399"}, 202 | {file = "coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235"}, 203 | {file = "coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d"}, 204 | {file = "coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a"}, 205 | {file = "coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3"}, 206 | {file = "coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c"}, 207 | {file = "coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396"}, 208 | {file = "coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40"}, 209 | {file = "coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594"}, 210 | {file = "coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a"}, 211 | {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b"}, 212 | {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3"}, 213 | {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0"}, 214 | {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f"}, 215 | {file = "coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431"}, 216 | {file = "coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07"}, 217 | {file = "coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260"}, 218 | {file = "coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239"}, 219 | ] 220 | 221 | [package.dependencies] 222 | tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} 223 | 224 | [package.extras] 225 | toml = ["tomli ; python_full_version <= \"3.11.0a6\""] 226 | 227 | [[package]] 228 | name = "cytoolz" 229 | version = "1.1.0" 230 | description = "Cython implementation of Toolz: High performance functional utilities" 231 | optional = true 232 | python-versions = ">=3.9" 233 | groups = ["main"] 234 | markers = "extra == \"eth\" and implementation_name == \"cpython\"" 235 | files = [ 236 | {file = "cytoolz-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:72d7043a88ea5e61ba9d17ea0d1c1eff10f645d7edfcc4e56a31ef78be287644"}, 237 | {file = "cytoolz-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d759e9ed421bacfeb456d47af8d734c057b9912b5f2441f95b27ca35e5efab07"}, 238 | {file = "cytoolz-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fdb5be8fbcc0396141189022724155a4c1c93712ac4aef8c03829af0c2a816d7"}, 239 | {file = "cytoolz-1.1.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c8c0a513dc89bc05cc72893609118815bced5ef201f1a317b4cc3423b3a0e750"}, 240 | {file = "cytoolz-1.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce94db4f8ebe842c30c0ece42ff5de977c47859088c2c363dede5a68f6906484"}, 241 | {file = "cytoolz-1.1.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b622d4f54e370c853ded94a668f94fe72c6d70e06ac102f17a2746661c27ab52"}, 242 | {file = "cytoolz-1.1.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:375a65baa5a5b4ff6a0c5ff17e170cf23312e4c710755771ca966144c24216b5"}, 243 | {file = "cytoolz-1.1.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c0d51bcdb3203a062a78f66bbe33db5e3123048e24a5f0e1402422d79df8ee2d"}, 244 | {file = "cytoolz-1.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1010869529bb05dc9802b6d776a34ca1b6d48b9deec70ad5e2918ae175be5c2f"}, 245 | {file = "cytoolz-1.1.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11a8f2e83295bdb33f35454d6bafcb7845b03b5881dcaed66ecbd726c7f16772"}, 246 | {file = "cytoolz-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0499c5e0a8e688ed367a2e51cc13792ae8f08226c15f7d168589fc44b9b9cada"}, 247 | {file = "cytoolz-1.1.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:87d44e6033d4c5e95a7d39ba59b8e105ba1c29b1ccd1d215f26477cc1d64be39"}, 248 | {file = "cytoolz-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a68cef396a7de237f7b97422a6a450dfb111722296ba217ba5b34551832f1f6e"}, 249 | {file = "cytoolz-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:06ad4c95b258141f138a93ebfdc1d76ac087afc1a82f1401100a1f44b44ba656"}, 250 | {file = "cytoolz-1.1.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ada59a4b3c59d4ac7162e0ed08667ffa78abf48e975c8a9f9d5b9bc50720f4fd"}, 251 | {file = "cytoolz-1.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a8957bcaea1ba01327a9b219d2adb84144377684f51444253890dab500ca171f"}, 252 | {file = "cytoolz-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6d8cdc299d67eb0f3b9ecdafeeb55eb3b7b7470e2d950ac34b05ed4c7a5572b8"}, 253 | {file = "cytoolz-1.1.0-cp310-cp310-win32.whl", hash = "sha256:d8e08464c5cdea4f6df31e84b11ed6bfd79cedb99fbcbfdc15eb9361a6053c5a"}, 254 | {file = "cytoolz-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:7e49922a7ed54262d41960bf3b835a7700327bf79cff1e9bfc73d79021132ff8"}, 255 | {file = "cytoolz-1.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:943a662d2e72ffc4438d43ab5a1de8d852237775a423236594a3b3e381b8032c"}, 256 | {file = "cytoolz-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dba8e5a8c6e3c789d27b0eb5e7ce5ed7d032a7a9aae17ca4ba5147b871f6e327"}, 257 | {file = "cytoolz-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:44b31c05addb0889167a720123b3b497b28dd86f8a0aeaf3ae4ffa11e2c85d55"}, 258 | {file = "cytoolz-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:653cb18c4fc5d8a8cfce2bce650aabcbe82957cd0536827367d10810566d5294"}, 259 | {file = "cytoolz-1.1.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:853a5b4806915020c890e1ce70cc056bbc1dd8bc44f2d74d555cccfd7aefba7d"}, 260 | {file = "cytoolz-1.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7b44e9de86bea013fe84fd8c399d6016bbb96c37c5290769e5c99460b9c53e5"}, 261 | {file = "cytoolz-1.1.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:098d628a801dc142e9740126be5624eb7aef1d732bc7a5719f60a2095547b485"}, 262 | {file = "cytoolz-1.1.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:779ee4096ed7a82cffab89372ffc339631c285079dbf33dbe7aff1f6174985df"}, 263 | {file = "cytoolz-1.1.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f2ce18dd99533d077e9712f9faa852f389f560351b1efd2f2bdb193a95eddde2"}, 264 | {file = "cytoolz-1.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac266a34437812cf841cecbfe19f355ab9c3dd1ef231afc60415d40ff12a76e4"}, 265 | {file = "cytoolz-1.1.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1920b9b9c13d60d0bb6cd14594b3bce0870022eccb430618c37156da5f2b7a55"}, 266 | {file = "cytoolz-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47caa376dafd2bdc29f8a250acf59c810ec9105cd6f7680b9a9d070aae8490ec"}, 267 | {file = "cytoolz-1.1.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5ab2c97d8aaa522b038cca9187b1153347af22309e7c998b14750c6fdec7b1cb"}, 268 | {file = "cytoolz-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4bce006121b120e8b359244ee140bb0b1093908efc8b739db8dbaa3f8fb42139"}, 269 | {file = "cytoolz-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7fc0f1e4e9bb384d26e73c6657bbc26abdae4ff66a95933c00f3d578be89181b"}, 270 | {file = "cytoolz-1.1.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:dd3f894ff972da1994d06ac6157d74e40dda19eb31fe5e9b7863ca4278c3a167"}, 271 | {file = "cytoolz-1.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0846f49cf8a4496bd42659040e68bd0484ce6af819709cae234938e039203ba0"}, 272 | {file = "cytoolz-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:16a3af394ade1973226d64bb2f9eb3336adbdea03ed5b134c1bbec5a3b20028e"}, 273 | {file = "cytoolz-1.1.0-cp311-cp311-win32.whl", hash = "sha256:b786c9c8aeab76cc2f76011e986f7321a23a56d985b77d14f155d5e5514ea781"}, 274 | {file = "cytoolz-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:ebf06d1c5344fb22fee71bf664234733e55db72d74988f2ecb7294b05e4db30c"}, 275 | {file = "cytoolz-1.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:b63f5f025fac893393b186e132e3e242de8ee7265d0cd3f5bdd4dda93f6616c9"}, 276 | {file = "cytoolz-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:99f8e134c9be11649342853ec8c90837af4089fc8ff1e8f9a024a57d1fa08514"}, 277 | {file = "cytoolz-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0a6f44cf9319c30feb9a50aa513d777ef51efec16f31c404409e7deb8063df64"}, 278 | {file = "cytoolz-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:945580dc158c557172fca899a35a99a16fbcebf6db0c77cb6621084bc82189f9"}, 279 | {file = "cytoolz-1.1.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:257905ec050d04f2f856854620d1e25556fd735064cebd81b460f54939b9f9d5"}, 280 | {file = "cytoolz-1.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82779049f352fb3ab5e8c993ab45edbb6e02efb1f17f0b50f4972c706cc51d76"}, 281 | {file = "cytoolz-1.1.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7d3e405e435320e08c5a1633afaf285a392e2d9cef35c925d91e2a31dfd7a688"}, 282 | {file = "cytoolz-1.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:923df8f5591e0d20543060c29909c149ab1963a7267037b39eee03a83dbc50a8"}, 283 | {file = "cytoolz-1.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:25db9e4862f22ea0ae2e56c8bec9fc9fd756b655ae13e8c7b5625d7ed1c582d4"}, 284 | {file = "cytoolz-1.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7a98deb11ccd8e5d9f9441ef2ff3352aab52226a2b7d04756caaa53cd612363"}, 285 | {file = "cytoolz-1.1.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dce4ee9fc99104bc77efdea80f32ca5a650cd653bcc8a1d984a931153d3d9b58"}, 286 | {file = "cytoolz-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80d6da158f7d20c15819701bbda1c041f0944ede2f564f5c739b1bc80a9ffb8b"}, 287 | {file = "cytoolz-1.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3b5c5a192abda123ad45ef716ec9082b4cf7d95e9ada8291c5c2cc5558be858b"}, 288 | {file = "cytoolz-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5b399ce7d967b1cb6280250818b786be652aa8ddffd3c0bb5c48c6220d945ab5"}, 289 | {file = "cytoolz-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e7e29a1a03f00b4322196cfe8e2c38da9a6c8d573566052c586df83aacc5663c"}, 290 | {file = "cytoolz-1.1.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5291b117d71652a817ec164e7011f18e6a51f8a352cc9a70ed5b976c51102fda"}, 291 | {file = "cytoolz-1.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8caef62f846a9011676c51bda9189ae394cdd6bb17f2946ecaedc23243268320"}, 292 | {file = "cytoolz-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:de425c5a8e3be7bb3a195e19191d28d9eb3c2038046064a92edc4505033ec9cb"}, 293 | {file = "cytoolz-1.1.0-cp312-cp312-win32.whl", hash = "sha256:296440a870e8d1f2e1d1edf98f60f1532b9d3ab8dfbd4b25ec08cd76311e79e5"}, 294 | {file = "cytoolz-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:07156987f224c6dac59aa18fb8bf91e1412f5463961862716a3381bf429c8699"}, 295 | {file = "cytoolz-1.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:23e616b38f5b3160c7bb45b0f84a8f3deb4bd26b29fb2dfc716f241c738e27b8"}, 296 | {file = "cytoolz-1.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:76c9b58555300be6dde87a41faf1f97966d79b9a678b7a526fcff75d28ef4945"}, 297 | {file = "cytoolz-1.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d1d638b10d3144795655e9395566ce35807df09219fd7cacd9e6acbdef67946a"}, 298 | {file = "cytoolz-1.1.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:26801c1a165e84786a99e03c9c9973356caaca002d66727b761fb1042878ef06"}, 299 | {file = "cytoolz-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a9a464542912d3272f6dccc5142df057c71c6a5cbd30439389a732df401afb7"}, 300 | {file = "cytoolz-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ed6104fa942aa5784bf54f339563de637557e3443b105760bc4de8f16a7fc79b"}, 301 | {file = "cytoolz-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56161f0ab60dc4159ec343509abaf809dc88e85c7e420e354442c62e3e7cbb77"}, 302 | {file = "cytoolz-1.1.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:832bd36cc9123535f1945acf6921f8a2a15acc19cfe4065b1c9b985a28671886"}, 303 | {file = "cytoolz-1.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1842636b6e034f229bf084c2bcdcfd36c8437e752eefd2c74ce9e2f10415cb6e"}, 304 | {file = "cytoolz-1.1.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:823df012ab90d2f2a0f92fea453528539bf71ac1879e518524cd0c86aa6df7b9"}, 305 | {file = "cytoolz-1.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2f1fcf9e7e7b3487883ff3f815abc35b89dcc45c4cf81c72b7ee457aa72d197b"}, 306 | {file = "cytoolz-1.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4cdb3fa1772116827f263f25b0cdd44c663b6701346a56411960534a06c082de"}, 307 | {file = "cytoolz-1.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1b5c95041741b81430454db65183e133976f45ac3c03454cfa8147952568529"}, 308 | {file = "cytoolz-1.1.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b2079fd9f1a65f4c61e6278c8a6d4f85edf30c606df8d5b32f1add88cbbe2286"}, 309 | {file = "cytoolz-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a92a320d72bef1c7e2d4c6d875125cf57fc38be45feb3fac1bfa64ea401f54a4"}, 310 | {file = "cytoolz-1.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06d1c79aa51e6a92a90b0e456ebce2288f03dd6a76c7f582bfaa3eda7692e8a5"}, 311 | {file = "cytoolz-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e1d7be25f6971e986a52b6d3a0da28e1941850985417c35528f6823aef2cfec5"}, 312 | {file = "cytoolz-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:964b248edc31efc50a65e9eaa0c845718503823439d2fa5f8d2c7e974c2b5409"}, 313 | {file = "cytoolz-1.1.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c9ff2b3c57c79b65cb5be14a18c6fd4a06d5036fb3f33e973a9f70e9ac13ca28"}, 314 | {file = "cytoolz-1.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:22290b73086af600042d99f5ce52a43d4ad9872c382610413176e19fc1d4fd2d"}, 315 | {file = "cytoolz-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a2ade74fccd080ea793382968913ee38d7a35c921df435bbf0a6aeecf0d17574"}, 316 | {file = "cytoolz-1.1.0-cp313-cp313-win32.whl", hash = "sha256:db5dbcfda1c00e937426cbf9bdc63c24ebbc358c3263bfcbc1ab4a88dc52aa8e"}, 317 | {file = "cytoolz-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9e2d3fe3b45c3eb7233746f7aca37789be3dceec3e07dcc406d3e045ea0f7bdc"}, 318 | {file = "cytoolz-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:32c559f95ff44a9ebcbd934acaa1e6dc8f3e6ffce4762a79a88528064873d6d5"}, 319 | {file = "cytoolz-1.1.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9e2cd93b28f667c5870a070ab2b8bb4397470a85c4b204f2454b0ad001cd1ca3"}, 320 | {file = "cytoolz-1.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f494124e141a9361f31d79875fe7ea459a3be2b9dadd90480427c0c52a0943d4"}, 321 | {file = "cytoolz-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:53a3262bf221f19437ed544bf8c0e1980c81ac8e2a53d87a9bc075dba943d36f"}, 322 | {file = "cytoolz-1.1.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:47663e57d3f3f124921f38055e86a1022d0844c444ede2e8f090d3bbf80deb65"}, 323 | {file = "cytoolz-1.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a5a8755c4104ee4e3d5ba434c543b5f85fdee6a1f1df33d93f518294da793a60"}, 324 | {file = "cytoolz-1.1.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4d96ff3d381423af1b105295f97de86d1db51732c9566eb37378bab6670c5010"}, 325 | {file = "cytoolz-1.1.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0ec96b3d537cdf47d4e76ded199f7440715f4c71029b45445cff92c1248808c2"}, 326 | {file = "cytoolz-1.1.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:208e2f2ef90a32b0acbff3303d90d89b13570a228d491d2e622a7883a3c68148"}, 327 | {file = "cytoolz-1.1.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d416a81bb0bd517558668e49d30a7475b5445f9bbafaab7dcf066f1e9adba36"}, 328 | {file = "cytoolz-1.1.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f32e94c91ffe49af04835ee713ebd8e005c85ebe83e7e1fdcc00f27164c2d636"}, 329 | {file = "cytoolz-1.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15d0c6405efc040499c46df44056a5c382f551a7624a41cf3e4c84a96b988a15"}, 330 | {file = "cytoolz-1.1.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:bf069c5381d757debae891401b88b3a346ba3a28ca45ba9251103b282463fad8"}, 331 | {file = "cytoolz-1.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d5cf15892e63411ec1bd67deff0e84317d974e6ab2cdfefdd4a7cea2989df66"}, 332 | {file = "cytoolz-1.1.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3e3872c21170f8341656f8692f8939e8800dcee6549ad2474d4c817bdefd62cd"}, 333 | {file = "cytoolz-1.1.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:b9ddeff8e8fd65eb1fcefa61018100b2b627e759ea6ad275d2e2a93ffac147bf"}, 334 | {file = "cytoolz-1.1.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:02feeeda93e1fa3b33414eb57c2b0aefd1db8f558dd33fdfcce664a0f86056e4"}, 335 | {file = "cytoolz-1.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d08154ad45349162b6c37f12d5d1b2e6eef338e657b85e1621e4e6a4a69d64cb"}, 336 | {file = "cytoolz-1.1.0-cp313-cp313t-win32.whl", hash = "sha256:10ae4718a056948d73ca3e1bb9ab1f95f897ec1e362f829b9d37cc29ab566c60"}, 337 | {file = "cytoolz-1.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:1bb77bc6197e5cb19784b6a42bb0f8427e81737a630d9d7dda62ed31733f9e6c"}, 338 | {file = "cytoolz-1.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:563dda652c6ff52d215704fbe6b491879b78d7bbbb3a9524ec8e763483cb459f"}, 339 | {file = "cytoolz-1.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d542cee7c7882d2a914a33dec4d3600416fb336734df979473249d4c53d207a1"}, 340 | {file = "cytoolz-1.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:31922849b701b0f24bb62e56eb2488dcd3aa6ae3057694bd6b3b7c4c2bc27c2f"}, 341 | {file = "cytoolz-1.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e68308d32afd31943314735c1335e4ab5696110e96b405f6bdb8f2a8dc771a16"}, 342 | {file = "cytoolz-1.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fc4bb48b3b866e1867f7c6411a4229e5b44be3989060663713e10efc24c9bd5f"}, 343 | {file = "cytoolz-1.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:456f77207d1445025d7ef262b8370a05492dcb1490cb428b0f3bf1bd744a89b0"}, 344 | {file = "cytoolz-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:174ebc71ebb20a9baeffce6ee07ee2cd913754325c93f99d767380d8317930f7"}, 345 | {file = "cytoolz-1.1.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8b3604fef602bcd53415055a4f68468339192fd17be39e687ae24f476d23d56e"}, 346 | {file = "cytoolz-1.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3604b959a01f64c366e7d10ec7634d5f5cfe10301e27a8f090f6eb3b2a628a18"}, 347 | {file = "cytoolz-1.1.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6db2127a3c1bc2f59f08010d2ae53a760771a9de2f67423ad8d400e9ba4276e8"}, 348 | {file = "cytoolz-1.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56584745ac647993a016a21bc76399113b7595e312f8d0a1b140c9fcf9b58a27"}, 349 | {file = "cytoolz-1.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db2c4c3a7f7bd7e03bb1a236a125c8feb86c75802f4ecda6ecfaf946610b2930"}, 350 | {file = "cytoolz-1.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48cb8a692111a285d2b9acd16d185428176bfbffa8a7c274308525fccd01dd42"}, 351 | {file = "cytoolz-1.1.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d2f344ba5eb17dcf38ee37fdde726f69053f54927db8f8a1bed6ac61e5b1890d"}, 352 | {file = "cytoolz-1.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:abf76b1c1abd031f098f293b6d90ee08bdaa45f8b5678430e331d991b82684b1"}, 353 | {file = "cytoolz-1.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:ddf9a38a5b686091265ff45b53d142e44a538cd6c2e70610d3bc6be094219032"}, 354 | {file = "cytoolz-1.1.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:946786755274f07bb2be0400f28adb31d7d85a7c7001873c0a8e24a503428fb3"}, 355 | {file = "cytoolz-1.1.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:d5b8f78b9fed79cf185ad4ddec099abeef45951bdcb416c5835ba05f0a1242c7"}, 356 | {file = "cytoolz-1.1.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fccde6efefdbc02e676ccb352a2ccc8a8e929f59a1c6d3d60bb78e923a49ca44"}, 357 | {file = "cytoolz-1.1.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:717b7775313da5f51b0fbf50d865aa9c39cb241bd4cb605df3cf2246d6567397"}, 358 | {file = "cytoolz-1.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5158744a09d0e0e4a4f82225e3a3c4ebf38f9ae74467aaa905467270e52f2794"}, 359 | {file = "cytoolz-1.1.0-cp314-cp314-win32.whl", hash = "sha256:1ed534bdbbf063b2bb28fca7d0f6723a3e5a72b086e7c7fe6d74ae8c3e4d00e2"}, 360 | {file = "cytoolz-1.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:472c1c9a085f5ad973ec0ad7f0b9ba0969faea6f96c9e397f6293d386f3a25ec"}, 361 | {file = "cytoolz-1.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:a7ad7ca3386fa86bd301be3fa36e7f0acb024f412f665937955acfc8eb42deff"}, 362 | {file = "cytoolz-1.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:64b63ed4b71b1ba813300ad0f06b8aff19a12cf51116e0e4f1ed837cea4debcf"}, 363 | {file = "cytoolz-1.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a60ba6f2ed9eb0003a737e1ee1e9fa2258e749da6477946008d4324efa25149f"}, 364 | {file = "cytoolz-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1aa58e2434d732241f7f051e6f17657e969a89971025e24578b5cbc6f1346485"}, 365 | {file = "cytoolz-1.1.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6965af3fc7214645970e312deb9bd35a213a1eaabcfef4f39115e60bf2f76867"}, 366 | {file = "cytoolz-1.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ddd2863f321d67527d3b67a93000a378ad6f967056f68c06467fe011278a6d0e"}, 367 | {file = "cytoolz-1.1.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4e6b428e9eb5126053c2ae0efa62512ff4b38ed3951f4d0888ca7005d63e56f5"}, 368 | {file = "cytoolz-1.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d758e5ef311d2671e0ae8c214c52e44617cf1e58bef8f022b547b9802a5a7f30"}, 369 | {file = "cytoolz-1.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a95416eca473e6c1179b48d86adcf528b59c63ce78f4cb9934f2e413afa9b56b"}, 370 | {file = "cytoolz-1.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36c8ede93525cf11e2cc787b7156e5cecd7340193ef800b816a16f1404a8dc6d"}, 371 | {file = "cytoolz-1.1.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c949755b6d8a649c5fbc888bc30915926f1b09fe42fea9f289e297c2f6ddd3"}, 372 | {file = "cytoolz-1.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e1b6d37545816905a76d9ed59fa4e332f929e879f062a39ea0f6f620405cdc27"}, 373 | {file = "cytoolz-1.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:05332112d4087904842b36954cd1d3fc0e463a2f4a7ef9477bd241427c593c3b"}, 374 | {file = "cytoolz-1.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:31538ca2fad2d688cbd962ccc3f1da847329e2258a52940f10a2ac0719e526be"}, 375 | {file = "cytoolz-1.1.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:747562aa70abf219ea16f07d50ac0157db856d447f7f498f592e097cbc77df0b"}, 376 | {file = "cytoolz-1.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:3dc15c48b20c0f467e15e341e102896c8422dccf8efc6322def5c1b02f074629"}, 377 | {file = "cytoolz-1.1.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3c03137ee6103ba92d5d6ad6a510e86fded69cd67050bd8a1843f15283be17ac"}, 378 | {file = "cytoolz-1.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:be8e298d88f88bd172b59912240558be3b7a04959375646e7fd4996401452941"}, 379 | {file = "cytoolz-1.1.0-cp314-cp314t-win32.whl", hash = "sha256:3d407140f5604a89578285d4aac7b18b8eafa055cf776e781aabb89c48738fad"}, 380 | {file = "cytoolz-1.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:56e5afb69eb6e1b3ffc34716ee5f92ffbdb5cb003b3a5ca4d4b0fe700e217162"}, 381 | {file = "cytoolz-1.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:27b19b4a286b3ff52040efa42dbe403730aebe5fdfd2def704eb285e2125c63e"}, 382 | {file = "cytoolz-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:08a63935c66488511b7b29b06233be0be5f4123622fc8fd488f28dc1b7e4c164"}, 383 | {file = "cytoolz-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:93bd0afcc4cc05794507084afaefb161c3639f283ee629bd0e8654b5c0327ba8"}, 384 | {file = "cytoolz-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f3d4da470cfd5cf44f6d682c6eb01363066e0af53ebe111225e44a618f9453d"}, 385 | {file = "cytoolz-1.1.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ba6c12d0e6a67399f4102b4980f4f1bebdbf226ed0a68e84617709d4009b4e71"}, 386 | {file = "cytoolz-1.1.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b557071405b4aeeaa7cbec1a95d15d6c8f37622fe3f4b595311e0e226ce772c"}, 387 | {file = "cytoolz-1.1.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cdb406001474726a47fbe903f3aba0de86f5c0b9c9861f55c09c366368225ae0"}, 388 | {file = "cytoolz-1.1.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b6072876ba56446d9ac29d349983677d6f44c6d1c6c1c6be44e66e377c57c767"}, 389 | {file = "cytoolz-1.1.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c3784c965c9a6822d315d099c3a85b0884ac648952815891c667b469116f1d0"}, 390 | {file = "cytoolz-1.1.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cc537ad78981df1a827773069fd3b7774f4478db43f518b1616efaf87d7d8f9"}, 391 | {file = "cytoolz-1.1.0-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:574ee9dfdc632db8bf9237f27f2a687d1a0b90d29d5e96cab2b21fd2b419c17d"}, 392 | {file = "cytoolz-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6594efbaea72dc58b368b53e745ad902c8d8cc41286f00b3743ceac464d5ef3f"}, 393 | {file = "cytoolz-1.1.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:7c849f9ddaf3c7faba938440f9c849235a2908b303063d49da3092a93acd695b"}, 394 | {file = "cytoolz-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1fef0296fb3577d0a08ad9b70344ee418f728f1ec21a768ffe774437d67ac859"}, 395 | {file = "cytoolz-1.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:1dce1e66fdf72cc474367bd7a7f2b90ec67bb8197dc3fe8ecd08f4ce3ab950a1"}, 396 | {file = "cytoolz-1.1.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:202fe9975efaec0085cab14a6a6050418bc041f5316f2cf098c0cd2aced4c50e"}, 397 | {file = "cytoolz-1.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:528349434601b9d55e65c6a495494de0001c9a06b431547fea4c60b5edc7d5b3"}, 398 | {file = "cytoolz-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3e248cdbf2a54bafdadf4486ddd32e8352f816d3caa2014e44de99f8c525d4a8"}, 399 | {file = "cytoolz-1.1.0-cp39-cp39-win32.whl", hash = "sha256:e63f2b70f4654648a5c6a176ae80897c0de6401f385540dce8e365019e800cfe"}, 400 | {file = "cytoolz-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:f731c53ed29959f105ae622b62e39603c207ed8e8cb2a40cd4accb63d9f92901"}, 401 | {file = "cytoolz-1.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:5a2120bf9e6e8f25e1b32748424a5571e319ef03a995a8fde663fd2feec1a696"}, 402 | {file = "cytoolz-1.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f32e93a55681d782fc6af939f6df36509d65122423cbc930be39b141064adff8"}, 403 | {file = "cytoolz-1.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5d9bc596751cbda8073e65be02ca11706f00029768fbbbc81e11a8c290bb41aa"}, 404 | {file = "cytoolz-1.1.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9b16660d01c3931951fab49db422c627897c38c1a1f0393a97582004019a4887"}, 405 | {file = "cytoolz-1.1.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b7de5718e2113d4efccea3f06055758cdbc17388ecc3341ba4d1d812837d7c1a"}, 406 | {file = "cytoolz-1.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a12a2a1a6bc44099491c05a12039efa08cc33a3d0f8c7b0566185e085e139283"}, 407 | {file = "cytoolz-1.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:047defa7f5f9a32f82373dbc3957289562e8a3fa58ae02ec8e4dca4f43a33a21"}, 408 | {file = "cytoolz-1.1.0.tar.gz", hash = "sha256:13a7bf254c3c0d28b12e2290b82aed0f0977a4c2a2bf84854fcdc7796a29f3b0"}, 409 | ] 410 | 411 | [package.dependencies] 412 | toolz = ">=0.8.0" 413 | 414 | [package.extras] 415 | cython = ["cython (>=0.29)"] 416 | test = ["pytest"] 417 | 418 | [[package]] 419 | name = "decorator" 420 | version = "5.2.1" 421 | description = "Decorators for Humans" 422 | optional = false 423 | python-versions = ">=3.8" 424 | groups = ["dev"] 425 | markers = "python_version >= \"3.11\"" 426 | files = [ 427 | {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, 428 | {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, 429 | ] 430 | 431 | [[package]] 432 | name = "eth-hash" 433 | version = "0.7.1" 434 | description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" 435 | optional = true 436 | python-versions = "<4,>=3.8" 437 | groups = ["main"] 438 | markers = "extra == \"eth\"" 439 | files = [ 440 | {file = "eth_hash-0.7.1-py3-none-any.whl", hash = "sha256:0fb1add2adf99ef28883fd6228eb447ef519ea72933535ad1a0b28c6f65f868a"}, 441 | {file = "eth_hash-0.7.1.tar.gz", hash = "sha256:d2411a403a0b0a62e8247b4117932d900ffb4c8c64b15f92620547ca5ce46be5"}, 442 | ] 443 | 444 | [package.extras] 445 | dev = ["build (>=0.9.0)", "bump_my_version (>=0.19.0)", "ipython", "mypy (==1.10.0)", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx_rtd_theme (>=1.0.0)", "towncrier (>=24,<25)", "tox (>=4.0.0)", "twine", "wheel"] 446 | docs = ["sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx_rtd_theme (>=1.0.0)", "towncrier (>=24,<25)"] 447 | pycryptodome = ["pycryptodome (>=3.6.6,<4)"] 448 | pysha3 = ["pysha3 (>=1.0.0,<2.0.0) ; python_version < \"3.9\"", "safe-pysha3 (>=1.0.0) ; python_version >= \"3.9\""] 449 | test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] 450 | 451 | [[package]] 452 | name = "eth-keys" 453 | version = "0.7.0" 454 | description = "eth-keys: Common API for Ethereum key operations" 455 | optional = true 456 | python-versions = "<4,>=3.8" 457 | groups = ["main"] 458 | markers = "extra == \"eth\"" 459 | files = [ 460 | {file = "eth_keys-0.7.0-py3-none-any.whl", hash = "sha256:b0cdda8ffe8e5ba69c7c5ca33f153828edcace844f67aabd4542d7de38b159cf"}, 461 | {file = "eth_keys-0.7.0.tar.gz", hash = "sha256:79d24fd876201df67741de3e3fefb3f4dbcbb6ace66e47e6fe662851a4547814"}, 462 | ] 463 | 464 | [package.dependencies] 465 | eth-typing = ">=3" 466 | eth-utils = ">=2" 467 | 468 | [package.extras] 469 | coincurve = ["coincurve (>=17.0.0)"] 470 | dev = ["asn1tools (>=0.146.2)", "build (>=0.9.0)", "bump_my_version (>=0.19.0)", "coincurve (>=17.0.0)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "hypothesis (>=5.10.3)", "ipython", "mypy (==1.10.0)", "pre-commit (>=3.4.0)", "pyasn1 (>=0.4.5)", "pytest (>=7.0.0)", "towncrier (>=24,<25)", "tox (>=4.0.0)", "twine", "wheel"] 471 | docs = ["towncrier (>=24,<25)"] 472 | test = ["asn1tools (>=0.146.2)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "hypothesis (>=5.10.3)", "pyasn1 (>=0.4.5)", "pytest (>=7.0.0)"] 473 | 474 | [[package]] 475 | name = "eth-typing" 476 | version = "5.2.1" 477 | description = "eth-typing: Common type annotations for ethereum python packages" 478 | optional = false 479 | python-versions = "<4,>=3.8" 480 | groups = ["main", "dev"] 481 | files = [ 482 | {file = "eth_typing-5.2.1-py3-none-any.whl", hash = "sha256:b0c2812ff978267563b80e9d701f487dd926f1d376d674f3b535cfe28b665d3d"}, 483 | {file = "eth_typing-5.2.1.tar.gz", hash = "sha256:7557300dbf02a93c70fa44af352b5c4a58f94e997a0fd6797fb7d1c29d9538ee"}, 484 | ] 485 | 486 | [package.dependencies] 487 | typing_extensions = ">=4.5.0" 488 | 489 | [package.extras] 490 | dev = ["build (>=0.9.0)", "bump_my_version (>=0.19.0)", "ipython", "mypy (==1.10.0)", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx_rtd_theme (>=1.0.0)", "towncrier (>=24,<25)", "tox (>=4.0.0)", "twine", "wheel"] 491 | docs = ["sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx_rtd_theme (>=1.0.0)", "towncrier (>=24,<25)"] 492 | test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] 493 | 494 | [[package]] 495 | name = "eth-utils" 496 | version = "5.3.1" 497 | description = "eth-utils: Common utility functions for python code that interacts with Ethereum" 498 | optional = true 499 | python-versions = "<4,>=3.8" 500 | groups = ["main"] 501 | markers = "extra == \"eth\"" 502 | files = [ 503 | {file = "eth_utils-5.3.1-py3-none-any.whl", hash = "sha256:1f5476d8f29588d25b8ae4987e1ffdfae6d4c09026e476c4aad13b32dda3ead0"}, 504 | {file = "eth_utils-5.3.1.tar.gz", hash = "sha256:c94e2d2abd024a9a42023b4ddc1c645814ff3d6a737b33d5cfd890ebf159c2d1"}, 505 | ] 506 | 507 | [package.dependencies] 508 | cytoolz = {version = ">=0.10.1", markers = "implementation_name == \"cpython\""} 509 | eth-hash = ">=0.3.1" 510 | eth-typing = ">=5.0.0" 511 | pydantic = ">=2.0.0,<3" 512 | toolz = {version = ">0.8.2", markers = "implementation_name == \"pypy\""} 513 | 514 | [package.extras] 515 | dev = ["build (>=0.9.0)", "bump_my_version (>=0.19.0)", "eth-hash[pycryptodome]", "hypothesis (>=4.43.0)", "ipython", "mypy (==1.10.0)", "mypy (==1.10.0)", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx_rtd_theme (>=1.0.0)", "towncrier (>=24,<25)", "tox (>=4.0.0)", "twine", "wheel"] 516 | docs = ["sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx_rtd_theme (>=1.0.0)", "towncrier (>=24,<25)"] 517 | test = ["hypothesis (>=4.43.0)", "mypy (==1.10.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] 518 | 519 | [[package]] 520 | name = "exceptiongroup" 521 | version = "1.3.0" 522 | description = "Backport of PEP 654 (exception groups)" 523 | optional = false 524 | python-versions = ">=3.7" 525 | groups = ["test"] 526 | markers = "python_version < \"3.11\"" 527 | files = [ 528 | {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, 529 | {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, 530 | ] 531 | 532 | [package.dependencies] 533 | typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} 534 | 535 | [package.extras] 536 | test = ["pytest (>=6)"] 537 | 538 | [[package]] 539 | name = "executing" 540 | version = "2.2.1" 541 | description = "Get the currently executing AST node of a frame, and other information" 542 | optional = false 543 | python-versions = ">=3.8" 544 | groups = ["dev"] 545 | markers = "python_version >= \"3.11\"" 546 | files = [ 547 | {file = "executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017"}, 548 | {file = "executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4"}, 549 | ] 550 | 551 | [package.extras] 552 | tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""] 553 | 554 | [[package]] 555 | name = "iniconfig" 556 | version = "2.1.0" 557 | description = "brain-dead simple config-ini parsing" 558 | optional = false 559 | python-versions = ">=3.8" 560 | groups = ["test"] 561 | files = [ 562 | {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, 563 | {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, 564 | ] 565 | 566 | [[package]] 567 | name = "ipython" 568 | version = "9.7.0" 569 | description = "IPython: Productive Interactive Computing" 570 | optional = false 571 | python-versions = ">=3.11" 572 | groups = ["dev"] 573 | markers = "python_version >= \"3.11\"" 574 | files = [ 575 | {file = "ipython-9.7.0-py3-none-any.whl", hash = "sha256:bce8ac85eb9521adc94e1845b4c03d88365fd6ac2f4908ec4ed1eb1b0a065f9f"}, 576 | {file = "ipython-9.7.0.tar.gz", hash = "sha256:5f6de88c905a566c6a9d6c400a8fed54a638e1f7543d17aae2551133216b1e4e"}, 577 | ] 578 | 579 | [package.dependencies] 580 | colorama = {version = ">=0.4.4", markers = "sys_platform == \"win32\""} 581 | decorator = ">=4.3.2" 582 | ipython-pygments-lexers = ">=1.0.0" 583 | jedi = ">=0.18.1" 584 | matplotlib-inline = ">=0.1.5" 585 | pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} 586 | prompt_toolkit = ">=3.0.41,<3.1.0" 587 | pygments = ">=2.11.0" 588 | stack_data = ">=0.6.0" 589 | traitlets = ">=5.13.0" 590 | typing_extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} 591 | 592 | [package.extras] 593 | all = ["ipython[doc,matplotlib,test,test-extra]"] 594 | black = ["black"] 595 | doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[matplotlib,test]", "setuptools (>=70.0)", "sphinx (>=8.0)", "sphinx-rtd-theme (>=0.1.8)", "sphinx_toml (==0.0.4)", "typing_extensions"] 596 | matplotlib = ["matplotlib (>3.9)"] 597 | test = ["packaging (>=20.1.0)", "pytest (>=7.0.0)", "pytest-asyncio (>=1.0.0)", "setuptools (>=61.2)", "testpath (>=0.2)"] 598 | test-extra = ["curio", "ipykernel (>6.30)", "ipython[matplotlib]", "ipython[test]", "jupyter_ai", "nbclient", "nbformat", "numpy (>=1.27)", "pandas (>2.1)", "trio (>=0.1.0)"] 599 | 600 | [[package]] 601 | name = "ipython-pygments-lexers" 602 | version = "1.1.1" 603 | description = "Defines a variety of Pygments lexers for highlighting IPython code." 604 | optional = false 605 | python-versions = ">=3.8" 606 | groups = ["dev"] 607 | markers = "python_version >= \"3.11\"" 608 | files = [ 609 | {file = "ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c"}, 610 | {file = "ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81"}, 611 | ] 612 | 613 | [package.dependencies] 614 | pygments = "*" 615 | 616 | [[package]] 617 | name = "jedi" 618 | version = "0.19.2" 619 | description = "An autocompletion tool for Python that can be used for text editors." 620 | optional = false 621 | python-versions = ">=3.6" 622 | groups = ["dev"] 623 | markers = "python_version >= \"3.11\"" 624 | files = [ 625 | {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, 626 | {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, 627 | ] 628 | 629 | [package.dependencies] 630 | parso = ">=0.8.4,<0.9.0" 631 | 632 | [package.extras] 633 | docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] 634 | qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] 635 | testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] 636 | 637 | [[package]] 638 | name = "matplotlib-inline" 639 | version = "0.2.1" 640 | description = "Inline Matplotlib backend for Jupyter" 641 | optional = false 642 | python-versions = ">=3.9" 643 | groups = ["dev"] 644 | markers = "python_version >= \"3.11\"" 645 | files = [ 646 | {file = "matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76"}, 647 | {file = "matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe"}, 648 | ] 649 | 650 | [package.dependencies] 651 | traitlets = "*" 652 | 653 | [package.extras] 654 | test = ["flake8", "nbdime", "nbval", "notebook", "pytest"] 655 | 656 | [[package]] 657 | name = "packaging" 658 | version = "25.0" 659 | description = "Core utilities for Python packages" 660 | optional = false 661 | python-versions = ">=3.8" 662 | groups = ["test"] 663 | files = [ 664 | {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, 665 | {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, 666 | ] 667 | 668 | [[package]] 669 | name = "parso" 670 | version = "0.8.5" 671 | description = "A Python Parser" 672 | optional = false 673 | python-versions = ">=3.6" 674 | groups = ["dev"] 675 | markers = "python_version >= \"3.11\"" 676 | files = [ 677 | {file = "parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887"}, 678 | {file = "parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a"}, 679 | ] 680 | 681 | [package.extras] 682 | qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] 683 | testing = ["docopt", "pytest"] 684 | 685 | [[package]] 686 | name = "pexpect" 687 | version = "4.9.0" 688 | description = "Pexpect allows easy control of interactive console applications." 689 | optional = false 690 | python-versions = "*" 691 | groups = ["dev"] 692 | markers = "python_version >= \"3.11\" and sys_platform != \"win32\" and sys_platform != \"emscripten\"" 693 | files = [ 694 | {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, 695 | {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, 696 | ] 697 | 698 | [package.dependencies] 699 | ptyprocess = ">=0.5" 700 | 701 | [[package]] 702 | name = "pluggy" 703 | version = "1.6.0" 704 | description = "plugin and hook calling mechanisms for python" 705 | optional = false 706 | python-versions = ">=3.9" 707 | groups = ["test"] 708 | files = [ 709 | {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, 710 | {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, 711 | ] 712 | 713 | [package.extras] 714 | dev = ["pre-commit", "tox"] 715 | testing = ["coverage", "pytest", "pytest-benchmark"] 716 | 717 | [[package]] 718 | name = "prompt-toolkit" 719 | version = "3.0.52" 720 | description = "Library for building powerful interactive command lines in Python" 721 | optional = false 722 | python-versions = ">=3.8" 723 | groups = ["dev"] 724 | markers = "python_version >= \"3.11\"" 725 | files = [ 726 | {file = "prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955"}, 727 | {file = "prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855"}, 728 | ] 729 | 730 | [package.dependencies] 731 | wcwidth = "*" 732 | 733 | [[package]] 734 | name = "ptyprocess" 735 | version = "0.7.0" 736 | description = "Run a subprocess in a pseudo terminal" 737 | optional = false 738 | python-versions = "*" 739 | groups = ["dev"] 740 | markers = "python_version >= \"3.11\" and sys_platform != \"win32\" and sys_platform != \"emscripten\"" 741 | files = [ 742 | {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, 743 | {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, 744 | ] 745 | 746 | [[package]] 747 | name = "pure-eval" 748 | version = "0.2.3" 749 | description = "Safely evaluate AST nodes without side effects" 750 | optional = false 751 | python-versions = "*" 752 | groups = ["dev"] 753 | markers = "python_version >= \"3.11\"" 754 | files = [ 755 | {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, 756 | {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, 757 | ] 758 | 759 | [package.extras] 760 | tests = ["pytest"] 761 | 762 | [[package]] 763 | name = "pycryptodome" 764 | version = "3.23.0" 765 | description = "Cryptographic library for Python" 766 | optional = false 767 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 768 | groups = ["main"] 769 | files = [ 770 | {file = "pycryptodome-3.23.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a176b79c49af27d7f6c12e4b178b0824626f40a7b9fed08f712291b6d54bf566"}, 771 | {file = "pycryptodome-3.23.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:573a0b3017e06f2cffd27d92ef22e46aa3be87a2d317a5abf7cc0e84e321bd75"}, 772 | {file = "pycryptodome-3.23.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:63dad881b99ca653302b2c7191998dd677226222a3f2ea79999aa51ce695f720"}, 773 | {file = "pycryptodome-3.23.0-cp27-cp27m-win32.whl", hash = "sha256:b34e8e11d97889df57166eda1e1ddd7676da5fcd4d71a0062a760e75060514b4"}, 774 | {file = "pycryptodome-3.23.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7ac1080a8da569bde76c0a104589c4f414b8ba296c0b3738cf39a466a9fb1818"}, 775 | {file = "pycryptodome-3.23.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6fe8258e2039eceb74dfec66b3672552b6b7d2c235b2dfecc05d16b8921649a8"}, 776 | {file = "pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4"}, 777 | {file = "pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae"}, 778 | {file = "pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477"}, 779 | {file = "pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7"}, 780 | {file = "pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446"}, 781 | {file = "pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265"}, 782 | {file = "pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b"}, 783 | {file = "pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d"}, 784 | {file = "pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a"}, 785 | {file = "pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625"}, 786 | {file = "pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39"}, 787 | {file = "pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27"}, 788 | {file = "pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843"}, 789 | {file = "pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490"}, 790 | {file = "pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575"}, 791 | {file = "pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b"}, 792 | {file = "pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a"}, 793 | {file = "pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f"}, 794 | {file = "pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa"}, 795 | {file = "pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886"}, 796 | {file = "pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2"}, 797 | {file = "pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c"}, 798 | {file = "pycryptodome-3.23.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:350ebc1eba1da729b35ab7627a833a1a355ee4e852d8ba0447fafe7b14504d56"}, 799 | {file = "pycryptodome-3.23.0-pp27-pypy_73-win32.whl", hash = "sha256:93837e379a3e5fd2bb00302a47aee9fdf7940d83595be3915752c74033d17ca7"}, 800 | {file = "pycryptodome-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddb95b49df036ddd264a0ad246d1be5b672000f12d6961ea2c267083a5e19379"}, 801 | {file = "pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e95564beb8782abfd9e431c974e14563a794a4944c29d6d3b7b5ea042110b4"}, 802 | {file = "pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e15c081e912c4b0d75632acd8382dfce45b258667aa3c67caf7a4d4c13f630"}, 803 | {file = "pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7fc76bf273353dc7e5207d172b83f569540fc9a28d63171061c42e361d22353"}, 804 | {file = "pycryptodome-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:45c69ad715ca1a94f778215a11e66b7ff989d792a4d63b68dc586a1da1392ff5"}, 805 | {file = "pycryptodome-3.23.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:865d83c906b0fc6a59b510deceee656b6bc1c4fa0d82176e2b77e97a420a996a"}, 806 | {file = "pycryptodome-3.23.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89d4d56153efc4d81defe8b65fd0821ef8b2d5ddf8ed19df31ba2f00872b8002"}, 807 | {file = "pycryptodome-3.23.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3f2d0aaf8080bda0587d58fc9fe4766e012441e2eed4269a77de6aea981c8be"}, 808 | {file = "pycryptodome-3.23.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64093fc334c1eccfd3933c134c4457c34eaca235eeae49d69449dc4728079339"}, 809 | {file = "pycryptodome-3.23.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ce64e84a962b63a47a592690bdc16a7eaf709d2c2697ababf24a0def566899a6"}, 810 | {file = "pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef"}, 811 | ] 812 | 813 | [[package]] 814 | name = "pydantic" 815 | version = "2.12.4" 816 | description = "Data validation using Python type hints" 817 | optional = true 818 | python-versions = ">=3.9" 819 | groups = ["main"] 820 | markers = "extra == \"eth\"" 821 | files = [ 822 | {file = "pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e"}, 823 | {file = "pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac"}, 824 | ] 825 | 826 | [package.dependencies] 827 | annotated-types = ">=0.6.0" 828 | pydantic-core = "2.41.5" 829 | typing-extensions = ">=4.14.1" 830 | typing-inspection = ">=0.4.2" 831 | 832 | [package.extras] 833 | email = ["email-validator (>=2.0.0)"] 834 | timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] 835 | 836 | [[package]] 837 | name = "pydantic-core" 838 | version = "2.41.5" 839 | description = "Core functionality for Pydantic validation and serialization" 840 | optional = true 841 | python-versions = ">=3.9" 842 | groups = ["main"] 843 | markers = "extra == \"eth\"" 844 | files = [ 845 | {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, 846 | {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, 847 | {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, 848 | {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, 849 | {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, 850 | {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, 851 | {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, 852 | {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, 853 | {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, 854 | {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, 855 | {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, 856 | {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, 857 | {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, 858 | {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, 859 | {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, 860 | {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, 861 | {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, 862 | {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, 863 | {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, 864 | {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, 865 | {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, 866 | {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, 867 | {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, 868 | {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, 869 | {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, 870 | {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, 871 | {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, 872 | {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, 873 | {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, 874 | {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, 875 | {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, 876 | {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, 877 | {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, 878 | {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, 879 | {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, 880 | {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, 881 | {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, 882 | {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, 883 | {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, 884 | {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, 885 | {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, 886 | {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, 887 | {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, 888 | {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, 889 | {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, 890 | {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, 891 | {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, 892 | {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, 893 | {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, 894 | {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, 895 | {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, 896 | {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, 897 | {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, 898 | {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, 899 | {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, 900 | {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, 901 | {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, 902 | {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, 903 | {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, 904 | {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, 905 | {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, 906 | {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, 907 | {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, 908 | {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, 909 | {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, 910 | {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, 911 | {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, 912 | {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, 913 | {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, 914 | {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, 915 | {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, 916 | {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, 917 | {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, 918 | {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, 919 | {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, 920 | {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, 921 | {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, 922 | {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, 923 | {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, 924 | {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, 925 | {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, 926 | {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, 927 | {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, 928 | {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, 929 | {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, 930 | {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, 931 | {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, 932 | {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, 933 | {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, 934 | {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, 935 | {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, 936 | {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, 937 | {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, 938 | {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, 939 | {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, 940 | {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, 941 | {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, 942 | {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, 943 | {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, 944 | {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, 945 | {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, 946 | {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, 947 | {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, 948 | {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, 949 | {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, 950 | {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, 951 | {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, 952 | {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, 953 | {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, 954 | {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, 955 | {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, 956 | {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, 957 | {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, 958 | {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, 959 | {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, 960 | {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, 961 | {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, 962 | {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, 963 | {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, 964 | {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, 965 | {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, 966 | ] 967 | 968 | [package.dependencies] 969 | typing-extensions = ">=4.14.1" 970 | 971 | [[package]] 972 | name = "pygments" 973 | version = "2.19.2" 974 | description = "Pygments is a syntax highlighting package written in Python." 975 | optional = false 976 | python-versions = ">=3.8" 977 | groups = ["dev", "test"] 978 | files = [ 979 | {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, 980 | {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, 981 | ] 982 | markers = {dev = "python_version >= \"3.11\""} 983 | 984 | [package.extras] 985 | windows-terminal = ["colorama (>=0.4.6)"] 986 | 987 | [[package]] 988 | name = "pytest" 989 | version = "8.4.2" 990 | description = "pytest: simple powerful testing with Python" 991 | optional = false 992 | python-versions = ">=3.9" 993 | groups = ["test"] 994 | files = [ 995 | {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, 996 | {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, 997 | ] 998 | 999 | [package.dependencies] 1000 | colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} 1001 | exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} 1002 | iniconfig = ">=1" 1003 | packaging = ">=20" 1004 | pluggy = ">=1.5,<2" 1005 | pygments = ">=2.7.2" 1006 | tomli = {version = ">=1", markers = "python_version < \"3.11\""} 1007 | 1008 | [package.extras] 1009 | dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] 1010 | 1011 | [[package]] 1012 | name = "pytest-cov" 1013 | version = "6.3.0" 1014 | description = "Pytest plugin for measuring coverage." 1015 | optional = false 1016 | python-versions = ">=3.9" 1017 | groups = ["test"] 1018 | files = [ 1019 | {file = "pytest_cov-6.3.0-py3-none-any.whl", hash = "sha256:440db28156d2468cafc0415b4f8e50856a0d11faefa38f30906048fe490f1749"}, 1020 | {file = "pytest_cov-6.3.0.tar.gz", hash = "sha256:35c580e7800f87ce892e687461166e1ac2bcb8fb9e13aea79032518d6e503ff2"}, 1021 | ] 1022 | 1023 | [package.dependencies] 1024 | coverage = {version = ">=7.5", extras = ["toml"]} 1025 | pluggy = ">=1.2" 1026 | pytest = ">=6.2.5" 1027 | 1028 | [package.extras] 1029 | testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] 1030 | 1031 | [[package]] 1032 | name = "ruff" 1033 | version = "0.14.5" 1034 | description = "An extremely fast Python linter and code formatter, written in Rust." 1035 | optional = false 1036 | python-versions = ">=3.7" 1037 | groups = ["dev"] 1038 | files = [ 1039 | {file = "ruff-0.14.5-py3-none-linux_armv6l.whl", hash = "sha256:f3b8248123b586de44a8018bcc9fefe31d23dda57a34e6f0e1e53bd51fd63594"}, 1040 | {file = "ruff-0.14.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f7a75236570318c7a30edd7f5491945f0169de738d945ca8784500b517163a72"}, 1041 | {file = "ruff-0.14.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6d146132d1ee115f8802356a2dc9a634dbf58184c51bff21f313e8cd1c74899a"}, 1042 | {file = "ruff-0.14.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2380596653dcd20b057794d55681571a257a42327da8894b93bbd6111aa801f"}, 1043 | {file = "ruff-0.14.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d1fa985a42b1f075a098fa1ab9d472b712bdb17ad87a8ec86e45e7fa6273e68"}, 1044 | {file = "ruff-0.14.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88f0770d42b7fa02bbefddde15d235ca3aa24e2f0137388cc15b2dcbb1f7c7a7"}, 1045 | {file = "ruff-0.14.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3676cb02b9061fee7294661071c4709fa21419ea9176087cb77e64410926eb78"}, 1046 | {file = "ruff-0.14.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b595bedf6bc9cab647c4a173a61acf4f1ac5f2b545203ba82f30fcb10b0318fb"}, 1047 | {file = "ruff-0.14.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f55382725ad0bdb2e8ee2babcbbfb16f124f5a59496a2f6a46f1d9d99d93e6e2"}, 1048 | {file = "ruff-0.14.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7497d19dce23976bdaca24345ae131a1d38dcfe1b0850ad8e9e6e4fa321a6e19"}, 1049 | {file = "ruff-0.14.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:410e781f1122d6be4f446981dd479470af86537fb0b8857f27a6e872f65a38e4"}, 1050 | {file = "ruff-0.14.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c01be527ef4c91a6d55e53b337bfe2c0f82af024cc1a33c44792d6844e2331e1"}, 1051 | {file = "ruff-0.14.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f66e9bb762e68d66e48550b59c74314168ebb46199886c5c5aa0b0fbcc81b151"}, 1052 | {file = "ruff-0.14.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d93be8f1fa01022337f1f8f3bcaa7ffee2d0b03f00922c45c2207954f351f465"}, 1053 | {file = "ruff-0.14.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c135d4b681f7401fe0e7312017e41aba9b3160861105726b76cfa14bc25aa367"}, 1054 | {file = "ruff-0.14.5-py3-none-win32.whl", hash = "sha256:c83642e6fccfb6dea8b785eb9f456800dcd6a63f362238af5fc0c83d027dd08b"}, 1055 | {file = "ruff-0.14.5-py3-none-win_amd64.whl", hash = "sha256:9d55d7af7166f143c94eae1db3312f9ea8f95a4defef1979ed516dbb38c27621"}, 1056 | {file = "ruff-0.14.5-py3-none-win_arm64.whl", hash = "sha256:4b700459d4649e2594b31f20a9de33bc7c19976d4746d8d0798ad959621d64a4"}, 1057 | {file = "ruff-0.14.5.tar.gz", hash = "sha256:8d3b48d7d8aad423d3137af7ab6c8b1e38e4de104800f0d596990f6ada1a9fc1"}, 1058 | ] 1059 | 1060 | [[package]] 1061 | name = "stack-data" 1062 | version = "0.6.3" 1063 | description = "Extract data from python stack frames and tracebacks for informative displays" 1064 | optional = false 1065 | python-versions = "*" 1066 | groups = ["dev"] 1067 | markers = "python_version >= \"3.11\"" 1068 | files = [ 1069 | {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, 1070 | {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, 1071 | ] 1072 | 1073 | [package.dependencies] 1074 | asttokens = ">=2.1.0" 1075 | executing = ">=1.2.0" 1076 | pure-eval = "*" 1077 | 1078 | [package.extras] 1079 | tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] 1080 | 1081 | [[package]] 1082 | name = "tomli" 1083 | version = "2.3.0" 1084 | description = "A lil' TOML parser" 1085 | optional = false 1086 | python-versions = ">=3.8" 1087 | groups = ["test"] 1088 | markers = "python_full_version <= \"3.11.0a6\"" 1089 | files = [ 1090 | {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, 1091 | {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, 1092 | {file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"}, 1093 | {file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}, 1094 | {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"}, 1095 | {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"}, 1096 | {file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"}, 1097 | {file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"}, 1098 | {file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"}, 1099 | {file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"}, 1100 | {file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"}, 1101 | {file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"}, 1102 | {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"}, 1103 | {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"}, 1104 | {file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"}, 1105 | {file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"}, 1106 | {file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"}, 1107 | {file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"}, 1108 | {file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"}, 1109 | {file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"}, 1110 | {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"}, 1111 | {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"}, 1112 | {file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"}, 1113 | {file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"}, 1114 | {file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"}, 1115 | {file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"}, 1116 | {file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"}, 1117 | {file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"}, 1118 | {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"}, 1119 | {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"}, 1120 | {file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"}, 1121 | {file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"}, 1122 | {file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"}, 1123 | {file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"}, 1124 | {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"}, 1125 | {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"}, 1126 | {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"}, 1127 | {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"}, 1128 | {file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"}, 1129 | {file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"}, 1130 | {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}, 1131 | {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}, 1132 | ] 1133 | 1134 | [[package]] 1135 | name = "toolz" 1136 | version = "1.1.0" 1137 | description = "List processing tools and functional utilities" 1138 | optional = true 1139 | python-versions = ">=3.9" 1140 | groups = ["main"] 1141 | markers = "extra == \"eth\" and (implementation_name == \"pypy\" or implementation_name == \"cpython\")" 1142 | files = [ 1143 | {file = "toolz-1.1.0-py3-none-any.whl", hash = "sha256:15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8"}, 1144 | {file = "toolz-1.1.0.tar.gz", hash = "sha256:27a5c770d068c110d9ed9323f24f1543e83b2f300a687b7891c1a6d56b697b5b"}, 1145 | ] 1146 | 1147 | [[package]] 1148 | name = "traitlets" 1149 | version = "5.14.3" 1150 | description = "Traitlets Python configuration system" 1151 | optional = false 1152 | python-versions = ">=3.8" 1153 | groups = ["dev"] 1154 | markers = "python_version >= \"3.11\"" 1155 | files = [ 1156 | {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, 1157 | {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, 1158 | ] 1159 | 1160 | [package.extras] 1161 | docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] 1162 | test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] 1163 | 1164 | [[package]] 1165 | name = "ty" 1166 | version = "0.0.1a27" 1167 | description = "An extremely fast Python type checker, written in Rust." 1168 | optional = false 1169 | python-versions = ">=3.8" 1170 | groups = ["dev"] 1171 | files = [ 1172 | {file = "ty-0.0.1a27-py3-none-linux_armv6l.whl", hash = "sha256:3cbb735f5ecb3a7a5f5b82fb24da17912788c109086df4e97d454c8fb236fbc5"}, 1173 | {file = "ty-0.0.1a27-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4a6367236dc456ba2416563301d498aef8c6f8959be88777ef7ba5ac1bf15f0b"}, 1174 | {file = "ty-0.0.1a27-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8e93e231a1bcde964cdb062d2d5e549c24493fb1638eecae8fcc42b81e9463a4"}, 1175 | {file = "ty-0.0.1a27-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5b6a8166b60117da1179851a3d719cc798bf7e61f91b35d76242f0059e9ae1d"}, 1176 | {file = "ty-0.0.1a27-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfbe8b0e831c072b79a078d6c126d7f4d48ca17f64a103de1b93aeda32265dc5"}, 1177 | {file = "ty-0.0.1a27-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90e09678331552e7c25d7eb47868b0910dc5b9b212ae22c8ce71a52d6576ddbb"}, 1178 | {file = "ty-0.0.1a27-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:88c03e4beeca79d85a5618921e44b3a6ea957e0453e08b1cdd418b51da645939"}, 1179 | {file = "ty-0.0.1a27-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ece5811322789fefe22fc088ed36c5879489cd39e913f9c1ff2a7678f089c61"}, 1180 | {file = "ty-0.0.1a27-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f2ccb4f0fddcd6e2017c268dfce2489e9a36cb82a5900afe6425835248b1086"}, 1181 | {file = "ty-0.0.1a27-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33450528312e41d003e96a1647780b2783ab7569bbc29c04fc76f2d1908061e3"}, 1182 | {file = "ty-0.0.1a27-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a0a9ac635deaa2b15947701197ede40cdecd13f89f19351872d16f9ccd773fa1"}, 1183 | {file = "ty-0.0.1a27-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:797fb2cd49b6b9b3ac9f2f0e401fb02d3aa155badc05a8591d048d38d28f1e0c"}, 1184 | {file = "ty-0.0.1a27-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7fe81679a0941f85e98187d444604e24b15bde0a85874957c945751756314d03"}, 1185 | {file = "ty-0.0.1a27-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:355f651d0cdb85535a82bd9f0583f77b28e3fd7bba7b7da33dcee5a576eff28b"}, 1186 | {file = "ty-0.0.1a27-py3-none-win32.whl", hash = "sha256:61782e5f40e6df622093847b34c366634b75d53f839986f1bf4481672ad6cb55"}, 1187 | {file = "ty-0.0.1a27-py3-none-win_amd64.whl", hash = "sha256:c682b238085d3191acddcf66ef22641562946b1bba2a7f316012d5b2a2f4de11"}, 1188 | {file = "ty-0.0.1a27-py3-none-win_arm64.whl", hash = "sha256:e146dfa32cbb0ac6afb0cb65659e87e4e313715e68d76fe5ae0a4b3d5b912ce8"}, 1189 | {file = "ty-0.0.1a27.tar.gz", hash = "sha256:d34fe04979f2c912700cbf0919e8f9b4eeaa10c4a2aff7450e5e4c90f998bc28"}, 1190 | ] 1191 | 1192 | [[package]] 1193 | name = "typing-extensions" 1194 | version = "4.15.0" 1195 | description = "Backported and Experimental Type Hints for Python 3.9+" 1196 | optional = false 1197 | python-versions = ">=3.9" 1198 | groups = ["main", "dev", "test"] 1199 | files = [ 1200 | {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, 1201 | {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, 1202 | ] 1203 | 1204 | [[package]] 1205 | name = "typing-inspection" 1206 | version = "0.4.2" 1207 | description = "Runtime typing introspection tools" 1208 | optional = true 1209 | python-versions = ">=3.9" 1210 | groups = ["main"] 1211 | markers = "extra == \"eth\"" 1212 | files = [ 1213 | {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, 1214 | {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, 1215 | ] 1216 | 1217 | [package.dependencies] 1218 | typing-extensions = ">=4.12.0" 1219 | 1220 | [[package]] 1221 | name = "wcwidth" 1222 | version = "0.2.14" 1223 | description = "Measures the displayed width of unicode strings in a terminal" 1224 | optional = false 1225 | python-versions = ">=3.6" 1226 | groups = ["dev"] 1227 | markers = "python_version >= \"3.11\"" 1228 | files = [ 1229 | {file = "wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1"}, 1230 | {file = "wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605"}, 1231 | ] 1232 | 1233 | [extras] 1234 | eth = ["eth-keys"] 1235 | 1236 | [metadata] 1237 | lock-version = "2.1" 1238 | python-versions = "^3.9" 1239 | content-hash = "f2bb44d75e8bb98fb412aa628e0b7069126d172136660ccbd97011583e508a72" 1240 | --------------------------------------------------------------------------------