From 06f0f0aa7975085ad9d61ab65928ac4d8d639043 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Thu, 13 Jan 2022 16:13:35 +0100 Subject: [PATCH] Add some EcDSA functionality --- CHANGELOG.md | 1 + src/ecu_ecdsa.erl | 53 ++++++++++++++++++++++++++++++++++++++++++++ src/ecu_misc.erl | 8 +++++-- test/ecdsa_tests.erl | 27 ++++++++++++++++++++++ 4 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 src/ecu_ecdsa.erl create mode 100644 test/ecdsa_tests.erl diff --git a/CHANGELOG.md b/CHANGELOG.md index 35e463f..2954334 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Basic operations (scalar arithmetics, point addition, scalar multiplication) for secp256k1 in pure Erlang code +- ecdsa sign/verify [Unreleased]: https://github.com/hanssv/ec_utils/compare/v0.1.0...HEAD diff --git a/src/ecu_ecdsa.erl b/src/ecu_ecdsa.erl new file mode 100644 index 0000000..f455b1e --- /dev/null +++ b/src/ecu_ecdsa.erl @@ -0,0 +1,53 @@ +%%% File : ecu_ecdsa.erl +%%% Author : Hans Svensson +%%% Description : ecdsa functionality +%%% Created : 13 Jan 2022 by Hans Svensson +-module(ecu_ecdsa). + +-export([sign/3, verify/4, + sign_secp256k1/2, + private_to_public/2]). + +private_to_public(secp256k1, <>) -> + ecu_secp256k1:compress(ecu_secp256k1:scalar_mul_base(PrivateKey)). + +sign(secp256k1, MsgHash = <<_:32/bytes>>, PrivateKey = <<_:32/bytes>>) -> + {Sig, _YVal} = sign_secp256k1(MsgHash, PrivateKey), + Sig. + +verify(secp256k1, MsgHash = <<_:32/bytes>>, PubKey = <<_:33/bytes>>, Sig = <<_:64/bytes>>) -> + verify(secp256k1, MsgHash, ecu_secp256k1:decompress(PubKey), Sig); +verify(secp256k1, MsgHash = <<_:32/bytes>>, PubKey = {_, _}, Sig = <<_:64/bytes>>) -> + <> = MsgHash, + <> = Sig, + Z = E rem ecu_secp256k1:n(), + W = ecu_secp256k1:s_inv(S), + [P1, P2] = ecu_misc:pcomp( + [fun() -> ecu_secp256k1:scalar_mul_base(ecu_secp256k1:s_mul(Z, W)) end, + fun() -> ecu_secp256k1:scalar_mul(ecu_secp256k1:s_mul(R, W), PubKey) end]), + {X, _Y} = ecu_secp256k1:p_add(P1, P2), + R == (X rem ecu_secp256k1:n()). + +sign_secp256k1(MsgHash = <<_:32/bytes>>, PrivateKey = <<_:32/bytes>>) -> + <> = MsgHash, + <> = PrivateKey, + Z = E rem ecu_secp256k1:n(), + K = pick_k(secp256k1), + {X, Y} = ecu_secp256k1:scalar_mul_base(K), + R = X rem ecu_secp256k1:n(), + S = ecu_secp256k1:s_mul(ecu_secp256k1:s_inv(K), + ecu_secp256k1:s_add(Z, ecu_secp256k1:s_mul(R, D))), + if R == 0 orelse S == 0 -> + sign(secp256k1, MsgHash, PrivateKey); + true -> + {<>, Y} + end. + +%% --- internal functions + +pick_k(secp256k1) -> + <> = crypto:strong_rand_bytes(32), + case K == 0 orelse K >= ecu_secp256k1:n() of + true -> pick_k(secp256k1); + false -> K + end. diff --git a/src/ecu_misc.erl b/src/ecu_misc.erl index 71ef230..7acdbd6 100644 --- a/src/ecu_misc.erl +++ b/src/ecu_misc.erl @@ -4,7 +4,7 @@ %%% Created : 13 Jan 2022 by Hans Svensson -module(ecu_misc). --export([eea/2]). +-export([eea/2, pcomp/1]). %% Extended Euclidean Algorithm eea(A, B) when ((A < 1) or (B < 1)) -> @@ -18,4 +18,8 @@ eea(G0, S0, T0, G1, S1, T1) -> Q = G0 div G1, eea(G1, S1, T1, G0 - (Q * G1), S0 - (Q * S1), T0 - (Q * T1)). - +%% Very rudimentary parallel computation... +pcomp(Fs) -> + Parent = self(), + Pids = [ spawn(fun() -> Parent ! {self(), F()} end) || F <- Fs ], + [ receive {Pid, X} -> X after 500 -> error(timeout) end || Pid <- Pids ]. diff --git a/test/ecdsa_tests.erl b/test/ecdsa_tests.erl new file mode 100644 index 0000000..1948fe8 --- /dev/null +++ b/test/ecdsa_tests.erl @@ -0,0 +1,27 @@ +%%% File : ecdsa_tests.erl +%%% Author : Hans Svensson +%%% Description : +%%% Created : 12 Jan 2022 by Hans Svensson +-module(ecdsa_tests). + +-compile([export_all, nowarn_export_all]). + +-include_lib("eunit/include/eunit.hrl"). + +gen_ecdsa_secp256k1_privkey() -> + <> = crypto:strong_rand_bytes(32), + P = (P0 rem (ecu_secp256k1:n() - 1)) + 1, + <>. + +ecdsa_sign_verify_secp256k1_test() -> + Data0 = [ {gen_ecdsa_secp256k1_privkey(), crypto:strong_rand_bytes(32)} || _ <- lists:seq(1, 10) ], + + Data = [{Pr, ecu_ecdsa:private_to_public(secp256k1, Pr), M} || {Pr, M} <- Data0], + + Test = fun(PrivK, PubK, MsgHash) -> + Sig = ecu_ecdsa:sign(secp256k1, MsgHash, PrivK), + ?assert(ecu_ecdsa:verify(secp256k1, MsgHash, PubK, Sig)) + end, + + {T, _} = timer:tc(fun() -> [ Test(Pr, Pu, M) || {Pr, Pu, M} <- Data ] end), + ?debugFmt("Average time for sign+verify: ~.3f ms", [(T / 1000) / length(Data)]).