diff --git a/lib/extendedkey.ex b/lib/extendedkey.ex index a7275c4..a0db3bd 100644 --- a/lib/extendedkey.ex +++ b/lib/extendedkey.ex @@ -42,18 +42,21 @@ defmodule Bitcoinex.ExtendedKey do def new(), do: %__MODULE__{child_nums: []} + @spec serialize(t()) :: {:ok, binary} | {:error, String.t()} + def serialize(dp = %__MODULE__{}), do: to_bin(dp) + @spec to_string(t()) :: {:ok, String.t()} | {:error, String.t()} - def to_string(%__MODULE__{child_nums: path}), do: tto_string(path, "") + def to_string(%__MODULE__{child_nums: path}), do: path_to_string(path, "") - defp tto_string([], path_acc), do: {:ok, path_acc} + defp path_to_string([], path_acc), do: {:ok, path_acc} - defp tto_string([l | rest], path_acc) do + defp path_to_string([l | rest], path_acc) do cond do l == :any -> - tto_string(rest, path_acc <> "*/") + path_to_string(rest, path_acc <> "*/") l == :anyh -> - tto_string(rest, path_acc <> "*'/") + path_to_string(rest, path_acc <> "*'/") l > @max_hardened_child_num -> {:error, "index cannot be greater than #{@max_hardened_child_num}"} @@ -63,7 +66,7 @@ defmodule Bitcoinex.ExtendedKey do # hardened l >= @min_hardened_child_num -> - tto_string( + path_to_string( rest, path_acc <> (l @@ -74,31 +77,108 @@ defmodule Bitcoinex.ExtendedKey do # unhardened true -> - tto_string(rest, path_acc <> Integer.to_string(l) <> "/") + path_to_string(rest, path_acc <> Integer.to_string(l) <> "/") + end + end + + @spec to_bin(t()) :: {:ok, binary} | {:error, String.t()} + def to_bin(%__MODULE__{child_nums: child_nums}) do + try do + {:ok, to_bin(child_nums, <<>>)} + rescue + e in ArgumentError -> {:error, e.message} + end + end + + defp to_bin([], path_acc), do: path_acc + + defp to_bin([lvl | rest], path_acc) do + cond do + lvl == :any or lvl == :anyh -> + raise(ArgumentError, + message: "Derivation Path with wildcard cannot be encoded to binary." + ) + + lvl > @max_hardened_child_num -> + raise(ArgumentError, message: "index cannot be greater than #{@max_hardened_child_num}") + + lvl < @min_non_hardened_child_num -> + raise(ArgumentError, message: "index cannot be less than #{@min_non_hardened_child_num}") + + true -> + lvlbin = + lvl + |> :binary.encode_unsigned(:little) + |> Bitcoinex.Utils.pad(4, :trailing) + + to_bin(rest, path_acc <> lvlbin) end end + @spec parse(binary) :: {:ok, t()} | {:error, String.t()} + def parse(dp), do: from_bin(dp) + @spec from_string(String.t()) :: {:ok, t()} | {:error, String.t()} def from_string(pathstr) do try do - {:ok, %__MODULE__{child_nums: tfrom_string(String.split(pathstr, "/"))}} + {:ok, + %__MODULE__{ + child_nums: + pathstr + |> String.split("/") + |> path_from_string([]) + |> Enum.reverse() + }} rescue e in ArgumentError -> {:error, e.message} end end - defp tfrom_string(path_list) do + defp path_from_string(path_list, child_nums) do case path_list do - [] -> [] - [""] -> [] - ["m" | rest] -> tfrom_string(rest) - ["*" | rest] -> [:any | tfrom_string(rest)] - ["*'" | rest] -> [:anyh | tfrom_string(rest)] - ["*h" | rest] -> [:anyh | tfrom_string(rest)] - [i | rest] -> [str_to_level(i) | tfrom_string(rest)] + [] -> + child_nums + + [""] -> + child_nums + + ["m" | rest] -> + if child_nums != [] do + raise(ArgumentError, + message: "m can only be present at the begining of a derivation path." + ) + else + path_from_string(rest, child_nums) + end + + ["*" | rest] -> + path_from_string(rest, [:any | child_nums]) + + ["*'" | rest] -> + path_from_string(rest, [:anyh | child_nums]) + + ["*h" | rest] -> + path_from_string(rest, [:anyh | child_nums]) + + [i | rest] -> + path_from_string(rest, [str_to_level(i) | child_nums]) end end + @spec from_bin(binary) :: {:ok, t()} | {:error, String.t()} + def from_bin(bin) do + try do + {:ok, %__MODULE__{child_nums: Enum.reverse(from_bin(bin, []))}} + rescue + _e in ArgumentError -> {:error, "invalid binary encoding of derivation path"} + end + end + + defp from_bin(<<>>, child_nums), do: child_nums + + defp from_bin(<>, child_nums), + do: from_bin(bin, [level | child_nums]) + defp str_to_level(level) do {num, is_hardened} = case String.split(level, ["'", "h"]) do @@ -111,6 +191,7 @@ defmodule Bitcoinex.ExtendedKey do nnum = String.to_integer(num) + # TODO benchmark and make this two comparisons if nnum in @min_non_hardened_child_num..@max_non_hardened_child_num do if is_hardened do nnum + @min_hardened_child_num @@ -124,6 +205,8 @@ defmodule Bitcoinex.ExtendedKey do def add(%__MODULE__{child_nums: path1}, %__MODULE__{child_nums: path2}), do: %__MODULE__{child_nums: path1 ++ path2} + + def depth(%__MODULE__{child_nums: child_nums}), do: length(child_nums) end @type t :: %__MODULE__{ @@ -245,11 +328,25 @@ defmodule Bitcoinex.ExtendedKey do # PARSE & SERIALIZE @doc """ - parse_extended_key takes binary or string representation + parse! calls parse, which takes binary or string representation + of an extended key and parses it to an extended key object. + parse! raises ArgumentError on failure. + """ + @spec parse!(binary) :: t() + def parse!(xpub) do + case parse(xpub) do + {:ok, res} -> res + {:error, msg} -> raise(ArgumentError, message: msg) + end + end + + @doc """ + parse takes binary or string representation of an extended key and parses it to an extended key object + returns {:error, msg} on failure """ - @spec parse_extended_key(binary) :: {:ok, t()} | {:error, String.t()} - def parse_extended_key( + @spec parse(binary) :: {:ok, t()} | {:error, String.t()} + def parse( xkey = <> + ) do + xkey + |> Base58.append_checksum() + |> parse() + end + # parse from string - def parse_extended_key(xkey) do + def parse(xkey) do case Base58.decode(xkey) do {:error, _} -> {:error, "error parsing key"} @@ -292,7 +400,7 @@ defmodule Bitcoinex.ExtendedKey do {:ok, xkey} -> xkey |> Base58.append_checksum() - |> parse_extended_key() + |> parse() end end @@ -303,23 +411,33 @@ defmodule Bitcoinex.ExtendedKey do end @doc """ - serialize_extended_key takes an extended key + serialize takes an extended key and returns the binary """ - @spec serialize_extended_key(t()) :: binary - def serialize_extended_key(xkey) do - (xkey.prefix <> - xkey.depth <> xkey.parent_fingerprint <> xkey.child_num <> xkey.chaincode <> xkey.key) - |> Base58.append_checksum() + @spec serialize(t(), list({:with_checksum?, boolean})) :: binary + def serialize(xkey, opts \\ []) do + with_checksum? = Keyword.get(opts, :with_checksum?, true) + + extended_key_without_checksum_bin = + xkey.prefix <> + xkey.depth <> xkey.parent_fingerprint <> xkey.child_num <> xkey.chaincode <> xkey.key + + case with_checksum? do + true -> + Base58.append_checksum(extended_key_without_checksum_bin) + + false -> + extended_key_without_checksum_bin + end end @doc """ display returns the extended key as a string """ - @spec display_extended_key(t()) :: String.t() - def display_extended_key(xkey) do + @spec display(t()) :: String.t() + def display(xkey) do xkey - |> serialize_extended_key() + |> serialize() |> Base58.encode_base() end @@ -339,7 +457,7 @@ defmodule Bitcoinex.ExtendedKey do (prefix <> depth_fingerprint_childnum <> chaincode <> <<0>> <> key) |> Base58.append_checksum() - |> parse_extended_key() + |> parse() else {:error, "invalid extended private key prefix"} end @@ -368,7 +486,7 @@ defmodule Bitcoinex.ExtendedKey do |> Kernel.<>(xprv.chaincode) |> Kernel.<>(pubkey) |> Base58.append_checksum() - |> parse_extended_key() + |> parse() rescue _ in MatchError -> {:error, "invalid private key"} end @@ -475,7 +593,7 @@ defmodule Bitcoinex.ExtendedKey do (xkey.prefix <> child_depth <> fingerprint <> i <> child_chaincode <> Point.sec(pubkey)) |> Base58.append_checksum() - |> parse_extended_key() + |> parse() end end end @@ -526,7 +644,7 @@ defmodule Bitcoinex.ExtendedKey do (xkey.prefix <> child_depth <> fingerprint <> i <> child_chaincode <> <<0>> <> child_key) |> Base58.append_checksum() - |> parse_extended_key() + |> parse() rescue _ in MatchError -> {:error, "invalid private key in extended private key"} end diff --git a/lib/psbt.ex b/lib/psbt.ex index e0553f4..62cded6 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -11,6 +11,7 @@ defmodule Bitcoinex.PSBT do alias Bitcoinex.PSBT.Global alias Bitcoinex.PSBT.In alias Bitcoinex.PSBT.Out + alias Bitcoinex.Transaction alias Bitcoinex.Transaction.Utils, as: TxUtils @type t() :: %__MODULE__{} @@ -77,12 +78,21 @@ defmodule Bitcoinex.PSBT do |> Base.encode64() end - defp parse(<<@magic::big-size(32), @separator::big-size(8), psbt::binary>>) do + def parse(<<@magic::big-size(32), @separator::big-size(8), psbt::binary>>) do # key-value pairs for all global data {global, psbt} = Global.parse_global(psbt) - in_counter = length(global.unsigned_tx.inputs) + + {in_counter, out_counter} = + cond do + # either unsigned_tx must be present for v0 or in/out count must be present for v2 PSBT + global.unsigned_tx != nil -> + {length(global.unsigned_tx.inputs), length(global.unsigned_tx.outputs)} + + global.input_count != nil && global.output_count != nil -> + {global.input_count, global.output_count} + end + {inputs, psbt} = In.parse_inputs(psbt, in_counter) - out_counter = length(global.unsigned_tx.outputs) {outputs, _} = Out.parse_outputs(psbt, out_counter) {:ok, @@ -92,6 +102,29 @@ defmodule Bitcoinex.PSBT do outputs: outputs }} end + + @spec from_tx(Transaction.t()) :: {:ok, Bitcoinex.PSBT.t()} + def from_tx(tx) do + inputs = In.from_tx_inputs(tx.inputs, tx.witnesses) + outputs = Out.from_tx_outputs(tx.outputs) + + {:ok, + %PSBT{ + global: Global.from_tx(tx), + inputs: inputs, + outputs: outputs + }} + end + + def to_tx(psbt) do + tx = psbt.global.unsigned_tx + + inputs = In.populate_script_sigs(tx.inputs, psbt.inputs) + + witnesses = In.populate_witnesses(psbt.inputs) + + %Bitcoinex.Transaction{tx | witnesses: witnesses, inputs: inputs} + end end defmodule Bitcoinex.PSBT.Utils do @@ -99,6 +132,7 @@ defmodule Bitcoinex.PSBT.Utils do Contains utility functions used throughout PSBT serialization. """ alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.ExtendedKey.DerivationPath def parse_compact_size_value(key_value) do {len, key_value} = TxUtils.get_counter(key_value) @@ -127,6 +161,48 @@ defmodule Bitcoinex.PSBT.Utils do val_len = TxUtils.serialize_compact_size_unsigned_int(byte_size(val)) key_len <> key <> val_len <> val end + + def serialize_repeatable_fields(_, nil, _), do: <<>> + + def serialize_repeatable_fields(field, values, serialize_func) do + for(kv <- values, do: serialize_func.(field, kv)) + |> :erlang.list_to_binary() + end + + def parse_fingerprint_path(data) do + <> = data + {:ok, path} = DerivationPath.parse(path_bin) + {pfp, path} + end + + # reuse this elsewhere + @spec serialize_fingerprint_path(binary, DerivationPath.t()) :: binary + def serialize_fingerprint_path(pfp, path) do + {:ok, path_bin} = DerivationPath.serialize(path) + pfp <> path_bin + end + + def parse_leaf_hashes(value, leaf_hash_ct) do + leaf_hashes_byte_size = 32 * leaf_hash_ct + <> = value + + leaf_hashes = + leaf_hashes + |> :erlang.binary_to_list() + |> Enum.chunk_every(32) + |> Enum.map(&:erlang.list_to_binary/1) + + {leaf_hashes, value} + end + + @spec serialize_leaf_hashes(list(binary)) :: binary + def serialize_leaf_hashes(leaf_hashes) do + leaf_hashes_bin = Enum.reduce(leaf_hashes, <<>>, fn leaf_hash, acc -> acc <> leaf_hash end) + TxUtils.serialize_compact_size_unsigned_int(length(leaf_hashes)) <> leaf_hashes_bin + end + + def append(nil, item), do: [item] + def append(items, item), do: items ++ [item] end defmodule Bitcoinex.PSBT.Global do @@ -137,17 +213,29 @@ defmodule Bitcoinex.PSBT.Global do alias Bitcoinex.Transaction alias Bitcoinex.Transaction.Utils, as: TxUtils alias Bitcoinex.PSBT.Utils, as: PsbtUtils - alias Bitcoinex.Base58 + alias Bitcoinex.ExtendedKey + alias Bitcoinex.ExtendedKey.DerivationPath, as: DerivationPath defstruct [ :unsigned_tx, :xpub, + :tx_version, + :fallback_locktime, + :input_count, + :output_count, + :tx_modifiable, :version, - :proprietary + :proprietary, + :unknown ] @psbt_global_unsigned_tx 0x00 @psbt_global_xpub 0x01 + @psbt_global_tx_version 0x02 + @psbt_global_fallback_locktime 0x03 + @psbt_global_input_count 0x04 + @psbt_global_output_count 0x05 + @psbt_global_tx_modifiable 0x06 @psbt_global_version 0xFB @psbt_global_proprietary 0xFC @@ -155,13 +243,15 @@ defmodule Bitcoinex.PSBT.Global do PsbtUtils.parse_key_value(psbt, %Global{}, &parse/3) end + def from_tx(tx), do: %Global{unsigned_tx: tx} + # unsigned transaction defp parse(<<@psbt_global_unsigned_tx::big-size(8)>>, psbt, global) do {txn_len, psbt} = TxUtils.get_counter(psbt) <> = psbt - # todo, different decode function for txn, directly in bytes - case Transaction.decode(Base.encode16(txn_bytes, case: :lower)) do + + case Transaction.decode(txn_bytes) do {:ok, txn} -> {%Global{global | unsigned_tx: txn}, psbt} @@ -173,39 +263,62 @@ defmodule Bitcoinex.PSBT.Global do defp parse(<<@psbt_global_xpub::big-size(8), xpub::binary-size(78)>>, psbt, global) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - <> = value + {master, path} = PsbtUtils.parse_fingerprint_path(value) + {:ok, xpub} = ExtendedKey.parse(xpub) - indexes = for <>, do: chunk + if :binary.decode_unsigned(xpub.depth) != DerivationPath.depth(path), + do: + raise(ArgumentError, + message: "invalid xpub in PSBT: depth does not match number of indexes provided" + ) - global_xpub = - case global.xpub do - nil -> - [ - %{ - xpub: Base58.encode(xpub), - master_pfp: master, - derivation: indexes - } - ] + global_xpub = %{ + xpub: xpub, + pfp: master, + derivation: path + } - _ -> - global.xpub ++ - [ - %{ - xpub: Base58.encode(xpub), - master_pfp: master, - derivation: indexes - } - ] - end + global_xpubs = PsbtUtils.append(global.xpub, global_xpub) - global = %Global{global | xpub: global_xpub} + global = %Global{global | xpub: global_xpubs} {global, psbt} end - defp parse(<<@psbt_global_version::big-size(8)>>, psbt, global) do + defp parse(<<@psbt_global_tx_version::big-size(8)>>, psbt, global) do + {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) + global = %Global{global | tx_version: value} + {global, psbt} + end + + defp parse(<<@psbt_global_fallback_locktime::big-size(8)>>, psbt, global) do + {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) + global = %Global{global | fallback_locktime: value} + {global, psbt} + end + + defp parse(<<@psbt_global_input_count::big-size(8)>>, psbt, global) do + {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + {input_count, _} = TxUtils.get_counter(value) + global = %Global{global | input_count: input_count} + {global, psbt} + end + + defp parse(<<@psbt_global_output_count::big-size(8)>>, psbt, global) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + {output_count, _} = TxUtils.get_counter(value) + global = %Global{global | output_count: output_count} + {global, psbt} + end + + defp parse(<<@psbt_global_tx_modifiable::big-size(8)>>, psbt, global) do + {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) + global = %Global{global | tx_modifiable: value} + {global, psbt} + end + + defp parse(<<@psbt_global_version::big-size(8)>>, psbt, global) do + {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) global = %Global{global | version: value} {global, psbt} end @@ -222,29 +335,87 @@ defmodule Bitcoinex.PSBT.Global do defp serialize_kv(:xpub, value) when value != nil do key = <<@psbt_global_xpub::big-size(8)>> - {:ok, key_data} = Base58.decode(value.xpub) + key_data = ExtendedKey.serialize(value.xpub, with_checksum?: false) - val = - <> <> - (for(chunk <- value.derivation, do: <>) - |> :erlang.list_to_binary()) + val = PsbtUtils.serialize_fingerprint_path(value.pfp, value.derivation) PsbtUtils.serialize_kv(key <> key_data, val) end + defp serialize_kv(:tx_version, value) when value != nil do + PsbtUtils.serialize_kv(<<@psbt_global_tx_version::big-size(8)>>, <>) + end + + defp serialize_kv(:fallback_locktime, value) when value != nil do + PsbtUtils.serialize_kv( + <<@psbt_global_fallback_locktime::big-size(8)>>, + <> + ) + end + + defp serialize_kv(:input_count, value) when value != nil do + PsbtUtils.serialize_kv( + <<@psbt_global_input_count::big-size(8)>>, + TxUtils.serialize_compact_size_unsigned_int(value) + ) + end + + defp serialize_kv(:output_count, value) when value != nil do + PsbtUtils.serialize_kv( + <<@psbt_global_output_count::big-size(8)>>, + TxUtils.serialize_compact_size_unsigned_int(value) + ) + end + + defp serialize_kv(:tx_modifiable, value) when value != nil do + PsbtUtils.serialize_kv(<<@psbt_global_tx_modifiable::big-size(8)>>, <>) + end + + defp serialize_kv(:version, value) when value != nil do + PsbtUtils.serialize_kv( + <<@psbt_global_version::big-size(8)>>, + <> + ) + end + + defp serialize_kv(:proprietary, value) when value != nil do + PsbtUtils.serialize_kv(<<@psbt_global_proprietary::big-size(8)>>, value) + end + def serialize_global(global) do - # TODO: serialize all other fields in global. - serialized_global = serialize_kv(:unsigned_tx, global.unsigned_tx) - - bip32 = - if global.xpub != nil do - for(bip32 <- global.xpub, do: serialize_kv(:xpub, bip32)) - |> :erlang.list_to_binary() - else - <<>> - end + serialized_global = + Enum.reduce( + [ + :unsigned_tx, + :xpub, + :tx_version, + :fallback_locktime, + :input_count, + :output_count, + :tx_modifiable, + :version, + :proprietary, + :unknown + ], + <<>>, + fn k, acc -> + case Map.get(global, k) do + nil -> + acc - serialized_global <> bip32 <> <<0x00::big-size(8)>> + [] -> + acc + + v = [_ | _] -> + acc <> PsbtUtils.serialize_repeatable_fields(k, v, &serialize_kv/2) + + v -> + acc <> serialize_kv(k, v) + end + end + ) + + serialized_global <> <<0x00::big-size(8)>> end end @@ -258,6 +429,7 @@ defmodule Bitcoinex.PSBT.In do alias Bitcoinex.PSBT.In alias Bitcoinex.PSBT.Utils, as: PsbtUtils alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.Script defstruct [ :non_witness_utxo, @@ -270,7 +442,23 @@ defmodule Bitcoinex.PSBT.In do :final_scriptsig, :final_scriptwitness, :por_commitment, - :proprietary + :ripemd160, + :sha256, + :hash160, + :hash256, + :previous_txid, + :output_index, + :sequence, + :required_time_locktime, + :required_height_locktime, + :tap_key_sig, + :tap_script_sig, + :tap_leaf_script, + :tap_bip32_derivation, + :tap_internal_key, + :tap_merkle_root, + :proprietary, + :unknown ] @psbt_in_non_witness_utxo 0x00 @@ -283,6 +471,21 @@ defmodule Bitcoinex.PSBT.In do @psbt_in_final_scriptsig 0x07 @psbt_in_final_scriptwitness 0x08 @psbt_in_por_commitment 0x09 + @psbt_in_ripemd160 0x0A + @psbt_in_sha256 0x0B + @psbt_in_hash160 0x0C + @psbt_in_hash256 0x0D + @psbt_in_previous_txid 0x0E + @psbt_in_output_index 0x0F + @psbt_in_sequence 0x10 + @psbt_in_required_time_locktime 0x11 + @psbt_in_required_height_locktime 0x12 + @psbt_in_tap_key_sig 0x13 + @psbt_in_tap_script_sig 0x14 + @psbt_in_tap_leaf_script 0x15 + @psbt_in_tap_bip32_derivation 0x16 + @psbt_in_tap_internal_key 0x17 + @psbt_in_tap_merkle_root 0x18 @psbt_in_proprietary 0xFC def parse_inputs(psbt, num_inputs) do @@ -290,6 +493,39 @@ defmodule Bitcoinex.PSBT.In do |> parse_input([], num_inputs) end + @spec from_tx_inputs(list(Transaction.In.t()), list(Transaction.Witness.t())) :: list() + def from_tx_inputs(tx_inputs, tx_witnesses) do + inputs_witnesses = Enum.zip(tx_inputs, tx_witnesses) + + Enum.reduce(inputs_witnesses, [], fn {input, witness}, acc -> + [ + %In{ + final_scriptsig: input.script_sig, + final_scriptwitness: witness + } + | acc + ] + end) + |> Enum.reverse() + end + + def populate_script_sigs(tx_inputs, psbt_inputs) do + inputs = Enum.zip(tx_inputs, psbt_inputs) + + Enum.reduce(inputs, [], fn {tx_in, psbt_in}, acc -> + [%Transaction.In{tx_in | script_sig: psbt_in.final_scriptsig} | acc] + end) + |> Enum.reverse() + end + + @spec populate_witnesses(list(In)) :: list(binary) + def populate_witnesses(psbt_inputs) do + Enum.reduce(psbt_inputs, [], fn psbt_in, acc -> + [psbt_in.final_scriptwitness | acc] + end) + |> Enum.reverse() + end + defp serialize_kv(:non_witness_utxo, value) when value != nil do PsbtUtils.serialize_kv(<<@psbt_in_non_witness_utxo::big-size(8)>>, TxUtils.serialize(value)) end @@ -312,7 +548,7 @@ defmodule Bitcoinex.PSBT.In do end defp serialize_kv(:sighash_type, value) when value != nil do - PsbtUtils.serialize_kv(<<@psbt_in_sighash_type::big-size(8)>>, value) + PsbtUtils.serialize_kv(<<@psbt_in_sighash_type::big-size(8)>>, <>) end defp serialize_kv(:final_scriptsig, value) when value != nil do @@ -346,14 +582,116 @@ defmodule Bitcoinex.PSBT.In do defp serialize_kv(:bip32_derivation, value) when value != nil do key_data = Base.decode16!(value.public_key, case: :lower) - val = - <> <> - (for(chunk <- value.derivation, do: <>) - |> :erlang.list_to_binary()) + val = PsbtUtils.serialize_fingerprint_path(value.pfp, value.derivation) PsbtUtils.serialize_kv(<<@psbt_in_bip32_derivation::big-size(8)>> <> key_data, val) end + defp serialize_kv(:por_commitment, value) when value != nil do + PsbtUtils.serialize_kv(<<@psbt_in_por_commitment::big-size(8)>>, value) + end + + defp serialize_kv(:in_ripemd160, value) when value != nil do + PsbtUtils.serialize_kv( + <<@psbt_in_ripemd160::big-size(8), value.hash::binary-size(20)>>, + value.preimage + ) + end + + defp serialize_kv(:in_sha256, value) when value != nil do + PsbtUtils.serialize_kv( + <<@psbt_in_sha256::big-size(8), value.hash::binary-size(32)>>, + value.preimage + ) + end + + defp serialize_kv(:in_hash160, value) when value != nil do + PsbtUtils.serialize_kv( + <<@psbt_in_hash160::big-size(8), value.hash::binary-size(20)>>, + value.preimage + ) + end + + defp serialize_kv(:in_hash256, value) when value != nil do + PsbtUtils.serialize_kv( + <<@psbt_in_hash256::big-size(8), value.hash::binary-size(32)>>, + value.preimage + ) + end + + defp serialize_kv(:previous_txid, value) when value != nil do + PsbtUtils.serialize_kv(<<@psbt_in_previous_txid::big-size(8)>>, value) + end + + defp serialize_kv(:output_index, value) when value != nil do + PsbtUtils.serialize_kv(<<@psbt_in_output_index::big-size(8)>>, <>) + end + + defp serialize_kv(:sequence, value) when value != nil do + PsbtUtils.serialize_kv(<<@psbt_in_sequence::big-size(8)>>, <>) + end + + defp serialize_kv(:required_time_locktime, value) when value != nil do + PsbtUtils.serialize_kv( + <<@psbt_in_required_time_locktime::big-size(8)>>, + <> + ) + end + + defp serialize_kv(:required_height_locktime, value) when value != nil do + PsbtUtils.serialize_kv( + <<@psbt_in_required_height_locktime::big-size(8)>>, + <> + ) + end + + defp serialize_kv(:tap_key_sig, value) when value != nil do + PsbtUtils.serialize_kv(<<@psbt_in_tap_key_sig::big-size(8)>>, value) + end + + defp serialize_kv(:tap_script_sig, value) when value != nil do + PsbtUtils.serialize_kv( + <<@psbt_in_tap_script_sig::big-size(8), value.pubkey::binary, value.leaf_hash::binary>>, + value.signature + ) + end + + defp serialize_kv(:tap_leaf_script, value) when value != nil do + # TODO:taproot make this use TapLeaf + script_bytes = Script.serialize_script(value.script) + + PsbtUtils.serialize_kv( + <<@psbt_in_tap_leaf_script::big-size(8), value.control_block::binary>>, + script_bytes <> <> + ) + end + + defp serialize_kv(:tap_bip32_derivation, value) when value != nil do + leaf_hashes = PsbtUtils.serialize_leaf_hashes(value.leaf_hashes) + fingerprint_path = PsbtUtils.serialize_fingerprint_path(value.pfp, value.derivation) + + PsbtUtils.serialize_kv( + <<@psbt_in_tap_bip32_derivation::big-size(8), value.pubkey::binary>>, + leaf_hashes <> fingerprint_path + ) + end + + defp serialize_kv(:tap_internal_key, value) when value != nil do + PsbtUtils.serialize_kv(<<@psbt_in_tap_internal_key::big-size(8)>>, value) + end + + defp serialize_kv(:tap_merkle_root, value) when value != nil do + PsbtUtils.serialize_kv(<<@psbt_in_tap_merkle_root::big-size(8)>>, value) + end + + defp serialize_kv(:proprietary, value) when value != nil do + PsbtUtils.serialize_kv(<<@psbt_in_proprietary::big-size(8)>>, value) + end + + defp serialize_kv(:unknown, %{key: k, value: v}) do + PsbtUtils.serialize_kv(k, v) + end + defp serialize_kv(_key, _value) do <<>> end @@ -376,11 +714,31 @@ defmodule Bitcoinex.PSBT.In do [ :non_witness_utxo, :witness_utxo, - :sighash_type, :partial_sig, + :sighash_type, :redeem_script, + :witness_script, + :bip32_derivation, :final_scriptsig, - :witness_script + :final_scriptwitness, + :por_commitment, + :ripemd160, + :sha256, + :hash160, + :hash256, + :previous_txid, + :output_index, + :sequence, + :required_time_locktime, + :required_height_locktime, + :tap_key_sig, + :tap_script_sig, + :tap_leaf_script, + :tap_bip32_derivation, + :tap_internal_key, + :tap_merkle_root, + :proprietary, + :unknown ], <<>>, fn k, acc -> @@ -388,24 +746,19 @@ defmodule Bitcoinex.PSBT.In do nil -> acc + [] -> + acc + + v = [_ | _] -> + acc <> PsbtUtils.serialize_repeatable_fields(k, v, &serialize_kv/2) + v -> acc <> serialize_kv(k, v) end end ) - bip32 = - if input.bip32_derivation != nil do - for(bip32 <- input.bip32_derivation, do: serialize_kv(:bip32_derivation, bip32)) - |> :erlang.list_to_binary() - else - <<>> - end - - serialized_input = - serialized_input <> - bip32 <> - serialize_kv(:final_scriptwitness, input.final_scriptwitness) <> <<0x00::big-size(8)>> + serialized_input = serialized_input <> <<0x00::big-size(8)>> serialize_input(inputs, serialized_inputs <> serialized_input) end @@ -414,26 +767,18 @@ defmodule Bitcoinex.PSBT.In do defp parse_input(psbt, inputs, num_inputs) do case PsbtUtils.parse_key_value(psbt, %In{}, &parse/3) do + # why are we not adding an empty in here? {nil, psbt} -> parse_input(psbt, inputs, num_inputs - 1) {input, psbt} -> - input = - case input do - %{bip32_derivation: bip32_derivation} when is_list(bip32_derivation) -> - %{input | bip32_derivation: Enum.reverse(bip32_derivation)} - - _ -> - input - end - parse_input(psbt, [input | inputs], num_inputs - 1) end end defp parse(<<@psbt_in_non_witness_utxo::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - {:ok, txn} = Transaction.decode(Base.encode16(value, case: :lower)) + {:ok, txn} = Transaction.decode(value) input = %In{input | non_witness_utxo: txn} {input, psbt} end @@ -448,19 +793,19 @@ defmodule Bitcoinex.PSBT.In do defp parse(<<@psbt_in_partial_sig::big-size(8), public_key::binary-size(33)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{ - input - | partial_sig: %{ - public_key: Base.encode16(public_key, case: :lower), - signature: Base.encode16(value, case: :lower) - } + partial_sig = %{ + public_key: Base.encode16(public_key, case: :lower), + signature: Base.encode16(value, case: :lower) } + partial_sigs = PsbtUtils.append(input.partial_sig, partial_sig) + input = %In{input | partial_sig: partial_sigs} + {input, psbt} end defp parse(<<@psbt_in_sighash_type::big-size(8)>>, psbt, input) do - {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) input = %In{input | sighash_type: value} {input, psbt} end @@ -480,30 +825,15 @@ defmodule Bitcoinex.PSBT.In do defp parse(<<@psbt_in_bip32_derivation::big-size(8), public_key::binary-size(33)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - <> = value - indexes = for <>, do: chunk + {pfp, path} = PsbtUtils.parse_fingerprint_path(value) - bip32_derivation = - case input.bip32_derivation do - nil -> - [ - %{ - public_key: Base.encode16(public_key, case: :lower), - pfp: pfp, - derivation: indexes - } - ] + derivation = %{ + public_key: Base.encode16(public_key, case: :lower), + pfp: pfp, + derivation: path + } - _ -> - [ - %{ - public_key: Base.encode16(public_key, case: :lower), - pfp: pfp, - derivation: indexes - } - | input.bip32_derivation - ] - end + bip32_derivation = PsbtUtils.append(input.bip32_derivation, derivation) input = %In{input | bip32_derivation: bip32_derivation} {input, psbt} @@ -521,6 +851,179 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end + defp parse(<<@psbt_in_ripemd160::big-size(8), hash::binary-size(20)>>, psbt, input) do + # TODO:validation check hash + {preimage, psbt} = PsbtUtils.parse_compact_size_value(psbt) + + data = %{ + hash: hash, + preimage: preimage + } + + ripemd160s = PsbtUtils.append(input.ripemd160, data) + + input = %In{input | ripemd160: ripemd160s} + {input, psbt} + end + + defp parse(<<@psbt_in_sha256::big-size(8), hash::binary-size(32)>>, psbt, input) do + # TODO:validation check hash + {preimage, psbt} = PsbtUtils.parse_compact_size_value(psbt) + + data = %{ + hash: hash, + preimage: preimage + } + + sha256s = PsbtUtils.append(input.sha256, data) + input = %In{input | sha256: sha256s} + {input, psbt} + end + + defp parse(<<@psbt_in_hash160::big-size(8), hash::binary-size(20)>>, psbt, input) do + # TODO:validation check hash + {preimage, psbt} = PsbtUtils.parse_compact_size_value(psbt) + + data = %{ + hash: hash, + preimage: preimage + } + + hash160s = PsbtUtils.append(input.hash160, data) + input = %In{input | hash160: hash160s} + {input, psbt} + end + + defp parse(<<@psbt_in_hash256::big-size(8), hash::binary-size(32)>>, psbt, input) do + # TODO:validation check hash + {preimage, psbt} = PsbtUtils.parse_compact_size_value(psbt) + + data = %{ + hash: hash, + preimage: preimage + } + + hash256s = PsbtUtils.append(input.hash256, data) + input = %In{input | hash256: hash256s} + {input, psbt} + end + + defp parse(<<@psbt_in_previous_txid::big-size(8)>>, psbt, input) do + {value = <<_::binary-size(32)>>, psbt} = PsbtUtils.parse_compact_size_value(psbt) + input = %In{input | previous_txid: value} + {input, psbt} + end + + defp parse(<<@psbt_in_output_index::big-size(8)>>, psbt, input) do + {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) + input = %In{input | output_index: value} + {input, psbt} + end + + defp parse(<<@psbt_in_sequence::big-size(8)>>, psbt, input) do + {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) + input = %In{input | sequence: value} + {input, psbt} + end + + defp parse(<<@psbt_in_required_time_locktime::big-size(8)>>, psbt, input) do + # TODO:validation must be > 500_000_000 + {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) + input = %In{input | required_time_locktime: value} + {input, psbt} + end + + defp parse(<<@psbt_in_required_height_locktime::big-size(8)>>, psbt, input) do + # TODO:validation must be < 500_000_000 + {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) + input = %In{input | required_height_locktime: value} + {input, psbt} + end + + defp parse(<<@psbt_in_tap_key_sig::big-size(8)>>, psbt, input) do + # TODO:validation validate script len (64|65) + {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + input = %In{input | tap_key_sig: value} + {input, psbt} + end + + defp parse( + <<@psbt_in_tap_script_sig::big-size(8), pubkey::binary-size(32), + leaf_hash::binary-size(32)>>, + psbt, + input + ) do + # TODO:validation validate sig len (64|65) + {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + + data = %{ + pubkey: pubkey, + leaf_hash: leaf_hash, + signature: value + } + + tap_script_sigs = PsbtUtils.append(input.tap_script_sig, data) + + input = %In{input | tap_script_sig: tap_script_sigs} + {input, psbt} + end + + defp parse(<<@psbt_in_tap_leaf_script::big-size(8), control_block::binary>>, psbt, input) do + {tapleaf, psbt} = PsbtUtils.parse_compact_size_value(psbt) + + {leaf_version, script_bytes} = + tapleaf + |> :erlang.binary_to_list() + |> List.pop_at(-1) + + script_bytes = :erlang.list_to_binary(script_bytes) + + {:ok, script} = Script.parse_script(script_bytes) + + data = %{ + # TODO:taproot make this a TapLeaf object + leaf_version: leaf_version, + script: script, + control_block: control_block + } + + tap_leaf_scripts = PsbtUtils.append(input.tap_leaf_script, data) + input = %In{input | tap_leaf_script: tap_leaf_scripts} + {input, psbt} + end + + defp parse(<<@psbt_in_tap_bip32_derivation::big-size(8), pubkey::binary-size(32)>>, psbt, input) do + {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + + {leaf_hash_ct, value} = TxUtils.get_counter(value) + {leaf_hashes, value} = PsbtUtils.parse_leaf_hashes(value, leaf_hash_ct) + {pfp, path} = PsbtUtils.parse_fingerprint_path(value) + + derivation = %{ + pubkey: pubkey, + leaf_hashes: leaf_hashes, + pfp: pfp, + derivation: path + } + + tap_bip32_derivation = PsbtUtils.append(input.tap_bip32_derivation, derivation) + + input = %In{input | tap_bip32_derivation: tap_bip32_derivation} + {input, psbt} + end + + defp parse(<<@psbt_in_tap_internal_key::big-size(8)>>, psbt, input) do + {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + input = %In{input | tap_internal_key: value} + {input, psbt} + end + + defp parse(<<@psbt_in_tap_merkle_root::big-size(8)>>, psbt, input) do + {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + input = %In{input | tap_merkle_root: value} + {input, psbt} + end + defp parse(<<@psbt_in_proprietary::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) input = %In{input | proprietary: value} @@ -533,6 +1036,23 @@ defmodule Bitcoinex.PSBT.In do input = %In{input | final_scriptwitness: value} {input, psbt} end + + defp parse(key, psbt, input) do + {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + kv = %{key: key, value: value} + + unknown = + case input.unknown do + nil -> + [kv] + + _ -> + input.unknown ++ [kv] + end + + input = %In{input | unknown: unknown} + {input, psbt} + end end defmodule Bitcoinex.PSBT.Out do @@ -541,17 +1061,30 @@ defmodule Bitcoinex.PSBT.Out do """ alias Bitcoinex.PSBT.Out alias Bitcoinex.PSBT.Utils, as: PsbtUtils + alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.Script defstruct [ :redeem_script, :witness_script, :bip32_derivation, + :amount, + :script, + :tap_internal_key, + :tap_tree, + :tap_bip32_derivation, :proprietary ] @psbt_out_redeem_script 0x00 @psbt_out_scriptwitness 0x01 @psbt_out_bip32_derivation 0x02 + @psbt_out_amount 0x03 + @psbt_out_script 0x04 + @psbt_out_tap_internal_key 0x05 + @psbt_out_tap_tree 0x06 + @psbt_out_tap_bip32_derivation 0x07 + @psbt_out_proprietary 0xFC def serialize_outputs(outputs) when is_list(outputs) and length(outputs) > 0 do serialize_output(outputs, <<>>) @@ -561,6 +1094,11 @@ defmodule Bitcoinex.PSBT.Out do <<>> end + def from_tx_outputs(tx_outputs) do + Enum.reduce(tx_outputs, [], fn _, acc -> [%Out{} | acc] end) + |> Enum.reverse() + end + defp serialize_kv(:redeem_script, value) when value != nil do PsbtUtils.serialize_kv( <<@psbt_out_redeem_script::big-size(8)>>, @@ -577,44 +1115,82 @@ defmodule Bitcoinex.PSBT.Out do defp serialize_kv(:bip32_derivation, value) when value != nil do key_data = Base.decode16!(value.public_key, case: :lower) - - val = - <> <> - (for(chunk <- value.derivation, do: <>) - |> :erlang.list_to_binary()) + val = PsbtUtils.serialize_fingerprint_path(value.pfp, value.derivation) PsbtUtils.serialize_kv(<<@psbt_out_bip32_derivation::big-size(8)>> <> key_data, val) end + defp serialize_kv(:amount, value) when value != nil do + PsbtUtils.serialize_kv(<<@psbt_out_amount::big-size(8)>>, <>) + end + + defp serialize_kv(:script, value) when value != nil do + PsbtUtils.serialize_kv(<<@psbt_out_script::big-size(8)>>, Script.serialize_script(value)) + end + + defp serialize_kv(:tap_internal_key, value) when value != nil do + PsbtUtils.serialize_kv(<<@psbt_out_tap_internal_key::big-size(8)>>, value) + end + + defp serialize_kv(:tap_tree, value) when value != nil do + tree = serialize_tap_tree(value.leaves) + PsbtUtils.serialize_kv(<<@psbt_out_tap_tree::big-size(8)>>, tree) + end + + defp serialize_kv(:tap_bip32_derivation, value) when value != nil do + key = <<@psbt_out_tap_bip32_derivation::big-size(8), value.pubkey::binary>> + leaf_hashes = PsbtUtils.serialize_leaf_hashes(value.leaf_hashes) + fingerprint_path = PsbtUtils.serialize_fingerprint_path(value.pfp, value.derivation) + + PsbtUtils.serialize_kv(key, leaf_hashes <> fingerprint_path) + end + + defp serialize_kv(:proprietary, value) when value != nil do + PsbtUtils.serialize_kv(<<@psbt_out_proprietary::big-size(8)>>, value) + end + defp serialize_kv(_key, _value) do <<>> end - defp serialize_output([], serialize_outputs), do: serialize_outputs + defp serialize_output([], serialized_outputs), do: serialized_outputs defp serialize_output(outputs, serialized_outputs) do [output | outputs] = outputs serialized_output = - case output do - %Out{bip32_derivation: nil, proprietary: nil, redeem_script: nil, witness_script: nil} -> - <<0x00::big-size(8)>> + Enum.reduce( + [ + :redeem_script, + :witness_script, + :bip32_derivation, + :amount, + :script, + :tap_internal_key, + :tap_tree, + :tap_bip32_derivation, + :proprietary, + :unknown + ], + <<>>, + fn k, acc -> + case Map.get(output, k) do + nil -> + acc - _ -> - serialized_output = - serialize_kv(:redeem_script, output.redeem_script) <> - serialize_kv(:witness_script, output.witness_script) - - bip32 = - if output.bip32_derivation != nil do - for(bip32 <- output.bip32_derivation, do: serialize_kv(:bip32_derivation, bip32)) - |> :erlang.list_to_binary() - else - <<>> - end - - serialized_output <> bip32 <> <<0x00::big-size(8)>> - end + [] -> + acc + + v = [_ | _] -> + acc <> PsbtUtils.serialize_repeatable_fields(k, v, &serialize_kv/2) + + v -> + acc <> serialize_kv(k, v) + end + end + ) + + serialized_output = serialized_output <> <<0x00::big-size(8)>> serialize_output(outputs, serialized_outputs <> serialized_output) end @@ -626,27 +1202,8 @@ defmodule Bitcoinex.PSBT.Out do defp parse_output(psbt, outputs, 0), do: {Enum.reverse(outputs), psbt} defp parse_output(psbt, outputs, num_outputs) do - case PsbtUtils.parse_key_value(psbt, %Out{}, &parse/3) do - {output = %Out{ - bip32_derivation: nil, - proprietary: nil, - redeem_script: nil, - witness_script: nil - }, psbt} -> - parse_output(psbt, [output | outputs], num_outputs - 1) - - {output, psbt} -> - output = - case output do - %{bip32_derivation: bip32_derivation} when is_list(bip32_derivation) -> - %{output | bip32_derivation: Enum.reverse(bip32_derivation)} - - _ -> - output - end - - parse_output(psbt, [output | outputs], num_outputs - 1) - end + {output, psbt} = PsbtUtils.parse_key_value(psbt, %Out{}, &parse/3) + parse_output(psbt, [output | outputs], num_outputs - 1) end defp parse(<<@psbt_out_redeem_script::big-size(8)>>, psbt, output) do @@ -668,36 +1225,103 @@ defmodule Bitcoinex.PSBT.Out do ) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - <> = value - indexes = for <>, do: chunk + {pfp, path} = PsbtUtils.parse_fingerprint_path(value) - bip32_derivation = - case output.bip32_derivation do - nil -> - [ - %{ - public_key: Base.encode16(public_key, case: :lower), - pfp: pfp, - derivation: indexes - } - ] + derivation = %{ + public_key: Base.encode16(public_key, case: :lower), + pfp: pfp, + derivation: path + } - _ -> - [ - %{ - public_key: Base.encode16(public_key, case: :lower), - pfp: pfp, - derivation: indexes - } - | output.bip32_derivation - ] - end + bip32_derivation = PsbtUtils.append(output.bip32_derivation, derivation) + output = %Out{output | bip32_derivation: bip32_derivation} + {output, psbt} + end + + defp parse(<<@psbt_out_amount::big-size(8)>>, psbt, output) do + {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) + output = %Out{output | amount: amount} + {output, psbt} + end + + defp parse(<<@psbt_out_script::big-size(8)>>, psbt, output) do + {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + {:ok, script} = Script.parse_script(value) + output = %Out{output | script: script} + {output, psbt} + end + + defp parse(<<@psbt_out_tap_internal_key::big-size(8)>>, psbt, output) do + {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + output = %Out{output | tap_internal_key: value} + {output, psbt} + end + + defp parse(<<@psbt_out_tap_tree::big-size(8)>>, psbt, output) do + {tree, psbt} = PsbtUtils.parse_compact_size_value(psbt) + leaves = parse_tap_tree(tree, []) + # hack to ensure tap_tree is not treated like a repeatable field + output = %Out{output | tap_tree: %{leaves: leaves}} + {output, psbt} + end - output = %Out{ - output - | bip32_derivation: bip32_derivation + defp parse( + <<@psbt_out_tap_bip32_derivation::big-size(8), pubkey::binary-size(32)>>, + psbt, + output + ) do + {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + + {leaf_hash_ct, value} = TxUtils.get_counter(value) + {leaf_hashes, value} = PsbtUtils.parse_leaf_hashes(value, leaf_hash_ct) + {pfp, path} = PsbtUtils.parse_fingerprint_path(value) + + derivation = %{ + pubkey: pubkey, + leaf_hashes: leaf_hashes, + pfp: pfp, + derivation: path } + tap_bip32_derivation = PsbtUtils.append(output.tap_bip32_derivation, derivation) + output = %Out{output | tap_bip32_derivation: tap_bip32_derivation} + {output, psbt} + end + + defp parse(<<@psbt_out_proprietary::big-size(8)>>, psbt, output) do + {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + output = %Out{output | proprietary: value} {output, psbt} end + + defp parse_tap_tree(<<>>, scripts), do: Enum.reverse(scripts) + + defp parse_tap_tree(tree, scripts) do + <> = tree + {script, tree} = PsbtUtils.parse_compact_size_value(rest) + {:ok, script} = Script.parse_script(script) + + data = %{ + # TODO:taproot make this TapLeaf + depth: depth, + leaf_version: leaf_version, + script: script + } + + # TODO:taproot ideally we can build an actual binary tree not just a list. + # But this is only useful once taproot is merged in + parse_tap_tree(tree, [data | scripts]) + end + + defp serialize_tap_tree(leaves) do + Enum.reduce(leaves, <<>>, fn leaf, acc -> + # TODO:taproot use Script.serialize_with_compact_size + script_bytes = Script.serialize_script(leaf.script) + + acc <> + <> <> + TxUtils.serialize_compact_size_unsigned_int(byte_size(script_bytes)) <> + script_bytes + end) + end end diff --git a/lib/script.ex b/lib/script.ex index 342a65d..930ac24 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -410,7 +410,7 @@ defmodule Bitcoinex.Script do @doc """ extract_multi_policy takes in a raw multisig script and returns the m, the - number of signatures required and the n authorized public keys. + number of signatures required, and the n authorized public keys. """ @spec extract_multi_policy(t()) :: {:ok, non_neg_integer(), list(Point.t())} | {:error, String.t()} diff --git a/lib/transaction.ex b/lib/transaction.ex index 1956d03..7e72972 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -18,6 +18,7 @@ defmodule Bitcoinex.Transaction do lock_time: non_neg_integer() } + # TODO refactor witnesses into input fields defstruct [ :version, :inputs, @@ -46,19 +47,23 @@ defmodule Bitcoinex.Transaction do @doc """ Decodes a transaction in a hex encoded string into binary. """ - def decode(tx_hex) when is_binary(tx_hex) do - case Base.decode16(tx_hex, case: :lower) do - {:ok, tx_bytes} -> - case parse(tx_bytes) do - {:ok, txn} -> - {:ok, txn} + def decode(serialized_tx) when is_binary(serialized_tx) do + tx_bytes = + case Base.decode16(serialized_tx, case: :lower) do + {:ok, tx_bytes} -> + tx_bytes + + # if decoding fails, attempt to parse as if serialized_tx is already binary. + :error -> + serialized_tx + end - :error -> - {:error, :parse_error} - end + case parse(tx_bytes) do + {:ok, txn} -> + {:ok, txn} :error -> - {:error, :decode_error} + {:error, :parse_error} end end @@ -234,7 +239,7 @@ defmodule Bitcoinex.Transaction.Witness do [witness | witnesses] = witnesses serialized_witness = - if Enum.empty?(witness.txinwitness) do + if witness == nil || Enum.empty?(witness.txinwitness) do <<0x0::big-size(8)>> else stack_len = TxUtils.serialize_compact_size_unsigned_int(length(witness.txinwitness)) diff --git a/test/extendedkey_test.exs b/test/extendedkey_test.exs index 269320f..0ca4958 100644 --- a/test/extendedkey_test.exs +++ b/test/extendedkey_test.exs @@ -122,22 +122,25 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L" } - @derivation_paths_to_strings [ + @derivation_paths_to_serialize [ %{ str: "84/0/0/2/1/", - deriv: %ExtendedKey.DerivationPath{child_nums: [84, 0, 0, 2, 1]} + deriv: %ExtendedKey.DerivationPath{child_nums: [84, 0, 0, 2, 1]}, + bin: <<84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0>> }, %{ str: "84'/0'/", deriv: %ExtendedKey.DerivationPath{ child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num] - } + }, + bin: <<84, 0, 0, 128, 0, 0, 0, 128>> }, %{ str: "84'/0'/", deriv: %ExtendedKey.DerivationPath{ child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num] - } + }, + bin: <<84, 0, 0, 128, 0, 0, 0, 128>> }, %{ str: "84'/0'/1/2/2147483647/", @@ -149,14 +152,16 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do 2, 2_147_483_647 ] - } + }, + bin: <<84, 0, 0, 128, 0, 0, 0, 128, 1, 0, 0, 0, 2, 0, 0, 0, 255, 255, 255, 127>> } ] - @strings_to_derivation_paths [ + @derivation_paths_to_parse [ %{ str: "84/0/0/2/1/", - deriv: %ExtendedKey.DerivationPath{child_nums: [84, 0, 0, 2, 1]} + deriv: %ExtendedKey.DerivationPath{child_nums: [84, 0, 0, 2, 1]}, + bin: <<84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0>> }, %{ str: "m/84'/0'/0'/2/1", @@ -168,31 +173,36 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do 2, 1 ] - } + }, + bin: <<84, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 128, 2, 0, 0, 0, 1, 0, 0, 0>> }, %{ str: "m/84'/0'/", deriv: %ExtendedKey.DerivationPath{ child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num] - } + }, + bin: <<84, 0, 0, 128, 0, 0, 0, 128>> }, %{ str: "m/84'/0'", deriv: %ExtendedKey.DerivationPath{ child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num] - } + }, + bin: <<84, 0, 0, 128, 0, 0, 0, 128>> }, %{ str: "84'/0'", deriv: %ExtendedKey.DerivationPath{ child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num] - } + }, + bin: <<84, 0, 0, 128, 0, 0, 0, 128>> }, %{ str: "84'/0'/", deriv: %ExtendedKey.DerivationPath{ child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num] - } + }, + bin: <<84, 0, 0, 128, 0, 0, 0, 128>> }, %{ str: "84'/0'/1/2/2147483647", @@ -204,7 +214,8 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do 2, 2_147_483_647 ] - } + }, + bin: <<84, 0, 0, 128, 0, 0, 0, 128, 1, 0, 0, 0, 2, 0, 0, 0, 255, 255, 255, 127>> }, %{ str: "m/84h/0h/0h/2/1", @@ -216,25 +227,29 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do 2, 1 ] - } + }, + bin: <<84, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 128, 2, 0, 0, 0, 1, 0, 0, 0>> }, %{ str: "m/84h/0h/", deriv: %ExtendedKey.DerivationPath{ child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num] - } + }, + bin: <<84, 0, 0, 128, 0, 0, 0, 128>> }, %{ str: "84h/0h", deriv: %ExtendedKey.DerivationPath{ child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num] - } + }, + bin: <<84, 0, 0, 128, 0, 0, 0, 128>> }, %{ str: "84h/0h/", deriv: %ExtendedKey.DerivationPath{ child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num] - } + }, + bin: <<84, 0, 0, 128, 0, 0, 0, 128>> } ] @@ -252,15 +267,15 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do # Extended Key Testing - describe "parse_extended_key/1" do + describe "parse/1" do test "successfully parse extended xprv" do t = @bip32_test_case_1 # priv - assert ExtendedKey.parse_extended_key(t.xprv_m) == {:ok, t.xprv_m_obj} - assert ExtendedKey.display_extended_key(t.xprv_m_obj) == t.xprv_m + assert ExtendedKey.parse(t.xprv_m) == {:ok, t.xprv_m_obj} + assert ExtendedKey.display(t.xprv_m_obj) == t.xprv_m # pub - assert ExtendedKey.parse_extended_key(t.xpub_m) == {:ok, t.xpub_m_obj} - assert ExtendedKey.display_extended_key(t.xpub_m_obj) == t.xpub_m + assert ExtendedKey.parse(t.xpub_m) == {:ok, t.xpub_m_obj} + assert ExtendedKey.display(t.xpub_m_obj) == t.xpub_m end end @@ -276,100 +291,100 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do test "BIP32 tests 1: successfully convert xprv to xpub." do t = @bip32_test_case_1 - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m) - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m) assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub} - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0h) - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m_0h) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m_0h) assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub} - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0h_1) - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h_1) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m_0h_1) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m_0h_1) assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub} - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0h_1_2h) - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m_0h_1_2h) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m_0h_1_2h) assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub} - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0h_1_2h_2) - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h_2) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m_0h_1_2h_2) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m_0h_1_2h_2) assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub} - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0h_1_2h_2_1000000000) - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h_2_1000000000) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m_0h_1_2h_2_1000000000) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m_0h_1_2h_2_1000000000) assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub} end test "BIP32 tests 1: derive prv keys in sequence" do t = @bip32_test_case_1 # derive prv child from prv parent_fingerprint - {:ok, m_xprv} = ExtendedKey.parse_extended_key(t.xprv_m) + {:ok, m_xprv} = ExtendedKey.parse(t.xprv_m) {:ok, m_0h_xprv} = ExtendedKey.derive_private_child(m_xprv, @min_hardened_child_num) - assert ExtendedKey.parse_extended_key(t.xprv_m_0h) == {:ok, m_0h_xprv} + assert ExtendedKey.parse(t.xprv_m_0h) == {:ok, m_0h_xprv} # derive child m/0'/1 {:ok, m_0h_1_xprv} = ExtendedKey.derive_private_child(m_0h_xprv, 1) - assert ExtendedKey.parse_extended_key(t.xprv_m_0h_1) == {:ok, m_0h_1_xprv} + assert ExtendedKey.parse(t.xprv_m_0h_1) == {:ok, m_0h_1_xprv} end test "BIP32 tests 1: derive pub keys from master prv key" do t = @bip32_test_case_1 - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m) {:ok, m_0h_xpub} = ExtendedKey.derive_public_child(xprv, @min_hardened_child_num) - assert ExtendedKey.parse_extended_key(t.xpub_m_0h) == {:ok, m_0h_xpub} + assert ExtendedKey.parse(t.xpub_m_0h) == {:ok, m_0h_xpub} - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m) {:ok, xprv} = ExtendedKey.derive_private_child(xprv, @min_hardened_child_num) {:ok, xprv} = ExtendedKey.derive_private_child(xprv, 1) {:ok, m_0h_1_2h_xpub} = ExtendedKey.derive_public_child(xprv, @min_hardened_child_num + 2) - assert ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h) == {:ok, m_0h_1_2h_xpub} + assert ExtendedKey.parse(t.xpub_m_0h_1_2h) == {:ok, m_0h_1_2h_xpub} end test "BIP32 tests 1: derive m/0'/1/2'/2/1000000000 from master key" do t = @bip32_test_case_1 - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m) {:ok, xprv} = ExtendedKey.derive_private_child(xprv, @min_hardened_child_num) {:ok, xprv} = ExtendedKey.derive_private_child(xprv, 1) {:ok, xprv} = ExtendedKey.derive_private_child(xprv, @min_hardened_child_num + 2) {:ok, xprv} = ExtendedKey.derive_private_child(xprv, 2) {:ok, m_0h_1_2h_2_1000000000_xprv} = ExtendedKey.derive_private_child(xprv, 1_000_000_000) - assert ExtendedKey.parse_extended_key(t.xprv_m_0h_1_2h_2_1000000000) == + assert ExtendedKey.parse(t.xprv_m_0h_1_2h_2_1000000000) == {:ok, m_0h_1_2h_2_1000000000_xprv} end test "BIP32 tests 1: derive pub child from pub parent_fingerprint" do t = @bip32_test_case_1 - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m_0h) {:ok, m_0h_1_xpub} = ExtendedKey.derive_public_child(xpub, 1) - assert ExtendedKey.parse_extended_key(t.xpub_m_0h_1) == {:ok, m_0h_1_xpub} + assert ExtendedKey.parse(t.xpub_m_0h_1) == {:ok, m_0h_1_xpub} - {:ok, xpub_m_0h_1_2h} = ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h) + {:ok, xpub_m_0h_1_2h} = ExtendedKey.parse(t.xpub_m_0h_1_2h) {:ok, m_0h_1_2h_2_xpub} = ExtendedKey.derive_public_child(xpub_m_0h_1_2h, 2) - assert ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h_2) == {:ok, m_0h_1_2h_2_xpub} + assert ExtendedKey.parse(t.xpub_m_0h_1_2h_2) == {:ok, m_0h_1_2h_2_xpub} {:ok, m_0h_1_2h_2_1000000000_xpub} = m_0h_1_2h_2_xpub |> ExtendedKey.derive_public_child(1_000_000_000) - assert ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h_2_1000000000) == + assert ExtendedKey.parse(t.xpub_m_0h_1_2h_2_1000000000) == {:ok, m_0h_1_2h_2_1000000000_xpub} end test "BIP32 tests 1: to_public_key works for both xprv and xpubs" do t = @bip32_test_case_1 - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m) - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m) pub = ExtendedKey.to_public_key(xpub) # test that to_public_key works for xprv and xpub keys @@ -380,7 +395,7 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do t = @bip32_test_case_1 seed = t.seed |> Base.decode16!(case: :lower) - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m) assert ExtendedKey.seed_to_master_private_key(seed) == {:ok, xprv} end @@ -389,20 +404,19 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do t = @bip32_test_case_1 seed = t.seed |> Base.decode16!(case: :lower) - {:ok, xprv} = t.xprv_m |> ExtendedKey.parse_extended_key() + {:ok, xprv} = t.xprv_m |> ExtendedKey.parse() deriv = %ExtendedKey.DerivationPath{child_nums: []} assert ExtendedKey.derive_extended_key(seed, deriv) == {:ok, xprv} # derive m/0'/1 - {:ok, xprv} = t.xprv_m_0h_1 |> ExtendedKey.parse_extended_key() + {:ok, xprv} = t.xprv_m_0h_1 |> ExtendedKey.parse() deriv = %ExtendedKey.DerivationPath{child_nums: [@min_hardened_child_num, 1]} assert ExtendedKey.derive_extended_key(seed, deriv) == {:ok, xprv} # derive xprv_m_0h_1_2h_2_1000000000 - {:ok, xprv_m_0h_1_2h_2_1000000000} = - t.xprv_m_0h_1_2h_2_1000000000 |> ExtendedKey.parse_extended_key() + {:ok, xprv_m_0h_1_2h_2_1000000000} = t.xprv_m_0h_1_2h_2_1000000000 |> ExtendedKey.parse() deriv = %ExtendedKey.DerivationPath{ child_nums: [@min_hardened_child_num, 1, @min_hardened_child_num + 2, 2, 1_000_000_000] @@ -416,55 +430,55 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do test "BIP32 tests 2: successfully convert xprv to xpub." do t = @bip32_test_case_2 - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m) - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m) assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub} - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0) - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m_0) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m_0) assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub} - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h) - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0_2147483647h) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m_0_2147483647h) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m_0_2147483647h) assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub} - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h_1) - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0_2147483647h_1) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m_0_2147483647h_1) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m_0_2147483647h_1) assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub} - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h_1_2147483646h) - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0_2147483647h_1_2147483646h) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m_0_2147483647h_1_2147483646h) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m_0_2147483647h_1_2147483646h) assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub} - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h_1_2147483646h_2) - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0_2147483647h_1_2147483646h_2) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m_0_2147483647h_1_2147483646h_2) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m_0_2147483647h_1_2147483646h_2) assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub} end test "BIP32 tests 2: derive prv keys in sequence" do t = @bip32_test_case_2 # derive prv child from prv parent_fingerprint - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m) {:ok, m_0_xprv} = ExtendedKey.derive_private_child(xprv, 0) - assert ExtendedKey.parse_extended_key(t.xprv_m_0) == {:ok, m_0_xprv} + assert ExtendedKey.parse(t.xprv_m_0) == {:ok, m_0_xprv} # derive child m/0/2147483647h {:ok, m_0_2147483647h_xprv} = ExtendedKey.derive_private_child(m_0_xprv, 2_147_483_647 + @min_hardened_child_num) - assert ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h) == {:ok, m_0_2147483647h_xprv} + assert ExtendedKey.parse(t.xprv_m_0_2147483647h) == {:ok, m_0_2147483647h_xprv} end test "BIP32 tests 2: derive pub keys from master prv key" do t = @bip32_test_case_2 - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m) {:ok, m_0_xpub} = ExtendedKey.derive_public_child(xprv, 0) - assert ExtendedKey.parse_extended_key(t.xpub_m_0) == {:ok, m_0_xpub} + assert ExtendedKey.parse(t.xpub_m_0) == {:ok, m_0_xpub} - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m) {:ok, xprv_temp} = ExtendedKey.derive_private_child(xprv, 0) {:ok, xprv_temp} = @@ -472,25 +486,25 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do {:ok, m_0_2147483647h_1_xpub} = ExtendedKey.derive_public_child(xprv_temp, 1) - assert ExtendedKey.parse_extended_key(t.xpub_m_0_2147483647h_1) == + assert ExtendedKey.parse(t.xpub_m_0_2147483647h_1) == {:ok, m_0_2147483647h_1_xpub} end test "BIP32 tests 2: derive child pub keys from prv and pubkey" do t = @bip32_test_case_2 - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m) {:ok, xpub1} = ExtendedKey.derive_public_child(xprv, 0) - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m) {:ok, xpub2} = ExtendedKey.derive_public_child(xpub, 0) assert xpub1 == xpub2 - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m_0_2147483647h) {:ok, xpub1} = ExtendedKey.derive_public_child(xprv, 1) - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0_2147483647h) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m_0_2147483647h) {:ok, xpub2} = ExtendedKey.derive_public_child(xpub, 1) assert xpub1 == xpub2 @@ -500,7 +514,7 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do t = @bip32_test_case_2 seed = t.seed |> Base.decode16!(case: :lower) - {:ok, xprv} = t.xprv_m |> ExtendedKey.parse_extended_key() + {:ok, xprv} = t.xprv_m |> ExtendedKey.parse() assert ExtendedKey.seed_to_master_private_key(seed) == {:ok, xprv} end @@ -509,12 +523,12 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do t = @bip32_test_case_2 seed = t.seed |> Base.decode16!(case: :lower) - {:ok, xprv} = t.xprv_m |> ExtendedKey.parse_extended_key() + {:ok, xprv} = t.xprv_m |> ExtendedKey.parse() deriv = %ExtendedKey.DerivationPath{child_nums: []} assert ExtendedKey.derive_extended_key(seed, deriv) == {:ok, xprv} - {:ok, xprv} = t.xprv_m_0_2147483647h |> ExtendedKey.parse_extended_key() + {:ok, xprv} = t.xprv_m_0_2147483647h |> ExtendedKey.parse() deriv = %ExtendedKey.DerivationPath{ child_nums: [0, 2_147_483_647 + @min_hardened_child_num] @@ -523,7 +537,7 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do assert ExtendedKey.derive_extended_key(seed, deriv) == {:ok, xprv} {:ok, xprv_m_0_2147483647h_1_2147483646h_2} = - t.xprv_m_0_2147483647h_1_2147483646h_2 |> ExtendedKey.parse_extended_key() + t.xprv_m_0_2147483647h_1_2147483646h_2 |> ExtendedKey.parse() deriv = %ExtendedKey.DerivationPath{ child_nums: [ @@ -544,14 +558,14 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do test "BIP32 tests 3: derive public key from private key" do t = @bip32_test_case_3 - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m) - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m) assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub} # check that to_extended_public_key is identity for xpub assert ExtendedKey.to_extended_public_key(xpub) == xpub - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0h) - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m_0h) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m_0h) assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub} assert ExtendedKey.to_extended_public_key(xpub) == xpub end @@ -559,25 +573,25 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do test "BIP32 tests 3: derive prv child from parent" do t = @bip32_test_case_3 - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m) {:ok, xprv_m_0h} = ExtendedKey.derive_private_child(xprv, @min_hardened_child_num) - assert ExtendedKey.parse_extended_key(t.xprv_m_0h) == {:ok, xprv_m_0h} + assert ExtendedKey.parse(t.xprv_m_0h) == {:ok, xprv_m_0h} end test "BIP32 tests 3: derive pub child from prv parent" do t = @bip32_test_case_3 - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m) {:ok, xpub_m_0h} = ExtendedKey.derive_public_child(xprv, @min_hardened_child_num) - assert ExtendedKey.display_extended_key(xpub_m_0h) == t.xpub_m_0h + assert ExtendedKey.display(xpub_m_0h) == t.xpub_m_0h end test "BIP32 tests 3: derive master prv key from seed" do t = @bip32_test_case_3 - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m) {:ok, s_xprv} = t.seed @@ -590,7 +604,7 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do test "BIP32 tests 3: derive child prv key from seed" do t = @bip32_test_case_3 - {:ok, xprv_m_0h} = ExtendedKey.parse_extended_key(t.xprv_m_0h) + {:ok, xprv_m_0h} = ExtendedKey.parse(t.xprv_m_0h) deriv = %ExtendedKey.DerivationPath{child_nums: [@min_hardened_child_num]} {:ok, s_xprv_m_0h} = @@ -605,7 +619,7 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do describe "Invalid Key testing" do test "invalid key testing" do for t <- @invalid_xkeys do - {err, _} = ExtendedKey.parse_extended_key(t) + {err, _} = ExtendedKey.parse(t) assert err == :error end end @@ -615,8 +629,8 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do test "derive prv and public key, sign msg, verify" do t = @bip32_test_case_1 - {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m) - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m) + {:ok, xprv} = ExtendedKey.parse(t.xprv_m) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m) {:ok, prv} = ExtendedKey.to_private_key(xprv) {:ok, pub} = ExtendedKey.to_public_key(xpub) @@ -632,7 +646,7 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do test "fail to derive hardened child from pubkey parent" do t = @bip32_test_case_3 - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m) {err, _msg} = ExtendedKey.derive_child_key(xpub, @min_hardened_child_num) assert :error == err end @@ -644,7 +658,7 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do child_nums: [@min_hardened_child_num, 1] } - {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m) {err, _msg} = ExtendedKey.derive_extended_key(xpub, deriv) assert :error == err end @@ -664,7 +678,7 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do t.xprv_m_obj |> ExtendedKey.derive_extended_key(deriv) - assert ExtendedKey.display_extended_key(child_key) == t.xprv_m_0h_1_2h_2 + assert ExtendedKey.display(child_key) == t.xprv_m_0h_1_2h_2 end test "successfully derive xpub child key with derivation path" do @@ -679,11 +693,11 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do ] } - {:ok, xprv_t1} = ExtendedKey.parse_extended_key(t.xprv_m) + {:ok, xprv_t1} = ExtendedKey.parse(t.xprv_m) {:ok, xprv_t2} = ExtendedKey.derive_extended_key(xprv_t1, deriv) {:ok, child_key} = ExtendedKey.to_extended_public_key(xprv_t2) - assert ExtendedKey.display_extended_key(child_key) == t.xpub_m_0_2147483647h_1_2147483646h + assert ExtendedKey.display(child_key) == t.xpub_m_0_2147483647h_1_2147483646h end test "test use of deriv path bip32 test 2" do @@ -699,24 +713,27 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do ] } - {:ok, m} = ExtendedKey.parse_extended_key(t.xprv_m) + {:ok, m} = ExtendedKey.parse(t.xprv_m) {:ok, child_key} = ExtendedKey.derive_extended_key(m, deriv) - assert ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h_1_2147483646h_2) == + assert ExtendedKey.parse(t.xprv_m_0_2147483647h_1_2147483646h_2) == {:ok, child_key} end end describe "Derivation Path parse/ser testing" do test "from_string/1" do - for t <- @strings_to_derivation_paths do + for t <- @derivation_paths_to_parse do + if ExtendedKey.DerivationPath.from_string(t.str) != {:ok, t.deriv}, + do: IO.puts(t.str) + assert ExtendedKey.DerivationPath.from_string(t.str) == {:ok, t.deriv} end end test "to_string/1" do - for t <- @derivation_paths_to_strings do + for t <- @derivation_paths_to_serialize do assert ExtendedKey.DerivationPath.to_string(t.deriv) == {:ok, t.str} end end @@ -728,4 +745,20 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do end end end + + describe "Binary encoding (for PSBT)" do + test "to_bin/1 - serialize" do + for t <- @derivation_paths_to_serialize do + assert ExtendedKey.DerivationPath.to_bin(t.deriv) == {:ok, t.bin} + assert ExtendedKey.DerivationPath.from_bin(t.bin) == {:ok, t.deriv} + end + end + + test "to_bin/1 - parse" do + for t <- @derivation_paths_to_parse do + assert ExtendedKey.DerivationPath.to_bin(t.deriv) == {:ok, t.bin} + assert ExtendedKey.DerivationPath.from_bin(t.bin) == {:ok, t.deriv} + end + end + end end diff --git a/test/psbt_test.exs b/test/psbt_test.exs index bcf9385..03c312d 100644 --- a/test/psbt_test.exs +++ b/test/psbt_test.exs @@ -82,18 +82,8 @@ defmodule Bitcoinex.PSBTTest do } ], expected_out: [ - %Bitcoinex.PSBT.Out{ - bip32_derivation: nil, - proprietary: nil, - redeem_script: nil, - witness_script: nil - }, - %Bitcoinex.PSBT.Out{ - bip32_derivation: nil, - proprietary: nil, - redeem_script: nil, - witness_script: nil - } + %Out{}, + %Out{} ] }, %{ @@ -126,36 +116,16 @@ defmodule Bitcoinex.PSBTTest do value: 9358 } ], - version: 2, - witnesses: nil + version: 2 } }, expected_in: [ %In{ - bip32_derivation: nil, final_scriptsig: - "47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa88292", - final_scriptwitness: nil, - non_witness_utxo: nil, - partial_sig: nil, - por_commitment: nil, - proprietary: nil, - redeem_script: nil, - sighash_type: nil, - witness_script: nil, - witness_utxo: nil + "47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa88292" }, %In{ - bip32_derivation: nil, - final_scriptsig: nil, - final_scriptwitness: nil, - non_witness_utxo: nil, - partial_sig: nil, - por_commitment: nil, - proprietary: nil, redeem_script: "001485d13537f2e265405a34dbafa9e3dda01fb82308", - sighash_type: nil, - witness_script: nil, witness_utxo: %Bitcoinex.Transaction.Out{ script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787", value: 100_000_000 @@ -163,25 +133,14 @@ defmodule Bitcoinex.PSBTTest do } ], expected_out: [ - %Bitcoinex.PSBT.Out{ - bip32_derivation: nil, - proprietary: nil, - redeem_script: nil, - witness_script: nil - }, - %Bitcoinex.PSBT.Out{ - bip32_derivation: nil, - proprietary: nil, - redeem_script: nil, - witness_script: nil - } + %Out{}, + %Out{} ] }, %{ psbt: "cHNidP8BAFICAAAAAZ38ZijCbFiZ/hvT3DOGZb/VXXraEPYiCXPfLTht7BJ2AQAAAAD/////AfA9zR0AAAAAFgAUezoAv9wU0neVwrdJAdCdpu8TNXkAAAAATwEENYfPAto/0AiAAAAAlwSLGtBEWx7IJ1UXcnyHtOTrwYogP/oPlMAVZr046QADUbdDiH7h1A3DKmBDck8tZFmztaTXPa7I+64EcvO8Q+IM2QxqT64AAIAAAACATwEENYfPAto/0AiAAAABuQRSQnE5zXjCz/JES+NTzVhgXj5RMoXlKLQH+uP2FzUD0wpel8itvFV9rCrZp+OcFyLrrGnmaLbyZnzB1nHIPKsM2QxqT64AAIABAACAAAEBKwBlzR0AAAAAIgAgLFSGEmxJeAeagU4TcV1l82RZ5NbMre0mbQUIZFuvpjIBBUdSIQKdoSzbWyNWkrkVNq/v5ckcOrlHPY5DtTODarRWKZyIcSEDNys0I07Xz5wf6l0F1EFVeSe+lUKxYusC4ass6AIkwAtSriIGAp2hLNtbI1aSuRU2r+/lyRw6uUc9jkO1M4NqtFYpnIhxENkMak+uAACAAAAAgAAAAAAiBgM3KzQjTtfPnB/qXQXUQVV5J76VQrFi6wLhqyzoAiTACxDZDGpPrgAAgAEAAIAAAAAAACICA57/H1R6HV+S36K6evaslxpL0DukpzSwMVaiVritOh75EO3kXMUAAACAAAAAgAEAAIAA", expected_global: %Global{ - proprietary: nil, unsigned_tx: %Bitcoinex.Transaction{ inputs: [ %Bitcoinex.Transaction.In{ @@ -198,47 +157,49 @@ defmodule Bitcoinex.PSBTTest do value: 499_990_000 } ], - version: 2, - witnesses: nil + version: 2 }, - version: nil, xpub: [ %{ - derivation: [2_147_483_822, 2_147_483_648], - master_pfp: 1_332_350_169, + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_822, 2_147_483_648] + }, + pfp: <<217, 12, 106, 79>>, xpub: - "tpubDBkJeJo2X94Yq3RVz65DoUgyLUkaDrkfyrn2VcgyCRSKCRonvKvCF2FpYDGJWDkdRHBajXJGpc63GnumUt63ySvqCu2XaTRGVTKMYGuFk9H" + Bitcoinex.ExtendedKey.parse!( + "tpubDBkJeJo2X94Yq3RVz65DoUgyLUkaDrkfyrn2VcgyCRSKCRonvKvCF2FpYDGJWDkdRHBajXJGpc63GnumUt63ySvqCu2XaTRGVTKMYGuFk9H" + ) }, %{ - derivation: [2_147_483_822, 2_147_483_649], - master_pfp: 1_332_350_169, + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_822, 2_147_483_649] + }, + pfp: <<217, 12, 106, 79>>, xpub: - "tpubDBkJeJo2X94YsvtBEU1eKoibEWiNv51nW5iHhs6VZp59jsE6nen8KZMFyGHuGbCvqjRqirgeMcfpVBkttpUUT6brm4duzSGoZeTbhqCNUu6" + Bitcoinex.ExtendedKey.parse!( + "tpubDBkJeJo2X94YsvtBEU1eKoibEWiNv51nW5iHhs6VZp59jsE6nen8KZMFyGHuGbCvqjRqirgeMcfpVBkttpUUT6brm4duzSGoZeTbhqCNUu6" + ) } ] }, expected_in: [ - %Bitcoinex.PSBT.In{ + %In{ bip32_derivation: [ %{ - derivation: [2_147_483_822, 2_147_483_648, 0], + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_822, 2_147_483_648, 0] + }, public_key: "029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c8871", - pfp: 1_332_350_169 + pfp: <<217, 12, 106, 79>> }, %{ - derivation: [2_147_483_822, 2_147_483_649, 0], + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_822, 2_147_483_649, 0] + }, public_key: "03372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b", - pfp: 1_332_350_169 + pfp: <<217, 12, 106, 79>> } ], - final_scriptsig: nil, - final_scriptwitness: nil, - non_witness_utxo: nil, - partial_sig: nil, - por_commitment: nil, - proprietary: nil, - redeem_script: nil, - sighash_type: nil, witness_script: "5221029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c88712103372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b52ae", witness_utxo: %Bitcoinex.Transaction.Out{ @@ -252,14 +213,13 @@ defmodule Bitcoinex.PSBTTest do %Out{ bip32_derivation: [ %{ - derivation: [2_147_483_648, 2_147_483_648, 2_147_483_649], + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_648, 2_147_483_648, 2_147_483_649] + }, public_key: "039eff1f547a1d5f92dfa2ba7af6ac971a4bd03ba4a734b03156a256b8ad3a1ef9", - pfp: 3_311_199_469 + pfp: <<237, 228, 92, 197>> } - ], - proprietary: nil, - redeem_script: nil, - witness_script: nil + ] } ] }, @@ -267,7 +227,6 @@ defmodule Bitcoinex.PSBTTest do psbt: "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoEBBUdSIQOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RiED3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg71SriIGA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GELSmumcAAACAAAAAgAQAAIAiBgPeVdHh2sgF4/iljB+/m5TALz26r+En/vykmV8m+CCDvRC0prpnAAAAgAAAAIAFAACAAAA=", expected_global: %Global{ - proprietary: nil, unsigned_tx: %Bitcoinex.Transaction{ inputs: [ %Bitcoinex.Transaction.In{ @@ -284,38 +243,35 @@ defmodule Bitcoinex.PSBTTest do value: 199_908_000 } ], - version: 2, - witnesses: nil - }, - version: nil, - xpub: nil + version: 2 + } }, expected_in: [ - %Bitcoinex.PSBT.In{ + %In{ bip32_derivation: [ %{ - derivation: [2_147_483_648, 2_147_483_648, 2_147_483_652], + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_648, 2_147_483_648, 2_147_483_652] + }, public_key: "03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46", - pfp: 1_740_285_620 + pfp: <<180, 166, 186, 103>> }, %{ - derivation: [2_147_483_648, 2_147_483_648, 2_147_483_653], + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_648, 2_147_483_648, 2_147_483_653] + }, public_key: "03de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd", - pfp: 1_740_285_620 + pfp: <<180, 166, 186, 103>> + } + ], + partial_sig: [ + %{ + public_key: "03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46", + signature: + "304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01" } ], - final_scriptsig: nil, - final_scriptwitness: nil, - non_witness_utxo: nil, - partial_sig: %{ - public_key: "03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46", - signature: - "304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01" - }, - por_commitment: nil, - proprietary: nil, redeem_script: "0020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681", - sighash_type: nil, witness_script: "522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae", witness_utxo: %Bitcoinex.Transaction.Out{ @@ -325,12 +281,7 @@ defmodule Bitcoinex.PSBTTest do } ], expected_out: [ - %Bitcoinex.PSBT.Out{ - bip32_derivation: nil, - proprietary: nil, - redeem_script: nil, - witness_script: nil - } + %Out{} ] }, %{ @@ -338,7 +289,6 @@ defmodule Bitcoinex.PSBTTest do psbt: "cHNidP8BAKcBAAAAAjHC7gs4NF4rUOrlta+j+wB8UHTEuLn0XY6FDUcGybQMAAAAAAD+////NUUKTkDqBbL9oqrAIk9199/ZANXi/8XEgguqQY8iiewAAAAAAP7///8CdImYAAAAAAAiACCs+u6eefBEoqCFYVWhxscCwh/WJZ+286/E8zNH9gRhd4CWmAAAAAAAF6kUV3ZMSDpAgQZkllBPVNL5uRPlwOOHAAAAAAABASuAlpgAAAAAACIAIDH8Jza8S0T6nWkCcU5GqgwxJ2rGEgWFgDSGiJVFJ5W0AQj9/QAEAEgwRQIhALL4SZucnmwtsJ2BguTQkajOkbvRTRcIMF2B/c26pnZDAiAwNPAWsW3b3PxNXZouG43Z2HJ4WufvpjM0x+VlprgFUAFHMEQCIGV66oyrbw0b9HXA8EeGKrIi88YhTGuhpQKdDxX1VivPAiAcxSrameybDohX8yINx2t452PyyqP6qUiTUMNnoAv+twFpUiECZ3pcsDl1tPNTASW/gFEm/PlWLEnQJN5h32F5qmC2U6AhA1fyyfYB3ma7Vg6JKICdCsQFD7/IchNleJnjTaTGbCFgIQP8V/0ULlUTx5q8mJ6eJh6GaCHkHXDkTnmFbpZRGDsQVVOuAAEBK4CWmAAAAAAAIgAgi3WHXCAbeRTULI6EPlb3Z3+J153IX4zK5bHRsqnrSO4BCPwEAEcwRAIgelTwDK+TOYwP6luGb5htloRgijKLoLmNrjk9imXolaICIFQ9Rq0MrOGcrYHC6BZIyyz+tB0Lm8FhqnARl7R+TpyaAUcwRAIgfHNbxYLcTt1yWeADHyo5ye4jtApn+YTgFzK16IsOW0QCIDcOnv2QYaZlc0etz9kfIrkpoepeTndtvEREKROzqqlCAWlSIQIIPVGeoWYEHRGxyDhpzTqE0uBZIjBj5DDXgBX5QWwecCECL5C1pXxiQ5uiuhZASuHYEUq+gXmXqE+wxPnV590o+HAhA0odK6A98KAdcHcI5pcbNfwR1oq0PsofJzNfvSKkdqCMU64AAQFpUiECPhqS90SDpMEqGW1sAlOsWJz63Vlk/z5sY6711XcFHtQhAk0OObM6tXeCqY/Qan0GUzheUJ7jt03EVVnm22OR0xN4IQNsC65rywLkfIV8SA7R0jiIyK1qZrg6sRHLa5JCr7HHJVOuIgICPhqS90SDpMEqGW1sAlOsWJz63Vlk/z5sY6711XcFHtQgAAAAAAAAAIACAACAAgAAAAAAAAAAAAAAAQAAAA0AAAAiAgJNDjmzOrV3gqmP0Gp9BlM4XlCe47dNxFVZ5ttjkdMTeCAAAAAAAAAAgAIAAIACAAAAAAAAAAAAAAABAAAADQAAACICA2wLrmvLAuR8hXxIDtHSOIjIrWpmuDqxEctrkkKvscclIAAAAAAAAACAAgAAgAIAAAAAAAAAAAAAAAEAAAANAAAAAAA=", expected_global: %Global{ - proprietary: nil, unsigned_tx: %Bitcoinex.Transaction{ inputs: [ %Bitcoinex.Transaction.In{ @@ -366,16 +316,11 @@ defmodule Bitcoinex.PSBTTest do value: 10_000_000 } ], - version: 1, - witnesses: nil - }, - version: nil, - xpub: nil + version: 1 + } }, expected_in: [ - %Bitcoinex.PSBT.In{ - bip32_derivation: nil, - final_scriptsig: nil, + %In{ final_scriptwitness: %Bitcoinex.Transaction.Witness{ txinwitness: [ "", @@ -384,22 +329,13 @@ defmodule Bitcoinex.PSBTTest do "522102677a5cb03975b4f3530125bf805126fcf9562c49d024de61df6179aa60b653a0210357f2c9f601de66bb560e8928809d0ac4050fbfc87213657899e34da4c66c21602103fc57fd142e5513c79abc989e9e261e866821e41d70e44e79856e9651183b105553ae" ] }, - non_witness_utxo: nil, - partial_sig: nil, - por_commitment: nil, - proprietary: nil, - redeem_script: nil, - sighash_type: nil, - witness_script: nil, witness_utxo: %Bitcoinex.Transaction.Out{ script_pub_key: "002031fc2736bc4b44fa9d6902714e46aa0c31276ac61205858034868895452795b4", value: 10_000_000 } }, - %Bitcoinex.PSBT.In{ - bip32_derivation: nil, - final_scriptsig: nil, + %In{ final_scriptwitness: %Bitcoinex.Transaction.Witness{ txinwitness: [ "", @@ -408,13 +344,6 @@ defmodule Bitcoinex.PSBTTest do "522102083d519ea166041d11b1c83869cd3a84d2e059223063e430d78015f9416c1e7021022f90b5a57c62439ba2ba16404ae1d8114abe817997a84fb0c4f9d5e7dd28f87021034a1d2ba03df0a01d707708e6971b35fc11d68ab43eca1f27335fbd22a476a08c53ae" ] }, - non_witness_utxo: nil, - partial_sig: nil, - por_commitment: nil, - proprietary: nil, - redeem_script: nil, - sighash_type: nil, - witness_script: nil, witness_utxo: %Bitcoinex.Transaction.Out{ script_pub_key: "00208b75875c201b7914d42c8e843e56f7677f89d79dc85f8ccae5b1d1b2a9eb48ee", @@ -423,60 +352,147 @@ defmodule Bitcoinex.PSBTTest do } ], expected_out: [ - %Bitcoinex.PSBT.Out{ + %Out{ bip32_derivation: [ %{ - derivation: [2_147_483_648, 2_147_483_650, 2, 0, 0, 1, 13], + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_648, 2_147_483_650, 2, 0, 0, 1, 13] + }, public_key: "023e1a92f74483a4c12a196d6c0253ac589cfadd5964ff3e6c63aef5d577051ed4", - pfp: 0 + pfp: <<0, 0, 0, 0>> }, %{ - derivation: [2_147_483_648, 2_147_483_650, 2, 0, 0, 1, 13], + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_648, 2_147_483_650, 2, 0, 0, 1, 13] + }, public_key: "024d0e39b33ab57782a98fd06a7d0653385e509ee3b74dc45559e6db6391d31378", - pfp: 0 + pfp: <<0, 0, 0, 0>> }, %{ - derivation: [2_147_483_648, 2_147_483_650, 2, 0, 0, 1, 13], + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_648, 2_147_483_650, 2, 0, 0, 1, 13] + }, public_key: "036c0bae6bcb02e47c857c480ed1d23888c8ad6a66b83ab111cb6b9242afb1c725", - pfp: 0 + pfp: <<0, 0, 0, 0>> } ], - proprietary: nil, - redeem_script: nil, witness_script: "5221023e1a92f74483a4c12a196d6c0253ac589cfadd5964ff3e6c63aef5d577051ed421024d0e39b33ab57782a98fd06a7d0653385e509ee3b74dc45559e6db6391d3137821036c0bae6bcb02e47c857c480ed1d23888c8ad6a66b83ab111cb6b9242afb1c72553ae" }, - %Bitcoinex.PSBT.Out{ - bip32_derivation: nil, - proprietary: nil, - redeem_script: nil, - witness_script: nil - } + %Out{} ] }, %{ psbt: "cHNidP8BAAoAAAAAAAAAAAAAAA==", expected_global: %Global{ - proprietary: nil, unsigned_tx: %Bitcoinex.Transaction{ inputs: [], lock_time: 0, outputs: [], - witnesses: nil, version: 0 - }, - version: nil, - xpub: nil + } }, expected_in: [], expected_out: [] }, + %{ + psbt: + "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA=", + expected_global: %Global{ + unsigned_tx: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "e47b5b7a879f13a8213815cf3dc3f5b35af1e217f412829bc4f75a8ca04909ab", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_294 + }, + %Bitcoinex.Transaction.In{ + prev_txid: "e47b5b7a879f13a8213815cf3dc3f5b35af1e217f412829bc4f75a8ca04909ab", + prev_vout: 1, + script_sig: "", + sequence_no: 4_294_967_294 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 199_900_000, + script_pub_key: "76a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac" + }, + %Bitcoinex.Transaction.Out{ + value: 9358, + script_pub_key: "76a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac" + } + ], + lock_time: 0 + } + }, + expected_in: [ + %In{ + non_witness_utxo: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126", + prev_vout: 0, + script_sig: + "473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31", + sequence_no: 4_294_967_294 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 99_999_699, + script_pub_key: "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac" + }, + %Bitcoinex.Transaction.Out{ + value: 100_000_000, + script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787" + } + ], + lock_time: 1_257_139 + } + }, + %In{ + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 100_000_000, + script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787" + }, + redeem_script: "001485d13537f2e265405a34dbafa9e3dda01fb82308" + } + ], + expected_out: [ + %Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_648, 2_147_483_648, 2_147_483_650] + }, + pfp: <<180, 166, 186, 103>>, + public_key: "02ead596687ca806043edc3de116cdf29d5e9257c196cd055cf698c8d02bf24e99" + } + ] + }, + %Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_648, 2_147_483_649, 2_147_483_650] + }, + pfp: <<180, 166, 186, 103>>, + public_key: "0394f62be9df19952c5587768aeb7698061ad2c4a25c894f47d8c162b4d7213d05" + } + ] + } + ] + }, %{ psbt: "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAQMEAQAAAAAAAA==", expected_global: %Global{ - proprietary: nil, unsigned_tx: %Bitcoinex.Transaction{ + version: 2, inputs: [ %Bitcoinex.Transaction.In{ prev_txid: "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126", @@ -485,29 +501,23 @@ defmodule Bitcoinex.PSBTTest do sequence_no: 4_294_967_294 } ], - lock_time: 1_257_139, outputs: [ %Bitcoinex.Transaction.Out{ - script_pub_key: "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac", - value: 99_999_699 + value: 99_999_699, + script_pub_key: "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac" }, %Bitcoinex.Transaction.Out{ - script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787", - value: 100_000_000 + value: 100_000_000, + script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787" } ], - witnesses: nil, - version: 2 - }, - version: nil, - xpub: nil + lock_time: 1_257_139 + } }, expected_in: [ - %Bitcoinex.PSBT.In{ - bip32_derivation: nil, - final_scriptsig: nil, - final_scriptwitness: nil, + %In{ non_witness_utxo: %Bitcoinex.Transaction{ + version: 1, inputs: [ %Bitcoinex.Transaction.In{ prev_txid: "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389", @@ -522,18 +532,16 @@ defmodule Bitcoinex.PSBTTest do sequence_no: 4_294_967_295 } ], - lock_time: 0, outputs: [ %Bitcoinex.Transaction.Out{ - script_pub_key: "76a91485cff1097fd9e008bb34af709c62197b38978a4888ac", - value: 200_000_000 + value: 200_000_000, + script_pub_key: "76a91485cff1097fd9e008bb34af709c62197b38978a4888ac" }, %Bitcoinex.Transaction.Out{ - script_pub_key: "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587", - value: 190_303_501_938 + value: 190_303_501_938, + script_pub_key: "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587" } ], - version: 1, witnesses: [ %Bitcoinex.Transaction.Witness{ txinwitness: [ @@ -547,29 +555,2028 @@ defmodule Bitcoinex.PSBTTest do "0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3" ] } - ] + ], + lock_time: 0 }, - partial_sig: nil, - por_commitment: nil, - proprietary: nil, - redeem_script: nil, - sighash_type: <<1, 0, 0, 0>>, - witness_script: nil, - witness_utxo: nil + sighash_type: 1 } ], expected_out: [ - %Bitcoinex.PSBT.Out{ - bip32_derivation: nil, - proprietary: nil, - redeem_script: nil, - witness_script: nil + %Out{}, + %Out{} + ] + }, + %{ + psbt: + "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAACvABAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAA=", + expected_global: %Global{ + unsigned_tx: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [%Bitcoinex.Transaction.Out{value: 0, script_pub_key: "6a0100"}], + lock_time: 0 + } + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + unknown: [ + %{ + key: <<240, 1, 2, 3, 4, 5, 6, 7, 8, 9>>, + value: <<1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15>> + } + ] + } + ], + expected_out: [ + %Out{} + ] + }, + %{ + psbt: + "cHNidP8BAJ0BAAAAAnEOp2q0XFy2Q45gflnMA3YmmBgFrp4N/ZCJASq7C+U1AQAAAAD/////GQmU1qizyMgsy8+y+6QQaqBmObhyqNRHRlwNQliNbWcAAAAAAP////8CAOH1BQAAAAAZdqkUtrwsDuVlWoQ9ea/t0MzD991kNAmIrGBa9AUAAAAAFgAUEYjvjkzgRJ6qyPsUHL9aEXbmoIgAAAAATwEEiLIeA55TDKyAAAAAPbyKXJdp8DGxfnf+oVGGAyIaGP0Y8rmlTGyMGsdcvDUC8jBYSxVdHH8c1FEgplPEjWULQxtnxbLBPyfXFCA3wWkQJ1acUDEAAIAAAACAAAAAgAABAR8A4fUFAAAAABYAFDO5gvkbKPFgySC0q5XljOUN2jpKIgIDMJaA8zx9446mpHzU7NZvH1pJdHxv+4gI7QkDkkPjrVxHMEQCIC1wTO2DDFapCTRL10K2hS3M0QPpY7rpLTjnUlTSu0JFAiAthsQ3GV30bAztoITyopHD2i1kBw92v5uQsZXn7yj3cgEiBgMwloDzPH3jjqakfNTs1m8fWkl0fG/7iAjtCQOSQ+OtXBgnVpxQMQAAgAAAAIAAAACAAAAAAAEAAAAAAQEfAOH1BQAAAAAWABQ4j7lEMH63fvRRl9CwskXgefAR3iICAsd3Fh9z0LfHK57nveZQKT0T8JW8dlatH1Jdpf0uELEQRzBEAiBMsftfhpyULg4mEAV2ElQ5F5rojcqKncO6CPeVOYj6pgIgUh9JynkcJ9cOJzybFGFphZCTYeJb4nTqIA1+CIJ+UU0BIgYCx3cWH3PQt8crnue95lApPRPwlbx2Vq0fUl2l/S4QsRAYJ1acUDEAAIAAAACAAAAAgAAAAAAAAAAAAAAiAgLSDKUC7iiWhtIYFb1DqAY3sGmOH7zb5MrtRF9sGgqQ7xgnVpxQMQAAgAAAAIAAAACAAAAAAAQAAAAA", + expected_global: %Global{ + unsigned_tx: %Bitcoinex.Transaction{ + version: 1, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "35e50bbb2a018990fd0d9eae051898267603cc597e608e43b65c5cb46aa70e71", + prev_vout: 1, + script_sig: "", + sequence_no: 4_294_967_295 + }, + %Bitcoinex.Transaction.In{ + prev_txid: "676d8d58420d5c4647d4a872b83966a06a10a4fbb2cfcb2cc8c8b3a8d6940919", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 100_000_000, + script_pub_key: "76a914b6bc2c0ee5655a843d79afedd0ccc3f7dd64340988ac" + }, + %Bitcoinex.Transaction.Out{ + value: 99_900_000, + script_pub_key: "00141188ef8e4ce0449eaac8fb141cbf5a1176e6a088" + } + ], + lock_time: 0 }, - %Bitcoinex.PSBT.Out{ - bip32_derivation: nil, - proprietary: nil, - redeem_script: nil, - witness_script: nil + xpub: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_697, 2_147_483_648, 2_147_483_648] + }, + pfp: <<39, 86, 156, 80>>, + xpub: %Bitcoinex.ExtendedKey{ + prefix: <<4, 136, 178, 30>>, + depth: <<3>>, + parent_fingerprint: <<158, 83, 12, 172>>, + child_num: <<128, 0, 0, 0>>, + chaincode: + <<61, 188, 138, 92, 151, 105, 240, 49, 177, 126, 119, 254, 161, 81, 134, 3, 34, + 26, 24, 253, 24, 242, 185, 165, 76, 108, 140, 26, 199, 92, 188, 53>>, + key: + <<2, 242, 48, 88, 75, 21, 93, 28, 127, 28, 212, 81, 32, 166, 83, 196, 141, 101, + 11, 67, 27, 103, 197, 178, 193, 63, 39, 215, 20, 32, 55, 193, 105>>, + checksum: <<230, 83, 80, 24>> + } + } + ] + }, + expected_in: [ + %In{ + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 100_000_000, + script_pub_key: "001433b982f91b28f160c920b4ab95e58ce50dda3a4a" + }, + partial_sig: [ + %{ + public_key: "03309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c", + signature: + "304402202d704ced830c56a909344bd742b6852dccd103e963bae92d38e75254d2bb424502202d86c437195df46c0ceda084f2a291c3da2d64070f76bf9b90b195e7ef28f77201" + } + ], + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_697, 2_147_483_648, 2_147_483_648, 0, 1] + }, + pfp: <<39, 86, 156, 80>>, + public_key: "03309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c" + } + ] + }, + %In{ + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 100_000_000, + script_pub_key: "0014388fb944307eb77ef45197d0b0b245e079f011de" + }, + partial_sig: [ + %{ + public_key: "02c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b110", + signature: + "304402204cb1fb5f869c942e0e26100576125439179ae88dca8a9dc3ba08f7953988faa60220521f49ca791c27d70e273c9b14616985909361e25be274ea200d7e08827e514d01" + } + ], + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_697, 2_147_483_648, 2_147_483_648, 0, 0] + }, + pfp: <<39, 86, 156, 80>>, + public_key: "02c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b110" + } + ] + } + ], + expected_out: [ + %Out{}, + %Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_697, 2_147_483_648, 2_147_483_648, 0, 4] + }, + pfp: <<39, 86, 156, 80>>, + public_key: "02d20ca502ee289686d21815bd43a80637b0698e1fbcdbe4caed445f6c1a0a90ef" + } + ] + } + ] + }, + %{ + psbt: "cHNidP8BAAoAAAAAAAAAAAAAAA==", + expected_global: %Global{ + unsigned_tx: %Bitcoinex.Transaction{ + version: 0, + inputs: [], + outputs: [], + lock_time: 0 + } + }, + expected_in: [], + expected_out: [] + }, + %{ + psbt: + "cHNidP8BAEwCAAAAAALT3/UFAAAAABl2qRTQxZkDxbrChodg6Q/VIaRmWqdlIIisAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4ezLhMAAAAA", + expected_global: %Global{ + unsigned_tx: %Bitcoinex.Transaction{ + version: 2, + inputs: [], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 99_999_699, + script_pub_key: "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac" + }, + %Bitcoinex.Transaction.Out{ + value: 100_000_000, + script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787" + } + ], + lock_time: 1_257_139 + } + }, + expected_in: [], + expected_out: [ + %Out{}, + %Out{} + ] + }, + # BIP 370 https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki#test-vectors + %{ + psbt: + "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==", + expected_global: %Global{ + tx_version: 2, + input_count: 1, + output_count: 2, + version: 2 + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + previous_txid: + <<11, 10, 217, 33, 65, 156, 28, 135, 25, 115, 93, 114, 220, 115, 159, 158, 169, 224, + 99, 141, 31, 228, 193, 238, 240, 249, 148, 64, 132, 129, 95, 200>>, + output_index: 0 + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + amount: 800_000_000, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<196, 48, 246, 76, 71, 86, 218, 49, 13, 189, 26, 8, 85, 114, 239, 41, 153, 38, 39, + 44>> + ] + } + }, + %Bitcoinex.PSBT.Out{ + amount: 199_998_859, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<77, 209, 147, 172, 150, 74, 86, 172, 27, 158, 28, 202, 132, 84, 254, 47, 71, 79, + 133, 19>> + ] + } + } + ] + }, + %{ + psbt: + "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAACICAtYB+EhGpnVfd2vgDj2d6PsQrMk1+4PEX7AWLUytWreSGPadhz5UAACAAQAAgAAAAIAAAAAAKgAAAAEDCAAIry8AAAAAAQQWABTEMPZMR1baMQ29GghVcu8pmSYnLAAiAgLjb7/1PdU0Bwz4/TlmFGgPNXqbhdtzQL8c+nRdKtezQBj2nYc+VAAAgAEAAIAAAACAAQAAAGQAAAABAwiLvesLAAAAAAEEFgAUTdGTrJZKVqwbnhzKhFT+L0dPhRMA", + expected_global: %Global{ + tx_version: 2, + input_count: 1, + output_count: 2, + version: 2 + }, + expected_in: [ + %In{ + non_witness_utxo: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "125b805e5a51d715356e3019058dfff3b5f3bf42de932f82a1964b216e25aac1", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + } + ], + lock_time: 0 + }, + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + }, + previous_txid: + <<11, 10, 217, 33, 65, 156, 28, 135, 25, 115, 93, 114, 220, 115, 159, 158, 169, 224, + 99, 141, 31, 228, 193, 238, 240, 249, 148, 64, 132, 129, 95, 200>>, + output_index: 0 + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + } + ], + amount: 800_000_000, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<196, 48, 246, 76, 71, 86, 218, 49, 13, 189, 26, 8, 85, 114, 239, 41, 153, 38, 39, + 44>> + ] + } + }, + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + } + ], + amount: 199_998_859, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<77, 209, 147, 172, 150, 74, 86, 172, 27, 158, 28, 202, 132, 84, 254, 47, 71, 79, + 133, 19>> + ] + } + } + ] + }, + %{ + psbt: + "cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEAUgIAAAABwaolbiFLlqGCL5PeQr/ztfP/jQUZMG41FddRWl6AWxIAAAAAAP////8BGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgAAAAABAR8Yxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAQ4gCwrZIUGcHIcZc11y3HOfnqngY40f5MHu8PmUQISBX8gBDwQAAAAAARAE/v///wAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==", + expected_global: %Global{ + tx_version: 2, + input_count: 1, + output_count: 2, + version: 2 + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + non_witness_utxo: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "125b805e5a51d715356e3019058dfff3b5f3bf42de932f82a1964b216e25aac1", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + } + ], + lock_time: 0 + }, + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + }, + previous_txid: + <<11, 10, 217, 33, 65, 156, 28, 135, 25, 115, 93, 114, 220, 115, 159, 158, 169, 224, + 99, 141, 31, 228, 193, 238, 240, 249, 148, 64, 132, 129, 95, 200>>, + output_index: 0, + sequence: 4_294_967_294 + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + } + ], + amount: 800_000_000, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<196, 48, 246, 76, 71, 86, 218, 49, 13, 189, 26, 8, 85, 114, 239, 41, 153, 38, 39, + 44>> + ] + } + }, + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + } + ], + amount: 199_998_859, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<77, 209, 147, 172, 150, 74, 86, 172, 27, 158, 28, 202, 132, 84, 254, 47, 71, 79, + 133, 19>> + ] + } + } + ] + }, + %{ + psbt: + "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAEQBP7///8BEQSMjcRiARIEECcAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==", + expected_global: %Global{ + tx_version: 2, + fallback_locktime: 0, + input_count: 1, + output_count: 2, + version: 2 + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + non_witness_utxo: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "125b805e5a51d715356e3019058dfff3b5f3bf42de932f82a1964b216e25aac1", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + } + ], + lock_time: 0 + }, + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + }, + previous_txid: + <<11, 10, 217, 33, 65, 156, 28, 135, 25, 115, 93, 114, 220, 115, 159, 158, 169, 224, + 99, 141, 31, 228, 193, 238, 240, 249, 148, 64, 132, 129, 95, 200>>, + output_index: 0, + sequence: 4_294_967_294, + required_time_locktime: 1_657_048_460, + required_height_locktime: 10000 + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + } + ], + amount: 800_000_000, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<196, 48, 246, 76, 71, 86, 218, 49, 13, 189, 26, 8, 85, 114, 239, 41, 153, 38, 39, + 44>> + ] + } + }, + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + } + ], + amount: 199_998_859, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<77, 209, 147, 172, 150, 74, 86, 172, 27, 158, 28, 202, 132, 84, 254, 47, 71, 79, + 133, 19>> + ] + } + } + ] + }, + %{ + psbt: + "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEBAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==", + expected_global: %Global{ + tx_version: 2, + input_count: 1, + output_count: 2, + tx_modifiable: 1, + version: 2 + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + non_witness_utxo: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "125b805e5a51d715356e3019058dfff3b5f3bf42de932f82a1964b216e25aac1", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + } + ], + lock_time: 0 + }, + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + }, + previous_txid: + <<11, 10, 217, 33, 65, 156, 28, 135, 25, 115, 93, 114, 220, 115, 159, 158, 169, 224, + 99, 141, 31, 228, 193, 238, 240, 249, 148, 64, 132, 129, 95, 200>>, + output_index: 0 + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + } + ], + amount: 800_000_000, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<196, 48, 246, 76, 71, 86, 218, 49, 13, 189, 26, 8, 85, 114, 239, 41, 153, 38, 39, + 44>> + ] + } + }, + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + } + ], + amount: 199_998_859, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<77, 209, 147, 172, 150, 74, 86, 172, 27, 158, 28, 202, 132, 84, 254, 47, 71, 79, + 133, 19>> + ] + } + } + ] + }, + %{ + psbt: + "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgECAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==", + expected_global: %Global{ + tx_version: 2, + input_count: 1, + output_count: 2, + tx_modifiable: 2, + version: 2 + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + non_witness_utxo: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "125b805e5a51d715356e3019058dfff3b5f3bf42de932f82a1964b216e25aac1", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + } + ], + lock_time: 0 + }, + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + }, + previous_txid: + <<11, 10, 217, 33, 65, 156, 28, 135, 25, 115, 93, 114, 220, 115, 159, 158, 169, 224, + 99, 141, 31, 228, 193, 238, 240, 249, 148, 64, 132, 129, 95, 200>>, + output_index: 0 + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + } + ], + amount: 800_000_000, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<196, 48, 246, 76, 71, 86, 218, 49, 13, 189, 26, 8, 85, 114, 239, 41, 153, 38, 39, + 44>> + ] + } + }, + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + } + ], + amount: 199_998_859, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<77, 209, 147, 172, 150, 74, 86, 172, 27, 158, 28, 202, 132, 84, 254, 47, 71, 79, + 133, 19>> + ] + } + } + ] + }, + %{ + psbt: + "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEEAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==", + expected_global: %Global{ + tx_version: 2, + input_count: 1, + output_count: 2, + tx_modifiable: 4, + version: 2 + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + non_witness_utxo: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "125b805e5a51d715356e3019058dfff3b5f3bf42de932f82a1964b216e25aac1", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + } + ], + lock_time: 0 + }, + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + }, + previous_txid: + <<11, 10, 217, 33, 65, 156, 28, 135, 25, 115, 93, 114, 220, 115, 159, 158, 169, 224, + 99, 141, 31, 228, 193, 238, 240, 249, 148, 64, 132, 129, 95, 200>>, + output_index: 0 + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + } + ], + amount: 800_000_000, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<196, 48, 246, 76, 71, 86, 218, 49, 13, 189, 26, 8, 85, 114, 239, 41, 153, 38, 39, + 44>> + ] + } + }, + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + } + ], + amount: 199_998_859, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<77, 209, 147, 172, 150, 74, 86, 172, 27, 158, 28, 202, 132, 84, 254, 47, 71, 79, + 133, 19>> + ] + } + } + ] + }, + %{ + psbt: + "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEIAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==", + expected_global: %Bitcoinex.PSBT.Global{ + tx_version: 2, + input_count: 1, + output_count: 2, + tx_modifiable: 8, + version: 2 + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + non_witness_utxo: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "125b805e5a51d715356e3019058dfff3b5f3bf42de932f82a1964b216e25aac1", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + } + ], + lock_time: 0 + }, + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + }, + previous_txid: + <<11, 10, 217, 33, 65, 156, 28, 135, 25, 115, 93, 114, 220, 115, 159, 158, 169, 224, + 99, 141, 31, 228, 193, 238, 240, 249, 148, 64, 132, 129, 95, 200>>, + output_index: 0 + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + } + ], + amount: 800_000_000, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<196, 48, 246, 76, 71, 86, 218, 49, 13, 189, 26, 8, 85, 114, 239, 41, 153, 38, 39, + 44>> + ] + } + }, + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + } + ], + amount: 199_998_859, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<77, 209, 147, 172, 150, 74, 86, 172, 27, 158, 28, 202, 132, 84, 254, 47, 71, 79, + 133, 19>> + ] + } + } + ] + }, + %{ + psbt: + "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEDAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==", + expected_global: %Global{ + tx_version: 2, + input_count: 1, + output_count: 2, + tx_modifiable: 3, + version: 2 + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + non_witness_utxo: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "125b805e5a51d715356e3019058dfff3b5f3bf42de932f82a1964b216e25aac1", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + } + ], + lock_time: 0 + }, + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + }, + previous_txid: + <<11, 10, 217, 33, 65, 156, 28, 135, 25, 115, 93, 114, 220, 115, 159, 158, 169, 224, + 99, 141, 31, 228, 193, 238, 240, 249, 148, 64, 132, 129, 95, 200>>, + output_index: 0 + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + } + ], + amount: 800_000_000, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<196, 48, 246, 76, 71, 86, 218, 49, 13, 189, 26, 8, 85, 114, 239, 41, 153, 38, 39, + 44>> + ] + } + }, + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + } + ], + amount: 199_998_859, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<77, 209, 147, 172, 150, 74, 86, 172, 27, 158, 28, 202, 132, 84, 254, 47, 71, 79, + 133, 19>> + ] + } + } + ] + }, + %{ + psbt: + "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEFAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==", + expected_global: %Global{ + tx_version: 2, + input_count: 1, + output_count: 2, + tx_modifiable: 5, + version: 2 + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + non_witness_utxo: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "125b805e5a51d715356e3019058dfff3b5f3bf42de932f82a1964b216e25aac1", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + } + ], + lock_time: 0 + }, + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + }, + previous_txid: + <<11, 10, 217, 33, 65, 156, 28, 135, 25, 115, 93, 114, 220, 115, 159, 158, 169, 224, + 99, 141, 31, 228, 193, 238, 240, 249, 148, 64, 132, 129, 95, 200>>, + output_index: 0 + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + } + ], + amount: 800_000_000, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<196, 48, 246, 76, 71, 86, 218, 49, 13, 189, 26, 8, 85, 114, 239, 41, 153, 38, 39, + 44>> + ] + } + }, + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + } + ], + amount: 199_998_859, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<77, 209, 147, 172, 150, 74, 86, 172, 27, 158, 28, 202, 132, 84, 254, 47, 71, 79, + 133, 19>> + ] + } + } + ] + }, + %{ + psbt: + "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEGAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==", + expected_global: %Global{ + tx_version: 2, + input_count: 1, + output_count: 2, + tx_modifiable: 6, + version: 2 + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + non_witness_utxo: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "125b805e5a51d715356e3019058dfff3b5f3bf42de932f82a1964b216e25aac1", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + } + ], + lock_time: 0 + }, + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + }, + previous_txid: + <<11, 10, 217, 33, 65, 156, 28, 135, 25, 115, 93, 114, 220, 115, 159, 158, 169, 224, + 99, 141, 31, 228, 193, 238, 240, 249, 148, 64, 132, 129, 95, 200>>, + output_index: 0 + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + } + ], + amount: 800_000_000, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<196, 48, 246, 76, 71, 86, 218, 49, 13, 189, 26, 8, 85, 114, 239, 41, 153, 38, 39, + 44>> + ] + } + }, + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + } + ], + amount: 199_998_859, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<77, 209, 147, 172, 150, 74, 86, 172, 27, 158, 28, 202, 132, 84, 254, 47, 71, 79, + 133, 19>> + ] + } + } + ] + }, + %{ + psbt: + "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEHAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==", + expected_global: %Global{ + tx_version: 2, + input_count: 1, + output_count: 2, + tx_modifiable: 7, + version: 2 + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + non_witness_utxo: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "125b805e5a51d715356e3019058dfff3b5f3bf42de932f82a1964b216e25aac1", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + } + ], + lock_time: 0 + }, + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + }, + previous_txid: + <<11, 10, 217, 33, 65, 156, 28, 135, 25, 115, 93, 114, 220, 115, 159, 158, 169, 224, + 99, 141, 31, 228, 193, 238, 240, 249, 148, 64, 132, 129, 95, 200>>, + output_index: 0 + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + } + ], + amount: 800_000_000, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<196, 48, 246, 76, 71, 86, 218, 49, 13, 189, 26, 8, 85, 114, 239, 41, 153, 38, 39, + 44>> + ] + } + }, + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + } + ], + amount: 199_998_859, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<77, 209, 147, 172, 150, 74, 86, 172, 27, 158, 28, 202, 132, 84, 254, 47, 71, 79, + 133, 19>> + ] + } + } + ] + }, + %{ + psbt: + "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgH/AfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==", + expected_global: %Global{ + tx_version: 2, + input_count: 1, + output_count: 2, + tx_modifiable: 255, + version: 2 + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + non_witness_utxo: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "125b805e5a51d715356e3019058dfff3b5f3bf42de932f82a1964b216e25aac1", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + } + ], + lock_time: 0 + }, + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + }, + previous_txid: + <<11, 10, 217, 33, 65, 156, 28, 135, 25, 115, 93, 114, 220, 115, 159, 158, 169, 224, + 99, 141, 31, 228, 193, 238, 240, 249, 148, 64, 132, 129, 95, 200>>, + output_index: 0 + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + } + ], + amount: 800_000_000, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<196, 48, 246, 76, 71, 86, 218, 49, 13, 189, 26, 8, 85, 114, 239, 41, 153, 38, 39, + 44>> + ] + } + }, + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + } + ], + amount: 199_998_859, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<77, 209, 147, 172, 150, 74, 86, 172, 27, 158, 28, 202, 132, 84, 254, 47, 71, 79, + 133, 19>> + ] + } + } + ] + }, + %{ + psbt: + "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQECAQYBBwH7BAIAAAAAAQBSAgAAAAHBqiVuIUuWoYIvk95Cv/O18/+NBRkwbjUV11FaXoBbEgAAAAAA/////wEYxpo7AAAAABYAFLCjrxRCCEEmk8p9FmhStS2wrvBuAAAAAAEBHxjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4BDiALCtkhQZwchxlzXXLcc5+eqeBjjR/kwe7w+ZRAhIFfyAEPBAAAAAABEAT+////AREEjI3EYgESBBAnAAAAIgIC1gH4SEamdV93a+AOPZ3o+xCsyTX7g8RfsBYtTK1at5IY9p2HPlQAAIABAACAAAAAgAAAAAAqAAAAAQMIAAivLwAAAAABBBYAFMQw9kxHVtoxDb0aCFVy7ymZJicsACICAuNvv/U91TQHDPj9OWYUaA81epuF23NAvxz6dF0q17NAGPadhz5UAACAAQAAgAAAAIABAAAAZAAAAAEDCIu96wsAAAAAAQQWABRN0ZOslkpWrBueHMqEVP4vR0+FEwA=", + expected_global: %Global{ + tx_version: 2, + fallback_locktime: 0, + input_count: 1, + output_count: 2, + tx_modifiable: 7, + version: 2 + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + non_witness_utxo: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "125b805e5a51d715356e3019058dfff3b5f3bf42de932f82a1964b216e25aac1", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + } + ], + lock_time: 0 + }, + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 999_999_000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + }, + previous_txid: + <<11, 10, 217, 33, 65, 156, 28, 135, 25, 115, 93, 114, 220, 115, 159, 158, 169, 224, + 99, 141, 31, 228, 193, 238, 240, 249, 148, 64, 132, 129, 95, 200>>, + output_index: 0, + sequence: 4_294_967_294, + required_time_locktime: 1_657_048_460, + required_height_locktime: 10000 + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + } + ], + amount: 800_000_000, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<196, 48, 246, 76, 71, 86, 218, 49, 13, 189, 26, 8, 85, 114, 239, 41, 153, 38, 39, + 44>> + ] + } + }, + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + } + ], + amount: 199_998_859, + script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<77, 209, 147, 172, 150, 74, 86, 172, 27, 158, 28, 202, 132, 84, 254, 47, 71, 79, + 133, 19>> + ] + } + } + ] + }, + # BIP 371 https://github.com/bitcoin/bips/blob/master/bip-0371.mediawiki#test-vectors + %{ + psbt: + "cHNidP8BAFICAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAFgAUdo4e60z0IIZgM/gKzv8PlyB0SWkAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1chFv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyGQB3Ky2nVgAAgAEAAIAAAACAAQAAAAAAAAABFyD+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMgAiAgNrdyptt02HU8mKgnlY3mx4qzMSEJ830+AwRIQkLs5z2Bh3Ky2nVAAAgAEAAIAAAACAAAAAAAAAAAAA", + expected_global: %Global{ + unsigned_tx: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "42b224669be683585854193052ef88b1efe2ee963af26c0dfe2730bfba4a7427", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 4_999_997_000, + script_pub_key: "0014768e1eeb4cf420866033f80aceff0f9720744969" + } + ], + lock_time: 0 + } + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 5_000_000_000, + script_pub_key: "51205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757" + }, + tap_bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_734, 2_147_483_649, 2_147_483_648, 1, 0] + }, + leaf_hashes: [], + pfp: <<119, 43, 45, 167>>, + pubkey: + <<254, 52, 144, 100, 201, 141, 110, 42, 133, 63, 163, 201, 177, 43, 216, 179, 4, + 161, 156, 25, 92, 96, 239, 167, 238, 35, 147, 4, 109, 63, 162, 50>> + } + ], + tap_internal_key: + <<254, 52, 144, 100, 201, 141, 110, 42, 133, 63, 163, 201, 177, 43, 216, 179, 4, 161, + 156, 25, 92, 96, 239, 167, 238, 35, 147, 4, 109, 63, 162, 50>> + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 0] + }, + pfp: <<119, 43, 45, 167>>, + public_key: "036b772a6db74d8753c98a827958de6c78ab3312109f37d3e0304484242ece73d8" + } + ] + } + ] + }, + %{ + psbt: + "cHNidP8BAFICAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAFgAUdo4e60z0IIZgM/gKzv8PlyB0SWkAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1cBE0C7U+yRe62dkGrxuocYHEi4as5aritTYFpyXKdGJWMUdvxvW67a9PLuD0d/NvWPOXDVuCc7fkl7l68uPxJcl680IRb+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMhkAdystp1YAAIABAACAAAAAgAEAAAAAAAAAARcg/jSQZMmNbiqFP6PJsSvYswShnBlcYO+n7iOTBG0/ojIAIgIDa3cqbbdNh1PJioJ5WN5seKszEhCfN9PgMESEJC7Oc9gYdystp1QAAIABAACAAAAAgAAAAAAAAAAAAA==", + expected_global: %Bitcoinex.PSBT.Global{ + unsigned_tx: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "42b224669be683585854193052ef88b1efe2ee963af26c0dfe2730bfba4a7427", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 4_999_997_000, + script_pub_key: "0014768e1eeb4cf420866033f80aceff0f9720744969" + } + ], + lock_time: 0 + } + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 5_000_000_000, + script_pub_key: "51205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757" + }, + tap_key_sig: + <<187, 83, 236, 145, 123, 173, 157, 144, 106, 241, 186, 135, 24, 28, 72, 184, 106, + 206, 90, 174, 43, 83, 96, 90, 114, 92, 167, 70, 37, 99, 20, 118, 252, 111, 91, 174, + 218, 244, 242, 238, 15, 71, 127, 54, 245, 143, 57, 112, 213, 184, 39, 59, 126, 73, + 123, 151, 175, 46, 63, 18, 92, 151, 175, 52>>, + tap_bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_734, 2_147_483_649, 2_147_483_648, 1, 0] + }, + leaf_hashes: [], + pfp: <<119, 43, 45, 167>>, + pubkey: + <<254, 52, 144, 100, 201, 141, 110, 42, 133, 63, 163, 201, 177, 43, 216, 179, 4, + 161, 156, 25, 92, 96, 239, 167, 238, 35, 147, 4, 109, 63, 162, 50>> + } + ], + tap_internal_key: + <<254, 52, 144, 100, 201, 141, 110, 42, 133, 63, 163, 201, 177, 43, 216, 179, 4, 161, + 156, 25, 92, 96, 239, 167, 238, 35, 147, 4, 109, 63, 162, 50>> + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 0] + }, + pfp: <<119, 43, 45, 167>>, + public_key: "036b772a6db74d8753c98a827958de6c78ab3312109f37d3e0304484242ece73d8" + } + ] + } + ] + }, + %{ + psbt: + "cHNidP8BAF4CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAIlEgg2mORYxmZOFZXXXaJZfeHiLul9eY5wbEwKS1qYI810MAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1chFv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyGQB3Ky2nVgAAgAEAAIAAAACAAQAAAAAAAAABFyD+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMgABBSARJNp67JLM0GyVRWJkf0N7E4uVchqEvivyJ2u92rPmcSEHESTaeuySzNBslUViZH9DexOLlXIahL4r8idrvdqz5nEZAHcrLadWAACAAQAAgAAAAIAAAAAABQAAAAA=", + expected_global: %Global{ + unsigned_tx: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "42b224669be683585854193052ef88b1efe2ee963af26c0dfe2730bfba4a7427", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 4_999_997_000, + script_pub_key: + "512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743" + } + ], + lock_time: 0 + } + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 5_000_000_000, + script_pub_key: "51205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757" + }, + tap_bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_734, 2_147_483_649, 2_147_483_648, 1, 0] + }, + leaf_hashes: [], + pfp: <<119, 43, 45, 167>>, + pubkey: + <<254, 52, 144, 100, 201, 141, 110, 42, 133, 63, 163, 201, 177, 43, 216, 179, 4, + 161, 156, 25, 92, 96, 239, 167, 238, 35, 147, 4, 109, 63, 162, 50>> + } + ], + tap_internal_key: + <<254, 52, 144, 100, 201, 141, 110, 42, 133, 63, 163, 201, 177, 43, 216, 179, 4, 161, + 156, 25, 92, 96, 239, 167, 238, 35, 147, 4, 109, 63, 162, 50>> + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + tap_internal_key: + <<17, 36, 218, 122, 236, 146, 204, 208, 108, 149, 69, 98, 100, 127, 67, 123, 19, 139, + 149, 114, 26, 132, 190, 43, 242, 39, 107, 189, 218, 179, 230, 113>>, + tap_bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_734, 2_147_483_649, 2_147_483_648, 0, 5] + }, + leaf_hashes: [], + pfp: <<119, 43, 45, 167>>, + pubkey: + <<17, 36, 218, 122, 236, 146, 204, 208, 108, 149, 69, 98, 100, 127, 67, 123, 19, + 139, 149, 114, 26, 132, 190, 43, 242, 39, 107, 189, 218, 179, 230, 113>> + } + ] + } + ] + }, + %{ + psbt: + "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgg2mORYxmZOFZXXXaJZfeHiLul9eY5wbEwKS1qYI810MAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJiFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4fgjICyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSrMBCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wJfG5v6l/3FP9XJEmZkIEOQG6YqhD1v35fZ4S8HQqabOIyBDILC/FvARtT6nvmFZJKp/J+XSmtIOoRVdhIZ2w7rRsqzAYhXBUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsDNlw4V9T/AyC+VD9Vg/6kZt2FyvgFzaKiZE68HT0ALCRFfLkkK98xFxPeFEfNgV85cWlxWMlop+0TfwgPzVuH4IyD6D3o87zsdDAps59JuF62gsuXJLRnvrUi0GFnLikUcqazAIRYssTrGgkjegGqmo2Wc88A+toIdCcgRSk6Gj+vehlu20jkBzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwl3Ky2nVgAAgAEAAIACAACAAAAAAAAAAAAhFkMgsL8W8BG1Pqe+YVkkqn8n5dKa0g6hFV2EhnbDutGyOQERXy5JCvfMRcT3hRHzYFfOXFpcVjJaKftE38ID81bh+HcrLadWAACAAQAAgAEAAIAAAAAAAAAAACEWUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAFAHxGHl0hFvoPejzvOx0MCmzn0m4XraCy5cktGe+tSLQYWcuKRRypOQFvfWIFnpSXoaSiZ1admHbaYBAa/zjjUpubk5zn+RrpcHcrLadWAACAAQAAgAMAAIAAAAAAAAAAAAEXIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrAARgg8DYuL3Wm9CClvePrIh2WrmcgzyX4GJDJWx13WstRXmUAAQUgESTaeuySzNBslUViZH9DexOLlXIahL4r8idrvdqz5nEhBxEk2nrskszQbJVFYmR/Q3sTi5VyGoS+K/Ina73as+ZxGQB3Ky2nVgAAgAEAAIAAAACAAAAAAAUAAAAA", + expected_global: %Global{ + unsigned_tx: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "2695697f810d60b5bc56eb7ff9c6f0546e5572f90120662ea7f90b236587d49b", + prev_vout: 1, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 4_999_997_000, + script_pub_key: + "512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743" + } + ], + lock_time: 0 + } + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 5_000_000_000, + script_pub_key: "5120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b692" + }, + tap_leaf_script: [ + %{ + control_block: + <<193, 80, 146, 155, 116, 193, 160, 73, 84, 183, 139, 75, 96, 53, 233, 122, 94, 7, + 138, 90, 15, 40, 236, 150, 213, 71, 191, 238, 154, 206, 128, 58, 192, 111, 125, + 98, 5, 158, 148, 151, 161, 164, 162, 103, 86, 157, 152, 118, 218, 96, 16, 26, + 255, 56, 227, 82, 155, 155, 147, 156, 231, 249, 26, 233, 112, 17, 95, 46, 73, + 10, 247, 204, 69, 196, 247, 133, 17, 243, 96, 87, 206, 92, 90, 92, 86, 50, 90, + 41, 251, 68, 223, 194, 3, 243, 86, 225, 248>>, + leaf_version: 192, + script: %Bitcoinex.Script{ + items: [ + 32, + <<44, 177, 58, 198, 130, 72, 222, 128, 106, 166, 163, 101, 156, 243, 192, 62, + 182, 130, 29, 9, 200, 17, 74, 78, 134, 143, 235, 222, 134, 91, 182, 210>>, + 172 + ] + } + }, + %{ + control_block: + <<193, 80, 146, 155, 116, 193, 160, 73, 84, 183, 139, 75, 96, 53, 233, 122, 94, 7, + 138, 90, 15, 40, 236, 150, 213, 71, 191, 238, 154, 206, 128, 58, 192, 151, 198, + 230, 254, 165, 255, 113, 79, 245, 114, 68, 153, 153, 8, 16, 228, 6, 233, 138, + 161, 15, 91, 247, 229, 246, 120, 75, 193, 208, 169, 166, 206>>, + leaf_version: 192, + script: %Bitcoinex.Script{ + items: [ + 32, + <<67, 32, 176, 191, 22, 240, 17, 181, 62, 167, 190, 97, 89, 36, 170, 127, 39, + 229, 210, 154, 210, 14, 161, 21, 93, 132, 134, 118, 195, 186, 209, 178>>, + 172 + ] + } + }, + %{ + control_block: + <<193, 80, 146, 155, 116, 193, 160, 73, 84, 183, 139, 75, 96, 53, 233, 122, 94, 7, + 138, 90, 15, 40, 236, 150, 213, 71, 191, 238, 154, 206, 128, 58, 192, 205, 151, + 14, 21, 245, 63, 192, 200, 47, 149, 15, 213, 96, 255, 169, 25, 183, 97, 114, + 190, 1, 115, 104, 168, 153, 19, 175, 7, 79, 64, 11, 9, 17, 95, 46, 73, 10, 247, + 204, 69, 196, 247, 133, 17, 243, 96, 87, 206, 92, 90, 92, 86, 50, 90, 41, 251, + 68, 223, 194, 3, 243, 86, 225, 248>>, + leaf_version: 192, + script: %Bitcoinex.Script{ + items: [ + 32, + <<250, 15, 122, 60, 239, 59, 29, 12, 10, 108, 231, 210, 110, 23, 173, 160, 178, + 229, 201, 45, 25, 239, 173, 72, 180, 24, 89, 203, 138, 69, 28, 169>>, + 172 + ] + } + } + ], + tap_bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_734, 2_147_483_649, 2_147_483_650, 0, 0] + }, + leaf_hashes: [ + <<205, 151, 14, 21, 245, 63, 192, 200, 47, 149, 15, 213, 96, 255, 169, 25, 183, + 97, 114, 190, 1, 115, 104, 168, 153, 19, 175, 7, 79, 64, 11, 9>> + ], + pfp: <<119, 43, 45, 167>>, + pubkey: + <<44, 177, 58, 198, 130, 72, 222, 128, 106, 166, 163, 101, 156, 243, 192, 62, 182, + 130, 29, 9, 200, 17, 74, 78, 134, 143, 235, 222, 134, 91, 182, 210>> + }, + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_734, 2_147_483_649, 2_147_483_649, 0, 0] + }, + leaf_hashes: [ + <<17, 95, 46, 73, 10, 247, 204, 69, 196, 247, 133, 17, 243, 96, 87, 206, 92, 90, + 92, 86, 50, 90, 41, 251, 68, 223, 194, 3, 243, 86, 225, 248>> + ], + pfp: <<119, 43, 45, 167>>, + pubkey: + <<67, 32, 176, 191, 22, 240, 17, 181, 62, 167, 190, 97, 89, 36, 170, 127, 39, 229, + 210, 154, 210, 14, 161, 21, 93, 132, 134, 118, 195, 186, 209, 178>> + }, + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{child_nums: []}, + leaf_hashes: [], + pfp: <<124, 70, 30, 93>>, + pubkey: + <<80, 146, 155, 116, 193, 160, 73, 84, 183, 139, 75, 96, 53, 233, 122, 94, 7, 138, + 90, 15, 40, 236, 150, 213, 71, 191, 238, 154, 206, 128, 58, 192>> + }, + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_734, 2_147_483_649, 2_147_483_651, 0, 0] + }, + leaf_hashes: [ + <<111, 125, 98, 5, 158, 148, 151, 161, 164, 162, 103, 86, 157, 152, 118, 218, 96, + 16, 26, 255, 56, 227, 82, 155, 155, 147, 156, 231, 249, 26, 233, 112>> + ], + pfp: <<119, 43, 45, 167>>, + pubkey: + <<250, 15, 122, 60, 239, 59, 29, 12, 10, 108, 231, 210, 110, 23, 173, 160, 178, + 229, 201, 45, 25, 239, 173, 72, 180, 24, 89, 203, 138, 69, 28, 169>> + } + ], + tap_internal_key: + <<80, 146, 155, 116, 193, 160, 73, 84, 183, 139, 75, 96, 53, 233, 122, 94, 7, 138, 90, + 15, 40, 236, 150, 213, 71, 191, 238, 154, 206, 128, 58, 192>>, + tap_merkle_root: + <<240, 54, 46, 47, 117, 166, 244, 32, 165, 189, 227, 235, 34, 29, 150, 174, 103, 32, + 207, 37, 248, 24, 144, 201, 91, 29, 119, 90, 203, 81, 94, 101>> + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + tap_internal_key: + <<17, 36, 218, 122, 236, 146, 204, 208, 108, 149, 69, 98, 100, 127, 67, 123, 19, 139, + 149, 114, 26, 132, 190, 43, 242, 39, 107, 189, 218, 179, 230, 113>>, + tap_bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_734, 2_147_483_649, 2_147_483_648, 0, 5] + }, + leaf_hashes: [], + pfp: <<119, 43, 45, 167>>, + pubkey: + <<17, 36, 218, 122, 236, 146, 204, 208, 108, 149, 69, 98, 100, 127, 67, 123, 19, + 139, 149, 114, 26, 132, 190, 43, 242, 39, 107, 189, 218, 179, 230, 113>> + } + ] + } + ] + }, + %{ + psbt: + "cHNidP8BAF4CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAIlEgCoy9yG3hzhwPnK6yLW33ztNoP+Qj4F0eQCqHk0HW9vUAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1chFv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyGQB3Ky2nVgAAgAEAAIAAAACAAQAAAAAAAAABFyD+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMgABBSBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wAEGbwLAIiBzblcpAP4SUliaIUPI88efcaBBLSNTr3VelwHHgmlKAqwCwCIgYxxfO1gyuPvev7GXBM7rMjwh9A96JPQ9aO8MwmsSWWmsAcAiIET6pJoDON5IjI3//s37bzKfOAvVZu8gyN9tgT6rHEJzrCEHRPqkmgM43kiMjf/+zftvMp84C9Vm7yDI322BPqscQnM5AfBreYuSoQ7ZqdC7/Trxc6U7FhfaOkFZygCCFs2Fay4Odystp1YAAIABAACAAQAAgAAAAAADAAAAIQdQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wAUAfEYeXSEHYxxfO1gyuPvev7GXBM7rMjwh9A96JPQ9aO8MwmsSWWk5ARis5AmIl4Xg6nDO67jhyokqenjq7eDy4pbPQ1lhqPTKdystp1YAAIABAACAAgAAgAAAAAADAAAAIQdzblcpAP4SUliaIUPI88efcaBBLSNTr3VelwHHgmlKAjkBKaW0kVCQFi11mv0/4Pk/ozJgVtC0CIy5M8rngmy42Cx3Ky2nVgAAgAEAAIADAACAAAAAAAMAAAAA", + expected_global: %Global{ + unsigned_tx: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "42b224669be683585854193052ef88b1efe2ee963af26c0dfe2730bfba4a7427", + prev_vout: 0, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 4_999_997_000, + script_pub_key: + "51200a8cbdc86de1ce1c0f9caeb22d6df7ced3683fe423e05d1e402a879341d6f6f5" + } + ], + lock_time: 0 + } + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 5_000_000_000, + script_pub_key: "51205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757" + }, + tap_bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_734, 2_147_483_649, 2_147_483_648, 1, 0] + }, + leaf_hashes: [], + pfp: <<119, 43, 45, 167>>, + pubkey: + <<254, 52, 144, 100, 201, 141, 110, 42, 133, 63, 163, 201, 177, 43, 216, 179, 4, + 161, 156, 25, 92, 96, 239, 167, 238, 35, 147, 4, 109, 63, 162, 50>> + } + ], + tap_internal_key: + <<254, 52, 144, 100, 201, 141, 110, 42, 133, 63, 163, 201, 177, 43, 216, 179, 4, 161, + 156, 25, 92, 96, 239, 167, 238, 35, 147, 4, 109, 63, 162, 50>> + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + tap_internal_key: + <<80, 146, 155, 116, 193, 160, 73, 84, 183, 139, 75, 96, 53, 233, 122, 94, 7, 138, 90, + 15, 40, 236, 150, 213, 71, 191, 238, 154, 206, 128, 58, 192>>, + tap_tree: %{ + leaves: [ + %{ + depth: 2, + leaf_version: 192, + script: %Bitcoinex.Script{ + items: [ + 32, + <<115, 110, 87, 41, 0, 254, 18, 82, 88, 154, 33, 67, 200, 243, 199, 159, 113, + 160, 65, 45, 35, 83, 175, 117, 94, 151, 1, 199, 130, 105, 74, 2>>, + 172 + ] + } + }, + %{ + depth: 2, + leaf_version: 192, + script: %Bitcoinex.Script{ + items: [ + 32, + <<99, 28, 95, 59, 88, 50, 184, 251, 222, 191, 177, 151, 4, 206, 235, 50, 60, + 33, 244, 15, 122, 36, 244, 61, 104, 239, 12, 194, 107, 18, 89, 105>>, + 172 + ] + } + }, + %{ + depth: 1, + leaf_version: 192, + script: %Bitcoinex.Script{ + items: [ + 32, + <<68, 250, 164, 154, 3, 56, 222, 72, 140, 141, 255, 254, 205, 251, 111, 50, + 159, 56, 11, 213, 102, 239, 32, 200, 223, 109, 129, 62, 171, 28, 66, 115>>, + 172 + ] + } + } + ] + }, + tap_bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_734, 2_147_483_649, 2_147_483_649, 0, 3] + }, + leaf_hashes: [ + <<240, 107, 121, 139, 146, 161, 14, 217, 169, 208, 187, 253, 58, 241, 115, 165, + 59, 22, 23, 218, 58, 65, 89, 202, 0, 130, 22, 205, 133, 107, 46, 14>> + ], + pfp: <<119, 43, 45, 167>>, + pubkey: + <<68, 250, 164, 154, 3, 56, 222, 72, 140, 141, 255, 254, 205, 251, 111, 50, 159, + 56, 11, 213, 102, 239, 32, 200, 223, 109, 129, 62, 171, 28, 66, 115>> + }, + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{child_nums: []}, + leaf_hashes: [], + pfp: <<124, 70, 30, 93>>, + pubkey: + <<80, 146, 155, 116, 193, 160, 73, 84, 183, 139, 75, 96, 53, 233, 122, 94, 7, 138, + 90, 15, 40, 236, 150, 213, 71, 191, 238, 154, 206, 128, 58, 192>> + }, + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_734, 2_147_483_649, 2_147_483_650, 0, 3] + }, + leaf_hashes: [ + <<24, 172, 228, 9, 136, 151, 133, 224, 234, 112, 206, 235, 184, 225, 202, 137, 42, + 122, 120, 234, 237, 224, 242, 226, 150, 207, 67, 89, 97, 168, 244, 202>> + ], + pfp: <<119, 43, 45, 167>>, + pubkey: + <<99, 28, 95, 59, 88, 50, 184, 251, 222, 191, 177, 151, 4, 206, 235, 50, 60, 33, + 244, 15, 122, 36, 244, 61, 104, 239, 12, 194, 107, 18, 89, 105>> + }, + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_734, 2_147_483_649, 2_147_483_651, 0, 3] + }, + leaf_hashes: [ + <<41, 165, 180, 145, 80, 144, 22, 45, 117, 154, 253, 63, 224, 249, 63, 163, 50, + 96, 86, 208, 180, 8, 140, 185, 51, 202, 231, 130, 108, 184, 216, 44>> + ], + pfp: <<119, 43, 45, 167>>, + pubkey: + <<115, 110, 87, 41, 0, 254, 18, 82, 88, 154, 33, 67, 200, 243, 199, 159, 113, 160, + 65, 45, 35, 83, 175, 117, 94, 151, 1, 199, 130, 105, 74, 2>> + } + ] + } + ] + }, + %{ + psbt: + "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgg2mORYxmZOFZXXXaJZfeHiLul9eY5wbEwKS1qYI810MAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJBFCyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwlAv4GNl1fW/+tTi6BX+0wfxOD17xhudlvrVkeR4Cr1/T1eJVHU404z2G8na4LJnHmu0/A5Wgge/NLMLGXdfmk9eUEUQyCwvxbwEbU+p75hWSSqfyfl0prSDqEVXYSGdsO60bIRXy5JCvfMRcT3hRHzYFfOXFpcVjJaKftE38ID81bh+EDh8atvq/omsjbyGDNxncHUKKt2jYD5H5mI2KvvR7+4Y7sfKlKfdowV8AzjTsKDzcB+iPhCi+KPbvZAQ8MpEYEaQRT6D3o87zsdDAps59JuF62gsuXJLRnvrUi0GFnLikUcqW99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwQOwfA3kgZGHIM0IoVCMyZwirAx8NpKJT7kWq+luMkgNNi2BUkPjNE+APmJmJuX4hX6o28S3uNpPS2szzeBwXV/ZiFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4fgjICyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSrMBCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wJfG5v6l/3FP9XJEmZkIEOQG6YqhD1v35fZ4S8HQqabOIyBDILC/FvARtT6nvmFZJKp/J+XSmtIOoRVdhIZ2w7rRsqzAYhXBUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsDNlw4V9T/AyC+VD9Vg/6kZt2FyvgFzaKiZE68HT0ALCRFfLkkK98xFxPeFEfNgV85cWlxWMlop+0TfwgPzVuH4IyD6D3o87zsdDAps59JuF62gsuXJLRnvrUi0GFnLikUcqazAIRYssTrGgkjegGqmo2Wc88A+toIdCcgRSk6Gj+vehlu20jkBzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwl3Ky2nVgAAgAEAAIACAACAAAAAAAAAAAAhFkMgsL8W8BG1Pqe+YVkkqn8n5dKa0g6hFV2EhnbDutGyOQERXy5JCvfMRcT3hRHzYFfOXFpcVjJaKftE38ID81bh+HcrLadWAACAAQAAgAEAAIAAAAAAAAAAACEWUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAFAHxGHl0hFvoPejzvOx0MCmzn0m4XraCy5cktGe+tSLQYWcuKRRypOQFvfWIFnpSXoaSiZ1admHbaYBAa/zjjUpubk5zn+RrpcHcrLadWAACAAQAAgAMAAIAAAAAAAAAAAAEXIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrAARgg8DYuL3Wm9CClvePrIh2WrmcgzyX4GJDJWx13WstRXmUAAQUgESTaeuySzNBslUViZH9DexOLlXIahL4r8idrvdqz5nEhBxEk2nrskszQbJVFYmR/Q3sTi5VyGoS+K/Ina73as+ZxGQB3Ky2nVgAAgAEAAIAAAACAAAAAAAUAAAAA", + expected_global: %Global{ + unsigned_tx: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "2695697f810d60b5bc56eb7ff9c6f0546e5572f90120662ea7f90b236587d49b", + prev_vout: 1, + script_sig: "", + sequence_no: 4_294_967_295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 4_999_997_000, + script_pub_key: + "512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743" + } + ], + lock_time: 0 + } + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 5_000_000_000, + script_pub_key: "5120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b692" + }, + tap_script_sig: [ + %{ + leaf_hash: + <<205, 151, 14, 21, 245, 63, 192, 200, 47, 149, 15, 213, 96, 255, 169, 25, 183, + 97, 114, 190, 1, 115, 104, 168, 153, 19, 175, 7, 79, 64, 11, 9>>, + pubkey: + <<44, 177, 58, 198, 130, 72, 222, 128, 106, 166, 163, 101, 156, 243, 192, 62, 182, + 130, 29, 9, 200, 17, 74, 78, 134, 143, 235, 222, 134, 91, 182, 210>>, + signature: + <<191, 129, 141, 151, 87, 214, 255, 235, 83, 139, 160, 87, 251, 76, 31, 196, 224, + 245, 239, 24, 110, 118, 91, 235, 86, 71, 145, 224, 42, 245, 253, 61, 94, 37, 81, + 212, 227, 78, 51, 216, 111, 39, 107, 130, 201, 156, 121, 174, 211, 240, 57, 90, + 8, 30, 252, 210, 204, 44, 101, 221, 126, 105, 61, 121>> + }, + %{ + leaf_hash: + <<17, 95, 46, 73, 10, 247, 204, 69, 196, 247, 133, 17, 243, 96, 87, 206, 92, 90, + 92, 86, 50, 90, 41, 251, 68, 223, 194, 3, 243, 86, 225, 248>>, + pubkey: + <<67, 32, 176, 191, 22, 240, 17, 181, 62, 167, 190, 97, 89, 36, 170, 127, 39, 229, + 210, 154, 210, 14, 161, 21, 93, 132, 134, 118, 195, 186, 209, 178>>, + signature: + <<225, 241, 171, 111, 171, 250, 38, 178, 54, 242, 24, 51, 113, 157, 193, 212, 40, + 171, 118, 141, 128, 249, 31, 153, 136, 216, 171, 239, 71, 191, 184, 99, 187, 31, + 42, 82, 159, 118, 140, 21, 240, 12, 227, 78, 194, 131, 205, 192, 126, 136, 248, + 66, 139, 226, 143, 110, 246, 64, 67, 195, 41, 17, 129, 26>> + }, + %{ + leaf_hash: + <<111, 125, 98, 5, 158, 148, 151, 161, 164, 162, 103, 86, 157, 152, 118, 218, 96, + 16, 26, 255, 56, 227, 82, 155, 155, 147, 156, 231, 249, 26, 233, 112>>, + pubkey: + <<250, 15, 122, 60, 239, 59, 29, 12, 10, 108, 231, 210, 110, 23, 173, 160, 178, + 229, 201, 45, 25, 239, 173, 72, 180, 24, 89, 203, 138, 69, 28, 169>>, + signature: + <<236, 31, 3, 121, 32, 100, 97, 200, 51, 66, 40, 84, 35, 50, 103, 8, 171, 3, 31, + 13, 164, 162, 83, 238, 69, 170, 250, 91, 140, 146, 3, 77, 139, 96, 84, 144, 248, + 205, 19, 224, 15, 152, 153, 137, 185, 126, 33, 95, 170, 54, 241, 45, 238, 54, + 147, 210, 218, 204, 243, 120, 28, 23, 87, 246>> + } + ], + tap_leaf_script: [ + %{ + control_block: + <<193, 80, 146, 155, 116, 193, 160, 73, 84, 183, 139, 75, 96, 53, 233, 122, 94, 7, + 138, 90, 15, 40, 236, 150, 213, 71, 191, 238, 154, 206, 128, 58, 192, 111, 125, + 98, 5, 158, 148, 151, 161, 164, 162, 103, 86, 157, 152, 118, 218, 96, 16, 26, + 255, 56, 227, 82, 155, 155, 147, 156, 231, 249, 26, 233, 112, 17, 95, 46, 73, + 10, 247, 204, 69, 196, 247, 133, 17, 243, 96, 87, 206, 92, 90, 92, 86, 50, 90, + 41, 251, 68, 223, 194, 3, 243, 86, 225, 248>>, + leaf_version: 192, + script: %Bitcoinex.Script{ + items: [ + 32, + <<44, 177, 58, 198, 130, 72, 222, 128, 106, 166, 163, 101, 156, 243, 192, 62, + 182, 130, 29, 9, 200, 17, 74, 78, 134, 143, 235, 222, 134, 91, 182, 210>>, + 172 + ] + } + }, + %{ + control_block: + <<193, 80, 146, 155, 116, 193, 160, 73, 84, 183, 139, 75, 96, 53, 233, 122, 94, 7, + 138, 90, 15, 40, 236, 150, 213, 71, 191, 238, 154, 206, 128, 58, 192, 151, 198, + 230, 254, 165, 255, 113, 79, 245, 114, 68, 153, 153, 8, 16, 228, 6, 233, 138, + 161, 15, 91, 247, 229, 246, 120, 75, 193, 208, 169, 166, 206>>, + leaf_version: 192, + script: %Bitcoinex.Script{ + items: [ + 32, + <<67, 32, 176, 191, 22, 240, 17, 181, 62, 167, 190, 97, 89, 36, 170, 127, 39, + 229, 210, 154, 210, 14, 161, 21, 93, 132, 134, 118, 195, 186, 209, 178>>, + 172 + ] + } + }, + %{ + control_block: + <<193, 80, 146, 155, 116, 193, 160, 73, 84, 183, 139, 75, 96, 53, 233, 122, 94, 7, + 138, 90, 15, 40, 236, 150, 213, 71, 191, 238, 154, 206, 128, 58, 192, 205, 151, + 14, 21, 245, 63, 192, 200, 47, 149, 15, 213, 96, 255, 169, 25, 183, 97, 114, + 190, 1, 115, 104, 168, 153, 19, 175, 7, 79, 64, 11, 9, 17, 95, 46, 73, 10, 247, + 204, 69, 196, 247, 133, 17, 243, 96, 87, 206, 92, 90, 92, 86, 50, 90, 41, 251, + 68, 223, 194, 3, 243, 86, 225, 248>>, + leaf_version: 192, + script: %Bitcoinex.Script{ + items: [ + 32, + <<250, 15, 122, 60, 239, 59, 29, 12, 10, 108, 231, 210, 110, 23, 173, 160, 178, + 229, 201, 45, 25, 239, 173, 72, 180, 24, 89, 203, 138, 69, 28, 169>>, + 172 + ] + } + } + ], + tap_bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_734, 2_147_483_649, 2_147_483_650, 0, 0] + }, + leaf_hashes: [ + <<205, 151, 14, 21, 245, 63, 192, 200, 47, 149, 15, 213, 96, 255, 169, 25, 183, + 97, 114, 190, 1, 115, 104, 168, 153, 19, 175, 7, 79, 64, 11, 9>> + ], + pfp: <<119, 43, 45, 167>>, + pubkey: + <<44, 177, 58, 198, 130, 72, 222, 128, 106, 166, 163, 101, 156, 243, 192, 62, 182, + 130, 29, 9, 200, 17, 74, 78, 134, 143, 235, 222, 134, 91, 182, 210>> + }, + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_734, 2_147_483_649, 2_147_483_649, 0, 0] + }, + leaf_hashes: [ + <<17, 95, 46, 73, 10, 247, 204, 69, 196, 247, 133, 17, 243, 96, 87, 206, 92, 90, + 92, 86, 50, 90, 41, 251, 68, 223, 194, 3, 243, 86, 225, 248>> + ], + pfp: <<119, 43, 45, 167>>, + pubkey: + <<67, 32, 176, 191, 22, 240, 17, 181, 62, 167, 190, 97, 89, 36, 170, 127, 39, 229, + 210, 154, 210, 14, 161, 21, 93, 132, 134, 118, 195, 186, 209, 178>> + }, + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{child_nums: []}, + leaf_hashes: [], + pfp: <<124, 70, 30, 93>>, + pubkey: + <<80, 146, 155, 116, 193, 160, 73, 84, 183, 139, 75, 96, 53, 233, 122, 94, 7, 138, + 90, 15, 40, 236, 150, 213, 71, 191, 238, 154, 206, 128, 58, 192>> + }, + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_734, 2_147_483_649, 2_147_483_651, 0, 0] + }, + leaf_hashes: [ + <<111, 125, 98, 5, 158, 148, 151, 161, 164, 162, 103, 86, 157, 152, 118, 218, 96, + 16, 26, 255, 56, 227, 82, 155, 155, 147, 156, 231, 249, 26, 233, 112>> + ], + pfp: <<119, 43, 45, 167>>, + pubkey: + <<250, 15, 122, 60, 239, 59, 29, 12, 10, 108, 231, 210, 110, 23, 173, 160, 178, + 229, 201, 45, 25, 239, 173, 72, 180, 24, 89, 203, 138, 69, 28, 169>> + } + ], + tap_internal_key: + <<80, 146, 155, 116, 193, 160, 73, 84, 183, 139, 75, 96, 53, 233, 122, 94, 7, 138, 90, + 15, 40, 236, 150, 213, 71, 191, 238, 154, 206, 128, 58, 192>>, + tap_merkle_root: + <<240, 54, 46, 47, 117, 166, 244, 32, 165, 189, 227, 235, 34, 29, 150, 174, 103, 32, + 207, 37, 248, 24, 144, 201, 91, 29, 119, 90, 203, 81, 94, 101>> + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + tap_internal_key: + <<17, 36, 218, 122, 236, 146, 204, 208, 108, 149, 69, 98, 100, 127, 67, 123, 19, 139, + 149, 114, 26, 132, 190, 43, 242, 39, 107, 189, 218, 179, 230, 113>>, + tap_bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_734, 2_147_483_649, 2_147_483_648, 0, 5] + }, + leaf_hashes: [], + pfp: <<119, 43, 45, 167>>, + pubkey: + <<17, 36, 218, 122, 236, 146, 204, 208, 108, 149, 69, 98, 100, 127, 67, 123, 19, + 139, 149, 114, 26, 132, 190, 43, 242, 39, 107, 189, 218, 179, 230, 113>> + } + ] } ] }