%%% 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>>.