diff --git a/src/ec_utils.app.src b/src/ec_utils.app.src index 8ff7526..af4fa2f 100644 --- a/src/ec_utils.app.src +++ b/src/ec_utils.app.src @@ -4,7 +4,7 @@ {registered, []}, {applications, [kernel, stdlib]}, {env,[]}, - {modules, []}, + {modules, [ecu_secp256k1, ecu_misc]}, {licenses, ["MIT"]}, {links, []} ]}. diff --git a/src/ecu_misc.erl b/src/ecu_misc.erl new file mode 100644 index 0000000..71ef230 --- /dev/null +++ b/src/ecu_misc.erl @@ -0,0 +1,21 @@ +%%% File : ecu_misc.erl +%%% Author : Hans Svensson +%%% Description : Misc. functionality +%%% Created : 13 Jan 2022 by Hans Svensson +-module(ecu_misc). + +-export([eea/2]). + +%% 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)). + + diff --git a/src/ecu_secp256k1.erl b/src/ecu_secp256k1.erl new file mode 100644 index 0000000..034f33e --- /dev/null +++ b/src/ecu_secp256k1.erl @@ -0,0 +1,117 @@ +%%% 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). + +-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(K) -> + scalar_mul(K, {?X, ?Y}). + +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(_, 0) -> 1; +pow(A, 1) -> A; +pow(A, B) -> pow(A, B, 1). + +pow(_, 0, R) -> R; +pow(A, B, R) when B rem 2 == 0 -> pow(A * A, B bsr 1, R); +pow(A, B, R) -> pow(?MUL(A, A), B bsr 1, ?MUL(R, A)). + +%% 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) -> + {1, S, _T} = ecu_misc:eea(A, ?P), + (S + ?P) rem ?P. + +%% 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/test/secp256k1_tests.erl b/test/secp256k1_tests.erl new file mode 100644 index 0000000..46236f0 --- /dev/null +++ b/test/secp256k1_tests.erl @@ -0,0 +1,73 @@ +%%% File : secp256k1_tests.erl +%%% Author : Hans Svensson +%%% Description : +%%% Created : 22 Dec 2021 by Hans Svensson +-module(secp256k1_tests). + +-compile([export_all, nowarn_export_all]). + +-include_lib("eunit/include/eunit.hrl"). + +on_curve_test() -> + %% https://chuckbatson.wordpress.com/2014/11/26/secp256k1-test-vectors/ + KnownPts = + [{16#79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, + 16#483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8}, + {16#C6047F9441ED7D6D3045406E95C07CD85C778E4B8CEF3CA7ABAC09B95C709EE5, + 16#1AE168FEA63DC339A3C58419466CEAEEF7F632653266D0E1236431A950CFE52A}, + {16#F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9, + 16#388F7B0F632DE8140FE337E62A37F3566500A99934C2231B6CB9FD7584B8E672}, + {16#E493DBF1C10D80F3581E4904930B1404CC6C13900EE0758474FA94ABE8C4CD13, + 16#51ED993EA0D455B75642E2098EA51448D967AE33BFBDFE40CFE97BDC47739922}, + {16#2F8BDE4D1A07209355B4A7250A5C5128E88B84BDDC619AB7CBA8D569B240EFE4, + 16#D8AC222636E5E3D6D4DBA9DDA6C9C426F788271BAB0D6840DCA87D3AA6AC62D6}], + + [ ?assert(ecu_secp256k1:on_curve(Pt)) || Pt <- KnownPts ], + + ?assert(not ecu_secp256k1:on_curve({42, 723})). + +scalar_mul_test() -> + %% https://chuckbatson.wordpress.com/2014/11/26/secp256k1-test-vectors/ + KnownPts = + [{1, + 16#79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, + 16#483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8}, + {2, + 16#C6047F9441ED7D6D3045406E95C07CD85C778E4B8CEF3CA7ABAC09B95C709EE5, + 16#1AE168FEA63DC339A3C58419466CEAEEF7F632653266D0E1236431A950CFE52A}, + {3, + 16#F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9, + 16#388F7B0F632DE8140FE337E62A37F3566500A99934C2231B6CB9FD7584B8E672}, + {4, + 16#E493DBF1C10D80F3581E4904930B1404CC6C13900EE0758474FA94ABE8C4CD13, + 16#51ED993EA0D455B75642E2098EA51448D967AE33BFBDFE40CFE97BDC47739922}, + {5, + 16#2F8BDE4D1A07209355B4A7250A5C5128E88B84BDDC619AB7CBA8D569B240EFE4, + 16#D8AC222636E5E3D6D4DBA9DDA6C9C426F788271BAB0D6840DCA87D3AA6AC62D6}, + {20, + 16#4CE119C96E2FA357200B559B2F7DD5A5F02D5290AFF74B03F3E471B273211C97, + 16#12BA26DCB10EC1625DA61FA10A844C676162948271D96967450288EE9233DC3A}, + {112233445566778899, + 16#A90CC3D3F3E146DAADFC74CA1372207CB4B725AE708CEF713A98EDD73D99EF29, + 16#5A79D6B289610C68BC3B47F3D72F9788A26A06868B4D8E433E1E2AD76FB7DC76}, + {112233445566778899112233445566778899, + 16#E5A2636BCFD412EBF36EC45B19BFB68A1BC5F8632E678132B885F7DF99C5E9B3, + 16#736C1CE161AE27B405CAFD2A7520370153C2C861AC51D6C1D5985D9606B45F39} + ], + + [ begin + {X, Y} = ecu_secp256k1:scalar_mul_base(K), + ?assertEqual({x, K, Ex}, {x, K, X}), + ?assertEqual({y, K, Ey}, {y, K, Y}) + end || {K, Ex, Ey} <- KnownPts ]. + +compression_test() -> + Test = fun(P) -> + CP = ecu_secp256k1:compress(P), + DP = ecu_secp256k1:decompress(CP), +%% ?debugFmt("\nP : ~200p\nCP: ~200p\nDP: ~200p", [P, CP, DP]), + ?assertEqual(P, DP) + end, + [ Test(ecu_secp256k1:scalar_mul_base(K)) || K <- lists:seq(10, 100) ]. + +