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