bip_hd.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. #!/usr/bin/env python3
  2. """
  3. test.modtest_d.bip_hd: bip_hd unit test for the MMGen suite
  4. """
  5. from mmgen.color import gray, pink, blue
  6. from mmgen.util import fmt
  7. from mmgen.bip_hd import Bip32ExtendedKey, BipHDConfig, BipHDNode, MasterNode, get_chain_params
  8. from ..include.common import cfg, vmsg
  9. # Source: BIP-32
  10. vectors_bip32 = [
  11. {
  12. 'seed': '000102030405060708090a0b0c0d0e0f',
  13. "m": {
  14. 'xpub': 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8',
  15. 'xprv': 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi',
  16. },
  17. "m/0'": {
  18. 'xpub': 'xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw',
  19. 'xprv': 'xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7',
  20. },
  21. "m/0'/1": {
  22. 'xpub': 'xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ',
  23. 'xprv': 'xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs',
  24. },
  25. "m/0'/1/2'": {
  26. 'xpub': 'xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5',
  27. 'xprv': 'xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM',
  28. },
  29. "m/0'/1/2'/2": {
  30. 'xpub': 'xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV',
  31. 'xprv': 'xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334',
  32. },
  33. "m/0'/1/2'/2/1000000000": {
  34. 'xpub': 'xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy',
  35. 'xprv': 'xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76',
  36. },
  37. }, {
  38. 'seed': 'fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542',
  39. 'm': {
  40. 'xpub': 'xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB',
  41. 'xprv': 'xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U',
  42. },
  43. "m/0": {
  44. 'xpub': 'xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH',
  45. 'xprv': 'xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt',
  46. },
  47. "m/0/2147483647'": {
  48. 'xpub': 'xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a',
  49. 'xprv': 'xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9',
  50. },
  51. "m/0/2147483647'/1": {
  52. 'xpub': 'xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon',
  53. 'xprv': 'xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef',
  54. },
  55. "m/0/2147483647'/1/2147483646'": {
  56. 'xpub': 'xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL',
  57. 'xprv': 'xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc',
  58. },
  59. "m/0/2147483647'/1/2147483646'/2": {
  60. 'xpub': 'xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt',
  61. 'xprv': 'xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j',
  62. },
  63. }, {
  64. 'comment': 'These vectors test for the retention of leading zeros. See bitpay/bitcore-lib#47 and iancoleman/bip39#58 for more information.',
  65. 'seed': '4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be',
  66. 'm': {
  67. 'xpub': 'xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13',
  68. 'xprv': 'xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6',
  69. },
  70. "m/0'": {
  71. 'xpub': 'xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y',
  72. 'xprv': 'xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L',
  73. },
  74. }, {
  75. 'comment': 'These vectors test for the retention of leading zeros. See btcsuite/btcutil#172 for more information.',
  76. 'seed': '3ddd5602285899a946114506157c7997e5444528f3003f6134712147db19b678',
  77. "m": {
  78. 'xpub': 'xpub661MyMwAqRbcGczjuMoRm6dXaLDEhW1u34gKenbeYqAix21mdUKJyuyu5F1rzYGVxyL6tmgBUAEPrEz92mBXjByMRiJdba9wpnN37RLLAXa',
  79. 'xprv': 'xprv9s21ZrQH143K48vGoLGRPxgo2JNkJ3J3fqkirQC2zVdk5Dgd5w14S7fRDyHH4dWNHUgkvsvNDCkvAwcSHNAQwhwgNMgZhLtQC63zxwhQmRv',
  80. },
  81. "m/0'": {
  82. 'xpub': 'xpub69AUMk3qDBi3uW1sXgjCmVjJ2G6WQoYSnNHyzkmdCHEhSZ4tBok37xfFEqHd2AddP56Tqp4o56AePAgCjYdvpW2PU2jbUPFKsav5ut6Ch1m',
  83. 'xprv': 'xprv9vB7xEWwNp9kh1wQRfCCQMnZUEG21LpbR9NPCNN1dwhiZkjjeGRnaALmPXCX7SgjFTiCTT6bXes17boXtjq3xLpcDjzEuGLQBM5ohqkao9G',
  84. },
  85. "m/0'/1'": {
  86. 'xpub': 'xpub6BJA1jSqiukeaesWfxe6sNK9CCGaujFFSJLomWHprUL9DePQ4JDkM5d88n49sMGJxrhpjazuXYWdMf17C9T5XnxkopaeS7jGk1GyyVziaMt',
  87. 'xprv': 'xprv9xJocDuwtYCMNAo3Zw76WENQeAS6WGXQ55RCy7tDJ8oALr4FWkuVoHJeHVAcAqiZLE7Je3vZJHxspZdFHfnBEjHqU5hG1Jaj32dVoS6XLT1',
  88. },
  89. }]
  90. # Source: BIP-32
  91. # These vectors test that invalid extended keys are recognized as invalid.
  92. vectors_bip32_invalid = [
  93. ('xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6LBpB85b3D2yc8sfvZU521AAwdZafEz7mnzBBsz4wKY5fTtTQBm', 'pubkey version / prvkey mismatch'),
  94. ('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGTQQD3dC4H2D5GBj7vWvSQaaBv5cxi9gafk7NF3pnBju6dwKvH', 'prvkey version / pubkey mismatch'),
  95. ('xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Txnt3siSujt9RCVYsx4qHZGc62TG4McvMGcAUjeuwZdduYEvFn', 'invalid pubkey prefix 04'),
  96. ('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGpWnsj83BHtEy5Zt8CcDr1UiRXuWCmTQLxEK9vbz5gPstX92JQ', 'invalid prvkey prefix 04'),
  97. ('xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6N8ZMMXctdiCjxTNq964yKkwrkBJJwpzZS4HS2fxvyYUA4q2Xe4', 'invalid pubkey prefix 01'),
  98. ('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD9y5gkZ6Eq3Rjuahrv17fEQ3Qen6J', 'invalid prvkey prefix 01'),
  99. ('xprv9s2SPatNQ9Vc6GTbVMFPFo7jsaZySyzk7L8n2uqKXJen3KUmvQNTuLh3fhZMBoG3G4ZW1N2kZuHEPY53qmbZzCHshoQnNf4GvELZfqTUrcv', 'zero depth with non-zero parent fingerprint'),
  100. ('xpub661no6RGEX3uJkY4bNnPcw4URcQTrSibUZ4NqJEw5eBkv7ovTwgiT91XX27VbEXGENhYRCf7hyEbWrR3FewATdCEebj6znwMfQkhRYHRLpJ', 'zero depth with non-zero parent fingerprint'),
  101. ('xprv9s21ZrQH4r4TsiLvyLXqM9P7k1K3EYhA1kkD6xuquB5i39AU8KF42acDyL3qsDbU9NmZn6MsGSUYZEsuoePmjzsB3eFKSUEh3Gu1N3cqVUN', 'zero depth with non-zero index'),
  102. ('xpub661MyMwAuDcm6CRQ5N4qiHKrJ39Xe1R1NyfouMKTTWcguwVcfrZJaNvhpebzGerh7gucBvzEQWRugZDuDXjNDRmXzSZe4c7mnTK97pTvGS8', 'zero depth with non-zero index'),
  103. ('DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHGMQzT7ayAmfo4z3gY5KfbrZWZ6St24UVf2Qgo6oujFktLHdHY4', 'unknown extended key version'),
  104. ('DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHPmHJiEDXkTiJTVV9rHEBUem2mwVbbNfvT2MTcAqj3nesx8uBf9', 'unknown extended key version'),
  105. ('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzF93Y5wvzdUayhgkkFoicQZcP3y52uPPxFnfoLZB21Teqt1VvEHx', 'private key 0 not in 1..n-1'),
  106. ('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD5SDKr24z3aiUvKr9bJpdrcLg1y3G', 'private key n not in 1..n-1'),
  107. ('xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Q5JXayek4PRsn35jii4veMimro1xefsM58PgBMrvdYre8QyULY', 'invalid pubkey 02000000...07'),
  108. ('xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHL', 'invalid checksum'),
  109. ]
  110. # Source: bip_utils
  111. vectors_derive = {
  112. 'bech32': {
  113. 0: 'bc1qwg77fxw0tkmc3h58tcnnpegxk7mp3h6ly44d3n',
  114. 1: 'bc1q6g79y6kwpkufevv2njacvnqnsdxmen68jyvjde',
  115. 2: 'bc1qknujpwlxc9e9e6avz50q5k90p552xy8g3qjd8u',
  116. }
  117. }
  118. # Source: bip_utils
  119. vectors_addrfmt = {
  120. 'pub': {
  121. 'compressed': 'xpub6GJknXsmpFcEubJsddGacncHhyY5Bk9zMQKC8pC97vBVAphrchYxuoJsAqfZW2uEMfPr6umSPRrhuaA7zeuExkwuAWiUcKcXjSf437VMLwR',
  122. 'segwit': 'ypub6aNved9dRKbMRjLfMbGPoXKgNN1tr86qi8WoBxSVr4mHX9fdzUxJFtEH63QZZxArk4f2fwFZQUQ7FRkNiBarTLu2Y69SRxzn68WysngXPrp',
  123. 'bech32': 'zpub6urFg31yVogtpf3Y7aV3CLuxQUsEpZ2asqBx3fzYMuFRTFrMNKxn5B2QXAGMSuwVfEA9KSJr2CUs8vqbmhUCkzVvxysB4p6vybLS2CgQnze',
  124. },
  125. 'prv': {
  126. 'compressed': 'xprvA3KQP2Lsyt3wh7EQXbjaFefZ9whanHS8zBPbLRnXZaeWJ2Ni5AEiMzzPKbKHsG7Dn6hkhSkG8V4H4XUsjszxU4nd2sMnc5ag9sHLLYBqrr4',
  127. 'segwit': 'yprvAMPaF7cjax34DFGCFZjPSPNwpLBQSfNzLubCPa2tHjEJeMLVSwe3i5uoEnUg4etxP3XEqr5ZJinjGkUJrte3xFNZ1jVKbjVaFJVHi4Msekw',
  128. 'bech32': 'zprvAgruGXV5fS8bcAy51Yx2qCyDrT2kR6JjWcGMFHavoZiSaTXCpneXXNhvfsMLeyBmjzACqkpxB2KGCMAe85wUrW1dnenu6kVHCk4kXh7XFE6',
  129. }
  130. }
  131. # Source: Asgardex Wallet
  132. vectors_multicoin = {
  133. 'btc_bech32': 'bc1qwg77fxw0tkmc3h58tcnnpegxk7mp3h6ly44d3n',
  134. 'eth': '373731f4d885Fc7Da05498F9f0804a87A14F891b',
  135. 'doge': 'DFX88RXpi4S4W24YVvuMgbdUcCAYNeEYGd',
  136. 'avax-c': '0x373731f4d885Fc7Da05498F9f0804a87A14F891b',
  137. 'ltc_bech32': 'ltc1q3uh5ga5cp9kkdfx6a52uymxj9keq4tpzep7er0',
  138. 'bch_compressed': 'bitcoincash:qpqpcllprftg4s0chdgkpxhxv23wfymq3gj7n0a9vw',
  139. 'bsc_smart': '0x373731f4d885Fc7Da05498F9f0804a87A14F891b',
  140. 'bnb_beacon': 'bnb179c3ymltqm4utlp089zxqeta5dvn48a305rhe5',
  141. 'rune': 'thor1nr6fye3nznyn20m5w6fey6w8a8l4q599cdqmpc',
  142. }
  143. def wif2addr(cfg, wif):
  144. from mmgen.tool.coin import tool_cmd
  145. return tool_cmd(
  146. cfg = cfg.base_cfg,
  147. cmdname = 'wif2addr',
  148. proto = cfg.base_cfg._proto,
  149. mmtype = cfg.addr_type).wif2addr(wif)
  150. class unit_tests:
  151. altcoin_deps = ('multicoin',)
  152. @property
  153. def _seed(self):
  154. if not hasattr(self, '__seed'):
  155. with open('test/ref/98831F3A.bip39') as fh:
  156. mnemonic = fh.read().strip()
  157. from mmgen.bip39 import bip39
  158. self.__seed = bip39().generate_seed(mnemonic.split())
  159. return self.__seed
  160. def chainparams(self, name, ut):
  161. for bipnum, idx, chain, addr_cls in (
  162. (44, 0, 'btc', 'P2PKH'),
  163. (49, 0, 'btc', 'P2SH'),
  164. (84, 0, 'btc', 'P2WPKH'),
  165. (44, 60, 'eth', 'Eth'),
  166. (44, 61, 'etc', 'Eth'),
  167. (44, 2, 'ltc', 'P2PKH'),
  168. (44, 3, 'doge', 'P2PKH'),
  169. ):
  170. res = get_chain_params(bipnum, chain)
  171. assert res.idx == idx, res.idx
  172. assert res.chain == chain.upper()
  173. assert res.addr_cls == addr_cls
  174. vmsg(f' {res}')
  175. vmsg('')
  176. return True
  177. def derive(self, name, ut):
  178. vmsg('seed: 98831F3A (default derivation)')
  179. m = MasterNode(cfg, self._seed)
  180. purpose = m.init_cfg(coin='btc', addr_type='bech32').derive_private()
  181. vmsg(f' {purpose.address=}')
  182. coin_type1 = purpose.derive_private()
  183. coin_type2 = m.to_coin_type(coin='btc', addr_type='bech32')
  184. assert coin_type1.address == coin_type2.address
  185. vmsg(f' {coin_type1.address=}')
  186. acct = coin_type2.derive_private(idx=0)
  187. chain1 = acct.derive_private(idx=0, hardened=False)
  188. chain2 = m.to_chain(idx=0, coin='btc', addr_type='bech32', public=False)
  189. assert chain2.address == chain1.address
  190. chain3 = m.to_coin_type(coin='btc', addr_type='bech32').to_chain(0, public=True)
  191. assert chain3.address == chain1.address
  192. vmsg(f' {chain1.address=}')
  193. a = BipHDNode.from_extended_key(cfg, 'btc', chain2.xpub)
  194. b = BipHDNode.from_extended_key(cfg, 'btc', chain2.xprv)
  195. vmsg(
  196. '\n xpub:\n' +
  197. fmt(str(Bip32ExtendedKey(b.xpub)), indent=' ')
  198. )
  199. assert a.xpub == b.xpub
  200. vmsg(' Addresses:')
  201. for i in range(3):
  202. res = chain1.derive_public(i)
  203. vmsg(f' {i} {res.address}')
  204. assert res.address == vectors_derive['bech32'][i]
  205. res = chain1.derive_private(i)
  206. assert res.address == vectors_derive['bech32'][i]
  207. vmsg('')
  208. return True
  209. def derive_addrfmt(self, name, ut):
  210. vmsg('seed: 98831F3A (default derivation)')
  211. m = MasterNode(cfg, self._seed)
  212. for addr_type in ('compressed', 'segwit', 'bech32'):
  213. chk_xpub = vectors_addrfmt['pub'][addr_type]
  214. chk_xprv = vectors_addrfmt['prv'][addr_type]
  215. res1 = m.to_chain(idx=0, coin='btc', addr_type=addr_type).derive_public(0)
  216. vmsg(f' {addr_type}: {res1.xpub}')
  217. assert res1.xpub == chk_xpub
  218. res2 = m.to_chain(idx=0, coin='btc', addr_type=addr_type).derive_private(0, False)
  219. vmsg(f' {addr_type}: {res2.xprv}')
  220. assert res2.xprv == chk_xprv
  221. assert res2.xpub == chk_xpub
  222. assert res2.address == wif2addr(res2.cfg, res2.privkey.wif)
  223. vmsg('')
  224. return True
  225. def path(self, name, ut):
  226. for vec in vectors_bip32:
  227. seed = bytes.fromhex(vec['seed'])
  228. vmsg(f'Seed: {vec["seed"]}')
  229. for n, path_str in enumerate(vec):
  230. if path_str in ('seed', 'comment'):
  231. continue
  232. path_arg = path_str.replace("'", 'H') if n % 2 else path_str
  233. node = BipHDNode.from_path(cfg, seed, path_arg, no_path_checks=True)
  234. vmsg(' Path {} {}'.format(pink(path_str), blue('('+node.desc+')')))
  235. for xkey_type in ('xpub', 'xprv'):
  236. vmsg(f' {getattr(node, xkey_type)}')
  237. assert getattr(node, xkey_type) == vec[path_str][xkey_type]
  238. vmsg('')
  239. return True
  240. def parse_extended(self, name, ut):
  241. vmsg('Parsing and validating extended keys:\n')
  242. for vec in vectors_bip32:
  243. vmsg(f' Seed: {vec["seed"]}')
  244. for path_str in vec:
  245. if path_str in ('seed', 'comment'):
  246. continue
  247. vmsg(' Path {}'.format(pink(path_str)))
  248. for xkey_type in ('xpub', 'xprv'):
  249. xkey = vec[path_str][xkey_type]
  250. vmsg(f' {xkey}')
  251. node = BipHDNode.from_extended_key(cfg, 'btc', xkey)
  252. assert getattr(node, xkey_type) == xkey
  253. vmsg('')
  254. return True
  255. def multicoin(self, name, ut):
  256. m = MasterNode(cfg, self._seed)
  257. fs = ' {:6} {:10} {}'
  258. vmsg(fs.format('COIN', 'ADDR_TYPE', 'ADDR'))
  259. for id_str, addr_chk in vectors_multicoin.items():
  260. ss = id_str.split('_')
  261. coin = ss[0]
  262. addr_type = ss[1] if len(ss) == 2 else None
  263. if coin not in BipHDConfig.supported_coins:
  264. vmsg(gray(fs.format(coin.upper(), (addr_type or ''), '[not supported yet]')))
  265. continue
  266. vmsg(fs.format(coin.upper(), (addr_type or 'auto'), addr_chk))
  267. node = m.to_chain(idx=0, coin=coin, addr_type=addr_type).derive_private(0)
  268. xpub_parsed = node.key_extended(public=True)
  269. xprv_parsed = node.key_extended(public=False)
  270. addr = node.address
  271. at_arg = 'compressed' if coin == 'doge' else None
  272. from_xpub = BipHDNode.from_extended_key(node.cfg.base_cfg, coin, xpub_parsed.base58, addr_type=at_arg)
  273. from_xprv = BipHDNode.from_extended_key(node.cfg.base_cfg, coin, xprv_parsed.base58, addr_type=at_arg)
  274. assert from_xpub.xpub == node.xpub, f'{from_xpub.xpub=} != {node.xpub}'
  275. assert from_xprv.xpub == node.xpub, f'{from_xprv.xpub=} != {node.xpub}'
  276. assert from_xpub.address == addr, f'{from_xpub.address} != {addr}'
  277. assert from_xprv.address == addr, f'{from_xprv.address} != {addr}'
  278. addr_from_wif = wif2addr(node.cfg, node.privkey.wif)
  279. proto = node.cfg.base_cfg._proto
  280. if proto.base_proto == 'Ethereum':
  281. addr = proto.checksummed_addr(node.address)
  282. addr_from_wif = proto.checksummed_addr(addr_from_wif)
  283. assert addr == addr_chk, f'{addr} != {addr_chk}'
  284. assert addr == addr_from_wif, f'{addr} != {addr_from_wif}'
  285. vmsg('')
  286. return True
  287. def errors(self, name, ut):
  288. vmsg('Checking error handling:')
  289. m = MasterNode(cfg, self._seed)
  290. m_btc = m.init_cfg(coin='btc', addr_type='bech32')
  291. purpose = m_btc.derive_private()
  292. coin_type = purpose.derive_private()
  293. acct = coin_type.derive_private(idx=0)
  294. chain = acct.derive_private(idx=0, hardened=False)
  295. def bad01():
  296. m.to_chain(idx=0, coin='erq', addr_type='C')
  297. def bad02():
  298. m_btc.derive_private(idx=0)
  299. def bad03():
  300. m_btc.derive_private(hardened=False)
  301. def bad04():
  302. purpose.derive_private(idx=8)
  303. def bad05():
  304. purpose.derive_private(hardened=False)
  305. def bad06():
  306. coin_type.derive_private() # no acct idx
  307. def bad08():
  308. m_btc.derive_public() # must be private
  309. def bad09():
  310. coin_type.derive_private(idx=8, hardened=False)
  311. def bad10():
  312. acct.derive_private()
  313. def bad11():
  314. chain.derive_private()
  315. def bad12():
  316. chain.derive_private(hardened=True, idx=3)
  317. bad_data = (
  318. ('unsupported coin', 'ValueError', 'not supported', bad01),
  319. ('depth 1 (purpose): idx not None', 'ValueError', 'index for path comp', bad02),
  320. ('depth 1 (purpose): hardened False', 'ValueError', 'value for ‘hardened’', bad03),
  321. ('depth 2 (coin type): idx mismatch', 'ValueError', 'index 8 at depth', bad04),
  322. ('depth 2 (coin type): hardened False', 'ValueError', 'value for ‘hardened’', bad05),
  323. ('depth 3 (account): idx not set', 'ValueError', 'must be set', bad06),
  324. ('depth 1 (purpose): node not hardened', 'ValueError', 'must be hardened', bad08),
  325. ('depth 3 (account): node not hardened', 'ValueError', 'value for ‘hardened’', bad09),
  326. ('depth 4 (chain): idx not set', 'ValueError', 'must be either 0', bad10),
  327. ('depth 5 (leaf node): idx not set', 'ValueError', 'must be set', bad11),
  328. ('depth 5 (leaf node): hardened True', 'ValueError', 'must be None', bad12),
  329. )
  330. ut.process_bad_data(bad_data, pfx='')
  331. vmsg('')
  332. return True
  333. def parse_extended_errors(self, name, ut):
  334. vmsg('Parsing and validating invalid extended keys:')
  335. vec = vectors_bip32_invalid
  336. func = [lambda m=n: BipHDNode.from_extended_key(cfg, 'btc', vec[m][0]) for n in range(len(vec))]
  337. exc = (
  338. 'first byte for public',
  339. 'first byte for private',
  340. 'first byte for public',
  341. 'first byte for private',
  342. 'first byte for public',
  343. 'first byte for private',
  344. 'non-zero parent fingerprint',
  345. 'non-zero parent fingerprint',
  346. 'non-zero index',
  347. 'non-zero index',
  348. 'unrecognized extended key v',
  349. 'unrecognized extended key v',
  350. 'private key is zero!',
  351. 'private key >= group order!',
  352. 'Public key could not be parsed', # extmod
  353. 'incorrect checksum',
  354. )
  355. ut.process_bad_data([(vec[n][1], 'ValueError', exc[n], func[n]) for n in range(len(vec))], pfx='')
  356. vmsg('')
  357. return True