Computing homomorphic distances between embeddings

from concrete import fhe
from concrete.fhe import Value
from concrete.compiler import TransportValue  # Import TransportValue

# Constants for scaling float embeddings
SCALING_FACTOR = 10 # Multiply by 10^4 to retain 4 decimal places
MAX_VALUE_incryption = 2**7 - 1   # Ensure values stay within 16-bit limit
MAX_VALUE = 2**17 - 1   # Ensure values stay within 16-bit limit

# Step 1: Define Encryption Function
def encrypt_single_value(x: int):
    print("***********", x)
    return x  # Identity function for FHE encryption

# Step 2: Define Distance Functions
def manhattan_distance(vec1, vec2):
    print("vec1 ", vec1)
    return sum([fhe.abs(a - b) for a, b in zip(vec1, vec2)])

def euclidean_distance_squared(vec1, vec2):
    return sum((a - b) ** 2 for a, b in zip(vec1, vec2))

# Step 3: Compile Encryption & Distance Circuits
print(f"🔹 Compiling encryption circuit...")
encryption_compiler = fhe.Compiler(encrypt_single_value, {"x": "encrypted"})

print(f"🔹 Compiling distance circuits...")
manhattan_compiler = fhe.Compiler(manhattan_distance, {"vec1": "encrypted", "vec2": "encrypted"})
euclidean_compiler = fhe.Compiler(euclidean_distance_squared, {"vec1": "encrypted", "vec2": "encrypted"})

# Step 4: Generate Encryption Circuit
inputset = [(a) for a in range(MAX_VALUE_incryption)]
print("inputset", inputset)
encryption_circuit = encryption_compiler.compile(inputset)

print(f"🔹 Generating keys for encryption...")
encryption_circuit.keygen()

# Step 5: Generate Distance Circuits
inputset_vectors = [(a, a + 100) for a in range(MAX_VALUE)]
print("inputset_vectors", inputset_vectors)

print(f"🔹 Compiling distance circuits with input set vectors...")
manhattan_circuit = manhattan_compiler.compile(inputset_vectors)
euclidean_circuit = euclidean_compiler.compile(inputset_vectors)

print(f"🔹 Generating keys for distance computation...")
manhattan_circuit.keygen()
euclidean_circuit.keygen()

# Step 6: Encrypt Face Embeddings
def encrypt_vector(vector, circuit):
    print("vector", vector)
    scaled_vector = [int(v * SCALING_FACTOR) for v in vector]
    print("scaled_vector", scaled_vector)
    encrypted_vector = [circuit.encrypt(v) for v in scaled_vector]
    print("encrypted_vector", encrypted_vector)
    # Validate the length of the encrypted vector
    expected_shape = (4, 1281)  # Expected shape: 4 feature vectors, each with 1281 values

    # Print shape information
    print("Length of encrypted_vector:", len(encrypted_vector))  # Should be 4
    for i, enc_val in enumerate(encrypted_vector):
        try:
           print(f"Encrypted vector {i} shape:", enc_val.shape)
        except AttributeError:
           print(f"Encrypted vector {i} has no shape attribute, but is type {type(enc_val)}")
    return encrypted_vector

face_embeddings = [
    [0.12, 0.56, 0.78, 0.34],
    [0.14, 0.54, 0.79, 0.31],
    [0.20, 0.60, 0.72, 0.40],
    [0.11, 0.50, 0.80, 0.30],
]

print(f"🔹 Encrypting face embeddings...")
encrypted_embeddings = [encrypt_vector(embedding, encryption_circuit) for embedding in face_embeddings]

# Step 7: Compute Homomorphic Distances
def compute_homomorphic_distances(enc_x, enc_y):
    print(f"Computing distances for encrypted vectors...")

    # Ensure that the encrypted values are passed as individual arguments, not as a list
    try:
        # Unpack the encrypted lists and pass them as separate arguments
        print("enc_x ", enc_x)
        print("enc_y ", enc_y)
        encrypted_manhattan = [manhattan_circuit.run(ex, ey) for ex, ey in zip(enc_x, enc_y)]
        decrypted_manhattan = manhattan_circuit.decrypt(encrypted_manhattan) / SCALING_FACTOR
        print("decrypted_manhattan ", decrypted_manhattan)
        encrypted_euclidean = euclidean_circuit.run(*enc_x, *enc_y)
        decrypted_euclidean = euclidean_circuit.decrypt(encrypted_euclidean) / (SCALING_FACTOR ** 2)

        return decrypted_manhattan, decrypted_euclidean

    except Exception as e:
        print("🚨 Error in computing homomorphic distances:", str(e))
        raise

print(f"🔹 Computing homomorphic distances between embeddings...")
results = {}
for i in range(len(encrypted_embeddings)):
    for j in range(i + 1, len(encrypted_embeddings)):
        key = f"Face {i+1} vs Face {j+1}"
        manhattan_result, euclidean_result = compute_homomorphic_distances(encrypted_embeddings[i], encrypted_embeddings[j])
        results[key] = {
            "Manhattan Distance": manhattan_result,
            "Euclidean Distance Squared": euclidean_result
        }

print(f"✅ Success! Computed distances:")
for key, value in results.items():
    print(f"{key} -> Manhattan: {value['Manhattan Distance']}, Euclidean²: {value['Euclidean Distance Squared']}")

1 Like