Skip to content

Using E3 in Your Program

Edu edited this page Sep 8, 2020 · 3 revisions

There are only two files required to build a program with E3: a C++ source file and a configuration file. The C++ file must be placed in its own project directory anywhere in the user's system. The configuration file does not have to be placed in the same directory as the C++ file, but the user will later have to indicate its path when building the project.

CPP File

E3 overloads all of the arithmetic and logical C++ operators, making it easier to write programs that run on encrypted data without having to learn a new API. E3 also provides secure data types to declare encrypted variables and perform operations on them.

Data-oblivious programming

Data-oblivious programming is a type of programming where the input data does not have an impact on the program's behavior. Data-oblivious programming is especially important when writing FHE programs because, although the data is encrypted, information about the execution context may still be leaked if the program makes decisions based on the input value. Existing algorithms may need modifications to be converted to a data-oblivious version. Two main pitfalls that should be avoided when writing data-oblivious programs are making control-flow decisions (if-else statement or loops) and accessing memory based on encrypted data. To address the problem of branching, the algorithm needs to execute for a predetermined, upper-bounded amount of iterations (i.e., obliviously), which would ensure that a correct result will be reached. For example, consider the following while statement:

sum = 0
i = 0
while(i < n) sum += arr[i++]

This statement depends on the value of n to exit the loop, which could leak the value of n. In a data-oblivious format, this statement would be converted to:

sum = 0
i = 0
while(i < maxIter) sum += (i < n)* arr[i++]

In the data-oblivious version, there is an upper bound on the number of iterations (maxIter) which is greater than n. However, the value of arr[i++] will only be added to the sum variable as long as i < n is true (1). When this logical statement becomes false (0), then arr[i++] will be multiplied by 0, which will add nothing to the sum. Other examples of data oblivious programming can be found in the doc directory.

E3 enforces data-oblivious programming by not allowing the user to branch based on encrypted data. Encrypted variables that are declared with the SecureInt type cannot be implicitly cast to bool types. For example, the logical expression if (x < y), with x,y defined as SecureInt, will fail to compile because x < y returns a SecureInt and not a boolean.

Secure Data Types

E3 provides secure data types that can be used to declare encrypted variables. These data types are named by the user in the configuration file. They are generated upon compilation based on the encryption library and the security parameters chosen by the user in the configuration file. E3's cgt.exe tool can generate both modular (used for modular arithmetic) and integral (used for bit-level arithmetic) data types. While only one modular type is generated (an integer), three basic integral types are generated by E3: 'int', 'unsigned', and 'bool'. Their class names are constructed from the basename specified in the configuration file by adding suffixes 'Int','Uint' and 'Bool' correspondingly. The data type names are defined in the configuration file using this syntax:

TypeName : CircuitType

Where TypeName can be chosen by the user, and the circuit type can be native, bridge, circuit, or ring. These circuit types are explained in more detail in Configuration File. To use the data types in the C++ file, the user must first include the "e3int.h" class which provides access to the secure data types. Then, the user can define the SecureInt type to be [user-defined name][regular datatype]. For example:

#include "e3int.h"

using SecureUint  = TypeNameUint<8>;
using SecureInt = TypeNameInt<8>;
using SecureBool = TypeNameBool;
SecureInt num1 = _5_Ep;

The SecureInt type is then used to declare a new encrypted variable, num1, which has a value of 5. The secure data types are also templated by the bistize of the variable. The bitsize can be any value between 1 and 64 bits. In the example above, both the unsigned integer and the integer have a bitsize of 8.

Using Postfixes to Encrypt Constants

E3 allows the user to initialize private variables with encrypted constants without having to manually encrypt every value. This can be done by appending a user-defined postfix to the end of the constant value. For example:

SecureInt num1 = _5_Ep;

In the example above, the postfix defined by the user is Ep. Note that there is an underscore before and after the constant value. When a postfix is used in the C++ program, an E3 helper tool will parse the C++ file, look for the constants with a postfix, and automatically generate the encrypted constants. This way, the executable generated by E3 will contain protected constants.

The postfix is defined in the configuration file as per the following syntax:

TypeName : CircuitType
{
    postfix = Ep
    postneg = En
}

The user can set two postfixes for each circuit type: postfix and postneg. postfix refers to the postfix appended to regular constants, while postneg refers to the postfix used with negative or signed constants. Note that postfix for constants is optional. If a postfix is not defined in the configuration file, then constants are forbidden in the program.

Configuration File

The configuration file is necessary to build a program using E3. It contains information about the secure data types, security parameters, and encryption libraries used in the program. This section will explain in detail how to create a configuration file suitable for your program.

General structure of the configuration file

The configuration file is usually placed in the same directory as the C++ source code, and it should be named 'cgt.cfg'. It has three main components: The secure types, the type names and the secure type parameters. The file has the following syntax:

password = password
maxinline = maxinline

TypeName1 : SecureType1
{
	parameter1 = value1
	parameter2 = value2
}

TypeName2 : SecureType2
{
	parameter1 = value1
	parameter2 = value2
}
...

The first variable is password. It is used for generating keys, and it can be set to any continuous string. The second variable, which is optional, is maxinline. On the left side of colon is the user-defined name for the secure data type. It can be any name you wish to use, but make sure that each SecureType has a different name. On the right side of the colon, declare the secure type you wish to use. Inside the curly brackets, assign values to the security parameters available for each secure type. The subsections below explain the components of the configuration file in more detail.

Type Names

The Type Names are the names you will use in your C++ source code to refer to the secure data types. When the program is compiled, E3 will generate secure classes that abstract the API of the chosen encryption library and allow the user to perform operations using overloaded C++ operators. These secure classes will have the names defined by the user in the configuration file. Three basic integral types are generated by E3: 'int', 'unsigned', and 'bool'. Their class names are constructed from the TypeName specified in the configuration file by adding the suffixes 'Int','Uint' and 'Bool' correspondingly (e.g. 'TypeNameInt', 'TypeNameUint', 'TypeNameBool'). On the other hand, only one modular type is generated by the E3 tool, and it uses the same name defined in the configuration file without modification (e.g. 'TypeName').

Secure Types

There are four secure types available in E3:

  • native: a weakly encrypted datatype often used for testing compilation without encryption.
  • circuit: an integral type used for bit-level arithmetic (See Bridging)
  • ring: a modular type used for modular arithmetic (See Bridging)
  • bridge: used to enable conversion from circuit type to ring type so that some operations can be done using boolean circuits and still be incorporated with other arithmetic operations (See Bridging)

Secure Type Parameters

Each secure type has certain encryption and security parameters that can be set or modified by the user. In addition to writing the parameters and their values in the body of the curly brackets, you can also declare them to be global variables by placing an @ sign before them and setting their value outside the brackets. For example, to set the circuitDB as a global variable, you would write:

@circuitDB = tfhe

The parameters available for each secure type are listed below :

  • circuit:
    • postfix: The postfix used to identify and automatically encrypt unsigned constants (see Using Postfixes to Encrypt Constants)
    • postneg: The postfix used to identify and automatically encrypt signed/negative constants (see Using Postfixes to Encrypt Constants)
    • encryption: The encryption scheme to be used for this secure type (see Encryption Schemes below)
    • circuitdb: This parameter indicates the version of the circuit to be used for bit-level arithmetic (for example, circuitDB = tfhe will use the circuits optimized for TFHE). If not indicated, it will try to use the circuits optimized for the chosen encryption scheme.
    • sizes: The bit-sizes to be included in the program for bit-level arithmetic. Bit-size 1 is always included, while the rest must be specified in this parameter (e.g. sizes=2-4,8 will include sizes 1,2,3,4,8).
    • polymodulusdegree: The degree of the polynomial modulus. It represents the degree of a power-of-two cyclotomic polynomial; therefore, it must be a positive power of 2. It determines the maximum number of coefficients that a ciphertext polynomial can have. Ciphertext size increases with the polynomial modulus degree, while performance decreases. Note that this parameter takes the exponent of 2 as a value, rather than the actual polynomial modulus degree (i.e. 15 rather than 2^15 or 32768).
    • plaintextmodulus: The plaintext modulus is a parameter specific to the SEAL BFV scheme. It defines the maximum value of the plaintext coefficient. To support batching, this value must be prime and should be congruent to 1 modulo 2 * the polynomial modulus degree.
    • encoder: This parameter is used for SEAL BFV and indicates if you are using integer or batching encoding.
  • bridge:
    • module: This parameter is used to enable bridging.
    • polymodulusdegree
    • plaintextmodulus
    • encoder
    • encryption
  • native:
    • postfix
    • postneg
  • ring:
    • postfix
    • postneg
    • polymodulusdegree
    • plaintextmodulusdegree
    • encoder
    • encryption

Encryption Libraries

Each secure type supports certain FHE libraries. This depends on whether the FHE library supports arithmetic circuits, boolean circuits, or both. The encryption libraries supported by each secure type are listed below. These libraries should be used as the values of the "encryption" parameter for each secure type. See Supported FHE Libraries for more information about each library:

  • bridge:
    • seal: Currently, only SEAL is implemented with bridging
  • circuit:
    • plain
    • bdd: An internally developed encryption scheme
    • fhew: The FHEW library
    • heli: The HELIB library
    • seal: SEAL library
    • tfhe: TFHE library
    • extern: An external encryption library that is not supported by E3.
  • ring:
    • pail: LibPaillier library
    • seal: SEAL library
    • seal_ckks: SEAL with CKKS scheme