defmodule BitcoinLib.Key.HD.DerivationPath do @moduledoc """ Can parse derivation paths string format into a native format m / purpose' / network' / account' / change / address_index Inspired by https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki https://learnmeabitcoin.com/technical/derivation-paths """ @enforce_keys [:type] defstruct [:type, :purpose, :network, :account, :change, :address_index] alias BitcoinLib.Key.HD.DerivationPath alias BitcoinLib.Key.HD.DerivationPath.{Parser, PathValues} alias BitcoinLib.Key.HD.DerivationPath.Parser.{Type, Purpose, Network, Change} @type t :: DerivationPath @hardened :math.pow(2, 31) |> trunc @doc """ Transforms a derivation path string into an elixir structure ## Examples iex> "m / 44' / 1' / 2' / 1 / 4" ...> |> BitcoinLib.Key.HD.DerivationPath.parse() { :ok, %BitcoinLib.Key.HD.DerivationPath{ type: :private, purpose: :bip44, network: :testnet, account: 2, change: :change_chain, address_index: 4 } } """ @spec parse(binary()) :: {:ok, DerivationPath.t()} def parse(derivation_path) do derivation_path |> Parser.parse_valid_derivation_path() end @doc """ Retruns a %DerivationPath from a set of parameters, with these values potentially missing: network, account, change, address_index ## Examples iex> BitcoinLib.Key.HD.DerivationPath.from_values("M", 0x80000054, 0x80000000, 0x80000000, 0, 0) %BitcoinLib.Key.HD.DerivationPath{ type: :public, purpose: :bip84, network: :mainnet, account: 0, change: :receiving_chain, address_index: 0 } """ @spec from_values( binary(), integer(), integer() | nil, integer() | nil, integer() | nil, integer() | nil ) :: DerivationPath.t() def from_values( type, purpose, network \\ nil, account \\ nil, change \\ nil, address_index \\ nil ) do %DerivationPath{ type: parse_type(type), purpose: parse_purpose(purpose), network: parse_network(network), account: convert_hardened_account(account), change: parse_change(change), address_index: address_index } end @doc """ Turns a list of path values into a %DerivationPath{} ## Examples iex> ["m", 0x80000054, 0x80000000, 0x80000005] ...> |> BitcoinLib.Key.HD.DerivationPath.from_list %BitcoinLib.Key.HD.DerivationPath{ type: :private, purpose: :bip84, network: :mainnet, account: 5 } """ @spec from_list(list()) :: DerivationPath.t() def from_list(values_list) do %PathValues{ type: type, purpose: purpose, network: network, account: account, change: change, address_index: address_index } = PathValues.from_list(values_list) from_values(type, purpose, network, account, change, address_index) end defp parse_type(type) do Type.get_atom(type) end defp parse_purpose(purpose) when is_integer(purpose) do Purpose.get_atom(purpose - @hardened) end defp parse_network(nil), do: nil defp parse_network(network) do Network.get_atom(network - @hardened) end defp convert_hardened_account(nil), do: nil defp convert_hardened_account(hardened_account) when is_integer(hardened_account) do hardened_account - @hardened end defp parse_change(nil), do: nil defp parse_change(change_chain) do Change.get_atom(change_chain) end end