From dcf5a960ef1c5bb70fe02a5b405341fffcf10e23 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Sun, 4 Apr 2021 12:08:22 -0400 Subject: [PATCH 01/14] Support M-of-N multisig --- Makefile | 4 +- rebar.config | 1 + rebar.lock | 4 + src/libp2p_crypto.app.src | 1 + src/libp2p_crypto.erl | 544 +++++++++++++++++++++++++++++++++++++- 5 files changed, 545 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index deefcaa..e8633f0 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ cover: $(REBAR) cover test: - $(REBAR) as test do eunit + $(REBAR) as test do eunit, cover ci: $(REBAR) as test do eunit,cover && $(REBAR) do xref, dialyzer @@ -33,7 +33,7 @@ ci: codecov --required -f _build/test/covertool/libp2p_crypto.covertool.xml typecheck: - $(REBAR) dialyzer + $(REBAR) do dialyzer, xref doc: $(REBAR) edoc diff --git a/rebar.config b/rebar.config index c0ad3bc..20fdfba 100644 --- a/rebar.config +++ b/rebar.config @@ -12,6 +12,7 @@ ]}. {deps, [ + {multihash, "1.1.0"}, {ecc_compact, "1.0.5"}, {enacl, "1.1.1"}, {erl_base58, "0.0.1"}, diff --git a/rebar.lock b/rebar.lock index cd14cae..12992dc 100644 --- a/rebar.lock +++ b/rebar.lock @@ -3,7 +3,9 @@ {<<"ecc_compact">>,{pkg,<<"ecc_compact">>,<<"1.0.5">>},0}, {<<"enacl">>,{pkg,<<"enacl">>,<<"1.1.1">>},0}, {<<"erl_base58">>,{pkg,<<"erl_base58">>,<<"0.0.1">>},0}, + {<<"keccakf1600">>,{pkg,<<"keccakf1600">>,<<"2.0.0">>},1}, {<<"multiaddr">>,{pkg,<<"multiaddr">>,<<"1.1.3">>},0}, + {<<"multihash">>,{pkg,<<"multihash">>,<<"1.1.0">>},0}, {<<"small_ints">>,{pkg,<<"small_ints">>,<<"0.1.0">>},1}]}. [ {pkg_hash,[ @@ -11,6 +13,8 @@ {<<"ecc_compact">>, <<"C9696FF16A1D721F2DC8CCD760440B8F45586522974C5C7BD88640822E08AACA">>}, {<<"enacl">>, <<"F65DC64D9BFF2D8A534CB77AEF14DA5E7A2FA148987D87856F79A4745C9C2627">>}, {<<"erl_base58">>, <<"37710854461D71DF338E73C65776302DB41C4BAB4674D2EC134ED7BCFC7B5552">>}, + {<<"keccakf1600">>, <<"69D02D844A101BF3C75484C9E334FD04B0F57280727E881CAC3BD8240432F43A">>}, {<<"multiaddr">>, <<"978E58E28F6FACAF428C87AF933612B1E2F3F2775F1794EDA5E831A4EACD2984">>}, + {<<"multihash">>, <<"BB5C40F7464FB5DCACFA0B5DC14C9BDA9F920617040CB6CFC4FA1B434A016F11">>}, {<<"small_ints">>, <<"82A824C8794A2DDC73CB5CD00EAD11331DB296521AD16A619C13D668572B868A">>}]} ]. diff --git a/src/libp2p_crypto.app.src b/src/libp2p_crypto.app.src index 4d51d99..616960c 100644 --- a/src/libp2p_crypto.app.src +++ b/src/libp2p_crypto.app.src @@ -23,6 +23,7 @@ enacl, ecc_compact, multiaddr, + multihash, erl_base58 ]}, {env,[]}, diff --git a/src/libp2p_crypto.erl b/src/libp2p_crypto.erl index 1d03214..55abe53 100644 --- a/src/libp2p_crypto.erl +++ b/src/libp2p_crypto.erl @@ -11,19 +11,45 @@ %% the type of keythat follows in the binary (KEYTYPE). -define(KEYTYPE_ECC_COMPACT, 0). -define(KEYTYPE_ED25519, 1). +-define(KEYTYPE_MULTISIG, 2). -define(NETTYPE_MAIN, 0). -define(NETTYPE_TEST, 1). +-define(MULTISIG_SIG_LEN_BYTES, 8). % TODO Will we ever have a sig len > 65535? +-define(MULTISIG_KEY_INDEX_BITS, 8). % TODO Will we ever need more than 255 keys? + +%% TODO Expose list of hash types from multihash lib? +-define(MULTI_HASH_TYPES_ALL, [ + sha256, + sha256_dbl, + sha512, + sha3_224, + sha3_256, + sha3_384, + sha3_512, + shake128, + shake256 +]). +-define(MULTI_HASH_TYPE_DEFAULT, sha256). +-define(MULTI_HASH_TYPES_SUPPORTED, [?MULTI_HASH_TYPE_DEFAULT]). +-define(MULTI_HASH_TYPES_DEPRECATED, []). % TODO Where to use? + -type key_type() :: ecc_compact | ed25519. -type network() :: mainnet | testnet. --type privkey() :: +-opaque privkey() :: {ecc_compact, ecc_compact:private_key()} | {ed25519, enacl_privkey()}. --type pubkey() :: +-opaque pubkey_multi() :: + {multisig, pos_integer(), pos_integer(), binary()}. + +-opaque pubkey_single() :: {ecc_compact, ecc_compact:public_key()} | {ed25519, enacl_pubkey()}. +-opaque pubkey() :: + pubkey_single() | pubkey_multi(). + -type pubkey_bin() :: <<_:8, _:_*8>>. -type sig_fun() :: fun((binary()) -> binary()). -type ecdh_fun() :: fun((pubkey()) -> binary()). @@ -31,7 +57,15 @@ -type enacl_privkey() :: <<_:256>>. -type enacl_pubkey() :: <<_:256>>. --export_type([privkey/0, pubkey/0, pubkey_bin/0, sig_fun/0, ecdh_fun/0]). +-export_type([ + privkey/0, + pubkey/0, + pubkey_bin/0, + pubkey_multi/0, + pubkey_single/0, + sig_fun/0, + ecdh_fun/0 +]). -export([ get_network/1, @@ -58,7 +92,10 @@ p2p_to_pubkey_bin/1, verify/3, keys_to_bin/1, - keys_from_bin/1 + keys_from_bin/1, + make_multisig_pubkey/3, + make_multisig_pubkey/4, + make_multisig_signature/4 ]). -define(network, libp2p_crypto_network). @@ -240,9 +277,17 @@ pubkey_to_bin(Network, {ecc_compact, PubKey}) -> erlang:error(not_compact) end; pubkey_to_bin(Network, {ed25519, PubKey}) -> - <<(from_network(Network)):4, ?KEYTYPE_ED25519:4, PubKey/binary>>. - -%% @doc Convertsa a given binary encoded public key to a tagged public + <<(from_network(Network)):4, ?KEYTYPE_ED25519:4, PubKey/binary>>; +pubkey_to_bin(Network, {multisig, M, N, KeysDigest}) -> + << + (from_network(Network)):4, + ?KEYTYPE_MULTISIG:4, + M:?MULTISIG_KEY_INDEX_BITS/integer-unsigned-little, + N:?MULTISIG_KEY_INDEX_BITS/integer-unsigned-little, + KeysDigest/binary + >>. + +%% @doc Converts a a given binary encoded public key to a tagged public %% key. The key is asserted to be on the current active network. -spec bin_to_pubkey(pubkey_bin()) -> pubkey(). bin_to_pubkey(PubKeyBin) -> @@ -260,6 +305,31 @@ bin_to_pubkey(Network, <>) -> case NetType == from_network(Network) of true -> {ed25519, PubKey}; false -> erlang:error({bad_network, NetType}) + end; +bin_to_pubkey( + Network, + << + NetType:4, + ?KEYTYPE_MULTISIG:4, + M:?MULTISIG_KEY_INDEX_BITS/integer-unsigned-little, + N:?MULTISIG_KEY_INDEX_BITS/integer-unsigned-little, + KeysDigest/binary + >> +) -> + case NetType == from_network(Network) of + true -> + case M =< N of + true -> + case multihash:decode(KeysDigest) of + {error, Reason} -> + erlang:error({bad_multihash, Reason}); + {ok, _, _, _} -> + {multisig, M, N, KeysDigest} + end; + false -> + erlang:error({m_higher_than_n, M, N}) + end; + false -> erlang:error({bad_network, NetType}) end. %% @doc Converts a public key to base58 check encoded string @@ -296,14 +366,92 @@ from_network(testnet) -> ?NETTYPE_TEST. to_network(?NETTYPE_MAIN) -> mainnet; to_network(?NETTYPE_TEST) -> testnet. +-spec key_size_bytes(non_neg_integer()) -> pos_integer(). +key_size_bytes(?KEYTYPE_ED25519) -> + 32; +key_size_bytes(?KEYTYPE_ECC_COMPACT) -> + 32; +key_size_bytes(KeyType) -> + error({bad_key_type, KeyType}). + %% @doc Verifies a binary against a given digital signature over the %% sha256 of the binary. -spec verify(binary(), binary(), pubkey()) -> boolean(). +verify(Bin, MultiSignature, {multisig, M, N, KeysDigest}) -> + try + {Keys, KeysLen} = multisig_parse_keys(MultiSignature, N), + N = length(Keys), + <> = MultiSignature, + case multihash:decode(KeysDigest) of + {error, _} -> + false; + {ok, _, HashType, _} -> + case multihash:hash(KeysBin, HashType) of + <> -> + ISigs = multisig_parse_isigs(ISigsBin, M, N), + %% Reject dup key index + case ISigs -- lists:ukeysort(1, ISigs) of + [] -> + %% Index range: 0..N-1 + KS = [{lists:nth(I + 1, Keys), S} || {I, S} <- ISigs], + M =< length([{} || {K, S} <- KS, verify(Bin, S, K)]); + [_|_] -> + false + end; + <<_/binary>> -> + false + end + end + catch _:_ -> + false + end; verify(Bin, Signature, {ecc_compact, PubKey}) -> public_key:verify(Bin, sha256, Signature, PubKey); verify(Bin, Signature, {ed25519, PubKey}) -> enacl:sign_verify_detached(Signature, Bin, PubKey). +-spec multisig_parse_keys(binary(), non_neg_integer()) -> + {[pubkey()], non_neg_integer()}. +multisig_parse_keys(<>, N) -> + multisig_parse_keys(MultiSignature, N, 0, []). + +multisig_parse_keys(<<_/binary>>, 0, ConsumedBytes, Keys) -> + {lists:reverse(Keys), ConsumedBytes}; +multisig_parse_keys(<>, N, ConsumedBytes0, Keys) -> + Size = key_size_bytes(KeyType), + <> = Rest0, + Key = bin_to_pubkey(<>), + ConsumedBytes1 = ConsumedBytes0 + 1 + Size, % 1 for (NetType + KeyType) + multisig_parse_keys(Rest1, N - 1, ConsumedBytes1, [Key | Keys]); +multisig_parse_keys(<<_/binary>>, _, _, _) -> + error(multisig_keys_misaligned). % TODO Result type + +-spec multisig_parse_isigs(binary(), pos_integer(), pos_integer()) -> + [{non_neg_integer(), binary()}]. +multisig_parse_isigs(<>, M, N) -> + multisig_parse_isigs(ISigsBin, M, N, []). + +multisig_parse_isigs(<<>>, _, _, ISigs) -> + ISigs; +multisig_parse_isigs( + <>, + M, + N, + ISigs +) -> + %% Indices are in the range 0..N-1 + case I >= N of + true -> + error({multisig_parse_isigs, invalid_index}); + false -> + <> = Rest0, + multisig_parse_isigs(Rest1, M, N, [{I, Sig} | ISigs]) + end; +multisig_parse_isigs(<<_/binary>>, _, _, _) -> + error({multisig_parse_isigs, misaligned}). + %% @doc Convert a binary to a base58 check encoded string. The encoded %% version is set to 0. %% @@ -370,10 +518,365 @@ base58check_decode(B58) -> {error, bad_checksum} end. +-spec pubkey_is_multisig(pubkey()) -> boolean(). +pubkey_is_multisig({multisig, _, _, _}) -> + true; +pubkey_is_multisig({ecc_compact, _}) -> + false; +pubkey_is_multisig({ed25519, _}) -> + false. + +%% @doc The binary form of this multisig-pubkey can be optained with +%% pubkey_to_bin (just as any other pubkey), the format of which will be +%% roughly: +%% +%% <> +%% +%% (for precise sizes and KeyType value, see: bin_to_pubkey and pubkey_to_bin) +%% @end +-spec make_multisig_pubkey(pos_integer(), pos_integer(), [pubkey()]) -> + {ok, binary()} | {error, Error} when + Error :: {contains_multisig_keys, [pubkey()]}. +make_multisig_pubkey(M, N, PubKeys) -> + make_multisig_pubkey(M, N, PubKeys, ?MULTI_HASH_TYPE_DEFAULT). + +-spec make_multisig_pubkey(pos_integer(), pos_integer(), [pubkey()], HashType) -> + {ok, binary()} | {error, Error} when + Error :: + {contains_multisig_keys, [pubkey()]} + | {hash_type_unknown, HashType} + | {hash_type_unsupported, HashType}, + HashType :: atom(). +make_multisig_pubkey(M, N, PubKeys, HashType) -> + case lists:member(HashType, ?MULTI_HASH_TYPES_ALL) of + true -> + case lists:member(HashType, ?MULTI_HASH_TYPES_SUPPORTED) of + true -> + make_multisig_pubkey_(M, N, PubKeys, HashType); + false -> + {error, {hash_type_unsupported, HashType}} + end; + false -> + {error, {hash_type_unknown, HashType}} + end. + +-spec make_multisig_pubkey_(pos_integer(), pos_integer(), [pubkey()], HashType) -> + {ok, binary()} | {error, Error} when + Error :: {contains_multisig_keys, [pubkey()]}, + HashType :: atom(). +make_multisig_pubkey_(M, N, PubKeys, HashType) -> + case lists:filter(fun pubkey_is_multisig/1, PubKeys) of + [_|_]=PKs -> + {error, {contains_multisig_keys, PKs}}; + [] -> + PubKeysBins = lists:map(fun pubkey_to_bin/1, PubKeys), + PubKeysBin = iolist_to_binary(PubKeysBins), + {ok, {multisig, M, N, multihash:hash(PubKeysBin, HashType)}} + end. + +%% @doc A multisig-signature is a concatanation of a list of N +%% individual-pubkeys and a list of M-N triples of individual-signatures +%% prefixed with an index (of corresponsing individual-pubkey in the +%% multisig-pubkey) and the length of the individual-signature: +%% +%% <> +%% where +%% Pubkeys = <> +%% Triples = +%% << +%% 0/integer, Len0/integer Sig0:Len0/binary, +%% ..., +%% N/integer, LenN/integer, SigN/binary +%% >> +%% +%% (see for precise sizes see: isig_to_bin, multisig_parse_isigs) +%% +%% PubKeys MUST be in the same order as when each signature-triple was constructed. +%% Signature-triples MAY be in any order. +%% @end +-spec make_multisig_signature( + binary(), + pubkey_multi(), + [pubkey_single()], + [{non_neg_integer(), binary()}] +) -> {ok, binary()} | {error, Error} when + Error :: + insufficient_signatures + | insufficient_keys + | too_many_keys + | bad_key_digest + | {invalid_signatures, [binary()]}. +make_multisig_signature(_, {multisig, M, _, _}, _, S) when M > length(S) -> + {error, insufficient_signatures}; +make_multisig_signature(_, {multisig, _, N, _}, K, _) when N > length(K) -> + {error, insufficient_keys}; +make_multisig_signature(_, {multisig, _, N, _}, K, _) when N < length(K) -> + {error, too_many_keys}; +make_multisig_signature(Msg, {multisig, _, _, KeysDigest}, Keys, ISigs) -> + {ok, _, HashType, _} = multihash:decode(KeysDigest), + KeysBin = iolist_to_binary(lists:map(fun pubkey_to_bin/1, Keys)), + case multihash:hash(KeysBin, HashType) of + <> -> + KeySigs = [{lists:nth(I + 1, Keys), S} || {I, S} <- ISigs], + case [S || {K, S} <- KeySigs, not verify(Msg, S, K)] of + [] -> + ISigsBins = lists:map(fun isig_to_bin/1, ISigs), + {ok, iolist_to_binary([KeysBin, ISigsBins])}; + [_|_]=Sigs -> + {error, {invalid_signatures, Sigs}} + end; + <<_/binary>> -> + {error, bad_key_digest} + end. + +-spec isig_to_bin({non_neg_integer(), binary()}) -> binary(). +isig_to_bin({I, <>}) -> + Len = byte_size(Sig), + << + I:?MULTISIG_KEY_INDEX_BITS/integer-unsigned-little, + Len:?MULTISIG_SIG_LEN_BYTES/integer-unsigned-little, + Sig/binary + >>. + -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). +%% @doc Generates an EUnit test-set from the given parameters. +%% For test-set representation details see: +%% http://erlang.org/doc/apps/eunit/chapter.html#eunit-test-representation +%% @end +make_multisig_test_cases(M, N, HashType, KeyType) -> + Title = + fun (Name) -> + lists:flatten(io_lib:format( + "Multisig test: ~s. Params: [M:~b, N:~b, HashType:~s, KeyType:~s].", + [Name, M, N, HashType, KeyType] + )) + end, + %% TODO PropEr? + MsgGood = <<"I'm in dire need of HNT and communications to reach others seem abortive.">>, + ISigsToBin = fun (ISigs) -> lists:map(fun isig_to_bin/1, ISigs) end, + KeySig = + fun () -> + #{secret := SK, public := PK} = generate_keys(KeyType), + {PK, (mk_sig_fun(SK))(MsgGood)} + end, + IKeySigs = [{I, KeySig()} || I <- lists:seq(0, N - 1)], + Keys0 = [K || {_, {K, _}} <- IKeySigs], + Keys = lists:map(fun pubkey_to_bin/1, Keys0), + ISigs = list_shuffle(lists:sublist([{I, S} || {I, {_, S}} <- IKeySigs], M)), + KeysDigest = multihash:hash(iolist_to_binary(Keys), HashType), + {ok, MultiPubKeyGood} = make_multisig_pubkey_(M, N, Keys0, HashType), + {ok, MultiSigGood} = make_multisig_signature(MsgGood, MultiPubKeyGood, Keys0, ISigs), + + Positive = + [ + %% pubkey + { + Title("pubkey serialization round trip"), + ?_assertEqual(MultiPubKeyGood, bin_to_pubkey(pubkey_to_bin(MultiPubKeyGood))) + }, + { + Title("pubkey_is_multisig"), + ?_assert(pubkey_is_multisig(MultiPubKeyGood)) + }, + { + Title("pubkey multihash support check"), + case lists:member(HashType, ?MULTI_HASH_TYPES_SUPPORTED) of + true -> + ?_assertEqual( + {ok, MultiPubKeyGood}, + make_multisig_pubkey(M, N, Keys0, HashType) + ); + false -> + ?_assertEqual( + {error, {hash_type_unsupported, HashType}}, + make_multisig_pubkey(M, N, Keys0, HashType) + ) + end + }, + + %% sig + { + Title("Everything validly constructed"), + ?_assert(verify(MsgGood, MultiSigGood, MultiPubKeyGood)) + } + ], + Negative = + [ + (fun() -> + HT = trust_me_im_a_valid_hash_type, + ?_assertEqual( + {error, {hash_type_unknown, HT}}, + make_multisig_pubkey(M, N, Keys0, HT) + ) + end)(), + ?_assertEqual( + {error, insufficient_signatures}, + make_multisig_signature( + MsgGood, + MultiPubKeyGood, + Keys0, + lists:sublist(ISigs, M - 1) + ) + ), + ?_assertEqual( + {error, insufficient_keys}, + make_multisig_signature( + MsgGood, + MultiPubKeyGood, + lists:sublist(Keys0, M - 1), + ISigs + ) + ), + ?_assertEqual( + {error, too_many_keys}, + make_multisig_signature( + MsgGood, + MultiPubKeyGood, + lists:duplicate(N + 1, hd(Keys0)), + ISigs + ) + ), + { + Title("make_multisig_signature with a wrong member key"), + ?_assertEqual( + {error, bad_key_digest}, + %% To hit this exact error we need valid: + %% - M + %% - N + %% - hash function and type + %% - key formats + %% - key count + %% BUT at least one of the member keys to be different from + %% what we expect: + make_multisig_signature( + MsgGood, + {multisig, M, N, + multihash:hash( + (fun() -> + {K, _} = KeySig(), + iolist_to_binary([pubkey_to_bin(K) | tl(Keys)]) + end)(), + HashType + ) + }, + Keys0, + ISigs + ) + ) + }, + { + Title("Break index on a random isig, by pushing it out of range"), + (fun () -> + R = rand:uniform(M) - 1, % Correct for 0-index + Replace = + fun ({I, S}) when I =:= R -> {N + 1, S}; (IS) -> IS end, + ISigsOutOfRange = lists:map(Replace, ISigs), + SigBad = iolist_to_binary([Keys, ISigsToBin(ISigsOutOfRange)]), + ?_assertNot(verify(MsgGood, SigBad, MultiPubKeyGood)) + end)() + }, + { + Title("Wrong message string"), + ?_assertNot(verify( + <>, + MultiSigGood, + MultiPubKeyGood + )) + }, + { + Title("Multisig with appended junk"), + ?_assertNot(verify( + MsgGood, + <>, + MultiPubKeyGood + )) + }, + { + Title("Pubkey with junk appended to keys digest"), + ?_assertNot(verify( + MsgGood, + MultiSigGood, + {multisig, M, N, <>} + )) + }, + { + Title("Pubkey M > N"), + ?_assertNot(verify( + MsgGood, + MultiSigGood, + {multisig, N + 1, N, KeysDigest} + )) + }, + { + Title("Pubkey N < M"), + ?_assertNot(verify( + MsgGood, + MultiSigGood, + {multisig, M, M - 1, KeysDigest} + )) + }, + { + Title("Pubkey M + 1"), + ?_assertNot(verify( + MsgGood, + MultiSigGood, + {multisig, M + 1, N, KeysDigest} + )) + }, + { + Title("Pubkey N + 1"), + ?_assertNot(verify( + MsgGood, + MultiSigGood, + {multisig, M, N + 1, KeysDigest} + )) + } + ] + ++ + case Keys of + [K1, K2 | Ks] -> + [{ + Title("Re-ordered keys"), + ?_assertNot(verify( + MsgGood, + iolist_to_binary([[K2, K1 | Ks], ISigsToBin(ISigs)]), + MultiPubKeyGood + )) + }]; + [_] -> + [] + end + ++ + case M > 1 of + false -> + []; + true -> + [IS | _] = ISigs, + SigBad = + [iolist_to_binary([Keys, ISigsToBin(lists:duplicate(M, IS))])], + [{ + Title("Reuse same signature M times"), + ?_assertNot(verify(MsgGood, SigBad, MultiPubKeyGood)) + }] + end, + Positive ++ Negative. + +multisig_test_() -> + Params = + [ + [M, N, H, K] + || + N <- lists:seq(1, 10), + M <- lists:seq(1, N), + %% TODO Maybe heterogeneous combinations of hash and key types? + H <- ?MULTI_HASH_TYPES_ALL, + K <- [ed25519, ecc_compact] + ], + {inparallel, test_generator(fun make_multisig_test_cases/4, Params)}. + save_load_test() -> SaveLoad = fun(Network, KeyType) -> FileName = nonl(os:cmd("mktemp")), @@ -562,8 +1065,35 @@ helium_wallet_decode_ecc_compact_test() -> ?assertEqual(FakeTestnetKeyMap, KeyMap), ok. +%% Test helpers =============================================================== + nonl([$\n | T]) -> nonl(T); nonl([H | T]) -> [H | nonl(T)]; nonl([]) -> []. +test_generator(F, Params) -> + Next = + fun() -> + case Params of + [] -> []; + [P | Ps] -> [apply(F, P) | test_generator(F, Ps)] + end + end, + {generator, Next}. + +-spec array_shuffle(array:array(A)) -> array:array(A). +array_shuffle(A0) -> + array:foldl( + fun (I, X, A1) -> + J = rand:uniform(I + 1) - 1, + array:set(J, X, array:set(I, array:get(J, A1), A1)) + end, + array:new(), + A0 + ). + +-spec list_shuffle([A]) -> [A]. +list_shuffle(L) -> + array:to_list(array_shuffle(array:from_list(L))). + -endif. From f10c6aa6dbb3bae1efe938d924c6e91d7e6a59a4 Mon Sep 17 00:00:00 2001 From: Rahul Garg Date: Mon, 19 Apr 2021 10:35:13 -0700 Subject: [PATCH 02/14] Add eqc for multisig testing --- eqc/multisig_eqc.erl | 65 +++++++++++++++++++++++++++++++++++++++++++ rebar.config | 6 +++- src/libp2p_crypto.erl | 3 +- 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 eqc/multisig_eqc.erl diff --git a/eqc/multisig_eqc.erl b/eqc/multisig_eqc.erl new file mode 100644 index 0000000..dbfdbde --- /dev/null +++ b/eqc/multisig_eqc.erl @@ -0,0 +1,65 @@ +-module(multisig_eqc). + +-include_lib("eqc/include/eqc.hrl"). + +-export([prop_multipubkey_test/0]). + +prop_multipubkey_test() -> + ?FORALL( + {KeyType, {M, N}, HashType, Msg}, + {gen_keytype(), gen_m_n(), gen_hashtype(), gen_msg()}, + begin + KeySig = fun() -> + #{secret := SK, public := PK} = libp2p_crypto:generate_keys(KeyType), + {PK, (libp2p_crypto:mk_sig_fun(SK))(Msg)} + end, + IKeySigs = [{I, KeySig()} || I <- lists:seq(0, N - 1)], + + ISigs = lists:sublist([{I, S} || {I, {_, S}} <- IKeySigs], M), + + Keys0 = [K || {_, {K, _}} <- IKeySigs], + + {ok, MultiPubKey} = libp2p_crypto:make_multisig_pubkey(M, N, Keys0, HashType), + {ok, MultiSig} = libp2p_crypto:make_multisig_signature(Msg, MultiPubKey, Keys0, ISigs), + + ?WHENFAIL( + begin + io:format("M ~p~n", [M]), + io:format("N ~p~n", [N]), + io:format("Msg ~p~n", [Msg]), + io:format("HashType ~p~n", [HashType]), + io:format("MultiPubKey ~p~n", [MultiPubKey]), + io:format("MultiSig ~p~n", [MultiSig]) + end, + conjunction([ + {valid_pubkey, libp2p_crypto:pubkey_is_multisig(MultiPubKey)}, + {valid_multipubkey_roundtrip, + (MultiPubKey == + libp2p_crypto:bin_to_pubkey(libp2p_crypto:pubkey_to_bin(MultiPubKey)))}, + {valid_multi_sig, libp2p_crypto:verify(Msg, MultiSig, MultiPubKey)} + ]) + ) + end + ). + +gen_keytype() -> + elements([ecc_compact, ed25519]). + +gen_m_n() -> + ?SUCHTHAT({M, N}, {int(), int()}, (N > M andalso N =< 255 andalso M >= 1)). + +gen_hashtype() -> + elements([ + sha256, + sha256_dbl, + sha512, + sha3_224, + sha3_256, + sha3_384, + sha3_512, + shake128, + shake256 + ]). + +gen_msg() -> + binary(32). diff --git a/rebar.config b/rebar.config index 20fdfba..55ec22f 100644 --- a/rebar.config +++ b/rebar.config @@ -25,7 +25,11 @@ warnings_as_errors ]}. -{plugins, [covertool, erlfmt]}. +{plugins, [ + covertool, + erlfmt, + {rebar3_eqc, {git, "https://github.com/Vagabond/rebar3-eqc-plugin", {branch, "master"}}} +]}. {shell, [{apps, [libp2p_crypto]}]}. diff --git a/src/libp2p_crypto.erl b/src/libp2p_crypto.erl index 55abe53..a4abb22 100644 --- a/src/libp2p_crypto.erl +++ b/src/libp2p_crypto.erl @@ -95,7 +95,8 @@ keys_from_bin/1, make_multisig_pubkey/3, make_multisig_pubkey/4, - make_multisig_signature/4 + make_multisig_signature/4, + pubkey_is_multisig/1 ]). -define(network, libp2p_crypto_network). From 2ccee1ac22d237d4254d85fdbc7ea7281bb0fa98 Mon Sep 17 00:00:00 2001 From: Rahul Garg Date: Mon, 19 Apr 2021 10:41:59 -0700 Subject: [PATCH 03/14] Only use sha256 in eqc --- eqc/multisig_eqc.erl | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/eqc/multisig_eqc.erl b/eqc/multisig_eqc.erl index dbfdbde..567644d 100644 --- a/eqc/multisig_eqc.erl +++ b/eqc/multisig_eqc.erl @@ -4,10 +4,12 @@ -export([prop_multipubkey_test/0]). +-define(HASHTYPE, sha256). + prop_multipubkey_test() -> ?FORALL( - {KeyType, {M, N}, HashType, Msg}, - {gen_keytype(), gen_m_n(), gen_hashtype(), gen_msg()}, + {KeyType, {M, N}, Msg}, + {gen_keytype(), gen_m_n(), gen_msg()}, begin KeySig = fun() -> #{secret := SK, public := PK} = libp2p_crypto:generate_keys(KeyType), @@ -19,7 +21,7 @@ prop_multipubkey_test() -> Keys0 = [K || {_, {K, _}} <- IKeySigs], - {ok, MultiPubKey} = libp2p_crypto:make_multisig_pubkey(M, N, Keys0, HashType), + {ok, MultiPubKey} = libp2p_crypto:make_multisig_pubkey(M, N, Keys0, ?HASHTYPE), {ok, MultiSig} = libp2p_crypto:make_multisig_signature(Msg, MultiPubKey, Keys0, ISigs), ?WHENFAIL( @@ -27,7 +29,6 @@ prop_multipubkey_test() -> io:format("M ~p~n", [M]), io:format("N ~p~n", [N]), io:format("Msg ~p~n", [Msg]), - io:format("HashType ~p~n", [HashType]), io:format("MultiPubKey ~p~n", [MultiPubKey]), io:format("MultiSig ~p~n", [MultiSig]) end, @@ -48,18 +49,19 @@ gen_keytype() -> gen_m_n() -> ?SUCHTHAT({M, N}, {int(), int()}, (N > M andalso N =< 255 andalso M >= 1)). -gen_hashtype() -> - elements([ - sha256, - sha256_dbl, - sha512, - sha3_224, - sha3_256, - sha3_384, - sha3_512, - shake128, - shake256 - ]). - gen_msg() -> binary(32). + +%% NOTE: Use this generator if we ever support other hashtypes +%% gen_hashtype() -> +%% elements([ +%% sha256, +%% sha256_dbl, +%% sha512, +%% sha3_224, +%% sha3_256, +%% sha3_384, +%% sha3_512, +%% shake128, +%% shake256 +%% ]). From 5f0c37691d1fe32c941a1150efc8da22ba9740e9 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Mon, 19 Apr 2021 13:56:25 -0400 Subject: [PATCH 04/14] Update comment --- src/libp2p_crypto.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libp2p_crypto.erl b/src/libp2p_crypto.erl index 55abe53..6ae627e 100644 --- a/src/libp2p_crypto.erl +++ b/src/libp2p_crypto.erl @@ -374,8 +374,7 @@ key_size_bytes(?KEYTYPE_ECC_COMPACT) -> key_size_bytes(KeyType) -> error({bad_key_type, KeyType}). -%% @doc Verifies a binary against a given digital signature over the -%% sha256 of the binary. +%% @doc Verifies a binary against a given digital signature. -spec verify(binary(), binary(), pubkey()) -> boolean(). verify(Bin, MultiSignature, {multisig, M, N, KeysDigest}) -> try From 7ec7303d91dd5db65debca7be3bf86498cd56809 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Mon, 26 Apr 2021 17:06:21 -0400 Subject: [PATCH 05/14] Upgrade multihash to 2.0.2 --- rebar.config | 2 +- rebar.lock | 6 ++-- src/libp2p_crypto.erl | 76 ++++++++++++++++++++++++++----------------- 3 files changed, 50 insertions(+), 34 deletions(-) diff --git a/rebar.config b/rebar.config index 55ec22f..8fe0824 100644 --- a/rebar.config +++ b/rebar.config @@ -12,7 +12,7 @@ ]}. {deps, [ - {multihash, "1.1.0"}, + {multihash, "2.0.2"}, {ecc_compact, "1.0.5"}, {enacl, "1.1.1"}, {erl_base58, "0.0.1"}, diff --git a/rebar.lock b/rebar.lock index 12992dc..a026050 100644 --- a/rebar.lock +++ b/rebar.lock @@ -3,9 +3,8 @@ {<<"ecc_compact">>,{pkg,<<"ecc_compact">>,<<"1.0.5">>},0}, {<<"enacl">>,{pkg,<<"enacl">>,<<"1.1.1">>},0}, {<<"erl_base58">>,{pkg,<<"erl_base58">>,<<"0.0.1">>},0}, - {<<"keccakf1600">>,{pkg,<<"keccakf1600">>,<<"2.0.0">>},1}, {<<"multiaddr">>,{pkg,<<"multiaddr">>,<<"1.1.3">>},0}, - {<<"multihash">>,{pkg,<<"multihash">>,<<"1.1.0">>},0}, + {<<"multihash">>,{pkg,<<"multihash">>,<<"2.0.2">>},0}, {<<"small_ints">>,{pkg,<<"small_ints">>,<<"0.1.0">>},1}]}. [ {pkg_hash,[ @@ -13,8 +12,7 @@ {<<"ecc_compact">>, <<"C9696FF16A1D721F2DC8CCD760440B8F45586522974C5C7BD88640822E08AACA">>}, {<<"enacl">>, <<"F65DC64D9BFF2D8A534CB77AEF14DA5E7A2FA148987D87856F79A4745C9C2627">>}, {<<"erl_base58">>, <<"37710854461D71DF338E73C65776302DB41C4BAB4674D2EC134ED7BCFC7B5552">>}, - {<<"keccakf1600">>, <<"69D02D844A101BF3C75484C9E334FD04B0F57280727E881CAC3BD8240432F43A">>}, {<<"multiaddr">>, <<"978E58E28F6FACAF428C87AF933612B1E2F3F2775F1794EDA5E831A4EACD2984">>}, - {<<"multihash">>, <<"BB5C40F7464FB5DCACFA0B5DC14C9BDA9F920617040CB6CFC4FA1B434A016F11">>}, + {<<"multihash">>, <<"DC1C06D226E4D90719A622ABEEC5F038D04425BE27D8048FA51C09DA3076A08B">>}, {<<"small_ints">>, <<"82A824C8794A2DDC73CB5CD00EAD11331DB296521AD16A619C13D668572B868A">>}]} ]. diff --git a/src/libp2p_crypto.erl b/src/libp2p_crypto.erl index c28167f..dbd7b52 100644 --- a/src/libp2p_crypto.erl +++ b/src/libp2p_crypto.erl @@ -20,17 +20,24 @@ %% TODO Expose list of hash types from multihash lib? -define(MULTI_HASH_TYPES_ALL, [ - sha256, - sha256_dbl, - sha512, + identity, + sha1, + sha2_256, + sha2_512, sha3_224, sha3_256, sha3_384, sha3_512, - shake128, - shake256 + keccak224, + keccak256, + keccak384, + keccak512, + blake2b256, + blake2b512, + blake2s128, + blake2s256 ]). --define(MULTI_HASH_TYPE_DEFAULT, sha256). +-define(MULTI_HASH_TYPE_DEFAULT, sha2_256). -define(MULTI_HASH_TYPES_SUPPORTED, [?MULTI_HASH_TYPE_DEFAULT]). -define(MULTI_HASH_TYPES_DEPRECATED, []). % TODO Where to use? @@ -321,10 +328,10 @@ bin_to_pubkey( true -> case M =< N of true -> - case multihash:decode(KeysDigest) of + case multihash:hash(KeysDigest) of {error, Reason} -> erlang:error({bad_multihash, Reason}); - {ok, _, _, _} -> + {ok, _} -> {multisig, M, N, KeysDigest} end; false -> @@ -382,12 +389,12 @@ verify(Bin, MultiSignature, {multisig, M, N, KeysDigest}) -> {Keys, KeysLen} = multisig_parse_keys(MultiSignature, N), N = length(Keys), <> = MultiSignature, - case multihash:decode(KeysDigest) of + case multihash:hash(KeysDigest) of {error, _} -> false; - {ok, _, HashType, _} -> - case multihash:hash(KeysBin, HashType) of - <> -> + {ok, HashType} -> + case multihash:digest(KeysBin, HashType) of + {ok, <>} -> ISigs = multisig_parse_isigs(ISigsBin, M, N), %% Reject dup key index case ISigs -- lists:ukeysort(1, ISigs) of @@ -398,7 +405,9 @@ verify(Bin, MultiSignature, {multisig, M, N, KeysDigest}) -> [_|_] -> false end; - <<_/binary>> -> + {ok, <<_/binary>>} -> + false; + {error, _} -> false end end @@ -562,16 +571,21 @@ make_multisig_pubkey(M, N, PubKeys, HashType) -> -spec make_multisig_pubkey_(pos_integer(), pos_integer(), [pubkey()], HashType) -> {ok, binary()} | {error, Error} when - Error :: {contains_multisig_keys, [pubkey()]}, + Error :: {contains_multisig_keys, [pubkey()]} + | {multihash_failure, Reason :: term()}, HashType :: atom(). make_multisig_pubkey_(M, N, PubKeys, HashType) -> case lists:filter(fun pubkey_is_multisig/1, PubKeys) of [_|_]=PKs -> {error, {contains_multisig_keys, PKs}}; [] -> - PubKeysBins = lists:map(fun pubkey_to_bin/1, PubKeys), - PubKeysBin = iolist_to_binary(PubKeysBins), - {ok, {multisig, M, N, multihash:hash(PubKeysBin, HashType)}} + PubKeysBin = iolist_to_binary([pubkey_to_bin(PK) || PK <- PubKeys]), + case multihash:digest(PubKeysBin, HashType) of + {ok, <>} -> + {ok, {multisig, M, N, KeysDigest}}; + {error, Reason} -> + {error, {multihash_failure, Reason}} + end end. %% @doc A multisig-signature is a concatanation of a list of N @@ -613,10 +627,10 @@ make_multisig_signature(_, {multisig, _, N, _}, K, _) when N > length(K) -> make_multisig_signature(_, {multisig, _, N, _}, K, _) when N < length(K) -> {error, too_many_keys}; make_multisig_signature(Msg, {multisig, _, _, KeysDigest}, Keys, ISigs) -> - {ok, _, HashType, _} = multihash:decode(KeysDigest), + {ok, HashType} = multihash:hash(KeysDigest), KeysBin = iolist_to_binary(lists:map(fun pubkey_to_bin/1, Keys)), - case multihash:hash(KeysBin, HashType) of - <> -> + case multihash:digest(KeysBin, HashType) of + {ok, <>} -> KeySigs = [{lists:nth(I + 1, Keys), S} || {I, S} <- ISigs], case [S || {K, S} <- KeySigs, not verify(Msg, S, K)] of [] -> @@ -625,7 +639,9 @@ make_multisig_signature(Msg, {multisig, _, _, KeysDigest}, Keys, ISigs) -> [_|_]=Sigs -> {error, {invalid_signatures, Sigs}} end; - <<_/binary>> -> + {ok, <<_/binary>>} -> + {error, bad_key_digest}; + {error, _} -> {error, bad_key_digest} end. @@ -666,7 +682,7 @@ make_multisig_test_cases(M, N, HashType, KeyType) -> Keys0 = [K || {_, {K, _}} <- IKeySigs], Keys = lists:map(fun pubkey_to_bin/1, Keys0), ISigs = list_shuffle(lists:sublist([{I, S} || {I, {_, S}} <- IKeySigs], M)), - KeysDigest = multihash:hash(iolist_to_binary(Keys), HashType), + {ok, KeysDigest} = multihash:digest(iolist_to_binary(Keys), HashType), {ok, MultiPubKeyGood} = make_multisig_pubkey_(M, N, Keys0, HashType), {ok, MultiSigGood} = make_multisig_signature(MsgGood, MultiPubKeyGood, Keys0, ISigs), @@ -754,13 +770,15 @@ make_multisig_test_cases(M, N, HashType, KeyType) -> make_multisig_signature( MsgGood, {multisig, M, N, - multihash:hash( - (fun() -> - {K, _} = KeySig(), - iolist_to_binary([pubkey_to_bin(K) | tl(Keys)]) - end)(), - HashType - ) + (fun() -> + {K, _} = KeySig(), + {ok, BadKeysDigest} = + multihash:digest( + iolist_to_binary([pubkey_to_bin(K) | tl(Keys)]), + HashType + ), + BadKeysDigest + end)() }, Keys0, ISigs From 5acf1c85c054f0f104460ea41160ff8abde8b1d6 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Tue, 27 Apr 2021 12:18:58 -0400 Subject: [PATCH 06/14] Fix network support in multisig --- src/libp2p_crypto.erl | 70 ++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/src/libp2p_crypto.erl b/src/libp2p_crypto.erl index dbd7b52..c57367c 100644 --- a/src/libp2p_crypto.erl +++ b/src/libp2p_crypto.erl @@ -100,9 +100,9 @@ verify/3, keys_to_bin/1, keys_from_bin/1, - make_multisig_pubkey/3, make_multisig_pubkey/4, - make_multisig_signature/4, + make_multisig_pubkey/5, + make_multisig_signature/5, pubkey_is_multisig/1 ]). @@ -429,7 +429,7 @@ multisig_parse_keys(<<_/binary>>, 0, ConsumedBytes, Keys) -> multisig_parse_keys(<>, N, ConsumedBytes0, Keys) -> Size = key_size_bytes(KeyType), <> = Rest0, - Key = bin_to_pubkey(<>), + Key = bin_to_pubkey(to_network(NetType), <>), ConsumedBytes1 = ConsumedBytes0 + 1 + Size, % 1 for (NetType + KeyType) multisig_parse_keys(Rest1, N - 1, ConsumedBytes1, [Key | Keys]); multisig_parse_keys(<<_/binary>>, _, _, _) -> @@ -543,25 +543,25 @@ pubkey_is_multisig({ed25519, _}) -> %% %% (for precise sizes and KeyType value, see: bin_to_pubkey and pubkey_to_bin) %% @end --spec make_multisig_pubkey(pos_integer(), pos_integer(), [pubkey()]) -> +-spec make_multisig_pubkey(network(), pos_integer(), pos_integer(), [pubkey()]) -> {ok, binary()} | {error, Error} when Error :: {contains_multisig_keys, [pubkey()]}. -make_multisig_pubkey(M, N, PubKeys) -> - make_multisig_pubkey(M, N, PubKeys, ?MULTI_HASH_TYPE_DEFAULT). +make_multisig_pubkey(Network, M, N, PubKeys) -> + make_multisig_pubkey(Network, M, N, PubKeys, ?MULTI_HASH_TYPE_DEFAULT). --spec make_multisig_pubkey(pos_integer(), pos_integer(), [pubkey()], HashType) -> +-spec make_multisig_pubkey(network(), pos_integer(), pos_integer(), [pubkey()], HashType) -> {ok, binary()} | {error, Error} when Error :: {contains_multisig_keys, [pubkey()]} | {hash_type_unknown, HashType} | {hash_type_unsupported, HashType}, HashType :: atom(). -make_multisig_pubkey(M, N, PubKeys, HashType) -> +make_multisig_pubkey(Network, M, N, PubKeys, HashType) -> case lists:member(HashType, ?MULTI_HASH_TYPES_ALL) of true -> case lists:member(HashType, ?MULTI_HASH_TYPES_SUPPORTED) of true -> - make_multisig_pubkey_(M, N, PubKeys, HashType); + make_multisig_pubkey_(Network, M, N, PubKeys, HashType); false -> {error, {hash_type_unsupported, HashType}} end; @@ -569,17 +569,17 @@ make_multisig_pubkey(M, N, PubKeys, HashType) -> {error, {hash_type_unknown, HashType}} end. --spec make_multisig_pubkey_(pos_integer(), pos_integer(), [pubkey()], HashType) -> +-spec make_multisig_pubkey_(network(), pos_integer(), pos_integer(), [pubkey()], HashType) -> {ok, binary()} | {error, Error} when Error :: {contains_multisig_keys, [pubkey()]} | {multihash_failure, Reason :: term()}, HashType :: atom(). -make_multisig_pubkey_(M, N, PubKeys, HashType) -> +make_multisig_pubkey_(Network, M, N, PubKeys, HashType) -> case lists:filter(fun pubkey_is_multisig/1, PubKeys) of [_|_]=PKs -> {error, {contains_multisig_keys, PKs}}; [] -> - PubKeysBin = iolist_to_binary([pubkey_to_bin(PK) || PK <- PubKeys]), + PubKeysBin = iolist_to_binary([pubkey_to_bin(Network, PK) || PK <- PubKeys]), case multihash:digest(PubKeysBin, HashType) of {ok, <>} -> {ok, {multisig, M, N, KeysDigest}}; @@ -609,6 +609,7 @@ make_multisig_pubkey_(M, N, PubKeys, HashType) -> %% Signature-triples MAY be in any order. %% @end -spec make_multisig_signature( + network(), binary(), pubkey_multi(), [pubkey_single()], @@ -620,15 +621,16 @@ make_multisig_pubkey_(M, N, PubKeys, HashType) -> | too_many_keys | bad_key_digest | {invalid_signatures, [binary()]}. -make_multisig_signature(_, {multisig, M, _, _}, _, S) when M > length(S) -> +make_multisig_signature(_, _, {multisig, M, _, _}, _, S) when M > length(S) -> {error, insufficient_signatures}; -make_multisig_signature(_, {multisig, _, N, _}, K, _) when N > length(K) -> +make_multisig_signature(_, _, {multisig, _, N, _}, K, _) when N > length(K) -> {error, insufficient_keys}; -make_multisig_signature(_, {multisig, _, N, _}, K, _) when N < length(K) -> +make_multisig_signature(_, _, {multisig, _, N, _}, K, _) when N < length(K) -> {error, too_many_keys}; -make_multisig_signature(Msg, {multisig, _, _, KeysDigest}, Keys, ISigs) -> +make_multisig_signature(Network, Msg, {multisig, _, _, KeysDigest}, Keys, ISigs) -> {ok, HashType} = multihash:hash(KeysDigest), - KeysBin = iolist_to_binary(lists:map(fun pubkey_to_bin/1, Keys)), + PK2Bin = fun(PK) -> pubkey_to_bin(Network, PK) end, + KeysBin = iolist_to_binary(lists:map(PK2Bin, Keys)), case multihash:digest(KeysBin, HashType) of {ok, <>} -> KeySigs = [{lists:nth(I + 1, Keys), S} || {I, S} <- ISigs], @@ -662,12 +664,12 @@ isig_to_bin({I, <>}) -> %% For test-set representation details see: %% http://erlang.org/doc/apps/eunit/chapter.html#eunit-test-representation %% @end -make_multisig_test_cases(M, N, HashType, KeyType) -> +make_multisig_test_cases(Network, M, N, HashType, KeyType) -> Title = fun (Name) -> lists:flatten(io_lib:format( - "Multisig test: ~s. Params: [M:~b, N:~b, HashType:~s, KeyType:~s].", - [Name, M, N, HashType, KeyType] + "Multisig test: ~s. Params: [Network: ~p, M:~b, N:~b, HashType:~s, KeyType:~s].", + [Name, Network, M, N, HashType, KeyType] )) end, %% TODO PropEr? @@ -680,18 +682,19 @@ make_multisig_test_cases(M, N, HashType, KeyType) -> end, IKeySigs = [{I, KeySig()} || I <- lists:seq(0, N - 1)], Keys0 = [K || {_, {K, _}} <- IKeySigs], - Keys = lists:map(fun pubkey_to_bin/1, Keys0), + PK2Bin = fun(PK) -> pubkey_to_bin(Network, PK) end, + Keys = lists:map(PK2Bin, Keys0), ISigs = list_shuffle(lists:sublist([{I, S} || {I, {_, S}} <- IKeySigs], M)), {ok, KeysDigest} = multihash:digest(iolist_to_binary(Keys), HashType), - {ok, MultiPubKeyGood} = make_multisig_pubkey_(M, N, Keys0, HashType), - {ok, MultiSigGood} = make_multisig_signature(MsgGood, MultiPubKeyGood, Keys0, ISigs), + {ok, MultiPubKeyGood} = make_multisig_pubkey_(Network, M, N, Keys0, HashType), + {ok, MultiSigGood} = make_multisig_signature(Network, MsgGood, MultiPubKeyGood, Keys0, ISigs), Positive = [ %% pubkey { Title("pubkey serialization round trip"), - ?_assertEqual(MultiPubKeyGood, bin_to_pubkey(pubkey_to_bin(MultiPubKeyGood))) + ?_assertEqual(MultiPubKeyGood, bin_to_pubkey(Network, PK2Bin(MultiPubKeyGood))) }, { Title("pubkey_is_multisig"), @@ -703,12 +706,12 @@ make_multisig_test_cases(M, N, HashType, KeyType) -> true -> ?_assertEqual( {ok, MultiPubKeyGood}, - make_multisig_pubkey(M, N, Keys0, HashType) + make_multisig_pubkey(Network, M, N, Keys0, HashType) ); false -> ?_assertEqual( {error, {hash_type_unsupported, HashType}}, - make_multisig_pubkey(M, N, Keys0, HashType) + make_multisig_pubkey(Network, M, N, Keys0, HashType) ) end }, @@ -725,12 +728,13 @@ make_multisig_test_cases(M, N, HashType, KeyType) -> HT = trust_me_im_a_valid_hash_type, ?_assertEqual( {error, {hash_type_unknown, HT}}, - make_multisig_pubkey(M, N, Keys0, HT) + make_multisig_pubkey(Network, M, N, Keys0, HT) ) end)(), ?_assertEqual( {error, insufficient_signatures}, make_multisig_signature( + Network, MsgGood, MultiPubKeyGood, Keys0, @@ -740,6 +744,7 @@ make_multisig_test_cases(M, N, HashType, KeyType) -> ?_assertEqual( {error, insufficient_keys}, make_multisig_signature( + Network, MsgGood, MultiPubKeyGood, lists:sublist(Keys0, M - 1), @@ -749,6 +754,7 @@ make_multisig_test_cases(M, N, HashType, KeyType) -> ?_assertEqual( {error, too_many_keys}, make_multisig_signature( + Network, MsgGood, MultiPubKeyGood, lists:duplicate(N + 1, hd(Keys0)), @@ -768,13 +774,14 @@ make_multisig_test_cases(M, N, HashType, KeyType) -> %% BUT at least one of the member keys to be different from %% what we expect: make_multisig_signature( + Network, MsgGood, {multisig, M, N, (fun() -> {K, _} = KeySig(), {ok, BadKeysDigest} = multihash:digest( - iolist_to_binary([pubkey_to_bin(K) | tl(Keys)]), + iolist_to_binary([PK2Bin(K) | tl(Keys)]), HashType ), BadKeysDigest @@ -885,15 +892,16 @@ make_multisig_test_cases(M, N, HashType, KeyType) -> multisig_test_() -> Params = [ - [M, N, H, K] + [Network, M, N, H, K] || N <- lists:seq(1, 10), M <- lists:seq(1, N), %% TODO Maybe heterogeneous combinations of hash and key types? H <- ?MULTI_HASH_TYPES_ALL, - K <- [ed25519, ecc_compact] + K <- [ed25519, ecc_compact], + Network <- [mainnet, testnet] ], - {inparallel, test_generator(fun make_multisig_test_cases/4, Params)}. + {inparallel, test_generator(fun make_multisig_test_cases/5, Params)}. save_load_test() -> SaveLoad = fun(Network, KeyType) -> From f890f52f26f380b89686afa56c17ddb2a8e0fec1 Mon Sep 17 00:00:00 2001 From: Jay Kickliter Date: Tue, 27 Apr 2021 11:04:16 -0700 Subject: [PATCH 07/14] Set default rust toolchain in CI --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c342664..6354fb5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,9 @@ jobs: - name: Checkout uses: actions/checkout@v2 + - name: Rust Toolchain + run: rustup default stable + - name: Cache Hex Packages uses: actions/cache@v1 with: From 682a9f8e893ae74f1e2d9e0bd785393ab687f924 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Tue, 27 Apr 2021 16:48:42 -0400 Subject: [PATCH 08/14] Support 2 more multihash types sha3_256 and blake2b256 --- src/libp2p_crypto.erl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/libp2p_crypto.erl b/src/libp2p_crypto.erl index c57367c..afc0bb8 100644 --- a/src/libp2p_crypto.erl +++ b/src/libp2p_crypto.erl @@ -37,8 +37,14 @@ blake2s128, blake2s256 ]). --define(MULTI_HASH_TYPE_DEFAULT, sha2_256). --define(MULTI_HASH_TYPES_SUPPORTED, [?MULTI_HASH_TYPE_DEFAULT]). +-define(MULTI_HASH_TYPE_DEFAULT, + sha2_256 +). +-define(MULTI_HASH_TYPES_SUPPORTED, [ + sha2_256, + sha3_256, + blake2b256 +]). -define(MULTI_HASH_TYPES_DEPRECATED, []). % TODO Where to use? -type key_type() :: ecc_compact | ed25519. From 3aef9d8cfe12c79da21ed638e8bbc62cb5ae9e18 Mon Sep 17 00:00:00 2001 From: Rahul Garg Date: Tue, 27 Apr 2021 16:35:25 -0700 Subject: [PATCH 09/14] Update eqc for API changes --- eqc/multisig_eqc.erl | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/eqc/multisig_eqc.erl b/eqc/multisig_eqc.erl index 567644d..6763cf5 100644 --- a/eqc/multisig_eqc.erl +++ b/eqc/multisig_eqc.erl @@ -4,12 +4,12 @@ -export([prop_multipubkey_test/0]). --define(HASHTYPE, sha256). +-define(NETWORK, mainnet). prop_multipubkey_test() -> ?FORALL( - {KeyType, {M, N}, Msg}, - {gen_keytype(), gen_m_n(), gen_msg()}, + {KeyType, {M, N}, Msg, HashType}, + {gen_keytype(), gen_m_n(), gen_msg(), gen_hashtype()}, begin KeySig = fun() -> #{secret := SK, public := PK} = libp2p_crypto:generate_keys(KeyType), @@ -21,8 +21,8 @@ prop_multipubkey_test() -> Keys0 = [K || {_, {K, _}} <- IKeySigs], - {ok, MultiPubKey} = libp2p_crypto:make_multisig_pubkey(M, N, Keys0, ?HASHTYPE), - {ok, MultiSig} = libp2p_crypto:make_multisig_signature(Msg, MultiPubKey, Keys0, ISigs), + {ok, MultiPubKey} = libp2p_crypto:make_multisig_pubkey(?NETWORK, M, N, Keys0, HashType), + {ok, MultiSig} = libp2p_crypto:make_multisig_signature(?NETWORK, Msg, MultiPubKey, Keys0, ISigs), ?WHENFAIL( begin @@ -53,15 +53,9 @@ gen_msg() -> binary(32). %% NOTE: Use this generator if we ever support other hashtypes -%% gen_hashtype() -> -%% elements([ -%% sha256, -%% sha256_dbl, -%% sha512, -%% sha3_224, -%% sha3_256, -%% sha3_384, -%% sha3_512, -%% shake128, -%% shake256 -%% ]). +gen_hashtype() -> + elements([ + sha2_256, + sha3_256, + blake2b256 + ]). From ce980fe752415206d3ed4df47cf5ebcf870c76ce Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Fri, 30 Apr 2021 13:52:01 -0400 Subject: [PATCH 10/14] Force a canonical order of pub keys in multisig --- src/libp2p_crypto.erl | 61 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/src/libp2p_crypto.erl b/src/libp2p_crypto.erl index afc0bb8..2f140c4 100644 --- a/src/libp2p_crypto.erl +++ b/src/libp2p_crypto.erl @@ -431,7 +431,7 @@ multisig_parse_keys(<>, N) -> multisig_parse_keys(MultiSignature, N, 0, []). multisig_parse_keys(<<_/binary>>, 0, ConsumedBytes, Keys) -> - {lists:reverse(Keys), ConsumedBytes}; + {multisig_member_keys_sort(Keys), ConsumedBytes}; multisig_parse_keys(<>, N, ConsumedBytes0, Keys) -> Size = key_size_bytes(KeyType), <> = Rest0, @@ -585,7 +585,7 @@ make_multisig_pubkey_(Network, M, N, PubKeys, HashType) -> [_|_]=PKs -> {error, {contains_multisig_keys, PKs}}; [] -> - PubKeysBin = iolist_to_binary([pubkey_to_bin(Network, PK) || PK <- PubKeys]), + PubKeysBin = multisig_member_keys_to_bin(Network, PubKeys), case multihash:digest(PubKeysBin, HashType) of {ok, <>} -> {ok, {multisig, M, N, KeysDigest}}; @@ -594,6 +594,27 @@ make_multisig_pubkey_(Network, M, N, PubKeys, HashType) -> end end. +-spec multisig_member_keys_to_bin(network(), [pubkey_single()]) -> binary(). +multisig_member_keys_to_bin(Network, PKs) -> + Bins = [pubkey_to_bin(Network, K) || K <- multisig_member_keys_sort(PKs)], + iolist_to_binary(Bins). + +-spec multisig_member_keys_sort([pubkey_single()]) -> [pubkey_single()]. +multisig_member_keys_sort(Keys0) -> + Keys1 = [{K, multisig_member_key_sort_form(K)} || K <- Keys0], + Cmp = fun ({_, A}, {_, B}) -> multisig_member_keys_cmp(A, B) end, + [K || {K, _} <- lists:sort(Cmp, Keys1)]. + +-spec multisig_member_keys_cmp(binary(), binary()) -> boolean(). +multisig_member_keys_cmp(A, B) -> + A < B. + +-spec multisig_member_key_sort_form(pubkey_single()) -> binary(). +multisig_member_key_sort_form({multisig, _, _, _}) -> + error({badarg, expected_single_but_given_multisig_pubkey}); +multisig_member_key_sort_form(PK) -> + list_to_binary(pubkey_to_b58(PK)). + %% @doc A multisig-signature is a concatanation of a list of N %% individual-pubkeys and a list of M-N triples of individual-signatures %% prefixed with an index (of corresponsing individual-pubkey in the @@ -635,8 +656,7 @@ make_multisig_signature(_, _, {multisig, _, N, _}, K, _) when N < length(K) -> {error, too_many_keys}; make_multisig_signature(Network, Msg, {multisig, _, _, KeysDigest}, Keys, ISigs) -> {ok, HashType} = multihash:hash(KeysDigest), - PK2Bin = fun(PK) -> pubkey_to_bin(Network, PK) end, - KeysBin = iolist_to_binary(lists:map(PK2Bin, Keys)), + KeysBin = multisig_member_keys_to_bin(Network, Keys), case multihash:digest(KeysBin, HashType) of {ok, <>} -> KeySigs = [{lists:nth(I + 1, Keys), S} || {I, S} <- ISigs], @@ -684,14 +704,19 @@ make_multisig_test_cases(Network, M, N, HashType, KeyType) -> KeySig = fun () -> #{secret := SK, public := PK} = generate_keys(KeyType), - {PK, (mk_sig_fun(SK))(MsgGood)} + {PK, multisig_member_key_sort_form(PK), (mk_sig_fun(SK))(MsgGood)} end, - IKeySigs = [{I, KeySig()} || I <- lists:seq(0, N - 1)], + KeySigs = + lists:sort( + fun ({_, A, _}, {_, B, _}) -> multisig_member_keys_cmp(A, B) end, + [KeySig() || _ <- lists:duplicate(N, {})] + ), + IKeySigs = mapi(fun({I, {K, _, S}}) -> {I, {K, S}} end, KeySigs, 0), Keys0 = [K || {_, {K, _}} <- IKeySigs], PK2Bin = fun(PK) -> pubkey_to_bin(Network, PK) end, - Keys = lists:map(PK2Bin, Keys0), + BinKeys = lists:map(PK2Bin, Keys0), ISigs = list_shuffle(lists:sublist([{I, S} || {I, {_, S}} <- IKeySigs], M)), - {ok, KeysDigest} = multihash:digest(iolist_to_binary(Keys), HashType), + {ok, KeysDigest} = multihash:digest(iolist_to_binary(BinKeys), HashType), {ok, MultiPubKeyGood} = make_multisig_pubkey_(Network, M, N, Keys0, HashType), {ok, MultiSigGood} = make_multisig_signature(Network, MsgGood, MultiPubKeyGood, Keys0, ISigs), @@ -784,10 +809,10 @@ make_multisig_test_cases(Network, M, N, HashType, KeyType) -> MsgGood, {multisig, M, N, (fun() -> - {K, _} = KeySig(), + {_, BinKey, _} = KeySig(), {ok, BadKeysDigest} = multihash:digest( - iolist_to_binary([PK2Bin(K) | tl(Keys)]), + iolist_to_binary([BinKey | tl(BinKeys)]), HashType ), BadKeysDigest @@ -805,7 +830,7 @@ make_multisig_test_cases(Network, M, N, HashType, KeyType) -> Replace = fun ({I, S}) when I =:= R -> {N + 1, S}; (IS) -> IS end, ISigsOutOfRange = lists:map(Replace, ISigs), - SigBad = iolist_to_binary([Keys, ISigsToBin(ISigsOutOfRange)]), + SigBad = iolist_to_binary([BinKeys, ISigsToBin(ISigsOutOfRange)]), ?_assertNot(verify(MsgGood, SigBad, MultiPubKeyGood)) end)() }, @@ -867,7 +892,7 @@ make_multisig_test_cases(Network, M, N, HashType, KeyType) -> } ] ++ - case Keys of + case BinKeys of [K1, K2 | Ks] -> [{ Title("Re-ordered keys"), @@ -887,7 +912,7 @@ make_multisig_test_cases(Network, M, N, HashType, KeyType) -> true -> [IS | _] = ISigs, SigBad = - [iolist_to_binary([Keys, ISigsToBin(lists:duplicate(M, IS))])], + [iolist_to_binary([BinKeys, ISigsToBin(lists:duplicate(M, IS))])], [{ Title("Reuse same signature M times"), ?_assertNot(verify(MsgGood, SigBad, MultiPubKeyGood)) @@ -1128,4 +1153,14 @@ array_shuffle(A0) -> list_shuffle(L) -> array:to_list(array_shuffle(array:from_list(L))). +-spec mapi(fun(({integer(), X}) -> Y), [X]) -> [Y]. +mapi(F, Xs) -> + mapi(F, Xs, 1). + +-spec mapi(fun(({integer(), X}) -> Y), [X], integer()) -> [Y]. +mapi(_, [], _) -> + []; +mapi(F, [X | Xs], I) -> + [F({I, X}) | mapi(F, Xs, I + 1)]. + -endif. From 677d864baa125c9e61365b439099f258c5f0383f Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Mon, 3 May 2021 07:41:52 -0700 Subject: [PATCH 11/14] Improve testing, resolve TODOs --- src/libp2p_crypto.erl | 101 ++++++++++++++++++++++++++++++++---------- 1 file changed, 78 insertions(+), 23 deletions(-) diff --git a/src/libp2p_crypto.erl b/src/libp2p_crypto.erl index 2f140c4..3a43eea 100644 --- a/src/libp2p_crypto.erl +++ b/src/libp2p_crypto.erl @@ -15,10 +15,15 @@ -define(NETTYPE_MAIN, 0). -define(NETTYPE_TEST, 1). --define(MULTISIG_SIG_LEN_BYTES, 8). % TODO Will we ever have a sig len > 65535? --define(MULTISIG_KEY_INDEX_BITS, 8). % TODO Will we ever need more than 255 keys? +-define(MULTISIG_SIG_LEN_BYTES, 8). %% signatures can be up to 256 bytes +-define(MULTISIG_KEY_INDEX_BITS, 8). %% up to 256 keys -%% TODO Expose list of hash types from multihash lib? +-define(PRIMITIVE_KEY_TYPES, [ + ecc_compact, + ed25519 + ]). + +%% used for testing and known/unknown checking -define(MULTI_HASH_TYPES_ALL, [ identity, sha1, @@ -45,7 +50,7 @@ sha3_256, blake2b256 ]). --define(MULTI_HASH_TYPES_DEPRECATED, []). % TODO Where to use? +-define(MULTI_HASH_TYPES_DEPRECATED, []). %% used ONLY in verify -type key_type() :: ecc_compact | ed25519. -type network() :: mainnet | testnet. @@ -399,21 +404,24 @@ verify(Bin, MultiSignature, {multisig, M, N, KeysDigest}) -> {error, _} -> false; {ok, HashType} -> - case multihash:digest(KeysBin, HashType) of - {ok, <>} -> - ISigs = multisig_parse_isigs(ISigsBin, M, N), - %% Reject dup key index - case ISigs -- lists:ukeysort(1, ISigs) of - [] -> - %% Index range: 0..N-1 - KS = [{lists:nth(I + 1, Keys), S} || {I, S} <- ISigs], - M =< length([{} || {K, S} <- KS, verify(Bin, S, K)]); - [_|_] -> + case lists:member(HashType, ?MULTI_HASH_TYPES_SUPPORTED ++ ?MULTI_HASH_TYPES_DEPRECATED) of + true -> + case multihash:digest(KeysBin, HashType) of + {ok, <>} -> + ISigs = multisig_parse_isigs(ISigsBin, M, N), + %% Reject dup key index + case ISigs -- lists:ukeysort(1, ISigs) of + [] -> + %% Index range: 0..N-1 + KS = [{lists:nth(I + 1, Keys), S} || {I, S} <- ISigs], + M =< length([{} || {K, S} <- KS, verify(Bin, S, K)]); + [_|_] -> + false + end; + _ -> false end; - {ok, <<_/binary>>} -> - false; - {error, _} -> + false -> false end end @@ -439,7 +447,7 @@ multisig_parse_keys(<>, N, ConsumedBytes0, K ConsumedBytes1 = ConsumedBytes0 + 1 + Size, % 1 for (NetType + KeyType) multisig_parse_keys(Rest1, N - 1, ConsumedBytes1, [Key | Keys]); multisig_parse_keys(<<_/binary>>, _, _, _) -> - error(multisig_keys_misaligned). % TODO Result type + error(multisig_keys_misaligned). -spec multisig_parse_isigs(binary(), pos_integer(), pos_integer()) -> [{non_neg_integer(), binary()}]. @@ -698,12 +706,15 @@ make_multisig_test_cases(Network, M, N, HashType, KeyType) -> [Name, Network, M, N, HashType, KeyType] )) end, - %% TODO PropEr? MsgGood = <<"I'm in dire need of HNT and communications to reach others seem abortive.">>, ISigsToBin = fun (ISigs) -> lists:map(fun isig_to_bin/1, ISigs) end, KeySig = fun () -> - #{secret := SK, public := PK} = generate_keys(KeyType), + #{secret := SK, public := PK} = case KeyType of + random -> + generate_keys(hd(list_shuffle(?PRIMITIVE_KEY_TYPES))); + _ -> generate_keys(KeyType) + end, {PK, multisig_member_key_sort_form(PK), (mk_sig_fun(SK))(MsgGood)} end, KeySigs = @@ -834,6 +845,22 @@ make_multisig_test_cases(Network, M, N, HashType, KeyType) -> ?_assertNot(verify(MsgGood, SigBad, MultiPubKeyGood)) end)() }, + { + Title("Duplicate sig from same index"), + (fun () -> + SigBad = iolist_to_binary([BinKeys, ISigsToBin([hd(ISigs)|ISigs])]), + ?_assertNot(verify(MsgGood, SigBad, MultiPubKeyGood)) + end)() + }, + { + Title("Unsupported hash type"), + (fun () -> + {ok, MultiPubKeyBad} = make_multisig_pubkey_(Network, M, N, Keys0, sha1), + {ok, SigBad} = make_multisig_signature(Network, MsgGood, MultiPubKeyBad, Keys0, ISigs), + SigBad = iolist_to_binary([BinKeys, ISigsToBin(ISigs)]), + ?_assertNot(verify(MsgGood, SigBad, MultiPubKeyBad)) + end)() + }, { Title("Wrong message string"), ?_assertNot(verify( @@ -850,6 +877,14 @@ make_multisig_test_cases(Network, M, N, HashType, KeyType) -> MultiPubKeyGood )) }, + { + Title("Multisig with unsupported hash"), + ?_assertNot(verify( + MsgGood, + multihash:digest(iolist_to_binary(BinKeys), sha1), + MultiPubKeyGood + )) + }, { Title("Pubkey with junk appended to keys digest"), ?_assertNot(verify( @@ -889,6 +924,27 @@ make_multisig_test_cases(Network, M, N, HashType, KeyType) -> MultiSigGood, {multisig, M, N + 1, KeysDigest} )) + }, + { + Title("bin_to_pubkey bad multihash"), + ?_assertError({bad_multihash, invalid_code}, bin_to_pubkey(mainnet, <>)) + }, + { + Title("bin_to_pubkey m higher than N"), + ?_assertError({m_higher_than_n, 5, 3}, bin_to_pubkey(mainnet, <>)) + }, + { + Title("bin_to_pubkey bad nettype"), + ?_assertError({bad_network, 1}, bin_to_pubkey(mainnet, <>)) } ] ++ @@ -927,9 +983,8 @@ multisig_test_() -> || N <- lists:seq(1, 10), M <- lists:seq(1, N), - %% TODO Maybe heterogeneous combinations of hash and key types? - H <- ?MULTI_HASH_TYPES_ALL, - K <- [ed25519, ecc_compact], + H <- ?MULTI_HASH_TYPES_SUPPORTED, + K <- [random | ?PRIMITIVE_KEY_TYPES], Network <- [mainnet, testnet] ], {inparallel, test_generator(fun make_multisig_test_cases/5, Params)}. From 0e03db6317c503a0fbb4e42a9cdf3b95eb632614 Mon Sep 17 00:00:00 2001 From: Andrew Thompson Date: Mon, 3 May 2021 08:06:28 -0700 Subject: [PATCH 12/14] Use erlang:error more consistently --- src/libp2p_crypto.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libp2p_crypto.erl b/src/libp2p_crypto.erl index 3a43eea..c2ba9e6 100644 --- a/src/libp2p_crypto.erl +++ b/src/libp2p_crypto.erl @@ -447,7 +447,7 @@ multisig_parse_keys(<>, N, ConsumedBytes0, K ConsumedBytes1 = ConsumedBytes0 + 1 + Size, % 1 for (NetType + KeyType) multisig_parse_keys(Rest1, N - 1, ConsumedBytes1, [Key | Keys]); multisig_parse_keys(<<_/binary>>, _, _, _) -> - error(multisig_keys_misaligned). + erlang:error(multisig_keys_misaligned). -spec multisig_parse_isigs(binary(), pos_integer(), pos_integer()) -> [{non_neg_integer(), binary()}]. @@ -467,13 +467,13 @@ multisig_parse_isigs( %% Indices are in the range 0..N-1 case I >= N of true -> - error({multisig_parse_isigs, invalid_index}); + erlang:error({multisig_parse_isigs, invalid_index}); false -> <> = Rest0, multisig_parse_isigs(Rest1, M, N, [{I, Sig} | ISigs]) end; multisig_parse_isigs(<<_/binary>>, _, _, _) -> - error({multisig_parse_isigs, misaligned}). + erlang:error({multisig_parse_isigs, misaligned}). %% @doc Convert a binary to a base58 check encoded string. The encoded %% version is set to 0. @@ -619,7 +619,7 @@ multisig_member_keys_cmp(A, B) -> -spec multisig_member_key_sort_form(pubkey_single()) -> binary(). multisig_member_key_sort_form({multisig, _, _, _}) -> - error({badarg, expected_single_but_given_multisig_pubkey}); + erlang:error({badarg, expected_single_but_given_multisig_pubkey}); multisig_member_key_sort_form(PK) -> list_to_binary(pubkey_to_b58(PK)). From 4fd037b74ca216b59b6efac87abe1db39bdbcdb4 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Mon, 3 May 2021 12:50:47 -0400 Subject: [PATCH 13/14] Test all multihash hash types --- src/libp2p_crypto.erl | 88 +++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 37 deletions(-) diff --git a/src/libp2p_crypto.erl b/src/libp2p_crypto.erl index c2ba9e6..d078efd 100644 --- a/src/libp2p_crypto.erl +++ b/src/libp2p_crypto.erl @@ -396,6 +396,15 @@ key_size_bytes(KeyType) -> %% @doc Verifies a binary against a given digital signature. -spec verify(binary(), binary(), pubkey()) -> boolean(). verify(Bin, MultiSignature, {multisig, M, N, KeysDigest}) -> + HashTypes = ?MULTI_HASH_TYPES_SUPPORTED ++ ?MULTI_HASH_TYPES_DEPRECATED, + verify_multisig(Bin, MultiSignature, {multisig, M, N, KeysDigest}, HashTypes); +verify(Bin, Signature, {ecc_compact, PubKey}) -> + public_key:verify(Bin, sha256, Signature, PubKey); +verify(Bin, Signature, {ed25519, PubKey}) -> + enacl:sign_verify_detached(Signature, Bin, PubKey). + +-spec verify_multisig(binary(), binary(), pubkey_multi(), [atom()]) -> boolean(). +verify_multisig(Bin, MultiSignature, {multisig, M, N, KeysDigest}, HashTypes) -> try {Keys, KeysLen} = multisig_parse_keys(MultiSignature, N), N = length(Keys), @@ -404,7 +413,7 @@ verify(Bin, MultiSignature, {multisig, M, N, KeysDigest}) -> {error, _} -> false; {ok, HashType} -> - case lists:member(HashType, ?MULTI_HASH_TYPES_SUPPORTED ++ ?MULTI_HASH_TYPES_DEPRECATED) of + case lists:member(HashType, HashTypes) of true -> case multihash:digest(KeysBin, HashType) of {ok, <>} -> @@ -427,11 +436,7 @@ verify(Bin, MultiSignature, {multisig, M, N, KeysDigest}) -> end catch _:_ -> false - end; -verify(Bin, Signature, {ecc_compact, PubKey}) -> - public_key:verify(Bin, sha256, Signature, PubKey); -verify(Bin, Signature, {ed25519, PubKey}) -> - enacl:sign_verify_detached(Signature, Bin, PubKey). + end. -spec multisig_parse_keys(binary(), non_neg_integer()) -> {[pubkey()], non_neg_integer()}. @@ -698,12 +703,12 @@ isig_to_bin({I, <>}) -> %% For test-set representation details see: %% http://erlang.org/doc/apps/eunit/chapter.html#eunit-test-representation %% @end -make_multisig_test_cases(Network, M, N, HashType, KeyType) -> +make_multisig_test_cases(Network, M, N, KeyType, HashType, HashTypes) -> Title = fun (Name) -> lists:flatten(io_lib:format( - "Multisig test: ~s. Params: [Network: ~p, M:~b, N:~b, HashType:~s, KeyType:~s].", - [Name, Network, M, N, HashType, KeyType] + "Multisig test: ~s. Params: [Network: ~p, M:~b, N:~b, KeyType:~s, HashType:~s, HashTypes: ~p].", + [Name, Network, M, N, KeyType, HashType, HashTypes] )) end, MsgGood = <<"I'm in dire need of HNT and communications to reach others seem abortive.">>, @@ -761,7 +766,7 @@ make_multisig_test_cases(Network, M, N, HashType, KeyType) -> %% sig { Title("Everything validly constructed"), - ?_assert(verify(MsgGood, MultiSigGood, MultiPubKeyGood)) + ?_assert(verify_multisig(MsgGood, MultiSigGood, MultiPubKeyGood, HashTypes)) } ], Negative = @@ -842,87 +847,94 @@ make_multisig_test_cases(Network, M, N, HashType, KeyType) -> fun ({I, S}) when I =:= R -> {N + 1, S}; (IS) -> IS end, ISigsOutOfRange = lists:map(Replace, ISigs), SigBad = iolist_to_binary([BinKeys, ISigsToBin(ISigsOutOfRange)]), - ?_assertNot(verify(MsgGood, SigBad, MultiPubKeyGood)) + ?_assertNot(verify_multisig(MsgGood, SigBad, MultiPubKeyGood, HashTypes)) end)() }, { Title("Duplicate sig from same index"), (fun () -> SigBad = iolist_to_binary([BinKeys, ISigsToBin([hd(ISigs)|ISigs])]), - ?_assertNot(verify(MsgGood, SigBad, MultiPubKeyGood)) + ?_assertNot(verify_multisig(MsgGood, SigBad, MultiPubKeyGood, HashTypes)) end)() }, { Title("Unsupported hash type"), (fun () -> - {ok, MultiPubKeyBad} = make_multisig_pubkey_(Network, M, N, Keys0, sha1), + {ok, MultiPubKeyBad} = make_multisig_pubkey_(Network, M, N, Keys0, HashType), {ok, SigBad} = make_multisig_signature(Network, MsgGood, MultiPubKeyBad, Keys0, ISigs), - SigBad = iolist_to_binary([BinKeys, ISigsToBin(ISigs)]), - ?_assertNot(verify(MsgGood, SigBad, MultiPubKeyBad)) + ?_assertNot(verify_multisig(MsgGood, SigBad, MultiPubKeyBad, HashTypes -- [HashType])) end)() }, { Title("Wrong message string"), - ?_assertNot(verify( + ?_assertNot(verify_multisig( <>, MultiSigGood, - MultiPubKeyGood + MultiPubKeyGood, + HashTypes )) }, { Title("Multisig with appended junk"), - ?_assertNot(verify( + ?_assertNot(verify_multisig( MsgGood, <>, - MultiPubKeyGood + MultiPubKeyGood, + HashTypes )) }, { Title("Multisig with unsupported hash"), - ?_assertNot(verify( + ?_assertNot(verify_multisig( MsgGood, multihash:digest(iolist_to_binary(BinKeys), sha1), - MultiPubKeyGood + MultiPubKeyGood, + HashTypes )) }, { Title("Pubkey with junk appended to keys digest"), - ?_assertNot(verify( + ?_assertNot(verify_multisig( MsgGood, MultiSigGood, - {multisig, M, N, <>} + {multisig, M, N, <>}, + HashTypes )) }, { Title("Pubkey M > N"), - ?_assertNot(verify( + ?_assertNot(verify_multisig( MsgGood, MultiSigGood, - {multisig, N + 1, N, KeysDigest} + {multisig, N + 1, N, KeysDigest}, + HashTypes )) }, { Title("Pubkey N < M"), - ?_assertNot(verify( + ?_assertNot(verify_multisig( MsgGood, MultiSigGood, - {multisig, M, M - 1, KeysDigest} + {multisig, M, M - 1, KeysDigest}, + HashTypes )) }, { Title("Pubkey M + 1"), - ?_assertNot(verify( + ?_assertNot(verify_multisig( MsgGood, MultiSigGood, - {multisig, M + 1, N, KeysDigest} + {multisig, M + 1, N, KeysDigest}, + HashTypes )) }, { Title("Pubkey N + 1"), - ?_assertNot(verify( + ?_assertNot(verify_multisig( MsgGood, MultiSigGood, - {multisig, M, N + 1, KeysDigest} + {multisig, M, N + 1, KeysDigest}, + HashTypes )) }, { @@ -952,10 +964,11 @@ make_multisig_test_cases(Network, M, N, HashType, KeyType) -> [K1, K2 | Ks] -> [{ Title("Re-ordered keys"), - ?_assertNot(verify( + ?_assertNot(verify_multisig( MsgGood, iolist_to_binary([[K2, K1 | Ks], ISigsToBin(ISigs)]), - MultiPubKeyGood + MultiPubKeyGood, + HashTypes )) }]; [_] -> @@ -971,23 +984,24 @@ make_multisig_test_cases(Network, M, N, HashType, KeyType) -> [iolist_to_binary([BinKeys, ISigsToBin(lists:duplicate(M, IS))])], [{ Title("Reuse same signature M times"), - ?_assertNot(verify(MsgGood, SigBad, MultiPubKeyGood)) + ?_assertNot(verify_multisig(MsgGood, SigBad, MultiPubKeyGood, HashTypes)) }] end, Positive ++ Negative. multisig_test_() -> + HashTypes = ?MULTI_HASH_TYPES_ALL, Params = [ - [Network, M, N, H, K] + [Network, M, N, K, H, HashTypes] || N <- lists:seq(1, 10), M <- lists:seq(1, N), - H <- ?MULTI_HASH_TYPES_SUPPORTED, + H <- HashTypes, K <- [random | ?PRIMITIVE_KEY_TYPES], Network <- [mainnet, testnet] ], - {inparallel, test_generator(fun make_multisig_test_cases/5, Params)}. + {inparallel, test_generator(fun make_multisig_test_cases/6, Params)}. save_load_test() -> SaveLoad = fun(Network, KeyType) -> From 337c32045edb18cc149f15d9dee3b866cdba9eb0 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Mon, 3 May 2021 13:36:01 -0400 Subject: [PATCH 14/14] Use dynamic network param instead of the hard-coded one. --- src/libp2p_crypto.erl | 62 ++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/src/libp2p_crypto.erl b/src/libp2p_crypto.erl index d078efd..c32cd9a 100644 --- a/src/libp2p_crypto.erl +++ b/src/libp2p_crypto.erl @@ -938,25 +938,55 @@ make_multisig_test_cases(Network, M, N, KeyType, HashType, HashTypes) -> )) }, { - Title("bin_to_pubkey bad multihash"), - ?_assertError({bad_multihash, invalid_code}, bin_to_pubkey(mainnet, <>)) + Title("bin_to_pubkey bad multihash"), + ?_assertError( + {bad_multihash, invalid_code}, + bin_to_pubkey( + Network, + << + (from_network(Network)):4, + ?KEYTYPE_MULTISIG:4, + 3:?MULTISIG_KEY_INDEX_BITS/integer-unsigned-little, + 5:?MULTISIG_KEY_INDEX_BITS/integer-unsigned-little, + "hello world" + >> + ) + ) }, { - Title("bin_to_pubkey m higher than N"), - ?_assertError({m_higher_than_n, 5, 3}, bin_to_pubkey(mainnet, <>)) + Title("bin_to_pubkey m higher than N"), + ?_assertError( + {m_higher_than_n, 5, 3}, + bin_to_pubkey( + Network, + << + (from_network(Network)):4, + ?KEYTYPE_MULTISIG:4, + 5:?MULTISIG_KEY_INDEX_BITS/integer-unsigned-little, + 3:?MULTISIG_KEY_INDEX_BITS/integer-unsigned-little, + "hello world" + >> + ) + ) }, { - Title("bin_to_pubkey bad nettype"), - ?_assertError({bad_network, 1}, bin_to_pubkey(mainnet, <>)) + Title("bin_to_pubkey bad nettype"), + (fun() -> + [BadNetwork] = [mainnet, testnet] -- [Network], + ?_assertError( + {bad_network, _}, + bin_to_pubkey( + BadNetwork, + << + (from_network(Network)):4, + ?KEYTYPE_MULTISIG:4, + 3:?MULTISIG_KEY_INDEX_BITS/integer-unsigned-little, + 5:?MULTISIG_KEY_INDEX_BITS/integer-unsigned-little, + "hello world" + >> + ) + ) + end)() } ] ++ @@ -995,7 +1025,7 @@ multisig_test_() -> [ [Network, M, N, K, H, HashTypes] || - N <- lists:seq(1, 10), + N <- lists:seq(1, 5), M <- lists:seq(1, N), H <- HashTypes, K <- [random | ?PRIMITIVE_KEY_TYPES],