tagny commited on
Commit
0908a41
1 Parent(s): a1cf584

image pairs matching - issue on config file in server.zip

Browse files
.gitignore CHANGED
@@ -7,3 +7,4 @@ server_tmp/
7
  client_tmp/
8
  .artifacts
9
  __pycache__/
 
 
7
  client_tmp/
8
  .artifacts
9
  __pycache__/
10
+ .python-version
README.md CHANGED
@@ -43,7 +43,7 @@ In a terminal, run:
43
 
44
  ```bash
45
  source .venv/bin/activate
46
- python3 app.py
47
  ```
48
 
49
  ## Interact with the application
 
43
 
44
  ```bash
45
  source .venv/bin/activate
46
+ python app.py
47
  ```
48
 
49
  ## Interact with the application
app.py CHANGED
@@ -1,4 +1,5 @@
1
- """A local gradio app that filters images using FHE."""
 
2
  from PIL import Image
3
  import os
4
  import shutil
@@ -10,11 +11,14 @@ import requests
10
  from itertools import chain
11
 
12
  from common import (
13
- AVAILABLE_FILTERS,
14
  CLIENT_TMP_PATH,
 
 
 
15
  SERVER_TMP_PATH,
16
  EXAMPLES,
17
- FILTERS_PATH,
18
  INPUT_SHAPE,
19
  KEYS_PATH,
20
  REPO_DIR,
@@ -27,31 +31,34 @@ subprocess.Popen(["uvicorn", "server:app"], cwd=REPO_DIR)
27
  time.sleep(3)
28
 
29
 
30
- def decrypt_output_with_wrong_key(encrypted_image, filter_name):
31
- """Decrypt the encrypted output using a different private key.
32
- """
33
- # Retrieve the filter's deployment path
34
- filter_path = FILTERS_PATH / f"{filter_name}/deployment"
35
 
36
  # Instantiate the client interface and generate a new private key
37
- wrong_client = FHEClient(filter_path, filter_name)
38
  wrong_client.generate_private_and_evaluation_keys(force=True)
39
 
40
  # Deserialize, decrypt and post-process the encrypted output using the new private key
41
- output_image = wrong_client.deserialize_decrypt_post_process(encrypted_image)
42
 
43
- # For filters that are expected to output black and white images, generate two other random
44
- # channels for better display
45
- if filter_name in ["black and white", "ridge detection"]:
46
- # Green channel
47
- wrong_client.generate_private_and_evaluation_keys(force=True)
48
- output_image[:, :, 1] = wrong_client.deserialize_decrypt_post_process(encrypted_image)[:, :, 0]
 
 
49
 
50
- # Blue channel
51
- wrong_client.generate_private_and_evaluation_keys(force=True)
52
- output_image[:, :, 2] = wrong_client.deserialize_decrypt_post_process(encrypted_image)[:, :, 0]
 
 
53
 
54
- return output_image
55
 
56
 
57
  def shorten_bytes_object(bytes_object, limit=500):
@@ -65,7 +72,7 @@ def shorten_bytes_object(bytes_object, limit=500):
65
  limit (int): The length to consider. Default to 500.
66
 
67
  Returns:
68
- str: Hexadecimal string shorten representation of the input byte object.
69
 
70
  """
71
  # Define a shift for better display
@@ -73,41 +80,41 @@ def shorten_bytes_object(bytes_object, limit=500):
73
  return bytes_object[shift : limit + shift].hex()
74
 
75
 
76
- def get_client(user_id, filter_name):
77
  """Get the client API.
78
 
79
  Args:
80
  user_id (int): The current user's ID.
81
- filter_name (str): The filter chosen by the user
82
 
83
  Returns:
84
  FHEClient: The client API.
85
  """
86
  return FHEClient(
87
- FILTERS_PATH / f"{filter_name}/deployment",
88
- filter_name,
89
- key_dir=KEYS_PATH / f"{filter_name}_{user_id}",
90
  )
91
 
92
 
93
- def get_client_file_path(name, user_id, filter_name):
94
  """Get the correct temporary file path for the client.
95
 
96
  Args:
97
  name (str): The desired file name.
98
  user_id (int): The current user's ID.
99
- filter_name (str): The filter chosen by the user
100
 
101
  Returns:
102
  pathlib.Path: The file path.
103
  """
104
- return CLIENT_TMP_PATH / f"{name}_{filter_name}_{user_id}"
105
 
106
 
107
  def clean_temporary_files(n_keys=20):
108
  """Clean keys and encrypted images.
109
 
110
- A maximum of n_keys keys and associated temporary files are allowed to be stored. Once this
111
  limit is reached, the oldest files are deleted.
112
 
113
  Args:
@@ -136,11 +143,11 @@ def clean_temporary_files(n_keys=20):
136
  file.unlink()
137
 
138
 
139
- def keygen(filter_name):
140
- """Generate the private key associated to a filter.
141
 
142
  Args:
143
- filter_name (str): The current filter to consider.
144
 
145
  Returns:
146
  (user_id, True) (Tuple[int, bool]): The current user's ID and a boolean used for visual display.
@@ -153,7 +160,7 @@ def keygen(filter_name):
153
  user_id = numpy.random.randint(0, 2**32)
154
 
155
  # Retrieve the client API
156
- client = get_client(user_id, filter_name)
157
 
158
  # Generate a private key
159
  client.generate_private_and_evaluation_keys(force=True)
@@ -165,7 +172,7 @@ def keygen(filter_name):
165
 
166
  # Save evaluation_key as bytes in a file as it is too large to pass through regular Gradio
167
  # buttons (see https://github.com/gradio-app/gradio/issues/1877)
168
- evaluation_key_path = get_client_file_path("evaluation_key", user_id, filter_name)
169
 
170
  with evaluation_key_path.open("wb") as evaluation_key_file:
171
  evaluation_key_file.write(evaluation_key)
@@ -173,13 +180,18 @@ def keygen(filter_name):
173
  return (user_id, True)
174
 
175
 
176
- def encrypt(user_id, input_image, filter_name):
177
- """Encrypt the given image for a specific user and filter.
 
 
178
 
179
  Args:
180
  user_id (int): The current user's ID.
181
  input_image (numpy.ndarray): The image to encrypt.
182
- filter_name (str): The current filter to consider.
 
 
 
183
 
184
  Returns:
185
  (input_image, encrypted_image_short) (Tuple[bytes]): The encrypted image and one of its
@@ -191,25 +203,34 @@ def encrypt(user_id, input_image, filter_name):
191
 
192
  if input_image is None:
193
  raise gr.Error("Please choose an image first.")
194
-
195
- if input_image.shape[-1] != 3:
196
- raise ValueError(f"Input image must have 3 channels (RGB). Current shape: {input_image.shape}")
197
-
198
- # Resize the image if it hasn't the shape (100, 100, 3)
199
- if input_image.shape != (100 , 100, 3):
 
 
 
 
 
 
 
200
  input_image_pil = Image.fromarray(input_image)
201
- input_image_pil = input_image_pil.resize((100, 100))
202
  input_image = numpy.array(input_image_pil)
203
 
204
  # Retrieve the client API
205
- client = get_client(user_id, filter_name)
206
 
207
  # Pre-process, encrypt and serialize the image
208
  encrypted_image = client.encrypt_serialize(input_image)
209
 
210
  # Save encrypted_image to bytes in a file, since too large to pass through regular Gradio
211
  # buttons, https://github.com/gradio-app/gradio/issues/1877
212
- encrypted_image_path = get_client_file_path("encrypted_image", user_id, filter_name)
 
 
213
 
214
  with encrypted_image_path.open("wb") as encrypted_image_file:
215
  encrypted_image_file.write(encrypted_image)
@@ -220,32 +241,45 @@ def encrypt(user_id, input_image, filter_name):
220
  return (resize_img(input_image), encrypted_image_short)
221
 
222
 
223
- def send_input(user_id, filter_name):
224
- """Send the encrypted input image as well as the evaluation key to the server.
225
 
226
  Args:
227
  user_id (int): The current user's ID.
228
- filter_name (str): The current filter to consider.
229
  """
230
  # Get the evaluation key path
231
- evaluation_key_path = get_client_file_path("evaluation_key", user_id, filter_name)
232
 
233
  if user_id == "" or not evaluation_key_path.is_file():
234
  raise gr.Error("Please generate the private key first.")
235
 
236
- encrypted_input_path = get_client_file_path("encrypted_image", user_id, filter_name)
 
 
 
 
 
 
237
 
238
- if not encrypted_input_path.is_file():
239
- raise gr.Error("Please generate the private key and then encrypt an image first.")
 
 
 
 
 
 
240
 
241
  # Define the data and files to post
242
  data = {
243
  "user_id": user_id,
244
- "filter": filter_name,
245
  }
246
 
247
  files = [
248
- ("files", open(encrypted_input_path, "rb")),
 
249
  ("files", open(evaluation_key_path, "rb")),
250
  ]
251
 
@@ -259,16 +293,16 @@ def send_input(user_id, filter_name):
259
  return response.ok
260
 
261
 
262
- def run_fhe(user_id, filter_name):
263
- """Apply the filter on the encrypted image previously sent using FHE.
264
 
265
  Args:
266
  user_id (int): The current user's ID.
267
- filter_name (str): The current filter to consider.
268
  """
269
  data = {
270
  "user_id": user_id,
271
- "filter": filter_name,
272
  }
273
 
274
  # Trigger the FHE execution on the encrypted image previously sent
@@ -280,23 +314,25 @@ def run_fhe(user_id, filter_name):
280
  if response.ok:
281
  return response.json()
282
  else:
283
- raise gr.Error("Please wait for the input image to be sent to the server.")
 
 
284
 
285
 
286
- def get_output(user_id, filter_name):
287
- """Retrieve the encrypted output image.
288
 
289
  Args:
290
  user_id (int): The current user's ID.
291
- filter_name (str): The current filter to consider.
292
 
293
  Returns:
294
- encrypted_output_image_short (bytes): A representation of the encrypted result.
295
 
296
  """
297
  data = {
298
  "user_id": user_id,
299
- "filter": filter_name,
300
  }
301
 
302
  # Retrieve the encrypted output image
@@ -310,26 +346,35 @@ def get_output(user_id, filter_name):
310
 
311
  # Save the encrypted output to bytes in a file as it is too large to pass through regular
312
  # Gradio buttons (see https://github.com/gradio-app/gradio/issues/1877)
313
- encrypted_output_path = get_client_file_path("encrypted_output", user_id, filter_name)
 
 
314
 
315
  with encrypted_output_path.open("wb") as encrypted_output_file:
316
  encrypted_output_file.write(encrypted_output)
317
 
318
- # Decrypt the image using a different (wrong) key for display
319
- output_image_representation = decrypt_output_with_wrong_key(encrypted_output, filter_name)
 
 
320
 
321
- return {encrypted_output_representation: gr.update(value=resize_img(output_image_representation))}
 
 
 
 
 
322
 
323
  else:
324
  raise gr.Error("Please wait for the FHE execution to be completed.")
325
 
326
 
327
- def decrypt_output(user_id, filter_name):
328
  """Decrypt the result.
329
 
330
  Args:
331
  user_id (int): The current user's ID.
332
- filter_name (str): The current filter to consider.
333
 
334
  Returns:
335
  (output_image, False, False) ((Tuple[numpy.ndarray, bool, bool]): The decrypted output, as
@@ -340,24 +385,26 @@ def decrypt_output(user_id, filter_name):
340
  raise gr.Error("Please generate the private key first.")
341
 
342
  # Get the encrypted output path
343
- encrypted_output_path = get_client_file_path("encrypted_output", user_id, filter_name)
 
 
344
 
345
  if not encrypted_output_path.is_file():
346
  raise gr.Error("Please run the FHE execution first.")
347
 
348
  # Load the encrypted output as bytes
349
  with encrypted_output_path.open("rb") as encrypted_output_file:
350
- encrypted_output_image = encrypted_output_file.read()
351
 
352
  # Retrieve the client API
353
- client = get_client(user_id, filter_name)
354
 
355
  # Deserialize, decrypt and post-process the encrypted output
356
- decrypted_ouput = client.deserialize_decrypt_post_process(encrypted_output_image)
357
 
358
  print(f"Decrypted output: {decrypted_ouput.shape=}")
359
-
360
- return {output_image: gr.update(value=resize_img(decrypted_ouput))}
361
 
362
 
363
  def resize_img(img, width=256, height=256):
@@ -370,6 +417,7 @@ def resize_img(img, width=256, height=256):
370
  # Convert back to a NumPy array
371
  return numpy.array(resized_img_pil)
372
 
 
373
  demo = gr.Blocks()
374
 
375
 
@@ -377,10 +425,11 @@ print("Starting the demo...")
377
  with demo:
378
  gr.Markdown(
379
  """
380
- <p align="center">
381
  <img width=200 src="https://user-images.githubusercontent.com/5758427/197816413-d9cddad3-ba38-4793-847d-120975e1da11.png">
382
- </p>
383
- <h1 align="center">Image Filtering On Encrypted Data Using Fully Homomorphic Encryption</h1>
 
384
  <p align="center">
385
  <a href="https://github.com/zama-ai/concrete-ml"> <img style="vertical-align: middle; display:inline-block; margin-right: 3px;" width=15 src="https://user-images.githubusercontent.com/5758427/197972109-faaaff3e-10e2-4ab6-80f5-7531f7cfb08f.png">Concrete-ML</a>
386
 
@@ -390,48 +439,70 @@ with demo:
390
 
391
  <a href="https://twitter.com/zama_fhe"> <img style="vertical-align: middle; display:inline-block; margin-right: 3px;" width=15 src="https://user-images.githubusercontent.com/5758427/197975044-bab9d199-e120-433b-b3be-abd73b211a54.png">@zama_fhe</a>
392
  </p>
393
- <p align="center">
394
  <img src="https://user-images.githubusercontent.com/56846628/219605302-5baafac4-cf6f-4f06-9a96-91cef2b84a63.png" width="70%" height="70%">
395
- </p>
396
- <p align="center">
397
- Test the app below, review
398
- <a href="https://zama.ai/post/encrypted-image-filtering-using-homomorphic-encryption"> our tutorial</a>
399
- , and try the build for yourself!
400
- </p>
401
  """
402
  )
403
 
404
  gr.Markdown("## Client side")
405
- gr.Markdown("### Step 1: Upload an image. ")
406
  gr.Markdown(
407
  f"The image will automatically be resized to shape ({INPUT_SHAPE[0]}x{INPUT_SHAPE[1]}). "
408
  "The image here, however, is displayed in its original resolution. The true image used "
409
  "in this demo can be seen in Step 8."
410
  )
 
411
  with gr.Row():
412
- input_image = gr.Image(
413
- value=None, label="Upload an image here.", height=256,
414
- width=256, sources="upload", interactive=True,
 
 
 
 
415
  )
416
 
417
  examples = gr.Examples(
418
- examples=EXAMPLES, inputs=[input_image], examples_per_page=5, label="Examples to use."
 
 
 
 
 
 
 
 
 
 
 
 
 
419
  )
420
 
 
 
 
 
 
 
421
 
422
- gr.Markdown("### Step 2: Choose your filter.")
423
- filter_name = gr.Dropdown(
424
- choices=AVAILABLE_FILTERS, value="inverted", label="Choose your filter", interactive=True
 
 
 
425
  )
426
 
427
  gr.Markdown("#### Notes")
428
  gr.Markdown(
429
  """
430
  - The private key is used to encrypt and decrypt the data and will never be shared.
431
- - No public key is required for these filter operators.
432
  """
433
  )
434
-
435
  gr.Markdown("### Step 3: Generate the private key.")
436
  keygen_button = gr.Button("Generate the private key.")
437
 
@@ -439,25 +510,49 @@ with demo:
439
  keygen_checkbox = gr.Checkbox(label="Private key generated:", interactive=False)
440
 
441
  user_id = gr.Textbox(label="", max_lines=2, interactive=False, visible=False)
442
-
443
- gr.Markdown("### Step 4: Encrypt the image using FHE.")
444
- encrypt_button = gr.Button("Encrypt the image using FHE.")
 
 
 
 
 
 
 
 
 
 
 
445
 
 
 
 
 
 
 
 
 
 
 
 
446
  with gr.Row():
447
- encrypted_input = gr.Textbox(
448
- label="Encrypted input representation:", max_lines=2, interactive=False
 
 
449
  )
450
 
451
  gr.Markdown("## Server side")
452
  gr.Markdown(
453
- "The encrypted value is received by the server. The server can then compute the filter "
454
  "directly over encrypted values. Once the computation is finished, the server returns "
455
  "the encrypted results to the client."
456
  )
457
 
458
- gr.Markdown("### Step 5: Send the encrypted image to the server.")
459
- send_input_button = gr.Button("Send the encrypted image to the server.")
460
- send_input_checkbox = gr.Checkbox(label="Encrypted image sent.", interactive=False)
461
 
462
  gr.Markdown("### Step 6: Run FHE execution.")
463
  execute_fhe_button = gr.Button("Run FHE execution.")
@@ -465,86 +560,105 @@ with demo:
465
  label="Total FHE execution time (in seconds):", max_lines=1, interactive=False
466
  )
467
 
468
- gr.Markdown("### Step 7: Receive the encrypted output image from the server.")
469
  gr.Markdown(
470
- "The image displayed here is the encrypted result sent by the server, which has been "
471
  "decrypted using a different private key. This is only used to visually represent an "
472
- "encrypted image."
 
 
 
473
  )
474
- get_output_button = gr.Button("Receive the encrypted output image from the server.")
475
 
476
  with gr.Row():
477
- encrypted_output_representation = gr.Image(
478
- label=f"Encrypted output representation ({INPUT_SHAPE[0]}x{INPUT_SHAPE[1]}):",
479
- interactive=False,
480
- height=256,
481
- width=256,
482
- )
 
483
 
484
  gr.Markdown("## Client side")
485
  gr.Markdown(
486
  "The encrypted output is sent back to the client, who can finally decrypt it with the "
487
- "private key. Only the client is aware of the original image and its transformed version."
488
  )
489
 
490
  gr.Markdown("### Step 8: Decrypt the output.")
491
  gr.Markdown(
492
- "The image displayed on the left is the input image used during the demo. The output image "
493
  "can be seen on the right."
494
- )
495
  decrypt_button = gr.Button("Decrypt the output")
496
 
497
  # Final input vs output display
498
  with gr.Row():
499
- original_image = gr.Image(
500
- input_image.value,
501
- label=f"Input image ({INPUT_SHAPE[0]}x{INPUT_SHAPE[1]}):",
502
  interactive=False,
503
  height=256,
504
- width=256,
505
  )
506
-
507
- output_image = gr.Image(
508
- label=f"Output image ({INPUT_SHAPE[0]}x{INPUT_SHAPE[1]}):",
509
  interactive=False,
510
  height=256,
511
  width=256,
512
  )
 
 
 
 
 
 
 
513
 
514
  # Button to generate the private key
515
  keygen_button.click(
516
  keygen,
517
- inputs=[filter_name],
518
  outputs=[user_id, keygen_checkbox],
519
  )
520
 
521
- # Button to encrypt inputs on the client side
522
- encrypt_button.click(
 
 
 
 
 
 
 
523
  encrypt,
524
- inputs=[user_id, input_image, filter_name],
525
- outputs=[original_image, encrypted_input],
526
  )
527
 
528
  # Button to send the encodings to the server using post method
529
  send_input_button.click(
530
- send_input, inputs=[user_id, filter_name], outputs=[send_input_checkbox]
531
  )
532
 
533
  # Button to send the encodings to the server using post method
534
- execute_fhe_button.click(run_fhe, inputs=[user_id, filter_name], outputs=[fhe_execution_time])
 
 
535
 
536
  # Button to send the encodings to the server using post method
537
  get_output_button.click(
538
- get_output,
539
- inputs=[user_id, filter_name],
540
- outputs=[encrypted_output_representation]
541
  )
542
 
543
  # Button to decrypt the output on the client side
544
  decrypt_button.click(
545
  decrypt_output,
546
- inputs=[user_id, filter_name],
547
- outputs=[output_image, keygen_checkbox, send_input_checkbox],
548
  )
549
 
550
  gr.Markdown(
 
1
+ """A local gradio app that detect matching images using FHE."""
2
+
3
  from PIL import Image
4
  import os
5
  import shutil
 
11
  from itertools import chain
12
 
13
  from common import (
14
+ AVAILABLE_MATCHERS,
15
  CLIENT_TMP_PATH,
16
+ ENCRYPTED_OUTPUT_NAME,
17
+ ENCRYPTED_QUERY_NAME,
18
+ ENCRYPTED_REFERENCE_NAME,
19
  SERVER_TMP_PATH,
20
  EXAMPLES,
21
+ MATCHERS_PATH,
22
  INPUT_SHAPE,
23
  KEYS_PATH,
24
  REPO_DIR,
 
31
  time.sleep(3)
32
 
33
 
34
+ def decrypt_output_with_wrong_key(encrypted_image, matcher_name):
35
+ """Decrypt the encrypted output using a different private key."""
36
+ # Retrieve the matcher's deployment path
37
+ matcher_path = MATCHERS_PATH / f"{matcher_name}/deployment"
 
38
 
39
  # Instantiate the client interface and generate a new private key
40
+ wrong_client = FHEClient(matcher_path, matcher_name)
41
  wrong_client.generate_private_and_evaluation_keys(force=True)
42
 
43
  # Deserialize, decrypt and post-process the encrypted output using the new private key
44
+ output_result = wrong_client.deserialize_decrypt_post_process(encrypted_image)
45
 
46
+ # # For matchers that are expected to output black and white images, generate two other random
47
+ # # channels for better display
48
+ # if matcher_name in ["black and white", "ridge detection"]:
49
+ # # Green channel
50
+ # wrong_client.generate_private_and_evaluation_keys(force=True)
51
+ # output_result[:, :, 1] = wrong_client.deserialize_decrypt_post_process(
52
+ # encrypted_image
53
+ # )[:, :, 0]
54
 
55
+ # # Blue channel
56
+ # wrong_client.generate_private_and_evaluation_keys(force=True)
57
+ # output_result[:, :, 2] = wrong_client.deserialize_decrypt_post_process(
58
+ # encrypted_image
59
+ # )[:, :, 0]
60
 
61
+ return output_result
62
 
63
 
64
  def shorten_bytes_object(bytes_object, limit=500):
 
72
  limit (int): The length to consider. Default to 500.
73
 
74
  Returns:
75
+ str: Hexadecimal string shorten representation of the input byte object.
76
 
77
  """
78
  # Define a shift for better display
 
80
  return bytes_object[shift : limit + shift].hex()
81
 
82
 
83
+ def get_client(user_id, matcher_name):
84
  """Get the client API.
85
 
86
  Args:
87
  user_id (int): The current user's ID.
88
+ matcher_name (str): The matcher chosen by the user
89
 
90
  Returns:
91
  FHEClient: The client API.
92
  """
93
  return FHEClient(
94
+ MATCHERS_PATH / f"{matcher_name}/deployment",
95
+ matcher_name,
96
+ key_dir=KEYS_PATH / f"{matcher_name}_{user_id}",
97
  )
98
 
99
 
100
+ def get_client_file_path(name, user_id, matcher_name):
101
  """Get the correct temporary file path for the client.
102
 
103
  Args:
104
  name (str): The desired file name.
105
  user_id (int): The current user's ID.
106
+ matcher_name (str): The matcher chosen by the user
107
 
108
  Returns:
109
  pathlib.Path: The file path.
110
  """
111
+ return CLIENT_TMP_PATH / f"{name}_{matcher_name}_{user_id}"
112
 
113
 
114
  def clean_temporary_files(n_keys=20):
115
  """Clean keys and encrypted images.
116
 
117
+ A maximum of n_keys keys and associated temporary files are allowed to be stored. Once this
118
  limit is reached, the oldest files are deleted.
119
 
120
  Args:
 
143
  file.unlink()
144
 
145
 
146
+ def keygen(matcher_name):
147
+ """Generate the private key associated to a matcher.
148
 
149
  Args:
150
+ matcher_name (str): The current matcher to consider.
151
 
152
  Returns:
153
  (user_id, True) (Tuple[int, bool]): The current user's ID and a boolean used for visual display.
 
160
  user_id = numpy.random.randint(0, 2**32)
161
 
162
  # Retrieve the client API
163
+ client = get_client(user_id, matcher_name)
164
 
165
  # Generate a private key
166
  client.generate_private_and_evaluation_keys(force=True)
 
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", user_id, matcher_name)
176
 
177
  with evaluation_key_path.open("wb") as evaluation_key_file:
178
  evaluation_key_file.write(evaluation_key)
 
180
  return (user_id, True)
181
 
182
 
183
+ def encrypt(
184
+ user_id, input_image, matcher_name, encrypted_image_name: str = "encrypted_image"
185
+ ):
186
+ """Encrypt the given image for a specific user and matcher.
187
 
188
  Args:
189
  user_id (int): The current user's ID.
190
  input_image (numpy.ndarray): The image to encrypt.
191
+ matcher_name (str): The current matcher to consider.
192
+ encrypted_image_name (str): how to name the encrypted image
193
+ to distinguish between the query and the reference images.
194
+ Defaults to "encrypted_image"
195
 
196
  Returns:
197
  (input_image, encrypted_image_short) (Tuple[bytes]): The encrypted image and one of its
 
203
 
204
  if input_image is None:
205
  raise gr.Error("Please choose an image first.")
206
+
207
+ if input_image.shape[-1] not in {3, 4}:
208
+ raise ValueError(
209
+ f"Input image must have 3 channels (RGB) or 4 channels. Current shape: {input_image.shape}"
210
+ )
211
+
212
+ if input_image.shape[-1] == 4:
213
+ # Discarding alpha channel from images stored as Numpy arrays
214
+ # (reference https://stackoverflow.com/questions/35902302/discarding-alpha-channel-from-images-stored-as-numpy-arrays)
215
+ input_image = input_image[:, :, :3]
216
+
217
+ # Resize the image if it hasn't the shape (INPUT_SHAPE[0], INPUT_SHAPE[1], 3)
218
+ if input_image.shape != (INPUT_SHAPE[0], INPUT_SHAPE[1], 3):
219
  input_image_pil = Image.fromarray(input_image)
220
+ input_image_pil = input_image_pil.resize((INPUT_SHAPE[0], INPUT_SHAPE[1]))
221
  input_image = numpy.array(input_image_pil)
222
 
223
  # Retrieve the client API
224
+ client = get_client(user_id, matcher_name)
225
 
226
  # Pre-process, encrypt and serialize the image
227
  encrypted_image = client.encrypt_serialize(input_image)
228
 
229
  # Save encrypted_image to bytes in a file, since too large to pass through regular Gradio
230
  # buttons, https://github.com/gradio-app/gradio/issues/1877
231
+ encrypted_image_path = get_client_file_path(
232
+ encrypted_image_name, user_id, matcher_name
233
+ )
234
 
235
  with encrypted_image_path.open("wb") as encrypted_image_file:
236
  encrypted_image_file.write(encrypted_image)
 
241
  return (resize_img(input_image), encrypted_image_short)
242
 
243
 
244
+ def send_input(user_id, matcher_name):
245
+ """Send the encrypted input images as well as the evaluation key to the server.
246
 
247
  Args:
248
  user_id (int): The current user's ID.
249
+ matcher_name (str): The current matcher to consider.
250
  """
251
  # Get the evaluation key path
252
+ evaluation_key_path = get_client_file_path("evaluation_key", user_id, matcher_name)
253
 
254
  if user_id == "" or not evaluation_key_path.is_file():
255
  raise gr.Error("Please generate the private key first.")
256
 
257
+ encrypted_query_image_path = get_client_file_path(
258
+ ENCRYPTED_QUERY_NAME, user_id, matcher_name
259
+ )
260
+
261
+ encrypted_reference_image_path = get_client_file_path(
262
+ ENCRYPTED_REFERENCE_NAME, user_id, matcher_name
263
+ )
264
 
265
+ for encrypted_input_path in {
266
+ encrypted_query_image_path,
267
+ encrypted_reference_image_path,
268
+ }:
269
+ if not encrypted_input_path.is_file():
270
+ raise gr.Error(
271
+ f"Please generate the private key and then encrypt an image first: {encrypted_input_path}"
272
+ )
273
 
274
  # Define the data and files to post
275
  data = {
276
  "user_id": user_id,
277
+ "matcher": matcher_name,
278
  }
279
 
280
  files = [
281
+ ("files", open(encrypted_query_image_path, "rb")),
282
+ ("files", open(encrypted_reference_image_path, "rb")),
283
  ("files", open(evaluation_key_path, "rb")),
284
  ]
285
 
 
293
  return response.ok
294
 
295
 
296
+ def run_fhe(user_id, matcher_name):
297
+ """Apply the matcher on the encrypted image previously sent using FHE.
298
 
299
  Args:
300
  user_id (int): The current user's ID.
301
+ matcher_name (str): The current matcher to consider.
302
  """
303
  data = {
304
  "user_id": user_id,
305
+ "matcher": matcher_name,
306
  }
307
 
308
  # Trigger the FHE execution on the encrypted image previously sent
 
314
  if response.ok:
315
  return response.json()
316
  else:
317
+ print(f"ERROR run_fhe: {response}")
318
+
319
+ raise gr.Error("Please wait for the input images to be sent to the server.")
320
 
321
 
322
+ def get_output(user_id, matcher_name):
323
+ """Retrieve the encrypted output.
324
 
325
  Args:
326
  user_id (int): The current user's ID.
327
+ matcher_name (str): The current filter to consider.
328
 
329
  Returns:
330
+ encrypted_output_short (bytes): A representation of the encrypted result.
331
 
332
  """
333
  data = {
334
  "user_id": user_id,
335
+ "matcher": matcher_name,
336
  }
337
 
338
  # Retrieve the encrypted output image
 
346
 
347
  # Save the encrypted output to bytes in a file as it is too large to pass through regular
348
  # Gradio buttons (see https://github.com/gradio-app/gradio/issues/1877)
349
+ encrypted_output_path = get_client_file_path(
350
+ ENCRYPTED_OUTPUT_NAME, user_id, matcher_name
351
+ )
352
 
353
  with encrypted_output_path.open("wb") as encrypted_output_file:
354
  encrypted_output_file.write(encrypted_output)
355
 
356
+ # Decrypt the output using a different (wrong) key for display
357
+ output_representation = decrypt_output_with_wrong_key(
358
+ encrypted_output, matcher_name
359
+ )
360
 
361
+ return {
362
+ encrypted_output_representation: gr.update(
363
+ value=output_representation
364
+ # value=resize_img(output_image_representation)
365
+ )
366
+ }
367
 
368
  else:
369
  raise gr.Error("Please wait for the FHE execution to be completed.")
370
 
371
 
372
+ def decrypt_output(user_id, matcher_name):
373
  """Decrypt the result.
374
 
375
  Args:
376
  user_id (int): The current user's ID.
377
+ matcher_name (str): The current matcher to consider.
378
 
379
  Returns:
380
  (output_image, False, False) ((Tuple[numpy.ndarray, bool, bool]): The decrypted output, as
 
385
  raise gr.Error("Please generate the private key first.")
386
 
387
  # Get the encrypted output path
388
+ encrypted_output_path = get_client_file_path(
389
+ ENCRYPTED_OUTPUT_NAME, user_id, matcher_name
390
+ )
391
 
392
  if not encrypted_output_path.is_file():
393
  raise gr.Error("Please run the FHE execution first.")
394
 
395
  # Load the encrypted output as bytes
396
  with encrypted_output_path.open("rb") as encrypted_output_file:
397
+ encrypted_output = encrypted_output_file.read()
398
 
399
  # Retrieve the client API
400
+ client = get_client(user_id, matcher_name)
401
 
402
  # Deserialize, decrypt and post-process the encrypted output
403
+ decrypted_ouput = client.deserialize_decrypt_post_process(encrypted_output)
404
 
405
  print(f"Decrypted output: {decrypted_ouput.shape=}")
406
+
407
+ return {output_result: gr.update(value=decrypted_ouput)}
408
 
409
 
410
  def resize_img(img, width=256, height=256):
 
417
  # Convert back to a NumPy array
418
  return numpy.array(resized_img_pil)
419
 
420
+
421
  demo = gr.Blocks()
422
 
423
 
 
425
  with demo:
426
  gr.Markdown(
427
  """
428
+ <!--p align="center">
429
  <img width=200 src="https://user-images.githubusercontent.com/5758427/197816413-d9cddad3-ba38-4793-847d-120975e1da11.png">
430
+ </p-->
431
+ <h1 align="center"> #ppaihackteam14 </h1>
432
+ <h1 align="center">Biometric image matching Using Fully Homomorphic Encryption</h1>
433
  <p align="center">
434
  <a href="https://github.com/zama-ai/concrete-ml"> <img style="vertical-align: middle; display:inline-block; margin-right: 3px;" width=15 src="https://user-images.githubusercontent.com/5758427/197972109-faaaff3e-10e2-4ab6-80f5-7531f7cfb08f.png">Concrete-ML</a>
435
 
 
439
 
440
  <a href="https://twitter.com/zama_fhe"> <img style="vertical-align: middle; display:inline-block; margin-right: 3px;" width=15 src="https://user-images.githubusercontent.com/5758427/197975044-bab9d199-e120-433b-b3be-abd73b211a54.png">@zama_fhe</a>
441
  </p>
442
+ <!--p align="center">
443
  <img src="https://user-images.githubusercontent.com/56846628/219605302-5baafac4-cf6f-4f06-9a96-91cef2b84a63.png" width="70%" height="70%">
444
+ </p-->
 
 
 
 
 
445
  """
446
  )
447
 
448
  gr.Markdown("## Client side")
449
+ gr.Markdown("### Step 1: Upload input images. ")
450
  gr.Markdown(
451
  f"The image will automatically be resized to shape ({INPUT_SHAPE[0]}x{INPUT_SHAPE[1]}). "
452
  "The image here, however, is displayed in its original resolution. The true image used "
453
  "in this demo can be seen in Step 8."
454
  )
455
+ gr.Markdown("The query image to certify.")
456
  with gr.Row():
457
+ input_query_img = gr.Image(
458
+ value=None,
459
+ label="Upload an image here.",
460
+ height=256,
461
+ width=256,
462
+ source="upload",
463
+ interactive=True,
464
  )
465
 
466
  examples = gr.Examples(
467
+ examples=EXAMPLES,
468
+ inputs=[input_query_img],
469
+ examples_per_page=5,
470
+ label="Examples to use.",
471
+ )
472
+ gr.Markdown("The reference image.")
473
+ with gr.Row():
474
+ input_reference_img = gr.Image(
475
+ value=None,
476
+ label="Upload an image here.",
477
+ height=256,
478
+ width=256,
479
+ source="upload",
480
+ interactive=True,
481
  )
482
 
483
+ examples = gr.Examples(
484
+ examples=EXAMPLES,
485
+ inputs=[input_reference_img],
486
+ examples_per_page=5,
487
+ label="Examples to use.",
488
+ )
489
 
490
+ gr.Markdown("### Step 2: Choose your matcher.")
491
+ matcher_name = gr.Dropdown(
492
+ choices=AVAILABLE_MATCHERS,
493
+ value="random guessing",
494
+ label="Choose your matcher",
495
+ interactive=True,
496
  )
497
 
498
  gr.Markdown("#### Notes")
499
  gr.Markdown(
500
  """
501
  - The private key is used to encrypt and decrypt the data and will never be shared.
502
+ - No public key is required for these matcher operators.
503
  """
504
  )
505
+
506
  gr.Markdown("### Step 3: Generate the private key.")
507
  keygen_button = gr.Button("Generate the private key.")
508
 
 
510
  keygen_checkbox = gr.Checkbox(label="Private key generated:", interactive=False)
511
 
512
  user_id = gr.Textbox(label="", max_lines=2, interactive=False, visible=False)
513
+ encrypted_query_image = gr.Textbox(
514
+ value=ENCRYPTED_QUERY_NAME,
515
+ label="",
516
+ max_lines=2,
517
+ interactive=False,
518
+ visible=False,
519
+ )
520
+ encrypted_reference_image = gr.Textbox(
521
+ value=ENCRYPTED_REFERENCE_NAME,
522
+ label="",
523
+ max_lines=2,
524
+ interactive=False,
525
+ visible=False,
526
+ )
527
 
528
+ gr.Markdown("### Step 4: Encrypt the images using FHE.")
529
+ encrypt_query_button = gr.Button("Encrypt the query image using FHE.")
530
+
531
+ with gr.Row():
532
+ encrypted_input_query = gr.Textbox(
533
+ label="Encrypted input query representation:",
534
+ max_lines=2,
535
+ interactive=False,
536
+ )
537
+
538
+ encrypt_reference_button = gr.Button("Encrypt the reference image using FHE.")
539
  with gr.Row():
540
+ encrypted_input_reference = gr.Textbox(
541
+ label="Encrypted input reference representation:",
542
+ max_lines=2,
543
+ interactive=False,
544
  )
545
 
546
  gr.Markdown("## Server side")
547
  gr.Markdown(
548
+ "The encrypted value is received by the server. The server can then compute the matcher "
549
  "directly over encrypted values. Once the computation is finished, the server returns "
550
  "the encrypted results to the client."
551
  )
552
 
553
+ gr.Markdown("### Step 5: Send the encrypted images to the server.")
554
+ send_input_button = gr.Button("Send the encrypted images to the server.")
555
+ send_input_checkbox = gr.Checkbox(label="Encrypted images sent.", interactive=False)
556
 
557
  gr.Markdown("### Step 6: Run FHE execution.")
558
  execute_fhe_button = gr.Button("Run FHE execution.")
 
560
  label="Total FHE execution time (in seconds):", max_lines=1, interactive=False
561
  )
562
 
563
+ gr.Markdown("### Step 7: Receive the encrypted output from the server.")
564
  gr.Markdown(
565
+ "The result displayed here is the encrypted result sent by the server, which has been "
566
  "decrypted using a different private key. This is only used to visually represent an "
567
+ "encrypted result."
568
+ )
569
+ get_output_button = gr.Button(
570
+ "Receive the encrypted output result from the server."
571
  )
 
572
 
573
  with gr.Row():
574
+ encrypted_output_representation = gr.Label()
575
+ # encrypted_output_representation = gr.Image(
576
+ # label=f"Encrypted output representation ({INPUT_SHAPE[0]}x{INPUT_SHAPE[1]}):",
577
+ # interactive=False,
578
+ # height=256,
579
+ # width=256,
580
+ # )
581
 
582
  gr.Markdown("## Client side")
583
  gr.Markdown(
584
  "The encrypted output is sent back to the client, who can finally decrypt it with the "
585
+ "private key. Only the client is aware of the original input images and the result of the matching."
586
  )
587
 
588
  gr.Markdown("### Step 8: Decrypt the output.")
589
  gr.Markdown(
590
+ "The images displayed on the left are the input images used during the demo. The output result "
591
  "can be seen on the right."
592
+ )
593
  decrypt_button = gr.Button("Decrypt the output")
594
 
595
  # Final input vs output display
596
  with gr.Row():
597
+ original_query_image = gr.Image(
598
+ input_query_img.value,
599
+ label=f"Input query image ({INPUT_SHAPE[0]}x{INPUT_SHAPE[1]}):",
600
  interactive=False,
601
  height=256,
602
+ width=256,
603
  )
604
+ original_reference_image = gr.Image(
605
+ input_reference_img.value,
606
+ label=f"Input reference image ({INPUT_SHAPE[0]}x{INPUT_SHAPE[1]}):",
607
  interactive=False,
608
  height=256,
609
  width=256,
610
  )
611
+ output_result = gr.Label()
612
+ # output_image = gr.Image(
613
+ # label=f"Output image ({INPUT_SHAPE[0]}x{INPUT_SHAPE[1]}):",
614
+ # interactive=False,
615
+ # height=256,
616
+ # width=256,
617
+ # )
618
 
619
  # Button to generate the private key
620
  keygen_button.click(
621
  keygen,
622
+ inputs=[matcher_name],
623
  outputs=[user_id, keygen_checkbox],
624
  )
625
 
626
+ # Button to encrypt input query on the client side
627
+ encrypt_query_button.click(
628
+ encrypt,
629
+ inputs=[user_id, input_query_img, matcher_name, encrypted_query_image],
630
+ outputs=[original_query_image, encrypted_input_query],
631
+ )
632
+
633
+ # Button to encrypt input reference on the client side
634
+ encrypt_reference_button.click(
635
  encrypt,
636
+ inputs=[user_id, input_reference_img, matcher_name, encrypted_reference_image],
637
+ outputs=[original_reference_image, encrypted_input_reference],
638
  )
639
 
640
  # Button to send the encodings to the server using post method
641
  send_input_button.click(
642
+ send_input, inputs=[user_id, matcher_name], outputs=[send_input_checkbox]
643
  )
644
 
645
  # Button to send the encodings to the server using post method
646
+ execute_fhe_button.click(
647
+ run_fhe, inputs=[user_id, matcher_name], outputs=[fhe_execution_time]
648
+ )
649
 
650
  # Button to send the encodings to the server using post method
651
  get_output_button.click(
652
+ get_output,
653
+ inputs=[user_id, matcher_name],
654
+ outputs=[encrypted_output_representation],
655
  )
656
 
657
  # Button to decrypt the output on the client side
658
  decrypt_button.click(
659
  decrypt_output,
660
+ inputs=[user_id, matcher_name],
661
+ outputs=[output_result, keygen_checkbox, send_input_checkbox],
662
  )
663
 
664
  gr.Markdown(
client_server_interface.py CHANGED
@@ -1,8 +1,8 @@
1
- "Client-server interface custom implementation for filter models."
2
 
3
  from concrete import fhe
4
 
5
- from filters import Filter
6
 
7
 
8
  class FHEServer:
@@ -19,22 +19,35 @@ class FHEServer:
19
  # Load the FHE circuit
20
  self.server = fhe.Server.load(self.path_dir / "server.zip")
21
 
22
- def run(self, serialized_encrypted_image, serialized_evaluation_keys):
23
- """Run the filter on the server over an encrypted image.
 
 
 
 
 
24
 
25
  Args:
26
- serialized_encrypted_image (bytes): The encrypted and serialized image.
 
27
  serialized_evaluation_keys (bytes): The serialized evaluation keys.
28
 
29
  Returns:
30
- bytes: The filter's output.
31
  """
32
  # Deserialize the encrypted input image and the evaluation keys
33
- encrypted_image = fhe.Value.deserialize(serialized_encrypted_image)
 
 
 
34
  evaluation_keys = fhe.EvaluationKeys.deserialize(serialized_evaluation_keys)
35
 
36
- # Execute the filter in FHE
37
- encrypted_output = self.server.run(encrypted_image, evaluation_keys=evaluation_keys)
 
 
 
 
38
 
39
  # Serialize the encrypted output image
40
  serialized_encrypted_output = encrypted_output.serialize()
@@ -43,60 +56,62 @@ class FHEServer:
43
 
44
 
45
  class FHEDev:
46
- """Development interface to save and load the filter."""
47
 
48
- def __init__(self, filter, path_dir):
49
  """Initialize the FHE interface.
50
 
51
  Args:
52
- filter (Filter): The filter to use in the FHE interface.
53
  path_dir (str): The path to the directory where the circuit is saved.
54
  """
55
 
56
- self.filter = filter
57
  self.path_dir = path_dir
58
 
59
  self.path_dir.mkdir(parents=True, exist_ok=True)
60
 
61
  def save(self):
62
  """Export all needed artifacts for the client and server interfaces."""
63
-
64
- assert self.filter.fhe_circuit is not None, (
65
- "The model must be compiled before saving it."
66
- )
67
 
68
  # Save the circuit for the server, using the via_mlir in order to handle cross-platform
69
  # execution
70
  path_circuit_server = self.path_dir / "server.zip"
71
- self.filter.fhe_circuit.server.save(path_circuit_server, via_mlir=True)
72
 
73
  # Save the circuit for the client
74
  path_circuit_client = self.path_dir / "client.zip"
75
- self.filter.fhe_circuit.client.save(path_circuit_client)
76
 
77
 
78
  class FHEClient:
79
- """Client interface to encrypt and decrypt FHE data associated to a Filter."""
80
 
81
- def __init__(self, path_dir, filter_name, key_dir=None):
82
  """Initialize the FHE interface.
83
 
84
  Args:
85
  path_dir (Path): The path to the directory where the circuit is saved.
86
- filter_name (str): The filter's name to consider.
87
  key_dir (Path): The path to the directory where the keys are stored. Default to None.
88
  """
89
  self.path_dir = path_dir
90
  self.key_dir = key_dir
91
 
92
  # If path_dir does not exist raise
93
- assert path_dir.exists(), f"{path_dir} does not exist. Please specify a valid path."
 
 
94
 
95
  # Load the client
96
  self.client = fhe.Client.load(self.path_dir / "client.zip", self.key_dir)
97
 
98
- # Instantiate the filter
99
- self.filter = Filter(filter_name)
100
 
101
  def generate_private_and_evaluation_keys(self, force=False):
102
  """Generate the private and evaluation keys.
@@ -131,7 +146,7 @@ class FHEClient:
131
  return serialized_encrypted_image
132
 
133
  def deserialize_decrypt_post_process(self, serialized_encrypted_output_image):
134
- """Deserialize, decrypt and post-process the output image in the clear.
135
 
136
  Args:
137
  serialized_encrypted_output_image (bytes): The serialized and encrypted output image.
@@ -140,14 +155,12 @@ class FHEClient:
140
  numpy.ndarray: The decrypted, deserialized and post-processed image.
141
  """
142
  # Deserialize the encrypted image
143
- encrypted_output_image = fhe.Value.deserialize(
144
- serialized_encrypted_output_image
145
- )
146
 
147
- # Decrypt the image
148
- output_image = self.client.decrypt(encrypted_output_image)
149
 
150
- # Post-process the image
151
- post_processed_output_image = self.filter.post_processing(output_image)
152
 
153
- return post_processed_output_image
 
1
+ "Client-server interface custom implementation for matcher models."
2
 
3
  from concrete import fhe
4
 
5
+ from matchers import Matcher
6
 
7
 
8
  class FHEServer:
 
19
  # Load the FHE circuit
20
  self.server = fhe.Server.load(self.path_dir / "server.zip")
21
 
22
+ def run(
23
+ self,
24
+ serialized_encrypted_query_image,
25
+ serialized_encrypted_reference_image,
26
+ serialized_evaluation_keys,
27
+ ):
28
+ """Run the matcher on the server over an encrypted image.
29
 
30
  Args:
31
+ serialized_encrypted_query_image (bytes): The encrypted and serialized query image.
32
+ serialized_encrypted_reference_image (bytes): The encrypted and serialized referenceimage.
33
  serialized_evaluation_keys (bytes): The serialized evaluation keys.
34
 
35
  Returns:
36
+ bytes: The matcher's output.
37
  """
38
  # Deserialize the encrypted input image and the evaluation keys
39
+ encrypted_query_image = fhe.Value.deserialize(serialized_encrypted_query_image)
40
+ encrypted_reference_image = fhe.Value.deserialize(
41
+ serialized_encrypted_reference_image
42
+ )
43
  evaluation_keys = fhe.EvaluationKeys.deserialize(serialized_evaluation_keys)
44
 
45
+ # Execute the matcher in FHE
46
+ encrypted_output = self.server.run(
47
+ encrypted_query_image,
48
+ encrypted_reference_image,
49
+ evaluation_keys=evaluation_keys,
50
+ )
51
 
52
  # Serialize the encrypted output image
53
  serialized_encrypted_output = encrypted_output.serialize()
 
56
 
57
 
58
  class FHEDev:
59
+ """Development interface to save and load the matcher."""
60
 
61
+ def __init__(self, matcher, path_dir):
62
  """Initialize the FHE interface.
63
 
64
  Args:
65
+ matcher (Matcher): The matcher to use in the FHE interface.
66
  path_dir (str): The path to the directory where the circuit is saved.
67
  """
68
 
69
+ self.matcher = matcher
70
  self.path_dir = path_dir
71
 
72
  self.path_dir.mkdir(parents=True, exist_ok=True)
73
 
74
  def save(self):
75
  """Export all needed artifacts for the client and server interfaces."""
76
+
77
+ assert (
78
+ self.matcher.fhe_circuit is not None
79
+ ), "The model must be compiled before saving it."
80
 
81
  # Save the circuit for the server, using the via_mlir in order to handle cross-platform
82
  # execution
83
  path_circuit_server = self.path_dir / "server.zip"
84
+ self.matcher.fhe_circuit.server.save(path_circuit_server, via_mlir=True)
85
 
86
  # Save the circuit for the client
87
  path_circuit_client = self.path_dir / "client.zip"
88
+ self.matcher.fhe_circuit.client.save(path_circuit_client)
89
 
90
 
91
  class FHEClient:
92
+ """Client interface to encrypt and decrypt FHE data associated to a matcher."""
93
 
94
+ def __init__(self, path_dir, matcher_name, key_dir=None):
95
  """Initialize the FHE interface.
96
 
97
  Args:
98
  path_dir (Path): The path to the directory where the circuit is saved.
99
+ matcher_name (str): The matcher's name to consider.
100
  key_dir (Path): The path to the directory where the keys are stored. Default to None.
101
  """
102
  self.path_dir = path_dir
103
  self.key_dir = key_dir
104
 
105
  # If path_dir does not exist raise
106
+ assert (
107
+ path_dir.exists()
108
+ ), f"{path_dir} does not exist. Please specify a valid path."
109
 
110
  # Load the client
111
  self.client = fhe.Client.load(self.path_dir / "client.zip", self.key_dir)
112
 
113
+ # Instantiate the matcher
114
+ self.matcher = Matcher(matcher_name)
115
 
116
  def generate_private_and_evaluation_keys(self, force=False):
117
  """Generate the private and evaluation keys.
 
146
  return serialized_encrypted_image
147
 
148
  def deserialize_decrypt_post_process(self, serialized_encrypted_output_image):
149
+ """Deserialize, decrypt and post-process the output result in the clear.
150
 
151
  Args:
152
  serialized_encrypted_output_image (bytes): The serialized and encrypted output image.
 
155
  numpy.ndarray: The decrypted, deserialized and post-processed image.
156
  """
157
  # Deserialize the encrypted image
158
+ encrypted_output = fhe.Value.deserialize(serialized_encrypted_output_image)
 
 
159
 
160
+ # Decrypt the result
161
+ output_result = self.client.decrypt(encrypted_output)
162
 
163
+ # Post-process the result
164
+ post_processed_output = self.matcher.post_processing(output_result)
165
 
166
+ return post_processed_output
common.py CHANGED
@@ -6,7 +6,7 @@ from pathlib import Path
6
  REPO_DIR = Path(__file__).parent
7
 
8
  # This repository's main necessary folders
9
- FILTERS_PATH = REPO_DIR / "filters"
10
  KEYS_PATH = REPO_DIR / ".fhe_keys"
11
  CLIENT_TMP_PATH = REPO_DIR / "client_tmp"
12
  SERVER_TMP_PATH = REPO_DIR / "server_tmp"
@@ -17,14 +17,13 @@ CLIENT_TMP_PATH.mkdir(exist_ok=True)
17
  SERVER_TMP_PATH.mkdir(exist_ok=True)
18
 
19
  # All the filters currently available in the demo
20
- AVAILABLE_FILTERS = [
21
- "identity",
22
- "inverted",
23
- "rotate",
24
- "black and white",
25
- "blur",
26
- "sharpen",
27
- "ridge detection",
28
  ]
29
 
30
  # The input images' shape. Images with different input shapes will be cropped and resized by Gradio
@@ -36,5 +35,10 @@ INPUT_EXAMPLES_DIR = REPO_DIR / "input_examples"
36
  # List of all image examples suggested in the demo
37
  EXAMPLES = [str(image) for image in INPUT_EXAMPLES_DIR.glob("**/*")]
38
 
 
 
 
 
 
39
  # Store the server's URL
40
  SERVER_URL = "http://localhost:8000/"
 
6
  REPO_DIR = Path(__file__).parent
7
 
8
  # This repository's main necessary folders
9
+ MATCHERS_PATH = REPO_DIR / "matchers"
10
  KEYS_PATH = REPO_DIR / ".fhe_keys"
11
  CLIENT_TMP_PATH = REPO_DIR / "client_tmp"
12
  SERVER_TMP_PATH = REPO_DIR / "server_tmp"
 
17
  SERVER_TMP_PATH.mkdir(exist_ok=True)
18
 
19
  # All the filters currently available in the demo
20
+ AVAILABLE_MATCHERS = [
21
+ "random guessing",
22
+ # "random guessing concrete 1.1.0",
23
+ # "random guessing concrete 1.1.0",
24
+ # "static distance",
25
+ # "MLP-based learned distance",
26
+ # "CNN-based learned distance",
 
27
  ]
28
 
29
  # The input images' shape. Images with different input shapes will be cropped and resized by Gradio
 
35
  # List of all image examples suggested in the demo
36
  EXAMPLES = [str(image) for image in INPUT_EXAMPLES_DIR.glob("**/*")]
37
 
38
+ # Encrypted image and output names
39
+ ENCRYPTED_QUERY_NAME = "encrypted_query_image"
40
+ ENCRYPTED_REFERENCE_NAME = "encrypted_reference_image"
41
+ ENCRYPTED_OUTPUT_NAME = "encrypted_output"
42
+
43
  # Store the server's URL
44
  SERVER_URL = "http://localhost:8000/"
data/old_code/client_server_interface.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "Client-server interface custom implementation for filter models."
2
+
3
+ from concrete import fhe
4
+
5
+ from filters import Filter
6
+
7
+
8
+ class FHEServer:
9
+ """Server interface run a FHE circuit."""
10
+
11
+ def __init__(self, path_dir):
12
+ """Initialize the FHE interface.
13
+
14
+ Args:
15
+ path_dir (Path): The path to the directory where the circuit is saved.
16
+ """
17
+ self.path_dir = path_dir
18
+
19
+ # Load the FHE circuit
20
+ self.server = fhe.Server.load(self.path_dir / "server.zip")
21
+
22
+ def run(self, serialized_encrypted_image, serialized_evaluation_keys):
23
+ """Run the filter on the server over an encrypted image.
24
+
25
+ Args:
26
+ serialized_encrypted_image (bytes): The encrypted and serialized image.
27
+ serialized_evaluation_keys (bytes): The serialized evaluation keys.
28
+
29
+ Returns:
30
+ bytes: The filter's output.
31
+ """
32
+ # Deserialize the encrypted input image and the evaluation keys
33
+ encrypted_image = fhe.Value.deserialize(serialized_encrypted_image)
34
+ evaluation_keys = fhe.EvaluationKeys.deserialize(serialized_evaluation_keys)
35
+
36
+ # Execute the filter in FHE
37
+ encrypted_output = self.server.run(encrypted_image, evaluation_keys=evaluation_keys)
38
+
39
+ # Serialize the encrypted output image
40
+ serialized_encrypted_output = encrypted_output.serialize()
41
+
42
+ return serialized_encrypted_output
43
+
44
+
45
+ class FHEDev:
46
+ """Development interface to save and load the filter."""
47
+
48
+ def __init__(self, filter, path_dir):
49
+ """Initialize the FHE interface.
50
+
51
+ Args:
52
+ filter (Filter): The filter to use in the FHE interface.
53
+ path_dir (str): The path to the directory where the circuit is saved.
54
+ """
55
+
56
+ self.filter = filter
57
+ self.path_dir = path_dir
58
+
59
+ self.path_dir.mkdir(parents=True, exist_ok=True)
60
+
61
+ def save(self):
62
+ """Export all needed artifacts for the client and server interfaces."""
63
+
64
+ assert self.filter.fhe_circuit is not None, (
65
+ "The model must be compiled before saving it."
66
+ )
67
+
68
+ # Save the circuit for the server, using the via_mlir in order to handle cross-platform
69
+ # execution
70
+ path_circuit_server = self.path_dir / "server.zip"
71
+ self.filter.fhe_circuit.server.save(path_circuit_server, via_mlir=True)
72
+
73
+ # Save the circuit for the client
74
+ path_circuit_client = self.path_dir / "client.zip"
75
+ self.filter.fhe_circuit.client.save(path_circuit_client)
76
+
77
+
78
+ class FHEClient:
79
+ """Client interface to encrypt and decrypt FHE data associated to a Filter."""
80
+
81
+ def __init__(self, path_dir, filter_name, key_dir=None):
82
+ """Initialize the FHE interface.
83
+
84
+ Args:
85
+ path_dir (Path): The path to the directory where the circuit is saved.
86
+ filter_name (str): The filter's name to consider.
87
+ key_dir (Path): The path to the directory where the keys are stored. Default to None.
88
+ """
89
+ self.path_dir = path_dir
90
+ self.key_dir = key_dir
91
+
92
+ # If path_dir does not exist raise
93
+ assert path_dir.exists(), f"{path_dir} does not exist. Please specify a valid path."
94
+
95
+ # Load the client
96
+ self.client = fhe.Client.load(self.path_dir / "client.zip", self.key_dir)
97
+
98
+ # Instantiate the filter
99
+ self.filter = Filter(filter_name)
100
+
101
+ def generate_private_and_evaluation_keys(self, force=False):
102
+ """Generate the private and evaluation keys.
103
+
104
+ Args:
105
+ force (bool): If True, regenerate the keys even if they already exist.
106
+ """
107
+ self.client.keygen(force)
108
+
109
+ def get_serialized_evaluation_keys(self):
110
+ """Get the serialized evaluation keys.
111
+
112
+ Returns:
113
+ bytes: The evaluation keys.
114
+ """
115
+ return self.client.evaluation_keys.serialize()
116
+
117
+ def encrypt_serialize(self, input_image):
118
+ """Encrypt and serialize the input image in the clear.
119
+
120
+ Args:
121
+ input_image (numpy.ndarray): The image to encrypt and serialize.
122
+
123
+ Returns:
124
+ bytes: The pre-processed, encrypted and serialized image.
125
+ """
126
+ # Encrypt the image
127
+ encrypted_image = self.client.encrypt(input_image)
128
+
129
+ # Serialize the encrypted image to be sent to the server
130
+ serialized_encrypted_image = encrypted_image.serialize()
131
+ return serialized_encrypted_image
132
+
133
+ def deserialize_decrypt_post_process(self, serialized_encrypted_output_image):
134
+ """Deserialize, decrypt and post-process the output image in the clear.
135
+
136
+ Args:
137
+ serialized_encrypted_output_image (bytes): The serialized and encrypted output image.
138
+
139
+ Returns:
140
+ numpy.ndarray: The decrypted, deserialized and post-processed image.
141
+ """
142
+ # Deserialize the encrypted image
143
+ encrypted_output_image = fhe.Value.deserialize(
144
+ serialized_encrypted_output_image
145
+ )
146
+
147
+ # Decrypt the image
148
+ output_image = self.client.decrypt(encrypted_output_image)
149
+
150
+ # Post-process the image
151
+ post_processed_output_image = self.filter.post_processing(output_image)
152
+
153
+ return post_processed_output_image
filters.py → data/old_code/filters.py RENAMED
@@ -3,7 +3,7 @@
3
  import numpy as np
4
  import torch
5
  from torch import nn
6
- from common import AVAILABLE_FILTERS, INPUT_SHAPE
7
 
8
  from concrete.fhe.compilation.compiler import Compiler
9
  from concrete.ml.common.utils import generate_proxy_function
@@ -58,7 +58,9 @@ class TorchRotate(nn.Module):
58
  class TorchConv(nn.Module):
59
  """Torch model with a single convolution operator."""
60
 
61
- def __init__(self, kernel, n_in_channels=3, n_out_channels=3, groups=1, threshold=None):
 
 
62
  """Initialize the filter.
63
 
64
  Args:
@@ -104,16 +106,15 @@ class TorchConv(nn.Module):
104
  kernel_shape[1],
105
  )
106
 
107
-
108
  else:
109
  raise ValueError(
110
  "Wrong kernel shape, only 1D or 2D kernels are accepted. Got kernel of shape "
111
  f"{kernel_shape}"
112
  )
113
 
114
- # Reshape the image. This is done because Torch convolutions and Numpy arrays (for PIL
115
- # display) don't follow the same shape conventions. More precisely, x is of shape
116
- # (Width, Height, Channels) while the conv2d operator requires an input of shape
117
  # (Batch, Channels, Height, Width)
118
  x = x.transpose(2, 0).unsqueeze(axis=0)
119
 
@@ -142,18 +143,18 @@ class Filter:
142
  filter_name (str): The filter to consider.
143
  """
144
 
145
- assert filter_name in AVAILABLE_FILTERS, (
146
- f"Unsupported image filter or transformation. Expected one of {*AVAILABLE_FILTERS,}, "
147
  f"but got {filter_name}",
148
  )
149
 
150
- # Define attributes associated to the filter
151
  self.filter_name = filter_name
152
  self.onnx_model = None
153
  self.fhe_circuit = None
154
  self.divide = None
155
 
156
- # Instantiate the torch module associated to the given filter name
157
  if filter_name == "identity":
158
  self.torch_model = TorchIdentity()
159
 
@@ -179,7 +180,6 @@ class Filter:
179
  # Define the value used when for dividing the output values in post-processing
180
  self.divide = 1000
181
 
182
-
183
  elif filter_name == "blur":
184
  kernel = np.ones((3, 3))
185
 
@@ -208,16 +208,16 @@ class Filter:
208
  # value to the result in order to better display the ridges
209
  self.torch_model = TorchConv(kernel, threshold=900)
210
 
211
-
212
  def compile(self):
213
  """Compile the filter on a representative inputset."""
214
- # Generate a random representative set of images used for compilation, following shape
215
  # PIL's shape RGB format for Numpy arrays (image_width, image_height, 3)
216
- # Additionally, this version's compiler only handles tuples of 1-batch array as inputset,
217
- # meaning we need to define the inputset as a Tuple[np.ndarray[shape=(H, W, 3)]]
218
  np.random.seed(42)
219
  inputset = tuple(
220
- np.random.randint(0, 256, size=(INPUT_SHAPE + (3, )), dtype=np.int64) for _ in range(100)
 
221
  )
222
 
223
  # Convert the Torch module to a Numpy module
@@ -227,11 +227,10 @@ class Filter:
227
  )
228
 
229
  # Get the proxy function and parameter mappings used for initializing the compiler
230
- # This is done in order to be able to provide any modules with arbitrary numbers of
231
  # encrypted arguments to Concrete Numpy's compiler
232
  numpy_filter_proxy, parameters_mapping = generate_proxy_function(
233
- numpy_module.numpy_forward,
234
- ["inputs"]
235
  )
236
 
237
  # Compile the filter and retrieve its FHE circuit
 
3
  import numpy as np
4
  import torch
5
  from torch import nn
6
+ from common import AVAILABLE_MATCHERS, INPUT_SHAPE
7
 
8
  from concrete.fhe.compilation.compiler import Compiler
9
  from concrete.ml.common.utils import generate_proxy_function
 
58
  class TorchConv(nn.Module):
59
  """Torch model with a single convolution operator."""
60
 
61
+ def __init__(
62
+ self, kernel, n_in_channels=3, n_out_channels=3, groups=1, threshold=None
63
+ ):
64
  """Initialize the filter.
65
 
66
  Args:
 
106
  kernel_shape[1],
107
  )
108
 
 
109
  else:
110
  raise ValueError(
111
  "Wrong kernel shape, only 1D or 2D kernels are accepted. Got kernel of shape "
112
  f"{kernel_shape}"
113
  )
114
 
115
+ # Reshape the image. This is done because Torch convolutions and Numpy arrays (for PIL
116
+ # display) don't follow the same shape conventions. More precisely, x is of shape
117
+ # (Width, Height, Channels) while the conv2d operator requires an input of shape
118
  # (Batch, Channels, Height, Width)
119
  x = x.transpose(2, 0).unsqueeze(axis=0)
120
 
 
143
  filter_name (str): The filter to consider.
144
  """
145
 
146
+ assert filter_name in AVAILABLE_MATCHERS, (
147
+ f"Unsupported image filter or transformation. Expected one of {*AVAILABLE_MATCHERS,}, "
148
  f"but got {filter_name}",
149
  )
150
 
151
+ # Define attributes associated to the filter
152
  self.filter_name = filter_name
153
  self.onnx_model = None
154
  self.fhe_circuit = None
155
  self.divide = None
156
 
157
+ # Instantiate the torch module associated to the given filter name
158
  if filter_name == "identity":
159
  self.torch_model = TorchIdentity()
160
 
 
180
  # Define the value used when for dividing the output values in post-processing
181
  self.divide = 1000
182
 
 
183
  elif filter_name == "blur":
184
  kernel = np.ones((3, 3))
185
 
 
208
  # value to the result in order to better display the ridges
209
  self.torch_model = TorchConv(kernel, threshold=900)
210
 
 
211
  def compile(self):
212
  """Compile the filter on a representative inputset."""
213
+ # Generate a random representative set of images used for compilation, following shape
214
  # PIL's shape RGB format for Numpy arrays (image_width, image_height, 3)
215
+ # Additionally, this version's compiler only handles tuples of 1-batch array as inputset,
216
+ # meaning we need to define the inputset as a Tuple[np.ndarray[shape=(H, W, 3)]]
217
  np.random.seed(42)
218
  inputset = tuple(
219
+ np.random.randint(0, 256, size=(INPUT_SHAPE + (3,)), dtype=np.int64)
220
+ for _ in range(100)
221
  )
222
 
223
  # Convert the Torch module to a Numpy module
 
227
  )
228
 
229
  # Get the proxy function and parameter mappings used for initializing the compiler
230
+ # This is done in order to be able to provide any modules with arbitrary numbers of
231
  # encrypted arguments to Concrete Numpy's compiler
232
  numpy_filter_proxy, parameters_mapping = generate_proxy_function(
233
+ numpy_module.numpy_forward, ["inputs"]
 
234
  )
235
 
236
  # Compile the filter and retrieve its FHE circuit
generate_dev_files.py → data/old_code/generate_dev_files.py RENAMED
@@ -1,13 +1,13 @@
1
  "A script to generate all development files necessary for the image filtering demo."
2
 
3
  import shutil
4
- from common import AVAILABLE_FILTERS, FILTERS_PATH
5
  from filters import Filter
6
  from client_server_interface import FHEDev
7
 
8
  print("Generating deployment files for all available filters")
9
 
10
- for filter_name in AVAILABLE_FILTERS:
11
  print("Filter:", filter_name, "\n")
12
 
13
  # Create the filter instance
@@ -17,7 +17,7 @@ for filter_name in AVAILABLE_FILTERS:
17
  filter.compile()
18
 
19
  # Define the directory path associated to this filter's deployment files
20
- deployment_path = FILTERS_PATH / (filter_name + "/deployment")
21
 
22
  # Delete the deployment folder and its content if it already exists
23
  if deployment_path.is_dir():
 
1
  "A script to generate all development files necessary for the image filtering demo."
2
 
3
  import shutil
4
+ from common import AVAILABLE_MATCHERS, MATCHERS_PATH
5
  from filters import Filter
6
  from client_server_interface import FHEDev
7
 
8
  print("Generating deployment files for all available filters")
9
 
10
+ for filter_name in AVAILABLE_MATCHERS:
11
  print("Filter:", filter_name, "\n")
12
 
13
  # Create the filter instance
 
17
  filter.compile()
18
 
19
  # Define the directory path associated to this filter's deployment files
20
+ deployment_path = MATCHERS_PATH / (filter_name + "/deployment")
21
 
22
  # Delete the deployment folder and its content if it already exists
23
  if deployment_path.is_dir():
generate_deployment_files.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ import shutil
3
+ from client_server_interface import FHEDev
4
+ from matchers import AVAILABLE_MATCHERS, Matcher
5
+
6
+
7
+ print("Generating deployment files for all available filters")
8
+ # This repository's directory
9
+ REPO_DIR = Path(__file__).parent
10
+ # This repository's main necessary folders
11
+ MATCHERS_PATH = REPO_DIR / "data/compiled_models"
12
+ for matcher_name in AVAILABLE_MATCHERS:
13
+ print("Matcher:", matcher_name, "\n")
14
+
15
+ # Create the filter instance
16
+ matcher = Matcher(matcher_name)
17
+
18
+ # Compile the model on a representative inputset
19
+ matcher.compile()
20
+
21
+ # Define the directory path associated to this filter's deployment files
22
+ deployment_path = MATCHERS_PATH / (matcher_name + "/deployment")
23
+
24
+ # Delete the deployment folder and its content if it already exists
25
+ if deployment_path.is_dir():
26
+ print("Delete the deployment folder and its content ...")
27
+ shutil.rmtree(deployment_path)
28
+
29
+ # Save the files needed for deployment
30
+ print(f"Saving the files at {deployment_path} ...")
31
+ fhe_dev_filter = FHEDev(matcher, deployment_path)
32
+ fhe_dev_filter.save()
33
+
34
+ print("Done !")
image_filtering_banner.png DELETED
Binary file (275 kB)
 
matchers.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import numpy as np
3
+ from torch import nn
4
+ import torch
5
+
6
+ from concrete.fhe.compilation.compiler import Compiler
7
+ from concrete.ml.common.utils import generate_proxy_function
8
+ from concrete.ml.torch.numpy_module import NumpyModule
9
+
10
+ from common import AVAILABLE_MATCHERS
11
+
12
+
13
+ class TorchRandomGuessing(nn.Module):
14
+ """Torch identity model."""
15
+
16
+ def __init__(self, classes_=[0, 1]):
17
+ super().__init__()
18
+ self.classes_ = classes_
19
+
20
+ def forward(self, q, r):
21
+ """Random guessing forward pass.
22
+
23
+ Args:
24
+ q (torch.Tensor): The input query.
25
+ r (torch.Tensor): The input reference.
26
+
27
+ Returns:
28
+ (torch.Tensor): .
29
+ """
30
+ q = q.sum()
31
+ r = r.sum()
32
+ return torch.tensor([random.choice([0, 1])]) + q - q + r - r
33
+
34
+
35
+ class Matcher:
36
+ def __init__(self, matcher_name):
37
+ assert matcher_name in AVAILABLE_MATCHERS, (
38
+ f"Unsupported image matcher. Expected one of {*AVAILABLE_MATCHERS,}, "
39
+ f"but got {matcher_name}",
40
+ )
41
+ self.fhe_circuit = None
42
+ self.matcher_name = matcher_name
43
+
44
+ if self.matcher_name == "random guessing":
45
+ self.torch_model = TorchRandomGuessing()
46
+
47
+ def compile(self):
48
+
49
+ inputset = [(np.array([10]), np.array([5]))]
50
+
51
+ print("torch module > numpy module ...")
52
+ numpy_module = NumpyModule(
53
+ # torch_model, dummy_input=torch.from_numpy(np.array([10], dtype=np.int64))
54
+ self.torch_model,
55
+ # dummy_input=(torch.tensor([10]), torch.tensor([5])),
56
+ dummy_input=(
57
+ torch.from_numpy(inputset[0][1]),
58
+ torch.from_numpy(inputset[0][1]),
59
+ ),
60
+ )
61
+
62
+ print("get proxy function ...")
63
+ # Get the proxy function and parameter mappings used for initializing the compiler
64
+ # This is done in order to be able to provide any modules with arbitrary numbers of
65
+ # encrypted arguments to Concrete Numpy's compiler
66
+ numpy_filter_proxy, parameters_mapping = generate_proxy_function(
67
+ numpy_module.numpy_forward, ["query", "reference"]
68
+ )
69
+
70
+ print("Compile the filter and retrieve its FHE circuit ...")
71
+ compiler = Compiler(
72
+ numpy_filter_proxy,
73
+ {
74
+ parameters_mapping["query"]: "encrypted",
75
+ parameters_mapping["reference"]: "encrypted",
76
+ },
77
+ )
78
+ self.fhe_circuit = compiler.compile(inputset)
79
+ return self.fhe_circuit
80
+
81
+ def post_processing(self, output_result):
82
+ """Apply post-processing to the decrypted output result.
83
+
84
+ Args:
85
+ output_result (np.ndarray): The decrypted result to post-process.
86
+
87
+ Returns:
88
+ output_result (np.ndarray): The post-processed result.
89
+ """
90
+ print(f"{output_result=}")
91
+
92
+ return "PASS" if output_result[0] == 1 else "FAIL"
93
+
94
+
95
+ # matcher = Matcher(matcher_name=AVAILABLE_MATCHERS[0])
96
+ # fhe_circuit = matcher.compile()
matchers/filter blur/deployment/circuit.mlir ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module {
2
+ func.func @main(%arg0: tensor<100x100x3x!FHE.eint<12>>) -> tensor<98x98x3x!FHE.eint<12>> {
3
+ %0 = "FHELinalg.transpose"(%arg0) {axes = [2, 1, 0]} : (tensor<100x100x3x!FHE.eint<12>>) -> tensor<3x100x100x!FHE.eint<12>>
4
+ %expanded = tensor.expand_shape %0 [[0, 1], [2], [3]] : tensor<3x100x100x!FHE.eint<12>> into tensor<1x3x100x100x!FHE.eint<12>>
5
+ %cst = arith.constant dense<1> : tensor<3x1x3x3xi13>
6
+ %1 = "FHELinalg.conv2d"(%expanded, %cst) {dilations = dense<1> : tensor<2xi64>, group = 3 : i64, padding = dense<0> : tensor<4xi64>, strides = dense<1> : tensor<2xi64>} : (tensor<1x3x100x100x!FHE.eint<12>>, tensor<3x1x3x3xi13>) -> tensor<1x3x98x98x!FHE.eint<12>>
7
+ %2 = "FHELinalg.transpose"(%1) {axes = [0, 3, 2, 1]} : (tensor<1x3x98x98x!FHE.eint<12>>) -> tensor<1x98x98x3x!FHE.eint<12>>
8
+ %collapsed = tensor.collapse_shape %2 [[0, 1], [2], [3]] : tensor<1x98x98x3x!FHE.eint<12>> into tensor<98x98x3x!FHE.eint<12>>
9
+ return %collapsed : tensor<98x98x3x!FHE.eint<12>>
10
+ }
11
+ }
matchers/filter blur/deployment/client.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c6b45511096f6da9aa73601b1372ffc42ec9272b40348d92f90427fbddff0571
3
+ size 360
matchers/filter blur/deployment/configuration.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "verbose": false,
3
+ "show_graph": null,
4
+ "show_mlir": null,
5
+ "show_optimizer": null,
6
+ "dump_artifacts_on_unexpected_failures": true,
7
+ "enable_unsafe_features": false,
8
+ "use_insecure_key_cache": false,
9
+ "insecure_key_cache_location": null,
10
+ "loop_parallelize": true,
11
+ "dataflow_parallelize": false,
12
+ "auto_parallelize": false,
13
+ "jit": false,
14
+ "p_error": null,
15
+ "global_p_error": null,
16
+ "auto_adjust_rounders": false,
17
+ "single_precision": true,
18
+ "parameter_selection_strategy": "mono",
19
+ "show_progress": false,
20
+ "progress_title": "",
21
+ "progress_tag": false
22
+ }
matchers/filter blur/deployment/server.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8cc44c02ca9ee6523a35dcb1a034b7852e59a3a587dcbf2af71ca4109fc1e138
3
+ size 824
matchers/random guessing concrete 1.1.0/deployment/client.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d4acd2ea04c038bf590d6b6ad5b0534f864a49168cde20bd39e5712401a22551
3
+ size 465
matchers/random guessing concrete 1.1.0/deployment/server.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b89a5baf4d7d1d73184f020a58531169f1945dc03f17ff688d8dc4f9988ae89c
3
+ size 1517
matchers/random guessing/deployment/client.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d4acd2ea04c038bf590d6b6ad5b0534f864a49168cde20bd39e5712401a22551
3
+ size 465
matchers/random guessing/deployment/server.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b89a5baf4d7d1d73184f020a58531169f1945dc03f17ff688d8dc4f9988ae89c
3
+ size 1517
matchers/random guessing_1.6.1/deployment/circuit.mlir ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module {
2
+ func.func @main(%arg0: tensor<1x!FHE.eint<4>>, %arg1: tensor<1x!FHE.eint<4>>) -> tensor<1x!FHE.eint<4>> {
3
+ %0 = "FHELinalg.sum"(%arg0) {axes = [], keep_dims = false} : (tensor<1x!FHE.eint<4>>) -> !FHE.eint<4>
4
+ %1 = "FHELinalg.sum"(%arg1) {axes = [], keep_dims = false} : (tensor<1x!FHE.eint<4>>) -> !FHE.eint<4>
5
+ %cst = arith.constant dense<1> : tensor<1xi2>
6
+ %from_elements = tensor.from_elements %0 : tensor<1x!FHE.eint<4>>
7
+ %2 = "FHELinalg.add_eint_int"(%from_elements, %cst) : (tensor<1x!FHE.eint<4>>, tensor<1xi2>) -> tensor<1x!FHE.eint<4>>
8
+ %3 = "FHELinalg.sub_eint"(%2, %from_elements) : (tensor<1x!FHE.eint<4>>, tensor<1x!FHE.eint<4>>) -> tensor<1x!FHE.eint<4>>
9
+ %from_elements_0 = tensor.from_elements %1 : tensor<1x!FHE.eint<4>>
10
+ %4 = "FHELinalg.add_eint"(%3, %from_elements_0) : (tensor<1x!FHE.eint<4>>, tensor<1x!FHE.eint<4>>) -> tensor<1x!FHE.eint<4>>
11
+ %5 = "FHELinalg.sub_eint"(%4, %from_elements_0) : (tensor<1x!FHE.eint<4>>, tensor<1x!FHE.eint<4>>) -> tensor<1x!FHE.eint<4>>
12
+ return %5 : tensor<1x!FHE.eint<4>>
13
+ }
14
+ }
matchers/random guessing_1.6.1/deployment/client.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d4acd2ea04c038bf590d6b6ad5b0534f864a49168cde20bd39e5712401a22551
3
+ size 465
matchers/random guessing_1.6.1/deployment/composition_rules.json ADDED
@@ -0,0 +1 @@
 
 
1
+ null
matchers/random guessing_1.6.1/deployment/configuration.json ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "verbose": false,
3
+ "compiler_debug_mode": false,
4
+ "compiler_verbose_mode": false,
5
+ "show_graph": null,
6
+ "show_bit_width_constraints": null,
7
+ "show_bit_width_assignments": null,
8
+ "show_assigned_graph": null,
9
+ "show_mlir": null,
10
+ "show_optimizer": null,
11
+ "show_statistics": null,
12
+ "dump_artifacts_on_unexpected_failures": true,
13
+ "enable_unsafe_features": false,
14
+ "use_insecure_key_cache": false,
15
+ "insecure_key_cache_location": null,
16
+ "loop_parallelize": true,
17
+ "dataflow_parallelize": false,
18
+ "auto_parallelize": false,
19
+ "compress_evaluation_keys": false,
20
+ "compress_input_ciphertexts": false,
21
+ "p_error": null,
22
+ "global_p_error": null,
23
+ "auto_adjust_rounders": false,
24
+ "auto_adjust_truncators": false,
25
+ "single_precision": false,
26
+ "parameter_selection_strategy": {
27
+ "py/reduce": [
28
+ {
29
+ "py/type": "concrete.fhe.compilation.configuration.ParameterSelectionStrategy"
30
+ },
31
+ {
32
+ "py/tuple": [
33
+ "multi"
34
+ ]
35
+ }
36
+ ]
37
+ },
38
+ "multi_parameter_strategy": {
39
+ "py/reduce": [
40
+ {
41
+ "py/type": "concrete.fhe.compilation.configuration.MultiParameterStrategy"
42
+ },
43
+ {
44
+ "py/tuple": [
45
+ "precision"
46
+ ]
47
+ }
48
+ ]
49
+ },
50
+ "show_progress": false,
51
+ "progress_title": "",
52
+ "progress_tag": false,
53
+ "fhe_simulation": false,
54
+ "fhe_execution": true,
55
+ "comparison_strategy_preference": [],
56
+ "bitwise_strategy_preference": [],
57
+ "shifts_with_promotion": true,
58
+ "multivariate_strategy_preference": [],
59
+ "min_max_strategy_preference": [],
60
+ "composable": false,
61
+ "use_gpu": false,
62
+ "relu_on_bits_threshold": 7,
63
+ "relu_on_bits_chunk_size": 3,
64
+ "if_then_else_chunk_size": 3,
65
+ "additional_pre_processors": [],
66
+ "additional_post_processors": [],
67
+ "rounding_exactness": {
68
+ "py/reduce": [
69
+ {
70
+ "py/type": "concrete.fhe.compilation.configuration.Exactness"
71
+ },
72
+ {
73
+ "py/tuple": [
74
+ "exact"
75
+ ]
76
+ }
77
+ ]
78
+ },
79
+ "approximate_rounding_config": {
80
+ "py/object": "concrete.fhe.compilation.configuration.ApproximateRoundingConfig",
81
+ "logical_clipping": true,
82
+ "approximate_clipping_start_precision": 5,
83
+ "reduce_precision_after_approximate_clipping": true,
84
+ "symetrize_deltas": true
85
+ },
86
+ "optimize_tlu_based_on_measured_bounds": false,
87
+ "enable_tlu_fusing": true,
88
+ "print_tlu_fusing": false,
89
+ "optimize_tlu_based_on_original_bit_width": 8,
90
+ "detect_overflow_in_simulation": false,
91
+ "dynamic_indexing_check_out_of_bounds": true,
92
+ "dynamic_assignment_check_out_of_bounds": true,
93
+ "simulate_encrypt_run_decrypt": false
94
+ }
matchers/random guessing_1.6.1/deployment/is_simulated ADDED
@@ -0,0 +1 @@
 
 
1
+ 0
matchers/random guessing_1.6.1/deployment/server.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b89a5baf4d7d1d73184f020a58531169f1945dc03f17ff688d8dc4f9988ae89c
3
+ size 1517
requirements.txt CHANGED
@@ -1,3 +1,3 @@
1
- concrete-ml==1.1.0
2
  gradio
3
  more_itertools
 
1
+ concrete-ml==1.6.1
2
  gradio
3
  more_itertools
server.py CHANGED
@@ -5,28 +5,35 @@ from typing import List
5
  from fastapi import FastAPI, File, Form, UploadFile
6
  from fastapi.responses import JSONResponse, Response
7
 
8
- from common import FILTERS_PATH, SERVER_TMP_PATH, AVAILABLE_FILTERS
 
 
 
 
 
 
 
9
  from client_server_interface import FHEServer
10
 
11
- # Load the server objects related to all currently available filters once and for all
12
  FHE_SERVERS = {
13
- filter: FHEServer(FILTERS_PATH / f"{filter}/deployment")
14
- for filter in AVAILABLE_FILTERS
15
  }
16
 
17
 
18
- def get_server_file_path(name, user_id, filter_name):
19
  """Get the correct temporary file path for the server.
20
 
21
  Args:
22
  name (str): The desired file name.
23
  user_id (int): The current user's ID.
24
- filter_name (str): The filter chosen by the user
25
 
26
  Returns:
27
  pathlib.Path: The file path.
28
  """
29
- return SERVER_TMP_PATH / f"{name}_{filter_name}_{user_id}"
30
 
31
 
32
  # Initialize an instance of FastAPI
@@ -42,53 +49,77 @@ def root():
42
  @app.post("/send_input")
43
  def send_input(
44
  user_id: str = Form(),
45
- filter: str = Form(),
46
  files: List[UploadFile] = File(),
47
  ):
48
  """Send the inputs to the server."""
49
  # Retrieve the encrypted input image and the evaluation key paths
50
- encrypted_image_path = get_server_file_path("encrypted_image", user_id, filter)
51
- evaluation_key_path = get_server_file_path("evaluation_key", user_id, filter)
 
 
 
 
 
52
 
53
  # Write the files using the above paths
54
- with encrypted_image_path.open("wb") as encrypted_image, evaluation_key_path.open(
 
 
 
 
55
  "wb"
56
  ) as evaluation_key:
57
- encrypted_image.write(files[0].file.read())
58
- evaluation_key.write(files[1].file.read())
 
59
 
60
 
61
  @app.post("/run_fhe")
62
  def run_fhe(
63
  user_id: str = Form(),
64
- filter: str = Form(),
65
  ):
66
- """Execute the filter on the encrypted input image using FHE."""
67
  # Retrieve the encrypted input image and the evaluation key paths
68
- encrypted_image_path = get_server_file_path("encrypted_image", user_id, filter)
69
- evaluation_key_path = get_server_file_path("evaluation_key", user_id, filter)
 
 
 
 
 
70
 
71
  # Read the files using the above paths
72
- with encrypted_image_path.open(
 
 
 
 
73
  "rb"
74
- ) as encrypted_image_file, evaluation_key_path.open("rb") as evaluation_key_file:
75
- encrypted_image = encrypted_image_file.read()
 
76
  evaluation_key = evaluation_key_file.read()
77
 
78
- # Load the FHE server related to the chosen filter
79
- fhe_server = FHE_SERVERS[filter]
80
 
81
  # Run the FHE execution
82
  start = time.time()
83
- encrypted_output_image = fhe_server.run(encrypted_image, evaluation_key)
 
 
84
  fhe_execution_time = round(time.time() - start, 2)
85
 
86
- # Retrieve the encrypted output image path
87
- encrypted_output_path = get_server_file_path("encrypted_output", user_id, filter)
 
 
88
 
89
  # Write the file using the above path
90
  with encrypted_output_path.open("wb") as encrypted_output:
91
- encrypted_output.write(encrypted_output_image)
92
 
93
  return JSONResponse(content=fhe_execution_time)
94
 
@@ -96,11 +127,13 @@ def run_fhe(
96
  @app.post("/get_output")
97
  def get_output(
98
  user_id: str = Form(),
99
- filter: str = Form(),
100
  ):
101
- """Retrieve the encrypted output image."""
102
- # Retrieve the encrypted output image path
103
- encrypted_output_path = get_server_file_path("encrypted_output", user_id, filter)
 
 
104
 
105
  # Read the file using the above path
106
  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 common import (
9
+ ENCRYPTED_OUTPUT_NAME,
10
+ ENCRYPTED_QUERY_NAME,
11
+ ENCRYPTED_REFERENCE_NAME,
12
+ MATCHERS_PATH,
13
+ SERVER_TMP_PATH,
14
+ AVAILABLE_MATCHERS,
15
+ )
16
  from client_server_interface import FHEServer
17
 
18
+ # Load the server objects related to all currently available matchers once and for all
19
  FHE_SERVERS = {
20
+ matcher: FHEServer(MATCHERS_PATH / f"{matcher}/deployment")
21
+ for matcher in AVAILABLE_MATCHERS
22
  }
23
 
24
 
25
+ def get_server_file_path(name, user_id, matcher_name):
26
  """Get the correct temporary file path for the server.
27
 
28
  Args:
29
  name (str): The desired file name.
30
  user_id (int): The current user's ID.
31
+ matcher_name (str): The matcher chosen by the user
32
 
33
  Returns:
34
  pathlib.Path: The file path.
35
  """
36
+ return SERVER_TMP_PATH / f"{name}_{matcher_name}_{user_id}"
37
 
38
 
39
  # Initialize an instance of FastAPI
 
49
  @app.post("/send_input")
50
  def send_input(
51
  user_id: str = Form(),
52
+ matcher: str = Form(),
53
  files: List[UploadFile] = File(),
54
  ):
55
  """Send the inputs to the server."""
56
  # Retrieve the encrypted input image and the evaluation key paths
57
+ encrypted_query_image_path = get_server_file_path(
58
+ ENCRYPTED_QUERY_NAME, user_id, matcher
59
+ )
60
+ encrypted_reference_image_path = get_server_file_path(
61
+ ENCRYPTED_REFERENCE_NAME, user_id, matcher
62
+ )
63
+ evaluation_key_path = get_server_file_path("evaluation_key", user_id, matcher)
64
 
65
  # Write the files using the above paths
66
+ with encrypted_query_image_path.open(
67
+ "wb"
68
+ ) as encrypted_query_image_file, encrypted_reference_image_path.open(
69
+ "wb"
70
+ ) as encrypted_reference_image_file, evaluation_key_path.open(
71
  "wb"
72
  ) as evaluation_key:
73
+ encrypted_query_image_file.write(files[0].file.read())
74
+ encrypted_reference_image_file.write(files[1].file.read())
75
+ evaluation_key.write(files[2].file.read())
76
 
77
 
78
  @app.post("/run_fhe")
79
  def run_fhe(
80
  user_id: str = Form(),
81
+ matcher: str = Form(),
82
  ):
83
+ """Execute the matcher on the encrypted input images using FHE."""
84
  # Retrieve the encrypted input image and the evaluation key paths
85
+ encrypted_query_image_path = get_server_file_path(
86
+ "encrypted_query_image", user_id, matcher
87
+ )
88
+ encrypted_reference_image_path = get_server_file_path(
89
+ "encrypted_reference_image", user_id, matcher
90
+ )
91
+ evaluation_key_path = get_server_file_path("evaluation_key", user_id, matcher)
92
 
93
  # Read the files using the above paths
94
+ with encrypted_query_image_path.open(
95
+ "rb"
96
+ ) as encrypted_query_image_file, encrypted_reference_image_path.open(
97
+ "rb"
98
+ ) as encrypted_reference_image_file, evaluation_key_path.open(
99
  "rb"
100
+ ) as evaluation_key_file:
101
+ encrypted_query_image = encrypted_query_image_file.read()
102
+ encrypted_reference_image = encrypted_reference_image_file.read()
103
  evaluation_key = evaluation_key_file.read()
104
 
105
+ # Load the FHE server related to the chosen matcher
106
+ fhe_server = FHE_SERVERS[matcher]
107
 
108
  # Run the FHE execution
109
  start = time.time()
110
+ encrypted_output = fhe_server.run(
111
+ encrypted_query_image, encrypted_reference_image, evaluation_key
112
+ )
113
  fhe_execution_time = round(time.time() - start, 2)
114
 
115
+ # Retrieve the encrypted output path
116
+ encrypted_output_path = get_server_file_path(
117
+ ENCRYPTED_OUTPUT_NAME, user_id, matcher
118
+ )
119
 
120
  # Write the file using the above path
121
  with encrypted_output_path.open("wb") as encrypted_output:
122
+ encrypted_output.write(encrypted_output)
123
 
124
  return JSONResponse(content=fhe_execution_time)
125
 
 
127
  @app.post("/get_output")
128
  def get_output(
129
  user_id: str = Form(),
130
+ matcher: str = Form(),
131
  ):
132
+ """Retrieve the encrypted output."""
133
+ # Retrieve the encrypted output path
134
+ encrypted_output_path = get_server_file_path(
135
+ ENCRYPTED_OUTPUT_NAME, user_id, matcher
136
+ )
137
 
138
  # Read the file using the above path
139
  with encrypted_output_path.open("rb") as encrypted_output_file: