lib/signing/psbt/generic_properties/bip32_derivation.ex

defmodule BitcoinLib.Signing.Psbt.GenericProperties.Bip32Derivation do
  defstruct [:pub_key, :master_key_fingerprint, :derivation_path]

  @compressed_pub_key_size 33
  @uncompressed_pub_key_size 65

  alias BitcoinLib.Signing.Psbt.Keypair
  alias BitcoinLib.Signing.Psbt.Keypair.{Key, Value}
  alias BitcoinLib.Signing.Psbt.GenericProperties.Bip32Derivation
  alias BitcoinLib.Key.PublicKey
  alias BitcoinLib.Key.HD.DerivationPath

  # TODO: document
  def parse(%Keypair{key: %Key{data: binary_pub_key}, value: %Value{data: remaining}}) do
    {master_key_fingerprint, remaining} = extract_master_key_fingerprint(remaining)
    {derivation_path, _remaining} = extract_derivation_path(remaining, [])

    %Bip32Derivation{
      pub_key: %PublicKey{key: binary_pub_key},
      master_key_fingerprint: master_key_fingerprint,
      derivation_path: DerivationPath.from_list(["m" | derivation_path])
    }
    |> validate_pub_key()
  end

  defp extract_master_key_fingerprint(<<fingerprint::bitstring-32, remaining::bitstring>>) do
    {fingerprint, remaining}
  end

  defp extract_derivation_path(<<>> = remaining, path) do
    {Enum.reverse(path), remaining}
  end

  defp extract_derivation_path(<<level::little-32, remaining::bitstring>>, path) do
    extract_derivation_path(remaining, [level | path])
  end

  defp validate_pub_key(%Bip32Derivation{pub_key: pub_key} = bip32_derivation) do
    case byte_size(pub_key.key) do
      @compressed_pub_key_size ->
        bip32_derivation

      @uncompressed_pub_key_size ->
        bip32_derivation

      wrong_size ->
        bip32_derivation
        |> Map.put(:error, "wrong pub key size, which is #{wrong_size} in hex format")
    end
  end
end

defimpl Inspect, for: BitcoinLib.Signing.Psbt.GenericProperties.Bip32Derivation do
  alias BitcoinLib.Signing.Psbt.GenericProperties.Bip32Derivation
  alias BitcoinLib.Formatting.HexBinary

  def inspect(%Bip32Derivation{} = bip32_derivation, opts) do
    %{
      bip32_derivation
      | master_key_fingerprint: %HexBinary{data: bip32_derivation.master_key_fingerprint}
    }
    |> Inspect.Any.inspect(opts)
  end
end