Spaces:
Sleeping
Sleeping
File size: 8,713 Bytes
f857b91 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | """
SecureLens β TRUE FHE Inference Server
=======================================
This server ONLY performs homomorphic computation on encrypted data.
It NEVER decrypts anything.
It has NO secret key β mathematically impossible to decrypt.
The client (Gradio Space) sends:
1. ciphertext bytes β encrypted 512-dim feature vector
2. public_context β CKKS context with NO secret key (safe to share)
This server computes:
Layer 1: W1 @ enc(features) + b1 (homomorphic)
Layer 2: W2 @ enc(h) + b2 (homomorphic)
Returns: encrypted logits (client decrypts these β not us)
TRUE FHE: Server sees ZERO plaintext, has ZERO secret key.
"""
from flask import Flask, request, jsonify
from flask_cors import CORS
import tenseal as ts
import numpy as np
import json
import os
import base64
import struct
import time
app = Flask(__name__)
CORS(app)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
# ββ Load plaintext model weights at startup ββββββββββββββββββββββββββββββ
# Weights are NOT sensitive β they are the model, not patient data.
# Only the patient's encrypted features are sensitive.
print("[SecureLens-Server] Loading trained weight matrices...")
def load_weights():
fw_path = os.path.join(BASE_DIR, "feature_weights.json")
lw_path = os.path.join(BASE_DIR, "linear_weights.json")
if not os.path.exists(fw_path):
raise FileNotFoundError(f"feature_weights.json not found at {fw_path}")
if not os.path.exists(lw_path):
raise FileNotFoundError(f"linear_weights.json not found at {lw_path}")
with open(fw_path) as f:
fw = json.load(f)
with open(lw_path) as f:
lw = json.load(f)
W1 = np.array(fw["W"], dtype=np.float64) # (256, 512)
b1 = np.array(fw["b"], dtype=np.float64) # (256,)
W2 = np.array(lw["W"], dtype=np.float64) # (2, 256)
b2 = np.array(lw["b"], dtype=np.float64) # (2,)
print(f" W1: {W1.shape} b1: {b1.shape}")
print(f" W2: {W2.shape} b2: {b2.shape}")
print("[SecureLens-Server] Weights ready. Server has NO secret key.")
return W1, b1, W2, b2
W1, b1, W2, b2 = load_weights()
# ββ Homomorphic Linear Layer ββββββββββββββββββββββββββββββββββββββββββββββ
def linear_he(enc_vec, W, b, layer_name="layer"):
"""
Computes W @ enc_vec + b HOMOMORPHICALLY.
- enc_vec is a CKKSVector (ciphertext)
- W, b are plaintext weights
- Result is a LIST of CKKSVectors β one per output neuron
- .decrypt() is NEVER called here
"""
results = []
for i in range(W.shape[0]):
enc_dot = enc_vec.dot(W[i].tolist()) # homomorphic dot product
enc_neuron = enc_dot + b[i] # homomorphic bias add
results.append(enc_neuron)
print(f"[Server] {layer_name}: {W.shape[1]}β{W.shape[0]} neurons (ENCRYPTED)")
return results
# ββ Routes ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@app.route("/", methods=["GET"])
def index():
return jsonify({
"service" : "SecureLens FHE Inference Server",
"status" : "running",
"has_secret_key" : False,
"can_decrypt" : False,
"endpoint" : "/api/predict_encrypted",
"description" : "Send encrypted features, receive encrypted logits. True FHE.",
})
@app.route("/health", methods=["GET"])
def health():
return jsonify({
"status" : "ok",
"weights_loaded" : W1 is not None,
"W1_shape" : list(W1.shape),
"W2_shape" : list(W2.shape),
"has_secret_key" : False,
})
@app.route("/api/predict_encrypted", methods=["POST"])
def predict_encrypted():
"""
TRUE FHE ENDPOINT β Server sees ZERO plaintext.
Client sends (multipart/form-data):
- ciphertext : binary β encrypted 512-dim feature vector
- public_context : binary β CKKS public context (no secret key)
Server does:
1. Reconstruct public context (cannot decrypt anything)
2. Deserialize ciphertext using public context
3. Layer1: W1 @ enc_features + b1 (homomorphic)
4. Layer2: W2 @ enc_h + b2 (homomorphic)
5. Serialize encrypted logits
6. Return as base64 JSON
Server NEVER calls .decrypt()
Server NEVER sees plaintext features
"""
if "ciphertext" not in request.files:
return jsonify({"error": "Missing 'ciphertext' field. Send encrypted feature vector."}), 400
if "public_context" not in request.files:
return jsonify({"error": "Missing 'public_context' field. Send your CKKS public context bytes."}), 400
try:
ct_bytes = request.files["ciphertext"].read()
pub_ctx_bytes = request.files["public_context"].read()
ct_size_kb = len(ct_bytes) / 1024
if ct_size_kb < 50:
return jsonify({
"error": f"Ciphertext too small ({ct_size_kb:.1f} KB). Expected ~326 KB CKKS ciphertext."
}), 400
t_start = time.time()
print(f"\n[Server] Received {ct_size_kb:.1f} KB ciphertext β TRUE FHE inference starting...")
print("[Server] Server has NO secret key β cannot decrypt.")
# Step 1: Reconstruct public context from client-sent bytes
# This context has NO secret key β mathematically cannot decrypt
context = ts.context_from(pub_ctx_bytes)
print("[Server] Public context reconstructed (no secret key inside)")
# Step 2: Deserialize ciphertext using public context
enc_features = ts.ckks_vector_from(context, ct_bytes)
print("[Server] Ciphertext deserialized β still encrypted")
# Step 3: Homomorphic Layer 1 β W1 @ enc_features + b1
enc_h_list = linear_he(enc_features, W1, b1, "Layer1(512β256)")
# Step 4: Homomorphic Layer 2 β W2 @ enc_h + b2
enc_out_list = []
for i in range(W2.shape[0]): # 2 output neurons
enc_sum = enc_h_list[0] * W2[i, 0]
for j in range(1, len(enc_h_list)):
enc_sum = enc_sum + (enc_h_list[j] * W2[i, j])
enc_out_list.append(enc_sum + b2[i])
print("[Server] Layer2(256β2): 2 encrypted logits computed (ENCRYPTED)")
# Step 5: Serialize encrypted results
# Format: [n_vecs(4B)][size1(4B)][vec1][size2(4B)][vec2]
result_bytes = struct.pack('!I', len(enc_out_list))
for vec in enc_out_list:
vec_bytes = vec.serialize()
result_bytes += struct.pack('!I', len(vec_bytes))
result_bytes += vec_bytes
latency_ms = (time.time() - t_start) * 1000
print(f"[Server] Done in {latency_ms:.0f}ms. Returning {len(result_bytes)//1024} KB encrypted result.")
print("[Server] Server decrypted: NOTHING β true FHE β")
return jsonify({
"success" : True,
"mode" : "TRUE_FHE",
"encrypted_result_b64" : base64.b64encode(result_bytes).decode("utf-8"),
"latency_ms" : round(latency_ms, 1),
"server_has_secret_key": False,
"server_decrypted" : False,
"server_saw" : "Ciphertext only β ZERO plaintext",
"ciphertext_size_kb" : round(ct_size_kb, 2),
"result_size_kb" : round(len(result_bytes) / 1024, 2),
"pipeline" : [
"Client extracted ResNet-18 features (client device)",
"Client CKKS-encrypted 512 features β 326 KB ciphertext (client device)",
f"Server received {ct_size_kb:.0f} KB ciphertext ONLY",
"Server deserialized with PUBLIC context (no secret key)",
"Server computed W1 @ enc(x) + b1 homomorphically",
"Server computed W2 @ enc(h) + b2 homomorphically",
"Server returned ENCRYPTED logits (cannot decrypt)",
"Client will decrypt with secret key (client device)",
],
})
except Exception as e:
import traceback
traceback.print_exc()
return jsonify({"error": str(e)}), 500
if __name__ == "__main__":
print("\n" + "=" * 55)
print(" SecureLens β TRUE FHE Inference Server")
print(" Server has NO secret key")
print(" Server NEVER decrypts anything")
print("=" * 55 + "\n")
app.run(host="0.0.0.0", port=7860, debug=False)
|