defmodule Bitcoinex.Secp256k1.ExtendedKeyTest do
  use ExUnit.Case
  doctest Bitcoinex.ExtendedKey

  alias Bitcoinex.ExtendedKey
  alias Bitcoinex.Secp256k1.Ecdsa

  @min_hardened_child_num 0x80000000
  # 2^32
  @max_hardened_child_num 0x100000000

  @invalid_xkeys [
    # changed prefix
    "zpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
    # invalid char
    "xpubi61MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
    # invalid prefix
    "apub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
    # invalid len
    "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet",
    # invalid len
    "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1E",
    # invalid len (with valid checksum)
    "Deb7pNXSbX7qSvc2eMjkNYTrggh4pBgYa2QMFjEjj6hUy1iHp71d1gf3ue7Ni7X8Pkhcy13aakoEYSMS1DFiMaXB4qYhN33dh3oqD9n1YWSxVk"
  ]

  @bip32_test_case_1 %{
    # test vectors from bip32: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#test-vectors
    seed: "000102030405060708090a0b0c0d0e0f",
    xpub_m:
      "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
    xpub_m_obj: %Bitcoinex.ExtendedKey{
      chaincode:
        <<135, 61, 255, 129, 192, 47, 82, 86, 35, 253, 31, 229, 22, 126, 172, 58, 85, 160, 73,
          222, 61, 49, 75, 180, 46, 226, 39, 255, 237, 55, 213, 8>>,
      checksum: <<171, 71, 59, 33>>,
      child_num: <<0, 0, 0, 0>>,
      depth: <<0>>,
      key:
        <<3, 57, 163, 96, 19, 48, 21, 151, 218, 239, 65, 251, 229, 147, 160, 44, 197, 19, 208,
          181, 85, 39, 236, 45, 241, 5, 14, 46, 143, 244, 156, 133, 194>>,
      parent_fingerprint: <<0, 0, 0, 0>>,
      prefix: <<4, 136, 178, 30>>
    },
    xprv_m:
      "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
    xprv_m_obj: %Bitcoinex.ExtendedKey{
      chaincode:
        <<135, 61, 255, 129, 192, 47, 82, 86, 35, 253, 31, 229, 22, 126, 172, 58, 85, 160, 73,
          222, 61, 49, 75, 180, 46, 226, 39, 255, 237, 55, 213, 8>>,
      checksum: <<231, 126, 157, 113>>,
      child_num: <<0, 0, 0, 0>>,
      depth: <<0>>,
      key:
        <<0, 232, 243, 46, 114, 61, 236, 244, 5, 26, 239, 172, 142, 44, 147, 201, 197, 178, 20,
          49, 56, 23, 205, 176, 26, 20, 148, 185, 23, 200, 67, 107, 53>>,
      parent_fingerprint: <<0, 0, 0, 0>>,
      prefix: <<4, 136, 173, 228>>
    },
    xpub_m_0h:
      "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw",
    xprv_m_0h:
      "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7",
    xpub_m_0h_1:
      "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ",
    xprv_m_0h_1:
      "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs",
    xpub_m_0h_1_2h:
      "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5",
    xprv_m_0h_1_2h:
      "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM",
    xpub_m_0h_1_2h_2:
      "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV",
    xprv_m_0h_1_2h_2:
      "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334",
    xpub_m_0h_1_2h_2_1000000000:
      "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy",
    xprv_m_0h_1_2h_2_1000000000:
      "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76"
  }

  @bip32_test_case_2 %{
    seed:
      "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
    xpub_m:
      "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB",
    xprv_m:
      "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U",
    xpub_m_0:
      "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH",
    xprv_m_0:
      "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt",
    xpub_m_0_2147483647h:
      "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a",
    xprv_m_0_2147483647h:
      "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9",
    xpub_m_0_2147483647h_1:
      "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon",
    xprv_m_0_2147483647h_1:
      "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef",
    xpub_m_0_2147483647h_1_2147483646h:
      "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL",
    xprv_m_0_2147483647h_1_2147483646h:
      "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc",
    xpub_m_0_2147483647h_1_2147483646h_2:
      "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt",
    xprv_m_0_2147483647h_1_2147483646h_2:
      "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j"
  }

  @bip32_test_case_3 %{
    # Test for retention of leading Zeros
    seed:
      "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be",
    xpub_m:
      "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13",
    xprv_m:
      "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6",
    xpub_m_0h:
      "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y",
    xprv_m_0h:
      "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L"
  }

  @derivation_paths_to_strings [
    %{
      str: "84/0/0/2/1/",
      deriv: %ExtendedKey.DerivationPath{child_nums: [84, 0, 0, 2, 1]}
    },
    %{
      str: "84'/0'/",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num]
      }
    },
    %{
      str: "84'/0'/",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num]
      }
    },
    %{
      str: "84'/0'/1/2/2147483647/",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [
          84 + @min_hardened_child_num,
          0 + @min_hardened_child_num,
          1,
          2,
          2_147_483_647
        ]
      }
    }
  ]

  @strings_to_derivation_paths [
    %{
      str: "84/0/0/2/1/",
      deriv: %ExtendedKey.DerivationPath{child_nums: [84, 0, 0, 2, 1]}
    },
    %{
      str: "m/84'/0'/0'/2/1",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [
          84 + @min_hardened_child_num,
          0 + @min_hardened_child_num,
          2_147_483_648,
          2,
          1
        ]
      }
    },
    %{
      str: "m/84'/0'/",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num]
      }
    },
    %{
      str: "m/84'/0'",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num]
      }
    },
    %{
      str: "84'/0'",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num]
      }
    },
    %{
      str: "84'/0'/",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num]
      }
    },
    %{
      str: "84'/0'/1/2/2147483647",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [
          84 + @min_hardened_child_num,
          0 + @min_hardened_child_num,
          1,
          2,
          2_147_483_647
        ]
      }
    },
    %{
      str: "m/84h/0h/0h/2/1",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [
          84 + @min_hardened_child_num,
          0 + @min_hardened_child_num,
          2_147_483_648,
          2,
          1
        ]
      }
    },
    %{
      str: "m/84h/0h/",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num]
      }
    },
    %{
      str: "84h/0h",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num]
      }
    },
    %{
      str: "84h/0h/",
      deriv: %ExtendedKey.DerivationPath{
        child_nums: [84 + @min_hardened_child_num, 0 + @min_hardened_child_num]
      }
    }
  ]

  @error_derivation_path_strings [
    "a/82/1",
    "m/85//1/",
    "h/5/",
    "0/0/h",
    "h",
    "'",
    "0/h/0",
    "m/1/1/#{@max_hardened_child_num}",
    "m/-1/0/1"
  ]

  # Extended Key Testing

  describe "parse_extended_key/1" do
    test "successfully parse extended xprv" do
      t = @bip32_test_case_1
      # priv
      assert ExtendedKey.parse_extended_key(t.xprv_m) == {:ok, t.xprv_m_obj}
      assert ExtendedKey.display_extended_key(t.xprv_m_obj) == t.xprv_m
      # pub
      assert ExtendedKey.parse_extended_key(t.xpub_m) == {:ok, t.xpub_m_obj}
      assert ExtendedKey.display_extended_key(t.xpub_m_obj) == t.xpub_m
    end
  end

  describe "to_extended_public_key/1" do
    test "successfully turn xprv into xpub" do
      t = @bip32_test_case_1
      assert ExtendedKey.to_extended_public_key(t.xprv_m_obj) == {:ok, t.xpub_m_obj}
    end
  end

  describe "BIP32 tests" do
    # Test 1
    test "BIP32 tests 1: successfully convert xprv to xpub." do
      t = @bip32_test_case_1

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0h)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0h_1)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h_1)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0h_1_2h)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0h_1_2h_2)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h_2)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0h_1_2h_2_1000000000)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h_2_1000000000)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}
    end

    test "BIP32 tests 1: derive prv keys in sequence" do
      t = @bip32_test_case_1
      # derive prv child from prv parent_fingerprint
      {:ok, m_xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, m_0h_xprv} = ExtendedKey.derive_private_child(m_xprv, @min_hardened_child_num)

      assert ExtendedKey.parse_extended_key(t.xprv_m_0h) == {:ok, m_0h_xprv}

      # derive child m/0'/1
      {:ok, m_0h_1_xprv} = ExtendedKey.derive_private_child(m_0h_xprv, 1)
      assert ExtendedKey.parse_extended_key(t.xprv_m_0h_1) == {:ok, m_0h_1_xprv}
    end

    test "BIP32 tests 1: derive pub keys from master prv key" do
      t = @bip32_test_case_1

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, m_0h_xpub} = ExtendedKey.derive_public_child(xprv, @min_hardened_child_num)

      assert ExtendedKey.parse_extended_key(t.xpub_m_0h) == {:ok, m_0h_xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xprv} = ExtendedKey.derive_private_child(xprv, @min_hardened_child_num)
      {:ok, xprv} = ExtendedKey.derive_private_child(xprv, 1)
      {:ok, m_0h_1_2h_xpub} = ExtendedKey.derive_public_child(xprv, @min_hardened_child_num + 2)

      assert ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h) == {:ok, m_0h_1_2h_xpub}
    end

    test "BIP32 tests 1: derive m/0'/1/2'/2/1000000000 from master key" do
      t = @bip32_test_case_1

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xprv} = ExtendedKey.derive_private_child(xprv, @min_hardened_child_num)
      {:ok, xprv} = ExtendedKey.derive_private_child(xprv, 1)
      {:ok, xprv} = ExtendedKey.derive_private_child(xprv, @min_hardened_child_num + 2)
      {:ok, xprv} = ExtendedKey.derive_private_child(xprv, 2)
      {:ok, m_0h_1_2h_2_1000000000_xprv} = ExtendedKey.derive_private_child(xprv, 1_000_000_000)

      assert ExtendedKey.parse_extended_key(t.xprv_m_0h_1_2h_2_1000000000) ==
               {:ok, m_0h_1_2h_2_1000000000_xprv}
    end

    test "BIP32 tests 1: derive pub child from pub parent_fingerprint" do
      t = @bip32_test_case_1

      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h)
      {:ok, m_0h_1_xpub} = ExtendedKey.derive_public_child(xpub, 1)

      assert ExtendedKey.parse_extended_key(t.xpub_m_0h_1) == {:ok, m_0h_1_xpub}

      {:ok, xpub_m_0h_1_2h} = ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h)
      {:ok, m_0h_1_2h_2_xpub} = ExtendedKey.derive_public_child(xpub_m_0h_1_2h, 2)

      assert ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h_2) == {:ok, m_0h_1_2h_2_xpub}

      {:ok, m_0h_1_2h_2_1000000000_xpub} =
        m_0h_1_2h_2_xpub
        |> ExtendedKey.derive_public_child(1_000_000_000)

      assert ExtendedKey.parse_extended_key(t.xpub_m_0h_1_2h_2_1000000000) ==
               {:ok, m_0h_1_2h_2_1000000000_xpub}
    end

    test "BIP32 tests 1: to_public_key works for both xprv and xpubs" do
      t = @bip32_test_case_1

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m)

      pub = ExtendedKey.to_public_key(xpub)
      # test that to_public_key works for xprv and xpub keys
      assert ExtendedKey.to_public_key(xprv) == pub
    end

    test "BIP32 tests 1: seed to master prv key" do
      t = @bip32_test_case_1

      seed = t.seed |> Base.decode16!(case: :lower)
      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)

      assert ExtendedKey.seed_to_master_private_key(seed) == {:ok, xprv}
    end

    test "BIP32 tests 1: derive key from seed and deriv path" do
      t = @bip32_test_case_1

      seed = t.seed |> Base.decode16!(case: :lower)
      {:ok, xprv} = t.xprv_m |> ExtendedKey.parse_extended_key()
      deriv = %ExtendedKey.DerivationPath{child_nums: []}

      assert ExtendedKey.derive_extended_key(seed, deriv) == {:ok, xprv}

      # derive m/0'/1
      {:ok, xprv} = t.xprv_m_0h_1 |> ExtendedKey.parse_extended_key()
      deriv = %ExtendedKey.DerivationPath{child_nums: [@min_hardened_child_num, 1]}

      assert ExtendedKey.derive_extended_key(seed, deriv) == {:ok, xprv}

      # derive xprv_m_0h_1_2h_2_1000000000
      {:ok, xprv_m_0h_1_2h_2_1000000000} =
        t.xprv_m_0h_1_2h_2_1000000000 |> ExtendedKey.parse_extended_key()

      deriv = %ExtendedKey.DerivationPath{
        child_nums: [@min_hardened_child_num, 1, @min_hardened_child_num + 2, 2, 1_000_000_000]
      }

      assert ExtendedKey.derive_extended_key(seed, deriv) == {:ok, xprv_m_0h_1_2h_2_1000000000}
    end

    # Test 2

    test "BIP32 tests 2: successfully convert xprv to xpub." do
      t = @bip32_test_case_2

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0_2147483647h)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h_1)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0_2147483647h_1)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h_1_2147483646h)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0_2147483647h_1_2147483646h)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h_1_2147483646h_2)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0_2147483647h_1_2147483646h_2)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}
    end

    test "BIP32 tests 2: derive prv keys in sequence" do
      t = @bip32_test_case_2
      # derive prv child from prv parent_fingerprint
      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, m_0_xprv} = ExtendedKey.derive_private_child(xprv, 0)

      assert ExtendedKey.parse_extended_key(t.xprv_m_0) == {:ok, m_0_xprv}

      # derive child m/0/2147483647h
      {:ok, m_0_2147483647h_xprv} =
        ExtendedKey.derive_private_child(m_0_xprv, 2_147_483_647 + @min_hardened_child_num)

      assert ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h) == {:ok, m_0_2147483647h_xprv}
    end

    test "BIP32 tests 2: derive pub keys from master prv key" do
      t = @bip32_test_case_2

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, m_0_xpub} = ExtendedKey.derive_public_child(xprv, 0)

      assert ExtendedKey.parse_extended_key(t.xpub_m_0) == {:ok, m_0_xpub}

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xprv_temp} = ExtendedKey.derive_private_child(xprv, 0)

      {:ok, xprv_temp} =
        ExtendedKey.derive_private_child(xprv_temp, 2_147_483_647 + @min_hardened_child_num)

      {:ok, m_0_2147483647h_1_xpub} = ExtendedKey.derive_public_child(xprv_temp, 1)

      assert ExtendedKey.parse_extended_key(t.xpub_m_0_2147483647h_1) ==
               {:ok, m_0_2147483647h_1_xpub}
    end

    test "BIP32 tests 2: derive child pub keys from prv and pubkey" do
      t = @bip32_test_case_2

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xpub1} = ExtendedKey.derive_public_child(xprv, 0)

      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m)
      {:ok, xpub2} = ExtendedKey.derive_public_child(xpub, 0)

      assert xpub1 == xpub2

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h)
      {:ok, xpub1} = ExtendedKey.derive_public_child(xprv, 1)

      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0_2147483647h)
      {:ok, xpub2} = ExtendedKey.derive_public_child(xpub, 1)

      assert xpub1 == xpub2
    end

    test "BIP32 tests 2: seed to master prv key" do
      t = @bip32_test_case_2

      seed = t.seed |> Base.decode16!(case: :lower)
      {:ok, xprv} = t.xprv_m |> ExtendedKey.parse_extended_key()

      assert ExtendedKey.seed_to_master_private_key(seed) == {:ok, xprv}
    end

    test "BIP32 tests 2: derive key from seed and deriv path" do
      t = @bip32_test_case_2

      seed = t.seed |> Base.decode16!(case: :lower)
      {:ok, xprv} = t.xprv_m |> ExtendedKey.parse_extended_key()
      deriv = %ExtendedKey.DerivationPath{child_nums: []}

      assert ExtendedKey.derive_extended_key(seed, deriv) == {:ok, xprv}

      {:ok, xprv} = t.xprv_m_0_2147483647h |> ExtendedKey.parse_extended_key()

      deriv = %ExtendedKey.DerivationPath{
        child_nums: [0, 2_147_483_647 + @min_hardened_child_num]
      }

      assert ExtendedKey.derive_extended_key(seed, deriv) == {:ok, xprv}

      {:ok, xprv_m_0_2147483647h_1_2147483646h_2} =
        t.xprv_m_0_2147483647h_1_2147483646h_2 |> ExtendedKey.parse_extended_key()

      deriv = %ExtendedKey.DerivationPath{
        child_nums: [
          0,
          2_147_483_647 + @min_hardened_child_num,
          1,
          2_147_483_646 + @min_hardened_child_num,
          2
        ]
      }

      assert ExtendedKey.derive_extended_key(seed, deriv) ==
               {:ok, xprv_m_0_2147483647h_1_2147483646h_2}
    end

    # Test 3

    test "BIP32 tests 3: derive public key from private key" do
      t = @bip32_test_case_3

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}
      # check that to_extended_public_key is identity for xpub
      assert ExtendedKey.to_extended_public_key(xpub) == xpub

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m_0h)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m_0h)
      assert ExtendedKey.to_extended_public_key(xprv) == {:ok, xpub}
      assert ExtendedKey.to_extended_public_key(xpub) == xpub
    end

    test "BIP32 tests 3: derive prv child from parent" do
      t = @bip32_test_case_3

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xprv_m_0h} = ExtendedKey.derive_private_child(xprv, @min_hardened_child_num)

      assert ExtendedKey.parse_extended_key(t.xprv_m_0h) == {:ok, xprv_m_0h}
    end

    test "BIP32 tests 3: derive pub child from prv parent" do
      t = @bip32_test_case_3

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xpub_m_0h} = ExtendedKey.derive_public_child(xprv, @min_hardened_child_num)

      assert ExtendedKey.display_extended_key(xpub_m_0h) == t.xpub_m_0h
    end

    test "BIP32 tests 3: derive master prv key from seed" do
      t = @bip32_test_case_3

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)

      {:ok, s_xprv} =
        t.seed
        |> Base.decode16!(case: :lower)
        |> ExtendedKey.seed_to_master_private_key()

      assert xprv == s_xprv
    end

    test "BIP32 tests 3: derive child prv key from seed" do
      t = @bip32_test_case_3

      {:ok, xprv_m_0h} = ExtendedKey.parse_extended_key(t.xprv_m_0h)
      deriv = %ExtendedKey.DerivationPath{child_nums: [@min_hardened_child_num]}

      {:ok, s_xprv_m_0h} =
        t.seed
        |> Base.decode16!(case: :lower)
        |> ExtendedKey.derive_extended_key(deriv)

      assert xprv_m_0h == s_xprv_m_0h
    end
  end

  describe "Invalid Key testing" do
    test "invalid key testing" do
      for t <- @invalid_xkeys do
        {err, _} = ExtendedKey.parse_extended_key(t)
        assert err == :error
      end
    end
  end

  describe "Derive Private and public key, sign message, verify signature" do
    test "derive prv and public key, sign msg, verify" do
      t = @bip32_test_case_1

      {:ok, xprv} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m)

      {:ok, prv} = ExtendedKey.to_private_key(xprv)
      {:ok, pub} = ExtendedKey.to_public_key(xpub)

      msg = "eat out from 5 pounds"
      z = :binary.decode_unsigned(Bitcoinex.Utils.double_sha256(msg))
      sig = Ecdsa.sign(prv, z)
      assert Ecdsa.verify_signature(pub, z, sig)
    end
  end

  describe "Fail on attempt to derive hardened child from pubkey" do
    test "fail to derive hardened child from pubkey parent" do
      t = @bip32_test_case_3

      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m)
      {err, _msg} = ExtendedKey.derive_child_key(xpub, @min_hardened_child_num)
      assert :error == err
    end

    test "fail to derive hardened child from pubkey parent with deriv path" do
      t = @bip32_test_case_3

      deriv = %ExtendedKey.DerivationPath{
        child_nums: [@min_hardened_child_num, 1]
      }

      {:ok, xpub} = ExtendedKey.parse_extended_key(t.xpub_m)
      {err, _msg} = ExtendedKey.derive_extended_key(xpub, deriv)
      assert :error == err
    end
  end

  # Derivation Path Testing

  describe "derive_extended_key/2 using BIP 32 test cases" do
    test "test use of deriv path" do
      t = @bip32_test_case_1

      deriv = %ExtendedKey.DerivationPath{
        child_nums: [@min_hardened_child_num, 1, @min_hardened_child_num + 2, 2]
      }

      {:ok, child_key} =
        t.xprv_m_obj
        |> ExtendedKey.derive_extended_key(deriv)

      assert ExtendedKey.display_extended_key(child_key) == t.xprv_m_0h_1_2h_2
    end

    test "successfully derive xpub child key with derivation path" do
      t = @bip32_test_case_2

      deriv = %ExtendedKey.DerivationPath{
        child_nums: [
          0,
          @min_hardened_child_num + 2_147_483_647,
          1,
          @min_hardened_child_num + 2_147_483_646
        ]
      }

      {:ok, xprv_t1} = ExtendedKey.parse_extended_key(t.xprv_m)
      {:ok, xprv_t2} = ExtendedKey.derive_extended_key(xprv_t1, deriv)
      {:ok, child_key} = ExtendedKey.to_extended_public_key(xprv_t2)

      assert ExtendedKey.display_extended_key(child_key) == t.xpub_m_0_2147483647h_1_2147483646h
    end

    test "test use of deriv path bip32 test 2" do
      t = @bip32_test_case_2

      deriv = %ExtendedKey.DerivationPath{
        child_nums: [
          0,
          2_147_483_647 + @min_hardened_child_num,
          1,
          2_147_483_646 + @min_hardened_child_num,
          2
        ]
      }

      {:ok, m} = ExtendedKey.parse_extended_key(t.xprv_m)

      {:ok, child_key} = ExtendedKey.derive_extended_key(m, deriv)

      assert ExtendedKey.parse_extended_key(t.xprv_m_0_2147483647h_1_2147483646h_2) ==
               {:ok, child_key}
    end
  end

  describe "Derivation Path parse/ser testing" do
    test "from_string/1" do
      for t <- @strings_to_derivation_paths do
        assert ExtendedKey.DerivationPath.from_string(t.str) == {:ok, t.deriv}
      end
    end

    test "to_string/1" do
      for t <- @derivation_paths_to_strings do
        assert ExtendedKey.DerivationPath.to_string(t.deriv) == {:ok, t.str}
      end
    end

    test "Raise exceptions on invalid derivation paths" do
      for t <- @error_derivation_path_strings do
        {res, _} = ExtendedKey.DerivationPath.from_string(t)
        assert :error == res
      end
    end
  end
end
