diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index cb703d8..c252f04 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -1,6 +1,6 @@ name: Elixir CI -on: [push, pull_request] +on: [push] jobs: build_with_opt_24: diff --git a/.tool-versions b/.tool-versions index 440d747..f08002d 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -elixir 1.14.2-otp-25 -erlang 25.1.2 +elixir 1.15.6-otp-26 +erlang 26.1.2 \ No newline at end of file diff --git a/lib/bech32.ex b/lib/bech32.ex index 6e31ab8..5558228 100644 --- a/lib/bech32.ex +++ b/lib/bech32.ex @@ -8,7 +8,7 @@ defmodule Bitcoinex.Bech32 do import Bitwise @gen [0x3B6A57B2, 0x26508E6D, 0x1EA119FA, 0x3D4233DD, 0x2A1462B3] - @data_charset_list 'qpzry9x8gf2tvdw0s3jn54khce6mua7l' + @data_charset_list ~c"qpzry9x8gf2tvdw0s3jn54khce6mua7l" @data_charset_map @data_charset_list |> Enum.zip(0..Enum.count(@data_charset_list)) |> Enum.into(%{}) diff --git a/lib/extendedkey.ex b/lib/extendedkey.ex index a0db3bd..4f9de07 100644 --- a/lib/extendedkey.ex +++ b/lib/extendedkey.ex @@ -103,7 +103,9 @@ defmodule Bitcoinex.ExtendedKey do 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}") + raise(ArgumentError, + message: "index cannot be less than #{@min_non_hardened_child_num}" + ) true -> lvlbin = @@ -215,8 +217,7 @@ defmodule Bitcoinex.ExtendedKey do parent_fingerprint: binary, child_num: binary, chaincode: binary, - key: binary, - checksum: binary + key: binary } @enforce_keys [ @@ -225,8 +226,7 @@ defmodule Bitcoinex.ExtendedKey do :parent_fingerprint, :child_num, :chaincode, - :key, - :checksum + :key ] defstruct [ @@ -235,23 +235,55 @@ defmodule Bitcoinex.ExtendedKey do :parent_fingerprint, :child_num, :chaincode, - :key, - :checksum + :key ] + # Single Sig + # xpub @xpub_pfx <<0x04, 0x88, 0xB2, 0x1E>> + # xprv @xprv_pfx <<0x04, 0x88, 0xAD, 0xE4>> + # tpub @tpub_pfx <<0x04, 0x35, 0x87, 0xCF>> + # tprv @tprv_pfx <<0x04, 0x35, 0x83, 0x94>> - + # ypub + @ypub_pfx <<0x04, 0x9D, 0x7C, 0xB2>> + # yprv + @yprv_pfx <<0x04, 0x9D, 0x78, 0x78>> + # upub + @upub_pfx <<0x04, 0x4A, 0x52, 0x62>> + # uprv + @uprv_pfx <<0x04, 0x4A, 0x4E, 0x28>> + # zpub + @zpub_pfx <<0x04, 0xB2, 0x47, 0x46>> + # zprv + @zprv_pfx <<0x04, 0xB2, 0x43, 0x0C>> + # vpub + @vpub_pfx <<0x04, 0x5F, 0x1C, 0xF6>> + # vprv + @vprv_pfx <<0x04, 0x5F, 0x18, 0xBC>> + # Multisig (no BIP or derivation path, from SLIP-132) + # @y_pub_pfx <<0x02,0x95,0xb4,0x3f>> #Ypub + # @y_prv_pfx <<0x02,0x95,0xb0,0x05>> #Yprv + # @z_pub_pfx <<0x02,0xaa,0x7e,0xd3>> #Zpub + # @z_prv_pfx <<0x02,0xaa,0x7a,0x99>> #Zprv @prv_prefixes [ @xprv_pfx, - @tprv_pfx + @tprv_pfx, + @yprv_pfx, + @uprv_pfx, + @zprv_pfx, + @vprv_pfx ] @pub_prefixes [ @xpub_pfx, - @tpub_pfx + @tpub_pfx, + @ypub_pfx, + @upub_pfx, + @zpub_pfx, + @vpub_pfx ] @all_prefixes @prv_prefixes ++ @pub_prefixes @@ -262,28 +294,97 @@ defmodule Bitcoinex.ExtendedKey do :xprv -> @xprv_pfx :tpub -> @tpub_pfx :tprv -> @tprv_pfx + :ypub -> @ypub_pfx + :yprv -> @yprv_pfx + :upub -> @upub_pfx + :uprv -> @uprv_pfx + :zpub -> @zpub_pfx + :zprv -> @zprv_pfx + :vpub -> @vpub_pfx + :vprv -> @vprv_pfx end end + defp bip44 do + [ + @xpub_pfx, + @xprv_pfx, + @tpub_pfx, + @tprv_pfx + ] + end + + defp bip49 do + [ + @ypub_pfx, + @yprv_pfx, + @upub_pfx, + @uprv_pfx + ] + end + + defp bip84 do + [ + @zpub_pfx, + @zprv_pfx, + @vpub_pfx, + @vprv_pfx + ] + end + defp prv_to_pub_prefix(prv_pfx) do case prv_pfx do @xprv_pfx -> @xpub_pfx @tprv_pfx -> @tpub_pfx + @yprv_pfx -> @ypub_pfx + @uprv_pfx -> @upub_pfx + @zprv_pfx -> @zpub_pfx + @vprv_pfx -> @vpub_pfx end end defp mainnet_prefixes do [ @xpub_pfx, - @xprv_pfx + @xprv_pfx, + @ypub_pfx, + @yprv_pfx, + @zpub_pfx, + @zprv_pfx ] end @spec network_from_prefix(binary) :: atom - defp network_from_prefix(prefix) do + def network_from_prefix(prefix) do if prefix in mainnet_prefixes(), do: :mainnet, else: :testnet end + @spec script_type_from_prefix(binary) :: atom + def script_type_from_prefix(prefix) do + cond do + prefix in bip44() -> :p2pkh + # p2sh or p2sh_p2wpkh? + prefix in bip49() -> :p2sh_p2wpkh + prefix in bip84() -> :p2wpkh + end + end + + @spec switch_prefix(t(), atom) :: t() | {:error, String.t()} + def switch_prefix(xkey = %__MODULE__{prefix: pfx}, new_pfx) do + new_pfx_bin = pfx_atom_to_bin(new_pfx) + + cond do + new_pfx_bin in @prv_prefixes and pfx in @prv_prefixes -> + %__MODULE__{xkey | prefix: new_pfx_bin} + + new_pfx_bin in @pub_prefixes and pfx in @pub_prefixes -> + %__MODULE__{xkey | prefix: new_pfx_bin} + + true -> + {:error, "switching between public and private prefixes will result in a useless key"} + end + end + @doc """ network_from_extended_key returns :testnet or :mainnet depending on the network prefix of the key. @@ -350,7 +451,7 @@ defmodule Bitcoinex.ExtendedKey do xkey = <> + _checksum::binary-size(4)>> ) do cond do prefix not in @all_prefixes -> @@ -373,8 +474,7 @@ defmodule Bitcoinex.ExtendedKey do parent_fingerprint: parent_fingerprint, child_num: child_num, chaincode: chaincode, - key: key, - checksum: checksum + key: key }} end end @@ -696,16 +796,16 @@ defmodule Bitcoinex.ExtendedKey do """ @spec derive_extended_key(t() | binary, DerivationPath.t()) :: {:ok, t()} | {:error, String.t()} def derive_extended_key(xkey = %__MODULE__{}, %DerivationPath{child_nums: path}), - do: rderive_extended_key(xkey, path) + do: derive_extended_key(xkey, path) def derive_extended_key(seed, %DerivationPath{child_nums: path}) do {:ok, xkey} = seed_to_master_private_key(seed) - rderive_extended_key(xkey, path) + derive_extended_key(xkey, path) end - defp rderive_extended_key(xkey = %__MODULE__{}, []), do: {:ok, xkey} + def derive_extended_key(xkey = %__MODULE__{}, []), do: {:ok, xkey} - defp rderive_extended_key(xkey = %__MODULE__{}, [p | rest]) do + def derive_extended_key(xkey = %__MODULE__{}, [p | rest]) do try do case p do # if asterisk (:any) is in path, return the immediate parent xkey @@ -715,7 +815,7 @@ defmodule Bitcoinex.ExtendedKey do # otherwise it is an integer, so derive child at that index. _ -> case derive_child_key(xkey, p) do - {:ok, child_key} -> rderive_extended_key(child_key, rest) + {:ok, child_key} -> derive_extended_key(child_key, rest) {:error, msg} -> {:error, msg} end end diff --git a/lib/segwit.ex b/lib/segwit.ex index d9c28f0..ce0fb3d 100644 --- a/lib/segwit.ex +++ b/lib/segwit.ex @@ -147,9 +147,9 @@ defmodule Bitcoinex.Segwit do defp is_program_length_valid?(_, _), do: false - defp parse_network('bc'), do: {:ok, :mainnet} - defp parse_network('tb'), do: {:ok, :testnet} - defp parse_network('bcrt'), do: {:ok, :regtest} + defp parse_network(~c"bc"), do: {:ok, :mainnet} + defp parse_network(~c"tb"), do: {:ok, :testnet} + defp parse_network(~c"bcrt"), do: {:ok, :regtest} defp parse_network(_), do: {:error, :invalid_network} defp witness_version_to_bech_encoding(0), do: :bech32 diff --git a/lib/transaction.ex b/lib/transaction.ex index 0f28292..2ebf644 100644 --- a/lib/transaction.ex +++ b/lib/transaction.ex @@ -699,7 +699,9 @@ defmodule Bitcoinex.Transaction.In do input = %In{ # TODO fix this prev_txid: - Base.encode16(<<:binary.decode_unsigned(prev_txid, :big)::little-size(256)>>, case: :lower), + Base.encode16(<<:binary.decode_unsigned(prev_txid, :big)::little-size(256)>>, + case: :lower + ), prev_vout: prev_vout, script_sig: Base.encode16(script_sig, case: :lower), sequence_no: sequence_no diff --git a/mix.exs b/mix.exs index 1385e50..b696d49 100644 --- a/mix.exs +++ b/mix.exs @@ -27,14 +27,14 @@ defmodule Bitcoinex.MixProject do defp deps do [ {:credo, "~> 1.6", only: [:dev, :test], runtime: false}, - {:dialyxir, "~> 1.2.0", only: [:dev, :test], runtime: false}, - {:excoveralls, "~> 0.10", only: :test}, + {:dialyxir, "~> 1.3.0", only: [:dev, :test], runtime: false}, + {:excoveralls, "~> 0.14.5", only: :test}, {:mix_test_watch, "~> 1.1", only: :dev, runtime: false}, {:stream_data, "~> 0.1", only: :test}, - {:timex, "~> 3.1"}, - {:decimal, "~> 1.0 or ~> 2.0"}, + {:timex, "== 3.7.6"}, + {:decimal, "~> 2.0"}, {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, - {:benchee, ">= 1.0.0", only: :dev} + {:benchee, ">= 1.1.0", only: :dev} ] end diff --git a/mix.lock b/mix.lock index d4daf34..a555443 100644 --- a/mix.lock +++ b/mix.lock @@ -1,21 +1,22 @@ %{ "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, - "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, - "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, + "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, - "dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"}, + "dialyxir": {:hex, :dialyxir, "1.3.0", "fd1672f0922b7648ff9ce7b1b26fcf0ef56dda964a459892ad15f6b4410b5284", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "00b2a4bcd6aa8db9dcb0b38c1225b7277dca9bc370b6438715667071a304696f"}, "earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"}, - "excoveralls": {:hex, :excoveralls, "0.15.1", "83c8cf7973dd9d1d853dce37a2fb98aaf29b564bf7d01866e409abf59dac2c0e", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f8416bd90c0082d56a2178cf46c837595a06575f70a5624f164a1ffe37de07e7"}, + "excoveralls": {:hex, :excoveralls, "0.14.6", "610e921e25b180a8538229ef547957f7e04bd3d3e9a55c7c5b7d24354abbba70", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "0eceddaa9785cfcefbf3cd37812705f9d8ad34a758e513bb975b081dce4eb11e"}, + "expo": {:hex, :expo, "0.5.1", "249e826a897cac48f591deba863b26c16682b43711dd15ee86b92f25eafd96d9", [:mix], [], "hexpm", "68a4233b0658a3d12ee00d27d37d856b1ba48607e7ce20fd376958d0ba6ce92b"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "gettext": {:hex, :gettext, "0.20.0", "75ad71de05f2ef56991dbae224d35c68b098dd0e26918def5bb45591d5c8d429", [:mix], [], "hexpm", "1c03b177435e93a47441d7f681a7040bd2a816ece9e2666d1c9001035121eb3d"}, - "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~> 2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, + "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, + "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, - "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, + "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, @@ -23,11 +24,11 @@ "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mix_test_watch": {:hex, :mix_test_watch, "1.1.0", "330bb91c8ed271fe408c42d07e0773340a7938d8a0d281d57a14243eae9dc8c3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "52b6b1c476cbb70fd899ca5394506482f12e5f6b0d6acff9df95c7f1e0812ec3"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, - "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"}, - "timex": {:hex, :timex, "3.7.9", "790cdfc4acfce434e442f98c02ea6d84d0239073bfd668968f82ac63e9a6788d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "64691582e5bb87130f721fc709acfb70f24405833998fabf35be968984860ce1"}, + "timex": {:hex, :timex, "3.7.6", "502d2347ec550e77fdf419bc12d15bdccd31266bb7d925b30bf478268098282f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "a296327f79cb1ec795b896698c56e662ed7210cc9eb31f0ab365eb3a62e2c589"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, } diff --git a/test/extendedkey_test.exs b/test/extendedkey_test.exs index 0ca4958..67c5fe9 100644 --- a/test/extendedkey_test.exs +++ b/test/extendedkey_test.exs @@ -33,7 +33,6 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do chaincode: <<135, 61, 255, 129, 192, 47, 82, 86, 35, 253, 31, 229, 22, 126, 172, 58, 85, 160, 73, 222, 61, 49, 75, 180, 46, 226, 39, 255, 237, 55, 213, 8>>, - checksum: <<171, 71, 59, 33>>, child_num: <<0, 0, 0, 0>>, depth: <<0>>, key: @@ -48,7 +47,6 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do chaincode: <<135, 61, 255, 129, 192, 47, 82, 86, 35, 253, 31, 229, 22, 126, 172, 58, 85, 160, 73, 222, 61, 49, 75, 180, 46, 226, 39, 255, 237, 55, 213, 8>>, - checksum: <<231, 126, 157, 113>>, child_num: <<0, 0, 0, 0>>, depth: <<0>>, key: @@ -122,6 +120,79 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L" } + @bip49_test_case %{ + masterseedWords: + ~w(abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about), + # (testnet) + masterseed: + "uprv8tXDerPXZ1QsVNjUJWTurs9kA1KGfKUAts74GCkcXtU8GwnH33GDRbNJpEqTvipfCyycARtQJhmdfWf8oKt41X9LL1zeD2pLsWmxEk3VAwd", + + # Account 0, root: m/49'/1'/0' + # (testnet) + account0Xprv: + "uprv91G7gZkzehuMVxDJTYE6tLivdF8e4rvzSu1LFfKw3b2Qx1Aj8vpoFnHdfUZ3hmi9jsvPifmZ24RTN2KhwB8BfMLTVqaBReibyaFFcTP1s9n", + # (testnet) + account0Xpub: + "upub5EFU65HtV5TeiSHmZZm7FUffBGy8UKeqp7vw43jYbvZPpoVsgU93oac7Wk3u6moKegAEWtGNF8DehrnHtv21XXEMYRUocHqguyjknFHYfgY", + + # Account 0, first receiving private key: m/49'/1'/0'/0/0 + account0recvPrivateKey: "cULrpoZGXiuC19Uhvykx7NugygA3k86b3hmdCeyvHYQZSxojGyXJ", + account0recvPrivateKeyHex: "c9bdb49cfbaedca21c4b1f3a7803c34636b1d7dc55a717132443fc3f4c5867e8", + account0recvPublicKeyHex: + "03a1af804ac108a8a51782198c2d034b28bf90c8803f5a53f76276fa69a4eae77f", + + # Address derivation + keyhash: "38971f73930f6c141d977ac4fd4a727c854935b3", + scriptSig: "001438971f73930f6c141d977ac4fd4a727c854935b3", + addressBytes: "336caa13e08b96080a32b5d818d59b4ab3b36742", + + # addressBytes base58check encoded for testnet + # (testnet) + address: "2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2" + } + + @bip84_test_case %{ + # test case from BIP 84: https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki#test-vectors + # mnemonic: ["abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "about"] + c_rootpriv: + "zprvAWgYBBk7JR8Gjrh4UJQ2uJdG1r3WNRRfURiABBE3RvMXYSrRJL62XuezvGdPvG6GFBZduosCc1YP5wixPox7zhZLfiUm8aunE96BBa4Kei5", + c_rootpriv_obj: %ExtendedKey{ + chaincode: + <<121, 35, 64, 141, 173, 211, 199, 181, 110, 237, 21, 86, 119, 7, 174, 94, 93, 202, 8, + 157, 233, 114, 224, 127, 59, 134, 4, 80, 226, 163, 183, 14>>, + child_num: <<0, 0, 0, 0>>, + depth: <<0>>, + key: + <<0, 24, 55, 193, 190, 142, 41, 149, 236, 17, 205, 162, 176, 102, 21, 27, 226, 207, 180, + 138, 223, 158, 71, 177, 81, 212, 106, 218, 179, 162, 28, 223, 103>>, + parent_fingerprint: <<0, 0, 0, 0>>, + prefix: <<4, 178, 67, 12>> + }, + c_rootpub: + "zpub6jftahH18ngZxLmXaKw3GSZzZsszmt9WqedkyZdezFtWRFBZqsQH5hyUmb4pCEeZGmVfQuP5bedXTB8is6fTv19U1GQRyQUKQGUTzyHACMF", + c_rootpub_obj: %ExtendedKey{ + chaincode: + <<121, 35, 64, 141, 173, 211, 199, 181, 110, 237, 21, 86, 119, 7, 174, 94, 93, 202, 8, + 157, 233, 114, 224, 127, 59, 134, 4, 80, 226, 163, 183, 14>>, + child_num: <<0, 0, 0, 0>>, + depth: <<0>>, + key: + <<3, 217, 2, 243, 95, 86, 14, 4, 112, 198, 51, 19, 199, 54, 145, 104, 217, 215, 223, 45, + 73, 191, 41, 95, 217, 251, 124, 177, 9, 204, 238, 4, 148>>, + parent_fingerprint: <<0, 0, 0, 0>>, + prefix: <<4, 178, 71, 70>> + }, + # Account 0, root: m/84'/0'/0' + c_xprv: + "zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqWvGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE", + c_xpub: + "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs", + # Account 0, first receiving address: m/84'/0'/0'/0/0 + c_privkey: "KyZpNDKnfs94vbrwhJneDi77V6jF64PWPF8x5cdJb8ifgg2DUc9d", + c_pubkey: "0330d54fd0dd420a6e5f8d3624f5f3482cae350f79d5f0753bf5beef9c2d91af3c", + c_address: "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu" + } + @derivation_paths_to_serialize [ %{ str: "84/0/0/2/1/", @@ -265,6 +336,25 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do "m/-1/0/1" ] + @switch_prefixes_examples [ + %{ + xprv: + "xprv9s21ZrQH143K2WGtpJzs2er7Gmf14DG7LAGUYqu258PZSnJoBCn1ji9LA98z9qgMb3RGgrVjmCrS5qQVJNsLfKB7j7cF3gLoqnNqhQgTSe1", + yprv: + "yprvABrGsX5C9jansoU1efnVEjwcSjoSzqFcFGnhLEnuT8mSVt82RrwaMmoUBM6a9kLGzgY5SL6JDsCyy82425HMTYribTJfdbAJ7WSV5xJadR1", + zprv: + "zprvAWgYBBk7JR8Gj6f8V2a7Sq37chwtwTF7APJv7dgnq99KYywFgX78yqTcCZ4A9ezCQKetBogrgXZXrQdcjmhNFnYKTo16DVynPEW8UYJW24M" + }, + %{ + xprv: + "xprv9s21ZrQH143K2rjUSB7cHKRPQbKawzWnY8WpijEume6RsZj1YHnstedsehZr3PpzEXctHMeYt8XmfPPSF24ZHQteAhC4djGU92j1r3Jgp3L", + yprv: + "yprvABrGsX5C9jant9vbGXuEVQWtaZU2tcWHTF33W88o9eUJvfYEnwxSWiJ1fuXS3JUueAjh2qF7LntKYfzzxiUa5eaF32tVDe5xQknfEcsqyG7", + zprv: + "zprvAWgYBBk7JR8GjT7i6tgrhVcPkXcUqEVnNMZGHX2gXerBymMU3c818mx9h7V23D8q3orVnJqfoTEsRxcZgQtastFquNauoYuSgUrJd6S8Ex9" + } + ] + # Extended Key Testing describe "parse/1" do @@ -280,12 +370,131 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do end describe "to_extended_public_key/1" do + test "successfully turn zprv into zpub" do + t = @bip84_test_case + assert ExtendedKey.to_extended_public_key(t.c_rootpriv_obj) == {:ok, t.c_rootpub_obj} + end + test "successfully turn xprv into xpub" do t = @bip32_test_case_1 assert ExtendedKey.to_extended_public_key(t.xprv_m_obj) == {:ok, t.xpub_m_obj} end end + describe "child derivation" do + test "fail to derive hardened children from pub parent_fingerprint" do + t = @bip32_test_case_1 + + {:ok, xpub} = ExtendedKey.parse(t.xpub_m) + + assert ExtendedKey.derive_public_child(xpub, @min_hardened_child_num) == + {:error, "idx must be in 0..2**31-1"} + + assert ExtendedKey.derive_public_child(xpub, @min_hardened_child_num + 1) == + {:error, "idx must be in 0..2**31-1"} + + assert ExtendedKey.derive_public_child(xpub, @max_hardened_child_num - 1) == + {:error, "idx must be in 0..2**31-1"} + end + + test "fail to derive children with invalid index" do + t = @bip32_test_case_1 + + {:ok, xprv} = ExtendedKey.parse(t.xprv_m) + {:ok, xpub} = ExtendedKey.parse(t.xpub_m) + + assert ExtendedKey.derive_private_child(xprv, @max_hardened_child_num) == + {:error, "idx must be in 0..2**32-1"} + + assert ExtendedKey.derive_private_child(xprv, @max_hardened_child_num + 1) == + {:error, "idx must be in 0..2**32-1"} + + assert ExtendedKey.derive_private_child(xprv, -1) == {:error, "idx must be in 0..2**32-1"} + + assert ExtendedKey.derive_public_child(xpub, -1) == {:error, "idx must be in 0..2**31-1"} + end + end + + describe "BIP84 tests" do + test "successfully derive zprv child key at path m/84'/0'/0'/" do + t = @bip84_test_case + + {:ok, xprv} = + ExtendedKey.derive_private_child(t.c_rootpriv_obj, @min_hardened_child_num + 84) + + {:ok, xprv} = ExtendedKey.derive_private_child(xprv, @min_hardened_child_num) + {:ok, xprv} = ExtendedKey.derive_private_child(xprv, @min_hardened_child_num) + + assert ExtendedKey.display(xprv) == t.c_xprv + end + + test "successfully derive zpub child key at path m/84'/0'/0'/" do + t = @bip84_test_case + + {:ok, xprv} = + ExtendedKey.derive_private_child(t.c_rootpriv_obj, @min_hardened_child_num + 84) + + {:ok, xprv} = ExtendedKey.derive_private_child(xprv, @min_hardened_child_num) + {:ok, xpub} = ExtendedKey.derive_public_child(xprv, @min_hardened_child_num) + + assert ExtendedKey.display(xpub) == t.c_xpub + end + + test "successfully derive private key WIF at m/84'/0'/0'/0/0" do + t = @bip84_test_case + + {:ok, xprv} = + ExtendedKey.derive_private_child(t.c_rootpriv_obj, @min_hardened_child_num + 84) + + {:ok, xprv} = ExtendedKey.derive_private_child(xprv, @min_hardened_child_num) + {:ok, xprv} = ExtendedKey.derive_private_child(xprv, @min_hardened_child_num) + {:ok, xprv} = ExtendedKey.derive_private_child(xprv, 0) + {:ok, xprv} = ExtendedKey.derive_private_child(xprv, 0) + {:ok, prvkey} = ExtendedKey.to_private_key(xprv) + + assert Bitcoinex.Secp256k1.PrivateKey.wif!(prvkey, :mainnet) == t.c_privkey + end + + test "successfully derive account xpub and then public key at m/84'/0'/0'/0/0" do + t = @bip84_test_case + + {:ok, xkey} = + ExtendedKey.derive_private_child(t.c_rootpriv_obj, @min_hardened_child_num + 84) + + {:ok, xkey} = ExtendedKey.derive_private_child(xkey, @min_hardened_child_num) + {:ok, xkey} = ExtendedKey.derive_public_child(xkey, @min_hardened_child_num) + {:ok, xkey} = ExtendedKey.derive_public_child(xkey, 0) + {:ok, xkey} = ExtendedKey.derive_public_child(xkey, 0) + {:ok, pk} = ExtendedKey.to_public_key(xkey) + + assert Bitcoinex.Secp256k1.Point.serialize_public_key(pk) == t.c_pubkey + end + end + + describe "BIP49 tests" do + test "successfully derive private keys from account yprv" do + t = @bip49_test_case + + {:ok, xprv} = ExtendedKey.parse(t.account0Xprv) + {:ok, xprv} = ExtendedKey.derive_private_child(xprv, 0) + {:ok, xprv} = ExtendedKey.derive_private_child(xprv, 0) + {:ok, prvkey} = ExtendedKey.to_private_key(xprv) + + assert Bitcoinex.Secp256k1.PrivateKey.wif!(prvkey, :testnet) == t.account0recvPrivateKey + end + + test "successfully derive public key from account ypub " do + t = @bip49_test_case + + {:ok, xpub} = ExtendedKey.parse(t.account0Xpub) + {:ok, xpub} = ExtendedKey.derive_public_child(xpub, 0) + {:ok, xpub} = ExtendedKey.derive_public_child(xpub, 0) + {:ok, pubkey} = ExtendedKey.to_public_key(xpub) + + assert Bitcoinex.Secp256k1.Point.serialize_public_key(pubkey) == t.account0recvPublicKeyHex + end + end + describe "BIP32 tests" do # Test 1 test "BIP32 tests 1: successfully convert xprv to xpub." do @@ -642,6 +851,26 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do end end + describe "Switch extended key prefixes" do + test "switch_prefix" do + for t <- @switch_prefixes_examples do + {:ok, t_xprv} = ExtendedKey.parse(t.xprv) + {:ok, t_yprv} = ExtendedKey.parse(t.yprv) + {:ok, t_zprv} = ExtendedKey.parse(t.zprv) + + yprv = ExtendedKey.switch_prefix(t_xprv, :yprv) + zprv = ExtendedKey.switch_prefix(t_xprv, :zprv) + + assert yprv == t_yprv + assert zprv == t_zprv + + xprv = ExtendedKey.switch_prefix(zprv, :xprv) + + assert xprv == t_xprv + end + end + end + describe "Fail on attempt to derive hardened child from pubkey" do test "fail to derive hardened child from pubkey parent" do t = @bip32_test_case_3 @@ -666,6 +895,59 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do # Derivation Path Testing + describe "derive_extended_key/2 using BIP 84 test cases" do + test "test use of deriv path" do + t = @bip84_test_case + + deriv = %ExtendedKey.DerivationPath{ + child_nums: [ + @min_hardened_child_num + 84, + @min_hardened_child_num, + @min_hardened_child_num + ] + } + + {:ok, child_key} = ExtendedKey.derive_extended_key(t.c_rootpriv_obj, deriv) + + assert ExtendedKey.display(child_key) == t.c_xprv + end + + test "successfully derive zpub child key at path m/84'/0'/0'/" do + t = @bip84_test_case + + deriv = %ExtendedKey.DerivationPath{ + child_nums: [ + @min_hardened_child_num + 84, + @min_hardened_child_num, + @min_hardened_child_num + ] + } + + {:ok, xprv} = ExtendedKey.derive_extended_key(t.c_rootpriv_obj, deriv) + {:ok, xpub} = ExtendedKey.to_extended_public_key(xprv) + assert ExtendedKey.display(xpub) == t.c_xpub + end + + test "successfully derive private key WIF at m/84'/0'/0'/0/0" do + t = @bip84_test_case + + deriv = %ExtendedKey.DerivationPath{ + child_nums: [ + @min_hardened_child_num + 84, + @min_hardened_child_num, + @min_hardened_child_num, + 0, + 0 + ] + } + + {:ok, xprv} = ExtendedKey.derive_extended_key(t.c_rootpriv_obj, deriv) + {:ok, privkey} = ExtendedKey.to_private_key(xprv) + + assert Bitcoinex.Secp256k1.PrivateKey.wif!(privkey, :mainnet) == t.c_privkey + end + end + describe "derive_extended_key/2 using BIP 32 test cases" do test "test use of deriv path" do t = @bip32_test_case_1 @@ -674,9 +956,7 @@ defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do child_nums: [@min_hardened_child_num, 1, @min_hardened_child_num + 2, 2] } - {:ok, child_key} = - t.xprv_m_obj - |> ExtendedKey.derive_extended_key(deriv) + {:ok, child_key} = ExtendedKey.derive_extended_key(t.xprv_m_obj, deriv) assert ExtendedKey.display(child_key) == t.xprv_m_0h_1_2h_2 end diff --git a/test/psbt_test.exs b/test/psbt_test.exs index c2ea453..8975b4e 100644 --- a/test/psbt_test.exs +++ b/test/psbt_test.exs @@ -732,8 +732,7 @@ defmodule Bitcoinex.PSBTTest do 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>> + 11, 67, 27, 103, 197, 178, 193, 63, 39, 215, 20, 32, 55, 193, 105>> } } ]