# ===================================================================
#
# Copyright (c) 2015, Legrandin <helderijs@gmail.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ===================================================================


from __future__ import annotations

import binascii
import re
import struct
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Literal,
    Optional,
    TypedDict,
    Union,
    cast,
    overload,
)

from Crypto import IncorrectPassphraseError, KdfLimitError, PassphraseRequiredError
from Crypto.Hash import SHA512, SHAKE256
from Crypto.Math.Numbers import Integer
from Crypto.PublicKey import (
    _create_subject_public_key_info,
    _expand_subject_public_key_info,
    _extract_subject_public_key_info,
)
from Crypto.Random import get_random_bytes
from Crypto.Util._bytes import tobytes
from Crypto.Util.asn1 import DerBitString, DerObjectId, DerOctetString, DerSequence

from ._point import CurveID as _CurveID
from ._point import EccPoint, EccXPoint, _curves

if TYPE_CHECKING:
    from typing_extensions import Unpack

    from Crypto.IO._PBES import ProtParams

RNG = Callable[[int], bytes]
Int = Union[int, Integer]


class _CurveRequired(TypedDict):
    curve: str


class ConstructParams(_CurveRequired, total=False):
    d: Union[int, Integer]
    seed: bytes
    point_x: Optional[Union[int, Integer]]
    point_y: Optional[Union[int, Integer]]


# The curves whose private key is a seed, and its length
_SEED_LENGTHS = {
    _CurveID.ED25519: 32,
    _CurveID.ED448: 57,
    _CurveID.CURVE25519: 32,
    _CurveID.CURVE448: 56,
}


def _seed_to_scalar(curve_id: int, seed: bytes) -> tuple[Optional[bytes], Integer]:
    """The prefix (EdDSA only) and the secret scalar of a seed.

    RFC 8032 (5.1.5 and 5.2.5) for Ed25519 and Ed448: the scalar comes from
    the hash of the seed. RFC 7748 (5) for X25519 and X448: the seed is the
    scalar. In both cases, the scalar is clamped (little endian).
    """

    prefix = None
    if curve_id == _CurveID.ED25519:
        seed_hash = SHA512.new(seed).digest()
        prefix, scalar = seed_hash[32:], bytearray(seed_hash[:32])
    elif curve_id == _CurveID.ED448:
        seed_hash = SHAKE256.new(seed).read(114)
        prefix, scalar = seed_hash[57:], bytearray(seed_hash[:57])
    else:
        scalar = bytearray(seed)

    if curve_id in (_CurveID.ED25519, _CurveID.CURVE25519):
        scalar[0] &= 0xF8
        scalar[31] = (scalar[31] & 0x7F) | 0x40
    else:
        scalar[0] &= 0xFC
        scalar[55] |= 0x80
        if curve_id == _CurveID.ED448:
            scalar[56] = 0
    return prefix, Integer.from_bytes(scalar, byteorder="little")


class UnsupportedEccFeature(ValueError):
    pass


class EccKey:
    r"""Class defining an ECC key.
    Do not instantiate directly.
    Use :func:`generate`, :func:`construct` or :func:`import_key` instead.

    :ivar curve: The **canonical** name of the curve as defined in the `ECC table`_.
    :vartype curve: string

    :ivar pointQ: an ECC point representing the public component.
    :vartype pointQ: :class:`EccPoint` or :class:`EccXPoint`

    :ivar d: A scalar that represents the private component
             in NIST P curves and secp256k1. It is smaller than the
             order of the generator point.
    :vartype d: integer

    :ivar seed: A seed that represents the private component
                in Ed22519 (32 bytes), Curve25519 (32 bytes),
                Curve448 (56 bytes), Ed448 (57 bytes).
    :vartype seed: bytes
    """

    def __init__(
        self,
        *,
        curve: Optional[str] = None,
        d: Optional[Union[int, Integer]] = None,
        seed: Optional[bytes] = None,
        point: Optional[Union[EccPoint, EccXPoint]] = None,
    ) -> None:
        """Create a new ECC key

        Keywords:
          curve : string
            The name of the curve.
          d : integer
            Mandatory for a private key on NIST P curves and secp256k1.
            It must be in the range ``[1..order-1]``.
          seed : bytes
            Mandatory for a private key on Ed25519 (32 bytes),
            Curve25519 (32 bytes), Curve448 (56 bytes) or Ed448 (57 bytes).
          point : EccPoint or EccXPoint
            Mandatory for a public key. If provided for a private key,
            the implementation will NOT check whether it matches ``d``.

        Only one parameter among ``d``, ``seed`` or ``point`` may be used.
        """

        curve_name = curve
        # Which of these attributes are set depends on the type of key
        self._d: Any = cast(Any, d)
        self._seed: Any = cast(Any, seed)
        self._point: Any = cast(Any, point)
        if curve_name is None and self._point:
            curve_name = self._point.curve

        if curve_name not in _curves:
            raise ValueError("Unsupported curve (%s)" % curve_name)
        self._curve = _curves[curve_name]
        self.curve = self._curve.canonical

        count = int(self._d is not None) + int(self._seed is not None)

        if count == 0:
            if self._point is None:
                raise ValueError("At lest one between parameters 'point', 'd' or 'seed' must be specified")
            return

        if count == 2:
            raise ValueError("Parameters d and seed are mutually exclusive")

        # NIST P curves and secp256k1 work with d, the other curves with a seed
        seed_len = _SEED_LENGTHS.get(self._curve.id)
        if seed_len is None:
            if self._seed is not None:
                raise ValueError("Parameter 'seed' cannot be used with NIST P-curves or secp256k1")
            self._d = Integer(self._d)
            if not 1 <= self._d < self._curve.order:
                raise ValueError("Parameter d must be an integer smaller than the curve order")
            return

        if self._d is not None:
            raise ValueError("Parameter d can only be used with NIST P curves or secp256k1")
        if len(self._seed) != seed_len:
            raise ValueError(
                "Parameter seed must be %d bytes long for %s" % (seed_len, self._curve.canonical)
            )
        self._prefix, self._d = _seed_to_scalar(self._curve.id, self._seed)

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, EccKey):
            return False

        if other.has_private() != self.has_private():
            return False

        return other.pointQ == self.pointQ

    def __repr__(self) -> str:
        if self.has_private():
            if self._curve.is_edwards:
                extra = ", seed=%s" % binascii.hexlify(self._seed).decode("latin-1")
            else:
                extra = ", d=%d" % int(self._d)
        else:
            extra = ""
        if self._curve.id in (_CurveID.CURVE25519, _CurveID.CURVE448):
            x = self.pointQ.x
            result = "EccKey(curve='%s', point_x=%d%s)" % (self._curve.canonical, x, extra)
        else:
            x, y = self.pointQ.xy
            result = "EccKey(curve='%s', point_x=%d, point_y=%d%s)" % (self._curve.canonical, x, y, extra)
        return result

    def has_private(self) -> bool:
        """``True`` if this key can be used for making signatures or decrypting data."""

        return self._d is not None

    # ECDSA
    def _sign(self, z, k):
        assert 0 < k < self._curve.order

        order = self._curve.order
        blind = Integer.random_range(min_inclusive=1, max_exclusive=order)

        blind_d = self._d * blind
        inv_blind_k = (blind * k).inverse(order)

        r = (self._curve.G * k)._x % order
        s = inv_blind_k * (blind * z + blind_d * r) % order
        return (r, s)

    # ECDSA
    def _verify(self, z, rs):
        order = self._curve.order
        sinv = rs[1].inverse(order)
        point1 = self._curve.G * ((sinv * z) % order)
        point2 = self.pointQ * ((sinv * rs[0]) % order)
        return (point1 + point2)._x % order == rs[0]

    @property
    def d(self) -> int:
        if not self.has_private():
            raise ValueError("This is not a private ECC key")
        return int(self._d)

    @property
    def seed(self) -> bytes:
        if not self.has_private():
            raise ValueError("This is not a private ECC key")
        return self._seed

    @property
    def pointQ(self) -> EccPoint:
        if self._point is None:
            self._point = self._curve.G * self._d
        return self._point

    def public_key(self) -> EccKey:
        """A matching ECC public key.

        Returns:
            a new :class:`EccKey` object
        """

        return EccKey(curve=self._curve.canonical, point=self.pointQ)

    def _export_SEC1(self, compress):
        if not self._curve.is_weierstrass:
            raise ValueError("SEC1 format is only supported for NIST P curves and secp256k1")

        # See 2.2 in RFC5480 and 2.3.3 in SEC1
        #
        # The first byte is:
        # - 0x02:   compressed, only X-coordinate, Y-coordinate is even
        # - 0x03:   compressed, only X-coordinate, Y-coordinate is odd
        # - 0x04:   uncompressed, X-coordinate is followed by Y-coordinate
        #
        # PAI is in theory encoded as 0x00.

        modulus_bytes = self.pointQ.size_in_bytes()

        x, y = self.pointQ._xy
        if compress:
            if y.is_odd():
                first_byte = b"\x03"
            else:
                first_byte = b"\x02"
            public_key = first_byte + x.to_bytes(modulus_bytes)
        else:
            public_key = b"\x04" + x.to_bytes(modulus_bytes) + y.to_bytes(modulus_bytes)
        return public_key

    def _export_eddsa_public(self):
        x, y = self.pointQ._xy
        if self._curve.id == _CurveID.ED25519:
            result = bytearray(y.to_bytes(32, byteorder="little"))
            result[31] = (int(x.is_odd()) << 7) | result[31]
        elif self._curve.id == _CurveID.ED448:
            result = bytearray(y.to_bytes(57, byteorder="little"))
            result[56] = int(x.is_odd()) << 7
        else:
            raise ValueError("Not an EdDSA key to export")
        return bytes(result)

    def _export_montgomery_public(self):
        if not self._curve.is_montgomery:
            raise ValueError("Not a Montgomery key to export")
        x = self.pointQ._x
        field_size = self.pointQ.size_in_bytes()
        result = bytearray(x.to_bytes(field_size, byteorder="little"))
        return bytes(result)

    def _export_subjectPublicKeyInfo(self, compress):
        if self._curve.is_edwards:
            oid = self._curve.oid
            public_key = self._export_eddsa_public()
            params = None
        elif self._curve.is_montgomery:
            oid = self._curve.oid
            public_key = self._export_montgomery_public()
            params = None
        else:
            oid = "1.2.840.10045.2.1"  # unrestricted
            public_key = self._export_SEC1(compress)
            params = DerObjectId(self._curve.oid)

        return _create_subject_public_key_info(oid, public_key, params)

    def _export_rfc5915_private_der(self, include_ec_params=True):
        assert self.has_private()

        # ECPrivateKey ::= SEQUENCE {
        #           version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
        #           privateKey     OCTET STRING,
        #           parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
        #           publicKey  [1] BIT STRING OPTIONAL
        #    }

        # Public key - uncompressed form
        modulus_bytes = self.pointQ.size_in_bytes()
        x, y = self.pointQ._xy
        public_key = b"\x04" + x.to_bytes(modulus_bytes) + y.to_bytes(modulus_bytes)

        seq = [
            1,
            DerOctetString(self._d.to_bytes(modulus_bytes)),
            DerObjectId(self._curve.oid, explicit=0),
            DerBitString(public_key, explicit=1),
        ]

        if not include_ec_params:
            del seq[2]

        return DerSequence(seq).encode()

    def _export_pkcs8(self, **kwargs):
        from Crypto.IO import PKCS8

        if self._seed is not None:
            oid = self._curve.oid
            private_key = DerOctetString(self._seed).encode()
            params = None
        else:
            oid = "1.2.840.10045.2.1"  # unrestricted
            private_key = self._export_rfc5915_private_der(include_ec_params=False)
            params = DerObjectId(self._curve.oid)

        result = PKCS8.wrap(private_key, oid, key_params=params, **kwargs)
        return result

    def _export_public_pem(self, compress):
        from Crypto.IO import PEM

        encoded_der = self._export_subjectPublicKeyInfo(compress)
        return PEM.encode(encoded_der, "PUBLIC KEY")

    def _export_private_pem(self, passphrase, **kwargs):
        from Crypto.IO import PEM

        encoded_der = self._export_rfc5915_private_der()
        return PEM.encode(encoded_der, "EC PRIVATE KEY", passphrase, **kwargs)

    def _export_private_clear_pkcs8_in_clear_pem(self):
        from Crypto.IO import PEM

        encoded_der = self._export_pkcs8()
        return PEM.encode(encoded_der, "PRIVATE KEY")

    def _export_private_encrypted_pkcs8_in_clear_pem(self, passphrase, **kwargs):
        from Crypto.IO import PEM

        assert passphrase
        encoded_der = self._export_pkcs8(passphrase=passphrase, **kwargs)
        return PEM.encode(encoded_der, "ENCRYPTED PRIVATE KEY")

    def _export_openssh(self, compress):
        if self.has_private():
            raise ValueError("Cannot export OpenSSH private keys")

        desc = self._curve.openssh

        if desc is None:
            raise ValueError("Cannot export %s keys as OpenSSH" % self.curve)
        elif desc == "ssh-ed25519":
            public_key = self._export_eddsa_public()
            comps = (tobytes(desc), tobytes(public_key))
        else:
            modulus_bytes = self.pointQ.size_in_bytes()

            x, y = self.pointQ._xy
            if compress:
                first_byte = 2 + y.is_odd()
                public_key = bytes([first_byte]) + x.to_bytes(modulus_bytes)
            else:
                public_key = b"\x04" + x.to_bytes(modulus_bytes) + y.to_bytes(modulus_bytes)

            middle = desc.split("-")[2]
            comps = (tobytes(desc), tobytes(middle), public_key)

        blob = b"".join([struct.pack(">I", len(x)) + x for x in comps])
        return desc + " " + binascii.b2a_base64(blob).decode("latin-1")

    @overload
    def export_key(
        self,
        *,
        format: Literal["PEM", "OpenSSH"],
        passphrase: Optional[Union[bytes, str]] = None,
        use_pkcs8: bool = True,
        protection: Optional[str] = None,
        compress: bool = False,
        prot_params: Optional[ProtParams] = None,
        randfunc: Optional[RNG] = None,
        threads: int = 1,
    ) -> str: ...

    @overload
    def export_key(
        self,
        *,
        format: Literal["DER", "SEC1", "raw"],
        passphrase: Optional[Union[bytes, str]] = None,
        use_pkcs8: bool = True,
        protection: Optional[str] = None,
        compress: bool = False,
        prot_params: Optional[ProtParams] = None,
        randfunc: Optional[RNG] = None,
        threads: int = 1,
    ) -> bytes: ...

    @overload
    def export_key(
        self,
        *,
        format: str,
        passphrase: Optional[Union[bytes, str]] = None,
        use_pkcs8: bool = True,
        protection: Optional[str] = None,
        compress: bool = False,
        prot_params: Optional[ProtParams] = None,
        randfunc: Optional[RNG] = None,
        threads: int = 1,
    ) -> Union[str, bytes]: ...

    def export_key(
        self,
        *,
        format: str,
        passphrase: Optional[Union[bytes, str]] = None,
        use_pkcs8: bool = True,
        protection: Optional[str] = None,
        compress: bool = False,
        prot_params: Optional[ProtParams] = None,
        randfunc: Optional[RNG] = None,
        threads: int = 1,
    ) -> Union[str, bytes]:
        """Export this ECC key.

        Args:
          format (string):
            The output format:

            - ``'DER'``. The key will be encoded in ASN.1 DER format (binary).
              For a public key, the ASN.1 ``subjectPublicKeyInfo`` structure
              defined in `RFC5480`_ will be used.
              For a private key, the ASN.1 ``ECPrivateKey`` structure defined
              in `RFC5915`_ is used instead (possibly within a PKCS#8 envelope,
              see the ``use_pkcs8`` flag below).
            - ``'PEM'``. The key will be encoded in a PEM_ envelope (ASCII).
            - ``'OpenSSH'``. The key will be encoded in the OpenSSH_ format
              (ASCII, public keys only).
            - ``'SEC1'``. The public key (i.e., the EC point) will be encoded
              into ``bytes`` according to Section 2.3.3 of `SEC1`_
              (which is a subset of the older X9.62 ITU standard).
              Only for NIST P-curves and secp256k1.
            - ``'raw'``. The public key will be encoded as ``bytes``,
              without any metadata.

              * For NIST P-curves and secp256k1: equivalent to ``'SEC1'``.
              * For Ed25519 and Ed448: ``bytes`` in the format
                defined in `RFC8032`_.
              * For Curve25519 and Curve448: ``bytes`` in the format
                defined in `RFC7748`_.

          passphrase (bytes or string):
            (*Private keys only*) The passphrase to protect the
            private key.

          use_pkcs8 (boolean):
            (*Private keys only*)
            If ``True`` (default and recommended), the `PKCS#8`_ representation
            will be used.
            It must be ``True`` for Ed25519, Ed448, Curve25519, and Curve448.

            If ``False`` and a passphrase is present, the obsolete PEM
            encryption will be used. It derives the key with a single round
            of MD5, and is therefore easy to brute force: use it only with
            a high-entropy passphrase (like 16 random bytes), for
            compatibility with old software.

          protection (string):
            The encryption scheme used when a private key is exported with
            password-protection and PKCS#8 (both ``DER`` and ``PEM`` formats).
            For all possible protection schemes,
            refer to :ref:`the encryption parameters of PKCS#8<enc_params>`.
            The default is ``'PBKDF2WithHMAC-SHA256AndAES256-CBC'``.

          compress (boolean):
            If ``True``, the method returns a more compact representation
            of the public key, with the X-coordinate only.

            If ``False`` (default), the method returns the full public key.

            This parameter is ignored for Ed25519/Ed448/Curve25519/Curve448,
            as compression is mandatory.

          prot_params (dict):
            When a private key is exported with password-protection
            and PKCS#8 (both ``DER`` and ``PEM`` formats), this dictionary
            contains the  parameters to use to derive the encryption key
            from the passphrase.
            For all possible values,
            refer to :ref:`the encryption parameters of PKCS#8<enc_params>`.
            The defaults meet the OWASP recommendations (with the default
            ``protection``): there is no need to set them.

          randfunc (callable):
            (*Private keys only*)
            A function that returns random bytes, for the encryption
            (salt and IV). By default, :func:`Crypto.Random.get_random_bytes`.

          threads (integer):
            (*Private keys only*)
            The maximum number of threads used to derive the encryption key
            with scrypt (default: 1, no extra threads; 0 for all CPU cores).
            Only useful with PKCS#8, a scrypt ``protection`` and a
            ``parallelization`` larger than 1 in ``prot_params``.
            The output does not depend on the number of threads.

        .. warning::
            If you don't provide a passphrase, the private key will be
            exported in the clear!

        .. _PEM:        https://www.rfc-editor.org/rfc/rfc1421
        .. _`PEM encryption`: https://www.rfc-editor.org/rfc/rfc1423
        .. _OpenSSH:    http://www.openssh.com/txt/rfc5656.txt
        .. _RFC5480:    https://tools.ietf.org/html/rfc5480
        .. _SEC1:       https://www.secg.org/sec1-v2.pdf
        .. _RFC7748:    https://tools.ietf.org/html/rfc7748

        Returns:
            A multi-line string (for ``'PEM'`` and ``'OpenSSH'``) or
            ``bytes`` (for ``'DER'``, ``'SEC1'``, and ``'raw'``) with the encoded key.
        """

        ext_format = format
        if ext_format not in ("PEM", "DER", "OpenSSH", "SEC1", "raw"):
            raise ValueError("Unknown format '%s'" % ext_format)

        if self.has_private():
            from Crypto.IO import PKCS8

            PKCS8._check_kdf_params(threads=threads)
            if passphrase is not None:
                passphrase = tobytes(passphrase)
                if not passphrase:
                    raise ValueError("Empty passphrase")
            elif protection or prot_params:
                raise ValueError("'protection' and 'prot_params' require a passphrase")

            if use_pkcs8 is False:
                if self._curve.is_edwards:
                    raise ValueError("'pkcs8' must be True for EdDSA curves")
                if self._curve.is_montgomery:
                    raise ValueError("'pkcs8' must be True for Curve25519")
                if protection is not None:
                    raise ValueError("'protection' is only supported for PKCS#8")

            # The parameters for the encryption with PKCS#8
            enc_args = {"protection": protection, "prot_params": prot_params, "randfunc": randfunc}

            if ext_format == "PEM":
                if use_pkcs8:
                    if passphrase:
                        return self._export_private_encrypted_pkcs8_in_clear_pem(
                            passphrase, threads=threads, **enc_args
                        )
                    else:
                        return self._export_private_clear_pkcs8_in_clear_pem()
                else:
                    if prot_params is not None:
                        raise ValueError("'prot_params' is only supported for PKCS#8")
                    return self._export_private_pem(passphrase, randfunc=randfunc)
            elif ext_format == "DER":
                # DER
                if passphrase and not use_pkcs8:
                    raise ValueError("Private keys can only be encrypted with DER using PKCS#8")
                if use_pkcs8:
                    return self._export_pkcs8(passphrase=passphrase, threads=threads, **enc_args)
                else:
                    return self._export_rfc5915_private_der()
            else:
                raise ValueError("Private keys cannot be exported in the '%s' format" % ext_format)
        else:  # Public key
            private_only = {
                "passphrase": passphrase,
                "use_pkcs8": None if use_pkcs8 is True else use_pkcs8,
                "protection": protection,
                "prot_params": prot_params,
                "randfunc": randfunc,
                "threads": None if threads == 1 else threads,
            }
            unexpected = {k: v for k, v in private_only.items() if v is not None}
            if unexpected:
                raise ValueError("Unexpected parameters: '%s'" % unexpected)
            if ext_format == "PEM":
                return self._export_public_pem(compress)
            elif ext_format == "DER":
                return self._export_subjectPublicKeyInfo(compress)
            elif ext_format == "SEC1":
                return self._export_SEC1(compress)
            elif ext_format == "raw":
                if self._curve.is_edwards:
                    return self._export_eddsa_public()
                elif self._curve.is_montgomery:
                    return self._export_montgomery_public()
                else:
                    return self._export_SEC1(compress)
            else:
                return self._export_openssh(compress)


def generate(*, curve: str, randfunc: Optional[RNG] = None) -> EccKey:
    """Generate a new private key on the given curve.

    Args:

      curve (string):
        Mandatory. It must be a curve name defined in the `ECC table`_.

      randfunc (callable):
        Optional. The RNG to read randomness from.
        If ``None``, :func:`Crypto.Random.get_random_bytes` is used.
    """

    curve_name = curve
    curve_info = _curves[curve_name]
    if randfunc is None:
        randfunc = get_random_bytes

    if _curves[curve_name].id == _CurveID.ED25519:
        seed = randfunc(32)
        new_key = EccKey(curve=curve_name, seed=seed)
    elif _curves[curve_name].id == _CurveID.ED448:
        seed = randfunc(57)
        new_key = EccKey(curve=curve_name, seed=seed)
    elif _curves[curve_name].id == _CurveID.CURVE25519:
        seed = randfunc(32)
        new_key = EccKey(curve=curve_name, seed=seed)
        _curves[curve_name].validate(new_key.pointQ)
    elif _curves[curve_name].id == _CurveID.CURVE448:
        seed = randfunc(56)
        new_key = EccKey(curve=curve_name, seed=seed)
        _curves[curve_name].validate(new_key.pointQ)
    else:
        d = Integer.random_range(min_inclusive=1, max_exclusive=curve_info.order, randfunc=randfunc)
        new_key = EccKey(curve=curve_name, d=d)

    return new_key


def construct(**kwargs: Unpack[ConstructParams]) -> EccKey:
    """Build a new ECC key (private or public) starting
    from some base components.

    In most cases, you will already have an existing key
    which you can read in with :func:`import_key` instead
    of this function.

    Args:
      curve (string):
        Mandatory. The name of the elliptic curve, as defined in the `ECC table`_.

      d (integer):
        Mandatory for a private key and a NIST P-curve (e.g., P-256) or secp256k1.
        It must be an integer in the range ``[1..order-1]``.

      seed (bytes):
        Mandatory for a private key and curves Ed25519 (32 bytes),
        Curve25519 (32 bytes), Curve448 (56 bytes) and Ed448 (57 bytes).

      point_x (integer):
        The X coordinate (affine) of the ECC point.
        Mandatory for a public key.

      point_y (integer):
        The Y coordinate (affine) of the ECC point.
        Mandatory for a public key,
        except for Curve25519 and Curve448.

    Returns:
      :class:`EccKey` : a new ECC key object
    """

    params = cast(dict[str, Any], kwargs)

    curve_name = params["curve"]
    curve = _curves[curve_name]
    point_x = params.pop("point_x", None)
    point_y = params.pop("point_y", None)

    if "point" in params:
        raise TypeError("Unknown keyword: point")

    if curve.id == _CurveID.CURVE25519 or curve.id == _CurveID.CURVE448:
        if point_x is not None:
            params["point"] = EccXPoint(point_x, curve_name)
        new_key = EccKey(**params)
        curve.validate(new_key.pointQ)

    else:
        if None not in (point_x, point_y):
            params["point"] = EccPoint(point_x, point_y, curve_name)
        new_key = EccKey(**params)

        # Validate that the private key matches the public one
        # because EccKey will not do that automatically
        if new_key.has_private() and "point" in params:
            pub_key = curve.G * new_key._d
            if pub_key.xy != (int(point_x), int(point_y)):
                raise ValueError("Private and public ECC keys do not match")

    return new_key


def _import_public_der(ec_point, curve_oid=None, curve_name=None):
    """Convert an encoded EC point into an EccKey object

    ec_point: byte string with the EC point (SEC1-encoded)
    curve_oid: string with the name the curve
    curve_name: string with the OID of the curve

    Either curve_id or curve_name must be specified

    """

    for _curve_name, curve in _curves.items():
        if curve_oid and curve.oid == curve_oid:
            break
        if curve_name == _curve_name:
            break
    else:
        if curve_oid:
            raise UnsupportedEccFeature("Unsupported ECC curve (OID: %s)" % curve_oid)
        else:
            raise UnsupportedEccFeature("Unsupported ECC curve (%s)" % curve_name)

    # See 2.2 in RFC5480 and 2.3.3 in SEC1
    # The first byte is:
    # - 0x02:   compressed, only X-coordinate, Y-coordinate is even
    # - 0x03:   compressed, only X-coordinate, Y-coordinate is odd
    # - 0x04:   uncompressed, X-coordinate is followed by Y-coordinate
    #
    # PAI is in theory encoded as 0x00.

    modulus_bytes = curve.p.size_in_bytes()
    point_type = ec_point[0]

    # Uncompressed point
    if point_type == 0x04:
        if len(ec_point) != (1 + 2 * modulus_bytes):
            raise ValueError("Incorrect EC point length")
        x = Integer.from_bytes(ec_point[1 : modulus_bytes + 1])
        y = Integer.from_bytes(ec_point[modulus_bytes + 1 :])
    # Compressed point
    elif point_type in (0x02, 0x03):
        if len(ec_point) != (1 + modulus_bytes):
            raise ValueError("Incorrect EC point length")
        x = Integer.from_bytes(ec_point[1:])
        if x >= curve.p:
            raise ValueError("The EC point does not belong to the curve")
        # Right now, we only support Short Weierstrass curves:
        # y^2 = x^3 + b for secp256k1, and y^2 = x^3 - 3x + b for the others,
        # computed without negative intermediate values
        if curve.id == _CurveID.SECP256K1:
            y = (x**3 + curve.b).sqrt(curve.p)
        else:
            y = (x**3 + curve.b + (curve.p - x) * 3).sqrt(curve.p)
        if point_type == 0x02 and y.is_odd():
            y = curve.p - y
        if point_type == 0x03 and y.is_even():
            y = curve.p - y
    else:
        raise ValueError("Incorrect EC point encoding")

    # SEC1 3.2.2.1: the coordinates must be smaller than p
    # (EccPoint would reduce them)
    if x >= curve.p or y >= curve.p:
        raise ValueError("The EC point does not belong to the curve")

    return construct(curve=_curve_name, point_x=x, point_y=y)


def _import_subjectPublicKeyInfo(encoded, *kwargs):
    """Convert a subjectPublicKeyInfo into an EccKey object"""

    # See RFC5480

    # Parse the generic subjectPublicKeyInfo structure
    oid, ec_point, params = _expand_subject_public_key_info(encoded)

    nist_p_oids = (
        "1.2.840.10045.2.1",  # id-ecPublicKey (unrestricted)
        "1.3.132.1.12",  # id-ecDH
        "1.3.132.1.13",  # id-ecMQV
    )
    eddsa_oids = {
        "1.3.101.112": ("Ed25519", _import_ed25519_public_key),  # id-Ed25519
        "1.3.101.113": ("Ed448", _import_ed448_public_key),  # id-Ed448
    }
    xdh_oids = {
        "1.3.101.110": ("Curve25519", _import_curve25519_public_key),  # id-X25519
        "1.3.101.111": ("Curve448", _import_curve448_public_key),  # id-X448
    }

    if oid in nist_p_oids:
        # See RFC5480

        # Parameters are mandatory and encoded as ECParameters
        # ECParameters ::= CHOICE {
        #   namedCurve         OBJECT IDENTIFIER
        #   -- implicitCurve   NULL
        #   -- specifiedCurve  SpecifiedECDomain
        # }
        # implicitCurve and specifiedCurve are not supported (as per RFC)
        if not params:
            raise ValueError("Missing ECC parameters for ECC OID %s" % oid)
        try:
            curve_oid = DerObjectId().decode(params).value
        except ValueError:
            raise ValueError("Error decoding namedCurve")

        # ECPoint ::= OCTET STRING
        return _import_public_der(ec_point, curve_oid=curve_oid)

    elif oid in eddsa_oids:
        # See RFC8410
        curve_name, import_eddsa_public_key = eddsa_oids[oid]

        # Parameters must be absent
        if params:
            raise ValueError("Unexpected ECC parameters for ECC OID %s" % oid)

        x, y = import_eddsa_public_key(ec_point)
        return construct(point_x=x, point_y=y, curve=curve_name)

    elif oid in xdh_oids:
        curve_name, import_xdh_public_key = xdh_oids[oid]

        # Parameters must be absent
        if params:
            raise ValueError("Unexpected ECC parameters for ECC OID %s" % oid)

        x = import_xdh_public_key(ec_point)
        return construct(point_x=x, curve=curve_name)

    else:
        raise UnsupportedEccFeature("Unsupported ECC OID: %s" % oid)


def _import_rfc5915_der(
    encoded: bytes, passphrase: Optional[bytes], curve_oid: Optional[str] = None
) -> EccKey:
    # See RFC5915 https://tools.ietf.org/html/rfc5915
    #
    # ECPrivateKey ::= SEQUENCE {
    #           version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
    #           privateKey     OCTET STRING,
    #           parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
    #           publicKey  [1] BIT STRING OPTIONAL
    #    }

    ec_private_key = DerSequence().decode(encoded, nr_elements=(2, 3, 4))
    if ec_private_key[0] != 1:
        raise ValueError("Incorrect ECC private key version")

    scalar_bytes = DerOctetString().decode(ec_private_key[1]).payload

    next_element = 2

    # Try to decode 'parameters'
    if next_element < len(ec_private_key):
        try:
            parameters = DerObjectId(explicit=0).decode(ec_private_key[next_element]).value
            if curve_oid is not None and parameters != curve_oid:
                raise ValueError("Curve mismatch")
            curve_oid = parameters
            next_element += 1
        except ValueError:
            pass

    if curve_oid is None:
        raise ValueError("No curve found")

    for curve_name, curve in _curves.items():  # noqa: B007 (used after the loop)
        if curve.oid == curve_oid:
            break
    else:
        raise UnsupportedEccFeature("Unsupported ECC curve (OID: %s)" % curve_oid)

    modulus_bytes = curve.p.size_in_bytes()
    if len(scalar_bytes) != modulus_bytes:
        raise ValueError("Private key is too small")

    # Try to decode 'publicKey'
    point_x = point_y = None
    if next_element < len(ec_private_key):
        try:
            public_key_enc = DerBitString(explicit=1).decode(ec_private_key[next_element]).value
            public_key = _import_public_der(public_key_enc, curve_oid=curve_oid)
            point_x = public_key.pointQ.x
            point_y = public_key.pointQ.y
            next_element += 1
        except ValueError:
            pass

    d = Integer.from_bytes(scalar_bytes)
    return construct(curve=curve_name, d=d, point_x=point_x, point_y=point_y)


def _import_pkcs8(encoded, passphrase, max_iteration_count=None, threads=1, max_memory=None):
    from Crypto.IO import PKCS8

    unwrapped = PKCS8.unwrap(encoded, passphrase, max_iteration_count, threads=threads, max_memory=max_memory)
    algo_oid, private_key, params = unwrapped

    nist_p_oids = (
        "1.2.840.10045.2.1",  # id-ecPublicKey (unrestricted)
        "1.3.132.1.12",  # id-ecDH
        "1.3.132.1.13",  # id-ecMQV
    )
    eddsa_oids = {
        "1.3.101.112": ("Ed25519", _import_ed25519_public_key),  # id-Ed25519
        "1.3.101.113": ("Ed448", _import_ed448_public_key),  # id-Ed448
    }
    xdh_oids = {
        "1.3.101.110": ("Curve25519", _import_curve25519_public_key),  # id-X25519
        "1.3.101.111": ("Curve448", _import_curve448_public_key),  # id-X448
    }

    # The optional publicKey of a OneAsymmetricKey (RFC 5958),
    # which must match the private key
    public_key = unwrapped.public_key

    if algo_oid in nist_p_oids:
        curve_oid = DerObjectId().decode(params).value
        key = _import_rfc5915_der(private_key, passphrase, curve_oid)
        if public_key is not None:
            # ECPoint (RFC 5480), as in the subjectPublicKeyInfo
            point = _import_public_der(public_key, curve_oid=curve_oid).pointQ
    elif algo_oid in eddsa_oids:
        curve_name, import_eddsa_public_key = eddsa_oids[algo_oid]
        if params is not None:
            raise ValueError("EdDSA ECC private key must not have parameters")
        seed = DerOctetString().decode(private_key).payload
        key = construct(curve=curve_name, seed=seed)
        if public_key is not None:
            x, y = import_eddsa_public_key(public_key)
            point = construct(curve=curve_name, point_x=x, point_y=y).pointQ
    elif algo_oid in xdh_oids:
        curve_name, import_xdh_public_key = xdh_oids[algo_oid]
        if params is not None:
            raise ValueError("%s ECC private key must not have parameters" % curve_name)
        seed = DerOctetString().decode(private_key).payload
        key = construct(curve=curve_name, seed=seed)
        if public_key is not None:
            x = import_xdh_public_key(public_key)
            point = construct(curve=curve_name, point_x=x).pointQ
    else:
        raise UnsupportedEccFeature("Unsupported ECC purpose (OID: %s)" % algo_oid)

    if public_key is not None and point != key.pointQ:
        raise ValueError("Private and public ECC keys do not match")

    return key


def _import_x509_cert(encoded, *kwargs):
    sp_info = _extract_subject_public_key_info(encoded)
    return _import_subjectPublicKeyInfo(sp_info)


def _import_der(encoded, passphrase, max_iteration_count=None, threads=1, max_memory=None):
    try:
        return _import_subjectPublicKeyInfo(encoded, passphrase)
    except UnsupportedEccFeature as err:
        raise err
    except (ValueError, TypeError, IndexError):
        pass

    try:
        return _import_x509_cert(encoded, passphrase)
    except UnsupportedEccFeature as err:
        raise err
    except (ValueError, TypeError, IndexError):
        pass

    try:
        return _import_rfc5915_der(encoded, passphrase)
    except UnsupportedEccFeature as err:
        raise err
    except (ValueError, TypeError, IndexError):
        pass

    try:
        return _import_pkcs8(encoded, passphrase, max_iteration_count, threads, max_memory)
    except (UnsupportedEccFeature, KdfLimitError, PassphraseRequiredError, IncorrectPassphraseError):
        raise
    except (ValueError, TypeError, IndexError):
        pass

    raise ValueError("Not an ECC DER key")


def _import_openssh_public(encoded):
    parts = encoded.split(b" ")
    if len(parts) not in (2, 3):
        raise ValueError("Not an openssh public key")

    try:
        keystring = binascii.a2b_base64(parts[1])

        keyparts = []
        while len(keystring) > 4:
            lk = struct.unpack(">I", keystring[:4])[0]
            keyparts.append(keystring[4 : 4 + lk])
            keystring = keystring[4 + lk :]

        if parts[0] != keyparts[0]:
            raise ValueError("Mismatch in openssh public key")

        # NIST P curves
        if parts[0].startswith(b"ecdsa-sha2-"):
            for _curve_name, curve in _curves.items():
                if curve.openssh is None:
                    continue
                if not curve.openssh.startswith("ecdsa-sha2"):
                    continue
                middle = tobytes(curve.openssh.split("-")[2])
                if keyparts[1] == middle:
                    break
            else:
                raise ValueError("Unsupported ECC curve: %r" % keyparts[1])

            if tobytes(curve.openssh) != parts[0]:
                raise ValueError("Mismatch in openssh public key")

            ecc_key = _import_public_der(keyparts[2], curve_oid=curve.oid)

        # EdDSA
        elif parts[0] == b"ssh-ed25519":
            x, y = _import_ed25519_public_key(keyparts[1])
            ecc_key = construct(curve="Ed25519", point_x=x, point_y=y)
        else:
            raise ValueError("Unsupported SSH key type: %r" % parts[0])

    except (IndexError, TypeError, binascii.Error):
        raise ValueError("Error parsing SSH key type: %r" % parts[0])

    return ecc_key


def _import_openssh_private_ecc(data, password, max_iteration_count=None):
    from ._openssh import check_padding, import_openssh_private_generic, read_bytes, read_string

    key_type, decrypted = import_openssh_private_generic(data, password, max_iteration_count)

    eddsa_keys = {
        "ssh-ed25519": ("Ed25519", _import_ed25519_public_key, 32),
    }

    # https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent-04
    if key_type.startswith("ecdsa-sha2"):
        ecdsa_curve_name, decrypted = read_string(decrypted)
        if ecdsa_curve_name not in _curves:
            raise UnsupportedEccFeature("Unsupported ECC curve %s" % ecdsa_curve_name)
        curve = _curves[ecdsa_curve_name]
        # Also rejects the curves without an OpenSSH key type (secp256k1)
        if curve.openssh != key_type:
            raise UnsupportedEccFeature(
                "ECC curve %s cannot be used with SSH key type %s" % (ecdsa_curve_name, key_type)
            )
        modulus_bytes = (curve.modulus_bits + 7) // 8

        public_key, decrypted = read_bytes(decrypted)

        if public_key[0] != 4:
            raise ValueError("Only uncompressed OpenSSH EC keys are supported")
        if len(public_key) != 2 * modulus_bytes + 1:
            raise ValueError("Incorrect public key length")

        point_x = Integer.from_bytes(public_key[1 : 1 + modulus_bytes])
        point_y = Integer.from_bytes(public_key[1 + modulus_bytes :])

        private_key, decrypted = read_bytes(decrypted)
        d = Integer.from_bytes(private_key)

        params = {"d": d, "curve": ecdsa_curve_name}

    elif key_type in eddsa_keys:
        curve_name, import_eddsa_public_key, seed_len = eddsa_keys[key_type]

        public_key, decrypted = read_bytes(decrypted)
        point_x, point_y = import_eddsa_public_key(public_key)

        private_public_key, decrypted = read_bytes(decrypted)
        seed = private_public_key[:seed_len]

        params = {"seed": seed, "curve": curve_name}
    else:
        raise ValueError("Unsupported SSH agent key type: " + key_type)

    _, padded = read_string(decrypted)  # Comment
    check_padding(padded)

    return construct(point_x=point_x, point_y=point_y, **params)


def _import_ed25519_public_key(encoded: bytes) -> tuple[Int, Int]:
    """Import an Ed25519 ECC public key, encoded as raw bytes as described
    in RFC8032_.

    Args:
      encoded (bytes):
        The Ed25519 public key to import. It must be 32 bytes long.

    Returns:
      x and y (integer)

    Raises:
      ValueError: when the given key cannot be parsed.

    .. _RFC8032: https://datatracker.ietf.org/doc/html/rfc8032
    """

    if len(encoded) != 32:
        raise ValueError("Incorrect length. Only Ed25519 public keys are supported.")

    try:
        point = EccPoint._decode_eddsa(encoded, "Ed25519")
    except ValueError:
        raise ValueError("Invalid Ed25519 public key")
    return point.xy


def _import_curve25519_public_key(encoded):
    """Import a Curve25519 ECC public key,
    encoded as raw bytes as described in RFC7748_.

    Args:
      encoded (bytes):
        The Curve25519 public key to import. It must be 32 bytes long.

    Returns:
      x (integer)

    Raises:
      ValueError: when the given key cannot be parsed.

    .. _RFC7748: https://datatracker.ietf.org/doc/html/rfc7748
    """

    if len(encoded) != 32:
        raise ValueError("Incorrect Curve25519 key length")

    x = bytearray(encoded)
    # RFC 7741, Section 5
    x[31] &= 0x7F
    point_x = Integer.from_bytes(x, byteorder="little")

    return point_x


def _import_curve448_public_key(encoded):
    """Import a Curve448 ECC public key,
    encoded as raw bytes as described in RFC7748_.

    Args:
      encoded (bytes):
        The Curve448 public key to import. It must be 56 bytes long.

    Returns:
      x (integer)

    Raises:
      ValueError: when the given key cannot be parsed.

    .. _RFC7748: https://datatracker.ietf.org/doc/html/rfc7748
    """

    if len(encoded) != 56:
        raise ValueError("Incorrect Curve448 key length")

    point_x = Integer.from_bytes(encoded, byteorder="little")

    return point_x


def _import_ed448_public_key(encoded: bytes) -> tuple[Int, Int]:
    """Import an Ed448 ECC public key, encoded as raw bytes as described
    in RFC8032_.

    Args:
      encoded (bytes):
        The Ed448 public key to import. It must be 57 bytes long.

    Returns:
        x and y (integer)

    Raises:
      ValueError: when the given key cannot be parsed.

    .. _RFC8032: https://datatracker.ietf.org/doc/html/rfc8032
    """

    if len(encoded) != 57:
        raise ValueError("Incorrect length. Only Ed448 public keys are supported.")

    try:
        point = EccPoint._decode_eddsa(encoded, "Ed448")
    except ValueError:
        raise ValueError("Invalid Ed448 public key")
    return point.xy


def import_key(
    encoded: Union[bytes, str],
    passphrase: Optional[Union[str, bytes]] = None,
    curve_name: Optional[str] = None,
    *,
    max_iteration_count: Optional[int] = None,
    threads: int = 1,
    max_memory: Optional[int] = None,
) -> EccKey:
    """Import an ECC key (public or private).

    Args:
      encoded (bytes or multi-line string):
        The ECC key to import.
        The function will try to automatically detect the right format.

        Supported formats for an ECC **public** key:

        * X.509 certificate: binary (DER) or ASCII (PEM).
        * X.509 ``subjectPublicKeyInfo``: binary (DER) or ASCII (PEM).
        * SEC1_ (or X9.62), as ``bytes``. NIST P curves and secp256k1 only.
          You must also provide the ``curve_name`` (with a value from the `ECC table`_)
        * OpenSSH line, defined in RFC5656_ and RFC8709_ (ASCII).
          This is normally the content of files like ``~/.ssh/id_ecdsa.pub``.

        Supported formats for an ECC **private** key:

        * A binary ``ECPrivateKey`` structure, as defined in `RFC5915`_ (DER).
          NIST P curves and secp256k1 only.
        * A `PKCS#8`_ structure (or the more recent Asymmetric Key
          Package, RFC5958_): binary (DER) or ASCII (PEM).
        * `OpenSSH 6.5`_ and newer versions (ASCII).

        Private keys can be in the clear or password-protected.

        For details about the PEM encoding, see `RFC1421`_/`RFC1423`_.

      passphrase (byte string):
        The passphrase to use for decrypting a private key.
        Encryption may be applied protected at the PEM level (not recommended)
        or at the PKCS#8 level (recommended).
        This parameter is ignored if the key in input is not encrypted.

      curve_name (string):
        For a SEC1 encoding only. This is the name of the curve,
        as defined in the `ECC table`_.

      max_iteration_count (integer):
        The maximum value allowed for the PBKDF2 iteration count or
        the scrypt cost *N·r·p/8* (that is, *N* for the usual *r=8* and *p=1*)
        of a `PKCS#8`_ encrypted key, or for the number of rounds of
        bcrypt_pbkdf of an encrypted OpenSSH key.
        A malicious key could require an extremely high number of
        iterations, tying up the CPU for hours.
        The default value is None: a limit for each KDF, which keeps
        the key derivation to about one minute at most
        (see :ref:`the limits for decryption<pkcs8_limits>`).
        Set it to ``0`` to disable the check.

      threads (integer):
        The maximum number of threads used to derive the decryption key
        with scrypt (default: 1, no extra threads; 0 for all CPU cores).
        Only a `PKCS#8`_ key protected with scrypt and a parallelization
        parameter larger than 1 can use them.

      max_memory (integer):
        The maximum memory (in bytes) for the key derivation of a `PKCS#8`_
        encrypted key, as a limit on the **total** of all threads together
        (not on each thread).
        Only scrypt uses a significant amount: 128 · (*N* + 2) · *r* bytes for
        **each** thread, plus 256 · *r* · *p* bytes for all the flows together.
        If the requested threads do not fit, fewer are used (down to one);
        if a single thread does not fit, the key is rejected.
        The default value is None (a limit of 256 MiB).
        Set it to ``0`` to disable the check.

    .. note::

        To import EdDSA private and public keys, when encoded as raw ``bytes``, use:

        * :func:`Crypto.Signature.eddsa.import_public_key`, or
        * :func:`Crypto.Signature.eddsa.import_private_key`.

    .. note::

        To import X25519/X448 private and public keys, when encoded as raw ``bytes``, use:

        * :func:`Crypto.Protocol.DH.import_x25519_public_key`
        * :func:`Crypto.Protocol.DH.import_x25519_private_key`
        * :func:`Crypto.Protocol.DH.import_x448_public_key`
        * :func:`Crypto.Protocol.DH.import_x448_private_key`

    Returns:
      :class:`EccKey` : a new ECC key object

    Raises:
      PassphraseRequiredError:
        (a ``ValueError``) when the private key is encrypted,
        but no passphrase was provided.
      IncorrectPassphraseError:
        (a ``ValueError``) when the private key cannot be decrypted
        with the passphrase (most likely, the passphrase is wrong).
      UnsupportedEccFeature:
        (a ``ValueError``) when the key uses a curve or a feature
        that is not supported.
      ValueError: when the given key cannot be parsed.

    .. _RFC1421: https://datatracker.ietf.org/doc/html/rfc1421
    .. _RFC1423: https://datatracker.ietf.org/doc/html/rfc1423
    .. _RFC5915: https://datatracker.ietf.org/doc/html/rfc5915
    .. _RFC5656: https://datatracker.ietf.org/doc/html/rfc5656
    .. _RFC8709: https://datatracker.ietf.org/doc/html/rfc8709
    .. _RFC5958: https://datatracker.ietf.org/doc/html/rfc5958
    .. _`PKCS#8`: https://datatracker.ietf.org/doc/html/rfc5958
    .. _`OpenSSH 6.5`: https://flak.tedunangst.com/post/new-openssh-key-format-and-bcrypt-pbkdf
    .. _SEC1: https://www.secg.org/sec1-v2.pdf
    """

    from Crypto.IO import PEM, PKCS8

    PKCS8._check_kdf_params(max_iteration_count, threads, max_memory)
    encoded = tobytes(encoded)
    if passphrase is not None:
        passphrase = tobytes(passphrase)

    # PEM
    if encoded.startswith(b"-----BEGIN OPENSSH PRIVATE KEY"):
        text_encoded = encoded.decode("latin-1")
        openssh_encoded, _marker, enc_flag = PEM.decode(text_encoded, passphrase)
        result = _import_openssh_private_ecc(openssh_encoded, passphrase, max_iteration_count)
        return result

    elif encoded.startswith(b"-----"):
        text_encoded = encoded.decode("latin-1")

        # Remove any EC PARAMETERS section
        # Ignore its content because the curve type must be already given in the key
        ecparams_start = "-----BEGIN EC PARAMETERS-----"
        ecparams_end = "-----END EC PARAMETERS-----"
        text_encoded = re.sub(ecparams_start + ".*?" + ecparams_end, "", text_encoded, flags=re.DOTALL)

        der_encoded, _marker, enc_flag = PEM.decode(text_encoded, passphrase)
        if enc_flag:
            passphrase = None
        try:
            result = _import_der(der_encoded, passphrase, max_iteration_count, threads, max_memory)
        except (
            UnsupportedEccFeature,
            KdfLimitError,
            PassphraseRequiredError,
            IncorrectPassphraseError,
        ):
            raise
        except ValueError:
            raise ValueError("Invalid DER encoding inside the PEM file")
        return result

    # OpenSSH
    if encoded.startswith((b"ecdsa-sha2-", b"ssh-ed25519")):
        return _import_openssh_public(encoded)

    # DER
    if len(encoded) > 0 and encoded[0] == 0x30:
        return _import_der(encoded, passphrase, max_iteration_count, threads, max_memory)

    # SEC1
    if len(encoded) > 0 and encoded[0] in (0x02, 0x03, 0x04):
        if curve_name is None:
            raise ValueError("No curve name was provided")
        return _import_public_der(encoded, curve_name=curve_name)

    raise ValueError("ECC key format is not supported")
