Skip to content

Commit

Permalink
Support M-of-N multisig
Browse files Browse the repository at this point in the history
  • Loading branch information
xandkar committed Apr 6, 2021
1 parent 35ed925 commit 7b134a4
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 2 deletions.
1 change: 1 addition & 0 deletions rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
]}.

{deps, [
{multihash, {git, "https://github.com/helium/erlang-multihash.git", {tag, "v2.0.2"}}},
{ecc_compact, "1.0.5"},
{enacl, "1.1.1"},
{erl_base58, "0.0.1"},
Expand Down
4 changes: 4 additions & 0 deletions rebar.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
{<<"enacl">>,{pkg,<<"enacl">>,<<"1.1.1">>},0},
{<<"erl_base58">>,{pkg,<<"erl_base58">>,<<"0.0.1">>},0},
{<<"multiaddr">>,{pkg,<<"multiaddr">>,<<"1.1.3">>},0},
{<<"multihash">>,
{git,"https://github.com/helium/erlang-multihash.git",
{ref,"d2057bf37d580d840dbd64918c768ef210f7d298"}},
0},
{<<"small_ints">>,{pkg,<<"small_ints">>,<<"0.1.0">>},1}]}.
[
{pkg_hash,[
Expand Down
1 change: 1 addition & 0 deletions src/libp2p_crypto.app.src
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
enacl,
ecc_compact,
multiaddr,
multihash,
erl_base58
]},
{env,[]},
Expand Down
64 changes: 62 additions & 2 deletions src/libp2p_crypto.erl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
%% 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).

Expand All @@ -22,7 +23,8 @@

-type pubkey() ::
{ecc_compact, ecc_compact:public_key()}
| {ed25519, enacl_pubkey()}.
| {ed25519, enacl_pubkey()}
| {multisig_m_of_n, pos_integer(), pos_integer(), binary()}.

-type pubkey_bin() :: <<_:8, _:_*8>>.
-type sig_fun() :: fun((binary()) -> binary()).
Expand Down Expand Up @@ -240,7 +242,9 @@ 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>>.
<<(from_network(Network)):4, ?KEYTYPE_ED25519:4, PubKey/binary>>;
pubkey_to_bin(Network, {multisig_m_of_n, M, N, KeysDigest}) ->
<<(from_network(Network)):4, ?KEYTYPE_MULTISIG:4, M/integer, N/integer, KeysDigest/binary>>.

%% @doc Convertsa a given binary encoded public key to a tagged public
%% key. The key is asserted to be on the current active network.
Expand All @@ -260,6 +264,11 @@ bin_to_pubkey(Network, <<NetType:4, ?KEYTYPE_ED25519:4, PubKey:32/binary>>) ->
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/integer, N/integer, KeysDigest/binary>>) ->
case NetType == from_network(Network) of
true -> {multisig_m_of_n, M, N, KeysDigest};
false -> erlang:error({bad_network, NetType})
end.

%% @doc Converts a public key to base58 check encoded string
Expand Down Expand Up @@ -299,6 +308,34 @@ to_network(?NETTYPE_TEST) -> testnet.
%% @doc Verifies a binary against a given digital signature over the
%% sha256 of the binary.
-spec verify(binary(), binary(), pubkey()) -> boolean().
verify(Bin, Signature, {multisig_m_of_n, M, N, KeysDigest}) ->
KeySize = 32, % TODO Macro?
KeysLen = N * KeySize,
<<KeysBin:KeysLen/binary, ISigsBin/binary>> = Signature,
{ok, HashType} = multihash:hash(KeysDigest),
case multihash:digest(KeysBin, HashType) of
{ok, KeysDigest} ->
% TODO Sort keys before or after decoding?
Keys =
lists:map(
fun bin_to_pubkey/1,
lists:sort(fun pubkey_bin_cmp/2, bin_split(KeysBin, KeySize))
),
KeySigs =
bin_fold(
fun (<<I:8/integer, Rest0/binary>>, KeySigs) ->
Key = lists:nth(I, Keys),
Size = sig_size(Key),
<<Sig:Size, Rest1/binary>> = Rest0,
{Rest1, [{Key, Sig} | KeySigs]}
end,
[],
ISigsBin
),
M =< length([{} || {K, S} <- KeySigs, verify(Bin, S, K)]);
_ ->
false
end;
verify(Bin, Signature, {ecc_compact, PubKey}) ->
public_key:verify(Bin, sha256, Signature, PubKey);
verify(Bin, Signature, {ed25519, PubKey}) ->
Expand Down Expand Up @@ -335,6 +372,11 @@ b58_to_version_bin(Str) ->
{error, Reason} -> error(Reason)
end.

-spec pubkey_bin_cmp(binary(), binary()) -> boolean().
pubkey_bin_cmp(<<A/binary>>, <<B/binary>>) ->
% TODO Something smarter?
A > B.

%% @doc Converts a given binary public key to a P2P address.
%%
%% @see p2p_to_pubkey_bin/1
Expand Down Expand Up @@ -370,6 +412,24 @@ base58check_decode(B58) ->
{error, bad_checksum}
end.

-spec sig_size(pubkey()) -> pos_integer().
sig_size({ecc_compact, _}) -> 560;
sig_size({ed25519 , _}) -> 512.

-spec bin_fold(fun((binary(), A) -> {binary(), A}), A, binary()) -> A.
bin_fold(_, Acc, <<>>) ->
Acc;
bin_fold(F, Acc0, <<Bin0/binary>>) ->
{<<Bin1/binary>>, Acc1} = F(Bin0, Acc0),
bin_fold(F, Acc1, Bin1).

-spec bin_split(B, pos_integer()) -> [B] when B :: binary().
bin_split(<<>>, _) ->
[];
bin_split(<<Bin/binary>>, ChunkSize) ->
<<Chunk:ChunkSize/binary, Rest/binary>> = Bin,
[Chunk | bin_split(Rest, ChunkSize)].

-ifdef(TEST).

-include_lib("eunit/include/eunit.hrl").
Expand Down

0 comments on commit 7b134a4

Please sign in to comment.