lib/key/hd/derivation_path/parser/change.ex

defmodule BitcoinLib.Key.HD.DerivationPath.Parser.Change do
  @moduledoc """
  Determines which change chain is being used, either receiving or change.

  This is a hardened value.

  See: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#change
  """

  @receiving_chain "0"
  @receiving_chain_value 0
  @receiving_chain_atom :receiving_chain

  @change_chain "1"
  @change_chain_value 1
  @change_chain_atom :change_chain

  @doc """
  Converts a list of path levels managed up until the change chain, extracts
  the change chain and returns the remaining levels

  ## Examples
      iex> ["1", "2", "3"]
      ...> |> BitcoinLib.Key.HD.DerivationPath.Parser.Change.extract()
      {:ok, :change_chain, ["2", "3"]}
  """
  @spec extract(list()) :: {:ok, nil, []} | {:ok, atom(), list()} | {:error, binary()}
  def extract([]), do: {:ok, nil, []}

  def extract([change_chain | rest]) do
    change_chain
    |> String.trim()
    |> convert(rest)
  end

  def get_atom(@receiving_chain_value), do: @receiving_chain_atom
  def get_atom(@change_chain_value), do: @change_chain_atom

  defp convert(@receiving_chain, rest), do: {:ok, @receiving_chain_atom, rest}
  defp convert(@change_chain, rest), do: {:ok, @change_chain_atom, rest}

  defp convert(change, _rest) do
    message =
      case String.ends_with?(change, "'") do
        true -> "change chain must NOT be a hardened value"
        false -> "#{change} is not a valid change chain"
      end

    {:error, message}
  end
end