Last active
December 15, 2015 12:38
-
-
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…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%% @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