-
Notifications
You must be signed in to change notification settings - Fork 14
FaCT Language Reference
This document is a detailed reference document for the FaCT language. If you’re just looking for some example code patterns, head to the Useful FaCT Patterns page.
FaCT is a domain-specific language with a C-like syntax. Programs are written in one or more *.fact files, and compiled into a single C object (*.o) file.
A FaCT file consists of a sequence of struct
definitions, C extern
declarations, and FaCT function definitions. Since FaCT code compiles to an object file, and not an executable, no main
function is necessary.
FaCT supports the following basic types:
-
bool
-
uint8
,uint16
,uint32
,uint64
,uint128
-
int8
,int16
,int32
,int64
Just as in many other languages, the FaCT compiler tracks the types of expressions and enforces type safety.
An expression of type uintN
can be silently upcast to an expression of type uintM
, where M > N
.
Similarly, an expression of type intN
can be silently upcast to an expression of type intM
where M > N
.
Integer types and bool
types cannot be cast to each other, implicitly or otherwise.
FaCT supports arrays of any basic type with a fixed known size, e.g. int32[4]
for an array of four int32
values.
If the length of the array is not known at compile time, empty brackets may be used, e.g. uint8[]
for an array of unsigned bytes with length determined at runtime.
Unlike pointers (e.g. in C), arrays cannot be reassigned — an array variable will always point to the same array.
If a struct
has been previously defined with name <name>
, its corresponding type can be referred to as struct <name>
.
FaCT has two labels: public
and secret
. All variables are required to have a label. The label of a variable is given at the variable’s declaration and cannot change.
The FaCT compiler keeps track of the labels of expressions, just as it keeps track of the types of expressions. The label of any expression derived from a secret expression is itself secret. Otherwise, if the expression is composed entirely of public expressions, then its label is public.
All variables in FaCT are either immutable (const
), or mutable (mut
). If not specified, a variable is const
by default. Variables that are const
may not be reassigned. Similarly, array variables that are const
may not have their elements be reassigned through that array variable, and structs that are const
may not have their fields updated through that struct variable.
FaCT supports the following basic expressions:
These are the boolean literals true
and false
, as well as integer literals. Literals are always considered public
expressions.
Hexadecimal and binary integer literals can be expressed using the prefixes 0x
and 0b
respectively, e.g. 0xfd3a
or 0b1001101
.
The label of a variable expression is the declared label of the variable being referenced.
E.g. array_var[7]
or buf[i - 1]
. The indexing expression must have label public
. The label of the resulting expression is the declared label of the array.
E.g. struct_var.c
or state.ctx[8].num_buffers
. The label of the resulting expression is the declared label of the field in the struct’s definition.
E.g. len array_var
. The len
keyword followed by an array variable gives the length of the array. If the size is known at compile time, this is treated just like an integer literal. If the size isn’t known until runtime, this is the runtime length of the array. This value is always considered to be public
.
E.g. (uint8)var_of_type_uint16
or (int32)(byte_buf[2] + 5)
. Any integer type may be explicitly cast to any other integer type. If the new type is smaller than the old type, the expression is truncated. If the new type is larger than the old type, the expression is either sign- or zero-extended, depending on whether the expression being cast is an int type or uint type, respectively. Truncation always happens, even if an expression is immediately cast back to its original size.
The label of a casted expression is the label of the original expression.
-
Integer negation (e.g.
-x
or-(len arr_var + 3)
) -
Logical not (e.g.
!cond
) -
Bitwise not (e.g.
~expr
)
The type and label of a unary expression is the type and label of the original expression.
-
Arithmetic (
+
,-
,*
,/
,%
) -
Comparison (
==
,!=
,<
,⇐
,>
,>=
) -
Boolean arithmetic (
&&
,||
) -
Bitwise arithmetic (
&
,|
,^
) -
Bit manipulation (
<<
,>>
,<<<
,>>>
)
If either operand of a binary expression is secret
, the resulting expression is secret
. Otherwise, the expression is public
.
The division (/
) and modulo (%
) operators can only operate on public
expressions.
If both operands of an arithmetic operation share the same integer type, the resulting expression will also have that same type. Note that this means arithmetic operations will silently wrap around. If the operands do not have the same type, but one operand can be implicitly upcast to the other, the upcast will take place before the operation.
The operands for the equality operators (==
, !=
) must either both be bool
or both be compatible integer types.
The type of a bitwise-and (&
) operation is the type of the smaller-typed operand, for convenience. E.g. the type of uint32_variable & uint8_variable
is uint8
.
The type of bit-shifting (<<
, >>
) and bit-rotating (<<<
, >>>
) operations are the type of the left operand.
E.g. cond ? expr_if_true : expr_if_false
. This is the same as the ternary operator from C.
If any of the three operands are secret
, the resulting expression is secret
. Otherwise, the expression is public
.
If the first operand (the condition) is secret
, then the expression will be transformed during compilation such that the selection is done in constant time. This ensures that the value of the condition is not leaked via timing.
The first operand must be a boolean, and the second and third operands must have compatible types. The type of the ternary expression is the type of the second/third operands (after implicit upcasting in the case of different, but compatible, types).
E.g. some_fn(arg1, arg2, arg3)
.
The resulting type and label are the type and label specified by the function’s definition/declaration.
If a function expects a mut
variable or array, the corresponding argument must be passed with the ref
keyword. E.g. foobar(ref x)
. The argument must be mutable itself — it is illegal to pass a const
variable with ref
.
For convenience, array views (see "Array declaration" section below) can be passed directly as arguments.
E.g. takes_4byte_array(arrview(buf, 0, 4))
or mutates_buffer(ref arrview(buf, 2, 26))
.
E.g. declassify(some_secret_expr)
.
A secret
expression can be explicitly declassified to become public
. This is not recommended.
Only do this if absolutely necessary.
For the sake of future clarity, both for yourself and for developers who will be reading your code later, we highly recommended that you leave detailed comments about why this declassify
is necessary and an explanation of why the resulting code is still secure.
If the code is indeed secure, and can be proven statically, consider submitting an issue (or pull request) to the FaCT compiler so that it can prove this property itself and obviate the need for the declassify
call.
E.g. public uint8 x = 8;
or secret mut int32 y = x - 6;
Must of the form LABEL [mut] TYPE NAME = EXPR;
. If mut
is not given, the variable is assumed to be const
.
An initializing expression is mandatory; uninitialized variables are not allowed.
E.g. public uint8[16] buf = arrzeros(16);
or secret mut uint32[] copied = arrcopy(other_array);
.
Similar to a basic variable declaration, but with an array initializer instead of a normal expression.
Valid array initializers are:
-
An array literal, e.g.
{ 2, 4, 3, 1 }
-
A variable (must be the same array type and length)
-
arrzeros(length)
, which results in a fully zeroed-out array -
arrcopy(array)
, which copies the specified array -
arrview(array, index, length)
, which creates a "view" into the specified array starting atindex
-
noinit(length)
, which does not initialize the array (only use if you will be initializing the array later in the function!)
In the above initializers, length
and index
must be public
expressions.
The arrview
initializer does not create a new array; if an element of the view is modified, the corresponding element of the original array is also modified. Views can be used to create subarrays of larger arrays, or for creating a const
view of a mut
array.
E.g. x = 5;
or buf[i] = x + 3;
Assignment works as with other C-like languages, provided the types are compatible.
It is illegal to assign a secret
expression into a public
variable, and it is illegal to assign anything into a public
variable when in a secret control flow context (see Conditionals below).
The "operator-equals" form of binary operators (x += expr
, etc.) is allowed, and is syntactic sugar for x = x + expr
etc.
E.g. if (x > 5) { … }
Just as in C, multiple conditionals can be chained using else if
and else
blocks.
If the expression is public
, the conditional works just like a C-style if
block.
If the expression is secret
, the conditional block is evaluated under a secret control flow context: all statements with side effects (such as assignment) are transformed into semantically equivalent constant-time form. Any information that would indicate whether or not the branch was taken is prevented from being leaked.
E.g. for (uint32 i = 0; i < nblocks + 2; i += 1) { … }
The first section of the for
loop must be a numeric variable declaration. The label is implicitly public
. The second section can be any public
boolean expression. The final section must be an assignment into the loop variable of a public
expression.
E.g. return x + 5;
The type and label of the returned expression must match the return type of the surrounding function.
If a return
statement is within a secret control flow context, the statement is transformed so that the value is stored in a variable and returned at the end of the function, so that the function’s timing does not end immediately. The rest of the function is evaluated in a secret control flow context. Be aware!
E.g. export secret int32 foo(secret uint8[32] buffer, public mut int32 outval) { … }
A FaCT function definition is similar to a C definition, but with additional annotations.
The return value (if not void
) must have a label. Each parameter must also have a label, and must be marked mut
if it will be an output parameter.
A function will not be exposed to the generated C API unless it is marked export
.
The C interface works as follows:
-
FaCT arrays correspond to C pointers
-
Arrays with unknown static length correspond to two parameters: a pointer and a length
-
-
FaCT simple types marked
mut
correspond to C pointers -
FaCT simple types not marked
mut
correspond to C simple types from stdint.h-
Note: the FaCT
bool
type corresponds to the Cuint8_t
type
-
If a function has the inline
attribute, then the FaCT compiler will force inlining at every call site. This is most useful for small helper utilities, and is a replacement for macros in C. A function cannot be both inline
and export
.
If a function has no attribute, it will not be exposed to the generated C API.
If a function has any mut
parameters (and thus has side effects), it can only be called from a secret context (e.g. within a secret branch) if it is not exported.
E.g. extern secret int32 foo(secret uint8[32] buffer, public mut int32 outval);
An extern
declaration allows FaCT functions to call the declared function as if it were defined with the stated signature.
The function is assumed to be defined elsewhere (usually in a C file elsewhere in the project).
E.g.
struct sample_type {
secret int32 field1;
public uint8 field2;
public uint8[3] spacing;
secret uint8 array_field[32];
}
void foo(struct sample_type x, mut struct sample_type y) { ... }
Struct definitions are intended for FaCT to be able to effectively interface with external code that depends on specific structure layouts. A struct does not have a label of its own; each instance of a struct has fields with the labels specified in the struct’s definition.
Structs in FaCT are always considered packed. If you need unpacked structs, you must manually add additional fields to fill out the proper spacing.