Skip to content
moritzwinger edited this page Mar 17, 2021 · 26 revisions

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.

Compilation / Optimizations

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, ...]

Setup

Remmeber to set -DPython_ROOT_DIR="" if CMake picks up the wrong python installation

OSX

  • 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 the python executable is for the wrong architecture. This can be checked by typing file $(which python). If your python executable is not arm64, do the following:
  1. XCode Command Line Tools:

Install Xcode Command Line Tools by typing

xcode-select --install
  1. miniforge:

Install miniforge for arm64 (Apple Silicon) from miniforge github. Miniforge enables installing python packages natively compiled for Apple Silicon.

  1. 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
  1. EVA:

Install EVA as described above using the appropriate conda environment (arm64) and specifying the appropriate python executable.

Windows

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.

Out-of-source build

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}
)

CLion

  • 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

Protocol Buffer IR

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).

CHET Protocol Buffer IR

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;
}
Clone this wiki locally