Compare commits

..

10 Commits

Author SHA1 Message Date
c262d63c89 Minor 2023-03-16 16:27:08 +09:00
0904ed3283 Merge branch 'master' of github.com:zxq9/ec_utils 2023-03-16 10:52:26 +09:00
8de9eefd75 Add SHA-3/Keccak dep 2023-03-16 10:52:01 +09:00
573b0fd1bb
Merge branch 'hanssv:master' into master 2023-03-16 10:33:21 +09:00
Hans Svensson
9db7c9220e QuickCheck tests for ec_recover 2023-01-07 17:05:34 +01:00
Hans Svensson
0dad93cf3c Better ecu_crypto API + ec_recover/2 2023-01-07 16:53:49 +01:00
Hans Svensson
5a0d41bd62 Cleanup and fix type specs 2022-10-15 22:24:17 +02:00
Hans Svensson
bf8623e9fc [test]: Fix enacl github reference 2022-10-15 20:25:45 +02:00
5d7f6349ac Update meta 2022-08-31 16:37:37 +09:00
0a82f63d93 Set version to 1.0.0 2022-08-31 16:34:04 +09:00
12 changed files with 101 additions and 51 deletions

View File

@ -3,8 +3,8 @@
{registered,[]},
{included_applications,[]},
{applications,[stdlib,kernel]},
{vsn,"0.1.0"},
{modules,[ed25519_eqc,eddsa_eqc,ec_utils,ecu_crypto,ecu_ecdsa,
ecu_ed25519,ecu_eddsa,ecu_misc,ecu_secp256k1,
benchmark_tests,crypto_tests,ecdsa_tests,
secp256k1_tests]}]}.
{vsn,"1.0.0"},
{modules,[ecu_crypto_eqc,ed25519_eqc,eddsa_eqc,ec_utils,
ecu_crypto,ecu_ecdsa,ecu_ed25519,ecu_eddsa,ecu_misc,
ecu_secp256k1,benchmark_tests,crypto_tests,
ecdsa_tests,secp256k1_tests]}]}.

25
eqc/ecu_crypto_eqc.erl Normal file
View File

@ -0,0 +1,25 @@
%%% File : ecu_crypto_eqc.erl
%%% Author : Hans Svensson
%%% Description :
%%% Created : 7 Jan 2023 by Hans Svensson
-module(ecu_crypto_eqc).
-compile([export_all, nowarn_export_all]).
-include_lib("eqc/include/eqc.hrl").
gen_ecdsa_secp256k1_privkey() ->
<<P0:256>> = crypto:strong_rand_bytes(32),
P = (P0 rem (ecu_secp256k1:n() - 1)) + 1,
return(<<P:256>>).
prop_recover() ->
?FORALL(PK, gen_ecdsa_secp256k1_privkey(),
begin
MsgHash = sha3:hash(256, PK),
Sig = ecu_crypto:eth_sign(MsgHash, PK),
Pub1 = ecrecover:recover(MsgHash, Sig),
Pub2 = ecu_crypto:ec_recover(MsgHash, Sig),
equals(Pub1, Pub2)
end).

View File

@ -7,7 +7,8 @@
{deps,
[{sha3, {git, "https://github.com/aeternity/erlang-sha3", {ref, "b5f27a2"}}}]}.
{profiles, [{test, [{deps, [{enacl, {git, "https://github.com/aeternity/enacl.git", {ref, "01dd0c2"}}},
{profiles, [{test, [{deps, [{enacl, {git, "https://github.com/aeternity/enacl.git", {ref, "5bae41c"}}},
{ecrecover, {git, "https://github.com/aeternity/ecrecover.git", {ref, "74b7816"}}}]}]},
{eqc, [{deps, [{enacl, {git, "https://github.com/aeternity/enacl.git", {ref, "38ffc76"}}}]}]}
{eqc, [{deps, [{enacl, {git, "https://github.com/aeternity/enacl.git", {ref, "38ffc76"}}},
{ecrecover, {git, "https://github.com/aeternity/ecrecover.git", {ref, "74b7816"}}}]}]}
]}.

View File

@ -1,4 +1,4 @@
-module(ec_utils).
-vsn("0.1.0").
-vsn("1.0.0").
-export([]).

View File

@ -3,10 +3,12 @@
%%% Description :
%%% Created : 13 Jan 2022 by Hans Svensson
-module(ecu_crypto).
-vsn("0.1.0").
-vsn("1.0.0").
-export([private_to_short/2, public_to_short/2,
eth_sign/2, eth_recover/2, eth_verify/3, eth_msg_hash/1,
ec_recover/2,
eth_sign/2, eth_recover/2, eth_verify/3,
eth_msg_sign/2, eth_msg_recover/2, eth_msg_verify/3, eth_msg_hash/1,
keccak256/1]).
private_to_short(bitcoin, PrivateKey) ->
@ -27,15 +29,33 @@ public_to_short(ethereum, PubKey) ->
ShortPub
end.
eth_msg_sign(Msg, PrivateKey = <<_:32/bytes>>) ->
eth_sign(eth_msg_hash(Msg), PrivateKey).
eth_sign(Msg, PrivateKey = <<_:32/bytes>>) ->
{BaseSig, YVal} = ecu_ecdsa:sign_secp256k1(eth_msg_hash(Msg), PrivateKey),
{BaseSig, YVal} = ecu_ecdsa:sign_secp256k1(Msg, PrivateKey),
V = if YVal rem 2 == 0 -> 27;
true -> 28
end,
<<V:8, BaseSig/bytes>>.
eth_recover(Msg, Sig = <<_:65/bytes>>) ->
MsgHash = eth_msg_hash(Msg),
eth_msg_recover(Msg, Sig = <<_:65/bytes>>) ->
eth_recover(eth_msg_hash(Msg), Sig).
%% This is the mythical Ethereum ECRECOVERY operation
ec_recover(MsgHash = <<_:32/bytes>>, Sig = <<_:65/bytes>>) ->
<<V:8, R:256, S:256>> = Sig,
case (V == 27 orelse V == 28) andalso
(R >= 1 andalso R =< ecu_secp256k1:n()) andalso
(S >= 1 andalso S =< ecu_secp256k1:n()) of
true ->
ShortPub = eth_recover(MsgHash, Sig),
<<0:96, ShortPub/binary>>;
false ->
<<0:256>>
end.
eth_recover(MsgHash = <<_:32/bytes>>, Sig = <<_:65/bytes>>) ->
<<E:256>> = MsgHash,
<<V:8, R:256, S:256>> = Sig,
Z = E rem ecu_secp256k1:n(),
@ -50,8 +70,11 @@ eth_recover(Msg, Sig = <<_:65/bytes>>) ->
<<_:12/bytes, RPub:20/bytes>> = keccak256(<<X:256, Y:256>>),
RPub.
eth_verify(Msg, PublicKey, Sig) ->
PublicKey == eth_recover(Msg, Sig).
eth_msg_verify(Msg, PublicKey, Sig) ->
eth_verify(eth_msg_hash(Msg), PublicKey, Sig).
eth_verify(Msg, PublickKey, Sig) ->
PublickKey == eth_recover(Msg, Sig).
eth_msg_hash(Msg0) ->
Msg = ["\x19Ethereum Signed Message:\n", integer_to_list(byte_size(Msg0)), Msg0],

View File

@ -3,7 +3,7 @@
%%% Description : ecdsa functionality
%%% Created : 13 Jan 2022 by Hans Svensson
-module(ecu_ecdsa).
-vsn("0.1.0").
-vsn("1.0.0").
-export([sign/3, verify/4,
sign_secp256k1/2,

View File

@ -4,7 +4,7 @@
%%% Just for usage when speed isn't crucial...
%%% Created : 13 Jan 2022 by Hans Svensson
-module(ecu_ed25519).
-vsn("0.1.0").
-vsn("1.0.0").
-define(P, 16#7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED).
-define(N, 16#1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED).
@ -12,12 +12,12 @@
-type pt_affine() :: {non_neg_integer(), non_neg_integer()}. %% {X, Y}
-type pt_hom_ext() :: {non_neg_integer(), non_neg_integer(),
non_neg_integer(), non_neg_integer()}. %% {X, Y, Z, T}
-type pt_compressed() :: <<_:32>>. %% Y coord + odd/even X.
-type pt_compressed() :: <<_:256>>. %% Y coord + odd/even X.
-type pt() :: pt_affine() | pt_hom_ext() | pt_compressed().
%% -type fld_elem() :: 0..(?P-1).
-type scalar() :: 0..(?N-1).
-type scalar() :: 0..(?N-1).
-define(D, 16#52036CEE2B6FFE738CC740797779E89800700A4D4141D8AB75EB4DCA135978A3).
-define(X, 16#216936D3CD6E53FEC0A4E231FDD6DC5C692CC7609525A7B2C9562D608F25D51A).
@ -34,6 +34,8 @@
-define(SUB(A, B), ((A - B + ?P) rem ?P)).
-define(DIV(A, B), f_div(A, B)).
-export_type([pt/0, scalar/0]).
-export([on_curve/1, p/0, n/0, pt_eq/2,
scalar_mul/2, scalar_mul_base/1,
scalar_mul_noclamp/2, scalar_mul_base_noclamp/1,
@ -77,14 +79,14 @@ n() -> ?N.
-define(TWO_POW_255_MINUS_1, 16#7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF).
-spec compress(P :: pt()) -> <<_:32>>.
-spec compress(P :: pt()) -> pt_compressed().
compress(<<_:32/binary>> = P) -> P;
compress({_, _, _, _} = P) -> compress(to_affine(P));
compress({X, Y}) ->
V = (Y band ?TWO_POW_255_MINUS_1) bor ((X band 1) bsl 255),
<<V:256/little>>.
-spec decompress(<<_:32>>) -> pt_hom_ext().
-spec decompress(pt_compressed()) -> pt_hom_ext().
decompress(<<Y0:256/little>>) ->
X0 = Y0 bsr 255,
Y = Y0 band ?TWO_POW_255_MINUS_1,

View File

@ -3,7 +3,7 @@
%%% Description : eddsa functionality - when possible compatible with enacl.
%%% Created : 19 Jan 2022 by Hans Svensson
-module(ecu_eddsa).
-vsn("0.1.0").
-vsn("1.0.0").
-export([sign_keypair/0,
sign_seed_keypair/1,
@ -17,23 +17,22 @@
%%
%% The keypair is returned as a map with keys 'public' and 'secret'.
%% @end
-spec sign_keypair() -> #{ atom() => binary() }.
-spec sign_keypair() -> #{ public => binary(), secret => binary() }.
sign_keypair() ->
Secret = crypto:strong_rand_bytes(32),
<<Seed:32/bytes, _/binary>> = crypto:hash(sha512, Secret),
<<Seed:32/binary, _/binary>> = crypto:hash(sha512, Secret),
Pub = ecu_ed25519:scalar_mul_base(Seed),
Pub = ecu_ed25519:compress(ecu_ed25519:scalar_mul_base(Seed)),
#{public => Pub, secret => <<Secret:32/binary, Pub:32/binary>>}.
%% @doc sign_seed_keypair/1 computes the signing keypair from a seed.
%%
%% The keypair is returned as a map with keys 'public' and 'secret'.
%% @end
-spec sign_seed_keypair(Seed :: <<_:32>>) -> #{ atom() => binary() }.
-spec sign_seed_keypair(Secret :: <<_:256>>) -> #{ public => binary(), secret => binary() }.
sign_seed_keypair(Secret) ->
<<Seed:32/bytes, _/binary>> = crypto:hash(sha512, Secret),
<<Seed:32/binary, _/binary>> = crypto:hash(sha512, Secret),
Pub = ecu_ed25519:compress(ecu_ed25519:scalar_mul_base(Seed)),
%% Pub = enacl:crypto_ed25519_scalarmult_base(Seed),
#{public => Pub, secret => <<Secret:32/binary, Pub:32/binary>>}.
@ -42,7 +41,7 @@ sign_seed_keypair(Secret) ->
%% Given a message `Msg' and a secret key `SK' the function will sign the
%% message and return a signed message `SM'.
%% @end
-spec sign(Msg :: iodata(), SK :: <<_:32>> | <<_:64>>) -> SM :: binary().
-spec sign(Msg :: iodata(), SK :: <<_:256>> | <<_:512>>) -> SM :: binary().
sign(Msg, SK) ->
BinMsg = iolist_to_binary(Msg),
Sig = sign_detached(Msg, SK),
@ -55,12 +54,12 @@ sign(Msg, SK) ->
%% `{error, failed_verification}' depending on the correctness of the
%% signature.
%% @end
-spec sign_open(SMsg :: binary(), PK :: <<_:32>>) ->
-spec sign_open(SMsg :: binary(), PK :: <<_:256>>) ->
{ok, Msg :: binary()} | {error, failed_verification}.
sign_open(<<Sig:64/binary, BinMsg/binary>>, PK) ->
<<R:32/bytes, Ss:32/bytes>> = Sig,
<<R:32/binary, Ss:32/binary>> = Sig,
Ks0 = crypto:hash(sha512, <<R/bytes, PK/bytes, BinMsg/bytes>>),
Ks0 = crypto:hash(sha512, <<R/binary, PK/binary, BinMsg/binary>>),
Ks = ecu_ed25519:scalar_reduce(Ks0),
LHS = ecu_ed25519:scalar_mul_base_noclamp(Ss),
@ -78,7 +77,7 @@ sign_open(<<Sig:64/binary, BinMsg/binary>>, PK) ->
%% Given a message `Msg' and a secret key `SK' the function will compute the
%% digital signature `Sig'.
%% @end
-spec sign_detached(Msg :: iodata(), SK :: <<_:32>>) -> Sig :: binary().
-spec sign_detached(Msg :: iodata(), SK :: <<_:256>> | <<_:512>>) -> Sig :: binary().
sign_detached(Msg, SK) ->
BinMsg = iolist_to_binary(Msg),
<<Secret:32/binary, _/binary>> = SK,
@ -120,12 +119,12 @@ sign_detached(Msg, SK) ->
%% function computes true iff the `Sig' is valid for `Msg' and `PK'; and,
%% false otherwise.
%% @end
-spec sign_verify_detached(Sig :: <<_:64>>, Msg :: iodata(), PK :: <<_:32>>) -> boolean().
-spec sign_verify_detached(Sig :: <<_:512>>, Msg :: iodata(), PK :: <<_:256>>) -> boolean().
sign_verify_detached(Sig, Msg, PK) ->
BinMsg = iolist_to_binary(Msg),
<<R:32/bytes, Ss:32/bytes>> = Sig,
<<R:32/binary, Ss:32/binary>> = Sig,
Ks0 = crypto:hash(sha512, <<R/bytes, PK/bytes, BinMsg/bytes>>),
Ks0 = crypto:hash(sha512, <<R/binary, PK/binary, BinMsg/binary>>),
Ks = ecu_ed25519:scalar_reduce(Ks0),
LHS = ecu_ed25519:scalar_mul_base_noclamp(Ss),
@ -137,5 +136,5 @@ sign_verify_detached(Sig, Msg, PK) ->
%% Clamp a 32-byte little-endian integer - i.e clear the lowest three bits
%% of the first byte and clear the highest and set the second highest of
%% the last byte (i.e. making it divisible by 8 and
clamp(<<B0:8, B1_30:30/bytes, B31:8>>) ->
<<(B0 band 16#f8):8, B1_30/bytes, ((B31 band 16#7f) bor 16#40):8>>.
clamp(<<B0:8, B1_30:30/binary, B31:8>>) ->
<<(B0 band 16#f8):8, B1_30/binary, ((B31 band 16#7f) bor 16#40):8>>.

View File

@ -3,7 +3,7 @@
%%% Description : Misc. functionality
%%% Created : 13 Jan 2022 by Hans Svensson
-module(ecu_misc).
-vsn("0.1.0").
-vsn("1.0.0").
-export([eea/2, exp_mod/3,
hex_to_bin/1, bin_to_hex/1,

View File

@ -4,7 +4,7 @@
%%% Just for usage when speed isn't crucial...
%%% Created : 22 Dec 2021 by Hans Svensson
-module(ecu_secp256k1).
-vsn("0.1.0").
-vsn("1.0.0").
-define(P, 16#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F).
-define(A, 16#00).

View File

@ -20,16 +20,16 @@ eth_sign_verify_test() ->
Data = [{Pr, ecu_crypto:private_to_short(ethereum, Pr), M} || {Pr, M} <- Data0],
Test = fun(PrivK, PubK, MsgHash) ->
Sig = ecu_crypto:eth_sign(MsgHash, PrivK),
?assertEqual(PubK, ecu_crypto:eth_recover(MsgHash, Sig))
Sig = ecu_crypto:eth_msg_sign(MsgHash, PrivK),
?assertEqual(PubK, ecu_crypto:eth_msg_recover(MsgHash, Sig))
end,
{T, _} = timer:tc(fun() -> [ Test(Pr, Pu, M) || {Pr, Pu, M} <- Data ] end),
?debugFmt("Average time for eth_sign+eth_recovery: ~.3f ms", [(T / 1000) / length(Data)]).
?debugFmt("Average time for eth_msg_sign+eth_msg_recovery: ~.3f ms", [(T / 1000) / length(Data)]).
recover_test() ->
<<R:256, S:256>> = ecu_misc:hex_to_bin("a5f270865420c8595128cf7132dcedb1221abf89286f926d067dff2fa59347c07a0fd06e8b4a567b0628a01d5398480a49c540c0cbd9980abd08cf3818f25e2e"),
%% <<Priv:256>> = hex_to_bin("9a4a5c038e7ce00f0ad216894afc00de6b41bbca1d4d7742104cb9f078c6d2df"),
%% <<Z:256>> = hex_to_bin("4a5c5d454721bbbb25540c3317521e71c373ae36458f960d2ad46ef088110e95"), %% MsgHash
ShortPub = ecu_misc:hex_to_bin("E53e2125F377D5c62a1FfbfEEB89A0826E9dE54C"),
?assertEqual(ShortPub, ecu_crypto:eth_recover(<<"test">>, <<28:8, R:256, S:256>>)).
?assertEqual(ShortPub, ecu_crypto:eth_msg_recover(<<"test">>, <<28:8, R:256, S:256>>)).

View File

@ -1,17 +1,17 @@
{a_email,"zxq9@zxq9.com"}.
{author,"Craig Everett"}.
{c_email,[]}.
{a_email,"hanssv@gmail.com"}.
{author,"Hans Svensson"}.
{c_email,"hanssv@gmail.com"}.
{copyright,"Hans Svensson"}.
{deps,[]}.
{deps,[{"otpr","sha3",{0,1,3}}]}.
{desc,"Helper functions for cryptography, mostly related to elliptic curves."}.
{file_exts,[]}.
{key_name,none}.
{license,"MIT"}.
{modules,[]}.
{name,"Elliptic Curve Utils"}.
{package_id,{"otpr","ec_utils",{0,1,0}}}.
{package_id,{"otpr","ec_utils",{1,0,0}}}.
{prefix,none}.
{repo_url,"https://github.com/zxq9/ec_utils"}.
{tags,[]}.
{repo_url,"https://github.com/hanssv/ec_utils"}.
{tags,["cryptography","elliptic curve","crypto","enacl"]}.
{type,lib}.
{ws_url,"https://github.com/hanssv/ec_utils"}.