romanbredehoft-zama
commited on
Commit
•
bc345ce
1
Parent(s):
ec21179
Generate a single key per user
Browse files- app.py +40 -44
- backend.py +102 -76
- server.py +48 -38
app.py
CHANGED
@@ -19,9 +19,10 @@ from settings import (
|
|
19 |
FAMILY_STATUS,
|
20 |
)
|
21 |
from backend import (
|
22 |
-
|
23 |
-
|
24 |
-
|
|
|
25 |
run_fhe,
|
26 |
get_output,
|
27 |
decrypt_output,
|
@@ -45,21 +46,26 @@ with demo:
|
|
45 |
|
46 |
gr.Markdown("## Client side")
|
47 |
|
48 |
-
gr.Markdown("### Step 1:
|
|
|
|
|
|
|
|
|
|
|
49 |
with gr.Row():
|
50 |
with gr.Column():
|
51 |
gr.Markdown("### User")
|
52 |
-
gender = gr.Radio(["Female", "Male"], label="Gender")
|
53 |
bool_inputs = gr.CheckboxGroup(["Car", "Property", "Work phone", "Phone", "Email"], label="What do you own ?")
|
54 |
num_children = gr.Slider(**CHILDREN_MIN_MAX, step=1, label="Number of children", info="How many children do you have (0 to 19) ?")
|
55 |
num_family = gr.Slider(**FAMILY_MIN_MAX, step=1, label="Family", info="How many members does your family have? (1 to 20) ?")
|
56 |
total_income = gr.Slider(**INCOME_MIN_MAX, label="Income", info="What's you total yearly income (in euros, 3780 to 220500) ?")
|
57 |
age = gr.Slider(**AGE_MIN_MAX, step=1, label="Age", info="How old are you (20 to 68) ?")
|
58 |
-
income_type = gr.Dropdown(choices=INCOME_TYPES, label="Income type", info="What is your main type of income ?")
|
59 |
-
education_type = gr.Dropdown(choices=EDUCATION_TYPES, label="Education", info="What is your education background ?")
|
60 |
-
family_status = gr.Dropdown(choices=FAMILY_STATUS, label="Family", info="What is your family status ?")
|
61 |
-
occupation_type = gr.Dropdown(choices=OCCUPATION_TYPES, label="Occupation", info="What is your main occupation ?")
|
62 |
-
housing_type = gr.Dropdown(choices=HOUSING_TYPES, label="Housing", info="In what type of housing do you live ?")
|
63 |
|
64 |
with gr.Column():
|
65 |
gr.Markdown("### Bank ")
|
@@ -67,50 +73,37 @@ with demo:
|
|
67 |
|
68 |
with gr.Column():
|
69 |
gr.Markdown("### Third party ")
|
70 |
-
employed = gr.Radio(["Yes", "No"], label="Is the person employed ?")
|
71 |
years_employed = gr.Slider(**EMPLOYED_MIN_MAX, step=1, label="Years of employment", info="How long have this person been employed (in years, 0 to 43) ?")
|
72 |
|
73 |
|
74 |
-
gr.Markdown("### Step
|
75 |
with gr.Row():
|
76 |
with gr.Column():
|
77 |
gr.Markdown("### User")
|
78 |
encrypt_button_user = gr.Button("Encrypt the inputs and send to server.")
|
79 |
|
80 |
-
user_id = gr.Textbox(label="", max_lines=2, interactive=False, visible=False)
|
81 |
encrypted_input_user = gr.Textbox(
|
82 |
label="Encrypted input representation:", max_lines=2, interactive=False
|
83 |
)
|
84 |
-
# keys_user = gr.Textbox(
|
85 |
-
# label="Keys representation:", max_lines=2, interactive=False
|
86 |
-
# )
|
87 |
-
|
88 |
|
89 |
|
90 |
with gr.Column():
|
91 |
gr.Markdown("### Bank ")
|
92 |
encrypt_button_bank = gr.Button("Encrypt the inputs and send to server.")
|
93 |
|
94 |
-
bank_id = gr.Textbox(label="", max_lines=2, interactive=False, visible=False)
|
95 |
encrypted_input_bank = gr.Textbox(
|
96 |
label="Encrypted input representation:", max_lines=2, interactive=False
|
97 |
)
|
98 |
-
# keys_bank = gr.Textbox(
|
99 |
-
# label="Keys representation:", max_lines=2, interactive=False
|
100 |
-
# )
|
101 |
|
102 |
|
103 |
with gr.Column():
|
104 |
gr.Markdown("### Third Party ")
|
105 |
encrypt_button_third_party = gr.Button("Encrypt the inputs and send to server.")
|
106 |
|
107 |
-
third_party_id = gr.Textbox(label="", max_lines=2, interactive=False, visible=False)
|
108 |
encrypted_input_third_party = gr.Textbox(
|
109 |
label="Encrypted input representation:", max_lines=2, interactive=False
|
110 |
)
|
111 |
-
# keys_3 = gr.Textbox(
|
112 |
-
# label="Keys representation:", max_lines=2, interactive=False
|
113 |
-
# )
|
114 |
|
115 |
gr.Markdown("## Server side")
|
116 |
gr.Markdown(
|
@@ -119,7 +112,7 @@ with demo:
|
|
119 |
"the encrypted result to the client."
|
120 |
)
|
121 |
|
122 |
-
gr.Markdown("### Step
|
123 |
execute_fhe_button = gr.Button("Run FHE execution.")
|
124 |
fhe_execution_time = gr.Textbox(
|
125 |
label="Total FHE execution time (in seconds):", max_lines=1, interactive=False
|
@@ -131,7 +124,7 @@ with demo:
|
|
131 |
"private key."
|
132 |
)
|
133 |
|
134 |
-
gr.Markdown("### Step
|
135 |
gr.Markdown(
|
136 |
"The output displayed here is the encrypted result sent by the server, which has been "
|
137 |
"decrypted using a different private key. This is only used to visually represent an "
|
@@ -143,55 +136,58 @@ with demo:
|
|
143 |
label="Encrypted output representation: ", max_lines=1, interactive=False
|
144 |
)
|
145 |
|
146 |
-
gr.Markdown("### Step
|
147 |
decrypt_button = gr.Button("Decrypt the output")
|
148 |
|
149 |
prediction_output = gr.Textbox(
|
150 |
label="Credit card approval high risk: ", max_lines=1, interactive=False
|
151 |
)
|
152 |
|
|
|
|
|
|
|
|
|
|
|
|
|
153 |
# Button to pre-process, generate the key, encrypt and send the user inputs from the client
|
154 |
# side to the server
|
155 |
encrypt_button_user.click(
|
156 |
-
|
157 |
-
inputs=[gender, bool_inputs, num_children, num_family, total_income, age,
|
158 |
-
education_type, family_status, occupation_type, housing_type],
|
159 |
-
outputs=[
|
160 |
)
|
161 |
|
162 |
# Button to pre-process, generate the key, encrypt and send the bank inputs from the client
|
163 |
# side to the server
|
164 |
encrypt_button_bank.click(
|
165 |
-
|
166 |
-
inputs=[account_length],
|
167 |
-
outputs=[
|
168 |
)
|
169 |
|
170 |
# Button to pre-process, generate the key, encrypt and send the third party inputs from the
|
171 |
# client side to the server
|
172 |
encrypt_button_third_party.click(
|
173 |
-
|
174 |
-
inputs=[employed, years_employed],
|
175 |
-
outputs=[
|
176 |
)
|
177 |
|
178 |
-
# TODO : ID should be unique
|
179 |
# Button to send the encodings to the server using post method
|
180 |
-
execute_fhe_button.click(run_fhe, inputs=[
|
181 |
|
182 |
-
# TODO : ID should be unique
|
183 |
# Button to send the encodings to the server using post method
|
184 |
get_output_button.click(
|
185 |
get_output,
|
186 |
-
inputs=[
|
187 |
outputs=[encrypted_output_representation],
|
188 |
)
|
189 |
|
190 |
-
# TODO : ID should be unique
|
191 |
# Button to decrypt the output as the user
|
192 |
decrypt_button.click(
|
193 |
decrypt_output,
|
194 |
-
inputs=[
|
195 |
outputs=[prediction_output],
|
196 |
)
|
197 |
|
|
|
19 |
FAMILY_STATUS,
|
20 |
)
|
21 |
from backend import (
|
22 |
+
keygen_send,
|
23 |
+
pre_process_encrypt_send_user,
|
24 |
+
pre_process_encrypt_send_bank,
|
25 |
+
pre_process_encrypt_send_third_party,
|
26 |
run_fhe,
|
27 |
get_output,
|
28 |
decrypt_output,
|
|
|
46 |
|
47 |
gr.Markdown("## Client side")
|
48 |
|
49 |
+
gr.Markdown("### Step 1: Generate the keys. ")
|
50 |
+
# TODO: Re-initialize the key message once generated and sent
|
51 |
+
keygen_button = gr.Button("Generate the keys and send evaluation key to the server.")
|
52 |
+
client_id = gr.Textbox(label="", max_lines=2, interactive=False, visible=False)
|
53 |
+
|
54 |
+
gr.Markdown("### Step 2: Infos. ")
|
55 |
with gr.Row():
|
56 |
with gr.Column():
|
57 |
gr.Markdown("### User")
|
58 |
+
gender = gr.Radio(["Female", "Male"], label="Gender", value="Female")
|
59 |
bool_inputs = gr.CheckboxGroup(["Car", "Property", "Work phone", "Phone", "Email"], label="What do you own ?")
|
60 |
num_children = gr.Slider(**CHILDREN_MIN_MAX, step=1, label="Number of children", info="How many children do you have (0 to 19) ?")
|
61 |
num_family = gr.Slider(**FAMILY_MIN_MAX, step=1, label="Family", info="How many members does your family have? (1 to 20) ?")
|
62 |
total_income = gr.Slider(**INCOME_MIN_MAX, label="Income", info="What's you total yearly income (in euros, 3780 to 220500) ?")
|
63 |
age = gr.Slider(**AGE_MIN_MAX, step=1, label="Age", info="How old are you (20 to 68) ?")
|
64 |
+
income_type = gr.Dropdown(choices=INCOME_TYPES, value=INCOME_TYPES[0], label="Income type", info="What is your main type of income ?")
|
65 |
+
education_type = gr.Dropdown(choices=EDUCATION_TYPES, value=EDUCATION_TYPES[0], label="Education", info="What is your education background ?")
|
66 |
+
family_status = gr.Dropdown(choices=FAMILY_STATUS, value=FAMILY_STATUS[0], label="Family", info="What is your family status ?")
|
67 |
+
occupation_type = gr.Dropdown(choices=OCCUPATION_TYPES, value=OCCUPATION_TYPES[0], label="Occupation", info="What is your main occupation ?")
|
68 |
+
housing_type = gr.Dropdown(choices=HOUSING_TYPES, value=HOUSING_TYPES[0], label="Housing", info="In what type of housing do you live ?")
|
69 |
|
70 |
with gr.Column():
|
71 |
gr.Markdown("### Bank ")
|
|
|
73 |
|
74 |
with gr.Column():
|
75 |
gr.Markdown("### Third party ")
|
76 |
+
employed = gr.Radio(["Yes", "No"], label="Is the person employed ?", value="Yes")
|
77 |
years_employed = gr.Slider(**EMPLOYED_MIN_MAX, step=1, label="Years of employment", info="How long have this person been employed (in years, 0 to 43) ?")
|
78 |
|
79 |
|
80 |
+
gr.Markdown("### Step 3: Encrypt using FHE and send the inputs to the server.")
|
81 |
with gr.Row():
|
82 |
with gr.Column():
|
83 |
gr.Markdown("### User")
|
84 |
encrypt_button_user = gr.Button("Encrypt the inputs and send to server.")
|
85 |
|
|
|
86 |
encrypted_input_user = gr.Textbox(
|
87 |
label="Encrypted input representation:", max_lines=2, interactive=False
|
88 |
)
|
|
|
|
|
|
|
|
|
89 |
|
90 |
|
91 |
with gr.Column():
|
92 |
gr.Markdown("### Bank ")
|
93 |
encrypt_button_bank = gr.Button("Encrypt the inputs and send to server.")
|
94 |
|
|
|
95 |
encrypted_input_bank = gr.Textbox(
|
96 |
label="Encrypted input representation:", max_lines=2, interactive=False
|
97 |
)
|
|
|
|
|
|
|
98 |
|
99 |
|
100 |
with gr.Column():
|
101 |
gr.Markdown("### Third Party ")
|
102 |
encrypt_button_third_party = gr.Button("Encrypt the inputs and send to server.")
|
103 |
|
|
|
104 |
encrypted_input_third_party = gr.Textbox(
|
105 |
label="Encrypted input representation:", max_lines=2, interactive=False
|
106 |
)
|
|
|
|
|
|
|
107 |
|
108 |
gr.Markdown("## Server side")
|
109 |
gr.Markdown(
|
|
|
112 |
"the encrypted result to the client."
|
113 |
)
|
114 |
|
115 |
+
gr.Markdown("### Step 4: Run FHE execution.")
|
116 |
execute_fhe_button = gr.Button("Run FHE execution.")
|
117 |
fhe_execution_time = gr.Textbox(
|
118 |
label="Total FHE execution time (in seconds):", max_lines=1, interactive=False
|
|
|
124 |
"private key."
|
125 |
)
|
126 |
|
127 |
+
gr.Markdown("### Step 5: Receive the encrypted output from the server.")
|
128 |
gr.Markdown(
|
129 |
"The output displayed here is the encrypted result sent by the server, which has been "
|
130 |
"decrypted using a different private key. This is only used to visually represent an "
|
|
|
136 |
label="Encrypted output representation: ", max_lines=1, interactive=False
|
137 |
)
|
138 |
|
139 |
+
gr.Markdown("### Step 6: Decrypt the output.")
|
140 |
decrypt_button = gr.Button("Decrypt the output")
|
141 |
|
142 |
prediction_output = gr.Textbox(
|
143 |
label="Credit card approval high risk: ", max_lines=1, interactive=False
|
144 |
)
|
145 |
|
146 |
+
# Button generate the keys
|
147 |
+
keygen_button.click(
|
148 |
+
keygen_send,
|
149 |
+
outputs=[client_id, keygen_button],
|
150 |
+
)
|
151 |
+
|
152 |
# Button to pre-process, generate the key, encrypt and send the user inputs from the client
|
153 |
# side to the server
|
154 |
encrypt_button_user.click(
|
155 |
+
pre_process_encrypt_send_user,
|
156 |
+
inputs=[client_id, gender, bool_inputs, num_children, num_family, total_income, age, \
|
157 |
+
income_type, education_type, family_status, occupation_type, housing_type],
|
158 |
+
outputs=[encrypted_input_user],
|
159 |
)
|
160 |
|
161 |
# Button to pre-process, generate the key, encrypt and send the bank inputs from the client
|
162 |
# side to the server
|
163 |
encrypt_button_bank.click(
|
164 |
+
pre_process_encrypt_send_bank,
|
165 |
+
inputs=[client_id, account_length],
|
166 |
+
outputs=[encrypted_input_bank],
|
167 |
)
|
168 |
|
169 |
# Button to pre-process, generate the key, encrypt and send the third party inputs from the
|
170 |
# client side to the server
|
171 |
encrypt_button_third_party.click(
|
172 |
+
pre_process_encrypt_send_third_party,
|
173 |
+
inputs=[client_id, employed, years_employed],
|
174 |
+
outputs=[encrypted_input_third_party],
|
175 |
)
|
176 |
|
|
|
177 |
# Button to send the encodings to the server using post method
|
178 |
+
execute_fhe_button.click(run_fhe, inputs=[client_id], outputs=[fhe_execution_time])
|
179 |
|
|
|
180 |
# Button to send the encodings to the server using post method
|
181 |
get_output_button.click(
|
182 |
get_output,
|
183 |
+
inputs=[client_id],
|
184 |
outputs=[encrypted_output_representation],
|
185 |
)
|
186 |
|
|
|
187 |
# Button to decrypt the output as the user
|
188 |
decrypt_button.click(
|
189 |
decrypt_output,
|
190 |
+
inputs=[client_id],
|
191 |
outputs=[prediction_output],
|
192 |
)
|
193 |
|
backend.py
CHANGED
@@ -78,55 +78,110 @@ def clean_temporary_files(n_keys=20):
|
|
78 |
server_files = SERVER_FILES.iterdir()
|
79 |
|
80 |
# Delete all files related to the ids whose keys were deleted
|
81 |
-
for
|
82 |
for user_id in user_ids:
|
83 |
-
if user_id in
|
84 |
-
|
|
|
85 |
|
86 |
|
87 |
-
def _get_client(client_id
|
88 |
-
"""Get the client
|
89 |
|
90 |
Args:
|
91 |
client_id (int): The client ID to consider.
|
92 |
-
client_type (str): The type of user to consider (either 'user', 'bank' or 'third_party').
|
93 |
|
94 |
Returns:
|
95 |
-
FHEModelClient: The client
|
96 |
"""
|
97 |
-
key_dir = FHE_KEYS / f"{
|
98 |
|
99 |
return MultiInputsFHEModelClient(DEPLOYMENT_PATH, key_dir=key_dir, nb_inputs=len(CLIENT_TYPES))
|
100 |
|
101 |
|
102 |
-
def
|
103 |
-
"""
|
104 |
|
105 |
Args:
|
|
|
|
|
106 |
client_id (int): The client ID to consider.
|
107 |
-
client_type (str): The type of
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
108 |
"""
|
109 |
# Clean temporary files
|
110 |
clean_temporary_files()
|
111 |
|
|
|
|
|
|
|
112 |
# Retrieve the client instance
|
113 |
-
client = _get_client(client_id
|
114 |
|
115 |
-
# Generate
|
116 |
client.generate_private_and_evaluation_keys(force=True)
|
117 |
|
118 |
-
# Retrieve the serialized evaluation key
|
119 |
-
# evaluation key is empty. However, for software reasons, it is still needed for proper FHE
|
120 |
-
# execution
|
121 |
evaluation_key = client.get_serialized_evaluation_keys()
|
122 |
|
123 |
-
# Save
|
124 |
# buttons (see https://github.com/gradio-app/gradio/issues/1877)
|
125 |
-
evaluation_key_path = _get_client_file_path("evaluation_key", client_id
|
126 |
|
127 |
with evaluation_key_path.open("wb") as evaluation_key_file:
|
128 |
evaluation_key_file.write(evaluation_key)
|
129 |
|
|
|
|
|
|
|
|
|
|
|
130 |
|
131 |
def _send_input(client_id, client_type):
|
132 |
"""Send the encrypted inputs as well as the evaluation key to the server.
|
@@ -135,8 +190,7 @@ def _send_input(client_id, client_type):
|
|
135 |
client_id (int): The client ID to consider.
|
136 |
client_type (str): The type of client to consider (either 'user', 'bank' or 'third_party').
|
137 |
"""
|
138 |
-
# Get the paths to the
|
139 |
-
evaluation_key_path = _get_client_file_path("evaluation_key", client_id, client_type)
|
140 |
encrypted_input_path = _get_client_file_path("encrypted_inputs", client_id, client_type)
|
141 |
|
142 |
# Define the data and files to post
|
@@ -147,7 +201,6 @@ def _send_input(client_id, client_type):
|
|
147 |
|
148 |
files = [
|
149 |
("files", open(encrypted_input_path, "rb")),
|
150 |
-
("files", open(evaluation_key_path, "rb")),
|
151 |
]
|
152 |
|
153 |
# Send the encrypted inputs and evaluation key to the server
|
@@ -160,24 +213,11 @@ def _send_input(client_id, client_type):
|
|
160 |
return response.ok
|
161 |
|
162 |
|
163 |
-
def
|
164 |
-
"""
|
165 |
-
|
166 |
-
Args:
|
167 |
-
name (str): The desired file name (either 'evaluation_key' or 'encrypted_inputs').
|
168 |
-
client_id (int): The client ID to consider.
|
169 |
-
client_type (str): The type of user to consider (either 'user', 'bank' or 'third_party').
|
170 |
-
|
171 |
-
Returns:
|
172 |
-
pathlib.Path: The file path.
|
173 |
-
"""
|
174 |
-
return CLIENT_FILES / f"{name}_{client_type}_{client_id}"
|
175 |
-
|
176 |
-
|
177 |
-
def _keygen_encrypt_send(inputs, client_type):
|
178 |
-
"""Encrypt the given inputs for a specific client.
|
179 |
|
180 |
Args:
|
|
|
181 |
inputs (numpy.ndarray): The inputs to encrypt.
|
182 |
client_type (str): The type of client to consider (either 'user', 'bank' or 'third_party').
|
183 |
|
@@ -185,15 +225,9 @@ def _keygen_encrypt_send(inputs, client_type):
|
|
185 |
client_id, encrypted_inputs_short (int, bytes): Integer ID representing the current client
|
186 |
and a byte short representation of the encrypted input to send.
|
187 |
"""
|
188 |
-
# Create an ID for the current client to consider
|
189 |
-
client_id = numpy.random.randint(0, 2**32)
|
190 |
-
|
191 |
-
_keygen(client_id, client_type)
|
192 |
|
193 |
# Retrieve the client instance
|
194 |
-
client = _get_client(client_id
|
195 |
-
|
196 |
-
# TODO : pre-process the data first
|
197 |
|
198 |
# Quantize, encrypt and serialize the inputs
|
199 |
encrypted_inputs = client.quantize_encrypt_serialize_multi_inputs(
|
@@ -215,14 +249,14 @@ def _keygen_encrypt_send(inputs, client_type):
|
|
215 |
|
216 |
_send_input(client_id, client_type)
|
217 |
|
218 |
-
|
219 |
-
return client_id, encrypted_inputs_short
|
220 |
|
221 |
|
222 |
-
def
|
223 |
-
"""Pre-process the
|
224 |
|
225 |
Args:
|
|
|
226 |
*inputs (Tuple[numpy.ndarray]): The inputs to pre-process.
|
227 |
|
228 |
Returns:
|
@@ -263,13 +297,14 @@ def pre_process_keygen_encrypt_send_user(*inputs):
|
|
263 |
|
264 |
preprocessed_user_inputs = PRE_PROCESSOR_USER.transform(user_inputs)
|
265 |
|
266 |
-
return
|
267 |
|
268 |
|
269 |
-
def
|
270 |
-
"""Pre-process the
|
271 |
|
272 |
Args:
|
|
|
273 |
*inputs (Tuple[numpy.ndarray]): The inputs to pre-process.
|
274 |
|
275 |
Returns:
|
@@ -278,13 +313,14 @@ def pre_process_keygen_encrypt_send_bank(*inputs):
|
|
278 |
"""
|
279 |
account_length = inputs[0]
|
280 |
|
281 |
-
return
|
282 |
|
283 |
|
284 |
-
def
|
285 |
-
"""Pre-process the
|
286 |
|
287 |
Args:
|
|
|
288 |
*inputs (Tuple[numpy.ndarray]): The inputs to pre-process.
|
289 |
|
290 |
Returns:
|
@@ -303,24 +339,20 @@ def pre_process_keygen_encrypt_send_third_party(*inputs):
|
|
303 |
|
304 |
preprocessed_third_party_inputs = PRE_PROCESSOR_THIRD_PARTY.transform(third_party_inputs)
|
305 |
|
306 |
-
return
|
307 |
|
308 |
|
309 |
-
def run_fhe(
|
310 |
"""Run the model on the encrypted inputs previously sent using FHE.
|
311 |
|
312 |
Args:
|
313 |
-
|
314 |
-
bank_id (int): The bank ID to consider.
|
315 |
-
third_party_id (int): The third party ID to consider.
|
316 |
"""
|
317 |
|
318 |
# TODO : add a warning for users to send all client types' inputs
|
319 |
|
320 |
data = {
|
321 |
-
"
|
322 |
-
"bank_id": bank_id,
|
323 |
-
"third_party_id": third_party_id,
|
324 |
}
|
325 |
|
326 |
# Trigger the FHE execution on the encrypted inputs previously sent
|
@@ -335,21 +367,17 @@ def run_fhe(user_id, bank_id, third_party_id):
|
|
335 |
raise gr.Error("Please wait for the inputs to be sent to the server.")
|
336 |
|
337 |
|
338 |
-
def get_output(
|
339 |
"""Retrieve the encrypted output.
|
340 |
|
341 |
Args:
|
342 |
-
|
343 |
-
bank_id (int): The bank ID to consider.
|
344 |
-
third_party_id (int): The third party ID to consider.
|
345 |
|
346 |
Returns:
|
347 |
encrypted_output_short (bytes): A byte short representation of the encrypted output.
|
348 |
"""
|
349 |
data = {
|
350 |
-
"
|
351 |
-
"bank_id": bank_id,
|
352 |
-
"third_party_id": third_party_id,
|
353 |
}
|
354 |
|
355 |
# Retrieve the encrypted output
|
@@ -363,7 +391,7 @@ def get_output(user_id, bank_id, third_party_id):
|
|
363 |
|
364 |
# Save the encrypted output to bytes in a file as it is too large to pass through regular
|
365 |
# Gradio buttons (see https://github.com/gradio-app/gradio/issues/1877)
|
366 |
-
encrypted_output_path = _get_client_file_path("encrypted_output",
|
367 |
|
368 |
with encrypted_output_path.open("wb") as encrypted_output_file:
|
369 |
encrypted_output_file.write(encrypted_output)
|
@@ -376,20 +404,18 @@ def get_output(user_id, bank_id, third_party_id):
|
|
376 |
raise gr.Error("Please wait for the FHE execution to be completed.")
|
377 |
|
378 |
|
379 |
-
def decrypt_output(
|
380 |
"""Decrypt the result.
|
381 |
|
382 |
Args:
|
383 |
-
|
384 |
-
bank_id (int): The bank ID to consider.
|
385 |
-
third_party_id (int): The third party ID to consider.
|
386 |
|
387 |
Returns:
|
388 |
output(numpy.ndarray): The decrypted output
|
389 |
|
390 |
"""
|
391 |
# Get the encrypted output path
|
392 |
-
encrypted_output_path = _get_client_file_path("encrypted_output",
|
393 |
|
394 |
if not encrypted_output_path.is_file():
|
395 |
raise gr.Error("Please run the FHE execution first.")
|
@@ -399,7 +425,7 @@ def decrypt_output(user_id, bank_id, third_party_id):
|
|
399 |
encrypted_output_proba = encrypted_output_file.read()
|
400 |
|
401 |
# Retrieve the client API
|
402 |
-
client = _get_client(
|
403 |
|
404 |
# Deserialize, decrypt and post-process the encrypted output
|
405 |
output_proba = client.deserialize_decrypt_dequantize(encrypted_output_proba)
|
|
|
78 |
server_files = SERVER_FILES.iterdir()
|
79 |
|
80 |
# Delete all files related to the ids whose keys were deleted
|
81 |
+
for user_directory in chain(client_files, server_files):
|
82 |
for user_id in user_ids:
|
83 |
+
if user_id in user_directory.name:
|
84 |
+
for client_server_file in user_directory.iterdir():
|
85 |
+
client_server_file.unlink()
|
86 |
|
87 |
|
88 |
+
def _get_client(client_id):
|
89 |
+
"""Get the client instance.
|
90 |
|
91 |
Args:
|
92 |
client_id (int): The client ID to consider.
|
|
|
93 |
|
94 |
Returns:
|
95 |
+
FHEModelClient: The client instance.
|
96 |
"""
|
97 |
+
key_dir = FHE_KEYS / f"{client_id}"
|
98 |
|
99 |
return MultiInputsFHEModelClient(DEPLOYMENT_PATH, key_dir=key_dir, nb_inputs=len(CLIENT_TYPES))
|
100 |
|
101 |
|
102 |
+
def _get_client_file_path(name, client_id, client_type=None):
|
103 |
+
"""Get the file path for the client.
|
104 |
|
105 |
Args:
|
106 |
+
name (str): The desired file name (either 'evaluation_key', 'encrypted_inputs') of
|
107 |
+
'encrypted_outputs').
|
108 |
client_id (int): The client ID to consider.
|
109 |
+
client_type (Optional[str]): The type of user to consider (either 'user', 'bank',
|
110 |
+
'third_party' or None). Default to None, which is used for evaluation key and output.
|
111 |
+
|
112 |
+
Returns:
|
113 |
+
pathlib.Path: The file path.
|
114 |
+
"""
|
115 |
+
client_type_suffix = ""
|
116 |
+
if client_type is not None:
|
117 |
+
client_type_suffix = f"_{client_type}"
|
118 |
+
|
119 |
+
dir_path = CLIENT_FILES / f"{client_id}"
|
120 |
+
dir_path.mkdir(exist_ok=True)
|
121 |
+
|
122 |
+
return dir_path / f"{name}{client_type_suffix}"
|
123 |
+
|
124 |
+
|
125 |
+
def _send_eval_key(client_id, evaluation_key_path):
|
126 |
+
"""Send the evaluation key to the server.
|
127 |
+
|
128 |
+
Args:
|
129 |
+
client_id (int): The client ID to consider.
|
130 |
+
evaluation_key_path (Path): Path to the evaluation key to send.
|
131 |
+
"""
|
132 |
+
|
133 |
+
# Define the data and files to post
|
134 |
+
data = {
|
135 |
+
"client_id": client_id,
|
136 |
+
}
|
137 |
+
|
138 |
+
files = [
|
139 |
+
("files", open(evaluation_key_path, "rb")),
|
140 |
+
]
|
141 |
+
|
142 |
+
# Send the evaluation key to the server
|
143 |
+
url = SERVER_URL + "send_eval_key"
|
144 |
+
with requests.post(
|
145 |
+
url=url,
|
146 |
+
data=data,
|
147 |
+
files=files,
|
148 |
+
) as response:
|
149 |
+
return response.ok
|
150 |
+
|
151 |
+
|
152 |
+
def keygen_send():
|
153 |
+
"""Generate the private and evaluation key, and send the evaluation key to the server.
|
154 |
+
|
155 |
+
Returns:
|
156 |
+
client_id (int): The current client ID to consider.
|
157 |
"""
|
158 |
# Clean temporary files
|
159 |
clean_temporary_files()
|
160 |
|
161 |
+
# Create an ID for the current client to consider
|
162 |
+
client_id = numpy.random.randint(0, 2**32)
|
163 |
+
|
164 |
# Retrieve the client instance
|
165 |
+
client = _get_client(client_id)
|
166 |
|
167 |
+
# Generate the private and evaluation keys
|
168 |
client.generate_private_and_evaluation_keys(force=True)
|
169 |
|
170 |
+
# Retrieve the serialized evaluation key
|
|
|
|
|
171 |
evaluation_key = client.get_serialized_evaluation_keys()
|
172 |
|
173 |
+
# Save evaluation key as bytes in a file as it is too large to pass through regular Gradio
|
174 |
# buttons (see https://github.com/gradio-app/gradio/issues/1877)
|
175 |
+
evaluation_key_path = _get_client_file_path("evaluation_key", client_id)
|
176 |
|
177 |
with evaluation_key_path.open("wb") as evaluation_key_file:
|
178 |
evaluation_key_file.write(evaluation_key)
|
179 |
|
180 |
+
# Send the evaluation key to the server
|
181 |
+
_send_eval_key(client_id, evaluation_key_path)
|
182 |
+
|
183 |
+
return client_id, gr.update(value="Keys are generated and sent ✅")
|
184 |
+
|
185 |
|
186 |
def _send_input(client_id, client_type):
|
187 |
"""Send the encrypted inputs as well as the evaluation key to the server.
|
|
|
190 |
client_id (int): The client ID to consider.
|
191 |
client_type (str): The type of client to consider (either 'user', 'bank' or 'third_party').
|
192 |
"""
|
193 |
+
# Get the paths to the encrypted inputs
|
|
|
194 |
encrypted_input_path = _get_client_file_path("encrypted_inputs", client_id, client_type)
|
195 |
|
196 |
# Define the data and files to post
|
|
|
201 |
|
202 |
files = [
|
203 |
("files", open(encrypted_input_path, "rb")),
|
|
|
204 |
]
|
205 |
|
206 |
# Send the encrypted inputs and evaluation key to the server
|
|
|
213 |
return response.ok
|
214 |
|
215 |
|
216 |
+
def _encrypt_send(client_id, inputs, client_type):
|
217 |
+
"""Encrypt the given inputs for a specific client and send it to the server.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
218 |
|
219 |
Args:
|
220 |
+
client_id (int): The current client ID to consider.
|
221 |
inputs (numpy.ndarray): The inputs to encrypt.
|
222 |
client_type (str): The type of client to consider (either 'user', 'bank' or 'third_party').
|
223 |
|
|
|
225 |
client_id, encrypted_inputs_short (int, bytes): Integer ID representing the current client
|
226 |
and a byte short representation of the encrypted input to send.
|
227 |
"""
|
|
|
|
|
|
|
|
|
228 |
|
229 |
# Retrieve the client instance
|
230 |
+
client = _get_client(client_id)
|
|
|
|
|
231 |
|
232 |
# Quantize, encrypt and serialize the inputs
|
233 |
encrypted_inputs = client.quantize_encrypt_serialize_multi_inputs(
|
|
|
249 |
|
250 |
_send_input(client_id, client_type)
|
251 |
|
252 |
+
return encrypted_inputs_short
|
|
|
253 |
|
254 |
|
255 |
+
def pre_process_encrypt_send_user(client_id, *inputs):
|
256 |
+
"""Pre-process, encrypt and send the user inputs for a specific client to the server.
|
257 |
|
258 |
Args:
|
259 |
+
client_id (int): The current client ID to consider.
|
260 |
*inputs (Tuple[numpy.ndarray]): The inputs to pre-process.
|
261 |
|
262 |
Returns:
|
|
|
297 |
|
298 |
preprocessed_user_inputs = PRE_PROCESSOR_USER.transform(user_inputs)
|
299 |
|
300 |
+
return _encrypt_send(client_id, preprocessed_user_inputs, "user")
|
301 |
|
302 |
|
303 |
+
def pre_process_encrypt_send_bank(client_id, *inputs):
|
304 |
+
"""Pre-process, encrypt and send the user inputs for a specific client to the server.
|
305 |
|
306 |
Args:
|
307 |
+
client_id (int): The current client ID to consider.
|
308 |
*inputs (Tuple[numpy.ndarray]): The inputs to pre-process.
|
309 |
|
310 |
Returns:
|
|
|
313 |
"""
|
314 |
account_length = inputs[0]
|
315 |
|
316 |
+
return _encrypt_send(client_id, account_length, "bank")
|
317 |
|
318 |
|
319 |
+
def pre_process_encrypt_send_third_party(client_id, *inputs):
|
320 |
+
"""Pre-process, encrypt and send the user inputs for a specific client to the server.
|
321 |
|
322 |
Args:
|
323 |
+
client_id (int): The current client ID to consider.
|
324 |
*inputs (Tuple[numpy.ndarray]): The inputs to pre-process.
|
325 |
|
326 |
Returns:
|
|
|
339 |
|
340 |
preprocessed_third_party_inputs = PRE_PROCESSOR_THIRD_PARTY.transform(third_party_inputs)
|
341 |
|
342 |
+
return _encrypt_send(client_id, preprocessed_third_party_inputs, "third_party")
|
343 |
|
344 |
|
345 |
+
def run_fhe(client_id):
|
346 |
"""Run the model on the encrypted inputs previously sent using FHE.
|
347 |
|
348 |
Args:
|
349 |
+
client_id (int): The current client ID to consider.
|
|
|
|
|
350 |
"""
|
351 |
|
352 |
# TODO : add a warning for users to send all client types' inputs
|
353 |
|
354 |
data = {
|
355 |
+
"client_id": client_id,
|
|
|
|
|
356 |
}
|
357 |
|
358 |
# Trigger the FHE execution on the encrypted inputs previously sent
|
|
|
367 |
raise gr.Error("Please wait for the inputs to be sent to the server.")
|
368 |
|
369 |
|
370 |
+
def get_output(client_id):
|
371 |
"""Retrieve the encrypted output.
|
372 |
|
373 |
Args:
|
374 |
+
client_id (int): The current client ID to consider.
|
|
|
|
|
375 |
|
376 |
Returns:
|
377 |
encrypted_output_short (bytes): A byte short representation of the encrypted output.
|
378 |
"""
|
379 |
data = {
|
380 |
+
"client_id": client_id,
|
|
|
|
|
381 |
}
|
382 |
|
383 |
# Retrieve the encrypted output
|
|
|
391 |
|
392 |
# Save the encrypted output to bytes in a file as it is too large to pass through regular
|
393 |
# Gradio buttons (see https://github.com/gradio-app/gradio/issues/1877)
|
394 |
+
encrypted_output_path = _get_client_file_path("encrypted_output", client_id)
|
395 |
|
396 |
with encrypted_output_path.open("wb") as encrypted_output_file:
|
397 |
encrypted_output_file.write(encrypted_output)
|
|
|
404 |
raise gr.Error("Please wait for the FHE execution to be completed.")
|
405 |
|
406 |
|
407 |
+
def decrypt_output(client_id):
|
408 |
"""Decrypt the result.
|
409 |
|
410 |
Args:
|
411 |
+
client_id (int): The current client ID to consider.
|
|
|
|
|
412 |
|
413 |
Returns:
|
414 |
output(numpy.ndarray): The decrypted output
|
415 |
|
416 |
"""
|
417 |
# Get the encrypted output path
|
418 |
+
encrypted_output_path = _get_client_file_path("encrypted_output", client_id)
|
419 |
|
420 |
if not encrypted_output_path.is_file():
|
421 |
raise gr.Error("Please run the FHE execution first.")
|
|
|
425 |
encrypted_output_proba = encrypted_output_file.read()
|
426 |
|
427 |
# Retrieve the client API
|
428 |
+
client = _get_client(client_id)
|
429 |
|
430 |
# Deserialize, decrypt and post-process the encrypted output
|
431 |
output_proba = client.deserialize_decrypt_dequantize(encrypted_output_proba)
|
server.py
CHANGED
@@ -5,25 +5,34 @@ from typing import List
|
|
5 |
from fastapi import FastAPI, File, Form, UploadFile
|
6 |
from fastapi.responses import JSONResponse, Response
|
7 |
|
8 |
-
from settings import DEPLOYMENT_PATH, SERVER_FILES
|
9 |
from utils.client_server_interface import MultiInputsFHEModelServer
|
10 |
|
11 |
# Load the server
|
12 |
FHE_SERVER = MultiInputsFHEModelServer(DEPLOYMENT_PATH)
|
13 |
|
14 |
|
15 |
-
def _get_server_file_path(name, client_id, client_type):
|
16 |
-
"""Get the
|
17 |
|
18 |
Args:
|
19 |
-
name (str): The desired file name (either 'evaluation_key'
|
|
|
20 |
client_id (int): The client ID to consider.
|
21 |
-
client_type (str): The type of user to consider (either 'user', 'bank'
|
|
|
22 |
|
23 |
Returns:
|
24 |
pathlib.Path: The file path.
|
25 |
"""
|
26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
|
28 |
|
29 |
# Initialize an instance of FastAPI
|
@@ -35,6 +44,20 @@ def root():
|
|
35 |
return {"message": "Welcome to Credit Card Approval Prediction server!"}
|
36 |
|
37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
@app.post("/send_input")
|
39 |
def send_input(
|
40 |
client_id: str = Form(),
|
@@ -42,44 +65,33 @@ def send_input(
|
|
42 |
files: List[UploadFile] = File(),
|
43 |
):
|
44 |
"""Send the inputs to the server."""
|
45 |
-
# Retrieve the encrypted inputs
|
46 |
encrypted_inputs_path = _get_server_file_path("encrypted_inputs", client_id, client_type)
|
47 |
-
evaluation_key_path = _get_server_file_path("evaluation_key", client_id, client_type)
|
48 |
|
49 |
-
# Write the
|
50 |
-
with encrypted_inputs_path.open("wb") as encrypted_inputs
|
51 |
-
"wb"
|
52 |
-
) as evaluation_key:
|
53 |
encrypted_inputs.write(files[0].file.read())
|
54 |
-
evaluation_key.write(files[1].file.read())
|
55 |
|
56 |
|
57 |
@app.post("/run_fhe")
|
58 |
def run_fhe(
|
59 |
-
|
60 |
-
bank_id: str = Form(),
|
61 |
-
third_party_id: str = Form(),
|
62 |
):
|
63 |
"""Execute the model on the encrypted inputs using FHE."""
|
64 |
-
#
|
65 |
-
evaluation_key_path = _get_server_file_path("evaluation_key",
|
66 |
-
|
67 |
-
|
68 |
-
encrypted_user_inputs_path = _get_server_file_path("encrypted_inputs", user_id, "user")
|
69 |
-
encrypted_bank_inputs_path = _get_server_file_path("encrypted_inputs", bank_id, "bank")
|
70 |
-
encrypted_third_party_inputs_path = _get_server_file_path("encrypted_inputs", third_party_id, "third_party")
|
71 |
-
with (
|
72 |
-
evaluation_key_path.open("rb") as evaluation_key_file,
|
73 |
-
encrypted_user_inputs_path.open("rb") as encrypted_user_inputs_file,
|
74 |
-
encrypted_bank_inputs_path.open("rb") as encrypted_bank_inputs_file,
|
75 |
-
encrypted_third_party_inputs_path.open("rb") as encrypted_third_party_inputs_file,
|
76 |
-
):
|
77 |
evaluation_key = evaluation_key_file.read()
|
78 |
-
encrypted_user_input = encrypted_user_inputs_file.read()
|
79 |
-
encrypted_bank_input = encrypted_bank_inputs_file.read()
|
80 |
-
encrypted_third_party_input = encrypted_third_party_inputs_file.read()
|
81 |
|
82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
83 |
|
84 |
# Run the FHE execution
|
85 |
start = time.time()
|
@@ -87,7 +99,7 @@ def run_fhe(
|
|
87 |
fhe_execution_time = round(time.time() - start, 2)
|
88 |
|
89 |
# Retrieve the encrypted output path
|
90 |
-
encrypted_output_path = _get_server_file_path("encrypted_output",
|
91 |
|
92 |
# Write the file using the above path
|
93 |
with encrypted_output_path.open("wb") as output_file:
|
@@ -98,13 +110,11 @@ def run_fhe(
|
|
98 |
|
99 |
@app.post("/get_output")
|
100 |
def get_output(
|
101 |
-
|
102 |
-
bank_id: str = Form(),
|
103 |
-
third_party_id: str = Form(),
|
104 |
):
|
105 |
"""Retrieve the encrypted output."""
|
106 |
# Retrieve the encrypted output path
|
107 |
-
encrypted_output_path = _get_server_file_path("encrypted_output",
|
108 |
|
109 |
# Read the file using the above path
|
110 |
with encrypted_output_path.open("rb") as encrypted_output_file:
|
|
|
5 |
from fastapi import FastAPI, File, Form, UploadFile
|
6 |
from fastapi.responses import JSONResponse, Response
|
7 |
|
8 |
+
from settings import DEPLOYMENT_PATH, SERVER_FILES, CLIENT_TYPES
|
9 |
from utils.client_server_interface import MultiInputsFHEModelServer
|
10 |
|
11 |
# Load the server
|
12 |
FHE_SERVER = MultiInputsFHEModelServer(DEPLOYMENT_PATH)
|
13 |
|
14 |
|
15 |
+
def _get_server_file_path(name, client_id, client_type=None):
|
16 |
+
"""Get the file path for the server.
|
17 |
|
18 |
Args:
|
19 |
+
name (str): The desired file name (either 'evaluation_key', 'encrypted_inputs' or
|
20 |
+
'encrypted_outputs').
|
21 |
client_id (int): The client ID to consider.
|
22 |
+
client_type (Optional[str]): The type of user to consider (either 'user', 'bank',
|
23 |
+
'third_party' or None). Default to None, which is used for evaluation key and output.
|
24 |
|
25 |
Returns:
|
26 |
pathlib.Path: The file path.
|
27 |
"""
|
28 |
+
client_type_suffix = ""
|
29 |
+
if client_type is not None:
|
30 |
+
client_type_suffix = f"_{client_type}"
|
31 |
+
|
32 |
+
dir_path = SERVER_FILES / f"{client_id}"
|
33 |
+
dir_path.mkdir(exist_ok=True)
|
34 |
+
|
35 |
+
return dir_path / f"{name}{client_type_suffix}"
|
36 |
|
37 |
|
38 |
# Initialize an instance of FastAPI
|
|
|
44 |
return {"message": "Welcome to Credit Card Approval Prediction server!"}
|
45 |
|
46 |
|
47 |
+
@app.post("/send_eval_key")
|
48 |
+
def send_eval_key(
|
49 |
+
client_id: str = Form(),
|
50 |
+
files: List[UploadFile] = File(),
|
51 |
+
):
|
52 |
+
"""Send the evaluation key to the server."""
|
53 |
+
# Retrieve the evaluation key path
|
54 |
+
evaluation_key_path = _get_server_file_path("evaluation_key", client_id)
|
55 |
+
|
56 |
+
# Write the file using the above path
|
57 |
+
with evaluation_key_path.open("wb") as evaluation_key:
|
58 |
+
evaluation_key.write(files[0].file.read())
|
59 |
+
|
60 |
+
|
61 |
@app.post("/send_input")
|
62 |
def send_input(
|
63 |
client_id: str = Form(),
|
|
|
65 |
files: List[UploadFile] = File(),
|
66 |
):
|
67 |
"""Send the inputs to the server."""
|
68 |
+
# Retrieve the encrypted inputs
|
69 |
encrypted_inputs_path = _get_server_file_path("encrypted_inputs", client_id, client_type)
|
|
|
70 |
|
71 |
+
# Write the file using the above path
|
72 |
+
with encrypted_inputs_path.open("wb") as encrypted_inputs:
|
|
|
|
|
73 |
encrypted_inputs.write(files[0].file.read())
|
|
|
74 |
|
75 |
|
76 |
@app.post("/run_fhe")
|
77 |
def run_fhe(
|
78 |
+
client_id: str = Form(),
|
|
|
|
|
79 |
):
|
80 |
"""Execute the model on the encrypted inputs using FHE."""
|
81 |
+
# Get the evaluation key
|
82 |
+
evaluation_key_path = _get_server_file_path("evaluation_key", client_id)
|
83 |
+
|
84 |
+
with evaluation_key_path.open("rb") as evaluation_key_file:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
evaluation_key = evaluation_key_file.read()
|
|
|
|
|
|
|
86 |
|
87 |
+
# Get the encrypted inputs from all parties
|
88 |
+
encrypted_inputs = []
|
89 |
+
for client_type in CLIENT_TYPES:
|
90 |
+
encrypted_inputs_path = _get_server_file_path("encrypted_inputs", client_id, client_type)
|
91 |
+
|
92 |
+
with encrypted_inputs_path.open("rb") as encrypted_inputs_file:
|
93 |
+
encrypted_input = encrypted_inputs_file.read()
|
94 |
+
encrypted_inputs.append(encrypted_input)
|
95 |
|
96 |
# Run the FHE execution
|
97 |
start = time.time()
|
|
|
99 |
fhe_execution_time = round(time.time() - start, 2)
|
100 |
|
101 |
# Retrieve the encrypted output path
|
102 |
+
encrypted_output_path = _get_server_file_path("encrypted_output", client_id)
|
103 |
|
104 |
# Write the file using the above path
|
105 |
with encrypted_output_path.open("wb") as output_file:
|
|
|
110 |
|
111 |
@app.post("/get_output")
|
112 |
def get_output(
|
113 |
+
client_id: str = Form(),
|
|
|
|
|
114 |
):
|
115 |
"""Retrieve the encrypted output."""
|
116 |
# Retrieve the encrypted output path
|
117 |
+
encrypted_output_path = _get_server_file_path("encrypted_output", client_id)
|
118 |
|
119 |
# Read the file using the above path
|
120 |
with encrypted_output_path.open("rb") as encrypted_output_file:
|