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 def from_keypair_list(nil) do nil end 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