Auction demo - a tutorial on using the Sharemind MPC platform

This time our blog post features a small tutorial on using the Sharemind MPC platform.

Storyline

I chose a very simple problem that has 3 stakeholders: Alice, Bob and Charlie (sorry for being so unimaginative with the names, but they are familiar to cryptographers).

The story goes that Charlie wants to sell his house on the beach. Potential buyers are Alice and Bob, both real estate speculators who compete with each other. Charlie is interested in the highest bidder and wants to hold an auction. Alice and Bob both want to make a bid, but since they are competing in the same district, they do not want to reveal their bids. So Charlie proposes to hold a closed auction.

So far Sharemind is not yet needed: Alice and Bob can both follow a simple procedure: write their bid on a piece of paper, put it into an envelope, seal the envelope and give it to Charlie. Charlie would open the envelopes and determine the winner. Charlie promises not to reveal the losing bid. Not every problem needs to be solved with Sharemind.

However, Alice is not happy with that plan. She is afraid that if Bob wins the auction and is going to celebrate the house deal with Charlie, it would be very easy for Bob to persuade Charlie to give him information about the amount Alice was willing to pay. That information would give Bob an edge over Alice in future deals. In short, Alice does not trust Charlie to keep the losing bid to himself.

Now they have a problem that is suitable for solving with Sharemind and we will help them. We need an application that allows Alice and Bob to enter their bids into the Sharemind system and another application for Charlie to determine the outcome of the auction (the winner and the value of the winning bid).

System architecture

I am not going to give you an overview of Sharemind MPC in general, but rather explain the setup for solving this particular problem. Each stakeholder will host a Sharemind Application Server, and in addition, each will host a Redis database.

The base for the code comes from our C++ client application demo, however the latter is not very descriptive as there is only one party that inputs data and also gets the results. In order to use multiple inputs from separate stakeholders, we will need to use some kind of data storage inside the Sharemind MPC system. At the time of writing this, there are two options for persistent data storage in the Sharemind Application Server:

  1. mod_tabledb — a table-based storage format based on HDF5.
  2. mod_keydb — an adapter to Redis key-value database.

I chose to keep the code simple and used mod_keydb, although it requires each stakeholder to also host a Redis server.

The data model is very simple, there are two variables named "a" and "b" in the key-value database. The first holds the bid for Alice and the second, "b", holds the amount for Bob.

Sharemind MPC setup

Note that if you are not following the tutorial step-by-step and are only interested in the general use case, you should skip this section and jump directly to the next section.

I am not going to go over the whole Sharemind MPC configuration, because this should be covered in the manuals shipped with the platform.

However, I will cover the following:

Key generation

Sharemind MPC uses TLS for securing the communications. We use public-key cryptography for TLS and for general authentication. There are many options to generate key pairs, two of them are listed below.

With OpenSSL

openssl req -x509 -days 3650 -nodes -newkey rsa:4096 -keyout "my-private-key" -out "my-public-key" -outform deropenssl rsa -in "my-private-key" -out "my-private-key" -outform der

With GnuTLS certtool

./certtool --generate-privkey --rsa --outfile my-private-key --outder./certtool --generate-self-signed --load-privkey my-private-key --inder --outfile my-public-key --outder

Keydb module configuration

In order to use the keydb database module, one needs to configure the Sharemind Application Server or Emulator to load the database module. One should add the following under the modules configuration:

[Module keydb]File = libsharemind_mod_keydb.soConfiguration = %{CurrentFileDirectory}/keydb.conf

The configuration string for the module points to the file where keydb module specific configuration is stored. The configuration file for the keydb may contain multiple sections, where each section describes one Redis host. When using the keydb module from a SecreC application, one has to first connect to one database. An example of the configuration with one Redis host follows:

; This section defines a new host.; Because duplicate section names are not allowed you can use any other string; after the initial Host part.[Host host]; The name to access this host from the SecreC application.Name = dbhost; The hostname were the Redis database is listening.Hostname =localhost; The port number where Redis database is listening.; This is optional, when not present it defaults to 6379 which is the default; port number for Redis.Port =6379; Internal parameter, sets the number of items to load per request when; streaming keys in keydb_scan and keydb_clean.; This is optional, it defaults to 25.ScanCount =25; Whether to disable overwrites or not. Disabling overwrites ensures that there; are no inconsistencies from overwrites that succeed only partially.; This is optional, it defaults to falseDisableOverwrite =false

When using the Emulator, one also needs to load the datastore manager facility. This can be done by adding the following to your Sharemind Emulator configuration file:

[FacilityModule datastoremanager]File = libsharemind_facility_datastoremanager.so#Configuration =

Access control

Sharemind MPC has granular access control. The access control policy can be configured based on users, programs and database resources.

First, we declare 3 users by their public keys.

[User Alice]TlsPublicKeyFile = %{CurrentFileDirectory}/alice-public-key[User Bob]TlsPublicKeyFile = %{CurrentFileDirectory}/bob-public-key[User Charlie]TlsPublicKeyFile = %{CurrentFileDirectory}/charlie-public-key

Next, we allow them to execute specific SecreC bytecode programs.

[Ruleset sharemind:server]execute:alice_bid.sb = Aliceexecute:bob_bid.sb = Bobexecute:charlie_result.sb = Charlie

Finally, we allow access to some key-value pairs in the keydb database.

[Ruleset sharemind:keydb]a:write:* = Aliceb:write:* = Boba:read:* = Charlieb:read:* = Charlie

Note that the last star in the keydb rules means that Alice can write to "a" with every program Alice is allowed to run. To be more specific, one can also write out the program name.

a:write:alice_bid.sb = Alice

Entering bids into Sharemind

Server side

We start with the SecreC code to enter the bid for Alice.

import shared3p;import keydb;import shared3p_keydb;domain pd_shared3p shared3p;void main() { keydb_connect("dbhost"); pd_shared3p uint b = argument("bid"); keydb_set("a", b); keydb_disconnect();}

At the start, we have some imports, which I will explain:

  • shared3p — the definition of the shared3p protection domain kind.
  • keydb — for the keydb functions using public data (in our case keydb_connect(string) and keydb_close()).
  • shared3p_keydb — for the keydb functions specialized for shared3p protection domain kind (keydb_set).

Then we have the definition of the security domain pd_shared3p of kind shared3p. In general, you can have multiple security domains with different kinds, but for now you can mostly ignore that, because we use only one protection domain kind: shared3p.

By the way, shared3p means that data is secret shared between 3 parties and the cryptographic protocols are secure in the passive (honest-but-curious) security model.

The main entry point of the program is called main just like in C, however the arguments to the SecreC program are not given as parameters of main, but they can be obtained using the argument(string) function.

First we establish connection to the Redis server by using the keydb_connect(string) function. Note that the name dbhost must match the name in the mod_keydb configuration file.

Next we declare a private variable b of the security domain pd_shared3p and type uint that is short for uint64, which is a 64-bit unsigned integer. In the same line we assign the variable with the argument named "bid" that is provided by the runner of the SecreC program.

Then we store the private data to the Redis database under the key "a", this means we store it as Alice's bid.

Lastly we close the connection to Redis, but this is actually not needed in this specific example, because the program is finished and the resources get freed anyway.

For Bob the SecreC program is almost identical, the only difference is:

-     keydb_set("a", b);+     keydb_set("b", b);

Instead of storing the bid to the database with key "a" we store the bid to "b", which corresponds to Bob's bid.

Client side

The corresponding C++ client application starts with argument parsing using Boost's program_options. After that we load the Sharemind Controller configuration with the following lines:

if (vm.count("conf")) { config = std::make_unique<sm::SystemControllerConfiguration>( vm["conf"].as<std::string>());} else { config = std::make_unique<sm::SystemControllerConfiguration>();}

If we have a "conf" option specified we give that to the constructor to load the configuration from that file, otherwise the client configuration is searched from the default locations (in this order):

  • ~/.config/sharemind/client.conf
  • /etc/xdg/sharemind/client.conf
  • /etc/sharemind/client.conf

Next we need to set up an LogHard::Logger instance:

auto logBackend(std::make_shared<LogHard::Backend>());logBackend->addAppender(std::make_shared<LogHard::StdAppender>());logBackend->addAppender(std::make_shared<LogHard::FileAppender>("auction-bid.log", LogHard::FileAppender::OVERWRITE));const LogHard::Logger logger(logBackend);

After that is done, we can create an instance of the SystemController and then create an SystemController::ValueMap instance for the arguments. We add an argument called "bid", that the SecreC program expects. Note that the SystemController::Value constructor also needs the protection domain and type of the argument. The third argument to Value constructor is of type std::shared_ptr<void> and the data is used directly as a pointer. Therefore we also need to pass in the size of the data. For passing in arrays of data, we would point to the start of the array and give the size as sizeof(sm::Uint64) * lengthOfArray.

Note: if the conversion from std::shared_ptr<sm::Uint64> to std::shared_ptr<void> is confusing, you should read this explanation.

sm::SystemControllerGlobals systemControllerGlobals;sm::SystemController c(logger, *config);// Initialize the argument map and set the argumentssm::SystemController::ValueMap arguments;arguments["bid"] =std::make_shared<sm::SystemController::Value>("pd_shared3p","uint64",std::make_shared<std::uint64_t>(bid),sizeof(std::uint64_t));

After we are finished with the arguments, we just run the right bytecode program with the arguments and as a result we will get back the same kind of SystemController::ValueMap that we used for the arguments:

sm::SystemController::ValueMap results = c.runCode(bytecode[bidder], arguments);

Note that bytecode[bidder] evaluates to "alice_bid.sb" or "bob_bid.sb" depending on the command line arguments.

Getting the results out of Sharemind

Server side

The SecreC code for getting the results is straightforward:

import shared3p;import keydb;import shared3p_keydb;import oblivious;domain pd_shared3p shared3p;void main() { keydb_connect("dbhost"); pd_shared3p uint a = keydb_get("a"); pd_shared3p uint b = keydb_get("b"); pd_shared3p bool aliceWon = a > b; pd_shared3p uint winningBid = choose(aliceWon, a, b); publish("aliceWon", aliceWon); publish("winningBid", winningBid); keydb_disconnect();}

We get Alice's bid "a" and Bob's bid "b" from the database and we compare them. The choose function is a private variant of the C's ternary operator (<cond> ? <true_expr> : <false_expr>). Note that in this case we cannot use the typical if/else because that would need a public boolean to branch on. The choose function is defined in the oblivious module.

For outputting something from a SecreC program, one has to use the publish function, which sends the secret shared output back to the client application.

Client side

The C++ code for getting the result is mostly the same as for entering the bid, just that now we give no arguments to the SecreC program and we will get back the published results.

sm::SystemControllerGlobals systemControllerGlobals;sm::SystemController c(logger, *config);// Initialize the argument map and set the argumentssm::SystemController::ValueMap arguments;// Run codelogger.info() << "Running SecreC bytecode on the servers.";sm::SystemController::ValueMap results = c.runCode("charlie_result.sb", arguments);// Print the resultbool aliceWon = results["aliceWon"]->getValue<std::uint8_t>();auto winningBid = results["winningBid"]->getValue<std::uint64_t>();logger.info() << "The winner is " << (aliceWon ? "Alice" : "Bob") << ". With a bid of " << winningBid << ".";

Demonstration

AliceBobCharlie

$ ./auction-bid -a --bid 10002018.04.26 17:34:50 INFO    [Controller] Loaded controller module 'shared3p' (15 data types, 1 PDKs) from'libsharemind_mod_shared3p_ctrl.so' using API version 1.2018.04.26 17:34:50 INFO    [Controller] Started protection domain 'pd_shared3p' of kind 'shared3p'.2018.04.26 17:34:50 INFO    Sending secret shared arguments and running SecreC bytecode on the servers2018.04.26 17:34:50 INFO    [Controller][Server DebugMiner1] To 127.0.0.1:30001: Connecting...2018.04.26 17:34:50 INFO    [Controller][Server DebugMiner2] To 127.0.0.1:30002: Connecting...2018.04.26 17:34:50 INFO    [Controller][Server DebugMiner1] To 127.0.0.1:30001: Connected. Authenticating...2018.04.26 17:34:50 INFO    [Controller][Server DebugMiner2] To 127.0.0.1:30002: Connected. Authenticating...2018.04.26 17:34:50 INFO    [Controller][Server DebugMiner3] To 127.0.0.1:30003: Connecting...2018.04.26 17:34:50 INFO    [Controller][Server DebugMiner3] To 127.0.0.1:30003: Connected. Authenticating...2018.04.26 17:34:50 INFO    [Controller][Server DebugMiner1] To 127.0.0.1:30001: Authenticated.2018.04.26 17:34:50 INFO    [Controller][Server DebugMiner3] To 127.0.0.1:30003: Authenticated.2018.04.26 17:34:50 INFO    [Controller][Server DebugMiner2] To 127.0.0.1:30002: Authenticated.2018.04.26 17:34:50 INFO    [Controller] Stopping the network...
$ ./auction-bid -b --bid 11002018.04.26 17:35:52 INFO    [Controller] Loaded controller module 'shared3p' (15 data types, 1 PDKs) from'libsharemind_mod_shared3p_ctrl.so' using API version 1.2018.04.26 17:35:52 INFO    [Controller] Started protection domain 'pd_shared3p' of kind 'shared3p'.2018.04.26 17:35:52 INFO    Sending secret shared arguments and running SecreC bytecode on the servers2018.04.26 17:35:52 INFO    [Controller][Server DebugMiner1] To 127.0.0.1:30001: Connecting...2018.04.26 17:35:52 INFO    [Controller][Server DebugMiner3] To 127.0.0.1:30003: Connecting...2018.04.26 17:35:52 INFO    [Controller][Server DebugMiner2] To 127.0.0.1:30002: Connecting...2018.04.26 17:35:52 INFO    [Controller][Server DebugMiner3] To 127.0.0.1:30003: Connected. Authenticating...2018.04.26 17:35:52 INFO    [Controller][Server DebugMiner2] To 127.0.0.1:30002: Connected. Authenticating...2018.04.26 17:35:52 INFO    [Controller][Server DebugMiner1] To 127.0.0.1:30001: Connected. Authenticating...2018.04.26 17:35:52 INFO    [Controller][Server DebugMiner3] To 127.0.0.1:30003: Authenticated.2018.04.26 17:35:52 INFO    [Controller][Server DebugMiner1] To 127.0.0.1:30001: Authenticated.2018.04.26 17:35:52 INFO    [Controller][Server DebugMiner2] To 127.0.0.1:30002: Authenticated.2018.04.26 17:35:52 INFO    [Controller] Stopping the network...
$ ./auction-result2018.04.26 17:36:20 INFO    [Controller] Loaded controller module 'shared3p' (15 data types, 1 PDKs) from'libsharemind_mod_shared3p_ctrl.so' using API version 1.2018.04.26 17:36:20 INFO    [Controller] Started protection domain 'pd_shared3p' of kind 'shared3p'.2018.04.26 17:36:20 INFO    Running SecreC bytecode on the servers.2018.04.26 17:36:20 INFO    [Controller][Server DebugMiner3] To 127.0.0.1:30003: Connecting...2018.04.26 17:36:20 INFO    [Controller][Server DebugMiner1] To 127.0.0.1:30001: Connecting...2018.04.26 17:36:20 INFO    [Controller][Server DebugMiner2] To 127.0.0.1:30002: Connecting...2018.04.26 17:36:20 INFO    [Controller][Server DebugMiner1] To 127.0.0.1:30001: Connected. Authenticating...2018.04.26 17:36:20 INFO    [Controller][Server DebugMiner3] To 127.0.0.1:30003: Connected. Authenticating...2018.04.26 17:36:20 INFO    [Controller][Server DebugMiner2] To 127.0.0.1:30002: Connected. Authenticating...2018.04.26 17:36:20 INFO    [Controller][Server DebugMiner1] To 127.0.0.1:30001: Authenticated.2018.04.26 17:36:20 INFO    [Controller][Server DebugMiner3] To 127.0.0.1:30003: Authenticated.2018.04.26 17:36:20 INFO    [Controller][Server DebugMiner2] To 127.0.0.1:30002: Authenticated.2018.04.26 17:36:20 INFO    The winner is Bob. With a bid of 1100.2018.04.26 17:36:20 INFO    [Controller] Stopping the network...

Running the examples with the Sharemind Emulator

For running the SecreC programs with the Sharemind Emulator, one needs to know the protocol for giving arguments and receiving the results. The argument stream protocol is described in a man page.

AliceBobCharlie

$ sharemind-emulator alice_bid.sb --str=bid --str=pd_shared3p --str=uint64 --size=8 --uint64=1000
$ sharemind-emulator bob_bid.sb --str=bid --str=pd_shared3p --str=uint64 --size=8 --uint64=1100
$ sharemind-emulator charlie_result.sb |./argument-stream-decipher.pyProcess returned status: 0aliceWon = [False]winningBid = [1100]

Here we piped the emulator output through argument-stream-decipher.py to make the output human readable. This file is available as part of the Sharemind Emulator distribution.

Conclusion

Charlie sold his house to Bob and neither Charlie nor Bob learned the amount Alice was willing to pay.

I have hopefully shown that it is not that hard to write privacy preserving application with the Sharemind MPC platform. The code itself is quite simple and the hardest part is organizational. All parties must coordinate to configure the servers, generate keys for the clients and the servers and then exchange the public keys. However, we are building tools to simplify that coordination.