MCFE — Multi-Client Functional Encryption¶
Multi-client 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.mcfe under their own names (one name per class,
no aliases; the naming rule is in pyfe4ai.core.registry).
DDH¶
Chotard, Jérémy, Edouard Dufour Sans, Romain Gay, Duong Hieu Phan, and David Pointcheval. “Decentralized multi-client functional encryption for inner product.” In Advances in Cryptology ASIACRYPT 2018, Brisbane, QLD, Australia, December 2-6, pp. 703-732. Springer International Publishing, 2018. | URL: https://eprint.iacr.org/2017/989
type: secret-key multi-client encryption (MCFE, DDH, random oracle)
setting: Integer based (order-q subgroup of Z_p^*, p = 2q + 1)
Construction (one coordinate j of client i’s vector is one CDGPP18 slot):
Setup:
s_{i,j} <- Z_q^2for every client i and coordinate j.Encrypt:
[u_l] = H(l) in G^2;c_{i,j} = g^{x_{i,j}} * [u_l]^{s_{i,j}}.KeyGen:
d = sum_{i,j} y_{i,j} * s_{i,j} in Z_q^2(one aggregated key).Decrypt:
g^{<x,y>} = prod c_{i,j}^{y_{i,j}} / [u_l]^d, then a bounded dlog.
Each client must encrypt at most once per label: two ciphertexts of the same client under the same label reveal the difference of the plaintexts.
- class pyfe4ai.schemes.mcfe.ddh.MCFEDDHKeyGenerator(config, **kwargs)[source]¶
Bases:
DDHKeyGeneratorBaseKey generator for DDH-based multi-client inner-product FE.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the MCFE 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, n, eta, sec_param).
- Return type:
- Returns:
Dict of public parameters.
- get_decryption_keys(sid, **kwargs)[source]¶
Derive the functional decryption key
d = sum y_{i,j} s_{i,j}.- Parameters:
sid (
str) – Session identifier.**kwargs – Must include
credentialswithfusion_weight(dict mapping client IDs to weight lists).
- Return type:
- Returns:
Dict with
d(a pair of integers mod q). The key is aggregated over all clients, so it gives no per-client decryption capability.- Raises:
FEKeyError – If credentials are missing.
FEValidationError – If a client id is unknown or a weight list is longer than that client’s
eta.
- class pyfe4ai.schemes.mcfe.ddh.MCFEDDH(config)[source]¶
Bases:
OfflineMCFEMixin,IPFEAbsCryptoCrypto operations for DDH-based multi-client inner-product FE.
- Parameters:
config (
dict)
- __init__(config)[source]¶
Initialise the MCFE crypto system.
- Parameters:
config (
dict) – Must containkeyswithpp(public parameters) and eithersk(encryption keys) ordk(decryption key).dlog_bound(default10^(precision + 2)) bounds|<x, y>|for the final discrete log.- Return type:
None
- encrypt(lst_pt, label)[source]¶
Encrypt a plaintext vector for one client under a label.
- Parameters:
- Return type:
- Returns:
Dict with
c(one group element per plaintext slot).- Raises:
FEKeyError – If public parameters or private keys are missing.
FEValidationError – If plaintext length or format is invalid.
- decrypt(dct_ct, dk, fusion_weight, label, term=None)[source]¶
Decrypt aggregated ciphertexts to recover the multi-client inner product.
- Parameters:
dct_ct (
dict) – Mapping of client IDs to their ciphertext dicts.dk (
dict) – Decryption key dict withd(pair of integers mod q).fusion_weight (
dict) – Mapping of client IDs to weight lists; must be the weights the key was derived for.term (default:
None) –prod_k H_k(l)^{d_k} mod pif already computed (prepare_key()).
- Returns:
The inner product as an integer, or
Noneif out of bound.- Raises:
FEKeyError – If decryption key is missing.
FESchemeError – If ciphertexts and fusion weights cover different clients.
- encrypt_lst_ndarray(lst_ndarray, **kwargs)[source]¶
Encrypt a list of ndarrays element-wise with a label.
Each element is encrypted under its own label derived from
label, the layer and the element index (seepyfe4ai.schemes.mcfe.labels).
- decrypt_lst_ndarray_ct(dict_ndarray_ct, dk, fusion_weight, label)[source]¶
Decrypt ndarray ciphertexts from multiple clients.
- compute_lst_ndarray_ct(dict_ndarray_ct, **kwargs)[source]¶
Aggregate ndarray ciphertexts (delegates to
decrypt_lst_ndarray_ct()).
DDH over ristretto255¶
Multi-client inner-product FE (DDH) over ristretto255 | Chotard, Dufour Sans, Gay, Phan, Pointcheval: | “Decentralized multi-client functional encryption for inner product”, | ASIACRYPT 2018, https://eprint.iacr.org/2017/989 – the MCFE of Sec. 4.
type: secret-key multi-client encryption (MCFE, DDH, random oracle)
setting: the prime-order group ristretto255 (RFC 9496), written additively
As pyfe4ai.schemes.mcfe.ddh in ristretto255 (one coordinate j of
client i is one CDGPP18 slot):
Setup:
s_{i,j} <- Z_L^2.Encrypt:
[u_l] = H(l) in G^2;c_{i,j} = x_{i,j} G + <s_{i,j}, [u_l]>.KeyGen:
d = sum_{i,j} y_{i,j} s_{i,j} in Z_L^2(one aggregated key).Decrypt:
sum y_{i,j} c_{i,j} - <d, [u_l]> = <x, y> G, then a bounded dlog.
H(l)_k = hash_to_ristretto255(k || encode_label(l)) (RFC 9380, DST
PYFE4AI-MCFE-DDH-H(label)-V01-CS02-with-ristretto255_XMD:SHA-512_R255MAP_RO_).
Each client must encrypt at most once per label; the ndarray helpers derive
one label per element.
- pyfe4ai.schemes.mcfe.ddh_ristretto255.hash_label(label)[source]¶
[u_l] = H(label) in G^2(random oracle into ristretto255).- Return type:
- class pyfe4ai.schemes.mcfe.ddh_ristretto255.MCFEDDHRistretto255KeyGenerator(config, **kwargs)[source]¶
Bases:
ECDDHKeyGeneratorBaseKey generator for DDH-based multi-client 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.mcfe.ddh_ristretto255.MCFEDDHRistretto255(config, **kwargs)[source]¶
Bases:
ECDDHCryptoBaseCrypto operations for DDH-based multi-client inner-product FE over ristretto255.
- Parameters:
config (
dict)
- encrypt(lst_pt, label)[source]¶
{c: [x_j G + <s_j, H(label)>]}; a client encrypts at most once per label.
- decrypt(dct_ct, dk, fusion_weight, label)[source]¶
sum_i <x_i, y_i>under label, orNoneif beyonddlog_bound.- Raises:
FEKeyError – If the decryption key is missing or malformed.
FESchemeError – If ciphertexts and weights cover different clients.
FEValidationError – If a ciphertext is malformed.
- Parameters:
- encrypt_lst_ndarray(lst_ndarray, **kwargs)[source]¶
Encrypt ndarrays element-wise, every element under its own label.
- decrypt_lst_ndarray_ct(dict_ndarray_ct, dk, fusion_weight, label)[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 Decentralized¶
Decentralised multi-client inner-product FE over DDH.
pyfe4ai.schemes.mcfe.ddh) andpyfe4ai.schemes.mcfe.decentralize).type: decentralised multi-client encryption (DMCFE, DDH + DCR, random oracle)
setting: Integer based (order-q subgroup of Z_p^*; Z_{N2^2}^* for key shares)
Encryption and decryption are those of CDGPP18 (c = g^x [H(l)]^s,
g^{<x,y>} = prod c^y / [H(l)]^d). There is no authority for functional
keys: for weights y every client i publishes an MCFE2 encryption of
its contribution c_i = (sum_j y_{i,j} s_{i,j} mod q) + q rho_i (a pair)
under the label H(y); the zero-sum MCFE2 keys make the product of all
clients’ shares decrypt to sum_i c_i, whose residue mod q is the
CDGPP18 functional key d. Every client must publish a share (with zero
weights if absent from y).
The carry mask rho_i is our own adaptation. ABMPSW20 states Fig. 14 for
MCFEs that need no discrete logarithm when decrypting (DCR, LWE): with
CDGPP18 the secrets are uniform mod q, so sum_i y_i s_i is not small.
Reducing each contribution mod q (as versions before this one did) makes
the integer sum d + q carry with carry in 0..n-1, which anyone
holding the published shares reads off: a comparison oracle on the clients’
reduced contributions, from which a series of keys reveals their secrets.
Here rho_i is uniform below 2^(stat_param + bits(n)) and derived
from a per-client key and H(y) (repeating a derivation repeats the share,
so masks cannot be averaged out), and carry + sum_i rho_i is within
statistical distance 2^-stat_param of a carry-independent distribution.
The argument is ours, hence the scheme’s experimental status; Theorem 38
covers the MCFE2 layer only.
- class pyfe4ai.schemes.mcfe.ddh_decentralized.DecentralizedMCFEDDHKeyGenerator(config, **kwargs)[source]¶
Bases:
MCFEDDHKeyGeneratorSets up CDGPP18 client keys and zero-sum MCFE2 keys (ABMPSW20 Fig. 14).
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the key generator.
- Parameters:
config (
dict) – As forMCFEDDHKeyGenerator(sec_param,eta,n,lst_nid), plusstat_paramfor MCFE2.- Return type:
None
- get_public_parameters()[source]¶
CDGPP18 public parameters plus
n2_mod,lst_nidand the contribution bound.- Return type:
- class pyfe4ai.schemes.mcfe.ddh_decentralized.DecentralizedMCFEDDH(config)[source]¶
Bases:
MCFEDDHCDGPP18 encryption / decryption with decentralised key derivation.
- Parameters:
config (
dict)
Publish this client’s encrypted key contribution for weights
y.
Combine every client’s share into the CDGPP18 key
{d}.- Raises:
FESchemeError – If a client’s share is missing or the shares were derived for different weights.
- Parameters:
dct_dk_shares (
dict)- Return type:
DDH Threshold¶
Threshold multi-client inner-product FE over DDH: CDGPP18 MCFE with a Shamir-shared functional key.
pyfe4ai.schemes.mcfe.ddh).s decryption serverst of them decrypt by Lagrange interpolation in the exponent.type: secret-key multi-client encryption, threshold functional decryption
setting: Integer based (order-q subgroup of Z_p^*, p = 2q + 1), random oracle
Construction (one coordinate j of client i’s vector is one CDGPP18 slot):
Setup:
s_{i,j} <- Z_q^2for every client slot.Encrypt:
[u_l] = H(l) in G^2;c_{i,j} = g^{x_{i,j}} * [u_l]^{s_{i,j}}.KeyGen:
d = sum y_{i,j} s_{i,j} in Z_q^2; each component ofdis Shamir-shared with thresholdtoverZ_qat points1..s; serverkreceivesd^(k) in Z_q^2.ShareDec: server
kof an enrolled setE(|E| >= t) publishesP_k = [u_l]^{lambda_k * d^(k)}withlambda_kits Lagrange coefficient at 0 forE(mod q), andC_y = prod c_{i,j}^{y_{i,j}}.Combine:
[u_l]^d = prod_{k in E} P_k;g^{<x,y>} = C_y / [u_l]^d, then a bounded dlog.
The functional key is a single aggregated value, so neither a server nor the
combiner can decrypt any individual client; fewer than t shares of d
are independent of d. Each client must encrypt at most once per label:
two ciphertexts of the same client slot under the same label reveal the
difference of the plaintexts. The ciphertext carries its (public) label so
that the servers can evaluate H(l).
Deviation from earlier versions (<= 0.2.0): the label used to enter as a public MD5 scalar and the key carried one share per client, which let the combiner strip each client’s mask; see CHANGELOG 0.3.0.
- class pyfe4ai.schemes.mcfe.ddh_threshold.ThresholdMCFEDDHKeyGenerator(config, **kwargs)[source]¶
Bases:
DDHKeyGeneratorBaseKey generator for threshold DDH-based multi-client inner-product FE.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the threshold MCFE key generator.
- Parameters:
config (
dict) – Scheme configuration. Recognised keys:sec_param,eta(slots per client),n(number of clients),s(number of decryption servers),t(threshold),lst_nidandlst_sid.- Return type:
None
- get_public_parameters()[source]¶
Return serialisable public parameters (p, g, eta, n, s, t, sec_param).
- Return type:
- get_decryption_keys(sid, **kwargs)[source]¶
Return server sid’s Shamir share of the functional key for
y.All servers asking for the same fusion weights receive shares of one and the same sharing. The key does not depend on the label; a
labelentry in the credentials is accepted and ignored.- Parameters:
sid (
str) – Decryption-server identifier, must be inlst_sid.**kwargs – Must include
credentialswithfusion_weight(dict mapping client IDs to integer weight lists).
- Return type:
- Returns:
Dict with
d(this server’s share, a pair mod q),sid,lst_sidandweights_id(digest of the weights), orNoneif sid is unknown.- Raises:
FEKeyError – If credentials are missing.
FEValidationError – If the fusion weights are malformed.
- class pyfe4ai.schemes.mcfe.ddh_threshold.ThresholdMCFEDDH(config)[source]¶
Bases:
IPFEAbsCryptoCrypto operations for threshold DDH-based multi-client inner-product FE.
- Parameters:
config (
dict)
- __init__(config)[source]¶
Initialise a client (
keys.sk), server (keys.dk) or combiner.- Parameters:
config (
dict) – Must containkeyswithppand optionallyskordk;idnames the client / server;precisionis the number of decimal digits kept when encrypting floats;dlog_bound(default10^(precision + 2)) bounds|<x, y>|(after scaling) for the final discrete log.- Return type:
None
- encrypt(pt, label)[source]¶
Encrypt one client’s vector under a label.
Values are scaled by
10^precisionand rounded to integers.- Parameters:
- Return type:
- Returns:
Dict with
c(one group element per slot) andlabel(the encoded label, seepyfe4ai.schemes.mcfe.labels.encode_label()).- Raises:
FEKeyError – If public parameters or private keys are missing.
FEValidationError – If the plaintext or label is malformed.
Compute this server’s partial decryption for an enrolled set.
- Parameters:
dict_ct (
dict) – Mapping of client IDs to ciphertexts (all under one label).credentials (
dict) – Mapping of client IDs to the fusion weights the key share was derived for.dk (
dict) – This server’s key share fromget_decryption_keys.lst_sid_enrolled (
list) – The servers whose partial decryptions will be combined (at leastt, including this one).label – The label the caller wants decrypted; ciphertexts under any other label (e.g. those of an earlier round) are refused.
- Return type:
- Returns:
Dict with
c_y(prod c^y),u_d([u_l]^{lambda d^(k)}),label,sidandlst_sid_enrolled.- Raises:
FEKeyError – If the key share is missing or malformed.
FESchemeError – If the server or enrolled set is invalid, or the ciphertexts do not match the fusion weights / a single label.
- combine_decrypt(partials, label=None)[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).label (default:
None) – Optional label the partials must be for.
- Raises:
FESchemeError – If a group is incomplete or invalid, is for another label or its partial decryptions disagree on the ciphertexts they decrypted, or the groups disagree.
- Return type:
- encrypt_lst_ndarray(lst_ndarray, **kwargs)[source]¶
Encrypt a list of ndarrays element-wise (
eta = 1slots).Every element is encrypted under its own label derived from
label, the layer and the element index, because a client must not encrypt twice under one label.
- compute_lst_ndarray_ct(dict_ndarray_ct, credentials, dk, lst_sid_enrolled, label)[source]¶
Partially decrypt every element of clients’ ndarray ciphertexts.
Element
(l, i)must be encrypted under its element label derived from label, so ciphertexts of another round or position are refused.- Parameters:
dict_ndarray_ct (
dict) – Mapping of client IDs to ciphertext ndarray lists.credentials (
dict) – Mapping of client IDs to fusion weights.dk (
dict) – This server’s key share.lst_sid_enrolled (
list) – The servers whose shares will be combined.label (
str) – The label the ndarrays were encrypted under.
- Return type:
- decrypt_lst_ndarray_ct(dict_ndarray_ct, label)[source]¶
Combine element-wise partial decryptions from at least
tservers.- Parameters:
dict_ndarray_ct – Mapping of server ids to the outputs of
compute_lst_ndarray_ct()(or a list of such mappings for several enrolled sets, seecombine_decrypt()).label (
str) – The label of the ndarrays; every partial must be for the element label of its position.
- 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).- Return type:
Damgård DDH¶
MDDH-based multi-client inner-product FE (Damgard-style DDH family).
type: secret-key multi-client encryption (MCFE, MDDH, random oracle)
setting: Integer based (order-q subgroup of Z_p^*, p = 2q + 1)
Construction (one coordinate j of client i’s vector is one slot; [a]
denotes g^a):
Setup:
H: labels -> G^{k+1}.KeyGen:
S_{i,j} <- Z_q^{k+1}per slot.Encrypt:
[u_l] = H(l);c_{i,j} = [S_{i,j} . u_l + x_{i,j}].KeyDer:
sk_y = sum y_{i,j} S_{i,j} in Z_q^{k+1}(one aggregated key).Decrypt:
[<x, y>] = prod c_{i,j}^{y_{i,j}} / [u_l . sk_y], then a bounded discrete log.
k is the config key mddh_k (default 1). For k = 1 (DDH) this is
exactly the CDGPP18 MCFE of pyfe4ai.schemes.mcfe.ddh, and it is also
what ABMPSW20’s approach (replace the IPFE randomness (g^r, h^r) by
H(l)) yields from the Damgard-style DDH IPFE of Agrawal, Libert, Stehle
(CRYPTO 2016), which is why the registry keeps the name damgard for it.
k >= 2 gives the k-Lin instantiation (a weaker assumption, k + 1
group elements per label hash and key).
Each client slot must encrypt at most once per label (one-IND security); the
ndarray helpers derive a distinct label per element. H hashes into the
quadratic-residue subgroup with SHAKE-256.
- class pyfe4ai.schemes.mcfe.damgard_ddh.MCFEDamgardKeyGenerator(config, **kwargs)[source]¶
Bases:
IPFEAbsKeyGenerator,ParameterCacheMixinKey generator for MDDH-based multi-client inner-product FE (ABMPSW20 Fig. 2).
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the key generator.
- Parameters:
config (
dict) – Scheme configuration. Recognised keys:n(number of clients),lst_nid,eta(slots per client),modulus_length(bits ofp, defaultsec_param),bound(bound on|x|and|y|) andmddh_k.- Return type:
None
- get_private_keys(nid, **kwargs)[source]¶
Return the encryption key
{s}of client nid (Noneif unknown).
- get_decryption_keys(sid, **kwargs)[source]¶
Derive the functional key
sk_y = sum y_{i,j} S_{i,j} in Z_q^{k+1}.- Parameters:
sid (
str) – Session identifier (unused).**kwargs – Must include
credentialswithfusion_weight(dict mapping client IDs to integer weight lists).
- Return type:
- Returns:
Dict with
d, one vector aggregated over all clients.- Raises:
FEKeyError – If credentials are missing.
FEValidationError – If a client id is unknown or a weight is malformed or exceeds
bound.
- class pyfe4ai.schemes.mcfe.damgard_ddh.MCFEDamgard(config, **kwargs)[source]¶
Bases:
OfflineMCFEMixin,IPFEAbsCryptoCrypto operations for MDDH-based multi-client inner-product FE (ABMPSW20 Fig. 2).
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise with
keys.ppand, for clients,keys.sk.- Parameters:
config (
dict)- Return type:
None
- encrypt(lst_pt, label)[source]¶
Encrypt one client’s integer vector under a label.
- Parameters:
- Return type:
- Returns:
Dict with
c(one group element per slot).- Raises:
FEKeyError – If public parameters or encryption keys are missing.
FEValidationError – If the plaintext is malformed or out of bound.
- decrypt(dct_ct, dk, fusion_weight, label, term=None)[source]¶
Recover
sum_i <x_i, y_i>from all clients’ ciphertexts under one label.- Parameters:
dct_ct (
dict) – Mapping of client IDs to ciphertext dicts.dk (
dict) – Functional key{d}derived for fusion_weight.fusion_weight (
dict) – Mapping of client IDs to weight lists.label (
str) – The encryption label.term (default:
None) –prod_m H_m(l)^{d_m} mod pif already computed (prepare_key()).
- Returns:
The inner product, or
Noneif it is out of the dlog bound (e.g. wrong label or key).- Raises:
FEKeyError – If the key is missing or malformed.
FESchemeError – If ciphertexts and weights do not match.
- encrypt_lst_ndarray(lst_ndarray, **kwargs)[source]¶
Encrypt ndarrays element-wise (
eta = 1), one derived label per element.
- compute_lst_ndarray_ct(dict_ndarray_ct, **kwargs)[source]¶
Aggregate-decrypt ndarray ciphertexts (delegates to
decrypt_lst_ndarray_ct()).
LWE¶
LWE-based multi-client inner-product FE.
type: secret-key multi-client encryption (MCFE, LWE, random oracle)
setting: Integer based (Z_q with a rounding map to Z_q0)
Construction (one coordinate j of client i’s vector is one slot; n is the
total number of slots, Delta = floor(q / K)):
Setup:
H: labels -> Z_q^{n0 + m0}full-domain hash.KeyGen:
Z_{i,j} = (s_{i,j}, t_{i,j})withs <- U(Z_q^{n0})andt <- D_{Z^{m0}, sigma_t}, sampled exactly by default (gaussian_sampler = "facct", recorded in the public parameters), as the statistical step of the proof (Lemma 34) assumes;"rounded"(a rounded continuous Gaussian, 2^-25..2^-15 from it per key in the October 2026 crypto review) stays selectable.Encrypt:
c_{i,j} = round_q0( Z_{i,j} . H(l) + Delta * x_{i,j} ).KeyDer:
sk_y = sum y_{i,j} Z_{i,j}(one aggregated vector, kept mod q).Decrypt:
mu' = sum y_{i,j} c_{i,j} - round_q0( sk_y . H(l) ) mod q0; output themuclosest tomu' q / (q0 Delta).
Deviation from the paper: messages and weights are signed (|x| <= X,
|y| <= Y) instead of {0..P-1} / {0..V-1}; the inner product is
encoded with K = 2 n X Y + 1 and decoded centred. Parameters are derived
by pyfe4ai.primitives.lattice.lwe_utils.abmpsw20_lwe_parameters() from the
constraints of Appendix C.3; this module uses the selective-security choice
of m0 and pyfe4ai.schemes.mcfe.fullysec_lwe the adaptive one.
The LWE dimension lwe_n defaults to a toy value; lwe_n = "auto"
(the standard security profile) selects the smallest dimension whose
q meets pyfe4ai.primitives.lattice.lwe_utils.DENSE_LWE_128_LOG_Q, a lattice
estimator bound for LWE(n0, q, ceil(2 sqrt(n0))) – the assumption of the
proof (Appendix C.2, game G2: LWE in dimension n0 modulo the scheme’s
q) – with a uniform secret.
Each client slot must encrypt at most once per label (one-IND security):
two ciphertexts of one slot under one label differ by about
(q0 / q) Delta (x - x'). The ndarray helpers therefore derive a distinct
label per element. H is SHAKE-256 (the paper’s implementation uses
counter-mode SHA-256), mapped to Z_q as the key generator’s
label_hash says (a public parameter;
LABEL_HASHES): by default rejection
sampling mod q from SHAKE128 (exactly uniform, fewer XOF bytes). Versions
<= 0.3.0 reduced bits(q) + 128-bit SHAKE-256 blocks ("shake256-wide",
still selectable), so their ciphertexts differ. The mode is bound to the
public parameters, the clients’ encryption keys, every single ciphertext and
every ndarray layer (label_hash: the code of
LABEL_HASH_CODES, absent for
"shake256-wide"; in an ndarray layer only its first element carries it,
for the whole layer); a missing field means "shake256-wide"
(the data of versions <= 0.3.0 stay readable), and encryption with a key of
another mode or decryption of a ciphertext of another mode raises instead of
returning a wrong sum. Functional keys sum y Z do not depend on the hash.
- pyfe4ai.schemes.mcfe.lwe.hash_label(label, q, length, mode)[source]¶
Return
H(label) in Z_q^length(random oracle; mode: thelabel_hashpublic parameter).
- pyfe4ai.schemes.mcfe.lwe.dot_mod(left, right, q)[source]¶
Return
<left, right> mod qfor integer vectors.
- class pyfe4ai.schemes.mcfe.lwe.MCFELWEKeyGenerator(config, **kwargs)[source]¶
Bases:
IPFEAbsKeyGeneratorKey generator for LWE-based multi-client inner-product FE (ABMPSW20 Fig. 4).
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the key generator and derive the parameters.
- Parameters:
config (
dict) – Scheme configuration. Recognised keys:n(number of clients),lst_nid,eta(slots per client),lwe_n(LWE dimensionn0; default 32,"auto"for the smallest one meetingDENSE_LWE_128_LOG_Q, thestandardprofile’s choice),bound_x,bound_y,stat_param(statistical parameterkappa),sec_param(recorded only),gaussian_sampler(that of the keyst:"facct", the default, or"rounded"; seescheme_sampler()) andlabel_hash(one ofLABEL_HASHES, default"shake128-rejection","shake256-wide"for the ciphertexts of versions <= 0.3.0; a public parameter). The keysbound_uandlabel_modulusof versions <= 0.2.0 are ignored.- Return type:
None
- get_private_keys(nid, **kwargs)[source]¶
Return the encryption key
{z, label_hash}of client nid (Noneif unknown).
- get_decryption_keys(sid, **kwargs)[source]¶
Derive the functional key
sk_y = sum y_{i,j} Z_{i,j}.- Parameters:
sid (
str) – Session identifier (unused).**kwargs – Must include
credentialswithfusion_weight(dict mapping client IDs to integer weight lists).
- Return type:
- Returns:
Dict with
sk_y, one vector aggregated over all clients.- Raises:
FEKeyError – If credentials are missing.
FEValidationError – If a client id is unknown or a weight is malformed or exceeds
bound_y.
- class pyfe4ai.schemes.mcfe.lwe.MCFELWE(config, **kwargs)[source]¶
Bases:
IPFEAbsCryptoCrypto operations for LWE-based multi-client inner-product FE (ABMPSW20 Fig. 4).
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise with
keys.ppand, for clients,keys.sk.- Parameters:
config (
dict)- Return type:
None
- encrypt(lst_pt, label)[source]¶
Encrypt one client’s integer vector under a label.
- Parameters:
- Return type:
- Returns:
Dict with
c(one element ofZ_q0per slot).- Raises:
FEKeyError – If public parameters or encryption keys are missing.
FEValidationError – If the plaintext is malformed or out of bound.
- decrypt(dct_ct, dk, fusion_weight, label, _in_layer=False)[source]¶
Recover
sum_i <x_i, y_i>from all clients’ ciphertexts under one label.- Parameters:
- Returns:
The inner product, or
Noneif the decoded value is outside[-n X Y, n X Y]. A wrong label or key yields an arbitrary value in that range rather than an error.- Raises:
FEKeyError – If the key is missing.
FESchemeError – If ciphertexts and weights do not match.
- encrypt_lst_ndarray(lst_ndarray, **kwargs)[source]¶
Encrypt ndarrays element-wise (
eta = 1), one derived label per element.
- compute_lst_ndarray_ct(dict_ndarray_ct, **kwargs)[source]¶
Aggregate-decrypt ndarray ciphertexts (delegates to
decrypt_lst_ndarray_ct()).
Fully-Secure LWE¶
LWE-based multi-client inner-product FE, adaptive-security parameters.
The adaptive setting requires
m0 * log2(4/3) >= log2(q) + 2 * kappa + 4 * n * log2(P).
type: secret-key multi-client encryption (MCFE, LWE, random oracle)
setting: Integer based (Z_q with a rounding map to Z_q0)
The algorithms are those of pyfe4ai.schemes.mcfe.lwe; only the choice
of m0 differs (larger, so that the statistical step of the proof also
covers adaptively chosen challenges). See that module for the construction,
the signed-message deviation and the one-ciphertext-per-label rule.
- class pyfe4ai.schemes.mcfe.fullysec_lwe.MCFEFullySecLWEKeyGenerator(config, **kwargs)[source]¶
Bases:
MCFELWEKeyGeneratorKey generator for the adaptively secure parameter set of ABMPSW20 Fig. 4.
- Parameters:
config (
dict)
LWE Decentralized¶
Decentralised multi-client inner-product FE over LWE.
pyfe4ai.schemes.mcfe.lwe) and MCFE2 = their DCR MCFE (Fig. 3,type: decentralised multi-client encryption (DMCFE, LWE + DCR, random oracle)
setting: Integer based (Z_q with a rounding map to Z_q0; Z_{N2^2}^* for key shares)
Encryption and decryption are those of the LWE MCFE. For weights y every
client i publishes an MCFE2 encryption of its contribution
sum_j y_{i,j} Z_{i,j} over the integers (a vector of length n0 + m0)
under the label H(y); the zero-sum MCFE2 keys make the product of all
clients’ shares decrypt to sk_y = sum_{i,j} y_{i,j} Z_{i,j} in
Z^(n0 + m0), the functional key of Fig. 4. Versions before this one
reduced each contribution mod q, so the decrypted integer sum also told
how many y_i t_i were negative in every coordinate. Every client
must publish a share. Secure against static corruptions (Theorem 38).
Versions <= 0.2.0 used an ad-hoc label scalar with per-client key
components instead. A key share costs n0 + m0 DCR encryptions of one
label hash (computed with a fixed-base method).
N2 only has to hold the shares (2 n2 X2^2 < N2, a few hundred bits
for X2 = q); under the standard security profile each of its primes
has at least STANDARD_DCR_PRIME_BITS = 1536 bits. Key derivation thus
also rests on DCR, which is not post-quantum, whatever the LWE parameters.
- class pyfe4ai.schemes.mcfe.lwe_decentralized.DecentralizedMCFELWEKeyGenerator(config, **kwargs)[source]¶
Bases:
MCFELWEKeyGenerator,ParameterCacheMixinSets up LWE MCFE client keys and zero-sum MCFE2 keys (ABMPSW20 Fig. 14).
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the key generator.
- Parameters:
config (
dict) – As forMCFELWEKeyGenerator(n,lst_nid,eta,lwe_n,bound_x,bound_y,stat_param).- Return type:
None
- class pyfe4ai.schemes.mcfe.lwe_decentralized.DecentralizedMCFELWE(config, **kwargs)[source]¶
Bases:
MCFELWELWE MCFE encryption / decryption with decentralised key derivation.
- Parameters:
config (
dict)
Publish this client’s encrypted contribution
sum_j y_{i,j} Z_{i,j}(over Z).
Combine every client’s share into the LWE MCFE key
{sk_y}.- Raises:
FESchemeError – If a client’s share is missing or the shares were derived for different weights.
- Parameters:
dct_dk_shares (
dict)- Return type:
LWE Threshold¶
Threshold LWE-based multi-client inner-product FE: ABMPSW20 LWE MCFE with a Shamir-shared functional key and noise-flooded partial decryptions.
s servers, any t decrypt.type: secret-key multi-client encryption, threshold functional decryption
setting: Integer based (Z_q with a rounding map to Z_q0), random oracle
Construction (notation of pyfe4ai.schemes.mcfe.lwe):
KeyGen:
sk_y = sum y_{i,j} Z_{i,j} mod q; every coordinate is Shamir-shared with thresholdtoverZ_q(qprime) at points1..s; serverkreceivessk_y^(k).ShareDec: server
kof an enrolled setEpublishesw_k = lambda_k <sk_y^(k), H(l)> + e_k mod qwithlambda_kits Lagrange coefficient forEand flooding noisee_k <- D_{Z, sigma_f}(truncated at 12 sigma_f), plussum y_{i,j} c_{i,j} mod q0for a consistency check.e_kis derived from a per-server secret key,l,lambda_kand the key share’spp_binding(the public parameters it is bound to), so requests for the same label and coefficient get the same answer.Combine:
v = sum_{k in E} w_k = <sk_y, H(l)> + sum e_k mod q; decodemu' = sum y c - round_q0(v)as in the MCFE.
Why the noise: without e_k a partial decryption is an exact linear
function of the server’s share, so n0 + m0 partial decryptions of one
server reveal its share and, with t shares, sk_y; even the combined
values <sk_y, H(l)> over n0 + m0 labels reveal sk_y. With the noise,
a server’s answers are LWE samples (H(l), <lambda_k sk_y^(k), H(l)> + e_k).
If fewer than t - 1 shares are corrupted the share is uniform; once
t - 1 are, an honest answer is equivalent to an LWE sample in sk_y,
whose last m0 coordinates are small (Gaussian of width sigma_t) rather
than uniform. The combined values are LWE samples in sk_y as well. This is
an LWE-based (computational) argument for dimension n0 + m0, modulus
q and width sigma_f; it is our own and not covered by the cited
papers, which is why the scheme’s status is experimental.
Noise is derandomised because fresh noise per request would let a combiner
average many answers to the same (l, E). Answers for different enrolled
sets containing server k carry independent noise; since the Lagrange
coefficients are small rationals, r such sets allow averaging that
reduces the noise by at most sqrt(r).
Sharing (config entry sharing; default "replicated" for t < s and
"shamir" for t = s): the argument above takes one answer carrying
<sk_y, H(l)>, with multiplier 1, per label and enrolled set. With
"shamir" and t < s (versions <= 0.3.0) that is not what a coalition
C of t - 1 servers sees: an honest answer is computable from C’s
shares up to M_k <sk_y, H(l)> + e_k,
M_k = lambda_k^E prod_{j in C} (k - j) / (0 - j), and several honest
answers of one enrolled set carry multipliers other than 0 and 1 (rationals
r_k / d_k, or integers such as -3 and 4). Then
d_k (w_k - known) - r_k v, with v the combined value, is the small
integer d_k e_k - r_k sum_E e_j: the answers fix the noise terms up to
multiples of D M (D the common denominator; sum_E e_j modulo
D), and v is an LWE sample whose error is D |M|_2 times narrower
than the sigma_f of one answer of multiplier 1: sqrt(10) for
s = 3, t = 2, C = {2}, E = {1, 3} (M = (3/4, 1/4)), up to 5
for s = 3, t = 2 and 79 for s = 5, t = 3 per enrolled set, and 9.1 and
869 over all enrolled sets of one label. This is not a break, but the argument
does not cover it. "replicated" (see
share_replicated()) makes every M_k
0 or 1, with one honest answer of multiplier 1 per enrolled set; the other
answers are computable up to their own noise. sigma_f and q are
unchanged. With t = s both are as described. An explicit "shamir" with
t < s is refused under the standard profile and draws an
FESecurityWarning under toy (this scheme caches no parameters).
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, with the
partial decryptions of the replicated key share of the expanded pieces; the
key shares hide sk_y computationally (SHAKE-256 keyed by the seeds as a
PRF) rather than perfectly.
Statistical smudging in the usual sense (noise hiding a small ciphertext
error) does not apply, because these ciphertexts carry no LWE error, only
rounding. sigma_f is the largest width that keeps decryption correct:
(q0 / q) * s * 12 sigma_f <= (n Y + 1) / 2 uses the half of the error
budget that pyfe4ai.primitives.lattice.lwe_utils.abmpsw20_lwe_parameters() reserves
for threshold decryption, which gives a noise-to-modulus ratio of roughly
1 / (q0 s).
- pyfe4ai.schemes.mcfe.lwe_threshold.flooding_sigma(params, n_slots, bound_y, s)[source]¶
Largest
sigma_fwith(q0 / q) * s * c * sigma_f <= (n Y + 1) / 2.
- class pyfe4ai.schemes.mcfe.lwe_threshold.ThresholdMCFELWEKeyGenerator(config, **kwargs)[source]¶
Bases:
MCFELWEKeyGeneratorKey generator for threshold ABMPSW20 LWE MCFE.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the key generator.
- Parameters:
config (
dict) – As forMCFELWEKeyGenerator, pluss(number of decryption servers),t(threshold),lst_sidandsharing("replicated", the default fort < s,"shamir", the default fort = s, or the opt-in"replicated-seeded"; seeresolve_sharing()).flooding_accountingmust be the default: the flooding here is part of an LWE argument, not a statistical step that a KL bound could account for.- Return type:
None
- get_public_parameters()[source]¶
Return the MCFE public parameters plus
s,t,lst_sid,sigma_f,sharing,threshold_constructionandsecurity_profile.- Return type:
- get_decryption_keys(sid, **kwargs)[source]¶
Return server sid’s share of
sk_yfor the given weights.All servers asking for the same fusion weights receive shares of one and the same sharing; the key is label-independent (a
labelin the credentials is ignored).- Parameters:
sid (
str) – Decryption-server identifier.**kwargs – Must include
credentialswithfusion_weight.
- Return type:
- Returns:
Dict with
sk_y(this server’s share),sid,lst_sid,weights_id(digest of the weights) andnoise_key(this server’s secret key for deriving flooding noise), orNoneif sid is unknown. With replicated sharingsk_yis replaced bysharingandpieces([{"set": T, "share": {"sk_y": ...}}]); with"replicated-seeded"bysharing,pieces(the correction piece, or nothing),seedsandlayout.
- class pyfe4ai.schemes.mcfe.lwe_threshold.ThresholdMCFELWE(config, **kwargs)[source]¶
Bases:
MCFELWECrypto operations for threshold ABMPSW20 LWE MCFE.
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)
Compute this server’s noise-flooded partial decryption.
- Parameters:
dict_ct (
dict) – Mapping of client IDs to ciphertexts under one label.credentials (
dict) – Dict withfusion_weight(the weights the key was derived for) andlabel.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,label,sidandlst_sid_enrolled.- Raises:
FESchemeError – If the enrolled set is invalid or the weights are not those the key share was derived for.
- 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 clients’ ndarray ciphertexts.
- Parameters:
dict_ndarray_ct (
dict) – Mapping of client IDs to ciphertext ndarray lists produced byencrypt_lst_ndarray()undercredentials["label"].credentials (
dict) – Dict withfusion_weightandlabel.dk (
dict) – This server’s key share.lst_sid_enrolled (
list) – The servers whose shares will be combined.
- Return type:
- decrypt_lst_ndarray_ct(dict_ndarray_ct, **kwargs)[source]¶
Combine element-wise partial decryptions from at least
tservers.- Parameters:
dict_ndarray_ct (
dict) – Mapping of server ids to the outputs ofcompute_lst_ndarray_ct().- 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).- Return type:
Ring-LWE¶
Ring-LWE identity-based multi-client inner-product FE.
pyfe4ai.schemes.mife.ring_lwe.type: identity-based multi-client encryption (MCFE, Ring-LWE, random oracle)
setting: R_q = Z_q[x] / (x^ring_n + 1)
Construction (MIFE notation; H(u'_i, l) in R_q^eta is a hash keyed by the
client secret u'_i):
Setup: MIFE setup plus a secret
u'_iper client.Encrypt:
ct_{i,l} = MIFE.Enc(msk_i, x_i + H(u'_i, l)), i.e. the slot encryptsx_i + u_i + H(u'_i, l).KeyGen: for weights
yand labell: the MIFE key({sk_{i,y}}, z)withz'' = z + sum_i y_i H(u'_i, l).Decrypt:
MIFE.Decwithz''.
Functional keys are bound to a label (identity-based MCFE): a key for
(y, l) only decrypts ciphertexts produced under l. Each client must
encrypt at most once per label. The ndarray helpers pack values into the
ring_n coefficients of one ciphertext per chunk; chunk c of label
l uses H(u'_i, (l, c)), and a key for n_chunks chunks carries one
z'' per chunk. H is SHAKE-256 over the client secret and the encoded
label (hash_to_zq_vector()), mapped to
Z_q as the key generator’s label_hash says (a public parameter;
LABEL_HASHES): by default rejection
sampling mod q from SHAKE128 ("shake128-rejection": exactly uniform,
fewer XOF bytes). Versions <= 0.3.0 reduced bits(q) + 128-bit SHAKE-256
blocks ("shake256-wide", still selectable), so their ciphertexts and
key terms differ. The mode is bound to everything that depends on it: the
public parameters, the clients’ encryption keys, every functional key (and
threshold key share), every single ciphertext and every ndarray layer
(label_hash: the code of LABEL_HASH_CODES,
once per layer dict, not per chunk; covered by the threshold variant’s
tags); a missing field means "shake256-wide" (the
data of versions <= 0.3.0, which stay readable), and encryption with a key
of another mode, or decryption of ciphertexts or with keys of another mode,
raises instead of returning wrong sums. Versions <= 0.2.0 masked inputs with
a small public label scalar.
- pyfe4ai.schemes.mcfe.ring_lwe.label_pad(u_prime, label, q, eta, ring_n, mode)[source]¶
Return
H(u'_i, label)asetarows ofring_ncoefficients mod q (mode: thelabel_hashpublic parameter; expanded by the backend’shash_to_zq(), natively when the extension has a context for(q, ring_n)).
- pyfe4ai.schemes.mcfe.ring_lwe.label_pad_material(u_prime, label, mode)[source]¶
The hash input of
label_pad()(the native kernels expand it themselves).
- pyfe4ai.schemes.mcfe.ring_lwe.chunk_key_index(layer, j, n_keys)[source]¶
Index of the chunk key for chunk j of an ndarray ciphertext layer.
- Raises:
FESchemeError – Unless the layer’s
first_chunkis a non-negative integer and all its chunks have a key among the n_keys.- Parameters:
- Return type:
- class pyfe4ai.schemes.mcfe.ring_lwe.MCFERingLWEKeyGenerator(config, **kwargs)[source]¶
Bases:
MIFERingLWEKeyGeneratorKey generator for BMMS21 identity-based MCFE over Ring-LWE.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
As
MIFERingLWEKeyGenerator, pluslabel_hash(one ofLABEL_HASHES, default"shake128-rejection";"shake256-wide"for the ciphertexts of versions <= 0.3.0), published in the public parameters.- Parameters:
config (
dict)- Return type:
None
- get_private_keys(nid, **kwargs)[source]¶
Return client nid’s keys: MIFE
pk,uand the label-pad secretu_prime.
- get_decryption_keys(sid, **kwargs)[source]¶
Derive the key for weights
yand labell(identity-based).- Parameters:
sid (
str) – Session identifier (unused).**kwargs –
credentialswithfusion_weight,labeland optionallyn_chunks(number of ndarray chunks to cover).
- Return type:
- Returns:
Dict with
sk_y(per-slot MIFE keys),z(pad term of the label itself),z_chunks(pad terms of chunks0..n_chunks-1) andlabel.
- class pyfe4ai.schemes.mcfe.ring_lwe.RingLWEPrecomputation(owner, label, shapes, first_chunks, native=None, rows=None)[source]¶
Bases:
OneTimePrecomputationOffline phase of
MCFERingLWE.encrypt_lst_ndarray()for one label and fixed layer shapes (seeMCFERingLWE.precompute()). Secret and one-time (pyfe4ai.core.one_time).
- class pyfe4ai.schemes.mcfe.ring_lwe.MCFERingLWE(config, **kwargs)[source]¶
Bases:
MIFERingLWECrypto operations for BMMS21 identity-based MCFE over Ring-LWE.
- Parameters:
config (
dict)
- property label_hash: str¶
The
label_hashpublic parameter (seepp_label_hash()).
- encrypt(matrix_pt, label)[source]¶
Encrypt this client’s matrix under label (once per client and label).
- decrypt(dct_ct, dk, fusion_weight, label)[source]¶
Recover the batch of inner products for a key bound to label.
- Raises:
FESchemeError – If the key was derived for another label or the ciphertexts do not cover exactly the key’s clients.
- Parameters:
- static key_chunks(lst_ndarray_ct)[source]¶
Number of chunk keys needed for the output of
encrypt_lst_ndarray().
- encrypt_lst_ndarray(lst_ndarray, **kwargs)[source]¶
Pack ndarrays into
ring_n-coefficient chunks (eta = 1).Chunk
c(counted across all layers) is encrypted under the chunk label(label, c), so each chunk has its own label pad.- Parameters:
lst_ndarray (
list) – The layers.**kwargs –
label(required) and optionallyprecomputed, the result ofprecompute()for this label and these layer shapes (the online phase; one-time).
- Return type:
- Returns:
One dict per layer with
shape,first_chunkandchunks.
- precompute(label, shapes)[source]¶
Offline phase of
encrypt_lst_ndarray()for layers of these shapes.Everything of the ciphertexts but the plaintext term – label pads, Gaussian samples,
pk r + e_0 + Delta (u + H)anda r + eper chunk – is computed now; the online phase,encrypt_lst_ndarray(layers, label=label, precomputed=...), only addsDelta x. The ciphertexts are those of a directencrypt_lst_ndarray()with the same samples.The result determines the encryption randomness: keep it as secret as the key, and use it once (an MCFE client encrypts at most once per label). The use is atomic, and
copy.copy,copy.deepcopy, pickling and use in another process (afteros.fork()) are refused (pyfe4ai.core.one_time). Natively each chunk is overwritten in memory when used; on the Python path the references to the chunks are dropped (Python integers cannot be wiped).- Parameters:
label (
str) – The label the layers will be encrypted under.shapes – One shape (tuple of ints, or an int for a 1-D layer) per layer.
- Return type:
Ring-LWE Decentralized¶
Ring-LWE identity-based decentralised multi-client inner-product FE.
pyfe4ai.schemes.mcfe.ring_lwe, decentralised with the compiler ofH_i(l) with sum_i H_i(l) = 0.type: identity-based decentralised multi-client encryption (Ring-LWE, ROM)
setting: R_q = Z_q[x] / (x^ring_n + 1)
Encryption and decryption are those of the identity-based MCFE. There is no
authority for functional keys: for weights y and label l client i
publishes
its per-slot IPFE key
sum_row y_{i,row} s_{i,row}(part of the key), andz_i = sum_row y_{i,row} (u_{i,row} + H(u'_i, l)_row) + M_i(l, y),
where the GDSum masks M_i(l, y) = sum_{j > i} PRF(K_ij, l, y) - sum_{j < i}
PRF(K_ji, l, y) cancel over all clients, so sum_i z_i is the pad term
z'' of the key and each z_i alone is uniformly masked. Every client
must publish a share; shares for other weights or labels do not combine.
Deviation: the PKC version of BMMS21 states GDSum only through its property;
this module realises it with pairwise PRF masks (SHAKE-256 keyed by
pairwise secrets K_ij, which this prototype samples in-process, as it
does all other keys; a deployment would agree on them by key exchange),
hashed to Z_q like the label pads (the label_hash public parameter).
Versions <= 0.2.0 used an ad-hoc label scalar instead.
- pyfe4ai.schemes.mcfe.ring_lwe_decentralized.gdsum_material(pair_key, context, mode)[source]¶
The hash input of one PRF term of
gdsum_mask()(mode: thelabel_hashpublic parameter).
- pyfe4ai.schemes.mcfe.ring_lwe_decentralized.gdsum_mask(nid, pair_keys, lst_nid, context, q, ring_n, mode)[source]¶
Return
M_i(context) = sum_{j>i} PRF(K_ij) - sum_{j<i} PRF(K_ji)in Z_q^ring_n (the PRF:hash_to_zq_vector()with thelabel_hashmode).
- class pyfe4ai.schemes.mcfe.ring_lwe_decentralized.DecentralizedMCFERingLWEKeyGenerator(config, **kwargs)[source]¶
Bases:
MCFERingLWEKeyGeneratorSets up identity-based MCFE client keys and pairwise GDSum secrets.
- Parameters:
config (
dict)
- class pyfe4ai.schemes.mcfe.ring_lwe_decentralized.DecentralizedMCFERingLWE(config, **kwargs)[source]¶
Bases:
MCFERingLWEIdentity-based MCFE encryption / decryption with GDSum key derivation.
- Parameters:
config (
dict)
Publish this client’s key share for weights
yand labell.- Parameters:
- Return type:
- Returns:
Dict with
sk_y(this slot’s IPFE key),zandz_chunks(masked pad terms),label,nidandweights_id.- Raises:
FEKeyError – Without the client’s keys, or if
uorshas fewer thanetarows ofring_nvalues.
Sum every client’s share into the identity-based MCFE key.
- Raises:
FESchemeError – If a client’s share is missing or the shares were derived for different weights, labels or chunk counts.
FEKeyError – If a share’s
sk_y,zor az_chunksentry is notring_nvalues.
- Parameters:
dct_dk_shares (
dict)- Return type:
Ring-LWE Threshold¶
Threshold Ring-LWE identity-based multi-client inner-product FE: the BMMS21 identity-based MCFE with a Shamir-shared functional key and statistically flooded partial decryptions.
pyfe4ai.schemes.mcfe.ring_lwe (BMMS21, PKC 2022, Section 6).type: identity-based multi-client encryption, threshold functional decryption
setting: R_q = Z_q[x] / (x^ring_n + 1), q a product of NTT-friendly primes (default) or an NTT-friendly prime
Defaults (both security profiles, standard and toy): binary
ciphertexts, key shares and partial decryptions (ct_format="binary"), an
RNS modulus (ring_modulus="rns") and key shares split into a weight part
per weights and a label part per label (key_shares="split"; whole key
shares for the replicated-seeded sharing). Each is selectable as before –
ct_format="json", ring_modulus="prime", key_shares="full" or
key_part="full" per call – and public parameters, parameter records,
ciphertexts and key shares of the earlier defaults (no ct_format: JSON; no
q_factors: a prime) keep working where they did. The defaults change no
construction id; their parameter records are param-rns.json (a folder’s
param.json is the prime’s). What does not match fails closed: a server
refuses ciphertexts and key shares of the other format or of other public
parameters (pp_binding), and a label part without its weight part
(ThresholdMCFERingLWE.add_weight_key_share()); a combiner refuses partial
decryptions of the other format (partial decryptions carry no digest of the
public parameters; servers are honest-but-curious, see below) and partial
decryptions of different sharings of one key (MixedSharingsError).
Construction (notation of pyfe4ai.schemes.mife.ring_lwe):
KeyGen: the identity-based key
({sk_i}, z'', z''_chunks)for(y, l); every coefficient is Shamir-shared with thresholdtoverZ_q.ShareDec: server
kof an enrolled setEpublishes, coefficient-wise,w_k = lambda_k (sum_i ct1_i sk_i^(k) + Delta z''^(k)) + e_kandc_y = sum_i sum_row y_row ct0_{i,row}.Combine:
c_y - sum_{k in E} w_k = Delta <x, y> + noise - sum e_k; decode.
Flooding: the exact key part differs from what the result and the
ciphertexts determine by the decryption noise
sum_row y_row (e'_row r + e_row) - e sum_row y_row s_row, bounded per
coefficient by B = n eta Y (2 ring_n 144 sigma1 sigma2 + 12 sigma3);
flooding noise e_k per coefficient smudges it (statistical argument;
uniform by default, Gaussian D_{Z, 2^kappa B} selectable, see below),
with q enlarged so that s flooding terms still decode.
Ciphertexts (and ndarray chunks) carry one HMAC tag per server over the
(chunk) label and the ciphertext (see “Ciphertext format” below); servers
only decrypt ciphertexts tagged for them under the label they serve, since
w_k is linear in ct1.
The noise is seeded with a per-server key (fixed per weights and label),
lambda_k, the label or chunk, the ct1 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
ct1 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 ct1 reveal together: averaging them would take about
2^(2 stat_param) partial decryptions with Gaussian noise (see below
for the uniform default), but other ways of combining these noisy linear
equations are not ruled out. The threshold layer is our own design.
Flooding distribution (key generator config flooding, a public
parameter):
"uniform"(default):e_kuniform on[-2^tau, 2^tau)withtau = bits(B) + kappa, read from the same seeded XOF (uniform_flooding_poly()). By the smudging lemma (Asharov et al., EUROCRYPT 2012, Lemma 2.1) the statistical distance per coefficient is|E| / 2^(tau + 1) < 2^-(kappa + 1), the per-sample bound the Gaussian meets as well;qholdss 2^tauinstead ofs 12 sigma_f(about 3 bits less), correctness needs no tail cut, and the sampler is exact and integer-only (nompfr). Unlike the Gaussian it has no margin beyond that bound: overNcoefficients the distance isN 2^-(kappa + 1)(the Gaussian’s issqrt(N)times its per-sample bound), and tellingEfromE + 1by repetition takes about2^(tau + 1)partial decryptions instead of2^(2 kappa) B^2. Parameter recordsbmms21-ibmcfe-shamir-smudging-uniform/v1; public parametersfloodingandflood_bits."gaussian":e_k <- D_{Z, 2^kappa B}as described above, the only flooding of versions <= 0.3.0 (public parametersigma_f; public parameters withoutfloodingare read as this), drawn by the pinnedFLOODING_SAMPLERatbits(sigma_f) + 320bits (recordsbmms21-ibmcfe-shamir-smudging/v2; thev1records of earlier versions are regenerated).
Decryption key shares record the flooding they were issued for (flooding,
with noise_version and noise_sampler); a server refuses a share of
another flooding than its public parameters’, which would answer a request
with a second, independent noise.
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 smudging argument 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 per coefficient,
M = lambda_k^E prod_{j in C} (k - j) / (0 - j), a non-integer rational
r / d for some (C, E, k), and d times the difference reveals
nu mod d coefficient by coefficient, whatever sigma_f. Fewer corrupted
servers, or none, can suffice (the aggregator alone reads nu modulo
(s - 1)! for t = 1), and combining several enrolled sets raises d
(144 for s = 5, t = 3); integer M reach 4 for s = 3, t = 2, which
multiplies the statistical distance of the flooding by 4. "replicated"
(see share_replicated()) makes M 0
or 1, so the argument holds as stated with q and ring_n unchanged; a
server holds C(s - 1, t - 1) key pieces instead of one share (dealt and
added up natively where the extension is built, with the values of the
Python code). With t = s both are sound. An explicit
"shamir" with t < s is refused under the standard profile and
draws an FESecurityWarning under toy; the replicated sharing has its
own parameter records (threshold_construction
bmms21-ibmcfe-replicated-smudging-uniform/v1, or
bmms21-ibmcfe-replicated-smudging/v1 with the Gaussian flooding), 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 (the size of a Shamir key share), 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. It has its own records
(bmms21-ibmcfe-replicated-seeded-smudging-uniform/v1, or
bmms21-ibmcfe-replicated-seeded-smudging/v1 with the Gaussian flooding).
flooding_accounting = "kl" with max_partial_decryptions = Q (opt-in,
t = s with the Shamir sharing and the Gaussian flooding only – the
flooding defaults to "gaussian" under "kl" and "uniform" is
refused there –, see pyfe4ai.schemes.threshold.kl) sizes the
Gaussian noise for a run in which every server produces at most Q partial
decryptions (one per ciphertext or chunk): KL <= 2^-(stat_param + 1)
for the N = s Q ring_n flooded coefficients, i.e.
sigma_f = B sqrt(N 2^stat_param), with a tail cut derived from N (16
sigma_f at N = 2^53), instead of a statistical distance
2^-stat_param per coefficient. 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; averaging the noise away would need about
N 2^stat_param requests. Under standard (the benchmark’s three
clients, X = 100, Y = 1, s = t = 3) N = 2^53 gives a q of
178 bits instead of 215 with the per-sample Gaussian flooding (212 with the
uniform default), at the same ring_n 8192. These parameters
have their own record (threshold_construction
bmms21-ibmcfe-shamir-smudging-kl/v2).
Ciphertext format (key generator config ct_format, a public parameter;
CT_FORMATS): "json" (decimal strings, tags over the JSON form,
as in versions <= 0.3.0; public parameters without ct_format) or
"binary" (the default): format 2 of
encode_threshold_ciphertext() – the
ct0 rows truncated to the batch length k (decryption reads them
coefficient-wise), ct1 whole, every value a ceil(bits(q)/8)-byte
word strictly below q – and binary partial decryptions of the first
k coefficients (about 71 and 54 bytes per element at 215 bits instead
of 225 and 222). Tags (v2) are HMAC-SHA256 under the client-server key of
binary_tag_message(): the public parameters’ digest
(pp_binding() over
tmcfe_ring_lwe_binding_fields()), the
(chunk) label, the ciphertext’s label hash code, k and SHA-256 digests of the ct0 and ct1 bytes; the noise
is seeded with digests of the ct1 bytes (binary_request_digests()).
A format-2 encoding is unique – a value >= q is refused, also under a
valid tag – so no client can re-encode one request to draw a second noise
for the same key part; the first k noise values are those of the full
draw (a prefix of the SHAKE stream for the uniform noise), so answers for
another k agree on their common coefficients. The format is bound to the
key shares (ct_format enters pp_binding where the public parameters
have it; JSON digests are unchanged): a key share is served in one format
only. Under binary public parameters JSON ciphertexts, format 1, v1 tags and
JSON partial decryptions are refused, and under JSON ones every binary form.
A client, server or combiner fixes its format when it is made and refuses to
work once the bound fields or the format of its public parameters (the
caller’s dict) have changed: one key part, one derivation.
Key shares per round (key generator config key_shares,
KEY_SHARES; "split" by default): a key share for (y, l) is the
label-independent sk_y and the label’s terms z''_l (and of its chunks).
ThresholdMCFERingLWEKeyGenerator.get_weight_key_share() issues the
shares of sk_y once per weights (servers keep them:
ThresholdMCFERingLWE.add_weight_key_share()), and
get_decryption_keys (key_part="label", the default with split key
shares) the shares of the label terms only (label_term=False leaves out
the label’s own z, which only single ciphertexts need): two of six
polynomials per round for two chunks; per round a server needs nothing else
from the key generator. key_shares="full" (or key_part="full" per
call) issues whole key shares, as in versions <= 0.3.0; the replicated-seeded
sharing has only these. Both
parts are of one sharing (Shamir points or replicated sets) and carry a
share_set_id; a server combines a label part only with the weight part of
the same weights, share set, sharing, binding, flooding, noise derivation and
MAC keys. A linear sharing of sk_y 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 full key share;
NTT(sk_y) of a kept weight part is computed once. With binary public
parameters every polynomial of the Shamir and replicated key shares is bytes
(ceil(bits(q)/8)-byte words, strictly below q; the seeded sharing keeps
decimal strings). Key material mixing both kinds is dealt as values; key shares
whose polynomials are neither bytes nor lists or tuples (e.g. memoryview),
are of another length, hold values other than ints, mpz and decimal
strings, or are replicated pieces mixing kinds, are refused (FEKeyError).
The key generator keeps the sharings of sk_y for the last
WEIGHT_SHARINGS_KEPT weights: weights issued again after that come
with a new share set – their weight part and every label part issued from
then on – which servers holding the old weight part refuse until given the
new one. A label part is of the weights’ share set current when it is issued
(one made or cached while another thread evicted that sharing is made again
for the new set) and is kept with that sharing, and dropped with it; per
sharing the label parts of the last LABEL_PARTS_KEPT labels are kept
(a label asked for again later gets a new sharing of its terms). Every sharing
the key generator issues – whole key shares, or the label parts of one label
– has a random sharing_id, which the servers’ partial decryptions carry:
partials of two sharings of one key would decode to a wrong value inside the
decoding bound rather than fail, so combiners refuse a mix
(MixedSharingsError; fetch the label parts again for all servers).
The two sharings’ answers of one server differ by its share of a sharing of
zero, which t - 1 servers compute from their own parts: a second sharing
reveals nothing. 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.
The threshold objects (key generator, clients, servers) are not portable across versions of pyfe4ai: a state pickled by another version is refused when loaded (create the object again).
Modulus (key generator config ring_modulus): "rns" (the default):
q the product of NTT primes below 2^62, at least
MIN_RNS_FACTORS (three: the native threshold kernels run on the
256-bit ring, also for the smaller q of the toy profile) and as many
as make each at most 60 bits (four for the 212 to 218 bits of the
standard profile, about 54 bits each), but four up to 248 bits, the most
the native ring holds (above, five or more on the Python path, with a
warning – "prime" stays native below 2^253;
rns_ntt_modulus_for()), of the bit length
of the prime and above the same bound, published as q_factors (bound to
the key shares); or "prime", an NTT prime (the modulus of earlier
versions). Every factor must exceed s (check_rns_servers()):
Shamir sharing over Z_q is then one field sharing per factor (CRT), with
units for points and differences; the replicated sharing, the flooding (an
argument over the integers), the label hash (exact rejection sampling on
[0, q)) and the decoding bound do not depend on the factorisation (see
RNS modulus for the Ring-LWE schemes: security review). The parameter record carries ring_modulus
and q_factors (file param-rns.json, beside the prime’s
param.json); the construction ids are those of the prime modulus. The
native backend runs the 256-bit kernels with one 64-bit NTT per factor
(NTT-domain values are residue tuples), with the values of the Python code
on the composite q.
With the native backend (pyfe4ai.backend; moduli below
2^253, which includes the flooded q of the standard profile) the MCFE
layer, the Shamir sharing of the key, the parsing, digests and NTT-domain
products of a partial decryption and the combination run natively, with
the values of the Python code; the uniform flooding noise is drawn natively
too (uniform_flooding_poly()’s values), the Gaussian by
discrete_gaussian_bigint() (natively too,
with the same samples).
- pyfe4ai.schemes.mcfe.ring_lwe_threshold.MIN_RNS_FACTORS = 3¶
Fewest factors of an RNS modulus of the threshold scheme. Its native kernels run on the 256-bit ring (two factors below
2^124would get the two-prime context of the MIFE / MCFE prototype, which has none of them).
- class pyfe4ai.schemes.mcfe.ring_lwe_threshold.ThresholdMCFERingLWEKeyGenerator(config, **kwargs)[source]¶
Bases:
MCFERingLWEKeyGeneratorKey generator for threshold BMMS21 identity-based MCFE over Ring-LWE.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the key generator.
- Parameters:
config (
dict) – As forMCFERingLWEKeyGenerator, pluss,t,lst_sid,stat_param(kappaof the flooding),flooding(one ofFLOODINGS, default"uniform","gaussian"for the partial decryptions of versions <= 0.3.0 and under"kl"; see the module docstring),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, Gaussian flooding; see the module docstring);ct_format(one ofCT_FORMATS, default"binary", published in the public parameters, whose clients and servers then use format 2;"json"for the format of versions <= 0.3.0);ring_modulus:"rns"(default): the floodedqis a product of at leastMIN_RNS_FACTORSNTT primes below 2^62 (four for the 212 to 218 bits of thestandardprofile, four up to 248 bits;rns_ntt_modulus_for()), each aboves, published asq_factors, with the parameter recordparam-rns.json; or"prime", an NTT prime (recordparam.json, as in earlier versions);key_shares(one ofKEY_SHARES):"split", the default for the Shamir and replicated sharings, or"full", the default (and only choice) for"replicated-seeded"– whatget_decryption_keys()issues without akey_part.- Raises:
FEValidationError – For an unknown
ct_format,ring_modulusorkey_shares, orkey_shares="split"with the"replicated-seeded"sharing.- Return type:
None
- setup()[source]¶
MCFE setup and the client-server MAC keys; forgets previously shared keys.
- Return type:
- get_private_keys(nid, **kwargs)[source]¶
Client nid’s MCFE keys plus
mac_keys({sid: key}) for the ciphertext tags.
- get_public_parameters()[source]¶
MCFE public parameters plus
s,t,lst_sid,flooding,flood_bits(uniform) orsigma_f(Gaussian),sharing,threshold_construction,security_profile,ct_format(only"binary"; absent means"json") and, 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 key for
(y, label, n_chunks).- Parameters:
sid (
str) – The server.**kwargs –
credentialswithfusion_weight,label, optionallyn_chunksandkey_part:"label"– only the label’s termszandz_chunks, the per-round part; the label-independentsk_ycomes once per weights fromget_weight_key_share()– or"full", a whole key share (default:"label"if the config’skey_sharesis"split", the default but for the replicated-seeded sharing, else"full") and, with"label",label_term(defaultTrue;Falseleaves outz, which only single ciphertexts of the label itself need).
- Return type:
- Returns:
Dict with shares
sk_y,z,z_chunksandlabel,sid,lst_sid,weights_id,noise_keyandmac_keys({nid: key}), orNoneif sid is unknown. With replicated sharing the shares are replaced bysharingandpieces([{"set": T, "share": {"sk_y", "z", "z_chunks"}}]); with"replicated-seeded"piecesholds the correction piece (or nothing), andseeds([{"set": T, "seed": hex}]) andlayoutgive the others. A label part haspart = "label"andshare_set_id(that of the weight part it completes, the weights’ current share set when it is issued) and nosk_y; label parts are kept with that sharing ofsk_y(later calls return the same shares, for the lastLABEL_PARTS_KEPTlabels) and dropped with it. Every key share part of one sharing has itssharing_id(the partial decryptions carry it; combiners refuse partials of different sharings). With binary public parameters (ct_format="binary") every polynomial of the Shamir and replicated sharings is bytes (ceil(bits(q)/8)-byte words, the same values).- Raises:
FEValidationError – For an unknown
key_part, a label part with neitherznor chunk keys, orkey_part="label"with the"replicated-seeded"sharing.
Return server sid’s share of the label-independent part of the key for the weights
y: the shares ofsk_y({nid: sk_{i,y}}, or replicated pieces of it), issued once per weights (later calls return the same shares), withpart = "weights",share_set_idand the fields ofget_decryption_keys(). Servers keep it (ThresholdMCFERingLWE.add_weight_key_share()); per label they then need onlyget_decryption_keys(..., key_part="label").The shares of
sk_yserve every label: a linear sharing reused with fresh sharings of the label terms is a sharing of each round’s key, andt - 1servers’ shares stay independent of the keys (each sharing has its own randomness); the flooding noise is seeded as before.- Parameters:
sid (
str) – The server.**kwargs –
credentialswithfusion_weight.
- Raises:
FEValidationError – With the
"replicated-seeded"sharing.- Return type:
- class pyfe4ai.schemes.mcfe.ring_lwe_threshold.ThresholdMCFERingLWE(config, **kwargs)[source]¶
Bases:
MCFERingLWECrypto operations for threshold BMMS21 identity-based MCFE over Ring-LWE.
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
MCFERingLWE; underflooding_accounting = "kl"a server needspartial_budget, thePartialDecryptionBudgetthat its deployment keeps for it;legacy_shamiras in the class docstring. The ciphertext format is that of the public parameters (ct_format,pp_ct_format()); act_formatin config must name the same.- Raises:
FEValidationError – For a
ct_formatother than the public parameters’.- Parameters:
config (
dict)- Return type:
None
- encrypt(matrix_pt, label)[source]¶
MCFE encryption under label, tagged for every server (format 2 for binary public parameters).
- encrypt_lst_ndarray(lst_ndarray, **kwargs)[source]¶
Packed ndarray encryption; every chunk is tagged under its chunk label.
- WEIGHT_SHARES_KEPT = 16¶
Weight key shares a server keeps (
add_weight_key_share()).
Keep this server’s weight key share (
ThresholdMCFERingLWEKeyGenerator.get_weight_key_share()) for the label key shares (key_part="label") of the same weights and share set; the lastWEIGHT_SHARES_KEPTare kept.NTT(sk_y)of the shares is cached across labels.- Raises:
FEKeyError – Unless share is a weight key share of this server.
- Parameters:
share (
dict)- Return type:
Whether this server keeps the weight key share that the label key share dk completes (same weights and share set):
Falsemeansadd_weight_key_share()first. A full key share needs none.
Compute this server’s flooded partial decryption.
- Parameters:
- Return type:
- Returns:
Dict with
w,c_y,k,ct_digest,sid,lst_sid_enrolledandsharing_id(that of the key share,Nonefor key shares without one).- Raises:
FESchemeError – Under
flooding_accounting = "kl"also when 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 chunk of the clients’ packed 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 chunk-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:
Paillier¶
DCR (Paillier) based multi-client inner-product FE.
type: secret-key multi-client encryption (MCFE, DCR, random oracle)
setting: Integer based (Z_{N^2}^*, N = p * q with safe primes p, q)
Construction (one coordinate j of client i’s vector is one slot; n below
is the total number of slots):
Setup: safe-prime modulus
N;H: labels -> Z_{N^2}^*full-domain hash.KeyGen:
s_{i,j} <- D_{Z, sigma}withsigma > sqrt(kappa + 2 n log2(2X)) * N^{5/2}(adaptive setting).Encrypt:
c_{i,j} = (1 + N)^{x_{i,j}} * H(l)^{s_{i,j}} mod N^2.KeyDer:
k = sum y_{i,j} s_{i,j}over the integers (one aggregated key).Decrypt:
C = prod c_{i,j}^{y_{i,j}} * H(l)^{-k};<x, y> = (C - 1 mod N^2) / N.
Correctness needs |x|, |y| < sqrt(N / 2n) (Fig. 3); the result is
returned centred modulo N, so signed inputs are supported. Each client
slot must encrypt at most once per label (one-IND security): two ciphertexts
of one slot under one label reveal (1 + N)^{x - x'}. The ndarray helpers
therefore derive a distinct label per element.
Implementation notes: H is SHAKE-256 expanded to bits(N^2) + 128 bits
(the paper’s implementation uses counter-mode SHA-256); D_{Z, sigma} is
a rounded continuous Gaussian computed at bits(sigma) + 320 bits of
precision (pyfe4ai.primitives.sampling.discrete_gaussian_bigint()).
bit_length is the size of each prime and is far below any real security
level by default.
- pyfe4ai.schemes.mcfe.paillier.dcr_sigma(n_mod, n_slots, bound_x, kappa)[source]¶
Return an integer
sigma > sqrt(kappa + 2 n log2(2X)) * N^{5/2}.This is the adaptive-security condition of ABMPSW20 Fig. 3 / Lemma 27 (the selective one drops the
2 n log2(2X)term).
- class pyfe4ai.schemes.mcfe.paillier.MCFEPaillierKeyGenerator(config, **kwargs)[source]¶
Bases:
IPFEAbsKeyGenerator,ParameterCacheMixinKey generator for DCR-based multi-client inner-product FE (ABMPSW20 Fig. 3).
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the key generator.
- Parameters:
config (
dict) – Scheme configuration. Recognised keys:n(number of clients),lst_nid,eta(slots per client),bit_length(bits of each safe prime),bound_x,bound_y,stat_param(statistical parameterkappa),sec_param(recorded only).- Return type:
None
- get_private_keys(nid, **kwargs)[source]¶
Return the encryption key
{s}of client nid (Noneif unknown).
- get_decryption_keys(sid, **kwargs)[source]¶
Derive the functional key
k = sum y_{i,j} s_{i,j}(over Z).- Parameters:
sid (
str) – Session identifier (unused).**kwargs – Must include
credentialswithfusion_weight(dict mapping client IDs to integer weight lists).
- Return type:
- Returns:
Dict with
k, a single integer aggregated over all clients.- Raises:
FEKeyError – If credentials are missing.
FEValidationError – If a client id is unknown or a weight is malformed or exceeds
bound_y.
- class pyfe4ai.schemes.mcfe.paillier.MCFEPaillier(config, **kwargs)[source]¶
Bases:
OfflineMCFEMixin,IPFEAbsCryptoCrypto operations for DCR-based multi-client inner-product FE (ABMPSW20 Fig. 3).
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise with
keys.ppand, for clients,keys.sk.- Parameters:
config (
dict)- Return type:
None
- encrypt(lst_pt, label)[source]¶
Encrypt one client’s integer vector under a label.
- Parameters:
- Return type:
- Returns:
Dict with
c(one element ofZ_{N^2}^*per slot).- Raises:
FEKeyError – If public parameters or encryption keys are missing.
FEValidationError – If the plaintext is malformed or out of bound.
- decrypt(dct_ct, dk, fusion_weight, label, term=None)[source]¶
Recover
sum_i <x_i, y_i>from all clients’ ciphertexts under one label.- Parameters:
- Returns:
The inner product (centred modulo
N), orNoneif the result is not a valid decryption (e.g. wrong label or key).- Raises:
FEKeyError – If the key is missing.
FESchemeError – If ciphertexts and weights cover different clients.
- encrypt_lst_ndarray(lst_ndarray, **kwargs)[source]¶
Encrypt ndarrays element-wise (
eta = 1), one derived label per element.
- compute_lst_ndarray_ct(dict_ndarray_ct, **kwargs)[source]¶
Aggregate-decrypt ndarray ciphertexts (delegates to
decrypt_lst_ndarray_ct()).
Paillier Decentralized¶
Decentralised multi-client inner-product FE over DCR.
pyfe4ai.schemes.mcfe.paillier) and MCFE2 = the same constructionN2 (pyfe4ai.schemes.mcfe.decentralize) –type: decentralised multi-client encryption (DMCFE, DCR, random oracle)
setting: Integer based (Z_{N^2}^* for data, Z_{N2^2}^* for key shares)
Encryption and decryption are those of the DCR MCFE. For weights y every
client i publishes an MCFE2 encryption of its contribution
k_i = sum_j y_{i,j} s_{i,j} (an integer: the MCFE1 key cannot be reduced
modulo anything public) under the label H(y); the zero-sum MCFE2 keys make
the product of all clients’ shares decrypt to k = sum_i k_i, the MCFE1
functional key. N2 is sized for |k_i| <= eta * Y * 12 sigma, with
sigma the MCFE1 key width bounded through N < 2^(2 bit_length). Every
client must publish a share. Secure against static corruptions
(Theorem 38). Versions <= 0.2.0 used an ad-hoc label scalar, per-client key
components and brute-forceable keys instead.
- class pyfe4ai.schemes.mcfe.paillier_decentralized.DecentralizedMCFEPaillierKeyGenerator(config, **kwargs)[source]¶
Bases:
MCFEPaillierKeyGeneratorSets up DCR MCFE client keys and zero-sum MCFE2 keys (ABMPSW20 Fig. 14).
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the key generator.
- Parameters:
config (
dict) – As forMCFEPaillierKeyGenerator(n,lst_nid,eta,bit_length,bound_x,bound_y,stat_param).- Return type:
None
- get_public_parameters()[source]¶
DCR MCFE public parameters plus
n2_mod,lst_nidand the bound.- Return type:
- class pyfe4ai.schemes.mcfe.paillier_decentralized.DecentralizedMCFEPaillier(config, **kwargs)[source]¶
Bases:
MCFEPaillierDCR MCFE encryption / decryption with decentralised key derivation.
- Parameters:
config (
dict)
Publish this client’s encrypted contribution
sum_j y_{i,j} s_{i,j}.
Combine every client’s share into the DCR MCFE key
{k}.- Raises:
FESchemeError – If a client’s share is missing or the shares were derived for different weights.
- Parameters:
dct_dk_shares (
dict)- Return type:
FH-Multi-IPE (Pairing)¶
Function-hiding multi-client inner-product FE over pairings.
type: multi-client encryption with function-hiding keys (FH-MCFE)
setting: asymmetric pairing group (default BLS12-381), random oracle
Each client i holds the master key imsk_i of an independent
function-hiding IPFE (pyfe4ai.schemes.mcfe.fh_ipe):
Enc:
[omega]_1 = H1(tag);ct_i = iEnc(imsk_i, [x_hat_i]_1)withx_hat_i = (x_i, omega, 0, 0^eta, 0, 0).KeyGen: fresh
t_1, ..., t_nwithsum t_i = 0;dk_i = iKeyGen(imsk_i, [y_hat_i]_2),y_hat_i = (y_i, t_i, 0, 0^eta, 0, 0).Dec:
prod_i iDec(dk_i, ct_i) = [sum <x_i, y_i> + omega sum t_i]_T = [<x, y>]_T, then a bounded discrete log.
The zero coordinates are the ones the security proof uses (NPS24 Section 2).
Each client’s partial result is masked by omega t_i and the IPFE hides
y_i; each client must encrypt at most once per tag. Symmetric pairing
groups (SS512, …) are rejected because SXDH does not hold there.
Versions <= 0.2.0 masked inputs with a public label scalar instead.
- pyfe4ai.schemes.mcfe.fh_multi_ipe_pairing.vector_dim(eta)[source]¶
Length of the extended vectors
(v, scalar, 0, 0^eta, 0, 0).
- class pyfe4ai.schemes.mcfe.fh_multi_ipe_pairing.FHContext(pp)[source]¶
Bases:
objectDeserialised public parameters shared by the FH (D)MCFE schemes.
- Parameters:
pp (
dict)
- x_hat(x, slot_scalar_elem)[source]¶
[(x, s, 0, 0^eta, 0, 0)]_1with the scalar slot given as a G1 element.
- y_hat(y, slot_scalar_elem)[source]¶
[(y, s, 0, 0^eta, 0, 0)]_2with the scalar slot given as a G2 element.
- class pyfe4ai.schemes.mcfe.fh_multi_ipe_pairing.MCFEFHMultiIPEPairingKeyGenerator(config, **kwargs)[source]¶
Bases:
IPFEAbsKeyGeneratorKey generator for the AGT21b function-hiding MCFE.
- 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). The keyssec_level,u_boundandlabel_modulusof versions <= 0.2.0 are ignored.- Return type:
None
- get_private_keys(nid, **kwargs)[source]¶
Return client nid’s encryption key
{imsk}(Noneif unknown).This is the slot’s FH-IPFE master key without the part only key generation needs. With it a client could strip the outer layer of its own slot of a functional key (outside the model: clients are not corrupted).
- class pyfe4ai.schemes.mcfe.fh_multi_ipe_pairing.MCFEFHMultiIPEPairing(config, **kwargs)[source]¶
Bases:
IPFEAbsCryptoCrypto operations for the AGT21b function-hiding MCFE.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise with
keys.ppand, for clients,keys.sk.- Parameters:
config (
dict)- Return type:
None
- encrypt(lst_pt, label)[source]¶
iEnc(imsk_i, [(x_i, H1(label), 0, ...)]_1); once per client and label.
- decrypt(dct_ct, dk, fusion_weight=None, label=None)[source]¶
log prod_i iDec(dk_i, ct_i);Noneif out of bound (wrong tag or key).fusion_weight and label are accepted for API compatibility and ignored: the key encodes
y(function hiding) and the ciphertexts carryH1(label).- Raises:
FESchemeError – If the ciphertexts do not cover every client of the key.
- Parameters:
- encrypt_lst_ndarray(lst_ndarray, **kwargs)[source]¶
Encrypt ndarrays element-wise (
eta = 1), one derived label per element.
FH-Multi-IPE Decentralized (Pairing)¶
Decentralized function-hiding multi-client inner-product FE over pairings.
pyfe4ai.schemes.mcfe.fh_multi_ipe_pairing); selective securitytype: decentralised multi-client encryption with function-hiding keys
setting: asymmetric pairing group (default BLS12-381), random oracle
Setup samples one FH-IPFE master key imsk_i per client
(pyfe4ai.schemes.mcfe.fh_ipe) and a sharing t~_1 + ... + t~_n = 0:
Enc:
[omega]_1 = H1(tag);ct_i = iEnc(imsk_i, [x_hat_i]_1)withx_hat_i = (x_i, omega t~_i, 0, 0^eta, 0, 0).DKeyGen:
[mu]_2 = H2(tag-f);dk_i = iKeyGen(imsk_i, [y_hat_i]_2)withy_hat_i = (y_i, mu, 0, 0^eta, 0, 0).Dec:
prod_i iDec(dk_i, ct_i) = [<x, y> + omega mu sum t~_i]_T = [<x, y>]_T.
There is no authority for functional keys: each client derives its own
dk_i from tag-f and its own weights y_i only, and keys of all
clients for the same tag-f are needed; together they decrypt
sum_i <x_i, y_i> for the weights each client chose. The proof allows one
key per client and tag-f: name every new weight vector with a fresh
tag_f. A client object refuses a second weight vector under a tag_f it
already used (other instances cannot know about it), and derives the key
randomness from a client secret, tag-f and y_i, so asking again
returns the same key. Each client must encrypt at most once per tag. Earlier
versions used tag-f = (tag_f, H(y)) over all clients’ weights, so a
corrupted client could test guesses of the other clients’ weights; versions
<= 0.2.0 masked inputs with a public label scalar instead.
- class pyfe4ai.schemes.mcfe.fh_multi_ipe_pairing_decentralized.DecentralizedMCFEFHMultiIPEPairingKeyGenerator(config, **kwargs)[source]¶
Bases:
MCFEFHMultiIPEPairingKeyGeneratorSetup of the NPS24 selectively function-hiding DMCFE.
- Parameters:
config (
dict)
- class pyfe4ai.schemes.mcfe.fh_multi_ipe_pairing_decentralized.DecentralizedMCFEFHMultiIPEPairing(config, **kwargs)[source]¶
Bases:
MCFEFHMultiIPEPairingEncryption, key derivation and decryption of the NPS24 FH-DMCFE.
- Parameters:
config (
dict)
- encrypt(lst_pt, label)[source]¶
iEnc(imsk_i, [(x_i, H1(label)^t~_i, 0, ...)]_1); once per client and label.
Derive this client’s key
dk_ifor its weightsy_i(NPS24 DKeyGen).- Parameters:
fusion_weight – This client’s weights (
etaintegers), or a dict of weights in which only this client’s entry is used.tag_f (
str) – Non-empty name of the function; all clients use the same one for one functional key, and a fresh one for every new weight vector (one key per client andtag_f).
- Return type:
- Returns:
Dict with
kandz(the FH-IPFE key),nidandtag_f(nothing derived fromy: keys are function hiding).- Raises:
FESchemeError – If this object already derived a key for other weights under tag_f.
Collect every client’s
dk_iinto the functional key.- Raises:
FESchemeError – If a client’s key is missing or the keys were derived for different function tags. (Keys for different weights under one
tag_fcombine into a key that decrypts to garbage.)- Parameters:
dct_dk_shares (
dict)- Return type:
FH-Multi-IPE Threshold (Pairing)¶
Threshold function-hiding multi-client inner-product FE over pairings.
The AGT21b FH-MCFE of pyfe4ai.schemes.mcfe.fh_multi_ipe_pairing
with its functional key Shamir-shared among s decryption servers, any
t of which decrypt together. Own construction (experimental, no
published security proof):
the authority computes the FH-MCFE key for
yin the exponent,dk_i = [e_i]_2andz = sum_i z_i(with[z_i]_Tthe per-client correction), Shamir-shares every entry of everye_iandzmodulo the group order and hands serverkits shares in the exponent only:[e^(k)_{i,j}]_2and[z^(k)]_T;server
kof an enrolled setSreturnsW_k = (prod_{i,j} e(ct_{i,j}, [e^(k)_{i,j}]_2) / [z^(k)]_T)^lambda_kwith its Lagrange coefficientlambda_k;prod_{k in S} W_k = prod_i iDec(dk_i, ct_i) = [<x, y>]_T, then a bounded discrete log.
Fewer than t servers learn nothing about the key (Shamir); t
colluding servers interpolate [e_i]_2 and [z]_T in the exponent, the
FH-MCFE functional key, which hides y. For any ciphertexts, the partial
decryptions of an enrolled set are, in the exponent, evaluations of a random
degree t - 1 polynomial whose constant term is the FH-MCFE decryption; a
combiner can at most compute what the holder of the FH-MCFE key could,
without learning the key itself. Earlier versions handed out the shares of
e_i in Z_p, from which t colluding servers solved for y;
versions <= 0.2.0 shared a public label-correction term instead.
- class pyfe4ai.schemes.mcfe.fh_multi_ipe_pairing_threshold.ThresholdMCFEFHMultiIPEPairingKeyGenerator(config, **kwargs)[source]¶
Bases:
MCFEFHMultiIPEPairingKeyGeneratorKey generator: FH-MCFE setup and Shamir-shared functional keys.
- Parameters:
config (
dict)
- __init__(config, **kwargs)[source]¶
Initialise the key generator.
- Parameters:
config (
dict) – As forMCFEFHMultiIPEPairingKeyGenerator, pluss,tandlst_sid.- Return type:
None
- setup()[source]¶
FH-MCFE setup and the client-server MAC keys; forgets previously shared keys.
- Return type:
- get_private_keys(nid, **kwargs)[source]¶
Client nid’s
imskplusmac_keys({sid: key}) for the ciphertext tags.
- get_decryption_keys(sid, **kwargs)[source]¶
Return server sid’s share of the FH-MCFE key for
y.All servers asking for the same weights receive shares of one key.
- Parameters:
sid (
str) – Decryption-server identifier, must be inlst_sid.**kwargs –
credentialswithfusion_weightcovering every client (alabelentry is ignored: keys are label-free).
- Return type:
- Returns:
Dict with
k(per client the shares of the key exponents asG2elements),z(the share ofzas aGTelement),sid,lst_sid, a randomkey_id(nothing derived fromy) andmac_keys({nid: key}), orNoneif sid is unknown.
- class pyfe4ai.schemes.mcfe.fh_multi_ipe_pairing_threshold.ThresholdMCFEFHMultiIPEPairing(config, **kwargs)[source]¶
Bases:
MCFEFHMultiIPEPairingCrypto operations for the threshold FH-MCFE.
- Parameters:
config (
dict)
- encrypt(lst_pt, label)[source]¶
FH-MCFE encryption under label; the ciphertext carries the encoded label and a tag for every server over both.
Compute this server’s partial decryption
W_kfor an enrolled set.- Parameters:
dict_ct (
dict) – One ciphertext per client (all under one label).credentials (
dict) – Ignored (the key share encodesy; servers need not know the function). Kept for API compatibility.dk (
dict) – This server’s key share fromget_decryption_keys.lst_sid_enrolled (
list) – The servers whose partials will be combined.label – The label (round) the server agrees to decrypt. The label is inside the FH ciphertexts, so the server checks the clients’ tags over the declared label and the ciphertext.
- Return type:
- Returns:
Dict with
w,ct_digest,key_id,label,sidandlst_sid_enrolled.- Raises:
FESchemeError – If a ciphertext is not under label or not tagged for this server by its client.
- combine_decrypt(partials, label=None)[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).label (default:
None) – Optional label the partials must be for.
- Raises:
FESchemeError – If a group is incomplete or invalid, is for another label or 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, label)[source]¶
Partially decrypt ndarray ciphertexts element-wise; element
(l, i)must be under its element label derived from label.
- decrypt_lst_ndarray_ct(dict_ndarray_ct, label)[source]¶
Combine element-wise partial decryptions from at least
tservers.- Raises:
FESchemeError – If the servers’ outputs differ in structure, are for other labels, or an element does not decrypt.
- Parameters:
label (
str)- Return type:
Building Blocks¶
Label encoding shared by the labelled multi-client schemes.
MCFE ciphertexts are deterministic per (client slot, label), so a client
must never encrypt twice under the same label. The ndarray helpers encrypt
every array element under its own ElementLabel; encode_label()
maps user labels (strings) and element labels into disjoint domains, so no
user-chosen string can collide with the label of an array element.
- class pyfe4ai.schemes.mcfe.labels.ElementLabel(label, layer, index)[source]¶
Bases:
NamedTupleLabel of one ndarray element: the caller’s label, the layer and the index.
- pyfe4ai.schemes.mcfe.labels.element_label(label, layer, index)[source]¶
Return the label of element index of layer layer under label.
- Parameters:
- Return type:
- pyfe4ai.schemes.mcfe.labels.encode_label(label)[source]¶
Return the unambiguous string that is hashed for label.
- Parameters:
label – A user label (
str) or anElementLabel.- Raises:
FEValidationError – For any other type.
- Return type:
- pyfe4ai.schemes.mcfe.labels.chunk_label(label, chunk)[source]¶
Return the label of batch chunk chunk under label.
Used by schemes that pack many values into one ciphertext (one ciphertext per chunk and label); encoded in the element domain with layer
-1, so it never collides with a user label or an element label.- Parameters:
- Return type:
Decentralised key derivation for MCFE schemes with a linear functional key.
Fig. 14 turns an MCFE (MCFE1) whose functional key is
sk_y = sum_i y_i sk_{1,i} into a decentralised one:
KeyGen: each client has
sk_{1,i}(MCFE1) and an MCFE2 keysk_{2,i}withsum_i sk_{2,i} = 0.KeyDerShare: client
ipublishesEnc2(sk_{2,i}, y_i sk_{1,i}, H(y))– an MCFE2 encryption of its key contribution under the labelH(y).KeyDerComb:
Dec2with the all-ones function, whose MCFE2 key issum_i sk_{2,i} = 0, returnssum_i y_i sk_{1,i} = sk_y.
This module provides MCFE2: the DCR MCFE of ABMPSW20 Fig. 3 (see
pyfe4ai.schemes.mcfe.paillier), one MCFE2 slot per coordinate of a
key contribution, so the all-ones key of each coordinate is the sum of the
clients’ MCFE2 keys for that coordinate. Its modulus N2 is sized for the
contributions (Fig. 3 requires 2 n2 X2^2 < N2 for n2 slots of bound
X2). Implementation choices: H(y) is a domain-separated hash of the
canonical fusion weights; the zero-sum MCFE2 keys come from pairwise
Gaussian masks r_{i,j} (client i adds r_{i,j} for j > i and
subtracts it for j < i) – the “interactively produce” step of Fig. 14
– which this prototype samples in-process, as it does all other key
material; a deployment would agree on each r_{i,j} pairwise.
- pyfe4ai.schemes.mcfe.decentralize.MIN_PRIME_BITS = 128¶
Smallest prime size of N2, whatever the contribution bound.
- pyfe4ai.schemes.mcfe.decentralize.key_label(fusion_weight)[source]¶
Return the MCFE2 label
H(y)binding key shares to the weightsy.
- pyfe4ai.schemes.mcfe.decentralize.mcfe2_prime_bits(n_slots, bound, min_bits=128)[source]¶
Prime size for
N2 = p qsuch that2 n2 X2^2 < N2(ABMPSW20 Fig. 3).
- pyfe4ai.schemes.mcfe.decentralize.generate_mcfe2_modulus(prime_bits)[source]¶
Return
N2 = p qfor fresh safe primes; the primes are discarded.
- pyfe4ai.schemes.mcfe.decentralize.zero_sum_keys(lst_nid, dim, sigma)[source]¶
Return MCFE2 keys
{nid: [s_{i,0..dim-1}]}withsum_i s_i = 0.Built from pairwise masks
r_{i,j} <- D_{Z^dim, sigma}, so each key is a sum ofn - 1Gaussians (width at leastsigma) and the keys of all clients cancel coordinate-wise.
- pyfe4ai.schemes.mcfe.decentralize.mcfe2_key_sigma(n2_mod, n_slots, bound, stat_param)[source]¶
Width of the pairwise masks: the ABMPSW20 Fig. 3 bound for MCFE2.
- pyfe4ai.schemes.mcfe.decentralize.encrypt_contribution(n2_mod, mcfe2_key, contribution, label, bound)[source]¶
MCFE2 encryption of a key contribution, one Fig. 3 slot per coordinate.
Returns
[(1 + N2)^{m_j} H(label)^{s_j} mod N2^2]_jas digit strings.
- pyfe4ai.schemes.mcfe.decentralize.combine_contributions(n2_mod, shares, lst_nid)[source]¶
MCFE2 decryption with the all-ones function (key 0) of every coordinate.
- Parameters:
- Return type:
- Returns:
[sum_i m_{i,j}]_jas signed integers (centred modN2).- Raises:
FESchemeError – If a client is missing or a coordinate fails to decode.
Function-hiding single-input inner-product encryption over asymmetric
pairings, with group-encoded inputs and output [<x, y>]_T.
n = 1), built from the AGRW17 scheme of Fig. 24 (inner layer) and thek = 1 (SXDH); Appendix B (the LV16 padding x || 0, y || 0)This is the black-box “FH-IPFE” that the function-hiding MCFE / DMCFE
constructions of pyfe4ai.schemes.mcfe.fh_multi_ipe_pairing use: its
encryption takes [x]_1 and its key generation [y]_2 (vectors of group
elements), and decryption returns [<x, y>]_T = e(g1, g2)^{<x, y>}, so
partial results of several instances can be multiplied.
With x' = (x, 0^m), y' = (y, 0^m) (m2 = 2m) and l = m2 + 3:
inner Enc:
s <- Z_p;ct_in = [(A s ; (x', z) + U A s)]_1 in G1^l;inner KeyGen:
r <- Z_p;v = (y', r);sk_in = [(-U^T v ; v)]_2 in G2^l,z_sum = z r;outer KeyGen of
[ct_in]_1:ct = [(W'^T ct_in ; ct_in)]_1 in G1^{l+2};outer Enc of
[sk_in]_2:r' <- Z_p;key = [(-A' r' ; sk_in + W' A' r')]_2;Dec:
prod_j e(ct_j, key_j) / [z_sum]_T = [<ct_in, sk_in> - z r]_T = [<x, y>]_T.
A, A' in Z_p^2, U in Z_p^{(m2+1) x 2}, W' in Z_p^{l x 2} and
z in Z_p form the master secret key.
- class pyfe4ai.schemes.mcfe.fh_ipe.FHIPE(group, g1, g2, m)[source]¶
Bases:
objectACFGU18 Fig. 12 (n = 1, k = 1) with LV16 padding, for vectors of length m.
- Parameters:
group (
PairingGroup)m (
int)
- property gt¶
e(g1, g2), computed on first use (encryption does not need it).
- encrypt(msk, x_enc, rng=None)[source]¶
Encrypt
[x]_1(melements of G1); returnsl + 2elements of G1.rng (an object with
getrandbits) makes the randomness deterministic.
- keygen(msk, y_enc, rng=None, r=None)[source]¶
Key for
[y]_2(melements of G2); returns (l + 2elements of G2,z_sum).rng (an object with
getrandbits) makes the randomness deterministic; r fixes the inner-layer randomness (shared by all slots of an ACFGU18 Fig. 12 key, whose pad term is thensum_i z_i r).