Security & Status

PyFE4AI is a research prototype: no scheme is hardened against side channels, and the default parameters are toy values. This page lists the review status of every scheme and the parameter presets; the README sections Scheme Status, Security Profiles and Known Issues / Security Notice add measured costs and the remaining limitations.

Scheme status

42 registered schemes: 34 reviewed, 5 experimental, 3 unreviewed and 0 known-insecure. The experimental ones are the threshold layers of tMIFE-LWE, tMCFE-LWE, tMCFE-Ring-LWE and tMCFE-FH-Multi-IPE and the carry mask of dMCFE-DDH: our own constructions on top of published schemes, with no published security proof. Query a scheme at runtime with pyfe4ai.get_scheme_status(family, variant) (or "family/variant") or FESchemeWrapper(...).status; the key generator and crypto objects of a known-insecure scheme emit FESecurityWarning. The table is rendered from SCHEME_STATUS, which is derived from the scheme registry (pyfe4ai.core.registry); the status and fidelity values are defined with SchemeStatus.

Scheme

Status

Fidelity

Construction

Notes

sife/ddh

reviewed

faithful

ABDP15 Construction 1 (DDH)

signed x, y; decryption bound dlog_bound (default 10^(precision + 2))

sife/ddh_ristretto255

unreviewed

unknown

ABDP15 Construction 1 (DDH) in ristretto255

ristretto255 (RFC 9496, about 126-bit security: Pollard rho in a group of order 2^252) instead of a 3072-bit Z_p^* subgroup; group arithmetic in Rust (curve25519-dalek) with the native extension, else in pure Python and not constant-time; new code, not yet reviewed

sife/lwe

reviewed

adapted

ABDP15 full version (ePrint 2015/017), LWE

the LWE instantiation as revised in the full version (no r-ary decomposition); its selective IND-CPA proof is the full version’s Thm. 4.4 with Thms. 6.6 and 6.7 (Bourse 2017 Thm. 4.3.5 with 8.2.2, 8.2.3, 8.3.2), not PKC Thm. 2, which does not cover noisy LWE keys; it reduces to LWE with errors sigma / (1 + 2X sqrt(eta)), the width standard reads the 128-bit table at; signed X/Y-bounded inputs with centred decoding; GoFE parameter recipe; pk noise E <- D_{Z,sigma_q} exactly (FACCT) under standard, the library-wide sampler under toy; A expanded from a public seed (SHAKE-128)

sife/paillier

reviewed

faithful

ALS16 (DCR)

D_{Z,sigma} realised as a high-precision rounded Gaussian

sife/damgard

reviewed

faithful

ALS16 DDH (ACFGU18 Fig. 7, k = 1)

h = g^a with a discarded

sife/ring_lwe

reviewed

adapted

BMMS21 Sec. 4 (selective) + Remark 1 batching

signed messages and weights (p = 2lXY + 2, centred decoding); widths from the Sec. 4.2 constraints (the paper’s Table 1 misses its smoothing conditions); one ciphertext packs up to ring_n vectors; single NTT prime q; ring_n defaults to the toy value 32

sife/fullysec_ring_lwe

reviewed

adapted

BMMS21 Sec. 5 (adaptive)

m >= 5 key polynomials instead of m = 3, which admits no modulus under the paper’s App. C.1 constraints (sigma’ >= q^(2/m) from Ring-LHL); signed inputs (p = 2lXY + 2, leveraging over (2X+1)^(2l)); one vector per ciphertext. Experimental: ndarray_layout = “packed” (up to ring_n vectors per ciphertext, leveraging over the span of the differences) rests on this project’s own argument, independently checked, not externally reviewed

sife/fullysec_lwe

reviewed

adapted

ALS16 LWE (ACFGU18 Fig. 8)

signed inputs (K = 2 eta X Y + 1, centred decoding); GoFE parameter recipe with m = 4 n log2 q (ALS16 Theorem 2); D_{Z,sigma} realised as a rounded Gaussian

sife/ddh_dynamic

reviewed

adapted

ABDP15 Construction 1, variable length

vectors of length <= eta handled as zero padding (prefix keys, truncated ciphertexts)

sife/fh_ipe_pairing

reviewed

adapted

KLMMRW18 Sec. 3

vectors padded to (x, 1, 0) / (y, 0, 1), so eta = 1 and zero vectors are safe; keys in G1, ciphertexts in G2; generic bilinear group model; the encryption key is master-secret equivalent

sife/part_fh_ipe_pairing

reviewed

adapted

Gay20 Fig. 8 (CPA, k = 1)

M is a random full-rank public matrix (subspace_dim < eta, default 1), published with V^T M in the clear; keys leak M^T y, which for small eta - subspace_dim often determines a bounded y; public Enc needs bounded t; Enc’ is run by the key generator; no QANIZK

mife/ddh

reviewed

adapted

ACFGU18 Fig. 4 + ABDP15 (DDH)

ACFGU18 instantiates Fig. 4 with ALS16; here the selectively secure ABDP15 IPFE in every slot

mife/ddh_ristretto255

unreviewed

unknown

ACFGU18 Fig. 4 + ABDP15 (DDH) in ristretto255

ristretto255 (RFC 9496, about 126-bit security: Pollard rho in a group of order 2^252) instead of a 3072-bit Z_p^* subgroup; group arithmetic in Rust (curve25519-dalek) with the native extension, else in pure Python and not constant-time; new code, not yet reviewed

mife/lwe

reviewed

adapted

ACFGU18 Fig. 4 + ABDP15 (LWE)

ACFGU18 instantiates Fig. 4 with ALS16; here the selectively secure ABDP15 IPFE (same two-step decryption; full-version proof, see sife/lwe), with parameters for dimension n * eta (footnote 14); the slot challenges of the ACFGU18 reduction differ by up to 4X, so standard reads the 128-bit table at sigma / (1 + 4X sqrt(eta)); slots share A, expanded from a public seed (SHAKE-128); pk noise exact (FACCT) under standard

mife/paillier

reviewed

adapted

ACFGU18 Fig. 4 + ALS16 (DCR, Fig. 9)

slots share N and g; D_{Z,sigma} realised as a high-precision rounded Gaussian

mife/damgard

reviewed

adapted

ACFGU18 Fig. 4 + ALS16 (DDH, Fig. 7)

slots share the group and h (the matrix A of Fig. 7)

mife/ring_lwe

reviewed

adapted

BMMS21 Sec. 6 MIFE-compiler (ACFGU18)

slots share a; parameters sized for dimension n * eta, with the Sec. 4.2 widths for bound 3X (ACFGU18 Fig. 4, footnote 12)

mife/fullysec_lwe

reviewed

adapted

ACFGU18 Fig. 4 + ALS16 (LWE, Fig. 8)

slots share A; parameters derived for dimension n * eta and bound 3X (ACFGU18 footnote 14), m = 4 n log2 q

mife/fh_ipe_pairing

reviewed

adapted

ACFGU18 Fig. 12 (k = 1)

one r per key shared by all slots; per-slot FH-IPFE as in mcfe/fh_ipe (LV16 padding of App. B); asymmetric pairing (default BLS12-381); keys carry a random id, never a digest of y

mife/fh_multi_ipe_pairing

reviewed

adapted

DOT18 Sec. 3.1 (bounded, k-LIN, k = sec_level)

n >= 2 enforced (zero sharing r_i); no Remark B.1 SKE layer, so hiding assumes every slot contributes a ciphertext; slot i’s client holds B^_i and can decode its own slot of a key; keys are group elements only, decryption does not need y

mife/ddh_threshold

reviewed

adapted

ACFGU18 Fig. 4 + ABDP15 + Shamir (TAPFed setting)

the functional key (d_i, z) is Shamir-shared mod q; any t servers interpolate in the exponent (threshold-ElGamal style), as mcfe/ddh_threshold

mife/lwe_threshold

experimental

ad-hoc

MIFE-LWE + replicated / Shamir sharing + smudging

MIFE layer as published; the threshold layer (statistical flooding, derandomised noise, enlarged q, clients’ HMAC tags on ciphertexts so that servers only decrypt ciphertexts made by the clients, replicated key sharing for t < s, where Shamir shares let partials reveal the decryption noise modulo small integers) is our own; the opt-in sharing=’replicated-seeded’ expands all key pieces but one from secret 256-bit seeds and hides the key shares only computationally (SHAKE-256 keyed by the seeds as a PRF); full replicated sharing stays the default for t < s; standard refuses it (at the reduction width of mife/lwe it needs the very end of the 128-bit table, 5888 to 6144, for a few small bounds and more for most others)

mcfe/ddh

reviewed

faithful

CDGPP18

mcfe/ddh_ristretto255

unreviewed

unknown

CDGPP18 in ristretto255

ristretto255 (RFC 9496, about 126-bit security: Pollard rho in a group of order 2^252) instead of a 3072-bit Z_p^* subgroup; labels hashed with RFC 9380 hash_to_ristretto255; group arithmetic in Rust (curve25519-dalek) with the native extension, else in pure Python and not constant-time; new code, not yet reviewed

mcfe/lwe

reviewed

adapted

ABMPSW20 Fig. 4, selective parameters

signed messages and weights (K = 2nXY + 1, centred decoding); t <- D_{Z,sigma_t} exactly (FACCT) by default, rounded selectable under toy

mcfe/paillier

reviewed

faithful

ABMPSW20 Fig. 3

D_{Z,sigma} realised as a high-precision rounded Gaussian

mcfe/damgard

reviewed

faithful

ABMPSW20 Fig. 2 (D_k-MDDH)

k = mddh_k (default 1, where it equals CDGPP18)

mcfe/ring_lwe

reviewed

adapted

BMMS21 Sec. 6 MCFE-compiler (identity-based)

functional keys are bound to a label; ndarray values are packed into one ciphertext per chunk, each chunk with its own label pad; parameters as mife/ring_lwe (widths for bound 3X)

mcfe/fullysec_lwe

reviewed

adapted

ABMPSW20 Fig. 4, adaptive parameters

signed messages and weights (K = 2nXY + 1, centred decoding); t <- D_{Z,sigma_t} exactly (FACCT) by default, rounded selectable under toy; m0 grows with n log2(2X + 1)

mcfe/fh_multi_ipe_pairing

reviewed

adapted

AGT21b Sec. 6.2 (as recapped in NPS24 Sec. 2)

FH-IPFE = ACFGU18 Fig. 12 (n = 1, SXDH) with LV16 padding; asymmetric pairing (default BLS12-381); keys carry a random id, never a digest of y

mcfe/fh_multi_ipe_pairing_threshold

experimental

ad-hoc

AGT21b FH-MCFE + Shamir

FH-MCFE layer as published; the key exponents Shamir-shared in the exponent ([e^(k)]_2, [z^(k)]_T), partial decryptions in G_T and labelled, client-tagged ciphertexts are our own; FH-IPFE = ACFGU18 Fig. 12 (n = 1, SXDH) with LV16 padding; asymmetric pairing (default BLS12-381); keys carry a random id, never a digest of y

mcfe/fh_multi_ipe_pairing_decentralized

reviewed

adapted

NPS24 Fig. 1 (selective FH-DMCFE)

explicit tag-f, each client’s key depends on (tag-f, y_i) only and is derived deterministically; one key per client and tag-f (enforced per client object); FH-IPFE = ACFGU18 Fig. 12 (n = 1, SXDH) with LV16 padding; asymmetric pairing (default BLS12-381); keys carry a random id, never a digest of y

mcfe/ddh_threshold

reviewed

adapted

CDGPP18 + Shamir (TAPFed setting)

the aggregated key is Shamir-shared; any t servers interpolate in the exponent (threshold-ElGamal style)

mcfe/ddh_decentralized

experimental

ad-hoc

ABMPSW20 Fig. 14 (MCFE1 = CDGPP18) + carry mask

Fig. 14 is stated for MCFEs without a discrete log in decryption; contributions are reduced mod q and masked by q rho_i (rho_i from a per-client key and H(y)) so that the carry of their sum stays hidden – our own argument; MCFE2 = ABMPSW20 Fig. 3 (DCR); the zero-sum MCFE2 keys (pairwise masks) are sampled in-process

mcfe/lwe_threshold

experimental

ad-hoc

ABMPSW20 Fig. 4 + replicated / Shamir sharing + flooding

MCFE layer as published; the threshold layer (replicated key sharing for t < s, Shamir for t = s) and its LWE-based flooding argument are our own (see the module docstring); the opt-in sharing=’replicated-seeded’ expands all key pieces but one from secret 256-bit seeds and hides the key shares only computationally (SHAKE-256 keyed by the seeds as a PRF); full replicated sharing stays the default for t < s

mcfe/lwe_decentralized

reviewed

adapted

ABMPSW20 Fig. 14 (MCFE1 = Fig. 4)

MCFE2 = ABMPSW20 Fig. 3 (DCR); the zero-sum MCFE2 keys (pairwise masks) are sampled in-process

mcfe/ring_lwe_threshold

experimental

ad-hoc

BMMS21 identity-based MCFE + replicated / Shamir sharing + smudging

MCFE layer as published; the threshold layer (statistical flooding, derandomised noise, enlarged q, clients’ HMAC tags on ciphertexts, replicated key sharing for t < s, where Shamir shares let partials reveal the decryption noise modulo small integers) is our own; the opt-in sharing=’replicated-seeded’ expands all key pieces but one from secret 256-bit seeds and hides the key shares only computationally (SHAKE-256 keyed by the seeds as a PRF); full replicated sharing stays the default for t < s; by default binary ciphertexts (format 2: ct0 cut to the batch length and every value strictly below q, tags over the parameters’ digest and the bytes, binary partials and key shares), an RNS modulus (q a product of at least three NTT primes, each above s) and split key shares (sk_y once per weights, the label terms per round); ct_format=’json’, ring_modulus=’prime’ and key_shares=’full’ select the formats of earlier versions

mcfe/ring_lwe_decentralized

reviewed

adapted

BMMS21 Sec. 6 identity-based DMCFE (ABKW19 + GDSum)

GDSum realised as pairwise PRF zero-sum masks (its full specification is only in the ePrint version); pairwise secrets sampled in-process

mcfe/paillier_decentralized

reviewed

adapted

ABMPSW20 Fig. 14 (MCFE1 = Fig. 3)

MCFE2 = ABMPSW20 Fig. 3 (DCR); the zero-sum MCFE2 keys (pairwise masks) are sampled in-process

quadratic/quad

reviewed

adapted

Gay20 Fig. 4 over Fig. 8 (CPA)

signed inputs (discrete log in [-n m B^3, n m B^3]); CPA variant (no QANIZK); asymmetric pairing (default BLS12-381)

quadratic/sgp

reviewed

adapted

DSGPP18 Fig. 2

public key pk = (g1^s, g2^t) as in the paper; signed inputs with one bound B for x, y and F (discrete log in [-n^2 B^3, n^2 B^3]); generic bilinear group model

quadratic/miqfe

reviewed

adapted

AGT22 Sec. 5 (MIFE for quadratic functions)

cross-user terms only (Def. 2.9); per-user encryption keys made of master ciphertexts; building blocks TAO20 FH-IPFE, Fig. 9 and Fig. 1 as in the paper; PRF = HMAC-SHA512; pos-security (every user contributes); k = mddh_k >= 2

Security profiles

The security_profile entry of a key generator’s config selects its parameters: "toy" (the default) is fast and not secure; "standard" aims at 128-bit classical security and refuses explicit values below it. Its exceptions: the pairing-based schemes run on BLS12-381, about 120-126-bit after the exTNFS attacks (with a FESecurityWarning), and sife/fullysec_lwe, mife/fullysec_lwe (ALS16), mife/lwe_threshold and mcfe/paillier_decentralized are refused. pyfe4ai.core.profiles below gives the parameters per family, including matrix_mode for the ABDP15 LWE schemes; Configuration options lists every option with its default under each profile, and the option combinations that are refused.

For the ABDP15 LWE schemes (sife/lwe, mife/lwe) the 128 bits are those of the LWE problem that their security proof reduces to. The lattice-estimator table is read at the error width sigma' = sigma / (1 + B sqrt(eta)) of that problem (B = 2 X, or 4 X per slot of mife/lwe), not at the wider sigma of the public key. That makes the dimension 17 to 67 percent larger for 8 <= X = Y <= 1000, and up to about twice as large at X = Y = 1.

The proof is the ABDP15 full version (ePrint 2015/017, Oct. 2015), Thm. 4.4 with Thms. 6.6 and 6.7 (Construction 6.5, Sec. 6.2), written out in F. Bourse’s PhD thesis (ENS 2017), Thm. 4.3.5 with Thms. 8.2.2, 8.2.3, 8.3.2. It is not Theorem 2 of the PKC 2015 paper, whose simulation does not cover noisy LWE keys. The width sigma' is the condition derived in the proof of Thm. 6.6 (= Bourse, Thm. 8.2.2); the full version’s Sec. 6.2 parameter list states the stronger, sufficient (l B + 1) sigma'.

Hypotheses of the papers that the library meets differently:

  • The literal p > l B^2 of Thm. 4.4 / Sec. 6.2 is not needed for the Regev instance. The change of basis acts on secret keys mod the LWE prime q. The library decodes the integer inner product (p > 2 eta X Y + 1), so admissible keys are orthogonal to x1 - x0 over Z.

  • The 4 X per slot of mife/lwe is the library’s ACFGU18 composition, covered by neither text.

  • The thesis’s LWE hardness bound (an absolute width above 2 sqrt(n); not stated in the full version) is met under standard: sigma' >= 2^16, the table’s smallest width.

mife/lwe_threshold is refused, like the ALS16 schemes. Once its modulus is enlarged for flooding (stat_param = 128), it needs a dimension at the very end of the table at that width (5888 to 6144, with a public matrix 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).

The opt-in KL-divergence accounting of the flooding noise (pyfe4ai.schemes.threshold.kl) would bring it to about 4864 for two clients, s = t = 3 and 2^40 flooded values. It applies only with flooding_accounting = "kl", t = s and an explicit max_partial_decryptions budget; the public matrix would still have 4.1 * 10^9 entries ("stream" mode), and the guarantee is weaker and holds only for the first max_partial_decryptions partial decryptions of every server: about stat_param - 6.85 bits in the bit-security sense of Micciancio and Walter, a total statistical distance of about 2^-65 at stat_param = 128. The refusal under standard holds with either accounting; "kl" applies to mife/lwe_threshold under toy.

The arguments of the security reviews that are part of the documentation:

Threshold trust model

The decryption servers of the threshold schemes (tMIFE, tMCFE) are honest-but-curious: fewer than t of them learn nothing beyond the result, but a partial decryption carries no proof of correctness, so one deviating server changes the result unnoticed. combine_decrypt accepts partial decryptions for several enrolled sets and raises if they disagree (see combine_consistently()); it also raises (FESchemeError, for one value and for an ndarray element alike) when the partial decryptions of one set were computed for different ciphertexts. In tMIFE-LWE, tMCFE-Ring-LWE and tMCFE-FH-Multi-IPE, servers only decrypt ciphertexts carrying their clients’ MAC tags. With the binary ciphertexts of tMCFE-Ring-LWE (ct_format="binary", its default; "json" selects the format of earlier versions) the tags also cover the digest of the public parameters, and the encoding is unique (values strictly below q), so that the flooding noise, seeded with the ciphertexts’ bytes, cannot be redrawn by re-encoding a ciphertext. Its key shares are split by default: a server keeps its share of the label-independent part of the key once per weights and receives the shares of the round’s label terms per round; a linear sharing reused with fresh sharings of the label terms is a sharing of each round’s key, the shares of t - 1 servers stay independent of the keys, and the noise is seeded as for a whole key share (key_shares="full" issues whole key shares). A server refuses a label part without the weight part of the same weights and share set, and key shares and ciphertexts of another format or other public parameters; a combiner refuses partial decryptions of another format, and partial decryptions of different sharings of one key: every sharing the key generator issues has a random sharing_id that the partials carry, since partials of two sharings (a label shared again after it left the key generator’s cache, LABEL_PARTS_KEPT) would decode to a wrong value inside the decoding bound rather than fail. A second sharing reveals nothing: one server’s answers under the two sharings differ by its share of a sharing of zero, which t - 1 servers compute from their own parts. Store and transmit the sharing_id with every key share and partial decryption: partials without one (as from key shares of earlier versions) combine only with each other, so a storage or transport layer that drops the id on every side would let partials of two sharings combine to a wrong value unnoticed. Partial decryptions carry no digest of the public parameters, so a combiner given those of another deployment of the same format is not warned (it holds no secret; under the honest-but-curious model above the servers send the partials of its deployment).

ML adapter

FESchemeWrapper runs the key generator itself, so it holds the master secret key and plays clients, key authority and decryptor in one trust domain: use it for experiments, not as a deployment (see pyfe4ai.ml). For mcfe/ring_lwe_threshold it also runs the decryption servers, so the threshold adds no protection there; the separate steps (partial_decrypt_gradients(), combine_partial_decryptions()) show the messages of a deployment, whose servers run in their own trust domains.

Gaussian sampling

The lattice and DCR proofs sample secrets and noise from the discrete Gaussian D_{Z,sigma}. There are two samplers.

"facct" (discrete_gaussian_facct()) samples D_{Z,sigma} exactly, to about 2^-185 per sample, for every width (natively below 2^448). It is the default of these schemes, which record their sampler as gaussian_sampler in their public parameters: the Ring-LWE MIFE / MCFE family (sife, mife, mcfe ring_lwe, the decentralized and threshold MCFE), the dense-LWE MCFE keys t (mcfe/lwe, fullysec_lwe, lwe_threshold, lwe_decentralized), sife/fullysec_ring_lwe and, under standard, the noise E of the ABDP15 public keys (sife/lwe and mife/lwe; mife/lwe_threshold, whose MIFE layer would do the same, is refused under standard). For them, in this order: the gaussian_sampler of the key generator’s or crypto object’s config; else the one the public parameters record; else, under the toy profile only and not for sife/fullysec_ring_lwe, the one a gaussian_sampler() block selects; else "facct". The process-wide choice (set_gaussian_sampler, PYFE4AI_GAUSSIAN_SAMPLER) does not apply to them. The standard profile refuses "rounded" for them, and a crypto object whose config overrides a recorded "facct" with "rounded" warns (FESecurityWarning; refused under standard).

ABDP15 under toy takes the gaussian_sampler of its key generator’s config, else the library-wide choice below, and records it as well.

"rounded" rounds a continuous Gaussian. It is the library-wide default, and so the sampler of the other lattice and DCR schemes unless chosen otherwise: ALS16 (sife / mife fullysec_lwe), the DCR schemes and, under toy, ABDP15 SIFE / MIFE LWE (and the keys of tMIFE-LWE); the schemes above can select it under toy (gaussian_sampler = "rounded"). The ideal rounded Gaussian (exact uniforms and arithmetic) is a mixture of centred discrete Gaussians, about 0.02 / sigma^2 from D_{Z,sigma} per sample: 2^-9 at sigma = 3.2, about 2^-33 per sample at the Ring-LWE sigma1 – about 2^-18 for one MIFE key of eta ring_n samples. Its implementations add their own terms (crypto review, October 2026):

  • the double-precision path (discrete_gaussian_matrix() and the native kernels, widths up to 2^40): the two outputs of a Box-Muller pair share one radius (rings that show in the tail), exact half-integer ties round to even (about 2^-53.8 sigma per sample), and no value exceeds about 8.57 sigma (the largest radius 53-bit uniforms give). One ciphertext’s noise is about 2^-12..2^-4 and one key about 2^-25..2^-15 from i.i.d. D_{Z,sigma}, against budgets of 2^-128: use it for toy parameters and benchmarks, not where a proof needs D_{Z,sigma};

  • the MPFR path (discrete_gaussian_bigint(), wider widths: the DCR keys and masks, all threshold flooding) differs from the ideal rounded Gaussian only by its arithmetic, at bits(sigma) + 320 bits (BIGINT_GUARD_BITS: bits(sigma) + stat_param + ceil(log2 N) + 8 for every accepted stat_param, at most 256, and up to 2^56 samples per key). At the earlier bits(sigma) + 128 it was 2 to 19 bits short of the budgets. The threshold schemes’ Gaussian flooding always uses it (FLOODING_SAMPLER, recorded in their decryption keys), whatever the sampler selected elsewhere; the default uniform flooding of mcfe/ring_lwe_threshold uses no Gaussian sampler.

The library-wide choice – which applies to the other schemes and to ABDP15 under toy only – is made per call (sampler=), per context or per process:

from pyfe4ai.primitives.sampling import gaussian_sampler, set_gaussian_sampler

with gaussian_sampler("facct"):     # this context
    ...
set_gaussian_sampler("facct")       # the whole process

or PYFE4AI_GAUSSIAN_SAMPLER=facct in the environment. "facct" costs 0.5 to 3 microseconds per sample natively (several in CPython). Its algorithm needs no secret-dependent branches, but CPython and big-integer arithmetic are not constant-time, so neither sampler resists timing side channels. For a given bit source its output is the same on every platform and on both backends, so it comes with known-answer tests. Parties that derive noise from a shared seed (the Gaussian flooding noise of the threshold LWE schemes, and of mcfe/ring_lwe_threshold with flooding="gaussian") must use the same sampler; the default flooding="uniform" of mcfe/ring_lwe_threshold draws its noise without a Gaussian sampler (uniform on [-2^tau, 2^tau) from the seeded SHAKE-256 stream, the same statistical distance per coefficient, see the module).

The key generator of a scheme that records its sampler publishes it in pp as the encryptors’ default; public parameters without it (earlier versions) mean "facct", and decryption does not depend on it. With the rounded sampler, sife/fullysec_ring_lwe’s keys are 0.02 / sigma1^2 per sample, over m n eta key samples, from D_{Z,sigma1} for the ideal rounded Gaussian – in the standard configurations about 2^-47 (elementwise eta = 1, X = 100), 2^-44.5 (packed eta = 1) and 2^-40.5 (eta = 4, X = Y = 4) – and about 2^-12 at eta = 1 with the double-precision path’s artifacts, terms its adaptive proof does not budget.

Status API

SchemeStatus, which this module re-exports, is documented with the scheme registry (pyfe4ai.core.registry).

Review status of every registered scheme.

Each registered (family, variant) has a SchemeStatus, declared with the scheme in pyfe4ai.core.registry (which also defines the status and fidelity values). This module derives the status table from the registry and provides what uses it: the queries, the FESecurityWarning emitted when a known-insecure key generator or crypto object is instantiated, and the README Scheme Status table (markdown_table()). All schemes additionally share the caveats of the README (research prototype, toy default parameters, no side-channel hardening).

pyfe4ai.core.status.SCHEME_STATUS: dict[tuple[str, str], SchemeStatus]

Review status of every registered (family, variant), derived from pyfe4ai.core.registry.SCHEMES (in its order): the source of the README Scheme Status table and of the status table of the docs.

pyfe4ai.core.status.get_scheme_status(family, variant=None)[source]

Return the review status of a registered scheme: (family, variant), or its id "family/variant" alone.

Raises:

KeyError – If the scheme is not registered.

Parameters:
  • family (str)

  • variant (str | None, default: None)

Return type:

SchemeStatus

pyfe4ai.core.status.status_for_module(module)[source]

Return ((family, variant), status) for a scheme module, if registered.

Parameters:

module (str)

Return type:

tuple[tuple[str, str], SchemeStatus] | None

pyfe4ai.core.status.warn_if_known_insecure(module, stacklevel=None)[source]

Emit FESecurityWarning if module implements a known-insecure scheme, attributed to the application’s call (the first frame outside pyfe4ai, warn_at_caller()), or stacklevel frames up if one is given.

Parameters:
  • module (str)

  • stacklevel (int | None, default: None)

Return type:

None

pyfe4ai.core.status.markdown_table()[source]

Render SCHEME_STATUS as the README Scheme Status table.

Return type:

str

Profiles API

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 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 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 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 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 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 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 (pyfe4ai.core.config) and the refusals (each scheme’s refused in pyfe4ai.core.registry) – are applied by pyfe4ai.core.config.resolve() when a key generator is built; this module names the profiles and the kind of every scheme.

pyfe4ai.core.profiles.SCHEME_KIND

Kind of every registered (family, variant) (the parameters the profiles size: pyfe4ai.core.registry.KINDS), derived from the registry.

pyfe4ai.core.profiles.STANDARD_DCR_PRIME_BITS = 1536

Bits of each prime of a DCR modulus under standard (N of 3072 bits).

pyfe4ai.core.profiles.STANDARD_STAT_PARAM = 128

Statistical parameter under standard (minimum and default).

pyfe4ai.core.profiles.MAX_STAT_PARAM = 256

Largest statistical parameter a key generator accepts, in both profiles; the MPFR Gaussians’ guard bits (BIGINT_GUARD_BITS) cover stat_param + ceil(log2 N) + 8 up to this value.

pyfe4ai.core.profiles.scheme_of(cls)[source]

Return the registered (family, variant) that cls implements or extends.

Parameters:

cls (type)

Return type:

tuple | None