Quick start¶
Functional encryption lets a key holder learn a chosen function of encrypted data – here an inner product – and nothing else. Every scheme in PyFE4AI has two objects: a key generator, which sets up the public parameters and issues keys, and a crypto class, whose objects encrypt (with the public parameters and a client’s key) or decrypt (with a functional key).
A first example¶
import pyfe4ai
x, y = [2, 1, 3], [4, 5, 6]
# sec_param = bit length of the DDH modulus (NOT a security level);
# 128 keeps the demo fast but is insecure -- for real use replace it with
# "security_profile": "standard" (a 3072-bit group)
kg, SIFEDDH = pyfe4ai.create("sife/ddh", {"sec_param": 128, "eta": len(x)})
kg.setup()
pp, sk = kg.get_public_parameters(), kg.get_private_keys()
dk = kg.get_decryption_keys("sid_0", credentials={"fusion_weight": y})
ct = SIFEDDH({"precision": 3, "keys": {"pp": pp, "sk": sk}}).encrypt(x)
result = SIFEDDH({"precision": 3, "keys": {"pp": pp}}).decrypt(ct, dk, y)
print(result) # <x, y> = 31
pyfe4ai.create takes a scheme id
"family/variant" and a config, and returns the scheme’s key generator –
built, not yet set up – with its crypto class. Options can also be passed
as keywords: pyfe4ai.create("sife/ddh", eta=3). The classes are
importable by name too (from pyfe4ai.schemes.sife import SIFEDDH,
SIFEDDHKeyGenerator), named after the id.
The key generator’s methods are the same in every family:
setup()draws the parameters and the master key;get_public_parameters()returnspp, for everyone;get_private_keys(nid)returns a client’s encryption key (sk);get_decryption_keys(sid, credentials={"fusion_weight": y, ...})returns a functional key for the weightsy(in the threshold schemes, one server’s key share).
The crypto objects take {"keys": {"pp": pp, "sk": sk}} to encrypt and
{"keys": {"pp": pp}} to decrypt; their encrypt and decrypt
arguments depend on the family (a vector, a vector and a label, …). The
module of each scheme documents them (API Reference), and the
Glossary lists the configuration keys.
Several clients¶
In multi-client FE (MCFE) every client encrypts its own vector under a shared label, such as a training round; the functional key reveals only the weighted sum over the clients, and only for ciphertexts of one label:
import pyfe4ai
x = {"alice": [1, 2], "bob": [3, -1], "carol": [0, 4]}
y = {"alice": [2, 1], "bob": [1, 1], "carol": [-1, 3]}
kg, MCFEDDH = pyfe4ai.create("mcfe/ddh", {"eta": 2, "n": 3, "lst_nid": list(x)})
kg.setup()
pp = kg.get_public_parameters()
label = "round-1"
cts = {nid: MCFEDDH({"id": nid, "keys": {"pp": pp, "sk": kg.get_private_keys(nid)}}).encrypt(x[nid], label)
for nid in x}
dk = kg.get_decryption_keys("aggregator", credentials={"fusion_weight": y})
print(MCFEDDH({"id": "aggregator", "keys": {"pp": pp}}).decrypt(cts, dk, y, label)) # 18
A client must encrypt at most one vector per label; the MCFE schemes assume it, and the ML adapter below enforces it.
Federated learning¶
pyfe4ai.ml wraps a scheme for gradient aggregation:
import numpy as np
from pyfe4ai.ml import FESchemeWrapper, aggregate_gradients, encrypt_gradient
clients = ["c0", "c1", "c2"]
wrapper = FESchemeWrapper("mcfe", "ring_lwe",
{"eta": 1, "n": 3, "lst_nid": clients, "bound_x": 10**4, "bound_y": 10},
precision=3)
grads = {nid: np.random.default_rng(i).uniform(-1, 1, 1000) for i, nid in enumerate(clients)}
enc = {nid: encrypt_gradient(g, wrapper, nid=nid, label="round-1") for nid, g in grads.items()}
total = aggregate_gradients(enc, wrapper, {nid: 1 for nid in clients}, label="round-1")
print(np.allclose(total, sum(grads.values()), atol=1e-2)) # True
The wrapper runs the key generator, so it holds the master key and plays every party: it is meant for experiments. Threshold decryption and the ML adapter covers the adapter’s policy checks, the threshold MCFE whose decryption servers it runs, and the separate messages of a deployment.
Real parameters¶
The default security_profile="toy" keeps parameters small: fast, and
not secure. "standard" aims at 128-bit security and refuses explicit
values below it:
import pyfe4ai
kg, SIFEDDH = pyfe4ai.create("sife/ddh", {"eta": 3, "security_profile": "standard"}) # 3072-bit group
The pairing-based schemes stay at about 120 to 126 bits (BLS12-381, with a
FESecurityWarning), and four schemes have
no parameter set that is both secure and usable, so standard refuses
them: see Security & Status and Configuration options.
Finding a scheme¶
import pyfe4ai
pyfe4ai.list_schemes() # all 42 ids
pyfe4ai.list_schemes("mcfe", capabilities="threshold") # the threshold MCFEs
scheme = pyfe4ai.get_scheme("mcfe/ring_lwe")
print(scheme.kind, sorted(scheme.capabilities), scheme.status.status)
Choosing a scheme explains the families, their capabilities and review status, and which scheme fits which task.