From 785df114f6afbc69df3ff1a7573847ad6b59f0dd Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Tue, 2 Nov 2021 00:22:19 -0700 Subject: [PATCH 01/63] use derivation paths in PSBT --- lib/extendedkey.ex | 88 +++++++++++++++++++++++++++++++++------ lib/psbt.ex | 34 +++++++-------- lib/script.ex | 2 +- test/extendedkey_test.exs | 80 ++++++++++++++++++++++++----------- test/psbt_test.exs | 40 +++++++++++++----- 5 files changed, 179 insertions(+), 65 deletions(-) diff --git a/lib/extendedkey.ex b/lib/extendedkey.ex index d3f9824..54a4fe7 100644 --- a/lib/extendedkey.ex +++ b/lib/extendedkey.ex @@ -42,8 +42,12 @@ defmodule Bitcoinex.ExtendedKey do def new(), do: %__MODULE__{child_nums: []} - @spec to_string(t()) :: {:ok, String.t()} | {:error, String.t()} - def to_string(%__MODULE__{child_nums: path}), do: tto_string(path, "") + @spec serialize(t(), atom) :: {:ok, String.t()} | {:ok, binary} | {:error, String.t()} + def serialize(dp = %__MODULE__{}, :to_string), do: path_to_string(dp) + def serialize(dp = %__MODULE__{}, :to_bin), do: to_bin(dp) + + @spec path_to_string(t()) :: {:ok, String.t()} | {:error, String.t()} + def path_to_string(%__MODULE__{child_nums: path}), do: tto_string(path, "") defp tto_string([], path_acc), do: {:ok, path_acc} @@ -78,26 +82,83 @@ defmodule Bitcoinex.ExtendedKey do end end - @spec from_string(String.t()) :: {:ok, t()} | {:error, String.t()} - def from_string(pathstr) do + @spec to_bin(t()) :: {:ok, binary} | {:error, String.t()} + def to_bin(%__MODULE__{child_nums: child_nums}) do + try do + {:ok, tto_bin(child_nums, <<>>)} + rescue + e in ArgumentError -> {:error, e.message} + end + end + + defp tto_bin([], path_acc), do: path_acc + defp tto_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) + tto_bin(rest, path_acc <> lvlbin) + end + end + + @spec parse(binary, atom) :: {:ok, t()} | {:error, String.t()} + def parse(dp, :from_bin), do: from_bin(dp) + def parse(dp, :from_string), do: path_from_string(dp) + + @spec path_from_string(String.t()) :: {:ok, t()} | {:error, String.t()} + def path_from_string(pathstr) do try do - {:ok, %__MODULE__{child_nums: tfrom_string(String.split(pathstr, "/"))}} + {:ok, %__MODULE__{child_nums: + pathstr + |> String.split("/") + |> tfrom_string([]) + |> Enum.reverse() + } + } rescue e in ArgumentError -> {:error, e.message} end end - defp tfrom_string(path_list) do + defp tfrom_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 + tfrom_string(rest, child_nums) + end + ["*" | rest] -> tfrom_string(rest, [:any | child_nums]) + ["*'" | rest] -> tfrom_string(rest, [:anyh | child_nums]) + ["*h" | rest] -> tfrom_string(rest, [:anyh | child_nums]) + [i | rest] -> tfrom_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(tfrom_bin(bin, []))}} + rescue + _e in ArgumentError -> {:error, "invalid binary encoding of derivation path"} end end + + defp tfrom_bin(<<>>, child_nums), do: child_nums + defp tfrom_bin(<>, child_nums), do: tfrom_bin(bin, [level | child_nums]) defp str_to_level(level) do {num, is_hardened} = @@ -111,6 +172,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 diff --git a/lib/psbt.ex b/lib/psbt.ex index e07794f..f04696d 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -121,6 +121,7 @@ defmodule Bitcoinex.PSBT.Global do alias Bitcoinex.Transaction.Utils, as: TxUtils alias Bitcoinex.PSBT.Utils, as: PsbtUtils alias Bitcoinex.Base58 + alias Bitcoinex.ExtendedKey.DerivationPath, as: DerivationPath defstruct [ :unsigned_tx, @@ -143,7 +144,7 @@ defmodule Bitcoinex.PSBT.Global do {txn_len, psbt} = TxUtils.get_counter(psbt) <> = psbt - # todo, different decode function for txn, directly in bytes + # TODO, different decode function for txn, directly in bytes case Transaction.decode(Base.encode16(txn_bytes, case: :lower)) do {:ok, txn} -> {%Global{global | unsigned_tx: txn}, psbt} @@ -158,7 +159,7 @@ defmodule Bitcoinex.PSBT.Global do <> = value - indexes = for <>, do: chunk + {:ok, indexes} = DerivationPath.parse(paths, :from_bin) global_xpub = case global.xpub do @@ -207,10 +208,9 @@ defmodule Bitcoinex.PSBT.Global do key = <<@psbt_global_xpub::big-size(8)>> {:ok, key_data} = Base58.decode(value.xpub) - val = - <> <> - (for(chunk <- value.derivation, do: <>) - |> :erlang.list_to_binary()) + {:ok, deriv_bin} = DerivationPath.serialize(value.derivation, :to_bin) + + val = <> <> deriv_bin PsbtUtils.serialize_kv(key <> key_data, val) end @@ -241,6 +241,7 @@ defmodule Bitcoinex.PSBT.In do alias Bitcoinex.PSBT.In alias Bitcoinex.PSBT.Utils, as: PsbtUtils alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.ExtendedKey.DerivationPath, as: DerivationPath defstruct [ :non_witness_utxo, @@ -329,10 +330,9 @@ 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()) + {:ok, deriv_bin} = DerivationPath.serialize(value.derivation, :to_bin) + + val = <> <> deriv_bin PsbtUtils.serialize_kv(<<@psbt_in_bip32_derivation::big-size(8)>> <> key_data, val) end @@ -464,7 +464,7 @@ defmodule Bitcoinex.PSBT.In do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) <> = value - indexes = for <>, do: chunk + {:ok, indexes} = DerivationPath.parse(paths, :from_bin) bip32_derivation = case input.bip32_derivation do @@ -524,6 +524,7 @@ defmodule Bitcoinex.PSBT.Out do """ alias Bitcoinex.PSBT.Out alias Bitcoinex.PSBT.Utils, as: PsbtUtils + alias Bitcoinex.ExtendedKey.DerivationPath, as: DerivationPath defstruct [ :redeem_script, @@ -560,11 +561,10 @@ 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()) + + {:ok, deriv_bin} = DerivationPath.serialize(value.derivation, :to_bin) + + val = <> <> deriv_bin PsbtUtils.serialize_kv(<<@psbt_out_bip32_derivation::big-size(8)>> <> key_data, val) end @@ -652,7 +652,7 @@ defmodule Bitcoinex.PSBT.Out do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) <> = value - indexes = for <>, do: chunk + {:ok, indexes} = DerivationPath.parse(paths, :from_bin) bip32_derivation = case output.bip32_derivation do diff --git a/lib/script.ex b/lib/script.ex index 63b1d97..6a00240 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -413,7 +413,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/test/extendedkey_test.exs b/test/extendedkey_test.exs index b2826b3..bf4e4fc 100644 --- a/test/extendedkey_test.exs +++ b/test/extendedkey_test.exs @@ -121,22 +121,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/", @@ -148,14 +151,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", @@ -167,31 +172,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", @@ -203,7 +213,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", @@ -215,25 +226,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>> } ] @@ -708,23 +723,40 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do end describe "Derivation Path parse/ser testing" do - test "from_string/1" do - for t <- @strings_to_derivation_paths do - assert ExtendedKey.DerivationPath.from_string(t.str) == {:ok, t.deriv} + test "path_from_string/1" do + for t <- @derivation_paths_to_parse do + if ExtendedKey.DerivationPath.path_from_string(t.str) != {:ok, t.deriv}, do: IO.puts t.str + assert ExtendedKey.DerivationPath.path_from_string(t.str) == {:ok, t.deriv} end end - test "to_string/1" do - for t <- @derivation_paths_to_strings do - assert ExtendedKey.DerivationPath.to_string(t.deriv) == {:ok, t.str} + test "path_to_string/1" do + for t <- @derivation_paths_to_serialize do + assert ExtendedKey.DerivationPath.path_to_string(t.deriv) == {:ok, t.str} end end test "Raise exceptions on invalid derivation paths" do for t <- @error_derivation_path_strings do - {res, _} = ExtendedKey.DerivationPath.from_string(t) + {res, _} = ExtendedKey.DerivationPath.path_from_string(t) assert :error == res 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 2c14957..a5396c6 100644 --- a/test/psbt_test.exs +++ b/test/psbt_test.exs @@ -204,13 +204,17 @@ defmodule Bitcoinex.PSBTTest do version: nil, xpub: [ %{ - derivation: [2_147_483_822, 2_147_483_648], + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_822, 2_147_483_648] + }, master_pfp: 1_332_350_169, xpub: "tpubDBkJeJo2X94Yq3RVz65DoUgyLUkaDrkfyrn2VcgyCRSKCRonvKvCF2FpYDGJWDkdRHBajXJGpc63GnumUt63ySvqCu2XaTRGVTKMYGuFk9H" }, %{ - derivation: [2_147_483_822, 2_147_483_649], + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2_147_483_822, 2_147_483_649] + }, master_pfp: 1_332_350_169, xpub: "tpubDBkJeJo2X94YsvtBEU1eKoibEWiNv51nW5iHhs6VZp59jsE6nen8KZMFyGHuGbCvqjRqirgeMcfpVBkttpUUT6brm4duzSGoZeTbhqCNUu6" @@ -221,12 +225,16 @@ defmodule Bitcoinex.PSBTTest do %Bitcoinex.PSBT.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 }, %{ - 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 } @@ -252,7 +260,9 @@ 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 } @@ -294,12 +304,16 @@ defmodule Bitcoinex.PSBTTest do %Bitcoinex.PSBT.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 }, %{ - 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 } @@ -426,17 +440,23 @@ defmodule Bitcoinex.PSBTTest do %Bitcoinex.PSBT.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 }, %{ - 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 }, %{ - 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 } From f66784aff9af750bebd5ce1d310b1d0ca8240da7 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Tue, 2 Nov 2021 00:24:00 -0700 Subject: [PATCH 02/63] rename parse/serialize/display functions --- lib/extendedkey.ex | 30 +++---- test/extendedkey_test.exs | 168 +++++++++++++++++++------------------- 2 files changed, 99 insertions(+), 99 deletions(-) diff --git a/lib/extendedkey.ex b/lib/extendedkey.ex index 54a4fe7..df23042 100644 --- a/lib/extendedkey.ex +++ b/lib/extendedkey.ex @@ -307,11 +307,11 @@ defmodule Bitcoinex.ExtendedKey do # PARSE & SERIALIZE @doc """ - parse_extended_key takes binary or string representation + parse takes binary or string representation of an extended key and parses it to an extended key object """ - @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 = < {:error, "error parsing key"} @@ -354,7 +354,7 @@ defmodule Bitcoinex.ExtendedKey do {:ok, xkey} -> xkey |> Base58.append_checksum() - |> parse_extended_key() + |> parse() end end @@ -365,11 +365,11 @@ 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 + @spec serialize(t()) :: binary + def serialize(xkey) do (xkey.prefix <> xkey.depth <> xkey.parent_fingerprint <> xkey.child_num <> xkey.chaincode <> xkey.key) |> Base58.append_checksum() @@ -378,10 +378,10 @@ defmodule Bitcoinex.ExtendedKey do @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 @@ -401,7 +401,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 @@ -430,7 +430,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 @@ -537,7 +537,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 @@ -588,7 +588,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/test/extendedkey_test.exs b/test/extendedkey_test.exs index bf4e4fc..5ab1377 100644 --- a/test/extendedkey_test.exs +++ b/test/extendedkey_test.exs @@ -266,15 +266,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 @@ -290,100 +290,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 @@ -394,7 +394,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 @@ -403,20 +403,20 @@ 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() + 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] @@ -430,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} = @@ -486,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 @@ -514,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 @@ -523,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] @@ -537,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: [ @@ -558,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 @@ -573,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 @@ -604,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} = @@ -619,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 @@ -629,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) @@ -646,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 @@ -658,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 @@ -678,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 @@ -693,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 @@ -713,11 +713,11 @@ 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 From 7845135b591ec48d86776ebd8258d3766657a02a Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Tue, 2 Nov 2021 01:05:48 -0700 Subject: [PATCH 03/63] use xpubs in PSBT --- lib/extendedkey.ex | 107 +++++++++++++++++++++++++++++--------- lib/psbt.ex | 23 +++++--- test/extendedkey_test.exs | 37 ++++++------- test/psbt_test.exs | 8 ++- 4 files changed, 123 insertions(+), 52 deletions(-) diff --git a/lib/extendedkey.ex b/lib/extendedkey.ex index df23042..d776fcb 100644 --- a/lib/extendedkey.ex +++ b/lib/extendedkey.ex @@ -92,10 +92,13 @@ defmodule Bitcoinex.ExtendedKey do end defp tto_bin([], path_acc), do: path_acc + defp tto_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.") + 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}") @@ -104,10 +107,11 @@ defmodule Bitcoinex.ExtendedKey do raise(ArgumentError, message: "index cannot be less than #{@min_non_hardened_child_num}") true -> - lvlbin = + lvlbin = lvl - |> :binary.encode_unsigned(:little) + |> :binary.encode_unsigned(:little) |> Bitcoinex.Utils.pad(4, :trailing) + tto_bin(rest, path_acc <> lvlbin) end end @@ -119,13 +123,14 @@ defmodule Bitcoinex.ExtendedKey do @spec path_from_string(String.t()) :: {:ok, t()} | {:error, String.t()} def path_from_string(pathstr) do try do - {:ok, %__MODULE__{child_nums: - pathstr - |> String.split("/") - |> tfrom_string([]) - |> Enum.reverse() - } - } + {:ok, + %__MODULE__{ + child_nums: + pathstr + |> String.split("/") + |> tfrom_string([]) + |> Enum.reverse() + }} rescue e in ArgumentError -> {:error, e.message} end @@ -133,32 +138,48 @@ defmodule Bitcoinex.ExtendedKey do defp tfrom_string(path_list, child_nums) do case path_list do - [] -> child_nums - [""] -> child_nums - ["m" | 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.") + raise(ArgumentError, + message: "m can only be present at the begining of a derivation path." + ) else - tfrom_string(rest, child_nums) + tfrom_string(rest, child_nums) end - ["*" | rest] -> tfrom_string(rest, [:any | child_nums]) - ["*'" | rest] -> tfrom_string(rest, [:anyh | child_nums]) - ["*h" | rest] -> tfrom_string(rest, [:anyh | child_nums]) - [i | rest] -> tfrom_string(rest, [str_to_level(i) | child_nums]) + + ["*" | rest] -> + tfrom_string(rest, [:any | child_nums]) + + ["*'" | rest] -> + tfrom_string(rest, [:anyh | child_nums]) + + ["*h" | rest] -> + tfrom_string(rest, [:anyh | child_nums]) + + [i | rest] -> + tfrom_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 + def from_bin(bin) do + try do {:ok, %__MODULE__{child_nums: Enum.reverse(tfrom_bin(bin, []))}} rescue _e in ArgumentError -> {:error, "invalid binary encoding of derivation path"} end end - + defp tfrom_bin(<<>>, child_nums), do: child_nums - defp tfrom_bin(<>, child_nums), do: tfrom_bin(bin, [level | child_nums]) + + defp tfrom_bin(<>, child_nums), + do: tfrom_bin(bin, [level | child_nums]) defp str_to_level(level) do {num, is_hardened} = @@ -172,7 +193,7 @@ defmodule Bitcoinex.ExtendedKey do nnum = String.to_integer(num) - #TODO benchmark and make this two comparisons + # 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 @@ -186,6 +207,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__{ @@ -306,9 +329,23 @@ defmodule Bitcoinex.ExtendedKey do # PARSE & SERIALIZE + @doc """ + 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(binary) :: {:ok, t()} | {:error, String.t()} def parse( @@ -345,6 +382,17 @@ defmodule Bitcoinex.ExtendedKey do end end + # parse without checksum (used for PSBT encodings) + def parse( + xkey = + <<_prefix::binary-size(4), _depth::binary-size(1), _parent_fingerprint::binary-size(4), + _child_num::binary-size(4), _chaincode::binary-size(32), _key::binary-size(33)>> + ) do + xkey + |> Base58.append_checksum() + |> parse() + end + # parse from string def parse(xkey) do case Base58.decode(xkey) do @@ -375,6 +423,17 @@ defmodule Bitcoinex.ExtendedKey do |> Base58.append_checksum() end + @doc """ + serialize takes an extended key + and returns the binary without the checksum appended + (used for PSBT encoding) + """ + @spec serialize(t(), atom) :: binary + def serialize(xkey = %__MODULE__{}, :no_checksum) do + xkey.prefix <> + xkey.depth <> xkey.parent_fingerprint <> xkey.child_num <> xkey.chaincode <> xkey.key + end + @doc """ display returns the extended key as a string """ diff --git a/lib/psbt.ex b/lib/psbt.ex index f04696d..67f1017 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -120,7 +120,7 @@ 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 [ @@ -160,13 +160,20 @@ defmodule Bitcoinex.PSBT.Global do <> = value {:ok, indexes} = DerivationPath.parse(paths, :from_bin) + {:ok, xpub} = ExtendedKey.parse(xpub) + + if :binary.decode_unsigned(xpub.depth) != DerivationPath.depth(indexes), + 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), + xpub: xpub, master_pfp: master, derivation: indexes } @@ -176,7 +183,7 @@ defmodule Bitcoinex.PSBT.Global do global.xpub ++ [ %{ - xpub: Base58.encode(xpub), + xpub: xpub, master_pfp: master, derivation: indexes } @@ -206,10 +213,10 @@ 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, :no_checksum) {:ok, deriv_bin} = DerivationPath.serialize(value.derivation, :to_bin) - + val = <> <> deriv_bin PsbtUtils.serialize_kv(key <> key_data, val) @@ -331,7 +338,7 @@ defmodule Bitcoinex.PSBT.In do key_data = Base.decode16!(value.public_key, case: :lower) {:ok, deriv_bin} = DerivationPath.serialize(value.derivation, :to_bin) - + val = <> <> deriv_bin PsbtUtils.serialize_kv(<<@psbt_in_bip32_derivation::big-size(8)>> <> key_data, val) @@ -561,9 +568,9 @@ defmodule Bitcoinex.PSBT.Out do defp serialize_kv(:bip32_derivation, value) when value != nil do key_data = Base.decode16!(value.public_key, case: :lower) - + {:ok, deriv_bin} = DerivationPath.serialize(value.derivation, :to_bin) - + val = <> <> deriv_bin PsbtUtils.serialize_kv(<<@psbt_out_bip32_derivation::big-size(8)>> <> key_data, val) diff --git a/test/extendedkey_test.exs b/test/extendedkey_test.exs index 5ab1377..c9a87d0 100644 --- a/test/extendedkey_test.exs +++ b/test/extendedkey_test.exs @@ -125,21 +125,21 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do %{ str: "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>> + 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>> + 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>> + bin: <<84, 0, 0, 128, 0, 0, 0, 128>> }, %{ str: "84'/0'/1/2/2147483647/", @@ -152,7 +152,7 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do 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 >> + bin: <<84, 0, 0, 128, 0, 0, 0, 128, 1, 0, 0, 0, 2, 0, 0, 0, 255, 255, 255, 127>> } ] @@ -160,7 +160,7 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do %{ str: "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>> + 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", @@ -173,35 +173,35 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do 1 ] }, - bin: <<84,0,0,128, 0,0,0,128, 0,0,0,128, 2,0,0,0, 1,0,0,0>> + 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>> + 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>> + 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>> + 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>> + bin: <<84, 0, 0, 128, 0, 0, 0, 128>> }, %{ str: "84'/0'/1/2/2147483647", @@ -214,7 +214,7 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do 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>> + 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", @@ -227,28 +227,28 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do 1 ] }, - bin: <<84,0,0,128, 0,0,0,128, 0,0,0,128, 2,0,0,0, 1,0,0,0>> + 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>> + 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>> + 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>> + bin: <<84, 0, 0, 128, 0, 0, 0, 128>> } ] @@ -415,8 +415,7 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do 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() + {: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] @@ -725,7 +724,9 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do describe "Derivation Path parse/ser testing" do test "path_from_string/1" do for t <- @derivation_paths_to_parse do - if ExtendedKey.DerivationPath.path_from_string(t.str) != {:ok, t.deriv}, do: IO.puts t.str + if ExtendedKey.DerivationPath.path_from_string(t.str) != {:ok, t.deriv}, + do: IO.puts(t.str) + assert ExtendedKey.DerivationPath.path_from_string(t.str) == {:ok, t.deriv} end end diff --git a/test/psbt_test.exs b/test/psbt_test.exs index a5396c6..2617d06 100644 --- a/test/psbt_test.exs +++ b/test/psbt_test.exs @@ -209,7 +209,9 @@ defmodule Bitcoinex.PSBTTest do }, master_pfp: 1_332_350_169, xpub: - "tpubDBkJeJo2X94Yq3RVz65DoUgyLUkaDrkfyrn2VcgyCRSKCRonvKvCF2FpYDGJWDkdRHBajXJGpc63GnumUt63ySvqCu2XaTRGVTKMYGuFk9H" + Bitcoinex.ExtendedKey.parse!( + "tpubDBkJeJo2X94Yq3RVz65DoUgyLUkaDrkfyrn2VcgyCRSKCRonvKvCF2FpYDGJWDkdRHBajXJGpc63GnumUt63ySvqCu2XaTRGVTKMYGuFk9H" + ) }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -217,7 +219,9 @@ defmodule Bitcoinex.PSBTTest do }, master_pfp: 1_332_350_169, xpub: - "tpubDBkJeJo2X94YsvtBEU1eKoibEWiNv51nW5iHhs6VZp59jsE6nen8KZMFyGHuGbCvqjRqirgeMcfpVBkttpUUT6brm4duzSGoZeTbhqCNUu6" + Bitcoinex.ExtendedKey.parse!( + "tpubDBkJeJo2X94YsvtBEU1eKoibEWiNv51nW5iHhs6VZp59jsE6nen8KZMFyGHuGbCvqjRqirgeMcfpVBkttpUUT6brm4duzSGoZeTbhqCNUu6" + ) } ] }, From 0fdbd74b97e6a5607c561014d792a55336adf66d Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Wed, 3 Nov 2021 00:20:28 -0700 Subject: [PATCH 04/63] rename prv functions --- lib/extendedkey.ex | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/extendedkey.ex b/lib/extendedkey.ex index d776fcb..33f2d75 100644 --- a/lib/extendedkey.ex +++ b/lib/extendedkey.ex @@ -47,17 +47,17 @@ defmodule Bitcoinex.ExtendedKey do def serialize(dp = %__MODULE__{}, :to_bin), do: to_bin(dp) @spec path_to_string(t()) :: {:ok, String.t()} | {:error, String.t()} - def path_to_string(%__MODULE__{child_nums: path}), do: tto_string(path, "") + def path_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}"} @@ -67,7 +67,7 @@ defmodule Bitcoinex.ExtendedKey do # hardened l >= @min_hardened_child_num -> - tto_string( + path_to_string( rest, path_acc <> (l @@ -78,22 +78,22 @@ 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, tto_bin(child_nums, <<>>)} + {:ok, to_bin(child_nums, <<>>)} rescue e in ArgumentError -> {:error, e.message} end end - defp tto_bin([], path_acc), do: path_acc + defp to_bin([], path_acc), do: path_acc - defp tto_bin([lvl | rest], path_acc) do + defp to_bin([lvl | rest], path_acc) do cond do lvl == :any or lvl == :anyh -> raise(ArgumentError, @@ -112,7 +112,7 @@ defmodule Bitcoinex.ExtendedKey do |> :binary.encode_unsigned(:little) |> Bitcoinex.Utils.pad(4, :trailing) - tto_bin(rest, path_acc <> lvlbin) + to_bin(rest, path_acc <> lvlbin) end end From a40c8dd2e8353fd1616715f65894b5732c13272c Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 20 Oct 2022 22:46:02 -0700 Subject: [PATCH 05/63] simplify func signatures --- lib/extendedkey.ex | 64 +++++++++++++++++++-------------------- lib/psbt.ex | 12 ++++---- test/extendedkey_test.exs | 12 ++++---- 3 files changed, 43 insertions(+), 45 deletions(-) diff --git a/lib/extendedkey.ex b/lib/extendedkey.ex index 33f2d75..7964fdc 100644 --- a/lib/extendedkey.ex +++ b/lib/extendedkey.ex @@ -9,7 +9,7 @@ defmodule Bitcoinex.ExtendedKey do defmodule DerivationPath do @moduledoc """ - Contains a list of integers (or the :any atom) representing a bip32 derivation path. + Contains a list of integers (or the :any atom) representing a bip32 derivation path. The :any atom represents a wildcard in the derivation path. DerivationPath structs can be used by ExtendedKey.derive_extended_key to derive a child key at the given path. """ @@ -42,12 +42,11 @@ defmodule Bitcoinex.ExtendedKey do def new(), do: %__MODULE__{child_nums: []} - @spec serialize(t(), atom) :: {:ok, String.t()} | {:ok, binary} | {:error, String.t()} - def serialize(dp = %__MODULE__{}, :to_string), do: path_to_string(dp) - def serialize(dp = %__MODULE__{}, :to_bin), do: to_bin(dp) + @spec serialize(t()) :: {:ok, binary} | {:error, String.t()} + def serialize(dp = %__MODULE__{}), do: to_bin(dp) - @spec path_to_string(t()) :: {:ok, String.t()} | {:error, String.t()} - def path_to_string(%__MODULE__{child_nums: path}), do: path_to_string(path, "") + @spec to_string(t()) :: {:ok, String.t()} | {:error, String.t()} + def to_string(%__MODULE__{child_nums: path}), do: path_to_string(path, "") defp path_to_string([], path_acc), do: {:ok, path_acc} @@ -116,19 +115,18 @@ defmodule Bitcoinex.ExtendedKey do end end - @spec parse(binary, atom) :: {:ok, t()} | {:error, String.t()} - def parse(dp, :from_bin), do: from_bin(dp) - def parse(dp, :from_string), do: path_from_string(dp) + @spec parse(binary) :: {:ok, t()} | {:error, String.t()} + def parse(dp), do: from_bin(dp) - @spec path_from_string(String.t()) :: {:ok, t()} | {:error, String.t()} - def path_from_string(pathstr) do + @spec from_string(String.t()) :: {:ok, t()} | {:error, String.t()} + def from_string(pathstr) do try do {:ok, %__MODULE__{ child_nums: pathstr |> String.split("/") - |> tfrom_string([]) + |> path_from_string([]) |> Enum.reverse() }} rescue @@ -136,7 +134,7 @@ defmodule Bitcoinex.ExtendedKey do end end - defp tfrom_string(path_list, child_nums) do + defp path_from_string(path_list, child_nums) do case path_list do [] -> child_nums @@ -150,36 +148,36 @@ defmodule Bitcoinex.ExtendedKey do message: "m can only be present at the begining of a derivation path." ) else - tfrom_string(rest, child_nums) + path_from_string(rest, child_nums) end ["*" | rest] -> - tfrom_string(rest, [:any | child_nums]) + path_from_string(rest, [:any | child_nums]) ["*'" | rest] -> - tfrom_string(rest, [:anyh | child_nums]) + path_from_string(rest, [:anyh | child_nums]) ["*h" | rest] -> - tfrom_string(rest, [:anyh | child_nums]) + path_from_string(rest, [:anyh | child_nums]) [i | rest] -> - tfrom_string(rest, [str_to_level(i) | child_nums]) + 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(tfrom_bin(bin, []))}} + {:ok, %__MODULE__{child_nums: Enum.reverse(from_bin(bin, []))}} rescue _e in ArgumentError -> {:error, "invalid binary encoding of derivation path"} end end - defp tfrom_bin(<<>>, child_nums), do: child_nums + defp from_bin(<<>>, child_nums), do: child_nums - defp tfrom_bin(<>, child_nums), - do: tfrom_bin(bin, [level | child_nums]) + defp from_bin(<>, child_nums), + do: from_bin(bin, [level | child_nums]) defp str_to_level(level) do {num, is_hardened} = @@ -287,7 +285,7 @@ defmodule Bitcoinex.ExtendedKey do end @doc """ - network_from_extended_key returns :testnet or :mainnet + network_from_extended_key returns :testnet or :mainnet depending on the network prefix of the key. """ @spec network_from_extended_key(t()) :: atom @@ -327,12 +325,12 @@ defmodule Bitcoinex.ExtendedKey do @spec get_child_num(t()) :: binary def get_child_num(%__MODULE__{child_num: child_num}), do: child_num - # PARSE & SERIALIZE + # PARSE & SERIALIZE @doc """ - parse! calls parse, which 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. + parse! raises ArgumentError on failure. """ @spec parse!(binary) :: t() def parse!(xpub) do @@ -343,7 +341,7 @@ defmodule Bitcoinex.ExtendedKey do end @doc """ - parse takes binary or string representation + parse takes binary or string representation of an extended key and parses it to an extended key object returns {:error, msg} on failure """ @@ -445,7 +443,7 @@ defmodule Bitcoinex.ExtendedKey do end @doc """ - seed_to_master_private_key transforms a bip32 seed + seed_to_master_private_key transforms a bip32 seed into a master extended private key """ @spec seed_to_master_private_key(binary, atom) :: {:ok, t()} | {:error, String.t()} @@ -514,7 +512,7 @@ defmodule Bitcoinex.ExtendedKey do end @doc """ - to_public_key takes an extended key xkey and + to_public_key takes an extended key xkey and returns the public key. """ @spec to_public_key(t()) :: {:ok, Point.t()} | {:error, String.t()} @@ -531,7 +529,7 @@ defmodule Bitcoinex.ExtendedKey do @doc """ derive_child uses a public or private key xkey to - derive the public or private key at index idx. + derive the public or private key at index idx. public key -> public child private key -> private child """ @@ -603,8 +601,8 @@ defmodule Bitcoinex.ExtendedKey do end @doc """ - derive_private_child uses a private key xkey to - derive the private key at index idx + derive_private_child uses a private key xkey to + derive the private key at index idx """ @spec derive_private_child(t(), non_neg_integer()) :: {:ok, t()} | {:error, String.t()} def derive_private_child(_, idx) when idx >>> 32 != 0, do: {:error, "idx must be in 0..2**32-1"} @@ -694,7 +692,7 @@ defmodule Bitcoinex.ExtendedKey do end @doc """ - derive_extended_key uses an extended xkey and a derivation + derive_extended_key uses an extended xkey and a derivation path to derive the extended key at that path """ @spec derive_extended_key(t() | binary, DerivationPath.t()) :: {:ok, t()} | {:error, String.t()} diff --git a/lib/psbt.ex b/lib/psbt.ex index 67f1017..4ada12e 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -159,7 +159,7 @@ defmodule Bitcoinex.PSBT.Global do <> = value - {:ok, indexes} = DerivationPath.parse(paths, :from_bin) + {:ok, indexes} = DerivationPath.parse(paths) {:ok, xpub} = ExtendedKey.parse(xpub) if :binary.decode_unsigned(xpub.depth) != DerivationPath.depth(indexes), @@ -215,7 +215,7 @@ defmodule Bitcoinex.PSBT.Global do key = <<@psbt_global_xpub::big-size(8)>> key_data = ExtendedKey.serialize(value.xpub, :no_checksum) - {:ok, deriv_bin} = DerivationPath.serialize(value.derivation, :to_bin) + {:ok, deriv_bin} = DerivationPath.serialize(value.derivation) val = <> <> deriv_bin @@ -337,7 +337,7 @@ defmodule Bitcoinex.PSBT.In do defp serialize_kv(:bip32_derivation, value) when value != nil do key_data = Base.decode16!(value.public_key, case: :lower) - {:ok, deriv_bin} = DerivationPath.serialize(value.derivation, :to_bin) + {:ok, deriv_bin} = DerivationPath.serialize(value.derivation) val = <> <> deriv_bin @@ -471,7 +471,7 @@ defmodule Bitcoinex.PSBT.In do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) <> = value - {:ok, indexes} = DerivationPath.parse(paths, :from_bin) + {:ok, indexes} = DerivationPath.parse(paths) bip32_derivation = case input.bip32_derivation do @@ -569,7 +569,7 @@ defmodule Bitcoinex.PSBT.Out do defp serialize_kv(:bip32_derivation, value) when value != nil do key_data = Base.decode16!(value.public_key, case: :lower) - {:ok, deriv_bin} = DerivationPath.serialize(value.derivation, :to_bin) + {:ok, deriv_bin} = DerivationPath.serialize(value.derivation) val = <> <> deriv_bin @@ -659,7 +659,7 @@ defmodule Bitcoinex.PSBT.Out do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) <> = value - {:ok, indexes} = DerivationPath.parse(paths, :from_bin) + {:ok, indexes} = DerivationPath.parse(paths) bip32_derivation = case output.bip32_derivation do diff --git a/test/extendedkey_test.exs b/test/extendedkey_test.exs index c9a87d0..3d7b076 100644 --- a/test/extendedkey_test.exs +++ b/test/extendedkey_test.exs @@ -722,24 +722,24 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do end describe "Derivation Path parse/ser testing" do - test "path_from_string/1" do + test "from_string/1" do for t <- @derivation_paths_to_parse do - if ExtendedKey.DerivationPath.path_from_string(t.str) != {:ok, t.deriv}, + if ExtendedKey.DerivationPath.from_string(t.str) != {:ok, t.deriv}, do: IO.puts(t.str) - assert ExtendedKey.DerivationPath.path_from_string(t.str) == {:ok, t.deriv} + assert ExtendedKey.DerivationPath.from_string(t.str) == {:ok, t.deriv} end end - test "path_to_string/1" do + test "to_string/1" do for t <- @derivation_paths_to_serialize do - assert ExtendedKey.DerivationPath.path_to_string(t.deriv) == {:ok, t.str} + assert ExtendedKey.DerivationPath.to_string(t.deriv) == {:ok, t.str} end end test "Raise exceptions on invalid derivation paths" do for t <- @error_derivation_path_strings do - {res, _} = ExtendedKey.DerivationPath.path_from_string(t) + {res, _} = ExtendedKey.DerivationPath.from_string(t) assert :error == res end end From f479ccbe1dc750fbdd6d0a85651606d19412e0d2 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sat, 22 Oct 2022 00:46:24 -0700 Subject: [PATCH 06/63] first draft adaptor signatures --- lib/secp256k1/schnorr.ex | 147 +++++++++++++++++++++++++++++++++++---- 1 file changed, 135 insertions(+), 12 deletions(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 0d21f4b..fac1ed8 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -41,24 +41,72 @@ defmodule Bitcoinex.Secp256k1.Schnorr do r_point = PrivateKey.to_point(k0) k = Secp256k1.force_even_y(k0) - e = - tagged_hash_challenge(Point.x_bytes(r_point) <> Point.x_bytes(d_point) <> z_bytes) - |> :binary.decode_unsigned() - |> Math.modulo(@n) + e = calculate_e(r_point, d_point, z_bytes) - sig_s = - (k.d + d.d * e) - |> Math.modulo(@n) + sig_s = calculate_s(k, d, e) {:ok, %Signature{r: r_point.x, s: sig_s}} end end end + @spec tweaked_sign(PrivateKey.t(), non_neg_integer, non_neg_integer, Point.t()) :: {:ok, Signature.t(), Point.t()} | {:error, String.t()} + def tweaked_sign(privkey, z, aux, pubtweak) do + case PrivateKey.validate(privkey) do + {:error, msg} -> + {:error, msg} + + {:ok, privkey} -> + z_bytes = Utils.int_to_big(z, 32) + aux_bytes = Utils.int_to_big(aux, 32) + d_point = PrivateKey.to_point(privkey) + d = Secp256k1.force_even_y(privkey) + d_bytes = Utils.int_to_big(d.d, 32) + tagged_aux_hash = tagged_hash_aux(aux_bytes) + t = Utils.xor_bytes(d_bytes, tagged_aux_hash) + + {:ok, k0} = + tagged_hash_nonce(t <> Point.x_bytes(d_point) <> z_bytes) + |> :binary.decode_unsigned() + |> Math.modulo(@n) + |> PrivateKey.new() + + if k0.d == 0 do + {:error, "invalid aux randomness"} + else + r_point = PrivateKey.to_point(k0) + k = Secp256k1.force_even_y(k0) + + tweaked_r_point = Math.add(r_point, pubtweak) + + e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) + + sig_s = calculate_s(k, d, e) + + {:ok, %Signature{r: r_point.x, s: sig_s}, pubtweak} + end + end + end + defp tagged_hash_aux(aux), do: Utils.tagged_hash("BIP0340/aux", aux) defp tagged_hash_nonce(nonce), do: Utils.tagged_hash("BIP0340/nonce", nonce) defp tagged_hash_challenge(chal), do: Utils.tagged_hash("BIP0340/challenge", chal) + defp calculate_s(k, d, e) do + (k.d + d.d * e) + |> Math.modulo(@n) + end + + defp calculate_e(nonce_bytes, pubkey_bytes, msg_bytes) do + tagged_hash_challenge(nonce_bytes <> pubkey_bytes <> msg_bytes) + |> :binary.decode_unsigned() + |> Math.modulo(@n) + end + + def validate_r(r_point, rx) do + !Point.is_inf(r_point) && Point.has_even_y(r_point) && r_point.x == rx + end + @doc """ verify whether the schnorr signature is valid for the given message hash and public key """ @@ -70,16 +118,91 @@ defmodule Bitcoinex.Secp256k1.Schnorr do r_bytes = Utils.int_to_big(r, 32) z_bytes = Utils.int_to_big(z, 32) - e = - tagged_hash_challenge(r_bytes <> Point.x_bytes(pubkey) <> z_bytes) - |> :binary.decode_unsigned() - |> Math.modulo(@n) + e = calculate_e(r_bytes, Point.x_bytes(pubkey), z_bytes) r_point = @generator_point |> Math.multiply(s) |> Math.add(Math.multiply(pubkey, Params.curve().n - e)) - !Point.is_inf(r_point) && Point.has_even_y(r_point) && r_point.x == r + validate_r(r_point, r) + end + + @spec verify_tweaked_signature(Point.t(), non_neg_integer, Signature.t(), Point.t()) :: + boolean | {:error, String.t()} + def verify_tweaked_signature(pubkey, z, %Signature{r: r, s: s}, pubtweak) do + if r >= Params.curve().p || s >= Params.curve().n, do: {:error, "invalid signature"} + + case Point.lift_x(r) do + {:error, err} -> + {:error, err} + + {:ok, given_r_point} -> + tweaked_point = Math.add(given_r_point, pubtweak) + + z_bytes = Utils.int_to_big(z, 32) + + e = calculate_e(Point.x_bytes(tweaked_point), Point.x_bytes(pubkey), z_bytes) + + r_point = + @generator_point + |> Math.multiply(s) + |> Math.add(Math.multiply(pubkey, Params.curve().n - e)) + + validate_r(r_point, r) + end + end + + + @spec extract_tweak(Point.t(), non_neg_integer, Signature.t(), Signature.t()) :: {:ok, non_neg_integer} | {:error, String.t()} + def extract_tweak(pubkey, z, %Signature{s: s}, %Signature{r: tweaked_r, s: tweaked_s}) do + tweaked_r_bytes = Utils.int_to_big(tweaked_r, 32) + + z_bytes = Utils.int_to_big(z, 32) + e = calculate_e(tweaked_r_bytes, Point.x_bytes(pubkey), z_bytes) + + tweaked_r_point = + @generator_point + |> Math.multiply(tweaked_s) + |> Math.add(Math.multiply(pubkey, Params.curve().n - e)) + + if validate_r(tweaked_r_point, tweaked_r) do + if tweaked_s < s do + {:error, "invalid tweak"} + else + {:ok, tweaked_s - s} + end + else + {:error, "failed to extract tweak due to invalid signature"} + end + end + + @spec extract_tweaked_signature(Point.t(), non_neg_integer, Signature.t(), non_neg_integer) :: {:ok, Signature.t()} | {:error, String.t()} + def extract_tweaked_signature(pubkey, z, %Signature{r: r, s: s}, tweak) do + case Point.lift_x(r) do + {:error, err} -> + {:error, err} + + {:ok, r_point} -> + tweak_point = PrivateKey.to_point(tweak) + + tweaked_r = Math.add(r_point, tweak_point).x + tweaked_s = tweak+s + + r_bytes = Utils.int_to_big(r, 32) + z_bytes = Utils.int_to_big(z, 32) + e = calculate_e(r_bytes, Point.x_bytes(pubkey), z_bytes) + + tweaked_r_point = + @generator_point + |> Math.multiply(tweaked_s) + |> Math.add(Math.multiply(pubkey, Params.curve().n - e)) + + if validate_r(tweaked_r_point, tweaked_r) do + {:ok, %Signature{r: tweaked_r, s: tweaked_s}} + else + {:error, "tweak does not produce valid signature"} + end + end end end From 2ccff2e66c93d5454bafd39fd6834f3611482f61 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sat, 22 Oct 2022 00:48:07 -0700 Subject: [PATCH 07/63] fmt --- lib/secp256k1/privatekey.ex | 1 + lib/secp256k1/schnorr.ex | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/secp256k1/privatekey.ex b/lib/secp256k1/privatekey.ex index 77b0a2a..734feb7 100644 --- a/lib/secp256k1/privatekey.ex +++ b/lib/secp256k1/privatekey.ex @@ -54,6 +54,7 @@ defmodule Bitcoinex.Secp256k1.PrivateKey do case new(d) do {:ok, sk} -> to_point(sk) + {:error, msg} -> {:error, msg} end diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index fac1ed8..021dade 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -50,7 +50,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do end end - @spec tweaked_sign(PrivateKey.t(), non_neg_integer, non_neg_integer, Point.t()) :: {:ok, Signature.t(), Point.t()} | {:error, String.t()} + @spec tweaked_sign(PrivateKey.t(), non_neg_integer, non_neg_integer, Point.t()) :: + {:ok, Signature.t(), Point.t()} | {:error, String.t()} def tweaked_sign(privkey, z, aux, pubtweak) do case PrivateKey.validate(privkey) do {:error, msg} -> @@ -153,8 +154,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do end end - - @spec extract_tweak(Point.t(), non_neg_integer, Signature.t(), Signature.t()) :: {:ok, non_neg_integer} | {:error, String.t()} + @spec extract_tweak(Point.t(), non_neg_integer, Signature.t(), Signature.t()) :: + {:ok, non_neg_integer} | {:error, String.t()} def extract_tweak(pubkey, z, %Signature{s: s}, %Signature{r: tweaked_r, s: tweaked_s}) do tweaked_r_bytes = Utils.int_to_big(tweaked_r, 32) @@ -177,7 +178,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do end end - @spec extract_tweaked_signature(Point.t(), non_neg_integer, Signature.t(), non_neg_integer) :: {:ok, Signature.t()} | {:error, String.t()} + @spec extract_tweaked_signature(Point.t(), non_neg_integer, Signature.t(), non_neg_integer) :: + {:ok, Signature.t()} | {:error, String.t()} def extract_tweaked_signature(pubkey, z, %Signature{r: r, s: s}, tweak) do case Point.lift_x(r) do {:error, err} -> @@ -187,7 +189,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do tweak_point = PrivateKey.to_point(tweak) tweaked_r = Math.add(r_point, tweak_point).x - tweaked_s = tweak+s + tweaked_s = tweak + s r_bytes = Utils.int_to_big(r, 32) z_bytes = Utils.int_to_big(z, 32) From 40372d54aefd47962ab3f0848f9b596928a12d5d Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sat, 22 Oct 2022 01:03:36 -0700 Subject: [PATCH 08/63] fix typo --- lib/secp256k1/schnorr.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 021dade..811c488 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -41,7 +41,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do r_point = PrivateKey.to_point(k0) k = Secp256k1.force_even_y(k0) - e = calculate_e(r_point, d_point, z_bytes) + e = calculate_e(Point.x_bytes(r_point), Point.x_bytes(d_point), z_bytes) sig_s = calculate_s(k, d, e) From 4baa1f227e84da5191da923776bbdfc3e6272959 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sat, 22 Oct 2022 02:48:51 -0700 Subject: [PATCH 09/63] simplify code & add extract_sig from adaptor --- lib/secp256k1/schnorr.ex | 90 +++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 811c488..907697d 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -50,9 +50,9 @@ defmodule Bitcoinex.Secp256k1.Schnorr do end end - @spec tweaked_sign(PrivateKey.t(), non_neg_integer, non_neg_integer, Point.t()) :: + @spec sign_for_tweak(PrivateKey.t(), non_neg_integer, non_neg_integer, Point.t()) :: {:ok, Signature.t(), Point.t()} | {:error, String.t()} - def tweaked_sign(privkey, z, aux, pubtweak) do + def sign_for_tweak(privkey, z, aux, pubtweak) do case PrivateKey.validate(privkey) do {:error, msg} -> {:error, msg} @@ -75,8 +75,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do if k0.d == 0 do {:error, "invalid aux randomness"} else - r_point = PrivateKey.to_point(k0) k = Secp256k1.force_even_y(k0) + r_point = PrivateKey.to_point(k) tweaked_r_point = Math.add(r_point, pubtweak) @@ -93,6 +93,12 @@ defmodule Bitcoinex.Secp256k1.Schnorr do defp tagged_hash_nonce(nonce), do: Utils.tagged_hash("BIP0340/nonce", nonce) defp tagged_hash_challenge(chal), do: Utils.tagged_hash("BIP0340/challenge", chal) + defp calculate_r(pubkey, s, e) do + @generator_point + |> Math.multiply(s) + |> Math.add(Math.multiply(pubkey, Params.curve().n - e)) + end + defp calculate_s(k, d, e) do (k.d + d.d * e) |> Math.modulo(@n) @@ -104,7 +110,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do |> Math.modulo(@n) end - def validate_r(r_point, rx) do + defp validate_r(r_point, rx) do !Point.is_inf(r_point) && Point.has_even_y(r_point) && r_point.x == rx end @@ -118,20 +124,16 @@ defmodule Bitcoinex.Secp256k1.Schnorr do r_bytes = Utils.int_to_big(r, 32) z_bytes = Utils.int_to_big(z, 32) - e = calculate_e(r_bytes, Point.x_bytes(pubkey), z_bytes) - r_point = - @generator_point - |> Math.multiply(s) - |> Math.add(Math.multiply(pubkey, Params.curve().n - e)) + r_point = calculate_r(pubkey, s, e) validate_r(r_point, r) end - @spec verify_tweaked_signature(Point.t(), non_neg_integer, Signature.t(), Point.t()) :: + @spec verify_untweaked_signature(Point.t(), non_neg_integer, Signature.t(), Point.t()) :: boolean | {:error, String.t()} - def verify_tweaked_signature(pubkey, z, %Signature{r: r, s: s}, pubtweak) do + def verify_untweaked_signature(pubkey, z, %Signature{r: r, s: s}, pubtweak) do if r >= Params.curve().p || s >= Params.curve().n, do: {:error, "invalid signature"} case Point.lift_x(r) do @@ -140,34 +142,33 @@ defmodule Bitcoinex.Secp256k1.Schnorr do {:ok, given_r_point} -> tweaked_point = Math.add(given_r_point, pubtweak) - z_bytes = Utils.int_to_big(z, 32) - e = calculate_e(Point.x_bytes(tweaked_point), Point.x_bytes(pubkey), z_bytes) - r_point = - @generator_point - |> Math.multiply(s) - |> Math.add(Math.multiply(pubkey, Params.curve().n - e)) + r_point = calculate_r(pubkey, s, e) validate_r(r_point, r) end end - @spec extract_tweak(Point.t(), non_neg_integer, Signature.t(), Signature.t()) :: - {:ok, non_neg_integer} | {:error, String.t()} - def extract_tweak(pubkey, z, %Signature{s: s}, %Signature{r: tweaked_r, s: tweaked_s}) do - tweaked_r_bytes = Utils.int_to_big(tweaked_r, 32) + @spec tweak_signature(Signature.t(), non_neg_integer | PrivateKey.t()) :: Signature.t() + def tweak_signature(sig, t = %PrivateKey{}), do: tweak_signature(sig, t.d) - z_bytes = Utils.int_to_big(z, 32) - e = calculate_e(tweaked_r_bytes, Point.x_bytes(pubkey), z_bytes) - - tweaked_r_point = - @generator_point - |> Math.multiply(tweaked_s) - |> Math.add(Math.multiply(pubkey, Params.curve().n - e)) + def tweak_signature(%Signature{r: r, s: s}, tweak) do + {:ok, t} = PrivateKey.new(tweak) + t_point = PrivateKey.to_point(t) + {:ok, r_point} = Point.lift_x(r) + %Signature{r: Math.add(r_point, t_point).x, s: tweak + s} + end - if validate_r(tweaked_r_point, tweaked_r) do + @doc """ + extract_tweak takes a signer pubkey, message hash z, untweaked signature (adaptor signature), + and a completed signature, verifies the signature, and then returns the revealed tweak secret + """ + @spec extract_tweak(Point.t(), non_neg_integer, Signature.t(), Signature.t()) :: + {:ok, non_neg_integer} | {:error, String.t()} + def extract_tweak(pubkey, z, %Signature{s: s}, tweaked_sig = %Signature{s: tweaked_s}) do + if verify_signature(pubkey, z, tweaked_sig) do if tweaked_s < s do {:error, "invalid tweak"} else @@ -178,30 +179,35 @@ defmodule Bitcoinex.Secp256k1.Schnorr do end end - @spec extract_tweaked_signature(Point.t(), non_neg_integer, Signature.t(), non_neg_integer) :: + @doc """ + extract_tweaked_signature takes a signer pubkey, message hash z, untweaked signature (adaptor signature), + and a tweak secret, and uses it to verify the adaptor signature, and returns the complete signature + """ + @spec extract_tweaked_signature( + Point.t(), + non_neg_integer, + Signature.t(), + non_neg_integer | PrivateKey.t() + ) :: {:ok, Signature.t()} | {:error, String.t()} + def extract_tweaked_signature(pubkey, z, sig, t = %PrivateKey{}), + do: extract_tweaked_signature(pubkey, z, sig, t.d) + def extract_tweaked_signature(pubkey, z, %Signature{r: r, s: s}, tweak) do case Point.lift_x(r) do {:error, err} -> {:error, err} {:ok, r_point} -> - tweak_point = PrivateKey.to_point(tweak) - - tweaked_r = Math.add(r_point, tweak_point).x tweaked_s = tweak + s - r_bytes = Utils.int_to_big(r, 32) - z_bytes = Utils.int_to_big(z, 32) - e = calculate_e(r_bytes, Point.x_bytes(pubkey), z_bytes) + tweak_point = PrivateKey.to_point(tweak) + tweaked_r_point = Math.add(r_point, tweak_point) - tweaked_r_point = - @generator_point - |> Math.multiply(tweaked_s) - |> Math.add(Math.multiply(pubkey, Params.curve().n - e)) + tweaked_sig = %Signature{r: tweaked_r_point.x, s: tweaked_s} - if validate_r(tweaked_r_point, tweaked_r) do - {:ok, %Signature{r: tweaked_r, s: tweaked_s}} + if verify_signature(pubkey, z, tweaked_sig) do + {:ok, %Signature{r: tweaked_r_point.x, s: tweaked_s}} else {:error, "tweak does not produce valid signature"} end From a04f43e583661dc262fbebba8bf91a4a353c974e Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sat, 22 Oct 2022 02:53:19 -0700 Subject: [PATCH 10/63] make code consistent across sign & adaptor sign --- lib/secp256k1/schnorr.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 907697d..868d711 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -38,8 +38,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do if k0.d == 0 do {:error, "invalid aux randomness"} else - r_point = PrivateKey.to_point(k0) k = Secp256k1.force_even_y(k0) + r_point = PrivateKey.to_point(k) e = calculate_e(Point.x_bytes(r_point), Point.x_bytes(d_point), z_bytes) From 47d2a012d799b89d34de241ac79bb0143ae710d2 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 27 Oct 2022 14:11:18 -0700 Subject: [PATCH 11/63] temp: try to fix R+T not even bug --- lib/secp256k1/point.ex | 2 +- lib/secp256k1/privatekey.ex | 5 + lib/secp256k1/schnorr.ex | 101 +++++++++++++------- lib/secp256k1/secp256k1.ex | 9 +- scripts/gen_test_vectors.exs | 58 ++++++++++++ scripts/schnorr_adaptor.exs | 40 ++++++++ scripts/test_broken_adaptor.exs | 30 ++++++ test/secp256k1/adaptor_test.exs | 151 ++++++++++++++++++++++++++++++ test/secp256k1/schnorr_test.exs | 8 +- test/secp256k1/secp256k1_test.exs | 17 +++- 10 files changed, 383 insertions(+), 38 deletions(-) create mode 100644 scripts/gen_test_vectors.exs create mode 100644 scripts/schnorr_adaptor.exs create mode 100644 scripts/test_broken_adaptor.exs create mode 100644 test/secp256k1/adaptor_test.exs diff --git a/lib/secp256k1/point.ex b/lib/secp256k1/point.ex index 2de8ddc..1bb6b7a 100644 --- a/lib/secp256k1/point.ex +++ b/lib/secp256k1/point.ex @@ -124,7 +124,7 @@ defmodule Bitcoinex.Secp256k1.Point do """ @spec x_bytes(t()) :: binary def x_bytes(%__MODULE__{x: x}) do - Bitcoinex.Utils.pad(:binary.encode_unsigned(x), 32, :leading) + Utils.int_to_big(x, 32) end @doc """ diff --git a/lib/secp256k1/privatekey.ex b/lib/secp256k1/privatekey.ex index 734feb7..1f01bf4 100644 --- a/lib/secp256k1/privatekey.ex +++ b/lib/secp256k1/privatekey.ex @@ -60,6 +60,11 @@ defmodule Bitcoinex.Secp256k1.PrivateKey do end end + @spec negate(t()) :: t() + def negate(%__MODULE__{d: d}) do + %__MODULE__{d: @n - d} + end + @doc """ serialize_private_key serializes a private key into hex """ diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 868d711..513d561 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -7,6 +7,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do alias Bitcoinex.Utils @n Params.curve().n + @p Params.curve().p @generator_point %Point{ x: Params.curve().g_x, @@ -29,30 +30,18 @@ defmodule Bitcoinex.Secp256k1.Schnorr do tagged_aux_hash = tagged_hash_aux(aux_bytes) t = Utils.xor_bytes(d_bytes, tagged_aux_hash) - {:ok, k0} = - tagged_hash_nonce(t <> Point.x_bytes(d_point) <> z_bytes) - |> :binary.decode_unsigned() - |> Math.modulo(@n) - |> PrivateKey.new() + {:ok, k} = calculate_k(t, d_point, z_bytes) + r_point = PrivateKey.to_point(k) + e = calculate_e(Point.x_bytes(r_point), Point.x_bytes(d_point), z_bytes) + sig_s = calculate_s(k, d, e) - if k0.d == 0 do - {:error, "invalid aux randomness"} - else - k = Secp256k1.force_even_y(k0) - r_point = PrivateKey.to_point(k) - - e = calculate_e(Point.x_bytes(r_point), Point.x_bytes(d_point), z_bytes) - - sig_s = calculate_s(k, d, e) - - {:ok, %Signature{r: r_point.x, s: sig_s}} - end + {:ok, %Signature{r: r_point.x, s: sig_s}} end end @spec sign_for_tweak(PrivateKey.t(), non_neg_integer, non_neg_integer, Point.t()) :: {:ok, Signature.t(), Point.t()} | {:error, String.t()} - def sign_for_tweak(privkey, z, aux, pubtweak) do + def sign_for_tweak(privkey, z, aux, tweak_point) do case PrivateKey.validate(privkey) do {:error, msg} -> {:error, msg} @@ -75,16 +64,28 @@ defmodule Bitcoinex.Secp256k1.Schnorr do if k0.d == 0 do {:error, "invalid aux randomness"} else - k = Secp256k1.force_even_y(k0) + r_point = PrivateKey.to_point(k0) + tweaked_r_point = Math.add(r_point, tweak_point) + IO.puts("!---\n") + IO.puts("k0: " <> to_string(k0.d)) + IO.puts("x: " <> to_string(tweaked_r_point.x) <> "\ny: " <> to_string(tweaked_r_point.y) <> "\n") + # we must ensure that R+T, the final signature's nonce point has even y + k = get_k_for_even_tweaked_nonce(k0, tweaked_r_point) + IO.puts("k1: " <> to_string(k.d)) r_point = PrivateKey.to_point(k) + tweaked_r_point = Math.add(r_point, tweak_point) + IO.puts("x: " <> to_string(tweaked_r_point.x) <> "\ny: " <> to_string(tweaked_r_point.y) <> "\n") + IO.puts("---!\n") + if Point.has_even_y(tweaked_r_point) do + sameK = to_string(k == k0) + {:error, "what is going on: sameK: #{sameK}"} + else + e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) + sig_s = calculate_s(k, d, e) + + {:ok, %Signature{r: r_point.x, s: sig_s}, tweak_point} + end - tweaked_r_point = Math.add(r_point, pubtweak) - - e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) - - sig_s = calculate_s(k, d, e) - - {:ok, %Signature{r: r_point.x, s: sig_s}, pubtweak} end end end @@ -93,6 +94,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do defp tagged_hash_nonce(nonce), do: Utils.tagged_hash("BIP0340/nonce", nonce) defp tagged_hash_challenge(chal), do: Utils.tagged_hash("BIP0340/challenge", chal) + defp calculate_r(pubkey, s, e) do @generator_point |> Math.multiply(s) @@ -104,6 +106,30 @@ defmodule Bitcoinex.Secp256k1.Schnorr do |> Math.modulo(@n) end + defp get_k_for_even_tweaked_nonce(k, tweaked_point) do + if Point.has_even_y(tweaked_point) do + IO.puts("k unchanged\n") + k + else + IO.puts("k changed\n") + PrivateKey.negate(k) + end + end + + defp calculate_k(t, d_point, z_bytes) do + {:ok, k0} = + tagged_hash_nonce(t <> Point.x_bytes(d_point) <> z_bytes) + |> :binary.decode_unsigned() + |> Math.modulo(@n) + |> PrivateKey.new() + + if k0.d == 0 do + {:error, "invalid aux randomness"} + else + {:ok, Secp256k1.force_even_y(k0)} + end + end + defp calculate_e(nonce_bytes, pubkey_bytes, msg_bytes) do tagged_hash_challenge(nonce_bytes <> pubkey_bytes <> msg_bytes) |> :binary.decode_unsigned() @@ -111,7 +137,15 @@ defmodule Bitcoinex.Secp256k1.Schnorr do end defp validate_r(r_point, rx) do - !Point.is_inf(r_point) && Point.has_even_y(r_point) && r_point.x == rx + cond do + Point.is_inf(r_point) -> + {:error, "R point is infinite"} + !Point.has_even_y(r_point) -> + {:error, "R point is not even"} + r_point.x != rx -> + {:error, "x's do not match #{r_point.x} vs #{rx}"} + true -> true + end end @doc """ @@ -120,7 +154,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do @spec verify_signature(Point.t(), non_neg_integer, Signature.t()) :: boolean | {:error, String.t()} def verify_signature(pubkey, z, %Signature{r: r, s: s}) do - if r >= Params.curve().p || s >= Params.curve().n, do: {:error, "invalid signature"} + if r >= @p || s >= @n, do: {:error, "invalid signature"} r_bytes = Utils.int_to_big(r, 32) z_bytes = Utils.int_to_big(z, 32) @@ -133,15 +167,14 @@ defmodule Bitcoinex.Secp256k1.Schnorr do @spec verify_untweaked_signature(Point.t(), non_neg_integer, Signature.t(), Point.t()) :: boolean | {:error, String.t()} - def verify_untweaked_signature(pubkey, z, %Signature{r: r, s: s}, pubtweak) do - if r >= Params.curve().p || s >= Params.curve().n, do: {:error, "invalid signature"} - + def verify_untweaked_signature(pubkey, z, %Signature{r: r, s: s}, tweak_point) do + if r >= @p || s >= @n, do: {:error, "invalid signature"} case Point.lift_x(r) do {:error, err} -> {:error, err} {:ok, given_r_point} -> - tweaked_point = Math.add(given_r_point, pubtweak) + tweaked_point = Math.add(given_r_point, tweak_point) z_bytes = Utils.int_to_big(z, 32) e = calculate_e(Point.x_bytes(tweaked_point), Point.x_bytes(pubkey), z_bytes) @@ -153,12 +186,12 @@ defmodule Bitcoinex.Secp256k1.Schnorr do @spec tweak_signature(Signature.t(), non_neg_integer | PrivateKey.t()) :: Signature.t() def tweak_signature(sig, t = %PrivateKey{}), do: tweak_signature(sig, t.d) - def tweak_signature(%Signature{r: r, s: s}, tweak) do {:ok, t} = PrivateKey.new(tweak) t_point = PrivateKey.to_point(t) {:ok, r_point} = Point.lift_x(r) - %Signature{r: Math.add(r_point, t_point).x, s: tweak + s} + tw_s = Math.modulo(tweak+s, @n) + %Signature{r: Math.add(r_point, t_point).x, s: tw_s} end @doc """ diff --git a/lib/secp256k1/secp256k1.ex b/lib/secp256k1/secp256k1.ex index e99cbc2..641b01f 100644 --- a/lib/secp256k1/secp256k1.ex +++ b/lib/secp256k1/secp256k1.ex @@ -113,7 +113,14 @@ defmodule Bitcoinex.Secp256k1 do @spec serialize_signature(t()) :: binary def serialize_signature(%__MODULE__{r: r, s: s}) do - :binary.encode_unsigned(r) <> :binary.encode_unsigned(s) + Utils.int_to_big(r, 32) <> Utils.int_to_big(s, 32) + end + + @spec to_hex(t()) :: binary + def to_hex(sig) do + sig + |> serialize_signature() + |> Base.encode16(case: :lower) end @doc """ diff --git a/scripts/gen_test_vectors.exs b/scripts/gen_test_vectors.exs new file mode 100644 index 0000000..43ed9ca --- /dev/null +++ b/scripts/gen_test_vectors.exs @@ -0,0 +1,58 @@ +alias Bitcoinex.Secp256k1 +alias Bitcoinex.Secp256k1.{Math, PrivateKey, Point, Schnorr, Signature} +alias Bitcoinex.Utils + +to_hex = fn i -> "0x" <> Integer.to_string(i, 16) end + +write_row = fn file, sk, pk, tw, t_point, z, aux, ut_sig, tw_sig, err, is_tweaked_s_even, is_tweaked_s_ooo -> IO.binwrite(file, +to_hex.(sk.d) <> "," <> Point.x_hex(pk) <> "," <> to_hex.(tw.d) <> "," +<> Point.x_hex(t_point) <> "," <> to_hex.(z) <> "," <> to_hex.(aux) <> "," +<> Signature.to_hex(ut_sig) <> "," <> Signature.to_hex(tw_sig) <> "," +<> err <> "," <> to_string(is_tweaked_s_even) <> "," <> to_string(is_tweaked_s_ooo) <> "\n") +end + +order_n = 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_BAAEDCE6_AF48A03B_BFD25E8C_D0364141 + +{:ok, good_file} = File.open("schnorr_adaptor_test_vectors-good.csv", [:write]) +{:ok, bad_file} = File.open("schnorr_adaptor_test_vectors-bad.csv", [:write]) + +IO.binwrite(good_file, "private_key,public_key,tweak_secret,tweak_point,message_hash,aux_rand,untweaked_adaptor_signature,tweaked_signature,is_tweaked_s_even\n") +IO.binwrite(bad_file, "private_key,public_key,tweak_secret,tweak_point,message_hash,aux_rand,untweaked_adaptor_signature,tweaked_signature,is_tweaked_s_even\n") + +for _ <- 1..50 do + ski = :rand.uniform(order_n-1) + {:ok, sk0} = PrivateKey.new(ski) + sk = Secp256k1.force_even_y(sk0) + pk = PrivateKey.to_point(sk) + + # tweak + ti = :rand.uniform(order_n-1) + {:ok, tw} = PrivateKey.new(ti) + tw = Secp256k1.force_even_y(tw) + tw_point = PrivateKey.to_point(tw) + + msg = + :rand.uniform(order_n-1) + |> :binary.encode_unsigned() + z = Utils.double_sha256(msg) |> :binary.decode_unsigned() + + aux = :rand.uniform(order_n-1) + + # create adaptor sig + {:ok, ut_sig, _tw_point} = Schnorr.sign_for_tweak(sk, z, aux, tw_point) + tw_sig = Schnorr.tweak_signature(ut_sig, tw.d) + + # checks + tweaked_s = tw.d+ut_sig.s + is_tweaked_s_ooo = tweaked_s > order_n + {:ok, tweaked_s} = PrivateKey.new(Math.modulo(tweaked_s, order_n)) + tweaked_forced_s = Secp256k1.force_even_y(tweaked_s) + is_tweaked_s_even = tweaked_forced_s == tweaked_s + + case Schnorr.verify_signature(pk, z, tw_sig) do + true -> + write_row.(good_file, sk, pk, tw, tw_point, z, aux, ut_sig, tw_sig, "", is_tweaked_s_even, is_tweaked_s_ooo) + {:error, err} -> + write_row.(bad_file, sk, pk, tw, tw_point, z, aux, ut_sig, tw_sig, err, is_tweaked_s_even, is_tweaked_s_ooo) + end +end diff --git a/scripts/schnorr_adaptor.exs b/scripts/schnorr_adaptor.exs new file mode 100644 index 0000000..932deb5 --- /dev/null +++ b/scripts/schnorr_adaptor.exs @@ -0,0 +1,40 @@ +alias Bitcoinex.Secp256k1 +alias Bitcoinex.Secp256k1.{PrivateKey, Point, Schnorr, Signature} +alias Bitcoinex.Utils +# private key +{:ok, sk} = PrivateKey.new(1234123412341234123412341234) +pk = PrivateKey.to_point(sk) + +# tweak +{:ok, t} = PrivateKey.new(658393766392737484910002828395) +t = Secp256k1.force_even_y(t) +t_point = PrivateKey.to_point(t) + +msg = "tweakin" +z = Utils.double_sha256(msg) |> :binary.decode_unsigned() + +aux = 1203948712823749283 + +# create adaptor sig +{:ok, ut_sig, t_point_} = Schnorr.sign_for_tweak(sk, z, aux, t_point) +t_point_ == t_point + +# adaptor sig is not a valid schnorr sig +!Schnorr.verify_signature(pk, z, ut_sig) + +# verify adaptor signature +Schnorr.verify_untweaked_signature(pk, z, ut_sig, t_point) + +# complete adaptor sig +tw_sig = Schnorr.tweak_signature(ut_sig, t.d) + +# complete sig must be valid schnorr sig +Schnorr.verify_signature(pk, z, tw_sig) + +# extract tweak +{:ok, tweak} = Schnorr.extract_tweak(pk, z, ut_sig, tw_sig) +tweak == t.d + +# extract signature given tweak +{:ok, sig} = Schnorr.extract_tweaked_signature(pk, z, ut_sig, t.d) +sig == tw_sig diff --git a/scripts/test_broken_adaptor.exs b/scripts/test_broken_adaptor.exs new file mode 100644 index 0000000..c9919b3 --- /dev/null +++ b/scripts/test_broken_adaptor.exs @@ -0,0 +1,30 @@ + +alias Bitcoinex.Secp256k1 +alias Bitcoinex.Secp256k1.{PrivateKey, Schnorr, Signature} + +test = %{ + privkey: 0x279D71D68D3EE997019D005BDF703C271001631A7EE12E4C9DAD10C0754912DC, + pubkey: 0x22c63594ea2c2199e0500cdf6dffecdf878441720789c8dfcfb9af06a96fd1e4, + tweak_secret: 0xF8EBFDF85A3AF0C337ECB165EF47D565DE15CBCEEB597A243C3D54DF49B703D5, + tweak_point: 0x6545e169e4d2e940e63207110a9d44dd5d4ca65aeb58e3e566658f62d41bd23f, + message_hash: 0x5736367EBB12EDC15B0FA75319B46D016F86A0E057B9237240D6185C93596367, + aux_rand: 0x7E4E37835DDFC6A82A011073DCB779D02F1F5B52A2937B6ADD5B9DA2528FC5C6, + untweaked_sig: "e2125e2f6d791ce59b604dfc0578a823008a5c86f2f2efbd0de68a4cb19688d817ebac918f08e0078c66a26c664d9f169d66dc54fdd95972e68a69b79797274a", + tweaked_sig: "320ab814c2e7e2567af8e738ce83e9fdc55ef57933dd52b169bba46fd3516e4c10d7aa89e943d0cac45353d25595747dc0cdcb3d39ea335b62f5600a1117e9de" +} + +z = test.message_hash +aux = test.aux_rand + +{:ok, t} = PrivateKey.new(test.tweak_secret) +t2 = Secp256k1.force_even_y(t) +t_point = PrivateKey.to_point(t) + +{:ok, sk} = PrivateKey.new(test.privkey) +sk2 = Secp256k1.force_even_y(sk) + +# use sk2 +pk = PrivateKey.to_point(sk2) +{:ok, ut_sig, t_point_} = Schnorr.sign_for_tweak(sk2, z, aux, t_point) +tw_sig = Schnorr.tweak_signature(ut_sig, t.d) +Schnorr.verify_signature(pk, z, tw_sig) diff --git a/test/secp256k1/adaptor_test.exs b/test/secp256k1/adaptor_test.exs new file mode 100644 index 0000000..127cc6b --- /dev/null +++ b/test/secp256k1/adaptor_test.exs @@ -0,0 +1,151 @@ +defmodule Bitcoinex.Secp256k1.SchnorrTest do + use ExUnit.Case + + alias Bitcoinex.Secp256k1 + alias Bitcoinex.Secp256k1.{PrivateKey, Schnorr, Signature} + + + @schnorr_adaptor_signatures [ + %{ + privkey: 0x279D71D68D3EE997019D005BDF703C271001631A7EE12E4C9DAD10C0754912DC, + pubkey: 0x22c63594ea2c2199e0500cdf6dffecdf878441720789c8dfcfb9af06a96fd1e4, + tweak_secret: 0xF8EBFDF85A3AF0C337ECB165EF47D565DE15CBCEEB597A243C3D54DF49B703D5, + tweak_point: 0x6545e169e4d2e940e63207110a9d44dd5d4ca65aeb58e3e566658f62d41bd23f, + message_hash: 0x5736367EBB12EDC15B0FA75319B46D016F86A0E057B9237240D6185C93596367, + aux_rand: 0x7E4E37835DDFC6A82A011073DCB779D02F1F5B52A2937B6ADD5B9DA2528FC5C6, + untweaked_sig: "e2125e2f6d791ce59b604dfc0578a823008a5c86f2f2efbd0de68a4cb19688d817ebac918f08e0078c66a26c664d9f169d66dc54fdd95972e68a69b79797274a", + tweaked_sig: "320ab814c2e7e2567af8e738ce83e9fdc55ef57933dd52b169bba46fd3516e4c10d7aa89e943d0cac45353d25595747dc0cdcb3d39ea335b62f5600a1117e9de" + }, + %{ + privkey: 0x6F0B8C685BEB787F3121851AC13DAD9549C235217C5F578D9E7BE3B3845D8773, + pubkey: 0x825fa6ec886022247b7b98d24d47a6f03d3a47f2fa3916a244eba7114ea94fa1, + tweak_secret: 0x76D2C1FE7E575063C8888D935645973AC3AE1FFD1C97F25D14F7567657D7F748, + tweak_point: 0xb99378d7cf782cf6ff265ef8e3ba447b36da89224c0682ca61cc9916a4fec4bf, + message_hash: 0xA193926FAFF8CE363937D8655892E60D45661C5FC510BC711E4B5049A6B4DD30, + aux_rand: 0x76C66B394932DC5704EC0FB4CE4D76D388707396AC64D9C35BFC081F11D51124, + untweaked_sig: "864897cd95c22dda7fce0435fe365c658118e52d578ba77c25d1f1e63ff3cd0916b38c325759c310e467a0ec026d8362b2f5155adb19a17eb06a5c661b039be1", + tweaked_sig: "e277ad505af9e82abcb122c3df410459494af86315988d4596bf0fb71f64bf338d864e30d5b11374acf02e7f58b31a9d76a33557f7b193dbc561b2dc72db9329" + }, + %{ + privkey: 0x624FB68C6C2474358EF57A9810BA5A17855D0FF8676D5D80E93DF314D5C3D277, + pubkey: 0x4af83421073f24e51235c43ffb85aa94dc3139af958a2532c6b412832fbbacc4, + tweak_secret: 0xF1997F60060268C14B81744869AEB04098A93704C3BDA2A640A1B26F74E246F4, + tweak_point: 0xb9dc16baaf262b3a60a1979833f9ca350e2219121fdb43d3fd2045d3142bb71f, + message_hash: 0x97FF15503ECA5167E871E04998F3C90976E8FAFF9AD1AF54F045153BA41E1663, + aux_rand: 0x71C1FE1F3A27B04EEC4142AC1612A7C6419B1740716E2C29CEE319E718950A04, + untweaked_sig: "08365373469000bca3fa28a95469f6331a20d4f03429372573b3c2d3d3d5b0f6801f053ece6e34cdf9fa8f43ec2ed3b730c388c0e444dd45f9cc1f920adac25f", + tweaked_sig: "9e3deb23f27730ce1be854b0145cc9bf99e3ee10491d28308160c7b65b0a56e871b8849ed4709d8f457c038c55dd83f90ebde2def8b9dfb07a9b7374af86c812" + }, + %{ + privkey: 0xB5196100AAC42311C9B33B1746CA0B38AC4547936DDC4AFFF547A862A4BF9890, + pubkey: 0x5b279f79dcf2414b66160515de53ac471a043d6d9f62fd007c09ad90704ffd28, + tweak_secret: 0xA69690763B4034FCF0F269E80464643E521475BFE08162632971AE0BDAA7AE8E, + tweak_point: 0xe9f219bb7c0a390a7df96a4f33b7f7d6179a9a4aa216ed1a750ddba1719072f0, + message_hash: 0xAD6F06747576C8EC210113A2C070A7A5688616EE841FA61026D2FF1CF9F7970F, + aux_rand: 0x63241AA5C78B99A849025EB3056E84DB011769DC8BBE782FAFA6E89D2BEE0C16, + untweaked_sig: "ef3766f37e9235e9652418bd60c54cd8e9c2db06f7effc542415fc76fac9c5cb75cc36cc9d5c20d303c30e43c2be82708141b8f721f75c7f889ebb24da81c818", + tweaked_sig: "aa9788a104f497f788bf4979a791a04c8408fd2f82075d5810f5f85394d567761c62c742d89c55cff4b5782bc722e6b018a751d053301ea6f23e0aa3e4f33565" + }, + %{ + privkey: 0x45AC71188B8B3B180D806ABB0289AB23357BF7CE866BFDB13509C68BAC5C88C4, + pubkey: 0x8b688d7ce32ab13bed3f0c5f119791f8b76913809c22983c8b6713d6fdc6f5a3, + tweak_secret: 0x6D2B06A08C0F20A64FA06DADF35F992F4DA441A29541268FB5F9AA9B7977A886, + tweak_point: 0x4d01a75b79ad34ce9d1dec59c581881db6a05865807705558a31e5b19047b024, + message_hash: 0x1E3D05CC94304EB6CEF384D5ADF36827A6CA4299D70B821B60240995F75BB6CB, + aux_rand: 0x7EFC5AD98D6AA0542A9534FA8637AB87A1B8E48FBAFD89AC6EA9348B44EEA049, + untweaked_sig: "655244da978cb1fa37690f3fe98c0e78b58390207781dae0d57f74dde06c0c3f6c8463e2c5f1e4dcabdfa050d9f18e3dfdd4bd70b55a024f3beab45fecb6e255", + tweaked_sig: "29abdd275d062b0bef51113fabaf3a70652387e7fa227c54b968b4943a7f4312d9af6a8352010582fb800dfecd51276d4b78ff134a9b28def1e45efb662e8adb" + }, + %{ + privkey: 0x24D807AB16645D504C75AA9B6355444FC291298AABEB4DAC5EB070898AD645B0, + pubkey: 0xf7609d85839d360fdba6a7bcde848807baeb6417d5ed4258c1c8bc4650543c7a, + tweak_secret: 0x4ABE441BF15DBB0378C2763FCA1E967EFA2636C6292A80711DA55AC7810CAA2A, + tweak_point: 0x6690f7703df1a815d8b4391889c47d8d8837369c6f2e00d5eb92abf1b90a206f, + message_hash: 0x9AE6F80954DA7A3D615987FC0CE97CC4E4567F5425A733DBD4C0BD496A3D5622, + aux_rand: 0xA421B1D6C0DA447395A19B0CF5BBB9879DAE8303CFD33C46F001DBE7EFE9853, + untweaked_sig: "96b4d6d0a19e7f42eea9ee4ad8d36a19e039623eeea47a6fb1658df2e20f7e23cbc02c5c59d7961cf88402a346584af229cb795295c5ea108440bd7de5ea01f0", + tweaked_sig: "a57c39c39b18fd5e6f572253e19227252a4a81a4ac3dc1ef804e6b306b142b10167e70784b355120714678e31076e1726942d3320fa7ca45e213b9b896c06ad9" + }, + %{ + privkey: 0x7EDA1030020279FA24CC33B79A665B73A9E715EB1EB2C6DBEBEE21AD7BCF1E2C, + pubkey: 0x65c57e92bd052d623ac708c34a9a73851e37c81cb763b9fe1ff88437ce42245f, + tweak_secret: 0x1DE275F6F723416882A18E0F4CCD398B7AA9C2A701BFD16D448A118BF82D3027, + tweak_point: 0xcbc6ed86f4442c6403d5c0a3b8157489c5cc55d5137bbd4648c58301979b6f6c, + message_hash: 0xA744D50F65508DDE9278AECAD6A2759BE3A83709877B427E4098085DB27B9826, + aux_rand: 0x12F582D5FA0B2F6245985FBC7C7220C55D441D027B64BA2A7289B9A15E5106F7, + untweaked_sig: "454d9f3fe892e1f31638a258b135ddb74beaed682936830b1d233432e7a2a575d9953241a92d0172bf8df6e198dc7a9052638337483e0881ccb96ba38ea973d3", + tweaked_sig: "db076ae6615ccd083a0d8e6d9e78c28cba696f4c4d3fbfb5c5a55b23d4de0d0af777a838a05042db422f84f0e5a9b41bcd0d45de49fdd9ef11437d2f86d6a3fa" + }, + %{ + privkey: 0x95896701D713F8AA383176C2BCD61F8EC55A29C5169AEBE9A8A32AC9803F8E7D, + pubkey: 0xe7d2b5e311e4f7e81d05c5c56a94f0e5438da78b2e512f78b540114dcf931080, + tweak_secret: 0x7038F53CA5C13C1534A595BC74799657805CB864A21AC78618E661E2A27F6DE4, + tweak_point: 0xe76f186a03ef4ca41b55ff18e2de2f713cf9a1f97f23f627681a2a47bea24a95, + message_hash: 0x4928F4C94434C570F12CF548F4B2F4C9124F5E6D1371020D55EC3D508DA49D61, + aux_rand: 0x51DDDD197BF29EE74235C80CECBE06C8E012BFFC0E227E87A966AD9BB6E6A07B, + untweaked_sig: "46d4e627dc2a8d3864f4e6738f88ab891e5e51da7b08251218d11b0f56f047b4d91271d08e0ba2ecde0169ce02307e49bca6cc749e3c6869dad5ace39f09c943", + tweaked_sig: "83ba54b75d4516cb376b51fc8fb9cd560aae652a2ff8cded96cb052ed3b1e12e494b670d33ccdf0212a6ff8a76aa14a28254a7f2910e8fb433e9b0397152f5e6" + }, + %{ + privkey: 0x695563958BE07A20700F6B624C2EBA9F336C47ADF7B8E202388600132D2E7CF3, + pubkey: 0x97826ac90a2e8ebf9c2f94c5f23863aed915b4c4e77701b9ef7b43e9c501d0f3, + tweak_secret: 0x6FDDBC57B407184C1D1C0B9040A14A19EF8824222431F39CB89FED2C9D67CE41, + tweak_point: 0xe772ca746c6f40e607167f6d95414dab603fb36e0a4e473ab69d6e1036c209ea, + message_hash: 0x9A6BBFEF82261C8EFA9CD7F3481728D68554B2E698187797EAF60A734B6307D9, + aux_rand: 0x1ED0360C5241E28038735B43C6705AE2A1A971CDD94FCED6817C005ED2DF8237, + untweaked_sig: "d44e4a61254b90b671a278ee8ae73add71c5cb89b9fda0611cabb3ab46c4bb5d2511324a297a4027f3bef12a79e07a16d1419922e0d6c205066b538efc655dfe", + tweaked_sig: "840a95a2d67b1fe09584a2b9f353277c318d4b21774a777a2dd2814de4e9d22094eeeea1dd81587410dafcbaba81c430c0c9bd450508b5a1bf0b40bb99cd2c3f" + } + ] + + describe "adaptor signature tests" do + test "test adaptor signatures" do + for t <- @schnorr_adaptor_signatures do + {:ok, sk} = PrivateKey.new(t.privkey) + sk = Secp256k1.force_even_y(sk) + pk = PrivateKey.to_point(sk) + # check pubkey is correct + assert t.pubkey == pk.x + + {:ok, tw} = PrivateKey.new(t.tweak_secret) + tw = Secp256k1.force_even_y(tw) + tw_point = PrivateKey.to_point(tw) + # check tweak point is correct + assert t.tweak_point == tw_point.x + + # parse sigs + {:ok, c_ut_sig} = Signature.parse_signature(t.untweaked_sig) + {:ok, c_tw_sig} = Signature.parse_signature(t.tweaked_sig) + + {:ok, ut_sig, t_point} = Schnorr.sign_for_tweak(sk, t.message_hash, t.aux_rand, tw_point) + # check tweak point hasn't changed + assert t_point == tw_point + # check untweaked sig + assert c_ut_sig == ut_sig + # adaptor sig is not a valid schnorr sig, must fail + assert !Schnorr.verify_signature(pk, t.message_hash, ut_sig) + # verify adaptor signature + assert Schnorr.verify_untweaked_signature(pk, t.message_hash, ut_sig, tw_point) + # complete adaptor sig + tw_sig = Schnorr.tweak_signature(ut_sig, tw) + # check sig + assert c_tw_sig == tw_sig + # ensure tweaked sig is same when using tweak private key and integer + tw_sig0 = Schnorr.tweak_signature(ut_sig, tw.d) + assert tw_sig == tw_sig0 + + # complete sig must be valid schnorr sig + assert Schnorr.verify_signature(pk, t.message_hash, tw_sig) + + # extract tweak secret + {:ok, tweak} = Schnorr.extract_tweak(pk, t.message_hash, ut_sig, tw_sig) + assert tweak == tw.d + assert t.tweak_secret == tweak + + # extract signature given tweak + {:ok, sig} = Schnorr.extract_tweaked_signature(pk, t.message_hash, ut_sig, t.tweak_secret) + assert sig == tw_sig + end + end + end +end diff --git a/test/secp256k1/schnorr_test.exs b/test/secp256k1/schnorr_test.exs index d5aa295..522d940 100644 --- a/test/secp256k1/schnorr_test.exs +++ b/test/secp256k1/schnorr_test.exs @@ -5,6 +5,7 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do alias Bitcoinex.Utils alias Bitcoinex.Secp256k1 alias Bitcoinex.Secp256k1.{Point, PrivateKey, Schnorr, Signature} + # alias Bitcoinex.Secp256k1.{PrivateKey} # BIP340 official test vectors: # https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv @@ -153,6 +154,9 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do } ] + + + describe "sign/3" do test "sign" do for t <- @schnorr_signatures_with_secrets do @@ -260,7 +264,7 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do for _ <- 1..1000 do secret = - 32 + 31 |> :crypto.strong_rand_bytes() |> :binary.decode_unsigned() @@ -271,4 +275,6 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do end end end + + end diff --git a/test/secp256k1/secp256k1_test.exs b/test/secp256k1/secp256k1_test.exs index a723d01..d03547d 100644 --- a/test/secp256k1/secp256k1_test.exs +++ b/test/secp256k1/secp256k1_test.exs @@ -3,7 +3,7 @@ defmodule Bitcoinex.Secp256k1.Secp256k1Test do doctest Bitcoinex.Secp256k1 alias Bitcoinex.Secp256k1 - alias Bitcoinex.Secp256k1.{Signature} + alias Bitcoinex.Secp256k1.{Signature, PrivateKey} @valid_der_signatures [ %{ @@ -165,4 +165,19 @@ defmodule Bitcoinex.Secp256k1.Secp256k1Test do end end end + + describe "force_even_y/1" do + test "force_even_y" do + for _ <- 1..1000 do + secret = + 31 + |> :crypto.strong_rand_bytes() + |> :binary.decode_unsigned() + + privkey = Secp256k1.force_even_y(%PrivateKey{d: secret}) + pubkey = PrivateKey.to_point(privkey) + assert rem(pubkey.y,2) == 0 + end + end + end end From 25eef3484e776ca639323783e3eedf415799e81b Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Mon, 16 Jan 2023 19:08:11 -0800 Subject: [PATCH 12/63] make ExtendedKey.serialize elixir-idiomatic --- lib/extendedkey.ex | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/lib/extendedkey.ex b/lib/extendedkey.ex index 7964fdc..2caa3a1 100644 --- a/lib/extendedkey.ex +++ b/lib/extendedkey.ex @@ -414,23 +414,18 @@ defmodule Bitcoinex.ExtendedKey do serialize takes an extended key and returns the binary """ - @spec serialize(t()) :: binary - def serialize(xkey) do - (xkey.prefix <> - xkey.depth <> xkey.parent_fingerprint <> xkey.child_num <> xkey.chaincode <> xkey.key) - |> Base58.append_checksum() - end - - @doc """ - serialize takes an extended key - and returns the binary without the checksum appended - (used for PSBT encoding) - """ - @spec serialize(t(), atom) :: binary - def serialize(xkey = %__MODULE__{}, :no_checksum) do - xkey.prefix <> - xkey.depth <> xkey.parent_fingerprint <> xkey.child_num <> xkey.chaincode <> xkey.key - end + @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 From 05f5edd06dc9ff0416a4ba57f69386fd93e0112a Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Fri, 27 Jan 2023 22:43:31 -0800 Subject: [PATCH 13/63] temp fix adaptors --- .tool-versions | 4 +- lib/secp256k1/schnorr.ex | 94 +++++++++++++++++++++++++--------------- 2 files changed, 60 insertions(+), 38 deletions(-) diff --git a/.tool-versions b/.tool-versions index 7ffb442..440d747 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -elixir 1.10.4-otp-22 -erlang 22.3.4.1 +elixir 1.14.2-otp-25 +erlang 25.1.2 diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 513d561..5dfa7d2 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -40,7 +40,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do end @spec sign_for_tweak(PrivateKey.t(), non_neg_integer, non_neg_integer, Point.t()) :: - {:ok, Signature.t(), Point.t()} | {:error, String.t()} + {:ok, Signature.t(), Point.t(), bool} | {:error, String.t()} def sign_for_tweak(privkey, z, aux, tweak_point) do case PrivateKey.validate(privkey) do {:error, msg} -> @@ -63,29 +63,19 @@ defmodule Bitcoinex.Secp256k1.Schnorr do if k0.d == 0 do {:error, "invalid aux randomness"} - else + + r_point = PrivateKey.to_point(k0) tweaked_r_point = Math.add(r_point, tweak_point) - IO.puts("!---\n") - IO.puts("k0: " <> to_string(k0.d)) - IO.puts("x: " <> to_string(tweaked_r_point.x) <> "\ny: " <> to_string(tweaked_r_point.y) <> "\n") # we must ensure that R+T, the final signature's nonce point has even y - k = get_k_for_even_tweaked_nonce(k0, tweaked_r_point) - IO.puts("k1: " <> to_string(k.d)) + {k, was_negated} = get_k_for_even_tweaked_nonce(k0, tweaked_r_point) r_point = PrivateKey.to_point(k) tweaked_r_point = Math.add(r_point, tweak_point) - IO.puts("x: " <> to_string(tweaked_r_point.x) <> "\ny: " <> to_string(tweaked_r_point.y) <> "\n") - IO.puts("---!\n") - if Point.has_even_y(tweaked_r_point) do - sameK = to_string(k == k0) - {:error, "what is going on: sameK: #{sameK}"} - else - e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) - sig_s = calculate_s(k, d, e) - - {:ok, %Signature{r: r_point.x, s: sig_s}, tweak_point} - end + e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) + sig_s = calculate_s(k, d, e) + + {:ok, %Signature{r: r_point.x, s: sig_s}, tweak_point, was_negated} end end end @@ -106,13 +96,15 @@ defmodule Bitcoinex.Secp256k1.Schnorr do |> Math.modulo(@n) end + # returns new_k, was_negated defp get_k_for_even_tweaked_nonce(k, tweaked_point) do if Point.has_even_y(tweaked_point) do IO.puts("k unchanged\n") - k - else + {k, false} + + IO.puts("k changed\n") - PrivateKey.negate(k) + {PrivateKey.negate(k), true} end end @@ -125,7 +117,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do if k0.d == 0 do {:error, "invalid aux randomness"} - else + + {:ok, Secp256k1.force_even_y(k0)} end end @@ -165,35 +158,61 @@ defmodule Bitcoinex.Secp256k1.Schnorr do validate_r(r_point, r) end - @spec verify_untweaked_signature(Point.t(), non_neg_integer, Signature.t(), Point.t()) :: + @spec verify_untweaked_signature(Point.t(), non_neg_integer, Signature.t(), Point.t(), boolean) :: boolean | {:error, String.t()} - def verify_untweaked_signature(pubkey, z, %Signature{r: r, s: s}, tweak_point) do - if r >= @p || s >= @n, do: {:error, "invalid signature"} + def verify_untweaked_signature(_, _, %Signature{r: r, s: s}, _, _) when r >= @p || s >= @n, do: {:error, "invalid signature"} + def verify_untweaked_signature(pubkey, z, %Signature{r: r, s: s}, tweak_point, was_negated) do + z_bytes = Utils.int_to_big(z, 32) case Point.lift_x(r) do {:error, err} -> {:error, err} {:ok, given_r_point} -> - tweaked_point = Math.add(given_r_point, tweak_point) - z_bytes = Utils.int_to_big(z, 32) - e = calculate_e(Point.x_bytes(tweaked_point), Point.x_bytes(pubkey), z_bytes) - - r_point = calculate_r(pubkey, s, e) + case get_even_tweaked_point(given_r_point, tweak_point, was_negated) do + {:error, err} -> + {:error, err} + + {:ok, tweaked_point} -> + e = calculate_e(Point.x_bytes(tweaked_point), Point.x_bytes(pubkey), z_bytes) + r_point = calculate_r(pubkey, s, e) + validate_r(r_point, r) + end + end + end - validate_r(r_point, r) + # bool is was_negated. + defp get_even_tweaked_point(r_point, tweak_point, false) do + Math.add(given_r_point, tweak_point) + end + defp get_even_tweaked_point(r_point, tweak_point, true) do + # force tweaked_point to have even y + tweaked_point = get_even_tweaked_point(r_point, tweak_point, false) + case Point.lift_x(tweaked_point.x) do + {:ok, tweaked_point} -> {:ok, tweaked_point} + {:error, err} -> {:error, err} end end @spec tweak_signature(Signature.t(), non_neg_integer | PrivateKey.t()) :: Signature.t() def tweak_signature(sig, t = %PrivateKey{}), do: tweak_signature(sig, t.d) - def tweak_signature(%Signature{r: r, s: s}, tweak) do - {:ok, t} = PrivateKey.new(tweak) + def tweak_signature(%Signature{r: r, s: s}, tweak, was_negated) do + t = get_even_tweak(tweak, was_negated) t_point = PrivateKey.to_point(t) {:ok, r_point} = Point.lift_x(r) tw_s = Math.modulo(tweak+s, @n) %Signature{r: Math.add(r_point, t_point).x, s: tw_s} end + def get_even_tweak(tweak, false) do + {:ok, t} = PrivateKey.new(tweak) + t + end + def get_even_tweak(tweak, true) do + tweak + |> get_even_tweak(false) + |> PrivateKey.negate() + end + @doc """ extract_tweak takes a signer pubkey, message hash z, untweaked signature (adaptor signature), and a completed signature, verifies the signature, and then returns the revealed tweak secret @@ -204,10 +223,12 @@ defmodule Bitcoinex.Secp256k1.Schnorr do if verify_signature(pubkey, z, tweaked_sig) do if tweaked_s < s do {:error, "invalid tweak"} - else + + {:ok, tweaked_s - s} end - else + + {:error, "failed to extract tweak due to invalid signature"} end end @@ -241,7 +262,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do if verify_signature(pubkey, z, tweaked_sig) do {:ok, %Signature{r: tweaked_r_point.x, s: tweaked_s}} - else + + {:error, "tweak does not produce valid signature"} end end From b7d57390805c3fe43b3f837a1e6f7c4654e7317b Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sun, 29 Jan 2023 22:54:55 -0800 Subject: [PATCH 14/63] fix schnorr adaptor signatures --- lib/secp256k1/point.ex | 10 ++ lib/secp256k1/schnorr.ex | 260 +++++++++++++----------------- test/secp256k1/adaptor_test.exs | 151 ----------------- test/secp256k1/schnorr_test.exs | 63 +++++++- test/secp256k1/secp256k1_test.exs | 2 +- 5 files changed, 183 insertions(+), 303 deletions(-) delete mode 100644 test/secp256k1/adaptor_test.exs diff --git a/lib/secp256k1/point.ex b/lib/secp256k1/point.ex index 8793347..50259bd 100644 --- a/lib/secp256k1/point.ex +++ b/lib/secp256k1/point.ex @@ -105,6 +105,16 @@ defmodule Bitcoinex.Secp256k1.Point do end end + @doc """ + negate returns the pubkey with the same x but the other y. + It does this by passing y % 2 == 0 as y_is_odd to Secp256k1.get_y. + """ + @spec negate(t()) :: t() + def negate(%__MODULE__{x: x, y: y}) do + {:ok, y} = Secp256k1.get_y(x, (y &&& 1) == 0) + %__MODULE__{x: x, y: y} + end + @doc """ sec serializes a compressed public key to binary """ diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 5de2132..7e612cc 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -36,6 +36,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do t = Utils.xor_bytes(d_bytes, tagged_aux_hash) {:ok, k0} = calculate_k(t, d_point, z_bytes) + if k0.d == 0 do {:error, "invalid aux randomness"} else @@ -46,8 +47,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do {:error, msg} k -> - e = calculate_e(Point.x_bytes(r_point) <> Point.x_bytes(d_point) <> z_bytes) - sig_s = calculate_s(k,d,e) + e = calculate_e(Point.x_bytes(r_point), Point.x_bytes(d_point), z_bytes) + sig_s = calculate_s(k, d, e) {:ok, %Signature{r: r_point.x, s: sig_s}} end @@ -56,51 +57,10 @@ defmodule Bitcoinex.Secp256k1.Schnorr do end end - @spec sign_for_tweak(PrivateKey.t(), non_neg_integer, non_neg_integer, Point.t()) :: - {:ok, Signature.t(), Point.t(), bool} | {:error, String.t()} - def sign_for_tweak(privkey, z, aux, tweak_point) do - case PrivateKey.validate(privkey) do - {:error, msg} -> - {:error, msg} - - {:ok, privkey} -> - z_bytes = Utils.int_to_big(z, 32) - aux_bytes = Utils.int_to_big(aux, 32) - d_point = PrivateKey.to_point(privkey) - d = Secp256k1.force_even_y(privkey) - d_bytes = Utils.int_to_big(d.d, 32) - tagged_aux_hash = tagged_hash_aux(aux_bytes) - t = Utils.xor_bytes(d_bytes, tagged_aux_hash) - - {:ok, k0} = - tagged_hash_nonce(t <> Point.x_bytes(d_point) <> z_bytes) - |> :binary.decode_unsigned() - |> Math.modulo(@n) - |> PrivateKey.new() - - if k0.d == 0 do - {:error, "invalid aux randomness"} - else - r_point = PrivateKey.to_point(k0) - tweaked_r_point = Math.add(r_point, tweak_point) - # we must ensure that R+T, the final signature's nonce point has even y - {k, was_negated} = get_k_for_even_tweaked_nonce(k0, tweaked_r_point) - r_point = PrivateKey.to_point(k) - tweaked_r_point = Math.add(r_point, tweak_point) - - e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) - sig_s = calculate_s(k, d, e) - - {:ok, %Signature{r: r_point.x, s: sig_s}, tweak_point, was_negated} - end - end - end - defp tagged_hash_aux(aux), do: Utils.tagged_hash("BIP0340/aux", aux) defp tagged_hash_nonce(nonce), do: Utils.tagged_hash("BIP0340/nonce", nonce) defp tagged_hash_challenge(chal), do: Utils.tagged_hash("BIP0340/challenge", chal) - defp calculate_r(pubkey, s, e) do @generator_point |> Math.multiply(s) @@ -112,17 +72,6 @@ defmodule Bitcoinex.Secp256k1.Schnorr do |> Math.modulo(@n) end - # returns new_k, was_negated - defp get_k_for_even_tweaked_nonce(k, tweaked_point) do - if Point.has_even_y(tweaked_point) do - IO.puts("k unchanged\n") - {k, false} - else - IO.puts("k changed\n") - {PrivateKey.negate(k), true} - end - end - defp calculate_k(t, d_point, z_bytes) do {:ok, k0} = tagged_hash_nonce(t <> Point.x_bytes(d_point) <> z_bytes) @@ -143,20 +92,39 @@ defmodule Bitcoinex.Secp256k1.Schnorr do |> Math.modulo(@n) end - defp validate_r(r_point, rx) do + defp partial_validate_r(r_point, rx) do cond do Point.is_inf(r_point) -> {:error, "R point is infinite"} - !Point.has_even_y(r_point) -> - {:error, "R point is not even"} + r_point.x != rx -> {:error, "x's do not match #{r_point.x} vs #{rx}"} - true -> true + # TODO ensure encrypted signatures are allwoed to have odd Y values + # !Point.has_even_y(r_point) -> + # {:error, "R point is not even"} + true -> + true + end + end + + defp validate_r(r_point, rx) do + cond do + Point.is_inf(r_point) -> + # {:error, "R point is infinite"} + false + !Point.has_even_y(r_point) -> + # {:error, "R point is not even"} + false + r_point.x != rx -> + # {:error, "x's do not match #{r_point.x} vs #{rx}"} + false + true -> + true end end @doc """ - verify whether the schnorr signature is valid for the given message hash and public key + verify_signature verifies whether the Schnorr signature is valid for the given message hash and public key """ @spec verify_signature(Point.t(), non_neg_integer, Signature.t()) :: boolean | {:error, String.t()} @@ -174,113 +142,105 @@ defmodule Bitcoinex.Secp256k1.Schnorr do validate_r(r_point, r) end - @spec verify_untweaked_signature(Point.t(), non_neg_integer, Signature.t(), Point.t(), boolean) :: - boolean | {:error, String.t()} - def verify_untweaked_signature(_, _, %Signature{r: r, s: s}, _, _) when r >= @p || s >= @n, do: {:error, "invalid signature"} - def verify_untweaked_signature(pubkey, z, %Signature{r: r, s: s}, tweak_point, was_negated) do + # negate a secret + defp conditional_negate(d, true), do: %PrivateKey{d: d} |> PrivateKey.negate() + defp conditional_negate(d, false), do: %PrivateKey{d: d} + + # negate a point (switches parity of P.y) + defp conditional_negate_point(point, true), do: Point.negate(point) + defp conditional_negate_point(point, false), do: point + + # Adaptor/Encrypted Signatures + + @doc """ + encrypted_sign signs a message hash z with Private Key sk but encrypts the signature using the tweak_point + as the encryption key. The signer need not know the decryption key / tweak itself, which can later be used + to decrypt the signature into a valid Schnorr signature. This produces an Adaptor Signature. + """ + @spec encrypted_sign(PrivateKey.t(), non_neg_integer(), non_neg_integer(), Point.t()) :: {:ok, Signature.t(), boolean} + def encrypted_sign(sk = %PrivateKey{}, z, aux, tweak_point = %Point{}) do z_bytes = Utils.int_to_big(z, 32) - case Point.lift_x(r) do - {:error, err} -> - {:error, err} - - {:ok, given_r_point} -> - case get_even_tweaked_point(given_r_point, tweak_point, was_negated) do - {:error, err} -> - {:error, err} - - {:ok, tweaked_point} -> - e = calculate_e(Point.x_bytes(tweaked_point), Point.x_bytes(pubkey), z_bytes) - r_point = calculate_r(pubkey, s, e) - validate_r(r_point, r) - end - end - end + aux_bytes = Utils.int_to_big(aux, 32) + d_point = PrivateKey.to_point(sk) - # bool is was_negated. - defp get_even_tweaked_point(r_point, tweak_point, false) do - Math.add(given_r_point, tweak_point) - end - defp get_even_tweaked_point(r_point, tweak_point, true) do - # force tweaked_point to have even y - tweaked_point = get_even_tweaked_point(r_point, tweak_point, false) - case Point.lift_x(tweaked_point.x) do - {:ok, tweaked_point} -> {:ok, tweaked_point} - {:error, err} -> {:error, err} - end - end + d = Secp256k1.force_even_y(sk) + d_bytes = Utils.int_to_big(d.d, 32) + tagged_aux_hash = tagged_hash_aux(aux_bytes) + t = Utils.xor_bytes(d_bytes, tagged_aux_hash) + # TODO always add tweak_point to the nonce to commit to it as well + {:ok, k0} = calculate_k(t, d_point, z_bytes) - @spec tweak_signature(Signature.t(), non_neg_integer | PrivateKey.t()) :: Signature.t() - def tweak_signature(sig, t = %PrivateKey{}), do: tweak_signature(sig, t.d) - def tweak_signature(%Signature{r: r, s: s}, tweak, was_negated) do - t = get_even_tweak(tweak, was_negated) - t_point = PrivateKey.to_point(t) - {:ok, r_point} = Point.lift_x(r) - tw_s = Math.modulo(tweak+s, @n) - %Signature{r: Math.add(r_point, t_point).x, s: tw_s} - end + r_point = PrivateKey.to_point(k0) + tweaked_r_point = Math.add(r_point, tweak_point) + # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true + {tweaked_r_point, was_negated} = make_point_even(tweaked_r_point) + k = conditional_negate(k0.d, was_negated) - def get_even_tweak(tweak, false) do - {:ok, t} = PrivateKey.new(tweak) - t - end - def get_even_tweak(tweak, true) do - tweak - |> get_even_tweak(false) - |> PrivateKey.negate() + e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) + s = calculate_s(k, d, e) + # we return Signature{R+T,s}, not a valid signature since s is untweaked. + {:ok, %Signature{r: tweaked_r_point.x, s: s}, was_negated} end @doc """ - extract_tweak takes a signer pubkey, message hash z, untweaked signature (adaptor signature), - and a completed signature, verifies the signature, and then returns the revealed tweak secret + verify_encrypted_signature verifies that an encrypted signature commits to a tweak_point / encryption key. + This is different from a regular Schnorr signature verification, as encrypted signatures are not valid Schnorr Signatures. """ - @spec extract_tweak(Point.t(), non_neg_integer, Signature.t(), Signature.t()) :: - {:ok, non_neg_integer} | {:error, String.t()} - def extract_tweak(pubkey, z, %Signature{s: s}, tweaked_sig = %Signature{s: tweaked_s}) do - if verify_signature(pubkey, z, tweaked_sig) do - if tweaked_s < s do - {:error, "invalid tweak"} + @spec verify_encrypted_signature(Signature.t(), Point.t(), non_neg_integer(), Point.t(), boolean) :: boolean + def verify_encrypted_signature( + %Signature{r: tweaked_r, s: s}, + pk = %Point{}, + z, + tweak_point = %Point{}, + was_negated + ) do + z_bytes = Utils.int_to_big(z, 32) - else - {:ok, tweaked_s - s} - end + {:ok, tweaked_r_point} = Point.lift_x(tweaked_r) + # This is subtracting the tweak_point (T) from the tweaked_point (R + T) to get the original R + tweak_point = conditional_negate_point(tweak_point, !was_negated) + r_point = Math.add(tweaked_r_point, tweak_point) + + e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(pk), z_bytes) + r_point2 = calculate_r(pk, s, e) + partial_validate_r(r_point, r_point2.x) + end + defp make_point_even(point) do + if Point.has_even_y(point) do + {point, false} else - {:error, "failed to extract tweak due to invalid signature"} + {Point.negate(point), true} end end @doc """ - extract_tweaked_signature takes a signer pubkey, message hash z, untweaked signature (adaptor signature), - and a tweak secret, and uses it to verify the adaptor signature, and returns the complete signature + decrypt_signature uses the tweak/decryption key to transform an + adaptor/encrypted signature into a final, valid Schnorr signature. """ - @spec extract_tweaked_signature( - Point.t(), - non_neg_integer, - Signature.t(), - non_neg_integer | PrivateKey.t() - ) :: - {:ok, Signature.t()} | {:error, String.t()} - def extract_tweaked_signature(pubkey, z, sig, t = %PrivateKey{}), - do: extract_tweaked_signature(pubkey, z, sig, t.d) - - def extract_tweaked_signature(pubkey, z, %Signature{r: r, s: s}, tweak) do - case Point.lift_x(r) do - {:error, err} -> - {:error, err} - - {:ok, r_point} -> - tweaked_s = tweak + s - - tweak_point = PrivateKey.to_point(tweak) - tweaked_r_point = Math.add(r_point, tweak_point) - - tweaked_sig = %Signature{r: tweaked_r_point.x, s: tweaked_s} + @spec decrypt_signature(Signature.t(), PrivateKey.t(), boolean) :: Signature.t() + def decrypt_signature(%Signature{r: r, s: s}, tweak, was_negated) do + tweak = conditional_negate(tweak, was_negated) + final_s = Math.modulo(tweak.d + s, @n) + %Signature{r: r, s: final_s} + end - if verify_signature(pubkey, z, tweaked_sig) do - {:ok, %Signature{r: tweaked_r_point.x, s: tweaked_s}} - else - {:error, "tweak does not produce valid signature"} - end - end + @doc """ + recover_decryption_key recovers the tweak or decryption key by + subtracting final_sig.s - encrypted_sig.s (mod n). The tweak is + negated if the original R+T point was negated during signing. + """ + @spec recover_decryption_key(Signature.t(), Signature.t(), boolean) :: + PrivateKey.t() | {:error, String.t()} + def recover_decryption_key(%Signature{r: enc_r}, %Signature{r: r}, _, _) when enc_r != r, + do: {:error, "invalid signature pair"} + + def recover_decryption_key( + _encrypted_sig = %Signature{s: enc_s}, + _sig = %Signature{s: s}, + was_negated + ) do + t = Math.modulo(s - enc_s, @n) + conditional_negate(t, was_negated) end end diff --git a/test/secp256k1/adaptor_test.exs b/test/secp256k1/adaptor_test.exs deleted file mode 100644 index 127cc6b..0000000 --- a/test/secp256k1/adaptor_test.exs +++ /dev/null @@ -1,151 +0,0 @@ -defmodule Bitcoinex.Secp256k1.SchnorrTest do - use ExUnit.Case - - alias Bitcoinex.Secp256k1 - alias Bitcoinex.Secp256k1.{PrivateKey, Schnorr, Signature} - - - @schnorr_adaptor_signatures [ - %{ - privkey: 0x279D71D68D3EE997019D005BDF703C271001631A7EE12E4C9DAD10C0754912DC, - pubkey: 0x22c63594ea2c2199e0500cdf6dffecdf878441720789c8dfcfb9af06a96fd1e4, - tweak_secret: 0xF8EBFDF85A3AF0C337ECB165EF47D565DE15CBCEEB597A243C3D54DF49B703D5, - tweak_point: 0x6545e169e4d2e940e63207110a9d44dd5d4ca65aeb58e3e566658f62d41bd23f, - message_hash: 0x5736367EBB12EDC15B0FA75319B46D016F86A0E057B9237240D6185C93596367, - aux_rand: 0x7E4E37835DDFC6A82A011073DCB779D02F1F5B52A2937B6ADD5B9DA2528FC5C6, - untweaked_sig: "e2125e2f6d791ce59b604dfc0578a823008a5c86f2f2efbd0de68a4cb19688d817ebac918f08e0078c66a26c664d9f169d66dc54fdd95972e68a69b79797274a", - tweaked_sig: "320ab814c2e7e2567af8e738ce83e9fdc55ef57933dd52b169bba46fd3516e4c10d7aa89e943d0cac45353d25595747dc0cdcb3d39ea335b62f5600a1117e9de" - }, - %{ - privkey: 0x6F0B8C685BEB787F3121851AC13DAD9549C235217C5F578D9E7BE3B3845D8773, - pubkey: 0x825fa6ec886022247b7b98d24d47a6f03d3a47f2fa3916a244eba7114ea94fa1, - tweak_secret: 0x76D2C1FE7E575063C8888D935645973AC3AE1FFD1C97F25D14F7567657D7F748, - tweak_point: 0xb99378d7cf782cf6ff265ef8e3ba447b36da89224c0682ca61cc9916a4fec4bf, - message_hash: 0xA193926FAFF8CE363937D8655892E60D45661C5FC510BC711E4B5049A6B4DD30, - aux_rand: 0x76C66B394932DC5704EC0FB4CE4D76D388707396AC64D9C35BFC081F11D51124, - untweaked_sig: "864897cd95c22dda7fce0435fe365c658118e52d578ba77c25d1f1e63ff3cd0916b38c325759c310e467a0ec026d8362b2f5155adb19a17eb06a5c661b039be1", - tweaked_sig: "e277ad505af9e82abcb122c3df410459494af86315988d4596bf0fb71f64bf338d864e30d5b11374acf02e7f58b31a9d76a33557f7b193dbc561b2dc72db9329" - }, - %{ - privkey: 0x624FB68C6C2474358EF57A9810BA5A17855D0FF8676D5D80E93DF314D5C3D277, - pubkey: 0x4af83421073f24e51235c43ffb85aa94dc3139af958a2532c6b412832fbbacc4, - tweak_secret: 0xF1997F60060268C14B81744869AEB04098A93704C3BDA2A640A1B26F74E246F4, - tweak_point: 0xb9dc16baaf262b3a60a1979833f9ca350e2219121fdb43d3fd2045d3142bb71f, - message_hash: 0x97FF15503ECA5167E871E04998F3C90976E8FAFF9AD1AF54F045153BA41E1663, - aux_rand: 0x71C1FE1F3A27B04EEC4142AC1612A7C6419B1740716E2C29CEE319E718950A04, - untweaked_sig: "08365373469000bca3fa28a95469f6331a20d4f03429372573b3c2d3d3d5b0f6801f053ece6e34cdf9fa8f43ec2ed3b730c388c0e444dd45f9cc1f920adac25f", - tweaked_sig: "9e3deb23f27730ce1be854b0145cc9bf99e3ee10491d28308160c7b65b0a56e871b8849ed4709d8f457c038c55dd83f90ebde2def8b9dfb07a9b7374af86c812" - }, - %{ - privkey: 0xB5196100AAC42311C9B33B1746CA0B38AC4547936DDC4AFFF547A862A4BF9890, - pubkey: 0x5b279f79dcf2414b66160515de53ac471a043d6d9f62fd007c09ad90704ffd28, - tweak_secret: 0xA69690763B4034FCF0F269E80464643E521475BFE08162632971AE0BDAA7AE8E, - tweak_point: 0xe9f219bb7c0a390a7df96a4f33b7f7d6179a9a4aa216ed1a750ddba1719072f0, - message_hash: 0xAD6F06747576C8EC210113A2C070A7A5688616EE841FA61026D2FF1CF9F7970F, - aux_rand: 0x63241AA5C78B99A849025EB3056E84DB011769DC8BBE782FAFA6E89D2BEE0C16, - untweaked_sig: "ef3766f37e9235e9652418bd60c54cd8e9c2db06f7effc542415fc76fac9c5cb75cc36cc9d5c20d303c30e43c2be82708141b8f721f75c7f889ebb24da81c818", - tweaked_sig: "aa9788a104f497f788bf4979a791a04c8408fd2f82075d5810f5f85394d567761c62c742d89c55cff4b5782bc722e6b018a751d053301ea6f23e0aa3e4f33565" - }, - %{ - privkey: 0x45AC71188B8B3B180D806ABB0289AB23357BF7CE866BFDB13509C68BAC5C88C4, - pubkey: 0x8b688d7ce32ab13bed3f0c5f119791f8b76913809c22983c8b6713d6fdc6f5a3, - tweak_secret: 0x6D2B06A08C0F20A64FA06DADF35F992F4DA441A29541268FB5F9AA9B7977A886, - tweak_point: 0x4d01a75b79ad34ce9d1dec59c581881db6a05865807705558a31e5b19047b024, - message_hash: 0x1E3D05CC94304EB6CEF384D5ADF36827A6CA4299D70B821B60240995F75BB6CB, - aux_rand: 0x7EFC5AD98D6AA0542A9534FA8637AB87A1B8E48FBAFD89AC6EA9348B44EEA049, - untweaked_sig: "655244da978cb1fa37690f3fe98c0e78b58390207781dae0d57f74dde06c0c3f6c8463e2c5f1e4dcabdfa050d9f18e3dfdd4bd70b55a024f3beab45fecb6e255", - tweaked_sig: "29abdd275d062b0bef51113fabaf3a70652387e7fa227c54b968b4943a7f4312d9af6a8352010582fb800dfecd51276d4b78ff134a9b28def1e45efb662e8adb" - }, - %{ - privkey: 0x24D807AB16645D504C75AA9B6355444FC291298AABEB4DAC5EB070898AD645B0, - pubkey: 0xf7609d85839d360fdba6a7bcde848807baeb6417d5ed4258c1c8bc4650543c7a, - tweak_secret: 0x4ABE441BF15DBB0378C2763FCA1E967EFA2636C6292A80711DA55AC7810CAA2A, - tweak_point: 0x6690f7703df1a815d8b4391889c47d8d8837369c6f2e00d5eb92abf1b90a206f, - message_hash: 0x9AE6F80954DA7A3D615987FC0CE97CC4E4567F5425A733DBD4C0BD496A3D5622, - aux_rand: 0xA421B1D6C0DA447395A19B0CF5BBB9879DAE8303CFD33C46F001DBE7EFE9853, - untweaked_sig: "96b4d6d0a19e7f42eea9ee4ad8d36a19e039623eeea47a6fb1658df2e20f7e23cbc02c5c59d7961cf88402a346584af229cb795295c5ea108440bd7de5ea01f0", - tweaked_sig: "a57c39c39b18fd5e6f572253e19227252a4a81a4ac3dc1ef804e6b306b142b10167e70784b355120714678e31076e1726942d3320fa7ca45e213b9b896c06ad9" - }, - %{ - privkey: 0x7EDA1030020279FA24CC33B79A665B73A9E715EB1EB2C6DBEBEE21AD7BCF1E2C, - pubkey: 0x65c57e92bd052d623ac708c34a9a73851e37c81cb763b9fe1ff88437ce42245f, - tweak_secret: 0x1DE275F6F723416882A18E0F4CCD398B7AA9C2A701BFD16D448A118BF82D3027, - tweak_point: 0xcbc6ed86f4442c6403d5c0a3b8157489c5cc55d5137bbd4648c58301979b6f6c, - message_hash: 0xA744D50F65508DDE9278AECAD6A2759BE3A83709877B427E4098085DB27B9826, - aux_rand: 0x12F582D5FA0B2F6245985FBC7C7220C55D441D027B64BA2A7289B9A15E5106F7, - untweaked_sig: "454d9f3fe892e1f31638a258b135ddb74beaed682936830b1d233432e7a2a575d9953241a92d0172bf8df6e198dc7a9052638337483e0881ccb96ba38ea973d3", - tweaked_sig: "db076ae6615ccd083a0d8e6d9e78c28cba696f4c4d3fbfb5c5a55b23d4de0d0af777a838a05042db422f84f0e5a9b41bcd0d45de49fdd9ef11437d2f86d6a3fa" - }, - %{ - privkey: 0x95896701D713F8AA383176C2BCD61F8EC55A29C5169AEBE9A8A32AC9803F8E7D, - pubkey: 0xe7d2b5e311e4f7e81d05c5c56a94f0e5438da78b2e512f78b540114dcf931080, - tweak_secret: 0x7038F53CA5C13C1534A595BC74799657805CB864A21AC78618E661E2A27F6DE4, - tweak_point: 0xe76f186a03ef4ca41b55ff18e2de2f713cf9a1f97f23f627681a2a47bea24a95, - message_hash: 0x4928F4C94434C570F12CF548F4B2F4C9124F5E6D1371020D55EC3D508DA49D61, - aux_rand: 0x51DDDD197BF29EE74235C80CECBE06C8E012BFFC0E227E87A966AD9BB6E6A07B, - untweaked_sig: "46d4e627dc2a8d3864f4e6738f88ab891e5e51da7b08251218d11b0f56f047b4d91271d08e0ba2ecde0169ce02307e49bca6cc749e3c6869dad5ace39f09c943", - tweaked_sig: "83ba54b75d4516cb376b51fc8fb9cd560aae652a2ff8cded96cb052ed3b1e12e494b670d33ccdf0212a6ff8a76aa14a28254a7f2910e8fb433e9b0397152f5e6" - }, - %{ - privkey: 0x695563958BE07A20700F6B624C2EBA9F336C47ADF7B8E202388600132D2E7CF3, - pubkey: 0x97826ac90a2e8ebf9c2f94c5f23863aed915b4c4e77701b9ef7b43e9c501d0f3, - tweak_secret: 0x6FDDBC57B407184C1D1C0B9040A14A19EF8824222431F39CB89FED2C9D67CE41, - tweak_point: 0xe772ca746c6f40e607167f6d95414dab603fb36e0a4e473ab69d6e1036c209ea, - message_hash: 0x9A6BBFEF82261C8EFA9CD7F3481728D68554B2E698187797EAF60A734B6307D9, - aux_rand: 0x1ED0360C5241E28038735B43C6705AE2A1A971CDD94FCED6817C005ED2DF8237, - untweaked_sig: "d44e4a61254b90b671a278ee8ae73add71c5cb89b9fda0611cabb3ab46c4bb5d2511324a297a4027f3bef12a79e07a16d1419922e0d6c205066b538efc655dfe", - tweaked_sig: "840a95a2d67b1fe09584a2b9f353277c318d4b21774a777a2dd2814de4e9d22094eeeea1dd81587410dafcbaba81c430c0c9bd450508b5a1bf0b40bb99cd2c3f" - } - ] - - describe "adaptor signature tests" do - test "test adaptor signatures" do - for t <- @schnorr_adaptor_signatures do - {:ok, sk} = PrivateKey.new(t.privkey) - sk = Secp256k1.force_even_y(sk) - pk = PrivateKey.to_point(sk) - # check pubkey is correct - assert t.pubkey == pk.x - - {:ok, tw} = PrivateKey.new(t.tweak_secret) - tw = Secp256k1.force_even_y(tw) - tw_point = PrivateKey.to_point(tw) - # check tweak point is correct - assert t.tweak_point == tw_point.x - - # parse sigs - {:ok, c_ut_sig} = Signature.parse_signature(t.untweaked_sig) - {:ok, c_tw_sig} = Signature.parse_signature(t.tweaked_sig) - - {:ok, ut_sig, t_point} = Schnorr.sign_for_tweak(sk, t.message_hash, t.aux_rand, tw_point) - # check tweak point hasn't changed - assert t_point == tw_point - # check untweaked sig - assert c_ut_sig == ut_sig - # adaptor sig is not a valid schnorr sig, must fail - assert !Schnorr.verify_signature(pk, t.message_hash, ut_sig) - # verify adaptor signature - assert Schnorr.verify_untweaked_signature(pk, t.message_hash, ut_sig, tw_point) - # complete adaptor sig - tw_sig = Schnorr.tweak_signature(ut_sig, tw) - # check sig - assert c_tw_sig == tw_sig - # ensure tweaked sig is same when using tweak private key and integer - tw_sig0 = Schnorr.tweak_signature(ut_sig, tw.d) - assert tw_sig == tw_sig0 - - # complete sig must be valid schnorr sig - assert Schnorr.verify_signature(pk, t.message_hash, tw_sig) - - # extract tweak secret - {:ok, tweak} = Schnorr.extract_tweak(pk, t.message_hash, ut_sig, tw_sig) - assert tweak == tw.d - assert t.tweak_secret == tweak - - # extract signature given tweak - {:ok, sig} = Schnorr.extract_tweaked_signature(pk, t.message_hash, ut_sig, t.tweak_secret) - assert sig == tw_sig - end - end - end -end diff --git a/test/secp256k1/schnorr_test.exs b/test/secp256k1/schnorr_test.exs index 522d940..454178e 100644 --- a/test/secp256k1/schnorr_test.exs +++ b/test/secp256k1/schnorr_test.exs @@ -4,9 +4,11 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do alias Bitcoinex.Utils alias Bitcoinex.Secp256k1 - alias Bitcoinex.Secp256k1.{Point, PrivateKey, Schnorr, Signature} + alias Bitcoinex.Secp256k1.{Params, Point, PrivateKey, Schnorr, Signature} # alias Bitcoinex.Secp256k1.{PrivateKey} + @n Params.curve().n + # BIP340 official test vectors: # https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv @schnorr_signatures_with_secrets [ @@ -154,8 +156,24 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do } ] + def get_rand_values_for_encrypted_sig() do + sk_int = :rand.uniform(@n - 1) + {:ok, sk} = PrivateKey.new(sk_int) + sk = Secp256k1.force_even_y(sk) + pk = PrivateKey.to_point(sk) + + # tweak + tweak_int = :rand.uniform(@n - 1) + {:ok, tweak} = PrivateKey.new(tweak_int) + tweak_point = PrivateKey.to_point(tweak) + msg = :rand.uniform(@n - 1) |> :binary.encode_unsigned() + z = Utils.double_sha256(msg) |> :binary.decode_unsigned() + aux = :rand.uniform(@n - 1) + + {sk, pk, tweak, tweak_point, z, aux} + end describe "sign/3" do test "sign" do @@ -276,5 +294,48 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do end end + describe "encrypted signature testing" do + test "encrypted_sign/4 and verify_encrypted_signature/5" do + for _ <- 1..100 do + {sk, pk, _tweak, tweak_point, z, aux} = get_rand_values_for_encrypted_sig() + + # create adaptor sig + {:ok, ut_sig, was_negated} = Schnorr.encrypted_sign(sk, z, aux, tweak_point) + assert Schnorr.verify_encrypted_signature(ut_sig, pk, z, tweak_point, was_negated) + end + end + + test "encrypt & decrypt signature" do + for _ <- 1..100 do + {sk, pk, tweak, tweak_point, z, aux} = get_rand_values_for_encrypted_sig() + + # create adaptor sig + {:ok, ut_sig, was_negated} = Schnorr.encrypted_sign(sk, z, aux, tweak_point) + assert Schnorr.verify_encrypted_signature(ut_sig, pk, z, tweak_point, was_negated) + # decrypt to real Schnorr Signature using tweak + sig = Schnorr.decrypt_signature(ut_sig, tweak.d, was_negated) + # ensure valid Schnorr signature + assert Schnorr.verify_signature(pk, z, sig) + end + end + + test "encrypt & recover descryption key" do + for _ <- 1..100 do + {sk, pk, tweak, tweak_point, z, aux} = get_rand_values_for_encrypted_sig() + + # create adaptor sig + {:ok, ut_sig, was_negated} = Schnorr.encrypted_sign(sk, z, aux, tweak_point) + assert Schnorr.verify_encrypted_signature(ut_sig, pk, z, tweak_point, was_negated) + + # decrypt to real Schnorr Signature using tweak + sig = Schnorr.decrypt_signature(ut_sig, tweak.d, was_negated) + # ensure valid Schnorr signature + assert Schnorr.verify_signature(pk, z, sig) + + recovered_tweak = Schnorr.recover_decryption_key(ut_sig, sig, was_negated) + assert recovered_tweak == tweak + end + end + end end diff --git a/test/secp256k1/secp256k1_test.exs b/test/secp256k1/secp256k1_test.exs index d03547d..4a6c4f5 100644 --- a/test/secp256k1/secp256k1_test.exs +++ b/test/secp256k1/secp256k1_test.exs @@ -176,7 +176,7 @@ defmodule Bitcoinex.Secp256k1.Secp256k1Test do privkey = Secp256k1.force_even_y(%PrivateKey{d: secret}) pubkey = PrivateKey.to_point(privkey) - assert rem(pubkey.y,2) == 0 + assert rem(pubkey.y, 2) == 0 end end end From ce1f86926ca76d9d004da15405a9c1bed378398d Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Tue, 31 Jan 2023 01:07:03 -0800 Subject: [PATCH 15/63] finish adaptor signatures! --- lib/secp256k1/schnorr.ex | 13 ++++++------ .../schnorr_adaptor_test_vectors.csv | 21 +++++++++++++++++++ test/secp256k1/schnorr_test.exs | 12 +++++------ 3 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 test/secp256k1/schnorr_adaptor_test_vectors.csv diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 7e612cc..0534fee 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -92,6 +92,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do |> Math.modulo(@n) end + # this is just like validate_r but without the R.y evenness check defp partial_validate_r(r_point, rx) do cond do Point.is_inf(r_point) -> @@ -99,9 +100,6 @@ defmodule Bitcoinex.Secp256k1.Schnorr do r_point.x != rx -> {:error, "x's do not match #{r_point.x} vs #{rx}"} - # TODO ensure encrypted signatures are allwoed to have odd Y values - # !Point.has_even_y(r_point) -> - # {:error, "R point is not even"} true -> true end @@ -158,7 +156,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do to decrypt the signature into a valid Schnorr signature. This produces an Adaptor Signature. """ @spec encrypted_sign(PrivateKey.t(), non_neg_integer(), non_neg_integer(), Point.t()) :: {:ok, Signature.t(), boolean} - def encrypted_sign(sk = %PrivateKey{}, z, aux, tweak_point = %Point{}) do + def encrypted_sign(sk = %PrivateKey{}, z, aux, %Point{x: tweak_point_x}) do z_bytes = Utils.int_to_big(z, 32) aux_bytes = Utils.int_to_big(aux, 32) d_point = PrivateKey.to_point(sk) @@ -171,6 +169,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do {:ok, k0} = calculate_k(t, d_point, z_bytes) r_point = PrivateKey.to_point(k0) + {:ok, tweak_point} = Point.lift_x(tweak_point_x) tweaked_r_point = Math.add(r_point, tweak_point) # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true {tweaked_r_point, was_negated} = make_point_even(tweaked_r_point) @@ -191,11 +190,12 @@ defmodule Bitcoinex.Secp256k1.Schnorr do %Signature{r: tweaked_r, s: s}, pk = %Point{}, z, - tweak_point = %Point{}, + %Point{x: tweak_point_x}, was_negated ) do z_bytes = Utils.int_to_big(z, 32) + {:ok, tweak_point} = Point.lift_x(tweak_point_x) {:ok, tweaked_r_point} = Point.lift_x(tweaked_r) # This is subtracting the tweak_point (T) from the tweaked_point (R + T) to get the original R tweak_point = conditional_negate_point(tweak_point, !was_negated) @@ -220,7 +220,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do """ @spec decrypt_signature(Signature.t(), PrivateKey.t(), boolean) :: Signature.t() def decrypt_signature(%Signature{r: r, s: s}, tweak, was_negated) do - tweak = conditional_negate(tweak, was_negated) + tweak = Secp256k1.force_even_y(tweak) + tweak = conditional_negate(tweak.d, was_negated) final_s = Math.modulo(tweak.d + s, @n) %Signature{r: r, s: final_s} end diff --git a/test/secp256k1/schnorr_adaptor_test_vectors.csv b/test/secp256k1/schnorr_adaptor_test_vectors.csv new file mode 100644 index 0000000..32d1f7b --- /dev/null +++ b/test/secp256k1/schnorr_adaptor_test_vectors.csv @@ -0,0 +1,21 @@ +private_key,public_key,tweak_secret,tweak_point,message_hash,aux_rand,untweaked_adaptor_signature,tweaked_signature,was_negated +"64d399f840a0153ae7573fcd736b2b6dc8d31417514ab67622eff2354ee22241","03077b0ff8da086bbd941887ea62d90f0ecdac8acb093254fb4d69dc97d65f56","44f76c9909398b59a6fc09f9cd446476ca91e00cffe8499c1c326e2f8b9289ca","c8d33563fe4ae990f75cc0baa7526df881d95759f1dd31fa10ea3d614b8d6bef","69d1666105256708b242da64ac91b8d455119375ddaf588487f5dfd31c843f9e","f1a3588373fe6a90cf4b8d91295f41693b606f46d41f0d221bd6ebff75817f23","1527c51cd4caf8319f3017e7d06dea2faa4aa7663fed96aea69b5244c34f3588e3fec4d6f7f890d943e8d5b3acc39799c94d925a62e3fda2291cb9bf2b537e6a","1527c51cd4caf8319f3017e7d06dea2faa4aa7663fed96aea69b5244c34f358828f6317001321c32eae4dfad7a07fc11d9309580b383a702857cc961e6afc6f3",false +"332b18ac4de2ee1b125bfa35e1b0ae692c900219ae36cdc94de0de9b7b82ec35","57270515af6b0cfc28cc621c18522ce7c490bff838d6fb3e057a9d518d975464","ddbfb38f862619928f205a925ad5d7f17fffefd2526d8b37a83b863d090f8ded","9bd47e739d691b7c81c83b08678b38afc4199060ceae0e1e61fa013dc2eff161","44d013b587c4b9982cefacf9e4014e96f52485cc7648815f519e5c7acf817b95","5a238ecfc487b6a1f52e9fda6047a3a6db0c5a4a6c581671908f81c35f744c34","019ae9f0058af51040046fd20f81e3c4db38af40107a01649beb82694084331d894ef7dd5635592f2de62c9e456ad757658dfd5f6b3a3d08e23755c04981f905","019ae9f0058af51040046fd20f81e3c4db38af40107a01649beb82694084331dab8f444dd00f3f9c9ec5d20bea94ff64a03cea73c815520cf9ce2e1010a8ac59",true +"a6f9e49b8e4b714d760dbd8f01abfe04e2108f5accd7ce81091d8f8a04d4e3ef","60e3b83eec196e0ea1195ae64580d3345654e162266800f1c5507bd7acc166ef","a78f44c328c5506fe347688640de9b085560d399be6ecb23dbf06ef3239d9117","e8314bb4e986ab44904e66adc3b272a626b5804cc89bf8a9338296bd93eee97c","7a14183e12079562ea17b59a9156b1a74bb7fdd6bb16413c4a6ac4599b2c3818","4d1860c32ea63d2d59856108d3eb55e14d962cb106daf333bf721cbeaf1de6b1","0d7755f38ce499b4757528e2dbc6045a10e4242babfd9bee595f03a83b8a559a55545812984531783a3b9cd6fca829fb14c8dc7731242c1fbe916d2c3c9f965c","0d7755f38ce499b4757528e2dbc6045a10e4242babfd9bee595f03a83b8a559aadc5134f6f7fe10856f43450bbc98ef17a16e5c421fe0137a2735cc5e9384686",true +"3ecdc22e7f8f527ba66ef75754ff386db60739ad691e8713b00c7d0e18650365","e3047a46759d8e47b22dd384cf8fe8631fefa466ea41b75030cea308c3360d45","8263cfdf5b3034958b7e503907ea95722deff04f0fdbbf76adbae6d79774f2de","95e028af7e8890ba89705fdd05e26f6c68050eea5f17c65095cccb903da76f8d","6c1ea354705a73aad3006aedab07ee311d343b1421b7f4daa4380c389a793180","eea79caadc04e2df90c29494e002bb1cf096c11996eecd8ff9c4733ff133c12c","d49a04f7cf7f64414ef67c1e113ec85a8fd683d6b102584dedd5b1737b8feccb47a6a2a1a3a2dafe60ea9e54b1ba83e99b0125d89cea8d516b9b5a6fa85bfb19","d49a04f7cf7f64414ef67c1e113ec85a8fd683d6b102584dedd5b1737b8feccbca0a7280fed30f93ec68ee8db9a5195bc8f11627acc64cc8195641473fd0edf7",false +"7789778cfe9d60aa50c339a11b36ffbbc5d350c345ab14e56f207f430deafca5","031551b96dfef020553900283837e36f1f669dc1b7857129a66a23dc3cef7b04","19db69599941098c1f375b8608094dbad22d72e1fc62cd65285321a029b63de6","4ee81875e67869acd8d49b53dcdfeea2c64360b4470d548cd0a66916e354e565","db7aa4a8785c8cab4d42f6c1f282982818f5b528b3d6beb5b99486de031bf4fd","03ea829059d5d7d36ff9554b3718a0177a04de8e5ff33730c72e480477840042","b847785cfbdfc4cabab8c87499e50898834cf2f9fbd5407d3aeaccd6c234410d8824b1197afe7727177b54d6378c6e3dbc60fabdfc24b60f572ce0637b02089b","b847785cfbdfc4cabab8c87499e50898834cf2f9fbd5407d3aeaccd6c234410da2001a73143f80b336b2b05c3f95bbf88e8e6d9ff88783747f800203a4b84681",false +"2c42d0167ee5cfa88a0cc1c16fa0c9dc38c01d47f30e4c5423c707aecdd365aa","b97f1f0ce685329fce080aff4c9a17f3dfc6a14bfe9689290e9b89ec7f24bb5d","f06324b650ec674ccefa36c4e19c4e700fa09bedd1f7129c44745479dffab65d","0f638142959e8e6dc979685bb4292b646389db2ec418f87629bac97dc6c21478","f72df7b15f31c5abb0db5af07efc53199afe533af041512b47276d7ab90336be","6b117aabe372c146924929aa00990d7392eca59fc31b416a833a7e2a56ab73a8","f5b66cdfb1c916c9ceb705f8fceeee166d7aec4a71910562a1b19d4507f2518dedade54f20ed0f0345cc22a4374971ce64f4bb7cfd0e03574e0bab7f4dd1a051","f5b66cdfb1c916c9ceb705f8fceeee166d7aec4a71910562a1b19d4507f2518dfd4ac098d000a7b676d1ebdf55ad235d1002fc75da5f90f6c969b5923e0d2b35",true +"0570c938ab1e31af3244c1c8de80bfac01da36cb00708506aa9d6294b1020275","a162076078883fe9ce67f32a28eb637796fe5a20c3ee5a4dcf5795d54142152a","cb97db136151e7fd66b36622e2f6ac9f1244e951b5bf15f9a3f38aee73be4ce3","6ba7a5b30ac5bd0e5b7641830cb74367b41136823f8be7100e3c14d80b3eebcd","78ca0adc4d4d892c4bb8060c10b69d3b05630a5b5189877b9f20691897988174","4b8d63dc59a313c09548593d2002a5af4ca601b37a98bb299d8605062ea8c88a","febe05ae7e214eb33f6fabcf7bd4297b1b06e14f3bcb8170e3db5cc02aadb6d4538757ed880d9f67d61947202c7d6ba6e82ad5bf99f51b6c318009f26c59acf5","febe05ae7e214eb33f6fabcf7bd4297b1b06e14f3bcb8170e3db5cc02aadb6d41f1f3300e95f87653cccad430f7418473fc0e22aa06b912a15a136540fe1b897",false +"cfeb57aa91b08f7df563c54cd5c37fe9354c34c7a3a0942a73a1bd28d01ca785","6379d1dcf2da0e5c90e05d02b32a5c969dd21ea986f2df5a329ce2154c2f54fb","6c8f2a3b7bae9cd68637fbaac2fc1b62d5ecacd24ad97163659e7016ed79ad35","bcd420b56d103b29f50376335b959b76ff1102f319cc0e8c791615cac203c0fe","91d16e79b8869ae5d5cabfb4818405613bb9a89b552370309e44dc923c5071ae","fea792688415e4ea1927a8287dbf938aff1795c590cd89fc83a6190820f4a619","d5743de4c0be2194c9c3c7343e0e48a1f4a02627691933355f7ffcd5a2fcb0427bf317bebab7ded50ebf1b253845b43da1ad71cb5d0bd93f8fc895a7f2ebc3d5","d5743de4c0be2194c9c3c7343e0e48a1f4a02627691933355f7ffcd5a2fcb0420f63ed833f0941fe88871f7a754998dacbc0c4f9123267dc2a2a2591057216a0",true +"94c063c368f720e36575be96aa0075d73e669b9a0cb4188306d8bb4cb1cfb1e1","637cf87ec03dd68084474aa9f22bcc94a0985048d0a38b24e12b821f492ff4b5","c9fb6ba3efe3064bfd7625a53f254e90c59f35969ee3a65e110d6437fd24b435","c33096b024f6871aa9958e620501df9adc43760b6213e0a0783d143e1f36e8d7","68d398b891283b42e4bda770d3cb644be617e82a93cbd281ceae542d66e3bf55","54c1eec0ece9844fd5de9512e72b4e3452cf5472dc4934f0a7ef59323468d42a","e651742898c15685204152e9f98abb1d313ae35f4f2a156847de4ca9ce2fd9afd56205207bda7af8933d3ac71f5ef475aa03bbe8fe78733ba42f1271106a174d","e651742898c15685204152e9f98abb1d313ae35f4f2a156847de4ca9ce2fd9af9f5d70c46bbd814490b3606c5e844307b4f41498ee13795df56a181c3d588a41",false +"9e0278e0e6f8a0767dd81ecea025adf55dfaf913f9b4ff347115956fd0fb327d","535b5a59e5879920b490835ac9dbf23a82435bd083b2cc66be5be9997e2ce681","c47f351884757e03385b7c5a60d43895fa4b88dc9388d20c773c0aa6704b7ee0","93373b7570b569d3945cdc306b18b29b5f71a502f2636accb4a0b34be6d73203","8d96a34cf601b53ea3f0f1b0a5ca315f89f8d968f148c071560fca35ce97d1d4","e57adc1b2b869f8e07d97d2725fcffe19338c737a78c6d9112b34cbc5fa7c1b0","93f023cc065d5b422cbfe35b3a75e4a5aeb59b91ea500dac2cb95768519cb2b74b08551ea14588ec3a29e1d2d88d629721bbfbc7af3f5785dd7e41ca03f7d857","93f023cc065d5b422cbfe35b3a75e4a5aeb59b91ea500dac2cb95768519cb2b70f878a3725bb06ef72855e2d39619b2e6158a7bd937f895694e7ede3a40d15f6",false +"e29f4867a64d1199a19777f91c9ec2f9d15dd08a93409144ea0064cb60c76a3e","85524318955cb8c63933e6e0c06b8fe561861628548539bf760c2f1840cf8ae7","3b415bb6c72bce59fc88681de981ae96875d5cccb98755e195b3e4399340171c","f86e55a60819bc5b9ba93746753f4fc0db87c10de5b7d25f1fe242b3c1bb6ebd","1ec2865fb83a22cddf06a22491d861b685e987d115e89c4d49b93672944553df","fd6e924750d9359ff32feacc6e61991d6da690d312514b5b896bcb3365f05bdb","013a94ce22a0fed453e1e7959ca619d4a9676fe863aa3450cfd2081b9829b7cf69bee8aad31fb077fc38eeef227519f5a1ceb06692d362c11ab34351c8a15381","013a94ce22a0fed453e1e7959ca619d4a9676fe863aa3450cfd2081b9829b7cf2e7d8cf40bf3e21dffb086d138f36b5f1a715399d94c0cdf84ff5f1835613c65",true +"5745cbbd4a3f91aace64d6d5dfd81787b40c11f9c6a934bdf3fc80b2c8e28fb8","c99af68ee4b762410875e5bda87af74683541ed597966c64c96f8c486ff1f19a","627977f34498f34ed896fb5019fb9ffa2dc7dd238bf3777a3701186df35299c7","0a801b75ed3ae344de1b255f53930fc82fade6a0ba0e0b07d8b109a2b9cc3701","47406b77cca9234a06bdb4fc65ac76649e392ea780bdb81a6fc6cf3221f1354c","652839026578293d7ce1969b4d457739e722a2f5952857889da87a7d1b6f0f89","89a576153cf1bc3bc7ba7322b431a2c76851430fd4185d8369dc6ed24ef74495faca5ac0354f0a837a66214ea462f7f4e7212218776ca7ea37eb8b77ce53cd7e","89a576153cf1bc3bc7ba7322b431a2c76851430fd4185d8369dc6ed24ef744959850e2ccf0b61734a1cf25fe8a6757fab95944f4eb79307000ea7309db0133b7",true +"0c097d304c27a23e91e639407341eb33bfc90f74184130251bb0553dbe2de854","2b0868c00a5799f5686afe3550ca15d94ee2a6a81b1ecbda18e0735ca7ff7a56","6dc0f73722daa9eef9d0dcf931841a07f53c844b30a08c3f89898e619bc855f3","5c44f4f8428117f9ee1ad3711ec1120ba5a8545cc7d5aace491a215b49d617ec","52f98f0d574cae4db1cd30cb67a3e65c44dedb16f07c7bd728bf0fb83d0ad408","460d47e19edb129b81d62e6b59f76dd7bd4c61a725da3338d706c0e755ac2ae9","79beeded1b00c785dd0a466baa5b650f436ae169d0782b0d46baf5c481768c8e9c67dee427981c760ab8c6b90ce3d9f22d07007d16845eb9f80fe1dc948efbf7","79beeded1b00c785dd0a466baa5b650f436ae169d0782b0d46baf5c481768c8e0a28d61b4a72c6650489a3b23e67f3fb6794a7e197dc4abdc1c711b1602110a9",false +"b1b904d991387680de13c606565643e4930d2bf79b721db4530401c3b92cf3cf","63cdee00d9f07194399fe6518512073190b71dc2a94084681e49a172158f90ff","b6928d5994fa41ebd66a22a826cd6ad4015a23feab9ae45f1eaac309007fca73","38388ce16e2c5e1eab89b4e56233dacb58d4aa286ae2806d5debd117c9c529f9","6eab75f6bf9de09d363e4fc2447d0fc03feb78a5fb06ccb39daf648e13fd3045","41f2e0be320908f40428a195e471e425864ab7acbafdb11e642a68be1598c2c3","09cf78f977503f3a0e231d33155f1e65d100eedd0af68e5d40fa9e392138f2cb97acb68f70542aa1f6641f228d0df53b3a3930e82cac62d5cd466b89ca9049e4","09cf78f977503f3a0e231d33155f1e65d100eedd0af68e5d40fa9e392138f2cbe11a2935db59e8b61ff9fc7a66408a65f38de9d0305a1eb26e6e070d9a46c0b2",true +"7a444d39de9cf6852cf431a622af15acb852dce5034735166bdc4cccf6d984d3","52853e8d893caeb43c6ab322625641ccece988667bf9d64670a4df4750b83d7c","bf611892850c5e814215dfecd9629ed0de56376bab46fe361db5c0ba4fe916d8","103dd130009f424fdc25eeb5a1392a36fe4e008290267722ac24eb492db00ead","49a5fbb6bdb0cd167e5fb7b4b8544fcd87a3433e66292173c101efcd97756349","7666bd5dce4e4ad0df76672bcf6f8123adf4f5314894b808fbf739e3e3e687bd","6c9610699213a961ceb63a32459b5408c3a20035dc0c8b126b67d04bba9bce9abd8f6cb88cc6090f789d6726cda594f72d0335653a5b18c27655fb38dee47d56","6c9610699213a961ceb63a32459b5408c3a20035dc0c8b126b67d04bba9bce9afe2e542607b9aa8e36878739f442f625095bdae03e5cbac81872990b5f31a7bf",true +"c19d1686e76d5b777af0924e6154615abb7db030a22e407a397ce7e301d87742","0ba3c133ab7dee2839552fc9e43601ab3cc501c21891f2fd34fce78e43b63e67","2e5b908a7466490f23b30f5b75bbf10ba0d9c901a8fa1188f5ba6b1d07277174","5fba25cf7f5fb85e24900d14f789604dd0eca2eafa53ef2a41e55a79740709b0","ca543d2652c3ff13ac98641cb65df9dd0f719706363dcfa74592b373a638e308","da2cde0c16fd3cebf8c84ec28dbf1a951f43ff930795b610df8cec40749a25e2","4f9a66e8ccd5f1c68e906c7ffc8a5284065e2960a13c8af05fd671a4a2f8f93e39584c6b03ef20b2edf449b5f6fb7a00a42eadc9aba1f3652cd143d37651700a","4f9a66e8ccd5f1c68e906c7ffc8a5284065e2960a13c8af05fd671a4a2f8f93e0afcbbe08f88d7a3ca413a5a813f88f50354e4c802a7e1dc3716d8b66f29fe96",true +"29e362a7bfef5fb7f4918ec19e25befa8a4a43a23c8ec7c6028f97226773a7bd","2102f0eed266ed80ec7568cb20eeead902f0c35309f5ba7c1567b819a8e1bbb7","6c82609f56f6c11866cc83ee2498d551c17ae68d72904b4af73ff8e5f45c3284","f9f9be4fc4d63060a3c3c0656679f3d757775f8f8641a71f1525e53b57a1d99b","c78fc24af3b81ec075e1e1764b4d67ea07b5a9dda2e1504e159eb427025fb80b","15968a700ee7598db61ab47d210a27f7d0d82c5d25739b6141b095de9e00942d","676d626dad99593a9aaff30b64f38c513858b95a3f76269c211a1ff05525f39afe63ee86c0240d1f2bda706dcb386ab6b1a3d73cb44250f9fd409b764a4e2bc9","676d626dad99593a9aaff30b64f38c513858b95a3f76269c211a1ff05525f39a6ae64f26171ace3792a6f45befd14009b86fe0e37789fc0934ae35cf6e741d0c",false +"b01defc1ec85fdd6381f62ecb596239e91245b940b18489071526064a538baef","baab11fd4992c19e6a7692944ff665c08e1fb06c7a26b0f2ee2253fc9f7227e8","a0dfd48fa85f67b08b3fdbb9a6bb71c74b4239148f65f35caa37b1d1800fce44","025342259c0aa8df863a2f151ff0c2b1d6064997d1c54c42e5b62d033e83c7af","f5032f3bb2f6767a6f95b3559b74e14edde45ab47f8ff5c48542d748374c5144","08a35c8959bf4a10b85c75b7ef260d39f865876f4bdb66511b53dccf3f1a9353","3eaec73e10aa4b1f870d426dd0570dd5266ab32a93564393aad145dac625d9933033dc488afdcf7a9ac18515f96a4ac4e73db690bde3f49e2ce53141f437f0a8","3eaec73e10aa4b1f870d426dd0570dd5266ab32a93564393aad145dac625d993d113b0d8335d372b260160cfa025bc8c327fefa54d49e7fad71ce3137447beec",false +"dea7c5fe87a447632a53dcf40716ad99bd04d983405d851f44ff2671f8f8a6ea","07f04f60aa3367725401b8a813c0575007438714f3b6afa9150acdae4867f2f8","840da049a59cc5b8830e54aee306788b2b6104655f0a23a9404671f821fe9573","4ba30d52534bbed6bb67fa5e1bdfb1bdb983843a56887e43e652e89ccaae519e","d568dee1f5d4d1b400641fee5afd68e176e234acda24b0eb2516f739a8e87dcf","cab1f1f1229e93ad2efb76649c8091717ace3ba01461ac559295c5e0e006d07d","956bbdab0e1ebbbec714dd126a749b4f94bc7e55258eea4ce589e50ef0153370f1f321db528ee4f907e8b66903f080575efd5d11bb2518d9ec021e6b4c0e1156","956bbdab0e1ebbbec714dd126a749b4f94bc7e55258eea4ce589e50ef01533706de58191acf21f4084da61ba20ea07cc339c58ac5c1af530abbbac732a0f7be3",true +"17936333370f03b826196c144d2051b3da132aa00f8bb5c12482a577cad7c31f","54dbd2074d2787dc5a3e64f197bc7beb59987a4d3ed70fc818cd48ff08efeff1","059bcae22f27b9d0b66912406950cd96ac21c89b859b5f0fa401dc4ef9c6ff4d","951bf3b6eec23b6670b605a98998da6e38664d9f64cdd4f3573bc2b6f443b98e","28592ae85cb8d559e3bbe91793ad9c1463f03d7070d2e97d33b3c26d69d2fc82","2386074d572d93c77e096f6fc448521cc180f9257d9f3fb5347b7abaeb08c00c","4d081829ae9d9a9d560fa11e1122d780ef5c765719bb537d5daae655606d4814c38510e3df3375f4789be40145260d0aab790cc40e6c567e8face06b61ef0f7d","4d081829ae9d9a9d560fa11e1122d780ef5c765719bb537d5daae655606d4814c920dbc60e5b2fc52f04f641ae76daa1579ad55f9407b58e33aebcba5bb60eca",false diff --git a/test/secp256k1/schnorr_test.exs b/test/secp256k1/schnorr_test.exs index 454178e..167ab0b 100644 --- a/test/secp256k1/schnorr_test.exs +++ b/test/secp256k1/schnorr_test.exs @@ -296,7 +296,7 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do describe "encrypted signature testing" do test "encrypted_sign/4 and verify_encrypted_signature/5" do - for _ <- 1..100 do + for _ <- 1..1000 do {sk, pk, _tweak, tweak_point, z, aux} = get_rand_values_for_encrypted_sig() # create adaptor sig @@ -306,7 +306,7 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do end test "encrypt & decrypt signature" do - for _ <- 1..100 do + for _ <- 1..1000 do {sk, pk, tweak, tweak_point, z, aux} = get_rand_values_for_encrypted_sig() # create adaptor sig @@ -314,14 +314,14 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do assert Schnorr.verify_encrypted_signature(ut_sig, pk, z, tweak_point, was_negated) # decrypt to real Schnorr Signature using tweak - sig = Schnorr.decrypt_signature(ut_sig, tweak.d, was_negated) + sig = Schnorr.decrypt_signature(ut_sig, tweak, was_negated) # ensure valid Schnorr signature assert Schnorr.verify_signature(pk, z, sig) end end test "encrypt & recover descryption key" do - for _ <- 1..100 do + for _ <- 1..1000 do {sk, pk, tweak, tweak_point, z, aux} = get_rand_values_for_encrypted_sig() # create adaptor sig @@ -329,12 +329,12 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do assert Schnorr.verify_encrypted_signature(ut_sig, pk, z, tweak_point, was_negated) # decrypt to real Schnorr Signature using tweak - sig = Schnorr.decrypt_signature(ut_sig, tweak.d, was_negated) + sig = Schnorr.decrypt_signature(ut_sig, tweak, was_negated) # ensure valid Schnorr signature assert Schnorr.verify_signature(pk, z, sig) recovered_tweak = Schnorr.recover_decryption_key(ut_sig, sig, was_negated) - assert recovered_tweak == tweak + assert recovered_tweak == Secp256k1.force_even_y(tweak) end end end From 6a4e775f9c19350f3094bea1511705a0685193a1 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Tue, 31 Jan 2023 01:14:39 -0800 Subject: [PATCH 16/63] add comments, make tests realistic by force even tweak --- lib/secp256k1/schnorr.ex | 3 +++ test/secp256k1/schnorr_test.exs | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 0534fee..9a991bb 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -169,6 +169,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do {:ok, k0} = calculate_k(t, d_point, z_bytes) r_point = PrivateKey.to_point(k0) + # ensure that tweak_point has even Y {:ok, tweak_point} = Point.lift_x(tweak_point_x) tweaked_r_point = Math.add(r_point, tweak_point) # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true @@ -195,6 +196,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do ) do z_bytes = Utils.int_to_big(z, 32) + # ensure that tweak_point has even Y {:ok, tweak_point} = Point.lift_x(tweak_point_x) {:ok, tweaked_r_point} = Point.lift_x(tweaked_r) # This is subtracting the tweak_point (T) from the tweaked_point (R + T) to get the original R @@ -220,6 +222,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do """ @spec decrypt_signature(Signature.t(), PrivateKey.t(), boolean) :: Signature.t() def decrypt_signature(%Signature{r: r, s: s}, tweak, was_negated) do + # force even on tweak is a backup. the passed tweak should already be properly negated tweak = Secp256k1.force_even_y(tweak) tweak = conditional_negate(tweak.d, was_negated) final_s = Math.modulo(tweak.d + s, @n) diff --git a/test/secp256k1/schnorr_test.exs b/test/secp256k1/schnorr_test.exs index 167ab0b..228d9c1 100644 --- a/test/secp256k1/schnorr_test.exs +++ b/test/secp256k1/schnorr_test.exs @@ -165,6 +165,7 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do # tweak tweak_int = :rand.uniform(@n - 1) {:ok, tweak} = PrivateKey.new(tweak_int) + tweak = Secp256k1.force_even_y(tweak) tweak_point = PrivateKey.to_point(tweak) msg = :rand.uniform(@n - 1) |> :binary.encode_unsigned() @@ -334,7 +335,7 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do assert Schnorr.verify_signature(pk, z, sig) recovered_tweak = Schnorr.recover_decryption_key(ut_sig, sig, was_negated) - assert recovered_tweak == Secp256k1.force_even_y(tweak) + assert recovered_tweak == tweak end end end From 8bccf204fb38b39c3a3cbc52ac66f747b1cd6037 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Tue, 31 Jan 2023 01:16:26 -0800 Subject: [PATCH 17/63] fmt --- lib/secp256k1/schnorr.ex | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 9a991bb..c7a6f44 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -100,6 +100,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do r_point.x != rx -> {:error, "x's do not match #{r_point.x} vs #{rx}"} + true -> true end @@ -110,12 +111,15 @@ defmodule Bitcoinex.Secp256k1.Schnorr do Point.is_inf(r_point) -> # {:error, "R point is infinite"} false + !Point.has_even_y(r_point) -> # {:error, "R point is not even"} false + r_point.x != rx -> # {:error, "x's do not match #{r_point.x} vs #{rx}"} false + true -> true end @@ -155,7 +159,8 @@ defmodule Bitcoinex.Secp256k1.Schnorr do as the encryption key. The signer need not know the decryption key / tweak itself, which can later be used to decrypt the signature into a valid Schnorr signature. This produces an Adaptor Signature. """ - @spec encrypted_sign(PrivateKey.t(), non_neg_integer(), non_neg_integer(), Point.t()) :: {:ok, Signature.t(), boolean} + @spec encrypted_sign(PrivateKey.t(), non_neg_integer(), non_neg_integer(), Point.t()) :: + {:ok, Signature.t(), boolean} def encrypted_sign(sk = %PrivateKey{}, z, aux, %Point{x: tweak_point_x}) do z_bytes = Utils.int_to_big(z, 32) aux_bytes = Utils.int_to_big(aux, 32) @@ -186,7 +191,13 @@ defmodule Bitcoinex.Secp256k1.Schnorr do verify_encrypted_signature verifies that an encrypted signature commits to a tweak_point / encryption key. This is different from a regular Schnorr signature verification, as encrypted signatures are not valid Schnorr Signatures. """ - @spec verify_encrypted_signature(Signature.t(), Point.t(), non_neg_integer(), Point.t(), boolean) :: boolean + @spec verify_encrypted_signature( + Signature.t(), + Point.t(), + non_neg_integer(), + Point.t(), + boolean + ) :: boolean def verify_encrypted_signature( %Signature{r: tweaked_r, s: s}, pk = %Point{}, From f4dc7300f91ae8547fcd7bb6f975e126a3e5925b Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Tue, 31 Jan 2023 23:01:37 -0800 Subject: [PATCH 18/63] do not assume even tweak --- lib/secp256k1/schnorr.ex | 8 +--- .../schnorr_adaptor_test_vectors.csv | 41 +++++++++---------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index c7a6f44..824d286 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -161,7 +161,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do """ @spec encrypted_sign(PrivateKey.t(), non_neg_integer(), non_neg_integer(), Point.t()) :: {:ok, Signature.t(), boolean} - def encrypted_sign(sk = %PrivateKey{}, z, aux, %Point{x: tweak_point_x}) do + def encrypted_sign(sk = %PrivateKey{}, z, aux,tweak_point = %Point{}) do z_bytes = Utils.int_to_big(z, 32) aux_bytes = Utils.int_to_big(aux, 32) d_point = PrivateKey.to_point(sk) @@ -175,7 +175,6 @@ defmodule Bitcoinex.Secp256k1.Schnorr do r_point = PrivateKey.to_point(k0) # ensure that tweak_point has even Y - {:ok, tweak_point} = Point.lift_x(tweak_point_x) tweaked_r_point = Math.add(r_point, tweak_point) # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true {tweaked_r_point, was_negated} = make_point_even(tweaked_r_point) @@ -202,13 +201,11 @@ defmodule Bitcoinex.Secp256k1.Schnorr do %Signature{r: tweaked_r, s: s}, pk = %Point{}, z, - %Point{x: tweak_point_x}, + tweak_point = %Point{}, was_negated ) do z_bytes = Utils.int_to_big(z, 32) - # ensure that tweak_point has even Y - {:ok, tweak_point} = Point.lift_x(tweak_point_x) {:ok, tweaked_r_point} = Point.lift_x(tweaked_r) # This is subtracting the tweak_point (T) from the tweaked_point (R + T) to get the original R tweak_point = conditional_negate_point(tweak_point, !was_negated) @@ -234,7 +231,6 @@ defmodule Bitcoinex.Secp256k1.Schnorr do @spec decrypt_signature(Signature.t(), PrivateKey.t(), boolean) :: Signature.t() def decrypt_signature(%Signature{r: r, s: s}, tweak, was_negated) do # force even on tweak is a backup. the passed tweak should already be properly negated - tweak = Secp256k1.force_even_y(tweak) tweak = conditional_negate(tweak.d, was_negated) final_s = Math.modulo(tweak.d + s, @n) %Signature{r: r, s: final_s} diff --git a/test/secp256k1/schnorr_adaptor_test_vectors.csv b/test/secp256k1/schnorr_adaptor_test_vectors.csv index 32d1f7b..2fae775 100644 --- a/test/secp256k1/schnorr_adaptor_test_vectors.csv +++ b/test/secp256k1/schnorr_adaptor_test_vectors.csv @@ -1,21 +1,20 @@ -private_key,public_key,tweak_secret,tweak_point,message_hash,aux_rand,untweaked_adaptor_signature,tweaked_signature,was_negated -"64d399f840a0153ae7573fcd736b2b6dc8d31417514ab67622eff2354ee22241","03077b0ff8da086bbd941887ea62d90f0ecdac8acb093254fb4d69dc97d65f56","44f76c9909398b59a6fc09f9cd446476ca91e00cffe8499c1c326e2f8b9289ca","c8d33563fe4ae990f75cc0baa7526df881d95759f1dd31fa10ea3d614b8d6bef","69d1666105256708b242da64ac91b8d455119375ddaf588487f5dfd31c843f9e","f1a3588373fe6a90cf4b8d91295f41693b606f46d41f0d221bd6ebff75817f23","1527c51cd4caf8319f3017e7d06dea2faa4aa7663fed96aea69b5244c34f3588e3fec4d6f7f890d943e8d5b3acc39799c94d925a62e3fda2291cb9bf2b537e6a","1527c51cd4caf8319f3017e7d06dea2faa4aa7663fed96aea69b5244c34f358828f6317001321c32eae4dfad7a07fc11d9309580b383a702857cc961e6afc6f3",false -"332b18ac4de2ee1b125bfa35e1b0ae692c900219ae36cdc94de0de9b7b82ec35","57270515af6b0cfc28cc621c18522ce7c490bff838d6fb3e057a9d518d975464","ddbfb38f862619928f205a925ad5d7f17fffefd2526d8b37a83b863d090f8ded","9bd47e739d691b7c81c83b08678b38afc4199060ceae0e1e61fa013dc2eff161","44d013b587c4b9982cefacf9e4014e96f52485cc7648815f519e5c7acf817b95","5a238ecfc487b6a1f52e9fda6047a3a6db0c5a4a6c581671908f81c35f744c34","019ae9f0058af51040046fd20f81e3c4db38af40107a01649beb82694084331d894ef7dd5635592f2de62c9e456ad757658dfd5f6b3a3d08e23755c04981f905","019ae9f0058af51040046fd20f81e3c4db38af40107a01649beb82694084331dab8f444dd00f3f9c9ec5d20bea94ff64a03cea73c815520cf9ce2e1010a8ac59",true -"a6f9e49b8e4b714d760dbd8f01abfe04e2108f5accd7ce81091d8f8a04d4e3ef","60e3b83eec196e0ea1195ae64580d3345654e162266800f1c5507bd7acc166ef","a78f44c328c5506fe347688640de9b085560d399be6ecb23dbf06ef3239d9117","e8314bb4e986ab44904e66adc3b272a626b5804cc89bf8a9338296bd93eee97c","7a14183e12079562ea17b59a9156b1a74bb7fdd6bb16413c4a6ac4599b2c3818","4d1860c32ea63d2d59856108d3eb55e14d962cb106daf333bf721cbeaf1de6b1","0d7755f38ce499b4757528e2dbc6045a10e4242babfd9bee595f03a83b8a559a55545812984531783a3b9cd6fca829fb14c8dc7731242c1fbe916d2c3c9f965c","0d7755f38ce499b4757528e2dbc6045a10e4242babfd9bee595f03a83b8a559aadc5134f6f7fe10856f43450bbc98ef17a16e5c421fe0137a2735cc5e9384686",true -"3ecdc22e7f8f527ba66ef75754ff386db60739ad691e8713b00c7d0e18650365","e3047a46759d8e47b22dd384cf8fe8631fefa466ea41b75030cea308c3360d45","8263cfdf5b3034958b7e503907ea95722deff04f0fdbbf76adbae6d79774f2de","95e028af7e8890ba89705fdd05e26f6c68050eea5f17c65095cccb903da76f8d","6c1ea354705a73aad3006aedab07ee311d343b1421b7f4daa4380c389a793180","eea79caadc04e2df90c29494e002bb1cf096c11996eecd8ff9c4733ff133c12c","d49a04f7cf7f64414ef67c1e113ec85a8fd683d6b102584dedd5b1737b8feccb47a6a2a1a3a2dafe60ea9e54b1ba83e99b0125d89cea8d516b9b5a6fa85bfb19","d49a04f7cf7f64414ef67c1e113ec85a8fd683d6b102584dedd5b1737b8feccbca0a7280fed30f93ec68ee8db9a5195bc8f11627acc64cc8195641473fd0edf7",false -"7789778cfe9d60aa50c339a11b36ffbbc5d350c345ab14e56f207f430deafca5","031551b96dfef020553900283837e36f1f669dc1b7857129a66a23dc3cef7b04","19db69599941098c1f375b8608094dbad22d72e1fc62cd65285321a029b63de6","4ee81875e67869acd8d49b53dcdfeea2c64360b4470d548cd0a66916e354e565","db7aa4a8785c8cab4d42f6c1f282982818f5b528b3d6beb5b99486de031bf4fd","03ea829059d5d7d36ff9554b3718a0177a04de8e5ff33730c72e480477840042","b847785cfbdfc4cabab8c87499e50898834cf2f9fbd5407d3aeaccd6c234410d8824b1197afe7727177b54d6378c6e3dbc60fabdfc24b60f572ce0637b02089b","b847785cfbdfc4cabab8c87499e50898834cf2f9fbd5407d3aeaccd6c234410da2001a73143f80b336b2b05c3f95bbf88e8e6d9ff88783747f800203a4b84681",false -"2c42d0167ee5cfa88a0cc1c16fa0c9dc38c01d47f30e4c5423c707aecdd365aa","b97f1f0ce685329fce080aff4c9a17f3dfc6a14bfe9689290e9b89ec7f24bb5d","f06324b650ec674ccefa36c4e19c4e700fa09bedd1f7129c44745479dffab65d","0f638142959e8e6dc979685bb4292b646389db2ec418f87629bac97dc6c21478","f72df7b15f31c5abb0db5af07efc53199afe533af041512b47276d7ab90336be","6b117aabe372c146924929aa00990d7392eca59fc31b416a833a7e2a56ab73a8","f5b66cdfb1c916c9ceb705f8fceeee166d7aec4a71910562a1b19d4507f2518dedade54f20ed0f0345cc22a4374971ce64f4bb7cfd0e03574e0bab7f4dd1a051","f5b66cdfb1c916c9ceb705f8fceeee166d7aec4a71910562a1b19d4507f2518dfd4ac098d000a7b676d1ebdf55ad235d1002fc75da5f90f6c969b5923e0d2b35",true -"0570c938ab1e31af3244c1c8de80bfac01da36cb00708506aa9d6294b1020275","a162076078883fe9ce67f32a28eb637796fe5a20c3ee5a4dcf5795d54142152a","cb97db136151e7fd66b36622e2f6ac9f1244e951b5bf15f9a3f38aee73be4ce3","6ba7a5b30ac5bd0e5b7641830cb74367b41136823f8be7100e3c14d80b3eebcd","78ca0adc4d4d892c4bb8060c10b69d3b05630a5b5189877b9f20691897988174","4b8d63dc59a313c09548593d2002a5af4ca601b37a98bb299d8605062ea8c88a","febe05ae7e214eb33f6fabcf7bd4297b1b06e14f3bcb8170e3db5cc02aadb6d4538757ed880d9f67d61947202c7d6ba6e82ad5bf99f51b6c318009f26c59acf5","febe05ae7e214eb33f6fabcf7bd4297b1b06e14f3bcb8170e3db5cc02aadb6d41f1f3300e95f87653cccad430f7418473fc0e22aa06b912a15a136540fe1b897",false -"cfeb57aa91b08f7df563c54cd5c37fe9354c34c7a3a0942a73a1bd28d01ca785","6379d1dcf2da0e5c90e05d02b32a5c969dd21ea986f2df5a329ce2154c2f54fb","6c8f2a3b7bae9cd68637fbaac2fc1b62d5ecacd24ad97163659e7016ed79ad35","bcd420b56d103b29f50376335b959b76ff1102f319cc0e8c791615cac203c0fe","91d16e79b8869ae5d5cabfb4818405613bb9a89b552370309e44dc923c5071ae","fea792688415e4ea1927a8287dbf938aff1795c590cd89fc83a6190820f4a619","d5743de4c0be2194c9c3c7343e0e48a1f4a02627691933355f7ffcd5a2fcb0427bf317bebab7ded50ebf1b253845b43da1ad71cb5d0bd93f8fc895a7f2ebc3d5","d5743de4c0be2194c9c3c7343e0e48a1f4a02627691933355f7ffcd5a2fcb0420f63ed833f0941fe88871f7a754998dacbc0c4f9123267dc2a2a2591057216a0",true -"94c063c368f720e36575be96aa0075d73e669b9a0cb4188306d8bb4cb1cfb1e1","637cf87ec03dd68084474aa9f22bcc94a0985048d0a38b24e12b821f492ff4b5","c9fb6ba3efe3064bfd7625a53f254e90c59f35969ee3a65e110d6437fd24b435","c33096b024f6871aa9958e620501df9adc43760b6213e0a0783d143e1f36e8d7","68d398b891283b42e4bda770d3cb644be617e82a93cbd281ceae542d66e3bf55","54c1eec0ece9844fd5de9512e72b4e3452cf5472dc4934f0a7ef59323468d42a","e651742898c15685204152e9f98abb1d313ae35f4f2a156847de4ca9ce2fd9afd56205207bda7af8933d3ac71f5ef475aa03bbe8fe78733ba42f1271106a174d","e651742898c15685204152e9f98abb1d313ae35f4f2a156847de4ca9ce2fd9af9f5d70c46bbd814490b3606c5e844307b4f41498ee13795df56a181c3d588a41",false -"9e0278e0e6f8a0767dd81ecea025adf55dfaf913f9b4ff347115956fd0fb327d","535b5a59e5879920b490835ac9dbf23a82435bd083b2cc66be5be9997e2ce681","c47f351884757e03385b7c5a60d43895fa4b88dc9388d20c773c0aa6704b7ee0","93373b7570b569d3945cdc306b18b29b5f71a502f2636accb4a0b34be6d73203","8d96a34cf601b53ea3f0f1b0a5ca315f89f8d968f148c071560fca35ce97d1d4","e57adc1b2b869f8e07d97d2725fcffe19338c737a78c6d9112b34cbc5fa7c1b0","93f023cc065d5b422cbfe35b3a75e4a5aeb59b91ea500dac2cb95768519cb2b74b08551ea14588ec3a29e1d2d88d629721bbfbc7af3f5785dd7e41ca03f7d857","93f023cc065d5b422cbfe35b3a75e4a5aeb59b91ea500dac2cb95768519cb2b70f878a3725bb06ef72855e2d39619b2e6158a7bd937f895694e7ede3a40d15f6",false -"e29f4867a64d1199a19777f91c9ec2f9d15dd08a93409144ea0064cb60c76a3e","85524318955cb8c63933e6e0c06b8fe561861628548539bf760c2f1840cf8ae7","3b415bb6c72bce59fc88681de981ae96875d5cccb98755e195b3e4399340171c","f86e55a60819bc5b9ba93746753f4fc0db87c10de5b7d25f1fe242b3c1bb6ebd","1ec2865fb83a22cddf06a22491d861b685e987d115e89c4d49b93672944553df","fd6e924750d9359ff32feacc6e61991d6da690d312514b5b896bcb3365f05bdb","013a94ce22a0fed453e1e7959ca619d4a9676fe863aa3450cfd2081b9829b7cf69bee8aad31fb077fc38eeef227519f5a1ceb06692d362c11ab34351c8a15381","013a94ce22a0fed453e1e7959ca619d4a9676fe863aa3450cfd2081b9829b7cf2e7d8cf40bf3e21dffb086d138f36b5f1a715399d94c0cdf84ff5f1835613c65",true -"5745cbbd4a3f91aace64d6d5dfd81787b40c11f9c6a934bdf3fc80b2c8e28fb8","c99af68ee4b762410875e5bda87af74683541ed597966c64c96f8c486ff1f19a","627977f34498f34ed896fb5019fb9ffa2dc7dd238bf3777a3701186df35299c7","0a801b75ed3ae344de1b255f53930fc82fade6a0ba0e0b07d8b109a2b9cc3701","47406b77cca9234a06bdb4fc65ac76649e392ea780bdb81a6fc6cf3221f1354c","652839026578293d7ce1969b4d457739e722a2f5952857889da87a7d1b6f0f89","89a576153cf1bc3bc7ba7322b431a2c76851430fd4185d8369dc6ed24ef74495faca5ac0354f0a837a66214ea462f7f4e7212218776ca7ea37eb8b77ce53cd7e","89a576153cf1bc3bc7ba7322b431a2c76851430fd4185d8369dc6ed24ef744959850e2ccf0b61734a1cf25fe8a6757fab95944f4eb79307000ea7309db0133b7",true -"0c097d304c27a23e91e639407341eb33bfc90f74184130251bb0553dbe2de854","2b0868c00a5799f5686afe3550ca15d94ee2a6a81b1ecbda18e0735ca7ff7a56","6dc0f73722daa9eef9d0dcf931841a07f53c844b30a08c3f89898e619bc855f3","5c44f4f8428117f9ee1ad3711ec1120ba5a8545cc7d5aace491a215b49d617ec","52f98f0d574cae4db1cd30cb67a3e65c44dedb16f07c7bd728bf0fb83d0ad408","460d47e19edb129b81d62e6b59f76dd7bd4c61a725da3338d706c0e755ac2ae9","79beeded1b00c785dd0a466baa5b650f436ae169d0782b0d46baf5c481768c8e9c67dee427981c760ab8c6b90ce3d9f22d07007d16845eb9f80fe1dc948efbf7","79beeded1b00c785dd0a466baa5b650f436ae169d0782b0d46baf5c481768c8e0a28d61b4a72c6650489a3b23e67f3fb6794a7e197dc4abdc1c711b1602110a9",false -"b1b904d991387680de13c606565643e4930d2bf79b721db4530401c3b92cf3cf","63cdee00d9f07194399fe6518512073190b71dc2a94084681e49a172158f90ff","b6928d5994fa41ebd66a22a826cd6ad4015a23feab9ae45f1eaac309007fca73","38388ce16e2c5e1eab89b4e56233dacb58d4aa286ae2806d5debd117c9c529f9","6eab75f6bf9de09d363e4fc2447d0fc03feb78a5fb06ccb39daf648e13fd3045","41f2e0be320908f40428a195e471e425864ab7acbafdb11e642a68be1598c2c3","09cf78f977503f3a0e231d33155f1e65d100eedd0af68e5d40fa9e392138f2cb97acb68f70542aa1f6641f228d0df53b3a3930e82cac62d5cd466b89ca9049e4","09cf78f977503f3a0e231d33155f1e65d100eedd0af68e5d40fa9e392138f2cbe11a2935db59e8b61ff9fc7a66408a65f38de9d0305a1eb26e6e070d9a46c0b2",true -"7a444d39de9cf6852cf431a622af15acb852dce5034735166bdc4cccf6d984d3","52853e8d893caeb43c6ab322625641ccece988667bf9d64670a4df4750b83d7c","bf611892850c5e814215dfecd9629ed0de56376bab46fe361db5c0ba4fe916d8","103dd130009f424fdc25eeb5a1392a36fe4e008290267722ac24eb492db00ead","49a5fbb6bdb0cd167e5fb7b4b8544fcd87a3433e66292173c101efcd97756349","7666bd5dce4e4ad0df76672bcf6f8123adf4f5314894b808fbf739e3e3e687bd","6c9610699213a961ceb63a32459b5408c3a20035dc0c8b126b67d04bba9bce9abd8f6cb88cc6090f789d6726cda594f72d0335653a5b18c27655fb38dee47d56","6c9610699213a961ceb63a32459b5408c3a20035dc0c8b126b67d04bba9bce9afe2e542607b9aa8e36878739f442f625095bdae03e5cbac81872990b5f31a7bf",true -"c19d1686e76d5b777af0924e6154615abb7db030a22e407a397ce7e301d87742","0ba3c133ab7dee2839552fc9e43601ab3cc501c21891f2fd34fce78e43b63e67","2e5b908a7466490f23b30f5b75bbf10ba0d9c901a8fa1188f5ba6b1d07277174","5fba25cf7f5fb85e24900d14f789604dd0eca2eafa53ef2a41e55a79740709b0","ca543d2652c3ff13ac98641cb65df9dd0f719706363dcfa74592b373a638e308","da2cde0c16fd3cebf8c84ec28dbf1a951f43ff930795b610df8cec40749a25e2","4f9a66e8ccd5f1c68e906c7ffc8a5284065e2960a13c8af05fd671a4a2f8f93e39584c6b03ef20b2edf449b5f6fb7a00a42eadc9aba1f3652cd143d37651700a","4f9a66e8ccd5f1c68e906c7ffc8a5284065e2960a13c8af05fd671a4a2f8f93e0afcbbe08f88d7a3ca413a5a813f88f50354e4c802a7e1dc3716d8b66f29fe96",true -"29e362a7bfef5fb7f4918ec19e25befa8a4a43a23c8ec7c6028f97226773a7bd","2102f0eed266ed80ec7568cb20eeead902f0c35309f5ba7c1567b819a8e1bbb7","6c82609f56f6c11866cc83ee2498d551c17ae68d72904b4af73ff8e5f45c3284","f9f9be4fc4d63060a3c3c0656679f3d757775f8f8641a71f1525e53b57a1d99b","c78fc24af3b81ec075e1e1764b4d67ea07b5a9dda2e1504e159eb427025fb80b","15968a700ee7598db61ab47d210a27f7d0d82c5d25739b6141b095de9e00942d","676d626dad99593a9aaff30b64f38c513858b95a3f76269c211a1ff05525f39afe63ee86c0240d1f2bda706dcb386ab6b1a3d73cb44250f9fd409b764a4e2bc9","676d626dad99593a9aaff30b64f38c513858b95a3f76269c211a1ff05525f39a6ae64f26171ace3792a6f45befd14009b86fe0e37789fc0934ae35cf6e741d0c",false -"b01defc1ec85fdd6381f62ecb596239e91245b940b18489071526064a538baef","baab11fd4992c19e6a7692944ff665c08e1fb06c7a26b0f2ee2253fc9f7227e8","a0dfd48fa85f67b08b3fdbb9a6bb71c74b4239148f65f35caa37b1d1800fce44","025342259c0aa8df863a2f151ff0c2b1d6064997d1c54c42e5b62d033e83c7af","f5032f3bb2f6767a6f95b3559b74e14edde45ab47f8ff5c48542d748374c5144","08a35c8959bf4a10b85c75b7ef260d39f865876f4bdb66511b53dccf3f1a9353","3eaec73e10aa4b1f870d426dd0570dd5266ab32a93564393aad145dac625d9933033dc488afdcf7a9ac18515f96a4ac4e73db690bde3f49e2ce53141f437f0a8","3eaec73e10aa4b1f870d426dd0570dd5266ab32a93564393aad145dac625d993d113b0d8335d372b260160cfa025bc8c327fefa54d49e7fad71ce3137447beec",false -"dea7c5fe87a447632a53dcf40716ad99bd04d983405d851f44ff2671f8f8a6ea","07f04f60aa3367725401b8a813c0575007438714f3b6afa9150acdae4867f2f8","840da049a59cc5b8830e54aee306788b2b6104655f0a23a9404671f821fe9573","4ba30d52534bbed6bb67fa5e1bdfb1bdb983843a56887e43e652e89ccaae519e","d568dee1f5d4d1b400641fee5afd68e176e234acda24b0eb2516f739a8e87dcf","cab1f1f1229e93ad2efb76649c8091717ace3ba01461ac559295c5e0e006d07d","956bbdab0e1ebbbec714dd126a749b4f94bc7e55258eea4ce589e50ef0153370f1f321db528ee4f907e8b66903f080575efd5d11bb2518d9ec021e6b4c0e1156","956bbdab0e1ebbbec714dd126a749b4f94bc7e55258eea4ce589e50ef01533706de58191acf21f4084da61ba20ea07cc339c58ac5c1af530abbbac732a0f7be3",true -"17936333370f03b826196c144d2051b3da132aa00f8bb5c12482a577cad7c31f","54dbd2074d2787dc5a3e64f197bc7beb59987a4d3ed70fc818cd48ff08efeff1","059bcae22f27b9d0b66912406950cd96ac21c89b859b5f0fa401dc4ef9c6ff4d","951bf3b6eec23b6670b605a98998da6e38664d9f64cdd4f3573bc2b6f443b98e","28592ae85cb8d559e3bbe91793ad9c1463f03d7070d2e97d33b3c26d69d2fc82","2386074d572d93c77e096f6fc448521cc180f9257d9f3fb5347b7abaeb08c00c","4d081829ae9d9a9d560fa11e1122d780ef5c765719bb537d5daae655606d4814c38510e3df3375f4789be40145260d0aab790cc40e6c567e8face06b61ef0f7d","4d081829ae9d9a9d560fa11e1122d780ef5c765719bb537d5daae655606d4814c920dbc60e5b2fc52f04f641ae76daa1579ad55f9407b58e33aebcba5bb60eca",false +"01138d3785033d091a3fdb849d9a1291f5a6d1d24a81bdddf8881b10204e6136","02a13ad24a8a69cb200f25fbb73c2d3f403a0718ca4b02864b4c26b033cacfe2ff","665900ff1ca9e0bd80c12815122c6d928505e2b23e0d2121341fb1d9bf474f3c","03911b512d73c3602d8ae4455bee5cc82d442c00d9754b403f5b3eb83e98ed2ed9","a10616fb1ec1e3e212c30ccc0fbb5f8a3e37c9a821e35aed3e7431776ffd7c8b","eb41b70f96364551f2a8dea5177d8f7b0456dde9b21df232c9cfe1801b5d7646","99e9a06a6e865d6fa64aa4c9464e8215db7031d5a170433fe3b7d15ab5d10098fa3daffb44ed51687508d3e64825f8aaf7a4ae4ad6c57965d0caadc1e3f7b65d","99e9a06a6e865d6fa64aa4c9464e8215db7031d5a170433fe3b7d15ab5d100986096b0fa61973225f5c9fbfb5a52663ec1fbb4166589fa4b4518010ed308c458",false +"20778aa439316b57aa4637c831f974a3b87b2c4e6eb95f073fda0cfa79bbdaa3","02bf1d005c2b11c3bfac8f5d518ad7b4796b6dcc37f418dcc9ba79d22abca18746","86e8589b1adfe4421f98dd88031345a3b3d0c5bc92fd50c4c50df38d64afd274","0255ac067d96a6c45deef413141ae55a7a00ca277b13bee4bac667fced556ead18","dc909e9617b5e440b3e0a2e85a7c82cc2e31e3fc650a6b119e60c65e98f0118f","20eee2ddffddd43f02d884b1844ecc6e387cc25300d0f835ff4973b2126a514b","22e48a3678437d52795bd83000ff0a2510db0cd0de65d3c8b25b222b0632bebbd6d19cef77faa6e6c60412b3782f8092745486b49476472fc1410da6c3510f00","22e48a3678437d52795bd83000ff0a2510db0cd0de65d3c8b25b222b0632bebb4fe944545d1ac2a4a66b352b751c3aeec083c0f80178f66afc331a195ea13c8c",true +"7a34b0f93dbbd0c91b588a16fe4bd5d23f1903671f6de4403213cc3ce214ce38","027fb795e7e29f605bca05761496ef55de514d1d4cb67b1bee84597ded249ab024","6828ca09e3098a5a71b3be510794c1159c0eff6db384a830575c62a0006eaae0","033dd2db0ac9870447c63d4003eb183b0545fb86ccf929111666138c31bae62a28","be4a83480af33dd41908f009b2d9d4cb218d230acb7c1d48c40a97985e2bb2a4","4a1d8c39d92fa7405efe687837683cbc8e20f01621132161324e78d8b6c469f5","1021d59e8395ed425dc40625f2fea249549a86621a2edce57a114bbf8a1127622cea4b231d718eb55b2a253ce9dc23a1e76824d74ec8174951fed0fec49de82a","1021d59e8395ed425dc40625f2fea249549a86621a2edce57a114bbf8a112762c4c181193a68045ae97666ebe247628b060802504a8c0f54ba74cceb94657e8b",true +"547abc10d9ad870d554edc78028d21893f915d718b5d7849fa127a37213cddb7","0217f7e8f23c85208c10e06d55e90abafcd0bdbda49098a63a11cba7c1b16cc4b0","65c8f90e572f1828c167377f64f40796ee8aec5ce2baee05c762cf812f33883b","0269aa06097d53b56a77e39b46cde9b337084fb70a1f8c610518bb3e9c6b228445","a4c968687391d781c22729a941b6564df3c94a8eb9433f6b73f0eebba0e94318","d9fa09c670a30b031a1e0e3f72743506044f6b39a89e73c0bf87d6accd5b94ad","593acb43862897e5af5f5429e9e9142d09c75378ec70128f700c869cb6bacbfa0d527137644c31169cace2948c580d7a96347a6637b61f95ffcc6ec513b88d03","593acb43862897e5af5f5429e9e9142d09c75378ec70128f700c869cb6bacbfa731b6a45bb7b493f5e141a13f14c151184bf66c31a710d9bc72f3e4642ec153e",false +"593cc37fd28d28005adbedfcdecd18a071330e7ad7d853d738c77c61bbb83ff9","02c923dae361f1bc224a246a0592000c051b03b821ab26f5edbf69db6256d8a665","467b336868572cef5496b4f011ebb73839c7cf0c7763f1161eb1bfbdeec40d26","03d31be8ac7b423907dfe5c9834d3900247ac43b7dcadc8465ebc54b128c8910f5","81f3576f1a142901d056da8588b164a8464efa4f41cbceddccdc6f235ce49f59","86d81f4359857bbf39fbb128103d3bad5f46da4a52eea554f30d5954cbde7cb4","c435b682a7763d516da6e6483668e56fcef6ded57ccfd90ef225270d47cdc3f718482a85147e73108256664fd3eea00f5fd74042d8ceb331b678d631500b2712","c435b682a7763d516da6e6483668e56fcef6ded57ccfd90ef225270d47cdc3f7d1ccf71cac2746212dbfb15fc202e8d5e0be4e1d10b3625757997500317d5b2d",true +"2eba8f149e45f2af5c2afe5f51990d204cf240d22fc9e2078c48d480cf5ca6bf","02cd768be3e2d46d7734ebb40c87c08177b16abc092a6ae89d0aae0ff965dff8eb","ba297393b1c9aa874ba18f0351b56d52ead9a2d0a740be41779e9fe9cac59734","02b86184c2c2ab98441bb98c63bf0d98d38b8aa7e25ade0dcc116f7f6ff6c0a4db","47966fb320c357639f981bc883cbfc84d23153f09d88aa671a21a6199690ba8f","40fb47e380da85678b0ddd36c99c049f31c912ea83efbca9bc069a45151aeb4f","34bddf1a2bcc8893189cc3f719393b83d8077dbe85a8ce2bb02c4847815260550f31a7e7d3ef1661db2d70d2e408948fb21739ba17fe6a8f970559a0b4ba6bf0","34bddf1a2bcc8893189cc3f719393b83d8077dbe85a8ce2bb02c484781526055c95b1b7b85b8c0e926ceffd635be01e29cf0dc8abf3f28d10ea3f98a7f800324",false +"11ca0283f12577e4f37d0addd595c16a24ecba5ad9e6c2c6ea484525507ee953","028aad61e58c99b051ba6f95eb920e293d0f6d6fe4b9b1d55dff16b2ec368c587e","c6b5be059c076f99855f2854ac597e3d5a43310c6442592e1e5d1a8dbfaef62d","0317409274a1cf439bb11c3c0a01a7a7fbcec7ecefbee1330dc7fba93c530bc4ea","6fa2ad2be58417feca91d9c7d79b1077becd0c60a002be0e3fefaf86dc63a4cf","909676b82df8909a1a86b0a470f1fd5865a1fbc615381ead571b0b25c0b7da70","91d927a751000f0c7c72a76bf0ad15a315b131205ca964e03f8aec6925eadc8d07d3f00cad97c89b46a42fd84d47669ded22c11bd73e1ecc78fce1e2d77817bf","91d927a751000f0c7c72a76bf0ad15a315b131205ca964e03f8aec6925eadc8dce89ae12499f3834cc03582cf9a0e4db4765f2283b8077fa9759fc7097270dec",false +"29e17ae4aa2b13df44c3131c6c237c29be522e2a6eeb711a8d1f24eb137aaf0d","02edcdb8d9f4eaf8f2c9be1314886d56b1ff9f7a1892f86df38c366533356b5452","32f2f3121b5d265815c0527bea181b23e87e0d594e3af6d263eda72e7c72f645","032eba5528c77755be8ceb944dd1e886ae747c6e1390b90235941dc6e18ba7c9de","5be83e6ada36f33de34535159ab5c4fde57a60826eae6a20547a8a8203cdae8f","540c1ef21bd5a46c0a6e31a1922717aeda414903c07ed68060f17df40e7d29a0","ce472e6c3d7e75ec2683a295344c12890c2f99ddfab53a292ed1817e10f9f1069c2963013648549d786aeeca133da9ed186ff51e43a05eaa193f98743a955657","ce472e6c3d7e75ec2683a295344c12890c2f99ddfab53a292ed1817e10f9f10669366fef1aeb2e4562aa9c4e29258ec92ff1e7c4f56567d7b551f145be226012",true +"3504bee0a1d65cdbed68e8decfe9075dca5db9dc97b6aeff701ff6dac421c5c1","02c016e5f5b76175063f6336480320cd7dc23076a6d700ae34cfdb48268dac65b9","a2113b65d633a93e4196c3d68dadc7e227f8dd9d06e4d2bc5b5a3ac55ee567f5","02abbee001326b50704b9b17efcf98702dc74600749d0730328d2d5f04ccd2cde1","fdd6b40c8bb467cfc316d310ccdd0ef9ec6efd89932bccdfb8d1381e96ff5480","fbb700a915376fda2a157861af255025b510694919cba7fb5fd7a7f0744b65c1","e2b92af7dc9e15c4db7eae369fa8ae47283723930ccdd5b61848864cf7c9fffbc83aa93cdce4a1f0027b29fd82c5293c59bfa277c08ad4c48e4a243557e06265","e2b92af7dc9e15c4db7eae369fa8ae47283723930ccdd5b61848864cf7c9fffb26296dd706b0f8b1c0e46626f517615a31c6c4dab9a6020832efe96ff8fafa70",true +"84ca840ee3800300d023a01a439fbe62051efa7711781d5e17dccf24f8aba61c","026688fd24a8d11df5e166b14edab4083718859bd97928a70e05d8673ade2e8c16","fbfe17a13ae16e0adf77cf96e9af145d2fc02580ce19cf98023a3b5f9ad04f85","0221e822b2b559396c6eabdc9a5d2cff7c27861287c4ee0579a9ea435fa6b0b8d6","35cc81ff675a06d4042035116930fd52dc0a8512066e65fa454ac1f05eca8dc8","fc98beeab724efccb4ea955d51ce5cc300fad15e2811a7917fed46dc04639285","b42581ccd0779f6371209ff597344213e8e48851b6c95e80c51a75736f88d0ac078c830c97fd15e9d42ac9dc4038477df6dc171a862061bed25012d1541e4759","b42581ccd0779f6371209ff597344213e8e48851b6c95e80c51a75736f88d0ac0b8e6b6b5d1ba7def4b2fa455689331f81cace80674f32628fe835fe89843915",true +"79a7b3f13d359db306ea1778dd4c7b891793e5e57025b35e8ed7895d570b86ea","02ea1a0c4081c7a9f86352e00b135e6fd2bd0ad17c50fd22a9517472e62af05380","be025a17089052495761ef09365783b24ccf6844bf476ce54f445d0416f512b2","02987da0d941a03f75cdfe7582b90e58a80d0c80bf3073bdc42f8dc65b94da05e0","8f62d16c18211ee53a15f200c24a7e4020a04843f62c27e3ae263c1f795b5e61","a32a6d8dbd4976e31474a405592bf6280b799597e6e946203a4ac12062a29f06","010ac477501ecb0ef5b4bd8b81d1ab09de7fd1c790153e1af59101f53492a07fcdd686259bc74c3828a07a2149b8d2f3601c67133bac4672912021cff27bf547","010ac477501ecb0ef5b4bd8b81d1ab09de7fd1c790153e1af59101f53492a07f0fd42c0e9336f9eed13e8b1813614f41134cfece7c64d98d41dbc4cbdb86e295",true +"b12d7d24c6f0b0afd29b43cef391e02b5182356366344f60c12f361c44eb0c1b","02f2c0830b948cfc2b7ac9aa7c6390955e0d6e20fd67930e5f6a87b8904ee57ae0","1a41e58047f2ecb0a60028d491355b2c2dca60b9894d624332f2c707220117e4","027b6ee8e1bd3272edcc9ba857f0a1d677a455e8cb18258866508a591d9f4fda39","d48ae86c6552cd7c5d536cd4bd28520ff89fc328816fe2d4ccbdedbfbbed10d4","7e6aba5e4f518dcd4d8e1ba1289e3d5b2fef7c5e7b4b94b0b4afe38557f07bd2","19d508f771a4db7e5a1ccce0d5565686bf9c9ff506650c48c6c057c7e725042fbe9c7badacfdca549b4c484e712567233ea4a17021ceba8ac9e804d753baf13a","19d508f771a4db7e5a1ccce0d5565686bf9c9ff506650c48c6c057c7e725042fd8de612df4f0b705414c7123025ac24f6c6f0229ab1c1ccdfcdacbde75bc091e",false +"d671ee3655eb17d4512d896d851953ec8bbf5b8cc7bacd005e5d3b6442d264b3","0269aafad3bb6e1d2694a2570d7e1513e0154b1f1c7ad2ae9fa7d926bbefae213a","5152217b5100c40ebf949d863f101ad72e1d51e24be6b7edc222c26933b0e20c","02acdd14736689da6d7cd8f200ab26759ebf9c65ef1207d17488c3b3b82fecf52c","28c2b4a6f7c5e7499beb923caf7d92646c0b11af4af6873f0d52c9c10e8150c4","a5fcda2417c758bac50a05e2e29cea8ec69a166e70496ad6cdddcef0c23bc5a2","ada74b4cf2f6f67c91c8f3011a612cb44f3b3760bb139d9d80fa5cd7657d53bfb7877e86424e3faa3f123162cd0b4479027be74d40a9569de0da29402e22e0da","ada74b4cf2f6f67c91c8f3011a612cb44f3b3760bb139d9d80fa5cd7657d53bf08d9a001934f03b8fea6cee90c1b5f5175ea5c48dd476e4fe32a8d1c919d81a5",false +"29d8887c2b4dc6ad8b4e96dacd1798ad7bc1f67e458386e2577c13fce3faf282","02842cb699537d1a0d46c5728a7341890c767b17b38cd49c758874dbd65ec698c3","332bc36dfc79a7369eaf37bf1637db0b740cdd1079e212d1480178f20a3d0176","02cea3bc0b5ec890822f52e8eba87690d1d92012f86622229fb5537db321a371ed","8bc125e47d1b0593b721656d01dd4f1f68ddfc3b1a2758197569404d0bd0836c","00f1be297d9ee2b7559d61b8dc8e9a781366bec75e126acc53b8bd0e3c84755e","c4dc81aae55b1e645bc1d45f5b30e18079f36d27449268919ccf889f89b8844a28f4c4d83ca844df41f97544dc1e8ce70db632b388a96a2708cd0994be7882f0","c4dc81aae55b1e645bc1d45f5b30e18079f36d27449268919ccf889f89b8844a5c2088463921ec15e0a8ad03f25667f281c30fc4028b7cf850ce8286c8b58466",false +"feda1cf986c989c081e1c6dc8b434715ee2b00f28a0823577a56c8a64e979736","02eaba7f222004910e7be62ed1c8a78e9dca7e6b31fdd5c5e4d0751d239bd63a20","06ab57ab98caad717e6a53baf5d1202ed25b258dec3a96a32200ac9eb78d4fa6","023238fe1444f7038dbc56fdf20246ff3c133a1053caa9aea700a63fe8eea172d9","79d4fb0e147bdb23b4b82d73fcb937d63bd61d94e6ad469c77a0678a3e5c15ed","66a8ee476af678dc112ff3aef4388e3f26b68db2cbe7c545d40baf1d8062447b","1e03ce47176eca24579c88907f17acdedea160054115f59a744822b1a06d68fb87219dacf6d9e6afe333a79c40892b8b1ba7c7151711d655f02110ab3bef2a4b","1e03ce47176eca24579c88907f17acdedea160054115f59a744822b1a06d68fb8dccf5588fa49421619dfb57365a4bb9ee02eca3034c6cf91221bd49f37c79f1",false +"723ec1fac7ab8087de8f3d6cbcd7fce6ad497a32683406031efbbb534af5e4f0","02ad3e90516e2aa3a4d848ca71848f8bef22b6449ca8ff719346717c3d08c022a0","7454898bbb9418ad0287f9861b0b9245d4ba592fa2d263eae113ca5afda3a6ac","02eb538ee72c2672479361070b692f2bd05536e4f535431fed7167262d05ee6393","12432a48bd8f9d319c2673fdd7e16ddf60d2300b984da1f24fa53f1a0149f4b0","1174a245a991e949616646358d4bd79f561289343a9929a1adbf245c8efdba51","fbb9d7d05da1d35097619164867b20267a69171b8e7d2c47ea11a7076abbd2cffb74559a030c3d7c804af55698f18f1bb30dc4b707b2dae19033cb57b2c3ec4d","fbb9d7d05da1d35097619164867b20267a69171b8e7d2c47ea11a7076abbd2cf6fc8df25bea0562982d2eedcb3fd2162cd1940fffb3c9e90b1753725e03151b8",false +"ce1ed0fc2587a09dbbb3edbeb1e7d2260e694e47219ff0b5803e3633fb6e3b8b","0297fce00d0ae1a7ca4a21a248bbb56d02236b4d9071589dd83a7fda160cab6a17","0df5e4cd6a732400b909810cb2423c81231b42c1b8de953aa0abc93acccf79ef","02a08986f8334a064dd81f79e0d46a451395f901401e0dc9c3dfbe21fd6fd803fa","31432c7343d699c883557306ff017beae7ce991c525edacee0fae6cd77693f27","55bd3426ff7f94c16a439d1d7a3ae8975e2eb660ddf8695c1b1cfc0bcc711b8e","41d93b7c672c3e43dd2c8f220acb5ac7dc05a787f8eb51ba74a75272b68bf3fe5b5d97b2d663ad2c36466345d7508bc815034f9f98accdaedff225301f038efe","41d93b7c672c3e43dd2c8f220acb5ac7dc05a787f8eb51ba74a75272b68bf3fe69537c8040d6d12cef4fe4528992c849381e9261518b62e9809dee6aebd308ed",false +"a99059144b0309a1ae1c742225b10fdbbf3cdd4e89536f91c227d58175220dd4","021c840d44397a1598ffc1b967acadab2a49308ce4b2749725a3f0af7887e63f82","82e0a36bfc1d3c1fa16272abbde846f6573c7b50c1f1d1dbc45bd6d3f9237da8","03e1bc5c665861bc71da5e12509b96bcbae9f8f3faad490155fc55e00fa5313712","fd8d2ec5dcacef0072f4666268d0877c5a46f5863b4f82a579b4ccfda67c4ced","6a4c6660d79d46131e1605a25e9b8b17a8d3646e53b4d42bc49fafd26e1bef3a","662d14456a5ab31a1012cecc068b141ca8d41757e73ec703aacc955bb54e4e433560e1951c0277557426a7310dde160b8c01bc083f424e7631b844dedd38280d","662d14456a5ab31a1012cecc068b141ca8d41757e73ec703aacc955bb54e4e43b2803e291fe53b35d2c434854ff5cf13ef741d9e2c991cd62d2ecc97b44aeba6",true +"e761546d8eef5cd892957115b0e896fb066d28b14179dff8bef75735ed968011","02a09f5a0da4c7c66d5d83b6427e64100762c342ebfd8825eb8050bc6db0ffc333","a739f4b46219bd5b6f2b8d0c945ec0beee7591d2dcdc32b44853d60a6cc19476","026167160fd64b079f5b47735c2c5e27817a8cb877e3c03c940b9808b0d87ccaf7","4ff6144e72807179659e93c8b786042a5fd2bfb08785b293eae5833cf5b490ed","5d815476dca33ef58022bae294b784d0757c80ed49dc20fc86bf7375f345c30d","6e2c6e1ebb9e22e6592bb2f0ca126b09f167d1c0c29d6f8296fe65db07a5b69ea6cdc7f44c2f71bc54be5b8051a6ced0f36c0db67169485500c67e45bd6c19f5","6e2c6e1ebb9e22e6592bb2f0ca126b09f167d1c0c29d6f8296fe65db07a5b69e4e07bca8ae492f17c3e9e88ce6058f912732c2a29efcdacd8947f5c359f76d2a",false +"1fe1a3cd19735058b4517c13f7d225a0dd0b7176e8b970f72337dfbe92ea1394","026c605773a227b87c6bffad7adad8b8699a2b3b84d61a874e12bc7ddaf4051daa","c013f96c065555a9185ed60e3a37edaa9b37c83a9a87ea164075822c2e962414","0362f76db1fb4049445b220cde65785502e22aff575baa8444bdff70320a7137a6","77e0910f8dd5b3a54c54b11106323848e26dcd142209f16206735665ae740651","0430912f3219a070d6f14d8ed4bea2c961ef002efe1815cd1fec456562fec2af","2cf0452d6c63c38f4396099c1fe24843757c5acc83f9ed105e93f2ae11b65d217dc9110ff8c01b5a5b9b5e6259836902baaa6a7bcfb9dd7571680cb5b9bfb8e2","2cf0452d6c63c38f4396099c1fe24843757c5acc83f9ed105e93f2ae11b65d21bdb517a3f26ac5b1433c88541f4b7b56da217f27e47a939af0c4e9165b5fd60f",true From 932687b1f887d05d1f15c53dea556233b06e3ee6 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Tue, 31 Jan 2023 23:03:19 -0800 Subject: [PATCH 19/63] do not assume tweak is even in tests --- test/secp256k1/schnorr_adaptor_test_vectors.csv | 1 + test/secp256k1/schnorr_test.exs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/test/secp256k1/schnorr_adaptor_test_vectors.csv b/test/secp256k1/schnorr_adaptor_test_vectors.csv index 2fae775..1d77803 100644 --- a/test/secp256k1/schnorr_adaptor_test_vectors.csv +++ b/test/secp256k1/schnorr_adaptor_test_vectors.csv @@ -1,3 +1,4 @@ +private_key_hex,public_key_sec,tweak_secret,tweak_point_sec,message_hash,aux_rand,untweaked_adaptor_signature,tweaked_signature,was_negated "01138d3785033d091a3fdb849d9a1291f5a6d1d24a81bdddf8881b10204e6136","02a13ad24a8a69cb200f25fbb73c2d3f403a0718ca4b02864b4c26b033cacfe2ff","665900ff1ca9e0bd80c12815122c6d928505e2b23e0d2121341fb1d9bf474f3c","03911b512d73c3602d8ae4455bee5cc82d442c00d9754b403f5b3eb83e98ed2ed9","a10616fb1ec1e3e212c30ccc0fbb5f8a3e37c9a821e35aed3e7431776ffd7c8b","eb41b70f96364551f2a8dea5177d8f7b0456dde9b21df232c9cfe1801b5d7646","99e9a06a6e865d6fa64aa4c9464e8215db7031d5a170433fe3b7d15ab5d10098fa3daffb44ed51687508d3e64825f8aaf7a4ae4ad6c57965d0caadc1e3f7b65d","99e9a06a6e865d6fa64aa4c9464e8215db7031d5a170433fe3b7d15ab5d100986096b0fa61973225f5c9fbfb5a52663ec1fbb4166589fa4b4518010ed308c458",false "20778aa439316b57aa4637c831f974a3b87b2c4e6eb95f073fda0cfa79bbdaa3","02bf1d005c2b11c3bfac8f5d518ad7b4796b6dcc37f418dcc9ba79d22abca18746","86e8589b1adfe4421f98dd88031345a3b3d0c5bc92fd50c4c50df38d64afd274","0255ac067d96a6c45deef413141ae55a7a00ca277b13bee4bac667fced556ead18","dc909e9617b5e440b3e0a2e85a7c82cc2e31e3fc650a6b119e60c65e98f0118f","20eee2ddffddd43f02d884b1844ecc6e387cc25300d0f835ff4973b2126a514b","22e48a3678437d52795bd83000ff0a2510db0cd0de65d3c8b25b222b0632bebbd6d19cef77faa6e6c60412b3782f8092745486b49476472fc1410da6c3510f00","22e48a3678437d52795bd83000ff0a2510db0cd0de65d3c8b25b222b0632bebb4fe944545d1ac2a4a66b352b751c3aeec083c0f80178f66afc331a195ea13c8c",true "7a34b0f93dbbd0c91b588a16fe4bd5d23f1903671f6de4403213cc3ce214ce38","027fb795e7e29f605bca05761496ef55de514d1d4cb67b1bee84597ded249ab024","6828ca09e3098a5a71b3be510794c1159c0eff6db384a830575c62a0006eaae0","033dd2db0ac9870447c63d4003eb183b0545fb86ccf929111666138c31bae62a28","be4a83480af33dd41908f009b2d9d4cb218d230acb7c1d48c40a97985e2bb2a4","4a1d8c39d92fa7405efe687837683cbc8e20f01621132161324e78d8b6c469f5","1021d59e8395ed425dc40625f2fea249549a86621a2edce57a114bbf8a1127622cea4b231d718eb55b2a253ce9dc23a1e76824d74ec8174951fed0fec49de82a","1021d59e8395ed425dc40625f2fea249549a86621a2edce57a114bbf8a112762c4c181193a68045ae97666ebe247628b060802504a8c0f54ba74cceb94657e8b",true diff --git a/test/secp256k1/schnorr_test.exs b/test/secp256k1/schnorr_test.exs index 228d9c1..fe4a8aa 100644 --- a/test/secp256k1/schnorr_test.exs +++ b/test/secp256k1/schnorr_test.exs @@ -165,7 +165,6 @@ defmodule Bitcoinex.Secp256k1.SchnorrTest do # tweak tweak_int = :rand.uniform(@n - 1) {:ok, tweak} = PrivateKey.new(tweak_int) - tweak = Secp256k1.force_even_y(tweak) tweak_point = PrivateKey.to_point(tweak) msg = :rand.uniform(@n - 1) |> :binary.encode_unsigned() From 92ae2b43c1c1d2e6ff22191ba8838a499a9d80aa Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Wed, 1 Feb 2023 01:19:49 -0800 Subject: [PATCH 20/63] add key-only taproot script creation --- lib/script.ex | 27 +++++- lib/utils.ex | 2 + test/script_test.exs | 223 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 251 insertions(+), 1 deletion(-) diff --git a/lib/script.ex b/lib/script.ex index 342a65d..05d5843 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -5,7 +5,7 @@ defmodule Bitcoinex.Script do import Bitcoinex.Opcode - alias Bitcoinex.Secp256k1.Point + alias Bitcoinex.Secp256k1.{Point, PrivateKey, Math} alias Bitcoinex.{Utils, Address, Segwit, Base58, Network} @@ -611,6 +611,31 @@ defmodule Bitcoinex.Script do def create_p2tr(q = %Point{}), do: create_witness_scriptpubkey(1, Point.x_bytes(q)) def create_p2tr(_), do: {:error, "public key must be #{@tapkey_length}-bytes"} + @spec create_p2tr_key_only(<<_::256>> | Point.t()) :: + {:ok, Bitcoinex.Script.t()} | {:error, String.t()} + def create_p2tr_key_only(<>) do + # Q = P + (int(hash_TapTweak(P)) * G) + fake_s = + pk + |> Utils.tagged_hash_taptweak() + |> :binary.decode_unsigned() + |> PrivateKey.new() + pk = Point.lift_x(pk) + + case {fake_s, pk} do + {{:error, msg}, _} -> {:error, msg} + {_, {:error, msg}} -> {:error, msg} + + {{:ok, fake_s}, {:ok, pk}} -> + fake_s_point = PrivateKey.to_point(fake_s) + pk + |> Math.add(fake_s_point) + |> create_p2tr() + end + end + def create_p2tr_key_only(p = %Point{}), do: create_p2tr_key_only(Point.x_bytes(p)) + + @doc """ create_p2sh_p2wpkh creates a p2wsh script using the passed 20-byte public key hash """ diff --git a/lib/utils.ex b/lib/utils.ex index 54d5dbf..804391e 100644 --- a/lib/utils.ex +++ b/lib/utils.ex @@ -13,6 +13,8 @@ defmodule Bitcoinex.Utils do sha256(tag_hash <> tag_hash <> str) end + def tagged_hash_taptweak(pkx), do: tagged_hash("TapTweak", pkx) + @spec replicate(term(), integer()) :: list(term()) def replicate(_num, 0) do [] diff --git a/test/script_test.exs b/test/script_test.exs index d7072cb..77441da 100644 --- a/test/script_test.exs +++ b/test/script_test.exs @@ -162,6 +162,229 @@ defmodule Bitcoinex.ScriptTest do "bc1gmk9yu" ] + # from https://github.com/bitcoin/bips/blob/master/bip-0341/wallet-test-vectors.json + @bip_341_script_pubkey_test_vectors [ + %{ + given: %{ + internalPubkey: "d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d", + scriptTree: nil + }, + intermediary: %{ + merkleRoot: nil, + tweak: "b86e7be8f39bab32a6f2c0443abbc210f0edac0e2c53d501b36b64437d9c6c70", + tweakedPubkey: "53a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343" + }, + expected: %{ + scriptPubKey: "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343", + bip350Address: "bc1p2wsldez5mud2yam29q22wgfh9439spgduvct83k3pm50fcxa5dps59h4z5" + } + }, + %{ + given: %{ + internalPubkey: "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27", + scriptTree: %{ + id: 0, + script: "20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac", + leafVersion: 192 + } + }, + intermediary: %{ + leafHashes: [ + "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21" + ], + merkleRoot: "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21", + tweak: "cbd8679ba636c1110ea247542cfbd964131a6be84f873f7f3b62a777528ed001", + tweakedPubkey: "147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3" + }, + expected: %{ + scriptPubKey: "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", + bip350Address: "bc1pz37fc4cn9ah8anwm4xqqhvxygjf9rjf2resrw8h8w4tmvcs0863sa2e586", + scriptPathControlBlocks: [ + "c1187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27" + ] + } + }, + %{ + given: %{ + internalPubkey: "93478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820", + scriptTree: %{ + id: 0, + script: "20b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007ac", + leafVersion: 192 + } + }, + intermediary: %{ + leafHashes: [ + "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b" + ], + merkleRoot: "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b", + tweak: "6af9e28dbf9d6aaf027696e2598a5b3d056f5fd2355a7fd5a37a0e5008132d30", + tweakedPubkey: "e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e" + }, + expected: %{ + scriptPubKey: "5120e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e", + bip350Address: "bc1punvppl2stp38f7kwv2u2spltjuvuaayuqsthe34hd2dyy5w4g58qqfuag5", + scriptPathControlBlocks: [ + "c093478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820" + ] + } + }, + %{ + given: %{ + internalPubkey: "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592", + scriptTree: [ + %{ + id: 0, + script: "20387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48ac", + leafVersion: 192 + }, + %{ + id: 1, + script: "06424950333431", + leafVersion: 250 + } + ] + }, + intermediary: %{ + leafHashes: [ + "8ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7", + "f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a" + ], + merkleRoot: "6c2dc106ab816b73f9d07e3cd1ef2c8c1256f519748e0813e4edd2405d277bef", + tweak: "9e0517edc8259bb3359255400b23ca9507f2a91cd1e4250ba068b4eafceba4a9", + tweakedPubkey: "712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5" + }, + expected: %{ + scriptPubKey: "5120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5", + bip350Address: "bc1pwyjywgrd0ffr3tx8laflh6228dj98xkjj8rum0zfpd6h0e930h6saqxrrm", + scriptPathControlBlocks: [ + "c0ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a", + "faee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf37865928ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7" + ] + } + }, + %{ + given: %{ + internalPubkey: "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8", + scriptTree: [ + %{ + id: 0, + script: "2044b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fdac", + leafVersion: 192 + }, + %{ + id: 1, + script: "07546170726f6f74", + leafVersion: 192 + } + ] + }, + intermediary: %{ + leafHashes: [ + "64512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89", + "2cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb" + ], + merkleRoot: "ab179431c28d3b68fb798957faf5497d69c883c6fb1e1cd9f81483d87bac90cc", + tweak: "639f0281b7ac49e742cd25b7f188657626da1ad169209078e2761cefd91fd65e", + tweakedPubkey: "77e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220" + }, + expected: %{ + scriptPubKey: "512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220", + bip350Address: "bc1pwl3s54fzmk0cjnpl3w9af39je7pv5ldg504x5guk2hpecpg2kgsqaqstjq", + scriptPathControlBlocks: [ + "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd82cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb", + "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd864512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89" + ] + } + }, + %{ + given: %{ + internalPubkey: "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f", + scriptTree: [ + %{ + id: 0, + script: "2072ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69ac", + leafVersion: 192 + }, + [ + %{ + id: 1, + script: "202352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8ac", + leafVersion: 192 + }, + %{ + id: 2, + script: "207337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186aac", + leafVersion: 192 + } + ] + ] + }, + intermediary: %{ + leafHashes: [ + "2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817", + "ba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c", + "9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf6" + ], + merkleRoot: "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2", + tweak: "b57bfa183d28eeb6ad688ddaabb265b4a41fbf68e5fed2c72c74de70d5a786f4", + tweakedPubkey: "91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605" + }, + expected: %{ + scriptPubKey: "512091b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605", + bip350Address: "bc1pjxmy65eywgafs5tsunw95ruycpqcqnev6ynxp7jaasylcgtcxczs6n332e", + scriptPathControlBlocks: [ + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fffe578e9ea769027e4f5a3de40732f75a88a6353a09d767ddeb66accef85e553", + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf62645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817", + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817" + ] + } + }, + %{ + given: %{ + internalPubkey: "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d", + scriptTree: [ + %{ + id: 0, + script: "2071981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2ac", + leafVersion: 192 + }, + [ + %{ + id: 1, + script: "20d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748ac", + leafVersion: 192 + }, + %{ + id: 2, + script: "20c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4cac", + leafVersion: 192 + } + ] + ] + }, + intermediary: %{ + leafHashes: [ + "f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d", + "737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711", + "d7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7" + ], + merkleRoot: "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def", + tweak: "6579138e7976dc13b6a92f7bfd5a2fc7684f5ea42419d43368301470f3b74ed9", + tweakedPubkey: "75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831" + }, + expected: %{ + scriptPubKey: "512075169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831", + bip350Address: "bc1pw5tf7sqp4f50zka7629jrr036znzew70zxyvvej3zrpf8jg8hqcssyuewe", + scriptPathControlBlocks: [ + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d3cd369a528b326bc9d2133cbd2ac21451acb31681a410434672c8e34fe757e91", + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312dd7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d", + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d" + ] + } + } + ] + describe "test basics functions" do test "test new/0 and empty?/1" do s = Script.new() From f9054d262982658c0348d2eb569694ffd8d6e061 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Fri, 3 Feb 2023 17:41:28 -0800 Subject: [PATCH 21/63] add Taproot.build_control_block and merkle tree handling --- lib/psbt.ex | 9 +- lib/script.ex | 147 +++++++------ lib/taproot.ex | 161 ++++++++++++++ lib/transaction.ex | 39 ++-- lib/utils.ex | 48 ++++- test/script_test.exs | 491 ++++++++++++++++++++++-------------------- test/taproot_test.exs | 288 +++++++++++++++++++++++++ 7 files changed, 845 insertions(+), 338 deletions(-) create mode 100644 lib/taproot.ex create mode 100644 test/taproot_test.exs diff --git a/lib/psbt.ex b/lib/psbt.ex index e0553f4..f3fdef4 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -12,6 +12,7 @@ defmodule Bitcoinex.PSBT do alias Bitcoinex.PSBT.In alias Bitcoinex.PSBT.Out alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.Utils @type t() :: %__MODULE__{} @@ -99,6 +100,7 @@ defmodule Bitcoinex.PSBT.Utils do Contains utility functions used throughout PSBT serialization. """ alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.Utils def parse_compact_size_value(key_value) do {len, key_value} = TxUtils.get_counter(key_value) @@ -123,8 +125,8 @@ defmodule Bitcoinex.PSBT.Utils do end def serialize_kv(key, val) do - key_len = TxUtils.serialize_compact_size_unsigned_int(byte_size(key)) - val_len = TxUtils.serialize_compact_size_unsigned_int(byte_size(val)) + key_len = Utils.serialize_compact_size_unsigned_int(byte_size(key)) + val_len = Utils.serialize_compact_size_unsigned_int(byte_size(val)) key_len <> key <> val_len <> val end end @@ -258,6 +260,7 @@ defmodule Bitcoinex.PSBT.In do alias Bitcoinex.PSBT.In alias Bitcoinex.PSBT.Utils, as: PsbtUtils alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.Utils defstruct [ :non_witness_utxo, @@ -299,7 +302,7 @@ defmodule Bitcoinex.PSBT.In do val = <> <> - TxUtils.serialize_compact_size_unsigned_int(byte_size(script)) <> script + Utils.serialize_compact_size_unsigned_int(byte_size(script)) <> script PsbtUtils.serialize_kv(<<@psbt_in_witness_utxo::big-size(8)>>, val) end diff --git a/lib/script.ex b/lib/script.ex index 05d5843..cb17357 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -5,15 +5,18 @@ defmodule Bitcoinex.Script do import Bitcoinex.Opcode - alias Bitcoinex.Secp256k1.{Point, PrivateKey, Math} + alias Bitcoinex.Secp256k1.{Point, Math, PrivateKey} - alias Bitcoinex.{Utils, Address, Segwit, Base58, Network} + alias Bitcoinex.{Utils, Address, Segwit, Base58, Network, Taproot} @wsh_length 32 @tapkey_length 32 @h160_length 20 @pubkey_lengths [33, 65] + # hash of G.x, used to construct unsolvable internal taproot keys + @h 0x0250929B74C1A04954B78B4B6035E97A5E078A5A0F28EC96D547BFEE9ACE803AC0 + @type script_type :: :p2pk | :p2pkh | :p2sh | :p2wpkh | :p2wsh | :p2tr | :multi | :non_standard @type t :: %__MODULE__{ @@ -25,7 +28,7 @@ defmodule Bitcoinex.Script do ] defstruct [:items] - defguard is_valid_multi(m, pubkeys) + defguard is_valid_multisig(m, pubkeys) when is_integer(m) and m > 0 and length(pubkeys) > 0 and length(pubkeys) >= m defp invalid_opcode_error(msg), do: {:error, "invalid opcode: #{msg}"} @@ -204,6 +207,11 @@ defmodule Bitcoinex.Script do serializer(script, <<>>) end + def serialize_with_compact_size(script = %__MODULE__{}) do + s = serialize_script(script) + Utils.serialize_compact_size_unsigned_int(byte_size(s)) <> s + end + @doc """ to_hex returns the hex of a serialized script. """ @@ -386,36 +394,36 @@ defmodule Bitcoinex.Script do def is_p2tr?(%__MODULE__{}), do: false @doc """ - is_multi? returns whether a given script is of the raw multisig format: + is_multisig? returns whether a given script is of the raw multisig format: OP_(INT) [Public Keys] OP_(INT) OP_CHECKMULTISIG """ - @spec is_multi?(t()) :: boolean - def is_multi?(%__MODULE__{items: [op_m | rest]}) + @spec is_multisig?(t()) :: boolean + def is_multisig?(%__MODULE__{items: [op_m | rest]}) when op_m > 0x50 and op_m <= 0x60 and length(rest) > 3 do - test_multi(rest, 0, op_m) + test_multisig(rest, 0, op_m) end - def is_multi?(_), do: false + def is_multisig?(_), do: false - defp test_multi([op_n, 0xAE], n, m) when op_n == 0x50 + n and m <= op_n, do: true + defp test_multisig([op_n, 0xAE], n, m) when op_n == 0x50 + n and m <= op_n, do: true - defp test_multi([op_push | [pk | rest]], n, m) when op_push in @pubkey_lengths do + defp test_multisig([op_push | [pk | rest]], n, m) when op_push in @pubkey_lengths do case Point.parse_public_key(pk) do - {:ok, _pk} -> test_multi(rest, n + 1, m) + {:ok, _pk} -> test_multisig(rest, n + 1, m) {:error, _msg} -> false end end - defp test_multi(_, _, _), do: false + defp test_multisig(_, _, _), do: false @doc """ - extract_multi_policy takes in a raw multisig script and returns the m, the + extract_multisig_policy takes in a raw multisig script and returns the m, the number of signatures required and the n authorized public keys. """ - @spec extract_multi_policy(t()) :: + @spec extract_multisig_policy(t()) :: {:ok, non_neg_integer(), list(Point.t())} | {:error, String.t()} - def extract_multi_policy(script = %__MODULE__{items: [op_m | items]}) do - if is_multi?(script) do + def extract_multisig_policy(script = %__MODULE__{items: [op_m | items]}) do + if is_multisig?(script) do {:ok, op_m - 0x50, extractor(items, [])} else {:error, "invalid raw multisig script"} @@ -445,7 +453,7 @@ defmodule Bitcoinex.Script do is_p2wsh?(script) -> :p2wsh is_p2pk?(script) -> :p2pk is_p2tr?(script) -> :p2tr - is_multi?(script) -> :multi + is_multisig?(script) -> :multi true -> :non_standard end end @@ -501,44 +509,44 @@ defmodule Bitcoinex.Script do end @doc """ - create_multi creates a raw multisig script using m and the list of public keys. + create_multisig creates a raw multisig script using m and the list of public keys. """ - @spec create_multi(non_neg_integer(), list(Point.t())) :: {:ok, t()} | {:error, String.t()} - def create_multi(m, pubkeys) when is_valid_multi(m, pubkeys) do + @spec create_multisig(non_neg_integer(), list(Point.t())) :: {:ok, t()} | {:error, String.t()} + def create_multisig(m, pubkeys) when is_valid_multisig(m, pubkeys) do try do # checkmultisig {:ok, s} = push_op(new(), 0xAE) {:ok, s} = push_op(s, 0x50 + length(pubkeys)) - s = fill_multi_keys(s, pubkeys) + s = fill_multisig_keys(s, pubkeys) push_op(s, 0x50 + m) rescue _ -> {:error, "invalid public key."} end end - def create_multi(_, _), do: {:error, "invalid multisig: must be of form: (int, list(%Point)"} + def create_multisig(_, _), do: {:error, "invalid multisig: must be of form: (int, list(%Point)"} - defp fill_multi_keys(s, []), do: s + defp fill_multisig_keys(s, []), do: s - defp fill_multi_keys(s, [pk = %Point{} | pubkeys]) do - {:ok, s} = push_data(fill_multi_keys(s, pubkeys), Point.sec(pk)) + defp fill_multisig_keys(s, [pk = %Point{} | pubkeys]) do + {:ok, s} = push_data(fill_multisig_keys(s, pubkeys), Point.sec(pk)) s end - defp fill_multi_keys(_, _), do: raise(ArgumentError) + defp fill_multisig_keys(_, _), do: raise(ArgumentError) @doc """ - create_p2sh_multi returns both a P2SH-wrapped multisig script + create_p2sh_multisig returns both a P2SH-wrapped multisig script and the underlying raw multisig script using m and the list of public keys. """ - @spec create_p2sh_multi(non_neg_integer(), list(Point.t())) :: + @spec create_p2sh_multisig(non_neg_integer(), list(Point.t())) :: {:ok, t(), t()} | {:error, String.t()} - def create_p2sh_multi(m, pubkeys) do - case create_multi(m, pubkeys) do - {:ok, multi} -> - h160 = hash160(multi) + def create_p2sh_multisig(m, pubkeys) do + case create_multisig(m, pubkeys) do + {:ok, multisig} -> + h160 = hash160(multisig) {:ok, p2sh} = create_p2sh(h160) - {:ok, p2sh, multi} + {:ok, p2sh, multisig} {:error, msg} -> {:error, msg} @@ -546,17 +554,17 @@ defmodule Bitcoinex.Script do end @doc """ - create_p2wsh_multi returns both a P2WSH-wrapped multisig script + create_p2wsh_multisig returns both a P2WSH-wrapped multisig script and the underlying raw multisig script using m and the list of public keys. """ - @spec create_p2wsh_multi(non_neg_integer(), list(Point.t())) :: + @spec create_p2wsh_multisig(non_neg_integer(), list(Point.t())) :: {:ok, t(), t()} | {:error, String.t()} - def create_p2wsh_multi(m, pubkeys) do - case create_multi(m, pubkeys) do - {:ok, multi} -> - h256 = sha256(multi) + def create_p2wsh_multisig(m, pubkeys) do + case create_multisig(m, pubkeys) do + {:ok, multisig} -> + h256 = sha256(multisig) {:ok, p2wsh} = create_p2wsh(h256) - {:ok, p2wsh, multi} + {:ok, p2wsh, multisig} {:error, msg} -> {:error, msg} @@ -603,38 +611,37 @@ defmodule Bitcoinex.Script do @doc """ create_p2tr creates a p2tr script using the passed 32-byte public key - or Point. If a point is passed, it's interpreted as q, the full witness - program or taproot output key per BIP 341 rather than the keyspend pubkey. - """ - @spec create_p2tr(binary | Point.t()) :: {:ok, t()} - def create_p2tr(<>), do: create_witness_scriptpubkey(1, pk) - def create_p2tr(q = %Point{}), do: create_witness_scriptpubkey(1, Point.x_bytes(q)) - def create_p2tr(_), do: {:error, "public key must be #{@tapkey_length}-bytes"} - - @spec create_p2tr_key_only(<<_::256>> | Point.t()) :: - {:ok, Bitcoinex.Script.t()} | {:error, String.t()} - def create_p2tr_key_only(<>) do - # Q = P + (int(hash_TapTweak(P)) * G) - fake_s = - pk - |> Utils.tagged_hash_taptweak() - |> :binary.decode_unsigned() - |> PrivateKey.new() - pk = Point.lift_x(pk) - - case {fake_s, pk} do - {{:error, msg}, _} -> {:error, msg} - {_, {:error, msg}} -> {:error, msg} - - {{:ok, fake_s}, {:ok, pk}} -> - fake_s_point = PrivateKey.to_point(fake_s) - pk - |> Math.add(fake_s_point) - |> create_p2tr() - end + or Point. If a point is passed, it's interpreted as p, the internal key. + If only p is passed, the script_tree is assumed to be empty. + """ + @spec create_p2tr(<<_::256>> | Point.t(), Taproot.script_tree()) :: + {:ok, Bitcoinex.Script.t()} + def create_p2tr(p, script_tree \\ nil) + def create_p2tr(p = %Point{}, script_tree), do: create_p2tr(Point.x_bytes(p), script_tree) + + def create_p2tr(<>, script_tree) do + {_, hash} = Taproot.merkelize_script_tree(script_tree) + {:ok, p} = Point.lift_x(px) + q = Taproot.tweak_pubkey(p, hash) + create_witness_scriptpubkey(1, Point.x_bytes(q)) end - def create_p2tr_key_only(p = %Point{}), do: create_p2tr_key_only(Point.x_bytes(p)) + @spec create_p2tr_script_only(Taproot.script_tree(), non_neg_integer()) :: + {:ok, Bitcoinex.Script.t()} + def create_p2tr_script_only(script_tree, r) do + case PrivateKey.new(r) do + {:error, msg} -> + {:error, msg} + + {:ok, sk} -> + {:ok, hk} = Point.lift_x(@h) + + sk + |> PrivateKey.to_point() + |> Math.add(hk) + |> create_p2tr(script_tree) + end + end @doc """ create_p2sh_p2wpkh creates a p2wsh script using the passed 20-byte public key hash diff --git a/lib/taproot.ex b/lib/taproot.ex new file mode 100644 index 0000000..168c87e --- /dev/null +++ b/lib/taproot.ex @@ -0,0 +1,161 @@ +defmodule Bitcoinex.Taproot do + alias Bitcoinex.Utils + + alias Bitcoinex.Secp256k1 + alias Bitcoinex.Secp256k1.{Math, Params, Point, PrivateKey} + + @n Params.curve().n + + # @bip342_leaf_version 0xc0 + + @type tapnode :: {tapnode, tapnode} | TapLeaf.t() | nil + + @spec tweak_privkey(PrivateKey.t(), binary) :: PrivateKey.t() | {:error, String.t()} + def tweak_privkey(sk0 = %PrivateKey{}, h) do + sk = Secp256k1.force_even_y(sk0) + + case PrivateKey.to_point(sk) do + {:error, msg} -> + {:error, msg} + + pk -> + t = calculate_taptweak(pk, h) + + if t > @n do + {:error, "invalid tweaked key"} + else + %PrivateKey{d: Math.modulo(sk.d + t, @n)} + end + end + end + + @spec tweak_pubkey(Point.t(), binary) :: Point.t() | {:error, String.t()} + def tweak_pubkey(pk = %Point{}, h) do + t = calculate_taptweak(pk, h) + + if t > @n do + {:error, "invalid tweaked key"} + else + t_point = PrivateKey.to_point(t) + Math.add(pk, t_point) + end + end + + @spec calculate_taptweak(Point.t(), binary) :: non_neg_integer + def calculate_taptweak(pk = %Point{}, h) do + pk + |> Point.x_bytes() + |> Kernel.<>(h) + |> tagged_hash_taptweak() + |> :binary.decode_unsigned() + end + + @spec tagged_hash_tapbranch(binary) :: binary + def tagged_hash_tapbranch(br), do: Utils.tagged_hash("TapBranch", br) + + @spec tagged_hash_taptweak(binary) :: binary + def tagged_hash_taptweak(root), do: Utils.tagged_hash("TapTweak", root) + + @spec tagged_hash_tapleaf(binary) :: binary + def tagged_hash_tapleaf(leaf), do: Utils.tagged_hash("TapLeaf", leaf) + + defmodule TapLeaf do + alias Bitcoinex.Script + alias Bitcoinex.Taproot + + @type t :: %__MODULE__{ + version: non_neg_integer(), + script: binary + } + @enforce_keys [ + :version, + :script + ] + defstruct [ + :version, + :script + ] + + @doc """ + from_script constructs a TapLeaf from a leaf_version and Script. + The script is stored as binary with the compact size prepended to it. + """ + @spec from_script(non_neg_integer(), Script.t()) :: t() + def from_script(leaf_version, script = %Script{}) do + s = Script.serialize_with_compact_size(script) + %__MODULE__{version: leaf_version, script: s} + end + + @spec from_string(non_neg_integer(), String.t()) :: t() + def from_string(leaf_version, script_hex) do + {:ok, script} = Script.parse_script(script_hex) + from_script(leaf_version, script) + end + + @doc """ + serialize returns a binary concatenation of the leaf_version and Script. TapLeaf structs + store the Script in binary and alredy prepended with the compact size, so that is not added here. + """ + @spec serialize(t()) :: binary + def serialize(%__MODULE__{version: v, script: s}), do: :binary.encode_unsigned(v) <> s + + @doc """ + hash returns the Hash_TapLeaf of the serialized TapLeaf + """ + @spec hash(t()) :: <<_::256>> + def hash(tapleaf = %__MODULE__{}), do: serialize(tapleaf) |> Taproot.tagged_hash_tapleaf() + end + + @typedoc """ + script_tree represents a Taproot Script Merkle Tree. Leaves are represented by TapLeaf structs + while branches are {script_tree, script_tree}. Since we sort based on hash at each level, + left vs right branches are irrelevant. An empty tree is represented by nil. + """ + @type script_tree :: TapLeaf.t() | {script_tree(), script_tree()} | nil + + @doc """ + merkelize_script_tree takes a script_tree (either nil, a TapLeaf, or a tuple of two script_trees) + and constructs the root node. It returns {root_node, hash}. The hash is nil if the script_tree is empty. + defined in BIP341 https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs + """ + @spec merkelize_script_tree(script_tree()) :: {list({TapLeaf.t(), binary}), binary} + def merkelize_script_tree(nil), do: {nil, <<>>} + + def merkelize_script_tree(leaf = %TapLeaf{}) do + hash = TapLeaf.hash(leaf) + + {[{leaf, <<>>}], hash} + end + + def merkelize_script_tree({left, right}) do + {{l_branches, l_hash}, {r_branches, r_hash}} = + {merkelize_script_tree(left), merkelize_script_tree(right)} + + # cross-mix the right hash with left branch and left hash with right branch + new_left = merge_branches(l_branches, r_hash) + new_right = merge_branches(r_branches, l_hash) + + node = new_left ++ new_right + + # combine the branches to form root node. + {l_hash, r_hash} = Utils.lexicographical_sort(l_hash, r_hash) + hash = tagged_hash_tapbranch(l_hash <> r_hash) + {node, hash} + end + + defp merge_branches([], _), do: [] + + defp merge_branches([{leaf, c} | tail], hash) do + [{leaf, c <> hash} | merge_branches(tail, hash)] + end + + @spec build_control_block(Point.t(), script_tree(), non_neg_integer()) :: binary + def build_control_block(p = %Point{}, script_tree, script_index) do + {tree, hash} = merkelize_script_tree(script_tree) + {tapleaf, merkle_path} = Enum.at(tree, script_index) + q = tweak_pubkey(p, hash) + q_parity = if Point.has_even_y(q), do: 0, else: 1 + + <> <> Point.x_bytes(p) <> merkle_path + end +end diff --git a/lib/transaction.ex b/lib/transaction.ex index 1956d03..8817a10 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -114,6 +114,7 @@ defmodule Bitcoinex.Transaction.Utils do alias Bitcoinex.Transaction.In alias Bitcoinex.Transaction.Out alias Bitcoinex.Transaction.Witness + alias Bitcoinex.Utils @doc """ Returns the Variable Length Integer used in serialization. @@ -149,9 +150,9 @@ defmodule Bitcoinex.Transaction.Utils do version = <> marker = <<0x00::big-size(8)>> flag = <<0x01::big-size(8)>> - tx_in_count = serialize_compact_size_unsigned_int(length(txn.inputs)) + tx_in_count = Utils.serialize_compact_size_unsigned_int(length(txn.inputs)) inputs = In.serialize_inputs(txn.inputs) |> :erlang.list_to_binary() - tx_out_count = serialize_compact_size_unsigned_int(length(txn.outputs)) + tx_out_count = Utils.serialize_compact_size_unsigned_int(length(txn.outputs)) outputs = Out.serialize_outputs(txn.outputs) |> :erlang.list_to_binary() witness = Witness.serialize_witness(txn.witnesses) lock_time = <> @@ -162,33 +163,14 @@ defmodule Bitcoinex.Transaction.Utils do def serialize(txn) do version = <> - tx_in_count = serialize_compact_size_unsigned_int(length(txn.inputs)) + tx_in_count = Utils.serialize_compact_size_unsigned_int(length(txn.inputs)) inputs = In.serialize_inputs(txn.inputs) |> :erlang.list_to_binary() - tx_out_count = serialize_compact_size_unsigned_int(length(txn.outputs)) + tx_out_count = Utils.serialize_compact_size_unsigned_int(length(txn.outputs)) outputs = Out.serialize_outputs(txn.outputs) |> :erlang.list_to_binary() lock_time = <> version <> tx_in_count <> inputs <> tx_out_count <> outputs <> lock_time end - - @doc """ - Returns the serialized variable length integer. - """ - def serialize_compact_size_unsigned_int(compact_size) do - cond do - compact_size >= 0 and compact_size <= 0xFC -> - <> - - compact_size <= 0xFFFF -> - <<0xFD>> <> <> - - compact_size <= 0xFFFFFFFF -> - <<0xFE>> <> <> - - compact_size <= 0xFF -> - <<0xFF>> <> <> - end - end end defmodule Bitcoinex.Transaction.Witness do @@ -197,6 +179,7 @@ defmodule Bitcoinex.Transaction.Witness do """ alias Bitcoinex.Transaction.Witness alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.Utils @type t :: %__MODULE__{ txinwitness: list(binary()) @@ -237,12 +220,12 @@ defmodule Bitcoinex.Transaction.Witness do if Enum.empty?(witness.txinwitness) do <<0x0::big-size(8)>> else - stack_len = TxUtils.serialize_compact_size_unsigned_int(length(witness.txinwitness)) + stack_len = Utils.serialize_compact_size_unsigned_int(length(witness.txinwitness)) field = Enum.reduce(witness.txinwitness, <<>>, fn v, acc -> {:ok, item} = Base.decode16(v, case: :lower) - item_len = TxUtils.serialize_compact_size_unsigned_int(byte_size(item)) + item_len = Utils.serialize_compact_size_unsigned_int(byte_size(item)) acc <> item_len <> item end) @@ -295,6 +278,7 @@ defmodule Bitcoinex.Transaction.In do """ alias Bitcoinex.Transaction.In alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.Utils @type t :: %__MODULE__{ prev_txid: binary(), @@ -330,7 +314,7 @@ defmodule Bitcoinex.Transaction.In do {:ok, script_sig} = Base.decode16(input.script_sig, case: :lower) - script_len = TxUtils.serialize_compact_size_unsigned_int(byte_size(script_sig)) + script_len = Utils.serialize_compact_size_unsigned_int(byte_size(script_sig)) serialized_input = [ prev_txid, @@ -377,6 +361,7 @@ defmodule Bitcoinex.Transaction.Out do """ alias Bitcoinex.Transaction.Out alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.Utils @type t :: %__MODULE__{ value: non_neg_integer(), @@ -400,7 +385,7 @@ defmodule Bitcoinex.Transaction.Out do {:ok, script_pub_key} = Base.decode16(output.script_pub_key, case: :lower) - script_len = TxUtils.serialize_compact_size_unsigned_int(byte_size(script_pub_key)) + script_len = Utils.serialize_compact_size_unsigned_int(byte_size(script_pub_key)) serialized_output = [<>, script_len, script_pub_key] serialize_output(outputs, [serialized_outputs, serialized_output]) diff --git a/lib/utils.ex b/lib/utils.ex index 804391e..22137c5 100644 --- a/lib/utils.ex +++ b/lib/utils.ex @@ -13,8 +13,6 @@ defmodule Bitcoinex.Utils do sha256(tag_hash <> tag_hash <> str) end - def tagged_hash_taptweak(pkx), do: tagged_hash("TapTweak", pkx) - @spec replicate(term(), integer()) :: list(term()) def replicate(_num, 0) do [] @@ -109,4 +107,50 @@ defmodule Bitcoinex.Utils do |> Enum.map(fn {b0, b1} -> Bitwise.bxor(b0, b1) end) |> :binary.list_to_bin() end + + # ascending order + def lexicographical_sort(bin0, bin1) when is_binary(bin0) and is_binary(bin1) do + if lexicographical_sort(:binary.bin_to_list(bin0), :binary.bin_to_list(bin1)) do + {bin0, bin1} + else + {bin1, bin0} + end + end + + # equality case + def lexicographical_sort([], []), do: true + + def lexicographical_sort([b0 | r0], [b1 | r1]) do + cond do + b0 == b1 -> + lexicographical_sort(r0, r1) + + b1 < b0 -> + # initial order was incorrect, must be swapped + false + + true -> + # bin0, bin1 was the correct order + true + end + end + + @doc """ + Returns the serialized variable length integer. + """ + def serialize_compact_size_unsigned_int(compact_size) do + cond do + compact_size >= 0 and compact_size <= 0xFC -> + <> + + compact_size <= 0xFFFF -> + <<0xFD>> <> <> + + compact_size <= 0xFFFFFFFF -> + <<0xFE>> <> <> + + compact_size <= 0xFF -> + <<0xFF>> <> <> + end + end end diff --git a/test/script_test.exs b/test/script_test.exs index 77441da..9f2dad6 100644 --- a/test/script_test.exs +++ b/test/script_test.exs @@ -4,6 +4,7 @@ defmodule Bitcoinex.ScriptTest do alias Bitcoinex.{Script, Utils} alias Bitcoinex.Secp256k1.Point + alias Bitcoinex.Taproot @raw_multisig_scripts [ "522103a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af957521036ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640d210311ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef53ae", @@ -166,226 +167,249 @@ defmodule Bitcoinex.ScriptTest do @bip_341_script_pubkey_test_vectors [ %{ given: %{ - internalPubkey: "d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d", - scriptTree: nil + internal_pubkey: "d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d", + script_tree: nil }, intermediary: %{ - merkleRoot: nil, - tweak: "b86e7be8f39bab32a6f2c0443abbc210f0edac0e2c53d501b36b64437d9c6c70", - tweakedPubkey: "53a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343" + merkle_root: nil, + tweak: "b86e7be8f39bab32a6f2c0443abbc210f0edac0e2c53d501b36b64437d9c6c70", + tweaked_pubkey: "53a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343" }, expected: %{ - scriptPubKey: "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343", - bip350Address: "bc1p2wsldez5mud2yam29q22wgfh9439spgduvct83k3pm50fcxa5dps59h4z5" + script_pubkey: "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343", + bip350_address: "bc1p2wsldez5mud2yam29q22wgfh9439spgduvct83k3pm50fcxa5dps59h4z5" } - }, - %{ + }, + %{ given: %{ - internalPubkey: "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27", - scriptTree: %{ - id: 0, - script: "20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac", - leafVersion: 192 - } + internal_pubkey: "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27", + script_tree: + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac" + ) }, intermediary: %{ - leafHashes: [ - "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21" - ], - merkleRoot: "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21", - tweak: "cbd8679ba636c1110ea247542cfbd964131a6be84f873f7f3b62a777528ed001", - tweakedPubkey: "147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3" + leaf_hashes: [ + "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21" + ], + merkle_root: "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21", + tweak: "cbd8679ba636c1110ea247542cfbd964131a6be84f873f7f3b62a777528ed001", + tweaked_pubkey: "147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3" }, expected: %{ - scriptPubKey: "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", - bip350Address: "bc1pz37fc4cn9ah8anwm4xqqhvxygjf9rjf2resrw8h8w4tmvcs0863sa2e586", - scriptPathControlBlocks: [ - "c1187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27" - ] + script_pubkey: "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", + bip350_address: "bc1pz37fc4cn9ah8anwm4xqqhvxygjf9rjf2resrw8h8w4tmvcs0863sa2e586", + script_path_control_blocks: [ + "c1187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27" + ] } - }, - %{ + }, + %{ given: %{ - internalPubkey: "93478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820", - scriptTree: %{ - id: 0, - script: "20b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007ac", - leafVersion: 192 - } + internal_pubkey: "93478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820", + script_tree: + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "20b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007ac" + ) }, intermediary: %{ - leafHashes: [ - "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b" - ], - merkleRoot: "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b", - tweak: "6af9e28dbf9d6aaf027696e2598a5b3d056f5fd2355a7fd5a37a0e5008132d30", - tweakedPubkey: "e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e" + leaf_hashes: [ + "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b" + ], + merkle_root: "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b", + tweak: "6af9e28dbf9d6aaf027696e2598a5b3d056f5fd2355a7fd5a37a0e5008132d30", + tweaked_pubkey: "e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e" }, expected: %{ - scriptPubKey: "5120e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e", - bip350Address: "bc1punvppl2stp38f7kwv2u2spltjuvuaayuqsthe34hd2dyy5w4g58qqfuag5", - scriptPathControlBlocks: [ - "c093478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820" - ] + script_pubkey: "5120e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e", + bip350_address: "bc1punvppl2stp38f7kwv2u2spltjuvuaayuqsthe34hd2dyy5w4g58qqfuag5", + script_path_control_blocks: [ + "c093478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820" + ] } - }, - %{ + }, + %{ given: %{ - internalPubkey: "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592", - scriptTree: [ - %{ - id: 0, - script: "20387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48ac", - leafVersion: 192 - }, - %{ - id: 1, - script: "06424950333431", - leafVersion: 250 - } - ] + internal_pubkey: "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592", + script_tree: { + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "20387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48ac" + ), + Taproot.TapLeaf.from_string( + # id: 1, + # version + 250, + # script + "06424950333431" + ) + } }, intermediary: %{ - leafHashes: [ - "8ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7", - "f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a" - ], - merkleRoot: "6c2dc106ab816b73f9d07e3cd1ef2c8c1256f519748e0813e4edd2405d277bef", - tweak: "9e0517edc8259bb3359255400b23ca9507f2a91cd1e4250ba068b4eafceba4a9", - tweakedPubkey: "712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5" + leaf_hashes: [ + "8ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7", + "f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a" + ], + merkle_root: "6c2dc106ab816b73f9d07e3cd1ef2c8c1256f519748e0813e4edd2405d277bef", + tweak: "9e0517edc8259bb3359255400b23ca9507f2a91cd1e4250ba068b4eafceba4a9", + tweaked_pubkey: "712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5" }, expected: %{ - scriptPubKey: "5120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5", - bip350Address: "bc1pwyjywgrd0ffr3tx8laflh6228dj98xkjj8rum0zfpd6h0e930h6saqxrrm", - scriptPathControlBlocks: [ - "c0ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a", - "faee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf37865928ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7" - ] + script_pubkey: "5120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5", + bip350_address: "bc1pwyjywgrd0ffr3tx8laflh6228dj98xkjj8rum0zfpd6h0e930h6saqxrrm", + script_path_control_blocks: [ + "c0ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a", + "faee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf37865928ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7" + ] } - }, - %{ + }, + %{ given: %{ - internalPubkey: "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8", - scriptTree: [ - %{ - id: 0, - script: "2044b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fdac", - leafVersion: 192 - }, - %{ - id: 1, - script: "07546170726f6f74", - leafVersion: 192 - } - ] + internal_pubkey: "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8", + script_tree: { + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "2044b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fdac" + ), + Taproot.TapLeaf.from_string( + # id: 1, + # version + 192, + # script + "07546170726f6f74" + ) + } }, intermediary: %{ - leafHashes: [ - "64512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89", - "2cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb" - ], - merkleRoot: "ab179431c28d3b68fb798957faf5497d69c883c6fb1e1cd9f81483d87bac90cc", - tweak: "639f0281b7ac49e742cd25b7f188657626da1ad169209078e2761cefd91fd65e", - tweakedPubkey: "77e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220" + leaf_hashes: [ + "64512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89", + "2cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb" + ], + merkle_root: "ab179431c28d3b68fb798957faf5497d69c883c6fb1e1cd9f81483d87bac90cc", + tweak: "639f0281b7ac49e742cd25b7f188657626da1ad169209078e2761cefd91fd65e", + tweaked_pubkey: "77e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220" }, expected: %{ - scriptPubKey: "512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220", - bip350Address: "bc1pwl3s54fzmk0cjnpl3w9af39je7pv5ldg504x5guk2hpecpg2kgsqaqstjq", - scriptPathControlBlocks: [ - "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd82cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb", - "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd864512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89" - ] + script_pubkey: "512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220", + bip350_address: "bc1pwl3s54fzmk0cjnpl3w9af39je7pv5ldg504x5guk2hpecpg2kgsqaqstjq", + script_path_control_blocks: [ + "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd82cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb", + "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd864512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89" + ] } - }, - %{ + }, + %{ given: %{ - internalPubkey: "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f", - scriptTree: [ - %{ - id: 0, - script: "2072ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69ac", - leafVersion: 192 - }, - [ - %{ - id: 1, - script: "202352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8ac", - leafVersion: 192 - }, - %{ - id: 2, - script: "207337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186aac", - leafVersion: 192 - } - ] - ] + internal_pubkey: "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f", + script_tree: { + Taproot.TapLeaf.from_string( + # id: 0, + 192, + # script + "2072ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69ac" + ), + { + Taproot.TapLeaf.from_string( + # id: 1, + 192, + # script + "202352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8ac" + ), + Taproot.TapLeaf.from_string( + # id: 2, + 192, + # script + "207337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186aac" + ) + } + } }, intermediary: %{ - leafHashes: [ - "2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817", - "ba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c", - "9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf6" - ], - merkleRoot: "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2", - tweak: "b57bfa183d28eeb6ad688ddaabb265b4a41fbf68e5fed2c72c74de70d5a786f4", - tweakedPubkey: "91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605" + leaf_hashes: [ + "2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817", + "ba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c", + "9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf6" + ], + merkle_root: "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2", + tweak: "b57bfa183d28eeb6ad688ddaabb265b4a41fbf68e5fed2c72c74de70d5a786f4", + tweaked_pubkey: "91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605" }, expected: %{ - scriptPubKey: "512091b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605", - bip350Address: "bc1pjxmy65eywgafs5tsunw95ruycpqcqnev6ynxp7jaasylcgtcxczs6n332e", - scriptPathControlBlocks: [ - "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fffe578e9ea769027e4f5a3de40732f75a88a6353a09d767ddeb66accef85e553", - "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf62645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817", - "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817" - ] + script_pubkey: "512091b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605", + bip350_address: "bc1pjxmy65eywgafs5tsunw95ruycpqcqnev6ynxp7jaasylcgtcxczs6n332e", + script_path_control_blocks: [ + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fffe578e9ea769027e4f5a3de40732f75a88a6353a09d767ddeb66accef85e553", + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf62645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817", + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817" + ] } - }, - %{ + }, + %{ given: %{ - internalPubkey: "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d", - scriptTree: [ - %{ - id: 0, - script: "2071981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2ac", - leafVersion: 192 - }, - [ - %{ - id: 1, - script: "20d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748ac", - leafVersion: 192 - }, - %{ - id: 2, - script: "20c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4cac", - leafVersion: 192 - } - ] - ] + internal_pubkey: "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d", + script_tree: { + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "2071981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2ac" + ), + { + Taproot.TapLeaf.from_string( + # id: 1, + # version + 192, + # script + "20d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748ac" + ), + Taproot.TapLeaf.from_string( + # id: 2, + # version + 192, + # script + "20c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4cac" + ) + } + } }, intermediary: %{ - leafHashes: [ - "f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d", - "737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711", - "d7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7" - ], - merkleRoot: "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def", - tweak: "6579138e7976dc13b6a92f7bfd5a2fc7684f5ea42419d43368301470f3b74ed9", - tweakedPubkey: "75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831" + leaf_hashes: [ + "f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d", + "737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711", + "d7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7" + ], + merkle_root: "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def", + tweak: "6579138e7976dc13b6a92f7bfd5a2fc7684f5ea42419d43368301470f3b74ed9", + tweaked_pubkey: "75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831" }, expected: %{ - scriptPubKey: "512075169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831", - bip350Address: "bc1pw5tf7sqp4f50zka7629jrr036znzew70zxyvvej3zrpf8jg8hqcssyuewe", - scriptPathControlBlocks: [ - "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d3cd369a528b326bc9d2133cbd2ac21451acb31681a410434672c8e34fe757e91", - "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312dd7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d", - "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d" - ] + script_pubkey: "512075169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831", + bip350_address: "bc1pw5tf7sqp4f50zka7629jrr036znzew70zxyvvej3zrpf8jg8hqcssyuewe", + script_path_control_blocks: [ + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d3cd369a528b326bc9d2133cbd2ac21451acb31681a410434672c8e34fe757e91", + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312dd7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d", + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d" + ] } - } + } ] - describe "test basics functions" do + describe "test basic functions" do test "test new/0 and empty?/1" do s = Script.new() assert Script.empty?(s) @@ -720,35 +744,14 @@ defmodule Bitcoinex.ScriptTest do assert p2wpkh == p2wpkh2 end - test "test create_p2tr" do - s = Script.new() - script_hex = "51200101010101010101010101010101010101010101010101010101010101010101" - pubkey_hex = "0101010101010101010101010101010101010101010101010101010101010101" - bin = Base.decode16!(pubkey_hex, case: :lower) - - {:ok, p2tr} = Script.push_data(s, bin) - {:ok, p2tr} = Script.push_op(p2tr, 0x51) - assert Script.is_p2tr?(p2tr) - - s_hex = Script.to_hex(p2tr) - assert s_hex == script_hex - - {:ok, p2tr_from_bin} = Script.create_p2tr(bin) - assert Script.to_hex(p2tr_from_bin) == script_hex - - {:ok, pubkey} = Point.lift_x(bin) - {:ok, p2tr_from_pk} = Script.create_p2tr(pubkey) - assert Script.to_hex(p2tr_from_pk) == script_hex - end - - test "test is_multi?" do + test "test is_multisig?" do for ms <- @raw_multisig_scripts do {:ok, multi} = Script.parse_script(ms) - assert Script.is_multi?(multi) + assert Script.is_multisig?(multi) end end - test "test create_multi" do + test "test create_multisig" do # these keys will be added to multisig as compressed keys {:ok, pk1} = Point.parse_public_key( @@ -765,8 +768,8 @@ defmodule Bitcoinex.ScriptTest do "0411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e83" ) - {:ok, multi} = Script.create_multi(2, [pk1, pk2, pk3]) - assert Script.is_multi?(multi) + {:ok, multisig} = Script.create_multisig(2, [pk1, pk2, pk3]) + assert Script.is_multisig?(multisig) {:ok, pk1} = Point.parse_public_key( @@ -783,24 +786,24 @@ defmodule Bitcoinex.ScriptTest do "02a5264fbc1be9b9a7d03d9637a5534ce8d59a06c4c1f30802fe52e7bf6c1dd971" ) - {:ok, multi} = Script.create_multi(1, [pk1, pk2, pk3]) - assert Script.is_multi?(multi) + {:ok, multi} = Script.create_multisig(1, [pk1, pk2, pk3]) + assert Script.is_multisig?(multi) - {:ok, multi} = Script.create_multi(2, [pk1, pk2, pk3]) - assert Script.is_multi?(multi) + {:ok, multi} = Script.create_multisig(2, [pk1, pk2, pk3]) + assert Script.is_multisig?(multi) - {:ok, multi} = Script.create_multi(3, [pk1, pk2, pk3]) - assert Script.is_multi?(multi) + {:ok, multi} = Script.create_multisig(3, [pk1, pk2, pk3]) + assert Script.is_multisig?(multi) # m cant be greater than n in m-of-n - {:error, _msg} = Script.create_multi(4, [pk1, pk2, pk3]) + {:error, _msg} = Script.create_multisig(4, [pk1, pk2, pk3]) # m cant be 0 - {:error, _msg} = Script.create_multi(0, [pk1, pk2, pk3]) + {:error, _msg} = Script.create_multisig(0, [pk1, pk2, pk3]) # m cant be greater than n in m-of-n - {:error, _msg} = Script.create_multi(0, []) + {:error, _msg} = Script.create_multisig(0, []) end - test "test create_p2sh_multi" do + test "test create_p2sh_multisig" do {:ok, pk1} = Point.parse_public_key( "04a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd" @@ -816,9 +819,9 @@ defmodule Bitcoinex.ScriptTest do "0411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e83" ) - {:ok, p2sh, multi} = Script.create_p2sh_multi(2, [pk1, pk2, pk3]) + {:ok, p2sh, multi} = Script.create_p2sh_multisig(2, [pk1, pk2, pk3]) assert Script.is_p2sh?(p2sh) - assert Script.is_multi?(multi) + assert Script.is_multisig?(multi) {:ok, pk1} = Point.parse_public_key( @@ -835,19 +838,19 @@ defmodule Bitcoinex.ScriptTest do "02a5264fbc1be9b9a7d03d9637a5534ce8d59a06c4c1f30802fe52e7bf6c1dd971" ) - {:ok, p2sh, multi} = Script.create_p2sh_multi(2, [pk1, pk2, pk3]) + {:ok, p2sh, multi} = Script.create_p2sh_multisig(2, [pk1, pk2, pk3]) assert Script.is_p2sh?(p2sh) - assert Script.is_multi?(multi) + assert Script.is_multisig?(multi) # m cant be greater than n in m-of-n - {:error, _msg} = Script.create_p2sh_multi(4, [pk1, pk2, pk3]) + {:error, _msg} = Script.create_p2sh_multisig(4, [pk1, pk2, pk3]) # m cant be 0 - {:error, _msg} = Script.create_p2sh_multi(0, [pk1, pk2, pk3]) + {:error, _msg} = Script.create_p2sh_multisig(0, [pk1, pk2, pk3]) # m cant be greater than n in m-of-n - {:error, _msg} = Script.create_p2sh_multi(0, []) + {:error, _msg} = Script.create_p2sh_multisig(0, []) end - test "test create_p2wsh_multi" do + test "test create_p2wsh_multisig" do {:ok, pk1} = Point.parse_public_key( "04a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd" @@ -863,9 +866,9 @@ defmodule Bitcoinex.ScriptTest do "0411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e83" ) - {:ok, p2wsh, multi} = Script.create_p2wsh_multi(2, [pk1, pk2, pk3]) + {:ok, p2wsh, multi} = Script.create_p2wsh_multisig(2, [pk1, pk2, pk3]) assert Script.is_p2wsh?(p2wsh) - assert Script.is_multi?(multi) + assert Script.is_multisig?(multi) {:ok, pk1} = Point.parse_public_key( @@ -882,16 +885,16 @@ defmodule Bitcoinex.ScriptTest do "02a5264fbc1be9b9a7d03d9637a5534ce8d59a06c4c1f30802fe52e7bf6c1dd971" ) - {:ok, p2wsh, multi} = Script.create_p2wsh_multi(2, [pk1, pk2, pk3]) + {:ok, p2wsh, multi} = Script.create_p2wsh_multisig(2, [pk1, pk2, pk3]) assert Script.is_p2wsh?(p2wsh) - assert Script.is_multi?(multi) + assert Script.is_multisig?(multi) # m cant be greater than n in m-of-n - {:error, _msg} = Script.create_p2wsh_multi(4, [pk1, pk2, pk3]) + {:error, _msg} = Script.create_p2wsh_multisig(4, [pk1, pk2, pk3]) # m cant be 0 - {:error, _msg} = Script.create_p2wsh_multi(0, [pk1, pk2, pk3]) + {:error, _msg} = Script.create_p2wsh_multisig(0, [pk1, pk2, pk3]) # m cant be greater than n in m-of-n - {:error, _msg} = Script.create_p2wsh_multi(0, []) + {:error, _msg} = Script.create_p2wsh_multisig(0, []) end test "test create scripts from pubkey" do @@ -1233,7 +1236,7 @@ defmodule Bitcoinex.ScriptTest do test "test serialize/parse multisig scripts" do for ms <- @raw_multisig_scripts do {:ok, multi} = Script.parse_script(ms) - assert Script.is_multi?(multi) + assert Script.is_multisig?(multi) assert Script.to_hex(multi) == ms end end @@ -1380,10 +1383,10 @@ defmodule Bitcoinex.ScriptTest do test "test raw multisig to address" do for multi <- @raw_multisigs_with_data do {:ok, ms} = Script.parse_script(multi.script_hex) - {:ok, m, pks} = Script.extract_multi_policy(ms) + {:ok, m, pks} = Script.extract_multisig_policy(ms) if multi.sh_addr != "" do - {:ok, p2sh, multi_script} = Script.create_p2sh_multi(m, pks) + {:ok, p2sh, multi_script} = Script.create_p2sh_multisig(m, pks) {:ok, addr} = Script.to_address(p2sh, multi.network) @@ -1392,7 +1395,7 @@ defmodule Bitcoinex.ScriptTest do end if multi.wsh_addr != "" do - {:ok, p2wsh, multi_script} = Script.create_p2wsh_multi(m, pks) + {:ok, p2wsh, multi_script} = Script.create_p2wsh_multisig(m, pks) {:ok, addr} = Script.to_address(p2wsh, multi.network) @@ -1530,16 +1533,16 @@ defmodule Bitcoinex.ScriptTest do test "extract policy from multisig script" do for multi <- @raw_multisigs_with_data do {:ok, ms} = Script.parse_script(multi.script_hex) - {:ok, m, pks} = Script.extract_multi_policy(ms) - {:ok, ms2} = Script.create_multi(m, pks) + {:ok, m, pks} = Script.extract_multisig_policy(ms) + {:ok, ms2} = Script.create_multisig(m, pks) assert ms == ms2 end for m <- @raw_multisig_scripts do {:ok, ms} = Script.parse_script(m) - {:ok, m, pks} = Script.extract_multi_policy(ms) - {:ok, ms2} = Script.create_multi(m, pks) + {:ok, m, pks} = Script.extract_multisig_policy(ms) + {:ok, ms2} = Script.create_multisig(m, pks) assert ms == ms2 end @@ -1588,4 +1591,20 @@ defmodule Bitcoinex.ScriptTest do assert Bitcoinex.Secp256k1.Point.sec(pubkey3) == pk3 end end + + describe "test bip341 create_p2tr" do + test "test bip341 test vectors" do + for t <- @bip_341_script_pubkey_test_vectors do + {:ok, p} = Point.lift_x(t.given.internal_pubkey) + # TODO move to Taproot_test + # {_node, hash} = Taproot.merkelize_script_tree(t.script_tree) + # assert Base.encode16(hash, case: :lower) == t.intermediary.merkle_root + # TODO check tweak & tweak_pubkey + + {:ok, script} = Script.create_p2tr(p, t.given.script_tree) + assert Script.to_hex(script) == t.expected.script_pubkey + assert Script.to_address(script, :mainnet) == {:ok, t.expected.bip350_address} + end + end + end end diff --git a/test/taproot_test.exs b/test/taproot_test.exs new file mode 100644 index 0000000..11640ef --- /dev/null +++ b/test/taproot_test.exs @@ -0,0 +1,288 @@ +defmodule TaprootTest do + use ExUnit.Case + doctest Bitcoinex.Script + + alias Bitcoinex.Taproot + alias Bitcoinex.Secp256k1.Point + + @bip_341_script_pubkey_test_vectors [ + %{ + given: %{ + internal_pubkey: "d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d", + script_tree: nil + }, + intermediary: %{ + merkle_root: <<>>, + tweak: "b86e7be8f39bab32a6f2c0443abbc210f0edac0e2c53d501b36b64437d9c6c70", + tweaked_pubkey: "53a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343" + }, + expected: %{ + script_pubkey: "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343", + bip350_address: "bc1p2wsldez5mud2yam29q22wgfh9439spgduvct83k3pm50fcxa5dps59h4z5" + } + }, + %{ + given: %{ + internal_pubkey: "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27", + script_tree: + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac" + ) + }, + intermediary: %{ + leaf_hashes: [ + "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21" + ], + merkle_root: "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21", + tweak: "cbd8679ba636c1110ea247542cfbd964131a6be84f873f7f3b62a777528ed001", + tweaked_pubkey: "147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3" + }, + expected: %{ + script_pubkey: "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", + bip350_address: "bc1pz37fc4cn9ah8anwm4xqqhvxygjf9rjf2resrw8h8w4tmvcs0863sa2e586", + script_path_control_blocks: [ + "c1187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27" + ] + } + }, + %{ + given: %{ + internal_pubkey: "93478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820", + script_tree: + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "20b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007ac" + ) + }, + intermediary: %{ + leaf_hashes: [ + "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b" + ], + merkle_root: "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b", + tweak: "6af9e28dbf9d6aaf027696e2598a5b3d056f5fd2355a7fd5a37a0e5008132d30", + tweaked_pubkey: "e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e" + }, + expected: %{ + script_pubkey: "5120e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e", + bip350_address: "bc1punvppl2stp38f7kwv2u2spltjuvuaayuqsthe34hd2dyy5w4g58qqfuag5", + script_path_control_blocks: [ + "c093478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820" + ] + } + }, + %{ + given: %{ + internal_pubkey: "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592", + script_tree: { + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "20387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48ac" + ), + Taproot.TapLeaf.from_string( + # id: 1, + # version + 250, + # script + "06424950333431" + ) + } + }, + intermediary: %{ + leaf_hashes: [ + "8ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7", + "f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a" + ], + merkle_root: "6c2dc106ab816b73f9d07e3cd1ef2c8c1256f519748e0813e4edd2405d277bef", + tweak: "9e0517edc8259bb3359255400b23ca9507f2a91cd1e4250ba068b4eafceba4a9", + tweaked_pubkey: "712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5" + }, + expected: %{ + script_pubkey: "5120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5", + bip350_address: "bc1pwyjywgrd0ffr3tx8laflh6228dj98xkjj8rum0zfpd6h0e930h6saqxrrm", + script_path_control_blocks: [ + "c0ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a", + "faee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf37865928ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7" + ] + } + }, + %{ + given: %{ + internal_pubkey: "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8", + script_tree: { + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "2044b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fdac" + ), + Taproot.TapLeaf.from_string( + # id: 1, + # version + 192, + # script + "07546170726f6f74" + ) + } + }, + intermediary: %{ + leaf_hashes: [ + "64512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89", + "2cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb" + ], + merkle_root: "ab179431c28d3b68fb798957faf5497d69c883c6fb1e1cd9f81483d87bac90cc", + tweak: "639f0281b7ac49e742cd25b7f188657626da1ad169209078e2761cefd91fd65e", + tweaked_pubkey: "77e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220" + }, + expected: %{ + script_pubkey: "512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220", + bip350_address: "bc1pwl3s54fzmk0cjnpl3w9af39je7pv5ldg504x5guk2hpecpg2kgsqaqstjq", + script_path_control_blocks: [ + "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd82cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb", + "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd864512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89" + ] + } + }, + %{ + given: %{ + internal_pubkey: "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f", + script_tree: { + Taproot.TapLeaf.from_string( + # id: 0, + 192, + # script + "2072ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69ac" + ), + { + Taproot.TapLeaf.from_string( + # id: 1, + 192, + # script + "202352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8ac" + ), + Taproot.TapLeaf.from_string( + # id: 2, + 192, + # script + "207337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186aac" + ) + } + } + }, + intermediary: %{ + leaf_hashes: [ + "2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817", + "ba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c", + "9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf6" + ], + merkle_root: "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2", + tweak: "b57bfa183d28eeb6ad688ddaabb265b4a41fbf68e5fed2c72c74de70d5a786f4", + tweaked_pubkey: "91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605" + }, + expected: %{ + script_pubkey: "512091b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605", + bip350_address: "bc1pjxmy65eywgafs5tsunw95ruycpqcqnev6ynxp7jaasylcgtcxczs6n332e", + script_path_control_blocks: [ + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fffe578e9ea769027e4f5a3de40732f75a88a6353a09d767ddeb66accef85e553", + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf62645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817", + "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817" + ] + } + }, + %{ + given: %{ + internal_pubkey: "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d", + script_tree: { + Taproot.TapLeaf.from_string( + # id: 0, + # version + 192, + # script + "2071981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2ac" + ), + { + Taproot.TapLeaf.from_string( + # id: 1, + # version + 192, + # script + "20d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748ac" + ), + Taproot.TapLeaf.from_string( + # id: 2, + # version + 192, + # script + "20c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4cac" + ) + } + } + }, + intermediary: %{ + leaf_hashes: [ + "f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d", + "737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711", + "d7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7" + ], + merkle_root: "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def", + tweak: "6579138e7976dc13b6a92f7bfd5a2fc7684f5ea42419d43368301470f3b74ed9", + tweaked_pubkey: "75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831" + }, + expected: %{ + script_pubkey: "512075169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831", + bip350_address: "bc1pw5tf7sqp4f50zka7629jrr036znzew70zxyvvej3zrpf8jg8hqcssyuewe", + script_path_control_blocks: [ + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d3cd369a528b326bc9d2133cbd2ac21451acb31681a410434672c8e34fe757e91", + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312dd7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d", + "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d" + ] + } + } + ] + + describe "taproot script_tree" do + test "calculate_taptweak/2 & tweak_pubkey" do + for t <- @bip_341_script_pubkey_test_vectors do + {:ok, pk} = Point.lift_x(t.given.internal_pubkey) + {_, hash} = Taproot.merkelize_script_tree(t.given.script_tree) + tweak = Taproot.calculate_taptweak(pk, hash) + tweak_hex = tweak |> :binary.encode_unsigned() |> Base.encode16(case: :lower) + assert tweak_hex == t.intermediary.tweak + + tweaked_pubkey = Taproot.tweak_pubkey(pk, hash) + tweaked_pubkey_hex = tweaked_pubkey |> Point.x_bytes() |> Base.encode16(case: :lower) + assert tweaked_pubkey_hex == t.intermediary.tweaked_pubkey + end + end + + test "merkelize_script_tree/1" do + for t <- @bip_341_script_pubkey_test_vectors do + {_, hash} = Taproot.merkelize_script_tree(t.given.script_tree) + assert Base.encode16(hash, case: :lower) == t.intermediary.merkle_root + end + end + + test "build_control_block/3" do + for t <- @bip_341_script_pubkey_test_vectors do + {:ok, pk} = Point.lift_x(t.given.internal_pubkey) + + unless t.given.script_tree == nil do + for {c_control_block, idx} <- Enum.with_index(t.expected.script_path_control_blocks) do + control_block = Taproot.build_control_block(pk, t.given.script_tree, idx) + assert Base.encode16(control_block, case: :lower) == c_control_block + end + end + end + end + end +end From 7f34d8094d4ca2cb27a23fd578900bee3a4f7576 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Fri, 3 Feb 2023 17:56:04 -0800 Subject: [PATCH 22/63] improve create_p2tr signature --- lib/script.ex | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/script.ex b/lib/script.ex index cb17357..ff377e2 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -614,9 +614,10 @@ defmodule Bitcoinex.Script do or Point. If a point is passed, it's interpreted as p, the internal key. If only p is passed, the script_tree is assumed to be empty. """ - @spec create_p2tr(<<_::256>> | Point.t(), Taproot.script_tree()) :: - {:ok, Bitcoinex.Script.t()} - def create_p2tr(p, script_tree \\ nil) + @spec create_p2tr(<<_::256>> | Point.t() | nil, Taproot.script_tree()) :: + {:ok, Bitcoinex.Script.t()} | {:ok, Bitcoinex.Script.t(), non_neg_integer()} | {:error, String.t()} + def create_p2tr(p \\ nil, script_tree \\ nil) + def create_p2tr(nil, nil), do: {:error, "script_tree or internal pubkey must be non-nil"} def create_p2tr(p = %Point{}, script_tree), do: create_p2tr(Point.x_bytes(p), script_tree) def create_p2tr(<>, script_tree) do @@ -625,9 +626,16 @@ defmodule Bitcoinex.Script do q = Taproot.tweak_pubkey(p, hash) create_witness_scriptpubkey(1, Point.x_bytes(q)) end + def create_p2tr(nil, script_tree) do + r = + 32 + |> :crypto.strong_rand_bytes() + |> :binary.decode_unsigned() + create_p2tr_script_only(script_tree, r) + end @spec create_p2tr_script_only(Taproot.script_tree(), non_neg_integer()) :: - {:ok, Bitcoinex.Script.t()} + {:ok, Bitcoinex.Script.t(), non_neg_integer()} def create_p2tr_script_only(script_tree, r) do case PrivateKey.new(r) do {:error, msg} -> @@ -636,10 +644,13 @@ defmodule Bitcoinex.Script do {:ok, sk} -> {:ok, hk} = Point.lift_x(@h) + {:ok, script} = sk |> PrivateKey.to_point() |> Math.add(hk) |> create_p2tr(script_tree) + + {:ok, script, r} end end From ad34bf6845f2f4aad47fa783d189633438baada6 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sun, 5 Feb 2023 02:30:38 -0800 Subject: [PATCH 23/63] finish bip341 sighash & tx signing --- lib/script.ex | 18 +- lib/segwit.ex | 4 +- lib/taproot.ex | 100 ++++++- lib/tapscript.ex | 125 +++++++++ lib/transaction.ex | 272 +++++++++++++++++- lib/utils.ex | 27 +- scripts/use_taproot_addr.exs | 92 ++++++ test/segwit_test.exs | 6 +- test/transaction_test.exs | 528 +++++++++++++++++++++++++++++++++++ 9 files changed, 1131 insertions(+), 41 deletions(-) create mode 100644 lib/tapscript.ex create mode 100644 scripts/use_taproot_addr.exs diff --git a/lib/script.ex b/lib/script.ex index ff377e2..666d2b7 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -615,7 +615,9 @@ defmodule Bitcoinex.Script do If only p is passed, the script_tree is assumed to be empty. """ @spec create_p2tr(<<_::256>> | Point.t() | nil, Taproot.script_tree()) :: - {:ok, Bitcoinex.Script.t()} | {:ok, Bitcoinex.Script.t(), non_neg_integer()} | {:error, String.t()} + {:ok, Bitcoinex.Script.t()} + | {:ok, Bitcoinex.Script.t(), non_neg_integer()} + | {:error, String.t()} def create_p2tr(p \\ nil, script_tree \\ nil) def create_p2tr(nil, nil), do: {:error, "script_tree or internal pubkey must be non-nil"} def create_p2tr(p = %Point{}, script_tree), do: create_p2tr(Point.x_bytes(p), script_tree) @@ -626,11 +628,13 @@ defmodule Bitcoinex.Script do q = Taproot.tweak_pubkey(p, hash) create_witness_scriptpubkey(1, Point.x_bytes(q)) end + def create_p2tr(nil, script_tree) do r = 32 |> :crypto.strong_rand_bytes() |> :binary.decode_unsigned() + create_p2tr_script_only(script_tree, r) end @@ -644,13 +648,13 @@ defmodule Bitcoinex.Script do {:ok, sk} -> {:ok, hk} = Point.lift_x(@h) - {:ok, script} = - sk - |> PrivateKey.to_point() - |> Math.add(hk) - |> create_p2tr(script_tree) + {:ok, script} = + sk + |> PrivateKey.to_point() + |> Math.add(hk) + |> create_p2tr(script_tree) - {:ok, script, r} + {:ok, script, r} end end diff --git a/lib/segwit.ex b/lib/segwit.ex index 1ebeb33..d9c28f0 100644 --- a/lib/segwit.ex +++ b/lib/segwit.ex @@ -85,8 +85,8 @@ defmodule Bitcoinex.Segwit do @doc """ Simpler Interface to check if address is valid """ - @spec is_valid_segswit_address?(String.t()) :: boolean - def is_valid_segswit_address?(address) when is_binary(address) do + @spec is_valid_segwit_address?(String.t()) :: boolean + def is_valid_segwit_address?(address) when is_binary(address) do case decode_address(address) do {:ok, _} -> true diff --git a/lib/taproot.ex b/lib/taproot.ex index 168c87e..85074f8 100644 --- a/lib/taproot.ex +++ b/lib/taproot.ex @@ -1,7 +1,7 @@ defmodule Bitcoinex.Taproot do alias Bitcoinex.Utils - alias Bitcoinex.Secp256k1 + alias Bitcoinex.{Secp256k1, Script} alias Bitcoinex.Secp256k1.{Math, Params, Point, PrivateKey} @n Params.curve().n @@ -50,15 +50,18 @@ defmodule Bitcoinex.Taproot do |> :binary.decode_unsigned() end - @spec tagged_hash_tapbranch(binary) :: binary + @spec tagged_hash_tapbranch(binary) :: <<_::256>> def tagged_hash_tapbranch(br), do: Utils.tagged_hash("TapBranch", br) - @spec tagged_hash_taptweak(binary) :: binary + @spec tagged_hash_taptweak(binary) :: <<_::256>> def tagged_hash_taptweak(root), do: Utils.tagged_hash("TapTweak", root) - @spec tagged_hash_tapleaf(binary) :: binary + @spec tagged_hash_tapleaf(binary) :: <<_::256>> def tagged_hash_tapleaf(leaf), do: Utils.tagged_hash("TapLeaf", leaf) + @spec tagged_hash_tapsighash(binary) :: <<_::256>> + def tagged_hash_tapsighash(sigmsg), do: Utils.tagged_hash("TapSighash", sigmsg) + defmodule TapLeaf do alias Bitcoinex.Script alias Bitcoinex.Taproot @@ -132,8 +135,8 @@ defmodule Bitcoinex.Taproot do {merkelize_script_tree(left), merkelize_script_tree(right)} # cross-mix the right hash with left branch and left hash with right branch - new_left = merge_branches(l_branches, r_hash) - new_right = merge_branches(r_branches, l_hash) + new_left = merkelize_branches(l_branches, r_hash) + new_right = merkelize_branches(r_branches, l_hash) node = new_left ++ new_right @@ -143,10 +146,10 @@ defmodule Bitcoinex.Taproot do {node, hash} end - defp merge_branches([], _), do: [] + defp merkelize_branches([], _), do: [] - defp merge_branches([{leaf, c} | tail], hash) do - [{leaf, c <> hash} | merge_branches(tail, hash)] + defp merkelize_branches([{leaf, c} | tail], hash) do + [{leaf, c <> hash} | merkelize_branches(tail, hash)] end @spec build_control_block(Point.t(), script_tree(), non_neg_integer()) :: binary @@ -158,4 +161,83 @@ defmodule Bitcoinex.Taproot do <> <> Point.x_bytes(p) <> merkle_path end + + # Should this take a Script or binary script + @spec merkelize_control_block(<<_::256>>, binary) :: any + def merkelize_control_block(<>, path) do + # Consume each 32-byte chunk of the rest of the control path, which are hashes of the merkle tree + path + |> :binary.bin_to_list() + |> Enum.chunk_every(32) + |> Enum.reduce(k0, fn e, k -> merkelize_path(e, k) end) + end + + defp merkelize_path(<>, <>) do + {l, r} = Utils.lexicographical_sort(e, k) + {:cont, tagged_hash_tapbranch(l <> r)} + end + + @spec validate_taproot_scriptpath_spend(Point.t(), binary, binary) :: + bool | {:error, String.t()} + def validate_taproot_scriptpath_spend( + q_point = %Point{}, + script, + <> <> <> <> path + ) do + leaf_version = extract_leaf_version(c) + + k0 = + tagged_hash_tapleaf( + leaf_version <> Utils.serialize_compact_size_unsigned_int(byte_size(script)) <> script + ) + + k = merkelize_control_block(k0, path) + # t is tweak + t = tagged_hash_taptweak(p <> k) |> :binary.decode_unsigned() + + case {PrivateKey.to_point(t), Point.lift_x(p)} do + {{:error, _}, _} -> + {:error, "control block yielded invalid tweak"} + + {_, {:error, _}} -> + {:error, "failed to parse point Q"} + + {tk, {:ok, pk}} -> + validate_q(q_point, Math.add(pk, tk), c) + # TODO MUST EVALUATE THE ACTUAL SCRIPT + end + end + + defp validate_q(given_q = %Point{}, calculated_q = %Point{}, <>) do + q_parity = extract_q_parity(c) + + cond do + q_parity != Point.has_even_y(given_q) -> + {:error, "incorrect Q parity"} + + given_q.x != calculated_q.x -> + {:error, "Q points do not match"} + + true -> + true + end + end + + @spec extract_leaf_version(<<_::8>>) :: binary + defp extract_leaf_version(<>) do + c + |> :binary.decode_unsigned() + |> Bitwise.band(0xFE) + |> :binary.encode_unsigned() + end + + @spec extract_q_parity(<<_::8>>) :: bool + defp extract_q_parity(<>) do + q_mod2 = + c + |> :binary.decode_unsigned() + |> Bitwise.band(1) + + q_mod2 == 0 + end end diff --git a/lib/tapscript.ex b/lib/tapscript.ex new file mode 100644 index 0000000..fa1b022 --- /dev/null +++ b/lib/tapscript.ex @@ -0,0 +1,125 @@ +# defmodule Tapscript do + +# defmodule Tapscript.Opcode do +# # BIP 342 opcodes (Tapscript) +# OP_CHECKSIGADD = 0xba + +# OP_INVALIDOPCODE = 0xff + +# OPCODE_NAMES.update({ +# OP_0: 'OP_0', +# OP_PUSHDATA1: 'OP_PUSHDATA1', +# OP_PUSHDATA2: 'OP_PUSHDATA2', +# OP_PUSHDATA4: 'OP_PUSHDATA4', +# OP_1NEGATE: 'OP_1NEGATE', +# OP_RESERVED: 'OP_RESERVED', +# OP_1: 'OP_1', +# OP_2: 'OP_2', +# OP_3: 'OP_3', +# OP_4: 'OP_4', +# OP_5: 'OP_5', +# OP_6: 'OP_6', +# OP_7: 'OP_7', +# OP_8: 'OP_8', +# OP_9: 'OP_9', +# OP_10: 'OP_10', +# OP_11: 'OP_11', +# OP_12: 'OP_12', +# OP_13: 'OP_13', +# OP_14: 'OP_14', +# OP_15: 'OP_15', +# OP_16: 'OP_16', +# OP_NOP: 'OP_NOP', +# OP_VER: 'OP_VER', +# OP_IF: 'OP_IF', +# OP_NOTIF: 'OP_NOTIF', +# OP_VERIF: 'OP_VERIF', +# OP_VERNOTIF: 'OP_VERNOTIF', +# OP_ELSE: 'OP_ELSE', +# OP_ENDIF: 'OP_ENDIF', +# OP_VERIFY: 'OP_VERIFY', +# OP_RETURN: 'OP_RETURN', +# OP_TOALTSTACK: 'OP_TOALTSTACK', +# OP_FROMALTSTACK: 'OP_FROMALTSTACK', +# OP_2DROP: 'OP_2DROP', +# OP_2DUP: 'OP_2DUP', +# OP_3DUP: 'OP_3DUP', +# OP_2OVER: 'OP_2OVER', +# OP_2ROT: 'OP_2ROT', +# OP_2SWAP: 'OP_2SWAP', +# OP_IFDUP: 'OP_IFDUP', +# OP_DEPTH: 'OP_DEPTH', +# OP_DROP: 'OP_DROP', +# OP_DUP: 'OP_DUP', +# OP_NIP: 'OP_NIP', +# OP_OVER: 'OP_OVER', +# OP_PICK: 'OP_PICK', +# OP_ROLL: 'OP_ROLL', +# OP_ROT: 'OP_ROT', +# OP_SWAP: 'OP_SWAP', +# OP_TUCK: 'OP_TUCK', +# OP_CAT: 'OP_CAT', +# OP_SUBSTR: 'OP_SUBSTR', +# OP_LEFT: 'OP_LEFT', +# OP_RIGHT: 'OP_RIGHT', +# OP_SIZE: 'OP_SIZE', +# OP_INVERT: 'OP_INVERT', +# OP_AND: 'OP_AND', +# OP_OR: 'OP_OR', +# OP_XOR: 'OP_XOR', +# OP_EQUAL: 'OP_EQUAL', +# OP_EQUALVERIFY: 'OP_EQUALVERIFY', +# OP_RESERVED1: 'OP_RESERVED1', +# OP_RESERVED2: 'OP_RESERVED2', +# OP_1ADD: 'OP_1ADD', +# OP_1SUB: 'OP_1SUB', +# OP_2MUL: 'OP_2MUL', +# OP_2DIV: 'OP_2DIV', +# OP_NEGATE: 'OP_NEGATE', +# OP_ABS: 'OP_ABS', +# OP_NOT: 'OP_NOT', +# OP_0NOTEQUAL: 'OP_0NOTEQUAL', +# OP_ADD: 'OP_ADD', +# OP_SUB: 'OP_SUB', +# OP_MUL: 'OP_MUL', +# OP_DIV: 'OP_DIV', +# OP_MOD: 'OP_MOD', +# OP_LSHIFT: 'OP_LSHIFT', +# OP_RSHIFT: 'OP_RSHIFT', +# OP_BOOLAND: 'OP_BOOLAND', +# OP_BOOLOR: 'OP_BOOLOR', +# OP_NUMEQUAL: 'OP_NUMEQUAL', +# OP_NUMEQUALVERIFY: 'OP_NUMEQUALVERIFY', +# OP_NUMNOTEQUAL: 'OP_NUMNOTEQUAL', +# OP_LESSTHAN: 'OP_LESSTHAN', +# OP_GREATERTHAN: 'OP_GREATERTHAN', +# OP_LESSTHANOREQUAL: 'OP_LESSTHANOREQUAL', +# OP_GREATERTHANOREQUAL: 'OP_GREATERTHANOREQUAL', +# OP_MIN: 'OP_MIN', +# OP_MAX: 'OP_MAX', +# OP_WITHIN: 'OP_WITHIN', +# OP_RIPEMD160: 'OP_RIPEMD160', +# OP_SHA1: 'OP_SHA1', +# OP_SHA256: 'OP_SHA256', +# OP_HASH160: 'OP_HASH160', +# OP_HASH256: 'OP_HASH256', +# OP_CODESEPARATOR: 'OP_CODESEPARATOR', +# OP_CHECKSIG: 'OP_CHECKSIG', +# OP_CHECKSIGVERIFY: 'OP_CHECKSIGVERIFY', +# OP_CHECKMULTISIG: 'OP_CHECKMULTISIG', +# OP_CHECKMULTISIGVERIFY: 'OP_CHECKMULTISIGVERIFY', +# OP_NOP1: 'OP_NOP1', +# OP_CHECKLOCKTIMEVERIFY: 'OP_CHECKLOCKTIMEVERIFY', +# OP_CHECKSEQUENCEVERIFY: 'OP_CHECKSEQUENCEVERIFY', +# OP_CHECKTEMPLATEVERIFY : 'OP_CHECKTEMPLATEVERIFY', +# OP_NOP5: 'OP_NOP5', +# OP_NOP6: 'OP_NOP6', +# OP_NOP7: 'OP_NOP7', +# OP_NOP8: 'OP_NOP8', +# OP_NOP9: 'OP_NOP9', +# OP_NOP10: 'OP_NOP10', +# OP_CHECKSIGADD: 'OP_CHECKSIGADD', +# OP_INVALIDOPCODE: 'OP_INVALIDOPCODE', +# }) +# end +# end diff --git a/lib/transaction.ex b/lib/transaction.ex index 8817a10..6ac8c83 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -9,6 +9,7 @@ defmodule Bitcoinex.Transaction do alias Bitcoinex.Transaction.Witness alias Bitcoinex.Utils alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.Taproot @type t() :: %__MODULE__{ version: non_neg_integer(), @@ -26,6 +27,27 @@ defmodule Bitcoinex.Transaction do :lock_time ] + @sighash_default 0x00 + @sighash_all 0x01 + @sighash_none 0x02 + @sighash_single 0x03 + @sighash_anyonecanpay 0x80 + @sighash_anyonecanpay_all 0x81 + @sighash_anyonecanpay_none 0x82 + @sighash_anyonecanpay_single 0x83 + + + + @valid_sighash_flags [ + @sighash_default, + @sighash_all, + @sighash_none, + @sighash_single, + @sighash_anyonecanpay_all, + @sighash_anyonecanpay_none, + @sighash_anyonecanpay_single + ] + @doc """ Returns the TxID of the given tranasction. @@ -43,6 +65,197 @@ defmodule Bitcoinex.Transaction do ) end + def bip341_sighash( tx = %__MODULE__{}, + hash_type, + ext_flag, + input_idx, + prev_amounts, + prev_scriptpubkeys) do + sigmsg = bip341_sigmsg(tx, hash_type, ext_flag, input_idx, prev_amounts, prev_scriptpubkeys) + Taproot.tagged_hash_tapsighash(sigmsg) + end + + @spec bip341_sigmsg( + t(), + non_neg_integer(), + non_neg_integer(), + non_neg_integer(), + list(non_neg_integer()), + list(<<_::280>>) + ) :: binary + def bip341_sigmsg(_, _, ext_flag, _, _, _) when ext_flag < 0 or ext_flag > 127, + do: {:error, "ext_flag out of range 0-127"} + + def bip341_sigmsg(_, hash_type, _, _, _, _) when hash_type not in @valid_sighash_flags, + do: {:error, "invalid sighash flag"} + + def bip341_sigmsg( + tx = %__MODULE__{}, + hash_type, + ext_flag, + input_idx, + prev_amounts, + prev_scriptpubkeys + ) do + tx_data = bip341_tx_data(tx, hash_type, prev_amounts, prev_scriptpubkeys) + bip341_sigmsg(tx, hash_type, ext_flag, input_idx, prev_amounts, prev_scriptpubkeys, tx_data) + end + + # TODO: refactor this function to take in individual subhashes instead of single cache + def bip341_sigmsg( + tx = %__MODULE__{}, + hash_type, + ext_flag, + input_idx, + prev_amounts, + prev_scriptpubkeys, + cached_tx_data + ) do + hash_byte = :binary.encode_unsigned(hash_type) + + input_data = + bip341_input_data( + tx, + hash_type, + ext_flag, + input_idx, + Enum.at(prev_amounts, input_idx), + Enum.at(prev_scriptpubkeys, input_idx) + ) + + output_data = bip341_output_data(tx, input_idx, hash_type) + + <<0>> <> hash_byte <> cached_tx_data <> input_data <> output_data + end + + # The results of this function can be reused across input signings. + def bip341_tx_data(tx, hash_type, prev_amounts, prev_scriptpubkeys) do + version = <> + lock_time = <> + acc = version <> lock_time + + acc = + if !hash_type_is_anyonecanpay(hash_type) do + sha_prevouts = bip341_sha_prevouts(tx.inputs) + sha_amounts = bip341_sha_amounts(prev_amounts) + sha_scriptpubkeys = bip341_sha_scriptpubkeys(prev_scriptpubkeys) + sha_sequences = bip341_sha_sequences(tx.inputs) + acc <> sha_prevouts <> sha_amounts <> sha_scriptpubkeys <> sha_sequences + else + acc + end + + if !hash_type_is_none_or_single(hash_type) do + sha_outputs = bip341_sha_outputs(tx.outputs) + acc <> sha_outputs + else + acc + end + end + + defp bip341_input_data( + tx, + hash_type, + ext_flag, + input_idx, + prev_amount, + <> + ) do + annex = get_annex(tx, input_idx) + spend_type = ext_flag * 2 + if annex == nil, do: 0, else: 1 + + input_commit = + if hash_type_is_anyonecanpay(hash_type) do + input = Enum.at(tx.inputs, input_idx) + prev_outpoint = Transaction.In.serialize_prevout(input) + + prev_outpoint <> + <> <> + prev_scriptpubkey <> <> + else + <> + end + + <> <> input_commit <> bip341_sha_annex(annex) + end + + defp bip341_output_data(tx, input_idx, hash_type) do + if hash_type_is_single(hash_type) do + tx.outputs + |> Enum.at(input_idx) + |> Out.serialize_output() + |> :erlang.list_to_binary() + |> Utils.sha256() + else + <<>> + end + end + + def hash_type_is_anyonecanpay(hash_type), + do: Bitwise.band(hash_type, @sighash_anyonecanpay) == @sighash_anyonecanpay + + defp hash_type_is_none_or_single(hash_type) do + b = Bitwise.band(hash_type, 3) + b == @sighash_none || b == @sighash_single + end + + defp hash_type_is_single(hash_type) do + Bitwise.band(hash_type, 3) == @sighash_single + end + + @spec get_annex(t(), non_neg_integer()) :: nil | binary | {:error, } + def get_annex(%__MODULE__{witnesses: nil}, _), do: nil + def get_annex(%__MODULE__{witnesses: []}, _), do: nil + def get_annex(%__MODULE__{witnesses: witnesses, inputs: inputs}, input_idx) + when input_idx >= 0 and input_idx < length(inputs) do + witnesses + |> Enum.at(input_idx) + |> Witness.get_annex() + end + + def get_annex(_, _), do: {:error, "input index is out of range"} + + @spec bip341_sha_prevouts(list(In.t())) :: <<_::256>> + def bip341_sha_prevouts(inputs) do + inputs + |> Transaction.In.serialize_prevouts() + |> Utils.sha256() + end + + def bip341_sha_amounts(prev_amounts) do + prev_amounts + |> Enum.reduce(<<>>, fn amount, acc -> acc <> <> end) + |> Utils.sha256() + end + + def bip341_sha_scriptpubkeys(prev_scriptpubkeys) do + prev_scriptpubkeys + |> Enum.reduce(<<>>, fn script, acc -> acc <> script end) + |> Utils.sha256() + end + + def bip341_sha_sequences(inputs) do + inputs + |> Transaction.In.serialize_sequences() + |> Utils.sha256() + end + + def bip341_sha_outputs(outputs) do + outputs + |> Transaction.Out.serialize_outputs() + |> Utils.sha256() + end + + def bip341_sha_annex(nil), do: <<>> + + def bip341_sha_annex(annex) do + annex + |> byte_size() + |> Utils.serialize_compact_size_unsigned_int() + |> Kernel.<>(annex) + |> Utils.sha256() + end + @doc """ Decodes a transaction in a hex encoded string into binary. """ @@ -189,7 +402,7 @@ defmodule Bitcoinex.Transaction.Witness do ] @doc """ - Wtiness accepts a binary and deserializes it. + Witness accepts a binary and deserializes it. """ @spec witness(binary) :: t() def witness(witness_bytes) do @@ -270,6 +483,21 @@ defmodule Bitcoinex.Transaction.Witness do stack_size - 1 ) end + + @spec get_annex(t()) :: nil | binary + def get_annex(%__MODULE__{txinwitness: witnesses}) when length(witnesses) < 2, do: nil + def get_annex(%__MODULE__{txinwitness: witnesses}) do + last = + witnesses + |> Enum.reverse() + |> Enum.at(0) + + case last do + #TODO switch to binary or int once witnesses are no longer stored as strings + "50" <> _ -> last + _ -> nil + end + end end defmodule Bitcoinex.Transaction.In do @@ -304,13 +532,7 @@ defmodule Bitcoinex.Transaction.In do defp serialize_input(inputs, serialized_inputs) do [input | inputs] = inputs - {:ok, prev_txid} = Base.decode16(input.prev_txid, case: :lower) - - prev_txid = - prev_txid - |> :binary.decode_unsigned(:big) - |> :binary.encode_unsigned(:little) - |> Bitcoinex.Utils.pad(32, :trailing) + prev_txid = prev_txid_little_endian(input.prev_txid) {:ok, script_sig} = Base.decode16(input.script_sig, case: :lower) @@ -327,6 +549,26 @@ defmodule Bitcoinex.Transaction.In do serialize_input(inputs, [serialized_inputs, serialized_input]) end + def serialize_prevouts(inputs) do + Enum.reduce(inputs, <<>>, fn input, acc -> acc <> serialize_prevout(input) end) + end + + def serialize_prevout(input) do + prev_txid = prev_txid_little_endian(input.prev_txid) + prev_txid <> <> + end + + def serialize_sequences(inputs) do + Enum.reduce(inputs, <<>>, fn input, acc -> acc <> <> end) + end + + def prev_txid_little_endian(prev_txid_hex) do + prev_txid_hex + |> Base.decode16!(case: :lower) + |> Utils.flip_endianness() + |> Utils.pad(32, :trailing) + end + def parse_inputs(counter, inputs) do parse(inputs, [], counter) end @@ -375,20 +617,22 @@ defmodule Bitcoinex.Transaction.Out do @spec serialize_outputs(list(Out.t())) :: iolist() def serialize_outputs(outputs) do - serialize_output(outputs, []) + serialize_outputs(outputs, []) end - defp serialize_output([], serialized_outputs), do: serialized_outputs + def serialize_outputs([], serialized_outputs), do: serialized_outputs - defp serialize_output(outputs, serialized_outputs) do - [output | outputs] = outputs + def serialize_outputs([output | outputs], serialized_outputs) do + serialized_output = serialize_output(output) + serialize_outputs(outputs, [serialized_outputs, serialized_output]) + end + def serialize_output(output) do {:ok, script_pub_key} = Base.decode16(output.script_pub_key, case: :lower) script_len = Utils.serialize_compact_size_unsigned_int(byte_size(script_pub_key)) - serialized_output = [<>, script_len, script_pub_key] - serialize_output(outputs, [serialized_outputs, serialized_output]) + [<>, script_len, script_pub_key] end def output(out_bytes) do diff --git a/lib/utils.ex b/lib/utils.ex index 22137c5..6380ba9 100644 --- a/lib/utils.ex +++ b/lib/utils.ex @@ -3,11 +3,12 @@ defmodule Bitcoinex.Utils do Contains useful utility functions used in Bitcoinex. """ - @spec sha256(iodata()) :: binary + @spec sha256(iodata()) :: <<_::256>> def sha256(str) do :crypto.hash(:sha256, str) end + @spec tagged_hash(binary, iodata()) :: <<_::256>> def tagged_hash(tag, str) do tag_hash = sha256(tag) sha256(tag_hash <> tag_hash <> str) @@ -22,7 +23,7 @@ defmodule Bitcoinex.Utils do for _ <- 1..num, do: x end - @spec double_sha256(iodata()) :: binary + @spec double_sha256(iodata()) :: <<_::256>> def double_sha256(preimage) do :crypto.hash( :sha256, @@ -30,7 +31,7 @@ defmodule Bitcoinex.Utils do ) end - @spec hash160(iodata()) :: binary + @spec hash160(iodata()) :: <<_::160>> def hash160(preimage) do :crypto.hash( :ripemd160, @@ -61,6 +62,13 @@ defmodule Bitcoinex.Utils do bin <> <<0::size(pad_len)>> end + @spec flip_endianness(binary) :: binary + def flip_endianness(bin) do + bin + |> :binary.decode_unsigned(:big) + |> :binary.encode_unsigned(:little) + end + @spec int_to_big(non_neg_integer(), non_neg_integer()) :: binary def int_to_big(i, p) do i @@ -68,14 +76,17 @@ defmodule Bitcoinex.Utils do |> pad(p, :leading) end + @spec int_to_little(non_neg_integer(), integer) :: binary def int_to_little(i, p) do i |> :binary.encode_unsigned(:little) |> pad(p, :trailing) end + @spec little_to_int(binary) :: non_neg_integer def little_to_int(i), do: :binary.decode_unsigned(i, :little) + @spec encode_int(non_neg_integer()) :: binary | {:error, <<_::160>>} def encode_int(i) when i > 0 do cond do i < 0xFD -> :binary.encode_unsigned(i) @@ -86,6 +97,7 @@ defmodule Bitcoinex.Utils do end end + @spec hex_to_bin(String.t()) :: binary | {:error, String.t()} def hex_to_bin(str) do str |> String.downcase() @@ -109,8 +121,9 @@ defmodule Bitcoinex.Utils do end # ascending order + @spec lexicographical_sort(binary, binary) :: {binary, binary} def lexicographical_sort(bin0, bin1) when is_binary(bin0) and is_binary(bin1) do - if lexicographical_sort(:binary.bin_to_list(bin0), :binary.bin_to_list(bin1)) do + if lexicographical_cmp(:binary.bin_to_list(bin0), :binary.bin_to_list(bin1)) do {bin0, bin1} else {bin1, bin0} @@ -118,9 +131,10 @@ defmodule Bitcoinex.Utils do end # equality case - def lexicographical_sort([], []), do: true + @spec lexicographical_cmp(list(byte), list(byte)) :: boolean + def lexicographical_cmp([], []), do: true - def lexicographical_sort([b0 | r0], [b1 | r1]) do + def lexicographical_cmp([b0 | r0], [b1 | r1]) do cond do b0 == b1 -> lexicographical_sort(r0, r1) @@ -138,6 +152,7 @@ defmodule Bitcoinex.Utils do @doc """ Returns the serialized variable length integer. """ + @spec serialize_compact_size_unsigned_int(non_neg_integer()) :: binary def serialize_compact_size_unsigned_int(compact_size) do cond do compact_size >= 0 and compact_size <= 0xFC -> diff --git a/scripts/use_taproot_addr.exs b/scripts/use_taproot_addr.exs new file mode 100644 index 0000000..8429361 --- /dev/null +++ b/scripts/use_taproot_addr.exs @@ -0,0 +1,92 @@ +alias Bitcoinex.Secp256k1 +alias Bitcoinex.Transaction +alias Bitcoinex.Script +alias Bitcoinex.Secp256k1 +alias Bitcoinex.Secp256k1.{Point, PrivateKey, Signature, Schnorr} +alias Bitcoinex.Taproot + + +new_secret = fn -> + 32 + |> :crypto.strong_rand_bytes() + |> :binary.decode_unsigned() +end + +{:ok, sk} = new_secret.() |> PrivateKey.new() +sk = Secp256k1.force_even_y(sk) +pk = PrivateKey.to_point(sk) + +script_tree = nil + +{:ok, scriptpubkey} = Script.create_p2tr(pk, script_tree) + +{:ok, addr} = Script.to_address(scriptpubkey, :regtest) +# addr = "bcrt1pfh4qvlzrgmf6f8e6urjkf3ax83kz02xqc8zujnpeycxgc3wrqmxs8py692" +txid = "bcdf4b0088a75c139d0b4858164534585b735dcdd18321824a31936abcbf04b4" +vout = 0 +amount = 100_000_000 + +dest_addr = "bcrt1pzeg29d38m506gtnunlg2tjh4hpvv4mtkjg5tku34ad24830pta5qg0kyn6" + +{:ok, dest_script, _network} = Script.from_address(dest_addr) + +tx = %Transaction{ + version: 1, + inputs: [ + %Transaction.In{ + prev_txid: txid, + prev_vout: vout, + script_sig: "", + sequence_no: 2147483648, + } + ], + outputs: [ + %Transaction.Out{ + value: 50_000_000, + script_pub_key: Script.to_hex(dest_script) + } + ], + lock_time: 0 +} + + #sighash_default +hash_type = 0x00 +ext_flag = 0 +input_idx = 0 + +sighash = Transaction.bip341_sighash( + tx, + hash_type, + ext_flag, + input_idx, + [amount], + [Script.serialize_with_compact_size(scriptpubkey)] +) + +{_, merkle_root_hash} = Taproot.merkelize_script_tree(script_tree) + +q_sk = Taproot.tweak_privkey(sk, merkle_root_hash) + +{:ok, sig} = Schnorr.sign(q_sk, :binary.decode_unsigned(sighash), 0) + +hash_byte = + if hash_type == 0x00 do + <<>> + else + <> + end + +witness_script = Signature.serialize_signature(sig) <> hash_byte |> Base.encode16(case: :lower) + +tx = %Transaction{tx | witnesses: [ + %Transaction.Witness{ + txinwitness: [ + witness_script + ] + } +] +} + +Transaction.serialize(tx) + +# txid: 86dcdf6a88480a16524aa353b47d11228d67f96f59c4a645d65d4aac09330065 diff --git a/test/segwit_test.exs b/test/segwit_test.exs index 2d4bcc7..4b1832c 100644 --- a/test/segwit_test.exs +++ b/test/segwit_test.exs @@ -108,19 +108,19 @@ defmodule Bitcoinex.SegwitTest do end end - describe "is_valid_segswit_address?/1" do + describe "is_valid_segwit_address?/1" do test "return true given valid address" do for {address, _hexscript} <- @valid_segwit_address_hexscript_pairs_mainnet ++ @valid_segwit_address_hexscript_pairs_testnet ++ @valid_segwit_address_hexscript_pairs_regtest do - assert Segwit.is_valid_segswit_address?(address) + assert Segwit.is_valid_segwit_address?(address) end end test "return false given invalid address" do for address <- @invalid_segwit_addresses do - refute Segwit.is_valid_segswit_address?(address) + refute Segwit.is_valid_segwit_address?(address) end end end diff --git a/test/transaction_test.exs b/test/transaction_test.exs index e4a7e7c..012d33b 100644 --- a/test/transaction_test.exs +++ b/test/transaction_test.exs @@ -3,6 +3,10 @@ defmodule Bitcoinex.TransactionTest do doctest Bitcoinex.Transaction alias Bitcoinex.Transaction + alias Bitcoinex.Utils + alias Bitcoinex.Script + alias Bitcoinex.Taproot + alias Bitcoinex.Secp256k1.{PrivateKey, Schnorr, Signature, Point} @txn_serialization_1 %{ tx_hex: @@ -29,6 +33,381 @@ defmodule Bitcoinex.TransactionTest do "0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac080047304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01473044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502473044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403483045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381483045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a08824730440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000" } + # https://github.com/bitcoin/bips/blob/master/bip-0341/wallet-test-vectors.jsonå + @bip341_test_vector %{ + given: %{ + unsigned_tx: + "02000000097de20cbff686da83a54981d2b9bab3586f4ca7e48f57f5b55963115f3b334e9c010000000000000000d7b7cab57b1393ace2d064f4d4a2cb8af6def61273e127517d44759b6dafdd990000000000fffffffff8e1f583384333689228c5d28eac13366be082dc57441760d957275419a418420000000000fffffffff0689180aa63b30cb162a73c6d2a38b7eeda2a83ece74310fda0843ad604853b0100000000feffffffaa5202bdf6d8ccd2ee0f0202afbbb7461d9264a25e5bfd3c5a52ee1239e0ba6c0000000000feffffff956149bdc66faa968eb2be2d2faa29718acbfe3941215893a2a3446d32acd050000000000000000000e664b9773b88c09c32cb70a2a3e4da0ced63b7ba3b22f848531bbb1d5d5f4c94010000000000000000e9aa6b8e6c9de67619e6a3924ae25696bb7b694bb677a632a74ef7eadfd4eabf0000000000ffffffffa778eb6a263dc090464cd125c466b5a99667720b1c110468831d058aa1b82af10100000000ffffffff0200ca9a3b000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac807840cb0000000020ac9a87f5594be208f8532db38cff670c450ed2fea8fcdefcc9a663f78bab962b0065cd1d", + inputs: [ + %{ + prev_scriptpubkey: + "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343", + amount_sats: 420_000_000 + }, + %{ + prev_scriptpubkey: + "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", + amount_sats: 462_000_000 + }, + %{ + prev_scriptpubkey: "76a914751e76e8199196d454941c45d1b3a323f1433bd688ac", + amount_sats: 294_000_000 + }, + %{ + prev_scriptpubkey: + "5120e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e", + amount_sats: 504_000_000 + }, + %{ + prev_scriptpubkey: + "512091b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605", + amount_sats: 630_000_000 + }, + %{ + prev_scriptpubkey: "00147dd65592d0ab2fe0d0257d571abf032cd9db93dc", + amount_sats: 378_000_000 + }, + %{ + prev_scriptpubkey: + "512075169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831", + amount_sats: 672_000_000 + }, + %{ + prev_scriptpubkey: + "5120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5", + amount_sats: 546_000_000 + }, + %{ + prev_scriptpubkey: + "512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220", + amount_sats: 588_000_000 + } + ] + }, + intermediary: %{ + hash_amounts: "58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde6", + hash_outputs: "a2e6dab7c1f0dcd297c8d61647fd17d821541ea69c3cc37dcbad7f90d4eb4bc5", + hash_prevouts: "e3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f", + hash_script_pubkeys: "23ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e21", + hash_sequences: "18959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957e" + }, + input_spending: [ + %{ + given: %{ + txin_index: 0, + internal_privkey: "6b973d88838f27366ed61c9ad6367663045cb456e28335c109e30717ae0c6baa", + merkle_root: nil, + hash_type: 3 + }, + intermediary: %{ + internal_pubkey: "d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d", + tweak: "b86e7be8f39bab32a6f2c0443abbc210f0edac0e2c53d501b36b64437d9c6c70", + tweaked_privkey: "2405b971772ad26915c8dcdf10f238753a9b837e5f8e6a86fd7c0cce5b7296d9", + sigmsg: + "0003020000000065cd1de3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde623ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e2118959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957e0000000000d0418f0e9a36245b9a50ec87f8bf5be5bcae434337b87139c3a5b1f56e33cba0", + precomputed_used: [ + "hash_amounts", + "hash_prevouts", + "hash_script_pubkeys", + "hash_sequences" + ], + sig_hash: "2514a6272f85cfa0f45eb907fcb0d121b808ed37c6ea160a5a9046ed5526d555" + }, + expected: %{ + witness: [ + "ed7c1647cb97379e76892be0cacff57ec4a7102aa24296ca39af7541246d8ff14d38958d4cc1e2e478e4d4a764bbfd835b16d4e314b72937b29833060b87276c03" + ] + } + }, + %{ + given: %{ + txin_index: 1, + internal_privkey: "1e4da49f6aaf4e5cd175fe08a32bb5cb4863d963921255f33d3bc31e1343907f", + merkle_root: "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21", + hash_type: 131 + }, + intermediary: %{ + internal_pubkey: "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27", + tweak: "cbd8679ba636c1110ea247542cfbd964131a6be84f873f7f3b62a777528ed001", + tweaked_privkey: "ea260c3b10e60f6de018455cd0278f2f5b7e454be1999572789e6a9565d26080", + sigmsg: + "0083020000000065cd1d00d7b7cab57b1393ace2d064f4d4a2cb8af6def61273e127517d44759b6dafdd9900000000808f891b00000000225120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3ffffffffffcef8fb4ca7efc5433f591ecfc57391811ce1e186a3793024def5c884cba51d", + precomputed_used: [], + sig_hash: "325a644af47e8a5a2591cda0ab0723978537318f10e6a63d4eed783b96a71a4d" + }, + expected: %{ + witness: [ + "052aedffc554b41f52b521071793a6b88d6dbca9dba94cf34c83696de0c1ec35ca9c5ed4ab28059bd606a4f3a657eec0bb96661d42921b5f50a95ad33675b54f83" + ] + } + }, + %{ + given: %{ + txin_index: 3, + internal_privkey: "d3c7af07da2d54f7a7735d3d0fc4f0a73164db638b2f2f7c43f711f6d4aa7e64", + merkle_root: "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b", + hash_type: 1 + }, + intermediary: %{ + internal_pubkey: "93478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820", + tweak: "6af9e28dbf9d6aaf027696e2598a5b3d056f5fd2355a7fd5a37a0e5008132d30", + tweaked_privkey: "97323385e57015b75b0339a549c56a948eb961555973f0951f555ae6039ef00d", + sigmsg: + "0001020000000065cd1de3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde623ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e2118959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957ea2e6dab7c1f0dcd297c8d61647fd17d821541ea69c3cc37dcbad7f90d4eb4bc50003000000", + precomputed_used: [ + "hash_amounts", + "hash_outputs", + "hash_prevouts", + "hash_script_pubkeys", + "hash_sequences" + ], + sig_hash: "bf013ea93474aa67815b1b6cc441d23b64fa310911d991e713cd34c7f5d46669" + }, + expected: %{ + witness: [ + "ff45f742a876139946a149ab4d9185574b98dc919d2eb6754f8abaa59d18b025637a3aa043b91817739554f4ed2026cf8022dbd83e351ce1fabc272841d2510a01" + ] + } + }, + %{ + given: %{ + txin_index: 4, + internal_privkey: "f36bb07a11e469ce941d16b63b11b9b9120a84d9d87cff2c84a8d4affb438f4e", + merkle_root: "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2", + hash_type: 0 + }, + intermediary: %{ + internal_pubkey: "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f", + tweak: "b57bfa183d28eeb6ad688ddaabb265b4a41fbf68e5fed2c72c74de70d5a786f4", + tweaked_privkey: "a8e7aa924f0d58854185a490e6c41f6efb7b675c0f3331b7f14b549400b4d501", + sigmsg: + "0000020000000065cd1de3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde623ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e2118959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957ea2e6dab7c1f0dcd297c8d61647fd17d821541ea69c3cc37dcbad7f90d4eb4bc50004000000", + precomputed_used: [ + "hash_amounts", + "hash_outputs", + "hash_prevouts", + "hash_script_pubkeys", + "hash_sequences" + ], + sig_hash: "4f900a0bae3f1446fd48490c2958b5a023228f01661cda3496a11da502a7f7ef" + }, + expected: %{ + witness: [ + "b4010dd48a617db09926f729e79c33ae0b4e94b79f04a1ae93ede6315eb3669de185a17d2b0ac9ee09fd4c64b678a0b61a0a86fa888a273c8511be83bfd6810f" + ] + } + }, + %{ + given: %{ + txin_index: 6, + internal_privkey: "415cfe9c15d9cea27d8104d5517c06e9de48e2f986b695e4f5ffebf230e725d8", + merkle_root: "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def", + hash_type: 2 + }, + intermediary: %{ + internal_pubkey: "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d", + tweak: "6579138e7976dc13b6a92f7bfd5a2fc7684f5ea42419d43368301470f3b74ed9", + tweaked_privkey: "241c14f2639d0d7139282aa6abde28dd8a067baa9d633e4e7230287ec2d02901", + sigmsg: + "0002020000000065cd1de3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde623ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e2118959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957e0006000000", + precomputed_used: [ + "hash_amounts", + "hash_prevouts", + "hash_script_pubkeys", + "hash_sequences" + ], + sig_hash: "15f25c298eb5cdc7eb1d638dd2d45c97c4c59dcaec6679cfc16ad84f30876b85" + }, + expected: %{ + witness: [ + "a3785919a2ce3c4ce26f298c3d51619bc474ae24014bcdd31328cd8cfbab2eff3395fa0a16fe5f486d12f22a9cedded5ae74feb4bbe5351346508c5405bcfee002" + ] + } + }, + %{ + given: %{ + txin_index: 7, + internal_privkey: "c7b0e81f0a9a0b0499e112279d718cca98e79a12e2f137c72ae5b213aad0d103", + merkle_root: "6c2dc106ab816b73f9d07e3cd1ef2c8c1256f519748e0813e4edd2405d277bef", + hash_type: 130 + }, + intermediary: %{ + internal_pubkey: "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592", + tweak: "9e0517edc8259bb3359255400b23ca9507f2a91cd1e4250ba068b4eafceba4a9", + tweaked_privkey: "65b6000cd2bfa6b7cf736767a8955760e62b6649058cbc970b7c0871d786346b", + sigmsg: + "0082020000000065cd1d00e9aa6b8e6c9de67619e6a3924ae25696bb7b694bb677a632a74ef7eadfd4eabf00000000804c8b2000000000225120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5ffffffff", + precomputed_used: [], + sig_hash: "cd292de50313804dabe4685e83f923d2969577191a3e1d2882220dca88cbeb10" + }, + expected: %{ + witness: [ + "ea0c6ba90763c2d3a296ad82ba45881abb4f426b3f87af162dd24d5109edc1cdd11915095ba47c3a9963dc1e6c432939872bc49212fe34c632cd3ab9fed429c482" + ] + } + }, + %{ + given: %{ + txin_index: 8, + internal_privkey: "77863416be0d0665e517e1c375fd6f75839544eca553675ef7fdf4949518ebaa", + merkle_root: "ab179431c28d3b68fb798957faf5497d69c883c6fb1e1cd9f81483d87bac90cc", + hash_type: 129 + }, + intermediary: %{ + internal_pubkey: "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8", + tweak: "639f0281b7ac49e742cd25b7f188657626da1ad169209078e2761cefd91fd65e", + tweaked_privkey: "ec18ce6af99f43815db543f47b8af5ff5df3b2cb7315c955aa4a86e8143d2bf5", + sigmsg: + "0081020000000065cd1da2e6dab7c1f0dcd297c8d61647fd17d821541ea69c3cc37dcbad7f90d4eb4bc500a778eb6a263dc090464cd125c466b5a99667720b1c110468831d058aa1b82af101000000002b0c230000000022512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220ffffffff", + precomputed_used: [ + "hash_outputs" + ], + sig_hash: "cccb739eca6c13a8a89e6e5cd317ffe55669bbda23f2fd37b0f18755e008edd2" + }, + expected: %{ + witness: [ + "bbc9584a11074e83bc8c6759ec55401f0ae7b03ef290c3139814f545b58a9f8127258000874f44bc46db7646322107d4d86aec8e73b8719a61fff761d75b5dd981" + ] + } + } + ], + auxiliary: %{ + signed_tx: + "020000000001097de20cbff686da83a54981d2b9bab3586f4ca7e48f57f5b55963115f3b334e9c010000000000000000d7b7cab57b1393ace2d064f4d4a2cb8af6def61273e127517d44759b6dafdd990000000000fffffffff8e1f583384333689228c5d28eac13366be082dc57441760d957275419a41842000000006b4830450221008f3b8f8f0537c420654d2283673a761b7ee2ea3c130753103e08ce79201cf32a022079e7ab904a1980ef1c5890b648c8783f4d10103dd62f740d13daa79e298d50c201210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798fffffffff0689180aa63b30cb162a73c6d2a38b7eeda2a83ece74310fda0843ad604853b0100000000feffffffaa5202bdf6d8ccd2ee0f0202afbbb7461d9264a25e5bfd3c5a52ee1239e0ba6c0000000000feffffff956149bdc66faa968eb2be2d2faa29718acbfe3941215893a2a3446d32acd050000000000000000000e664b9773b88c09c32cb70a2a3e4da0ced63b7ba3b22f848531bbb1d5d5f4c94010000000000000000e9aa6b8e6c9de67619e6a3924ae25696bb7b694bb677a632a74ef7eadfd4eabf0000000000ffffffffa778eb6a263dc090464cd125c466b5a99667720b1c110468831d058aa1b82af10100000000ffffffff0200ca9a3b000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac807840cb0000000020ac9a87f5594be208f8532db38cff670c450ed2fea8fcdefcc9a663f78bab962b0141ed7c1647cb97379e76892be0cacff57ec4a7102aa24296ca39af7541246d8ff14d38958d4cc1e2e478e4d4a764bbfd835b16d4e314b72937b29833060b87276c030141052aedffc554b41f52b521071793a6b88d6dbca9dba94cf34c83696de0c1ec35ca9c5ed4ab28059bd606a4f3a657eec0bb96661d42921b5f50a95ad33675b54f83000141ff45f742a876139946a149ab4d9185574b98dc919d2eb6754f8abaa59d18b025637a3aa043b91817739554f4ed2026cf8022dbd83e351ce1fabc272841d2510a010140b4010dd48a617db09926f729e79c33ae0b4e94b79f04a1ae93ede6315eb3669de185a17d2b0ac9ee09fd4c64b678a0b61a0a86fa888a273c8511be83bfd6810f0247304402202b795e4de72646d76eab3f0ab27dfa30b810e856ff3a46c9a702df53bb0d8cc302203ccc4d822edab5f35caddb10af1be93583526ccfbade4b4ead350781e2f8adcd012102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f90141a3785919a2ce3c4ce26f298c3d51619bc474ae24014bcdd31328cd8cfbab2eff3395fa0a16fe5f486d12f22a9cedded5ae74feb4bbe5351346508c5405bcfee0020141ea0c6ba90763c2d3a296ad82ba45881abb4f426b3f87af162dd24d5109edc1cdd11915095ba47c3a9963dc1e6c432939872bc49212fe34c632cd3ab9fed429c4820141bbc9584a11074e83bc8c6759ec55401f0ae7b03ef290c3139814f545b58a9f8127258000874f44bc46db7646322107d4d86aec8e73b8719a61fff761d75b5dd9810065cd1d" + } + } + + # https://gist.github.com/giacomocaironi/e41a45195b2ac6863ec46e8f86324757 + @bip341_sighash_all %{ + sighash_flag: 0x00, + unsigned_tx: + "02000000025f6092ec9bb430830dfc344260dd5a03cf355186e774be49b2fe5c362f56cb8d00000000000000000061431892d76aa28b5ed1e3da8800fa0d7190c4b4f22be5f416d2d07e573b32e10100000000000000000100ca9a3b000000001976a914682dfdbc97ab5c31300f36d3c12c6fd854b1b35a88ac00000000", + signed_tx: + "020000000001025f6092ec9bb430830dfc344260dd5a03cf355186e774be49b2fe5c362f56cb8d00000000000000000061431892d76aa28b5ed1e3da8800fa0d7190c4b4f22be5f416d2d07e573b32e10100000000000000000100ca9a3b000000001976a914682dfdbc97ab5c31300f36d3c12c6fd854b1b35a88ac0247304402203120452eed289de04e17740232b5f97fac0bc91e4cbb7750bb3d9f4f3c09477b02207e4e363c8d7914f707ff3ddf84e3201f9e402a7dccd02be5d7739d91b0f91adf01210271be339aeae9ed2c6a5a7f8ac5f49638da387612be881c7ed2fb3848b0ef8a6c01408608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae98378fd9f664e274c9c2a2744197da522fdf1e3aba999b318e2587be098d90d453300000000", + inputs: [ + %{ + prev_scriptpubkey: "0014196a5bea745288a7f947993c28e3a0f2108d2e0a", + value: 500_000_000, + privkey: "6b3973ee2ce444ada0147716925f6f77569350804835498593dd3be95163d558", + pubkey: "0271be339aeae9ed2c6a5a7f8ac5f49638da387612be881c7ed2fb3848b0ef8a6c" + }, + %{ + prev_scriptpubkey: "512029d942d0408906b359397b6f87c5145814a9aefc8c396dd05efa8b5b73576bf2", + value: 600_000_000, + privkey: "cf3780a32ef3b2d70366f0124ee40195a251044e82a13146106be75ee049ac02", + signature: + "8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae98378fd9f664e274c9c2a2744197da522fdf1e3aba999b318e2587be098d90d4533" + } + ], + intermediary: %{ + data: + "0000020000000000000032553b113292dfa8216546e721388a6c19c76626ca65dc187e0348d6ed445f815733468db74734c00efa0b466bca091d8f1aab074af2538f36bd0a734a5940c5423cd73484fc5e3e0a623442846c279c2216f25a2f32d161fea6c5821a1adde7af5570f5a1810b7af78caf4bc70a660f0df51e42baf91d4de5b2328de0e83dfc8cdee56004a241f9c79cc55b7d79eaed04909d84660502a2d4e9c357c2047cf50001000000", + sighash: "07333acfe6dce8196f1ad62b2e039a3d9f0b6627bf955be767c519c0f8789ff4", + sha_prevouts: %{ + data: + "5f6092ec9bb430830dfc344260dd5a03cf355186e774be49b2fe5c362f56cb8d0000000061431892d76aa28b5ed1e3da8800fa0d7190c4b4f22be5f416d2d07e573b32e101000000", + hash: "32553b113292dfa8216546e721388a6c19c76626ca65dc187e0348d6ed445f81" + }, + sha_amounts: %{ + data: "0065cd1d000000000046c32300000000", + hash: "5733468db74734c00efa0b466bca091d8f1aab074af2538f36bd0a734a5940c5" + }, + sha_scriptpubkeys: %{ + data: + "160014196a5bea745288a7f947993c28e3a0f2108d2e0a22512029d942d0408906b359397b6f87c5145814a9aefc8c396dd05efa8b5b73576bf2", + hash: "423cd73484fc5e3e0a623442846c279c2216f25a2f32d161fea6c5821a1adde7" + }, + sha_sequences: %{ + data: "0000000000000000", + hash: "af5570f5a1810b7af78caf4bc70a660f0df51e42baf91d4de5b2328de0e83dfc" + }, + sha_outputs: %{ + data: "00ca9a3b000000001976a914682dfdbc97ab5c31300f36d3c12c6fd854b1b35a88ac", + hash: "8cdee56004a241f9c79cc55b7d79eaed04909d84660502a2d4e9c357c2047cf5" + } + } + } + + # SIGHASH_ANYONECANPAY(ALL) + # @bip341_sighash_anyonecanpay_all %{ + # sighash_flag: 0x81, + # unsigned_tx: + # "02000000015c82840e7a0e5283c5516e742352566408de5c40d45ab0a2f872b37f188976c200000000000000000002003b5808000000001600141192fac5233e4eefa18859396b74851de18f8f4700e1f5050000000022512032c22a6e048b9d4183f612bc1b73a58fc0d4e7f548fd71b732063645d43f420200000000", + # signed_tx: + # "020000000001015c82840e7a0e5283c5516e742352566408de5c40d45ab0a2f872b37f188976c200000000000000000002003b5808000000001600141192fac5233e4eefa18859396b74851de18f8f4700e1f5050000000022512032c22a6e048b9d4183f612bc1b73a58fc0d4e7f548fd71b732063645d43f4202014153fd82ff31642b92ae43cf0010e2aac2c51a781cb2ce8c72f80477a4900d2f3a4bb1eb986bc000bd5b055c62872ac8c426eb69186b3f2e46656189d1ba97a3078100000000", + # inputs: [ + # %{ + # prev_scriptpubkey: + # "5120fe7633a26b281a80ee75d344b07ec97e738d4038de288b6caf7d38e06a6c3ee1", + # value: 250_000_000, + # privkey: "3c1d300faf1d8706fd07137e1cc1d59967ccc0efa6212fc03b2ac7c382fa9133", + # # has sighash anyonecanpay appended + # signature: + # "53fd82ff31642b92ae43cf0010e2aac2c51a781cb2ce8c72f80477a4900d2f3a4bb1eb986bc000bd5b055c62872ac8c426eb69186b3f2e46656189d1ba97a30781" + # } + # ], + # intermediary: %{ + # data: + # "00810200000000000000d070f96ca70c4dea1042a92e6abf04883e75bd3ad7dd4dcdf18153cda431cbd8005c82840e7a0e5283c5516e742352566408de5c40d45ab0a2f872b37f188976c20000000080b2e60e00000000225120fe7633a26b281a80ee75d344b07ec97e738d4038de288b6caf7d38e06a6c3ee100000000", + # sighash: "11998278e8f4fe9ec6e360642a91536a5498a30cf711712ed3d9c25dfede876b", + # sha_outputs: %{ + # data: + # "003b5808000000001600141192fac5233e4eefa18859396b74851de18f8f4700e1f5050000000022512032c22a6e048b9d4183f612bc1b73a58fc0d4e7f548fd71b732063645d43f4202", + # hash: "d070f96ca70c4dea1042a92e6abf04883e75bd3ad7dd4dcdf18153cda431cbd8" + # } + # } + # } + + # SIGHASH_SINGLE + # @bip341_sighash_single %{ + # sighash_flag: 0x03, + # unsigned_tx: + # "0200000002f4d49508b20d94df7d19b5ce7f7d713140cdad392c23a5ab3c996c19284d5f7b0100000000000000006912ceb41a4e62b77a3b7bec7cd777fbd5c8188821ecb2e1fd0a6e56ed3315920000000000000000000200c2eb0b000000001976a9143bfe0f94eb78a2227664c6ebcf81719467c0106f88ac00a3e11100000000220020f1dca6047a919edc31378db3c5fcd1e93eea73f9c7fd8632ab47f18c8b8165f400000000", + # signed_tx: + # "02000000000102f4d49508b20d94df7d19b5ce7f7d713140cdad392c23a5ab3c996c19284d5f7b010000006a483045022100cc57364c2fc8ec973a038a0d859ccfbec969165bd4a1b5cbf91cfa4cd1170b0102206460814f1093b241fc8afd04b61741d5d5ae90fd404e3e7a2500d298d4e8c73503201da42d0fb59c4aa380bb7c9775d6d2da16f01eda6b3921d227faaa999de16021000000006912ceb41a4e62b77a3b7bec7cd777fbd5c8188821ecb2e1fd0a6e56ed3315920000000000000000000200c2eb0b000000001976a9143bfe0f94eb78a2227664c6ebcf81719467c0106f88ac00a3e11100000000220020f1dca6047a919edc31378db3c5fcd1e93eea73f9c7fd8632ab47f18c8b8165f40141346f28fec0d31fafde1a1aa30634d6962d59e118a79456671347060697937424fd77d679deb1c7652ae15b5d932fe3bbace65b07d6cf6639b638e21688b485c0030000000000", + # inputs: [ + # %{ + # prev_scriptpubkey: + # "512088782d9105c8774f5f2f2857aec1519bf83aa53371bd44d3a0735e9841b73c28", + # value: 100_000_000, + # privkey: "eb54ef369f599d3da9ecfeab0529160dfc76c92f1e32ade4ba33abd8408a23b8", + # signature: + # "cc57364c2fc8ec973a038a0d859ccfbec969165bd4a1b5cbf91cfa4cd1170b016460814f1093b241fc8afd04b61741d5d5ae90fd404e3e7a2500d298d4e8c735", + # der_signature: + # "3045022100cc57364c2fc8ec973a038a0d859ccfbec969165bd4a1b5cbf91cfa4cd1170b0102206460814f1093b241fc8afd04b61741d5d5ae90fd404e3e7a2500d298d4e8c73503" + # }, + # %{ + # prev_scriptpubkey: "76a914a589246bf9c64679b6d186608c2a2bee949f23e088ac", + # value: 500_000_000, + # privkey: "5dd3e4e9cd6073da94108e26e8c5c3ccbd510197ab12bd787b19b0e88181da9c", + # pubkey: "1da42d0fb59c4aa380bb7c9775d6d2da16f01eda6b3921d227faaa999de16021" + # } + # ], + # intermediary: %{ + # data: + # "00030200000000000000c637b7e49b49272269dd8b409682f66cf28191d62028216cb200239ff5e2f8dd4a3c35ddd52f7ffa12848588e82193156f84a476894a095352892c8f274fc863480fb749c75701a17230706caddbfce6f422151d9722386e82bfcd3f4fdc2704af5570f5a1810b7af78caf4bc70a660f0df51e42baf91d4de5b2328de0e83dfc00000000007b1b0fd12e11d0c5792199a8cd4f4de1e823635e6947909f10d09a3e84cce760", + # sighash: "278ba0eea80c753d166879a844b51f290770609ec171a21fdfb6c0232d25c2e0", + # sha_prevouts: %{ + # data: + # "f4d49508b20d94df7d19b5ce7f7d713140cdad392c23a5ab3c996c19284d5f7b010000006912ceb41a4e62b77a3b7bec7cd777fbd5c8188821ecb2e1fd0a6e56ed33159200000000", + # hash: "c637b7e49b49272269dd8b409682f66cf28191d62028216cb200239ff5e2f8dd" + # }, + # sha_amounts: %{ + # data: "00e1f505000000000065cd1d00000000", + # hash: "4a3c35ddd52f7ffa12848588e82193156f84a476894a095352892c8f274fc863" + # }, + # sha_scriptpubkeys: %{ + # data: + # "22512088782d9105c8774f5f2f2857aec1519bf83aa53371bd44d3a0735e9841b73c281976a914a589246bf9c64679b6d186608c2a2bee949f23e088ac", + # hash: "480fb749c75701a17230706caddbfce6f422151d9722386e82bfcd3f4fdc2704" + # }, + # sha_sequences: %{ + # data: "0000000000000000", + # hash: "af5570f5a1810b7af78caf4bc70a660f0df51e42baf91d4de5b2328de0e83dfc" + # } + # } + # } + describe "decode/1" do test "decodes legacy bitcoin transaction" do txn_test = @txn_serialization_1 @@ -233,4 +612,153 @@ defmodule Bitcoinex.TransactionTest do assert "76a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac" == out_1.script_pub_key end end + + describe "bip341_sighash" do + test "BIP341 test vector" do + t = @bip341_test_vector + {:ok, unsigned_tx} = Transaction.decode(t.given.unsigned_tx) + + sha_prevouts = Transaction.bip341_sha_prevouts(unsigned_tx.inputs) + assert sha_prevouts == Utils.hex_to_bin(t.intermediary.hash_prevouts) + + prev_amounts = Enum.map(t.given.inputs, fn input -> input.amount_sats end) + sha_amounts = Transaction.bip341_sha_amounts(prev_amounts) + assert sha_amounts == Utils.hex_to_bin(t.intermediary.hash_amounts) + + prev_scriptpubkeys = + Enum.map(t.given.inputs, fn input -> + {:ok, s} = Script.parse_script(input.prev_scriptpubkey) + Script.serialize_with_compact_size(s) + end) + + sha_scriptpubkeys = Transaction.bip341_sha_scriptpubkeys(prev_scriptpubkeys) + assert sha_scriptpubkeys == Utils.hex_to_bin(t.intermediary.hash_script_pubkeys) + + sha_sequences = Transaction.bip341_sha_sequences(unsigned_tx.inputs) + assert sha_sequences == Utils.hex_to_bin(t.intermediary.hash_sequences) + + sha_outputs = Transaction.bip341_sha_outputs(unsigned_tx.outputs) + assert sha_outputs == Utils.hex_to_bin(t.intermediary.hash_outputs) + + # test sighash for each input + for i <- t.input_spending do + sigmsg = + Transaction.bip341_sigmsg( + unsigned_tx, + i.given.hash_type, + 0, + i.given.txin_index, + prev_amounts, + prev_scriptpubkeys + ) + assert Base.encode16(sigmsg, case: :lower) == i.intermediary.sigmsg + + sighash = Taproot.tagged_hash_tapsighash(sigmsg) + assert Base.encode16(sighash, case: :lower) == i.intermediary.sig_hash + + {:ok, sk} = + i.given.internal_privkey + |> Base.decode16!(case: :lower) + |> :binary.decode_unsigned() + |> PrivateKey.new() + + merkle_root = + if i.given.merkle_root == nil do + <<>> + else + i.given.merkle_root + |> Utils.hex_to_bin() + end + + + tweaked_sk = Taproot.tweak_privkey(sk, merkle_root) + assert tweaked_sk.d |> :binary.encode_unsigned() |> Base.encode16(case: :lower) == i.intermediary.tweaked_privkey + + # BIP341 declares test vectors to all use aux=0 + {:ok, sig} = Schnorr.sign(tweaked_sk, :binary.decode_unsigned(sighash), 0) + + hash_byte = + if i.given.hash_type == 0x00 do + <<>> + else + <> + end + assert Base.encode16(Signature.serialize_signature(sig) <> hash_byte, case: :lower) == Enum.at(i.expected.witness, 0) + end + + end + + test "SIGHASH_ALL" do + t = @bip341_sighash_all + {:ok, unsigned_tx} = Transaction.decode(t.unsigned_tx) + # intermediary hashes + sha_prevouts = Transaction.bip341_sha_prevouts(unsigned_tx.inputs) + assert sha_prevouts == Utils.hex_to_bin(t.intermediary.sha_prevouts.hash) + + prev_amounts = Enum.map(t.inputs, fn input -> input.value end) + sha_amounts = Transaction.bip341_sha_amounts(prev_amounts) + assert sha_amounts == Utils.hex_to_bin(t.intermediary.sha_amounts.hash) + + prev_scriptpubkeys = + Enum.map(t.inputs, fn input -> + {:ok, s} = Script.parse_script(input.prev_scriptpubkey) + Script.serialize_with_compact_size(s) + end) + + sha_scriptpubkeys = Transaction.bip341_sha_scriptpubkeys(prev_scriptpubkeys) + assert sha_scriptpubkeys == Utils.hex_to_bin(t.intermediary.sha_scriptpubkeys.hash) + + sha_sequences = Transaction.bip341_sha_sequences(unsigned_tx.inputs) + assert sha_sequences == Utils.hex_to_bin(t.intermediary.sha_sequences.hash) + + sha_outputs = Transaction.bip341_sha_outputs(unsigned_tx.outputs) + assert sha_outputs == Utils.hex_to_bin(t.intermediary.sha_outputs.hash) + + sigmsg = + Transaction.bip341_sigmsg( + unsigned_tx, + t.sighash_flag, + 0, + 1, + prev_amounts, + prev_scriptpubkeys + ) + + assert sigmsg == Utils.hex_to_bin(t.intermediary.data) + sighash = Taproot.tagged_hash_tapsighash(sigmsg) + assert sighash == Utils.hex_to_bin(t.intermediary.sighash) + # # TODO signing & check signed_tx + {:ok, sk} = + Enum.at(t.inputs, 1).privkey + |> Base.decode16!(case: :lower) + |> :binary.decode_unsigned() + |> PrivateKey.new() + + # test vector gives us Q pk and sk, no need to tweak + pk = PrivateKey.to_point(sk) + # TODO I Don't know what Aux was used for this test vector? + assert <<0x51, 0x20>> <> Point.x_bytes(pk) == Utils.hex_to_bin(Enum.at(t.inputs, 1).prev_scriptpubkey) + # {:ok, sig} = Schnorr.sign(sk, :binary.decode_unsigned(sighash), 0) + # assert Signature.serialize_signature(sig) == Utils.hex_to_bin(Enum.at(t.inputs, 1).signature) + end + + # test "SIGHASH_ANYONECANPAY_ALL" do + # t = @bip341_sighash_anyonecanpay_all + # {:ok, unsigned_tx} = Transaction.decode(t.unsigned_tx) + + # sha_outputs = Transaction.bip341_sha_outputs(unsigned_tx.outputs) + # assert sha_outputs == Base.decode16!(t.intermediary.sha_outputs.hash) + + # prev_amounts = Enum.map(t.inputs, fn input -> input.value end) + # prev_scriptpubkeys = Enum.map(t.inputs, fn input -> Base.decode16!(input.prev_scriptpubkey) end) + + # sigmsg = Transaction.bip341_sigmsg(unsigned_tx, t.sighash_flag, 0, 0, prev_amounts, prev_scriptpubkeys) + # assert sigmsg == Base.decode16!(t.intermediary.data) + + # end + + # test "SIGHASH_SINGLE" do + + # end + end end From 02a7fbd6f7146030fd630cf70740017f23cfb523 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sun, 5 Feb 2023 16:13:28 -0800 Subject: [PATCH 24/63] add scriptpath spend, op_checksigadd, scripts for examples --- lib/opcode.ex | 3 + lib/script.ex | 19 +- lib/taproot.ex | 4 +- lib/transaction.ex | 103 ++++++++--- ..._taproot_addr.exs => taproot_keyspend.exs} | 28 +-- scripts/taproot_multi_scriptspend.exs | 168 ++++++++++++++++++ scripts/taproot_scriptspend.exs | 109 ++++++++++++ test/transaction_test.exs | 17 +- 8 files changed, 406 insertions(+), 45 deletions(-) rename scripts/{use_taproot_addr.exs => taproot_keyspend.exs} (73%) create mode 100644 scripts/taproot_multi_scriptspend.exs create mode 100644 scripts/taproot_scriptspend.exs diff --git a/lib/opcode.ex b/lib/opcode.ex index 31e7c93..1fe6c0b 100644 --- a/lib/opcode.ex +++ b/lib/opcode.ex @@ -227,7 +227,9 @@ defmodule Bitcoinex.Opcode do op_codeseparator: 0xAB, op_checksig: 0xAC, op_checksigverify: 0xAD, + # disabled in tapscript op_checkmultisig: 0xAE, + # disabled in tapscript op_checkmultisigverify: 0xAF, op_nop1: 0xB0, op_nop2: 0xB1, @@ -240,6 +242,7 @@ defmodule Bitcoinex.Opcode do op_nop8: 0xB7, op_nop9: 0xB8, op_nop10: 0xB9, + op_checksigadd: 0xBA, op_smallinteger: 0xFA, op_pubkeys: 0xFB, op_pubkeyhash: 0xFD, diff --git a/lib/script.ex b/lib/script.ex index 666d2b7..8999d32 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -12,7 +12,7 @@ defmodule Bitcoinex.Script do @wsh_length 32 @tapkey_length 32 @h160_length 20 - @pubkey_lengths [33, 65] + @pubkey_lengths [@tapkey_length, 33, 65] # hash of G.x, used to construct unsolvable internal taproot keys @h 0x0250929B74C1A04954B78B4B6035E97A5E078A5A0F28EC96D547BFEE9ACE803AC0 @@ -339,7 +339,7 @@ defmodule Bitcoinex.Script do true end - def is_p2pk?(%__MODULE__{}), do: false + def is_p2pk?(_), do: false @doc """ is_p2pkh? returns whether a given script is of the p2pkh format: @@ -351,7 +351,7 @@ defmodule Bitcoinex.Script do }), do: true - def is_p2pkh?(%__MODULE__{}), do: false + def is_p2pkh?(_), do: false @doc """ is_p2sh? returns whether a given script is of the p2sh format: @@ -361,7 +361,7 @@ defmodule Bitcoinex.Script do def is_p2sh?(%__MODULE__{items: [0xA9, @h160_length, <<_::binary-size(@h160_length)>>, 0x87]}), do: true - def is_p2sh?(%__MODULE__{}), do: false + def is_p2sh?(_), do: false @doc """ is_p2wpkh? returns whether a given script is of the p2wpkh format: @@ -371,7 +371,7 @@ defmodule Bitcoinex.Script do def is_p2wpkh?(%__MODULE__{items: [0x00, @h160_length, <<_::binary-size(@h160_length)>>]}), do: true - def is_p2wpkh?(%__MODULE__{}), do: false + def is_p2wpkh?(_), do: false @doc """ is_p2wsh? returns whether a given script is of the p2wsh format: @@ -381,7 +381,7 @@ defmodule Bitcoinex.Script do def is_p2wsh?(%__MODULE__{items: [0x00, @wsh_length, <<_::binary-size(@wsh_length)>>]}), do: true - def is_p2wsh?(%__MODULE__{}), do: false + def is_p2wsh?(_), do: false @doc """ is_p2tr? returns whether a given script is of the p2tr format: @@ -391,7 +391,7 @@ defmodule Bitcoinex.Script do def is_p2tr?(%__MODULE__{items: [0x51, @tapkey_length, <<_::binary-size(@tapkey_length)>>]}), do: true - def is_p2tr?(%__MODULE__{}), do: false + def is_p2tr?(_), do: false @doc """ is_multisig? returns whether a given script is of the raw multisig format: @@ -464,12 +464,13 @@ defmodule Bitcoinex.Script do create_p2pk creates a p2pk script using the passed public key """ @spec create_p2pk(binary) :: {:ok, t()} | {:error, String.t()} - def create_p2pk(pk) when is_binary(pk) and byte_size(pk) in [33, 65] do + def create_p2pk(pk) when is_binary(pk) and byte_size(pk) in @pubkey_lengths do {:ok, s} = push_op(new(), 0xAC) push_data(s, pk) end - def create_p2pk(_), do: {:error, "pubkey must be 33 or 65 bytes compressed or uncompressed SEC"} + def create_p2pk(_), + do: {:error, "pubkey must be 32, 33, 65 bytes compressed or uncompressed SEC"} @doc """ create_p2pkh creates a p2pkh script using the passed 20-byte public key hash diff --git a/lib/taproot.ex b/lib/taproot.ex index 85074f8..b098688 100644 --- a/lib/taproot.ex +++ b/lib/taproot.ex @@ -6,7 +6,9 @@ defmodule Bitcoinex.Taproot do @n Params.curve().n - # @bip342_leaf_version 0xc0 + @bip342_leaf_version 0xC0 + + def bip342_leaf_version(), do: @bip342_leaf_version @type tapnode :: {tapnode, tapnode} | TapLeaf.t() | nil diff --git a/lib/transaction.ex b/lib/transaction.ex index 6ac8c83..2cae4fe 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -36,8 +36,6 @@ defmodule Bitcoinex.Transaction do @sighash_anyonecanpay_none 0x82 @sighash_anyonecanpay_single 0x83 - - @valid_sighash_flags [ @sighash_default, @sighash_all, @@ -65,13 +63,18 @@ defmodule Bitcoinex.Transaction do ) end - def bip341_sighash( tx = %__MODULE__{}, - hash_type, - ext_flag, - input_idx, - prev_amounts, - prev_scriptpubkeys) do - sigmsg = bip341_sigmsg(tx, hash_type, ext_flag, input_idx, prev_amounts, prev_scriptpubkeys) + def bip341_sighash( + tx = %__MODULE__{}, + hash_type, + ext_flag, + input_idx, + prev_amounts, + prev_scriptpubkeys, + opts \\ [] + ) do + sigmsg = + bip341_sigmsg(tx, hash_type, ext_flag, input_idx, prev_amounts, prev_scriptpubkeys, opts) + Taproot.tagged_hash_tapsighash(sigmsg) end @@ -81,12 +84,23 @@ defmodule Bitcoinex.Transaction do non_neg_integer(), non_neg_integer(), list(non_neg_integer()), - list(<<_::280>>) + list(<<_::280>>), + list({:tapleaf, Taproot.TapLeaf.t()}) ) :: binary - def bip341_sigmsg(_, _, ext_flag, _, _, _) when ext_flag < 0 or ext_flag > 127, + def bip341_sigmsg( + tx, + hash_type, + ext_flag, + input_idx, + prev_amounts, + prev_scriptpubkeys, + opts \\ [] + ) + + def bip341_sigmsg(_, _, ext_flag, _, _, _, _) when ext_flag < 0 or ext_flag > 127, do: {:error, "ext_flag out of range 0-127"} - def bip341_sigmsg(_, hash_type, _, _, _, _) when hash_type not in @valid_sighash_flags, + def bip341_sigmsg(_, hash_type, _, _, _, _, _) when hash_type not in @valid_sighash_flags, do: {:error, "invalid sighash flag"} def bip341_sigmsg( @@ -95,21 +109,42 @@ defmodule Bitcoinex.Transaction do ext_flag, input_idx, prev_amounts, - prev_scriptpubkeys + prev_scriptpubkeys, + opts ) do tx_data = bip341_tx_data(tx, hash_type, prev_amounts, prev_scriptpubkeys) - bip341_sigmsg(tx, hash_type, ext_flag, input_idx, prev_amounts, prev_scriptpubkeys, tx_data) + + bip341_sigmsg_with_cache( + tx, + hash_type, + ext_flag, + input_idx, + prev_amounts, + prev_scriptpubkeys, + tx_data, + opts + ) end - # TODO: refactor this function to take in individual subhashes instead of single cache - def bip341_sigmsg( + @spec bip341_sigmsg_with_cache( + t(), + non_neg_integer(), + non_neg_integer(), + non_neg_integer(), + list(non_neg_integer()), + list(binary), + binary, + list({:tapleaf, Taproot.TapLeaf.t()}) + ) :: binary + def bip341_sigmsg_with_cache( tx = %__MODULE__{}, hash_type, ext_flag, input_idx, prev_amounts, prev_scriptpubkeys, - cached_tx_data + cached_tx_data, + opts \\ [] ) do hash_byte = :binary.encode_unsigned(hash_type) @@ -125,7 +160,21 @@ defmodule Bitcoinex.Transaction do output_data = bip341_output_data(tx, input_idx, hash_type) - <<0>> <> hash_byte <> cached_tx_data <> input_data <> output_data + tapleaf = Keyword.get(opts, :tapleaf, nil) + + ext = + case tapleaf do + tl = %Taproot.TapLeaf{} -> + # TODO last_executed_codesep_pos not implemented + sigmsg_extension(ext_flag, tl) + + nil -> + sigmsg_extension(ext_flag) + end + + <<0>> <> + hash_byte <> + cached_tx_data <> input_data <> output_data <> ext end # The results of this function can be reused across input signings. @@ -203,9 +252,10 @@ defmodule Bitcoinex.Transaction do Bitwise.band(hash_type, 3) == @sighash_single end - @spec get_annex(t(), non_neg_integer()) :: nil | binary | {:error, } + @spec get_annex(t(), non_neg_integer()) :: nil | binary | {:error} def get_annex(%__MODULE__{witnesses: nil}, _), do: nil def get_annex(%__MODULE__{witnesses: []}, _), do: nil + def get_annex(%__MODULE__{witnesses: witnesses, inputs: inputs}, input_idx) when input_idx >= 0 and input_idx < length(inputs) do witnesses @@ -256,6 +306,18 @@ defmodule Bitcoinex.Transaction do |> Utils.sha256() end + def sigmsg_extension(0), do: <<>> + + def sigmsg_extension(1, tapleaf, last_executed_codesep_pos \\ 0xFFFFFFFF), + do: bip342_sigmsg_ext(tapleaf, last_executed_codesep_pos) + + def bip342_sigmsg_ext(tapleaf = %Taproot.TapLeaf{}, last_executed_codesep_pos \\ 0xFFFFFFFF) do + key_version = 0x00 + + Taproot.TapLeaf.hash(tapleaf) <> + <> <> <> + end + @doc """ Decodes a transaction in a hex encoded string into binary. """ @@ -486,6 +548,7 @@ defmodule Bitcoinex.Transaction.Witness do @spec get_annex(t()) :: nil | binary def get_annex(%__MODULE__{txinwitness: witnesses}) when length(witnesses) < 2, do: nil + def get_annex(%__MODULE__{txinwitness: witnesses}) do last = witnesses @@ -493,7 +556,7 @@ defmodule Bitcoinex.Transaction.Witness do |> Enum.at(0) case last do - #TODO switch to binary or int once witnesses are no longer stored as strings + # TODO switch to binary or int once witnesses are no longer stored as strings "50" <> _ -> last _ -> nil end diff --git a/scripts/use_taproot_addr.exs b/scripts/taproot_keyspend.exs similarity index 73% rename from scripts/use_taproot_addr.exs rename to scripts/taproot_keyspend.exs index 8429361..8229459 100644 --- a/scripts/use_taproot_addr.exs +++ b/scripts/taproot_keyspend.exs @@ -5,27 +5,33 @@ alias Bitcoinex.Secp256k1 alias Bitcoinex.Secp256k1.{Point, PrivateKey, Signature, Schnorr} alias Bitcoinex.Taproot - -new_secret = fn -> - 32 - |> :crypto.strong_rand_bytes() - |> :binary.decode_unsigned() +new_privkey = fn -> + {:ok, sk} = + 32 + |> :crypto.strong_rand_bytes() + |> :binary.decode_unsigned() + |> PrivateKey.new() + Secp256k1.force_even_y(sk) end -{:ok, sk} = new_secret.() |> PrivateKey.new() -sk = Secp256k1.force_even_y(sk) +sk = new_privkey.() pk = PrivateKey.to_point(sk) +# EDIT if you want to include a script tree (optional, but spending is not yet enabled) script_tree = nil {:ok, scriptpubkey} = Script.create_p2tr(pk, script_tree) {:ok, addr} = Script.to_address(scriptpubkey, :regtest) +# EDIT to the unique addr # addr = "bcrt1pfh4qvlzrgmf6f8e6urjkf3ax83kz02xqc8zujnpeycxgc3wrqmxs8py692" +# EDIT to the txid of your send to the addr above txid = "bcdf4b0088a75c139d0b4858164534585b735dcdd18321824a31936abcbf04b4" +# EDIT to the vout of the output you created for the addr vout = 0 +# EDIT to the amount you sent in the UTXO above amount = 100_000_000 - +# EDIT where you would like to spend to dest_addr = "bcrt1pzeg29d38m506gtnunlg2tjh4hpvv4mtkjg5tku34ad24830pta5qg0kyn6" {:ok, dest_script, _network} = Script.from_address(dest_addr) @@ -49,7 +55,7 @@ tx = %Transaction{ lock_time: 0 } - #sighash_default +#sighash_default hash_type = 0x00 ext_flag = 0 input_idx = 0 @@ -87,6 +93,8 @@ tx = %Transaction{tx | witnesses: [ ] } -Transaction.serialize(tx) +Transaction.Utils.serialize(tx) +# the last zero arg is to disable max_fee_rate +# use bitcoin-cli sendrawtransaction 0 # txid: 86dcdf6a88480a16524aa353b47d11228d67f96f59c4a645d65d4aac09330065 diff --git a/scripts/taproot_multi_scriptspend.exs b/scripts/taproot_multi_scriptspend.exs new file mode 100644 index 0000000..101062c --- /dev/null +++ b/scripts/taproot_multi_scriptspend.exs @@ -0,0 +1,168 @@ +alias Bitcoinex.Secp256k1 +alias Bitcoinex.Transaction +alias Bitcoinex.Script +alias Bitcoinex.Secp256k1 +alias Bitcoinex.Secp256k1.{Point, PrivateKey, Signature, Schnorr} +alias Bitcoinex.Taproot + +new_privkey = fn -> + {:ok, sk} = + 32 + |> :crypto.strong_rand_bytes() + |> :binary.decode_unsigned() + |> PrivateKey.new() + Secp256k1.force_even_y(sk) +end + +# internal_key +internal_sk = new_privkey.() +internal_pk = PrivateKey.to_point(internal_sk) + +# p2pk script key 1 +p2pk1_sk = new_privkey.() +p2pk1_pk = PrivateKey.to_point(p2pk1_sk) + +{:ok, p2pk1_script} = Script.create_p2pk(Point.x_bytes(p2pk1_pk)) + +# p2pk script key 2 +p2pk2_sk = new_privkey.() +p2pk2_pk = PrivateKey.to_point(p2pk2_sk) + +{:ok, p2pk2_script} = Script.create_p2pk(Point.x_bytes(p2pk2_pk)) + + +leaf0 = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk1_script) + +leaf1 = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk2_script) + +# single leaf +script_tree = {leaf0, leaf1} + +{:ok, scriptpubkey} = Script.create_p2tr(internal_pk, script_tree) + +{:ok, addr} = Script.to_address(scriptpubkey, :regtest) +# addr = bcrt1pwfprzzadfx3meyn5glm0rfmyl6lgmelqgwfqvxwx3hr756tcr77spqf2aq +# broadcast and mine funding tx +# EDIT note txid of funding tx +txid = "f11bfaa48121c582f652f0a1643c7b5a1692fe3582dff7819d2787486e595f68" +# EDIT to the vout of the output you created for the addr +vout = 0 +# EDIT to the amount you sent in the UTXO above +amount = 100_000_000 +# EDIT where you would like to spend to +dest_addr = "bcrt1pzeg29d38m506gtnunlg2tjh4hpvv4mtkjg5tku34ad24830pta5qg0kyn6" + +{:ok, dest_script, _network} = Script.from_address(dest_addr) + +tx = %Transaction{ + version: 1, + inputs: [ + %Transaction.In{ + prev_txid: txid, + prev_vout: vout, + script_sig: "", + sequence_no: 2147483648, + } + ], + outputs: [ + %Transaction.Out{ + value: 50_000_000, + script_pub_key: Script.to_hex(dest_script) + } + ], + lock_time: 0 +} + +#sighash_default +hash_type = 0x00 +ext_flag = 1 +input_idx = 0 + +# here is where you choose which leaf to use. script_idx must match leaf # +script_idx = 0 +leaf = leaf0 +sk = p2pk1_sk +script = p2pk1_script + +sighash = Transaction.bip341_sighash( + tx, + hash_type, + ext_flag, + input_idx, + [amount], + [Script.serialize_with_compact_size(scriptpubkey)], + tapleaf: leaf +) + +aux_rand = 0 + +{:ok, sig} = Schnorr.sign(sk, :binary.decode_unsigned(sighash), aux_rand) + +control_block = Taproot.build_control_block(internal_pk, script_tree, script_idx) + +hash_byte = + if hash_type == 0x00 do + <<>> + else + <> + end + +sig_hex = Signature.serialize_signature(sig) <> hash_byte |> Base.encode16(case: :lower) +script_hex = Script.to_hex(script) +control_block_hex = control_block |> Base.encode16(case: :lower) + +tx = %Transaction{tx | witnesses: [ + %Transaction.Witness{ + txinwitness: [sig_hex, script_hex, control_block_hex] + } +] +} + +Transaction.Utils.serialize(tx) |> Base.encode16(case: :lower) + +# 01000000000101685f596e4887279d81f7df8235fe92165a7b3c64a1f052f682c52181a4fa1bf10000000000000000800180f0fa02000000002251201650a2b627dd1fa42e7c9fd0a5caf5b858caed769228bb7235eb5553c5e15f680340f53ea81564380d49cd2bdf4bee89b5784a846048818cda51c284c6fd8eaf542bd3d25df4d696c5063d016b8fcab85e2eb78503bca277783a721a2c053c7266972220f6c4e1e4276a75cfcbba374608887f64e09a55a9e6166fe4856324d084c4a784ac41c1a280169a8ed09e3c4b34f832c0ae44d78bb081631c00084f12a602218734a27d9d06d23f4e9ac0a6d9eb2923d59c40b113cb3a150062410da36bc7408584faee00000000 + +# ALTERNATE script +script_idx = 1 +leaf = leaf1 +sk = p2pk2_sk +script = p2pk2_script + +sighash = Transaction.bip341_sighash( + tx, + hash_type, + ext_flag, + input_idx, + [amount], + [Script.serialize_with_compact_size(scriptpubkey)], + tapleaf: leaf +) + +aux_rand = 0 + +{:ok, sig} = Schnorr.sign(sk, :binary.decode_unsigned(sighash), aux_rand) + +control_block = Taproot.build_control_block(internal_pk, script_tree, script_idx) + +hash_byte = + if hash_type == 0x00 do + <<>> + else + <> + end + +sig_hex = Signature.serialize_signature(sig) <> hash_byte |> Base.encode16(case: :lower) +script_hex = Script.to_hex(script) +control_block_hex = control_block |> Base.encode16(case: :lower) + +tx = %Transaction{tx | witnesses: [ + %Transaction.Witness{ + txinwitness: [sig_hex, script_hex, control_block_hex] + } +] +} + +Transaction.Utils.serialize(tx) |> Base.encode16(case: :lower) + + +# a97fb556cff86dda196cb2c9fa4892de259dc8b910ec72b60975822b88b05130 diff --git a/scripts/taproot_scriptspend.exs b/scripts/taproot_scriptspend.exs new file mode 100644 index 0000000..acafaf8 --- /dev/null +++ b/scripts/taproot_scriptspend.exs @@ -0,0 +1,109 @@ +alias Bitcoinex.Secp256k1 +alias Bitcoinex.Transaction +alias Bitcoinex.Script +alias Bitcoinex.Secp256k1 +alias Bitcoinex.Secp256k1.{Point, PrivateKey, Signature, Schnorr} +alias Bitcoinex.Taproot + +new_privkey = fn -> + {:ok, sk} = + 32 + |> :crypto.strong_rand_bytes() + |> :binary.decode_unsigned() + |> PrivateKey.new() + Secp256k1.force_even_y(sk) +end + +# internal_key +internal_sk = new_privkey.() +internal_pk = PrivateKey.to_point(internal_sk) + +# p2pk script key +p2pk_sk = new_privkey.() +p2pk_pk = PrivateKey.to_point(p2pk_sk) + +{:ok, p2pk_script} = Script.create_p2pk(Point.x_bytes(p2pk_pk)) + +# single leaf +script_tree = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk_script) + +{:ok, scriptpubkey} = Script.create_p2tr(internal_pk, script_tree) + +{:ok, addr} = Script.to_address(scriptpubkey, :regtest) +# addr = bcrt1ptkdqw3d39fuzg9qtw6r6e2rj098tp5vffhhnyu8r7m62j00q3atq8hpx0m +# broadcast and mine funding tx +# EDIT note txid of funding tx +txid = "0492bc1dce2ee85c92533942219e1ca72069a395d2dc77d595bd2f171da4e6ce" +# EDIT to the vout of the output you created for the addr +vout = 0 +# EDIT to the amount you sent in the UTXO above +amount = 100_000_000 +# EDIT where you would like to spend to +dest_addr = "bcrt1pzeg29d38m506gtnunlg2tjh4hpvv4mtkjg5tku34ad24830pta5qg0kyn6" + +{:ok, dest_script, _network} = Script.from_address(dest_addr) + +tx = %Transaction{ + version: 1, + inputs: [ + %Transaction.In{ + prev_txid: txid, + prev_vout: vout, + script_sig: "", + sequence_no: 2147483648, + } + ], + outputs: [ + %Transaction.Out{ + value: 50_000_000, + script_pub_key: Script.to_hex(dest_script) + } + ], + lock_time: 0 +} + +#sighash_default +hash_type = 0x00 +ext_flag = 1 +input_idx = 0 + +sighash = Transaction.bip341_sighash( + tx, + hash_type, + ext_flag, + input_idx, + [amount], + [Script.serialize_with_compact_size(scriptpubkey)], + tapleaf: script_tree +) + + + +aux_rand = 0 +script_idx = 0 + +{:ok, sig} = Schnorr.sign(p2pk_sk, :binary.decode_unsigned(sighash), aux_rand) + +control_block = Taproot.build_control_block(internal_pk, script_tree, script_idx) + +hash_byte = + if hash_type == 0x00 do + <<>> + else + <> + end + +sig_hex = Signature.serialize_signature(sig) <> hash_byte |> Base.encode16(case: :lower) +script_hex = Script.to_hex(p2pk_script) +control_block_hex = control_block |> Base.encode16(case: :lower) + +tx = %Transaction{tx | witnesses: [ + %Transaction.Witness{ + txinwitness: [sig_hex, script_hex, control_block_hex] + } +] +} + +Transaction.Utils.serialize(tx) |> Base.encode16(case: :lower) + +# a97fb556cff86dda196cb2c9fa4892de259dc8b910ec72b60975822b88b05130 diff --git a/test/transaction_test.exs b/test/transaction_test.exs index 012d33b..cec9aa5 100644 --- a/test/transaction_test.exs +++ b/test/transaction_test.exs @@ -328,6 +328,7 @@ defmodule Bitcoinex.TransactionTest do } } + # TODO write test # SIGHASH_ANYONECANPAY(ALL) # @bip341_sighash_anyonecanpay_all %{ # sighash_flag: 0x81, @@ -358,6 +359,7 @@ defmodule Bitcoinex.TransactionTest do # } # } + # TODO write test # SIGHASH_SINGLE # @bip341_sighash_single %{ # sighash_flag: 0x03, @@ -651,6 +653,7 @@ defmodule Bitcoinex.TransactionTest do prev_amounts, prev_scriptpubkeys ) + assert Base.encode16(sigmsg, case: :lower) == i.intermediary.sigmsg sighash = Taproot.tagged_hash_tapsighash(sigmsg) @@ -670,9 +673,10 @@ defmodule Bitcoinex.TransactionTest do |> Utils.hex_to_bin() end - tweaked_sk = Taproot.tweak_privkey(sk, merkle_root) - assert tweaked_sk.d |> :binary.encode_unsigned() |> Base.encode16(case: :lower) == i.intermediary.tweaked_privkey + + assert tweaked_sk.d |> :binary.encode_unsigned() |> Base.encode16(case: :lower) == + i.intermediary.tweaked_privkey # BIP341 declares test vectors to all use aux=0 {:ok, sig} = Schnorr.sign(tweaked_sk, :binary.decode_unsigned(sighash), 0) @@ -683,9 +687,10 @@ defmodule Bitcoinex.TransactionTest do else <> end - assert Base.encode16(Signature.serialize_signature(sig) <> hash_byte, case: :lower) == Enum.at(i.expected.witness, 0) - end + assert Base.encode16(Signature.serialize_signature(sig) <> hash_byte, case: :lower) == + Enum.at(i.expected.witness, 0) + end end test "SIGHASH_ALL" do @@ -737,7 +742,9 @@ defmodule Bitcoinex.TransactionTest do # test vector gives us Q pk and sk, no need to tweak pk = PrivateKey.to_point(sk) # TODO I Don't know what Aux was used for this test vector? - assert <<0x51, 0x20>> <> Point.x_bytes(pk) == Utils.hex_to_bin(Enum.at(t.inputs, 1).prev_scriptpubkey) + assert <<0x51, 0x20>> <> Point.x_bytes(pk) == + Utils.hex_to_bin(Enum.at(t.inputs, 1).prev_scriptpubkey) + # {:ok, sig} = Schnorr.sign(sk, :binary.decode_unsigned(sighash), 0) # assert Signature.serialize_signature(sig) == Utils.hex_to_bin(Enum.at(t.inputs, 1).signature) end From 11fe186ef511919e1db48ff6fffc690fba761633 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sun, 5 Feb 2023 21:56:43 -0800 Subject: [PATCH 25/63] add tx hex to taproot multi-spend ex --- scripts/taproot_multi_scriptspend.exs | 29 ++++++++++++++------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/scripts/taproot_multi_scriptspend.exs b/scripts/taproot_multi_scriptspend.exs index 101062c..c68fa38 100644 --- a/scripts/taproot_multi_scriptspend.exs +++ b/scripts/taproot_multi_scriptspend.exs @@ -19,21 +19,21 @@ internal_sk = new_privkey.() internal_pk = PrivateKey.to_point(internal_sk) # p2pk script key 1 -p2pk1_sk = new_privkey.() -p2pk1_pk = PrivateKey.to_point(p2pk1_sk) +p2pk0_sk = new_privkey.() +p2pk1_pk = PrivateKey.to_point(p2pk0_sk) -{:ok, p2pk1_script} = Script.create_p2pk(Point.x_bytes(p2pk1_pk)) +{:ok, p2pk0_script} = Script.create_p2pk(Point.x_bytes(p2pk1_pk)) # p2pk script key 2 -p2pk2_sk = new_privkey.() -p2pk2_pk = PrivateKey.to_point(p2pk2_sk) +p2pk1_sk = new_privkey.() +p2pk2_pk = PrivateKey.to_point(p2pk1_sk) -{:ok, p2pk2_script} = Script.create_p2pk(Point.x_bytes(p2pk2_pk)) +{:ok, p2pk1_script} = Script.create_p2pk(Point.x_bytes(p2pk2_pk)) -leaf0 = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk1_script) +leaf0 = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk0_script) -leaf1 = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk2_script) +leaf1 = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk1_script) # single leaf script_tree = {leaf0, leaf1} @@ -79,10 +79,12 @@ ext_flag = 1 input_idx = 0 # here is where you choose which leaf to use. script_idx must match leaf # + +# SCRIPT 0 script_idx = 0 leaf = leaf0 -sk = p2pk1_sk -script = p2pk1_script +sk = p2pk0_sk +script = p2pk0_script sighash = Transaction.bip341_sighash( tx, @@ -125,8 +127,8 @@ Transaction.Utils.serialize(tx) |> Base.encode16(case: :lower) # ALTERNATE script script_idx = 1 leaf = leaf1 -sk = p2pk2_sk -script = p2pk2_script +sk = p2pk1_sk +script = p2pk1_script sighash = Transaction.bip341_sighash( tx, @@ -164,5 +166,4 @@ tx = %Transaction{tx | witnesses: [ Transaction.Utils.serialize(tx) |> Base.encode16(case: :lower) - -# a97fb556cff86dda196cb2c9fa4892de259dc8b910ec72b60975822b88b05130 +# 01000000000101685f596e4887279d81f7df8235fe92165a7b3c64a1f052f682c52181a4fa1bf10000000000000000800180f0fa02000000002251201650a2b627dd1fa42e7c9fd0a5caf5b858caed769228bb7235eb5553c5e15f68034077228590ea058878df865afdfbb950be53d55da15ee1a4a7f03ee5cc0c01003fb3d290c799b428a400d0743ebb3c9caa2b1c27f99d851cee6464ea7642f939582220e2aa46c4e1f43cb01c89195b39ea80dbf91d7054052fa0cb5805828b786cd734ac41c1a280169a8ed09e3c4b34f832c0ae44d78bb081631c00084f12a602218734a27d509753bb6e46ebbdcfb16bbd3c7c07b59b7aede009760ba50a5ae8ae1d0ee49200000000 From 8792568db1be9257fa2fd7db4a5c2ee28bab2b5d Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sun, 5 Feb 2023 22:56:41 -0800 Subject: [PATCH 26/63] cleanup --- lib/lightning_network/invoice.ex | 2 +- lib/taproot.ex | 28 +++-- lib/transaction.ex | 17 +++ scripts/taproot_multi_scriptspend.exs | 4 +- scripts/taproot_scriptspend.exs | 2 +- test/script_test.exs | 5 - test/transaction_test.exs | 173 ++++++++++---------------- 7 files changed, 105 insertions(+), 126 deletions(-) diff --git a/lib/lightning_network/invoice.ex b/lib/lightning_network/invoice.ex index e216318..9819006 100644 --- a/lib/lightning_network/invoice.ex +++ b/lib/lightning_network/invoice.ex @@ -150,7 +150,7 @@ defmodule Bitcoinex.LightningNetwork.Invoice do # TODO if destination exist from tagged field, we dun need to recover but to verify it with signature # but that require convert lg sig before using secp256k1 to verify it - # TODO refactor too nested + # TODO refactor to nested case Bitcoinex.Secp256k1.Ecdsa.ecdsa_recover_compact(hash, signature, recoveryId) do {:ok, pubkey} -> if is_nil(destination) or destination == pubkey do diff --git a/lib/taproot.ex b/lib/taproot.ex index b098688..3954ff3 100644 --- a/lib/taproot.ex +++ b/lib/taproot.ex @@ -8,6 +8,7 @@ defmodule Bitcoinex.Taproot do @bip342_leaf_version 0xC0 + @spec bip342_leaf_version :: 192 def bip342_leaf_version(), do: @bip342_leaf_version @type tapnode :: {tapnode, tapnode} | TapLeaf.t() | nil @@ -65,12 +66,16 @@ defmodule Bitcoinex.Taproot do def tagged_hash_tapsighash(sigmsg), do: Utils.tagged_hash("TapSighash", sigmsg) defmodule TapLeaf do + @moduledoc """ + TapLeaf represents a leaf of a Taproot Merkle tree. A leaf + contains a version and a Script. + """ alias Bitcoinex.Script alias Bitcoinex.Taproot @type t :: %__MODULE__{ version: non_neg_integer(), - script: binary + script: Script.t() } @enforce_keys [ :version, @@ -82,19 +87,18 @@ defmodule Bitcoinex.Taproot do ] @doc """ - from_script constructs a TapLeaf from a leaf_version and Script. + new constructs a TapLeaf from a leaf_version and Script. The script is stored as binary with the compact size prepended to it. """ - @spec from_script(non_neg_integer(), Script.t()) :: t() - def from_script(leaf_version, script = %Script{}) do - s = Script.serialize_with_compact_size(script) - %__MODULE__{version: leaf_version, script: s} + @spec new(non_neg_integer(), Script.t()) :: t() + def new(leaf_version, script = %Script{}) do + %__MODULE__{version: leaf_version, script: script} end @spec from_string(non_neg_integer(), String.t()) :: t() def from_string(leaf_version, script_hex) do {:ok, script} = Script.parse_script(script_hex) - from_script(leaf_version, script) + new(leaf_version, script) end @doc """ @@ -102,7 +106,8 @@ defmodule Bitcoinex.Taproot do store the Script in binary and alredy prepended with the compact size, so that is not added here. """ @spec serialize(t()) :: binary - def serialize(%__MODULE__{version: v, script: s}), do: :binary.encode_unsigned(v) <> s + def serialize(%__MODULE__{version: v, script: s}), + do: :binary.encode_unsigned(v) <> Script.serialize_with_compact_size(s) @doc """ hash returns the Hash_TapLeaf of the serialized TapLeaf @@ -179,6 +184,11 @@ defmodule Bitcoinex.Taproot do {:cont, tagged_hash_tapbranch(l <> r)} end + @doc """ + validate_taproot_scriptpath_spend DOES NOT validate the actual script according to BIP342. + It only validates the BIP341 rules around how a scriptPath spend works. + See: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#script-validation-rules + """ @spec validate_taproot_scriptpath_spend(Point.t(), binary, binary) :: bool | {:error, String.t()} def validate_taproot_scriptpath_spend( @@ -206,7 +216,7 @@ defmodule Bitcoinex.Taproot do {tk, {:ok, pk}} -> validate_q(q_point, Math.add(pk, tk), c) - # TODO MUST EVALUATE THE ACTUAL SCRIPT + # TODO evaluate actual script? end end diff --git a/lib/transaction.ex b/lib/transaction.ex index 2cae4fe..cdae60d 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -63,6 +63,15 @@ defmodule Bitcoinex.Transaction do ) end + @spec bip341_sighash( + t(), + non_neg_integer(), + non_neg_integer(), + non_neg_integer(), + list(non_neg_integer()), + list(<<_::280>>), + list({:tapleaf, Taproot.TapLeaf.t()}) + ) :: <<_::256>> def bip341_sighash( tx = %__MODULE__{}, hash_type, @@ -178,6 +187,8 @@ defmodule Bitcoinex.Transaction do end # The results of this function can be reused across input signings. + @spec bip341_tx_data(t(), non_neg_integer(), list(non_neg_integer()), list(<<_::280>>)) :: + binary def bip341_tx_data(tx, hash_type, prev_amounts, prev_scriptpubkeys) do version = <> lock_time = <> @@ -240,6 +251,7 @@ defmodule Bitcoinex.Transaction do end end + @spec hash_type_is_anyonecanpay(non_neg_integer()) :: boolean def hash_type_is_anyonecanpay(hash_type), do: Bitwise.band(hash_type, @sighash_anyonecanpay) == @sighash_anyonecanpay @@ -272,30 +284,35 @@ defmodule Bitcoinex.Transaction do |> Utils.sha256() end + @spec bip341_sha_amounts(list(non_neg_integer())) :: <<_::256>> def bip341_sha_amounts(prev_amounts) do prev_amounts |> Enum.reduce(<<>>, fn amount, acc -> acc <> <> end) |> Utils.sha256() end + @spec bip341_sha_scriptpubkeys(list(<<_::280>>)) :: <<_::256>> def bip341_sha_scriptpubkeys(prev_scriptpubkeys) do prev_scriptpubkeys |> Enum.reduce(<<>>, fn script, acc -> acc <> script end) |> Utils.sha256() end + @spec bip341_sha_sequences(list(Transaction.In.t())) :: <<_::256>> def bip341_sha_sequences(inputs) do inputs |> Transaction.In.serialize_sequences() |> Utils.sha256() end + @spec bip341_sha_outputs(list(Transaction.Out.t())) :: <<_::256>> def bip341_sha_outputs(outputs) do outputs |> Transaction.Out.serialize_outputs() |> Utils.sha256() end + @spec bip341_sha_annex(nil | binary) :: <<_::256>> def bip341_sha_annex(nil), do: <<>> def bip341_sha_annex(annex) do diff --git a/scripts/taproot_multi_scriptspend.exs b/scripts/taproot_multi_scriptspend.exs index c68fa38..ed3e0e5 100644 --- a/scripts/taproot_multi_scriptspend.exs +++ b/scripts/taproot_multi_scriptspend.exs @@ -31,9 +31,9 @@ p2pk2_pk = PrivateKey.to_point(p2pk1_sk) {:ok, p2pk1_script} = Script.create_p2pk(Point.x_bytes(p2pk2_pk)) -leaf0 = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk0_script) +leaf0 = Taproot.TapLeaf.new(Taproot.bip342_leaf_version(), p2pk0_script) -leaf1 = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk1_script) +leaf1 = Taproot.TapLeaf.new(Taproot.bip342_leaf_version(), p2pk1_script) # single leaf script_tree = {leaf0, leaf1} diff --git a/scripts/taproot_scriptspend.exs b/scripts/taproot_scriptspend.exs index acafaf8..6c2ab8d 100644 --- a/scripts/taproot_scriptspend.exs +++ b/scripts/taproot_scriptspend.exs @@ -25,7 +25,7 @@ p2pk_pk = PrivateKey.to_point(p2pk_sk) {:ok, p2pk_script} = Script.create_p2pk(Point.x_bytes(p2pk_pk)) # single leaf -script_tree = Taproot.TapLeaf.from_script(Taproot.bip342_leaf_version(), p2pk_script) +script_tree = Taproot.TapLeaf.new(Taproot.bip342_leaf_version(), p2pk_script) {:ok, scriptpubkey} = Script.create_p2tr(internal_pk, script_tree) diff --git a/test/script_test.exs b/test/script_test.exs index 9f2dad6..e361dfd 100644 --- a/test/script_test.exs +++ b/test/script_test.exs @@ -1596,11 +1596,6 @@ defmodule Bitcoinex.ScriptTest do test "test bip341 test vectors" do for t <- @bip_341_script_pubkey_test_vectors do {:ok, p} = Point.lift_x(t.given.internal_pubkey) - # TODO move to Taproot_test - # {_node, hash} = Taproot.merkelize_script_tree(t.script_tree) - # assert Base.encode16(hash, case: :lower) == t.intermediary.merkle_root - # TODO check tweak & tweak_pubkey - {:ok, script} = Script.create_p2tr(p, t.given.script_tree) assert Script.to_hex(script) == t.expected.script_pubkey assert Script.to_address(script, :mainnet) == {:ok, t.expected.bip350_address} diff --git a/test/transaction_test.exs b/test/transaction_test.exs index cec9aa5..e730f4d 100644 --- a/test/transaction_test.exs +++ b/test/transaction_test.exs @@ -6,7 +6,7 @@ defmodule Bitcoinex.TransactionTest do alias Bitcoinex.Utils alias Bitcoinex.Script alias Bitcoinex.Taproot - alias Bitcoinex.Secp256k1.{PrivateKey, Schnorr, Signature, Point} + alias Bitcoinex.Secp256k1.{PrivateKey, Schnorr, Signature} @txn_serialization_1 %{ tx_hex: @@ -295,6 +295,7 @@ defmodule Bitcoinex.TransactionTest do prev_scriptpubkey: "512029d942d0408906b359397b6f87c5145814a9aefc8c396dd05efa8b5b73576bf2", value: 600_000_000, privkey: "cf3780a32ef3b2d70366f0124ee40195a251044e82a13146106be75ee049ac02", + # We don't know what aux was used, so this can't be recreated :/ signature: "8608a76e87a5be42162284e8d7efc6cf71470351b36e07914fd0cfcb7beae98378fd9f664e274c9c2a2744197da522fdf1e3aba999b318e2587be098d90d4533" } @@ -328,87 +329,34 @@ defmodule Bitcoinex.TransactionTest do } } - # TODO write test # SIGHASH_ANYONECANPAY(ALL) - # @bip341_sighash_anyonecanpay_all %{ - # sighash_flag: 0x81, - # unsigned_tx: - # "02000000015c82840e7a0e5283c5516e742352566408de5c40d45ab0a2f872b37f188976c200000000000000000002003b5808000000001600141192fac5233e4eefa18859396b74851de18f8f4700e1f5050000000022512032c22a6e048b9d4183f612bc1b73a58fc0d4e7f548fd71b732063645d43f420200000000", - # signed_tx: - # "020000000001015c82840e7a0e5283c5516e742352566408de5c40d45ab0a2f872b37f188976c200000000000000000002003b5808000000001600141192fac5233e4eefa18859396b74851de18f8f4700e1f5050000000022512032c22a6e048b9d4183f612bc1b73a58fc0d4e7f548fd71b732063645d43f4202014153fd82ff31642b92ae43cf0010e2aac2c51a781cb2ce8c72f80477a4900d2f3a4bb1eb986bc000bd5b055c62872ac8c426eb69186b3f2e46656189d1ba97a3078100000000", - # inputs: [ - # %{ - # prev_scriptpubkey: - # "5120fe7633a26b281a80ee75d344b07ec97e738d4038de288b6caf7d38e06a6c3ee1", - # value: 250_000_000, - # privkey: "3c1d300faf1d8706fd07137e1cc1d59967ccc0efa6212fc03b2ac7c382fa9133", - # # has sighash anyonecanpay appended - # signature: - # "53fd82ff31642b92ae43cf0010e2aac2c51a781cb2ce8c72f80477a4900d2f3a4bb1eb986bc000bd5b055c62872ac8c426eb69186b3f2e46656189d1ba97a30781" - # } - # ], - # intermediary: %{ - # data: - # "00810200000000000000d070f96ca70c4dea1042a92e6abf04883e75bd3ad7dd4dcdf18153cda431cbd8005c82840e7a0e5283c5516e742352566408de5c40d45ab0a2f872b37f188976c20000000080b2e60e00000000225120fe7633a26b281a80ee75d344b07ec97e738d4038de288b6caf7d38e06a6c3ee100000000", - # sighash: "11998278e8f4fe9ec6e360642a91536a5498a30cf711712ed3d9c25dfede876b", - # sha_outputs: %{ - # data: - # "003b5808000000001600141192fac5233e4eefa18859396b74851de18f8f4700e1f5050000000022512032c22a6e048b9d4183f612bc1b73a58fc0d4e7f548fd71b732063645d43f4202", - # hash: "d070f96ca70c4dea1042a92e6abf04883e75bd3ad7dd4dcdf18153cda431cbd8" - # } - # } - # } - - # TODO write test - # SIGHASH_SINGLE - # @bip341_sighash_single %{ - # sighash_flag: 0x03, - # unsigned_tx: - # "0200000002f4d49508b20d94df7d19b5ce7f7d713140cdad392c23a5ab3c996c19284d5f7b0100000000000000006912ceb41a4e62b77a3b7bec7cd777fbd5c8188821ecb2e1fd0a6e56ed3315920000000000000000000200c2eb0b000000001976a9143bfe0f94eb78a2227664c6ebcf81719467c0106f88ac00a3e11100000000220020f1dca6047a919edc31378db3c5fcd1e93eea73f9c7fd8632ab47f18c8b8165f400000000", - # signed_tx: - # "02000000000102f4d49508b20d94df7d19b5ce7f7d713140cdad392c23a5ab3c996c19284d5f7b010000006a483045022100cc57364c2fc8ec973a038a0d859ccfbec969165bd4a1b5cbf91cfa4cd1170b0102206460814f1093b241fc8afd04b61741d5d5ae90fd404e3e7a2500d298d4e8c73503201da42d0fb59c4aa380bb7c9775d6d2da16f01eda6b3921d227faaa999de16021000000006912ceb41a4e62b77a3b7bec7cd777fbd5c8188821ecb2e1fd0a6e56ed3315920000000000000000000200c2eb0b000000001976a9143bfe0f94eb78a2227664c6ebcf81719467c0106f88ac00a3e11100000000220020f1dca6047a919edc31378db3c5fcd1e93eea73f9c7fd8632ab47f18c8b8165f40141346f28fec0d31fafde1a1aa30634d6962d59e118a79456671347060697937424fd77d679deb1c7652ae15b5d932fe3bbace65b07d6cf6639b638e21688b485c0030000000000", - # inputs: [ - # %{ - # prev_scriptpubkey: - # "512088782d9105c8774f5f2f2857aec1519bf83aa53371bd44d3a0735e9841b73c28", - # value: 100_000_000, - # privkey: "eb54ef369f599d3da9ecfeab0529160dfc76c92f1e32ade4ba33abd8408a23b8", - # signature: - # "cc57364c2fc8ec973a038a0d859ccfbec969165bd4a1b5cbf91cfa4cd1170b016460814f1093b241fc8afd04b61741d5d5ae90fd404e3e7a2500d298d4e8c735", - # der_signature: - # "3045022100cc57364c2fc8ec973a038a0d859ccfbec969165bd4a1b5cbf91cfa4cd1170b0102206460814f1093b241fc8afd04b61741d5d5ae90fd404e3e7a2500d298d4e8c73503" - # }, - # %{ - # prev_scriptpubkey: "76a914a589246bf9c64679b6d186608c2a2bee949f23e088ac", - # value: 500_000_000, - # privkey: "5dd3e4e9cd6073da94108e26e8c5c3ccbd510197ab12bd787b19b0e88181da9c", - # pubkey: "1da42d0fb59c4aa380bb7c9775d6d2da16f01eda6b3921d227faaa999de16021" - # } - # ], - # intermediary: %{ - # data: - # "00030200000000000000c637b7e49b49272269dd8b409682f66cf28191d62028216cb200239ff5e2f8dd4a3c35ddd52f7ffa12848588e82193156f84a476894a095352892c8f274fc863480fb749c75701a17230706caddbfce6f422151d9722386e82bfcd3f4fdc2704af5570f5a1810b7af78caf4bc70a660f0df51e42baf91d4de5b2328de0e83dfc00000000007b1b0fd12e11d0c5792199a8cd4f4de1e823635e6947909f10d09a3e84cce760", - # sighash: "278ba0eea80c753d166879a844b51f290770609ec171a21fdfb6c0232d25c2e0", - # sha_prevouts: %{ - # data: - # "f4d49508b20d94df7d19b5ce7f7d713140cdad392c23a5ab3c996c19284d5f7b010000006912ceb41a4e62b77a3b7bec7cd777fbd5c8188821ecb2e1fd0a6e56ed33159200000000", - # hash: "c637b7e49b49272269dd8b409682f66cf28191d62028216cb200239ff5e2f8dd" - # }, - # sha_amounts: %{ - # data: "00e1f505000000000065cd1d00000000", - # hash: "4a3c35ddd52f7ffa12848588e82193156f84a476894a095352892c8f274fc863" - # }, - # sha_scriptpubkeys: %{ - # data: - # "22512088782d9105c8774f5f2f2857aec1519bf83aa53371bd44d3a0735e9841b73c281976a914a589246bf9c64679b6d186608c2a2bee949f23e088ac", - # hash: "480fb749c75701a17230706caddbfce6f422151d9722386e82bfcd3f4fdc2704" - # }, - # sha_sequences: %{ - # data: "0000000000000000", - # hash: "af5570f5a1810b7af78caf4bc70a660f0df51e42baf91d4de5b2328de0e83dfc" - # } - # } - # } + @bip341_sighash_anyonecanpay_all %{ + sighash_flag: 0x81, + unsigned_tx: + "02000000015c82840e7a0e5283c5516e742352566408de5c40d45ab0a2f872b37f188976c200000000000000000002003b5808000000001600141192fac5233e4eefa18859396b74851de18f8f4700e1f5050000000022512032c22a6e048b9d4183f612bc1b73a58fc0d4e7f548fd71b732063645d43f420200000000", + signed_tx: + "020000000001015c82840e7a0e5283c5516e742352566408de5c40d45ab0a2f872b37f188976c200000000000000000002003b5808000000001600141192fac5233e4eefa18859396b74851de18f8f4700e1f5050000000022512032c22a6e048b9d4183f612bc1b73a58fc0d4e7f548fd71b732063645d43f4202014153fd82ff31642b92ae43cf0010e2aac2c51a781cb2ce8c72f80477a4900d2f3a4bb1eb986bc000bd5b055c62872ac8c426eb69186b3f2e46656189d1ba97a3078100000000", + inputs: [ + %{ + prev_scriptpubkey: "5120fe7633a26b281a80ee75d344b07ec97e738d4038de288b6caf7d38e06a6c3ee1", + value: 250_000_000, + privkey: "3c1d300faf1d8706fd07137e1cc1d59967ccc0efa6212fc03b2ac7c382fa9133", + # has sighash anyonecanpay appended + signature: + "53fd82ff31642b92ae43cf0010e2aac2c51a781cb2ce8c72f80477a4900d2f3a4bb1eb986bc000bd5b055c62872ac8c426eb69186b3f2e46656189d1ba97a30781" + } + ], + intermediary: %{ + data: + "00810200000000000000d070f96ca70c4dea1042a92e6abf04883e75bd3ad7dd4dcdf18153cda431cbd8005c82840e7a0e5283c5516e742352566408de5c40d45ab0a2f872b37f188976c20000000080b2e60e00000000225120fe7633a26b281a80ee75d344b07ec97e738d4038de288b6caf7d38e06a6c3ee100000000", + sighash: "11998278e8f4fe9ec6e360642a91536a5498a30cf711712ed3d9c25dfede876b", + sha_outputs: %{ + data: + "003b5808000000001600141192fac5233e4eefa18859396b74851de18f8f4700e1f5050000000022512032c22a6e048b9d4183f612bc1b73a58fc0d4e7f548fd71b732063645d43f4202", + hash: "d070f96ca70c4dea1042a92e6abf04883e75bd3ad7dd4dcdf18153cda431cbd8" + } + } + } describe "decode/1" do test "decodes legacy bitcoin transaction" do @@ -732,40 +680,49 @@ defmodule Bitcoinex.TransactionTest do assert sigmsg == Utils.hex_to_bin(t.intermediary.data) sighash = Taproot.tagged_hash_tapsighash(sigmsg) assert sighash == Utils.hex_to_bin(t.intermediary.sighash) - # # TODO signing & check signed_tx - {:ok, sk} = - Enum.at(t.inputs, 1).privkey - |> Base.decode16!(case: :lower) - |> :binary.decode_unsigned() - |> PrivateKey.new() - - # test vector gives us Q pk and sk, no need to tweak - pk = PrivateKey.to_point(sk) - # TODO I Don't know what Aux was used for this test vector? - assert <<0x51, 0x20>> <> Point.x_bytes(pk) == - Utils.hex_to_bin(Enum.at(t.inputs, 1).prev_scriptpubkey) - - # {:ok, sig} = Schnorr.sign(sk, :binary.decode_unsigned(sighash), 0) - # assert Signature.serialize_signature(sig) == Utils.hex_to_bin(Enum.at(t.inputs, 1).signature) end - # test "SIGHASH_ANYONECANPAY_ALL" do - # t = @bip341_sighash_anyonecanpay_all - # {:ok, unsigned_tx} = Transaction.decode(t.unsigned_tx) + test "SIGHASH_ANYONECANPAY_ALL" do + t = @bip341_sighash_anyonecanpay_all + {:ok, unsigned_tx} = Transaction.decode(t.unsigned_tx) - # sha_outputs = Transaction.bip341_sha_outputs(unsigned_tx.outputs) - # assert sha_outputs == Base.decode16!(t.intermediary.sha_outputs.hash) + sha_outputs = Transaction.bip341_sha_outputs(unsigned_tx.outputs) + assert sha_outputs == Utils.hex_to_bin(t.intermediary.sha_outputs.hash) - # prev_amounts = Enum.map(t.inputs, fn input -> input.value end) - # prev_scriptpubkeys = Enum.map(t.inputs, fn input -> Base.decode16!(input.prev_scriptpubkey) end) + prev_amounts = Enum.map(t.inputs, fn input -> input.value end) - # sigmsg = Transaction.bip341_sigmsg(unsigned_tx, t.sighash_flag, 0, 0, prev_amounts, prev_scriptpubkeys) - # assert sigmsg == Base.decode16!(t.intermediary.data) + prev_scriptpubkeys = + Enum.map(t.inputs, fn input -> + {:ok, s} = Script.parse_script(input.prev_scriptpubkey) + Script.serialize_with_compact_size(s) + end) - # end + sigmsg = + Transaction.bip341_sigmsg( + unsigned_tx, + t.sighash_flag, + 0, + 0, + prev_amounts, + prev_scriptpubkeys + ) + + assert sigmsg == Utils.hex_to_bin(t.intermediary.data) - # test "SIGHASH_SINGLE" do + sighash = Taproot.tagged_hash_tapsighash(sigmsg) + assert sighash == Utils.hex_to_bin(t.intermediary.sighash) - # end + sighash2 = + Transaction.bip341_sighash( + unsigned_tx, + t.sighash_flag, + 0, + 0, + prev_amounts, + prev_scriptpubkeys + ) + + assert sighash2 == Utils.hex_to_bin(t.intermediary.sighash) + end end end From 406e73fb2e74489d1ecf7f401dd05191635b3b5b Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sun, 5 Feb 2023 22:57:48 -0800 Subject: [PATCH 27/63] rm tapscript.ex --- lib/tapscript.ex | 125 ----------------------------------------------- 1 file changed, 125 deletions(-) delete mode 100644 lib/tapscript.ex diff --git a/lib/tapscript.ex b/lib/tapscript.ex deleted file mode 100644 index fa1b022..0000000 --- a/lib/tapscript.ex +++ /dev/null @@ -1,125 +0,0 @@ -# defmodule Tapscript do - -# defmodule Tapscript.Opcode do -# # BIP 342 opcodes (Tapscript) -# OP_CHECKSIGADD = 0xba - -# OP_INVALIDOPCODE = 0xff - -# OPCODE_NAMES.update({ -# OP_0: 'OP_0', -# OP_PUSHDATA1: 'OP_PUSHDATA1', -# OP_PUSHDATA2: 'OP_PUSHDATA2', -# OP_PUSHDATA4: 'OP_PUSHDATA4', -# OP_1NEGATE: 'OP_1NEGATE', -# OP_RESERVED: 'OP_RESERVED', -# OP_1: 'OP_1', -# OP_2: 'OP_2', -# OP_3: 'OP_3', -# OP_4: 'OP_4', -# OP_5: 'OP_5', -# OP_6: 'OP_6', -# OP_7: 'OP_7', -# OP_8: 'OP_8', -# OP_9: 'OP_9', -# OP_10: 'OP_10', -# OP_11: 'OP_11', -# OP_12: 'OP_12', -# OP_13: 'OP_13', -# OP_14: 'OP_14', -# OP_15: 'OP_15', -# OP_16: 'OP_16', -# OP_NOP: 'OP_NOP', -# OP_VER: 'OP_VER', -# OP_IF: 'OP_IF', -# OP_NOTIF: 'OP_NOTIF', -# OP_VERIF: 'OP_VERIF', -# OP_VERNOTIF: 'OP_VERNOTIF', -# OP_ELSE: 'OP_ELSE', -# OP_ENDIF: 'OP_ENDIF', -# OP_VERIFY: 'OP_VERIFY', -# OP_RETURN: 'OP_RETURN', -# OP_TOALTSTACK: 'OP_TOALTSTACK', -# OP_FROMALTSTACK: 'OP_FROMALTSTACK', -# OP_2DROP: 'OP_2DROP', -# OP_2DUP: 'OP_2DUP', -# OP_3DUP: 'OP_3DUP', -# OP_2OVER: 'OP_2OVER', -# OP_2ROT: 'OP_2ROT', -# OP_2SWAP: 'OP_2SWAP', -# OP_IFDUP: 'OP_IFDUP', -# OP_DEPTH: 'OP_DEPTH', -# OP_DROP: 'OP_DROP', -# OP_DUP: 'OP_DUP', -# OP_NIP: 'OP_NIP', -# OP_OVER: 'OP_OVER', -# OP_PICK: 'OP_PICK', -# OP_ROLL: 'OP_ROLL', -# OP_ROT: 'OP_ROT', -# OP_SWAP: 'OP_SWAP', -# OP_TUCK: 'OP_TUCK', -# OP_CAT: 'OP_CAT', -# OP_SUBSTR: 'OP_SUBSTR', -# OP_LEFT: 'OP_LEFT', -# OP_RIGHT: 'OP_RIGHT', -# OP_SIZE: 'OP_SIZE', -# OP_INVERT: 'OP_INVERT', -# OP_AND: 'OP_AND', -# OP_OR: 'OP_OR', -# OP_XOR: 'OP_XOR', -# OP_EQUAL: 'OP_EQUAL', -# OP_EQUALVERIFY: 'OP_EQUALVERIFY', -# OP_RESERVED1: 'OP_RESERVED1', -# OP_RESERVED2: 'OP_RESERVED2', -# OP_1ADD: 'OP_1ADD', -# OP_1SUB: 'OP_1SUB', -# OP_2MUL: 'OP_2MUL', -# OP_2DIV: 'OP_2DIV', -# OP_NEGATE: 'OP_NEGATE', -# OP_ABS: 'OP_ABS', -# OP_NOT: 'OP_NOT', -# OP_0NOTEQUAL: 'OP_0NOTEQUAL', -# OP_ADD: 'OP_ADD', -# OP_SUB: 'OP_SUB', -# OP_MUL: 'OP_MUL', -# OP_DIV: 'OP_DIV', -# OP_MOD: 'OP_MOD', -# OP_LSHIFT: 'OP_LSHIFT', -# OP_RSHIFT: 'OP_RSHIFT', -# OP_BOOLAND: 'OP_BOOLAND', -# OP_BOOLOR: 'OP_BOOLOR', -# OP_NUMEQUAL: 'OP_NUMEQUAL', -# OP_NUMEQUALVERIFY: 'OP_NUMEQUALVERIFY', -# OP_NUMNOTEQUAL: 'OP_NUMNOTEQUAL', -# OP_LESSTHAN: 'OP_LESSTHAN', -# OP_GREATERTHAN: 'OP_GREATERTHAN', -# OP_LESSTHANOREQUAL: 'OP_LESSTHANOREQUAL', -# OP_GREATERTHANOREQUAL: 'OP_GREATERTHANOREQUAL', -# OP_MIN: 'OP_MIN', -# OP_MAX: 'OP_MAX', -# OP_WITHIN: 'OP_WITHIN', -# OP_RIPEMD160: 'OP_RIPEMD160', -# OP_SHA1: 'OP_SHA1', -# OP_SHA256: 'OP_SHA256', -# OP_HASH160: 'OP_HASH160', -# OP_HASH256: 'OP_HASH256', -# OP_CODESEPARATOR: 'OP_CODESEPARATOR', -# OP_CHECKSIG: 'OP_CHECKSIG', -# OP_CHECKSIGVERIFY: 'OP_CHECKSIGVERIFY', -# OP_CHECKMULTISIG: 'OP_CHECKMULTISIG', -# OP_CHECKMULTISIGVERIFY: 'OP_CHECKMULTISIGVERIFY', -# OP_NOP1: 'OP_NOP1', -# OP_CHECKLOCKTIMEVERIFY: 'OP_CHECKLOCKTIMEVERIFY', -# OP_CHECKSEQUENCEVERIFY: 'OP_CHECKSEQUENCEVERIFY', -# OP_CHECKTEMPLATEVERIFY : 'OP_CHECKTEMPLATEVERIFY', -# OP_NOP5: 'OP_NOP5', -# OP_NOP6: 'OP_NOP6', -# OP_NOP7: 'OP_NOP7', -# OP_NOP8: 'OP_NOP8', -# OP_NOP9: 'OP_NOP9', -# OP_NOP10: 'OP_NOP10', -# OP_CHECKSIGADD: 'OP_CHECKSIGADD', -# OP_INVALIDOPCODE: 'OP_INVALIDOPCODE', -# }) -# end -# end From 17e28df170d2f9e0f79fc45ace7b4fe9ffe5d082 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 9 Feb 2023 00:34:22 -0800 Subject: [PATCH 28/63] add full DLC example --- lib/script.ex | 24 +- lib/secp256k1/schnorr.ex | 68 +++++- lib/transaction.ex | 2 + scripts/dlc.exs | 438 +++++++++++++++++++++++++++++++++++ scripts/taproot_keyspend.exs | 2 +- 5 files changed, 516 insertions(+), 18 deletions(-) create mode 100644 scripts/dlc.exs diff --git a/lib/script.ex b/lib/script.ex index 8999d32..fd86724 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -15,7 +15,7 @@ defmodule Bitcoinex.Script do @pubkey_lengths [@tapkey_length, 33, 65] # hash of G.x, used to construct unsolvable internal taproot keys - @h 0x0250929B74C1A04954B78B4B6035E97A5E078A5A0F28EC96D547BFEE9ACE803AC0 + @h 0x50929B74C1A04954B78B4B6035E97A5E078A5A0F28EC96D547BFEE9ACE803AC0 @type script_type :: :p2pk | :p2pkh | :p2sh | :p2wpkh | :p2wsh | :p2tr | :multi | :non_standard @@ -642,6 +642,12 @@ defmodule Bitcoinex.Script do @spec create_p2tr_script_only(Taproot.script_tree(), non_neg_integer()) :: {:ok, Bitcoinex.Script.t(), non_neg_integer()} def create_p2tr_script_only(script_tree, r) do + p = calculate_unsolvable_internal_key(r) + {:ok, script} = create_p2tr(p, script_tree) + {:ok, script, r} + end + + def calculate_unsolvable_internal_key(r) do case PrivateKey.new(r) do {:error, msg} -> {:error, msg} @@ -649,16 +655,18 @@ defmodule Bitcoinex.Script do {:ok, sk} -> {:ok, hk} = Point.lift_x(@h) - {:ok, script} = - sk - |> PrivateKey.to_point() - |> Math.add(hk) - |> create_p2tr(script_tree) - - {:ok, script, r} + sk + |> PrivateKey.to_point() + |> Math.add(hk) end end + @spec validate_unsolvable_internal_key(t(), Taproot.script_tree(), non_neg_integer) :: boolean + def validate_unsolvable_internal_key(p2tr_script, script_tree, r) do + {:ok, script, _} = create_p2tr_script_only(script_tree, r) + script == p2tr_script + end + @doc """ create_p2sh_p2wpkh creates a p2wsh script using the passed 20-byte public key hash """ diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 824d286..ab9e650 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -17,6 +17,45 @@ defmodule Bitcoinex.Secp256k1.Schnorr do @spec sign(PrivateKey.t(), non_neg_integer(), non_neg_integer()) :: {:ok, Signature.t()} | {:error, String.t()} def sign(privkey, z, aux) do + case calculate_signature_nonce(privkey, z, aux) do + {:error, msg} -> + {:error, msg} + + {:ok, k, d} -> + sig = sign_with_nonce(d, k, z) + {:ok, sig} + end + end + + @doc """ + sign_with_nonce creates a signature (R,s) from a given + private key sk, nonce k, and sighash z + DANGER: signing different messages with the same sk and k + will leak sk. + """ + def sign_with_nonce(sk, k, z) do + d = Secp256k1.force_even_y(sk) + k = Secp256k1.force_even_y(k) + case {d, k} do + {{:error, _}, _} -> + {:error, "failed to force signing key even"} + {_, {:error, _}} -> + {:error, "failed to force nonce secret even"} + + {d = %PrivateKey{}, k = %PrivateKey{}} -> + r_point = PrivateKey.to_point(k) + d_point = PrivateKey.to_point(d) + z_bytes = Utils.int_to_big(z, 32) + e = calculate_e(Point.x_bytes(r_point), Point.x_bytes(d_point), z_bytes) + sig_s = calculate_s(k, sk, e) + %Signature{r: r_point.x, s: sig_s} + end + end + + @spec calculate_signature_nonce(PrivateKey.t(), non_neg_integer(), non_neg_integer()) :: + {:error, String.t()} + | {:ok, PrivateKey.t(), PrivateKey.t()} + def calculate_signature_nonce(privkey, z, aux) do case PrivateKey.validate(privkey) do {:error, msg} -> {:error, msg} @@ -40,20 +79,15 @@ defmodule Bitcoinex.Secp256k1.Schnorr do if k0.d == 0 do {:error, "invalid aux randomness"} else - r_point = PrivateKey.to_point(k0) - case Secp256k1.force_even_y(k0) do {:error, msg} -> {:error, msg} k -> - e = calculate_e(Point.x_bytes(r_point), Point.x_bytes(d_point), z_bytes) - sig_s = calculate_s(k, d, e) - - {:ok, %Signature{r: r_point.x, s: sig_s}} - end + {:ok, k, d} + end end - end + end end end @@ -243,7 +277,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do """ @spec recover_decryption_key(Signature.t(), Signature.t(), boolean) :: PrivateKey.t() | {:error, String.t()} - def recover_decryption_key(%Signature{r: enc_r}, %Signature{r: r}, _, _) when enc_r != r, + def recover_decryption_key(%Signature{r: enc_r}, %Signature{r: r}, _) when enc_r != r, do: {:error, "invalid signature pair"} def recover_decryption_key( @@ -254,4 +288,20 @@ defmodule Bitcoinex.Secp256k1.Schnorr do t = Math.modulo(s - enc_s, @n) conditional_negate(t, was_negated) end + + # @spec calculate_signature_point(Point.t(), Point.(), <<_::256>>) :: Point.t() | {:error, String.t()} + def calculate_signature_point(r_point, pk, z_bytes) do + e = + calculate_e(Point.x_bytes(r_point), Point.x_bytes(pk), z_bytes) + |> PrivateKey.new() + + case e do + {:error, msg} -> {:error, msg} + + {:ok, e} -> + Math.multiply(pk, e.d) + |> Math.add(r_point) + end + end + end diff --git a/lib/transaction.ex b/lib/transaction.ex index cdae60d..72adf4c 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -94,6 +94,7 @@ defmodule Bitcoinex.Transaction do non_neg_integer(), list(non_neg_integer()), list(<<_::280>>), + # TODO do good caching list({:tapleaf, Taproot.TapLeaf.t()}) ) :: binary def bip341_sigmsg( @@ -186,6 +187,7 @@ defmodule Bitcoinex.Transaction do cached_tx_data <> input_data <> output_data <> ext end + # TODO good caching # The results of this function can be reused across input signings. @spec bip341_tx_data(t(), non_neg_integer(), list(non_neg_integer()), list(<<_::280>>)) :: binary diff --git a/scripts/dlc.exs b/scripts/dlc.exs new file mode 100644 index 0000000..6c25039 --- /dev/null +++ b/scripts/dlc.exs @@ -0,0 +1,438 @@ +alias Bitcoinex.{Secp256k1,Transaction, Script, Utils, Taproot} +alias Bitcoinex.Secp256k1.{Point, PrivateKey, Signature, Schnorr} + + +new_rand_int = fn -> + 32 + |> :crypto.strong_rand_bytes() + |> :binary.decode_unsigned() +end + +new_privkey = fn -> + {:ok, sk} = + new_rand_int.() + |> PrivateKey.new() + Secp256k1.force_even_y(sk) +end + +multisig_2_of_2_script = fn a, b -> + # Script will be pseudo-multisig: + # OP_CHECKSIGVERIFY OP_CHECKSIG + # Scripts are stacks, so must be inserted in reverse order. + # This also means Alices Signature must come first in the witness_script + s = Script.new() + {:ok, s} = Script.push_op(s, :op_checksig) + {:ok, s} = Script.push_data(s, Point.x_bytes(a)) + {:ok, s} = Script.push_op(s, :op_checksigverify) + {:ok, s} = Script.push_data(s, Point.x_bytes(b)) + s +end + +# Initial setup for this example: give Alice and bob one coin worth 100,010,000 sats each, in order to fund the DLC. +# these ouotputs will be simple keyspend-only P2TRs +alice_init_sk = new_privkey.() +alice_init_pk = PrivateKey.to_point(alice_init_sk) +alice_init_script_tree = nil +{:ok, alice_init_script} = Script.create_p2tr(alice_init_pk, alice_init_script_tree) +{:ok, alice_init_addr} = Script.to_address(alice_init_script, :regtest) + +bob_init_sk = new_privkey.() +bob_init_pk = PrivateKey.to_point(bob_init_sk) +bob_init_script_tree = nil +{:ok, bob_init_script} = Script.create_p2tr(bob_init_pk, bob_init_script_tree) +{:ok, bob_init_addr} = Script.to_address(bob_init_script, :regtest) + +# In your regtest node, send bitcoin to each of these 2 addresses in the amount 100_010_000. +# if you use a different amount, edit the *_init_amount variables below +# note the outpoints for both sends. +alice_init_txid = "57495e49895e87ac3ba2f2467abc6124df166a251a0e304eb770ccc040063af4" +alice_init_vout = 1 +alice_init_amount = 100_010_000 + +bob_init_txid = "783bf305752377c006310d66756d680fa9e33c0e870d024b1c2269aa88f4654c" +bob_init_vout = 1 +bob_init_amount = 100_010_000 + +### BEGIN DLC EXAMPLE ### + +# First, Alice and Bob will create a 2-of-2 funding address + +alice_fund_sk = new_privkey.() +alice_fund_pk = PrivateKey.to_point(alice_fund_sk) + +bob_fund_sk = new_privkey.() +bob_fund_pk = PrivateKey.to_point(bob_fund_sk) + +fund_script = multisig_2_of_2_script.(alice_fund_pk, bob_fund_pk) +# If you want to examine the script, uncomment next line: +# Script.display_script(fund_script) + +fund_leaf = Taproot.TapLeaf.new(Taproot.bip342_leaf_version(), fund_script) + +# WARNING: this address only has 1 spend option: a 2-of-2 multisig. Without additional timeout +# spendpaths (allowing both parties to recover funds if the other disappears), this is a trusted +# and unsafe contract to enter. TODO: add timeout allowing both parties to reclaim funds. + +# P2TR for funding addr will have no internal key. Only way to spend is to satisfy the 2-of-2 +# TODO: when MuSig is implemented, the KeySpend route can act as 2-of-2 instead, and is cheaper. +{:ok, fund_scriptpubkey, r} = Script.create_p2tr_script_only(fund_leaf, new_rand_int.()) + +# in the above, r is a random number used to create a unique but provably unspendable internal key +# in the case that we don't want a keypath spend to be possible. If either alice or bob generated the funding +# scriptpubkey, the other should verify that the internal key is unspendable and reconstruct the same script +# to ensure they know what contract they are sending their BTC to. +fund_p = Script.calculate_unsolvable_internal_key(r) +is_correct_p2tr = Script.validate_unsolvable_internal_key(fund_scriptpubkey, fund_leaf, r) + +# ALICE NOR BOB should fund this address until both CETs are signed with adaptor sigs +{:ok, fund_addr} = Script.to_address(fund_scriptpubkey, :regtest) + +# We create the funding transaction. +init_amounts = [alice_init_amount, bob_init_amount] +init_scriptpubkeys = [ + Script.serialize_with_compact_size(alice_init_script), + Script.serialize_with_compact_size(bob_init_script) +] + +fund_amount = 200_010_000 +fund_tx = %Transaction{ + version: 1, + inputs: [ + %Transaction.In{ + prev_txid: alice_init_txid, + prev_vout: alice_init_vout, + script_sig: "", + sequence_no: 2147483648 + }, + %Transaction.In{ + prev_txid: bob_init_txid, + prev_vout: bob_init_vout, + script_sig: "", + sequence_no: 2147483648 + } + ], + outputs: [ + %Transaction.Out{ + # fee of 10_000 sats paid for this tx. This means you'll probably have to disable max fee constraints in bitcoind to broadcast txs + # the next 10_000 will go towards spending this output to settle the contract + value: fund_amount, + script_pub_key: Script.to_hex(fund_scriptpubkey) + } + # A more realistic example would have change outputs to each party here. Omitted for simplicity + ], + lock_time: 0 +} + +# even though the fund tx is not signed, we can calculate the txid and use that to create contract execution txs (CETs) +# which will spend the output created in this tx +fund_txid = Transaction.transaction_id(fund_tx) +# d4be73514a8535c6e21a6007b0de049ba3d4d7d93ee953852c4b7ce97369ff49 +fund_vout = 0 +fund_amounts = [fund_amount] +fund_scriptpubkeys = [Script.serialize_with_compact_size(fund_scriptpubkey)] + +# Oracle Identity & Signing Key +oracle_sk = new_privkey.() +oracle_pk = PrivateKey.to_point(oracle_sk) + +# Oracle creates 2 tweak/point pairs, one for each possible outcome +# The bet will be a simple MOON or CRASH bet. + +# the same nonce must be used for both outcomes in order to guarantee that the Oracle +# cannot sign both events without leaking their own private key. In a more trust-minimized +# example, the Oracle should prove ownership of a UTXO with the public key they use for +# the signing, in order to prove that they have something at stake if they should sign both events. + +# Oracle does not use the standard BIP340 method for generating a nonce. +# This is because the nonce must not commit to the message, so that it can +# be reused for either outcome. +oracle_event_nonce = new_privkey.() +event_nonce_point = PrivateKey.to_point(oracle_event_nonce) + +moon_msg = "MOON! the price rose" +crash_msg = "CRASH! the price fell" + +# Oracle Broadcasts its intention to sign one of the 2 events +public = %{ + oracle_pk: oracle_pk, + event_nonce_point: event_nonce_point, + case1: moon_msg, + case2: crash_msg + # note, arbitrary number of outcomes are workable here. +} + +# Alice And Bob can now compute the Signature Point, which they will use as the tweak point for their adaptor signatures +moon_sighash = Utils.double_sha256(public.case1) +crash_sighash = Utils.double_sha256(public.case2) + +moon_sig_point = Schnorr.calculate_signature_point(public.event_nonce_point, oracle_pk, moon_sighash) +crash_sig_point = Schnorr.calculate_signature_point(public.event_nonce_point, oracle_pk, crash_sighash) + +# Alice and Bob create CETs (Settlement Transactions, which will spend the funding tx (not yet signed/broadcasted)) + +# Alice and Bob each need Addresses to settle to +# For simplicity, these dest addresses will be internal key only Taproot addresses +alice_dest_sk = new_privkey.() +alice_dest_pk = PrivateKey.to_point(alice_dest_sk) +{:ok, alice_dest_script} = Script.create_p2tr(alice_dest_pk, nil) +{:ok, alice_dest_addr} = Script.to_address(alice_dest_script, :regtest) + +bob_dest_sk = new_privkey.() +bob_dest_pk = PrivateKey.to_point(bob_dest_sk) +{:ok, bob_dest_script} = Script.create_p2tr(bob_dest_pk, nil) +{:ok, bob_dest_addr} = Script.to_address(bob_dest_script, :regtest) + + +# CET hash type will be sighash default for both, for simplicity +cet_hash_type = 0x00 + + +# First CET: MOON, alice wins, and gets 75% of the funding tx (excluding fees) +moon_cet = %Transaction{ + version: 1, + inputs: [ + %Transaction.In{ + prev_txid: fund_txid, + prev_vout: fund_vout, + script_sig: "", + sequence_no: 2147483648, + } + ], + outputs: [ + # ALICE WINS! gets 150M sats from the 1M she put in + %Transaction.Out{ + value: 150_000_000, + script_pub_key: Script.to_hex(alice_dest_script) + }, + # BOB LOSES :( gets 50M sats from the 1M he put in + %Transaction.Out{ + value: 50_000_000, + script_pub_key: Script.to_hex(bob_dest_script) + } + ], + lock_time: 0 +} +# calculate the sighash for the MOON CET +moon_cet_sighash = Transaction.bip341_sighash( + moon_cet, + cet_hash_type, # sighash_default (all) + 0x01, # we are using taproot scriptpath spend, so ext_flag must be 1 + 0, # index we're going to sign + fund_amounts, # list of amounts for each input being spent + fund_scriptpubkeys, # list of prev scriptpubkeys for each input being spent + tapleaf: fund_leaf +) |> :binary.decode_unsigned() + +# Second CET: CRASH, bob wins, and gets 75% of the funding tx (excluding fees) +crash_cet = %Transaction{ + version: 1, + inputs: [ + # Notice this input is the same coin spent in the Moon CET tx. So they can't both be valid. + %Transaction.In{ + prev_txid: fund_txid, + prev_vout: fund_vout, + script_sig: "", + sequence_no: 2147483648, + } + ], + outputs: [ + # ALICE LOSES :( gets 50M sats from the 1M she put in + %Transaction.Out{ + value: 50_000_000, + script_pub_key: Script.to_hex(alice_dest_script) + }, + # BOB WINS! gets 150M sats from the 1M he put in + %Transaction.Out{ + value: 150_000_000, + script_pub_key: Script.to_hex(bob_dest_script) + } + ], + lock_time: 0 +} +# calculate the Sighash for the CRASH CET +crash_cet_sighash = Transaction.bip341_sighash( + crash_cet, + cet_hash_type, # sighash_default (all) + 0x01, # we are using taproot scriptpath spend, so ext_flag = 1 + 0, # only one input in this tx + fund_amounts, # list of amounts for each input being spent + fund_scriptpubkeys, # list of prev scriptpubkeys for each input being spent + tapleaf: fund_leaf +) |> :binary.decode_unsigned() + +# Alice and Bob now create adaptor signatures for each of these CETs. They must share both of their +# Adaptor Signatures with one another. These next 4 steps are in no particular order. + +# Alice creates adaptor sig for Crash Case using crash_sig_point (tweak point/encryption key). +aux_rand = new_rand_int.() # generate some entropy for this signature +{:ok, alice_crash_adaptor_sig, alice_crash_was_negated} = Schnorr.encrypted_sign(alice_fund_sk, crash_cet_sighash, aux_rand, crash_sig_point) + +# Bob creates adaptor sig for Moon Case using moon_sig_point (tweak point/encryption key). +aux_rand = new_rand_int.() # generate some entropy for this signature +{:ok, bob_moon_adaptor_sig, bob_moon_was_negated} = Schnorr.encrypted_sign(bob_fund_sk, moon_cet_sighash, aux_rand, moon_sig_point) + +# Alice creates adaptor sig for Moon Case using moon_sig_point. +aux_rand = new_rand_int.() # generate some entropy for this signature +{:ok, alice_moon_adaptor_sig, alice_moon_was_negated} = Schnorr.encrypted_sign(alice_fund_sk, moon_cet_sighash, aux_rand, moon_sig_point) + +# Bob creates adaptor sig for Crash Case using crash_sig_point. +aux_rand = new_rand_int.() # generate some entropy for this signature +{:ok, bob_crash_adaptor_sig, bob_crash_was_negated} = Schnorr.encrypted_sign(bob_fund_sk, crash_cet_sighash, aux_rand, crash_sig_point) + +# Verification Time! Alice and Bob must each verify one another's adaptor signatures to ensure they will be +# valid once the Oracle publishes the resolution signature + +# Bob verifies Alice's Crash signature +is_valid = Schnorr.verify_encrypted_signature(alice_crash_adaptor_sig, alice_fund_pk, crash_cet_sighash, crash_sig_point, alice_crash_was_negated) + +# Alice verifies Bob's Moon signature +is_valid = Schnorr.verify_encrypted_signature(bob_moon_adaptor_sig, bob_fund_pk, moon_cet_sighash, moon_sig_point, bob_moon_was_negated) + +# Bob verifies Alice's Moon signature +is_valid = Schnorr.verify_encrypted_signature(alice_moon_adaptor_sig, alice_fund_pk, moon_cet_sighash, moon_sig_point, alice_moon_was_negated) + +# Alice verifies Bob's Crash signature +is_valid = Schnorr.verify_encrypted_signature(bob_crash_adaptor_sig, bob_fund_pk, crash_cet_sighash, crash_sig_point, bob_crash_was_negated) + +# IFF all four adaptor signatures are valid, we're good to go! + +# Now that each party has the other's adaptor signatures, they can sign and broadcast the +# Funding transaction (that was created way above). They could not previously do so safely, +# because if they had locked BTC in the 2-of-2 multisig before having their counterparty's +# Adaptor signatures, they could have lost those funds if the counterparty disappeared. +# NOTE: Again, the funding tx has no timeouts in place, so if the Oracle AND a counterparty disappear, +# those funds are locked irretreivably. + +fund_hash_type = 0x00 # sighash_default (all) +fund_ext_flag = 0 # both parties are using keyspend +# This would be done independently by both parties, or collaboratively using PSBT, +alice_fund_sighash = Transaction.bip341_sighash( + fund_tx, + fund_hash_type, + fund_ext_flag, + 0, # alice's input comes first + init_amounts, + init_scriptpubkeys +) |> :binary.decode_unsigned() + +bob_fund_sighash = Transaction.bip341_sighash( + fund_tx, + fund_hash_type, + fund_ext_flag, + 1, # bob's input comes second + init_amounts, + init_scriptpubkeys +) |> :binary.decode_unsigned() + +# each party signs the funding transaction, moving their BTC into the 2-of-2 multisig. + +# In order to sign the tx with the internal private key, they must first tweak it +# so that it can sign for the external taproot key +{_, alice_init_merkle_root_hash} = Taproot.merkelize_script_tree(alice_init_script_tree) +alice_q = Taproot.tweak_privkey(alice_init_sk, alice_init_merkle_root_hash) +aux_rand = new_rand_int.() # more entropy +{:ok, alice_fund_sig} = Schnorr.sign(alice_q, alice_fund_sighash, aux_rand) + +{_, bob_init_merkle_root_hash} = Taproot.merkelize_script_tree(bob_init_script_tree) +aux_rand = new_rand_int.() # even more entropy +bob_q = Taproot.tweak_privkey(bob_init_sk, bob_init_merkle_root_hash) +{:ok, bob_fund_sig} = Schnorr.sign(bob_q, bob_fund_sighash, aux_rand) + +fund_hash_byte = + if fund_hash_type == 0x00 do + <<>> + else + <> + end + +alice_sig_hex = Signature.serialize_signature(alice_fund_sig) <> fund_hash_byte |> Base.encode16(case: :lower) +alice_witness = %Transaction.Witness{ + txinwitness: [ + alice_sig_hex + ] +} + +# NEXT: construct fund witness. +bob_sig_hex = Signature.serialize_signature(bob_fund_sig) <> fund_hash_byte |> Base.encode16(case: :lower) +bob_witness = %Transaction.Witness{ + txinwitness: [ + bob_sig_hex + ] +} + +fund_tx = %Transaction{fund_tx | witnesses: [ + alice_witness, bob_witness +]} + +fund_tx_hex = Transaction.Utils.serialize(fund_tx) |> Base.encode16(case: :lower) +# 5ac6d378bdf1c55b08e92a03de343797b662cb953cd8f3bbfb7e0108bbba7841 + +# FUND TX IS READY TO BROADCAST! +# bitcoin-cli sendrawtransaction 0 +# The 0 at the end disables max_fee constraints. Only do this if you're willing to pay a high fee. + +# ...wait for oracle to announce result... + +# RESOLUTION 1: MOON! Skip to RESOLUTION 2 to execute the CRASH scenario instead + +# Oracle Publishes: +moon_sig = Schnorr.sign_with_nonce(oracle_sk, oracle_event_nonce, :binary.decode_unsigned(moon_sighash)) + +# Alice & Bob should make sure this is a valid Schnorr signature +is_valid = Schnorr.verify_signature(oracle_pk, :binary.decode_unsigned(moon_sighash), moon_sig) + +# alice & bob can both now extract the moon_tweak, which is moon_sig.s and complete their moon_adaptor sigs. +# Since alice won, she has more incentive to do so +%Signature{s: settlement_secret} = moon_sig + +{:ok, settlement_secret} = PrivateKey.new(settlement_secret) + +cet_hash_byte = + if cet_hash_type == 0x00 do + <<>> + else + <> + end + +# Then, Alice can decrypt Bob's signature +bob_moon_sig = Schnorr.decrypt_signature(bob_moon_adaptor_sig, settlement_secret, bob_moon_was_negated) +# why not verify? +is_valid = Schnorr.verify_signature(bob_fund_pk, moon_cet_sighash, bob_moon_sig) + + +# Alice can also decrypt her own adaptor signature +alice_moon_sig = Schnorr.decrypt_signature(alice_moon_adaptor_sig, settlement_secret, alice_moon_was_negated) +# Don't trust, verify +is_valid = Schnorr.verify_signature(alice_fund_pk, moon_cet_sighash, alice_moon_sig) + +# fund_p is the internal taproot key. In this case, it is unsolvable, as verified earlier. +# we take fund_leaf, the script_tree from earlier, and select the index of the script we want to spend. +# Here, there is only 1 script in the tree, so idx must be 0 +control_block = Taproot.build_control_block(fund_p, fund_leaf, 0) + +# serialize everything for insertion into the tx +bob_moon_sig_hex = Signature.serialize_signature(bob_moon_sig) <> cet_hash_byte |> Base.encode16(case: :lower) +alice_moon_sig_hex = Signature.serialize_signature(alice_moon_sig) <> cet_hash_byte |> Base.encode16(case: :lower) +fund_script_hex = Script.to_hex(fund_script) +control_block_hex = control_block |> Base.encode16(case: :lower) + + +# She then adds these to the Moon CET and broadcasts it +tx = %Transaction{moon_cet | witnesses: [ + %Transaction.Witness{ + txinwitness: [bob_moon_sig_hex, alice_moon_sig_hex, fund_script_hex, control_block_hex] + } +] +} + +tx = %Transaction{moon_cet | witnesses: [ + %Transaction.Witness{ + txinwitness: [alice_moon_sig_hex, bob_moon_sig_hex, fund_script_hex, control_block_hex] + } +] +} + +Transaction.Utils.serialize(tx) |> Base.encode16(case: :lower) + + +# RESOLUTION 2: CRASH! diff --git a/scripts/taproot_keyspend.exs b/scripts/taproot_keyspend.exs index 8229459..e6a99c9 100644 --- a/scripts/taproot_keyspend.exs +++ b/scripts/taproot_keyspend.exs @@ -26,7 +26,7 @@ script_tree = nil # EDIT to the unique addr # addr = "bcrt1pfh4qvlzrgmf6f8e6urjkf3ax83kz02xqc8zujnpeycxgc3wrqmxs8py692" # EDIT to the txid of your send to the addr above -txid = "bcdf4b0088a75c139d0b4858164534585b735dcdd18321824a31936abcbf04b4" +txid = "9d539c9fc4a17ddd920e0bd8d5d43f92382a36765f48a7725e92e184a21a50d7" # EDIT to the vout of the output you created for the addr vout = 0 # EDIT to the amount you sent in the UTXO above From 0d18d7a2038597a5c47a9fde6f928c3dbce6baae Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 9 Feb 2023 08:44:37 -0800 Subject: [PATCH 29/63] syntax --- scripts/dlc.exs | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/dlc.exs b/scripts/dlc.exs index 6c25039..11b8c9a 100644 --- a/scripts/dlc.exs +++ b/scripts/dlc.exs @@ -182,11 +182,9 @@ bob_dest_pk = PrivateKey.to_point(bob_dest_sk) {:ok, bob_dest_script} = Script.create_p2tr(bob_dest_pk, nil) {:ok, bob_dest_addr} = Script.to_address(bob_dest_script, :regtest) - # CET hash type will be sighash default for both, for simplicity cet_hash_type = 0x00 - # First CET: MOON, alice wins, and gets 75% of the funding tx (excluding fees) moon_cet = %Transaction{ version: 1, From 511c8d21f8c458e019a9800b3af3f487dc3ada39 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 9 Feb 2023 09:56:25 -0800 Subject: [PATCH 30/63] add taproot multisig script construction --- lib/script.ex | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/script.ex b/lib/script.ex index 342a65d..1b332b7 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -158,6 +158,17 @@ defmodule Bitcoinex.Script do end end + @spec push_num(t(), non_neg_integer()) :: t() + def push_num(%__MODULE__{items: stack}, num) when num >= 0 and num <= 16 do + op_num = num_to_op_num(num) + %__MODULE__{items: [op_num | stack]} + end + + def num_to_op_num(0), do: 0 + def num_to_op_num(num) when num > 0 and num <= 16 do + 0x50 + num + end + # SERIALIZE & PARSE defp serializer(%__MODULE__{items: []}, acc), do: acc @@ -527,6 +538,26 @@ defmodule Bitcoinex.Script do defp fill_multi_keys(_, _), do: raise(ArgumentError) + @spec create_taproot_multisig(non_neg_integer(), list(Point.t())) :: Script.t() + def create_taproot_multisig(m, pubkeys) when is_valid_multi(m, pubkeys) do + {:ok, s} = push_op(new(), :op_equal) + {:ok, s} = push_num(s, m) + fill_taproot_multi_keys(s, Enum.reverse(pubkeys)) + end + + # creates a script using the pubkeys *in reverse order*. + defp fill_taproot_multi_keys(s, []), do: s + defp fill_taproot_multi_keys(s, [last_key]) do + {:ok, s} = push_op(s, :op_checksig) + {:ok, s} = push_data(s, Point.x_bytes(last_key)) + s + end + defp fill_taproot_multi_keys(s, [key | rest]) do + {:ok, s} = push_op(s, :op_checksigadd) + {:ok, s} = push_data(s, Point.x_bytes(key)) + fill_taproot_multi_keys(s, rest) + end + @doc """ create_p2sh_multi returns both a P2SH-wrapped multisig script and the underlying raw multisig script using m and the list of public keys. From 45ea0b3dcf34163cbdd78cbd984030d9c4ec29eb Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sun, 12 Feb 2023 11:12:03 -0800 Subject: [PATCH 31/63] add spec for function --- lib/script.ex | 2 ++ scripts/dlc.exs | 9 +-------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/script.ex b/lib/script.ex index fd86724..e480bb2 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -647,6 +647,8 @@ defmodule Bitcoinex.Script do {:ok, script, r} end + @spec calculate_unsolvable_internal_key(non_neg_integer) :: + {:error, String.t()} | Bitcoinex.Secp256k1.Point.t() def calculate_unsolvable_internal_key(r) do case PrivateKey.new(r) do {:error, msg} -> diff --git a/scripts/dlc.exs b/scripts/dlc.exs index 11b8c9a..697aab0 100644 --- a/scripts/dlc.exs +++ b/scripts/dlc.exs @@ -135,8 +135,7 @@ fund_scriptpubkeys = [Script.serialize_with_compact_size(fund_scriptpubkey)] oracle_sk = new_privkey.() oracle_pk = PrivateKey.to_point(oracle_sk) -# Oracle creates 2 tweak/point pairs, one for each possible outcome -# The bet will be a simple MOON or CRASH bet. +# The bet will be a simple EAGLES or CHIEFS bet. # the same nonce must be used for both outcomes in order to guarantee that the Oracle # cannot sign both events without leaking their own private key. In a more trust-minimized @@ -416,12 +415,6 @@ control_block_hex = control_block |> Base.encode16(case: :lower) # She then adds these to the Moon CET and broadcasts it -tx = %Transaction{moon_cet | witnesses: [ - %Transaction.Witness{ - txinwitness: [bob_moon_sig_hex, alice_moon_sig_hex, fund_script_hex, control_block_hex] - } -] -} tx = %Transaction{moon_cet | witnesses: [ %Transaction.Witness{ From b97759694f7dd55775b7f8768606c63905f7c608 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Mon, 13 Feb 2023 09:03:07 -0800 Subject: [PATCH 32/63] add psbt new fields --- lib/psbt.ex | 403 ++++++++++++++++++++++++++++++++++++++++++++- lib/transaction.ex | 27 +-- 2 files changed, 417 insertions(+), 13 deletions(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index 1a1fafb..cbfd700 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -92,6 +92,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 +122,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 +151,18 @@ defmodule Bitcoinex.PSBT.Utils do val_len = TxUtils.serialize_compact_size_unsigned_int(byte_size(val)) key_len <> key <> val_len <> val end + + def parse_fingerprint_path(data) do + <> = data + {:ok, indexes} = DerivationPath.parse(paths) + {pfp, indexes} + end + + def parse_leaf_hashes(value, leaf_hash_ct) do + <> = value + leaf_hashes = Enum.chunk_every(leaf_hashes, 32) + {leaf_hashes, value} + end end defmodule Bitcoinex.PSBT.Global do @@ -143,12 +179,22 @@ defmodule Bitcoinex.PSBT.Global do defstruct [ :unsigned_tx, :xpub, + :tx_version, + :fallback_locktime, + :input_count, + :output_count, + :tx_modifiable, :version, :proprietary ] @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 @@ -156,13 +202,14 @@ 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} @@ -212,6 +259,38 @@ defmodule Bitcoinex.PSBT.Global do {global, psbt} end + defp parse(<<@psbt_global_tx_version::big-size(8)>>, psbt, global) do + {value, 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 + {value, 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 {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) global = %Global{global | version: value} @@ -239,6 +318,27 @@ defmodule Bitcoinex.PSBT.Global do 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_fallback_locktime::big-size(8)>>, <>) + end + + def serialize_global(global) do # TODO: serialize all other fields in global. serialized_global = serialize_kv(:unsigned_tx, global.unsigned_tx) @@ -266,6 +366,7 @@ defmodule Bitcoinex.PSBT.In do alias Bitcoinex.PSBT.Utils, as: PsbtUtils alias Bitcoinex.Transaction.Utils, as: TxUtils alias Bitcoinex.ExtendedKey.DerivationPath, as: DerivationPath + alias Bitcoinex.Script defstruct [ :non_witness_utxo, @@ -278,6 +379,21 @@ defmodule Bitcoinex.PSBT.In do :final_scriptsig, :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 ] @@ -291,6 +407,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 @@ -298,6 +429,35 @@ defmodule Bitcoinex.PSBT.In do |> parse_input([], num_inputs) end + @spec from_tx_inputs(list(Transaction.In.t()), list(Transaction.Witness.t())) :: list(%In{}) + 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 + + 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 @@ -528,6 +688,152 @@ 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 + } + input = %In{input | ripemd160: data} + {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 + } + input = %In{input | sha256: data} + {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 + } + input = %In{input | hash160: data} + {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 + } + input = %In{input | hash256: data} + {input, psbt} + end + + defp parse(<<@psbt_in_previous_txid::big-size(8)>>, psbt, input) do + {<>, 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 + # TODO:validation must be > 500_000_000 + {<>, 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 + {<>, 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 script len (64|65) + {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + data = %{ + pubkey: pubkey, + leaf_hash: leaf_hash, + signature: value + } + input = %In{input | tap_script_sig: data} + {input, psbt} + end + + defp parse(<<@psbt_in_tap_leaf_script::big-size(8), control_block>>, psbt, input) do + {tapleaf, psbt} = PsbtUtils.parse_compact_size_value(psbt) + {script_bytes, <>} = PsbtUtils.parse_compact_size_value(tapleaf) + {: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, + } + input = %In{input | tap_leaf_script: data} + {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 = 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 = + case input.tap_bip32_derivation do + nil -> + [derivation] + + _ -> + [derivation | input.tap_bip32_derivation] + end + + 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} @@ -548,18 +854,32 @@ defmodule Bitcoinex.PSBT.Out do """ alias Bitcoinex.PSBT.Out alias Bitcoinex.PSBT.Utils, as: PsbtUtils + alias Bitcoinex.Transaction.Utils, as: TxUtils alias Bitcoinex.ExtendedKey.DerivationPath, as: DerivationPath + alias Bitcoinex.Script defstruct [ :redeem_script, :witness_script, :bip32_derivation, + :amount, + :script, + :tap_internal_key, + :tap_tree, + :tap_bip32_derivation, + :proprietary, :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, <<>>) @@ -569,6 +889,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)>>, @@ -707,4 +1032,78 @@ defmodule Bitcoinex.PSBT.Out do {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, []) + output = %Out{output | tap_tree: leaves} + {output, psbt} + end + + 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 = 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 = + case output.tap_bip32_derivation do + nil -> + [derivation] + + _ -> + [derivation | output.tap_bip32_derivation] + end + + 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 + } + parse_tap_tree(tree, [data | scripts]) + end + end 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)) From f4d7a3d2651e77939e917988bb15485d8cab5c5f Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 16 Feb 2023 17:25:18 -0800 Subject: [PATCH 33/63] psbt v2 serialization funcs --- lib/psbt.ex | 172 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 145 insertions(+), 27 deletions(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index cbfd700..b70bd24 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -153,16 +153,27 @@ defmodule Bitcoinex.PSBT.Utils do end def parse_fingerprint_path(data) do - <> = data + <> = data {:ok, indexes} = DerivationPath.parse(paths) {pfp, indexes} end + # reuse this elsewhere + def serialize_fingerprint_path(pfp, path) do + {:ok, path} = DerivationPath.serialize(path) + <> + end + def parse_leaf_hashes(value, leaf_hash_ct) do <> = value leaf_hashes = Enum.chunk_every(leaf_hashes, 32) {leaf_hashes, value} end + + def serialize_leaf_hashes(leaf_hashes) do + leaf_hashes = Enum.reduce(leaf_hashes, <<>>, fn leaf_hash, acc -> acc <> leaf_hash end) + TxUtils.serialize_compact_size_unsigned_int(length(leaf_hashes)) <> leaf_hashes + end end defmodule Bitcoinex.PSBT.Global do @@ -221,9 +232,7 @@ 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 - - {:ok, indexes} = DerivationPath.parse(paths) + {master, indexes} = PsbtUtils.parse_fingerprint_path(value) {:ok, xpub} = ExtendedKey.parse(xpub) if :binary.decode_unsigned(xpub.depth) != DerivationPath.depth(indexes), @@ -238,7 +247,7 @@ defmodule Bitcoinex.PSBT.Global do [ %{ xpub: xpub, - master_pfp: master, + pfp: master, derivation: indexes } ] @@ -248,7 +257,7 @@ defmodule Bitcoinex.PSBT.Global do [ %{ xpub: xpub, - master_pfp: master, + pfp: master, derivation: indexes } ] @@ -260,13 +269,13 @@ defmodule Bitcoinex.PSBT.Global do end defp parse(<<@psbt_global_tx_version::big-size(8)>>, psbt, global) do - {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + {<>, 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 - {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) global = %Global{global | fallback_locktime: value} {global, psbt} end @@ -311,9 +320,7 @@ defmodule Bitcoinex.PSBT.Global do key = <<@psbt_global_xpub::big-size(8)>> key_data = ExtendedKey.serialize(value.xpub, :no_checksum) - {:ok, deriv_bin} = DerivationPath.serialize(value.derivation) - - val = <> <> deriv_bin + val = PsbtUtils.serialize_fingerprint_path(value.pfp, value.derivation) PsbtUtils.serialize_kv(key <> key_data, val) end @@ -335,7 +342,11 @@ defmodule Bitcoinex.PSBT.Global do end defp serialize_kv(:tx_modifiable, value) when value != nil do - PsbtUtils.serialize_kv(<<@psbt_global_fallback_locktime::big-size(8)>>, <>) + PsbtUtils.serialize_kv(<<@psbt_global_tx_modifiable::big-size(8)>>, <>) + end + + defp serialize_kv(:proprietary, value) when value != nil do + PsbtUtils.serialize_kv(<<@psbt_global_proprietary::big-size(8)>>, value) end @@ -480,7 +491,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 @@ -514,13 +525,84 @@ defmodule Bitcoinex.PSBT.In do defp serialize_kv(:bip32_derivation, value) when value != nil do key_data = Base.decode16!(value.public_key, case: :lower) - {:ok, deriv_bin} = DerivationPath.serialize(value.derivation) - - val = <> <> deriv_bin + 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, value.leaf_hash>>, 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>>, 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.path) + + PsbtUtils.serialize_kv(<<@psbt_in_tap_bip32_derivation::big-size(8), value.pubkey>>, 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(_key, _value) do <<>> end @@ -600,7 +682,7 @@ defmodule Bitcoinex.PSBT.In do 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 @@ -627,7 +709,7 @@ defmodule Bitcoinex.PSBT.In do 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 @@ -733,7 +815,7 @@ defmodule Bitcoinex.PSBT.In do end defp parse(<<@psbt_in_previous_txid::big-size(8)>>, psbt, input) do - {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) + {value = <<_::binary-size(32)>>, psbt} = PsbtUtils.parse_compact_size_value(psbt) input = %In{input | previous_txid: value} {input, psbt} end @@ -745,20 +827,20 @@ defmodule Bitcoinex.PSBT.In do end defp parse(<<@psbt_in_sequence::big-size(8)>>, psbt, input) do - # TODO:validation must be > 500_000_000 {<>, 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 + # 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} @@ -801,7 +883,7 @@ defmodule Bitcoinex.PSBT.In do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) {leaf_hash_ct, value} = TxUtils.get_counter(value) - leaf_hashes = PsbtUtils.parse_leaf_hashes(value, leaf_hash_ct) + {leaf_hashes, value} = PsbtUtils.parse_leaf_hashes(value, leaf_hash_ct) {pfp, path} = PsbtUtils.parse_fingerprint_path(value) derivation = %{ @@ -910,12 +992,38 @@ 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 = PsbtUtils.serialize_fingerprint_path(value.pfp, value.derivation) - {:ok, deriv_bin} = DerivationPath.serialize(value.derivation) + PsbtUtils.serialize_kv(<<@psbt_out_bip32_derivation::big-size(8)>> <> key_data, val) + end - val = <> <> deriv_bin + defp serialize_kv(:amount, value) when value != nil do + PsbtUtils.serialize_kv(<<@psbt_out_amount::big-size(8)>>, <>) + end - PsbtUtils.serialize_kv(<<@psbt_out_bip32_derivation::big-size(8)>> <> key_data, val) + 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) + 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>> + leaf_hashes = PsbtUtils.serialize_leaf_hashes(value.leaf_hashes) + fingerprint_path = PsbtUtils.serialize_fingerprint_path(value.pfp, value.path) + + 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 @@ -1000,8 +1108,7 @@ defmodule Bitcoinex.PSBT.Out do ) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - <> = value - {:ok, indexes} = DerivationPath.parse(paths) + {pfp, indexes} = PsbtUtils.parse_fingerprint_path(value) bip32_derivation = case output.bip32_derivation do @@ -1106,4 +1213,15 @@ defmodule Bitcoinex.PSBT.Out do 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 From 41fd7d5ce113911ae6d2040a2b3e34fbcf1809e4 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 16 Feb 2023 17:33:42 -0800 Subject: [PATCH 34/63] fix lints --- lib/psbt.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index b70bd24..c2f9426 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -165,7 +165,7 @@ defmodule Bitcoinex.PSBT.Utils do end def parse_leaf_hashes(value, leaf_hash_ct) do - <> = value + <> = value leaf_hashes = Enum.chunk_every(leaf_hashes, 32) {leaf_hashes, value} end @@ -937,7 +937,6 @@ defmodule Bitcoinex.PSBT.Out do alias Bitcoinex.PSBT.Out alias Bitcoinex.PSBT.Utils, as: PsbtUtils alias Bitcoinex.Transaction.Utils, as: TxUtils - alias Bitcoinex.ExtendedKey.DerivationPath, as: DerivationPath alias Bitcoinex.Script defstruct [ From 31dedf71d5bf084d1efa39422a332fdef43d6316 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Mon, 20 Feb 2023 23:54:03 -0800 Subject: [PATCH 35/63] rm duplicate test cases & add serialization & improve logic for handling duplicate keys --- lib/extendedkey.ex | 22 ++- lib/psbt.ex | 438 +++++++++++++++++++++++++-------------------- test/psbt_test.exs | 256 +++++++++----------------- 3 files changed, 339 insertions(+), 377 deletions(-) diff --git a/lib/extendedkey.ex b/lib/extendedkey.ex index a65d7ff..a0db3bd 100644 --- a/lib/extendedkey.ex +++ b/lib/extendedkey.ex @@ -416,16 +416,20 @@ defmodule Bitcoinex.ExtendedKey do """ @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 + 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 + 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 diff --git a/lib/psbt.ex b/lib/psbt.ex index c2f9426..9793149 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -113,7 +113,7 @@ defmodule Bitcoinex.PSBT do witnesses = In.populate_witnesses(psbt.inputs) - %Bitcoinex.Transaction{ tx | witnesses: witnesses, inputs: inputs} + %Bitcoinex.Transaction{tx | witnesses: witnesses, inputs: inputs} end end @@ -152,28 +152,39 @@ defmodule Bitcoinex.PSBT.Utils do 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, indexes} = DerivationPath.parse(paths) - {pfp, indexes} + <> = 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} = DerivationPath.serialize(path) - <> + {:ok, path_bin} = DerivationPath.serialize(path) + pfp <> path_bin end def parse_leaf_hashes(value, leaf_hash_ct) do - <> = value + <> = value leaf_hashes = Enum.chunk_every(leaf_hashes, 32) {leaf_hashes, value} end + @spec serialize_leaf_hashes(list(binary)) :: binary def serialize_leaf_hashes(leaf_hashes) do - leaf_hashes = Enum.reduce(leaf_hashes, <<>>, fn leaf_hash, acc -> acc <> leaf_hash end) - TxUtils.serialize_compact_size_unsigned_int(length(leaf_hashes)) <> leaf_hashes + 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 @@ -220,6 +231,7 @@ defmodule Bitcoinex.PSBT.Global do {txn_len, psbt} = TxUtils.get_counter(psbt) <> = psbt + case Transaction.decode(txn_bytes) do {:ok, txn} -> {%Global{global | unsigned_tx: txn}, psbt} @@ -232,10 +244,10 @@ 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) - {master, indexes} = PsbtUtils.parse_fingerprint_path(value) + {master, path} = PsbtUtils.parse_fingerprint_path(value) {:ok, xpub} = ExtendedKey.parse(xpub) - if :binary.decode_unsigned(xpub.depth) != DerivationPath.depth(indexes), + 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" @@ -248,7 +260,7 @@ defmodule Bitcoinex.PSBT.Global do %{ xpub: xpub, pfp: master, - derivation: indexes + derivation: path } ] @@ -258,7 +270,7 @@ defmodule Bitcoinex.PSBT.Global do %{ xpub: xpub, pfp: master, - derivation: indexes + derivation: path } ] end @@ -318,7 +330,7 @@ defmodule Bitcoinex.PSBT.Global do defp serialize_kv(:xpub, value) when value != nil do key = <<@psbt_global_xpub::big-size(8)>> - key_data = ExtendedKey.serialize(value.xpub, :no_checksum) + key_data = ExtendedKey.serialize(value.xpub, with_checksum?: false) val = PsbtUtils.serialize_fingerprint_path(value.pfp, value.derivation) @@ -330,15 +342,24 @@ defmodule Bitcoinex.PSBT.Global do end defp serialize_kv(:fallback_locktime, value) when value != nil do - PsbtUtils.serialize_kv(<<@psbt_global_fallback_locktime::big-size(8)>>, <>) + 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)) + 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)) + 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 @@ -349,7 +370,6 @@ defmodule Bitcoinex.PSBT.Global 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) @@ -376,7 +396,6 @@ defmodule Bitcoinex.PSBT.In do alias Bitcoinex.PSBT.In alias Bitcoinex.PSBT.Utils, as: PsbtUtils alias Bitcoinex.Transaction.Utils, as: TxUtils - alias Bitcoinex.ExtendedKey.DerivationPath, as: DerivationPath alias Bitcoinex.Script defstruct [ @@ -405,7 +424,8 @@ defmodule Bitcoinex.PSBT.In do :tap_bip32_derivation, :tap_internal_key, :tap_merkle_root, - :proprietary + :proprietary, + :unknown ] @psbt_in_non_witness_utxo 0x00 @@ -418,12 +438,12 @@ 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_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 @@ -443,30 +463,34 @@ defmodule Bitcoinex.PSBT.In do @spec from_tx_inputs(list(Transaction.In.t()), list(Transaction.Witness.t())) :: list(%In{}) 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{ + [ + %In{ final_scriptsig: input.script_sig, final_scriptwitness: witness - } | acc] + } + | 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] + + 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() + 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 @@ -535,19 +559,31 @@ defmodule Bitcoinex.PSBT.In do 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) + 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) + 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) + 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) + 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 @@ -563,11 +599,17 @@ defmodule Bitcoinex.PSBT.In do end defp serialize_kv(:required_time_locktime, value) when value != nil do - PsbtUtils.serialize_kv(<<@psbt_in_required_time_locktime::big-size(8)>>, <>) + 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)>>, <>) + PsbtUtils.serialize_kv( + <<@psbt_in_required_height_locktime::big-size(8)>>, + <> + ) end defp serialize_kv(:tap_key_sig, value) when value != nil do @@ -575,20 +617,30 @@ defmodule Bitcoinex.PSBT.In do 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, value.leaf_hash>>, value.signature) + PsbtUtils.serialize_kv( + <<@psbt_in_tap_script_sig::big-size(8), value.pubkey, value.leaf_hash>>, + 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>>, script_bytes <> <> ) + + PsbtUtils.serialize_kv( + <<@psbt_in_tap_leaf_script::big-size(8), value.control_block>>, + 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.path) - PsbtUtils.serialize_kv(<<@psbt_in_tap_bip32_derivation::big-size(8), value.pubkey>>, leaf_hashes <> fingerprint_path) + PsbtUtils.serialize_kv( + <<@psbt_in_tap_bip32_derivation::big-size(8), value.pubkey>>, + leaf_hashes <> fingerprint_path + ) end defp serialize_kv(:tap_internal_key, value) when value != nil do @@ -603,6 +655,10 @@ defmodule Bitcoinex.PSBT.In 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 @@ -625,11 +681,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 -> @@ -637,24 +713,20 @@ 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 <> <<0x00::big-size(8)>> serialize_input(inputs, serialized_inputs <> serialized_input) end @@ -663,19 +735,11 @@ 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 @@ -697,14 +761,14 @@ 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 @@ -729,30 +793,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 - {:ok, indexes} = DerivationPath.parse(paths) + {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} @@ -773,44 +822,54 @@ defmodule Bitcoinex.PSBT.In do 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 } - input = %In{input | ripemd160: data} + + 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 } - input = %In{input | sha256: data} + 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 } - input = %In{input | hash160: data} + 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 } - input = %In{input | hash256: data} + hash256s = PsbtUtils.append(input.hash256, data) + input = %In{input | hash256: hash256s} {input, psbt} end @@ -853,15 +912,23 @@ defmodule Bitcoinex.PSBT.In do {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 script len (64|65) + 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 } - input = %In{input | tap_script_sig: data} + tap_script_sigs = PsbtUtils.append(input.tap_script_sig, data) + + input = %In{input | tap_script_sig: tap_script_sigs} {input, psbt} end @@ -869,13 +936,15 @@ defmodule Bitcoinex.PSBT.In do {tapleaf, psbt} = PsbtUtils.parse_compact_size_value(psbt) {script_bytes, <>} = PsbtUtils.parse_compact_size_value(tapleaf) {: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, + control_block: control_block } - input = %In{input | tap_leaf_script: data} + tap_leaf_scripts = PsbtUtils.append(input.tap_leaf_script, data) + input = %In{input | tap_leaf_script: tap_leaf_scripts} {input, psbt} end @@ -893,23 +962,18 @@ defmodule Bitcoinex.PSBT.In do derivation: path } - tap_bip32_derivation = - case input.tap_bip32_derivation do - nil -> - [derivation] - - _ -> - [derivation | input.tap_bip32_derivation] - end + 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} @@ -928,6 +992,20 @@ 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 @@ -948,7 +1026,6 @@ defmodule Bitcoinex.PSBT.Out do :tap_internal_key, :tap_tree, :tap_bip32_derivation, - :proprietary, :proprietary ] @@ -1029,31 +1106,43 @@ defmodule Bitcoinex.PSBT.Out 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 @@ -1065,27 +1154,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 @@ -1107,35 +1177,16 @@ defmodule Bitcoinex.PSBT.Out do ) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - {pfp, indexes} = 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 - } - ] - - _ -> - [ - %{ - public_key: Base.encode16(public_key, case: :lower), - pfp: pfp, - derivation: indexes - } - | output.bip32_derivation - ] - end + {pfp, path} = PsbtUtils.parse_fingerprint_path(value) - output = %Out{ - output - | bip32_derivation: bip32_derivation + derivation = %{ + public_key: Base.encode16(public_key, case: :lower), + pfp: pfp, + derivation: path } + bip32_derivation = PsbtUtils.append(output.bip32_derivation, derivation) + output = %Out{output | bip32_derivation: bip32_derivation} {output, psbt} end @@ -1165,7 +1216,11 @@ defmodule Bitcoinex.PSBT.Out do {output, psbt} end - defp parse(<<@psbt_out_tap_bip32_derivation::big-size(8), pubkey::binary-size(32)>>, psbt, output) do + 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) @@ -1179,15 +1234,7 @@ defmodule Bitcoinex.PSBT.Out do derivation: path } - tap_bip32_derivation = - case output.tap_bip32_derivation do - nil -> - [derivation] - - _ -> - [derivation | output.tap_bip32_derivation] - end - + tap_bip32_derivation = PsbtUtils.append( output.tap_bip32_derivation, derivation) output = %Out{output | tap_bip32_derivation: tap_bip32_derivation} {output, psbt} end @@ -1199,16 +1246,19 @@ defmodule Bitcoinex.PSBT.Out do 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 } + parse_tap_tree(tree, [data | scripts]) end @@ -1217,10 +1267,10 @@ defmodule Bitcoinex.PSBT.Out do # 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 + acc <> + <> <> + TxUtils.serialize_compact_size_unsigned_int(byte_size(script_bytes)) <> + script_bytes end) end - end diff --git a/test/psbt_test.exs b/test/psbt_test.exs index 5823b1d..e622f9d 100644 --- a/test/psbt_test.exs +++ b/test/psbt_test.exs @@ -132,30 +132,11 @@ defmodule Bitcoinex.PSBTTest do }, 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 +144,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 - } + %Bitcoinex.PSBT.Out{}, + %Bitcoinex.PSBT.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,16 +168,14 @@ defmodule Bitcoinex.PSBTTest do value: 499_990_000 } ], - version: 2, - witnesses: nil + version: 2 }, - version: nil, xpub: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ child_nums: [2_147_483_822, 2_147_483_648] }, - master_pfp: 1_332_350_169, + pfp: <<217, 12, 106, 79>>, xpub: Bitcoinex.ExtendedKey.parse!( "tpubDBkJeJo2X94Yq3RVz65DoUgyLUkaDrkfyrn2VcgyCRSKCRonvKvCF2FpYDGJWDkdRHBajXJGpc63GnumUt63ySvqCu2XaTRGVTKMYGuFk9H" @@ -217,7 +185,7 @@ defmodule Bitcoinex.PSBTTest do derivation: %Bitcoinex.ExtendedKey.DerivationPath{ child_nums: [2_147_483_822, 2_147_483_649] }, - master_pfp: 1_332_350_169, + pfp: <<217, 12, 106, 79>>, xpub: Bitcoinex.ExtendedKey.parse!( "tpubDBkJeJo2X94YsvtBEU1eKoibEWiNv51nW5iHhs6VZp59jsE6nen8KZMFyGHuGbCvqjRqirgeMcfpVBkttpUUT6brm4duzSGoZeTbhqCNUu6" @@ -233,24 +201,16 @@ defmodule Bitcoinex.PSBTTest do 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: %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{ @@ -268,12 +228,9 @@ defmodule Bitcoinex.PSBTTest do 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 + ] } ] }, @@ -298,11 +255,8 @@ defmodule Bitcoinex.PSBTTest do value: 199_908_000 } ], - version: 2, - witnesses: nil - }, - version: nil, - xpub: nil + version: 2 + } }, expected_in: [ %Bitcoinex.PSBT.In{ @@ -312,28 +266,22 @@ defmodule Bitcoinex.PSBTTest do 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: %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>> } ], - final_scriptsig: nil, - final_scriptwitness: nil, - non_witness_utxo: nil, - partial_sig: %{ + 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{ @@ -343,12 +291,7 @@ defmodule Bitcoinex.PSBTTest do } ], expected_out: [ - %Bitcoinex.PSBT.Out{ - bip32_derivation: nil, - proprietary: nil, - redeem_script: nil, - witness_script: nil - } + %Bitcoinex.PSBT.Out{} ] }, %{ @@ -384,16 +327,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, final_scriptwitness: %Bitcoinex.Transaction.Witness{ txinwitness: [ "", @@ -402,13 +340,6 @@ 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", @@ -416,8 +347,6 @@ defmodule Bitcoinex.PSBTTest do } }, %Bitcoinex.PSBT.In{ - bip32_derivation: nil, - final_scriptsig: nil, final_scriptwitness: %Bitcoinex.Transaction.Witness{ txinwitness: [ "", @@ -426,13 +355,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", @@ -448,155 +370,141 @@ defmodule Bitcoinex.PSBTTest do 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: %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: %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 - } + %Bitcoinex.PSBT.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: - "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAQMEAQAAAAAAAA==", + "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA=", expected_global: %Global{ - proprietary: nil, unsigned_tx: %Bitcoinex.Transaction{ + version: 2, inputs: [ %Bitcoinex.Transaction.In{ - prev_txid: "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126", + 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 } ], - lock_time: 1_257_139, outputs: [ %Bitcoinex.Transaction.Out{ - script_pub_key: "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac", - value: 99_999_699 + value: 199_900_000, + script_pub_key: "76a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac" }, %Bitcoinex.Transaction.Out{ - script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787", - value: 100_000_000 + value: 9358, + script_pub_key: "76a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac" } ], witnesses: nil, - version: 2 - }, - version: nil, - xpub: nil + lock_time: 0 + } }, expected_in: [ %Bitcoinex.PSBT.In{ - bip32_derivation: nil, - final_scriptsig: nil, - final_scriptwitness: nil, non_witness_utxo: %Bitcoinex.Transaction{ + version: 2, inputs: [ %Bitcoinex.Transaction.In{ - prev_txid: "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389", - prev_vout: 1, - script_sig: "160014be18d152a9b012039daf3da7de4f53349eecb985", - sequence_no: 4_294_967_295 - }, - %Bitcoinex.Transaction.In{ - prev_txid: "b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886", - prev_vout: 1, - script_sig: "160014fe3e9ef1a745e974d902c4355943abcb34bd5353", - sequence_no: 4_294_967_295 + prev_txid: "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126", + prev_vout: 0, + script_sig: + "473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31", + sequence_no: 4_294_967_294 } ], - lock_time: 0, outputs: [ %Bitcoinex.Transaction.Out{ - script_pub_key: "76a91485cff1097fd9e008bb34af709c62197b38978a4888ac", - value: 200_000_000 + value: 99_999_699, + script_pub_key: "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac" }, %Bitcoinex.Transaction.Out{ - script_pub_key: "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587", - value: 190_303_501_938 + value: 100_000_000, + script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787" } ], - version: 1, - witnesses: [ - %Bitcoinex.Transaction.Witness{ - txinwitness: [ - "304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01", - "03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105" - ] - }, - %Bitcoinex.Transaction.Witness{ - txinwitness: [ - "3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01", - "0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3" - ] - } - ] + witnesses: nil, + lock_time: 1_257_139 + } + }, + %Bitcoinex.PSBT.In{ + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 100_000_000, + script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787" }, - partial_sig: nil, - por_commitment: nil, - proprietary: nil, - redeem_script: nil, - sighash_type: <<1, 0, 0, 0>>, - witness_script: nil, - witness_utxo: nil + redeem_script: "001485d13537f2e265405a34dbafa9e3dda01fb82308" } ], expected_out: [ %Bitcoinex.PSBT.Out{ - bip32_derivation: nil, - proprietary: nil, - redeem_script: nil, - witness_script: nil + 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" + } + ] }, %Bitcoinex.PSBT.Out{ - bip32_derivation: nil, - proprietary: nil, - redeem_script: nil, - witness_script: nil + 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: + # "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAACvABAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAA=", + # expected_global: %{} #TODO fix + # } ] describe "decode/1" do From 5820c7538d38a03c297ee7d6e7fed5de7179b63b Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Tue, 21 Feb 2023 00:31:18 -0800 Subject: [PATCH 36/63] add psbt test vectors --- test/psbt_test.exs | 311 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 277 insertions(+), 34 deletions(-) diff --git a/test/psbt_test.exs b/test/psbt_test.exs index e622f9d..3a8fe8b 100644 --- a/test/psbt_test.exs +++ b/test/psbt_test.exs @@ -78,22 +78,12 @@ 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{ } ] }, %{ @@ -144,8 +134,8 @@ defmodule Bitcoinex.PSBTTest do } ], expected_out: [ - %Bitcoinex.PSBT.Out{}, - %Bitcoinex.PSBT.Out{} + %Out{}, + %Out{} ] }, %{ @@ -194,7 +184,7 @@ defmodule Bitcoinex.PSBTTest do ] }, expected_in: [ - %Bitcoinex.PSBT.In{ + %In{ bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -238,7 +228,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{ @@ -259,7 +248,7 @@ defmodule Bitcoinex.PSBTTest do } }, expected_in: [ - %Bitcoinex.PSBT.In{ + %In{ bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -291,7 +280,7 @@ defmodule Bitcoinex.PSBTTest do } ], expected_out: [ - %Bitcoinex.PSBT.Out{} + %Out{} ] }, %{ @@ -331,7 +320,7 @@ defmodule Bitcoinex.PSBTTest do } }, expected_in: [ - %Bitcoinex.PSBT.In{ + %In{ final_scriptwitness: %Bitcoinex.Transaction.Witness{ txinwitness: [ "", @@ -346,7 +335,7 @@ defmodule Bitcoinex.PSBTTest do value: 10_000_000 } }, - %Bitcoinex.PSBT.In{ + %In{ final_scriptwitness: %Bitcoinex.Transaction.Witness{ txinwitness: [ "", @@ -363,7 +352,7 @@ defmodule Bitcoinex.PSBTTest do } ], expected_out: [ - %Bitcoinex.PSBT.Out{ + %Out{ bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -390,7 +379,7 @@ defmodule Bitcoinex.PSBTTest do witness_script: "5221023e1a92f74483a4c12a196d6c0253ac589cfadd5964ff3e6c63aef5d577051ed421024d0e39b33ab57782a98fd06a7d0653385e509ee3b74dc45559e6db6391d3137821036c0bae6bcb02e47c857c480ed1d23888c8ad6a66b83ab111cb6b9242afb1c72553ae" }, - %Bitcoinex.PSBT.Out{} + %Out{} ] }, %{ @@ -436,12 +425,11 @@ defmodule Bitcoinex.PSBTTest do script_pub_key: "76a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac" } ], - witnesses: nil, lock_time: 0 } }, expected_in: [ - %Bitcoinex.PSBT.In{ + %In{ non_witness_utxo: %Bitcoinex.Transaction{ version: 2, inputs: [ @@ -463,11 +451,10 @@ defmodule Bitcoinex.PSBTTest do script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787" } ], - witnesses: nil, lock_time: 1_257_139 } }, - %Bitcoinex.PSBT.In{ + %In{ witness_utxo: %Bitcoinex.Transaction.Out{ value: 100_000_000, script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787" @@ -476,7 +463,7 @@ defmodule Bitcoinex.PSBTTest do } ], expected_out: [ - %Bitcoinex.PSBT.Out{ + %Out{ bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -487,7 +474,7 @@ defmodule Bitcoinex.PSBTTest do } ] }, - %Bitcoinex.PSBT.Out{ + %Out{ bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -500,11 +487,267 @@ defmodule Bitcoinex.PSBTTest do } ] }, - # %{ - # psbt: - # "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAACvABAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAA=", - # expected_global: %{} #TODO fix - # } + %{ + 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{ + unsigned_tx: %Bitcoinex.Transaction{ + version: 2, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126", + prev_vout: 0, + script_sig: "", + sequence_no: 4294967294 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 99999699, + script_pub_key: "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac" + }, + %Bitcoinex.Transaction.Out{ + value: 100000000, + script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787" + } + ], + lock_time: 1257139 + }, + }, + expected_in: [ + %In{ + non_witness_utxo: %Bitcoinex.Transaction{ + version: 1, + inputs: [ + %Bitcoinex.Transaction.In{ + prev_txid: "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389", + prev_vout: 1, + script_sig: "160014be18d152a9b012039daf3da7de4f53349eecb985", + sequence_no: 4294967295 + }, + %Bitcoinex.Transaction.In{ + prev_txid: "b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886", + prev_vout: 1, + script_sig: "160014fe3e9ef1a745e974d902c4355943abcb34bd5353", + sequence_no: 4294967295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 200000000, + script_pub_key: "76a91485cff1097fd9e008bb34af709c62197b38978a4888ac" + }, + %Bitcoinex.Transaction.Out{ + value: 190303501938, + script_pub_key: "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587" + } + ], + witnesses: [ + %Bitcoinex.Transaction.Witness{ + txinwitness: ["304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01", + "03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105"] + }, + %Bitcoinex.Transaction.Witness{ + txinwitness: ["3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01", + "0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3"] + } + ], + lock_time: 0 + }, + sighash_type: 1, + } + ], + expected_out: [ + %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: 4294967295 + } + ], + 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: 4294967295 + }, + %Bitcoinex.Transaction.In{ + prev_txid: "676d8d58420d5c4647d4a872b83966a06a10a4fbb2cfcb2cc8c8b3a8d6940919", + prev_vout: 0, + script_sig: "", + sequence_no: 4294967295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 100000000, + script_pub_key: "76a914b6bc2c0ee5655a843d79afedd0ccc3f7dd64340988ac" + }, + %Bitcoinex.Transaction.Out{ + value: 99900000, + script_pub_key: "00141188ef8e4ce0449eaac8fb141cbf5a1176e6a088" + } + ], + lock_time: 0 + }, + xpub: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2147483697, 2147483648, 2147483648] + }, + 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: 100000000, + script_pub_key: "001433b982f91b28f160c920b4ab95e58ce50dda3a4a" + }, + partial_sig: [ + %{ + public_key: "03309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c", + signature: "304402202d704ced830c56a909344bd742b6852dccd103e963bae92d38e75254d2bb424502202d86c437195df46c0ceda084f2a291c3da2d64070f76bf9b90b195e7ef28f77201" + } + ], + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2147483697, 2147483648, 2147483648, 0, 1] + }, + pfp: <<39, 86, 156, 80>>, + public_key: "03309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c" + } + ] + }, + %In{ + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 100000000, + script_pub_key: "0014388fb944307eb77ef45197d0b0b245e079f011de" + }, + partial_sig: [ + %{ + public_key: "02c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b110", + signature: "304402204cb1fb5f869c942e0e26100576125439179ae88dca8a9dc3ba08f7953988faa60220521f49ca791c27d70e273c9b14616985909361e25be274ea200d7e08827e514d01" + } + ], + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2147483697, 2147483648, 2147483648, 0, 0] + }, + pfp: <<39, 86, 156, 80>>, + public_key: "02c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b110" + } + ] + } + ], + expected_out: [ + %Out{ + + }, + %Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2147483697, 2147483648, 2147483648, 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: 99999699, + script_pub_key: "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac" + }, + %Bitcoinex.Transaction.Out{ + value: 100000000, + script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787" + } + ], + witnesses: nil, + lock_time: 1257139 + }, + }, + expected_in: [], + expected_out: [ + %Out{}, + %Out{} + ] + } ] describe "decode/1" do From ed4f6b6c94cd7cd356615f03a687a5cb893b7992 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Tue, 21 Feb 2023 00:49:33 -0800 Subject: [PATCH 37/63] add all fields to psbt global serializer --- lib/psbt.ex | 101 ++++++++++++++++++++++---------------- test/psbt_test.exs | 117 ++++++++++++++++++++++++--------------------- 2 files changed, 121 insertions(+), 97 deletions(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index 9793149..a49defa 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -153,6 +153,7 @@ defmodule Bitcoinex.PSBT.Utils do 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() @@ -207,7 +208,8 @@ defmodule Bitcoinex.PSBT.Global do :output_count, :tx_modifiable, :version, - :proprietary + :proprietary, + :unknown ] @psbt_global_unsigned_tx 0x00 @@ -253,29 +255,15 @@ defmodule Bitcoinex.PSBT.Global do message: "invalid xpub in PSBT: depth does not match number of indexes provided" ) - global_xpub = - case global.xpub do - nil -> - [ - %{ - xpub: xpub, - pfp: master, - derivation: path - } - ] + global_xpub = %{ + xpub: xpub, + pfp: master, + derivation: path + } - _ -> - global.xpub ++ - [ - %{ - xpub: xpub, - pfp: master, - derivation: path - } - ] - end + global_xpubs = PsbtUtils.append(global.xpub, global_xpub) - global = %Global{global | xpub: global_xpub} + global = %Global{global | xpub: global_xpubs} {global, psbt} end @@ -371,18 +359,39 @@ defmodule Bitcoinex.PSBT.Global do 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 + + [] -> + acc + + v = [_ | _] -> + acc <> PsbtUtils.serialize_repeatable_fields(k, v, &serialize_kv/2) + + v -> + acc <> serialize_kv(k, v) + end + end + ) - serialized_global <> bip32 <> <<0x00::big-size(8)>> + serialized_global <> <<0x00::big-size(8)>> end end @@ -725,8 +734,7 @@ defmodule Bitcoinex.PSBT.In do end ) - serialized_input = - serialized_input <> <<0x00::big-size(8)>> + serialized_input = serialized_input <> <<0x00::big-size(8)>> serialize_input(inputs, serialized_inputs <> serialized_input) end @@ -842,6 +850,7 @@ defmodule Bitcoinex.PSBT.In do hash: hash, preimage: preimage } + sha256s = PsbtUtils.append(input.sha256, data) input = %In{input | sha256: sha256s} {input, psbt} @@ -855,6 +864,7 @@ defmodule Bitcoinex.PSBT.In do hash: hash, preimage: preimage } + hash160s = PsbtUtils.append(input.hash160, data) input = %In{input | hash160: hash160s} {input, psbt} @@ -868,6 +878,7 @@ defmodule Bitcoinex.PSBT.In do hash: hash, preimage: preimage } + hash256s = PsbtUtils.append(input.hash256, data) input = %In{input | hash256: hash256s} {input, psbt} @@ -926,6 +937,7 @@ defmodule Bitcoinex.PSBT.In do 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} @@ -943,6 +955,7 @@ defmodule Bitcoinex.PSBT.In do 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} @@ -996,13 +1009,16 @@ defmodule Bitcoinex.PSBT.In do 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 + case input.unknown do + nil -> + [kv] + + _ -> + input.unknown ++ [kv] + end + input = %In{input | unknown: unknown} {input, psbt} end @@ -1107,6 +1123,7 @@ defmodule Bitcoinex.PSBT.Out do end defp serialize_output([], serialized_outputs), do: serialized_outputs + defp serialize_output(outputs, serialized_outputs) do [output | outputs] = outputs @@ -1234,7 +1251,7 @@ defmodule Bitcoinex.PSBT.Out do derivation: path } - tap_bip32_derivation = PsbtUtils.append( output.tap_bip32_derivation, derivation) + tap_bip32_derivation = PsbtUtils.append(output.tap_bip32_derivation, derivation) output = %Out{output | tap_bip32_derivation: tap_bip32_derivation} {output, psbt} end diff --git a/test/psbt_test.exs b/test/psbt_test.exs index 3a8fe8b..8fe186e 100644 --- a/test/psbt_test.exs +++ b/test/psbt_test.exs @@ -78,12 +78,12 @@ defmodule Bitcoinex.PSBTTest do ] } ] - }, + } } ], expected_out: [ - %Out{ }, - %Out{ } + %Out{}, + %Out{} ] }, %{ @@ -265,11 +265,13 @@ defmodule Bitcoinex.PSBTTest do pfp: <<180, 166, 186, 103>> } ], - partial_sig: [%{ - public_key: "03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46", - signature: - "304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01" - }], + partial_sig: [ + %{ + public_key: "03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46", + signature: + "304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01" + } + ], redeem_script: "0020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681", witness_script: "522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae", @@ -488,7 +490,8 @@ defmodule Bitcoinex.PSBTTest do ] }, %{ - psbt: "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAQMEAQAAAAAAAA==", + 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{ unsigned_tx: %Bitcoinex.Transaction{ version: 2, @@ -497,21 +500,21 @@ defmodule Bitcoinex.PSBTTest do prev_txid: "f61b1742ca13176464adb3cb66050c00787bb3a4eead37e985f2df1e37718126", prev_vout: 0, script_sig: "", - sequence_no: 4294967294 + sequence_no: 4_294_967_294 } ], outputs: [ %Bitcoinex.Transaction.Out{ - value: 99999699, + value: 99_999_699, script_pub_key: "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac" }, %Bitcoinex.Transaction.Out{ - value: 100000000, + value: 100_000_000, script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787" } ], - lock_time: 1257139 - }, + lock_time: 1_257_139 + } }, expected_in: [ %In{ @@ -522,38 +525,42 @@ defmodule Bitcoinex.PSBTTest do prev_txid: "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389", prev_vout: 1, script_sig: "160014be18d152a9b012039daf3da7de4f53349eecb985", - sequence_no: 4294967295 + sequence_no: 4_294_967_295 }, %Bitcoinex.Transaction.In{ prev_txid: "b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886", prev_vout: 1, script_sig: "160014fe3e9ef1a745e974d902c4355943abcb34bd5353", - sequence_no: 4294967295 + sequence_no: 4_294_967_295 } ], outputs: [ %Bitcoinex.Transaction.Out{ - value: 200000000, + value: 200_000_000, script_pub_key: "76a91485cff1097fd9e008bb34af709c62197b38978a4888ac" }, %Bitcoinex.Transaction.Out{ - value: 190303501938, + value: 190_303_501_938, script_pub_key: "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587" } ], witnesses: [ %Bitcoinex.Transaction.Witness{ - txinwitness: ["304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01", - "03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105"] + txinwitness: [ + "304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01", + "03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105" + ] }, %Bitcoinex.Transaction.Witness{ - txinwitness: ["3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01", - "0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3"] + txinwitness: [ + "3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01", + "0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3" + ] } ], lock_time: 0 }, - sighash_type: 1, + sighash_type: 1 } ], expected_out: [ @@ -572,7 +579,7 @@ defmodule Bitcoinex.PSBTTest do prev_txid: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", prev_vout: 0, script_sig: "", - sequence_no: 4294967295 + sequence_no: 4_294_967_295 } ], outputs: [%Bitcoinex.Transaction.Out{value: 0, script_pub_key: "6a0100"}], @@ -594,7 +601,8 @@ defmodule Bitcoinex.PSBTTest do ] }, %{ - psbt: "cHNidP8BAJ0BAAAAAnEOp2q0XFy2Q45gflnMA3YmmBgFrp4N/ZCJASq7C+U1AQAAAAD/////GQmU1qizyMgsy8+y+6QQaqBmObhyqNRHRlwNQliNbWcAAAAAAP////8CAOH1BQAAAAAZdqkUtrwsDuVlWoQ9ea/t0MzD991kNAmIrGBa9AUAAAAAFgAUEYjvjkzgRJ6qyPsUHL9aEXbmoIgAAAAATwEEiLIeA55TDKyAAAAAPbyKXJdp8DGxfnf+oVGGAyIaGP0Y8rmlTGyMGsdcvDUC8jBYSxVdHH8c1FEgplPEjWULQxtnxbLBPyfXFCA3wWkQJ1acUDEAAIAAAACAAAAAgAABAR8A4fUFAAAAABYAFDO5gvkbKPFgySC0q5XljOUN2jpKIgIDMJaA8zx9446mpHzU7NZvH1pJdHxv+4gI7QkDkkPjrVxHMEQCIC1wTO2DDFapCTRL10K2hS3M0QPpY7rpLTjnUlTSu0JFAiAthsQ3GV30bAztoITyopHD2i1kBw92v5uQsZXn7yj3cgEiBgMwloDzPH3jjqakfNTs1m8fWkl0fG/7iAjtCQOSQ+OtXBgnVpxQMQAAgAAAAIAAAACAAAAAAAEAAAAAAQEfAOH1BQAAAAAWABQ4j7lEMH63fvRRl9CwskXgefAR3iICAsd3Fh9z0LfHK57nveZQKT0T8JW8dlatH1Jdpf0uELEQRzBEAiBMsftfhpyULg4mEAV2ElQ5F5rojcqKncO6CPeVOYj6pgIgUh9JynkcJ9cOJzybFGFphZCTYeJb4nTqIA1+CIJ+UU0BIgYCx3cWH3PQt8crnue95lApPRPwlbx2Vq0fUl2l/S4QsRAYJ1acUDEAAIAAAACAAAAAgAAAAAAAAAAAAAAiAgLSDKUC7iiWhtIYFb1DqAY3sGmOH7zb5MrtRF9sGgqQ7xgnVpxQMQAAgAAAAIAAAACAAAAAAAQAAAAA", + 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, @@ -603,22 +611,22 @@ defmodule Bitcoinex.PSBTTest do prev_txid: "35e50bbb2a018990fd0d9eae051898267603cc597e608e43b65c5cb46aa70e71", prev_vout: 1, script_sig: "", - sequence_no: 4294967295 + sequence_no: 4_294_967_295 }, %Bitcoinex.Transaction.In{ prev_txid: "676d8d58420d5c4647d4a872b83966a06a10a4fbb2cfcb2cc8c8b3a8d6940919", prev_vout: 0, script_sig: "", - sequence_no: 4294967295 + sequence_no: 4_294_967_295 } ], outputs: [ %Bitcoinex.Transaction.Out{ - value: 100000000, + value: 100_000_000, script_pub_key: "76a914b6bc2c0ee5655a843d79afedd0ccc3f7dd64340988ac" }, %Bitcoinex.Transaction.Out{ - value: 99900000, + value: 99_900_000, script_pub_key: "00141188ef8e4ce0449eaac8fb141cbf5a1176e6a088" } ], @@ -627,7 +635,7 @@ defmodule Bitcoinex.PSBTTest do xpub: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ - child_nums: [2147483697, 2147483648, 2147483648] + child_nums: [2_147_483_697, 2_147_483_648, 2_147_483_648] }, pfp: <<39, 86, 156, 80>>, xpub: %Bitcoinex.ExtendedKey{ @@ -635,12 +643,12 @@ defmodule Bitcoinex.PSBTTest do 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>>, + 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>> } } @@ -649,19 +657,20 @@ defmodule Bitcoinex.PSBTTest do expected_in: [ %In{ witness_utxo: %Bitcoinex.Transaction.Out{ - value: 100000000, + value: 100_000_000, script_pub_key: "001433b982f91b28f160c920b4ab95e58ce50dda3a4a" }, partial_sig: [ %{ public_key: "03309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c", - signature: "304402202d704ced830c56a909344bd742b6852dccd103e963bae92d38e75254d2bb424502202d86c437195df46c0ceda084f2a291c3da2d64070f76bf9b90b195e7ef28f77201" + signature: + "304402202d704ced830c56a909344bd742b6852dccd103e963bae92d38e75254d2bb424502202d86c437195df46c0ceda084f2a291c3da2d64070f76bf9b90b195e7ef28f77201" } ], bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ - child_nums: [2147483697, 2147483648, 2147483648, 0, 1] + child_nums: [2_147_483_697, 2_147_483_648, 2_147_483_648, 0, 1] }, pfp: <<39, 86, 156, 80>>, public_key: "03309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c" @@ -670,19 +679,20 @@ defmodule Bitcoinex.PSBTTest do }, %In{ witness_utxo: %Bitcoinex.Transaction.Out{ - value: 100000000, + value: 100_000_000, script_pub_key: "0014388fb944307eb77ef45197d0b0b245e079f011de" }, partial_sig: [ %{ public_key: "02c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b110", - signature: "304402204cb1fb5f869c942e0e26100576125439179ae88dca8a9dc3ba08f7953988faa60220521f49ca791c27d70e273c9b14616985909361e25be274ea200d7e08827e514d01" + signature: + "304402204cb1fb5f869c942e0e26100576125439179ae88dca8a9dc3ba08f7953988faa60220521f49ca791c27d70e273c9b14616985909361e25be274ea200d7e08827e514d01" } ], bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ - child_nums: [2147483697, 2147483648, 2147483648, 0, 0] + child_nums: [2_147_483_697, 2_147_483_648, 2_147_483_648, 0, 0] }, pfp: <<39, 86, 156, 80>>, public_key: "02c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b110" @@ -691,23 +701,19 @@ defmodule Bitcoinex.PSBTTest do } ], expected_out: [ - %Out{ - - }, + %Out{}, %Out{ bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ - child_nums: [2147483697, 2147483648, 2147483648, 0, 4] + 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==", @@ -717,30 +723,31 @@ defmodule Bitcoinex.PSBTTest do inputs: [], outputs: [], lock_time: 0 - }, + } }, expected_in: [], expected_out: [] }, %{ - psbt: "cHNidP8BAEwCAAAAAALT3/UFAAAAABl2qRTQxZkDxbrChodg6Q/VIaRmWqdlIIisAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4ezLhMAAAAA", + psbt: + "cHNidP8BAEwCAAAAAALT3/UFAAAAABl2qRTQxZkDxbrChodg6Q/VIaRmWqdlIIisAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4ezLhMAAAAA", expected_global: %Global{ unsigned_tx: %Bitcoinex.Transaction{ version: 2, inputs: [], outputs: [ %Bitcoinex.Transaction.Out{ - value: 99999699, + value: 99_999_699, script_pub_key: "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac" }, %Bitcoinex.Transaction.Out{ - value: 100000000, + value: 100_000_000, script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787" } ], witnesses: nil, - lock_time: 1257139 - }, + lock_time: 1_257_139 + } }, expected_in: [], expected_out: [ From 5eba54e14cc4df3cb71b1170bbe5c10ab5ce5077 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 22:23:38 -0800 Subject: [PATCH 38/63] add BIP370 & 371 test vectors for psbt --- lib/psbt.ex | 114 +-- test/psbt_test.exs | 1820 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 1888 insertions(+), 46 deletions(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index a49defa..beb18dc 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -77,12 +77,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, @@ -174,7 +183,11 @@ defmodule Bitcoinex.PSBT.Utils do def parse_leaf_hashes(value, leaf_hash_ct) do <> = value - leaf_hashes = Enum.chunk_every(leaf_hashes, 32) + leaf_hashes = + leaf_hashes + |> :erlang.binary_to_list() + |> Enum.chunk_every(32) + |> Enum.map(&:erlang.list_to_binary/1) {leaf_hashes, value} end @@ -301,7 +314,7 @@ defmodule Bitcoinex.PSBT.Global do end defp parse(<<@psbt_global_version::big-size(8)>>, psbt, global) do - {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) global = %Global{global | version: value} {global, psbt} end @@ -354,6 +367,13 @@ defmodule Bitcoinex.PSBT.Global 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 @@ -627,7 +647,7 @@ defmodule Bitcoinex.PSBT.In do defp serialize_kv(:tap_script_sig, value) when value != nil do PsbtUtils.serialize_kv( - <<@psbt_in_tap_script_sig::big-size(8), value.pubkey, value.leaf_hash>>, + <<@psbt_in_tap_script_sig::big-size(8), value.pubkey::binary, value.leaf_hash::binary>>, value.signature ) end @@ -637,17 +657,17 @@ defmodule Bitcoinex.PSBT.In do script_bytes = Script.serialize_script(value.script) PsbtUtils.serialize_kv( - <<@psbt_in_tap_leaf_script::big-size(8), value.control_block>>, + <<@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.path) + fingerprint_path = PsbtUtils.serialize_fingerprint_path(value.pfp, value.derivation) PsbtUtils.serialize_kv( - <<@psbt_in_tap_bip32_derivation::big-size(8), value.pubkey>>, + <<@psbt_in_tap_bip32_derivation::big-size(8), value.pubkey::binary>>, leaf_hashes <> fingerprint_path ) end @@ -752,21 +772,21 @@ defmodule Bitcoinex.PSBT.In do end end - defp parse(<<@psbt_in_non_witness_utxo::big-size(8)>>, psbt, input) do + def parse(<<@psbt_in_non_witness_utxo::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) {:ok, txn} = Transaction.decode(value) input = %In{input | non_witness_utxo: txn} {input, psbt} end - defp parse(<<@psbt_in_witness_utxo::big-size(8)>>, psbt, input) do + def parse(<<@psbt_in_witness_utxo::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) out = Out.output(value) input = %In{input | witness_utxo: out} {input, psbt} end - defp parse(<<@psbt_in_partial_sig::big-size(8), public_key::binary-size(33)>>, psbt, input) do + def parse(<<@psbt_in_partial_sig::big-size(8), public_key::binary-size(33)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) partial_sig = %{ @@ -780,25 +800,25 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end - defp parse(<<@psbt_in_sighash_type::big-size(8)>>, psbt, input) do + def parse(<<@psbt_in_sighash_type::big-size(8)>>, psbt, input) do {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) input = %In{input | sighash_type: value} {input, psbt} end - defp parse(<<@psbt_in_redeem_script::big-size(8)>>, psbt, input) do + def parse(<<@psbt_in_redeem_script::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) input = %In{input | redeem_script: Base.encode16(value, case: :lower)} {input, psbt} end - defp parse(<<@psbt_in_witness_script::big-size(8)>>, psbt, input) do + def parse(<<@psbt_in_witness_script::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) input = %In{input | witness_script: Base.encode16(value, case: :lower)} {input, psbt} end - defp parse(<<@psbt_in_bip32_derivation::big-size(8), public_key::binary-size(33)>>, psbt, input) do + def parse(<<@psbt_in_bip32_derivation::big-size(8), public_key::binary-size(33)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) {pfp, path} = PsbtUtils.parse_fingerprint_path(value) @@ -815,19 +835,19 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end - defp parse(<<@psbt_in_final_scriptsig::big-size(8)>>, psbt, input) do + def parse(<<@psbt_in_final_scriptsig::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) input = %In{input | final_scriptsig: Base.encode16(value, case: :lower)} {input, psbt} end - defp parse(<<@psbt_in_por_commitment::big-size(8)>>, psbt, input) do + def parse(<<@psbt_in_por_commitment::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) input = %In{input | por_commitment: value} {input, psbt} end - defp parse(<<@psbt_in_ripemd160::big-size(8), hash::binary-size(20)>>, psbt, input) do + def 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) @@ -842,7 +862,7 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end - defp parse(<<@psbt_in_sha256::big-size(8), hash::binary-size(32)>>, psbt, input) do + def 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) @@ -856,7 +876,7 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end - defp parse(<<@psbt_in_hash160::big-size(8), hash::binary-size(20)>>, psbt, input) do + def 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) @@ -870,7 +890,7 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end - defp parse(<<@psbt_in_hash256::big-size(8), hash::binary-size(32)>>, psbt, input) do + def 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) @@ -884,46 +904,46 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end - defp parse(<<@psbt_in_previous_txid::big-size(8)>>, psbt, input) do + def 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 + def 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 + def 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 + def 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 + def 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 + def 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( + def parse( <<@psbt_in_tap_script_sig::big-size(8), pubkey::binary-size(32), leaf_hash::binary-size(32)>>, psbt, @@ -944,9 +964,15 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end - defp parse(<<@psbt_in_tap_leaf_script::big-size(8), control_block>>, psbt, input) do + def parse(<<@psbt_in_tap_leaf_script::big-size(8), control_block::binary>>, psbt, input) do {tapleaf, psbt} = PsbtUtils.parse_compact_size_value(psbt) - {script_bytes, <>} = PsbtUtils.parse_compact_size_value(tapleaf) + + {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 = %{ @@ -961,7 +987,7 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end - defp parse(<<@psbt_in_tap_bip32_derivation::big-size(8), pubkey::binary-size(32)>>, psbt, input) do + def 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) @@ -981,32 +1007,32 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end - defp parse(<<@psbt_in_tap_internal_key::big-size(8)>>, psbt, input) do + def 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 + def 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 + def parse(<<@psbt_in_proprietary::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) input = %In{input | proprietary: value} {input, psbt} end - defp parse(<<@psbt_in_final_scriptwitness::big-size(8)>>, psbt, input) do + def parse(<<@psbt_in_final_scriptwitness::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) value = Witness.witness(value) input = %In{input | final_scriptwitness: value} {input, psbt} end - defp parse(key, psbt, input) do + def parse(key, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) kv = %{key: key, value: value} @@ -1102,14 +1128,14 @@ defmodule Bitcoinex.PSBT.Out do end defp serialize_kv(:tap_tree, value) when value != nil do - tree = serialize_tap_tree(value) + 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>> + 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.path) + fingerprint_path = PsbtUtils.serialize_fingerprint_path(value.pfp, value.derivation) PsbtUtils.serialize_kv(key, leaf_hashes <> fingerprint_path) end @@ -1229,7 +1255,8 @@ defmodule Bitcoinex.PSBT.Out do 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, []) - output = %Out{output | tap_tree: leaves} + # hack to ensure tap_tree is not treated like a repeatable field + output = %Out{output | tap_tree: %{leaves: leaves}} {output, psbt} end @@ -1241,7 +1268,7 @@ defmodule Bitcoinex.PSBT.Out do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) {leaf_hash_ct, value} = TxUtils.get_counter(value) - leaf_hashes = PsbtUtils.parse_leaf_hashes(value, leaf_hash_ct) + {leaf_hashes, value} = PsbtUtils.parse_leaf_hashes(value, leaf_hash_ct) {pfp, path} = PsbtUtils.parse_fingerprint_path(value) derivation = %{ @@ -1265,7 +1292,7 @@ defmodule Bitcoinex.PSBT.Out do defp parse_tap_tree(<<>>, scripts), do: Enum.reverse(scripts) defp parse_tap_tree(tree, scripts) do - <> = tree + <> = tree {script, tree} = PsbtUtils.parse_compact_size_value(rest) {:ok, script} = Script.parse_script(script) @@ -1276,6 +1303,7 @@ defmodule Bitcoinex.PSBT.Out do script: script } + # TODO: ideally we can build an actual binary tree not just a list. But arbitrary depths make this hard parse_tap_tree(tree, [data | scripts]) end diff --git a/test/psbt_test.exs b/test/psbt_test.exs index 8fe186e..776cf88 100644 --- a/test/psbt_test.exs +++ b/test/psbt_test.exs @@ -117,7 +117,6 @@ defmodule Bitcoinex.PSBTTest do } ], version: 2, - witnesses: nil } }, expected_in: [ @@ -290,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{ @@ -745,7 +743,6 @@ defmodule Bitcoinex.PSBTTest do script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787" } ], - witnesses: nil, lock_time: 1_257_139 } }, @@ -754,6 +751,1823 @@ defmodule Bitcoinex.PSBTTest do %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: 4294967295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 999999000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + } + ], + lock_time: 0 + }, + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 999999000, + 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: [2147483732, 2147483649, 2147483648, 0, 42] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + } + ], + amount: 800000000, + 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: [2147483732, 2147483649, 2147483648, 1, 100] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + } + ], + amount: 199998859, + 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: 4294967295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 999999000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + } + ], + lock_time: 0 + }, + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 999999000, + 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: [2147483732, 2147483649, 2147483648, 0, 42] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + } + ], + amount: 800000000, + 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: [2147483732, 2147483649, 2147483648, 1, 100] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + } + ], + amount: 199998859, + 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: 4294967295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 999999000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + } + ], + lock_time: 0 + }, + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 999999000, + 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: [2147483732, 2147483649, 2147483648, 0, 42] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + } + ], + amount: 800000000, + 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: [2147483732, 2147483649, 2147483648, 1, 100] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + } + ], + amount: 199998859, + 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: 4294967295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 999999000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + } + ], + lock_time: 0 + }, + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 999999000, + 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: [2147483732, 2147483649, 2147483648, 0, 42] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + } + ], + amount: 800000000, + 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: [2147483732, 2147483649, 2147483648, 1, 100] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + } + ], + amount: 199998859, + 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: 4294967295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 999999000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + } + ], + lock_time: 0 + }, + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 999999000, + 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: [2147483732, 2147483649, 2147483648, 0, 42] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + } + ], + amount: 800000000, + 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: [2147483732, 2147483649, 2147483648, 1, 100] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + } + ], + amount: 199998859, + 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: 4294967295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 999999000, + script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" + } + ], + lock_time: 0 + }, + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 999999000, + 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: 4294967294, + required_time_locktime: 1657048460, + required_height_locktime: 10000, + } + ], + expected_out: [ + %Bitcoinex.PSBT.Out{ + bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2147483732, 2147483649, 2147483648, 0, 42] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + } + ], + amount: 800000000, + 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: [2147483732, 2147483649, 2147483648, 1, 100] + }, + pfp: <<246, 157, 135, 62>>, + public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + } + ], + amount: 199998859, + 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: 4294967295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 4999997000, + script_pub_key: "0014768e1eeb4cf420866033f80aceff0f9720744969" + } + ], + lock_time: 0 + } + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 5000000000, + script_pub_key: "51205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757" + }, + tap_bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2147483734, 2147483649, 2147483648, 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: [2147483732, 2147483649, 2147483648, 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: 4294967295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 4999997000, + script_pub_key: "0014768e1eeb4cf420866033f80aceff0f9720744969" + } + ], + lock_time: 0 + }, + }, + + expected_in: [ + %Bitcoinex.PSBT.In{ + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 5000000000, + 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: [2147483734, 2147483649, 2147483648, 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: [2147483732, 2147483649, 2147483648, 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: 4294967295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 4999997000, + script_pub_key: "512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743" + } + ], + lock_time: 0 + }, + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 5000000000, + script_pub_key: "51205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757" + }, + tap_bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2147483734, 2147483649, 2147483648, 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: [2147483734, 2147483649, 2147483648, 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: 4294967295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 4999997000, + script_pub_key: "512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743" + } + ], + lock_time: 0 + }, + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 5000000000, + 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: [2147483734, 2147483649, 2147483650, 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: [2147483734, 2147483649, 2147483649, 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: [2147483734, 2147483649, 2147483651, 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: [2147483734, 2147483649, 2147483648, 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: 4294967295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 4999997000, + script_pub_key: "51200a8cbdc86de1ce1c0f9caeb22d6df7ced3683fe423e05d1e402a879341d6f6f5" + } + ], + lock_time: 0 + }, + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 5000000000, + script_pub_key: "51205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757" + }, + tap_bip32_derivation: [ + %{ + derivation: %Bitcoinex.ExtendedKey.DerivationPath{ + child_nums: [2147483734, 2147483649, 2147483648, 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: [2147483734, 2147483649, 2147483649, 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: [2147483734, 2147483649, 2147483650, 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: [2147483734, 2147483649, 2147483651, 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: 4294967295 + } + ], + outputs: [ + %Bitcoinex.Transaction.Out{ + value: 4999997000, + script_pub_key: "512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743" + } + ], + lock_time: 0 + }, + }, + expected_in: [ + %Bitcoinex.PSBT.In{ + witness_utxo: %Bitcoinex.Transaction.Out{ + value: 5000000000, + 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: [2147483734, 2147483649, 2147483650, 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: [2147483734, 2147483649, 2147483649, 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: [2147483734, 2147483649, 2147483651, 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: [2147483734, 2147483649, 2147483648, 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>> + } + ], + } + ] } ] From d534c2622ec6c4635159f85e7eb56df4d45ce6ad Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 22:31:07 -0800 Subject: [PATCH 39/63] add todo --- lib/psbt.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index beb18dc..c98269e 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -1303,7 +1303,8 @@ defmodule Bitcoinex.PSBT.Out do script: script } - # TODO: ideally we can build an actual binary tree not just a list. But arbitrary depths make this hard + # 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 From 289e49c94cf34d4ad31ac52cb6ff4b7f919307c5 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 23:05:15 -0800 Subject: [PATCH 40/63] lint --- lib/secp256k1/schnorr.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 824d286..b09c4e8 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -161,7 +161,7 @@ defmodule Bitcoinex.Secp256k1.Schnorr do """ @spec encrypted_sign(PrivateKey.t(), non_neg_integer(), non_neg_integer(), Point.t()) :: {:ok, Signature.t(), boolean} - def encrypted_sign(sk = %PrivateKey{}, z, aux,tweak_point = %Point{}) do + def encrypted_sign(sk = %PrivateKey{}, z, aux, tweak_point = %Point{}) do z_bytes = Utils.int_to_big(z, 32) aux_bytes = Utils.int_to_big(aux, 32) d_point = PrivateKey.to_point(sk) From 5cc1ef6c1310b126971ea3a41685a92b3e04ba82 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 23:08:36 -0800 Subject: [PATCH 41/63] lint --- lib/psbt.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index c98269e..141c7cf 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -182,7 +182,8 @@ defmodule Bitcoinex.PSBT.Utils do end def parse_leaf_hashes(value, leaf_hash_ct) do - <> = value + leaf_hashes_byte_size = 32 * leaf_hash_ct + <> = value leaf_hashes = leaf_hashes |> :erlang.binary_to_list() From e659a67fac2af9b10dc666b84b8f669ebb4858b8 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 23:10:47 -0800 Subject: [PATCH 42/63] lint --- lib/secp256k1/schnorr.ex | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 56d1c15..91027a0 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -36,9 +36,11 @@ defmodule Bitcoinex.Secp256k1.Schnorr do def sign_with_nonce(sk, k, z) do d = Secp256k1.force_even_y(sk) k = Secp256k1.force_even_y(k) + case {d, k} do {{:error, _}, _} -> {:error, "failed to force signing key even"} + {_, {:error, _}} -> {:error, "failed to force nonce secret even"} @@ -85,9 +87,9 @@ defmodule Bitcoinex.Secp256k1.Schnorr do k -> {:ok, k, d} - end + end end - end + end end end @@ -296,12 +298,12 @@ defmodule Bitcoinex.Secp256k1.Schnorr do |> PrivateKey.new() case e do - {:error, msg} -> {:error, msg} + {:error, msg} -> + {:error, msg} {:ok, e} -> Math.multiply(pk, e.d) |> Math.add(r_point) end end - end From 581403f2003a811814a920e9f22ecdb9301a5eb7 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 23:12:41 -0800 Subject: [PATCH 43/63] fmt --- lib/psbt.ex | 13 +- test/psbt_test.exs | 841 +++++++++++++++++++++++---------------------- 2 files changed, 434 insertions(+), 420 deletions(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index 141c7cf..49b9bc7 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -184,11 +184,13 @@ defmodule Bitcoinex.PSBT.Utils do 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 @@ -945,11 +947,11 @@ defmodule Bitcoinex.PSBT.In do end def parse( - <<@psbt_in_tap_script_sig::big-size(8), pubkey::binary-size(32), - leaf_hash::binary-size(32)>>, - psbt, - input - ) do + <<@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) @@ -972,6 +974,7 @@ defmodule Bitcoinex.PSBT.In do tapleaf |> :erlang.binary_to_list() |> List.pop_at(-1) + script_bytes = :erlang.list_to_binary(script_bytes) {:ok, script} = Script.parse_script(script_bytes) diff --git a/test/psbt_test.exs b/test/psbt_test.exs index 776cf88..03c312d 100644 --- a/test/psbt_test.exs +++ b/test/psbt_test.exs @@ -116,7 +116,7 @@ defmodule Bitcoinex.PSBTTest do value: 9358 } ], - version: 2, + version: 2 } }, expected_in: [ @@ -1220,7 +1220,7 @@ defmodule Bitcoinex.PSBTTest do input_count: 1, output_count: 2, tx_modifiable: 4, - version: 2, + version: 2 }, expected_in: [ %Bitcoinex.PSBT.In{ @@ -1249,7 +1249,7 @@ defmodule Bitcoinex.PSBTTest do 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, + output_index: 0 } ], expected_out: [ @@ -1379,13 +1379,14 @@ defmodule Bitcoinex.PSBTTest do ] }, %{ - psbt: "cHNidP8BAgQCAAAAAQQBAQEFAQIBBgEDAfsEAgAAAAABAFICAAAAAcGqJW4hS5ahgi+T3kK/87Xz/40FGTBuNRXXUVpegFsSAAAAAAD/////ARjGmjsAAAAAFgAUsKOvFEIIQSaTyn0WaFK1LbCu8G4AAAAAAQEfGMaaOwAAAAAWABSwo68UQghBJpPKfRZoUrUtsK7wbgEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAAiAgLWAfhIRqZ1X3dr4A49nej7EKzJNfuDxF+wFi1MrVq3khj2nYc+VAAAgAEAAIAAAACAAAAAACoAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAIgIC42+/9T3VNAcM+P05ZhRoDzV6m4Xbc0C/HPp0XSrXs0AY9p2HPlQAAIABAACAAAAAgAEAAABkAAAAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==", + 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, + version: 2 }, expected_in: [ %Bitcoinex.PSBT.In{ @@ -1396,24 +1397,25 @@ defmodule Bitcoinex.PSBTTest do prev_txid: "125b805e5a51d715356e3019058dfff3b5f3bf42de932f82a1964b216e25aac1", prev_vout: 0, script_sig: "", - sequence_no: 4294967295 + sequence_no: 4_294_967_295 } ], outputs: [ %Bitcoinex.Transaction.Out{ - value: 999999000, + value: 999_999_000, script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" } ], lock_time: 0 }, witness_utxo: %Bitcoinex.Transaction.Out{ - value: 999999000, + 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, + 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: [ @@ -1421,52 +1423,53 @@ defmodule Bitcoinex.PSBTTest do bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ - child_nums: [2147483732, 2147483649, 2147483648, 0, 42] + 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: 800000000, + 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>> + <<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: [2147483732, 2147483649, 2147483648, 1, 100] + 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: 199998859, + 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>> + <<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==", + 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, + version: 2 }, expected_in: [ %Bitcoinex.PSBT.In{ @@ -1477,24 +1480,25 @@ defmodule Bitcoinex.PSBTTest do prev_txid: "125b805e5a51d715356e3019058dfff3b5f3bf42de932f82a1964b216e25aac1", prev_vout: 0, script_sig: "", - sequence_no: 4294967295 + sequence_no: 4_294_967_295 } ], outputs: [ %Bitcoinex.Transaction.Out{ - value: 999999000, + value: 999_999_000, script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" } ], lock_time: 0 }, witness_utxo: %Bitcoinex.Transaction.Out{ - value: 999999000, + 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, + 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: [ @@ -1502,52 +1506,53 @@ defmodule Bitcoinex.PSBTTest do bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ - child_nums: [2147483732, 2147483649, 2147483648, 0, 42] + 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: 800000000, + 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>> + <<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: [2147483732, 2147483649, 2147483648, 1, 100] + 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: 199998859, + 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>> + <<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==", + 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, + version: 2 }, expected_in: [ %Bitcoinex.PSBT.In{ @@ -1558,24 +1563,25 @@ defmodule Bitcoinex.PSBTTest do prev_txid: "125b805e5a51d715356e3019058dfff3b5f3bf42de932f82a1964b216e25aac1", prev_vout: 0, script_sig: "", - sequence_no: 4294967295 + sequence_no: 4_294_967_295 } ], outputs: [ %Bitcoinex.Transaction.Out{ - value: 999999000, + value: 999_999_000, script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" } ], lock_time: 0 }, witness_utxo: %Bitcoinex.Transaction.Out{ - value: 999999000, + 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, + 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: [ @@ -1583,52 +1589,53 @@ defmodule Bitcoinex.PSBTTest do bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ - child_nums: [2147483732, 2147483649, 2147483648, 0, 42] + 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: 800000000, + 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>> + <<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: [2147483732, 2147483649, 2147483648, 1, 100] + 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: 199998859, + 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>> + <<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==", + 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, + version: 2 }, expected_in: [ %Bitcoinex.PSBT.In{ @@ -1639,78 +1646,79 @@ defmodule Bitcoinex.PSBTTest do prev_txid: "125b805e5a51d715356e3019058dfff3b5f3bf42de932f82a1964b216e25aac1", prev_vout: 0, script_sig: "", - sequence_no: 4294967295 + sequence_no: 4_294_967_295 } ], outputs: [ %Bitcoinex.Transaction.Out{ - value: 999999000, + value: 999_999_000, script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" } ], lock_time: 0 }, witness_utxo: %Bitcoinex.Transaction.Out{ - value: 999999000, + 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, + 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: [2147483732, 2147483649, 2147483648, 0, 42] + 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: 800000000, + 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>> + <<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: [2147483732, 2147483649, 2147483648, 1, 100] + 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: 199998859, + 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>> + <<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==", + 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, + version: 2 }, expected_in: [ %Bitcoinex.PSBT.In{ @@ -1721,24 +1729,25 @@ defmodule Bitcoinex.PSBTTest do prev_txid: "125b805e5a51d715356e3019058dfff3b5f3bf42de932f82a1964b216e25aac1", prev_vout: 0, script_sig: "", - sequence_no: 4294967295 + sequence_no: 4_294_967_295 } ], outputs: [ %Bitcoinex.Transaction.Out{ - value: 999999000, + value: 999_999_000, script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" } ], lock_time: 0 }, witness_utxo: %Bitcoinex.Transaction.Out{ - value: 999999000, + 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, + 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: [ @@ -1746,53 +1755,54 @@ defmodule Bitcoinex.PSBTTest do bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ - child_nums: [2147483732, 2147483649, 2147483648, 0, 42] + 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: 800000000, + 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>> + <<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: [2147483732, 2147483649, 2147483648, 1, 100] + 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: 199998859, + 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>> + <<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=", + 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, + version: 2 }, expected_in: [ %Bitcoinex.PSBT.In{ @@ -1803,27 +1813,28 @@ defmodule Bitcoinex.PSBTTest do prev_txid: "125b805e5a51d715356e3019058dfff3b5f3bf42de932f82a1964b216e25aac1", prev_vout: 0, script_sig: "", - sequence_no: 4294967295 + sequence_no: 4_294_967_295 } ], outputs: [ %Bitcoinex.Transaction.Out{ - value: 999999000, + value: 999_999_000, script_pub_key: "0014b0a3af144208412693ca7d166852b52db0aef06e" } ], lock_time: 0 }, witness_utxo: %Bitcoinex.Transaction.Out{ - value: 999999000, + 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>>, + 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: 4294967294, - required_time_locktime: 1657048460, - required_height_locktime: 10000, + sequence: 4_294_967_294, + required_time_locktime: 1_657_048_460, + required_height_locktime: 10000 } ], expected_out: [ @@ -1831,47 +1842,48 @@ defmodule Bitcoinex.PSBTTest do bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ - child_nums: [2147483732, 2147483649, 2147483648, 0, 42] + 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: 800000000, + 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>> + <<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: [2147483732, 2147483649, 2147483648, 1, 100] + 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: 199998859, + 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>> + <<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", + psbt: + "cHNidP8BAFICAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAFgAUdo4e60z0IIZgM/gKzv8PlyB0SWkAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1chFv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyGQB3Ky2nVgAAgAEAAIAAAACAAQAAAAAAAAABFyD+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMgAiAgNrdyptt02HU8mKgnlY3mx4qzMSEJ830+AwRIQkLs5z2Bh3Ky2nVAAAgAEAAIAAAACAAAAAAAAAAAAA", expected_global: %Global{ unsigned_tx: %Bitcoinex.Transaction{ version: 2, @@ -1880,12 +1892,12 @@ defmodule Bitcoinex.PSBTTest do prev_txid: "42b224669be683585854193052ef88b1efe2ee963af26c0dfe2730bfba4a7427", prev_vout: 0, script_sig: "", - sequence_no: 4294967295 + sequence_no: 4_294_967_295 } ], outputs: [ %Bitcoinex.Transaction.Out{ - value: 4999997000, + value: 4_999_997_000, script_pub_key: "0014768e1eeb4cf420866033f80aceff0f9720744969" } ], @@ -1895,23 +1907,24 @@ defmodule Bitcoinex.PSBTTest do expected_in: [ %Bitcoinex.PSBT.In{ witness_utxo: %Bitcoinex.Transaction.Out{ - value: 5000000000, + value: 5_000_000_000, script_pub_key: "51205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757" }, tap_bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ - child_nums: [2147483734, 2147483649, 2147483648, 1, 0] + 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>> + 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>> + 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: [ @@ -1919,7 +1932,7 @@ defmodule Bitcoinex.PSBTTest do bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ - child_nums: [2147483732, 2147483649, 2147483648, 0, 0] + child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 0] }, pfp: <<119, 43, 45, 167>>, public_key: "036b772a6db74d8753c98a827958de6c78ab3312109f37d3e0304484242ece73d8" @@ -1929,7 +1942,8 @@ defmodule Bitcoinex.PSBTTest do ] }, %{ - psbt: "cHNidP8BAFICAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAFgAUdo4e60z0IIZgM/gKzv8PlyB0SWkAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1cBE0C7U+yRe62dkGrxuocYHEi4as5aritTYFpyXKdGJWMUdvxvW67a9PLuD0d/NvWPOXDVuCc7fkl7l68uPxJcl680IRb+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMhkAdystp1YAAIABAACAAAAAgAEAAAAAAAAAARcg/jSQZMmNbiqFP6PJsSvYswShnBlcYO+n7iOTBG0/ojIAIgIDa3cqbbdNh1PJioJ5WN5seKszEhCfN9PgMESEJC7Oc9gYdystp1QAAIABAACAAAAAgAAAAAAAAAAAAA==", + 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, @@ -1938,39 +1952,44 @@ defmodule Bitcoinex.PSBTTest do prev_txid: "42b224669be683585854193052ef88b1efe2ee963af26c0dfe2730bfba4a7427", prev_vout: 0, script_sig: "", - sequence_no: 4294967295 + sequence_no: 4_294_967_295 } ], outputs: [ %Bitcoinex.Transaction.Out{ - value: 4999997000, + value: 4_999_997_000, script_pub_key: "0014768e1eeb4cf420866033f80aceff0f9720744969" } ], lock_time: 0 - }, + } }, - expected_in: [ %Bitcoinex.PSBT.In{ witness_utxo: %Bitcoinex.Transaction.Out{ - value: 5000000000, + 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_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: [2147483734, 2147483649, 2147483648, 1, 0] + 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>> + 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>> , + 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: [ @@ -1978,17 +1997,18 @@ defmodule Bitcoinex.PSBTTest do bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ - child_nums: [2147483732, 2147483649, 2147483648, 0, 0] + 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=", + psbt: + "cHNidP8BAF4CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAIlEgg2mORYxmZOFZXXXaJZfeHiLul9eY5wbEwKS1qYI810MAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1chFv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyGQB3Ky2nVgAAgAEAAIAAAACAAQAAAAAAAAABFyD+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMgABBSARJNp67JLM0GyVRWJkf0N7E4uVchqEvivyJ2u92rPmcSEHESTaeuySzNBslUViZH9DexOLlXIahL4r8idrvdqz5nEZAHcrLadWAACAAQAAgAAAAIAAAAAABQAAAAA=", expected_global: %Global{ unsigned_tx: %Bitcoinex.Transaction{ version: 2, @@ -1997,61 +2017,65 @@ defmodule Bitcoinex.PSBTTest do prev_txid: "42b224669be683585854193052ef88b1efe2ee963af26c0dfe2730bfba4a7427", prev_vout: 0, script_sig: "", - sequence_no: 4294967295 + sequence_no: 4_294_967_295 } ], outputs: [ %Bitcoinex.Transaction.Out{ - value: 4999997000, - script_pub_key: "512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743" + value: 4_999_997_000, + script_pub_key: + "512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743" } ], lock_time: 0 - }, + } }, expected_in: [ %Bitcoinex.PSBT.In{ witness_utxo: %Bitcoinex.Transaction.Out{ - value: 5000000000, + value: 5_000_000_000, script_pub_key: "51205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757" }, tap_bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ - child_nums: [2147483734, 2147483649, 2147483648, 1, 0] + 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>> + 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>>, + 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_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: [2147483734, 2147483649, 2147483648, 0, 5] + 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>> + 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", + 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, @@ -2060,76 +2084,74 @@ defmodule Bitcoinex.PSBTTest do prev_txid: "2695697f810d60b5bc56eb7ff9c6f0546e5572f90120662ea7f90b236587d49b", prev_vout: 1, script_sig: "", - sequence_no: 4294967295 + sequence_no: 4_294_967_295 } ], outputs: [ %Bitcoinex.Transaction.Out{ - value: 4999997000, - script_pub_key: "512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743" + value: 4_999_997_000, + script_pub_key: + "512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743" } ], lock_time: 0 - }, + } }, expected_in: [ %Bitcoinex.PSBT.In{ witness_utxo: %Bitcoinex.Transaction.Out{ - value: 5000000000, + 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>>, + 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>>, + <<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>>, + 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>>, + <<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>>, + 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>>, + <<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 ] } @@ -2138,85 +2160,83 @@ defmodule Bitcoinex.PSBTTest do tap_bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ - child_nums: [2147483734, 2147483649, 2147483650, 0, 0] + 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>> + <<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>> + 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: [2147483734, 2147483649, 2147483649, 0, 0] + 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>> + <<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>> + 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>> + 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: [2147483734, 2147483649, 2147483651, 0, 0] + 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>> + <<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>> + 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>>, + 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_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: [2147483734, 2147483649, 2147483648, 0, 5] + 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>> + 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", + 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, @@ -2225,146 +2245,142 @@ defmodule Bitcoinex.PSBTTest do prev_txid: "42b224669be683585854193052ef88b1efe2ee963af26c0dfe2730bfba4a7427", prev_vout: 0, script_sig: "", - sequence_no: 4294967295 + sequence_no: 4_294_967_295 } ], outputs: [ %Bitcoinex.Transaction.Out{ - value: 4999997000, - script_pub_key: "51200a8cbdc86de1ce1c0f9caeb22d6df7ced3683fe423e05d1e402a879341d6f6f5" + value: 4_999_997_000, + script_pub_key: + "51200a8cbdc86de1ce1c0f9caeb22d6df7ced3683fe423e05d1e402a879341d6f6f5" } ], lock_time: 0 - }, + } }, expected_in: [ %Bitcoinex.PSBT.In{ witness_utxo: %Bitcoinex.Transaction.Out{ - value: 5000000000, + value: 5_000_000_000, script_pub_key: "51205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757" }, tap_bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ - child_nums: [2147483734, 2147483649, 2147483648, 1, 0] + 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>> + 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>>, + 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_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, + <<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 + ] + } } - }, - %{ - 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: [2147483734, 2147483649, 2147483649, 0, 3] + 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>> + <<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>> + 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>> + 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: [2147483734, 2147483649, 2147483650, 0, 3] + 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>> + <<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>> + 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: [2147483734, 2147483649, 2147483651, 0, 3] + 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>> + <<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>> + 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", + 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, @@ -2373,117 +2389,115 @@ defmodule Bitcoinex.PSBTTest do prev_txid: "2695697f810d60b5bc56eb7ff9c6f0546e5572f90120662ea7f90b236587d49b", prev_vout: 1, script_sig: "", - sequence_no: 4294967295 + sequence_no: 4_294_967_295 } ], outputs: [ %Bitcoinex.Transaction.Out{ - value: 4999997000, - script_pub_key: "512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743" + value: 4_999_997_000, + script_pub_key: + "512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743" } ], lock_time: 0 - }, + } }, expected_in: [ %Bitcoinex.PSBT.In{ witness_utxo: %Bitcoinex.Transaction.Out{ - value: 5000000000, + 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: + <<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: + <<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>> + 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>>, + 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>>, + <<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>>, + 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>>, + <<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>>, + 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>>, + <<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 ] } @@ -2492,80 +2506,77 @@ defmodule Bitcoinex.PSBTTest do tap_bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ - child_nums: [2147483734, 2147483649, 2147483650, 0, 0] + 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>> + <<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>> + 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: [2147483734, 2147483649, 2147483649, 0, 0] + 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>> + <<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>> + 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>> + 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: [2147483734, 2147483649, 2147483651, 0, 0] + 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>> + <<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>> + 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>>, + 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_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: [2147483734, 2147483649, 2147483648, 0, 5] + 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>> + 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>> } - ], + ] } ] } From 02ab1cffa70e4b9cd2c963c59a1f5fa653920c4f Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 23:15:57 -0800 Subject: [PATCH 44/63] fmt --- lib/psbt.ex | 56 +++++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index 49b9bc7..b5cdb43 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -775,21 +775,23 @@ defmodule Bitcoinex.PSBT.In do end end - def parse(<<@psbt_in_non_witness_utxo::big-size(8)>>, psbt, input) do + @spec parse(any, nonempty_binary, %In{}) :: + {%In{}, binary} + 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(value) input = %In{input | non_witness_utxo: txn} {input, psbt} end - def parse(<<@psbt_in_witness_utxo::big-size(8)>>, psbt, input) do + defp parse(<<@psbt_in_witness_utxo::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) out = Out.output(value) input = %In{input | witness_utxo: out} {input, psbt} end - def parse(<<@psbt_in_partial_sig::big-size(8), public_key::binary-size(33)>>, psbt, input) 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) partial_sig = %{ @@ -803,25 +805,25 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end - def parse(<<@psbt_in_sighash_type::big-size(8)>>, psbt, input) do + defp parse(<<@psbt_in_sighash_type::big-size(8)>>, psbt, input) do {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) input = %In{input | sighash_type: value} {input, psbt} end - def parse(<<@psbt_in_redeem_script::big-size(8)>>, psbt, input) do + defp parse(<<@psbt_in_redeem_script::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) input = %In{input | redeem_script: Base.encode16(value, case: :lower)} {input, psbt} end - def parse(<<@psbt_in_witness_script::big-size(8)>>, psbt, input) do + defp parse(<<@psbt_in_witness_script::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) input = %In{input | witness_script: Base.encode16(value, case: :lower)} {input, psbt} end - def parse(<<@psbt_in_bip32_derivation::big-size(8), public_key::binary-size(33)>>, psbt, input) 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) {pfp, path} = PsbtUtils.parse_fingerprint_path(value) @@ -838,19 +840,19 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end - def parse(<<@psbt_in_final_scriptsig::big-size(8)>>, psbt, input) do + defp parse(<<@psbt_in_final_scriptsig::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) input = %In{input | final_scriptsig: Base.encode16(value, case: :lower)} {input, psbt} end - def parse(<<@psbt_in_por_commitment::big-size(8)>>, psbt, input) do + defp parse(<<@psbt_in_por_commitment::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) input = %In{input | por_commitment: value} {input, psbt} end - def parse(<<@psbt_in_ripemd160::big-size(8), hash::binary-size(20)>>, psbt, input) do + 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) @@ -865,7 +867,7 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end - def parse(<<@psbt_in_sha256::big-size(8), hash::binary-size(32)>>, psbt, input) do + 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) @@ -879,7 +881,7 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end - def parse(<<@psbt_in_hash160::big-size(8), hash::binary-size(20)>>, psbt, input) do + 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) @@ -893,7 +895,7 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end - def parse(<<@psbt_in_hash256::big-size(8), hash::binary-size(32)>>, psbt, input) do + 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) @@ -907,46 +909,46 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end - def parse(<<@psbt_in_previous_txid::big-size(8)>>, psbt, input) do + 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 - def parse(<<@psbt_in_output_index::big-size(8)>>, psbt, input) do + 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 - def parse(<<@psbt_in_sequence::big-size(8)>>, psbt, input) do + 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 - def parse(<<@psbt_in_required_time_locktime::big-size(8)>>, psbt, input) do + 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 - def parse(<<@psbt_in_required_height_locktime::big-size(8)>>, psbt, input) do + 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 - def parse(<<@psbt_in_tap_key_sig::big-size(8)>>, psbt, input) do + 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 - def parse( + defp parse( <<@psbt_in_tap_script_sig::big-size(8), pubkey::binary-size(32), leaf_hash::binary-size(32)>>, psbt, @@ -967,7 +969,7 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end - def parse(<<@psbt_in_tap_leaf_script::big-size(8), control_block::binary>>, psbt, input) do + 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} = @@ -991,7 +993,7 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end - def parse(<<@psbt_in_tap_bip32_derivation::big-size(8), pubkey::binary-size(32)>>, psbt, input) do + 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) @@ -1011,32 +1013,32 @@ defmodule Bitcoinex.PSBT.In do {input, psbt} end - def parse(<<@psbt_in_tap_internal_key::big-size(8)>>, psbt, input) do + 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 - def parse(<<@psbt_in_tap_merkle_root::big-size(8)>>, psbt, input) do + 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 - def parse(<<@psbt_in_proprietary::big-size(8)>>, psbt, input) do + defp parse(<<@psbt_in_proprietary::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) input = %In{input | proprietary: value} {input, psbt} end - def parse(<<@psbt_in_final_scriptwitness::big-size(8)>>, psbt, input) do + defp parse(<<@psbt_in_final_scriptwitness::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) value = Witness.witness(value) input = %In{input | final_scriptwitness: value} {input, psbt} end - def parse(key, psbt, input) do + defp parse(key, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) kv = %{key: key, value: value} From 9bd20fa800437d221dccf4d09699bc3c2347151c Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 23:20:21 -0800 Subject: [PATCH 45/63] lint --- lib/secp256k1/schnorr.ex | 52 +++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index b09c4e8..f6f49fa 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -35,23 +35,20 @@ defmodule Bitcoinex.Secp256k1.Schnorr do tagged_aux_hash = tagged_hash_aux(aux_bytes) t = Utils.xor_bytes(d_bytes, tagged_aux_hash) - {:ok, k0} = calculate_k(t, d_point, z_bytes) + case calculate_k(t, d_point, z_bytes) do + {:ok, k0} -> + r_point = PrivateKey.to_point(k0) - if k0.d == 0 do - {:error, "invalid aux randomness"} - else - r_point = PrivateKey.to_point(k0) + case Secp256k1.force_even_y(k0) do + {:error, msg} -> + {:error, msg} - case Secp256k1.force_even_y(k0) do - {:error, msg} -> - {:error, msg} + k -> + e = calculate_e(Point.x_bytes(r_point), Point.x_bytes(d_point), z_bytes) + sig_s = calculate_s(k, d, e) - k -> - e = calculate_e(Point.x_bytes(r_point), Point.x_bytes(d_point), z_bytes) - sig_s = calculate_s(k, d, e) - - {:ok, %Signature{r: r_point.x, s: sig_s}} - end + {:ok, %Signature{r: r_point.x, s: sig_s}} + end end end end @@ -171,19 +168,20 @@ defmodule Bitcoinex.Secp256k1.Schnorr do tagged_aux_hash = tagged_hash_aux(aux_bytes) t = Utils.xor_bytes(d_bytes, tagged_aux_hash) # TODO always add tweak_point to the nonce to commit to it as well - {:ok, k0} = calculate_k(t, d_point, z_bytes) - - r_point = PrivateKey.to_point(k0) - # ensure that tweak_point has even Y - tweaked_r_point = Math.add(r_point, tweak_point) - # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true - {tweaked_r_point, was_negated} = make_point_even(tweaked_r_point) - k = conditional_negate(k0.d, was_negated) - - e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) - s = calculate_s(k, d, e) - # we return Signature{R+T,s}, not a valid signature since s is untweaked. - {:ok, %Signature{r: tweaked_r_point.x, s: s}, was_negated} + case calculate_k(t, d_point, z_bytes) do + {:ok, k0} -> + r_point = PrivateKey.to_point(k0) + # ensure that tweak_point has even Y + tweaked_r_point = Math.add(r_point, tweak_point) + # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true + {tweaked_r_point, was_negated} = make_point_even(tweaked_r_point) + k = conditional_negate(k0.d, was_negated) + + e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) + s = calculate_s(k, d, e) + # we return Signature{R+T,s}, not a valid signature since s is untweaked. + {:ok, %Signature{r: tweaked_r_point.x, s: s}, was_negated} + end end @doc """ From afadef269dab8a16f07d1f71d895e30cca9ae945 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 23:21:09 -0800 Subject: [PATCH 46/63] fmt --- lib/psbt.ex | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index b5cdb43..dab87fd 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -949,11 +949,11 @@ defmodule Bitcoinex.PSBT.In do end defp parse( - <<@psbt_in_tap_script_sig::big-size(8), pubkey::binary-size(32), - leaf_hash::binary-size(32)>>, - psbt, - input - ) do + <<@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) From e133c5c2145d6a27d0b77022c311921a1ca4ac1e Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 23:24:10 -0800 Subject: [PATCH 47/63] fix aliases --- lib/taproot.ex | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/taproot.ex b/lib/taproot.ex index 3954ff3..5cb6f8e 100644 --- a/lib/taproot.ex +++ b/lib/taproot.ex @@ -1,6 +1,7 @@ defmodule Bitcoinex.Taproot do alias Bitcoinex.Utils + alias Bitcoinex.Taproot alias Bitcoinex.{Secp256k1, Script} alias Bitcoinex.Secp256k1.{Math, Params, Point, PrivateKey} @@ -11,8 +12,6 @@ defmodule Bitcoinex.Taproot do @spec bip342_leaf_version :: 192 def bip342_leaf_version(), do: @bip342_leaf_version - @type tapnode :: {tapnode, tapnode} | TapLeaf.t() | nil - @spec tweak_privkey(PrivateKey.t(), binary) :: PrivateKey.t() | {:error, String.t()} def tweak_privkey(sk0 = %PrivateKey{}, h) do sk = Secp256k1.force_even_y(sk0) @@ -121,7 +120,7 @@ defmodule Bitcoinex.Taproot do while branches are {script_tree, script_tree}. Since we sort based on hash at each level, left vs right branches are irrelevant. An empty tree is represented by nil. """ - @type script_tree :: TapLeaf.t() | {script_tree(), script_tree()} | nil + @type script_tree :: Taproot.TapLeaf.t() | {script_tree(), script_tree()} | nil @doc """ merkelize_script_tree takes a script_tree (either nil, a TapLeaf, or a tuple of two script_trees) From 72a89d0869c99bd1c564e7c4645c0c67b8cfa313 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 23:27:23 -0800 Subject: [PATCH 48/63] lint --- lib/psbt.ex | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index dab87fd..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__{} @@ -492,7 +493,7 @@ defmodule Bitcoinex.PSBT.In do |> parse_input([], num_inputs) end - @spec from_tx_inputs(list(Transaction.In.t()), list(Transaction.Witness.t())) :: list(%In{}) + @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) @@ -775,8 +776,6 @@ defmodule Bitcoinex.PSBT.In do end end - @spec parse(any, nonempty_binary, %In{}) :: - {%In{}, binary} 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(value) From 3a48dbdb653b03e57fad5120ff12e4eed94591e9 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 23:32:35 -0800 Subject: [PATCH 49/63] lint --- lib/secp256k1/schnorr.ex | 41 ++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index f6f49fa..01391c9 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -163,24 +163,29 @@ defmodule Bitcoinex.Secp256k1.Schnorr do aux_bytes = Utils.int_to_big(aux, 32) d_point = PrivateKey.to_point(sk) - d = Secp256k1.force_even_y(sk) - d_bytes = Utils.int_to_big(d.d, 32) - tagged_aux_hash = tagged_hash_aux(aux_bytes) - t = Utils.xor_bytes(d_bytes, tagged_aux_hash) - # TODO always add tweak_point to the nonce to commit to it as well - case calculate_k(t, d_point, z_bytes) do - {:ok, k0} -> - r_point = PrivateKey.to_point(k0) - # ensure that tweak_point has even Y - tweaked_r_point = Math.add(r_point, tweak_point) - # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true - {tweaked_r_point, was_negated} = make_point_even(tweaked_r_point) - k = conditional_negate(k0.d, was_negated) - - e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) - s = calculate_s(k, d, e) - # we return Signature{R+T,s}, not a valid signature since s is untweaked. - {:ok, %Signature{r: tweaked_r_point.x, s: s}, was_negated} + case Secp256k1.force_even_y(sk) do + {:error, msg} -> + {:error, msg} + + d -> + d_bytes = Utils.int_to_big(d.d, 32) + tagged_aux_hash = tagged_hash_aux(aux_bytes) + t = Utils.xor_bytes(d_bytes, tagged_aux_hash) + # TODO always add tweak_point to the nonce to commit to it as well + case calculate_k(t, d_point, z_bytes) do + {:ok, k0} -> + r_point = PrivateKey.to_point(k0) + # ensure that tweak_point has even Y + tweaked_r_point = Math.add(r_point, tweak_point) + # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true + {tweaked_r_point, was_negated} = make_point_even(tweaked_r_point) + k = conditional_negate(k0.d, was_negated) + + e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) + s = calculate_s(k, d, e) + # we return Signature{R+T,s}, not a valid signature since s is untweaked. + {:ok, %Signature{r: tweaked_r_point.x, s: s}, was_negated} + end end end From 73b7500e0ea71d31a8bd2eaab023af5468c4eeb4 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Thu, 23 Feb 2023 23:35:19 -0800 Subject: [PATCH 50/63] lint --- lib/secp256k1/schnorr.ex | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index 91027a0..b0a1c13 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -76,18 +76,15 @@ defmodule Bitcoinex.Secp256k1.Schnorr do tagged_aux_hash = tagged_hash_aux(aux_bytes) t = Utils.xor_bytes(d_bytes, tagged_aux_hash) - {:ok, k0} = calculate_k(t, d_point, z_bytes) - - if k0.d == 0 do - {:error, "invalid aux randomness"} - else - case Secp256k1.force_even_y(k0) do - {:error, msg} -> - {:error, msg} - - k -> - {:ok, k, d} - end + case calculate_k(t, d_point, z_bytes) do + {:ok, k0} -> + case Secp256k1.force_even_y(k0) do + {:error, msg} -> + {:error, msg} + + k -> + {:ok, k, d} + end end end end From fa7df7f301827adaf0fb2546167c552e82128063 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Fri, 24 Feb 2023 00:01:11 -0800 Subject: [PATCH 51/63] lint --- lib/secp256k1/schnorr.ex | 42 +++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/lib/secp256k1/schnorr.ex b/lib/secp256k1/schnorr.ex index b0a1c13..d53b43a 100644 --- a/lib/secp256k1/schnorr.ex +++ b/lib/secp256k1/schnorr.ex @@ -199,24 +199,30 @@ defmodule Bitcoinex.Secp256k1.Schnorr do aux_bytes = Utils.int_to_big(aux, 32) d_point = PrivateKey.to_point(sk) - d = Secp256k1.force_even_y(sk) - d_bytes = Utils.int_to_big(d.d, 32) - tagged_aux_hash = tagged_hash_aux(aux_bytes) - t = Utils.xor_bytes(d_bytes, tagged_aux_hash) - # TODO always add tweak_point to the nonce to commit to it as well - {:ok, k0} = calculate_k(t, d_point, z_bytes) - - r_point = PrivateKey.to_point(k0) - # ensure that tweak_point has even Y - tweaked_r_point = Math.add(r_point, tweak_point) - # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true - {tweaked_r_point, was_negated} = make_point_even(tweaked_r_point) - k = conditional_negate(k0.d, was_negated) - - e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) - s = calculate_s(k, d, e) - # we return Signature{R+T,s}, not a valid signature since s is untweaked. - {:ok, %Signature{r: tweaked_r_point.x, s: s}, was_negated} + case Secp256k1.force_even_y(sk) do + {:error, msg} -> + {:error, msg} + + d -> + d_bytes = Utils.int_to_big(d.d, 32) + tagged_aux_hash = tagged_hash_aux(aux_bytes) + t = Utils.xor_bytes(d_bytes, tagged_aux_hash) + # TODO always add tweak_point to the nonce to commit to it as well + case calculate_k(t, d_point, z_bytes) do + {:ok, k0} -> + r_point = PrivateKey.to_point(k0) + # ensure that tweak_point has even Y + tweaked_r_point = Math.add(r_point, tweak_point) + # ensure (R+T).y is even, if not, negate it, negate k, and set was_negated = true + {tweaked_r_point, was_negated} = make_point_even(tweaked_r_point) + k = conditional_negate(k0.d, was_negated) + + e = calculate_e(Point.x_bytes(tweaked_r_point), Point.x_bytes(d_point), z_bytes) + s = calculate_s(k, d, e) + # we return Signature{R+T,s}, not a valid signature since s is untweaked. + {:ok, %Signature{r: tweaked_r_point.x, s: s}, was_negated} + end + end end @doc """ From eb0b3eb9435b69d71d94350609b7c02275bcb743 Mon Sep 17 00:00:00 2001 From: Sachin Meier <46731461+SachinMeier@users.noreply.github.com> Date: Fri, 3 Mar 2023 19:59:16 -0500 Subject: [PATCH 52/63] Add PSBT fields functionality (#14) * add add-field funcs * add specs and global add-field funcs * refactor for code efficiency * finalize add-fields funcs for psbt * temp: fix script vs binary * all PSBT pubkeys -> Point structs * fix credo lint * lint * lint --- lib/psbt.ex | 558 ++++++++++++++++++++++++++++++--------- lib/transaction.ex | 6 + test/psbt_test.exs | 644 +++++++++++++++++++++++++++++++++++---------- 3 files changed, 943 insertions(+), 265 deletions(-) diff --git a/lib/psbt.ex b/lib/psbt.ex index ada776a..61af54e 100644 --- a/lib/psbt.ex +++ b/lib/psbt.ex @@ -11,9 +11,9 @@ defmodule Bitcoinex.PSBT do alias Bitcoinex.PSBT.Global alias Bitcoinex.PSBT.In alias Bitcoinex.PSBT.Out + alias Bitcoinex.PSBT.Utils alias Bitcoinex.Transaction alias Bitcoinex.Transaction.Utils, as: TxUtils - alias Bitcoinex.Utils @type t() :: %__MODULE__{} @@ -26,6 +26,7 @@ defmodule Bitcoinex.PSBT do @magic 0x70736274 @separator 0xFF + @spec separator :: 255 def separator, do: @separator @doc """ @@ -104,7 +105,7 @@ defmodule Bitcoinex.PSBT do }} end - @spec from_tx(Transaction.t()) :: {:ok, Bitcoinex.PSBT.t()} + @spec from_tx(Transaction.t()) :: {:ok, PSBT.t()} def from_tx(tx) do inputs = In.from_tx_inputs(tx.inputs, tx.witnesses) outputs = Out.from_tx_outputs(tx.outputs) @@ -126,6 +127,24 @@ defmodule Bitcoinex.PSBT do %Bitcoinex.Transaction{tx | witnesses: witnesses, inputs: inputs} end + + @spec add_global_field(PSBT.t(), atom, any) :: PSBT.t() + def add_global_field(psbt, field, value) do + global = Global.add_field(psbt.global, field, value) + %PSBT{psbt | global: global} + end + + @spec add_input_field(PSBT.t(), integer, atom, any) :: PSBT.t() + def add_input_field(psbt, input_idx, field, value) do + inputs = Utils.set_item_field(psbt.inputs, input_idx, &In.add_field/3, field, value) + %PSBT{psbt | inputs: inputs} + end + + @spec set_output_field(PSBT.t(), non_neg_integer, atom, any) :: PSBT.t() + def set_output_field(psbt, output_idx, field, value) do + outputs = Utils.set_item_field(psbt.outputs, output_idx, &Out.add_field/3, field, value) + %PSBT{psbt | outputs: outputs} + end end defmodule Bitcoinex.PSBT.Utils do @@ -164,6 +183,7 @@ defmodule Bitcoinex.PSBT.Utils do key_len <> key <> val_len <> val end + @spec serialize_repeatable_fields(atom, list(any), any) :: binary def serialize_repeatable_fields(_, nil, _), do: <<>> def serialize_repeatable_fields(field, values, serialize_func) do @@ -171,6 +191,7 @@ defmodule Bitcoinex.PSBT.Utils do |> :erlang.list_to_binary() end + @spec parse_fingerprint_path(<<_::32, _::_*8>>) :: {<<_::32>>, DerivationPath.t()} def parse_fingerprint_path(data) do <> = data {:ok, path} = DerivationPath.parse(path_bin) @@ -203,8 +224,18 @@ defmodule Bitcoinex.PSBT.Utils do Utils.serialize_compact_size_unsigned_int(length(leaf_hashes)) <> leaf_hashes_bin end + @spec append(nil | list, any) :: [any] def append(nil, item), do: [item] def append(items, item), do: items ++ [item] + + def set_item_field(items, idx, add_field_func, field, value) do + item = + items + |> Enum.at(idx) + |> add_field_func.(field, value) + + List.replace_at(items, idx, item) + end end defmodule Bitcoinex.PSBT.Global do @@ -242,11 +273,74 @@ defmodule Bitcoinex.PSBT.Global do @psbt_global_version 0xFB @psbt_global_proprietary 0xFC + def add_field(global, :unsigned_tx, unsigned_tx = %Transaction{}) + when global.unsigned_tx == nil do + %Global{global | unsigned_tx: unsigned_tx} + end + + def add_field( + global, + :xpub, + global_xpub = %{ + xpub: %ExtendedKey{}, + pfp: <<_::binary-size(4)>>, + derivation: %DerivationPath{} + } + ) do + global_xpubs = PsbtUtils.append(global.xpub, global_xpub) + %Global{global | xpub: global_xpubs} + end + + def add_field(global, :tx_version, value) when global.tx_version == nil and value > 0 do + %Global{global | tx_version: value} + end + + def add_field(global, :fallback_locktime, value) when value >= 0 do + %Global{global | fallback_locktime: value} + end + + def add_field(global, :input_count, input_count) when input_count > 0 do + %Global{global | input_count: input_count} + end + + def add_field(global, :output_count, output_count) when output_count > 0 do + %Global{global | output_count: output_count} + end + + def add_field(global, :tx_modifiable, value) do + %Global{global | tx_modifiable: value} + end + + def add_field(global, :version, value) do + %Global{global | version: value} + end + + # TODO: fix + def add_field(global, :proprietary, value = %{key: k, value: v}) + when is_binary(k) and is_binary(v) do + proprietaries = PsbtUtils.append(global.proprietary, value) + %Global{global | proprietary: proprietaries} + end + + def add_field(global, :unknown, value = %{key: k, value: v}) + when is_binary(k) and is_binary(v) do + unknown = PsbtUtils.append(global.unknown, value) + %Global{global | unknown: unknown} + end + + @spec parse_global(nonempty_binary) :: {Global, binary} def parse_global(psbt) do PsbtUtils.parse_key_value(psbt, %Global{}, &parse/3) end - def from_tx(tx), do: %Global{unsigned_tx: tx} + def from_tx(tx) do + %Global{ + unsigned_tx: tx, + tx_version: tx.version, + input_count: length(tx.inputs), + output_count: length(tx.outputs) + } + end # unsigned transaction defp parse(<<@psbt_global_unsigned_tx::big-size(8)>>, psbt, global) do @@ -256,7 +350,8 @@ defmodule Bitcoinex.PSBT.Global do case Transaction.decode(txn_bytes) do {:ok, txn} -> - {%Global{global | unsigned_tx: txn}, psbt} + global = add_field(global, :unsigned_tx, txn) + {global, psbt} {:error, error_msg} -> {:error, error_msg} @@ -281,42 +376,39 @@ defmodule Bitcoinex.PSBT.Global do derivation: path } - global_xpubs = PsbtUtils.append(global.xpub, global_xpub) - - global = %Global{global | xpub: global_xpubs} - + global = add_field(global, :xpub, global_xpub) {global, psbt} end 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 = add_field(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 = add_field(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 = add_field(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 = add_field(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 = add_field(global, :tx_modifiable, value) {global, psbt} end @@ -326,9 +418,15 @@ defmodule Bitcoinex.PSBT.Global do {global, psbt} end - defp parse(<<@psbt_global_proprietary::big-size(8)>>, psbt, global) do + defp parse(<<@psbt_global_proprietary::big-size(8), key::binary>>, psbt, global) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - global = %Global{global | proprietary: value} + global = add_field(global, :proprietary, %{key: key, value: value}) + {global, psbt} + end + + defp parse(key, psbt, global) do + {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + global = add_field(global, :unknown, %{key: key, value: value}) {global, psbt} end @@ -381,10 +479,15 @@ defmodule Bitcoinex.PSBT.Global do ) end - defp serialize_kv(:proprietary, value) when value != nil do - PsbtUtils.serialize_kv(<<@psbt_global_proprietary::big-size(8)>>, value) + defp serialize_kv(:proprietary, %{key: k, value: v}) when is_binary(k) and is_binary(v) do + PsbtUtils.serialize_kv(<<@psbt_global_proprietary::big-size(8), k::binary>>, v) + end + + defp serialize_kv(:unknown, %{key: k, value: v}) do + PsbtUtils.serialize_kv(k, v) end + @spec serialize_global(Global) :: nonempty_binary def serialize_global(global) do serialized_global = Enum.reduce( @@ -434,6 +537,7 @@ defmodule Bitcoinex.PSBT.In do alias Bitcoinex.Transaction.Utils, as: TxUtils alias Bitcoinex.Utils alias Bitcoinex.Script + alias Bitcoinex.Secp256k1.Point defstruct [ :non_witness_utxo, @@ -492,6 +596,168 @@ defmodule Bitcoinex.PSBT.In do @psbt_in_tap_merkle_root 0x18 @psbt_in_proprietary 0xFC + @minimum_time_locktime Transaction.minimum_time_locktime() + @valid_sighash_flags Transaction.valid_sighash_flags() + + def add_field(input, :non_witness_utxo, tx = %Transaction{}) + when input.non_witness_utxo == nil do + %In{input | non_witness_utxo: tx} + end + + def add_field(input, :witness_utxo, utxo = %Out{}) do + %In{input | witness_utxo: utxo} + end + + def add_field(input, :partial_sig, sig = %{public_key: _, signature: _}) do + sigs = PsbtUtils.append(input.partial_sig, sig) + %In{input | partial_sig: sigs} + end + + def add_field(input, :sighash_type, sighash_type) when sighash_type in @valid_sighash_flags do + %In{input | sighash_type: sighash_type} + end + + def add_field(input, :redeem_script, redeem_script) when is_binary(redeem_script) do + {:ok, script} = Script.parse_script(redeem_script) + add_field(input, :redeem_script, script) + end + + def add_field(input, :redeem_script, redeem_script = %Script{}) do + %In{input | redeem_script: redeem_script} + end + + def add_field(input, :witness_script, witness_script) do + {:ok, _} = Base.decode16(witness_script, case: :lower) + %In{input | witness_script: witness_script} + end + + def add_field(input, :bip32_derivation, derivation = %{public_key: _, pfp: _, derivation: _}) do + derivations = PsbtUtils.append(input.bip32_derivation, derivation) + %In{input | bip32_derivation: derivations} + end + + def add_field(input, :final_scriptsig, final_scriptsig) when is_binary(final_scriptsig) do + {:ok, script} = Script.parse_script(final_scriptsig) + add_field(input, :final_scriptsig, script) + end + + def add_field(input, :final_scriptsig, final_scriptsig = %Script{}) do + %In{input | final_scriptsig: final_scriptsig} + end + + def add_field(input, :final_scriptwitness, final_scriptwitness = %Transaction.Witness{}) do + %In{input | final_scriptwitness: final_scriptwitness} + end + + def add_field(input, :por_commitment, por_commitment) when is_binary(por_commitment) do + %In{input | por_commitment: por_commitment} + end + + def add_field(input, :ripemd160, ripemd160 = %{hash: h, preimage: p}) + when is_binary(h) and is_binary(p) do + ripemd160s = PsbtUtils.append(input.ripemd160, ripemd160) + %In{input | ripemd160: ripemd160s} + end + + def add_field(input, :sha256, sha256 = %{hash: h, preimage: p}) + when is_binary(h) and is_binary(p) do + sha256s = PsbtUtils.append(input.sha256, sha256) + %In{input | sha256: sha256s} + end + + def add_field(input, :hash160, hash160 = %{hash: h, preimage: p}) + when is_binary(h) and is_binary(p) do + hash160s = PsbtUtils.append(input.hash160, hash160) + %In{input | hash160: hash160s} + end + + def add_field(input, :hash256, hash256 = %{hash: h, preimage: p}) + when is_binary(h) and is_binary(p) do + hash256s = PsbtUtils.append(input.hash256, hash256) + %In{input | hash256: hash256s} + end + + def add_field(input, :previous_txid, <>) do + %In{input | previous_txid: previous_txid} + end + + def add_field(input, :output_index, output_index) + when is_integer(output_index) and output_index >= 0 do + %In{input | output_index: output_index} + end + + def add_field(input, :sequence, sequence) when is_integer(sequence) and sequence >= 0 do + %In{input | sequence: sequence} + end + + def add_field(input, :required_time_locktime, locktime) + when is_integer(locktime) and locktime >= @minimum_time_locktime do + %In{input | required_time_locktime: locktime} + end + + def add_field(input, :required_height_locktime, locktime) + when is_integer(locktime) and locktime < @minimum_time_locktime do + %In{input | required_height_locktime: locktime} + end + + def add_field(input, :tap_key_sig, tap_key_sig) + when is_binary(tap_key_sig) and byte_size(tap_key_sig) in [64, 65] do + %In{input | tap_key_sig: tap_key_sig} + end + + def add_field( + input, + :tap_script_sig, + tap_script_sig = %{public_key: _, leaf_hash: _, signature: _} + ) do + sigs = PsbtUtils.append(input.tap_script_sig, tap_script_sig) + %In{input | tap_script_sig: sigs} + end + + # TODO:taproot make this TapLeaf + def add_field( + input, + :tap_leaf_script, + tap_leaf_script = %{leaf_version: _, script: _, control_block: _} + ) do + scripts = PsbtUtils.append(input.tap_leaf_script, tap_leaf_script) + %In{input | tap_leaf_script: scripts} + end + + def add_field( + input, + :tap_bip32_derivation, + tap_bip32_derivation = %{public_key: _, leaf_hashes: _, pfp: _, derivation: _} + ) do + derivations = PsbtUtils.append(input.tap_bip32_derivation, tap_bip32_derivation) + %In{input | tap_bip32_derivation: derivations} + end + + def add_field(input, :tap_internal_key, <>) do + {:ok, pk} = Point.lift_x(tap_internal_key) + add_field(input, :tap_internal_key, pk) + end + + def add_field(input, :tap_internal_key, tap_internal_key = %Point{}) do + %In{input | tap_internal_key: tap_internal_key} + end + + def add_field(input, :tap_merkle_root, <>) do + %In{input | tap_merkle_root: tap_merkle_root} + end + + def add_field(input, :proprietary, proprietary = %{key: k, value: v}) + when is_binary(k) and is_binary(v) do + proprietaries = PsbtUtils.append(input.proprietary, proprietary) + %In{input | proprietary: proprietaries} + end + + def add_field(input, :unknown, value = %{key: k, value: v}) + when is_binary(k) and is_binary(v) do + unknown = PsbtUtils.append(input.unknown, value) + %In{input | unknown: unknown} + end + def parse_inputs(psbt, num_inputs) do psbt |> parse_input([], num_inputs) @@ -545,7 +811,7 @@ defmodule Bitcoinex.PSBT.In do end defp serialize_kv(:partial_sig, value) when value != nil do - key_data = Base.decode16!(value.public_key, case: :lower) + key_data = Point.sec(value.public_key) val = Base.decode16!(value.signature, case: :lower) PsbtUtils.serialize_kv(<<@psbt_in_partial_sig::big-size(8)>> <> key_data, val) @@ -558,14 +824,14 @@ defmodule Bitcoinex.PSBT.In do defp serialize_kv(:final_scriptsig, value) when value != nil do PsbtUtils.serialize_kv( <<@psbt_in_final_scriptsig::big-size(8)>>, - Base.decode16!(value, case: :lower) + Script.serialize_script(value) ) end defp serialize_kv(:redeem_script, value) when value != nil do PsbtUtils.serialize_kv( <<@psbt_in_redeem_script::big-size(8)>>, - Base.decode16!(value, case: :lower) + Script.serialize_script(value) ) end @@ -584,7 +850,7 @@ defmodule Bitcoinex.PSBT.In do end defp serialize_kv(:bip32_derivation, value) when value != nil do - key_data = Base.decode16!(value.public_key, case: :lower) + key_data = Point.sec(value.public_key) val = PsbtUtils.serialize_fingerprint_path(value.pfp, value.derivation) @@ -595,35 +861,35 @@ defmodule Bitcoinex.PSBT.In do PsbtUtils.serialize_kv(<<@psbt_in_por_commitment::big-size(8)>>, value) end - defp serialize_kv(:in_ripemd160, value) when value != nil do + defp serialize_kv(:in_ripemd160, %{hash: hash, preimage: preimage}) do PsbtUtils.serialize_kv( - <<@psbt_in_ripemd160::big-size(8), value.hash::binary-size(20)>>, - value.preimage + <<@psbt_in_ripemd160::big-size(8), hash::binary-size(20)>>, + preimage ) end - defp serialize_kv(:in_sha256, value) when value != nil do + defp serialize_kv(:in_sha256, %{hash: hash, preimage: preimage}) do PsbtUtils.serialize_kv( - <<@psbt_in_sha256::big-size(8), value.hash::binary-size(32)>>, - value.preimage + <<@psbt_in_sha256::big-size(8), hash::binary-size(32)>>, + preimage ) end - defp serialize_kv(:in_hash160, value) when value != nil do + defp serialize_kv(:in_hash160, %{hash: hash, preimage: preimage}) do PsbtUtils.serialize_kv( - <<@psbt_in_hash160::big-size(8), value.hash::binary-size(20)>>, - value.preimage + <<@psbt_in_hash160::big-size(8), hash::binary-size(20)>>, + preimage ) end - defp serialize_kv(:in_hash256, value) when value != nil do + defp serialize_kv(:in_hash256, %{hash: hash, preimage: preimage}) do PsbtUtils.serialize_kv( - <<@psbt_in_hash256::big-size(8), value.hash::binary-size(32)>>, - value.preimage + <<@psbt_in_hash256::big-size(8), hash::binary-size(32)>>, + preimage ) end - defp serialize_kv(:previous_txid, value) when value != nil do + defp serialize_kv(:previous_txid, <>) do PsbtUtils.serialize_kv(<<@psbt_in_previous_txid::big-size(8)>>, value) end @@ -655,7 +921,8 @@ defmodule Bitcoinex.PSBT.In do 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>>, + <<@psbt_in_tap_script_sig::big-size(8), Point.x_bytes(value.public_key)::binary, + value.leaf_hash::binary>>, value.signature ) end @@ -675,21 +942,21 @@ defmodule Bitcoinex.PSBT.In do fingerprint_path = PsbtUtils.serialize_fingerprint_path(value.pfp, value.derivation) PsbtUtils.serialize_kv( - <<@psbt_in_tap_bip32_derivation::big-size(8), value.pubkey::binary>>, + <<@psbt_in_tap_bip32_derivation::big-size(8), Point.x_bytes(value.public_key)::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) + PsbtUtils.serialize_kv(<<@psbt_in_tap_internal_key::big-size(8)>>, Point.x_bytes(value)) end - defp serialize_kv(:tap_merkle_root, value) when value != nil do + defp serialize_kv(:tap_merkle_root, <>) 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) + defp serialize_kv(:proprietary, %{key: k, value: v}) when is_binary(k) and is_binary(v) do + PsbtUtils.serialize_kv(<<@psbt_in_proprietary::big-size(8), k::binary>>, v) end defp serialize_kv(:unknown, %{key: k, value: v}) do @@ -783,46 +1050,47 @@ defmodule Bitcoinex.PSBT.In do 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(value) - input = %In{input | non_witness_utxo: txn} + input = add_field(input, :non_witness_utxo, txn) {input, psbt} end defp parse(<<@psbt_in_witness_utxo::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) out = Out.output(value) - input = %In{input | witness_utxo: out} + input = add_field(input, :witness_utxo, out) {input, psbt} end 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) + {:ok, pk} = Point.parse_public_key(public_key) + partial_sig = %{ - public_key: Base.encode16(public_key, case: :lower), + public_key: pk, signature: Base.encode16(value, case: :lower) } - partial_sigs = PsbtUtils.append(input.partial_sig, partial_sig) - input = %In{input | partial_sig: partial_sigs} + input = add_field(input, :partial_sig, partial_sig) {input, psbt} end defp parse(<<@psbt_in_sighash_type::big-size(8)>>, psbt, input) do {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | sighash_type: value} + input = add_field(input, :sighash_type, value) {input, psbt} end defp parse(<<@psbt_in_redeem_script::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | redeem_script: Base.encode16(value, case: :lower)} + input = add_field(input, :redeem_script, value) {input, psbt} end defp parse(<<@psbt_in_witness_script::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | witness_script: Base.encode16(value, case: :lower)} + input = add_field(input, :witness_script, Base.encode16(value, case: :lower)) {input, psbt} end @@ -831,27 +1099,27 @@ defmodule Bitcoinex.PSBT.In do {pfp, path} = PsbtUtils.parse_fingerprint_path(value) + {:ok, pk} = Point.parse_public_key(public_key) + derivation = %{ - public_key: Base.encode16(public_key, case: :lower), + public_key: pk, pfp: pfp, derivation: path } - bip32_derivation = PsbtUtils.append(input.bip32_derivation, derivation) - - input = %In{input | bip32_derivation: bip32_derivation} + input = add_field(input, :bip32_derivation, derivation) {input, psbt} end defp parse(<<@psbt_in_final_scriptsig::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | final_scriptsig: Base.encode16(value, case: :lower)} + input = add_field(input, :final_scriptsig, value) {input, psbt} end defp parse(<<@psbt_in_por_commitment::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | por_commitment: value} + input = add_field(input, :por_commitment, value) {input, psbt} end @@ -864,9 +1132,7 @@ defmodule Bitcoinex.PSBT.In do preimage: preimage } - ripemd160s = PsbtUtils.append(input.ripemd160, data) - - input = %In{input | ripemd160: ripemd160s} + input = add_field(input, :ripemd160, data) {input, psbt} end @@ -879,8 +1145,7 @@ defmodule Bitcoinex.PSBT.In do preimage: preimage } - sha256s = PsbtUtils.append(input.sha256, data) - input = %In{input | sha256: sha256s} + input = add_field(input, :sha256, data) {input, psbt} end @@ -893,8 +1158,7 @@ defmodule Bitcoinex.PSBT.In do preimage: preimage } - hash160s = PsbtUtils.append(input.hash160, data) - input = %In{input | hash160: hash160s} + input = add_field(input, :hash160, data) {input, psbt} end @@ -907,47 +1171,43 @@ defmodule Bitcoinex.PSBT.In do preimage: preimage } - hash256s = PsbtUtils.append(input.hash256, data) - input = %In{input | hash256: hash256s} + input = add_field(input, :hash256, data) {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} + {<>, psbt} = PsbtUtils.parse_compact_size_value(psbt) + input = add_field(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 = add_field(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 = add_field(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 = add_field(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 = add_field(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 = add_field(input, :tap_key_sig, value) {input, psbt} end @@ -960,15 +1220,15 @@ defmodule Bitcoinex.PSBT.In do # TODO:validation validate sig len (64|65) {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + {:ok, pk} = Point.lift_x(pubkey) + data = %{ - pubkey: pubkey, + public_key: pk, 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 = add_field(input, :tap_script_sig, data) {input, psbt} end @@ -991,53 +1251,51 @@ defmodule Bitcoinex.PSBT.In do control_block: control_block } - tap_leaf_scripts = PsbtUtils.append(input.tap_leaf_script, data) - input = %In{input | tap_leaf_script: tap_leaf_scripts} + input = add_field(input, :tap_leaf_script, data) {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) + {:ok, pk} = Point.lift_x(pubkey) {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, + public_key: pk, 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 = add_field(input, :tap_bip32_derivation, 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 = add_field(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 = add_field(input, :tap_merkle_root, value) {input, psbt} end - defp parse(<<@psbt_in_proprietary::big-size(8)>>, psbt, input) do + defp parse(<<@psbt_in_proprietary::big-size(8), key::binary>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - input = %In{input | proprietary: value} + input = add_field(input, :proprietary, %{key: key, value: value}) {input, psbt} end defp parse(<<@psbt_in_final_scriptwitness::big-size(8)>>, psbt, input) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) value = Witness.witness(value) - input = %In{input | final_scriptwitness: value} + input = add_field(input, :final_scriptwitness, value) {input, psbt} end @@ -1045,16 +1303,7 @@ defmodule Bitcoinex.PSBT.In 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 = add_field(input, :unknown, kv) {input, psbt} end end @@ -1067,7 +1316,9 @@ defmodule Bitcoinex.PSBT.Out do alias Bitcoinex.Utils alias Bitcoinex.PSBT.Utils, as: PsbtUtils alias Bitcoinex.Transaction.Utils, as: TxUtils + alias Bitcoinex.Transaction.Out, as: TxOut alias Bitcoinex.Script + alias Bitcoinex.Secp256k1.Point defstruct [ :redeem_script, @@ -1078,7 +1329,8 @@ defmodule Bitcoinex.PSBT.Out do :tap_internal_key, :tap_tree, :tap_bip32_derivation, - :proprietary + :proprietary, + :unknown ] @psbt_out_redeem_script 0x00 @@ -1091,6 +1343,65 @@ defmodule Bitcoinex.PSBT.Out do @psbt_out_tap_bip32_derivation 0x07 @psbt_out_proprietary 0xFC + def add_field(output, :redeem_script, script_bytes) + when is_binary(script_bytes) and output.script_bytes == nil do + {:ok, redeem_script} = Script.parse_script(script_bytes) + add_field(output, :redeem_script, redeem_script) + end + + def add_field(output, :redeem_script, script = %Script{}) do + %Out{output | redeem_script: script} + end + + def add_field(output, :witness_script, witness_script) + when is_binary(witness_script) and output.witness_script == nil do + %Out{output | witness_script: witness_script} + end + + def add_field(output, :bip32_derivation, derivation = %{public_key: _, pfp: _, derivation: _}) do + # ensure no duplicate keys? + derivations = PsbtUtils.append(output.bip32_derivation, derivation) + %Out{output | bip32_derivation: derivations} + end + + def add_field(output, :amount, amount) when is_integer(amount) and amount >= 0 do + %Out{output | amount: amount} + end + + def add_field(output, :script, script_bytes) when is_binary(script_bytes) do + {:ok, script} = Script.parse_script(script_bytes) + add_field(output, :script, script) + end + + def add_field(output, :script, script = %Script{}) do + %Out{output | script: script} + end + + def add_field(output, :tap_internal_key, pk) when is_binary(pk) do + {:ok, pk} = Point.lift_x(pk) + %Out{output | tap_internal_key: pk} + end + + # TODO:taproot find a good format for taptree + def add_field(output, :tap_tree, tree) do + %Out{output | tap_tree: tree} + end + + def add_field( + output, + :tap_bip32_derivation, + derivation = %{public_key: _, leaf_hashes: _, pfp: _, derivation: _} + ) do + derivations = PsbtUtils.append(output.tap_bip32_derivation, derivation) + %Out{output | tap_bip32_derivation: derivations} + end + + # TODO: fix + def add_field(output, :proprietary, kv = %{key: _, value: _}) do + kvs = PsbtUtils.append(output.proprietary, kv) + %Out{output | proprietary: kvs} + end + def serialize_outputs(outputs) when is_list(outputs) and length(outputs) > 0 do serialize_output(outputs, <<>>) end @@ -1099,6 +1410,7 @@ defmodule Bitcoinex.PSBT.Out do <<>> end + @spec from_tx_outputs(list(TxOut)) :: list(Out) def from_tx_outputs(tx_outputs) do Enum.reduce(tx_outputs, [], fn _, acc -> [%Out{} | acc] end) |> Enum.reverse() @@ -1107,7 +1419,7 @@ defmodule Bitcoinex.PSBT.Out do defp serialize_kv(:redeem_script, value) when value != nil do PsbtUtils.serialize_kv( <<@psbt_out_redeem_script::big-size(8)>>, - Base.decode16!(value, case: :lower) + Script.serialize_script(value) ) end @@ -1119,7 +1431,7 @@ defmodule Bitcoinex.PSBT.Out do end defp serialize_kv(:bip32_derivation, value) when value != nil do - key_data = Base.decode16!(value.public_key, case: :lower) + key_data = Point.sec(value.public_key) val = PsbtUtils.serialize_fingerprint_path(value.pfp, value.derivation) PsbtUtils.serialize_kv(<<@psbt_out_bip32_derivation::big-size(8)>> <> key_data, val) @@ -1134,7 +1446,7 @@ defmodule Bitcoinex.PSBT.Out do end defp serialize_kv(:tap_internal_key, value) when value != nil do - PsbtUtils.serialize_kv(<<@psbt_out_tap_internal_key::big-size(8)>>, value) + PsbtUtils.serialize_kv(<<@psbt_out_tap_internal_key::big-size(8)>>, Point.x_bytes(value)) end defp serialize_kv(:tap_tree, value) when value != nil do @@ -1143,15 +1455,19 @@ defmodule Bitcoinex.PSBT.Out do end defp serialize_kv(:tap_bip32_derivation, value) when value != nil do - key = <<@psbt_out_tap_bip32_derivation::big-size(8), value.pubkey::binary>> + key = <<@psbt_out_tap_bip32_derivation::big-size(8)>> <> Point.x_bytes(value.public_key) 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) + defp serialize_kv(:proprietary, %{key: k, value: v}) when is_binary(k) and is_binary(v) do + PsbtUtils.serialize_kv(<<@psbt_out_proprietary::big-size(8), k::binary>>, v) + end + + defp serialize_kv(:unknown, %{key: k, value: v}) when is_binary(k) and is_binary(v) do + PsbtUtils.serialize_kv(k, v) end defp serialize_kv(_key, _value) do @@ -1213,13 +1529,13 @@ defmodule Bitcoinex.PSBT.Out do defp parse(<<@psbt_out_redeem_script::big-size(8)>>, psbt, output) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - output = %Out{output | redeem_script: Base.encode16(value, case: :lower)} + output = add_field(output, :redeem_script, value) {output, psbt} end defp parse(<<@psbt_out_scriptwitness::big-size(8)>>, psbt, output) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) - output = %Out{output | witness_script: Base.encode16(value, case: :lower)} + output = add_field(output, :witness_script, Base.encode16(value, case: :lower)) {output, psbt} end @@ -1230,35 +1546,35 @@ defmodule Bitcoinex.PSBT.Out do ) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) + {:ok, pk} = Point.parse_public_key(public_key) + {pfp, path} = PsbtUtils.parse_fingerprint_path(value) derivation = %{ - public_key: Base.encode16(public_key, case: :lower), + public_key: pk, pfp: pfp, derivation: path } - bip32_derivation = PsbtUtils.append(output.bip32_derivation, derivation) - output = %Out{output | bip32_derivation: bip32_derivation} + output = add_field(output, :bip32_derivation, 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 = add_field(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 = add_field(output, :script, value) {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 = add_field(output, :tap_internal_key, value) {output, psbt} end @@ -1266,14 +1582,14 @@ defmodule Bitcoinex.PSBT.Out 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 = add_field(output, :tap_tree, %{leaves: leaves}) {output, psbt} end defp parse( <<@psbt_out_tap_bip32_derivation::big-size(8), pubkey::binary-size(32)>>, psbt, - output + output = %Out{} ) do {value, psbt} = PsbtUtils.parse_compact_size_value(psbt) @@ -1281,21 +1597,23 @@ defmodule Bitcoinex.PSBT.Out do {leaf_hashes, value} = PsbtUtils.parse_leaf_hashes(value, leaf_hash_ct) {pfp, path} = PsbtUtils.parse_fingerprint_path(value) + {:ok, pk} = Point.lift_x(pubkey) + derivation = %{ - pubkey: pubkey, + public_key: pk, 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 = add_field(output, :tap_bip32_derivation, derivation) {output, psbt} end + # TODO: fix 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 = add_field(output, :proprietary, value) {output, psbt} end diff --git a/lib/transaction.ex b/lib/transaction.ex index 07e46eb..6408b31 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -28,6 +28,10 @@ defmodule Bitcoinex.Transaction do :lock_time ] + @minimum_time_locktime 500_000_000 + + def minimum_time_locktime(), do: @minimum_time_locktime + @sighash_default 0x00 @sighash_all 0x01 @sighash_none 0x02 @@ -47,6 +51,8 @@ defmodule Bitcoinex.Transaction do @sighash_anyonecanpay_single ] + def valid_sighash_flags(), do: @valid_sighash_flags + @doc """ Returns the TxID of the given tranasction. diff --git a/test/psbt_test.exs b/test/psbt_test.exs index 03c312d..c2ea453 100644 --- a/test/psbt_test.exs +++ b/test/psbt_test.exs @@ -121,11 +121,28 @@ defmodule Bitcoinex.PSBTTest do }, expected_in: [ %In{ - final_scriptsig: - "47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa88292" + final_scriptsig: %Bitcoinex.Script{ + items: [ + 71, + <<48, 68, 2, 32, 71, 89, 102, 23, 151, 192, 27, 3, 107, 37, 146, 137, 72, 104, 98, + 24, 52, 125, 137, 134, 75, 113, 158, 31, 127, 207, 87, 209, 229, 17, 101, 135, 2, + 32, 83, 9, 234, 191, 86, 170, 77, 136, 145, 255, 209, 17, 253, 241, 51, 111, 58, + 41, 218, 134, 109, 127, 132, 134, 215, 85, 70, 206, 237, 175, 147, 25, 1>>, + 33, + <<3, 92, 220, 97, 252, 123, 169, 113, 192, 181, 1, 166, 70, 162, 168, 59, 16, 44, + 180, 56, 129, 33, 124, 166, 130, 220, 134, 226, 215, 63, 168, 130, 146>> + ] + } }, %In{ - redeem_script: "001485d13537f2e265405a34dbafa9e3dda01fb82308", + redeem_script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<133, 209, 53, 55, 242, 226, 101, 64, 90, 52, 219, 175, 169, 227, 221, 160, 31, + 184, 35, 8>> + ] + }, witness_utxo: %Bitcoinex.Transaction.Out{ script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787", value: 100_000_000 @@ -189,14 +206,24 @@ defmodule Bitcoinex.PSBTTest do derivation: %Bitcoinex.ExtendedKey.DerivationPath{ child_nums: [2_147_483_822, 2_147_483_648, 0] }, - public_key: "029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c8871", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 71_297_889_195_667_677_566_853_709_053_103_131_423_162_117_776_603_813_866_869_556_867_184_864_299_121, + y: + 104_942_558_329_072_212_830_372_841_913_067_137_391_352_852_664_610_839_861_441_469_392_637_640_386_592 + }, pfp: <<217, 12, 106, 79>> }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ child_nums: [2_147_483_822, 2_147_483_649, 0] }, - public_key: "03372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 24_953_540_938_576_426_582_583_323_081_660_174_044_181_452_719_495_711_599_804_679_737_956_351_918_091, + y: + 115_045_788_123_487_702_367_674_850_763_085_807_099_618_969_835_940_161_186_892_982_331_420_489_690_021 + }, pfp: <<217, 12, 106, 79>> } ], @@ -216,7 +243,12 @@ defmodule Bitcoinex.PSBTTest do derivation: %Bitcoinex.ExtendedKey.DerivationPath{ child_nums: [2_147_483_648, 2_147_483_648, 2_147_483_649] }, - public_key: "039eff1f547a1d5f92dfa2ba7af6ac971a4bd03ba4a734b03156a256b8ad3a1ef9", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 71_916_192_309_307_030_987_819_255_271_417_383_252_226_513_354_309_312_442_460_376_541_014_510_935_801, + y: + 87_829_264_540_646_754_532_057_909_755_234_567_745_025_032_566_591_945_062_448_323_127_703_490_972_993 + }, pfp: <<237, 228, 92, 197>> } ] @@ -253,25 +285,47 @@ defmodule Bitcoinex.PSBTTest do derivation: %Bitcoinex.ExtendedKey.DerivationPath{ child_nums: [2_147_483_648, 2_147_483_648, 2_147_483_652] }, - public_key: "03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 80_151_448_986_003_541_602_445_390_187_849_273_116_474_332_975_424_144_708_997_035_409_020_762_307_910, + y: + 69_508_417_946_258_487_178_124_295_602_214_619_390_077_167_556_721_617_739_677_223_796_084_805_268_603 + }, pfp: <<180, 166, 186, 103>> }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ child_nums: [2_147_483_648, 2_147_483_648, 2_147_483_653] }, - public_key: "03de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 100_565_082_940_006_144_500_918_712_336_860_239_214_176_819_872_577_604_419_994_557_980_044_395_840_445, + y: + 10_103_911_892_721_234_278_209_537_983_272_986_690_525_296_344_245_993_216_791_872_735_802_656_255_649 + }, pfp: <<180, 166, 186, 103>> } ], partial_sig: [ %{ - public_key: "03b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd46", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 80_151_448_986_003_541_602_445_390_187_849_273_116_474_332_975_424_144_708_997_035_409_020_762_307_910, + y: + 69_508_417_946_258_487_178_124_295_602_214_619_390_077_167_556_721_617_739_677_223_796_084_805_268_603 + }, signature: "304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a01" } ], - redeem_script: "0020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681", + redeem_script: %Bitcoinex.Script{ + items: [ + 0, + 32, + <<119, 31, 209, 138, 212, 89, 102, 109, 212, 159, 61, 86, 78, 61, 188, 66, 244, 200, + 71, 116, 227, 96, 173, 161, 104, 22, 168, 237, 72, 141, 86, 129>> + ] + }, witness_script: "522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae", witness_utxo: %Bitcoinex.Transaction.Out{ @@ -358,21 +412,36 @@ defmodule Bitcoinex.PSBTTest do derivation: %Bitcoinex.ExtendedKey.DerivationPath{ child_nums: [2_147_483_648, 2_147_483_650, 2, 0, 0, 1, 13] }, - public_key: "023e1a92f74483a4c12a196d6c0253ac589cfadd5964ff3e6c63aef5d577051ed4", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 28_090_348_957_135_603_138_302_582_488_634_744_063_769_373_744_003_534_936_250_327_398_955_197_210_324, + y: + 72_997_351_855_862_386_313_265_421_144_400_773_180_384_569_016_361_889_288_840_365_141_506_887_425_034 + }, pfp: <<0, 0, 0, 0>> }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ child_nums: [2_147_483_648, 2_147_483_650, 2, 0, 0, 1, 13] }, - public_key: "024d0e39b33ab57782a98fd06a7d0653385e509ee3b74dc45559e6db6391d31378", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 34_853_223_431_373_393_811_263_769_947_202_683_171_474_774_417_547_322_886_690_942_546_874_362_696_568, + y: + 113_268_030_251_073_357_691_209_583_004_229_905_838_714_795_934_778_666_512_471_345_919_935_656_053_596 + }, pfp: <<0, 0, 0, 0>> }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ child_nums: [2_147_483_648, 2_147_483_650, 2, 0, 0, 1, 13] }, - public_key: "036c0bae6bcb02e47c857c480ed1d23888c8ad6a66b83ab111cb6b9242afb1c725", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 48_870_426_774_663_552_239_665_427_301_830_100_617_674_007_218_870_368_410_715_268_929_150_665_213_733, + y: + 384_321_948_423_668_768_499_139_968_164_679_158_512_264_350_568_463_086_818_860_781_694_386_290_525 + }, pfp: <<0, 0, 0, 0>> } ], @@ -459,7 +528,14 @@ defmodule Bitcoinex.PSBTTest do value: 100_000_000, script_pub_key: "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787" }, - redeem_script: "001485d13537f2e265405a34dbafa9e3dda01fb82308" + redeem_script: %Bitcoinex.Script{ + items: [ + 0, + 20, + <<133, 209, 53, 55, 242, 226, 101, 64, 90, 52, 219, 175, 169, 227, 221, 160, 31, + 184, 35, 8>> + ] + } } ], expected_out: [ @@ -470,7 +546,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_648, 2_147_483_648, 2_147_483_650] }, pfp: <<180, 166, 186, 103>>, - public_key: "02ead596687ca806043edc3de116cdf29d5e9257c196cd055cf698c8d02bf24e99" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 106_218_583_072_196_447_736_380_334_552_715_158_727_992_653_635_463_477_362_034_291_905_132_141_629_081, + y: + 111_853_877_987_030_790_093_148_386_887_435_750_835_805_008_949_758_207_878_306_607_752_401_323_629_588 + } } ] }, @@ -481,7 +562,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_648, 2_147_483_649, 2_147_483_650] }, pfp: <<180, 166, 186, 103>>, - public_key: "0394f62be9df19952c5587768aeb7698061ad2c4a25c894f47d8c162b4d7213d05" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 67_377_249_048_514_558_622_301_148_581_987_854_752_621_201_928_971_282_930_920_275_574_615_944_805_637, + y: + 33_878_338_431_620_821_020_481_726_477_149_418_114_572_000_720_118_999_798_151_283_966_732_510_331_525 + } } ] } @@ -660,7 +746,12 @@ defmodule Bitcoinex.PSBTTest do }, partial_sig: [ %{ - public_key: "03309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 21_976_933_772_883_498_789_027_591_114_401_201_619_393_627_105_936_886_383_472_697_361_469_744_065_884, + y: + 7_200_326_197_606_395_130_472_957_753_047_368_877_161_908_639_095_988_897_070_963_103_467_471_435_399 + }, signature: "304402202d704ced830c56a909344bd742b6852dccd103e963bae92d38e75254d2bb424502202d86c437195df46c0ceda084f2a291c3da2d64070f76bf9b90b195e7ef28f77201" } @@ -671,7 +762,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_697, 2_147_483_648, 2_147_483_648, 0, 1] }, pfp: <<39, 86, 156, 80>>, - public_key: "03309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 21_976_933_772_883_498_789_027_591_114_401_201_619_393_627_105_936_886_383_472_697_361_469_744_065_884, + y: + 7_200_326_197_606_395_130_472_957_753_047_368_877_161_908_639_095_988_897_070_963_103_467_471_435_399 + } } ] }, @@ -682,7 +778,12 @@ defmodule Bitcoinex.PSBTTest do }, partial_sig: [ %{ - public_key: "02c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b110", + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 90_220_664_355_153_390_194_324_778_431_845_886_101_502_662_137_446_551_229_950_573_847_917_083_275_536, + y: + 30_671_849_324_763_586_853_276_572_668_087_977_292_899_353_337_776_873_336_350_612_915_119_803_985_620 + }, signature: "304402204cb1fb5f869c942e0e26100576125439179ae88dca8a9dc3ba08f7953988faa60220521f49ca791c27d70e273c9b14616985909361e25be274ea200d7e08827e514d01" } @@ -693,7 +794,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_697, 2_147_483_648, 2_147_483_648, 0, 0] }, pfp: <<39, 86, 156, 80>>, - public_key: "02c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b110" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 90_220_664_355_153_390_194_324_778_431_845_886_101_502_662_137_446_551_229_950_573_847_917_083_275_536, + y: + 30_671_849_324_763_586_853_276_572_668_087_977_292_899_353_337_776_873_336_350_612_915_119_803_985_620 + } } ] } @@ -707,7 +813,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_697, 2_147_483_648, 2_147_483_648, 0, 4] }, pfp: <<39, 86, 156, 80>>, - public_key: "02d20ca502ee289686d21815bd43a80637b0698e1fbcdbe4caed445f6c1a0a90ef" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 95_008_039_234_411_418_297_358_865_313_145_660_837_471_761_864_556_347_248_745_408_858_102_616_985_839, + y: + 58_958_258_549_746_503_926_788_822_872_770_747_559_389_384_344_394_656_102_791_551_503_991_185_090_332 + } } ] } @@ -842,7 +953,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -862,7 +978,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -925,7 +1046,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -945,7 +1071,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1011,7 +1142,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1031,7 +1167,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1094,7 +1235,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1114,7 +1260,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1177,7 +1328,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1197,7 +1353,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1260,7 +1421,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1280,7 +1446,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1343,7 +1514,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1363,7 +1539,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1426,7 +1607,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1446,7 +1632,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1509,7 +1700,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1529,7 +1725,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1592,7 +1793,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1612,7 +1818,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1675,7 +1886,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1695,7 +1911,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1758,7 +1979,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1778,7 +2004,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1845,7 +2076,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 42] }, pfp: <<246, 157, 135, 62>>, - public_key: "02d601f84846a6755f776be00e3d9de8fb10acc935fb83c45fb0162d4cad5ab792" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 96_798_430_025_534_287_057_818_182_799_781_202_983_716_126_197_033_126_318_275_350_637_861_242_845_074, + y: + 24_577_140_550_790_602_129_463_064_506_346_425_976_399_276_187_317_960_372_865_959_737_887_833_605_246 + } } ], amount: 800_000_000, @@ -1865,7 +2101,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 1, 100] }, pfp: <<246, 157, 135, 62>>, - public_key: "02e36fbff53dd534070cf8fd396614680f357a9b85db7340bf1cfa745d2ad7b340" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 102_872_461_497_842_797_785_413_923_910_218_265_357_521_021_030_644_560_335_939_528_912_485_972_685_632, + y: + 49_593_273_316_372_432_714_030_705_100_641_392_787_703_279_192_668_893_534_395_536_493_521_009_155_774 + } } ], amount: 199_998_859, @@ -1917,14 +2158,20 @@ defmodule Bitcoinex.PSBTTest do }, 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>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 114_980_336_156_212_694_879_327_992_636_798_621_605_698_402_417_475_818_833_771_862_351_800_336_097_842, + y: + 80_701_412_123_039_057_594_876_775_965_687_848_606_566_558_952_919_681_215_239_681_970_556_263_873_620 + } } ], - 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>> + tap_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 114_980_336_156_212_694_879_327_992_636_798_621_605_698_402_417_475_818_833_771_862_351_800_336_097_842, + y: + 80_701_412_123_039_057_594_876_775_965_687_848_606_566_558_952_919_681_215_239_681_970_556_263_873_620 + } } ], expected_out: [ @@ -1935,7 +2182,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 0] }, pfp: <<119, 43, 45, 167>>, - public_key: "036b772a6db74d8753c98a827958de6c78ab3312109f37d3e0304484242ece73d8" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 48_608_022_430_402_926_014_916_497_053_789_681_947_094_617_356_347_258_841_609_839_742_612_155_560_920, + y: + 69_442_595_448_928_048_809_603_412_737_824_612_252_030_373_964_082_523_340_701_998_153_322_752_075_027 + } } ] } @@ -1982,14 +2234,20 @@ defmodule Bitcoinex.PSBTTest do }, 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>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 114_980_336_156_212_694_879_327_992_636_798_621_605_698_402_417_475_818_833_771_862_351_800_336_097_842, + y: + 80_701_412_123_039_057_594_876_775_965_687_848_606_566_558_952_919_681_215_239_681_970_556_263_873_620 + } } ], - 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>> + tap_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 114_980_336_156_212_694_879_327_992_636_798_621_605_698_402_417_475_818_833_771_862_351_800_336_097_842, + y: + 80_701_412_123_039_057_594_876_775_965_687_848_606_566_558_952_919_681_215_239_681_970_556_263_873_620 + } } ], expected_out: [ @@ -2000,7 +2258,12 @@ defmodule Bitcoinex.PSBTTest do child_nums: [2_147_483_732, 2_147_483_649, 2_147_483_648, 0, 0] }, pfp: <<119, 43, 45, 167>>, - public_key: "036b772a6db74d8753c98a827958de6c78ab3312109f37d3e0304484242ece73d8" + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 48_608_022_430_402_926_014_916_497_053_789_681_947_094_617_356_347_258_841_609_839_742_612_155_560_920, + y: + 69_442_595_448_928_048_809_603_412_737_824_612_252_030_373_964_082_523_340_701_998_153_322_752_075_027 + } } ] } @@ -2043,21 +2306,30 @@ defmodule Bitcoinex.PSBTTest do }, 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>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 114_980_336_156_212_694_879_327_992_636_798_621_605_698_402_417_475_818_833_771_862_351_800_336_097_842, + y: + 80_701_412_123_039_057_594_876_775_965_687_848_606_566_558_952_919_681_215_239_681_970_556_263_873_620 + } } ], - 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>> + tap_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 114_980_336_156_212_694_879_327_992_636_798_621_605_698_402_417_475_818_833_771_862_351_800_336_097_842, + y: + 80_701_412_123_039_057_594_876_775_965_687_848_606_566_558_952_919_681_215_239_681_970_556_263_873_620 + } } ], 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_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 7_754_432_814_978_735_047_277_584_654_213_252_760_875_963_706_567_224_418_638_150_419_547_067_508_337, + y: + 42_423_437_181_898_177_373_695_716_172_273_752_501_072_424_002_872_139_093_103_823_791_254_979_333_346 + }, tap_bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -2065,9 +2337,12 @@ defmodule Bitcoinex.PSBTTest do }, 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>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 7_754_432_814_978_735_047_277_584_654_213_252_760_875_963_706_567_224_418_638_150_419_547_067_508_337, + y: + 42_423_437_181_898_177_373_695_716_172_273_752_501_072_424_002_872_139_093_103_823_791_254_979_333_346 + } } ] } @@ -2167,9 +2442,12 @@ defmodule Bitcoinex.PSBTTest do 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>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 20_214_902_921_207_623_608_562_994_083_326_228_570_924_245_815_299_146_006_330_164_270_236_901_816_018, + y: + 33_295_055_140_301_703_998_324_163_407_344_732_932_777_411_489_229_368_797_248_015_921_617_797_465_334 + } }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -2180,17 +2458,23 @@ defmodule Bitcoinex.PSBTTest do 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>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 30_362_719_820_274_234_030_344_172_757_366_317_297_290_310_294_146_862_871_341_483_479_788_794_073_522, + y: + 61_647_782_036_840_226_561_126_690_482_215_573_260_410_436_215_868_166_657_038_664_786_860_649_378_740 + } }, %{ 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>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 36_444_060_476_547_731_421_425_013_472_121_489_344_383_018_981_262_552_973_668_657_287_772_036_414_144, + y: + 22_537_504_475_708_154_238_330_251_540_244_790_414_456_712_057_027_634_449_505_794_721_772_594_235_652 + } }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -2201,14 +2485,20 @@ defmodule Bitcoinex.PSBTTest do 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>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 113_105_558_507_633_336_913_885_034_341_920_459_137_683_993_564_483_976_665_524_654_145_797_510_995_113, + y: + 107_072_043_951_624_069_052_082_507_734_100_608_508_887_739_811_142_108_528_627_392_964_071_353_710_384 + } } ], - 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_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 36_444_060_476_547_731_421_425_013_472_121_489_344_383_018_981_262_552_973_668_657_287_772_036_414_144, + y: + 22_537_504_475_708_154_238_330_251_540_244_790_414_456_712_057_027_634_449_505_794_721_772_594_235_652 + }, 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>> @@ -2216,9 +2506,13 @@ defmodule Bitcoinex.PSBTTest do ], 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_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 7_754_432_814_978_735_047_277_584_654_213_252_760_875_963_706_567_224_418_638_150_419_547_067_508_337, + y: + 42_423_437_181_898_177_373_695_716_172_273_752_501_072_424_002_872_139_093_103_823_791_254_979_333_346, + z: 0 + }, tap_bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -2226,9 +2520,13 @@ defmodule Bitcoinex.PSBTTest do }, 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>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 7_754_432_814_978_735_047_277_584_654_213_252_760_875_963_706_567_224_418_638_150_419_547_067_508_337, + y: + 42_423_437_181_898_177_373_695_716_172_273_752_501_072_424_002_872_139_093_103_823_791_254_979_333_346, + z: 0 + } } ] } @@ -2271,21 +2569,30 @@ defmodule Bitcoinex.PSBTTest do }, 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>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 114_980_336_156_212_694_879_327_992_636_798_621_605_698_402_417_475_818_833_771_862_351_800_336_097_842, + y: + 80_701_412_123_039_057_594_876_775_965_687_848_606_566_558_952_919_681_215_239_681_970_556_263_873_620 + } } ], - 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>> + tap_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 114_980_336_156_212_694_879_327_992_636_798_621_605_698_402_417_475_818_833_771_862_351_800_336_097_842, + y: + 80_701_412_123_039_057_594_876_775_965_687_848_606_566_558_952_919_681_215_239_681_970_556_263_873_620 + } } ], 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_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 36_444_060_476_547_731_421_425_013_472_121_489_344_383_018_981_262_552_973_668_657_287_772_036_414_144, + y: + 22_537_504_475_708_154_238_330_251_540_244_790_414_456_712_057_027_634_449_505_794_721_772_594_235_652 + }, tap_tree: %{ leaves: [ %{ @@ -2336,17 +2643,24 @@ defmodule Bitcoinex.PSBTTest do 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>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 31_200_121_508_428_702_019_893_244_742_884_023_762_479_223_940_651_285_251_383_304_714_523_995_030_131, + y: + 53_120_362_633_623_697_201_506_777_401_802_198_637_852_190_256_815_688_867_012_397_976_660_138_781_348, + z: 0 + } }, %{ 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>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 36_444_060_476_547_731_421_425_013_472_121_489_344_383_018_981_262_552_973_668_657_287_772_036_414_144, + y: + 22_537_504_475_708_154_238_330_251_540_244_790_414_456_712_057_027_634_449_505_794_721_772_594_235_652 + } }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -2357,9 +2671,13 @@ defmodule Bitcoinex.PSBTTest do 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>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 44_829_100_993_385_313_407_048_989_888_172_542_182_954_521_609_934_891_498_742_790_273_562_110_482_793, + y: + 35_170_189_511_235_529_380_611_046_465_697_762_403_784_417_654_141_736_675_144_354_496_257_103_432_480, + z: 0 + } }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -2370,9 +2688,13 @@ defmodule Bitcoinex.PSBTTest do 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>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 52_210_932_321_595_760_052_581_731_536_224_183_934_983_599_287_982_449_328_637_004_030_112_510_331_394, + y: + 77_569_177_239_768_622_921_463_492_097_741_805_392_046_359_270_612_182_587_656_363_386_108_779_135_734, + z: 0 + } } ] } @@ -2413,9 +2735,12 @@ defmodule Bitcoinex.PSBTTest do 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>>, + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 20_214_902_921_207_623_608_562_994_083_326_228_570_924_245_815_299_146_006_330_164_270_236_901_816_018, + y: + 33_295_055_140_301_703_998_324_163_407_344_732_932_777_411_489_229_368_797_248_015_921_617_797_465_334 + }, 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, @@ -2426,9 +2751,12 @@ defmodule Bitcoinex.PSBTTest do 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>>, + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 30_362_719_820_274_234_030_344_172_757_366_317_297_290_310_294_146_862_871_341_483_479_788_794_073_522, + y: + 61_647_782_036_840_226_561_126_690_482_215_573_260_410_436_215_868_166_657_038_664_786_860_649_378_740 + }, 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, @@ -2439,9 +2767,12 @@ defmodule Bitcoinex.PSBTTest do 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>>, + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 113_105_558_507_633_336_913_885_034_341_920_459_137_683_993_564_483_976_665_524_654_145_797_510_995_113, + y: + 107_072_043_951_624_069_052_082_507_734_100_608_508_887_739_811_142_108_528_627_392_964_071_353_710_384 + }, 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, @@ -2513,9 +2844,12 @@ defmodule Bitcoinex.PSBTTest do 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>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 20_214_902_921_207_623_608_562_994_083_326_228_570_924_245_815_299_146_006_330_164_270_236_901_816_018, + y: + 33_295_055_140_301_703_998_324_163_407_344_732_932_777_411_489_229_368_797_248_015_921_617_797_465_334 + } }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -2526,17 +2860,23 @@ defmodule Bitcoinex.PSBTTest do 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>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 30_362_719_820_274_234_030_344_172_757_366_317_297_290_310_294_146_862_871_341_483_479_788_794_073_522, + y: + 61_647_782_036_840_226_561_126_690_482_215_573_260_410_436_215_868_166_657_038_664_786_860_649_378_740 + } }, %{ 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>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 36_444_060_476_547_731_421_425_013_472_121_489_344_383_018_981_262_552_973_668_657_287_772_036_414_144, + y: + 22_537_504_475_708_154_238_330_251_540_244_790_414_456_712_057_027_634_449_505_794_721_772_594_235_652 + } }, %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -2547,14 +2887,20 @@ defmodule Bitcoinex.PSBTTest do 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>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 113_105_558_507_633_336_913_885_034_341_920_459_137_683_993_564_483_976_665_524_654_145_797_510_995_113, + y: + 107_072_043_951_624_069_052_082_507_734_100_608_508_887_739_811_142_108_528_627_392_964_071_353_710_384 + } } ], - 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_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 36_444_060_476_547_731_421_425_013_472_121_489_344_383_018_981_262_552_973_668_657_287_772_036_414_144, + y: + 22_537_504_475_708_154_238_330_251_540_244_790_414_456_712_057_027_634_449_505_794_721_772_594_235_652 + }, 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>> @@ -2562,9 +2908,13 @@ defmodule Bitcoinex.PSBTTest do ], 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_internal_key: %Bitcoinex.Secp256k1.Point{ + x: + 7_754_432_814_978_735_047_277_584_654_213_252_760_875_963_706_567_224_418_638_150_419_547_067_508_337, + y: + 42_423_437_181_898_177_373_695_716_172_273_752_501_072_424_002_872_139_093_103_823_791_254_979_333_346, + z: 0 + }, tap_bip32_derivation: [ %{ derivation: %Bitcoinex.ExtendedKey.DerivationPath{ @@ -2572,9 +2922,13 @@ defmodule Bitcoinex.PSBTTest do }, 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>> + public_key: %Bitcoinex.Secp256k1.Point{ + x: + 7_754_432_814_978_735_047_277_584_654_213_252_760_875_963_706_567_224_418_638_150_419_547_067_508_337, + y: + 42_423_437_181_898_177_373_695_716_172_273_752_501_072_424_002_872_139_093_103_823_791_254_979_333_346, + z: 0 + } } ] } From c711203ec5b9ee417eb73e36434b65a83eb889e6 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sat, 4 Mar 2023 21:55:31 -0500 Subject: [PATCH 53/63] taproot_multisig -> tapscript_multisig --- lib/script.ex | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/script.ex b/lib/script.ex index 1b332b7..ec5fe82 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -538,24 +538,24 @@ defmodule Bitcoinex.Script do defp fill_multi_keys(_, _), do: raise(ArgumentError) - @spec create_taproot_multisig(non_neg_integer(), list(Point.t())) :: Script.t() - def create_taproot_multisig(m, pubkeys) when is_valid_multi(m, pubkeys) do + @spec create_tapscript_multisig(non_neg_integer(), list(Point.t())) :: Script.t() + def create_tapscript_multisig(m, pubkeys) when is_valid_multi(m, pubkeys) do {:ok, s} = push_op(new(), :op_equal) {:ok, s} = push_num(s, m) - fill_taproot_multi_keys(s, Enum.reverse(pubkeys)) + fill_tapscript_multi_keys(s, Enum.reverse(pubkeys)) end # creates a script using the pubkeys *in reverse order*. - defp fill_taproot_multi_keys(s, []), do: s - defp fill_taproot_multi_keys(s, [last_key]) do + defp fill_tapscript_multi_keys(s, []), do: s + defp fill_tapscript_multi_keys(s, [last_key]) do {:ok, s} = push_op(s, :op_checksig) {:ok, s} = push_data(s, Point.x_bytes(last_key)) s end - defp fill_taproot_multi_keys(s, [key | rest]) do + defp fill_tapscript_multi_keys(s, [key | rest]) do {:ok, s} = push_op(s, :op_checksigadd) {:ok, s} = push_data(s, Point.x_bytes(key)) - fill_taproot_multi_keys(s, rest) + fill_tapscript_multi_keys(s, rest) end @doc """ From 4774678ae44baff21e4659a0691c1db274115bef Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sat, 4 Mar 2023 23:35:28 -0500 Subject: [PATCH 54/63] implement bip67 --- lib/script.ex | 66 ++++++++++++++------ lib/utils.ex | 2 +- test/script_test.exs | 142 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 187 insertions(+), 23 deletions(-) diff --git a/lib/script.ex b/lib/script.ex index d2d20c3..fa547b4 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -162,12 +162,13 @@ defmodule Bitcoinex.Script do end @spec push_num(t(), non_neg_integer()) :: t() - def push_num(%__MODULE__{items: stack}, num) when num >= 0 and num <= 16 do + def push_num(%__MODULE__{items: stack}, num) when num >= 0 and num <= 16 do op_num = num_to_op_num(num) - %__MODULE__{items: [op_num | stack]} + {:ok, %__MODULE__{items: [op_num | stack]}} end def num_to_op_num(0), do: 0 + def num_to_op_num(num) when num > 0 and num <= 16 do 0x50 + num end @@ -522,9 +523,19 @@ defmodule Bitcoinex.Script do @doc """ create_multisig creates a raw multisig script using m and the list of public keys. - """ - @spec create_multisig(non_neg_integer(), list(Point.t())) :: {:ok, t()} | {:error, String.t()} - def create_multisig(m, pubkeys) when is_valid_multisig(m, pubkeys) do + this function sorts the pubkeys lexicographically, complying with BIP 67. For a non-compliant + version of this function, use create_unsorted_multisig + """ + @spec create_multisig(non_neg_integer(), list(Point.t()), list({:bip67_sort, boolean})) :: {:ok, t()} | {:error, String.t()} + def create_multisig(m, pubkeys, opts \\ []) + def create_multisig(m, pubkeys, opts) when is_valid_multisig(m, pubkeys) do + pubkeys = + if Keyword.get(opts, :bip67_sort, true) do + lexicographical_sort_pubkeys(pubkeys) + else + pubkeys + end + try do # checkmultisig {:ok, s} = push_op(new(), 0xAE) @@ -536,7 +547,7 @@ defmodule Bitcoinex.Script do end end - def create_multisig(_, _), do: {:error, "invalid multisig: must be of form: (int, list(%Point)"} + def create_multisig(_, _, _), do: {:error, "invalid multisig: must be of form: (int, list(%Point)"} defp fill_multisig_keys(s, []), do: s @@ -548,33 +559,50 @@ defmodule Bitcoinex.Script do defp fill_multisig_keys(_, _), do: raise(ArgumentError) @spec create_tapscript_multisig(non_neg_integer(), list(Point.t())) :: Script.t() - def create_tapscript_multisig(m, pubkeys) when is_valid_multi(m, pubkeys) do - {:ok, s} = push_op(new(), :op_equal) + def create_tapscript_multisig(m, pubkeys) when is_valid_multisig(m, pubkeys) do + {:ok, s} = push_op(new(), :op_numequal) {:ok, s} = push_num(s, m) - fill_tapscript_multi_keys(s, Enum.reverse(pubkeys)) + pubkeys = lexicographical_sort_pubkeys(pubkeys) + fill_tapscript_multisig_keys(s, Enum.reverse(pubkeys)) end # creates a script using the pubkeys *in reverse order*. - defp fill_tapscript_multi_keys(s, []), do: s - defp fill_tapscript_multi_keys(s, [last_key]) do + defp fill_tapscript_multisig_keys(s, []), do: s + + defp fill_tapscript_multisig_keys(s, [last_key]) do {:ok, s} = push_op(s, :op_checksig) {:ok, s} = push_data(s, Point.x_bytes(last_key)) s end - defp fill_tapscript_multi_keys(s, [key | rest]) do + + defp fill_tapscript_multisig_keys(s, [key | rest]) do {:ok, s} = push_op(s, :op_checksigadd) {:ok, s} = push_data(s, Point.x_bytes(key)) - fill_tapscript_multi_keys(s, rest) + fill_tapscript_multisig_keys(s, rest) + end + + # BIP67 + def lexicographical_sort_pubkeys(pubkeys) do + pubkeys + |> Enum.map(fn pubkey -> Point.sec(pubkey) |> :erlang.binary_to_list() end) + |> Enum.sort(&Utils.lexicographical_cmp/2) + |> Enum.map(fn bin_list -> + {:ok, pk} = + :erlang.list_to_binary(bin_list) + |> Point.parse_public_key() + + pk + end) end @doc """ create_p2sh_multisig returns both a P2SH-wrapped multisig script and the underlying raw multisig script using m and the list of public keys. """ - @spec create_p2sh_multisig(non_neg_integer(), list(Point.t())) :: + @spec create_p2sh_multisig(non_neg_integer(), list(Point.t()), list({:bip67_sort, boolean})) :: {:ok, t(), t()} | {:error, String.t()} - def create_p2sh_multisig(m, pubkeys) do - case create_multisig(m, pubkeys) do + def create_p2sh_multisig(m, pubkeys, opts \\ []) do + case create_multisig(m, pubkeys, opts) do {:ok, multisig} -> h160 = hash160(multisig) {:ok, p2sh} = create_p2sh(h160) @@ -589,10 +617,10 @@ defmodule Bitcoinex.Script do create_p2wsh_multisig returns both a P2WSH-wrapped multisig script and the underlying raw multisig script using m and the list of public keys. """ - @spec create_p2wsh_multisig(non_neg_integer(), list(Point.t())) :: + @spec create_p2wsh_multisig(non_neg_integer(), list(Point.t()), list({:bip67_sort, boolean})) :: {:ok, t(), t()} | {:error, String.t()} - def create_p2wsh_multisig(m, pubkeys) do - case create_multisig(m, pubkeys) do + def create_p2wsh_multisig(m, pubkeys, opts \\ []) do + case create_multisig(m, pubkeys, opts) do {:ok, multisig} -> h256 = sha256(multisig) {:ok, p2wsh} = create_p2wsh(h256) diff --git a/lib/utils.ex b/lib/utils.ex index 6380ba9..e5bf9c4 100644 --- a/lib/utils.ex +++ b/lib/utils.ex @@ -137,7 +137,7 @@ defmodule Bitcoinex.Utils do def lexicographical_cmp([b0 | r0], [b1 | r1]) do cond do b0 == b1 -> - lexicographical_sort(r0, r1) + lexicographical_cmp(r0, r1) b1 < b0 -> # initial order was incorrect, must be swapped diff --git a/test/script_test.exs b/test/script_test.exs index e361dfd..3df6567 100644 --- a/test/script_test.exs +++ b/test/script_test.exs @@ -34,6 +34,13 @@ defmodule Bitcoinex.ScriptTest do wsh_addr: "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej", sh_addr: "" }, + # from tx: a6e48d3b1c63598598c0f79886bdd059a9a472b071d34c706bedc2288d2cfdcf + %{ + script_hex: "52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae", + network: :mainnet, + wsh_addr: "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej", + sh_addr: "" + }, # from tx: 578e8ab81e6eff430fd37eaa460377387d75f0221881fe13cf9e5b400d98b0e3 %{ script_hex: @@ -52,6 +59,16 @@ defmodule Bitcoinex.ScriptTest do } ] + @raw_sorted_multisig_with_data [ + # from tx: a6e48d3b1c63598598c0f79886bdd059a9a472b071d34c706bedc2288d2cfdcf + %{ + script_hex: "52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae", + network: :mainnet, + wsh_addr: "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej", + sh_addr: "" + }, + ] + @p2pk_scripts [ # from tx df2b060fa2e5e9c8ed5eaf6a45c13753ec8c63282b2688322eba40cd98ea067a "4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac", @@ -409,6 +426,72 @@ defmodule Bitcoinex.ScriptTest do } ] + # https://github.com/bitcoin/bips/blob/master/bip-0067.mediawiki#test-vectors + @bip67_test_vectors_m 2 + @bip67_tests [ + %{ + pubkeys: [ + "02ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8", + "02fe6f0a5a297eb38c391581c4413e084773ea23954d93f7753db7dc0adc188b2f" + ], + sorted: [ + "02fe6f0a5a297eb38c391581c4413e084773ea23954d93f7753db7dc0adc188b2f", + "02ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8" + ], + script: + "522102fe6f0a5a297eb38c391581c4413e084773ea23954d93f7753db7dc0adc188b2f2102ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f852ae", + address: "39bgKC7RFbpoCRbtD5KEdkYKtNyhpsNa3Z" + }, + # (Already sorted, no action required) + %{ + pubkeys: [ + "02632b12f4ac5b1d1b72b2a3b508c19172de44f6f46bcee50ba33f3f9291e47ed0", + "027735a29bae7780a9755fae7a1c4374c656ac6a69ea9f3697fda61bb99a4f3e77", + "02e2cc6bd5f45edd43bebe7cb9b675f0ce9ed3efe613b177588290ad188d11b404" + ], + sorted: [ + "02632b12f4ac5b1d1b72b2a3b508c19172de44f6f46bcee50ba33f3f9291e47ed0", + "027735a29bae7780a9755fae7a1c4374c656ac6a69ea9f3697fda61bb99a4f3e77", + "02e2cc6bd5f45edd43bebe7cb9b675f0ce9ed3efe613b177588290ad188d11b404" + ], + script: + "522102632b12f4ac5b1d1b72b2a3b508c19172de44f6f46bcee50ba33f3f9291e47ed021027735a29bae7780a9755fae7a1c4374c656ac6a69ea9f3697fda61bb99a4f3e772102e2cc6bd5f45edd43bebe7cb9b675f0ce9ed3efe613b177588290ad188d11b40453ae", + address: "3CKHTjBKxCARLzwABMu9yD85kvtm7WnMfH" + }, + %{ + pubkeys: [ + "030000000000000000000000000000000000004141414141414141414141414141", + "020000000000000000000000000000000000004141414141414141414141414141", + "020000000000000000000000000000000000004141414141414141414141414140", + "030000000000000000000000000000000000004141414141414141414141414140" + ], + sorted: [ + "020000000000000000000000000000000000004141414141414141414141414140", + "020000000000000000000000000000000000004141414141414141414141414141", + "030000000000000000000000000000000000004141414141414141414141414140", + "030000000000000000000000000000000000004141414141414141414141414141" + ], + script: + "522102000000000000000000000000000000000000414141414141414141414141414021020000000000000000000000000000000000004141414141414141414141414141210300000000000000000000000000000000000041414141414141414141414141402103000000000000000000000000000000000000414141414141414141414141414154ae", + address: "32V85igBri9zcfBRVupVvwK18NFtS37FuD" + }, + %{ + pubkeys: [ + "022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da", + "03e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e9", + "021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc18" + ], + sorted: [ + "021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc18", + "022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da", + "03e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e9" + ], + script: + "5221021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc1821022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da2103e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e953ae", + address: "3Q4sF6tv9wsdqu2NtARzNCpQgwifm2rAba" + } + ] + describe "test basic functions" do test "test new/0 and empty?/1" do s = Script.new() @@ -1380,11 +1463,36 @@ defmodule Bitcoinex.ScriptTest do assert Script.to_address(s, :mainnet) == {:ok, addr} end - test "test raw multisig to address" do + test "test raw unsorted multisig to address" do for multi <- @raw_multisigs_with_data do {:ok, ms} = Script.parse_script(multi.script_hex) {:ok, m, pks} = Script.extract_multisig_policy(ms) + if multi.sh_addr != "" do + {:ok, p2sh, multi_script} = Script.create_p2sh_multisig(m, pks, bip67_sort: false) + + {:ok, addr} = Script.to_address(p2sh, multi.network) + + assert multi_script == ms + assert addr == multi.sh_addr + end + + if multi.wsh_addr != "" do + {:ok, p2wsh, multi_script} = Script.create_p2wsh_multisig(m, pks, bip67_sort: false) + + {:ok, addr} = Script.to_address(p2wsh, multi.network) + + assert multi_script == ms + assert addr == multi.wsh_addr + end + end + end + + test "raw sorted multisig to address" do + for multi <- @raw_sorted_multisig_with_data do + {:ok, ms} = Script.parse_script(multi.script_hex) + {:ok, m, pks} = Script.extract_multisig_policy(ms) + if multi.sh_addr != "" do {:ok, p2sh, multi_script} = Script.create_p2sh_multisig(m, pks) @@ -1534,7 +1642,7 @@ defmodule Bitcoinex.ScriptTest do for multi <- @raw_multisigs_with_data do {:ok, ms} = Script.parse_script(multi.script_hex) {:ok, m, pks} = Script.extract_multisig_policy(ms) - {:ok, ms2} = Script.create_multisig(m, pks) + {:ok, ms2} = Script.create_multisig(m, pks, bip67_sort: false) assert ms == ms2 end @@ -1542,7 +1650,7 @@ defmodule Bitcoinex.ScriptTest do for m <- @raw_multisig_scripts do {:ok, ms} = Script.parse_script(m) {:ok, m, pks} = Script.extract_multisig_policy(ms) - {:ok, ms2} = Script.create_multisig(m, pks) + {:ok, ms2} = Script.create_multisig(m, pks, bip67_sort: false) assert ms == ms2 end @@ -1602,4 +1710,32 @@ defmodule Bitcoinex.ScriptTest do end end end + + describe "bip 67" do + def parse_pk(pk) do + {:ok, pk} = Point.parse_public_key(pk) + pk + end + + test "sorting pubkeys" do + for t <- @bip67_tests do + pubkeys = Enum.map(t.pubkeys, &parse_pk/1) + sorted_pubkeys = Enum.map(t.sorted, &parse_pk/1) + + pubkeys = Script.lexicographical_sort_pubkeys(pubkeys) + + zip_list = Enum.zip(pubkeys, sorted_pubkeys) + Enum.each(zip_list, fn {pubkey, sorted_pubkey} -> assert pubkey == sorted_pubkey end) + end + end + + test "script creation" do + for t <- @bip67_tests do + pubkeys = Enum.map(t.pubkeys, &parse_pk/1) + {:ok, script, redeem_script} = Script.create_p2sh_multisig(@bip67_test_vectors_m, pubkeys) + assert Script.to_hex(redeem_script) == t.script + assert Script.to_address(script, :mainnet) == {:ok, t.address} + end + end + end end From e5c7554ec0189593513205839cbbc44f08a7cb7b Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sun, 5 Mar 2023 00:23:33 -0500 Subject: [PATCH 55/63] implement bip69 and add testing --- lib/transaction.ex | 39 ++++++++++++++++++++ test/transaction_test.exs | 75 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/lib/transaction.ex b/lib/transaction.ex index 6408b31..c819836 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -679,6 +679,7 @@ defmodule Bitcoinex.Transaction.In do remaining input = %In{ + # TODO fix this prev_txid: Base.encode16(<<:binary.decode_unsigned(prev_txid, :big)::little-size(256)>>, case: :lower), prev_vout: prev_vout, @@ -688,6 +689,26 @@ defmodule Bitcoinex.Transaction.In do parse(remaining, [input | inputs], count - 1) end + + def lexicographical_sort_inputs(inputs) do + Enum.sort(inputs, &lexicographical_cmp_inputs/2) + end + + def lexicographical_cmp_inputs(input1, input2) do + # compare txids then vouts + input1little_txid = Base.decode16!(input1.prev_txid, case: :lower) # |> Utils.flip_endianness() + input1bin = + input1little_txid <> <> + |> :erlang.binary_to_list() + + input2little_txid = Base.decode16!(input2.prev_txid, case: :lower) # |> Utils.flip_endianness() + input2bin = + input2little_txid <> <> + |> :erlang.binary_to_list() + + Utils.lexicographical_cmp(input1bin, input2bin) + end + end defmodule Bitcoinex.Transaction.Out do @@ -753,4 +774,22 @@ defmodule Bitcoinex.Transaction.Out do parse(remaining, [output | outputs], count - 1) end + + def lexicographical_sort_outputs(outputs) do + Enum.sort(outputs, &lexicographical_cmp_output/2) + end + + def lexicographical_cmp_output(o1, o2) do + # first compare amounts, then scriptpubkeys + cond do + o1.value < o2.value -> + true + o1.value > o2.value -> + false + o1.value == o2.value -> + o1spk = Base.decode16!(o1.script_pub_key) |> :erlang.binary_to_list() + o2spk = Base.decode16!(o2.script_pub_key) |> :erlang.binary_to_list() + Utils.lexicographical_cmp(o1spk, o2spk) + end + end end diff --git a/test/transaction_test.exs b/test/transaction_test.exs index e730f4d..79af24d 100644 --- a/test/transaction_test.exs +++ b/test/transaction_test.exs @@ -358,6 +358,54 @@ defmodule Bitcoinex.TransactionTest do } } + @bip69_txs [ + %{ + tx_hex: "0100000011aad553bb1650007e9982a8ac79d227cd8c831e1573b11f25573a37664e5f3e64000000006a47304402205438cedd30ee828b0938a863e08d810526123746c1f4abee5b7bc2312373450c02207f26914f4275f8f0040ab3375bacc8c5d610c095db8ed0785de5dc57456591a601210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffc26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f5485d1fde028000000006b483045022100f81d98c1de9bb61063a5e6671d191b400fda3a07d886e663799760393405439d0220234303c9af4bad3d665f00277fe70cdd26cd56679f114a40d9107249d29c979401210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff456a9e597129f5df2e11b842833fc19a94c563f57449281d3cd01249a830a1f0000000006a47304402202310b00924794ef68a8f09564fd0bb128838c66bc45d1a3f95c5cab52680f166022039fc99138c29f6c434012b14aca651b1c02d97324d6bd9dd0ffced0782c7e3bd01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff571fb3e02278217852dd5d299947e2b7354a639adc32ec1fa7b82cfb5dec530e000000006b483045022100d276251f1f4479d8521269ec8b1b45c6f0e779fcf1658ec627689fa8a55a9ca50220212a1e307e6182479818c543e1b47d62e4fc3ce6cc7fc78183c7071d245839df01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff5d8de50362ff33d3526ac3602e9ee25c1a349def086a7fc1d9941aaeb9e91d38010000006b4830450221008768eeb1240451c127b88d89047dd387d13357ce5496726fc7813edc6acd55ac022015187451c3fb66629af38fdb061dfb39899244b15c45e4a7ccc31064a059730d01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff60ad3408b89ea19caf3abd5e74e7a084344987c64b1563af52242e9d2a8320f3000000006b4830450221009be4261ec050ebf33fa3d47248c7086e4c247cafbb100ea7cee4aa81cd1383f5022008a70d6402b153560096c849d7da6fe61c771a60e41ff457aac30673ceceafee01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffe9b483a8ac4129780c88d1babe41e89dc10a26dedbf14f80a28474e9a11104de010000006b4830450221009bc40eee321b39b5dc26883f79cd1f5a226fc6eed9e79e21d828f4c23190c57e022078182fd6086e265589105023d9efa4cba83f38c674a499481bd54eee196b033f01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffe28db9462d3004e21e765e03a45ecb147f136a20ba8bca78ba60ebfc8e2f8b3b000000006a47304402200fb572b7c6916515452e370c2b6f97fcae54abe0793d804a5a53e419983fae1602205191984b6928bf4a1e25b00e5b5569a0ce1ecb82db2dea75fe4378673b53b9e801210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff7a1ef65ff1b7b7740c662ab6c9735ace4a16279c23a1db5709ed652918ffff54010000006a47304402206bc218a925f7280d615c8ea4f0131a9f26e7fc64cff6eeeb44edb88aba14f1910220779d5d67231bc2d2d93c3c5ab74dcd193dd3d04023e58709ad7ffbf95161be6201210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff850cecf958468ca7ffa6a490afe13b8c271b1326b0ddc1fdfdf9f3c7e365fdba000000006a473044022047df98cc26bd2bfdc5b2b97c27aead78a214810ff023e721339292d5ce50823d02205fe99dc5f667908974dae40cc7a9475af7fa6671ba44f64a00fcd01fa12ab523012102ca46fa75454650afba1784bc7b079d687e808634411e4beff1f70e44596308a1ffffffff8640e312040e476cf6727c60ca3f4a3ad51623500aacdda96e7728dbdd99e8a5000000006a47304402205566aa84d3d84226d5ab93e6f253b57b3ef37eb09bb73441dae35de86271352a02206ee0b7f800f73695a2073a2967c9ad99e19f6ddf18ce877adf822e408ba9291e01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff91c1889c5c24b93b56e643121f7a05a34c10c5495c450504c7b5afcb37e11d7a000000006b483045022100df61d45bbaa4571cdd6c5c822cba458cdc55285cdf7ba9cd5bb9fc18096deb9102201caf8c771204df7fd7c920c4489da7bc3a60e1d23c1a97e237c63afe53250b4a01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff2470947216eb81ea0eeeb4fe19362ec05767db01c3aa3006bb499e8b6d6eaa26010000006a473044022031501a0b2846b8822a32b9947b058d89d32fc758e009fc2130c2e5effc925af70220574ef3c9e350cef726c75114f0701fd8b188c6ec5f84adce0ed5c393828a5ae001210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff0abcd77d65cc14363f8262898335f184d6da5ad060ff9e40bf201741022c2b40010000006b483045022100a6ac110802b699f9a2bff0eea252d32e3d572b19214d49d8bb7405efa2af28f1022033b7563eb595f6d7ed7ec01734e17b505214fe0851352ed9c3c8120d53268e9a01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffa43bebbebf07452a893a95bfea1d5db338d23579be172fe803dce02eeb7c037d010000006b483045022100ebc77ed0f11d15fe630fe533dc350c2ddc1c81cfeb81d5a27d0587163f58a28c02200983b2a32a1014bab633bfc9258083ac282b79566b6b3fa45c1e6758610444f401210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffb102113fa46ce949616d9cda00f6b10231336b3928eaaac6bfe42d1bf3561d6c010000006a473044022010f8731929a55c1c49610722e965635529ed895b2292d781b183d465799906b20220098359adcbc669cd4b294cc129b110fe035d2f76517248f4b7129f3bf793d07f01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffb861fab2cde188499758346be46b5fbec635addfc4e7b0c8a07c0a908f2b11b4000000006a47304402207328142bb02ef5d6496a210300f4aea71f67683b842fa3df32cae6c88b49a9bb022020f56ddff5042260cfda2c9f39b7dec858cc2f4a76a987cd2dc25945b04e15fe01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff027064d817000000001976a9144a5fba237213a062f6f57978f796390bdcf8d01588ac00902f50090000001976a9145be32612930b8323add2212a4ec03c1562084f8488ac00000000", + ordered_inputs: [ + %{txid: "0e53ec5dfb2cb8a71fec32dc9a634a35b7e24799295ddd5278217822e0b31f57", + vout: 0}, + %{txid: "26aa6e6d8b9e49bb0630aac301db6757c02e3619feb4ee0eea81eb1672947024", + vout: 1}, + %{txid: "28e0fdd185542f2c6ea19030b0796051e7772b6026dd5ddccd7a2f93b73e6fc2", + vout: 0}, + %{txid: "381de9b9ae1a94d9c17f6a08ef9d341a5ce29e2e60c36a52d333ff6203e58d5d", + vout: 1}, + %{txid: "3b8b2f8efceb60ba78ca8bba206a137f14cb5ea4035e761ee204302d46b98de2", + vout: 0}, + %{txid: "402b2c02411720bf409eff60d05adad684f135838962823f3614cc657dd7bc0a", + vout: 1}, + %{txid: "54ffff182965ed0957dba1239c27164ace5a73c9b62a660c74b7b7f15ff61e7a", + vout: 1}, + %{txid: "643e5f4e66373a57251fb173151e838ccd27d279aca882997e005016bb53d5aa", + vout: 0}, + %{txid: "6c1d56f31b2de4bfc6aaea28396b333102b1f600da9c6d6149e96ca43f1102b1", + vout: 1}, + %{txid: "7a1de137cbafb5c70405455c49c5104ca3057a1f1243e6563bb9245c9c88c191", + vout: 0}, + %{txid: "7d037ceb2ee0dc03e82f17be7935d238b35d1deabf953a892a4507bfbeeb3ba4", + vout: 1}, + %{txid: "a5e899dddb28776ea9ddac0a502316d53a4a3fca607c72f66c470e0412e34086", + vout: 0}, + %{txid: "b4112b8f900a7ca0c8b0e7c4dfad35c6be5f6be46b3458974988e1cdb2fa61b8", + vout: 0}, + %{txid: "bafd65e3c7f3f9fdfdc1ddb026131b278c3be1af90a4a6ffa78c4658f9ec0c85", + vout: 0}, + %{txid: "de0411a1e97484a2804ff1dbde260ac19de841bebad1880c782941aca883b4e9", + vout: 1}, + %{txid: "f0a130a84912d03c1d284974f563c5949ac13f8342b8112edff52971599e6a45", + vout: 0}, + %{txid: "f320832a9d2e2452af63154bc687493484a0e7745ebd3aaf9ca19eb80834ad60", + vout: 0}, + ], + ordered_outputs: [ + %{value: 400057456, + scriptpubkey: "76a9144a5fba237213a062f6f57978f796390bdcf8d01588ac"}, + %{value: 40000000000, + scriptpubkey: "76a9145be32612930b8323add2212a4ec03c1562084f8488ac"} + ] + } + ] + describe "decode/1" do test "decodes legacy bitcoin transaction" do txn_test = @txn_serialization_1 @@ -725,4 +773,31 @@ defmodule Bitcoinex.TransactionTest do assert sighash2 == Utils.hex_to_bin(t.intermediary.sighash) end end + + describe "bip 69" do + test "input sorting" do + for t <- @bip69_txs do + {:ok, tx} = Transaction.decode(t.tx_hex) + + # randomly shuffle inputs then sort them again + for _ <- 1..10 do + rand_inputs = Enum.shuffle(tx.inputs) + sorted_inputs = Transaction.In.lexicographical_sort_inputs(rand_inputs) + zip_list = Enum.zip(sorted_inputs, t.ordered_inputs) + Enum.each(zip_list, fn {sorted_input, ordered_input} -> + assert sorted_input.prev_txid == ordered_input.txid + assert sorted_input.prev_vout == ordered_input.vout + end) + + shuffled_outputs = Enum.shuffle(tx.outputs) + sorted_outputs = Transaction.Out.lexicographical_sort_outputs(shuffled_outputs) + zip_list = Enum.zip(sorted_outputs, t.ordered_outputs) + Enum.each(zip_list, fn {sorted_output, ordered_output} -> + assert sorted_output.value == ordered_output.value + assert sorted_output.script_pub_key == ordered_output.scriptpubkey + end) + end + end + end + end end From de6b5bb8cd59fed76228d1a14c2d805b7920be8d Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sun, 5 Mar 2023 00:28:35 -0500 Subject: [PATCH 56/63] fmt and add second bip69 test --- lib/script.ex | 7 +++- lib/transaction.ex | 15 ++++--- test/script_test.exs | 8 ++-- test/transaction_test.exs | 85 +++++++++++++++++++++------------------ 4 files changed, 66 insertions(+), 49 deletions(-) diff --git a/lib/script.ex b/lib/script.ex index fa547b4..9cc2388 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -526,8 +526,10 @@ defmodule Bitcoinex.Script do this function sorts the pubkeys lexicographically, complying with BIP 67. For a non-compliant version of this function, use create_unsorted_multisig """ - @spec create_multisig(non_neg_integer(), list(Point.t()), list({:bip67_sort, boolean})) :: {:ok, t()} | {:error, String.t()} + @spec create_multisig(non_neg_integer(), list(Point.t()), list({:bip67_sort, boolean})) :: + {:ok, t()} | {:error, String.t()} def create_multisig(m, pubkeys, opts \\ []) + def create_multisig(m, pubkeys, opts) when is_valid_multisig(m, pubkeys) do pubkeys = if Keyword.get(opts, :bip67_sort, true) do @@ -547,7 +549,8 @@ defmodule Bitcoinex.Script do end end - def create_multisig(_, _, _), do: {:error, "invalid multisig: must be of form: (int, list(%Point)"} + def create_multisig(_, _, _), + do: {:error, "invalid multisig: must be of form: (int, list(%Point)"} defp fill_multisig_keys(s, []), do: s diff --git a/lib/transaction.ex b/lib/transaction.ex index c819836..9317072 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -696,19 +696,22 @@ defmodule Bitcoinex.Transaction.In do def lexicographical_cmp_inputs(input1, input2) do # compare txids then vouts - input1little_txid = Base.decode16!(input1.prev_txid, case: :lower) # |> Utils.flip_endianness() + # |> Utils.flip_endianness() + input1little_txid = Base.decode16!(input1.prev_txid, case: :lower) + input1bin = - input1little_txid <> <> + (input1little_txid <> <>) |> :erlang.binary_to_list() - input2little_txid = Base.decode16!(input2.prev_txid, case: :lower) # |> Utils.flip_endianness() + # |> Utils.flip_endianness() + input2little_txid = Base.decode16!(input2.prev_txid, case: :lower) + input2bin = - input2little_txid <> <> + (input2little_txid <> <>) |> :erlang.binary_to_list() Utils.lexicographical_cmp(input1bin, input2bin) end - end defmodule Bitcoinex.Transaction.Out do @@ -784,8 +787,10 @@ defmodule Bitcoinex.Transaction.Out do cond do o1.value < o2.value -> true + o1.value > o2.value -> false + o1.value == o2.value -> o1spk = Base.decode16!(o1.script_pub_key) |> :erlang.binary_to_list() o2spk = Base.decode16!(o2.script_pub_key) |> :erlang.binary_to_list() diff --git a/test/script_test.exs b/test/script_test.exs index 3df6567..8d9605b 100644 --- a/test/script_test.exs +++ b/test/script_test.exs @@ -36,7 +36,8 @@ defmodule Bitcoinex.ScriptTest do }, # from tx: a6e48d3b1c63598598c0f79886bdd059a9a472b071d34c706bedc2288d2cfdcf %{ - script_hex: "52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae", + script_hex: + "52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae", network: :mainnet, wsh_addr: "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej", sh_addr: "" @@ -62,11 +63,12 @@ defmodule Bitcoinex.ScriptTest do @raw_sorted_multisig_with_data [ # from tx: a6e48d3b1c63598598c0f79886bdd059a9a472b071d34c706bedc2288d2cfdcf %{ - script_hex: "52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae", + script_hex: + "52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae", network: :mainnet, wsh_addr: "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej", sh_addr: "" - }, + } ] @p2pk_scripts [ diff --git a/test/transaction_test.exs b/test/transaction_test.exs index 79af24d..86b439d 100644 --- a/test/transaction_test.exs +++ b/test/transaction_test.exs @@ -360,48 +360,53 @@ defmodule Bitcoinex.TransactionTest do @bip69_txs [ %{ - tx_hex: "0100000011aad553bb1650007e9982a8ac79d227cd8c831e1573b11f25573a37664e5f3e64000000006a47304402205438cedd30ee828b0938a863e08d810526123746c1f4abee5b7bc2312373450c02207f26914f4275f8f0040ab3375bacc8c5d610c095db8ed0785de5dc57456591a601210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffc26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f5485d1fde028000000006b483045022100f81d98c1de9bb61063a5e6671d191b400fda3a07d886e663799760393405439d0220234303c9af4bad3d665f00277fe70cdd26cd56679f114a40d9107249d29c979401210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff456a9e597129f5df2e11b842833fc19a94c563f57449281d3cd01249a830a1f0000000006a47304402202310b00924794ef68a8f09564fd0bb128838c66bc45d1a3f95c5cab52680f166022039fc99138c29f6c434012b14aca651b1c02d97324d6bd9dd0ffced0782c7e3bd01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff571fb3e02278217852dd5d299947e2b7354a639adc32ec1fa7b82cfb5dec530e000000006b483045022100d276251f1f4479d8521269ec8b1b45c6f0e779fcf1658ec627689fa8a55a9ca50220212a1e307e6182479818c543e1b47d62e4fc3ce6cc7fc78183c7071d245839df01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff5d8de50362ff33d3526ac3602e9ee25c1a349def086a7fc1d9941aaeb9e91d38010000006b4830450221008768eeb1240451c127b88d89047dd387d13357ce5496726fc7813edc6acd55ac022015187451c3fb66629af38fdb061dfb39899244b15c45e4a7ccc31064a059730d01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff60ad3408b89ea19caf3abd5e74e7a084344987c64b1563af52242e9d2a8320f3000000006b4830450221009be4261ec050ebf33fa3d47248c7086e4c247cafbb100ea7cee4aa81cd1383f5022008a70d6402b153560096c849d7da6fe61c771a60e41ff457aac30673ceceafee01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffe9b483a8ac4129780c88d1babe41e89dc10a26dedbf14f80a28474e9a11104de010000006b4830450221009bc40eee321b39b5dc26883f79cd1f5a226fc6eed9e79e21d828f4c23190c57e022078182fd6086e265589105023d9efa4cba83f38c674a499481bd54eee196b033f01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffe28db9462d3004e21e765e03a45ecb147f136a20ba8bca78ba60ebfc8e2f8b3b000000006a47304402200fb572b7c6916515452e370c2b6f97fcae54abe0793d804a5a53e419983fae1602205191984b6928bf4a1e25b00e5b5569a0ce1ecb82db2dea75fe4378673b53b9e801210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff7a1ef65ff1b7b7740c662ab6c9735ace4a16279c23a1db5709ed652918ffff54010000006a47304402206bc218a925f7280d615c8ea4f0131a9f26e7fc64cff6eeeb44edb88aba14f1910220779d5d67231bc2d2d93c3c5ab74dcd193dd3d04023e58709ad7ffbf95161be6201210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff850cecf958468ca7ffa6a490afe13b8c271b1326b0ddc1fdfdf9f3c7e365fdba000000006a473044022047df98cc26bd2bfdc5b2b97c27aead78a214810ff023e721339292d5ce50823d02205fe99dc5f667908974dae40cc7a9475af7fa6671ba44f64a00fcd01fa12ab523012102ca46fa75454650afba1784bc7b079d687e808634411e4beff1f70e44596308a1ffffffff8640e312040e476cf6727c60ca3f4a3ad51623500aacdda96e7728dbdd99e8a5000000006a47304402205566aa84d3d84226d5ab93e6f253b57b3ef37eb09bb73441dae35de86271352a02206ee0b7f800f73695a2073a2967c9ad99e19f6ddf18ce877adf822e408ba9291e01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff91c1889c5c24b93b56e643121f7a05a34c10c5495c450504c7b5afcb37e11d7a000000006b483045022100df61d45bbaa4571cdd6c5c822cba458cdc55285cdf7ba9cd5bb9fc18096deb9102201caf8c771204df7fd7c920c4489da7bc3a60e1d23c1a97e237c63afe53250b4a01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff2470947216eb81ea0eeeb4fe19362ec05767db01c3aa3006bb499e8b6d6eaa26010000006a473044022031501a0b2846b8822a32b9947b058d89d32fc758e009fc2130c2e5effc925af70220574ef3c9e350cef726c75114f0701fd8b188c6ec5f84adce0ed5c393828a5ae001210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff0abcd77d65cc14363f8262898335f184d6da5ad060ff9e40bf201741022c2b40010000006b483045022100a6ac110802b699f9a2bff0eea252d32e3d572b19214d49d8bb7405efa2af28f1022033b7563eb595f6d7ed7ec01734e17b505214fe0851352ed9c3c8120d53268e9a01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffa43bebbebf07452a893a95bfea1d5db338d23579be172fe803dce02eeb7c037d010000006b483045022100ebc77ed0f11d15fe630fe533dc350c2ddc1c81cfeb81d5a27d0587163f58a28c02200983b2a32a1014bab633bfc9258083ac282b79566b6b3fa45c1e6758610444f401210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffb102113fa46ce949616d9cda00f6b10231336b3928eaaac6bfe42d1bf3561d6c010000006a473044022010f8731929a55c1c49610722e965635529ed895b2292d781b183d465799906b20220098359adcbc669cd4b294cc129b110fe035d2f76517248f4b7129f3bf793d07f01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffb861fab2cde188499758346be46b5fbec635addfc4e7b0c8a07c0a908f2b11b4000000006a47304402207328142bb02ef5d6496a210300f4aea71f67683b842fa3df32cae6c88b49a9bb022020f56ddff5042260cfda2c9f39b7dec858cc2f4a76a987cd2dc25945b04e15fe01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff027064d817000000001976a9144a5fba237213a062f6f57978f796390bdcf8d01588ac00902f50090000001976a9145be32612930b8323add2212a4ec03c1562084f8488ac00000000", + tx_hex: + "0100000011aad553bb1650007e9982a8ac79d227cd8c831e1573b11f25573a37664e5f3e64000000006a47304402205438cedd30ee828b0938a863e08d810526123746c1f4abee5b7bc2312373450c02207f26914f4275f8f0040ab3375bacc8c5d610c095db8ed0785de5dc57456591a601210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffc26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f5485d1fde028000000006b483045022100f81d98c1de9bb61063a5e6671d191b400fda3a07d886e663799760393405439d0220234303c9af4bad3d665f00277fe70cdd26cd56679f114a40d9107249d29c979401210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff456a9e597129f5df2e11b842833fc19a94c563f57449281d3cd01249a830a1f0000000006a47304402202310b00924794ef68a8f09564fd0bb128838c66bc45d1a3f95c5cab52680f166022039fc99138c29f6c434012b14aca651b1c02d97324d6bd9dd0ffced0782c7e3bd01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff571fb3e02278217852dd5d299947e2b7354a639adc32ec1fa7b82cfb5dec530e000000006b483045022100d276251f1f4479d8521269ec8b1b45c6f0e779fcf1658ec627689fa8a55a9ca50220212a1e307e6182479818c543e1b47d62e4fc3ce6cc7fc78183c7071d245839df01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff5d8de50362ff33d3526ac3602e9ee25c1a349def086a7fc1d9941aaeb9e91d38010000006b4830450221008768eeb1240451c127b88d89047dd387d13357ce5496726fc7813edc6acd55ac022015187451c3fb66629af38fdb061dfb39899244b15c45e4a7ccc31064a059730d01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff60ad3408b89ea19caf3abd5e74e7a084344987c64b1563af52242e9d2a8320f3000000006b4830450221009be4261ec050ebf33fa3d47248c7086e4c247cafbb100ea7cee4aa81cd1383f5022008a70d6402b153560096c849d7da6fe61c771a60e41ff457aac30673ceceafee01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffe9b483a8ac4129780c88d1babe41e89dc10a26dedbf14f80a28474e9a11104de010000006b4830450221009bc40eee321b39b5dc26883f79cd1f5a226fc6eed9e79e21d828f4c23190c57e022078182fd6086e265589105023d9efa4cba83f38c674a499481bd54eee196b033f01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffe28db9462d3004e21e765e03a45ecb147f136a20ba8bca78ba60ebfc8e2f8b3b000000006a47304402200fb572b7c6916515452e370c2b6f97fcae54abe0793d804a5a53e419983fae1602205191984b6928bf4a1e25b00e5b5569a0ce1ecb82db2dea75fe4378673b53b9e801210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff7a1ef65ff1b7b7740c662ab6c9735ace4a16279c23a1db5709ed652918ffff54010000006a47304402206bc218a925f7280d615c8ea4f0131a9f26e7fc64cff6eeeb44edb88aba14f1910220779d5d67231bc2d2d93c3c5ab74dcd193dd3d04023e58709ad7ffbf95161be6201210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff850cecf958468ca7ffa6a490afe13b8c271b1326b0ddc1fdfdf9f3c7e365fdba000000006a473044022047df98cc26bd2bfdc5b2b97c27aead78a214810ff023e721339292d5ce50823d02205fe99dc5f667908974dae40cc7a9475af7fa6671ba44f64a00fcd01fa12ab523012102ca46fa75454650afba1784bc7b079d687e808634411e4beff1f70e44596308a1ffffffff8640e312040e476cf6727c60ca3f4a3ad51623500aacdda96e7728dbdd99e8a5000000006a47304402205566aa84d3d84226d5ab93e6f253b57b3ef37eb09bb73441dae35de86271352a02206ee0b7f800f73695a2073a2967c9ad99e19f6ddf18ce877adf822e408ba9291e01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff91c1889c5c24b93b56e643121f7a05a34c10c5495c450504c7b5afcb37e11d7a000000006b483045022100df61d45bbaa4571cdd6c5c822cba458cdc55285cdf7ba9cd5bb9fc18096deb9102201caf8c771204df7fd7c920c4489da7bc3a60e1d23c1a97e237c63afe53250b4a01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff2470947216eb81ea0eeeb4fe19362ec05767db01c3aa3006bb499e8b6d6eaa26010000006a473044022031501a0b2846b8822a32b9947b058d89d32fc758e009fc2130c2e5effc925af70220574ef3c9e350cef726c75114f0701fd8b188c6ec5f84adce0ed5c393828a5ae001210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff0abcd77d65cc14363f8262898335f184d6da5ad060ff9e40bf201741022c2b40010000006b483045022100a6ac110802b699f9a2bff0eea252d32e3d572b19214d49d8bb7405efa2af28f1022033b7563eb595f6d7ed7ec01734e17b505214fe0851352ed9c3c8120d53268e9a01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffa43bebbebf07452a893a95bfea1d5db338d23579be172fe803dce02eeb7c037d010000006b483045022100ebc77ed0f11d15fe630fe533dc350c2ddc1c81cfeb81d5a27d0587163f58a28c02200983b2a32a1014bab633bfc9258083ac282b79566b6b3fa45c1e6758610444f401210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffb102113fa46ce949616d9cda00f6b10231336b3928eaaac6bfe42d1bf3561d6c010000006a473044022010f8731929a55c1c49610722e965635529ed895b2292d781b183d465799906b20220098359adcbc669cd4b294cc129b110fe035d2f76517248f4b7129f3bf793d07f01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffb861fab2cde188499758346be46b5fbec635addfc4e7b0c8a07c0a908f2b11b4000000006a47304402207328142bb02ef5d6496a210300f4aea71f67683b842fa3df32cae6c88b49a9bb022020f56ddff5042260cfda2c9f39b7dec858cc2f4a76a987cd2dc25945b04e15fe01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff027064d817000000001976a9144a5fba237213a062f6f57978f796390bdcf8d01588ac00902f50090000001976a9145be32612930b8323add2212a4ec03c1562084f8488ac00000000", ordered_inputs: [ - %{txid: "0e53ec5dfb2cb8a71fec32dc9a634a35b7e24799295ddd5278217822e0b31f57", - vout: 0}, - %{txid: "26aa6e6d8b9e49bb0630aac301db6757c02e3619feb4ee0eea81eb1672947024", - vout: 1}, - %{txid: "28e0fdd185542f2c6ea19030b0796051e7772b6026dd5ddccd7a2f93b73e6fc2", - vout: 0}, - %{txid: "381de9b9ae1a94d9c17f6a08ef9d341a5ce29e2e60c36a52d333ff6203e58d5d", - vout: 1}, - %{txid: "3b8b2f8efceb60ba78ca8bba206a137f14cb5ea4035e761ee204302d46b98de2", - vout: 0}, - %{txid: "402b2c02411720bf409eff60d05adad684f135838962823f3614cc657dd7bc0a", - vout: 1}, - %{txid: "54ffff182965ed0957dba1239c27164ace5a73c9b62a660c74b7b7f15ff61e7a", - vout: 1}, - %{txid: "643e5f4e66373a57251fb173151e838ccd27d279aca882997e005016bb53d5aa", - vout: 0}, - %{txid: "6c1d56f31b2de4bfc6aaea28396b333102b1f600da9c6d6149e96ca43f1102b1", - vout: 1}, - %{txid: "7a1de137cbafb5c70405455c49c5104ca3057a1f1243e6563bb9245c9c88c191", - vout: 0}, - %{txid: "7d037ceb2ee0dc03e82f17be7935d238b35d1deabf953a892a4507bfbeeb3ba4", - vout: 1}, - %{txid: "a5e899dddb28776ea9ddac0a502316d53a4a3fca607c72f66c470e0412e34086", - vout: 0}, - %{txid: "b4112b8f900a7ca0c8b0e7c4dfad35c6be5f6be46b3458974988e1cdb2fa61b8", - vout: 0}, - %{txid: "bafd65e3c7f3f9fdfdc1ddb026131b278c3be1af90a4a6ffa78c4658f9ec0c85", - vout: 0}, - %{txid: "de0411a1e97484a2804ff1dbde260ac19de841bebad1880c782941aca883b4e9", - vout: 1}, - %{txid: "f0a130a84912d03c1d284974f563c5949ac13f8342b8112edff52971599e6a45", - vout: 0}, - %{txid: "f320832a9d2e2452af63154bc687493484a0e7745ebd3aaf9ca19eb80834ad60", - vout: 0}, + %{txid: "0e53ec5dfb2cb8a71fec32dc9a634a35b7e24799295ddd5278217822e0b31f57", vout: 0}, + %{txid: "26aa6e6d8b9e49bb0630aac301db6757c02e3619feb4ee0eea81eb1672947024", vout: 1}, + %{txid: "28e0fdd185542f2c6ea19030b0796051e7772b6026dd5ddccd7a2f93b73e6fc2", vout: 0}, + %{txid: "381de9b9ae1a94d9c17f6a08ef9d341a5ce29e2e60c36a52d333ff6203e58d5d", vout: 1}, + %{txid: "3b8b2f8efceb60ba78ca8bba206a137f14cb5ea4035e761ee204302d46b98de2", vout: 0}, + %{txid: "402b2c02411720bf409eff60d05adad684f135838962823f3614cc657dd7bc0a", vout: 1}, + %{txid: "54ffff182965ed0957dba1239c27164ace5a73c9b62a660c74b7b7f15ff61e7a", vout: 1}, + %{txid: "643e5f4e66373a57251fb173151e838ccd27d279aca882997e005016bb53d5aa", vout: 0}, + %{txid: "6c1d56f31b2de4bfc6aaea28396b333102b1f600da9c6d6149e96ca43f1102b1", vout: 1}, + %{txid: "7a1de137cbafb5c70405455c49c5104ca3057a1f1243e6563bb9245c9c88c191", vout: 0}, + %{txid: "7d037ceb2ee0dc03e82f17be7935d238b35d1deabf953a892a4507bfbeeb3ba4", vout: 1}, + %{txid: "a5e899dddb28776ea9ddac0a502316d53a4a3fca607c72f66c470e0412e34086", vout: 0}, + %{txid: "b4112b8f900a7ca0c8b0e7c4dfad35c6be5f6be46b3458974988e1cdb2fa61b8", vout: 0}, + %{txid: "bafd65e3c7f3f9fdfdc1ddb026131b278c3be1af90a4a6ffa78c4658f9ec0c85", vout: 0}, + %{txid: "de0411a1e97484a2804ff1dbde260ac19de841bebad1880c782941aca883b4e9", vout: 1}, + %{txid: "f0a130a84912d03c1d284974f563c5949ac13f8342b8112edff52971599e6a45", vout: 0}, + %{txid: "f320832a9d2e2452af63154bc687493484a0e7745ebd3aaf9ca19eb80834ad60", vout: 0} ], ordered_outputs: [ - %{value: 400057456, - scriptpubkey: "76a9144a5fba237213a062f6f57978f796390bdcf8d01588ac"}, - %{value: 40000000000, - scriptpubkey: "76a9145be32612930b8323add2212a4ec03c1562084f8488ac"} + %{value: 400_057_456, scriptpubkey: "76a9144a5fba237213a062f6f57978f796390bdcf8d01588ac"}, + %{ + value: 40_000_000_000, + scriptpubkey: "76a9145be32612930b8323add2212a4ec03c1562084f8488ac" + } + ] + }, + %{ + tx_hex: + "010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d28350000000049483045022100aa46504baa86df8a33b1192b1b9367b4d729dc41e389f2c04f3e5c7f0559aae702205e82253a54bf5c4f65b7428551554b2045167d6d206dfe6a2e198127d3f7df1501ffffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000004847304402202329484c35fa9d6bb32a55a70c0982f606ce0e3634b69006138683bcd12cbb6602200c28feb1e2555c3210f1dddb299738b4ff8bbe9667b68cb8764b5ac17b7adf0001ffffffff0200e1f505000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00180d8f000000004341044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac00000000", + ordered_inputs: [ + %{txid: "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055", vout: 0}, + %{txid: "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055", vout: 1} + ], + ordered_outputs: [ + %{ + value: 100_000_000, + scriptpubkey: + "41046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac" + }, + %{ + value: 2_400_000_000, + scriptpubkey: + "41044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac" + } ] } ] @@ -784,6 +789,7 @@ defmodule Bitcoinex.TransactionTest do rand_inputs = Enum.shuffle(tx.inputs) sorted_inputs = Transaction.In.lexicographical_sort_inputs(rand_inputs) zip_list = Enum.zip(sorted_inputs, t.ordered_inputs) + Enum.each(zip_list, fn {sorted_input, ordered_input} -> assert sorted_input.prev_txid == ordered_input.txid assert sorted_input.prev_vout == ordered_input.vout @@ -792,6 +798,7 @@ defmodule Bitcoinex.TransactionTest do shuffled_outputs = Enum.shuffle(tx.outputs) sorted_outputs = Transaction.Out.lexicographical_sort_outputs(shuffled_outputs) zip_list = Enum.zip(sorted_outputs, t.ordered_outputs) + Enum.each(zip_list, fn {sorted_output, ordered_output} -> assert sorted_output.value == ordered_output.value assert sorted_output.script_pub_key == ordered_output.scriptpubkey From 3c2599d6d64045018466094b71f6a3fd59907c68 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Sun, 5 Mar 2023 09:15:55 -0500 Subject: [PATCH 57/63] fix spec --- lib/script.ex | 2 +- test/transaction_test.exs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/script.ex b/lib/script.ex index 9cc2388..6bc5a02 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -161,7 +161,7 @@ defmodule Bitcoinex.Script do end end - @spec push_num(t(), non_neg_integer()) :: t() + @spec push_num(t(), non_neg_integer()) :: {:ok, t()} def push_num(%__MODULE__{items: stack}, num) when num >= 0 and num <= 16 do op_num = num_to_op_num(num) {:ok, %__MODULE__{items: [op_num | stack]}} diff --git a/test/transaction_test.exs b/test/transaction_test.exs index 86b439d..6ab3861 100644 --- a/test/transaction_test.exs +++ b/test/transaction_test.exs @@ -393,7 +393,10 @@ defmodule Bitcoinex.TransactionTest do tx_hex: "010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d28350000000049483045022100aa46504baa86df8a33b1192b1b9367b4d729dc41e389f2c04f3e5c7f0559aae702205e82253a54bf5c4f65b7428551554b2045167d6d206dfe6a2e198127d3f7df1501ffffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000004847304402202329484c35fa9d6bb32a55a70c0982f606ce0e3634b69006138683bcd12cbb6602200c28feb1e2555c3210f1dddb299738b4ff8bbe9667b68cb8764b5ac17b7adf0001ffffffff0200e1f505000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00180d8f000000004341044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac00000000", ordered_inputs: [ - %{txid: "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055", vout: 0}, + %{ + txid: "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055", + vout: 0 + }, %{txid: "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055", vout: 1} ], ordered_outputs: [ From ad4f19de5402ae945176bf840e412cb94c01df10 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Mon, 6 Mar 2023 23:14:53 -0500 Subject: [PATCH 58/63] refactor sorting for multisig --- lib/script.ex | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/script.ex b/lib/script.ex index 6bc5a02..91610d9 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -531,12 +531,7 @@ defmodule Bitcoinex.Script do def create_multisig(m, pubkeys, opts \\ []) def create_multisig(m, pubkeys, opts) when is_valid_multisig(m, pubkeys) do - pubkeys = - if Keyword.get(opts, :bip67_sort, true) do - lexicographical_sort_pubkeys(pubkeys) - else - pubkeys - end + pubkeys = sort_pubkeys(pubkeys, opts) try do # checkmultisig @@ -561,11 +556,11 @@ defmodule Bitcoinex.Script do defp fill_multisig_keys(_, _), do: raise(ArgumentError) - @spec create_tapscript_multisig(non_neg_integer(), list(Point.t())) :: Script.t() - def create_tapscript_multisig(m, pubkeys) when is_valid_multisig(m, pubkeys) do + @spec create_tapscript_multisig(non_neg_integer(), list(Point.t()), list({:bip67_sort, boolean})) :: t() + def create_tapscript_multisig(m, pubkeys, opts \\ []) when is_valid_multisig(m, pubkeys) do + pubkeys = sort_pubkeys(pubkeys, opts) {:ok, s} = push_op(new(), :op_numequal) {:ok, s} = push_num(s, m) - pubkeys = lexicographical_sort_pubkeys(pubkeys) fill_tapscript_multisig_keys(s, Enum.reverse(pubkeys)) end @@ -584,6 +579,14 @@ defmodule Bitcoinex.Script do fill_tapscript_multisig_keys(s, rest) end + def sort_pubkeys(pubkeys, opts) do + if Keyword.get(opts, :bip67_sort, true) do + lexicographical_sort_pubkeys(pubkeys) + else + pubkeys + end + end + # BIP67 def lexicographical_sort_pubkeys(pubkeys) do pubkeys From 6f42ab9dcf2e94c0cfc09712ea16310a3560f8ca Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Mon, 6 Mar 2023 23:16:32 -0500 Subject: [PATCH 59/63] rm excess --- lib/transaction.ex | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/transaction.ex b/lib/transaction.ex index 9317072..730a37b 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -696,16 +696,12 @@ defmodule Bitcoinex.Transaction.In do def lexicographical_cmp_inputs(input1, input2) do # compare txids then vouts - # |> Utils.flip_endianness() input1little_txid = Base.decode16!(input1.prev_txid, case: :lower) - input1bin = (input1little_txid <> <>) |> :erlang.binary_to_list() - # |> Utils.flip_endianness() input2little_txid = Base.decode16!(input2.prev_txid, case: :lower) - input2bin = (input2little_txid <> <>) |> :erlang.binary_to_list() From d065db9a0cfe2f138703e009f257dd2aed0f5f17 Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Mon, 6 Mar 2023 23:29:45 -0500 Subject: [PATCH 60/63] fmt --- lib/script.ex | 6 +++++- lib/transaction.ex | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/script.ex b/lib/script.ex index 91610d9..c696299 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -556,7 +556,11 @@ defmodule Bitcoinex.Script do defp fill_multisig_keys(_, _), do: raise(ArgumentError) - @spec create_tapscript_multisig(non_neg_integer(), list(Point.t()), list({:bip67_sort, boolean})) :: t() + @spec create_tapscript_multisig( + non_neg_integer(), + list(Point.t()), + list({:bip67_sort, boolean}) + ) :: t() def create_tapscript_multisig(m, pubkeys, opts \\ []) when is_valid_multisig(m, pubkeys) do pubkeys = sort_pubkeys(pubkeys, opts) {:ok, s} = push_op(new(), :op_numequal) diff --git a/lib/transaction.ex b/lib/transaction.ex index 730a37b..1dd8763 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -697,11 +697,13 @@ defmodule Bitcoinex.Transaction.In do def lexicographical_cmp_inputs(input1, input2) do # compare txids then vouts input1little_txid = Base.decode16!(input1.prev_txid, case: :lower) + input1bin = (input1little_txid <> <>) |> :erlang.binary_to_list() input2little_txid = Base.decode16!(input2.prev_txid, case: :lower) + input2bin = (input2little_txid <> <>) |> :erlang.binary_to_list() From e64c0e408daa10f97d3f87cea9dbc6cbf51abed2 Mon Sep 17 00:00:00 2001 From: Sachin Meier <46731461+SachinMeier@users.noreply.github.com> Date: Tue, 7 Mar 2023 23:18:51 -0500 Subject: [PATCH 61/63] Add tapscript multisig helpers & BIP 67 & BIP 69 (#15) * add taproot multisig script construction * taproot_multisig -> tapscript_multisig * implement bip67 * implement bip69 and add testing * fmt and add second bip69 test * refactor sorting for multisig --- lib/script.ex | 87 ++++++++++++++++++++--- lib/transaction.ex | 42 +++++++++++ lib/utils.ex | 2 +- test/script_test.exs | 144 +++++++++++++++++++++++++++++++++++++- test/transaction_test.exs | 85 ++++++++++++++++++++++ 5 files changed, 347 insertions(+), 13 deletions(-) diff --git a/lib/script.ex b/lib/script.ex index e480bb2..c696299 100644 --- a/lib/script.ex +++ b/lib/script.ex @@ -161,6 +161,18 @@ defmodule Bitcoinex.Script do end end + @spec push_num(t(), non_neg_integer()) :: {:ok, t()} + def push_num(%__MODULE__{items: stack}, num) when num >= 0 and num <= 16 do + op_num = num_to_op_num(num) + {:ok, %__MODULE__{items: [op_num | stack]}} + end + + def num_to_op_num(0), do: 0 + + def num_to_op_num(num) when num > 0 and num <= 16 do + 0x50 + num + end + # SERIALIZE & PARSE defp serializer(%__MODULE__{items: []}, acc), do: acc @@ -511,9 +523,16 @@ defmodule Bitcoinex.Script do @doc """ create_multisig creates a raw multisig script using m and the list of public keys. + this function sorts the pubkeys lexicographically, complying with BIP 67. For a non-compliant + version of this function, use create_unsorted_multisig """ - @spec create_multisig(non_neg_integer(), list(Point.t())) :: {:ok, t()} | {:error, String.t()} - def create_multisig(m, pubkeys) when is_valid_multisig(m, pubkeys) do + @spec create_multisig(non_neg_integer(), list(Point.t()), list({:bip67_sort, boolean})) :: + {:ok, t()} | {:error, String.t()} + def create_multisig(m, pubkeys, opts \\ []) + + def create_multisig(m, pubkeys, opts) when is_valid_multisig(m, pubkeys) do + pubkeys = sort_pubkeys(pubkeys, opts) + try do # checkmultisig {:ok, s} = push_op(new(), 0xAE) @@ -525,7 +544,8 @@ defmodule Bitcoinex.Script do end end - def create_multisig(_, _), do: {:error, "invalid multisig: must be of form: (int, list(%Point)"} + def create_multisig(_, _, _), + do: {:error, "invalid multisig: must be of form: (int, list(%Point)"} defp fill_multisig_keys(s, []), do: s @@ -536,14 +556,63 @@ defmodule Bitcoinex.Script do defp fill_multisig_keys(_, _), do: raise(ArgumentError) + @spec create_tapscript_multisig( + non_neg_integer(), + list(Point.t()), + list({:bip67_sort, boolean}) + ) :: t() + def create_tapscript_multisig(m, pubkeys, opts \\ []) when is_valid_multisig(m, pubkeys) do + pubkeys = sort_pubkeys(pubkeys, opts) + {:ok, s} = push_op(new(), :op_numequal) + {:ok, s} = push_num(s, m) + fill_tapscript_multisig_keys(s, Enum.reverse(pubkeys)) + end + + # creates a script using the pubkeys *in reverse order*. + defp fill_tapscript_multisig_keys(s, []), do: s + + defp fill_tapscript_multisig_keys(s, [last_key]) do + {:ok, s} = push_op(s, :op_checksig) + {:ok, s} = push_data(s, Point.x_bytes(last_key)) + s + end + + defp fill_tapscript_multisig_keys(s, [key | rest]) do + {:ok, s} = push_op(s, :op_checksigadd) + {:ok, s} = push_data(s, Point.x_bytes(key)) + fill_tapscript_multisig_keys(s, rest) + end + + def sort_pubkeys(pubkeys, opts) do + if Keyword.get(opts, :bip67_sort, true) do + lexicographical_sort_pubkeys(pubkeys) + else + pubkeys + end + end + + # BIP67 + def lexicographical_sort_pubkeys(pubkeys) do + pubkeys + |> Enum.map(fn pubkey -> Point.sec(pubkey) |> :erlang.binary_to_list() end) + |> Enum.sort(&Utils.lexicographical_cmp/2) + |> Enum.map(fn bin_list -> + {:ok, pk} = + :erlang.list_to_binary(bin_list) + |> Point.parse_public_key() + + pk + end) + end + @doc """ create_p2sh_multisig returns both a P2SH-wrapped multisig script and the underlying raw multisig script using m and the list of public keys. """ - @spec create_p2sh_multisig(non_neg_integer(), list(Point.t())) :: + @spec create_p2sh_multisig(non_neg_integer(), list(Point.t()), list({:bip67_sort, boolean})) :: {:ok, t(), t()} | {:error, String.t()} - def create_p2sh_multisig(m, pubkeys) do - case create_multisig(m, pubkeys) do + def create_p2sh_multisig(m, pubkeys, opts \\ []) do + case create_multisig(m, pubkeys, opts) do {:ok, multisig} -> h160 = hash160(multisig) {:ok, p2sh} = create_p2sh(h160) @@ -558,10 +627,10 @@ defmodule Bitcoinex.Script do create_p2wsh_multisig returns both a P2WSH-wrapped multisig script and the underlying raw multisig script using m and the list of public keys. """ - @spec create_p2wsh_multisig(non_neg_integer(), list(Point.t())) :: + @spec create_p2wsh_multisig(non_neg_integer(), list(Point.t()), list({:bip67_sort, boolean})) :: {:ok, t(), t()} | {:error, String.t()} - def create_p2wsh_multisig(m, pubkeys) do - case create_multisig(m, pubkeys) do + def create_p2wsh_multisig(m, pubkeys, opts \\ []) do + case create_multisig(m, pubkeys, opts) do {:ok, multisig} -> h256 = sha256(multisig) {:ok, p2wsh} = create_p2wsh(h256) diff --git a/lib/transaction.ex b/lib/transaction.ex index 6408b31..1dd8763 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -679,6 +679,7 @@ defmodule Bitcoinex.Transaction.In do remaining input = %In{ + # TODO fix this prev_txid: Base.encode16(<<:binary.decode_unsigned(prev_txid, :big)::little-size(256)>>, case: :lower), prev_vout: prev_vout, @@ -688,6 +689,27 @@ defmodule Bitcoinex.Transaction.In do parse(remaining, [input | inputs], count - 1) end + + def lexicographical_sort_inputs(inputs) do + Enum.sort(inputs, &lexicographical_cmp_inputs/2) + end + + def lexicographical_cmp_inputs(input1, input2) do + # compare txids then vouts + input1little_txid = Base.decode16!(input1.prev_txid, case: :lower) + + input1bin = + (input1little_txid <> <>) + |> :erlang.binary_to_list() + + input2little_txid = Base.decode16!(input2.prev_txid, case: :lower) + + input2bin = + (input2little_txid <> <>) + |> :erlang.binary_to_list() + + Utils.lexicographical_cmp(input1bin, input2bin) + end end defmodule Bitcoinex.Transaction.Out do @@ -753,4 +775,24 @@ defmodule Bitcoinex.Transaction.Out do parse(remaining, [output | outputs], count - 1) end + + def lexicographical_sort_outputs(outputs) do + Enum.sort(outputs, &lexicographical_cmp_output/2) + end + + def lexicographical_cmp_output(o1, o2) do + # first compare amounts, then scriptpubkeys + cond do + o1.value < o2.value -> + true + + o1.value > o2.value -> + false + + o1.value == o2.value -> + o1spk = Base.decode16!(o1.script_pub_key) |> :erlang.binary_to_list() + o2spk = Base.decode16!(o2.script_pub_key) |> :erlang.binary_to_list() + Utils.lexicographical_cmp(o1spk, o2spk) + end + end end diff --git a/lib/utils.ex b/lib/utils.ex index 6380ba9..e5bf9c4 100644 --- a/lib/utils.ex +++ b/lib/utils.ex @@ -137,7 +137,7 @@ defmodule Bitcoinex.Utils do def lexicographical_cmp([b0 | r0], [b1 | r1]) do cond do b0 == b1 -> - lexicographical_sort(r0, r1) + lexicographical_cmp(r0, r1) b1 < b0 -> # initial order was incorrect, must be swapped diff --git a/test/script_test.exs b/test/script_test.exs index e361dfd..8d9605b 100644 --- a/test/script_test.exs +++ b/test/script_test.exs @@ -34,6 +34,14 @@ defmodule Bitcoinex.ScriptTest do wsh_addr: "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej", sh_addr: "" }, + # from tx: a6e48d3b1c63598598c0f79886bdd059a9a472b071d34c706bedc2288d2cfdcf + %{ + script_hex: + "52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae", + network: :mainnet, + wsh_addr: "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej", + sh_addr: "" + }, # from tx: 578e8ab81e6eff430fd37eaa460377387d75f0221881fe13cf9e5b400d98b0e3 %{ script_hex: @@ -52,6 +60,17 @@ defmodule Bitcoinex.ScriptTest do } ] + @raw_sorted_multisig_with_data [ + # from tx: a6e48d3b1c63598598c0f79886bdd059a9a472b071d34c706bedc2288d2cfdcf + %{ + script_hex: + "52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae", + network: :mainnet, + wsh_addr: "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej", + sh_addr: "" + } + ] + @p2pk_scripts [ # from tx df2b060fa2e5e9c8ed5eaf6a45c13753ec8c63282b2688322eba40cd98ea067a "4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac", @@ -409,6 +428,72 @@ defmodule Bitcoinex.ScriptTest do } ] + # https://github.com/bitcoin/bips/blob/master/bip-0067.mediawiki#test-vectors + @bip67_test_vectors_m 2 + @bip67_tests [ + %{ + pubkeys: [ + "02ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8", + "02fe6f0a5a297eb38c391581c4413e084773ea23954d93f7753db7dc0adc188b2f" + ], + sorted: [ + "02fe6f0a5a297eb38c391581c4413e084773ea23954d93f7753db7dc0adc188b2f", + "02ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8" + ], + script: + "522102fe6f0a5a297eb38c391581c4413e084773ea23954d93f7753db7dc0adc188b2f2102ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f852ae", + address: "39bgKC7RFbpoCRbtD5KEdkYKtNyhpsNa3Z" + }, + # (Already sorted, no action required) + %{ + pubkeys: [ + "02632b12f4ac5b1d1b72b2a3b508c19172de44f6f46bcee50ba33f3f9291e47ed0", + "027735a29bae7780a9755fae7a1c4374c656ac6a69ea9f3697fda61bb99a4f3e77", + "02e2cc6bd5f45edd43bebe7cb9b675f0ce9ed3efe613b177588290ad188d11b404" + ], + sorted: [ + "02632b12f4ac5b1d1b72b2a3b508c19172de44f6f46bcee50ba33f3f9291e47ed0", + "027735a29bae7780a9755fae7a1c4374c656ac6a69ea9f3697fda61bb99a4f3e77", + "02e2cc6bd5f45edd43bebe7cb9b675f0ce9ed3efe613b177588290ad188d11b404" + ], + script: + "522102632b12f4ac5b1d1b72b2a3b508c19172de44f6f46bcee50ba33f3f9291e47ed021027735a29bae7780a9755fae7a1c4374c656ac6a69ea9f3697fda61bb99a4f3e772102e2cc6bd5f45edd43bebe7cb9b675f0ce9ed3efe613b177588290ad188d11b40453ae", + address: "3CKHTjBKxCARLzwABMu9yD85kvtm7WnMfH" + }, + %{ + pubkeys: [ + "030000000000000000000000000000000000004141414141414141414141414141", + "020000000000000000000000000000000000004141414141414141414141414141", + "020000000000000000000000000000000000004141414141414141414141414140", + "030000000000000000000000000000000000004141414141414141414141414140" + ], + sorted: [ + "020000000000000000000000000000000000004141414141414141414141414140", + "020000000000000000000000000000000000004141414141414141414141414141", + "030000000000000000000000000000000000004141414141414141414141414140", + "030000000000000000000000000000000000004141414141414141414141414141" + ], + script: + "522102000000000000000000000000000000000000414141414141414141414141414021020000000000000000000000000000000000004141414141414141414141414141210300000000000000000000000000000000000041414141414141414141414141402103000000000000000000000000000000000000414141414141414141414141414154ae", + address: "32V85igBri9zcfBRVupVvwK18NFtS37FuD" + }, + %{ + pubkeys: [ + "022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da", + "03e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e9", + "021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc18" + ], + sorted: [ + "021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc18", + "022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da", + "03e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e9" + ], + script: + "5221021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc1821022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da2103e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e953ae", + address: "3Q4sF6tv9wsdqu2NtARzNCpQgwifm2rAba" + } + ] + describe "test basic functions" do test "test new/0 and empty?/1" do s = Script.new() @@ -1380,11 +1465,36 @@ defmodule Bitcoinex.ScriptTest do assert Script.to_address(s, :mainnet) == {:ok, addr} end - test "test raw multisig to address" do + test "test raw unsorted multisig to address" do for multi <- @raw_multisigs_with_data do {:ok, ms} = Script.parse_script(multi.script_hex) {:ok, m, pks} = Script.extract_multisig_policy(ms) + if multi.sh_addr != "" do + {:ok, p2sh, multi_script} = Script.create_p2sh_multisig(m, pks, bip67_sort: false) + + {:ok, addr} = Script.to_address(p2sh, multi.network) + + assert multi_script == ms + assert addr == multi.sh_addr + end + + if multi.wsh_addr != "" do + {:ok, p2wsh, multi_script} = Script.create_p2wsh_multisig(m, pks, bip67_sort: false) + + {:ok, addr} = Script.to_address(p2wsh, multi.network) + + assert multi_script == ms + assert addr == multi.wsh_addr + end + end + end + + test "raw sorted multisig to address" do + for multi <- @raw_sorted_multisig_with_data do + {:ok, ms} = Script.parse_script(multi.script_hex) + {:ok, m, pks} = Script.extract_multisig_policy(ms) + if multi.sh_addr != "" do {:ok, p2sh, multi_script} = Script.create_p2sh_multisig(m, pks) @@ -1534,7 +1644,7 @@ defmodule Bitcoinex.ScriptTest do for multi <- @raw_multisigs_with_data do {:ok, ms} = Script.parse_script(multi.script_hex) {:ok, m, pks} = Script.extract_multisig_policy(ms) - {:ok, ms2} = Script.create_multisig(m, pks) + {:ok, ms2} = Script.create_multisig(m, pks, bip67_sort: false) assert ms == ms2 end @@ -1542,7 +1652,7 @@ defmodule Bitcoinex.ScriptTest do for m <- @raw_multisig_scripts do {:ok, ms} = Script.parse_script(m) {:ok, m, pks} = Script.extract_multisig_policy(ms) - {:ok, ms2} = Script.create_multisig(m, pks) + {:ok, ms2} = Script.create_multisig(m, pks, bip67_sort: false) assert ms == ms2 end @@ -1602,4 +1712,32 @@ defmodule Bitcoinex.ScriptTest do end end end + + describe "bip 67" do + def parse_pk(pk) do + {:ok, pk} = Point.parse_public_key(pk) + pk + end + + test "sorting pubkeys" do + for t <- @bip67_tests do + pubkeys = Enum.map(t.pubkeys, &parse_pk/1) + sorted_pubkeys = Enum.map(t.sorted, &parse_pk/1) + + pubkeys = Script.lexicographical_sort_pubkeys(pubkeys) + + zip_list = Enum.zip(pubkeys, sorted_pubkeys) + Enum.each(zip_list, fn {pubkey, sorted_pubkey} -> assert pubkey == sorted_pubkey end) + end + end + + test "script creation" do + for t <- @bip67_tests do + pubkeys = Enum.map(t.pubkeys, &parse_pk/1) + {:ok, script, redeem_script} = Script.create_p2sh_multisig(@bip67_test_vectors_m, pubkeys) + assert Script.to_hex(redeem_script) == t.script + assert Script.to_address(script, :mainnet) == {:ok, t.address} + end + end + end end diff --git a/test/transaction_test.exs b/test/transaction_test.exs index e730f4d..6ab3861 100644 --- a/test/transaction_test.exs +++ b/test/transaction_test.exs @@ -358,6 +358,62 @@ defmodule Bitcoinex.TransactionTest do } } + @bip69_txs [ + %{ + tx_hex: + "0100000011aad553bb1650007e9982a8ac79d227cd8c831e1573b11f25573a37664e5f3e64000000006a47304402205438cedd30ee828b0938a863e08d810526123746c1f4abee5b7bc2312373450c02207f26914f4275f8f0040ab3375bacc8c5d610c095db8ed0785de5dc57456591a601210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffc26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f5485d1fde028000000006b483045022100f81d98c1de9bb61063a5e6671d191b400fda3a07d886e663799760393405439d0220234303c9af4bad3d665f00277fe70cdd26cd56679f114a40d9107249d29c979401210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff456a9e597129f5df2e11b842833fc19a94c563f57449281d3cd01249a830a1f0000000006a47304402202310b00924794ef68a8f09564fd0bb128838c66bc45d1a3f95c5cab52680f166022039fc99138c29f6c434012b14aca651b1c02d97324d6bd9dd0ffced0782c7e3bd01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff571fb3e02278217852dd5d299947e2b7354a639adc32ec1fa7b82cfb5dec530e000000006b483045022100d276251f1f4479d8521269ec8b1b45c6f0e779fcf1658ec627689fa8a55a9ca50220212a1e307e6182479818c543e1b47d62e4fc3ce6cc7fc78183c7071d245839df01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff5d8de50362ff33d3526ac3602e9ee25c1a349def086a7fc1d9941aaeb9e91d38010000006b4830450221008768eeb1240451c127b88d89047dd387d13357ce5496726fc7813edc6acd55ac022015187451c3fb66629af38fdb061dfb39899244b15c45e4a7ccc31064a059730d01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff60ad3408b89ea19caf3abd5e74e7a084344987c64b1563af52242e9d2a8320f3000000006b4830450221009be4261ec050ebf33fa3d47248c7086e4c247cafbb100ea7cee4aa81cd1383f5022008a70d6402b153560096c849d7da6fe61c771a60e41ff457aac30673ceceafee01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffe9b483a8ac4129780c88d1babe41e89dc10a26dedbf14f80a28474e9a11104de010000006b4830450221009bc40eee321b39b5dc26883f79cd1f5a226fc6eed9e79e21d828f4c23190c57e022078182fd6086e265589105023d9efa4cba83f38c674a499481bd54eee196b033f01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffe28db9462d3004e21e765e03a45ecb147f136a20ba8bca78ba60ebfc8e2f8b3b000000006a47304402200fb572b7c6916515452e370c2b6f97fcae54abe0793d804a5a53e419983fae1602205191984b6928bf4a1e25b00e5b5569a0ce1ecb82db2dea75fe4378673b53b9e801210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff7a1ef65ff1b7b7740c662ab6c9735ace4a16279c23a1db5709ed652918ffff54010000006a47304402206bc218a925f7280d615c8ea4f0131a9f26e7fc64cff6eeeb44edb88aba14f1910220779d5d67231bc2d2d93c3c5ab74dcd193dd3d04023e58709ad7ffbf95161be6201210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff850cecf958468ca7ffa6a490afe13b8c271b1326b0ddc1fdfdf9f3c7e365fdba000000006a473044022047df98cc26bd2bfdc5b2b97c27aead78a214810ff023e721339292d5ce50823d02205fe99dc5f667908974dae40cc7a9475af7fa6671ba44f64a00fcd01fa12ab523012102ca46fa75454650afba1784bc7b079d687e808634411e4beff1f70e44596308a1ffffffff8640e312040e476cf6727c60ca3f4a3ad51623500aacdda96e7728dbdd99e8a5000000006a47304402205566aa84d3d84226d5ab93e6f253b57b3ef37eb09bb73441dae35de86271352a02206ee0b7f800f73695a2073a2967c9ad99e19f6ddf18ce877adf822e408ba9291e01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff91c1889c5c24b93b56e643121f7a05a34c10c5495c450504c7b5afcb37e11d7a000000006b483045022100df61d45bbaa4571cdd6c5c822cba458cdc55285cdf7ba9cd5bb9fc18096deb9102201caf8c771204df7fd7c920c4489da7bc3a60e1d23c1a97e237c63afe53250b4a01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff2470947216eb81ea0eeeb4fe19362ec05767db01c3aa3006bb499e8b6d6eaa26010000006a473044022031501a0b2846b8822a32b9947b058d89d32fc758e009fc2130c2e5effc925af70220574ef3c9e350cef726c75114f0701fd8b188c6ec5f84adce0ed5c393828a5ae001210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff0abcd77d65cc14363f8262898335f184d6da5ad060ff9e40bf201741022c2b40010000006b483045022100a6ac110802b699f9a2bff0eea252d32e3d572b19214d49d8bb7405efa2af28f1022033b7563eb595f6d7ed7ec01734e17b505214fe0851352ed9c3c8120d53268e9a01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffa43bebbebf07452a893a95bfea1d5db338d23579be172fe803dce02eeb7c037d010000006b483045022100ebc77ed0f11d15fe630fe533dc350c2ddc1c81cfeb81d5a27d0587163f58a28c02200983b2a32a1014bab633bfc9258083ac282b79566b6b3fa45c1e6758610444f401210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffb102113fa46ce949616d9cda00f6b10231336b3928eaaac6bfe42d1bf3561d6c010000006a473044022010f8731929a55c1c49610722e965635529ed895b2292d781b183d465799906b20220098359adcbc669cd4b294cc129b110fe035d2f76517248f4b7129f3bf793d07f01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffb861fab2cde188499758346be46b5fbec635addfc4e7b0c8a07c0a908f2b11b4000000006a47304402207328142bb02ef5d6496a210300f4aea71f67683b842fa3df32cae6c88b49a9bb022020f56ddff5042260cfda2c9f39b7dec858cc2f4a76a987cd2dc25945b04e15fe01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff027064d817000000001976a9144a5fba237213a062f6f57978f796390bdcf8d01588ac00902f50090000001976a9145be32612930b8323add2212a4ec03c1562084f8488ac00000000", + ordered_inputs: [ + %{txid: "0e53ec5dfb2cb8a71fec32dc9a634a35b7e24799295ddd5278217822e0b31f57", vout: 0}, + %{txid: "26aa6e6d8b9e49bb0630aac301db6757c02e3619feb4ee0eea81eb1672947024", vout: 1}, + %{txid: "28e0fdd185542f2c6ea19030b0796051e7772b6026dd5ddccd7a2f93b73e6fc2", vout: 0}, + %{txid: "381de9b9ae1a94d9c17f6a08ef9d341a5ce29e2e60c36a52d333ff6203e58d5d", vout: 1}, + %{txid: "3b8b2f8efceb60ba78ca8bba206a137f14cb5ea4035e761ee204302d46b98de2", vout: 0}, + %{txid: "402b2c02411720bf409eff60d05adad684f135838962823f3614cc657dd7bc0a", vout: 1}, + %{txid: "54ffff182965ed0957dba1239c27164ace5a73c9b62a660c74b7b7f15ff61e7a", vout: 1}, + %{txid: "643e5f4e66373a57251fb173151e838ccd27d279aca882997e005016bb53d5aa", vout: 0}, + %{txid: "6c1d56f31b2de4bfc6aaea28396b333102b1f600da9c6d6149e96ca43f1102b1", vout: 1}, + %{txid: "7a1de137cbafb5c70405455c49c5104ca3057a1f1243e6563bb9245c9c88c191", vout: 0}, + %{txid: "7d037ceb2ee0dc03e82f17be7935d238b35d1deabf953a892a4507bfbeeb3ba4", vout: 1}, + %{txid: "a5e899dddb28776ea9ddac0a502316d53a4a3fca607c72f66c470e0412e34086", vout: 0}, + %{txid: "b4112b8f900a7ca0c8b0e7c4dfad35c6be5f6be46b3458974988e1cdb2fa61b8", vout: 0}, + %{txid: "bafd65e3c7f3f9fdfdc1ddb026131b278c3be1af90a4a6ffa78c4658f9ec0c85", vout: 0}, + %{txid: "de0411a1e97484a2804ff1dbde260ac19de841bebad1880c782941aca883b4e9", vout: 1}, + %{txid: "f0a130a84912d03c1d284974f563c5949ac13f8342b8112edff52971599e6a45", vout: 0}, + %{txid: "f320832a9d2e2452af63154bc687493484a0e7745ebd3aaf9ca19eb80834ad60", vout: 0} + ], + ordered_outputs: [ + %{value: 400_057_456, scriptpubkey: "76a9144a5fba237213a062f6f57978f796390bdcf8d01588ac"}, + %{ + value: 40_000_000_000, + scriptpubkey: "76a9145be32612930b8323add2212a4ec03c1562084f8488ac" + } + ] + }, + %{ + tx_hex: + "010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d28350000000049483045022100aa46504baa86df8a33b1192b1b9367b4d729dc41e389f2c04f3e5c7f0559aae702205e82253a54bf5c4f65b7428551554b2045167d6d206dfe6a2e198127d3f7df1501ffffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000004847304402202329484c35fa9d6bb32a55a70c0982f606ce0e3634b69006138683bcd12cbb6602200c28feb1e2555c3210f1dddb299738b4ff8bbe9667b68cb8764b5ac17b7adf0001ffffffff0200e1f505000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00180d8f000000004341044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac00000000", + ordered_inputs: [ + %{ + txid: "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055", + vout: 0 + }, + %{txid: "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055", vout: 1} + ], + ordered_outputs: [ + %{ + value: 100_000_000, + scriptpubkey: + "41046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac" + }, + %{ + value: 2_400_000_000, + scriptpubkey: + "41044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac" + } + ] + } + ] + describe "decode/1" do test "decodes legacy bitcoin transaction" do txn_test = @txn_serialization_1 @@ -725,4 +781,33 @@ defmodule Bitcoinex.TransactionTest do assert sighash2 == Utils.hex_to_bin(t.intermediary.sighash) end end + + describe "bip 69" do + test "input sorting" do + for t <- @bip69_txs do + {:ok, tx} = Transaction.decode(t.tx_hex) + + # randomly shuffle inputs then sort them again + for _ <- 1..10 do + rand_inputs = Enum.shuffle(tx.inputs) + sorted_inputs = Transaction.In.lexicographical_sort_inputs(rand_inputs) + zip_list = Enum.zip(sorted_inputs, t.ordered_inputs) + + Enum.each(zip_list, fn {sorted_input, ordered_input} -> + assert sorted_input.prev_txid == ordered_input.txid + assert sorted_input.prev_vout == ordered_input.vout + end) + + shuffled_outputs = Enum.shuffle(tx.outputs) + sorted_outputs = Transaction.Out.lexicographical_sort_outputs(shuffled_outputs) + zip_list = Enum.zip(sorted_outputs, t.ordered_outputs) + + Enum.each(zip_list, fn {sorted_output, ordered_output} -> + assert sorted_output.value == ordered_output.value + assert sorted_output.script_pub_key == ordered_output.scriptpubkey + end) + end + end + end + end end From 059cee5764456079e3a1722ca5486628d1f6d4ef Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Wed, 8 Mar 2023 20:08:00 -0500 Subject: [PATCH 62/63] fix bip69 bug --- lib/transaction.ex | 4 ++-- test/transaction_test.exs | 31 ++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/lib/transaction.ex b/lib/transaction.ex index 1dd8763..3ce9e5b 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -790,8 +790,8 @@ defmodule Bitcoinex.Transaction.Out do false o1.value == o2.value -> - o1spk = Base.decode16!(o1.script_pub_key) |> :erlang.binary_to_list() - o2spk = Base.decode16!(o2.script_pub_key) |> :erlang.binary_to_list() + o1spk = Base.decode16!(o1.script_pub_key, case: :lower) |> :erlang.binary_to_list() + o2spk = Base.decode16!(o2.script_pub_key, case: :lower) |> :erlang.binary_to_list() Utils.lexicographical_cmp(o1spk, o2spk) end end diff --git a/test/transaction_test.exs b/test/transaction_test.exs index 6ab3861..458afd1 100644 --- a/test/transaction_test.exs +++ b/test/transaction_test.exs @@ -397,7 +397,10 @@ defmodule Bitcoinex.TransactionTest do txid: "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055", vout: 0 }, - %{txid: "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055", vout: 1} + %{ + txid: "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055", + vout: 1 + } ], ordered_outputs: [ %{ @@ -411,6 +414,32 @@ defmodule Bitcoinex.TransactionTest do "41044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac" } ] + }, + # fake tx, same as above but with amount changed. signatures invalid + %{ + tx_hex: "010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d28350000000049483045022100aa46504baa86df8a33b1192b1b9367b4d729dc41e389f2c04f3e5c7f0559aae702205e82253a54bf5c4f65b7428551554b2045167d6d206dfe6a2e198127d3f7df1501ffffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000004847304402202329484c35fa9d6bb32a55a70c0982f606ce0e3634b69006138683bcd12cbb6602200c28feb1e2555c3210f1dddb299738b4ff8bbe9667b68cb8764b5ac17b7adf0001ffffffff0200e1f505000000004341044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac00e1f505000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00000000", + ordered_inputs: [ + %{ + txid: "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055", + vout: 0 + }, + %{ + txid: "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055", + vout: 1 + } + ], + ordered_outputs: [ + %{ + value: 100_000_000, + scriptpubkey: + "41044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac" + }, + %{ + value: 100_000_000, + scriptpubkey: + "41046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac" + } + ] } ] From 7904b9c06b4cfea298b9a29a75b29ffe942b3f3e Mon Sep 17 00:00:00 2001 From: Sachin Meier Date: Wed, 8 Mar 2023 20:11:05 -0500 Subject: [PATCH 63/63] fmt --- test/transaction_test.exs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/transaction_test.exs b/test/transaction_test.exs index 458afd1..6c85010 100644 --- a/test/transaction_test.exs +++ b/test/transaction_test.exs @@ -417,7 +417,8 @@ defmodule Bitcoinex.TransactionTest do }, # fake tx, same as above but with amount changed. signatures invalid %{ - tx_hex: "010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d28350000000049483045022100aa46504baa86df8a33b1192b1b9367b4d729dc41e389f2c04f3e5c7f0559aae702205e82253a54bf5c4f65b7428551554b2045167d6d206dfe6a2e198127d3f7df1501ffffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000004847304402202329484c35fa9d6bb32a55a70c0982f606ce0e3634b69006138683bcd12cbb6602200c28feb1e2555c3210f1dddb299738b4ff8bbe9667b68cb8764b5ac17b7adf0001ffffffff0200e1f505000000004341044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac00e1f505000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00000000", + tx_hex: + "010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d28350000000049483045022100aa46504baa86df8a33b1192b1b9367b4d729dc41e389f2c04f3e5c7f0559aae702205e82253a54bf5c4f65b7428551554b2045167d6d206dfe6a2e198127d3f7df1501ffffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000004847304402202329484c35fa9d6bb32a55a70c0982f606ce0e3634b69006138683bcd12cbb6602200c28feb1e2555c3210f1dddb299738b4ff8bbe9667b68cb8764b5ac17b7adf0001ffffffff0200e1f505000000004341044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac00e1f505000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00000000", ordered_inputs: [ %{ txid: "35288d269cee1941eaebb2ea85e32b42cdb2b04284a56d8b14dcc3f5c65d6055",