-
Notifications
You must be signed in to change notification settings - Fork 123
EzPC Wiki Page
EzPC provides a very C like programming interface. Here is a documentation on how to code in EzPC.
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).
-
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.
-
bool_bl
: represents a single bit boolean value; boolean shared.
-
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.
-
bool_pl
: represents a public boolean single bit value.
-
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 syntaxuint32_al[2] arr;
To access i
th 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;
-
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.
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;
Functions and variables in EzPC cannot have _
(an underscore) in their names.
EzPC supports multiline comments (OCaml style) by enclosing the code between (*
and *)
.
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 |
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);
}
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 functionfoo
, then you should place aconst
identifier in front of the param in function definition and the functionfoo
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.
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 *)
}
EzPC provides clean interface to take user input and give output to a select subset of the participating parties.
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
orSERVER
. For e.g.input(CLIENT, x, int32_al)
means that input to the variablex
is taken from the party acting as aCLIENT
. For the 2PC backend of EzPC, there are 2 parties, one being theCLIENT
and the other being theSERVER
. -
VAR: it specifies the name of the variable into which the input is taken. For e.g.
input(CLIENT, x, int32_al)
means thatCLIENT
s input is taken into the variablex
. -
TYPE: it specifies the data type of the variable into which input is taken. For e.g.
input(CLIENT, x, int32_al)
means thatCLIENT
s input is taken into the variablex
which is of typeint32_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.
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
orALL
, whereALL
means that bothCLIENT
andSERVER
get the output. - VAR: it specifies the name of the variable that is to be outputted.
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 *)
}
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.
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.
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 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;
};
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;
};
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.
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 willuint32_al y = -x+1u;
. But negation of constants is supported - which meansuint32_al y = -2u;
orint64_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;
oruint32_al y = 1u-x;
oruint32_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 thisinput(CLIENT, x, int32_al)
, you will get an error. You should remove the line wherex
is declared becauseinput(CLIENT, x, int32_al)
will automatically declarex
.
Use ezpc.sh
to compile programs with signed integers. For programs with only unsigned integers, ezpc executable works fine.
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);
}