From 0dad93cf3c5390b630fe89ace4555e642f2ae1a0 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Sat, 7 Jan 2023 16:53:49 +0100 Subject: [PATCH] Better ecu_crypto API + ec_recover/2 --- src/ecu_crypto.erl | 35 +++++++++++++++++++++++++++++------ test/crypto_tests.erl | 8 ++++---- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/ecu_crypto.erl b/src/ecu_crypto.erl index c2a166e..74e7dfa 100644 --- a/src/ecu_crypto.erl +++ b/src/ecu_crypto.erl @@ -5,7 +5,9 @@ -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, + 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) -> @@ -26,15 +28,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, <>. -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>>) -> + <> = 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>>) -> <> = MsgHash, <> = Sig, Z = E rem ecu_secp256k1:n(), @@ -49,8 +69,11 @@ eth_recover(Msg, Sig = <<_:65/bytes>>) -> <<_:12/bytes, RPub:20/bytes>> = keccak256(<>), 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], diff --git a/test/crypto_tests.erl b/test/crypto_tests.erl index 0561901..0970a3d 100644 --- a/test/crypto_tests.erl +++ b/test/crypto_tests.erl @@ -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() -> <> = ecu_misc:hex_to_bin("a5f270865420c8595128cf7132dcedb1221abf89286f926d067dff2fa59347c07a0fd06e8b4a567b0628a01d5398480a49c540c0cbd9980abd08cf3818f25e2e"), %% <> = hex_to_bin("9a4a5c038e7ce00f0ad216894afc00de6b41bbca1d4d7742104cb9f078c6d2df"), %% <> = 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>>)).