lib/transaction/validator/input.ex

defmodule BitcoinLib.Transaction.Validator.Input do
  @moduledoc """
  Validates a transaction input
  """

  alias BitcoinLib.Crypto
  alias BitcoinLib.Script
  alias BitcoinLib.Transaction
  alias BitcoinLib.Transaction.{Input}
  alias BitcoinLib.Transaction.Validator.{Prevout, ScriptSig}
  alias BitcoinLib.Transaction.Validator.Input, as: ValidatorInput

  require Logger

  defstruct [:type, :der_signature, :sighash_type]

  @spec validate(Input.t(), (binary() -> Transaction.t())) ::
          {:ok, boolean()} | {:error, binary()}
  def validate(%Input{} = input, get_transaction_by_id) do
    with {:ok, output} <- Prevout.from_input(input, get_transaction_by_id) do
      case Script.identify(output.script_pub_key) do
        {:p2pkh, public_key_hash} ->
          validate_p2pkh(input, public_key_hash)

        other ->
          Logger.warning(inspect(other, label: "unknown prevout script type"))
          false
      end
    end
  end

  defp validate_p2pkh(input, public_key_hash) do
    with {:ok,
          %{public_key: public_key, der_signature: der_signature, sighash_type: sighash_type}} <-
           ScriptSig.p2pkh_from_input(input) do
      if public_key |> Crypto.hash160() == public_key_hash do
        {
          :ok,
          %ValidatorInput{type: :p2pkh, der_signature: der_signature, sighash_type: sighash_type}
        }
      else
        {:error, "public key and public key hash don't match"}
      end
    else
      {:error, message} -> {:error, message}
    end
  end
end