ut_bip_hd.py 18 KB

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