Implementation Notes

Random Number Generation

Some SDK APIs require a random number data source. A BitSupplier provides a source of random data. This function should be a cryptographically secure random number generator.

Warning
The pseudo-random number generator provided with the examples is for sample use only and is not suitable for cryptographically secure applications.

Protecting Secrets

Member code works with private keys, so member code must be run in a trusted environment.

Warning
If member memory is available to an untrusted source, the member private key could be exposed.

The EpidZeroMemory function is used by the memory allocation routines EpidAlloc, EpidRealloc and EpidFree to wipe the memory as memory is freed.

Warning
The EpidZeroMemory function may be optimized away by some compilers. If it is, you should consider using a compiler or operating system specific memory sanitization function (e.g. memcpy_s or SecureZeroMemory).

Replacing Math Primitives

SDK math primitives are designed to be replaced with your own implementation if you need to rely on custom hardware for performance. The SDK is designed to simplify this process by isolating implementation details behind a clearly defined interface, defined by the non-internal headers in the epid/common/math directory. Math functionality has detailed tests to ease validation.

Octstring/Buffer Types

Serialized information in the SDK is passed in fixed size buffer types whenever possible. Collectively these fixed size buffer types are called Octstrings.

In epid/common/types.h, there are a large number of packed structs that contain other packed structs, which eventually contain OctStr* types. Normally these are named *Str and are refered to as Str types.

OctStr* types are buffers that hold N bits, where N is the number at the end of the type name. These types usually represent numbers in a Big Endian format (buffer[0] is the most significant value).

Str types generally represent fixed size groups of numbers such as a point or vector.

OctStr* and Str types are usually populated by reading a buffer from a file or other storage, or by calling a serialize function. OctStr* and Str types must be packed so that the compiler does not insert padding. In the current code, this is done using pragmas.

#pragma pack(1)
// OctStr* type holding 256 bits
typedef struct OctStr256 {
unsigned char data[256 / CHAR_BIT];
// Str type consisting of a single 256 bit number
typedef struct FqElemStr {
// Str type consisting of a two other Str types
typedef struct G1ElemStr {

Many APIs use void* parameters where OctStr* types are expected. If more than one size is allowed, a size parameter is usually also required.

Flexible Arrays

A common idiom in the SDK is the use of flexible array types. These types are structs with the last element being an array of size 1 of some type. Flexible array types always have a size value embedded in the struct. The name of the count and array fields differs between flexible array types.

typedef struct Sample {
OctStr32 data;
OctStr32 count;
G1ElemStr array[1];
} Sample;

Flexible array types are expected to be in a buffer of size sizeof(FA) + ((N-1) * sizeof(E)) where FA is the flexible array type, N is the number of elements in the array and E is the type of each element. Note that this may be smaller than sizeof(FA) if N is 0, in which case referencing any element is an error.

Sample* AllocSample(size_t count) {
return (Sample*)malloc(sizeof(Sample)
+ (count * sizeof(G1ElemStr))
- sizeof(G1ElemStr));
}

In many cases, functions that accept flexible array types will also expect a buffer size that is compared against the computed size of the array as a sanity check.