Current section

Files

Jump to
tradehub lib tradehub mnemonic.ex
Raw

lib/tradehub/mnemonic.ex

defmodule Tradehub.Mnemonic do
@moduledoc false
@words Tradehub.Mnemonic.Words.all()
@invalid_entropy "Invalid entropy"
@invalid_mnemonic "Invalid mnemonic"
@invalid_checksum "Invalid mnemonic checksum"
def generate(strength \\ 256)
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
# NOTE: gives error sometimes
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
Pbkdf2.Base.hash_password(mnemonic, "mnemonic" <> password,
rounds: 2048,
format: :hex,
digest: "sha512",
length: 64
)
end
def validate_mnemonic(words) do
try do
mnemonic_to_entropy(words)
true
rescue
_ -> false
end
end
def validate_seed(mnemonic, hash, password \\ "") do
Pbkdf2.Base.verify_pass(mnemonic, hash, "mnemonic" <> password, :sha512, "2048", :hex)
end
def binary_to_byte(bin), do: Integer.parse(bin, 2) |> elem(0)
def derive_checksum_bits(entropy) do
checksum_length = (byte_size(entropy) * 8 / 32) |> trunc
hash = :crypto.hash(:sha256, entropy)
to_binary_string(hash) |> String.slice(0..(checksum_length - 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
lpad(pad_string <> string, pad_string, length)
else
string
end
end
end