diff --git a/src/dkg_hybriddkg.erl b/src/dkg_hybriddkg.erl index 742ffc8..2156e89 100644 --- a/src/dkg_hybriddkg.erl +++ b/src/dkg_hybriddkg.erl @@ -6,11 +6,23 @@ -type session() :: {Leader :: pos_integer(), Round :: pos_integer()}. - %% Q hat in the protocol --type qhat() :: #{pos_integer() => {dkg_commitment:commitment(), erlang_pbc:element()}}. --type qbar() :: #{pos_integer() => erlang_pbc:element()}. +-type lead_ch() :: any(). +-type signed_echo() :: any(). +-type signed_ready() :: any(). +-type vss_ready() :: any(). --record(state, { +-type leader_change() :: #{Leader :: pos_integer() => [{Sender :: pos_integer(), lead_ch()}]}. + +-type elq() :: #{{Leader :: pos_integer(), Q :: [pos_integer()]} => [{Sender :: pos_integer(), SignedEcho :: signed_echo()}]}. +-type rlq() :: #{{Leader :: pos_integer(), Q :: [pos_integer()]} => [{Sender :: pos_integer(), SignedReady :: signed_ready()}]}. + +-type rhat() :: [vss_ready()]. +-type qbar() :: [pos_integer()]. +-type qhat() :: [pos_integer()]. +-type mbar() :: [signed_ready() | signed_echo()]. + +-record(dkg, { + state = leader_unconfirmed :: leader_unconfirmed | functional | agreement_started | agreement_completed | leader_change_started | dkg_completed, id :: pos_integer(), n :: pos_integer(), f :: pos_integer(), @@ -18,13 +30,18 @@ u :: erlang_pbc:element(), u2 :: erlang_pbc:element(), vss_map :: #{pos_integer() => dkg_hybridvss:vss()}, - echoes_this_round = [] :: [non_neg_integer()], %% eL,Q in the protocol, echoes seen this round - readies_this_round = [] :: [non_neg_integer()], %% rL,Q in the protocol, readies seen this round - vss_done_this_round = #{} :: qhat(), - vss_done_last_round = #{} :: qbar(), - leader = 1 :: pos_integer(), + vss_results = #{} :: #{pos_integer() => {C :: dkg_commitment:commitment(), Si :: erlang_pbc:element()}}, + qbar = [] :: qbar(), + qhat = [] :: qhat(), + rhat = [] :: rhat(), + mbar = [] :: mbar(), + elq = #{} :: elq(), + rlq = #{} :: rlq(), session :: session(), - started = false :: boolean() + lc_flag = false :: boolean(), + leader :: pos_integer(), + l_next :: pos_integer(), + leader_change = #{} :: leader_change() }). %% upon initialization: @@ -40,16 +57,27 @@ init(Id, N, F, T, G1, G2, Round) -> erlang_pbc:element_pp_init(G2), Session = {1, Round}, VSSes = lists:foldl(fun(E, Map) -> - VSS = dkg_hybridvss:init(Id, N, F, T, G1, G2, {E, Round}), - maps:put(E, VSS, Map) - end, #{}, dkg_util:allnodes(N)), - #state{id=Id, n=N, f=F, t=T, u=G1, u2=G2, session=Session, vss_map=VSSes}. - -start(State = #state{started=false, id=Id, u=Generator}) -> - MyVSS = maps:get(Id, State#state.vss_map), - Secret = erlang_pbc:element_random(erlang_pbc:element_new('Zr', Generator)), + VSS = dkg_hybridvss:init(Id, N, F, T, G1, G2, {E, Round}), + maps:put(E, VSS, Map) + end, #{}, dkg_util:allnodes(N)), + + #dkg{id=Id, + n=N, + f=F, + t=T, + u=G1, + u2=G2, + leader=1, + l_next=l_next(1, N), + session=Session, + vss_map=VSSes}. + +start(DKG = #dkg{id=Id, u=G1}) -> + MyVSS = maps:get(Id, DKG#dkg.vss_map), + Secret = erlang_pbc:element_random(erlang_pbc:element_new('Zr', G1)), {NewVSS, {send, ToSend}} = dkg_hybridvss:input(MyVSS, Secret), - {State#state{vss_map=maps:put(Id, NewVSS, State#state.vss_map), started=true}, {send, dkg_util:wrap({vss, Id, State#state.session}, ToSend)}}. + {DKG#dkg{vss_map=maps:put(Id, NewVSS, DKG#dkg.vss_map), state=functional}, {send, dkg_util:wrap({vss, Id, DKG#dkg.session}, ToSend)}}. + %% upon (Pd, τ, out, shared, Cd , si,d , Rd ) (first time): %% Qhat ← {Pd}; Rhat ← {Rd} @@ -58,62 +86,80 @@ start(State = #state{started=false, id=Id, u=Generator}) -> %% send the message (L, τ, send, Qhat, Rhat) to each Pj %% else %% delay ← delay(T); start timer(delay) -handle_msg(State = #state{session=Session={Leader, _}}, Sender, {{vss, VssID, Session}, VssMSG}) -> - case dkg_hybridvss:handle_msg(maps:get(VssID, State#state.vss_map), Sender, VssMSG) of +handle_msg(DKG = #dkg{session=Session={Leader, _}}, Sender, {{vss, VSSId, Session}, VssMSG}) -> + case dkg_hybridvss:handle_msg(maps:get(VSSId, DKG#dkg.vss_map), Sender, VssMSG) of {NewVSS, ok} -> - {State#state{vss_map=maps:put(VssID, NewVSS, State#state.vss_map)}, ok}; + {DKG#dkg{vss_map=maps:put(VSSId, NewVSS, DKG#dkg.vss_map), state=agreement_started}, ok}; {NewVSS, {send, ToSend}} -> - {State#state{vss_map=maps:put(VssID, NewVSS, State#state.vss_map)}, {send, dkg_util:wrap({vss, VssID, Session}, ToSend)}}; - {NewVSS, {result, {_Session, Commitment, Si}}} -> + {DKG#dkg{vss_map=maps:put(VSSId, NewVSS, DKG#dkg.vss_map), state=agreement_started}, {send, dkg_util:wrap({vss, VSSId, Session}, ToSend)}}; + {NewVSS, {result, {_Session, Commitment, Si, Rd}}} -> + %% upon (Pd, τ, out, shared, Cd , si,d , Rd ) (first time): + %% Qhat ← {Pd}; Rhat ← {Rd} + %% if |Qhat| = t + 1 and Qbar = ∅ then + %% if Pi = L then + %% send the message (L, τ, send, Qhat, Rhat) to each Pj + %% else + %% delay ← delay(T); start timer(delay) %% TODO 'extended' VSS should output signed ready messages - VSSDoneThisRound = maps:put(VssID, {Commitment, Si}, State#state.vss_done_this_round), - case maps:size(VSSDoneThisRound) == State#state.t + 1 andalso maps:size(State#state.vss_done_last_round) == 0 of + + NewDKG = DKG#dkg{vss_map=maps:put(VSSId, NewVSS, DKG#dkg.vss_map), + vss_results=maps:put(VSSId, {Commitment, Si}, DKG#dkg.vss_results), + qhat=[VSSId | DKG#dkg.qhat], + rhat=[{signed_vss_ready, Rd} | DKG#dkg.rhat] + }, + + case length(NewDKG#dkg.qhat) == NewDKG#dkg.t + 1 andalso length(NewDKG#dkg.qbar) == 0 of true -> - case State#state.id == Leader of + case NewDKG#dkg.id == Leader of true -> - %% this is not multicast in the protocol, but we have multicast support, sooooooo.... - {State#state{vss_map=maps:put(VssID, NewVSS, State#state.vss_map), - vss_done_this_round=VSSDoneThisRound}, - {send, [{multicast, {send, Session, maps:keys(VSSDoneThisRound)}}]}}; + {NewDKG, {send, [{multicast, {send, Session, NewDKG#dkg.qhat, {rhat, NewDKG#dkg.rhat}}}]}}; false -> - {State#state{vss_map=maps:put(VssID, NewVSS, State#state.vss_map), vss_done_this_round=VSSDoneThisRound}, ok} + {NewDKG, start_timer} end; false -> - {State#state{vss_map=maps:put(VssID, NewVSS, State#state.vss_map), vss_done_this_round=VSSDoneThisRound}, ok} + {NewDKG, ok} end end; %% upon a message (L, τ, send, Q, R/M) from L (first time): %% if verify-signature(Q, R/M) and (Qbar = ∅ or Qbar = Q) then %% send the message (L, τ, echo, Q)sign to each Pj -handle_msg(State = #state{session=Session={Leader, _}}, Sender, {send, Session, VSSDone}) when Sender == Leader -> +handle_msg(DKG = #dkg{session=Session={_Leader, _}}, _Sender, {send, Session, Q, {rhat, _Rhat}}) -> %% TODO verify signatures - case maps:size(State#state.vss_done_last_round) == 0 orelse lists:sort(maps:keys(State#state.vss_done_this_round)) == lists:sort(VSSDone) of + case length(DKG#dkg.qbar) == 0 orelse lists:usort(DKG#dkg.qbar) == lists:usort(Q) of true -> - {State, {send, [{multicast, {echo, Session, VSSDone}}]}}; + {DKG, {send, [{multicast, {echo, Session, Q}}]}}; false -> - {State, ok} + {DKG, ok} end; +handle_msg(DKG, _Sender, {send, _Session, _, _}) -> + {DKG, ok}; + %% upon a message (L, τ, echo, Q)sign from Pm (first time): %% e(L,Q) ← e(L,Q) + 1 %% if e(L,Q) = ceil((n+t+1)/2) and r(L,Q) < t + 1 then %% Qbar ← Q; Mbar ← ceil((n+t+1)/2) signed echo messages for Q %% send the message (L, τ, ready, Q)sign to each Pj -handle_msg(State = #state{session=Session, n=N, t=T}, Sender, {echo, Session, VSSDone}) -> - case lists:member(Sender, State#state.echoes_this_round) of +handle_msg(DKG = #dkg{id=_Id, session=Session, n=N, t=T}, Sender, {echo, Session, Q}=EchoMsg) -> + case update_elq(DKG, Sender, EchoMsg) of false -> - EchoesThisRound = [Sender | State#state.echoes_this_round], - case length(EchoesThisRound) == ceil((N + T + 1) / 2) andalso length(State#state.readies_this_round) < T + 1 of + {DKG, ok}; + {true, NewDKG} -> + case count_echo(NewDKG, Q) == ceil((N+T+1)/2) andalso count_ready(NewDKG, Q) < T+1 of true -> - %% TODO QBar <- Q, MBar <- ... - {State#state{echoes_this_round=EchoesThisRound}, {send, [{multicast, {ready, Session, VSSDone}}]}}; + %% update qbar + NewQbar = Q, + %% update mbar + NewMbar = get_echo(NewDKG, Q), + %% send ready message + {NewDKG#dkg{qbar=NewQbar, mbar=NewMbar}, {send, [{multicast, {ready, Session, Q}}]}}; false -> - {State#state{echoes_this_round=EchoesThisRound}, ok} - end; - true -> - {State, ok} + {NewDKG, ok} + end end; +handle_msg(DKG, _Sender, {echo, _Session, _}=_EchoMsg) -> + {DKG, ok}; %% upon a message (L, τ, ready, Q)sign from Pm (first time): %% r(L,Q) ← r(L,Q) + 1 @@ -125,63 +171,227 @@ handle_msg(State = #state{session=Session, n=N, t=T}, Sender, {echo, Session, VS %% WAIT for shared output-messages for each Pd ∈ Q %% si ← SUM(si,d) ∀Pd ∈ Q; ∀p,q : C ← MUL(Cd)p,q ∀Pd ∈ Q %% output (L, τ, DKG-completed, C, si) -handle_msg(State = #state{n=N, t=T, f=F}, Sender, {ready, Session, VSSDone}) -> - case lists:member(Sender, State#state.readies_this_round) of +handle_msg(DKG = #dkg{id=_Id, n=N, t=T, f=F}, Sender, {ready, Session, Q}=ReadyMsg) -> + case update_rlq(DKG, Sender, ReadyMsg) of false -> - ReadiesThisRound = [Sender | State#state.readies_this_round], - case length(ReadiesThisRound) == T + 1 andalso length(State#state.echoes_this_round) < ceil((N + T + 1) /2) of + {DKG, ok}; + {true, NewDKG} -> + case count_ready(NewDKG, Q) == T+1 andalso count_echo(NewDKG, Q) < ceil((N+T+1)/2) of + true -> + %% update qbar + NewQbar = Q, + %% update mbar + NewMbar = get_ready(NewDKG, Q), + %% send ready msg + {NewDKG#dkg{qbar=NewQbar, mbar=NewMbar}, {send, [{multicast, {ready, Session, Q}}]}}; + false -> + case count_ready(NewDKG, Q) == N-T-F of + true -> + %% TODO stop timer + %% TODO presumably we need to check this when we get a VSS result as well? + OutputCommitment = output_commitment(NewDKG), + PublicKeyShares = public_key_shares(NewDKG, OutputCommitment), + VerificationKey = verification_key(OutputCommitment), + Shard = shard(NewDKG), + {NewDKG#dkg{state=dkg_completed}, {result, {Shard, VerificationKey, PublicKeyShares}}}; + false -> + {NewDKG#dkg{state=agreement_started}, ok} + end + end + end; +handle_msg(DKG, _Sender, {ready, _Session, _VSSDone}=_ReadyMsg) -> + %% DKG received ready message from itself, what to do? + {DKG, ok}; + +%% upon timeout: +%% if lcflag = false then +%% if Q = ∅ then +%% lcflag ← true; send msg (τ, lead-ch, L + 1, Qhat, Rhat)sign to each Pj +%% else +%% lcflag ← true; send msg (τ, lead-ch, L + 1, Qbar, Mbar)sign to each Pj +handle_msg(DKG=#dkg{lc_flag=false, + id=_Id, + qbar=Qbar, + qhat=Qhat, + rhat=Rhat, + mbar=Mbar, + session={CurrentLeader, Round}}, _Sender, timeout) -> + NewDKG = DKG#dkg{lc_flag=true}, + + Msg = case length(Qbar) == 0 of + true -> + {send, [{multicast, {leader_change, {CurrentLeader+1, Round}, Qhat, {rhat, Rhat}}}]}; + false -> + {send, [{multicast, {leader_change, {CurrentLeader+1, Round}, Qbar, {mbar, Mbar}}}]} + end, + {NewDKG, Msg}; +handle_msg(DKG, _Sender, timeout) -> + %% lc_flag is true + {DKG, ok}; + +%% TODO +%% Leader-change for node Pi: session (τ ) and leader L +%% upon a msg (τ, lead-ch, Lbar, Q, R/M)sign from Pj (first time): +%% if Lbar > L and verify-signature(Q, R/M) then +%% lcL ← lcL + 1 +%% Lnext ← min (Lnext , L) +%% if R/M = R then +%% Qhat ← Q; Rhat <- R +%% else +%% Qbar ← Q; Mbar ← M +%% if (SUM(lcL) = t + f + 1 and lcflag = false) then +%% if Qbar = ∅ then +%% send the msg (τ, lead-ch, Lnext, Qhat, Rhat) to each Pj +%% else +%% send the msg (τ, lead-ch, Lnext, Qbar, Mbar) to each Pj +%% else if (lcL = n − t − f ) then +%% Mbar ← R <- n − t − f signed lead-ch messages for Lbar +%% L ← Lbar; Lnext ← L − 1 +%% lcL ← 0; lcflag = false +%% if Pi = L then +%% if Q = ∅ then +%% send the message (L, τ, send, Qhat, Rhat) to each Pj +%% else +%% send the message (L, τ, send, Qbar, Mbar) to each Pj +%% else +%% delay ← delay(T ) +%% start timer(delay) +handle_msg(DKG=#dkg{leader=Leader, t=T, n=N, f=F, qhat=Qhat0, rhat=Rhat0, l_next=LNext}, + Sender, + {leader_change, {Lbar, Round}, Q, RorM}=LeaderChangeMsg) when Lbar > Leader -> + %% TODO: verify the signature(Q, R/M) + case store_leader_change(DKG, Sender, LeaderChangeMsg) of + {true, NewDKG0} -> + %% new lnext + NewLNext = min(LNext, Lbar), + NewDKG = case RorM of + {rhat, Rhat} -> + %% FIXME: do this better + NewQhat = lists:usort(Qhat0 ++ Q), + NewRhat = lists:usort(Rhat0 ++ Rhat), + NewDKG0#dkg{qhat=NewQhat, rhat=NewRhat}; + {mbar, Mbar} -> + NewDKG0#dkg{qbar=Q, mbar=Mbar} + end, + case count_leader_change(NewDKG) == T+F+1 andalso not NewDKG#dkg.lc_flag of true -> - %% TODO QBar <- Q, MBar <- ... - {State#state{readies_this_round=ReadiesThisRound}, {send, [{multicast, {ready, Session, VSSDone}}]}}; + case length(NewDKG#dkg.qbar) == 0 of + true -> + {send, [{multicast, {leader_change, {NewLNext, Round}, NewDKG#dkg.qhat, {rhat, NewDKG#dkg.rhat}}}]}; + false -> + {send, [{multicast, {leader_change, {NewLNext, Round}, NewDKG#dkg.qbar, {mbar, NewDKG#dkg.mbar}}}]} + end; false -> - case length(ReadiesThisRound) == N - T - F of + LeaderChangeMsgs = maps:get(Lbar, NewDKG#dkg.leader_change, []), + case length(LeaderChangeMsgs) == N-T-F of true -> - case lists:all(fun(E) -> maps:is_key(E, State#state.vss_done_this_round) end, ReadiesThisRound) of + NewerMbar = NewerRhat = LeaderChangeMsgs, + NewLeader = Lbar, + NewerLNext = l_next(Leader, N), + NewLeaderChangeMap = maps:put(NewLeader, [], NewDKG#dkg.leader_change), + NewSession = {NewLeader, Round}, + NewerDKG = NewDKG#dkg{lc_flag=false, + mbar=NewerMbar, + rhat=NewerRhat, + leader=NewLeader, + l_next=NewerLNext, + leader_change=NewLeaderChangeMap, + session=NewSession}, + case DKG#dkg.id == NewLeader of true -> - %% TODO presumably we need to check this when we get a VSS result as well? - OutputCommitment = output_commitment(State, VSSDone), - PublicKeyShares = public_key_shares(State, OutputCommitment), - VerificationKey = verification_key(OutputCommitment), - Shard = shard(State, VSSDone), - {State#state{readies_this_round=ReadiesThisRound}, {result, {Shard, VerificationKey, PublicKeyShares}}}; + case length(NewerDKG#dkg.qbar) == 0 of + true -> + {NewerDKG, {send, [{multicast, {send, NewSession, NewerDKG#dkg.qhat, {rhat, NewerDKG#dkg.rhat}}}]}}; + false -> + {NewerDKG, {send, [{multicast, {send, NewSession, NewerDKG#dkg.qbar, {mbar, NewerDKG#dkg.mbar}}}]}} + end; false -> - {State#state{readies_this_round=ReadiesThisRound}, ok} + {NewerDKG, start_timer} end; false -> - {State#state{readies_this_round=ReadiesThisRound}, ok} + {NewDKG, ok} end end; - true -> - {State, ok} + false -> + {DKG, ok} end; -handle_msg(State, _Sender, Msg) -> - {State, {unhandled_msg, Msg}}. +handle_msg(DKG, _Sender, {leader_change, {_Lbar, _Round}, _, _}) -> + {DKG, ok}; +handle_msg(DKG, _Sender, Msg) -> + {DKG, {unhandled_msg, Msg}}. %% helper functions --spec output_commitment(#state{}, [non_neg_integer()]) -> dkg_commitment:commitment(). -output_commitment(_State=#state{vss_done_this_round=VSSDoneThisRound}, VSSDone) -> - [FirstCommitment | RemainingCommitments] = lists:foldl(fun(VSSId, Acc) -> - {Commitment, _} = maps:get(VSSId, VSSDoneThisRound), - [Commitment | Acc] - end, [], VSSDone), - lists:foldl(fun(Commitment, Acc) -> - dkg_commitment:mul(Acc, Commitment) - end, FirstCommitment, RemainingCommitments). - --spec public_key_shares(#state{}, dkg_commitment:commitment()) -> [erlang_pbc:element()]. -public_key_shares(_State=#state{n=N}, OutputCommitment) -> +-spec output_commitment(#dkg{}) -> dkg_commitment:commitment(). +output_commitment(_DKG=#dkg{vss_results=R, u2=U2, t=T, n=N}) -> + maps:fold(fun(_K, {Commitment, _}, Acc) -> + dkg_commitment:mul(Commitment, Acc) + end, dkg_commitment:new(lists:seq(1, N), U2, T), R). + +-spec public_key_shares(#dkg{}, dkg_commitment:commitment()) -> [erlang_pbc:element()]. +public_key_shares(_DKG=#dkg{n=N}, OutputCommitment) -> [dkg_commitment:public_key_share(OutputCommitment, NodeID) || NodeID <- dkg_util:allnodes(N)]. -spec verification_key(dkg_commitment:commitment()) -> erlang_pbc:element(). verification_key(OutputCommitment) -> dkg_commitmentmatrix:lookup([1, 1], dkg_commitment:matrix(OutputCommitment)). --spec shard(#state{}, [non_neg_integer()]) -> erlang_pbc:element(). -shard(_State=#state{vss_done_this_round=VSSDoneThisRound}, VSSDone) -> - [FirstShare | RemainingShares] = lists:foldl(fun(VSSId, Acc) -> - {_, Share} = maps:get(VSSId, VSSDoneThisRound), - [Share | Acc] - end, [], VSSDone), - lists:foldl(fun(Share, Acc) -> - erlang_pbc:element_add(Acc, Share) - end, FirstShare, RemainingShares). +-spec shard(#dkg{}) -> erlang_pbc:element(). +shard(_DKG=#dkg{vss_results=R, u=U}) -> + Zero = erlang_pbc:element_set(erlang_pbc:element_new('Zr', U), 0), + maps:fold(fun(_K, {_, Si}, Acc) -> + erlang_pbc:element_add(Acc, Si) + end, Zero, R). + +l_next(L, N) -> + case L - 1 < 1 of + true -> + N; + false -> + L - 1 + end. + +update_elq(DKG=#dkg{elq=Elq}, Sender, {echo, _Session, Q0}=EchoMsg) -> + Q = lists:usort(Q0), + EchoForQAndLeader = maps:get({DKG#dkg.leader, Q}, Elq, []), + case lists:keyfind(Sender, 1, EchoForQAndLeader) of + false -> + NewDKG = DKG#dkg{elq=maps:put({DKG#dkg.leader, Q}, [{Sender, EchoMsg} | EchoForQAndLeader], Elq)}, + {true, NewDKG}; + _ -> + %% already have this echo + false + end. + +count_echo(_DKG=#dkg{elq=Elq, leader=Leader}, Q0) -> + Q = lists:usort(Q0), + length(maps:get({Leader, Q}, Elq, [])). +count_ready(_DKG=#dkg{rlq=Rlq, leader=Leader}, Q0) -> + Q = lists:usort(Q0), + length(maps:get({Leader, Q}, Rlq, [])). + +get_echo(_DKG=#dkg{elq=Elq, leader=Leader}, Q) -> maps:get({Leader, Q}, Elq, []). +get_ready(_DKG=#dkg{rlq=Rlq, leader=Leader}, Q) -> maps:get({Leader, Q}, Rlq, []). + +update_rlq(DKG=#dkg{rlq=Rlq}, Sender, {ready, _Session, Q0}=ReadyMsg) -> + Q = lists:usort(Q0), + ReadyForQAndLeader = maps:get({DKG#dkg.leader, Q}, Rlq, []), + case lists:keyfind(Sender, 1, ReadyForQAndLeader) of + false -> + NewDKG = DKG#dkg{rlq=maps:put({DKG#dkg.leader, Q}, [{Sender, ReadyMsg} | ReadyForQAndLeader], Rlq)}, + {true, NewDKG}; + _ -> + %% already have this echo + false + end. + +store_leader_change(DKG, Sender, {leader_change, {Lbar, _Round}, _, _}=LeaderChangeMsg) -> + L = maps:get(Lbar, DKG#dkg.leader_change, []), + case lists:keyfind(Sender, 1, L) of + false -> + NewLCM = maps:put(Lbar, lists:keystore(Sender, 1, L, {Sender, LeaderChangeMsg}), DKG#dkg.leader_change), + {true, DKG#dkg{leader_change=NewLCM}}; + _ -> + false + end. + +count_leader_change(DKG) -> length(lists:flatten(maps:keys(DKG#dkg.leader_change))). diff --git a/src/dkg_hybridvss.erl b/src/dkg_hybridvss.erl index c2ed144..512583d 100644 --- a/src/dkg_hybridvss.erl +++ b/src/dkg_hybridvss.erl @@ -27,7 +27,7 @@ -type send_msg() :: {unicast, pos_integer(), {send, {session(), dkg_commitmentmatrix:serialized_matrix(), dkg_polynomial:polynomial()}}}. -type echo_msg() :: {unicast, pos_integer(), {echo, {session(), dkg_commitmentmatrix:serialized_matrix(), binary()}}}. -type ready_msg() :: {unicast, pos_integer(), {ready, {session(), dkg_commitmentmatrix:serialized_matrix(), binary()}}}. --type result() :: {result, {session(), dkg_commitment:commitment(), [erlang_pbc:element()]}}. +-type result() :: {result, {session(), dkg_commitment:commitment(), [erlang_pbc:element()], map()}}. -export_type([vss/0, session/0]). @@ -132,7 +132,7 @@ handle_msg(State=#state{echoes=Echoes, id=Id, n=N, t=T, session=Session}, Sender %% send the message (Pd, τ, ready, C, a(j)) to Pj %% else if rC = n − t − f then %% si ← a(0); output (Pd , τ, out, shared, C, si ) -handle_msg(State=#state{readies=Readies, n=N, t=T, f=F, id=Id, commitment=Commitment}, Sender, {ready, {Session, SerializedCommitmentMatrix0, SA}}) -> +handle_msg(State=#state{readies=Readies, n=N, t=T, f=F, id=Id, commitment=Commitment}, Sender, {ready, {Session, SerializedCommitmentMatrix0, SA}}=ReadyMsg) -> CommitmentMatrix0 = dkg_commitmentmatrix:deserialize(SerializedCommitmentMatrix0, State#state.u), A = erlang_pbc:binary_to_element(State#state.u, SA), case dkg_commitmentmatrix:verify_point(State#state.u2, CommitmentMatrix0, Sender, Id, A) of @@ -150,16 +150,16 @@ handle_msg(State=#state{readies=Readies, n=N, t=T, f=F, id=Id, commitment=Commit Msgs = lists:map(fun(Node) -> {unicast, Node, {ready, {Session, SerializedCommitmentMatrix0, erlang_pbc:element_to_binary(lists:nth(Node+1, SubShares))}}} end, dkg_util:allnodes(N)), - NewState = State#state{readies=maps:put(Sender, true, Readies), commitment=NewCommitment, received_commitment=true}, + NewState = State#state{readies=maps:put(Sender, ReadyMsg, Readies), commitment=NewCommitment, received_commitment=true}, {NewState, {send, Msgs}}; false -> case dkg_commitment:num_readies(NewCommitment) == (N-T-F) of true-> [SubShare] = dkg_commitment:interpolate(NewCommitment, ready, []), - NewState = State#state{readies=maps:put(Sender, true, Readies), commitment=NewCommitment, received_commitment=true}, - {NewState, {result, {Session, Commitment, SubShare}}}; + NewState = State#state{readies=maps:put(Sender, ReadyMsg, Readies), commitment=NewCommitment, received_commitment=true}, + {NewState, {result, {Session, Commitment, SubShare, NewState#state.readies}}}; false -> - NewState = State#state{readies=maps:put(Sender, true, Readies), commitment=NewCommitment, received_commitment=true}, + NewState = State#state{readies=maps:put(Sender, ReadyMsg, Readies), commitment=NewCommitment, received_commitment=true}, {NewState, ok} end end; diff --git a/test/dkg_distributed_SUITE.erl b/test/dkg_distributed_SUITE.erl index ed80bff..f90c6cb 100644 --- a/test/dkg_distributed_SUITE.erl +++ b/test/dkg_distributed_SUITE.erl @@ -32,8 +32,8 @@ end_per_suite(Config) -> Config. init_per_testcase(TestCase, Config) -> - %% assuming each testcase will work with 5 nodes for now - NodeNames = [eric, kenny, kyle, ike, stan, randy, butters, token, jimmy, timmy], + %% assuming each testcase will work with 7 nodes for now + NodeNames = [eric, kenny, kyle, ike, stan, randy, butters], Nodes = dkg_ct_utils:pmap(fun(Node) -> dkg_ct_utils:start_node(Node, Config, TestCase) end, NodeNames), @@ -41,8 +41,8 @@ init_per_testcase(TestCase, Config) -> _ = [dkg_ct_utils:connect(Node) || Node <- NodeNames], N = length(Nodes), - F = 3, - T = 1, + F = 0, + T = 2, {ok, _} = ct_cover:add_nodes(Nodes), [{nodes, Nodes}, {n, N}, {f, F}, {t, T} | Config]. diff --git a/test/dkg_hybriddkg_SUITE.erl b/test/dkg_hybriddkg_SUITE.erl index c4f7801..523e9e3 100644 --- a/test/dkg_hybriddkg_SUITE.erl +++ b/test/dkg_hybriddkg_SUITE.erl @@ -5,20 +5,24 @@ -export([all/0, init_per_testcase/2, end_per_testcase/2]). -export([ - init_test/1, - mnt224_test/1 + symmetric_test/1, + asymmetric_test/1, + leader_change_symmetric_test/1, + leader_change_asymmetric_test/1 ]). all() -> [ - init_test, - mnt224_test + symmetric_test, + asymmetric_test, + leader_change_symmetric_test, + leader_change_asymmetric_test ]. init_per_testcase(_, Config) -> N = list_to_integer(os:getenv("N", "10")), - F = 3, - T = 1, + F = 0, + T = 3, Ph = 0, Module = dkg_hybriddkg, [{n, N}, {f, F}, {module, Module}, {t, T}, {ph, Ph} | Config]. @@ -26,7 +30,7 @@ init_per_testcase(_, Config) -> end_per_testcase(_, _Config) -> ok. -init_test(Config) -> +symmetric_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), T = proplists:get_value(t, Config), @@ -86,7 +90,7 @@ init_test(Config) -> ?assertEqual(N, length(sets:to_list(ConvergedResults))), ok. -mnt224_test(Config) -> +asymmetric_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), T = proplists:get_value(t, Config), @@ -137,3 +141,116 @@ mnt224_test(Config) -> ?assertEqual(N, length(sets:to_list(ConvergedResults))), ok. + +leader_change_symmetric_test(Config) -> + N = proplists:get_value(n, Config), + F = proplists:get_value(f, Config), + T = proplists:get_value(t, Config), + Module = proplists:get_value(module, Config), + Group = erlang_pbc:group_new('SS512'), + G1 = erlang_pbc:element_from_hash(erlang_pbc:element_new('G1', Group), crypto:strong_rand_bytes(32)), + G2 = case erlang_pbc:pairing_is_symmetric(Group) of + true -> G1; + false -> erlang_pbc:element_from_hash(erlang_pbc:element_new('G2', Group), crypto:strong_rand_bytes(32)) + end, + + {StatesWithId, Replies} = lists:unzip(lists:map(fun(E) -> + {State, {send, Replies}} = Module:start(Module:init(E, N, F, T, G1, G2, {1, 0})), + {{E, State}, {E, {send, Replies}}} + end, lists:seq(2, N))), + + {_FinalStates, ConvergedResults} = dkg_test_utils:do_send_outer(Module, Replies, StatesWithId, sets:new()), + ct:pal("Results ~p", [sets:to_list(ConvergedResults)]), + + %% XXX: this is the same as the pubkeyshare test, I'm sure there is more to it + SecretKeyShares = lists:keysort(1, [ {Node, SecretKey} || {result, {Node, {SecretKey, _VerificationKey, _VerificationKeys}}} <- sets:to_list(ConvergedResults)]), + VerificationKeys = lists:keysort(1, [ {Node, VerificationKey} || {result, {Node, {_SecretKey, VerificationKey, _VerificationKeys}}} <- sets:to_list(ConvergedResults)]), + VerificationKeyss = lists:keysort(1, [ {Node, VerificationKeyz} || {result, {Node, {_SecretKey, _VerificationKey, VerificationKeyz}}} <- sets:to_list(ConvergedResults)]), + ct:pal("Secret key shares ~p", [[ erlang_pbc:element_to_string(S) || {_, S} <- SecretKeyShares]]), + ct:pal("Public key shares ~p", [[ erlang_pbc:element_to_string(S) || {_, S} <- VerificationKeys]]), + ct:pal("Public key shares ~p", [[ lists:map(fun erlang_pbc:element_to_string/1, S) || {_, S} <- VerificationKeyss]]), + PublicKeySharePoly = [Share || Share <- element(2, hd(VerificationKeyss))], + KnownSecret = dkg_polynomial:evaluate(PublicKeySharePoly, 0), + Indices = [ erlang_pbc:element_set(erlang_pbc:element_new('Zr', G1), I) || I <- lists:seq(1, N) ], + Alpha = erlang_pbc:element_set(erlang_pbc:element_new('Zr', G1), 0), + CalculatedSecret = dkg_lagrange:interpolate(PublicKeySharePoly, Indices, Alpha), + ?assert(erlang_pbc:element_cmp(KnownSecret, CalculatedSecret)), + + %% attempt to construct some TPKE keys... + + PrivateKeys = lists:map(fun({result, {Node, {SK, VK, VKs}}}) -> + PK = tpke_pubkey:init(N, F, G1, G2, VK, VKs, 'SS512'), + tpke_privkey:init(PK, SK, Node-1) + end, sets:to_list(ConvergedResults)), + PubKey = tpke_privkey:public_key(hd(PrivateKeys)), + Msg = crypto:hash(sha256, crypto:strong_rand_bytes(12)), + MessageToSign = tpke_pubkey:hash_message(PubKey, Msg), + Signatures = [ tpke_privkey:sign(PrivKey, MessageToSign) || PrivKey <- PrivateKeys], + ct:pal("~p", [[tpke_pubkey:verify_signature_share(PubKey, Share, MessageToSign) || Share <- Signatures]]), + ?assert(lists:all(fun(X) -> X end, [tpke_pubkey:verify_signature_share(PubKey, Share, MessageToSign) || Share <- Signatures])), + {ok, Sig} = tpke_pubkey:combine_signature_shares(PubKey, dealer:random_n(T+1, Signatures), MessageToSign), + ?assert(tpke_pubkey:verify_signature(PubKey, Sig, MessageToSign)), + + Message = crypto:hash(sha256, <<"my hovercraft is full of eels">>), + CipherText = tpke_pubkey:encrypt(PubKey, Message), + ?assert(tpke_pubkey:verify_ciphertext(PubKey, CipherText)), + Shares = [ tpke_privkey:decrypt_share(SK, CipherText) || SK <- PrivateKeys ], + ct:pal("Decrypted shares ~p", [Shares]), + ?assert(lists:all(fun(X) -> X end, [tpke_pubkey:verify_share(PubKey, Share, CipherText) || Share <- Shares])), + ?assertEqual(Message, tpke_pubkey:combine_shares(PubKey, CipherText, dealer:random_n(T+1, Shares))), + + ?assertEqual(N-1, length(sets:to_list(ConvergedResults))), + ok. + +leader_change_asymmetric_test(Config) -> + N = proplists:get_value(n, Config), + F = proplists:get_value(f, Config), + T = proplists:get_value(t, Config), + Module = proplists:get_value(module, Config), + Group = erlang_pbc:group_new('MNT224'), + G1 = erlang_pbc:element_from_hash(erlang_pbc:element_new('G1', Group), crypto:strong_rand_bytes(32)), + G2 = case erlang_pbc:pairing_is_symmetric(Group) of + true -> G1; + false -> erlang_pbc:element_from_hash(erlang_pbc:element_new('G2', Group), crypto:strong_rand_bytes(32)) + end, + + {StatesWithId, Replies} = lists:unzip(lists:map(fun(E) -> + {State, {send, Replies}} = Module:start(Module:init(E, N, F, T, G1, G2, {1, 0})), + {{E, State}, {E, {send, Replies}}} + end, lists:seq(2, N))), + + {_FinalStates, ConvergedResults} = dkg_test_utils:do_send_outer(Module, Replies, StatesWithId, sets:new()), + %ct:pal("Results ~p", [sets:to_list(ConvergedResults)]), + + %% XXX: this is the same as the pubkeyshare test, I'm sure there is more to it + SecretKeyShares = lists:keysort(1, [ {Node, SecretKey} || {result, {Node, {SecretKey, _VerificationKey, _VerificationKeys}}} <- sets:to_list(ConvergedResults)]), + VerificationKeys = lists:keysort(1, [ {Node, VerificationKey} || {result, {Node, {_SecretKey, VerificationKey, _VerificationKeys}}} <- sets:to_list(ConvergedResults)]), + VerificationKeyss = lists:keysort(1, [ {Node, VerificationKeyz} || {result, {Node, {_SecretKey, _VerificationKey, VerificationKeyz}}} <- sets:to_list(ConvergedResults)]), + ct:pal("Secret key shares ~p", [[ erlang_pbc:element_to_string(S) || {_, S} <- SecretKeyShares]]), + ct:pal("Public key shares ~p", [[ erlang_pbc:element_to_string(S) || {_, S} <- VerificationKeys]]), + ct:pal("Public key shares ~p", [[ lists:map(fun erlang_pbc:element_to_string/1, S) || {_, S} <- VerificationKeyss]]), + PublicKeySharePoly = [Share || Share <- element(2, hd(VerificationKeyss))], + KnownSecret = dkg_polynomial:evaluate(PublicKeySharePoly, 0), + Indices = [ erlang_pbc:element_set(erlang_pbc:element_new('Zr', G1), I) || I <- lists:seq(1, N) ], + Alpha = erlang_pbc:element_set(erlang_pbc:element_new('Zr', G1), 0), + CalculatedSecret = dkg_lagrange:interpolate(PublicKeySharePoly, Indices, Alpha), + ?assert(erlang_pbc:element_cmp(KnownSecret, CalculatedSecret)), + + %% attempt to construct some TPKE keys... + + PrivateKeys = lists:map(fun({result, {Node, {SK, VK, VKs}}}) -> + PK = tpke_pubkey:init(N, F, G1, G2, VK, VKs, 'MNT224'), + tpke_privkey:init(PK, SK, Node-1) + end, sets:to_list(ConvergedResults)), + PubKey = tpke_privkey:public_key(hd(PrivateKeys)), + Msg = crypto:hash(sha256, crypto:strong_rand_bytes(12)), + MessageToSign = tpke_pubkey:hash_message(PubKey, Msg), + Signatures = [ tpke_privkey:sign(PrivKey, MessageToSign) || PrivKey <- PrivateKeys], + ct:pal("~p", [[tpke_pubkey:verify_signature_share(PubKey, Share, MessageToSign) || Share <- Signatures]]), + ?assert(lists:all(fun(X) -> X end, [tpke_pubkey:verify_signature_share(PubKey, Share, MessageToSign) || Share <- Signatures])), + {ok, Sig} = tpke_pubkey:combine_signature_shares(PubKey, dealer:random_n(T+1, Signatures), MessageToSign), + ?assert(tpke_pubkey:verify_signature(PubKey, Sig, MessageToSign)), + + ?assertEqual(N-1, length(sets:to_list(ConvergedResults))), + ok. + diff --git a/test/dkg_hybridvss_SUITE.erl b/test/dkg_hybridvss_SUITE.erl index befd1fe..6c5481d 100644 --- a/test/dkg_hybridvss_SUITE.erl +++ b/test/dkg_hybridvss_SUITE.erl @@ -49,11 +49,11 @@ init_test(Config) -> {_FinalStates, ConvergedResults} = dkg_test_utils:do_send_outer(Module, [{1, {send, MsgsToSend}}], StatesWithId, sets:new()), %% check that the shares from nodes can be interpolated to calculate the original secret back - NodesAndShares = lists:foldl(fun({result, {Node, {_Session, _Commitment, Share}}}, Acc) -> + NodesAndShares = lists:foldl(fun({result, {Node, {_Session, _Commitment, Share, _Rd}}}, Acc) -> maps:put(Node, Share, Acc) end, #{}, sets:to_list(ConvergedResults)), - AllCommitments = [Commitment || {result, {_Node, {_Session, Commitment, _Share}}} <- sets:to_list(ConvergedResults)], + AllCommitments = [Commitment || {result, {_Node, {_Session, Commitment, _Share, _Rd}}} <- sets:to_list(ConvergedResults)], OutputCommitment = hd(AllCommitments), %[VerificationKey | PublicKeyShares] = dkg_commitment:interpolate(OutputCommitment, ready, lists:seq(1, N)), @@ -121,11 +121,11 @@ mnt224_test(Config) -> {_FinalStates, ConvergedResults} = dkg_test_utils:do_send_outer(Module, [{1, {send, MsgsToSend}}], StatesWithId, sets:new()), %% check that the shares from nodes can be interpolated to calculate the original secret back - NodesAndShares = lists:foldl(fun({result, {Node, {_Session, _Commitment, Share}}}, Acc) -> + NodesAndShares = lists:foldl(fun({result, {Node, {_Session, _Commitment, Share, _Rd}}}, Acc) -> maps:put(Node, Share, Acc) end, #{}, sets:to_list(ConvergedResults)), - AllCommitments = [Commitment || {result, {_Node, {_Session, Commitment, _Share}}} <- sets:to_list(ConvergedResults)], + AllCommitments = [Commitment || {result, {_Node, {_Session, Commitment, _Share, _Rd}}} <- sets:to_list(ConvergedResults)], OutputCommitment = hd(AllCommitments), %[VerificationKey | PublicKeyShares] = dkg_commitment:interpolate(OutputCommitment, ready, lists:seq(1, N)), diff --git a/test/dkg_test_utils.erl b/test/dkg_test_utils.erl index ce5b3aa..e80e06d 100644 --- a/test/dkg_test_utils.erl +++ b/test/dkg_test_utils.erl @@ -2,15 +2,27 @@ -export([do_send_outer/4, random_n/2]). -do_send_outer(_Mod, [], States, Acc) -> - {States, Acc}; +do_send_outer(Mod, [], States, Acc) -> + case get_timers() of + [] -> + {States, Acc}; + Timers -> + ct:pal("Timers ~p", [Timers]), + {R, NewStates} = do_send(Mod, {0, {send, [ {unicast, J, timeout} || J <- Timers]}}, [], States), + erlang:put(timers, []), + do_send_outer(Mod, R, NewStates, Acc) + end; do_send_outer(Mod, [{result, {Id, Result}} | T], Pids, Acc) -> do_send_outer(Mod, T, Pids, sets:add_element({result, {Id, Result}}, Acc)); do_send_outer(Mod, [H|T], States, Acc) -> {R, NewStates} = do_send(Mod, H, [], States), do_send_outer(Mod, T++R, NewStates, Acc). +do_send(_Mod, {Id, start_timer}, Acc, States) -> + set_timer(Id), + {Acc, States}; do_send(_Mod, {Id, {result, Result}}, Acc, States) -> + cancel_timer(Id), {[{result, {Id, Result}} | Acc], States}; do_send(_Mod, {_, ok}, Acc, States) -> {Acc, States}; @@ -34,6 +46,20 @@ do_send(Mod, {Id, {send, [{multicast, Msg}|T]}}, Acc, States) -> do_send(_, Bleh, _, _) -> erlang:error(Bleh). +set_timer(Id) -> + Timers = get_timers(), + erlang:put(timers, lists:usort([Id|Timers])). + +cancel_timer(Id) -> + Timers = get_timers(), + erlang:put(timers, Timers -- [Id]). + +get_timers() -> + case erlang:get(timers) of + undefined -> []; + R -> R + end. + random_n(N, List) -> lists:sublist(shuffle(List), N). diff --git a/test/dkg_worker.erl b/test/dkg_worker.erl index 27e7b35..cdc6ea2 100644 --- a/test/dkg_worker.erl +++ b/test/dkg_worker.erl @@ -94,7 +94,6 @@ dispatch(Other, State) -> do_send([], _) -> ok; do_send([{unicast, Dest, Msg}|T], State) -> - %io:format("~p unicasting ~p to ~p~n", [State#state.id, Msg, global:whereis_name(name(Dest))]), gen_server:cast({global, name(Dest)}, {dkg, State#state.id, Msg}), do_send(T, State); do_send([{multicast, Msg}|T], State) ->