diff --git a/eqc/ed25519_eqc.erl b/eqc/ed25519_eqc.erl index f24ea08..17d60f2 100644 --- a/eqc/ed25519_eqc.erl +++ b/eqc/ed25519_eqc.erl @@ -99,18 +99,28 @@ prop_scalar_mul_noclamp() -> equals(E, ecu_ed25519:compress(P)) end). -xprop_scalar_enacl() -> - ?FORALL(S, gen_scalar(), +prop_arithmetics1() -> + ?FORALL({P1, P2, P3}, {gen_point(), gen_point(), gen_point()}, begin - _P = enacl:crypto_ed25519_scalarmult_base(S), - true + Res1 = ecu_ed25519:p_add(P1, ecu_ed25519:p_add(P2, P3)), + Res2 = ecu_ed25519:p_add(ecu_ed25519:p_add(P1, P2), P3), + equal_pts(Res1, Res2) end). -xprop_scalar_ecu() -> - ?FORALL(S, gen_scalar(), +prop_arithmetics2() -> + ?FORALL({P1, P2}, {gen_point(), gen_point()}, begin - _P = ecu_ed25519:scalar_mul_base(S), - true + Res1 = ecu_ed25519:p_sub(ecu_ed25519:p_add(P1, P2), P2), + equal_pts(P1, Res1) + end). + +prop_dbl() -> + ?FORALL(P, gen_point(), + begin + A = ecu_ed25519:p_add(P, P), + B = ecu_ed25519:p_dbl(P), + ?WHENFAIL(eqc:format("~p\n /=\n~p\n", [ecu_ed25519:to_affine(A), ecu_ed25519:to_affine(B)]), + ecu_ed25519:pt_eq(A, B)) end). even(<>) -> <>. diff --git a/eqc/eddsa_eqc.erl b/eqc/eddsa_eqc.erl new file mode 100644 index 0000000..ac488c4 --- /dev/null +++ b/eqc/eddsa_eqc.erl @@ -0,0 +1,70 @@ +%%% Author : Hans Svensson +%%% Description : +%%% Created : 19 Jan 2022 by Hans Svensson +-module(eddsa_eqc). + +-compile([export_all, nowarn_export_all]). + +-include_lib("eqc/include/eqc.hrl"). + +%% Let's use enacl/libsodium as the oracle +-define(N, 16#1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED). + +-define(KP, #{public => <<161,254,128,151,126,253,139,99,47,29,229,140,67,224,50,78,70,156,225,182,242,171,89,114,47,163,254,192,59,35,148,234>>, secret => <<102,73,74,74,245,130,53,139,149,247,67,138,211,86,72,227,20,43,6,39,134,133,215,10,3,159,123,152,144,208,176,138,161,254,128,151,126,253,139,99,47,29,229,140,67,224,50,78,70,156,225,182,242,171,89,114,47,163,254,192,59,35,148,234>>}). + + +gen_large_n() -> + ?LET(<>, binary(64), 1 + (X rem (?N - 1))). + +gen_scalar() -> + ?LET(N, gen_large_n(), <>). + +gen_point() -> + ?LET(S, gen_scalar(), enacl:crypto_ed25519_scalarmult_base_noclamp(S)). + +prop_keypair_seed() -> + ?FORALL(Seed, binary(32), + begin + KP1 = enacl:sign_seed_keypair(Seed), + KP2 = ecu_eddsa:sign_seed_keypair(Seed), + equals(KP1, KP2) + end). + +prop_sign() -> + ?FORALL({Priv, Msg}, {binary(32), binary(48)}, + begin + #{secret := SK} = enacl:sign_seed_keypair(Priv), + Sig1 = enacl:sign(Msg, SK), + Sig2 = ecu_eddsa:sign(Msg, SK), + equals(Sig1, Sig2) + end). + +prop_sign_open() -> + ?FORALL({Priv, Msg}, {noshrink(binary(32)), noshrink(binary(48))}, + begin + #{secret := SK, public := Pub} = enacl:sign_seed_keypair(Priv), + Sig = enacl:sign(Msg, SK), + Res1 = enacl:sign_open(Sig, Pub), + Res2 = ecu_eddsa:sign_open(Sig, Pub), + equals(Res1, Res2) + end). + +prop_sign_detached() -> + ?FORALL({Priv, Msg}, {binary(32), binary(48)}, + begin + #{secret := SK} = enacl:sign_seed_keypair(Priv), + Sig1 = enacl:sign_detached(Msg, SK), + Sig2 = ecu_eddsa:sign_detached(Msg, SK), + equals(Sig1, Sig2) + end). + +prop_sign_verify_detached() -> + ?FORALL({Priv, Msg}, {noshrink(binary(32)), noshrink(binary(48))}, + begin + #{secret := SK, public := Pub} = enacl:sign_seed_keypair(Priv), + Sig = enacl:sign_detached(Msg, SK), + Res1 = enacl:sign_verify_detached(Sig, Msg, Pub), + Res2 = ecu_eddsa:sign_verify_detached(Sig, Msg, Pub), + equals(Res1, Res2) + end). + diff --git a/rebar.config b/rebar.config index 2e7fc31..1f09316 100644 --- a/rebar.config +++ b/rebar.config @@ -7,6 +7,7 @@ {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, "01dd0c2"}}}, + {ecrecover, {git, "https://github.com/aeternity/ecrecover.git", {ref, "74b7816"}}}]}]}, {eqc, [{deps, [{enacl, {git, "https://github.com/aeternity/enacl.git", {ref, "01dd0c2"}}}]}]} ]}. diff --git a/src/ec_utils.app.src b/src/ec_utils.app.src index af4fa2f..d3decf0 100644 --- a/src/ec_utils.app.src +++ b/src/ec_utils.app.src @@ -4,7 +4,7 @@ {registered, []}, {applications, [kernel, stdlib]}, {env,[]}, - {modules, [ecu_secp256k1, ecu_misc]}, + {modules, [ecu_secp256k1, ecu_ed25519, ecu_ecdsa, ecu_eddsa, ecu_crypto, ecu_misc]}, {licenses, ["MIT"]}, {links, []} ]}. diff --git a/src/ecu_ed25519.erl b/src/ecu_ed25519.erl index 9c0a9f8..9aaa47b 100644 --- a/src/ecu_ed25519.erl +++ b/src/ecu_ed25519.erl @@ -36,7 +36,8 @@ -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, - p_add/2, + scalar_reduce/1, + p_add/2, p_sub/2, p_neg/1, p_dbl/1, compress/1, decompress/1, f_add/2, f_mul/2, f_sub/2, f_div/2, f_inv/1, s_add/2, s_mul/2, s_sub/2, s_div/2, s_inv/1]). @@ -92,12 +93,18 @@ decompress(<>) -> false -> to_ext_hom({?P - X, Y}) end. +p_neg({X, Y}) -> {?P - X, Y}; +p_neg({X, Y, Z, T}) -> {?P - X, Y, Z, ?P - T}; +p_neg(P) -> p_neg(to_ext_hom(P)). + +p_sub(P1, P2) -> p_add(P1, p_neg(P2)). + -spec p_add(X :: pt(), Y :: pt()) -> pt_hom_ext(). p_add({X1, Y1, Z1, T1}, {X2, Y2, Z2, T2}) -> A = ?MUL(?SUB(Y1, X1), ?SUB(Y2, X2)), B = ?MUL(?ADD(Y1, X1), ?ADD(Y2, X2)), - C = ?MUL(?MUL(T1, T2), ?MUL(2, ?D)), - D = ?MUL(2, ?MUL(Z1, Z2)), + C = ?MUL(?MUL(T1, T2), 2 * ?D), + D = ?MUL(2 * Z1, Z2), E = ?SUB(B, A), F = ?SUB(D, C), G = ?ADD(D, C), @@ -106,9 +113,23 @@ p_add({X1, Y1, Z1, T1}, {X2, Y2, Z2, T2}) -> p_add(P1, P2) -> p_add(to_ext_hom(P1), to_ext_hom(P2)). +p_dbl({X, Y, Z, _T}) -> + A = ?MUL(X, X), + B = ?MUL(Y, Y), + C = ?MUL(2 * Z, Z), + D = ?P - A, + XY = X + Y, + E = ?SUB(?MUL(XY, XY), ?ADD(A, B)), + G = ?ADD(D, B), + F = ?SUB(G, C), + H = ?SUB(D, B), + {?MUL(E, F), ?MUL(G, H), ?MUL(F, G), ?MUL(E, H)}; +p_dbl(P) -> + p_dbl(to_ext_hom(P)). + -spec scalar_mul_base(Scalar :: scalar() | binary()) -> pt_hom_ext(). scalar_mul_base(<>) -> - scalar_mul_base(K); + scalar_mul_(clamp(K), ?GE); scalar_mul_base(K) when is_integer(K), K >= 0, K < ?N -> scalar_mul_(clamp(K), ?GE). @@ -152,21 +173,24 @@ f_inv(A) -> f_pow(A, ?P - 2). %% Arithmetics in curve group order N -s_add(A, B) -> (A + B) rem ?N. -s_mul(A, B) -> (A * B) rem ?N. -s_sub(A, B) -> (A - B + ?N) rem ?N. +s_add(<>, <>) -> <<((A + B) rem ?N):256/little>>. +s_mul(<>, <>) -> <<((A * B) rem ?N):256/little>>. +s_sub(<>, <>) -> <<((A - B + ?N) rem ?N):256/little>>. s_div(A, B) -> s_mul(A, s_inv(B)). -s_inv(A) -> +s_inv(<>) -> {1, S, _T} = ecu_misc:eea(A, ?N), - (S + ?N) rem ?N. + <<((S + ?N) rem ?N):256/little>>. + +scalar_reduce(<>) -> + <<(S rem ?N):256/little>>. %% --- internal functions scalar_mul_(0, _P) -> {0, 1, 1, 0}; scalar_mul_(1, P) -> P; scalar_mul_(K, P) -> case K rem 2 of - 0 -> scalar_mul_(K div 2, p_add(P, P)); + 0 -> scalar_mul_(K div 2, p_dbl(P)); 1 -> p_add(P, scalar_mul_(K - 1, P)) end. diff --git a/src/ecu_eddsa.erl b/src/ecu_eddsa.erl new file mode 100644 index 0000000..4d383a5 --- /dev/null +++ b/src/ecu_eddsa.erl @@ -0,0 +1,140 @@ +%%% File : ecu_eddsa.erl +%%% Author : Hans Svensson +%%% Description : eddsa functionality - when possible compatible with enacl. +%%% Created : 19 Jan 2022 by Hans Svensson +-module(ecu_eddsa). + +-export([sign_keypair/0, + sign_seed_keypair/1, + sign/2, + sign_open/2, + sign_detached/2, + sign_verify_detached/3]). + + +%% @doc sign_keypair/0 creates a keypair for signing +%% +%% The keypair is returned as a map with keys 'public' and 'secret'. +%% @end +-spec sign_keypair() -> #{ atom() => binary() }. +sign_keypair() -> + Secret = crypto:strong_rand_bytes(32), + <> = crypto:hash(sha512, Secret), + + Pub = ecu_ed25519:scalar_mul_base(Seed), + #{public => Pub, secret => <>}. + +%% @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() }. +sign_seed_keypair(Secret) -> + <> = crypto:hash(sha512, Secret), + Pub = ecu_ed25519:compress(ecu_ed25519:scalar_mul_base(Seed)), +%% Pub = enacl:crypto_ed25519_scalarmult_base(Seed), + + #{public => Pub, secret => <>}. + +%% @doc sign/2 signs a message with private/secret key. +%% +%% 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(). +sign(Msg, SK) -> + BinMsg = iolist_to_binary(Msg), + Sig = sign_detached(Msg, SK), + <>. + +%% @doc sign_open/2 opens a signed message. +%% +%% Given a signed message `SMsg' and a public key `PK', verify that the +%% message has the right signature. Returns either `{ok, Msg}' or +%% `{error, failed_verification}' depending on the correctness of the +%% signature. +%% @end +-spec sign_open(SMsg :: binary(), PK :: <<_:32>>) -> + {ok, Msg :: binary()} | {error, failed_verification}. +sign_open(<>, PK) -> + <> = Sig, + + Ks0 = crypto:hash(sha512, <>), + Ks = ecu_ed25519:scalar_reduce(Ks0), + + LHS = ecu_ed25519:scalar_mul_base_noclamp(Ss), + + RHS = ecu_ed25519:p_add(R, ecu_ed25519:scalar_mul_noclamp(Ks, PK)), + + case ecu_ed25519:pt_eq(LHS, RHS) of + true -> {ok, BinMsg}; + false -> {error, failed_verification} + end. + +%% @doc sign_detached/2 computes the signature of a message with private/secret +%% key. +%% +%% 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(). +sign_detached(Msg, SK) -> + BinMsg = iolist_to_binary(Msg), + <> = SK, + + %% Grab the Seed, also referred to as 'a' (clamped) and the Prefix + <> = crypto:hash(sha512, Secret), + Seed = clamp(Seed0), + + Pub = case SK of + <<_:32/binary, Pub0:32/binary>> -> + Pub0; + _ -> + ecu_ed25519:compress(ecu_ed25519:scalar_mul_base(Seed0)) + end, + + %% Compute r = H(prefix || msg) + Rs0 = crypto:hash(sha512, <>), + Rs = ecu_ed25519:scalar_reduce(Rs0), + + %% Compute R = s⋅G (and since we want the computation to be invertible use + %% the 'noclamp' version). + R = ecu_ed25519:compress(ecu_ed25519:scalar_mul_base_noclamp(Rs)), + + %% Compute k = H(R' || Pub || msg) + Ks0 = crypto:hash(sha512, <>), + Ks = ecu_ed25519:scalar_reduce(Ks0), + + %% Compute s = (r + k * a) mod L + Ss = ecu_ed25519:s_add(Rs, ecu_ed25519:s_mul(Ks, Seed)), + + %% Form the signature {R, s} + <>. + + +%% @doc sign_verify_detached/3 verifies the given signature against the given +%% message for the given public key. +%% +%% Given a signature `Sig', a message `Msg', and a public key `PK', the +%% 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(). +sign_verify_detached(Sig, Msg, PK) -> + BinMsg = iolist_to_binary(Msg), + <> = Sig, + + Ks0 = crypto:hash(sha512, <>), + Ks = ecu_ed25519:scalar_reduce(Ks0), + + LHS = ecu_ed25519:scalar_mul_base_noclamp(Ss), + + RHS = ecu_ed25519:p_add(R, ecu_ed25519:scalar_mul_noclamp(Ks, PK)), + + ecu_ed25519:pt_eq(LHS, RHS). + +%% 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 band 16#f8):8, B1_30/bytes, ((B31 band 16#7f) bor 16#40):8>>. diff --git a/src/ecu_secp256k1.erl b/src/ecu_secp256k1.erl index 28267bb..185c681 100644 --- a/src/ecu_secp256k1.erl +++ b/src/ecu_secp256k1.erl @@ -38,9 +38,13 @@ p() -> ?P. n() -> ?N. +scalar_mul_base(<>) -> + scalar_mul(K, {?X, ?Y}); scalar_mul_base(K) -> scalar_mul(K, {?X, ?Y}). +scalar_mul(<>, P) -> + scalar_mul(K, P); scalar_mul(0, _P) -> {0, 0}; scalar_mul(1, P) -> diff --git a/test/benchmark_tests.erl b/test/benchmark_tests.erl new file mode 100644 index 0000000..3262d26 --- /dev/null +++ b/test/benchmark_tests.erl @@ -0,0 +1,140 @@ +%%% File : benchmark_tests.erl +%%% Author : Hans Svensson +%%% Description : +%%% Created : 20 Jan 2022 by Hans Svensson +-module(benchmark_tests). + +-compile([export_all, nowarn_export_all]). + +-include_lib("eunit/include/eunit.hrl"). + +gen_scalar() -> + <> = crypto:strong_rand_bytes(32), + 1 + X rem (ecu_ed25519:n() - 1). + +bench_point_add_test() -> + Pts = [ enacl:crypto_ed25519_scalarmult_base(<<(gen_scalar()):256/little>>) || _ <- lists:seq(1, 100) ], + + PtsEnacl0 = lists:zip(Pts, tl(Pts) ++ [hd(Pts)]), + PtsEd255190 = [ {ecu_ed25519:to_ext_hom(P1), ecu_ed25519:to_ext_hom(P2)} || {P1, P2} <- lists:zip(Pts, tl(Pts) ++ [hd(Pts)]) ], +%% PtsEd255190 = lists:zip(Pts, tl(Pts) ++ [hd(Pts)]), + + PtsEnacl = lists:append(lists:duplicate(1000, PtsEnacl0)), + PtsEd25519 = lists:append(lists:duplicate(100, PtsEd255190)), + + {TimeEnacl, _} = timer:tc(fun() -> [enacl:crypto_ed25519_add(P1, P2) || {P1, P2} <- PtsEnacl], ok end), + {TimeEd25519, _} = timer:tc(fun() -> [ecu_ed25519:p_add(P1, P2) || {P1, P2} <- PtsEd25519], ok end), +%% {TimeEd25519, _} = timer:tc(fun() -> [ecu_ed25519:compress(ecu_ed25519:p_add(P1, P2)) || {P1, P2} <- PtsEd25519], ok end), + + ?debugFmt("", []), + stats("Point add", "enacl/libsodium", length(PtsEnacl), TimeEnacl), + stats("Point add", "ecu_ed25519 ", length(PtsEd25519), TimeEd25519), + diff(TimeEnacl / length(PtsEnacl), TimeEd25519 / length(PtsEd25519)), + ok. + +bench_scalar_mul_base_test() -> + Scalars0 = [ <<(gen_scalar()):256/little>> || _ <- lists:seq(1, 100) ], + ScalarsSecp = lists:append(lists:duplicate(1, Scalars0)), + ScalarsEnacl = lists:append(lists:duplicate(100, Scalars0)), + ScalarsEd25519 = lists:append(lists:duplicate(30, Scalars0)), + + {TimeSecp, _} = timer:tc(fun() -> [ecu_secp256k1:scalar_mul_base(S) || S <- ScalarsSecp], ok end), + {TimeEnacl, _} = timer:tc(fun() -> [enacl:crypto_ed25519_scalarmult_base(S) || S <- ScalarsEnacl], ok end), + {TimeEd25519, _} = timer:tc(fun() -> [ecu_ed25519:scalar_mul_base(S) || S <- ScalarsEd25519], ok end), + + ?debugFmt("", []), + stats("Scalar mul base", "ecu_secp256k1 ", length(ScalarsSecp), TimeSecp), + stats("Scalar mul base", "enacl/libsodium", length(ScalarsEnacl), TimeEnacl), + stats("Scalar mul base", "ecu_ed25519 ", length(ScalarsEd25519), TimeEd25519), + diff(TimeEnacl / length(ScalarsEnacl), TimeEd25519 / length(ScalarsEd25519)), + ok. + +bench_scalar_mul_test() -> + Scalars0 = [ <<(gen_scalar()):256/little>> || _ <- lists:seq(1, 100) ], + ScalarsSecp = lists:append(lists:duplicate(1, Scalars0)), + ScalarsEnacl = lists:append(lists:duplicate(100, Scalars0)), + ScalarsEd25519 = lists:append(lists:duplicate(10, Scalars0)), + + Test = fun(F, P0, Ss) -> lists:foldl(fun(S, P) -> F(S, P) end, P0, Ss) end, + + {TimeSecp, _} = timer:tc(fun() -> Test(fun ecu_secp256k1:scalar_mul/2, ecu_secp256k1:scalar_mul_base(hd(ScalarsSecp)), tl(ScalarsSecp)) end), + {TimeEnacl, _} = timer:tc(fun() -> Test(fun enacl:crypto_ed25519_scalarmult/2, enacl:crypto_ed25519_scalarmult_base(hd(ScalarsEnacl)), tl(ScalarsEnacl)) end), + {TimeEd25519, _} = timer:tc(fun() -> Test(fun ecu_ed25519:scalar_mul/2, ecu_ed25519:scalar_mul_base(hd(ScalarsEd25519)), tl(ScalarsEd25519)) end), + + ?debugFmt("", []), + stats("Scalar mul", "ecu_secp256k1 ", length(ScalarsSecp), TimeSecp), + stats("Scalar mul", "enacl/libsodium", length(ScalarsEnacl), TimeEnacl), + stats("Scalar mul", "ecu_ed25519 ", length(ScalarsEd25519), TimeEd25519), + diff(TimeEnacl / length(ScalarsEnacl), TimeEd25519 / length(ScalarsEd25519)), + ok. + +bench_sign_test() -> + KeyPairs = [ enacl:sign_keypair() || _ <- lists:seq(1, 10) ], + Messages = [ crypto:strong_rand_bytes(X) || X <- lists:seq(20, 49) ], + + Data = [ {K, Msg} || {Msg, K} <- lists:zip(Messages, lists:append(lists:duplicate(3, KeyPairs))) ], + + DataEnacl = lists:append(lists:duplicate(1000, Data)), + DataEd25519 = lists:append(lists:duplicate(20, Data)), + + {TimeEnacl, _} = timer:tc(fun() -> [enacl:sign_detached(Msg, maps:get(secret, K)) || {K, Msg} <- DataEnacl], ok end), + {TimeEd25519, _} = timer:tc(fun() -> [ecu_eddsa:sign_detached(Msg, maps:get(secret, K)) || {K, Msg} <- DataEd25519], ok end), + + + ?debugFmt("", []), + stats("Message sign", "enacl/libsodium", length(DataEnacl), TimeEnacl), + stats("Message sign", "ecu_ed25519 ", length(DataEd25519), TimeEd25519), + diff(TimeEnacl / length(DataEnacl), TimeEd25519 / length(DataEd25519)), + ok. + +bench_verify_test() -> + KeyPairs = [ enacl:sign_keypair() || _ <- lists:seq(1, 10) ], + Messages = [ crypto:strong_rand_bytes(X) || X <- lists:seq(20, 49) ], + + Data = [ {K, Msg, enacl:sign_detached(Msg, maps:get(secret, K))} + || {Msg, K} <- lists:zip(Messages, lists:append(lists:duplicate(3, KeyPairs))) ], + + DataEnacl = lists:append(lists:duplicate(1000, Data)), + DataEd25519 = lists:append(lists:duplicate(20, Data)), + + {TimeEnacl, _} = timer:tc(fun() -> [enacl:sign_verify_detached(Sig, Msg, maps:get(public, K)) || {K, Msg, Sig} <- DataEnacl], ok end), + {TimeEd25519, _} = timer:tc(fun() -> [ecu_eddsa:sign_verify_detached(Sig, Msg, maps:get(public, K)) || {K, Msg, Sig} <- DataEd25519], ok end), + + + ?debugFmt("", []), + stats("Message verify", "enacl/libsodium", length(DataEnacl), TimeEnacl), + stats("Message verify", "ecu_ed25519 ", length(DataEd25519), TimeEd25519), + diff(TimeEnacl / length(DataEnacl), TimeEd25519 / length(DataEd25519)), + ok. + +gen_ecdsa_secp256k1_privkey() -> + <> = crypto:strong_rand_bytes(32), + P = (P0 rem (ecu_secp256k1:n() - 1)) + 1, + <>. + +bench_ecverify_test() -> + Data0 = [ {gen_ecdsa_secp256k1_privkey(), crypto:strong_rand_bytes(32)} || _ <- lists:seq(1, 10) ], + Data1 = [ {Pr, Msg, ecu_crypto:private_to_short(ethereum, Pr), ecu_crypto:eth_sign(Msg, Pr)} || {Pr, Msg} <- Data0 ], + + RustData = lists:append(lists:duplicate(100, Data1)), + ECUData = lists:append(lists:duplicate(5, Data1)), + + %% ensure loading + ecrecover:recover(<<0:256>>, <<123:520>>), + + {TimeRust, _} = timer:tc(fun() -> [ ecrecover:recover(Msg, Sig) || {_, Msg, _, Sig} <- RustData ], ok end), + {TimeECU, _} = timer:tc(fun() -> [ ecu_crypto:eth_recover(Msg, Sig) || {_, Msg, _, Sig} <- ECUData ], ok end), + + ?debugFmt("", []), + stats("Message verify", "ecrecover (Rust)", length(RustData), TimeRust), + stats("Message verify", "ecu_crypto ", length(ECUData), TimeECU), + diff(TimeRust / length(RustData), TimeECU / length(ECUData)), + ok. + +stats(What, Who, N, T) -> + ?debugFmt("~s with ~s ~.2f us/op", [What, Who, T / N]). + +diff(T1, T2) when T1 > T2 -> + diff(T2, T1); +diff(T1, T2) -> + ?debugFmt("Speed difference x~.2f", [T2 / T1]).