add bip39 files from Hans
This commit is contained in:
@@ -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.
|
||||||
|
|
||||||
@@ -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)).
|
||||||
Reference in New Issue
Block a user