MIFE — Multi-Input Functional Encryption¶
Multi-input functional encryption schemes: the key generator and crypto class of every
scheme of the family (the registry: pyfe4ai.core.registry).
The key generator and crypto class of every scheme below are importable
from pyfe4ai.schemes.mife under their own names (one name per class,
no aliases; the naming rule is in pyfe4ai.core.registry).
DDH¶
Multi Input Functional Encryption (DDH) | From “Multi-Input Functional Encryption for Inner Products: | Function-Hiding Realizations and Constructions without Pairings” | Published in: CRYPTO 2018 | By: Michel Abdalla, Dario Catalano, Dario Fiore, Romain Gay, and Bogdan Ursu | URL: https://eprint.iacr.org/2017/972.pdf – Fig. 4, instantiated with the | DDH IPFE of Abdalla, Bourse, De Caro, Pointcheval (PKC 2015, Construction 1) | in every slot.
type: private-key multi-input encryption
setting: Integer based (order-q subgroup of Z_p^*, p = 2q + 1)
Slot i has an independent IPFE key s_i in Z_q^eta (h_i = g^{s_i})
and a one-time pad u_i in Z_q^eta; client i holds (h_i, u_i).
Enc:
ct_i = (g^r, g^{x_i + u_i} h_i^r)(every element reduced mod p).KeyGen:
d_i = <s_i, y_i> mod qper slot andz = sum_i <u_i, y_i> mod q.Dec:
prod_i (prod_j c_{i,j}^{y_{i,j}} / t_i^{d_i}) / g^z = g^{<x, y>}, then a discrete log bounded bydlog_bound.
Each slot’s partial result is g^{<x_i + u_i, y_i>}: the pad hides
<x_i, y_i>, only the sum over all slots is revealed. As in every MIFE,
there are no labels – a key holder may combine any ciphertexts of each
slot, so clients must not reuse a key epoch across independent inputs
(use MCFE for repeated aggregation). Deviation from ACFGU18, which uses the
adaptively secure ALS16 IPFE: ABDP15 is selectively secure. Versions <= 0.2.0
did not reduce ciphertexts modulo p (their factors revealed x + u)
nor keys modulo q (a key with a large weight revealed the pads).
- class pyfe4ai.schemes.mife.ddh.MIFEDDHKeyGenerator(config, **kwargs)[source]¶
Bases:
DDHKeyGeneratorBaseKey generator for DDH-based multi-input inner-product FE.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the MIFE key generator.
- Parameters:
config (
dict) – Scheme configuration. Recognised keys:sec_param,eta(int or per-client dict),n(number of clients),s,lst_nid(client identifiers).- Return type:
None
- get_public_parameters()[source]¶
Return serialisable public parameters (g, p, q, n, eta, sec_param).
- Return type:
- class pyfe4ai.schemes.mife.ddh.MIFEDDH(config)[source]¶
Bases:
OfflineRandomMixin,IPFEAbsCryptoCrypto operations for DDH-based multi-input inner-product FE.
- Parameters:
config (
dict)
- __init__(config)[source]¶
Initialise the MIFE crypto system.
- Parameters:
config (
dict) – Must containkeyswithppand, for clients,sk;dlog_bound(default10^(precision + 2)) bounds the inner products decryption recovers.- Return type:
None
- encrypt(lst_pt)[source]¶
Encrypt this client’s
etaintegers:(g^r, g^{x_j + u_j} h_j^r).- Raises:
FEKeyError – If public parameters or private keys are missing.
FEValidationError – If the plaintext is not
etaintegers.
- Parameters:
lst_pt (
list)- Return type:
- decrypt(dct_ct, dk, fusion_weight)[source]¶
Recover
sum_i <x_i, y_i>from one ciphertext per slot.- Returns:
The inner product, or
Noneif it is out of the range the discrete-log table covers (at least|<x, y>| <= dlog_bound).- Raises:
FEKeyError – If the decryption key is missing.
FESchemeError – If ciphertexts, key and weights do not cover the same slots or the weights are not the key’s.
FEValidationError – If a ciphertext is malformed.
- Parameters:
- encrypt_lst_ndarray(lst_ndarray, **kwargs)[source]¶
Encrypt ndarrays element-wise (
eta = 1).Every element is a separate ciphertext under the same slot key, so a holder of one functional key can combine elements of different positions (MIFE has no labels): use these helpers for one input per key epoch, and MCFE for federated aggregation.
DDH over ristretto255¶
Multi Input Functional Encryption (DDH) over ristretto255 | From “Multi-Input Functional Encryption for Inner Products: | Function-Hiding Realizations and Constructions without Pairings” | Published in: CRYPTO 2018 | By: Michel Abdalla, Dario Catalano, Dario Fiore, Romain Gay, and Bogdan Ursu | URL: https://eprint.iacr.org/2017/972.pdf – Fig. 4, instantiated with the | DDH IPFE of Abdalla, Bourse, De Caro, Pointcheval (PKC 2015, Construction 1) | in every slot.
type: private-key multi-input encryption
setting: the prime-order group ristretto255 (RFC 9496), written additively
As pyfe4ai.schemes.mife.ddh in ristretto255. Slot i has an IPFE
key s_i in Z_L^eta (H_i = s_i G) and a one-time pad u_i in Z_L^eta;
client i holds (H_i, u_i).
Enc:
ct_i = (r G, (x_i + u_i) G + r H_i).KeyGen:
d_i = <s_i, y_i> mod Lper slot andz = sum_i <u_i, y_i> mod L.Dec:
sum_i (sum_j y_{i,j} c_{i,j} - d_i t_i) - z G = <x, y> G, then a discrete log bounded bydlog_bound.
As in every MIFE there are no labels: clients must not reuse a key epoch across independent inputs (use MCFE for repeated aggregation).
- class pyfe4ai.schemes.mife.ddh_ristretto255.MIFEDDHRistretto255KeyGenerator(config, **kwargs)[source]¶
Bases:
ECDDHKeyGeneratorBaseKey generator for DDH-based multi-input inner-product FE over ristretto255.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Check the configuration.
- Parameters:
config (
dict) –n(number of clients),lst_nid,eta(int or per-client dict),group(optional,"ristretto255").- Return type:
None
- get_public_parameters()[source]¶
Return serialisable public parameters.
- Return type:
- Returns:
A dict of public parameters, or
Noneon error.
- class pyfe4ai.schemes.mife.ddh_ristretto255.MIFEDDHRistretto255(config, **kwargs)[source]¶
Bases:
ECDDHCryptoBaseCrypto operations for DDH-based multi-input inner-product FE over ristretto255.
- Parameters:
config (
dict)
- decrypt(dct_ct, dk, fusion_weight)[source]¶
sum_i <x_i, y_i>, orNoneif beyonddlog_bound.- Raises:
FEKeyError – If the decryption key is missing.
FESchemeError – If ciphertexts, key and weights do not cover the same slots or the weights are not the key’s.
FEValidationError – If a ciphertext is malformed.
- Parameters:
- encrypt_lst_ndarray(lst_ndarray, **kwargs)[source]¶
Encrypt ndarrays element-wise (
eta = 1); one input per key epoch.
- decrypt_lst_ndarray_ct(dict_ndarray_ct, dk, fusion_weight)[source]¶
Decrypt aggregated ndarray ciphertexts.
- Parameters:
- Return type:
- Returns:
One float array of decrypted results per layer.
- Raises:
FESchemeError – If an element does not decrypt (see
decoded_layer()).
DDH Threshold¶
Threshold multi-input inner-product FE (DDH), in the setting of | R. Xu et al., “TAPFed: Threshold Secure Aggregation for Privacy-Preserving | Federated Learning”, IEEE TDSC, doi: 10.1109/TDSC.2024.3350206.
type: private-key multi-input encryption with threshold decryption
setting: Integer based (order-q subgroup of Z_p^*, p = 2q + 1)
The MIFE of pyfe4ai.schemes.mife.ddh (ACFGU18 Fig. 4 with the ABDP15
DDH IPFE per slot). Its functional key – the per-slot d_i and the pad
term z – is Shamir-shared modulo q among s decryption servers;
any t of them decrypt by interpolating in the exponent, as the
threshold layer of pyfe4ai.schemes.mcfe.ddh_threshold:
server
kwith Lagrange coefficientlambda_kpublishesc_y = prod_{i,j} c_{i,j}^{y_{i,j}}(public) andw_k = prod_i t_i^{lambda_k d_i^(k)} g^{lambda_k z^(k)};the combiner computes
c_y / prod_k w_k = g^{<x, y>}and a bounded discrete log.
Fewer than t servers learn nothing about the key, and a set of partial
decryptions reveals no more than the MIFE key holder learns (the sum).
Versions <= 0.2.0 used an own IPFE replicated over eta rows and did not
bind key shares to their weights.
- class pyfe4ai.schemes.mife.ddh_threshold.ThresholdMIFEDDHKeyGenerator(config, **kwargs)[source]¶
Bases:
MIFEDDHKeyGeneratorMIFE-DDH setup plus Shamir-shared functional keys.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the key generator.
- Parameters:
config (
dict) – As forMIFEDDHKeyGenerator, plustandlst_sid(sdecryption servers).- Return type:
None
- class pyfe4ai.schemes.mife.ddh_threshold.ThresholdMIFEDDH(config)[source]¶
Bases:
MIFEDDHEncryption (with quantisation) and threshold decryption.
- Parameters:
config (
dict)
Compute this server’s partial decryption for an enrolled set.
- Parameters:
- Return type:
- Returns:
Dict with
c_y,w,ct_digest,sidandlst_sid_enrolled.
- combine_decrypt(partials)[source]¶
Combine partial decryptions from at least
tservers.- Parameters:
partials –
{sid: share_decrypt(...)}for one enrolled set, or a list of such mappings for several enrolled sets, whose results must then agree (seecombine_consistently(): servers are assumed honest-but-curious, and only this redundancy detects one that returns a wrong partial).- Raises:
FESchemeError – If a group is incomplete or invalid, its partial decryptions disagree on the ciphertexts they decrypted, or the groups disagree.
- Return type:
- encrypt_lst_ndarray(lst_ndarray, **kwargs)[source]¶
Encrypt ndarrays element-wise (
eta = 1); seeMIFEDDH.encrypt_lst_ndarray.
- compute_lst_ndarray_ct(dict_ndarray_ct, credentials, dk, lst_sid_enrolled)[source]¶
Partially decrypt ndarray ciphertexts element-wise.
- decrypt_lst_ndarray_ct(dict_ndarray_ct, **kwargs)[source]¶
Combine element-wise partial decryptions from at least
tservers.- Raises:
FESchemeError – If the servers’ outputs differ in structure, an element does not decrypt (out of range), or the partial decryptions of an element disagree on the ciphertexts (the error and message of
combine_decrypt).- Parameters:
dict_ndarray_ct (
dict)- Return type:
Damgård DDH¶
DDH (Damgard-style) multi-input inner-product FE.
type: private-key multi-input encryption (MIFE, DDH)
setting: Integer based (order-q subgroup of Z_p^*, p = 2q + 1)
Construction (slot i is client i; [a] = g^a, h = g^a):
Setup:
W_i = (s_i, t_i) <- (Z_q^eta)^2,pk_{i,j} = g^{s_{i,j}} h^{t_{i,j}}, one-time padu_i <- Z_q^eta; clientikeeps(pk_i, u_i).Encrypt:
w = x_i + u_i mod q;C = g^r,D = h^r,E_j = g^{w_j} pk_{i,j}^r.KeyGen: per slot
(<s_i, y_i>, <t_i, y_i>)andz = sum_i <u_i, y_i> mod q.Decrypt: Dec1 per slot
[<w_i, y_i>] = prod_j E_j^{y_j} / (C^{<s,y>} D^{<t,y>});[<x, y>] = prod_i [<w_i, y_i>] / [z], then a bounded dlog (Dec2).
The pad makes each slot’s partial result [<x_i + u_i, y_i>] uniform, so
the key holder learns only <x, y>; versions <= 0.2.0 omitted it and
revealed every <x_i, y_i>. Deviation: all slots share the group and
h (the matrix A of Fig. 7) while W_i and u_i are independent
per slot, instead of running Setup independently per slot. A decryption
needs ciphertexts of every slot named in the key.
- class pyfe4ai.schemes.mife.damgard_ddh.MIFEDamgardKeyGenerator(config, **kwargs)[source]¶
Bases:
DamgardDDHKeyGeneratorBaseKey generator for DDH multi-input inner-product FE (ACFGU18 Fig. 4 + ALS16).
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the key generator.
- Parameters:
config (
dict) – Scheme configuration. Recognised keys:n(number of slots),lst_nid,eta(slot dimension),modulus_length(bits ofp),bound(on|x|and|y|),sec_param.- Return type:
None
- get_private_keys(nid, **kwargs)[source]¶
Return slot nid’s encryption key
{pk, u}(Noneif unknown).The key includes the one-time pad and must stay with the client.
- get_decryption_keys(sid, **kwargs)[source]¶
Derive
({(<s_i, y_i>, <t_i, y_i>)}_i, z = sum <u_i, y_i> mod q).- Parameters:
sid (
str) – Session identifier (unused).**kwargs – Must include
credentialswithfusion_weight(dict mapping every client ID to an integer weight list).
- Raises:
FEKeyError – If credentials are missing.
FEValidationError – If a client is missing or a weight is malformed.
- Return type:
- class pyfe4ai.schemes.mife.damgard_ddh.MIFEDamgard(config, **kwargs)[source]¶
Bases:
IPFEAbsCryptoCrypto operations for DDH multi-input inner-product FE (ACFGU18 Fig. 4 + ALS16).
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise with
keys.ppand, for clients,keys.sk.- Parameters:
config (
dict)- Return type:
None
- decrypt(dct_ct, dk, fusion_weight)[source]¶
Recover
sum_i <x_i, y_i>from one ciphertext per slot.- Returns:
The inner product, or
Noneif it is out of the dlog bound.- Raises:
FEKeyError – If the key is missing.
FESchemeError – If ciphertexts do not cover exactly the key’s slots.
- Parameters:
LWE¶
LWE multi-input inner-product FE (selective security).
type: private-key multi-input encryption (MIFE, LWE)
setting: Integer based (Z_q, plaintext modulus p, Delta = floor(q / p))
Construction (slot i is client i, L = q):
Setup: public
A; per slotS_i(lwe_n x etauniform),pk_i = A S_i + E_iand a one-time padu_i <- Z_q^eta; clientikeeps(pk_i, u_i).Encrypt:
w = x_i + u_i mod q;ct0 = A^T r,ct1 = pk_i^T r + Delta * w mod q(rbinary).KeyGen: per slot
sk_i = S_i y_i mod q, andz = sum_i <u_i, y_i> mod q.Decrypt: Dec1 per slot
<y_i, ct1> - <ct0, sk_i> = Delta <w_i, y_i> + noise_i;mu = round(centre(sum_i Dec1_i - Delta z) / Delta)(Dec2).
The pad hides each slot’s partial result; versions <= 0.2.0 had no pad and
encoded floor(x q / p), so the key holder decoded every <x_i, y_i>.
Deviations: ACFGU18 instantiates Fig. 4 with the adaptively secure ALS16 LWE
IPFE (that is pyfe4ai.schemes.mife.fullysec_lwe); this module uses
the selectively secure ABDP15 IPFE, whose two-step decryption is the same.
All slots share A (secrets and pads are independent per slot). A
decryption needs ciphertexts of every slot named in the key.
Security of a slot’s IPFE rests on the LWE proof of the ABDP15 full version,
Thm. 4.4 with Thms. 6.6 and 6.7 (see pyfe4ai.schemes.sife.lwe). Here
it reduces to LWE with errors of width sigma / (1 + 4 X sqrt(eta)), since
the per-slot challenge vectors of the ACFGU18 reduction differ by up to
4 X per coordinate; this composition is the library’s own, and neither
paper covers it. The pad makes
the slot plaintexts w uniform mod q; the proof only needs their
difference to be short. standard reads the 128-bit table at that width.
A is published as a seed and expanded with SHAKE-128; encryption runs
in numpy over an A kept in memory or expanded per batch
(matrix_mode), see pyfe4ai.schemes.common.abdp15_lwe. Versions <= 0.2.0
published A entry by entry.
- class pyfe4ai.schemes.mife.lwe.MIFELWEKeyGenerator(config, **kwargs)[source]¶
Bases:
IPFEAbsKeyGenerator,ABDP15KeyGeneratorMixinKey generator for LWE-based multi-input inner-product FE.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the MIFE LWE key generator.
- Parameters:
config (
dict) – Scheme configuration. Recognised keys:sec_param,eta,n(number of clients),lwe_n(default 32;"auto"for the smallest dimension meeting the 128-bit table, thestandardprofile’s choice),bound_x,bound_yandmatrix_mode("memory"or"stream", seepyfe4ai.schemes.common.abdp15_lwe).- Return type:
None
- get_public_parameters()[source]¶
Return serialisable public parameters.
- Return type:
- Returns:
Dict containing
A_seed,m,lwe_n,p,q,eta,n,bound_x,bound_y,sec_param.
- get_decryption_keys(sid, **kwargs)[source]¶
Derive a functional decryption key for the given fusion weights.
- Parameters:
sid (
str) – Session identifier.**kwargs – Must include
credentialswithfusion_weight(dict mapping client IDs to weight lists).
- Return type:
- Returns:
Dict with
sk_y(per-client projected secret keys).- Raises:
FEKeyError – If credentials are missing.
FEValidationError – If fusion weights are invalid or exceed
bound_y.
- class pyfe4ai.schemes.mife.lwe.MIFELWE(config, **kwargs)[source]¶
Bases:
IPFEAbsCrypto,ABDP15CryptoMixinCrypto operations for LWE-based multi-input inner-product FE.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the MIFE LWE crypto system.
- Parameters:
config (
dict) – Must containkeyswithpp(public parameters) and optionallysk(encryption keys); optionalmatrix_mode("memory"or"stream").- Return type:
None
- encrypt(lst_pt)[source]¶
Encrypt a plaintext vector for one client.
- Parameters:
lst_pt (
list) – Integer plaintext vector of lengtheta.- Return type:
- Returns:
Dict with
ct0andct1(serialised LWE ciphertext vectors).- Raises:
FEKeyError – If public parameters or encryption keys are missing.
FEValidationError – If plaintext length or values are invalid.
- decrypt(dct_ct, dk, fusion_weight)[source]¶
Decrypt aggregated ciphertexts to recover the multi-input inner product.
- Parameters:
- Returns:
The inner product as an integer.
- Raises:
FEKeyError – If decryption key is missing.
FEValidationError – If fusion weight length or values are invalid.
- encrypt_lst_ndarray(lst_ndarray, **kwargs)[source]¶
Encrypt a list of ndarrays element-wise (requires
eta=1).
- compute_lst_ndarray_ct(dict_ndarray_ct, **kwargs)[source]¶
Aggregate ndarray ciphertexts (delegates to
decrypt_lst_ndarray_ct()).
Fully-Secure LWE¶
LWE multi-input inner-product FE (adaptive security).
type: private-key multi-input encryption (MIFE, LWE)
setting: Integer based (Z_q, Delta = floor(q / K))
Construction (slot i is client i, L = q):
Setup*: parameters of ALS16 derived as for vectors of dimension
n etaand plaintext bound3X(Section 4.2, footnote 14 and Fig. 4); publicA; per slotZ_i <- D(ALS16 key distribution),U_i = Z_i Aand a one-time padu_i <- Z_q^eta; clientikeeps(U_i, u_i).Encrypt:
w = x_i + u_i mod q;c0 = A s + e0,c1 = U_i s + e1 + Delta * w mod q.KeyGen: per slot
Z_i^T y_i mod q, andz = sum_i <u_i, y_i> mod q.Decrypt: Dec1 per slot
<y_i, c1> - <Z_i^T y_i, c0> = Delta <w_i, y_i> + noise_i;mu = round(centre(sum_i Dec1_i - Delta z) / Delta)(Dec2).
The pad hides each slot’s partial result; versions <= 0.2.0 had no pad (the
key holder decoded every <x_i, y_i>) and derived single-slot parameters.
Deviation: all slots share A instead of running Setup independently per
slot (Z_i and u_i are independent). A decryption needs ciphertexts of
every slot named in the key.
- class pyfe4ai.schemes.mife.fullysec_lwe.MIFEFullySecLWEKeyGenerator(config, **kwargs)[source]¶
Bases:
IPFEAbsKeyGenerator,ParameterCacheMixinKey generator for fully secure LWE-based multi-input inner-product FE.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the MIFE fully secure LWE key generator.
- Parameters:
config (
dict) – Scheme configuration. Recognised keys:n(number of clients, required),lst_nid,eta,lwe_n(default 64),bound_xandbound_y. Thestandardprofile refuses this scheme.- Return type:
None
- get_public_parameters()[source]¶
Return serialisable public parameters.
- Return type:
- Returns:
A dict of public parameters, or
Noneon error.
- class pyfe4ai.schemes.mife.fullysec_lwe.MIFEFullySecLWE(config, **kwargs)[source]¶
Bases:
IPFEAbsCryptoCrypto operations for fully secure LWE-based multi-input inner-product FE.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the MIFE fully secure LWE crypto object.
- Parameters:
config (
dict) – Must containkeyswithpp(public parameters) and optionallysk(encryption keys).- Return type:
None
- encrypt_lst_ndarray(lst_ndarray, **kwargs)[source]¶
Encrypt a list of ndarrays element-wise (requires
eta = 1).- Parameters:
lst_ndarray (
list) – List of numpy arrays, scaled by10^precision.- Return type:
- Returns:
One object array of ciphertexts per input array.
- Raises:
NotImplementedError – If
eta != 1.
LWE Threshold¶
Threshold LWE multi-input inner-product FE: ACFGU18 MIFE-LWE with a Shamir-shared functional key and statistically flooded partial decryptions.
pyfe4ai.schemes.mife.lwe (ACFGU18 Fig. 4 over thes servers, any t decrypt.type: private-key multi-input encryption, threshold functional decryption
setting: Integer based (Z_q, q prime, Delta = floor(q / p))
Construction (notation of pyfe4ai.schemes.mife.lwe):
KeyGen: the MIFE key
({sk_i = S_i y_i}, z); every coordinate of everysk_iandzis Shamir-shared with thresholdtoverZ_qat points1..s.ShareDec: server
kof an enrolled setEpublishesw_k = lambda_k (sum_i <ct0_i, sk_i^(k)> + Delta z^(k)) + e_k mod qandc_y = sum_i <y_i, ct1_i> mod qfor a consistency check.Combine:
c_y - sum_{k in E} w_k = Delta <x, y> + noise - sum e_k; decode.
Flooding: c_y - (sum_i <ct0_i, sk_i> + Delta z) = Delta <x, y> + nu where
the ciphertext noise nu = sum_i y_i^T E_i^T r_i is bounded by
B = n eta Y m 12 sigma. From the result and the ciphertexts one can
compute the exact key part only up to nu; the flooding noise
e_k <- D_{Z, 2^kappa B} makes the partial decryptions statistically
indistinguishable from ones simulated from the result (the standard
smudging argument for threshold LWE decryption). q is enlarged so that
s flooding terms still decode: Delta / 2 > B + s * 12 * 2^kappa B; at
a fixed lwe_n this lowers the noise-to-modulus ratio of the underlying
LWE instance, and m grows with log2 q so that A^T r stays close to
uniform (versions <= 0.2.0 kept the m of the smaller base modulus: about
half the length the leftover hash lemma needs). e_k is derived from a
per-server secret key and the request (see below), so repeating a request
cannot be used to average the noise away. The threshold layer is our own
design.
Ciphertext authentication: w_k is linear in ct0, so whoever chooses the
ciphertexts a server decrypts could submit ct0 = c e_j for chosen c and
read the functional key off the partials coordinate by coordinate (the
flooding hides a fixed noise term, not a chosen multiple of the key). Client
i and server k therefore share a MAC key; every ciphertext carries one
HMAC-SHA256 tag per server over its serialised (ct0, ct1), and a server
only decrypts ciphertexts whose tags for it verify. A corrupted client can
still put any ciphertext in its own slot, which reveals that slot’s key part
S_i y_i and nothing about the other slots; mixing authentic ciphertexts of
different rounds is the usual multi-input functionality. The noise is seeded
with the server’s key, lambda_k, the ct0 values and the key share’s
pp_binding (the public parameters it is bound to), so a request for
the same key part draws the same noise; a corrupted client varying its own
ct0 changes the key part and so draws fresh noise each time. The flooding
argument is statistical per answer (a statistical distance of about
2^-stat_param per coefficient, adding up over the answers); it does not
bound what many answers to varied ct0 reveal together about the other
slots’ part: averaging them would take about 2^(2 stat_param) partial
decryptions, but other ways of combining these noisy linear equations are
not ruled out.
Sharing (config entry sharing; default "replicated" for t < s and
"shamir" for t = s): with "shamir" and t < s – the sharing of
versions <= 0.3.0 – the argument above does not hold. With t - 1 servers
C corrupted, an honest partial is computable from C’s shares and the
result up to e_k - M nu,
M = lambda_k^E prod_{j in C} (k - j) / (0 - j), and M is a non-integer
rational for some (C, E, k) (6 of 18 for s = 3, t = 2): for
M = r / d, d times the difference is the small integer
d e_k - r nu, which gives nu mod d whatever sigma_f. Fewer
corrupted servers, or none, can suffice (the aggregator alone reads nu
modulo (s - 1)! for t = 1), and combining the partials of several
enrolled sets raises d (144 for s = 5, t = 3); where M is an
integer it reaches 4 (45 for s = 5, t = 3), and the statistical distance
of the flooding grows |M|-fold. "replicated" shares the key additively
over the (t - 1)-subsets of the servers (see
share_replicated()): each server of
E adds up the pieces assigned to it, M is 0 or 1 and the smudging
argument holds as stated, with q unchanged; a server holds
C(s - 1, t - 1) pieces instead of one share. With t = s both are sound
(M = 1). An explicit "shamir" with t < s draws an
FESecurityWarning under toy (the standard profile refuses the
scheme altogether, see below); the two
sharings have their own parameter records (construction
acfgu18-abdp15-replicated-smudging-seeded-a/v1 for "replicated"), so a
record cached by an earlier version for t < s is not reused by the
default. Servers refuse Shamir key shares with t < s unless created with
legacy_shamir = "warn" (never with public parameters that record the
standard profile); key shares of versions <= 0.3.0 are refused in any
case (another flooding derivation, see
check_flooding_version());
public parameters record threshold_construction and
security_profile, and servers refuse constructions they do not know.
The opt-in "replicated-seeded" (see
share_replicated_seeded()) deals the
same kind of pieces, all but one expanded from 32-byte seeds: a server stores
seeds and at most one full piece, and its partial decryptions are those of
the replicated key share of the expanded pieces; the key shares hide the key
computationally (SHAKE-256 keyed by the seeds as a PRF) rather than
perfectly. Its record is acfgu18-abdp15-replicated-seeded-smudging-seeded-a/v1.
The standard security profile refuses this scheme. lwe_n = "auto"
reads pyfe4ai.primitives.lattice.lwe_utils.WIDE_LWE_128_LOG_RATIO at the width
sigma / (1 + 4 X sqrt(eta)) of the MIFE layer’s security reduction (see
pyfe4ai.schemes.common.abdp15_lwe). At the flooding-enlarged q of
stat_param = 128 (two clients) that takes 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 more than the table’s 6144
for most others (X = Y = 4 to 6, X = Y >= 12, most eta >= 2);
X = Y = 50 would need about 6500. Read at sigma itself, the
earlier rule took 5952 for two clients (q of 217 bits, m of
1.3 * 10^6, A of 7.8 * 10^9 entries, so A had to be streamed):
key generation took 38 minutes, one encryption 572 s, a batch of 64
encryptions 679 s and a partial decryption 13 ms.
flooding_accounting = "kl" with max_partial_decryptions = Q
(opt-in, t = s with the Shamir sharing only, see
pyfe4ai.schemes.threshold.kl) sizes the noise for a run in
which every server produces at most Q partial decryptions:
KL <= 2^-(stat_param + 1) for the N = s Q flooded values, i.e.
sigma_f = B sqrt(N 2^stat_param), with a tail cut derived from N (16
sigma_f at N = 2^40), instead of a statistical distance
2^-stat_param per value. It is a different, weaker guarantee
(stat_param - 6.85 bits in the bit-security sense of Micciancio and
Walter, about 2^-65 of total statistical distance at stat_param =
128), valid only for the first Q partial decryptions of every server,
which each server counts in the partial_budget its deployment gives it.
At stat_param = 128 with X = Y = 50, two clients, s = t = 3 and
N = 2^40 it would take lwe_n about 4864 at sigma' (q of 173
bits, A of 4.1 * 10^9 entries, so "stream" mode), but the
standard profile refuses this scheme with either accounting (a
maintainer decision of October 2026); "kl" is available under toy.
These parameters have their own record (construction
acfgu18-abdp15-shamir-smudging-kl/v2).
- class pyfe4ai.schemes.mife.lwe_threshold.ThresholdMIFELWEKeyGenerator(config, **kwargs)[source]¶
Bases:
MIFELWEKeyGeneratorKey generator for threshold MIFE-LWE (ACFGU18 + Shamir + smudging).
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the key generator.
- Parameters:
config (
dict) – As forMIFELWEKeyGenerator, pluss(number of servers),t(threshold),lst_sid,stat_param(statistical parameterkappaof the flooding),sharing("replicated", the default fort < s,"shamir", the default fort = s, or the opt-in"replicated-seeded"; seeresolve_sharing()) and, opt-in,flooding_accounting = "kl"withmax_partial_decryptions(t = s, Shamir sharing; see the module docstring).- Return type:
None
- setup()[source]¶
Sample the MIFE keys and the client-server MAC keys; forget any previously shared keys.
- Return type:
- get_private_keys(nid, **kwargs)[source]¶
Return client nid’s MIFE encryption key plus
mac_keys({sid: hex}, one key per server for the ciphertext tags).
- get_public_parameters()[source]¶
Return the MIFE public parameters plus
s,t,lst_sid,sigma_f,sharing,threshold_construction,security_profileand, under"kl",flooding_accounting,max_partial_decryptions,stat_paramandflood_tail_cut.- Return type:
- get_decryption_keys(sid, **kwargs)[source]¶
Return server sid’s share of the MIFE functional key.
- Parameters:
sid (
str) – Decryption-server identifier.**kwargs – Must include
credentialswithfusion_weight.
- Return type:
- Returns:
Dict with
sk_y(per-slot share vectors),z(share of the pad term),sid,lst_sid,weights_id,noise_keyandmac_keys({nid: hex}, to verify the clients’ ciphertext tags), orNoneif sid is unknown. With replicated sharingsk_yandzare replaced bysharingandpieces([{"set": T, "share": {"sk_y": ..., "z": ...}}]); with"replicated-seeded"piecesholds the correction piece (or nothing), andseedsandlayoutgive the others.
- class pyfe4ai.schemes.mife.lwe_threshold.ThresholdMIFELWE(config, **kwargs)[source]¶
Bases:
MIFELWECrypto operations for threshold MIFE-LWE (ACFGU18 + Shamir + smudging).
A server refuses Shamir key shares with
t < s(versions <= 0.3.0) unless its config haslegacy_shamir = "warn"(seeLEGACY_SHAMIR_POLICIES).- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
As
MIFELWE; underflooding_accounting = "kl"a server needspartial_budget, thePartialDecryptionBudgetthat its deployment keeps for it;legacy_shamiras in the class docstring.- Parameters:
config (
dict)- Return type:
None
Compute this server’s flooded partial decryption.
- Parameters:
dict_ct (
dict) – One ciphertext per slot of the key.credentials (
dict) – Mapping of client IDs to the fusion weights the key share was derived for.dk (
dict) – This server’s key share.lst_sid_enrolled (
list) – The servers whose partial decryptions will be combined (at leastt, including this one).
- Return type:
- Returns:
Dict with
w,c_y,ct_digest,sidandlst_sid_enrolled.- Raises:
FESchemeError – If the enrolled set is invalid, the weights are not those of the key share, a ciphertext’s tag for this server does not verify (it was not produced by that slot’s client), or, under
flooding_accounting = "kl", the server has nopartial_budgetor its budget is spent (refused requests consume nothing).
- combine_decrypt(partials)[source]¶
Combine partial decryptions from at least
tservers.- Parameters:
partials –
{sid: share_decrypt(...)}for one enrolled set, or a list of such mappings for several enrolled sets, whose results must then agree (seecombine_consistently(): servers are assumed honest-but-curious, and only this redundancy detects one that returns a wrong partial).- Raises:
FESchemeError – If a group is incomplete or invalid, its partial decryptions disagree on the ciphertexts they decrypted, or the groups disagree.
- Return type:
- compute_lst_ndarray_ct(dict_ndarray_ct, credentials, dk, lst_sid_enrolled)[source]¶
Partially decrypt every element of the slots’ ndarray ciphertexts (under
"kl"refused whole unless the budget covers all of them; each partial produced then counts).
- decrypt_lst_ndarray_ct(dict_ndarray_ct, **kwargs)[source]¶
Combine element-wise partial decryptions from at least
tservers.- Raises:
FESchemeError – If the servers’ outputs differ in structure, an element does not decrypt (out of range), or the partial decryptions of an element disagree on the ciphertexts (the error and message of
combine_decrypt).- Parameters:
dict_ndarray_ct (
dict)- Return type:
Ring-LWE¶
Ring-LWE multi-input inner-product FE.
type: private-key multi-input encryption (MIFE, Ring-LWE)
setting: R_q = Z_q[x] / (x^ring_n + 1), Delta = floor(q / p)
Construction (slot i is client i; every row of the plaintext matrix is
one coordinate, its coefficients a batch of independent values):
Setup: public
a; per slots_{i,row}(Gaussian),pk_{i,row} = a s_{i,row} + e, and a one-time padu_{i,row} <- R_q(uniform coefficients); clientikeeps(pk_i, u_i).Encrypt:
w = x_i + u_i(coefficient-wise mod q);ct0_row = pk_row r + e_row + Delta w_row,ct1 = a r + e.KeyGen: per slot
sk_i = sum_row y_{i,row} s_{i,row}andz = sum_i sum_row y_{i,row} u_{i,row}(coefficient-wise mod q).Decrypt: Dec1 per slot
sum_row y_row ct0_row - ct1 sk_i; Dec2 decodessum_i Dec1_i - Delta zcoefficient-wise.
The pad hides each slot’s partial result; versions <= 0.2.0 had no pad and
decoded every slot separately. Parameters are derived as for a single-input
scheme of dimension n eta (ACFGU18 Setup*), since only the sum over all
slots is decoded, and with the Gaussian widths of BMMS21 Sec. 4.2 for
messages of norm 3X: ACFGU18 Fig. 4 needs an IPFE for the class
F_{m,3X,Y} (footnote 12; the hybrids of Lemma 3 encrypt
x^j - x^1 + x'^1), while p stays sized for X. Versions <= 0.3.0
used X, which meets that condition only for nine or more clients.
Deviation: all slots share a (secrets and pads are independent per
slot). A decryption needs ciphertexts of every slot named in
the key.
- class pyfe4ai.schemes.mife.ring_lwe.MIFERingLWEKeyGenerator(config, **kwargs)[source]¶
Bases:
IPFEAbsKeyGenerator,ParameterCacheMixinKey generator for Ring-LWE-based multi-input inner-product FE.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the MIFE Ring-LWE key generator.
- Parameters:
config (
dict) – Scheme configuration. Recognised keys:n(number of clients, required),lst_nid,sec_param(kappaof the noise bounds),eta,bound_x,bound_yandring_n(defaultDEFAULT_RING_N;"auto"for the smallest dimension meeting the 128-bit table, thestandardprofile’s choice);ring_modulus:"prime"(default) or"rns",qthe product of two NTT primes (opt-in; see RNS modulus for the Ring-LWE schemes: security review).- Return type:
None
- get_public_parameters()[source]¶
Return serialisable public parameters.
- Return type:
- Returns:
A dict of public parameters, or
Noneon error.
- class pyfe4ai.schemes.mife.ring_lwe.MIFERingLWE(config, **kwargs)[source]¶
Bases:
IPFEAbsCryptoCrypto operations for Ring-LWE-based multi-input inner-product FE.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the MIFE Ring-LWE crypto object.
- Parameters:
config (
dict) – Must containkeyswithpp(public parameters) and optionallysk(encryption keys).ct_format:"json"(default:ct0andct1as decimal strings) or"binary"(ct: bytes, seeencode_binary_ciphertext(); about a third of the size, and faster to produce and read). Decryption reads both formats.gaussian_sampler: that of the encryption noise (default: that ofpp, else"facct"; seescheme_sampler()).- Return type:
None
- property A: list¶
The public polynomial
a, converted frompp["A"]on first use (decryption never needs it).
- decrypt(dct_ct, dk, fusion_weight)[source]¶
Recover the batch of inner products
sum_i <x_i[:, k], y_i>.- Returns:
List with one inner product per batch position (
Noneentries if a decoded value exceedsn eta X Y).- Raises:
FEKeyError – If the key is missing.
FESchemeError – If ciphertexts do not cover exactly the key’s slots or have different batch lengths.
- Parameters:
- encrypt_lst_ndarray(lst_ndarray, **kwargs)[source]¶
Not implemented for this scheme; raises
NotImplementedError.
Paillier¶
DCR (Paillier) multi-input inner-product FE.
type: private-key multi-input encryption (MIFE, DCR)
setting: Integer based (Z_{N^2}^*, N = p q with safe primes)
Construction (slot i is client i, L = N):
Setup:
g = g'^{2N} mod N^2; per slots_i <- D_{Z^eta, sigma}withsigma > sqrt(kappa) N^{5/2},h_{i,j} = g^{s_{i,j}}and a one-time padu_i <- Z_N^eta; clientikeeps(h_i, u_i).Encrypt:
w = x_i + u_i mod N;C_0 = g^r(r <- {0..N/4}),C_j = (1 + w_j N) h_{i,j}^r.KeyGen: per slot
d_i = <s_i, y_i>over Z, andz = sum_i <u_i, y_i> mod N.Decrypt: Dec1 per slot
E(<w_i, y_i>) = prod_j C_j^{y_j} C_0^{-d_i};(prod_i E(<w_i, y_i>) * (1 + N)^{-z} - 1 mod N^2) / N, centred mod N.
The pad hides each slot’s partial result; versions <= 0.2.0 had no pad (the
key holder learned every <x_i, y_i>) and drew keys from a range small
enough to brute-force from h. Deviation: all slots share N and g
instead of running Setup independently per slot (s_i and u_i are
independent); the factorization of N is never stored. A decryption needs
ciphertexts of every slot named in the key. Correctness needs
2 n eta X Y < N.
- class pyfe4ai.schemes.mife.paillier.MIFEPaillierKeyGenerator(config, **kwargs)[source]¶
Bases:
IPFEAbsKeyGenerator,ParameterCacheMixinKey generator for Paillier-based multi-input inner-product FE.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the MIFE Paillier key generator.
- Parameters:
config (
dict) – Scheme configuration. Recognised keys:n(number of clients, required),lst_nid,eta,bit_length(bits of each prime ofN, default 128),bound_x,bound_yandstat_param(default 64).- Return type:
None
- setup()[source]¶
Sample per slot
s_i <- D_{Z^eta, sigma}(ALS16) and a padu_i <- Z_N^eta.- Return type:
- get_public_parameters()[source]¶
Return serialisable public parameters.
- Return type:
- Returns:
A dict of public parameters, or
Noneon error.
- class pyfe4ai.schemes.mife.paillier.MIFEPaillier(config, **kwargs)[source]¶
Bases:
OfflineRandomMixin,IPFEAbsCryptoCrypto operations for Paillier-based multi-input inner-product FE.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the MIFE Paillier crypto object.
- Parameters:
config (
dict) – Must containkeyswithpp(public parameters) and optionallysk(encryption keys).- Return type:
None
- encrypt_lst_ndarray(lst_ndarray, **kwargs)[source]¶
Encrypt a list of ndarrays element-wise (requires
eta = 1).- Parameters:
lst_ndarray (
list) – List of numpy arrays, scaled by10^precision.- Return type:
- Returns:
One object array of ciphertexts per input array.
- Raises:
NotImplementedError – If
eta != 1.
FH-IPE (Pairing)¶
Function-hiding multi-input inner-product FE over pairings.
k = 1 (SXDH) and the LV16 padding of Appendix B.type: private-key multi-input encryption with function-hiding keys
setting: asymmetric pairing group (default BLS12-381)
Slot i has the master key msk_i of a function-hiding IPFE
(pyfe4ai.schemes.mcfe.fh_ipe.FHIPE, which contains the inner
AGRW17 secret z_i):
Enc:
ct_i = FHIPE.Enc(msk_i, [x_i]_1).KeyGen: one
rfor all slots;dk_i = FHIPE.KeyGen(msk_i, [y_i]_2; r)and[z]_T = [r sum_i z_i]_T.Dec:
prod_i FHIPE.Dec(ct_i, dk_i) / [z]_T = [sum_i <x_i, y_i>]_T, then one bounded discrete log.
A slot’s partial result is masked by r z_i, so the key holder learns the
sum only, and slot keys of different functional keys (different r) do
not combine. Keys carry no digest of y and decryption does not need it.
A slot’s client holds that slot’s outer key W_i (needed to encrypt) and
could strip the outer layer of its own slot of a functional key – outside
the model, where clients are not corrupted. As in every MIFE there are no labels: a key holder may combine
any ciphertexts of each slot. Versions <= 0.2.0 ran independent KLMMRW
instances per slot and decoded every slot separately.
- class pyfe4ai.schemes.mife.fh_ipe_pairing.MIFEFHIPEPairingKeyGenerator(config, **kwargs)[source]¶
Bases:
IPFEAbsKeyGeneratorKey generator for the ACFGU18 function-hiding MIFE.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the key generator.
- Parameters:
config (
dict) –n,lst_nid,eta,bound_x,bound_yandpairing_group_param(asymmetric, default BLS12-381).- Return type:
None
- class pyfe4ai.schemes.mife.fh_ipe_pairing.MIFEFHIPEPairing(config, **kwargs)[source]¶
Bases:
IPFEAbsCryptoCrypto operations for the ACFGU18 function-hiding MIFE.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise with
keys.ppand, for clients,keys.sk.- Parameters:
config (
dict)- Return type:
None
- decrypt(dct_ct, dk, fusion_weight=None)[source]¶
log prod_i FHIPE.Dec(ct_i, dk_i) / [z]_T;Noneif out of bound.fusion_weight is accepted for API compatibility and ignored: the key encodes
y(function hiding).- Raises:
FESchemeError – If the ciphertexts do not cover every slot of the key.
- Parameters:
- encrypt_lst_ndarray(lst_ndarray, **kwargs)[source]¶
Not provided: MIFE is not used for element-wise aggregation (no labels).
- compute_lst_ndarray_ct(dict_ndarray_ct, **kwargs)[source]¶
Not provided (see
encrypt_lst_ndarray()).
FH-Multi-IPE (Pairing)¶
Full-hiding multi-input inner-product FE over pairings (bounded slots)
| “Full-Hiding (Unbounded) Multi-Input Inner Product Functional Encryption
| from the k-Linear Assumption”
| By Pratish Datta, Tatsuaki Okamoto, Junichi Tomida, PKC 2018 (ePrint
| 2018/061) – Section 3.1, the bounded scheme from k-LIN with dual pairing
| vector spaces; structure as in GoFE’s innerprod/fullysec/fh_multi_ipe.go.
type: private-key multi-input encryption with function-hiding keys
setting: asymmetric pairing group (default BLS12-381),
k = sec_level
Per slot dual orthogonal bases B_i, B*_i of dimension 2 eta + 2k + 1
(B_i B*_i^T = mu I), g_T = e(g1, g2)^mu public.
Enc:
c_i = [(x_i, 0^eta, 1, 0^(k-1), phi, 0) B_i]_1(phi <- Z_p^k).KeyGen:
k_i = [(y_i, 0^eta, r_i, gamma_i, 0^k, 0) B*_i]_2with freshsum_i r_i = 0andgamma_i <- Z_p^(k-1).Dec:
prod_i e(c_i, k_i) = g_T^{sum <x_i, y_i>}and one discrete log.
Each slot’s pairing is masked by r_i; slot keys of different functional
keys do not combine; keys hold group elements only and decryption does not
need y. Deviations: no SKE layer (Remark B.1), so hiding assumes every
slot contributes a ciphertext; slot i’s client holds B^_i in Z_p
and could decode its own slot of a key (outside the model). n >= 2 is
required (the proof needs the zero sharing r_i over at least two slots;
with n = 1 and k = 1 keys were deterministic). As in every MIFE there
are no labels.
- class pyfe4ai.schemes.mife.fh_multi_ipe_pairing.MIFEFHMultiIPEPairingKeyGenerator(config, **kwargs)[source]¶
Bases:
IPFEAbsKeyGenerator,ParameterCacheMixinKey generator for function-hiding multi-slot multi-input IPE over pairings.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the MIFE FH-Multi-IPE key generator.
- Parameters:
config (
dict) – Scheme configuration. Recognised keys:n(number of clients, at least 2),lst_nid,eta,sec_level(kof k-LIN, default 2),bound_x,bound_yandpairing_group_param(asymmetric group, default"BLS12-381").- Return type:
None
- get_public_parameters()[source]¶
Return serialisable public parameters.
- Return type:
- Returns:
A dict of public parameters, or
Noneon error.
- class pyfe4ai.schemes.mife.fh_multi_ipe_pairing.MIFEFHMultiIPEPairing(config, **kwargs)[source]¶
Bases:
IPFEAbsCryptoCrypto operations for function-hiding multi-slot multi-input IPE over pairings.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the MIFE FH-Multi-IPE crypto object.
- Parameters:
config (
dict) – Must containkeyswithpp(public parameters) and optionallysk(encryption keys).- Return type:
None
- decrypt(dct_ct, dk, fusion_weight=None)[source]¶
log_{g_T} prod_i e(c_i, k_i);Noneif out of bound.fusion_weight is accepted for API compatibility and ignored (the key encodes
y).- Raises:
FESchemeError – Unless there is one ciphertext and one key part per slot.
- Parameters:
- encrypt_lst_ndarray(lst_ndarray, **kwargs)[source]¶
Encrypt ndarrays element-wise (
eta = 1).Every element is a separate ciphertext of the slot, and MIFE has no labels: the holder of one key can combine elements of different positions. Use for one input per key epoch, not for aggregation.