Skip to content

Commit

Permalink
Implemented multiple players and added some stats.
Browse files Browse the repository at this point in the history
  • Loading branch information
goblin committed Jun 24, 2012
1 parent ef92e4b commit b64775c
Show file tree
Hide file tree
Showing 7 changed files with 326 additions and 320 deletions.
8 changes: 3 additions & 5 deletions Makefile
@@ -1,9 +1,7 @@
TMCG=$(shell libTMCG-config --libs --cflags) -Wl,-rpath $(shell libTMCG-config --prefix)/lib

all: master slave
all: client

master: master.cc Makefile
g++ -o master $(TMCG) master.cc
client: client.cc Makefile
g++ -o client $(TMCG) -pthread -lboost_system client.cc

slave: slave.cc Makefile
g++ -o slave $(TMCG) slave.cc
60 changes: 44 additions & 16 deletions README
@@ -1,30 +1,58 @@
This is a simple proof of concept to implement mental poker for two players
(texas hold'em) using libTMCG. IT WILL WORK!
This is a simple proof of concept to implement mental poker (texas hold'em)
for multiple players using libTMCG. IT WILL WORK!

It's not playable in current state, it's just a proof of concept that
checks performance.

It requires libTMCG from http://www.nongnu.org/libtmcg/
It also requires boost::asio to be installed system-wide.

To compile, type make (or gmake) - make sure libTMCG-config is in your path.

To test run, use:
To test run, decide on how many players you want to simulate, let's say
it's <num_players>. Open <num_players> terminal windows. Then SLOWLY
start booting up a client in each terminal window like so:

$ ./client <num_players> <player_id> <base_port>

Where <player_id> is a number from between 0 and <num_players> - you should
increment it by 1 in each terminal window. <base_port> is the base TCP port
that the clients will listen on. Currently only 127.0.0.1 is supported.
Each client will use <base_port> + <player_id> as the port it listens on,
and will connect to all other clients' ports. Again, add new clients slowly
and in order, and wait for all previous clients to display a "waiting for
connection from x" message before starting client x. If you connect them
too fast or out of order, it might blow up.

As you start the last client, they will begin shuffling the deck and
communicating encrypted data, and at the end they will all display their
corresponding cards. The idea is that these cards will be used by a front-end
and there will be appropriate pausing between each game step to allow players
to make bets. Cards are represented as an integer between 0 and 51.

See the file stats.dat for running times. It was done on an AMD Phenom X6
1090 processor running at 3.6GHz per core. The format of this file is:

$ mkfifo fi fo
<number_of_players> <total_runtime_in_secs> <total_transferred_data_in_mb>

then in one terminal run:
Total transferred data is measured by simply running tcpdump for the
corresponding ports and checking the pcap file size.

$ ./master > fi < fo
To visualize the data, use:

and in another run:
$ gnuplot -p stats.gnuplot

$ ./slave < fi > fo
You can see the running time and total data transferred increases slightly
polynomially at a first glance.

On an AMD Phenom 3.6GHz it takes about 3.5 seconds for the whole run
(shuffling and trasmitting the deck and revealing all cards). It transmits
just below 160kB of data each way.
Remember it's not optimized at all, and there's plenty of room for improvement.

If it feels too slow for you, edit both .cc files and change the security
parameter t from 64 to something lower (e.g. 48) - read libTMCG's docs for
more info.
It is also possible to trade off the running time with security. You can
change the security parameter t in client.cc:117 from 64 to something lower.
Read libTMCG's docs for more info.

But it works! We can easily implement 2-player texas hold'em without a central
server or trusted authority!
Conclusion:
It works! We can easily implement 2-player texas hold'em without a central
server or trusted authority! More players are also possible, but there's
always the problem of detecting collusion, and it's a bit slower to shuffle.

267 changes: 267 additions & 0 deletions client.cc
@@ -0,0 +1,267 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <iostream>
#include <vector>
#include <boost/asio.hpp>
#include <boost/format.hpp>
#include <libTMCG.hh>

#define debug(a...) { fprintf(stderr, gettime().c_str()); fprintf(stderr, a); }

using namespace boost::asio;

std::vector<std::istream*> inputs;
std::vector<std::ostream*> outputs;
int num_players;
int player_id;
int base_port;

std::string gettime()
{
return (boost::format("[%u] ") % time(NULL)).str();
}

void get_card(VTMF_Card &card, SchindelhauerTMCG *tmcg,
BarnettSmartVTMF_dlog *vtmf)
{
//debug("getting card\n");
tmcg->TMCG_SelfCardSecret(card, vtmf);

for(int i = 0; i < num_players; ++i) {
if(i == player_id)
continue;

if(!tmcg->TMCG_VerifyCardSecret(card, vtmf, *inputs[i], *outputs[i]))
debug("card verification failed\n");
}
debug(" got card %u\n", tmcg->TMCG_TypeOfCard(card, vtmf));
}

void wait_for(int who)
{
io_service ios;
ip::tcp::endpoint ep(ip::tcp::v4(), base_port + player_id);
ip::tcp::acceptor acceptor(ios, ep);

ip::tcp::iostream *stream = new ip::tcp::iostream;
debug("waiting for connection from %d\n", who);
acceptor.accept(*stream->rdbuf());
debug("got connection from %d\n", who);
std::string hello;
std::getline(*stream, hello);
if(hello != (boost::format("hello %d %d\r") % who %
num_players).str()) {
debug("incorrect hello %s\n", hello.c_str());
exit(1);
}
debug("confirmed connection from %d\n", who);
inputs.push_back(stream);
}

void connect_to(int who)
{
debug("connecting to %d\n", who);
ip::tcp::iostream *stream = new ip::tcp::iostream("127.0.0.1",
(boost::format("%d") % (base_port + who)).str());
if(!*stream) {
debug("couldn't connect, trying again\n");
sleep(1);
connect_to(who);
return;
}
debug("sending hello %d %d\n", player_id, num_players);
*stream << (boost::format("hello %d %d\r\n") % player_id %
num_players).str();
stream->flush();
debug("appears connected\n");
outputs.push_back(stream);
}

void initialize_connections()
{
for(int i=0; i<num_players; ++i)
{
if(i < player_id) {
connect_to(i);
wait_for(i);
} else if(i == player_id) {
// create a dummy stream
debug("creating dummy stream for myself\n");
inputs.push_back(NULL);
outputs.push_back(NULL);
} else {
wait_for(i);
connect_to(i);
}
}
}

int main(int argc, char **argv)
{
if(argc != 4) {
debug("usage: %s <num_players> <player_id> <base_port>\n", argv[0]);
return 1;
}

num_players = atoi(argv[1]);
player_id = atoi(argv[2]);
base_port = atoi(argv[3]);

initialize_connections();

if(!init_libTMCG())
debug("init failed\n");

SchindelhauerTMCG *tmcg = new SchindelhauerTMCG(64, num_players, 6);
BarnettSmartVTMF_dlog *vtmf;

if(player_id == 0) {
vtmf = new BarnettSmartVTMF_dlog();

if(!vtmf->CheckGroup())
debug("checkgroup failed\n");

debug("> publishing group:\n");
for(int i = 1; i < num_players; ++i) {
vtmf->PublishGroup(*outputs[i]);
}
} else {
debug("> waiting for group\n");
vtmf = new BarnettSmartVTMF_dlog(*inputs[0]);

if(!vtmf->CheckGroup())
debug("checkgroup failed\n");
}
debug("< done\n");

vtmf->KeyGenerationProtocol_GenerateKey();

debug("> publishing key\n");
for(int i = 0; i < num_players; ++i) {
if(i != player_id)
vtmf->KeyGenerationProtocol_PublishKey(*outputs[i]);
}
debug("< done\n");

debug("> give me keys\n");
for(int i = 0; i < num_players; i++) {
if(i != player_id) {
debug("> waiting for key from %d\n", i);
if(!vtmf->KeyGenerationProtocol_UpdateKey(*inputs[i]))
debug("wrong key\n");
debug("thanks\n");
}
}
debug("< done\n");

vtmf->KeyGenerationProtocol_Finalize();

// create the deck
TMCG_OpenStack<VTMF_Card> deck;
for (size_t type = 0; type < 52; ++type)
{
VTMF_Card c;
tmcg->TMCG_CreateOpenCard(c, vtmf, type);
deck.push(type, c);
}

// shuffle the deck
TMCG_Stack<VTMF_Card> s;
s.push(deck);

for(int i = 0; i < num_players; ++i)
{
TMCG_Stack<VTMF_Card> s2;
if(i == player_id) {
TMCG_StackSecret<VTMF_CardSecret> ss;
tmcg->TMCG_CreateStackSecret(ss, false, s.size(), vtmf);
tmcg->TMCG_MixStack(s, s2, ss, vtmf);
for(int i2 = 0; i2 < num_players; ++i2) {
if(i2 == player_id)
continue;
debug("> sending stack\n");
*outputs[i2] << s2 << std::endl;
debug("< done\n");
debug("> proving stack\n");
tmcg->TMCG_ProveStackEquality(s, s2, ss, false, vtmf,
*inputs[i2], *outputs[i2]);
debug("< done\n");
}
} else {
debug("> give me stack\n");
*inputs[i] >> s2;
debug("thanks\n");
if(!inputs[i]->good())
debug("read or parse error\n");
debug("> verifying stack\n");
if (!tmcg->TMCG_VerifyStackEquality(s, s2, false, vtmf,
*inputs[i], *outputs[i]))
debug("bad stack\n");
debug("done\n");
}
s = s2;
}

// get cards
TMCG_Stack<VTMF_Card> cards;
VTMF_Card c;

// push 2 cards for each player and then 5 for the board
debug("pushing cards\n");
for(int i = 0; i < 2 * num_players + 5; ++i) {
s.pop(c);
cards.push(c);
}

debug("giving hands\n");
for(int i = 0; i < num_players; ++i) {
if(i == player_id) {
debug(" getting my hand\n");
get_card(cards[i*2], tmcg, vtmf);
get_card(cards[i*2 + 1], tmcg, vtmf);
} else {
tmcg->TMCG_ProveCardSecret(cards[i*2], vtmf, *inputs[i],
*outputs[i]);
tmcg->TMCG_ProveCardSecret(cards[i*2+1], vtmf, *inputs[i],
*outputs[i]);
}
}

debug("giving/getting board\n");
for(int i = 0; i < num_players; ++i) {
for(int j = 0; j < 5; ++j) {
int card_id = 2 * num_players + j;
if(i == player_id) {
get_card(cards[card_id], tmcg, vtmf);
} else {
tmcg->TMCG_ProveCardSecret(cards[card_id], vtmf,
*inputs[i], *outputs[i]);
}
}
}

debug("revealing all hands\n");
for(int i = 0; i < num_players; ++i) {
for(int j = 0; j < num_players; ++j) {
if(i == player_id) {
if(j == player_id)
continue;
debug(" getting player %d's hand\n", j);
get_card(cards[j*2], tmcg, vtmf);
get_card(cards[j*2 + 1], tmcg, vtmf);
} else {
if(j == i)
continue;
debug(" revealing player %d's hand to player %d\n", j, i);
tmcg->TMCG_ProveCardSecret(cards[j*2], vtmf, *inputs[i],
*outputs[i]);
tmcg->TMCG_ProveCardSecret(cards[j*2+1], vtmf, *inputs[i],
*outputs[i]);
}
}
}

return 0;
}

0 comments on commit b64775c

Please sign in to comment.