romanbredehoft-zama commited on
Commit
bc345ce
1 Parent(s): ec21179

Generate a single key per user

Browse files
Files changed (3) hide show
  1. app.py +40 -44
  2. backend.py +102 -76
  3. server.py +48 -38
app.py CHANGED
@@ -19,9 +19,10 @@ from settings import (
19
  FAMILY_STATUS,
20
  )
21
  from backend import (
22
- pre_process_keygen_encrypt_send_user,
23
- pre_process_keygen_encrypt_send_bank,
24
- pre_process_keygen_encrypt_send_third_party,
 
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: Infos. ")
 
 
 
 
 
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 2: Keygen, encrypt using FHE and send the inputs to the server.")
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 6: Run FHE execution.")
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 7: Receive the encrypted output from the server.")
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 8: Decrypt the output.")
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
- pre_process_keygen_encrypt_send_user,
157
- inputs=[gender, bool_inputs, num_children, num_family, total_income, age, income_type, \
158
- education_type, family_status, occupation_type, housing_type],
159
- outputs=[user_id, encrypted_input_user],
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
- pre_process_keygen_encrypt_send_bank,
166
- inputs=[account_length],
167
- outputs=[bank_id, encrypted_input_bank],
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
- pre_process_keygen_encrypt_send_third_party,
174
- inputs=[employed, years_employed],
175
- outputs=[third_party_id, encrypted_input_third_party],
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=[user_id, bank_id, third_party_id], outputs=[fhe_execution_time])
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=[user_id, bank_id, third_party_id],
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=[user_id, bank_id, third_party_id],
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 file in chain(client_files, server_files):
82
  for user_id in user_ids:
83
- if user_id in file.name:
84
- file.unlink()
 
85
 
86
 
87
- def _get_client(client_id, client_type):
88
- """Get the client API.
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 API.
96
  """
97
- key_dir = FHE_KEYS / f"{client_type}_{client_id}"
98
 
99
  return MultiInputsFHEModelClient(DEPLOYMENT_PATH, key_dir=key_dir, nb_inputs=len(CLIENT_TYPES))
100
 
101
 
102
- def _keygen(client_id, client_type):
103
- """Generate the private key associated to a client.
104
 
105
  Args:
 
 
106
  client_id (int): The client ID to consider.
107
- client_type (str): The type of client to consider (either 'user', 'bank' or 'third_party').
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  """
109
  # Clean temporary files
110
  clean_temporary_files()
111
 
 
 
 
112
  # Retrieve the client instance
113
- client = _get_client(client_id, client_type)
114
 
115
- # Generate a private key
116
  client.generate_private_and_evaluation_keys(force=True)
117
 
118
- # Retrieve the serialized evaluation key. In this case, as circuits are fully leveled, this
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 evaluation_key as bytes in a file as it is too large to pass through regular Gradio
124
  # buttons (see https://github.com/gradio-app/gradio/issues/1877)
125
- evaluation_key_path = _get_client_file_path("evaluation_key", client_id, client_type)
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 evaluation key and encrypted inputs
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 _get_client_file_path(name, client_id, client_type):
164
- """Get the correct temporary file path for the client.
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, client_type)
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
- # TODO: also return private key representation if possible
219
- return client_id, encrypted_inputs_short
220
 
221
 
222
- def pre_process_keygen_encrypt_send_user(*inputs):
223
- """Pre-process the given inputs for a specific client.
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 _keygen_encrypt_send(preprocessed_user_inputs, "user")
267
 
268
 
269
- def pre_process_keygen_encrypt_send_bank(*inputs):
270
- """Pre-process the given inputs for a specific client.
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 _keygen_encrypt_send(account_length, "bank")
282
 
283
 
284
- def pre_process_keygen_encrypt_send_third_party(*inputs):
285
- """Pre-process the given inputs for a specific client.
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 _keygen_encrypt_send(preprocessed_third_party_inputs, "third_party")
307
 
308
 
309
- def run_fhe(user_id, bank_id, third_party_id):
310
  """Run the model on the encrypted inputs previously sent using FHE.
311
 
312
  Args:
313
- user_id (int): The user ID to consider.
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
- "user_id": user_id,
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(user_id, bank_id, third_party_id):
339
  """Retrieve the encrypted output.
340
 
341
  Args:
342
- user_id (int): The user ID to consider.
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
- "user_id": user_id,
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", user_id + bank_id + third_party_id, "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(user_id, bank_id, third_party_id):
380
  """Decrypt the result.
381
 
382
  Args:
383
- user_id (int): The user ID to consider.
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", user_id + bank_id + third_party_id, "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(user_id, "user")
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 correct temporary file path for the server.
17
 
18
  Args:
19
- name (str): The desired file name (either 'evaluation_key' or 'encrypted_inputs').
 
20
  client_id (int): The client ID to consider.
21
- client_type (str): The type of user to consider (either 'user', 'bank' or 'third_party').
 
22
 
23
  Returns:
24
  pathlib.Path: The file path.
25
  """
26
- return SERVER_FILES / f"{name}_{client_type}_{client_id}"
 
 
 
 
 
 
 
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 and the evaluation key paths
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 files using the above paths
50
- with encrypted_inputs_path.open("wb") as encrypted_inputs, evaluation_key_path.open(
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
- user_id: str = Form(),
60
- bank_id: str = Form(),
61
- third_party_id: str = Form(),
62
  ):
63
  """Execute the model on the encrypted inputs using FHE."""
64
- # Retrieve the evaluation key (from the user, as all evaluation keys should be the same)
65
- evaluation_key_path = _get_server_file_path("evaluation_key", user_id, "user")
66
-
67
- # Get the encrypted inputs
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
- encrypted_inputs = (encrypted_user_input, encrypted_bank_input, encrypted_third_party_input)
 
 
 
 
 
 
 
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", user_id + bank_id + third_party_id, "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
- user_id: str = Form(),
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", user_id + bank_id + third_party_id, "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: