Lowest-Level Interface

In the following, we will introduce the most protocol-independent interfaces by walking through Utils/protocol-tutorial. It implements the Rep3 multiplication protocol independently of the usual protocol interface for illustration purposes.

// set up networking on localhost
int my_number = atoi(argv[1]);
int port_base = 9999;
Names N(my_number, 3, "localhost", port_base);
CryptoPlayer P(N);

This sets up pairwise encrypted connections as in the low-level example.

// correlated randomness for resharing
SeededPRNG G[2];

The protocol requires every pair of parties to have a common PRNG, so we need two instances. We use SeededPRNG to make sure to never use an uninitialized one.

// synchronize with other parties
octetStream os;
os.append(G[0].get_seed(), SEED_SIZE);

octetStream is generally used to serialize and aggregate network communication. In this case, we use it to store the seed of one of the PRNGs.

P.pass_around(os, os, 1);

Player::pass_around() allows simultaneous sending to the “next” party and receiving from the “previous” party. We use this with the buffer holding the seed. As we don’t need the send buffer afterwards, we can use the same buffer for receiving.

G[1].SetSeed(os.consume(SEED_SIZE));

We seed the second PRNG using the received data. PRNG::SetSeed() implicitly uses the required number of bits.

// simplify code
typedef Z2<64> Z;

In this example, we use integers modulo \(2^{64}\), but the protocol also works for any modulus, so we could also use gfp_.

// start with same shares on all parties for simplicity
// replicated secret sharing of 3
Z a[2] = {1, 1};
// and 6
Z b[2] = {2, 2};

For every secret number in Rep3, every party holds a pair of numbers in the domain such that every pair of parties has the same number. The sum of the unique numbers is the secret.

// compute an additive sharing of the product
Z cc = a[0] * (b[0] + b[1]) + a[1] * b[0];

In a first step, every party computes an additive share of the product. See Araki et al. for details. All domain classes support the standard operators.

// result shares
Z c[2];

// re-randomize
c[0] = cc + G[0].get<Z>() - G[1].get<Z>();

Sending the computed additive secret sharing directly to another party to get back to a replicative secret sharing would be insecure. Therefore, we randomize it using random numbers from the two PRNGs.

// send and receive share
os.reset_write_head();
c[0].pack(os);
P.pass_around(os, os, 1);
c[1].unpack(os);

We clear the buffer, serialize our share, send it to the “next” party, and receive one from the “previous” party. This concludes the multiplication protocol. Z2::pack() and Z2::unpack() are main methods for (de-)serialization. All domain classes support this. You can use octetStream::output() to write the buffer to a C++ output stream.

// open value to party 0
if (P.my_num() == 1)
{
    os.reset_write_head();
    c[0].pack(os);
    P.send_to(0, os);
}

To allow party 0 to output the result, party 1 serializes one of their shares and sends it to party 0.

// output result on party 0, which should be 18
if (P.my_num() == 0)
{
    P.receive_player(1, os);
    cout << "My shares: " << c[0] << ", " << c[1] << endl;
    cout << "Result: " << (os.get<Z>() + c[0] + c[1]) << endl;
}

Party 0 receives the missing share from party 1 and reconstructs the secret by summing up.

You can run the example as follows in the main directory:

make protocol-tutorial.x
for i in 0 1 2; do ./protocol-tutorial.x $i & true; done