-
Notifications
You must be signed in to change notification settings - Fork 16
EVA
EVA is an FHE compiler written in C++ (it also includes a Python wrapper) developed by the Research in Software Engineering (RiSE) group and Cryptography and Privacy Research group at Microsoft Research Redmond.
It seems that, like many MPC compilers that use Python as an input language, EVA (at least via its Python interface) essentially "unrolls" Python loops because they are invisibel to it. Other than some MPC compilers, it does not seem to offer a for loop annotation or equivalent to do true runtime loops. This makes sense since loops are generally inefficient in FHE.
It seems like when multiplying an encrypted variable (i.e. something that will later be a ctxt) with a list/vector, the vector is repeated to fill up to vector_size. I.e. if vec = [2] * vec_size is some encrypted input, then vec * [2, 3, 0] will result in [4, 6, 0, 4, 6, 0, 4, 6, 0, ...]
Remmeber to set -DPython_ROOT_DIR="" if CMake picks up the wrong python installation
- Dependencies (requires homebrew):
brew install clang
brew install make
brew install autoconf && brew install automake
- Installing the Protobuf Compiler:
Download the appropriate release here: https://github.com/google/protobuf/releases
Unzip the folder
Enter the folder and run
./autogen.sh && ./configure && make
Finally, run
make check
sudo make install
which protoc
protoc --version
- SEAL:
git clone -b v3.6.0 https://github.com/microsoft/SEAL.git
cd SEAL
cmake -DSEAL_THROW_ON_TRANSPARENT_CIPHERTEXT=OFF .
make -j
sudo make install
- EVA:
We strongly recommend creating a new python environment using conda
or something similar.
Numpy
needs to be installed:
git clone https://github.com/microsoft/EVA.git
cd EVA
git submodule update --init
cmake -DPYTHON_EXECUTABLE:FILEPATH=<YOUR_PYTHON_EXECUTABLE> .
make -j
python -m pip install -e python/
python python/setup.py bdist_wheel --dist-dir='.'
python tests/all.py
- NOTE: Apple M1 Chip (ARM64 Architecture):
If running
python tests/all.py
fails on Apple M1 chips, it might be the case that thepython
executable is for the wrong architecture. This can be checked by typingfile $(which python)
. If yourpython
executable is notarm64
, do the following:
- XCode Command Line Tools:
Install Xcode Command Line Tools by typing
xcode-select --install
- miniforge:
Install miniforge for arm64 (Apple Silicon) from miniforge github. Miniforge enables installing python packages natively compiled for Apple Silicon.
- Create Conda Environment:
Create an empty Conda environment, then activate it and install python 3.8 as well as numpy:
conda create --name arm64
conda activate arm64
conda install -y python==3.8.6
conda install numpy
- EVA:
Install EVA as described above using the appropriate conda
environment (arm64) and specifying the appropriate python
executable.
When using WSL on Windows, note that there is an issue with python's bdist_*
commands failing on mounted drives (e.g. /mnt/c/
in WSL) that can be fixed by granting "Full control" to "Authenticated Users" in the Windows File settings for the EVA source code folder.
By default, EVA (at least in its initial version which we evaluated for this SoK) does not copy the /tests
folder to the CMake binary directory.
By adding a CMakeList.txt
file to the folder with the content below and an add_subdirectory(tests)
to the top-level CMakeLists.txt
, this can be resolved.
add_custom_target(tests ALL DEPENDS python)
add_custom_command(TARGET tests
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
- Note that all targets must be built for the python module installation to work (in CLion, "all targets" is not created by default but can be manually added as a configuration).
- If compiling using WSL, CLion will call cmake via its absolute path, not from a shell in the code directory, and won't have venv's sourced. Therefore, results will differ between CLion's cmake and executing cmake in the same folder in bash.
One can specify the target python environment for find_package(Python) by setting
-DPython_ROOT_DIR="/home/alexander/.pyenv/versions/3.7.2/"
in CLion's cmake options
EVA uses the following language to represent programs:
eva.proto
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
syntax = "proto3";
package eva.msg;
message Term {
uint32 op = 1;
// Absolute indices to list of terms
repeated uint64 operands = 2;
repeated Attribute attributes = 3;
}
message ConstantValue {
uint32 size = 1;
// If sparse_indices is set then values are interpreted as a sparse set of values
// Otherwise values is interpreted as dense with broadcasting semantics and size must divide vec_size
// If values is empty then the whole constant is zero
repeated double values = 2;
repeated uint32 sparse_indices = 3;
}
message Attribute {
uint32 key = 1;
oneof value {
uint32 uint32 = 2;
sint32 int32 = 3;
uint32 type = 4;
ConstantValue constant_value = 5;
}
}
message TermName {
uint64 term = 1;
string name = 2;
}
message Program {
uint32 ir_version = 1;
string name = 2;
uint32 vec_size = 3;
repeated Term terms = 4;
repeated TermName inputs = 5;
repeated TermName outputs = 6;
}
However, when saving a program using the Python interface save(EvaProgram, string)
, the saved protobuf is actually of type known_type.proto
:
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
syntax = "proto3";
package eva.msg;
import "google/protobuf/any.proto";
message KnownType {
google.protobuf.Any contents = 1;
string creator = 2;
}
So, to access the actual program saved using save(poly, 'poly.eva')
in this snippet
poly = EvaProgram('Polynomial', vec_size=8)
with poly:
x = Input('x')
Output('y', 3 * x ** 2 + 5 * x - 2)
poly.set_output_ranges(20)
poly.set_input_scales(20)
compiler = CKKSCompiler()
poly, params, signature = compiler.compile(poly)
save(poly, 'poly.eva')
save(params, 'poly.evaparams')
save(signature, 'poly.evasignature')
we have to do the following:
import known_type_pb2
import eva_pb2
with open('poly.eva', 'rb') as f:
read_kt = known_type_pb2.KnownType()
read_kt.ParseFromString(f.read())
read_eva = eva_pb2.Program()
print(read_eva.ParseFromString(read_kt.contents.value))
where the two imported modules are crated via protoc --python_out=. known_type.proto
and protoc --python_out=. eva.proto
(on the shell).
The CHET compiler uses a slightly different IR defined in vec_lang.proto
:
syntax = "proto3";
package VecLang;
enum OpCode {
UNDEFINED_OP = 0;
// Basic computation
NEGATE = 1;
ADD = 2;
SUB = 3;
MULTIPLY = 4;
SUM = 5;
COPY = 6;
// Advanced computation
ROTATE_LEFT = 7;
ROTATE_RIGHT = 8;
// HE-specific computation
RELINEARIZE = 9;
MOD_SWITCH = 10;
RESCALE = 11;
NORMALIZE_SCALE = 12; // not being used currently
}
enum ObjectType {
UNDEFINED_TYPE = 0;
SCALAR_CONST = 1; // differentiate between double and int?
SCALAR_PLAIN = 2;
SCALAR_CIPHER = 3;
VECTOR_CONST = 4;
VECTOR_PLAIN = 5;
VECTOR_CIPHER = 6;
}
message Object {
uint64 id = 1; // unique identifier
}
message Instruction {
Object output = 1;
OpCode op_code = 2;
repeated Object args = 3;
}
message Vector {
repeated double elements = 1; // size must be 1 for scalar types
// separate field for int?
}
message Input {
Object obj = 1;
ObjectType type = 2; // must not be a constant type
double scale = 3; // in log2
}
message Constant {
Object obj = 1;
ObjectType type = 2; // must be a constant type
double scale = 3; // in log2
Vector vec = 4; // includes data (at compile-time)
}
message Output {
Object obj = 1;
double scale = 2; // in log2
}
message Program {
uint64 vec_size = 1; // must be a power of 2
repeated Constant constants = 2; // data available at compile-time
repeated Input inputs = 3; // data only available at runtime (must not include constants)
repeated Output outputs = 4;
repeated Instruction insts = 5;
}
message SEALObject {
enum SEALType {
ENCRYPTION_PARAMETERS = 0;
CIPHERTEXT = 1;
PLAINTEXT = 2;
SECRET_KEY = 3;
PUBLIC_KEY = 4;
GALOIS_KEYS = 5;
RELIN_KEYS = 6;
}
SEALType seal_type = 1;
bytes data = 2;
}
message ProgramEncryptionParameters {
SEALObject encryption_parameters = 1;
repeated uint64 galois_elements = 2;
}
message PublicKeys {
SEALObject public_key = 1;
SEALObject galois_keys = 2;
SEALObject relin_keys = 3;
}
message NamedValues {
map<string, SEALObject> values = 1;
}
message InputData {
Object obj = 1;
Vector vec = 2;
ObjectType type = 3; // must match that in Input: only replicated here for convenience
double scale = 4; // must match that in Input: only replicated here for convenience
}
message ProgramInput {
repeated InputData inputs = 1;
}
message OutputData {
Object obj = 1;
Vector vec = 2;
}
message ProgramOutput {
repeated OutputData outputs = 1;
}
- Home
- Compilers & Optimizations
- Libraries
- Benchmark Programs
- Implementation Docs