Crittografia Omomorfa in Pratica: TFHE, Concrete-ML e Inferenza ML su Dati Crittografati
Principi della Crittografia Omomorfa
La crittografia normale è come una cassaforte chiusa. La crittografia omomorfa è come una cassaforte con bracci manipolatori incorporati — puoi eseguire computazioni sul contenuto mentre è ancora chiuso dentro.
Formalmente: date le crittografie E(a) ed E(b), puoi calcolare E(a + b) ed E(a × b) senza conoscere a o b. Ogni computazione può essere decomposta in addizioni e moltiplicazioni, quindi è Turing-completo. Il problema è la performance.
TFHE: Lo Schema Dietro Concrete-ML
TFHE rappresenta dati crittografati come polinomi su un toro. L'innovazione chiave è il bootstrapping veloce — TFHE può rinfrescare il rumore del ciphertext in ~10ms, rispetto ai minuti degli schemi più vecchi.
Il rumore cresce con ogni operazione:
- Addizione: Crescita lineare (economica)
- Moltiplicazione: Crescita esponenziale (costosa)
- Bootstrapping: Reset del rumore (~10ms ma abilita ulteriore computazione)
Concrete-ML: FHE per Machine Learning
from concrete.ml.sklearn import LogisticRegression
import numpy as np
X_train = np.load("features_train.npy")
y_train = np.load("labels_train.npy")
model = LogisticRegression(n_bits=8)
model.fit(X_train, y_train)
plaintext_accuracy = model.score(X_test, y_test)
print(f"Accuracy plaintext: {plaintext_accuracy:.4f}")
# Compila il modello in un circuito FHE
fhe_circuit = model.compile(X_train)
# Inferenza su dati crittografati
encrypted_input = fhe_circuit.encrypt(X_test[0:1])
encrypted_result = fhe_circuit.run(encrypted_input)
decrypted_result = fhe_circuit.decrypt(encrypted_result)
print(f"Predizione FHE: {decrypted_result}")
Il parametro n_bits=8 è critico:
| n_bits | Accuracy Plaintext | Tempo Inferenza FHE | Speedup vs. 16-bit | |--------|--------------------|--------------------|---------------------| | 4 | 0.8912 | 45ms | 18x | | 6 | 0.9156 | 95ms | 8.5x | | 8 | 0.9234 | 180ms | 4.5x | | 12 | 0.9241 | 520ms | 1.6x | | 16 | 0.9243 | 810ms | 1x |
Il Pipeline di Credit Scoring
[App Cliente] → [Crittografa feature localmente] → [Invia ciphertext all'API]
↓
[Server Inferenza FHE (AWS)]
↓
[Risultato crittografato]
↓
[App Cliente] ← [Decrittografa risultato localmente] ← [Restituisci predizione crittografata]
I dati finanziari del cliente non lasciano mai il dispositivo in chiaro.
# Lato server
from concrete.ml.deployment import FHEModelServer
server = FHEModelServer(path_dir="./deployed_model")
@app.post("/predict")
async def predict(request: Request):
encrypted_input = await request.body()
encrypted_result = server.run(
serialized_encrypted_quantized_data=encrypted_input,
serialized_evaluation_keys=get_evaluation_keys(request.headers["client-id"]),
)
return Response(content=encrypted_result, media_type="application/octet-stream")
# Lato client
from concrete.ml.deployment import FHEModelClient
client = FHEModelClient(path_dir="./client_model", key_dir="./keys")
client.generate_private_and_evaluation_keys()
clear_input = np.array([[65000, 0.32, 7, 3, 720, 12000, 0, 1]])
encrypted_input = client.quantize_encrypt_serialize(clear_input)
evaluation_keys = client.get_serialized_evaluation_keys()
# ... invia encrypted_input e evaluation_keys al server ...
encrypted_result = response.content
decrypted_prediction = client.deserialize_decrypt_dequantize(encrypted_result)
print(f"Categoria credit score: {decrypted_prediction}")
Reti Neurali Custom
import torch
from concrete.ml.torch.compile import compile_torch_model
class CreditNet(torch.nn.Module):
def __init__(self):
super().__init__()
self.fc1 = torch.nn.Linear(20, 64)
self.fc2 = torch.nn.Linear(64, 32)
self.fc3 = torch.nn.Linear(32, 2)
def forward(self, x):
x = torch.relu(self.fc1(x))
x = torch.relu(self.fc2(x))
x = self.fc3(x)
return x
model = CreditNet()
# ... training ...
quantized_model = compile_torch_model(
model,
X_train,
n_bits=6,
rounding_threshold_bits=6,
)
Regole che ho imparato nel modo difficile:
- ReLU è costoso — ogni ReLU richiede un confronto, che in FHE significa un'operazione di programmable bootstrapping (~10ms ciascuna).
- Profondità più che larghezza — una rete a 3 strati con 64 neuroni per strato è molto più lenta di una a 2 strati con 128 neuroni.
- Evita max/min/argmax — richiedono confronti multipli. Usa average pooling invece di max pooling.
- Batch normalization è gratis — si fonde nello strato lineare precedente durante la compilazione.
La Realtà delle Performance
Risultati su AWS c6i.2xlarge (8 vCPU, nessuna accelerazione GPU):
| Modello | Accuracy | Inferenza FHE | Plaintext | Rallentamento | |---------|----------|---------------|-----------|---------------| | Regressione Logistica | 0.923 | 85ms | 0.02ms | 4,250x | | Albero di Decisione (depth=5) | 0.941 | 210ms | 0.01ms | 21,000x | | XGBoost (10 alberi, depth=4) | 0.956 | 1,240ms | 0.08ms | 15,500x | | SVR Lineare | 0.917 | 72ms | 0.01ms | 7,200x | | NN Custom (2 strati, 64 unità) | 0.948 | 3,400ms | 0.15ms | 22,667x |
Quel rallentamento di 4,250x per la regressione logistica si traduce in 85ms — completamente praticabile per chiamate API real-time.
Cosa NON È Possibile (Ancora)
- Modelli linguistici grandi: Un modello taglia GPT-2 richiederebbe ore per token.
- Classificazione immagini con CNN profonde: Inferenza ResNet-50 sarebbe ~45 minuti.
- Qualsiasi cosa con flusso di controllo dinamico: I circuiti FHE sono statici.
- Feature space grandi: 1000+ feature significa 1000+ valori crittografati da elaborare.
Dimensioni Chiavi e Trasferimento di Rete
FHE ha un problema di espansione dati:
Input crittografato: ~42 KB
Chiavi di valutazione: ~158 MB (inviate una volta per client, cachate dal server)
Output crittografato: ~4 KB
Quei 158 MB di chiave di valutazione sono l'elefante nella stanza. Con compressione si riducono a ~40 MB.
L'Architettura che Funziona
"""
1. Client genera chiavi una volta, memorizza localmente
2. Chiavi di valutazione caricate sul server una volta (cachate con TTL)
3. Ogni richiesta di inferenza invia solo input crittografato (~42 KB)
4. Server restituisce risultato crittografato (~4 KB)
5. Client decrittografa localmente
Trasferimento dati totale per richiesta: ~46 KB
Budget latenza:
- Rete (input crittografato): 15ms
- Inferenza FHE (server): 340ms
- Rete (risultato crittografato): 5ms
- Decrittazione client: 2ms
Totale: ~362ms
"""
Il tempo di inferenza di 340ms è per il modello XGBoost che la banca ha effettivamente scelto — 15 alberi di profondità 4, quantizzato a 6 bit, elaborando 20 feature finanziarie. L'accuracy è 95.2% sul loro holdout set, rispetto al 96.1% per il modello in chiaro. La banca ha accettato quel tradeoff dello 0.9% perché FHE ha permesso di elaborare dati clienti da un'istituzione partner senza accordi di condivisione dati, che avrebbero richiesto 8 mesi di revisione legale.
Dove Sta Andando
L'accelerazione hardware TFHE sta arrivando. Zama sta costruendo ASIC custom che promettono 100-1000x di speedup rispetto a FHE su CPU. La libreria HEXL di Intel già fornisce 2-4x di speedup usando istruzioni AVX-512. L'accelerazione GPU mostra miglioramenti di 10-50x.
Quando l'hardware recupererà — e lo farà, perché la domanda di mercato è chiara — un'inferenza da 340ms diventerà 3ms. A quel punto, FHE diventa praticabile per applicazioni real-time a tutto campo.
Per ora, il punto ideale è chiaro: modelli piccoli-medi su dati strutturati con meno di 100 feature. Se il tuo caso d'uso rientra in quel profilo e necessiti garanzie di privacy matematiche più forti di "promettiamo di non guardare i tuoi dati," la crittografia omomorfa è pronta.