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