diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d30b5b0..0d3cc5a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,8 +8,8 @@ on: jobs: CI: - runs-on: ubuntu-18.04 - container: heliumsystems/builder-erlang:2 + runs-on: ubuntu-latest + # container: heliumsystems/builder-erlang:2 steps: - name: Checkout @@ -39,17 +39,23 @@ jobs: restore-keys: | ${{ runner.os }}-dialyzer- - - name: Build - run: rebar3 compile + - name: Install stable choolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true - - name: Run tests - run: rebar3 as test do eunit,ct,cover + - name: Build + run: make - name: Run Dialyzer - run: rebar3 do dialyzer #, xref + run: make typecheck + + - name: Run tests + run: make test - name: Generate coverage report - run: rebar3 covertool generate + run: make cover - name: Upload coverage report if: ${{ !env.ACT }} diff --git a/Makefile b/Makefile index a63b297..337a864 100644 --- a/Makefile +++ b/Makefile @@ -9,10 +9,10 @@ clean: $(REBAR) clean cover: - $(REBAR) cover + $(REBAR) covertool generate test: compile - $(REBAR) as test do ct + $(REBAR) as test do eunit,ct typecheck: $(REBAR) dialyzer diff --git a/rebar.config b/rebar.config index d209aac..c36b507 100644 --- a/rebar.config +++ b/rebar.config @@ -22,13 +22,12 @@ ]}. {deps, [ - {erlang_pbc, ".*", {git, "https://github.com/helium/erlang_pbc.git", {branch, "master"}}} + {erlang_tc, ".*", {git, "https://github.com/helium/erlang-tc.git", {branch, "main"}}} ]}. {profiles, [ {test, [ {deps, [ - {erlang_tpke, ".*", {git, "https://github.com/helium/erlang-tpke.git", {branch, "master"}}}, {relcast, ".*", {git, "https://github.com/helium/relcast.git", {branch, "master"}}} ]} ]} diff --git a/rebar.lock b/rebar.lock index e5d4c11..0b05fd0 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,4 +1,4 @@ -[{<<"erlang_pbc">>, - {git,"https://github.com/helium/erlang_pbc.git", - {ref,"1d2651ba01ba81b748c553d9f729c0e167eeab72"}}, +[{<<"erlang_tc">>, + {git,"https://github.com/helium/erlang-tc.git", + {ref,"b3ef1d5541586f5c85b6d231345a921d57be32a3"}}, 0}]. diff --git a/src/dkg.app.src b/src/dkg.app.src index e9216c6..37aa8cb 100644 --- a/src/dkg.app.src +++ b/src/dkg.app.src @@ -5,7 +5,7 @@ {applications, [kernel, stdlib, - erlang_pbc + erlang_tc ]}, {env,[]}, {modules, []}, diff --git a/src/dkg_bipolynomial.erl b/src/dkg_bipolynomial.erl deleted file mode 100644 index 5e4975c..0000000 --- a/src/dkg_bipolynomial.erl +++ /dev/null @@ -1,162 +0,0 @@ --module(dkg_bipolynomial). - --export([generate/2, - generate/3, - add/2, - sub/2, - degree/1, - evaluate/2, - print/1, - cmp/2, - is_zero/1, - lookup/2, - serialize/1, - deserialize/2]). - --record(bipolynomial, { - t :: -1 | non_neg_integer(), - elements :: [erlang_pbc:element()] - }). - --type bipolynomial() :: #bipolynomial{}. - --export_type([bipolynomial/0]). - --spec generate(erlang_pbc:element(), pos_integer()) -> bipolynomial(). -%% generate a bivariate polynomial of degree T -generate(Pairing, T) -> - R = lists:foldl(fun(I, Acc) -> - RandCoeff = erlang_pbc:element_random(erlang_pbc:element_new('Zr', Pairing)), - NewAcc = setelement(I+1, Acc, erlang:append_element(element(I+1, Acc), RandCoeff)), - lists:foldl(fun(J, Acc2) -> - RandCoeff2 = erlang_pbc:element_random(erlang_pbc:element_new('Zr', Pairing)), - setelement(I+1, - setelement(J+1, Acc2, - erlang:append_element(element(J+1, Acc2), RandCoeff2)), - erlang:append_element(element(I+1, Acc2), RandCoeff2)) - end, NewAcc, lists:seq(I+1, T)) - end, list_to_tuple(lists:duplicate(T+1, {})), lists:seq(0, T)), - #bipolynomial{t=T, elements=lists:flatten([ tuple_to_list(E) || E <- tuple_to_list(R)])}. - -%% generate a bivariate polynomial of degree T with a fixed term --spec generate(erlang_pbc:element(), pos_integer(), erlang_pbc:element() | integer()) -> bipolynomial(). -generate(Pairing, T, Term) -> - insert([1, 1], generate(Pairing, T), Term). - --spec add(bipolynomial(), bipolynomial()) -> bipolynomial(). -add(PolyA, PolyB) -> - merge(PolyA, PolyB, fun erlang_pbc:element_add/2). - --spec sub(bipolynomial(), bipolynomial()) -> bipolynomial(). -sub(PolyA, PolyB) -> - merge(PolyA, PolyB, fun erlang_pbc:element_sub/2). - --spec is_zero(bipolynomial()) -> boolean(). -is_zero(#bipolynomial{t=T}) -> - T == 0. - --spec degree(bipolynomial()) -> non_neg_integer(). -degree(#bipolynomial{t=T}) -> - T. - --spec cmp(bipolynomial(), bipolynomial()) -> boolean(). -cmp(PolyA, PolyB) -> - %% check whether degree(PolyA) == degree(PolyB) - %% and all the coefficients should match - degree(PolyA) == degree(PolyB) - andalso - lists:all(fun(X) -> - X - end, - [ erlang_pbc:element_cmp(lookup([I, J], PolyA), lookup([I, J], PolyB)) || I <- lists:seq(1, degree(PolyA)), J <- lists:seq(1, degree(PolyB))]). - --spec evaluate(bipolynomial(), erlang_pbc:element()) -> dkg_polynomial:polynomial(). -evaluate(Poly, X) -> - PolyX = [X], %% polynomial has degree 0 - Result = [], %% empty result polynomial - %% go in reverse for coefficient rows - lists:foldl(fun(Row, Acc) -> - Temp = dkg_polynomial:mul(Acc, PolyX), - dkg_polynomial:add(Temp, Row) - end, Result, lists:reverse(rows(Poly))). - --spec print(bipolynomial()) -> any(). -print(Poly) -> - list_to_tuple(lists:map(fun(R) -> - list_to_tuple([ erlang_pbc:element_to_string(X) || X <- R]) - end, rows(Poly))). - --spec merge(bipolynomial(), bipolynomial(), fun()) -> bipolynomial(). -merge(PolyA, PolyB, MergeFun) -> - Degree = max(degree(PolyA), degree(PolyB)), - %% find the bigger term - [LargerPoly|_] = lists:sort(fun(A, B) -> degree(A) > degree(B) end, [PolyA, PolyB]), - %% why can't we just use set0 here? - Zero = erlang_pbc:element_add(lookup([1,1], LargerPoly), erlang_pbc:element_neg(lookup([1,1], LargerPoly))), - - %% make sure both matrices are the same size - ExpandedPolyA = expand(PolyA, Degree, Zero), - ExpandedPolyB = expand(PolyB, Degree, Zero), - - %% run the merge function on every matrix element - %% use a cartesian product to simplify the iteration - MergedPoly = lists:foldl(fun({Row, Col}, Acc) -> - insert([Row, Col], Acc, MergeFun(lookup([Row, Col], ExpandedPolyA), lookup([Row, Col], ExpandedPolyB))) - end, ExpandedPolyA, [ {R, C} || R <- lists:seq(1, Degree+1), C <- lists:seq(1, Degree+1)]), - - %% trim any leading coefficients that are 0 - %% and delete any rows at the end that are all 0s - prune(MergedPoly). - --spec expand(bipolynomial(), non_neg_integer(), erlang_pbc:element()) -> bipolynomial(). -expand(Poly, Degree, Padding) -> - case degree(Poly) == Degree of - true -> - Poly; - false -> - ExtraRows = lists:duplicate((Degree+1 - degree(Poly)+1)*(Degree+1), Padding), - Elements = lists:flatten([ R ++ lists:duplicate(Degree - degree(Poly), Padding) || R <- rows(Poly) ] ++ ExtraRows), - #bipolynomial{t=Degree, elements=Elements} - end. - -prune(Poly = #bipolynomial{t=T}) -> - %% we need to find the minimum degree needed to represent this polynomial - %% essentially this means how many rows and columns can we prune of zeros - %% while keeping the matrix square - Width = lists:foldl(fun(Row, MaxWidth) -> - W = length(lists:dropwhile(fun erlang_pbc:element_is0/1, lists:reverse(Row))), - max(W, MaxWidth) - end, 0, rows(Poly)), - %% find how many trailing rows are empty and use that to calculate the minimum height - Height = T+1 - length(lists:takewhile(fun(Row) -> - lists:all(fun erlang_pbc:element_is0/1, Row) - end, lists:reverse(rows(Poly)))), - - NewDimension = max(Height, Width), - Rows = lists:sublist(rows(Poly), NewDimension), - Elements = lists:map(fun(Row) -> - lists:sublist(Row, NewDimension) - end, Rows), - #bipolynomial{t=NewDimension-1, elements=lists:flatten(Elements)}. - -lookup([Row, Col], #bipolynomial{t=T, elements=Elements}) -> - lists:nth(((Row-1)*(T+1)) + Col, Elements). - -insert([Row, Col], BiPoly = #bipolynomial{t=T, elements=Elements}, Val) -> - {Head, [_|Tail]} = lists:split(((Row-1)*(T+1)) + Col - 1, Elements), - BiPoly#bipolynomial{elements = Head ++ [Val | Tail]}. - -serialize(#bipolynomial{t=T, elements=Elements}) -> - {T, erlang_pbc:elements_to_binary(Elements)}. - -deserialize({T, Binary}, Element) -> - #bipolynomial{t=T, elements=erlang_pbc:binary_to_elements(Element, Binary)}. - -rows(#bipolynomial{t=T, elements=Elements}) -> - rows(T, Elements, []). - -rows(_, [], Acc) -> - lists:reverse(Acc); -rows(T, Elements, Acc) -> - {Row, Rest} = lists:split(T+1, Elements), - rows(T, Rest, [Row|Acc]). diff --git a/src/dkg_commitment.erl b/src/dkg_commitment.erl index 1f9b9af..82cecaf 100644 --- a/src/dkg_commitment.erl +++ b/src/dkg_commitment.erl @@ -1,10 +1,11 @@ -module(dkg_commitment). --export([new/3, +-export([new/4, + new/3, cmp/2, - mul/2, verify_poly/3, public_key_share/2, + public_key_set/1, verify_point/4, interpolate/3, add_echo/3, @@ -15,25 +16,22 @@ echoes/1, ready_proofs/1, matrix/1, - set_matrix/2, - binary_matrix/1, + hash/1, serialize/1, deserialize/2 ]). -record(commitment, { - matrix :: dkg_commitmentmatrix:matrix(), - binary_matrix :: binary(), - generator :: erlang_pbc:element(), + commitment :: binary(), nodes = [] :: [pos_integer()], echoes = #{} :: echoes(), readies =#{} :: readies(), - proofs =#{} :: ready_proofs() + proofs =#{} :: ready_proofs(), + commitment_cache_fun :: fun((binary() | {binary(), tc_bicommitment:bicommitment()}) -> tc_bicommitment:bicommitment() | ok) }). -record(serialized_commitment, { - binary_matrix :: binary(), - generator :: binary(), + commitment :: binary(), nodes = [] :: [pos_integer()], echoes = #{} :: #{pos_integer() => binary()}, readies = #{} :: #{pos_integer() => binary()}, @@ -41,67 +39,65 @@ }). -type commitment() :: #commitment{}. --type echoes() :: #{pos_integer() => erlang_pbc:element()}. --type readies() :: #{pos_integer() => erlang_pbc:element()}. +-type echoes() :: #{pos_integer() => tc_fr:fr()}. +-type readies() :: #{pos_integer() => tc_fr:fr()}. -type serialized_commitment() :: #serialized_commitment{}. -type ready_proofs() :: #{pos_integer() => binary()}. -export_type([commitment/0, ready_proofs/0, serialized_commitment/0]). --spec new([pos_integer(),...], erlang_pbc:element(), integer() | dkg_bipolynomial:bipolynomial()) -> commitment(). -new(NodeIDs, Generator, Degree) when is_integer(Degree) -> - Matrix = dkg_commitmentmatrix:new(Generator, Degree), - #commitment{nodes=NodeIDs, matrix=Matrix, binary_matrix=dkg_commitmentmatrix:serialize(Matrix), generator=Generator}; -new(NodeIDs, Generator, BiPoly) -> - Matrix = dkg_commitmentmatrix:new(Generator, BiPoly), - #commitment{nodes=NodeIDs, matrix=Matrix, binary_matrix=dkg_commitmentmatrix:serialize(Matrix), generator=Generator}. +-spec new([pos_integer(),...], tc_bicommitment:bicommitment(), binary(), fun()) -> commitment(). +new(NodeIDs, Commitment, SerializedCommitment, CacheFun) -> + %% seed the cache since we have both here + ok = CacheFun({SerializedCommitment, Commitment}), + #commitment{nodes=NodeIDs, commitment=SerializedCommitment, commitment_cache_fun=CacheFun}. + +-spec new([pos_integer(),...], tc_bicommitment:bicommitment(), fun()) -> commitment(). +new(NodeIDs, Commitment, CacheFun) -> + SerializedCommitment = tc_bicommitment:serialize(Commitment), + %% seed the cache since we have both here + ok = CacheFun({SerializedCommitment, Commitment}), + #commitment{nodes=NodeIDs, commitment=SerializedCommitment, commitment_cache_fun=CacheFun}. -spec cmp(commitment(), commitment()) -> boolean(). cmp(CommitmentA, CommitmentB) -> - dkg_commitmentmatrix:cmp(CommitmentA#commitment.matrix, CommitmentB#commitment.matrix). - --spec mul(commitment(), commitment()) -> commitment(). -mul(CommitmentA, CommitmentB) -> - NewMatrix = dkg_commitmentmatrix:mul(CommitmentA#commitment.matrix, CommitmentB#commitment.matrix), - CommitmentA#commitment{matrix=NewMatrix}. + CommitmentA#commitment.commitment == CommitmentB#commitment.commitment. --spec verify_poly(commitment(), pos_integer(), dkg_polynomial:polynomial()) -> boolean(). +-spec verify_poly(commitment(), pos_integer(), tc_poly:poly()) -> boolean(). verify_poly(Commitment, VerifierID, Poly) -> - dkg_commitmentmatrix:verify_poly(Commitment#commitment.generator, Commitment#commitment.matrix, VerifierID, Poly). + tc_bicommitment:verify_poly(gc(Commitment), Poly, VerifierID). --spec public_key_share(commitment(), pos_integer()) -> erlang_pbc:element(). +-spec public_key_share(commitment(), pos_integer()) -> tc_public_key_share:pk_share(). public_key_share(Commitment, NodeID) -> - dkg_commitmentmatrix:public_key_share(Commitment#commitment.generator, Commitment#commitment.matrix, NodeID). + tc_public_key_set:public_key_share(tc_public_key_set:from_commitment(tc_bicommitment:row(gc(Commitment), 0)), NodeID-1). --spec verify_point(commitment(), pos_integer(), pos_integer(), erlang_pbc:element()) -> boolean(). +-spec public_key_set(commitment()) -> tc_public_key_set:pk_set(). +public_key_set(Commitment) -> + tc_public_key_set:from_commitment(tc_bicommitment:row(gc(Commitment), 0)). + +-spec verify_point(commitment(), pos_integer(), pos_integer(), binary()) -> boolean(). verify_point(Commitment, SenderID, VerifierID, Point) -> - dkg_commitmentmatrix:verify_point(Commitment#commitment.generator, Commitment#commitment.matrix, SenderID, VerifierID, Point). + case maps:get(SenderID, Commitment#commitment.echoes, undefined) == Point orelse + maps:get(SenderID, Commitment#commitment.readies, undefined) == Point of + true -> + true; + false -> + tc_bicommitment:validate_point(gc(Commitment), SenderID, VerifierID, tc_fr:deserialize(Point)) + end. --spec interpolate(commitment(), echo | ready, [pos_integer()]) -> [erlang_pbc:element()]. -interpolate(Commitment, EchoOrReady, ActiveNodeIDs) -> +-spec interpolate(commitment(), pos_integer(), echo | ready) -> tc_poly:poly(). +interpolate(Commitment, T, EchoOrReady) -> Map = case EchoOrReady of echo -> Commitment#commitment.echoes; ready -> Commitment#commitment.readies end, - {Indices0, Elements} = lists:unzip(maps:to_list(Map)), - %% turn the node IDs into PBC elements - Indices = [ erlang_pbc:element_set(erlang_pbc:element_new('Zr', hd(Elements)), I) || I <- Indices0 ], - Shares = lists:foldl(fun(Index, Acc) -> - case maps:is_key(Index, Map) of - false -> - %% Node ${Index} has not sent us a share, interpolate it - Alpha = erlang_pbc:element_set(erlang_pbc:element_new('Zr', hd(Elements)), Index), - LagrangePoly = dkg_lagrange:coefficients(Indices, Alpha), - InterpolatedShare = dkg_lagrange:evaluate_zr(LagrangePoly, Elements), - [ InterpolatedShare | Acc]; - true -> - %% Node ${Index} has sent us a share - [ maps:get(Index, Map) | Acc] - end - end, [], [0 | ActiveNodeIDs]), %% note that we also evaluate at 0 - lists:reverse(Shares). - --spec add_echo(commitment(), pos_integer(), erlang_pbc:element()) -> {true | false, commitment()}. + Received = [ + {tc_fr:into(Index), tc_fr:deserialize(Val)} + || {Index, Val} <- lists:sublist(maps:to_list(Map), T+1) + ], + tc_poly:interpolate_from_fr(Received). + +-spec add_echo(commitment(), pos_integer(), tc_fr:fr()) -> {true | false, commitment()}. add_echo(Commitment = #commitment{nodes=Nodes, echoes=Echoes}, NodeID, Echo) when NodeID /= 0 -> case lists:member(NodeID, Nodes) of true -> @@ -116,7 +112,7 @@ add_echo(Commitment = #commitment{nodes=Nodes, echoes=Echoes}, NodeID, Echo) whe {false, Commitment} end. --spec add_ready(commitment(), pos_integer(), erlang_pbc:element()) -> {true | false, commitment()}. +-spec add_ready(commitment(), pos_integer(), tc_fr:fr()) -> {true | false, commitment()}. add_ready(Commitment = #commitment{nodes=Nodes, readies=Readies}, NodeID, Ready) when NodeID /= 0 -> case lists:member(NodeID, Nodes) of true -> @@ -160,44 +156,42 @@ echoes(#commitment{echoes=Echoes}) -> -spec ready_proofs(commitment()) -> ready_proofs(). ready_proofs(#commitment{proofs=Proofs}) -> + Proofs; +ready_proofs(#serialized_commitment{proofs=Proofs}) -> Proofs. --spec matrix(commitment()) -> dkg_commitmentmatrix:matrix(). -matrix(#commitment{matrix=Matrix}) -> - Matrix. - --spec set_matrix(commitment(), dkg_commitmentmatrix:matrix()) -> commitment(). -set_matrix(C = #commitment{}, Matrix) -> - C#commitment{matrix=Matrix, binary_matrix=dkg_commitmentmatrix:serialize(Matrix)}. +-spec matrix(commitment()) -> binary(). +matrix(#commitment{commitment=Commitment}) -> + Commitment. -binary_matrix(#commitment{binary_matrix=B}) -> - B. +hash(#commitment{commitment=Commitment}) -> + erlang:phash2(Commitment). -spec serialize(commitment()) -> serialized_commitment(). -serialize(#commitment{binary_matrix=BinMatrix, - generator=Generator, +serialize(#commitment{commitment=Commitment, nodes=Nodes, echoes=Echoes, readies=Readies, proofs=Proofs}) -> - #serialized_commitment{binary_matrix=BinMatrix, - generator=erlang_pbc:element_to_binary(Generator), + #serialized_commitment{commitment=Commitment, nodes=Nodes, - echoes=maps:map(fun(_K, V) -> erlang_pbc:element_to_binary(V) end, Echoes), - readies=maps:map(fun(_K, V) -> erlang_pbc:element_to_binary(V) end, Readies), + echoes=Echoes, + readies=Readies, proofs=Proofs}. --spec deserialize(serialized_commitment(), erlang_pbc:element()) -> commitment(). -deserialize(#serialized_commitment{binary_matrix=SerializedMatrix, - generator=SerializedGenerator, +-spec deserialize(serialized_commitment(), fun()) -> commitment(). +deserialize(#serialized_commitment{commitment=Commitment, nodes=Nodes, echoes=Echoes, readies=Readies, - proofs=Proofs}, U) -> - #commitment{matrix=dkg_commitmentmatrix:deserialize(SerializedMatrix, U), - binary_matrix=SerializedMatrix, - generator=erlang_pbc:binary_to_element(U, SerializedGenerator), + proofs=Proofs}, CCacheFun) -> + #commitment{commitment=Commitment, + commitment_cache_fun=CCacheFun, nodes=Nodes, proofs=Proofs, - echoes=maps:map(fun(_K, V) -> erlang_pbc:binary_to_element(U, V) end, Echoes), - readies=maps:map(fun(_K, V) -> erlang_pbc:binary_to_element(U, V) end, Readies)}. + echoes=Echoes, + readies=Readies}. + +gc(#commitment{commitment=Commitment, commitment_cache_fun=Fun}) -> + Fun(Commitment). + diff --git a/src/dkg_commitmentmatrix.erl b/src/dkg_commitmentmatrix.erl deleted file mode 100644 index b1ab473..0000000 --- a/src/dkg_commitmentmatrix.erl +++ /dev/null @@ -1,133 +0,0 @@ --module(dkg_commitmentmatrix). - --export([new/2, - lookup/2, - print/1, - cmp/2, - mul/2, - verify_poly/4, - verify_point/5, - public_key_share/3, - serialize/1, - deserialize/2 - ]). - --record(commitmentmatrix, { - t :: -1 | non_neg_integer(), - elements :: [erlang_pbc:element()] - }). - --type matrix() :: #commitmentmatrix{}. --type serialized_matrix() :: binary(). - --export_type([matrix/0, serialized_matrix/0]). - --spec new(erlang_pbc:element(), integer() | dkg_bipolynomial:bipolynomial()) -> matrix(). -new(Generator, T) when is_integer(T) -> - %% generate an empty commitment matrix of degree T - One = erlang_pbc:element_set(Generator, 1), - Elements = lists:flatten(lists:foldl(fun(_, Acc) -> - [lists:duplicate(T+1, One)| Acc] - end, [], lists:seq(0, T))), - #commitmentmatrix{t=T, elements=Elements}; -new(Generator, BiPoly) when is_tuple(BiPoly) -> - T = dkg_bipolynomial:degree(BiPoly), - erlang_pbc:element_pp_init(Generator), - Elements = lists:flatten([ [ erlang_pbc:element_pow(Generator, dkg_bipolynomial:lookup([I+1, J+1], BiPoly)) || J <- lists:seq(0, T) ] || I <- lists:seq(0, T) ]), - #commitmentmatrix{t=T, elements=Elements}. - -lookup([Row, Col], #commitmentmatrix{t=T, elements=Elements}) -> - lists:nth(((Row-1)*(T+1)) + Col, Elements). - -print(Matrix) -> - list_to_tuple(lists:map(fun(R) -> - list_to_tuple([ erlang_pbc:element_to_string(X) || X <- R]) - end, rows(Matrix))). - --spec cmp(matrix(), matrix()) -> boolean(). -cmp(MatrixA, MatrixB) -> - lists:all(fun({I, J}) -> - erlang_pbc:element_cmp(I, J) - end, lists:zip(MatrixA#commitmentmatrix.elements, MatrixB#commitmentmatrix.elements)). - --spec mul(matrix(), matrix()) -> matrix(). -mul(MatrixA, MatrixB) -> - %% Here each entry is multiplied with corresponding entry in the other matrix - %% This is not normal matrix multiplication - %% It is assumed that both matrices are the same size - - %% run the merge function on every matrix element - %% use a cartesian product to simplify the iteration - Elements = lists:map(fun({A, B}) -> - erlang_pbc:element_mul(A, B) - end, lists:zip(MatrixA#commitmentmatrix.elements, MatrixB#commitmentmatrix.elements)), - #commitmentmatrix{t=MatrixA#commitmentmatrix.t, elements=Elements}. - --spec verify_poly(erlang_pbc:element(), matrix(), non_neg_integer(), dkg_polynomial:polynomial()) -> boolean(). -verify_poly(U, Matrix, VerifierID, Poly) -> - %% TODO obviously use something appropriate here - I = erlang_pbc:element_set(hd(Poly), VerifierID), - - lists:foldl(fun(_L, false) -> - false; - ({Row, PE}, _Acc) -> - E1 = erlang_pbc:element_pow(U, PE), - E2 = lists:foldl(fun(J, Acc2) -> - erlang_pbc:element_mul(erlang_pbc:element_pow(Acc2, I), J) - end, erlang_pbc:element_set(U, 1), lists:reverse(Row)), - erlang_pbc:element_cmp(E1, E2) - end, true, lists:zip(rows(Matrix), Poly)). - --spec verify_point(erlang_pbc:element(), matrix(), non_neg_integer(), non_neg_integer(), erlang_pbc:element()) -> boolean(). -verify_point(U, Matrix, SenderID, VerifierID, Point) -> - M = erlang_pbc:element_set(Point, SenderID), - I = erlang_pbc:element_set(Point, VerifierID), - G1 = erlang_pbc:element_set(U, 1), - erlang_pbc:element_pp_init(G1), - - Ga = erlang_pbc:element_pow(U, Point), - Res = lists:foldl(fun(Row, Acc) -> - R = erlang_pbc:element_pow(Acc, M), - RowTotal = lists:foldl(fun(J, Acc2) -> - erlang_pbc:element_mul(erlang_pbc:element_pow(Acc2, I), J) - end, G1, lists:reverse(Row)), - erlang_pbc:element_mul(R, RowTotal) - end, G1, lists:reverse(rows(Matrix))), - erlang_pbc:element_cmp(Ga, Res). - --spec public_key_share(erlang_pbc:element(), matrix(), non_neg_integer()) -> erlang_pbc:element(). -public_key_share(U, Matrix, NodeID) -> - %% TODO this shares significant code with verify_point, consider refactoring them to share common code - M = erlang_pbc:element_set(erlang_pbc:element_new('Zr', U), NodeID), - I = erlang_pbc:element_set(erlang_pbc:element_new('Zr', U), 0), - G1 = erlang_pbc:element_set(U, 1), - erlang_pbc:element_pp_init(G1), - - %% return the public key share - %% NOTE: C++ traverses the matrix in reverse, following the same - lists:foldl(fun(Row, Acc) -> - R = erlang_pbc:element_pow(Acc, M), - RowTotal = lists:foldl(fun(J, Acc2) -> - erlang_pbc:element_mul(erlang_pbc:element_pow(Acc2, I), J) - end, G1, lists:reverse(Row)), - erlang_pbc:element_mul(R, RowTotal) - end, G1, lists:reverse(rows(Matrix))). - --spec serialize(matrix()) -> serialized_matrix(). -serialize(#commitmentmatrix{t=T, elements=Elements}) -> - BinElements = erlang_pbc:elements_to_binary(Elements), - <>. - --spec deserialize(serialized_matrix(), erlang_pbc:element()) -> matrix(). -deserialize(<>, U) -> - Elements = erlang_pbc:binary_to_elements(U, BinElements), - #commitmentmatrix{t=T, elements=Elements}. - -rows(#commitmentmatrix{t=T, elements=Elements}) -> - rows(T, Elements, []). - -rows(_, [], Acc) -> - lists:reverse(Acc); -rows(T, Elements, Acc) -> - {Row, Rest} = lists:split(T+1, Elements), - rows(T, Rest, [Row|Acc]). diff --git a/src/dkg_hybriddkg.erl b/src/dkg_hybriddkg.erl index 35c7839..7a2ee51 100644 --- a/src/dkg_hybriddkg.erl +++ b/src/dkg_hybriddkg.erl @@ -1,11 +1,13 @@ -module(dkg_hybriddkg). --export([init/8, +-export([init/6, input/2, % for testing start/1, serialize/1, + deserialize/3, deserialize/4, - status/1 + status/1, + default_commitment_cache_fun/1 ]). -export([handle_msg/3]). @@ -15,8 +17,6 @@ n :: pos_integer(), f :: pos_integer(), t :: pos_integer(), - u :: erlang_pbc:element(), - u2 :: erlang_pbc:element(), shares_map = #{} :: shares_map(), shares_results = #{} :: shares_results(), shares_seen = [] :: node_set(), @@ -30,7 +30,8 @@ leader_changing = false :: boolean(), leader_vote_counts = #{} :: leader_vote_counts(), await_vss = false :: boolean(), - elections_allowed = false :: boolean() + elections_allowed = false :: boolean(), + commitment_cache_fun :: fun() }). -type node_set() :: [pos_integer()]. @@ -47,8 +48,7 @@ -type leader_vote_counts() :: #{Leader :: pos_integer() => [{Sender :: pos_integer(), signed_leader_change()}]}. -type shares_map() :: #{pos_integer() => dkg_hybridvss:vss()}. -type serialized_shares_map() :: #{pos_integer() => #{atom() => binary() | map()}}. --type shares_results() :: #{pos_integer() => {C :: dkg_commitment:commitment(), Si :: erlang_pbc:element()}}. --type serialized_shares_results() :: #{pos_integer() => {C :: dkg_commitment:serialized_commitment(), Si :: binary()}}. +-type shares_results() :: #{pos_integer() => {C :: dkg_commitment:commitment(), Si :: tc_fr:fr()}}. -type dkg() :: #dkg{}. -export_type([dkg/0]). @@ -73,13 +73,10 @@ %% for all d ∈ [1, n] do %% initialize extended-HybridVSS Sh protocol (Pd, τ ) -spec init(Id :: pos_integer(), N :: pos_integer(), F :: pos_integer(), T :: non_neg_integer(), - G1 :: erlang_pbc:element(), G2 :: erlang_pbc:element(), Round :: binary(), Options :: [{atom(), term()} | atom]) -> #dkg{}. -init(Id, N, F, T, G1, G2, Round, Options) -> +init(Id, N, F, T, Round, Options) -> true = N >= (3*T + 2*F + 1), - erlang_pbc:element_pp_init(G1), - erlang_pbc:element_pp_init(G2), Callback = proplists:get_value(callback, Options, false), %% XXX Don't allow elections by default. We don't have signed proofs they should %% occur so it's not safe to enable this in a non development context. @@ -87,8 +84,9 @@ init(Id, N, F, T, G1, G2, Round, Options) -> %% these will cause an exception if not present in the options list {signfun, SignFun} = lists:keyfind(signfun, 1, Options), {verifyfun, VerifyFun} = lists:keyfind(verifyfun, 1, Options), + CCacheFun = proplists:get_value(commitment_cache_fun, Options, fun default_commitment_cache_fun/1), Shares = lists:foldl(fun(E, Map) -> - Share = dkg_hybridvss:init(Id, N, F, T, G1, G2, {E, Round}, Callback, SignFun, VerifyFun), + Share = dkg_hybridvss:init(Id, N, F, T, {E, Round}, Callback, SignFun, VerifyFun, CCacheFun), Map#{E => Share} end, #{}, dkg_util:allnodes(N)), @@ -96,12 +94,11 @@ init(Id, N, F, T, G1, G2, Round, Options) -> n = N, f = F, t = T, - u = G1, - u2 = G2, leader = 1, leader_cap = leader_cap(1, N), shares_map = Shares, - elections_allowed = Elections + elections_allowed = Elections, + commitment_cache_fun=CCacheFun }. %% the dgk starts to go on its own, this is here for the sake of @@ -109,9 +106,9 @@ init(Id, N, F, T, G1, G2, Round, Options) -> input(State, _) -> start(State). -start(DKG = #dkg{id=Id, u=G1}) -> +start(DKG = #dkg{id=Id}) -> #{Id := MyShares} = SharesMap = DKG#dkg.shares_map, - Secret = erlang_pbc:element_random(erlang_pbc:element_new('Zr', G1)), + Secret = rand:uniform(trunc(math:pow(2, 64))), {NewShares, {send, ToSend}} = dkg_hybridvss:input(MyShares, Secret), {DKG#dkg{shares_map = SharesMap#{Id => NewShares}}, {send, dkg_util:wrap({vss, Id}, ToSend)}}. @@ -129,13 +126,13 @@ handle_msg(DKG=#dkg{await_vss = true}, Sender, {{vss, SharesId}, SharesMsg}) -> {send, dkg_util:wrap({vss, SharesId}, ToSend)}}; {NewShares, {result, {_Session, Commitment, Si}}} -> NewDKG = DKG#dkg{shares_map = maps:put(SharesId, NewShares, DKG#dkg.shares_map), - shares_results = maps:put(SharesId, {Commitment, Si}, DKG#dkg.shares_results), + shares_results = maps:put(SharesId, {dkg_commitment:serialize(Commitment), tc_fr:serialize(Si)}, DKG#dkg.shares_results), shares_seen = [SharesId | DKG#dkg.shares_seen] }, case output_ready(NewDKG, NewDKG#dkg.shares_acked) of true -> - {Shard, VerificationKey, PublicKeyShares} = output(NewDKG, NewDKG#dkg.shares_acked), - {NewDKG, {result, {Shard, VerificationKey, PublicKeyShares}}}; + KeyShare = output(NewDKG, NewDKG#dkg.shares_acked), + {NewDKG, {result, KeyShare}}; false -> {NewDKG, ok} end @@ -163,7 +160,7 @@ handle_msg(DKG=#dkg{leader = Leader}, Sender, {{vss, SharesId}, SharesMsg}) -> %% delay ← delay(T); start timer(delay) NewDKG = DKG#dkg{shares_map = maps:put(SharesId, NewShares, DKG#dkg.shares_map), - shares_results = maps:put(SharesId, {Commitment, Si}, DKG#dkg.shares_results), + shares_results = maps:put(SharesId, {dkg_commitment:serialize(Commitment), tc_fr:serialize(Si)}, DKG#dkg.shares_results), shares_seen = [SharesId | DKG#dkg.shares_seen] }, case length(NewDKG#dkg.shares_seen) == NewDKG#dkg.t + 1 andalso length(NewDKG#dkg.shares_acked) == 0 of @@ -258,8 +255,8 @@ handle_msg(DKG = #dkg{id=_Id, n=N, t=T, f=F}, Sender, {signed_ready, Shares}=Rea %% TODO stop timer case output_ready(NewDKG, Shares) of true -> - {Shard, VerificationKey, PublicKeyShares} = output(NewDKG, Shares), - {NewDKG, {result, {Shard, VerificationKey, PublicKeyShares}}}; + KeyShare = output(NewDKG, Shares), + {NewDKG, {result, KeyShare}}; false -> {NewDKG#dkg{await_vss = true}, ok} end; @@ -396,34 +393,23 @@ output_ready(#dkg{shares_results = R0}, Shares) -> maps:size(R) == length(Shares). output(DKG, Shares) -> - OutputCommitment = output_commitment(DKG, Shares), - PublicKeyShares = public_key_shares(DKG, OutputCommitment), - VerificationKey = verification_key(OutputCommitment), + PublicKeySet = output_public_key_set(DKG, Shares), Shard = shard(DKG, Shares), - {Shard, VerificationKey, PublicKeyShares}. - --spec output_commitment(#dkg{}, node_set()) -> dkg_commitment:commitment(). -output_commitment(_DKG=#dkg{shares_results=R0, u2=U2, t=T, n=N}, Shares) -> - R = maps:with(Shares, R0), - 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(#dkg{}, node_set()) -> erlang_pbc:element(). -shard(_DKG=#dkg{shares_results=R0, u=U}, Shares) -> - R = maps:with(Shares, R0), - 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). + tc_key_share:new(DKG#dkg.id - 1, PublicKeySet, Shard). + +-spec output_public_key_set(#dkg{}, node_set()) -> tc_public_key_set:pk_set(). +output_public_key_set(DKG=#dkg{shares_results=R0}, Shares) -> + {[Head|Commitments], _Shares} = lists:unzip(maps:values(maps:with(Shares, R0))), + lists:foldl(fun(Commitment, Acc) -> + tc_public_key_set:combine(Acc, dkg_commitment:public_key_set(dkg_commitment:deserialize(Commitment, DKG#dkg.commitment_cache_fun))) + end, dkg_commitment:public_key_set(dkg_commitment:deserialize(Head, DKG#dkg.commitment_cache_fun)), Commitments). + +-spec shard(#dkg{}, node_set()) -> tc_secret_key_share:sk_share(). +shard(_DKG=#dkg{shares_results=R0}, Shares) -> + {_Commitments, [Head|Keys]} = lists:unzip(maps:values(maps:with(Shares, R0))), + lists:foldl(fun(Si, Acc) -> + tc_secret_key_share:combine(Acc, tc_secret_key_share:from_fr(tc_fr:deserialize(Si))) + end, tc_secret_key_share:from_fr(tc_fr:deserialize(Head)), Keys). -spec leader_cap(pos_integer(), pos_integer()) -> pos_integer(). leader_cap(L, N) -> @@ -521,8 +507,6 @@ serialize(#dkg{id = Id, n = N, f = F, t = T, - u = U, - u2 = U2, shares_map = SharesMap, shares_results = SharesResults, shares_acked = SharesAcked, @@ -536,15 +520,13 @@ serialize(#dkg{id = Id, leader_cap = LeaderCap, leader_vote_counts = LeaderVoteCounts, await_vss = AwaitVSS}) -> - PreSer = #{u => erlang_pbc:element_to_binary(U), - u2 => erlang_pbc:element_to_binary(U2), - shares_map => serialize_shares_map(SharesMap), - shares_results => serialize_shares_results(SharesResults)}, + PreSer = #{shares_map => serialize_shares_map(SharesMap)}, M0 = #{id => Id, n => N, f => F, t => T, shares_seen => SharesSeen, + shares_results => SharesResults, shares_acked => SharesAcked, vss_ready_proofs => ReadyProofs, echo_proofs => Echo_Proofs, @@ -558,11 +540,13 @@ serialize(#dkg{id = Id, M = maps:map(fun(_K, Term) -> term_to_binary(Term) end, M0), maps:merge(PreSer, M). --spec deserialize(#{}, erlang_pbc:element(), fun(), fun()) -> dkg(). -deserialize(Map0, Element, SignFun, VerifyFun) when is_map(Map0) -> - Map = maps:map(fun(K, V) when K == u; K == u2; - K == shares_map; - K == shares_results -> +-spec deserialize(#{}, fun(), fun()) -> dkg(). +deserialize(Map0, SignFun, VerifyFun) when is_map(Map0) -> + deserialize(Map0, SignFun, VerifyFun, fun default_commitment_cache_fun/1). + +-spec deserialize(#{}, fun(), fun(), fun()) -> dkg(). +deserialize(Map0, SignFun, VerifyFun, CCacheFun) when is_map(Map0) -> + Map = maps:map(fun(K, V) when K == shares_map -> V; (_K, B) -> binary_to_term(B) @@ -571,10 +555,8 @@ deserialize(Map0, Element, SignFun, VerifyFun) when is_map(Map0) -> n := N, f := F, t := T, - u := SerializedU, - u2 := SerializedU2, shares_map := SerializedSharesMap, - shares_results := SerializedSharesResults, + shares_results := SharesResults, shares_acked := SharesAcked, shares_seen := SharesSeen, vss_ready_proofs := ReadyProofs, @@ -592,10 +574,8 @@ deserialize(Map0, Element, SignFun, VerifyFun) when is_map(Map0) -> n = N, f = F, t = T, - u = erlang_pbc:binary_to_element(Element, SerializedU), - u2 = erlang_pbc:binary_to_element(Element, SerializedU2), - shares_map = deserialize_shares_map(SerializedSharesMap, Element, SignFun, VerifyFun), - shares_results = deserialize_shares_results(SerializedSharesResults, Element), + shares_map = deserialize_shares_map(SerializedSharesMap, SignFun, VerifyFun, CCacheFun), + shares_results = SharesResults, shares_acked = SharesAcked, shares_seen = SharesSeen, vss_ready_proofs = ReadyProofs, @@ -606,7 +586,8 @@ deserialize(Map0, Element, SignFun, VerifyFun) when is_map(Map0) -> leader = Leader, leader_cap = LeaderCap, leader_vote_counts = LeaderVoteCounts, - await_vss = AwaitVSS}. + await_vss = AwaitVSS, + commitment_cache_fun=CCacheFun}. -spec serialize_shares_map(shares_map()) -> serialized_shares_map(). serialize_shares_map(SharesMap) -> @@ -615,11 +596,11 @@ serialize_shares_map(SharesMap) -> maps:put(Name, dkg_hybridvss:serialize(Shares), Acc) end, #{}, SharesMap). --spec deserialize_shares_map(serialized_shares_map(), erlang_pbc:element(), fun(), fun()) -> shares_map(). -deserialize_shares_map(SerializedSharesMap, Element, SignFun, VerifyFun) -> +-spec deserialize_shares_map(serialized_shares_map(), fun(), fun(), fun()) -> shares_map(). +deserialize_shares_map(SerializedSharesMap, SignFun, VerifyFun, CCacheFun) -> maps:fold(fun(K, Shares, Acc) -> Name = shares_name(K), - maps:put(Name, dkg_hybridvss:deserialize(Shares, Element, SignFun, VerifyFun), Acc) + maps:put(Name, dkg_hybridvss:deserialize(Shares, SignFun, VerifyFun, CCacheFun), Acc) end, #{}, SerializedSharesMap). shares_name(N) when is_integer(N) -> @@ -629,33 +610,6 @@ shares_name(Key) when is_atom(Key) -> "share_" ++ Int = L, list_to_integer(Int). --spec serialize_shares_results(shares_results()) -> serialized_shares_results(). -serialize_shares_results(SharesResults) -> - maps:fold(fun(K, {C, Si}, Acc) -> - Name = result_name(K), - Ser = term_to_binary({dkg_commitment:serialize(C), - erlang_pbc:element_to_binary(Si)}, - [compressed]), - maps:put(Name, Ser, Acc) - end, #{}, SharesResults). - --spec deserialize_shares_results(serialized_shares_results(), erlang_pbc:element()) -> - shares_results(). -deserialize_shares_results(SerializedSharesResults, U) -> - maps:fold(fun(K, Bin, Acc) -> - Name = result_name(K), - {C, Si} = binary_to_term(Bin), - maps:put(Name, {dkg_commitment:deserialize(C, U), - erlang_pbc:binary_to_element(U, Si)}, Acc) - end, #{}, SerializedSharesResults). - -result_name(N) when is_integer(N) -> - list_to_atom("result_" ++ integer_to_list(N)); -result_name(Key) when is_atom(Key) -> - L = atom_to_list(Key), - "result_" ++ Int = L, - list_to_integer(Int). - is_chosen_vss(_, #dkg{elections_allowed=true}) -> %% never optimize these VSSes away if we do elections true; @@ -717,3 +671,6 @@ status(DKG) -> leader_changing => DKG#dkg.leader_changing, leader => DKG#dkg.leader, leader_cap => DKG#dkg.leader_cap}. + +default_commitment_cache_fun({_Ser, _DeSer}) -> ok; +default_commitment_cache_fun(Ser) -> tc_bicommitment:deserialize(Ser). diff --git a/src/dkg_hybridvss.erl b/src/dkg_hybridvss.erl index e8d55e3..acee3e4 100644 --- a/src/dkg_hybridvss.erl +++ b/src/dkg_hybridvss.erl @@ -1,6 +1,6 @@ -module(dkg_hybridvss). --export([init/10]). +-export([init/8, init/9]). -export([input/2, serialize/1, @@ -17,8 +17,6 @@ n :: pos_integer(), f :: pos_integer(), t :: pos_integer(), - u :: erlang_pbc:element(), - u2 :: erlang_pbc:element(), session :: session(), received_commitment = false :: boolean(), commitments = #{} :: #{binary() => dkg_commitment:commitment()}, @@ -26,69 +24,70 @@ sent_echoes = [] :: [integer()], sent_readies = [] :: [integer()], signfun :: signfun(), - verifyfun :: verifyfun() + verifyfun :: verifyfun(), + commitments_sent = #{} :: #{integer() => [pos_integer()]}, + commitments_received = #{} :: #{integer() => [pos_integer()]}, + commitment_cache_fun :: fun() }). %% Note that the 'Round' here is assumed to be some unique combination of members and some strictly increasing counter(s) or nonce. %% For example, something like the SHA of the public keys of all the members and some global DKG counter. %% The counter/nonce should NOT repeat under any circumstances or ready messages may be reused to forge subsequent round results. -type session() :: {Dealer :: pos_integer(), Round :: binary()}. --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 send_msg() :: {unicast, pos_integer(), {send, {session(), dkg_commitment:serialized_commitment(), binary()}}}. +-type echo_msg() :: {unicast, pos_integer(), {echo, {session(), dkg_commitment:serialized_commitment(), binary()}}}. +-type ready_msg() :: {unicast, pos_integer(), {ready, {session(), dkg_commitment:serialized_commitment(), binary()}}}. +-type result() :: {result, {session(), dkg_commitment:commitment(), tc_fr:fr()}}. -type vss() :: #vss{}. -type signfun() :: fun((Msg :: binary()) -> Signature :: binary()). -type verifyfun() :: fun((Sender :: pos_integer(), Msg :: binary(), Signature :: binary()) -> boolean()). -export_type([vss/0, session/0]). + +init(Id, N, F, T, Session, Callback, SignFun, VerifyFun) -> + init(Id, N, F, T, Session, Callback, SignFun, VerifyFun, fun dkg_hybriddkg:default_commitment_cache_fun/1). + -spec init(Id :: pos_integer(), N :: pos_integer(), F :: pos_integer(), T :: pos_integer(), - G1 :: erlang_pbc:element(), G2 :: erlang_pbc:element(), Session :: session(), Callback :: boolean(), - SignFun :: signfun(), VerifyFun :: verifyfun())-> vss(). -init(Id, N, F, T, G1, G2, Session, Callback, SignFun, VerifyFun) -> + SignFun :: signfun(), VerifyFun :: verifyfun(), CCacheFun :: fun())-> vss(). +init(Id, N, F, T, Session, Callback, SignFun, VerifyFun, CCacheFun) -> true = N >= (3*T + 2*F + 1), #vss{id=Id, n=N, f=F, t=T, session=Session, - u=G1, - u2=G2, callback=Callback, signfun = SignFun, - verifyfun = VerifyFun}. + verifyfun = VerifyFun, + commitment_cache_fun=CCacheFun}. %% upon a message (Pd, τ, in, share, s): /* only Pd */ %% choose a symmetric bivariate polynomial φ(x,y) = ∑tj,l=0 φjl x^j y^l ∈R Zp[x,y] and φ00 = s %% C ←{Cjl } t j,l=0 where Cjl = gφ^jl for j,l ∈[0,t] %% for all j ∈ [1,n] do %% aj(y) ← φ(j,y); send the message (Pd, τ, send, C, aj) to Pj --spec input(VSS :: vss(), Secret :: erlang_pbc:element()) -> {vss(), {send, [send_msg()]} | ok}. -input(VSS = #vss{session=Session={Dealer,_}, id=Id, u=U, u2=U2, t=T, n=N, callback=true}, Secret) when Dealer == Id -> - BiPoly = dkg_bipolynomial:generate(U2, T, Secret), - Commitment = dkg_commitment:new(dkg_util:allnodes(N), U2, BiPoly), +-spec input(VSS :: vss(), Secret :: integer()) -> {vss(), {send, [send_msg()]} | ok}. +input(VSS = #vss{session=Session={Dealer,_}, id=Id, t=T, n=N, callback=CB}, Secret) when Dealer == Id -> + BiPoly = tc_bipoly:with_secret(Secret, T), + Commitment = dkg_commitment:new(dkg_util:allnodes(N), tc_bipoly:commitment(BiPoly), VSS#vss.commitment_cache_fun), %% only serialize this once, not in the loop below - SerializedCommitmentMatrix = dkg_commitmentmatrix:serialize(dkg_commitment:matrix(Commitment)), + SerializedCommitmentMatrix = dkg_commitment:matrix(Commitment), - Msgs = lists:map(fun(Node) -> - NodeZr = erlang_pbc:element_set(erlang_pbc:element_new('Zr', U), Node), - dkg_polynomial:serialize(dkg_bipolynomial:evaluate(BiPoly, NodeZr)) - end, dkg_util:allnodes(N)), - {store_commitment(Commitment, VSS), {send, [{callback, {send, {Session, SerializedCommitmentMatrix, Msgs}}}]}}; -input(VSS = #vss{session=Session={Dealer,_}, id=Id, u=U, u2=U2, t=T, n=N}, Secret) when Dealer == Id -> - BiPoly = dkg_bipolynomial:generate(U2, T, Secret), - Commitment = dkg_commitment:new(dkg_util:allnodes(N), U2, BiPoly), - %% only serialize this once, not in the loop below - SerializedCommitmentMatrix = dkg_commitmentmatrix:serialize(dkg_commitment:matrix(Commitment)), - - Msgs = lists:map(fun(Node) -> - NodeZr = erlang_pbc:element_set(erlang_pbc:element_new('Zr', U), Node), - Aj = dkg_polynomial:serialize(dkg_bipolynomial:evaluate(BiPoly, NodeZr)), - {unicast, Node, {send, {Session, SerializedCommitmentMatrix, Aj}}} - end, dkg_util:allnodes(N)), - {store_commitment(Commitment, VSS), {send, Msgs}}; + case CB of + true -> + Msgs = lists:map(fun(Node) -> + tc_poly:serialize(tc_bipoly:row(BiPoly, Node)) + end, dkg_util:allnodes(N)), + {commitment_sent(SerializedCommitmentMatrix, dkg_util:allnodes(N), store_commitment(Commitment, Id, VSS)), {send, [{callback, {send, {Session, SerializedCommitmentMatrix, Msgs}}}]}}; + false -> + Msgs = lists:map(fun(Node) -> + Aj = tc_poly:serialize(tc_bipoly:row(BiPoly, Node)), + {unicast, Node, {send, {Session, SerializedCommitmentMatrix, Aj}}} + end, dkg_util:allnodes(N)), + {commitment_sent(SerializedCommitmentMatrix, dkg_util:allnodes(N), store_commitment(Commitment, Id, VSS)), {send, Msgs}} + end; input(VSS, _Secret) -> {VSS, ok}. @@ -97,29 +96,26 @@ input(VSS, _Secret) -> %% for all j ∈ [1, n] do %% send the message (Pd , τ, echo, C, a(j)) to Pj -spec handle_msg(vss(), pos_integer(), send_msg() | echo_msg() | ready_msg()) -> {vss(), {send, [echo_msg() | ready_msg()]} | ok | ignore | result()}. -handle_msg(VSS=#vss{n=N, id=Id, session=Session, received_commitment=false, callback=true}, Sender, {send, {Session = {Sender, _}, SerializedCommitmentMatrix0, SA}}) -> - Commitment = get_commitment(SerializedCommitmentMatrix0, VSS), - A = dkg_polynomial:deserialize(SA, VSS#vss.u), - case dkg_commitment:verify_poly(Commitment, Id, A) of - true -> - Msgs = lists:map(fun(Node) -> - erlang_pbc:element_to_binary(dkg_polynomial:evaluate(A, Node)) - end, dkg_util:allnodes(N)), - {store_commitment(Commitment, echo, VSS#vss{received_commitment=true}), {send, [{callback, {echo, {Session, SerializedCommitmentMatrix0, Msgs}}}]}}; - false -> - {VSS, ok} - end; -handle_msg(VSS=#vss{n=N, id=Id, session=Session, received_commitment=false}, Sender, {send, {Session = {Sender, _}, SerializedCommitmentMatrix0, SA}}) -> - Commitment = get_commitment(SerializedCommitmentMatrix0, VSS), - A = dkg_polynomial:deserialize(SA, VSS#vss.u), - case dkg_commitment:verify_poly(Commitment, Id, A) of - true -> - Msgs = lists:map(fun(Node) -> - {unicast, Node, {echo, {Session, SerializedCommitmentMatrix0, erlang_pbc:element_to_binary(dkg_polynomial:evaluate(A, Node))}}} - end, dkg_util:allnodes(N)), - {store_commitment(Commitment, echo, VSS#vss{received_commitment=true}), {send, Msgs}}; - false -> - {VSS, ok} +handle_msg(VSS=#vss{n=N, id=Id, session=Session, received_commitment=false, callback=CB}, Sender, {send, {Session = {Sender, _}, SerializedCommitmentMatrix0, SA}}) -> + case get_commitment(SerializedCommitmentMatrix0, VSS) of + {ok, Commitment} -> + A = tc_poly:deserialize(SA), + case dkg_commitment:verify_poly(Commitment, Id, A) of + true when CB == true -> + Msgs = lists:map(fun(Node) -> + tc_fr:serialize(tc_poly:eval(A, Node)) + end, dkg_util:allnodes(N)), + {commitment_sent(SerializedCommitmentMatrix0, dkg_util:allnodes(N), store_commitment(Commitment, Sender, echo, VSS#vss{received_commitment=true})), {send, [{callback, {echo, {Session, SerializedCommitmentMatrix0, Msgs}}}]}}; + true -> + Msgs = lists:map(fun(Node) -> + {unicast, Node, {echo, {Session, maybe_send_commitment(SerializedCommitmentMatrix0, N, VSS), tc_fr:serialize(tc_poly:eval(A, Node))}}} + end, dkg_util:allnodes(N)), + {commitment_sent(SerializedCommitmentMatrix0, dkg_util:allnodes(N), store_commitment(Commitment, Sender, echo, VSS#vss{received_commitment=true})), {send, Msgs}}; + false -> + {VSS, ok} + end; + {error, _Reason} -> + {VSS, ignore} end; handle_msg(VSS, _Sender, {send, {_Session, _Commitment, _A}}) -> %% already received a commitment, or it's not from the dealer; ignore this one @@ -133,37 +129,41 @@ handle_msg(VSS, _Sender, {send, {_Session, _Commitment, _A}}) -> %% for all j ∈ [1, n] do %% send the message (Pd, τ, ready, C, a(j)) to Pj handle_msg(VSS=#vss{id=Id, n=N, t=T, session=Session, done=false, callback=CB}, Sender, {echo, {Session, SerializedCommitmentMatrix0, SA}}) -> - Commitment = get_commitment(SerializedCommitmentMatrix0, VSS), - A = erlang_pbc:binary_to_element(VSS#vss.u, SA), - case dkg_commitment:verify_point(Commitment, Sender, Id, A) of - true -> - case dkg_commitment:add_echo(Commitment, Sender, A) of - {true, NewCommitment} -> - case dkg_commitment:num_echoes(NewCommitment) == ceil((N+T+1)/2) andalso - dkg_commitment:num_readies(NewCommitment) < (T+1) of - true when CB == true -> - Subshares = dkg_commitment:interpolate(NewCommitment, echo, dkg_util:allnodes(N)), - ReadyProof = construct_proof(VSS), - Msgs = lists:map(fun(Node) -> - erlang_pbc:element_to_binary(lists:nth(Node+1, Subshares)) - end, dkg_util:allnodes(N)), - {store_commitment(NewCommitment, ready, VSS), {send, [{callback, {ready, {Session, SerializedCommitmentMatrix0, Msgs, ReadyProof}}}]}}; - true -> - %% not in callback mode - Subshares = dkg_commitment:interpolate(NewCommitment, echo, dkg_util:allnodes(N)), - ReadyProof = construct_proof(VSS), - Msgs = lists:map(fun(Node) -> - {unicast, Node, {ready, {Session, SerializedCommitmentMatrix0, erlang_pbc:element_to_binary(lists:nth(Node+1, Subshares)), ReadyProof}}} - end, dkg_util:allnodes(N)), - {store_commitment(NewCommitment, ready, VSS), {send, Msgs}}; - false -> - {store_commitment(NewCommitment, VSS), ok} + case get_commitment(SerializedCommitmentMatrix0, VSS) of + {ok, Commitment} -> + case dkg_commitment:num_echoes(Commitment) < ceil((N+T+1)/2) andalso + dkg_commitment:verify_point(Commitment, Sender, Id, SA) of + true -> + case dkg_commitment:add_echo(Commitment, Sender, SA) of + {true, NewCommitment} -> + case dkg_commitment:num_echoes(NewCommitment) == ceil((N+T+1)/2) andalso + dkg_commitment:num_readies(NewCommitment) < (T+1) of + true when CB == true -> + SubShares = dkg_commitment:interpolate(NewCommitment, T, echo), + ReadyProof = construct_proof(VSS), + Msgs = lists:map(fun(Node) -> + tc_fr:serialize(tc_poly:eval(SubShares, Node)) + end, dkg_util:allnodes(N)), + {store_commitment(NewCommitment, Sender, ready, VSS), {send, [{callback, {ready, {Session, SerializedCommitmentMatrix0, Msgs, ReadyProof}}}]}}; + true -> + %% not in callback mode + SubShares = dkg_commitment:interpolate(NewCommitment, T, echo), + ReadyProof = construct_proof(VSS), + Msgs = lists:map(fun(Node) -> + {unicast, Node, {ready, {Session, maybe_send_commitment(SerializedCommitmentMatrix0, Node, VSS), tc_fr:serialize(tc_poly:eval(SubShares, Node)), ReadyProof}}} + end, dkg_util:allnodes(N)), + {store_commitment(NewCommitment, Sender, ready, VSS), {send, Msgs}}; + false -> + {store_commitment(NewCommitment, Sender, VSS), ok} + end; + {false, OldCommitment} -> + {store_commitment(OldCommitment, Sender, VSS), ok} end; - {false, OldCommitment} -> - {store_commitment(OldCommitment, VSS), ok} + false -> + {VSS, ok} end; - false -> - {VSS, ok} + {error, _Error} -> + {VSS, ignore} end; %% upon a message (Pd, τ, ready, C, α) from Pm (first time): @@ -176,51 +176,55 @@ handle_msg(VSS=#vss{id=Id, n=N, t=T, session=Session, done=false, callback=CB}, %% else if rC = n − t − f then %% si ← a(0); output (Pd , τ, out, shared, C, si ) handle_msg(VSS=#vss{n=N, t=T, f=F, id=Id, done=false, callback=CB}, Sender, {ready, {Session, SerializedCommitmentMatrix0, SA, ReadyProof}}=_ReadyMsg) -> - Commitment = get_commitment(SerializedCommitmentMatrix0, VSS), - A = erlang_pbc:binary_to_element(VSS#vss.u, SA), - case verify_proof(VSS, Sender, ReadyProof) andalso - dkg_commitment:verify_point(Commitment, Sender, Id, A) of - true -> - %% The point is valid and we have a ready proof - case dkg_commitment:add_ready(Commitment, Sender, A) of - {true, NewCommitment0} -> - {true, NewCommitment} = dkg_commitment:add_ready_proof(NewCommitment0, Sender, ReadyProof), - case dkg_commitment:num_readies(NewCommitment) == (T+1) andalso - dkg_commitment:num_echoes(NewCommitment) < ceil((N+T+1)/2) of - true when CB == true -> - SubShares = dkg_commitment:interpolate(NewCommitment, ready, dkg_util:allnodes(N)), - MyReadyProof = construct_proof(VSS), - Msgs = lists:map(fun(Node) -> - erlang_pbc:element_to_binary(lists:nth(Node+1, SubShares)) - end, dkg_util:allnodes(N)), - NewVSS = store_commitment(NewCommitment, ready, VSS), - {NewVSS, {send, [{callback, {ready, {Session, SerializedCommitmentMatrix0, Msgs, MyReadyProof}}}]}}; - true -> - %% not in callback mode - SubShares = dkg_commitment:interpolate(NewCommitment, ready, dkg_util:allnodes(N)), - MyReadyProof = construct_proof(VSS), - Msgs = lists:map(fun(Node) -> - {unicast, Node, {ready, {Session, SerializedCommitmentMatrix0, erlang_pbc:element_to_binary(lists:nth(Node+1, SubShares)), MyReadyProof}}} - end, dkg_util:allnodes(N)), - NewVSS = store_commitment(NewCommitment, ready, VSS), - {NewVSS, {send, Msgs}}; - false -> - case dkg_commitment:num_readies(NewCommitment) == (N-T-F) andalso - maps:size(dkg_commitment:ready_proofs(NewCommitment)) == (N-T-F) of - true-> - [SubShare] = dkg_commitment:interpolate(NewCommitment, ready, []), - %% clear the commitments out of our state and return the winning one - {VSS#vss{done=true, commitments=#{}}, {result, {Session, NewCommitment, SubShare}}}; + case get_commitment(SerializedCommitmentMatrix0, VSS) of + {ok, Commitment} -> + case verify_proof(VSS, Sender, ReadyProof) andalso + dkg_commitment:verify_point(Commitment, Sender, Id, SA) of + true -> + %% The point is valid and we have a ready proof + case dkg_commitment:add_ready(Commitment, Sender, SA) of + {true, NewCommitment0} -> + {true, NewCommitment} = dkg_commitment:add_ready_proof(NewCommitment0, Sender, ReadyProof), + case dkg_commitment:num_readies(NewCommitment) == (T+1) andalso + dkg_commitment:num_echoes(NewCommitment) < ceil((N+T+1)/2) of + true when CB == true -> + SubShares = dkg_commitment:interpolate(NewCommitment, T, ready), + MyReadyProof = construct_proof(VSS), + Msgs = lists:map(fun(Node) -> + tc_fr:serialize(tc_poly:eval(SubShares, Node)) + end, dkg_util:allnodes(N)), + NewVSS = store_commitment(NewCommitment, ready, VSS), + {NewVSS, {send, [{callback, {ready, {Session, SerializedCommitmentMatrix0, Msgs, MyReadyProof}}}]}}; + true -> + %% not in callback mode + SubShares = dkg_commitment:interpolate(NewCommitment, T, ready), + MyReadyProof = construct_proof(VSS), + Msgs = lists:map(fun(Node) -> + {unicast, Node, {ready, {Session, maybe_send_commitment(SerializedCommitmentMatrix0, Node, VSS), tc_fr:serialize(tc_poly:eval(SubShares, Node)), MyReadyProof}}} + end, dkg_util:allnodes(N)), + NewVSS = store_commitment(NewCommitment, ready, VSS), + {NewVSS, {send, Msgs}}; false -> - NewVSS = store_commitment(NewCommitment, VSS), - {NewVSS, ok} - end + case dkg_commitment:num_readies(NewCommitment) == (N-T-F) andalso + maps:size(dkg_commitment:ready_proofs(NewCommitment)) == (N-T-F) of + true-> + SubShares = dkg_commitment:interpolate(NewCommitment, T, ready), + SubShare = tc_poly:eval(SubShares, 0), + %% clear the commitments out of our state and return the winning one + {VSS#vss{done=true, commitments=#{}}, {result, {Session, NewCommitment, SubShare}}}; + false -> + NewVSS = store_commitment(NewCommitment, Sender, VSS), + {NewVSS, ok} + end + end; + {false, OldCommitment} -> + {store_commitment(OldCommitment, Sender, VSS), ok} end; - {false, OldCommitment} -> - {store_commitment(OldCommitment, VSS), ok} + false -> + {VSS, ok} end; - false -> - {VSS, ok} + {error, _Error} -> + {VSS, ignore} end; handle_msg(VSS, _Sender, _Msg) -> %% we're likely done here, so there's no point in processing more messages @@ -231,15 +235,13 @@ serialize(#vss{id = Id, n = N, f = F, t = T, - u = U, - u2 = U2, done = Done, session = Session, received_commitment = ReceivedCommitment, commitments = Commitments, + commitments_sent = CS, + commitments_received = CR, callback = Callback}) -> - PreSer = #{u => erlang_pbc:element_to_binary(U), - u2 => erlang_pbc:element_to_binary(U2)}, M0 = #{id => Id, commitments => maps:map(fun(_, V) -> dkg_commitment:serialize(V) end, Commitments), @@ -249,42 +251,41 @@ serialize(#vss{id = Id, done => Done, session => Session, received_commitment => ReceivedCommitment, + commitments_sent => CS, + commitments_received => CR, callback => Callback}, - M = maps:map(fun(_K, Term) -> term_to_binary(Term) end, M0), - maps:merge(PreSer, M). + maps:map(fun(_K, Term) -> term_to_binary(Term) end, M0). --spec deserialize(#{atom() => binary() | map()}, erlang_pbc:element(), fun(), fun()) -> vss(). -deserialize(Map0, Element, SignFun, VerifyFun) -> - Map = maps:map(fun(K, V) when K == u; K == u2; - K == shares_results -> - V; - (_K, B) -> +-spec deserialize(#{atom() => binary() | map()}, fun(), fun(), fun()) -> vss(). +deserialize(Map0, SignFun, VerifyFun, CCacheFun) -> + Map = maps:map(fun(_K, B) -> binary_to_term(B) end, Map0), #{id := Id, n := N, f := F, t := T, - u := SerializedU, - u2 := SerializedU2, done := Done, session := Session, received_commitment := ReceivedCommitment, commitments := SerializedCommitments, + commitments_sent := CS, + commitments_received := CR, callback := Callback} = Map, #vss{id = Id, n = N, f = F, t = T, - u = erlang_pbc:binary_to_element(Element, SerializedU), - u2 = erlang_pbc:binary_to_element(Element, SerializedU2), done = Done, session = Session, received_commitment = ReceivedCommitment, - commitments = maps:map(fun(_, V) -> dkg_commitment:deserialize(V, Element) end, SerializedCommitments), + commitments = maps:map(fun(_, V) -> dkg_commitment:deserialize(V, CCacheFun) end, SerializedCommitments), + commitments_sent = CS, + commitments_received = CR, signfun = SignFun, verifyfun = VerifyFun, - callback = Callback}. + callback = Callback, + commitment_cache_fun=CCacheFun}. -spec status(vss()) -> map(). status(VSS) -> @@ -304,26 +305,42 @@ status(VSS) -> done => VSS#vss.done }. -get_commitment(SerializedMatrix, VSS = #vss{n=N, t=T, u2=G2}) -> +get_commitment(Hash, VSS) when is_integer(Hash) -> + case maps:find(Hash, VSS#vss.commitments) of + {ok, Commitment} -> + {ok, Commitment}; + _ -> + {error, unknown_commitment_hash} + end; +get_commitment(SerializedMatrix, VSS = #vss{n=N, t=T, commitment_cache_fun=Fun}) -> Key = erlang:phash2(SerializedMatrix), case maps:find(Key, VSS#vss.commitments) of {ok, Commitment} -> - Commitment; + {ok, Commitment}; error -> - Commitment = dkg_commitment:new(dkg_util:allnodes(N), G2, T), - dkg_commitment:set_matrix(Commitment, dkg_commitmentmatrix:deserialize(SerializedMatrix, G2)) + try tc_bicommitment:deserialize(SerializedMatrix) of + BiCommitment -> + case tc_bicommitment:degree(BiCommitment) == T of + true -> + {ok, dkg_commitment:new(dkg_util:allnodes(N), BiCommitment, SerializedMatrix, Fun)}; + false -> + {error, degree_mismatch} + end + catch _:_ -> + {error, commitment_deserialization_failed} + end end. -store_commitment(Commitment, VSS) -> - Key = erlang:phash2(dkg_commitment:binary_matrix(Commitment)), - VSS#vss{commitments=maps:put(Key, Commitment, VSS#vss.commitments)}. +store_commitment(Commitment, Sender, VSS) -> + Key = dkg_commitment:hash(Commitment), + commitment_received(dkg_commitment:matrix(Commitment), Sender, VSS#vss{commitments=maps:put(Key, Commitment, VSS#vss.commitments)}). -store_commitment(Commitment, ready, VSS) -> - Key = erlang:phash2(dkg_commitment:binary_matrix(Commitment)), - VSS#vss{commitments=maps:put(Key, Commitment, VSS#vss.commitments), sent_readies=[Key|VSS#vss.sent_readies]}; -store_commitment(Commitment, echo, VSS) -> - Key = erlang:phash2(dkg_commitment:binary_matrix(Commitment)), - VSS#vss{commitments=maps:put(Key, Commitment, VSS#vss.commitments), sent_echoes=[Key|VSS#vss.sent_echoes]}. +store_commitment(Commitment, Sender, ready, VSS) -> + Key = dkg_commitment:hash(Commitment), + commitment_received(dkg_commitment:matrix(Commitment), Sender, VSS#vss{commitments=maps:put(Key, Commitment, VSS#vss.commitments), sent_readies=[Key|VSS#vss.sent_readies]}); +store_commitment(Commitment, Sender, echo, VSS) -> + Key = dkg_commitment:hash(Commitment), + commitment_received(dkg_commitment:matrix(Commitment), Sender, VSS#vss{commitments=maps:put(Key, Commitment, VSS#vss.commitments), sent_echoes=[Key|VSS#vss.sent_echoes]}). construct_proof(#vss{id=Id, session={Dealer, Round}, signfun=SignFun}) -> @@ -338,3 +355,20 @@ verify_proof(#vss{session={Dealer, Round}, verifyfun=VerifyFun}, Sender, Proof) Msg = <>, VerifyFun(Sender, Msg, Proof). + +commitment_sent(SerializedCommitment, Nodes, VSS=#vss{commitments_sent=CS}) -> + VSS#vss{commitments_sent=maps:update_with(erlang:phash2(SerializedCommitment), fun(V) -> lists:usort(Nodes ++ V) end, Nodes, CS)}. + +commitment_received(SerializedCommitment, Node, VSS=#vss{commitments_received=CR}) -> + VSS#vss{commitments_received=maps:update_with(erlang:phash2(SerializedCommitment), fun(V) -> lists:usort([Node|V]) end, [Node], CR)}. + +maybe_send_commitment(Commitment, Node, VSS) -> + Hash = erlang:phash2(Commitment), + case lists:member(Node, maps:get(Hash, VSS#vss.commitments_sent, [])) orelse + lists:member(Node, maps:get(Hash, VSS#vss.commitments_sent, [])) of + true -> + Hash; + false -> + Commitment + end. + diff --git a/src/dkg_lagrange.erl b/src/dkg_lagrange.erl deleted file mode 100644 index 16ce65c..0000000 --- a/src/dkg_lagrange.erl +++ /dev/null @@ -1,49 +0,0 @@ --module(dkg_lagrange). - --export([interpolate/3, - coefficients/2, - evaluate_g1/2, - evaluate_zr/2]). - --type indices() :: [integer(),...] | [erlang_pbc:element(),...]. - --spec interpolate(dkg_polynomial:polynomial(), indices() , erlang_pbc:element()) -> erlang_pbc:element(). -interpolate(Poly, Indices, Alpha) -> - Coefficients = coefficients(Indices, Alpha), - Shares = [ dkg_polynomial:evaluate(Poly, Index) || Index <- lists:seq(1, length(Indices)) ], - %% XXX: interpolate is done on G1 since the same group is used in the - %% public_key_shares in the dkg_commitmentmatrix - %% TODO: maybe move the group to function call? - dkg_lagrange:evaluate_g1(Coefficients, Shares). - --spec coefficients(indices(), erlang_pbc:element()) -> dkg_polynomial:polynomial(). -coefficients(Indices, Alpha) -> - One = erlang_pbc:element_set(erlang_pbc:element_new('Zr', Alpha), 1), - IndicesWithIndex = enumerate(Indices), - lists:reverse(lists:foldl(fun({I, Index}, Acc1) -> - Num = lists:foldl(fun(E, Acc) -> - erlang_pbc:element_mul(Acc, E) - end, One, [ erlang_pbc:element_sub(Idx, Alpha) || {J, Idx} <- IndicesWithIndex, J /= I]), - - Den = lists:foldl(fun(E, Acc) -> - erlang_pbc:element_mul(Acc, E) - end, One, [ erlang_pbc:element_sub(Idx, Index) || {J, Idx} <- IndicesWithIndex, J /= I]), - [erlang_pbc:element_div(Num, Den) | Acc1] - end, [], IndicesWithIndex)). - --spec evaluate_g1(dkg_polynomial:polynomial(), [erlang_pbc:element(), ...]) -> erlang_pbc:element(). -evaluate_g1(Coefficients, Shares) -> - One = erlang_pbc:element_set(erlang_pbc:element_new('G2', hd(Coefficients)), 1), - lists:foldl(fun({Coefficient, Share}, Acc) -> - erlang_pbc:element_mul(Acc, erlang_pbc:element_pow(Share, Coefficient)) - end, One, lists:zip(Coefficients, Shares)). - --spec evaluate_zr(dkg_polynomial:polynomial(), [erlang_pbc:element(), ...]) -> erlang_pbc:element(). -evaluate_zr(Coefficients, Shares) -> - Zero = erlang_pbc:element_set(erlang_pbc:element_new('Zr', hd(Coefficients)), 0), - lists:foldl(fun({Coefficient, Share}, Acc) -> - erlang_pbc:element_add(Acc, erlang_pbc:element_mul(Share, Coefficient)) - end, Zero, lists:zip(Coefficients, Shares)). - -enumerate(List) -> - lists:zip(lists:seq(1, length(List)), List). diff --git a/src/dkg_polynomial.erl b/src/dkg_polynomial.erl deleted file mode 100644 index 2e2749c..0000000 --- a/src/dkg_polynomial.erl +++ /dev/null @@ -1,120 +0,0 @@ --module(dkg_polynomial). --compile({no_auto_import,[evaluate/2]}). - --export([generate/2, - generate/3, - generate/4, - add/2, - sub/2, - mul/2, - evaluate/2, - cmp/2, - degree/1, - is_zero/1, - is_equal/2, - print/1, - serialize/1, - deserialize/2]). - --type polynomial() :: [erlang_pbc:element()]. --export_type([polynomial/0]). - -%% Create a random polynomial of degree t >= 0 --spec generate(erlang_pbc:element(), pos_integer()) -> polynomial(). -generate(Pairing, T) -> - [ erlang_pbc:element_random(erlang_pbc:element_new('Zr', Pairing)) || _ <- lists:seq(0, T)]. - -%% Create a random polynomial of degree t with the given constant term --spec generate(erlang_pbc:element(), pos_integer(), pos_integer() | erlang_pbc:element()) -> polynomial(). -generate(Pairing, T, Term) when is_integer(Term) -> - generate(Pairing, T, erlang_pbc:element_set(erlang_pbc:element_new('Zr', Pairing), Term)); -generate(Pairing, T, Term) -> - [Term | generate(Pairing, T - 1)]. - -%% generate a random polynomial of degree t such that f(Index) => Term --spec generate(erlang_pbc:element(), integer(), erlang_pbc:element(), integer() | erlang_pbc:element()) -> polynomial(). -generate(Pairing, T, Index, Term) -> - [Head | Tail] = Poly = generate(Pairing, T), - Val = evaluate(Poly, Index), - NewHead = erlang_pbc:element_add(erlang_pbc:element_sub(Head, Val), Term), - [NewHead | Tail]. - --spec degree(polynomial()) -> non_neg_integer(). -degree(Poly) -> - length(Poly) - 1. - --spec is_zero(polynomial()) -> boolean(). -is_zero(Poly) -> - %% XXX: check all elements are 0 which they will be if the length(poly) == 0 - %% I don't understand why this is needed - %% lists:all(fun erlang_pbc:element_is0/1, Poly) andalso length(Poly) == 0. - length(Poly) == 0. - --spec is_equal(polynomial(), polynomial()) -> boolean(). -is_equal(PolyA, PolyB) -> - %% check all elements are equal for polyA and polyB - lists:all(fun({A, B}) -> - erlang_pbc:element_cmp(A, B) - end, lists:zip(PolyA, PolyB)). - --spec add(polynomial(), polynomial()) -> polynomial(). -add(PolyA, PolyB) -> - merge(PolyA, PolyB, fun erlang_pbc:element_add/2). - --spec sub(polynomial(), polynomial()) -> polynomial(). -sub(PolyA, PolyB) -> - merge(PolyA, PolyB, fun erlang_pbc:element_sub/2). - --spec mul(polynomial(), polynomial()) -> polynomial(). -mul(PolyA, PolyB) -> - %% why can't we just use set0 here? - Zero = erlang_pbc:element_add(hd(PolyA++PolyB), erlang_pbc:element_neg(hd(PolyA++PolyB))), - lists:foldl(fun(V, Acc0) -> - Acc = [Zero|Acc0], - Temp = [ erlang_pbc:element_mul(A, V) || A <- PolyA ], - add(Acc, Temp) - end, [], PolyB). - --spec cmp(polynomial(), polynomial()) -> boolean(). -cmp(PolyA, PolyB) -> - %% check the degree(PolyA) == degree(PolyB) - %% check that each element is the same the original - degree(PolyA) == degree(PolyB) andalso - is_equal(PolyA, PolyB). - -%% Apply a polynomial at a point x using Horner's rule --spec evaluate(polynomial(), erlang_pbc:element() | integer()) -> erlang_pbc:element(). -evaluate(Poly, X) -> - %% why can't we just use set0 here? - Zero = erlang_pbc:element_add(hd(Poly), erlang_pbc:element_neg(hd(Poly))), - %% go in reverse for coefficients - lists:foldl(fun(Coeff, Acc) -> - erlang_pbc:element_add(erlang_pbc:element_mul(Acc, X), Coeff) - end, Zero, lists:reverse(Poly)). - --spec print(polynomial()) -> any(). -print(Poly) -> - [ erlang_pbc:element_to_string(X) || X <- Poly]. - --spec merge(polynomial(), polynomial(), fun()) -> polynomial(). -merge(PolyA, PolyB, MergeFun) -> - Degree = max(degree(PolyA) + 1, degree(PolyB) + 1), - %% why can't we just use set0 here? - Zero = erlang_pbc:element_add(hd(PolyA++PolyB), erlang_pbc:element_neg(hd(PolyA++PolyB))), - - %% pad the shorter polynomial so they're the same length - ExpandedPolyA = PolyA ++ lists:duplicate(Degree - length(PolyA), Zero), - ExpandedPolyB = PolyB ++ lists:duplicate(Degree - length(PolyB), Zero), - - MergedPoly = lists:map(fun({A, B}) -> - MergeFun(A, B) - end, lists:zip(ExpandedPolyA, ExpandedPolyB)), - - %% remove any trailing 0s - lists:reverse(lists:dropwhile(fun erlang_pbc:element_is0/1, lists:reverse(MergedPoly))). - -serialize(Poly) -> - erlang_pbc:elements_to_binary(Poly). - -deserialize(Poly, Element) -> - erlang_pbc:binary_to_elements(Element, Poly). diff --git a/src/dkg_util.erl b/src/dkg_util.erl index 60d836b..7c5f721 100644 --- a/src/dkg_util.erl +++ b/src/dkg_util.erl @@ -1,6 +1,6 @@ -module(dkg_util). --export([allnodes/1, wrap/2]). +-export([allnodes/1, wrap/2, commitment_cache_fun/0]). allnodes(N) -> lists:seq(1, N). @@ -15,3 +15,18 @@ wrap(Id, [{unicast, Dest, Msg}|T]) -> wrap(Id, [{callback, Msg}|T]) -> [{callback, {Id, Msg}}|wrap(Id, T)]. +commitment_cache_fun() -> + T = ets:new(t, []), + fun Self({Ser, DeSer0}) -> + ets:insert(T, {erlang:phash2(Ser), DeSer0}), + ok; + Self(Ser) -> + case ets:lookup(T, erlang:phash2(Ser)) of + [] -> + DeSer = tc_bicommitment:deserialize(Ser), + ok = Self({Ser, DeSer}), + DeSer; + [{_, Res}] -> + Res + end + end. diff --git a/test/dkg_bipolynomial_SUITE.erl b/test/dkg_bipolynomial_SUITE.erl deleted file mode 100644 index ce77849..0000000 --- a/test/dkg_bipolynomial_SUITE.erl +++ /dev/null @@ -1,105 +0,0 @@ --module(dkg_bipolynomial_SUITE). --compile({no_auto_import,[evaluate/2]}). - --include_lib("eunit/include/eunit.hrl"). - --export([all/0, init_per_testcase/2, end_per_testcase/2]). - --export([generate_with_constant_term_test/1, - self_subtract_test/1, - add_zero_test/1, - subtract_zero_test/1, - add_different_sizes_test/1, - negative_comparison_test/1, - evaluate_test/1]). - -all() -> - [generate_with_constant_term_test, - self_subtract_test, - add_zero_test, - subtract_zero_test, - add_different_sizes_test, - negative_comparison_test, - evaluate_test]. - -init_per_testcase(_, Config) -> - Pairing = erlang_pbc:group_new('SS512'), - [{pairing, Pairing} | Config]. - -end_per_testcase(_, Config) -> - Config. - -generate_with_constant_term_test(Config) -> - Pairing = proplists:get_value(pairing, Config), - FortyTwo = erlang_pbc:element_set(erlang_pbc:element_new('Zr', Pairing), 42), - Poly = dkg_bipolynomial:generate(Pairing, 5, FortyTwo), - ?assertEqual(FortyTwo, dkg_bipolynomial:lookup([1, 1], Poly)). - -self_subtract_test(Config) -> - Pairing = proplists:get_value(pairing, Config), - Poly = dkg_bipolynomial:generate(Pairing, 5), - ct:pal("Poly: ~p~n", [dkg_bipolynomial:print(Poly)]), - %% subtracting a polynomial from itself should yield all 0s - ZeroPoly = dkg_bipolynomial:sub(Poly, Poly), - ct:pal("ZeroPoly: ~p~n", [dkg_bipolynomial:print(ZeroPoly)]), - %% polynomial should trim any trailing empty fields (ie all of them!) - ?assertEqual(-1, dkg_bipolynomial:degree(ZeroPoly)), - ok. - -add_zero_test(Config) -> - Pairing = proplists:get_value(pairing, Config), - Poly = dkg_bipolynomial:generate(Pairing, 5), - ZeroPoly = dkg_bipolynomial:sub(Poly, Poly), - %% adding a zeropolynomial to polynomial should be the same polynomial - ZeroAddedPoly = dkg_bipolynomial:add(Poly, ZeroPoly), - ct:pal("Poly: ~p~n", [Poly]), - ct:pal("ZeroAddedPoly: ~p~n", [ZeroAddedPoly]), - ?assert(dkg_bipolynomial:cmp(Poly, ZeroAddedPoly)), - ok. - -add_different_sizes_test(Config) -> - Pairing = proplists:get_value(pairing, Config), - PolyA = dkg_bipolynomial:generate(Pairing, 5), - PolyB = dkg_bipolynomial:generate(Pairing, 8), - AddedPoly = dkg_bipolynomial:add(PolyA, PolyB), - %% result should be of degree 8 - ?assertEqual(8, dkg_bipolynomial:degree(AddedPoly)), - %% if we subtract B from the result, we should get back A with degree 5 - SubtractedPoly = dkg_bipolynomial:sub(AddedPoly, PolyB), - ?assertEqual(5, dkg_bipolynomial:degree(SubtractedPoly)), - ?assert(dkg_bipolynomial:cmp(PolyA, SubtractedPoly)), - ok. - -subtract_zero_test(Config) -> - Pairing = proplists:get_value(pairing, Config), - Poly = dkg_bipolynomial:generate(Pairing, 5), - ZeroPoly = dkg_bipolynomial:sub(Poly, Poly), - %% subtracting a zeropolynomial to polynomial should be the same polynomial - ZeroSubtractedPoly = dkg_bipolynomial:sub(Poly, ZeroPoly), - ct:pal("Poly: ~p~n", [Poly]), - ct:pal("ZeroSubtractedPoly: ~p~n", [ZeroSubtractedPoly]), - ?assert(dkg_bipolynomial:cmp(Poly, ZeroSubtractedPoly)), - ok. - -evaluate_test(Config) -> - Pairing = proplists:get_value(pairing, Config), - Five = erlang_pbc:element_set(erlang_pbc:element_new('Zr', Pairing), 5), - Six = erlang_pbc:element_set(erlang_pbc:element_new('Zr', Pairing), 6), - BiPoly = dkg_bipolynomial:generate(Pairing, 5), - - PolyA = dkg_bipolynomial:evaluate(BiPoly, Five), - PolyB = dkg_bipolynomial:evaluate(BiPoly, Six), - - ResultA = dkg_polynomial:evaluate(PolyA, Six), - ResultB = dkg_polynomial:evaluate(PolyB, Five), - ct:pal("~p == ~p", [erlang_pbc:element_to_string(ResultA), erlang_pbc:element_to_string(ResultB)]), - ?assert(erlang_pbc:element_cmp(ResultA, ResultB)), - ok. - -negative_comparison_test(Config) -> - Pairing = proplists:get_value(pairing, Config), - PolyA = dkg_bipolynomial:generate(Pairing, 5), - PolyB = dkg_bipolynomial:add(PolyA, PolyA), - %% since PolyA /= 2*PolyA - ?assertEqual(false, dkg_bipolynomial:cmp(PolyA, PolyB)), - ok. diff --git a/test/dkg_commitment_SUITE.erl b/test/dkg_commitment_SUITE.erl new file mode 100644 index 0000000..deb5661 --- /dev/null +++ b/test/dkg_commitment_SUITE.erl @@ -0,0 +1,81 @@ +-module(dkg_commitment_SUITE). + +-compile({no_auto_import, [evaluate/2]}). + +-include_lib("eunit/include/eunit.hrl"). + +-export([all/0, init_per_testcase/2, end_per_testcase/2]). +-export([ + verify_poly_test/1, + commitment_cmp_test/1, + verify_point_test/1 +]). + +all() -> + [ + verify_poly_test, + verify_point_test, + commitment_cmp_test + ]. + +init_per_testcase(_, Config) -> + Config. + +end_per_testcase(_, Config) -> + Config. + +verify_poly_test(_Config) -> + Secret = rand:uniform(4096), + BiPoly = tc_bipoly:with_secret(Secret, 4), + BiCommitment = tc_bipoly:commitment(BiPoly), + NodeIDs = lists:seq(1, 4), + TaggedPolys = [{I, tc_bipoly:row(BiPoly, I)} || I <- NodeIDs], + DKGCommitment = dkg_commitment:new(NodeIDs, BiCommitment, dkg_util:commitment_cache_fun()), + ?assert( + lists:all( + fun({I, Poly}) -> + dkg_commitment:verify_poly(DKGCommitment, I, Poly) + end, + TaggedPolys + ) + ). + +verify_point_test(_Config) -> + Secret = rand:uniform(4096), + RandomBiPoly = tc_bipoly:with_secret(Secret, 4), + BiCommitment = tc_bipoly:commitment(RandomBiPoly), + NodeIDs = lists:seq(1, 4), + TaggedPolys = [{I, tc_bipoly:row(RandomBiPoly, I)} || I <- NodeIDs], + DKGCommitment = dkg_commitment:new(NodeIDs, BiCommitment, dkg_util:commitment_cache_fun()), + Res = lists:map( + fun({SenderId, Poly}) -> + case dkg_commitment:verify_poly(DKGCommitment, SenderId, Poly) of + true -> + %% verify_poly succeeded, check verify_point for verifiers + lists:map( + fun({VerifierId, Poly2}) -> + Point = tc_poly:eval(Poly2, SenderId), + SPoint = tc_fr:serialize(Point), + dkg_commitment:verify_point(DKGCommitment, SenderId, VerifierId, SPoint) + end, + TaggedPolys + ); + false -> + false + end + end, + TaggedPolys + ), + ct:pal("Res: ~p~n", [Res]), + ?assert(lists:all(fun(X) -> X end, lists:flatten(Res))), + ok. + +commitment_cmp_test(_Config) -> + Secret1 = rand:uniform(4096), + RandomBiPoly1 = tc_bipoly:with_secret(Secret1, 5), + C1 = dkg_commitment:new(1, tc_bipoly:commitment(RandomBiPoly1), dkg_util:commitment_cache_fun()), + Secret2 = rand:uniform(4096), + RandomBiPoly2 = tc_bipoly:with_secret(Secret2, 5), + C2 = dkg_commitment:new(2, tc_bipoly:commitment(RandomBiPoly2), dkg_util:commitment_cache_fun()), + ?assertEqual(false, dkg_commitment:cmp(C1, C2)), + ok. diff --git a/test/dkg_commitmentmatrix_SUITE.erl b/test/dkg_commitmentmatrix_SUITE.erl deleted file mode 100644 index 5cc610d..0000000 --- a/test/dkg_commitmentmatrix_SUITE.erl +++ /dev/null @@ -1,97 +0,0 @@ --module(dkg_commitmentmatrix_SUITE). --compile({no_auto_import,[evaluate/2]}). - --include_lib("eunit/include/eunit.hrl"). - --export([all/0, init_per_testcase/2, end_per_testcase/2]). --export([verify_poly_test/1, - public_key_share_test/1, - matrix_comparison_test/1, - verify_point_test/1]). - -all() -> - [verify_poly_test, - verify_point_test, - matrix_comparison_test, - public_key_share_test]. - -init_per_testcase(_, Config) -> - Pairing = erlang_pbc:group_new('SS512'), - [{pairing, Pairing} | Config]. - -end_per_testcase(_, Config) -> - Config. - -verify_poly_test(Config) -> - Pairing = proplists:get_value(pairing, Config), - Generator = erlang_pbc:element_from_hash(erlang_pbc:element_new('G1', Pairing), <<"honeybadger">>), - Secret = erlang_pbc:element_random(erlang_pbc:element_new('Zr', Pairing)), - BiPoly = dkg_bipolynomial:generate(Generator, 4, Secret), - CommitmentMatrix = dkg_commitmentmatrix:new(Generator, BiPoly), - TaggedPolys = [ {I, dkg_bipolynomial:evaluate(BiPoly, erlang_pbc:element_set(erlang_pbc:element_new('Zr', Pairing), I))} || I <- lists:seq(1, 4) ], - ?assert(lists:all(fun({I, Poly}) -> - dkg_commitmentmatrix:verify_poly(Generator, CommitmentMatrix, I, Poly) - end, TaggedPolys)). - -verify_point_test(Config) -> - Pairing = proplists:get_value(pairing, Config), - Secret = erlang_pbc:element_random(erlang_pbc:element_new('Zr', Pairing)), - Generator = erlang_pbc:element_from_hash(erlang_pbc:element_new('G1', Pairing), <<"honeybadger">>), - RandomBiPoly = dkg_bipolynomial:generate(Pairing, 4, Secret), - CommitmentMatrix = dkg_commitmentmatrix:new(Generator, RandomBiPoly), - TaggedPolys = [ {J, dkg_bipolynomial:evaluate(RandomBiPoly, erlang_pbc:element_set(erlang_pbc:element_new('Zr', Pairing), J))} || J <- lists:seq(1, 4) ], - Res = lists:map(fun({SenderId, Poly}) -> - case dkg_commitmentmatrix:verify_poly(Generator, CommitmentMatrix, SenderId, Poly) of - true -> - %% verify_poly succeeded, check verify_point for verifiers - lists:map(fun({VerifierId, Poly2}) -> - Point = dkg_polynomial:evaluate(Poly2, erlang_pbc:element_set(erlang_pbc:element_new('Zr', Pairing), SenderId)), - dkg_commitmentmatrix:verify_point(Generator, CommitmentMatrix, SenderId, VerifierId, Point) - end, TaggedPolys); - false -> - false - end - end, TaggedPolys), - ct:pal("Res: ~p~n", [Res]), - ?assert(lists:all(fun(X) -> X end, lists:flatten(Res))), - ok. - -public_key_share_test(Config) -> - Pairing = proplists:get_value(pairing, Config), - Secret = erlang_pbc:element_random(erlang_pbc:element_new('Zr', Pairing)), - Generator = erlang_pbc:element_from_hash(erlang_pbc:element_new('G1', Pairing), <<"honeybadger">>), - ct:pal("Secret: ~p~n", [erlang_pbc:element_to_string(Secret)]), - RandomBiPoly = dkg_bipolynomial:generate(Pairing, 5, Secret), - ct:pal("RandomBiPoly: ~p~n", [dkg_bipolynomial:print(RandomBiPoly)]), - CommitmentMatrix = dkg_commitmentmatrix:new(Generator, RandomBiPoly), - ct:pal("CommitmentMatrix: ~p~n", [dkg_commitmentmatrix:print(CommitmentMatrix)]), - PublicKeySharePolynomial = [ dkg_commitmentmatrix:public_key_share(Generator, CommitmentMatrix, NodeId) || NodeId <- lists:seq(1, 6)], - ct:pal("PublicKeyShares: ~p~n", [dkg_polynomial:print(PublicKeySharePolynomial)]), - KnownSecret = dkg_polynomial:evaluate(PublicKeySharePolynomial, 0), - ct:pal("KnownSecret: ~p~n", [erlang_pbc:element_to_string(KnownSecret)]), - Indices = [ erlang_pbc:element_set(erlang_pbc:element_new('Zr', Pairing), I) || I <- lists:seq(1, 6) ], - ct:pal("Indices: ~p~n", [dkg_polynomial:print(Indices)]), - Alpha = erlang_pbc:element_set(erlang_pbc:element_new('Zr', Pairing), 0), - ct:pal("Alpha: ~p~n", [erlang_pbc:element_to_string(Alpha)]), - CalculatedSecret = dkg_lagrange:interpolate(PublicKeySharePolynomial, Indices, Alpha), - ct:pal("CalculatedSecret: ~p~n", [erlang_pbc:element_to_string(CalculatedSecret)]), - ?assert(erlang_pbc:element_cmp(KnownSecret, CalculatedSecret)), - ok. - -matrix_comparison_test(Config) -> - Pairing = proplists:get_value(pairing, Config), - Generator = erlang_pbc:element_from_hash(erlang_pbc:element_new('G1', Pairing), <<"honeybadger">>), - Secret1 = erlang_pbc:element_random(erlang_pbc:element_new('Zr', Pairing)), - ct:pal("Secret1: ~p~n", [erlang_pbc:element_to_string(Secret1)]), - RandomBiPoly1 = dkg_bipolynomial:generate(Pairing, 5, Secret1), - ct:pal("RandomBiPoly1: ~p~n", [dkg_bipolynomial:print(RandomBiPoly1)]), - CommitmentMatrix1 = dkg_commitmentmatrix:new(Generator, RandomBiPoly1), - ct:pal("CommitmentMatrix1: ~p~n", [dkg_commitmentmatrix:print(CommitmentMatrix1)]), - Secret2 = erlang_pbc:element_random(erlang_pbc:element_new('Zr', Pairing)), - ct:pal("Secret2: ~p~n", [erlang_pbc:element_to_string(Secret2)]), - RandomBiPoly2 = dkg_bipolynomial:generate(Pairing, 5, Secret2), - ct:pal("RandomBiPoly2: ~p~n", [dkg_bipolynomial:print(RandomBiPoly2)]), - CommitmentMatrix2 = dkg_commitmentmatrix:new(Generator, RandomBiPoly2), - ct:pal("CommitmentMatrix2: ~p~n", [dkg_commitmentmatrix:print(CommitmentMatrix2)]), - ?assertEqual(false, dkg_commitmentmatrix:cmp(CommitmentMatrix1, CommitmentMatrix2)), - ok. diff --git a/test/dkg_ct_utils.erl b/test/dkg_ct_utils.erl index 51b366a..0c4cb03 100644 --- a/test/dkg_ct_utils.erl +++ b/test/dkg_ct_utils.erl @@ -7,7 +7,8 @@ start_node/3, partition_cluster/2, heal_cluster/2, - connect/1 + connect/1, + random_n/2 ]). pmap(F, L) -> @@ -115,3 +116,9 @@ attempt_connect(Node) -> true -> {ok, connected} end. + +random_n(N, List) -> + lists:sublist(shuffle(List), N). + +shuffle(List) -> + [X || {_,X} <- lists:sort([{rand:uniform(), N} || N <- List])]. diff --git a/test/dkg_distributed_SUITE.erl b/test/dkg_distributed_SUITE.erl index 8eca6e3..bee3e69 100644 --- a/test/dkg_distributed_SUITE.erl +++ b/test/dkg_distributed_SUITE.erl @@ -11,11 +11,11 @@ all/0 ]). --export([symmetric_test/1, asymmetric_test/1]). +-export([symmetric_test/1]). %% common test callbacks -all() -> [symmetric_test, asymmetric_test]. +all() -> [symmetric_test]. init_per_suite(Config) -> os:cmd(os:find_executable("epmd")++" -daemon"), @@ -58,21 +58,10 @@ symmetric_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), T = proplists:get_value(t, Config), - {G1, G2} = dkg_test_utils:generate('SS512'), - run(N, F, T, 'SS512', G1, G2, Nodes), + run(N, F, T, Nodes), ok. -asymmetric_test(Config) -> - Nodes = proplists:get_value(nodes, Config), - N = proplists:get_value(n, Config), - F = proplists:get_value(f, Config), - T = proplists:get_value(t, Config), - {G1, G2} = dkg_test_utils:generate('MNT224'), - run(N, F, T, 'MNT224', G1, G2, Nodes), - ok. - - -run(N, F, T, Curve, G1, G2, Nodes) -> +run(N, F, T, Nodes) -> %% load dkg_worker on each node {Mod, Bin, _} = code:get_object_code(dkg_worker), _ = dkg_ct_utils:pmap(fun(Node) -> @@ -83,7 +72,7 @@ run(N, F, T, Curve, G1, G2, Nodes) -> Workers = [{Node, rpc:call(Node, dkg_worker, start_link, - [I, N, F, T, Curve, erlang_pbc:element_to_binary(G1), erlang_pbc:element_to_binary(G2), <<0>>])} || {I, Node} <- dkg_test_utils:enumerate(Nodes)], + [I, N, F, T, <<0>>])} || {I, Node} <- dkg_test_utils:enumerate(Nodes)], ok = global:sync(), ct:pal("workers ~p", [Workers]), @@ -102,4 +91,38 @@ run(N, F, T, Curve, G1, G2, Nodes) -> _ = [ ct:pal("~p is_done? :~p", [Node, dkg_worker:is_done(W)]) || {Node, {ok, W}} <- Workers], + true = check_status(Workers), + [ unlink(W) || {_, {ok, W}} <- Workers ]. + +%% helper functions + +check_status(Workers) -> + Statuses = [dkg_worker:dkg_status(W) || {_Node, {ok, W}} <- Workers], + + Check1 = lists:all(fun(Status) -> + 5 == maps:get(echoes_required, Status) andalso + 3 == maps:get(readies_required, Status) + end, + Statuses), + + CountShares = lists:foldl(fun(Status, Acc) -> + SharesMap = maps:get(shares_map, Status), + + CountDone = maps:fold(fun(_ID, Result, Acc2) -> + case maps:get(done, Result, false) of + true -> Acc2 + 1; + false -> Acc2 + end + end, 0, SharesMap), + + CountDone + Acc + end, + 0, + Statuses), + + %% for each worker, we expect i - 1 shares worse case + MinimumExpectedShares = (length(Workers) - 1) * length(Workers), + Check2 = CountShares >= MinimumExpectedShares, + + Check1 andalso Check2. diff --git a/test/dkg_handler.erl b/test/dkg_handler.erl index 0457116..3834ceb 100644 --- a/test/dkg_handler.erl +++ b/test/dkg_handler.erl @@ -15,10 +15,8 @@ -record(state, { dkg :: dkg_hybriddkg:dkg() | dkg_hybriddkg:serialized_dkg(), round :: integer(), - curve :: 'SS512' | 'MNT224', - g1 :: erlang_pbc:element() | binary(), - g2 :: erlang_pbc:element() | binary(), - privkey :: undefined | tpke_privkey:privkey() | tpke_privkey:privkey_serialized(), + privkey :: undefined | tc_secret_key_share:sk_share() | binary(), + pubkey_set :: undefined | tc_public_key_set:pk_set() | binary(), n :: pos_integer(), t :: pos_integer(), id :: pos_integer() @@ -29,31 +27,21 @@ init(DKGArgs) -> N = proplists:get_value(n, DKGArgs), F = proplists:get_value(f, DKGArgs), T = proplists:get_value(t, DKGArgs), - Curve = proplists:get_value(curve, DKGArgs), - G1 = proplists:get_value(g1, DKGArgs), - G2 = proplists:get_value(g2, DKGArgs), Round = proplists:get_value(round, DKGArgs), - {G1_Prime, G2_Prime} = case is_binary(G1) andalso is_binary(G2) of - true -> - Group = erlang_pbc:group_new(Curve), - {erlang_pbc:binary_to_element(Group, G1), erlang_pbc:binary_to_element(Group, G2)}; - false -> - {G1, G2} - end, - DKG = dkg_hybriddkg:init(ID, N, F, T, G1_Prime, G2_Prime, Round, [{callback, true}, {signfun, fun(_) -> <<"lol">> end}, {verifyfun, fun(_, _, _) -> true end}]), - {ok, #state{round=Round, id=ID, n=N, t=T, dkg=DKG, curve=Curve, g1=G1_Prime, g2=G2_Prime}}. + DKG = dkg_hybriddkg:init(ID, N, F, T, Round, [{callback, true}, {signfun, fun(_) -> <<"lol">> end}, {verifyfun, fun(_, _, _) -> true end}]), + {ok, #state{round=Round, id=ID, n=N, t=T, dkg=DKG}}. handle_command(start_round, State) -> {DKG, {send, ToSend}} = dkg_hybriddkg:start(State#state.dkg), {reply, ok, fixup_msgs(ToSend), State#state{dkg=DKG}}; handle_command(is_done, State) -> {reply, State#state.privkey /= undefined, ignore}; -handle_command(get_pubkey, #state{privkey=PrivKey}) when PrivKey /= undefined -> - {reply, tpke_privkey:public_key(PrivKey), ignore}; -handle_command({sign_share, MessageToSign}, #state{privkey=PrivKey}) when PrivKey /= undefined -> - {reply, tpke_privkey:sign(PrivKey, MessageToSign), ignore}; -handle_command({dec_share, CipherText}, #state{privkey=PrivKey}) when PrivKey /= undefined -> - {reply, tpke_privkey:decrypt_share(PrivKey, CipherText), ignore}; +handle_command(get_pubkey, #state{pubkey_set=PubKeySet}) when PubKeySet /= undefined -> + {reply, PubKeySet, ignore}; +handle_command({sign_share, MessageToSign}, #state{privkey=PrivKey, id=ID}) when PrivKey /= undefined -> + {reply, {ID - 1, tc_secret_key_share:sign(PrivKey, MessageToSign)}, ignore}; +handle_command({dec_share, CipherText}, #state{privkey=PrivKey, id=ID}) when PrivKey /= undefined -> + {reply, {ID - 1, tc_secret_key_share:decrypt_share(PrivKey, CipherText)}, ignore}; handle_command(status, #state{dkg=DKG}) -> {reply, dkg_hybriddkg:status(DKG), ignore}; handle_command(Msg, _State) -> @@ -65,17 +53,8 @@ handle_message(Msg, Actor, State) -> {_DKG, ignore} -> ignore; {DKG, ok} -> {State#state{dkg=DKG}, []}; - {DKG, {result, {Shard, VerificationKey, VerificationKeys}}} -> - PubKey = tpke_pubkey:init(State#state.n, - State#state.t, - State#state.g1, - State#state.g2, - VerificationKey, - VerificationKeys, - State#state.curve), - PrivKey = tpke_privkey:init(PubKey, Shard, State#state.id - 1), - - {State#state{dkg=DKG, privkey=PrivKey}, []}; + {DKG, {result, KeyShare}} -> + {State#state{dkg=DKG, privkey=tc_key_share:secret_key_share(KeyShare), pubkey_set=tc_key_share:public_key_set(KeyShare)}, []}; {DKG, {send, ToSend}} -> {State#state{dkg=DKG}, fixup_msgs(ToSend)}; {DKG, start_timer} -> @@ -95,57 +74,62 @@ callback_message(Actor, Message, _State) -> serialize(State) -> SerializedDKG = dkg_hybriddkg:serialize(State#state.dkg), - G1 = erlang_pbc:element_to_binary(State#state.g1), - G2 = erlang_pbc:element_to_binary(State#state.g2), - Map = #{dkg => SerializedDKG, - round => term_to_binary(State#state.round), - curve => term_to_binary(State#state.curve), - n => term_to_binary(State#state.n), - t => term_to_binary(State#state.t), - id => term_to_binary(State#state.id), - g1 => G1, - g2 => G2}, - case State#state.privkey of + PrivKey = case State#state.privkey of undefined -> - Map; + undefined; Other -> - maps:put(privkey, term_to_binary(tpke_privkey:serialize(Other)), Map) - end. + tc_secret_key_share:serialize(Other) + end, + + PubKeySet = case State#state.pubkey_set of + undefined -> + undefined; + Other2 -> + tc_public_key_set:serialize(Other2) + end, + + #{dkg => SerializedDKG, + round => term_to_binary(State#state.round), + n => term_to_binary(State#state.n), + t => term_to_binary(State#state.t), + privkey => term_to_binary(PrivKey), + pubkey_set => term_to_binary(PubKeySet), + id => term_to_binary(State#state.id)}. deserialize(Map) -> #{round := Round0, - curve := Curve0, n := N0, t := T0, id := ID0, - g1 := G1_0, - g2 := G2_0, privkey := PrivKey0, + pubkey_set := PubKeySet0, dkg := DKG0} = Map, - Curve = binary_to_term(Curve0), Round = binary_to_term(Round0), N = binary_to_term(N0), T = binary_to_term(T0), ID = binary_to_term(ID0), - Group = erlang_pbc:group_new(Curve), - G1 = erlang_pbc:binary_to_element(Group, G1_0), - G2 = erlang_pbc:binary_to_element(Group, G2_0), - DKG = dkg_hybriddkg:deserialize(DKG0, G1, fun(_) -> <<"lol">> end, fun(_, _, _) -> true end), - PrivKey = case PrivKey0 of + DKG = dkg_hybriddkg:deserialize(DKG0, fun(_) -> <<"lol">> end, fun(_, _, _) -> true end), + PrivKey = case binary_to_term(PrivKey0) of undefined -> undefined; Other -> - tpke_privkey:deserialize(binary_to_term(Other)) + tc_secret_key_share:deserialize(Other) + end, + PubKeySet = case binary_to_term(PubKeySet0) of + undefined -> + undefined; + Other2 -> + tc_public_key_set:deserialize(Other2) end, + #state{dkg=DKG, privkey=PrivKey, + pubkey_set=PubKeySet, n = N, t = T, id = ID, - curve = Curve, - round = Round, - g1=G1, g2=G2}. + round = Round}. restore(OldState, _NewState) -> {ok, OldState}. diff --git a/test/dkg_hybriddkg_SUITE.erl b/test/dkg_hybriddkg_SUITE.erl index 37d611c..2b31275 100644 --- a/test/dkg_hybriddkg_SUITE.erl +++ b/test/dkg_hybriddkg_SUITE.erl @@ -8,9 +8,7 @@ -export([all/0, init_per_testcase/2, end_per_testcase/2]). -export([ symmetric_test/1, - asymmetric_test/1, leader_change_symmetric_test/1, - leader_change_asymmetric_test/1, split_key_test/1, sign_verify_test/1, sign_verify_fail_test/1 @@ -19,9 +17,7 @@ all() -> [ symmetric_test, - asymmetric_test, leader_change_symmetric_test, - leader_change_asymmetric_test, split_key_test, sign_verify_test, sign_verify_fail_test @@ -45,17 +41,7 @@ symmetric_test(Config) -> T = proplists:get_value(t, Config), InitialLeader = proplists:get_value(init_leader, Config), Module = proplists:get_value(module, Config), - {G1, G2} = dkg_test_utils:generate('SS512'), - run(N, F, T, Module, 'SS512', G1, G2, InitialLeader). - -asymmetric_test(Config) -> - N = proplists:get_value(n, Config), - F = proplists:get_value(f, Config), - T = proplists:get_value(t, Config), - InitialLeader = proplists:get_value(init_leader, Config), - Module = proplists:get_value(module, Config), - {G1, G2} = dkg_test_utils:generate('MNT224'), - run(N, F, T, Module, 'MNT224', G1, G2, InitialLeader). + run(N, F, T, Module, InitialLeader). leader_change_symmetric_test(Config) -> N = proplists:get_value(n, Config), @@ -63,17 +49,7 @@ leader_change_symmetric_test(Config) -> T = proplists:get_value(t, Config), InitialLeader = 2, Module = proplists:get_value(module, Config), - {G1, G2} = dkg_test_utils:generate('SS512'), - run(N, F, T, Module, 'SS512', G1, G2, InitialLeader, [{elections, true}, {signfun, fun(_) -> <<"lol">> end}, {verifyfun, fun(_, _, _) -> true end}]). - -leader_change_asymmetric_test(Config) -> - N = proplists:get_value(n, Config), - F = proplists:get_value(f, Config), - T = proplists:get_value(t, Config), - InitialLeader = 2, - Module = proplists:get_value(module, Config), - {G1, G2} = dkg_test_utils:generate('MNT224'), - run(N, F, T, Module, 'MNT224', G1, G2, InitialLeader, [{elections, true}, {signfun, fun(_) -> <<"lol">> end}, {verifyfun, fun(_, _, _) -> true end}]). + run(N, F, T, Module, InitialLeader, [{elections, true}, {signfun, fun(_) -> <<"lol">> end}, {verifyfun, fun(_, _, _) -> true end}]). sign_verify_test(Config) -> N = proplists:get_value(n, Config), @@ -92,8 +68,7 @@ sign_verify_test(Config) -> SigFuns = [ fun(Msg) -> public_key:sign(Msg, sha256, PrivKey) end || {PrivKey, _} <- Keys ], InitialLeader = 2, Module = proplists:get_value(module, Config), - {G1, G2} = dkg_test_utils:generate('SS512'), - run(N, F, T, Module, 'SS512', G1, G2, InitialLeader, [{elections, true}, {signfuns, SigFuns}, {verifyfun, VerifyFun}]). + run(N, F, T, Module, InitialLeader, [{elections, true}, {signfuns, SigFuns}, {verifyfun, VerifyFun}]). sign_verify_fail_test(Config) -> N = proplists:get_value(n, Config), @@ -113,8 +88,7 @@ sign_verify_fail_test(Config) -> SigFuns = [ fun(Msg) -> public_key:sign(Msg, sha256, PrivKey) end || {PrivKey, _} <- Keys ], InitialLeader = 2, Module = proplists:get_value(module, Config), - {G1, G2} = dkg_test_utils:generate('SS512'), - ?assertException(error, {assertEqual, _}, run(N, F, T, Module, 'SS512', G1, G2, InitialLeader, [{elections, true}, {signfuns, SigFuns}, {verifyfun, VerifyFun}])). + ?assertException(error, {assertEqual, _}, run(N, F, T, Module, InitialLeader, [{elections, true}, {signfuns, SigFuns}, {verifyfun, VerifyFun}])). -record(sk_state, @@ -128,9 +102,8 @@ split_key_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), T = proplists:get_value(t, Config), - {G1, G2} = dkg_test_utils:generate('SS512'), - BaseConfig = [N, F, T, G1, G2, <<0>>, [{elections, true}, {signfun, fun(_) -> <<"lol">> end}, {verifyfun, fun(_, _, _) -> true end}]], + BaseConfig = [N, F, T, <<0>>, [{elections, true}, {signfun, fun(_) -> <<"lol">> end}, {verifyfun, fun(_, _, _) -> true end}]], Init = fun() -> @@ -149,7 +122,7 @@ split_key_test(Config) -> run_fake(Init, fun sk_model/7, os:timestamp(), %% {1543,599707,659249}, % known failure case [{Node, ignored} || Node <- lists:seq(1, N)], % input - N, F, T, 'SS512', G1, G2). + T). %% this model alters the sequence of events to trigger the split keys %% condition (in the original code). it does so by making sure that @@ -187,10 +160,10 @@ sk_model(_Message, _From, _To, _NodeState, _NewState, _Actions, ModelState) -> {actions, [], ModelState}. -run(N, F, T, Module, Curve, G1, G2, InitialLeader) -> - run(N, F, T, Module, Curve, G1, G2, InitialLeader, [{signfun, fun(_) -> <<"lol">> end}, {verifyfun, fun(_, _, _) -> true end}]). +run(N, F, T, Module, InitialLeader) -> + run(N, F, T, Module, InitialLeader, [{signfun, fun(_) -> <<"lol">> end}, {verifyfun, fun(_, _, _) -> true end}]). -run(N, F, T, Module, Curve, G1, G2, InitialLeader, Options) -> +run(N, F, T, Module, InitialLeader, Options) -> {StatesWithId, Replies} = lists:unzip(lists:map(fun(E) -> %% check if we have a real, per node, sig fun SigFun = case proplists:get_value(signfuns, Options) of @@ -200,7 +173,7 @@ run(N, F, T, Module, Curve, G1, G2, InitialLeader, Options) -> SigFuns when is_list(SigFuns) -> lists:nth(E, SigFuns) end, - {State, {send, Replies}} = Module:start(Module:init(E, N, F, T, G1, G2, <<0>>, + {State, {send, Replies}} = Module:start(Module:init(E, N, F, T, <<0>>, lists:keystore(signfun, 1, Options, {signfun, SigFun}))), {{E, State}, {E, {send, Replies}}} end, lists:seq(InitialLeader, N))), @@ -209,56 +182,32 @@ run(N, F, T, Module, Curve, G1, G2, InitialLeader, Options) -> ?assertEqual(N-InitialLeader+1, length(sets:to_list(ConvergedResults))), ct:pal("Results ~p", [sets:to_list(ConvergedResults)]), - verify_results(ConvergedResults, N, F, T, G1, G2, Curve). + verify_results(ConvergedResults, T). -run_fake(Init, Model, Seed, Input, N, F, T, Curve, G1, G2) -> +run_fake(Init, Model, Seed, Input, T) -> {ok, Results} = fakecast:start_test(Init, Model, Seed, Input), ct:pal("results ~p", [Results]), - verify_results(Results, N, F, T, G1, G2, Curve). - -verify_results(ConvergedResults, N, F, T, G1, G2, Curve) -> - %% 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", [[ {I, erlang_pbc:element_to_string(S)} || {I, 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, Curve), - 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)), - - case erlang_pbc:pairing_is_symmetric(G1) of - true -> - %% threshold decryption only works with symmetric curve - Message = crypto:hash(sha256, <<"my hovercraft is full of eels">>), - CipherText = tpke_pubkey:encrypt(PubKey, Message), - {ok, VerifiedCipherText} = tpke_pubkey:verify_ciphertext(PubKey, CipherText), - Shares = [ tpke_privkey:decrypt_share(SK, VerifiedCipherText) || SK <- PrivateKeys ], - ct:pal("Decrypted shares ~p", [Shares]), - ?assert(lists:all(fun(X) -> X end, [tpke_pubkey:verify_share(PubKey, Share, VerifiedCipherText) || Share <- Shares])), - ?assertEqual(Message, tpke_pubkey:combine_shares(PubKey, VerifiedCipherText, dealer:random_n(T+1, Shares))); - false -> - ok - end. + verify_results(Results, T). + +verify_results(ConvergedResults, T) -> + SecretKeyShares = [ KeyShare || {result, {_Node, KeyShare}} <- sets:to_list(ConvergedResults)], + + AKey = hd(SecretKeyShares), + MessageToSign = crypto:hash(sha256, crypto:strong_rand_bytes(12)), + Signatures = [ tc_key_share:sign_share(KeyShare, MessageToSign) || KeyShare <- SecretKeyShares], + ct:pal("~p", [[tc_key_share:verify_signature_share(AKey, Share, MessageToSign) || Share <- Signatures]]), + ?assert(lists:all(fun(X) -> X end, [tc_key_share:verify_signature_share(AKey, Share, MessageToSign) || Share <- Signatures])), + {ok, CombinedSignature} = tc_key_share:combine_signature_shares(AKey, Signatures), + ?assert(tc_key_share:verify(AKey, CombinedSignature, MessageToSign)), + + Message = crypto:hash(sha256, <<"my hovercraft is full of eels">>), + CipherText = tc_key_share:encrypt(AKey, Message), + true = tc_ciphertext:verify(CipherText), + DecShares = [tc_key_share:decrypt_share(KeyShare, CipherText) || KeyShare <- SecretKeyShares], + ?assert(lists:all(fun(E) -> E end, [tc_key_share:verify_decryption_share(AKey, DecShare, CipherText) || DecShare <- DecShares])), + ?assertEqual({ok, Message}, tc_key_share:combine_decryption_shares(AKey, [S || S <- dkg_ct_utils:random_n(T+1, DecShares)], CipherText)), + ok. diff --git a/test/dkg_hybridvss_SUITE.erl b/test/dkg_hybridvss_SUITE.erl index 67c1e16..a880079 100644 --- a/test/dkg_hybridvss_SUITE.erl +++ b/test/dkg_hybridvss_SUITE.erl @@ -7,17 +7,13 @@ -export([all/0, init_per_testcase/2, end_per_testcase/2]). -export([ symmetric_test/1, - asymmetric_test/1, - fake_symmetric/1, - fake_asymmetric/1 + fake_symmetric/1 ]). all() -> [ symmetric_test, - asymmetric_test, - fake_symmetric, - fake_asymmetric + fake_symmetric ]. init_per_testcase(_, Config) -> @@ -36,18 +32,7 @@ symmetric_test(Config) -> F = proplists:get_value(f, Config), T = proplists:get_value(t, Config), Module = proplists:get_value(module, Config), - Curve = 'SS512', - {G1, G2} = dkg_test_utils:generate(Curve), - run(Module, N, F, T, Curve, G1, G2). - -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), - Curve = 'MNT224', - {G1, G2} = dkg_test_utils:generate(Curve), - run(Module, N, F, T, Curve, G1, G2). + run(Module, N, F, T). -record(state, { @@ -59,10 +44,8 @@ fake_symmetric(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), T = proplists:get_value(t, Config), - Curve = 'SS512', - {G1, G2} = dkg_test_utils:generate(Curve), - TestArgs = [N, F, T, G1, G2, {1, <<0>>}, false, fun(_) -> <<"lol">> end, fun(_, _, _) -> true end], + TestArgs = [N, F, T, {1, <<0>>}, false, fun(_) -> <<"lol">> end, fun(_, _, _) -> true end], Init = fun() -> {ok, #fc_conf{ @@ -75,11 +58,9 @@ fake_symmetric(Config) -> } end, - Secret = erlang_pbc:element_random(erlang_pbc:element_new('Zr', G1)), - + Secret = rand:uniform(4096), - ok = run_fake(Init, fun model/7, os:timestamp(), [{1, Secret}], - N, F, T, Curve, G1, G2, Secret). + ok = run_fake(Init, fun model/7, os:timestamp(), [{1, Secret}], T). %% this model is trivial, we only want to catch outputs and never %% change anything about the execution @@ -96,54 +77,28 @@ model(_Message, _From, To, _NodeState, _NewState, {result, Result}, model(_Message, _From, _To, _NodeState, _NewState, _Actions, ModelState) -> {actions, [], ModelState}. +run(Module, N, F, T) -> -fake_asymmetric(Config) -> - N = proplists:get_value(n, Config), - F = proplists:get_value(f, Config), - T = proplists:get_value(t, Config), - Curve = 'MNT224', - {G1, G2} = dkg_test_utils:generate(Curve), - TestArgs = [N, F, T, G1, G2, {1, <<0>>}, false, fun(_) -> <<"lol">> end, fun(_, _, _) -> true end], - Init = fun() -> - {ok, - #fc_conf{ - test_mod = dkg_hybridvss, - nodes = [aaa, bbb, ccc, ddd, eee, fff, ggg, hhh, iii, jjj], %% are names useful? - configs = [[Id] ++ TestArgs || Id <- lists:seq(1, N)], - id_start = 1 - }, - #state{node_count = 10} - } - end, - - Secret = erlang_pbc:element_random(erlang_pbc:element_new('Zr', G1)), + [Dealer | Rest] = [ Module:init(Id, N, F, T, {1, <<0>>}, false, fun(_) -> <<"lol">> end, fun(_, _, _) -> true end) || Id <- lists:seq(1, N) ], - - ok = run_fake(Init, fun model/7, os:timestamp(), [{1, Secret}], - N, F, T, Curve, G1, G2, Secret). - -run(Module, N, F, T, Curve, G1, G2) -> - - [Dealer | Rest] = [ Module:init(Id, N, F, T, G1, G2, {1, <<0>>}, false, fun(_) -> <<"lol">> end, fun(_, _, _) -> true end) || Id <- lists:seq(1, N) ], - - Secret = erlang_pbc:element_random(erlang_pbc:element_new('Zr', G1)), + Secret = 1234, {NewDealerState, {send, MsgsToSend}} = Module:input(Dealer, Secret), States = [NewDealerState | Rest], StatesWithId = lists:zip(lists:seq(1, length(States)), States), {_FinalStates, ConvergedResults} = dkg_test_utils:do_send_outer(Module, [{1, {send, MsgsToSend}}], StatesWithId, sets:new()), - verify_results(Secret, ConvergedResults, N, F, T, Curve, G1, G2). + verify_results(ConvergedResults, T). -run_fake(Init, Model, Seed, Input, N, F, T, Curve, G1, G2, Secret) -> +run_fake(Init, Model, Seed, Input, T) -> {ok, Results} = fakecast:start_test(Init, Model, Seed, Input), ct:pal("results ~p", [Results]), - verify_results(Secret, Results, N, F, T, Curve, G1, G2). + verify_results(Results, T). -verify_results(Secret, ConvergedResults, N, F, T, Curve, G1, G2) -> +verify_results(ConvergedResults, T) -> %% 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) -> @@ -153,48 +108,24 @@ verify_results(Secret, ConvergedResults, N, F, T, Curve, G1, G2) -> AllCommitments = [Commitment || {result, {_Node, {_Session, Commitment, _Share}}} <- sets:to_list(ConvergedResults)], OutputCommitment = hd(AllCommitments), - %[VerificationKey | PublicKeyShares] = dkg_commitment:interpolate(OutputCommitment, ready, lists:seq(1, N)), - VerificationKey = dkg_commitmentmatrix:lookup([1, 1], dkg_commitment:matrix(OutputCommitment)), - true = erlang_pbc:element_cmp(VerificationKey, dkg_commitment:public_key_share(OutputCommitment, 0)), - PublicKeyShares = [dkg_commitment:public_key_share(OutputCommitment, NodeID) || NodeID <- lists:seq(1, N)] , + PublicKeySet = dkg_commitment:public_key_set(OutputCommitment), + PublicKey = tc_public_key_set:public_key(PublicKeySet), - PublicKey = tpke_pubkey:init(N, F, G1, G2, VerificationKey, PublicKeyShares, Curve), - PrivateKeys = [ tpke_privkey:init(PublicKey, Share, NodeID-1) || {NodeID, Share} <- maps:to_list(NodesAndShares)], - Msg = crypto:hash(sha256, crypto:strong_rand_bytes(12)), - MessageToSign = tpke_pubkey:hash_message(PublicKey, Msg), - SignatureShares = [tpke_privkey:sign(PrivateKey, MessageToSign) || PrivateKey <- PrivateKeys], - ?assert(lists:all(fun(E) -> E end, [tpke_pubkey:verify_signature_share(PublicKey, SignatureShare, MessageToSign) || SignatureShare <- SignatureShares])), + PrivateKeys = [ {NodeID, tc_secret_key_share:from_fr(Share)} || {NodeID, Share} <- lists:keysort(1, maps:to_list(NodesAndShares))], + MessageToSign = crypto:hash(sha256, crypto:strong_rand_bytes(12)), + SignatureShares = [{NodeID - 1, tc_secret_key_share:sign(PrivateKey, MessageToSign)} || {NodeID, PrivateKey} <- PrivateKeys], + ct:pal("sig results ~p", [[tc_public_key_share:verify_signature_share(tc_public_key_set:public_key_share(PublicKeySet, I), SignatureShare, MessageToSign) || {I, SignatureShare} <- SignatureShares]]), + ?assert(lists:all(fun(E) -> E end, [tc_public_key_share:verify_signature_share(tc_public_key_set:public_key_share(PublicKeySet, I), SignatureShare, MessageToSign) || {I, SignatureShare} <- SignatureShares])), + {ok, CombinedSignature} = tc_public_key_set:combine_signatures(PublicKeySet, SignatureShares), + ?assert(tc_pubkey:verify(PublicKey, CombinedSignature, MessageToSign)), - case erlang_pbc:element_cmp(G1, G2) of - true -> - Message = crypto:hash(sha256, <<"my hovercraft is full of eels">>), - CipherText = tpke_pubkey:encrypt(PublicKey, Message), - {ok, VerifiedCipherText} = tpke_pubkey:verify_ciphertext(PublicKey, CipherText), - DecShares = [tpke_privkey:decrypt_share(PrivateKey, VerifiedCipherText) || PrivateKey <- PrivateKeys], - ?assert(lists:all(fun(E) -> E end, [tpke_pubkey:verify_share(PublicKey, DecShare, VerifiedCipherText) || DecShare <- DecShares])), - ?assertEqual(Message, tpke_pubkey:combine_shares(PublicKey, VerifiedCipherText, dealer:random_n(T+1, DecShares))); - false -> - ok - end, + Message = crypto:hash(sha256, <<"my hovercraft is full of eels">>), + CipherText = tc_pubkey:encrypt(PublicKey, Message), + true = tc_ciphertext:verify(CipherText), + DecShares = [{NodeID, tc_secret_key_share:decrypt_share(PrivateKey, CipherText)} || {NodeID, PrivateKey} <- PrivateKeys], + ?assert(lists:all(fun(E) -> E end, [tc_public_key_share:verify_decryption_share(tc_public_key_set:public_key_share(PublicKeySet, NodeID-1), DecShare, CipherText) || {NodeID, DecShare} <- DecShares])), + ?assertEqual({ok, Message}, tc_public_key_set:decrypt(PublicKeySet, [{NodeID-1, S} || {NodeID, S} <- dkg_ct_utils:random_n(T+1, DecShares)], CipherText)), true = lists:all(fun(C) -> dkg_commitment:cmp(hd(AllCommitments), C) end, tl(AllCommitments)), - {Indices0, Elements} = lists:unzip(maps:to_list(NodesAndShares)), - Indices = [ erlang_pbc:element_set(erlang_pbc:element_new('Zr', hd(Elements)), I) || I <- Indices0 ], - Shares = lists:foldl(fun(Index, Acc) -> - case maps:is_key(Index, NodesAndShares) of - false -> - %% Node ${Index} has not sent us a share, interpolate it - Alpha = erlang_pbc:element_set(erlang_pbc:element_new('Zr', hd(Elements)), Index), - LagrangePoly = dkg_lagrange:coefficients(Indices, Alpha), - InterpolatedShare = dkg_lagrange:evaluate_zr(LagrangePoly, Elements), - [ InterpolatedShare | Acc]; - true -> - %% Node ${Index} has sent us a share - [ maps:get(Index, NodesAndShares) | Acc] - end - end, [], [0 | lists:seq(1,N)]), %% note that we also evaluate at 0 - - CalculatedSecret = hd(lists:reverse(Shares)), - ?assert(erlang_pbc:element_cmp(CalculatedSecret, Secret)), ok. diff --git a/test/dkg_lagrange_SUITE.erl b/test/dkg_lagrange_SUITE.erl deleted file mode 100644 index 38c3dfc..0000000 --- a/test/dkg_lagrange_SUITE.erl +++ /dev/null @@ -1,84 +0,0 @@ --module(dkg_lagrange_SUITE). --compile({no_auto_import,[evaluate/2]}). - --include_lib("eunit/include/eunit.hrl"). - --export([all/0, init_per_testcase/2, end_per_testcase/2]). --export([numeric_random_poly_test/1, numeric_fx2_test/1, poly_test/1]). - -all() -> - [numeric_random_poly_test, numeric_fx2_test, poly_test]. - -init_per_testcase(_, Config) -> - Indices = lists:seq(1, 4), - ct:pal("Indices: ~p~n", [Indices]), - Alpha = 5, - ct:pal("Alpha: ~p~n", [Alpha]), - Coefficients = coefficients(Indices, Alpha), - ct:pal("Coefficients: ~p~n", [Coefficients]), - [{indices, Indices}, {coefficients, Coefficients}, {alpha, Alpha}| Config]. - -end_per_testcase(_, Config) -> - Config. - -numeric_fx2_test(Config) -> - Indices = proplists:get_value(indices, Config), - Coefficients = proplists:get_value(coefficients, Config), - %% f(x) = x^2 - RandomShares = [ math:pow(Index, 2) || Index <- Indices ], - ct:pal("Random Shares: ~p~n", [RandomShares]), - Applied = evaluate(Coefficients, RandomShares), - ct:pal("Applied: ~p~n", [Applied]), - Applied = 25.0. - -numeric_random_poly_test(Config) -> - Indices = proplists:get_value(indices, Config), - Coefficients = proplists:get_value(coefficients, Config), - %% f(x) = 2x^3 + 7x^2 - 6x +5 - RandomShares = [ (2*math:pow(Index, 3)) + (7*math:pow(Index, 2)) - (6*Index) + 5 || Index <- Indices ], - ct:pal("Random Shares: ~p~n", [RandomShares]), - Applied = evaluate(Coefficients, RandomShares), - ct:pal("Applied: ~p~n", [Applied]), - Applied = 400.0. - -poly_test(_Config) -> - Pairing = erlang_pbc:group_new('SS512'), - Poly = dkg_polynomial:generate(Pairing, 2), - ct:pal("Poly: ~p~n", [dkg_polynomial:print(Poly)]), - Indices = [ erlang_pbc:element_set(erlang_pbc:element_new('Zr', Pairing), I) || I <- lists:seq(1, 3) ], - ct:pal("Indices: ~p~n", [dkg_polynomial:print(Indices)]), - Alpha = erlang_pbc:element_set(erlang_pbc:element_new('Zr', Pairing), 4), - ct:pal("Alpha: ~p~n", [erlang_pbc:element_to_string(Alpha)]), - Coefficients = dkg_lagrange:coefficients(Indices, Alpha), - ct:pal("Coefficients: ~p~n", [dkg_polynomial:print(Coefficients)]), - KnownValueAt4 = dkg_polynomial:evaluate(Poly, 4), - ct:pal("KnownValueAt4: ~p~n", [erlang_pbc:element_to_string(KnownValueAt4)]), - Shares = [ dkg_polynomial:evaluate(Poly, Index) || Index <- lists:seq(1, 3) ], - ct:pal("Shares: ~p~n", [dkg_polynomial:print(Shares)]), - Applied = dkg_lagrange:evaluate_zr(Coefficients, Shares), - ct:pal("Applied: ~p~n", [erlang_pbc:element_to_string(Applied)]), - ?assert(erlang_pbc:element_cmp(KnownValueAt4, Applied)), - ok. - -%% helper functions -coefficients(Indices, Alpha) -> - One = 1, - lists:reverse(lists:foldl(fun({I, Index}, Acc1) -> - Num = lists:foldl(fun(E, Acc) -> - Acc * E - end, One, [ Idx - Alpha || {J, Idx} <- enumerate(Indices), J /= I]), - - Den = lists:foldl(fun(E, Acc) -> - Acc * E - end, One, [ Idx - Index || {J, Idx} <- enumerate(Indices), J /= I]), - [Num/Den | Acc1] - end, [], enumerate(Indices))). - -evaluate(Coefficients, Shares) -> - Zero = 0, - lists:foldl(fun({Coefficient, Share}, Acc) -> - Acc + (Share * Coefficient) - end, Zero, lists:zip(Coefficients, Shares)). - -enumerate(List) -> - lists:zip(lists:seq(1, length(List)), List). diff --git a/test/dkg_multiproc_SUITE.erl b/test/dkg_multiproc_SUITE.erl index 56b1abe..143417b 100644 --- a/test/dkg_multiproc_SUITE.erl +++ b/test/dkg_multiproc_SUITE.erl @@ -5,14 +5,12 @@ -export([all/0, init_per_testcase/2, end_per_testcase/2]). -export([ - symmetric_test/1, - asymmetric_test/1 + symmetric_test/1 ]). all() -> [ - symmetric_test, - asymmetric_test + symmetric_test ]. init_per_testcase(_, Config) -> @@ -29,38 +27,41 @@ symmetric_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), T = proplists:get_value(t, Config), - {G1, G2} = dkg_test_utils:generate('SS512'), - run(N, F, T, 'SS512', G1, G2). + run(N, F, T). -asymmetric_test(Config) -> - N = proplists:get_value(n, Config), - F = proplists:get_value(f, Config), - T = proplists:get_value(t, Config), - {G1, G2} = dkg_test_utils:generate('MNT224'), - run(N, F, T, 'MNT224', G1, G2). +run(N, F, T) -> -run(N, F, T, Curve, G1, G2) -> - Workers = [ element(2, dkg_worker:start_link(Id, N, F, T, Curve, G1, G2, <<0>>)) || Id <- lists:seq(1, N) ], + %fprof:trace(start), + Workers = [ element(2, dkg_worker:start_link(Id, N, F, T, <<0>>)) || Id <- lists:seq(1, N) ], [ dkg_worker:start_round(Worker) || Worker <- Workers ], %% wait for DKG to complete ok = dkg_ct_utils:wait_until(fun() -> lists:all(fun(W) -> dkg_worker:is_done(W) end, Workers) end, 60*2, 500), + + %fprof:trace(stop), + %fprof:profile(), + %fprof:analyse([totals, {dest, "/tmp/dkg.analysis"}]), + %fprof:stop(), + %% check everyone agreed on the same public key Keys = [ dkg_worker:get_pubkey(W) || W <- Workers], - true = lists:all(fun(X) -> tpke_pubkey:serialize(X) == tpke_pubkey:serialize(hd(Keys)) end, Keys), + true = lists:all(fun(X) -> tc_pubkey:cmp(X, hd(Keys)) end, Keys), PubKey = hd(Keys), - case erlang_pbc:pairing_is_symmetric(G1) of - true -> - %% check threshold signatures work - Msg = crypto:hash(sha256, crypto:strong_rand_bytes(12)), - MessageToSign = tpke_pubkey:hash_message(PubKey, Msg), - Signatures = [ dkg_worker:sign_share(W, MessageToSign) || W <- Workers], - ?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)); - false -> - ok - end. + %% check threshold signatures work + MessageToSign= crypto:hash(sha256, crypto:strong_rand_bytes(12)), + Signatures = [ dkg_worker:sign_share(W, MessageToSign) || W <- Workers], + ?assert(lists:all(fun(X) -> X end, [dkg_worker:verify_signature_share(hd(Workers), Share, MessageToSign) || Share <- Signatures])), + {ok, CombinedSignature} = dkg_worker:combine_signature_shares(hd(Workers), dkg_ct_utils:random_n(T+1, Signatures)), + ?assert(tc_pubkey:verify(PubKey, CombinedSignature, MessageToSign)), + + %% check threshold decryption works + Message = crypto:hash(sha256, <<"my hovercraft is full of eels">>), + CipherText = tc_pubkey:encrypt(PubKey, Message), + true = tc_ciphertext:verify(CipherText), + DecShares = [ dkg_worker:dec_share(W, CipherText) || W <- Workers], + ?assert(lists:all(fun(E) -> E end, [dkg_worker:verify_decryption_share(hd(Workers), DecShare, CipherText) || DecShare <- DecShares])), + ?assertEqual({ok, Message}, dkg_worker:combine_decryption_shares(hd(Workers), dkg_ct_utils:random_n(T+1, DecShares), CipherText)), + ok. diff --git a/test/dkg_polynomial_SUITE.erl b/test/dkg_polynomial_SUITE.erl deleted file mode 100644 index 128093f..0000000 --- a/test/dkg_polynomial_SUITE.erl +++ /dev/null @@ -1,97 +0,0 @@ --module(dkg_polynomial_SUITE). --compile({no_auto_import,[evaluate/2]}). - --include_lib("eunit/include/eunit.hrl"). - --export([all/0, init_per_testcase/2, end_per_testcase/2]). - --export([constant_term_test/1, - self_subtract_test/1, - self_add_test/1, - mul_by_zero_test/1, - mul_by_one_test/1, - negative_comparison_test/1, - f_of_x_test/1]). - -all() -> - [constant_term_test, - self_subtract_test, - self_add_test, - mul_by_one_test, - mul_by_zero_test, - negative_comparison_test, - f_of_x_test]. - -init_per_testcase(_, Config) -> - Pairing = erlang_pbc:group_new('SS512'), - [{pairing, Pairing} | Config]. - -end_per_testcase(_, Config) -> - Config. - -constant_term_test(Config) -> - Pairing = proplists:get_value(pairing, Config), - PolyA = dkg_polynomial:generate(Pairing, 5, 91), - PolyB = dkg_polynomial:generate(Pairing, 5, 91), - PolyC = dkg_polynomial:sub(PolyA, PolyB), - NinetyOne = erlang_pbc:element_set(erlang_pbc:element_new('Zr', Pairing), 91), - %% first term should now be 0 - ?assert(erlang_pbc:element_is0(hd(PolyC))), - ?assertEqual("0", hd(dkg_polynomial:print(PolyC))), - ?assert(erlang_pbc:element_cmp(hd(PolyA), NinetyOne)), - ?assert(erlang_pbc:element_cmp(hd(PolyB), NinetyOne)), - ok. - -self_subtract_test(Config) -> - Pairing = proplists:get_value(pairing, Config), - Poly = dkg_polynomial:generate(Pairing, 5), - %% subtracting a polynomial from itself should yield all 0s - ZeroPoly = dkg_polynomial:sub(Poly, Poly), - ?assert(dkg_polynomial:is_zero(ZeroPoly)). - -self_add_test(Config) -> - Pairing = proplists:get_value(pairing, Config), - Poly = dkg_polynomial:generate(Pairing, 5), - DoublePoly = dkg_polynomial:add(Poly, Poly), - %% check the length remains the same - ?assertEqual(6, length(Poly)), - ?assertEqual(6, length(DoublePoly)), - %% check that each element is double the original - ?assert(lists:all(fun({A, B}) -> erlang_pbc:element_cmp(A, erlang_pbc:element_div(B, 2)) end, - lists:zip(Poly, DoublePoly))). - -mul_by_zero_test(Config) -> - Pairing = proplists:get_value(pairing, Config), - Poly = dkg_polynomial:generate(Pairing, 5), - MulPoly = dkg_polynomial:mul(Poly, [erlang_pbc:element_set(erlang_pbc:element_new('Zr', Pairing), 0)]), - %% check the length remains the same - ?assertEqual(6, length(Poly)), - ?assert(dkg_polynomial:is_zero(MulPoly)). - -mul_by_one_test(Config) -> - Pairing = proplists:get_value(pairing, Config), - Poly = dkg_polynomial:generate(Pairing, 5), - MulPoly = dkg_polynomial:mul(Poly, [erlang_pbc:element_set(erlang_pbc:element_new('Zr', Pairing), 1)]), - %% check the length remains the same - ?assertEqual(6, length(Poly)), - ?assertEqual(6, length(MulPoly)), - ?assert(dkg_polynomial:cmp(Poly, MulPoly)). - -f_of_x_test(Config) -> - Pairing = proplists:get_value(pairing, Config), - Five = erlang_pbc:element_set(erlang_pbc:element_new('Zr', Pairing), 5), - Six = erlang_pbc:element_set(erlang_pbc:element_new('Zr', Pairing), 6), - Poly = dkg_polynomial:generate(Pairing, 2, Six, Five), - Ans = dkg_polynomial:evaluate(Poly, Six), - ?assert(erlang_pbc:element_cmp(Five, Ans)). - -negative_comparison_test(Config) -> - Pairing = proplists:get_value(pairing, Config), - Poly = dkg_polynomial:generate(Pairing, 5), - DoublePoly = dkg_polynomial:mul(Poly, [erlang_pbc:element_set(erlang_pbc:element_new('Zr', Pairing), 2)]), - %% check that the elements in DoublePoly are double of Poly - ?assert(lists:all(fun({A, B}) -> - erlang_pbc:element_cmp(erlang_pbc:element_mul(A, 2), B) - end, lists:zip(Poly, DoublePoly))), - %% check that the poly doesnt match with DoublePoly, f(x) /= 2*f(x) - ?assertEqual(false, dkg_polynomial:cmp(Poly, DoublePoly)). diff --git a/test/dkg_relcast_distributed_SUITE.erl b/test/dkg_relcast_distributed_SUITE.erl index ded009f..9078c05 100644 --- a/test/dkg_relcast_distributed_SUITE.erl +++ b/test/dkg_relcast_distributed_SUITE.erl @@ -60,8 +60,7 @@ symmetric_test(Config) -> F = proplists:get_value(f, Config), T = proplists:get_value(t, Config), DataDir = proplists:get_value(data_dir, Config), - {G1, G2} = dkg_test_utils:generate('SS512'), - run(N, F, T, 'SS512', G1, G2, Nodes, DataDir), + run(N, F, T, Nodes, DataDir), ok. asymmetric_test(Config) -> @@ -70,12 +69,11 @@ asymmetric_test(Config) -> F = proplists:get_value(f, Config), T = proplists:get_value(t, Config), DataDir = proplists:get_value(data_dir, Config), - {G1, G2} = dkg_test_utils:generate('MNT224'), - run(N, F, T, 'MNT224', G1, G2, Nodes, DataDir), + run(N, F, T, Nodes, DataDir), ok. -run(N, F, T, Curve, G1, G2, Nodes, DataDir) -> +run(N, F, T, Nodes, DataDir) -> %% load dkg_relcast_worker on each node {Mod, Bin, _} = code:get_object_code(dkg_relcast_worker), _ = dkg_ct_utils:pmap(fun(Node) -> @@ -90,9 +88,6 @@ run(N, F, T, Curve, G1, G2, Nodes, DataDir) -> {n, N}, {f, F}, {t, T}, - {curve, Curve}, - {g1, erlang_pbc:element_to_binary(G1)}, - {g2, erlang_pbc:element_to_binary(G2)}, {data_dir, DataDir}, {round, <<0>>}]])} || {I, Node} <- dkg_test_utils:enumerate(Nodes)], ok = global:sync(), diff --git a/test/dkg_relcast_multiproc_SUITE.erl b/test/dkg_relcast_multiproc_SUITE.erl index eedc078..4803dda 100644 --- a/test/dkg_relcast_multiproc_SUITE.erl +++ b/test/dkg_relcast_multiproc_SUITE.erl @@ -5,14 +5,12 @@ -export([all/0, init_per_testcase/2, end_per_testcase/2]). -export([ - symmetric_test/1, - asymmetric_test/1 + symmetric_test/1 ]). all() -> [ - symmetric_test, - asymmetric_test + symmetric_test ]. init_per_testcase(TestCase, Config) -> @@ -33,28 +31,16 @@ symmetric_test(Config) -> T = proplists:get_value(t, Config), R = proplists:get_value(round, Config), DataDir = proplists:get_value(data_dir, Config), - Curve = 'SS512', - {G1, G2} = dkg_test_utils:generate(Curve), - run(N, F, T, R, Curve, G1, G2, DataDir). + run(N, F, T, R, DataDir). -asymmetric_test(Config) -> - N = proplists:get_value(n, Config), - F = proplists:get_value(f, Config), - T = proplists:get_value(t, Config), - R = proplists:get_value(round, Config), - DataDir = proplists:get_value(data_dir, Config), - Curve = 'MNT224', - {G1, G2} = dkg_test_utils:generate(Curve), - run(N, F, T, R, Curve, G1, G2, DataDir). +run(N, F, T, R, DataDir) -> -run(N, F, T, R, Curve, G1, G2, DataDir) -> + %fprof:trace([start, {procs, all}]), + fprof:trace(start), Workers = [ element(2, dkg_relcast_worker:start_link([{id, Id}, {n, N}, {f, F}, {t, T}, - {curve, Curve}, - {g1, G1}, - {g2, G2}, {data_dir, DataDir}, {round, R}, {callback, true}])) || Id <- lists:seq(1, N) ], @@ -64,20 +50,31 @@ run(N, F, T, R, Curve, G1, G2, DataDir) -> ok = dkg_ct_utils:wait_until(fun() -> lists:all(fun(W) -> dkg_relcast_worker:is_done(W) end, Workers) end, 60*2, 500), + + fprof:trace(stop), + %fprof:trace([stop]), + fprof:profile(), + fprof:analyse([totals, {dest, "/tmp/dkg.analysis"}]), + fprof:stop(), + %% check everyone agreed on the same public key Keys = [ dkg_relcast_worker:get_pubkey(W) || W <- Workers], - true = lists:all(fun(X) -> tpke_pubkey:serialize(X) == tpke_pubkey:serialize(hd(Keys)) end, Keys), - PubKey = hd(Keys), + true = lists:all(fun(X) -> tc_public_key_set:serialize(X) == tc_public_key_set:serialize(hd(Keys)) end, Keys), + PubKey = tc_public_key_set:public_key(hd(Keys)), + PubKeySet = hd(Keys), - case erlang_pbc:pairing_is_symmetric(G1) of - true -> - %% check threshold signatures work - Msg = crypto:hash(sha256, crypto:strong_rand_bytes(12)), - MessageToSign = tpke_pubkey:hash_message(PubKey, Msg), - Signatures = [ dkg_relcast_worker:sign_share(W, MessageToSign) || W <- Workers], - ?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)); - false -> - ok - end. + %% check threshold signatures work + MessageToSign= crypto:hash(sha256, crypto:strong_rand_bytes(12)), + Signatures = [ dkg_relcast_worker:sign_share(W, MessageToSign) || W <- Workers], + ?assert(lists:all(fun(X) -> X end, [tc_public_key_share:verify_signature_share(tc_public_key_set:public_key_share(PubKeySet, I), Share, MessageToSign) || {I, Share} <- Signatures])), + {ok, CombinedSignature} = tc_public_key_set:combine_signatures(PubKeySet, dkg_ct_utils:random_n(T+1, Signatures)), + ?assert(tc_pubkey:verify(PubKey, CombinedSignature, MessageToSign)), + + %% check threshold decryption works + Message = crypto:hash(sha256, <<"my hovercraft is full of eels">>), + CipherText = tc_pubkey:encrypt(PubKey, Message), + true = tc_ciphertext:verify(CipherText), + DecShares = [ dkg_relcast_worker:dec_share(W, CipherText) || W <- Workers], + ?assert(lists:all(fun(E) -> E end, [tc_public_key_share:verify_decryption_share(tc_public_key_set:public_key_share(PubKeySet, NodeID), DecShare, CipherText) || {NodeID, DecShare} <- DecShares])), + ?assertEqual({ok, Message}, tc_public_key_set:decrypt(PubKeySet, dkg_ct_utils:random_n(T+1, DecShares), CipherText)), + ok. diff --git a/test/dkg_test_utils.erl b/test/dkg_test_utils.erl index e275964..e3f3147 100644 --- a/test/dkg_test_utils.erl +++ b/test/dkg_test_utils.erl @@ -1,6 +1,6 @@ -module(dkg_test_utils). --export([do_send_outer/4, random_n/2, enumerate/1, generate/1]). +-export([do_send_outer/4, random_n/2, enumerate/1]). do_send_outer(Mod, [], States, Acc) -> case get_timers() of @@ -70,12 +70,3 @@ shuffle(List) -> enumerate(List) -> lists:zip(lists:seq(1, length(List)), List). - -generate(Curve) -> - Group = erlang_pbc:group_new(Curve), - 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, - {G1, G2}. diff --git a/test/dkg_worker.erl b/test/dkg_worker.erl index 68c974a..6f4c091 100644 --- a/test/dkg_worker.erl +++ b/test/dkg_worker.erl @@ -2,22 +2,32 @@ -behaviour(gen_server). --export([start_link/8, start_round/1, is_done/1, get_pubkey/1, sign_share/2, dec_share/2]). +-export([ + start_link/5, + start_round/1, + is_done/1, + dkg_status/1, + get_pubkey/1, + sign_share/2, + dec_share/2, + verify_signature_share/3, + combine_signature_shares/2, + verify_decryption_share/3, + combine_decryption_shares/3 +]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2]). -record(state, { - n :: pos_integer(), - f :: pos_integer(), - t :: pos_integer(), - id :: pos_integer(), - dkg :: dkg_hybriddkg:dkg(), - curve :: 'SS512' | 'MNT224', - g1 :: erlang_pbc:element(), - g2 :: erlang_pbc:element(), - round :: binary(), - privkey :: undefined | tpke_privkey:privkey() - }). + n :: pos_integer(), + f :: pos_integer(), + t :: pos_integer(), + id :: pos_integer(), + dkg :: dkg_hybriddkg:dkg(), + round :: binary(), + key_share :: undefined | tc_key_share:tc_key_share(), + commitment_cache_fun +}). start_round(Pid) -> gen_server:cast(Pid, start_round). @@ -25,6 +35,9 @@ start_round(Pid) -> is_done(Pid) -> gen_server:call(Pid, is_done, infinity). +dkg_status(Pid) -> + gen_server:call(Pid, dkg_status, infinity). + get_pubkey(Pid) -> gen_server:call(Pid, get_pubkey, infinity). @@ -34,28 +47,57 @@ sign_share(Pid, Msg) -> dec_share(Pid, Share) -> gen_server:call(Pid, {dec_share, Share}, infinity). -start_link(Id, N, F, T, Curve, G1, G2, Round) -> - gen_server:start_link({global, name(Id)}, ?MODULE, [Id, N, F, T, Curve, G1, G2, Round], []). - -init([Id, N, F, T, Curve, G10, G20, Round]) -> - {G1, G2} = case is_binary(G10) andalso is_binary(G20) of - true -> - Group = erlang_pbc:group_new(Curve), - {erlang_pbc:binary_to_element(Group, G10), erlang_pbc:binary_to_element(Group, G20)}; - false -> - {G10, G20} - end, - DKG = dkg_hybriddkg:init(Id, N, F, T, G1, G2, Round, [{signfun, fun(_) -> <<"lol">> end}, {verifyfun, fun(_, _, _) -> true end}]), - {ok, #state{n=N, f=F, t=T, id=Id, curve=Curve, g1=G1, g2=G2, round=Round, dkg=DKG}}. +verify_signature_share(Pid, Share, Msg) -> + gen_server:call(Pid, {verify_signature_share, Share, Msg}, infinity). + +combine_signature_shares(Pid, Shares) -> + gen_server:call(Pid, {combine_signature_shares, Shares}, infinity). + +verify_decryption_share(Pid, Share, CipherText) -> + gen_server:call(Pid, {verify_decryption_share, Share, CipherText}, infinity). + +combine_decryption_shares(Pid, Shares, CipherText) -> + gen_server:call(Pid, {combine_decryption_shares, Shares, CipherText}, infinity). + +start_link(Id, N, F, T, Round) -> + gen_server:start_link({global, name(Id)}, ?MODULE, [Id, N, F, T, Round], []). + +init([Id, N, F, T, Round]) -> + CCacheFun = dkg_util:commitment_cache_fun(), + DKG = dkg_hybriddkg:init(Id, N, F, T, Round, [ + {signfun, fun(_) -> <<"lol">> end}, + {verifyfun, fun(_, _, _) -> true end}, + {commitment_cache_fun, CCacheFun} + ]), + {ok, #state{ + n = N, + f = F, + t = T, + id = Id, + round = Round, + dkg = DKG, + commitment_cache_fun = CCacheFun + }}. handle_call(is_done, _From, State) -> - {reply, State#state.privkey /= undefined, State}; + {reply, State#state.key_share /= undefined, State}; +handle_call(dkg_status, _From, #state{dkg = DKG} = State) -> + {reply, dkg_hybriddkg:status(DKG), State}; handle_call(get_pubkey, _From, State) -> - {reply, tpke_privkey:public_key(State#state.privkey), State}; + {reply, tc_key_share:public_key(State#state.key_share), State}; handle_call({sign_share, MessageToSign}, _From, State) -> - {reply, tpke_privkey:sign(State#state.privkey, MessageToSign), State}; + {reply, tc_key_share:sign_share(State#state.key_share, MessageToSign), State}; handle_call({dec_share, CipherText}, _From, State) -> - {reply, tpke_privkey:decrypt_share(State#state.privkey, CipherText), State}; + {reply, tc_key_share:decrypt_share(State#state.key_share, CipherText), State}; +handle_call({combine_signature_shares, Shares}, _From, State) -> + {reply, tc_key_share:combine_signature_shares(State#state.key_share, Shares), State}; +handle_call({verify_signature_share, Share, Msg}, _From, State) -> + {reply, tc_key_share:verify_signature_share(State#state.key_share, Share, Msg), State}; +handle_call({combine_decryption_shares, Shares, CipherText}, _From, State) -> + {reply, tc_key_share:combine_decryption_shares(State#state.key_share, Shares, CipherText), + State}; +handle_call({verify_decryption_share, Share, CipherText}, _From, State) -> + {reply, tc_key_share:verify_decryption_share(State#state.key_share, Share, CipherText), State}; handle_call(Msg, _From, State) -> io:format("unhandled msg ~p~n", [Msg]), {reply, ok, State}. @@ -63,7 +105,7 @@ handle_call(Msg, _From, State) -> handle_cast(start_round, State) -> NewState = dispatch(dkg_hybriddkg:start(State#state.dkg), State), {noreply, NewState}; -handle_cast({dkg, PeerID, Msg}, State = #state{dkg=DKG}) -> +handle_cast({dkg, PeerID, Msg}, State = #state{dkg = DKG}) -> NewState = dispatch(dkg_hybriddkg:handle_msg(DKG, PeerID, Msg), State), {noreply, NewState}; handle_cast(Msg, State) -> @@ -74,11 +116,8 @@ handle_info(Msg, State) -> io:format("unhandled msg ~p~n", [Msg]), {noreply, State}. -dispatch({NewDKG, {result, {Shard, VerificationKey, VerificationKeys}}}, State) -> - %ct:pal("~p finished", [State#state.id]), - PubKey = tpke_pubkey:init(State#state.n, State#state.t, State#state.g1, State#state.g2, VerificationKey, VerificationKeys, State#state.curve), - PrivKey = tpke_privkey:init(PubKey, Shard, State#state.id - 1), - update_dkg(NewDKG, State#state{privkey=PrivKey}); +dispatch({NewDKG, {result, KeyShare}}, State) -> + update_dkg(NewDKG, State#state{key_share = KeyShare}); dispatch({NewDKG, {send, ToSend}}, State) -> do_send(ToSend, State), update_dkg(NewDKG, State); @@ -86,26 +125,33 @@ dispatch({NewDKG, ok}, State) -> update_dkg(NewDKG, State); dispatch({NewDKG, Other}, State) -> io:format("UNHANDLED ~p~n", [Other]), - State#state{dkg=NewDKG}; + State#state{dkg = NewDKG}; dispatch(Other, State) -> io:format("UNHANDLED2 ~p~n", [Other]), State. do_send([], _) -> ok; -do_send([{unicast, Dest, Msg}|T], State) -> +do_send([{unicast, Dest, Msg} | T], State) -> gen_server:cast({global, name(Dest)}, {dkg, State#state.id, Msg}), do_send(T, State); -do_send([{multicast, Msg}|T], State) -> +do_send([{multicast, Msg} | T], State) -> %io:format("~p multicasting ~p to ~p~n", [State#state.id, Msg, [global:whereis_name(name(Dest)) || Dest <- lists:seq(1, State#state.n)]]), - [ gen_server:cast({global, name(Dest)}, {dkg, State#state.id, Msg}) || Dest <- lists:seq(1, State#state.n)], + [ + gen_server:cast({global, name(Dest)}, {dkg, State#state.id, Msg}) + || Dest <- lists:seq(1, State#state.n) + ], do_send(T, State). %% helper functions -update_dkg(DKG, State)-> - NewDKG = dkg_hybriddkg:deserialize(dkg_hybriddkg:serialize(DKG), State#state.g1, fun(_) -> <<"lol">> end, fun(_, _, _) -> true end), - State#state{dkg=NewDKG}. +update_dkg(DKG, State) -> + NewDKG = dkg_hybriddkg:deserialize( + dkg_hybriddkg:serialize(DKG), + fun(_) -> <<"lol">> end, + fun(_, _, _) -> true end, + State#state.commitment_cache_fun + ), + State#state{dkg = NewDKG}. name(N) -> list_to_atom(lists:flatten(["dkg_worker_", integer_to_list(N)])). -