defmodule BitcoinLib.Transaction.BetterSigner.Inputs do alias BitcoinLib.Transaction alias BitcoinLib.Crypto alias BitcoinLib.Key.{PrivateKey, PublicKey} alias BitcoinLib.Script alias BitcoinLib.Script.Opcodes @sighash_all 1 @byte 8 @four_bytes 32 def sign(transaction, private_keys) do signature_results = private_keys |> Enum.with_index() |> Enum.map(fn {private_key, input_index} -> sign_input(private_key, input_index, transaction) end) with {:ok, signed_inputs} <- validate_results(signature_results) do %{transaction | inputs: signed_inputs} else {:error, message} -> {:error, message} end end defp validate_results(signature_results) do all_ok? = signature_results |> Enum.all?(fn {status, _} -> status == :ok end) if all_ok?, do: {:ok, signature_results |> Enum.map(fn {_, input} -> input end)}, ## in case of error, will need to specifiy which input, and return a meaningful message else: {:error, "one signature has failed"} end defp sign_input(private_key, input_index, transaction) do input_to_sign = Enum.at(transaction.inputs, input_index) with public_key <- PublicKey.from_private_key(private_key) do signed_input = transaction |> Transaction.strip_signatures(except: input_to_sign) |> create_preimage(@sighash_all) |> create_signature(private_key) |> sign_input(input_to_sign, public_key, @sighash_all) {:ok, signed_input} else {:error, message} -> {:error, message} end end defp create_preimage(transaction, sighash_type) do transaction |> Transaction.encode() |> append_sighash(sighash_type) |> Crypto.double_sha256() end defp create_signature(preimage, private_key), do: PrivateKey.sign_message(preimage, private_key) defp sign_input(signature, input_to_sign, public_key, sighash_type) do script_sig = signature |> create_script_sig(public_key, sighash_type) %{input_to_sign | script_sig: script_sig} end defp create_script_sig(signature, public_key, sighash_type) do Script.encode([ %Opcodes.Data{value: <>}, %Opcodes.Data{value: public_key.key} ]) |> elem(1) end defp append_sighash(transaction, sighash), do: <> end