The MLIR dialect, its conversion passes (TFHEToConcrete, ConcreteToCAPI, ConcreteToLLVM), the optimizer component, the compiler binary, the Rust crate and the program_info parameter file all still carried the upstream project's name. They now carry this one's. Two things deliberately keep the word, because in them it is not the name. mlir::OpTrait CRTP parameters mean "the derived type", so they are spelled Derived and OpTy — LLVM's other conventional spelling for the same idiom. concreteShape is a capnp field: renaming it would change the serialized protocol, and the prose describing it is ordinary English. This also repairs references the earlier partial rename left dangling: Security.cpp included concrete/curves.gen.h and the directory is torus/, torus-cpu.h included concrete-num_complex.h, the Torus dialect CMakeLists named ConcreteOps.td, and compiler/setup.py asked find_namespace_packages for a "concrete" package that no longer exists — so it built without the package it ships.
3.8 KiB
Calling from other languages
After doing a compilation, we end up with a couple of artifacts, including crypto parameters and a binary file containing the executable circuit. In order to be able to encrypt and run the circuit properly, we need to know how to interpret these artifacts, and there are a couple of utility functions which can be used to load them. These utility functions can be accessed through a variety of languages, including Python and C++.
Demo
We will use a really simple example for a demo, but the same steps can be done for any other circuit. example.mlir will contain the MLIR below:
func.func @main(%arg0: tensor<4x4x!FHE.eint<6>>, %arg1: tensor<4x2xi7>) -> tensor<4x2x!FHE.eint<6>> {
%0 = "FHELinalg.matmul_eint_int"(%arg0, %arg1): (tensor<4x4x!FHE.eint<6>>, tensor<4x2xi7>) -> (tensor<4x2x!FHE.eint<6>>)
%tlu = arith.constant dense<[40, 13, 20, 62, 47, 41, 46, 30, 59, 58, 17, 4, 34, 44, 49, 5, 10, 63, 18, 21, 33, 45, 7, 14, 24, 53, 56, 3, 22, 29, 1, 39, 48, 32, 38, 28, 15, 12, 52, 35, 42, 11, 6, 43, 0, 16, 27, 9, 31, 51, 36, 37, 55, 57, 54, 2, 8, 25, 50, 23, 61, 60, 26, 19]> : tensor<64xi64>
%result = "FHELinalg.apply_lookup_table"(%0, %tlu): (tensor<4x2x!FHE.eint<6>>, tensor<64xi64>) -> (tensor<4x2x!FHE.eint<6>>)
return %result: tensor<4x2x!FHE.eint<6>>
}
You can use the toruscompiler binary to compile this MLIR program. Same can be done with torus-python, as we only need the compilation artifacts at the end.
$ toruscompiler --action=compile -o python-demo example.mlir
You should be able to see artifacts listed in the python-demo directory
$ ls python-demo/
client_parameters.torus.params.json compilation_feedback.json fhecircuit-client.h sharedlib.so staticlib.a
Now we want to use the Python bindings in order to call the compiled circuit.
from torus.compiler import (ClientSupport, LambdaArgument, LibrarySupport)
The main struct to manage compilation artifacts is LibrarySupport. You will have to create one with the path you used during compilation, then load the result of the compilation
lib_support = LibrarySupport.new("/path/to/your/python-demo/")
compilation_result = lib_support.reload()
Using the compilation result, you can load the server lambda (the entrypoint to the executable compiled circuit) as well as the client parameters (containing crypto parameters)
server_lambda = lib_support.load_server_lambda(compilation_result)
client_params = lib_support.load_client_parameters(compilation_result)
The client parameters will serve the client to generate keys and encrypt arguments for the circuit
client_support = ClientSupport.new()
key_set = client_support.key_set(client_params)
args = [
LambdaArgument.from_tensor_u8([1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4], [4, 4]),
LambdaArgument.from_tensor_u8([1, 2, 1, 2, 1, 2, 1, 2], [4, 2])
]
encrypted_args = client_support.encrypt_arguments(client_params, key_set, args)
Only evaluation keys are required for the execution of the circuit. You can execute the circuit on the encrypted arguments via server_lambda_call
eval_keys = key_set.get_evaluation_keys()
encrypted_result = lib_support.server_call(server_lambda, encrypted_args, eval_keys)
At this point you have the encrypted result and can decrypt it using the keyset which holds the secret key
result_arg = client_support.decrypt_result(client_params, key_set, encrypted_result)
print("result tensor dims: {}".format(result_arg.n_values()))
print("result tensor data: {}".format(result_arg.get_values()))
There is also a couple of tests in test_compilation.py that can show how to both compile and run a circuit between a client and server using serialization.