Add some Ethereum crypto functionality
This commit is contained in:
parent
06f0f0aa79
commit
68b4456469
@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Basic operations (scalar arithmetics, point addition, scalar multiplication)
|
- Basic operations (scalar arithmetics, point addition, scalar multiplication)
|
||||||
for secp256k1 in pure Erlang code
|
for secp256k1 in pure Erlang code
|
||||||
- ecdsa sign/verify
|
- ecdsa sign/verify
|
||||||
|
- Some crypto (Ethereum) specific operations ecrecovery/ecverify, etc.
|
||||||
|
|
||||||
|
|
||||||
[Unreleased]: https://github.com/hanssv/ec_utils/compare/v0.1.0...HEAD
|
[Unreleased]: https://github.com/hanssv/ec_utils/compare/v0.1.0...HEAD
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
{erl_opts, [debug_info]}.
|
{erl_opts, [debug_info]}.
|
||||||
{deps, []}.
|
{deps,
|
||||||
|
[{sha3, {git, "https://github.com/aeternity/erlang-sha3", {ref, "b5f27a2"}}}]}.
|
||||||
|
12
rebar.lock
Normal file
12
rebar.lock
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{"1.2.0",
|
||||||
|
[{<<"hex2bin">>,{pkg,<<"hex2bin">>,<<"1.0.0">>},1},
|
||||||
|
{<<"sha3">>,
|
||||||
|
{git,"https://github.com/aeternity/erlang-sha3",
|
||||||
|
{ref,"b5f27a29ba1179e5907c50d7ec7aa79b2857e981"}},
|
||||||
|
0}]}.
|
||||||
|
[
|
||||||
|
{pkg_hash,[
|
||||||
|
{<<"hex2bin">>, <<"AAC26EAB998AE80EACEE1C7607C629AB503EBF77A62B9242BAE2B94D47DCB71E">>}]},
|
||||||
|
{pkg_hash_ext,[
|
||||||
|
{<<"hex2bin">>, <<"E7012D1D9AADD26E680F0983D26FB8923707F05FAC9688F19F530FA3795E716F">>}]}
|
||||||
|
].
|
60
src/ecu_crypto.erl
Normal file
60
src/ecu_crypto.erl
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
%%% File : ecu_crypto.erl
|
||||||
|
%%% Author : Hans Svensson
|
||||||
|
%%% Description :
|
||||||
|
%%% Created : 13 Jan 2022 by Hans Svensson
|
||||||
|
-module(ecu_crypto).
|
||||||
|
|
||||||
|
-export([private_to_short/2, public_to_short/2,
|
||||||
|
eth_sign/2, eth_recover/2, eth_verify/3, eth_msg_hash/1,
|
||||||
|
keccak256/1]).
|
||||||
|
|
||||||
|
private_to_short(bitcoin, PrivateKey) ->
|
||||||
|
public_to_short(bitcoin, aeu_ecdsa:private_to_public(secp256k1, PrivateKey));
|
||||||
|
private_to_short(ethereum, <<PrivateKey:256>>) ->
|
||||||
|
public_to_short(ethereum, ecu_secp256k1:scalar_mul_base(PrivateKey)).
|
||||||
|
|
||||||
|
public_to_short(bitcoin, PubKey = <<_:33/bytes>>) ->
|
||||||
|
crypto:hash(ripemd160, crypto:hash(sha256, PubKey));
|
||||||
|
public_to_short(bitcoin, PubKey) ->
|
||||||
|
crypto:hash(ripemd160, crypto:hash(sha256, ecu_secp256k1:compress(PubKey)));
|
||||||
|
public_to_short(ethereum, PubKey) ->
|
||||||
|
case PubKey of
|
||||||
|
<<_:33/bytes>> -> public_to_short(ethereum, ecu_secp256k1:decompress(PubKey));
|
||||||
|
<<4:8, X:256, Y:256>> -> public_to_short(ethereum, {X, Y});
|
||||||
|
{X, Y} ->
|
||||||
|
<<_:12/bytes, ShortPub:20/bytes>> = keccak256(<<X:256, Y:256>>),
|
||||||
|
ShortPub
|
||||||
|
end.
|
||||||
|
|
||||||
|
eth_sign(Msg, PrivateKey = <<_:32/bytes>>) ->
|
||||||
|
{BaseSig, YVal} = ecu_ecdsa:sign_secp256k1(eth_msg_hash(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),
|
||||||
|
<<E:256>> = MsgHash,
|
||||||
|
<<V:8, R:256, S:256>> = Sig,
|
||||||
|
Z = E rem ecu_secp256k1:n(),
|
||||||
|
RInv = ecu_secp256k1:s_inv(R),
|
||||||
|
Rd = ecu_secp256k1:decompress(<<(V - 27 + 2):8, R:256>>),
|
||||||
|
[P1, P2] =
|
||||||
|
ecu_misc:pcomp(
|
||||||
|
[fun() -> ecu_secp256k1:scalar_mul(ecu_secp256k1:s_mul(RInv, S), Rd) end,
|
||||||
|
fun() -> ecu_secp256k1:scalar_mul_base(ecu_secp256k1:s_mul(RInv,Z)) end]),
|
||||||
|
|
||||||
|
{X, Y} = ecu_secp256k1:p_add(P1, ecu_secp256k1:p_neg(P2)),
|
||||||
|
<<_:12/bytes, RPub:20/bytes>> = keccak256(<<X:256, Y:256>>),
|
||||||
|
RPub.
|
||||||
|
|
||||||
|
eth_verify(Msg, PublicKey, Sig) ->
|
||||||
|
PublicKey == eth_recover(Msg, Sig).
|
||||||
|
|
||||||
|
eth_msg_hash(Msg0) ->
|
||||||
|
Msg = ["\x19Ethereum Signed Message:\n", integer_to_list(byte_size(Msg0)), Msg0],
|
||||||
|
keccak256(iolist_to_binary(Msg)).
|
||||||
|
|
||||||
|
keccak256(Bin) ->
|
||||||
|
sha3:hash(256, Bin).
|
@ -4,7 +4,9 @@
|
|||||||
%%% Created : 13 Jan 2022 by Hans Svensson
|
%%% Created : 13 Jan 2022 by Hans Svensson
|
||||||
-module(ecu_misc).
|
-module(ecu_misc).
|
||||||
|
|
||||||
-export([eea/2, pcomp/1]).
|
-export([eea/2,
|
||||||
|
hex_to_bin/1, bin_to_hex/1,
|
||||||
|
pcomp/1]).
|
||||||
|
|
||||||
%% Extended Euclidean Algorithm
|
%% Extended Euclidean Algorithm
|
||||||
eea(A, B) when ((A < 1) or (B < 1)) ->
|
eea(A, B) when ((A < 1) or (B < 1)) ->
|
||||||
@ -23,3 +25,17 @@ pcomp(Fs) ->
|
|||||||
Parent = self(),
|
Parent = self(),
|
||||||
Pids = [ spawn(fun() -> Parent ! {self(), F()} end) || F <- Fs ],
|
Pids = [ spawn(fun() -> Parent ! {self(), F()} end) || F <- Fs ],
|
||||||
[ receive {Pid, X} -> X after 500 -> error(timeout) end || Pid <- Pids ].
|
[ receive {Pid, X} -> X after 500 -> error(timeout) end || Pid <- Pids ].
|
||||||
|
|
||||||
|
%% Hex encode/decode
|
||||||
|
-spec hex_to_bin(Input :: string()) -> binary().
|
||||||
|
hex_to_bin(S) ->
|
||||||
|
hex_to_bin(S, []).
|
||||||
|
hex_to_bin([], Acc) ->
|
||||||
|
list_to_binary(lists:reverse(Acc));
|
||||||
|
hex_to_bin([X,Y|T], Acc) ->
|
||||||
|
{ok, [V], []} = io_lib:fread("~16u", [X,Y]),
|
||||||
|
hex_to_bin(T, [V | Acc]).
|
||||||
|
|
||||||
|
-spec bin_to_hex(Input :: binary()) -> string().
|
||||||
|
bin_to_hex(Bin) ->
|
||||||
|
lists:flatten([io_lib:format("~2.16.0B", [X]) || X <- binary_to_list(Bin)]).
|
||||||
|
35
test/crypto_tests.erl
Normal file
35
test/crypto_tests.erl
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
%%% File : crypto_tests.erl
|
||||||
|
%%% Author : Hans Svensson
|
||||||
|
%%% Description :
|
||||||
|
%%% Created : 12 Jan 2022 by Hans Svensson
|
||||||
|
-module(crypto_tests).
|
||||||
|
|
||||||
|
-compile([export_all, nowarn_export_all]).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
|
||||||
|
gen_ecdsa_secp256k1_privkey() ->
|
||||||
|
<<P0:256>> = crypto:strong_rand_bytes(32),
|
||||||
|
P = (P0 rem (ecu_secp256k1:n() - 1)) + 1,
|
||||||
|
<<P:256>>.
|
||||||
|
|
||||||
|
eth_sign_verify_test() ->
|
||||||
|
Data0 = [ {gen_ecdsa_secp256k1_privkey(), crypto:strong_rand_bytes(32)} || _ <- lists:seq(1, 10) ],
|
||||||
|
|
||||||
|
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))
|
||||||
|
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)]).
|
||||||
|
|
||||||
|
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>>)).
|
Loading…
x
Reference in New Issue
Block a user