"""
Generic classes used for creating an asymmetric encryption scheme.
"""
from __future__ import annotations
import inspect
import sys
from abc import ABC, abstractmethod
from typing import Any, Generic, Protocol, Tuple, TypeVar, cast
from .encryption_scheme import CT, CV, KM, PT, RP, EncryptionScheme
if sys.version_info < (3, 11):
    from typing_extensions import Self
else:
    from typing import Self
[docs]
class PublicKey(Protocol):
    """
    Public Key of an AsymmetricEncryptionScheme.
    This should be subclassed for every AsymmetricEncryptionScheme.
    """
[docs]
    def serialize(self, **_kwargs: Any) -> Any:
        r"""
        Serialization function for public keys, which will be passed to the communication module.
        :param \**_kwargs: Optional extra keyword arguments.
        :raise SerializationError: When communication library is not installed.
        :return: serialized version of this PublicKey.
        """ 
[docs]
    @staticmethod
    def deserialize(obj: Any, **_kwargs: Any) -> PublicKey:
        r"""
        Deserialization function for public keys, which will be passed to the communication module.
        :param obj: serialized version of a PublicKey.
        :param \**_kwargs: optional extra keyword arguments
        :raise SerializationError: When communication library is not installed.
        :return: Deserialized PublicKey from the given dict.
        """ 
 
[docs]
class SecretKey(Protocol):
    """
    Secret Key of an AsymmetricEncryptionScheme.
    This should be subclassed for every AsymmetricEncryptionScheme.
    """
[docs]
    def serialize(self, **_kwargs: Any) -> Any:
        r"""
        Serialization function for secret keys, which will be passed to the communication module.
        :param \**_kwargs: Optional extra keyword arguments.
        :raise SerializationError: When communication library is not installed.
        :return: serialized version of this SecretKey.
        """ 
[docs]
    @staticmethod
    def deserialize(obj: Any, **_kwargs: Any) -> SecretKey:
        r"""
        Deserialization function for public keys, which will be passed to the communication module.
        :param obj: serialized version of a SecretKey.
        :param \**_kwargs: optional extra keyword arguments
        :raise SerializationError: When communication library is not installed.
        :return: Deserialized SecretKey from the given dict.
        """ 
 
PK = TypeVar("PK", bound=PublicKey)
SK = TypeVar("SK", bound=SecretKey)
[docs]
class AsymmetricEncryptionScheme(
    Generic[KM, PT, RP, CV, CT, PK, SK], EncryptionScheme[KM, PT, RP, CV, CT], ABC
):
    """
    Abstract base class for an AsymmetricEncryptionScheme. Subclass of EncryptionScheme.
    """
[docs]
    @classmethod
    def from_security_parameter(cls, *args: Any, **kwargs: Any) -> Self:
        r"""
        Generate a new AsymmetricEncryptionScheme from a security parameter. Note that regular
        arguments will be passed to the generate_key_material  method, so all parameter that are
        required for the constructor should be passed as keyword arguments.
        :param \*args: Security parameter(s) for key generation.
        :param \**kwargs: Security parameter(s) and optional extra arguments for the constructor.
        :raises ValueError: If a keyword argument is not valid for key generation or the
            constructor.
        :return: A new EncryptionScheme.
        """
        gen_names = inspect.getfullargspec(cls.generate_key_material)[0]
        init_names = [
            name for name in inspect.getfullargspec(cls.__init__)[0] if name != "self"
        ]
        gen_kwargs = {}
        init_kwargs = {}
        for kwarg, val in kwargs.items():
            if kwarg in gen_names:
                # arguments used for generating key material
                gen_kwargs[kwarg] = val
            elif kwarg in init_names:
                # arguments used in the __init__ method
                init_kwargs[kwarg] = val
            else:
                raise ValueError(
                    f"The keyword arguments should either be used for key generation, "
                    f"or passed to the constructor, but parameter with name {kwarg} "
                    f"is not present in either."
                )
        public_key, secret_key = cast(
            Tuple[PK, SK], cls.generate_key_material(*args, **gen_kwargs)
        )
        return cls(public_key, secret_key, **init_kwargs) 
[docs]
    @classmethod
    def from_public_key(cls, public_key: PK, **kwargs: Any) -> Self:
        r"""
        Generate a new AsymmetricEncryptionScheme from a public key (e.g. when received from another
        party) and possibly additional parameters.
        :param public_key: The PublicKey of this scheme instantiation.
        :param \**kwargs: Optional extra keyword arguments for the constructor.
        :return: A new EncryptionScheme.
        """
        return cls(public_key=public_key, secret_key=None, **kwargs) 
[docs]
    def __init__(
        self,
        public_key: PK,
        secret_key: SK | None,
        *_args: Any,
        **_kwargs: Any,
    ) -> None:
        r"""
        Construct an AsymmetricEncryptionScheme with the given keypair and optional keyword
        arguments. All keyword arguments are combined with the public key to create an ID, so all
        the __init__ of a custom subclass of AsymmetricEncryptionScheme should pass all their
        parameter values as keyword arguments to this __init__ for the ID generation to work
        properly. If this does not happen, then schemes might be considered equal when they are
        totally different.
        :param public_key: Asymmetric PublicKey.
        :param secret_key: Asymmetric SecretKey, might be None when the SecretKey is unknown.
        :param \*_args: Optional extra arguments for the constructor of a concrete implementation.
        :param \**_kwargs: Optional extra keyword arguments for the constructor of a concrete
            implementation.
        """
        self.__pk = public_key
        self.__sk = secret_key
        EncryptionScheme.__init__(self) 
[docs]
    @classmethod
    @abstractmethod
    def generate_key_material(cls, *args: Any, **kwargs: Any) -> KM:
        r"""
        Method to generate key material (PublicKey and SecretKey) for this scheme.
        :param \*args: Required arguments to generate said key material.
        :param \**kwargs: Required arguments to generate said key material.
        :return: Tuple containing first the PublicKey of this scheme and then the SecretKey.
        """ 
    @property
    def public_key(self) -> PK:
        """
        PublicKey of this instantiation of the scheme.
        :return: PublicKey of this instantiation.
        """
        return self.__pk
    @property
    def secret_key(self) -> SK | None:
        """
        SecretKey of this instantiation of the scheme.
        :return: SecretKey of this instantiation, or None when it is unknown.
        """
        return self.__sk