From 4460bcc7cbc19e9760576efed10af0b53acee0a0 Mon Sep 17 00:00:00 2001 From: Rahul Garg Date: Fri, 26 Mar 2021 01:40:26 +0530 Subject: [PATCH] Add support for erlang-tc key shares. PBC compatability is retained. --- rebar.config | 3 +- rebar.lock | 10 +- src/hbbft.app.src | 5 +- src/hbbft.erl | 1172 ++++++++++++++-------- src/hbbft_acs.erl | 8 +- src/hbbft_bba.erl | 11 +- src/hbbft_cc.erl | 131 ++- src/hbbft_utils.erl | 52 +- test/hbbft_SUITE.erl | 580 +++++++---- test/hbbft_acs_SUITE.erl | 27 +- test/hbbft_bba_SUITE.erl | 43 +- test/hbbft_cc_SUITE.erl | 101 +- test/hbbft_distributed_SUITE.erl | 470 ++++++--- test/hbbft_handler.erl | 10 +- test/hbbft_mr_fake_SUITE.erl | 8 +- test/hbbft_relcast_SUITE.erl | 22 +- test/hbbft_relcast_distributed_SUITE.erl | 14 +- test/hbbft_relcast_worker.erl | 27 +- test/hbbft_test_utils.erl | 18 +- test/hbbft_worker.erl | 40 +- 20 files changed, 1781 insertions(+), 971 deletions(-) diff --git a/rebar.config b/rebar.config index 40996e1..d8b35ec 100644 --- a/rebar.config +++ b/rebar.config @@ -3,7 +3,8 @@ {cover_export_enabled, true}. {cover_enabled, true}. {deps, [ - {erlang_tpke, {git, "https://github.com/helium/erlang_tpke.git", {branch, "master"}}}, + {erlang_tc, ".*", {git, "https://github.com/helium/erlang-tc.git", {branch, "main"}}}, + {erlang_tpke, ".*", {git, "https://github.com/helium/erlang-tpke.git", {branch, "master"}}}, {erasure, {git, "https://github.com/helium/erlang-erasure.git", {branch, "master"}}}, {merkerl, "1.0.1"} ]}. diff --git a/rebar.lock b/rebar.lock index 8d96e1a..2488c37 100644 --- a/rebar.lock +++ b/rebar.lock @@ -5,11 +5,15 @@ 0}, {<<"erlang_pbc">>, {git,"https://github.com/helium/erlang_pbc.git", - {ref,"120fceedd4a918aec02422c864f1aefd1de1459c"}}, + {ref,"1d2651ba01ba81b748c553d9f729c0e167eeab72"}}, 1}, + {<<"erlang_tc">>, + {git,"https://github.com/helium/erlang-tc.git", + {ref,"b3ef1d5541586f5c85b6d231345a921d57be32a3"}}, + 0}, {<<"erlang_tpke">>, - {git,"https://github.com/helium/erlang_tpke.git", - {ref,"0d9f8c5b30c10c73727402465963e6a33e680a56"}}, + {git,"https://github.com/helium/erlang-tpke.git", + {ref,"02e955cd16ca31519b1b9aaf46cf98ddc406b469"}}, 0}, {<<"merkerl">>,{pkg,<<"merkerl">>,<<"1.0.1">>},0}]}. [ diff --git a/src/hbbft.app.src b/src/hbbft.app.src index 0488934..aa2d444 100644 --- a/src/hbbft.app.src +++ b/src/hbbft.app.src @@ -6,8 +6,9 @@ [kernel, stdlib, merkerl, - erasure, - erlang_tpke + erlang_tc, + erlang_tpke, + erasure ]}, {env,[]}, {modules, []}, diff --git a/src/hbbft.erl b/src/hbbft.erl index 54e1813..afe16d6 100644 --- a/src/hbbft.erl +++ b/src/hbbft.erl @@ -1,179 +1,266 @@ -module(hbbft). --export([init/6, init/7, init/9, - get_stamp_fun/1, - set_stamp_fun/4, - start_on_demand/1, - input/2, - finalize_round/3, - finalize_round/2, - next_round/1, - next_round/3, - round/1, - buf/1, buf/2, - encrypt/2, - decrypt/2, - handle_msg/3, - serialize/1, - serialize/2, - deserialize/2, - status/1, - have_key/1, - is_serialized/1]). +-export([ + init/6, init/7, + init/9, + get_stamp_fun/1, + set_stamp_fun/4, + start_on_demand/1, + input/2, + finalize_round/3, + finalize_round/2, + next_round/1, + next_round/3, + round/1, + buf/1, buf/2, + handle_msg/3, + serialize/1, + serialize/2, + deserialize/2, + status/1, + have_key/1, + is_serialized/1 +]). -ifdef(TEST). -export([ - encode_list/1, - get_encrypted_key/2, - abstraction_breaking_set_acs_results/2, - abstraction_breaking_set_enc_keys/2 - ]). + encrypt/3, + get_encrypted_key/2, + decrypt/2, + encode_list/1, + abstraction_breaking_set_acs_results/2, + abstraction_breaking_set_enc_keys/2 +]). -endif. -type acs_results() :: [{non_neg_integer(), binary()}]. --type enc_keys() :: #{non_neg_integer() => tpke_pubkey:ciphertext()}. +-type enc_keys() :: #{non_neg_integer() => tc_ciphertext:ciphertext() | tpke_pubkey:ciphertext()}. -record(hbbft_data, { - batch_size :: pos_integer(), - secret_key :: undefined | tpke_privkey:privkey(), - n :: pos_integer(), - f :: pos_integer(), - j :: non_neg_integer(), - round = 0 :: non_neg_integer(), - buf = [] :: [binary()], - max_buf = infinity :: infinity | pos_integer(), - acs :: hbbft_acs:acs_data(), - acs_init = false :: boolean(), - sent_txns = false :: boolean(), - sent_sig = false :: boolean(), - acs_results = [] :: acs_results(), - enc_keys = #{} :: enc_keys(), %% will only ever hold verified ciphertexts - dec_shares = #{} :: #{{non_neg_integer(), non_neg_integer()} => {boolean() | undefined, {non_neg_integer(), erlang_pbc:element()}}}, - decrypted = #{} :: #{non_neg_integer() => [binary()]}, - sig_shares = #{} :: #{non_neg_integer() => {non_neg_integer(), erlang_pbc:element()}}, - thingtosign :: undefined | erlang_pbc:element(), - stampfun :: undefined | {atom(), atom(), list()}, - stamps = [] :: [{non_neg_integer(), binary()}], - failed_combine = [] :: [non_neg_integer()], - failed_decrypt = [] :: [non_neg_integer()] - }). + batch_size :: pos_integer(), + curve :: curve(), + key_share :: key_share(), + n :: pos_integer(), + f :: pos_integer(), + j :: non_neg_integer(), + round = 0 :: non_neg_integer(), + buf = [] :: [binary()], + max_buf = infinity :: infinity | pos_integer(), + acs :: hbbft_acs:acs_data(), + acs_init = false :: boolean(), + sent_txns = false :: boolean(), + sent_sig = false :: boolean(), + acs_results = [] :: acs_results(), + %% will only ever hold verified ciphertexts + enc_keys = #{} :: enc_keys(), + dec_shares = #{} :: #{ + {non_neg_integer(), non_neg_integer()} => + {boolean() | undefined, {non_neg_integer(), tc_decryption_share:dec_share()} | erlang_pbc:element()} + }, + decrypted = #{} :: #{non_neg_integer() => [binary()]}, + sig_shares = #{} :: #{non_neg_integer() => {non_neg_integer(), tc_signature_share:sig_share() | erlang_pbc:element()}}, + thingtosign :: undefined | binary() | erlang_pbc:element(), + stampfun :: undefined | {atom(), atom(), list()}, + stamps = [] :: [{non_neg_integer(), binary()}], + failed_combine = [] :: [non_neg_integer()], + failed_decrypt = [] :: [non_neg_integer()] +}). +-type curve() :: 'SS512' | 'BLS12-381'. +-type key_share() :: undefined | tc_key_share:tc_key_share() | tpke_privkey:privkey(). -type hbbft_data() :: #hbbft_data{}. -type acs_msg() :: {{acs, non_neg_integer()}, hbbft_acs:msgs()}. -type dec_msg() :: {dec, non_neg_integer(), non_neg_integer(), {non_neg_integer(), binary()}}. -type sign_msg() :: {sign, non_neg_integer(), binary()}. --type rbc_wrapped_output() :: hbbft_utils:unicast({{acs, non_neg_integer()}, {{rbc, non_neg_integer()}, hbbft_rbc:val_msg()}}) | hbbft_utils:multicast({{acs, non_neg_integer()}, {{rbc, non_neg_integer()}, hbbft_rbc:echo_msg() | hbbft_rbc:ready_msg()}}). --type bba_wrapped_output() :: hbbft_utils:multicast({{acs, non_neg_integer()}, hbbft_acs:bba_msg()}). +-type rbc_wrapped_output() :: + hbbft_utils:unicast({{acs, non_neg_integer()}, {{rbc, non_neg_integer()}, hbbft_rbc:val_msg()}}) + | hbbft_utils:multicast( + {{acs, non_neg_integer()}, + {{rbc, non_neg_integer()}, hbbft_rbc:echo_msg() | hbbft_rbc:ready_msg()}} + ). +-type bba_wrapped_output() :: hbbft_utils:multicast( + {{acs, non_neg_integer()}, hbbft_acs:bba_msg()} +). + +-export_type([curve/0, key_share/0]). -if(?OTP_RELEASE > 22). %% Ericsson why do you hate us so? --define(ENCRYPT(Key, IV, AAD, PlainText, TagLength), crypto:crypto_one_time_aead(aes_256_gcm, Key, IV, PlainText, AAD, TagLength, true)). --define(DECRYPT(Key, IV, AAD, CipherText, Tag), crypto:crypto_one_time_aead(aes_256_gcm, Key, IV, CipherText, AAD, Tag, false)). +-define(ENCRYPT(Key, IV, AAD, PlainText, TagLength), + crypto:crypto_one_time_aead(aes_256_gcm, Key, IV, PlainText, AAD, TagLength, true) +). +-define(DECRYPT(Key, IV, AAD, CipherText, Tag), + crypto:crypto_one_time_aead(aes_256_gcm, Key, IV, CipherText, AAD, Tag, false) +). -else. --define(ENCRYPT(Key, IV, AAD, PlainText, TagLength), crypto:block_encrypt(aes_gcm, Key, IV, {AAD, PlainText, TagLength})). --define(DECRYPT(Key, IV, AAD, CipherText, Tag), crypto:block_decrypt(aes_gcm, Key, IV, {AAD, CipherText, Tag})). +-define(ENCRYPT(Key, IV, AAD, PlainText, TagLength), + crypto:block_encrypt(aes_gcm, Key, IV, {AAD, PlainText, TagLength}) +). +-define(DECRYPT(Key, IV, AAD, CipherText, Tag), + crypto:block_decrypt(aes_gcm, Key, IV, {AAD, CipherText, Tag}) +). -endif. -spec have_key(hbbft_data()) -> boolean(). -have_key(#hbbft_data{secret_key = Key}) -> +have_key(#hbbft_data{key_share = Key}) -> %% we don't have a key if it's undefined Key /= undefined. -spec status(hbbft_data()) -> map(). status(HBBFTData) -> - #{batch_size => HBBFTData#hbbft_data.batch_size, - buf => length(HBBFTData#hbbft_data.buf), - max_buf => HBBFTData#hbbft_data.max_buf, - round => HBBFTData#hbbft_data.round, - acs_init => HBBFTData#hbbft_data.acs_init, - acs => hbbft_acs:status(HBBFTData#hbbft_data.acs), - sent_txns => HBBFTData#hbbft_data.sent_txns, - sent_sig => HBBFTData#hbbft_data.sent_sig, - acs_results => element(1, lists:unzip(HBBFTData#hbbft_data.acs_results)), - decryption_shares => group_by(maps:keys(HBBFTData#hbbft_data.dec_shares)), - valid_decryption_shares => group_by(maps:keys(maps:filter(fun(_, {Valid, _}) -> Valid == true end, HBBFTData#hbbft_data.dec_shares))), - invalid_decryption_shares => group_by(maps:keys(maps:filter(fun(_, {Valid, _}) -> Valid == false end, HBBFTData#hbbft_data.dec_shares))), - unvalidated_decryption_shares => group_by(maps:keys(maps:filter(fun(_, {Valid, _}) -> Valid == undefined end, HBBFTData#hbbft_data.dec_shares))), - decrypted => maps:keys(HBBFTData#hbbft_data.decrypted), - j => HBBFTData#hbbft_data.j, - failed_combine => HBBFTData#hbbft_data.failed_combine, - failed_decrypt => HBBFTData#hbbft_data.failed_decrypt - }. - --spec init(tpke_privkey:privkey(), pos_integer(), non_neg_integer(), non_neg_integer(), pos_integer(), infinity | pos_integer()) -> hbbft_data(). -init(SK, N, F, J, BatchSize, MaxBuf) -> - init(SK, N, F, J, BatchSize, MaxBuf, undefined, 0, []). - --spec init(tpke_privkey:privkey(), pos_integer(), non_neg_integer(), non_neg_integer(), pos_integer(), infinity | pos_integer(), {atom(), atom(), list()}) -> hbbft_data(). -init(SK, N, F, J, BatchSize, MaxBuf, {M, Fn, A}) -> - init(SK, N, F, J, BatchSize, MaxBuf, {M, Fn, A}, 0, []). - -init(SK, N, F, J, BatchSize, MaxBuf, StampFun, Round, Buf) -> - #hbbft_data{secret_key=SK, - n=N, f=F, j=J, - batch_size=BatchSize, - acs=hbbft_acs:init(SK, N, F, J), - round = Round, - buf = Buf, - max_buf=MaxBuf, - stampfun=StampFun}. + #{ + curve => HBBFTData#hbbft_data.curve, + batch_size => HBBFTData#hbbft_data.batch_size, + buf => length(HBBFTData#hbbft_data.buf), + max_buf => HBBFTData#hbbft_data.max_buf, + round => HBBFTData#hbbft_data.round, + acs_init => HBBFTData#hbbft_data.acs_init, + acs => hbbft_acs:status(HBBFTData#hbbft_data.acs), + sent_txns => HBBFTData#hbbft_data.sent_txns, + sent_sig => HBBFTData#hbbft_data.sent_sig, + acs_results => element(1, lists:unzip(HBBFTData#hbbft_data.acs_results)), + decryption_shares => group_by(maps:keys(HBBFTData#hbbft_data.dec_shares)), + valid_decryption_shares => group_by( + maps:keys( + maps:filter( + fun(_, {Valid, _}) -> Valid == true end, + HBBFTData#hbbft_data.dec_shares + ) + ) + ), + invalid_decryption_shares => group_by( + maps:keys( + maps:filter( + fun(_, {Valid, _}) -> Valid == false end, + HBBFTData#hbbft_data.dec_shares + ) + ) + ), + unvalidated_decryption_shares => group_by( + maps:keys( + maps:filter( + fun(_, {Valid, _}) -> Valid == undefined end, + HBBFTData#hbbft_data.dec_shares + ) + ) + ), + decrypted => maps:keys(HBBFTData#hbbft_data.decrypted), + j => HBBFTData#hbbft_data.j, + failed_combine => HBBFTData#hbbft_data.failed_combine, + failed_decrypt => HBBFTData#hbbft_data.failed_decrypt + }. + +-spec init( + tc_key_share:tc_key_share(), + pos_integer(), + non_neg_integer(), + non_neg_integer(), + pos_integer(), + infinity | pos_integer() +) -> hbbft_data(). +init(KeyShare, N, F, J, BatchSize, MaxBuf) -> + init(KeyShare, N, F, J, BatchSize, MaxBuf, undefined, 0, []). + +-spec init( + tc_key_share:tc_key_share(), + pos_integer(), + non_neg_integer(), + non_neg_integer(), + pos_integer(), + infinity | pos_integer(), + {atom(), atom(), list()} +) -> hbbft_data(). +init(KeyShare, N, F, J, BatchSize, MaxBuf, {M, Fn, A}) -> + init(KeyShare, N, F, J, BatchSize, MaxBuf, {M, Fn, A}, 0, []). + +init(KeyShare, N, F, J, BatchSize, MaxBuf, StampFun, Round, Buf) -> + Curve = hbbft_utils:curve(KeyShare), + #hbbft_data{ + curve = Curve, + key_share = KeyShare, + n = N, + f = F, + j = J, + batch_size = BatchSize, + acs = hbbft_acs:init(KeyShare, N, F, J), + round = Round, + buf = Buf, + max_buf = MaxBuf, + stampfun = StampFun + }. -ifdef(TEST). --spec abstraction_breaking_set_acs_results(State, acs_results()) -> - State - when State :: hbbft_data(). +-spec abstraction_breaking_set_acs_results(State, acs_results()) -> State when + State :: hbbft_data(). abstraction_breaking_set_acs_results(State, AcsResults) -> - State#hbbft_data{acs_results=AcsResults}. + State#hbbft_data{acs_results = AcsResults}. --spec abstraction_breaking_set_enc_keys(State, enc_keys()) -> - State - when State :: hbbft_data(). +-spec abstraction_breaking_set_enc_keys(State, enc_keys()) -> State when State :: hbbft_data(). abstraction_breaking_set_enc_keys(State, EncKeys) -> - State#hbbft_data{enc_keys=EncKeys}. + State#hbbft_data{enc_keys = EncKeys}. -endif. - -spec get_stamp_fun(hbbft_data()) -> {atom(), atom(), list()} | undefined. -get_stamp_fun(#hbbft_data{stampfun=S}) -> +get_stamp_fun(#hbbft_data{stampfun = S}) -> S. -spec set_stamp_fun(atom(), atom(), list(), hbbft_data()) -> hbbft_data(). set_stamp_fun(M, F, A, Data) when is_atom(M), is_atom(F) -> - Data#hbbft_data{stampfun={M, F, A}}. + Data#hbbft_data{stampfun = {M, F, A}}. %% start acs on demand --spec start_on_demand(hbbft_data()) -> {hbbft_data(), already_started | {send, [rbc_wrapped_output()]}}. -start_on_demand(Data = #hbbft_data{buf=Buf, j=J, n=N, secret_key=SK, batch_size=BatchSize, acs_init=false, - stamps=Stamps, decrypted=Decrypted}) -> +-spec start_on_demand(hbbft_data()) -> + {hbbft_data(), already_started | {send, [rbc_wrapped_output()]}}. +start_on_demand( + Data = #hbbft_data{ + buf = Buf, + j = J, + n = N, + key_share = KeyShare, + curve = Curve, + batch_size = BatchSize, + acs_init = false, + stamps = Stamps, + decrypted = Decrypted + } +) -> %% pick proposed whichever is lesser from batchsize/n or buffer - Proposed = hbbft_utils:random_n(min((BatchSize div N), length(Buf)), lists:sublist(Buf, BatchSize)), - %% encrypt x -> tpke.enc(pk, proposed) - Stamp = case Data#hbbft_data.stampfun of - undefined -> <<>>; - {M, F, A} -> erlang:apply(M, F, A) - end, + Proposed = hbbft_utils:random_n( + min((BatchSize div N), length(Buf)), + lists:sublist(Buf, BatchSize) + ), + Stamp = + case Data#hbbft_data.stampfun of + undefined -> <<>>; + {M, F, A} -> erlang:apply(M, F, A) + end, true = is_binary(Stamp), - EncX = encrypt(tpke_privkey:public_key(SK), encode_list([Stamp|Proposed])), + EncX = encrypt(Curve, KeyShare, encode_list([Stamp|Proposed])), %% time to kick off a round {NewACSState, {send, ACSResponse}} = hbbft_acs:input(Data#hbbft_data.acs, EncX), %% add this to acs set in data and send out the ACS response(s) %% %% Also, store our own proposal and stamp so we can avoid combining/decrypting it %% later if it gets included in the ACS result - {Data#hbbft_data{acs=NewACSState, acs_init=true, - stamps=lists:keystore(J, 1, Stamps, {J, Stamp}), - decrypted=maps:put(J, Proposed, Decrypted)}, - {send, hbbft_utils:wrap({acs, Data#hbbft_data.round}, ACSResponse)}}; + {Data#hbbft_data{ + acs = NewACSState, + acs_init = true, + stamps = lists:keystore(J, 1, Stamps, {J, Stamp}), + decrypted = maps:put(J, Proposed, Decrypted) + }, + {send, hbbft_utils:wrap({acs, Data#hbbft_data.round}, ACSResponse)}}; start_on_demand(Data) -> {Data, already_started}. %% someone submitting a transaction to the replica set -spec input(hbbft_data(), binary()) -> {hbbft_data(), ok | {send, [rbc_wrapped_output()]} | full}. -input(Data = #hbbft_data{buf=Buf, max_buf=MaxBuf}, Txn) when is_binary(Txn), length(Buf) < MaxBuf-> +input(Data = #hbbft_data{buf = Buf, max_buf = MaxBuf}, Txn) when + is_binary(Txn), length(Buf) < MaxBuf +-> %% add this txn to the the buffer NewBuf = Buf ++ [Txn], - maybe_start_acs(Data#hbbft_data{buf=NewBuf}); -input(Data = #hbbft_data{buf=_Buf}, _Txn) when is_binary(_Txn) -> + maybe_start_acs(Data#hbbft_data{buf = NewBuf}); +input(Data = #hbbft_data{buf = _Buf}, _Txn) when is_binary(_Txn) -> %% drop the txn {Data, full}. @@ -181,262 +268,435 @@ input(Data = #hbbft_data{buf=_Buf}, _Txn) when is_binary(_Txn) -> %% to remove from the buffer (accepted or invalid). Transactions missing causal context %% (eg. a missing monotonic nonce prior to the current nonce) should remain in the buffer and thus %% should not be placed in TransactionsToRemove. Once this returns, the user should call next_round/1. --spec finalize_round(hbbft_data(), [binary()], binary()) -> {hbbft_data(), {send, [hbbft_utils:multicast(sign_msg())]}}. -finalize_round(Data, TransactionsToRemove, ThingToSign) -> - NewBuf = lists:filter(fun(Item) -> - not lists:member(Item, TransactionsToRemove) - end, Data#hbbft_data.buf), - HashThing = tpke_pubkey:hash_message(tpke_privkey:public_key(Data#hbbft_data.secret_key), ThingToSign), - BinShare = hbbft_utils:share_to_binary(tpke_privkey:sign(Data#hbbft_data.secret_key, HashThing)), +-spec finalize_round(hbbft_data(), [binary()], binary()) -> + {hbbft_data(), {send, [hbbft_utils:multicast(sign_msg())]}}. +finalize_round(Data, TransactionsToRemove, ThingToSign0) -> + NewBuf = lists:filter( + fun(Item) -> + not lists:member(Item, TransactionsToRemove) + end, + Data#hbbft_data.buf + ), + {SigShare, ThingToSign} = case Data#hbbft_data.curve of + 'BLS12-381' -> + {tc_key_share:sign_share(Data#hbbft_data.key_share, ThingToSign0), ThingToSign0}; + 'SS512' -> + HashThing = tpke_pubkey:hash_message(tpke_privkey:public_key(Data#hbbft_data.key_share), ThingToSign0), + {tpke_privkey:sign(Data#hbbft_data.key_share, HashThing), HashThing} + end, + BinSigShare = hbbft_utils:sig_share_to_binary(Data#hbbft_data.curve, SigShare), %% multicast the signature to everyone - {Data#hbbft_data{thingtosign=HashThing, buf=NewBuf}, {send, [{multicast, {sign, Data#hbbft_data.round, BinShare}}]}}. + {Data#hbbft_data{thingtosign = ThingToSign, buf = NewBuf}, + {send, [{multicast, {sign, Data#hbbft_data.round, BinSigShare}}]}}. %% does not require a signed message --spec finalize_round(hbbft_data(), [binary()])-> hbbft_data(). +-spec finalize_round(hbbft_data(), [binary()]) -> hbbft_data(). finalize_round(Data, TransactionsToRemove) -> - NewBuf = lists:filter(fun(Item) -> - not lists:member(Item, TransactionsToRemove) - end, Data#hbbft_data.buf), - Data#hbbft_data{buf=NewBuf}. + NewBuf = lists:filter( + fun(Item) -> + not lists:member(Item, TransactionsToRemove) + end, + Data#hbbft_data.buf + ), + Data#hbbft_data{buf = NewBuf}. %% The user has obtained a signature and is ready to go to the next round -spec next_round(hbbft_data()) -> {hbbft_data(), ok | {send, []}}. -next_round(Data = #hbbft_data{secret_key=SK, n=N, f=F, j=J}) -> +next_round(Data = #hbbft_data{key_share = KeyShare, n = N, f = F, j = J}) -> %% reset all the round-dependant bits of the state and increment the round - NewData = Data#hbbft_data{round=Data#hbbft_data.round + 1, acs=hbbft_acs:init(SK, N, F, J), - acs_init=false, acs_results=[], - sent_txns=false, sent_sig=false, enc_keys=#{}, - dec_shares=#{}, decrypted=#{}, - failed_combine=[], failed_decrypt=[], - sig_shares=#{}, thingtosign=undefined, stamps=[]}, + NewData = Data#hbbft_data{ + round = Data#hbbft_data.round + 1, + acs = hbbft_acs:init(KeyShare, N, F, J), + acs_init = false, + acs_results = [], + sent_txns = false, + sent_sig = false, + enc_keys = #{}, + dec_shares = #{}, + decrypted = #{}, + failed_combine = [], + failed_decrypt = [], + sig_shares = #{}, + thingtosign = undefined, + stamps = [] + }, maybe_start_acs(NewData). -spec next_round(hbbft_data(), pos_integer(), [binary()]) -> {hbbft_data(), ok | {send, []}}. -next_round(Data = #hbbft_data{secret_key=SK, n=N, f=F, j=J, buf=Buf}, NextRound, TransactionsToRemove) -> +next_round( + Data = #hbbft_data{key_share = KeyShare, n = N, f = F, j = J, buf = Buf}, + NextRound, + TransactionsToRemove +) -> %% remove the request transactions - NewBuf = lists:filter(fun(Item) -> - not lists:member(Item, TransactionsToRemove) - end, Buf), + NewBuf = lists:filter( + fun(Item) -> + not lists:member(Item, TransactionsToRemove) + end, + Buf + ), %% reset all the round-dependant bits of the state and increment the round - NewData = Data#hbbft_data{round=NextRound, acs=hbbft_acs:init(SK, N, F, J), - acs_init=false, acs_results=[], - sent_txns=false, sent_sig=false, enc_keys=#{}, - dec_shares=#{}, decrypted=#{}, buf=NewBuf, - failed_combine=[], failed_decrypt=[], - sig_shares=#{}, thingtosign=undefined, stamps=[]}, + NewData = Data#hbbft_data{ + round = NextRound, + acs = hbbft_acs:init(KeyShare, N, F, J), + acs_init = false, + acs_results = [], + sent_txns = false, + sent_sig = false, + enc_keys = #{}, + dec_shares = #{}, + decrypted = #{}, + buf = NewBuf, + failed_combine = [], + failed_decrypt = [], + sig_shares = #{}, + thingtosign = undefined, + stamps = [] + }, maybe_start_acs(NewData). -spec round(hbbft_data()) -> non_neg_integer(). -round(_Data=#hbbft_data{round=Round}) -> +round(_Data = #hbbft_data{round = Round}) -> Round. -spec buf(hbbft_data()) -> [any()]. -buf(_Data=#hbbft_data{buf = Buf}) -> +buf(_Data = #hbbft_data{buf = Buf}) -> Buf. -spec buf([binary()], hbbft_data()) -> hbbft_data(). buf(Buf, Data) -> - Data#hbbft_data{buf=Buf}. - --spec handle_msg(State, J :: non_neg_integer(), Msg) -> - {State, Next} | ignore - when State :: hbbft_data(), - Msg :: acs_msg() | dec_msg() | sign_msg(), - Next - :: ok - | defer - | {send, [NextMsg]} - | {result, Result}, - NextMsg - :: hbbft_utils:multicast(dec_msg() | sign_msg()) - | rbc_wrapped_output() - | bba_wrapped_output(), - Result - :: {signature, binary()} - | {transactions, list(), [binary()]}. -handle_msg(Data = #hbbft_data{round=R}, _J, {{acs, R2}, _ACSMsg}) when R2 > R -> + Data#hbbft_data{buf = Buf}. + +-spec handle_msg(State, J :: non_neg_integer(), Msg) -> {State, Next} | ignore when + State :: hbbft_data(), + Msg :: acs_msg() | dec_msg() | sign_msg(), + Next :: + ok + | defer + | {send, [NextMsg]} + | {result, Result}, + NextMsg :: + hbbft_utils:multicast(dec_msg() | sign_msg()) + | rbc_wrapped_output() + | bba_wrapped_output(), + Result :: + {signature, binary()} + | {transactions, list(), [binary()]}. +handle_msg(Data = #hbbft_data{round = R}, _J, {{acs, R2}, _ACSMsg}) when R2 > R -> %% ACS requested we defer this message for now {Data, defer}; -handle_msg(Data = #hbbft_data{round=R}, J, {{acs, R}, ACSMsg}) -> +handle_msg(Data = #hbbft_data{round = R}, J, {{acs, R}, ACSMsg}) -> %% ACS message for this round case hbbft_acs:handle_msg(Data#hbbft_data.acs, J, ACSMsg) of - ignore -> ignore; + ignore -> + ignore; {NewACS, ok} -> - {Data#hbbft_data{acs=NewACS}, ok}; + {Data#hbbft_data{acs = NewACS}, ok}; {NewACS, {send, ACSResponse}} -> - {Data#hbbft_data{acs=NewACS}, {send, hbbft_utils:wrap({acs, Data#hbbft_data.round}, ACSResponse)}}; + {Data#hbbft_data{acs = NewACS}, + {send, hbbft_utils:wrap({acs, Data#hbbft_data.round}, ACSResponse)}}; {NewACS, {result_and_send, Results0, {send, ACSResponse}}} -> %% ACS[r] has returned, time to move on to the decrypt phase %% start decrypt phase - {Replies, Results, EncKeys} = lists:foldl(fun({I, Result}, {RepliesAcc, ResultsAcc, EncKeysAcc}=Acc) -> - %% this function will validate the ciphertext is consistent with our key - case get_encrypted_key(Data#hbbft_data.secret_key, Result) of - {ok, EncKey} -> - %% we've validated the ciphertext, this is now safe to do - Share = tpke_privkey:decrypt_share(Data#hbbft_data.secret_key, EncKey), - SerializedShare = hbbft_utils:share_to_binary(Share), - {[{multicast, {dec, Data#hbbft_data.round, I, SerializedShare}}|RepliesAcc], [{I, Result}|ResultsAcc], maps:put(I, EncKey, EncKeysAcc)}; - error -> - %% invalid ciphertext, we should not proceed with this result - Acc - end - end, {[], [], #{}}, Results0), + {Replies, Results, EncKeys} = lists:foldl( + fun({I, Result}, {RepliesAcc, ResultsAcc, EncKeysAcc} = Acc) -> + %% this function will validate the ciphertext is consistent with our key + {EncKey, KeyIsValid} = case Data#hbbft_data.curve of + 'BLS12-381' -> + EncKey0 = tc_ciphertext:deserialize(Result), + {EncKey0, tc_ciphertext:verify(EncKey0)}; + 'SS512' -> + case get_encrypted_key(Data#hbbft_data.key_share, Result) of + {ok, EncKey0} -> + {EncKey0, true}; + error -> + {nothing, false} + end + end, + case KeyIsValid of + true -> + %% we've validated the ciphertext, this is now safe to do + Share = case Data#hbbft_data.curve of + 'BLS12-381' -> + tc_key_share:decrypt_share(Data#hbbft_data.key_share, EncKey); + 'SS512' -> + tpke_privkey:decrypt_share(Data#hbbft_data.key_share, EncKey) + end, + SerializedShare = hbbft_utils:dec_share_to_binary(Data#hbbft_data.curve, Share), + {[ + {multicast, {dec, Data#hbbft_data.round, I, SerializedShare}} + | RepliesAcc + ], + [{I, Result} | ResultsAcc], maps:put(I, EncKey, EncKeysAcc)}; + false -> + %% invalid ciphertext, we should not proceed with this result + Acc + end + end, + {[], [], #{}}, + Results0 + ), %% verify any shares we received before we got the ACS result %% %% check if we have a copy of our own proposal. this will always be true %% unless we did an upgrade in the middle of a round. we can remove this check %% later HasOwnProposal = maps:is_key(J, Data#hbbft_data.decrypted), - VerifiedShares = maps:map(fun({I, _}, {undefined, Share}) when - %% don't verify if this is our own proposal and we have a copy of it - not (I == J andalso HasOwnProposal) -> - case maps:find(I, EncKeys) of - {ok, EncKey} -> - Valid = tpke_pubkey:verify_share(tpke_privkey:public_key(Data#hbbft_data.secret_key), Share, EncKey), - {Valid, Share}; - error -> - %% this is a share for an RBC we will never decode - {undefined, Share} - end; - (_, V) -> - V - end, Data#hbbft_data.dec_shares), + VerifiedShares = maps:map( + fun + ({I, _}, {undefined, Share}) when + %% don't verify if this is our own proposal and we have a copy of it + not (I == J andalso HasOwnProposal) + -> + case maps:find(I, EncKeys) of + {ok, EncKey} -> + Valid = case Data#hbbft_data.curve of + 'BLS12-381' -> + tc_key_share:verify_decryption_share( + Data#hbbft_data.key_share, + Share, + EncKey + ); + 'SS512' -> + tpke_pubkey:verify_share(tpke_privkey:public_key(Data#hbbft_data.key_share), Share, EncKey) + end, + {Valid, Share}; + error -> + %% this is a share for an RBC we will never decode + {undefined, Share} + end; + (_, V) -> + V + end, + Data#hbbft_data.dec_shares + ), %% if we are not in the ACS result set, filter out our own results {ResultIndices, _} = lists:unzip(Results), Decrypted = maps:with(ResultIndices, Data#hbbft_data.decrypted), - Stamps = lists:filter(fun({I, _Stamp}) -> - lists:member(I, ResultIndices) - end, Data#hbbft_data.stamps), - {Data#hbbft_data{acs=NewACS, acs_results=Results, dec_shares=VerifiedShares, decrypted=Decrypted, stamps=Stamps, enc_keys=EncKeys}, - {send, hbbft_utils:wrap({acs, Data#hbbft_data.round}, ACSResponse) ++ Replies}}; + Stamps = lists:filter( + fun({I, _Stamp}) -> + lists:member(I, ResultIndices) + end, + Data#hbbft_data.stamps + ), + {Data#hbbft_data{ + acs = NewACS, + acs_results = Results, + dec_shares = VerifiedShares, + decrypted = Decrypted, + stamps = Stamps, + enc_keys = EncKeys + }, + {send, hbbft_utils:wrap({acs, Data#hbbft_data.round}, ACSResponse) ++ Replies}}; {NewACS, defer} -> - {Data#hbbft_data{acs=NewACS}, defer} + {Data#hbbft_data{acs = NewACS}, defer} end; -handle_msg(Data = #hbbft_data{round=R}, _J, {dec, R2, _I, _Share}) when R2 > R -> +handle_msg(Data = #hbbft_data{round = R}, _J, {dec, R2, _I, _Share}) when R2 > R -> {Data, defer}; -handle_msg(Data = #hbbft_data{round=R}, J, {dec, R, I, Share}) -> +handle_msg(Data = #hbbft_data{round = R, curve = Curve, key_share = SK}, J, {dec, R, I, Share}) -> %% check if we have enough to decode the bundle - case maps:is_key(I, Data#hbbft_data.decrypted) %% have we already decrypted for this instance? - orelse maps:is_key({I, J}, Data#hbbft_data.dec_shares) of %% do we already have this share? + + %% have we already decrypted for this instance? + case + maps:is_key(I, Data#hbbft_data.decrypted) orelse + %% do we already have this share? + maps:is_key({I, J}, Data#hbbft_data.dec_shares) + of true -> %% we already have this share, or we've already decrypted this ACS result %% we don't need this ignore; false -> %% the Share now is a binary, deserialize it and then store in the dec_shares map - DeserializedShare = hbbft_utils:binary_to_share(Share, tpke_privkey:public_key(Data#hbbft_data.secret_key)), + DeserializedShare = hbbft_utils:binary_to_dec_share(Curve, SK, Share), %% add share to map and validate any previously unvalidated shares %% %% check if we have a copy of our own proposal. this will always be true %% unless we did an upgrade in the middle of a round. we can remove this check %% later HasOwnProposal = maps:is_key(J, Data#hbbft_data.decrypted), - NewShares = maps:map(fun({I1, _}, {undefined, AShare}) when - %% don't verify if this is our own proposal and we have a copy of it - not (I1 == J andalso HasOwnProposal) -> - case maps:find(I1, Data#hbbft_data.enc_keys) of - {ok, EncKey} -> - %% we validated the ciphertext above so we don't need to re-check it here - Valid = tpke_pubkey:verify_share(tpke_privkey:public_key(Data#hbbft_data.secret_key), AShare, EncKey), - {Valid, AShare}; - error -> - %% this is a share for an RBC we will never decode - {undefined, AShare} - end; - (_, V) -> - V - end, maps:put({I, J}, {undefined, DeserializedShare}, Data#hbbft_data.dec_shares)), - SharesForThisBundle = [ S || {{Idx, _}, S} <- maps:to_list(NewShares), I == Idx], - case lists:keymember(I, 1, Data#hbbft_data.acs_results) %% was this instance included in the ACS result set? - andalso maps:is_key(I, Data#hbbft_data.enc_keys) %% do we have a valid ciphertext - andalso length(SharesForThisBundle) > Data#hbbft_data.f of %% do we have f+1 decryption shares? + NewShares = maps:map( + fun + ({I1, _}, {undefined, AShare}) when + %% don't verify if this is our own proposal and we have a copy of it + not (I1 == J andalso HasOwnProposal) + -> + case maps:find(I1, Data#hbbft_data.enc_keys) of + {ok, EncKey} -> + %% we validated the ciphertext above so we don't need to re-check it here + Valid = case Data#hbbft_data.curve of + 'BLS12-381' -> + tc_key_share:verify_decryption_share( + Data#hbbft_data.key_share, + AShare, + EncKey + ); + 'SS512' -> + tpke_pubkey:verify_share(tpke_privkey:public_key(Data#hbbft_data.key_share), AShare, EncKey) + end, + {Valid, AShare}; + error -> + %% this is a share for an RBC we will never decode + {undefined, AShare} + end; + (_, V) -> + V + end, + maps:put({I, J}, {undefined, DeserializedShare}, Data#hbbft_data.dec_shares) + ), + SharesForThisBundle = [S || {{Idx, _}, S} <- maps:to_list(NewShares), I == Idx], + %% was this instance included in the ACS result set? + case + lists:keymember(I, 1, Data#hbbft_data.acs_results) andalso + %% do we have a valid ciphertext + maps:is_key(I, Data#hbbft_data.enc_keys) andalso + %% do we have f+1 decryption shares? + length(SharesForThisBundle) > Data#hbbft_data.f + of true -> EncKey = maps:get(I, Data#hbbft_data.enc_keys), - case combine_shares(Data#hbbft_data.f, Data#hbbft_data.secret_key, SharesForThisBundle, EncKey) of + case + combine_shares( + Data#hbbft_data.curve, + Data#hbbft_data.f, + Data#hbbft_data.key_share, + SharesForThisBundle, + EncKey + ) + of undefined -> %% can't recover the key, consider this ACS failed if we have 2f+1 shares and still can't recover the key case length(SharesForThisBundle) > 2 * Data#hbbft_data.f of true -> %% ok, just declare this ACS returned an empty list NewDecrypted = maps:put(I, [], Data#hbbft_data.decrypted), - check_completion(Data#hbbft_data{dec_shares=NewShares, decrypted=NewDecrypted, - failed_combine=[I|Data#hbbft_data.failed_combine]}); + check_completion(Data#hbbft_data{ + dec_shares = NewShares, + decrypted = NewDecrypted, + failed_combine = [I | Data#hbbft_data.failed_combine] + }); false -> - {Data#hbbft_data{dec_shares=NewShares}, ok} + {Data#hbbft_data{dec_shares = NewShares}, ok} end; - DecKey -> - {I, Enc} = lists:keyfind(I, 1, Data#hbbft_data.acs_results), - case decrypt(DecKey, Enc) of + Decrypted0 -> + Decrypted = case Data#hbbft_data.curve of + 'BLS12-381' -> + %% the ciphertext is direct in this mode + Decrypted0; + 'SS512' -> + %% we decrypted the key only + {I, Enc} = lists:keyfind(I, 1, Data#hbbft_data.acs_results), + decrypt(Decrypted0, Enc) + end, + case Decrypted of error -> + %% this only happens for SS512 %% can't decrypt, consider this ACS a failure %% just declare this ACS returned an empty list because we had %% f+1 valid shares but the resulting decryption key was unusuable to decrypt %% the transaction bundle NewDecrypted = maps:put(I, [], Data#hbbft_data.decrypted), check_completion(Data#hbbft_data{dec_shares=NewShares, decrypted=NewDecrypted, - failed_decrypt=[I|Data#hbbft_data.failed_decrypt]}); - Decrypted -> - #hbbft_data{batch_size=B, n=N} = Data, + failed_decrypt=[I|Data#hbbft_data.failed_decrypt]}); + _ -> + #hbbft_data{batch_size = B, n = N} = Data, case decode_list(Decrypted, []) of - [_Stamp | Transactions] - when length(Transactions) > (B div N) -> + [_Stamp | Transactions] when length(Transactions) > (B div N) -> % Batch exceeds agreed-upon size. % Ignoring this proposal. check_completion( - Data#hbbft_data{ - dec_shares = - NewShares, - decrypted = - maps:put(I, [], Data#hbbft_data.decrypted) - } - ); + Data#hbbft_data{ + dec_shares = + NewShares, + decrypted = + maps:put(I, [], Data#hbbft_data.decrypted) + } + ); [Stamp | Transactions] -> - NewDecrypted = maps:put(I, Transactions, Data#hbbft_data.decrypted), + NewDecrypted = maps:put( + I, + Transactions, + Data#hbbft_data.decrypted + ), Stamps = [{I, Stamp} | Data#hbbft_data.stamps], - check_completion(Data#hbbft_data{dec_shares=NewShares, - decrypted=NewDecrypted, stamps=Stamps}); + check_completion(Data#hbbft_data{ + dec_shares = NewShares, + decrypted = NewDecrypted, + stamps = Stamps + }); {error, _} -> %% this is an invalid proposal. Because the shares are verifiable %% we know that everyone will fail to decrypt so we declare this as an empty share, %% as in the decryption failure case above, and continue %% TODO track failed decodes like we track failed decrypts NewDecrypted = maps:put(I, [], Data#hbbft_data.decrypted), - check_completion(Data#hbbft_data{dec_shares=NewShares, - decrypted=NewDecrypted}) + check_completion(Data#hbbft_data{ + dec_shares = NewShares, + decrypted = NewDecrypted + }) end end end; false -> %% not enough shares yet - {Data#hbbft_data{dec_shares=NewShares}, ok} + {Data#hbbft_data{dec_shares = NewShares}, ok} end end; -handle_msg(Data = #hbbft_data{round=R, thingtosign=ThingToSign}, _J, {sign, R2, _BinShare}) when ThingToSign == undefined orelse R2 > R -> +handle_msg(Data = #hbbft_data{round = R, thingtosign = ThingToSign}, _J, {sign, R2, _BinShare}) when + ThingToSign == undefined orelse R2 > R +-> {Data, defer}; -handle_msg(Data = #hbbft_data{round=R, thingtosign=ThingToSign}, J, {sign, R, BinShare}) when ThingToSign /= undefined -> +handle_msg(Data = #hbbft_data{round = R, thingtosign = ThingToSign, curve = Curve, key_share = SK}, J, {sign, R, BinShare}) when + ThingToSign /= undefined +-> %% messages related to signing the final block for this round, see finalize_round for more information %% Note: this is an extension to the HoneyBadger BFT specification - Share = hbbft_utils:binary_to_share(BinShare, tpke_privkey:public_key(Data#hbbft_data.secret_key)), + Share = hbbft_utils:binary_to_sig_share(Curve, SK, BinShare), %% verify the share - PubKey = tpke_privkey:public_key(Data#hbbft_data.secret_key), - case tpke_pubkey:verify_signature_share(PubKey, Share, ThingToSign) of + ValidShare = case Data#hbbft_data.curve of + 'BLS12-381' -> + tc_key_share:verify_signature_share(Data#hbbft_data.key_share, Share, ThingToSign); + 'SS512' -> + tpke_pubkey:verify_signature_share(tpke_privkey:public_key(Data#hbbft_data.key_share), Share, ThingToSign) + end, + case ValidShare of true -> NewSigShares = maps:put(J, Share, Data#hbbft_data.sig_shares), %% check if we have at least f+1 shares case maps:size(NewSigShares) > Data#hbbft_data.f andalso not Data#hbbft_data.sent_sig of true -> %% ok, we have enough people agreeing with us we can combine the signature shares - {ok, Sig} = tpke_pubkey:combine_verified_signature_shares(PubKey, maps:values(NewSigShares)), - case tpke_pubkey:verify_signature(PubKey, Sig, ThingToSign) of + {ok, Sig} = case Data#hbbft_data.curve of + 'BLS12-381' -> + tc_key_share:combine_signature_shares( + Data#hbbft_data.key_share, + maps:values(NewSigShares)); + 'SS512' -> + tpke_pubkey:combine_verified_signature_shares(tpke_privkey:public_key(Data#hbbft_data.key_share), maps:values(NewSigShares)) + end, + ValidSignature = case Data#hbbft_data.curve of + 'BLS12-381' -> + tc_key_share:verify(Data#hbbft_data.key_share, Sig, ThingToSign); + 'SS512' -> + tpke_pubkey:verify_signature(tpke_privkey:public_key(Data#hbbft_data.key_share), Sig, ThingToSign) + end, + case ValidSignature of true -> + SerializedSig = case Data#hbbft_data.curve of + 'BLS12-381' -> + tc_signature:serialize(Sig); + 'SS512' -> + erlang_pbc:element_to_binary(Sig) + end, %% verified signature, send the signature - {Data#hbbft_data{sig_shares=NewSigShares, sent_sig=true}, {result, {signature, erlang_pbc:element_to_binary(Sig)}}}; + {Data#hbbft_data{sig_shares = NewSigShares, sent_sig = true}, + {result, {signature, SerializedSig}}}; false -> %% must have duplicate signature shares, keep waiting - {Data#hbbft_data{sig_shares=NewSigShares}, ok} + {Data#hbbft_data{sig_shares = NewSigShares}, ok} end; false -> - {Data#hbbft_data{sig_shares=NewSigShares}, ok} + {Data#hbbft_data{sig_shares = NewSigShares}, ok} end; false -> ignore @@ -446,37 +706,61 @@ handle_msg(_Data, _J, _Msg) -> ignore. -spec maybe_start_acs(hbbft_data()) -> {hbbft_data(), ok | {send, [rbc_wrapped_output()]}}. -maybe_start_acs(Data = #hbbft_data{n=N, j=J, secret_key=SK, batch_size=BatchSize, - decrypted=Decrypted, stamps=Stamps}) -> +maybe_start_acs( + Data = #hbbft_data{ + n = N, + j = J, + key_share = KeyShare, + curve = Curve, + batch_size = BatchSize, + decrypted = Decrypted, + stamps = Stamps + } +) -> case length(Data#hbbft_data.buf) > BatchSize andalso Data#hbbft_data.acs_init == false of true -> %% compose a transaction bundle %% get the top b elements from buf %% pick a random B/N selection of them - Proposed = hbbft_utils:random_n(BatchSize div N, lists:sublist(Data#hbbft_data.buf, length(Data#hbbft_data.buf) - BatchSize + 1, BatchSize)), - %% encrypt x -> tpke.enc(pk, proposed) - Stamp = case Data#hbbft_data.stampfun of - undefined -> <<>>; - {M, F, A} -> erlang:apply(M, F, A) - end, + Proposed = hbbft_utils:random_n( + BatchSize div N, + lists:sublist( + Data#hbbft_data.buf, + length(Data#hbbft_data.buf) - BatchSize + 1, + BatchSize + ) + ), + Stamp = + case Data#hbbft_data.stampfun of + undefined -> <<>>; + {M, F, A} -> erlang:apply(M, F, A) + end, true = is_binary(Stamp), - EncX = encrypt(tpke_privkey:public_key(SK), encode_list([Stamp|Proposed])), + EncX = encrypt(Curve, KeyShare, encode_list([Stamp|Proposed])), %% time to kick off a round {NewACSState, {send, ACSResponse}} = hbbft_acs:input(Data#hbbft_data.acs, EncX), %% add this to acs set in data and send out the ACS response(s) %% %% Also, store our own proposal and stamp so we can avoid combining/decrypting it %% later if it gets included in the ACS result - {Data#hbbft_data{acs=NewACSState, acs_init=true, stamps=lists:keystore(J, 1, Stamps, {J, Stamp}), - decrypted=maps:put(J, Proposed, Decrypted)}, - {send, hbbft_utils:wrap({acs, Data#hbbft_data.round}, ACSResponse)}}; + {Data#hbbft_data{ + acs = NewACSState, + acs_init = true, + stamps = lists:keystore(J, 1, Stamps, {J, Stamp}), + decrypted = maps:put(J, Proposed, Decrypted) + }, + {send, hbbft_utils:wrap({acs, Data#hbbft_data.round}, ACSResponse)}}; false -> %% not enough transactions for this round yet {Data, ok} end. --spec encrypt(tpke_pubkey:pubkey(), binary()) -> binary(). -encrypt(PK, Bin) -> +-spec encrypt('SS512', tpke_pubkey:pubkey(), binary()) -> binary(); + ('BLS12-381', tc_key_share:tc_key_share(), binary()) -> binary(). +encrypt('BLS12-381', SK, Bin) -> + tc_ciphertext:serialize(tc_key_share:encrypt(SK, Bin)); +encrypt('SS512', SK, Bin) -> + PK = tpke_privkey:public_key(SK), %% generate a random AES key and IV Key = crypto:strong_rand_bytes(32), IV = crypto:strong_rand_bytes(16), @@ -506,140 +790,166 @@ decrypt(Key, Bin) -> <> = Bin, ?DECRYPT(Key, IV, <>, CipherText, Tag). --spec serialize(hbbft_data()) -> {#{atom() => binary() | map()}, - tpke_privkey:privkey_serialized() | tpke_privkey:privkey()}. +-spec serialize(hbbft_data()) -> + {#{atom() => binary() | map()}, binary() | tc_key_share:tc_key_share()}. serialize(Data) -> %% serialize the SK unless explicitly told not to serialize(Data, true). --spec serialize(hbbft_data(), boolean()) -> {#{atom() => binary() | map()}, - tpke_privkey:privkey_serialized() | tpke_privkey:privkey()}. -serialize(#hbbft_data{secret_key=SK}=Data, false) -> +-spec serialize(hbbft_data(), boolean()) -> + {#{atom() => binary() | map()}, binary() | tc_key_share:tc_key_share()}. +serialize(#hbbft_data{key_share = SK} = Data, false) -> %% dont serialize the private key {serialize_hbbft_data(Data), SK}; -serialize(#hbbft_data{secret_key=SK}=Data, true) -> +serialize(#hbbft_data{key_share = SK, curve=Curve} = Data, true) -> %% serialize the private key as well - {serialize_hbbft_data(Data), tpke_privkey:serialize(SK)}. + SerSK = case Curve of + 'BLS12-381' -> + tc_key_share:serialize(SK); + 'SS512' -> + tpke_privkey:serialize(SK) + end, + {serialize_hbbft_data(Data), SerSK}. --spec deserialize(#{atom() => binary() | map()}, tpke_privkey:privkey()) -> hbbft_data(). +-spec deserialize(#{atom() => binary() | map()}, tc_key_share:tc_key_share()) -> hbbft_data(). deserialize(M0, SK) -> - M = maps:map(fun(acs, V) -> V; - (_K, V) -> binary_to_term(V) - end, M0), - #{batch_size := BatchSize, - n := N, - f := F, - j := J, - round := Round, - max_buf := MaxBuf, - acs := ACSData, - acs_init := ACSInit, - sent_txns := SentTxns, - sent_sig := SentSig, - acs_results := ACSResults, - decrypted := Decrypted, - sig_shares := SigShares, - dec_shares := DecShares, - thingtosign := ThingToSign, - stampfun := Stampfun, - stamps := Stamps} = M, - - NewThingToSign = case ThingToSign of - undefined -> undefined; - _ -> tpke_pubkey:deserialize_element(tpke_privkey:public_key(SK), ThingToSign) - end, - EncKeys = case maps:find(enc_keys, M) of - {ok, EncKeys0} -> - maps:map(fun(_, Ciphertext) -> tpke_pubkey:binary_to_ciphertext(Ciphertext, tpke_privkey:public_key(SK)) end, EncKeys0); - error -> - %% upgrade from not having keys in state - lists:foldl(fun({I, Res}, Acc) -> - %% this function will validate the ciphertext is consistent with our key - case get_encrypted_key(SK, Res) of - {ok, EncKey} -> - maps:put(I, EncKey, Acc); - error -> - Acc - end - end, #{}, ACSResults) - end, - - - #hbbft_data{secret_key=SK, - batch_size=BatchSize, - n=N, - f=F, - j=J, - round=Round, - buf=[], - max_buf=MaxBuf, - acs=hbbft_acs:deserialize(ACSData, SK), - acs_init=ACSInit, - sent_txns=SentTxns, - sent_sig=SentSig, - acs_results=ACSResults, - decrypted=Decrypted, - enc_keys=EncKeys, - dec_shares=maps:map(fun(_, {Valid, Share}) -> - {Valid, hbbft_utils:binary_to_share(Share, tpke_privkey:public_key(SK))} - end, DecShares), - sig_shares=maps:map(fun(_, Share) -> hbbft_utils:binary_to_share(Share, tpke_privkey:public_key(SK)) end, SigShares), - thingtosign=NewThingToSign, - failed_combine=maps:get(failed_combine, M, []), - failed_decrypt=maps:get(failed_decrypt, M, []), - stampfun=Stampfun, - stamps=Stamps}. + M = maps:map( + fun + (acs, V) -> V; + (_K, V) -> binary_to_term(V) + end, + M0 + ), + #{ + batch_size := BatchSize, + n := N, + f := F, + j := J, + round := Round, + max_buf := MaxBuf, + acs := ACSData, + acs_init := ACSInit, + sent_txns := SentTxns, + sent_sig := SentSig, + acs_results := ACSResults, + decrypted := Decrypted, + sig_shares := SigShares, + dec_shares := DecShares, + thingtosign := ThingToSign, + stampfun := Stampfun, + stamps := Stamps, + enc_keys := EncKeys0 + } = M, + + Curve = hbbft_utils:curve(SK), + + EncKeys = maps:map( + fun(_, Ciphertext) -> + case Curve of + 'BLS12-381' -> + tc_ciphertext:deserialize(Ciphertext); + 'SS512' -> + tpke_pubkey:binary_to_ciphertext(Ciphertext, tpke_privkey:public_key(SK)) + end + end, + EncKeys0 + ), + #hbbft_data{ + curve = Curve, + key_share = SK, + batch_size = BatchSize, + n = N, + f = F, + j = J, + round = Round, + buf = [], + max_buf = MaxBuf, + acs = hbbft_acs:deserialize(ACSData, SK), + acs_init = ACSInit, + sent_txns = SentTxns, + sent_sig = SentSig, + acs_results = ACSResults, + decrypted = Decrypted, + enc_keys = EncKeys, + dec_shares = maps:map( + fun(_, {Valid, Share}) -> + {Valid, hbbft_utils:binary_to_dec_share(Curve, SK, Share)} + end, + DecShares + ), + sig_shares = maps:map( + fun(_, Share) -> hbbft_utils:binary_to_sig_share(Curve, SK, Share) end, + SigShares + ), + thingtosign = ThingToSign, + failed_combine = maps:get(failed_combine, M, []), + failed_decrypt = maps:get(failed_decrypt, M, []), + stampfun = Stampfun, + stamps = Stamps + }. -spec serialize_hbbft_data(hbbft_data()) -> #{atom() => binary() | map()}. -serialize_hbbft_data(#hbbft_data{batch_size=BatchSize, - n=N, - f=F, - j=J, - round=Round, - max_buf=MaxBuf, - acs=ACSData, - acs_init=ACSInit, - sent_txns=SentTxns, - sent_sig=SentSig, - acs_results=ACSResults, - enc_keys=EncKeys, - dec_shares=DecShares, - sig_shares=SigShares, - decrypted=Decrypted, - thingtosign=ThingToSign, - failed_combine=FailedCombine, - failed_decrypt=FailedDecrypt, - stampfun=Stampfun, - stamps=Stamps}) -> - - NewThingToSign = case ThingToSign of - undefined -> undefined; - _ -> erlang_pbc:element_to_binary(ThingToSign) - end, - - M = #{batch_size => BatchSize, - n => N, - f => F, - round => Round, - max_buf => MaxBuf, - acs => hbbft_acs:serialize(ACSData), - acs_init => ACSInit, - sent_txns => SentTxns, - decrypted => Decrypted, - j => J, - sent_sig => SentSig, - acs_results => ACSResults, - enc_keys => maps:map(fun(_, Ciphertext) -> tpke_pubkey:ciphertext_to_binary(Ciphertext) end, EncKeys), - dec_shares => maps:map(fun(_, {Valid, Share}) -> {Valid, hbbft_utils:share_to_binary(Share)} end, DecShares), - sig_shares => maps:map(fun(_, V) -> hbbft_utils:share_to_binary(V) end, SigShares), - thingtosign => NewThingToSign, - failed_combine => FailedCombine, - failed_decrypt => FailedDecrypt, - stampfun => Stampfun, - stamps => Stamps}, - maps:map(fun(acs, V) -> V; - (_K, V) -> term_to_binary(V, [compressed]) - end, M). +serialize_hbbft_data(#hbbft_data{ + batch_size = BatchSize, + curve = Curve, + n = N, + f = F, + j = J, + round = Round, + max_buf = MaxBuf, + acs = ACSData, + acs_init = ACSInit, + sent_txns = SentTxns, + sent_sig = SentSig, + acs_results = ACSResults, + enc_keys = EncKeys, + dec_shares = DecShares, + sig_shares = SigShares, + decrypted = Decrypted, + thingtosign = ThingToSign, + failed_combine = FailedCombine, + failed_decrypt = FailedDecrypt, + stampfun = Stampfun, + stamps = Stamps +}) -> + M = #{ + batch_size => BatchSize, + n => N, + f => F, + round => Round, + max_buf => MaxBuf, + acs => hbbft_acs:serialize(ACSData), + acs_init => ACSInit, + sent_txns => SentTxns, + decrypted => Decrypted, + j => J, + sent_sig => SentSig, + acs_results => ACSResults, + enc_keys => maps:map(fun(_, Ciphertext) -> case Curve of + 'BLS12-381' -> + tc_ciphertext:serialize(Ciphertext); + 'SS512' -> + tpke_pubkey:ciphertext_to_binary(Ciphertext) + end + end, EncKeys), + dec_shares => maps:map( + fun(_, {Valid, Share}) -> {Valid, hbbft_utils:dec_share_to_binary(Curve, Share)} end, + DecShares + ), + sig_shares => maps:map(fun(_, V) -> hbbft_utils:sig_share_to_binary(Curve, V) end, SigShares), + thingtosign => ThingToSign, + failed_combine => FailedCombine, + failed_decrypt => FailedDecrypt, + stampfun => Stampfun, + stamps => Stamps + }, + maps:map( + fun + (acs, V) -> V; + (_K, V) -> term_to_binary(V, [compressed]) + end, + M + ). -spec is_serialized(hbbft_data() | #{atom() => binary() | map()}) -> boolean(). is_serialized(Data) when is_map(Data) -> true; @@ -650,33 +960,67 @@ group_by(Tuples) -> group_by([], D) -> lists:keysort(1, [{K, lists:sort(V)} || {K, V} <- dict:to_list(D)]); -group_by([{K, V}|T], D) -> +group_by([{K, V} | T], D) -> group_by(T, dict:append(K, V, D)). check_completion(Data) -> - case maps:size(Data#hbbft_data.decrypted) == length(Data#hbbft_data.acs_results) andalso not Data#hbbft_data.sent_txns of + case + maps:size(Data#hbbft_data.decrypted) == length(Data#hbbft_data.acs_results) andalso + not Data#hbbft_data.sent_txns + of true -> %% we did it! %% Combine all unique messages into a single list - TransactionsThisRound = lists:usort(lists:flatten(maps:values(Data#hbbft_data.decrypted))), + TransactionsThisRound = lists:usort( + lists:flatten(maps:values(Data#hbbft_data.decrypted)) + ), StampsThisRound = lists:usort(Data#hbbft_data.stamps), %% return the transactions we agreed on to the user %% we have no idea which transactions are valid, invalid, out of order or missing %% causal context (eg. a nonce is not monotonic) so we return them to the user to let them %% figure it out. We expect the user to call finalize_round/3 once they've decided what they want to accept %% from this set of transactions. - {Data#hbbft_data{sent_txns=true}, {result, {transactions, StampsThisRound, TransactionsThisRound}}}; + {Data#hbbft_data{sent_txns = true}, + {result, {transactions, StampsThisRound, TransactionsThisRound}}}; false -> {Data, ok} end. --spec combine_shares(pos_integer(), tpke_privkey:privkey(), [tpke_privkey:share()], tpke_pubkey:ciphertext()) -> undefined | binary(). -combine_shares(F, SK, SharesForThisBundle, EncKey) -> +-spec combine_shares + ( + 'BLS12-381', + pos_integer(), + tc_key_share:tc_key_share(), + [{non_neg_integer(), tc_decryption_share:dec_share()}], + tc_ciphertext:ciphertext() + ) -> undefined | binary(); + ( + 'SS512', + pos_integer(), + tpke_privkey:privkey(), + [{non_neg_integer(), erlang_pbc:element()}], + erlang_pbc:element() + ) -> undefined | binary(). +combine_shares(Curve, F, SK, SharesForThisBundle, Ciphertext) -> %% only use valid shares so an invalid share doesn't corrupt our result - ValidSharesForThisBundle = [ S || {true, S} <- SharesForThisBundle ], + ValidSharesForThisBundle = [S || {true, S} <- SharesForThisBundle], case length(ValidSharesForThisBundle) > F of true -> - tpke_pubkey:combine_shares(tpke_privkey:public_key(SK), EncKey, ValidSharesForThisBundle); + case Curve of + 'BLS12-381' -> + {ok, Bin} = tc_key_share:combine_decryption_shares( + SK, + ValidSharesForThisBundle, + Ciphertext + ), + Bin; + 'SS512' -> + tpke_pubkey:combine_shares( + tpke_privkey:public_key(SK), + Ciphertext, + ValidSharesForThisBundle + ) + end; false -> %% not enough valid shares to bother trying to combine them undefined @@ -687,7 +1031,7 @@ encode_list(L) -> encode_list([], Acc) -> list_to_binary(lists:reverse(Acc)); -encode_list([H|T], Acc) -> +encode_list([H | T], Acc) -> Sz = byte_size(H), case Sz >= 16#ffffff of true -> @@ -699,6 +1043,6 @@ encode_list([H|T], Acc) -> decode_list(<<>>, Acc) -> lists:reverse(Acc); decode_list(<>, Acc) -> - decode_list(Tail, [Entry|Acc]); + decode_list(Tail, [Entry | Acc]); decode_list(_, _Acc) -> {error, bad_chunk_encoding}. diff --git a/src/hbbft_acs.erl b/src/hbbft_acs.erl index 532fb69..5b5203c 100644 --- a/src/hbbft_acs.erl +++ b/src/hbbft_acs.erl @@ -69,7 +69,7 @@ status(ACSData) -> rbc => maps:map(fun(_K, #rbc_state{rbc_data=RBCData, result=R}) -> #{rbc => hbbft_rbc:status(RBCData), result => is_binary(R)} end, ACSData#acs_data.rbc), bba => maps:map(fun(_K, #bba_state{bba_data=BBAData, result=R, input=I}) -> #{bba => hbbft_bba:status(BBAData), result => R, input => I} end, ACSData#acs_data.bba)}. --spec init(tpke_privkey:privkey(), pos_integer(), non_neg_integer(), non_neg_integer()) -> acs_data(). +-spec init(tc_key_share:tc_key_share(), pos_integer(), non_neg_integer(), non_neg_integer()) -> acs_data(). init(SK, N, F, J) -> %% instantiate all the RBCs %% J=leader, I=Pid @@ -261,7 +261,7 @@ serialize(#acs_data{done=Done, n=N, f=F, j=J, rbc=RBCMap, bba=BBAMap}) -> M1#{rbc => serialize_state(RBCMap, rbc), bba => serialize_state(BBAMap, bba)}. --spec deserialize(acs_serialized_data() | #{}, tpke_privkey:privkey()) -> acs_data(). +-spec deserialize(acs_serialized_data() | #{}, tc_key_share:tc_key_share()) -> acs_data(). deserialize(#acs_serialized_data{done=Done, n=N, f=F, j=J, rbc=RBCMap, bba=BBAMap}, SK) -> #acs_data{done=Done, n=N, f=F, j=J, rbc=deserialize_state(RBCMap, rbc), @@ -292,7 +292,7 @@ bba_k(K) -> deserialize_state(State, rbc) -> maps:fold(fun(K, V, M) -> M#{deser_rbc_key(K) => deserialize_rbc_state(V)} end, #{}, State). --spec deserialize_state(#{non_neg_integer() => bba_serialized_state()}, bba, tpke_privkey:privkey()) -> #{}. +-spec deserialize_state(#{non_neg_integer() => bba_serialized_state()}, bba, tc_key_share:tc_key_share()) -> #{}. deserialize_state(State, bba, SK) -> maps:fold(fun(K, V, M) -> M#{deser_bba_key(K) => deserialize_bba_state(V, SK)} end, #{}, State). @@ -326,7 +326,7 @@ serialize_bba_state(#bba_state{bba_data=BBAData, input=Input, result=Result}) -> term_to_binary(#bba_serialized_state{bba_data=hbbft_bba:serialize(BBAData), input=Input, result=Result}, [compressed]). --spec deserialize_bba_state(bba_serialized_state() | #{}, tpke_privkey:privkey()) -> bba_state(). +-spec deserialize_bba_state(bba_serialized_state() | #{}, tc_key_share:tc_key_share()) -> bba_state(). deserialize_bba_state(#bba_serialized_state{bba_data=BBAData, input=Input, result=Result}, SK) -> #bba_state{bba_data=hbbft_bba:deserialize(BBAData, SK), input=Input, result=Result}; deserialize_bba_state(BinRec, SK) -> diff --git a/src/hbbft_bba.erl b/src/hbbft_bba.erl index 3e4c125..d7fdd98 100644 --- a/src/hbbft_bba.erl +++ b/src/hbbft_bba.erl @@ -5,7 +5,7 @@ -record(bba_data, { state = init :: init | waiting | done, round = 0 :: non_neg_integer(), - secret_key :: tpke_privkey:privkey(), + secret_key :: tc_key_share:tc_key_share(), coin :: undefined | hbbft_cc:cc_data(), est :: undefined | 0 | 1, output :: undefined | 0 | 1, @@ -70,7 +70,7 @@ status(BBAData) -> bvals_broadcasted => vals(BBAData#bba_data.broadcasted) }. --spec init(tpke_privkey:privkey(), pos_integer(), non_neg_integer()) -> bba_data(). +-spec init(tc_key_share:tc_key_share(), pos_integer(), non_neg_integer()) -> bba_data(). init(SK, N, F) -> #bba_data{secret_key=SK, n=N, f=F}. @@ -243,7 +243,7 @@ serialize(#bba_data{state=State, broadcasted=Broadcasted, bin_values=BinValues}. --spec deserialize(bba_serialized_data(), tpke_privkey:privkey()) -> bba_data(). +-spec deserialize(bba_serialized_data(), tc_key_share:tc_key_share()) -> bba_data(). deserialize(#bba_serialized_data{state=State, round=Round, coin=Coin, @@ -356,7 +356,10 @@ check(N, F, ToCheck, Map, Terms, Fun) -> maybe_init_coin(Data) -> case Data#bba_data.coin of undefined -> - hbbft_cc:init(Data#bba_data.secret_key, term_to_binary({Data#bba_data.round}), Data#bba_data.n, Data#bba_data.f); + hbbft_cc:init(Data#bba_data.secret_key, + term_to_binary({Data#bba_data.round}), + Data#bba_data.n, + Data#bba_data.f); C -> C end. diff --git a/src/hbbft_cc.erl b/src/hbbft_cc.erl index fff69da..f4d47e5 100644 --- a/src/hbbft_cc.erl +++ b/src/hbbft_cc.erl @@ -4,12 +4,12 @@ -record(cc_data, { state = waiting :: waiting | done, - sk :: tpke_privkey:privkey(), + sk :: tc_key_share:tc_key_share() | tpke_privkey:privkey(), %% Note: sid is assumed to be a unique nonce that serves as name of this common coin - sid :: erlang_pbc:element(), + sid :: binary() | erlang_pbc:element(), n :: pos_integer(), f :: non_neg_integer(), - shares = maps:new() :: #{non_neg_integer() => tpke_privkey:share()} + shares = maps:new() :: #{non_neg_integer() => {non_neg_integer(), tc_signature_share:sig_share()}} }). -record(cc_serialized_data, { @@ -32,7 +32,7 @@ status(undefined) -> undefined; status(CCData) -> #{state => CCData#cc_data.state, - shares => serialize_shares(CCData#cc_data.shares) + shares => serialize_shares(hbbft_utils:curve(CCData#cc_data.sk), CCData#cc_data.shares) }. %% Figure12. Bullet1 @@ -41,23 +41,34 @@ status(CCData) -> %% secret key shares {ski }, one for each party (secret key ski is %% distributed to party Pi). Note that a single setup can be used to %% support a family of Coins indexed by arbitrary sid strings. --spec init(tpke_privkey:privkey(), binary() | erlang_pbc:element(), pos_integer(), non_neg_integer()) -> cc_data(). -init(SecretKeyShard, Bin, N, F) when is_binary(Bin) -> - Sid = tpke_pubkey:hash_message(tpke_privkey:public_key(SecretKeyShard), Bin), - init(SecretKeyShard, Sid, N, F); -init(SecretKeyShard, Sid, N, F) -> - #cc_data{sk=SecretKeyShard, n=N, f=F, sid=Sid}. - +-spec init(tc_key_share:tc_key_share(), + binary(), + pos_integer(), + non_neg_integer()) -> cc_data(). +init(KeyShare, Sid0, N, F) -> + Sid = case tc_key_share:is_key_share(KeyShare) of + true -> + Sid0; + false -> + tpke_pubkey:hash_message(tpke_privkey:public_key(KeyShare), Sid0) + end, + #cc_data{sk=KeyShare, n=N, f=F, sid=Sid}. %% Figure12. Bullet2 %% on input GetCoin, multicast ThresholdSignpk (ski, sid) -spec get_coin(cc_data()) -> {cc_data(), ok | {send, [hbbft_utils:multicast(share_msg())]}}. get_coin(Data = #cc_data{state=done}) -> {Data, ok}; -get_coin(Data) -> - Share = tpke_privkey:sign(Data#cc_data.sk, Data#cc_data.sid), - SerializedShare = hbbft_utils:share_to_binary(Share), - {Data, {send, [{multicast, {share, SerializedShare}}]}}. +get_coin(Data = #cc_data{sk=SK}) -> + case hbbft_utils:curve(SK) of + 'BLS12-381' -> + Share = tc_key_share:sign_share(Data#cc_data.sk, Data#cc_data.sid), + {Data, {send, [{multicast, {share, hbbft_utils:sig_share_to_binary('BLS12-381', Share)}}]}}; + 'SS512' -> + Share = tpke_privkey:sign(Data#cc_data.sk, Data#cc_data.sid), + SerializedShare = hbbft_utils:sig_share_to_binary('SS512', Share), + {Data, {send, [{multicast, {share, SerializedShare}}]}} + end. %% upon receiving at least f + 1 shares, attempt to combine them @@ -73,24 +84,47 @@ handle_msg(Data, J, {share, Share}) -> -spec share(cc_data(), non_neg_integer(), binary()) -> {cc_data(), ok | {result, integer()}} | ignore. share(#cc_data{state=done}, _J, _Share) -> ignore; -share(Data, J, Share) -> +share(Data=#cc_data{sk=SK}, J, Share) -> case maps:is_key(J, Data#cc_data.shares) of false -> - %% store the deserialized share in the shares map, convenient to use later to verify signature - DeserializedShare = hbbft_utils:binary_to_share(Share, tpke_privkey:public_key(Data#cc_data.sk)), - case tpke_pubkey:verify_signature_share(tpke_privkey:public_key(Data#cc_data.sk), DeserializedShare, Data#cc_data.sid) of + Curve = hbbft_utils:curve(SK), + DeserializedShare = hbbft_utils:binary_to_sig_share(Curve, SK, Share), + ValidShare = case Curve of + 'BLS12-381' -> + tc_key_share:verify_signature_share(Data#cc_data.sk, + DeserializedShare, + Data#cc_data.sid); + 'SS512' -> + tpke_pubkey:verify_signature_share(tpke_privkey:public_key(Data#cc_data.sk), DeserializedShare, Data#cc_data.sid) + end, + case ValidShare of true -> + %% store the deserialized share in the shares map, convenient to use later to verify signature NewData = Data#cc_data{shares=maps:put(J, DeserializedShare, Data#cc_data.shares)}, %% check if we have at least f+1 shares case maps:size(NewData#cc_data.shares) > Data#cc_data.f of true -> %% combine shares - {ok, Sig} = tpke_pubkey:combine_verified_signature_shares(tpke_privkey:public_key(NewData#cc_data.sk), maps:values(NewData#cc_data.shares)), + {ok, Sig} = case Curve of + 'BLS12-381' -> + tc_key_share:combine_signature_shares(SK, maps:values(NewData#cc_data.shares)); + 'SS512' -> + tpke_pubkey:combine_verified_signature_shares(tpke_privkey:public_key(NewData#cc_data.sk), maps:values(NewData#cc_data.shares)) + end, %% check if the signature is valid - case tpke_pubkey:verify_signature(tpke_privkey:public_key(NewData#cc_data.sk), Sig, NewData#cc_data.sid) of + ValidSignature = case Curve of + 'BLS12-381' -> + tc_key_share:verify(NewData#cc_data.sk, Sig, NewData#cc_data.sid); + 'SS512' -> + tpke_pubkey:verify_signature(tpke_privkey:public_key(NewData#cc_data.sk), Sig, NewData#cc_data.sid) + end, + case ValidSignature of true -> %% TODO do something better here! - <> = erlang_pbc:element_to_binary(Sig), + <> = case Curve of + 'BLS12-381' -> tc_signature:serialize(Sig); + 'SS512' -> erlang_pbc:element_to_binary(Sig) + end, {NewData#cc_data{state=done}, {result, Val}}; false -> {NewData, ok} @@ -107,18 +141,43 @@ share(Data, J, Share) -> end. -spec serialize(cc_data()) -> cc_serialized_data(). -serialize(#cc_data{state=State, sid=SID, n=N, f=F, shares=Shares}) -> - #cc_serialized_data{state=State, sid=erlang_pbc:element_to_binary(SID), n=N, f=F, shares=serialize_shares(Shares)}. - --spec deserialize(cc_serialized_data(), tpke_privkey:privkey()) -> cc_data(). -deserialize(#cc_serialized_data{state=State, sid=SID, n=N, f=F, shares=Shares}, SK) -> - Element = tpke_pubkey:deserialize_element(tpke_privkey:public_key(SK), SID), - #cc_data{state=State, sk=SK, sid=Element, n=N, f=F, shares=deserialize_shares(Shares, SK)}. - --spec serialize_shares(#{non_neg_integer() => tpke_privkey:share()}) -> #{non_neg_integer() => binary()}. -serialize_shares(Shares) -> - maps:map(fun(_K, V) -> hbbft_utils:share_to_binary(V) end, Shares). +serialize(#cc_data{state = State, sid = SID, n = N, sk = SK, f = F, shares = Shares}) -> + #cc_serialized_data{ + state = State, + sid = serialize_sid(SID), + n = N, + f = F, + shares = serialize_shares(hbbft_utils:curve(SK), Shares) + }. + +-spec deserialize(cc_serialized_data(), tc_key_share:tc_key_share() | tpke_privkey:privkey()) -> + cc_data(). +deserialize(#cc_serialized_data{state = State, sid = SID, n = N, f = F, shares = Shares}, SK) -> + #cc_data{ + state = State, + sk = SK, + sid = deserialize_sid(SK, SID), + n = N, + f = F, + shares = deserialize_shares(hbbft_utils:curve(SK), SK, Shares) + }. + +-spec serialize_shares('BLS12-381' | 'SS512', #{non_neg_integer() => tc_signature_share:sig_share()}) -> #{non_neg_integer() => binary()}. +serialize_shares(Curve, Shares) -> + maps:map(fun(_K, V) -> hbbft_utils:sig_share_to_binary(Curve, V) end, Shares). + +-spec deserialize_shares('BLS12-381' | 'SS512', tc_key_share:tc_key_share() | tpke_privkey:privkey(), #{non_neg_integer() => binary()}) -> #{non_neg_integer() => tc_signature_share:sig_share()}. +deserialize_shares(Curve, SK, Shares) -> + maps:map(fun(_K, V) -> hbbft_utils:binary_to_sig_share(Curve, SK, V) end, Shares). + +serialize_sid(SID) when is_binary(SID) -> + SID; +serialize_sid(SID) -> + erlang_pbc:element_to_binary(SID). + +deserialize_sid(SK, SID) -> + case hbbft_utils:curve(SK) of + 'BLS12-381' -> SID; + 'SS512' -> tpke_pubkey:deserialize_element(tpke_privkey:public_key(SK), SID) + end. --spec deserialize_shares(#{non_neg_integer() => binary()}, tpke_privkey:privkey()) -> #{non_neg_integer() => tpke_privkey:share()}. -deserialize_shares(Shares, SK) -> - maps:map(fun(_K, V) -> hbbft_utils:binary_to_share(V, tpke_privkey:public_key(SK)) end, Shares). diff --git a/src/hbbft_utils.erl b/src/hbbft_utils.erl index 276b6a8..3728f57 100644 --- a/src/hbbft_utils.erl +++ b/src/hbbft_utils.erl @@ -5,17 +5,46 @@ -export_type([unicast/1, multicast/1]). --export([share_to_binary/1, binary_to_share/2, wrap/2, random_n/2, shuffle/1]). +-export([ + sig_share_to_binary/2, + binary_to_sig_share/3, + dec_share_to_binary/2, + binary_to_dec_share/3, + wrap/2, + random_n/2, + shuffle/1, + curve/1 +]). --spec share_to_binary(tpke_privkey:share()) -> binary(). -share_to_binary({ShareIdx, ShareElement}) -> +sig_share_to_binary('BLS12-381', {ShareIdx, SigShare}) -> %% Assume less than 256 members in the consensus group - ShareBinary = erlang_pbc:element_to_binary(ShareElement), + ShareBinary = tc_signature_share:serialize(SigShare), + <>; +sig_share_to_binary('SS512', {ShareIdx, SigShare}) -> + ShareBinary = erlang_pbc:element_to_binary(SigShare), <>. --spec binary_to_share(binary(), tpke_pubkey:pubkey()) -> tpke_privkey:share(). -binary_to_share(<>, PK) -> - ShareElement = tpke_pubkey:deserialize_element(PK, ShareBinary), +binary_to_sig_share('BLS12-381', _SK, <>) -> + SigShare = tc_signature_share:deserialize(ShareBinary), + {ShareIdx, SigShare}; +binary_to_sig_share('SS512', SK, <>) -> + ShareElement = tpke_pubkey:deserialize_element(tpke_privkey:public_key(SK), ShareBinary), + {ShareIdx, ShareElement}. + +dec_share_to_binary('BLS12-381', {ShareIdx, DecShare}) -> + %% Assume less than 256 members in the consensus group + ShareBinary = tc_decryption_share:serialize(DecShare), + <>; +dec_share_to_binary('SS512', {ShareIdx, DecShare}) -> + %% Assume less than 256 members in the consensus group + ShareBinary = erlang_pbc:element_to_binary(DecShare), + <>. + +binary_to_dec_share('BLS12-381', _SK, <>) -> + DecShare = tc_decryption_share:deserialize(ShareBinary), + {ShareIdx, DecShare}; +binary_to_dec_share('SS512', SK, <>) -> + ShareElement = tpke_pubkey:deserialize_element(tpke_privkey:public_key(SK), ShareBinary), {ShareIdx, ShareElement}. %% wrap a subprotocol's outbound messages with a protocol identifier @@ -34,3 +63,12 @@ random_n(N, List) -> -spec shuffle(list()) -> list(). shuffle(List) -> [X || {_,X} <- lists:sort([{rand:uniform(), N} || N <- List])]. + +-spec curve(KeyShare :: hbbft:key_share()) -> hbbft:curve(). +curve(KeyShare) -> + case tc_key_share:is_key_share(KeyShare) of + true -> + 'BLS12-381'; + false -> + 'SS512' + end. diff --git a/test/hbbft_SUITE.erl b/test/hbbft_SUITE.erl index 34b52cf..2707dcc 100644 --- a/test/hbbft_SUITE.erl +++ b/test/hbbft_SUITE.erl @@ -4,46 +4,66 @@ -include_lib("eunit/include/eunit.hrl"). -include_lib("relcast/include/fakecast.hrl"). --export([all/0, init_per_testcase/2, end_per_testcase/2]). +-export([all/0, groups/0, init_per_group/2, end_per_group/2, init_per_testcase/2, end_per_testcase/2]). -export([ - init_test/1, - one_actor_no_txns_test/1, - two_actors_no_txns_test/1, - one_actor_missing_test/1, - two_actors_missing_test/1, - encrypt_decrypt_test/1, - start_on_demand_test/1, - one_actor_wrong_key_test/1, - one_actor_corrupted_key_test/1, - one_actor_oversized_batch_test/1, - batch_size_limit_minimal_test/1, - initial_fakecast_test/1 - ]). + init_test/1, + one_actor_no_txns_test/1, + two_actors_no_txns_test/1, + one_actor_missing_test/1, + two_actors_missing_test/1, + encrypt_decrypt_test/1, + start_on_demand_test/1, + one_actor_wrong_key_test/1, + one_actor_corrupted_key_test/1, + one_actor_oversized_batch_test/1, + batch_size_limit_minimal_test/1, + initial_fakecast_test/1 +]). all() -> + [{group, ss512}, {group, bls12_381}]. + +test_cases() -> [ - init_test, - one_actor_no_txns_test, - two_actors_no_txns_test, - one_actor_missing_test, - two_actors_missing_test, - encrypt_decrypt_test, - start_on_demand_test, - one_actor_wrong_key_test, - one_actor_corrupted_key_test, - one_actor_oversized_batch_test, - batch_size_limit_minimal_test, - initial_fakecast_test + init_test, + one_actor_no_txns_test, + two_actors_no_txns_test, + one_actor_missing_test, + two_actors_missing_test, + encrypt_decrypt_test, + start_on_demand_test, + one_actor_wrong_key_test, + one_actor_corrupted_key_test, + one_actor_oversized_batch_test, + batch_size_limit_minimal_test, + initial_fakecast_test ]. +groups() -> + [{ss512, [], test_cases() -- [batch_size_limit_minimal_test]}, + {bls12_381, [], test_cases()}]. + +init_per_group(ss512, Config) -> + [{curve, 'SS512'} | Config]; +init_per_group(bls12_381, Config) -> + [{curve, 'BLS12-381'} | Config]. + +end_per_group(_, _Config) -> + ok. + init_per_testcase(_, Config) -> - N = 5, + N = 4, F = N div 4, Module = hbbft, BatchSize = 20, - {ok, Dealer} = dealer:new(N, F+1, 'SS512'), - {ok, {PubKey, PrivateKeys}} = dealer:deal(Dealer), - [{n, N}, {f, F}, {batchsize, BatchSize}, {module, Module}, {pubkey, PubKey}, {privatekeys, PrivateKeys} | Config]. + case proplists:get_value(curve, Config, 'BLS12-381') of + 'BLS12-381' -> + PrivateKeys = tc_key_share:deal(N, F); + 'SS512' -> + {ok, Dealer} = dealer:new(N, F+1, 'SS512'), + {ok, {_PubKey, PrivateKeys}} = dealer:deal(Dealer) + end, + [{n, N}, {f, F}, {batchsize, BatchSize}, {module, Module}, {privatekeys, PrivateKeys} | Config]. end_per_testcase(_, _Config) -> ok. @@ -51,17 +71,24 @@ end_per_testcase(_, _Config) -> init_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), + Curve = proplists:get_value(curve, Config), BatchSize = proplists:get_value(batchsize, Config), - PubKey = proplists:get_value(pubkey, Config), PrivateKeys = proplists:get_value(privatekeys, Config), - Workers = [ element(2, hbbft_worker:start_link(N, F, I, tpke_privkey:serialize(SK), BatchSize, false)) || {I, SK} <- enumerate(PrivateKeys) ], - Msgs = [ crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N*20)], + ct:pal("privkeys ~p", [PrivateKeys]), + Workers = [ + element(2, hbbft_worker:start_link(N, F, I, hbbft_test_utils:serialize_key(Curve, SK), BatchSize, false)) + || {I, SK} <- enumerate(PrivateKeys) + ], + Msgs = [crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N * 20)], %% feed the badgers some msgs - lists:foreach(fun(Msg) -> - Destinations = random_n(rand:uniform(N), Workers), - ct:log("destinations ~p~n", [Destinations]), - [ok = hbbft_worker:submit_transaction(Msg, D) || D <- Destinations] - end, Msgs), + lists:foreach( + fun(Msg) -> + Destinations = random_n(rand:uniform(N), Workers), + ct:log("destinations ~p~n", [Destinations]), + [ok = hbbft_worker:submit_transaction(Msg, D) || D <- Destinations] + end, + Msgs + ), %% wait for all the worker's mailboxes to settle and %% wait for the chains to converge @@ -73,9 +100,9 @@ init_test(Config) -> [Chain] = sets:to_list(Chains), ct:log("chain is of height ~p~n", [length(Chain)]), %% verify they are cryptographically linked - true = hbbft_worker:verify_chain(Chain, PubKey), + true = hbbft_worker:verify_chain(Chain, hd(PrivateKeys)), %% check all the transactions are unique - BlockTxns = lists:flatten([ hbbft_worker:block_transactions(B) || B <- Chain ]), + BlockTxns = lists:flatten([hbbft_worker:block_transactions(B) || B <- Chain]), true = length(BlockTxns) == sets:size(sets:from_list(BlockTxns)), %% check they're all members of the original message list true = sets:is_subset(sets:from_list(BlockTxns), sets:from_list(Msgs)), @@ -89,21 +116,33 @@ one_actor_no_txns_test(Config) -> Module = proplists:get_value(module, Config), PrivateKeys = proplists:get_value(privatekeys, Config), - StatesWithIndex = [{J, hbbft:init(Sk, N, F, J, BatchSize, infinity)} || {J, Sk} <- lists:zip(lists:seq(0, N - 1), PrivateKeys)], - Msgs = [ crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N*10)], + StatesWithIndex = [ + {J, hbbft:init(Sk, N, F, J, BatchSize, infinity)} + || {J, Sk} <- lists:zip(lists:seq(0, N - 1), PrivateKeys) + ], + Msgs = [crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N * 10)], %% send each message to a random subset of the HBBFT actors - {NewStates, Replies} = lists:foldl(fun(Msg, {States, Replies}) -> - Destinations = hbbft_utils:random_n(rand:uniform(N-1), lists:sublist(States, N-1)), - {NewStates, NewReplies} = lists:unzip(lists:map(fun({J, Data}) -> - {NewData, Reply} = hbbft:input(Data, Msg), - {{J, NewData}, {J, Reply}} - end, lists:keysort(1, Destinations))), - {lists:ukeymerge(1, NewStates, States), merge_replies(N, NewReplies, Replies)} - end, {StatesWithIndex, []}, Msgs), + {NewStates, Replies} = lists:foldl( + fun(Msg, {States, Replies}) -> + Destinations = hbbft_utils:random_n(rand:uniform(N - 1), lists:sublist(States, N - 1)), + {NewStates, NewReplies} = lists:unzip( + lists:map( + fun({J, Data}) -> + {NewData, Reply} = hbbft:input(Data, Msg), + {{J, NewData}, {J, Reply}} + end, + lists:keysort(1, Destinations) + ) + ), + {lists:ukeymerge(1, NewStates, States), merge_replies(N, NewReplies, Replies)} + end, + {StatesWithIndex, []}, + Msgs + ), %% check that at least N-F actors have started ACS: ?assert(length(Replies) >= N - F), %% all the nodes that have started ACS should have tried to send messages to all N peers (including themselves) - ?assert(lists:all(fun(E) -> E end, [ length(R) == N || {_, {send, R}} <- Replies ])), + ?assert(lists:all(fun(E) -> E end, [length(R) == N || {_, {send, R}} <- Replies])), %% start it on runnin' {_, ConvergedResults} = hbbft_test_utils:do_send_outer(Module, Replies, NewStates, sets:new()), %% check all N actors returned a result @@ -123,21 +162,33 @@ two_actors_no_txns_test(Config) -> Module = proplists:get_value(module, Config), PrivateKeys = proplists:get_value(privatekeys, Config), - StatesWithIndex = [{J, hbbft:init(Sk, N, F, J, BatchSize, infinity)} || {J, Sk} <- lists:zip(lists:seq(0, N - 1), PrivateKeys)], - Msgs = [ crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N*10)], + StatesWithIndex = [ + {J, hbbft:init(Sk, N, F, J, BatchSize, infinity)} + || {J, Sk} <- lists:zip(lists:seq(0, N - 1), PrivateKeys) + ], + Msgs = [crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N * 10)], %% send each message to a random subset of the HBBFT actors - {NewStates, Replies} = lists:foldl(fun(Msg, {States, Replies}) -> - Destinations = hbbft_utils:random_n(rand:uniform(N-2), lists:sublist(States, N-2)), - {NewStates, NewReplies} = lists:unzip(lists:map(fun({J, Data}) -> - {NewData, Reply} = hbbft:input(Data, Msg), - {{J, NewData}, {J, Reply}} - end, lists:keysort(1, Destinations))), - {lists:ukeymerge(1, NewStates, States), merge_replies(N, NewReplies, Replies)} - end, {StatesWithIndex, []}, Msgs), + {NewStates, Replies} = lists:foldl( + fun(Msg, {States, Replies}) -> + Destinations = hbbft_utils:random_n(rand:uniform(N - 2), lists:sublist(States, N - 2)), + {NewStates, NewReplies} = lists:unzip( + lists:map( + fun({J, Data}) -> + {NewData, Reply} = hbbft:input(Data, Msg), + {{J, NewData}, {J, Reply}} + end, + lists:keysort(1, Destinations) + ) + ), + {lists:ukeymerge(1, NewStates, States), merge_replies(N, NewReplies, Replies)} + end, + {StatesWithIndex, []}, + Msgs + ), %% check that at least N-F actors have started ACS: ?assert(length(Replies) =< N - F), %% all the nodes that have started ACS should have tried to send messages to all N peers (including themselves) - ?assert(lists:all(fun(E) -> E end, [ length(R) == N || {_, {send, R}} <- Replies ])), + ?assert(lists:all(fun(E) -> E end, [length(R) == N || {_, {send, R}} <- Replies])), %% start it on runnin' {_, ConvergedResults} = hbbft_test_utils:do_send_outer(Module, Replies, NewStates, sets:new()), %% check no actors returned a result @@ -151,25 +202,37 @@ one_actor_missing_test(Config) -> Module = proplists:get_value(module, Config), PrivateKeys = proplists:get_value(privatekeys, Config), - StatesWithIndex = [{J, hbbft:init(Sk, N, F, J, BatchSize, infinity)} || {J, Sk} <- lists:zip(lists:seq(0, N - 2), lists:sublist(PrivateKeys, N-1))], - Msgs = [ crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N*10)], + StatesWithIndex = [ + {J, hbbft:init(Sk, N, F, J, BatchSize, infinity)} + || {J, Sk} <- lists:zip(lists:seq(0, N - 2), lists:sublist(PrivateKeys, N - 1)) + ], + Msgs = [crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N * 10)], %% send each message to a random subset of the HBBFT actors - {NewStates, Replies} = lists:foldl(fun(Msg, {States, Replies}) -> - Destinations = hbbft_utils:random_n(rand:uniform(N-1), States), - {NewStates, NewReplies} = lists:unzip(lists:map(fun({J, Data}) -> - {NewData, Reply} = hbbft:input(Data, Msg), - {{J, NewData}, {J, Reply}} - end, lists:keysort(1, Destinations))), - {lists:ukeymerge(1, NewStates, States), merge_replies(N, NewReplies, Replies)} - end, {StatesWithIndex, []}, Msgs), + {NewStates, Replies} = lists:foldl( + fun(Msg, {States, Replies}) -> + Destinations = hbbft_utils:random_n(rand:uniform(N - 1), States), + {NewStates, NewReplies} = lists:unzip( + lists:map( + fun({J, Data}) -> + {NewData, Reply} = hbbft:input(Data, Msg), + {{J, NewData}, {J, Reply}} + end, + lists:keysort(1, Destinations) + ) + ), + {lists:ukeymerge(1, NewStates, States), merge_replies(N, NewReplies, Replies)} + end, + {StatesWithIndex, []}, + Msgs + ), %% check that at least N-F actors have started ACS: ?assert(length(Replies) >= N - F), %% all the nodes that have started ACS should have tried to send messages to all N peers (including themselves) - ?assert(lists:all(fun(E) -> E end, [ length(R) == N || {_, {send, R}} <- Replies ])), + ?assert(lists:all(fun(E) -> E end, [length(R) == N || {_, {send, R}} <- Replies])), %% start it on runnin' {_, ConvergedResults} = hbbft_test_utils:do_send_outer(Module, Replies, NewStates, sets:new()), %% check no actors returned a result - ?assertEqual(4, sets:size(ConvergedResults)), + ?assertEqual(N - 1, sets:size(ConvergedResults)), DistinctResults = sets:from_list([BVal || {result, {_, BVal}} <- sets:to_list(ConvergedResults)]), %% check all N actors returned the same result ?assertEqual(1, sets:size(DistinctResults)), @@ -185,21 +248,33 @@ two_actors_missing_test(Config) -> Module = proplists:get_value(module, Config), PrivateKeys = proplists:get_value(privatekeys, Config), - StatesWithIndex = [{J, hbbft:init(Sk, N, F, J, BatchSize, infinity)} || {J, Sk} <- lists:zip(lists:seq(0, N - 3), lists:sublist(PrivateKeys, N-2))], - Msgs = [ crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N*10)], + StatesWithIndex = [ + {J, hbbft:init(Sk, N, F, J, BatchSize, infinity)} + || {J, Sk} <- lists:zip(lists:seq(0, N - 3), lists:sublist(PrivateKeys, N - 2)) + ], + Msgs = [crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N * 10)], %% send each message to a random subset of the HBBFT actors - {NewStates, Replies} = lists:foldl(fun(Msg, {States, Replies}) -> - Destinations = hbbft_utils:random_n(rand:uniform(N-2), States), - {NewStates, NewReplies} = lists:unzip(lists:map(fun({J, Data}) -> - {NewData, Reply} = hbbft:input(Data, Msg), - {{J, NewData}, {J, Reply}} - end, lists:keysort(1, Destinations))), - {lists:ukeymerge(1, NewStates, States), merge_replies(N, NewReplies, Replies)} - end, {StatesWithIndex, []}, Msgs), + {NewStates, Replies} = lists:foldl( + fun(Msg, {States, Replies}) -> + Destinations = hbbft_utils:random_n(rand:uniform(N - 2), States), + {NewStates, NewReplies} = lists:unzip( + lists:map( + fun({J, Data}) -> + {NewData, Reply} = hbbft:input(Data, Msg), + {{J, NewData}, {J, Reply}} + end, + lists:keysort(1, Destinations) + ) + ), + {lists:ukeymerge(1, NewStates, States), merge_replies(N, NewReplies, Replies)} + end, + {StatesWithIndex, []}, + Msgs + ), %% check that at least N-F actors have started ACS: ?assert(length(Replies) =< N - F), %% all the nodes that have started ACS should have tried to send messages to all N peers (including themselves) - ?assert(lists:all(fun(E) -> E end, [ length(R) == N || {_, {send, R}} <- Replies ])), + ?assert(lists:all(fun(E) -> E end, [length(R) == N || {_, {send, R}} <- Replies])), %% start it on runnin' {_, ConvergedResults} = hbbft_test_utils:do_send_outer(Module, Replies, NewStates, sets:new()), %% check no actors returned a result @@ -207,35 +282,50 @@ two_actors_missing_test(Config) -> ok. encrypt_decrypt_test(Config) -> - PubKey = proplists:get_value(pubkey, Config), - PrivateKeys = proplists:get_value(privatekeys, Config), - - PlainText = crypto:strong_rand_bytes(24), - Enc = hbbft:encrypt(PubKey, PlainText), - {ok, EncKey} = hbbft:get_encrypted_key(hd(PrivateKeys), Enc), - DecKey = tpke_pubkey:combine_shares(PubKey, EncKey, [ tpke_privkey:decrypt_share(SK, EncKey) || SK <- PrivateKeys]), - ?assertEqual(PlainText, hbbft:decrypt(DecKey, Enc)), + case proplists:get_value(curve, Config, 'BLS12-381') of + 'BLS12-381' -> + PrivateKeys = [SK1 | _RemainingSKs] = proplists:get_value(privatekeys, Config), + PlainText = crypto:strong_rand_bytes(24), + Ciphertext = tc_ciphertext:deserialize(hbbft:encrypt('BLS12-381', hd(PrivateKeys), PlainText)), + DecShares = [tc_key_share:decrypt_share(SK, Ciphertext) || SK <- PrivateKeys], + {ok, Decrypted} = tc_key_share:combine_decryption_shares(SK1, DecShares, Ciphertext), + ?assertEqual(PlainText, Decrypted); + 'SS512' -> + PrivateKeys = proplists:get_value(privatekeys, Config), + PubKey = tpke_privkey:public_key(hd(PrivateKeys)), + PlainText = crypto:strong_rand_bytes(24), + Enc = hbbft:encrypt('SS512', hd(PrivateKeys), PlainText), + {ok, EncKey} = hbbft:get_encrypted_key(hd(PrivateKeys), Enc), + DecKey = tpke_pubkey:combine_shares(PubKey, EncKey, [ tpke_privkey:decrypt_share(SK, EncKey) || SK <- PrivateKeys]), + ?assertEqual(PlainText, hbbft:decrypt(DecKey, Enc)) + end, ok. start_on_demand_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), + Curve = proplists:get_value(curve, Config), BatchSize = proplists:get_value(batchsize, Config), - PubKey = proplists:get_value(pubkey, Config), PrivateKeys = proplists:get_value(privatekeys, Config), - Workers = [ element(2, hbbft_worker:start_link(N, F, I, tpke_privkey:serialize(SK), BatchSize, false)) || {I, SK} <- enumerate(PrivateKeys) ], + Workers = [ + element(2, hbbft_worker:start_link(N, F, I, hbbft_test_utils:serialize_key(Curve, SK), BatchSize, false)) + || {I, SK} <- enumerate(PrivateKeys) + ], [W1, _W2 | RemainingWorkers] = Workers, - Msgs = [ crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N*20)], + Msgs = [crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N * 20)], KnownMsg = crypto:strong_rand_bytes(128), %% feed the badgers some msgs - lists:foreach(fun(Msg) -> - Destinations = random_n(rand:uniform(length(RemainingWorkers)), RemainingWorkers), - ct:log("destinations ~p~n", [Destinations]), - [ok = hbbft_worker:submit_transaction(Msg, D) || D <- Destinations] - end, Msgs), + lists:foreach( + fun(Msg) -> + Destinations = random_n(rand:uniform(length(RemainingWorkers)), RemainingWorkers), + ct:log("destinations ~p~n", [Destinations]), + [ok = hbbft_worker:submit_transaction(Msg, D) || D <- Destinations] + end, + Msgs + ), ok = hbbft_worker:submit_transaction(KnownMsg, W1), @@ -251,9 +341,9 @@ start_on_demand_test(Config) -> [Chain] = sets:to_list(Chains), ct:log("chain is of height ~p~n", [length(Chain)]), %% verify they are cryptographically linked - true = hbbft_worker:verify_chain(Chain, PubKey), + true = hbbft_worker:verify_chain(Chain, hd(PrivateKeys)), %% check all the transactions are unique - BlockTxns = lists:flatten([ hbbft_worker:block_transactions(B) || B <- Chain ]), + BlockTxns = lists:flatten([hbbft_worker:block_transactions(B) || B <- Chain]), true = lists:member(KnownMsg, BlockTxns), true = length(BlockTxns) == sets:size(sets:from_list(BlockTxns)), %% check they're all members of the original message list @@ -264,24 +354,35 @@ start_on_demand_test(Config) -> one_actor_wrong_key_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), + Curve = proplists:get_value(curve, Config), BatchSize = proplists:get_value(batchsize, Config), - PubKey = proplists:get_value(pubkey, Config), PrivateKeys0 = proplists:get_value(privatekeys, Config), - {ok, Dealer} = dealer:new(N, F+1, 'SS512'), - {ok, {_PubKey, PrivateKeys1}} = dealer:deal(Dealer), + case Curve of + 'BLS12-381' -> + PrivateKeys1 = tc_key_share:deal(N, F); + 'SS512' -> + {ok, Dealer} = dealer:new(N, F+1, 'SS512'), + {ok, {_PubKey, PrivateKeys1}} = dealer:deal(Dealer) + end, %% give actor 1 a completely unrelated key %% this will prevent it from doing any valid threshold cryptography %% and thus it will not be able to reach consensus - PrivateKeys = [hd(PrivateKeys1)|tl(PrivateKeys0)], + PrivateKeys = [hd(PrivateKeys1) | tl(PrivateKeys0)], - Workers = [ element(2, hbbft_worker:start_link(N, F, I, tpke_privkey:serialize(SK), BatchSize, false)) || {I, SK} <- enumerate(PrivateKeys) ], - Msgs = [ crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N*20)], + Workers = [ + element(2, hbbft_worker:start_link(N, F, I, hbbft_test_utils:serialize_key(Curve, SK), BatchSize, false)) + || {I, SK} <- enumerate(PrivateKeys) + ], + Msgs = [crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N * 20)], %% feed the badgers some msgs - lists:foreach(fun(Msg) -> - Destinations = random_n(rand:uniform(N), Workers), - ct:log("destinations ~p~n", [Destinations]), - [ok = hbbft_worker:submit_transaction(Msg, D) || D <- Destinations] - end, Msgs), + lists:foreach( + fun(Msg) -> + Destinations = random_n(rand:uniform(N), Workers), + ct:log("destinations ~p~n", [Destinations]), + [ok = hbbft_worker:submit_transaction(Msg, D) || D <- Destinations] + end, + Msgs + ), %% wait for all the worker's mailboxes to settle and %% wait for the chains to converge @@ -295,9 +396,9 @@ one_actor_wrong_key_test(Config) -> [Chain] = sets:to_list(Chains), ct:log("chain is of height ~p~n", [length(Chain)]), %% verify they are cryptographically linked - true = hbbft_worker:verify_chain(lists:reverse(Chain), PubKey), + true = hbbft_worker:verify_chain(lists:reverse(Chain), hd(PrivateKeys0)), %% check all the transactions are unique - BlockTxns = lists:flatten([ hbbft_worker:block_transactions(B) || B <- Chain ]), + BlockTxns = lists:flatten([hbbft_worker:block_transactions(B) || B <- Chain]), true = length(BlockTxns) == sets:size(sets:from_list(BlockTxns)), %% check they're all members of the original message list true = sets:is_subset(sets:from_list(BlockTxns), sets:from_list(Msgs)), @@ -308,25 +409,34 @@ one_actor_wrong_key_test(Config) -> one_actor_corrupted_key_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), + Curve = proplists:get_value(curve, Config), BatchSize = proplists:get_value(batchsize, Config), - PubKey = proplists:get_value(pubkey, Config), - [PK1|PrivateKeys0] = proplists:get_value(privatekeys, Config), - PKE = element(3, PK1), - %% scramble the private element of the key + [PK1 | PrivateKeys0] = proplists:get_value(privatekeys, Config), + %% scramble the index of the key %% this will not prevent the actor for encrypting their bundle %% merely prevent it producing valid decryption shares %% thus all the actors will be able to converge - PK2 = setelement(3, PK1, erlang_pbc:element_random(PKE)), - PrivateKeys = [PK2|PrivateKeys0], - - Workers = [ element(2, hbbft_worker:start_link(N, F, I, tpke_privkey:serialize(SK), BatchSize, false)) || {I, SK} <- enumerate(PrivateKeys) ], - Msgs = [ crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N*20)], + {Pos, Val} = case Curve of + 'BLS12-381' -> {4, element(4, PK1) + 1000}; + 'SS512' -> {3, erlang_pbc:element_random(element(3, PK1))} + end, + PK2 = setelement(Pos, PK1, Val), + PrivateKeys = [PK2 | PrivateKeys0], + + Workers = [ + element(2, hbbft_worker:start_link(N, F, I, hbbft_test_utils:serialize_key(Curve, SK), BatchSize, false)) + || {I, SK} <- enumerate(PrivateKeys) + ], + Msgs = [crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N * 20)], %% feed the badgers some msgs - lists:foreach(fun(Msg) -> - Destinations = random_n(rand:uniform(N), Workers), - ct:log("destinations ~p~n", [Destinations]), - [ok = hbbft_worker:submit_transaction(Msg, D) || D <- Destinations] - end, Msgs), + lists:foreach( + fun(Msg) -> + Destinations = random_n(rand:uniform(N), Workers), + ct:log("destinations ~p~n", [Destinations]), + [ok = hbbft_worker:submit_transaction(Msg, D) || D <- Destinations] + end, + Msgs + ), %% wait for all the worker's mailboxes to settle and %% wait for the chains to converge @@ -338,9 +448,9 @@ one_actor_corrupted_key_test(Config) -> [Chain] = sets:to_list(Chains), ct:log("chain is of height ~p~n", [length(Chain)]), %% verify they are cryptographically linked - true = hbbft_worker:verify_chain(Chain, PubKey), + true = hbbft_worker:verify_chain(Chain, hd(PrivateKeys)), %% check all the transactions are unique - BlockTxns = lists:flatten([ hbbft_worker:block_transactions(B) || B <- Chain ]), + BlockTxns = lists:flatten([hbbft_worker:block_transactions(B) || B <- Chain]), true = length(BlockTxns) == sets:size(sets:from_list(BlockTxns)), %% check they're all members of the original message list true = sets:is_subset(sets:from_list(BlockTxns), sets:from_list(Msgs)), @@ -350,31 +460,35 @@ one_actor_corrupted_key_test(Config) -> one_actor_oversized_batch_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), + Curve = proplists:get_value(curve, Config), B = proplists:get_value(batchsize, Config), - PK = proplists:get_value(pubkey, Config), - [SK1 | SKs] = proplists:get_value(privatekeys, Config), + PrivateKeys = [SK1 | SKs] = proplists:get_value(privatekeys, Config), - BadWorker = % with an oversized batch parameter - ok(hbbft_worker:start_link(N, F, 0, tpke_privkey:serialize(SK1), B + 1, false)), + % with an oversized batch parameter + BadWorker = + ok(hbbft_worker:start_link(N, F, 0, hbbft_test_utils:serialize_key(Curve, SK1), B + 1, false)), GoodWorkers = - [ok(hbbft_worker:start_link(N, F, I + 1, tpke_privkey:serialize(SKi), B, false)) - || {I, SKi} <- enumerate(SKs) + [ + ok(hbbft_worker:start_link(N, F, I + 1, hbbft_test_utils:serialize_key(Curve, SKi), B, false)) + || {I, SKi} <- enumerate(SKs) ], % Each node needs at least B/N transactions. GoodTxns = [list_to_binary("GOOD_" ++ integer_to_list(I)) || I <- lists:seq(1, B * N)], - BadTxns = [list_to_binary("BAD_" ++ integer_to_list(I)) || I <- lists:seq(1, B)], + BadTxns = [list_to_binary("BAD_" ++ integer_to_list(I)) || I <- lists:seq(1, B)], % Submit transactions: lists:foreach( - fun (T) -> ok = hbbft_worker:submit_transaction(T, BadWorker) end, - BadTxns), + fun(T) -> ok = hbbft_worker:submit_transaction(T, BadWorker) end, + BadTxns + ), lists:foreach( - fun (T) -> - Destinations = random_n(rand:uniform(N), GoodWorkers), - [ok = hbbft_worker:submit_transaction(T, D) || D <- Destinations] - end, - GoodTxns), + fun(T) -> + Destinations = random_n(rand:uniform(N), GoodWorkers), + [ok = hbbft_worker:submit_transaction(T, D) || D <- Destinations] + end, + GoodTxns + ), % Wait for all the worker's mailboxes to settle: ok = wait_for_chains([BadWorker | GoodWorkers], 2), @@ -387,7 +501,7 @@ one_actor_oversized_batch_test(Config) -> lists:flatten([hbbft_worker:block_transactions(Block) || Block <- Chain]), % Transactions are cryptographically linked: - ?assertMatch(true, hbbft_worker:verify_chain(Chain, PK)), + ?assertMatch(true, hbbft_worker:verify_chain(Chain, hd(PrivateKeys))), % Transactions are unique: ?assertMatch([], CommittedTxns -- lists:usort(CommittedTxns)), @@ -397,14 +511,14 @@ one_actor_oversized_batch_test(Config) -> ?assertMatch([], CommittedTxns -- GoodTxns), ok. -batch_size_limit_minimal_test(_) -> +batch_size_limit_minimal_test(Config) -> % Same test goal as one_actor_oversized_batch_test, but % the absolute minimal to test the state transition. N = 1, F = 0, + Curve = proplists:get_value(curve, Config), BatchSize = 1, - {ok, Dealer} = dealer:new(N, F + 1, 'SS512'), - {ok, {PK, [SK | _]}} = dealer:deal(Dealer), + [SK | _] = tc_key_share:deal(N, F), % Protocol begins. ProtocolInstanceId = 0, @@ -415,11 +529,12 @@ batch_size_limit_minimal_test(_) -> % Pretending ACS happened here. Stamp = <<"trust-me-im-a-stamp">>, - Enc = hbbft:encrypt(PK, hbbft:encode_list([Stamp | Buf])), - {ok, EncKey} = hbbft:get_encrypted_key(SK, Enc), - AcsInstanceId = 0, % E? + Enc = tc_pubkey:encrypt(tc_key_share:public_key(SK), hbbft:encode_list([Stamp | Buf])), + + % E? + AcsInstanceId = 0, State_1 = hbbft:abstraction_breaking_set_acs_results(State_0, [{AcsInstanceId, Enc}]), - State_2 = hbbft:abstraction_breaking_set_enc_keys(State_1, #{AcsInstanceId => EncKey}), + State_2 = hbbft:abstraction_breaking_set_enc_keys(State_1, #{AcsInstanceId => Enc}), % Decoding transactions from ACS, which we expect to be rejectected. {State_3, Result} = @@ -430,30 +545,43 @@ batch_size_limit_minimal_test(_) -> dec, hbbft:round(State_2), AcsInstanceId, - hbbft_utils:share_to_binary(tpke_privkey:decrypt_share(SK, EncKey)) + hbbft_utils:dec_share_to_binary(Curve, tc_key_share:decrypt_share(SK, Enc)) } ), ?assertMatch({result, {transactions, [], []}}, Result), ?assertMatch(#{sent_txns := true}, hbbft:status(State_3)), ok. --record(state, - { - node_count :: integer(), - stopped = false :: boolean(), - results = sets:new() :: sets:set() - }). - -trivial(_Message, _From, _To, _NodeState, _NewState, _Actions, - #state{stopped = false} = State) -> +-record(state, { + node_count :: integer(), + stopped = false :: boolean(), + results = sets:new() :: sets:set() +}). + +trivial( + _Message, + _From, + _To, + _NodeState, + _NewState, + _Actions, + #state{stopped = false} = State +) -> case rand:uniform(100) of 100 -> {actions, [{stop_node, 2}], State#state{stopped = true}}; _ -> {actions, [], State} end; -trivial(_Message, _From, To, _NodeState, _NewState, {result, Result}, - #state{results = Results0} = State) -> +trivial( + _Message, + _From, + To, + _NodeState, + _NewState, + {result, Result}, + #state{results = Results0} = State +) -> Results = sets:add_element({result, {To, Result}}, Results0), %% ct:pal("results len ~p ~p", [sets:size(Results), sets:to_list(Results)]), case sets:size(Results) == State#state.node_count of @@ -466,38 +594,48 @@ trivial(_Message, _From, _To, _NodeState, _NewState, _Actions, ModelState) -> {actions, [], ModelState}. initial_fakecast_test(Config) -> - N = 4, % proplists:get_value(n, Config), - F = 1, % proplists:get_value(f, Config), + % proplists:get_value(n, Config), + N = 4, + % proplists:get_value(f, Config), + F = 1, BatchSize = proplists:get_value(batchsize, Config), Module = proplists:get_value(module, Config), PrivateKeys0 = proplists:get_value(privatekeys, Config), {PrivateKeys, _} = lists:split(N, PrivateKeys0), Init = fun() -> - {ok, - #fc_conf{ - test_mod = Module, - nodes = lists:seq(1, N), - configs = [[Sk, N, F, ID, BatchSize, infinity] - || {ID, Sk} <- lists:zip(lists:seq(0, N - 1), PrivateKeys)] - }, - #state{node_count = N - 2} - } - end, - Msgs = [ crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N*10)], + {ok, + #fc_conf{ + test_mod = Module, + nodes = lists:seq(1, N), + configs = [ + [Sk, N, F, ID, BatchSize, infinity] + || {ID, Sk} <- lists:zip(lists:seq(0, N - 1), PrivateKeys) + ] + }, + #state{node_count = N - 2}} + end, + Msgs = [crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N * 10)], %% send each message to a random subset of the HBBFT actors Input = fun() -> - lists:foldl(fun(ID, Acc) -> - Size = max(length(Msgs), BatchSize + (rand:uniform(length(Msgs)))), - Subset = hbbft_test_utils:random_n(Size, Msgs), - lists:append([{ID, Msg} || Msg <- Subset], Acc) - end, [], lists:seq(0, N - 1)) + lists:foldl( + fun(ID, Acc) -> + Size = max(length(Msgs), BatchSize + (rand:uniform(length(Msgs)))), + Subset = hbbft_test_utils:random_n(Size, Msgs), + lists:append([{ID, Msg} || Msg <- Subset], Acc) + end, + [], + lists:seq(0, N - 1) + ) end, %% start it on runnin' - {ok, ConvergedResults} = fakecast:start_test(Init, fun trivial/7, - os:timestamp(), - Input), + {ok, ConvergedResults} = fakecast:start_test( + Init, + fun trivial/7, + os:timestamp(), + Input + ), %% check all N actors returned a result ?assertEqual(N - 2, sets:size(ConvergedResults)), @@ -520,43 +658,63 @@ random_n(N, List) -> lists:sublist(shuffle(List), N). shuffle(List) -> - [X || {_,X} <- lists:sort([{rand:uniform(), N} || N <- List])]. + [X || {_, X} <- lists:sort([{rand:uniform(), N} || N <- List])]. merge_replies(N, NewReplies, Replies) when N < 0 orelse length(NewReplies) == 0 -> Replies; merge_replies(N, NewReplies, Replies) -> case lists:keyfind(N, 1, NewReplies) of false -> - merge_replies(N-1, lists:keydelete(N, 1, NewReplies), Replies); + merge_replies(N - 1, lists:keydelete(N, 1, NewReplies), Replies); {N, ok} -> - merge_replies(N-1, lists:keydelete(N, 1, NewReplies), Replies); + merge_replies(N - 1, lists:keydelete(N, 1, NewReplies), Replies); {N, {send, ToSend}} -> - NewSend = case lists:keyfind(N, 1, Replies) of - false -> - {N, {send, ToSend}}; - {N, OldSend} -> - {N, {send, OldSend ++ ToSend}} - end, - merge_replies(N-1, lists:keydelete(N, 1, NewReplies), lists:keystore(N, 1, Replies, NewSend)) + NewSend = + case lists:keyfind(N, 1, Replies) of + false -> + {N, {send, ToSend}}; + {N, OldSend} -> + {N, {send, OldSend ++ ToSend}} + end, + merge_replies( + N - 1, + lists:keydelete(N, 1, NewReplies), + lists:keystore(N, 1, Replies, NewSend) + ) end. wait_for_chains(Workers, MinHeight) -> - hbbft_ct_utils:wait_until(fun() -> - Chains = lists:map(fun(W) -> - {ok, Blocks} = hbbft_worker:get_blocks(W), - Blocks - end, Workers), - - lists:all(fun(C) -> length(C) >= MinHeight end, Chains) - end, 60*2, 500). + hbbft_ct_utils:wait_until( + fun() -> + Chains = lists:map( + fun(W) -> + {ok, Blocks} = hbbft_worker:get_blocks(W), + Blocks + end, + Workers + ), + + lists:all(fun(C) -> length(C) >= MinHeight end, Chains) + end, + 60 * 2, + 500 + ). get_common_chain(Workers) -> - AllChains = lists:map(fun(W) -> - {ok, Blocks} = hbbft_worker:get_blocks(W), - Blocks - end, Workers), + AllChains = lists:map( + fun(W) -> + {ok, Blocks} = hbbft_worker:get_blocks(W), + Blocks + end, + Workers + ), %% find the shortest chain and check all chains have the same common prefix - ShortestChainLen = hd(lists:sort([ length(C) || C <- AllChains ])), + ShortestChainLen = hd(lists:sort([length(C) || C <- AllChains])), %% chains are stored in reverse, so we have to reverse them to get the first N blocks and then re-reverse them to restore expected ordering - sets:from_list(lists:map(fun(C) -> lists:reverse(lists:sublist(lists:reverse(C), ShortestChainLen)) end, AllChains)). + sets:from_list( + lists:map( + fun(C) -> lists:reverse(lists:sublist(lists:reverse(C), ShortestChainLen)) end, + AllChains + ) + ). diff --git a/test/hbbft_acs_SUITE.erl b/test/hbbft_acs_SUITE.erl index f206f68..5a8672c 100644 --- a/test/hbbft_acs_SUITE.erl +++ b/test/hbbft_acs_SUITE.erl @@ -34,9 +34,8 @@ init_per_testcase(_, Config) -> N = list_to_integer(os:getenv("N", "10")), % N > 25 runs for minutes. F = N div 4, Module = hbbft_acs, - {ok, Dealer} = dealer:new(N, F+1, 'SS512'), - {ok, {_PubKey, PrivateKeys}} = dealer:deal(Dealer), - [{n, N}, {f, F}, {module, Module}, {privatekeys, PrivateKeys} | Config]. + KeyShares = tc_key_share:deal(N, F), + [{n, N}, {f, F}, {module, Module}, {key_shares, KeyShares} | Config]. end_per_testcase(_, _Config) -> eprof:stop_profiling(), @@ -47,9 +46,9 @@ init_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), Module = proplists:get_value(module, Config), - PrivateKeys = proplists:get_value(privatekeys, Config), + KeyShares = proplists:get_value(key_shares, Config), Msgs = [ crypto:strong_rand_bytes(512) || _ <- lists:seq(1, N)], - StatesWithId = [{J, hbbft_acs:init(Sk, N, F, J)} || {J, Sk} <- lists:zip(lists:seq(0, N - 1), PrivateKeys)], + StatesWithId = [{J, hbbft_acs:init(Sk, N, F, J)} || {J, Sk} <- lists:zip(lists:seq(0, N - 1), KeyShares)], MixedList = lists:zip(Msgs, StatesWithId), Res = lists:map(fun({Msg, {J, State}}) -> {NewState, Result} = hbbft_acs:input(State, Msg), @@ -69,9 +68,9 @@ one_dead_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), Module = proplists:get_value(module, Config), - PrivateKeys = proplists:get_value(privatekeys, Config), + KeyShares = proplists:get_value(key_shares, Config), Msgs = [ crypto:strong_rand_bytes(512) || _ <- lists:seq(1, N)], - StatesWithId = [{J, hbbft_acs:init(Sk, N, F, J)} || {J, Sk} <- lists:zip(lists:seq(0, N - 1), PrivateKeys)], + StatesWithId = [{J, hbbft_acs:init(Sk, N, F, J)} || {J, Sk} <- lists:zip(lists:seq(0, N - 1), KeyShares)], MixedList = lists:zip(Msgs, StatesWithId), Res = lists:map(fun({Msg, {J, State}}) -> {NewState, Result} = hbbft_acs:input(State, Msg), @@ -91,9 +90,9 @@ f_dead_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), Module = proplists:get_value(module, Config), - PrivateKeys = proplists:get_value(privatekeys, Config), + KeyShares = proplists:get_value(key_shares, Config), Msgs = [ crypto:strong_rand_bytes(512) || _ <- lists:seq(1, N)], - StatesWithId = [{J, hbbft_acs:init(Sk, N, F, J)} || {J, Sk} <- lists:zip(lists:seq(0, N - 1), PrivateKeys)], + StatesWithId = [{J, hbbft_acs:init(Sk, N, F, J)} || {J, Sk} <- lists:zip(lists:seq(0, N - 1), KeyShares)], MixedList = lists:zip(Msgs, StatesWithId), Res = lists:map(fun({Msg, {J, State}}) -> {NewState, Result} = hbbft_acs:input(State, Msg), @@ -113,9 +112,9 @@ fplusone_dead_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), Module = proplists:get_value(module, Config), - PrivateKeys = proplists:get_value(privatekeys, Config), + KeyShares = proplists:get_value(key_shares, Config), Msgs = [ crypto:strong_rand_bytes(512) || _ <- lists:seq(1, N)], - StatesWithId = [{J, hbbft_acs:init(Sk, N, F, J)} || {J, Sk} <- lists:zip(lists:seq(0, N - 1), PrivateKeys)], + StatesWithId = [{J, hbbft_acs:init(Sk, N, F, J)} || {J, Sk} <- lists:zip(lists:seq(0, N - 1), KeyShares)], MixedList = lists:zip(Msgs, StatesWithId), Res = lists:map(fun({Msg, {J, State}}) -> {NewState, Result} = hbbft_acs:input(State, Msg), @@ -162,9 +161,9 @@ fakecast_test(Config) -> N = 4, % proplists:get_value(n, Config), F = 1, % proplists:get_value(f, Config), Module = proplists:get_value(module, Config), - PrivateKeys0 = proplists:get_value(privatekeys, Config), + KeyShares0 = proplists:get_value(key_shares, Config), - {PrivateKeys, _} = lists:split(N, PrivateKeys0), + {KeyShares, _} = lists:split(N, KeyShares0), Init = fun() -> {ok, @@ -172,7 +171,7 @@ fakecast_test(Config) -> test_mod = Module, nodes = lists:seq(1, N), %% are names useful? configs = [[Sk, N, F, ID] - || {ID, Sk} <- lists:zip(lists:seq(0, N - 1), PrivateKeys)] + || {ID, Sk} <- lists:zip(lists:seq(0, N - 1), KeyShares)] }, #state{node_count = N - F} } diff --git a/test/hbbft_bba_SUITE.erl b/test/hbbft_bba_SUITE.erl index 6361532..df96cd4 100644 --- a/test/hbbft_bba_SUITE.erl +++ b/test/hbbft_bba_SUITE.erl @@ -30,9 +30,8 @@ init_per_testcase(_, Config) -> N = list_to_integer(os:getenv("N", "34")), F = (N - 1) div 3, Module = hbbft_bba, - {ok, Dealer} = dealer:new(N, F+1, 'SS512'), - {ok, {PubKey, PrivateKeys}} = dealer:deal(Dealer), - [{n, N}, {f, F}, {dealer, Dealer}, {module, Module}, {pubkey, PubKey}, {privatekeys, PrivateKeys} | Config]. + KeyShares = tc_key_share:deal(N, F), + [{n, N}, {f, F}, {module, Module}, {key_shares, KeyShares} | Config]. end_per_testcase(_, _Config) -> ok. @@ -42,9 +41,8 @@ termination_test(Config) -> Fun = fun(Vals) -> N = 7, F = 2, - {ok, Dealer} = dealer:new(N, F+1, 'SS512'), - {ok, {_PubKey, PrivateKeys}} = dealer:deal(Dealer), - States = [hbbft_bba:init(Sk, N, F) || Sk <- PrivateKeys], + KeyShares = tc_key_share:deal(N, F), + States = [hbbft_bba:init(Sk, N, F) || Sk <- KeyShares], StatesWithId = lists:zip(lists:seq(0, length(States) - 1), States), MixedList = lists:zip(Vals, StatesWithId), %% all valid members should call get_coin @@ -74,9 +72,9 @@ init_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), Module = proplists:get_value(module, Config), - {ok, Dealer} = dealer:new(N, F+1, 'SS512'), - {ok, {_PubKey, PrivateKeys}} = dealer:deal(Dealer), - States = [hbbft_bba:init(Sk, N, F) || Sk <- PrivateKeys], + KeyShares = tc_key_share:deal(N, F+1), + + States = [hbbft_bba:init(Sk, N, F) || Sk <- KeyShares], StatesWithId = lists:zip(lists:seq(0, length(States) - 1), States), %% all valid members should call get_coin Res = lists:map(fun({J, State}) -> @@ -95,8 +93,8 @@ init_with_zeroes_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), Module = proplists:get_value(module, Config), - PrivateKeys = proplists:get_value(privatekeys, Config), - States = [hbbft_bba:init(Sk, N, F) || Sk <- PrivateKeys], + KeyShares = proplists:get_value(key_shares, Config), + States = [hbbft_bba:init(Sk, N, F) || Sk <- KeyShares], StatesWithId = lists:zip(lists:seq(0, length(States) - 1), States), ZeroList = lists:zip([1|lists:duplicate(N-1, 0)], StatesWithId), %% all valid members should call get_coin @@ -117,8 +115,8 @@ init_with_ones_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), Module = proplists:get_value(module, Config), - PrivateKeys = proplists:get_value(privatekeys, Config), - States = [hbbft_bba:init(Sk, N, F) || Sk <- PrivateKeys], + KeyShares = proplists:get_value(key_shares, Config), + States = [hbbft_bba:init(Sk, N, F) || Sk <- KeyShares], StatesWithId = lists:zip(lists:seq(0, length(States) - 1), States), OneList = lists:zip(lists:duplicate(N-1, 1) ++ [0], StatesWithId), %% all valid members should call get_coin @@ -139,9 +137,8 @@ init_with_mixed_zeros_and_ones_test(Config) -> Module = proplists:get_value(module, Config), N = 10, F = 2, - {ok, Dealer} = dealer:new(N, F+1, 'SS512'), - {ok, {_PubKey, PrivateKeys}} = dealer:deal(Dealer), - States = [hbbft_bba:init(Sk, N, F) || Sk <- PrivateKeys], + KeyShares = tc_key_share:deal(N, F), + States = [hbbft_bba:init(Sk, N, F) || Sk <- KeyShares], StatesWithId = lists:zip(lists:seq(0, length(States) - 1), States), MixedList = lists:zip([1, 1, 1, 0, 1, 0, 0, 0, 0, 0], StatesWithId), %% all valid members should call get_coin @@ -162,8 +159,8 @@ f_dead_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), Module = proplists:get_value(module, Config), - PrivateKeys = proplists:get_value(privatekeys, Config), - States = lists:sublist([hbbft_bba:init(Sk, N, F) || Sk <- PrivateKeys], 1, N-F), + KeyShares = proplists:get_value(key_shares, Config), + States = lists:sublist([hbbft_bba:init(Sk, N, F) || Sk <- KeyShares], 1, N-F), StatesWithId = lists:zip(lists:seq(0, N - 1 - F), States), %% all valid members should call get_coin Res = lists:map(fun({J, State}) -> @@ -182,8 +179,8 @@ fplusone_dead_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), Module = proplists:get_value(module, Config), - PrivateKeys = proplists:get_value(privatekeys, Config), - States = lists:sublist([hbbft_bba:init(Sk, N, F) || Sk <- PrivateKeys], 1, N - (F + 1)), + KeyShares = proplists:get_value(key_shares, Config), + States = lists:sublist([hbbft_bba:init(Sk, N, F) || Sk <- KeyShares], 1, N - (F + 1)), StatesWithId = lists:zip(lists:seq(0, N - 1 - (F + 1)), States), %% all valid members should call get_coin Res = lists:map(fun({J, State}) -> @@ -256,14 +253,14 @@ fakecast_test(Config) -> Module = proplists:get_value(module, Config), N = 4, F = 1, - {ok, Dealer} = dealer:new(N, F+1, 'SS512'), - {ok, {_PubKey, PrivateKeys}} = dealer:deal(Dealer), + KeyShares = tc_key_share:deal(N, F + 1), + Init = fun() -> {ok, #fc_conf{ test_mod = Module, nodes = [aaa, bbb, ccc, ddd], - configs = [[Sk, N, F] || Sk <- PrivateKeys] + configs = [[Sk, N, F] || Sk <- KeyShares] }, #state{node_count = N} } diff --git a/test/hbbft_cc_SUITE.erl b/test/hbbft_cc_SUITE.erl index 9112f49..c21c95d 100644 --- a/test/hbbft_cc_SUITE.erl +++ b/test/hbbft_cc_SUITE.erl @@ -3,7 +3,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). --export([all/0, init_per_testcase/2, end_per_testcase/2]). +-export([all/0, groups/0, init_per_group/2, end_per_group/2, init_per_testcase/2, end_per_testcase/2]). -export([ init_test/1, f_dead_test/1, @@ -15,6 +15,9 @@ ]). all() -> + [{group, ss512}, {group, bls12_381}]. + +test_cases() -> [ init_test, f_dead_test, @@ -25,13 +28,32 @@ all() -> mixed_keys_test ]. +groups() -> + [{ss512, [], test_cases()}, + {bls12_381, [], test_cases()}]. + +init_per_group(ss512, Config) -> + [{curve, 'SS512'} | Config]; +init_per_group(bls12_381, Config) -> + [{curve, 'BLS12-381'} | Config]. + +end_per_group(_, _Config) -> + ok. + init_per_testcase(_, Config) -> N = list_to_integer(os:getenv("N", "34")), F = N div 4, Module = hbbft_cc, - {ok, Dealer} = dealer:new(N, F+1, 'SS512'), - {ok, {PubKey, PrivateKeys}} = dealer:deal(Dealer), - [{n, N}, {f, F}, {dealer, Dealer}, {module, Module}, {pubkey, PubKey}, {privatekeys, PrivateKeys} | Config]. + + case proplists:get_value(curve, Config, 'BLS12-381') of + 'BLS12-381' -> + KeyShares = tc_key_share:deal(N, F); + 'SS512' -> + {ok, Dealer} = dealer:new(N, F+1, 'SS512'), + {ok, {_PubKey, KeyShares}} = dealer:deal(Dealer) + end, + + [{n, N}, {f, F}, {key_shares, KeyShares}, {module, Module} | Config]. end_per_testcase(_, _Config) -> ok. @@ -40,10 +62,9 @@ init_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), Module = proplists:get_value(module, Config), - PubKey = proplists:get_value(pubkey, Config), - PrivateKeys = proplists:get_value(privatekeys, Config), - Sid = tpke_pubkey:hash_message(PubKey, crypto:strong_rand_bytes(32)), - States = [hbbft_cc:init(Sk, Sid, N, F) || Sk <- PrivateKeys], + KeyShares = proplists:get_value(key_shares, Config), + Sid = crypto:strong_rand_bytes(32), + States = [hbbft_cc:init(Sk, Sid, N, F) || Sk <- KeyShares], StatesWithId = lists:zip(lists:seq(0, length(States) - 1), States), %% all valid members should call get_coin Res = lists:map(fun({J, State}) -> @@ -62,9 +83,8 @@ f_dead_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), Module = proplists:get_value(module, Config), - PubKey = proplists:get_value(pubkey, Config), - PrivateKeys = proplists:get_value(privatekeys, Config), - Sid = tpke_pubkey:hash_message(PubKey, crypto:strong_rand_bytes(32)), + PrivateKeys = proplists:get_value(key_shares, Config), + Sid = crypto:strong_rand_bytes(32), InitialStates = [hbbft_cc:init(Sk, Sid, N, F) || Sk <- PrivateKeys], ToCrash = hbbft_test_utils:random_n(F, InitialStates), StatesAfterCrash = ordsets:to_list(ordsets:subtract(ordsets:from_list(InitialStates),ordsets:from_list(ToCrash))), @@ -90,9 +110,8 @@ fplusone_dead_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config) + 1, Module = proplists:get_value(module, Config), - PubKey = proplists:get_value(pubkey, Config), - PrivateKeys = proplists:get_value(privatekeys, Config), - Sid = tpke_pubkey:hash_message(PubKey, crypto:strong_rand_bytes(32)), + PrivateKeys = proplists:get_value(key_shares, Config), + Sid = crypto:strong_rand_bytes(32), InitialStates = [hbbft_cc:init(Sk, Sid, N, F) || Sk <- PrivateKeys], ToCrash = hbbft_test_utils:random_n(F+1, InitialStates), StatesAfterCrash = ordsets:to_list(ordsets:subtract(ordsets:from_list(InitialStates),ordsets:from_list(ToCrash))), @@ -117,9 +136,8 @@ too_many_dead_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config) + (N div 2), Module = proplists:get_value(module, Config), - PubKey = proplists:get_value(pubkey, Config), - PrivateKeys = proplists:get_value(privatekeys, Config), - Sid = tpke_pubkey:hash_message(PubKey, crypto:strong_rand_bytes(32)), + PrivateKeys = proplists:get_value(key_shares, Config), + Sid = crypto:strong_rand_bytes(32), InitialStates = [hbbft_cc:init(Sk, Sid, N, F) || Sk <- PrivateKeys], ToCrash = hbbft_test_utils:random_n(F, InitialStates), @@ -142,12 +160,18 @@ too_many_dead_test(Config) -> key_mismatch_f9_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), + Curve = proplists:get_value(curve, Config), Module = proplists:get_value(module, Config), - PubKey = proplists:get_value(pubkey, Config), - Dealer = proplists:get_value(dealer, Config), - PrivateKeys = proplists:get_value(privatekeys, Config), - {ok, {_, PrivateKeys2}} = dealer:deal(Dealer), - Sid = tpke_pubkey:hash_message(PubKey, crypto:strong_rand_bytes(32)), + PrivateKeys = proplists:get_value(key_shares, Config), + PrivateKeys2 = case Curve of + 'BLS12-381' -> + tc_key_share:deal(N, F); + 'SS512' -> + {ok, Dealer} = dealer:new(N, F+1, 'SS512'), + {ok, {_PubKey, KeyShares}} = dealer:deal(Dealer), + KeyShares + end, + Sid = crypto:strong_rand_bytes(32), %% choose 20 from pk1 %% choose 17 from pk2 InitialStates = [hbbft_cc:init(Sk, Sid, N, F) || Sk <- lists:sublist(PrivateKeys, (N-2*F)) ++ lists:sublist(PrivateKeys2, 2*F)], @@ -170,12 +194,18 @@ key_mismatch_f9_test(Config) -> key_mismatch_f10_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config) + 1, + Curve = proplists:get_value(curve, Config), Module = proplists:get_value(module, Config), - PubKey = proplists:get_value(pubkey, Config), - Dealer = proplists:get_value(dealer, Config), - PrivateKeys = proplists:get_value(privatekeys, Config), - {ok, {_, PrivateKeys2}} = dealer:deal(Dealer), - Sid = tpke_pubkey:hash_message(PubKey, crypto:strong_rand_bytes(32)), + PrivateKeys = proplists:get_value(key_shares, Config), + PrivateKeys2 = case Curve of + 'BLS12-381' -> + tc_key_share:deal(N, F); + 'SS512' -> + {ok, Dealer} = dealer:new(N, F+1, 'SS512'), + {ok, {_PubKey, KeyShares}} = dealer:deal(Dealer), + KeyShares + end, + Sid = crypto:strong_rand_bytes(32), InitialStates = [hbbft_cc:init(Sk, Sid, N, F) || Sk <- lists:sublist(PrivateKeys, N-F) ++ lists:sublist(PrivateKeys2, F)], StatesWithId = lists:zip(lists:seq(0, N - 1), InitialStates), %% all valid members should call get_coin @@ -197,13 +227,18 @@ key_mismatch_f10_test(Config) -> mixed_keys_test(Config) -> N = proplists:get_value(n, Config), F = proplists:get_value(f, Config), + Curve = proplists:get_value(curve, Config), Module = proplists:get_value(module, Config), - PubKey = proplists:get_value(pubkey, Config), - Dealer = proplists:get_value(dealer, Config), - PrivateKeys = proplists:get_value(privatekeys, Config), - {ok, {_, PrivateKeys2}} = dealer:deal(Dealer), - - Sid = tpke_pubkey:hash_message(PubKey, crypto:strong_rand_bytes(32)), + PrivateKeys = proplists:get_value(key_shares, Config), + PrivateKeys2 = case Curve of + 'BLS12-381' -> + tc_key_share:deal(N, F); + 'SS512' -> + {ok, Dealer} = dealer:new(N, F+1, 'SS512'), + {ok, {_PubKey, KeyShares}} = dealer:deal(Dealer), + KeyShares + end, + Sid = crypto:strong_rand_bytes(32), InitialState1 = [hbbft_cc:init(Sk, Sid, N, F) || Sk <- PrivateKeys], InitialState2 = [hbbft_cc:init(Sk, Sid, N, F) || Sk <- PrivateKeys2], diff --git a/test/hbbft_distributed_SUITE.erl b/test/hbbft_distributed_SUITE.erl index 6c7fe19..889acee 100644 --- a/test/hbbft_distributed_SUITE.erl +++ b/test/hbbft_distributed_SUITE.erl @@ -4,26 +4,42 @@ -include_lib("kernel/include/inet.hrl"). -export([ - init_per_suite/1, - end_per_suite/1, - init_per_testcase/2, - end_per_testcase/2, - all/0 - ]). + groups/0, + init_per_group/2, + end_per_group/2, + init_per_suite/1, + end_per_suite/1, + init_per_testcase/2, + end_per_testcase/2, + all/0 +]). -export([simple_test/1, serialization_test/1, partition_test/1, partition_and_filter_test/1]). -%% common test callbacks +all() -> + [{group, ss512}, {group, bls12_381}]. -all() -> [simple_test, serialization_test, partition_test, partition_and_filter_test]. +groups() -> + [{ss512, [], test_cases()}, + {bls12_381, [], test_cases()}]. + +init_per_group(ss512, Config) -> + [{curve, 'SS512'} | Config]; +init_per_group(bls12_381, Config) -> + [{curve, 'BLS12-381'} | Config]. + +end_per_group(_, _Config) -> + ok. + +test_cases() -> [simple_test, serialization_test, partition_test, partition_and_filter_test]. init_per_suite(Config) -> - os:cmd(os:find_executable("epmd")++" -daemon"), + os:cmd(os:find_executable("epmd") ++ " -daemon"), {ok, Hostname} = inet:gethostname(), - case net_kernel:start([list_to_atom("runner@"++Hostname), shortnames]) of + case net_kernel:start([list_to_atom("runner@" ++ Hostname), shortnames]) of {ok, _} -> ok; {error, {already_started, _}} -> ok; - {error, {{already_started, _},_}} -> ok + {error, {{already_started, _}, _}} -> ok end, Config. @@ -34,9 +50,12 @@ end_per_suite(Config) -> init_per_testcase(TestCase, Config) -> %% assuming each testcase will work with 4 nodes for now NodeNames = [eric, kenny, kyle, stan], - Nodes = hbbft_ct_utils:pmap(fun(Node) -> - hbbft_ct_utils:start_node(Node, Config, TestCase) - end, NodeNames), + Nodes = hbbft_ct_utils:pmap( + fun(Node) -> + hbbft_ct_utils:start_node(Node, Config, TestCase) + end, + NodeNames + ), _ = [hbbft_ct_utils:connect(Node) || Node <- NodeNames], @@ -52,170 +71,243 @@ end_per_testcase(_TestCase, Config) -> simple_test(Config) -> Nodes = proplists:get_value(nodes, Config), + Curve = proplists:get_value(curve, Config), %% master starts the dealer N = length(Nodes), F = (N div 3), BatchSize = 20, - {ok, Dealer} = dealer:new(N, F+1, 'SS512'), - {ok, {PubKey, PrivateKeys}} = dealer:deal(Dealer), + KeyShares = keyshares(Config), %% each node gets a secret key - NodesSKs = lists:zip(Nodes, PrivateKeys), + NodesSKs = lists:zip(Nodes, KeyShares), %% load hbbft_worker on each node {Mod, Bin, _} = code:get_object_code(hbbft_worker), - _ = hbbft_ct_utils:pmap(fun(Node) -> - rpc:call(Node, erlang, load_module, [Mod, Bin]) - end, Nodes), + _ = hbbft_ct_utils:pmap( + fun(Node) -> + rpc:call(Node, erlang, load_module, [Mod, Bin]) + end, + Nodes + ), %% start a hbbft_worker on each node - Workers = [{Node, rpc:call(Node, hbbft_worker, start_link, [N, F, I, tpke_privkey:serialize(SK), BatchSize, false])} || {I, {Node, SK}} <- enumerate(NodesSKs)], + Workers = [ + {Node, + rpc:call(Node, hbbft_worker, start_link, [ + N, + F, + I, + hbbft_test_utils:serialize_key(Curve, SK), + BatchSize, + false + ])} + || {I, {Node, SK}} <- enumerate(NodesSKs) + ], ok = global:sync(), - [ link(W) || {_, {ok, W}} <- Workers ], + [link(W) || {_, {ok, W}} <- Workers], %% bunch of msgs - Msgs = [ crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N*20)], + Msgs = [crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N * 20)], %% feed the nodes some msgs - lists:foreach(fun(Msg) -> - Destinations = random_n(rand:uniform(N), Workers), - ct:pal("destinations ~p~n", [Destinations]), - [hbbft_worker:submit_transaction(Msg, Destination) || {_Node, {ok, Destination}} <- Destinations] - end, Msgs), + lists:foreach( + fun(Msg) -> + Destinations = random_n(rand:uniform(N), Workers), + ct:pal("destinations ~p~n", [Destinations]), + [ + hbbft_worker:submit_transaction(Msg, Destination) + || {_Node, {ok, Destination}} <- Destinations + ] + end, + Msgs + ), %% wait for all the worker's mailboxes to settle and. %% wait for the chains to converge - ok = hbbft_ct_utils:wait_until(fun() -> - Chains = lists:map(fun({_Node, {ok, W}}) -> - {ok, Blocks} = hbbft_worker:get_blocks(W), - Blocks - end, Workers), - - lists:all(fun(C) -> length(C) > 1 end, Chains) - end, 60*2, 500), - - - AllChains = lists:map(fun({_Node, {ok, W}}) -> - {ok, Blocks} = hbbft_worker:get_blocks(W), - Blocks - end, Workers), + ok = hbbft_ct_utils:wait_until( + fun() -> + Chains = lists:map( + fun({_Node, {ok, W}}) -> + {ok, Blocks} = hbbft_worker:get_blocks(W), + Blocks + end, + Workers + ), + + lists:all(fun(C) -> length(C) > 1 end, Chains) + end, + 60 * 2, + 500 + ), + + AllChains = lists:map( + fun({_Node, {ok, W}}) -> + {ok, Blocks} = hbbft_worker:get_blocks(W), + Blocks + end, + Workers + ), %% find the shortest chain and check all chains have the same common prefix - ShortestChainLen = hd(lists:sort([ length(C) || C <- AllChains ])), + ShortestChainLen = hd(lists:sort([length(C) || C <- AllChains])), - Chains = sets:from_list(lists:map(fun(C) -> lists:reverse(lists:sublist(lists:reverse(C), ShortestChainLen)) end, AllChains)), + Chains = sets:from_list( + lists:map( + fun(C) -> lists:reverse(lists:sublist(lists:reverse(C), ShortestChainLen)) end, + AllChains + ) + ), ct:pal("~p distinct chains~n", [sets:size(Chains)]), %true = (2 > sets:size(Chains)), %true = (2 < length(hd(sets:to_list(Chains)))), - lists:foreach(fun(Chain) -> - %ct:pal("Chain: ~p~n", [Chain]), - ct:pal("chain is of height ~p~n", [length(Chain)]), + lists:foreach( + fun(Chain) -> + %ct:pal("Chain: ~p~n", [Chain]), + ct:pal("chain is of height ~p~n", [length(Chain)]), - %% verify they are cryptographically linked, - true = hbbft_worker:verify_chain(Chain, PubKey), + %% verify they are cryptographically linked, + true = hbbft_worker:verify_chain(Chain, hd(KeyShares)), - %% check all transactions are unique - BlockTxns = lists:flatten([ hbbft_worker:block_transactions(B) || B <- Chain ]), - true = length(BlockTxns) == sets:size(sets:from_list(BlockTxns)), + %% check all transactions are unique + BlockTxns = lists:flatten([hbbft_worker:block_transactions(B) || B <- Chain]), + true = length(BlockTxns) == sets:size(sets:from_list(BlockTxns)), - %% check they're all members of the original message list - true = sets:is_subset(sets:from_list(BlockTxns), sets:from_list(Msgs)), - ct:pal("chain contains ~p distinct transactions~n", [length(BlockTxns)]) - end, sets:to_list(Chains)), + %% check they're all members of the original message list + true = sets:is_subset(sets:from_list(BlockTxns), sets:from_list(Msgs)), + ct:pal("chain contains ~p distinct transactions~n", [length(BlockTxns)]) + end, + sets:to_list(Chains) + ), %% check we actually converged and made a chain true = (1 == sets:size(Chains)), true = (0 < length(hd(sets:to_list(Chains)))), - [ unlink(W) || {_, {ok, W}} <- Workers ], + [unlink(W) || {_, {ok, W}} <- Workers], ok. serialization_test(Config) -> Nodes = proplists:get_value(nodes, Config), + Curve = proplists:get_value(curve, Config), %% master starts the dealer N = length(Nodes), F = (N div 3), BatchSize = 20, - {ok, Dealer} = dealer:new(N, F+1, 'SS512'), - {ok, {PubKey, PrivateKeys}} = dealer:deal(Dealer), + KeyShares = keyshares(Config), %% each node gets a secret key - NodesSKs = lists:zip(Nodes, PrivateKeys), + NodesSKs = lists:zip(Nodes, KeyShares), %% load hbbft_worker on each node {Mod, Bin, _} = code:get_object_code(hbbft_worker), - _ = hbbft_ct_utils:pmap(fun(Node) -> - rpc:call(Node, erlang, load_module, [Mod, Bin]) - end, Nodes), + _ = hbbft_ct_utils:pmap( + fun(Node) -> + rpc:call(Node, erlang, load_module, [Mod, Bin]) + end, + Nodes + ), %% start a hbbft_worker on each node - Workers = [{Node, rpc:call(Node, hbbft_worker, start_link, [N, F, I, tpke_privkey:serialize(SK), BatchSize, false])} || {I, {Node, SK}} <- enumerate(NodesSKs)], + Workers = [ + {Node, + rpc:call(Node, hbbft_worker, start_link, [ + N, + F, + I, + hbbft_test_utils:serialize_key(Curve, SK), + BatchSize, + false + ])} + || {I, {Node, SK}} <- enumerate(NodesSKs) + ], ok = global:sync(), - [ link(W) || {_, {ok, W}} <- Workers ], + [link(W) || {_, {ok, W}} <- Workers], %% bunch of msgs - Msgs = [ crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N*20)], + Msgs = [crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N * 20)], %% feed the nodes some msgs - lists:foreach(fun(Msg) -> - Destinations = random_n(rand:uniform(N), Workers), - ct:pal("destinations ~p~n", [Destinations]), - [hbbft_worker:submit_transaction(Msg, Destination) || {_Node, {ok, Destination}} <- Destinations] - end, Msgs), + lists:foreach( + fun(Msg) -> + Destinations = random_n(rand:uniform(N), Workers), + ct:pal("destinations ~p~n", [Destinations]), + [ + hbbft_worker:submit_transaction(Msg, Destination) + || {_Node, {ok, Destination}} <- Destinations + ] + end, + Msgs + ), %% wait for all the worker's mailboxes to settle and. %% wait for the chains to converge ok = hbbft_ct_utils:wait_until( - fun() -> - Chains = lists:map(fun({_Node, {ok, W}}) -> - {ok, Blocks} = hbbft_worker:get_blocks(W), - Blocks - end, Workers), - lists:all(fun(C) -> length(C) > 1 end, Chains) - end, 60*2, 500), - - - AllChains = lists:map(fun({_Node, {ok, W}}) -> - {ok, Blocks} = hbbft_worker:get_blocks(W), - Blocks - end, Workers), + fun() -> + Chains = lists:map( + fun({_Node, {ok, W}}) -> + {ok, Blocks} = hbbft_worker:get_blocks(W), + Blocks + end, + Workers + ), + lists:all(fun(C) -> length(C) > 1 end, Chains) + end, + 60 * 2, + 500 + ), + + AllChains = lists:map( + fun({_Node, {ok, W}}) -> + {ok, Blocks} = hbbft_worker:get_blocks(W), + Blocks + end, + Workers + ), %% find the shortest chain and check all chains have the same common prefix - ShortestChainLen = hd(lists:sort([ length(C) || C <- AllChains ])), + ShortestChainLen = hd(lists:sort([length(C) || C <- AllChains])), - Chains = sets:from_list(lists:map(fun(C) -> lists:reverse(lists:sublist(lists:reverse(C), ShortestChainLen)) end, AllChains)), + Chains = sets:from_list( + lists:map( + fun(C) -> lists:reverse(lists:sublist(lists:reverse(C), ShortestChainLen)) end, + AllChains + ) + ), ct:pal("~p distinct chains~n", [sets:size(Chains)]), %true = (2 > sets:size(Chains)), %true = (2 < length(hd(sets:to_list(Chains)))), - lists:foreach(fun(Chain) -> - %ct:pal("Chain: ~p~n", [Chain]), - ct:pal("chain is of height ~p~n", [length(Chain)]), + lists:foreach( + fun(Chain) -> + %ct:pal("Chain: ~p~n", [Chain]), + ct:pal("chain is of height ~p~n", [length(Chain)]), - %% verify they are cryptographically linked, - true = hbbft_worker:verify_chain(Chain, PubKey), + %% verify they are cryptographically linked, + true = hbbft_worker:verify_chain(Chain, hd(KeyShares)), - %% check all transactions are unique - BlockTxns = lists:flatten([ hbbft_worker:block_transactions(B) || B <- Chain ]), - true = length(BlockTxns) == sets:size(sets:from_list(BlockTxns)), + %% check all transactions are unique + BlockTxns = lists:flatten([hbbft_worker:block_transactions(B) || B <- Chain]), + true = length(BlockTxns) == sets:size(sets:from_list(BlockTxns)), - %% check they're all members of the original message list - true = sets:is_subset(sets:from_list(BlockTxns), sets:from_list(Msgs)), - ct:pal("chain contains ~p distinct transactions~n", [length(BlockTxns)]) - end, sets:to_list(Chains)), + %% check they're all members of the original message list + true = sets:is_subset(sets:from_list(BlockTxns), sets:from_list(Msgs)), + ct:pal("chain contains ~p distinct transactions~n", [length(BlockTxns)]) + end, + sets:to_list(Chains) + ), %% check we actually converged and made a chain true = (1 == sets:size(Chains)), true = (0 < length(hd(sets:to_list(Chains)))), - [ unlink(W) || {_, {ok, W}} <- Workers ], + [unlink(W) || {_, {ok, W}} <- Workers], ok. %% partition the first node from f other nodes @@ -229,113 +321,168 @@ partition_and_filter_test(Config) -> partition_test_(Config, Filter) -> Nodes = proplists:get_value(nodes, Config), + Curve = proplists:get_value(curve, Config), %% master starts the dealer N = length(Nodes), F = (N div 3), ct:pal("N is ~p, F is ~p", [N, F]), BatchSize = 20, - {ok, Dealer} = dealer:new(N, F+1, 'SS512'), - {ok, {PubKey, PrivateKeys}} = dealer:deal(Dealer), + KeyShares = keyshares(Config), %% each node gets a secret key - NodesSKs = lists:zip(Nodes, PrivateKeys), + NodesSKs = lists:zip(Nodes, KeyShares), %% load hbbft_worker on each node {Mod, Bin, _} = code:get_object_code(hbbft_worker), - _ = hbbft_ct_utils:pmap(fun(Node) -> - rpc:call(Node, erlang, load_module, [Mod, Bin]) - end, Nodes), + _ = hbbft_ct_utils:pmap( + fun(Node) -> + rpc:call(Node, erlang, load_module, [Mod, Bin]) + end, + Nodes + ), %% partition the first node from f other nodes FirstNode = hd(Nodes), PartitionedNodes = lists:sublist(Nodes, 2, F), - [ true = ct_rpc:call(FirstNode, erlang, set_cookie, [PartitionedNode, canttouchthis]) || PartitionedNode <- PartitionedNodes ], - [ ct_rpc:call(FirstNode, erlang, disconnect_node, [PartitionedNode]) || PartitionedNode <- PartitionedNodes ], - true = lists:all(fun(E) -> E == pang end, [ ct_rpc:call(FirstNode, net_adm, ping, [PartitionedNode]) || PartitionedNode <- PartitionedNodes]), - true = lists:all(fun(E) -> E == pang end, [ ct_rpc:call(PartitionedNode, net_adm, ping, [FirstNode]) || PartitionedNode <- PartitionedNodes]), + [ + true = ct_rpc:call(FirstNode, erlang, set_cookie, [PartitionedNode, canttouchthis]) + || PartitionedNode <- PartitionedNodes + ], + [ + ct_rpc:call(FirstNode, erlang, disconnect_node, [PartitionedNode]) + || PartitionedNode <- PartitionedNodes + ], + true = lists:all(fun(E) -> E == pang end, [ + ct_rpc:call(FirstNode, net_adm, ping, [PartitionedNode]) + || PartitionedNode <- PartitionedNodes + ]), + true = lists:all(fun(E) -> E == pang end, [ + ct_rpc:call(PartitionedNode, net_adm, ping, [FirstNode]) + || PartitionedNode <- PartitionedNodes + ]), ct:pal("Partitioning ~p from ~p", [FirstNode, PartitionedNodes]), OtherNodes = Nodes -- [FirstNode | PartitionedNodes], %% start a hbbft_worker on each node - Workers = [{Node, rpc:call(Node, hbbft_worker, start_link, [N, F, I, tpke_privkey:serialize(SK), BatchSize, false])} || {I, {Node, SK}} <- enumerate(NodesSKs)], + Workers = [ + {Node, + rpc:call(Node, hbbft_worker, start_link, [ + N, + F, + I, + hbbft_test_utils:serialize_key(Curve, SK), + BatchSize, + false + ])} + || {I, {Node, SK}} <- enumerate(NodesSKs) + ], ok = global:sync(), case Filter of undefined -> ok; _ -> - [ hbbft_worker:set_filter(Filter, Worker) || {Node, {ok, Worker}} <- Workers, lists:member(Node, OtherNodes) ] + [ + hbbft_worker:set_filter(Filter, Worker) + || {Node, {ok, Worker}} <- Workers, lists:member(Node, OtherNodes) + ] end, - [ link(W) || {_, {ok, W}} <- Workers ], + [link(W) || {_, {ok, W}} <- Workers], %% bunch of msgs - Msgs = [ crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N*20)], + Msgs = [crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N * 20)], %% feed the nodes some msgs - lists:foreach(fun(Msg) -> - Destinations = random_n(rand:uniform(N), Workers), - ct:pal("destinations ~p~n", [Destinations]), - [hbbft_worker:submit_transaction(Msg, Destination) || {_Node, {ok, Destination}} <- Destinations] - end, Msgs), + lists:foreach( + fun(Msg) -> + Destinations = random_n(rand:uniform(N), Workers), + ct:pal("destinations ~p~n", [Destinations]), + [ + hbbft_worker:submit_transaction(Msg, Destination) + || {_Node, {ok, Destination}} <- Destinations + ] + end, + Msgs + ), %% wait for all the worker's mailboxes to settle and. %% wait for the chains to converge - Done = hbbft_ct_utils:wait_until(fun() -> - Chains = lists:map(fun({_Node, {ok, W}}) -> - {ok, Blocks} = hbbft_worker:get_blocks(W), - Blocks - end, Workers), - - %% check we actually converged and made a chain - lists:all(fun(C) -> length(C) > 1 end, Chains) - end, 60*2, 500), - - AllChains = lists:map(fun({_Node, {ok, W}}) -> - {ok, Blocks} = hbbft_worker:get_blocks(W), - Blocks - end, Workers), + Done = hbbft_ct_utils:wait_until( + fun() -> + Chains = lists:map( + fun({_Node, {ok, W}}) -> + {ok, Blocks} = hbbft_worker:get_blocks(W), + Blocks + end, + Workers + ), + + %% check we actually converged and made a chain + lists:all(fun(C) -> length(C) > 1 end, Chains) + end, + 60 * 2, + 500 + ), + + AllChains = lists:map( + fun({_Node, {ok, W}}) -> + {ok, Blocks} = hbbft_worker:get_blocks(W), + Blocks + end, + Workers + ), %% find the shortest chain and check all chains have the same common prefix - ShortestChainLen = hd(lists:sort([ length(C) || C <- AllChains ])), + ShortestChainLen = hd(lists:sort([length(C) || C <- AllChains])), - Chains = sets:from_list(lists:map(fun(C) -> lists:reverse(lists:sublist(lists:reverse(C), ShortestChainLen)) end, AllChains)), + Chains = sets:from_list( + lists:map( + fun(C) -> lists:reverse(lists:sublist(lists:reverse(C), ShortestChainLen)) end, + AllChains + ) + ), ct:pal("~p distinct chains~n", [sets:size(Chains)]), ct:pal("chain lengths ~p ~n", [[length(C) || C <- sets:to_list(Chains)]]), case Done of - ok -> ok; + ok -> + ok; _ -> - lists:foreach(fun({_Node, {ok, W}}) -> - Status = hbbft_worker:status(W), - ct:pal("Status for ~p is ~p", [_Node, Status]) - end, Workers), + lists:foreach( + fun({_Node, {ok, W}}) -> + Status = hbbft_worker:status(W), + ct:pal("Status for ~p is ~p", [_Node, Status]) + end, + Workers + ), ct:fail("error") end, - lists:foreach(fun(Chain) -> - %ct:pal("Chain: ~p~n", [Chain]), - ct:pal("chain is of height ~p~n", [length(Chain)]), + lists:foreach( + fun(Chain) -> + %ct:pal("Chain: ~p~n", [Chain]), + ct:pal("chain is of height ~p~n", [length(Chain)]), - %% verify they are cryptographically linked, - true = hbbft_worker:verify_chain(Chain, PubKey), + %% verify they are cryptographically linked, + true = hbbft_worker:verify_chain(Chain, hd(KeyShares)), - %% check all transactions are unique - BlockTxns = lists:flatten([ hbbft_worker:block_transactions(B) || B <- Chain ]), - true = length(BlockTxns) == sets:size(sets:from_list(BlockTxns)), + %% check all transactions are unique + BlockTxns = lists:flatten([hbbft_worker:block_transactions(B) || B <- Chain]), + true = length(BlockTxns) == sets:size(sets:from_list(BlockTxns)), - %% check they're all members of the original message list - true = sets:is_subset(sets:from_list(BlockTxns), sets:from_list(Msgs)), - ct:pal("chain contains ~p distinct transactions~n", [length(BlockTxns)]) - end, sets:to_list(Chains)), + %% check they're all members of the original message list + true = sets:is_subset(sets:from_list(BlockTxns), sets:from_list(Msgs)), + ct:pal("chain contains ~p distinct transactions~n", [length(BlockTxns)]) + end, + sets:to_list(Chains) + ), - [ unlink(W) || {_, {ok, W}} <- Workers ], + [unlink(W) || {_, {ok, W}} <- Workers], ok. - - %% helpers enumerate(List) -> lists:zip(lists:seq(0, length(List) - 1), List). @@ -344,4 +491,17 @@ random_n(N, List) -> lists:sublist(shuffle(List), N). shuffle(List) -> - [X || {_,X} <- lists:sort([{rand:uniform(), N} || N <- List])]. + [X || {_, X} <- lists:sort([{rand:uniform(), N} || N <- List])]. + +keyshares(Config) -> + Nodes = proplists:get_value(nodes, Config), + N = length(Nodes), + F = (N div 3), + case proplists:get_value(curve, Config, 'BLS12-381') of + 'BLS12-381' -> + KeyShares = tc_key_share:deal(N, F); + 'SS512' -> + {ok, Dealer} = dealer:new(N, F+1, 'SS512'), + {ok, {_PubKey, KeyShares}} = dealer:deal(Dealer) + end, + KeyShares. diff --git a/test/hbbft_handler.erl b/test/hbbft_handler.erl index 8f80834..85cd4ce 100644 --- a/test/hbbft_handler.erl +++ b/test/hbbft_handler.erl @@ -14,12 +14,12 @@ -record(state, { hbbft :: hbbft:hbbft(), - sk :: tpke_privkey:privkey(), - ssk :: tpke_privkey:privkey_serialized() + sk :: tc_key_share:tc_key_share(), + ssk :: binary() }). init(HBBFTArgs) -> - SK = tpke_privkey:deserialize(proplists:get_value(sk, HBBFTArgs)), + SK = tc_key_share:deserialize(proplists:get_value(sk, HBBFTArgs)), N = proplists:get_value(n, HBBFTArgs), F = proplists:get_value(f, HBBFTArgs), ID = proplists:get_value(id, HBBFTArgs), @@ -73,7 +73,7 @@ handle_message(Msg, Actor, State) -> self() ! {transactions, Txns}, {State#state{hbbft=HBBFT}, []}; {HBBFT, {result, {signature, Sig}}} -> - self() ! {signature, Sig, tpke_privkey:public_key(State#state.sk)}, + self() ! {signature, Sig}, {State#state{hbbft=HBBFT}, []} end. @@ -86,7 +86,7 @@ serialize(State) -> deserialize(Binary) -> State = binary_to_term(Binary), - SK = tpke_privkey:deserialize(State#state.ssk), + SK = tc_key_share:deserialize(State#state.ssk), HBBFT = hbbft:deserialize(State#state.hbbft, SK), #state{hbbft=HBBFT}. diff --git a/test/hbbft_mr_fake_SUITE.erl b/test/hbbft_mr_fake_SUITE.erl index 7dd3f80..521634f 100644 --- a/test/hbbft_mr_fake_SUITE.erl +++ b/test/hbbft_mr_fake_SUITE.erl @@ -19,8 +19,8 @@ init_per_testcase(_, Config) -> F = 1, Module = hbbft, BatchSize = 10, - {ok, Dealer} = dealer:new(N, F+1, 'SS512'), - {ok, {PubKey, PrivateKeys}} = dealer:deal(Dealer), + PrivateKeys = tc_key_share:deal(N, F), + PubKey = tc_key_share:public_key(hd(PrivateKeys)), [{n, N}, {f, F}, {batchsize, BatchSize}, {module, Module}, {pubkey, PubKey}, {privatekeys, PrivateKeys} | Config]. end_per_testcase(_, _Config) -> @@ -95,12 +95,12 @@ mr(_Message, _From, _To, _State, NewState, all_msgs = Msgs, txns = StateTxns} = ModelState) -> %% !!!!!! note that this is fragile and may break if the record changes - Round = element(7, NewState), + Round = element(8, NewState), %% finalize the round for this node NewNewState = hbbft:finalize_round(NewState, Txns), {NewNewNewState, Actions} = hbbft:next_round(NewNewState), - fakecast:trace("buffer remaining ~p", [length(element(8, NewNewNewState))]), + fakecast:trace("buffer remaining ~p", [length(element(9, NewNewNewState))]), %% check if all messages have been put into the queue, and if all %% messages have appeared as transactions. diff --git a/test/hbbft_relcast_SUITE.erl b/test/hbbft_relcast_SUITE.erl index ce9bda3..82191f4 100644 --- a/test/hbbft_relcast_SUITE.erl +++ b/test/hbbft_relcast_SUITE.erl @@ -10,7 +10,6 @@ two_actors_no_txns_test/1, one_actor_missing_test/1, two_actors_missing_test/1, - encrypt_decrypt_test/1, start_on_demand_test/1 ]). @@ -21,7 +20,6 @@ all() -> two_actors_no_txns_test, one_actor_missing_test, two_actors_missing_test, - encrypt_decrypt_test, start_on_demand_test ]. @@ -30,9 +28,8 @@ init_per_testcase(TestCase, Config) -> F = N div 4, Module = hbbft, BatchSize = 20, - {ok, Dealer} = dealer:new(N, F+1, 'SS512'), - {ok, {PubKey, PrivateKeys}} = dealer:deal(Dealer), - + PrivateKeys = tc_key_share:deal(N, F), + PubKey = tc_key_share:public_key(hd(PrivateKeys)), [{n, N}, {f, F}, {batchsize, BatchSize}, @@ -56,7 +53,7 @@ init_test(Config) -> Workers = lists:foldl(fun({I, SK}, Acc) -> {ok, W} = hbbft_relcast_worker:start_link([ {id, I}, - {sk, tpke_privkey:serialize(SK)}, + {sk, tc_key_share:serialize(SK)}, {n, N}, {f, F}, {data_dir, DataDir}, @@ -229,17 +226,6 @@ two_actors_missing_test(Config) -> ?assertEqual(0, sets:size(ConvergedResults)), ok. -encrypt_decrypt_test(Config) -> - PubKey = proplists:get_value(pubkey, Config), - PrivateKeys = proplists:get_value(privatekeys, Config), - - PlainText = crypto:strong_rand_bytes(24), - Enc = hbbft:encrypt(PubKey, PlainText), - {ok, EncKey} = hbbft:get_encrypted_key(hd(PrivateKeys), Enc), - DecKey = tpke_pubkey:combine_shares(PubKey, EncKey, [ tpke_privkey:decrypt_share(SK, EncKey) || SK <- PrivateKeys]), - ?assertEqual(PlainText, hbbft:decrypt(DecKey, Enc)), - ok. - start_on_demand_test(Config) -> PubKey = proplists:get_value(pubkey, Config), N = proplists:get_value(n, Config), @@ -252,7 +238,7 @@ start_on_demand_test(Config) -> Workers = lists:foldl(fun({I, SK}, Acc) -> {ok, W} = hbbft_relcast_worker:start_link([ {id, I}, - {sk, tpke_privkey:serialize(SK)}, + {sk, tc_key_share:serialize(SK)}, {n, N}, {f, F}, {data_dir, DataDir}, diff --git a/test/hbbft_relcast_distributed_SUITE.erl b/test/hbbft_relcast_distributed_SUITE.erl index 758cbb1..43751d2 100644 --- a/test/hbbft_relcast_distributed_SUITE.erl +++ b/test/hbbft_relcast_distributed_SUITE.erl @@ -58,8 +58,7 @@ simple_test(Config) -> N = length(Nodes), F = (N div 3), BatchSize = 20, - {ok, Dealer} = dealer:new(N, F+1, 'SS512'), - {ok, {PubKey, PrivateKeys}} = dealer:deal(Dealer), + PrivateKeys = tc_key_share:deal(N, F), %% each node gets a secret key NodesSKs = lists:zip(Nodes, PrivateKeys), @@ -75,7 +74,7 @@ simple_test(Config) -> Workers = [{Node, ct_rpc:call(Node, hbbft_relcast_worker, start_link, - [[{id, I}, {sk, tpke_privkey:serialize(SK)}, {n, N}, {f, F}, {batchsize, BatchSize}, {data_dir, DataDir}]] + [[{id, I}, {sk, tc_key_share:serialize(SK)}, {n, N}, {f, F}, {batchsize, BatchSize}, {data_dir, DataDir}]] )} || {I, {Node, SK}} <- hbbft_test_utils:enumerate(NodesSKs)], ok = global:sync(), @@ -118,7 +117,7 @@ simple_test(Config) -> ct:pal("chain is of height ~p~n", [length(Chain)]), %% verify they are cryptographically linked, - true = hbbft_relcast_worker:verify_chain(Chain, PubKey), + true = hbbft_relcast_worker:verify_chain(Chain, tc_key_share:public_key(hd(PrivateKeys))), %% check all transactions are unique BlockTxns = lists:flatten([ hbbft_relcast_worker:block_transactions(B) || B <- Chain ]), @@ -154,8 +153,7 @@ partition_test_(Config, Filter) -> N = length(Nodes), F = (N div 3), BatchSize = 20, - {ok, Dealer} = dealer:new(N, F+1, 'SS512'), - {ok, {PubKey, PrivateKeys}} = dealer:deal(Dealer), + PrivateKeys = tc_key_share:deal(N, F), %% each node gets a secret key NodesSKs = lists:zip(Nodes, PrivateKeys), @@ -180,7 +178,7 @@ partition_test_(Config, Filter) -> Workers = [{Node, ct_rpc:call(Node, hbbft_relcast_worker, start_link, - [[{id, I}, {sk, tpke_privkey:serialize(SK)}, {n, N}, {f, F}, {batchsize, BatchSize}, {data_dir, DataDir}]] + [[{id, I}, {sk, tc_key_share:serialize(SK)}, {n, N}, {f, F}, {batchsize, BatchSize}, {data_dir, DataDir}]] )} || {I, {Node, SK}} <- hbbft_test_utils:enumerate(NodesSKs)], ok = global:sync(), @@ -250,7 +248,7 @@ partition_test_(Config, Filter) -> ct:pal("chain is of height ~p~n", [length(Chain)]), %% verify they are cryptographically linked, - true = hbbft_relcast_worker:verify_chain(Chain, PubKey), + true = hbbft_relcast_worker:verify_chain(Chain, tc_key_share:public_key(hd(PrivateKeys))), %% check all transactions are unique BlockTxns = lists:flatten([ hbbft_relcast_worker:block_transactions(B) || B <- Chain ]), diff --git a/test/hbbft_relcast_worker.erl b/test/hbbft_relcast_worker.erl index 0c7fdf4..dc5465e 100644 --- a/test/hbbft_relcast_worker.erl +++ b/test/hbbft_relcast_worker.erl @@ -18,6 +18,7 @@ -record(state, { relcast :: term(), id :: integer(), + sk :: tc_key_share:tc_key_share(), peers :: map(), tempblock = undefined :: block(), blocks = [] :: [block()], @@ -58,6 +59,7 @@ bba_filter(ID) -> init(Args) -> N = proplists:get_value(n, Args), ID = proplists:get_value(id, Args), + SK = proplists:get_value(sk, Args), DataDir = proplists:get_value(data_dir, Args), Members = lists:seq(1, N), erlang:send_after(1500, self(), inbound_tick), @@ -65,7 +67,7 @@ init(Args) -> [{create, true}, {data_dir, DataDir ++ integer_to_list(ID)}]), Peers = maps:from_list([{I, undefined} || I <- Members, I /= ID ]), - {ok, do_send(#state{relcast=Relcast, id=ID, peers=Peers})}. + {ok, do_send(#state{relcast=Relcast, id=ID, peers=Peers, sk=tc_key_share:deserialize(SK)})}. handle_call({submit_txn, Txn}, _From, State=#state{relcast=Relcast0}) -> {Resp, Relcast} = relcast:command({txn, Txn}, Relcast0), @@ -100,11 +102,10 @@ handle_cast({ack, Sender, Seq}, State) -> %% ct:pal("ack, Sender: ~p", [Sender]), {ok, NewRelcast} = relcast:ack(Sender, Seq, State#state.relcast), {noreply, do_send(State#state{relcast=NewRelcast})}; -handle_cast({block, Block, SerializedPubKey}, State) -> +handle_cast({block, Block}, State) -> case lists:member(Block, State#state.blocks) of false -> - PubKey = tpke_pubkey:deserialize(SerializedPubKey), - case verify_block_fit([Block | State#state.blocks], PubKey) of + case verify_block_fit([Block | State#state.blocks], tc_key_share:public_key(State#state.sk)) of true -> {ok, Relcast} = relcast:command(next_round, State#state.relcast), {noreply, do_send(State#state{relcast=Relcast, @@ -141,15 +142,15 @@ handle_info({transactions, Txns}, State) -> NewBlock = new_block(UniqueTxns, State), {ok, Relcast} = relcast:command({finalize_round, Txns, term_to_binary(NewBlock)}, State#state.relcast), {noreply, do_send(State#state{relcast=Relcast, tempblock=NewBlock})}; -handle_info({signature, Sig, Pubkey}, State=#state{tempblock=TempBlock, peers=Peers}) when TempBlock /= undefined -> +handle_info({signature, Sig}, State=#state{tempblock=TempBlock, peers=Peers}) when TempBlock /= undefined -> %% ct:pal("Got signature: ~p", [Sig]), %% ct:pal("TempBlock: ~p", [TempBlock]), NewBlock = TempBlock#block{signature=Sig}, case lists:member(NewBlock, State#state.blocks) of false -> - case verify_block_fit([NewBlock | State#state.blocks], Pubkey) of + case verify_block_fit([NewBlock | State#state.blocks], tc_key_share:public_key(State#state.sk)) of true -> - _ = [gen_server:cast({global, name(I)}, {block, NewBlock, tpke_pubkey:serialize(Pubkey)}) || {I, _} <- maps:to_list(Peers)], + _ = [gen_server:cast({global, name(I)}, {block, NewBlock}) || {I, _} <- maps:to_list(Peers)], {ok, Relcast} = relcast:command(next_round, State#state.relcast), {noreply, do_send(State#state{relcast=Relcast, tempblock=undefined, @@ -211,9 +212,9 @@ verify_block_fit([A, B | _], PubKey) -> case A#block.prev_hash == hash_block(B) of true -> %% A should have a valid signature - HM = tpke_pubkey:hash_message(PubKey, term_to_binary(A#block{signature= <<>>})), - Signature = tpke_pubkey:deserialize_element(PubKey, A#block.signature), - case tpke_pubkey:verify_signature(PubKey, Signature, HM) of + Msg = term_to_binary(A#block{signature= <<>>}), + Signature = tc_signature:deserialize(A#block.signature), + case tc_pubkey:verify(PubKey, Signature, Msg) of true -> true; false -> @@ -233,9 +234,9 @@ verify_chain([G], PubKey) -> case G#block.prev_hash == <<>> of true -> %% genesis block should have a valid signature - HM = tpke_pubkey:hash_message(PubKey, term_to_binary(G#block{signature= <<>>})), - Signature = tpke_pubkey:deserialize_element(PubKey, G#block.signature), - tpke_pubkey:verify_signature(PubKey, Signature, HM); + Msg = term_to_binary(G#block{signature= <<>>}), + Signature = tc_signature:deserialize(G#block.signature), + tc_pubkey:verify(PubKey, Signature, Msg); false -> ct:log("no genesis block~n"), false diff --git a/test/hbbft_test_utils.erl b/test/hbbft_test_utils.erl index b60a1a0..a30aeb0 100644 --- a/test/hbbft_test_utils.erl +++ b/test/hbbft_test_utils.erl @@ -1,6 +1,22 @@ -module(hbbft_test_utils). --export([do_send_outer/4, shuffle/1, random_n/2, enumerate/1, merge_replies/3]). +-export([serialize_key/2, deserialize_key/1, do_send_outer/4, shuffle/1, random_n/2, enumerate/1, merge_replies/3]). + +serialize_key(Curve, SK) -> + case Curve of + 'BLS12-381' -> + {Curve, tc_key_share:serialize(SK)}; + 'SS512' -> + {Curve, tpke_privkey:serialize(SK)} + end. + +deserialize_key({Curve, SerKey}) -> + case Curve of + 'BLS12-381' -> + tc_key_share:deserialize(SerKey); + 'SS512' -> + tpke_privkey:deserialize(SerKey) + end. % TODO Type of Acc elements % TODO Type of States elements diff --git a/test/hbbft_worker.erl b/test/hbbft_worker.erl index 75969bf..3d12d97 100644 --- a/test/hbbft_worker.erl +++ b/test/hbbft_worker.erl @@ -20,8 +20,8 @@ hbbft :: hbbft:hbbft_data(), blocks :: [#block{}], tempblock :: undefined | #block{}, - sk :: tpke_privkey:privkey(), - ssk :: tpke_privkey:privkey_serialized(), + sk :: tc_key_share:tc_key_share(), + ssk :: binary(), to_serialize = false :: boolean(), filter = fun(_ID, _Msg) -> true end :: fun((any()) -> boolean()) }). @@ -53,37 +53,33 @@ bba_filter(ID) -> verify_chain([], _) -> true; -verify_chain([G], PubKey) -> +verify_chain([G], KeyShare) -> ct:log("verifying genesis block~n"), %% genesis block has no prev hash case G#block.prev_hash == <<>> of true -> %% genesis block should have a valid signature - HM = tpke_pubkey:hash_message(PubKey, term_to_binary(G#block{signature= <<>>})), - Signature = tpke_pubkey:deserialize_element(PubKey, G#block.signature), - tpke_pubkey:verify_signature(PubKey, Signature, HM); + verify_block_signature(KeyShare, G); false -> ct:log("no genesis block~n"), false end; -verify_chain(Chain, PubKey) -> +verify_chain(Chain, KeyShare) -> ct:log("Chain verification depth ~p~n", [length(Chain)]), - case verify_block_fit(Chain, PubKey) of - true -> verify_chain(tl(Chain), PubKey); + case verify_block_fit(Chain, KeyShare) of + true -> verify_chain(tl(Chain), KeyShare); false -> ct:log("bad signature~n"), false end. verify_block_fit([B], _) when B#block.prev_hash == <<>> -> true; -verify_block_fit([A, B | _], PubKey) -> +verify_block_fit([A, B | _], KeyShare) -> %% A should have the the prev_hash of B case A#block.prev_hash == hash_block(B) of true -> %% A should have a valid signature - HM = tpke_pubkey:hash_message(PubKey, term_to_binary(A#block{signature= <<>>})), - Signature = tpke_pubkey:deserialize_element(PubKey, A#block.signature), - case tpke_pubkey:verify_signature(PubKey, Signature, HM) of + case verify_block_signature(KeyShare, A) of true -> true; false -> @@ -95,12 +91,25 @@ verify_block_fit([A, B | _], PubKey) -> false end. +verify_block_signature(KeyShare, A) -> + case tc_key_share:is_key_share(KeyShare) of + true -> + Signature = tc_signature:deserialize(A#block.signature), + tc_key_share:verify(KeyShare, Signature, term_to_binary(A#block{signature= <<>>})); + false -> + PubKey = tpke_privkey:public_key(KeyShare), + HM = tpke_pubkey:hash_message(PubKey, term_to_binary(A#block{signature= <<>>})), + Signature = tpke_pubkey:deserialize_element(PubKey, A#block.signature), + tpke_pubkey:verify_signature(PubKey, Signature, HM) + end. + + block_transactions(Block) -> Block#block.transactions. init([N, F, ID, SK, BatchSize, ToSerialize]) -> %% deserialize the secret key once - DSK = tpke_privkey:deserialize(SK), + DSK = hbbft_test_utils:deserialize_key(SK), %% init hbbft HBBFT = hbbft:init(DSK, N, F, ID, BatchSize, infinity), %% store the serialized state and serialized SK @@ -141,7 +150,7 @@ handle_cast({block, NewBlock}, State=#state{sk=SK, hbbft=HBBFT}) -> false -> ct:log("XXXXXXXX~n"), %% a new block, check if it fits on our chain - case verify_block_fit([NewBlock|State#state.blocks], tpke_privkey:public_key(SK)) of + case verify_block_fit([NewBlock|State#state.blocks], SK) of true -> %% advance to the next round ct:log("~p skipping to next round~n", [self()]), @@ -168,6 +177,7 @@ dispatch({NewHBBFT, {send, ToSend}}, State) -> do_send(ToSend, State), State#state{hbbft=maybe_serialize_HBBFT(NewHBBFT, State#state.to_serialize)}; dispatch({NewHBBFT, {result, {transactions, _, Txns}}}, State) -> + %% ct:pal("got transactions ~p", [Txns]), NewBlock = case State#state.blocks of [] -> %% genesis block