diff --git a/utils/vw/src/ec_utils.erl b/utils/vw/src/ec_utils.erl new file mode 100644 index 0000000..113beb4 --- /dev/null +++ b/utils/vw/src/ec_utils.erl @@ -0,0 +1,4 @@ +-module(ec_utils). +-vsn("1.0.0"). + +-export([]). diff --git a/utils/vw/src/ecu_crypto.erl b/utils/vw/src/ecu_crypto.erl new file mode 100644 index 0000000..6f1c8ef --- /dev/null +++ b/utils/vw/src/ecu_crypto.erl @@ -0,0 +1,61 @@ +%%% File : ecu_crypto.erl +%%% Author : Hans Svensson +%%% Description : +%%% Created : 13 Jan 2022 by Hans Svensson +-module(ecu_crypto). +-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, + keccak256/1]). + +private_to_short(bitcoin, PrivateKey) -> + public_to_short(bitcoin, aeu_ecdsa:private_to_public(secp256k1, PrivateKey)); +private_to_short(ethereum, <>) -> + 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(<>), + 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, + <>. + +eth_recover(Msg, Sig = <<_:65/bytes>>) -> + MsgHash = eth_msg_hash(Msg), + <> = MsgHash, + <> = 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(<>), + 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). diff --git a/utils/vw/src/ecu_ecdsa.erl b/utils/vw/src/ecu_ecdsa.erl new file mode 100644 index 0000000..17d29ea --- /dev/null +++ b/utils/vw/src/ecu_ecdsa.erl @@ -0,0 +1,54 @@ +%%% File : ecu_ecdsa.erl +%%% Author : Hans Svensson +%%% Description : ecdsa functionality +%%% Created : 13 Jan 2022 by Hans Svensson +-module(ecu_ecdsa). +-vsn("1.0.0"). + +-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/utils/vw/src/ecu_ed25519.erl b/utils/vw/src/ecu_ed25519.erl new file mode 100644 index 0000000..8122fc6 --- /dev/null +++ b/utils/vw/src/ecu_ed25519.erl @@ -0,0 +1,215 @@ +%%% File : ecu_ed25519.erl +%%% Author : Hans Svensson +%%% Description : Trying to whip together a pure Erlang ed25519 +%%% Just for usage when speed isn't crucial... +%%% Created : 13 Jan 2022 by Hans Svensson +-module(ecu_ed25519). +-vsn("1.0.0"). + +-define(P, 16#7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED). +-define(N, 16#1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED). + +-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() :: pt_affine() | pt_hom_ext() | pt_compressed(). + +%% -type fld_elem() :: 0..(?P-1). +-type scalar() :: 0..(?N-1). + +-define(D, 16#52036CEE2B6FFE738CC740797779E89800700A4D4141D8AB75EB4DCA135978A3). +-define(X, 16#216936D3CD6E53FEC0A4E231FDD6DC5C692CC7609525A7B2C9562D608F25D51A). +-define(Y, 16#6666666666666666666666666666666666666666666666666666666666666658). +-define(XY, 16#67875F0FD78B766566EA4E8E64ABE37D20F09F80775152F56DDE8AB3A5B7DDA3). +-define(GA, {?X, ?Y}). +-define(GE, {?X, ?Y, 1, ?XY}). + +-define(PPLUS3DIV8, 16#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE). +-define(TWOPOWPMINUS1DIV4, 16#2B8324804FC1DF0B2B4D00993DFBD7A72F431806AD2FE478C4EE1B274A0EA0B0). + +-define(ADD(A, B), ((A + B) rem ?P)). +-define(MUL(A, B), ((A * B) rem ?P)). +-define(SUB(A, B), ((A - B + ?P) rem ?P)). +-define(DIV(A, B), f_div(A, B)). + +-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, + 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]). + +-ifdef(TEST). +-compile([export_all, nowarn_export_all]). +-endif. + +-spec pt_eq(P1 :: pt(), P2 :: pt()) -> boolean(). +pt_eq({X1, Y1}, {X2, Y2}) -> + X1 == X2 andalso Y1 == Y2; +pt_eq(C1, C2) when is_binary(C1), is_binary(C2) -> + C1 == C2; +pt_eq({X1, Y1, Z1, _T1}, {X2, Y2, Z2, _T2}) -> + ?SUB(?MUL(X1, Z2), ?MUL(X2, Z1)) == 0 + andalso ?SUB(?MUL(Y1, Z2), ?MUL(Y2, Z1)) == 0; +pt_eq(P1, P2) -> + pt_eq(to_ext_hom(P1), to_ext_hom(P2)). + +%% Libsodium has additional checks +%% - weak keys +%% - canonical representation +%% - main subgroup... +on_curve({X, Y}) -> + X2 = ?MUL(X, X), + Y2 = ?MUL(Y, Y), + LHS = ?SUB(Y2, X2), + RHS = ?ADD(1, ?MUL(?D, ?MUL(X2, Y2))), + 0 == ?SUB(LHS, RHS); +on_curve(P) -> + on_curve(to_affine(P)). + +p() -> ?P. + +n() -> ?N. + +-define(TWO_POW_255_MINUS_1, 16#7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF). + +-spec compress(P :: pt()) -> <<_:32>>. +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), + <>. + +-spec decompress(<<_:32>>) -> pt_hom_ext(). +decompress(<>) -> + X0 = Y0 bsr 255, + Y = Y0 band ?TWO_POW_255_MINUS_1, + X = xrecover(Y), + case X rem 2 == X0 of + true -> to_ext_hom({X, Y}); + 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), 2 * ?D), + D = ?MUL(2 * Z1, Z2), + E = ?SUB(B, A), + F = ?SUB(D, C), + G = ?ADD(D, C), + H = ?ADD(A, B), + {?MUL(E, F), ?MUL(G, H), ?MUL(F, G), ?MUL(E, H)}; +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_(clamp(K), ?GE); +scalar_mul_base(K) when is_integer(K), K >= 0, K < ?N -> + scalar_mul_(clamp(K), ?GE). + +-spec scalar_mul(Scalar :: scalar() | binary(), Pt :: pt()) -> pt_hom_ext(). +scalar_mul(<>, P) -> + scalar_mul(K, P); +scalar_mul(K, P) -> + scalar_mul_(clamp(K), to_ext_hom(P)). + +-spec scalar_mul_base_noclamp(Scalar :: scalar() | binary()) -> pt_hom_ext(). +scalar_mul_base_noclamp(<>) -> + scalar_mul_(K, ?GE); +scalar_mul_base_noclamp(K) when is_integer(K), K >= 0, K < ?N -> + scalar_mul_(K, ?GE). + +-spec scalar_mul_noclamp(Scalar :: scalar() | binary(), Pt :: pt()) -> pt_hom_ext(). +scalar_mul_noclamp(<>, P) -> + scalar_mul_noclamp(K, P); +scalar_mul_noclamp(K, P) -> + scalar_mul_(K, to_ext_hom(P)). + +to_ext_hom({_, _, _, _} = P) -> P; +to_ext_hom(<<_:32/binary>> = P) -> decompress(P); +to_ext_hom({X, Y}) -> {X, Y, 1, ?MUL(X, Y)}. + +to_affine({_, _} = P) -> P; +to_affine(<<_:32/binary>> = P) -> to_affine(decompress(P)); +to_affine({X, Y, Z, _}) -> + ZInv = f_inv(Z), + {?MUL(X, ZInv), ?MUL(Y, ZInv)}. + +f_pow(A, B) -> ecu_misc:exp_mod(A, B, ?P). + +%% Arithmetics in prime field P +f_add(A, B) -> (A + B) rem ?P. +f_mul(A, B) -> (A * B) rem ?P. +f_sub(A, B) -> (A - B + ?P) rem ?P. +f_div(A, B) -> f_mul(A, f_inv(B)). + +f_inv(A) -> + f_pow(A, ?P - 2). + +%% Arithmetics in curve group order 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(<>) -> + {1, S, _T} = ecu_misc:eea(A, ?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_dbl(P)); + 1 -> p_add(P, scalar_mul_(K - 1, P)) + end. + +clamp(K) -> + ((K band (bnot 7)) band (bnot (128 bsl 248))) bor (64 bsl 248). + +xrecover(Y) -> + Y2 = ?MUL(Y, Y), + U = ?SUB(Y2, 1), + V = ?ADD(?MUL(?D, Y2), 1), + UdivV = ?DIV(U, V), + X0 = f_pow(UdivV, ?PPLUS3DIV8), + + X0_2 = ?MUL(X0, X0), + case ?SUB(X0_2, UdivV) of + 0 -> X0; + _ -> case ?ADD(X0_2, UdivV) of + 0 -> ?MUL(X0, ?TWOPOWPMINUS1DIV4); + _ -> error(xrecover_failed) + end + end. diff --git a/utils/vw/src/ecu_eddsa.erl b/utils/vw/src/ecu_eddsa.erl new file mode 100644 index 0000000..aea16dc --- /dev/null +++ b/utils/vw/src/ecu_eddsa.erl @@ -0,0 +1,141 @@ +%%% 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). +-vsn("1.0.0"). + +-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/utils/vw/src/ecu_misc.erl b/utils/vw/src/ecu_misc.erl new file mode 100644 index 0000000..4f2d7c3 --- /dev/null +++ b/utils/vw/src/ecu_misc.erl @@ -0,0 +1,53 @@ +%%% File : ecu_misc.erl +%%% Author : Hans Svensson +%%% Description : Misc. functionality +%%% Created : 13 Jan 2022 by Hans Svensson +-module(ecu_misc). +-vsn("1.0.0"). + +-export([eea/2, exp_mod/3, + hex_to_bin/1, bin_to_hex/1, + pcomp/1]). + +%% A^B mod P +exp_mod(_A, 0, _P) -> 1; +exp_mod(A, B, P) when A > 0 -> + binary:decode_unsigned(crypto:mod_pow(A, B, P)); +exp_mod(A, B, P) -> + X = exp_mod(-A, B, P), + case B rem 2 == 0 orelse X == 0 of + true -> X; + false -> P - X + end. + +%% Extended Euclidean Algorithm +eea(A, B) when ((A < 1) or (B < 1)) -> + undefined; +eea(A, B) -> + eea(A, 1, 0, B, 0, 1). + +eea(G, S, T, 0, _, _) -> + {G, S, T}; +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 ]. + +%% 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)]). diff --git a/utils/vw/src/ecu_secp256k1.erl b/utils/vw/src/ecu_secp256k1.erl new file mode 100644 index 0000000..7811d8c --- /dev/null +++ b/utils/vw/src/ecu_secp256k1.erl @@ -0,0 +1,115 @@ +%%% File : ecu_secp256k1.erl +%%% Author : Hans Svensson +%%% Description : Trying to whip together a pure Erlang secp256k1 +%%% Just for usage when speed isn't crucial... +%%% Created : 22 Dec 2021 by Hans Svensson +-module(ecu_secp256k1). +-vsn("1.0.0"). + +-define(P, 16#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F). +-define(A, 16#00). +-define(B, 16#07). +-define(X, 16#79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798). +-define(Y, 16#483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8). +-define(N, 16#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141). +-define(E, 16#7AE96A2B657C07106E64479EAC3434E99CF0497512F58995C1396C28719501EE). + +-define(ADD(A, B), ((A + B) rem ?P)). +-define(MUL(A, B), ((A * B) rem ?P)). +-define(SUB(A, B), ((A - B + ?P) rem ?P)). +-define(DIV(A, B), f_div(A, B)). + +-export([on_curve/1, p/0, n/0, + scalar_mul/2, scalar_mul_base/1, p_add/2, p_neg/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]). + +-ifdef(TEST). +-compile([export_all, nowarn_export_all]). +-endif. + +on_curve({X, Y}) -> + %% y^2 = x^3 + 7 + X3 = ?MUL(?MUL(X, X), X), + Y2 = ?MUL(Y, Y), + Y2 == ?ADD(X3, ?B). + +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) -> + P; +scalar_mul(K, P) -> + case K rem 2 == 0 of + true -> scalar_mul(K div 2, p_add(P, P)); + false -> p_add(P, scalar_mul(K - 1, P)) + end. + +compress({X, Y}) when Y rem 2 == 0 -> <<2:8, X:256>>; +compress({X, _}) -> <<3:8, X:256>>; +compress(<<4:8, X:256, Y:256>>) -> compress({X, Y}). + +decompress(<>) -> + Y0 = ?B + ?MUL(X, ?MUL(X, X)), + Y1 = pow(Y0, (?P + 1) div 4), + case Y1 rem 2 == N rem 2 of + true -> {X, Y1}; + false -> {X, ?P - Y1} + end. + +p_neg({X, Y}) -> {X, ?P - Y}. + +p_add(P1, {0, 0}) -> P1; +p_add({0, 0}, P2) -> P2; +p_add({X, Y1}, {X, Y2}) when Y1 /= Y2 -> {0, 0}; +p_add(P = {X1, Y1}, P) -> + M = ?DIV(?MUL(3, ?MUL(X1, X1)), ?MUL(2, Y1)), + X3 = ?SUB(?MUL(M, M), ?MUL(2, X1)), + Y3 = ?SUB(?MUL(M, ?SUB(X1, X3)), Y1), + {X3, Y3}; +p_add({X1, Y1}, {X2, Y2}) -> + M = ?DIV(?SUB(Y2, Y1), ?SUB(X2, X1)), + X3 = ?SUB(?MUL(M, M), ?ADD(X1, X2)), + Y3 = ?SUB(?MUL(M, ?SUB(X1, X3)), Y1), + {X3, Y3}. + +pow(A, B) -> ecu_misc:exp_mod(A, B, ?P). + +%% Arithmetics in prime field P +f_add(A, B) -> (A + B) rem ?P. +f_mul(A, B) -> (A * B) rem ?P. +f_sub(A, B) -> (A - B + ?P) rem ?P. +f_div(A, B) -> f_mul(A, f_inv(B)). + +f_inv(A) -> + 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_div(A, B) -> s_mul(A, s_inv(B)). + +s_inv(A) -> + {1, S, _T} = ecu_misc:eea(A, ?N), + (S + ?N) rem ?N. + +%% curve() -> +%% #{ p => 16#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, +%% a => 16#00, b => 16#07, +%% x => 16#79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, +%% y => 16#483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, +%% n => 16#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141, +%% e => 16#7AE96A2B657C07106E64479EAC3434E99CF0497512F58995C1396C28719501EE +%% }. diff --git a/utils/vw/src/vw.erl b/utils/vw/src/vw.erl index ccffeb3..f01519b 100644 --- a/utils/vw/src/vw.erl +++ b/utils/vw/src/vw.erl @@ -17,20 +17,17 @@ start(ArgV) -> %% %% WHEN back: get decompose to work -go(["help"]) -> - help(); -go(["--help"]) -> - help(); -go(["generate", "keypair"]) -> - error(nyi); -go(["decompose", TxStr]) -> - decompose(TxStr); -go(_) -> - error(invalid_subcommand). +go(["help"]) -> help(); +go(["--help"]) -> help(); +go(["decompose", TxStr]) -> decompose(TxStr); +go(["generate", "keypair"]) -> generate_keypair(); +go(X) -> error({invalid_subcommand, X}). + help() -> io:format("you can't help people who won't help themselves~n", []). + decompose(TxStr) -> case vd:decompose(TxStr) of {ok, X} -> @@ -38,3 +35,10 @@ decompose(TxStr) -> {error, Error} -> io:format("ERROR: ~tp~n", [Error]) end. + + +generate_keypair() -> + #{public := PublicKey, + secret := SecretKey} = ecu_eddsa:sign_keypair(), + io:format("Public Key: ~w~n", [PublicKey]), + io:format("Secret Key: ~w~n", [SecretKey]).