Skip to content

Instantly share code, notes, and snippets.

@NicMcPhee
Last active December 15, 2015 12:38
Show Gist options
  • Save NicMcPhee/5261143 to your computer and use it in GitHub Desktop.
Save NicMcPhee/5261143 to your computer and use it in GitHub Desktop.
An example in Erlang of a crytographically fair distributed coin toss. This example also includes totally unnecessary calls to an inefficient recursive definition of the fibonacci function to generate some non-trivial computation in each process so we can better see the parallelism in the system monitors. This is almost identical to the previous…
%% @author mcphee
%% @doc An example in Erlang of a crytographically fair
%% distributed coin toss.
-module(dist_coin_toss).
%% ====================================================================
%% API functions
%% ====================================================================
-export([run/3, pair/2, pair_all/1, create_people/3, make_person/3, counter_loop/2, wait/1, wait_for_choice/5, wait_for_outcome/4]).
run(First_host, Second_host, Num_people) ->
global:register_name(counter, spawn(dist_coin_toss, counter_loop, [0, 0])),
Peeps = create_people(First_host, Second_host, Num_people),
pair_all(Peeps).
create_people(First_host, Second_host, Num_people) ->
lists:map(fun(N) -> make_person(First_host, Second_host, N) end, lists:seq(1, Num_people)).
make_person(First_host, Second_host, N) ->
Host = case crypto:rand_uniform(0, 2) of
0 -> First_host;
1 -> Second_host
end,
spawn(Host, dist_coin_toss, wait, [N]) .
pair(First_pid, Second_pid) ->
First_pid ! {start, Second_pid}.
pair_all([]) -> true;
pair_all([A, B | Rest]) ->
pair(A, B),
pair_all(Rest).
counter_loop(Heads_wins, Tails_wins) ->
receive
report ->
io:format("~w heads wins and ~w tails wins~n", [Heads_wins, Tails_wins]),
counter_loop(Heads_wins, Tails_wins);
{heads, won} ->
io:format("Heads won~n"),
counter_loop(1 + Heads_wins, Tails_wins);
{tails, lost} ->
io:format("Heads won~n"),
counter_loop(1 + Heads_wins, Tails_wins);
{heads, lost} ->
io:format("Tails won~n"),
counter_loop(Heads_wins, 1 + Tails_wins);
{tails, won} ->
io:format("Tails won~n"),
counter_loop(Heads_wins, 1 + Tails_wins)
end.
% The starting state, waiting for someone to pair us up with another
% process to do a cryptographic coin toss. One option in this state is
% to receive a start message, which pairs us with another process; we
% create a hashed flip and send that to them. The other option is to
% receive a choose message from another process that has already created
% a hashed flip and asks us to choose heads or tails and send our choice.
wait(ID) ->
receive
{start, Partner} ->
Flip = case crypto:rand_uniform(0, 2) of
0 -> tails;
1 -> heads
end,
io:format("Flip was ~w.~n", [Flip]),
{Salt, Hashed_flip} = hash_flip(Flip),
Partner ! {choose, self(), Hashed_flip},
wait_for_choice(ID, Partner, Flip, Salt, Hashed_flip);
{choose, Partner, Hashed_flip} ->
% The fib call is just to put some expensive computation in
% the system so we can see the parallelism at the system
% system monitor level. Feel free to remove this, or raise
% or lower the argument to fib to change the amount of work
% being done here.
F = fib(35),
Choice = case (crypto:rand_uniform(0, 2) + F) rem 2 of
0 -> tails;
1 -> heads
end,
% io:format("Choice was ~w.~n", [Choice]),
Partner ! {check_outcome, self(), Choice, Hashed_flip},
wait_for_outcome(ID, Partner, Choice, Hashed_flip);
stop ->
true
end.
% In this state we've flipped the coin, and sent a hash of the flip
% to our partner. We're now waiting for them to choose heads or tails
% and send us that choice. When we receive that we determine whether
% they won and send that and the unhashed flip to our partner for
% confirmation.
wait_for_choice(ID, Partner, Flip, Salt, Hashed_flip) ->
receive
{check_outcome, Partner, Choice, Hashed_flip} ->
Result = case Choice == Flip of
true -> won;
false -> lost
end,
io:format("Flip was ~w, choice was ~w, results was ~w.~n", [Flip, Choice, Result]),
Partner ! {confirm_result, self(), Result, Flip, Salt, Hashed_flip}
end.
% In this state we've made our choice and sent it to our partner. We're
% now waiting for them to decide if we won and send us that result along with
% the unhashed version of the flip and the salt so we can confirm that they didn't
% cheat.
wait_for_outcome(ID, Partner, Choice, Hashed_flip) ->
receive
{confirm_result, Partner, Result, Flip, Salt, Hashed_flip} ->
case (Choice == Flip) == (Result == won) of
true -> true;
false -> exit("Partner didn't process toss correctly")
end,
{Salt, Hash_check} = hash_flip(Salt, Flip),
case Hash_check of
Hashed_flip -> true;
_ -> exit("Partner's hashed flip didn't match")
end,
io:format("Processing choice ~w and result ~w.~n", [Choice, Result]),
Counter_PID = global:whereis_name(counter),
Counter_PID ! {Choice, Result}
end.
%% ====================================================================
%% Internal functions
%% ====================================================================
hash_flip(Flip) ->
Salt = integer_to_list(crypto:rand_uniform(0, 1000000)),
hash_flip(Salt, Flip).
hash_flip(Salt, Flip) ->
{Salt, crypto:md5(string:concat(Salt, atom_to_list(Flip)))}.
fib(0) -> 0;
fib(1) -> 1;
fib(N) -> fib(N-1) + fib(N-2).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment