Current section

Files

Jump to
eth lib mnemonic.ex
Raw

lib/mnemonic.ex

require IEx
defmodule Mnemonic do
@moduledoc """
Documentation for Mnemonic.
"""
@doc """
Hello world.
## Examples
iex> Mnemonic.hello
:world
"""
@words Mnemonic.Words.all()
@invalid_entropy "Invalid entropy"
@invalid_mnemonic "Invalid mnemonic"
@invalid_checksum "Invalid mnemonic checksum"
def generate(strength \\ 128) # alias: entropy_to_mnemonic
def generate(strength) when rem(strength, 32) !== 0, do: raise @invalid_entropy
def generate(strength) do
entropy = :crypto.strong_rand_bytes(trunc(strength / 8))
entropy_to_mnemonic(entropy)
end
def entropy_to_mnemonic(entropy) do
target_entropy = case String.valid?(entropy) do
true -> Base.decode16!(entropy, case: :mixed)
_ -> entropy
end
entropy_length = byte_size(target_entropy)
cond do
entropy_length < 16 -> raise @invalid_entropy
entropy_length > 32 -> raise @invalid_entropy
rem(entropy_length, 4) !== 0 -> raise @invalid_entropy
true ->
entropy_bits = to_binary_string(target_entropy)
checksum_bits = derive_checksum_bits(target_entropy)
bits = entropy_bits <> checksum_bits
Regex.scan(~r/(.{1,11})/, bits)
|> Enum.map(fn(x) -> List.first(x) end)
|> Enum.map(fn(binary) ->
index = binary_to_byte(binary)
Enum.at(@words, index)
end)
|> Enum.join(" ")
end
end
def mnemonic_to_entropy(mnemonic) do
words = mnemonic |> String.split(" ")
if rem(length(words), 3) !== 0, do: raise @invalid_mnemonic
bits = Enum.map(words, fn(word) ->
index = Enum.find_index(@words, fn(element) -> element == word end)
if !index, do: raise @invalid_mnemonic
Integer.to_string(index, 2) |> lpad("0", 11)
end)
|> Enum.join("")
divider_index = trunc(Float.floor(String.length(bits) / 33) * 32)
entropy_bits = bits |> String.slice(0..(divider_index - 1))
checksum_bits = bits |> String.slice(divider_index..-1)
entropy_bytes = Regex.scan(~r/(.{1,8})/, entropy_bits)
|> Enum.map(fn(x) -> List.first(x) end)
|> Enum.map(fn(x) -> binary_to_byte(x) end)
entropy_bytes_length = length(entropy_bytes)
cond do
entropy_bytes_length < 16 -> raise @invalid_entropy
entropy_bytes_length > 32 -> raise @invalid_entropy
rem(entropy_bytes_length, 4) !== 0 -> raise @invalid_entropy
true ->
entropy = :binary.list_to_bin(entropy_bytes)
new_checksum = derive_checksum_bits(entropy)
if new_checksum !== checksum_bits, do: raise @invalid_checksum
Base.encode16(entropy)
end
end
# def mnemonic_to_seed(mnemonic, password) do
#
# end
# def validate(words) do # NOTE: maybe
#
# end
def binary_to_byte(bin), do: Integer.parse(bin, 2) |> elem(0)
def derive_checksum_bits(entropy) do
cs = (byte_size(entropy) * 8) / 32 |> trunc
hash = :crypto.hash(:sha256, entropy)
to_binary_string(hash) |> String.slice(0..(cs - 1))
end
def to_binary_string(binary) do
:binary.bin_to_list(binary)
|> Enum.map(fn(x) -> Integer.to_string(x, 2) |> lpad("0", 8) end)
|> Enum.join("")
end
defp lpad(string, pad_string, length) do
if String.length(string) < length do
str = pad_string <> string
lpad(str, pad_string, length)
else
string
end
end
end