ut_bip_hd.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. #!/usr/bin/env python3
  2. """
  3. test.modtest_d.ut_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. }
  142. def wif2addr(cfg, wif):
  143. from mmgen.tool.coin import tool_cmd
  144. return tool_cmd(
  145. cfg = cfg.base_cfg,
  146. cmdname = 'wif2addr',
  147. proto = cfg.base_cfg._proto,
  148. mmtype = cfg.addr_type).wif2addr(wif)
  149. class unit_tests:
  150. altcoin_deps = ('multicoin',)
  151. @property
  152. def _seed(self):
  153. if not hasattr(self, '__seed'):
  154. with open('test/ref/98831F3A.bip39') as fh:
  155. mnemonic = fh.read().strip()
  156. from mmgen.bip39 import bip39
  157. self.__seed = bip39().generate_seed(mnemonic.split())
  158. return self.__seed
  159. def chainparams(self, name, ut):
  160. for bipnum, idx, chain, addr_cls in (
  161. (44, 0, 'btc', 'P2PKH'),
  162. (49, 0, 'btc', 'P2SH'),
  163. (84, 0, 'btc', 'P2WPKH'),
  164. (44, 60, 'eth', 'Eth'),
  165. (44, 61, 'etc', 'Eth'),
  166. (44, 2, 'ltc', 'P2PKH'),
  167. (44, 3, 'doge', 'P2PKH'),
  168. ):
  169. res = get_chain_params(bipnum, chain)
  170. assert res.idx == idx, res.idx
  171. assert res.chain == chain.upper()
  172. assert res.addr_cls == addr_cls
  173. vmsg(f' {res}')
  174. vmsg('')
  175. return True
  176. def derive(self, name, ut):
  177. vmsg('seed: 98831F3A (default derivation)')
  178. m = MasterNode(cfg, self._seed)
  179. purpose = m.init_cfg(coin='btc', addr_type='bech32').derive_private()
  180. vmsg(f' {purpose.address=}')
  181. coin_type1 = purpose.derive_private()
  182. coin_type2 = m.to_coin_type('btc', addr_type='bech32')
  183. assert coin_type1.address == coin_type2.address
  184. vmsg(f' {coin_type1.address=}')
  185. acct = coin_type2.derive_private(idx=0)
  186. chain1 = acct.derive_private(idx=0, hardened=False)
  187. chain2 = m.to_chain(idx=0, coin='btc', addr_type='bech32', public=False)
  188. assert chain2.address == chain1.address
  189. chain3 = m.to_coin_type(coin='btc', addr_type='bech32').to_chain(0, public=True)
  190. assert chain3.address == chain1.address
  191. vmsg(f' {chain1.address=}')
  192. a = BipHDNode.from_extended_key(cfg, 'btc', chain2.xpub)
  193. b = BipHDNode.from_extended_key(cfg, 'btc', chain2.xprv)
  194. vmsg(
  195. '\n xpub:\n' +
  196. fmt(str(Bip32ExtendedKey(b.xpub)), indent=' ')
  197. )
  198. assert a.xpub == b.xpub
  199. vmsg(' Addresses:')
  200. for i in range(3):
  201. res = chain1.derive_public(i)
  202. vmsg(f' {i} {res.address}')
  203. assert res.address == vectors_derive['bech32'][i]
  204. res = chain1.derive_private(i)
  205. assert res.address == vectors_derive['bech32'][i]
  206. vmsg('')
  207. return True
  208. def derive_addrfmt(self, name, ut):
  209. vmsg('seed: 98831F3A (default derivation)')
  210. m = MasterNode(cfg, self._seed)
  211. for addr_type in ('compressed', 'segwit', 'bech32'):
  212. chk_xpub = vectors_addrfmt['pub'][addr_type]
  213. chk_xprv = vectors_addrfmt['prv'][addr_type]
  214. res1 = m.to_chain(idx=0, coin='btc', addr_type=addr_type).derive_public(0)
  215. vmsg(f' {addr_type}: {res1.xpub}')
  216. assert res1.xpub == chk_xpub
  217. res2 = m.to_chain(idx=0, coin='btc', addr_type=addr_type).derive_private(0, False)
  218. vmsg(f' {addr_type}: {res2.xprv}')
  219. assert res2.xprv == chk_xprv
  220. assert res2.xpub == chk_xpub
  221. assert res2.address == wif2addr(res2.cfg, res2.privkey.wif)
  222. vmsg('')
  223. return True
  224. def path(self, name, ut):
  225. for vec in vectors_bip32:
  226. seed = bytes.fromhex(vec['seed'])
  227. vmsg(f'Seed: {vec["seed"]}')
  228. for n, path_str in enumerate(vec):
  229. if path_str in ('seed', 'comment'):
  230. continue
  231. path_arg = path_str.replace("'", 'H') if n % 2 else path_str
  232. node = BipHDNode.from_path(cfg, seed, path_arg, no_path_checks=True)
  233. vmsg(' Path {} {}'.format(pink(path_str), blue('('+node.desc+')')))
  234. for xkey_type in ('xpub', 'xprv'):
  235. vmsg(f' {getattr(node, xkey_type)}')
  236. assert getattr(node, xkey_type) == vec[path_str][xkey_type]
  237. vmsg('')
  238. return True
  239. def parse_extended(self, name, ut):
  240. vmsg('Parsing and validating extended keys:\n')
  241. for vec in vectors_bip32:
  242. vmsg(f' Seed: {vec["seed"]}')
  243. for path_str in vec:
  244. if path_str in ('seed', 'comment'):
  245. continue
  246. vmsg(' Path {}'.format(pink(path_str)))
  247. for xkey_type in ('xpub', 'xprv'):
  248. xkey = vec[path_str][xkey_type]
  249. vmsg(f' {xkey}')
  250. node = BipHDNode.from_extended_key(cfg, 'btc', xkey)
  251. assert getattr(node, xkey_type) == xkey
  252. vmsg('')
  253. return True
  254. def multicoin(self, name, ut):
  255. m = MasterNode(cfg, self._seed)
  256. fs = ' {:6} {:10} {}'
  257. vmsg(fs.format('COIN', 'ADDR_TYPE', 'ADDR'))
  258. for id_str, addr_chk in vectors_multicoin.items():
  259. ss = id_str.split('_')
  260. coin = ss[0]
  261. addr_type = ss[1] if len(ss) == 2 else None
  262. if coin not in BipHDConfig.supported_coins:
  263. vmsg(gray(fs.format(coin.upper(), (addr_type or ''), '[not supported yet]')))
  264. continue
  265. vmsg(fs.format(coin.upper(), (addr_type or 'auto'), addr_chk))
  266. node = m.to_chain(idx=0, coin=coin, addr_type=addr_type).derive_private(0)
  267. xpub_parsed = node.key_extended(public=True)
  268. xprv_parsed = node.key_extended(public=False)
  269. addr = node.address
  270. at_arg = 'compressed' if coin == 'doge' else None
  271. from_xpub = BipHDNode.from_extended_key(node.cfg.base_cfg, coin, xpub_parsed.base58, addr_type=at_arg)
  272. from_xprv = BipHDNode.from_extended_key(node.cfg.base_cfg, coin, xprv_parsed.base58, addr_type=at_arg)
  273. assert from_xpub.xpub == node.xpub, f'{from_xpub.xpub=} != {node.xpub}'
  274. assert from_xprv.xpub == node.xpub, f'{from_xprv.xpub=} != {node.xpub}'
  275. assert from_xpub.address == addr, f'{from_xpub.address} != {addr}'
  276. assert from_xprv.address == addr, f'{from_xprv.address} != {addr}'
  277. addr_from_wif = wif2addr(node.cfg, node.privkey.wif)
  278. proto = node.cfg.base_cfg._proto
  279. if proto.base_proto == 'Ethereum':
  280. addr = proto.checksummed_addr(node.address)
  281. addr_from_wif = proto.checksummed_addr(addr_from_wif)
  282. assert addr == addr_chk, f'{addr} != {addr_chk}'
  283. assert addr == addr_from_wif, f'{addr} != {addr_from_wif}'
  284. vmsg('')
  285. return True
  286. def errors(self, name, ut):
  287. vmsg('Checking error handling:')
  288. m = MasterNode(cfg, self._seed)
  289. m_btc = m.init_cfg(coin='btc', addr_type='bech32')
  290. purpose = m_btc.derive_private()
  291. coin_type = purpose.derive_private()
  292. acct = coin_type.derive_private(idx=0)
  293. chain = acct.derive_private(idx=0, hardened=False)
  294. def bad01():
  295. m.to_chain(idx=0, coin='erq', addr_type='C')
  296. def bad02():
  297. m_btc.derive_private(idx=0)
  298. def bad03():
  299. m_btc.derive_private(hardened=False)
  300. def bad04():
  301. purpose.derive_private(idx=8)
  302. def bad05():
  303. purpose.derive_private(hardened=False)
  304. def bad06():
  305. coin_type.derive_private() # no acct idx
  306. def bad08():
  307. m_btc.derive_public() # must be private
  308. def bad09():
  309. coin_type.derive_private(idx=8, hardened=False)
  310. def bad10():
  311. acct.derive_private()
  312. def bad11():
  313. chain.derive_private()
  314. def bad12():
  315. chain.derive_private(hardened=True, idx=3)
  316. bad_data = (
  317. ('unsupported coin', 'ValueError', 'not supported', bad01),
  318. ('depth 1 (purpose): idx not None', 'ValueError', 'index for path comp', bad02),
  319. ('depth 1 (purpose): hardened False', 'ValueError', 'value for ‘hardened’', bad03),
  320. ('depth 2 (coin type): idx mismatch', 'ValueError', 'index 8 at depth', bad04),
  321. ('depth 2 (coin type): hardened False', 'ValueError', 'value for ‘hardened’', bad05),
  322. ('depth 3 (account): idx not set', 'ValueError', 'must be set', bad06),
  323. ('depth 1 (purpose): node not hardened', 'ValueError', 'must be hardened', bad08),
  324. ('depth 3 (account): node not hardened', 'ValueError', 'value for ‘hardened’', bad09),
  325. ('depth 4 (chain): idx not set', 'ValueError', 'must be either 0', bad10),
  326. ('depth 5 (leaf node): idx not set', 'ValueError', 'must be set', bad11),
  327. ('depth 5 (leaf node): hardened True', 'ValueError', 'must be None', bad12),
  328. )
  329. ut.process_bad_data(bad_data, pfx='')
  330. vmsg('')
  331. return True
  332. def parse_extended_errors(self, name, ut):
  333. vmsg('Parsing and validating invalid extended keys:')
  334. vec = vectors_bip32_invalid
  335. func = [lambda m=n: BipHDNode.from_extended_key(cfg, 'btc', vec[m][0]) for n in range(len(vec))]
  336. exc = (
  337. 'first byte for public',
  338. 'first byte for private',
  339. 'first byte for public',
  340. 'first byte for private',
  341. 'first byte for public',
  342. 'first byte for private',
  343. 'non-zero parent fingerprint',
  344. 'non-zero parent fingerprint',
  345. 'non-zero index',
  346. 'non-zero index',
  347. 'unrecognized extended key v',
  348. 'unrecognized extended key v',
  349. 'private key is zero!',
  350. 'private key >= group order!',
  351. 'Public key could not be parsed', # extmod
  352. 'incorrect checksum',
  353. )
  354. ut.process_bad_data([(vec[n][1], 'ValueError', exc[n], func[n]) for n in range(len(vec))], pfx='')
  355. vmsg('')
  356. return True