diff --git a/.github/workflows/python_detailed.yml b/.github/workflows/python_detailed.yml index 418faae..bb78951 100644 --- a/.github/workflows/python_detailed.yml +++ b/.github/workflows/python_detailed.yml @@ -2,11 +2,11 @@ name: GitHub actions detailed on: push: - branches: ["**"] + branches: [ "**" ] pull_request: - branches: ["**"] + branches: [ "**" ] repository_dispatch: - types: ["**"] + types: [ "**" ] permissions: contents: read @@ -20,21 +20,24 @@ jobs: build: strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ ubuntu-latest, macos-latest, windows-latest ] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - name: Set up Python 3.10 - uses: actions/setup-python@v3 + - name: Install uv + uses: astral-sh/setup-uv@v3 with: - python-version: "3.10" + version: "latest" + enable-cache: true + cache-dependency-glob: "**/pyproject.toml" + + - name: Set up Python 3.9 + run: uv python install 3.9 - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install nose2 + run: uv sync --extra dev - name: Install liboqs POSIX if: matrix.os != 'windows-latest' @@ -47,17 +50,17 @@ jobs: - name: Run examples POSIX if: matrix.os != 'windows-latest' run: | - pip install . - python examples/kem.py + uv sync --extra dev + uv run examples/kem.py echo - python examples/sig.py + uv run examples/sig.py echo - python examples/rand.py + uv run examples/rand.py - name: Run unit tests POSIX if: matrix.os != 'windows-latest' run: | - nose2 --verbose + uv run nose2 --verbose - name: Install liboqs Windows if: matrix.os == 'windows-latest' @@ -73,16 +76,16 @@ jobs: shell: cmd run: | set PATH=%PATH%;${{env.WIN_LIBOQS_INSTALL_PATH}}\bin - pip install . - python examples/kem.py + uv sync --extra dev + uv run examples/kem.py echo. - python examples/sig.py + uv run examples/sig.py echo. - python examples/rand.py + uv run examples/rand.py - name: Run unit tests Windows shell: cmd if: matrix.os == 'windows-latest' run: | set PATH=%PATH%;${{env.WIN_LIBOQS_INSTALL_PATH}}\bin - nose2 --verbose + uv run nose2 --verbose diff --git a/.github/workflows/python_simplified.yml b/.github/workflows/python_simplified.yml index 387ce6e..d41c4fe 100644 --- a/.github/workflows/python_simplified.yml +++ b/.github/workflows/python_simplified.yml @@ -2,11 +2,11 @@ name: GitHub actions simplified on: push: - branches: ["**"] + branches: [ "**" ] pull_request: - branches: ["**"] + branches: [ "**" ] repository_dispatch: - types: ["**"] + types: [ "**" ] permissions: contents: read @@ -15,25 +15,29 @@ jobs: build: strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ ubuntu-latest, macos-latest, windows-latest ] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - name: Set up Python 3.10 - uses: actions/setup-python@v3 + - name: Install uv + uses: astral-sh/setup-uv@v3 with: - python-version: "3.10" + version: "latest" + enable-cache: true + cache-dependency-glob: "**/pyproject.toml" + + - name: Set up Python 3.9 + run: uv python install 3.9 - name: Run examples run: | - python -m pip install --upgrade pip - pip install . - python examples/kem.py - python examples/sig.py - python examples/rand.py + uv sync --extra dev + uv run examples/kem.py + uv run examples/sig.py + uv run examples/rand.py - name: Run unit tests run: | - nose2 --verbose + uv run nose2 --verbose diff --git a/.gitignore b/.gitignore index 6894774..f0096dd 100644 --- a/.gitignore +++ b/.gitignore @@ -117,4 +117,5 @@ pip-selfcheck.json pyvenv.cfg # vim -*.swp \ No newline at end of file +*.swp +/uv.lock diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..6c6f2db --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,38 @@ +fail_fast: false +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: "trailing-whitespace" + - id: "check-case-conflict" + - id: "check-merge-conflict" + - id: "debug-statements" + - id: "end-of-file-fixer" + - id: "mixed-line-ending" + args: [ "--fix", "crlf" ] + types: + - python + - yaml + - toml + - text + - id: "detect-private-key" + - id: "check-yaml" + - id: "check-toml" + - id: "check-json" + + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.7.3 + hooks: + - id: ruff + args: [ "--fix" ] + files: "oqs" + + - id: ruff-format + files: "oqs" + + - repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + name: isort (python) + files: "oqs" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8a2409f --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +src-dir = oqs +tests-dir = tests +examples-dir = examples + +.PHONY pull: +pull: + git pull origin master + git submodule update --init --recursive + +.PHONY lint: +lint: + echo "Running ruff..." + uv run ruff check --config pyproject.toml --diff $(src-dir) $(tests-dir) $(examples-dir) + +.PHONY format: +format: + echo "Running ruff check with --fix..." + uv run ruff check --config pyproject.toml --fix --unsafe-fixes $(src-dir) $(tests-dir) $(examples-dir) + + echo "Running ruff..." + uv run ruff format --config pyproject.toml $(src-dir) $(tests-dir) $(examples-dir) + + echo "Running isort..." + uv run isort --settings-file pyproject.toml $(src-dir) $(tests-dir) $(examples-dir) + +.PHONE mypy: +mypy: + echo "Running MyPy..." + uv run mypy --config-file pyproject.toml + +.PHONY outdated: +outdated: + uv tree --outdated --universal + +.PHONY sync: +sync: + uv sync --extra dev --extra lint diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/kem.py b/examples/kem.py index a9deb2d..4a5c826 100644 --- a/examples/kem.py +++ b/examples/kem.py @@ -1,36 +1,39 @@ # Key encapsulation Python example +import logging +from pprint import pformat import oqs -from pprint import pprint -print("liboqs version:", oqs.oqs_version()) -print("liboqs-python version:", oqs.oqs_python_version()) -print("Enabled KEM mechanisms:") -kems = oqs.get_enabled_kem_mechanisms() -pprint(kems, compact=True) +logging.basicConfig(format="%(asctime)s %(message)s", level=logging.INFO) +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +logger.info("liboqs version: %s", oqs.oqs_version()) +logger.info("liboqs-python version: %s", oqs.oqs_python_version()) +logger.info("Enabled KEM mechanisms: %s", pformat(oqs.get_enabled_kem_mechanisms(), compact=True)) # Create client and server with sample KEM mechanisms kemalg = "Kyber512" -with oqs.KeyEncapsulation(kemalg) as client: - with oqs.KeyEncapsulation(kemalg) as server: - print("\nKey encapsulation details:") - pprint(client.details) +with oqs.KeyEncapsulation(kemalg) as client, oqs.KeyEncapsulation(kemalg) as server: + # print("\nKey encapsulation details:") + logger.info("Client details: %s", pformat(client.details)) - # Client generates its keypair - public_key_client = client.generate_keypair() - # Optionally, the secret key can be obtained by calling export_secret_key() - # and the client can later be re-instantiated with the key pair: - # secret_key_client = client.export_secret_key() + # Client generates its keypair + public_key_client = client.generate_keypair() + # Optionally, the secret key can be obtained by calling export_secret_key() + # and the client can later be re-instantiated with the key pair: + # secret_key_client = client.export_secret_key() - # Store key pair, wait... (session resumption): - # client = oqs.KeyEncapsulation(kemalg, secret_key_client) + # Store key pair, wait... (session resumption): + # client = oqs.KeyEncapsulation(kemalg, secret_key_client) - # The server encapsulates its secret using the client's public key - ciphertext, shared_secret_server = server.encap_secret(public_key_client) + # The server encapsulates its secret using the client's public key + ciphertext, shared_secret_server = server.encap_secret(public_key_client) - # The client decapsulates the server's ciphertext to obtain the shared secret - shared_secret_client = client.decap_secret(ciphertext) + # The client decapsulates the server's ciphertext to obtain the shared secret + shared_secret_client = client.decap_secret(ciphertext) - print( - "\nShared secretes coincide:", shared_secret_client == shared_secret_server - ) + logger.info( + "Shared secretes coincide: %s", + shared_secret_client == shared_secret_server, + ) diff --git a/examples/rand.py b/examples/rand.py index 75e5c89..5af394f 100644 --- a/examples/rand.py +++ b/examples/rand.py @@ -1,22 +1,27 @@ # Various RNGs Python example - +import logging import platform # to learn the OS we're on + import oqs.rand as oqsrand # must be explicitly imported -from oqs import oqs_version, oqs_python_version +from oqs import oqs_python_version, oqs_version + +logging.basicConfig(format="%(asctime)s %(message)s", level=logging.INFO) +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) -print("liboqs version:", oqs_version()) -print("liboqs-python version:", oqs_python_version()) +logger.info("liboqs version: %s", oqs_version()) +logger.info("liboqs-python version: %s", oqs_python_version()) oqsrand.randombytes_switch_algorithm("system") -print( - "{:17s}".format("System (default):"), - " ".join("{:02X}".format(x) for x in oqsrand.randombytes(32)), +logger.info( + "System (default): %s", + " ".join(f"{x:02X}" for x in oqsrand.randombytes(32)), ) # We do not yet support OpenSSL under Windows if platform.system() != "Windows": oqsrand.randombytes_switch_algorithm("OpenSSL") - print( - "{:17s}".format("OpenSSL:"), - " ".join("{:02X}".format(x) for x in oqsrand.randombytes(32)), + logger.info( + "OpenSSL: %s", + " ".join(f"{x:02X}" for x in oqsrand.randombytes(32)), ) diff --git a/examples/sig.py b/examples/sig.py index ea07508..1d84297 100644 --- a/examples/sig.py +++ b/examples/sig.py @@ -1,36 +1,40 @@ # Signature Python example +import logging +from pprint import pformat import oqs -from pprint import pprint -print("liboqs version:", oqs.oqs_version()) -print("liboqs-python version:", oqs.oqs_python_version()) -print("Enabled signature mechanisms:") -sigs = oqs.get_enabled_sig_mechanisms() -pprint(sigs, compact=True) +logging.basicConfig(format="%(asctime)s %(message)s", level=logging.INFO) +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) -message = "This is the message to sign".encode() +logger.info("liboqs version: %s", oqs.oqs_version()) +logger.info("liboqs-python version: %s", oqs.oqs_python_version()) +logger.info( + "Enabled signature mechanisms: %s", + pformat(oqs.get_enabled_sig_mechanisms(), compact=True), +) + +message = b"This is the message to sign" # Create signer and verifier with sample signature mechanisms sigalg = "Dilithium2" -with oqs.Signature(sigalg) as signer: - with oqs.Signature(sigalg) as verifier: - print("\nSignature details:") - pprint(signer.details) +with oqs.Signature(sigalg) as signer, oqs.Signature(sigalg) as verifier: + logger.info("Signature details: %s", pformat(signer.details)) - # Signer generates its keypair - signer_public_key = signer.generate_keypair() - # Optionally, the secret key can be obtained by calling export_secret_key() - # and the signer can later be re-instantiated with the key pair: - # secret_key = signer.export_secret_key() + # Signer generates its keypair + signer_public_key = signer.generate_keypair() + # Optionally, the secret key can be obtained by calling export_secret_key() + # and the signer can later be re-instantiated with the key pair: + # secret_key = signer.export_secret_key() - # Store key pair, wait... (session resumption): - # signer = oqs.Signature(sigalg, secret_key) + # Store key pair, wait... (session resumption): + # signer = oqs.Signature(sigalg, secret_key) - # Signer signs the message - signature = signer.sign(message) + # Signer signs the message + signature = signer.sign(message) - # Verifier verifies the signature - is_valid = verifier.verify(message, signature, signer_public_key) + # Verifier verifies the signature + is_valid = verifier.verify(message, signature, signer_public_key) - print("\nValid signature?", is_valid) + logger.info("Valid signature? %s", is_valid) diff --git a/oqs/__init__.py b/oqs/__init__.py index f82845b..6dab0c3 100644 --- a/oqs/__init__.py +++ b/oqs/__init__.py @@ -1 +1,35 @@ -from oqs.oqs import * +from oqs.oqs import ( + OQS_SUCCESS, + OQS_VERSION, + KeyEncapsulation, + MechanismNotEnabledError, + MechanismNotSupportedError, + Signature, + get_enabled_kem_mechanisms, + get_enabled_sig_mechanisms, + get_supported_kem_mechanisms, + get_supported_sig_mechanisms, + is_kem_enabled, + is_sig_enabled, + native, + oqs_python_version, + oqs_version, +) + +__all__ = ( + "KeyEncapsulation", + "MechanismNotEnabledError", + "MechanismNotSupportedError", + "OQS_SUCCESS", + "OQS_VERSION", + "Signature", + "get_enabled_kem_mechanisms", + "get_enabled_sig_mechanisms", + "get_supported_kem_mechanisms", + "get_supported_sig_mechanisms", + "is_kem_enabled", + "is_sig_enabled", + "native", + "oqs_python_version", + "oqs_version", +) diff --git a/oqs/oqs.py b/oqs/oqs.py index fc147da..a244ed6 100644 --- a/oqs/oqs.py +++ b/oqs/oqs.py @@ -1,5 +1,5 @@ """ -Open Quantum Safe (OQS) Python wrapper for liboqs +Open Quantum Safe (OQS) Python wrapper for liboqs. The liboqs project provides post-quantum public key cryptography algorithms: https://github.com/open-quantum-safe/liboqs @@ -7,23 +7,37 @@ This module provides a Python 3 interface to liboqs. """ +from __future__ import annotations + import ctypes as ct # to call native import ctypes.util as ctu import importlib.metadata # to determine module version at runtime -import os # to run OS commands (install liboqs on demand if not found) +import logging import platform # to learn the OS we're on +import subprocess import sys import tempfile # to install liboqs on demand import time import warnings +from pathlib import Path +from typing import TYPE_CHECKING, Any, ClassVar, Final, TypeVar, cast + +if TYPE_CHECKING: + from collections.abc import Sequence + from types import TracebackType + +TKeyEncapsulation = TypeVar("TKeyEncapsulation", bound="KeyEncapsulation") +TSignature = TypeVar("TSignature", bound="Signature") +logger = logging.getLogger(__name__) -def oqs_python_version(): + +def oqs_python_version() -> str | None: """liboqs-python version string.""" try: result = importlib.metadata.version("liboqs-python") except importlib.metadata.PackageNotFoundError: - warnings.warn("Please install liboqs-python using pip install") + warnings.warn("Please install liboqs-python using pip install", stacklevel=2) return None return result @@ -33,115 +47,155 @@ def oqs_python_version(): OQS_VERSION = oqs_python_version() -def _countdown(seconds): +def _countdown(seconds: int) -> None: while seconds > 0: - print(seconds, end=" ") + logger.info("Installing in %s seconds...", seconds) sys.stdout.flush() seconds -= 1 time.sleep(1) - print() -def _load_shared_obj(name, additional_searching_paths=None): - """Attempts to load shared library.""" - paths = [] +def _load_shared_obj( + name: str, + additional_searching_paths: Sequence[Path] | None = None, +) -> ct.CDLL: + """Attempt to load shared library.""" + paths: list[Path] = [] dll = ct.windll if platform.system() == "Windows" else ct.cdll # Search additional path, if any if additional_searching_paths: for path in additional_searching_paths: if platform.system() == "Darwin": - paths.append( - os.path.abspath(path) + os.path.sep + "lib" + name + ".dylib" - ) + paths.append(path.absolute() / Path(f"lib{name}").with_suffix(".dylib")) elif platform.system() == "Windows": - paths.append(os.path.abspath(path) + os.path.sep + name + ".dll") + paths.append(path.absolute() / Path(name).with_suffix(".dll")) # Does not work # os.environ["PATH"] += os.path.abspath(path) else: # Linux/FreeBSD/UNIX - paths.append(os.path.abspath(path) + os.path.sep + "lib" + name + ".so") + paths.append(path.absolute() / Path(f"lib{name}").with_suffix(".so")) # https://stackoverflow.com/questions/856116/changing-ld-library-path-at-runtime-for-ctypes # os.environ["LD_LIBRARY_PATH"] += os.path.abspath(path) # Search typical locations - try: - paths.insert(0, ctu.find_library(name)) - except FileNotFoundError: - pass - try: - paths.insert(0, ctu.find_library("lib" + name)) - except FileNotFoundError: - pass + + if found_lib := ctu.find_library(name): + paths.insert(0, Path(found_lib)) + + if found_lib := ctu.find_library("lib" + name): + paths.insert(0, Path(found_lib)) for path in paths: if path: try: - lib = dll.LoadLibrary(path) - return lib + lib: ct.CDLL = dll.LoadLibrary(str(path)) except OSError: pass - raise RuntimeError("No " + name + " shared libraries found") + else: + return lib + + msg = f"No {name} shared libraries found" + raise RuntimeError(msg) -def _install_liboqs(target_directory, oqs_version=None): - """Install liboqs version oqs_version (if None, installs latest at HEAD) in the target_directory.""" +def _install_liboqs(target_directory: Path, oqs_version_to_install: str | None = None) -> None: + """Install liboqs version oqs_version (if None, installs latest at HEAD) in the target_directory.""" # noqa: E501 with tempfile.TemporaryDirectory() as tmpdirname: - oqs_install_str = ( - "cd " - + tmpdirname - + " && git clone https://github.com/open-quantum-safe/liboqs" - ) - if oqs_version: - oqs_install_str += " --branch " + oqs_version - oqs_install_str += ( - " --depth 1 && cmake -S liboqs -B liboqs/build -DBUILD_SHARED_LIBS=ON -DOQS_BUILD_ONLY_LIB=ON -DCMAKE_INSTALL_PREFIX=" - + target_directory + oqs_install_cmd = [ + "cd", + tmpdirname, + "&&", + "git", + "clone", + "https://github.com/open-quantum-safe/liboqs", + ] + if oqs_version_to_install: + oqs_install_cmd.extend(["--branch", oqs_version_to_install]) + + oqs_install_cmd.extend( + [ + "--depth", + "1", + "&&", + "cmake", + "-S", + "liboqs", + "-B", + "liboqs/build", + "-DBUILD_SHARED_LIBS=ON", + "-DOQS_BUILD_ONLY_LIB=ON", + f"-DCMAKE_INSTALL_PREFIX={target_directory}", + ], ) + if platform.system() == "Windows": - oqs_install_str += " -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE" - oqs_install_str += " && cmake --build liboqs/build --parallel 4 && cmake --build liboqs/build --target install" - print("liboqs not found, installing it in " + target_directory) + oqs_install_cmd.append("-DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE") + + oqs_install_cmd.extend( + [ + "&&", + "cmake", + "--build", + "liboqs/build", + "--parallel", + "4", + "&&", + "cmake", + "--build", + "liboqs/build", + "--target", + "install", + ], + ) + logger.info("liboqs not found, installing it in %s", str(target_directory)) _countdown(5) - os.system(oqs_install_str) - print("Done installing liboqs") + _retcode = subprocess.call(" ".join(oqs_install_cmd), shell=True) # noqa: S602 -def _load_liboqs(): - home_dir = os.path.expanduser("~") - oqs_install_dir = os.path.abspath(home_dir + os.path.sep + "_oqs") # $HOME/_oqs + if _retcode != 0: + logger.exception("Error installing liboqs.") + sys.exit(1) + + logger.info("Done installing liboqs") + + +def _load_liboqs() -> ct.CDLL: + home_dir = Path.home() + oqs_install_dir = home_dir / "_oqs" oqs_lib_dir = ( - os.path.abspath(oqs_install_dir + os.path.sep + "bin") # $HOME/_oqs/bin + oqs_install_dir / "bin" # $HOME/_oqs/bin if platform.system() == "Windows" - else os.path.abspath(oqs_install_dir + os.path.sep + "lib") # $HOME/_oqs/lib + else oqs_install_dir / "lib" # $HOME/_oqs/lib ) try: - _liboqs = _load_shared_obj(name="oqs", additional_searching_paths=[oqs_lib_dir]) - assert _liboqs + liboqs = _load_shared_obj(name="oqs", additional_searching_paths=[oqs_lib_dir]) + assert liboqs # noqa: S101 except RuntimeError: # We don't have liboqs, so we try to install it automatically - _install_liboqs(target_directory=oqs_install_dir, oqs_version=OQS_VERSION) + _install_liboqs(target_directory=oqs_install_dir, oqs_version_to_install=OQS_VERSION) # Try loading it again try: - _liboqs = _load_shared_obj( - name="oqs", additional_searching_paths=[oqs_lib_dir] + liboqs = _load_shared_obj( + name="oqs", + additional_searching_paths=[oqs_lib_dir], ) - assert _liboqs + assert liboqs # noqa: S101 except RuntimeError: sys.exit("Could not load liboqs shared library") - return _liboqs + return liboqs _liboqs = _load_liboqs() # Expected return value from native OQS functions -OQS_SUCCESS = 0 -OQS_ERROR = -1 +OQS_SUCCESS: Final[int] = 0 +OQS_ERROR: Final[int] = -1 -def native(): +def native() -> ct.CDLL: """Handle to native liboqs handler.""" return _liboqs @@ -150,41 +204,37 @@ def native(): native().OQS_init() -def oqs_version(): - """liboqs version string.""" +def oqs_version() -> str: + """`liboqs` version string.""" native().OQS_version.restype = ct.c_char_p - return ct.c_char_p(native().OQS_version()).value.decode("UTF-8") + return ct.c_char_p(native().OQS_version()).value.decode("UTF-8") # type: ignore[union-attr] # Warn the user if the liboqs version differs from liboqs-python version if oqs_version() != oqs_python_version(): warnings.warn( - "liboqs version {} differs from liboqs-python version {}".format( - oqs_version(), oqs_python_version() - ) + f"liboqs version {oqs_version()} differs from liboqs-python version " + f"{oqs_python_version()}", + stacklevel=2, ) class MechanismNotSupportedError(Exception): """Exception raised when an algorithm is not supported by OQS.""" - def __init__(self, alg_name): - """ - :param alg_name: requested algorithm name. - """ + def __init__(self, alg_name: str) -> None: + """:param alg_name: requested algorithm name.""" self.alg_name = alg_name - self.message = alg_name + " is not supported by OQS" + self.message = f"{alg_name} is not supported by OQS" class MechanismNotEnabledError(MechanismNotSupportedError): """Exception raised when an algorithm is supported but not enabled by OQS.""" - def __init__(self, alg_name): - """ - :param alg_name: requested algorithm name. - """ + def __init__(self, alg_name: str) -> None: + """:param alg_name: requested algorithm name.""" self.alg_name = alg_name - self.message = alg_name + " is supported but not enabled by OQS" + self.message = f"{alg_name} is supported but not enabled by OQS" class KeyEncapsulation(ct.Structure): @@ -201,7 +251,7 @@ class KeyEncapsulation(ct.Structure): free | OQS_KEM_free """ - _fields_ = [ + _fields_: ClassVar[list[tuple[str, Any]]] = [ ("method_name", ct.c_char_p), ("alg_version", ct.c_char_p), ("claimed_nist_level", ct.c_ubyte), @@ -215,9 +265,9 @@ class KeyEncapsulation(ct.Structure): ("decaps_cb", ct.c_void_p), ] - def __init__(self, alg_name, secret_key=None): + def __init__(self, alg_name: str, secret_key: int | bytes | None = None) -> None: """ - Creates new KeyEncapsulation with the given algorithm. + Create new KeyEncapsulation with the given algorithm. :param alg_name: KEM mechanism algorithm name. Enabled KEM mechanisms can be obtained with get_enabled_KEM_mechanisms(). @@ -229,8 +279,7 @@ def __init__(self, alg_name, secret_key=None): # perhaps it's a supported but not enabled alg if alg_name in _supported_KEMs: raise MechanismNotEnabledError(alg_name) - else: - raise MechanismNotSupportedError(alg_name) + raise MechanismNotSupportedError(alg_name) self._kem = native().OQS_KEM_new(ct.create_string_buffer(alg_name.encode())) @@ -247,102 +296,131 @@ def __init__(self, alg_name, secret_key=None): if secret_key: self.secret_key = ct.create_string_buffer( - secret_key, self._kem.contents.length_secret_key + secret_key, + self._kem.contents.length_secret_key, ) - def __enter__(self): + def __enter__(self: TKeyEncapsulation) -> TKeyEncapsulation: return self - def __exit__(self, ctx_type, ctx_value, ctx_traceback): + def __exit__( + self, + ctx_type: type[BaseException] | None, + ctx_value: BaseException | None, + ctx_traceback: TracebackType | None, + ) -> None: self.free() - def generate_keypair(self): + def generate_keypair(self) -> bytes | int: """ - Generates a new keypair and returns the public key. + Generate a new keypair and returns the public key. If needed, the secret key can be obtained with export_secret_key(). """ public_key = ct.create_string_buffer(self._kem.contents.length_public_key) self.secret_key = ct.create_string_buffer(self._kem.contents.length_secret_key) rv = native().OQS_KEM_keypair( - self._kem, ct.byref(public_key), ct.byref(self.secret_key) + self._kem, + ct.byref(public_key), + ct.byref(self.secret_key), ) return bytes(public_key) if rv == OQS_SUCCESS else 0 - def export_secret_key(self): - """Exports the secret key.""" + def export_secret_key(self) -> bytes: + """Export the secret key.""" return bytes(self.secret_key) - def encap_secret(self, public_key): + def encap_secret(self, public_key: int | bytes) -> tuple[bytes, bytes | int]: """ - Generates and encapsulates a secret using the provided public key. + Generate and encapsulates a secret using the provided public key. :param public_key: the peer's public key. """ my_public_key = ct.create_string_buffer( - public_key, self._kem.contents.length_public_key + public_key, + self._kem.contents.length_public_key, + ) + ciphertext: ct.Array[ct.c_char] = ct.create_string_buffer( + self._kem.contents.length_ciphertext, + ) + shared_secret: ct.Array[ct.c_char] = ct.create_string_buffer( + self._kem.contents.length_shared_secret, ) - ciphertext = ct.create_string_buffer(self._kem.contents.length_ciphertext) - shared_secret = ct.create_string_buffer(self._kem.contents.length_shared_secret) rv = native().OQS_KEM_encaps( - self._kem, ct.byref(ciphertext), ct.byref(shared_secret), my_public_key + self._kem, + ct.byref(ciphertext), + ct.byref(shared_secret), + my_public_key, ) - return bytes(ciphertext), bytes(shared_secret) if rv == OQS_SUCCESS else 0 - def decap_secret(self, ciphertext): + # TODO: What should it return? + # 1. tuple[bytes | int, bytes | int] + # 2. tuple[bytes, bytes | int] + # 3. tuple[bytes, bytes] | int + return ( + bytes(cast(bytes, ciphertext)), + bytes(cast(bytes, shared_secret)) if rv == OQS_SUCCESS else 0, + ) + + def decap_secret(self, ciphertext: int | bytes) -> bytes | int: """ - Decapsulates the ciphertext and returns the secret. + Decapsulate the ciphertext and returns the secret. :param ciphertext: the ciphertext received from the peer. """ my_ciphertext = ct.create_string_buffer( - ciphertext, self._kem.contents.length_ciphertext + ciphertext, + self._kem.contents.length_ciphertext, + ) + shared_secret: ct.Array[ct.c_char] = ct.create_string_buffer( + self._kem.contents.length_shared_secret, ) - shared_secret = ct.create_string_buffer(self._kem.contents.length_shared_secret) rv = native().OQS_KEM_decaps( - self._kem, ct.byref(shared_secret), my_ciphertext, self.secret_key + self._kem, + ct.byref(shared_secret), + my_ciphertext, + self.secret_key, ) - return bytes(shared_secret) if rv == OQS_SUCCESS else 0 + return bytes(cast(bytes, shared_secret)) if rv == OQS_SUCCESS else 0 - def free(self): + def free(self) -> None: """Releases the native resources.""" if hasattr(self, "secret_key"): native().OQS_MEM_cleanse( - ct.byref(self.secret_key), self._kem.contents.length_secret_key + ct.byref(self.secret_key), + self._kem.contents.length_secret_key, ) native().OQS_KEM_free(self._kem) - def __repr__(self): - return "Key encapsulation mechanism: " + self._kem.contents.method_name.decode() + def __repr__(self) -> str: + return f"Key encapsulation mechanism: {self._kem.contents.method_name.decode()}" native().OQS_KEM_new.restype = ct.POINTER(KeyEncapsulation) native().OQS_KEM_alg_identifier.restype = ct.c_char_p -def is_kem_enabled(alg_name): +def is_kem_enabled(alg_name: str) -> bool: """ - Returns True if the KEM algorithm is enabled. + Return True if the KEM algorithm is enabled. :param alg_name: a KEM mechanism algorithm name. """ return native().OQS_KEM_alg_is_enabled(ct.create_string_buffer(alg_name.encode())) -_KEM_alg_ids = [ - native().OQS_KEM_alg_identifier(i) for i in range(native().OQS_KEM_alg_count()) -] -_supported_KEMs = [i.decode() for i in _KEM_alg_ids] -_enabled_KEMs = [i for i in _supported_KEMs if is_kem_enabled(i)] +_KEM_alg_ids = [native().OQS_KEM_alg_identifier(i) for i in range(native().OQS_KEM_alg_count())] +_supported_KEMs: list[str] = [i.decode() for i in _KEM_alg_ids] # noqa: N816 +_enabled_KEMs: list[str] = [i for i in _supported_KEMs if is_kem_enabled(i)] # noqa: N816 -def get_enabled_kem_mechanisms(): - """Returns the list of enabled KEM mechanisms.""" +def get_enabled_kem_mechanisms() -> list[str]: + """Return the list of enabled KEM mechanisms.""" return _enabled_KEMs -def get_supported_kem_mechanisms(): - """Returns the list of supported KEM mechanisms.""" +def get_supported_kem_mechanisms() -> list[str]: + """Return the list of supported KEM mechanisms.""" return _supported_KEMs @@ -360,7 +438,7 @@ class Signature(ct.Structure): free | OQS_SIG_free """ - _fields_ = [ + _fields_: ClassVar[list[tuple[str, Any]]] = [ ("method_name", ct.c_char_p), ("alg_version", ct.c_char_p), ("claimed_nist_level", ct.c_ubyte), @@ -373,12 +451,12 @@ class Signature(ct.Structure): ("verify_cb", ct.c_void_p), ] - def __init__(self, alg_name, secret_key=None): + def __init__(self, alg_name: str, secret_key: int | bytes | None = None) -> None: """ - Creates new Signature with the given algorithm. + Create new Signature with the given algorithm. - :param alg_name: a signature mechanism algorithm name. Enabled signature mechanisms can be obtained with - get_enabled_sig_mechanisms(). + :param alg_name: a signature mechanism algorithm name. Enabled signature mechanisms can be + obtained with get_enabled_sig_mechanisms(). :param secret_key: optional, if generated by generate_keypair(). """ super().__init__() @@ -386,8 +464,7 @@ def __init__(self, alg_name, secret_key=None): # perhaps it's a supported but not enabled alg if alg_name in _supported_sigs: raise MechanismNotEnabledError(alg_name) - else: - raise MechanismNotSupportedError(alg_name) + raise MechanismNotSupportedError(alg_name) self._sig = native().OQS_SIG_new(ct.create_string_buffer(alg_name.encode())) self.details = { @@ -402,33 +479,43 @@ def __init__(self, alg_name, secret_key=None): if secret_key: self.secret_key = ct.create_string_buffer( - secret_key, self._sig.contents.length_secret_key + secret_key, + self._sig.contents.length_secret_key, ) - def __enter__(self): + def __enter__(self: TSignature) -> TSignature: return self - def __exit__(self, ctx_type, ctx_value, ctx_traceback): + def __exit__( + self, + ctx_type: type[BaseException] | None, + ctx_value: BaseException | None, + ctx_traceback: TracebackType | None, + ) -> None: self.free() - def generate_keypair(self): + def generate_keypair(self) -> bytes | int: """ - Generates a new keypair and returns the public key. + Generate a new keypair and returns the public key. If needed, the secret key can be obtained with export_secret_key(). """ - public_key = ct.create_string_buffer(self._sig.contents.length_public_key) + public_key: ct.Array[ct.c_char] = ct.create_string_buffer( + self._sig.contents.length_public_key, + ) self.secret_key = ct.create_string_buffer(self._sig.contents.length_secret_key) rv = native().OQS_SIG_keypair( - self._sig, ct.byref(public_key), ct.byref(self.secret_key) + self._sig, + ct.byref(public_key), + ct.byref(self.secret_key), ) - return bytes(public_key) if rv == OQS_SUCCESS else 0 + return bytes(cast(bytes, public_key)) if rv == OQS_SUCCESS else 0 - def export_secret_key(self): - """Exports the secret key.""" + def export_secret_key(self) -> bytes: + """Export the secret key.""" return bytes(self.secret_key) - def sign(self, message): + def sign(self, message: bytes) -> bytes | int: """ Signs the provided message and returns the signature. @@ -437,9 +524,11 @@ def sign(self, message): # Provide length to avoid extra null char my_message = ct.create_string_buffer(message, len(message)) message_len = ct.c_int(len(my_message)) - signature = ct.create_string_buffer(self._sig.contents.length_signature) + signature: ct.Array[ct.c_char] = ct.create_string_buffer( + self._sig.contents.length_signature, + ) sig_len = ct.c_int( - self._sig.contents.length_signature + self._sig.contents.length_signature, ) # initialize to maximum signature size rv = native().OQS_SIG_sign( self._sig, @@ -450,11 +539,11 @@ def sign(self, message): self.secret_key, ) - return bytes(signature[: sig_len.value]) if rv == OQS_SUCCESS else 0 + return bytes(cast(bytes, signature[: sig_len.value])) if rv == OQS_SUCCESS else 0 - def verify(self, message, signature, public_key): + def verify(self, message: bytes, signature: bytes, public_key: bytes) -> bool: """ - Verifies the provided signature on the message; returns True if valid. + Verify the provided signature on the message; returns True if valid. :param message: the signed message. :param signature: the signature on the message. @@ -468,50 +557,55 @@ def verify(self, message, signature, public_key): my_signature = ct.create_string_buffer(signature, len(signature)) sig_len = ct.c_int(len(my_signature)) my_public_key = ct.create_string_buffer( - public_key, self._sig.contents.length_public_key + public_key, + self._sig.contents.length_public_key, ) rv = native().OQS_SIG_verify( - self._sig, my_message, message_len, my_signature, sig_len, my_public_key + self._sig, + my_message, + message_len, + my_signature, + sig_len, + my_public_key, ) - return True if rv == OQS_SUCCESS else False + return rv == OQS_SUCCESS - def free(self): + def free(self) -> None: """Releases the native resources.""" if hasattr(self, "secret_key"): native().OQS_MEM_cleanse( - ct.byref(self.secret_key), self._sig.contents.length_secret_key + ct.byref(self.secret_key), + self._sig.contents.length_secret_key, ) native().OQS_SIG_free(self._sig) - def __repr__(self): - return "Signature mechanism: " + self._sig.contents.method_name.decode() + def __repr__(self) -> str: + return f"Signature mechanism: {self._sig.contents.method_name.decode()}" native().OQS_SIG_new.restype = ct.POINTER(Signature) native().OQS_SIG_alg_identifier.restype = ct.c_char_p -def is_sig_enabled(alg_name): +def is_sig_enabled(alg_name: str) -> bool: """ - Returns True if the signature algorithm is enabled. + Return True if the signature algorithm is enabled. :param alg_name: a signature mechanism algorithm name. """ return native().OQS_SIG_alg_is_enabled(ct.create_string_buffer(alg_name.encode())) -_sig_alg_ids = [ - native().OQS_SIG_alg_identifier(i) for i in range(native().OQS_SIG_alg_count()) -] +_sig_alg_ids = [native().OQS_SIG_alg_identifier(i) for i in range(native().OQS_SIG_alg_count())] _supported_sigs = [i.decode() for i in _sig_alg_ids] _enabled_sigs = [i for i in _supported_sigs if is_sig_enabled(i)] -def get_enabled_sig_mechanisms(): - """Returns the list of enabled signature mechanisms.""" +def get_enabled_sig_mechanisms() -> list[str]: + """Return the list of enabled signature mechanisms.""" return _enabled_sigs -def get_supported_sig_mechanisms(): - """Returns the list of supported signature mechanisms.""" +def get_supported_sig_mechanisms() -> list[str]: + """Return the list of supported signature mechanisms.""" return _supported_sigs diff --git a/oqs/rand.py b/oqs/rand.py index 5e7fd77..c64f50e 100644 --- a/oqs/rand.py +++ b/oqs/rand.py @@ -1,5 +1,5 @@ """ -Open Quantum Safe (OQS) Python Wrapper for liboqs +Open Quantum Safe (OQS) Python Wrapper for liboqs. The liboqs project provides post-quantum public key cryptography algorithms: https://github.com/open-quantum-safe/liboqs @@ -7,32 +7,36 @@ This module provides a Python 3 interface to libOQS RNGs. """ +import ctypes as ct + import oqs -def randombytes(bytes_to_read): +def randombytes(bytes_to_read: int) -> bytes: """ - Generates random bytes. This implementation uses either the default RNG algorithm ("system"), or whichever - algorithm has been selected by random_bytes_switch_algorithm(). + Generate random bytes. This implementation uses either the default RNG algorithm ("system"), + or whichever algorithm has been selected by random_bytes_switch_algorithm(). :param bytes_to_read: the number of random bytes to generate. :return: random bytes. """ - result = oqs.ct.create_string_buffer(bytes_to_read) - oqs.native().OQS_randombytes(result, oqs.ct.c_int(bytes_to_read)) + result = ct.create_string_buffer(bytes_to_read) + oqs.native().OQS_randombytes(result, ct.c_int(bytes_to_read)) return bytes(result) -def randombytes_switch_algorithm(alg_name): +def randombytes_switch_algorithm(alg_name: str) -> None: """ - Switches the core OQS_randombytes to use the specified algorithm. See liboqs headers for more details. + Switches the core OQS_randombytes to use the specified algorithm. See liboqs + headers for more details. :param alg_name: algorithm name, possible values are "system" and "OpenSSL". """ if ( oqs.native().OQS_randombytes_switch_algorithm( - oqs.ct.create_string_buffer(alg_name.encode()) + ct.create_string_buffer(alg_name.encode()), ) != oqs.OQS_SUCCESS ): - raise RuntimeError("Can not switch algorithm") + msg = "Can not switch algorithm" + raise RuntimeError(msg) diff --git a/pyproject.toml b/pyproject.toml index fd558c0..c5296ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,6 @@ -[build-system] -requires = [ - "setuptools>=42", - "wheel", -] -build-backend = "setuptools.build_meta" - [project] name = "liboqs-python" -requires-python = ">=3.8" +requires-python = ">=3.9" version = "0.10.0" description = "Python bindings for liboqs, providing post-quantum public key cryptography algorithms" authors = [ @@ -15,13 +8,119 @@ authors = [ ] readme = "README.md" license = { file = "LICENSE" } -dependencies = [ - "nose2", +dependencies = [] + +[tool.uv] +package = true + +[project.optional-dependencies] +dev = [ + "isort==5.13.2", + "pre-commit==4.0.1", + "ruff==0.7.3", + "bandit==1.7.10", + "nose2==0.15.1", ] +lint = [ + "mypy==1.13.0", + "types-pytz==2024.2.0.20241003", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["oqs"] [project.urls] homepage = "https://github.com/open-quantum-safe/liboqs-python" repository = "https://github.com/open-quantum-safe/liboqs-python.git" -[tool.setuptools] -py-modules = [] +[tool.isort] +py_version = 39 +src_paths = ["oqs"] +line_length = 99 +multi_line_output = 3 +force_grid_wrap = 0 +include_trailing_comma = true +split_on_trailing_comma = false +single_line_exclusions = ["."] +sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] +skip_gitignore = true +extend_skip = ["__pycache__"] +extend_skip_glob = [] + +[tool.ruff] +src = ["oqs"] +target-version = "py39" +line-length = 99 +exclude = [ + ".git", + ".mypy_cache", + ".ruff_cache", + "__pypackages__", + "__pycache__", + "*.pyi", + "venv", + ".venv", +] + +[tool.ruff.lint] +select = ["ALL"] +ignore = [ + "A003", + "ANN002", "ANN003", "ANN101", "ANN102", "ANN401", + "C901", + "D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", "D203", "D205", "D212", + "ERA001", + "FA100", "FA102", + "FBT001", "FBT002", + "FIX002", + "I001", + "PLR0911", "PLR0912", "PLR0913", "PLR0915", "PLR5501", + "PLW0120", + "RUF001", + "TD002", "TD003" +] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" + +[tool.mypy] +python_version = "3.9" +mypy_path = "." +packages = ["oqs"] +plugins = [] +allow_redefinition = true +check_untyped_defs = true +disallow_any_generics = true +disallow_incomplete_defs = true +disallow_untyped_calls = true +disallow_untyped_defs = true +extra_checks = true +follow_imports_for_stubs = true +ignore_missing_imports = false +namespace_packages = true +no_implicit_optional = true +no_implicit_reexport = true +pretty = true +show_absolute_path = true +show_error_codes = true +show_error_context = true +warn_redundant_casts = true +warn_unused_configs = true +warn_unused_ignores = true + +disable_error_code = [ + "no-redef", +] + +exclude = [ + "\\.?venv", + "\\.idea", + "\\.tests?", +] diff --git a/setup.py b/setup.py deleted file mode 100644 index ab322e4..0000000 --- a/setup.py +++ /dev/null @@ -1,7 +0,0 @@ -from setuptools import find_packages, setup - -setup( - packages=find_packages( - exclude=["tests", "docs", "examples"], - ), -) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_kem.py b/tests/test_kem.py index cd13640..348fb10 100644 --- a/tests/test_kem.py +++ b/tests/test_kem.py @@ -1,67 +1,72 @@ -import oqs import platform # to learn the OS we're on import random +import oqs + # KEMs for which unit testing is disabled -disabled_KEM_patterns = [] +disabled_KEM_patterns = [] # noqa: N816 if platform.system() == "Windows": - disabled_KEM_patterns = ["Classic-McEliece"] + disabled_KEM_patterns = ["Classic-McEliece"] # noqa: N816 -def test_correctness(): +def test_correctness() -> tuple[None, str]: for alg_name in oqs.get_enabled_kem_mechanisms(): if any(item in alg_name for item in disabled_KEM_patterns): continue yield check_correctness, alg_name -def check_correctness(alg_name): +def check_correctness(alg_name: str) -> None: with oqs.KeyEncapsulation(alg_name) as kem: public_key = kem.generate_keypair() ciphertext, shared_secret_server = kem.encap_secret(public_key) shared_secret_client = kem.decap_secret(ciphertext) - assert shared_secret_client == shared_secret_server + assert shared_secret_client == shared_secret_server # noqa: S101 -def test_wrong_ciphertext(): +def test_wrong_ciphertext() -> tuple[None, str]: for alg_name in oqs.get_enabled_kem_mechanisms(): if any(item in alg_name for item in disabled_KEM_patterns): continue yield check_wrong_ciphertext, alg_name -def check_wrong_ciphertext(alg_name): +def check_wrong_ciphertext(alg_name: str) -> None: with oqs.KeyEncapsulation(alg_name) as kem: public_key = kem.generate_keypair() ciphertext, shared_secret_server = kem.encap_secret(public_key) wrong_ciphertext = bytes(random.getrandbits(8) for _ in range(len(ciphertext))) shared_secret_client = kem.decap_secret(wrong_ciphertext) - assert shared_secret_client != shared_secret_server + assert shared_secret_client != shared_secret_server # noqa: S101 -def test_not_supported(): +def test_not_supported() -> None: try: - with oqs.KeyEncapsulation("bogus") as kem: - raise AssertionError("oqs.MechanismNotSupportedError was not raised.") + with oqs.KeyEncapsulation("bogus") as _kem: + msg = "oqs.MechanismNotSupportedError was not raised." + raise AssertionError(msg) # noqa: TRY301 except oqs.MechanismNotSupportedError: pass - except Exception as ex: - raise AssertionError("An unexpected exception was raised. " + ex) + except Exception as ex: # noqa: BLE001 + msg = f"An unexpected exception was raised. {ex}" + raise AssertionError(msg) # noqa: B904 -def test_not_enabled(): +def test_not_enabled() -> None: # TODO: test broken as the compiled lib determines which algorithms are supported and enabled for alg_name in oqs.get_supported_kem_mechanisms(): if alg_name not in oqs.get_enabled_kem_mechanisms(): # Found a non-enabled but supported alg try: - with oqs.KeyEncapsulation(alg_name) as kem: - raise AssertionError("oqs.MechanismNotEnabledError was not raised.") + with oqs.KeyEncapsulation(alg_name) as _kem: + msg = "oqs.MechanismNotEnabledError was not raised." + raise AssertionError(msg) # noqa: TRY301 except oqs.MechanismNotEnabledError: pass - except Exception as ex: - raise AssertionError("An unexpected exception was raised. " + ex) + except Exception as ex: # noqa: BLE001 + msg = f"An unexpected exception was raised. {ex}" + raise AssertionError(msg) # noqa: B904 if __name__ == "__main__": diff --git a/tests/test_sig.py b/tests/test_sig.py index 5053df4..0f0e5a1 100644 --- a/tests/test_sig.py +++ b/tests/test_sig.py @@ -1,7 +1,8 @@ -import oqs import platform # to learn the OS we're on import random +import oqs + # Sigs for which unit testing is disabled disabled_sig_patterns = [] @@ -9,91 +10,95 @@ disabled_sig_patterns = ["Rainbow-V"] -def test_correctness(): +def test_correctness() -> tuple[None, str]: for alg_name in oqs.get_enabled_sig_mechanisms(): if any(item in alg_name for item in disabled_sig_patterns): continue yield check_correctness, alg_name -def check_correctness(alg_name): +def check_correctness(alg_name: str) -> None: with oqs.Signature(alg_name) as sig: message = bytes(random.getrandbits(8) for _ in range(100)) public_key = sig.generate_keypair() signature = sig.sign(message) - assert sig.verify(message, signature, public_key) + assert sig.verify(message, signature, public_key) # noqa: S101 -def test_wrong_message(): +def test_wrong_message() -> tuple[None, str]: for alg_name in oqs.get_enabled_sig_mechanisms(): if any(item in alg_name for item in disabled_sig_patterns): continue yield check_wrong_message, alg_name -def check_wrong_message(alg_name): +def check_wrong_message(alg_name: str) -> None: with oqs.Signature(alg_name) as sig: message = bytes(random.getrandbits(8) for _ in range(100)) public_key = sig.generate_keypair() signature = sig.sign(message) wrong_message = bytes(random.getrandbits(8) for _ in range(len(message))) - assert not (sig.verify(wrong_message, signature, public_key)) + assert not (sig.verify(wrong_message, signature, public_key)) # noqa: S101 -def test_wrong_signature(): +def test_wrong_signature() -> tuple[None, str]: for alg_name in oqs.get_enabled_sig_mechanisms(): if any(item in alg_name for item in disabled_sig_patterns): continue yield check_wrong_signature, alg_name -def check_wrong_signature(alg_name): +def check_wrong_signature(alg_name: str) -> None: with oqs.Signature(alg_name) as sig: message = bytes(random.getrandbits(8) for _ in range(100)) public_key = sig.generate_keypair() signature = sig.sign(message) wrong_signature = bytes(random.getrandbits(8) for _ in range(len(signature))) - assert not (sig.verify(message, wrong_signature, public_key)) + assert not (sig.verify(message, wrong_signature, public_key)) # noqa: S101 -def test_wrong_public_key(): +def test_wrong_public_key() -> tuple[None, str]: for alg_name in oqs.get_enabled_sig_mechanisms(): if any(item in alg_name for item in disabled_sig_patterns): continue yield check_wrong_public_key, alg_name -def check_wrong_public_key(alg_name): +def check_wrong_public_key(alg_name: str) -> None: with oqs.Signature(alg_name) as sig: message = bytes(random.getrandbits(8) for _ in range(100)) public_key = sig.generate_keypair() signature = sig.sign(message) wrong_public_key = bytes(random.getrandbits(8) for _ in range(len(public_key))) - assert not (sig.verify(message, signature, wrong_public_key)) + assert not (sig.verify(message, signature, wrong_public_key)) # noqa: S101 -def test_not_supported(): +def test_not_supported() -> None: try: - with oqs.Signature("bogus") as sig: - raise AssertionError("oqs.MechanismNotSupportedError was not raised.") + with oqs.Signature("bogus") as _sig: + msg = "oqs.MechanismNotSupportedError was not raised." + raise AssertionError(msg) # noqa: TRY301 except oqs.MechanismNotSupportedError: pass - except Exception as ex: - raise AssertionError("An unexpected exception was raised. " + ex) + except Exception as ex: # noqa: BLE001 + msg = f"An unexpected exception was raised. {ex}" + raise AssertionError(msg) # noqa: B904 -def test_not_enabled(): +def test_not_enabled() -> None: # TODO: test broken as the compiled lib determines which algorithms are supported and enabled for alg_name in oqs.get_supported_sig_mechanisms(): if alg_name not in oqs.get_enabled_sig_mechanisms(): # Found a non-enabled but supported alg try: - with oqs.Signature(alg_name) as sig: - raise AssertionError("oqs.MechanismNotEnabledError was not raised.") + with oqs.Signature(alg_name) as _sig: + msg = "oqs.MechanismNotEnabledError was not raised." + raise AssertionError(msg) # noqa: TRY301 except oqs.MechanismNotEnabledError: pass - except Exception as ex: - raise AssertionError("An unexpected exception was raised. " + ex) + except Exception as ex: # noqa: BLE001 + msg = f"An unexpected exception was raised. {ex}" + raise AssertionError(msg) # noqa: B904 if __name__ == "__main__":