lib/signing/psbt/input.ex

defmodule BitcoinLib.Signing.Psbt.Input do
  defstruct [
    :utxo,
    :partial_sig,
    :sighash_type,
    :redeem_script,
    :witness_script,
    :final_script_sig,
    bip32_derivations: [],
    unknowns: []
  ]

  alias BitcoinLib.Signing.Psbt.{Keypair, KeypairList, Input}

  alias BitcoinLib.Signing.Psbt.GenericProperties.{
    Bip32Derivation,
    Proprietary,
    RedeemScript,
    WitnessScript
  }

  alias BitcoinLib.Signing.Psbt.Input.{
    NonWitnessUtxo,
    WitnessUtxo,
    PartialSig,
    SighashType,
    FinalScriptSig,
    FinalScriptWitness,
    ProofOfReservesCommitment,
    Ripemd160Preimage,
    Sha256Preimage,
    Hash160Preimage,
    Hash256Preimage,
    OutputIndex
  }

  @non_witness_utxo 0x0
  @witness_utxo 0x1
  @partial_sig 0x2
  @sighash_type 0x3
  @redeem_script 0x4
  @witness_script 0x5
  @bip32_derivation 0x6
  @final_script_sig 0x7
  @final_script_witness 0x8
  @proof_of_reserves_commitment 0x9
  @ripemd160_preimage 0xA
  @sha256_preimage 0xB
  @hash160_preimage 0xC
  @hash256_preimage 0xD
  @output_index 0xF
  @proprietary 0xFC

  # TODO: document
  def from_keypair_list(nil) do
    nil
  end

  # TODO: document
  def from_keypair_list(%KeypairList{} = keypair_list) do
    keypair_list.keypairs
    |> validate_keys
    |> dispatch_keypairs
  end

  defp validate_keys(keypairs) do
    keys =
      keypairs
      |> Enum.map(& &1.key)

    duplicate_keys = keys -- Enum.uniq(keys)

    case duplicate_keys do
      [] ->
        {:ok, keypairs}

      duplicates ->
        {:error, "duplicate keys: #{inspect(duplicates)}"}
    end
  end

  defp dispatch_keypairs({:error, message}) do
    %{error: message}
  end

  defp dispatch_keypairs({:ok, keypairs}) do
    keypairs
    |> Enum.reduce(%Input{}, &dispatch_keypair/2)
  end

  defp dispatch_keypair(%Keypair{key: key, value: value} = keypair, input) do
    case key.type do
      @non_witness_utxo -> add_non_witness_utxo(input, keypair)
      @witness_utxo -> add_witness_utxo(input, keypair)
      @partial_sig -> add_partial_sig(input, key.data, value)
      @sighash_type -> add_sighash_type(input, value)
      @redeem_script -> add_redeem_script(input, keypair)
      @witness_script -> add_witness_script(input, keypair)
      @bip32_derivation -> add_bip32_derivation(input, keypair)
      @final_script_sig -> add_final_script_sig(input, keypair)
      @final_script_witness -> add_final_script_witness(input, keypair)
      @proof_of_reserves_commitment -> add_proof_of_reserves_commitment(input, keypair)
      @ripemd160_preimage -> add_ripemd160_preimage(input, keypair)
      @sha256_preimage -> add_sha256_preimage(input, keypair)
      @hash160_preimage -> add_hash160_preimage(input, keypair)
      @hash256_preimage -> add_hash256_preimage(input, keypair)
      @output_index -> add_output_index(input, keypair)
      @proprietary -> add_proprietary(input, keypair)
      _ -> add_unknown(input, keypair)
    end
  end

  defp add_non_witness_utxo(input, keypair) do
    case NonWitnessUtxo.parse(keypair) do
      {:ok, utxo} ->
        input
        |> Map.put(:utxo, utxo)

      {:error, message} ->
        input
        |> Map.put(:error, message)
    end
  end

  defp add_witness_utxo(input, keypair) do
    case WitnessUtxo.parse(keypair) do
      {:ok, utxo} ->
        input
        |> Map.put(:utxo, utxo)

      {:error, message} ->
        input
        |> Map.put(:error, message)
    end
  end

  defp add_partial_sig(input, key_value, value) do
    partial_sig = PartialSig.parse(key_value, value.data)

    case Map.get(partial_sig, :error) do
      nil -> input |> Map.put(:partial_sig, partial_sig)
      message -> input |> Map.put(:error, message)
    end
  end

  defp add_sighash_type(input, value) do
    sighash_type = SighashType.parse(value.data)

    case sighash_type do
      %SighashType{} ->
        input
        |> Map.put(:sighash_type, sighash_type.value)

      %{error: message} ->
        input |> Map.put(:error, message)
    end
  end

  defp add_redeem_script(input, keypair) do
    case RedeemScript.parse(keypair) do
      {:ok, redeem_script} -> input |> Map.put(:redeem_script, redeem_script.script)
      {:error, message} -> input |> Map.put(:error, message)
    end
  end

  defp add_witness_script(input, keypair) do
    case WitnessScript.parse(keypair) do
      {:ok, witness_script} -> input |> Map.put(:witness_script, witness_script.script)
      {:error, message} -> input |> Map.put(:error, message)
    end
  end

  defp add_bip32_derivation(%{bip32_derivations: derivations} = input, keypair) do
    derivation = Bip32Derivation.parse(keypair)

    case Map.get(derivation, :error) do
      nil ->
        input
        |> Map.put(:bip32_derivations, [derivation | derivations])

      message ->
        input |> Map.put(:error, message)
    end
  end

  defp add_final_script_sig(input, keypair) do
    case FinalScriptSig.parse(keypair) do
      {:ok, final_script_sig} ->
        input
        |> Map.put(:final_script_sig, final_script_sig.script_sig)

      {:error, message} ->
        input
        |> Map.put(:error, message)
    end
  end

  defp add_final_script_witness(input, keypair) do
    final_script_witness = FinalScriptWitness.parse(keypair)

    case Map.get(final_script_witness, :error) do
      nil ->
        input
        |> Map.put(:final_script_witness, final_script_witness.script_witness)

      message ->
        input
        |> Map.put(:error, message)
    end
  end

  defp add_proof_of_reserves_commitment(input, keypair) do
    proof_of_reserves_commitment = ProofOfReservesCommitment.parse(keypair)

    input
    |> Map.put(:proof_of_reserves_commitment, proof_of_reserves_commitment.value)
  end

  defp add_ripemd160_preimage(input, keypair) do
    ripemd160_preimage = Ripemd160Preimage.parse(keypair.key, keypair.value)

    case Map.get(ripemd160_preimage, :error) do
      nil ->
        input
        |> Map.put(:ripemd160_preimage, ripemd160_preimage)

      message ->
        input
        |> Map.put(:error, message)
    end
  end

  defp add_sha256_preimage(input, keypair) do
    sha256_preimage = Sha256Preimage.parse(keypair.key, keypair.value)

    case Map.get(sha256_preimage, :error) do
      nil ->
        input
        |> Map.put(:sha256_preimage, sha256_preimage)

      message ->
        input
        |> Map.put(:error, message)
    end
  end

  defp add_hash160_preimage(input, keypair) do
    hash160_preimage = Hash160Preimage.parse(keypair.key, keypair.value)

    case Map.get(hash160_preimage, :error) do
      nil ->
        input
        |> Map.put(:hash160_preimage, hash160_preimage)

      message ->
        input
        |> Map.put(:error, message)
    end
  end

  defp add_hash256_preimage(input, keypair) do
    hash256_preimage = Hash256Preimage.parse(keypair.key, keypair.value)

    case Map.get(hash256_preimage, :error) do
      nil ->
        input
        |> Map.put(:hash256_preimage, hash256_preimage)

      message ->
        input
        |> Map.put(:error, message)
    end
  end

  defp add_output_index(input, keypair) do
    output_index = OutputIndex.parse(keypair)

    case Map.get(output_index, :error) do
      nil ->
        input
        |> Map.put(:output_index, output_index)

      message ->
        input
        |> Map.put(:error, message)
    end
  end

  defp add_proprietary(%{error: _message} = input, _keypair), do: input

  defp add_proprietary(input, keypair) do
    input
    |> Map.put(:proprietary, Proprietary.parse(keypair))
  end

  defp add_unknown(input, keypair) do
    input
    |> Map.put(:unknowns, [keypair | input.unknowns])
  end
end