Skip to content

EzPC Wiki Page

Pratik Bhatu edited this page Jan 10, 2021 · 1 revision

EzPC provides a very C like programming interface. Here is a documentation on how to code in EzPC.

Data Types

Here are the data types supported by EzPC. These data types are based on the type of underlying cryptographic secret sharing which can be arithmetic or boolean. By default, EzPC uses arithmetic shares for integers.

Here Secret data types are the ones whose values are kept secret from each participating party during the protocol and Public types are not hidden and known to all parties (e.g. constants).

Secret Integer Data Types

  • int32_al: represents 32-bit signed integers which are arithmetically shared.
  • uint32_al: represents 32-bit unsigned integers; arithmetically shared.
  • int64_al: represents 64-bit signed integers; arithmetically shared.
  • uint64_al: represents 64-bit unsigned integers; arithmetically shared.
  • int32_bl: represents 32-bit signed integers which are boolean shared.
  • uint32_bl: represents 32-bit unsigned integers; boolean shared.
  • int64_bl: represents 64-bit signed integers; boolean shared.
  • uint64_bl: represents 64-bit unsigned integers; boolean shared.

Secret Boolean Data Types

  • bool_bl: represents a single bit boolean value; boolean shared.

Public Integer Data Types

  • int32_pl: represents public 32-bit signed integers.
  • uint32_pl: represents public 32-bit unsigned integers.
  • int64_pl: represents public 64-bit signed integers.
  • uint64_pl: represents public 64-bit unsigned integers.

Public Boolean Data Types

  • bool_pl: represents a public boolean single bit value.

Non-primitive Data Types

  • Arrays: EzPC supports arrays of the above mentioned data types. To define an array of uint32_al with size = 2, for example, this is the syntax uint32_al[2] arr;

To access ith element of an array arr, use arr[i].

NOTE: Arrays in EzPC can be declared by using a Public data type for size argument. For e.g.

uint32_pl s = 2u;
uint32_al[s] arr;

Global Data

  • int32: represents public 32-bit signed integers.
  • uint32: represents public 32-bit unsigned integers.
  • int64: represents public 64-bit signed integers.
  • uint64: represents public 64-bit unsigned integers.

Constants

Every constant in EzPC should carry a suffix to disambiguate its representation.

  • 32-bit signed constants don't require a suffix. For e.g. 101.
  • 32-bit unsigned constants should be suffixed with u. For e.g. 101u.
  • 64-bit signed constants should be suffixed with L. For e.g. 101L.
  • 64-bit unsigned constants should be suffixed with uL. For e.g. 101uL.

EzPC is strongly typed so you cannot do the following:

int64_pl a = 3;

or

int64_pl big = 3L;
int32_pl small = big;

Variable and Function Naming

Functions and variables in EzPC cannot have _ (an underscore) in their names.

Comments in EzPC

EzPC supports multiline comments (OCaml style) by enclosing the code between (* and *).

Operators in EzPC

Arithmetic Operators Bitwise Operators Logical Operators
+ Add >> Arithmetic Right Shift > Greater Than
- Subtract << Left Shift < Lesser Than
* Multiplication & Bitwise AND == Equal
/ Division | Bitwise OR >= Greater Than Equal
% Modulo ^ Bitwise XOR <= Lesser Than Equal
^^ Power ~ Bitwise Negation ! Logical negation
&& Logical AND
|| Logical OR
@ Logical XOR
>>> Logical Right Shift

Functions in EzPC

An EzPC program code, much like a C/C++ program, should have a main function in it. It can be defined as:

def void main()
{
(* body of main function *)
}

Any other function, say foo(x, y), where x is uint32_al and y is uint32_al[2] (size 2 array) and returns nothing, can be defined as:

def void foo(uint32_al x, uint32_al[2] y)
{
(* function body *)
}

If main() function has to call foo, then the code looks like:

def void main()
{
uint32_al x = 9u; (* assigning a constant value *)
uint32_al[2] y;
foo(x, y); 
}

Functions With Non-void Return Type

Any function, say foo(x, y), where x is uint32_al and y is uint32_al and returns int32_al, can be defined as:

def int32_al foo(uint32_al x, uint32_al y)
{
(* function body *)
(* Example of returning *)
(*
int32_al z;
...
return z;
*)
}

NOTE: A function in EzPC can only return Primitive Data Types.

A function can be called in 2 forms: expression form and statement form. Based on the form, there are some EzPC specifics which are defined below.

  • Expression form: int32_al z = foo(x, y) is an example of the expression form. When a function is called in the expression form, there is a constriant that all of the args to the function should be immutable by reference (scroll down to get more information about call by value and call by reference in EzPC) . This means that if you want to send an argument by reference to the function foo, then you should place a const identifier in front of the param in function definition and the function foo should not try to update the value of the arg variable in the function body. For e.g.
def int32_al foo(uint32_al x, const uint32_al[2] y)
{
(* function body *)
(* Example of returning *)
(*
int32_al z;
...
return z;
*)
}

In the code above, notice const identifier in from of argument y, this is important for the program to compile. Inside the foo function, variable y can only be read-from and cannot be written to once const identifier is used with it. Now if main() function has to call foo in the expression form, then the code looks like:

def void main()
{
uint32_al x = 9u; (* assigning a constant value for illustration only *)
uint32_al[2] y;
int32_al z = foo(x, y); 
}
  • Statement form: foo(x, y) is an example of the statement form. It can be used freely without any constraint.

Defining functions with arrays of variable lengths

If you want to pass an array to your function, but the size of the array to be passed is not fixed, then you can do something like this:

def int32_al foo(int32_pl size, uint32_al[size] y)
{
(* function body *)
}

Input and Output in EzPC

EzPC provides clean interface to take user input and give output to a select subset of the participating parties.

Input

EzPC provides input function to take input from user. input(ROLE, VAR, TYPE) input function takes 3 arguments:

  • ROLE: it specifies the party from which input has to be taken. ROLE can be either CLIENT or SERVER. For e.g. input(CLIENT, x, int32_al) means that input to the variable x is taken from the party acting as a CLIENT. For the 2PC backend of EzPC, there are 2 parties, one being the CLIENT and the other being the SERVER.
  • VAR: it specifies the name of the variable into which the input is taken. For e.g. input(CLIENT, x, int32_al) means that CLIENTs input is taken into the variable x.
  • TYPE: it specifies the data type of the variable into which input is taken. For e.g. input(CLIENT, x, int32_al) means that CLIENTs input is taken into the variable x which is of type int32_al.

NOTE: If you have already declared a variable x and you try to use input(CLIENT, x, int32_al), then you will get an error. You don't need to declare x. It will automatically be declared when you use input(CLIENT, x, int32_al).

NOTE: You can use input(CLIENT, x, int32_al[21]) to directly read into an array x of size 21.

Output

To output the clear (unencrypted) value of a variable to a subset of parties, EzPC provides output function. output(ROLE, VAR) output function takes 2 arguments:

  • ROLE: it specifies the subset of parties to which output will be shown in clear. It can be CLIENT, SERVER or ALL, where ALL means that both CLIENT and SERVER get the output.
  • VAR: it specifies the name of the variable that is to be outputted.

Call by Reference and Call by Value in EzPC

Call by Value

In EzPC, anything that is not an array is passed around functions by value. This means that the changes made to primitive data types will not sustain when the call is returned back from the callee to the caller.

def void foo(uint32_al x, uint32_al y)
{
(* value of x here is 9u and value of y is 8u *)
x = y;
(* value of x now is 8u *)
}

def void main
{
uint32_al x = 9u;
uint32_al y = 8u;
foo(x, y);
(* value of x here is 9u. It did NOT SUSTAIN the change made by foo *)
(* this was call by value *)
}

Call by Reference

All the arrays in EzPC are passed around by reference. Any changes made to arrays by the callee will sustain after the call returns back to the caller.

def void foo(uint32_al[1] x, uint32_al y)
{
(* value of x[0] here is 9u and value of y is 8u *)
x[0] = y;
(* value of x[0] now is 8u *)
}

def void main
{
uint32_al[1] x;
x[0] = 9u;
uint32_al y = 8u;
foo(x, y);
(* value of x[0] here is 8u. The changes made by foo persisted *)
(* this was call by reference *)
}

To make primitive datatypes passed by reference, you can declare a singleton array with the primitive variable in it. For e.g. see the above code. We used singleton array to pass x by reference.

Conditionals, Loops and Arrays in EzPC

It is a very common practice in cryptography that the execution time of the program shouldn’t depend on the values that have to be kept secret or are secret shared. It is easy to see that if the program execution time depends on a secret value, then information about this secret value’s magnitude or bits can be leaked by just minutely observing the runtime of the program.

Due to the above-mentioned rationale, EzPC does not allow Secret variables to be used in if statements and for loops and does not allow indexing of arrays with Secret variables.

Arrays

  int64_al[100] arr;
  (* Can't index arrays with secret values.*)
  input(CLIENT, idx, int64_al);
  int64_al out = arr[idx];      (* Illegal Instruction. Susceptible to side channel attacks *)

Loops

Loops in EzPC cannot have Secret variables in their conditions. For e.g.

int32_pl N = 100;
int32_al[N] arr;
for i=[0:N]
{
arr[i] = i; 
};

Conditional Statements

if statements in EzPC cannot have Secret vars in its condition. For e.g.

int32_al a = 5;
bool_pl b = true;
if (b)
{
a = 20;
}
else
{
a = 30;
};

Multiplexers

You can reorganize your code in such a way that you can replace if statements that rely on Secret vars by multiplexer expressions. For e.g. an if condition like, where x is a Secret variable:

if(x > 0uL)
{
y = y + 1uL;
}

can be replaced with this:

y = (x > 0uL)?y+1uL:y;

However say we do this

input(CLIENT, a, int64_al);
input(SERVER, b, int64_al);
int64_pl c;
c = (a > b) ? 100 : 90;    (* This will not compile as we are leaking private information (a>b) through the public value of c.

Common Parse Errors and Fixes

When programming in EzPC, you might face parse errors. Some common parse errors are mentioned below.

  • Missing ; (semi-colon) at the end of the statement.
  • Using _ (underscore) in function or variable names.
  • Other common syntactic typos like missing ) , } etc.
  • Using language specific keywords as variable names, like output, input.
  • Currently negation of a variable is not supported. For example, uint32_al y = -x; would result in parse error. So will uint32_al y = -x+1u;. But negation of constants is supported - which means uint32_al y = -2u; or int64_al y = -10L+x; will compile fine. To use negation of variables, use binary subtract operation in place of unary minus. For example, uint32_al y = 0u-x; or uint32_al y = 1u-x; or uint32_al y = 0u-x+1u; will work fine.
  • If you get a coersion error, say when you do something like this
int64_bl a = 0L;
int64_bl b = 1L;
a = a + (a*b);

and you get an error like this a +_<no label> <boolean ~> arithmetic> a *_arithmetic <boolean ~> arithmetic> b . Then you can fix this error by doing this

int64_bl a = 0L;
int64_bl b = 1L;
a = a +_al (a *_al b);

By doing this, you tell the compiler explicitly to first cast both a and b from int64_bl to int64_al, perform the addition and multiplication in arithmetic form and then cast both of them back to int64_bl.

  • If you plan on using 64-bit variables, then make sure to use ./ezpc.sh <yourprogram>.ezpc --bitlen 64.
  • If you have already declared a variable or an array x and then you write something like this input(CLIENT, x, int32_al), you will get an error. You should remove the line where x is declared because input(CLIENT, x, int32_al) will automatically declare x.

Compiling EzPC Program to CPP

Use ezpc.sh to compile programs with signed integers. For programs with only unsigned integers, ezpc executable works fine.

Example Showing Interplay b/w Arithmetic and Boolean types

def void main()
{
input(SERVER, a, bool_bl);
input(CLIENT, b, bool_bl);
input(SERVER, c, uint32_al);
input(CLIENT, d, uint32_al);
input(SERVER, e, uint32_bl);
bool_bl f = ((c * d) > e);
bool_bl g = ((a && b) || f);
output(ALL, g);
}