Source code for pyfe4ai.core.profiles

"""Security profiles: parameter presets for the key generators.

The ``security_profile`` entry of a key generator's config selects one:

``"toy"`` (default)
    The library's small defaults -- fast enough for tests and demos, and
    **not secure**. A config without the entry, or with ``"toy"``, is used
    exactly as given.

``"standard"``
    Parameters aiming at 128-bit classical security where this prototype
    can reach it, chosen per scheme family:

    * discrete log (DDH, Damgard / MDDH and their MIFE / MCFE variants): a
      3072-bit safe-prime group, the RFC 3526 MODP group 15 (NIST SP 800-57
      Part 1 Rev. 5, Table 2: L = 3072 for 128 bits). The decentralised
      ``mcfe/ddh_decentralized`` then needs a DCR modulus of about 6410 bits
      for combining key shares, whose safe primes take minutes to generate;
    * DCR (Paillier variants): ``N`` of about 3072 bits (two 1536-bit safe
      primes);
    * DDH over ristretto255 (``*/ddh_ristretto255``): the group is fixed, of
      order about ``2^252`` (Pollard rho about ``2^126``, the 128-bit class of
      X25519 / Ed25519); the profile sets nothing;
    * Ring-LWE: ``kappa = 128`` and the smallest ring dimension whose derived
      modulus ``q`` meets :data:`~pyfe4ai.primitives.lattice.ring_lwe_utils.RING_LWE_128_LOG_Q`,
      a lattice-estimator table (the method of the LWE tables) read at the
      smallest width among the RLWE problems of the scheme's proof (about 8
      for the BMMS21 parameters of Sec. 4.2 and 5; ``log2 q`` <= 31 / 57 /
      111 / 218 / 435 / 872 for n = 1024 ... 32768 at that width). It replaces the
      Homomorphic Encryption Standard's bound, which this estimator puts at
      about 125 bits; ``sife/fullysec_ring_lwe`` (BMMS21 Sec. 5, adaptive) also
      picks its number ``m`` of key polynomials: the smallest one whose ``q``
      meets that bound;
    * dense LWE, the ABMPSW20 MCFE family (``mcfe/lwe``, ``fullysec_lwe``,
      ``lwe_threshold``, ``lwe_decentralized``): ``lwe_n = "auto"``, the
      smallest dimension ``n0`` whose derived ``q`` meets
      :data:`~pyfe4ai.primitives.lattice.lwe_utils.DENSE_LWE_128_LOG_Q` -- a lattice
      estimator table for LWE(n0, q, ceil(2 sqrt(n0))) with a uniform secret,
      the problem of the scheme's security proof. These schemes have no public
      matrix: a label is hashed to ``n0 + m0`` values and each ciphertext is
      one inner product, cheaper than a 3072-bit DDH ciphertext. The
      decentralised variant combines key shares under a DCR modulus of at
      least 3072 bits (``STANDARD_DCR_PRIME_BITS`` per prime), so its key
      derivation is not post-quantum;
    * LWE with a seeded public matrix, the ABDP15 IPFE (``sife/lwe``,
      ``mife/lwe``): ``lwe_n = "auto"``, the smallest dimension whose
      ``(q, sigma')`` meets :data:`~pyfe4ai.primitives.lattice.lwe_utils.WIDE_LWE_128_LOG_RATIO`,
      a lattice estimator table for LWE with a uniform secret, read at the
      error width ``sigma' = sigma / (1 + B sqrt(eta))`` of the LWE problem the
      security proof reduces to (``B = 2 X``, or ``4 X`` in the slots of
      ``mife/lwe``; ABDP15 full version, Thm. 4.4, with the width derived in
      the proof of Thm. 6.6; see :mod:`pyfe4ai.schemes.common.abdp15_lwe`).
      That gives ``lwe_n`` of about 900 to 1700 for small bounds; reading the
      table at the public key's own ``sigma`` would give 14 to 40 percent
      less for ``X = Y >= 8``. ``A``
      (``m ~ lwe_n log2 q`` rows, 10^8 to 4 * 10^8 entries) is published as a
      seed; ``matrix_mode`` chooses between keeping it expanded in memory
      (about 0.1 to 0.3 s per encryption, 2 to 6 GB) and expanding it for
      every batch (``"stream"``: seconds per batch, about 0.5 GB). The noise
      ``E`` of the public keys is sampled exactly (``"facct"``;
      ``"rounded"`` is refused);
    * pairing-based schemes: BLS12-381 (the Rust extension) -- about 120 to
      126-bit security after the exTNFS attacks on its 381-bit field
      (Barbulescu and Duquesne, J. Cryptol. 2019), **below** the 128-bit
      target; a :class:`~pyfe4ai.core.exceptions.FESecurityWarning` says so.

    Refused, because no parameter set is both secure and usable in pure
    Python: ``sife/fullysec_lwe`` and ``mife/fullysec_lwe`` (ALS16: at a
    dimension the lattice estimator puts at 128 bits, n = 3328 to 4352 for
    ``X = Y <= 50`` and ``eta <= 10``, and with ``m = 4 n log2 q`` as its
    Theorem 2 requires, the public matrix has 1 to 2 * 10^10 entries and a
    ciphertext 2.9 to 4.5 * 10^6 coordinates of 219 to 258 bits),
    ``mife/lwe_threshold``. At ``sigma'`` its flooding-enlarged ``q``
    (``stat_param = 128``, two clients) needs an LWE dimension at the very end
    of the table (5888 to 6144, ``A`` of 6.5 to 7.8 * 10^9 entries) for a few
    small bounds, e.g. ``X = Y`` in {1, 2, 3, 8, 10} at ``eta = 1``, and
    beyond it for most others (``X = Y`` = 4 to 6, ``X = Y >= 12``, most
    ``eta >= 2``; about 6500 for ``X = Y = 50``). The opt-in KL accounting
    of the flooding noise (``t = s`` only, with an explicit budget and a
    weaker guarantee) would take about 4864, but the refusal holds with
    either accounting. Also refused:
    ``mcfe/paillier_decentralized`` (its key shares are combined under a DCR
    modulus of more than 15000 bits).

    The statistical parameter ``stat_param`` (rounding and leftover-hash
    slack of ABMPSW20, flooding of the threshold schemes, the masks of
    ``mcfe/ddh_decentralized`` and the Paillier and MCFE2 key distributions)
    is 128, as ``kappa`` of the Ring-LWE schemes: a statistical distance of
    ``2^-128`` per sample, so that ``Q`` samples stay below ``Q 2^-128``.
    That holds for exact Gaussians (the ``"facct"`` sampler, the default of
    the Ring-LWE MIFE / MCFE family, the dense-LWE MCFE keys, SIFE FullySec
    Ring-LWE and, under ``standard``, the ABDP15 public keys) and for the
    MPFR path at ``bits(sigma) + 320`` bits;
    the double-precision rounded sampler does not meet it (see the security
    notes in the documentation).

    ``mcfe/ring_lwe_threshold`` with ``t = s`` (Shamir sharing, Gaussian
    flooding) also accepts, as an explicit opt-in that the profile never
    sets, ``flooding_accounting = "kl"`` with a mandatory
    ``max_partial_decryptions`` (``mife/lwe_threshold`` takes it too, but only
    under ``toy``: this profile refuses that scheme with either accounting). There ``stat_param`` bounds the Hellinger
    distance between the adversary's real and simulated views of a run in
    which every server produces at most that many partial decryptions,
    instead of the per-sample statistical distance above:
    ``stat_param - 6.85`` bits of statistical security in the bit-security
    framework of Micciancio and Walter for decision games, or about
    ``2^-65`` of total statistical distance read conventionally. The servers
    count against the limit in a budget that the deployment gives them and
    must keep across processes and restarts. A different and weaker
    guarantee, with its own parameter records; see
    :mod:`pyfe4ai.schemes.threshold.kl`.

    Values given explicitly in the config must not be weaker than the
    profile's.

The profile's rules -- its minima and presets per kind
(:mod:`pyfe4ai.core.config`) and the refusals (each scheme's ``refused``
in :mod:`pyfe4ai.core.registry`) -- are applied by
:func:`pyfe4ai.core.config.resolve` when a key generator is built; this
module names the profiles and the kind of every scheme.
"""

from __future__ import annotations

from pyfe4ai.core.registry import SCHEMES, scheme_for_module

TOY = "toy"
STANDARD = "standard"
PROFILES = (TOY, STANDARD)

#: Kind of every registered ``(family, variant)`` (the parameters the
#: profiles size: :data:`pyfe4ai.core.registry.KINDS`), derived from the
#: registry.
#:
#: :meta hide-value:
SCHEME_KIND = {scheme.key: scheme.kind for scheme in SCHEMES}

#: Bits of each prime of a DCR modulus under ``standard`` (``N`` of 3072 bits).
STANDARD_DCR_PRIME_BITS = 1536

#: Statistical parameter under ``standard`` (minimum and default).
STANDARD_STAT_PARAM = 128
#: Largest statistical parameter a key generator accepts, in both profiles;
#: the MPFR Gaussians' guard bits (:data:`~pyfe4ai.primitives.sampling.BIGINT_GUARD_BITS`)
#: cover ``stat_param + ceil(log2 N) + 8`` up to this value.
MAX_STAT_PARAM = 256


[docs] def scheme_of(cls: type) -> tuple | None: """Return the registered ``(family, variant)`` that *cls* implements or extends.""" for klass in cls.__mro__: found = scheme_for_module(klass.__module__) if found is not None: return found.key return None