Obliv-C - Documentation
About this Documentation
This documention is provided to make the Obliv-C API simpler for users to understand in order to write programs in Obliv-C successfully, and uses an informal tone to achieve this purpose. For a more formal introduction to Obliv-C, read Obliv-C: A Language for Extensible Data-Oblivious Computation. For getting started quickly programming with Obliv-C, we recommend using the tutorial.
This documentation is generated from https://github.com/uvasrg/oblivc-web/; please submit an Issue or PR if you have suggestions on improving it.
Introduction
Obliv-C was designed to provide a convenient bridge between users of secure computation protocols and protocol developers. Programmers who want to use secure computation, but are not experts in cryptography, should be able to use it easily and securely. On the other hand, experts should be able to provide extra primitives as simple library functions without having to invent a full language/compiler from scratch. This is intended to rectify the current situation where a new language needs to be invented every time someone decides to develop a new cryptographic protocol (e.g., ORAM for random access, private set intersection protocols, or protocols for other adversarial models). This way researchers can test new cryptographic techniques without having to be experts in language design/compilers.
The way this language is supposed to be used is as follows: we start with a code written in this Obliv-C, a strict extension of standard C. All parties agree to execute this program in a secure execution protocol, where most inputs are private, and only the output of the computation are to be revealed to all (or some) parties. The intermediate results stay hidden, protected by the underlying cryptographic protocols.
Oblivious Types
The obliv
qualifier
The obliv
qualifier should be declared on any variable that is
supposed to depend on unknown inputs in any way; obliv
-qualified
values implement secure encryption on the variable, and can only be
declared in an Obliv-C file (.oc
).
Supported data types for the obliv
qualifier are bool
, int
,
char
, short
, long
, long long
, and float
. Non-obliv values
can be implicitly converted into obliv
, but obliv
values cannot be
implicitly converted to non-obliv. Doing this requires
revealObliv<Tname>()
(described below) to be called to indicate that
previously-oblivious data is now being revealed.
Control Structures
For oblivious execution, program control can never depend on obliv
values, which are not (semantically) accessible to the execution. In general, we cannot hide the length of time a protocol takes to run, and in the same vein, the number of iterations of a loop is also known to all parties (since they are all executing the protocol). Hence, Obliv-C will not allow an obliv
variable to be ever used in the condition of any existing control structure (e.g., for
, while
, if
, etc.).
Oblivious conditionals: obliv if
The new control structure ‘obliv if’ can be used with obliv
values in the predicate. Its syntax is exactly like the normal if structure:
obliv if(...) {
...
} else obliv if(...) {
...
} else {
...
}
This control structure allows you to use obliv
variables in if conditions, and works a lot like normal if blocks.
However, while it allows you to use obliv variables as a condition, you must realize some obvious differences. For one, during execution, it is not known (to the system executing this program) if the condition is true or not — that is the whole point of obliv
-qualified variables. So the blocks will be actually executed regardless of the condition: it doesn’t matter if the condition is true or not. So consider this:
obliv int x, y;
...
obliv if (x > 0) y = 4;
The body of the obliv if
will be executed regardless of whether x
is positive or not. However, none of the parties would know if their execution actually changed the value of y
or not. If x
was positive, y
is now 4 after this is executed. If not, y
still has the same value as before. But the actual value of y
is stored encrypted in the circuit, and unknown to the executions. The assignment operation simply has no impact on the semantic value of y
if the condition is false.
While obliv if
allows you to use obliv
variables in a condition, it does come with its own set of restrictions. For example, this is not allowed:
int x=0;
obliv int y;
obliv if (y > 0) x = 1; // error x is not obliv
In general, inside a given obliv if
context, the program cannot modify any non-obliv variable that was declared outside the block, since that would betray the value of obliv condition.
If you find yourself wanting to do this, you probably want x
to be
an obliv int
in the first place. (If you really want to do this, you
can escape the type system using ~obliv
to create an unconditional
segment.)
However, this is legal:
obliv if (...) {
for (int i = 0; i < n; i++) { … }
}
Even though both i
and n
are non-obliv, this is okay, since i
was declared inside the obliv if
block and n
is never modified. The requirement is that any changes made to non-obliv variables while inside an oblivious conditional scope are never visible from the outside.
Control statements like break
, continue
, return
, goto
, and
case labels, etc. cannot be used to jump across obliv if
boundaries. Jump-like statements can only move control flow within the same obliv if
block.
Functions
Consider the following code:
int x;
void func() { x++; }
…
void protocolMain() {
obliv if (...)
func(); // Not valid code!
}
This is obviously problematic, since it would violate the restrictions on obliv if
: we are modifying a global, non-obliv variable x
inside an oblivious context.
To accommodate function calls in oblivious contexts, Obliv-C introduces obliv
functions. We now have two kinds of functions:
void func() { … }
void func2() obliv { … }
The difference is simple: you are allowed to perform any operation inside a normal (non-obliv) function (func()
), but it may not be invoked from inside an oblivious context (which includes any obliv if
block, as well as inside other obliv
functions).
On the other hand, func2
is an obliv
function — it can be called from anywhere, but may not modify any non-obliv variables not declared inside the function.
There is one more issue related to calling functions that comes up when passing parameters by reference. Consider this:
void ofunc(int* p1, const int* p2) obliv { … }
When this function is invoked, there are restrictions on what p1
can point to. For example, this is problematic:
int n, m;
obliv if (...) { ofunc(&n,&m); } // error
obliv if (...) { int i; ofunc(&i,&m); } // ok
When compiling, the compiler sees n
being passed by reference. So it assumes that ofunc
may be used to modify n
. The variable i
is declared inside, so the second invocation is okay, since i can be modified in this scope. Non-obliv variables declared outside becomes something like a const
variable when entering an oblivious scope. We say “something like a const” because there is a slight difference between C’s notion of const
, and our notion here of a frozen
variable.
frozen
For simple variables, frozen
may be implicitly converted to const
: frozen int*
can be used whenever a function really expects const int*
. The difference, however, shows up in case of structs, where they are not implicitly convertible. Here, frozen struct foo*
refers to the “deep-const” version of foo
.
Consider the following code fragment:
struct S { int x, *p; };
void func(const struct S* a, frozen struct S* b) {
// Neither a->x nor b->x can be modified here, as expected
a->x = 5; // error
b->x = 5; // error
// But *b->p is different from *a->p
*a->p = 5; // ok: a->p has type int *const,
// as opposed to const int*
*b->p = 5; // still error; frozen is recursively applied
// to all pointer fields in a struct/union
}
This slight difference is the reason Obliv-C introducing the new keyword frozen
.
In general, any non-obliv variable becomes frozen-qualified when entering an oblivious scope. For simpler types, this distinction between const
and frozen
does not matter. But for structs, unions, pointer-to-pointers, etc., it does. For example, int **frozen
is actually the same as const int *const *const
, which is different from const int**
or int **const
.
Unconditional Segments
This is the one part of Obliv-C that looks very different from anything in C. Essentially, it chalks out a block of code for unconditional execution even though the surrounding code is conditional on some oblivious data.
For example,
int n = …;
obliv if (cond) { // line 1
n = 5; // error: cannot modify outer non-obliv n in obliv scope
~obliv(c) {
n = 5; // valid: but the assignment happens even if c is false
}
}
Since the execution of the ~obliv
block does not depend on any obliv value, it gets rid of all restrictions on frozen variable modification. The restrictions can be later reinstated by using a nested obliv-if structure, if desired.
However, remember that we are in an oblivious context, and the contents of the ~obliv
block will be executed regardless of the value of the condition. So, n
will be modified inside the ~obliv
even if the condition is false!
The value of the parameter c
in ~obliv(c)
will hold the condition. The ~obliv(varname)
syntax declares a new variable, varname
of type obliv bool
that indicate whether the enclosing oblivious condition is true or false.
This syntax is useful for library developers when they know that there is a more efficient way of executing obliv functions than what one can otherwise write. Many of the standard library functions (for example, ORAM designs) use this feature, so you can find useful examples of its use there. In general, when this is used, it is the responsibility of the programmer to make it look like nothing is being executed even when the condition is false (i.e., its execution should not have any visible non-obliv side effects if cond is false, or such side-effects should be constrained to implementation-internal variables only). On the other hand, neither should the programmer assume that it will always be executed even if the condition is false, since the compiler is allowed to optimize it away.
The ~obliv
escapes the normal type system for Obliv-C, allowing programmers write code that could make no sense if you are not careful. But, it poses no security risk — the value of the oblivious condition (and all obliv variables) is not known to the execution and protected by the protocol, so there is no risk of leaking oblivious data by using ~obliv
.
API Documentation
Take a look through the tutorial to get started. In order to start running your Obliv-C program between two parties, ensure that the following Obliv-C library functions are called in order:
int protocolAcceptTcp2P(ProtocolDesc* pd, const char* port)
int protocolConnectTcp2P(ProtocolDesc* pd, const char* server, const char* port)
void setCurrentParty(ProtocolDesc* pd, int party)
void execYaoProtocol(ProtocolDesc* pd, protocol_run start, void* arg)
- Any Obliv-C functions needed in your
.oc
files void cleanupProtocol(ProtocolDesc* pd)
C Functions
These functions should be called in a C file, and require using #include <obliv.h>
.
int protocolAcceptTcp2P(ProtocolDesc* pd, const char* port)
The first party calls this function to listen for a second party on the supplied port parameter. Use conditional logic to ensure only the first party calls this function.
int protocolConnectTcp2P(ProtocolDesc* pd, const char* server, const char* port)
The second party should call this function after the first party calls
protocolAcceptTcp2P()
; use conditional logic to ensure this. Supply
either ‘localhost’, the IP address, or the DNS name of the first party
to the const char* server
parameter.
void setCurrentParty(ProtocolDesc* pd, int party)
Sets the proper parties into the ProtocolDesc
. Call this before
calling execYaoProtocol()
.
void execYaoProtocol(ProtocolDesc* pd, protocol_run start, void* arg)
Call this function in your C file’s main()
function to execute Yao’s
garbled circuits protocol. Supply the starting function name (without
parentheses) in the Obliv-C file as the parameter for protocol_run start
. Supply any arguments for the Obliv-C starting function in
void* arg
; pass in a struct to supply multiple parameters. Before
calling this function, establish a connection between parties with
protocolAcceptTcp2P()
and protocolConnectTcp2P()
, and call
setCurrentParty()
.
void cleanupProtocol(ProtocolDesc* pd)
Call this function in your C file after execYaoProtocol()
has been
called. This function will clean up the ProtocolDesc
object.
Obliv-C Functions
These functions should be used in Obliv-C (.oc
) files. They require `#include <obliv.oh>'.
Valid substitutions for tname
are bool
, char
, int
, short
,
long
, long long
, or float
. The value of _<Tname>
is the
corresponding type name with its first letter capitalized. (As of
March 2018, the obliv
qualifier is not supported for
double-precision floating point, however, single-precision floating
point support is
available.)
obliv tname feedObliv<Tname>(tname v, int party)
This function returns an obliv
-qualified type from the specified
party to both parties. For example, for float
, there is:
obliv float feedOblivFloat(float v, int party)
Sample code for using this function to feed an int array from the specified party into an obliv int array to both parties:
void toObliv(int n, obliv int *oa, int *a, int party) {
int i, res;
for(i = 0; i < n; i++) {
oa[i] = feedOblivInt(a[i], party);
}
}
name ocBroadcast<Tname>(tname v, int source)
Returns v from the specified source (party). This function allows
non-obliv data to be transferred across parties, similar to
feedObliv<Tname>()
.
bool revealObliv<Tname>(tname* dest, obliv tname src, int party)
Return value true indicated that dest
was written to
successfully. party
indicates which party will receive the revealed
obliv value; use 0
for all parties to receive the revealed value. It
is typical for this function to be called towards the end of a
protocol execution, revealing the output of the function that was
computed security to both parties.