lib/key/hd/hmac.ex

defmodule BitcoinLib.Key.HD.Hmac do
  @moduledoc """
  Computes HMAC on either a public or a private key in the aim of computing
  a child key
  """

  alias BitcoinLib.Crypto
  alias BitcoinLib.Key.{PrivateKey, PublicKey}

  @doc """
  Computes HMAC on either a public or a private key in the aim of computing
  a child key

  ## Examples
      iex> %BitcoinLib.Key.PrivateKey {
      ...>   key: <<0xE8F32E723DECF4051AEFAC8E2C93C9C5B214313817CDB01A1494B917C8436B35::256>>,
      ...>   chain_code: <<0x873DFF81C02F525623FD1FE5167EAC3A55A049DE3D314BB42EE227FFED37D508::256>>
      ...> }
      ...> |> BitcoinLib.Key.HD.Hmac.compute(0)
      {
        <<0x6539AE80B3618C22F5F8CC4171D04835570BDA8DB11B5BF1779AFAE7EC7C79C3::256>>,
        <<0xD323F1BE5AF39A2D2F08F5E8F664633849653DBE329802E9847CFC85F8D7B52A::256>>
      }
  """
  @spec compute(%PrivateKey{} | %PublicKey{}, integer(), boolean()) :: {bitstring(), bitstring()}
  def compute(private_key, index, hardened? \\ false)

  def compute(%PrivateKey{} = private_key, index, hardened? = false) do
    private_key
    |> PublicKey.from_private_key()
    |> compute(index, hardened?)
  end

  def compute(%PrivateKey{} = private_key, index, hardened? = true) do
    get_input(private_key, index, hardened?)
    |> execute(private_key.chain_code)
  end

  def compute(%PublicKey{} = public_key, index, hardened?) do
    get_input(public_key, index, hardened?)
    |> execute(public_key.chain_code)
  end

  defp get_input(%PrivateKey{} = private_key, index, _hardened? = true)
       when is_integer(index) do
    <<(<<0>>), private_key.key::bitstring, index::size(32)>>
  end

  defp get_input(%PublicKey{} = public_key, index, _hardened? = false)
       when is_integer(index) do
    key = public_key.key

    <<key::bitstring, index::size(32)>>
  end

  defp execute(hmac_input, chain_code) do
    <<derived_key::bitstring-256, child_chain::bitstring-256>> =
      hmac_input
      |> Crypto.hmac(chain_code)

    {derived_key, child_chain}
  end
end