|
@@ -0,0 +1,419 @@
|
|
|
+#!/usr/bin/env python3
|
|
|
+#
|
|
|
+# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
|
|
|
+# Copyright (C)2013-2024 The MMGen Project <mmgen@tuta.io>
|
|
|
+# Licensed under the GNU General Public License, Version 3:
|
|
|
+# https://www.gnu.org/licenses
|
|
|
+# Public project repositories:
|
|
|
+# https://github.com/mmgen/mmgen-wallet
|
|
|
+# https://gitlab.com/mmgen/mmgen-wallet
|
|
|
+
|
|
|
+"""
|
|
|
+test.unit_tests_d.ut_bip_hd: bip_hd unit test for the MMGen suite
|
|
|
+"""
|
|
|
+
|
|
|
+from mmgen.color import gray,pink,blue
|
|
|
+from mmgen.util import fmt
|
|
|
+from mmgen.bip_hd import Bip32ExtendedKey,BipHDConfig,BipHDNode,MasterNode,get_chain_params
|
|
|
+
|
|
|
+from ..include.common import cfg,vmsg
|
|
|
+
|
|
|
+# Source: BIP-32
|
|
|
+vectors_bip32 = [
|
|
|
+{
|
|
|
+ 'seed': '000102030405060708090a0b0c0d0e0f',
|
|
|
+ "m": {
|
|
|
+ 'xpub': 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8',
|
|
|
+ 'xprv': 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi',
|
|
|
+ },
|
|
|
+ "m/0'": {
|
|
|
+ 'xpub': 'xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw',
|
|
|
+ 'xprv': 'xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7',
|
|
|
+ },
|
|
|
+ "m/0'/1": {
|
|
|
+ 'xpub': 'xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ',
|
|
|
+ 'xprv': 'xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs',
|
|
|
+ },
|
|
|
+ "m/0'/1/2'": {
|
|
|
+ 'xpub': 'xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5',
|
|
|
+ 'xprv': 'xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM',
|
|
|
+ },
|
|
|
+ "m/0'/1/2'/2": {
|
|
|
+ 'xpub': 'xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV',
|
|
|
+ 'xprv': 'xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334',
|
|
|
+ },
|
|
|
+ "m/0'/1/2'/2/1000000000": {
|
|
|
+ 'xpub': 'xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy',
|
|
|
+ 'xprv': 'xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76',
|
|
|
+ },
|
|
|
+},{
|
|
|
+ 'seed': 'fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542',
|
|
|
+ 'm': {
|
|
|
+ 'xpub': 'xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB',
|
|
|
+ 'xprv': 'xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U',
|
|
|
+ },
|
|
|
+ "m/0": {
|
|
|
+ 'xpub': 'xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH',
|
|
|
+ 'xprv': 'xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt',
|
|
|
+ },
|
|
|
+ "m/0/2147483647'": {
|
|
|
+ 'xpub': 'xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a',
|
|
|
+ 'xprv': 'xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9',
|
|
|
+ },
|
|
|
+ "m/0/2147483647'/1": {
|
|
|
+ 'xpub': 'xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon',
|
|
|
+ 'xprv': 'xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef',
|
|
|
+ },
|
|
|
+ "m/0/2147483647'/1/2147483646'": {
|
|
|
+ 'xpub': 'xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL',
|
|
|
+ 'xprv': 'xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc',
|
|
|
+ },
|
|
|
+ "m/0/2147483647'/1/2147483646'/2": {
|
|
|
+ 'xpub': 'xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt',
|
|
|
+ 'xprv': 'xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j',
|
|
|
+ },
|
|
|
+},{
|
|
|
+ 'comment': 'These vectors test for the retention of leading zeros. See bitpay/bitcore-lib#47 and iancoleman/bip39#58 for more information.',
|
|
|
+ 'seed': '4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be',
|
|
|
+ 'm': {
|
|
|
+ 'xpub': 'xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13',
|
|
|
+ 'xprv': 'xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6',
|
|
|
+ },
|
|
|
+ "m/0'": {
|
|
|
+ 'xpub': 'xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y',
|
|
|
+ 'xprv': 'xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L',
|
|
|
+ },
|
|
|
+},{
|
|
|
+ 'comment': 'These vectors test for the retention of leading zeros. See btcsuite/btcutil#172 for more information.',
|
|
|
+ 'seed': '3ddd5602285899a946114506157c7997e5444528f3003f6134712147db19b678',
|
|
|
+ "m": {
|
|
|
+ 'xpub': 'xpub661MyMwAqRbcGczjuMoRm6dXaLDEhW1u34gKenbeYqAix21mdUKJyuyu5F1rzYGVxyL6tmgBUAEPrEz92mBXjByMRiJdba9wpnN37RLLAXa',
|
|
|
+ 'xprv': 'xprv9s21ZrQH143K48vGoLGRPxgo2JNkJ3J3fqkirQC2zVdk5Dgd5w14S7fRDyHH4dWNHUgkvsvNDCkvAwcSHNAQwhwgNMgZhLtQC63zxwhQmRv',
|
|
|
+ },
|
|
|
+ "m/0'": {
|
|
|
+ 'xpub': 'xpub69AUMk3qDBi3uW1sXgjCmVjJ2G6WQoYSnNHyzkmdCHEhSZ4tBok37xfFEqHd2AddP56Tqp4o56AePAgCjYdvpW2PU2jbUPFKsav5ut6Ch1m',
|
|
|
+ 'xprv': 'xprv9vB7xEWwNp9kh1wQRfCCQMnZUEG21LpbR9NPCNN1dwhiZkjjeGRnaALmPXCX7SgjFTiCTT6bXes17boXtjq3xLpcDjzEuGLQBM5ohqkao9G',
|
|
|
+ },
|
|
|
+ "m/0'/1'": {
|
|
|
+ 'xpub': 'xpub6BJA1jSqiukeaesWfxe6sNK9CCGaujFFSJLomWHprUL9DePQ4JDkM5d88n49sMGJxrhpjazuXYWdMf17C9T5XnxkopaeS7jGk1GyyVziaMt',
|
|
|
+ 'xprv': 'xprv9xJocDuwtYCMNAo3Zw76WENQeAS6WGXQ55RCy7tDJ8oALr4FWkuVoHJeHVAcAqiZLE7Je3vZJHxspZdFHfnBEjHqU5hG1Jaj32dVoS6XLT1',
|
|
|
+ },
|
|
|
+}]
|
|
|
+
|
|
|
+# Source: BIP-32
|
|
|
+# These vectors test that invalid extended keys are recognized as invalid.
|
|
|
+vectors_bip32_invalid = [
|
|
|
+ ('xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6LBpB85b3D2yc8sfvZU521AAwdZafEz7mnzBBsz4wKY5fTtTQBm', 'pubkey version / prvkey mismatch'),
|
|
|
+ ('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGTQQD3dC4H2D5GBj7vWvSQaaBv5cxi9gafk7NF3pnBju6dwKvH', 'prvkey version / pubkey mismatch'),
|
|
|
+ ('xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Txnt3siSujt9RCVYsx4qHZGc62TG4McvMGcAUjeuwZdduYEvFn', 'invalid pubkey prefix 04'),
|
|
|
+ ('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGpWnsj83BHtEy5Zt8CcDr1UiRXuWCmTQLxEK9vbz5gPstX92JQ', 'invalid prvkey prefix 04'),
|
|
|
+ ('xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6N8ZMMXctdiCjxTNq964yKkwrkBJJwpzZS4HS2fxvyYUA4q2Xe4', 'invalid pubkey prefix 01'),
|
|
|
+ ('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD9y5gkZ6Eq3Rjuahrv17fEQ3Qen6J', 'invalid prvkey prefix 01'),
|
|
|
+ ('xprv9s2SPatNQ9Vc6GTbVMFPFo7jsaZySyzk7L8n2uqKXJen3KUmvQNTuLh3fhZMBoG3G4ZW1N2kZuHEPY53qmbZzCHshoQnNf4GvELZfqTUrcv', 'zero depth with non-zero parent fingerprint'),
|
|
|
+ ('xpub661no6RGEX3uJkY4bNnPcw4URcQTrSibUZ4NqJEw5eBkv7ovTwgiT91XX27VbEXGENhYRCf7hyEbWrR3FewATdCEebj6znwMfQkhRYHRLpJ', 'zero depth with non-zero parent fingerprint'),
|
|
|
+ ('xprv9s21ZrQH4r4TsiLvyLXqM9P7k1K3EYhA1kkD6xuquB5i39AU8KF42acDyL3qsDbU9NmZn6MsGSUYZEsuoePmjzsB3eFKSUEh3Gu1N3cqVUN', 'zero depth with non-zero index'),
|
|
|
+ ('xpub661MyMwAuDcm6CRQ5N4qiHKrJ39Xe1R1NyfouMKTTWcguwVcfrZJaNvhpebzGerh7gucBvzEQWRugZDuDXjNDRmXzSZe4c7mnTK97pTvGS8', 'zero depth with non-zero index'),
|
|
|
+ ('DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHGMQzT7ayAmfo4z3gY5KfbrZWZ6St24UVf2Qgo6oujFktLHdHY4', 'unknown extended key version'),
|
|
|
+ ('DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHPmHJiEDXkTiJTVV9rHEBUem2mwVbbNfvT2MTcAqj3nesx8uBf9', 'unknown extended key version'),
|
|
|
+ ('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzF93Y5wvzdUayhgkkFoicQZcP3y52uPPxFnfoLZB21Teqt1VvEHx', 'private key 0 not in 1..n-1'),
|
|
|
+ ('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD5SDKr24z3aiUvKr9bJpdrcLg1y3G', 'private key n not in 1..n-1'),
|
|
|
+ ('xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Q5JXayek4PRsn35jii4veMimro1xefsM58PgBMrvdYre8QyULY', 'invalid pubkey 02000000...07'),
|
|
|
+ ('xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHL', 'invalid checksum'),
|
|
|
+]
|
|
|
+
|
|
|
+# Source: bip_utils
|
|
|
+vectors_derive = {
|
|
|
+ 'bech32': {
|
|
|
+ 0: 'bc1qwg77fxw0tkmc3h58tcnnpegxk7mp3h6ly44d3n',
|
|
|
+ 1: 'bc1q6g79y6kwpkufevv2njacvnqnsdxmen68jyvjde',
|
|
|
+ 2: 'bc1qknujpwlxc9e9e6avz50q5k90p552xy8g3qjd8u',
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+# Source: bip_utils
|
|
|
+vectors_addrfmt = {
|
|
|
+ 'pub': {
|
|
|
+ 'compressed': 'xpub6GJknXsmpFcEubJsddGacncHhyY5Bk9zMQKC8pC97vBVAphrchYxuoJsAqfZW2uEMfPr6umSPRrhuaA7zeuExkwuAWiUcKcXjSf437VMLwR',
|
|
|
+ 'segwit': 'ypub6aNved9dRKbMRjLfMbGPoXKgNN1tr86qi8WoBxSVr4mHX9fdzUxJFtEH63QZZxArk4f2fwFZQUQ7FRkNiBarTLu2Y69SRxzn68WysngXPrp',
|
|
|
+ 'bech32': 'zpub6urFg31yVogtpf3Y7aV3CLuxQUsEpZ2asqBx3fzYMuFRTFrMNKxn5B2QXAGMSuwVfEA9KSJr2CUs8vqbmhUCkzVvxysB4p6vybLS2CgQnze',
|
|
|
+ },
|
|
|
+ 'prv': {
|
|
|
+ 'compressed': 'xprvA3KQP2Lsyt3wh7EQXbjaFefZ9whanHS8zBPbLRnXZaeWJ2Ni5AEiMzzPKbKHsG7Dn6hkhSkG8V4H4XUsjszxU4nd2sMnc5ag9sHLLYBqrr4',
|
|
|
+ 'segwit': 'yprvAMPaF7cjax34DFGCFZjPSPNwpLBQSfNzLubCPa2tHjEJeMLVSwe3i5uoEnUg4etxP3XEqr5ZJinjGkUJrte3xFNZ1jVKbjVaFJVHi4Msekw',
|
|
|
+ 'bech32': 'zprvAgruGXV5fS8bcAy51Yx2qCyDrT2kR6JjWcGMFHavoZiSaTXCpneXXNhvfsMLeyBmjzACqkpxB2KGCMAe85wUrW1dnenu6kVHCk4kXh7XFE6',
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+# Source: Asgardex Wallet
|
|
|
+vectors_multicoin = {
|
|
|
+ 'btc_bech32': 'bc1qwg77fxw0tkmc3h58tcnnpegxk7mp3h6ly44d3n',
|
|
|
+ 'eth': '373731f4d885Fc7Da05498F9f0804a87A14F891b',
|
|
|
+ 'doge': 'DFX88RXpi4S4W24YVvuMgbdUcCAYNeEYGd',
|
|
|
+ 'avax-c': '0x373731f4d885Fc7Da05498F9f0804a87A14F891b',
|
|
|
+ 'ltc_bech32': 'ltc1q3uh5ga5cp9kkdfx6a52uymxj9keq4tpzep7er0',
|
|
|
+ 'bch_cashaddr': 'bitcoincash:qpqpcllprftg4s0chdgkpxhxv23wfymq3gj7n0a9vw',
|
|
|
+ 'bsc_smart': '0x373731f4d885Fc7Da05498F9f0804a87A14F891b',
|
|
|
+ 'bnb_beacon': 'bnb179c3ymltqm4utlp089zxqeta5dvn48a305rhe5',
|
|
|
+}
|
|
|
+
|
|
|
+def wif2addr(cfg,wif):
|
|
|
+ from mmgen.tool.coin import tool_cmd
|
|
|
+ return tool_cmd(
|
|
|
+ cfg = cfg.base_cfg,
|
|
|
+ cmdname = 'wif2addr',
|
|
|
+ proto = cfg.base_cfg._proto,
|
|
|
+ mmtype = cfg.addr_type).wif2addr(wif)
|
|
|
+
|
|
|
+class unit_tests:
|
|
|
+
|
|
|
+ altcoin_deps = ('multicoin',)
|
|
|
+
|
|
|
+ @property
|
|
|
+ def _seed(self):
|
|
|
+ if not hasattr(self,'__seed'):
|
|
|
+ with open('test/ref/98831F3A.bip39') as fh:
|
|
|
+ mnemonic = fh.read().strip()
|
|
|
+ from mmgen.bip39 import bip39
|
|
|
+ self.__seed = bip39().generate_seed(mnemonic.split())
|
|
|
+ return self.__seed
|
|
|
+
|
|
|
+ def chainparams(self,name,ut):
|
|
|
+ for bipnum,idx,chain,addr_cls in (
|
|
|
+ (44, 0, 'btc', 'P2PKH'),
|
|
|
+ (49, 0, 'btc', 'P2SH'),
|
|
|
+ (84, 0, 'btc', 'P2WPKH'),
|
|
|
+ (44, 60, 'eth', 'Eth'),
|
|
|
+ (44, 61, 'etc', 'Eth'),
|
|
|
+ (44, 2, 'ltc', 'P2PKH'),
|
|
|
+ (44, 3, 'doge', 'P2PKH'),
|
|
|
+ ):
|
|
|
+ res = get_chain_params(bipnum,chain)
|
|
|
+ assert res.idx == idx, res.idx
|
|
|
+ assert res.chain == chain.upper()
|
|
|
+ assert res.addr_cls == addr_cls
|
|
|
+ vmsg(f' {res}')
|
|
|
+ vmsg('')
|
|
|
+ return True
|
|
|
+
|
|
|
+ def derive(self,name,ut):
|
|
|
+ vmsg('seed: 98831F3A (default derivation)')
|
|
|
+
|
|
|
+ m = MasterNode(cfg,self._seed)
|
|
|
+
|
|
|
+ purpose = m.init_cfg(coin='btc',addr_type='bech32').derive_private()
|
|
|
+ vmsg(f' {purpose.address=}')
|
|
|
+
|
|
|
+ coin_type1 = purpose.derive_private()
|
|
|
+
|
|
|
+ coin_type2 = m.to_coin_type('btc',addr_type='bech32')
|
|
|
+ assert coin_type1.address == coin_type2.address
|
|
|
+ vmsg(f' {coin_type1.address=}')
|
|
|
+
|
|
|
+ acct = coin_type2.derive_private(idx=0)
|
|
|
+ chain1 = acct.derive_private(idx=0,hardened=False)
|
|
|
+
|
|
|
+ chain2 = m.to_chain(idx=0,coin='btc',addr_type='bech32',public=False)
|
|
|
+ assert chain2.address == chain1.address
|
|
|
+
|
|
|
+ chain3 = m.to_coin_type(coin='btc',addr_type='bech32').to_chain(0,public=True)
|
|
|
+ assert chain3.address == chain1.address
|
|
|
+ vmsg(f' {chain1.address=}')
|
|
|
+
|
|
|
+ a = BipHDNode.from_extended_key(cfg,'btc',chain2.xpub)
|
|
|
+ b = BipHDNode.from_extended_key(cfg,'btc',chain2.xprv)
|
|
|
+ vmsg(
|
|
|
+ '\n xpub:\n' +
|
|
|
+ fmt(str(Bip32ExtendedKey(b.xpub)),indent=' ')
|
|
|
+ )
|
|
|
+ assert a.xpub == b.xpub
|
|
|
+
|
|
|
+ vmsg(' Addresses:')
|
|
|
+ for i in range(3):
|
|
|
+ res = chain1.derive_public(i)
|
|
|
+ vmsg(f' {i} {res.address}')
|
|
|
+ assert res.address == vectors_derive['bech32'][i]
|
|
|
+ res = chain1.derive_private(i)
|
|
|
+ assert res.address == vectors_derive['bech32'][i]
|
|
|
+
|
|
|
+ vmsg('')
|
|
|
+ return True
|
|
|
+
|
|
|
+ def derive_addrfmt(self,name,ut):
|
|
|
+ vmsg('seed: 98831F3A (default derivation)')
|
|
|
+
|
|
|
+ m = MasterNode(cfg,self._seed)
|
|
|
+
|
|
|
+ for addr_type in ('compressed','segwit','bech32'):
|
|
|
+ chk_xpub = vectors_addrfmt['pub'][addr_type]
|
|
|
+ chk_xprv = vectors_addrfmt['prv'][addr_type]
|
|
|
+
|
|
|
+ res1 = m.to_chain(idx=0,coin='btc',addr_type=addr_type).derive_public(0)
|
|
|
+ vmsg(f' {addr_type}: {res1.xpub}')
|
|
|
+ assert res1.xpub == chk_xpub
|
|
|
+
|
|
|
+ res2 = m.to_chain(idx=0,coin='btc',addr_type=addr_type).derive_private(0,False)
|
|
|
+ vmsg(f' {addr_type}: {res2.xprv}')
|
|
|
+ assert res2.xprv == chk_xprv
|
|
|
+ assert res2.xpub == chk_xpub
|
|
|
+
|
|
|
+ assert res2.address == wif2addr(res2.cfg, res2.privkey.wif)
|
|
|
+
|
|
|
+ vmsg('')
|
|
|
+ return True
|
|
|
+
|
|
|
+ def path(self,name,ut):
|
|
|
+
|
|
|
+ for vec in vectors_bip32:
|
|
|
+ seed = bytes.fromhex(vec['seed'])
|
|
|
+ vmsg(f'Seed: {vec["seed"]}')
|
|
|
+
|
|
|
+ for n,path_str in enumerate(vec):
|
|
|
+ if path_str in ('seed','comment'):
|
|
|
+ continue
|
|
|
+
|
|
|
+ path_arg = path_str.replace("'",'H') if n % 2 else path_str
|
|
|
+ node = BipHDNode.from_path(cfg,seed,path_arg,no_path_checks=True)
|
|
|
+ vmsg(' Path {} {}'.format(pink(path_str),blue('('+node.desc+')')))
|
|
|
+
|
|
|
+ for xkey_type in ('xpub','xprv'):
|
|
|
+ vmsg(f' {getattr(node,xkey_type)}')
|
|
|
+ assert getattr(node,xkey_type) == vec[path_str][xkey_type]
|
|
|
+
|
|
|
+ vmsg('')
|
|
|
+
|
|
|
+ return True
|
|
|
+
|
|
|
+ def parse_extended(self,name,ut):
|
|
|
+ vmsg('Parsing and validating extended keys:\n')
|
|
|
+
|
|
|
+ for vec in vectors_bip32:
|
|
|
+ seed = bytes.fromhex(vec['seed'])
|
|
|
+ vmsg(f' Seed: {vec["seed"]}')
|
|
|
+
|
|
|
+ for n,path_str in enumerate(vec):
|
|
|
+ if path_str in ('seed','comment'):
|
|
|
+ continue
|
|
|
+
|
|
|
+ vmsg(' Path {}'.format(pink(path_str)))
|
|
|
+ for xkey_type in ('xpub','xprv'):
|
|
|
+ xkey = vec[path_str][xkey_type]
|
|
|
+ vmsg(f' {xkey}')
|
|
|
+ node = BipHDNode.from_extended_key(cfg,'btc',xkey)
|
|
|
+ assert getattr(node,xkey_type) == xkey
|
|
|
+
|
|
|
+ vmsg('')
|
|
|
+
|
|
|
+ return True
|
|
|
+
|
|
|
+ def multicoin(self,name,ut):
|
|
|
+ m = MasterNode(cfg,self._seed)
|
|
|
+
|
|
|
+ fs = ' {:6} {:10} {}'
|
|
|
+ vmsg(fs.format('COIN','ADDR_TYPE','ADDR'))
|
|
|
+ for id_str,addr_chk in vectors_multicoin.items():
|
|
|
+ ss = id_str.split('_')
|
|
|
+ coin = ss[0]
|
|
|
+ addr_type = ss[1] if len(ss) == 2 else None
|
|
|
+ if coin not in BipHDConfig.supported_coins:
|
|
|
+ vmsg(gray(fs.format(coin.upper(), (addr_type or ''), '[not supported yet]')))
|
|
|
+ continue
|
|
|
+ node = m.to_chain(idx=0,coin=coin,addr_type=addr_type).derive_private(0)
|
|
|
+ xpub_parsed = node.key_extended(public=True)
|
|
|
+ xprv_parsed = node.key_extended(public=False)
|
|
|
+ addr = node.address
|
|
|
+ at_arg = 'compressed' if coin == 'doge' else None
|
|
|
+ from_xpub = BipHDNode.from_extended_key(node.cfg.base_cfg, coin, xpub_parsed.base58, addr_type=at_arg)
|
|
|
+ from_xprv = BipHDNode.from_extended_key(node.cfg.base_cfg, coin, xprv_parsed.base58, addr_type=at_arg)
|
|
|
+ assert from_xpub.xpub == node.xpub, f'{from_xpub.xpub=} != {node.xpub}'
|
|
|
+ assert from_xprv.xpub == node.xpub, f'{from_xprv.xpub=} != {node.xpub}'
|
|
|
+ assert from_xpub.address == addr, f'{from_xpub.address} != {addr}'
|
|
|
+ assert from_xprv.address == addr, f'{from_xprv.address} != {addr}'
|
|
|
+ addr_from_wif = wif2addr(node.cfg, node.privkey.wif)
|
|
|
+ proto = node.cfg.base_cfg._proto
|
|
|
+ if proto.base_proto == 'Ethereum':
|
|
|
+ addr = proto.checksummed_addr(node.address)
|
|
|
+ addr_from_wif = proto.checksummed_addr(addr_from_wif)
|
|
|
+ vmsg(fs.format(coin.upper(), (addr_type or 'auto'), addr))
|
|
|
+ assert addr == addr_chk, f'{addr} != {addr_chk}'
|
|
|
+ assert addr == addr_from_wif, f'{addr} != {addr_from_wif}'
|
|
|
+
|
|
|
+ vmsg('')
|
|
|
+ return True
|
|
|
+
|
|
|
+ def errors(self,name,ut):
|
|
|
+ vmsg('Checking error handling:')
|
|
|
+
|
|
|
+ m = MasterNode(cfg,self._seed)
|
|
|
+ m_btc = m.init_cfg(coin='btc',addr_type='bech32')
|
|
|
+
|
|
|
+ purpose = m_btc.derive_private()
|
|
|
+ coin_type = purpose.derive_private()
|
|
|
+ acct = coin_type.derive_private(idx=0)
|
|
|
+ chain = acct.derive_private(idx=0,hardened=False)
|
|
|
+
|
|
|
+ def bad01():
|
|
|
+ m.to_chain(idx=0,coin='erq',addr_type='C')
|
|
|
+ def bad02():
|
|
|
+ m_btc.derive_private(idx=0)
|
|
|
+ def bad03():
|
|
|
+ m_btc.derive_private(hardened=False)
|
|
|
+ def bad04():
|
|
|
+ purpose.derive_private(idx=8)
|
|
|
+ def bad05():
|
|
|
+ purpose.derive_private(hardened=False)
|
|
|
+ def bad06():
|
|
|
+ coin_type.derive_private() # no acct idx
|
|
|
+ def bad08():
|
|
|
+ m_btc.derive_public() # must be private
|
|
|
+ def bad09():
|
|
|
+ coin_type.derive_private(idx=8,hardened=False)
|
|
|
+ def bad10():
|
|
|
+ acct.derive_private()
|
|
|
+ def bad11():
|
|
|
+ chain.derive_private()
|
|
|
+ def bad12():
|
|
|
+ chain.derive_private(hardened=True,idx=3)
|
|
|
+
|
|
|
+ bad_data = (
|
|
|
+ ('unsupported coin', 'ValueError', 'not supported', bad01),
|
|
|
+ ('depth 1 (purpose): idx not None', 'ValueError', 'index for path comp', bad02),
|
|
|
+ ('depth 1 (purpose): hardened False', 'ValueError', 'value for ‘hardened’', bad03),
|
|
|
+ ('depth 2 (coin type): idx mismatch', 'ValueError', 'index 8 at depth', bad04),
|
|
|
+ ('depth 2 (coin type): hardened False', 'ValueError', 'value for ‘hardened’', bad05),
|
|
|
+ ('depth 3 (account): idx not set', 'ValueError', 'must be set', bad06),
|
|
|
+ ('depth 1 (purpose): node not hardened', 'ValueError', 'must be hardened', bad08),
|
|
|
+ ('depth 3 (account): node not hardened', 'ValueError', 'value for ‘hardened’', bad09),
|
|
|
+ ('depth 4 (chain): idx not set', 'ValueError', 'must be either 0', bad10),
|
|
|
+ ('depth 5 (leaf node): idx not set', 'ValueError', 'must be set', bad11),
|
|
|
+ ('depth 5 (leaf node): hardened True', 'ValueError', 'must be None', bad12),
|
|
|
+ )
|
|
|
+
|
|
|
+ ut.process_bad_data(bad_data,pfx='')
|
|
|
+ vmsg('')
|
|
|
+ return True
|
|
|
+
|
|
|
+ def parse_extended_errors(self,name,ut):
|
|
|
+ vmsg('Parsing and validating invalid extended keys:')
|
|
|
+ vec = vectors_bip32_invalid
|
|
|
+ func = [lambda m=n: BipHDNode.from_extended_key(cfg,'btc',vec[m][0]) for n in range(len(vec))]
|
|
|
+ exc = (
|
|
|
+ 'first byte for public',
|
|
|
+ 'first byte for private',
|
|
|
+ 'first byte for public',
|
|
|
+ 'first byte for private',
|
|
|
+ 'first byte for public',
|
|
|
+ 'first byte for private',
|
|
|
+ 'non-zero parent fingerprint',
|
|
|
+ 'non-zero parent fingerprint',
|
|
|
+ 'non-zero index',
|
|
|
+ 'non-zero index',
|
|
|
+ 'unrecognized extended key v',
|
|
|
+ 'unrecognized extended key v',
|
|
|
+ 'private key is zero!',
|
|
|
+ 'private key >= group order!',
|
|
|
+ 'Public key could not be parsed', # extmod
|
|
|
+ 'incorrect checksum',
|
|
|
+ )
|
|
|
+ ut.process_bad_data([(vec[n][1], 'ValueError', exc[n], func[n]) for n in range(len(vec))],pfx='')
|
|
|
+ vmsg('')
|
|
|
+ return True
|