defmodule BitcoinLib.Key.HD.Entropy do @moduledoc """ Computing entropy from different sources, mostly for seed phrase creation """ @doc """ Takes a list of dice rolls as a string argument, having values ranging from 0 to 5, transforming them into a single integer that could serve as entropy for seed phrase creation ## Examples iex> "12345612345612345612345612345612345612345612345612" ...> |> BitcoinLib.Key.HD.Entropy.from_dice_rolls() ...> |> elem(1) 32_310_461_525_491_050_757_677_748_469_648_273_221 """ @spec from_dice_rolls(binary()) :: {:ok, integer()} | {:error, binary()} def from_dice_rolls(dice_rolls) do dice_rolls |> validate_rolls |> maybe_decrement_rolls |> maybe_parse_rolls end @doc """ Takes a list of dice rolls as a string argument, having values ranging from 0 to 5, transforming them into a single integer that could serve as entropy for seed phrase creation ## Examples iex> "12345612345612345612345612345612345612345612345612" ...> |> BitcoinLib.Key.HD.Entropy.from_dice_rolls!() 32_310_461_525_491_050_757_677_748_469_648_273_221 """ @spec from_dice_rolls!(binary()) :: integer() def from_dice_rolls!(dice_rolls) do from_dice_rolls(dice_rolls) |> elem(1) end defp validate_rolls(dice_rolls) do case String.length(dice_rolls) do n when n in [50, 99] -> validate_dices(dice_rolls) number_of_rolls -> {:error, "Please provide 50 dice rolls for 12 words or 99 dice rolls for 24 words, got #{number_of_rolls}"} end end defp validate_dices(dice_rolls) do case Regex.match?(~r/^[1-6]+$/, dice_rolls) do true -> {:ok, dice_rolls} false -> {:error, "Please provide characters inside the [1,6] range, got #{dice_rolls}"} end end defp maybe_decrement_rolls({:ok, dice_rolls}) do zero_based_dice_rolls = <> |> for(do: <>) |> Enum.join() {:ok, zero_based_dice_rolls} end defp maybe_decrement_rolls(input), do: input defp maybe_parse_rolls({:ok, dice_rolls}) do entropy = dice_rolls |> Integer.parse(6) |> elem(0) {:ok, entropy} end defp maybe_parse_rolls(input), do: input end