text:
defmodule Bitcoinex.ScriptTest do
  use ExUnit.Case
  doctest Bitcoinex.Script

  alias Bitcoinex.{Script, Utils}
  alias Bitcoinex.Secp256k1.Point

  @raw_multisig_scripts [
    "522103a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af957521036ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640d210311ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef53ae",
    # from testnet tx: c7d1582d4cf85fbd10732002c5bb06068d4b86cfd5cca151ef88104c6702435a
    "52210265e6f7fb614a369c9230912a3bb09c33c5c5be2e1bcfc2293ecaed46708e0b5c2103f546edf7b434b50aa0115c1c82a0f9a96505d9eff55d2fe3b848c4b51c06b6432102908375f301c7ea583f7e113939eab1164abda4ac27898b1cf78abf1c82f02da953ae",
    # from tx: 336b9de8941d945a41f9d982eaadb344b3457d22edf43a1aaec29252a58e6566
    "52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae",
    # from tx: 578e8ab81e6eff430fd37eaa460377387d75f0221881fe13cf9e5b400d98b0e3
    "52210353d36c27d345e0cda30800d769dbf239b16d19e0a28821c092ad277f1ae569f121034e2c48afb6d9cd6446442eeb6867a7787f16d28758b88ec99fa178d941930c6b2103142776b8982a94855686f175a31edbdc48336741e08b07c4cad5bc6852f6197353ae",
    "522103af40079dabd57915a41fc676c9a1e8e9278b9aa7e3b2cf7db63435e357bd589521024a84c829eb98eb69b37be6adcdc206b462b4c9549a0db2dbc91279a2f031a50a2102a5264fbc1be9b9a7d03d9637a5534ce8d59a06c4c1f30802fe52e7bf6c1dd97153ae"
  ]

  @raw_multisigs_with_data [
    # from testnet tx: c7d1582d4cf85fbd10732002c5bb06068d4b86cfd5cca151ef88104c6702435a
    %{
      script_hex:
        "52210265e6f7fb614a369c9230912a3bb09c33c5c5be2e1bcfc2293ecaed46708e0b5c2103f546edf7b434b50aa0115c1c82a0f9a96505d9eff55d2fe3b848c4b51c06b6432102908375f301c7ea583f7e113939eab1164abda4ac27898b1cf78abf1c82f02da953ae",
      network: :testnet,
      wsh_addr: "",
      sh_addr: "2NFhEhTAiXJ8z8yEYnXe6SJX5E8bRgB5ZgY"
    },
    # from tx: 336b9de8941d945a41f9d982eaadb344b3457d22edf43a1aaec29252a58e6566
    %{
      script_hex:
        "52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae",
      network: :mainnet,
      wsh_addr: "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej",
      sh_addr: ""
    },
    # from tx: 578e8ab81e6eff430fd37eaa460377387d75f0221881fe13cf9e5b400d98b0e3
    %{
      script_hex:
        "52210353d36c27d345e0cda30800d769dbf239b16d19e0a28821c092ad277f1ae569f121034e2c48afb6d9cd6446442eeb6867a7787f16d28758b88ec99fa178d941930c6b2103142776b8982a94855686f175a31edbdc48336741e08b07c4cad5bc6852f6197353ae",
      network: :mainnet,
      wsh_addr: "bc1q95wu94w9a08yzed4vc4v9z8v3zndltmgurkh893uy2qdk0lacneqawrq36",
      sh_addr: ""
    },
    # from tx: 578e8ab81e6eff430fd37eaa460377387d75f0221881fe13cf9e5b400d98b0e3
    %{
      script_hex:
        "522103af40079dabd57915a41fc676c9a1e8e9278b9aa7e3b2cf7db63435e357bd589521024a84c829eb98eb69b37be6adcdc206b462b4c9549a0db2dbc91279a2f031a50a2102a5264fbc1be9b9a7d03d9637a5534ce8d59a06c4c1f30802fe52e7bf6c1dd97153ae",
      network: :mainnet,
      wsh_addr: "bc1qamfphjerjc4jdmmnwku26x8e49m2455x65kldefw79j6j24fya9sy5r9jg",
      sh_addr: ""
    }
  ]

  @p2pk_scripts [
    # from tx df2b060fa2e5e9c8ed5eaf6a45c13753ec8c63282b2688322eba40cd98ea067a
    "4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac",
    # from tx 2d05f0c9c3e1c226e63b5fac240137687544cf631cd616fd34fd188fc9020866
    "4104e70a02f5af48a1989bf630d92523c9d14c45c75f7d1b998e962bff6ff9995fc5bdb44f1793b37495d80324acba7c8f537caaf8432b8d47987313060cc82d8a93ac",
    # from tx da69323ec33972675d9594b6569983bfc2257bced36d8df541a2aadfe31db016
    "21035ce3ee697cd5148e12ab7bb45c1ef4dd5ee2bf4867d9d35135e214e073211344ac"
  ]

  @p2sh_scripts [
    # from tx db11e3569da3583f3001514163b5f1c4e0556dd550bfa4518f15095258d43bf3
    "a9148a7810adbe753308a8ccae63f81841c92554174487",
    # from tx 6dd1763922d5bf62a90d61b4dc1bce41df3f2dd599cbd94b5a75264b3dd2c7b2
    "a9148b56848ee759ae7370ca2af7d4d244db2dd3de6b87",
    "a9140549064a44f4864730c5f80495b062478cd6e68587"
  ]

  @p2pkh_scripts [
    # from tx 0a6140bbf75e73f11b90c4dabf71f83394d493d635c2bbf19d207fb821de74f5
    "76a91408be653b5582bb9c1b85ab1da70906946c90acc588ac",
    # from tx be626ca78e84876dec63622f9de36264925d8390fbaae65948bf459c623ede7f
    "76a9140609f52a6954adfdc13ecbcf07713c5a12aee8f988ac",
    "76a91491c9753821600b37ced3d0b04de88cd2cea06c2588ac",
    # from tx 6dd1763922d5bf62a90d61b4dc1bce41df3f2dd599cbd94b5a75264b3dd2c7b2
    "76a9146c25434c537c6e69fa3ebf4ea47f00b5e3e1d6b988ac"
  ]

  @p2wpkh_scripts [
    # from tx 16940cf0fd17da81f47fbae29a6e1eaad844fd45e772292022d0f066db43f007
    "00146756d75cc116c710bdfa4cbc12dd3f9629d5d61f",
    # from tx 0176631c41a73cf1391822a9dc50a3719e96c33f4f2e51e7f9ea65afd87781b6
    "00140d3953d2b732f7a293e3c4dc2b6b309f71b9c692",
    # from tx 6dd1763922d5bf62a90d61b4dc1bce41df3f2dd599cbd94b5a75264b3dd2c7b2
    "0014c944072f05f3dadf417c90f3f19c3b784562b116"
  ]

  @p2wsh_scripts [
    # from tx d3bde81de54f8ace1cf98bab6b06772f752979e3d4e7866691fcb2965d9c766c
    "0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d",
    # from tx c561120cdbe161186a3264aeda32ee964e4615a0831bbd204dd36ff9460d6af7
    "0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d"
  ]

  # from
  # https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki#test-vectors-for-v0-v16-native-segregated-witness-addresses
  @bip350_test_vectors [
    %{
      b32: "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",
      hex: "0014751e76e8199196d454941c45d1b3a323f1433bd6",
      net: :mainnet
    },
    %{
      b32: "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
      hex: "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262",
      net: :testnet
    },
    %{
      b32: "bc1pqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsyjer9e",
      hex: "51200101010101010101010101010101010101010101010101010101010101010101",
      net: :mainnet
    },
    %{
      b32: "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
      hex: "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
      net: :testnet
    },
    %{
      b32: "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c",
      hex: "5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
      net: :testnet
    },
    %{
      b32: "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0",
      hex: "512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
      net: :mainnet
    }
  ]

  @bip_350_invalid_test_vectors [
    # Invalid human-readable part
    "tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut",
    # Invalid checksum (Bech32 instead of Bech32m)
    "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd",
    # Invalid checksum (Bech32 instead of Bech32m)
    "tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf",
    # Invalid checksum (Bech32 instead of Bech32m)
    "BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL",
    # Invalid checksum (Bech32m instead of Bech32)
    "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh",
    # Invalid checksum (Bech32m instead of Bech32)
    "tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47",
    # Invalid character in checksum
    "bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4",
    # Invalid witness version
    "BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R",
    # Invalid program length (1 byte)
    "bc1pw5dgrnzv",
    # Invalid program length (41 bytes)
    "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav",
    # Invalid program length for witness version 0 (per BIP141)
    "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
    # Mixed case
    "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq",
    # zero padding of more than 4 bits
    "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf",
    # Non-zero padding in 8-to-5 conversion
    "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j",
    # Empty data section
    "bc1gmk9yu"
  ]

  describe "test basics functions" do
    test "test new/0 and empty?/1" do
      s = Script.new()
      assert Script.empty?(s)

      {:ok, s} = Script.push_op(s, :op_true)
      assert !Script.empty?(s)
    end

    test "test script_length/1 and byte_length/1" do
      s = Script.new()
      assert Script.script_length(s) == 0
      assert Script.byte_length(s) == 0

      {:ok, s} = Script.push_op(s, :op_true)
      assert Script.script_length(s) == 1
      assert Script.byte_length(s) == 1

      {:ok, s} = Script.push_data(s, <<1, 1, 1, 1>>)
      assert Script.script_length(s) == 3
      assert Script.byte_length(s) == 6

      s_hex = "0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d"
      {:ok, s} = Script.parse_script(s_hex)
      assert Script.script_length(s) == 3
      assert Script.byte_length(s) == 34

      s_hex = "0014a38e224fc2ead8f32b13e3cef6bbf3520f16378c"
      {:ok, s} = Script.parse_script(s_hex)
      assert Script.script_length(s) == 3
      assert Script.byte_length(s) == 22

      s_hex =
        "4de901c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91ceec5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7ec5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc96419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be9bc9958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9"

      {:ok, s} = Script.parse_script(s_hex)
      assert Script.script_length(s) == 2
      assert Script.byte_length(s) == 492

      s_hex =
        "a94c50c5802547372094c58025802547372094c5802547802c9ca07652be7e8025472547372094c5802547802c9ca07652be7e802547372094c5802547802c9ca07652be7e47802c9ca07652be7e6419bc9aa1ac"

      {:ok, s} = Script.parse_script(s_hex)
      assert Script.script_length(s) == 4
      assert Script.byte_length(s) == 84
    end

    test "test get_op_num/1 and get_op_atom/1" do
      assert {:ok, 0x00} == Script.get_op_num(:op_0)
      assert {:ok, 0x60} == Script.get_op_num(:op_16)
      assert {:ok, 0xFE} == Script.get_op_num(:op_pubkey)
      assert :error == Script.get_op_num(:op_eval)

      assert {:ok, :op_0} == Script.get_op_atom(0x00)
      assert {:ok, :op_pushdata1} == Script.get_op_atom(0x4C)
      assert {:ok, :op_invalidopcode} == Script.get_op_atom(0xFF)
      assert {:ok, :op_nop1} == Script.get_op_atom(0xB0)

      assert 5 == Script.get_op_atom(5)
      assert 75 == Script.get_op_atom(75)
      assert :error == Script.get_op_atom(-1)
    end

    test "test pop/1" do
      s = Script.new()
      assert Script.pop(s) == nil

      {:ok, s} = Script.push_op(s, :op_true)
      {:ok, 81, s1} = Script.pop(s)
      assert Script.empty?(s1)

      {:ok, s} = Script.push_data(s1, <<1, 1, 1, 1, 1, 1, 1>>)
      {:ok, 7, s1} = Script.pop(s)
      {:ok, <<1, 1, 1, 1, 1, 1, 1>>, s2} = Script.pop(s1)
      assert Script.pop(s2) == nil
    end
  end

  describe "test push_op/2" do
    test "test pushing and popping ops by atom" do
      s = Script.new()

      {:ok, s} = Script.push_op(s, :op_true)
      {:ok, s} = Script.push_op(s, :op_false)
      {:ok, s} = Script.push_op(s, :op_checksig)

      assert Script.script_length(s) == 3
      assert Script.byte_length(s) == 3

      {:ok, o1, s1} = Script.pop(s)
      {:ok, o2, s2} = Script.pop(s1)
      {:ok, o3, s3} = Script.pop(s2)

      # checksig
      assert o1 == 0xAC
      # false
      assert o2 == 0x00
      # true
      assert o3 == 0x51
      # empty
      assert Script.empty?(s3)
    end

    test "test pushing and popping ops by integer" do
      s = Script.new()
      # true
      {:ok, s} = Script.push_op(s, 0x51)
      # false
      {:ok, s} = Script.push_op(s, 0x00)
      # checksig
      {:ok, s} = Script.push_op(s, 0xAC)

      assert Script.script_length(s) == 3
      assert Script.byte_length(s) == 3

      {:ok, o1, s1} = Script.pop(s)
      {:ok, o2, s2} = Script.pop(s1)
      {:ok, o3, _s3} = Script.pop(s2)

      assert o1 == 0xAC
      assert o2 == 0x00
      assert o3 == 0x51
    end
  end

  describe "push_data/2" do
    test "push public key" do
      s = Script.new()
      hex = "033b15e1b8c51bb947a134d17addc3eb6abbda551ad02137699636f907ad7e0f1a"
      bin = Base.decode16!(hex, case: :lower)
      {:ok, s} = Script.push_data(s, bin)
      {:ok, len, s2} = Script.pop(s)
      {:ok, pk, _s3} = Script.pop(s2)

      assert len == 33
      assert pk == bin
    end

    test "push pubkey hash" do
      s = Script.new()
      hex = "d1914384b57de2944ce1b6a90adf2f7b72cfe61e"
      bin = Base.decode16!(hex, case: :lower)
      Script.push_data(s, bin)
    end

    test "push data 1" do
      s = Script.new()

      hex =
        "c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9"

      bin = Utils.hex_to_bin(hex)
      {:ok, s} = Script.push_data(s, bin)

      {:ok, op2, s1} = Script.pop(s)
      {:ok, bin2, _s2} = Script.pop(s1)

      assert op2 == 76
      assert bin2 == bin
    end

    test "push data 2" do
      s = Script.new()

      hex =
        "c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91ceec5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7ec5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc96419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be9bc9958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9"

      bin = Utils.hex_to_bin(hex)
      {:ok, s} = Script.push_data(s, bin)

      {:ok, op2, s1} = Script.pop(s)
      {:ok, bin2, _s2} = Script.pop(s1)

      assert op2 == 77
      assert bin2 == bin
    end
  end

  describe "test specific script creation and identification" do
    test "test parse p2pk from uncompressed key" do
      s = Script.new()
      # from tx df2b060fa2e5e9c8ed5eaf6a45c13753ec8c63282b2688322eba40cd98ea067a
      hex =
        "04184f32b212815c6e522e66686324030ff7e5bf08efb21f8b00614fb7690e19131dd31304c54f37baa40db231c918106bb9fd43373e37ae31a0befc6ecaefb867"

      bin = Base.decode16!(hex, case: :lower)
      {:ok, s1} = Script.push_op(s, :op_checksig)
      {:ok, s1} = Script.push_data(s1, bin)

      assert Script.is_p2pk?(s1)

      {:ok, s2} = Script.create_p2pk(bin)
      assert s2 == s1

      # from tx da69323ec33972675d9594b6569983bfc2257bced36d8df541a2aadfe31db016
      hex = "035ce3ee697cd5148e12ab7bb45c1ef4dd5ee2bf4867d9d35135e214e073211344"
      bin = Base.decode16!(hex, case: :lower)
      {:ok, s3} = Script.push_op(s, :op_checksig)
      {:ok, s3} = Script.push_data(s3, bin)

      assert Script.is_p2pk?(s3)

      {:ok, s4} = Script.create_p2pk(bin)
      assert s4 == s3
    end

    test "test is_p2sh? and create_p2sh" do
      s = Script.new()
      hex = "d1914384b57de2944ce1b6a90adf2f7b72cfe61e"
      bin = Base.decode16!(hex, case: :lower)
      {:ok, s} = Script.push_op(s, 0x87)
      {:ok, s} = Script.push_data(s, bin)
      {:ok, s} = Script.push_op(s, 0xA9)

      {:ok, s2} = Script.create_p2sh(bin)

      assert Script.is_p2sh?(s)
      assert s2 == s
    end

    test "test is_p2sh? with non p2sh" do
      # p2pkh from tx 0a6140bbf75e73f11b90c4dabf71f83394d493d635c2bbf19d207fb821de74f5
      s_hex = "76a91408be653b5582bb9c1b85ab1da70906946c90acc588ac"
      {:ok, s} = Script.parse_script(s_hex)

      assert !Script.is_p2sh?(s)

      # p2wsh from tx d3bde81de54f8ace1cf98bab6b06772f752979e3d4e7866691fcb2965d9c766c
      s_hex = "0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d"
      {:ok, s} = Script.parse_script(s_hex)

      assert !Script.is_p2sh?(s)

      # p2wpkh from tx 16940cf0fd17da81f47fbae29a6e1eaad844fd45e772292022d0f066db43f007
      s_hex = "00146756d75cc116c710bdfa4cbc12dd3f9629d5d61f"
      {:ok, s} = Script.parse_script(s_hex)

      assert !Script.is_p2sh?(s)
    end

    test "test is_p2pkh? and create_p2pkh" do
      s = Script.new()
      hex = "033b15e1b8c51bb947a134d17addc3eb6abbda551ad02137699636f907ad7e0f1a"
      bin = Base.decode16!(hex, case: :lower)
      h160 = Utils.hash160(bin)
      {:ok, s} = Script.push_op(s, :op_checksig)
      {:ok, s} = Script.push_op(s, :op_equalverify)
      {:ok, s} = Script.push_data(s, h160)
      {:ok, s} = Script.push_op(s, :op_hash160)
      {:ok, s} = Script.push_op(s, :op_dup)

      {:ok, s2} = Script.create_p2pkh(h160)

      assert Script.is_p2pkh?(s)
      assert s2 == s
    end

    test "test is_p2pkh? with non p2pkh" do
      # p2wsh from tx d3bde81de54f8ace1cf98bab6b06772f752979e3d4e7866691fcb2965d9c766c
      s_hex = "0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d"
      {:ok, s} = Script.parse_script(s_hex)

      assert !Script.is_p2pkh?(s)

      # p2sh from tx db11e3569da3583f3001514163b5f1c4e0556dd550bfa4518f15095258d43bf3
      s_hex = "a9148a7810adbe753308a8ccae63f81841c92554174487"
      {:ok, s} = Script.parse_script(s_hex)

      assert !Script.is_p2pkh?(s)

      # p2wpkh from tx 16940cf0fd17da81f47fbae29a6e1eaad844fd45e772292022d0f066db43f007
      s_hex = "00146756d75cc116c710bdfa4cbc12dd3f9629d5d61f"
      {:ok, s} = Script.parse_script(s_hex)

      assert !Script.is_p2pkh?(s)
    end

    test "test is_p2wpkh? and create_p2wpkh" do
      s = Script.new()
      hex = "033b15e1b8c51bb947a134d17addc3eb6abbda551ad02137699636f907ad7e0f1a"
      bin = Base.decode16!(hex, case: :lower)
      h160 = Utils.hash160(bin)

      {:ok, s} = Script.push_data(s, h160)
      {:ok, s} = Script.push_op(s, 0x00)

      {:ok, s2} = Script.create_p2wpkh(h160)

      assert Script.is_p2wpkh?(s)
      assert s2 == s
    end

    test "test is_p2wpkh? with non p2wpkh" do
      # p2wsh from tx d3bde81de54f8ace1cf98bab6b06772f752979e3d4e7866691fcb2965d9c766c
      s_hex = "0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d"
      {:ok, s} = Script.parse_script(s_hex)

      assert !Script.is_p2pkh?(s)

      # p2sh from tx db11e3569da3583f3001514163b5f1c4e0556dd550bfa4518f15095258d43bf3
      s_hex = "a9148a7810adbe753308a8ccae63f81841c92554174487"
      {:ok, s} = Script.parse_script(s_hex)

      assert !Script.is_p2pkh?(s)

      # p2wpkh from tx 16940cf0fd17da81f47fbae29a6e1eaad844fd45e772292022d0f066db43f007
      s_hex = "00146756d75cc116c710bdfa4cbc12dd3f9629d5d61f"
      {:ok, s} = Script.parse_script(s_hex)

      assert !Script.is_p2pkh?(s)
    end

    test "test create_p2sh_p2wpkh" do
      s = Script.new()
      hex = "033b15e1b8c51bb947a134d17addc3eb6abbda551ad02137699636f907ad7e0f1a"
      bin = Base.decode16!(hex, case: :lower)
      h160 = Utils.hash160(bin)

      {:ok, p2wpkh} = Script.push_data(s, h160)
      {:ok, p2wpkh} = Script.push_op(p2wpkh, 0x00)
      sbin = Script.serialize_script(p2wpkh)
      sh = Utils.hash160(sbin)

      {:ok, p2sh} = Script.push_op(s, 0x87)
      {:ok, p2sh} = Script.push_data(p2sh, sh)
      {:ok, p2sh} = Script.push_op(p2sh, 0xA9)

      {:ok, p2sh2, p2wpkh2} = Script.create_p2sh_p2wpkh(h160)

      assert Script.is_p2sh?(p2sh)
      assert p2sh == p2sh2
      assert Script.is_p2wpkh?(p2wpkh2)
      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
      for ms <- @raw_multisig_scripts do
        {:ok, multi} = Script.parse_script(ms)
        assert Script.is_multi?(multi)
      end
    end

    test "test create_multi" do
      # these keys will be added to multisig as compressed keys
      {:ok, pk1} =
        Point.parse_public_key(
          "04a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd"
        )

      {:ok, pk2} =
        Point.parse_public_key(
          "046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187"
        )

      {:ok, pk3} =
        Point.parse_public_key(
          "0411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e83"
        )

      {:ok, multi} = Script.create_multi(2, [pk1, pk2, pk3])
      assert Script.is_multi?(multi)

      {:ok, pk1} =
        Point.parse_public_key(
          "03af40079dabd57915a41fc676c9a1e8e9278b9aa7e3b2cf7db63435e357bd5895"
        )

      {:ok, pk2} =
        Point.parse_public_key(
          "024a84c829eb98eb69b37be6adcdc206b462b4c9549a0db2dbc91279a2f031a50a"
        )

      {:ok, pk3} =
        Point.parse_public_key(
          "02a5264fbc1be9b9a7d03d9637a5534ce8d59a06c4c1f30802fe52e7bf6c1dd971"
        )

      {:ok, multi} = Script.create_multi(1, [pk1, pk2, pk3])
      assert Script.is_multi?(multi)

      {:ok, multi} = Script.create_multi(2, [pk1, pk2, pk3])
      assert Script.is_multi?(multi)

      {:ok, multi} = Script.create_multi(3, [pk1, pk2, pk3])
      assert Script.is_multi?(multi)

      # m cant be greater than n in m-of-n
      {:error, _msg} = Script.create_multi(4, [pk1, pk2, pk3])
      # m cant be 0
      {:error, _msg} = Script.create_multi(0, [pk1, pk2, pk3])
      # m cant be greater than n in m-of-n
      {:error, _msg} = Script.create_multi(0, [])
    end

    test "test create_p2sh_multi" do
      {:ok, pk1} =
        Point.parse_public_key(
          "04a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd"
        )

      {:ok, pk2} =
        Point.parse_public_key(
          "046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187"
        )

      {:ok, pk3} =
        Point.parse_public_key(
          "0411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e83"
        )

      {:ok, p2sh, multi} = Script.create_p2sh_multi(2, [pk1, pk2, pk3])
      assert Script.is_p2sh?(p2sh)
      assert Script.is_multi?(multi)

      {:ok, pk1} =
        Point.parse_public_key(
          "03af40079dabd57915a41fc676c9a1e8e9278b9aa7e3b2cf7db63435e357bd5895"
        )

      {:ok, pk2} =
        Point.parse_public_key(
          "024a84c829eb98eb69b37be6adcdc206b462b4c9549a0db2dbc91279a2f031a50a"
        )

      {:ok, pk3} =
        Point.parse_public_key(
          "02a5264fbc1be9b9a7d03d9637a5534ce8d59a06c4c1f30802fe52e7bf6c1dd971"
        )

      {:ok, p2sh, multi} = Script.create_p2sh_multi(2, [pk1, pk2, pk3])
      assert Script.is_p2sh?(p2sh)
      assert Script.is_multi?(multi)

      # m cant be greater than n in m-of-n
      {:error, _msg} = Script.create_p2sh_multi(4, [pk1, pk2, pk3])
      # m cant be 0
      {:error, _msg} = Script.create_p2sh_multi(0, [pk1, pk2, pk3])
      # m cant be greater than n in m-of-n
      {:error, _msg} = Script.create_p2sh_multi(0, [])
    end

    test "test create_p2wsh_multi" do
      {:ok, pk1} =
        Point.parse_public_key(
          "04a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd"
        )

      {:ok, pk2} =
        Point.parse_public_key(
          "046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187"
        )

      {:ok, pk3} =
        Point.parse_public_key(
          "0411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e83"
        )

      {:ok, p2wsh, multi} = Script.create_p2wsh_multi(2, [pk1, pk2, pk3])
      assert Script.is_p2wsh?(p2wsh)
      assert Script.is_multi?(multi)

      {:ok, pk1} =
        Point.parse_public_key(
          "03af40079dabd57915a41fc676c9a1e8e9278b9aa7e3b2cf7db63435e357bd5895"
        )

      {:ok, pk2} =
        Point.parse_public_key(
          "024a84c829eb98eb69b37be6adcdc206b462b4c9549a0db2dbc91279a2f031a50a"
        )

      {:ok, pk3} =
        Point.parse_public_key(
          "02a5264fbc1be9b9a7d03d9637a5534ce8d59a06c4c1f30802fe52e7bf6c1dd971"
        )

      {:ok, p2wsh, multi} = Script.create_p2wsh_multi(2, [pk1, pk2, pk3])
      assert Script.is_p2wsh?(p2wsh)
      assert Script.is_multi?(multi)

      # m cant be greater than n in m-of-n
      {:error, _msg} = Script.create_p2wsh_multi(4, [pk1, pk2, pk3])
      # m cant be 0
      {:error, _msg} = Script.create_p2wsh_multi(0, [pk1, pk2, pk3])
      # m cant be greater than n in m-of-n
      {:error, _msg} = Script.create_p2wsh_multi(0, [])
    end

    test "test create scripts from pubkey" do
      hex = "033b15e1b8c51bb947a134d17addc3eb6abbda551ad02137699636f907ad7e0f1a"
      h160 = hex |> Base.decode16!(case: :lower) |> Utils.hash160()
      {:ok, pubkey} = Point.parse_public_key(hex)

      {:ok, p2pkh} = Script.public_key_to_p2pkh(pubkey)
      assert Script.is_p2pkh?(p2pkh)

      # check correct p2pkh format and pkh
      {:ok, op_dup, rest} = Script.pop(p2pkh)
      {:ok, op_h160, rest} = Script.pop(rest)
      {:ok, len, rest} = Script.pop(rest)
      {:ok, pkh, _rest} = Script.pop(rest)
      assert op_dup == 0x76
      assert op_h160 == 0xA9
      assert len == 20
      assert pkh == h160

      {:ok, p2wpkh} = Script.public_key_to_p2wpkh(pubkey)
      assert Script.is_p2wpkh?(p2wpkh)
      # check pkh is correct
      {:ok, witver, rest} = Script.pop(p2wpkh)
      {:ok, len, rest} = Script.pop(rest)
      {:ok, pkh, rest} = Script.pop(rest)
      assert witver == 0
      assert len == 20
      assert pkh == h160
      assert Script.empty?(rest)

      {:ok, p2sh_p2wpkh, p2wpkh} = Script.public_key_to_p2sh_p2wpkh(pubkey)
      assert Script.is_p2sh?(p2sh_p2wpkh)
      assert Script.is_p2wpkh?(p2wpkh)
    end

    test "test get_script_type/1" do
      for s <- @p2pk_scripts do
        {:ok, script} = Script.parse_script(s)
        assert Script.get_script_type(script) == :p2pk
      end

      for s <- @p2pkh_scripts do
        {:ok, script} = Script.parse_script(s)
        assert Script.get_script_type(script) == :p2pkh
      end

      for s <- @p2sh_scripts do
        {:ok, script} = Script.parse_script(s)
        assert Script.get_script_type(script) == :p2sh
      end

      for s <- @p2wpkh_scripts do
        {:ok, script} = Script.parse_script(s)
        assert Script.get_script_type(script) == :p2wpkh
      end

      for s <- @p2wsh_scripts do
        {:ok, script} = Script.parse_script(s)
        assert Script.get_script_type(script) == :p2wsh
      end
    end
  end

  describe "test parsing scripts" do
    test "test parse pushdata1 script" do
      # pushdata1 <data>
      data_hex =
        "c5802547372094c58025802547372094c5802547802c9ca07652be7e8025472547372094c5802547802c9ca07652be7e802547372094c5802547802c9ca07652be7e47802c9ca07652be7e6419bc9aa1"

      s_hex =
        "4c50c5802547372094c58025802547372094c5802547802c9ca07652be7e8025472547372094c5802547802c9ca07652be7e802547372094c5802547802c9ca07652be7e47802c9ca07652be7e6419bc9aa1"

      bin = data_hex |> Base.decode16!(case: :lower)
      {:ok, s} = Script.parse_script(s_hex)
      {:ok, pushdata, s} = Script.pop(s)
      {:ok, data, s} = Script.pop(s)

      assert pushdata == 0x4C
      assert data == bin
      assert Script.empty?(s)

      # op_hash160 pushdata1 <data> op_checksig
      data_hex =
        "c5802547372094c58025802547372094c5802547802c9ca07652be7e8025472547372094c5802547802c9ca07652be7e802547372094c5802547802c9ca07652be7e47802c9ca07652be7e6419bc9aa1"

      s_hex =
        "a94c50c5802547372094c58025802547372094c5802547802c9ca07652be7e8025472547372094c5802547802c9ca07652be7e802547372094c5802547802c9ca07652be7e47802c9ca07652be7e6419bc9aa1ac"

      bin = data_hex |> Base.decode16!(case: :lower)
      {:ok, s} = Script.parse_script(s_hex)
      {:ok, op160, s} = Script.pop(s)
      {:ok, pushdata, s} = Script.pop(s)
      {:ok, data, s} = Script.pop(s)
      {:ok, cs, s} = Script.pop(s)

      assert op160 == 0xA9
      assert pushdata == 0x4C
      assert data == bin
      assert cs == 0xAC
      assert Script.empty?(s)
    end

    test "test parse pushdata2 script" do
      # pushdata2 <data>
      data_hex =
        "c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91ceec5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7ec5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc96419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be9bc9958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9"

      s_hex =
        "4de901c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91ceec5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7ec5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc96419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be9bc9958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9"

      bin = data_hex |> Base.decode16!(case: :lower)
      {:ok, s} = Script.parse_script(s_hex)
      {:ok, pushdata, s} = Script.pop(s)
      {:ok, data, s} = Script.pop(s)

      assert pushdata == 0x4D
      assert data == bin
      assert Script.empty?(s)

      # hash160 pushdata2 <data> checksig
      data_hex =
        "c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91ceec5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7ec5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc96419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be9bc9958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9"

      s_hex =
        "a94de901c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91ceec5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7ec5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc96419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be9bc9958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9ac"

      bin = data_hex |> Base.decode16!(case: :lower)

      {:ok, s} = Script.parse_script(s_hex)
      {:ok, op_h160, s} = Script.pop(s)
      {:ok, pushdata, s} = Script.pop(s)
      {:ok, data, s} = Script.pop(s)
      {:ok, op_cs, s} = Script.pop(s)

      assert op_h160 == 0xA9
      assert pushdata == 0x4D
      assert data == bin
      assert op_cs == 0xAC
      assert Script.empty?(s)
    end

    test "test parse p2pkh scriptsig" do
      # from tx 8d44970f599588688044a5b52169497be9b2813c0d8f76a19cf693dafc5acfc0
      s_hex =
        "47304402202cd4021840da717ce426f8bcc9dc1f701aa4939391242b0941a2788610caf240022037aa257d0c0b2b3f29f5f9ee29d161bcac701c604cca83df9d50fd7c397d535a012103f0f8011ca3ae27e757d23852c59192e223c62e490d59a709201392b5dfe55c21"

      sig_hex =
        "304402202cd4021840da717ce426f8bcc9dc1f701aa4939391242b0941a2788610caf240022037aa257d0c0b2b3f29f5f9ee29d161bcac701c604cca83df9d50fd7c397d535a01"

      pubkey_hex = "03f0f8011ca3ae27e757d23852c59192e223c62e490d59a709201392b5dfe55c21"
      {:ok, s} = Script.parse_script(s_hex)
      {:ok, push71, s} = Script.pop(s)
      {:ok, sig, s} = Script.pop(s)
      {:ok, push33, s} = Script.pop(s)
      {:ok, pubkey, _s} = Script.pop(s)

      assert push71 == 71
      assert sig == sig_hex |> Base.decode16!(case: :lower)
      assert push33 == 33
      assert pubkey == pubkey_hex |> Base.decode16!(case: :lower)
    end

    test "test parse p2pk from uncompressed key" do
      # from tx df2b060fa2e5e9c8ed5eaf6a45c13753ec8c63282b2688322eba40cd98ea067a
      s_hex =
        "4104184f32b212815c6e522e66686324030ff7e5bf08efb21f8b00614fb7690e19131dd31304c54f37baa40db231c918106bb9fd43373e37ae31a0befc6ecaefb867ac"

      {:ok, s} = Script.parse_script(s_hex)

      assert Script.is_p2pk?(s)

      # from tx da69323ec33972675d9594b6569983bfc2257bced36d8df541a2aadfe31db016
      s_hex = "21035ce3ee697cd5148e12ab7bb45c1ef4dd5ee2bf4867d9d35135e214e073211344ac"
      {:ok, s} = Script.parse_script(s_hex)

      assert Script.is_p2pk?(s)
    end

    test "test parse p2pkh" do
      s_hex = "76a914c58025473720941cee958bca07652be7e6419bc988ac"
      {:ok, s} = Script.parse_script(s_hex)

      assert Script.is_p2pkh?(s)

      # from tx d3bde81de54f8ace1cf98bab6b06772f752979e3d4e7866691fcb2965d9c766c
      s_hex = "76a914c689464b843e9782e54c662f544e452940357a9888ac"
      {:ok, s} = Script.parse_script(s_hex)

      assert Script.is_p2pkh?(s)

      # from tx 0a6140bbf75e73f11b90c4dabf71f83394d493d635c2bbf19d207fb821de74f5
      s_hex = "76a91408be653b5582bb9c1b85ab1da70906946c90acc588ac"
      {:ok, s} = Script.parse_script(s_hex)

      assert Script.is_p2pkh?(s)
    end

    test "test parse p2sh" do
      # from tx 0afe8c093add0610e50f647fa5a5e09d72fbc2eed6da6aab045746b69161def2
      s_hex = "a914cbb5d42faa8e9267f3a4ab9eabde9ebc9016ef8787"
      {:ok, s} = Script.parse_script(s_hex)

      assert Script.is_p2sh?(s)

      # p2wsh from tx d3bde81de54f8ace1cf98bab6b06772f752979e3d4e7866691fcb2965d9c766c
      s_hex = "0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d"
      {:ok, s} = Script.parse_script(s_hex)

      assert !Script.is_p2sh?(s)
    end

    test "test parse p2wpkh" do
      # from tx fd910133a2febe8e0edbd25c908f0a8339afda29ff820a1f845e8dd2dccc5658
      s_hex = "0014a38e224fc2ead8f32b13e3cef6bbf3520f16378c"
      {:ok, s} = Script.parse_script(s_hex)

      assert Script.is_p2wpkh?(s)

      # p2sh from tx 0afe8c093add0610e50f647fa5a5e09d72fbc2eed6da6aab045746b69161def2
      s_hex = "a914cbb5d42faa8e9267f3a4ab9eabde9ebc9016ef8787"
      {:ok, s} = Script.parse_script(s_hex)

      assert !Script.is_p2wpkh?(s)

      # p2wsh from tx d3bde81de54f8ace1cf98bab6b06772f752979e3d4e7866691fcb2965d9c766c
      s_hex = "0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d"
      {:ok, s} = Script.parse_script(s_hex)

      assert !Script.is_p2wpkh?(s)
    end

    test "test parse p2wsh" do
      # from tx d3bde81de54f8ace1cf98bab6b06772f752979e3d4e7866691fcb2965d9c766c
      s_hex = "0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d"
      {:ok, s} = Script.parse_script(s_hex)

      assert Script.is_p2wsh?(s)

      # p2pkh from tx fd910133a2febe8e0edbd25c908f0a8339afda29ff820a1f845e8dd2dccc5658
      s_hex = "0014a38e224fc2ead8f32b13e3cef6bbf3520f16378c"
      {:ok, s} = Script.parse_script(s_hex)

      assert !Script.is_p2wsh?(s)
    end

    test "test parse short script" do
      assert Script.parse_script("") == {:ok, Script.new()}
      assert Script.parse_script("00") == {:ok, %Script{items: [0]}}
      assert Script.parse_script("51") == {:ok, %Script{items: [0x51]}}
      assert Script.parse_script("5151") == {:ok, %Script{items: [0x51, 0x51]}}
    end
  end

  describe "test parse invalid scripts" do
    test "test parse invalid scripts" do
      scripts = [
        "4caa12",
        "004ca112",
        "4c6ac5802547372094c580258025473720941cee958bca07652beaaaa7e6419bc91cee958b802720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9",
        "1",
        "21",
        "31",
        "4c"
      ]

      for hex <- scripts do
        {res, _msg} = Script.parse_script(hex)
        assert res == :error
      end
    end
  end

  describe "test serialize scripts" do
    test "test serialize pushdata1" do
      data_hex =
        "c5802547372094c58025802547372094c5802547802c9ca07652be7e8025472547372094c5802547802c9ca07652be7e802547372094c5802547802c9ca07652be7e47802c9ca07652be7e6419bc9aa1"

      s_hex =
        "4c50c5802547372094c58025802547372094c5802547802c9ca07652be7e8025472547372094c5802547802c9ca07652be7e802547372094c5802547802c9ca07652be7e47802c9ca07652be7e6419bc9aa1"

      {:ok, s1} = Script.parse_script(s_hex)

      bin = data_hex |> Base.decode16!(case: :lower)
      {:ok, s2} = Script.push_data(Script.new(), bin)

      assert s2 == s1
      assert Script.serialize_script(s2) == s_hex |> Base.decode16!(case: :lower)
      assert Script.to_hex(s2) == s_hex

      s_hex =
        "a94c50c5802547372094c58025802547372094c5802547802c9ca07652be7e8025472547372094c5802547802c9ca07652be7e802547372094c5802547802c9ca07652be7e47802c9ca07652be7e6419bc9aa1ac"

      {:ok, s} = Script.parse_script(s_hex)
      assert Script.serialize_script(s) == s_hex |> Base.decode16!(case: :lower)
      assert Script.to_hex(s) == s_hex
    end

    test "test serialize pushdata2" do
      s_hex =
        "4de901c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91ceec5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7ec5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc96419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be9bc9958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9"

      {:ok, s} = Script.parse_script(s_hex)

      assert Script.serialize_script(s) == s_hex |> Base.decode16!(case: :lower)
      assert Script.to_hex(s) == s_hex
    end

    test "test serialize common scripts" do
      s_hex = "0014a38e224fc2ead8f32b13e3cef6bbf3520f16378c"

      {:ok, s} = Script.parse_script(s_hex)

      assert Script.serialize_script(s) == s_hex |> Base.decode16!(case: :lower)
      assert Script.to_hex(s) == s_hex

      s_hex = "0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d"

      {:ok, s} = Script.parse_script(s_hex)

      assert Script.serialize_script(s) == s_hex |> Base.decode16!(case: :lower)
      assert Script.to_hex(s) == s_hex

      s_hex = "a914cbb5d42faa8e9267f3a4ab9eabde9ebc9016ef8787"

      {:ok, s} = Script.parse_script(s_hex)

      assert Script.serialize_script(s) == s_hex |> Base.decode16!(case: :lower)
      assert Script.to_hex(s) == s_hex

      s_hex = "76a914c58025473720941cee958bca07652be7e6419bc988ac"

      {:ok, s} = Script.parse_script(s_hex)

      assert Script.serialize_script(s) == s_hex |> Base.decode16!(case: :lower)
      assert Script.to_hex(s) == s_hex
    end

    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.to_hex(multi) == ms
      end
    end

    test "serialize other scripts" do
      # op_0 op_0 op_0 op_0 op_0 op_1
      s_hex = "000000000051"
      {:ok, s} = Script.parse_script(s_hex)
      assert Script.serialize_script(s) == s_hex |> Base.decode16!(case: :lower)
      assert Script.to_hex(s) == s_hex

      # op_0 op_0 pushbytes_1 <<0x00>>
      s_hex = "00000100"
      {:ok, s} = Script.parse_script(s_hex)
      assert Script.serialize_script(s) == s_hex |> Base.decode16!(case: :lower)
      assert Script.to_hex(s) == s_hex

      # op_0
      s_hex = "00"
      {:ok, s} = Script.parse_script(s_hex)
      assert Script.serialize_script(s) == <<0x00>>
    end
  end

  describe "test display_script/1" do
    test "test display_script/1 with p2pkh" do
      text =
        "OP_DUP OP_HASH160 OP_PUSHBYTES_20 c58025473720941cee958bca07652be7e6419bc9 OP_EQUALVERIFY OP_CHECKSIG"

      s_hex = "76a914c58025473720941cee958bca07652be7e6419bc988ac"
      {:ok, s} = Script.parse_script(s_hex)

      assert Script.display_script(s) == text
    end

    test "display_script/1 with random script" do
      text = "OP_0 OP_PUSHDATA1 OP_16 OP_5 OP_RETURN OP_ROLL OP_CHECKSIG OP_NOP1 0102030405"
      s = %Script{items: [0x00, 0x4C, 0x60, 0x55, 0x6A, 0x7A, 0xAC, 0xB0, <<1, 2, 3, 4, 5>>]}

      assert Script.display_script(s) == text
    end

    test "push data 1" do
      text =
        "OP_PUSHDATA1 c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9"

      s = Script.new()

      hex =
        "c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9"

      bin = Utils.hex_to_bin(hex)
      {:ok, s} = Script.push_data(s, bin)

      assert Script.display_script(s) == text
    end

    test "push data 2" do
      text =
        "OP_PUSHDATA2 c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91ceec5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7ec5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc96419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be9bc9958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9"

      s = Script.new()

      hex =
        "c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91ceec5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7ec5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be7e6419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc96419bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9bc91cee958bc58025473720941cee958bca07652be7e6419bc9ca07652c5802547372094c58025478025473720941cee958bca07652be7e6419bc91cee958b8025473720941cee958bca07652be7e6419bc91cee958b3720941cee958bca07652be9bc9958bc58025473720941cee958bca07652be7e6419bc9ca07652be7e6419bc9"

      bin = Utils.hex_to_bin(hex)
      {:ok, s} = Script.push_data(s, bin)

      assert Script.display_script(s) == text
    end
  end

  describe "test to_address/2" do
    test "test p2sh_p2wpkh address" do
      addr = "3HTC7s9dwBzK9Gn9mzejanBV25i35PvGSQ"
      p2wpkh_addr = "bc1q24sn878fl3s6q8ryted85zskyn0ffyl043pphf"
      hex = "034c3773e6ee01be50be219e5dd14179f0feb0c1fc8cd25fd3cb3ca37f607a4534"
      h160 = hex |> Base.decode16!(case: :lower) |> Utils.hash160()
      {:ok, p2sh, p2wpkh} = Script.create_p2sh_p2wpkh(h160)
      assert Script.to_address(p2sh, :mainnet) == {:ok, addr}
      assert Script.to_address(p2wpkh, :mainnet) == {:ok, p2wpkh_addr}

      addr = "3KjjkKUqDSqPCeAhTFP57Xp87JKsdqJLYH"
      p2wpkh_addr = "bc1qlqhcjnnu2g8fwy9vlju0mtseqf50e95hcz4vne"
      hex = "035d03803d60e65786c328c6ef0077c3b2d017542d6f3a67e23f21568d30f8f619"
      h160 = hex |> Base.decode16!(case: :lower) |> Utils.hash160()
      {:ok, p2sh, p2wpkh} = Script.create_p2sh_p2wpkh(h160)
      assert Script.to_address(p2sh, :mainnet) == {:ok, addr}
      assert Script.to_address(p2wpkh, :mainnet) == {:ok, p2wpkh_addr}
    end

    test "test p2pkh address" do
      # from tx 1af0fbe9141371e29ab870121a3d9ae361d6664d789e367e6341e8a4b3311ea0
      addr = "12wRAwmwVBXrnquwwc8uH5xHT7ExaP6gU3"
      hex = "0247c446b01e77fc0318be2db38f97b441b98bb171cdb467a2efee7276a760ea58"
      h160 = hex |> Base.decode16!(case: :lower) |> Utils.hash160()
      {:ok, s} = Script.create_p2pkh(h160)
      assert Script.to_address(s, :mainnet) == {:ok, addr}

      # from tx d531beedb3c4e0996e3df72318f174505bb0b4f99c0a7c70ba1fcd0f27e45fa8
      addr = "133qBf9RgtwAyn11mrLHPWydTwSsfhwsav"
      hex = "0230016a9764716395085ed329b00928bf04d36f2b803f5cd457df021db42a59df"
      h160 = hex |> Base.decode16!(case: :lower) |> Utils.hash160()
      {:ok, s} = Script.create_p2pkh(h160)
      assert Script.to_address(s, :mainnet) == {:ok, addr}

      # from testnet tx d87beaa61c425eb5d6b4687f8d186df5e4a764483b3a616032741ac0615405ec
      addr = "mrHhy9DgpBbDLoJsACv4QXXY7f2B5Fq5o1"
      hex = "037ed58c914720772c59f7a1e7e76fba0ef95d7c5667119798586301519b9ad2cf"
      h160 = hex |> Base.decode16!(case: :lower) |> Utils.hash160()
      {:ok, s} = Script.create_p2pkh(h160)
      assert Script.to_address(s, :testnet) == {:ok, addr}
    end

    test "test p2wpkh address" do
      # from tx 915924af3d478a1ca5e32b81fe327ee78b24d8475ed90666242dfd4bf52fc33d
      addr = "bc1qtvdt23u5mpkkaxw596s5d5hcjm2qgs68p4k9qe"
      hex = "03e6cf64144a2243816151623a5ddaf47b3be771f4d5e450dfb93ea38372af0ceb"
      h160 = hex |> Base.decode16!(case: :lower) |> Utils.hash160()
      {:ok, s} = Script.create_p2wpkh(h160)
      assert Script.to_address(s, :mainnet) == {:ok, addr}

      # from testnet tx 8bcb4bef012fff4191919f5615b894f43b940bd625162018b0d710c71a9b2603
      addr = "tb1qtk3vfds84nvcrlxucu5a8p8dqz2k6w5e8h9m8d"
      hex = "02ca6a71b4f16b75763cb5467666a94972d18ebda3baf296b37149ffea78dc6129"
      h160 = hex |> Base.decode16!(case: :lower) |> Utils.hash160()
      {:ok, s} = Script.create_p2wpkh(h160)
      assert Script.to_address(s, :testnet) == {:ok, addr}
    end

    test "test p2wsh address" do
      # tx from d3bde81de54f8ace1cf98bab6b06772f752979e3d4e7866691fcb2965d9c766c
      addr = "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej"

      redeem_script =
        "52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae"

      h256 = redeem_script |> Base.decode16!(case: :lower) |> Utils.sha256()
      {:ok, s} = Script.create_p2wsh(h256)
      assert Script.to_address(s, :mainnet) == {:ok, addr}
    end

    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)

        if multi.sh_addr != "" do
          {:ok, p2sh, multi_script} = Script.create_p2sh_multi(m, pks)

          {: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_multi(m, pks)

          {:ok, addr} = Script.to_address(p2wsh, multi.network)

          assert multi_script == ms
          assert addr == multi.wsh_addr
        end
      end
    end

    test "test bip350 test vectors" do
      for t <- @bip350_test_vectors do
        {:ok, s} = Script.parse_script(t.hex)
        assert Script.to_address(s, t.net) == {:ok, t.b32}
      end
    end

    test "test invalid bip350 test vectors" do
      for t <- @bip_350_invalid_test_vectors do
        assert {:error, _} = Script.parse_script(t)
      end
    end
  end

  describe "test from_address" do
    test "test from_address p2sh" do
      addr = "3HTC7s9dwBzK9Gn9mzejanBV25i35PvGSQ"
      {:ok, script, network} = Script.from_address(addr)
      assert Script.is_p2sh?(script)
      assert network == :mainnet

      addr = "3KjjkKUqDSqPCeAhTFP57Xp87JKsdqJLYH"
      {:ok, script, network} = Script.from_address(addr)
      assert Script.is_p2sh?(script)
      assert network == :mainnet

      # from tx 266dff8196f461bbdaeef2e3f36d0d488ddaef7784c4c30c1c10f61c50c946f1
      addr = "38znSNNyb7vouwY5b8CptywqkAG98Fpbqa"
      {:ok, script, network} = Script.from_address(addr)
      assert Script.is_p2sh?(script)
      assert network == :mainnet

      # from testnet tx baa8253599aa83d12843ee99035b97c90944c2e4b58cb956f1b0b9245290cf4b
      addr = "2Mt4uopJDw128m9RDj41Y3JiFDM6jH38ZCL"
      {:ok, script, network} = Script.from_address(addr)
      assert Script.is_p2sh?(script)
      assert network == :testnet

      # from testnet tx baa8253599aa83d12843ee99035b97c90944c2e4b58cb956f1b0b9245290cf4b
      addr = "2MxLK9iAH3kXPGDqVTACpWV4uJ5htXhzdJa"
      {:ok, script, network} = Script.from_address(addr)
      assert Script.is_p2sh?(script)
      assert network == :testnet
    end

    test "test from_address p2pkh" do
      # from tx 0a110fa84e4a25c35612019cadaea694925ebb3227da922ee2b013f9200465aa
      addr = "12wRAwmwVBXrnquwwc8uH5xHT7ExaP6gU3"
      {:ok, script, network} = Script.from_address(addr)
      assert Script.is_p2pkh?(script)
      assert network == :mainnet

      # from tx 49e714b8cc3b76c72876b1c358422f05139c3f57c26b9872bee79825ad6c4edc
      addr = "133qBf9RgtwAyn11mrLHPWydTwSsfhwsav"
      {:ok, script, network} = Script.from_address(addr)
      assert Script.is_p2pkh?(script)
      assert network == :mainnet

      # from testnet tx 3139a85541275582ce40afe906eb6d70c3ea9f244c5a793b27bcbba1a8146279
      addr = "mrHhy9DgpBbDLoJsACv4QXXY7f2B5Fq5o1"
      {:ok, script, network} = Script.from_address(addr)
      assert Script.is_p2pkh?(script)
      assert network == :testnet

      # from testnet tx 2af39706c2b96da6d46544459e679f3f8c31f09b3c004a718080d4a5d25059dd
      addr = "mfWxJ45yp2SFn7UciZyNpvDKrzbhyfKrY8"
      {:ok, script, network} = Script.from_address(addr)
      assert Script.is_p2pkh?(script)
      assert network == :testnet

      # from testnet tx 2af39706c2b96da6d46544459e679f3f8c31f09b3c004a718080d4a5d25059dd
      addr = "mxVFsFW5N4mu1HPkxPttorvocvzeZ7KZyk"
      {:ok, script, network} = Script.from_address(addr)
      assert Script.is_p2pkh?(script)
      assert network == :testnet
    end

    test "test from_address p2wsh" do
      # from tx 576330af0ca5ff19063d0abb56a5f2c68284bd25b4067dd0e05582ece53998f0
      addr = "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej"
      {:ok, script, network} = Script.from_address(addr)
      assert Script.is_p2wsh?(script)
      assert network == :mainnet

      # from tx 1b4895f3d5d11e4b5557ac19b91e250e0e287c4042c78ef07e439088486d3ca6
      addr = "bc1ql8ruf3d5yuuwau49egx0ljcsu7enk52pvyy2jt9vdpsl2zxdefzs0xdexp"
      {:ok, script, network} = Script.from_address(addr)
      assert Script.is_p2wsh?(script)
      assert network == :mainnet

      # from testnet tx 45e4999d43aacc61ba182f700d2e7ecf62dde579f05c2f0021d3784122673783
      addr = "tb1qxhagtd0mj06ktjpsst9lluyf4yytrm5yh4qjccc5ledfuuwhcyhqppu84v"
      {:ok, script, network} = Script.from_address(addr)
      assert Script.is_p2wsh?(script)
      assert network == :testnet

      # from testnet tx 6a9e92240f4a8e2e30a31bfdd879f9647bf0ec44df77e1260983da64d411bff5
      addr = "tb1qmexazupzk6hq6xhu0kdtx4ejj2gjrggs3qgsmq0fye7mjkeja85qed89yv"
      {:ok, script, network} = Script.from_address(addr)
      assert Script.is_p2wsh?(script)
      assert network == :testnet
    end

    test "test from_address p2wpkh" do
      addr = "tb1qtk3vfds84nvcrlxucu5a8p8dqz2k6w5e8h9m8d"
      {:ok, script, network} = Script.from_address(addr)
      assert Script.is_p2wpkh?(script)
      assert network == :testnet

      addr = "bc1qtvdt23u5mpkkaxw596s5d5hcjm2qgs68p4k9qe"
      {:ok, script, network} = Script.from_address(addr)
      assert Script.is_p2wpkh?(script)
      assert network == :mainnet
    end

    test "test bip350 test vectors" do
      for t <- @bip350_test_vectors do
        {:ok, s, net} = Script.from_address(t.b32)
        assert Script.to_hex(s) == t.hex
        assert net == t.net
      end
    end
  end

  describe "test extract multisig policy" 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)

        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)

        assert ms == ms2
      end
    end
  end

  describe "full tests" do
    test "test parse, serialize and create addresses for multisig" do
      # from tx 0a6140bbf75e73f11b90c4dabf71f83394d493d635c2bbf19d207fb821de74f5
      redeem_script =
        "52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae"

      p2wsh_addr = "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej"
      {:ok, s} = Script.parse_script(redeem_script)

      {:ok, p2wsh} =
        s
        |> Script.serialize_script()
        |> Utils.sha256()
        |> Script.create_p2wsh()

      assert Script.to_address(p2wsh, :mainnet) == {:ok, p2wsh_addr}

      {:ok, op2, s} = Script.pop(s)
      {:ok, push33, s} = Script.pop(s)
      {:ok, pk1, s} = Script.pop(s)
      {:ok, push33_, s} = Script.pop(s)
      {:ok, pk2, s} = Script.pop(s)
      {:ok, push33__, s} = Script.pop(s)
      {:ok, pk3, s} = Script.pop(s)
      {:ok, op3, s} = Script.pop(s)
      {:ok, op_cms, s} = Script.pop(s)

      {:ok, pubkey1} = Bitcoinex.Secp256k1.Point.parse_public_key(pk1)
      {:ok, pubkey2} = Bitcoinex.Secp256k1.Point.parse_public_key(pk2)
      {:ok, pubkey3} = Bitcoinex.Secp256k1.Point.parse_public_key(pk3)

      assert Script.empty?(s)
      assert push33 == 33 and push33_ == 33 and push33__ == 33
      assert op2 == 0x52
      assert op3 == 0x53
      assert op_cms == 0xAE

      assert Bitcoinex.Secp256k1.Point.sec(pubkey1) == pk1
      assert Bitcoinex.Secp256k1.Point.sec(pubkey2) == pk2
      assert Bitcoinex.Secp256k1.Point.sec(pubkey3) == pk3
    end
  end
end


text:
defmodule Bitcoinex.SegwitTest do
  use ExUnit.Case
  doctest Bitcoinex.Segwit

  alias Bitcoinex.Segwit
  import Bitcoinex.Utils, only: [replicate: 2]

  @valid_segwit_address_hexscript_pairs_mainnet [
    {"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4",
     "0014751e76e8199196d454941c45d1b3a323f1433bd6"},
    {"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y",
     "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"},
    {"BC1SW50QGDZ25J", "6002751e"},
    {"bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs", "5210751e76e8199196d454941c45d1b3a323"},
    {"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0",
     "512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"}
  ]

  @valid_segwit_address_hexscript_pairs_testnet [
    {"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
     "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"},
    {"tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c",
     "5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"},
    {"tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
     "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"}
  ]

  @valid_segwit_address_hexscript_pairs_regtest [
    {"bcrt1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qzf4jry",
     "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"},
    {"bcrt1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvseswlauz7",
     "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"}
  ]

  @invalid_segwit_addresses [
    "tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut",
    "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd",
    "tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf",
    "BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL",
    "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh",
    "tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47",
    "bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4",
    "BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R",
    "bc1pw5dgrnzv",
    "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav",
    "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty",
    "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5",
    "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2",
    "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq",
    "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf",
    "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j",
    "bc1rw5uspcuh",
    "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90",
    "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
    "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7",
    "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du",
    "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv",
    "bc1gmk9yu"
  ]

  describe "decode/1" do
    test "successfully decode with valid segwit addresses in mainnet" do
      for {address, hexscript} <- @valid_segwit_address_hexscript_pairs_mainnet do
        assert_valid_segwit_address(address, hexscript, :mainnet)
      end
    end

    test "successfully decode with valid segwit addresses in testnet" do
      for {address, hexscript} <- @valid_segwit_address_hexscript_pairs_testnet do
        assert_valid_segwit_address(address, hexscript, :testnet)
      end
    end

    test "successfully decode with valid segwit addresses in regtest" do
      for {address, hexscript} <- @valid_segwit_address_hexscript_pairs_regtest do
        assert_valid_segwit_address(address, hexscript, :regtest)
      end
    end

    test "fail to decode with invalid address" do
      for address <- @invalid_segwit_addresses do
        assert {:error, _error} = Segwit.decode_address(address)
      end
    end
  end

  describe "encode_address/1" do
    test "successfully encode with valid netwrok, version and program " do
      version = 1
      program = replicate(1, 10)
      assert {:ok, mainnet_address} = Segwit.encode_address(:mainnet, version, program)
      assert {:ok, testnet_address} = Segwit.encode_address(:testnet, version, program)
      assert {:ok, regtest_address} = Segwit.encode_address(:regtest, version, program)
      all_addresses = [mainnet_address, testnet_address, regtest_address]
      # make sure they are different
      assert Enum.uniq(all_addresses) == all_addresses
    end

    test "fail to encode with program length > 40 " do
      assert {:error, _error} = Segwit.encode_address(:mainnet, 1, replicate(1, 41))
    end

    test "fail to encode with version 0 but program length not equalt to 20 or 32 " do
      assert {:ok, _address} = Segwit.encode_address(:mainnet, 0, replicate(1, 20))
      assert {:ok, _address} = Segwit.encode_address(:mainnet, 0, replicate(1, 32))
      assert {:error, _error} = Segwit.encode_address(:mainnet, 0, replicate(1, 21))
      assert {:error, _error} = Segwit.encode_address(:mainnet, 0, replicate(1, 33))
    end
  end

  describe "is_valid_segswit_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)
      end
    end

    test "return false given invalid address" do
      for address <- @invalid_segwit_addresses do
        refute Segwit.is_valid_segswit_address?(address)
      end
    end
  end

  # local private test helper
  defp assert_valid_segwit_address(address, hexscript, network) do
    assert {:ok, {hrp, version, program}} = Segwit.decode_address(address)
    assert hrp == network
    assert version in 0..16
    assert Segwit.get_segwit_script_pubkey(version, program) == hexscript

    # encode after decode should be the same(after downcase) as before
    {:ok, new_address} = Segwit.encode_address(hrp, version, program)
    assert new_address == String.downcase(address)
  end
end

repo_name: RiverFinancial/bitcoinex

repo_language: Elixir

mime_type: text/plain


text:
defmodule Bitcoinex.TransactionTest do
  use ExUnit.Case
  doctest Bitcoinex.Transaction

  alias Bitcoinex.Transaction

  @txn_serialization_1 %{
    tx_hex:
      "01000000010470c3139dc0f0882f98d75ae5bf957e68dadd32c5f81261c0b13e85f592ff7b0000000000ffffffff02b286a61e000000001976a9140f39a0043cf7bdbe429c17e8b514599e9ec53dea88ac01000000000000001976a9148a8c9fd79173f90cf76410615d2a52d12d27d21288ac00000000"
  }

  @txn_segwit_serialization_1 %{
    tx_hex:
      "01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f00000000494830450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac000247304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee0121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635711000000"
  }

  @txn_segwit_serialization_2 %{
    tx_hex:
      "01000000000102fe3dc9208094f3ffd12645477b3dc56f60ec4fa8e6f5d67c565d1c6b9216b36e000000004847304402200af4e47c9b9629dbecc21f73af989bdaa911f7e6f6c2e9394588a3aa68f81e9902204f3fcf6ade7e5abb1295b6774c8e0abd94ae62217367096bc02ee5e435b67da201ffffffff0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f80000000000ffffffff0100f2052a010000001976a914a30741f8145e5acadf23f751864167f32e0963f788ac000347304402200de66acf4527789bfda55fc5459e214fa6083f936b430a762c629656216805ac0220396f550692cd347171cbc1ef1f51e15282e837bb2b30860dc77c8f78bc8501e503473044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e27034721026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac00000000"
  }

  @txn_segwit_serialization_3 %{
    tx_hex:
      "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009bdf0089feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac02473044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb012103ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a2687392040000"
  }

  @txn_segwit_serialization_4 %{
    tx_hex:
      "0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac080047304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01473044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502473044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403483045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381483045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a08824730440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000"
  }

  describe "decode/1" do
    test "decodes legacy bitcoin transaction" do
      txn_test = @txn_serialization_1
      {:ok, txn} = Transaction.decode(txn_test.tx_hex)
      assert 1 == length(txn.inputs)
      assert 2 == length(txn.outputs)
      assert 1 == txn.version
      assert nil == txn.witnesses
      assert 0 == txn.lock_time

      assert "b020bdec4e92cb69db93557dcbbfcc73076fc01f6828e41eb3ef5f628414ee62" ==
               Transaction.transaction_id(txn)

      in_1 = Enum.at(txn.inputs, 0)

      assert "7bff92f5853eb1c06112f8c532ddda687e95bfe55ad7982f88f0c09d13c37004" == in_1.prev_txid
      assert 0 == in_1.prev_vout
      assert "" == in_1.script_sig
      assert 4_294_967_295 == in_1.sequence_no

      out_0 = Enum.at(txn.outputs, 0)
      assert 514_229_938 == out_0.value
      assert "76a9140f39a0043cf7bdbe429c17e8b514599e9ec53dea88ac" == out_0.script_pub_key

      out_1 = Enum.at(txn.outputs, 1)
      assert 1 == out_1.value
      assert "76a9148a8c9fd79173f90cf76410615d2a52d12d27d21288ac" == out_1.script_pub_key
    end

    test "decodes native segwit p2wpkh bitcoin transaction" do
      txn_test = @txn_segwit_serialization_1
      {:ok, txn} = Transaction.decode(txn_test.tx_hex)
      assert 2 == length(txn.inputs)
      assert 2 == length(txn.outputs)
      assert 1 == txn.version
      assert 2 == length(txn.witnesses)
      assert 17 == txn.lock_time

      assert "e8151a2af31c368a35053ddd4bdb285a8595c769a3ad83e0fa02314a602d4609" ==
               Transaction.transaction_id(txn)

      in_1 = Enum.at(txn.inputs, 0)

      assert "9f96ade4b41d5433f4eda31e1738ec2b36f6e7d1420d94a6af99801a88f7f7ff" == in_1.prev_txid
      assert 0 == in_1.prev_vout

      assert "4830450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01" ==
               in_1.script_sig

      assert 4_294_967_278 == in_1.sequence_no

      in_2 = Enum.at(txn.inputs, 1)

      assert "8ac60eb9575db5b2d987e29f301b5b819ea83a5c6579d282d189cc04b8e151ef" == in_2.prev_txid
      assert 1 == in_2.prev_vout
      assert "" == in_2.script_sig
      assert 4_294_967_295 == in_2.sequence_no

      witness_in_0 = Enum.at(txn.witnesses, 0)
      assert 0 == witness_in_0.txinwitness

      witness_in_1 = Enum.at(txn.witnesses, 1)

      assert [
               "304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee01",
               "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357"
             ] == witness_in_1.txinwitness

      out_0 = Enum.at(txn.outputs, 0)
      assert 112_340_000 == out_0.value
      assert "76a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac" == out_0.script_pub_key

      out_1 = Enum.at(txn.outputs, 1)
      assert 223_450_000 == out_1.value
      assert "76a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac" == out_1.script_pub_key
    end

    test "decodes native segwit p2wsh bitcoin transaction" do
      txn_test = @txn_segwit_serialization_2
      {:ok, txn} = Transaction.decode(txn_test.tx_hex)
      assert 2 == length(txn.inputs)
      assert 1 == length(txn.outputs)
      assert 1 == txn.version
      assert 2 == length(txn.witnesses)
      assert 0 == txn.lock_time

      assert "570e3730deeea7bd8bc92c836ccdeb4dd4556f2c33f2a1f7b889a4cb4e48d3ab" ==
               Transaction.transaction_id(txn)

      in_0 = Enum.at(txn.inputs, 0)

      assert "6eb316926b1c5d567cd6f5e6a84fec606fc53d7b474526d1fff3948020c93dfe" == in_0.prev_txid
      assert 0 == in_0.prev_vout

      assert "47304402200af4e47c9b9629dbecc21f73af989bdaa911f7e6f6c2e9394588a3aa68f81e9902204f3fcf6ade7e5abb1295b6774c8e0abd94ae62217367096bc02ee5e435b67da201" ==
               in_0.script_sig

      assert 4_294_967_295 == in_0.sequence_no

      in_1 = Enum.at(txn.inputs, 1)

      assert "f825690aee1b3dc247da796cacb12687a5e802429fd291cfd63e010f02cf1508" == in_1.prev_txid
      assert 0 == in_1.prev_vout
      assert "" == in_1.script_sig
      assert 4_294_967_295 == in_1.sequence_no

      witness_in_0 = Enum.at(txn.witnesses, 0)
      assert 0 == witness_in_0.txinwitness

      witness_in_1 = Enum.at(txn.witnesses, 1)

      assert [
               "304402200de66acf4527789bfda55fc5459e214fa6083f936b430a762c629656216805ac0220396f550692cd347171cbc1ef1f51e15282e837bb2b30860dc77c8f78bc8501e503",
               "3044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e2703",
               "21026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac"
             ] == witness_in_1.txinwitness

      out_1 = Enum.at(txn.outputs, 0)
      assert 5_000_000_000 == out_1.value
      assert "76a914a30741f8145e5acadf23f751864167f32e0963f788ac" == out_1.script_pub_key
    end

    test "decodes segwit p2sh-pw2pkh bitcoin transaction" do
      txn_test = @txn_segwit_serialization_3
      {:ok, txn} = Transaction.decode(txn_test.tx_hex)
      assert 1 == length(txn.inputs)
      assert 2 == length(txn.outputs)
      assert 1 == txn.version
      assert 1 == length(txn.witnesses)
      assert 1170 == txn.lock_time

      assert "ef48d9d0f595052e0f8cdcf825f7a5e50b6a388a81f206f3f4846e5ecd7a0c23" ==
               Transaction.transaction_id(txn)

      in_0 = Enum.at(txn.inputs, 0)

      assert "77541aeb3c4dac9260b68f74f44c973081a9d4cb2ebe8038b2d70faa201b6bdb" == in_0.prev_txid
      assert 1 == in_0.prev_vout

      assert "16001479091972186c449eb1ded22b78e40d009bdf0089" ==
               in_0.script_sig

      assert 4_294_967_294 == in_0.sequence_no

      witness_in_0 = Enum.at(txn.witnesses, 0)

      assert [
               "3044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb01",
               "03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873"
             ] == witness_in_0.txinwitness

      out_0 = Enum.at(txn.outputs, 0)
      assert 199_996_600 == out_0.value
      assert "76a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac" == out_0.script_pub_key

      out_1 = Enum.at(txn.outputs, 1)
      assert 800_000_000 == out_1.value
      assert "76a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac" == out_1.script_pub_key
    end

    test "decodes segwit p2sh-p2wsh bitcoin transaction" do
      txn_test = @txn_segwit_serialization_4
      {:ok, txn} = Transaction.decode(txn_test.tx_hex)
      assert 1 == length(txn.inputs)
      assert 2 == length(txn.outputs)
      assert 1 == txn.version
      assert 1 == length(txn.witnesses)
      assert 0 == txn.lock_time

      assert "27eae69aff1dd4388c0fa05cbbfe9a3983d1b0b5811ebcd4199b86f299370aac" ==
               Transaction.transaction_id(txn)

      in_0 = Enum.at(txn.inputs, 0)

      assert "6eb98797a21c6c10aa74edf29d618be109f48a8e94c694f3701e08ca69186436" == in_0.prev_txid
      assert 1 == in_0.prev_vout

      assert "220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54" ==
               in_0.script_sig

      assert 4_294_967_295 == in_0.sequence_no

      witness_in_0 = Enum.at(txn.witnesses, 0)

      assert [
               "",
               "304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01",
               "3044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502",
               "3044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403",
               "3045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381",
               "3045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a0882",
               "30440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783",
               "56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae"
             ] == witness_in_0.txinwitness

      out_0 = Enum.at(txn.outputs, 0)
      assert 900_000_000 == out_0.value
      assert "76a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688ac" == out_0.script_pub_key

      out_1 = Enum.at(txn.outputs, 1)
      assert 87_000_000 == out_1.value
      assert "76a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac" == out_1.script_pub_key
    end
  end
end


text:
defmodule Bitcoinex.Secp256k1.PrivateKeyTest do
  use ExUnit.Case
  doctest Bitcoinex.Secp256k1.PrivateKey

  alias Bitcoinex.Secp256k1.PrivateKey

  describe "serialize_private_key/1" do
    test "successfully pad private key" do
      sk = %PrivateKey{d: 123_414_253_234_542_345_423_623}

      assert "000000000000000000000000000000000000000000001a224cd1a01427f38b07" ==
               PrivateKey.serialize_private_key(sk)
    end
  end

  describe "wif/2" do
    test "successfully return private key wif encoding" do
      sk = %PrivateKey{d: 123_414_253_234_542_345_423_623}

      assert "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi4ZxKRdkhWeLbjoGkhRF5E" ==
               PrivateKey.wif!(sk, :mainnet)

      assert "cMahea7zqjxrtgAbB7LSGbcQUr1uX1okdzTtkBA29TFk41r74ddm" ==
               PrivateKey.wif!(sk, :testnet)
    end
  end

  describe "parse_wif/1" do
    test "successfully return private key from wif str" do
      sk = %PrivateKey{d: 123_414_253_234_542_345_423_623}
      wif = "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi4ZxKRdkhWeLbjoGkhRF5E"
      assert {:ok, sk, :mainnet, true} == PrivateKey.parse_wif(wif)
      twif = "cMahea7zqjxrtgAbB7LSGbcQUr1uX1okdzTtkBA29TFk41r74ddm"
      assert {:ok, sk, :testnet, true} == PrivateKey.parse_wif(twif)
    end
  end

  describe "parse & serialize wif" do
    test "successfully return private key from wif str" do
      sk = %PrivateKey{d: 123_414_253_234_542_345_423_623}
      wif = PrivateKey.wif!(sk, :mainnet)
      assert {:ok, sk, :mainnet, true} == PrivateKey.parse_wif(wif)
    end
  end
end


text:
defmodule Bitcoinex.Secp256k1.SchnorrTest do
  use ExUnit.Case
  doctest Bitcoinex.Secp256k1.Schnorr

  alias Bitcoinex.Utils
  alias Bitcoinex.Secp256k1
  alias Bitcoinex.Secp256k1.{Point, PrivateKey, Schnorr, Signature}

  # BIP340 official test vectors:
  # https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv
  @schnorr_signatures_with_secrets [
    %{
      secret: 3,
      pubkey: "F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
      aux_rand: 0x0000000000000000000000000000000000000000000000000000000000000000,
      message: 0x0000000000000000000000000000000000000000000000000000000000000000,
      signature:
        "E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0",
      result: true
    },
    %{
      secret: 0xB7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF,
      pubkey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
      aux_rand: 0x0000000000000000000000000000000000000000000000000000000000000001,
      message: 0x243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,
      signature:
        "6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A",
      result: true
    },
    %{
      secret: 0xC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9,
      pubkey: "DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
      aux_rand: 0xC87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906,
      message: 0x7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C,
      signature:
        "5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7",
      result: true
    },
    # test fails if msg is reduced mod p or n
    %{
      secret: 0x0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710,
      pubkey: "25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517",
      aux_rand: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,
      message: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,
      signature:
        "7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3",
      result: true
    }
  ]

  # BIP340 official test vectors:
  # https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv
  @schnorr_signatures_no_secrets @schnorr_signatures_with_secrets ++
                                   [
                                     %{
                                       pubkey:
                                         "D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9",
                                       message:
                                         0x4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703,
                                       signature:
                                         "00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4",
                                       result: true
                                     },
                                     %{
                                       pubkey:
                                         "EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34",
                                       message:
                                         0x4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703,
                                       signature:
                                         "00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4",
                                       result: false
                                     },
                                     %{
                                       pubkey:
                                         "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
                                       message:
                                         0x243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,
                                       signature:
                                         "FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2",
                                       result: false
                                     },
                                     %{
                                       pubkey:
                                         "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
                                       message:
                                         0x243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,
                                       signature:
                                         "1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD",
                                       result: false
                                     },
                                     %{
                                       pubkey:
                                         "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
                                       message:
                                         0x243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,
                                       signature:
                                         "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6",
                                       result: false
                                     },
                                     %{
                                       pubkey:
                                         "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
                                       message:
                                         0x243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,
                                       signature:
                                         "0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051",
                                       result: false
                                     },
                                     %{
                                       pubkey:
                                         "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
                                       message:
                                         0x243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,
                                       signature:
                                         "00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197",
                                       result: false
                                     },
                                     %{
                                       pubkey:
                                         "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
                                       message:
                                         0x243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,
                                       signature:
                                         "4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B",
                                       result: false
                                     },
                                     %{
                                       pubkey:
                                         "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
                                       message:
                                         0x243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,
                                       signature:
                                         "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B",
                                       result: false
                                     },
                                     %{
                                       pubkey:
                                         "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
                                       message:
                                         0x243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,
                                       signature:
                                         "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
                                       result: false
                                     },
                                     %{
                                       pubkey:
                                         "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30",
                                       message:
                                         0x243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,
                                       signature:
                                         "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B",
                                       result: false
                                     }
                                   ]

  describe "sign/3" do
    test "sign" do
      for t <- @schnorr_signatures_with_secrets do
        {:ok, sk} = PrivateKey.new(t.secret)

        {:ok, sig} = Schnorr.sign(sk, t.message, t.aux_rand)
        result = Signature.serialize_signature(sig) |> Base.encode16(case: :upper) == t.signature
        assert result == t.result
      end
    end

    test "sign & verify" do
      for t <- @schnorr_signatures_with_secrets do
        {:ok, sk} = PrivateKey.new(t.secret)
        {:ok, pubkey} = Point.lift_x(t.pubkey)

        {:ok, sig} = Schnorr.sign(sk, t.message, t.aux_rand)
        result = Schnorr.verify_signature(pubkey, t.message, sig)
        assert result == t.result
      end
    end
  end

  describe "verify_signature/3" do
    test "verify_signature" do
      for t <- @schnorr_signatures_no_secrets do
        pk_res =
          t.pubkey
          |> Utils.hex_to_bin()
          |> Point.lift_x()

        sig_res =
          t.signature
          |> Base.decode16!(case: :upper)
          |> Signature.parse_signature()

        case {pk_res, sig_res} do
          {{:ok, pubkey}, {:ok, sig}} ->
            assert Schnorr.verify_signature(pubkey, t.message, sig) == t.result

          _ ->
            assert !t.result
        end
      end
    end
  end

  describe "fuzz test signing" do
    setup do
      privkey = Secp256k1.force_even_y(%PrivateKey{d: 123_414_253_234_542_345_423_623})
      pubkey = PrivateKey.to_point(privkey)
      {:ok, privkey: privkey, pubkey: pubkey}
    end

    test "successfully sign a large number of random messages", %{
      privkey: privkey,
      pubkey: pubkey
    } do
      aux =
        32
        |> :crypto.strong_rand_bytes()
        |> :binary.decode_unsigned()

      for _ <- 1..1000 do
        z =
          32
          |> :crypto.strong_rand_bytes()
          |> :binary.decode_unsigned()

        {:ok, sig} = Schnorr.sign(privkey, z, aux)
        assert Schnorr.verify_signature(pubkey, z, sig)
      end
    end

    test "successfully sign a message with a large number of aux inputs", %{
      privkey: privkey,
      pubkey: pubkey
    } do
      z =
        32
        |> :crypto.strong_rand_bytes()
        |> :binary.decode_unsigned()

      for _ <- 1..1000 do
        aux =
          32
          |> :crypto.strong_rand_bytes()
          |> :binary.decode_unsigned()

        {:ok, sig} = Schnorr.sign(privkey, z, aux)
        assert Schnorr.verify_signature(pubkey, z, sig)
      end
    end

    test "successfully sign a message with a large number of keys" do
      z =
        32
        |> :crypto.strong_rand_bytes()
        |> :binary.decode_unsigned()

      aux =
        32
        |> :crypto.strong_rand_bytes()
        |> :binary.decode_unsigned()

      for _ <- 1..1000 do
        secret =
          32
          |> :crypto.strong_rand_bytes()
          |> :binary.decode_unsigned()

        privkey = Secp256k1.force_even_y(%PrivateKey{d: secret})
        pubkey = PrivateKey.to_point(privkey)
        {:ok, sig} = Schnorr.sign(privkey, z, aux)
        assert Schnorr.verify_signature(pubkey, z, sig)
      end
    end
  end
end


text:
defmodule Bitcoinex.Secp256k1.PointTest do
  use ExUnit.Case
  doctest Bitcoinex.Secp256k1.Point

  alias Bitcoinex.Secp256k1
  alias Bitcoinex.Secp256k1.Point

  @x_only_pubkeys [
    "F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
    "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
    "DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
    "25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517",
    "D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9"
  ]

  describe "serialize_public_key/1" do
    test "successfully pad public key" do
      assert "020003b94aecea4d0a57a6c87cf43c50c8b3736f33ab7fd34f02441b6e94477689" ==
               Point.serialize_public_key(%Point{
                 x:
                   6_579_384_254_631_425_969_190_483_614_785_133_746_155_874_651_439_631_590_927_590_192_220_436_105,
                 y:
                   71_870_263_570_581_286_056_939_190_487_148_011_225_641_308_782_404_760_504_903_461_107_415_970_265_024
               })
    end
  end

  describe "parse_public_key/1" do
    test "successfully parse public key from sec" do
      sec =
        Base.decode16!("033b15e1b8c51bb947a134d17addc3eb6abbda551ad02137699636f907ad7e0f1a",
          case: :lower
        )

      assert Point.parse_public_key(sec) ==
               {:ok,
                %Point{
                  x:
                    26_725_119_729_089_203_965_150_132_282_997_341_343_516_273_140_835_737_223_575_952_640_907_021_258_522,
                  y:
                    35_176_335_436_138_229_778_595_179_837_068_778_482_032_382_451_813_967_420_917_290_469_529_927_283_651
                }}
    end

    test "successfully parse uncompressed key from sec" do
      sec =
        Base.decode16!(
          "048fdc3d8944cc8d8fe6c666c41a8ed42e60aa399861a756707e127a80b383d178edfbf94dda0487f7910d130f2a37a0647be9335eab5b8d3aa5242445e1604024",
          case: :lower
        )

      assert Point.parse_public_key(sec) ==
               {:ok,
                %Point{
                  x: 0x8FDC3D8944CC8D8FE6C666C41A8ED42E60AA399861A756707E127A80B383D178,
                  y: 0xEDFBF94DDA0487F7910D130F2A37A0647BE9335EAB5B8D3AA5242445E1604024
                }}
    end

    test "successfully parse compressed key from sec hex" do
      sec = "0299d7ff3d96c731e54e75637798cab801fe80827191e280f53427bc8915323e8b"

      pk = %Point{
        x:
          69_585_499_557_921_076_123_288_400_932_281_161_043_766_220_600_235_811_505_715_105_664_976_077_078_155,
        y:
          102_549_807_389_226_195_103_316_638_704_859_105_787_106_440_500_810_433_784_118_696_258_589_643_376_818,
        z: 0
      }

      assert Point.parse_public_key(sec) == {:ok, pk}
      assert Point.serialize_public_key(pk) == sec
    end
  end

  describe "sec/1" do
    test "successfully calculate SEC encoding and hash160 of public key" do
      correct_hash160 = "d1914384b57de2944ce1b6a90adf2f7b72cfe61e"

      hash160 =
        %Point{
          x:
            26_725_119_729_089_203_965_150_132_282_997_341_343_516_273_140_835_737_223_575_952_640_907_021_258_522,
          y:
            35_176_335_436_138_229_778_595_179_837_068_778_482_032_382_451_813_967_420_917_290_469_529_927_283_651
        }
        |> Point.sec()
        |> Bitcoinex.Utils.hash160()
        |> Base.encode16(case: :lower)

      assert correct_hash160 == hash160
    end
  end

  describe "lift_x/1" do
    test "lift_x on 32-byte x-only pubkeys" do
      for t <- @x_only_pubkeys do
        {:ok, pubkey} = Point.lift_x(t)
        assert Point.has_even_y(pubkey)
        assert Secp256k1.verify_point(pubkey)
      end
    end

    test "return error when x is equal or greater than Secp256k1 p" do
      for i <- 0..2 do
        assert {:error, error} = Point.lift_x(Bitcoinex.Secp256k1.Params.curve().p + i)
        assert String.match?(error, ~r/(too large)/)
      end
    end
  end
end