%%% @doc %%% Key functions go here. %%% %%% The main reason this is a module of its own is that in the original architecture %%% it was a process rather than just a library of functions. Now that it exists, though, %%% there is little motivation to cram everything here into the controller process's %%% code. %%% @end -module(gmc_key_master). -vsn("0.4.0"). -export([make_key/2, encode/1, decode/1]). -export([lcg/1]). -include("gmc.hrl"). make_key("", <<>>) -> Pair = #{public := Public} = ecu_eddsa:sign_keypair(), ID = gmser_api_encoder:encode(account_pubkey, Public), Name = binary_to_list(ID), #key{name = Name, id = ID, pair = Pair}; make_key("", Seed) -> Pair = #{public := Public} = ecu_eddsa:sign_seed_keypair(Seed), ID = gmser_api_encoder:encode(account_pubkey, Public), Name = binary_to_list(ID), #key{name = Name, id = ID, pair = Pair}; make_key(Name, <<>>) -> Pair = #{public := Public} = ecu_eddsa:sign_keypair(), ID = gmser_api_encoder:encode(account_pubkey, Public), #key{name = Name, id = ID, pair = Pair}; make_key(Name, Seed) -> Pair = #{public := Public} = ecu_eddsa:sign_seed_keypair(Seed), ID = gmser_api_encoder:encode(account_pubkey, Public), #key{name = Name, id = ID, pair = Pair}. -spec encode(Secret) -> Phrase when Secret :: binary(), Phrase :: string(). %% @doc %% The encoding and decoding procesures are written to be able to handle any %% width of bitstring or binary and a variable size dictionary. The magic numbers %% 32, 4096 and 12 have been dropped in because currently these are known, but that %% will change in the future if the key size or type changes. encode(Bin) -> <> = Bin, DictSize = 4096, Words = read_words(), % Width = chunksize(DictSize - 1, 2), Width = 12, Chunks = chunksize(Number, DictSize), Binary = <>, encode(Width, Binary, Words). encode(Width, Bits, Words) -> CheckSum = checksum(Width, Bits), encode(Width, <>, Words, []). encode(_, <<>>, _, Acc) -> unicode:characters_to_list(lists:join(" ", lists:reverse(Acc))); encode(Width, Bits, Words, Acc) -> <> = Bits, Word = lists:nth(I + 1, Words), encode(Width, Rest, Words, [Word | Acc]). -spec decode(Phrase) -> {ok, Secret} | {error, Reason} when Phrase :: string(), Secret :: binary(), Reason :: bad_phrase | bad_word. %% @doc %% Reverses the encoded secret string back into its binary representation. decode(Encoded) -> DictSize = 4096, Words = read_words(), Width = chunksize(DictSize - 1, 2), decode(Width, Words, Encoded). decode(Width, Words, Encoded) when is_list(Encoded) -> decode(Width, Words, list_to_binary(Encoded)); decode(Width, Words, Encoded) -> Split = string:lexemes(Encoded, " "), decode(Width, Words, Split, <<>>). decode(Width, Words, [Word | Rest], Acc) -> case find(Word, Words) of {ok, N} -> decode(Width, Words, Rest, <>); Error -> Error end; decode(Width, _, [], Acc) -> sumcheck(Width, Acc). chunksize(N, C) -> chunksize(N, C, 0). chunksize(0, _, A) -> A; chunksize(N, C, A) -> chunksize(N div C, C, A + 1). read_words() -> Path = filename:join([zx:get_home(), "priv", "words4096.txt"]), {ok, Bin} = file:read_file(Path), string:lexemes(Bin, "\n"). find(Word, Words) -> find(Word, Words, 0). find(Word, [Word | _], N) -> {ok, N}; find(Word, [_ | Rest], N) -> find(Word, Rest, N + 1); find(Word, [], _) -> {error, {bad_word, Word}}. checksum(Width, Bits) -> checksum(Width, Bits, 0). checksum(_, <<>>, Sum) -> Sum; checksum(Width, Bits, Sum) -> <> = Bits, checksum(Width, Rest, N bxor Sum). sumcheck(Width, Bits) -> <> = Bits, case checksum(Width, Binary) =:= CheckSum of true -> <> = Binary, {ok, <>}; false -> {error, bad_phrase} end. -spec lcg(integer()) -> integer(). %% A simple PRNG that fits into 32 bits and is easy to implement anywhere (Kotlin). %% Specifically, it is a "linear congruential generator" of the Lehmer variety. %% The constants used are based on recommendations from Park, Miller and Stockmeyer: %% https://www.firstpr.com.au/dsp/rand31/p105-crawford.pdf#page=4 %% %% The input value should be between 1 and 2^31-1. %% %% The purpose of this PRNG is for password-based dictionary shuffling. lcg(N) -> M = 16#7FFFFFFF, A = 48271, Q = 44488, % M div A R = 3399, % M rem A Div = N div Q, Rem = N rem Q, S = Rem * A, T = Div * R, Result = S - T, case Result < 0 of false -> Result; true -> Result + M end.