Client/Server demo with Pyfhel

This demo shows an example of Client-Server interaction, where the client sends an encrypted vector and the server answers with the weighted average based on his weights.

To run with real Client/Server separation (using flask and Demo_5bis_CS_Server.py), change the flag below to True.

USE_REAL_SERVER: bool = False

1. Setup Client

import numpy as np
from Pyfhel import Pyfhel, PyCtxt
if USE_REAL_SERVER:
    try:
        import requests
    except ImportError:
        print("This demo requires the `requests` python module (install with pip). Exiting.")
        exit(0)

# Generate Pyfhel session
print(f"[Client] Initializing Pyfhel session and data...")
HE_client = Pyfhel(context_params={'scheme':'ckks', 'n':2**13, 'scale':2**30, 'qi_sizes':[30]*5})
HE_client.keyGen()             # Generates both a public and a private key
HE_client.relinKeyGen()
HE_client.rotateKeyGen()

# Generate and encrypt data
x = np.array([1.5, 2, 3.3, 4])
cx = HE_client.encrypt(x)

# Serializing data and public context information
s_context    = HE_client.to_bytes_context()
s_public_key = HE_client.to_bytes_public_key()
s_relin_key  = HE_client.to_bytes_relin_key()
s_rotate_key = HE_client.to_bytes_rotate_key()
s_cx         = cx.to_bytes()

print(f"[Client] sending HE_client={HE_client} and cx={cx}")
[Client] Initializing Pyfhel session and data...
[Client] sending HE_client=<ckks Pyfhel obj at 0x7fecac57d300, [pk:Y, sk:Y, rtk:Y, rlk:Y, contx(n=8192, t=0, sec=128, qi=[30, 30, 30, 30, 30], scale=1073741824.0, )]> and cx=<Pyfhel Ciphertext at 0x7fecac57d4f0, scheme=ckks, size=2/2, scale_bits=30, mod_level=0>

2. Setup Server

print(f"[Client] launching server (could be launched separately)...")
if(USE_REAL_SERVER):
    import subprocess, os
    from pathlib import Path
    dir = Path(os.path.realpath("__file__")).parent
    process = subprocess.Popen(
        ["python", str(dir / "Demo_5bis_CS_Server.py")],
        stderr=subprocess.STDOUT,
    )
    import time
    time.sleep(6)       # Wait for server initialization
else:
    print(f"[Server] mock started!...")
print("[Client] server initialized...")
[Client] launching server (could be launched separately)...
[Server] mock started!...
[Client] server initialized...

3. Launch a request to the server

We map the bytes into strings based on https://stackoverflow.com/a/27527728

if(USE_REAL_SERVER):
    r = requests.post('http://127.0.0.1:5000/fhe_mse',
        json={
            'context': s_context.decode('cp437'),
            'pk': s_public_key.decode('cp437'),
            'rlk':s_relin_key.decode('cp437'),
            'rtk':s_rotate_key.decode('cp437'),
            'cx': s_cx.decode('cp437'),
        })
    c_res = PyCtxt(pyfhel=HE_client, bytestring=r.text.encode('cp437'))
else: # Mocking server code (from Demo_5bis_CS_Server.py)
    # Read all bytestrings
    HE_server = Pyfhel()
    HE_server.from_bytes_context(s_context)
    HE_server.from_bytes_public_key(s_public_key)
    HE_server.from_bytes_relin_key(s_relin_key)
    HE_server.from_bytes_rotate_key(s_rotate_key)
    cx = PyCtxt(pyfhel=HE_server, bytestring=s_cx)
    print(f"[Server] received HE_server={HE_server} and cx={cx}")

    # Encode weights in plaintext
    w = np.array([0.5, -1.5,   4,  5])
    ptxt_w = HE_server.encode(w)

    # Compute weighted average
    c_mean = (cx * ptxt_w)
    c_mean /= 4  # 4
    c_mean += (c_mean >> 1)   # cumulative sum
    c_mean += (c_mean >> 2)   # element [3] contains the result
    print(f"[Server] Average computed! Responding: c_mean={c_mean}")

    c_res = c_mean.copy() # Copying with a single command
[Server] received HE_server=<ckks Pyfhel obj at 0x7fecaf55ead0, [pk:Y, sk:-, rtk:Y, rlk:Y, contx(n=8192, t=0, sec=128, qi=[30, 30, 30, 30, 30], scale=1073741824.0, )]> and cx=<Pyfhel Ciphertext at 0x7fecac106360, scheme=ckks, size=2/2, scale_bits=30, mod_level=0>
[Server] Average computed! Responding: c_mean=<Pyfhel Ciphertext at 0x7fecac57d4f0, scheme=ckks, size=2/2, scale_bits=60, mod_level=2>

4. Process Response

Decrypting result

res = HE_client.decryptFrac(c_res)

# Checking result
w = np.array([0.5, -1.5,   4,  5]) # in the server
expected = np.mean(x*w)
print(f"[Client] Response received! Result is {np.round(res[3], 4)}, should be {expected}")
[Client] Response received! Result is 7.7382, should be 7.7375
if USE_REAL_SERVER:
    process.kill()

Total running time of the script: (0 minutes 2.354 seconds)

Estimated memory usage: 377 MB

Gallery generated by Sphinx-Gallery