You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
530 lines
31 KiB
530 lines
31 KiB
import unittest
|
|
from unittest import mock
|
|
import shutil
|
|
import tempfile
|
|
|
|
import lib
|
|
from lib import storage, bitcoin, keystore, constants
|
|
from lib.transaction import Transaction
|
|
from lib.simple_config import SimpleConfig
|
|
from lib.wallet import TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT
|
|
|
|
from plugins.trustedcoin import trustedcoin
|
|
|
|
from . import TestCaseForTestnet
|
|
|
|
|
|
class WalletIntegrityHelper:
|
|
|
|
gap_limit = 1 # make tests run faster
|
|
|
|
@classmethod
|
|
def check_seeded_keystore_sanity(cls, test_obj, ks):
|
|
test_obj.assertTrue(ks.is_deterministic())
|
|
test_obj.assertFalse(ks.is_watching_only())
|
|
test_obj.assertFalse(ks.can_import())
|
|
test_obj.assertTrue(ks.has_seed())
|
|
|
|
@classmethod
|
|
def check_xpub_keystore_sanity(cls, test_obj, ks):
|
|
test_obj.assertTrue(ks.is_deterministic())
|
|
test_obj.assertTrue(ks.is_watching_only())
|
|
test_obj.assertFalse(ks.can_import())
|
|
test_obj.assertFalse(ks.has_seed())
|
|
|
|
@classmethod
|
|
def create_standard_wallet(cls, ks, gap_limit=None):
|
|
store = storage.WalletStorage('if_this_exists_mocking_failed_648151893')
|
|
store.put('keystore', ks.dump())
|
|
store.put('gap_limit', gap_limit or cls.gap_limit)
|
|
w = lib.wallet.Standard_Wallet(store)
|
|
w.synchronize()
|
|
return w
|
|
|
|
@classmethod
|
|
def create_multisig_wallet(cls, ks1, ks2, ks3=None, gap_limit=None):
|
|
"""Creates a 2-of-2 or 2-of-3 multisig wallet."""
|
|
store = storage.WalletStorage('if_this_exists_mocking_failed_648151893')
|
|
store.put('x%d/' % 1, ks1.dump())
|
|
store.put('x%d/' % 2, ks2.dump())
|
|
if ks3 is None:
|
|
multisig_type = "%dof%d" % (2, 2)
|
|
else:
|
|
multisig_type = "%dof%d" % (2, 3)
|
|
store.put('x%d/' % 3, ks3.dump())
|
|
store.put('wallet_type', multisig_type)
|
|
store.put('gap_limit', gap_limit or cls.gap_limit)
|
|
w = lib.wallet.Multisig_Wallet(store)
|
|
w.synchronize()
|
|
return w
|
|
|
|
|
|
# TODO passphrase/seed_extension
|
|
class TestWalletKeystoreAddressIntegrityForMainnet(unittest.TestCase):
|
|
|
|
@mock.patch.object(storage.WalletStorage, '_write')
|
|
def test_electrum_seed_standard(self, mock_write):
|
|
seed_words = 'cycle rocket west magnet parrot shuffle foot correct salt library feed song'
|
|
self.assertEqual(bitcoin.seed_type(seed_words), 'standard')
|
|
|
|
ks = keystore.from_seed(seed_words, '', False)
|
|
|
|
WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks)
|
|
self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore))
|
|
|
|
self.assertEqual(ks.xprv, 'xprv9s21ZrQH143K32jECVM729vWgGq4mUDJCk1ozqAStTphzQtCTuoFmFafNoG1g55iCnBTXUzz3zWnDb5CVLGiFvmaZjuazHDL8a81cPQ8KL6')
|
|
self.assertEqual(ks.xpub, 'xpub661MyMwAqRbcFWohJWt7PHsFEJfZAvw9ZxwQoDa4SoMgsDDM1T7WK3u9E4edkC4ugRnZ8E4xDZRpk8Rnts3Nbt97dPwT52CwBdDWroaZf8U')
|
|
|
|
w = WalletIntegrityHelper.create_standard_wallet(ks)
|
|
self.assertEqual(w.txin_type, 'p2pkh')
|
|
|
|
self.assertEqual(w.get_receiving_addresses()[0], '1NNkttn1YvVGdqBW4PR6zvc3Zx3H5owKRf')
|
|
self.assertEqual(w.get_change_addresses()[0], '1KSezYMhAJMWqFbVFB2JshYg69UpmEXR4D')
|
|
|
|
@mock.patch.object(storage.WalletStorage, '_write')
|
|
def test_electrum_seed_segwit(self, mock_write):
|
|
seed_words = 'bitter grass shiver impose acquire brush forget axis eager alone wine silver'
|
|
self.assertEqual(bitcoin.seed_type(seed_words), 'segwit')
|
|
|
|
ks = keystore.from_seed(seed_words, '', False)
|
|
|
|
WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks)
|
|
self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore))
|
|
|
|
self.assertEqual(ks.xprv, 'zprvAZswDvNeJeha8qZ8g7efN3FXYVJLaEUsE9TW6qXDEbVe74AZ75c2sZFZXPNFzxnhChDQ89oC8C5AjWwHmH1HeRKE1c4kKBQAmjUDdKDUZw2')
|
|
self.assertEqual(ks.xpub, 'zpub6nsHdRuY92FsMKdbn9BfjBCG6X8pyhCibNP6uDvpnw2cyrVhecvHRMa3Ne8kdJZxjxgwnpbHLkcR4bfnhHy6auHPJyDTQ3kianeuVLdkCYQ')
|
|
|
|
w = WalletIntegrityHelper.create_standard_wallet(ks)
|
|
self.assertEqual(w.txin_type, 'p2wpkh')
|
|
|
|
self.assertEqual(w.get_receiving_addresses()[0], 'bc1q3g5tmkmlvxryhh843v4dz026avatc0zzr6h3af')
|
|
self.assertEqual(w.get_change_addresses()[0], 'bc1qdy94n2q5qcp0kg7v9yzwe6wvfkhnvyzje7nx2p')
|
|
|
|
@mock.patch.object(storage.WalletStorage, '_write')
|
|
def test_electrum_seed_old(self, mock_write):
|
|
seed_words = 'powerful random nobody notice nothing important anyway look away hidden message over'
|
|
self.assertEqual(bitcoin.seed_type(seed_words), 'old')
|
|
|
|
ks = keystore.from_seed(seed_words, '', False)
|
|
|
|
WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks)
|
|
self.assertTrue(isinstance(ks, keystore.Old_KeyStore))
|
|
|
|
self.assertEqual(ks.mpk, 'e9d4b7866dd1e91c862aebf62a49548c7dbf7bcc6e4b7b8c9da820c7737968df9c09d5a3e271dc814a29981f81b3faaf2737b551ef5dcc6189cf0f8252c442b3')
|
|
|
|
w = WalletIntegrityHelper.create_standard_wallet(ks)
|
|
self.assertEqual(w.txin_type, 'p2pkh')
|
|
|
|
self.assertEqual(w.get_receiving_addresses()[0], '1FJEEB8ihPMbzs2SkLmr37dHyRFzakqUmo')
|
|
self.assertEqual(w.get_change_addresses()[0], '1KRW8pH6HFHZh889VDq6fEKvmrsmApwNfe')
|
|
|
|
@mock.patch.object(storage.WalletStorage, '_write')
|
|
def test_electrum_seed_2fa(self, mock_write):
|
|
seed_words = 'kiss live scene rude gate step hip quarter bunker oxygen motor glove'
|
|
self.assertEqual(bitcoin.seed_type(seed_words), '2fa')
|
|
|
|
xprv1, xpub1, xprv2, xpub2 = trustedcoin.TrustedCoinPlugin.xkeys_from_seed(seed_words, '')
|
|
|
|
ks1 = keystore.from_xprv(xprv1)
|
|
self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))
|
|
self.assertEqual(ks1.xprv, 'xprv9uraXy9F3HP7i8QDqwNTBiD8Jf4bPD4Epif8cS8qbUbgeidUesyZpKmzfcSeHutsGfFnjgih7kzwTB5UQVRNB5LoXaNc8pFusKYx3KVVvYR')
|
|
self.assertEqual(ks1.xpub, 'xpub68qvwUg8sewQvcUgwxuTYr9rrgu5nfn6BwajQpYT9p8fXWxdCRHpN86UWruWJAD1ede8Sv8ERrTa22Gyc4SBfm7zFpcyoVWVBKCVwnw6s1J')
|
|
self.assertEqual(ks1.xpub, xpub1)
|
|
|
|
ks2 = keystore.from_xprv(xprv2)
|
|
self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore))
|
|
self.assertEqual(ks2.xprv, 'xprv9uraXy9F3HP7kKSiRAvLV7Nrjj7YzspDys7dvGLLu4tLZT49CEBxPWp88dHhVxvZ69SHrPQMUCWjj4Ka2z9kNvs1HAeEf3extGGeSWqEVqf')
|
|
self.assertEqual(ks2.xpub, 'xpub68qvwUg8sewQxoXBXCTLrFKbHkx3QLY5M63EiejxTQRKSFPHjmWCwK8byvZMM2wZNYA3SmxXoma3M1zxhGESHZwtB7SwrxRgKXAG8dCD2eS')
|
|
self.assertEqual(ks2.xpub, xpub2)
|
|
|
|
long_user_id, short_id = trustedcoin.get_user_id(
|
|
{'x1/': {'xpub': xpub1},
|
|
'x2/': {'xpub': xpub2}})
|
|
xpub3 = trustedcoin.make_xpub(trustedcoin.get_signing_xpub(), long_user_id)
|
|
ks3 = keystore.from_xpub(xpub3)
|
|
WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks3)
|
|
self.assertTrue(isinstance(ks3, keystore.BIP32_KeyStore))
|
|
|
|
w = WalletIntegrityHelper.create_multisig_wallet(ks1, ks2, ks3)
|
|
self.assertEqual(w.txin_type, 'p2sh')
|
|
|
|
self.assertEqual(w.get_receiving_addresses()[0], '35L8XmCDoEBKeaWRjvmZvoZvhp8BXMMMPV')
|
|
self.assertEqual(w.get_change_addresses()[0], '3PeZEcumRqHSPNN43hd4yskGEBdzXgY8Cy')
|
|
|
|
@mock.patch.object(storage.WalletStorage, '_write')
|
|
def test_bip39_seed_bip44_standard(self, mock_write):
|
|
seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial'
|
|
self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))
|
|
|
|
ks = keystore.from_bip39_seed(seed_words, '', "m/44'/0'/0'")
|
|
|
|
self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore))
|
|
|
|
self.assertEqual(ks.xprv, 'xprv9zGLcNEb3cHUKizLVBz6RYeE9bEZAVPjH2pD1DEzCnPcsemWc3d3xTao8sfhfUmDLMq6e3RcEMEvJG1Et8dvfL8DV4h7mwm9J6AJsW9WXQD')
|
|
self.assertEqual(ks.xpub, 'xpub6DFh1smUsyqmYD4obDX6ngaxhd53Zx7aeFjoobebm7vbkT6f9awJWFuGzBT9FQJEWFBL7UyhMXtYzRcwDuVbcxtv9Ce2W9eMm4KXLdvdbjv')
|
|
|
|
w = WalletIntegrityHelper.create_standard_wallet(ks)
|
|
self.assertEqual(w.txin_type, 'p2pkh')
|
|
|
|
self.assertEqual(w.get_receiving_addresses()[0], '16j7Dqk3Z9DdTdBtHcCVLaNQy9MTgywUUo')
|
|
self.assertEqual(w.get_change_addresses()[0], '1GG5bVeWgAp5XW7JLCphse14QaC4qiHyWn')
|
|
|
|
@mock.patch.object(storage.WalletStorage, '_write')
|
|
def test_bip39_seed_bip49_p2sh_segwit(self, mock_write):
|
|
seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial'
|
|
self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))
|
|
|
|
ks = keystore.from_bip39_seed(seed_words, '', "m/49'/0'/0'")
|
|
|
|
self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore))
|
|
|
|
self.assertEqual(ks.xprv, 'yprvAJEYHeNEPcyBoQYM7sGCxDiNCTX65u4ANgZuSGTrKN5YCC9MP84SBayrgaMyZV7zvkHrr3HVPTK853s2SPk4EttPazBZBmz6QfDkXeE8Zr7')
|
|
self.assertEqual(ks.xpub, 'ypub6XDth9u8DzXV1tcpDtoDKMf6kVMaVMn1juVWEesTshcX4zUVvfNgjPJLXrD9N7AdTLnbHFL64KmBn3SNaTe69iZYbYCqLCCNPZKbLz9niQ4')
|
|
|
|
w = WalletIntegrityHelper.create_standard_wallet(ks)
|
|
self.assertEqual(w.txin_type, 'p2wpkh-p2sh')
|
|
|
|
self.assertEqual(w.get_receiving_addresses()[0], '35ohQTdNykjkF1Mn9nAVEFjupyAtsPAK1W')
|
|
self.assertEqual(w.get_change_addresses()[0], '3KaBTcviBLEJajTEMstsA2GWjYoPzPK7Y7')
|
|
|
|
@mock.patch.object(storage.WalletStorage, '_write')
|
|
def test_bip39_seed_bip84_native_segwit(self, mock_write):
|
|
# test case from bip84
|
|
seed_words = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
|
|
self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))
|
|
|
|
ks = keystore.from_bip39_seed(seed_words, '', "m/84'/0'/0'")
|
|
|
|
self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore))
|
|
|
|
self.assertEqual(ks.xprv, 'zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqWvGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE')
|
|
self.assertEqual(ks.xpub, 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs')
|
|
|
|
w = WalletIntegrityHelper.create_standard_wallet(ks)
|
|
self.assertEqual(w.txin_type, 'p2wpkh')
|
|
|
|
self.assertEqual(w.get_receiving_addresses()[0], 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu')
|
|
self.assertEqual(w.get_change_addresses()[0], 'bc1q8c6fshw2dlwun7ekn9qwf37cu2rn755upcp6el')
|
|
|
|
@mock.patch.object(storage.WalletStorage, '_write')
|
|
def test_electrum_multisig_seed_standard(self, mock_write):
|
|
seed_words = 'blast uniform dragon fiscal ensure vast young utility dinosaur abandon rookie sure'
|
|
self.assertEqual(bitcoin.seed_type(seed_words), 'standard')
|
|
|
|
ks1 = keystore.from_seed(seed_words, '', True)
|
|
WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks1)
|
|
self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))
|
|
self.assertEqual(ks1.xprv, 'xprv9s21ZrQH143K3t9vo23J3hajRbzvkRLJ6Y1zFrUFAfU3t8oooMPfb7f87cn5KntgqZs5nipZkCiBFo5ZtaSD2eDo7j7CMuFV8Zu6GYLTpY6')
|
|
self.assertEqual(ks1.xpub, 'xpub661MyMwAqRbcGNEPu3aJQqXTydqR9t49Tkwb4Esrj112kw8xLthv8uybxvaki4Ygt9xiwZUQGeFTG7T2TUzR3eA4Zp3aq5RXsABHFBUrq4c')
|
|
|
|
# electrum seed: ghost into match ivory badge robot record tackle radar elbow traffic loud
|
|
ks2 = keystore.from_xpub('xpub661MyMwAqRbcGfCPEkkyo5WmcrhTq8mi3xuBS7VEZ3LYvsgY1cCFDbenT33bdD12axvrmXhuX3xkAbKci3yZY9ZEk8vhLic7KNhLjqdh5ec')
|
|
WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2)
|
|
self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore))
|
|
|
|
w = WalletIntegrityHelper.create_multisig_wallet(ks1, ks2)
|
|
self.assertEqual(w.txin_type, 'p2sh')
|
|
|
|
self.assertEqual(w.get_receiving_addresses()[0], '32ji3QkAgXNz6oFoRfakyD3ys1XXiERQYN')
|
|
self.assertEqual(w.get_change_addresses()[0], '36XWwEHrrVCLnhjK5MrVVGmUHghr9oWTN1')
|
|
|
|
@mock.patch.object(storage.WalletStorage, '_write')
|
|
def test_electrum_multisig_seed_segwit(self, mock_write):
|
|
seed_words = 'snow nest raise royal more walk demise rotate smooth spirit canyon gun'
|
|
self.assertEqual(bitcoin.seed_type(seed_words), 'segwit')
|
|
|
|
ks1 = keystore.from_seed(seed_words, '', True)
|
|
WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks1)
|
|
self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))
|
|
self.assertEqual(ks1.xprv, 'ZprvAjxLRqPiDfPDxXrm8JvcoCGRAW6xUtktucG6AMtdzaEbTEJN8qcECvujfhtDU3jLJ9g3Dr3Gz5m1ypfMs8iSUh62gWyHZ73bYLRWyeHf6y4')
|
|
self.assertEqual(ks1.xpub, 'Zpub6xwgqLvc42wXB1wEELTdALD9iXwStMUkGqBgxkJFYumaL2dWgNvUkjEDWyDFZD3fZuDWDzd1KQJ4NwVHS7hs6H6QkpNYSShfNiUZsgMdtNg')
|
|
|
|
# electrum seed: hedgehog sunset update estate number jungle amount piano friend donate upper wool
|
|
ks2 = keystore.from_xpub('Zpub6y4oYeETXAbzLNg45wcFDGwEG3vpgsyMJybiAfi2pJtNF3i3fJVxK2BeZJaw7VeKZm192QHvXP3uHDNpNmNDbQft9FiMzkKUhNXQafUMYUY')
|
|
WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2)
|
|
self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore))
|
|
|
|
w = WalletIntegrityHelper.create_multisig_wallet(ks1, ks2)
|
|
self.assertEqual(w.txin_type, 'p2wsh')
|
|
|
|
self.assertEqual(w.get_receiving_addresses()[0], 'bc1qvzezdcv6vs5h45ugkavp896e0nde5c5lg5h0fwe2xyfhnpkxq6gq7pnwlc')
|
|
self.assertEqual(w.get_change_addresses()[0], 'bc1qxqf840dqswcmu7a8v82fj6ej0msx08flvuy6kngr7axstjcaq6us9hrehd')
|
|
|
|
@mock.patch.object(storage.WalletStorage, '_write')
|
|
def test_bip39_multisig_seed_bip45_standard(self, mock_write):
|
|
seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial'
|
|
self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))
|
|
|
|
ks1 = keystore.from_bip39_seed(seed_words, '', "m/45'/0")
|
|
self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))
|
|
self.assertEqual(ks1.xprv, 'xprv9vyEFyXf7pYVv4eDU3hhuCEAHPHNGuxX73nwtYdpbLcqwJCPwFKknAK8pHWuHHBirCzAPDZ7UJHrYdhLfn1NkGp9rk3rVz2aEqrT93qKRD9')
|
|
self.assertEqual(ks1.xpub, 'xpub69xafV4YxC6o8Yiga5EiGLAtqR7rgNgNUGiYgw3S9g9pp6XYUne1KxdcfYtxwmA3eBrzMFuYcNQKfqsXCygCo4GxQFHfywxpUbKNfYvGJka')
|
|
|
|
# bip39 seed: tray machine cook badge night page project uncover ritual toward person enact
|
|
# der: m/45'/0
|
|
ks2 = keystore.from_xpub('xpub6B26nSWddbWv7J3qQn9FbwPPQktSBdPQfLfHhRK4375QoZq8fvM8rQey1koGSTxC5xVoMzNMaBETMUmCqmXzjc8HyAbN7LqrvE4ovGRwNGg')
|
|
WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2)
|
|
self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore))
|
|
|
|
w = WalletIntegrityHelper.create_multisig_wallet(ks1, ks2)
|
|
self.assertEqual(w.txin_type, 'p2sh')
|
|
|
|
self.assertEqual(w.get_receiving_addresses()[0], '3JPTQ2nitVxXBJ1yhMeDwH6q417UifE3bN')
|
|
self.assertEqual(w.get_change_addresses()[0], '3FGyDuxgUDn2pSZe5xAJH1yUwSdhzDMyEE')
|
|
|
|
@mock.patch.object(storage.WalletStorage, '_write')
|
|
def test_bip39_multisig_seed_p2sh_segwit(self, mock_write):
|
|
# bip39 seed: pulse mixture jazz invite dune enrich minor weapon mosquito flight fly vapor
|
|
# der: m/49'/0'/0'
|
|
# NOTE: there is currently no bip43 standard derivation path for p2wsh-p2sh
|
|
ks1 = keystore.from_xprv('YprvAUXFReVvDjrPerocC3FxVH748sJUTvYjkAhtKop5VnnzVzMEHr1CHrYQKZwfJn1As3X4LYMav6upxd5nDiLb6SCjRZrBH76EFvyQAG4cn79')
|
|
self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))
|
|
self.assertEqual(ks1.xpub, 'Ypub6hWbqA2p47QgsLt5J4nxrR3ngu8xsPGb7PdV8CDh48KyNngNqPKSqertAqYhQ4umELu1UsZUCYfj9XPA6AdSMZWDZQobwF7EJ8uNrECaZg1')
|
|
|
|
# bip39 seed: slab mixture skin evoke harsh tattoo rare crew sphere extend balcony frost
|
|
# der: m/49'/0'/0'
|
|
ks2 = keystore.from_xpub('Ypub6iNDhL4WWq5kFZcdFqHHwX4YTH4rYGp8xbndpRrY7WNZFFRfogSrL7wRTajmVHgR46AT1cqUG1mrcRd7h1WXwBsgX2QvT3zFbBCDiSDLkau')
|
|
WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2)
|
|
self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore))
|
|
|
|
w = WalletIntegrityHelper.create_multisig_wallet(ks1, ks2)
|
|
self.assertEqual(w.txin_type, 'p2wsh-p2sh')
|
|
|
|
self.assertEqual(w.get_receiving_addresses()[0], '35LeC45QgCVeRor1tJD6LiDgPbybBXisns')
|
|
self.assertEqual(w.get_change_addresses()[0], '39RhtDchc6igmx5tyoimhojFL1ZbQBrXa6')
|
|
|
|
|
|
class TestWalletKeystoreAddressIntegrityForTestnet(TestCaseForTestnet):
|
|
|
|
@mock.patch.object(storage.WalletStorage, '_write')
|
|
def test_bip39_multisig_seed_p2sh_segwit_testnet(self, mock_write):
|
|
# bip39 seed: finish seminar arrange erosion sunny coil insane together pretty lunch lunch rose
|
|
# der: m/49'/1'/0'
|
|
# NOTE: there is currently no bip43 standard derivation path for p2wsh-p2sh
|
|
ks1 = keystore.from_xprv('Uprv9BEixD3As2LK5h6G2SNT3cTqbZpsWYPceKTSuVAm1yuSybxSvQz2MV1o8cHTtctQmj4HAenb3eh5YJv4YRZjv35i8fofVnNbs4Dd2B4i5je')
|
|
self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))
|
|
self.assertEqual(ks1.xpub, 'Upub5QE5Mia4hPtcJBAj8TuTQkQa9bfMv17U1YP3hsaNaKSRrQHbTxJGuHLGyv3MbKZixuPyjfXGUdbTjE4KwyFcX8YD7PX5ybTDbP11UT8UpZR')
|
|
|
|
# bip39 seed: square page wood spy oil story rebel give milk screen slide shuffle
|
|
# der: m/49'/1'/0'
|
|
ks2 = keystore.from_xpub('Upub5QRzUGRJuWJe5MxGzwgQAeyJjzcdGTXkkq77w6EfBkCyf5iWppSaZ4caY2MgWcU9LP4a4uE5apUFN4wLoENoe9tpu26mrUxeGsH84dN3JFh')
|
|
WalletIntegrityHelper.check_xpub_keystore_sanity(self, ks2)
|
|
self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore))
|
|
|
|
w = WalletIntegrityHelper.create_multisig_wallet(ks1, ks2)
|
|
self.assertEqual(w.txin_type, 'p2wsh-p2sh')
|
|
|
|
self.assertEqual(w.get_receiving_addresses()[0], '2MzsfTfTGomPRne6TkctMmoDj6LwmVkDrMt')
|
|
self.assertEqual(w.get_change_addresses()[0], '2NFp9w8tbYYP9Ze2xQpeYBJQjx3gbXymHX7')
|
|
|
|
|
|
class TestWalletSending(TestCaseForTestnet):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.electrum_path = tempfile.mkdtemp()
|
|
cls.config = SimpleConfig({'electrum_path': cls.electrum_path})
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
super().tearDownClass()
|
|
shutil.rmtree(cls.electrum_path)
|
|
|
|
def create_standard_wallet_from_seed(self, seed_words):
|
|
ks = keystore.from_seed(seed_words, '', False)
|
|
return WalletIntegrityHelper.create_standard_wallet(ks, gap_limit=2)
|
|
|
|
@mock.patch.object(storage.WalletStorage, '_write')
|
|
def test_sending_between_p2wpkh_and_compressed_p2pkh(self, mock_write):
|
|
wallet1 = self.create_standard_wallet_from_seed('bitter grass shiver impose acquire brush forget axis eager alone wine silver')
|
|
wallet2 = self.create_standard_wallet_from_seed('cycle rocket west magnet parrot shuffle foot correct salt library feed song')
|
|
|
|
# bootstrap wallet1
|
|
funding_tx = Transaction('01000000014576dacce264c24d81887642b726f5d64aa7825b21b350c7b75a57f337da6845010000006b483045022100a3f8b6155c71a98ad9986edd6161b20d24fad99b6463c23b463856c0ee54826d02200f606017fd987696ebbe5200daedde922eee264325a184d5bbda965ba5160821012102e5c473c051dae31043c335266d0ef89c1daab2f34d885cc7706b267f3269c609ffffffff0240420f00000000001600148a28bddb7f61864bdcf58b2ad13d5aeb3abc3c42a2ddb90e000000001976a914c384950342cb6f8df55175b48586838b03130fad88ac00000000')
|
|
funding_txid = funding_tx.txid()
|
|
funding_output_value = 1000000
|
|
self.assertEqual('add2535aedcbb5ba79cc2260868bb9e57f328738ca192937f2c92e0e94c19203', funding_txid)
|
|
wallet1.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
|
|
|
# wallet1 -> wallet2
|
|
outputs = [(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 250000)]
|
|
tx = wallet1.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
|
|
|
|
self.assertTrue(tx.is_complete())
|
|
self.assertTrue(tx.is_segwit())
|
|
self.assertEqual(1, len(tx.inputs()))
|
|
tx_copy = Transaction(tx.serialize())
|
|
self.assertEqual(wallet1.is_mine(tx.inputs()[0]['address']), wallet1.is_mine(tx_copy.inputs()[0]['address']))
|
|
self.assertTrue(wallet1.is_mine(tx.inputs()[0]['address']))
|
|
self.assertEqual(wallet1.txin_type, tx_copy.inputs()[0]['type'])
|
|
self.assertEqual(tx.wtxid(), tx_copy.wtxid())
|
|
self.assertEqual('3c06ae4d9be8226a472b3e7f7c127c7e3016f525d658d26106b80b4c7e3228e2', tx_copy.txid())
|
|
|
|
wallet1.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED) # TX_HEIGHT_UNCONF_PARENT but nvm
|
|
wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
|
|
|
# wallet2 -> wallet1
|
|
outputs = [(bitcoin.TYPE_ADDRESS, wallet1.get_receiving_address(), 100000)]
|
|
tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
|
|
|
|
self.assertTrue(tx.is_complete())
|
|
self.assertFalse(tx.is_segwit())
|
|
self.assertEqual(1, len(tx.inputs()))
|
|
tx_copy = Transaction(tx.serialize())
|
|
self.assertEqual(wallet2.is_mine(tx.inputs()[0]['address']), wallet2.is_mine(tx_copy.inputs()[0]['address']))
|
|
self.assertTrue(wallet2.is_mine(tx.inputs()[0]['address']))
|
|
self.assertEqual(wallet2.txin_type, tx_copy.inputs()[0]['type'])
|
|
self.assertEqual(tx.wtxid(), tx_copy.wtxid())
|
|
self.assertEqual('5f25707571eb776bdf14142f9966bf2a681906e0a79501edbb99a972c2ceb972', tx_copy.txid())
|
|
|
|
wallet1.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
|
wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
|
|
|
# wallet level checks
|
|
self.assertEqual((0, funding_output_value - 250000 - 5000 + 100000, 0), wallet1.get_balance())
|
|
self.assertEqual((0, 250000 - 5000 - 100000, 0), wallet2.get_balance())
|
|
|
|
@mock.patch.object(storage.WalletStorage, '_write')
|
|
def test_sending_between_p2sh_2of3_and_uncompressed_p2pkh(self, mock_write):
|
|
wallet1a = WalletIntegrityHelper.create_multisig_wallet(
|
|
keystore.from_seed('blast uniform dragon fiscal ensure vast young utility dinosaur abandon rookie sure', '', True),
|
|
keystore.from_xpub('tpubD6NzVbkrYhZ4YTPEgwk4zzr8wyo7pXGmbbVUnfYNtx6SgAMF5q3LN3Kch58P9hxGNsTmP7Dn49nnrmpE6upoRb1Xojg12FGLuLHkVpVtS44'),
|
|
keystore.from_xpub('tpubD6NzVbkrYhZ4XJzYkhsCbDCcZRmDAKSD7bXi9mdCni7acVt45fxbTVZyU6jRGh29ULKTjoapkfFsSJvQHitcVKbQgzgkkYsAmaovcro7Mhf'),
|
|
gap_limit=2
|
|
)
|
|
wallet1b = WalletIntegrityHelper.create_multisig_wallet(
|
|
keystore.from_seed('cycle rocket west magnet parrot shuffle foot correct salt library feed song', '', True),
|
|
keystore.from_xpub('tpubD6NzVbkrYhZ4YTPEgwk4zzr8wyo7pXGmbbVUnfYNtx6SgAMF5q3LN3Kch58P9hxGNsTmP7Dn49nnrmpE6upoRb1Xojg12FGLuLHkVpVtS44'),
|
|
keystore.from_xpub('tpubD6NzVbkrYhZ4YARFMEZPckrqJkw59GZD1PXtQnw14ukvWDofR7Z1HMeSCxfYEZVvg4VdZ8zGok5VxHwdrLqew5cMdQntWc5mT7mh1CSgrnX'),
|
|
gap_limit=2
|
|
)
|
|
# ^ third seed: ghost into match ivory badge robot record tackle radar elbow traffic loud
|
|
wallet2 = self.create_standard_wallet_from_seed('powerful random nobody notice nothing important anyway look away hidden message over')
|
|
|
|
# bootstrap wallet1
|
|
funding_tx = Transaction('010000000001014121f99dc02f0364d2dab3d08905ff4c36fc76c55437fd90b769c35cc18618280100000000fdffffff02d4c22d00000000001600143fd1bc5d32245850c8cb5be5b09c73ccbb9a0f75001bb7000000000017a91480c2353f6a7bc3c71e99e062655b19adb3dd2e4887024830450221008781c78df0c9d4b5ea057333195d5d76bc29494d773f14fa80e27d2f288b2c360220762531614799b6f0fb8d539b18cb5232ab4253dd4385435157b28a44ff63810d0121033de77d21926e09efd04047ae2d39dbd3fb9db446e8b7ed53e0f70f9c9478f735dac11300')
|
|
funding_txid = funding_tx.txid()
|
|
funding_output_value = 12000000
|
|
self.assertEqual('b25cd55687c9e528c2cfd546054f35fb6741f7cf32d600f07dfecdf2e1d42071', funding_txid)
|
|
wallet1a.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
|
|
|
# wallet1 -> wallet2
|
|
outputs = [(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 370000)]
|
|
tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
|
|
tx = Transaction(tx.serialize()) # simulates moving partial txn between cosigners
|
|
self.assertFalse(tx.is_complete())
|
|
wallet1b.sign_transaction(tx, password=None)
|
|
|
|
self.assertTrue(tx.is_complete())
|
|
self.assertFalse(tx.is_segwit())
|
|
self.assertEqual(1, len(tx.inputs()))
|
|
tx_copy = Transaction(tx.serialize())
|
|
self.assertEqual(wallet1a.is_mine(tx.inputs()[0]['address']), wallet1a.is_mine(tx_copy.inputs()[0]['address']))
|
|
self.assertTrue(wallet1a.is_mine(tx.inputs()[0]['address']))
|
|
self.assertEqual(wallet1a.txin_type, tx_copy.inputs()[0]['type'])
|
|
self.assertEqual(tx.wtxid(), tx_copy.wtxid())
|
|
self.assertEqual('26f3bdd0402e1cff19126244ebe3d32722cef0db507c7229ca8754f5e06ef25d', tx_copy.txid())
|
|
|
|
wallet1a.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
|
wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
|
|
|
# wallet2 -> wallet1
|
|
outputs = [(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 100000)]
|
|
tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
|
|
|
|
self.assertTrue(tx.is_complete())
|
|
self.assertFalse(tx.is_segwit())
|
|
self.assertEqual(1, len(tx.inputs()))
|
|
tx_copy = Transaction(tx.serialize())
|
|
self.assertEqual(wallet2.is_mine(tx.inputs()[0]['address']), wallet2.is_mine(tx_copy.inputs()[0]['address']))
|
|
self.assertTrue(wallet2.is_mine(tx.inputs()[0]['address']))
|
|
self.assertEqual(wallet2.txin_type, tx_copy.inputs()[0]['type'])
|
|
self.assertEqual(tx.wtxid(), tx_copy.wtxid())
|
|
self.assertEqual('c573b3f8464a4ed40dfc79d0889a780f44e917beef7a75883b2427c2987f3e95', tx_copy.txid())
|
|
|
|
wallet1a.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
|
wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
|
|
|
# wallet level checks
|
|
self.assertEqual((0, funding_output_value - 370000 - 5000 + 100000, 0), wallet1a.get_balance())
|
|
self.assertEqual((0, 370000 - 5000 - 100000, 0), wallet2.get_balance())
|
|
|
|
@mock.patch.object(storage.WalletStorage, '_write')
|
|
def test_sending_between_p2wsh_2of3_and_p2wsh_p2sh_2of2(self, mock_write):
|
|
wallet1a = WalletIntegrityHelper.create_multisig_wallet(
|
|
keystore.from_seed('bitter grass shiver impose acquire brush forget axis eager alone wine silver', '', True),
|
|
keystore.from_xpub('Vpub5fcdcgEwTJmbmqAktuK8Kyq92fMf7sWkcP6oqAii2tG47dNbfkGEGUbfS9NuZaRywLkHE6EmUksrqo32ZL3ouLN1HTar6oRiHpDzKMAF1tf'),
|
|
keystore.from_xpub('Vpub5fjkKyYnvSS4wBuakWTkNvZDaBM2vQ1MeXWq368VJHNr2eT8efqhpmZ6UUkb7s2dwCXv2Vuggjdhk4vZVyiAQTwUftvff73XcUGq2NQmWra'),
|
|
gap_limit=2
|
|
)
|
|
wallet1b = WalletIntegrityHelper.create_multisig_wallet(
|
|
keystore.from_seed('snow nest raise royal more walk demise rotate smooth spirit canyon gun', '', True),
|
|
keystore.from_xpub('Vpub5fjkKyYnvSS4wBuakWTkNvZDaBM2vQ1MeXWq368VJHNr2eT8efqhpmZ6UUkb7s2dwCXv2Vuggjdhk4vZVyiAQTwUftvff73XcUGq2NQmWra'),
|
|
keystore.from_xpub('Vpub5gSKXzxK7FeKQedu2q1z9oJWxqvX72AArW3HSWpEhc8othDH8xMDu28gr7gf17sp492BuJod8Tn7anjvJrKpETwqnQqX7CS8fcYyUtedEMk'),
|
|
gap_limit=2
|
|
)
|
|
# ^ third seed: hedgehog sunset update estate number jungle amount piano friend donate upper wool
|
|
wallet2a = WalletIntegrityHelper.create_multisig_wallet(
|
|
# bip39: finish seminar arrange erosion sunny coil insane together pretty lunch lunch rose, der: m/1234'/1'/0', p2wsh-p2sh multisig
|
|
keystore.from_xprv('Uprv9CvELvByqm8k2dpecJVjgLMX1z5DufEjY4fBC5YvdGF5WjGCa7GVJJ2fYni1tyuF7Hw83E6W2ZBjAhaFLZv2ri3rEsubkCd5avg4EHKoDBN'),
|
|
keystore.from_xpub('Upub5Qb8ik4Cnu8g97KLXKgVXHqY6tH8emQvqtBncjSKsyfTZuorPtTZgX7ovKKZHuuVGBVd1MTTBkWez1XXt2weN1sWBz6SfgRPQYEkNgz81QF'),
|
|
gap_limit=2
|
|
)
|
|
wallet2b = WalletIntegrityHelper.create_multisig_wallet(
|
|
# bip39: square page wood spy oil story rebel give milk screen slide shuffle, der: m/1234'/1'/0', p2wsh-p2sh multisig
|
|
keystore.from_xprv('Uprv9BbnKEXJxXaNvdEsRJ9VA9toYrSeFJh5UfGBpM2iKe8Uh7UhrM9K8ioL53s8gvCoGfirHHaqpABDAE7VUNw8LNU1DMJKVoWyeNKu9XcDC19'),
|
|
keystore.from_xpub('Upub5RuakRisg8h3F7u7iL2k3UJFa1uiK7xauHamzTxYBbn4PXbM7eajr6M9Q2VCr6cVGhfhqWQqxnABvtSATuVM1xzxk4nA189jJwzaMn1QX7V'),
|
|
gap_limit=2
|
|
)
|
|
|
|
# bootstrap wallet1
|
|
funding_tx = Transaction('01000000000101a41aae475d026c9255200082c7fad26dc47771275b0afba238dccda98a597bd20000000000fdffffff02400d0300000000002200203c43ac80d6e3015cf378bf6bac0c22456723d6050bef324ec641e7762440c63c9dcd410000000000160014824626055515f3ed1d2cfc9152d2e70685c71e8f02483045022100b9f39fad57d07ce1e18251424034f21f10f20e59931041b5167ae343ce973cf602200fefb727fa0ffd25b353f1bcdae2395898fe407b692c62f5885afbf52fa06f5701210301a28f68511ace43114b674371257bb599fd2c686c4b19544870b1799c954b40e9c11300')
|
|
funding_txid = funding_tx.txid()
|
|
funding_output_value = 200000
|
|
self.assertEqual('d2bd6c9d332db8e2c50aa521cd50f963fba214645aab2f7556e061a412103e21', funding_txid)
|
|
wallet1a.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
|
|
|
# wallet1 -> wallet2
|
|
outputs = [(bitcoin.TYPE_ADDRESS, wallet2a.get_receiving_address(), 165000)]
|
|
tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
|
|
tx = Transaction(tx.serialize()) # simulates moving partial txn between cosigners
|
|
self.assertFalse(tx.is_complete())
|
|
wallet1b.sign_transaction(tx, password=None)
|
|
|
|
self.assertTrue(tx.is_complete())
|
|
self.assertTrue(tx.is_segwit())
|
|
self.assertEqual(1, len(tx.inputs()))
|
|
tx_copy = Transaction(tx.serialize())
|
|
self.assertEqual(wallet1a.is_mine(tx.inputs()[0]['address']), wallet1a.is_mine(tx_copy.inputs()[0]['address']))
|
|
self.assertTrue(wallet1a.is_mine(tx.inputs()[0]['address']))
|
|
self.assertEqual(wallet1a.txin_type, tx_copy.inputs()[0]['type'])
|
|
self.assertEqual(tx.wtxid(), tx_copy.wtxid())
|
|
self.assertEqual('6e9c3cd8788bdb970a124ea06136d52bc01cec4f9b1e217627d5e90ebe77d049', tx_copy.txid())
|
|
|
|
wallet1a.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
|
wallet2a.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
|
|
|
# wallet2 -> wallet1
|
|
outputs = [(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 100000)]
|
|
tx = wallet2a.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
|
|
tx = Transaction(tx.serialize()) # simulates moving partial txn between cosigners
|
|
self.assertFalse(tx.is_complete())
|
|
wallet2b.sign_transaction(tx, password=None)
|
|
|
|
self.assertTrue(tx.is_complete())
|
|
self.assertTrue(tx.is_segwit())
|
|
self.assertEqual(1, len(tx.inputs()))
|
|
tx_copy = Transaction(tx.serialize())
|
|
self.assertEqual(wallet2a.is_mine(tx.inputs()[0]['address']), wallet2a.is_mine(tx_copy.inputs()[0]['address']))
|
|
self.assertTrue(wallet2a.is_mine(tx.inputs()[0]['address']))
|
|
self.assertEqual(wallet2a.txin_type, tx_copy.inputs()[0]['type'])
|
|
self.assertEqual(tx.wtxid(), tx_copy.wtxid())
|
|
self.assertEqual('84b0dcb43022385f7a10e2710e5625a2be3cd6e390387b6100b55500d5eea8f6', tx_copy.txid())
|
|
|
|
wallet1a.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
|
wallet2a.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
|
|
|
# wallet level checks
|
|
self.assertEqual((0, funding_output_value - 165000 - 5000 + 100000, 0), wallet1a.get_balance())
|
|
self.assertEqual((0, 165000 - 5000 - 100000, 0), wallet2a.get_balance())
|
|
|