defmodule BitcoinLib.Transaction.Output do @moduledoc """ Based on https://learnmeabitcoin.com/technical/output """ defstruct [:value, :script_pub_key, :invalid_script, :error_message] alias BitcoinLib.Signing.Psbt.CompactInteger alias BitcoinLib.Transaction.Output alias BitcoinLib.Script @type t :: Output @byte 8 @doc """ Extracts a transaction output from a bitstring ## Examples iex> <<0xf0ca052a010000001976a914cbc20a7664f2f69e5355aa427045bc15e7c6c77288ac00000000::304>> ...> |> BitcoinLib.Transaction.Output.extract_from { :ok, %BitcoinLib.Transaction.Output{ value: 4999990000, script_pub_key: [ %BitcoinLib.Script.Opcodes.Stack.Dup{}, %BitcoinLib.Script.Opcodes.Crypto.Hash160{}, %BitcoinLib.Script.Opcodes.Data{value: <<0xcbc20a7664f2f69e5355aa427045bc15e7c6c772::160>>}, %BitcoinLib.Script.Opcodes.BitwiseLogic.EqualVerify{}, %BitcoinLib.Script.Opcodes.Crypto.CheckSig{script: <<0x76a914cbc20a7664f2f69e5355aa427045bc15e7c6c77288ac::200>>} ] }, <<0, 0, 0, 0>> } """ @spec extract_from(binary()) :: {:ok, Output.t(), bitstring()} | {:error, binary(), bitstring(), bitstring()} def extract_from(<>) do case extract_script_pub_key(remaining) do {:ok, script_pub_key, remaining} -> output = %Output{value: value, script_pub_key: script_pub_key} {:ok, output, remaining} {:error, message, remaining_from_script, remaining} -> output = %Output{ value: value, script_pub_key: [], invalid_script: remaining_from_script, error_message: message } {:ok, output, remaining} end end @doc """ Encodes an output into a bitstring ## Examples iex> %BitcoinLib.Transaction.Output{ ...> script_pub_key: [ ...> %BitcoinLib.Script.Opcodes.Stack.Dup{}, ...> %BitcoinLib.Script.Opcodes.Crypto.Hash160{}, ...> %BitcoinLib.Script.Opcodes.Data{value: <<0xf86f0bc0a2232970ccdf4569815db500f1268361::160>>}, ...> %BitcoinLib.Script.Opcodes.BitwiseLogic.EqualVerify{}, ...> %BitcoinLib.Script.Opcodes.Crypto.CheckSig{} ...> ], ...> value: 129000000 ...> } |> BitcoinLib.Transaction.Output.encode <<0x4062b00700000000::64>> <> # value <<0x19::8>> <> # script_pub_key size <<0x76a914f86f0bc0a2232970ccdf4569815db500f126836188ac::200>> # script_pub_key """ @spec encode(Output.t()) :: bitstring() def encode(%Output{} = output) do {script_pub_key_size, script_pub_key} = output.script_pub_key |> Script.encode() <> end defp extract_script_pub_key(remaining) do %CompactInteger{value: script_pub_key_size, remaining: remaining} = CompactInteger.extract_from(remaining) case script_pub_key_size <= byte_size(remaining) do true -> script_pub_key_bit_size = script_pub_key_size * @byte <> = remaining case Script.parse(script_pub_key) do {:ok, script_pub_key} -> {:ok, script_pub_key, remaining} {:error, message} -> {:error, message, script_pub_key, remaining} end false -> {:error, "badly formatted script pub key", remaining} end end end