142 lines
4.8 KiB
Erlang
142 lines
4.8 KiB
Erlang
%%% 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),
|
|
<<Seed:32/bytes, _/binary>> = crypto:hash(sha512, Secret),
|
|
|
|
Pub = ecu_ed25519:scalar_mul_base(Seed),
|
|
#{public => Pub, secret => <<Secret:32/binary, Pub:32/binary>>}.
|
|
|
|
%% @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) ->
|
|
<<Seed:32/bytes, _/binary>> = crypto:hash(sha512, Secret),
|
|
Pub = ecu_ed25519:compress(ecu_ed25519:scalar_mul_base(Seed)),
|
|
%% Pub = enacl:crypto_ed25519_scalarmult_base(Seed),
|
|
|
|
#{public => Pub, secret => <<Secret:32/binary, Pub:32/binary>>}.
|
|
|
|
%% @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),
|
|
<<Sig/binary, BinMsg/binary>>.
|
|
|
|
%% @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(<<Sig:64/binary, BinMsg/binary>>, PK) ->
|
|
<<R:32/bytes, Ss:32/bytes>> = Sig,
|
|
|
|
Ks0 = crypto:hash(sha512, <<R/bytes, PK/bytes, BinMsg/bytes>>),
|
|
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),
|
|
<<Secret:32/binary, _/binary>> = SK,
|
|
|
|
%% Grab the Seed, also referred to as 'a' (clamped) and the Prefix
|
|
<<Seed0:32/bytes, Prefix:32/bytes>> = 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, <<Prefix/bytes, BinMsg/bytes>>),
|
|
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, <<R/bytes, Pub/bytes, BinMsg/bytes>>),
|
|
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}
|
|
<<R/bytes, Ss/bytes>>.
|
|
|
|
|
|
%% @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),
|
|
<<R:32/bytes, Ss:32/bytes>> = Sig,
|
|
|
|
Ks0 = crypto:hash(sha512, <<R/bytes, PK/bytes, BinMsg/bytes>>),
|
|
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:8, B1_30:30/bytes, B31:8>>) ->
|
|
<<(B0 band 16#f8):8, B1_30/bytes, ((B31 band 16#7f) bor 16#40):8>>.
|