add bip39 files from Hans

This commit is contained in:
2022-12-22 00:24:26 -07:00
parent c4f71c8b4d
commit ce4047f795
2 changed files with 151 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
%%% Author : Hans Svensson
%%% Description : Implement BIP0039
%%% Created : 12 Jan 2022 by Hans Svensson
-module(ebip39).
-ifdef(TEST).
-export([read_wordlist/0, gen_mnemonic/2]).
-endif.
-export([generate_mnemonic/1,
mnemonic_to_seed/2,
validate_mnemonic/1]).
-define(WORDLIST, "wordlist_en.txt").
-spec mnemonic_to_seed(Mnemonic :: binary(),
PassPhrase :: iodata()) -> <<_:512>>.
mnemonic_to_seed(Mnemonic, PassPhrase) ->
assert_mnemonic(Mnemonic),
{ok, BinSeed} = epbkdf2:pbkdf2(sha512,
Mnemonic,
iolist_to_binary(["mnemonic", PassPhrase]),
2048),
BinSeed.
-spec generate_mnemonic(Size :: non_neg_integer()) -> binary().
generate_mnemonic(Size) ->
case Size rem 32 of
0 -> gen_mnemonic(Size);
_ -> error({bad_mnemonic_size, Size, not_a_multiple_of_32})
end.
-spec validate_mnemonic(Mnemonic :: binary()) -> ok | {error, Reason :: atom()}.
validate_mnemonic(Mnemonic) ->
Table = word_table(),
Ixs = [ maps:get(W, Table) || W <- string:lexemes(Mnemonic, " ") ],
Size = length(Ixs) * 11,
CSize = Size rem 32,
SSize = (Size - CSize) div 8,
<<Seed:SSize/bytes, CS:(CSize)>> = << <<Ix:11>> || Ix <- Ixs >>,
case crypto:hash(sha256, Seed) of
<<CS:(CSize), _/bitstring>> -> ok;
_ -> {error, checksum_error}
end.
%% --- Internal functions
assert_mnemonic(Mnemonic) ->
case validate_mnemonic(Mnemonic) of
ok -> ok;
{error, R} -> error({bad_mnemonic_string, R})
end.
gen_mnemonic(Size) ->
gen_mnemonic(Size, crypto:strong_rand_bytes(Size div 8)).
gen_mnemonic(Size, Seed) ->
CSize = Size div 32,
<<CS:(CSize), _/bitstring>> = crypto:hash(sha256, Seed),
Table = ix_table(),
Words = [ maps:get(G, Table) || <<G:11>> <= <<Seed/bytes, CS:(CSize)>> ],
iolist_to_binary(lists:join(<<" ">>, Words)).
word_table() ->
Words = read_wordlist(),
maps:from_list(lists:zip(Words, lists:seq(0, 2047))).
ix_table() ->
Words = read_wordlist(),
maps:from_list(lists:zip(lists:seq(0, 2047), Words)).
read_wordlist() ->
File = filename:join(code:priv_dir(ebip39), ?WORDLIST),
case file:read_file(File) of
{ok, BinData} ->
read_wordlist(BinData);
{error, enotdir} -> %% In escript?
try
Escript = escript:script_name(),
{ok, Sections} = escript:extract(Escript, []),
Archive = proplists:get_value(archive, Sections),
FileName = filename:join([ebip39, priv, ?WORDLIST]),
case zip:extract(Archive, [{file_list, [FileName]}, memory]) of
{ok, [{_, BinData}]} -> read_wordlist(BinData);
_ -> error(wordlist_not_found)
end
catch _E:_R:_S ->
error(wordlist_not_found)
end
end.
read_wordlist(BinData) ->
Words = string:lexemes(BinData, "\n"),
2048 = length(Words),
Words.
+55
View File
@@ -0,0 +1,55 @@
%%% File : epbkdf2.erl
%%% Author : Hans Svensson
%%% Description : Simplistic implementation of PBKDF2 on top of `crypto`
%%% Created : 12 Jan 2022 by Hans Svensson
-module(epbkdf2).
-export([pbkdf2/4,
pbkdf2/5,
hmac/3]).
-spec pbkdf2(HMacHash :: crypto:hmac_hash_algorithm(),
Pwd :: binary(),
Salt :: binary(),
Iterations :: non_neg_integer()) -> {ok, Key :: binary()}.
pbkdf2(HMacHash, Password, Salt, Iterations) ->
pbkdf2(HMacHash, Password, Salt, Iterations, 64).
-spec pbkdf2(HMacHash :: crypto:hmac_hash_algorithm(),
Pwd :: binary(),
Salt :: binary(),
Iterations :: non_neg_integer(),
DerivedLength :: non_neg_integer()) -> {ok, Key :: binary()}.
pbkdf2(HMacHash, Password, Salt, Iterations, DerivedLength) ->
Bin = pbkdf2(HMacHash, Password, Salt, Iterations, DerivedLength, 1, []),
{ok, Bin}.
-spec hmac(HMacHash :: crypto:hmac_hash_algorithm(),
Key :: binary(),
Data :: binary()) -> binary().
hmac(HMacHash, Key, Data) ->
HMAC = crypto:mac_init(hmac, HMacHash, Key),
HMAC1 = crypto:mac_update(HMAC, Data),
crypto:mac_final(HMAC1).
%% --- internal functions
pbkdf2(HMacHash, Password, Salt, Iterations, DerivedLength, BlockIndex, Acc) ->
case iolist_size(Acc) > DerivedLength of
true ->
<<Bin:DerivedLength/binary, _/binary>> = iolist_to_binary(lists:reverse(Acc)),
Bin;
false ->
Block = pbkdf2(HMacHash, Password, Salt, Iterations, BlockIndex, 1, <<>>, <<>>),
pbkdf2(HMacHash, Password, Salt, Iterations, DerivedLength, BlockIndex + 1, [Block | Acc])
end.
pbkdf2(_HMacHash, _Password, _Salt, Iterations, _BlockIndex, Iteration, _Prev, Acc)
when Iteration > Iterations ->
Acc;
pbkdf2(HMacHash, Password, Salt, Iterations, BlockIndex, 1, _Prev, _Acc) ->
InitialBlock = hmac(HMacHash, Password, <<Salt/binary, BlockIndex:32/integer>>),
pbkdf2(HMacHash, Password, Salt, Iterations, BlockIndex, 2, InitialBlock, InitialBlock);
pbkdf2(HMacHash, Password, Salt, Iterations, BlockIndex, Iteration, Prev, Acc) ->
Next = hmac(HMacHash, Password, Prev),
pbkdf2(HMacHash, Password, Salt, Iterations, BlockIndex, Iteration + 1, Next, crypto:exor(Next, Acc)).