defmodule BitcoinLib.Transaction.Input do @moduledoc """ Based on https://learnmeabitcoin.com/technical/input """ defstruct [:txid, :vout, :script_sig, :sequence] @byte 8 alias BitcoinLib.Crypto.Bitstring alias BitcoinLib.Transaction.Input alias BitcoinLib.Script alias BitcoinLib.Signing.Psbt.CompactInteger @type t :: Input @doc """ Extracts a transaction input from a bitstring ## Examples iex> <<0x7b1eabe0209b1fe794124575ef807057c77ada2138ae4fa8d6c4de0398a14f3f0000000000ffffffff01f0ca052a010000001976a914cbc20a7664f2f69e5355aa427045bc15e7c6c77288ac00000000::640>> ...> |> BitcoinLib.Transaction.Input.extract_from(false) { :ok, %BitcoinLib.Transaction.Input{ txid: "3f4fa19803dec4d6a84fae3821da7ac7577080ef75451294e71f9b20e0ab1e7b", vout: 0, script_sig: [], sequence: 4294967295 }, <<0x01f0ca052a010000001976a914cbc20a7664f2f69e5355aa427045bc15e7c6c77288ac00000000::312>> } """ @spec extract_from(binary(), bool) :: {:ok, Input.t(), bitstring()} | {:error, binary()} def extract_from( <>, is_coinbase? \\ false ) do txid = little_txid |> Bitstring.reverse() |> Binary.to_hex() case extract_script_sig(remaining, is_coinbase?) do {:error, message} -> {:error, message} {:ok, script_sig, remaining} -> {sequence, remaining} = extract_sequence(remaining) { :ok, %Input{ txid: txid, vout: vout, script_sig: script_sig, sequence: sequence }, remaining } end end @doc """ Encodes an input into a bitstring ## Examples iex> %BitcoinLib.Transaction.Input{ ...> sequence: 0xFFFFFFFF, ...> txid: "5e2383defe7efcbdc9fdd6dba55da148b206617bbb49e6bb93fce7bfbb459d44", ...> vout: 1 ...> } |> BitcoinLib.Transaction.Input.encode() <<0x449d45bbbfe7fc93bbe649bb7b6106b248a15da5dbd6fdc9bdfc7efede83235e0100000000ffffffff::328>> """ @spec encode(Input.t()) :: bitstring() def encode(%Input{} = input) do txid = input.txid |> Binary.from_hex() |> Bitstring.reverse() {script_size, script} = input.script_sig |> format_script_sig() <> end @doc """ Replaces a potential signature in the input with [] ## Examples iex> %BitcoinLib.Transaction.Input{ ...> txid: "77541aeb3c4dac9260b68f74f44c973081a9d4cb2ebe8038b2d70faa201b6bdb", ...> vout: 1, ...> script_sig: [ ...> %BitcoinLib.Script.Opcodes.Data{ ...> value: <<0x001479091972186C449EB1DED22B78E40D009BDF0089::176>> ...> } ...> ], ...> sequence: 4_294_967_294 ...> } ...> |> BitcoinLib.Transaction.Input.strip_signature %BitcoinLib.Transaction.Input{ txid: "77541aeb3c4dac9260b68f74f44c973081a9d4cb2ebe8038b2d70faa201b6bdb", vout: 1, script_sig: <<>>, sequence: 4_294_967_294 } """ @spec strip_signature(Input.t()) :: Input.t() def strip_signature(%Input{} = input) do %{input | script_sig: <<>>} end defp format_script_sig(nil), do: {<<0::8>>, <<>>} defp format_script_sig(script_sig) when is_list(script_sig), do: script_sig |> Script.encode() defp format_script_sig(script_sig) when is_binary(script_sig) do script_sig_bit_size = byte_size(script_sig) {<>, script_sig} end defp extract_script_sig(remaining, true = _is_coinbase?) do %CompactInteger{value: script_sig_size, remaining: remaining} = CompactInteger.extract_from(remaining) script_sig_bit_size = script_sig_size * @byte <> = remaining {:ok, script_sig, remaining} end defp extract_script_sig(remaining, false = _is_coinbase?) do %CompactInteger{value: script_sig_size, remaining: remaining} = CompactInteger.extract_from(remaining) remaining_size = byte_size(remaining) if script_sig_size > remaining_size do {:error, "trying to get a #{script_sig_size} bytes signature in a #{remaining_size} block of data"} else script_sig_bit_size = script_sig_size * @byte <> = remaining case Script.parse(script_sig) do {:ok, script_sig} -> {:ok, script_sig, remaining} {:error, message} -> {:error, message} end end end defp extract_sequence(remaining) do <> = remaining {sequence, remaining} end end